[动手学深度学习DAY2]:文本预处理、语言模型及RNN基础

Part1.文本预处理

文本预处理主要分为:

  1. 读入文本
  2. 分词
  3. 建立字典
  4. 将文本的词序列转为索引序列
读入文本

分词

我们对每个句子进行分词,也就是将一个句子划分成若干个词(token),转换为一个词的序列。

我们也可以通过现有的分词工具进行分词,例如spaCyNLTK

Part2.语言模型

本部分主要包括

  1. 语言模型
  2. n元语法
  3. 时序数据采样
    • 随机采样
    • 相邻采样
语言模型

给定一个长度为T的词的序列\(w_1, w_2, \ldots, w_T\),语言模型的目标就是评估该序列是否合理,即计算该序列的概率:$$P(w_1, w_2, \ldots, w_T).$$假设序列\(w_1, w_2, \ldots, w_T\)中的每个词是依次生成的,那么通过条件概率我们可以把概率公式改写为:$$P(w_1, w_2, \ldots, w_T)
= \prod_{t=1}^T P(w_t \mid w_1, \ldots, w_{t-1})\\
= P(w_1)P(w_2 \mid w_1) \cdots P(w_T \mid w_1w_2\cdots w_{T-1})
$$其中单个词的概率可以通过语料库的词频进行计算,联合概率可以利用条件概率进行计算。

n元语法

n元语法通过马尔可夫假设简化模型,马尔科夫假设是指一个词的出现只与前面n个词相关,即n阶马尔可夫链。基于n-1阶马尔可夫链,我们可以将语言模型改写为:$$P(w_1, w_2, \ldots, w_T) = \prod_{t=1}^T P(w_t \mid w_{t-(n-1)}, \ldots, w_{t-1}) .$$以上也叫n元语法(n-grams),它是基于n-1阶马尔可夫链的概率语言模型。

时序数据采样

时序数据的一个样本通常包含连续的字符。假设时间步数为5,样本序列为5个字符,即“想”“要”“有”“直”“升”。该样本的标签序列为这些字符分别在训练集中的下一个字符,即“要”“有”“直”“升”“机”,即X=“想要有直升”,Y=“要有直升机”。

现在我们考虑序列“想要有直升机,想要和你飞到宇宙去”,如果时间步数为5,有以下可能的样本和标签:

X:“想要有直升”,Y:“要有直升机”
X:“要有直升机”,Y:“有直升机,”
X:“有直升机,”,Y:“直升机,想”
...
X:“要和你飞到”,Y:“和你飞到宇”
X:“和你飞到宇”,Y:“你飞到宇宙”
X:“你飞到宇宙”,Y:“飞到宇宙去”
可以看到,如果序列的长度为T,时间步数为n,那么一共有T-n个合法的样本,但是这些样本有大量的重合,我们通常采用更加高效的采样方式。我们有两种方式对时序数据进行采样,分别是随机采样和相邻采样。

随机采样

在随机采样中,每个样本是原始序列上任意截取的一段序列,相邻的两个随机小批量在原始序列上的位置不一定相毗邻。

相邻采样

在相邻采样中,相邻的两个随机小批量在原始序列上的位置相毗邻。

Part3.RNN基础

该部分主要包含

  1. RNN简介
  2. 手动实现RNN
  3. 基于pytorch实现RNN
RNN(循环神经网络)

循环神经网络引入一个隐藏变量H,用\(H_t\)表示在时间步t的值。\(H_t\)的计算基于\(X_t\)和\(H_{t-1}\),可以认为\(H_t\)记录了到当前字符为止的序列信息,利用\(H_t\)对序列的下一个字符进行预测。

具体地,假设\(\boldsymbol{X}_t \in \mathbb{R}^{n \times d}\)是时间步t的输入,\(\boldsymbol{H}_t \in \mathbb{R}^{n \times t}\)是时间步t的隐藏变量,那么:$$\boldsymbol{H}_t = \phi(\boldsymbol{X}_t \boldsymbol{W}_{xh} + \boldsymbol{H}_{t-1} \boldsymbol{W}_{hh} + \boldsymbol{b}_h).$$在时间步t的输出为:$$\boldsymbol{O}_t = \boldsymbol{H}_t \boldsymbol{W}_{hq} + \boldsymbol{b}_q.$$

手动实现RNN

将以构件一个RNN语言模型为例子展示

准备数据

one-hot向量

我们需要将字符表示成向量,这里采用one-hot向量。假设词典大小是N,每次字符对应一个从0到N-1的唯一的索引,则该字符的向量是一个长度为N的向量,若字符的索引是i,则该向量的第i个位置为1,其他位置为0。

初始化模型参数

定义模型

裁剪梯度

循环神经网络中较容易出现梯度衰减或梯度爆炸,这会导致网络几乎无法训练。裁剪梯度(clip gradient)是一种应对梯度爆炸的方法。假设我们把所有模型参数的梯度拼接成一个向量\(\boldsymbol{g}\) ,并设裁剪的阈值是\(\theta\)。裁剪后的梯度:$$\min\left(\frac{\theta}{|\boldsymbol{g}|}, 1\right)\boldsymbol{g}$

预测函数

训练函数

基于pytorch的实现
定义模型

我们使用Pytorch中的nn.RNN来构造循环神经网络。在本节中,我们主要关注nn.RNN的以下几个构造函数参数:

  • input_size - The number of expected features in the input x
  • hidden_size – The number of features in the hidden state h
  • nonlinearity – The non-linearity to use. Can be either 'tanh' or 'relu'. Default: 'tanh'
  • batch_first – If True, then the input and output tensors are provided as (batch_size, num_steps, input_size). Default: False
    这里的batch_first决定了输入的形状,我们使用默认的参数False,对应的输入形状是 (num_steps, batch_size, input_size)。

forward函数的参数为:

  • input of shape (num_steps, batch_size, input_size): tensor containing the features of the input sequence.
  • h_0 of shape (num_layers * num_directions, batch_size, hidden_size): tensor containing the initial hidden state for each element in the batch. Defaults to zero if not provided. If the RNN is bidirectional, num_directions should be 2, else it should be 1.
    forward函数的返回值是:
  • output of shape (num_steps, batch_size, num_directions * hidden_size): tensor containing the output features (h_t) from the last layer of the RNN, for each t.
  • h_n of shape (num_layers * num_directions, batch_size, hidden_size): tensor containing the hidden state for t = num_steps.
    现在我们构造一个nn.RNN实例。

def predict_rnn_pytorch(prefix, num_chars, model, vocab_size, device, idx_to_char,
char_to_idx):
state = None
output = [char_to_idx[prefix[0]]] # output记录prefix加上预测的num_chars个字符
for t in range(num_chars + len(prefix) - 1):
X = torch.tensor([output[-1]], device=device).view(1, 1)
(Y, state) = model(X, state) # 前向计算不需要传入模型参数
if t < len(prefix) - 1:
output.append(char_to_idx[prefix[t + 1]])
else:
output.append(Y.argmax(dim=1).item())
return ''.join([idx_to_char[i] for i in output])

训练函数

You May Also Like

About the Author: zhuyeye

发表回复

您的电子邮箱地址不会被公开。