南宁网站制作费用,邢台专业网站建设源码,江苏省建设网站首页,wordpress 目录关键词注#xff1a;书中对代码的讲解并不详细#xff0c;本文对很多细节做了详细注释。另外#xff0c;书上的源代码是在Jupyter Notebook上运行的#xff0c;较为分散#xff0c;本文将代码集中起来#xff0c;并加以完善#xff0c;全部用vscode在python 3.9.18下测试通过书中对代码的讲解并不详细本文对很多细节做了详细注释。另外书上的源代码是在Jupyter Notebook上运行的较为分散本文将代码集中起来并加以完善全部用vscode在python 3.9.18下测试通过同时对于书上部分章节也做了整合。
Chapter8 Recurrent Neural Networks
8.3 Language Models and the Dataset
假设长度为 T T T的文本序列中的词元依次为 x 1 , x 2 , … , x T x_1, x_2, \ldots, x_T x1,x2,…,xT。于是 x t x_t xt 1 ≤ t ≤ T 1 \leq t \leq T 1≤t≤T可以被认为是文本序列在时间步 t t t处的观测或标签。在给定这样的文本序列时语言模型language model的目标是估计序列的联合概率 P ( x 1 , x 2 , … , x T ) . P(x_1, x_2, \ldots, x_T). P(x1,x2,…,xT).例如只需要一次抽取一个词元 x t ∼ P ( x t ∣ x t − 1 , … , x 1 ) x_t \sim P(x_t \mid x_{t-1}, \ldots, x_1) xt∼P(xt∣xt−1,…,x1)一个理想的语言模型就能够基于模型本身生成自然文本。
8.3.1 Learning a Language Model
假设在单词级别对文本数据进行词元化包含了四个单词的一个文本序列的概率是 P ( deep , learning , is , fun ) P ( deep ) P ( learning ∣ deep ) P ( is ∣ deep , learning ) P ( fun ∣ deep , learning , is ) . P(\text{deep}, \text{learning}, \text{is}, \text{fun}) P(\text{deep}) P(\text{learning} \mid \text{deep}) P(\text{is} \mid \text{deep}, \text{learning}) P(\text{fun} \mid \text{deep}, \text{learning}, \text{is}). P(deep,learning,is,fun)P(deep)P(learning∣deep)P(is∣deep,learning)P(fun∣deep,learning,is).
为了训练语言模型我们需要计算单词的概率以及给定前面几个单词后出现某个单词的条件概率这些概率本质上就是语言模型的参数。假设训练集是一个大型的文本语料库训练集中词的概率可以根据给定词的相对词频来计算比如可以将估计值 P ^ ( deep ) \hat{P}(\text{deep}) P^(deep)计算为任何以单词“deep”开头的句子的概率。一种稍稍不太精确的方法是统计单词“deep”在数据集中的出现次数然后将其除以整个语料库中的单词总数不太精确指的是单个词的出现概率可能会受到其周围上下文的影响“稀有词”可能会导致参数估计不准确因为在训练集中可能无法捕获到这些词在其他文本中的真实分布情况某个词在语料库中出现的次数可能会受到文本主题、文体等因素的影响。接下来我们可以尝试估计 P ^ ( learning ∣ deep ) n ( deep, learning ) n ( deep ) , \hat{P}(\text{learning} \mid \text{deep}) \frac{n(\text{deep, learning})}{n(\text{deep})}, P^(learning∣deep)n(deep)n(deep, learning),
其中 n ( x ) n(x) n(x)和 n ( x , x ′ ) n(x, x) n(x,x′)分别是单个单词和连续单词对的出现次数。不幸的是由于连续单词对“deep learning”的出现频率要低得多所以估计这类单词正确的概率要困难得多。除非我们提供某种解决方案来将这些单词组合指定为非零计数否则将无法在语言模型中使用它们。一种常见的策略是执行某种形式的拉普拉斯平滑Laplace smoothing具体方法是在所有计数中添加一个小常量。用 n n n表示训练集中的单词总数用 m m m表示唯一单词的数量如下式所示 P ^ ( x ) n ( x ) ϵ 1 / m n ϵ 1 , P ^ ( x ′ ∣ x ) n ( x , x ′ ) ϵ 2 P ^ ( x ′ ) n ( x ) ϵ 2 , P ^ ( x ′ ′ ∣ x , x ′ ) n ( x , x ′ , x ′ ′ ) ϵ 3 P ^ ( x ′ ′ ) n ( x , x ′ ) ϵ 3 . \begin{aligned} \hat{P}(x) \frac{n(x) \epsilon_1/m}{n \epsilon_1}, \\ \hat{P}(x \mid x) \frac{n(x, x) \epsilon_2 \hat{P}(x)}{n(x) \epsilon_2}, \\ \hat{P}(x \mid x,x) \frac{n(x, x,x) \epsilon_3 \hat{P}(x)}{n(x, x) \epsilon_3}. \end{aligned} P^(x)P^(x′∣x)P^(x′′∣x,x′)nϵ1n(x)ϵ1/m,n(x)ϵ2n(x,x′)ϵ2P^(x′),n(x,x′)ϵ3n(x,x′,x′′)ϵ3P^(x′′).
其中 ϵ 1 、 e p s i l o n 2 \epsilon_1、epsilon_2 ϵ1、epsilon2和 ϵ 3 \epsilon_3 ϵ3是超参数。以 ϵ 1 \epsilon_1 ϵ1为例当 ϵ 1 0 \epsilon_1 0 ϵ10时不应用平滑当 ϵ 1 \epsilon_1 ϵ1接近正无穷大时 P ^ ( x ) \hat{P}(x) P^(x)接近均匀概率分布 1 / m 1/m 1/m。 然而这样的模型很容易变得无效原因如下首先我们需要存储所有的计数其次模型完全忽略了单词的意思最后长单词序列大部分是没出现过的因此一个模型如果只是简单地统计先前“看到”的单词序列频率面对这种问题时肯定表现不佳。
如果 P ( x t 1 ∣ x t , … , x 1 ) P ( x t 1 ∣ x t ) P(x_{t1} \mid x_t, \ldots, x_1) P(x_{t1} \mid x_t) P(xt1∣xt,…,x1)P(xt1∣xt)则序列上的分布满足一阶马尔可夫性质。阶数越高对应的依赖关系就越长。这种性质推导出了许多可以应用于序列建模的近似公式 P ( x 1 , x 2 , x 3 , x 4 ) P ( x 1 ) P ( x 2 ) P ( x 3 ) P ( x 4 ) P ( x 1 , x 2 , x 3 , x 4 ) P ( x 1 ) P ( x 2 ∣ x 1 ) P ( x 3 ∣ x 2 ) P ( x 4 ∣ x 3 ) P ( x 1 , x 2 , x 3 , x 4 ) P ( x 1 ) P ( x 2 ∣ x 1 ) P ( x 3 ∣ x 1 , x 2 ) P ( x 4 ∣ x 2 , x 3 ) \begin{aligned} P(x_1, x_2, x_3, x_4) P(x_1) P(x_2) P(x_3) P(x_4)\\ P(x_1, x_2, x_3, x_4) P(x_1) P(x_2 \mid x_1) P(x_3 \mid x_2) P(x_4 \mid x_3) \\ P(x_1, x_2, x_3, x_4) P(x_1) P(x_2 \mid x_1) P(x_3 \mid x_1, x_2) P(x_4 \mid x_2, x_3) \end{aligned} P(x1,x2,x3,x4)P(x1,x2,x3,x4)P(x1,x2,x3,x4)P(x1)P(x2)P(x3)P(x4)P(x1)P(x2∣x1)P(x3∣x2)P(x4∣x3)P(x1)P(x2∣x1)P(x3∣x1,x2)P(x4∣x2,x3) 通常涉及一个、两个和三个变量的概率公式分别被称为一元语法unigram、二元语法bigram和三元语法trigram模型。也就是说一元语法假设文本中的每个词都是相互独立的即某个词的出现概率只依赖不依赖于其他词一元语法模型将整个文本的概率表示为每个单词出现的概率的乘积。二元语法考虑了相邻两个词之间的关系假设某个词的出现概率仅依赖于它前面一个词三元语法同理。
8.3.2 Natural Language SStatistics
import random
import torch
from d2l import torch as d2l
import matplotlib.pyplot as plttokens d2l.tokenize(d2l.read_time_machine())
# 因为每个文本行不一定是一个句子或一个段落因此我们把所有文本行拼接到一起
corpus [token for line in tokens for token in line]
vocab d2l.Vocab(corpus)
print(vocab.token_freqs[:10])freqs [freq for token, freq in vocab.token_freqs]
d2l.plot(freqs, xlabeltoken: x, ylabelfrequency: n(x),xscalelog, yscalelog)
plt.show()词频图
最流行的词看起来很无聊被称为停用词stop words因此可以被过滤掉但它们本身仍然是有意义的。此外还有个明显的现象是词频衰减的速度相当快。通过此图我们可以发现词频以一种明确的方式迅速衰减。将前几个单词作为例外消除后剩余的所有单词大致遵循双对数坐标图(xscale‘log’, yscale‘log’)上的一条直线这意味着单词的频率满足齐普夫定律Zipf’s law即第 i i i个最常用单词的频率 n i n_i ni满足 log n i − α log i c \log n_i -\alpha \log i c logni−αlogic
其中 α \alpha α是刻画分布的指数 c c c是常数。上式等价于 n i ∝ 1 i α n_i \propto \frac{1}{i^\alpha} ni∝iα1 这告诉我们想要通过计数统计和平滑来建模单词是不可行的因为这样建模的结果会大大高估尾部单词的频率也就是所谓的不常用单词。换句话说齐普夫定律告诉我们自然语言中的单词分布呈现出一种“长尾”现象即少数单词的出现频率非常高而大多数单词的出现频率则相对较低呈现出尾部单词的大量分布。
#bigram
bigram_tokens [pair for pair in zip(corpus[:-1], corpus[1:])]
bigram_vocab d2l.Vocab(bigram_tokens)
print(bigram_vocab.token_freqs[:10])#trigram
trigram_tokens [triple for triple in zip(corpus[:-2], corpus[1:-1], corpus[2:])]
trigram_vocab d2l.Vocab(trigram_tokens)
print(trigram_vocab.token_freqs[:10])bigram_freqs [freq for token, freq in bigram_vocab.token_freqs]
trigram_freqs [freq for token, freq in trigram_vocab.token_freqs]
d2l.plot([freqs, bigram_freqs, trigram_freqs], xlabeltoken: x,ylabelfrequency: n(x), xscalelog, yscalelog,legend[unigram, bigram, trigram])
plt.show()一元、二元和三元词频图
从上图可看出
除了一元语法词单词序列也遵循齐普夫定律尽管公式指数 α \alpha α更小词表中 n n n元组的数量并没有那么大这说明语言中存在相当多的结构即词元序列组合很丰富很多 n n n元组很少出现这使得拉普拉斯平滑非常不适合语言建模因此我们将使用基于深度学习的模型。
8.3.3 Reading Long Sequence Data
当序列变得太长而不能被模型一次性全部处理时我们可能希望拆分这样的序列方便模型读取。假设我们将使用神经网络来训练语言模型模型中的网络一次处理具有预定义长度例如 n n n个时间步的一个小批量序列。首先由于文本序列可以是任意长的于是任意长的序列可以被我们划分为具有相同时间步数的子序列。当训练我们的神经网络时这样的小批量子序列将被输入到模型中。假设网络一次只处理具有 n n n个时间步的子序列。下图画出了从原始文本序列获得子序列的所有不同的方式其中 n 5 n5 n5并且每个时间步的词元对应于一个字符。 事实上上图中不同的取法都一样好然而如果只选择一个偏移量那么用于训练网络的、所有可能的子序列的覆盖范围将是有限的。因此我们可以从随机偏移量开始划分序列以同时获得覆盖性coverage和随机性randomness。
def seq_data_iter_random(corpus, batch_size, num_steps): #save使用随机抽样生成一个小批量子序列#从随机偏移量开始对序列进行分区随机范围为[0,num_steps-1]corpus corpus[random.randint(0, num_steps - 1):]num_subseqs (len(corpus) - 1) // num_steps #将输入序列中的每个词作为训练数据的特征而将对应的下一个词作为标签减去1是为了确保每个子序列都有对应的标签#initial_indices为长度为num_steps的子序列的起始索引initial_indices list(range(0, num_subseqs * num_steps, num_steps))#打乱处理后在随机抽样的迭代过程中来自两个相邻的、随机的、小批量中的子序列不一定在原始序列上相邻random.shuffle(initial_indices)def data(pos):#返回从pos位置开始的长度为num_steps的序列return corpus[pos: pos num_steps]num_batches num_subseqs // batch_size#batch_size指定每个小批量中子序列样本的数目for i in range(0, batch_size * num_batches, batch_size):initial_indices_per_batch initial_indices[i: i batch_size]#initial_indices包含子序列的随机起始索引#X是模型的输入序列Y是对应于X中每个样本的下一个词的目标序列(标签)X [data(j) for j in initial_indices_per_batch]Y [data(j 1) for j in initial_indices_per_batch]yield torch.tensor(X), torch.tensor(Y)my_seq list(range(35))
for X, Y in seq_data_iter_random(my_seq, batch_size2, num_steps5):print(X: , X, \nY:, Y)def seq_data_iter_sequential(corpus, batch_size, num_steps):#save使用顺序分区生成一个小批量子序列# 从随机偏移量开始划分序列offset random.randint(0, num_steps)num_tokens ((len(corpus) - offset - 1) // batch_size) * batch_sizeXs torch.tensor(corpus[offset: offset num_tokens])Ys torch.tensor(corpus[offset 1: offset 1 num_tokens])print(Xs,Ys)Xs, Ys Xs.reshape(batch_size, -1), Ys.reshape(batch_size, -1)print(Xs,Ys)num_batches Xs.shape[1] // num_stepsfor i in range(0, num_steps * num_batches, num_steps):X Xs[:, i: i num_steps]Y Ys[:, i: i num_steps]yield X, Yfor X, Y in seq_data_iter_sequential(my_seq, batch_size2, num_steps5):print(X: , X, \nY:, Y)class SeqDataLoader: #save加载序列数据的迭代器def __init__(self, batch_size, num_steps, use_random_iter, max_tokens):if use_random_iter:self.data_iter_fn d2l.seq_data_iter_randomelse:self.data_iter_fn d2l.seq_data_iter_sequentialself.corpus, self.vocab d2l.load_corpus_time_machine(max_tokens)self.batch_size, self.num_steps batch_size, num_stepsdef __iter__(self):return self.data_iter_fn(self.corpus, self.batch_size, self.num_steps)def load_data_time_machine(batch_size, num_steps, #saveuse_random_iterFalse, max_tokens10000):返回时光机器数据集的迭代器和词表data_iter SeqDataLoader(batch_size, num_steps, use_random_iter, max_tokens)return data_iter, data_iter.vocab