word embedding是稠密的实数向量。Word embedding是一个词的语义表示,有效地编码了词的语义信息。

one-hot编码

在自然语言处理任务中,我们常常要与词打交道。那么在计算机上,我们怎么表示一个单词呢?一种思路是one-hot编码。假设词汇表为$V$,词汇表大小(vocab_size)为$N_V$。我们可以用向量$N_V$维向量$[1,0,0…,0,0]$来表示第一个词。以此类推,来表示所有的词。
这种方法有致命的弱点。首先是向量维度太大,太稀疏,效率太低。更要命的是,one-hot编码把词与词间看做完全独立的,没有表达出词与词之间的联系和相似性。而这正是我们想要的。
举个例子,我们想要构建一个语言模型。有以下三个句子

  • 数学家待在实验室里。
  • 物理学家待在实验室里。
  • 数学家解决了一个难题。

我们又看到一个新的句子:

  • 物理学家解决了一个难题。

我们希望语言模型可以学习到以下特点:

  • 数学家物理学家在一个句子中同样的位置出现。这两个词之间有某种语义上的联系
  • 数学家曾经出现在我们看到的这个新句子中物理学家出现的位置。

这就是语义相似性想表达的。语义相似性可以将没见过的数据与已经见过的数据联系起来,来解决语言数据的稀疏性问题。这个例子基于一个基本的语义学假设:出现在相似文本中的词汇在语义上是相互联系的。这称为distributional hypothesis
值得一提的是,在分类问题中,one-hot编码很适合用在类别的编码上。

word embedding

我们怎样编码来表达词汇的语义相似性呢?我们考虑词汇的semantic attributes。例如,物理学家和数学家学可能[头发不多,爱喝咖啡,会看论文,会说英语]。我们可以用这四个属性来编码物理学家数学家。$$q_物 = [0.9,0.8,0.98,0.8]$$$$q_数 = [0.91,0.89,0.9,0.85]$$
我们可以衡量这两个词之间的语义相似度:$$similarity(q_物,q_数) = \frac{q_物\cdot q_数}{|q_物| \cdot |q_数|}=cos(\phi) 其中\phi是两个向量之间的夹角。$$
但我们如何选择属性特征,并决定每个属性的值呢?深度学习的核心思想是神经网络学习特征表示,而不用人为指定特征。我们干脆将Word embedding作为神经网络的参数,让神经网络在训练的过程中学习Word embedding。
神经网络学到的Word embedding是潜在语义属性。也就是说,如果两个词在某个维度上都有大的值,我们并不知道这个维度代表了什么属性,这不能人为解释。这就是潜在语义属性的含义。
总的来说,Word embedding是一个词的语义表示,有效地编码了词的语义信息。

PyTorch实现word embedding

代码如下:

1
2
3
4
5
6
7
8
import torch 
import torch.nn as nn
from torch.autograd import Variable
# 词汇表字典
word_to_ix = {'The': 0, 'dog': 1, 'ate': 2, 'the': 3, 'apple': 4, 'Everybody': 5, 'read': 6, 'that': 7, 'book': 8}
vocab_size = len(word_to_ix)
embedding_dim = 15
word_embeddings = nn.Embedding(vocab_size,embedding_dim)

nn.Embedding()随机初始化了一个形状为[vocab_size,embedding_dim]的词向量矩阵,是神经网络的参数。
接下来我们查询”dog”这个词的向量表示。

1
2
3
4
dog_idx = torch.LongTensor([word_to_ix['dog']]) #注意输入应该是一维数组。
dog_idx = Variable(dog_idx)
dog_embed = word_embeddings(dog_idx) #注意不是索引
print(dog_embed)

上述代码中,要访问dog的词向量,要得到一个Variable。word_embeddings的输入应该是一个一维tensor。
接下来,我们查询一句话的向量表示。

1
2
3
4
5
6
sent = 'The dog ate the apple'.split()
sent_idxs = [word_to_ix[w] for w in sent]
sent_idxs = torch.LongTensor(sent_idxs)
sent_idxs = Variable(sent_idxs)
sent_embeds = embeds(sent_idxs)
print(sent_embeds)

pytorch加载预训练词向量

之前的方法中,词向量是随机初始化的,作为模型参数在训练过程中不断优化。通常我们要用到预训练的词向量,这样可以节省训练时间,并可能取得更好的训练结果。下面介绍两种加载预训练词向量的方式。
方式一:

1
2
3
4
import torch 
word_embeddings = torch.nn.Embedding(vocab_size,embedding_dim) #创建一个词向量矩阵
pretrain_embedding = np.array(np.load(np_path),dtype = 'float32') #np_path是一个存储预训练词向量的文件路径
word_embeddings.weight.data.copy_(troch.from_numpy(pretrain_embedding)) #思路是将np.ndarray形式的词向量转换为pytorch的tensor,再复制到原来创建的词向量矩阵中

方式二:

1
2
word_embeddings = torch.nn.Embedding(vocab_size,embedding_dim) #创建一个词向量矩阵
word_embeddings.weight = nn.Parameter(torch.FloatTensor(pretrain_embedding))

涉及函数详解

numpy()与from_numpy()

1
torch.from_numpy(ndarray) $\to$ tensor
  • 作用:numpy桥,将numpy.ndarray转换为pytorch的tensor.返回的张量与numpy.ndarray共享同一内存空间,修改一个另一个也会被修改。
1
tensor.numpy()
  • 作用:numpy桥,将pytorch的tensor转换为numpy.ndarray.二者共享同一内存空间,修改一个另一个也会被修改。

举个例子:

1
2
3
a = np.arange(5)
b = torch.from_numpy(a)
c = b.numpy()

tensor.copy_(src)

  • 作用:将src中的元素复制到tensor并返回。两个tensor应该有相同数目的元素和形状,可以是不同数据类型或存储在不同设备上。

举个例子:

1
2
3
a = torch.randn(1,5)
b = torch.randn(1,5)
b.copy_(a)

参考链接