第五章(1):词嵌入进阶之Glove模型讲解与pytorch实现
欢迎大家来到安静到无声的 《基于pytorch的自然语言处理入门与实践》,如果对所写内容感兴趣请看《基于pytorch的自然语言处理入门与实践》系列讲解 - 总目录,同时这也可以作为大家学习的参考。欢迎订阅,请多多支持!
目录标题
1. Glove模型的概述
在自然语言处理领域,"GloVe"指的是Global Vectors for Word Representation(全局向量词表示)的缩写。该模型是一种用于将单词表示为向量的技术,它旨在将语义上相关的单词捕捉到同一向量空间中。GloVe模型的核心思想是利用大量的语料库数据,通过计算共现矩阵来建立单词之间的联系。共现矩阵记录了在相邻的上下文窗口中,每对单词出现在一起的频率。
通过对这个共现矩阵进行数学处理,GloVe模型可以得到每个单词的词向量表示。与其他词向量模型(如Word2Vec)不同,GloVe模型不仅考虑上下文窗口内的单词,还考虑了全局语料库中所有单词对之间的共现关系。这使得GloVe能够更好地捕捉到相似单词之间的关系和语义信息。通过使用GloVe模型,可以将单词表示为连续的向量空间中的点,且这些向量之间的距离反映了它们之间的语义关系。
2. 共现矩阵
GloVe模型的核心是共现矩阵,它用于记录语料库中每对单词的共现频率。共现矩阵是一个方阵,其中的每个元素表示两个单词在上下文窗口内同时出现的次数。具体来说,假设我们有一个包含 N N N个单词的语料库,并且假设我们的上下文窗口(window length)大小为 k k k(即考虑每个单词的前后 k k k个单词作为上下文)。那么共现矩阵 X X X的大小为 N × N N×N N×N。
X X X的每个元素 X i j X_{ij} Xij表示j在词i上下文出现的次数,此外 X i j X_{ij} Xij可能与 X j i X_{ji} Xji不相等,因为单词 i i i和 j j j的上下文窗口可能不重叠。共现矩阵的构建是通过遍历整个语料库来实现的。在遍历语料库的过程中,每当我们发现两个单词在某个上下文窗口内同时出现时,我们就在共现矩阵中相应的位置增加计数。
一旦得到了共现矩阵 X X X,GloVe模型会进一步对 X X X进行处理,以获得最终的单词向量表示。这个处理过程包括使用矩阵分解技术(如奇异值分解或因子分解机)来学习单词的向量表示,使得这些向量能够捕捉到单词之间的语义关系。
例如:语料库如下:
• I like deep learning.
• I like NLP.
• I enjoy flying.
则共现矩阵表示如下:(使用对称的窗函数(左右window length都为1) )
例如:中心词是like,I和deep是上下文。
3 词向量和共现矩阵的近似关系
我们定义一些变量表示
- X i j X_{ij} Xij表示 j j j在词 i i i上下文出现的次数;
- X i = ∑ k X i k \mathrm{X_{i}=\sum_{k}X_{ik}} Xi=∑kXik表示任意单词 k k k出现在 i i i上下文的次数;
- P i j = P ( j ∣ i ) = X i j X i \mathrm{P_{ij}~=~P\left(j|i\right)=\frac{X_{ij}}{X_i}} Pij = P(j∣i)=XiXij表示单词 j j j出现在单词 i i i上下文的概率。
例子:通过一个简单的例子来展示是如何从共现概率抽取出某种语义的,考虑两个单词 i i i和 j j j之间的某种特殊兴趣。 假设我们对热力学感兴趣,然后我们令 i = i c e i=ice i=ice(冰)和 j = s t e a m j=steam j=steam(蒸汽),两个单词之间的关系能通过研究它们基于不同单词 k k k得到的共现矩阵概率之比来确定。令 k = s o l i d k=solid k=solid(固体),我们期望 P i k / P j k P_{ik}/P_{jk} Pik/Pjk的值很大;同理假设 k = g a s k=gas k=gas(气体),我们期望 P i k / P j k P_{ik}/P_{jk} Pik/Pjk的值很小;而对于那些与两者都相关或都不相关的单词比如 k = w a t e r k=water k=water或 k = f a s h i o n k=fashion k=fashion, P i k / P j k P_{ik}/P_{jk} Pik/Pjk的值应该接近1。如下表所示为在大语料库中计算的概率,与我们的期望很符合。
上表显示了在大语料库中的这些概率值,以及它们的比值,这些比值肯定了我们的期望。与原始概率相比,比值更能区分相关的单词(比如solid与gas)和不相关的单词(water与fashion),并且也能更好地区分这两个相关的单词。
- 观察上式,注意到比值 P i k / P j k P_{ik}/P_{jk} Pik/Pjk依赖于3个单词 i i i、 j j j和 k k k,那么常用的模型如下式:
F ( w i , w j , w k ∼ ) = P i k P j k \mathrm{F\left(w_i,w_j,\stackrel{\sim}{w_k}\right)=\frac{P_{ik}}{P_{jk}}} F(wi,wj,wk∼)=PjkPik
其中 w w w为词向量。 - 将 P i k P_{ik} Pik和 P j k P_{jk} Pjk的差距(距离)在向量空间中表现出来,可使用两者之间的差,即:
F ( w i − w j , w ~ k ) = P i k P j k F(w_i-w_j,\tilde{w}_k)=\frac{P_{ik}}{P_{jk}} F(wi−wj,w~k)=PjkPik - 因为 P i k / P j k P_{ik}/P_{jk} Pik/Pjk是标量,但是前者是向量,我们可以将两个向量做内积,将左侧也转化为标量,如下式所示:
F ( ( w i − w j ) T w k ∼ ) = P i k P j k \mathrm{F\left((w_i-w_j)^T\stackrel{\sim}{w_k}\right)=\frac{P_{ik}}{P_{jk}}} F((wi−wj)Twk∼)=PjkPik这样也便于计算函数 F ( ⋅ ) F(·) F(⋅)。 - 构造前后关系。考虑到 w i w_i wi和 w k w_k wk一个作为中心词一个作为上下文词,它们的角色是可以互换的,公式在这种角色互换下应保持不变的形式,任意词作为中心词和背景词的词向量应该相等。词与词之间共现次数矩阵应该对称:因此,公式需要继续变换,即
F ( ( w i − w j ) T w ~ k ) = F ( w i T w ~ k ) F ( w j T w ~ k ) F ( w i T w ~ k ) = P i k = X i k X i . \begin{aligned}F\left((w_i-w_j)^T\tilde{w}_k\right)&=\frac{F(w_i^T\tilde{w}_k)}{F(w_j^T\tilde{w}_k)}\\\\F(w_i^T\tilde{w}_k)&=P_{ik}=\frac{X_{ik}}{X_i}.\end{aligned} F((wi−wj)Tw~k)F(wiTw~k)=F(wjTw~k)F(wiTw~k)=Pik=XiXik. - 令 F ( ⋅ ) F(·) F(⋅)取指数函数exp,然后在将两边同时取log函数,可得:
w i T w k ∼ = log ( P i k ) = log ( X i k ) − log ( X i ) \mathrm{w_i^T\stackrel{\sim}{w_k}=\log(P_{ik})=\log(X_{ik})-\log(X_i)} wiTwk∼=log(Pik)=log(Xik)−log(Xi) - 由于 X i X_i Xi和 k k k无关,将 X i X_i Xi看做一个 w i w_i wi的偏置 b i b_i bi,再为 w k w_k wk添加另一个偏置 b k b_k bk,就可以解决对称性的问题,这样就可以使用词向量来表示词频:
w i T w ~ k + b i + b ~ k = log ( X i k ) w_{i}^{T}\tilde{w}_{k}+b_{i}+\tilde{b}_{k}=\log(X_{ik}) wiTw~k+bi+b~k=log(Xik) - 当某两个单词没有共现情况时,比值会出现0,为解决 l o g 0 log0 log0为无穷小的问题,作者利用均方误差(最小二乘法)计算损失,并且引入了权重函数 f ( X i j ) \mathrm{f}\left(X_{\mathrm{ij}}\right) f(Xij)。
J = ∑ i , j = 1 V f ( X i j ) ( w i T w j ~ + b i + b j ~ − log X i j ) 2 \mathrm{J=\sum_{i,j=1}^Vf(X_{ij})(w_i^T\widetilde{w_j}+b_i+\widetilde{b_j}-\log X_{ij})^2} J=i,j=1∑Vf(Xij)(wiTwj +bi+bj −logXij)2
其中 V V V是词典大小,其中权重函数满足如下条件:
- 如果两个词没有出现过,则权重为0。
- f ( x ) f(x) f(x)为一个非递减的函数,经常出现的单词其权重值越大。
- f ( x ) f(x) f(x)对于较大的 x x x不能取太大的值,所以一些经常出现的词权重不会太大,比如一些停止词。
- 作者采用的分段函数如下:
f ( x ) = { ( x / x max ) α i f x < x m a x 1 otherwise . \mathrm{f(x)=\begin{cases}(x/x_{\max})^\alpha&\mathrm{~if~x<x_{max}}\\1&\text{otherwise }.&\end{cases}} f(x)={(x/xmax)α1 if x<xmaxotherwise .在论文中的 α \alpha α为0.75,图像如下所示:
模型的表现依赖于 x m a x x_{max} xmax,实验中 x m a x = 100 x_{max}=100 xmax=100,当 α \alpha α为0.75时效果较好。模型最终生成两个词向量,分别为 W W W和 W ~ \widetilde{W} W ,论文中作者采用了 W + W ~ W+\widetilde{W} W+W 作为最终的词向量。
4. 基于pytorch的Glove模型实现
以下代码主要来自GloVe之Pytorch实现_代码部分_glove pytorch_散人stu174的博客-CSDN博客,我进行了重新的执行,并加以详实注释!
4.1 预处理 ,构建一个词汇表和相应的索引
from collections import Counter
with open("train.txt","r",encoding="utf-8") as f:
corpus=f.read()
corpus=corpus.lower().split(" ") #一共有多少个单词
#设置词典大小
vocab_size=10000
#统计词的数量,原数据约237148个单词,用于记录语料库中出现频次最高的前vocab_size个词及其对应的频次。
word_count=dict(Counter(corpus).most_common(vocab_size))
#<unk>表示为登陆词(新词) 计算列表中每一个单词的词频数,并相加
word_count["<unk>"]=len(corpus)-sum(list(word_count.values()))
#建立词表索引
idx2word=list(word_count.keys())
word2idx={w:i for i,w in enumerate(idx2word)}
这段代码的作用是对给定的文本语料库进行处理,以构建一个词汇表(vocabulary)和相应的索引。
-
首先,代码导入了
collections
模块中的Counter
类。 -
接下来,代码使用
open()
函数打开名为 “train.txt” 的文本文件,并通过指定编码方式为 “utf-8” 读取文件内容,并将内容存储在变量corpus
中。 -
然后,代码将
corpus
字符串转换为小写,并使用.split(" ")
方法按照空格将其分割为单词列表。这一步的目的是为了方便对单词进行统计和处理。 -
接下来,代码设置了一个变量
vocab_size
,用于指定词汇表的大小,即最多包含多少个不同的单词。 -
然后,代码使用
Counter(corpus)
统计单词在corpus
中出现的次数,并通过调用.most_common(vocab_size)
方法,获取出现频次最高的前vocab_size
个单词及其对应的频次,并将结果以字典的形式存储在word_count
变量中。 -
接下来,代码计算未被记录在
word_count
中的单词的总频次,并将其存储在word_count["<unk>"]
中。这里的<unk>
表示未登录词或者新词。 -
最后,代码将
word_count
字典中的键(即单词)转换为列表,并存储在idx2word
变量中。然后,使用字典推导式生成一个名为word2idx
的字典,该字典将词汇表中的每个单词映射为一个唯一的索引。
通过这段代码,你可以得到一个词汇表及相应的索引,可用于后续对文本进行处理、分析或建模等任务。
4.2 构建共现矩阵(co-occurrence matrix)
import numpy as np
import sys
window_size=5
coMatrix=np.zeros((vocab_size,vocab_size),np.int32)
print("共现矩阵占内存大小为:{}MB".format(sys.getsizeof(coMatrix)/(1024*1024)))
#语料库进行编码
corpus_encode=[word2idx.get(w,1999) for w in corpus] #是一个列表
#遍历语料库
for i,center_word in enumerate(corpus_encode):
#这一步和word2vec一样,提取背景词,但是后面有要防止边界越界
pos_indices=list(range(i-window_size,i))+list(range(i+1,i+1+window_size))
pos_indices=[idx%len(corpus_encode) for idx in pos_indices] #对pos_indices中的每个索引进行取模运算,将其限制在区间范围内。这是因为corpus的长度是20,所以索引不能超过这个范围。
context_word=[corpus_encode[idx] for idx in pos_indices] #这段代码的意思是根据上面提到的上下文位置索引列表pos_indices,从corpus中选择对应位置的词,将它们存储在列表context_word中。
#context_word代表当前center_word周围的所有关联词
for word_idx in context_word:
coMatrix[center_word][word_idx]+=1 #可以理解为横坐标的中心词,纵坐标为上下文词出现的频数
if((i+1)%1000000==0):
print("已完成{}/{}".format(i+1,len(corpus_encode)))
共现矩阵占内存大小为:381.46983337402344MB
已完成1000000/15313011
已完成2000000/15313011
已完成3000000/15313011
已完成4000000/15313011
已完成5000000/15313011
已完成6000000/15313011
已完成7000000/15313011
已完成8000000/15313011
已完成9000000/15313011
已完成10000000/15313011
已完成11000000/15313011
已完成12000000/15313011
已完成13000000/15313011
已完成14000000/15313011
已完成15000000/15313011
这段代码的作用是构建一个共现矩阵(co-occurrence matrix),用于记录语料库中每个单词与其周围上下文单词的共现频次。
-
首先,代码创建了一个大小为
(vocab_size, vocab_size)
的全零矩阵coMatrix
,用于存储共现矩阵。其中,vocab_size
表示词汇表的大小,即单词的数量。 -
然后,代码使用
sys.getsizeof(coMatrix)/(1024*1024)
计算出coMatrix
占用内存的大小,并通过print()
函数输出结果。 -
接下来,代码对语料库进行编码。通过遍历
corpus
中的每个单词,使用word2idx.get(w,1999)
将其转换为对应的索引,并将结果存储在列表corpus_encode
中。如果某个单词不存在于词汇表中,则将其编码为 1999,这里的 1999 是一个特殊的索引值,用于表示未登录词或者新词。 -
然后,代码开始遍历
corpus_encode
列表,对每个中心词进行处理。为了提取中心词周围的背景词,代码创建了一个名为pos_indices
的索引列表。该列表包含了当前中心词位置前后window_size
范围内的索引值。需要注意的是,为了避免索引越界,代码使用了取模运算将索引限制在语料库范围内(长度为len(corpus_encode)
)。 -
接下来,代码根据
pos_indices
列表从corpus_encode
中选择对应位置的词,并将它们存储在列表context_word
中。这样,context_word
就代表了当前中心词周围的所有背景词。 -
然后,代码通过遍历
context_word
列表,将每个背景词与当前中心词在coMatrix
中的对应位置加一。这一操作可以理解为,将共现矩阵中横坐标为中心词、纵坐标为上下文词的位置的值加一。 -
最后,代码通过
(i+1) % 1000000 == 0
的判断,每处理 1000000 个中心词,输出一条提示信息,指示已经完成的进度。
通过这段代码,你可以得到一个共现矩阵,其中记录了语料库中每个单词与其周围上下文单词的共现频次。
4.3 构建惩罚系数矩阵
# 检查矩阵是否为对称矩阵
is_symmetric = np.allclose(coMatrix, coMatrix.T)
print("是否为对称阵? :", is_symmetric)
del corpus,corpus_encode #释放内存
是否为对称阵? : True
xMax=100 #出现的最大值,依照原始论文所建立
alpha=0.75 #惩罚系数
wMatrix=np.zeros_like(coMatrix,np.float32) #惩罚系数矩阵
print("惩罚系数矩阵占内存大小为:{}MB".format(sys.getsizeof(wMatrix)/(1024*1024)))
for i in range(vocab_size):
for j in range(vocab_size):
if(coMatrix[i][j]>0): #判断频数是否是大于0
wMatrix[i][j]=(coMatrix[i][j]/xMax)**alpha if coMatrix[i][j]<xMax else 1
if((i+1)%1000==0):
print("已完成{}/{}".format(i+1,vocab_size))
惩罚系数矩阵占内存大小为:381.46983337402344MB
已完成1000/10000
已完成2000/10000
已完成3000/10000
已完成4000/10000
已完成5000/10000
已完成6000/10000
已完成7000/10000
已完成8000/10000
已完成9000/10000
已完成10000/10000
这段代码的作用是根据共现矩阵 coMatrix
构建一个惩罚系数矩阵 wMatrix
,用于对共现频次进行惩罚。
-
首先,代码定义了两个参数
xMax
和alpha
。xMax
表示共现频次的最大值,根据原始论文建议进行设定。alpha
是一个惩罚系数,用于控制共现频次的惩罚程度。 -
然后,代码创建了一个与
coMatrix
大小相同、元素类型为浮点数的全零矩阵wMatrix
,用于存储惩罚系数。 -
接下来,代码通过
sys.getsizeof(wMatrix)/(1024*1024)
计算出wMatrix
占用内存的大小,并通过print()
函数输出结果。 -
然后,代码使用两重循环遍历共现矩阵中的每个元素。如果共现频次大于 0,则根据公式
(coMatrix[i][j]/xMax)**alpha if coMatrix[i][j]<xMax else 1
计算对应位置上的惩罚系数,并将结果赋值给wMatrix[i][j]
。这里的判断条件coMatrix[i][j]<xMax
用于控制共现频次超过xMax
的情况,即当共现频次大于等于xMax
时,惩罚系数设置为 1。 -
最后,代码通过
(i+1) % 1000 == 0
的判断,每处理 1000 个元素,输出一条提示信息,指示已经完成的进度。
通过这段代码,你可以得到一个惩罚系数矩阵 wMatrix
,其中记录了共现频次经过惩罚后的值。这个惩罚系数矩阵将用于后续的词向量计算。
4.4 创建用于训练的数据集
import torch
from torch.utils.data.dataset import Dataset
from torch.utils.data.dataloader import DataLoader
train_set=[]
for i in range(vocab_size):
for j in range(vocab_size):
if(coMatrix[i][j]!=0): #仅仅收集频次不是0的数据
train_set.append([i,j])
if((i+1)%1000==0):
print("已完成{}/{}".format(i+1,vocab_size))
已完成1000/10000
已完成2000/10000
已完成3000/10000
已完成4000/10000
已完成5000/10000
已完成6000/10000
已完成7000/10000
已完成8000/10000
已完成9000/10000
已完成10000/10000
这段代码的作用是创建一个用于训练的数据集 train_set
。其中,数据集中的每个样本由两个索引值组成,表示共现矩阵中频次不为零的元素的位置。
-
首先,代码定义了一个
batch_size
,表示每个批次的样本数量。 -
然后,代码创建了一个空列表
train_set
,用于存储数据集中的样本。 -
接下来,代码使用两重循环遍历共现矩阵中的每个元素。如果某个元素的频次不等于零,则将其所在位置的索引值
[i,j]
添加到train_set
中。这样,train_set
中就记录了那些频次不为零的元素在共现矩阵中的位置。 -
最后,代码通过
(i+1) % 1000 == 0
的判断,每处理 1000 个元素,输出一条提示信息,指示已经完成的进度。
通过这段代码,你可以得到一个数据集 train_set
,其中包含了共现矩阵中频次不为零的元素的位置。接下来,你可以使用这个数据集进行训练,并根据需要使用 DataLoader
创建数据加载器,以便进行批次化的训练。
class dataset(Dataset):
def __init__(self,coMatrix,wMatrix,train_set):
super(dataset,self).__init__()
self.coMatrix=torch.Tensor(coMatrix)
self.wMatrix=torch.Tensor(wMatrix)
self.train_set=torch.Tensor(train_set)
def __len__(self):
return len(self.train_set)
def __getitem__(self,idx):
#中心词和背景词
i,k=train_set[idx]
#共现频次
x_ik=self.coMatrix[i][k]
#惩罚系数
w=self.wMatrix[i][k]
return i,k,x_ik,w
这段代码定义了一个名为 dataset
的类,继承自 torch.utils.data.Dataset
。这个类用于创建自定义的数据集类,以便在训练过程中使用。
类的构造方法 __init__
接受三个参数:coMatrix
、wMatrix
和 train_set
。在构造方法内,通过调用父类的构造方法 super(dataset, self).__init__()
初始化基类。然后,将传入的 coMatrix
、wMatrix
和 train_set
转换为 torch.Tensor
并分别赋值给类的属性 self.coMatrix
、self.wMatrix
和 self.train_set
,以便在类的其他方法中使用。
类还实现了两个方法:__len__
和 __getitem__
。
__len__
方法返回数据集的长度,即共有多少个样本。它简单地返回 self.train_set
的长度。
__getitem__
方法根据索引 idx
返回数据集中指定位置的样本。首先,根据索引 idx
从 self.train_set
中获取对应的中心词和背景词的索引值 i
和 k
。然后,通过索引 i
和 k
从 self.coMatrix
中获取共现频次 x_ik
,并从 self.wMatrix
中获取惩罚系数 w
。最后,返回 i
、k
、x_ik
、w
这四个值作为样本。
通过这个数据集类,你可以将共现矩阵、惩罚系数矩阵和训练集整合在一起,以便在模型训练过程中方便地获取对应的训练样本。你可以使用 DataLoader
将该数据集传入,并设置相应的批次大小、并行加载等参数,从而实现数据的批次化加载和训练。
4.5 构建一个基于 GloVe 模型的词向量训练模型
batch_size=10
data=dataset(coMatrix,wMatrix,train_set)
dataloader=DataLoader(data,batch_size,shuffle=True,drop_last=True,num_workers=0)
del coMatrix,wMatrix
import torch.nn as nn
from torch.nn.modules import Module
class GloVe(Module):
def __init__(self,vocab_size,d_model=50):
super(GloVe,self).__init__()
self.vocab_size=vocab_size
self.d_model=d_model
#中心词矩阵和背景词矩阵
self.V=nn.Embedding(vocab_size,d_model)
self.U=nn.Embedding(vocab_size,d_model)
#中心词偏置和背景词偏置
self.B_v=nn.Embedding(vocab_size,1)
self.B_u=nn.Embedding(vocab_size,1)
#随机初始化参数(这种初始化方式收敛更快),embedding原来是默认(0,1)正态分布
initrange = 0.5 / self.vocab_size
self.V.weight.data.uniform_(-initrange, initrange)
self.U.weight.data.uniform_(-initrange, initrange)
self.B_v.weight.data.uniform_(-initrange, initrange)
self.B_u.weight.data.uniform_(-initrange, initrange)
def forward(self,i,k,x_ik,w):#输入的是中心词,背景词,共现频数,惩罚系数
#i[batch],k[batch],x_ik[batch],f_x_ik[batch]
v_i=self.V(i)
u_k=self.U(k)
b_i=self.B_v(i)
b_k=self.B_u(k)
#v_i[batch,d_model] u_k[batch,d_model] b_i[batch,1] b_k[batch,1]
#这里要清楚torch.mul()是矩阵按位相乘,两个矩阵的shape要相同
#torch.mm()则是矩阵的乘法
loss=(torch.mul(v_i,u_k).sum(dim=1)+b_i+b_k-torch.log(x_ik))**2
#loss[batch]
loss=loss*w*0.5
return loss.sum()
def get_embeding(self):
#两者相加输出,而非取其中一个
return self.V.weight.data.cpu().numpy()+self.U.weight.data.cpu().numpy()
这段代码定义了一个名为 GloVe
的类,继承自 torch.nn.Module
。GloVe
类是一个模型类,用于实现 GloVe(Global Vectors for Word Representation)模型。
在类的构造方法 __init__
中,接受两个参数 vocab_size
和 d_model
,分别表示词汇表大小和嵌入维度。在构造方法内,首先调用父类的构造方法 super(GloVe, self).__init__()
初始化基类。然后,定义了多个成员变量,包括 vocab_size
、d_model
、中心词矩阵 V
、背景词矩阵 U
、中心词偏置 B_v
和背景词偏置 B_u
。
V
和 U
是通过 nn.Embedding
定义的两个嵌入层,分别用于表示中心词和背景词的向量。B_v
和 B_u
是通过 nn.Embedding
定义的两个偏置层,用于表示中心词和背景词的偏置项。
接下来,在构造方法中使用均匀分布的随机数对上述参数进行初始化。其中,initrange
是一个初始范围参数,它的取值是 0.5 / vocab_size
。通过调用 self.V.weight.data.uniform_(-initrange, initrange)
等方法,对参数进行均匀分布的随机初始化。
forward
方法定义了前向计算过程。它接受 i
、k
、x_ik
和 w
四个输入参数,分别表示中心词、背景词、共现频数和惩罚系数。在方法内部,通过调用 self.V(i)
和 self.U(k)
分别获取中心词和背景词的向量表示,以及调用 self.B_v(i)
和 self.B_u(k)
获取中心词和背景词的偏置项。然后,使用这些参数计算损失函数,并返回损失值。
get_embeding
方法用于获取模型的嵌入层参数,返回中心词矩阵和背景词矩阵的和作为嵌入向量。
通过这个模型类,你可以构建一个基于 GloVe 模型的词向量训练模型。在模型的前向计算过程中,会根据输入的中心词和背景词,在嵌入层中查找对应的向量表示,并根据公式计算损失值。最后,通过反向传播更新模型参数,实现词向量的训练。
4.6 GloVe 模型的训练
import scipy
from torch.optim.adagrad import Adagrad
from sklearn.metrics.pairwise import cosine_similarity
d_model=100
model = GloVe(vocab_size, d_model).cuda() #创建模型
#寻找附近的词,和Word2vec一样
def nearest_word(word, embedding_weights):
index = word2idx[word]
embedding = embedding_weights[index]
cos_dis = np.array([scipy.spatial.distance.cosine(e, embedding) for e in embedding_weights],np.float)
return [idx2word[i] for i in cos_dis.argsort()[:10]]#找到前10个最相近词语
epochs = 10
optim = Adagrad(model.parameters(), lr=0.05) #选择Adagrad优化器
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
for epoch in range(epochs):
allloss=0
for step,(i,k,x_ik,w) in enumerate(dataloader):
#反向传播梯度更新
optim.zero_grad()
i=i.to(device)
k=k.to(device)
x_ik=x_ik.to(device)
w=w.to(device)
loss=model(i,k,x_ik,w)
loss.backward()
optim.step()
#记录总损失
allloss = allloss + loss
if((step+1)%2000==0):
print("epoch:{}, iter:{}, loss:{}".format(epoch+1,step+1,allloss/(step+1)))
if((step+1)%5000==0):
print("nearest to one is{}".format(nearest_word("one",model.get_embeding())))
nearest to one is['one', 'two', 'nine', 'eight', 'three', 'four', 'seven', 'five', 'six', 'zero']
epoch:10, iter:1282000, loss:1.2875440120697021
epoch:10, iter:1284000, loss:1.2875365018844604
nearest to one is['one', 'two', 'nine', 'eight', 'three', 'four', 'seven', 'five', 'six', 'zero']
epoch:10, iter:1286000, loss:1.2875158786773682
这段代码主要是使用了之前定义的 GloVe
模型进行词向量训练,并且在每个 epoch 结束后,输出损失值和找到的与单词 “one” 最相近的词语。
首先,通过 GloVe(vocab_size, d_model).cuda()
创建了一个模型对象 model
,并将其移动到 CUDA 设备上进行计算。
然后,定义了一个 nearest_word
函数,用于寻找与给定单词最相近的词语。该函数接受两个参数,即要查找的单词和词向量的权重矩阵 embedding_weights
。在函数内部,首先通过 word2idx[word]
获取给定单词的索引,然后从权重矩阵 embedding_weights
中获取对应的词向量表示 embedding
。接下来,计算给定单词与所有其他单词之间的余弦相似度,并根据相似度排序找到前 10 个最相近的词语,最后返回这些词语。
接下来,使用 Adagrad 优化器 Adagrad(model.parameters(), lr=0.05)
对模型参数进行优化。
然后,根据设备是否支持 CUDA 选择将数据放在 GPU 上还是 CPU 上。
接下来,开始训练过程,共进行 epochs
轮。在每个 epoch 中,对数据加载器 dataloader
进行迭代,迭代的结果包括中心词索引 i
、背景词索引 k
、共现频数 x_ik
和惩罚系数 w
。在每个步骤(step)中,首先将这些输入数据移动到对应的设备上。然后,通过调用模型的前向计算 model(i, k, x_ik, w)
获取损失值 loss
。接下来,通过调用 loss.backward()
进行反向传播,计算参数的梯度。最后,通过调用优化器的 optim.step()
方法更新模型参数。
在每个 epoch 的过程中,记录总损失 allloss
并输出当前的损失值以及迭代次数。当迭代次数达到 2000 的倍数时,输出损失值。当迭代次数达到 5000 的倍数时,输出与单词 “one” 最相近的词语。
通过这段代码,可以进行 GloVe 模型的训练,并且可以查找与指定单词最相近的词语。
参考
共现矩阵
Glove模型介绍 (cbww.cn)
GloVe模型理解_愤怒的可乐的博客-CSDN博客
GloVe模型原理简介 - 知乎 (zhihu.com)
《GloVe: Global Vectors for Word Representation》论文
一个基于PyTorch实现的Glove词向量的实例_pytorch glove_程序员的自我反思的博客-CSDN博客
GloVe之Pytorch实现_代码部分_glove pytorch_散人stu174的博客-CSDN博客
--------推荐专栏--------
🔥 手把手实现Image captioning
💯CNN模型压缩
💖模式识别与人工智能(程序与算法)
🔥FPGA—Verilog与Hls学习与实践
💯基于Pytorch的自然语言处理入门与实践