kaggle貓狗大戰之AlexNet(一)
這篇文章主要介紹如何利用AlexNet預訓練模型來訓練一個貓狗分類器,主要內容包括:
- 專案結構介紹
- 資料探索
- 資料的準備
- AlexNet模型的構建
- 模型的訓練和效能評估
- 結果的提交
一、專案結構介紹
1、相關資料下載地址
專案地址:https://github.com/steelOneself/kaggle/tree/master/cat_vs_dog/AlexNet
資料下載地址:https://www.kaggle.com/c/dogs-vs-cats-redux-kernels-edition/data
AlexNet預訓練檔案下載地址:http://www.cs.toronto.edu/~guerzhoy/tf_alexnet/
2、專案檔案介紹
checkpoints --- 用來儲存訓練後生成的模型檔案
model --- 用來存放預訓練模型檔案
tensorboard ---用來儲存訓練過程中的日誌資訊
txt --- 用來存放資料集的圖片資訊
AlexNet.py --- AlexNet模型結構檔案
DataGenrator.py --- 資料生成類
Exploration.py --- 視覺化分析
Generate_txt.py --- 將圖片資訊儲存為txt檔案
run.py --- 模型訓練、評估、結構生成
util_data.py --- 資料集工具類,將資料分為訓練集和驗證集
二、資料探索
1、資料介紹
資料包含兩部分,訓練集和測試集,訓練集有25000張圖片,測試集有12500張圖片,在訓練集的圖片名稱中包含了圖片的標籤資訊,而測試集的圖片名稱代表的圖片的id,提交結果的時候要求提交圖片的id和預測對應圖片的標籤(1表示dog,0表示cat)。
2、資料探索
分析訓練集中貓狗的分佈情況
三、資料的準備
注意:我將展示出來的程式碼進行了省略,由於程式碼比較多,比較佔空間,詳細程式碼請參考git。
1、生成txt檔案
import os #設定txt儲存目錄 save_txt_dir = "txt" def generate_txt(save_mode,train_img_dir): '''將圖片的id和標籤資訊寫入到txt中 :param save_mode: train or test :param train_img_dir: 圖片所在的目錄 :return: 空 ''' ..... if __name__ == "__main__": #將訓練集圖片儲存為txt檔案 generate_txt("train","D:/dataset/kaggle/cat_or_dog/train/train") #將測試集圖片儲存為txt檔案 generate_txt("test","D:/dataset/kaggle/cat_or_dog/test/test")
將圖片資訊儲存為txt檔案,儲存格式:圖片id,圖片路徑,圖片標籤
2、將資料分為訓練集和驗證集
import pandas as pd
from sklearn.utils import shuffle
def get_img_infos(mode,img_info_txt,label_name_to_num=None):
'''讀取txt中儲存的圖片資訊
:param mode: train or test
:param img_info_txt: 檔案資訊儲存的txt路徑
:param label_name_to_num: 將字串標籤轉為數字
:return: 圖片id資訊和圖片的標籤(mode為train時不為空,mode為test時為空)
'''
...
return img_ids,img_labels,img_paths
def split_dataset(img_ids,img_paths,img_labels,val_size=0.1):
'''
:param img_ids: 圖片的id列表
:param img_paths: 圖片的路徑列表
:param img_labels: 圖片的標籤列表
:param val_size: 驗證集大小5000
:param test_size: 測試集大小10000
:return: 訓練集資料,驗證集資料,測試集資料
'''
...
#將訓練集檔案和測試集檔案儲存為csv檔案
train_dataset.to_csv("txt/train.csv")
val_dataset.to_csv("txt/val.csv")
return train_dataset,val_dataset
if __name__ == "__main__":
train_img_ids,train_img_labels,img_paths = get_img_infos("train","txt/train.txt")
train_dataset,val_dataset = split_dataset(train_img_ids,img_paths,train_img_labels)
將25000張圖片分為訓練集和驗證集,訓練集佔20000張圖片,驗證集佔5000張圖片,分割的時候需要注意,訓練集和驗證集中貓和狗所佔的比例相同。
3、資料生成類
import tensorflow as tf
import numpy as np
from tensorflow.python.framework.ops import convert_to_tensor
class ImageDataGenerator(object):
'''
初始化圖片生成引數
'''
......
def _parse_function_train(self,filename,label):
#將標籤轉為one-hot編碼
one_hot = tf.one_hot(label,self.num_classes)
#載入圖片的預處理
img_string = tf.read_file(filename)
img_decode = tf.image.decode_jpeg(img_string,channels=3)
img_resized = tf.image.resize_images(img_decode,[227,227])
return img_resized,one_hot
利用CPU資源來載入資料,在讀取圖片的時候需要將圖片轉為227×227,因為AlexNet要求輸出圖片的大小是227×227。
四、AlexNet模型構建
import tensorflow as tf
import numpy as np
'''
卷積函式
'''
def conv(x,filter_height,filter_width,num_filters,stride_y,stride_x,name,padding="SAME",groups=1):
#獲取輸入tensor的channel
input_channels = int(x.get_shape()[-1])
#建立一個lambda函式
convolve = lambda i,k:tf.nn.conv2d(i,k,strides=[1,stride_y,stride_x,1],padding=padding)
with tf.variable_scope(name) as scope:
#定義權重
weights = tf.get_variable("weights",shape=[filter_height,filter_width,
input_channels/groups,num_filters])
#定義偏置
biases = tf.get_variable("biases",shape=[num_filters])
if groups == 1:
conv = convolve(x,weights)
else:
input_groups = tf.split(axis=3,num_or_size_splits=groups,value=x)
weight_groups = tf.split(axis=3,num_or_size_splits=groups,value=weights)
output_groups = [convolve(i,k) for i,k in zip(input_groups,weight_groups)]
#連線卷積層
conv = tf.concat(axis=3,values=output_groups)
bias = tf.reshape(tf.nn.bias_add(conv,biases),tf.shape(conv))
#relu啟用函式
relu = tf.nn.relu(bias,name=scope.name)
return relu
'''
全連線層函式
'''
def fc(x,num_in,num_out,name,relu=True):
with tf.variable_scope(name) as scope:
#定義權重和偏置
weights = tf.get_variable("weights",shape=[num_in,num_out],trainable=True)
biases = tf.get_variable("biases",[num_out],trainable=True)
fc_out = tf.nn.xw_plus_b(x,weights,biases,name=scope.name)
if relu:
fc_out = tf.nn.relu(fc_out)
return fc_out
'''
最大池化層函式
'''
def max_pool(x,filter_height,filter_width,stride_y,stride_x,name,padding="SAME"):
return tf.nn.max_pool(x,ksize=[1,filter_height,filter_width,1],strides=[1,stride_y,stride_x,1],
padding=padding,name=name)
'''
lrn層
'''
def lrn(x,radius,alpha,beta,name,bias=1.0):
return tf.nn.local_response_normalization(x,depth_radius=radius,alpha=alpha,beta=beta,bias=bias,name=name)
'''
dropout層
'''
def dropout(x,keep_prob):
return tf.nn.dropout(x,keep_prob)
'''
定義AlexNet類
'''
class AlexNet(object):
'''
初始化AlexNet網路
引數:
x:輸入的tensor
keep_prob:dropout節點保留概率
num_classes:需要分類的數量
skip_layer:需要重新訓練的層
weights_path:預訓練引數檔案的路徑
'''
def __init__(self,x,keep_prob,num_classes,skip_layer,weights_path="default"):
self.X = x
self.KEEP_PROB = keep_prob
self.NUM_CLASSES = num_classes
self.SKIP_LAYER = skip_layer
if weights_path == "default":
self.WEIGHTS_PATH = "model/bvlc_alexnet.npy"
else:
self.WEIGHTS_PATH = weights_path
self.create()
'''
建立AlexNet網路的計算圖
'''
def create(self):
#第一層卷積
conv1 = conv(self.X,11,11,96,4,4,padding="VALID",name="conv1")
norm1 = lrn(conv1,2,2e-05,0.75,name="norm1")
pool1 = max_pool(norm1,3,3,2,2,padding="VALID",name="pool1")
#第二層卷積
conv2 = conv(pool1,5,5,256,1,1,groups=2,name="conv2")
norm2 = lrn(conv2,2,2e-05,0.75,name="norm2")
pool2 = max_pool(norm2,3,3,2,2,padding="VALID",name="pool2")
#第三層卷積
conv3 = conv(pool2,3,3,384,1,1,name="conv3")
#第四層卷積
conv4 = conv(conv3,3,3,384,1,1,groups=2,name="conv4")
#第五層卷積
conv5 = conv(conv4,3,3,256,1,1,groups=2,name="conv5")
pool5 = max_pool(conv5,3,3,2,2,padding="VALID",name="pool5")
#第六層,全連線層
flattened = tf.reshape(pool5,[-1,6*6*256])
fc6 = fc(flattened,6*6*256,4096,name="fc6")
dropout6 = dropout(fc6,self.KEEP_PROB)
#第七層,全連線層
fc7 = fc(dropout6,4096,4096,name="fc7")
dropout7 = dropout(fc7,self.KEEP_PROB)
#第八層,全連線層
self.fc8 = fc(dropout7,4096,self.NUM_CLASSES,relu=False,name="fc8")
'''
載入預訓練權重檔案初始化權重
'''
def load_initial_weights(self,session):
#載入預訓練權重檔案
weights_dict = np.load(self.WEIGHTS_PATH,encoding="bytes").item()
#遍歷所有的層,看是否需要重新訓練
for op_name in weights_dict:
if op_name not in self.SKIP_LAYER:
with tf.variable_scope(op_name,reuse=True):
for data in weights_dict[op_name]:
if len(data.shape) == 1:
var = tf.get_variable("biases",trainable=False)
session.run(var.assign(data))
else:
var = tf.get_variable("weights",trainable=False)
session.run(var.assign(data))
五、模型的訓練和效能評估
1、模型的訓練
模型引數設定
#設定訓練檔案的路徑
train_txt = "txt/train.txt"
test_txt = "txt/test.txt"
learning_rate = 0.0001
num_epochs = 10
batch_size = 128
dropout_rate = 0.5
num_classes = 2
train_layers = ["fc6","fc7","fc8"]
train_layers設定需要重新訓練的層數,在這次訓練過程中,只重新訓練AlexNet的最後三層全連線層,其餘的層保持不變。
訓練完成之後,在checkpoints會產生ckpt模型檔案,每一個epoch儲存一次模型檔案,只有當後一個在驗證集上的準確率大於前一個時才會儲存模型檔案,在儲存模型檔案的時候後面有附帶該次epoch在驗證集上的準確率。
2、模型評估
3、檢視模型在驗證集上分類正確和分類錯誤的圖片
上面一行表示分類正確的圖片,下面一行表示分類錯誤的圖片
4、檢視驗證集預測結果的分佈情況
5、混淆矩陣
6、驗證集分類結果報告
六、提交結果
kaggle的成績是計算預測結果的交叉熵損失值,在生成預測結果的時候,通過fc8輸出的結果還需要經過一個softmax層才能輸出每個類別的概率,直接使用預測類標(0或1)比預測概率的成績會低一些。
總結:本篇文章主要介紹瞭如何使用AlexNet來構建一個預訓練模型,在下一篇文章將會介紹如何來預訓練一個更復雜的Inception-resent網路。