iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 12
0
AI & Data

輕鬆掌握 Keras 及相關應用系列 第 12

Day 12:影像資料增補(Data Augmentation)

前言

前一篇利用CNN辨識手寫阿拉伯數字,其實有幾個缺點:

  1. MNIST 的訓練資料與滑鼠撰寫的樣式有所差異,我猜 MNIST 的資料收集應該是請受測者寫在紙上,再掃描存檔,因此有灰階及鋸齒狀,所以,如果要實際應用,應該自己收集訓練資料。
  2. 要收集上萬筆受測者的資料,可能不太容易,另外,有些人書寫可能字體歪斜、偏一邊、或字體大小不同,我們可以透過【資料增補】(Data Augmentation)方式自動產生這些資料。
  3. 可以套用預訓模型(Pre-trained Models),利用已訓練好的模型,使辨識更精準。

以下就前兩點實作,辨識貓或狗,看看效果如何。

前置作業

如果要在本機執行,須進行以下前置作業:

  1. Kaggle下載資料集,內有貓與狗的圖片。
  2. 解壓縮至PetImages目錄。

如果要在 Colab 執行,就直接在 Notebook 內完成以上兩件事:

# 下載資料集
!curl -O https://download.microsoft.com/download/3/E/1/3E1C3F21-ECDB-4869-8368-6DEBA77B919F/kagglecatsanddogs_3367a.zip

# 解壓縮
!unzip -q kagglecatsanddogs_3367a.zip

原版程式請自【Image classification from scratch】下載,經測試在 Colab 環境下載資料集及解壓縮,比 Windows 環境快上許多,建議直接在Colab環境上測試。在Windows環境上執行,可解壓縮部份檔案即可,只是準確度會降低。

實作

這次的範例檔為12_01_CatAndDog.ipynb,程式有點長,又有一些新指令,我們就一段段詳加說明:

  1. 讀取PetImages目錄內所有圖檔,掃描每一個檔案,若表頭不含"JFIF",即為不合格的檔案,不納入訓練資料內。
import os

num_skipped = 0
for folder_name in ("Cat", "Dog"):
    folder_path = os.path.join("PetImages", folder_name)
    for fname in os.listdir(folder_path):
        fpath = os.path.join(folder_path, fname)
        try:
            fobj = open(fpath, "rb")
            is_jfif = tf.compat.as_bytes("JFIF") in fobj.peek(10)
        finally:
            fobj.close()

        if not is_jfif:
            num_skipped += 1
            # Delete corrupted image
            os.remove(fpath)

print("Deleted %d images" % num_skipped)
  1. 建立訓練(Training)及驗證(Validation)資料集,注意,Tensorflow v2.3.0 才支援image_dataset_from_directory 這個函數,它會讀取目錄中的檔案,存入 dataset,它是 Python generator,一次只會讀取一批(batch)資料。
image_size = (180, 180)
batch_size = 32

train_ds = tf.keras.preprocessing.image_dataset_from_directory(
    "PetImages",
    validation_split=0.2,
    subset="training",
    seed=1337,
    image_size=image_size,
    batch_size=batch_size,
)
val_ds = tf.keras.preprocessing.image_dataset_from_directory(
    "PetImages",
    validation_split=0.2,
    subset="validation",
    seed=1337,
    image_size=image_size,
    batch_size=batch_size,
)
  1. 【資料增補】(Data Augmentation):本例只作水平翻轉、旋轉,Keras 還支援垂直翻轉、偏上/下(height_shift_range)、偏左/右(width_shift_range)、放大/縮小(zoom_range)、明亮度調整(brightness_range)、...等,詳細可參考【Image data preprocessing】
# RandomFlip("horizontal"):水平翻轉、
# RandomRotation(0.1):旋轉 0.1 比例 
data_augmentation = keras.Sequential(
    [
        layers.experimental.preprocessing.RandomFlip("horizontal"),
        layers.experimental.preprocessing.RandomRotation(0.1),
    ]
)

轉換後的樣本如下圖:
https://ithelp.ithome.com.tw/upload/images/20200912/20001976JDLKGMzBtg.png

  1. prefetch:預先讀取訓練資料,以提升效能
train_ds = train_ds.prefetch(buffer_size=32)
val_ds = val_ds.prefetch(buffer_size=32)
  1. 建立更複雜的模型
def make_model(input_shape, num_classes):
    inputs = keras.Input(shape=input_shape)
    # Image augmentation block
    x = data_augmentation(inputs)

    # Entry block
    x = layers.experimental.preprocessing.Rescaling(1.0 / 255)(x)
    x = layers.Conv2D(32, 3, strides=2, padding="same")(x)
    x = layers.BatchNormalization()(x)
    x = layers.Activation("relu")(x)

    x = layers.Conv2D(64, 3, padding="same")(x)
    x = layers.BatchNormalization()(x)
    x = layers.Activation("relu")(x)

    previous_block_activation = x  # Set aside residual

    for size in [128, 256, 512, 728]:
        x = layers.Activation("relu")(x)
        x = layers.SeparableConv2D(size, 3, padding="same")(x)
        x = layers.BatchNormalization()(x)

        x = layers.Activation("relu")(x)
        x = layers.SeparableConv2D(size, 3, padding="same")(x)
        x = layers.BatchNormalization()(x)

        x = layers.MaxPooling2D(3, strides=2, padding="same")(x)

        # Project residual
        residual = layers.Conv2D(size, 1, strides=2, padding="same")(
            previous_block_activation
        )
        x = layers.add([x, residual])  # Add back residual
        previous_block_activation = x  # Set aside next residual

    x = layers.SeparableConv2D(1024, 3, padding="same")(x)
    x = layers.BatchNormalization()(x)
    x = layers.Activation("relu")(x)

    x = layers.GlobalAveragePooling2D()(x)
    if num_classes == 2:
        activation = "sigmoid"
        units = 1
    else:
        activation = "softmax"
        units = num_classes

    x = layers.Dropout(0.5)(x)
    outputs = layers.Dense(units, activation=activation)(x)
    return keras.Model(inputs, outputs)

BatchNormalization layer 是將輸出標準化,通常可增加準確度。

  1. 訓練模型,以 dataset 為輸入資料型態。
epochs = 5
model.compile(
    optimizer=keras.optimizers.Adam(1e-3),
    loss="binary_crossentropy",
    metrics=["accuracy"],
)
model.fit(
    train_ds, epochs=epochs, validation_data=val_ds, 
)

訓練50 epochs,驗證準確率約 96%。

  1. 任取一筆資料測試
img = keras.preprocessing.image.load_img(
    "./PetImages/Cat/18.jpg", target_size=image_size
)
img_array = keras.preprocessing.image.img_to_array(img)
img_array = tf.expand_dims(img_array, 0)  # Create batch axis

predictions = model.predict(img_array)
score = predictions[0]
print(
    "This image is %.2f percent cat and %.2f percent dog."
    % (100 * (1 - score), 100 * score)
)

語法差異

Keras 獨立套件之前的寫法如下,使用 ImageDataGenerator來產生增補資料,model.fit_generator 取代 model.fit,在Tensorflow 依然可以使用 ImageDataGenerator來產生增補資料,用法稍有差異,可以參考下一篇:

datagen = ImageDataGenerator(
        rotation_range=10,
        zoom_range=0.1,
        width_shift_range=0.1,
        height_shift_range=0.1)

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

history = model.fit_generator(datagen.flow(X_train, y_train, batch_size=batch_size), epochs=epochs,
                              validation_data=(X_test, y_test), verbose=2,
                              steps_per_epoch=X_train.shape[0]//batch_size,
                              callbacks=[learning_rate_reduction])

結論

這一次我們自行準備訓練資料,並透過【資料增補】(Data Augmentation)方式自動產生更多資料。之後我們將套用預訓模型(Pre-trained Models),利用已訓練好的模型,使辨識更精準。

本篇範例檔為 12_01_CatAndDog.ipynb,,可自【這裡】下載。


上一篇
Day 11:卷積神經網路(CNN) 剖析與視覺化
下一篇
Day 13:測試 CNN 的桌面程式
系列文
輕鬆掌握 Keras 及相關應用30
0
rtfgvb74125
iT邦新手 5 級 ‧ 2021-03-23 15:34:14

老師您好,想請問老師在"建立訓練(Training)及驗證(Validation)資料集"的時候是怎麼給予label的

以子目錄名稱為label,cat及dog,其下放圖檔即可。

了解謝謝老師

0
sunnyyeh
iT邦新手 5 級 ‧ 2021-05-15 20:43:02

想問一下 如果圖片資料不平衡了話 應該怎麼做

有兩個方式:

  1. image_dataset_from_directory()函數可以將增補的檔案存在某個目錄下,之後,可以自行按比例抽樣產生訓練資料。
  2. model.fit()有一個參數class_weight,可以設定各類別的比重,較少樣本的類別占較大權種,平衡訓練結果。
sunnyyeh iT邦新手 5 級 ‧ 2021-05-24 01:42:40 檢舉

喔喔原來 感謝!!

0
sunnyyeh
iT邦新手 5 級 ‧ 2021-05-24 01:47:51

另外 想問一下
如果同時有照片又有數值 怎麼一起訓練模型?
因為照片轉成RPG矩陣後會變成很大的矩陣 不知道該怎麼加入列

csv檔裡面含有照片路徑、性別、年齡等數值
照片切割成50x50,照片總共有100張
那進去訓練的資料大小就會是(100,50,50,3)
要怎麼再加入excel上的數值資料(像是年紀啊、性別啊)一起訓練啊

看更多先前的回應...收起先前的回應...

非常好的問題,可以使用 functional API 建立模型,兩種input,一個是影像,一個是照片屬性,之後再concat即可。參閱:
https://www.pyimagesearch.com/2019/02/04/keras-multiple-inputs-and-mixed-data/

sunnyyeh iT邦新手 5 級 ‧ 2021-05-24 15:09:45 檢舉

不好意思 我剛才用您的方法測試 沒有辦法如期合併欸@@
((6392, 50, 50, 3), (6392, 3))

sunnyyeh iT邦新手 5 級 ‧ 2021-05-24 15:10:09 檢舉

會出現像是這樣的錯誤
all the input arrays must have same number of dimensions, but the array at index 0 has 4 dimension(s) and the array at index 1 has 2 dimension(s)

是以 Keras functional API 合併,而非資料合併,因CNN已轉成特徵向量,無法合併,故使用 functional API。
https://keras.io/guides/functional_api/

0
rtfgvb74125
iT邦新手 5 級 ‧ 2021-07-27 00:14:40

老師我在使用

train_ds = train_ds.prefetch(buffer_size=32)
val_ds = val_ds.prefetch(buffer_size=32)

在做訓練的時候都一直出現這個錯誤訊息,
the kernel appears to have died. it will restart automatically.
如果不使用這種取資料方式做訓練就正常,請問該怎麼解決?

看更多先前的回應...收起先前的回應...

使用下列指令試試看:

dataset = dataset.prefetch(tf.data.AUTOTUNE)

好的我試試看

老師我想請問我用相同的方法只是在不同的環境,一個是用1660S GPU 一個是用3060 GPU,我用3060跑的時候就會出現 kernel dead的情況,但1660S卻沒有,1660S的效能應該比3060差才對怎麼會1660S能跑3060不能跑,老師有遇過這種情況嗎?

沒有喔,通常是記憶體爆掉,檢查一下GPU toolkit是否安裝成功,並且看看 cmd 是否有錯誤訊息。

CUDA 是有安裝成功的,cmd是win10的還是jupyter的,我是用jupyter跑的,但jupyter倒是有出現這個錯誤訊息。

 Allocation of 8433180672 exceeds 10% of free system memory.

我要留言

立即登入留言