上一节课我们讲了Tensorflow的工作机制和如何导入测试数据,接下来让我们来看一个最简单示例。学习这节课程需要对机器学习的逻辑回归算法有所了解,如果你对softmax多分类,损失函数,梯度下降算法等感觉很陌生,先学习小白写的机器学习系列.之.逻辑回归。
任务描述
创建一个简单的逻辑回归多分类的学习算法,我们的训练目标是,通过输入鸢尾花的花萼和花瓣的长度和宽度, 机器能够准确在三种鸢尾花的分类中判断出该鸢尾花的种类。
在该例子中我们会用到鸢尾花数据集(参照百度词条IRIS数据集),该数据集共有150条记录,每条记录包含以下5个数据。
- 花萼长度(Sepal.Length),单位是cm;
- 花萼宽度(Sepal.Width),单位是cm;
- 花瓣长度(Petal.Length),单位是cm;
- 花瓣宽度(Petal.Width),单位是cm;
- 种类:山鸢尾(Iris Setosa)、杂色鸢尾(Iris Versicolour),维吉尼亚鸢尾(Iris Virginica)。
算法介绍
我们会用到一个简单的逻辑回归算法,具体实现过程如下图。
第一步:将花萼长度,花萼宽度,花瓣长度,花瓣宽度作为四个输入参数,分别对应x1,x2,x3,x4。
第二步:一个简单的线性变换,将X乘以一个权重矩阵weight再加上偏差bias,得到中间结果Z。这里要注意的是,因为我们的输出分类共有三种,所以我们要得到的Z是一个3x1的矩阵。
第三步:我们会使用到softmax这个多分类函数。softmax可以将一组输入参数映射到(0,1)区间内,可以看做概率。我们最终得到的输出结果S是一个3x1的矩阵,每个元素代表鸢尾花的一种分类,该元素的值是输入的数据属于这种分类上的概率。
代码实现
我们先来看一下这个计算的过程。如图四所示:
1. 变量定义如下
输入参数 X: 1x4的矩阵
输入标签 Y:3x1的矩阵,[1,0,0] 表示山鸢尾(Iris Setosa), [0,1,0] 表示杂色鸢尾(Iris Versicolour),[0,0,1]表示维吉尼亚鸢尾(Iris Virginica)
权重矩阵W:4x3的矩阵,随机初始化
W = tf.Variable(tf.random_normal([4,3], mean=0.0, stddev=1.0,
dtype=tf.float32), trainable=True, name='weight')
偏移矩阵b:3x1的矩阵,初始化为0
b = tf.Variable(tf.zeros([3]),trainable=True, name='bias')
2. 构建计算图
第一步 计算预测结果
先计算X和W的乘积并加上b得到一个3x1的矩阵,对这个结果再求softmax,则可得到一个3x1的矩阵,矩阵中的每个元素是在(0,1)空间的一个概率。
logits = tf.nn.softmax(tf.matmul(X,W) + b, name = 'softmax')
第二步 计算损失
按照惯例,我们使用交叉熵作为损失函数(loss function)。其中Y是训练集中的数据类别标签, logits是我们的逻辑回归算法给出的结果。
cross_entropy = -tf.reduce_sum(Y*tf.log(logits))
第三步 使用梯度下降算法来更新W和b以获得最小化的损失
Tensorflow提供了非常简洁的梯度下降算法的调用,只需要调用一条语句,则可实现好几重功能。
先定义一个tf.train.GradientDescentOptimizer对象,且设它的梯度下降步长为0.01。
调用minimize方法计算损失函数的梯度,并自动更新W和b以获得最小化的loss,至此一个学习周期已经完成。
train_op = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)
第四步 计算训练中的正确率
在机器学习中我们还要监督机器学习的效果,评估训练中的正确率,以判断学习的效果和防止过度拟合等情况。因此,我们在每一次的学习中还需要评估算法输出的正确率。依然,我们也可以用一句代码在Tensorflow里实现监督。
train_eval = tf.reduce_mean(tf.cast(tf.equal(tf.arg_max(logits,1),
tf.arg_max(Y,1)),dtype=tf.float32))
其中arg_max方法需要指定一个维度,然后返回该张量在该维度里最大值的索引。举例:
对[ [ 1 0 0 ] [ 0 1 0 ] [ 0 0 1] ] 使用arg_max方法,并且指定dimension = 1(即[ 1 0 0 ] ,[ 0 1 0 ]和 [ 0 0 1]三个张量), 注意tensorflow里的dimension从0开始,则得到的结果是[ 0, 1, 2 ],即第1个张量[ 1 0 0 ]最大值的索引为0,第2个张量 [ 0 1 0 ]最大值的索引为1,第3个张量 [ 0 0 1]最大值的索引为2。
我们对正确标签Y和预测结果分别使用这个方法,就会得到两个一维的张量,元素值属于集合{0, 1, 2}。
然后我们使用equal比较上面产生的两个张量的每个元素是否一一相等,其返回值是一个布尔型的张量,维度和进行比较的两个张量一样,每个元素的值是两个张量对应元素比较的布尔值结果,相等为True(1),不等为False(0)。比如:
使用equal方法比较 [ 1 2 0 2 0 ] 和 [ 0 2 0 1 2 ] ,结果为 [ 0 1 1 0 0 ]
因此我们会得到一个由 0 和 1 组成的张量,再使用reduce_mean方法求其均值,既可以得到在这个张量里1的元素所占的比例, 也就是算法输出logits和训练数据标签Y比较后得到的正确率。
3. 执行训练
第一步 初始化变量和队列
global_variables_initializer用于初始化所有的变量, 而local_variables_initializer初始化局部变量,局部变量指的是被添加入Graph中,但是未被储存的变量。
先分别生成两个初始化算符,创建一个tf.Session对象作为当前Session。调用初始化算符既可以用session对象的run方法,也可以直接调用算符的run方法。
然后,启动队列,这在上一节课里已经详细介绍了,此处不再赘述。
init_local = tf.local_variables_initializer()
init_global = tf.global_variables_initializer()
with tf.Session() as sess:
#初始化本地和全局变量
init_local.run()
init_global.run()
#创建一个Coordinator用于训练结束后关闭队列
coord = tf.train.Coordinator()
#启动队列
threads = tf.train.start_queue_runners(sess, coord)
第二步 建立一个循环反复执行训练
从图四可知,我们计算cross_entropy和梯度下降算法(train_op),会完成一次对W和b的更新。因此使用一个循环,来运行cross_entropy和train_op, 我们将循环的次数限定为2000次或者训练数据队列停止。 在训练的同时, 我们还想监控训练的正确率,因此在run函数里在加一个train_eval的操作,返回值就是该次训练的正确率。我们将这些数值都在控制台输出。
try:
i = 0
while (not coord.should_stop()) and i < 2000:
loss, _,accuracy = sess.run([cross_entropy,train_op,train_eval])
print('Train step:' + str(i) + '------------------------------')
print('Logits: '+str(Y_))
print('Loss: '+str(loss))
print('Train accuracy:' + str(accuracy*100)+'%')
i += 1
except tf.errors.OutOfRangeError:
print("Training done")
finally:
coord.request_stop()
coord.join(threads)
接下来我们就可执行代码,然后泡一杯香茗,看着计算机学习如何通过花瓣和花萼的尺寸来识别鸢尾花的类别了。
4. 输出结果
运行代码,我们得到loss和train accuracy的图像分别如下。准确率前期有比较大的波动,后期逐渐稳定趋于100%。 而损失函数的结果也逐渐变小。
完整源码
# -*- coding: utf-8 -*-
"""
Spyder Editor
This is a temporary script file.
"""
import tensorflow as tf
TRAIN_BATCH_SIZE = 3 #每次取一个数据点训练
def get_batch_data(file_queue, batch_size):
reader = tf.TextLineReader(skip_header_lines=1)
key, value = reader.read(file_queue)
Id, Sepal_Length,Sepal_Width,Petal_Length,Petal_Width,label = tf.decode_csv(value,record_defaults=[[1],[1.0],[1.0],[1.0],[1.0],['null']])
Y = tf.case({
tf.equal(label, tf.constant('setosa')): lambda: tf.constant([1,0,0],dtype = tf.float32), \
tf.equal(label, tf.constant('versicolor')): lambda: tf.constant([0,1,0],dtype = tf.float32), \
tf.equal(label, tf.constant('virginica')): lambda: tf.constant([0,0,1],dtype = tf.float32),}, \
lambda: tf.constant([0,0,0],dtype = tf.float32), exclusive=True)
get_data, get_label = tf.train.batch([[Sepal_Length,Sepal_Width,Petal_Length,Petal_Width],Y],
batch_size = batch_size)
return get_data, get_label
with tf.Graph().as_default() as g:
#生成训练数据文件队列,此处我们只有一个训练数据文件
train_file_queue = tf.train.string_input_producer(['iris.csv'], num_epochs=None)
#从训练数据文件列表中获取数据和标签
data,Y = get_batch_data(train_file_queue, TRAIN_BATCH_SIZE)
#将数据reshape成[每批次训练样本数,每个训练样本包含的数据量]
X =tf.reshape(data,[TRAIN_BATCH_SIZE,4])
#随机初始化一个weight变量
W = tf.Variable(tf.random_normal([4,3], mean=0.0, stddev=1.0, dtype=tf.float32),
trainable=True, name='weight')
#初始化bias变量为0
b = tf.Variable(tf.zeros([3]),trainable=True, name='bias')
#使用softmax作为激活函数
logits = tf.nn.softmax(tf.matmul(X,W) + b, name = 'softmax')
#计算交叉熵
cross_entropy = -tf.reduce_sum(Y*tf.log(logits))
#定义一个优化器
Optimizer = tf.train.GradientDescentOptimizer(0.01)
#计算梯度
gradient = Optimizer.compute_gradients(cross_entropy)
#更新W和B来最小化交叉熵
train_op = Optimizer.apply_gradients(gradient)
train_eval = tf.reduce_mean(tf.cast(tf.equal(tf.arg_max(logits,1),tf.arg_max(Y,1)),dtype=tf.float32))
#local_variables_initializer则会报错
init_local = tf.local_variables_initializer()
#初始化所有全局变量,
init_global = tf.global_variables_initializer()
with tf.Session() as sess:
#初始化本地和全局变量
init_local.run()
init_global.run()
#创建一个Coordinator用于训练结束后关闭队列
coord = tf.train.Coordinator()
#启动队列
threads = tf.train.start_queue_runners(sess, coord)
try:
i = 0
while (not coord.should_stop()) and i < 2000:
loss, _,accuracy = sess.run([cross_entropy,train_op,train_eval])
print('Train step:' + str(i) + '-----------------------------------')
print('Loss: '+str(loss))
print('Train accuracy:' + str(accuracy*100)+'%')
i += 1
except tf.errors.OutOfRangeError:
print("Training done")
finally:
coord.request_stop()
coord.join(threads)