神经网络和深度学习目前提供了针对图像识别,语音识别和自然语言处理领域诸多问题的最佳解决方案。传统的编程方法中,我们告诉计算机如何去做,将大问题划分为许多小问题,精确地定义了计算机很容易执行的任务。而神经网络不需要我们告诉计算机如何处理问题,而是通过从观测数据中学习,计算出他自己的解决方案。
TensorFlow对基于深度神经网络的深度学习实现进行了充分的封装,在我们的工程实践中拿来就用,确实非常方便。
神经网络然鹅,知其然也要知其所以然。
当你对深度神经网络的基本概念不甚了解的时候,其实很难看明白代码究竟在说什么,比如前向计算、残差、反向传播、梯度下降、激活函数等等。
深度神经网络(Deep Neural Networks, 以下简称DNN)是深度学习的基础,这种方法脱胎于对人类大脑神经系统的模拟和重建。
在深度学习领域,神经元是最底层的单元。
1. 神经元
对于神经元的研究由来已久,1904年生物学家就已经知晓了神经元的组成结构。一个神经元通常具有多个树突,主要用来接受传入信息;而轴突只有一条,轴突尾端有许多轴突末梢可以给其他多个神经元传递信息。轴突末梢跟其他神经元的树突产生连接,从而传递信号。这个连接的位置在生物学上叫做“突触”。
神经元神经元模型是一个包含输入,输出与计算功能的模型。输入可以类比为神经元的树突,而输出可以类比为神经元的轴突,计算则可以类比为细胞核。
下图是一个典型的神经元模型:包含有3个输入,1个输出,以及2个计算功能(求和&非线性函数)。
注意中间的箭头线。这些线称为“连接”。每个连接上有一个“权值”。
神经元模型2. 简单神经网络
将多个单一的“神经元”组合到一起时,一些神经元的输出作为另外一些神经元的输入,这样就组成了一个单层的神经网络。
单层神经网络我们把接受输入数据的层叫做输入层,对应的,输出结果的层叫做输出层。中间的神经元组成中间层(或者隐藏层)。
大多数情况下,设计一个神经网络的时候,输入层和输出层是固定的,而中间隐藏层的层数和节点可以自由变化。
当每一层节点的输出结果会发送到下一层所有的节点作为输入时,叫做全连接网络。
3. 深度神经网络
当我们将简单的单层神经网络中的隐藏层扩展出来多层时,就得到了深度神经网络。
深度神经网络4. 前向计算
神经元模型在数学上,完成了y=f(wx+b)这样一个计算过程:输入*权值求和偏移后,使用非线性的激活函数求得输出的值。
y=f(wx+b)以下截图来源于知乎,数学公式在简书中不支持,只能截图了。
y=f(wx+b)就是全连接神经网络的前向计算公式。
我们可以在训练过程中,不断的调整权值w和偏置项b的值,就可以让整个神经网络表现出我们想要的行为。
5. 激活函数
人们在研究生物神经细胞时发现,当神经元的兴奋程度超过某个限度,神经元就会被激活而输出神经脉冲,当神经元的兴奋程度低于某个限度时,神经元就不会被激活,也就不会发出神经脉冲。
在自然界,生物神经元的输出和输入并不是按比例的关系,而是非线性的关系。
于是人们在设计人工神经网络时,就设计了一个叫做激活函数的东西,来对前面已经计算得到的结果做一个非线性计算(对应数学上的空间扭曲),这样的人工神经网络的表现力更好。
常用的激活函数有4种:
5.1 sigmoid函数
sigmoidsigmoid激活函数,符合实际,当输入值很小时,输出接近于0;当输入值很大时,输出值接近于1。
但sigmoid激活函数有较大的缺点,是主要有两点:
(1)容易引起梯度消失。当输入值很小或很大时,梯度趋向于0,相当于函数曲线左右两端函数导数趋向于0。
(2)非零中心化,会影响梯度下降的动态性。
5.2 tanh函数
tanh与sigmoid相比,输出至的范围变成了0中心化[-1, 1]。但梯度消失现象依然存在。
5.3 relu函数
relu有许多优点,是目前神经网络中使用最多的激活函数。
relu优点:
(1)不会出现梯度消失,收敛速度快;
(2)前向计算量小,只需要计算max(0, x),不像sigmoid中有指数计算;
(3)反向传播计算快,导数计算简单,无需指数、除法计算;
(4)有些神经元的值为0,使网络具有saprse性质,可减小过拟合。
缺点:比较脆弱,在训练时容易“die”,反向传播中如果一个参数为0,后面的参数就会不更新。使用合适的学习率会减弱这种情况。
5.4 leak relu函数
leak relu是对relu缺点的改进,当输入值小于0时,输出值为αx,其中α是一个很小的常数。这样在反向传播中就不容易出现“die”的情况。
6. 反向传播
当我们已经有了非线性的激活函数,我们只要在多层的非线性神经元上找到输出误差和权重的导数关系,就可以完成神经网络的训练。
反向传播就是利用了链式求导的性质,每次都是通过后一层的误差来计算前一层的误差,这样就避免了重复计算某一层的误差多次。从而节约了计算量,让大规模的神经网络有了可以被计算的可能。
7. 残差
从现象来看,就是损失函数对没有经过激励的运算结果值的偏导。本质上也可以看成是每一层运算结果值(没有激励)对误差的贡献。
残差其实是对z的偏导数。对于一个神经元,它和上一层的很多神经元相连接,这些神经元的输出经过一个加权,然后相加的结果就是Z,也就是说这z是神经元的真正输入,残差表示的就是最终的代价函数对网络中的一个个神经元输入的偏导。残差体现的是对于代价的贡献的敏感程度,对于一个大的残差,稍微给点输入,就不行了,导致最后的损失很大。z又是关于权重w的函数,所以,按照链式法则可以传递到w对代价函数的贡献敏感度上。
8. 其他概念
涉及的其他概念还有:损失函数、梯度下降、基于冲量的优化算法、Adagrad优化算法、Adadelta优化算法、Adam优化算法。
但是,常用的优化算法(相对于梯度下降算法),TensorFlow都已经内置了,工程角度来看,直接使用其API即可。
tf.train.GradientDescentOptimizer
tf.train.MomentumOptimizer
tf.train.AdagradOptimizer
tf.train.AdadeltaOptimizer
tf.train.RMSPropOptimizer
tf.train.AdamOptimizer
tf.train.FtrlOptimizer
9. 简单示例
下面是一个3层深度神经网络训练sin(x)的示例,每训练1000次绘制一次图,可以直观的看到训练过程。
#coding=utf-8
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
import pylab
'''
用TensorFlow来拟合一个正弦函数
'''
def draw_correct_line():
'''
绘制标准的sin曲线
'''
x = np.arange(0, 2 * np.pi, 0.01)
x = x.reshape((len(x), 1))
y = np.sin(x)
pylab.plot(x, y, label='standard sin')
plt.axhline(linewidth=1, color='r')
def get_train_data():
'''
返回一个训练样本(train_x, train_y),
其中train_x是随机的自变量, train_y是train_x的sin函数值
'''
train_x = np.random.uniform(0.0, 2 * np.pi, (1))
train_y = np.sin(train_x)
return train_x, train_y
def inference(input_data):
'''
定义前向计算的网络结构
Args:
输入的x的值,单个值
'''
with tf.variable_scope('hidden1'):
#第一个隐藏层,采用16个隐藏节点
weights = tf.get_variable("weight", [1, 16], tf.float32,
initializer=tf.random_normal_initializer(0.0, 1))
biases = tf.get_variable("biase", [1, 16], tf.float32,
initializer=tf.random_normal_initializer(0.0, 1))
hidden1 = tf.sigmoid(tf.multiply(input_data, weights) + biases)
with tf.variable_scope('hidden2'):
#第二个隐藏层,采用16个隐藏节点
weights = tf.get_variable("weight", [16, 16], tf.float32,
initializer=tf.random_normal_initializer(0.0, 1))
biases = tf.get_variable("biase", [16], tf.float32,
initializer=tf.random_normal_initializer(0.0, 1))
hidden2 = tf.sigmoid(tf.matmul(hidden1, weights) + biases)
with tf.variable_scope('hidden3'):
#第三个隐藏层,采用16个隐藏节点
weights = tf.get_variable("weight", [16, 16], tf.float32,
initializer=tf.random_normal_initializer(0.0, 1))
biases = tf.get_variable("biase", [16], tf.float32,
initializer=tf.random_normal_initializer(0.0, 1))
hidden3 = tf.sigmoid(tf.matmul(hidden2, weights) + biases)
with tf.variable_scope('output_layer'):
#输出层
weights = tf.get_variable("weight", [16, 1], tf.float32,
initializer=tf.random_normal_initializer(0.0, 1))
biases = tf.get_variable("biase", [1], tf.float32,
initializer=tf.random_normal_initializer(0.0, 1))
output = tf.matmul(hidden3, weights) + biases
return output
def train():
# 学习率
learning_rate = 0.01
x = tf.placeholder(tf.float32)
y = tf.placeholder(tf.float32)
net_out = inference(x)
# 定义损失函数的op
loss = tf.square(net_out - y)
# 采用随机梯度下降的优化函数
opt = tf.train.GradientDescentOptimizer(learning_rate)
train_op = opt.minimize(loss)
init = tf.global_variables_initializer()
with tf.Session() as sess:
sess.run(init)
print("start training....")
# 训练次数
train_times = 1000000
for i in range(train_times + 1):
train_x, train_y = get_train_data()
print("training %d" %i)
sess.run(train_op, feed_dict={x: train_x, y: train_y})
# 每完成10分之1,绘图检查一次
draw_check= train_times / 10
if ((i != 0) and (i % draw_check == 0)):
times = i
test_x_ndarray = np.arange(0, 2 * np.pi, 0.01)
test_y_ndarray = np.zeros([len(test_x_ndarray)])
ind = 0
for test_x in test_x_ndarray:
test_y = sess.run(net_out, feed_dict={x: test_x, y: 1})
np.put(test_y_ndarray, ind, test_y)
ind += 1
# 先绘制标准的sin函数的曲线,
# 再用虚线绘制我们计算出来模拟sin函数的曲线
draw_correct_line()
pylab.plot(test_x_ndarray, test_y_ndarray, '--', label=str(times) + ' times')
plt.legend()
pylab.show()
print("end.")
if __name__ == "__main__":
train()
训练1000次的结果图:
训练1000次训练5000次的结果图:
训练5000次训练10000次的结果图:
训练10000次训练10万次的结果图
训练10万次普通i5笔记本,没有使用GPU,训练10万次,用时在1分钟左右。
Kevin,2018年8月1日,成都。
网友评论