烟台市建设工程交易中心网站,南宁网站设计和开发大赛,做图片的软件免费,国外的购物网站有哪些来源#xff1a;投稿 作者#xff1a;175 编辑#xff1a;学姐 往期内容#xff1a;
从零实现深度学习框架1#xff1a;RNN从理论到实战#xff08;理论篇#xff09;
从零实现深度学习框架2#xff1a;RNN从理论到实战#xff08;实战篇#xff09;
从零实现深度… 来源投稿 作者175 编辑学姐 往期内容
从零实现深度学习框架1RNN从理论到实战理论篇
从零实现深度学习框架2RNN从理论到实战实战篇
从零实现深度学习框架3再探多层双向RNN的实现本篇
在前面的文章中我们实现了多层、双向RNN。但是这几天一直在思考这种实现方式是不是有问题。因为RNN的实现关乎后面ELMo和seq2seq所以不得不重视。
双向RNN的实现方式
以两层双向RNN为例。我们之前实现的方式类似如下图所示 这两张图片来自于https://github.com/pytorch/pytorch/issues/4930#issuecomment-361851298 就是正向RNN和反向RNN可以看成是两个独立的两层RNN网络最终拼接了它们的输出。但是总感觉双向RNN不会这么简单带着这个疑问去拜读了双向RNN的论文1得到下面的这张图片 如果采用这种方式的话那么两层双向RNN的实现应该像下图这样 即第一层BRNN的输出同时考虑了正向和方向输出将它们拼接在一起作为第二层BRNN的输入。
但是这时遇到了一个问题如果这样实现的话那么输出的维度会怎样呢BRNN中每层参数的维度会产生怎样的变化呢
遇事不决找Torch我们摸着PyTorch过河。
带着这个问题我们去看PyTorch的文档并查阅资料梳理一下PyTorch实现的RNN(GRU、LSTM)中各种输入、输出、隐藏状态的维度。
理解RNN中的各种维度
以RNN为例为什么不以最复杂的LSTM为例呢因为LSTM参数过多相比RNN太过复杂不太容易理解。柿子要挑软的捏我们理解了RNN再去理解GRU或LSTM就会简单多了。 此图片参考了https://stackoverflow.com/a/48305882 从上图可以看出在一个堆叠了l层的RNN中output包含了最后一层RNN输出的所有隐藏状态h_n包含了最后一个时间步上所有层的输出。
我们知道了它们的构成方式下面看一下它们和上图中另外两个参数input和h_0在不同类型的RNN中维度如何2。 inputRNN的输入序列。若batch_firstFalse则其大小为(seq_len, batch_size, input_size)若batch_firstTrue则其大小为(batch_size, seq_len, input_size) h_0 RNN的初始隐藏状态可以为空。大小为(num_layers * num_directions, batch_size, hidden_size) output RNN最后一层所有时间步的输出。若batch_firstFalse则其大小为(seq_len, batch_size, num_directions * hidden_size)若batch_firstTrue则其大小为(batch_size, seq_len, num_directions * hidden_size) h_nRNN中所有层最后一个时间步的隐藏状态。其大小为(num_layers * num_directions, batch_size, hidden_size)。不受batch_first的影响其批次维度表现和batch_firstFalse一样。后面以代码实现的角度解释下为何这样不代表官方的意图。
其中seq_len表示输入序列长度batch_size表示批次大小input_size表示输入的特征数量num_layers表示层数num_directions表示方向个数单向RNN时为1双向RNN时为2hidden_size表示隐藏状态的特征数。
的形状应该和是一致的。
下面我们进行验证首先看一下初始参数
# 输入大小
INPUT_SIZE 2
# 序列长度
SEQ_LENGTH 5
# 隐藏大小
HIDDEN_SIZE 3
# 批大小
BATCH_SIZE 4以及输入
inputs Tensor.randn(BATCH_SIZE, SEQ_LENGTH, INPUT_SIZE)简单RNN
简单RNN就是单向单层RNN
rnn nn.RNN(input_sizeINPUT_SIZE, hidden_sizeHIDDEN_SIZE, num_layers1, batch_firstTrue)output, h_n rnn(inputs)print(fInput Shape: {inputs.shape} )
print(fOutput Shape: {output.shape} )
print(fHidden Shape: {h_n.shape} )inputs维度是我们预先定理好的注意这里batch_firstTrue所以inputs的第一个维度是批大小。 output来自最后一层所有时间步的输出时间步长度为5包含整个批次内4条数据每条数据的输出维度为3可以理解为3分类问题。 $h_n$来自单层最后一个时间步的隐藏状态包含整个批次内4条数据每条数据的输出维度为3。
Input Shape: (4, 5, 2)
Output Shape: (4, 5, 3)
Hidden Shape: (1, 4, 3) 堆叠RNN
如果将层数改成3我们就得到了3层RNN堆叠在一起的架构来看下此时output和h_n的维度会发生怎样的变化。
rnn nn.RNN(input_sizeINPUT_SIZE, hidden_sizeHIDDEN_SIZE, num_layers3, batch_firstTrue)output, h_n rnn(inputs)print(fInput Shape: {inputs.shape} )
print(fOutput Shape: {output.shape} )
print(fHidden Shape: {h_n.shape} )Input Shape: (4, 5, 2)
Output Shape: (4, 5, 3)
Hidden Shape: (3, 4, 3) output来自最后一层所有时间步的输出时间步长度为5包含整个批次内4条数据每条数据的输出维度为3。其维度保持不变。 h_n来自所有三层最后一个时间步的隐藏状态包含整个批次内4条数据每条数据的输出维度为3。可以看到其输出的第一个维度大小由1变成了3因为包含了3层的结果。
双向RNN
传入bidirectionalTrue并将层数改回单层。
rnn nn.RNN(input_sizeINPUT_SIZE, hidden_sizeHIDDEN_SIZE, num_layers1, batch_firstTrue, bidirectionalTrue)output, h_n rnn(inputs)print(fInput Shape: {inputs.shape} )
print(fOutput Shape: {output.shape} )
print(fHidden Shape: {h_n.shape} )Input Shape: (4, 5, 2)
Output Shape: (4, 5, 6)
Hidden Shape: (2, 4, 3) output来自最后一层所有时间步的输出时间步长度为5包含整个批次内4条数据每条数据的输出维度为3由于是双向包含了两个方向上的结果在此维度上进行堆叠所以由3变成了6。 h_n最后一个时间步的隐藏状态包含整个批次内4条数据每条数据的输出维度为3。第一个维度由1变成了2因为在此维度上堆叠了双向的结果。
它们都包含了双向的结果那如果想分别得到每个方向上的结果要怎么做呢
对于output。若batch_firstTrue将output按照out.reshape(shape(batch_size, seq_len, num_directions, hidden_size))进行变形正向和反向的维度值为别为0和1。
对于h_n按照h_n.reshape(shape(num_layers, num_directions, batch_size, hidden_size))正向和反向的维度值为别为0和1。
我们来对output进行拆分
# batch_firstTrue
output_reshaped output.reshape((BATCH_SIZE, SEQ_LENGTH, 2, HIDDEN_SIZE))
print(Shape of the output after directions are separated: , output_reshaped.shape)# 分别获取正向和反向的输出
output_forward output_reshaped[:, :, 0, :]
output_backward output_reshaped[:, :, 1, :]
print(Forward output Shape: , output_forward.shape)
print(Backward output Shape: , output_backward.shape)Shape of the output after directions are separated: (4, 5, 2, 3)
Forward output Shape: (4, 5, 3)
Backward output Shape: (4, 5, 3)对h_n进行拆分
# 1: 层数 2: 方向数
h_n_reshaped h_n.reshape((1, 2, BATCH_SIZE, HIDDEN_SIZE))
print(Shape of the hidden after directions are separated: , h_n_reshaped.shape)h_n_forward h_n_reshaped[:, 0, :, :]
h_n_backward h_n_reshaped[:, 1, :, :]
print(Forward h_n Shape: , h_n_forward.shape)
print(Backward h_n Shape: , h_n_backward.shape)Shape of the hidden after directions are separated: (1, 2, 4, 3)
Forward h_n Shape: (1, 4, 3)
Backward h_n Shape: (1, 4, 3)堆叠双向RNN
设置bidirectionalTrue并将层数设成3层。
rnn nn.RNN(input_sizeINPUT_SIZE, hidden_sizeHIDDEN_SIZE, num_layers3, batch_firstTrue, bidirectionalTrue)output, h_n rnn(inputs)print(fInput Shape: {inputs.shape} )
print(fOutput Shape: {output.shape} )
print(fHidden Shape: {h_n.shape} )Input Shape: (4, 5, 2)
Output Shape: (4, 5, 6)
Hidden Shape: (6, 4, 3) output来自最后一层所有时间步的输出时间步长度为5包含整个批次内4条数据每条数据的输出维度为3由于是双向包含了两个方向上的结果在此维度上进行堆叠所以由3变成了6。 h_n来自所有三层最后一个时间步的隐藏状态包含整个批次内4条数据每条数据的输出维度为3。第一个维度由变成了6因为三层输出在此维度上堆叠了双向的结果。
如果我们也对它们按方向进行拆分的话。
首先对output拆分
# batch_firstTrue
output_reshaped output.reshape((BATCH_SIZE, SEQ_LENGTH, 2, HIDDEN_SIZE))
print(Shape of the output after directions are separated: , output_reshaped.shape)# 分别获取正向和反向的输出
output_forward output_reshaped[:, :, 0, :]
output_backward output_reshaped[:, :, 1, :]
print(Forward output Shape: , output_forward.shape)
print(Backward output Shape: , output_backward.shape)Shape of the output after directions are separated: (4, 5, 2, 3)
Forward output Shape: (4, 5, 3)
Backward output Shape: (4, 5, 3)其次对h_out拆分
# 3: 层数 2: 方向数
h_n_reshaped h_n.reshape((3, 2, BATCH_SIZE, HIDDEN_SIZE))
print(Shape of the hidden after directions are separated: , h_n_reshaped.shape)h_n_forward h_n_reshaped[:, 0, :, :]
h_n_backward h_n_reshaped[:, 1, :, :]
print(Forward h_n Shape: , h_n_forward.shape)
print(Backward h_n Shape: , h_n_backward.shape)Shape of the hidden after directions are separated: (3, 2, 4, 3)
Forward h_n Shape: (3, 4, 3)
Backward h_n Shape: (3, 4, 3)重构双向RNN的实现 我们按照对每层输出状态进行拼接的方式来重构多层双向RNN。
这里有一个问题是由于我们对隐藏状态进行了拼接 其维度变成了(n_steps, batch_size, num_directions * hidden_size)。
受到了PyTorch官网启发 ~RNN.weight_ih_l[k] – the learnable input-hidden weights of the k-th layer, of shape (hidden_size, input_size) for k 0. Otherwise, the shape is (hidden_size, num_directions * hidden_size) ~RNN.weight_hh_l[k] – the learnable hidden-hidden weights of the k-th layer, of shape (hidden_size, hidden_size)
所以我们相应地改变输入到隐藏状态的维度(hidden_size, num_directions * hidden_size)。
我们说 h_n的输出维度不受batch_first的影响其批次维度表现和batch_firstFalse一样。这是因为在实现时为了统一将input的时间步放到了第1个维度将批大小放到中间input就像batch_firstFalse一样而隐藏状态的方式和它保持一致即可。
if self.batch_first:batch_size, n_steps, _ input.shapeinput input.transpose((1, 0, 2)) # 将batch放到中间维度下面看具体实现
RNNCellBase
class RNNCellBase(Module):def reset_parameters(self) - None:stdv 1.0 / math.sqrt(self.hidden_size) if self.hidden_size 0 else 0for weight in self.parameters():init.uniform_(weight, -stdv, stdv)def __init__(self, input_size, hidden_size: int, num_chunks: int, bias: bool True, num_directions1,reset_parametersTrue, deviceNone, dtypeNone) - None:RNN单时间步的抽象:param input_size: 输入x的特征数:param hidden_size: 隐藏状态的特征数:param bias: 线性层是否包含偏置:param nonlinearity: 非线性激活函数 tanh | relu (mode RNN)factory_kwargs {device: device, dtype: dtype}super(RNNCellBase, self).__init__()self.input_size input_sizeself.hidden_size hidden_size# 输入x的线性变换self.input_trans Linear(num_directions * input_size, num_chunks * hidden_size, biasbias, **factory_kwargs)# 隐藏状态的线性变换self.hidden_trans Linear(hidden_size, num_chunks * hidden_size, biasbias, **factory_kwargs)if reset_parameters:self.reset_parameters()def extra_repr(self) - str:s input_size{input_size}, hidden_size{hidden_size}if bias in self.__dict__ and self.bias is not True:s , bias{bias}if nonlinearity in self.__dict__ and self.nonlinearity ! tanh:s , nonlinearity{nonlinearity}return s.format(**self.__dict__)RNNCell
class RNNCell(RNNCellBase):def __init__(self, input_size, hidden_size: int, bias: bool True, nonlinearity: str tanh, num_directions1,reset_parametersTrue, deviceNone, dtypeNone):factory_kwargs {device: device, dtype: dtype, reset_parameters: reset_parameters}super(RNNCell, self).__init__(input_size, hidden_size, num_chunks1, biasbias, num_directionsnum_directions,**factory_kwargs)if nonlinearity tanh:self.activation F.tanhelse:self.activation F.reludef forward(self, x: Tensor, h: Tensor, c: Tensor None) - Tuple[Tensor, None]:h_next self.activation(self.input_trans(x) self.hidden_trans(h))return h_next, None在RNNCell的forward中也返回了一个元组元组中第二个元素代表了c_next为了兼容LSTM的实现。
RNNBase
class RNNBase(Module):def __init__(self, cell: RNNCellBase, input_size: int, hidden_size: int, batch_first: bool False,num_layers: int 1, bidirectional: bool False, bias: bool True, dropout: float 0,reset_parametersTrue, deviceNone, dtypeNone) - None::param input_size: 输入x的特征数:param hidden_size: 隐藏状态的特征数:param batch_first: 批次维度是否在前面:param num_layers: 层数:param bidirectional: 是否为双向:param bias: 线性层是否包含偏置:param dropout: 用于多层堆叠RNN默认为0代表不使用dropout:param reset_parameters: 是否执行reset_parameters:param device::param dtype:super(RNNBase, self).__init__()factory_kwargs {device: device, dtype: dtype, reset_parameters: reset_parameters}self.num_layers num_layersself.hidden_size hidden_sizeself.input_size input_sizeself.batch_first batch_firstself.bidirectional bidirectionalself.bias biasself.num_directions 2 if self.bidirectional else 1# 支持多层self.cells ModuleList([cell(input_size, hidden_size, bias, **factory_kwargs)] [cell(hidden_size, hidden_size, bias, num_directionsself.num_directions,**factory_kwargs) for _ inrange(num_layers - 1)])if self.bidirectional:# 支持双向self.back_cells copy.deepcopy(self.cells)self.dropout dropoutif dropout ! 0:# Dropout层self.dropout_layer Dropout(dropout)def _one_directional_op(self, input, n_steps, cell, h, c) - Tuple[Tensor, Tensor, Tensor]:hs []# 沿着input时间步进行遍历for t in range(n_steps):inp input[t]h, c cell(inp, h, c)hs.append(h)return h, c, F.stack(hs)def _handle_hidden_state(self, input, state):assert input.ndim 3 # 必须传入批数据最小批大小为1if self.batch_first:batch_size, n_steps, _ input.shapeinput input.transpose((1, 0, 2)) # 将batch放到中间维度else:n_steps, batch_size, _ input.shapeif state is None:h Tensor.zeros((self.num_layers * self.num_directions, batch_size, self.hidden_size), dtypeinput.dtype,deviceinput.device)else:h state# 得到每层的状态hs list(F.unbind(h)) # 按层数拆分hreturn hs, [None] * len(hs), input, n_steps, batch_sizedef forward(self, input: Tensor, state: Tensor) - Tuple[Tensor, Tensor, Tensor]:RNN的前向传播:param input: 形状 [n_steps, batch_size, input_size] 若batch_firstFalse:param state: (隐藏状态单元状态)元组 每个元素形状 [num_layers, batch_size, hidden_size]:return:num_directions 2 if self.bidirectional else 1output: (n_steps, batch_size, num_directions * hidden_size)若batch_firstFalse 或(batch_size, n_steps, num_directions * hidden_size)若batch_firstTrue包含每个时间步最后一层(多层RNN)的输出h_th_n: (num_directions * num_layers, batch_size, hidden_size) 包含最终隐藏状态c_n: (num_directions * num_layers, batch_size, hidden_size) 包含最终单元状态(LSTM)非LSTM为Nonehs, cs, input, n_steps, batch_size self._handle_hidden_state(input, state)# 正向得到的h_n反向得到的h_n,正向得到的c_n反向得到的c_nh_n_f, h_n_b, c_n_f, c_n_b [], [], [], []for layer in range(self.num_layers):h, c, hs_f self._one_directional_op(input, n_steps, self.cells[layer], hs[layer], cs[layer])h_n_f.append(h) # 保存最后一个时间步的隐藏状态c_n_f.append(c)if self.bidirectional:h, c, hs_b self._one_directional_op(F.flip(input, 0), n_steps, self.back_cells[layer],hs[layer self.num_layers], cs[layer self.num_layers])hs_b F.flip(hs_b, 0) # 将输出时间步维度逆序使得时间步t0上是看了整个序列的结果。# 拼接两个方向上的输入h_n_b.append(h)c_n_b.append(c)input F.cat([hs_f, hs_b], 2) # (n_steps, batch_size, num_directions * hidden_size)else:input hs_f # (n_steps, batch_size, num_directions * hidden_size)# 在第1层之后最后一层之前需要经过dropoutif self.dropout and layer ! self.num_layers - 1:input self.dropout_layer(input)output input # (n_steps, batch_size, num_directions * hidden_size) 最后一层最后计算的输入就是它的输出c_n Noneif self.bidirectional:h_n F.cat([F.stack(h_n_f), F.stack(h_n_b)], 0)if c is not None:c_n F.cat([F.stack(c_n_f), F.stack(c_n_b)], 0)else:h_n F.stack(h_n_f)if c is not None:c_n F.stack(c_n_f)if self.batch_first:output output.transpose((1, 0, 2))return output, h_n, c_ndef extra_repr(self) - str:s input_size{input_size}, hidden_size{hidden_size}if self.num_layers ! 1:s , num_layers{num_layers}if self.bias is not True:s , bias{bias}if self.batch_first is not False:s , batch_first{batch_first}if self.dropout:s , dropout{dropout}if self.bidirectional is not False:s , bidirectional{bidirectional}return s.format(**self.__dict__)同样做了兼容LSTM的实现会多了一些if判断。
RNN
class RNN(RNNBase):def __init__(self, *args, **kwargs) - None::param input_size: 输入x的特征数:param hidden_size: 隐藏状态的特征数:param batch_first::param num_layers: 层数:param bidirectional: 是否为双向:param bias: 线性层是否包含偏置:param dropout: 用于多层堆叠RNN默认为0代表不使用dropout:param nonlinearity: 非线性激活函数 tanh | relusuper(RNN, self).__init__(RNNCell, *args, **kwargs)def forward(self, input: Tensor, state: Tensor None) - Tuple[Tensor, Tensor]:output, h_n, _ super().forward(input, state)return output, h_n因为基类RNNBase的forward会返回output,h_n,c_n所以RNN这里重写了forward方法仅返回output和h_n。
通过这种方式实现GRU和RNN非常类似。
GRU
class GRU(RNNBase):def __init__(self, *args, **kwargs)::param input_size: 输入x的特征数:param hidden_size: 隐藏状态的特征数:param batch_first::param num_layers: 层数:param bidirectional: 是否为双向:param bias: 线性层是否包含偏置:param dropout: 用于多层堆叠RNN默认为0代表不使用dropoutsuper(GRU, self).__init__(GRUCell, *args, **kwargs)def forward(self, input: Tensor, state: Tensor None) - Tuple[Tensor, Tensor]:output, h_n, _ super().forward(input, state)return output, h_n实例测试
同样的配置下
embedding_dim 128
hidden_dim 128
batch_size 32
num_epoch 10
n_layers 2
dropout 0.2model RNN(len(vocab), embedding_dim, hidden_dim, num_class, n_layers, dropout, bidirectionalTrue, modemode)两层双向RNN可以得到75%的准确率。
Training Epoch 0: 94it [01:16, 1.23it/s]
Loss: 220.78
Training Epoch 1: 94it [01:16, 1.24it/s]
Loss: 151.85
Training Epoch 2: 94it [01:14, 1.26it/s]
Loss: 125.62
Training Epoch 3: 94it [01:15, 1.25it/s]
Loss: 110.55
Training Epoch 4: 94it [01:14, 1.27it/s]
Loss: 100.75
Training Epoch 5: 94it [01:13, 1.28it/s]
Loss: 94.12
Training Epoch 6: 94it [01:12, 1.29it/s]
Loss: 88.64
Training Epoch 7: 94it [01:12, 1.29it/s]
Loss: 84.51
Training Epoch 8: 94it [01:13, 1.28it/s]
Loss: 80.83
Training Epoch 9: 94it [01:13, 1.27it/s]
Loss: 78.12
Testing: 29it [00:06, 4.79it/s]
Acc: 0.75
Cost:749.8793613910675完整代码
https://github.com/nlp-greyfoss/metagrad
References
Bidirectional recurrent neural networkshttps://www.researchgate.net/publication/3316656_Bidirectional_recurrent_neural_networks
Pytorch [Basics] — Intro toRNNhttps://towardsdatascience.com/pytorch-basics-how-to-train-your-neural-net-intro-to-rnn-cb6ebc594677
关注下方《学姐带你玩AI》
220篇AI必读论文免费领取
码字不易欢迎大家点赞评论收藏