编码器-解码器架构(序列到序列问题的基础框架)
机器翻译是序列转换模型的一个核心问题, 其输入和输出都是长度可变的序列
编码器(encoder): 它接受一个长度可变的序列作为输入, 并将其转换为具有固定形状的编码状态。
解码器(decoder): 它将固定形状的编码状态映射到长度可变的序列。
架构代码体现:
Encoder接收输入序列 X,返回编码输出
Decoder先用 init_state 接收编码器的状态,再用 forward 逐步生成输出
把前两者串联起来,定义完整的数据流
Seq2seq(序列到序列学习)
核心思想:是用两个循环神经网络(RNN)分别充当编码器和解码器
以翻译模型为例:
特定的“<eos>”表示序列结束词元,一旦输出序列生成此词元,模型就会停止预测
特定的“<bos>”表示序列开始词元,它是解码器的输入序列的第一个词元。 其次,使用循环神经网络编码器最终的隐状态来初始化解码器的隐状态。
*训练翻译模型时因为知道具体的输入对应的输出,可以在解码器部分连续的更新
但是如果是句子推理模型就有点区别了
衡量生成序列的好坏:BLUE
BLEU 通过比较预测序列与标签(参考)序列之间的n-gram 重叠程度来衡量生成质量。
如何计算n-gram:
预测中某个 n-gram 的匹配次数,不能超过它在标签中出现的次数
【例】标签:A,B,C,D,E,F 序列:A,B,B,C,D
计算n= 1,p1
(第二个B的匹配次数大于标签出现次数不能匹配)
当n=2时,p2
如何计算BLUE?
左侧:长度惩罚,预测长度短于标签长度,min会取负数,exp一个负数就会降低分数
其余min取0,不会降低分数
右侧:加权几何平均
具体的实现:
编码器RNN的实现
嵌入层:vocab_size 个词,每个变成 embed_size 维向量,把词元索引变成稠密向量
*用低维稠密向量反应词义的远近(随机的索引反应不出)
GRU 循环网络:逐个读取词元,更新隐状态,最终把整句话"压缩"成一个状态向量
X为输入一批句子,每个句子有 num_steps 个词元索引
embedding 嵌入后形状: (batch_size, num_steps, embed_size)
output:所有时间步的输出(这里用不到)
state:最后一个时间步的隐藏状态(包含了对这个句子的最终理解)
解码器RNN的实现:
每个时间步t,解码器输出yt的概率取决于:
1.已经生成的词:y1,y2,...,yt′−1
2.上下文变量:c (编码器给的整句语义)
因为要得到序列的输出,所以相比于incoder多了一个dense层
GRU 输入维度 =embed_size + num_hiddens
初始的隐藏状态为incoder最后的输出state
context = state[-1].repeat(X.shape[0], 1, 1)
# state[-1] 形状: (batch_size, num_hiddens) 最后一层的最终隐状态
# repeat 后形状: (num_steps, batch_size, num_hiddens)
为什么要repeat?
编码器的上下文c只有一个(对应最后一个时间步),但解码器要生成 num_steps 个词。每个时间步生成都需要看到源句子的语义,所以把 c 复制 num_steps 次,让每个时间步都能"读到"它。
之前数据处理对于长度不足的数据进行了padding,我们应该将填充词元的预测排除在损失函数的计算之外
损失函数:
1.创建全1的权重矩阵,和 label 形状相同
2.用 sequence_mask 把填充位置置0,有效位置=1,填充位置=0
3设置 reduction='none',让父类返回每个位置的损失
4.计算交叉熵
训练:
训练时,解码器需要逐个词生成翻译。如果让模型自己生成:
第1步猜错了 -> 第2步基于错误输入继续错 -> 错误像滚雪球一样累积
Teacher Forcing 的解决方案:训练时,解码器的输入不用模型自己生成的,而是用真实标签(ground truth)。
初始化参数
优化器和损失函数
按epoch训练
构造一个<bos>列向量作为输入
每个时间步,解码器看到"正确答案的上一词",预测"当前词"。
反向传播,梯度裁剪,参数更新
预测:
和训练时最大的区别是:没有 Teacher Forcing 了,模型必须自己生成一个词,再用这个词作为下一步的输入。
核心部分:
BLUE的实现: