word2vector训练代码详解

打印 上一主题 下一主题

主题 1023|帖子 1023|积分 3069

目次
1.代码实现
2.知识点 


 
1.代码实现

  1. #导包
  2. import math
  3. import torch
  4. from torch import nn
  5. import dltools
复制代码
  1. #加载PTB数据集  ,需要把PTB数据集的文件夹放在代码上一级目录的data文件中,不用解压
  2. #批次大小、窗口大小、噪声词大小
  3. batch_size, max_window_size, num_noise_words = 512, 5, 5  
  4. #获取数据集迭代器、词汇表
  5. data_iter, vocab = dltools.load_data_ptb(batch_size, max_window_size, num_noise_words)
复制代码
  1. #讲解嵌入层embedding的用法(此行代码无用)
  2. #嵌入层
  3. #通过嵌入层来获取skip—gram的中心词向量和上下文词向量
  4. embed = nn.Embedding(num_embeddings=20, embedding_dim=4)  
  5. # num_embeddings就是词表大小
  6. # X的shape=(batch_size, num_steps)
  7. # --one_hot编码--->(batch_size, num_steps, num_embedding(vocab_size))
  8. # --点乘中心词矩阵-->(batch_size, num_steps, embed_size)
复制代码
  1. embed.weight.shape   #讲解嵌入层embedding的用法(此行代码无用)
复制代码
  1. torch.Size([20, 4])
复制代码
   embedding层先one_hot编码,再进行与embedding层的矩阵(num_embeddings,embedding_dim)乘法 
  1. #构造skip_gram的前向传播
  2. def skip_gram(center, contexts_and_negatives, embed_v, embed_u):
  3.     """
  4.     embed_v:表示对中心词进行embedding层
  5.     embed_u:对上下文词进行embedding层
  6.     """
  7.     v = embed_v(center)                 #中心词的词向量表达
  8.     u = embed_u(contexts_and_negatives) #上下文词的词向量表达
  9.     #用中心词来预测上下文词
  10.     #u_shape = (batch_size, num_steps, embed_size)---->(batch_size, embed_size, num_steps)进行矩阵乘法
  11.     pred = torch.bmm(v, u.permute(0, 2, 1))  #矩阵乘法(bmm三维乘法),不用管batch_size维度
  12.     return pred
复制代码
  1. #假设数据
  2. skip_gram(torch.ones((2, 1), dtype=torch.long), torch.ones((2, 4), dtype=torch.long), embed, embed)
复制代码
  1. tensor([[[3.1980, 3.1980, 3.1980, 3.1980]],
  2.         [[3.1980, 3.1980, 3.1980, 3.1980]]], grad_fn=<BmmBackward0>)
复制代码
  1. #假设数据
  2. skip_gram(torch.ones((2, 1), dtype=torch.long), torch.ones((2, 4), dtype=torch.long), embed, embed).shape
复制代码
   torch.Size([2, 1, 4])
  1. #带掩码的二元交叉熵损失
  2. class SigmoidBCELoss(nn.Module):
  3.     def __init__(self):
  4.         super().__init__()  #直接继承父类的初始化属性和方法
  5.    
  6.     def forward(self, inputs, target, mask=None):
  7.         #nn.functional.binary_cross_entropy_with_logits表示返回的不是转化后的概率,是原始计算的数据结果
  8.         #weight=mask权重将掩码带上
  9.         #reduction='none'表示不将计算结果聚合,算损失时(默认聚合)
  10.         out = nn.functional.binary_cross_entropy_with_logits(inputs, target, weight=mask, reduction='none')
  11.         return out.mean(dim=1)  #计算结果是二维的,在索引1维度上聚合求平均
  12. loss = SigmoidBCELoss()
复制代码
  1. [[1.1, -2.2, 3.3, -4.4]] * 2
复制代码
  1. [[1.1, -2.2, 3.3, -4.4], [1.1, -2.2, 3.3, -4.4]]
复制代码
  1. torch.tensor([[1.1, -2.2, 3.3, -4.4]] * 2).shape
复制代码
   torch.Size([2, 4])
  1. #假设数据测试
  2. pred = torch.tensor([[1.1, -2.2, 3.3, -4.4]] * 2)
  3. label = torch.tensor([[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0]])
  4. mask = torch.tensor([[1, 1, 1, 1], [1, 1, 0, 0]])
  5. #mask每一行都有4个数值,所以* mask.shape[1]=4
  6. #但是mask中的数值0表示权重,是补充步长的,不重要,需要计算有效序列的损失平均值,所以 / mask.sum(axis=1)
  7. loss(pred, label, mask) * mask.shape[1] / mask.sum(axis=1)
复制代码
   tensor([0.9352, 1.8462])
  1. #初始化模型参数,定义两个嵌入层
  2. #一开始,embed_weights会标准正态分布的数据初始化
  3. #两个embedding层的参数不一样,不能重复使用,需要初始化定义两个
  4. embed_size = 100
  5. net = nn.Sequential(nn.Embedding(num_embeddings=len(vocab), embedding_dim=embed_size),
  6.                     nn.Embedding(num_embeddings=len(vocab), embedding_dim=embed_size))
复制代码
 
  1. #定义训练过程
  2. def train(net, data_iter, lr, num_epochs, device=dltools.try_gpu()):
  3.     #修改embedding层的初始化方法,使用nn.init.xavier_uniform_初始化embed.weight权重,在NLP中不使用标准正态分布的额数据初始化权重
  4.     def init_weights(m):
  5.         if type(m) == nn.Embedding:
  6.             nn.init.xavier_uniform_(m.weight)
  7.     net.apply(init_weights)  
  8.     net = net.to(device)
  9.     #设置梯度下降的优化器
  10.     optimizer = torch.optim.Adam(net.parameters(), lr=lr)
  11.     #设置绘制可视化的动图(epoch——loss)
  12.     animator = dltools.Animator(xlabel='epoch', ylabel='loss', xlim=[1, num_epochs])
  13.    
  14.     #设置累加
  15.     metric = dltools.Accumulator(2)   #2种数据需要累加
  16.     for epoch in range(num_epochs):  #遍历训练次数
  17.         #设置计时器, 赋值批次数量
  18.         timer, num_batches = dltools.Timer(), len(data_iter)    #data_iter是分好批次的数据集,长度就是批次数量num_batches
  19.         for i, batch in enumerate(data_iter):   #i是索引, batch是取出的一批批数据
  20.             #梯度清零
  21.             optimizer.zero_grad()
  22.             #接收中心词, 上下文词_噪声词, 掩码, 标记目标值
  23.             center, context_negative, mask, label = [data.to(device) for data in batch]
  24.             #调用skip_gram模型预测
  25.             pred = skip_gram(center, context_negative, embed_v=net[0], embed_u=net[1])
  26.             #计算损失
  27.             l = loss(pred.reshape(label.shape).float(), label.float(), mask) / mask.shape[1] * mask.sum(dim=1)
  28.             #用loss反向传播  ,loss先sum()聚合变成标量(合并成一个数值), 只有标量才能反向传播
  29.             l.sum().backward()
  30.             #梯度更新
  31.             optimizer.step()
  32.             #累加
  33.             metric.add(l.sum(), l.numel())   #l.sum()数值求和累加, l.numel()数量累加
  34.             #   %  取余数      
  35.             #  //  商向下取整
  36.             #迭代到总数据量的5%的倍数时 或者 处理到最后一批数据时,执行下面操作
  37.             #  i+1是因为i是从0开始遍历的
  38.             if (i + 1) % (num_batches // 5) == 0 or i == num_batches - 1:  
  39.                 #epoch + (i+1) / num_batches当前迭代次数占整个数据集的比例
  40.                 animator.add(epoch + (i+1) / num_batches, (metric[0] / metric[1]))
  41.     print(f'loss {metric[0] / metric[1]:.3f}', f'{metric[1] / timer.stop():.1f} tokens/sec on {str(device)}')      
复制代码
  1. lr, num_epochs = 0.002, 50
  2. train(net, data_iter, lr, num_epochs)
复制代码

  1. #如果能够找到词的近义词, 就说明训练的不错
  2. def get_similar_tokens(query_token, k, embed):
  3.     """
  4.     query_token:需要预测的词
  5.     k:最高相似度的词数量
  6.     embed:embedding层的哪一层
  7.     """
  8.     #获取词向量权重    (词向量权重*词的one_hot编码,就是词向量)
  9.     W = embed.weight.data
  10.     print(f'W的shape:{W.shape}')
  11.     x = W[vocab[query_token]]     #embedding层是按照索引查表查词对应的权重-->优点
  12.     print(f'x的shape:{x.shape}')
  13.     #计算余弦相似度
  14.     #torch.mv两个向量的点乘
  15.     cos = torch.mv(W, x) / torch.sqrt(torch.sum(W * W, dim=1) * torch.sum(x * x) + 1e-9)
  16.     print(f'cos的shape:{cos.shape}')
  17.     #排序选择前k个对应的索引
  18.     topk = torch.topk(cos, k=k+1)[1].cpu().numpy().astype('int32')
  19.     for i in topk[1:]:   #排除query_token他本身,自己与自己余弦相似度最高
  20.         print(f'cosine sim={float(cos[i]):.3f}:{vocab.to_tokens(i)}')
  21.         
复制代码
  1. get_similar_tokens('food', 3, net[0])
复制代码
   
  1. W的shape:torch.Size([6719, 100])
  2. x的shape:torch.Size([100])
  3. cos的shape:torch.Size([6719])
  4. cosine sim=0.430:feed
  5. cosine sim=0.418:precious
  6. cosine sim=0.412:drink
复制代码
2.知识点 

 


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

知者何南

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表