引言

本系列文章是七月在线的<PyTorch入门与实战>课程的一个笔记。
本文用的PyTorch版本是1.7.1


  • 上一篇:PyTorch学习笔记——PyTorch简介
  • 下一篇:PyTorch学习笔记——语言模型

为什么需要词向量

为了便于计算机处理,我们需要把文档、单词向量化。
而且除了向量化之后,还希望单词的表达能计算相似词信息。

向量化单词,最早的方法是one-hot表示法,但是这种表示没有包含语义信息,并且也不知道某个单词在某篇文章中的重要性。

后来有人提出了TF-IDF方法,这种词袋模型能考虑到单词的重要性,但是语义的相似性还是捕捉不到。


所谓语义信息,就是代表各种青蛙的单词,向量化之后,这些向量的距离越接近越好。距离越近则表示它们的意思越近。

后来有人提出了分布式表示。假设你想知道某个单词的含义,你只要知道这个单词与哪些词语同时出现。即一个单词可以用周围的单词来表示。


这就是Word2Vec,原理可以点进去看看。这里补充下Skip-Gram模型的目标函数:
1T∑t=1T∑−c≤j≤c,j≠0log⁡p(wt+j∣wt)\frac{1}{T} \sum_{t=1}^T \sum_{ -c \leq j \leq c , j \neq 0} \log p(w_{t+j}|w_t) T1t=1Tcjc,j=0logp(wt+jwt)
其中TTT代表文本长度。 wt+jw_{t+j}wt+jwtw_twt附近的单词。
就是给定中心词,它周围单词出现的概率越大越好。

本文的重点是学习PyTorch,用到的数据 见百度网盘: 密码:v2z5 。

接下来基于PyTorch实现Word2Vec:

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.utils.data as tudfrom collections import Counter
import numpy as np
import random
import mathimport pandas as pd
import scipy
import sklearn
from sklearn.metrics.pairwise import cosine_similarityUSE_CUDA = torch.cuda.is_available()seed = 53113
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)if USE_CUDA:torch.cuda.manual_seed(seed)
# 实现Skip-gram模型C = 3 # 窗口大小
K = 100 # 负采样个数
NUM_EPOCHS = 2
MAX_VOCAB_SIZE = 30000
BATCH_SIZE = 128
LEARNING_RATE = 0.2
EMBEDDING_SIZE = 100def word_tokenize(text):return text.split()

首先是设置好参数。
然后读取训练数据:

with open('./datasets/text8/text8.train.txt','r') as fin:text = fin.read()text = text.split()
text[:200] # 看200个单词

然后构造词典和相应的映射。

# 词典
vocab = dict(Counter(text).most_common(MAX_VOCAB_SIZE-1))
vocab['<unk>'] = len(text) - np.sum(list(vocab.values())) # 整个文本的单词数,减去词典中的对应的单词数 得到未知单词数
id_2_word = [word for word in vocab.keys()]
word_2_id = {word:i for i,word in enumerate(id_2_word)}
# 得到每个单词出现的次数
word_counts = np.array([count for count in vocab.values()],dtype=np.float32)
# 计算每个单词的频率
word_freqs = word_counts / np.sum(word_counts)
word_freqs = word_freqs ** (3./4.)
word_freqs = word_freqs / np.sum(word_freqs)
VOCAB_SIZE = len(id_2_word)

PyTorch提供了Dataset结合DataLoader可以实现训练数据的加载以及本文的负采样。

继承Dataset需要提供以下两个方法的实现:

  • __len__ 返回数据集元素数量
  • __getitem__ 支持索引操作,比如dataset[i]能获得第i个元素
class WordEmbeddingDataset(tud.Dataset):def __init__(self, text, word_2_id, id_2_word, word_freqs, word_counts):'''text: 单词列表,训练集中所有单词word_2_id : 单词到id的字典id_2_word: id到单词的映射word_freqs: 每个单词的频率word_counts: 每个单词出现的次数'''super(WordEmbeddingDataset,self).__init__()# 将每个单词转换为idself.text_encoded = [word_2_id.get(word, word_2_id['<unk>']) for word in text]# 转换成Tensorself.text_encoded = torch.Tensor(self.text_encoded)# 保存word_2_id和id_2_worself.word_2_id = word_2_idself.id_2_word = id_2_word# 转换成tensor并保存self.word_freqs = torch.Tensor(word_freqs)self.word_counts = torch.Tensor(word_counts)def __len__(self):# 数据集大小就是text_encoded的长度return len(self.text_encoded)def __getitem__(self,idx):'''负采样,用于训练返回:中心词中心词附近的positive单词随机采样K个单词作为negative样本'''# 中心词center_word = self.text_encoded[idx]# 上文下单词的索引pos_indices = list(range(idx - C,idx)) + list(range(idx+1,idx+C+1)) # 可能会超出文本长度pos_indices = [i % len(self.text_encoded) for i in pos_indices]pos_words = self.text_encoded[pos_indices]# 负采样neg_words = torch.multinomial(self.word_freqs, K * pos_words.shape[0],True)return center_word, pos_words, neg_words # 形状依次是: [] [6] [600]

其中用到的torch.multinomial


返回一个tensor,每行包含从input相应行中定义的多项分布(概率)中抽取的num_samples个样本,返回的是索引。

下面基于Dataset来构造DataLoader

dataset = WordEmbeddingDataset(text, word_2_id, id_2_word, word_freqs, word_counts)
dataloader = tud.DataLoader(dataset,batch_size=BATCH_SIZE,shuffle=True,num_workers=0)

然后就开始定义模型了:

# 定义PyTorch模型
# 实现的是Skip-gram模型
class EmbeddingModel(nn.Module):def __init__(self,vocab_size,embed_size):super(EmbeddingModel,self).__init__()self.vocab_size = vocab_sizeself.embed_size = embed_sizeinitrange = 0.5 / self.embed_sizeself.output_embed = nn.Embedding(self.vocab_size, self.embed_size, sparse=False)# 对权重进行随机初始化self.output_embed.weight.data.uniform_(-initrange, initrange)self.input_embed = nn.Embedding(self.vocab_size, self.embed_size, sparse=False)self.input_embed.weight.data.uniform_(-initrange, initrange)def forward(self,input_labels, pos_labels, neg_labels):'''input_labels: [batch_size]pos_labels: [batch_size,(window_size * 2)]neg_labels: [batch_size,(window_size * 2 * K)]'''batch_size = input_labels.size(0)input_embedding = self.input_embed(input_labels) #[batch_size,embed_size]pos_embedding = self.output_embed(pos_labels) #[batch_size,(window_size * 2),embed_size]neg_embedding = self.output_embed(neg_labels) #[batch_size,(window_size * 2 * K),embed_size]#input_embedding.unsqueeze(2) # unsqueeze在指定的位置插入1个维度,变成[batch_size, embed_size,1]pos_dot = torch.bmm(pos_embedding,input_embedding.unsqueeze(2)).squeeze() # [batch_size,window_size * 2 ]  squeeze()neg_dot = torch.bmm(neg_embedding,-input_embedding.unsqueeze(2)).squeeze() # [batch_size,window_size * 2  * K]log_pos = F.logsigmoid(pos_dot).sum(1)log_neg = F.logsigmoid(neg_dot).sum(1)loss = log_pos + log_negreturn -lossdef input_embeddings(self):return self.input_embed.weight.data.cpu().numpy()


先来看一下nn.Embedding,说的是存储了单词的嵌入向量。实际上是根据指定的维度初始化了一个权重矩阵,本例可以理解为初始化了self.vocab_size个大小为self.embed_size的tensor,每个tensor就是一个单词的词嵌入向量。


torch.bmm做的是批次内的矩阵乘法。

 pos_dot = torch.bmm(pos_embedding,input_embedding.unsqueeze(2)).squeeze() # [batch_size,window_size * 2 ]  squeeze()neg_dot = torch.bmm(neg_embedding,-input_embedding.unsqueeze(2)).squeeze() # [batch_size,window_size * 2  * K]log_pos = F.logsigmoid(pos_dot).sum(1)log_neg = F.logsigmoid(neg_dot).sum(1)loss = log_pos + log_neg

以上代码实现的是论文1中的公式(4)(4)(4)


其中vwIv_{wI}vwI是输入词向量input_embeddingvwi′v^\prime_{wi}vwi是基于Pn(w)P_n(w)Pn(w)生成的负采样单词词向量neg_embeddingvwO′v^\prime_{wO}vwO是输出词向量pos_embedding

下面定义模型:

# 定义一个模型以及把模型移动到GPU
model = EmbeddingModel(VOCAB_SIZE, EMBEDDING_SIZE)
if USE_CUDA:model = model.cuda()

通过PyTorch这种框架,我们只需要实现好前向传播,它能帮我进行反向传播。

# 训练模型
optimizer = torch.optim.SGD(model.parameters(),lr=LEARNING_RATE)
for e in range(NUM_EPOCHS):for i, (input_labels,pos_labels,neg_labels) in enumerate(dataloader):input_labels,pos_labels,neg_labels = input_labels.long(),pos_labels.long(),neg_labels.long()if USE_CUDA:input_labels,pos_labels,neg_labels = input_labels.cuda(),pos_labels.cuda(),neg_labels.cuda()optimizer.zero_grad()loss = model(input_labels,pos_labels,neg_labels).mean()loss.backward()optimizer.step()if i % 100 == 0:print('epoch ',e ,' iteration ', i , loss.item())

在我本机上要跑2个小时,基于使用GPU的情况。

下面我们测试得到的结果:

embedding_weights = model.input_embeddings()
def find_nearest(word):index = word_2_id[word]embedding = embedding_weights[index]cos_dis = np.array([scipy.spatial.distance.cosine(e, embedding) for e in embedding_weights])return [id_2_word[i] for i in cos_dis.argsort()[:10]]for word in ["good", "fresh", "monster", "green", "like", "america", "chicago", "work", "computer", "language"]:print(word, find_nearest(word))


可以看到,确实学到了一些相关的语义信息。

参考


  1. Distributed Representations of Words and Phrases and their Compositionality ↩︎

PyTorch学习笔记——词向量简介相关推荐

  1. PyTorch学习笔记(二):PyTorch简介与基础知识

    往期学习资料推荐: 1.Pytorch实战笔记_GoAI的博客-CSDN博客 2.Pytorch入门教程_GoAI的博客-CSDN博客 本系列目录: PyTorch学习笔记(一):PyTorch环境安 ...

  2. 【NLP】CS224N课程笔记|词向量I: 简介, SVD和Word2Vec

    NewBeeNLP原创出品 公众号专栏作者@Ryan 知乎 | 机器学习课程笔记 CS224N课程笔记系列,持续更新中 课程主页:  http://web.stanford.edu/class/cs2 ...

  3. Pytorch学习笔记总结

    往期Pytorch学习笔记总结: 1.Pytorch实战笔记_GoAI的博客-CSDN博客 2.Pytorch入门教程_GoAI的博客-CSDN博客 Pytorch系列目录: PyTorch学习笔记( ...

  4. PyTorch学习笔记(五):模型定义、修改、保存

    往期学习资料推荐: 1.Pytorch实战笔记_GoAI的博客-CSDN博客 2.Pytorch入门教程_GoAI的博客-CSDN博客 本系列目录: PyTorch学习笔记(一):PyTorch环境安 ...

  5. PyTorch学习笔记(三):PyTorch主要组成模块

    往期学习资料推荐: 1.Pytorch实战笔记_GoAI的博客-CSDN博客 2.Pytorch入门教程_GoAI的博客-CSDN博客 本系列目录: PyTorch学习笔记(一):PyTorch环境安 ...

  6. Pytorch学习笔记-第十章

    Pytorch学习笔记-第十章图像描述 model data_preprocess data feature_extract main 记录一下个人学习和使用Pytorch中的一些问题.强烈推荐 &l ...

  7. PyTorch 学习笔记(六):PyTorch hook 和关于 PyTorch backward 过程的理解 call

    您的位置 首页 PyTorch 学习笔记系列 PyTorch 学习笔记(六):PyTorch hook 和关于 PyTorch backward 过程的理解 发布: 2017年8月4日 7,195阅读 ...

  8. 深度学习入门之PyTorch学习笔记:卷积神经网络

    深度学习入门之PyTorch学习笔记 绪论 1 深度学习介绍 2 深度学习框架 3 多层全连接网络 4 卷积神经网络 4.1 主要任务及起源 4.2 卷积神经网络的原理和结构 4.2.1 卷积层 1. ...

  9. 深度学习入门之PyTorch学习笔记:多层全连接网络

    深度学习入门之PyTorch学习笔记 绪论 1 深度学习介绍 2 深度学习框架 3 多层全连接网络 3.1 PyTorch基础 3.2 线性模型 3.2.1 问题介绍 3.2.2 一维线性回归 3.2 ...

  10. PyTorch学习笔记(七):PyTorch可视化

    PyTorch可视化 往期学习资料推荐: 1.Pytorch实战笔记_GoAI的博客-CSDN博客 2.Pytorch入门教程_GoAI的博客-CSDN博客 本系列目录: PyTorch学习笔记(一) ...

最新文章

  1. Go 学习笔记(64)— Go error.New 创建接口错误对象、fmt.Errorf 创建接口错误对象、errors.Is 和 errors.As
  2. 小猿圈web前端之移动端Vue+Vant实现上传压缩旋转图片功能
  3. Leetcode 64 最小路径和 (每日一题 20210721)
  4. 别人家的程序员是如何使用 Java 进行 Web 抓取的?
  5. 学习Java软件开发该从何入手
  6. 史上最全的MSSQL笔记
  7. Harbo1.5.2离线搭建
  8. UDT源代码下载链接
  9. swift项目 9.3以前版本模拟器运行出错
  10. 1000道Python题库系列分享
  11. 与IP地址有关的那些点
  12. 前端商城vue项目案例1
  13. 服务器SNMP协议测试
  14. getinfo怎么用php,PHP的函数curl-curl_getinfo
  15. 一步教你轻松实现--Word方括号打勾☑
  16. C++ priority_queue的使用及模拟实现
  17. 增加点赞手势图及提交按钮图标
  18. 亚马逊买家秀视频怎么上传?上传买家秀视频的作用是什么
  19. Linux文件系统--文件类型
  20. 从0到1的CTF之旅————Web(1)

热门文章

  1. ORACLE进制转换函数
  2. 【排序算法】堆排序——常规方法
  3. Altium Designer(六):Make Library
  4. php 判断设备是手机还是平板还是pc
  5. python3----如何简单地理解Python中的if __name__ == '__main__'
  6. 【转】linux常用命令:find、grep
  7. youphp学习整理
  8. [NOIP2009 最优贸易]
  9. 纯JS日历控件自动输入日期到TextBox、文本框当中
  10. 转:百度又开始踢新浪屁股了