以Kaggle中猫狗大战为例,总结图像分类问题的一般步骤

前言

一直在学习计算机视觉方面的基础知识,前几天接触到了第一个实际的使用CNN来进行图像分类的Kaggle上的猫狗分类的问题,虽然最后效果不太理想,但是个人觉得对于图像分类问题,这个项目的解决过程和方法具有很强的借鉴意义,这里进行一次详细的总结。

项目条件

项目要达到的目的

通过对训练集中的25000张猫狗的照片进行有监督的训练,来预测测试集中12500张图片是小狗的概率。

数据总览

在这里插入图片描述

在这里插入图片描述
训练集中,有25000张照片,可以看到每一种中要么有一只狗,要么有一只猫,这一点在测试集上也是一样。
通过查看,每张图片的像素值也各有差异。
在这里插入图片描述
在测试集中,情况和训练集差不多,只不过就是少了图片的类别标签。

处理数据

神经网络不可能对图片直接处理,当然要先把图片转化为某种数字形式,代码如下,注释我写的很详细:

import tensorflow as tf
import numpy as np
import os
#引入必要的python 模块

def get_all_files(file_path, is_random=True):
    """
    获取图片路径及其标签
    :param file_path: a sting, 图片所在目录
    :param is_random: True or False, 是否乱序
    :return:
    """
    image_list = []  #图片列表
    label_list = []  #标签列表

    cat_count = 0  
    dog_count = 0  #对猫狗的数量进行计数
    for item in os.listdir(file_path):  #item 表示file_path文件夹里的所有文件,用来遍历
        item_path = file_path + '/' + item  #item_path用来表示每一张照片的相对路径
        item_label = item.split('.')[0]  # 文件名形如  cat.0.jpg,只需要取第一个,这里要么是'cat',要么是'dog'
        
        if os.path.isfile(item_path):
            image_list.append(item_path) #如果item_path是一个文件的话,就将其加入到列表“image_list”中
        else:
            raise ValueError('文件夹中有非文件项.')  #python raise语句抛出一个指定异常

        if item_label == 'cat':  # 猫标记为'0'
            label_list.append(0)
            cat_count += 1
        else:  # 狗标记为'1'
            label_list.append(1)
            dog_count += 1
    print('数据集中有%d只猫,%d只狗.' % (cat_count, dog_count))

    image_list = np.asarray(image_list)  #python asarray函数可以把列表转化为ndarray类型的数据
    label_list = np.asarray(label_list)
    # 乱序文件
    if is_random:
        rnd_index = np.arange(len(image_list))  #生成一个从1到25000的ndarray的数组
        np.random.shuffle(rnd_index)            #把rnd_index随机洗牌
        image_list = image_list[rnd_index]
        label_list = label_list[rnd_index]

    return image_list, label_list  #返回图片的路径列表和标签列表


def get_test_files(file_path):
    '''
    因为test文件没有标签,而且为了后面生成按照1-12500 顺序排序的文件,所以这里另写一个针对test文件的函数
    '''

    image_list = []  #图片列表
    label_list = []  # 标签列表,虽然没有标签,但是为了在处理时保持统一,还是有加上

    for i in range(12500): #这里i为(0 -- 12499 )
        num = i+1   #这里num为(1 -- 12500 )
        item_path = file_path+'/'+str(num)+'.jpg'  
        # 这里item_path通过字符串拼接的方式,得到每一个图片文件的相对路径
        if os.path.isfile(item_path):
           image_list.append(item_path)
           label_list.append(0)
        else:
           raise ValueError('文件夹中有非文件项.') #python raise语句刨出一个指定异常
    
    image_list = np.asarray(image_list)  #python asarray函数可以把列表转化为ndarray类型的数据
    label_list = np.asarray(label_list)
    
    return image_list,label_list #返回图片的路径列表和标签列表



def get_batch(train_list, image_size, batch_size, capacity, is_random=True):
    """
    获取训练批次
    :param train_list: 2-D list, [image_list, label_list]
    :param image_size: a int, 训练图像大小
    :param batch_size: a int, 每个批次包含的样本数量
    :param capacity: a int, 队列容量
    :param is_random: True or False, 是否乱序
    :return:
    """

    intput_queue = tf.train.slice_input_producer(train_list, shuffle=False)  
    #这个函数是用来生成文件名队列,


    # 从路径中读取图片
    image_train = tf.read_file(intput_queue[0])  #要使用read_file函数来读取文件名队列中的文件
    image_train = tf.image.decode_jpeg(image_train, channels=3)  # 这里是jpg格式,返回值格式为:uint8
    #图像在存储时并不是直接记录这些矩阵中的数字,而是记录经过压缩编码之后的结果。所以要将一张图象还原成一个三维矩阵。需要解码的过程,这里的函数实现的就是这个功能

    image_train = tf.image.resize_images(image_train, [image_size, image_size])  #这个函数的第一个输入值是原始图像,第二个是要转换成的图像大小

    image_train = tf.cast(image_train, tf.float32) / 255.  # 转换数据类型并归一化
    #tf.cast()函数的作用是执行 tensorflow 中张量数据类型转换,比如读入的图片如果是int8类型的,一般在要在训练前把图像的数据格式转换为float32

    # 图片标签
    label_train = intput_queue[1]

    # 获取批次
    if is_random:
        image_train_batch, label_train_batch = tf.train.shuffle_batch([image_train, label_train],
                                                                      batch_size=batch_size,
                                                                      capacity=capacity,
                                                                      min_after_dequeue=100,
                                                                      num_threads=2)
    else:
        image_train_batch, label_train_batch = tf.train.batch([image_train, label_train],
                                                              batch_size=1,
                                                              capacity=capacity,
                                                              num_threads=1)
    return image_train_batch, label_train_batch
    

if __name__ == '__main__':
    import matplotlib.pyplot as plt

    # 测试图片读取
    image_dir = 'test'
    train_list = get_test_files(image_dir)
    image_train_batch, label_train_batch = get_batch(train_list, 256, 1, 200, False)
    # image_train_batch是shape=(1, 256, 256, 3)的张量,label_train_batch是shape=(1,)的张量
    sess = tf.Session()

    coord = tf.train.Coordinator() #创建一个线程协调器,用来管理之后在Session中启动的所有线程
    threads = tf.train.start_queue_runners(sess=sess, coord=coord)  #启动入队线程,由多个或单个线程,按照设定规则,把文件读入Filename Queue中

    try:
        for step in range(10):
            if coord.should_stop():
                break

            image_batch, label_batch = sess.run([image_train_batch, label_train_batch])
            if label_batch[0] == 0:
                label = 'Cat'
            else:
                label = 'Dog'
            plt.imshow(image_batch[0]), plt.title(label)
            plt.show()

    except tf.errors.OutOfRangeError:
        print('Done.')
    finally:
        coord.request_stop()

    coord.join(threads=threads)
    sess.close()
    

卷积模型

import tensorflow as tf
import tensorflow.contrib.layers as layers 
#在tf.contrib.layers内部,有许多产生layer操作及其相关权重和偏差变量的函数。这些大部分都是用来构建不同深度学习架构的


def inference(images, n_classes): #images 是256*256*3,n_classes代表类别的数量
    # conv1, shape = [kernel_size, kernel_size, channels, kernel_numbers]
    with tf.variable_scope("conv1",reuse=tf.AUTO_REUSE) as scope:  #变量作用域
        weights = tf.get_variable("weights",

                                  shape=[3, 3, 3, 16],
                                  dtype=tf.float32,
                                  initializer=tf.truncated_normal_initializer(stddev=0.1, dtype=tf.float32))
        biases = tf.get_variable("biases",
                                 shape=[16],
                                 dtype=tf.float32,
                                 initializer=tf.constant_initializer(0.1))
        conv = tf.nn.conv2d(images, weights, strides=[1, 1, 1, 1], padding="SAME")
        pre_activation = tf.nn.bias_add(conv, biases)
        conv1 = tf.nn.relu(pre_activation, name="conv1")

    # pool1 && norm1
    with tf.variable_scope("pooling1_lrn",reuse=tf.AUTO_REUSE) as scope:
        pool1 = tf.nn.max_pool(conv1, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],
                               padding="SAME", name="pooling1")
        norm1 = tf.nn.lrn(pool1, depth_radius=4, bias=1.0, alpha=0.001/9.0,
                          beta=0.75, name='norm1')  #LRN对局部神经元的活动创建竞争机制,使得响应比较大的值相对更大,比较小的值就会更小。提高了模型的泛化能力。

    # conv2
    with tf.variable_scope("conv2",reuse=tf.AUTO_REUSE) as scope:
        weights = tf.get_variable("weights",
                                  shape=[3, 3, 16, 16],
                                  dtype=tf.float32,
                                  initializer=tf.truncated_normal_initializer(stddev=0.1, dtype=tf.float32))
        biases = tf.get_variable("biases",
                                 shape=[16],
                                 dtype=tf.float32,
                                 initializer=tf.constant_initializer(0.1))
        conv = tf.nn.conv2d(norm1, weights, strides=[1, 1, 1, 1], padding="SAME")
        pre_activation = tf.nn.bias_add(conv, biases)
        conv2 = tf.nn.relu(pre_activation, name="conv2")

    # pool2 && norm2
    with tf.variable_scope("pooling2_lrn",reuse=tf.AUTO_REUSE) as scope:
        pool2 = tf.nn.max_pool(conv2, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],
                               padding="SAME", name="pooling2")
        norm2 = tf.nn.lrn(pool2, depth_radius=4, bias=1.0, alpha=0.001/9.0,
                          beta=0.75, name='norm2')

    # full-connect1
    with tf.variable_scope("fc1",reuse=tf.AUTO_REUSE) as scope:
        reshape = layers.flatten(norm2)
        #对于输入的P,将每一个example展开为1-D的Tensor, 但是依然保留batch-size。它返回一个[batch_size, k]的Tensor。
        #通常在CNN的最后一步连接到Fully Connected 的网络之前会将其展开,例如CNN的conv层输出的tensor的shape为[batch_size, height, width, channel],
        # 刚展开会就是[batch_size, height * width * channel]。

        dim = reshape.get_shape()[1].value  #reshape[0]是batch
        weights = tf.get_variable("weights",
                                  shape=[dim, 128],
                                  dtype=tf.float32,
                                  initializer=tf.truncated_normal_initializer(stddev=0.005, dtype=tf.float32))
        biases = tf.get_variable("biases",
                                 shape=[128],
                                 dtype=tf.float32,
                                 initializer=tf.constant_initializer(0.1))
        fc1 = tf.nn.relu(tf.matmul(reshape, weights) + biases, name="fc1")
        #

    # full_connect2
    with tf.variable_scope("fc2",reuse=tf.AUTO_REUSE) as scope:
        weights = tf.get_variable("weights",
                                  shape=[128, 128],
                                  dtype=tf.float32,
                                  initializer=tf.truncated_normal_initializer(stddev=0.005, dtype=tf.float32))
        biases = tf.get_variable("biases",
                                 shape=[128],
                                 dtype=tf.float32,
                                 initializer=tf.constant_initializer(0.1))
        fc2 = tf.nn.relu(tf.matmul(fc1, weights) + biases, name="fc2")

    # softmax
    with tf.variable_scope("softmax_linear",reuse=tf.AUTO_REUSE) as scope:
        weights = tf.get_variable("weights",
                                  shape=[128, n_classes],
                                  dtype=tf.float32,
                                  initializer=tf.truncated_normal_initializer(stddev=0.005, dtype=tf.float32))
        biases = tf.get_variable("biases",
                                 shape=[n_classes],
                                 dtype=tf.float32,
                                 initializer=tf.constant_initializer(0.1))
        softmax_linear = tf.add(tf.matmul(fc2, weights), biases, name="softmax_linear")
        #softmax_linear = tf.nn.softmax(softmax_linear)
        #softmax_linear是[8,2]的矩阵

    return softmax_linear


def losses(logits, labels):  #计算损失值
    with tf.variable_scope('loss'):
        cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=logits,
                                                                       labels=labels)
        #这里的交叉熵函数的输入值不是经过softmax函数处理过的,是原始数据
        loss = tf.reduce_mean(cross_entropy)
    return loss


def evaluation(logits, labels):
    with tf.variable_scope("accuracy"):
        correct = tf.nn.in_top_k(logits, labels, 1)
        #这里解释一下,tf.nn.in_top_k方法的作用,例如logits=[[0.1,0.9],[0.6,0.4]],labels=[1,1],对于logits来说,其每个元素的最大值的索引为[1,0],所以最后的结果
        #为[true,false],如果0代表dog,1代表cat,预测的结果也就是[cat,dog],实际为[cat,cat],那么返回值也就是[true,falase]
        correct = tf.cast(correct, tf.float16)
        # cast方法可以把一组bool类型的张量转化为形如[0,1,1,1]类型的张量
        accuracy = tf.reduce_mean(correct)
        #reduce_mean的作用是用来求平均值,这里省掉了一个参数axis,当axis=1时是按行进行计算,是0的时候就按列进行计算
    return accuracy

训练和评估

import time
from input_data import * 
from model import *
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np


# 训练模型
def training():
    N_CLASSES = 2  #分类的类别数量,这里是狗和猫
    IMG_SIZE = 208   #需要把图片转化为的大小
    BATCH_SIZE = 8   #每批处理的数量
    CAPACITY = 200   # 队列容量
    MAX_STEP = 10000  #循环的步数
    LEARNING_RATE = 1e-4 

    # 测试图片读取
    image_dir = 'train'  #训练集所在的目录
    logs_dir = 'logs_1'     # 检查点保存路径,这里是用来保存训练完成以后的各个参数

    sess = tf.Session()

    train_list = get_all_files(image_dir, True)   #返回图片的路径列表和标签列表
    image_train_batch, label_train_batch = get_batch(train_list, IMG_SIZE, BATCH_SIZE, CAPACITY, True)
    # image_train_batch是shape=(8, 256, 256, 3)的张量,label_train_batch是shape=(8,)的张量
    train_logits = inference(image_train_batch, N_CLASSES)  #返回一个[8,2]的矩阵

    train_loss = losses(train_logits, label_train_batch)  #这里train_loss是一个标量
    train_acc = evaluation(train_logits, label_train_batch)  #准确率

    train_op = tf.train.AdamOptimizer(LEARNING_RATE).minimize(train_loss)  #使用adam优化来训练模型,减少loss

    var_list = tf.trainable_variables()  #表示可以训练的参数的数量
    paras_count = tf.reduce_sum([tf.reduce_prod(v.shape) for v in var_list])
    #reduce_sum 表示对张量进行求和
    print('参数数目:%d' % sess.run(paras_count), end='\n\n') #计算参数的数量

    saver = tf.train.Saver()  #实例化一个Saver()类,用来进行参数的保存和读取

    sess.run(tf.global_variables_initializer())

    coord = tf.train.Coordinator()  #创建一个线程协调器
    threads = tf.train.start_queue_runners(sess=sess, coord=coord) #把文件名队列推进真正的内存队列

    s_t = time.time()
    try:
        for step in range(MAX_STEP):
            if coord.should_stop():
                break

            _, loss, acc = sess.run([train_op, train_loss, train_acc])
            #这里 train_op 的返回值没有意义,所以并不需要,没有意义的值以’_‘来代替。
            tl=sess.run(train_logits)


            if step % 100 == 0:  # 实时记录训练过程并显示
                runtime = time.time() - s_t
                print('train_logits:' + str(tl))
                print('Step: %6d, loss: %.8f, accuracy: %.2f%%, time:%.2fs, time left: %.2fhours'
                      % (step, loss, acc * 100, runtime, (MAX_STEP - step) * runtime / 360000))
                s_t = time.time()

            if step % 1000 == 0 or step == MAX_STEP - 1:  # 保存检查点
                checkpoint_path = os.path.join(logs_dir, 'model.ckpt') #将多个路径进行组合
                saver.save(sess, checkpoint_path, global_step=step)
                # global_step 把训练的step作为文件名的后追加在后面。

    except tf.errors.OutOfRangeError:
        print('Done.')
    finally:
        coord.request_stop()

    coord.join(threads=threads)
    sess.close()


# 测试检查点
def eval():
    N_CLASSES = 2
    IMG_SIZE = 208
    BATCH_SIZE = 1
    CAPACITY = 200
    MAX_STEP = 12500

    test_dir = 'test'
    logs_dir = 'logs_1'     # 检查点目录

    sess = tf.Session()

    test_list = get_test_files(test_dir)
    image_test_batch, label_test_batch = get_batch(test_list, IMG_SIZE, BATCH_SIZE, CAPACITY, False)
    test_logits = inference(image_test_batch, N_CLASSES)
    test_logits = tf.nn.softmax(test_logits)  # 用softmax转化为百分比数值

    # 载入检查点
    saver = tf.train.Saver()
    print('\n载入检查点...')
    ckpt = tf.train.get_checkpoint_state(logs_dir)
    if ckpt and ckpt.model_checkpoint_path:
        # print(ckpt.model_checkpoint_path)

        global_step = ckpt.model_checkpoint_path.split('/')[-1].split('-')[-1]
        # 这里的global_step是最大的那个数。

        saver.restore(sess, ckpt.model_checkpoint_path)

        print('载入成功,global_step = %s\n' % global_step)
    else:
        print('没有找到检查点')
    coord = tf.train.Coordinator()
    threads = tf.train.start_queue_runners(sess=sess, coord=coord)
    dogOrCat = []  
    try:
        for step in range(MAX_STEP):  #这里一次只能处理10张,一次循环处理一张
            if coord.should_stop():
                break

            prediction = sess.run(test_logits)
            
            dogOrCat.append(prediction[0][1])

            if (step % 100 == 0):
                print('%d images have been dealed with!'%step)

    except tf.errors.OutOfRangeError:
        print('Done.')
    finally:
        coord.request_stop()



    coord.join(threads=threads)
    sess.close()
    return dogOrCat


if __name__ == '__main__':
    training()
    result = eval()
    name = ['label']
    test = pd.DataFrame(columns=name, data=result)
    test.to_csv('testcsv.csv', encoding='utf-8')


  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值