简介

词向量技术,也称为词嵌入技术(word-embedding),是一种将高维稀疏的向量压缩到低维稠密向量的技术。常见于自然语言处理领域对单词的预处理过程,例如将单词的one-hot向量是高维稀疏的,不但占用大量空间,而且向量之间提供的信息很少。但经过词嵌入技术生成的向量,不但是低维的并且包含了很多词语间的语义、语法的信息。因此在NLP中,往往会先针对语料生成相应的词向量,然后再把词向量喂入具体任务的神经网络。

Glove词向量是常用的词向量之一,由斯坦福大学发布,原版代码是C实现的,有兴趣的读者可以前往Glove官网下载。本文是根据Glove论文的原理,利用深度学习库PyTorch实现的一个简易版本,仅仅用作学习的示例。

相关的库

1、python3.6
2、torch
3、numpy
4、scipy
5、text8英文语料

Glove原理

在开始写代码之前,我们对Glove原理进行一次快速介绍。Glove词向量核心思想是利用词与词之间的共现次数来进行训练的,所谓共现就是在一个上下文窗口大小范围内两个词语同时出现的次数,把上下文窗口从语料库的头到尾遍历一次,就得到了一个全局的共现矩阵,然后再基于这个共现矩阵进行词向量的训练。

下面,我们来看看需要优化的目标函数:
J = ∑ i , j N f ( X i , j ) ( v i T w j + b i + b j − l o g ( X i , j ) ) 2 J = \displaystyle\sum_{i,j}^N f(X_{i,j})(v_i^Tw_j+b_i+b_j-log(X_{i,j}))^2 J=i,jNf(Xi,j)(viTwj+bi+bjlog(Xi,j))2
其中, N N N表示词汇表大小, X i , j X_{i,j} Xi,j表示词语i和词语j的共现次数, v i v_i vi表示中心词i对应的词向量, w j w_j wj表示上下文词j对应的上下文向量。 b i 、 b j bi、bj bibj是偏置项,而 f ( ⋅ ) f(·) f()表示权重函数,该函数会抑制共现次数过高的词对造成的影响。从目标函数可以看出,模型会训练出两个向量 v 、 w v、w vw,原文作者建议将这二者的和作为最后的词向量表示。

实现

1、加载语料库进行预处理
这一步主要是把语料加载到内存中,生成词汇表等。我们直接来看代码:

def getCorpus(filetype, size):if filetype == 'dev':filepath = '../corpus/text8.dev.txt'elif filetype == 'test':filepath = '../corpus/text8.test.txt'else:filepath = '../corpus/text8.train.txt'with open(filepath, "r") as f:text = f.read()text = text.lower().split()text = text[: min(len(text), size)]vocab_dict = dict(Counter(text).most_common(MAX_VOCAB_SIZE - 1))vocab_dict['<unk>'] = len(text) - sum(list(vocab_dict.values()))idx_to_word = list(vocab_dict.keys())word_to_idx = {word:ind for ind, word in enumerate(idx_to_word)}word_counts = np.array(list(vocab_dict.values()), dtype=np.float32)word_freqs = word_counts / sum(word_counts)print("Words list length:{}".format(len(text)))print("Vocab size:{}".format(len(idx_to_word)))return text, idx_to_word, word_to_idx, word_counts, word_freqs

内容很简单,因为语料库也不大,所以能直接加载到内存中。根据语料库内的单词,挑选出前2000个频率最高的词语构成词汇表,剩下的词语则用UNK代替。然后生成一个单词-序号、序号-单词的映射表、词频列表等,以便后续的查询操作。

2、计算词共现矩阵
词共现矩阵的计算方式如下:给出一个句子如"china is a wonderful country and everyone is kind"。假设中心词是wonderful,窗口大小是2,那么我我们增加"wonderful,is"、“wonderful,a”、“wonderful,country”、"wonderful,and"的共现次数,即: X w o n d e r f u l , i s X_{wonderful,is} Xwonderful,is+=1, X w o n d e r f u l , a X_{wonderful,a} Xwonderful,a+=1、 X w o n d e r f u l , c o u n t r y X_{wonderful,country} Xwonderful,country+=1等。接着把窗口向右移动,统计下一个中心词-上下文词的共现次数,直到遍历完整个语料库。

def buildCooccuranceMatrix(text, word_to_idx):vocab_size = len(word_to_idx)maxlength = len(text)text_ids = [word_to_idx.get(word, word_to_idx["<unk>"]) for word in text]cooccurance_matrix = np.zeros((vocab_size, vocab_size), dtype=np.int32)print("Co-Matrix consumed mem:%.2fMB" % (sys.getsizeof(cooccurance_matrix)/(1024*1024)))for i, center_word_id in enumerate(text_ids):window_indices = list(range(i - WINDOW_SIZE, i)) + list(range(i + 1, i + WINDOW_SIZE + 1))window_indices = [i % maxlength for i in window_indices]window_word_ids = [text_ids[index] for index in window_indices]for context_word_id in window_word_ids:cooccurance_matrix[center_word_id][context_word_id] += 1if (i+1) % 1000000 == 0:print(">>>>> Process %dth word" % (i+1))print(">>>>> Save co-occurance matrix completed.")return cooccurance_matrix

3、计算权重函数矩阵
由于经常出现的词语实际上不会提供太多的信息,比如to、the之类的停用词,此时他们与别的词的共现频率就会很高,对训练词向量是一个阻碍,因此需要抑制共现频率过高造成的影响,使其在一个合理的范围内。根据原论文所述,其权重函数形式如下:
f ( x ) = { ( x / x m a x ) 0.75 if  x < x m a x 1 if  x ≥ x m a x f(x) = \begin{cases} (x/xmax)^{0.75} &\text{if } x < xmax \\ 1 &\text{if } x ≥ xmax \end{cases} f(x)={(x/xmax)0.751ifx<xmaxifxxmax
其中,xmax=100.0

对于 X i , j X_{i,j} Xi,j可以分别计算出一个对应的 f ( X i , j ) f(X_{i,j}) f(Xi,j),由此可以形成一个权重矩阵。实际上权重的计算可以放到前向传播的时候实时计算出来,不需要这样一个矩阵,但笔者这里为了节省前向传播的时间,提前计算出来并保存到矩阵内,等需要用的时候直接查找即可。代码如下所示:

def buildWeightMatrix(co_matrix):xmax = 100.0weight_matrix = np.zeros_like(co_matrix, dtype=np.float32)print("Weight-Matrix consumed mem:%.2fMB" % (sys.getsizeof(weight_matrix) / (1024 * 1024)))for i in range(co_matrix.shape[0]):for j in range(co_matrix.shape[1]):weight_matrix[i][j] = math.pow(co_matrix[i][j] / xmax, 0.75) if co_matrix[i][j] < xmax else 1if (i+1) % 1000 == 0:print(">>>>> Process %dth weight" % (i+1))print(">>>>> Save weight matrix completed.")return weight_matrix

4、创建DataLoader
DataLoader是PyTorch的一个数据加载器,利用它可以很方便地把训练集分批、打乱训练集等,我们新建一个类WordEmbeddingDataset继承自torch.untils.data.Dataset,代码如下图所示:

class WordEmbeddingDataset(tud.Dataset):def __init__(self, co_matrix, weight_matrix):self.co_matrix = co_matrixself.weight_matrix = weight_matrixself.train_set = []for i in range(self.weight_matrix.shape[0]):for j in range(self.weight_matrix.shape[1]):if weight_matrix[i][j] != 0:# 这里对权重进行了筛选,去掉权重为0的项# 因为共现次数为0会导致log(X)变成nanself.train_set.append((i, j))   def __len__(self):'''必须重写的方法:return: 返回训练集的大小'''return len(self.train_set)def __getitem__(self, index):'''必须重写的方法:param index:样本索引 :return: 返回一个样本'''(i, j) = self.train_set[index]return i, j, torch.tensor(self.co_matrix[i][j], dtype=torch.float), self.weight_matrix[i][j]

可以看出,返回的样本包括了词语i和j在词汇表中的需要,以及他们的共现频率和权重,这样就可以直接用于前向传播的计算中。

5、创建训练模型
下面来创建我们的训练模型,在pytorch中创建模型很简单,只需要继承nn.Module,然后重写前向传播函数即可,由于pytorch有自动求导机制,所以我们不需要担心反向传播的问题。

class GloveModelForBGD(nn.Module):def __init__(self, vocab_size, embed_size):super().__init__()self.vocab_size = vocab_sizeself.embed_size = embed_size#声明v和w为Embedding向量self.v = nn.Embedding(vocab_size, embed_size)self.w = nn.Embedding(vocab_size, embed_size)self.biasv = nn.Embedding(vocab_size, 1)self.biasw = nn.Embedding(vocab_size, 1)#随机初始化参数initrange = 0.5 / self.embed_sizeself.v.weight.data.uniform_(-initrange, initrange)self.w.weight.data.uniform_(-initrange, initrange)def forward(self, i, j, co_occur, weight):#根据目标函数计算Loss值vi = self.v(i) #分别根据索引i和j取出对应的词向量和偏差值wj = self.w(j)bi = self.biasv(i)bj = self.biasw(j)similarity = torch.mul(vi, wj)similarity = torch.sum(similarity, dim=1)loss = similarity + bi + bj - torch.log(co_occur)loss = 0.5 * weight * loss * lossreturn loss.sum().mean()def gloveMatrix(self):'''获得词向量,这里把两个向量相加作为最后的词向量:return: '''return self.v.weight.data.numpy() + self.w.weight.data.numpy()

6、训练
下面是对上文的几个点进行串联,最后进行训练,具体的可以参考代码:

EMBEDDING_SIZE = 50      #50个特征
MAX_VOCAB_SIZE = 2000 #词汇表大小为2000个词语
WINDOW_SIZE = 5           #窗口大小为5NUM_EPOCHS = 10         #迭代10次
BATCH_SIZE = 10           #一批有10个样本
LEARNING_RATE = 0.05  #初始学习率
TEXT_SIZE = 20000000  #控制从语料库读取语料的规模text, idx_to_word, word_to_idx, word_counts, word_freqs = getCorpus('train', size=TEXT_SIZE)    #加载语料及预处理
co_matrix = buildCooccuranceMatrix(text, word_to_idx)    #构建共现矩阵
weight_matrix = buildWeightMatrix(co_matrix)             #构建权重矩阵
dataset = WordEmbeddingDataset(co_matrix, weight_matrix) #创建dataset
dataloader = tud.DataLoader(dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=0)
model = GloveModelForBGD(MAX_VOCAB_SIZE, EMBEDDING_SIZE) #创建模型
optimizer = torch.optim.Adagrad(model.parameters(), lr=LEARNING_RATE) #选择Adagrad优化器print_every = 10000
save_every = 50000
epochs = NUM_EPOCHS
iters_per_epoch = int(dataset.__len__() / BATCH_SIZE)
total_iterations = iters_per_epoch * epochs
print("Iterations: %d per one epoch, Total iterations: %d " % (iters_per_epoch, total_iterations))
start = time.time()
for epoch in range(epochs):loss_print_avg = 0iteration = iters_per_epoch * epochfor i, j, co_occur, weight in dataloader:iteration += 1optimizer.zero_grad()   #每一批样本训练前重置缓存的梯度loss = model(i, j, co_occur, weight)    #前向传播loss.backward()     #反向传播optimizer.step()    #更新梯度loss_print_avg += loss.item()
torch.save(model.state_dict(), WEIGHT_FILE)

实验结果

1、利用余弦相似度衡量向量之间的相似性
按照上述代码进行训练后,我们执行以下代码来看看训练得到的模型的效果怎么样。我们利用余弦相似度来衡量两个向量是否相似,如果两个词语是同属一类的词语,那么它们的向量的距离应该很近的。我们定义以下的函数:

def find_nearest(word, embedding_weights):index = word_to_idx[word]embedding = embedding_weights[index]cos_dis = np.array([scipy.spatial.distance.cosine(e, embedding) for e in embedding_weights])return [idx_to_word[i] for i in cos_dis.argsort()[:10]]#找到前10个最相近词语

然后列举几个词语,看看跟他们最相似的几个词语是什么,运行以下代码:

glove_matrix = model.gloveMatrix()
for word in ["good", "one", "green", "like", "america", "queen", "better", "paris", "work", "computer", "language"]:print(word, find_nearest(word, glove_matrix))

运行结果如下:

可以看出,在某些词语比如数字、颜色、计算机、形容词比较级等,取得了不错的效果。

2、利用向量之间的运算考察词向量的关联性
词向量有另外一个特性,即“man to king is woman to ?”,向量化表述就是 v ( c a n d i d a t e ) = v ( k i n g ) − v ( m a n ) + v ( w o m a n ) v(candidate) = v(king)-v(man)+v(woman) v(candidate)=v(king)v(man)+v(woman),也即是候选词的词向量可以由另外三个词向量通过加减运算得到。那么,显然上述的候选词应该是“queen”。我们利用这个特性来举几个例子:

def findRelationshipVector(word1, word2, word3):word1_idx = word_to_idx[word1]word2_idx = word_to_idx[word2]word3_idx = word_to_idx[word3]embedding = glove_matrix[word2_idx] - glove_matrix[word1_idx] + glove_matrix[word3_idx]cos_dis = np.array([scipy.spatial.distance.cosine(e, embedding) for e in glove_matrix])for i in cos_dis.argsort()[:5]:print("{} to {} as {} to {}".format(word1, word2, word3, idx_to_word[i]))findRelationshipVector('man', 'king', 'woman')
findRelationshipVector('america', 'washington', 'france')
findRelationshipVector('good', 'better', 'bad')

运行以上代码,可以得到以下结果:

可以看出,结果一般般,但还是能看得到模型在努力地筛选出了更为相关的信息,只不过准确度需要再进一步提高。

3、利用SVD进行词向量的降维并可视化显示
SVD技术常用于将高维向量降维,我们将词向量降到二维,然后把降维后的向量画到画板上,看看这些词向量有没有很好地聚合在一起。如果聚合在一起了,表示词向量是相近的,也即是模型有比较好地学习到了词语之间的语义、语法关系。运行以下代码并观察结果:

#数据降维以及可视化
candidate_words = ['one','two','three','four','five','six','seven','eight','night','ten','color','green','blue','red','black',                      'man','woman','king','queen','wife','son','daughter','brown','zero','computer','hardware','software','system','program','america','china','france','washington','good','better','bad']
candidate_indexes = [word_to_idx[word] for word in candidate_words]
choosen_indexes = candidate_indexes
choosen_vectors = [glove_matrix[index] for index in choosen_indexes]U, S, VH = np.linalg.svd(choosen_vectors, full_matrices=False)
for i in range(len(choosen_indexes)):
plt.text(U[i, 0], U[i, 1], idx_to_word[choosen_indexes[i]])coordinate = U[:, 0:2]
plt.xlim((np.min(coordinate[:, 0]) - 0.1, np.max(coordinate[:, 0]) + 0.1))
plt.ylim((np.min(coordinate[:, 1]) - 0.1, np.max(coordinate[:, 1]) + 0.1))
plt.show()


从结果图可以观察到,同类型语义相近的词语基本都靠得很近。

结论

通过对实验结果的观察,可以发现训练得到的Glove模型效果还不错,由于笔者设备的限制,仅仅对1500万个词语的语料库以及2000个词语的词汇表,采取了50个特征值,进行了10个轮次的训练。如果语料库更大、词汇表更大以及特征数量更多,相信训练出来的词向量将会有更好的效果。由于本文仅仅是作为一个学习的样例,所以Glove的训练到此为止,谢谢各位的阅读~

项目代码

最后贴出笔者的项目代码地址,包括了语料库以及模型等,有需要的同学可以自行下载:PyTorchGlove

一个基于PyTorch实现的Glove词向量的实例相关推荐

  1. 基于pytorch构建双向LSTM(Bi-LSTM)文本情感分类实例(使用glove词向量)

    学长给的代码,感觉结构清晰,还是蛮不错的,想以后就照着这样的结构走好了,记录一下. 首先配置环境 matplotlib==3.4.2 numpy==1.20.3 pandas==1.3.0 sklea ...

  2. 【Pytorch基础教程37】Glove词向量训练及TSNE可视化

    note Glove模型目标:词的向量化表示,使得向量之间尽可能多蕴含语义和语法信息.首先基于语料库构建词的共现矩阵,然后基于共现矩阵和GloVe模型学习词向量. 对词向量计算相似度可以用cos相似度 ...

  3. NLP【05】pytorch实现glove词向量(附代码详解)

    上一篇:NLP[04]tensorflow 实现Wordvec(附代码详解) 下一篇:NLP[06]RCNN原理及文本分类实战(附代码详解) 完整代码下载:https://github.com/ttj ...

  4. 详解GloVe词向量模型

      词向量的表示可以分成两个大类1:基于统计方法例如共现矩阵.奇异值分解SVD:2:基于语言模型例如神经网络语言模型(NNLM).word2vector(CBOW.skip-gram).GloVe.E ...

  5. 机器阅读理解笔记之glove词向量与attentive readerimpatient reader和bi-DAF

    glove词向量模型 词向量的表示可以分成两类: 基于统计方法 共现矩阵.svd 基于语言模型 神经网络语言模型,word2vector,glove,elmo  word2vector中的skip-g ...

  6. 2.8 GloVe词向量-深度学习第五课《序列模型》-Stanford吴恩达教授

    Glove 词向量 (GloVe Word Vectors) 你已经了解了几个计算词嵌入的算法,另一个在NLP社区有着一定势头的算法是GloVe算法,这个算法并不如Word2Vec或是Skip-Gra ...

  7. glove词向量的加载以及预处理

    1. glove词向量的加载 2. glove词向量的预处理 3. 碎碎念 这两天要做论文的实验,关于句向量的. 因为实验代码涉及到对词向量的处理,就记一下吧,其实之前也是看过的,但是中间做了另外一个 ...

  8. 使用glove词向量

    在我的个人博客上很早就把这篇文章写出来了,现在转到CSDN,作为word2vec的姊妹篇,无论你使用的是word2vec还是glove,两种方式都是通用的.仅作少许改动即可. 前段时间把word2ve ...

  9. 【NLP】基于GloVe词向量的迁移学习

    作者 | Kourosh Alizadeh 编译 | VK 来源 | Towards Data Science 在过去,我在为我的一个项目训练词向量,但我一直在碰壁.我在研究哲学史上的文本,试图找到可 ...

最新文章

  1. 作为程序员,这些实用工具你必须要知道!
  2. Spring集成–强大的拆分器聚合器
  3. 前端学习(1388):多人管理项目8user登录
  4. MySQL—修改数据库root用户密码
  5. 属于 Hadoop 的大数据时代已结束
  6. Cocos Creator 编辑器扩展
  7. [转载] Python - filter()用法
  8. ESPNet: Efficient Spatial Pyramid of Dilated Convolutions for Semantic Segmentation(自动驾驶领域轻量级模型)
  9. c语言课外读书笔记谭浩强,谭浩强C语言读书笔记
  10. 计算机控制系统第二章答案,计算机控制技术(第2版)部分课后题答案
  11. SPSS作业-检验两组数据有无显著
  12. 时间序列预测 | Python实现GAN时间序列数据生成建模
  13. winrm java客户端_Windows 远程管理WinRM | 学步园
  14. MDM数据血缘设计方案
  15. 深入浅出CChart 每日一课——快乐高四第五十九课 殊途同归,炫彩界面库之C代码风格
  16. 微型计算机延时,延迟时间
  17. 工业机器人应用编程考核设备
  18. SpringBoot中使用Easyexcel实现Excel导入导出功能(三)
  19. 八字易经算法之用JAVA实现完整排盘系统
  20. SAM-DETR源码讲解

热门文章

  1. 3DMAX解析愤怒的小鸟
  2. 投影仪如何选择?怎样选购家用投影仪
  3. 健身房训练计划—背部
  4. Python 爬取懂车帝详情页“全部车型模块信息”!懂车帝就火起来了吗?
  5. GIS基础制图之地形图
  6. win10,win11 下部署Vicuna-7B,Vicuna-13B模型,gpu cpu运行
  7. Axure中插入Highcharts动态图表
  8. 【批处理脚本】-2.4-打开命令start(典型应用:微信多开)
  9. 无人机飞控平台ArduPilot源码入门教程 — 简介
  10. 工具篇:TailScale免费实现远程设备互连(无费用方案,亲测,零基础安装),支持手机、Windows或linux系统、NAS