李宏毅CNN作業的筆記-食物分類

第一次做MachineLearning的筆記,因爲實在是碰到問題了。最近跟着李宏毅老師學習到了很多知識,課程地址:點擊前往

第三個作業是CNN。但是課程網站裏的ExampleCode給的是Pytorch版本的,而我用的是Tensorflow,懶得再安裝pytorch環境運行,所以從頭到尾自己用keras寫了一下,在這篇文章裏我記錄一下我出現的一些問題。

作業描述

其實當作業給的數據集有1.0GBytes的時候,我就知道問題沒這麼簡單。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-HRO6LbAA-1586609341890)(/Users/wanglei/Desktop/Study Monitor/作業/簡書/李宏毅CNN作業的筆記-食物分類/1.png)]

其中,training文件夾中有一萬張圖片,validation文件夾中有3000張驗證圖片。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Ii6WARFt-1586609341892)(/Users/wanglei/Desktop/Study Monitor/作業/簡書/李宏毅CNN作業的筆記-食物分類/2.png)]

他們的文件名數字開頭就是他們的類型,我們需要分類圖片中的食物類型,一萬張圖片,共11種食物

讀取數據

def readfile(path, label):
    # label 是一個 boolean variable,代表需不需要回傳 y 值
    image_dir = sorted(os.listdir(path))
    x = np.zeros((len(image_dir), 128, 128, 3), dtype=np.uint8)
    y = np.zeros((len(image_dir)), dtype=np.uint8)
    for i, file in enumerate(image_dir):
        img = cv2.imread(os.path.join(path, file))
        x[i, :, :] = cv2.resize(img,(128, 128))
        if label:
            y[i] = int(file.split("_")[0])
    if label:
        return x, y
    else:
        return x
workspace_dir = './data'
print("Reading data")
train_x, train_y = readfile(os.path.join(workspace_dir, "training"), True)
print("Size of training data = {}".format(len(train_x)))
val_x, val_y = readfile(os.path.join(workspace_dir, "validation"), True)
print("Size of validation data = {}".format(len(val_x)))
test_x = readfile(os.path.join(workspace_dir, "testing"), False)
print("Size of Testing data = {}".format(len(test_x)))

這一步是照搬的ExampleCode的函數式,不是本次的重點。

設計CNN

先看代碼:

model = keras.models.Sequential()
# the raw shape (128,128,3)
# padding: same:對邊緣補0 valid:對邊緣不補0
model.add(keras.layers.Conv2D(32, (3,3), strides=(1,1), padding='same', activation='relu', input_shape=(128,128,3)))
# This time the shape is (32,128,128,3)
# MaxPooling
model.add(keras.layers.MaxPooling2D(pool_size=(2,2),strides=(2,2)))
# (32,64,64,3)
model.add(keras.layers.Conv2D(64, (3,3), strides=(1,1), padding='same', activation='relu'))   
model.add(keras.layers.MaxPooling2D(pool_size=(2,2),strides=(2,2)))
#  (64,32,32,3)
model.add(keras.layers.Flatten())
# Flatten and input Neural NetWOrk; set activation = selu(auto_batch_normalization)
# 20 layers; 100 nodes
for _ in range(20):
    model.add(keras.layers.Dense(100,activation='selu'))

model.add(keras.layers.Dense(11, activation='softmax')) 
model.compile(loss = 'sparse_categorical_crossentropy',optimizer = 'adam',
              metrics = ['accuracy'])

image data先經過一次Convolution,我設計了32個filter,每個是(3,3)的矩陣,至於爲什麼選這個數,大家都喜歡用(具體原因我還在研究這篇文章),再經過一層MaxPooling,這裏也用常用的(2,2),再來一組這樣的操作,只不過這時候的filter的個數變成了原來的2倍。

然後對輸出的數據進行Flatten之後輸入到Neural Network裏面去。網絡一共20個layer,每個layer有100個NodeActivation Function選擇selu是爲了自動批歸一化,提高準確率。

然後因爲是分類問題,並且輸入的分類target的值是1、2、3、4…所以選了sparse_categorical_crossentropy作爲loss function

訓練網絡

第一次訓練

logdir = './dnn_callbacks'
if not os.path.exists(logdir):
    os.mkdir(logdir)
output_model_file = os.path.join(logdir,
                                "cnn_classfication_model.h5")
callbacks = [
    keras.callbacks.TensorBoard(logdir),
    keras.callbacks.ModelCheckpoint(output_model_file,
                                   save_best_only=True),
]
history = model.fit(train_x, train_y, epochs=10, 
                   callbacks=callbacks)  

加入了兩個callback是爲了調試、在第一次訓練的時候,我沒有考慮validation數據集,我想用驗證集來驗證訓練出來的網絡的正確性,方便一些。

並且由於我用的設備MacBookPro沒有支持tensorflow-gpu的外設,所以epochs只設置了10次,不過這也讓我電腦運行了十幾分鍾。

在trainning過程中,表現優秀,正確率達到0.87

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-NMDqQIoq-1586609341893)(/Users/wanglei/Desktop/Study Monitor/作業/簡書/李宏毅CNN作業的筆記-食物分類/5.png)]

但是在valiadtion數據集上,只有0.2的正確率。。。

在這裏插入圖片描述

好吧,像這種在training data上有好的performance,但是在testing data上的performance很差,根據李宏毅老師的課,大概有三種解決辦法,還有救:

  • Early Stoping

    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-YPvxExUv-1586609341894)(/Users/wanglei/Desktop/Study Monitor/作業/簡書/李宏毅CNN作業的筆記-食物分類/4.png)]

  • Dropout

  • Regularization(正則化)

第二次訓練 - 加上Dropout

在tensorflow2.x中加上Dropout的方法就是新增一層layer

創建模型時候的代碼變稱下面這樣:

model = keras.models.Sequential()
model.add(keras.layers.Conv2D(32, (3,3), strides=(1,1), padding='same', activation='relu', input_shape=(128,128,3)))
model.add(keras.layers.MaxPooling2D(pool_size=(2,2),strides=(2,2)))
model.add(keras.layers.Conv2D(64, (3,3), strides=(1,1), padding='same', activation='relu'))   
model.add(keras.layers.MaxPooling2D(pool_size=(2,2),strides=(2,2)))
model.add(keras.layers.Flatten())
for _ in range(20):
    model.add(keras.layers.Dense(100,activation='selu'))
model.add(keras.layers.AlphaDropout(rate=0.5))	###### 這裏是新增的dropout
model.add(keras.layers.Dense(11, activation='softmax')) 
model.compile(loss = 'sparse_categorical_crossentropy',optimizer = 'adam',
              metrics = ['accuracy'])

除了新增了一行AlphaDropout的layer,其他都不變。AlphaDropout比普通的Dropout有更好的性質。

在這裏插入圖片描述

擦,excuse me,這平均15%的正確率是怎麼回事?

心存僥倖,我還是在驗證集上驗證了一下。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-cIlS7fgj-1586609341895)(/Users/wanglei/Desktop/Study Monitor/作業/簡書/李宏毅CNN作業的筆記-食物分類/8.png)]

哎,又失敗了,既然Dropout不行,那就再加上EarlyStopping

第三次訓練 - 加上Early Stopping

但這個時候,我們需要放棄驗證集來做evaluate了,因爲EarlyStopping需要驗證集做訓練,所以我們要手動看test的performance了。你可能要問我,爲什麼不從training data中分出一部分用作驗證集?很簡單、我比較懶,我能夠想到用numpyrandom方法從驗證集中隨機取出一部分用作trainning data,另一部分用作validation data,原本的validation文件夾裏的圖片用作testing data

但這也得我們加上validation data後,train出了好的performance纔可以,事實證明,我不用這麼白忙活。因爲加上了Early Stopping,又出了問題。

在tensorflow2.x中加上Early Stopping的途徑是設置callbacks。這裏我把之前加的Dropout也刪去了,因爲本來加上Dropout就有非常poorperformance,加上應該會更低吧。

logdir = './dnn_callbacks'
if not os.path.exists(logdir):
    os.mkdir(logdir)
output_model_file = os.path.join(logdir,
                                "cnn_classfication_model.h5")
callbacks = [
    keras.callbacks.TensorBoard(logdir),
    keras.callbacks.ModelCheckpoint(output_model_file,
                                   save_best_only=True),
    keras.callbacks.EarlyStopping(patience=5,min_delta=1e-3) # 這裏是新增的內容
]
history = model.fit(train_x, train_y, epochs=10, 
                   callbacks=callbacks)  

我們再運行。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-e7rniJJm-1586609341895)(/Users/wanglei/Desktop/Study Monitor/作業/簡書/李宏毅CNN作業的筆記-食物分類/9.png)]

Early Stopping 並沒有發揮作用,也就是說,即使我們不加Early Stopping,僅加上驗證集,也就會得到這樣的結果,真是蛋疼。

但應付train的poor performance,我們還有兩種解決辦法:

  • New Activation Function

    我認爲,這個不需要考慮了吧,還有什麼比selu更適合的呢。

  • Adapative Learning Rate

    我認爲,這個也不許需要考慮了吧,我用的優化器是adam,還不夠Adapative麼。

綜上,現在還沒找到好的解決方案,也許是CNN網絡的設計問題?或許是epoch的次數不夠多。初學者,希望各位路人大佬能教教我。

如果你是在我的個人博客看到的這篇文章,推薦點擊最上方的前往CSDN閱讀進行留言,根據相關法律法規,博客暫未開通評論功能。

另外還可以體驗我用JavaScript在前端編寫的線性迴歸小遊戲:點擊前往

後記

問題解決了,還是自己太年輕。現在通用的CNN網絡,大部分都是自Conv做手腳,比如很多層conv加上兩到三層fully connected這樣的。而我這個網絡是兩到三層conv加上二十層fully conntected。建議大家把我當作反面教材。
特別感謝來自TG的不知名網友 Jieming Zhou 的指點迷津。

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