上次我们介绍了神经网络的基本原理,其中举了一个非常简单的用神经网络进行异或运算的例子。这个网络接受两个输入(1或0),最终输出1或0。我们的异或网络有一个特性,当前的输入和以往的输入无关。也就是说,给定输入(1, 0),输出永远是1,至于上一个输入是不是也是(1, 0),还是(0, 1),还是别的什么,完全不影响当前输入的运算结果。从函数式编程的角度来说,这个异或网络是一个纯函数。
实际工程中的神经网络当然不会这么简单,但是基本结构是类似的。接受一个或多个输入,通过多层神经网络的运算,最终输出一个结果。
那么,这样的结构会不会有什么不足呢?
就编程而言,我们知道,纯函数是有很多不足的,对于很多实际问题,副作用是很重要的。一些纯函数编程语言,通过各种绕弯的方式把副作用“藏”起来,但是,相应地,这些语言在建模一些实际问题时就不那么直接了。
同理,某些问题也需要神经网络具备一些副作用。例如,字符识别问题。显然,单个字符的识别,和前后的字符都是相关的。再比如,处理视频,视频的每一帧,和前后帧也是相关的。上面两个例子的共性,是处理输入序列。
那么,我们怎样才能让神经网络记住历史信息呢?
如果是一般的程序语言,那有很多方法。最粗暴的做法就是声明几个全局变量,让这些全局变量记住某些状态。但是,神经网络里并没有全局变量这种东西。那该怎么办呢?
如果我们意识到,全局变量其实是函数的隐含输入, 那么答案其实很简单。我们将上一个状态作为函数的输入,然后除了返回输出值外,额外返回一个新的状态,并将这个状态传递给下一个函数。通过一连串函数的接力,不依赖全局变量,实现了状态的“保存”。
例如,在一般的程序语言中,取随机数的函数大概是这个样子的:
# 用全局变量存储随机数种子
seed = 0
def random(section):
global seed
seed = next_random(seed)
output = process(seed, section)
return output
在纯函数语言里会变成这样:
# 将状态作为输入传递
def random(section, old_seed):
seed = next_random(old_seed)
output = process(seed, section)
# 额外返回状态
return (output, seed)
我们看到,在纯函数语言中,状态被转化为一系列函数的输出。类似地,神经网络可以将输入序列转化为状态序列输出。这样的神经网络,称为循环神经网络(Recurrent Neural Network),简称RNN。
rnn = RNN(D, H)
这里的D代表输入序列,H代表状态序列。输入序列D被转化为状态序列H。
h = rnn.next(h0, x)
这里h0是初始状态序列,x是输入序列。返回的输出h为包含时序信息的状态序列。
下一步运算之间使用上一步产生的状态序列:
h = rnn.next(x)
h的具体计算方法为:
h[t] = tanh(Wh(h[t- 1]) + Wx(x[t]) + b)
其中,Wh是记录状态之间连接的矩阵,Wx是记录输入与状态之间连接的矩阵,b是偏置。tanh函数的选取是经验的结果,在实践中发现,tanh的效果比较好。
如果我们把循环神经网络展开的话,我们会得到一串神经网络,每一个网络会把它的输出传递到下一个网络中:
展开图清楚地表明了RNN的结构和序列数据的密切关系。另外,我们可以看到,这一串的神经网络,神似前述纯函数语言中通过一串函数传递状态的做法。
以上就是循环神经网络的基本原理。实际工程中,基本不用上面的原生循环神经网络,而是使用经过改进的LSTM(Long Short-Term Memory,长短期记忆网络)。
我们可以看到,原生RNN中状态的计算方法是比较简单的。这就带来一个问题,如果相关的两个输入,在输入序列中的距离较远,那最终的效果就比较差了。当然,有时候,调整一些参数,可以将距离较远的相关输入(长时依赖关系)联系起来,但大多数时候,这很困难。
因此,我们考虑给我们的循环网络加入长期记忆功能。在状态之外,增加一个结构(不妨把这个结构叫做细胞)。然后,通过某些阀门,控制何时遗忘信息,何时将信息保存到细胞内。
# LSTM和原生循环网络的总体结构是一致的。
lstm = nn.LSTM(D, H)
# 输入门,决定是否将信息传入细胞
i[t] = sigmoid(ai[t])
# 遗忘门,决定是否遗忘信息
f[t] = sigmoid(af[t])
# 输出门,决定细胞状态中哪些信息将被输出
o[t] = sigmoid(ao[t])
# 使用`tanh`调整输出结果(确保输出值在-1和1之间)
g[t] = tanh(ag[t])
# 细胞状态既取决于上一个时刻的细胞状态,
# 也取决于输入门和遗忘门的作用
c[t] = f[t] * c[t - 1] + i[t] * g[t]
# 状态取决于输出门和细胞状态
h[t] = o[t] * tanh(c[t])
从上面我们可以看到,LSTM和原生RNN的基本原理是一样的,LSTM在RNN的基础上增加了一个细胞状态而已。
机器人网原创文章,未经授权禁止转载。详情见转载须知
本文来自机器人网,如若转载,请注明出处:https://www.jqr.com/news/008527