零、前言
Tensorflow是深度学习最知名的框架之一,其命名来源于本身的运行原理。Tensor(张量)意味着N维数组(矩阵),Flow(流)意味着基于数据流图的计算,TensorFlow为张量从流图的一端流动到另一端计算过程。实际上任何基于数据流图的计算都可以利用Tensorflow去实现。
官网:https://www.tensorflow.org/
参考:https://www.bilibili.com/video/av9156347/
一、简介
Tensorflow是基于数据流图的开源框架,主要用于数值计算,由Google Brain团队开发用于机器学习和深度神经网络的研究。Tensorflow虽然是开源的,但是实际上Google还有一个内部版本没有开源,专为Google内部提供服务。除了Tensorflow,主流的深度学习框架还有Caffe、Keras、CNTK、Torch7、MXNet、Leaf、Theano等,但是Tensorflow几乎是最流行的,源自于它的多个优点:
- Python API(大多数框架都支持)
- 可以在多种平台(服务端、移动端)使用多个CPU、GPU进行计算
- 足够灵活
- 可视化(TensorBoard)
- Checkpoints
- 自动微分
- 庞大的社区
- 大量使用Tensorflow的项目
Tensorflow为什么会选择基于图的运算模式呢?
- 节省计算资源,每次只需要计算与结果有关的子图
- 可以将图分成小块自动微分
- 方便部署在多个设备上
- 很多机器学习算法都可以视为图的结构
一、入门
1、安装
pip install tensorflow
pip install tensorflow-gpu
注:第一个为cpu版本,第二个为gpu版本,使用gpu版本需要安装显卡驱动和CUDA+cudnn
显卡驱动安装建议参考:
https://blog.csdn.net/ksws0292756/article/details/79160742
CUDA+cudnn一定要注意和tensorflow的版本,建议参考:
https://blog.csdn.net/qysh123/article/details/79977891
2、Tensor(张量)
之前说了Tensorflow是基于数据流图的,实际上Tensorflow的计算分为两个部分,一是图的构建,二是图的计算。计算需要创建Session来执行,而Tensor就是对数据的表示。
0维的Tensor:标量
1维的Tensor:向量
n维的Tensor:n维矩阵(n>1)
3、TensorBoard
Tensorboard是Tensorflow的可视化工具,安装Tensorflow时默认安装,使用非常简单,在需要进行可视化的代码中加入代码:
writer = tf.summary.FileWriter('./graph', sess.graph)
它会将图的结构写入到./graph目录中,比如下面这个例子:
import tensorflow as tf
a = tf.constant(2)
b = tf.constant(3)
x = tf.add(a, b)
with tf.Session() as sess:
writer = tf.summary.FileWriter('./graphs', sess.graph)
print(sess.run(x))
writer.close() # close the writer when you’re done using it
然后在终端运行:
tensorboard --logdir="./graphs"
即可代开http://localhost:6006,查看Tensorboard的可视化界面
4、基本示例
我们先看一些例子,有一个直观的感受,先运行一个简单的加法运算
代码:
import tensorflow as tf
a = tf.add(1,2)
print a
输出:
Tensor("Add:0", shape=(), dtype=int32)
数据流图如下,因为没有给相加的两个节点命名,所以Tensorflow给他们自动命名了x,y
并没有输出3这个结果,因为add这个语句只是创建了一个计算图,包含一个add节点,输出的类型是int32。那么怎么获得a的值呢,我们必须创建一个session,在session中创建的图才能够被运算,所以Tensorflow并不像传统代码的顺序执行逻辑。
代码:
import tensorflow as tf
a = tf.add(1,2)
sess = tf.Session()
print sess.run(a)
sess.close()
输出:
3
当然,可以使用下面这个更加简介的写法:
import tensorflow as tf
a = tf.add(1,2)
with tf.Session() as sess:
print sess.run(a)
像Python中的其他语句with的用法一致,我们使用with打开Session后下面的代码块里就可以使用Session,并且不用再显示的关闭。然后看下面的例子
代码:
import tensorflow as tf
x = 2
y = 3
op1 = tf.add(x, y)
op2 = tf.multiply(x, y)
op3 = tf.pow(op2, op1)
with tf.Session() as sess:
print sess.run(op3)
输出:
7776
数据流图:
代码中with之前的语句实现了图的建立,Session的代码块实现了图的计算。这里的计算实际上是一种反馈机制,比如当执行
print sess.run(op3)
Tensorflow会查找计算op3需要的资源,根据所建立的图需要计算Mul和Add,当这两个节点计算完成后再去计算Pow的值。也就是说Tensorflow不会预先计算Mul和Add的值,这是一种惰性求值的思想。进一步的,看下面的例子
代码:
import tensorflow as tf
x = 2
y = 3
op1 = tf.add(x, y)
op_ = tf.multiply(x, op1)
op2 = tf.multiply(x, y)
op3 = tf.pow(op2, op1)
with tf.Session() as sess:
print sess.run(op3)
输出:
7776
数据流图:
这个图里面多出了左上角的运算节点,但代码所求的值仍然是op3,仅需要Add和Mul_1节点,这时Tensorflow仅会计算需要的节点,而
op_ = tf.multiply(x, op1)
所定义的部分并不会被计算,因此你可以把仅需要计算的部分放到sess.run中,这样既节省了计算资源又提高了运算效率。也可以将图分成多个小块,让他们在不同的CPU或GPU下运行
例如,代码:
import tensorflow as tf
with tf.device("/cpu:0"):
a = tf.constant([[1.0, 2.0], [3.0, 4.0]], name="a")
b = tf.constant([[1.0, 2.0], [3.0, 4.0]], name="b")
c = tf.matmul(a, b)
with tf.Session() as sess:
writer = tf.summary.FileWriter('../models/test', sess.graph)
print sess.run(c)
输出:
[[ 7. 10.]
[15. 22.]]
数据流图:
5、数据类型
5.1 常数类型(Constant types)
可以通过下面的方式创造一个常数
tf.constant(value, dtype=None, shape=None, name='Const', verify_shape=False)
比如在上一节最后一个例子中我们定义了两个常数a、b
a = tf.constant([[1.0, 2.0], [3.0, 4.0]], name="a")
b = tf.constant([[1.0, 2.0], [3.0, 4.0]], name="b")
其中name参数表示节点的名称,其他参数dtype表示数据类型,比如可以是int32或者float32;shape表示数据格式,比如shape=[2,3]表示两行三列的矩阵;verify_shape表示是否对数据格式进行验证,当其值为True时会与shape进行比对,当数据格式不符时就会报错。同时还有一些特殊的常量值的创建方法:
创建一个所有值为0的矩阵
tf.zeros(shape, dtype=tf.float32, name=None)
创建一个所有值为0的tensor对象,实际上是将input_tensor置0
tf.zeros_like(input_tensor, dtype=None, name=None, optimize=True)
创建一个所有值为1的矩阵
tf.ones(shape, dtype=tf.float32, name=None)
创建一个所有值为1的tensor对象,实际上是将input_tensor置0
tf.ones_like(input_tensor, dtype=None, name=None, optimize=True)
创建一个维度为dims,所有值为1的矩阵
tf.fill(dims, value, name=None)
5.2 变量类型(Variable types)
常量一旦完成定义就无法改变,为了更好的计算,引入了变量,可以通过以下方式引入一个变量
tf.Variable(value, trainable=True, collections=None, validate_shape=True, name=None)
其中value可以为多种类型
a = tf.Variable(2, name='scalar')
b = tf.Variable([2, 3], name='vector')
c = tf.Variable([[0, 1], [2, 3]], name='matrix')
w = tf.Variable(tf.zeros([784, 10]), name='weight')
变量有下面几个操作
x = tf.Variable()
x.initializer # 初始化
x.eval() # 读取里面的值
x.assign() # 分配值给这个变量
注意一点,在使用变量之前必须对其进行初始化,初始化可以看作是一种变量的分配值操作。最简单的初始化方式是一次性初始化所有的变量
init = tf.global_variables_initializer()
with tf.Session() as sess:
sess.run(init)
也可以对某一部分变量进行初始化
init_ab = tf.variable_initializer([a, b], name='init_ab')
with tf.Session() as sess:
sess.run(init_ab)
或者是对某一个变量进行初始化
w = tf.Variable(tf.zeros([784, 10]))
with tf.Session() as sess:
sess.run(w.initializer)
如果我们想取出变量的值,有两种方法
w = tf.Variable(tf.truncated_normal([10, 10], name='normal'))
with tf.Session() as sess:
sess.run(w.initializer)
print(w.eval()) # 方法一
print(sess.run(w)) # 方法二
也可以根据一个变量来定义一个变量
w = tf.Variable(tf.truncated_normal([700, 10]))
u = tf.Variable(w * 2)
5.3 占位符(Placeholders)
tensorflow中一般有两步,第一步是定义图,第二步是在session中进行图中的计算。对于图中我们暂时不知道值的量,我们可以定义为占位符,之后再用feed_dict去赋值。定义占位符的方式非常简单:
tf.placeholder(dtype, shape=None, name=None)
dtype是必须要指定的参数,shape如果是None,说明任何大小的tensor都能够接受,使用shape=None很容易定义好图,但是在debug的时候这将成为噩梦,所以最好是指定好shape。我们可以给出下面的小例子。
代码:
import tensorflow as tf
a = tf.placeholder(tf.float32, shape=[3])
b = tf.constant([5, 5, 5], tf.float32)
c = a + b
with tf.Session() as sess:
writer = tf.summary.FileWriter('../models/test', sess.graph)
print sess.run(c, feed_dict={a: [1, 2, 3]})
输出:
[6. 7. 8.]
三、进阶
1、 线性回归示例
本节将构造一个线性回归模型去解决一个实际的问题:希望能够找到一个城市中纵火案和盗窃案之间的关系,纵火案的数量是X,盗窃案的数量是Y,我们假设存在如下线性关系
Y = wX + b
1.1 代码
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
import xlrd
# Phase1: assemble the graph
# Step1: read data from file
book = xlrd.open_workbook("../data/fire_theft.xls", encoding_override='utf-8')
sheet = book.sheet_by_index(0)
data = np.asarray(
[sheet.row_values(i) for i in range(1, sheet.nrows)], dtype=np.float32)
n_samples = sheet.nrows - 1
# Step2: create placeholder for linux X and Y
X = tf.placeholder(tf.float32, shape=[], name="input")
Y = tf.placeholder(tf.float32, shape=[], name="label")
# Step3: create weight and bias
w = tf.get_variable("weight", shape=[], initializer=tf.truncated_normal_initializer())
b = tf.get_variable("bias", shape=[], initializer=tf.zeros_initializer())
# Step4: predict Y from X
Y_predicted = w * X + b
# Step5: use the square error as the loss function
loss = tf.square(Y - Y_predicted, name="loss")
# Step6: using gradient descent with learning rate of 0.01 to minimize loss
optimizer = tf.train.GradientDescentOptimizer(learning_rate=1e-3).minimize(loss)
# Phase2: Train our model
init = tf.global_variables_initializer()
with tf.Session() as sess:
# Step 7: initialize the necessary variables, in this case, w and b
writer = tf.summary.FileWriter("../models/linear", graph=sess.graph)
sess.run(init)
# Step 8: train the model
for i in range(100):
total_loss = 0
for x,y in data:
_, l = sess.run([optimizer, loss], feed_dict={X:x, Y:y})
total_loss += l
print("Epoch {0}: {1}".format(i, total_loss / n_samples))
w, b = sess.run([w, b])
writer.close()
# plot the result
X, Y = data.T[0], data.T[1]
plt.plot(X, Y, "bo", label="Real data")
plt.plot(X, X*w + b, "r", label='Predicted data')
plt.legend()
plt.show()
1.2 解释
Phase1
构造图
Step1
从表格里读取数据
Step2
创建两个占位符X和Y,因为公式为:Y = wX + b,X和Y是需要根据Step1中读取的值动态输入的,所以这里要创建两个占位符。
Step3
创建两个变量w和b,权重w和偏移b是所求的参数,所以这里要设置成变量进行动态优化。
Step4
定义线性函数Y_predicted = w * X + b
Step5
定义损失函数,实际为真是Y值与预测Y_predicted值的平方差
Step6
定义优化器,这里使用的是梯度下降法,这一行拆开来看就是两步:
# Create an optimizer with the desired parameters.
opt = GradientDescentOptimizer(learning_rate=0.1)
# Add Ops to the graph to minimize a cost by updating a list of variables.
# "cost" is a Tensor, and the list of variables contains tf.Variable objects.
opt_op = opt.minimize(cost, var_list=<list of variables>)
第一步是定义优化器类型,第二步是定义优化方法;minimize也是相当于执行了两个操作,第一步计算梯度,第二步应用梯度,当定义好优化函数
opt = tf.train.GradientDescentOptimizer(learning_rate)
之后,可以通过
grads_and_vars = opt.compute_gradients(loss, <list of variables>)
来计算loss对于一个变量列表里面每一个变量的梯度,得到的grads_and_vars是一个list of tuples,list中的每个tuple都是由(gradient, variable)构成的,我们可以通过get_grads_and_vars = [(gv[0], gv[1]) for gv in grads_and_vars]将其分别取出来,然后通过opt.apply_gradients(get_grads_and_vars)来更新里面的参数,下面我们举一个小例子:
import tensorflow as tf
x = tf.Variable(5, dtype=tf.float32)
y = tf.Variable(3, dtype=tf.float32)
z = x**2 + x * y + 3
sess = tf.Session()
# initialize variable
sess.run(tf.global_variables_initializer())
# define optimizer
optimizer = tf.train.GradientDescentOptimizer(0.1)
# compute gradient z w.r.t x and y
grads_and_vars = optimizer.compute_gradients(z, [x, y])
# fetch the variable
get_grads_and_vars = [(gv[0], gv[1]) for gv in grads_and_vars]
# dz/dx = 2*x + y= 13
# dz/dy = x = 5
print('grads and variables')
print('x: grad {}, value {}'.format(
sess.run(get_grads_and_vars[0][0]), sess.run(get_grads_and_vars[0][1])))
print('y: grad {}, value {}'.format(
sess.run(get_grads_and_vars[1][0]), sess.run(get_grads_and_vars[1][1])))
print('Before optimization')
print('x: {}, y: {}'.format(sess.run(x), sess.run(y)))
# optimize parameters
opt = optimizer.apply_gradients(get_grads_and_vars)
# x = x - 0.1 * dz/dx = 5 - 0.1 * 13 = 3.7
# y = y - 0.1 * dz/dy = 3 - 0.1 * 5 = 2.5
print('After optimization using learning rate 0.1')
sess.run(opt)
print('x: {:.3f}, y: {:.3f}'.format(sess.run(x), sess.run(y)))
sess.close()
输出:
grads and variables
x: grad 13.0, value 5.0
y: grad 5.0, value 3.0
Before optimization
x: 5.0, y: 3.0
After optimization using learning rate 0.1
x: 3.700, y: 2.500
梯度下降就是要找到参数优化的方法,步长learning_rate是指每次调整的大小,关于梯度下降有一篇很好的文章:
https://www.jianshu.com/p/c7e642877b0e
Phase2
计算图
Step7
首先初始化所有变量,并将构造的图写入到文件中
Step8
模型训练,代码包含两层for循环;第一层for循环为训练的轮次,第二层for循环为数据读入,每次读取一对X和Y,然后执行优化器并求损失值,最后通过w, b = sess.run([w, b])获得最终优化的权重w和偏移b
输出:
数据流图:
2、 逻辑回归示例
本例尝试解决典型的手写数字识别的问题,采用MNIST数据集,模型公式如下:
2.1 代码
import numpy as np
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import time
# Define paramaters for model
learning_rate = 0.01
batch_size = 128
n_epochs = 30
# Step1: read in data
# using TF Learn's built in function to load MNIST data to the folder data/mnist
mnist = input_data.read_data_sets("../data/mnist", one_hot=True)
# Step2: create placeholders for features and labels
# each image in the MNIST data is of shape 28*28 = 784
# therefore, each image is represented with a 1x784 tensor
# there are 10 classes for each image, corresponding to digits 0 - 9.
# Features are of the type float, and labels are of the type int
x = tf.placeholder(tf.float32, shape=[None, 784], name="image")
y = tf.placeholder(tf.int32, shape=[None, 10], name="label")
# Step3: create weight and bias
# weights and biases are initialized to 0
# shape of w depends on the dimension of X and Y so that Y = X * w + b
# shape of b depends on Y
w = tf.get_variable("weight", shape=[784, 10], initializer=tf.truncated_normal_initializer())
b = tf.get_variable('bias', shape=[10], initializer=tf.zeros_initializer())
# Step4: build model
# the model that returns the logits.
# this logits will be later passed through softmax layer
# to get the probability distribution of possible label of the image
# DO NOT DO SOFTMAX HERE
logits = tf.matmul(x, w) + b
# Step5: define loss function
# use cross entropy loss of the real labels with the softmax of logits
# use the method:
entropy = tf.nn.softmax_cross_entropy_with_logits(labels=y, logits=logits)
# then use tf.reduce_mean to get the mean loss of the batch
loss = tf.reduce_mean(entropy, axis=0)
# test model
preds = tf.nn.softmax(logits)
correct_preds = tf.equal(tf.argmax(preds, 1), tf.argmax(y, 1))
accuracy = tf.reduce_sum(tf.cast(correct_preds, tf.float32), axis=0)
# Step6 define training op
# using gradient descent to minimize loss
optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss)
with tf.Session() as sess:
writer = tf.summary.FileWriter("../models/logistic")
start_time = time.time()
sess.run(tf.global_variables_initializer())
n_batches = int(mnist.train.num_examples / batch_size)
for i in range(n_epochs):
total_loss = 0
for _ in range(n_batches):
X_batch, Y_batch = mnist.train.next_batch(batch_size)
_, loss_batch = sess.run([optimizer, loss], feed_dict={x: X_batch, y:Y_batch})
total_loss += loss_batch
print('Average loss epoch {0}: {1}'.format(i, total_loss / n_batches))
print('Total time: {0} seconds'.format(time.time() - start_time))
print('Optimization Finished!') # should be around 0.35 after 25 epochs
# test the model
n_batches = int(mnist.test.num_examples / batch_size)
total_correct_preds = 0
for i in range(n_batches):
X_batch, Y_batch = mnist.test.next_batch(batch_size)
accuracy_batch = sess.run(accuracy, feed_dict={x: X_batch, y: Y_batch})
total_correct_preds += accuracy_batch
print('Accuracy {0}'.format(total_correct_preds / mnist.test.num_examples))
2.2 解释
Step1
读取数据,使用了tensorflow内置的读取mnist数据集的函数
Step2
创建两个占位符x和y,x的shape为[None, 784],这里图片的大小是28*28=784,所以每一个图片需要一个1×784的tensor;y的shape为[None, 10],结果是一个10维的向量,表明0-9十个数字。因为x和y每次都是输入batch_size大小的数据,所以这里都要在第一维设置None表明接受任意数量的输入。
Step3
创建变量w和b,w和b的shape取决于x和y
Step4
根据公式1构建模型,这里的logits是利用权重w和偏移b求得的值,是神经网络最后一层的输出,它的大小就是[batchsize,num_classes],单样本的话,大小就是num_classes,它本身就代表图片是哪个数字的“可能性”,可以把他理解成未归一化的概率。
Step5
定义损失函数,这里先求了一个交叉熵,大概也分为两部步。第一步对logits做一个softmax,相当于对上一步的logits做归一化,对于单样本而言,输出就是一个num_classes大小的向量[Y0,Y1,Y2,Y3,Y4,Y5,Y6,Y7,Y8,Y9](其中Y0,Y1,Y2…分别代表了是属于该类的概率);第二步是softmax的输出向量[Y0,Y1, Y2…]和样本的实际标签做一个交叉熵,这里样本的实际标签也是一个向量,比如3的向量为[0,0,0,1,0,0,0,0,0,0](独热编码),根据公式
其中指代实际的标签中第i个的值;就是softmax的输出向量中的第i个值。如果预测的越准确那么值就越小,最后reduce_mean求平均获得loos值,即是我们的优化目标(softmax_cross_entropy_with_logits返回的是一个向量,因此要对向量内所有元素求平均)。
Step6
使用梯度下降法最小化损失。然后进入session代码块,先说一下这行代码
n_batches = int(mnist.train.num_examples / batch_size)
这里先求了以下n_batches,也就是有多少个batch_size大小的数据,深度学习里进行样本训练每次都只会读取batch_size大小的数据,当热也可以将batch_size的大小定义为样本集的大小,这样所有的样本就会一次性全部读入,但batch_size的作用在于平衡算法效率与内存容量之间的平衡,batch_size太大内存难以承受同时也会导致迭代次数减少,使参数修正变得缓慢;batch_size太小,算法难以收敛。
然后是两个for循环,第一层for循环是指训练的轮次,第二层for循环是训练的批次;最后有一个测试集验证的过程