现代循环神经网络实战:机器翻译( 六 )


这段代码定义了一个序列到序列学习的循环神经网络解码器 ,其作用是将输入序列转换为目标序列 。
解码器的构建与编码器相似,只是将输入序列的嵌入向量和编码器的输出向量连接起来,一起作为解码器的输入 。
具体解释如下:
init 函数:初始化解码器 。与编码器相似,它包含了一个嵌入层、一个循环神经网络层和一个全连接层 。其中嵌入层和循环神经网络层的输入维度为+ ,即当前时间步的输入和前一时间步的输出向量在特征维上的拼接 。全连接层的输出维度为目标词汇表大小。
函数:初始化解码器的初始状态 。由于解码器的初始状态需要从编码器的输出中获取,因此这里实现了一个函数,它将编码器的最后一层隐状态作为解码器的初始状态 。
函数:解码器的前向计算过程 。首先将输入序列 X 通过嵌入层进行词嵌入,然后在特征维上进行维度置换,使得时间步维度为第一维度 。然后通过广播,将编码器的最后一层隐状态重复 X 的时间步维度次,并与 X 在特征维上进行拼接,得到。将输入到循环神经网络层中,得到解码器的输出向量和最终隐状态 state 。最后,通过全连接层将转换为目标词汇表大小的输出向量,并在维度上进行置换,使得时间步维度恢复为第二维度,返回解码器的输出向量和最终隐状态 state 。
上述模型结构如图:
损失函数
在每个时间步,解码器预测了输出词元的概率分布 。类似于语言模型,可以使用来获得分布,并通过计算交叉熵损失函数来进行优化 。回想一下上节中,特定的填充词元被添加到序列的末尾,因此不同长度的序列可以以相同形状的小批量加载 。但是,我们应该将填充词元的预测排除在损失函数的计算之外 。
为此,我们可以使用下面的函数通过零值化屏蔽不相关的项,以便后面任何不相关预测的计算都是与零的乘积,结果都等于零 。例如,如果两个序列的有效长度(不包括填充词元)分别为1和2,则第一个序列的第一项和第二个序列的前两项之后的剩余项将被清除为零 。
#@savedef sequence_mask(X, valid_len, value=http://www.kingceram.com/post/0):"""在序列中屏蔽不相关的项"""maxlen = X.size(1)mask = torch.arange((maxlen), dtype=torch.float32,device=X.device)[None, :] < valid_len[:, None]X[~mask] = valuereturn XX = torch.tensor([[1, 2, 3], [4, 5, 6]])sequence_mask(X, torch.tensor([1, 2]))
这是一个用于屏蔽序列中不相关项的函数,主要用于在计算序列损失函数时排除填充项 。它的输入包括三个参数:
X:需要屏蔽的序列,形状为(, , );
:每个序列的有效长度,形状为(,);
value:用于替换不相关项的值,默认为0 。
函数中,首先通过X.size(1)获取序列的最大长度,然后使用torch.()创建一个形状为(1, )的张量,表示序列中每个位置的下标,该张量的dtype和与X相同 。接下来,使用机制将转换为形状为(, )的张量,其中每行的前[i]个元素为True,其余为False,即表示序列中第i个位置之前的元素是有效的 。最后,通过将X[~mask]设置为value,将不相关的项替换为指定的值,得到屏蔽后的序列 。最终,该函数返回被屏蔽后的序列 。
广播机制
广播()是中一个重要的机制,它允许在两个张量之间进行一些形状不同的操作,如张量加减、乘除、逐元素操作等 。当两个张量的形状不同时,如果它们满足一定的规则,就可以通过广播使它们的形状相同,从而进行相应的操作 。
广播规则如下:
如果两个张量的维数不同,则在较小的张量的形状前面添加1,直到维数相同 。如果两个张量在某个维度的大小不同,且其中一个大小为1,则可以沿着该维度扩展该张量,使其大小与另一个张量相同 。如果两个张量在某个维度的大小不同,且两个大小都不为1,则无法进行广播,操作将失败并报错 。