最近在看RNN循环神经网络,但是网上的教程杂七杂八的太乱了,这里我将网上的教程大概整理一下,以供大家一起起学习。
1、为什么需要RNN
我们知道 对于传统神经网络,通过训练之后,给特定的输入就会得到期望的输出。但是对于传统的神经网络,前一个输入与后一个输入之间通常是没有联系的,并且其输出之间也是没有联系的。
试想一下,如果我们要处理进行翻译一段文字,这时候将单个文字输入到神经网络里面,输出之间是没有任何关联的,这就导致我们翻译的文本前后不连贯,尤其对于这种序列模型,输入与输出类型并不一定一致。
RNN具有强大的处理各种输入输出类型的能力:
- 情感分析(Sentiment Classification) – 这可以是简单的把一条推文分为正负两种情绪的任务。所以输入是任意长度的推文, 而输出是固定的长度和类型.
- 图像标注(Image Captioning) – 假设我们有一个图片,我们需要一个对该图片的文本描述。所以,我们的输入是单一的图像,输出是一系列或序列单词。这里的图像可能是固定大小的,但输出是不同长度的文字描述。
- 语言翻译(Language Translation) – 这里假设我们想将英文翻译为法语. 每种语言都有自己的语义,对同一句话有不同的长度。因此,这里的输入和输出是不同长度的。
2、RNN基本结构
如图这是一种基本的RNN结构
从图可以看出,输入层输入X后,经过权重矩阵U之后进入隐藏层S,隐藏层S同时通过权重矩阵V之后得到输出O和通过权重矩阵W又和输入X一起成为输入。我这样描述感觉描述不清楚,我们可以把它展开来看:
这里输入展开成为一个个序列X1,X2,X3....XT,前一个输入通过RNN-cell之后成为输出y和a,并通过a来影响下一个输出,这样就建立起前一个输入和后一个输入之间的联系。
对于一个单独的RNN-Cell结构如图所示:
同时这也是RNN单步前向传播的计算方法,Python实现:
# GRADED FUNCTION: rnn_cell_forward
def rnn_cell_forward(xt, a_prev, parameters):
"""
根据图2实现RNN单元的单步前向传播
参数:
xt -- 时间步“t”输入的数据,维度为(n_x, m)
a_prev -- 时间步“t - 1”的隐藏隐藏状态,维度为(n_a, m)
parameters -- 字典,包含了以下内容:
Wax -- 矩阵,输入乘以权重,维度为(n_a, n_x)
Waa -- 矩阵,隐藏状态乘以权重,维度为(n_a, n_a)
Wya -- 矩阵,隐藏状态与输出相关的权重矩阵,维度为(n_y, n_a)
ba -- 偏置,维度为(n_a, 1)
by -- 偏置,隐藏状态与输出相关的偏置,维度为(n_y, 1)
返回:
a_next -- 下一个隐藏状态,维度为(n_a, m)
yt_pred -- 在时间步“t”的预测,维度为(n_y, m)
cache -- 反向传播需要的元组,包含了(a_next, a_prev, xt, parameters)
"""
# Retrieve parameters from "parameters"
Wax = parameters["Wax"]
Waa = parameters["Waa"]
Wya = parameters["Wya"]
ba = parameters["ba"]
by = parameters["by"]
# compute next activation state using the formula given above
a_next = np.tanh(np.dot(Wax,xt) + np.dot(Waa,a_prev) + ba)
# compute output of the current cell using the formula given above
yt_pred = softmax(np.dot(Wya,a_next) + by)
# store values you need for backward propagation in cache
cache = (a_next, a_prev, xt, parameters)
return a_next, yt_pred, cache
对于整个RNN的前向传播:
整体的前向传播就是讲每一步输出的a作为下一步的输入一直向前传播。
Python代码如图:
# GRADED FUNCTION: rnn_forward
def rnn_forward(x, a0, parameters):
"""
根据图3来实现循环神经网络的前向传播
参数:
x -- 输入的全部数据,维度为(n_x, m, T_x)
a0 -- 初始化隐藏状态,维度为 (n_a, m)
parameters -- 字典,包含了以下内容:
Wax -- 矩阵,输入乘以权重,维度为(n_a, n_x)
Waa -- 矩阵,隐藏状态乘以权重,维度为(n_a, n_a)
Wya -- 矩阵,隐藏状态与输出相关的权重矩阵,维度为(n_y, n_a)
ba -- 偏置,维度为(n_a, 1)
by -- 偏置,隐藏状态与输出相关的偏置,维度为(n_y, 1)
返回:
a -- 所有时间步的隐藏状态,维度为(n_a, m, T_x)
y_pred -- 所有时间步的预测,维度为(n_y, m, T_x)
caches -- 为反向传播的保存的元组,维度为(【列表类型】cache, x))
"""
# Initialize "caches" which will contain the list of all caches
caches = []
# Retrieve dimensions from shapes of x and Wy
n_x, m, T_x = x.shape
n_y, n_a = parameters["Wya"].shape
# initialize "a" and "y" with zeros (≈2 lines)
a = np.zeros((n_a,m,T_x))
y_pred = np.zeros((n_y,m,T_x))
# Initialize a_next (≈1 line)
a_next = a0
# loop over all time-steps
for t in range(T_x): # T_x为时间,表示按照时间前进
# Update next hidden state, compute the prediction, get the cache (≈1 line)
a_next, yt_pred, cache = rnn_cell_forward(x[:,:,t],a_next,parameters)
# Save the value of the new "next" hidden state in a (≈1 line)
a[:,:,t] = a_next
# Save the value of the prediction in y (≈1 line)
y_pred[:,:,t] = yt_pred
# Append "cache" to "caches" (≈1 line)
caches.append(cache)
# store values needed for backward propagation in cache
caches = (caches, x)
return a, y_pred, caches
对于神经网络都有其反向传播,RNN当然也有其对应的反向传播,如图所示:
python实现:
def rnn_cell_backward(da_next, cache):
"""
实现基本的RNN单元的单步反向传播
参数:
da_next -- 关于下一个隐藏状态的损失的梯度。
cache -- 字典类型,rnn_step_forward()的输出
返回:
gradients -- 字典,包含了以下参数:
dx -- 输入数据的梯度,维度为(n_x, m)
da_prev -- 上一隐藏层的隐藏状态,维度为(n_a, m)
dWax -- 输入到隐藏状态的权重的梯度,维度为(n_a, n_x)
dWaa -- 隐藏状态到隐藏状态的权重的梯度,维度为(n_a, n_a)
dba -- 偏置向量的梯度,维度为(n_a, 1)
"""
# Retrieve values from cache
(a_next, a_prev, xt, parameters) = cache
# Retrieve values from parameters
Wax = parameters["Wax"]
Waa = parameters["Waa"]
Wya = parameters["Wya"]
ba = parameters["ba"]
by = parameters["by"]
# compute the gradient of tanh with respect to a_next (≈1 line)
# 计算tanh相对于a_next的梯度.
dtanh = (1 - np.square(a_next)) * da_next
# compute the gradient of the loss with respect to Wax (≈2 lines)
dxt = np.dot(Wax.T,dtanh)
dWax = np.dot(dtanh,xt.T)
# compute the gradient with respect to Waa (≈2 lines)
da_prev = np.dot(Waa.T,dtanh)
dWaa = np.dot(dtanh,a_prev.T)
# compute the gradient with respect to b (≈1 line)
dba = np.sum(dtanh,axis = 1,keepdims=True)
# 必须加keepdims = True,这样可以保证输出为矩阵形式
# Store the gradients in a python dictionary
gradients = {"dxt": dxt, "da_prev": da_prev, "dWax": dWax, "dWaa": dWaa, "dba": dba}
return gradients
3、双向RNN
我们知道,对于一句话“他来自__,他的国家首都是北京”,这句话空格处不止受到前半句的影响,而且受到后半段的影响,并且后半段的影响更为严重。之前的基本RNN结构只能处理受到前面的影响,而不能处理来自后面的影响,故这就需要一种能够处理前后影响的RNN结构----双向RNN。
结构如图所示:
计算方法与单向类似,只是多了反向计算。
4、深层双向RNN
之前介绍的RNN都只有一个隐藏层,将两个或两个以上的隐藏层堆起来就实现了深层RNN。
5、传统RNN的缺点
- 梯度爆炸:RNN梯度消失是因为激活函数tanh函数的倒数在0到1之间,反向传播时更新前面时刻的参数时,当参数W初始化为小于1的数,则多个(tanh函数’ * W)相乘,将导致求得的偏导极小(小于1的数连乘),从而导致梯度消失。
- 梯度消失:当参数初始化为足够大,使得tanh函数的倒数乘以W大于1,则将导致偏导极大(大于1的数连乘),从而导致梯度爆炸。