TensorFlow(十一)AlexNet网络(猫狗大战)详解与代码实现

Kaggle是由联合创始人、首席执行官安东尼·高德布卢姆(Anthony Goldbloom)2010年在墨尔本创立的,主要为开发商和数据科学家提供举办机器学习竞赛、托管数据库、编写和分享代码的平台。该平台已经吸引了80万名数据科学家的关注,这些用户资源或许正是吸引谷歌的主要因素。

本代码实现的就是对于Kaggle著名比赛项目猫狗大战的实现,访问https://www.kaggle.com/c/dogs-vs-cats获取数据集和比赛的论坛等等。

                                

也可以从我的百度云获取

链接:https://pan.baidu.com/s/1cJdI9Nd6SCEwg2vMNwseIQ 
提取码:g1sf 

1.首先对文件进行预处理裁剪至227*227

import cv2
import os

dir = "D:\\dataset_kaggledogvscat\\train" #输入原文件文件夹路径

for root , dirs , files in os.walk(dir): #os.walk文件、目录遍历器
    for file in files:
        filepath = os.path.join(root , file) #连接两个或更多的路径名组件
        #使用try except 处理异常
        try:
            image = cv2.imread(filepath) #读取原文件
            dim = (227 , 227) #规定像素为227*227
            resized = cv2.resize(image , dim) #将数据裁剪为227*227
            path = 'D:\\dataset_kaggledogvscat\\data_pretreatment\\train\\' + file #''中为保存文件路径
            cv2.imwrite(path , resized) #将裁剪好的数据保存到规定的文件夹
        #如果出现无法裁剪的数据输出文件路径 并将数据删除
        except:
               print(filepath)
               os.remove(filepath)

输出后的文件夹图片 

                        

命令行未输出说明所有的图片都成功完成预处理 

同样的方法处理测试集

 

2.将图片数据转化为tfrecord

2.1 注释数据集

首先将猫狗把图片分别放在根目录的dog 和 cat 文件夹下

                              

将dog中的所有图片标注为 1 将cat文件夹下的图片标注为 0 

import os
import numpy as np

file_dir = "D:\\dataset_kaggledogvscat\\data_pretreatment\\train"

images = [] #每张图片的路径组成的列表
temp = [] #保存cat dog文件夹路径
for root , sub_folders , files in os.walk(file_dir):
        
    for name in files:
        images.append(os.path.join(root , name))
            
    for name in sub_folders:
        temp.append(os.path.join(root , name))
            
labels = [] #保存注释列表

#此时temp为根目录下所有文件夹的路径列表 一次取出一个文件夹 对文件夹里面的所有数据图片进行注释
for one_folder in temp:
    n_img = len(os.listdir(one_folder)) #得到图片总数
    letter = one_folder.split('\\')[-1]  #按照“\\”分割 取出最后一个也就是文件夹的名称
    
    #标注数据集 将cat标注为0 dog标注为1 
    if letter == 'cat':
                labels = np.append(labels , n_img*[0])
               
    else:
                labels = np.append(labels , n_img*[1])
          
temp = np.array([images , labels]) #重新创建数组temp 将images 和 labels 最为一对键值对写入
temp = temp.transpose() #将temp转置
np.random.shuffle(temp) #打乱数据集的顺序

image_list = list(temp[:, 0]) #取出数组中的第一维 也就是图片的路径列表
label_list = list(temp[:, 1]) #取出数组中的第二维 也就是图片的标签列表
label_list = [int(float(i)) for i in label_list]

变量如下:

2.2 制作数据集

 

import tensorflow as tf
import os
import numpy as np
import cv2

file_dir = "D:\\dataset_kaggledogvscat\\data_pretreatment\\train"
save_dir = "D:\\dataset_kaggledogvscat\\dataset"

images = []
temp = []

images = [] #每张图片的路径组成的列表
temp = [] #保存cat dog文件夹路径
for root , sub_folders , files in os.walk(file_dir):
        
    for name in files:
        images.append(os.path.join(root , name))
            
    for name in sub_folders:
        temp.append(os.path.join(root , name))
            
labels = [] #保存注释列表

#此时temp为根目录下所有文件夹的路径列表 一次取出一个文件夹 对文件夹里面的所有数据图片进行注释
for one_folder in temp:
    n_img = len(os.listdir(one_folder)) #得到图片总数
    letter = one_folder.split('\\')[-1]  #按照“\\”分割 取出最后一个也就是文件夹的名称

    #标注数据集 将cat标注为0 dog标注为1 
    if letter == 'cat':
                labels = np.append(labels , n_img*[0])
               
    else:
                labels = np.append(labels , n_img*[1])
          
temp = np.array([images , labels]) #重新创建数组temp 将images 和 labels 最为一对键值对写入
temp = temp.transpose() #将temp转置
np.random.shuffle(temp) #打乱数据集的顺序

images_list = list(temp[:, 0]) #取出数组中的第一维 也就是图片的路径列表
labels_list = list(temp[:, 1]) #取出数组中的第二维 也就是图片的标签列表
labels_list = [int(float(i)) for i in labels_list]
        
filename = os.path.join(save_dir , 'catvsdog_path_dataset.tfrecords')
n_samples = len(labels_list)
writer = tf.python_io.TFRecordWriter(filename)
print('\n开始制作数据集...')
for i in np.arange(0 , n_samples):
    #try:
        print("正在制作第 %d 张 \n" % i)
        image = cv2.imread(images_list[i])
        image_raw = image.tostring()
        example = tf.train.Example(features = tf.train.Features(feature = {
                'label' : tf.train.Feature(int64_list = tf.train.Int64List(value = labels_list)),
                'image_raw' : tf.train.Feature(bytes_list = tf.train.BytesList(value = [image_raw]))}))
        writer.write(example.SerializeToString())
            
    #except:
        #print('无法读取此文件:' , images[i])
            
#writer.close()
print('\n数据集制作完成路径为: %s' % filename)
    
    

制作完成的数据集 后缀为.tfrecoreds

                                              

但是可以看到这样的数据集体积非常庞大有4.18g 生成的过程也异常缓慢 因此可以直接将图片地址作为数据集进行保存

 

import tensorflow as tf


image = tf.cast(image_list , tf.string)
label = tf.cast(label_list , tf.int32)

input_queue = tf.train.slice_input_producer([image , label])

label = input_queue[1]
image_contents = tf.read_file(input_queue[0])
image = tf.iamge.decode_jpeg(image_contents , channels = 3)

image = tf.resize_image_with_crop_or_pad(image , 227 , 227)
image = tf.image.per_image_standardization(image)
image_batch , label_batch = tf.train.batch([image , label] , batch_size = 200 , num_threads = 64 , capacaity = 300)
label_batch = tf.reshape(label_batch , [batch_size])
tf.train.batch(
    tensors,
    batch_size,
    num_threads=1,
    capacity=32,
    enqueue_many=False,
    shapes=None,
    dynamic_pad=False,
    allow_smaller_final_batch=False,
    shared_name=None,
    name=None
)

函数功能:利用一个tensor的列表或字典来获取一个batch数据

参数介绍:

tensors:一个列表或字典的tensor用来进行入队
batch_size:设置每次从队列中获取出队数据的数量
num_threads:用来控制入队tensors线程的数量,如果num_threads大于1,则batch操作将是非确定性的,输出的batch可能会乱序
capacity:一个整数,用来设置队列中元素的最大数量
enqueue_many:在tensors中的tensor是否是单个样本
shapes:可选,每个样本的shape,默认是tensors的shape
dynamic_pad:Boolean值.允许输入变量的shape,出队后会自动填补维度,来保持与batch内的shapes相同
allow_samller_final_batch:可选,Boolean值,如果为True队列中的样本数量小于batch_size时,出队的数量会以最终遗留下来的样本进行出队,如果为Flalse,小于batch_size的样本不会做出队处理
shared_name:可选,通过设置该参数,可以对多个会话共享队列
name:可选,操作的名字

 

3.搭建神经网络 训练神经网络 测试、保存模型

这里需要强调的一个概念是在传统的文件系统是将图片读入内存后再进行计算 但是这样效率低下 导致浪费了文件读入内存的这部分时间 因此解决这个的办法就是将数据的读取和计算分为两个线程 读取数据的线程不断将数据写入内存 计算的线程直接从线程取出即可 

                   

但是由于图片的大小问题 就像上面我们制作的数据集 过于的庞大 导致运行效率还是很慢 于是在之间加入文件队列 这里需要了解一个新的概念epoch 就是指对这个数据集的所有图片全部运算一次 这样传递文件路径的方式显得就更为高效 

                   

tf.train.string_input_producer 函数便是将一个文件名的list传入 然后创建上图这两个队列的函数

tf.train.string_input_producer(
    string_tensor,
    num_epochs=None,
    shuffle=True,
    seed=None,
    capacity=32,
    shared_name=None,
    name=None,
    cancel_op=None
)

定义于:tensorflow/python/input.py。

输出字符串到一个输入管道队列。

注意:如果num_epochs不是None,则此函数创建本地计数器 epochs。使用local_variables_initializer()初始化局部变量。

参数:

string_tensor:1-D字符串Tensor。

num_epochs:一个整数(可选)。如果指定,string_input_producer在产生OutOfRange错误之前从string_tensor中产生num_epochs次字符串。如果未指定,则可以无限次循环遍历字符串。

shuffle:布尔值。如果为true,则在每个epoch内随机打乱顺序。

seed:一个整数(可选)。如果shuffle==True,则使用种子。

capacity:一个整数。设置队列容量。

shared_name:(可选的)。如果设置,则此队列将在多个会话的给定名称下共享。对具有此队列的设备打开的所有会话都可以通过shared_name访问它。在分布式设置中使用它意味着只有能够访问此操作的其中一个会话才能看到每个名称。

name:此操作的名称(可选)。

cancel_op:取消队列的操作(可选)。

返回:

一个带有输出字符串的队列。此队列的一个QueueRunner被添加到当前Graph的QUEUE_RUNNER集合中。

此时 我们还需要一个函数去启动填充队列的线程 并进行进一步的计算 这个函数就是tf.train.start_queue_runner

最终我们使用将文件路径打包 最后 转为一个batch的方法来训练神经网络

import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
import time 
import os
from PIL import Image


"""
输入:数据集路径 路径下分别是 cat 和 dog 文件夹
输出:两个列表 1.图片路径列表 2.在1中同位置图片的标签 (输出时会随机打乱)
"""

def get_file(file_dir):
    images = [] #每张图片的路径组成的列表
    temp = [] #保存cat dog文件夹路径
    for root , sub_folders , files in os.walk(file_dir):
            
        for name in files:
            images.append(os.path.join(root , name))
                
        for name in sub_folders:
            temp.append(os.path.join(root , name))
                
    labels = [] #保存注释列表
    
    #此时temp为根目录下所有文件夹的路径列表 一次取出一个文件夹 对文件夹里面的所有数据图片进行注释
    for one_folder in temp:
        n_img = len(os.listdir(one_folder)) #得到图片总数
        letter = one_folder.split('\\')[-1]  #按照“\\”分割 取出最后一个也就是文件夹的名称
    
        #标注数据集 将cat标注为0 dog标注为1 
        if letter == 'cat':
                    labels = np.append(labels , n_img*[0])
                   
        else:
                    labels = np.append(labels , n_img*[1])
              
    temp = np.array([images , labels]) #重新创建数组temp 将images 和 labels 最为一对键值对写入
    temp = temp.transpose() #将temp转置
    np.random.shuffle(temp) #打乱数据集的顺序

    image_list = list(temp[:, 0]) #取出数组中的第一维 也就是图片的路径列表
    label_list = list(temp[:, 1]) #取出数组中的第二维 也就是图片的标签列表
    label_list = [int(float(i)) for i in label_list]
    print(label_list)

    return image_list , label_list

"""
输入:get_file输出的存有文件路径和与之对应的标签的两个列表 图片的宽和高 一个batch的数量 
输出:两个tensor 一个是(200 * 270 * 270 * 3)的一个batch的图片 另一个是(200 * 1)的一个batch的标签
通过路径获得数据集 将数据集图片与对应标签打包作为数据集输入AlexNet网络
"""
def get_batch(image_list , label_list , img_width , img_height , batch_size , capacity):
    image = tf.cast(image_list , tf.string)
    label = tf.cast(label_list , tf.int64)
    
    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)
    
    image = tf.image.resize_image_with_crop_or_pad(image , 227 , 227)
    image = tf.image.per_image_standardization(image)
    image_batch , label_batch = tf.train.batch([image , label] , batch_size = 200 , num_threads = 64 , capacity = 300)
    label_batch = tf.reshape(label_batch , [batch_size])
    return image_batch , label_batch

#输入文件路径 获得两个batch
x_train , y_train = get_file("D:\\dataset_kaggledogvscat\\data_pretreatment\\train")
image_batch , label_batch = get_batch(x_train , y_train , 227 , 227 , 200 , 2048)

def batch_norm(inputs , is_training , is_conv_out = True , decay = 0.999):
    
    scale = tf.Variable(tf.ones([inputs.get_shape()[-1]]))
    beta = tf.Variable(tf.zeros([inputs.get_shape()[-1]]))
    pop_mean = tf.Variable(tf.zeros(inputs.get_shape()[-1]) , trainable = False)
    pop_var = tf.Variable(tf.ones(inputs.get_shape()[-1]) , trainable = False)
    
    
    if is_training:
        if is_conv_out:
            batch_mean , batch_var = tf.nn.moments(inputs , [0,1,2])
            
        else:
            batch_mean , batch_var = tf.nn.moments(inputs , [0])
            
        train_mean = tf.assign(pop_mean , pop_mean * decay + batch_mean * (1 - decay))
        train_var = tf.assign(pop_var , pop_var * decay + batch_var * (1 - decay))
        
        with tf.control_dependencies([train_mean , train_var]):
            return tf.nn.batch_normalization(inputs , batch_mean , batch_var , beta , scale , 0.001)
        
    else:
        return tf.nn.batch_normalization(inputs , pop_mean , pop_var , beta , scale , 0.001)
    
with tf.device('/cpu:0'):
    
    #模型参数
    learning_rate = 1e-4 #1×10^(-4)
    training_iters = 200
    batch_size = 200
    display_step = 5
    n_classes = 2
    n_fc1 = 4096
    n_fc2 = 2048
    
    #构建神经网络
    x = tf.placeholder(tf.float32 , [None , 227 , 227 , 3])
    y = tf.placeholder(tf.int32 , [None , n_classes])
    
    #权重
    W_conv = {
            'conv1' : tf.Variable(tf.truncated_normal([11 , 11 , 3 , 96] , stddev = 0.0001)) , 
            'conv2' : tf.Variable(tf.truncated_normal([5 , 5 , 96 , 256] , stddev = 0.01)) , 
            'conv3' : tf.Variable(tf.truncated_normal([3 , 3 , 256 , 384] , stddev = 0.01)) , 
            'conv4' : tf.Variable(tf.truncated_normal([3 , 3 , 384 , 384] , stddev = 0.01)) , 
            'conv5' : tf.Variable(tf.truncated_normal([3 , 3 , 384 , 256] , stddev = 0.01)) , 
            'fc1' : tf.Variable(tf.truncated_normal([13 * 13 * 256 , n_fc1] , stddev = 0.1)) , 
            'fc2' : tf.Variable(tf.truncated_normal([n_fc1 , n_fc2] , stddev = 0.1)) , 
            'fc3' : tf.Variable(tf.truncated_normal([n_fc2 , n_classes] , stddev = 0.1))
            }
    
    #偏置
    b_conv = {
            'conv1' : tf.Variable(tf.constant(0.0 , dtype = tf.float32 , shape=[96])) , 
            'conv2' : tf.Variable(tf.constant(0.1 , dtype = tf.float32 , shape=[256])) , 
            'conv3' : tf.Variable(tf.constant(0.1 , dtype = tf.float32 , shape=[384])) , 
            'conv4' : tf.Variable(tf.constant(0.1 , dtype = tf.float32 , shape=[384])) , 
            'conv5' : tf.Variable(tf.constant(0.1 , dtype = tf.float32 , shape=[256])) , 
            'fc1' : tf.Variable(tf.constant(0.1 , dtype = tf.float32 , shape=[n_fc1])) , 
            'fc2' : tf.Variable(tf.constant(0.1 , dtype = tf.float32 , shape=[n_fc2])) , 
            'fc3' : tf.Variable(tf.constant(0.0 , dtype = tf.float32 , shape=[n_classes]))
            }
    
    
    #将输入的x裁剪为(227 * 227)的三通道图像
    x_image = tf.reshape(x , [-1 , 227 , 227 , 3])
    
    #卷积层 1
    conv1 = tf.nn.conv2d(x_image , W_conv['conv1'] , strides = [1 , 4 , 4 , 1] , padding = 'VALID')
    conv1 = tf.nn.bias_add(conv1 , b_conv['conv1'])
    conv1 = batch_norm(conv1 , True)
    conv1 = tf.nn.relu(conv1)
    
    #池化层 1
    pool1 = tf.nn.avg_pool(conv1 , ksize = [1 , 3 , 3 , 1] , strides = [1 , 2, 2, 1] , padding = 'VALID')
    
    #LRN层
    norm1 = tf.nn.lrn(pool1  , 5 , bias = 1.0 , alpha = 0.001 / 9.0 , beta = 0.75)
    
    #卷积层 2
    conv2 = tf.nn.conv2d(norm1 , W_conv['conv2'] , strides = [1 , 1 , 1 , 1] , padding = 'SAME')
    conv2 = tf.nn.bias_add(conv2 , b_conv['conv2'])
    conv2 = batch_norm(conv2 , True)
    conv2 = tf.nn.relu(conv2)    
    
    #池化层 2 
    pool2 = tf.nn.avg_pool(conv2 , ksize = [1 , 3 , 3 , 1] , strides = [1 , 2 , 2 , 1] , padding = 'VALID')
    
    #LRN层
    norm2 = tf.nn.lrn(pool2  , 5 , bias = 1.0 , alpha = 0.001 / 9.0 , beta = 0.75)
    
    #卷积层 3
    conv3 = tf.nn.conv2d(norm2 , W_conv['conv3'] , strides = [1 , 1 , 1 , 1] , padding = 'SAME')
    conv3 = tf.nn.bias_add(conv3 , b_conv['conv3'])
    conv3 = batch_norm(conv3 , True)
    conv3 = tf.nn.relu(conv3)
    
    #卷积层 4 
    conv4 = tf.nn.conv2d(conv3 , W_conv['conv4'] , strides = [1 , 1 , 1 , 1] , padding = 'SAME')
    conv4 = tf.nn.bias_add(conv4 , b_conv['conv4'])
    conv4 = batch_norm(conv4 , True)
    conv4 = tf.nn.relu(conv4)
    
    #卷积层 5 
    conv5 = tf.nn.conv2d(conv4 , W_conv['conv5'] , strides = [1 , 1 , 1 , 1] , padding = 'SAME')
    conv5 = tf.nn.bias_add(conv5 , b_conv['conv5'])
    conv5 = batch_norm(conv5 , True)
    conv5 = tf.nn.relu(conv2)
    
    #池化层5
    pool5 = tf.nn.avg_pool(conv5 , ksize = [1 , 3 , 3 , 1] , strides = [1 , 2 , 2 , 1] , padding = 'VALID')
    reshape = tf.reshape(pool5 , [-1 , 13 * 13 * 256])
    
    fc1 = tf.add(tf.matmul(reshape , W_conv['fc1'] ) , b_conv['fc1'])
    fc1 = batch_norm(fc1 , True , False)
    fc1 = tf.nn.relu(fc1)
    
    #全连接层 2
    fc2 = tf.add(tf.matmul(fc1 , W_conv['fc2'] ) , b_conv['fc2'])
    fc2 = batch_norm(fc2 , True , False)
    fc2 = tf.nn.relu(fc2)
    
    #全连接层3 分类层
    fc3 = tf.add(tf.matmul(fc2 , W_conv['fc3'] ) , b_conv['fc3'])
    
    #定义损失函数
    loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits = fc3 , labels = y))
    optimizer = tf.train.GradientDescentOptimizer(learning_rate = learning_rate).minimize(loss)
    
    #测试模型
    correct_pred = tf.equal(tf.argmax(fc3 , 1) , tf.argmax(y , 1))
    accuracy = tf.reduce_mean(tf.cast(correct_pred , tf.float32))    
    
    init = tf.global_variables_initializer()

#使用onehot编码 重新标记
def onehot(labels):
    n_sample = len(labels)
    n_class = max(labels) + 1
    onehot_labels = np.zeros((n_sample , n_class))
    onehot_labels[np.arange(n_sample) , labels] = 1
    return onehot_labels

#训练模型的存放的地址 和 吗,名称
save_model = "D:\\dataset_kaggledogvscat\\model\\AlexNetModel.ckpt" 

#训练函数
def train(opech):
    with tf.Session() as sess:
        sess.run(init)
        
        saver = tf.train.Saver()
        
        #输出日志
        trian_writer = tf.summary.FileWriter("D:\\dataset_kaggledogvscat\\log" , sess.graph)
        
        #记录每次训练的情况在坐标图上的点
        point = []
        start_time = time.time()
        coord = tf.train.Coordinator()
        threads = tf.train.start_queue_runners(coord = coord)
        
        step = 0
        
        #opech为迭代次数 每次输入一个batch去训练
        for i in range(opech):
            step = i
            image , label = sess.run([image_batch , label_batch])
            labels = onehot(label)
            sess.run(optimizer , feed_dict = {x:image , y:labels})
            loss_record = sess.run(loss , feed_dict = {x:image , y:labels})
            print("目前损失为: %f \n" % loss_record)
            point.append(loss_record)
            end_time = time.time()
            print("花费时间: " , (end_time - start_time))
            print("----------------------------第 %d 轮训练已经完成-----------------------" % i)
            
        print("训练全部完成!")
        saver.save(sess , save_model)
        print("模型已经成功保存至 %s !" % save_model)
        
        coord.request_stop()
        coord.join(threads)
        plt.plot(point)
        plt.xlabel('迭代次数')
        plt.ylabel('损失率')
        plt.tittle('学习率 = %f , 迭代次数 = %d , 批量 = %d' % (learning_rate , training_iters , batch_size))
        plt.tight_layout()
        plt.savefig('D:\\dataset_kaggledogvscat\\train_result\\catvsdog_AlexNet.jpg' , dpi = 200)
        
def per_class(imagefile):
    
    image = Image.open(imagefile)
    image = image.resize([227 , 227])
    image_array = np.array(image)
    
    image = tf.cast(image_array , tf.float32)
    image = tf.image.per_image_standardization(image)
    image = tf.reshape(image , [1 , 227 , 227 , 3])
    
    saver = tf.train.Saver()
    with tf.Session() as sess:
        
        save_model = tf.train.latest_checkpoint('D:\\dataset_kaggledogvscat\\model')
        saver.restore(sess , save_model)
        image = tf.reshape(image , [1 , 227 , 227 , 3])
        image = sess.run(image)
        prediction = sess.run(fc3 , feed_dict = {x : image})
        
        max_index = np.argmax(prediction)
        if max_index == 0 :
            return "cat"
        else:
            return "dog"

#执行以上程序
imagefile = "D:\\dataset_kaggledogvscat\\data_pretreatment\\test"
cat = dog = 0

train(1000)
for root , sub_folders , files in os.walk(imagefile):
    for name in files:
        imagefile = os.path.join(root , name)
       # print(imagefile)
        if per_class(imagefile) == "cat":
            cat += 1
        else:
            dog += 1
            
        print("cat : " , cat , " | dog : " , dog)

4.训练结果

                           

                                     

5.模型的使用与加载

可以看到保存的模型有四个文件 关于这四个文件的读取与意义将会在我的下面一篇博文讲解

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值