未验证 提交 21b6d83f 编写于 作者: G goldmermaid 提交者: GitHub

[polish] chapter 14 peer review (#1019)

* chapter 14 polish half

* chapter 14 soft polish done
上级 6f23af8e
......@@ -53,7 +53,7 @@ $$
作为另一种近似训练方法,*层序Softmax*(hierarchical softmax)使用二叉树( :numref:`fig_hi_softmax`中说明的数据结构),其中树的每个叶节点表示词表$\mathcal{V}$中的一个词。
![用于近似训练的分层softmax,其中树的每个叶节点表示词表中的一个词](../img/hi-softmax.svg)
![用于近似训练的分层softmax,其中树的每个叶节点表示词表中的一个词](../img/hi-softmax.svg)
:label:`fig_hi_softmax`
用$L(w)$表示二叉树中表示字$w$的从根节点到叶节点的路径上的节点数(包括两端)。设$n(w,j)$为该路径上的$j^\mathrm{th}$节点,其上下文字向量为$\mathbf{u}_{n(w, j)}$。例如, :numref:`fig_hi_softmax`中的$L(w_3) = 4$。分层softmax将 :eqref:`eq_skip-gram-softmax`中的条件概率近似为
......
......@@ -211,7 +211,8 @@ def _pad_bert_inputs(examples, max_len, vocab):
#@save
class _WikiTextDataset(gluon.data.Dataset):
def __init__(self, paragraphs, max_len):
# 输入`paragraphs[i]`是代表段落的句子字符串列表;而输出`paragraphs[i]`是代表段落的句子列表,其中每个句子都是词元列表
# 输入`paragraphs[i]`是代表段落的句子字符串列表;
# 而输出`paragraphs[i]`是代表段落的句子列表,其中每个句子都是词元列表
paragraphs = [d2l.tokenize(
paragraph, token='word') for paragraph in paragraphs]
sentences = [sentence for paragraph in paragraphs
......@@ -248,7 +249,8 @@ class _WikiTextDataset(gluon.data.Dataset):
#@save
class _WikiTextDataset(torch.utils.data.Dataset):
def __init__(self, paragraphs, max_len):
# 输入`paragraphs[i]`是代表段落的句子字符串列表;而输出`paragraphs[i]`是代表段落的句子列表,其中每个句子都是词元列表
# 输入`paragraphs[i]`是代表段落的句子字符串列表;
# 而输出`paragraphs[i]`是代表段落的句子列表,其中每个句子都是词元列表
paragraphs = [d2l.tokenize(
paragraph, token='word') for paragraph in paragraphs]
sentences = [sentence for paragraph in paragraphs
......@@ -285,7 +287,7 @@ class _WikiTextDataset(torch.utils.data.Dataset):
```{.python .input}
#@save
def load_data_wiki(batch_size, max_len):
"""加载WikiText-2数据集"""
"""加载WikiText-2数据集"""
num_workers = d2l.get_dataloader_workers()
data_dir = d2l.download_extract('wikitext-2', 'wikitext-2')
paragraphs = _read_wiki(data_dir)
......@@ -299,7 +301,7 @@ def load_data_wiki(batch_size, max_len):
#@tab pytorch
#@save
def load_data_wiki(batch_size, max_len):
"""加载WikiText-2数据集"""
"""加载WikiText-2数据集"""
num_workers = d2l.get_dataloader_workers()
data_dir = d2l.download_extract('wikitext-2', 'wikitext-2')
paragraphs = _read_wiki(data_dir)
......
......@@ -21,7 +21,7 @@
如我们所见,ELMo对上下文进行双向编码,但使用特定于任务的架构;而GPT是任务无关的,但是从左到右编码上下文。BERT(来自Transformers的双向编码器表示)结合了这两个方面的优点。它对上下文进行双向编码,并且对于大多数的自然语言处理任务 :cite:`Devlin.Chang.Lee.ea.2018`只需要最少的架构改变。通过使用预训练的Transformer编码器,BERT能够基于其双向上下文表示任何词元。在下游任务的监督学习过程中,BERT在两个方面与GPT相似。首先,BERT表示将被输入到一个添加的输出层中,根据任务的性质对模型架构进行最小的更改,例如预测每个词元与预测整个序列。其次,对预训练Transformer编码器的所有参数进行微调,而额外的输出层将从头开始训练。 :numref:`fig_elmo-gpt-bert` 描述了ELMo、GPT和BERT之间的差异。
![ELMo、GPT和BERT的比较](../img/elmo-gpt-bert.svg)
![ELMo、GPT和BERT的比较](../img/elmo-gpt-bert.svg)
:label:`fig_elmo-gpt-bert`
BERT进一步改进了11种自然语言处理任务的技术水平,这些任务分为以下几个大类:(1)单一文本分类(如情感分析)、(2)文本对分类(如自然语言推断)、(3)问答、(4)文本标记(如命名实体识别)。从上下文敏感的ELMo到任务不可知的GPT和BERT,它们都是在2018年提出的。概念上简单但经验上强大的自然语言深度表示预训练已经彻底改变了各种自然语言处理任务的解决方案。
......@@ -56,7 +56,7 @@ from torch import nn
#@tab all
#@save
def get_tokens_and_segments(tokens_a, tokens_b=None):
"""获取输入序列的词元及其片段索引"""
"""获取输入序列的词元及其片段索引"""
tokens = ['<cls>'] + tokens_a + ['<sep>']
# 0和1分别标记片段A和B
segments = [0] * (len(tokens_a) + 2)
......@@ -69,7 +69,7 @@ def get_tokens_and_segments(tokens_a, tokens_b=None):
BERT选择Transformer编码器作为其双向架构。在Transformer编码器中常见是,位置嵌入被加入到输入序列的每个位置。然而,与原始的Transformer编码器不同,BERT使用*可学习的*位置嵌入。总之,
:numref:`fig_bert-input`表明BERT输入序列的嵌入是词元嵌入、片段嵌入和位置嵌入的和。
![BERT输入序列的嵌入是词元嵌入、片段嵌入和位置嵌入的和](../img/bert-input.svg)
![BERT输入序列的嵌入是词元嵌入、片段嵌入和位置嵌入的和](../img/bert-input.svg)
:label:`fig_bert-input`
下面的`BERTEncoder`类类似于 :numref:`sec_transformer`中实现的`TransformerEncoder`类。与`TransformerEncoder`不同,`BERTEncoder`使用片段嵌入和可学习的位置嵌入。
......@@ -77,7 +77,7 @@ BERT选择Transformer编码器作为其双向架构。在Transformer编码器中
```{.python .input}
#@save
class BERTEncoder(nn.Block):
"""BERT encoder."""
"""BERT编码器"""
def __init__(self, vocab_size, num_hiddens, ffn_num_hiddens, num_heads,
num_layers, dropout, max_len=1000, **kwargs):
super(BERTEncoder, self).__init__(**kwargs)
......@@ -104,7 +104,7 @@ class BERTEncoder(nn.Block):
#@tab pytorch
#@save
class BERTEncoder(nn.Module):
"""BERT encoder."""
"""BERT编码器"""
def __init__(self, vocab_size, num_hiddens, norm_shape, ffn_num_input,
ffn_num_hiddens, num_heads, num_layers, dropout,
max_len=1000, key_size=768, query_size=768, value_size=768,
......@@ -202,8 +202,8 @@ class MaskLM(nn.Block):
pred_positions = pred_positions.reshape(-1)
batch_size = X.shape[0]
batch_idx = np.arange(0, batch_size)
# 假设`batch_size=2,`num_pred_positions`=3
# 那么`batch_idx`是`np.array([0,0,0,1,1])`
# 假设batch_size=2,num_pred_positions=3
# 那么batch_idx是np.array([0,0,0,1,1])
batch_idx = np.repeat(batch_idx, num_pred_positions)
masked_X = X[batch_idx, pred_positions]
masked_X = masked_X.reshape((batch_size, num_pred_positions, -1))
......@@ -228,8 +228,8 @@ class MaskLM(nn.Module):
pred_positions = pred_positions.reshape(-1)
batch_size = X.shape[0]
batch_idx = torch.arange(0, batch_size)
# 假设`batch_size=2,`num_pred_positions`=3
# 那么`batch_idx`是`np.array([0,0,0,1,1])`
# 假设batch_size=2,num_pred_positions=3
# 那么batch_idx是np.array([0,0,0,1,1])
batch_idx = torch.repeat_interleave(batch_idx, num_pred_positions)
masked_X = X[batch_idx, pred_positions]
masked_X = masked_X.reshape((batch_size, num_pred_positions, -1))
......@@ -288,7 +288,7 @@ class NextSentencePred(nn.Block):
self.output = nn.Dense(2)
def forward(self, X):
# `X`的形状: (batch size, `num_hiddens`)
# X的形状: (batch size,`num_hiddens`)
return self.output(X)
```
......@@ -318,7 +318,7 @@ nsp_Y_hat.shape
```{.python .input}
#@tab pytorch
encoded_X = torch.flatten(encoded_X, start_dim=1)
# NSP的输入形状: (batch size, `num_hiddens`)
# NSP的输入形状: (batch size`num_hiddens`)
nsp = NextSentencePred(encoded_X.shape[-1])
nsp_Y_hat = nsp(encoded_X)
nsp_Y_hat.shape
......@@ -358,13 +358,14 @@ class BERTModel(nn.Block):
self.mlm = MaskLM(vocab_size, num_hiddens)
self.nsp = NextSentencePred()
def forward(self, tokens, segments, valid_lens=None, pred_positions=None):
def forward(self, tokens, segments, valid_lens=None,
pred_positions=None):
encoded_X = self.encoder(tokens, segments, valid_lens)
if pred_positions is not None:
mlm_Y_hat = self.mlm(encoded_X, pred_positions)
else:
mlm_Y_hat = None
# 用于下一句预测的多层感知机分类器的隐藏层。0是“<cls>”标记的索引。
# 用于下一句预测的多层感知机分类器的隐藏层,0是“<cls>”标记的索引
nsp_Y_hat = self.nsp(self.hidden(encoded_X[:, 0, :]))
return encoded_X, mlm_Y_hat, nsp_Y_hat
```
......@@ -389,13 +390,14 @@ class BERTModel(nn.Module):
self.mlm = MaskLM(vocab_size, num_hiddens, mlm_in_features)
self.nsp = NextSentencePred(nsp_in_features)
def forward(self, tokens, segments, valid_lens=None, pred_positions=None):
def forward(self, tokens, segments, valid_lens=None,
pred_positions=None):
encoded_X = self.encoder(tokens, segments, valid_lens)
if pred_positions is not None:
mlm_Y_hat = self.mlm(encoded_X, pred_positions)
else:
mlm_Y_hat = None
# 用于下一句预测的多层感知机分类器的隐藏层。0是“<cls>”标记的索引。
# 用于下一句预测的多层感知机分类器的隐藏层,0是“<cls>”标记的索引
nsp_Y_hat = self.nsp(self.hidden(encoded_X[:, 0, :]))
return encoded_X, mlm_Y_hat, nsp_Y_hat
```
......
......@@ -84,14 +84,14 @@ $$\mathbf{u}_j^\top \mathbf{v}_i + b_i + c_j \approx \log\, x_{ij}.$$
## 小结
* 可以使用诸如词-词共现计数的全局语料库统计来解释跳元模型。
* 诸如词-词共现计数的全局语料库统计可以来解释跳元模型。
* 交叉熵损失可能不是衡量两种概率分布差异的好选择,特别是对于大型语料库。GloVe使用平方损失来拟合预先计算的全局语料库统计数据。
* 对于GloVe中的任意词,中心词向量和上下文词向量在数学上是等价的。
* GloVe可以从词-词共现概率的比率来解释。
## 练习
1. 如果词$w_i$和$w_j$在同一上下文窗口中同时出现,我们如何使用它们在文本序列中的距离来重新设计计算条件概率$p_{ij}$的方法?提示:参见GloVe论文的第4.2节 :cite:`Pennington.Socher.Manning.2014`
1. 如果词$w_i$和$w_j$在同一上下文窗口中同时出现,我们如何使用它们在文本序列中的距离来重新设计计算条件概率$p_{ij}$的方法?提示:参见GloVe论文 :cite:`Pennington.Socher.Manning.2014`的第4.2节
1. 对于任何一个词,它的中心词偏置和上下文偏置在数学上是等价的吗?为什么?
[Discussions](https://discuss.d2l.ai/t/385)
# 自然语言处理:预训练
:label:`chap_nlp_pretrain`
人类需要交流。出于人类这种基本需要,每天都产生了大量的书面文本。考虑到社交媒体、聊天应用、电子邮件、产品评论、新闻文章、研究论文和书籍中的丰富文本,使计算机能够理解它们以提供帮助或基于人类语言做出决策变得至关重要。
人与人之间需要交流。
出于人类这种基本需要,每天都有大量的书面文本产生。
比如,社交媒体、聊天应用、电子邮件、产品评论、新闻文章、
研究论文和书籍中的丰富文本,
使计算机能够理解它们以提供帮助或基于人类语言做出决策变得至关重要。
*自然语言处理*是指研究使用自然语言的计算机和人类之间的交互。
在实践中,使用自然语言处理技术来处理和分析文本(人类自然语言)数据是非常常见的,例如 :numref:`sec_language_model`的语言模型和 :numref:`sec_machine_translation`的机器翻译模型。
要理解文本,我们可以从学习它的表示开始。利用来自大型语料库的现有文本序列,
*自监督学习*(self-supervised learning)已被广泛用于预训练文本表示,例如通过使用周围文本的其它部分来预测文本的隐藏部分。通过这种方式,模型可以通过有监督地从*海量*文本数据中学习,而不需要*昂贵*的标签标注!
正如我们将在本章中看到的那样,当将每个单词或子词视为单个词元时,可以在大型语料库上使用word2vec、GloVe或子词嵌入模型预先训练每个词元的词元。经过预训练后,每个词元的表示可以是一个向量,但是,无论上下文是什么,它都保持不变。例如,“bank”(可以译作银行或者河岸)的向量表示在“go to the bank to deposit some money”(去银行存点钱)和“go to the bank to sit down”(去河岸坐下来)中是相同的。因此,许多较新的预训练模型使相同词元的表示适应于不同的上下文。其中包括基于transformer编码器的更深的自监督模型BERT。在本章中,我们将重点讨论如何预训练文本的这种表示,如 :numref:`fig_nlp-map-pretrain`中所强调的那样。
![预训练好的文本表示可以送入不同下游自然语言处理应用的各种深度学习架构。本章主要研究上游文本的预训练。](../img/nlp-map-pretrain.svg)
在实践中,使用自然语言处理技术来处理和分析文本数据是非常常见的,
例如 :numref:`sec_language_model`的语言模型
和 :numref:`sec_machine_translation`的机器翻译模型。
要理解文本,我们可以从学习它的表示开始。
利用来自大型语料库的现有文本序列,
*自监督学习*(self-supervised learning)
已被广泛用于预训练文本表示,
例如通过使用周围文本的其它部分来预测文本的隐藏部分。
通过这种方式,模型可以通过有监督地从*海量*文本数据中学习,而不需要*昂贵*的标签标注!
本章我们将看到:当将每个单词或子词视为单个词元时,
可以在大型语料库上使用word2vec、GloVe或子词嵌入模型预先训练每个词元的词元。
经过预训练后,每个词元的表示可以是一个向量。
但是,无论上下文是什么,它都保持不变。
例如,“bank”(可以译作银行或者河岸)的向量表示在
“go to the bank to deposit some money”(去银行存点钱)
和“go to the bank to sit down”(去河岸坐下来)中是相同的。
因此,许多较新的预训练模型使相同词元的表示适应于不同的上下文,
其中包括基于transformer编码器的更深的自监督模型BERT。
在本章中,我们将重点讨论如何预训练文本的这种表示,
如 :numref:`fig_nlp-map-pretrain`中所强调的那样。
![预训练好的文本表示可以放入各种深度学习架构,应用于不同自然语言处理任务(本章主要研究上游文本的预训练)](../img/nlp-map-pretrain.svg)
:label:`fig_nlp-map-pretrain`
为了看清全局, :numref:`fig_nlp-map-pretrain`显示了预训练的文本表示可以被送入到不同下游自然语言处理应用的各种深度学习架构。我们将在 :numref:`chap_nlp_app`中介绍它们。
:numref:`fig_nlp-map-pretrain`显示了
预训练好的文本表示可以放入各种深度学习架构,应用于不同自然语言处理任务。
我们将在 :numref:`chap_nlp_app`中介绍它们。
```toc
......
......@@ -48,7 +48,7 @@ d2l.DATA_HUB['wiki.en'] = (d2l.DATA_URL + 'wiki.en.zip',
#@tab all
#@save
class TokenEmbedding:
"""Token Embedding."""
"""GloVe嵌入"""
def __init__(self, embedding_name):
self.idx_to_token, self.idx_to_vec = self._load_embedding(
embedding_name)
......@@ -137,8 +137,8 @@ def knn(W, x, k):
#@tab all
def get_similar_tokens(query_token, k, embed):
topk, cos = knn(embed.idx_to_vec, embed[[query_token]], k + 1)
for i, c in zip(topk[1:], cos[1:]): # Exclude the input word
print(f'cosine sim={float(c):.3f}: {embed.idx_to_token[int(i)]}')
for i, c in zip(topk[1:], cos[1:]): # 排除输入词
print(f'{embed.idx_to_token[int(i)]}:cosine相似度={float(c):.3f}')
```
`glove_6b50d`中预训练词向量的词表包含400000个词和一个特殊的未知词元。排除输入词和未知词元后,我们在词表中找到与“chip”一词语义最相似的三个词。
......
......@@ -5,9 +5,12 @@
## fastText模型
回想一下词在word2vec中是如何表示的。在跳元模型和连续词袋模型中,同一词的不同变形形式直接由不同的向量表示,不需要共享参数。为了使用形态信息,*fastText*模型提出了一种*子词嵌入*方法,其中子词是一个字符$n$-gram :cite:`Bojanowski.Grave.Joulin.ea.2017`。fastText可以被认为是子词级跳元模型,而非学习词级向量表示,其中每个*中心词*由其子词级向量之和表示。
回想一下词在word2vec中是如何表示的。在跳元模型和连续词袋模型中,同一词的不同变形形式直接由不同的向量表示,不需要共享参数。为了使用形态信息,*fastText模型*提出了一种*子词嵌入*方法,其中子词是一个字符$n$-gram :cite:`Bojanowski.Grave.Joulin.ea.2017`。fastText可以被认为是子词级跳元模型,而非学习词级向量表示,其中每个*中心词*由其子词级向量之和表示。
让我们来说明如何以单词“where”为例获得fastText中每个中心词的子词。首先,在词的开头和末尾添加特殊字符“&lt;”和“&gt;”,以将前缀和后缀与其他子词区分开来。然后,从词中提取字符$n$-gram。例如,值$n=3$时,我们将获得长度为3的所有子词:“&lt;wh”、“whe”、“her”、“ere”、“re&gt;”和特殊子词“&lt;where&gt;”。
让我们来说明如何以单词“where”为例获得fastText中每个中心词的子词。首先,在词的开头和末尾添加特殊字符“&lt;”和“&gt;”,以将前缀和后缀与其他子词区分开来。
然后,从词中提取字符$n$-gram。
例如,值$n=3$时,我们将获得长度为3的所有子词:
&lt;wh”、“whe”、“her”、“ere”、“re&gt;”和特殊子词“&lt;where&gt;”。
在fastText中,对于任意词$w$,用$\mathcal{G}_w$表示其长度在3和6之间的所有子词与其特殊子词的并集。词表是所有词的子词的集合。假设$\mathbf{z}_g$是词典中的子词$g$的向量,则跳元模型中作为中心词的词$w$的向量$\mathbf{v}_w$是其子词向量的和:
......@@ -80,7 +83,7 @@ num_merges = 10
for i in range(num_merges):
max_freq_pair = get_max_freq_pair(token_freqs)
token_freqs = merge_symbols(max_freq_pair, token_freqs, symbols)
print(f'merge #{i + 1}:', max_freq_pair)
print(f'合并#{i + 1}:', max_freq_pair)
```
在字节对编码的10次迭代之后,我们可以看到列表`symbols`现在又包含10个从其他符号迭代合并而来的符号。
......@@ -130,7 +133,7 @@ print(segment_BPE(tokens, symbols))
## 小结
* fastText模型提出了一种子词嵌入方法基于word2vec中的跳元模型,它将中心词表示为其子词向量之和。
* fastText模型提出了一种子词嵌入方法基于word2vec中的跳元模型,它将中心词表示为其子词向量之和。
* 字节对编码执行训练数据集的统计分析,以发现词内的公共符号。作为一种贪心方法,字节对编码迭代地合并最频繁的连续符号对。
* 子词嵌入可以提高稀有词和词典外词的表示质量。
......
......@@ -32,7 +32,7 @@ d2l.DATA_HUB['ptb'] = (d2l.DATA_URL + 'ptb.zip',
#@save
def read_ptb():
"""将PTB数据集加载到文本行的列表中"""
"""将PTB数据集加载到文本行的列表中"""
data_dir = d2l.download_extract('ptb')
# Read the training set.
with open(os.path.join(data_dir, 'ptb.train.txt')) as f:
......@@ -63,8 +63,8 @@ $$ P(w_i) = \max\left(1 - \sqrt{\frac{t}{f(w_i)}}, 0\right),$$
#@tab all
#@save
def subsample(sentences, vocab):
"""下采样高频词"""
# 排除未知词元 '<unk>'
"""下采样高频词"""
# 排除未知词元'<unk>'
sentences = [[token for token in line if vocab[token] != vocab.unk]
for line in sentences]
counter = d2l.count_corpus(sentences)
......@@ -85,8 +85,9 @@ subsampled, counter = subsample(sentences, vocab)
```{.python .input}
#@tab all
d2l.show_list_len_pair_hist(['origin', 'subsampled'], '# tokens per sentence',
'count', sentences, subsampled);
d2l.show_list_len_pair_hist(
['origin', 'subsampled'], '# tokens per sentence',
'count', sentences, subsampled);
```
对于单个词元,高频词“the”的采样率不到1/20。
......@@ -94,9 +95,9 @@ d2l.show_list_len_pair_hist(['origin', 'subsampled'], '# tokens per sentence',
```{.python .input}
#@tab all
def compare_counts(token):
return (f'# of "{token}": '
f'before={sum([l.count(token) for l in sentences])}, '
f'after={sum([l.count(token) for l in subsampled])}')
return (f'"{token}"的数量:'
f'之前={sum([l.count(token) for l in sentences])}, '
f'之后={sum([l.count(token) for l in subsampled])}')
compare_counts('the')
```
......@@ -146,9 +147,9 @@ def get_centers_and_contexts(corpus, max_window_size):
```{.python .input}
#@tab all
tiny_dataset = [list(range(7)), list(range(7, 10))]
print('dataset', tiny_dataset)
print('数据集', tiny_dataset)
for center, context in zip(*get_centers_and_contexts(tiny_dataset, 2)):
print('center', center, 'has contexts', context)
print('中心词', center, '的上下文词是', context)
```
在PTB数据集上进行训练时,我们将最大上下文窗口大小设置为5。下面提取数据集中的所有中心词及其上下文词。
......@@ -156,7 +157,7 @@ for center, context in zip(*get_centers_and_contexts(tiny_dataset, 2)):
```{.python .input}
#@tab all
all_centers, all_contexts = get_centers_and_contexts(corpus, 5)
f'# center-context pairs: {sum([len(contexts) for contexts in all_contexts])}'
f'# “中心词-上下文词对”的数量: {sum([len(contexts) for contexts in all_contexts])}'
```
## 负采样
......@@ -167,7 +168,7 @@ f'# center-context pairs: {sum([len(contexts) for contexts in all_contexts])}'
#@tab all
#@save
class RandomGenerator:
"""根据n个采样权重在 {1, ..., n} 中随机抽取。"""
"""根据n个采样权重在{1, ..., n}中随机抽取"""
def __init__(self, sampling_weights):
# Exclude
self.population = list(range(1, len(sampling_weights) + 1))
......@@ -188,6 +189,8 @@ class RandomGenerator:
例如,我们可以在索引1、2和3中绘制10个随机变量$X$,采样概率为$P(X=1)=2/9, P(X=2)=3/9$和$P(X=3)=4/9$,如下所示。
```{.python .input}
#@tab all
#@save
generator = RandomGenerator([2, 3, 4])
[generator.draw() for _ in range(10)]
```
......@@ -198,7 +201,7 @@ generator = RandomGenerator([2, 3, 4])
#@tab all
#@save
def get_negatives(all_contexts, vocab, counter, K):
"""返回负采样中的噪声词"""
"""返回负采样中的噪声词"""
# 索引为1、2、...(索引0是词表中排除的未知标记)
sampling_weights = [counter[vocab.to_tokens(i)]**0.75
for i in range(1, len(vocab))]
......@@ -231,13 +234,14 @@ all_negatives = get_negatives(all_contexts, vocab, counter, 5)
#@tab all
#@save
def batchify(data):
"""返回带有负采样的跳元模型的小批量样本"""
"""返回带有负采样的跳元模型的小批量样本"""
max_len = max(len(c) + len(n) for _, c, n in data)
centers, contexts_negatives, masks, labels = [], [], [], []
for center, context, negative in data:
cur_len = len(context) + len(negative)
centers += [center]
contexts_negatives += [context + negative + [0] * (max_len - cur_len)]
contexts_negatives += \
[context + negative + [0] * (max_len - cur_len)]
masks += [[1] * cur_len + [0] * (max_len - cur_len)]
labels += [[1] * len(context) + [0] * (max_len - len(context))]
return (d2l.reshape(d2l.tensor(centers), (-1, 1)), d2l.tensor(
......@@ -264,7 +268,7 @@ for name, data in zip(names, batch):
```{.python .input}
#@save
def load_data_ptb(batch_size, max_window_size, num_noise_words):
"""下载PTB数据集,然后将其加载到内存中"""
"""下载PTB数据集,然后将其加载到内存中"""
sentences = read_ptb()
vocab = d2l.Vocab(sentences, min_freq=10)
subsampled, counter = subsample(sentences, vocab)
......@@ -312,9 +316,9 @@ def load_data_ptb(batch_size, max_window_size, num_noise_words):
dataset = PTBDataset(all_centers, all_contexts, all_negatives)
data_iter = torch.utils.data.DataLoader(dataset, batch_size, shuffle=True,
collate_fn=batchify,
num_workers=num_workers)
data_iter = torch.utils.data.DataLoader(
dataset, batch_size, shuffle=True,
collate_fn=batchify, num_workers=num_workers)
return data_iter, vocab
```
......
......@@ -240,7 +240,8 @@ def get_similar_tokens(query_token, k, embed):
W = embed.weight.data()
x = W[vocab[query_token]]
# 计算余弦相似性。增加1e-9以获得数值稳定性
cos = np.dot(W, x) / np.sqrt(np.sum(W * W, axis=1) * np.sum(x * x) + 1e-9)
cos = np.dot(W, x) / np.sqrt(np.sum(W * W, axis=1) * np.sum(x * x) + \
1e-9)
topk = npx.topk(cos, k=k+1, ret_typ='indices').asnumpy().astype('int32')
for i in topk[1:]: # 删除输入词
print(f'cosine sim={float(cos[i]):.3f}: {vocab.to_tokens(i)}')
......
# 词嵌入(Word2vec)
:label:`sec_word2vec`
自然语言是用来表达意义的复杂系统。在这个系统中,词是意义的基本单元。顾名思义,
*词向量*是用于表示单词的向量,
并且还可以被认为是单词的特征向量或表示。将单词映射到实向量的技术称为*词嵌入*。近年来,词嵌入逐渐成为自然语言处理的基础知识。
自然语言是用来表达人脑思维的复杂系统。
在这个系统中,词是意义的基本单元。顾名思义,
*词向量*是用于表示单词意义的向量,
并且还可以被认为是单词的特征向量或表示。
将单词映射到实向量的技术称为*词嵌入*
近年来,词嵌入逐渐成为自然语言处理的基础知识。
## 独热向量是一个糟糕的选择
## 为何独热向量是一个糟糕的选择
在 :numref:`sec_rnn_scratch`中,我们使用独热向量来表示词(字符就是单词)。假设词典中不同词的数量(词典大小)为$N$,每个词对应一个从$0$到$N−1$的不同整数(索引)。为了得到索引为$i$的任意词的独热向量表示,我们创建了一个全为0的长度为$N$的向量,并将位置$i$的元素设置为1。这样,每个词都被表示为一个长度为$N$的向量,可以直接由神经网络使用。
......@@ -32,7 +35,7 @@ $$P(\textrm{"the"},\textrm{"man"},\textrm{"his"},\textrm{"son"}\mid\textrm{"love
$$P(\textrm{"the"}\mid\textrm{"loves"})\cdot P(\textrm{"man"}\mid\textrm{"loves"})\cdot P(\textrm{"his"}\mid\textrm{"loves"})\cdot P(\textrm{"son"}\mid\textrm{"loves"}).$$
![跳元模型考虑了在给定中心词的情况下生成周围上下文词的条件概率](../img/skip-gram.svg)
![跳元模型考虑了在给定中心词的情况下生成周围上下文词的条件概率](../img/skip-gram.svg)
:label:`fig_skip_gram`
在跳元模型中,每个词都有两个$d$维向量表示,用于计算条件概率。更具体地说,对于词典中索引为$i$的任何词,分别用$\mathbf{v}_i\in\mathbb{R}^d$和$\mathbf{u}_i\in\mathbb{R}^d$表示其用作*中心词**上下文词*时的两个向量。给定中心词$w_c$(词典中的索引$c$),生成任何上下文词$w_o$(词典中的索引$o$)的条件概率可以通过对向量点积的softmax操作来建模:
......@@ -72,7 +75,7 @@ $$\begin{aligned}\frac{\partial \text{log}\, P(w_o \mid w_c)}{\partial \mathbf{v
$$P(\textrm{"loves"}\mid\textrm{"the"},\textrm{"man"},\textrm{"his"},\textrm{"son"}).$$
![连续词袋模型考虑了给定周围上下文词生成中心词条件概率](../img/cbow.svg)
![连续词袋模型考虑了给定周围上下文词生成中心词条件概率](../img/cbow.svg)
:label:`fig_cbow`
......@@ -108,9 +111,9 @@ $$\frac{\partial \log\, P(w_c \mid \mathcal{W}_o)}{\partial \mathbf{v}_{o_i}} =
## 小结
* 词向量是用来表示词的向量,也可以看作是词的特征向量或表示。将词映射到实向量的技术称为词嵌入。
* 词向量是用于表示单词意义的向量,也可以看作是词的特征向量。将词映射到实向量的技术称为词嵌入。
* word2vec工具包含跳元模型和连续词袋模型。
* 跳元模型假设单词可用于在文本序列中生成其周围的单词;而连续词袋模型假设基于上下文词来生成中心单词。
* 跳元模型假设一个单词可用于在文本序列中,生成其周围的单词;而连续词袋模型假设基于上下文词来生成中心单词。
## 练习
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册