【开源框架】Tensorflow图像分类从模型自定义到测试

上一篇介绍了 Caffe ,这篇将介绍 TensorFlow 相关的内容。

1 什么是 TensorFlow

TensorFlow 是 Google brain 推出的开源机器学习库,与 Caffe 一样,主要用作深度学习相关的任务。

与 Caffe 相比 TensorFlow 的安装简单很多,一条 pip 命令就可以解决,新手也不会误入各种坑。

TensorFlow = Tensor + Flow

Tensor 就是张量,代表 N 维数组,与 Caffe 中的 blob 是类似的;Flow 即流,代表基于数据流图的计算。神经网络的运算过程,就是数据从一层流动到下一层,在 Caffe 的每一个中间 layer 参数中,都有 bottom 和 top,这就是一个分析和处理的过程。TensorFlow 更直接强调了这个过程。

TensorFlow 最大的特点是计算图,即先定义好图,然后进行运算,所以所有的 TensorFlow 代码,都包含两部分:

(1)创建计算图,表示计算的数据流。它做了什么呢?实际上就是定义好了一些操作,你可以将它看做是 Caffe 中的 prototxt 的定义过程。

(2)运行会话,执行图中的运算,可以看作是 Caffe 中的训练过程。只是 TensorFlow 的会话比 Caffe 灵活很多,由于是 Python 接口,取中间结果分析,Debug 等方便很多。

2 TensorFlow 训练

咱们这是实战课,没有这么多时间去把所有事情细节都说清楚,而是抓住主要脉络。有了 TensorFlow 这个工具后,我们接下来的任务就是开始训练模型。训练模型,包括数据准备、模型定义、结果保存与分析。

2.1 数据准备

上一节我们说过 Caffe 中的数据准备,只需要准备一个 list 文件,其中每一行存储 image、labelid 就可以了,那是 Caffe 默认的分类网络的 imagedata 层的输入格式。如果想定义自己的输入格式,可以去新建自定义的 Data Layer,而 Caffe 官方的 data layer 和 imagedata layer 都非常稳定,几乎没有变过,这是我更欣赏 Caffe 的一个原因。因为输入数据,简单即可。相比之下,TensorFlow 中的数据输入接口就要复杂很多,更新也非常快,我知乎有一篇文章,说过从《从 Caffe 到 TensorFlow 1,IO 操作》,有兴趣的读者可以了解一下。

这里我们不再说 TensorFlow 中有多少种数据 IO 方法,先确定好我们的数据格式,那就是跟 Caffe 一样,准备好一个 list,它的格式一样是 image、labelid,然后再看如何将数据读入 TensorFlow 进行训练。

我们定义一个类,叫 imagedata,模仿 Caffe 中的使用方式。代码如下,源代码可移步 Git 公众号:

import tensorflow as tf
    from tensorflow.contrib.data import Dataset
    from tensorflow.python.framework import dtypes
    from tensorflow.python.framework.ops import convert_to_tensor
    import numpy as np
    class ImageData:        def read_txt_file(self):
            self.img_paths = []
            self.labels = []
            for line in open(self.txt_file, 'r'):
                items = line.split(' ')
                self.img_paths.append(items[0])
                self.labels.append(int(items[1]))
        def __init__(self, txt_file, batch_size, num_classes,
                     image_size,buffer_scale=100):
            self.image_size = image_size
            self.batch_size = batch_size
            self.txt_file = txt_file ##txt list file,stored as: imagename id            self.num_classes = num_classes
            buffer_size = batch_size * buffer_scale

        # 读取图片        self.read_txt_file()
        self.dataset_size = len(self.labels) 
        print "num of train datas=",self.dataset_size
        # 转换成Tensor        self.img_paths = convert_to_tensor(self.img_paths, dtype=dtypes.string)
        self.labels = convert_to_tensor(self.labels, dtype=dtypes.int32)

        # 创建数据集        data = Dataset.from_tensor_slices((self.img_paths, self.labels))
        print "data type=",type(data)
        data = data.map(self.parse_function)
        data = data.repeat(1000)
        data = data.shuffle(buffer_size=buffer_size)

        # 设置self data Batch        self.data = data.batch(batch_size)
        print "self.data type=",type(self.data)

        def augment_dataset(self,image,size):
            distorted_image = tf.image.random_brightness(image,
                                               max_delta=63)
            distorted_image = tf.image.random_contrast(distorted_image,
                                             lower=0.2, upper=1.8)
            # Subtract off the mean and divide by the variance of the pixels.            float_image = tf.image.per_image_standardization(distorted_image)
            return float_image

        def parse_function(self, filename, label):
            label_ = tf.one_hot(label, self.num_classes)
            img = tf.read_file(filename)
            img = tf.image.decode_jpeg(img, channels=3)
            img = tf.image.convert_image_dtype(img, dtype = tf.float32)
            img = tf.random_crop(img,[self.image_size[0],self.image_size[1],3])
            img = tf.image.random_flip_left_right(img)
            img = self.augment_dataset(img,self.image_size)
            return img, label_

下面来分析上面的代码,类是 ImageData,它包含几个函数,__init__构造函数,read_txt_file数据读取函数,parse_function数据预处理函数,augment_dataset数据增强函数。

我们直接看构造函数吧,分为几个步骤:

(1)读取变量,文本 list 文件txt_file,批处理大小batch_size,类别数num_classes,要处理成的图片大小image_size,一个内存变量buffer_scale=100

(2)在获取完这些值之后,就到了read_txt_file函数。代码很简单,就是利用self.img_pathsself.labels存储输入 txt 中的文件列表和对应的 label,这一点和 Caffe 很像了。

(3)然后,就是分别将img_pathslabels 转换为 Tensor,函数是convert_to_tensor,这是 Tensor 内部的数据结构。

(4)创建 dataset,Dataset.from_tensor_slices,这一步,是为了将 img 和 label 合并到一个数据格式,此后我们将利用它的接口,来循环读取数据做训练。当然,创建好 dataset 之后,我们需要给它赋值才能真正的有数据。data.map 就是数据的预处理,包括读取图片、转换格式、随机旋转等操作,可以在这里做。

data = data.repeat(1000) 是将数据复制 1000 份,这可以满足我们训练 1000 个 epochs。data = data.shuffle(buffer_size=buffer_size)就是数据 shuffle 了,buffer_size就是在做 shuffle 操作时的控制变量,内存越大,就可以用越大的值。

(5)给 selft.data 赋值,我们每次训练的时候,是取一个 batchsize 的数据,所以 self.data = data.batch(batch_size),就是从上面创建的 dataset 中,一次取一个 batch 的数据。

到此,数据接口就定义完毕了,接下来在训练代码中看如何使用迭代器进行数据读取就可以了。

关于更多 TensorFlow 的数据读取方法,请移步知乎专栏和公众号。

2.2 模型定义

创建数据接口后,我们开始定义一个网络。

def simpleconv3net(x):        x_shape = tf.shape(x)
        with tf.variable_scope("conv3_net"):
            conv1 = tf.layers.conv2d(x, name="conv1", filters=12,kernel_size=[3,3], strides=(2,2), activation=tf.nn.relu,kernel_initializer=tf.contrib.layers.xavier_initializer(),bias_initializer=tf.contrib.layers.xavier_initializer())
            bn1 = tf.layers.batch_normalization(conv1, training=True, name='bn1')
            conv2 = tf.layers.conv2d(bn1, name="conv2", filters=24,kernel_size=[3,3], strides=(2,2), activation=tf.nn.relu,kernel_initializer=tf.contrib.layers.xavier_initializer(),bias_initializer=tf.contrib.layers.xavier_initializer())
            bn2 = tf.layers.batch_normalization(conv2, training=True, name='bn2')
            conv3 = tf.layers.conv2d(bn2, name="conv3", filters=48,kernel_size=[3,3], strides=(2,2), activation=tf.nn.relu,kernel_initializer=tf.contrib.layers.xavier_initializer(),bias_initializer=tf.contrib.layers.xavier_initializer())
            bn3 = tf.layers.batch_normalization(conv3, training=True, name='bn3')
            conv3_flat = tf.reshape(bn3, [-1, 5 * 5 * 48])
            dense = tf.layers.dense(inputs=conv3_flat, units=128, activation=tf.nn.relu,name="dense",kernel_initializer=tf.contrib.layers.xavier_initializer())
            logits= tf.layers.dense(inputs=dense, units=2, activation=tf.nn.relu,name="logits",kernel_initializer=tf.contrib.layers.xavier_initializer())

            if debug:
                print "x size=",x.shape
                print "relu_conv1 size=",conv1.shape
                print "relu_conv2 size=",conv2.shape
                print "relu_conv3 size=",conv3.shape
                print "dense size=",dense.shape
                print "logits size=",logits.shape

        return logits

上面就是我们定义的网络,是一个简单的3层卷积。在 tf.layers 下,有各种网络层,这里就用到了 tf.layers.conv2dtf.layers.batch_normalizationtf.layers.dense,分别是卷积层,BN 层和全连接层。我们以一个卷积层为例:

conv1 = tf.layers.conv2d(x, name="conv1", filters=12,kernel_size=[3,3], strides=(2,2), activation=tf.nn.relu,kernel_initializer=tf.contrib.layers.xavier_initializer(),bias_initializer=tf.contrib.layers.xavier_initializer())

x 即输入,name 是网络名字,filters 是卷积核数量,kernel_size即卷积核大小,strides 是卷积 stride,activation 即激活函数,kernel_initializerbias_initializer分别是初始化方法。可见已经将激活函数整合进了卷积层,更全面的参数,请自查 API。

其实网络的定义,还有其他接口,tf.nn、tf.layers、tf.contrib,各自重复,在我看来有些混乱。

这里之所以用 tf.layers,就是因为参数丰富,适合从头训练一个模型。

2.3 模型训练

老规矩,我们直接上代码,其实很简单。

////-------1 定义一些全局变量-------////

    from dataset import *
    from net import simpleconv3net
    import sys
    import cv2
    txtfile = sys.argv[1]
    batch_size = 16    num_classes = 2    image_size = (48,48)
    learning_rate = 0.01    debug=False
////-------2 载入网络结构,定义损失函数,创建计算图-------////

    dataset = ImageData(txtfile,batch_size,num_classes,image_size)
    iterator = dataset.data.make_one_shot_iterator()
    dataset_size = dataset.dataset_size
    batch_images,batch_labels = iterator.get_next()
    Ylogits = simpleconv3net(batch_images)

    print "Ylogits size=",Ylogits.shape

    Y = tf.nn.softmax(Ylogits)
    cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits=Ylogits, labels=batch_labels)
    cross_entropy = tf.reduce_mean(cross_entropy)
    correct_prediction = tf.equal(tf.argmax(Y, 1), tf.argmax(batch_labels, 1))
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
    update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
    with tf.control_dependencies(update_ops):
        train_step = tf.train.AdamOptimizer(learning_rate).minimize(cross_entropy)

    saver = tf.train.Saver()
    in_steps = 100
    checkpoint_dir = 'checkpoints/'
    if not os.path.exists(checkpoint_dir):
        os.mkdir(checkpoint_dir)
    log_dir = 'logs/'
    if not os.path.exists(log_dir):
        os.mkdir(log_dir)
    summary = tf.summary.FileWriter(logdir=log_dir)
    loss_summary = tf.summary.scalar("loss", cross_entropy)
    acc_summary = tf.summary.scalar("acc", accuracy)
    image_summary = tf.summary.image("image", batch_images)
////-------3 执行会话,保存相关变量,还可以添加一些debug函数来查看中间结果-------////

    with tf.Session() as sess:  
        init = tf.global_variables_initializer()
        sess.run(init)  
        steps = 10000  
        for i in range(steps): 
            _,cross_entropy_,accuracy_,batch_images_,batch_labels_,loss_summary_,acc_summary_,image_summary_ = sess.run([train_step,cross_entropy,accuracy,batch_images,batch_labels,loss_summary,acc_summary,image_summary])
            if i % in_steps == 0 :
                print i,"iterations,loss=",cross_entropy_,"acc=",accuracy_
                saver.save(sess, checkpoint_dir + 'model.ckpt', global_step=i)    
                summary.add_summary(loss_summary_, i)
                summary.add_summary(acc_summary_, i)
                summary.add_summary(image_summary_, i)
                #print "predict=",Ylogits," labels=",batch_labels

                if debug:
                    imagedebug = batch_images_[0].copy()
                    imagedebug = np.squeeze(imagedebug)
                    print imagedebug,imagedebug.shape
                    print np.max(imagedebug)
                    imagelabel = batch_labels_[0].copy()
                    print np.squeeze(imagelabel)

                    imagedebug = cv2.cvtColor((imagedebug*255).astype(np.uint8),cv2.COLOR_RGB2BGR)
                    cv2.namedWindow("debug image",0)
                    cv2.imshow("debug image",imagedebug)
                    k = cv2.waitKey(0)
                    if k == ord('q'):
                        break

2.4 可视化

TensorFlow 很方便的一点,就是 Tensorboard 可视化。Tensorboard 的具体原理就不细说了,很简单,就是三步。

第一步,创建日志目录。

log_dir = 'logs/'    if not os.path.exists(log_dir):
        os.mkdir(log_dir)

第二步,创建 summary 操作并分配标签,如我们要记录 loss、acc 和迭代中的图片,则创建了下面的变量:

loss_summary = tf.summary.scalar("loss", cross_entropy)acc_summary = tf.summary.scalar("acc", accuracy)image_summary = tf.summary.image("image", batch_images)

第三步,session 中记录结果,如下面代码:

_,cross_entropy_,accuracy_,batch_images_,batch_labels_,loss_summary_,acc_summary_,image_summary_ = sess.run([train_step,cross_entropy,accuracy,batch_images,batch_labels,loss_summary,acc_summary,image_summary])

查看训练过程和最终结果时使用:

tensorboard --logdir=logs

Loss 和 acc 的曲线图如下:

《【开源框架】Tensorflow图像分类从模型自定义到测试》
《【开源框架】Tensorflow图像分类从模型自定义到测试》

3 TensorFlow 测试

上面已经训练好了模型,我们接下来的目标,就是要用它来做 inference 了。同样给出代码。

import tensorflow as tf
from net import simpleconv3net
import sys
import numpy as np
import cv2
import os

testsize = 48
x = tf.placeholder(tf.float32, [1,testsize,testsize,3])
y = simpleconv3net(x)
y = tf.nn.softmax(y)

lines = open(sys.argv[2]).readlines()
count = 0
acc = 0
posacc = 0
negacc = 0
poscount = 0
negcount = 0

with tf.Session() as sess:  
    init = tf.global_variables_initializer()
    sess.run(init)  
    saver = tf.train.Saver()
    saver.restore(sess,sys.argv[1])
    
    #test one by one, you can change it into batch inputs
    for line in lines:
        imagename,label = line.strip().split(' ')
        img = tf.read_file(imagename)
        img = tf.image.decode_jpeg(img,channels = 3)
        img = tf.image.convert_image_dtype(img,dtype = tf.float32)
        img = tf.image.resize_images(img,(testsize,testsize),method=tf.image.ResizeMethod.NEAREST_NEIGHBOR)
        img = tf.image.per_image_standardization(img)

        imgnumpy = img.eval()
        imgs = np.zeros([1,testsize,testsize,3],dtype=np.float32)
        imgs[0:1,] = imgnumpy

        result = sess.run(y, feed_dict={x:imgs})
        result = np.squeeze(result)
        if result[0] > result[1]:
            predict = 0
        else:
            predict = 1

        count = count + 1
        if str(predict) == '0':
            negcount = negcount + 1
            if str(label) == str(predict):
                negacc = negacc + 1
                acc = acc + 1
        else:
            poscount = poscount + 1
            if str(label) == str(predict):
                posacc = posacc + 1
                acc = acc + 1
    
        print result
print "acc = ",float(acc) / float(count)
print "poscount=",poscount
print "posacc = ",float(posacc) / float(poscount)
print "negcount=",negcount
print "negacc = ",float(negacc) / float(negcount)

从上面的代码可知,与 Train 时同样,需要定义模型,这个跟 Caffe 在测试时使用的 Deploy 是一样的。

然后,用 restore 函数从 saver 中载入参数,读取图像并准备好网络的格式,sess.run 就可以得到最终的结果了。

总结:本篇内容讲解了一个最简单的分类例子,相比大部分已封装好的 mnist 或 cifar 为例的代码来说更实用。我们自己准备了数据集,自己设计了网络并进行了结果可视化,学习了如何使用已经训练好的模型做预测。

《【开源框架】Tensorflow图像分类从模型自定义到测试》
《【开源框架】Tensorflow图像分类从模型自定义到测试》

本系列完整文章:

第一篇:【caffe速成】caffe图像分类从模型自定义到测试

第二篇:【tensorflow速成】Tensorflow图像分类从模型自定义到测试

第三篇:【pytorch速成】Pytorch图像分类从模型自定义到测试

第四篇:【paddlepaddle速成】paddlepaddle图像分类从模型自定义到测试

第五篇:【Keras速成】Keras图像分类从模型自定义到测试

第六篇:【mxnet速成】mxnet图像分类从模型自定义到测试

第七篇:【cntk速成】cntk图像分类从模型自定义到测试

第八篇:【chainer速成】chainer图像分类从模型自定义到测试

第九篇:【DL4J速成】Deeplearning4j图像分类从模型自定义到测试

第十篇:【MatConvnet速成】MatConvnet图像分类从模型自定义到测试

第十一篇:【Lasagne速成】Lasagne/Theano图像分类从模型自定义到测试

第十二篇:【darknet速成】Darknet图像分类从模型自定义到测试

    原文作者:龙鹏-言有三
    原文地址: https://zhuanlan.zhihu.com/p/39456589
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞