回声状态网络(Echo State Network)

青春壹個敷衍的年華 提交于 2020-02-12 02:15:35

洛伦兹系统

数据生成见博文:混沌系统数值模拟
在这里插入图片描述
在这里插入图片描述

回声状态网络

动力学模型

  • 状态更新函数
    在这里插入图片描述
  • 输出函数
    在这里插入图片描述
    结构下图所示,虚线连接不是必要的
    在这里插入图片描述

python 实现

所需的包

import networkx as nx
import json
from decimal import Decimal
from collections import OrderedDict

配置文件

# if config file not exists, use this default config
default_config = """{
  "input": {
    "nodes": 2,
    "functions": 
      [
        "lambda x: np.cos(32 * np.pi * x)",
        "lambda x: np.sin(32 * np.pi * x) + np.cos(128 * np.pi * x)"
      ],
    "length": 5000
  },
  "reservoir": {
    "start_node": 202,
    "end_node": 203,
    "step": 1,
    "degree_function": "lambda x: np.sqrt(x)",
    "sigma": 0.5,
    "bias": 1, 
    "leakage_rate": 0.3,
    "regression_parameter": 1e-8
  },
  "output": {
    "nodes": 2
  },
  "training": {
    "init": 1000,
    "train": 3000,
    "test": 2000,
    "error": 1999
  }
}
"""

Reservoir 类

  • 默认读取配置文件 ‘reservoir.config’,如果找不到配置文件就用上面的默认配置
class Reservoir:
    def __init__(self):
        config_file_name = 'reservoir.config'
        global config
        config = json.loads(default_config, object_pairs_hook=OrderedDict)
        
        # Input layer
        self.M = config["input"]["nodes"]
        self.input_len = config["input"]["length"]
        self.input_func = []
        dataset = []
        for i in range(self.M):
            self.input_func.append(eval(config["input"]["functions"][i]))
            dataset.append(self.input_func[i](
                np.arange(self.input_len) / self.input_len))
        self.dataset = np.array(list(zip(*dataset))).T # shape = (M, length)

        # Reservoir layer
        self.start_node = config["reservoir"]["start_node"]
        self.N = self.start_node
        self.step = config["reservoir"]["step"]
        self.end_node = config["reservoir"]["end_node"]
        self.degree_func = eval(config["reservoir"]["degree_function"])
        self.D = self.degree_func(self.start_node)
        self.sigma = config["reservoir"]["sigma"]
        self.bias = config["reservoir"]["bias"]
        self.alpha = config["reservoir"]["leakage_rate"]
        self.beta = config["reservoir"]["regression_parameter"]

        # Output layer
        self.P = config["output"]["nodes"]

        # Training relevant
        self.init_len = config["training"]["init"]
        self.train_len = config["training"]["train"]
        self.test_len = config["training"]["test"]
        self.error_len = config["training"]["error"]
  • 参数解释
参数 解释
M 输入节点数
N 隐层节点数,储备池中的单元数
P 输出节点数
D 网络平均度
start_node,step,end_node 搜索最佳隐层节点数时的起始节点数、步长、终止
beta、regression_parameter 岭回归的参数
sigma 随机初始化权重的分布范围[σ,σ][-\sigma,\sigma]
bias 加权求和再加偏置
alpha LIF的渗漏系数
  • 默认配置文件中定义了数据集,如需使用自己的数据集,使用如下代码
   def set_dataset(self, data):
       self.M = self.P = data.shape[0]
       self.dataset = data
  • 训练
变量 含义
r 每一时刻的隐层状态
S 预测目标
R 记录训练时间段的输入和状态
A 隐层网络的邻接矩阵
rho 原始 A 的谱半径

状态更新:
r(t+1)=(1α)r(t)+αtanh(Ar(t)+Winu(t)+b)r(t +1) = (1 - \alpha)r(t) + \alpha * \tanh(A * r(t) + W_{in} * u(t) + b)输出 vv 不止取决与状态,还直接和输入有关:
v=Wout1r(t+1)+Wout2u+Wout3bv=W_{out}^1 \cdot r(t+1)+W_{out}^2 \cdot u + W_{out}^3 \cdot b

    def train(self):
        # collection of reservoir state vectors
        self.R = np.zeros((1 + self.N + self.M, self.train_len - self.init_len))
        # collection of input signals
        self.S = np.vstack([x[self.init_len + 1: self.train_len + 1] for x in self.dataset])
        self.r = np.zeros((self.N, 1))
        np.random.seed(43) #42
        self.Win = np.random.uniform(-self.sigma,
                                     self.sigma, (self.N, self.M + 1))
        # TODO: the values of non-zero elements are randomly drawn from uniform dist [-1, 1]
        g = nx.erdos_renyi_graph(self.N, self.D / self.N, 42, True)
        # nx.draw(g, node_size=self.N)
        self.A = nx.adjacency_matrix(g).todense()
        # spectral radius: rho
        self.rho = max(abs(np.linalg.eig(self.A)[0]))
        self.A *= 1.25 / self.rho
        # run the reservoir with the data and collect r
        for t in range(self.train_len):
            u = np.vstack([x[t] for x in self.dataset])
            # r(t + \Delta t) = (1 - alpha)r(t) + alpha * tanh(A * r(t) + W_in * u(t) + bias)
            self.r = (1 - self.alpha) * self.r + self.alpha * np.tanh(np.dot(self.A,self.r) + np.dot(self.Win, np.vstack((self.bias, u))))
            if t >= self.init_len:
                self.R[:, [t - self.init_len]] = np.vstack((self.bias, u, self.r))[:, 0]
        # train the output
        R_T = self.R.T  # Transpose
        # W_out*r = s
        # W_out = (s * r^T) * ((r * r^T) + beta * I)
        self.Wout = np.dot(np.dot(self.S, R_T), np.linalg.inv(np.dot(self.R, R_T) + self.beta * np.eye(self.M + self.N + 1)))
  • 训练完后,以输出作为下一时刻输入,无限运行下去

    def _run(self):
        # run the trained ESN in alpha generative mode. no need to initialize here,
        # because r is initialized with training data and we continue from there.
        self.S = np.zeros((self.P, self.test_len))
        u = np.vstack([x[self.train_len] for x in self.dataset])
        for t in range(self.test_len):
            # r(t + \Delta t) = (1 - alpha)r(t) + alpha * tanh(A * r(t) + Win * u(t) + bias)
            self.r = (1 - self.alpha) * self.r + self.alpha * np.tanh(np.dot(self.A,
                      self.r) + np.dot(self.Win, np.vstack((self.bias, u))))
            s = np.dot(self.Wout, np.vstack((self.bias, u, self.r)))
            self.S[:, t] = np.squeeze(np.asarray(s))
            # use output as input
            u = s
        # compute Root Mean Square (RMS) error for the first self.error_len time steps
        self.RMS = []
        for i in range(self.P):
            self.RMS.append(sum(np.square(self.dataset[i, self.train_len+1: self.train_len+self.error_len+1] - self.S[i, 0: self.error_len])) / self.error_len)
  • 画图
    def draw(self):
        f, plots = plt.subplots(1, self.M, figsize=(self.M * 5, 5))
        plt.suptitle('N = ' + str(self.N) + ', Degree = %.5f' % (self.D))
        for i in range(self.M):
            p = plots[i]
            p.text(0.5, -0.1, 'RMS = %.15e' % self.RMS[i], size=10, ha="center", transform=p.transAxes)
            p.plot(self.S[i], label = 'prediction')
            p.plot(self.dataset[i][self.train_len + 1 : self.train_len + self.test_len + 1], label = 'input signal')
            p.legend(loc = 'upper right')
        plt.show()
  • 完整运行:训练+预测
    def run(self):
        with open('reservoir.output', 'a') as output:

            for i in range(self.start_node, self.end_node + 1, self.step):
                self.N = i 
                self.D = self.degree_func(self.N)
                self.train()
                self._run()
                self.summary()
                self.draw()
    
    def summary(self):
        res = 'N= ' + str(self.N) + ', D= ' + '%.15f' % self.D + ', '
        for j in range(self.P): 
            res += 'RMS_{}= {:.5e}, '.format(j, Decimal(self.RMS[j]))
        res += '\n'
        print(res)

运行结果

默认的测试样例

r = Reservoir()
# r.set_dataset(x)
r.run()

在这里插入图片描述

lorentz 系统

r = Reservoir()
r.set_dataset(x)
r.run()

在这里插入图片描述
看起来好像乱七八糟,但实际上ESN已经重建了洛伦兹系统的动态行为,看如下 3D 图:

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax = fig.add_subplot()
plt.plot(*r.dataset, 'r')
plt.plot(*r.S, 'g')

在这里插入图片描述

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!