Tensorflow 入门

零、前言

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循环是训练的批次;最后有一个测试集验证的过程

发表评论

电子邮件地址不会被公开。 必填项已用*标注