Tensorflow入门一条龙
-
- 前景提要
- Tensorflow介绍
- Tensorflow安装
- 梯度下降
- 处理结构
-
- Tensorflow结构
- Tensor 介绍
- Tensorflow 编程
- Session会话控制
- Variable 定义变量
- Placeholder 输入值设置
- 激励函数 (Activation Function)
- 激励函数Activation Function
- 添加神经层 -- 激励函数添加图层
- 建立神经网络
- 结果可视化
- 加速神经网络训练
-
- Stochastic Gradient Descent (SGD)
- Momentum
- AdaGrad
- RMSProp
- Adam
- 最后还是希望你们能给我点一波小小的关注。
- 奉上自己诚挚的爱心
前景提要
本想写一期强化学习DQN的教程,害怕出现劝退的现象,那就先教一期Tensorflow。
根据前一期深度学习实战手写体识别上来看,有些人跟我说Tensorflow基础没打好,直接上战场的话啥也看不懂,那这期从入门到入土的总结一下Tensorflow吧。
以往文章: 手写体识别实战教程
Tensorflow介绍
Tensorflow 是一个使用数据流图进行数值计算的开放源代码软件库。 图中的节点代表数学运算,而图中的边则代表在这些节点之间传递的多维数组(张量)。借助这种灵活的架构,您可以通过一个 API 将计算工作部署到桌面设备、服务器或移动设备中的一个或多个 CPU 或 GPU。Tensorflow 最初是由 Google Brain 团队(隶属于 Google 机器智能研究部门)中的研究人员和工程师开发的,旨在用于进行机器学习和深度神经网络研究。但该系统具有很好的通用性,还可以应用于众多其他领域。
Tensorflow安装
Tensorflow分为GPU版本以及CPU版本
pip安装CPU版本Tensorflow:pip install --upgrade --ignore-installed tensorflow
pip安装GPU版本Tensorflow:pip install --upgrade --ignore-installed tensorflow-gpu
(前提是电脑需要支持GPU版本才可以安装)
然后检测安装环境
梯度下降
梯度下降是迭代法的一种,可以用于求解最小二乘问题(线性和非线性都可以)。在求解机器学习算法的模型参数,即无约束优化问题时,梯度下降(Gradient Descent)是最常采用的方法之一,另一种常用的方法是最小二乘法。在求解损失函数的最小值时,可以通过梯度下降法来一步步的迭代求解,得到最小化的损失函数和模型参数值。反过来,如果我们需要求解损失函数的最大值,这时就需要用梯度上升法来迭代了。在机器学习中,基于基本的梯度下降法发展了两种梯度下降方法,分别为随机梯度下降法和批量梯度下降法。
梯度下降算法(Gradient Descent Optimization)是神经网络模型训练最常用的优化算法。对于深度学习模型,基本都是采用梯度下降算法来进行优化训练的。梯度下降算法背后的原理:目标函数J(θ) 关于参数θ的梯度将是目标函数上升最快的方向。对于最小化优化问题,只需要将参数沿着梯度相反的方向前进一个步长,就可以实现目标函数的下降。这个步长又称为学习速率η。参数更新公式如下:
其中 ∇ \nabla ∇ J(θ) 是参数的梯度,根据计算目标函数J(θ)采用数据量的不同,梯度下降算法又可以分为批量梯度下降算法(Batch Gradient Descent),随机梯度下降算法(Stochastic GradientDescent)和小批量梯度下降算法(Mini-batch Gradient Descent)。对于批量梯度下降算法,其J(θ)是在整个训练集上计算的,如果数据集比较大,可能会面临内存不足问题,而且其收敛速度一般比较慢。随机梯度下降算法是另外一个极端,J(θ)是针对训练集中的一个训练样本计算的,又称为在线学习,即得到了一个样本,就可以执行一次参数更新。所以其收敛速度会快一些,但是有可能出现目标函数值震荡现象,因为高频率的参数更新导致了高方差。小批量梯度下降算法是折中方案,选取训练集中一个小批量样本计算J(θ),这样可以保证训练过程更稳定,而且采用批量训练方法也可以利用矩阵计算的优势。这是目前最常用的梯度下降算法。
神经网络中的 W 可不止一个, 如果只有一个 W, 我们就能画出之前那样的误差曲线, 如果有两个 W 也简单, 我们可以用一个3D 图来展示, 可是超过3个 W, 我们可就没办法很好的可视化出来啦. 这可不是最要命的. 在通常的神经网络中, 误差曲线可没这么优雅.
处理结构
Tensorflow结构
Tensorflow 首先要定义神经网络的结构, 然后再把数据放入结构当中去运算和 training。
因为TensorFlow是采用数据流图(data flow graphs)来计算,所以首先我们得创建一个数据流流图, 然后再将我们的数据(数据以张量(tensor)的形式存在)放在数据流图中计算. 节点(Nodes)在图中表示数学操作,图中的线(edges)则表示在节点间相互联系的多维数据数组, 即张量(tensor). 训练模型时tensor会不断的从数据流图中的一个节点flow到另一节点, 这就是TensorFlow名字的由来.
Tensor 介绍
张量(Tensor): 张量有多种. 零阶张量为 纯量或标量 (scalar) 也就是一个数值. 比如 [1]
一阶张量为 向量 (vector), 比如 一维的 [1, 2, 3]
二阶张量为 矩阵 (matrix), 比如 二维的 [[1, 2, 3],[4, 5, 6],[7, 8, 9]]
以此类推, 还有 三阶 三维的 …
Tensorflow 编程
首先加载 tensorflow 和 numpy 两个模块,
# 为了防止2.x的Tensorflow报错。在这里直接使用兼容版本
# 此次教程中均适用该兼容版本
import tensorflow.compat.v1 as tf
tf.disable_v2_behavior()
# 导入Numpy
import numpy as np
使用 numpy 来创建我们的数据
# 生成一百个随机数列,以float32数据定制
x_data = np.random.rand(100).astype(np.float32)
# 预测W和b的值
y_data = x_data*0.1 + 0.3
接着, 我们用 tf.Variable
来创建描述 y 的参数. 我们可以把 y_data = x_data*0.1 + 0.3
想象成 y=Weights * x + biases
, 然后神经网络也就是学着把 Weights 变成 0.1, biases 变成 0.3.
# tf中定义变量需要使用Variable,使用随机数列生成方式生成一维结构,-1到1之间
Weights = tf.Variable(tf.random_uniform([1], -1.0, 1.0))
# 初始给b的值为0,训练结尾尽量靠近3
biases = tf.Variable(tf.zeros([1]))
# 预测值Y定义
y = Weights*x_data + biases
# 计算预测值与真实值之间的差别
loss = tf.reduce_mean(tf.square(y-y_data))
# 使用梯度下降优化器减少误差,提高参数准确度,学习度为0.5
optimizer = tf.train.GradientDescentOptimizer(0.5)
train = optimizer.minimize(loss)
# 初始化Variable这个变量,不初始化无法定义新变量
init = tf.global_variables_initializer()
接着,我们再创建会话 Session
. 我们用 Session
来执行 init
初始化步骤. 并且, 用 Session
来 run
每一次 training
的数据. 逐步提升神经网络的预测准确性.
# 定义一个Session对象
with tf.Session() as sess:
# 运行初始化,激活整个神经网络
sess.run(init)
# 训练两百次
for step in range(200):
# 训练train梯度下降
sess.run(train)
# 每二十步打印一次变量,每次使用tf中参数都需要run
if step % 20 == 0:
print(step, sess.run(Weights), sess.run(biases))
运行结果展示,我们能发现随机产生的数据在经过两百次的训练之下,线性回归于y_data = x_data*0.1 + 0.3
结果值,说明此次梯度下降成功。
Session会话控制
Tensorflow 中的 Session
, Session
是 Tensorflow 为了控制,和输出文件的执行的语句. 运行 session.run()
可以获得你要得知的运算结果, 或者是你所要运算的部分.
# 定义两个常量
matrix1 = tf.constant([[3,3]])
matrix2 = tf.constant([[2],
[2]])
# tf内部的矩阵乘法,相当于np.dot(matrix1,matrix2)
product = tf.matmul(matrix1,matrix2)
当我们使用之后,我们打印一次该变量值,我们会发现他是个 tf变量,也就是在这个时候,Tensorflow并没有进行该运算
print(product)
# tf.Tensor([[12]], shape=(1, 1), dtype=int32)
当我们使用Session运行该变量时,打印该结果,我们会发现他会为我们计算矩阵乘法
with tf.Session() as sess:
print(sess.run(product))
# [[12]]
Variable 定义变量
Tensorflow 中定义变量与Python中定义变量有一些不一样。
- tf中定义了某字符串是变量,它才是变量。否则他就是常量。
- tf中定义变量后必须需要初始化才可以使用。
定义语法: state = tf.Variable()
# 定义变量,初始值为0,给该变量一个名字为counter
state = tf.Variable(0, name='counter')
# print(state.name)
# -> counter
# 定义常量
one = tf.constant(1)
# 定义加法步骤 (注: 此步并没有直接计算)
new_value = tf.add(state, one)
# 将 state 更新成 new_value
update = tf.assign(state, new_value)
# 当你创建变量之后,一定要初始化创建函数才能使用!
# 一定要定义 init = tf.global_variables_initializer()
init = tf.global_variables_initializer()
# 使用 Session
with tf.Session() as sess:
sess.run(init)
for _ in range(3):
sess.run(update)
print(sess.run(state))
运行结果展示,我们会发现循环三遍,每次都运行一次update功能,打印state也必须写在sess.run()中才能正常输出,否则输出无效。
Placeholder 输入值设置
placeholder
是 Tensorflow 中的占位符,暂时储存变量。相当于 Python 中的 input() 方法。
Tensorflow 如果想要从外部传入data, 那就需要用到 tf.placeholder()
, 然后以这种形式传输数据 sess.run(***, feed_dict={input: **})
#在 Tensorflow 中需要定义 placeholder 的 type ,一般为 float32 形式
input1 = tf.placeholder(tf.float32)
input2 = tf.placeholder(tf.float32)
# mul = multiply 是将input1和input2 做乘法运算,并输出为 output
ouput = tf.multiply(input1, input2)
接下来, 传值的工作交给了 sess.run()
, 需要传入的值放在了feed_dict={}
并一一对应每一个 input. placeholder
与 feed_dict={}
是绑定在一起出现的。
with tf.Session() as sess:
print(sess.run(ouput, feed_dict={ input1: [9.], input2: [3.]}))
# [ 27.]
激励函数 (Activation Function)
神经网络中激励函数的作用通俗上讲就是将多个线性输入转换为非线性的关系。如果不使用激励函数的话,神经网络的每层都只是做线性变换,即使是多层输入叠加后也还是线性变换。通过激励函数引入非线性因素后,使神经网络的表示能力更强了。
介绍几个常用的激励函数:
-
sigmoid 函数
这应该是神经网络中使用最频繁的激励函数了,它把一个实数压缩至0到1之间,当输入的数字非常大的时候,结果会接近1,当输入非常大的负数时,则会得到接近0的结果。在早期的神经网络中使用得非常多,因为它很好地解释了神经元受到刺激后是否被激活和向后传递的场景(0:几乎没有被激活,1:完全被激活),不过近几年在深度学习的应用中比较少见到它的身影,因为使用sigmoid函数容易出现梯度弥散或者梯度饱和。当神经网络的层数很多时,如果每一层的激励函数都采用sigmoid函数的话,就会产生梯度弥散的问题,因为利用反向传播更新参数时,会乘以它的导数,所以会一直减小。如果输入的是比较大或者比较小的数(例如输入100,经Sigmoid函数后结果接近于1,梯度接近于0),会产生饱和效应,导致神经元类似于死亡状态。
-
tanh 函数
tanh函数将输入值压缩至-1到1之间。该函数与Sigmoid类似,也存在着梯度弥散或梯度饱和的缺点。 -
ReLU函数
ReLU是修正线性单元(The Rectified Linear Unit)的简称,近些年来在深度学习中使用得很多,可以解决梯度弥散问题,因为它的导数等于1或者就是0。相对于sigmoid和tanh激励函数,对ReLU求梯度非常简单,计算也很简单,可以非常大程度地提升随机梯度下降的收敛速度。(因为ReLU是线性的,而sigmoid和tanh是非线性的)
但ReLU的缺点是比较脆弱,随着训练的进行,可能会出现神经元死亡的情况,例如有一个很大的梯度流经ReLU单元后,那权重的更新结果可能是,在此之后任何的数据点都没有办法再激活它了。如果发生这种情况,那么流经神经元的梯度从这一点开始将永远是0。也就是说,ReLU神经元在训练中不可逆地死亡了。 -
Leaky ReLU 函数
Leaky ReLU主要是为了避免梯度消失,当神经元处于非激活状态时,允许一个非0的梯度存在,这样不会出现梯度消失,收敛速度快。它的优缺点跟ReLU类似。 -
ELU 函数
ELU在正值区间的值为x本身,这样减轻了梯度弥散问题(x>0区间导数处处为1),这点跟ReLU、Leaky ReLU相似。而在负值区间,ELU在输入取较小值时具有软饱和的特性,提升了对噪声的鲁棒性 -
Maxout 函数
Maxout也是近些年非常流行的激励函数,简单来说,它是ReLU和Leaky ReLU的一个泛化版本,当w1、b1设置为0时,便转换为ReLU公式。
因此,Maxout继承了ReLU的优点,同时又没有“一不小心就挂了”的担忧。但相比ReLU,因为有2次线性映射运算,因此计算量也会翻倍。
激励函数Activation Function
激励函数运行时激活神经网络中某一部分神经元,将激活信息向后传入下一层的神经系统。激励函数的实质是非线性方程。 Tensorflow 的神经网络 里面处理较为复杂的问题时都会需要运用激励函数 activation function
。
添加神经层 – 激励函数添加图层
定义添加神经层的函数def add_layer()
,它有四个参数:输入值、输入的大小、输出的大小和激励函数,我们设定默认的激励函数是None
。
因为在生成初始参数时,随机变量(normal distribution)会比全部为0要好很多,所以我们这里的weights为一个in_size行, out_size列的随机变量矩阵。
在机器学习中,biases的推荐值不为0,所以我们这里是在0向量的基础上又加了0.1。
定义Wx_plus_b, 即神经网络未激活的值。其中,tf.matmul()是矩阵的乘法。
# 定义函数,四个参数值
def add_layer(inputs, in_size, out_size, activation_function=None):
# 定义一个in_size行out_size列的矩阵
Weights = tf.Variable(tf.random_normal([in_size, out_size]))
# 定义一个一行out_size列的均为0.1的矩阵
biases = tf.Variable(tf.zeros([1, out_size]) + 0.1)
# 定义预测值为Wx+b
Wx_plus_b = tf.matmul(inputs, Weights) + biases
# 如果是线性关系的话,只需要保留现状就好
if not activation_function:outputs = Wx_plus_b
# 如果是非线性方程,我们只需要将值传入激励函数中即可
else:outputs = activation_function(Wx_plus_b)
# 返回outputs
return outputs
建立神经网络
我们现在需要建立一个神经网络层,运算出和真实值的对比再准备提升这个预测值。
包括添加神经层,计算误差,训练步骤,判断是否在学习。
我们所构建的x_data
和y_data
并不是严格的一元二次函数的关系,因为我们多加了一个noise
,这样看起来会更像真实情况。
# 建立一个-1到1的区间,含有三百行的等差数列
x_data = np.linspace(-1,1,300, dtype=np.float32)[:, np.newaxis]
# 添加一个正态分布的噪点,均值为0,标准差为0.05,shape与x_data相同
noise = np.random.normal(0, 0.05, x_data.shape).astype(np.float32)
# 建立一个x的二次方 - 0.5,并加上一个噪点
y_data = np.square(x_data) - 0.5 + noise
利用占位符定义我们所需的神经网络的输入。 tf.placeholder()
就是代表占位符,这里的None代表无论输入有多少都可以,因为输入只有一个特征,所以这里是1。
xs = tf.placeholder(tf.float32, [None, 1])
ys = tf.placeholder(tf.float32, [None, 1])
接下来,我们就可以开始定义神经层了。 通常神经层都包括输入层、隐藏层和输出层。这里的输入层只有一个属性, 所以我们就只有一个输入;隐藏层我们可以自己假设,这里我们假设隐藏层有10个神经元; 输出层和输入层的结构是一样的,所以我们的输出层也是只有一层。 所以,我们构建的是——输入层1个、隐藏层10个、输出层1个的神经网络。
# 定义隐藏层,使用 Tensorflow 自带的激励函数tf.nn.relu
l1 = add_layer(xs, 1, 10, activation_function=tf.nn.relu)
# 定义输出层。此时的输入就是隐藏层的输出——l1,输入有10层(隐藏层的输出层),输出有1层。
prediction = add_layer(l1, 10, 1, activation_function=None)
# 计算损失函数---预测值prediction和真实值的误差,对二者差的平方求和再取平均。
loss = tf.reduce_mean(tf.reduce_sum(tf.square(ys - prediction),
reduction_indices=[1]))
# 在这里提高学习准确率,我们仍然使用随机梯度下降方法,以0.1的效率来最小化误差loss
train_step = tf.train.GradientDescentOptimizer(0.1).minimize(loss)
# 初始化变量,很重要
init = tf.global_variables_initializer()
这次实验学习1000次。机器学习的内容是train_step
, 用 Session 来 run 每一次 training 的数据,逐步提升神经网络的预测准确性。
with tf.Session() as sess:
sess.run(init)
for i in range(1000):
sess.run(train_step, feed_dict={ xs: x_data, ys: y_data})
if i % 50 == 0:
print(sess.run(loss, feed_dict={ xs: x_data, ys: y_data}))
运行结果展示,误差在逐渐减小,这说明机器学习是有积极的效果的。
结果可视化
我们这次使用的是matplotlib
模块
下载:pip install matplotlib
构建图形,用散点图描述真实数据之间的关系。
# 生成一个图片框
fig = plt.figure()
# 生成一个连续性的图片集合,制作一个1*1的位置图集
ax = fig.add_subplot(1,1,1)
# 制作散点图,将x和y传入其中
ax.scatter(x_data, y_data)
# 打开交互模式
plt.ion()#本次运行请注释,全局运行不要注释
# 输出图片
plt.show()
输出的可视化:
接下来,我们来显示预测数据。
每隔50次训练刷新一次图形,用红色、宽度为5的线来显示我们的预测数据和输入之间的关系,并暂停0.1s。
for i in range(1000):
sess.run(train_step, feed_dict={ xs: x_data, ys: y_data})
if i % 50 == 0:
# 绘制之后开始画下一条线,将上个线抹除掉
try:ax.lines.remove(lines[0])
except Exception:pass
prediction_value = sess.run(prediction, feed_dict={ xs: x_data})
# 绘制曲线,x,预测y的数据,红色的线,宽度为5
lines = ax.plot(x_data, prediction_value, 'r-', lw=5)
# 暂停0.1秒,查看结果
plt.pause(0.1)
加速神经网络训练
越复杂的神经网络 , 越多的数据 , 我们需要在训练神经网络的过程上花费的时间也就越多. 原因很简单, 就是因为计算量太大了. 可是往往有时候为了解决复杂的问题, 复杂的结构和大数据又是不能避免的, 所以我们需要寻找一些方法, 让神经网络聪明起来, 快起来。
加速训练包括以下几种模式:
- Stochastic Gradient Descent (SGD)
- Momentum
- AdaGrad
- RMSProp
- Adam
Stochastic Gradient Descent (SGD)
所以, 最基础的方法就是 SGD 啦, 想像红色方块是我们要训练的 data, 如果用普通的训练方法, 就需要重复不断的把整套数据放入神经网络 NN训练, 这样消耗的计算资源会很大。
SGD 并不是最快速的训练方法, 红色的线是 SGD, 但它到达学习目标的时间是在这些方法中最长的一种. 我们还有很多其他的途径来加速训练。
Momentum
大多数其他途径是在更新神经网络参数那一步上动动手脚. 传统的参数 W 的更新是把原始的 W 累加上一个负的学习率(learning rate) 乘以校正值 (dx). 这种方法可能会让学习过程曲折无比, 看起来像 喝醉的人回家时, 摇摇晃晃走了很多弯路。
所以我们把这个人从平地上放到了一个斜坡上, 只要他往下坡的方向走一点点, 由于向下的惯性, 他不自觉地就一直往下走, 走的弯路也变少了. 这就是 Momentum 参数更新. 另外一种加速方法叫AdaGrad。
AdaGrad
这种方法是在学习率上面动手脚, 使得每一个参数更新都会有自己与众不同的学习率, 他的作用和 momentum 类似, 不过不是给喝醉酒的人安排另一个下坡, 而是给他一双不好走路的鞋子, 使得他一摇晃着走路就脚疼, 鞋子成为了走弯路的阻力, 逼着他往前直着走. 他的数学形式是这样的. 接下来又有什么方法呢? 如果把下坡和不好走路的鞋子合并起来, 是不是更好呢? 没错, 这样我们就有了 RMSProp 更新方法。
RMSProp
有了 momentum 的惯性原则 , 加上 adagrad 的对错误方向的阻力, 我们就能合并成这样. 让 RMSProp同时具备他们两种方法的优势. 不过细心的同学们肯定看出来了, 似乎在 RMSProp 中少了些什么. 原来是我们还没把 Momentum合并完全, RMSProp 还缺少了 momentum 中的 这一部分. 所以, 我们在 Adam 方法中补上了这种想法。
Adam
计算m 时有 momentum 下坡的属性, 计算 v 时有 adagrad 阻力的属性, 然后再更新参数时 把 m 和 V 都考虑进去. 实验证明, 大多数时候, 使用 adam 都能又快又好的达到目标, 迅速收敛. 所以说, 在加速神经网络训练的时候, 一个下坡, 一双破鞋子, 功不可没。