diff --git a/chapter_natural-language-processing/attention.md b/chapter_natural-language-processing/attention.md index 07e22e1cc4ed06ea24126942bcd7721bb805c6c0..030196164032c6d5d7120a2661bbef6a42a3eace 100644 --- a/chapter_natural-language-processing/attention.md +++ b/chapter_natural-language-processing/attention.md @@ -5,7 +5,7 @@ ## 动机 -以英语-法语翻译为例,给定一对输入序列“They”、“are”、“watching”、“.”和输出序列“Ils”、“regardent”、“.”。解码器可以在输出序列的时间步1使用更多编码了“They”、“are”信息的背景变量来生成“Ils”,在时间步2使用更多编码了“watching”信息的背景变量来生成“regardent”,在时间步3使用更多编码了“.”信息的背景变量来生成“.”。这看上去就像是在解码器的每一时间步对输入序列中不同时间步编码的信息分配不同的注意力。这也是注意力机制的由来。它最早由Bahanau等提出 [1]。 +以英语-法语翻译为例,给定一对输入序列“They”、“are”、“watching”、“.”和输出序列“Ils”、“regardent”、“.”。解码器可以在输出序列的时间步1使用更集中编码了“They”、“are”信息的背景变量来生成“Ils”,在时间步2使用更集中编码了“watching”信息的背景变量来生成“regardent”,在时间步3使用更集中编码了“.”信息的背景变量来生成“.”。这看上去就像是在解码器的每一时间步对输入序列中不同时间步编码的信息分配不同的注意力。这也是注意力机制的由来,它最早由Bahanau等提出 [1]。 ## 设计 @@ -13,12 +13,12 @@ 本节沿用[“编码器—解码器(seq2seq)”](seq2seq.md)一节里的数学符号。 我们对[“编码器—解码器(seq2seq)”](seq2seq.md)一节里的解码器稍作修改。在时间步$t^\prime$,设解码器的背景变量为$\boldsymbol{c}_{t^\prime}$,输出$y_{t^\prime}$的特征向量为$\boldsymbol{y}_{t^\prime}$。 -和输入的特征向量一样,这里每个输出的特征向量也可能是模型参数。解码器在时间步$t^\prime$的隐藏状态 +和输入的特征向量一样,这里每个输出的特征向量也是模型参数。解码器在时间步$t^\prime$的隐藏状态 $$\boldsymbol{s}_{t^\prime} = g(\boldsymbol{y}_{t^\prime-1}, \boldsymbol{c}_{t^\prime}, \boldsymbol{s}_{t^\prime-1}).$$ -令编码器在时间步$t$的隐藏状态为$\boldsymbol{h}_t$,且时间步数为$T$。解码器在时间步$t^\prime$的背景变量为 +令编码器在时间步$t$的隐藏状态为$\boldsymbol{h}_t$,且总时间步数为$T$。解码器在时间步$t^\prime$的背景变量为 $$\boldsymbol{c}_{t^\prime} = \sum_{t=1}^T \alpha_{t^\prime t} \boldsymbol{h}_t,$$ @@ -30,7 +30,7 @@ $$\alpha_{t^\prime t} = \frac{\exp(e_{t^\prime t})}{ \sum_{k=1}^T \exp(e_{t^\pri $$e_{t^\prime t} = a(\boldsymbol{s}_{t^\prime - 1}, \boldsymbol{h}_t).$$ -上式中的函数$a$有多种设计方法。Bahanau等使用了 +上式中的函数$a$有多种设计方法。Bahanau等使用了前馈神经网络(feedforward neural network): $$e_{t^\prime t} = \boldsymbol{v}^\top \tanh(\boldsymbol{W}_s \boldsymbol{s}_{t^\prime - 1} + \boldsymbol{W}_h \boldsymbol{h}_t),$$ diff --git a/chapter_natural-language-processing/beam-search.md b/chapter_natural-language-processing/beam-search.md index 6c9138c18246315fc8a108cc424741da3b0d4f03..ade867190dee5ad2ca7ad78f01fbfd09fdbd6142 100644 --- a/chapter_natural-language-processing/beam-search.md +++ b/chapter_natural-language-processing/beam-search.md @@ -1,16 +1,17 @@ -# 束搜索 +# 束搜索(beam search) 上一节介绍了如何训练输入输出均为不定长序列的编码器—解码器。在准备训练数据集时,我们通常会在样本的输入序列和输出序列后面分别附上一个特殊符号“<eos>”(end of sequence)表示序列的终止。在预测中,模型该如何输出不定长序列呢? 为了便于讨论,假设解码器的输出是一段文本序列。我们将在本节的讨论中沿用上一节的数学符号。 +设输出文本词典$\mathcal{Y}$(包含特殊符号“<eos>”)的大小为$|\mathcal{Y}|$,输出序列的最大长度为$T^\prime$。那么,所有可能的输出序列一共有$\mathcal{O}(|\mathcal{Y}|^{T^\prime})$种。这些输出序列中所有特殊符号“<eos>”及其后面的子序列将被舍弃。 -## 穷举搜索 +我们在描述解码器时提到,输出序列基于输入序列的条件概率是$\prod_{t^\prime=1}^{T^\prime} \mathbb{P}(y_{t^\prime} \mid y_1, \ldots, y_{t^\prime-1}, \boldsymbol{c})$。 -设输出文本词典$\mathcal{Y}$(包含特殊符号“<eos>”)的大小为$|\mathcal{Y}|$,输出序列的最大长度为$T^\prime$。那么,所有可能的输出序列一共有$\mathcal{O}(|\mathcal{Y}|^{T^\prime})$种。这些输出序列中所有特殊符号“<eos>”及其后面的子序列将被舍弃。 +## 穷举搜索 -我们在描述解码器时提到,输出序列基于输入序列的条件概率是$\prod_{t^\prime=1}^{T^\prime} \mathbb{P}(y_{t^\prime} \mid y_1, \ldots, y_{t^\prime-1}, \boldsymbol{c})$。为了搜索该概率最大的输出序列,一种方法是穷举所有可能序列的概率,并输出概率最大的序列。我们将该序列称为最优序列,并将这种搜索方法称为穷举搜索(exhaustive search)。很明显,穷举搜索的计算开销$\mathcal{O}(|\mathcal{Y}|^{T^\prime})$很容易过高而无法使用(例如,$10000^{10} = 1 \times 10^{40}$)。 +为了搜索该概率最大的输出序列,一种方法是穷举所有可能序列的概率,并输出概率最大的序列。我们将该序列称为最优序列,并将这种搜索方法称为穷举搜索(exhaustive search)。很明显,穷举搜索的计算开销$\mathcal{O}(|\mathcal{Y}|^{T^\prime})$很容易太大而无法使用(例如,$10000^{10} = 1 \times 10^{40}$)。 ## 贪婪搜索 @@ -23,7 +24,7 @@ $$y_{t^\prime} = \text{argmax}_{y_{t^\prime} \in \mathcal{Y}} \mathbb{P}(y_{t^\p 设输出文本词典$\mathcal{Y}$的大小为$|\mathcal{Y}|$,输出序列的最大长度为$T^\prime$。 -贪婪搜索的计算开销是$\mathcal{O}(|\mathcal{Y}| \times {T^\prime})$。它比起穷举搜索的计算开销显著下降(例如,$10000 \times 10 = 1 \times 10^5$)。然而,贪婪搜索并不能保证输出是最优序列。 +贪婪搜索的计算开销是$\mathcal{O}(|\mathcal{Y}| \times {T^\prime})$。它比起穷举搜索的计算开销显著下降(例如,当${T^\prime}=10$时,$10000 \times 10 = 1 \times 10^5$)。然而,贪婪搜索并不能保证输出是最优序列。 ## 束搜索 @@ -31,7 +32,7 @@ $$y_{t^\prime} = \text{argmax}_{y_{t^\prime} \in \mathcal{Y}} \mathbb{P}(y_{t^\p 束搜索(beam search)介于上面二者之间。我们通过一个具体例子描述它。 -假设输出序列的词典中只包含五个元素:$\mathcal{Y} = \{A, B, C, D, E\}$,且其中一个为特殊符号“<eos>”。设束搜索的超参数束宽(beam width)等于2,输出序列最大长度为3。 +假设输出序列的词典中只包含五个元素:$\mathcal{Y} = \{A, B, C, D, E\}$,且其中一个为特殊符号“<eos>”。设束搜索的超参数束宽(beam size)等于2,输出序列最大长度为3。 在输出序列的时间步1时,假设条件概率$\mathbb{P}(y_{t^\prime} \mid \boldsymbol{c})$最大的两个词为$A$和$C$。我们在时间步2时将对所有的$y_2 \in \mathcal{Y}$都分别计算$\mathbb{P}(y_2 \mid A, \boldsymbol{c})$和$\mathbb{P}(y_2 \mid C, \boldsymbol{c})$,并从计算出的10个概率中取最大的两个:假设为$\mathbb{P}(B \mid A, \boldsymbol{c})$和$\mathbb{P}(E \mid C, \boldsymbol{c})$。那么,我们在时间步3时将对所有的$y_3 \in \mathcal{Y}$都分别计算$\mathbb{P}(y_3 \mid A, B, \boldsymbol{c})$和$\mathbb{P}(y_3 \mid C, E, \boldsymbol{c})$,并从计算出的10个概率中取最大的两个:假设为$\mathbb{P}(D \mid A, B, \boldsymbol{c})$和$\mathbb{P}(D \mid C, E, \boldsymbol{c})$。 @@ -41,7 +42,7 @@ $$ \frac{1}{L^\alpha} \log \mathbb{P}(y_1, \ldots, y_{L}) = \frac{1}{L^\alpha} \ 其中$L$为候选序列长度,$\alpha$一般可选为0.75。分母上的$L^\alpha$是为了惩罚较长序列在以上分数中较多的对数相加项。 -穷举搜索和贪婪搜索也可看作是两种特殊束宽的束搜索。束搜索通过更灵活的束宽来权衡计算开销和搜索质量。 +穷举搜索和贪婪搜索也可看作是两种特殊束宽的束搜索。束搜索通过更灵活的束宽来权衡计算开销和搜索质量。通常束宽等于1技能在机器翻译中取得不错的结果[1]。 ## 小结 @@ -58,3 +59,7 @@ $$ \frac{1}{L^\alpha} \log \mathbb{P}(y_1, \ldots, y_{L}) = \frac{1}{L^\alpha} \ ## 扫码直达[讨论区](https://discuss.gluon.ai/t/topic/6817) ![](../img/qr_beam-search.svg) + +## 参考文献 + +[1] Sutskever, Ilya, Oriol Vinyals, and Quoc V. Le. "Sequence to sequence learning with neural networks." NIPS. 2014. diff --git a/chapter_natural-language-processing/glove-fasttext.md b/chapter_natural-language-processing/glove-fasttext.md index 7dd641d867d291f601d3c748255b0a8bc86bae7a..f36fdbf2faab667e44372bcd46cab6f03681c243 100644 --- a/chapter_natural-language-processing/glove-fasttext.md +++ b/chapter_natural-language-processing/glove-fasttext.md @@ -6,7 +6,7 @@ ## GloVe -GloVe使用了词与词之间的共现(co-occurrence)信息。我们定义$\boldsymbol{X}$为共现词频矩阵,其中元素$x_{ij}$为词$j$出现在词$i$的背景的次数。这里的“背景”有多种可能的定义。举个例子,在一段文本序列中,如果词$j$出现在词$i$左边或者右边不超过10个词的距离,我们可以认为词$j$出现在词$i$的背景一次。令$x_i = \sum_k x_{ik}$为任意词出现在词$i$的背景的次数。那么, +GloVe使用了词与词之间的共现(co-occurrence)信息。我们定义$\boldsymbol{X}$为共现词频矩阵,其中元素$x_{ij}$为词$j$出现在词$i$的背景的次数。这里的“背景”(context)有多种可能的定义。举个例子,在一段文本序列中,如果词$j$出现在词$i$左边或者右边不超过10个词的距离,我们可以认为词$j$出现在词$i$的背景一次。令$x_i = \sum_k x_{ik}$为任意词出现在词$i$的背景的次数。那么, $$P_{ij} = \mathbb{P}(j \mid i) = \frac{x_{ij}}{x_i}$$ @@ -15,7 +15,7 @@ $$P_{ij} = \mathbb{P}(j \mid i) = \frac{x_{ij}}{x_i}$$ ### 共现概率比值 -GloVe论文里展示了以下一组词对的共现概率与比值 [1]: +GloVe论文里展示了以下词对的共现概率与比值 [1]: * $\mathbb{P}(k \mid \text{ice})$:0.00019($k$= solid),0.000066($k$= gas),0.003($k$= water),0.000017($k$= fashion) * $\mathbb{P}(k \mid \text{steam})$:0.000022($k$= solid),0.00078($k$= gas),0.0022($k$= water),0.000018($k$= fashion) @@ -24,10 +24,10 @@ GloVe论文里展示了以下一组词对的共现概率与比值 [1]: 我们可以观察到以下现象: -* 对于与ice(冰)相关而与steam(蒸汽)不相关的词$k$,例如$k=$solid(固体),我们期望共现概率比值$P_{ik}/P_{jk}$较大,例如上面最后一行结果中的8.9。 -* 对于与ice不相关而与steam相关的词$k$,例如$k=$gas(气体),我们期望共现概率比值$P_{ik}/P_{jk}$较小,例如上面最后一行结果中的0.085。 -* 对于与ice和steam都相关的词$k$,例如$k=$water(水),我们期望共现概率比值$P_{ik}/P_{jk}$接近1,例如上面最后一行结果中的1.36。 -* 对于与ice和steam都不相关的词$k$,例如$k=$fashion(时尚),我们期望共现概率比值$P_{ik}/P_{jk}$接近1,例如上面最后一行结果中的0.96。 +* 对于与ice(冰)相关而与steam(蒸汽)不相关的词$k$,例如$k=$solid(固体),我们期望共现概率比值$P_{ik}/P_{jk}$较大,例如上面最后一行结果中的值8.9。 +* 对于与ice不相关而与steam相关的词$k$,例如$k=$gas(气体),我们期望共现概率比值$P_{ik}/P_{jk}$较小,例如上面最后一行结果中的值0.085。 +* 对于与ice和steam都相关的词$k$,例如$k=$water(水),我们期望共现概率比值$P_{ik}/P_{jk}$接近1,例如上面最后一行结果中的值1.36。 +* 对于与ice和steam都不相关的词$k$,例如$k=$fashion(时尚),我们期望共现概率比值$P_{ik}/P_{jk}$接近1,例如上面最后一行结果中的值0.96。 由此可见,共现概率比值能比较直观地表达词与词之间的关系。GloVe试图用有关词向量的函数来表达共现概率比值。 @@ -35,19 +35,19 @@ GloVe论文里展示了以下一组词对的共现概率与比值 [1]: GloVe的核心思想在于使用词向量表达共现概率比值。而任意一个这样的比值需要三个词$i$、$j$和$k$的词向量。对于共现概率$P_{ij} = \mathbb{P}(j \mid i)$,我们称词$i$和词$j$分别为中心词和背景词。我们使用$\boldsymbol{v}_i$和$\tilde{\boldsymbol{v}}_i$分别表示词$i$作为中心词和背景词的词向量。 -我们可以用有关词向量的函数$f$来表达共现概率比值: +我们可以用有关词向量的函数$F$来表达共现概率比值: -$$f(\boldsymbol{v}_i, \boldsymbol{v}_j, \tilde{\boldsymbol{v}}_k) = \frac{P_{ik}}{P_{jk}}.$$ +$$F(\boldsymbol{v}_i, \boldsymbol{v}_j, \tilde{\boldsymbol{v}}_k) = \frac{P_{ik}}{P_{jk}}.$$ -需要注意的是,函数$f$可能的设计并不唯一。下面我们考虑一种较为合理的可能性。 +需要注意的是,函数$F$可能的设计并不唯一。下面我们考虑一种较为合理的可能性。 首先,用向量之差来表达共现概率的比值,并将上式改写成 -$$f(\boldsymbol{v}_i - \boldsymbol{v}_j, \tilde{\boldsymbol{v}}_k) = \frac{P_{ik}}{P_{jk}}.$$ +$$F(\boldsymbol{v}_i - \boldsymbol{v}_j, \tilde{\boldsymbol{v}}_k) = \frac{P_{ik}}{P_{jk}}.$$ 由于共现概率比值是一个标量,我们可以使用向量之间的内积把函数$f$的自变量进一步改写,得到 -$$f((\boldsymbol{v}_i - \boldsymbol{v}_j)^\top \tilde{\boldsymbol{v}}_k) = \frac{P_{ik}}{P_{jk}}.$$ +$$F((\boldsymbol{v}_i - \boldsymbol{v}_j)^\top \tilde{\boldsymbol{v}}_k) = \frac{P_{ik}}{P_{jk}}.$$ 由于任意一对词共现的对称性,我们希望以下两个性质可以同时被满足: @@ -58,36 +58,36 @@ $$f((\boldsymbol{v}_i - \boldsymbol{v}_j)^\top \tilde{\boldsymbol{v}}_k) = \frac $$f((\boldsymbol{v}_i - \boldsymbol{v}_j)^\top \tilde{\boldsymbol{v}}_k) = \frac{f(\boldsymbol{v}_i^\top \tilde{\boldsymbol{v}}_k)}{f(\boldsymbol{v}_j^\top \tilde{\boldsymbol{v}}_k)},$$ -并得到$f(x) = \text{exp}(x)$。以上两式的右边联立, +并令$F(x) = \text{exp}(x)$。以上两式的右边联立, -$$\exp(\boldsymbol{v}_i^\top \tilde{\boldsymbol{v}}_k) = P_{ik} = \frac{x_{ik}}{x_i}.$$ +$$F(\boldsymbol{v}_i^\top \tilde{\boldsymbol{v}}_k) = \exp(\boldsymbol{v}_i^\top \tilde{\boldsymbol{v}}_k) = P_{ik} = \frac{x_{ik}}{x_i}.$$ 由上式可得 -$$\boldsymbol{v}_i^\top \tilde{\boldsymbol{v}}_k = \log(x_{ik}) - \log(x_i).$$ +$$\boldsymbol{v}_i^\top \tilde{\boldsymbol{v}}_k = \log(P_{ik}) = \log(x_{ik}) - \log(x_i).$$ -另一方面,我们可以把上式中$\log(x_i)$替换成两个偏差项之和$b_i + b_k$,得到 +另一方面,我们可以把上式中$\log(x_i)$替换成两个偏差项之和$b_i + \tilde{b_k}$,得到 -$$\boldsymbol{v}_i^\top \tilde{\boldsymbol{v}}_k = \log(x_{ik}) - b_i - b_k.$$ +$$\boldsymbol{v}_i^\top \tilde{\boldsymbol{v}}_k = \log(x_{ik}) - b_i - \tilde{b_k}.$$ 将$i$和$k$互换,我们可验证一对词共现的两个对称性质可以同时被上式满足。 因此,对于任意一对词$i$和$j$,我们可以用它们词向量表达共现词频的对数: -$$\boldsymbol{v}_i^\top \tilde{\boldsymbol{v}}_j + b_i + b_j = \log(x_{ij}).$$ +$$\boldsymbol{v}_i^\top \tilde{\boldsymbol{v}}_j + b_i + \tilde{b_j} = \log(x_{ij}).$$ ### 损失函数 GloVe中的共现词频是直接在训练数据上统计得到的。为了学习词向量和相应的偏差项,我们希望上式中的左边与右边尽可能接近。给定词典$\mathcal{V}$和权重函数$h(x_{ij})$,GloVe的损失函数为 -$$\sum_{i \in \mathcal{V}, j \in \mathcal{V}} h(x_{ij}) \left(\boldsymbol{v}_i^\top \tilde{\boldsymbol{v}}_j + b_i + b_j - \log(x_{ij})\right)^2,$$ +$$\sum_{i \in \mathcal{V}, j \in \mathcal{V}} h(x_{ij}) \left(\boldsymbol{v}_i^\top \tilde{\boldsymbol{v}}_j + b_i + \tilde{b_j} - \log(x_{ij})\right)^2,$$ 其中权重函数$h(x)$的一个建议选择是,当$x < c$(例如$c = 100$),令$h(x) = (x/c)^\alpha$(例如$\alpha = 0.75$),反之令$h(x) = 1$。由于权重函数的存在,损失函数的计算复杂度与共现词频矩阵$\boldsymbol{X}$中非零元素的数目呈正相关。我们可以从$\boldsymbol{X}$中随机采样小批量非零元素,并使用优化算法迭代共现词频相关词的向量和偏差项。 需要注意的是,对于任意一对$i, j$,损失函数中存在以下两项之和 -$$h(x_{ij}) (\boldsymbol{v}_i^\top \tilde{\boldsymbol{v}}_j + b_i + b_j - \log(x_{ij}))^2 + h(x_{ji}) (\boldsymbol{v}_j^\top \tilde{\boldsymbol{v}}_i + b_j + b_i - \log(x_{ji}))^2.$$ +$$h(x_{ij}) (\boldsymbol{v}_i^\top \tilde{\boldsymbol{v}}_j + b_i + \tilde{b_j} - \log(x_{ij}))^2 + h(x_{ji}) (\boldsymbol{v}_j^\top \tilde{\boldsymbol{v}}_i + \tilde{b_j} + b_i - \log(x_{ji}))^2.$$ 由于上式中$x_{ij} = x_{ji}$,对调$\boldsymbol{v}$和$\tilde{\boldsymbol{v}}$并不改变损失函数中这两项之和。也就是说,在损失函数所有项上对调$\boldsymbol{v}$和$\tilde{\boldsymbol{v}}$也不改变整个损失函数的值。因此,任意词的中心词向量和背景词向量是等价的。只是由于初始化值的不同,同一个词最终学习到的两组词向量可能不同。当学习得到所有词向量以后,GloVe使用一个词的中心词向量与背景词向量之和作为该词的最终词向量。 @@ -112,13 +112,13 @@ $$ - \text{log} \mathbb{P} (w_o \mid w_c) = -\text{log} \frac{1}{1+\text{exp}(-\ fastText对于一些语言较重要,例如阿拉伯语、德语和俄语。例如,德语中有很多复合词,例如乒乓球(英文table tennis)在德语中叫“Tischtennis”。fastText可以通过子词表达两个词的相关性,例如“Tischtennis”和“Tennis”。 -由于词是自然语言的基本表义单元,词向量广泛应用于各类自然语言处理的场景中。例如,我们可以在之前介绍的语言模型中使用在更大规模语料上预训练的词向量。我们将在接下来两节的实验中应用这些预训练的词向量,例如求近似词和类比词,以及文本分类。 +由于词是自然语言的基本表义单元,词向量广泛应用于各类自然语言处理的场景中。例如,我们可以在之前介绍的语言模型中使用在更大规模语料上预训练的词向量,从而增强语言模型结果。我们将在接下来两节的实验中应用这些预训练的词向量,例如求近似词和类比词,以及文本分类。 ## 小结 -* GloVe用词向量表达共现词频的对数。 +* GloVe用词向量表示共现词频的对数。 * fastText用子词向量之和表达整词。它能表示词典以外的新词。 diff --git a/chapter_natural-language-processing/index.md b/chapter_natural-language-processing/index.md index 9646aae383845755484b196d4520237a95a5557a..dfb6283124bbd75f02f34dc70c406f22b9fdc9c8 100644 --- a/chapter_natural-language-processing/index.md +++ b/chapter_natural-language-processing/index.md @@ -2,10 +2,9 @@ 自然语言处理关注计算机与人类自然语言的交互。在实际中,我们常常使用自然语言处理技术,例如“循环神经网络”一章中介绍的语言模型,来处理和分析大量的自然语言数据。 -本章中,我们将先介绍如何用向量表示词,并应用这些词向量求近义词和类比词。接着,在文本分类任务中,我们进一步应用词向量分析文本情感。此外,自然语言处理任务中很多输出是不定长的,例如任意长度的句子。我们将描述应对这类问题的编码器—解码器模型以及注意力机制,并将它们应用于机器翻译中。 +本章中,我们将先介绍如何用向量表示词,并应用这些词向量寻找近义词和类比词。接着,在文本分类任务中,我们进一步应用词向量分析文本情感。此外,自然语言处理任务中很多输出是不定长的,例如任意长度的句子。我们将描述应对这类问题的编码器—解码器模型以及注意力机制,并将它们应用于机器翻译中。 - -```eval_rst +```{.python .input .eval_rst} .. toctree:: :maxdepth: 2 diff --git a/chapter_natural-language-processing/pretrained-embedding.md b/chapter_natural-language-processing/pretrained-embedding.md index 8ff2e9df4add90d20add02d2276973d963306fbe..f6e8677c2a1b3bc96fb8decec0547a64430b208e 100644 --- a/chapter_natural-language-processing/pretrained-embedding.md +++ b/chapter_natural-language-processing/pretrained-embedding.md @@ -1,6 +1,6 @@ # 求近似词和类比词 -本节介绍如何应用在大规模语料上预训练的词向量,例如求近似词和类比词。这里使用的预训练的GloVe和fastText词向量分别来自它们的项目网站 [1,2]。 +本节介绍如何使用在大规模语料上预训练的词向量,例如求近似词和类比词。这里使用的预训练的GloVe和fastText词向量分别来自它们的项目网站 [1,2]。 首先导入实验所需的包或模块。 @@ -12,7 +12,7 @@ from mxnet.gluon import nn ## 由数据集建立词典和载入词向量 -下面,我们以fastText为例,由数据集建立词典并载入词向量。fastText提供了基于不同语言的多套预训练的词向量。这些词向量是在大规模语料上训练得到的,例如维基百科语料。以下打印了其中的10套。 +下面,我们以fastText为例,由数据集建立词典并载入词向量。fastText提供了基于不同语言的多套预训练的词向量。这些词向量是在大规模语料上训练得到的,例如维基百科语料。以下打印了其中的10种。 ```{.python .input n=34} print(text.embedding.get_pretrained_file_names('fasttext')[:10]) diff --git a/chapter_natural-language-processing/sentiment-analysis.md b/chapter_natural-language-processing/sentiment-analysis.md index 86764e3581c367d83fa3684abd63a0fa31b61862..9ccf8dd19dd82789e2516500ff46be796912ac2f 100644 --- a/chapter_natural-language-processing/sentiment-analysis.md +++ b/chapter_natural-language-processing/sentiment-analysis.md @@ -1,12 +1,12 @@ # 文本分类:情感分析 -文本分类即把一段不定长的文本序列变换为类别。在文本分类问题中,情感分析是一项重要的自然语言处理的任务。例如,Netflix或者IMDb可以对每部电影的评论进行情感分类,从而帮助各个平台改进产品,提升用户体验。 +文本分类即把一段不定长的文本序列变换为类别。在文本分类问题中,情感分析是一项重要的自然语言处理任务。例如,Netflix或者IMDb可以对每部电影的评论进行情感分类,从而帮助各个平台改进产品,提升用户体验。 本节介绍如何使用Gluon来创建一个情感分类模型。该模型将判断一段不定长的文本序列中包含的是正面还是负面的情绪,也即将文本序列分类为正面或负面。 ## 模型设计 -在这个模型中,我们将应用预训练的词向量和含多个隐藏层的双向循环神经网络。首先,文本序列的每一个词将以预训练的词向量作为词的特征向量。然后,我们使用双向循环神经网络对特征序列进一步编码得到序列信息。最后,我们将编码的序列信息通过全连接层变换为输出。在本节的实验中,我们将双向长短期记忆在最初时间步和最终时间步的隐藏状态连结,并作为特征序列的编码信息传递给输出层分类。 +在这个模型中,我们将应用预训练的词向量和含多个隐藏层的双向循环神经网络。首先,文本序列的每一个词将以预训练的词向量作为词的特征向量。然后,我们使用双向循环神经网络对特征序列进一步编码得到序列信息。最后,我们将编码的序列信息通过全连接层变换为输出。在本节的实验中,我们将双向长短期记忆在最初时间步和最终时间步的隐藏状态连结,作为特征序列的编码信息传递给输出层分类。 在实验开始前,导入所需的包或模块。 @@ -90,7 +90,7 @@ for review, score in test_data: ## 创建词典 -现在,我们可以根据分好词的训练数据集来创建词典了。这里我们设置了特殊符号“<unk>”(unknown)。它将表示一切不存在于训练数据集词典中的词。 +现在,我们可以根据分好词的训练数据集来创建词典了。这里我们设置了特殊符号“<unk>”(unknown),它将表示一切不存在于训练数据集词典中的词。 ```{.python .input n=6} token_counter = collections.Counter() @@ -109,7 +109,7 @@ vocab = text.vocab.Vocabulary(token_counter, unknown_token='', ## 预处理数据 -下面,我们继续对数据进行预处理。每个不定长的评论将被特殊符号`padding`补成长度为`maxlen`的序列。 +下面,我们继续对数据进行预处理。每个不定长的评论将被特殊符号`PAD`补成长度为`maxlen`的序列,以及生成`NDArray`类型的序列。 ```{.python .input n=7} def encode_samples(tokenized_samples, vocab): @@ -124,7 +124,7 @@ def encode_samples(tokenized_samples, vocab): features.append(feature) return features -def pad_samples(features, maxlen=500, padding=0): +def pad_samples(features, maxlen=500, PAD=0): padded_features = [] for feature in features: if len(feature) > maxlen: @@ -133,7 +133,7 @@ def pad_samples(features, maxlen=500, padding=0): padded_feature = feature # 添加 PAD 符号使每个序列等长(长度为 maxlen )。 while len(padded_feature) < maxlen: - padded_feature.append(padding) + padded_feature.append(PAD) padded_features.append(padded_feature) return padded_features @@ -157,7 +157,7 @@ glove_embedding = text.embedding.create( ## 定义模型 -下面我们根据模型设计里的描述定义情感分类模型。其中的Embedding实例即嵌入层。 +下面我们根据模型设计里的描述定义情感分类模型。其中的Embedding实例即嵌入层,LSTM实例即生成编码信息的隐含层,Dense实例即生成分类结果的输出层。 ```{.python .input} class SentimentNet(nn.Block): @@ -171,7 +171,7 @@ class SentimentNet(nn.Block): input_size=embed_size) self.decoder = nn.Dense(num_outputs, flatten=False) - def forward(self, inputs, begin_state=None): + def forward(self, inputs): embeddings = self.embedding(inputs) states = self.encoder(embeddings) # 连结初始时间步和最终时间步的隐藏状态。 @@ -180,7 +180,7 @@ class SentimentNet(nn.Block): return outputs ``` -由于情感分类的训练数据集并不是很大,我们将直接使用在更大规模语料上预训练的词向量作为每个词的特征向量。在训练中,我们不再迭代这些词向量,即模型嵌入层中的参数。当我们使用完整数据集时,我们可以重新调节下面的超参数,例如增加迭代周期。 +由于情感分类的训练数据集并不是很大,为防止过拟合现象,我们将直接使用在更大规模语料上预训练的词向量作为每个词的特征向量。在训练中,我们不再更新这些词向量,即模型嵌入层中的参数。 ```{.python .input} num_outputs = 2 @@ -196,7 +196,7 @@ net = SentimentNet(vocab, embed_size, num_hiddens, num_layers, bidirectional) net.initialize(init.Xavier(), ctx=ctx) # 设置 embedding 层的 weight 为预训练的词向量。 net.embedding.weight.set_data(glove_embedding.idx_to_vec.as_in_context(ctx)) -# 训练中不迭代词向量(net.embedding中的模型参数)。 +# 训练中不更新词向量(net.embedding中的模型参数)。 net.embedding.collect_params().setattr('grad_req', 'null') trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': lr}) loss = gloss.SoftmaxCrossEntropyLoss() @@ -241,7 +241,7 @@ for epoch in range(1, num_epochs + 1): % (epoch, train_loss, train_acc, test_loss, test_acc)) ``` -下面我们试着对一个简单的句子分析情感(1和0分别代表正面和负面)。为了在更复杂的句子上得到较准确的分类,我们需要使用完整数据集训练模型,并适当增大训练周期。 +下面我们试着分析一个简单的句子的情感(1和0分别代表正面和负面)。为了在更复杂的句子上得到较准确的分类,我们需要使用完整数据集训练模型,并适当增大训练周期。 ```{.python .input} review = ['this', 'movie', 'is', 'great'] @@ -259,9 +259,11 @@ nd.argmax(net(nd.reshape( * 使用IMDb完整数据集,并把迭代周期改为3。你的模型能在训练和测试数据集上得到怎样的准确率?通过调节超参数,你能进一步提升模型表现吗? -* 使用更大的预训练词向量,例如300维的GloVe词向量。 +* 使用更大的预训练词向量,例如300维的GloVe词向量,能否对分类结果带来提升? -* 使用spacy分词工具。你需要安装spacy:`pip install spacy`,并且安装英文包:`python -m spacy download en`。在代码中,先导入spacy:`import spacy`。然后加载spacy英文包:`spacy_en = spacy.load('en')`。最后定义函数:`def tokenizer(text): return [tok.text for tok in spacy_en.tokenizer(text)]`替换原来的基于空格分词的`tokenizer`函数。需要注意的是,GloVe的词向量对于名词词组的存储方式是用“-”连接各个单词,例如词组“new york”在GloVe中的表示为“new-york”。而使用spacy分词之后“new york”的存储可能是“new york”。你能使模型在测试集上的准确率提高到0.85以上吗? +* 使用spacy分词工具,能否提升分类效果?。你需要安装spacy:`pip install spacy`,并且安装英文包:`python -m spacy download en`。在代码中,先导入spacy:`import spacy`。然后加载spacy英文包:`spacy_en = spacy.load('en')`。最后定义函数:`def tokenizer(text): return [tok.text for tok in spacy_en.tokenizer(text)]`替换原来的基于空格分词的`tokenizer`函数。需要注意的是,GloVe的词向量对于名词词组的存储方式是用“-”连接各个单词,例如词组“new york”在GloVe中的表示为“new-york”。而使用spacy分词之后“new york”的存储可能是“new york”。 + +* 通过上面三种方法,你能使模型在测试集上的准确率提高到0.85以上吗? diff --git a/chapter_natural-language-processing/seq2seq.md b/chapter_natural-language-processing/seq2seq.md index cc48eba942cedb6393c7f4a852deff9304101040..2a856c396a1f2bae0609f514afa55c17ed31c3da 100644 --- a/chapter_natural-language-processing/seq2seq.md +++ b/chapter_natural-language-processing/seq2seq.md @@ -1,10 +1,10 @@ # 编码器—解码器(seq2seq) -在很多应用中,输入和输出都可以是不定长序列。以机器翻译为例,输入是可以是一段不定长的英语文本序列,输出可以是一段不定长的法语文本序列,例如 +在很多应用中,输入和输出都可以是不定长序列。以机器翻译为例,输入可以是一段不定长的英语文本序列,输出可以是一段不定长的法语文本序列,例如 -> 英语:“They”、“are”、“watching”、“.” +> 输入(英语):“They”、“are”、“watching”、“.” -> 法语:“Ils”、“regardent”、“.” +> 输出(法语):“Ils”、“regardent”、“.” 当输入输出都是不定长序列时,我们可以使用编码器—解码器(encoder-decoder)[1] 或者seq2seq模型 [2]。这两个模型本质上都用到了两个循环神经网络,分别叫做编码器和解码器。编码器对应输入序列,解码器对应输出序列。下面我们来介绍编码器—解码器的设计。 @@ -19,7 +19,7 @@ $$\boldsymbol{h}_t = f(\boldsymbol{x}_t, \boldsymbol{h}_{t-1}). $$ -假设输入序列的时间步数为$T$。编码器通过自定义函数$q$将各个时间步的隐藏状态变换为背景变量 +假设输入序列的总时间步数为$T$。编码器通过自定义函数$q$将各个时间步的隐藏状态变换为背景变量 $$\boldsymbol{c} = q(\boldsymbol{h}_1, \ldots, \boldsymbol{h}_T).$$ @@ -32,7 +32,7 @@ $$\boldsymbol{c} = q(\boldsymbol{h}_1, \ldots, \boldsymbol{h}_T).$$ ## 解码器 -刚刚已经介绍,假设输入序列的时间步数为$T$,编码器输出的背景变量$\boldsymbol{c}$编码了整个输入序列$x_1, \ldots, x_T$的信息。给定训练样本中的输出序列$y_1, y_2, \ldots, y_{T^\prime}$。假设其中每个时间步$t^\prime$的输出同时取决于该时间步之前的输出序列和背景变量。那么,根据最大似然估计,我们可以最大化输出序列基于输入序列的条件概率 +刚刚已经介绍,假设输入序列的总时间步数为$T$,编码器输出的背景变量$\boldsymbol{c}$编码了整个输入序列$x_1, \ldots, x_T$的信息。给定训练样本中的输出序列$y_1, y_2, \ldots, y_{T^\prime}$。假设其中每个时间步$t^\prime$的输出同时取决于该时间步之前的输出序列和背景变量。那么,根据最大似然估计,我们可以最大化输出序列基于输入序列的条件概率 $$ \begin{aligned} @@ -45,14 +45,14 @@ $$ 并得到该输出序列的损失 -$$- \log\mathbb{P}(y_1, \ldots, y_{T^\prime}).$$ +$$- \log\mathbb{P}(y_1, \ldots, y_{T^\prime}) = -\sum_{t^\prime=1}^{T^\prime} \log \mathbb{P}(y_{t^\prime} \mid y_1, \ldots, y_{t^\prime-1}, \boldsymbol{c}).$$ 为此,我们可以使用另一个循环神经网络作为解码器。 在输出序列的时间步$t^\prime$,解码器将上一时间步的输出$y_{t^\prime-1}$以及背景变量$\boldsymbol{c}$作为输入,并将它们与上一时间步的隐藏状态$\boldsymbol{s}_{t^\prime-1}$变换为当前时间步的隐藏状态$\boldsymbol{s}_{t^\prime}$。因此,我们可以用函数$g$表达解码器隐藏层的变换: $$\boldsymbol{s}_{t^\prime} = g(y_{t^\prime-1}, \boldsymbol{c}, \boldsymbol{s}_{t^\prime-1}).$$ -有了解码器的隐藏状态后,我们可以自定义输出层来计算损失中的$\mathbb{P}(y_{t^\prime} \mid y_1, \ldots, y_{t^\prime-1}, \boldsymbol{c})$,例如基于当前时间步的解码器隐藏状态 $\boldsymbol{s}_{t^\prime}$、上一时间步的输出$y_{t^\prime-1}$以及背景变量$\boldsymbol{c}$来计算当前时间步输出$y_{t^\prime}$的概率分布。 +有了解码器的隐藏状态后,我们可以自定义输出层来计算损失中的$\mathbb{P}(y_{t^\prime} \mid y_1, \ldots, y_{t^\prime-1}, \boldsymbol{c})$,例如基于当前时间步的解码器隐藏状态 $\boldsymbol{s}_{t^\prime}$、上一时间步的输出$y_{t^\prime-1}$以及背景变量$\boldsymbol{c}$来计算当前时间步输出$y_{t^\prime}$的概率分布(例如:softmax)。 在实际中,我们常使用深度循环神经网络作为编码器和解码器。我们将在本章稍后的[“机器翻译”](nmt.md)一节中实现含深度循环神经网络的编码器和解码器。 diff --git a/chapter_natural-language-processing/word2vec.md b/chapter_natural-language-processing/word2vec.md index 294c5cf2e46cf107ddda877c22be54135599b58d..066971a400ca9bf3ea28b4655908fda6a74f0ec8 100644 --- a/chapter_natural-language-processing/word2vec.md +++ b/chapter_natural-language-processing/word2vec.md @@ -6,7 +6,7 @@ ## 为何不采用one-hot向量 -我们在[“循环神经网络——从零开始”](../chapter_recurrent-neural-networks/rnn-scratch.md)一节中使用one-hot向量表示字符,并以字符为词。回忆一下,假设词典中不同词的数量(词典大小)为$N$,每个词可以和从0到$N-1$的连续整数一一对应。这些与词对应的整数也叫词的索引。 +我们在[“循环神经网络——从零开始”](../chapter_recurrent-neural-networks/rnn-scratch.md)一节中使用one-hot向量表示词。回忆一下,假设词典中不同词的数量(词典大小)为$N$,每个词可以和从0到$N-1$的连续整数一一对应。这些与词对应的整数也叫词的索引。 假设一个词的索引为$i$,为了得到该词的one-hot向量表示,我们创建一个全0的长为$N$的向量,并将其第$i$位设成1。 然而,使用one-hot词向量通常并不是一个好选择。一个主要的原因是,one-hot词向量无法表达不同词之间的相似度,例如余弦相似度。由于任意一对向量$\boldsymbol{x}, \boldsymbol{y} \in \mathbb{R}^d$的余弦相似度为 @@ -32,7 +32,7 @@ word2vec工具主要包含跳字模型和连续词袋模型。下面将分别介 ### 跳字模型 -在跳字模型中,我们用一个词来预测它在文本序列周围的词。举个例子,假设文本序列是“the”、“man”、“hit”、“his”和“son”。跳字模型所关心的是,给定“hit”生成邻近词“the”、“man”、“his”和“son”的条件概率。在这个例子中,“hit”叫中心词,“the”、“man”、“his”和“son”叫背景词。由于“hit”只生成与它距离不超过2的背景词,该时间窗口的大小为2。 +在跳字模型中,我们用一个词来预测它在文本序列周围的词。举个例子,假设文本序列是“the”、“man”、“love”、“his”和“son”。跳字模型所关心的是,给定“love”生成邻近词“the”、“man”、“his”和“son”的条件概率。在这个例子中,“love”叫中心词,“the”、“man”、“his”和“son”叫背景词。这里我们设定时间窗口的大小为2,所以“love”只生成与它距离不超过2的背景词。 我们来描述一下跳字模型。 @@ -66,7 +66,7 @@ $$\frac{\partial \text{log} \mathbb{P}(w_o \mid w_c)}{\partial \mathbf{v}_c} = \ ### 连续词袋模型 -连续词袋模型与跳字模型类似。与跳字模型最大的不同是,连续词袋模型用一个中心词在文本序列周围的词来预测该中心词。举个例子,假设文本序列为“the”、 “man”、“hit”、“his”和“son”。连续词袋模型所关心的是,邻近词“the”、“man”、“his”和“son”一起生成中心词“hit”的概率。 +连续词袋模型与跳字模型类似。与跳字模型最大的不同是,连续词袋模型用一个中心词在文本序列周围的词来预测该中心词。举个例子,假设文本序列为“the”、 “man”、“love”、“his”和“son”。连续词袋模型所关心的是,邻近词“the”、“man”、“his”和“son”一起生成中心词“love”的概率。 假设词典索引集$\mathcal{V}$的大小为$|\mathcal{V}|$,且$\mathcal{V} = \{0, 1, \ldots, |\mathcal{V}|-1\}$。给定一个长度为$T$的文本序列中,时间步$t$的词为$w^{(t)}$。当时间窗口大小为$m$时,连续词袋模型需要最大化由背景词生成任一中心词的概率 @@ -99,7 +99,7 @@ $$\frac{\partial \text{log} \mathbb{P}(w_c \mid w_{o_1}, \ldots, w_{o_{2m}})}{\p ## 近似训练法 -我们可以看到,无论是跳字模型还是连续词袋模型,每一步梯度计算的开销与词典$\mathcal{V}$的大小相关。当词典较大时,例如几十万到上百万,这种训练方法的计算开销会较大。因此,我们将使用近似的方法来计算这些梯度,从而减小计算开销。常用的近似训练法包括负采样和层序softmax。 +我们可以看到,无论是跳字模型还是连续词袋模型,每一步梯度计算的开销与词典$\mathcal{V}$的大小相关.。当词典较大时,例如几十万到上百万,这种训练方法的计算开销会较大。因此,我们将使用近似的方法来计算这些梯度,从而减小计算开销。常用的近似训练法包括负采样(negative sampling)和层序softmax。 @@ -109,7 +109,7 @@ $$\frac{\partial \text{log} \mathbb{P}(w_c \mid w_{o_1}, \ldots, w_{o_{2m}})}{\p 实际上,词典$\mathcal{V}$的大小之所以会在损失中出现,是因为给定中心词$w_c$生成背景词$w_o$的条件概率$\mathbb{P}(w_o \mid w_c)$使用了softmax运算,而softmax运算正是考虑了背景词可能是词典中的任一词,并体现在分母上。 -不妨换个角度考虑给定中心词生成背景词的条件概率。假设中心词$w_c$生成背景词$w_o$由以下相互独立事件联合组成来近似: +不妨换个角度考虑给定中心词生成背景词的条件概率。我们先定义噪声词分布$\mathbb{P}(w)$,接着假设中心词$w_c$生成背景词$w_o$由以下相互独立事件联合组成来近似: * 中心词$w_c$和背景词$w_o$同时出现时间窗口。 * 中心词$w_c$和第1个噪声词$w_1$不同时出现在该时间窗口(噪声词$w_1$按噪声词分布$\mathbb{P}(w)$随机生成,且假设一定和$w_c$不同时出现在该时间窗口)。 @@ -144,7 +144,7 @@ $$-\text{log} \mathbb{P}(w^{(t)} \mid w^{(t-m)}, \ldots, w^{(t-1)}, w^{(t+1)} $$-\text{log} \frac{1}{1+\text{exp}\left(-\mathbf{u}_c^\top (\mathbf{v}_{o_1} + \ldots + \mathbf{v}_{o_{2m}}) /(2m)\right)} - \sum_{k=1, w_k \sim \mathbb{P}(w)}^K \text{log} \frac{1}{1+\text{exp}\left((\mathbf{u}_{i_k}^\top (\mathbf{v}_{o_1} + \ldots + \mathbf{v}_{o_{2m}}) /(2m)\right)}.$$ -同样,当我们把$K$取较小值时,负采样每次迭代的计算开销将较小。 +同样,当我们把$K$取较小值时,负采样每次迭代的计算开销将较小。另外,定义噪声词分布$\mathbb{P}(w)$为单元语言模型概率分布(unigram distribution)的3/4次方的时候,采样效果最佳。 ## 层序softmax @@ -157,14 +157,14 @@ $$-\text{log} \frac{1}{1+\text{exp}\left(-\mathbf{u}_c^\top (\mathbf{v}_{o_1} + 假设$L(w)$为从二叉树的根节点到词$w$的叶子节点的路径(包括根和叶子节点)上的节点数。设$n(w,j)$为该路径上第$j$个节点,并设该节点的向量为$\mathbf{u}_{n(w,j)}$。以图10.1为例,$L(w_3) = 4$。设词典中的词$w_i$的词向量为$\mathbf{v}_i$。那么,跳字模型和连续词袋模型所需要计算的给定词$w_i$生成词$w$的条件概率为: -$$\mathbb{P}(w \mid w_i) = \prod_{j=1}^{L(w)-1} \sigma\left( \left[n(w, j+1) = \text{leftChild}(n(w,j))\right] \cdot \mathbf{u}_{n(w,j)}^\top \mathbf{v}_i\right),$$ +$$\mathbb{P}(w \mid w_i) = \prod_{j=1}^{L(w)-1} \sigma\left( \left[[n(w, j+1) = \text{leftChild}(n(w,j))\right]] \cdot \mathbf{u}_{n(w,j)}^\top \mathbf{v}_i\right),$$ -其中$\sigma(x) = 1/(1+\text{exp}(-x))$,$\text{leftChild}(n)$是节点$n$的左孩子节点,如果判断$x$为真,$[x] = 1$;反之$[x] = -1$。由于$\sigma(x)+\sigma(-x) = 1$,给定词$w_i$生成词典$\mathcal{V}$中任一词的条件概率之和为1这一条件也将满足: +其中$\sigma(x) = 1/(1+\text{exp}(-x))$,$\text{leftChild}(n)$是节点$n$的左孩子节点,如果判断$x$为真,$[[x]] = 1$;反之$[[x]] = -1$。由于$\sigma(x)+\sigma(-x) = 1$,给定词$w_i$生成词典$\mathcal{V}$中任一词的条件概率之和为1这一条件也将满足: $$\sum_{w \in \mathcal{V}} \mathbb{P}(w \mid w_i) = 1.$$ -让我们计算图10.1中给定词$w_i$生成词$w_3$的条件概率。我们需要将$w_i$的词向量$\mathbf{v}_i$和根节点到$w_3$路径上的非叶子节点向量一一求内积。由于在二叉树中由根节点到叶子节点$w_3$的路径上需要向左、向右、再向左地遍历,我们得到 +让我们计算图10.1中给定词$w_i$生成词$w_3$的条件概率。我们需要将$w_i$的词向量$\mathbf{v}_i$和根节点到$w_3$路径上的非叶子节点向量一一求内积。由于在二叉树中由根节点到叶子节点$w_3$的路径上需要中序遍历,我们得到 $$\mathbb{P}(w_3 \mid w_i) = \sigma(\mathbf{u}_{n(w_3,1)}^\top \mathbf{v}_i) \cdot \sigma(-\mathbf{u}_{n(w_3,2)}^\top \mathbf{v}_i) \cdot \sigma(\mathbf{u}_{n(w_3,3)}^\top \mathbf{v}_i).$$ @@ -176,6 +176,7 @@ $$\mathbb{P}(w_3 \mid w_i) = \sigma(\mathbf{u}_{n(w_3,1)}^\top \mathbf{v}_i) \cd ## 小结 +* 词向量是用于表示自然语言中词的语义的向量。 * word2vec工具中的跳字模型和连续词袋模型通常使用近似训练法,例如负采样和层序softmax,从而减小训练的计算开销。