费 宁,张浩然,2
(1.南京邮电大学 计算机学院、软件学院,江苏 南京 210003;2.大连交通大学 软件学院,辽宁 大连 116028)
随着大数据技术的发展,简单的机器逻辑已经满足不了人们对于各种电子设备智能化的要求,因此人工智能应运而生。目前相关的研究主要集中在针对特定场景的算法优化和通用机器学习框架的开发。在后者当中尤其以Google的深度学习框架TensorFlow最为著名,TensorFlow目前已经支持众多编程语言、操作系统环境和硬件架构(CPU和GPU)[1]。TensorFlow是利用人工智能神经网络来分析和处理复杂数据结构的系统[2],是一种支持深度学习的开源软件库,可以实现卷积神经网络(CNN)、循环神经网络(RNN)和深度神经网络(DNN)等经典算法,并在语音识别、自然语言理解、计算机视觉等方面得到了广泛应用[3]。
TensorFlow(TF)顾名思义,是由tensor加flow组成。这里的tensor是指参加运算所需要的数据,其类型可以是整形、浮点和字符串;在TF中的存在形式可以是变量(tf.Variable)、常量(tf.constant)、占位符(tf.placeholder)以及稀疏矩阵(tf.SparseTensor);其维度可以为标量(Scalar)、一阶数组(Vector)、二阶矩阵(Matrix)以及其他高阶数据。flow是指数据的流向,实际所代表的是对数据的操作(数据类型转换、矩阵运算、梯度运算等)和流程的控制(循环、条件判断、命名空间等)。为了便于理解,Google使用一种数据流图的形式来表示,其中tensor用边表示,用来描述数据的流向(入数据和出数据);对数据所施加的操作用节点表示,没有冲突的操作可以并行运行,这也是GPU在很多场景中能够提高TF运算的原因。
Google采用了数据流图来形象地描述TensorFlow的运算过程。在数据流图中,整个计算过程用“节点”(nodes)和“边”(edges)的有向图来描述。“节点”一般用来表示施加的数学操作,或者表示数据输入的起点和输出的终点。“边”表示“节点”之间的输入/输出关系。这些数据“边”可以传递“大小可动态调整”的多维数据数组,即“张量”(tensor)[4]。
图1 TensorFlow数据流图示意
图1为一个简单的线性回归训练的数据流图。其中X为输入变量(往往为一阶数组,即Vector)在和初始预测的权重(weight)进行矩阵乘之后,加上偏移量(bias)即可得到估计的输出,这个结果和输入Y进行比较(图中的sub运算,也就是差运算)可以得到当次运算的损耗(loss)。实际上整个回归运算就是不断递归求出最小损耗的权重(weight)和偏移(bias)的过程[5-6]。这个过程一般采用梯度下降Gradients Descent[7-9]来验证是否收敛,从而提炼出最终的训练结果。
TensorFlow程序的构造如下所述:
下面的代码显示了TensorFlow程序的典型结构。该程序用线性回归来拟合一组权重为1、偏移量为0.3的数据。TensorFlow在编程时分成了三个部分:输入变量的初始化和预处理;训练方法的定义和参数的初始化;会话层的执行。
第一步,定义输入层的输入值x_data,是一百个随机生成的数据,输出层为y_data。在较复杂的神经网络中,可以先制定好定义框架,将具体的数据生成方式滞后定义。这样可以在整个神经网络框架的不同地方使用不同的数据生成方式调用这个变量,从而达到精简代码长度的效果。
第二步,定义TensorFlow结构体,如果涉及到神经网络结构的搭建,一般需要定义权重(weight)和偏移(bias)两个常用的矩阵[10-11]。在此例中,定义了weight的一维张量,是介于-1.0到1.0的随机数;同时初始化bias为0。此例目的是期望将weight和bias分别训练至y_data表达式中预设置的0.1和0.3。
再定义机器学习中最重要的损耗值(loss),也就是在经过某一组数据的训练后,TensorFlow建造的神经网络训练值与真实值的差值[12-13]。有了损耗值之后,计算机就可以通过比较训练值和真实值,并利用稍后定义的优化函数(optimizer)进行训练(train),一般通过梯度下降算法来实现。同时,optimizer优化函数还将对计算机学习的速率进行设定,学习速率是一个小于1的常数。
import tensorflow as tf //第一部分:定义输入变量
import numpy as np
x_data=np.random.rand(100).astype(np.float32) //x_data作为输入值
y_data=x_data*0.1+0.3 //y_data作为输出,预设权重为0.1,偏移为0.3
weights=tf.Variable(tf.random_uniform([1],-1.0,1.0))//定义初始权重 //第二部分:定义训练方法
biases=tf.Variable(tf.zeros([1])) //定义初始偏移
y=weights*x_data+biases //定义拟合算法,此处为线性回归
loss=tf.reduce_mean(tf.square(y-y_data)) //计算损耗
optimizer=tf.train.GradientDescentOptimizer(0.5) //定义优化函数,设置学习速率
train=optimizer.minimize(loss) //使用优化器训练以减少误差
init=tf.initialize_all_variables() //初始化所有变量
sess=tf.Session() //第三部分:会话执行
sess.run(init)//初始化变量
for step in range(201)://拟合平面
sess.run(train)
if step%20==0;
print(step,sess.run(weights),sess.run(biases))
第三步,会话层(session)的定义和执行。此例直接调用TensorFlow本身默认的会话层,同时还需要完成训练循环体的设定,训练循环体在train函数中定义,直接使用sess.run(train)来激活。在初始化会话中的变量之后,就可以开始训练,结果如图2所示。图2(a)是算法在学习效率为0.1时的情况,可以看到两个预测值逐渐逼近0.1和0.3,这说明算法能够有效学习,并且通过二者比值可以看出最终的学习准确率为99%;图2(b)显示设置学习效率为0.9时,由于下降梯度过快错过了最低点而导致算法无法收敛,所以学习无法继续。
(a)学习效率为0.1 (b)学习效率为0.9图2 学习效率分别为0.1和0.9时的输出值
在神经网络框架成型之后,下面可以建立一个具体的神经网络了。此处通过一个典型的三层神经网络来研究TensorFlow神经网络的实现机制,其包含一个输入层,一个隐藏层及一个输出层,其组成模块如图3所示。
图3 三层神经网络模块示意
以下的TensorFlow源代码来自GitHub(https://morvanzhou.github.io/),其具备良好的程序结构和清晰的注释,是一个典型的三层人工神经网络。该神经网络的关键代码如下。其作用是通过训练使计算机能够将二维平面上的散点拟合成一条折线。在定义的隐藏层结构中,定义了权重、偏移,每一次训练的算法为输入乘以权重加偏移、并且规定了相应的激励函数,这样就得到一个隐藏层模板。隐藏层训练的结果使用ReLU函数进行筛选修正,结果送至输出层进行处理。此处选择了目前广泛应用的ReLU函数,其原理是丢弃所有负值域且保持正值域的线性选择,运算速度较快。
x_data=np.linspace(-1,1,300)[:,np.newaxis] //数据输入输出的定义
noise=np.random.normal(0,0.05,x_data.shape)
y_data=np.square(x_data)-0.5+noise
xs=tf.placeholder(tf.float32,[None,1]) //定义节点准备接收数据
ys=tf.placeholder(tf.float32,[None,1])
l1=add_layer(xs,1,10,activation_function = tf.nn.relu) //定义神经层
prediction=add_layer(l1,10,1,activation_function=None)
loss=tf.reduce_mean(tf.reduce_sum(tf.square(ys-prediction), reduction_indices-[1])) //定义损耗
train_step=tf.train.GradientDescentOptimizer(0.1).minimize(loss) //选择优化器使loss最小
隐藏层定义十个神经元,这些神经元的输入即是隐藏层的输出,每个神经元的训练算法与隐藏层相同,即乘以权重加偏移,所不同的是这一层不再有激励函数,其输出即为最终训练的结果。
至此神经网络构建完毕。初始化所有定义的数据和神经层之后,将图放在默认的Session中运行。每一次训练之后,训练结果将通过feed函数反馈给神经网络以调整神经节点结构。学习过程可以使用matplotlib模块图像化为图4。图中散点是计算机随机生成的,其横轴数据即为图3中的一维向量X;黑色折线是计算机对这些散点的拟合。可以看到多数的点集中在黑色折线附近,表明该神经网络训练取得了比较好的训练结果。
图4 使用matplotlib可视化的训练结果
Google开发出TensorFlow中内置的神经网络可视化工具TensorBoard[14]。TensorBoard除了能够得到如图1所示的数据流图之外,还可以对训练过程中的各个分量进行观测,从而更好地理解训练过程及调整训练参数。
如果想要通过TensorBoard观察训练过程中的各个分量,就需要将相关变量加入tf.summary模块。目前支持的类型有一维数据(scalar)、直方图(histogram)、文本以及包含图形和声音的tensor综合信息等。在整个训练过程中,损耗的递减过程表明了整个训练持续收敛,也就是说所选择的激励函数能够在每一步有效地筛选训练结果。
在实际的训练过程中,可以观察每一步各个分量的分布情况。图5显示了第996次训练过程中隐藏层(图ReLu层)和输出层(图中Prediction层)的权重和偏移的分布情况。
这里需要注意的是,由于在每次训练过程中权重和偏移为一组向量,这里用直方图来表示其分布情况,并非代表每组向量的值。在实际调试过程中,也可以将任何类型的变量转化为字符串通过文本类型来显示。
图5 非一维数据(Scalar)的直方图可视化
通过对Google TensorFlow框架的研究,展示了使用TensorFlow完整实现人工神经网络的全过程。结合TensorFlow的数据流图以及TensorBoard所提供的可视化工具,进一步探索TensorFlow的实现机制及调试方法。