循环神经网络
基本结构

如图所示,在图中可观察到输入变量x以及各层次结构布局:输入层直接连接至隐含层,并经由隐含层层传递至输出层;其中自循环权重矩阵W是其独特之处。具体而言,在该模型中:U代表输入层至隐含层的权重矩阵;W代表状态与隐含层之间的权重矩阵;V代表隐含层层至输出层的权重矩阵;s表示状态变量值;此外,在该模型中:不同时间节点下,W,U,V保持不变;这一机制类似于卷积神经网络中的过滤器概念,在此方案下实现了权值共享机制:通过这一机制实现参数共享以降低整体模型规模
隐含层结构

该网络在每一时刻t都具有相同的拓扑结构。其中输出向量x属于n维空间(即x∈ℝⁿ),隐层神经元数量为m个(记作|m|),输出层神经元数量设定为r个(即|r|)。因此权重矩阵U是一个n×m维度的矩阵;而W则是一个m×m维度方阵(因为W用于将上一时间步的状态映射到当前状态),V则是一个m×r维度矩阵用于生成最终输出。此外,在这一过程中:
xt表示t时刻输入的数据
at代表t时刻隐层的状态信息
ot表示t时刻生成的输出结果
需要注意的是:
at不仅携带当前时刻的信息… 还能反映所有历史状态的影响。
前向传播
传统的神经网络通常使用前向传播和反向传播来训练模型参数。 recurrent neural networks (RNNs) 的基本原理与其传统架构相似。为了帮助理解其运作机制,请参考以下具体的图例。

其中x代表输入数据(通常是以向量形式呈现),a代表状态变量(即动态变化的状态信息),y代表输出结果或预测值(这些结果基于当前的状态和输入)。其前向传播过程如下:根据设定的初始条件来赋值初始状态a。

,然后计算状态及输出,具体如下:

随机设置一些数字实现一下:
import numpy as np
X = [2,3] # 输入值
state = [0.0,0.0] # 初始状态
w_cell_state = np.asarray([[0.1,0.2],[0.3,0.4],[0.5,0.6]]) # waa = [[0.1,0.2],[0.3,0.4]] wax = [0.5,0.6]
b_cell = np.asarray([0.2,-0.2]) # b
w_output = np.asarray([[1.0],[2.0]])
b_output = 0.1
for i in range(len(X)):
state = np.append(state,X[i])
before_activation = np.dot(state,w_cell_state) + b_cell
state = np.tanh(before_activation)
final_output = np.dot(state,w_output) + b_output
print("状态_%i\t:"%i,state)
print("输出值_%i\t:"%i,final_output)
状态_0 : [0.83365461 0.76159416]
输出值_0 : [2.45684292]
状态_1 : [0.96485486 0.96873777]
输出值_1 : [3.0023304]
代码解释
循环神经网络变形
循环神经网络在长时间序列的不同时间点上重复执行相同的操作以构建极其深的计算图并且实现了参数共享这一过程是一个复杂而关键的设计环节。而梯度消失或爆炸作为一个普遍存在的挑战在深度神经网络中尤为常见尤其是在RNN架构中这一问题表现得尤为突出这是因为相邻时间步之间存在紧密联系使得它们对应的导数要么均小于1要么均大于1从而导致每个权重都会朝着相同的方向发生显著变化这使得与前馈神经网络相比RNN模型更容易出现极端化的梯度变化现象特别是在面对较长的时间序列时这种问题会更加明显具体而言当使用简单的RNN模型处理较大的时间跨度时模型往往难以收敛甚至可能导致整个网络无法有效训练从而失去了处理长时记忆的能力这种局限性在自然语言处理等实际应用领域中具有严重的负面影响为此提出了多种解决方案其中较为常用的方法包括:第一类采用更适合的数据激活函数如ReLU函数其独特的导数特性能够有效避免"梯度消失"现象的发生;第二类引入Batch Normalization(BN)层不仅有助于加快训练速度还能有效抑制过拟合;第三类重新设计网络架构以实现对长期依赖关系的有效捕捉例如LSTM(长短时记忆单元)模型正是通过创新性的机制解决了上述核心问题
LSTM
长短时记忆神经网络(Long Short-Term Memory, LSTM)能够很好地解决信息的长期依赖问题并防止梯度消失或爆炸现象与传统的RNN相比其独特的特点在于巧妙地设计了一个循环结构以优化信息处理过程。该网络通过两个机制来管理单元状态c的内容其中遗忘门负责决定上一个时间步的状态有多少信息会被保留到当前时间步而输入门则决定了当前输入数据如何影响单元状态c。最后通过输出门这个机制LSTM能够调控单元状态c中有多少信息会被传递给当前输出值ht从而完成信息处理任务

GRU
GRU相较于LSTM在结构上做了诸多简化,在模型架构上仅保留了一个门控单元。这使得其计算效率得到了显著提升的同时内存占用也相应降低。GRU在设计上实现了两个主要改进:一是通过重新设计门控单元实现了对加法操作的支持,并且去除了记忆细胞的状态。
- 将输入/遗弃/输出分为两个: 分别为更新( update gate ) zt 和重置( reset gate ) rt。
- 单元状态与输出合并成为一个: ht。
GRU结构图

小点圈是点乘。
Bi-RNN
LSTM的变种除了GRU之外,比较流行还有双向循环神经网络(Bidirectional RecurrentNeural Networks,Bi-RNN),Bi-RNN模型由Schuster、Paliwal于1997年首次提出,和LSTM同年。Bi-RNN增加了RNN可利用信息,普通MLP,数据长度有限制。RNN,可以处理不固定长度时序数据,但无法利用未来信息。而Bi-RNN,同时使用时序数据输入历史及未来数据,时序相反的两个循环神经网络连接同一输出,输出层可以同时获取历史未来信息。
采用Bi-RNN能提升模型效果。百度语音识别就是通过Bi-RNN综合上下文语境,提升模型准确率。
双向循环神经网络的基本思想是提出每一个训练序列向前和向后分别是两个循环神经网络(RNN),而且这两个都连接着一个输出层。这个结构提供给输出层输入序列中每一个点完整的过去和未来的上下文信息。6个独特的权值在每一个时步被重复的利用,6个权值分别对应:输入到向前和向后隐含层(w1,w3)、隐含层到隐含层自己(w2,w5)、向前和向后隐含层到输出层(w4,w6)。值得注意的是,向前和向后隐含层之间没有信息流,这保证了展开图是非循环的。

pytorch实现
该框架提供了实现循环神经网络所需的一系列API设计与接口实现方案中的一种方式,在这一系列API中包含了多种不同的实现方式:其中最基础的是单个单元版本如RNNCell、LSTMCell以及RGUCell等基础组件模块;而对于更高层次的应用需求则可以通过封装形式使用现成的函数库接口即包括RNN、LSTM以及GRU等模块进行快速构建与应用开发操作;其主要区别体现在输入方式上即单个单元版本会将每个时间步或者序列片段作为独立输入传递给处理模块而对应的封装实现则能够一次性接收一个完整的时序数据序列来进行批量处理
torch.nn.RNN(args, *kwargs)
代码解释
nn.RNN参数如下:
input_size:输入x的特征数量
hidden_size:隐藏层的特征数量
num_layers:RNN的层数
nonlinearity:指定非线性函数使用tanh还是Relu,默认是tanh
bias:如果是false,那么RNN层就不会使用偏置权重bi和bh,默认是True
batch_first:如果True的话,那么输入Tensor的shape应该是(batch,seq,feature),输出也是这样。默认网络输入是(seq,batch,feature),即序列长度、批次大小、特征维度。
dropout:如果值非零(该参数取值范围为0~1之间),那么除了最后一层外,其他层的输出都会加上一个dropout层,缺省为零。
代码解释
LSTM
import torch.nn as nn
import torch
class LSTMCell(nn.Module):
def __init__(self,input_size, hidden_size,cell_size,output_size):
super(LSTMCell, self).__init__()
self.hidden_size = hidden_size
self.cell_size = cell_size
self.gate = nn.Linear(input_size +hidden_size, cell_size)
self.output = nn.Linear(hidden_size,output_size)
self.sigmoid = nn.Sigmoid()
self.tanh = nn.Tanh()
self.softmax = nn.LogSigmoid()
def forward(self, input, hidden, cell):
combined = torch.cat((input, hidden), 1)
f_gate = self.sigmoid(self.gate(combined))
i_gate = self.sigmoid(self.gate(combined))
o_gate = self.sigmoid(self.gate(combined))
z_gate = self.tanh(self.gate(combined))
cell = torch.add(torch.mul(cell,f_gate), torch.mul(z_gate,i_gate))
hidden = torch.mul(self.tanh(cell),o_gate)
output = self.output(hidden)
output = self.softmax(output)
return output,hidden, cell
def initHidden(self):
return torch.zeros(1, self.hidden_size)
def initCell(self):
return torch.zeros(1, self.cell_size)
代码解释
GRU
import torch.nn as nn
import torch
class GRUCell(nn.Module):
def __init__(self, input_size,hidden_size, output_size):
super(GRUCell, self).__init__()
self.hidden_size = hidden_size
self.gate = nn.Linear(input_size + hidden_size, hidden_size)
self.output = nn.Linear(hidden_size,output_size)
self.sigmoid = nn.Sigmoid()
self.tanh = nn.Tanh()
self.softmax = nn.LogSoftmax(dim = 1)
def forward(self,input,hidden):
combined = torch.cat((input,hidden),1)
z_gate = self.sigmoid(self.gate(combined))
r_gate = self.sigmoid(self.gate(combined))
combined_01 = torch.cat((input,torch.mul(hidden, r_gate)),1)
h1_state = self.tanh(self.gate(combined_01))
h_state = torch.add(torch.mul((1-z_gate), hidden), torch.mul(h1_state,z_gate))
output = self.output(h_state)
output = self.softmax(output)
return output,h_state
def initHidden(self):
return torch.zeros(1,self.hidden_size)
grucell = GRUCell(input_size = 20, hidden_size = 20,output_size = 10)
input = torch.randn(32,20)
h_0 = torch.randn(32,20)
output,hn = grucell(input,h_0)
print(output.size(),hn.size())
代码解释
