美文网首页
【Tensonrflow速成】Tensorflow图像分类从模型

【Tensonrflow速成】Tensorflow图像分类从模型

作者: 有三AI | 来源:发表于2019-03-28 13:48 被阅读0次

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

    作者 | 言有三(微信号Longlongtogo)

    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_paths和 self.labels存储输入 txt 中的文件列表和对应的 label,这一点和 Caffe 很像了。

    (3)然后,就是分别将img_paths和 labels 转换为 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.conv2d,tf.layers.batch_normalization和 tf.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_initializer和bias_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 的曲线图如下:

    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 为例的代码来说更实用。我们自己准备了数据集,自己设计了网络并进行了结果可视化,学习了如何使用已经训练好的模型做预测。

    本系列完整文章:

    第一篇:【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图像分类从模型自定义到测试

    相关文章

      网友评论

          本文标题:【Tensonrflow速成】Tensorflow图像分类从模型

          本文链接:https://www.haomeiwen.com/subject/zbsqbqtx.html