神经网络近似函数

神经网络的强大在于其能在有限区间内近似任意函数,更精确的说是MLP(多层感知机)是万能的函数近似机,证明由Cybenko给出。注意是在有限区间内,论文中也是在单位超立方体(the unit hypercube)内讨论。如果不是有限区间,可能得靠RNN,比如某些具有周期性质的函数,就能使用RNN近似。


MLP近似爱心函数

看到微博上推的使用MLP来近似函数的博文,我觉得少点东西,做点补充。第一步就挑一个有趣点的函数吧,随便挑个心形函数x^2+(y-\sqrt[3]{x^2})^2。然后用keras很快就能搭个MLP,具体见github

训练之后就能得到对这个函数的近似,画成图如下:

heart
近似爱心函数

还有个哥们居然搞了个penis的函数,无法直视,有兴趣的可以尝试。


RNN近似周期函数

超出训练的范围,MLP的近似就无效了,想想也知道,泛化能力不可能那么强。而RNN在一定程度上可以弥补一下,最好是有点周期性的函数。为了简单起见,就先用\sin(x)测试好了。同样用keras快速搭个RNN,具体见github

关键是训练数据如何构造,也就是输入输出是怎样的。跟MLP不同,这里用函数值预测函数值,而不是输入x。因为在如时间序列预测的情况下,输入x是未知的,只能使用之前已知的函数值。我用相邻 feature_length个点组成一步的输入,总共 seq_length 步,其中每个点间隔 interval 。那预测就是后面的点,也可以只预测一个,我测试是预测 feature_length个,其中一半重合。比如0到32个点预测16到48个点(这样的做法似乎也能使用在MLP预测时间序列中,Quantitative Finance论文)。说不清楚看下图或者看代码。

rnn predict
RNN输入和输出

之后就是训练了,\sin(x)可以说是完美近似,后面因为累积误差导致不能近似的很好也是正常的。我又测试了\sin(x) * \sqrt[10]{x},有一个递增项,效果就没有\sin(x)好了,但短期内效果还是好的。注意我画得图的区间不在测试区间内,也就是网络没看到过的数据,正因为RNN具有一定泛化能力才能有如此表现。

sinx
sin(x)
sinxx
sin(x)*x^0.1

累积误差比较蛋疼,如下的情况,就算是测试数据的范围内也会有问题。可以用beamsearch,或者其他目标函数做,比如Q-learning。比较复杂,这里只讨论最简单的情况。

误差
累积误差

总结

MLP还是很厉害的,只要参数足够多,就能在一定区间内近似任意函数。而RNN相当于图灵机,之前也写过使用RNN做时间序列预测的博文,由于数据不能放出,所以也没有把代码放出。这次的代码与上次是类似的,改改也能试试做时间序列预测,就我的经验来看,没有传统方法好。

 

RNN建模时间序列

RNN建模时间序列

RNN简介

下图是最简单的RNN。 x 为输入序列,我们需要将他展开为 x_0, x_1, ... x_t,因为实际应用中序列是有限长的。展开的过程即下图的左边到右边。

RNN
图自WILDML

图中 s_t 为隐含层,可以认为是记忆单元,即 s_t 概括了 x_0x_{t-1} 的信息,利用 x_t, s_t 可以预测输出 o_t. 训练时需要使用o_t 与真实的输出做比较,即产生一个误差,最小化这个误差就是对这个RNN的训练过程。具体可以参考网友的介绍
可以用数学公式来描述这个过程。

    \[s_t=f(Ux_t + Ws_{t-1})\]

    \[o_t = \mathrm{softmax}(Vs_t)\]

    \[cost = \sum_t f_{err}(o_t, \hat o_t)\]

其中f 为激活函数,可以是sigmoid或者tanh等其他类型的函数。Wiki上有详细的介绍。f_{err} 可以是Cross entropy(分类常用的误差函数),也可以是简单的RMSE,根据不同的情况选择不同的误差函数,只要可导(可次导)就行。

开导的目标函数就可以使用一些库的自动求导功能进行求解。如Theano可以直接进行符号求导,可以节省很多代码以及bug。

LSTM原理

RNN有个致命的缺点就是对长序列的建模效果不好,可以认为RNN记忆单元不行。因此 Hochreiter & Schmidhuber (1997) 提出了新的模型LSTM(Long short-term memory), 如下图.

 

LSTM
图自colah.github.io

网上有很多资源对其进行了详细的描述:

  1. Understanding LSTM Networks
  2. Implementing a GRU/LSTM RNN with Python and Theano

我简单说下, LSTM的单个单元的操作如下:

    \begin{align*} i &=\sigma(x_tU^i + s_{t-1} W^i)\\ f &=\sigma(x_t U^f +s_{t-1} W^f) \\ o &=\sigma(x_t U^o + s_{t-1} W^o) \\ g &=\tanh(x_t U^g + s_{t-1}W^g) \\ c_t &= c_{t-1} \circ f + g \circ i \\ s_t &=\tanh(c_t) \circ o \end{align*}

同样这复杂的操作通过程序的自动求导就很简单,可以看这个例子

基本与RNN类似,增加了记忆单元 c 和遗忘门f。 原理就是在RNN的基础上,利用记忆单元来更好的对长序列进行概括(记忆)。

除LSTM还有很多变种,如GRU, LSTM with peephole connections等,这篇文章做了一些比较。

RNN应用

RNN主要用于序列建模,有几种模型,如下图。

 

char-model
图自karpathy.github.io

 


时间序列建模

这是别的实验室一个项目的测试,数据集不好放出来,而且由于数据实在太少,效果比较一般,他目的是根据已有的数据,预测下一周的cpu busy和cpu context switches。由于是预测未来,我们需要对过去进行总结。RNN就是做这样一件事情:对过去出现过的模式进行概括记忆,看看未来会不会再次出现并预测。问题就是怎么通过RNN预测什么。这里没有代码,写得不清楚,可以看另一篇博文,神经网络近似函数,有模型有代码。

模型

在我实现的模型中,我利用0分钟到n分的数据 [x_0, x_1, ... x_n] 预测下一个时间段的数据如 [x_s, x_{s+1},...x_{s+n}], 是一个many to many的模型。在我测试的模型中,我选取n=288 即一整天的数据,s=1 即下一个5分钟。可以表示为:
目标: [x_1, x_2, x_3, ..., x_{1+n}]
输入: [x_0, x_1, x_2, ... , x_n]
但这样的模型在实际应用中是不行的,每次输入只有一个数据点,在测试时会导致很大的偏差。我采取的办法是将m 个时间点叠加,将输入变为

    \begin{align*} x_0, \quad x_{36}, \quad x_{72}, .... \\ x_1, \quad x_{37}, \quad x_{73}, .... \\ x_2, \quad x_{38}, \quad x_{74}, ... \\ ..., \quad ..., \quad ....\\ x_{m}, \quad x_{m+36},x_{m+72} \end{align*}

注意纵向为特征,即 x_0, x_1, ..x_{m} 为一个特征。36的间隔也可以选择合适的,我随便选了一个。
对应输出就变为m 长的向量(m 为 feature_length),也可以输出一个单一的预测值 x_{m+1}。我认为在测试时,不知道未来数据的情况下,使用向量更合理。如下:

    \begin{align*} x_1, \quad x_{37}, \quad x_{73}, .... \\ x_2, \quad x_{38}, \quad x_{74}, ....\\ x_3, \quad x_{39}, \quad x_{75}, ...\\ ..., \quad ..., \quad ....\\ x_{m+1}, \quad x_{m+37},x_{m+73} \end{align*}

简单的说,就是单步我要从[x_0, x_1, x_2, ... , x_m] 预测 [x_1, x_2, x_3, ..., x_{1+m}],注意第一个模型的不同之处。我训练时我一次输入几组就是seq_length。
feature_length 越长就越可以防止抖动,但是对局部细节刻画就比较难。
seq_length越长记忆的越多,可能导致过拟合,也会导致训练的困难以及数据量太小了。太短抓不住特征。

实验平台

  • 操作系统:Linux
  • 深度学习库:Theano + Blocks, 可以使用以下命令安装

pip install git+git://github.com/mila-udem/blocks.git \
-r https://raw.githubusercontent.com/mila-udem/blocks/master/requirements.txt

实验效果

在cpu context switches表现较好。我选取CPU1, CPU7, CPU8进行测试。其实都差不多, 需要调参,没有足够时间。第7周数据在训练的时候是不用的,只用前5周数据量还是太小。我用第6周的数据预测第7周。下图中蓝线代表真实数据,绿色部分是用第六周的数据一个个预测但使用真实值代替下次输入的预测值。红色部分完全使用预测值。

CPU Context Switches

CPU7: rmse: 0.06

Screenshot_20160526_232717

CPU Busy

CPU7: rmse: 0.148

Screenshot_20160526_232739