【AutoKeras 自動化機器學習:隨手解 Dogs vs. Cats 影像辨識】 ![](https://i.imgur.com/2Xp2e4T.png) 上回我們看到 AutoKeras 的 ImageClassifier 類別如何對 MNIST 手寫數字資料集和 CIFAR-10 影像資料集產生高準確率的預測模型,這些也是我們書上實際展示的範例。不過這兩個資料集有個缺點:圖片實在太小了。美編之前還跟小編說,這圖是不是截得不夠清楚哇?(笑) 所以今天我們來看另一個範例:資料科學競賽網站 Kaggle 上的 Dogs vs. Cats Redux (https://www.kaggle.com/c/dogs-vs-cats-redux-kernels-edition/overview),是練習性質的競賽,當中的訓練集包含貓和狗的照片總共25,000 張,測試集則有無標籤的貓狗照片 12,500 張,每張圖的長寬至少有 100 像素以上,也不太需要做太多的預處理,相當適合今天的展示目的。 ![](https://i.imgur.com/eqqModS.png) 這資料集其實出自微軟和寵物領養網站 Petfinder.com 在 2007 年合作的 Asirra (https://www.microsoft.com/en-us/download/details.aspx?id=54765),也就是一個要求使用者分辨貓狗圖片來證明自己是真人的 CAPTCHA 工具,因為當時對機器來說,區分貓狗比解讀數字難多了。不過,如今你能否用神經網路『破解』它呢? 當然可以,不過目前 Kaggle 上多數的最佳解法都牽涉到模型調校、使用額外資料集或結合多重模型的技巧。在此我們要來看看,若你完全只使用 ak. ImageClassifier 的懶人訓練法能做到怎樣的程度。 以下的訓練是在小編自己的電腦進行,配有 GTX 1660 Ti 加速卡 (6G 記憶體),訓練時間約為半天。只要在電腦上安裝 NVIDIA 驅動程式和 CUDA 函式庫,AutoKeras 在訓練時就會自動使用 GPU。 (得注意的是,假如資料集非常龐大,那麼有可能訓練到某個模型時會因顯卡記憶體不足而當掉。通常用顯卡工具程式清一下記憶體、再重跑一次訓練,AutoKeras 會讀取硬碟中的記錄,從之前中斷的地方繼續。一般 AutoKeras 會根據記憶體自動調整訓練批量 (batch size),但你也可以自行指定,好預防記憶體不足問題。) ==================== 1. 首先下載 Kaggle 提供的資料集,訓練集在 train 目錄下,測試集在 test 目錄下。 由於訓練集的貓狗圖片剛好一半一半,為了後面讀取方便,我們在 train 底下建 cat 和 dog 子目錄,然後把對應的圖片拖進去: ``` \train \cat \dog ``` 至於測試集,Kaggle 上沒有給答案,所以我們通通把圖片丟進 test 子目錄: ``` \test \test ``` 2. 匯入套件: ``` import autokeras as ak import tensorflow as tf ``` 3. 然後我們使用 AutoKeras 包裝的 Keras 工具函式,指定從 train 和 test 目錄讀取圖片 (傳回 tf.data.Dataset 物件),並將大小設為 200 x 200: ``` train = ak.image_dataset_from_directory('./train', image_size=(200, 200)) test = ak.image_dataset_from_directory('./test', image_size=(200, 200)) ``` 這圖片大小是隨手設的,一般範例經常會用 224 x 224,因為這是許多預訓練好的影像模型使用的輸入大小。不過,AutoKeras 會自動在模型加入 resizing 層,所以我們暫時不用管它。 4. 下面就用 ImageClassifier 測試 3 個預設模型: ``` clf = ak.ImageClassifier(max_trials=3) clf.fit(train, callbacks=[tf.keras.callbacks.EarlyStopping(patience=3)]) ``` 由於小編不想花太多時間,而對 RGB 圖片表現最好的第 3 種模型 (Efficient B7) 訓練上又相當花時間,所以這裡加入 Keras 回呼函式 EarlyStopping,要它在模型連續 3 周期的訓練都沒進步時就停止 (預設是 10 次)。 ImageClassifier 預設的訓練指標為 val_loss (驗證集損失值),而這也是 EarlyStopping 依據的指標。在我們的書中,也會提到如何指定其他訓練指標,例如 PR AUC 等等。 訓練結果如下: ``` Trial 3 Complete [04h 36m 47s] val_loss: 0.02502167783677578 Best val_loss So Far: 0.02502167783677578 Total elapsed time: 06h 51m 16s INFO:tensorflow:Oracle triggered exit Epoch 1/6 Not enough memory, reduce batch size to 16. Epoch 1/6 Not enough memory, reduce batch size to 8. Epoch 1/6 Not enough memory, reduce batch size to 4. Epoch 1/6 6250/6250 [==============================] - 2113s 338ms/step - loss: 0.1483 - accuracy: 0.9423 Epoch 2/6 6250/6250 [==============================] - 2100s 336ms/step - loss: 0.0230 - accuracy: 0.9924 Epoch 3/6 6250/6250 [==============================] - 2101s 336ms/step - loss: 0.0111 - accuracy: 0.9966 Epoch 4/6 6250/6250 [==============================] - 2090s 334ms/step - loss: 0.0088 - accuracy: 0.9972 Epoch 5/6 6250/6250 [==============================] - 2241s 359ms/step - loss: 0.0068 - accuracy: 0.9978 Epoch 6/6 6250/6250 [==============================] - 2087s 334ms/step - loss: 0.0067 - accuracy: 0.9978 INFO:tensorflow:Assets written to: .\image_classifier\best_model\assets ``` 第一個預設模型 (雙層 CNN) 的最佳損失值約為 0.4,第二個預設模型 (ResNet50) 約為 0.16,而第三個 EfficientNet B7 則是 0.025。三個模型訓練完畢後,AutoKeras 自動選擇表現最佳的三號模型,並合併訓練集和驗證集來訓練最後一次。 以往在 Keras 中,我們可能得使用 ModelCheckpoint 回呼函式來記錄最佳模型,並在訓練結束後重新載入其權重。不過在 AutoKeras 就不須這麼做了,因為它會記得候選模型在訓練幾周期時表現最好。 由上可見,模型對訓練集的準確率達到 99.78%,損失值為 0.0067。 5. 將模型匯出為 Keras 模型並檢視摘要: ``` model = clf.export_model() model.summary() ``` 這會印出 `Model: "model"` ![](https://i.imgur.com/sRwNYVV.jpg) 6. 由於測試集沒有解答,我們來對其圖片產生預測結果,以便稍後跟圖片比對: ``` predicted = clf.predict(test) predicted ``` 產生結果如下: ``` array([['cat'], ['dog'], ['dog'], ..., ['dog'], ['dog'], ['dog']], dtype='<U3') ``` 可見模型產生的預測結果不是 0 或 1,而是字串 ‘cat’ 與 ‘dog’。 7. 下面我們便用 matplotlib 將前 40 張測試集圖片跟其預測標籤畫出來 (由於 test 是 tf.data.Dataset 物件,走訪上比較麻煩一點): ``` import matplotlib.pyplot as plt count = 40 i = 0 fig = plt.figure(figsize=(10, 16)) for batch in test.as_numpy_iterator(): for img in batch[0]: ax = fig.add_subplot(8, 5, i + 1) ax.set_title(f'Predicted: {predicted[i][0]}') ax.set_axis_off() plt.imshow(img / 255) i += 1 if i >= count: break else: continue break plt.tight_layout() plt.show() ``` ![](https://i.imgur.com/ke0YNfU.jpg) 這個結果取決於tf.data.Dataset 物件的讀取順序,你看到的可能會略有不同。 8. 那麼,模型拿來預測其他圖片的成效如何呢?由於 Dogs vs. Cats Redux 競賽以及網路上許多範例都會顯示貓與狗的預測機率,而不是單獨的預測分類,所以我們要改用前面匯出的 Keras 模型,它的預測結果就是機率而不是特定標籤: ``` import cv2 import numpy as np img_original = [] img = [] for i in range(4): new_image = cv2.cvtColor(cv2.imread(f'./test{i+1}.jpg'), cv2.COLOR_BGR2RGB) img_original.append(new_image) img.append(cv2.resize(new_image, (200, 200))) new_test = np.array(img) new_predicted = model.predict(new_test).flatten() new_predicted ``` 我們在同目錄下放了四張測試圖片,test1.jpg~test4.jpg。我們使用 OpenCV 讀取它、保留一份原始圖檔,並產生大小調整為 200 x 200 的新圖檔 (匯出的 Keras 模型會需要你提供相符大小的圖片),以此產生預測值: `array([0.00111161, 0.99979526, 0.09952653, 0.8113645 ], dtype=float32)` 由於貓狗資料集等於只有分類 0 (貓) 和 1 (狗),這種二元分類相當於正負,因此 Keras 模型傳回的是『分類為正 (1=狗) 的機率』。例如,第一筆預測值是 0.0011,這表示模型認為它是狗的機率為 0.11%,貓的機率就是 1 - 0.0011 = 0.9989 (99.89%)。 9. 下面我們將這四張圖片和其兩個分類的預測機率都畫出來: ``` fig = plt.figure(figsize=(10, 8)) for i in range(4): ax = fig.add_subplot(2, 2, i+1) plt.imshow(img_original[i]) ax.set_title(f'Dog: {new_predicted[i]*100:.3f}%\nCat: {(1-new_predicted[i])*100:.3f}%') ax.set_axis_off() plt.tight_layout() plt.show() ``` 最上面兩張是小編在路邊拍的。 至於下面兩張…所以《貓》的主角真的是貓無誤,而熊吉是狗嗎?XD ![](https://i.imgur.com/21m3LZg.png) 10. 最後,雖然這篇的目的不是在比賽,不過我們還是可以產生一份符合競賽要求的輸出結果: ``` import pandas as pd img = [] for i in range(12500): new_image = cv2.cvtColor(cv2.imread(f'./test/test/{i+1}.jpg'), cv2.COLOR_BGR2RGB) img.append(cv2.resize(new_image, (200, 200))) test = np.array(img) predicted = model.predict(test).flatten() submission = pd.DataFrame({'id': np.arange(predicted.size)+1, 'label': predicted}) submission ``` 為了確保圖片能按正確順序預測 (前面的 tf.data.Dataset 物件不保證這種順序),我們再次用 OpenCV 讀一讀取檔案,丟給模型產生預測值,然後把圖片 id 和預測結果 (分類 1 的機率) 轉成 pandas.DataFrame: ``` id label 0 1 0.999999 1 2 0.999980 2 3 1.000000 3 4 1.000000 4 5 0.000031 ... ... ... 12495 12496 0.000001 12496 12497 0.000763 12497 12498 1.000000 12498 12499 1.000000 12499 12500 0.001510 12500 rows × 2 columns ``` 11. 最後將 DataFrame 輸出為 CSV 檔並上傳 Kaggle: `submission.to_csv('./submission.csv', index=False)` ![](https://i.imgur.com/jdUFB6X.png) 此競賽的計分方式是拿你的結果跟正確結果比對,並算出 log loss,值越低越好。(log loss 必須透過機率計算,所以若你的預測值四捨五入為整數 0 或 1,那麼 log loss 就會暴增)。我們得到 0.078,在五年前的排行榜 (1,314 組參賽者) 大概能排到 200 多名。 雖然看起來好像還好,但請記得:在完全沒有做額外預處理 (訓練集中其實有 52 張標錯分類的圖,而且還有不是貓或狗的圖)、沒有提供額外資料、模型微調或跟使用集成式學習,而且還用 EarlyStopping 縮短訓練時間的前提下,這模型的預測準確率已經相當高了,建模部分則一樣只用了 3 行程式而已。若你擁有足夠的運算資源 (例如 NVIDIA P100/V100 加速卡) 和時間,你就能讓 AutoKeras 繼續搜尋模型來試著改良它。 在書中的第 8 章,也會提到如何在同一個 AutoKeras 中建立多重模型。你可以拿同一份資料產生多個預測結果,或者讓多重模型預測同一個結果。這些都有機會讓你進一步提高那百分之零點幾的預測率。 ![](https://i.imgur.com/6RaYSIn.jpg) 想了解更多《AutoML 自動化機器學習:用 AutoKeras 超輕鬆打造高效能 AI 模型》 博客來 → https://pse.is/3yhe9q