RNN神经网络_循环神经网络的基本原理[通俗易懂]

RNN神经网络_循环神经网络的基本原理[通俗易懂]RNN神经网络RNN网络是一种基础的多层反馈神经网络,该神经网络的节点定向连接成环

1.RNN神经网络

RNN网络是一种基础的多层反馈神经网络,该神经网络的节点定向连接成环。相比于前馈神经网络,该网络内部具有很强的记忆性,它可以利用内部的记忆来处理任意时序的输入序列。

循环网络的概念

循环网络是一种对序列数据有较强的处理能力的网络。在网络模型中不同部分进行权值共享使得模型可以扩展到不同样式的样本,比如CNN网络中一个确定好的卷积核模板,几乎可以处理任何大小的图片。将图片中分成多个区域,使用同样的卷积核对每一个区域进行处理,最后可以获得非常好的处理结果。
同样的,循环网络使用类似的模块(形式上相似)对整个序列进行处理,可以将很长的序列进行泛化,得到需要的结果。(因为循环神将网络可以将序列进行较好的处理,且时间同样也是有序数列,在实际应用中,RNN循环神经网络对于处理时序数据具有天然的优势。)

网络的结构

类似于CNN网络的卷积核概念,在RNN网络中也有类似的概念“计算图”。计算图实质上是用来描述一个函数方法的模块,也等同于一个数据处理“盒子”。
每一个计算图有输入和输出的数据,同时在某一时刻的计算图的数据对下一时刻计算图的数据造成影响。
计算图可以按照序列展开成为一个具有深度的链结构,在这个深度结构中,也构成了参数的共享。
其中最为普遍的系统形式为:
在这里插入图片描述
利用这个公式可以将每一个序列的元素进行连接处理,将公式进行展开可以得到类似于下面的形式:(看上去很像迭代)
在这里插入图片描述
进行展开后可以得到第t个节点(或第t个时刻)的处理结果。在式子中有不变的参数θ,通过保证θ可以保证整个网络进行权值共享。

RNN网络

在这里插入图片描述

RNN网络中加入了其他参数的输入。即在某一节点或者某一时刻t,输入网络的还有外界信号x^t,这样将上式进行重写:
在这里插入图片描述
因为网络的参数通过循环不断地传入网络,这一点类似于人的思考过程,RNN网络可以保持信息的持久性,这一点是其他神经网络中不具备的。也是因为这个特性,RNN网络可以很好地对时序模块进行处理。
对于网络的理解,可以理解成对于通过一个网络,每次传入的参数不同。传入参数的一部分是某一时刻t的新参数,另一部分是上一时刻t-1已经获取到的参数。

RNN拓扑结构

图中的w是指每个时间点之间的权重矩阵,RNN之所以能解决序列问题,是因为它可以记住每一时刻的信息,每一时刻的隐藏层的输出不仅由输入决定,也由上一时刻隐藏层的输出有关。(要注意的是:在整个训练过程中,每一时刻所用的权重都是同一个的w
在这里插入图片描述
通过将网络展开,可以看到对于一个序列x^t,可以使用RNN循环网络进行处理,网络具有联想能力,可以将之前的信息加以保存,并在之后的循环中进行调用。
注意:这里的h^(x)和后面的要做不同的理解(理解为隐藏层的输出会更好,因为我参考的网站上是写的是激活函数,所以图中这样写了)

RNN的记忆能力

用实例理解RNN的记忆能力。将RNN网络的拓扑进行简化:

在这里插入图片描述
使用RNN循环网络对一段话进行预测,“我是中国人,我爱中国,我的母语是()”。对于网络来说,假设中国人的信息是在x^ (2)输入,括号中的内容在 o^n输出。这里涉及到联想功能,对于一个训练好的网络,输出信息时网络会根据前文的信息进行判断输出的类型和内容。
RNN网络存在一些局限性,输出的信息还有很多依赖因素。在理想的情况下,RNN网络的输出精度不收循环次数的增加而降低。但是在实际应用中,当输入关键信息的位置和输出信息的位置之间的节点变得非常长之后,RNN神经网络会丧失链接到 判断输出的关键信息位置 的能力。
在实际运用中国,对于循环神经网络,人们经常应用的是RNN网络的变体,例如LSTM网络。在LSTM网络中没有因为位置远近而降低输出精度的问题。

2.RNN变体LSTM网络

LSTM网络,即长短期记忆网络。因为该网络的结构,该网络适合处理序列中间隔和延时较长的事件。

LSTM细胞体结构

在基础RNN网络中,之所以不能解决长期依赖问题,是因为RNN处理数据的计算图(细胞体)结构简单,只有一个非常简单的结构,比如说只进行一个sigmoid函数的数据处理,这使得网络很难对长时间信息进行记忆。同时网络中对于哪些信息需要舍弃,哪些信息需要保留也无法做出很好的判断。
在LSTM网络中,通过将细胞体结构复杂化,在算法中加入了判断信息是否有用的处理器。在网络中加入了三道门,分别叫做“遗忘门、输入门、输出门”。信息进入网络中,网络会根据一定规则来判断信息是否有用,用用的信息加以保留,无用的信息进行遗忘。
以下是LSTM网络涉及到三个门的操作:

  • 遗忘门:为网络中的第一个门,网络对信息进行判断,决定信息的弃留。将遗忘门作为网络第一个门,是因为需要将网络信息进行过滤,放在之后容易对新加入的信息进行错误处理。在遗忘门中信息经过一个sigmoid函数处理得到新的信息f1^(t),这个网络层决定了信息的保存还是丢弃。
    在这里插入图片描述

  • 输入门:输入门分为两部分,首先绿线选择什么值需要进行更新,黄色线确定什么值进行传递。这两步用如下公式执行:
    绿线部分:
    在这里插入图片描述

黄线部分:
在这里插入图片描述
现在已经将网络信息中需要更新的旧信息予以遗忘,新信息确定好并获取其内容,之后是将信息进行整合,更新细胞体状态,并且得到细胞体的一部分输出:
在这里插入图片描述

  • 输出门:最后是将信息进行输出,采用的是如下计算方法:
    在这里插入图片描述
    经过输出门的处理,将细胞信息进行了选择,输出了我们需要的信息。

以下是LSTM细胞体一件图的形式呈现:
在这里插入图片描述

3.RNN其他形式变体结构

Peephole网络

这个变体由Gers和Schmidhuber在2000年提出,该网络将细胞体的状态作为网络神经层的一部分输入。整合了细胞状态的输入,使得网络判断可依据的信息变多,这种优化方法也称作是窥孔优化

在这里插入图片描述

Coupled网络

在这个网络中,细胞状态的遗忘是相对的,遗忘门遗忘了一些信息后,相应的在输入门就会生成一些信息。未被遗忘的信息传输到输入门中:
在这里插入图片描述
在这里插入图片描述

GRU网络

该网络是简化版的LSTM网络,在这个网络中将细胞状态的概念去掉,通过一条输出链在每时刻和输入信息进行整合作为输出并传输到下一时刻。正是因为这样的结构,使得GRU网络收敛的时间和需要的信息量的要求上都优于LSTM网络。
在这里插入图片描述

4.RNN网络传播过程

  1. RNN网络的正向传播:
    隐藏层的输出:
    在这里插入图片描述
    通过和参数矩阵相乘得到输出层的公式:
    在这里插入图片描述
    预测值:
    在这里插入图片描述
  2. RNN网络反向传播算法:
    思路是通过梯度下降算法一轮 轮地迭代,得到合适的RNN模型参数U、W、V、b、c。由于是基于时间的反向传播,所以RNN的反向传播有时也叫BPTT。RNN网络有一个特点就是权值共享,即U、W、V、b、c这些参数在序列的各个位置是共享的,反向传播更新的是相同的参数。
    对于RNN,在序列的每个位置都有损失函数,因此最终的损失函数L为:
    在这里插入图片描述
    其中V,c的梯度计算比较简单:
    在这里插入图片描述

但是W,U,b梯度的计算比较复杂,从整个RNN 的时间序列来看,我们可以发现,在每个时刻里W,U,b会对这个时刻的h(t),y_hat(t) 作出“贡献”,进而对这个时刻的同时也会对这个时刻的L(t)作出“贡献”。同时,由于对这个时刻的 h(t) 作出“贡献”,进而会对t+1 时刻的h(t+1)作出“贡献”,从而对t+1 时刻的L(t+1) 作出“贡献”。而最后一个时刻的h(t) 就不会传递到下一时刻了(因为这是最后一个时刻)。因此,从时刻 1 到时刻T-1 ,W,U,b 都会对损失L“贡献”两次,最后一个时刻只“贡献”一次。因为从时刻 1 到时刻T-1 都“贡献”了两次,所以这部分的梯度会是两部分之和。而最后一时刻的梯度只有一部分,所以最后一时刻的h(t)的梯度如下:
在这里插入图片描述

从δ^(t+1)递推δ ^(t),梯度由两部分组成:
在这里插入图片描述

得到了δ ^(t),再对W,U,b求导就简单了:
在这里插入图片描述

  1. RNN的损失函数:最常使用的是交叉熵损失函数
    在这里插入图片描述

实例:利用RNN进行简单加法运算

首先,这个网络中用到的激活函数是sigmoid函数,所以sigmoid函数及其求导sigmoid∗(1−sigmoid)定义如下:

def sigmoid(x):
    y = 1/(1+np.exp(-x))
    return y
def dersigmoid(x):
    y = x*(1-x)
    return y

网络具有三层结构:输入层、隐藏层、输出层。先将网络三层结构的节点数进行设定,在之后的操作中三个矩阵中的数值不断随着网络的学习进行更改,而全零矩阵为的是进行每一次学习暂存结果的记录。每一次循环完之后都会重置为零矩阵:

step = 0.1
ipnumber = 2
hdnumber = 32
opnumber = 2

neu_i2h = 2*np.random.random((ipnumber,hdnumber)) - 1
neu_h2o = 2*np.random.random((hdnumber,opnumber)) - 1
neu_h2h = 2*np.random.random((hdnumber,hdnumber)) - 1
# zeros_like()函数返回传入数组相同形状的全零矩阵
neu_i2hN = np.zeros_like(neu_i2h)
neu_h2oN = np.zeros_like(neu_h2o)
neu_h2hN = np.zeros_like(neu_h2h)

训练数据集初始化
为了方便,将网络输入的数值转换为2进制

i2b = { 
   }
bdigit = 8
MAXnumber = pow(2,bdigit)
# unpackbits()将数据转化为2进制
bin=np.unpackbits(np.array([range(MAXnumber)],dtype=np.uint8).T,axis=1)
for i in range(MAXnumber):
    i2b[i] = bin[i]

设定加法问题:

a_decimal = np.random.randint(MAXnumber / 2)
b_decimal = np.random.randint(MAXnumber / 2)
c_decimal = a_decimal + b_decimal
a = i2b[a_decimal]
b = i2b[b_decimal]
c = i2b[c_decimal]

预测值初始化:将预测值进行初始化,包括网络的输出、误差值,输出层的导数,这几个值进行存储,为以后训练和反向传播做准备:

# 网络预测值的二进制数组
binary = np.zeros_like(c)
# 误差值
aError = 0
#导数值
oplayer_der = list()
hdlayer_val = list()
hdlayer_val.append(np.zeros(hdnumber)) 

训练神经网络

for locate in range(bdigit):
	# 从2进制的低位到高位检索整个二进制数
    X = np.array([[a[bdigit - locate - 1],b[bdigit - locate - 1]]])
    Y = np.array([[c[bdigit - locate - 1]]]).T
	# 隐藏层:sigmoid(UX+Wh)
    hdlayer = sigmoid(np.dot(X,neu_i2h) + np.dot(hdlayer_val[-1],neu_h2h))
    # 输出层:sigmoid(hV)
    oplayer = sigmoid(np.dot(hdlayer,neu_h2o))
	# 误差值
    oplayer_error = Y - oplayer
    # 输出层的导数
    oplayer_der.append((oplayer_error)*dersigmoid(oplayer))
    # 累加误差的绝对值
    aError += np.abs(oplayer_error[0])
    # 预测值
    binary[bdigit - locate - 1] = np.round(oplayer[0][0])
    # 隐藏层的值
    hdlayer_val.append(copy.deepcopy(hdlayer))

反向传播:

# 
Fhdlayer_dels = np.zeros(hdnumber)
for locate in range(bdigit):
    X = np.array([[a[locate],b[locate]]])
    # t时刻的隐藏层
    hdlayer = hdlayer_val[-locate-1]
    # t-1时刻的隐藏层
    hdlayer_pre = hdlayer_val[-locate-2]
	# 输出层的导数
    oplayer_dels = oplayer_der[-locate-1]
    # 隐藏层的导数
    hdlayer_dels = (Fhdlayer_dels.dot(neu_h2h.T) + oplayer_dels.dot(neu_h2o.T)) * dersigmoid(hdlayer)
	# 学习值的暂存
    neu_h2oN += np.atleast_2d(hdlayer).T.dot(oplayer_dels)
    neu_h2hN += np.atleast_2d(hdlayer_pre).T.dot(hdlayer_dels)
    neu_i2hN += X.T.dot(hdlayer_dels)
    Fhdlayer_dels = hdlayer_dels

权值更新

neu_i2h += neu_i2hN * step
neu_h2o += neu_h2oN * step
neu_h2h += neu_h2hN * step

neu_i2hN *= 0
neu_h2oN *= 0
neu_h2hN *= 0

完整代码

# RNN网络是一种基础的多层反馈神经网络,该神经网络的节点定向连接成环
# 相对于前馈神经网络,该网络内部有很强的记忆性,可以利用其内部记忆来处理任一时许的输入序列
import numpy as np
step = 0.1
# 输入层的参数
ipnumber = 2
# 隐藏层节点数
hdnumber = 32
# 输出层节点数
opnumber = 2

# 输入层到隐藏层之间的参数矩阵
nue_i2h = 2*np.random.random((ipnumber, hdnumber))
# 隐藏层到输出层之间的参数矩阵
nue_h2o = 2*np.random.random((hdnumber, opnumber))-1
# 隐藏层到隐藏层之间的权重矩阵
nue_h2h = 2*np.random.random((hdnumber, hdnumber))-1
# zeros_like()返回一个和输入矩阵同形状的全零矩阵
# 设置三个用来存储网络更新权值的暂存值,
# 这些矩阵在每一次循环之后都会被重置为零矩阵
nue_i2hN = np.zeros_like(nue_i2h)
nue_h2oN = np.zeros_like(nue_h2o)
nue_h2hN = np.zeros_like(nue_h2h)

# 训练数据集初始化,为了便于神经网络更精确的计算,将输入神经网络的数值转换成二进制数字
# i2b的字典中储存着所有范围内的2的8次方里的二进制情况
i2b = { 
   }
bdigit = 8
MAXnumber = pow(2, bdigit)
# unpackbits函数将整数化为二进制
bin = np.unpackbits(np.array([range(MAXnumber)], dtype=np.uint8).T, axis=1)
for i in range(MAXnumber):
    i2b[i] = bin[i]
# 训练神经网络(训练的次数越多网络拟合效果越好)
# 这里给神经网络一个简单的加法问题,目的是让神经网络通过20000次的训练能够学会简单的加法
def sigmoid(x):
    return 1/(1+np.exp(-x))
def dersigmoid(x):
    return x*(1-x)
# 设定加法问题,训练好的网络可以通过一步步权值进行处理最终得到正确答案
# 设定加数和和,两个加数应不大于2的8次方的一般(为防止和的值溢出)
for i in range(20000):
    a_decimal = np.random.randint(MAXnumber/2)
    b_decimal = np.random.randint(MAXnumber/2)
    c_decimal = a_decimal+b_decimal
    # print(a_decimal, b_decimal, c_decimal)
    a = i2b[a_decimal]
    b = i2b[b_decimal]
    c = i2b[c_decimal]
    # 预测值初始化,设定几个数组,将得到的预测值、误差值、输出层导数进行存储。为了之后的训练和反向传播作准备
    # 网络预测值二进制数组
    binary = np.zeros_like(c)
    # 误差值
    aError = 0
    # 导数值
    oplayer_der = list()
    hdplayer_val = list()
    hdplayer_val.append(np.zeros(hdnumber))

    # 训练神经网络
    # 1. 在训练神经网络过程中首先将数据进行转换
    # 2. 数据通过层计算权值
    # 3. 将权值进行存储以便之后的反向传播运算
    import copy
    for locate in range(bdigit):
        # 从2进制的第一位开始检索
        X = np.array([[a[bdigit - locate - 1], b[bdigit - locate - 1]]])
        Y = np.array([[c[bdigit - locate - 1]]]).T


        # 隐藏层计算:输入参数和参数矩阵相乘,上一隐藏层的输出和权重矩阵相乘
        hdlayer = sigmoid(np.dot(X, nue_i2h)+np.dot(hdplayer_val[-1], nue_h2h))
        oplayer = sigmoid(np.dot(hdlayer, nue_h2o))
        # 误差分析
        oplayer_error = Y - oplayer  # 误差值
        oplayer_der.append((oplayer_error)*dersigmoid(oplayer))  # 每时刻的导数值
        aError +=np.abs(oplayer_error[0])  # 累加误差绝对值
        # 返回浮点数的四舍五入值
        binary[bdigit - locate - 1] = np.round(oplayer[0][0])
        # deepcopy深复制,对新对象的改变不会影响原有对象 hdlayer的shape:(1,32)
        hdplayer_val.append(copy.deepcopy(hdlayer))

    # 进行反向传播(得到网络的输出值,接下来进行反向传播)
    Fhdlayer_dels = np.zeros(hdnumber)

    for locate in range(bdigit):
        X = np.array([[a[locate], b[locate]]])  # 检索数据
        hdlayer = hdplayer_val[-locate-1]  # 从数据中取出当前隐藏层
        hdlayer_pre = hdplayer_val[-locate-2]  # 从数据中取出前一个隐藏层
        # 输出层的导数
        oplayer_dels = oplayer_der[-locate-1]
        # 隐藏层导数
        hdlayer_dels = (Fhdlayer_dels.dot(nue_h2h.T)+oplayer_dels.dot(nue_h2o.T))*dersigmoid(hdlayer)
		# V的导数值
        nue_h2oN += np.atleast_2d(hdlayer).T.dot(oplayer_dels)
        # W的导数值
        nue_h2hN += np.atleast_2d(hdlayer_pre).T.dot(hdlayer_dels)
        # U的 导数值
        nue_i2hN += X.T.dot(hdlayer_dels)
        # t+1时刻的δ(t+1)
        Fhdlayer_dels = hdlayer_dels

    # 进行权值更新,这里权值更新用的是加法是因为上面计算计算输出层和隐藏层的导数使用的是oplayer_error = Y - oplayer,而RNN的反向传播算法中,误差值是y^hat-y
    nue_i2h += nue_i2hN * step
    nue_h2o += nue_h2oN * step
    nue_h2h += nue_h2hN * step

    nue_i2hN *= 0
    nue_h2oN *= 0
    nue_h2hN *= 0

print("Error:" + str(aError))
print("Predicted:" + str(binary))
print("True:" + str(c))
value = 0
for index, x in enumerate(reversed(binary)):
    value += x*pow(2, index)
print(str(a_decimal) + " + " + str(b_decimal) + " = " + str(value))

今天的文章RNN神经网络_循环神经网络的基本原理[通俗易懂]分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/69022.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注