自然语言处理领域具有挑战性的原因有几个,但最主要也是最微不足道的一个是处理不是数字而是文本的数据。
因此,有必要将文本转换为数值,以尽可能保留单词的含义,而不会丢失单词在人类头脑中唤起的任何东西。
这种转换称为词嵌入。我们可以进入一个较低层次的分析并创建一个词嵌入,或者更高层次的分析,然后创建一个句子嵌入。这取决于我们选择了哪些元素。
迄今为止,最好的尝试是将单词映射成数字向量。目标是在向量空间中将相似的单词放在一起。我们可以使用余弦相似度来度量向量相似度。
让我们在代码中看到上图的示例。
import numpy as np
# vector representing the word 'beer'
vec1_beer = np.array([0.9, 0.82, 0.75])
# vector representing the word 'wine'
vec2_wine = np.array([0.5, 0.98, 0.92])
# vector representing the word 'house'
vec3_house = np.array([0.91, 0.11, 0.25])
def cosine_similarity(w1, w2):
return np.dot(w1,w2)/(np.dot(w1,w1)*np.dot(w2,w2))**0.5
print('Similarity between beer & wine: ', cosine_similarity(vec1_beer,vec2_wine)) # -> output: 0.947
print('Similarity between beer & house: ', cosine_similarity(vec1_beer,vec3_house)) # -> output: 0.807
print('Similarity between wine & house: ', cosine_similarity(vec2_winr, vec3_house)) # -> output: 0.581
你可以注意到啤酒和葡萄酒之间的相似性很高,这很有道理,对吧?
在本文中,我们将讨论非上下文嵌入方法,其中单词的表示不依赖于上下文。
例如,即使下面句子中的单词second有不同的含义,使用这些嵌入方法,其向量表示也将始终相同。
This is the second time that I try it
He just arrived one second later
如果你对有影响力的论文感兴趣,这里有一个简短的列表:
- Word2Vec (Mikolov et al., 2013);
- Global Vectors for Word Representation (GloVe) (Pennington et al., 2014);
- FastText ( Mikolov et al., 2016);
Word2Vec
2013年,通过Word2Vec,谷歌的Mikolov等人彻底改变了嵌入范式:从那时起,嵌入将是神经网络的权重,根据任务进行调整,以最小化一些损失。嵌入已成为一种神经网络算法。
然后,我们将要做的是训练一个简单的神经网络,在一个特定的任务上有一个单一的隐藏层。但请注意,我们不会使用网络来解决手头的任务,但我们感兴趣的只是表示词嵌入的网络的权重。
我们将训练NN做以下工作。给定句子中间的一个特定单词(输入单词),查看附近的单词(窗口大小),然后随机选取一个。经过训练后,神经网络将告诉我们词汇表中每个单词成为我们选择的附近单词的概率。
我们将训练一个具有单个隐藏层的简单神经网络来执行某项任务,但实际上我们不会将该神经网络用于我们训练它的任务!相反,目标只是学习隐藏层的权重(这与自动编码器的目标类似)。最后,我们将看到这些权重是我们正在尝试学习的单词向量。
例如,如果我们给训练过的网络输入单词New,那么像York和City这样的单词的输出概率将高于book和lightsaber。
Word2Vec可以使用两种不同的方法构建:
- CBOW:神经网络观察周围的单词并预测中间的单词。
- SkipGram:神经网络接收一个单词,然后尝试预测周围的单词。
SkipGram
首先,为了简单起见,让我们从三个单词的句子语料库中创建一个词汇表。
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
from torch.autograd import Variable
import matplotlib.pyplot as plt
dtype = torch.FloatTensor
# 3 Words Sentence (to semplify)
# All them form our text corpus
sentences = [ "i like dog", "i like cat", "i like animal",
"dog cat animal", "apple cat dog like", "dog fish milk like",
"dog cat eyes like", "i like apple", "apple i hate",
"apple i movie", "book music like", "cat dog hate", "cat dog like"]
# list all the words present in our corpus
word_sequence = " ".join(sentences).split()
print(word_sequence )
# build the vocabulary
word_list = list(set(word_sequence))
print(word_list)
word_dict = {w: i for i, w in enumerate(word_list)}
print(word_dict)
# Word2Vec Parameter
batch_size = 20 # To show 2 dim embedding graph
embedding_size = 2 # To show 2 dim embedding graph
voc_size = len(word_list)
例如,如果我们输入单词like,上下文单词将是I和dog。
# input word
j = 1
print("Input word : ")
print(word_sequence[j], word_dict[word_sequence[j]])
# context words
print("Context words : ")
print(word_sequence[j - 1], word_sequence[j + 1])
print([word_dict[word_sequence[j - 1]], word_dict[word_sequence[j + 1]]])
现在我们准备好准备数据了。我们将使用window_size=1,因此给定单词j,目标将仅为j-1和j+1。
# Make skip gram of one size window
skip_grams = []
for i in range(1, len(word_sequence) - 1):
input = word_dict[word_sequence[i]]
context = [word_dict[word_sequence[i - 1]], word_dict[word_sequence[i + 1]]]
for w in context:
skip_grams.append([input, w])
#lets plot some data
skip_grams[:6]
为跳板准备数据在构建模型之前,让我们分批构造数据,并将单词转换为one-hot向量。
np.random.seed(172)
def random_batch(data, size):
random_inputs = []
random_labels = []
random_index = np.random.choice(range(len(data)), size, replace=False)
for i in random_index:
# one-hot encoding of words
random_inputs.append(np.eye(voc_size)[data[i][0]]) # input
random_labels.append(data[i][1]) # context word
return random_inputs, random_labels
random_batch(skip_grams[:6], size=3)
# inputs: like , i, dog , context: i, dog, i
构建模型
这是我们模型的一部分。隐藏层神经元没有激活函数,但输出神经元使用softmax。
像PyTorch中的一样,让我们定义一个类来构建我们的神经网络。为了简单起见,我们的嵌入大小是2,这样我们可以在2维向量空间上绘制嵌入。
我们的神经网络的输入具有等于vocb_size的维度(因为我们使用的是one-hot向量)。此外,输出具有dimension=vocb_size,每个输出神经元的权重告诉我们表示特定神经元的单词接近输入中给定单词的概率。
# Model
class Word2Vec(nn.Module):
def __init__(self):
super(Word2Vec, self).__init__()
# parameters between -1 and + 1
self.W = nn.Parameter(-2 * torch.rand(voc_size, embedding_size) + 1).type(dtype) # voc_size -> embedding_size Weight
self.V = nn.Parameter(-2 * torch.rand(embedding_size, voc_size) + 1).type(dtype) # embedding_size -> voc_size Weight
def forward(self, X):
hidden_layer = torch.matmul(X, self.W) # hidden_layer : [batch_size, embedding_size]
output_layer = torch.matmul(hidden_layer, self.V) # output_layer : [batch_size, voc_size]
#return output_layer
return output_layer
model = Word2Vec()
# Set the model in train mode
model.train()
criterion = nn.CrossEntropyLoss() # Softmax (for multi-class classification problems) is already included
optimizer = optim.Adam(model.parameters(), lr=0.001)
是时候训练我们的模型了!
# Training
for epoch in range(5000):
input_batch, target_batch = random_batch(skip_grams, batch_size)
# new_tensor(data, dtype=None, device=None, requires_grad=False)
input_batch = torch.Tensor(input_batch)
target_batch = torch.LongTensor(target_batch)
optimizer.zero_grad()
output = model(input_batch)
# output : [batch_size, voc_size], target_batch : [batch_size] (LongTensor, not one-hot)
loss = criterion(output, target_batch)
if (epoch + 1)%1000 == 0:
print('Epoch:', '%04d' % (epoch + 1), 'cost =', '{:.6f}'.format(loss))
loss.backward()
optimizer.step()
一旦训练结束,词嵌入被存储在模型参数中。
# Learned W
W, _= model.parameters()
print(W.detach())
输出应该是这样的。
绘制嵌入
for i, word in enumerate(word_list):
W, _= model.parameters()
W = W.detach()
x,y = float(W[i][0]), float(W[i][1])
plt.scatter(x, y)
plt.annotate(word, xy=(x, y), xytext=(5, 2), textcoords='offset points', ha='right', va='bottom')
plt.show()
好吧,这个图表和嵌入没有意义。这只是一个虚构的例子。我们希望彼此之间有相似的词。你可以做的是使用诸如GloVe之类的预训练嵌入,并在你的项目中使用它们!
感谢阅读!