循环神经网络 RNN
问题引入
在前篇中所提到的线性网络,大致上都是稠密网络(DNN)这种网络的输入大多是样本的多维度的特征,以此来得到一个想要的输出。
显然,面对序列问题,即处理视频流、预测天气、自然语言等等问题时,此时输入的x1…xn实际上是一组有序列性即存在前后关系的样本。每个xi为一个样本的所包含的特征元组。
针对这样的问题,我们也会想到利用线性的全连接网络来进行处理,但事实上,全连接网络所需要计算的权重太多,并不能够解决问题。
关于权重
对于一张128通道的图片,若想要利用5 * 5的卷积核将它转换成一张64通道的图片,则需要计算的权重有大约20W个
\[128 \times 5^2 \times 64 = 204800\]即卷积层的权重数目只与通道数以及卷积核大小有关,但全连接层的权重数与转成一维向量以后的整个数据量有关
若转为一维向量以后,输入为4096个元素,输出为1024个元素,则需要计算的权重大约有400W个
\[4096 \times 1024 = 4194304\]显然,全连接层所需要计算的权重远多于卷积层。
卷积神经网络采用权重共享机制,实际上是计划通过将输入图片的一个局部特征映射成输出图片的一个像素点而发挥作用的,也就是通过卷积核,使得输出层的每一个像素只与输入层一个局部的方块相连接,这就避免了在全连接情形下的,输出层的每个像素都在或多或少的被输入层的任意一个像素影响这一复杂的情况。
另外,图像信息的特征:图片底层特征与特征在图片中的位置无关,也是权重共享的一个依据。例如对于图像的边缘特征,无论边缘处在图像的什么位置,都是以同样的方法定义,作为同样的特征进行区别的。
因此,为了能够较好地解决序列问题,也要利用到权重共享的思路方法,来减少在计算过程中所需要的权重。因此产生了RNN。
RNN CELL
RNN原理
模块名称 | 作用 |
---|---|
xt | 表时刻t时的输入数据 |
RNN Cell | 本质上是一个线性层 |
ht | 表时刻t时得到的输出(隐含层) |
本质上,RNN Cell为一个线性层Linear,在t时刻下的N维向量,经过Cell后即可变为一个M维的向量ht,而与其他线性层不同,RNN Cell为一个共享的线性层。即重复利用,权重共享。将上图展开来看如下。
由于x1⋯xn,为一组序列信息,每一个xi都至少应包含xi−1的信息。也就是说,针对x2的操作所得到的h2中,应当包含x1的信息,因此在设计中,把x1处理后得到的h1一并向下传递。
上图中的h0是一种前置信息。例如若实现图像到文本的转换,可以利用CNN+FC对图像进行操作,再将输出的向量作为h0参与到RNN的运算中。
若没有可获知的前置信息,可将h0设置为与xi同维度的零向量。
图中的RNN Cell为同一个Linear,即让设计好的Linear反复参与运算,实现权重共享。
代码实现
代码实现有两种模式,一是实现自己的RNN Cell,再自己重写循环调用等逻辑。二是直接调用RNN的网络。
重点在于控制其输入输出的维度。
这里只写直接调用RNN,实际中使用LSTM/GRU
#说明input维度,hidden维度,以及RNN层数
#RNN计算耗时大,不建议层数过深
rnn = torch.nn.RNN(input_size = input_size, hidden_size = hidden_size, num_layers = num_layers)
#inputs指的是X1……Xn的整个输入序列
#hidden指的是前置条件H0
#out指的是每一次迭代的H1……Hn隐藏层序列
#hidden_out指的是最后一次迭代得到输出Hn
out, hidden_out = rnn(inputs, hidden)
#其他参数
#batch_first=True inputs维度从(SeqLen*Batch*input_size)变为(Batch*SeqLen*input_size)
维度说明
为说明输入输出维度大小,假定当前有如下配置的数据
参数 | 值 | 说明 |
---|---|---|
BatchSize | 1 | 批量大小 |
SeqLen | 5 | 样本数目x1…x5 |
InputSize | 4 | 输入维度 |
HiddenSize | 2 | 隐藏层(输出)维度 |
numLayers | 3 | RNN层数 |
对于RNN而言,在out, hidden_out = rnn(inputs, hidden)
参数 | 值 | 说明 |
---|---|---|
input | ([5, 1, 4]) | input.shape=(seqLen, batch_size, input_size) |
hidden | ([3, 1, 2]) | hidden.shape=(numLayers, batch_size, hidden_size) |
out | ([5, 1, 2]) | out.shape=(seqLen, batch_size, hidden_size) |
hidden_out | ([3, 1, 2]) | hidden_out.shape=(numLayers, batch_size, hidden_size) |
其中,左侧及下侧为输入部分,右侧及上侧为输出部分。
左侧是每一层RNN的前置条件,下侧为输入序列。右侧为每一层的隐藏层最终输出,上侧为隐藏层输出序列,此时hn=hn3。
对seq_len的理解
RNN的seq_len是个比较绕的地方,假如我们有id = 1,2,3,4,5,6,7,8,9,10一共10个样本。每个样本有feature_dims的特征。
假设我们设定seq_len是3。
那现在数据的形式应该为1-2-3,2-3-4,3-4-5,4-5-6,5-6-7,6-7-8,7-8-9,8-9-10,9-10-0,10-0-0(最后两个数据不完整,进行补零)的10个数据。这是我们真正有了seq_len这个参数,带有“循环”这个概念,要放进RNN等序列模型中进行处理的数据。
假设我们设定batch_size为2,batch_first=True
的情况下:
那我们取出第一个batch为1-2-3,2-3-4。这个batch的size就是(2,3,feature_dims)了。我们把这个玩意儿喂进模型。
接下来第二个batch为3-4-5,4-5-6。
第三个batch为5-6-7,6-7-8。
第四个batch为7-8-9,8-9-10。
第五个batch为9-10-0,10-0-0。
我们的数据一共生成了5个batch。
可以看到,num_batch = num_samples / batch_size(这里没有进行向上或向下取整是因为在某些地方可以设置是否需要那些不完整的被进行补零的batch),seq_len仍然不会影响最后生成的batch的数量,只有batch_size和num_samples会对batch的数量进行影响。
本文参考自B站《PyTorch深度学习实践》P12