参考
- https://wmathor.com/index.php/archives/1443/
- NLP之word2vec,使用tensorflow2.0
- 一文弄懂Word2Vec之skip-gram(含详细代码)
一、什么是Skip-gram算法
Skip-gram算法就是在给出目标单词(中心单词)的情况下,预测它的上下文单词(除中心单词外窗口内的其他单词,这里的窗口大小是2,也就是左右各两个单词)。
二、表示方法
1.one-hot向量
one-hot向量就是利用一个R^{|V|*1}向量来表示单词。|V|是词表中单词的数量。一个单词在英文词汇表中的索引位置是多少,那么相对应的那一行元素就是1,其他元素都是0。
2.词向量(word vector)
词向量就是用一组d维的向量代表单词。
3.单词矩阵
单词矩阵是所有单词的词向量的集合。
注意,我们这里要用到两个单词矩阵,一个是目标单词的词向量组成的矩阵,用 W表示。W的尺寸是d * V。
另外一个矩阵是由除目标单词外的其他单词词向量的转置组成的矩阵,用W’表示,尺寸是V * d,这里用与上一个W的尺寸相反,至于为什么我们后面解释。
另外需要说明的是,由于每一个单词都有可能作为目标单词或其他单词,因此,实际上这两个矩阵是分别包含所有单词的词向量的。
————————————————
版权声明:本文为CSDN博主「迪迦瓦特曼」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_43734080/article/details/121708679
4.单词相似度
5.softmax函数
我们需要知道的是softmax函数就是能够把输入转换为概率分布,也就是说使输入的实数变成分数。
三、具体实现
在Skip-gram中,同样先选定一个中心词,并把其他词作为这个中心词的上下文。如 图4 Skip-gram所示,把“Spiked”作为中心词,把“Pineapples、are、and、yellow”作为中心词的上下文。不同的是,在学习过程中,使用中心词的词向量去推理上下文,这样上下文定义的语义被传入中心词的表示中,如“pineapple → Spiked”, 从而达到学习语义信息的目的。
算法实现:https://blog.csdn.net/weixin_43734080/article/details/121708679
代码
# %%
# code by Tae Hwan Jung @graykode
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt
def random_batch():#随机打乱词汇和标签
random_inputs = []
random_labels = []
random_index = np.random.choice(range(len(skip_grams)), batch_size, replace=False)
# numpy.random.choice(a, size=None, replace=True, p=None)
# 从a(只要是ndarray都可以,但必须是一维的)中随机抽取数字,并组成指定大小(size)的数组
# replace:True表示可以取相同数字,False表示不可以取相同数字
# 数组p:与数组a相对应,表示取数组a中每个元素的概率,默认为选取每个元素的概率相同。
# 由于Word2Vec的输入是one - hot表示,所以我们先构建一个对角全1的矩阵,利用np.eye(rows)方法,其中的参数rows表示全1矩阵的行数,对于这个问题来说,语料库中总共有多少个单词,就有多少行
# 然后根据skip_grams每行第一列的值,取出相应全1矩阵的行。将这些取出的行,append到一个list中去,最终的这个list就是所有的样本 X。
# 标签不需要one - hot表示,只需要类别值,所以只用把skip_grams中每行的第二列取出来存起来即可
for i in random_index:
random_inputs.append(np.eye(voc_size)[skip_grams[i][0]]) # target
# np.eye()的函数,除了生成对角阵外,还可以将一个label数组,大小为(1, m)或者(m, 1)的数组,转化成one - hot数组。
# 例如它可以将类别总数为6的labels = [1, 2, 3, 0, 1, 1]
# 的数组转化成数组[
# [0, 1, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0], [0, 0, 0, 1, 0, 0], [0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0], [0, 1,
# 0, 0,
# 0, 0]]
# 这就是所谓的one - hot的形式。
random_labels.append(skip_grams[i][1]) # context word
return random_inputs, random_labels
# Model
class Word2Vec(nn.Module):
def __init__(self):
super(Word2Vec, self).__init__()
# W and WT is not Traspose relationship
self.W = nn.Linear(voc_size, embedding_size, bias=False) # voc_size > embedding_size Weight
self.WT = nn.Linear(embedding_size, voc_size, bias=False) # embedding_size > voc_size Weight
def forward(self, X):
# X : [batch_size, voc_size]
hidden_layer = self.W(X) # hidden_layer : [batch_size, embedding_size]
output_layer = self.WT(hidden_layer) # output_layer : [batch_size, voc_size]
return output_layer
if __name__ == '__main__':
batch_size = 2 # mini-batch size
embedding_size = 2 # embedding size
sentences = ["apple banana fruit", "banana orange fruit", "orange banana fruit",
"dog cat animal", "cat monkey animal", "monkey dog animal"]
word_sequence = " ".join(sentences).split()# 用空格分开,所有的单词 有重复单词
word_list = " ".join(sentences).split()
word_list = list(set(word_list)) #自动清除集合类型中的元素重复数据(set),以及元素排序
word_dict = {w: i for i, w in enumerate(word_list)}#编号,单词 : 编号
voc_size = len(word_list)
# Make skip gram of one size window
skip_grams = []
for i in range(1, len(word_sequence) - 1):
target = 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([target, w])
model = Word2Vec()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
# Training
for epoch in range(5000):
input_batch, target_batch = random_batch()
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()
for i, label in enumerate(word_list):
W, WT = model.parameters()
x, y = W[0][i].item(), W[1][i].item()#第一列为x,第二列为y
plt.scatter(x, y)
plt.annotate(label, xy=(x, y), xytext=(5, 2), textcoords='offset points', ha='right', va='bottom')
plt.show()