做国外网站销售,wordpress付费下载功能,seo竞价培训,前几年做那个网站能致富1、神经网络语言模型从语言模型的角度来看#xff0c;N 元语言模型存在明显的缺点。首先#xff0c;模型容易受到数据稀疏的影响#xff0c;一般需要对模型进行平滑处理#xff1b;其次#xff0c;无法对长度超过 N 的上下文依赖关系进行建模。神经网络语言模型 (Neural N…1、神经网络语言模型从语言模型的角度来看N 元语言模型存在明显的缺点。首先模型容易受到数据稀疏的影响一般需要对模型进行平滑处理其次无法对长度超过 N 的上下文依赖关系进行建模。神经网络语言模型 (Neural Network Language Model )在一定程度上克服了这些问题。一方面通过引入词的分布式表示也就是词向量大大缓解了数据稀疏带来的影响另一方面利用更先进的神经网络模型结构如循环神经网络、Transformer 等)可以对长距离上下文依赖进行有效的建模。 正因为这些优异的特性加上语言模型任务本身无须人工标注数据的优势神经网络语言模型几乎己经替代 N 元语言模型。1.1、预训练任务给定一段文本w1,w2...,wn语言模型的基本任务是根据历史上下文对下一时刻的词进行预测也就是计算条件概率 P(wt l w1,w2...,wt-1。为了构建语言模型可以将其转化为以词表为类别标签集合的分类问题其输人为历史词序列w1,w2...,wt-1输出为目标词wt 。然后就可以从无标注的文本语料中构建训练数据集并通过优化该数据集上的分类损失如交叉熵损失或负对数似然损失对模型进行训练。由于监督信号来自数据自身因此这种学习方式也被称为自监督学习 (Self supervised Learning)。 在讨论模型的具体实现方式之前首先面临的一个问题是如何处理动态长度的历史词序列模型输人一个直观的想法是使用词袋表示但是这种表示方式忽略了词的顺序信息语义表达能力非常有限。前馈神经网络语言模型 ( Feed-forward Neural Network Language Model以及循环神经网络语言模型 (Recurrent Neural Network Language Model, RNNIM)分别从数据和模型的角度解决这一问题。1.2、前馈神经网络语言模型 ( Feed-forward Neural Network Language Model前馈神经网络语言模型利用了传统N元语言模型中的马尔可夫假设( Markov Assumprion对下一个词的预测只与历史中最近的n一1 个词相关。从形式上看因此模型的输人变成了长度为n-1 的定长词序列模型的任务也转化为对条件概率进行估计。 前馈神经网络由输人层、词向量层、隐含层和输出层构成。在前馈神经网络语言模型中词向量层首先对输人层长为n-1的历史词序列进行编码将每个词表示为一个低维的实数向量即词向量然后隐含层对词向量层进行线性变换并使用激活函数实现非线性映射最后输出层通过线性变换将隐含层向量映射至词表空间再通过 Softmax 函数得到在词表上的归一化的概率分布1输入层。模型的输入层由当前时刻t的历史词序列构成要为离散的符号表示。在具体实现中既可以使用每个词的独热编码 ( One-Hot Encoding)也可以直接使用每个词在词表中的位置下标。 2词向量层。词向量层将输入层中的每个词分别映射至一个低维、稠密的实值特征向量。词向量层也可以理解为一个查找表 (Lookup Table)获取词量的过程也就是根据词的素引从查找表中找出对应位置的向量的过程。 3隐含层。隐含对词向量层x进行线性变换与激活4输出层。模型的输出层对h做线性变换并利用 Softmax函数进行归一化以而获得词表 V空间内的概率分布。综上所述前馈神经网络语言模型的自由参数包含词向量矩阵E词向量层与隐含层之间的权值矩阵和偏置 模型训练完成后矩阵E则为预训练得到的静态词向量模型实现1、数据准备使用 NLTK 中提供的 Reuters 语料库该语料库被广泛用于文本分类任务其中包含 10788 篇新闻类文档每篇文档具有1个或至个类别。这里忽路数据中的文本类别信息而只使用其中的文本数据进行词向量的训练。由于在语言模型的训练过程中需要 人一些预留的标记例如句首标记、句尾标记以及在构建批次Batch时用于补齐序列长度的标记Padding token 等因此定义以下常量 BOS_TOKEN bos句首标记
EOS_TOKEN eos包尾标记
PAD_TOKEN pad # 补齐标记然后加载语料库构建数据集建立词表from collections import defaultdict, Counterclass Vocab:def __init__(self, tokensNone):self.idx_to_token list()self.token_to_idx dict()if tokens is not None:if unk not in tokens:tokens tokens [unk]for token in tokens:self.idx_to_token.append(token)self.token_to_idx[token] len(self.idx_to_token) - 1self.unk self.token_to_idx[unk]classmethoddef build(cls, text, min_freq1, reserved_tokensNone):token_freqs defaultdict(int)for sentence in text:for token in sentence:token_freqs[token] 1uniq_tokens [unk] (reserved_tokens if reserved_tokens else [])uniq_tokens [token for token, freq in token_freqs.items() \if freq min_freq and token ! unk]return cls(uniq_tokens)def __len__(self):return len(self.idx_to_token)def __getitem__(self, token):return self.token_to_idx.get(token, self.unk)def convert_tokens_to_ids(self, tokens):return [self[token] for token in tokens]def convert_ids_to_tokens(self, indices):return [self.idx_to_token[index] for index in indices]def save_vocab(vocab, path):with open(path, w) as writer:writer.write(\n.join(vocab.idx_to_token))def read_vocab(path):with open(path, r) as f:tokens f.read().split(\n)return Vocab(tokens)#加载语料库
def load_reuters():#从 nltk 导入reutersfrom nltk.corpus import reuters#获取所有句子text reuters.sents()# lowercase (optional)text [[word.lower() for word in sentence] for sentence in text]#构建词表传入预留标记vocab Vocab.build(text, reserved_tokens[PAD_TOKEN, BOS_TOKEN, EOS_TOKEN])#利用词表将文本数据转换为idcorpus [vocab.convert_tokens_to_ids(sentence) for sentence in text]return corpus, vocab#保存词向量
def save_pretrained(vocab, embeds, save_path):Save pretrained token vectors in a unified format, where the first linespecifies the number_of_tokens and embedding_dim followed with alltoken vectors, one token per line.with open(save_path, w) as writer:writer.write(f{embeds.shape[0]} {embeds.shape[1]}\n)for idx, token in enumerate(vocab.idx_to_token):vec .join([{:.4f}.format(x) for x in embeds[idx]])writer.write(f{token} {vec}\n)print(fPretrained embeddings saved to: {save_path})2、数据创建数据处理类实现架前馈神经网络模型的训练数据构建与存取功能#从dataset类中派生一个子类
class NGramDataset(Dataset):def __init__(self, corpus, vocab, context_size2):self.data []self.bos vocab[BOS_TOKEN]#句首标记self.eos vocab[EOS_TOKEN]#句尾标记for sentence in tqdm(corpus, descDataset Construction):# 插入句首句尾符号sentence [self.bos] sentence [self.eos]#如果句子长度小于预定义的上下文大小则跳过if len(sentence) context_size:continuefor i in range(context_size, len(sentence)):# 模型输入长为context_size的上文context sentence[i-context_size:i]# 模型输出当前词target sentence[i]#每个训练样本由(context, target)构成self.data.append((context, target))def __len__(self):return len(self.data)def __getitem__(self, i):return self.data[i]def collate_fn(self, examples):# 从独立样本集合中构建batch输入输出inputs torch.tensor([ex[0] for ex in examples], dtypetorch.long)targets torch.tensor([ex[1] for ex in examples], dtypetorch.long)return (inputs, targets)3、模型创建语言模型类参数包含词向量层由词向量层到隐含层由隐含层到输出层的线性变换参数class FeedForwardNNLM(nn.Module):def __init__(self, vocab_size, embedding_dim, context_size, hidden_dim):super(FeedForwardNNLM, self).__init__()# 词嵌入层self.embeddings nn.Embedding(vocab_size, embedding_dim)# 线性变换词嵌入层-隐含层self.linear1 nn.Linear(context_size * embedding_dim, hidden_dim)# 线性变换隐含层-输出层self.linear2 nn.Linear(hidden_dim, vocab_size)# 使用ReLU激活函数self.activate F.reluinit_weights(self)def forward(self, inputs):#将输入词序列映射为词向量通过view函数对映射后的词向量序列组成的三维张量进行重构完成词表的拼接embeds self.embeddings(inputs).view((inputs.shape[0], -1))hidden self.activate(self.linear1(embeds))output self.linear2(hidden)# 根据输出层logits计算概率分布并取对数以便于计算对数似然# 这里采用PyTorch库的log_softmax实现log_probs F.log_softmax(output, dim1)return log_probs4、训练#超参数设置
embedding_dim 64 #词向量维度
context_size 2 #隐含层维度
hidden_dim 128 #批次大小
batch_size 1024 #输入上下文长度
num_epoch 10 #训练迭代次数# 读取文本数据构建FFNNLM训练数据集n-grams
corpus, vocab load_reuters()
dataset NGramDataset(corpus, vocab, context_size)
data_loader get_loader(dataset, batch_size)# 负对数似然损失函数
nll_loss nn.NLLLoss()
# 构建FFNNLM并加载至device
device torch.device(cuda if torch.cuda.is_available() else cpu)
model FeedForwardNNLM(len(vocab), embedding_dim, context_size, hidden_dim)
model.to(device)
# 使用Adam优化器
optimizer optim.Adam(model.parameters(), lr0.001)model.train()
total_losses []
for epoch in range(num_epoch):total_loss 0for batch in tqdm(data_loader, descfTraining Epoch {epoch}):inputs, targets [x.to(device) for x in batch]optimizer.zero_grad()log_probs model(inputs)loss nll_loss(log_probs, targets)loss.backward()optimizer.step()total_loss loss.item()print(fLoss: {total_loss:.2f})total_losses.append(total_loss)# 保存词向量model.embeddings
save_pretrained(vocab, model.embeddings.weight.data, ffnnlm.vec)1.3、循环神经网络语言模型 (Recurrent Neural Network Language Model, RNNIM)在前馈神经网络语言模型中对下—个词的预测需要回看多长的历史是由超参数n决定的。但是不同的句子对历史长度的期望往往是变化的。例如对于句子 “他 喜欢吃 苹果”根据“吃” 容易推测出下个词有很大概率 是一种食物。因此只需要考虑较短的历史就足够了。而对于结构较为复杂的句子如“他感冒了于是下班 之后 去了 医院”则需要看到较长的历史 (“感冒”才能合理地预测出目标词 〝医院”。 循环神经网络语言模型正是为了处理这种不定长依赖而设计的一种语言模型。循环神经网络是用来处理序列数据的一种神经网络而自然语言正好满足这种序列结构性质。循环神经网络语言模型中的每一时刻都维护一个隐含 状态该状态蕴含了 当前词的所有历史信息且与当新词一起被作为下一时刻的输入。这个随时刻变化而不断更新的隐含状态也被称作记忆Merory)。 以上只是循环神经网络最基本的形式当序列较长时训练阶段会存在梯度弥散(Vanishing gradient 或者梯度爆炸Exploding gradient) 的风险。为了应对这一问题以前的做法是在梯度反向传播的过程中按长度进行截断 ( Truncated Backpropagation Through Time 从而使得模型能够得到有效的训练但是与此同时也减弱了模型对于长距离依赖的建模能力。这种做法一直持续到2015 年左右之后被含有门控机制的循环神经网络如长短时记忆网络LSTM)代替。模型实现1、数据准备仍然使用 NLTK 中提供的 Reuters 语料库2、数据创建数据处理类使用序列预测的方式构建训练样本与前馈神经网络模型不同RNNLM是输入长度是动态变化的因此构建批次时需要对批次内的样本补齐时长度一致class RnnlmDataset(Dataset):def __init__(self, corpus, vocab):self.data []self.bos vocab[BOS_TOKEN]self.eos vocab[EOS_TOKEN]self.pad vocab[PAD_TOKEN]for sentence in tqdm(corpus, descDataset Construction):# 模型输入BOS_TOKEN, w_1, w_2, ..., w_ninput [self.bos] sentence# 模型输出w_1, w_2, ..., w_n, EOS_TOKENtarget sentence [self.eos]self.data.append((input, target))def __len__(self):return len(self.data)def __getitem__(self, i):return self.data[i]def collate_fn(self, examples):# 从独立样本集合中构建batch输入输出inputs [torch.tensor(ex[0]) for ex in examples]targets [torch.tensor(ex[1]) for ex in examples]# 对batch内的样本进行padding使其具有相同长度inputs pad_sequence(inputs, batch_firstTrue, padding_valueself.pad)targets pad_sequence(targets, batch_firstTrue, padding_valueself.pad)return (inputs, targets)3、模型class RNNLM(nn.Module):def __init__(self, vocab_size, embedding_dim, hidden_dim):super(RNNLM, self).__init__()# 词嵌入层self.embeddings nn.Embedding(vocab_size, embedding_dim)# 循环神经网络这里使用LSTMself.rnn nn.LSTM(embedding_dim, hidden_dim, batch_firstTrue)# 输出层self.output nn.Linear(hidden_dim, vocab_size)def forward(self, inputs):embeds self.embeddings(inputs)# 计算每一时刻的隐含层表示hidden, _ self.rnn(embeds)output self.output(hidden)log_probs F.log_softmax(output, dim2)return log_probs4、训练embedding_dim 64
context_size 2
hidden_dim 128
batch_size 1024
num_epoch 10# 读取文本数据构建FFNNLM训练数据集n-grams
corpus, vocab load_reuters()
dataset RnnlmDataset(corpus, vocab)
data_loader get_loader(dataset, batch_size)# 负对数似然损失函数忽略pad_token处的损失
nll_loss nn.NLLLoss(ignore_indexdataset.pad)
# 构建RNNLM并加载至device
device torch.device(cuda if torch.cuda.is_available() else cpu)
model RNNLM(len(vocab), embedding_dim, hidden_dim)
model.to(device)
# 使用Adam优化器
optimizer optim.Adam(model.parameters(), lr0.001)model.train()
for epoch in range(num_epoch):total_loss 0for batch in tqdm(data_loader, descfTraining Epoch {epoch}):inputs, targets [x.to(device) for x in batch]optimizer.zero_grad()log_probs model(inputs)loss nll_loss(log_probs.view(-1, log_probs.shape[-1]), targets.view(-1))loss.backward()optimizer.step()total_loss loss.item()print(fLoss: {total_loss:.2f})save_pretrained(vocab, model.embeddings.weight.data, rnnlm.vec)运行结果2、Word2vec词向量从词向量学习的角度来看基于神经网络语言模型的预训练方法存在一个明显的缺点即当对t时刻词进行预测时模型只利用了历史词序列作为输人而损失了与“未来”上下文之间的共现信息。介绍一类训练效率更高、 表达能力更强的词向量预训练模型一-Word2vec 其中包括 CBOW ( Continuous Bag-of Words模型以及 Skip-grarn 模型。这两个模型由 Tomas Mikolov 等人于 2013年提出它们不再是严格意义上的语言模型完全基于词与词之间的共现信息实现词向量的学习2.1、CBOW模型给定一段文本CBOW 模型的基本思想是根据上下文对目标词进行预测。 与神经网络语言模型不同CBOW模型不考虑上下文中单词的位置或者顺序因此模型的输人实际上是一个“词袋”而非序列这也是模型取名为 “Coninuous Bag-of words”的原因。但是这并不意味者位置信息毫无用处。相关研究表明融入相对位置信息之后所得到的词向量在语法相关的自然语言处理任务如词性标注、依存句法分析上表现更好。与一般的前馈神经 网络相比CBOW 模型的隐含层只是执行对词向量层取平均的操作而没有线性变换以及非线性激活的过程。所以也可以认为 CBOW 模型是没有隐含层的这也是CBOW 模型具有高训练效率的主要原因。 模型实现import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.utils.data import Dataset
from torch.nn.utils.rnn import pad_sequence
from tqdm.auto import tqdm
from utils import BOS_TOKEN, EOS_TOKEN, PAD_TOKEN
from utils import load_reuters, save_pretrained, get_loader, init_weightsclass CbowDataset(Dataset):def __init__(self, corpus, vocab, context_size2):self.data []self.bos vocab[BOS_TOKEN]self.eos vocab[EOS_TOKEN]for sentence in tqdm(corpus, descDataset Construction):sentence [self.bos] sentence [self.eos]#如果句子长度不足以构建上下文目标词训练样本则跳过if len(sentence) context_size * 2 1:continuefor i in range(context_size, len(sentence) - context_size):# 模型输入左右分别取context_size长度的上下文context sentence[i-context_size:i] sentence[i1:icontext_size1]# 模型输出当前词target sentence[i]self.data.append((context, target))def __len__(self):return len(self.data)def __getitem__(self, i):return self.data[i]def collate_fn(self, examples):inputs torch.tensor([ex[0] for ex in examples])targets torch.tensor([ex[1] for ex in examples])return (inputs, targets)#与前馈神经网络较为接近区别在于隐含层完全线性化只需要对输入层向量取平均
class CbowModel(nn.Module):def __init__(self, vocab_size, embedding_dim):super(CbowModel, self).__init__()# 词嵌入层self.embeddings nn.Embedding(vocab_size, embedding_dim)# 线性变换隐含层-输出层self.output nn.Linear(embedding_dim, vocab_size)init_weights(self)def forward(self, inputs):embeds self.embeddings(inputs)# 计算隐含层对上下文词向量求平均hidden embeds.mean(dim1)output self.output(hidden)log_probs F.log_softmax(output, dim1)return log_probsembedding_dim 64
context_size 2
hidden_dim 128
batch_size 1024
num_epoch 10# 读取文本数据构建CBOW模型训练数据集
corpus, vocab load_reuters()
dataset CbowDataset(corpus, vocab, context_sizecontext_size)
data_loader get_loader(dataset, batch_size)nll_loss nn.NLLLoss()
# 构建CBOW模型并加载至device
device torch.device(cuda if torch.cuda.is_available() else cpu)
model CbowModel(len(vocab), embedding_dim)
model.to(device)
optimizer optim.Adam(model.parameters(), lr0.001)model.train()
for epoch in range(num_epoch):total_loss 0for batch in tqdm(data_loader, descfTraining Epoch {epoch}):inputs, targets [x.to(device) for x in batch]optimizer.zero_grad()log_probs model(inputs)loss nll_loss(log_probs, targets)loss.backward()optimizer.step()total_loss loss.item()print(fLoss: {total_loss:.2f})# 保存词向量model.embeddings
save_pretrained(vocab, model.embeddings.weight.data, cbow.vec)运行结果2.2、 Skip-gram 模型 绝大多数词向量学习模型本质上都是在建立词与其上下文之间的联系。CBOW 模型使用上下文窗口中词的集合作为条件输人预测目标词。而Skip-gram 模型在此基础之上作了进一步的简化使用Ct中的每个词作为独立的上下文对目标词进行预测。因此Skip-gram 模型建立的是词与词之间的共现关系即原文献口对于 Skip-gram 模型的描述是根据当前词wt 预测其上下文中的词wtj即。这两种形式是等价的。模型实现import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.utils.data import Dataset
from torch.nn.utils.rnn import pad_sequence
from tqdm.auto import tqdm
from utils import BOS_TOKEN, EOS_TOKEN, PAD_TOKEN
from utils import load_reuters, save_pretrained, get_loader, init_weights#模型输入与CBOW接近区别在于输入输出都是单个词即在一定程度上下文窗口大小内共现的词对
class SkipGramDataset(Dataset):def __init__(self, corpus, vocab, context_size2):self.data []self.bos vocab[BOS_TOKEN]self.eos vocab[EOS_TOKEN]for sentence in tqdm(corpus, descDataset Construction):sentence [self.bos] sentence [self.eos]for i in range(1, len(sentence)-1):# 模型输入当前词w sentence[i]# 模型输出一定窗口大小内的上下文left_context_index max(0, i - context_size)right_context_index min(len(sentence), i context_size)context sentence[left_context_index:i] sentence[i1:right_context_index1]self.data.extend([(w, c) for c in context])def __len__(self):return len(self.data)def __getitem__(self, i):return self.data[i]def collate_fn(self, examples):inputs torch.tensor([ex[0] for ex in examples])targets torch.tensor([ex[1] for ex in examples])return (inputs, targets)class SkipGramModel(nn.Module):def __init__(self, vocab_size, embedding_dim):super(SkipGramModel, self).__init__()self.embeddings nn.Embedding(vocab_size, embedding_dim)self.output nn.Linear(embedding_dim, vocab_size)init_weights(self)def forward(self, inputs):embeds self.embeddings(inputs)#根据当前词的词向量对上下文进行预测output self.output(embeds)log_probs F.log_softmax(output, dim1)return log_probsembedding_dim 64
context_size 2
hidden_dim 128
batch_size 1024
num_epoch 10# 读取文本数据构建Skip-gram模型训练数据集
corpus, vocab load_reuters()
dataset SkipGramDataset(corpus, vocab, context_sizecontext_size)
data_loader get_loader(dataset, batch_size)nll_loss nn.NLLLoss()
# 构建Skip-gram模型并加载至device
device torch.device(cuda if torch.cuda.is_available() else cpu)
model SkipGramModel(len(vocab), embedding_dim)
model.to(device)
optimizer optim.Adam(model.parameters(), lr0.001)model.train()
for epoch in range(num_epoch):total_loss 0for batch in tqdm(data_loader, descfTraining Epoch {epoch}):inputs, targets [x.to(device) for x in batch]optimizer.zero_grad()log_probs model(inputs)loss nll_loss(log_probs, targets)loss.backward()optimizer.step()total_loss loss.item()print(fLoss: {total_loss:.2f})# 保存词向量model.embeddings
save_pretrained(vocab, model.embeddings.weight.data, skipgram.vec)输出2.3、 负采样 目前介绍的词向量预训练模型可以归纳为对目标词的条件项测任务。如根据上下文预测当前词CBOW 模型或者根据当前词预测上下文Skip-gram模型。 当词表规模较大且计算资源有限时这类模型的训练过程会受到输出层概率归一化 (Normalization计算效率的影响。负采样方法则提供了一种新的任务视角给定当前词与其上下文最大化两者共现的概率。这样一来问题就被简化为对于(w,c)的二元分类问题共现或者非共现从而规避了大词表上的归一化计算。 基于负采样的 Skip-gram 模型 1、数据在基于负采样的 Skip-gram 模型中对于每个训练正样本需要根据某个负采样概率分布生成相应的负样本同时需要保证负样本不包含当前上下文窗口内的词。一种实现方式是在构建训练数据的过程中就完成负样本的生成这样在训练时直接读取负样本即可。这样做的优点是训练过程无须再进行负采样因而效率较高缺点是每次迭代使用的是同样的负样本缺乏多样性。 这里采用在训练过程中实时进行负采样的实现方式import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.utils.data import Dataset
from torch.nn.utils.rnn import pad_sequence
from tqdm.auto import tqdm
from utils import BOS_TOKEN, EOS_TOKEN, PAD_TOKEN
from utils import load_reuters, save_pretrained, get_loader, init_weightsclass SGNSDataset(Dataset):def __init__(self, corpus, vocab, context_size2, n_negatives5, ns_distNone):self.data []self.bos vocab[BOS_TOKEN]self.eos vocab[EOS_TOKEN]self.pad vocab[PAD_TOKEN]for sentence in tqdm(corpus, descDataset Construction):sentence [self.bos] sentence [self.eos]for i in range(1, len(sentence)-1):# 模型输入(w, context) 输出为0/1表示context是否为负样本w sentence[i]left_context_index max(0, i - context_size)right_context_index min(len(sentence), i context_size)context sentence[left_context_index:i] sentence[i1:right_context_index1]context [self.pad] * (2 * context_size - len(context))self.data.append((w, context))# 负样本数量self.n_negatives n_negatives# 负采样分布若参数ns_dist为None则使用uniform分布self.ns_dist ns_dist if ns_dist is not None else torch.ones(len(vocab))def __len__(self):return len(self.data)def __getitem__(self, i):return self.data[i]def collate_fn(self, examples):words torch.tensor([ex[0] for ex in examples], dtypetorch.long)contexts torch.tensor([ex[1] for ex in examples], dtypetorch.long)batch_size, context_size contexts.shapeneg_contexts []# 对batch内的样本分别进行负采样for i in range(batch_size):# 保证负样本不包含当前样本中的contextns_dist self.ns_dist.index_fill(0, contexts[i], .0)neg_contexts.append(torch.multinomial(ns_dist, self.n_negatives * context_size, replacementTrue))neg_contexts torch.stack(neg_contexts, dim0)return words, contexts, neg_contexts2、模型class SGNSModel(nn.Module):def __init__(self, vocab_size, embedding_dim):super(SGNSModel, self).__init__()# 词嵌入self.w_embeddings nn.Embedding(vocab_size, embedding_dim)# 上下文嵌入self.c_embeddings nn.Embedding(vocab_size, embedding_dim)def forward_w(self, words):w_embeds self.w_embeddings(words)return w_embedsdef forward_c(self, contexts):c_embeds self.c_embeddings(contexts)return c_embeds3、训练def get_unigram_distribution(corpus, vocab_size):# 从给定语料中统计unigram概率分布token_counts torch.tensor([0] * vocab_size)total_count 0for sentence in corpus:total_count len(sentence)for token in sentence:token_counts[token] 1unigram_dist torch.div(token_counts.float(), total_count)return unigram_dist#超参数
embedding_dim 64
context_size 2
hidden_dim 128
batch_size 1024
num_epoch 10
n_negatives 10 #负样本数量# 读取文本数据
corpus, vocab load_reuters()
# 计算unigram概率分布
unigram_dist get_unigram_distribution(corpus, len(vocab))
# 根据unigram分布计算负采样分布: p(w)**0.75
negative_sampling_dist unigram_dist ** 0.75
negative_sampling_dist / negative_sampling_dist.sum()
# 构建SGNS训练数据集
dataset SGNSDataset(corpus,vocab,context_sizecontext_size,n_negativesn_negatives,ns_distnegative_sampling_dist
)
data_loader get_loader(dataset, batch_size)device torch.device(cuda if torch.cuda.is_available() else cpu)
model SGNSModel(len(vocab), embedding_dim)
model.to(device)
optimizer optim.Adam(model.parameters(), lr0.001)model.train()
for epoch in range(num_epoch):total_loss 0for batch in tqdm(data_loader, descfTraining Epoch {epoch}):words, contexts, neg_contexts [x.to(device) for x in batch]optimizer.zero_grad()batch_size words.shape[0]# 提取batch内词、上下文以及负样本的向量表示word_embeds model.forward_w(words).unsqueeze(dim2)context_embeds model.forward_c(contexts)neg_context_embeds model.forward_c(neg_contexts)# 正样本的分类对数似然context_loss F.logsigmoid(torch.bmm(context_embeds, word_embeds).squeeze(dim2))context_loss context_loss.mean(dim1)# 负样本的分类对数似然neg_context_loss F.logsigmoid(torch.bmm(neg_context_embeds, word_embeds).squeeze(dim2).neg())neg_context_loss neg_context_loss.view(batch_size, -1, n_negatives).sum(dim2)neg_context_loss neg_context_loss.mean(dim1)# 损失负对数似然loss -(context_loss neg_context_loss).mean()loss.backward()optimizer.step()total_loss loss.item()print(fLoss: {total_loss:.2f})# 合并词嵌入矩阵与上下文嵌入矩阵作为最终的预训练词向量
combined_embeds model.w_embeddings.weight model.c_embeddings.weight
save_pretrained(vocab, combined_embeds.data, sgns.vec)输出3、GloVe词向量无论是基于神经网络语言模型还是 word2vec 的词向量预训练方法本质上都是利用文本中词与词在局部上下文中的共现信息作为自监督学习信号。除此之外另一类常用于估计词向量的方法是基于矩阵分解的方法例如潜在语义分析等。这类方法首先对语料进行统计分析并获得合有全局统计信息的“词-上下文”共现短阵然后利用奇异值分解 (Singular Value Decomposition, SVD )对该矩阵进行降维进而得到词的低维表示。然而传统的矩阵分解方法得到的词 向量不具备良好的几何性质因此结合词向量以及矩阵分解的思想提出Glove ( Global Vectors for Word Representation模型。 3.1、模型实现 Glove模型的基本思想是利用词向量对 “词-上下文〞共现矩阵进行预测或者回归从而实现隐式的短阵分解。import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.utils.data import Dataset
from torch.nn.utils.rnn import pad_sequence
from tqdm.auto import tqdm
from utils import BOS_TOKEN, EOS_TOKEN, PAD_TOKEN
from utils import load_reuters, save_pretrained, get_loader, init_weights
from collections import defaultdict#构建数据处理块该模块需要完成共现矩阵的构建与存取
class GloveDataset(Dataset):def __init__(self, corpus, vocab, context_size2):# 记录词与上下文在给定语料中的共现次数self.cooccur_counts defaultdict(float)self.bos vocab[BOS_TOKEN]self.eos vocab[EOS_TOKEN]for sentence in tqdm(corpus, descDataset Construction):sentence [self.bos] sentence [self.eos]for i in range(1, len(sentence)-1):w sentence[i]left_contexts sentence[max(0, i - context_size):i]right_contexts sentence[i1:min(len(sentence), i context_size)1]# 共现次数随距离衰减: 1/d(w, c)for k, c in enumerate(left_contexts[::-1]):self.cooccur_counts[(w, c)] 1 / (k 1)for k, c in enumerate(right_contexts):self.cooccur_counts[(w, c)] 1 / (k 1)self.data [(w, c, count) for (w, c), count in self.cooccur_counts.items()]def __len__(self):return len(self.data)def __getitem__(self, i):return self.data[i]def collate_fn(self, examples):words torch.tensor([ex[0] for ex in examples])contexts torch.tensor([ex[1] for ex in examples])counts torch.tensor([ex[2] for ex in examples])return (words, contexts, counts)class GloveModel(nn.Module):def __init__(self, vocab_size, embedding_dim):super(GloveModel, self).__init__()# 词嵌入及偏置向量self.w_embeddings nn.Embedding(vocab_size, embedding_dim)self.w_biases nn.Embedding(vocab_size, 1)# 上下文嵌入及偏置向量self.c_embeddings nn.Embedding(vocab_size, embedding_dim)self.c_biases nn.Embedding(vocab_size, 1)def forward_w(self, words):w_embeds self.w_embeddings(words)w_biases self.w_biases(words)return w_embeds, w_biasesdef forward_c(self, contexts):c_embeds self.c_embeddings(contexts)c_biases self.c_biases(contexts)return c_embeds, c_biases#超参数
embedding_dim 64
context_size 2
batch_size 1024
num_epoch 10# 用以控制样本权重的超参数
m_max 100
alpha 0.75
# 从文本数据中构建GloVe训练数据集
corpus, vocab load_reuters()
dataset GloveDataset(corpus,vocab,context_sizecontext_size
)
data_loader get_loader(dataset, batch_size)device torch.device(cuda if torch.cuda.is_available() else cpu)
model GloveModel(len(vocab), embedding_dim)
model.to(device)
optimizer optim.Adam(model.parameters(), lr0.001)model.train()
for epoch in range(num_epoch):total_loss 0for batch in tqdm(data_loader, descfTraining Epoch {epoch}):words, contexts, counts [x.to(device) for x in batch]# 提取batch内词、上下文的向量表示及偏置word_embeds, word_biases model.forward_w(words)context_embeds, context_biases model.forward_c(contexts)# 回归目标值必要时可以使用log(counts1)进行平滑log_counts torch.log(counts)# 样本权重weight_factor torch.clamp(torch.pow(counts / m_max, alpha), max1.0)optimizer.zero_grad()# 计算batch内每个样本的L2损失loss (torch.sum(word_embeds * context_embeds, dim1, keepdimTrue) word_biases context_biases - log_counts) ** 2# 样本加权损失wavg_loss (weight_factor * loss).mean()wavg_loss.backward()optimizer.step()total_loss wavg_loss.item()print(fLoss: {total_loss:.2f})# 合并词嵌入矩阵与上下文嵌入矩阵作为最终的预训练词向量
combined_embeds model.w_embeddings.weight model.c_embeddings.weight
save_pretrained(vocab, combined_embeds.data, glove.vec)输出