第二课 一个简单的逻辑回归分类例子

上一节课我们讲了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经过softmax的逻辑回归算法得到输出S

第一步:将花萼长度,花萼宽度,花瓣长度,花瓣宽度作为四个输入参数,分别对应x1,x2,x3,x4。

第二步:一个简单的线性变换,将X乘以一个权重矩阵weight再加上偏差bias,得到中间结果Z。这里要注意的是,因为我们的输出分类共有三种,所以我们要得到的Z是一个3x1的矩阵。

图二 x乘以weight再加上bias得到中间变量Z

第三步:我们会使用到softmax这个多分类函数。softmax可以将一组输入参数映射到(0,1)区间内,可以看做概率。我们最终得到的输出结果S是一个3x1的矩阵,每个元素代表鸢尾花的一种分类,该元素的值​是输入的数据属于这种分类上的概率​​。

图三,S是一个3x1的矩阵,每个元素代表一种分类,该元素的值​是输入X属于这种分类上的概率​​。​

 

代码实现

我们先来看一下这个计算的过程。如图四所示:

图四 训练是一个循环往复的过程

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%。 而损失函数的结果也逐渐变小。

图五 正确率accuracy的图形,每循环50次后的结果
图六损失函数的计算结果,每循环50次记录的结果

完整源码

Github源码链接:https://github.com/deechean/Using-Tensorflow-to-create-your-own-neural-network-samples/blob/master/softmax_irisclassify.py

# -*- 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)   

猜你喜欢

转载自blog.csdn.net/deecheanW/article/details/82505144