简单循环网络(Simple Recurrent Network,SRN)只有一个隐藏层的神经网络.
1. 实现SRN
(1)使用Numpy
实验结果:
inputs is [[1. 1.]
[1. 1.]
[2. 2.]]
state_t is [0. 0.]
--------------------------------------
inputs is [1. 1.]
state_t is [0. 0.]
output_y is 4.0 4.0
---------------
inputs is [1. 1.]
state_t is (2.0, 2.0)
output_y is 12.0 12.0
---------------
inputs is [2. 2.]
state_t is (6.0, 6.0)
output_y is 32.0 32.0
---------------
(2)在1的基础上,增加激活函数tanh
inputs is [[1. 1.]
[1. 1.]
[2. 2.]]
state_t is [0. 0.]
--------------------------------------
inputs is [1. 1.]
state_t is [0. 0.]
output_y is 1.9280551601516338 1.9280551601516338
---------------
inputs is [1. 1.]
state_t is (0.9640275800758169, 0.9640275800758169)
output_y is 1.9984510891336251 1.9984510891336251
---------------
inputs is [2. 2.]
state_t is (0.9992255445668126, 0.9992255445668126)
output_y is 1.9999753470497836 1.9999753470497836
---------------
(3)使用nn.RNNCell实现
==================== 0 ====================
Input : tensor([[1., 1.]])
hidden : tensor([[0., 0.]])
output : tensor([[1.9281, 1.9281]], grad_fn=<AddmmBackward>)
==================== 1 ====================
Input : tensor([[1., 1.]])
hidden : tensor([[0.9640, 0.9640]], grad_fn=<TanhBackward>)
output : tensor([[1.9985, 1.9985]], grad_fn=<AddmmBackward>)
==================== 2 ====================
Input : tensor([[2., 2.]])
hidden : tensor([[0.9992, 0.9992]], grad_fn=<TanhBackward>)
output : tensor([[2.0000, 2.0000]], grad_fn=<AddmmBackward>)
(4)使用nn.RNN实现
Input : tensor([[1., 1.]])
hidden: 0 0
Output: tensor([[1.9281, 1.9281]], grad_fn=<AddmmBackward>)
--------------------------------------
Input : tensor([[1., 1.]])
hidden: tensor([[0.9640, 0.9640]], grad_fn=<SelectBackward>)
Output: tensor([[1.9985, 1.9985]], grad_fn=<AddmmBackward>)
--------------------------------------
Input : tensor([[2., 2.]])
hidden: tensor([[0.9992, 0.9992]], grad_fn=<SelectBackward>)
Output: tensor([[2.0000, 2.0000]], grad_fn=<AddmmBackward>)
这两个代码段的主要区别在于参数初始化的方式和对模型的定义细节处理。
代码段1中使用了torch.nn.init
模块手动对RNN模型的参数进行了初始化,分别将权重参数初始化为全1,偏置参数初始化为全0。而代码段2并没有手动进行参数初始化,而是依赖于PyTorch在定义RNN模型时自动进行的参数初始化过程。
另外,代码段1在定义线性层时,手动设置了权重和偏置的数值,而代码段2没有显示地设置权重和偏置的数值,而是依赖于PyTorch的默认初始化。
在计算过程中,两者都是先对输入序列进行RNN计算,然后通过线性层将隐藏状态映射到最终输出。这部分逻辑是相似的。
2. 实现“序列到序列”
Seq2Seq模型主要应用于序列到序列的任务,如机器翻译、对话生成等。它的核心思想是将一个输入序列映射到一个固定长度的向量表示,然后再将这个向量表示映射到一个输出序列。
在机器翻译中,输入序列通常是源语言的句子,输出序列是目标语言的句子。由于句子的长度是可变的,因此Seq2Seq模型会使用编码器-解码器结构来处理输入和输出序列的不同长度。编码器将输入序列逐步输入RNN,将最后一个时间步的隐藏状态作为向量表示。解码器则使用这个向量表示作为初始隐藏状态,并逐步生成输出序列。
Seq2Seq模型的关键之处在于,它能够处理输入序列和输出序列长度不一致的情况。编码器可以处理任意长度的输入序列,并将其编码为固定长度的向量表示。解码器则可以根据这个向量表示动态地生成输出序列,使得输出序列的长度可以根据需要进行调整。
3. “编码器-解码器”的简单实现
编码器-解码器是一种深度学习架构,通常用于处理序列到序列(Seq2Seq)的任务,如机器翻译、文本摘要、阅读理解、语音识别等。它由两部分组成:编码器和解码器。
编码器部分使用卷积神经网络(CNN)来将输入序列编码成一个固定长度的向量表示。这个向量包含了输入序列的重要特征信息。
解码器部分使用循环神经网络(RNN)来将编码器输出的向量解码成目标序列。解码器通过学习来生成与目标序列相匹配的输出
4.简单总结nn.RNNCell、nn.RNN
可以这么说:
nn.RNNCell就像是一个RNN单元的“工厂”,它可以根据你的需求生产出各种不同的RNN单元。每一个RNN单元只处理一次时间步长的输入,但是它可以被灵活地组合起来,构成各种复杂的RNN结构。
nn.RNN则像是一个RNN网络的“总指挥”,它可以接收整个序列的输入,并且在内部使用多个RNNCell来实现RNN的计算。相对于nn.RNNCell,nn.RNN提供了更加高级的接口,可以方便地构建出整个RNN网络,使得我们不需要手动连接RNN单元,简化了编程的难度。
5.谈一谈对“序列”、“序列到序列”的理解
一般情况,输入序列和输出序列长度不同(如机器翻译)且开始预测目标前需要整个输入序列。这要求更先进的设置,这就是人们通常所说的“序列到序列模型”。其原理如下:
- 一个RNN层(或多层)作为编码器(encoder):其处理输入序列并返回其独有的内部状态。注意我们丢弃了解码RNN的输出,只获取其状态。这个状态将在下一步的解码器中作为“上下文”或“条件”。
- 另外一个RNN层(或多层)作为解码器(decoder):它被训练成给定目标序列的前一个字符时预测下一个字符。特别的,它被训练成将目标序列转换为偏移了一个时间步的相同序列,这里的训练过程被称为“teacher forcing”。重要的是,编码器用于输出状态向量作为初始状态,解码器从中获得它要生成什么内容的信息。最终,解码器学到在给定输入序列的条件下,由给定的targets[…t]生成targets[t+1…]。
以推理的方式,比如,当我们想解码未知的输入序列,需要略微不同的处理:
- 1)将输入序列编码为状态向量。
- 2)以大小为1的目标序列开始(仅仅是序列起始字符)。
- 3)将状态向量和1字符目标序列输入到解码器来预测下一个字符。
- 4)使用上述预测抽取下一个字符(本例简单使用argmax最大评分)。
- 5)将得到的字符插入到目标序列。
- 6)重复上述步骤,直到生成序列终止字符或达到字符上限。
相同的处理也可以用于不使用“teacher forcing”的Seq2Seq网络,比如,将解码器预测注入解码器的方法。
6.总结本周理论课和作业,写心得体会
本次作业主要是阅读理解代码并复现,熟悉了nn.RNNCell()和nn.RNN()的内部实现,并对比其不同,第二部分完成不好因为最近一直发烧,等好了再完善作业。