jlearning.cn

回声状态网络(ESN)基础教程

最近在看回声状态网络(Echo State Network)的内容,注意到中文搜索引擎搜不到关于有关Echo State Network通俗的讲解,打算写一下关于ESN的一个基本教程。本文先用一小段简单介绍ESN是什么,然后用公式来表示这种网络,并说明他的优缺点,最后用一个可执行的简单例子来演示一下ESN的效果。

由于本人能力有限,如在阅读过程中有任何疑问或者发现错误请在评论中指出。

ESN是RNN的一种,也是由输入层,隐藏层,输出层组成,并且在隐藏层到隐藏层之间有一个连接,用来保留前面时刻留下的信息。不同于RNN,ESN的输入层到隐藏层、隐藏层到隐藏层的连接权值是随机初始化,并且固定不变。在训练的过程中,我们只需要去训练隐藏层到输出层的连接权值。这就变成了一个线性回归问题,所以ESN训练起来非常快。

微信截图_20170529161715

ESN的神经网络如图所示,储备池就是常规神经网络中的隐藏层。输入层到储备池的连接为Win,储备池到下一个时刻储备池状态的连接为W,储备池到输出层的连接为Wout。另外还有一个前一时刻的输出层到下一个时刻的储备池的连接Wback,这个连接不是必须的(图中用虚线表示),由于这是一个入门级的基础教程,所以在后面的公式和例子代码中都不会涉及这一个连接。

图中所示,$t$时刻的输入为$u(t)$,一共K个节点,储备池状态为$x(t)$,N个节点,输出为$y(t)$,L个节点。

每一个时刻输入$u(t)$,储备池都会更新状态,储备池的状态更新方式为:

$$x(t+1)=f(W_{in}u(t+1)+Wx(t))$$

这个式子里,$W_{in}$和$W$都是在最初建立网络的时候随机初始化的,并且固定不变。$u(t+1)$是这个时刻的输入,$x(t)$是上一个时刻的储备池状态,在t=0时刻可以用0初始化。$f$是一个激活函数,通常使用$tanh$。

在建模的时候,和一般的神经网络一样,会在连接矩阵上加上一个偏置量,所以输入的$u$是一个长度为1+K的向量,$W_{in}$是一个[1+k,N]的矩阵,$x$是一个长度为N的向量,$W$是一个[N,N]的矩阵。

回声状态网络的输出方式为:

$$y(t)=W_{out}[1;u(t);x(t)]$$

有了储备池状态,再确定了ESN的输出方式之后,就可以根据目标输出$y^{target}$来确定$W_{out}$来让$y(t)$和$y^{target}(t)$的差距尽可能的小。这是一个简单的线性回归问题,计算的方法有很多种,不再赘述。

微信截图_20170529170125

到这里,我们就完成了ESN的训练工作。整个网络只需要训练$W_{out}$,所以它的训练过程非常快,这是ESN的优点之一。另外,对于一维时序数列的处理和预测,ESN有很好的优势。但对于高维的时序数列,比如说视频帧处理,ESN就不太能胜任了。

为了让这个网络能够正常的运转,还有一些地方是需要注意的:

  1. 之所以叫回声状态网络,是因为前面时刻输入的信息会通过$W$回回荡在储备池中,就像回声一样。为了避免储备池状态爆炸,$W$的特征值必须要小于等于1。这也就引入了ESN中谱半径的概念:$W$的最大特征值。
  2. 由于网络中只有$W_{out}$是可变的,为了尽可能多的表示不同的数据规律,$W$必须要设置的非常大,才能从中找出各种不同的特征进行输出。另一方面,$W$的稀疏性也很重要,Hinton在多伦多大学的公开课里解释是:

建立一个松散的连接,这样某一信息可以在网络中的一小部分回荡,而不会迅速的传播到其他部分。

我不是特别的理解。希望有人解释一下。

最后引用一个例子来彻底理解最基本的ESN。

在页面中下载python源码和数据集,在python2.7环境中运行。

数据就是一维的,代码中每次输入长度为1,预测数据中后一位的值,当然长度也是1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# -*- coding: utf-8 -*-
"""
A minimalistic Echo State Networks demo with Mackey-Glass (delay 17) data
in "plain" scientific Python.
by Mantas LukoĹĄeviÄ?ius 2012
http://minds.jacobs-university.de/mantas
"""
from numpy import *
from matplotlib.pyplot import *
import scipy.linalg
# load the data
#前2000个数据用来训练,2001-4000的数据用来测试。训练数据中,前100项用来初始化储备池,以让储备池中形成良好的回声之后再开始训练。
trainLen = 2000
testLen = 2000
initLen = 100
data = loadtxt('MackeyGlass_t17.txt')
# plot some of it
figure(10).clear()
plot(data[0:1000])
title('A sample of data')
# generate the ESN reservoir
inSize = outSize = 1
resSize = 1000
a = 0.3 # leaking rate 可以看作储备池更新的速度,可不不加,即设为1.
random.seed(42)
#随机初始化 Win 和 W
Win = (random.rand(resSize,1+inSize)-0.5) * 1
W = random.rand(resSize,resSize)-0.5
#对W进行防缩,以满足稀疏的要求。
# Option 1 - direct scaling (quick&dirty, reservoir-specific):
#W *= 0.135
# Option 2 - normalizing and setting spectral radius (correct, slow):
print 'Computing spectral radius...',
rhoW = max(abs(linalg.eig(W)[0]))
print 'done.'
W *= 1.25 / rhoW
# allocated memory for the design (collected states) matrix
X = zeros((1+inSize+resSize,trainLen-initLen))
# set the corresponding target matrix directly
Yt = data[None,initLen+1:trainLen+1]
#输入所有的训练数据,然后得到每一时刻的输入值和储备池状态。
# run the reservoir with the data and collect X
x = zeros((resSize,1))
for t in range(trainLen):
u = data[t]
x = (1-a)*x + a*tanh( dot( Win, vstack((1,u)) ) + dot( W, x ) )
if t >= initLen:
X[:,t-initLen] = vstack((1,u,x))[:,0]
#使用Wout根据输入值和储备池状态去拟合目标值,这是一个简单的线性回归问题,这里使用的是岭回归(Ridge Regression)。
# train the output
reg = 1e-8 # regularization coefficient
X_T = X.T
Wout = dot( dot(Yt,X_T), linalg.inv( dot(X,X_T) + \
reg*eye(1+inSize+resSize) ) )
#Wout = dot( Yt, linalg.pinv(X) )
#使用训练数据进行前向处理得到结果
# run the trained ESN in a generative mode. no need to initialize here,
# because x is initialized with training data and we continue from there.
Y = zeros((outSize,testLen))
u = data[trainLen]
for t in range(testLen):
x = (1-a)*x + a*tanh( dot( Win, vstack((1,u)) ) + dot( W, x ) )
y = dot( Wout, vstack((1,u,x)) )
Y[:,t] = y
# generative mode:生成模型
u = y
## this would be a predictive mode:预测模型
#u = data[trainLen+t+1]
# compute MSE for the first errorLen time steps
errorLen = 500
mse = sum( square( data[trainLen+1:trainLen+errorLen+1] - Y[0,0:errorLen] ) ) / errorLen
print 'MSE = ' + str( mse )
# plot some signals
figure(1).clear()
plot( data[trainLen+1:trainLen+testLen+1], 'g' )
plot( Y.T, 'b' )
title('Target and generated signals $y(n)$ starting at $n=0$')
legend(['Target signal', 'Free-running predicted signal'])
figure(2).clear()
plot( X[0:20,0:200].T )
title('Some reservoir activations $\mathbf{x}(n)$')
figure(3).clear()
bar( range(1+inSize+resSize), Wout.T )
title('Output weights $\mathbf{W}^{out}$')
show()

实验结果是再生成模型的情况下,最后得到的均方差为 2.3*10^-5。

生成的结果与测试结果对比图:

微信截图_20170529172248

再预测模型中,均方差为6.95*10^-8。

预测值与真实值的对比图为:

微信截图_20170529172425

效果还是肥肠好的!

参考文献:

[1] Jaeger H. The “echo state” approach to analysing and training recurrent neural networks-with an erratum note[J]. Bonn, Germany: German National Research Center for Information Technology GMD Technical Report, 2001, 148(34): 13.

[2] Lukoševičius M. A practical guide to applying echo state networks[M]//Neural networks: Tricks of the trade. Springer Berlin Heidelberg, 2012: 659-686.

[3] 罗熊, 黎江, 孙增圻. 回声状态网络的研究进展[J]. 北京科技大学学报, 2012, 34(2):217-222.

[4] http://minds.jacobs-university.de/mantas/code

[5] https://zh.coursera.org/learn/neural-networks/lecture/s1bdp/echo-state-networks-9-min