TensorFlow技术调研报告
TensorFlow是目前世界上最受欢迎的深度学习框架,主要应用于图像识别、语言理解、语音理解等领域方面。它具有快速、灵活并适合产品及大规模应用等特点。公司里的AI装维质检以及文本分析方面皆可通过TensorFlow实现。希望通过对本文的学习,大家对TensorFlow的有所了解,并可以使用TensorFlow做一些实践,体验一下TensorFlow的奇妙之处。
1 什么是TensorFlow?
TensorFlow是Google Brain的第二代机器学习系统,已经开源。TensorFlow受到了AI开发社区的广泛欢迎,是Github上最受欢迎的深度学习框架之一,也是整个社区上fork最多的项目。目前,TensorFlow已经被下载了超过790万次。Tensorflow是一个采用数据流图(data flow graphs),用于数值计算的开源软件库,常被应用于各种感知、语言理解、语音识别、图像识别等多项机器深度学习领域。
TensorFlow使用计算图表来执行其所有的计算。计算被表示为tf.Graph对象的一个实例,而其中的数据被表示为tf.Tensor对象,并使用tf.Operation对象对这样的张量对象进行操作。然后再使用tf.Session对象的会话中执行该图表。
图中节点(Nodes)一般表示施加的数学操作,或者表示数据输入(feed in)的起点/输出(push out)的终点,或者是读取/写入持久变量(persistent variable)的终点
图中线/边(edges)则表示在节点间相互联系的多维数据组,即张量(tensor)。
TF工作流程:
1) 建立一个计算图
2) 初始化变量
3) 创建会话
4) 在会话中运行计算流图
5) 关闭会话(保持代码完整性)
例子:用TF拟合逼近线性函数,这是TF实现线性回归算法的一个例子----帮助理解TF基本工作流程
##用TensorFlow拟合逼近线性函数 # 导入相关包 import tensorflow as tf import numpy as np ##create data ## x_data=np.random.rand(100).astype(np.float32) #随机生成100个浮点数 y_data=x_data*0.1+0.3 ##创建TensorFlow变量 Weights=tf.Variable(tf.random_uniform([1],-1.0,1.0))#创建一个变量,初始值是一个-1至1之间的随机值;[1]表示1维 biases=tf.Variable(tf.zeros([1])) #创建一个一维的初始值为0的变量 # 创建线性模型 y=Weights*x_data+biases # 最小方差 loss=tf.reduce_mean(tf.square(y-y_data)) optimizer=tf.train.GradientDescentOptimizer(0.5)#0.5为学习效率,此为优化器 train=optimizer.minimize(loss) # 初始化变量 init=tf.global_variables_initializer()#初始化 # 建立会话 with tf.Session() as sess: sess.run(init) # 激活变量初始化 for step in range(201):#迭代次数:201次 sess.run(train) if step%20==0: print(step,sess.run(Weights),sess.run(biases)) #打印迭代次数、Weights、biases
输出结果
2 TensorFlow基本概念
2.1 TF数据模型-tensor
2.1.1 张量的基本概念
Tensorflow中最基本的单位是常量、变量和占位符,这些所有的数据都通过张量(Tensor)的形式来表示。张量可以被理解为类型化的多维数组。0维张量是一个数字,也称为标量;1维张量称为“向量”,即一维数组;2维张量称为矩阵……第n阶张量可以理解为一个n维数组。一个张量中主要保存了三个属性:名字name、维度shape、类型type。
# eg: import tensorflow a=tf.constant([1,2,3]) b=tf.constant([2,3,5]) result=tf.add(a,b,name=”add”) print(result)
运行结果:tensor(“add:0”,shape=(3,),dtype=int32)
add:0表示result这个张量是计算节点”add”输出的第一个结果;
shape=(3, )表示张量是一个一维数组,这个数组的长度是3;
dtype=int32 是数据类型,TensorFlow会对参与运算的所有张量进行类型的检查,当类型不匹配时会报错。
2.1.2 张量的表示
1)TF常量表示
由tf.constant()构造。常量可作为储存超参数或其他结构信息的变量。如a=tf.constant(2,tf.int32,name=”a”),生成一个值为2,类型为int32,名为a的常量。
# tensor1是一个0维的int32 tensor tensor1=tf.constant(1234) # tensor2是一个1维的int32 tensor tensor2=tf.constant(123,456,789) # tensor3是一个二维的int32 tensor tensor3=tf.constant([123,456,789],[222,333,444])
2)TF变量表示
由tf.Variable()构造。在设置完成后改变,但变量的数据类型和形状无法改变。当训练模型时,变量一般用来存储和更新参数,建模时变量需要被明确地初始化。TensorFlow提供了一系列操作符来初始化张量,初始值是常量或是随机值。在初始化时需要指定张量的shape,变量的shape通常是固定的。如,b=tf.Variable([1,3],name=”vector”) 定义了一个名为vector的,shape为[2,],初始值为[1,3]的向量。
如 weights=tf.Variable(tf.random_normal([2,3],stddev=2),name=”weights”)
这里生成2*3的矩阵,元素的均值为0,标准差为2,初始值为随机值。
常数生成函数:tf.zeros()和tf.ones()能够创建一个初始值为0或1的张量。
随机生成函数:tf.random_normal()够创建一个包含多个随机值的张量。tf.truncated_normal()能够创建一个包含从截断的正态分布中随机抽取的值的张量。
3)TF占位符表示
由tf.placeholder()构造。占位符不需要初始值,仅用于分配必要的内存空间。输入输出数据在tf中是用placeholder占位符来定义的。
X = tf.placeholder(tf.float32, shape=[None, 100])
上面声明了一个张量X,数据类型是float,100列,行数不确定。
占位符在运行时,通过 Session.run 的函数的 feed_dict填入(外部)数据。如下所示
# 导入相关包 import tensorflow as tf input1=tf.placeholder(tf.float32)#定义一个浮点型的占位符 input2=tf.placeholder(tf.float32) output=tf.multiply(input1,input2) #相乘操作 # 建立会话 with tf.Session() as sess: # 将浮点型数据7和2通过feed_dict分别喂给input1和input2 print(sess.run(output,feed_dict={input1:[7.],input2:[2.]}))
结果输出:[14.]
2.2 TF运行模型-session
TensorFlow的运行模型为session(会话),会话拥有和管理TensorFlow程序运行时的所有资源,当计算完成后,需要关闭会话来帮助系统收回资源,否则就可能出现资源泄漏的问题。
tensorflow构建的计算图必须通过Session会话才能执行,如果只是在计算图中定义了图的节点但没有使用Session会话的话,就不能运行该节点。比如在tensorflow中定义了两个矩阵a和b,和一个计算a和b乘积的c节点,如果想要得到a和b的乘积(也就是c节点的运算结果)的话,必须要建立Session会话,并调用Session中的run方法运行c节点才行。
TensorFlow使用会话的方式有如下两种:
# 第一种 sess=tf.Session() #创建一个会话 sess.run(result) #运行此会话 sess.close() #运行完毕关闭此会话 # 第二种 #通过python的上下文管理器来使用会话,当上下文退出时会话管理和资源释放也自动完成 with tf.Session() as sess: sess.run(result)
运行结果:array([3,5,8],dtype=int32)
当使用第一种模式,程序因为异常而退出时,关闭会话的函数就可能不会执行从而导致资源泄漏,因此推荐使用第二种模式。
2.3 TF计算模型-graph
TensorFlow的计算模型为Graph(图),所有不同的变量以及对这些变量的操作都保存在图中。一个TensorFlow图描述了计算的过程,为了进行计算,图必须在会话里被启动,会话将图的op(节点)分发到诸如CPU或GPU之类的设备上,同时提供执行op的方法。这些方法执行后,将产生的tensor返回。TensorFlow中可利tf.summary.FileWriter()将计算图写入到文件中,通过tensorboard可视化计算流图。
import tensorflow as tf with tf.name_scope('graph') as scope: #创建两个张量(Tensor) a=tf.constant(5,name="a")#定义一个节点名称为a,值为b的常量 b=tf.constant(10,name="b") c=tf.add(a,b,name="add") #定义操作 #执行计算 with tf.Session() as sess: writer=tf.summary.FileWriter("logs/",sess.graph)#将计算图写入到logs/目录下的一个文件中 init=tf.global_variables_initializer() #初始化激活 sess.run(init)
执行上述代码后,在cmd中输入tensorboard --logdir=PATH\logs(logs文件夹的路径),得到一个网址,复制到浏览器即可查看tensorboard内容。
生成如下图:
TensorFlow的Tensor表明了它的数据结构,而Flow则直观地表现出张量之间通过计算互相转化的过程。TensorFlow中每一个计算都是图上的一个节点,节点之间的边描述了计算之间的依赖关系,如上图所示,a、b为常量,不依赖任何计算,而add计算则依赖读取两个常量的取值。
Tensorflow程序一般可以分为两个阶段。在第一个阶段要定义计算图中所有的计算,第二个阶段为执行计算。
在tensorflow程序中,系统会自动维护一个默认的计算图。可以通过tf.Graph函数生成新的计算图,需要注意的是:不同计算图上的张量和运算都不会共享。
Tensorflow中的计算图不仅仅可以用来隔离张量和计算,他还提供了管理张量和计算的机制。计算图可以通过函数来指定运行计算的设备。这为使用提供了机制。
import tensorflow as tf g=tf.Graph with g.device('/gpu:0'): result=a+b
3 TensorFlow训练神经网络(CNN)
人工神经网络是一种模仿生物神经网络的结构和功能的数学模型或者计算模型,主要用来做分类识别等应用。单个神经元的结构如下图所示。
输出是由输入数据乘以同纬度的权重向量加上一个偏置量所得,即Y=wx+b。神经网络的基本结构如下。神经网络的就是计算最佳参数的过程。
3.1 CNN基础知识
卷积神经网络CNN是一种前馈神经网络,它由三部分构成。第一部分是输入层。第二部分由n个卷积层和池化层(采样层)的组合组成。第三部分由一个全连接的多层感知机分类器构成。卷积层负责提取特征,采样层负责特征选择,全连接层负责分类。卷积神经网络,就是会自动的对于一张图片学习出最好的卷积核以及这些卷积核的组合方式,然后来进行判断。卷积神经网络以参数少、训练快、得分高、易迁移的特点全面碾压之前简单的神经网络。
?
? 汽车识别
·卷积层
卷积的主要目的是为了从输入图像中提取特征。卷积可以通过从输入的一小块数据中学到图像的特征,并可以保留像素间的空间关系。和传统的全连接层不同,卷积层中的每一个节点的输入只是上一层神经网络的一小块,这个小块常用的大小有33或者55。卷积层试图将神经网络中的每一小块进行更加深入地分析从而得到抽象程度更高的特征。一般来说,通过卷积层处理过的节点矩阵会变得更深,如下图,这个结构被称为卷积核(Kernel)或过滤器(Filter),卷积核放在神经网络里,就代表对应的权重(weight)。
#卷积遍历垂直、水平方向步数为1(中间的两个1),SAME:边缘外自动补0,遍历相乘
tf.nn.conv2d(x, W, strides = [1,1,1,1], padding = 'SAME')
·池化层
池化层降低了各个特征图的维度,但可以保持大部分重要的信息。池化方法有下面几种方式:最大化、平均化、加和等等。对于最大池化(Max Pooling),定义了一个空间领域(比如,2*2的窗口),并从窗口内的修正特征图中取出最大的元素。除了最大池化,也可以用平均值,或者对窗口内的元素求和。在实际中,最大池化被证明效果更好一些。
池化函数可以逐渐降低输入表示的空间尺度,使输入数据(特征维度)变得更小,并且网络中的参数和计算的数量更加可控的减小,因此可以控制过拟合。
*#池化层采用kernel大小为2*2,步数也为2,周围补0**,取最大值*
tf.nn.max_pool(x, ksize=[1,2,2,1], strides=[1,2,2,1], padding='SAME')
3.2 TensorFlow训练神经网络步骤
包含神经网络的图(如上图所示)应包含以下步骤:
- 输入数据集:训练数据集和标签、测试数据集和标签(以及验证数据集和标签)。
测试和验证数据集可以放在tf.constant()中。而训练数据集被放在tf.placeholder()中,这样它可以在训练期间分批输入(随机梯度下降)。 - 神经网络模型及其所有的层定义,即定义神经网络的结构和前向传播的输出结果。如卷积层、池化层、全连接层等。这可以是一个简单的完全连接的神经网络,仅由一层组成,或者由5、9、16层组成的更复杂的神经网络。
- 权重矩阵和偏差矢量以适当的形状进行定义和初始化。(每层一个权重矩阵和偏差矢量)
- 损失值:模型可以输出分对数矢量(估计的训练标签),并通过将分对数与实际标签进行比较,计算出损失值(具有交叉熵函数的softmax)。损失值表示估计训练标签与实际训练标签的接近程度,并用于更新权重值。
- 优化器:它用于将计算得到的损失值来更新反向传播算法中的权重和偏差。
最后生成会话并且将预处理后的数据喂入神经网络,反复运行反向传播优化算法。
完整实例程序如下:
import tensorflow as tf from numpy.random import RandomState batch_size=8 #定义训练数据batch的大小 # 定义权重变量 w1=tf.Variable(tf.random_normal([2,3],stddev=1,seed=1)) w2=tf.Variable(tf.random_normal([3,1],stddev=1,seed=1)) # 定义输入数据的占位符 x=tf.placeholder(tf.float32,shape=(None,2),name="x-input") y_=tf.placeholder(tf.float32,shape=(None,1),name="y-input") a=tf.matmul(x,w1) #定义操作相乘 y=tf.matmul(a,w2) #定义损失函数和反向传播的算法 cross_entropy=-tf.reduce_mean(y_*tf.log(tf.clip_by_value(y,1e-10,1.0))) train_step=tf.train.AdamOptimizer(0.001).minimize(cross_entropy)#优化器,学习速率是0.001 rdm=RandomState(1)#随机数生成器 X=rdm.rand(128,2) #随机生成128*2的数组 Y=[[int(x1+x2<1)] for (x1,x2) in X] #标签Y值:0/1,128行 with tf.Session() as sess: init_op=tf.global_variables_initializer() sess.run(init_op) #输出目前(未经训练)的参数取值 print("w1:",sess.run(w1)) print("w2:",sess.run(w2)) print("\n") #训练模型 steps=5000 #迭代次数5000次 for i in range(steps): start=(i*batch_size)%128 end=(i*batch_size)%128 +batch_size sess.run(train_step,feed_dict={x:X[start:end],y_:Y[start:end]})#喂数据 if i%1000==0: total_cross_entropy=sess.run(cross_entropy,feed_dict={x:X,y_:Y})#交叉熵损失函数 print("After %d step(s),crossentropy on all data is %g" %(i,total_cross_entropy)) # 输出训练后的参数取值,运行结果如 print("\n") print("w1:",sess.run(w1)) print("w2:",sess.run(w2))
训练前权重矩阵:
训练后权重矩阵:
3.3 MNIST手写数字识别实例
举个常见例子,手写数字识别---图片分类问题
MNIST数据集
---包含70000张手写数字黑白图片,将其中60000张作为训练集,10000张作为测试集。每个MNIST数据单元有两部分组成:一张包含手写数字的图片xs(每一张图片包含28x28像素)和一个对应的标签ys
把图片这个数组展开成一个向量,长度是28x28 = 784。如何展开(横向/纵向…)不重要,只要保持各个图片采用相同的方式展开就行(注:这里只是简单介绍TF基本模型,对于图片的二维结构信息暂不考虑)
在训练集中,xs是一个形状为[60000, 784]的张量,第一维用来索引图片,第二维用来索引每张图片中的像素点;每个标签ysi为一个one-hot向量,形如[0,1,0,0,0,0,0,0,0,0]
MNIST的每张图片代表一个0-9的数字,现在希望得到给定图片代表每个数字的概率,这是一个典型的softmax回归案例。简单介绍softmax回归模型
对于输入的xs加权求和,再分别加上一个偏置项,最后再输入到softmax函数中,转成概率形式:
代码:
# 导入相关包 import tensorflow as tf from tensorflow.examples.tutorials.mnist import input_data from PIL import Image, ImageFilter import matplotlib.pyplot as plt # 读取图片数据集 mnist = input_data.read_data_sets('../data/MNIST_data/',one_hot=True) '''模型构建''' # 声明一个占位符,None表示输入图片的数量不定,28*28图片分辨率,即 x = tf.placeholder(tf.float32, [None, 784]) # 类别是0-9数字,总共10个类别,对应输出分类结果 y_ = tf.placeholder(tf.float32, [None, 10]) def weight_variable(shape): # 正态分布,标准差为0.1,默认最大为1,最小为-1,均值为0 initial = tf.truncated_normal(shape,stddev = 0.1) return tf.Variable(initial) def bias_variable(shape): # 创建一个结构为shape矩阵也可以说是数组shape声明其行列,初始化所有值为0.1 initial = tf.constant(0.1,shape = shape) return tf.Variable(initial) def conv2d(x,W): # 卷积遍历垂直、水平方向步数为1(中间的两个1),SAME:边缘外自动补0,遍历相乘 return tf.nn.conv2d(x, W, strides = [1,1,1,1], padding = 'SAME') def max_pool_2x2(x): # 池化卷积结果(conv2d)池化层采用kernel大小为2*2,步数也为2,周围补0,取最大值。数据量缩小了4倍 return tf.nn.max_pool(x, ksize=[1,2,2,1], strides=[1,2,2,1], padding='SAME') # x_image又把xs reshape成了28*28*1的形状,因为是灰色图片,所以通道是1.作为训练时的input,-1代表图片数量不定 x_image = tf.reshape(x,[-1,28,28,1]) ''' 第一层卷积操作 ''' # 第一二参数值得卷积核尺寸大小,即patch,5*5,第三个参数是图像通道数,第四个参数是卷积核的数目,代表会出现多少个卷积特征图像; W_conv1 = weight_variable([5, 5, 1, 32])#[卷积核的高度,卷积核的宽度,图像通道数,卷积核个数] # 对于每一个卷积核都有一个对应的偏置量。 b_conv1 = bias_variable([32]) # 图片乘以卷积核,并加上偏执量,卷积结果28x28x32,集32个28*28feature map h_conv1 = tf.nn.relu(conv2d(x_image,W_conv1) + b_conv1)#激活函数 relu,即 max(features, 0)。即将矩阵中每行的非最大值置0 # 池化结果14x14x32 卷积结果乘以池化卷积核 h_pool1 = max_pool_2x2(h_conv1) '''第二层卷积操作''' # 32通道卷积,卷积出64个特征 W_conv2 = weight_variable([5, 5, 32, 64]) # 64个偏置数据 b_conv2 = bias_variable([64]) # 注意h_pool1是上一层的池化结果,#卷积结果14x14x64 h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2) # 池化结果7x7x64 h_pool2 = max_pool_2x2(h_conv2) # 原图像尺寸28*28,第一轮图像缩小为14*14,共有32张,第二轮后图像缩小为7*7,共有64张 '''全连接层操作''' # 二维张量,第一个参数7*7*64的patch,也可以认为是只有一行7*7*64个数据的卷积,第二个参数代表加入一个有1024个神经元的全连接层 W_fc1 = weight_variable([7 * 7 * 64, 1024]) # 1024个偏执数据 b_fc1 = bias_variable([1024]) # 将第二层卷积池化结果reshape成只有一行7*7*64个数据# [n_samples, 7, 7, 64] ->> [n_samples, 7*7*64] h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64]) # 卷积操作,结果是1*1*1024,单行乘以单列等于1*1矩阵,matmul实现最基本的矩阵相乘,不同于tf.nn.conv2d的遍历相乘,自动认为是前行向量后列向量 h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1) # dropout操作,减少过拟合,其实就是降低上一层某些输入的权重scale,甚至置为0,升高某些输入的权值,甚至置为2,防止评测曲线出现震荡,个人觉得样本较少时很必要 # 设置神经元被选中的概率使用占位符,由dropout自动确定scale,也可以自定义,比如0.5, keep_prob = tf.placeholder("float") #对卷积结果执行dropout操作 h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob) '''输出层''' # 二维张量,1*1024矩阵卷积,共10个卷积,对应我们开始的ys长度为10 W_fc2 = weight_variable([1024, 10]) b_fc2 = bias_variable([10]) # 最后的分类,结果为1*1*10 向量化后的图片x和权重矩阵W相乘,加上偏置b y_conv=tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2) '''定义loss(最小误差概率),选定优化优化loss''' # 定义交叉熵为loss函数 cross_entropy = -tf.reduce_sum(y_*tf.log(y_conv)) #y_表示期望输出,实际值 # 调用优化器优化,其实就是通过喂数据争取cross_entropy最小化,le-4是学习速率 #train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy) train_step = tf.train.GradientDescentOptimizer(1e-3).minimize(cross_entropy)#梯度下降法 # 数据训练及评估 #tf.argmax(y,1)返回的是模型对于任一输入x预测到的标签值,tf.argmax(y_,1) 代表正确的标签 correct_prediction = tf.equal(tf.argmax(y_conv,1), tf.argmax(y_,1))# 返回的是一个布尔数组 accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))#将布尔值转换为浮点数,来代表对、错,然后取平均值 saver = tf.train.Saver() #定义saver,保存模型 with tf.Session() as sess: # 初始化所以张量 sess.run(tf.global_variables_initializer()) '''模型训练,测试时此部分注释掉''' for i in range(20000): #迭代次数20000 #一次训练50个样本 batch = mnist.train.next_batch(50) if i % 100 == 0: train_accuracy = accuracy.eval(feed_dict={ x: batch[0], y_: batch[1], keep_prob: 1.0}) print('step %d, training accuracy %g' % (i, train_accuracy)) train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5}) saver.save(sess, 'model/model.ckpt') #模型储存位置 print('test accuracy %g' % accuracy.eval(feed_dict={ x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0})) '''模型测试,训练时此部分注释掉''' # saver.restore(sess, "model/model.ckpt") #使用模型,参数和之前的代码保持一致 # prediction=tf.argmax(y_conv,1) # while True: # adress = input("请输入图片地址:")#形如'test_num/num5.png' # im = Image.open(str(adress)) #读取的图片所在路径,注意是28*28像素 # plt.imshow(im) #显示需要识别的图片 # plt.show() # im = im.convert('L')# 转换成灰色图像 # tv = list(im.getdata()) # tva = [(255-x)*1.0/255.0 for x in tv] #像素值归一化 # # predint=prediction.eval(feed_dict={x: [tva],keep_prob: 1.0}, session=sess) # print('识别结果:') # print(predint[0])
参考资料
1、TF官方文档中文版
http://wiki.jikexueyuan.com/project/tensorflow-zh/
2、《tensorflow实战google深度学习框架》
3、《机器学习速成课程-使用TensorFlow AP》
https://developers.google.cn/machine-learning/crash-course/ml-intro
4、知乎专栏: