前两天学的卷积神经网络,今天学循环神经网络 RNN。
之前在用卷积神经网络的是,一张图片上像素做卷积,所用到的卷积核明确且一整张都用这个卷积核(权重共享)。所以用到的权重少,而全连接层,每一个点对应下一个节点都有一个权重,所以整个网络,全链接层运算量占比大。
RNN,专门处理带有 序列模式 的数据(数据前后有序 ,或者本项数据需要前一项数据特征),且采用了共享权重来减少运算量。
RNN cell:
其中 下面的黄色框的 x1 x2 x3 x4 表示 1 2 3 4 时刻x的输入。
上面蓝色框的 h1 h2 h3 h4 表示相应的输出 也叫hidden。
而中间的 RNN Cell相当于一个Linear线性层。
且前面的h0表示前一层的特征信息。
每一层的RNN Cell都是让输入进来的X和上一层输出的h做运算然后给到下一层,当作下一层的h。
这么多的RNN Cell可以表示为下图:
即他们都使用同一个线性层。
在RNN中最常用的激活函数是 tanh 就是sin cos 的那个tan。
自行创建RNN Cell:
参数 输入维度、隐层维度。
其中的张量维度关系:
input输入维度: batch ,input_size batch 批量样本N,和输入维度x (N * x)
hidden 隐层维度 : 同样的 batch 和hidden_size 。(N * h)
hidden 输出的隐层维度: 同样的batch 和hidden_size 。(N * h)
显然,因为要让输入数据X和上一层输出的H做加法,所以输入的数据X的维度要和H的维度一致,输入的维度X做一个线性变换变换成和H一个维度。
x做线性变换维度就是 输入的维度 * 隐层的维度(设此时batch为1.),即 inputsize * hidensize
看一个例子:
假设现在的数据:
batchsize = 1
seqlen = 3 也就是 x1 x2 x3 三个元素
inputsize = 4 向量中四个元素
hidden = 2 向量中两个元素
则 input.shape = (1,4) . output.shape = (1,2)
整个的tensor维度 (seqlen,batchsize,inputsize) = (3,1,4)
例子代码:
import torchbatch_size = 1
seq_len = 3
input_size = 4
hidden_size = 2
cell = torch.nn.RNNCell(input_size=input_size, hidden_size=hidden_size)
dataset = torch.randn(seq_len, batch_size, input_size)
hidden = torch.zeros(batch_size, hidden_size) # h0
for idx, input in enumerate(dataset):print('=' * 20, idx, '=' * 20)print('输入维度', input.shape)hidden = cell(input, hidden) # 这次的输入加 上次隐层的输出print('输出维度', hidden.shape)print(hidden)得到结果:
==================== 0 ====================
输入维度 torch.Size([1, 4])
输出维度 torch.Size([1, 2])
tensor([[0.1037, 0.7474]], grad_fn=<TanhBackward0>)
==================== 1 ====================
输入维度 torch.Size([1, 4])
输出维度 torch.Size([1, 2])
tensor([[0.4714, 0.4577]], grad_fn=<TanhBackward0>)
==================== 2 ====================
输入维度 torch.Size([1, 4])
输出维度 torch.Size([1, 2])
tensor([[0.5499, 0.4984]], grad_fn=<TanhBackward0>)
直接使用RNN:
参数 : 输入维度,隐层维度,RNN有多少层
下图,调用时候的参数传入, inputs就是整个的输入数据X。
输入的hidden就是 h0。
输出的两个张量,out 就是里面的h1,h2…
输出的hiden就是整个循环完输出的那个最后一个hn。
其中输入维度要求:
input : (seqsize,batch,inputsize)
hidden: (numlayers,batch,hiddensize)
输出维度:
output: (seqsize,batch,hiddensize)
hidden: (numlayers,batch,hiddensize)
像下图这样,就是有三层RNN,则 numlayers = 3.
注意其中的隐层h数值的传播,不仅从左到右,还有从下到上的传播。
输入就是左边的h (相当于上面说的h0)和最下面一行的x。
最后的输出,最上面哪一行h就是output。最右边这一列就是hidden。
练习
现在要将hello变成ohlol。
首先使用 RNN Cell:
则下图黄色输入的每一个对应 hellow,上面的蓝色输出则对应这ohlol。
因为程序需要向量,所以先将字母变成向量。
将字母对应到相应的数字代表(字典),然后hello就变成了 10223 如下图。
然后再将其变成向量:
如下图,很显然能看出来。 1,则用这一行的下标1填写成1表示,其余为0。
向量矩阵宽度就是 字典中有多少个字母。从0开始排嘛。
最后面的那个矩阵叫 独热向量,(one-hot vectors)
显然 下图中的 矩阵 inputsize = 4
代码数据准备:
如下图:
其中 X_one_hot 表示 遍历初始的x数据,拿出来每一个x去上面定义的矩阵中找对应的行,比如x进来的是1,则去上面的矩阵中拿走第一行,这样就是让一个数变成了一个向量供后面使用。
构造的查询矩阵维度 : inputsize * seqsize。
由于input的输入维度要求 (seqsize,batchsize,inputsize)。所以 变化一下维度。
labels 同样变化维度
模型类设计
其中 def init_hidden 是工具函数,用于设置初始的那个h0。
然后损失函数和优化器:
Adam 基于梯度下降的优化器 改进版。
开始训练:
训练之前别忘了归零优化器的梯度。
net.init_hidden() 初始化hidden 就是算那个h0
这里的loss并没有用item() 因为这里算出来的是RNN Cell中的一个,是需要构造计算图的,全部相加之后才是最终的loss。最后取值的时候 依旧需要 loss.item()
需要注意的是 这其中的维度:
inputs 维度前面说了 (seqsize,batchsize,inputsize)
而input维度是 (batchsize,inputsize)
labels维度 (seqsize,1)
label维度 (1)
下面使用RNN实现
数据集准备
模型类设计;
训练类:
这里的 inputs维度 (seqsize,batchsize,inputsize)
输出维度 outputs (seqsize,batchsize,hiddensize)
关于独热向量, 是有一些缺点的,里面的向量比较稀疏,数据量大的时候,维度非常高。其中字典的对应关系是硬编码。
改进的向量: Embedding vectors
下面的Embed表示。将独热向量变成稠密的表示,然后RNN Cell,然后加一个线性层,作用: 将隐层和输出的分类调整到 一致