[catsVSdogs]猫狗大战代码注释讲解_1

cats_VS_dogs的分类问题

目录

cats_VS_dogs的分类问题

一:input.py的输入训练样本集的部分

训练文件进行操作

二:model.py模型部分,负责实现我们的神经网络


很经典的案例,现在讲讲他,如有错误,多多评论指教

他的train_set和test_set均可在这个网址下载到https://www.kaggle.com/c/dogs-vs-cats-redux-kernels-edition/data

不想看下面我的剖析的,也可以直接看这里的源代码https://github.com/ZZZstudent/catsVSdogs1

将对应的数据图片集修改好,可以直接运行的,当然,能自己一个个敲出来,慢慢理解就最好不过了,现在把我理解的写出来,留着自己看,也希望各位大佬多多指点

 

首先,我们需要对整个project有一个整体的把握;

(1)数据集[data]

(2)存放代码的文件夹[other](文件夹西面的——pycache_是自己生成的,不管,直接建立input.py等三个文件就行)

(3)这个网络还是挺复杂的,需要一个tensorboard来看看我们最后生成的是什么样子[logs]

所以,就建了一个如下图所示的项目列表

[catsVSdogs]猫狗大战代码注释讲解_1_第1张图片

一:input.py的输入训练样本集的部分

输入训练样本集所需要的一些头文件

 

import tensorflow as tf
import numpy as np
import os#目录有关系

其中一个要强调的是inport os就是对目录内的文件进行操作的,因为我们要从文件夹中读取图片,所以,需要这个

现在我们可以看一下,我们的猫狗大战的图片数据集究竟长的什么样子

[catsVSdogs]猫狗大战代码注释讲解_1_第2张图片

[catsVSdogs]猫狗大战代码注释讲解_1_第3张图片

 

#%%
img_width = 208
img_height = 208

由上面的猫狗的图,不难发现,采集回来的图片大小不一,有的长了,有了高了,大小不一样,这样对后面的批batch处理就比较的麻烦,所以,我们这里就对所有图像统一成大小[208,208],上面的代码部分就是干这个的。
那下面,就开始对待训练文件进行操作吧

训练文件进行操作

#%%
train_dir = 'D:/Anaconda3/projects/catsVSdogs/data/train/'
def get_files(file_dir):#返回存放数据的路径以及label标签
    '''
    args:
        file_dir: file directory
    returns:
        list of images and labels
    '''
    cats =  []
    label_cats = []
    dogs = []
    label_dogs = []
    for file in os.listdir(file_dir):#返回这个路径下所有文件的名字
        name = file.split(sep='.')
        if name[0]=='cat':
            cats.append(file_dir +file)
            label_cats.append(0)
        else:
            dogs.append(file_dir + file)
            label_dogs.append(1)
    print('there are %d cats\nthere are %d dogs' %(len(cats),len(dogs)))
            
    image_list = np.hstack((cats, dogs))#将猫狗图片堆积起来
    label_list = np.hstack((label_cats,label_dogs))#label也堆积起来
    
    temp = np.array([image_list,label_list])
    temp = temp.transpose()
    np.random.shuffle(temp)#打乱数据
    
    image_list = list(temp[:,0])
    label_list = list(temp[:,1])
    label_list = [int(i) for i in label_list]
 
    return image_list,label_list

上面这一串字的代码呢,其实就是讲train_set的训练样本传进来,然后将他们通过文件名来分成cat和dog另类,分类的标识是‘.’,并给他们大上0或者1的标签,猫的label=0,狗的label=1.将数据集打乱,生成image_list,和label_list。最后返回这两个部分

 

#%%
def get_batch(image,label,image_W,image_H,batch_size,capacity):
    '''
    Args:
        image: list type get_files函数返回的image_list
        label: list type  get_files函数返回的label_list
        image_W: image width 图片大小不一,需要裁减的宽高
        image_H: image width
        batch_size: batch size,每一批的图片数量
        capacity: the maximum elements in queue队列中容纳的图片数
    Returns:
        iamge_batch:4D tensor [batch_size,width,height,3],dtype=tf.float32;#图像是rgb所以通道是3
        label_batch:1D tensor [batch_size],dtype=tf.int32
    
    '''
    global label_batch
    #在python的函数中和全局同名的变量,如果你有修改变量的值就会变成局部变量,
    #在修改之前对该变量的引用自然就会出现没定义这样的错误了,如果确定要引用全局变量,并且要对它修改,必须加上global关键字。
    image = tf.cast(image,tf.string) #转换数据类型
    label = tf.cast(label,tf.int32)
    
    #make  an input queue输入队列
    input_queue = tf.train.slice_input_producer([image,label])
    
    label = input_queue[1]
    image_contents = tf.read_file(input_queue[0])
    image = tf.image.decode_jpeg(image_contents,channels=3)#解码
    ###############################
    #data argymentation should go to here可以做一些数据特征加强
    #################################
    image = tf.image.resize_image_with_crop_or_pad(image,image_W,image_H)#裁减图片的长宽
    image = tf.image.per_image_standardization(image)#神经网络对图片要求很高,所以标准化图片减去均值除以方差
    image_batch,label_batch = tf.train.batch([image,label],#list
                                           batch_size = batch_size,
                                           num_threads = 64,
                                           capacity = capacity)#队列中最多能容纳的个数
 #image_batch,label_batch = tf.train.shuffle_batch([image,label],
 #                                                 batch_size = BATCH_SIZE,
 #                                                 num_threads=64,
 #                                                 capacity=CAPACITY,
 #                                                 min_after_dequeue=CAPACITY-1)  
    label_batch = tf.reshape(label_batch,[batch_size])
    
    return image_batch,label_batch

其实对图像训练样本进行训练的时候,并不是一个一个的输入,而是一批一批的输入,这里面就涉及到怎么组合成一个batch。

这时候该思考,为什么要把训练数据集设置成一个个batch呢?

答案是:如果损失函数是非凸的话,整个训练样本尽管算的动,可能会卡在局部最优解上;分批训练表示全样本的抽样实现,也就是相当于人为的引入了修正梯度上的采样噪声,使得“一路不同,找别路”的方法,更有可能搜索到全局最优解。

 

由get_batch(image,label,image_W,image_H,batch_size,capacity)函数的要输入的参数,并结合下面的注释,自己理解,下面着重说道cast(x, dtype, name=None) 这个转换数据类型的函数。例如下面代码(举个栗子)

a = tf.Variable([1,0,0,1,1])
b = tf.cast(a,dtype=tf.bool)
sess = tf.Session()
sess.run(tf.initialize_all_variables())
print(sess.run(b))
#[ True False False  True  True]

就是将a中的[1,0,0,1,1]的variable变量类型转换成了bool类型,即[ True False False True True]
针对这里的image = tf.cast(image,tf.string),就是将image的输入图像,用string的形式,我理解的应该就是猫=cat的string形式,狗=dog的数据形式,应为前面get_files已经将图像集分成了cat和dog两部分;label = tf.cast(label,tf.int32)就是将标签0或1的int形式了。毕竟0和1也可以转化为bool行的true和false

 

下面,看一下组合成队列的一个函数吧,就能对上面的图像更改大小有更深的了解了,先看官方文档对tf.train.slice_input_producer()的注释

[catsVSdogs]猫狗大战代码注释讲解_1_第4张图片

其中看最后一条,tesor_list中的tensor必须有相同的大小,这也就是上面图像大小一致的原因所在。最后,输出image_batch和label_batch

这样训练样本图像和标签批次都做好了,具体一批次多少,就根据后面的定义传进来的值来具体考虑了,这会在train.py中考虑到

现在有了文件样本输入,有了数据批次,这个时候就可以先不管后面还要做什么,可以先看看我们这个input的部分有没有做对,能不能将他们分批次来处理。

其实在第一步get_files时候,就可以先运行,检测下文件路径是否正确,是否能够分类好猫狗数据集,然后输出多少只猫和多少只狗。那就在下面的代码中一并看看输出的效果吧。

 

#%%TEST those two function

import matplotlib.pyplot as plt

BATCH_SIZE = 10
CAPACITY = 256 #队列中最多容纳图片的个数
IMG_W = 208
IMG_H = 208
train_dir = 'D:/Anaconda3/projects/catsVSdogs/data/train/'
image_list,label_list = get_files(train_dir)
image_batch,label_batch = get_batch(image_list,label_list,IMG_W,IMG_H,BATCH_SIZE,CAPACITY)

with tf.Session() as sess:
    i = 0
    coord = tf.train.Coordinator()
    threads = tf.train.start_queue_runners(coord=coord)
    
    try:
        while not coord.should_stop() and i<1:
            
            img,label = sess.run([image_batch,label_batch])
            
            #just test one batch
            for j in np.arange(BATCH_SIZE):
                print('label: %d' %label[j])
                plt.imshow(img[j,:,:,:])#4D数据后面全用冒号
                plt.show()
            i+=1
            
    except tf.errors.OutOfRangeError:
        print('done!')
    finally:
        coord.request_stop()
    coord.join(threads)

看下输出,这里面batch_size=10.有点多,就截图一部分给看看,有点奇怪的是他像素有点奇怪是吧。在image = tf.image.resize_image_with_crop_or_pad(image,image_W,image_H)#裁减图片的长宽。这种方法是从图像中心向四周裁剪,如果图片超过规定尺寸,最后只会剩中间区域的一部分,可能一只狗只剩下躯干,头都不见了,用这样的图片训练结果肯定会受到影响。不防尝试下image = tf.image.resize_images(image, [image_H, image_W], method=tf.image.ResizeMethod.NEAREST_NEIGHBOR)对图像进行缩放,而不是裁剪。

 

缩放之后视频中还进行了per_image_standardization (标准化)步骤,但加了这步之后,得到的图片是花的,虽然各个通道单独提出来是正常的,三通道一起就不对了,删了标准化这步结果正常。

[catsVSdogs]猫狗大战代码注释讲解_1_第5张图片[catsVSdogs]猫狗大战代码注释讲解_1_第6张图片
这样12500个cats,12500个dogs。

#################################

input.py分割线end

##################################

二:model.py模型部分,负责实现我们的神经网络

现在已经将训练样本输入了进来,分成了batch批次,但是并没有对原始的样本数据进行什么样子的处理和运算,原始的图像样本只是换了一种形式在存在的而已,所以,我们下面再model.py部分就是对我们的处理模型进行一个搭建,那现在开始。

传统的,需要库文件的载入

 

import tensorflow as tf

上面也在用到,这里就不详述了,看下面,才是model的重点所在

 

直接看看代码吧

 

#%%
def inference(images,batch_size,n_classes):
    '''build the model
    Args:
        images:image batch产生的批次,4D tensor,tf.float32,[batch_size,width,height,channels]#n_classes=2,2分类问题
    returns:
        output tensor with the computed logits,float,[batch_size,n_classes]#最后一层全连接输出
    '''
#conv1, shape = [kernel size,kernel size,channels,kernal numbers]
    with tf.variable_scope('conv1') 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))#均值默认为0
        biases = tf.get_variable('biases',
                                 shape = [16],#shape要与weights里面kernal numbers保持一致
                                 dtype=tf.float32,
                                 initializer=tf.constant_initializer(0.1))#初始值0.1,很重要
        conv = tf.nn.conv2d(images,weights,strides=[1,1,1,1],padding='SAME')#2D卷积计算
        pre_activation = tf.nn.bias_add(conv,biases)
        conv1 = tf.nn.relu(pre_activation,name=scope.name)#激活函数
        
#pool1 and norm1
    with tf.variable_scope('pooling_lrn') as scope:
        pool1 = tf.nn.max_pool(conv1,ksize=[1,3,3,1],strides=[1,2,2,1],#最常见的ksize和stride,定义的是滑动的距离
                               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')#参照cfr10
        
#conv2
    with tf.variable_scope('conv2') 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 and norm2
    with tf.variable_scope('pooling2_lrn') as scope:
         norm2 = tf.nn.lrn(conv2,depth_radius=4,bias=1.0,alpha=0.001/9.0,
                          beta=0.75,name='norm2')
         pool2 = tf.nn.max_pool(norm2,ksize=[1,3,3,1],strides=[1,1,1,1],
                               padding = 'SAME',name='pooling2')
    
    
#local3全连接层
    with tf.variable_scope('local3') as scope:
        reshape = tf.reshape(pool2,shape=[batch_size,-1])
        dim = reshape.get_shape()[1].value       
        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],#神经元个数128
                                 dtype=tf.float32,
                                 initializer=tf.constant_initializer(0.1))
        local3 = tf.nn.relu(tf.matmul(reshape, weights) + biases,name=scope.name)#矩阵乘法
#local4全连接层
    with tf.variable_scope('local4') 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))
        local4 = tf.nn.relu(tf.matmul(local3,weights)+biases,name='local4')  
#softmax
    with tf.variable_scope('softmax_linear') as scope:
        weights = tf.get_variable('softmax_linear',
                              shape = [128,n_classes],#n_classes=2
                              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(local4,weights),biases,name='softmax_linear')#不要激活函数
        return softmax_linear

从上面的标注,就可以看出,这是一个传统的卷积神经网络(CNN),其中有conv1--maxpool--norm1--conv2--norm2---maxpool--local3全连接层--local4全连接层--softmax分类。

 

[catsVSdogs]猫狗大战代码注释讲解_1_第7张图片

下面,就揪出来部分代码,做一个参数的详述

 

#conv1, 
    with tf.variable_scope('conv1') as scope:
        weights = tf.get_variable('weights',
                                  shape = [3,3,3,16],      #shape = [kernel size,kernel size,channels,kernal numbers]
                                  dtype = tf.float32,
                                  initializer=tf.truncated_normal_initializer(stddev=0.1,dtype=tf.float32))#均值默认为0
        biases = tf.get_variable('biases',
                                 shape = [16],#shape要与weights里面kernal numbers保持一致
                                 dtype=tf.float32,
                                 initializer=tf.constant_initializer(0.1))#初始值0.1,很重要
        conv = tf.nn.conv2d(images,weights,strides=[1,1,1,1],padding='SAME')#2D卷积计算
        pre_activation = tf.nn.bias_add(conv,biases)
        conv1 = tf.nn.relu(pre_activation,name=scope.name)#激活函数shape = [kernel size,kernel size,channels,kernal numbers]
                                  dtype = tf.float32,
                                  initializer=tf.truncated_normal_initializer(stddev=0.1,dtype=tf.float32))#均值默认为0
        biases = tf.get_variable('biases',
                                 shape = [16],#shape要与weights里面kernal numbers保持一致
                                 dtype=tf.float32,
                                 initializer=tf.constant_initializer(0.1))#初始值0.1,很重要
        conv = tf.nn.conv2d(images,weights,strides=[1,1,1,1],padding='SAME')#2D卷积计算
        pre_activation = tf.nn.bias_add(conv,biases)
        conv1 = tf.nn.relu(pre_activation,name=scope.name)#激活函数
with tf.variable_scope('conv1') as scope:这个呢,是为了生存一个结构图链表,作为名字用的,就不详述

weights里面的shape=[3,3,3,16],对应着[kernel size,kernel size,channels,kernal numbers],即卷积核的size为3*3,channel=3,卷积核数量为16个
biases即它的偏置,为了后面的计算wx+b,所以biases的shape=[16],初始化为0.1

 

在卷积conv中,conv2d里面的是步幅strides=[1,1,1,1], # stride [1, x_movement, y_movement, 1],padding分为valid(小)和same(补0不变)

下面就是两个运算,将卷积处理后的conv与biases相加,然后再relu激活函数

 

#pool1 and norm1
    with tf.variable_scope('pooling_lrn') as scope:
        pool1 = tf.nn.max_pool(conv1,ksize=[1,3,3,1],strides=[1,2,2,1],#最常见的ksize和stride,定义的是滑动的距离
                               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')#参照cfr10


tf.nn.max_pool(value, ksize, strides, padding, name=None)
参数是四个,和卷积很类似:
第一个参数value:需要池化的输入,一般池化层接在卷积层后面,所以输入通常是feature map,依然是[batch, height, width, channels]这样的shape
第二个参数ksize:池化窗口的大小,取一个四维向量,一般是[1, height, width, 1],因为我们不想在batch和channels上做池化,所以这两个维度设为了1
第三个参数strides:和卷积类似,窗口在每一个维度上滑动的步长,一般也是[1, stride,stride, 1]
第四个参数padding:和卷积类似,可以取'VALID' 或者'SAME'
返回一个Tensor,类型不变,shape仍然是[batch, height, width, channels]这种形式

norm层---局部响应一体化点击打开链接具体可以点击这个看看,就放一个截图在这里了

 

[catsVSdogs]猫狗大战代码注释讲解_1_第8张图片
 

后面就是全连接的部分,自己看看代码理解

还有定义损失函数

 

#%%
def losses(logits,labels):'''compute loss from logits and labels
    Args:
         logits:logits tensor,float,[batch_size,n_classes]
         labels:label tensor,tf.int32,[batch_size]
         
    returns:
        loss tensor of float type
     '''
 with tf.variable_scope('loss') as scope: cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits\ (logits=logits,labels=labels,name='xentropy_per_example') loss = tf.reduce_mean(cross_entropy,name='loss') tf.summary.scalar(scope.name+'/loss',loss) return loss
定义训练函数
#%%训练优化
def training(loss,learning_rate):
    '''Training ops, the op returned by this function is must be passed to
        'sess.run()' call to cause the model to train.
        
    Args:
        loss:loss tensor,from losses()
    
    Returns:train_op:The op for training
    '''
    with tf.name_scope('optimizer'):
        optimizer = tf.train.AdamOptimizer(learning_rate = learning_rate)
        global_step = tf.Variable(0, name='global_step',trainable=False)
        train_op = optimizer.minimize(loss,global_step=global_step)
    return train_op
#%%
def evaluation(logits,labels):
    """evaluate the quality of the logits at predicting the label.
    Args:
        logits:logits tensor, float - [batch_size,NUM_CLASSES].
        labels:labels tensor,int32 - [batch_size],with value in the range[0,NUM_CLASSES]
    returns:
        a scalar int32 tensor with the number of examples (out of batch_size)
        that were predicted correctly.
    
    """
    with tf.variable_scope('accuracy') as scope:
        correct = tf.nn.in_top_k(logits,labels,1)
        correct = tf.cast(correct,tf.float16)
        accuracy = tf.reduce_mean(correct)
        tf.summary.scalar(scope.name+'/accuracy',accuracy)
    return accuracy

 

 

好了,这两个部分结束了,下面就是正式启动训练的一部分,先看我的电脑训练到13000步的准确率截图(是应该乘100的,忘记了),已经到94%了。

[catsVSdogs]猫狗大战代码注释讲解_1_第9张图片
最后,感谢下优酷上一个大师的讲解课,可以去优酷上直接搜索,这里就不附带连接了。
下一节将最后的一部分整理下。

想get更多有趣知识?请加微信公众号“小白CV”,谢谢

[catsVSdogs]猫狗大战代码注释讲解_1_第10张图片

 

你可能感兴趣的:(Python,CNN,tensorflow)