背景和引入
在所有的 NLP 任务中,首先面临的第一个问题是我们该如何表示单词。这种表示将作为 inputs 输入到特定任务的模型中,如机器翻译,文本分类等典型 NLP 任务。
同义词表达单词
一个很容易想到的解决方案是使用同义词来表示一个单词的意义。 比如 WordNet, 一个包含同义词(和有 “is a” 关系的词)的词库。
导包
!pip install --user -U nltk
!python -m nltk.downloader popular
如获取 "good" 的同义词
from nltk.corpus import wordnet as wn
poses = { 'n':'noun', 'v':'verb', 's':'adj (s)', 'a':'adj', 'r':'adv'}
for synset in wn.synsets("good"):
print("{}: {}".format(poses[synset.pos()],", ".join([l.name() for l in synset.lemmas()])))
2
3
4
如获取与 “pandas” 有 "is a" 关系的词
panda = wn.synset("panda.n.01")
hyper = lambda s: s.hypernyms()
list(panda.closure(hyper))
2
3
WordNet 的问题
- 单词与单词之间缺少些微差异的描述。比如 “高效” 只在某些语境下是 "好" 的同义词
- 丢失一些词的新含义。比如 “芜湖”,“蚌埠” 等词的新含义
- 相对主观
- 需要人手动创建和调整
- 无法准确计算单词的相似性
one-hot 编码
在传统 NLP 中,人们使用 one-hot 向量(一个向量只有一个值为 1,其余的值为 0)来表示单词 如:motel = [0 0 0 0 0 0 0 0 0 0 1 0 0 0 0]
如:hotel = [0 0 0 0 0 0 0 1 0 0 0 0 0 0 0]
one-hot 向量的维度是词汇表的大小(如:500,000)
注:上面示例词向量的维度为方便展示所以比较小
one-hot 向量表示单词的问题:
- 这些词向量是正交向量,无法通过数学计算(如点积)计算相似性
- 依赖 WordNet 等同义词库建立相似性效果也不好
dense word vectors 表达单词
如果我们可以使用某种方法为每个单词构建一个合适的 dense vector,如下图,那么通过点积等数学计算就可以获得单词之间的某种联系
Word2vec
语言学基础
首先,我们引入一个上世纪五十年代,一个语言学家的研究成果:“一个单词的意义由它周围的单词决定”
“You shall know a word by the company it keeps” (J. R. Firth 1957: 11)
这是现代 NLP 中一个最为成功的理念。
我们先引入上下文 context 的概念:当单词 w 出现在文本中时,其上下文 context 是出现在 w 附近的一组单词(在固定大小的窗口内),如下图
这些上下文单词 context words 决定了 banking 的意义
Word2vec 概述
Word2vec (Mikolov et al. 2013) 是一个用来学习 dense word vector 的算法:
- 我们使用大量的文本语料库
- 词汇表中的每个单词都由一个词向量 dense word vector 表示
- 遍历文本中的每个位置 t,都有一个中心词 c(center) 和上下文词 o(“outside”),如图 1 中的 banking
- 在整个语料库上使用数学方法最大化单词 o 在单词 c 周围出现了这一事实,从而得到单词表中每一个单词的 dense vector
- 不断调整词向量 dense word vector 以达到最好的效果
Skip-gram(SG)
Word2vec 包含两个模型,Skip-gram 与 CBOW。下面,我们先讲 Skip-gram 模型,用此模型详细讲解概述中所提到的内容。
概述中我们提到,我们希望最大化单词 o 在单词 c 周围出现了这一事实,而我们需要用数学语言表示 “单词 o 在单词 c 周围出现了” 这一事件,如此才能进行词向量的不断调整。
很自然地,我们需要使用概率工具描述事件的发生,我们想到用条件概率
下图展示了以 “into” 为中心词,窗口大小为 2 的情况下它的上下文词。以及相对应的
我们滑动窗口,再以 banking 为中心词
那么,如果我们在整个语料库上不断地滑动窗口,我们可以得到所有位置的
此式还可以依图 3 写为:
加 log, 加负号,缩放大小可得:
上式即为 skip-gram 的损失函数,最小化损失函数,就可以得到合适的词向量
得到式 1 后,产生了两个问题:
P (o|c) 怎么表示?
为何最小化损失函数能够得到良好表示的词向量 dense word vector?
回答 1:我们使用中心词 c 和上下文词 o 的相似性来计算
使用词向量的点积表示 P (o|c) 的原因:1. 计算简单 2. 出现在一起的词向量意义相关,则希望它们相似
又 P (o|c) 是一个概率,所以我们在整个语料库上使用 softmax 将点积的值映射到概率,如图 6
注:注意到上图,中心词词向量为
回答 2:由上文所述,
Word2vec 模型结构
如图八所示,这是一个输入为 1 X V 维的 one-hot 向量(V 为整个词汇表的长度,这个向量只有一个 1 值,其余为 0 值表示一个词),单隐藏层(隐藏层的维度为 N,这里是一个超参数,这个参数由我们定义,也就是词向量的维度),输出为 1 X V 维的 softmax 层的模型。
模型的输入为 1 X V 形状的 one-hot 向量(V 为整个词汇表的长度,这个向量只有一个 1 值,其余为 0 值表示一个词)。隐藏层的维度为 N,这里是一个超参数,这个参数由我们定义,也就是词向量的维度。
我们这里,考虑 Skip-gram 算法,输入为中心词 c 的 one-hot 表示
由输入层到隐藏层,根据矩阵乘法规则,可知,
而
有 V 个 w, 其中的 P (o|c) 即实际样本中的上下文词的概率,为我们最为关注的值。
CBOW
如上文所述,Skip-gram 为给定中心词,预测周围的词,即求 P (o|c),如下图所示:
而 CBOW 为给定周围的词,预测中心词,即求 P (c|o), 如下图所示:
注意:在使用 CBOW 时,上文所给出的模型结构并没有变,在这里,我们输入多个上下文词 o,在隐藏层,将这多个上下文词经过第一个参数矩阵的计算得到的词向量相加作为隐藏单元的值。其余均不变,
负采样 Negative Sampling
softmax 函数带来的问题
我们再看一眼,通过 softmax 得到的
可以看到,
他提出了两种改进的方法:Hierarchical Softmax 和 Negative Sampling,因为 Negative Sampling 更加常见,所以我们下面只介绍 Negative Sampling,感兴趣的朋友可以在文章下面的参考资料中学习 Hierarchical Softmax。
负采样 Negative Sampling
我们依然以 Skip-gram 为例(CBOW 与之差别不大,感兴趣的朋友们依然可以参阅参考资料)
我们首先给出负采样的损失函数:
其中
由函数单调性易知,
核心代码与核心推导
Naive softmax 损失函数
损失函数关于
可以看到涉及整个 U 矩阵的计算,计算量很大,关于
损失函数及其梯度的求解
来自:https://github.com/lrs1353281004/CS224n_winter2019_notes_and_assignments
def naiveSoftmaxLossAndGradient(
centerWordVec,
outsideWordIdx,
outsideVectors,
dataset
):
""" Naive Softmax loss & gradient function for word2vec models
Arguments:
centerWordVec -- numpy ndarray, center word's embedding
in shape (word vector length, )
(v_c in the pdf handout)
outsideWordIdx -- integer, the index of the outside word
(o of u_o in the pdf handout)
outsideVectors -- outside vectors is
in shape (num words in vocab, word vector length)
for all words in vocab (tranpose of U in the pdf handout)
dataset -- needed for negative sampling, unused here.
Return:
loss -- naive softmax loss
gradCenterVec -- the gradient with respect to the center word vector
in shape (word vector length, )
(dJ / dv_c in the pdf handout)
gradOutsideVecs -- the gradient with respect to all the outside word vectors
in shape (num words in vocab, word vector length)
(dJ / dU)
"""
# centerWordVec: (embedding_dim,1)
# outsideVectors: (vocab_size,embedding_dim)
scores = np.matmul(outsideVectors, centerWordVec) # size=(vocab_size, 1)
probs = softmax(scores) # size=(vocab, 1)
loss = -np.log(probs[outsideWordIdx]) # scalar
dscores = probs.copy() # size=(vocab, 1)
dscores[outsideWordIdx] = dscores[outsideWordIdx] - 1 # dscores=y_hat - y
gradCenterVec = np.matmul(outsideVectors, dscores) # J关于vc的偏导数公式 size=(vocab_size, 1)
gradOutsideVecs = np.outer(dscores, centerWordVec) # J关于u的偏导数公式 size=(vocab_size, embedding_dim)
return loss, gradCenterVec, gradOutsideVecs
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
负采样损失函数
负采样损失函数关于
可以看到其只与
负采样方法的损失函数及其导数的求解
def negSamplingLossAndGradient(
centerWordVec,
outsideWordIdx,
outsideVectors,
dataset,
K=10
):
negSampleWordIndices = getNegativeSamples(outsideWordIdx, dataset, K)
indices = [outsideWordIdx] + negSampleWordIndices
gradCenterVec =np.zeros(centerWordVec.shape) # (embedding_size,1)
gradOutsideVecs = np.zeros(outsideVectors.shape) # (vocab_size, embedding_size)
loss = 0.0
u_o = outsideVectors[outsideWordIdx] # size=(embedding_size,1)
z = sigmoid(np.dot(u_o, centerWordVec)) # size=(1, )
loss -= np.log(z) # 损失函数的第一部分
gradCenterVec += u_o * (z - 1) # J关于vc的偏导数的第一部分
gradOutsideVecs[outsideWordIdx] = centerWordVec * (z - 1) # J关于u_o的偏导数计算
for i in range(K):
neg_id = indices[1 + i]
u_k = outsideVectors[neg_id]
z = sigmoid(-np.dot(u_k, centerWordVec))
loss -= np.log(z)
gradCenterVec += u_k * (1-z)
gradOutsideVecs[neg_id] += centerWordVec * (1 - z)
return loss, gradCenterVec, gradOutsideVecs
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
参考资料
- Mikolov T, Sutskever I, Chen K, et al. Distributed representations of words and phrases and their compositionality[J]. Advances in neural information processing systems, 2013, 26.
- https://www.cnblogs.com/peghoty/p/3857839.html
- http://web.stanford.edu/class/cs224n/