深入理解人工神经网络——从原理到实现
来源:互联网 发布:穿越火线数据异常 编辑:程序博客网 时间:2024/06/05 15:11
王琦 QQ:451165431 计算机视觉&深度学习
转载请注明出处 :http://blog.csdn.net/Rainbow0210/article/details/78396755。
本篇通过在MNIST上的实验,引出神经网络相关问题,详细阐释其原理以及常用技巧、调参方法。欢迎讨论相关技术&学术问题,但谢绝拿来主义。
代码是博主自己写的,因为倾向于详细阐述底层原理,所以没有用TensorFlow等主流DL框架。实验结果与TF等框架可能稍有不同,其原因在于权重初始化方式的差异等,但并不影响对于网络的本质的理解。
此外,值得注意的是博主在一年多前写的关于DBN的博客:
http://blog.csdn.net/rainbow0210/article/details/53010694
在本次实验中,也证明了使用 Relu 作为激活函数时可以达到 Sigmoid+DBN 近乎相同的性能。DBN所解决的是深度网络的参数初始化的问题,在传统激活函数中(Sigmoid系),随着网络权值的更新,会有相当一部分节点的值分布在激活函数值域的两侧,导致其梯度近乎为零,使得网络难以更新,即“梯度消亡”。而通过 DBN 去初始化网络权重,再 fine-tuning,可以很大程度上解决这个问题。但实际上,利用 Relu 激活函数替代 Sigmoid 可以更本质地解决这个问题。这也是为什么如今即便是很深的网络,也可以直接进行训练的原因(有足够多的训练样本)。
实验概述
数据集
- MNIST:0-9共10个数字,其中,60000条训练样本,10000条测试样本,每条样本分辨率为
28×28 。拉伸为1 维向量,因而为28×28=784 维。
实验平台
- Python 2.7
实验内容
通过MLP完成对MNIST数据集的分类。具体有需要实现一下三部分:
- 全连接层:实现并比较1层隐层的 MLP 与2层隐层的MLP的性能差异
- 激活函数:实现并比较 Relu 和 Sigmoid 两种激活函数的性能差异
- 损失函数:实现 EuclideanLoss
实验原理
上图是一个典型的包含两个隐层的MLP,其中,每个圆圈代表一个神经元,且每个神经元模型包含一个可微的非线性激活函数(如Sigmoid);每两个神经元之间的连线具有一个权值
MLP的训练往往基于反向传播算法,这也是本次试验的重点。其中,包含如下两个阶段
- 前向传播:网络参数(
w 和b )固定,输入信号在网络中一层一层传播(图中对应于自下向上),直到输出端。 - 反向传播:通过比较网络的输出信号和期望的输出信号所差生的一个误差信号(
loss ),该误差信号通过网络一层一层传播(图中对应于自上向下),网络参数(w 和b )通过计算梯度不断修正,使得loss 逐渐收敛于局部极小值,即误差信号逐步减小。
前向传播(Forward)
记第
注意到
所以,可以将
激活后,第一个隐层的输出值
其中,
类似地,第二个隐层的输出值
至此,即完成了前向传播的过程,我们通过网络的输入信号
反向传播(Backward)
在进行反向传播之前,我们需要定义误差信号,即损失函数(
欲更新权重系数,使得
对于输出层,由于其存在一个期望响应,即
输出层
注意到:
所以:
则修正量
其中,负号意味着在权空间中梯度下降。定义局部梯度
则:
其中,
隐藏层
其中后两项的偏导是与输出层完全类似的:
对于
注意到:
所以:
进而有:
则修正量
其中,负号意味着在权空间中梯度下降。定义局部梯度
则:
其中,
学习速率( Learning Rate )
通过网络训练的逐步迭代,反向传播算法可以得到在权空间中基于最速下降的轨迹的近似,所以,学习速率
动量( Momentom )
一个既能加快学习速度,又能保证网络的稳定性的一个简单的方法就是为修正量增加动量项:
其中,每一项后面的括号(即(n)和(n-1))表示在第n个回合( epoch )的修正量,
权值衰减( Weight Decay )
在极为有限的训练样本以及大量的网络参数的情况下,网络的训练非常容易发生过拟合的现象,导致泛化能力降低,而权值衰减可以一定程度地避免过拟合的发生。其通常的做法是在损失函数中加上
其中,
代码实现
损失函数( EuclideanLoss )
class EuclideanLoss(object): def __init__(self, name): self.name = name def forward(self, input, target): return 0.5 * np.mean(np.sum(np.square(input - target), axis=1)) def backward(self, input, target): return (input - target) / len(input)
全连接层
class Linear(Layer): def __init__(self, name, in_num, out_num, init_std): super(Linear, self).__init__(name, trainable=True) self.in_num = in_num self.out_num = out_num self.W = np.random.randn(in_num, out_num) * init_std self.b = np.zeros(out_num) self.grad_W = np.zeros((in_num, out_num)) self.grad_b = np.zeros(out_num) self.diff_W = np.zeros((in_num, out_num)) self.diff_b = np.zeros(out_num) def forward(self, input): self._saved_for_backward(input) output = np.dot(input, self.W) + self.b return output def backward(self, grad_output): input = self._saved_tensor self.grad_W = np.dot(input.T, grad_output) self.grad_b = np.sum(grad_output, axis=0) return np.dot(grad_output, self.W.T) def update(self, config): mm = config['momentum'] lr = config['learning_rate'] wd = config['weight_decay'] self.diff_W = mm * self.diff_W + (self.grad_W + wd * self.W) self.W = self.W - lr * self.diff_W self.diff_b = mm * self.diff_b + (self.grad_b + wd * self.b) self.b = self.b - lr * self.diff_b
激活函数
Relu:
class Relu(Layer): def __init__(self, name): super(Relu, self).__init__(name) def forward(self, input): self._saved_for_backward(input) return np.maximum(0, input) def backward(self, grad_output): input = self._saved_tensor return grad_output * (input > 0)
Sigmoid:
class Sigmoid(Layer): def __init__(self, name): super(Sigmoid, self).__init__(name) def forward(self, input): output = 1 / (1 + np.exp(-input)) self._saved_for_backward(output) return output def backward(self, grad_output): output = self._saved_tensor return grad_output * output * (1 - output)
实验结果
学习速率
将网络层数固定为
相应的三个实验的函数曲线:
从图表中可以看出,随着学习速率的减小,网络的学习过程更加平稳,曲线更加光滑,但是,网络的收敛速度也会相应减小,需要训练的epoch数会变多。
激活函数
将网络层数固定为
相应的两个实验的函数曲线:
从图表中可以看出,相比于 Relu 作为激活函数,Sigmoid 作为激活函数时,网络的收敛速度更慢,性能也相对变差。这是因为当 Sigmoid 作为激活函数时,会有部分神经元达到“饱和”的状态:其值分布在 Sigmoid 函数的值域(即(0,1))的两端,梯度近乎为0,导致其值难以更新。但是 Relu 作为激活函数时则不会出现这种情况。
动量
将网络层数固定为
相应的三个实验的函数曲线:
从图表中可以看出,适当增加动量参数的值可以加快网络的学习速度,但又不会使得网络震荡的太厉害,学习过程相对平稳。
权值衰减
将网络层数固定为
相应的六个实验的函数曲线:
从图表中可以看出,权值衰减系数不宜过大,当其小于
网络层数
单隐层网络节点数固定为
相应的四个实验的函数曲线:
从图表中可以看出,相比于 Sigmoid,Relu作为激活函数时网络具有更好的性能。此外,双隐层的Sigmoid 训练难度很大,收敛速度极慢。而双隐层 Relu 网络相比于单隐层 Relu 网络具有更好的性能,这是因为多隐层可以更好地拟合高度非线性的分类面,从而使得模型具有更强的分类能力。
- 深入理解人工神经网络——从原理到实现
- 深入理解卷积神经网络(CNN)——从原理到实现
- 人工神经网络的深入理解
- 人工神经网络的深入理解
- 神经网络从原理到实现
- 从感知机到人工神经网络
- 人工神经网络本质理解
- Mybatis实现原理深入解析——原理分析之一:从JDBC到Mybatis
- 卷积神经网络(CNN):从原理到实现
- 卷积神经网络(CNN):从原理到实现
- 深度|人工神经网络深入分析
- 人工神经网络的设计与实现(一) 原理
- Python与人工神经网络(13)——实现卷积神经网络
- 一文搞定BP神经网络——从原理到应用(原理篇)
- 人工神经网络之我见-【神经网络的原理】
- 机器学习笔记——人工神经网络
- CTR——人工神经网络+决策树
- CTR——人工神经网络+决策树
- 『转载』jQuery Validate 菜鸟教程
- 『实践』jdbc批量插入数据
- 『实践』腾讯云服务器发布项目
- 『转载』eclipse插件安装
- 『转载』使用TortoiseSVN客户端
- 深入理解人工神经网络——从原理到实现
- 微信公众号开发之文本消息自动回复
- 微信公众号开发之语音消息识别
- c++11多线程编程(二):joining和detaching 线程
- PE文件结构及其加载机制(三)
- 微信公众号开发之LBS
- 51nod 1681 公共祖先【树状数组】【DFS序】
- 微信公众号开发之数据库
- 洛谷 1631 序列合并 堆 解题报告