神经网络
目录
神经网络向量化:
http://deeplearning.stanford.edu/wiki/index.php/%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C%E5%90%91%E9%87%8F%E5%8C%96
向量化练习:http://deeplearning.stanford.edu/wiki/index.php/Exercise:Vectorization
正文
一、单一神经元网络

一个神经元网络是最简单最基础的神经网络。
如上图所示:
输入是 \(x_1, x_2, x_3 和 截距 +1\)
输出是 \(h_{x,b}(x)\)
中间的运算过程是 \(f(W_1x_1 + W_2x_2 + W_3x_3)=f(W^Tx)=h_{W,b}(x)\)
这里的f(·)被称为激活函数。
这里激活函数选择sigmoid函数,公式如下: \(f(z) = \frac{1}{1+e^{-z}}\)
二、多个神经元网络

神经网络就是将许多单一“神经元”联结在一起。
其中:
\(L_1\)是输入层,\(L_2\)是隐藏层,\(L_3\)是输出层。
参数:\((W, b) = (W^{(1)}, b^{(1)}, W^{(2)}, b^{(2)})\)
\(W^{(l)}_{ij}\) 是 第l层第j单元 与 第 l+1层第i单元 之间的联结参数。 \(b^{(l)}_{i}\) 是 第l+1层第i单元的偏置项。
\(a^{(l)}_{i}\) 是 第l层第i大暖的激活值(输出值),那么:
\[a^(1)_1 = x_1, a^(1)_2 = x_2, a^(1)_3 = x_3\]\(a^{(2)}_1 = f(W^{(1)}_{11}x_1 + W^{(1)}_{12}x_2 + W^{(1)}_{13}x_3 + b^{(1)}_1)\) \(a^{(2)}_2 = f(W^{(1)}_{21}x_1 + W^{(1)}_{22}x_2 + W^{(1)}_{23}x_3 + b^{(2)}_1)\) \(a^{(2)}_3 = f(W^{(1)}_{31}x_1 + W^{(1)}_{22}x_2 + W^{(1)}_{33}x_3 + b^{(3)}_1)\)
\[h_{W,b}(x)= a^{(3)}_1 = f(W^{(2)}_{11}a^{(2)}_1 + W^{(2)}_{12}a^{(2)}_2 + W^{(2)}_{13}a^{(2)}_1 + b^{(2)}_1)\]重新撰写是:
\(z^{(l+1)} = W^{(l)}a^{(l)} + b^{(l)}\) \(a^{(l+1)} = f(z^{(l+1)})\)
先计算第一层,再计算第二层,再计算第三层,依次计算,直到输出层。
这里依次向前计算,没有闭环或回路,被称为前馈神经网络。
三、多个输出单元

四、自编码神经网络(稀疏)
符号:
| 符号 | 含义 | |
|---|---|---|
| \(x\) | 训练样本的输入特征,\(x \in \Re^{n}\). | |
| \(y\) | 输出值/目标值. 这里 \(y\) 可以是向量. 在autoencoder中,\(y=x\). | |
| \((x^{(i)}, y^{(i)})\) | 第\(i\)个训练样本 | |
| \(h_{W,b}(x)\) | 输入为\(x\)时的假设输出,其中包含参数\(W,b\). 该输出应当与目标值\(y\)具有相同的维数. | |
| \(W^{(l)}_{ij}\) | 连接第\(l\)层\(j\)单元和第\(l+1\)层\(i\)单元的参数. | |
| \(b^{(l)}_{i}\) | 第\(l+1\)层\(i\)单元的偏置项. 也可以看作是连接第\(l\)层偏置单元和第\(l+1\)层\(i\)单元的参数. | |
| \(\theta\) | 参数向量. 可以认为该向量是通过将参数\(W,b\) 组合展开为一个长的列向量而得到. | |
| \(a^{(l)}_i\) | 网络中第\(l\)层\(i\)单元的激活(输出)值.另外,由于\(L_1\)层是输入层,所以\(a^{(1)}_i = x_i\). | |
| \(f(\cdot)\) | 激活函数. 本文中我们使用\(f(z) = \tanh(z)\). | |
| \(z^{(l)}_i\) | 第\(l\)层\(i\)单元所有输入的加权和. 因此有\(a^{(l)}_i = f(z^{(l)}_i)\). | |
| \(\alpha\) | 学习率 | |
| \(s_l\) | 第\(l\)层的单元数目(不包含偏置单元). | |
| \(n_l\) | 网络中的层数. 通常\(L_1\)层是输入层,\(L_{n_l}\) 层是输出层. | |
| \(\lambda\) | 权重衰减系数. | |
| \(\hat{x}\) | 对于一个autoencoder,该符号表示其输出值;亦即输入值\(x\)的重构值. 与\(h_{W,b}(x)\)含义相同. | |
| \(\rho\) | 稀疏值,可以用它指定我们所需的稀疏程度 | |
| \(\hat\rho_i\) | (sparse autoencoder中)隐藏单元\(i\)的平均激活值. | |
| \(\beta\) | (sparse autoencoder目标函数中)稀疏值惩罚项的权重. |
练习:http://deeplearning.stanford.edu/wiki/index.php/Exercise:Sparse_Autoencoder
1、自编码神经网络介绍
前面的神经网络都是监督学习,即训练样本是有类别标签的
自编码神经网络是无监督学习算法,其训练样本是不带类别标签的。训练样本集合为 \(\{x^{(1)}, x^{(2)}, x^{(3)}, \ldots\}\) ,其中 \(x^{(i)} \in \Re^{n}\)
自编码神经网络使用反向传播算法,让目标值等于输入值, 比如\(y^{(i)} = x^{(i)}\) 。
下图是一个自编码神经网络的示例。

自编码神经网络尝试学习一个 \(h_{W,b}(x) \approx x\) 的函数。
换句话说,它尝试逼近一个恒等函数,从而使得输出 \(\hat{x}\) 接近于输入 \(x\) 。
恒等函数虽然看上去不太有学习的意义,但是当我们为自编码神经网络加入某些限制,
比如限定隐藏神经元的数量,我们就可以从输入数据中发现一些有趣的结构。
举例来说,假设某个自编码神经网络的输入 \(x\) 是一张 \(10 \times 10\) 图像(共100个像素)的像素灰度值,
于是 \(n=100\) ,其隐藏层 \(L_2\) 中有50个隐藏神经元。注意,输出也是100维的 \(y \in \Re^{100}\) 。
由于只有50个隐藏神经元,我们迫使自编码神经网络去学习输入数据的压缩表示,
也就是说,它必须从50维的隐藏神经元激活度向量 \(a^{(2)} \in \Re^{50}\) 中重构出100维的像素灰度值输入\(x\) 。
如果网络的输入数据是完全随机的,比如每一个输入 \(x_i\) 都是一个跟其它特征完全无关的独立同分布高斯随机变量,那么这一压缩表示将会非常难学习。
但是如果输入数据中隐含着一些特定的结构,比如某些输入特征是彼此相关的,那么这一算法就可以发现输入数据中的这些相关性。
事实上,这一简单的自编码神经网络通常可以学习出一个跟主元分析(PCA)结果非常相似的输入数据的低维表示。
我们刚才的论述是基于隐藏神经元数量较小的假设。
但是即使隐藏神经元的数量较大(可能比输入像素的个数还要多),我们仍然通过给自编码神经网络施加一些其他的限制条件来发现输入数据中的结构。
具体来说,如果我们给隐藏神经元加入稀疏性限制,那么自编码神经网络即使在隐藏神经元数量较多的情况下仍然可以发现输入数据中一些有趣的结构。
稀疏性可以被简单地解释如下。
如果当神经元的输出接近于1的时候我们认为它被激活,而输出接近于0的时候认为它被抑制,那么使得神经元大部分的时间都是被抑制的限制则被称作稀疏性限制。这里我们假设的神经元的激活函数是sigmoid函数。如果你使用tanh作为激活函数的话,当神经元输出为-1的时候,我们认为神经元是被抑制的。
注意到 \(a^{(2)}_j\) 表示隐藏神经元 \(j\) 的激活度,
但是这一表示方法中并未明确指出哪一个输入 \(x\) 带来了这一激活度。
所以我们将使用 \(a^{(2)}_j(x)\) 来表示在给定输入为 \(x\) 情况下,自编码神经网络隐藏神经元 \(j\) 的激活度。
进一步,让
\[\begin{align} \hat\rho_j = \frac{1}{m} \sum_{i=1}^m \left[ a^{(2)}_j(x^{(i)}) \right] \end{align}\]表示隐藏神经元 \(j\) 的平均活跃度(在训练集上取平均)。
我们可以近似的加入一条限制
\[\begin{align} \hat\rho_j = \rho, \end{align}\]其中, \(\rho\) 是稀疏性参数,通常是一个接近于0的较小的值(比如\(\rho\) = 0.05 )。
换句话说,我们想要让隐藏神经元 \(j\) 的平均活跃度接近0.05。
为了满足这一条件,隐藏神经元的活跃度必须接近于0。
为了实现这一限制,我们将会在我们的优化目标函数中加入一个额外的惩罚因子,而这一惩罚因子将惩罚那些 \(\hat\rho_j\) 和 \(\rho\) 有显著不同的情况从而使得隐藏神经元的平均活跃度保持在较小范围内。
?????? 为什么要使得活跃度比较低呢
惩罚因子的具体形式有很多种合理的选择,我们将会选择以下这一种:
\[\begin{align} \sum_{j=1}^{s_2} \rho \log \frac{\rho}{\hat\rho_j} + (1-\rho) \log \frac{1-\rho}{1-\hat\rho_j}. \end{align}\]这里, \(s_2\) 是隐藏层中隐藏神经元的数量,而索引\(j\)依次代表隐藏层中的每一个神经元。
如果你对相对熵(KL divergence)比较熟悉,这一惩罚因子实际上是基于它的。
于是惩罚因子也可以被表示为
\[\begin{align} \sum_{j=1}^{s_2} {\rm KL}(\rho || \hat\rho_j), \end{align}\]其中
\[{\rm KL}(\rho || \hat\rho_j) = \rho \log \frac{\rho}{\hat\rho_j} + (1-\rho) \log \frac{1-\rho}{1-\hat\rho_j}\]是一个以 \(\rho\) 为均值和一个以 \(\hat\rho_j\) 为均值的两个伯努利随机变量之间的相对熵。
相对熵是一种标准的用来测量两个分布之间差异的方法。
| 这一惩罚因子有如下性质,当 \(\hat\rho_j = \rho\) 时 $${\rm KL}(\rho | \hat\rho_j) = 0$$ , |
并且随着 \(\hat\rho_j\) 与 \(\rho\) 之间的差异增大而单调递增。
举例来说,在下图中,我们设定 \(\rho = 0.2\)
| 并且画出了相对熵值 $${\rm KL}(\rho | \hat\rho_j)\(随着\)\hat\rho_j$$ 变化的变化。 |

我们可以看出,相对熵在 \(\hat\rho_j = \rho\) 时达到它的最小值0,而当 \(\hat\rho_j\) 靠近0或者1的时候,相对熵则变得非常大(其实是趋向于\(\infty\))。
所以,最小化这一惩罚因子具有使得 \(\hat\rho_j\) 靠近 \(\rho\) 的效果。 现在,我们的总体代价函数可以表示为
\[\begin{align} J_{\rm sparse}(W,b) = J(W,b) + \beta \sum_{j=1}^{s_2} {\rm KL}(\rho || \hat\rho_j), \end{align}\]其中 \(J(W,b)\) 如之前所定义,而 \(\beta\) 控制稀疏性惩罚因子的权重。
\(\hat\rho_j\) 项则也(间接地)取决于 \(W,b\) ,因为它是隐藏神经元 \(j\) 的平均激活度,而隐藏层神经元的激活度取决于 \(W,b\) 。
为了对相对熵进行导数计算,我们可以使用一个易于实现的技巧,这只需要在你的程序中稍作改动即可。
具体来说,前面在后向传播算法中计算第二层( \(l=2\) )更新的时候我们已经计算了
\[\begin{align} \delta^{(2)}_i = \left( \sum_{j=1}^{s_{2}} W^{(2)}_{ji} \delta^{(3)}_j \right) f'(z^{(2)}_i), \end{align}\]现在我们将其换成
\[\begin{align} \delta^{(2)}_i = \left( \left( \sum_{j=1}^{s_{2}} W^{(2)}_{ji} \delta^{(3)}_j \right) + \beta \left( - \frac{\rho}{\hat\rho_i} + \frac{1-\rho}{1-\hat\rho_i} \right) \right) f'(z^{(2)}_i) . \end{align}\]就可以了。
有一个需要注意的地方就是我们需要知道 \(\hat\rho_i\) 来计算这一项更新。
所以在计算任何神经元的后向传播之前,你需要对所有的训练样本计算一遍前向传播,从而获取平均激活度。
如果你的训练样本可以小到被整个存到内存之中(对于编程作业来说,通常如此),你可以方便地在你所有的样本上计算前向传播并将得到的激活度存入内存并且计算平均激活度 。
然后你就可以使用事先计算好的激活度来对所有的训练样本进行后向传播的计算。
如果你的数据量太大,无法全部存入内存,你就可以扫过你的训练样本并计算一次前向传播,然后将获得的结果累积起来并计算平均激活度 \(\hat\rho_i\)
(当某一个前向传播的结果中的激活度 \(a^{(2)}_i\)被用于计算平均激活度 \(\hat\rho_i\) 之后就可以将此结果删除)。
然后当你完成平均激活度 \(\hat\rho_i\) 的计算之后,你需要重新对每一个训练样本做一次前向传播从而可以对其进行后向传播的计算。
对于后一种情况,你对每一个训练样本需要计算两次前向传播,所以在计算上的效率会稍低一些。
如果想要使用经过以上修改的后向传播来实现自编码神经网络,
那么你就会对目标函数 \(J_{\rm sparse}(W,b)\) 做梯度下降。
使用梯度验证方法,你可以自己来验证梯度下降算法是否正确。。
2、自编码器训练结果可视化
训练完(稀疏)自编码器,我们还想把这自编码器学到的函数可视化出来,好弄明白它到底学到了什么。
我们以在10×10图像(即n=100)上训练自编码器为例。在该自编码器中,每个隐藏单元i对如下关于输入的函数进行计算:
\[\begin{align} a^{(2)}_i = f\left(\sum_{j=1}^{100} W^{(1)}_{ij} x_j + b^{(1)}_i \right). \end{align}\]我们将要可视化的函数,就是上面这个以2D图像为输入、并由隐藏单元\(i\)计算出来的函数。
它是依赖于参数\(W^{(1)}_{ij}\)的(暂时忽略偏置项\(b_i\))。
需要注意的是,\(a^{(2)}_i\)可看作输入\(x\)的非线性特征。
不过还有个问题:什么样的输入图像\(x\)可让\(a^{(2)}_i\)得到最大程度的激励?
(通俗一点说,隐藏单元\(i\)要找个什么样的特征?)。
这里我们必须给\(x\)加约束,否则会得到平凡解。
| 若假设输入有范数约束$$ | x | ^2 = \sum_{i=1}^{100} x_i^2 \leq 1$$, |
则可证令隐藏单元\(i\)得到最大激励的输入应由下面公式计算的像素\(x_j\)给出(共需计算100个像素,j=1,…,100):
\[\begin{align} x_j = \frac{W^{(1)}_{ij}}{\sqrt{\sum_{j=1}^{100} (W^{(1)}_{ij})^2}}. \end{align}\]当我们用上式算出各像素的值、把它们组成一幅图像、并将图像呈现在我们面前之时,
隐藏单元\(i\)所追寻特征的真正含义也渐渐明朗起来。
假如我们训练的自编码器有100个隐藏单元,可视化结果就会包含100幅这样的图像——每个隐藏单元都对应一幅图像。
审视这100幅图像,我们可以试着体会这些隐藏单元学出来的整体效果是什么样的。
当我们对稀疏自编码器(100个隐藏单元,在10X10像素的输入上训练 )进行上述可视化处理之后,结果如下所示:

上图的每个小方块都给出了一个(带有有界范数 的)输入图像\(x\),它可使这100个隐藏单元中的某一个获得最大激励。
我们可以看到,不同的隐藏单元学会了在图像的不同位置和方向进行边缘检测。
显而易见,这些特征对物体识别等计算机视觉任务是十分有用的。
若将其用于其他输入域(如音频),该算法也可学到对这些输入域有用的表示或特征。
何时使用神经网络
1.1:神经网络需要大量的信息数据来训练。将神经网络想象成一个孩子。它首先观察父母如何走路。然后它才会独立行走,并且每走一步,孩子都会学习如何执行特定的任务。如果你不让它走,它可能永远不会学习如何走路。你可以提供给孩子的“数据”越多,效果就越好。
1.2:当你有适当类型的神经网络来解决问题时。 每个问题都有自己的难点。数据决定了你解决问题的方式。例如,如果问题是序列生成,递归神经网络更适合,而如果它是一个图像相关的问题,你可能会采取卷积神经网络。
1.3:硬件要求对于运行深度神经网络模型是至关重要的。神经网络很早以前就被“发现”了,但是近年来,神经网络一直在发光,这是因为计算能力的强大。如果你想用这些网络解决现实生活中的问题,准备购买一些高性能硬件吧!
如何解决神经网络问题
神经网络是一种特殊类型的机器学习(ML)算法。因此,与每个ML算法一样,它遵循数据预处理,模型构建和模型评估等常规ML工作流程。我列出了一个如何处理神经网络问题的待办事项清单。
1.检查神经网络是否可以提升传统算法。
2.做一个调查,哪个神经网络架构最适合即将解决的问题。
3.通过你选择的语言/库来定义神经网络架构。
4.将数据转换为正确的格式,并将其分成批。
5.根据你的需要预处理数据。
6.增加数据以增加规模并制作更好的训练模型。
7.将数据批次送入神经网络。
8.训练和监测训练集和验证数据集的变化。
9.测试你的模型,并保存以备将来使用。
最流行的深度学习库是Python提供的API,其次是Lua中,Java和Matlab的。最流行的库是:
Caffe(http://t.cn/8FnOmHE)
DeepLearning4j(http://t.cn/RXidtGH)
TensorFlow(http://t.cn/RYRN172)
Theano(http://t.cn/RGDldaz)
Torch(http://torch.ch/)
TensorFlow实现神经网络
注意:我们可以使用不同的神经网络体系结构来解决这个问题,但是为了简单起见,我们需要实现前馈多层感知器。
神经网络的常见的实现如下:
1.定义要编译的神经网络体系结构。
2.将数据传输到你的模型。
3.将数据首先分成批次,然后进行预处理。
4.然后将其加入神经网络进行训练。
5.显示特定的时间步数的准确度。
6.训练结束后保存模型以供将来使用。
7.在新数据上测试模型并检查其执行情况。
%pylab inline
import os
import numpy as np
import pandas as pd
from scipy.misc import imread
from sklearn.metrics import accuracy_score
import tensorflow as tf
# 设置初始值,以便可以控制模型的随机性
seed = 128
rng = np.random.RandomState(seed)
# 第一步:设置保管目录路径
root_dir = os.path.abspath('../..')
data_dir = os.path.join(root_dir, 'data')
sub_dir = os.path.join(root_dir, 'sub')
# 检查目录存在性
os.path.exists(root_dir)
os.path.exists(data_dir)
os.path.exists(sub_dir)
# 读数据
train = pd.read_csv(os.path.join(data_dir, 'Train', 'train.csv'))
test = pd.read_csv(os.path.join(data_dir, 'Test.csv'))
sample_submission = pd.read_csv(os.path.join(data_dir, 'Sample_Submission.csv'))
# 看数据
img_name = rng.choice(train,filename)
filepath = os.path.join(date_dir, 'Train', 'Images', 'train', img_name)
img = imread(filepath, flatten=True)
pylab.imshow(img, cmap='gray')
pylab.axis('off')
pylab.show()
# 为了方便后续数据处理,将所有图像存储为numpy数组
temp = []
for img_name in train.filename:
image_path = os.path.join(data_dir, 'Train', 'Images', 'train', img_name)
img = imread(image_path, flatten=True)
img = img.astype('float32')
temp.append(img)
train_x = np.stack(temp)
temp = []
for img_name in test.filename:
image_path = os.path.join(data_dir, 'Train', 'Images', 'test', img_name)
img = imread(image_path, flatten=True)
img = img.astype('float32')
temp.append(img)
test_x = np.stack(temp)
# 为了测试模型的正确性,创建验证集
split_size = int(train_x.shape[0]*0.7)
train_x, val_x = train_x[:split_size], train_x[split_size:]
train_y, val_y = train.label.values[:split_size], train.label.values[split_size:]
# 定义一些辅助函数
def dense_to_one_hot(labels_dense, num_classes=10):
"""将分类标签转换成one-hot向量"""
num_labels = labels_dense.shape[0]
index_offset = np.arange(num_labels)*num_classes
labels_one_hot = np.zeros((num_labels, num_classes))
labels_one_hot.flat[index_offset + labels_dense.ravel()] = 1
return labels_one_hot
89
def preproc(unclean_batch_x):
"""将数值范围转换为0-1"""
temp_batch = unclean_batch_x/unclean_batch_x.max()
return temp_batch
def batch_creator(batch_size, dataset_length, dataset_name):
"""将样本随机分块, 并返回适合的格式"""
batch_mask = rng.choice(dataset_length, batch_size)
batch_x = eval(dataset_name+'_x')[[batch_mask]].reshape(-1, input_num_units)
batch_x = preproc(batch_x)
if dataset_name == 'train':
batch_y = eval(dataset_name).ix[batch_mask, 'label'].values
batch_y = dense_to_one_hot(batch_y)
return batch_x, batch_y
# 接下来搭建神经网络
# 设置所有的变量
# 每一层神经元的个数
input_num_units = 28*28
hidden_num_units = 500
output_num_units = 10
# 定义变量
x = tf.placeholder(tf.float32, [None, input_num_units])
y = tf.placeholder(tf.float32, [NOne, output_num_units])
# 其他变量
epochs = 5
batch_size = 128
learning_rate 0.01
# 定义权重和偏差
weights = {
'hidden':tf.Variable(tf.random_normal([input_num_units, hidden_num_units], seed=seed)),
'output':tf.Variable(tf.random_normal([hidden_num_units, output_num_units], seed=seed))
}
biases = {
'hidden':tf.Variable(tf.random_normal([hidden_num_units], seed=seed)),
'output':tf.Variable(tf.random_normal([output_num_units], seed=seed))
}
# 计算神经网络计算图
hidden_layer = tf.add(tf.matmul(x, weights['hidden']), biases['hidden'])
hidden_layer = tf.nn.relu(hidden_layer)
output_layer = tf.matmul(hidden_layer, weights['output']) + biases['output']
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(output_layer, y))
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost)
init = tf.initialize_all_variables()
# 建会话,运行计算图
with tf.Session() as sess:
sess.run(init)
for epoch in range(epoches):
avg_cost = 0
total_batch = int(train.shape[0]/batch_size)
for i in range(total_batch):
batch_x, batch_y = batch_creator(batch_size, train_x.shape[0], 'train')
_, c = sess.run([optimizer, cost], feed_dict = {x: batch_x, y: batch_y})
avg_cost = c/total_batch
print("Epoch:", (epoch+1), "cost =", "{:,5f}".format(avg_cost))
print("\nTraining complete")
# 验证集
pred_temp = tf.equal(tf.argmax(output_layer, 1), tf.argmax(y, 1))
accuracy = tf.reduce_mean(tf.cost(pred_temp, "float"))
print("Validation Accuracy:", accuracy.eval({x: val_x.reshape(-1, input_num_units), y:dense_to_one_hot(val_y)}))
predict = tf.argmax(output_layer, 1)
pred = predict.eval({x: test_x.reshape(-1, input_num_units)})
径向基函数(RBF)神经网络
RBF网络能够逼近任意非线性的函数。可以处理系统内难以解析的规律性,具有很好的泛化能力,并且具有较快的学习速度。
当网络的一个或多个可调参数(权值或阈值)对任何一个输出都有影响时,这样的网络称为全局逼近网络。
由于对于每次输入,网络上的每一个权值都要调整,从而导致全局逼近网络的学习速度很慢,比如BP网络。
如果对于输入空间的某个局部区域只有少数几个连接权值影响输出,则该网络称为局部逼近网络,比如RBF网络。常见的局部逼近网络有RBF网络、小脑模型(CMAC)网络、B样条网络等。
接下来重点先介绍RBF网络的原理,然后给出其实现。先看如下图

RBF首先要选择P个基函数,每个基函数对应一个训练样本,各基函数的形式为 \(\varphi(\begin{Vmatrix} X-X_p \end{Vmatrix})\)。
由于距离是径向同性的,因此称为径向量函数。
\(\begin{Vmatrix} X-X_p \end{Vmatrix}\)叫做2范数。
其中,X是m维的向量,样本容量为P。
可以容易知道\(X_i\)是径向量函数\(\varphi_i\)的中心。
上图中隐含层的作用是把向量从低维的m映射到高维的P,这样低维线性不可分的情况到高维就线性可分了。
对于每一个样本数据,有如下方程组:
\[w_1 \varphi_1(\begin{Vmatrix} X_1-X_1 \end{Vmatrix}) + w_2 \varphi_2(\begin{Vmatrix} X_1-X_2 \end{Vmatrix}) + \cdots + w_p \varphi_p(\begin{Vmatrix} X_1-X_p \end{Vmatrix}) = d_1\] \[w_1 \varphi_1(\begin{Vmatrix} X_2-X_1 \end{Vmatrix}) + w_2 \varphi_2(\begin{Vmatrix} X_2-X_2 \end{Vmatrix}) + \cdots + w_p \varphi_p(\begin{Vmatrix} X_2-X_p \end{Vmatrix}) = d_2\] \[\vdots\] \[w_1 \varphi_1(\begin{Vmatrix} X_p-X_1 \end{Vmatrix}) + w_2 \varphi_2(\begin{Vmatrix} X_p-X_2 \end{Vmatrix}) + \cdots + w_p \varphi_p(\begin{Vmatrix} X_p-X_p \end{Vmatrix}) = d_p\]写成向量的形式就是\(\phi W = D\),显然\(\phi\)是对称矩阵,并且与X的维度无关。
当\(\phi\)可逆时,有\(W = \phi^{-1}D\)。
通常函数\(\varphi\)可以选取如下几个函数:
1、高斯函数
\[\varphi(x) = e^{-\frac{x^2}{2 \sigma^2}}\]2、反常S型函数
\[\varphi(x) = \frac{1}{1 + e^{\frac{x^2}{\sigma^2}}}\]3、拟多二次函数
\[\varphi(x) = \frac{1}{\sqrt{x^2+\sigma^2}}\]上述的插值方法叫做完全内插法,也就是说要求插值曲面必须经过每个样本点,即隐含层数目为P。
但是现在考虑这样一种情况:如果样本中有噪声,那么神经网络将拟合出一个错误的曲面,降低了泛化能力。所以通常情况下,在RBF网络中,隐含层选择K个,其中K<P。
另外,在插值矩阵求逆时可能导致结果不稳定,为了解决这个问题,引入了正则化理论。
正则化的基本思想是通过加入一个含有解的先验知识的约束来控制映射函数的光滑性,这样相似的输入就对应着相似的输出。
正则化RBF网络 输入样本有P个时,隐藏层神经元数目为P,且第p个神经元采用的变换函数为G(X,Xp),它们相同的扩展常数σ。输出层神经元直接把净输入作为输出。输入层到隐藏层的权值全设为1,隐藏层到输出层的权值是需要训练得到的:逐一输入所有的样本,计算隐藏层上所有的Green函数,根据(2)式计算权值。
广义RBF网络 Cover定理指出:将复杂的模式分类问题非线性地映射到高维空间将比投影到低维空间更可能线性可分。
广义RBF网络:从输入层到隐藏层相当于是把低维空间的数据映射到高维空间,输入层细胞个数为样本的维度,所以隐藏层细胞个数一定要比输入层细胞个数多。从隐藏层到输出层是对高维空间的数据进行线性分类的过程,可以采用单层感知器常用的那些学习规则,参见神经网络基础和感知器。
注意广义RBF网络只要求隐藏层神经元个数大于输入层神经元个数,并没有要求等于输入样本个数,实际上它比样本数目要少得多。因为在标准RBF网络中,当样本数目很大时,就需要很多基函数,权值矩阵就会很大,计算复杂且容易产生病态问题。另外广RBF网与传统RBF网相比,还有以下不同:
径向基函数的中心不再限制在输入数据点上,而由训练算法确定。 各径向基函数的扩展常数不再统一,而由训练算法确定。 输出函数的线性变换中包含阈值参数,用于补偿基函数在样本集上的平均值与目标值之间的差别。 因此广义RBF网络的设计包括:
结构设计–隐藏层含有几个节点合适
参数设计–各基函数的数据中心及扩展常数、输出节点的权值。
下面给出计算数据中心的两种方法:
数据中心从样本中选取。样本密集的地方多采集一些。各基函数采用统一的偏扩展常数:
dmax是所选数据中心之间的最大距离,M是数据中心的个数。扩展常数这么计算是为了避免径向基函数太尖或太平。 自组织选择法,比如对样本进行聚类、梯度训练法、资源分配网络等。各聚类中心确定以后,根据各中心之间的距离确定对应径向基函数的扩展常数。
λ是重叠系数。
接下来求权值W时就不能再用了,因为对于广义RBF网络,其行数大于列数,此时可以求Φ伪逆。
数据中心的监督学习算法
最一般的情况,RBF函数中心、扩展常数、输出权值都应该采用监督学习算法进行训练,经历一个误差修正学习的过程,与BP网络的学习原理一样。同样采用梯度下降法,定义目标函数为
ei为输入第i个样本时的误差信号。
上式的输出函数中忽略了阈值。
为使目标函数最小化,各参数的修正量应与其负梯度成正比,即
具体计算式为
上述目标函数是所有训练样本引起的误差总和,导出的参数修正公式是一种批处理式调整,即所有样本输入一轮后调整一次。目标函数也可以为瞬时值形式,即当前输入引起的误差
此时参数的修正值为:
下面我们就分别用本文最后提到的聚类的方法和数据中心的监督学习方法做一道练习题。
考虑Hermit多项式的逼近问题
Python代码实现:
from scipy import *
from scipy.linalg import norm, pinv
from matplotlib import pyplot as plt
class RBF:
def __init__(self, indim, numCenters, outdim):
self.indim = indim
self.outdim = outdim
self.numCenters = numCenters
self.centers = [random.uniform(-1, 1, indim) for i in xrange(numCenters)]
self.beta = 8
self.W = random.random((self.numCenters, self.outdim))
def _basisfunc(self, c, d):
assert len(d) == self.indim
return exp(-self.beta * norm(c-d)**2)
def _calcAct(self, X):
# calculate activations of RBFs
G = zeros((X.shape[0], self.numCenters), float)
for ci, c in enumerate(self.centers):
for xi, x in enumerate(X):
G[xi,ci] = self._basisfunc(c, x)
return G
def train(self, X, Y):
""" X: matrix of dimensions n x indim
y: column vector of dimension n x 1 """
# choose random center vectors from training set
rnd_idx = random.permutation(X.shape[0])[:self.numCenters]
self.centers = [X[i,:] for i in rnd_idx]
print "center", self.centers
# calculate activations of RBFs
G = self._calcAct(X)
print G
# calculate output weights (pseudoinverse)
self.W = dot(pinv(G), Y)
def test(self, X):
""" X: matrix of dimensions n x indim """
G = self._calcAct(X)
Y = dot(G, self.W)
return Y
if __name__ == '__main__':
n = 100
x = mgrid[-1:1:complex(0,n)].reshape(n, 1)
# set y and add random noise
y = sin(3*(x+0.5)**3 - 1)
# y += random.normal(0, 0.1, y.shape)
# rbf regression
rbf = RBF(1, 10, 1)
rbf.train(x, y)
z = rbf.test(x)
# plot original data
plt.figure(figsize=(12, 8))
plt.plot(x, y, 'k-')
# plot learned model
plt.plot(x, z, 'r-', linewidth=2)
# plot rbfs
plt.plot(rbf.centers, zeros(rbf.numCenters), 'gs')
for c in rbf.centers:
# RF prediction lines
cx = arange(c-0.7, c+0.7, 0.01)
cy = [rbf._basisfunc(array([cx_]), array([c])) for cx_ in cx]
plt.plot(cx, cy, '-', color='gray', linewidth=0.2)
plt.xlim(-1.2, 1.2)
plt.show()
多层前向神经网络
多层前向神经网络由三部分组成:输出层、隐藏层、输出层,每层由单元组成;
输入层由训练集的实例特征向量传入,经过连接结点的权重传入下一层,前一层的输出是下一层的输入;隐藏层的个数是任意的,输入层只有一层,输出层也只有一层;
除去输入层之外,隐藏层和输出层的层数和为n,则该神经网络称为n层神经网络,如下图为2层的神经网络;
一层中加权求和,根据非线性方程进行转化输出;理论上,如果有足够多的隐藏层和足够大的训练集,可以模拟出任何方程
设计神经网络结构
使用神经网络之前,必须要确定神经网络的层数,以及每层单元的个数;
为了加速学习过程,特征向量在传入输入层前,通常需要标准化到0和1之间;
离散型变量可以被编码成每一个输入单元对应一个特征值可能赋的值
比如:特征值A可能去三个值(a0,a1,a2),那么可以使用3个输入单元来代表A 如果A=a0,则代表a0的单元值取1,其余取0; 如果A=a1,则代表a1的单元值取1,其余取0; 如果A=a2,则代表a2的单元值取1,其余取0;
神经网络既解决分类(classification)问题,也可以解决回归(regression)问题。对于分类问题,如果是两类,则可以用一个输出单元(0和1)分别表示两类;如果多余两类,则每一个类别用一个输出单元表示,所以输出层的单元数量通常等一类别的数量。
没有明确的规则来设计最佳个数的隐藏层,一般根据实验测试误差和准确率来改进实验
交叉验证方法
如何计算准确率?最简单的方法是通过一组训练集和测试集,训练集通过训练得到模型,将测试集输入模型得到测试结果,将测试结果和测试集的真实标签进行比较,得到准确率。
在机器学习领域一个常用的方法是交叉验证方法。一组数据不分成2份,可能分为10份, 第1次:第1份作为测试集,剩余9份作为训练集; 第2次:第2份作为测试集,剩余9份作为训练集; ……
这样经过10次训练,得到10组准确率,将这10组数据求平均值得到平均准确率的结果。这里10是特例。一般意义上将数据分为k份,称该算法为K-fold cross validation,即每一次选择k份中的一份作为测试集,剩余k-1份作为训练集,重复k次,最终得到平均准确率,是一种比较科学准确的方法。
BP算法
通过迭代来处理训练集中的实例;
对比经过神经网络后预测值与真实值之间的差;
反方向(从输出层=>隐藏层=>输入层)来最小化误差,来更新每个连接的权重;
4.1、算法详细介绍
输入:数据集、学习率、一个多层神经网络构架;
输出:一个训练好的神经网络;
初始化权重和偏向:随机初始化在-1到1之间(或者其他),每个单元有一个偏向;对于每一个训练实例X,执行以下步骤:
1、由输入层向前传送:
结合神经网络示意图进行分析:

由输入层到隐藏层:
\[O_j= \sum_i w_{ij} x_i + \theta_j\]由隐藏层到输出层:
\[O_k= \sum_j w_{jk} O_j + \theta_k\]两个公式进行总结,可以得到:
\[I_j = \sum_i w_{ij} O_i + \theta_j\]\(I_j\)为当前层单元值,\(O_i\)为上一层的单元值,\(W_{ij}\)为两层之间,连接两个单元值的权重值,
\(\theta_j\)为每一层的偏向值。我们要对每一层的输出进行非线性的转换,示意图如下:

当前层输出为\(I_j\),f为非线性转化函数,又称为激活函数,定义如下:
\[f(x) = \frac{1}{1+e^{-x}}\]即每一层的输出为:
\[O_j = \frac{1}{1+e^{-I_j}}\]这样就可以通过输入值正向得到每一层的输出值。
2、根据误差反向传送 对于输出层:其中\(T_k\)是真实值,\(O_k\)是预测值
$$Err_k = O_k(1-O_k)(T_k - O_k)
对于隐藏层:
\[Err_j = O_j(1-O_j)\sum_k Err_k w_{jk}\]权重更新:其中l为学习率
\[\Delta w_{ij} = (l) Err_j O_i\] \[w_{ij} = w_{ij} + \Delta w_{ij}\]偏向更新:
\[\Delta \theta_j = (l)Err_j\] \[\theta_j = \theta_j + \Delta \theta_j\]3、终止条件
偏重的更新低于某个阈值;
预测的错误率低于某个阈值;
达到预设一定的循环次数;
4、非线性转化函数
上面提到的非线性转化函数f,一般情况下可以用两种函数:
(1)tanh(x)函数:
tanh(x)=sinh(x)/cosh(x)
sinh(x)=(exp(x)-exp(-x))/2
cosh(x)=(exp(x)+exp(-x))/2
(2)逻辑函数,本文上面用的就是逻辑函数
BP神经网络的Python实现
import numpy as np
# 定义非线性转换函数
def tanh(x):
return np.tanh(x)
def tanh_deriv(x):
return 1.0 - np.tanh(x)*np.tanh(x)
def logistic(x):
return 1.0/(1.0+np.exp(-x))
def logistic_derivative(x):
return logistic(x)*(1-logistic(x))
class NeuralNetwork:
def __init__(self, layers, activation='tanh'):
"""
参数:
layers:list包含每层神经元的数量,因此至少有两个值
activation:可以是logistic或者tanh
"""
if activation == 'logistic':
self.activation = logistic
self.activation_deriv = logistic_derivative
elif activation == 'tanh':
self.activation = tanh
self.activation_deriv = tanh_deriv
self.weights = []
for i in range(1, len(layers) - 1):
self.weights.append((2*np.random.random((layers[i-1]+1, layers[i]+1))-1)*0.25)
self.weights.append((2*np.random.random((layers[i]+1, layers[i+1])))-1*0.25)
def fit(self, X, y, learning_rate=0.2, epochs=10000):
X = np.atleast_2d(X)
temp = np.ones([X.shape[0], X.shape[1]+1])
temp[:, 0:-1] = X
X = temp
y = np.array(y)
for k in range(epochs):
i = np.random.randint(X.shape[0])
a = [X[i]]
for l in range(len(self.weights)):
a.append(self.activation(np.dot(a[l], self.weights[l])))
error = y[i] - a[-1]
deltas = [error * self.activation_deriv(a[-1])]
for l in range(len(a) - 2, 0, -1):
deltas.append(deltas[-1].dot(self.weights[l].T)*self.activation_deriv(a[l]))
deltas.reverse()
for i in range(len(self.weights)):
layer = np.atleast_2d(a[i])
delta = np.atleast_2d(deltas[i])
self.weights[i] += learning_rate * layer.T.dot(delta)
def predict(self, x):
x = np.array(x)
temp = np.ones(x.shape[0]+1)
temp[0:-1] = x
a = temp
for l in range(0, len(self.weights)):
a = self.activation(np.dot(a, self.weights[l]))
return a
运行程序
from BP import NeuralNetwork
import numpy as np
nn = NeuralNetwork([2,2,1], 'tanh')
x = np.array([[0,0], [0,1], [1,0], [1,1]])
y = np.array([1,0,0,1])
nn.fit(x,y,0.1,10000)
for i in [[0,0], [0,1], [1,0], [1,1]]:
print(i, nn.predict(i))
反向传播算法:
三层神经网络:Layer L1是输入层,Layer L2是隐含层,Layer L3是隐含层,
手里有一堆数据{x1,x2,x3,…,xn},输出也是一堆数据{y1,y2,y3,…,yn},
现在要他们在隐含层做某种变换,让你把数据灌进去后得到你期望的输出。
如果你希望你的输出和原始输入一样,那么就是最常见的自编码模型(Auto-Encoder)。
如果你的输出和原始输入不一样,那么就是很常见的人工神经网络了,
相当于让原始数据通过一个映射来得到我们想要的输出数据,也就是我们今天要讲的话题。
# -*- coding: utf-8 -*-
'''
Created on
@author: Belle
'''
from numpy.random.mtrand import randint
import numpy as np
'''双曲函数'''
def tanh(value):
return (1 / (1 + np.math.e ** (-value)))
'''双曲函数的导数'''
def tanhDer(value):
tanhValue = tanh(value)
return tanhValue * (1 - tanhValue)
'''
Bp神经网络model
'''
class BpNeuralNetWorkModel:
def __init__(self, trainningSet, label, layerOfNumber, studyRate):
'''学习率'''
self.studyRate = studyRate
'''计算隐藏层神经元的数量'''
self.hiddenNeuronNum = int(np.sqrt(trainningSet.shape[1] + label.shape[1]) + randint(1, 10))
'''层数据'''
self.layers = []
'''创建输出层'''
currentLayer = Layer()
currentLayer.initW(trainningSet.shape[1], self.hiddenNeuronNum)
self.layers.append(currentLayer)
'''创建隐藏层'''
for index in range(layerOfNumber - 1):
currentLayer = Layer()
self.layers.append(currentLayer)
'''输出层后面不需要求权重值'''
if index == layerOfNumber - 2:
break
nextLayNum = 0
'''初始化各个层的权重置'''
if index == layerOfNumber - 3:
'''隐藏层到输出层'''
nextLayNum = label.shape[1]
else:
'''隐藏层到隐藏层'''
nextLayNum = self.hiddenNeuronNum
currentLayer.initW(self.hiddenNeuronNum, nextLayNum)
'''输出层的分类值'''
currentLayer = self.layers[len(self.layers) - 1]
currentLayer.label = label
'''神经网络前向传播'''
def forward(self, trainningSet):
'''计算输入层的输出值'''
currentLayer = self.layers[0]
currentLayer.alphas = trainningSet
currentLayer.caculateOutPutValues()
preLayer = currentLayer
for index in range(1, len(self.layers)):
currentLayer = self.layers[index]
'''上一层的out put values就是这一层的zValues'''
currentLayer.zValues = preLayer.outPutValues
'''计算alphas'''
currentLayer.caculateAlphas()
'''最后一层不需要求输出值,只要求出alpha'''
if index == len(self.layers) - 1:
break
'''输入层计算out puts'''
currentLayer.caculateOutPutValues()
'''指向上一层的layer'''
preLayer = currentLayer
'''神经网络后向传播'''
def backPropogation(self):
layerCount = len(self.layers)
'''输出层的残差值'''
currentLayer = self.layers[layerCount - 1]
currentLayer.caculateOutPutLayerError()
'''输出层到隐藏层'''
preLayer = currentLayer
layerCount = layerCount - 1
while layerCount >= 1:
'''当前层'''
currentLayer = self.layers[layerCount - 1]
'''更新权重'''
currentLayer.updateWeight(preLayer.errors, self.studyRate)
if layerCount != 1:
currentLayer.culateLayerError(preLayer.errors)
layerCount = layerCount - 1
preLayer = currentLayer
'''
创建层
'''
class Layer:
def __init__(self):
self.b = 0
'''使用正态分布的随机值初始化w的值'''
def initW(self, numOfAlpha, nextLayNumOfAlpha):
self.w = np.mat(np.random.randn(nextLayNumOfAlpha, numOfAlpha))
'''计算当前层的alphas'''
def caculateAlphas(self):
'''alpha = f(z)'''
self.alphas = np.mat([tanh(self.zValues[row1,0]) for row1 in range(len(self.zValues))])
'''求f'(z)的值(即f的导数值)'''
self.zDerValues = np.mat([tanhDer(self.zValues[row1,0]) for row1 in range(len(self.zValues))])
'''计算out puts'''
def caculateOutPutValues(self):
'''计算当前层z = w * alpha的的下一层的输入值'''
self.outPutValues = self.w * self.alphas.T + self.b
'''计算输出层的残差'''
def caculateOutPutLayerError(self):
self.errors = np.multiply(-(self.label - self.alphas), self.zDerValues)
print("out put layer alphas ..." + str(self.alphas))
'''计算其它层的残差'''
def culateLayerError(self, preErrors):
self.errors = np.mat([(self.w[:,column].T * preErrors.T * self.zDerValues[:,column])[0,0] for column in range(self.w.shape[1])])
'''更新权重'''
def updateWeight(self, preErrors, studyRate):
data = np.zeros((preErrors.shape[1], self.alphas.shape[1]))
for index in range(preErrors.shape[1]):
data[index,:] = self.alphas * (preErrors[:,index][0,0])
self.w = self.w - studyRate * data
'''
训练神经网络模型
@param train_set: 训练样本
@param labelOfNumbers: 训练总类别
@param layerOfNumber: 神经网络层数,包括输出层,隐藏层和输出层(默认只有一个输入层,隐藏层和输出层)
'''
def train(train_set, label, layerOfNumber = 3, sampleTrainningTime = 5000, studyRate = 0.6):
neuralNetWork = BpNeuralNetWorkModel(train_set, label, layerOfNumber, studyRate)
'''训练数据'''
for row in range(train_set.shape[0]):
'''当个样本使用梯度下降的方法训练sampleTrainningTime次'''
for time in range(sampleTrainningTime):
'''前向传播 '''
neuralNetWork.forward(train_set[row,:])
'''反向传播'''
neuralNetWork.backPropogation()
参考文献:
http://ufldl.stanford.edu/wiki/index.php/%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C
https://mp.weixin.qq.com/s/lrWH4yWglIr161Y5bmu5OA
https://mp.weixin.qq.com/s/SrGfpcgqxtPQUD-Y5rFhEw
https://mp.weixin.qq.com/s/lrWH4yWglIr161Y5bmu5OA
http://www.cnblogs.com/zhangchaoyang/articles/2591663.html
https://mp.weixin.qq.com/s/GyOc-W6jOLRaCTu4V6ohMQ