当前位置:   article > 正文

词嵌入(Word Embedding)原理详解

词嵌入

  词嵌入模型是自然语言处理(NLP)中语言模型与表征学习技术的统称。在自然语言处理过程中,我们需要将单词(word)映射到对应的向量,从而能够用于模型训练。通常情况下可以使用one-hot向量来表示单词,但是one-hot向量长度为单词表所有单词的个数,数量过于庞大,并且各个单词之间相似度为0,这与我们日常生活是很不符的(不同的单词之间可能会比较相近,在文本中经常在一起出现;也可能不相近,在文本距离较远)。所以选择新的方法来表示单词便显得尤为重要。

  对于上述问题,可以用词嵌入的方法来解决。使用词嵌入的基本思路是:先用one-hot编码等方法来标记单词,然后构建一个包含embedding层的神经网络,模型的输入和输出一般为在文本中位置相近的单词one-hot向量。训练完成中,将单词one-hot向量输入到embedding中,embedding的输出向量即为该单词的新的嵌入表示。这些向量一般情况下远远小于one-hot向量长度,并且可以用于度量各个单词之间的相似与类比关系。

一:基本原理

  兔兔先以下面的一个文本为例:

“rabbit are very lovely animals, and we should not hurt them  ”

  对于这样的文本,它包含11种单词,所以把单词按一定顺序排列,则每个单词可以由长度为11的向量表示。

  若单词表:[rabbit,are,very,lovely,animals,and,we,should,not,hurt,them],此时rabbit的one-hot表示为[1,0,0,0,0,0,0,0,0,0,0]

  之后,我们需要构造一个模型,以文本中相邻的单词为模型输入与输出来进行训练。然而,模型的输入与输出需要固定个数。在这里,兔兔引入Word2vec中常用的两种模型:跳词模型与连续词袋模型。

1.跳词模型

  跳词模型,它是通过文本中某个单词来推测前后几个单词。例如,根据‘rabbit’来推断前后的单词可能为‘a’,'is','eating','carrot'。在训练模型时我们在文本中选取若干连续的固定长度的单词序列,把前后的一些单词作为输出,中间的某个位置的单词作为输入。

2.连续词袋模型

  连续词袋模型与跳词模型恰好相反,它是根据文本序列中周围单词来预测中心词。在训练模型时,把序列中周围单词作为输入,中心词作为输出。

  对于词嵌入,emdedding层是模型的核心部分,它一般在整个网络第一层。在Pytorch中有专门的nn.embedding层来实现该部分,但实际上,embedding层的结构可以非常多样,最简单的使用仅仅一层全连接层也是可行的,embedding层理论上也可以是一个层数很多的网络。训练结束后,把某个单词的one-hots输入到embedding,embedding的输出为该单词嵌入表示。(embedding在训练时输入单词数固定,但是预测时可以输入一个单词,也可以接受多个单词输入,这也说明embedding结构较为特殊,在后面兔兔会详细讲述)

二:方法实现

  兔兔在下面案例中使用连续词袋模型,每次从文本中选取序列长度为9,输入单词数为,8,输出单词数为1,中心词位于序列中间位置。并且采用pytorch中的emdedding和自己设计embedding两种方法,词嵌入维度为50。文本节选自《The Tale Of Peter Rabbit》(彼得兔的故事),本文词总数:793,词汇数:401,需要的同学可以在资源中下载。

  对于文本数据,我们不考虑文本中标点符号,为了避免同一单词大小写不同的差异,一律将单词转成小写。

文本数据处理部分:

  1. import torch
  2. import re
  3. import numpy as np
  4. txt=[] #文本数据
  5. with open('peter_rabbit.txt',encoding='utf-8') as f:
  6. for line in f.readlines():
  7. l=line.strip()
  8. spilted_sentence=re.split(" |;|-|,|!|\'",l)
  9. for w in spilted_sentence:
  10. if w !='':
  11. txt.append(w.lower())
  12. vol=list(set(txt)) #单词表
  13. n=len(vol) #单词表单词数
  14. vol_dict=dict(zip(vol,np.arange(n))) #单词索引
  15. data=[]
  16. label=[]
  17. for i in range(784):
  18. in_words=txt[i:i+4]
  19. in_words.extend(txt[i+6:i+10])
  20. out_word=txt[i+5]
  21. in_one_hot=np.zeros((8,n))
  22. out_one_hot=np.zeros((1,n))
  23. out_one_hot[0,vol_dict[out_word]]=1
  24. for j in range(8):
  25. in_one_hot[j,vol_dict[in_words[j]]]=1
  26. data.append(in_one_hot)
  27. label.append(out_one_hot)
  28. class dataset:
  29. def __init__(self):
  30. self.n=784 #训练样本数
  31. def __len__(self):
  32. return self.n
  33. def __getitem__(self, item):
  34. traindata=torch.tensor(np.array(data),dtype=torch.float32) #运行model1此处用long,model2用float32.
  35. trainlabel=torch.tensor(np.array(label),dtype=torch.float32)
  36. return traindata[item],trainlabel[item]

  这部分代码兔兔保存在dataset.py文件下,它将原始文本进行拆分并以一定顺序拆成需要训练的输入数据(data)与输出数据(label),并以one-hot表示。

1.使用nn.Embedding构建模型

  对于nn.Embedding(),至少需要两个参数:num_embeddings与embedding_dim,表示词汇表大小与词嵌入维度。在这里参数为(401,50)。

  理论上说,nn.Embedding应该类似于一个输入维度为词汇表长度、输出为词嵌入维度的全连接层。我们可以通过以下方法验证。

  1. embed=nn.Embedding(401,200)
  2. print(list(embed.parameters())[0].data)
  3. print(list(embed.parameters())[0].data.shape)

  最终的参数的确表明它是一个单层的全连接网络。与普通全连接网络不同的是:它的每个神经元输入是一个词向量而不是数,这样它的输入维度可以是[batch_size,num_input_word,one_hot_dim]。所以它更像是共享相同参数的num_word个平行的全连接层,类似于卷积神经网络中的通道channel。所以embedding层输出的维度[batch,num_input_word,embed_dim]。将这个输入下一个全连接层时,需要改变embedding输出数据维度。该部分代码保存在model1.py中。

  1. import torch
  2. from torch import nn
  3. from torch.utils.data import DataLoader
  4. from dataset import dataset
  5. class model(nn.Module):
  6. def __init__(self):
  7. super().__init__()
  8. self.embed=nn.Embedding(401,50)
  9. self.fc1=nn.Linear(160400,100)
  10. self.act1=nn.ReLU()
  11. self.fc2=nn.Linear(100,401*1)
  12. self.act2=nn.Sigmoid()
  13. def forward(self,input):
  14. b,_,_=input.shape
  15. out=self.embed(input).view(b,1,-1)
  16. out=self.fc1(out)
  17. out=self.act1(out)
  18. out=self.fc2(out)
  19. out=self.act2(out)
  20. return out
  21. if __name__=='__main__':
  22. model=model()
  23. optim=torch.optim.Adam(params=model.parameters())
  24. Loss=nn.MSELoss()
  25. traindata=DataLoader(dataset(),batch_size=5,shuffle=True)
  26. for i in range(100):
  27. print('the {} epoch'.format(i))
  28. for d in traindata:
  29. yp=model(d[0])
  30. loss=Loss(yp,d[1])
  31. optim.zero_grad()
  32. loss.backward()
  33. optim.step()
  34. torch.save(model,'model_1.pkl')

2.自己构造embedding

  兔兔这里定义了一个含有层数为2的全连接层为embedding层,方法与前面embedding的效果相近。该部分代码保存在model2.py中。

  1. import torch
  2. from torch import nn
  3. from torch.utils.data import DataLoader
  4. from dataset import dataset
  5. import numpy as np
  6. class embedding(nn.Module):
  7. def __init__(self,in_dim,embed_dim):
  8. super().__init__()
  9. self.embed=nn.Sequential(nn.Linear(in_dim,200),
  10. nn.ReLU(),
  11. nn.Linear(200,embed_dim),
  12. nn.Sigmoid())
  13. def forward(self,input):
  14. b,c,_=input.shape
  15. output=[]
  16. for i in range(c):
  17. out=self.embed(input[:,i])
  18. output.append(out.detach().numpy())
  19. return torch.tensor(np.array(output),dtype=torch.float32).permute(1,0,2)
  20. class model(nn.Module):
  21. def __init__(self):
  22. super().__init__()
  23. self.embed=embedding(401,50)
  24. self.fc1=nn.Linear(400,4000)
  25. self.act1=nn.ReLU()
  26. self.fc2=nn.Linear(4000,401*1)
  27. self.act2=nn.Sigmoid()
  28. def forward(self,input):
  29. b,_,_=input.shape
  30. out=self.embed (input).reshape(b,-1)
  31. out=self.fc1 (out)
  32. out=self.act1(out)
  33. out=self.fc2(out)
  34. out=self.act2(out)
  35. out=out.view(b,1,-1)
  36. return out
  37. if __name__=='__main__':
  38. model=model()
  39. optim=torch.optim.Adam(params=model.parameters())
  40. Loss=nn.MSELoss()
  41. traindata=DataLoader(dataset(),batch_size=5,shuffle=True)
  42. for i in range(100):
  43. print('the {} epoch'.format(i))
  44. for d in traindata:
  45. yp=model(d[0])
  46. loss=Loss(yp,d[1])
  47. optim.zero_grad()
  48. loss.backward()
  49. optim.step()
  50. torch.save(model,'model_2.pkl')

三:总结

  词嵌入模型作为自然语言处理的一种方法,其种类广泛,并且不局限于兔兔本文讲述的方法。而这些词嵌入方法的思想基本大致相同。从本质上来说,词嵌入与自编码器有诸多相似之处,它都是将数据维度降低,并且通常以更加合理的方式来表示数据,在实际应用总,这类方法对于深度学习模型的训练及优化具有重要意义。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/360392
推荐阅读
相关标签
  

闽ICP备14008679号