點(diǎn)擊上方“機(jī)器學(xué)習(xí)愛好者社區(qū)” 選擇“星標(biāo)”公眾號(hào),重磅干貨,第一時(shí)間送達(dá) Cats vs. Dogs (貓狗大戰(zhàn))是Kaggle 大數(shù)據(jù)競(jìng)賽某一年的一道賽題,利用給定的數(shù)據(jù)集,用算法實(shí)現(xiàn)貓和狗的識(shí)別。數(shù)據(jù)集可以從Kaggle 官網(wǎng)上下載,即https://www./c/dogs-vs-cats 。數(shù)據(jù)集由訓(xùn)練數(shù)據(jù)和測(cè)試數(shù)據(jù)組成,訓(xùn)練數(shù)據(jù)包含貓和狗各12500 張圖片,測(cè)試數(shù)據(jù)包含12500 張貓和狗的圖片。 ??首先在Pycharm 上新建Cats_vs_Dogs 工程,工程目錄結(jié)構(gòu)為:
data 文件夾下包含test 和train 兩個(gè)子文件夾,分別用于存放測(cè)試數(shù)據(jù)和訓(xùn)練數(shù)據(jù)。
logs 文件夾用于存放我們訓(xùn)練時(shí)的模型結(jié)構(gòu)以及訓(xùn)練參數(shù)。
input_data.py 負(fù)責(zé)實(shí)現(xiàn)讀取數(shù)據(jù),生成批次(batch )。
model.py 負(fù)責(zé)實(shí)現(xiàn)我們的神經(jīng)網(wǎng)絡(luò)模型。
training.py 負(fù)責(zé)實(shí)現(xiàn)模型的訓(xùn)練以及評(píng)估。
接下來分成數(shù)據(jù)讀取、模型構(gòu)造、模型訓(xùn)練、測(cè)試模型四個(gè)部分來講。 訓(xùn)練數(shù)據(jù)的讀取(input_data.py)??首先需要引入如下模塊: import tensorflow as tf import numpy as np import os 因?yàn)槲覀冃枰@取test 目錄下的文件,所以要導(dǎo)入os 模塊。 # 獲取文件路徑和標(biāo)簽,file_dir是文件夾路徑,該函數(shù)返回亂序后的圖片和標(biāo)簽 def get_files(file_dir): cats = [] label_cats = [] dogs = [] label_dogs = []
for file in os.listdir(file_dir): # 載入數(shù)據(jù)路徑并寫入標(biāo)簽值 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)) temp = np.array([image_list, label_list]) temp = temp.transpose() # 轉(zhuǎn)置 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 ??函數(shù)get_files 的功能是獲取給定路徑file_dir 下的所有的訓(xùn)練數(shù)據(jù)(包括圖片和標(biāo)簽),以list 的形式返回。由于訓(xùn)練數(shù)據(jù)前12500 張是貓,后12500 張是狗,如果直接按這個(gè)順序訓(xùn)練,訓(xùn)練效果可能會(huì)受影響(猜測(cè)的),所以需要將順序打亂。因?yàn)閳D片和標(biāo)簽是一一對(duì)應(yīng)的,所以要整合到一起亂序。 ??這里先用np.hstack 方法將貓和狗圖片和標(biāo)簽整合到一起,得到image_list 和label_list ,hstack((a,b)) 的功能是將a 和b 以水平的方式連接,比如原來cats 和dogs 是長度為12500 的向量,執(zhí)行了hstack(cats, dogs) 后,image_list 的長度為25000 ,同理label_list 的長度也為25000 。接著將一一對(duì)應(yīng)的image_list 和label_list 再合并一次。temp 的大小是2 * 25000 ,經(jīng)過轉(zhuǎn)置(變成25000 * 2 ),然后使用np.random.shuffle 方法進(jìn)行亂序。 ??最后從temp 中分別取出亂序后的image_list 和label_list 列向量,作為函數(shù)的返回值。這里要注意,因?yàn)?code style="padding: 2px 4px;outline: 0px;font-family: "Source Code Pro", "DejaVu Sans Mono", "Ubuntu Mono", "Anonymous Pro", "Droid Sans Mono", Menlo, Monaco, Consolas, Inconsolata, Courier, monospace, "PingFang SC", "Microsoft YaHei", sans-serif;font-size: 14px;line-height: 22px;color: rgb(199, 37, 78);background-color: rgb(249, 242, 244);border-radius: 2px;">label_list里面的數(shù)據(jù)類型是字符串類型,所以加上label_list = [int(i) for i in label_list] 這么一行將其轉(zhuǎn)為int 類型。 # 生成相同大小的批次,參數(shù)capacity隊(duì)列容量,返回值是圖像和標(biāo)簽的batch def get_batch(image, label, image_W, image_H, batch_size, capacity): # 將python.list類型轉(zhuǎn)換成tf能夠識(shí)別的格式 image = tf.cast(image, tf.string) label = tf.cast(label, tf.int32)
input_queue = tf.train.slice_input_producer([image, label]) # 生成隊(duì)列 image_contents = tf.read_file(input_queue[0]) label = input_queue[1] image = tf.image.decode_jpeg(image_contents, channels=3) # 統(tǒng)一圖片大小 image = tf.image.resize_images(image, [image_H, image_W], \ method=tf.image.ResizeMethod.NEAREST_NEIGHBOR) image = tf.cast(image, tf.float32) image_batch, label_batch = tf.train.batch([image, label], batch_size=batch_size, \ num_threads=64, capacity=capacity)
# label_batch = tf.reshape(label_batch, [batch_size]) return image_batch, label_batch ??函數(shù)get_batch 用于將圖片分批次,因?yàn)橐淮涡詫⑺?code style="padding: 2px 4px;outline: 0px;font-family: "Source Code Pro", "DejaVu Sans Mono", "Ubuntu Mono", "Anonymous Pro", "Droid Sans Mono", Menlo, Monaco, Consolas, Inconsolata, Courier, monospace, "PingFang SC", "Microsoft YaHei", sans-serif;font-size: 14px;line-height: 22px;color: rgb(199, 37, 78);background-color: rgb(249, 242, 244);border-radius: 2px;">25000張圖片載入內(nèi)存不現(xiàn)實(shí)也不必要,所以將圖片分成不同批次進(jìn)行訓(xùn)練。對(duì)于把訓(xùn)練數(shù)據(jù)集設(shè)置成一個(gè)個(gè)batch ,其解釋為:如果損失函數(shù)是非凸的話,整個(gè)訓(xùn)練樣本盡管算的動(dòng),可能會(huì)卡在局部最優(yōu)解上;分批訓(xùn)練表示全樣本的抽樣實(shí)現(xiàn),也就是相當(dāng)于人為地引入了修正梯度上的采樣噪聲,使得一路不同,找別路 的方法,更有可能搜索到全局最優(yōu)解。這里傳入的image 和label 參數(shù)就是函數(shù)get_files 返回的image_list 和label_list ,是python 中的list 類型,所以需要將其轉(zhuǎn)為TensorFlow 可以識(shí)別的tensor 格式。 ??這里使用隊(duì)列來獲取數(shù)據(jù),因?yàn)殛?duì)列操作牽扯到線程,這里引用了一張圖解釋: ??我認(rèn)為大體上可以這么理解:每次訓(xùn)練時(shí),從隊(duì)列中取一個(gè)batch 送到網(wǎng)絡(luò)進(jìn)行訓(xùn)練,然后又有新的圖片從訓(xùn)練庫中注入隊(duì)列,這樣循環(huán)往復(fù)。隊(duì)列相當(dāng)于起到了訓(xùn)練庫到網(wǎng)絡(luò)模型間數(shù)據(jù)管道的作用,訓(xùn)練數(shù)據(jù)通過隊(duì)列送入網(wǎng)絡(luò)。 ??我們使用slice_input_producer 來建立一個(gè)隊(duì)列,將image 和label 放入一個(gè)list 中當(dāng)做參數(shù)傳給該函數(shù),然后從隊(duì)列中取得image 和label 。要注意,用read_file 讀取圖片之后,要按照?qǐng)D片格式進(jìn)行解碼。本例程中訓(xùn)練數(shù)據(jù)是jpg 格式的,所以使用decode_jpeg 解碼器,如果是其他格式,就要用其他解碼器。注意decode 出來的數(shù)據(jù)類型是uint8 ,之后模型卷積層里面conv2d 要求輸入數(shù)據(jù)為float32 類型,所以需要進(jìn)行類型轉(zhuǎn)換。 ??因?yàn)橛?xùn)練庫中圖片大小是不一樣的,所以還需要將圖片裁剪成相同大小(img_W 和img_H )。有些程序員使用resize_image_with_crop_or_pad 方法來裁剪圖片,這種方法是從圖像中心向四周裁剪,如果圖片超過規(guī)定尺寸,最后只會(huì)剩中間區(qū)域的一部分,可能一只狗只剩下軀干,頭都不見了,用這樣的圖片訓(xùn)練結(jié)果肯定會(huì)受到影響。所以這里稍微改動(dòng)了一下,使用resize_images 對(duì)圖像進(jìn)行縮放,而不是裁剪,采用NEAREST_NEIGHBOR 插值方法。 ??然后用tf.train.batch 方法獲取batch ,還有一種方法是tf.train.shuffle_batch ,因?yàn)橹耙呀?jīng)亂序過了,這里用普通的batch 函數(shù)。 ??最后將得到的image_batch 和label_batch 返回,image_batch 是一個(gè)4D 的tensor ,即[batch, width, height, channels] ,label_batch 是一個(gè)1D 的tensor ,即[batch] 。 ??可以用下面的代碼測(cè)試獲取圖片是否成功,因?yàn)橹皩D片轉(zhuǎn)為float32 了,因此這里imshow 出來的圖片色彩會(huì)有點(diǎn)奇怪,因?yàn)楸緛?code style="padding: 2px 4px;outline: 0px;font-family: "Source Code Pro", "DejaVu Sans Mono", "Ubuntu Mono", "Anonymous Pro", "Droid Sans Mono", Menlo, Monaco, Consolas, Inconsolata, Courier, monospace, "PingFang SC", "Microsoft YaHei", sans-serif;font-size: 14px;line-height: 22px;color: rgb(199, 37, 78);background-color: rgb(249, 242, 244);border-radius: 2px;">imshow是顯示uint8 類型的數(shù)據(jù)(灰度值在uint8 類型下是0 至255 ,轉(zhuǎn)為float32 后會(huì)超出這個(gè)范圍,所以色彩有點(diǎn)奇怪),不過這不影響后面模型的訓(xùn)練:
import matplotlib.pyplot as plt
BATCH_SIZE = 2 CAPACITY = 256 IMG_W = 208 IMG_H = 208
train_dir = "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])
for j in np.arange(BATCH_SIZE): print("label: %d" % label[j]) plt.imshow(img[j, :, :, :]) plt.show()
i += 1 except tf.errors.OutOfRangeError: print("done!") finally: coord.request_stop()
coord.join(threads) 卷積神經(jīng)網(wǎng)絡(luò)模型的構(gòu)造(model.py)
??以下仿照TensorFlow 的官方例程cifar-10 的網(wǎng)絡(luò)結(jié)構(gòu)來編寫的,就是兩個(gè)卷積層(每個(gè)卷積層后加一個(gè)池化層),兩個(gè)全連接層,最后使用softmax 輸出分類結(jié)果: import tensorflow as tf
def inference(images, batch_size, n_classes): # conv1, shape = [kernel_size, kernel_size, channels, kernel_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)) 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")
with tf.variable_scope("pooling1_lrn") as scope: # pool1 && norm1 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')
with tf.variable_scope("conv2") as scope: # conv2 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")
with tf.variable_scope("pooling2_lrn") as scope: # pool2 && norm2 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')
with tf.variable_scope("fc1") as scope: # full-connect1 reshape = tf.reshape(norm2, 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], dtype=tf.float32, initializer=tf.constant_initializer(0.1)) fc1 = tf.nn.relu(tf.matmul(reshape, weights) + biases, name="fc1")
with tf.variable_scope("fc2") as scope: # full_connect2 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")
with tf.variable_scope("softmax_linear") as scope: # softmax 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")
return softmax_linear 發(fā)現(xiàn)程序里面有很多with tf.variable_scope("name") 的語句,這其實(shí)是TensorFlow 中的變量作用域機(jī)制,目的是有效便捷地管理需要的變量。變量作用域機(jī)制在TensorFlow 中主要由兩部分組成:
tf.get_variable(<name>, <shape>, <initializer>) :創(chuàng)建一個(gè)變量。
tf.variable_scope(<scope_name>) :指定命名空間。
如果需要共享變量,需要通過reuse_variables 方法來指定。 def losses(logits, labels): 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 trainning(loss, learning_rate): 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): 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 函數(shù)losses 用于計(jì)算訓(xùn)練過程中的loss ,這里輸入?yún)?shù)logtis 是函數(shù)inference 的輸出,代表圖片對(duì)貓和狗的預(yù)測(cè)概率,labels 則是圖片對(duì)應(yīng)的標(biāo)簽。 ??通過在程序中設(shè)置斷點(diǎn),查看logtis 的值,結(jié)果如下圖所示,一個(gè)數(shù)值代表屬于貓的概率,一個(gè)數(shù)值代表屬于狗的概率,兩者的和為1 :
函數(shù)tf.nn.sparse_sotfmax_cross_entropy_with_logtis 是將稀疏表示的label 與輸出層計(jì)算出來結(jié)果做對(duì)比。然后因?yàn)橛?xùn)練的時(shí)候是16 張圖片一個(gè)batch ,所以再用tf.reduce_mean 求一下平均值,就得到了這個(gè)batch 的平均loss 。對(duì)于training(loss, learning_rate) ,loss 是訓(xùn)練的loss ,learning_rate 是學(xué)習(xí)率,使用AdamOptimizer 優(yōu)化器來使loss 朝著變小的方向優(yōu)化。evaluation(logits, labels) 的功能是在訓(xùn)練過程中實(shí)時(shí)監(jiān)測(cè)驗(yàn)證數(shù)據(jù)的準(zhǔn)確率,達(dá)到反映訓(xùn)練效果的作用。
·合作、交流請(qǐng)關(guān)注:公眾號(hào)「機(jī)器學(xué)習(xí)愛好者社區(qū)」(ML_shequ) ·轉(zhuǎn)載請(qǐng)?zhí)砑游⑿牛簓imudeguo
|