1. 程式人生 > >100天搞定機器學習|day40-42 Tensorflow Keras識別貓狗

100天搞定機器學習|day40-42 Tensorflow Keras識別貓狗

100天搞定機器學習|1-38天
100天搞定機器學習|day39 Tensorflow Keras手寫數字識別

前文我們用keras的Sequential 模型實現mnist手寫數字識別,準確率0.9713。今天我們完成day40-42的課程,實現貓、狗的識別。

本文資料集下載地址
https://download.microsoft.com/download/3/E/1/3E1C3F21-ECDB-4869-8368-6DEBA77B919F/kagglecatsanddogs_3367a.zip

本文需要用到OpenCV和Tqdm。OpenCV是跨平臺計算機視覺庫,可以實現了影象處理和計算機視覺方面的很多通用演算法。Tqdm是用來顯示進度條的,使用很直觀(在迴圈體裡邊加個tqdm),基本不影響原程式效率。安裝都很簡單,只需要pip install即可:

1、資料預處理

資料集各有12501張貓和狗的影象,先對這些影象進行尺寸統一和顏色處理。

匯入庫

import numpy as np
import matplotlib.pyplot as plt
import osimport cv2
from tqdm import tqdm

看一張轉換後的圖片

DATADIR = "...\\PetImages" # 資料集的路徑,請根據需要修改
CATEGORIES = ["Dog", "Cat"]
for category in CATEGORIES:     
    path = os.path.join(DATADIR,category)  # 建立路徑    
    for img in os.listdir(path):  # 迭代遍歷每個圖片
        img_array = cv2.imread(os.path.join(path,img) ,cv2.IMREAD_GRAYSCALE)  # 轉化成array
        plt.imshow(img_array, cmap='gray')  # 轉換成影象展示
        plt.show()  # display!
        break  # 我們作為演示只展示一張,所以直接break了    break  #同上

看下array中儲存的影象資料:

print(img_array)

[[117 117 119 … 133 132 132]
[118 117 119 … 135 134 134]
[119 118 120 … 137 136 136]

[ 79  74  73 …  80  76  73]
[ 78  72  69 …  72  73  74]
[ 74  71  70 …  75  73  71]]
看下array的形狀:

print(img_array.shape)

(375, 500)
我們可以看到這是一張很大的圖片,並且擁有RGB3個通道,這並不是我們想要的,所以接下來我們將要進行的操作會使影象變小,並且只剩下灰度:

IMG_SIZE = 50
new_array = cv2.resize(img_array, (IMG_SIZE, IMG_SIZE))
plt.imshow(new_array, cmap='gray')
plt.show()

SIZE設定成50有一些模糊,嘗試下100:

IMG_SIZE = 100
new_array = cv2.resize(img_array, (IMG_SIZE, IMG_SIZE))
plt.imshow(new_array, cmap='gray')
plt.show()

接下來,我們將要建立所有這些培訓資料,但是,首先,我們應該留出一些影象進行最終測試。我將手動建立一個名為Testing的目錄,然後在其中建立2個目錄,一個用於Dog,一個用於Cat。從這裡開始,我將把Dog和Cat的前15張影象移到訓練版本中。確保移動它們,而不是複製。我們將使用它進行最終測試。

training_data = []
def create_training_data():
    for category in CATEGORIES: 
         path = os.path.join(DATADIR,category)
          class_num = CATEGORIES.index(category)  # 得到分類,其中 0=dog 1=cat
        for img in tqdm(os.listdir(path)):
              try: 
               img_array = cv2.imread(os.path.join(path,img) ,cv2.IMREAD_GRAYSCALE) 
                 new_array = cv2.resize(img_array, (IMG_SIZE, IMG_SIZE))  # 大小轉換
                training_data.append([new_array, class_num])  # 加入訓練資料中
            except Exception as e:  # 為了保證輸出是整潔的
                pass            #except OSError as e: 
           #    print("OSErrroBad img most likely", e, os.path.join(path,img)) 
           #except Exception as e: 
           #    print("general exception", e, os.path.join(path,img))create_training_data()print(len(training_data))

100%|██████████| 12501/12501 [00:36<00:00, 342.82it/s]
100%|██████████| 12501/12501 [00:39<00:00, 320.35it/s]
24946
我們有大約25,000張圖片。
我們要做的一件事是確保我們的資料是平衡的。在這個資料集的情況下,我可以看到資料集開始時是平衡的。平衡,我的意思是每個班級都有相同數量的例子(相同數量的狗和貓)。如果不平衡,要麼將類權重傳遞給模型,以便它可以適當地測量誤差,或者通過將較大的集修剪為與較小集相同的大小來平衡樣本。
現在資料集中要麼全是dog要麼全是cat,因此接下來要引入隨機:

import random
random.shuffle(training_data)

我們的training_data是一個列表,這意味著它是可變的,所以它現在很好地改組了。我們可以通過迭代幾個初始樣本並打印出類來確認這一點:

for sample in training_data[:10]: 
   print(sample[1])

0
1
0
1
1
0
1
0
1
0
現在可以看到已經是0、1交替了,我們可以開始我們的模型了:

X = []
y = []
for features,label in training_data:
    X.append(features)
    y.append(label)
print(X[0].reshape(-1, IMG_SIZE, IMG_SIZE, 1))
X = np.array(X).reshape(-1, IMG_SIZE, IMG_SIZE, 1)

讓我們儲存這些資料,這樣我們就不需要每次想要使用神經網路模型時繼續計算它:

import pickle
pickle_out = open("...\\X.pickle","wb")
pickle.dump(X, pickle_out)pickle_out.close()
pickle_out = open("...\\y.pickle","wb")
pickle.dump(y, pickle_out)pickle_out.close()
# We can always load it in to our current script, or a totally new one by doing:
pickle_in = open("...\\X.pickle","rb")
X = pickle.load(pickle_in)
pickle_in = open("...\\y.pickle","rb")
y = pickle.load(pickle_in)

現在我們已經拿出了資料集,我們已經準備好覆蓋卷積神經網路,並用我們的資料進行分類。
以上就是這次的關於資料集操作的全部任務。


2、訓練模型

基礎知識
基本的CNN結構如下:Convolution(卷積) -> Pooling(池化) -> Convolution -> Pooling -> Fully Connected Layer(全連線層) -> Output

Convolution(卷積)是獲取原始資料並從中建立特徵對映的行為。Pooling(池化)是下采樣,通常以“max-pooling”的形式,我們選擇一個區域,然後在該區域中取最大值,這將成為整個區域的新值。Fully Connected Layers(全連線層)是典型的神經網路,其中所有節點都“完全連線”。卷積層不像傳統的神經網路那樣完全連線。

卷積:我們將採用某個視窗,並在該視窗中查詢要素,該視窗的功能現在只是新功能圖中的一個畫素大小的功能,但實際上我們將有多層功能圖。接下來,我們將該視窗滑過並繼續該過程,繼續此過程,直到覆蓋整個影象。

池化:最常見的池化形式是“最大池化”,其中我們簡單地獲取視窗中的最大值,並且該值成為該區域的新值。

全連線層:每個卷積和池化步驟都是隱藏層。在此之後,我們有一個完全連線的層,然後是輸出層。完全連線的層是典型的神經網路(多層感知器)型別的層,與輸出層相同。

注意
本次程式碼中所需的X.pickle和y.pickle為上一篇的輸出,路徑請根據自己的情況更改!

此篇中文為譯者根據原文整理得到,可能有不當之處,可以點選此處檢視原文。

import tensorflow as tf
from tensorflow.keras.datasets import cifar10
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout, Activation, Flatten
from tensorflow.keras.layers import Conv2D, MaxPooling2D

import pickle

pickle_in = open("../datasets/X.pickle","rb")
X = pickle.load(pickle_in)

pickle_in = open("../datasets/y.pickle","rb")
y = pickle.load(pickle_in)

X = X/255.0

model = Sequential()

model.add(Conv2D(256, (3, 3), input_shape=X.shape[1:]))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Conv2D(256, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Flatten())  # this converts our 3D feature maps to 1D feature vectors

model.add(Dense(64))

model.add(Dense(1))
model.add(Activation('sigmoid'))

model.compile(loss='binary_crossentropy',
              optimizer='adam',
              metrics=['accuracy'])

model.fit(X, y, batch_size=32, epochs=3, validation_split=0.3)

Train on 17462 samples, validate on 7484 samples
Epoch 1/3
step - loss: 0.6728 - acc: 0.6019 - val_loss: 0.6317 - val_acc: 0.6463
Epoch 2/3
step - loss: 0.6164 - acc: 0.6673 - val_loss: 0.6117 - val_acc: 0.6776
Epoch 3/3
step - loss: 0.5690 - acc: 0.7129 - val_loss: 0.5860 - val_acc: 0.6963

在僅僅三個epoches之後,我們的驗證準確率為71%。如果我們繼續進行更多的epoches,我們可能會做得更好,但我們應該討論我們如何知道我們如何做。為了解決這個問題,我們可以使用TensorFlow附帶的TensorBoard,它可以幫助在訓練模型時視覺化模型。

3、模型調參

在這一部分,我們將討論的是TensorBoard。TensorBoard是一個方便的應用程式,允許在瀏覽器中檢視模型或模型的各個方面。我們將TensorBoard與Keras一起使用的方式是通過Keras回撥。實際上有很多Keras回撥,你可以自己製作。

from tensorflow.keras.callbacks import TensorBoard
#建立TensorBoard回撥物件
NAME = "Cats-vs-dogs-CNN"

tensorboard = TensorBoard(log_dir="logs/{}".format(NAME))

最終,你會希望獲得更多的自定義NAME,但現在這樣做。因此,這將儲存模型的訓練資料logs/NAME,然後由TensorBoard讀取。

最後,我們可以通過將它新增到.fit方法中來將此回撥新增到我們的模型中,例如:

model.fit(X, y,
          batch_size=32,
          epochs=3,
          validation_split=0.3,
          callbacks=[tensorboard])

請注意,這callbacks是一個列表。也可以將其他回撥傳遞到此列表中。我們的模型還沒有定義,所以現在讓我們把它們放在一起:

import tensorflow as tf
from tensorflow.keras.datasets import cifar10
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout, Activation, Flatten
from tensorflow.keras.layers import Conv2D, MaxPooling2D
from tensorflow.keras.callbacks import TensorBoard
# more info on callbakcs: https://keras.io/callbacks/ model saver is cool too.
import pickle
import time

NAME = "Cats-vs-dogs-CNN"

pickle_in = open("../datasets/X.pickle","rb")
X = pickle.load(pickle_in)

pickle_in = open("../datasets/y.pickle","rb")
y = pickle.load(pickle_in)

X = X/255.0

model = Sequential()

model.add(Conv2D(256, (3, 3), input_shape=X.shape[1:]))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Conv2D(256, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Flatten())  # this converts our 3D feature maps to 1D feature vectors
model.add(Dense(64))

model.add(Dense(1))
model.add(Activation('sigmoid'))

tensorboard = TensorBoard(log_dir="logs/{}".format(NAME))

model.compile(loss='binary_crossentropy',
              optimizer='adam',
              metrics=['accuracy'],
              )

model.fit(X, y,
          batch_size=32,
          epochs=3,
          validation_split=0.3,
          callbacks=[tensorboard])

Train on 17462 samples, validate on 7484 samples
Epoch 1/3
step - loss: 0.6992 - acc: 0.5480 - val_loss: 0.6900 - val_acc: 0.5274
Epoch 2/3
step - loss: 0.6754 - acc: 0.5782 - val_loss: 0.6685 - val_acc: 0.5885
Epoch 3/3
step - loss: 0.6377 - acc: 0.6483 - val_loss: 0.6217 - val_acc: 0.6625
執行此之後,應該有一個名為的新目錄logs。我們現在可以使用tensorboard從這個目錄中視覺化初始結果。開啟控制檯,切換到工作目錄,然後鍵入:tensorboard --logdir=logs/。應該看到一個通知:TensorBoard 1.10.0 at http://H-PC:6006 (Press CTRL+C to quit)“h-pc”是機器的名稱。開啟瀏覽器並前往此地址。你應該看到類似的東西:

現在我們可以看到我們的模型隨著時間的推移。讓我們改變模型中的一些東西。首先,我們從未在密集層中新增啟用。另外,讓我們嘗試整體較小的模型:

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout, Activation, Flatten
from tensorflow.keras.layers import Conv2D, MaxPooling2D
from tensorflow.keras.callbacks import TensorBoard
# more info on callbakcs: https://keras.io/callbacks/ model saver is cool too.
import pickle
import time

NAME = "Cats-vs-dogs-64x2-CNN"

pickle_in = open("../datasets/X.pickle","rb")
X = pickle.load(pickle_in)

pickle_in = open("../datasets/y.pickle","rb")
y = pickle.load(pickle_in)

X = X/255.0

model = Sequential()

model.add(Conv2D(64, (3, 3), input_shape=X.shape[1:]))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Conv2D(64, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Flatten())  # this converts our 3D feature maps to 1D feature vectors
model.add(Dense(64))
model.add(Activation('relu'))

model.add(Dense(1))
model.add(Activation('sigmoid'))

tensorboard = TensorBoard(log_dir="logs/{}".format(NAME))

model.compile(loss='binary_crossentropy',
              optimizer='adam',
              metrics=['accuracy'],
              )

model.fit(X, y,
          batch_size=32,
          epochs=10,
          validation_split=0.3,
          callbacks=[tensorboard])

除此之外,我還改名為NAME = "Cats-vs-dogs-64x2-CNN"。不要忘記這樣做,否則你會偶然附加到你以前的型號的日誌,它看起來不太好。我們現在檢查TensorBoard:

看起來更好!但是,可能會立即注意到驗證丟失的形狀。損失是衡量錯誤的標準,看起來很明顯,在我們的第四個時代之後,事情開始變得糟糕。

有趣的是,我們的驗證準確性仍然持續,但我想它最終會開始下降。更可能的是,第一件遭受的事情確實是你的驗證損失。這應該提醒你,你幾乎肯定會開始過度適應。這種情況發生的原因是該模型不斷嘗試減少樣本損失。

在某些時候,模型不是學習關於實際資料的一般事物,而是開始只記憶輸入資料。如果你繼續這樣做,是的,樣本中的“準確性”會上升,但你的樣本,以及你試圖為模型提供的任何新資料可能會表現得很差。

參考

https://github.com/MLEveryday/100-Days-Of-ML-Code/blob/master/Code/Day%2040.ipynb

https://github.com/MLEveryday/100-Days-Of-ML-Code/blob/master/Code/Day%2041.ipynb

https://github.com/MLEveryday/100-Days-Of-ML-Code/blob/master/Code/Day%2042.ip