diff --git a/chapter_recurrent-neural-networks/language-models-and-dataset.md b/chapter_recurrent-neural-networks/language-models-and-dataset.md index ec12fb41c..43f6fb49d 100644 --- a/chapter_recurrent-neural-networks/language-models-and-dataset.md +++ b/chapter_recurrent-neural-networks/language-models-and-dataset.md @@ -1,17 +1,17 @@ # 语言模型和数据集 :label:`sec_language_model` -在 :numref:`sec_text_preprocessing` 中,我们了解了如何将文本数据映射为标记,以及这些标记可以被视为一系列离散的观测,例如单词或字符。假设长度为 $T$ 的文本序列中的标记依次为 $x_1, x_2, \ldots, x_T$。于是,$x_t$($1 \leq t \leq T$) 可以被认为是文本序列在时间步 $t$ 处的观测或标签。在给定这样的文本序列时,*语言模型*(language model)的目标是估计序列的联合概率 +在 :numref:`sec_text_preprocessing` 中,我们了解了如何将文本数据映射为词元,以及这些词元可以被视为一系列离散的观测,例如单词或字符。假设长度为 $T$ 的文本序列中的词元依次为 $x_1, x_2, \ldots, x_T$。于是,$x_t$($1 \leq t \leq T$) 可以被认为是文本序列在时间步 $t$ 处的观测或标签。在给定这样的文本序列时,*语言模型*(language model)的目标是估计序列的联合概率 $$P(x_1, x_2, \ldots, x_T).$$ -语言模型是非常有用的。例如,只需要一次抽取一个标记 $x_t \sim P(x_t \mid x_{t-1}, \ldots, x_1)$,一个理想的语言模型就能够基于模型本身生成自然文本。与猴子使用打字机完全不同的是,从这样的模型中浮现的所有文本都将作为自然语言(例如,英语文本)来传递。此外,只需要基于前面的对话片断中的文本,就足以生成一个有意义的对话。显然,我们离设计出这样的系统还很遥远,因为它需要“理解”文本,而不仅仅是生成在语法上合理的内容。 +语言模型是非常有用的。例如,只需要一次抽取一个词元 $x_t \sim P(x_t \mid x_{t-1}, \ldots, x_1)$,一个理想的语言模型就能够基于模型本身生成自然文本。与猴子使用打字机完全不同的是,从这样的模型中浮现的所有文本都将作为自然语言(例如,英语文本)来传递。此外,只需要基于前面的对话片断中的文本,就足以生成一个有意义的对话。显然,我们离设计出这样的系统还很遥远,因为它需要“理解”文本,而不仅仅是生成在语法上合理的内容。 尽管如此,语言模型依然是非常有用的,即使在它们受限的形式下。例如,短语“to recognize speech”和“to wreck a nice beach”听起来非常相似。这种相似性会导致语音识别中的歧义,但是这很容易通过语言模型来解决,因为第二种翻译的感觉很奇怪。同样,在文档摘要生成算法中,“狗咬人”比“人咬狗”出现的频率要高得多,或者“我想吃奶奶”是一个相当令人不安的语句,而“我想吃,奶奶”则要和蔼得多。 ## 学习语言模型 -显而易见,我们面对的问题是如何对一个文档,甚至是一个标记序列进行建模。假设在单词级别对文本数据进行词元化,我们可以依靠在 :numref:`sec_sequence` 中对序列模型的分析。让我们从基本概率规则开始: +显而易见,我们面对的问题是如何对一个文档,甚至是一个词元序列进行建模。假设在单词级别对文本数据进行词元化,我们可以依靠在 :numref:`sec_sequence` 中对序列模型的分析。让我们从基本概率规则开始: $$P(x_1, x_2, \ldots, x_T) = \prod_{t=1}^T P(x_t \mid x_1, \ldots, x_{t-1}).$$ @@ -127,7 +127,7 @@ trigram_vocab = d2l.Vocab(trigram_tokens) trigram_vocab.token_freqs[:10] ``` -最后,让我们[**直观地对比三种模型中的标记频率**]:一元语法、二元语法和三元语法。 +最后,让我们[**直观地对比三种模型中的词元频率**]:一元语法、二元语法和三元语法。 ```{.python .input} #@tab all @@ -144,7 +144,7 @@ d2l.plot([freqs, bigram_freqs, trigram_freqs], xlabel='token: x', 由于序列数据本质上是连续的,因此我们在处理数据时需要解决这个问题。在 :numref:`sec_sequence` 中我们以一种相当特别的方式做到了这一点。当序列变得太长而不能被模型一次性全部处理时,我们可能希望拆分这样的序列方便模型读取。现在让我们描述一下总体策略。在介绍该模型之前,假设我们将使用神经网络来训练语言模型,模型中的网络一次处理具有预定义长度(例如 $n$ 个时间步)的一个小批量序列。现在的问题是如何[**随机地生成一个小批量数据的特征和标签以供读取。**] -首先,由于文本序列可以是任意长的,例如整本《时光机器》(*The Time Machine*),于是任意长的序列可以被我们划分为具有相同时间步数的子序列。当训练我们的神经网络时,这样的小批量子序列将被输入到模型中。假设网络一次只处理具有 $n$ 个时间步的子序列。 :numref:`fig_timemachine_5gram` 画出了从原始文本序列获得子序列的所有不同的方式,其中 $n=5$,并且每个时间步的标记对应于一个字符。请注意,因为我们可以选择任意偏移量来指示初始位置,所以我们有相当大的自由度。 +首先,由于文本序列可以是任意长的,例如整本《时光机器》(*The Time Machine*),于是任意长的序列可以被我们划分为具有相同时间步数的子序列。当训练我们的神经网络时,这样的小批量子序列将被输入到模型中。假设网络一次只处理具有 $n$ 个时间步的子序列。 :numref:`fig_timemachine_5gram` 画出了从原始文本序列获得子序列的所有不同的方式,其中 $n=5$,并且每个时间步的词元对应于一个字符。请注意,因为我们可以选择任意偏移量来指示初始位置,所以我们有相当大的自由度。 ![分割文本时,不同的偏移量会导致不同的子序列。](../img/timemachine-5gram.svg) :label:`fig_timemachine_5gram` @@ -153,7 +153,7 @@ d2l.plot([freqs, bigram_freqs, trigram_freqs], xlabel='token: x', ### 随机采样 -(**在随机采样中,每个样本都是在原始的长序列上任意捕获的子序列。**)在迭代过程中,来自两个相邻的、随机的、小批量中的子序列不一定在原始序列上相邻。对于语言建模,目标是基于到目前为止我们看到的标记来预测下一个标记,因此标签是移位了一个标记的原始序列。 +(**在随机采样中,每个样本都是在原始的长序列上任意捕获的子序列。**)在迭代过程中,来自两个相邻的、随机的、小批量中的子序列不一定在原始序列上相邻。对于语言建模,目标是基于到目前为止我们看到的词元来预测下一个词元,因此标签是移位了一个词元的原始序列。 下面的代码每次都从数据中随机生成一个小批量。在这里,参数 `batch_size` 指定了每个小批量中子序列样本的数目,参数 `num_steps` 是每个子序列中预定义的时间步数。 diff --git a/chapter_recurrent-neural-networks/rnn-scratch.md b/chapter_recurrent-neural-networks/rnn-scratch.md index c7c636dfd..ba4db3fe1 100644 --- a/chapter_recurrent-neural-networks/rnn-scratch.md +++ b/chapter_recurrent-neural-networks/rnn-scratch.md @@ -44,9 +44,9 @@ train_random_iter, vocab_random_iter = d2l.load_data_time_machine( ## [**独热编码**] -回想一下,在 `train_iter` 中,每个标记都表示为一个数字索引。将这些索引直接输入神经网络可能会使学习变得困难。我们通常将每个标记表示为更具表现力的特征向量。最简单的表示称为*独热编码*(one-hot encoding),它在 :numref:`subsec_classification-problem` 中介绍过。 +回想一下,在 `train_iter` 中,每个词元都表示为一个数字索引。将这些索引直接输入神经网络可能会使学习变得困难。我们通常将每个词元表示为更具表现力的特征向量。最简单的表示称为*独热编码*(one-hot encoding),它在 :numref:`subsec_classification-problem` 中介绍过。 -简言之,将每个索引映射为相互不同的单位向量:假设词汇表中不同标记的数目为$N$(即 `len(vocab)`),标记索引的范围为$0$到$N-1$。如果标记的索引是整数$i$,那么我们创建一个长度为$N$的全$0$向量,并将第$i$处的元素设置为$1$。此向量是原始标记的一个独热向量。索引为$0$和$2$的独热向量如下所示。 +简言之,将每个索引映射为相互不同的单位向量:假设词汇表中不同词元的数目为$N$(即 `len(vocab)`),词元索引的范围为$0$到$N-1$。如果词元的索引是整数$i$,那么我们创建一个长度为$N$的全$0$向量,并将第$i$处的元素设置为$1$。此向量是原始词元的一个独热向量。索引为$0$和$2$的独热向量如下所示。 ```{.python .input} npx.one_hot(np.array([0, 2]), len(vocab)) @@ -458,7 +458,7 @@ def grad_clipping(grads, theta): #@save def train_epoch_ch8(net, train_iter, loss, updater, device, use_random_iter): """训练模型一个迭代周期(定义见第8章)。""" state, timer = None, d2l.Timer() - metric = d2l.Accumulator(2) # 训练损失之和, 标记数量 + metric = d2l.Accumulator(2) # 训练损失之和, 词元数量 for X, Y in train_iter: if state is None or use_random_iter: # 在第一次迭代或使用随机抽样时初始化`state` @@ -484,7 +484,7 @@ def train_epoch_ch8(net, train_iter, loss, updater, device, use_random_iter): def train_epoch_ch8(net, train_iter, loss, updater, device, use_random_iter): """训练模型一个迭代周期(定义见第8章)。""" state, timer = None, d2l.Timer() - metric = d2l.Accumulator(2) # 训练损失之和, 标记数量 + metric = d2l.Accumulator(2) # 训练损失之和, 词元数量 for X, Y in train_iter: if state is None or use_random_iter: # 在第一次迭代或使用随机抽样时初始化`state` @@ -521,7 +521,7 @@ def train_epoch_ch8(net, train_iter, loss, updater, device, use_random_iter): def train_epoch_ch8(net, train_iter, loss, updater, params, use_random_iter): """训练模型一个迭代周期(定义见第8章)。""" state, timer = None, d2l.Timer() - metric = d2l.Accumulator(2) # 训练损失之和, 标记数量 + metric = d2l.Accumulator(2) # 训练损失之和, 词元数量 for X, Y in train_iter: if state is None or use_random_iter: # 在第一次迭代或使用随机抽样时初始化`state` @@ -567,7 +567,7 @@ def train_ch8(net, train_iter, vocab, lr, num_epochs, device, #@save net, train_iter, loss, updater, device, use_random_iter) if (epoch + 1) % 10 == 0: animator.add(epoch + 1, [ppl]) - print(f'困惑度 {ppl:.1f}, {speed:.1f} 标记/秒 {str(device)}') + print(f'困惑度 {ppl:.1f}, {speed:.1f} 词元/秒 {str(device)}') print(predict('time traveller')) print(predict('traveller')) ``` @@ -594,7 +594,7 @@ def train_ch8(net, train_iter, vocab, lr, num_epochs, device, if (epoch + 1) % 10 == 0: print(predict('time traveller')) animator.add(epoch + 1, [ppl]) - print(f'困惑度 {ppl:.1f}, {speed:.1f} 标记/秒 {str(device)}') + print(f'困惑度 {ppl:.1f}, {speed:.1f} 词元/秒 {str(device)}') print(predict('time traveller')) print(predict('traveller')) ``` @@ -620,12 +620,12 @@ def train_ch8(net, train_iter, vocab, num_hiddens, lr, num_epochs, strategy, print(predict('time traveller')) animator.add(epoch + 1, [ppl]) device = d2l.try_gpu()._device_name - print(f'困惑度 {ppl:.1f}, {speed:.1f} 标记/秒 {str(device)}') + print(f'困惑度 {ppl:.1f}, {speed:.1f} 词元/秒 {str(device)}') print(predict('time traveller')) print(predict('traveller')) ``` -[**现在,我们训练循环神经网络模型。**]因为我们在数据集中只使用10000个标记,所以模型需要更多的迭代周期来更好地收敛。 +[**现在,我们训练循环神经网络模型。**]因为我们在数据集中只使用10000个词元,所以模型需要更多的迭代周期来更好地收敛。 ```{.python .input} #@tab mxnet,pytorch diff --git a/chapter_recurrent-neural-networks/rnn.md b/chapter_recurrent-neural-networks/rnn.md index 04080bf67..50fbcd5f7 100644 --- a/chapter_recurrent-neural-networks/rnn.md +++ b/chapter_recurrent-neural-networks/rnn.md @@ -98,19 +98,19 @@ d2l.matmul(d2l.concat((X, H), 1), d2l.concat((W_xh, W_hh), 0)) ## 基于循环神经网络的字符级语言模型 -回想一下 :numref:`sec_language_model` 中的语言模型,我们的目标是根据过去的和当前的标记预测下一个标记,因此我们将原始序列移位一个标记作为标签。Bengio等人首先提出使用神经网络进行语言建模 :cite:`Bengio.Ducharme.Vincent.ea.2003`。接下来,我们将说明如何使用循环神经网络来构建语言模型。设小批量大小为1,批量中的那个文本序列为"machine"。为了简化后续部分的训练,我们考虑使用 *字符级语言模型*(character-level language model),将文本标记化为字符而不是单词。 :numref:`fig_rnn_train` 演示了如何通过基于字符级语言建模的循环神经网络使用当前的和先前的字符预测下一个字符。 +回想一下 :numref:`sec_language_model` 中的语言模型,我们的目标是根据过去的和当前的词元预测下一个词元,因此我们将原始序列移位一个词元作为标签。Bengio等人首先提出使用神经网络进行语言建模 :cite:`Bengio.Ducharme.Vincent.ea.2003`。接下来,我们将说明如何使用循环神经网络来构建语言模型。设小批量大小为1,批量中的那个文本序列为"machine"。为了简化后续部分的训练,我们考虑使用 *字符级语言模型*(character-level language model),将文本词元化为字符而不是单词。 :numref:`fig_rnn_train` 演示了如何通过基于字符级语言建模的循环神经网络使用当前的和先前的字符预测下一个字符。 ![基于循环神经网络的字符级语言模型。输入序列和标签序列分别为“machin”和“achine”。](../img/rnn-train.svg) :label:`fig_rnn_train` 在训练过程中,我们对每个时间步的输出层的输出进行softmax操作,然后利用交叉熵损失计算模型输出和标签之间的误差。由于隐藏层中隐藏状态的循环计算, :numref:`fig_rnn_train` 中的第$3$个时间步的输出$\mathbf{O}_3$由文本序列“m”、“a”和“c”确定。由于训练数据中这个文本序列的下一个字符是“h”,因此第$3$个时间步的损失将取决于下一个字符的概率分布,而下一个字符是基于特征序列“m”、“a”、“c”和这个时间步的标签“h”生成的。 -在实践中,我们使用的批量大小为$n>1$,每个标记都由一个$d$维向量表示。因此,在时间步$t$输入$\mathbf X_t$将是一个$n\times d$矩阵,这与我们在 :numref:`subsec_rnn_w_hidden_states` 中讨论的相同。 +在实践中,我们使用的批量大小为$n>1$,每个词元都由一个$d$维向量表示。因此,在时间步$t$输入$\mathbf X_t$将是一个$n\times d$矩阵,这与我们在 :numref:`subsec_rnn_w_hidden_states` 中讨论的相同。 ## 困惑度(Perplexity) :label:`subsec_perplexity` -最后,让我们讨论如何度量语言模型的质量,这将在后续部分中用于评估基于循环神经网络的模型。一种方法是检查文本有多么令人惊讶。一个好的语言模型能够用高度准确的标记来预测我们接下来会看到什么。考虑一下由不同的语言模型给出的对短语"It is raining"的续写: +最后,让我们讨论如何度量语言模型的质量,这将在后续部分中用于评估基于循环神经网络的模型。一种方法是检查文本有多么令人惊讶。一个好的语言模型能够用高度准确的词元来预测我们接下来会看到什么。考虑一下由不同的语言模型给出的对短语"It is raining"的续写: 1. "It is raining outside" 1. "It is raining banana tree" @@ -120,20 +120,20 @@ d2l.matmul(d2l.concat((X, H), 1), d2l.concat((W_xh, W_hh), 0)) 我们可以通过计算序列的似然概率来度量模型的质量。不幸的是,这是一个难以理解,也难以比较较的数字。毕竟,较短的序列比较长的序列更有可能出现,因此评估模型产生托尔斯泰的巨著《战争与和平》的可能性不可避免地会比产生圣埃克苏佩里的中篇小说《小王子》可能性要小得多。而缺少的可能性值相当于平均数。 -在这里,信息论可以派上用场了。我们在引入softmax回归( :numref:`subsec_info_theory_basics` )时定义了熵、惊奇和交叉熵,并在[信息论的在线附录](https://d2l.ai/chapter_appendix-mathematics-for-deep-learning/information-theory.html)中讨论了更多的信息论知识。如果想要压缩文本,我们可以询问根据当前标记集预测的下一个标记。一个更好的语言模型应该能让我们更准确地预测下一个标记。因此,它应该允许我们在压缩序列时花费更少的比特。所以我们可以通过一个序列中所有的$n$个标记的交叉熵损失的平均值来衡量: +在这里,信息论可以派上用场了。我们在引入softmax回归( :numref:`subsec_info_theory_basics` )时定义了熵、惊奇和交叉熵,并在[信息论的在线附录](https://d2l.ai/chapter_appendix-mathematics-for-deep-learning/information-theory.html)中讨论了更多的信息论知识。如果想要压缩文本,我们可以询问根据当前词元集预测的下一个词元。一个更好的语言模型应该能让我们更准确地预测下一个词元。因此,它应该允许我们在压缩序列时花费更少的比特。所以我们可以通过一个序列中所有的$n$个词元的交叉熵损失的平均值来衡量: $$\frac{1}{n} \sum_{t=1}^n -\log P(x_t \mid x_{t-1}, \ldots, x_1),$$ :eqlabel:`eq_avg_ce_for_lm` -其中$P$由语言模型给出,$x_t$是在时间步$t$从该序列中观察到的实际标记。这使得不同长度的文档的性能具有了可比性。由于历史原因,自然语言处理的科学家更喜欢使用一个叫做 *困惑度*(perplexity)的量。简而言之,它是 :eqref:`eq_avg_ce_for_lm` 的指数: +其中$P$由语言模型给出,$x_t$是在时间步$t$从该序列中观察到的实际词元。这使得不同长度的文档的性能具有了可比性。由于历史原因,自然语言处理的科学家更喜欢使用一个叫做 *困惑度*(perplexity)的量。简而言之,它是 :eqref:`eq_avg_ce_for_lm` 的指数: $$\exp\left(-\frac{1}{n} \sum_{t=1}^n \log P(x_t \mid x_{t-1}, \ldots, x_1)\right).$$ -当我们决定下一个标记是哪个时,困惑度的最好的理解可以是下一个标记的实际选择数的调和平均数。让我们看看一些案例: +当我们决定下一个词元是哪个时,困惑度的最好的理解可以是下一个词元的实际选择数的调和平均数。让我们看看一些案例: -* 在最好的情况下,模型总是完美地估计标签标记的概率为1。在这种情况下,模型的困惑度为1。 -* 在最坏的情况下,模型总是预测标签标记的概率为0。在这种情况下,困惑度是正无穷大。 -* 在基线上,该模型的预测是词汇表的所有可用标记上的均匀分布。在这种情况下,困惑度等于词汇表中唯一标记的数量。事实上,如果我们在没有任何压缩的情况下存储序列,这将是我们能做的最好的编码方式。因此,这种方式提供了一个重要的上限,而任何实际模型都必须超越这个上限。 +* 在最好的情况下,模型总是完美地估计标签词元的概率为1。在这种情况下,模型的困惑度为1。 +* 在最坏的情况下,模型总是预测标签词元的概率为0。在这种情况下,困惑度是正无穷大。 +* 在基线上,该模型的预测是词汇表的所有可用词元上的均匀分布。在这种情况下,困惑度等于词汇表中唯一词元的数量。事实上,如果我们在没有任何压缩的情况下存储序列,这将是我们能做的最好的编码方式。因此,这种方式提供了一个重要的上限,而任何实际模型都必须超越这个上限。 在接下来的章节中,我们将基于循环神经网络实现字符级语言模型,并使用困惑度来评估这样的模型。 @@ -148,7 +148,7 @@ $$\exp\left(-\frac{1}{n} \sum_{t=1}^n \log P(x_t \mid x_{t-1}, \ldots, x_1)\righ ## 练习 1. 如果我们使用循环神经网络来预测文本序列中的下一个字符,那么任意输出所需的维度是什么? -1. 为什么循环神经网络可以基于文本序列中所有先前的标记,在某个时间步表示当前标记的条件概率? +1. 为什么循环神经网络可以基于文本序列中所有先前的词元,在某个时间步表示当前词元的条件概率? 1. 如果你基于一个长序列进行反向传播,梯度会发生什么状况? 1. 与本节中描述的语言模型相关的问题有哪些? diff --git a/chapter_recurrent-neural-networks/text-preprocessing.md b/chapter_recurrent-neural-networks/text-preprocessing.md index 80af55785..2dde4324d 100644 --- a/chapter_recurrent-neural-networks/text-preprocessing.md +++ b/chapter_recurrent-neural-networks/text-preprocessing.md @@ -8,8 +8,8 @@ 这些步骤通常包括: 1. 将文本作为字符串加载到内存中。 -1. 将字符串拆分为标记(如单词和字符)。 -1. 建立一个词汇表,将拆分的标记映射到数字索引。 +1. 将字符串拆分为词元(如单词和字符)。 +1. 建立一个词汇表,将拆分的词元映射到数字索引。 1. 将文本转换为数字索引序列,方便模型操作。 ```{.python .input} @@ -60,13 +60,13 @@ print(lines[10]) ## 词元化 下面的 `tokenize` 函数将文本行列表作为输入,列表中的每个元素是一个文本序列(如一条文本行)。 -[**每个文本序列又被拆分成一个标记列表**],*标记*(token)是文本的基本单位。 -最后,返回一个由标记列表组成的列表,其中的每个标记都是一个字符串(string)。 +[**每个文本序列又被拆分成一个词元列表**],*词元*(token)是文本的基本单位。 +最后,返回一个由词元列表组成的列表,其中的每个词元都是一个字符串(string)。 ```{.python .input} #@tab all def tokenize(lines, token='word'): #@save - """将文本行拆分为单词或字符标记。""" + """将文本行拆分为单词或字符词元。""" if token == 'word': return [line.split() for line in lines] elif token == 'char': @@ -81,13 +81,13 @@ for i in range(11): ## 词汇表 -标记的类型是字符串,而模型需要的输入是数字,因此这种类型不方便模型使用。 -现在,让我们[**构建一个字典,通常也叫做*词汇表*(vocabulary),用来将字符串类型的标记映射到从$0$开始的数字索引中**]。 -我们先将训练集中的所有文档合并在一起,对它们的唯一标记进行统计,得到的统计结果称之为*语料*(corpus)。 -然后根据每个唯一标记的出现频率,为其分配一个数字索引。 -很少出现的标记通常被移除,这可以降低复杂性。 -另外,语料库中不存在或已删除的任何标记都将映射到一个特定的未知标记 “<unk>” 。 -我们可以选择增加一个列表,用于保存那些被保留的标记,例如:填充标记(“<pad>”);序列开始标记(“<bos>”);序列结束标记(“<eos>”)。 +词元的类型是字符串,而模型需要的输入是数字,因此这种类型不方便模型使用。 +现在,让我们[**构建一个字典,通常也叫做*词汇表*(vocabulary),用来将字符串类型的词元映射到从$0$开始的数字索引中**]。 +我们先将训练集中的所有文档合并在一起,对它们的唯一词元进行统计,得到的统计结果称之为*语料*(corpus)。 +然后根据每个唯一词元的出现频率,为其分配一个数字索引。 +很少出现的词元通常被移除,这可以降低复杂性。 +另外,语料库中不存在或已删除的任何词元都将映射到一个特定的未知词元 “<unk>” 。 +我们可以选择增加一个列表,用于保存那些被保留的词元,例如:填充词元(“<pad>”);序列开始词元(“<bos>”);序列结束词元(“<eos>”)。 ```{.python .input} #@tab all @@ -102,7 +102,7 @@ class Vocab: #@save counter = count_corpus(tokens) self.token_freqs = sorted(counter.items(), key=lambda x: x[1], reverse=True) - # 未知标记的索引为0 + # 未知词元的索引为0 self.unk, uniq_tokens = 0, [''] + reserved_tokens uniq_tokens += [token for token, freq in self.token_freqs if freq >= min_freq and token not in uniq_tokens] @@ -125,15 +125,15 @@ class Vocab: #@save return [self.idx_to_token[index] for index in indices] def count_corpus(tokens): #@save - """统计标记的频率。""" + """统计词元的频率。""" # 这里的 `tokens` 是 1D 列表或 2D 列表 if len(tokens) == 0 or isinstance(tokens[0], list): - # 将标记列表展平成使用标记填充的一个列表 + # 将词元列表展平成使用词元填充的一个列表 tokens = [token for line in tokens for token in line] return collections.Counter(tokens) ``` -我们首先使用时光机器数据集作为语料库来[**构建词汇表**],然后打印前几个高频标记及其索引。 +我们首先使用时光机器数据集作为语料库来[**构建词汇表**],然后打印前几个高频词元及其索引。 ```{.python .input} #@tab all @@ -152,15 +152,15 @@ for i in [0, 10]: ## 整合所有功能 -在使用上述函数时,我们[**将所有功能打包到`load_corpus_time_machine`函数中**],该函数返回 `corpus`(标记索引列表)和 `vocab`(时光机器语料库的词汇表)。 +在使用上述函数时,我们[**将所有功能打包到`load_corpus_time_machine`函数中**],该函数返回 `corpus`(词元索引列表)和 `vocab`(时光机器语料库的词汇表)。 我们在这里所做的改变是: -1. 为了简化后面章节中的训练,我们使用字符(而不是单词)实现文本标记化; -2. 时光机器数据集中的每个文本行不一定是一个句子或一个段落,还可能是一个单词,因此返回的 `corpus` 仅处理为单个列表,而不是使用多个标记列表构成的一个列表。 +1. 为了简化后面章节中的训练,我们使用字符(而不是单词)实现文本词元化; +2. 时光机器数据集中的每个文本行不一定是一个句子或一个段落,还可能是一个单词,因此返回的 `corpus` 仅处理为单个列表,而不是使用多个词元列表构成的一个列表。 ```{.python .input} #@tab all def load_corpus_time_machine(max_tokens=-1): #@save - """返回时光机器数据集的标记索引列表和词汇表。""" + """返回时光机器数据集的词元索引列表和词汇表。""" lines = read_time_machine() tokens = tokenize(lines, 'char') vocab = Vocab(tokens) @@ -178,12 +178,12 @@ len(corpus), len(vocab) ## 小结 * 文本是序列数据的一种重要形式。 -* 为了对文本进行预处理,我们通常将文本拆分为标记,构建词汇表将标记字符串映射为数字索引,并将文本数据转换为标记索引以供模型操作。 +* 为了对文本进行预处理,我们通常将文本拆分为词元,构建词汇表将词元字符串映射为数字索引,并将文本数据转换为词元索引以供模型操作。 ## 练习 -1. 标记化是一个关键的预处理步骤,它因语言而异,尝试找到另外三种常用的标记化文本的方法。 -1. 在本节的实验中,将文本标记为单词和更改 `Vocab` 实例的 `min_freq` 参数。这对词汇量有何影响? +1. 词元化是一个关键的预处理步骤,它因语言而异,尝试找到另外三种常用的词元化文本的方法。 +1. 在本节的实验中,将文本词元为单词和更改 `Vocab` 实例的 `min_freq` 参数。这对词汇量有何影响? :begin_tab:`mxnet` [Discussions](https://discuss.d2l.ai/t/2093)