文章目录
- 前言
- 1. 安装Tensorflow-gpu
-
- 安装版本
- 系统变量
- pip install tensorflow-gpu==版本号
- 安装测试
- 2. 数据基础—张量
-
- 张量的轴(axis)
- 张量降维函数reduce_sum/max/mean/...
- (未完待续)
- 3. 数学基础—梯度下降
-
- SGD
- 4. Tensorflow基本操作
-
- 数据流图
- 基本操作
- 5. Tensorflow—手写数字分类
-
- 神经网络基本概念
- 全连接网络
- 卷积神经网络
前言
学习资料来源Tensorflow中文社区
1. 安装Tensorflow-gpu
Tensorflow有CPU版本和GPU加速两个版本,GPU加速版本在处理大型图像识别任务的时候速度会更快一些,但要求安装Cuda和cudnn,这对显卡有要求(本机显卡Nvidia GTX1060)。
安装版本
- Python的IDE为Pycharm,Tensorflow各个版本需要匹配python和Cuda以及cudnn,在安装前首先看看自己显卡支持的Cuda最高到版本(如下图在“帮助”—>“系统信息”里面看到,已经支持Cuda10了),现在最高版本的tensorflow只需要Cuda9,所以安装Cuda9,推荐版本:Python/Cuda/cudnn匹配版本
系统变量
- 安装Cuda,之前会检查兼容性,不兼容的话可以不用管,也能安装使用,然后一路默认安装即可。完成后将cudnn解压后的所有文件复制粘贴到Cuda对应的文件夹下C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v9.0。
- 用户变量Path下添加:
- C:\ProgramData\NVIDIA GPU Computing Toolkit\v9.2;
- 在系统变量中添加如下几个变量:
- CUDA_SDK_PATH = C:\ProgramData\NVIDIACorporation\CUDA Samples\v9.2
- CUDA_LIB_PATH = %CUDA_PATH%\lib\x64
- CUDA_BIN_PATH = %CUDA_PATH%\bin
- CUDA_SDK_BIN_PATH = %CUDA_SDK_PATH%\bin\win64
- CUDA_SDK_LIB_PATH = %CUDA_SDK_PATH%\common\lib\x64
pip install tensorflow-gpu==版本号
- tensorflow-gpu版本号
安装测试
import tensorflow as tf
hello =tf.constant("Hello!TensorFlow")
sess = tf.Session()
print(sess.run(hello))
2. 数据基础—张量
张量可以简单理解为矩阵,只是这个矩阵可以有很多维度,不仅是平时的一维向量或者二维矩阵。
张量的轴(axis)
为了帮助理解轴的概念,可以先简单定义3阶张量的构成(3阶以上张量在欧式空间没法理解):
- 1、首先每一个单独的元素按照行构成向量,如:[1,2,3],假设沿着1,2,3的方向叫做x向,即向量元素延伸的方向;
-
2、然后不同的向量沿垂直x的方向顺序放在一起,这个方向叫y向,即向量延伸的方向(注意区分向量元素延伸的方向),进而得到了矩阵,如:
[[1,2,3],
[4,5,6],
[7,8,9]]
(写成:[[1, 2, 3],[4, 5, 6],[7, 8, 9]])
-
3、最后,将矩阵沿着垂直于xy平面的方向顺序放在一起,这个方向叫z向,即矩阵延伸的方向,就得到3阶张量(长方体)。
[[[1, 2, 3],[4, 5, 6],[7, 8, 9]],
[[4, 5, 6],[7, 8, 6],[1, 3.3, 2]]]
因此,在3阶张量里面就有三个轴,x,y和z,其中z轴是表示矩阵延伸的方向,这是生成3阶张量的最后一步,把这个轴的索引设为0(理解为索引越小,步骤越靠后);y轴是表示向量延伸的方向,这是倒数第二步,这个轴索引设为1;x轴是表示向量元素 延伸的方向,这是倒数第3步,这个轴索引设为2。对于2阶或者1阶张量,只有沿x方向的1轴和沿y方向的0轴。
总结一下:沿着哪一个轴延伸的内容级别越高(假设级别高低为:矩阵>向量>元素),则哪一个轴的索引就越低。此外,在索引张量中的元素时候,比如索引3阶张量中的元素,通常用(a0,a1,a2)表示:其中,a0表示被索引元素在0轴(z)上的第a0个矩阵里面;a1表示被索引元素在a0这个矩阵的1轴(y)上的第a1个向量里面;a2表示被索引元素即为a1这个向量里面的2轴(x)上的第a2个元素。例如:上述3阶张量的“3.3”这个元素的索引为(2,3,2)。
张量降维函数reduce_sum/max/mean/…
理解了张量的轴,就可以使用reduce_sum/max/mean…这个函数了。之所以讲这个函数,就是因为在神经网络设计损失函数—loss函数时,需要用到reduce。reduce可以理解为降维,将高阶张量降维为低阶张量,后面的sum/max/mean这些可以理解为降维的方法。以sum为例,还是如下矩阵:
[[1,2,3],
[4,5,6],
[7,8,9]]
首先进行如下运算:
import tensorflow as tf
A = tf.constant([[1, 2, 3],[4, 5, 6],[7, 8, 9]], dtype= tf.float32)
B1 = tf.reduce_sum(A,reduction_indices=0)
sess = tf.Session()
print(sess.run(B1))
结果输出:[12. 15. 18.]
再进行以下运算:
import tensorflow as tf
A = tf.constant([[1, 2, 3],[4, 5, 6],[7, 8, 9]], dtype= tf.float32)
B2 = tf.reduce_sum(A,reduction_indices=1)
sess = tf.Session()
print(sess.run(B2))
结果输出:[ 6. 15. 24.]
可以发现,reduce_sum(A,reduction_indices=)中仅reduction_indices不一样,这个参数就是降维时候的参考轴。对于这个张量,索引轴为0,1。因此,reduction_indices=0时,意味将张量A沿着上述的“y”方向压缩,将这个矩阵上下挤扁,即[1+4+7, 2+5+8, 3+6+9] = [12. 15. 18.];reduction_indices=1时,意味将张量A沿着上述的“x”方向压缩,将这个矩阵左右挤扁,即[1+2+3, 4+5+6, 7+8+9] = [ 6. 15. 24.]。但需要注意,不管是上下挤扁还是左右挤扁,最后输出都会转换为行向量输出。
(未完待续)
3. 数学基础—梯度下降
神经网络在对权值更新时,绝大多数采用误差反向传播与梯度下降的方法。tensorflow已经处理好了方向传播求微分的事情,同时也提供了多种更新梯度的方法,随机梯度下降(Stochastic Gradiant Descent, SGD)、Adagrad、Momentum、Adam等算法。目前没有绝对优的算法,这个在后面会进行对比。
SGD
随机梯度下降是最原始一种权值更新方法,表达式为:
[外链图片转存失败(img-H5myZK15-1562574427149)(https://latex.codecogs.com/png.download?W=W-\eta\frac{\partial L}{\partial W})]
n为学习率,是一个固定值,取值太小,学习效率低下且容易陷入局部最优,取值太大也有可能跳过最优值,来回跳动误差也很大。此外,上述公式中的梯度(偏导部分)反映的是当前点的坡度下降最大的方向而不一定指向全局最优点。例如,在一个三维曲面优化z,曲面沿着x方向的梯度变化很小,沿着y方向梯度变化很大。此时,如果把学习率取值过小,则沿着x方向的优化过程会很慢,如果学习率取值过大,则沿着y向的误差跳动就很大。
4. Tensorflow基本操作
数据流图
[外链图片转存失败(img-Cph2kumI-1562574427150)(http://www.tensorfly.cn/images/tensors_flowing.gif)]
上图表示了Tensorflow的计算模式——数据流图(后面一一介绍):
- 使用图 (graph) 来表示计算任务(可以对比Matlab/Simulink的计算模式).
- 在被称之为会话 (Session) 的上下文 (context) 中执行图.
- 使用张量(tensor)表示数据.
- 通过变量 (Variable) 维护状态.
- 使用 feed 和 fetch 可以为任意的操作(arbitrary operation) 赋值或者从其中获取数据
数据流图(Data Flow Graph)?(引用自Tensorflow中文社区)
数据流图用“结点”(nodes)和“线”(edges)的有向图来描述数学计算。“节点” 一般用来表示施加的数学操作,但也可以表示数据输入(feed in)的起点/输出(push out)的终点,或者是读取/写入持久变量(persistent variable)的终点。“线”表示“节点”之间的输入/输出关系。这些数据“线”可以输运“size可动态调整”的多维数据数组,即“张量”(tensor)。张量从图中流过的直观图像是这个工具取名为“Tensorflow”的原因。一旦输入端的所有张量准备好,节点将被分配到各种计算设备完成异步并行地执行运算。
基本操作
- 构建数据流图:构建图的第一步, 是创建源 op (source op),把op当作是上面数据流图中的节点,一个节点的输出可以作为其他节点的输入。把一个个节点创建好,定义好输入输出就形成了数据流图;例如:
则上述图有三个节点, 两个 constant() op, 和一个matmul() op.import tensorflow as tf # 创建一个常量 op, 产生一个 1x2 矩阵. 这个 op 被作为一个节点 # 加到默认图中. # 构造器的返回值代表该常量 op 的返回值. matrix1 = tf.constant([[3., 3.]]) # 创建另外一个常量 op, 产生一个 2x1 矩阵. matrix2 = tf.constant([[2.],[2.]]) # 创建一个矩阵乘法 matmul op , 把 'matrix1' 和 'matrix2' 作为输入. # 返回值 'product' 代表矩阵乘法的结果. product = tf.matmul(matrix1, matrix2)
- 启动图:Tensorflow里面创建了数据流图后,如何运行程序不会有数据输出。上面这个例子,print(produce)的话,输出下面结果: 构造完数据流图后,要启动这个图才可以得到数据输出。启动图的第一步是创建一个 Session 对象:
Session 对象在使用完后需要关闭以释放资源. 除了显式调用 close 外, 也可以使用 “with” 代码块 来自动完成关闭动作:# 启动默认图. sess = tf.Session() # 调用 sess 的 'run()' 方法来执行矩阵乘法 op, 传入 'product' 作为该方法的参数. # 上面提到, 'product' 代表了矩阵乘法 op 的输出, 传入它是向方法表明, 我们希望取回 # 矩阵乘法 op 的输出. # 整个执行过程是自动化的, 会话负责传递 op 所需的全部输入. op 通常是并发执行的. # 函数调用 'run(product)' 触发了图中三个 op (两个常量 op 和一个矩阵乘法 op) 的执行. # 返回值 'result' 是一个 numpy `ndarray` 对象. result = sess.run(product) print(result) # 任务完成, 关闭会话. sess.close()
另外,采用这种方式可以指定CPU或者GPUwith tf.Session() as sess: result = sess.run([product]) print(result)
设备用字符串进行标识. 目前支持的设备包括:with tf.Session() as sess: with tf.device("/gpu:1"): ...
- “/cpu:0”: 机器的 CPU.
- “/gpu:0”: 机器的第一个 GPU, 如果有的话.
- “/gpu:1”: 机器的第二个 GPU, 以此类推
- 定义与初始化变量:采用以下方式来定义一个变量:
所有变量定义以后都要进行初始化然后才能参与图计算:# 创建一个变量, 初始化为标量 0. state = tf.Variable(0, name="counter")
init = tf.global_variables_initializer() #使用变量,就要对其初始化(以前版本是tf.initialize_all_variables()) sess = tf.Session() #定义Session,并使用Session来初始化步骤 sess.run(init) sess.close()
- 查看数据流图中的数据:在sess.run()中传入需要的节点。
input1 = tf.constant(3.0) input2 = tf.constant(2.0) input3 = tf.constant(5.0) intermed = tf.add(input2, input3) mul = tf.matmul(input1, intermed) with tf.Session() as sess: result = sess.run([mul, intermed]) print(result)
-
Tensor赋值:在数据流图中,有的tensor需要外接传入参数,比如神经网络的输入参数和标签等。TensorFlow 提供了 feed 机制, 该机制可以临时替代图中的任意操作中的 tensor 可以对图中任何操作提交补丁, 直接插入一个 tensor。
通常,占位符placeholder与feed配合使用。可以把placeholder看作是定义的一个数据输入接口,数据从placeholder这里传输进数据流图中。用例如下:
input1 = tf.placeholder(tf.float32) input2 = tf.placeholder(tf.float32) output = tf.matmul(input1, input2) with tf.Session() as sess: print(sess.run([output], feed_dict={input1:[7.], input2:[2.]}))#feed_dict采用字典的形势“喂”数据
5. Tensorflow—手写数字分类
神经网络基本概念
神经网络的基本概念可以参见神经网络浅讲:从神经元到深度学习和零基础入门深度学习,很详细。这里介绍利用python/Tensorflow实现手写阿拉伯数字的识别和分类。具体来说,采用全连接网络和卷积神经网络分别实现。样本集采用MNIST数据,里面包含60000组训练数据和10000组测试数据。每组数据都对应着一幅图片和该图片的标签。采用如下代码下载MNIST数据:
from tensorflow.examples.tutorials.mnist import input_data #导入mnist库
mnist = input_data.read_data_sets('./MNIST_data', one_hot=True) #如果没有mnist数据就进行下载;使用one_hot编码
print(mnist.test.images.shape, mnist.test.labels.shape)
这里运行可能会提示下载不了数据,拒绝访问。一个解决办法就是自己去网站下载数据,这四个数据包下载以后,不需要自己解压,在主程序目录下新建一个文件夹“MNIST_data”,将数据放在文件夹里面即可。上面程序运行后就会自动在这个文件夹里面解压数据并导入。
之后运行上述程序就会输出以下结果。可以看到,mnist的测试集mnist.test包含图像和标签,图像是一个784个数据的行向量,标签是10个数据的行向量。图片包含灰度图和彩色图等。灰度图的通道为1,也就是用一个2维矩阵就可以表示,每一个像素点的值在0-255之间。RGB彩色图的通道为3,每一个像素点的颜色是由3个值决定的,所以需要用3个二维矩阵表示,也就是三维矩阵。这里的MNIST是灰度图,大小为28x28=784,并且灰度图进行了归一化,在0-1之间。这里将这个28x28的矩阵按一行一行首位相连,排成了1x784的行向量。注意,这就是全连接网络的要求,全连接网络只能处理一维数据,而卷积神经网络可以处理三维数据。数据的标签是一个包含10个元素的向量,比如标签[0,0,0,0,0,1,0,0,0,0],表示这张图片的数字是“5”。
...
Extracting ./MNIST_data\train-images-idx3-ubyte.gz
...
Extracting ./MNIST_data\train-labels-idx1-ubyte.gz
...
Extracting ./MNIST_data\t10k-images-idx3-ubyte.gz
...
Extracting ./MNIST_data\t10k-labels-idx1-ubyte.gz
(10000, 784) (10000, 10)
全连接网络
所谓全连接网络,就是指上一层所有的神经元都与下一层的所有神经元连接。比如,输入层有3个神经元,隐藏层有4个神经元,那从输入到隐藏层就有3x4=12个权值。
首先,编写一个用于定义神经网络层的文件,命名为Add_Layer.py:
import tensorflow as tf
#添加一个全链接神经网络层
def add_affine_layer(inputs, w_shape, b_shape, activation_function = None):
r'''
:param inputs:上一层所有神经元的输出数据,shape=[batch, -1]
:param w_shape:权值矩阵, shape=[n_size, out_size], in_size: 上一层神经元输出数据的列数, out_size: 本层神经元个数
:param b_shape:偏置矩阵,shape=[out_size]
:param activation_function: 激活函数
:return: 本层神经元输出,权值,偏置
'''
weights = tf.Variable(tf.random_normal(w_shape, stddev=0.1))#定义权值矩阵.,方差选择很重要
biases = tf.Variable(tf.constant(0.1, shape=b_shape))#定义偏置
activation_input = tf.matmul(inputs, weights) + biases#诱导局部阈
if activation_function is None:
outputs = activation_input
else:
outputs = activation_function(activation_input)
return outputs, weights, biases
#添加卷积层
def add_conv_layer(input, w_shape, b_shape, strides=[1, 1, 1, 1], padding='SAME', activation_function=None):
r'''
:param input:上层神经元输入,shape=[batch, height, width, channel]
:param w_shape:卷积核,shape=[height, width, channel, num]
:param b_shape:偏置,shape=[num]
:param strides:卷积核滑动步长,shape[1, row_step, col_step, 1]
:param padding:填充,"SAME"或""VALID",填充算法,参见help(tf.nn.convolution)
:param activation_function:激活函数
:return:本层神经元输出,权值,偏置
'''
weight = tf.Variable(tf.random_normal(w_shape, stddev=0.1))#定义权值矩阵.,方差选择很重要
biass = tf.Variable(tf.constant(0.1, shape=b_shape))
activation_input = tf.nn.conv2d(input, weight, strides=strides, padding=padding) + biass
if activation_function is None:
output = activation_input
else:
output = activation_function(activation_input)
return output, weight, biass
#添加池化层
def add_pool_layer(input, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME', pool_function=tf.nn.max_pool):
r'''
:param input:卷积结果
:param ksize:池化窗大小,shape=[1, height, width, 1]
:param strides:滑动步长
:param padding:填充,"SAME"或""VALID",填充算法,help(tf.nn.convolution)
:param pool_function:池化函数
:return:池化结果
'''
return pool_function(input, ksize=ksize, strides=strides, padding=padding)
然后,构建全连接神经网络Example_Affine.py:
from Add_Layer import *
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets('./MNIST_data', one_hot=True)# 导入数据
xs = tf.placeholder(tf.float32, shape=[None, 784])#定义输入接口,输入图像数据
ys = tf.placeholder(tf.float32, shape=[None, 10])#定义输入接口,输入图像标签
l1, w1, b1 = add_affine_layer(xs, [784, 10], [1, 10], activation_function=tf.nn.softmax)#定义一层全连接网络,输入直接到输出
Loss = tf.reduce_mean(-tf.reduce_sum(ys * tf.log(l1), 1))#定义代价函数,交叉熵函数
train = tf.train.AdagradOptimizer(0.5).minimize(Loss)
accuracy = tf.reduce_mean(tf.cast(tf.equal(tf.argmax(l1, 1), tf.argmax(ys, 1)), tf.float32))#计算神经网络的精度
'''初始化变量,必须有'''
sess = tf.InteractiveSession()
ini = tf.global_variables_initializer()
sess.run(ini)
for i in range(10000):
x, y = mnist.train.next_batch(100)
sess.run(train, feed_dict={xs: x, ys: y})
if i%100 == 0:#每隔100个对比一下测试精度和训练精度
train_accuracy = accuracy.eval(feed_dict={xs: x, ys: y})
test_accuracy = accuracy.eval(feed_dict={xs: mnist.test.images, ys: mnist.test.labels})
print(train_accuracy, test_accuracy)
这个测试精度在91%左右。
卷积神经网络
同样,用上面的神经网络层定义卷积神经网络。这里的卷积网络为:“输入-卷积层-Relu-池化-卷积-Relu-池化-全连接-Relu-全连接-Softmax-输出”
from Add_Layer import *
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets('./MNIST_data', one_hot=True)
xs = tf.placeholder("float")
ys = tf.placeholder("float", [None, 10])
xs_in = tf.reshape(xs, [-1, 28, 28, 1])
l1, w1, b1 = add_conv_layer(xs_in, [5, 5, 1, 32], [32], activation_function=tf.nn.relu)#定义卷积层
p1 = add_pool_layer(l1)#定义池化层
l2, w2 , b2 = add_conv_layer(p1, [5, 5, 32, 64], [64], activation_function=tf.nn.relu)
p2 = add_pool_layer(l2)
l3_in = tf.reshape(p2, [-1, 64*49])#因为全连接层只支持一维数据,所以要将64个特征图全部展开成一行
l3, w3, b3 = add_affine_layer(l3_in, [64*49, 1024], [1024], activation_function=tf.nn.relu)#定义全连接层
l4, w4, b4 = add_affine_layer(l3, [1024, 10], [10], activation_function=tf.nn.softmax)
Loss = tf.reduce_mean(-tf.reduce_sum(ys*tf.log(l4), 1))
train = tf.train.AdamOptimizer(1e-4).minimize(Loss)
accuracy = tf.reduce_mean(tf.cast(tf.equal(tf.argmax(l4, 1), tf.argmax(ys, 1)), "float"))
sess = tf.InteractiveSession()
ini = tf.global_variables_initializer()
sess.run(ini)
for i in range(1000):
x_train, y_train = mnist.train.next_batch(50)
sess.run(train, feed_dict={xs: x_train, ys: y_train})
if i % 100 == 0:
train_accuracy = accuracy.eval(feed_dict={xs: x_train, ys: y_train})
test_accuracy = accuracy.eval(feed_dict={xs:mnist.test.images, ys: mnist.test.labels})
print(train_accuracy, test_accuracy)
这个测试精度在99%