在训练word2vec的时候,为了预测目标词是哪个词,我们要使用softmax函数进行预测,也就是一个softmax多分类的问题(每个单词就是一类)。类似下面的式子
p
(
o
j
∣
w
i
)
=
e
f
(
o
j
,
w
i
)
∑
j
=
1
∣
V
∣
e
f
(
o
j
,
w
i
)
(1)
\tag{1} p(o_{j}|w_{i}) = {e^{f(o_{j}, w_{i})} \over \sum_{j = 1}^{|V|} e^{f(o_{j}, w_{i})}}
p(oj∣wi)=∑j=1∣V∣ef(oj,wi)ef(oj,wi)(1)
其中
f
(
o
j
,
w
i
)
f(o_{j}, w_{i})
f(oj,wi)衡量了预测单词
o
j
与
输
入
单
词
w
i
o_{j}与输入单词w_{i}
oj与输入单词wi之间的某种联系。分母计算了整个词汇表的大小,所以在训练的时候很慢。解决的办法就是负采样,每次只评估所有类别的一个很小的子集,也就是分母不再是计算整个词汇表的大小,而是从词汇表中挑选出一些样本,然后在这些样本上进行计算。
p
(
o
j
∣
w
i
)
=
e
f
(
o
j
,
w
i
)
∑
j
=
1
∣
V
N
E
∣
e
f
(
o
j
,
w
i
)
(2)
\tag{2} p(o_{j}|w_{i}) = {e^{f(o_{j}, w_{i})} \over \sum_{j = 1}^{|V_{NE}|} e^{f(o_{j}, w_{i})}}
p(oj∣wi)=∑j=1∣VNE∣ef(oj,wi)ef(oj,wi)(2)
其中
V
N
E
V_{NE}
VNE是负采样的样本集大小。
TF中采用上述思想实现了一些负采样损失的函数。
def sampled_softmax_loss(weights,
biases,
labels,
inputs,
num_sampled,
num_classes,
num_true=1,
sampled_values=None,
remove_accidental_hits=True,
partition_strategy="mod",
name="sampled_softmax_loss",
seed=None)
这个函数通过 模型的交叉熵损失。负采样类别子集由采样类别和真实类别组成,即 。模型最后一层输出是 , 经过 softmax 激活函数转成模型输出的概率得出 。这个函数只能处理单个标签的情况,如果要处理多标签的情况使用下面的函数。
def nce_loss(weights,
biases,
inputs,
labels,
num_sampled,
num_classes,
num_true=1,
sampled_values=None,
remove_accidental_hits=False,
partition_strategy="mod",
name="nce_loss")
还有博客上说word2vec的负采样思想是:
比如根据这句话训练word2vec,The quick brown fox jumps over the lazy dog.
,那么根据skip-gram的形式,我们设置中心词fox的上下文范围。我们使用skip_window
来表示我们从中心词的左右两侧选取的词的数量,num_skips
表示我们在中心词的上下文中选取作为输出词的数量。例如:当skip_window=2,num_skips = 2,
中心词为fox时 ,我们获得fox的上下文窗口为 [‘quick’,‘brown’,‘jumps’,‘over’]。我们随机从窗口中选取两个词作为输出,那么我们的样本元组应该如同:('fox','brown'),('fox','over')
由于训练神经网络模型为了达到更高的精度,需要通过每个训练样本细微地调整每个神经元的权重参数,因此每个训练样本都会微调神经网络中的所有参数。假设词汇表大小为10000,输出层神经元个数也为10000,那么输入一个训练样本('fox','brown')
的时候,我们要更新这10000个神经元的权值。这样会导致训练过程很慢。
Negative Sampling通过让每个训练样本只修改一小部分权重(而不是网络中的全部权重)来解决计算量特别大的问题。如果使用了 negative sampling 仅仅去更新positive word
和选择的其他几个negative words
的结点对应的权重,这样计算量会大大降低。
个人觉得上述思想和我一开始所说的达到的效果一致,但是理解起来可能有困难。有理解的同学可以指点一下。
关于word2vec更详细的信息可以看博客word2vec详解(CBOW,skip-gram,负采样,分层Softmax)