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.模型的使用與加載

可以看到保存的模型有四個文件 關於這四個文件的讀取與意義將會在我的下面一篇博文講解

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章