机器翻译
指将一段文本从一种语言自动翻译到另一种语言
-
读取和预处理数据
# 将一个序列中所有的词记录在all_tokens中以便之后构造词典,然后在该序列后面添加PAD直到序列 # 长度变为max_seq_len,然后将序列保存在all_seqs中 def process_one_seq(seq_tokens, all_tokens, all_seqs, max_seq_len): all_tokens.extend(seq_tokens) seq_tokens += [EOS] + [PAD] * (max_seq_len - len(seq_tokens) - 1) all_seqs.append(seq_tokens) # 使用所有的词来构造词典。并将所有序列中的词变换为词索引后构造NDArray实例 def build_data(all_tokens, all_seqs): vocab = text.vocab.Vocabulary(collections.Counter(all_tokens), reserved_tokens=[PAD, BOS, EOS]) indices = [vocab.to_indices(seq) for seq in all_seqs] return vocab, nd.array(indices)
使用
def read_data(max_seq_len): # in和out分别是input和output的缩写 in_tokens, out_tokens, in_seqs, out_seqs = [], [], [], [] #读取法语-英语数据集 with io.open('../data/fr-en-small.txt') as f: # lines = f.readlines() for line in lines: #法语和英语之间用\t分割的 in_seq, out_seq = line.rstrip().split('\t') #把法语和英语用空格分隔开 in_seq_tokens, out_seq_tokens = in_seq.split(' '), out_seq.split(' ') if max(len(in_seq_tokens), len(out_seq_tokens)) > max_seq_len - 1: continue # 如果加上EOS后长于max_seq_len,则忽略掉此样本 #预处理序列 process_one_seq(in_seq_tokens, in_tokens, in_seqs, max_seq_len) process_one_seq(out_seq_tokens, out_tokens, out_seqs, max_seq_len) #构造词典 in_vocab, in_data = build_data(in_tokens, in_seqs) out_vocab, out_data = build_data(out_tokens, out_seqs) return in_vocab, out_vocab, gdata.ArrayDataset(in_data, out_data)
-
含注意力机制的编码器—解码器
编码器:
将输入语言的词索引通过词嵌入层得到词的表征,然后输入到一个多层门控循环单元中。class Encoder(nn.Block): def __init__(self, vocab_size, embed_size, num_hiddens, num_layers, drop_prob=0, **kwargs): super(Encoder, self).__init__(**kwargs) #嵌入层 self.embedding = nn.Embedding(vocab_size, embed_size) #循环神经网络 self.rnn = rnn.GRU(num_hiddens, num_layers, dropout=drop_prob) def forward(self, inputs, state): # 输入形状是(批量大小, 时间步数)。将输出互换样本维和时间步维 embedding = self.embedding(inputs).swapaxes(0, 1) return self.rnn(embedding, state) def begin_state(self, *args, **kwargs): return self.rnn.begin_state(*args, **kwargs)
注意力机制:
- Dense的flatten选项
当输入维度大于2时(即不止行列时),dense会默认把除第一维歪的维度均视作特征维,然后自动转换为行为样本、列为特征的二维矩阵。但若只希望将输入的最后一维作为特征,其他维度不变,那么就把Dense实例的flatten选项设为False。
- Dense的flatten选项
训练时的模型:
预测时的模型:
两个是不一样的!
注意力机制
由来 :翻译的过程像是解码器的每一时间步对输入序列中不同时间步的表征或编码信息分配不同的注意力一样。
以循环神经网络为例,注意力机制通过对编码器所有时间步的隐藏状态做加权平均来得到背景变量。解码器在每一时间步调整这些权重,即注意力权重,从而能够在不同时间步分别关注输入序列中的不同部分并编码进相应时间步的背景变量。(因人而异)
记 c t ′ \boldsymbol{c}_{t'} ct′是解码器在时间步 t ′ t' t′的背景变量,那么解码器在该时间步的隐藏状态可以改写为
s t ′ = g ( y t ′ − 1 , c t ′ , s t ′ − 1 ) . \boldsymbol{s}_{t'} = g(\boldsymbol{y}_{t'-1}, \boldsymbol{c}_{t'}, \boldsymbol{s}_{t'-1}). st′=g(yt′−1,ct′,st′−1).
难点在于:
- 计算背景变量
首先,函数 𝑎 根据解码器在时间步1的隐藏状态和编码器在各个时间步的隐藏状态计算softmax运算的输入。softmax运算输出概率分布并对编码器各个时间步的隐藏状态做加权平均,从而得到背景变量。
具体来说,令编码器在时间步 t t t的隐藏状态为 h t \boldsymbol{h}_t ht,且总时间步数为 T T T。那么解码器在时间步 t ′ t' t′的背景变量为所有编码器隐藏状态的加权平均:
c t ′ = ∑ t = 1 T α t ′ t h t , \boldsymbol{c}_{t'} = \sum_{t=1}^T \alpha_{t' t} \boldsymbol{h}_t, ct′=t=1∑Tαt′tht,
其中给定 t ′ t' t′时,权重 α t ′ t \alpha_{t' t} αt′t在 t = 1 , … , T t=1,\ldots,T t=1,…,T的值是一个概率分布。为了得到概率分布,我们可以使用softmax运算:
α t ′ t = exp ( e t ′ t ) ∑ k = 1 T exp ( e t ′ k ) , t = 1 , … , T . \alpha_{t' t} = \frac{\exp(e_{t' t})}{ \sum_{k=1}^T \exp(e_{t' k}) },\quad t=1,\ldots,T. αt′t=∑k=1Texp(et′k)exp(et′t),t=1,…,T.
现在,我们需要定义如何计算上式中softmax运算的输入 e t ′ t e_{t' t} et′t。由于 e t ′ t e_{t' t} et′t同时取决于解码器的时间步 t ′ t' t′和编码器的时间步 t t t,我们不妨以解码器在时间步 t ′ − 1 t'-1 t′−1的隐藏状态 s t ′ − 1 \boldsymbol{s}_{t' - 1} st′−1与编码器在时间步 t t t的隐藏状态 h t \boldsymbol{h}_t ht为输入,并通过函数 a a a计算 e t ′ t e_{t' t} et′t:
e t ′ t = a ( s t ′ − 1 , h t ) . e_{t' t} = a(\boldsymbol{s}_{t' - 1}, \boldsymbol{h}_t). et′t=a(st′−1,ht).
这里函数 a a a有多种选择,如果两个输入向量长度相同,一个简单的选择是计算它们的内积 a ( s , h ) = s ⊤ h a(\boldsymbol{s}, \boldsymbol{h})=\boldsymbol{s}^\top \boldsymbol{h} a(s,h)=s⊤h。而最早提出注意力机制的论文则将输入连结后通过含单隐藏层的多层感知机变换 [1]:
a ( s , h ) = v ⊤ tanh ( W s s + W h h ) , a(\boldsymbol{s}, \boldsymbol{h}) = \boldsymbol{v}^\top \tanh(\boldsymbol{W}_s \boldsymbol{s} + \boldsymbol{W}_h \boldsymbol{h}), a(s,h)=v⊤tanh(Wss+Whh),
其中 v \boldsymbol{v} v、 W s \boldsymbol{W}_s Ws、 W h \boldsymbol{W}_h Wh都是可以学习的模型参数。
-
矢量化计算
因为矢量化计算更高效。
考虑一个常见的简单情形,即编码器和解码器的隐藏单元个数均为 h h h,且函数 a ( s , h ) = s ⊤ h a(\boldsymbol{s}, \boldsymbol{h})=\boldsymbol{s}^\top \boldsymbol{h} a(s,h)=s⊤h。假设我们希望根据解码器单个隐藏状态 s t ′ − 1 ∈ R h \boldsymbol{s}_{t' - 1} \in \mathbb{R}^{h} st′−1∈Rh和编码器所有隐藏状态 h t ∈ R h , t = 1 , … , T \boldsymbol{h}_t \in \mathbb{R}^{h}, t = 1,\ldots,T ht∈Rh,t=1,…,T来计算背景向量 c t ′ ∈ R h \boldsymbol{c}_{t'}\in \mathbb{R}^{h} ct′∈Rh。
我们可以将查询项矩阵 Q ∈ R 1 × h \boldsymbol{Q} \in \mathbb{R}^{1 \times h} Q∈R1×h设为 s t ′ − 1 ⊤ \boldsymbol{s}_{t' - 1}^\top st′−1⊤,并令键项矩阵 K ∈ R T × h \boldsymbol{K} \in \mathbb{R}^{T \times h} K∈RT×h和值项矩阵 V ∈ R T × h \boldsymbol{V} \in \mathbb{R}^{T \times h} V∈RT×h相同且第 t t t行均为 h t ⊤ \boldsymbol{h}_t^\top ht⊤。此时,我们只需要通过矢量化计算
softmax ( Q K ⊤ ) V \text{softmax}(\boldsymbol{Q}\boldsymbol{K}^\top)\boldsymbol{V} softmax(QK⊤)V
即可算出转置后的背景向量 c t ′ ⊤ \boldsymbol{c}_{t'}^\top ct′⊤。当查询项矩阵 Q \boldsymbol{Q} Q的行数为 n n n时,上式将得到 n n n行的输出矩阵。输出矩阵与查询项矩阵在相同行上一一对应。 -
更新隐藏状态
以门控循环单元为例,在解码器中我们可以对门控循环单元的设计稍作修改,从而变换上一时间步 t ′ − 1 t'-1 t′−1的输出 y t ′ − 1 \boldsymbol{y}_{t'-1} yt′−1、隐藏状态 s t ′ − 1 \boldsymbol{s}_{t' - 1} st′−1和当前时间步 t ′ t' t′的含注意力机制的背景变量 c t ′ \boldsymbol{c}_{t'} ct′ [1]。解码器在时间步 t ′ t' t′的隐藏状态为
s t ′ = z t ′ ⊙ s t ′ − 1 + ( 1 − z t ′ ) ⊙ s ~ t ′ , \boldsymbol{s}_{t'} = \boldsymbol{z}_{t'} \odot \boldsymbol{s}_{t'-1} + (1 - \boldsymbol{z}_{t'}) \odot \tilde{\boldsymbol{s}}_{t'}, st′=zt′⊙st′−1+(1−zt′)⊙s~t′,
其中的重置门、更新门和候选隐藏状态分别为
r t ′ = σ ( W y r y t ′ − 1 + W s r s t ′ − 1 + W c r c t ′ + b r ) , z t ′ = σ ( W y z y t ′ − 1 + W s z s t ′ − 1 + W c z c t ′ + b z ) , s ~ t ′ = tanh ( W y s y t ′ − 1 + W s s ( s t ′ − 1 ⊙ r t ′ ) + W c s c t ′ + b s ) , \begin{aligned} \boldsymbol{r}_{t'} &= \sigma(\boldsymbol{W}_{yr} \boldsymbol{y}_{t'-1} + \boldsymbol{W}_{sr} \boldsymbol{s}_{t' - 1} + \boldsymbol{W}_{cr} \boldsymbol{c}_{t'} + \boldsymbol{b}_r),\\ \boldsymbol{z}_{t'} &= \sigma(\boldsymbol{W}_{yz} \boldsymbol{y}_{t'-1} + \boldsymbol{W}_{sz} \boldsymbol{s}_{t' - 1} + \boldsymbol{W}_{cz} \boldsymbol{c}_{t'} + \boldsymbol{b}_z),\\ \tilde{\boldsymbol{s}}_{t'} &= \text{tanh}(\boldsymbol{W}_{ys} \boldsymbol{y}_{t'-1} + \boldsymbol{W}_{ss} (\boldsymbol{s}_{t' - 1} \odot \boldsymbol{r}_{t'}) + \boldsymbol{W}_{cs} \boldsymbol{c}_{t'} + \boldsymbol{b}_s), \end{aligned} rt′zt′s~t′=σ(Wyryt′−1+Wsrst′−1+Wcrct′+br),=σ(Wyzyt′−1+Wszst′−1+Wczct′+bz),=tanh(Wysyt′−1+Wss(st′−1⊙rt′)+Wcsct′+bs),
其中含下标的 W \boldsymbol{W} W和 b \boldsymbol{b} b分别为门控循环单元的权重参数和偏差参数。
Transform
Transformer模型的架构,与seq2seq模型相似,Transformer同样基于编码器-解码器架构,其区别主要在于以下三点:
Transformer blocks:将seq2seq模型重的循环网络替换为了Transformer Blocks,该模块包含一个多头注意力层(Multi-head Attention Layers)以及两个position-wise feed-forward networks(FFN)。对于解码器来说,另一个多头注意力层被用于接受编码器的隐藏状态。
Add and norm:多头注意力层和前馈网络的输出被送到两个“add and norm”层进行处理,该层包含残差结构以及层归一化。
Position encoding:由于自注意力层并没有区分元素的顺序,所以一个位置编码层被用于向序列元素里添加位置信息。