Shaoqun Liu's blog
搜索文档…
Tensorflow基础

0x00 数据流图

首先明确一个问题,Tensorflow是基于数据流图的计算。就比如说如下代码:
1
import tensorflow as tf
2
3
a = tf.constant([[1, 2]])
4
b = tf.constant([[3], [4]])
5
c = tf.matmul(a, b)
6
7
with tf.Session() as sess:
8
result = sess.run(c)
9
print(result)
Copied!
a和b是两个矩阵,c是a和b这两个矩阵乘法运算后的结果。在代码执行到第8行sess.run(c)之前,c的值时没有经过计算。相反,之前的代码只是简单地创建了一个数据流图中的一个结点。只有当执行到sess.run(c)的时候,Tensorflow才开始计算c的值,继而顺着数据流图首先计算出a和b的值,然后再计算两个矩阵的乘积得出c的结果。

0x01 reduce_sum和reduce_mean

一开始的时候着实搞不懂这两个函数到底是干什么的。说得直白一点,reduce就是消除一个维度,sum就是使用求和的方法,reduce_sum即为使用求和的方法来消除一个维度。同理,reduce_mean即为使用取平均值的方法来消除一个维度。先来看其文档中的一个2维的例子:
1
x = tf.constant([[1, 1, 1], [1, 1, 1]])
Copied!
显然,这是一个2行3列——2x3——的矩阵。当执行tf.reduce_sum(x, 0)时,第二个参数的0即为消除维度0,也就是消除2x3里面的那个2,将两行中每一列的元素相加,得[2 2 2]即为最终结果。如果指定第二个参数为1,即为消除维度1,将每一列的元素相加,即可得[3 3]即为最终结果。如果不指定reduce_sum的第二个参数的话,默认即为消除所有维度,得到的结果即为矩阵中所有元素的相加,即为数字6。
然后,再来看一个复杂的例子:
1
x = tf.constant([[[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]], [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]]])
Copied!
显然,这是一个2x3x4的矩阵,执行tf.reduce_sum(x, 0)我们将得到一个3x4的矩阵,如下:
1
[[2 2 2 2]
2
[2 2 2 2]
3
[2 2 2 2]]
Copied!
执行tf.reduce_sum(x, 1)我们将得到一个2x4的矩阵,如下:
1
[[3 3 3 3]
2
[3 3 3 3]]
Copied!
执行tf.reduce_sum(x, 2)我们将得到一个2x3的矩阵,如下:
1
[[4 4 4]
2
[4 4 4]]
Copied!
执行tf.reduce_sum(x)我们将得到原矩阵中,所有元素的和,即为数字24

0x02 第一次训练

我们的第一次训练,使用机器学习的方法通过既定的点来拟合一条直线。代码如下:
1
import tensorflow as tf
2
import numpy as np
3
4
# 生成100个随机数,形状为1x100
5
x = np.random.rand(100)
6
# 正确答案序列
7
y_correct = 3 * x + 4
8
9
# 设置斜率(权重)和偏移,以1和2作为初始值
10
k = tf.Variable(1.)
11
b = tf.Variable(2.)
12
13
# y的预测值
14
y_predict = k * x + b
15
16
# 以方差作为损失函数
17
loss = tf.reduce_mean(tf.square(y_predict - y_correct))
18
19
# 以0.1为学习率进行梯度下降
20
optimizer = tf.train.GradientDescentOptimizer(0.1)
21
22
# 梯度下降的目的是为了降低损失函数loss的值
23
train = optimizer.minimize(loss)
24
25
with tf.Session() as sess:
26
# 初始化所有变量
27
sess.run(tf.global_variables_initializer())
28
# 训练200次
29
for i in range(200):
30
# 进行梯度下降
31
sess.run(train)
32
# 每隔20次循环输出一次k和b的值
33
if i % 20 == 0:
34
print(sess.run([k, b]))
Copied!

0x03 学习率

在上述代码中,我们设置的学习率为0.1,训练200次,所得的[k, b]的最终结果为:
1
[2.9544437, 4.025851]
Copied!
当我们试图扩大学习率至1的时候,训练200次,所得的[k, b]的最终结果为:
1
[-2.7914815e+34, -5.4713246e+34]
Copied!
当我们将学习率缩小10倍至0.01时,训练200次,所得的[k, b]的最终结果为:
1
[2.526483, 4.2690997]
Copied!
我们抛开复杂的数学知识,来看学习率,学习啦实际上管控的是梯度下降时的下降步幅。如下图:
如果学习率太小,梯度下降的速度就会变得缓慢,如上面的例子中,学习率为0.1和0.01时的对比。如果学习率太大,梯度下降的步伐就会摇摆不定,易产生震荡,始终难以找到一个最小值,如上面例子中,学习率为0.1和1时的对比。所以说,在训练过程中,一般根据训练轮数设置动态变化的学习率。
  • 刚开始训练时:学习率以 0.01 ~ 0.001 为宜。
  • 一定轮数过后:逐渐减缓。
  • 接近训练结束:学习速率的衰减应该在100倍以上。

0x04 第一个神经网络

在这个例子中,我们拟合一条曲线,并且为其中的点加入随机的噪音值,然后使用神经网络去拟合这条曲线,代码如下:
1
import tensorflow as tf
2
import numpy as np
3
import matplotlib.pyplot as plt
4
5
# np.linspace(起始点, 终止点(含), 数据量),此函数返回一个数组
6
# 数组内数据自起始点开始到终止点结束,共有200个元素,且这200个元素之间是等距的
7
# np.newaxis用于在某位置增加一个维度,比如初始为[1,2,3]
8
# 后经[:, np.newaxis]在末尾增加一个维度即为[[1],[2],[3]]
9
# x_data即为一个200x1的矩阵
10
x_data = np.linspace(-0.5, 0.5, 200)[:, np.newaxis]
11
# 产生一个均值为0方差为0.05的形状为200x1的符合正态分布的随机数矩阵
12
noise = np.random.normal(0, 0.05, (200, 1))
13
# 平方函数加上一定的噪声,作为最终值
14
y_correct = np.square(x_data) + noise
15
16
# 因为需要使用sess.run进行数据投喂,所以定义两个占位符
17
# None的标识为有多少行都行
18
x = tf.placeholder(tf.float32, [None, 1])
19
y = tf.placeholder(tf.float32, [None, 1])
20
21
# 神经网络中间层
22
weight_1 = tf.Variable(tf.random.normal([1, 10]))
23
bias_1 = tf.Variable(tf.zeros([1, 10]))
24
# y_predict_1的矩阵形状为[None, 10]
25
y_predict_1 = tf.matmul(x, weight_1) + bias_1
26
prediction_1 = tf.nn.tanh(y_predict_1)
27
28
# 神经网络输出层
29
weight_2 = tf.Variable(tf.random.normal([10, 1]))
30
bias_2 = tf.Variable(tf.zeros([1, 1]))
31
y_predict_2 = tf.matmul(prediction_1, weight_2) + bias_2
32
prediction_2 = tf.nn.tanh(y_predict_2)
33
34
loss = tf.reduce_mean(tf.square(prediction_2 - y))
35
train = tf.train.GradientDescentOptimizer(0.1).minimize(loss)
36
37
with tf.Session() as sess:
38
sess.run(tf.global_variables_initializer())
39
for i in range(2000):
40
sess.run(train, feed_dict={x: x_data, y: y_correct})
41
# 通过学习的结果,带入初始的数据进行预测,并获取预测值
42
predict_value = sess.run(prediction_2, feed_dict={x: x_data})
43
# 画图
44
plt.figure()
45
# 对正确值进行描点
46
plt.scatter(x_data, y_correct)
47
# 画出预测值图像
48
plt.plot(x_data, predict_value, 'r-', lw=5)
49
plt.show()
Copied!
使用tanh作激活函数,拟合结果如图所示:

0x05 激活函数

激活函数用于为神经元引入非线性元素,使其可以拟合任何非线性函数。如果没有激活函数,神经网络无论有多少层,输入输出都是线性组合。常见的激活函数有sigmoid, tanh, Relu, softmax。
sigmoid函数定义域可以到任何值,值域为[0, 1],此输出范围和概率范围一致,因此可以用概率的方式解释输出。其表达式为:
sigmoid(x)=11+ex\text{sigmoid}(x)=\frac{1}{1+e^{-x}}
其导数为:
y=y(1y)y'=y(1-y)
tanh函数就是数学上的双曲正切函数,其与sigmoid函数之间的关系为:
tanh(x)=2×sigmoid(2x)1\tanh(x)=2\times\text{sigmoid}(2x)-1
其导数为:
y=1y2y'=1-y^2
对比sigmoid和tanh两者导数输出可知,tanh函数的导数比sigmoid函数导数值更大,即梯度变化更快,也就是在训练过程中收敛速度更快。
Relu函数如下:
Relu(x)=max(0,x)\text{Relu}(x)=\max(0,x)
其导数为常数,当x小于0时导数为0,大于0时导数为1。计算速度和收敛速度非常快。

0x06 One-hot 编码

one-hot编码,一般译为独热编码,例如,MNIST手写数字识别中,目标答案只有0-9十个数字,在one-hot编码中这就是一个有10个元素的数组,则数字1使用one-hot编码表示即为[0 1 0 0 0 0 0 0 0 0],同理数字2即为[0 0 1 0 0 0 0 0 0 0]。即为:除了答案的索引之外,其他值均为0。

0x07 MNIST 初上手

下面给出一段训练MNIST手写数字识别的代码的代码:
1
import tensorflow as tf
2
from tensorflow.examples.tutorials.mnist import input_data
3
4
# 以one-hot编码读入data文件夹中的数据
5
mnist = input_data.read_data_sets("data", one_hot=True)
6
7
# 行表示有多少张图片,列表示每张图片的28*28=784个像素点
8
x = tf.placeholder(tf.float32, shape=[None, 784])
9
# 行表示有多少张图片,列表示返回有10种不同的可能(one-hot集有10列)
10
y = tf.placeholder(tf.float32, shape=[None, 10])
11
12
# x * weight所得矩阵即为[None, 10]
13
weight = tf.Variable(tf.zeros([784, 10]))
14
bias = tf.Variable(tf.zeros([10]))
15
16
# 使用softmax函数作为激活函数,softmax返回一个概率矩阵,即预测的one-hot集中每一个数据的概率
17
prediction = tf.nn.softmax(tf.matmul(x, weight) + bias)
18
# 用方差来衡量损失
19
loss = tf.reduce_mean(tf.square(y - prediction))
20
21
# 以0.2的学习率进行训练
22
train = tf.train.GradientDescentOptimizer(0.2).minimize(loss)
23
24
with tf.Session() as sess:
25
sess.run(tf.global_variables_initializer())
26
# 整个数据集将分批投放,每一批的大小为100
27
batch_size = 100
28
# 一共有多少批
29
batch_num = mnist.train.num_examples // batch_size
30
# 将所有的数据循环训练100次
31
for _ in range(100):
32
# 分批进行训练
33
for i in range(batch_num):
34
# 获取数据集及答案集
35
images, labels = mnist.train.next_batch(batch_size)
36
sess.run(train, feed_dict={x: images, y: labels})
37
# 测量准确率
38
# argmax(input, axis)用于获取input沿着axis的方向的最大值
39
# 其中axis默认为0,表示列方向,axis=1表示行方向
40
# softmax函数返回一个概率矩阵,此处使用
41
# tf.argmax(prediction, 1)和tf.argmax(y, 1)
42
# 用于获取预测所得最大概率的那一方
43
# 和答案集one-hot数据中标识为1的那一方所在的下标是否相等
44
correct_prediction = tf.equal(tf.argmax(prediction, 1), tf.argmax(y, 1))
45
# 将数据转换为float32类型
46
# 然后对矩阵中所有的数据求均值得出准确率
47
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
48
print(sess.run(accuracy, feed_dict={x: mnist.test.images, y: mnist.test.labels}) * 100, "%")
Copied!

0x08 重新认识学习率

在上述训练MNIST数据集的例子中,我们使用的学习率为恒定值0.2,循环训练100遍后对验证集所产生的准确率大约为92.68%。正如我们之前所述,学习率太小,梯度下降的速率会比较低下,如果学习率太大,梯度下降的步伐就会摇摆不定,容易产生震荡。所以,此处我们可以使用动态学习率,初始设置一个较大的学习率,此后随着迭代次数的增加,指数减缓学习率的值,例如:
依据上述公式,我们假设初始学习率为2,每训练5次,指数减小一次学习率为例,改进上述代码,得:
1
import tensorflow as tf
2
import math
3
from tensorflow.examples.tutorials.mnist import input_data
4
5
mnist = input_data.read_data_sets("data", one_hot=True)
6
7
x = tf.placeholder(tf.float32, shape=[None, 784])
8
y = tf.placeholder(tf.float32, shape=[None, 10])
9
10
weight = tf.Variable(tf.zeros([784, 10]))
11
bias = tf.Variable(tf.zeros([10]))
12
13
prediction = tf.nn.softmax(tf.matmul(x, weight) + bias)
14
15
loss = tf.reduce_mean(tf.square(y - prediction))
16
17
with tf.Session() as sess:
18
sess.run(tf.global_variables_initializer())
19
batch_size = 100
20
batch_num = mnist.train.num_examples // batch_size
21
learn_rate_t = 0
22
train = tf.train.GradientDescentOptimizer(2).minimize(loss)
23
for _ in range(100):
24
if _ % 5 == 0:
25
# 动态学习率
26
learn_rate = math.pow(0.95, learn_rate_t) * 2
27
learn_rate_t = learn_rate_t + 1
28
train = tf.train.GradientDescentOptimizer(learn_rate).minimize(loss)
29
for i in range(batch_num):
30
images, labels = mnist.train.next_batch(batch_size)
31
sess.run(train, feed_dict={x: images, y: labels})
32
correct_prediction = tf.equal(tf.argmax(prediction, 1), tf.argmax(y, 1))
33
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
34
print(sess.run(accuracy, feed_dict={x: mnist.test.images, y: mnist.test.labels}) * 100, "%")
Copied!
使用此代码再对MNIST进行训练,此后使用测试集来验证所得的准确率为93.10%。

0x09 梯度下降法原理

梯度下降法核心公式就一个:
x1=x0αf(x0)x_1=x_0-\alpha f'(x_0)
其中:
  • x0x_0
    代表x的当前位置
  • x1x_1
    代表x的下一个位置
  • α\alpha
    代表学习率
假设对于函数
f(x)=x2f(x)=x^2
,我们要通过梯度下降找出其最小值,假设学习率我们固定为0.25,当前位置
x0=3x_0=3
,我们来逐步进行梯度下降:
f(x)=x2f(x)=2xstep 1.  x=30.25×2×3=1.5step 2.  x=1.50.25×2×1.5=0.75step 3.  x=0.750.25×2×0.75=0.375f(x)=x^2\Rightarrow f'(x)=2x \\ \text{step 1. }~x=3-0.25\times2\times 3=1.5 \\ \text{step 2. }~x=1.5-0.25\times2\times 1.5=0.75 \\ \text{step 3. }~x=0.75-0.25\times2\times 0.75=0.375 \\ \cdots
通过梯度下降,
xx
的值逐渐向
f(x)f(x)
的最小值点
x=0x=0
靠近了。

0x0A 二次代价函数和交叉熵代价函数

二次代价函数的数学表达实为:
C=12nx(yprediction)2prediction=σ(z)z=wx+bC=\frac{1}{2n}\sum_x(y-\text{prediction})^2 \\ \text{prediction}=\sigma(z) \\ z=wx+b
其中:
  • n为样本数量
  • x为输入值,y为正确的输出值
  • prediction为预测值,
    σ(z)\sigma(z)
    为激活函数
在python中,我们一般以如下的形式表达二次代价函数:
1
loss = tf.reduce_mean(tf.square(y - prediction))
Copied!
我们假设只有一组样本,来对上述的公式进行简化得:
C=(yprediction)22prediction=σ(z)z=wx+bC=\frac{(y-\text{prediction})^2}{2} \\ \text{prediction}=\sigma(z) \\ z=wx+b
在深度学习的过程中,我们通过梯度下降的方法不断优化w和b的值,使最终产生的代价函数值尽可能地小。由此,对上述简化的例子,在梯度下降时所使用的2个偏导数分别为:
Cw=(yprediction)σ(z)xCb=(yprediction)σ(z)prediction=σ(z)z=wx+b\frac{\partial C}{\partial w}=(y-\text{prediction})\sigma'(z)x \\ \frac{\partial C}{\partial b}=(y-\text{prediction})\sigma'(z) \\ \text{prediction}=\sigma(z) \\ z=wx+b
由上式,我们发现其梯度下降的速度与激活函数的导数成正比,激活函数的导数越大,w和b调整地就越快,训练收敛地也就越快。鉴于,我们一般使用sigmoid函数作为激活函数,我们来看一下这个函数的图像:
由上图我们可以发现,在绿点的时候,sigmoid函数的斜率是低于其在红点时的斜率的。由此在梯度下降的过程中,在绿点的下降速率也是低于在红点时的下降速率的。但是,我们可以看到,绿点离最小值的距离是大于红点离最小值的距离的。这往往不是我们想要的,我们想要的是错误越大改正的幅度越大,从而学习得越快。
由此,我们抛弃了二次代价函数,引入了交叉熵代价函数,其基本的数学形式如下:
C=1nx[yln(prediction)+(1y)ln(1prediction)]prediction=σ(z)z=wx+bC=-\frac{1}{n}\sum_x[y\ln(\text{prediction})+(1-y)\ln(1-\text{prediction})] \\ \text{prediction}=\sigma(z) \\ z=wx+b
我们对交叉熵代价函数求导可得:
Cw=1nxx(σ(z)y)Cb=1nx(σ(z)y)z=wx+b\frac{\partial C}{\partial w}=\frac{1}{n}\sum_x x(\sigma(z)-y) \\ \frac{\partial C}{\partial b}=\frac{1}{n}\sum_x (\sigma(z)-y) \\ z=wx+b
由此,我们可以看出,当误差
σ(z)y\sigma(z)-y
越大,参数w和b调整的速度就越快,梯度下降的速率也就越大。

0x0B 使用交叉熵代价函数优化MNIST的训练

下面我们将使用交叉熵代价函数,对上文中所给出的MNIST训练集进行优化,代码如下:
1
import tensorflow as tf
2
from tensorflow.examples.tutorials.mnist import input_data
3
4
mnist = input_data.read_data_sets("data", one_hot=True)
5
6
x = tf.placeholder(tf.float32, shape=[None, 784])
7
y = tf.placeholder(tf.float32, shape=[None, 10])
8
9
weight = tf.Variable(tf.zeros([784, 10]))
10
bias = tf.Variable(tf.zeros([10]))
11
12
# tensorflow所自带的交叉熵代价函数已内置求解softmax函数
13
# 所以此处的prediction无需计算softmax
14
prediction = tf.matmul(x, weight) + bias
15
16
# 使用tf.nn.softmax_cross_entropy_with_logits来计算
17
# softmax的交叉熵结果,其中参数labels表示正确答案集
18
# logits表示预测的结果集
19
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y, logits=prediction))
20
21
# 以0.2的学习率进行训练
22
train = tf.train.GradientDescentOptimizer(0.2).minimize(loss)
23
24
with tf.Session() as sess:
25
sess.run(tf.global_variables_initializer())
26
batch_size = 100
27
batch_num = mnist.train.num_examples // batch_size
28
for _ in range(100):
29
for i in range(batch_num):
30
images, labels = mnist.train.next_batch(batch_size)
31
sess.run(train, feed_dict={x: images, y: labels})
32
correct_prediction = tf.equal(tf.argmax(prediction, 1), tf.argmax(y, 1))
33
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
34
print(sess.run(accuracy, feed_dict={x: mnist.test.images, y: mnist.test.labels}) * 100, "%")
Copied!
通过对比上述2代码,我们可以发现使用交叉熵代价函数,可以使训练快速地收敛到一定范围,上述代码训练所得的准确率在92.61%左右。
最近更新 1yr ago