callbacks回調函數(Checkpoint、TensorBoard)、tf.data、ImageDataGenerator

日萌社

人工智能AI:Keras PyTorch MXNet TensorFlow PaddlePaddle 深度學習實戰(不定時更新)


 

4.6 TF常用功能模塊

4.6.1 fit的callbacks詳解

回調是在訓練過程的給定階段應用的一組函數。可以使用回調來獲取培訓期間內部狀態和模型統計信息的視圖。您可以將回調列表(作爲關鍵字參數callbacks)傳遞給或類的fit()方法。然後將在訓練的每個階段調用回調的相關方法。

  • 定製化保存模型
  • 保存events文件

4.6.1.1 ModelCheckpoint

from tensorflow.python.keras.callbacks import ModelCheckpoint

  • keras.callbacks.ModelCheckpoint(filepath, monitor='val_loss', save_best_only=False, save_weights_only=False, mode='auto', period=1)
    • Save the model after every epoch:每隔多少次迭代保存模型
    • filepath: 保存模型字符串
      • 如果設置 weights.{epoch:02d}-{val_loss:.2f}.hdf5格式,將會每隔epoch number數量並且將驗證集的損失保存在該位置
      • 如果設置weights.{epoch:02d}-{val_acc:.2f}.hdf5,將會按照val_acc的值進行保存模型
    • monitor: quantity to monitor.設置爲'val_acc'或者'val_loss'
    • save_best_only: if save_best_only=True, 只保留比上次模型更好的結果
    • save_weights_only: if True, 只保存去那種(model.save_weights(filepath)), else the full model is saved (model.save(filepath)).
    • mode: one of {auto, min, max}. 如果save_best_only=True, 對於val_acc, 要設置max, 對於val_loss要設置min
    • period: 迭代保存checkpoints的間隔
check = ModelCheckpoint('./ckpt/singlenn_{epoch:02d}-{val_acc:.2f}.h5',
                                monitor='val_acc',
                                save_best_only=True,
                                save_weights_only=True,
                                mode='auto',
                                period=1)

SingleNN.model.fit(self.train, self.train_label, epochs=5, callbacks=[check], validation_data=(x, y))

注意:使用ModelCheckpoint一定要在fit當中指定驗證集才能使用,否則報錯誤。 


metrics 是 sparse_categorical_crossentropy,則ModelCheckpoint中的第一個路勁字符串信息中和monitor參數中都需要用val_sparse_categorical_crossentropy
metrics 是 acc,則ModelCheckpoint中的第一個路勁字符串信息中和monitor參數中都需要用val_acc
metrics 是 accuracy,則ModelCheckpoint中的第一個路勁字符串信息中和monitor參數中都需要用val_accuracy


4.6.1.2 Tensorboard

  • 添加Tensorboard觀察損失等情況
  • keras.callbacks.TensorBoard(log_dir='./logs', histogram_freq=0, batch_size=32, write_graph=True, write_grads=False, write_images=False, embeddings_freq=0, embeddings_layer_names=None, embeddings_metadata=None, embeddings_data=None, update_freq='epoch')
    • log_dir:保存事件文件目錄
    • write_graph=True:是否顯示圖結構
    • write_images=False:是否顯示圖片
    • write_grads=True:是否顯示梯度histogram_freq 必須大於0
# 添加tensoboard觀察
 tensorboard = keras.callbacks.TensorBoard(log_dir='./graph', histogram_freq=1,
                                                  write_graph=True, write_images=True)

SingleNN.model.fit(self.train, self.train_label, epochs=5, callbacks=[tensorboard])
  • 打開終端查看:
# 指定存在文件的目錄,打開下面命令
tensorboard --logdir="./"

4.6.2 tf.data :數據集的構建與預處理

  • 問題引入:

在大部分時候,我們希望使用自己的數據集來訓練模型。然而,面對一堆格式不一的原始數據文件,將其預處理並讀入程序的過程往往十分繁瑣,甚至比模型的設計還要耗費精力。比如,爲了讀入一批圖像文件,我們可能需要糾結於 python 的各種圖像處理包(比如 pillow ),自己設計 Batch 的生成方式,最後還可能在運行的效率上不盡如人意。爲此,TensorFlow 提供了 tf.data 這一模塊,包括了一套靈活的數據集構建 API,能夠幫助我們快速、高效地構建數據輸入的流水線,尤其適用於數據量巨大的場景。

4.6.2.1 數據集對象的建立

  • tf.data 的核心是 tf.data.Dataset 類,提供了對數據集的高層封裝。
    • tf.data.Dataset 由一系列的可迭代訪問的元素(element)組成,每個元素包含一個或多個張量。比如說,對於一個由圖像組成的數據集,每個元素可以是一個形狀爲 長×寬×通道數 的圖片張量,也可以是由圖片張量和圖片標籤張量組成的元組(Tuple)。

1、tf.data.Dataset.from_tensor_slices()

最基礎的建立 tf.data.Dataset 的方法是使用 tf.data.Dataset.from_tensor_slices() ,適用於數據量較小(能夠整個裝進內存)的情況。

import tensorflow as tf
import numpy as np

X = tf.constant([2015, 2016, 2017, 2018, 2019])
Y = tf.constant([12000, 14000, 15000, 16500, 17500])

# 也可以使用NumPy數組,效果相同
# X = np.array([2015, 2016, 2017, 2018, 2019])
# Y = np.array([12000, 14000, 15000, 16500, 17500])

dataset = tf.data.Dataset.from_tensor_slices((X, Y))

for x, y in dataset:
    print(x.numpy(), y.numpy())

輸出

2013 12000
2014 14000
2015 15000
2016 16500
2017 17500

同樣類似地,我們可以載入前章的 MNIST 數據集:

import matplotlib.pyplot as plt 

(train_data, train_label), (_, _) = tf.keras.datasets.mnist.load_data()
# [60000, 28, 28, 1]
train_data = np.expand_dims(train_data.astype(np.float32) / 255.0, axis=-1)
mnist_dataset = tf.data.Dataset.from_tensor_slices((train_data, train_label))

for image, label in mnist_dataset:
    print(label.numpy())
    print(image.numpy())

4.6.2.2 數據集對象的預處理

tf.data.Dataset 類爲我們提供了多種數據集預處理方法。最常用的如:

  • 1、Dataset.map(f) :
  • 對數據集中的每個元素應用函數 f ,得到一個新的數據集(這部分往往結合 tf.io 進行讀寫和解碼文件, tf.image 進行圖像處理);
  • 2、Dataset.shuffle(buffer_size) :
  • 將數據集打亂(設定一個固定大小的緩衝區(Buffer),取出前 buffer_size 個元素放入,並從緩衝區中隨機採樣,採樣後的數據用後續數據替換);
  • 3、Dataset.batch(batch_size) :
  • 將數據集分成批次,即對每 batch_size 個元素,使用 tf.stack() 在第 0 維合併,成爲一個元素。
  • 4、Dataset.prefetch() :

    • 預取出數據集中的若干個元素
  • 5、除此以外,還有 Dataset.repeat() (重複數據集的元素)、 Dataset.reduce() (與 Map 相對的聚合操作)、 Dataset.take ()等,可參考 API 文檔 進一步瞭解。

4.6.4.3 使用案例

1、使用 Dataset.map() 將所有圖片旋轉 90 度:

def rot90(image, label):
    image = tf.image.rot90(image)
    return image, label

mnist_dataset = mnist_dataset.map(rot90)

for image, label in mnist_dataset:
    plt.title(label.numpy())
    plt.imshow(image.numpy()[:, :, 0])
    plt.show()

2、使用 Dataset.batch() 將數據集劃分批次,每個批次的大小爲 4:

# 獲取批次數據
mnist_dataset = mnist_dataset.batch(4)

for images, labels in mnist_dataset:
    fig, axs = plt.subplots(1, 4)
    for i in range(4):
        axs[i].set_title(labels.numpy()[i])
        axs[i].imshow(images.numpy()[i, :, :, 0])
    plt.show()

3、使用 Dataset.shuffle() 將數據打散後再設置批次,緩存大小設置爲 10000

  • 設定一個固定大小爲 buffer_size 的緩衝區(Buffer);初始化時,取出數據集中的前 buffer_size 個元素放入緩衝區;
  • 每次需要從數據集中取元素時,即從緩衝區中隨機採樣一個元素並取出,然後從後續的元素中取出一個放回到之前被取出的位置,以維持緩衝區的大小。
mnist_dataset = mnist_dataset.shuffle(buffer_size=10000).batch(4)

for images, labels in mnist_dataset:
    fig, axs = plt.subplots(1, 4)
    for i in range(4):
        axs[i].set_title(labels.numpy()[i])
        axs[i].imshow(images.numpy()[i, :, :, 0])
    plt.show()

注:Dataset.shuffle() 時緩衝區大小 buffer_size 的設置,每次的數據都會被隨機打散。當 buffer_size 設置爲 1 時,其實等價於沒有進行任何打散。

  • 當數據集的標籤順序分佈極爲不均勻(例如二元分類時數據集前 N 個的標籤爲 0,後 N 個的標籤爲 1)時,較小的緩衝區大小會使得訓練時取出的 Batch 數據很可能全爲同一標籤,從而影響訓練效果。一般而言,數據集的順序分佈若較爲隨機,則緩衝區的大小可較小,否則則需要設置較大的緩衝區。

4.6.4.3 數據集元素的獲取與使用

1、構建好數據並預處理後,我們需要從其中迭代獲取數據以用於訓練。tf.data.Dataset 是一個 Python 的可迭代對象,因此可以使用 For 循環迭代獲取數據,即:

dataset = tf.data.Dataset.from_tensor_slices((A, B, C, ...))
for a, b, c, ... in dataset:
    # 對張量a, b, c等進行操作,例如送入模型進行訓練

2、可以使用 iter() 顯式創建一個 Python 迭代器並使用 next() 獲取下一個元素,即:

dataset = tf.data.Dataset.from_tensor_slices((A, B, C, ...))
it = iter(dataset)
a_0, b_0, c_0, ... = next(it)
a_1, b_1, c_1, ... = next(it)

3、Keras 支持使用 tf.data.Dataset 直接作爲輸入。當調用 tf.keras.Model 的 fit() 和 evaluate() 方法時,可以將參數中的輸入數據 x 指定爲一個元素格式爲 (輸入數據, 標籤數據) 的 Dataset ,並忽略掉參數中的標籤數據 y 。例如,對於上述的 MNIST 數據集,常規的 Keras 訓練方式是:

model.fit(x=train_data, y=train_label, epochs=num_epochs, batch_size=batch_size)

使用 tf.data.Dataset 後,我們可以直接傳入 Dataset :

model.fit(mnist_dataset, epochs=num_epochs)

如果已經通過 Dataset.batch() 方法劃分了數據集的批次,所以這裏fit中也無需提供批次的大小。

4.6.4.4 使用tf.data的並行化策略提高訓練流程效率

當訓練模型時,我們希望充分利用計算資源,減少 CPU/GPU 的空載時間。然而有時,數據集的準備處理非常耗時,使得我們在每進行一次訓練前都需要花費大量的時間準備待訓練的數據,而此時 GPU 只能空載而等待數據,造成了計算資源的浪費。

tf.data 的數據集對象爲我們提供了 Dataset.prefetch() 方法,使得我們可以讓數據集對象 Dataset 在訓練時預取出若干個元素,使得在 GPU 訓練的同時 CPU 可以準備數據,從而提升訓練流程的效率

使用:

mnist_dataset = mnist_dataset.prefetch(buffer_size=tf.data.experimental.AUTOTUNE)
  • 參數 buffer_size 既可手工設置,也可設置爲 tf.data.experimental.AUTOTUNE 從而由 TensorFlow 自動選擇合適的數值。

  • 2、還有一種方式也能夠提高利用CPU資源

    • Dataset.map() 也可以利用多 GPU 資源,並行化地對數據項進行變換,從而提高效率。

通過設置 Dataset.map() 的 num_parallel_calls 參數實現數據轉換的並行化。上面的是未並行化的圖示,下面是兩核並行的圖示,時間會縮小

# 添加參數使用,num_parallel_calls 設置爲 tf.data.experimental.AUTOTUNE 以讓 TensorFlow 自動選擇合適的數值
train_dataset = train_dataset.map(
        map_func=_decode_and_resize, 
        num_parallel_calls=tf.data.experimental.AUTOTUNE)

通過 prefetch() 的使用和在 map() 過程中加入 num_parallel_calls 參數,模型訓練的時間可縮減至原來的一半甚至更低。

4.6.5 案例:實現貓狗圖像分類

數據集來自 kaggle 上的一個競賽:Dogs vs. Cats,訓練集有25000張,貓狗各佔一半。測試集12500張,沒有標定是貓還是狗。

  • 目的:貓狗圖片二分類任務爲示例
  • 步驟:
    • 1、數據集的獲取和構建
    • 2、模型構建和封裝
    • 3、訓練以及測試過程實現

1、數據集的獲取和構建

class CatOrDog(object):
    """貓狗分類
    """
    num_epochs = 1
    batch_size = 32
    learning_rate = 0.001
        # 訓練目錄
    train_cats_dir = '/root/cv_project/tf_example/cats_vs_dogs/train/cats/'
    train_dogs_dir = '/root/cv_project/tf_example/cats_vs_dogs/train/dogs/'
    # 驗證目錄
    test_cats_dir = '/root/cv_project/tf_example/cats_vs_dogs/valid/cats/'
    test_dogs_dir = '/root/cv_project/tf_example/cats_vs_dogs/valid/dogs/'

    def __init__(self):
        # 1、讀取訓練集的貓狗文件
        self.train_cat_filenames = tf.constant([CatOrDog.train_cats_dir + filename
                                                for filename in os.listdir(CatOrDog.train_cats_dir)])
        self.train_dog_filenames = tf.constant([CatOrDog.train_dogs_dir + filename
                                                for filename in os.listdir(CatOrDog.train_dogs_dir)])
        # 2、貓狗文件列表合併,並且初始化貓狗的目標值,0爲貓,1爲狗
        self.train_filenames = tf.concat([self.train_cat_filenames, self.train_dog_filenames], axis=-1)
        self.train_labels = tf.concat([
            tf.zeros(self.train_cat_filenames.shape, dtype=tf.int32),
            tf.ones(self.train_dog_filenames.shape, dtype=tf.int32)],
            axis=-1)

定義數據的獲取方法,通過tf.data指定

    def get_batch(self):
        """獲取dataset批次數據
        :return:
        """
        train_dataset = tf.data.Dataset.from_tensor_slices((self.train_filenames, self.train_labels))
        # 進行數據的map, 隨機,批次和預存儲
        train_dataset = train_dataset.map(
            map_func=_decode_and_resize,
            num_parallel_calls=tf.data.experimental.AUTOTUNE)
        train_dataset = train_dataset.shuffle(buffer_size=20000)
        train_dataset = train_dataset.batch(CatOrDog.batch_size)
        train_dataset = train_dataset.prefetch(tf.data.experimental.AUTOTUNE)
        return train_dataset

# 圖片處理函數,讀取,解碼並且進行輸入形狀修改
def _decode_and_resize(filename, label):
    image_string = tf.io.read_file(filename)
    image_decoded = tf.image.decode_jpeg(image_string)
    image_resized = tf.image.resize(image_decoded, [256, 256]) / 255.0
    return image_resized, label

2、模型構建和封裝

通過構造兩層卷積+兩個全連接層的網絡

        self.model = tf.keras.Sequential([
            tf.keras.layers.Conv2D(32, 3, activation='relu', input_shape=(256, 256, 3)),
            tf.keras.layers.MaxPooling2D(),
            tf.keras.layers.Conv2D(32, 5, activation='relu'),
            tf.keras.layers.MaxPooling2D(),
            tf.keras.layers.Flatten(),
            tf.keras.layers.Dense(64, activation='relu'),
            tf.keras.layers.Dense(2, activation='softmax')
        ])

3、訓練以及測試過程實現

模型訓練過程,這裏就免去指定ckpt以及tensorboard的callbacks了,可以自己去指定實驗

    def train(self, train_dataset):
        """訓練過程
        :return:
        """
        self.model.compile(
            optimizer=tf.keras.optimizers.Adam(learning_rate=CatOrDog.learning_rate),
            loss=tf.keras.losses.sparse_categorical_crossentropy,
            metrics=[tf.keras.metrics.sparse_categorical_accuracy]
        )

        self.model.fit(train_dataset, epochs=CatOrDog.num_epochs)
        self.model.save_weights("./ckpt/cat_or_dogs.h5")

測試過程

  • 1、需要提供一個讀取測試數據及的dataset數據集
  • 2、進行model的預測,可以先進行模型保存之後,再次讀取進行預測
    def test(self):

        # 1、構建測試數據集
        test_cat_filenames = tf.constant([CatOrDog.test_cats_dir + filename
                                          for filename in os.listdir(CatOrDog.test_cats_dir)])
        test_dog_filenames = tf.constant([CatOrDog.test_dogs_dir + filename
                                          for filename in os.listdir(CatOrDog.test_dogs_dir)])
        test_filenames = tf.concat([test_cat_filenames, test_dog_filenames], axis=-1)
        test_labels = tf.concat([
            tf.zeros(test_cat_filenames.shape, dtype=tf.int32),
            tf.ones(test_dog_filenames.shape, dtype=tf.int32)],
            axis=-1)
                # 2、構建dataset
        test_dataset = tf.data.Dataset.from_tensor_slices((test_filenames, test_labels))
        test_dataset = test_dataset.map(_decode_and_resize)
        test_dataset = test_dataset.batch(batch_size)

        # 3、加載模型進行評估
        if os.path.exists("./ckpt/cat_or_dogs.h5"):
            self.model.load_weights("./ckpt/cat_or_dogs.h5")

        print(self.model.metrics_names)
        print(self.model.evaluate(test_dataset))

4.6.6 ImageDataGenerator介紹

當存在大量本地圖片的時候,我們需要通過實時數據增強生成張量圖像數據批次。數據將不斷循環(按批次)。下面就介紹一個強大的工具

1、訓練的時候讀取本地圖片以及類別

tf.keras.preprocessing.image.ImageDataGenerator(featurewise_center=False,  
                                             samplewise_center=False, 
                                             featurewise_std_normalization=False, 
                                             samplewise_std_normalization=False, 
                                             zca_whitening=False, 
                                             zca_epsilon=1e-06, 
                                             rotation_range=0, 
                                             width_shift_range=0.0, 
                                             height_shift_range=0.0, 
                                             brightness_range=None, 
                                             shear_range=0.0, 
                                             zoom_range=0.0, 
                                             channel_shift_range=0.0, 
                                             fill_mode='nearest', 
                                             cval=0.0, 
                                             horizontal_flip=False, 
                                             vertical_flip=False, 
                                             rescale=None, 
                                             preprocessing_function=None, 
                                             data_format=None, 
                                             validation_split=0.0, 
                                             dtype=None)
  • 完整參數介紹參考TensorFlow官網文檔:https://www.tensorflow.org/api_docs/python/tf/keras/preprocessing/image/ImageDataGenerator#view-aliases

  • train_generator = ImageDataGenerator()

    • 生產圖片的批次張量值並且提供數據增強功能
    • rescale=1.0 / 255,:標準化
    • zca_whitening=False: # zca白化的作用是針對圖片進行PCA降維操作,減少圖片的冗餘信息
    • rotation_range=20:默認0, 旋轉角度,在這個角度範圍隨機生成一個值
    • width_shift_range=0.2,:默認0,水平平移
    • height_shift_range=0.2:默認0, 垂直平移
    • shear_range=0.2:# 平移變換
    • zoom_range=0.2:
    • horizontal_flip=True:水平翻轉

2、使用方法介紹

  • 使用flow(x, y, batch_size)
(x_train, y_train), (x_test, y_test) = cifar10.load_data()

datagen = ImageDataGenerator(
        featurewise_center=True,
        featurewise_std_normalization=True,
        rotation_range=20,
        width_shift_range=0.2,
        height_shift_range=0.2,
        horizontal_flip=True)

for e in range(epochs):
        print('Epoch', e)
        batches = 0
        for x_batch, y_batch in datagen.flow(x_train, y_train, batch_size=32):
            model.fit(x_batch, y_batch)
  • 使用train_generator.flow_from_directory(

    • directory=path,# 讀取目錄

    • target_size=(h,w),# 目標形狀

    • batch_size=size,# 批數量大小

    • class_mode='binary', # 目標值格式,One of "categorical", "binary", "sparse",

      • "categorical" :2D one-hot encoded labels
      • "binary" will be 1D binary labels
    • shuffle=True

    • 這個API固定了讀取的目錄格式,參考:

    • data/
          train/
              dogs/
                  dog001.jpg
                  dog002.jpg
                  ...
              cats/
                  cat001.jpg
                  cat002.jpg
                  ...
          validation/
              dogs/
                  dog001.jpg
                  dog002.jpg
                  ...
              cats/
                  cat001.jpg
                  cat002.jpg
                  ...
      

4.6.7 案例:ImageDataGenerator與遷移學習結合(基於VGG)

4.6.7.1 案例效果

Epoch 1/2
 1/13 [=>............................] - ETA: 3:20 - loss: 1.6811 - acc: 0.1562
 2/13 [===>..........................] - ETA: 3:01 - loss: 1.5769 - acc: 0.2500
 3/13 [=====>........................] - ETA: 2:44 - loss: 1.4728 - acc: 0.3958
 4/13 [========>.....................] - ETA: 2:27 - loss: 1.3843 - acc: 0.4531
 5/13 [==========>...................] - ETA: 2:14 - loss: 1.3045 - acc: 0.4938
 6/13 [============>.................] - ETA: 1:58 - loss: 1.2557 - acc: 0.5156
 7/13 [===============>..............] - ETA: 1:33 - loss: 1.1790 - acc: 0.5759
 8/13 [=================>............] - ETA: 1:18 - loss: 1.1153 - acc: 0.6211
 9/13 [===================>..........] - ETA: 1:02 - loss: 1.0567 - acc: 0.6562
10/13 [======================>.......] - ETA: 46s - loss: 1.0043 - acc: 0.6875 
11/13 [========================>.....] - ETA: 31s - loss: 0.9580 - acc: 0.7159
12/13 [==========================>...] - ETA: 15s - loss: 0.9146 - acc: 0.7344
13/13 [==============================] - 249s 19s/step - loss: 0.8743 - acc: 0.7519 - val_loss: 0.3906 - val_acc: 0.9000
Epoch 2/2
 1/13 [=>............................] - ETA: 2:56 - loss: 0.3862 - acc: 1.0000
 2/13 [===>..........................] - ETA: 2:44 - loss: 0.3019 - acc: 1.0000
 3/13 [=====>........................] - ETA: 2:35 - loss: 0.2613 - acc: 1.0000
 4/13 [========>.....................] - ETA: 2:01 - loss: 0.2419 - acc: 0.9844
 5/13 [==========>...................] - ETA: 1:49 - loss: 0.2644 - acc: 0.9688
 6/13 [============>.................] - ETA: 1:36 - loss: 0.2494 - acc: 0.9688
 7/13 [===============>..............] - ETA: 1:24 - loss: 0.2362 - acc: 0.9732
 8/13 [=================>............] - ETA: 1:10 - loss: 0.2234 - acc: 0.9766
 9/13 [===================>..........] - ETA: 58s - loss: 0.2154 - acc: 0.9757 
10/13 [======================>.......] - ETA: 44s - loss: 0.2062 - acc: 0.9781
11/13 [========================>.....] - ETA: 29s - loss: 0.2007 - acc: 0.9801
12/13 [==========================>...] - ETA: 14s - loss: 0.1990 - acc: 0.9792


13/13 [==============================] - 243s 19s/step - loss: 0.1923 - acc: 0.9809 - val_loss: 0.1929 - val_acc: 0.9300

4.6.7.2 數據集以及遷移需求

數據集是某場景下5個類別圖片的識別

我們利用現有的VGG模型去進行微調

4.6.7.3 思路和步驟

  • 讀取本地的圖片數據以及類別
    • keras.preprocessing.image import ImageDataGenerator提供了讀取轉換功能
  • 模型的結構修改(添加我們自定的分類層)
  • freeze掉原始VGG模型
  • 編譯以及訓練和保存模型方式
  • 輸入數據進行預測

4.6.7.4 訓練的時候讀取本地圖片以及類別

  • 基於上面工具的讀取代碼
train_datagen = ImageDataGenerator(
            rescale=1./255,
            shear_range=0.2,
            zoom_range=0.2,
            horizontal_flip=True)

test_datagen = ImageDataGenerator(rescale=1./255)

train_generator = train_datagen.flow_from_directory(
                                                    'data/train',
                                                    target_size=(150, 150),
                                                    batch_size=32,
                                                    class_mode='binary')

validation_generator = test_datagen.flow_from_directory(
                                                        'data/validation',
                                                        target_size=(150, 150),
                                                        batch_size=32,
                                                        class_mode='binary')
# 使用fit_generator
model.fit_generator(
train_generator,
steps_per_epoch=2000,
epochs=50,
validation_data=validation_generator,
validation_steps=800)

代碼:

首先導入包

import tensorflow as tf
from tensorflow import keras
from tensorflow.python.keras.preprocessing.image import ImageDataGenerator

我們定義一個遷移學習的類,然後進行相關屬性設置和讀取代碼

class TransferModel(object):

    def __init__(self):

        self.model_size = (224, 224)
        self.train_dir = "./data/train/"
        self.test_dir = "./data/test/"
        self.batch_size = 32

        self.train_generator = ImageDataGenerator(rescale=1.0 / 255)
        self.test_generator = ImageDataGenerator(rescale=1.0 / 255)

    def read_img_to_generator(self):
        """
        讀取本地固定格式數據
        :return:
        """
        train_gen = self.train_generator.flow_from_directory(directory=self.train_dir,
                                                             target_size=self.model_size,
                                                             batch_size=self.batch_size,
                                                             class_mode='binary',
                                                             shuffle=True)
        test_gen = self.test_generator.flow_from_directory(directory=self.test_dir,
                                                           target_size=self.model_size,
                                                           batch_size=self.batch_size,
                                                           class_mode='binary',
                                                           shuffle=True)
        return train_gen, test_gen

打印結果爲

<keras_preprocessing.image.DirectoryIterator object at 0x12f52cf28>

4.6.7.5 VGG模型的修改添加全連接層-GlobalAveragePooling2D

  • notop模型:
    • 是否包含最後的3個全連接層(whether to include the 3 fully-connected layers at the top of the network)。用來做fine-tuning專用,專門開源了這類模型。

‘weights='imagenet'’,意思是VGG在imagenet比賽中預訓練的權重,使用resnet訓練

# 在__init__中添加
self.base_model = VGG16(weights='imagenet', include_top=False)

base_model會有相關屬性,模型的輸入結構:inputs,模型的輸出結構,我們修改需要得到已有VGG的輸入和自定義模型的輸出構建成一個新的模型。

模型源碼:

    if include_top:
        # Classification block
        x = layers.Flatten(name='flatten')(x)
        x = layers.Dense(4096, activation='relu', name='fc1')(x)
        x = layers.Dense(4096, activation='relu', name='fc2')(x)
        x = layers.Dense(classes, activation='softmax', name='predictions')(x)
    else:
        if pooling == 'avg':
            x = layers.GlobalAveragePooling2D()(x)
        elif pooling == 'max':
            x = layers.GlobalMaxPooling2D()(x)
  • 一個GlobalAveragePooling2D + 兩個全連接層
    • 在圖像分類任務中,模型經過最後CNN層後的尺寸爲[bath_size, img_width, img_height, channels],通常的做法是:接一個flatten layer,將尺寸變爲[batch_size, w channels]再至少接一個FC layer,這樣做的最大問題是:模型參數多,且容易過擬合。
    • 利用pooling layer來替代最後的FC layer

解釋如下:

from keras.layers import Dense, Input, Conv2D
from keras.layers import MaxPooling2D, GlobalAveragePooling2D

x = Input(shape=[8, 8, 2048])  
# 假定最後一層CNN的層輸出爲(None, 8, 8, 2048)
x = GlobalAveragePooling2D(name='avg_pool')(x)  # shape=(?, 2048)
# 取每一個特徵圖的平均值作爲輸出,用以替代全連接層
x = Dense(1000, activation='softmax', name='predictions')(x)  # shape=(?, 1000)
# 1000爲類別
  • 5類圖片識別模型修改

我們需要拿到基礎VGG模型,並且VGG提供所有層參數訓練好的模型和沒有全連接層參數的模型notop模型

from tensorflow.python.keras import Model
def refine_vgg_model(self):
    """
        添加尾部全連接層
        :return:
        """
    # [<tf.Tensor 'block5_pool/MaxPool:0' shape=(?, ?, ?, 512) dtype=float32>]
    x = self.base_model.outputs[0]

    # 輸出到全連接層,加上全局池化 [None, ?, ?, 512]---->[None, 1 * 512]
    x = keras.layers.GlobalAveragePooling2D()(x)
    x = keras.layers.Dense(1024, activation=tf.nn.relu)(x)
    y_predict = keras.layers.Dense(5, activation=tf.nn.softmax)(x)

    model = keras.Model(inputs=self.base_model.inputs, outputs=y_predict)

    return model

4.6.7.6 freeze VGG模型結構

目的:讓VGG結構當中的權重參數不參與訓練,只訓練我們添加的最後兩層全連接網絡的權重參數

  • 通過使用每一層的layer.trainable=False
    def freeze_vgg_model(self):
        """
        freeze掉VGG的結構
        :return:
        """
        for layer in self.base_model.layers:
            layer.trainable = False

4.6.7.7 編譯和訓練

  • 編譯

同樣還是進行編譯, 在遷移學習中算法:學習率初始化較小的值,0.001,0.0001,因爲已經在已訓練好的模型基礎之上更新,所以不需要太大學習率去學習

    def compile(self, model):

        model.compile(optimizer=keras.optimizers.Adam(),
                      loss=keras.losses.sparse_categorical_crossentropy,
                      metrics=['accuracy'])
  • main函數
if __name__ == '__main__':
    tm = TransferModel()

    train_gen, test_gen = tm.read_img_to_generator()
    model = tm.refine_vgg_model()

    tm.freeze_vgg_model()
    tm.compile(model)
    tm.fit(model, train_gen, test_gen)

4.6.7.8 進行預測

預測的步驟就是讀取圖片以及處理到模型中預測,加載我們訓練的模型

    def predict(self, model):

        model.load_weights("./Transfer.h5")

        # 2、對圖片進行加載和類型修改
        image = load_img("./data/test/dinosaurs/402.jpg", target_size=(224, 224))

        print(image)
        # 轉換成numpy array數組
        image = img_to_array(image)
        print("圖片的形狀:", image.shape)

        # 形狀從3維度修改成4維
        img = image.reshape((1, image.shape[0], image.shape[1], image.shape[2]))
        print("改變形狀結果:", img.shape)

        # 3、處理圖像內容,歸一化處理等,進行預測
        img = preprocess_input(img)
        print(img.shape)
        y_predict = model.predict(img)

        index = np.argmax(y_predict, axis=1)

        print(self.label_dict[str(index[0])])

建立圖片類別的字典

self.label_dict = {
            '0': 'bus',
            '1': 'dinosaurs',
            '2': 'elephants',
            '3': 'flowers',
            '4': 'horse'
        }

4.6.6 總結

  • Checkpoint使用
  • TensorBoard使用
  • tf.data模塊使用
  • ImageDataGenerator的使用

keras_tool.py

import tensorflow as tf
import numpy as np
import os
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2"


def function_tool():

    (train_data, train_label), (_, _) = tf.keras.datasets.mnist.load_data()
    # [60000, 28, 28, 1]
    train_data = np.expand_dims(train_data.astype(np.float32) / 255.0, axis=-1)
    mnist_dataset = tf.data.Dataset.from_tensor_slices((train_data, train_label))

    # 1、翻轉處理
    def rot90(image, label):
        image = tf.image.rot90(image)
        return image, label

    mnist_dataset = mnist_dataset.map(rot90)
    mnist_dataset = mnist_dataset.batch(4)

    for images, labels in mnist_dataset:
        print(labels.numpy())
        print(images)


def _decode_and_resize(filename, label):
    """對於輸入的datset中圖片做解析
    :param filename: 圖片完整路徑
    :param label: 目標值
    :return:
    """
    image_string = tf.io.read_file(filename)
    image_decoded = tf.image.decode_jpeg(image_string)
    image_resized = tf.image.resize(image_decoded, [256, 256]) / 255.0
    return image_resized, label


# 1、數據集的獲取和構建
# 2、模型構建和封裝
# 3、訓練以及測試過程實現
class CatOrDog(object):
    """貓狗分類案例,使用tf.data讀取數據
    """
    num_epochs = 1
    batch_size = 32
    learning_rate = 0.001
    # 訓練目錄
    train_cats_dir = '/root/cv_project/tf_example/cats_vs_dogs/train/cats/'
    train_dogs_dir = '/root/cv_project/tf_example/cats_vs_dogs/train/dogs/'
    # 驗證目錄
    test_cats_dir = '/root/cv_project/tf_example/cats_vs_dogs/valid/cats/'
    test_dogs_dir = '/root/cv_project/tf_example/cats_vs_dogs/valid/dogs/'

    def __init__(self):
        # 1、數據集的獲取和構建
        # 讀取目錄下的文件名稱,把貓狗文件合併
        self.train_cat_filenames = tf.constant([CatOrDog.train_cats_dir + filename for
                                                filename in os.listdir(CatOrDog.train_cats_dir)])
        self.train_dog_filenames = tf.constant([CatOrDog.train_dogs_dir + filename for
                                                filename in os.listdir(CatOrDog.train_dogs_dir)])

        self.train_filenames = tf.concat([self.train_cat_filenames, self.train_dog_filenames], axis=-1)
        # 構造出貓0,狗1的目標值結果
        self.train_labels = tf.concat([tf.zeros(self.train_cat_filenames.shape, dtype=tf.int32),
                                      tf.ones(self.train_dog_filenames.shape, dtype=tf.int32)],
                                      axis=-1)

        self.model = tf.keras.Sequential([
            tf.keras.layers.Conv2D(32, 3, activation='relu', input_shape=(256, 256, 3)),
            tf.keras.layers.MaxPooling2D(),
            tf.keras.layers.Conv2D(32, 5, activation='relu'),
            tf.keras.layers.MaxPooling2D(),
            tf.keras.layers.Flatten(),
            tf.keras.layers.Dense(64, activation='relu'),
            tf.keras.layers.Dense(2, activation='softmax')
        ])

    def get_batch(self):
        """獲取dataset類型的訓練數據過程
        :return:
        """
        # from_tensor_slices
        train_dataset = tf.data.Dataset.from_tensor_slices((self.train_filenames, self.train_labels))
        # map, shuffle, batch, prefetch
        train_dataset = train_dataset.map(_decode_and_resize)
        train_dataset = train_dataset.shuffle(buffer_size=20000)
        train_dataset = train_dataset.batch(CatOrDog.batch_size)
        train_dataset = train_dataset.repeat(1)
        train_dataset = train_dataset.prefetch(tf.data.experimental.AUTOTUNE)
        return train_dataset

    def train(self, train_dataset):
        """訓練過程
        :return:
        """
        self.model.compile(
            optimizer=tf.keras.optimizers.Adam(learning_rate=CatOrDog.learning_rate),
            loss=tf.keras.losses.sparse_categorical_crossentropy,
            metrics=[tf.keras.metrics.sparse_categorical_accuracy]
        )

        self.model.fit(train_dataset)
        self.model.save_weights("./ckpt/cat_or_dogs.h5")

    def test(self):
        # 1、構建測試數據集
        test_cat_filenames = tf.constant([CatOrDog.test_cats_dir + filename
                                          for filename in os.listdir(CatOrDog.test_cats_dir)])
        test_dog_filenames = tf.constant([CatOrDog.test_dogs_dir + filename
                                          for filename in os.listdir(CatOrDog.test_dogs_dir)])
        test_filenames = tf.concat([test_cat_filenames, test_dog_filenames], axis=-1)
        test_labels = tf.concat([
            tf.zeros(test_cat_filenames.shape, dtype=tf.int32),
            tf.ones(test_dog_filenames.shape, dtype=tf.int32)],
            axis=-1)
        # 2、構建dataset
        test_dataset = tf.data.Dataset.from_tensor_slices((test_filenames, test_labels))
        test_dataset = test_dataset.map(_decode_and_resize)
        test_dataset = test_dataset.batch(CatOrDog.batch_size)

        # 3、加載模型進行評估
        if os.path.exists("./ckpt/cat_or_dogs.h5"):
            self.model.load_weights("./ckpt/cat_or_dogs.h5")

        print(self.model.metrics_names)
        print(self.model.evaluate(test_dataset))


if __name__ == '__main__':
    # function_tool()
    cod = CatOrDog()
    train_dataset = cod.get_batch()
    print(train_dataset)
    # cod.train(train_dataset)
    cod.test_v2()

tfrecords_example.py

import os
import tensorflow as tf
import os
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2"

train_cats_dir = './cats_vs_dogs/train/cats/'
train_dogs_dir = './cats_vs_dogs/train/dogs/'
tfrecord_file = './cats_vs_dogs/train.tfrecords'


def main():

    train_cat_filenames = [train_cats_dir + filename for filename in os.listdir(train_cats_dir)]
    train_dog_filenames = [train_dogs_dir + filename for filename in os.listdir(train_dogs_dir)]
    train_filenames = train_cat_filenames + train_dog_filenames
    # 將 cat 類的標籤設爲0,dog 類的標籤設爲1
    train_labels = [0] * len(train_cat_filenames) + [1] * len(train_dog_filenames)

    with tf.io.TFRecordWriter(tfrecord_file) as writer:
        for filename, label in zip(train_filenames, train_labels):
            # 1、讀取數據集圖片到內存,image 爲一個 Byte 類型的字符串
            image = open(filename, 'rb').read()
            # 2、建立 tf.train.Feature 字典
            feature = {
                'image': tf.train.Feature(bytes_list=tf.train.BytesList(value=[image])),  # 圖片是一個 Bytes 對象
                'label': tf.train.Feature(int64_list=tf.train.Int64List(value=[label]))  # 標籤是一個 Int 對象
            }
            # 3、通過字典建立 Example
            example = tf.train.Example(features=tf.train.Features(feature=feature))
            # 4\將Example序列化並寫入 TFRecord 文件
            writer.write(example.SerializeToString())


def read():

    # 1、讀取 TFRecord 文件
    raw_dataset = tf.data.TFRecordDataset(tfrecord_file)

    # 2、定義Feature結構,告訴解碼器每個Feature的類型是什麼
    feature_description = {
        'image': tf.io.FixedLenFeature([], tf.string),
        'label': tf.io.FixedLenFeature([], tf.int64),
    }

    # 3、將 TFRecord 文件中的每一個序列化的 tf.train.Example 解碼
    def _parse_example(example_string):
        feature_dict = tf.io.parse_single_example(example_string, feature_description)
        feature_dict['image'] = tf.io.decode_jpeg(feature_dict['image'])  # 解碼JPEG圖片
        return feature_dict['image'], feature_dict['label']

    dataset = raw_dataset.map(_parse_example)

    for image, label in dataset:
        print(image, label)


if __name__ == '__main__':
    # main()
    read()

model_serving.py

import tensorflow as tf
import os
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2"


def main():
    num_epochs = 1
    batch_size = 32
    learning_rate = 0.001

    model = tf.keras.models.Sequential([
        tf.keras.layers.Flatten(),
        tf.keras.layers.Dense(120, activation=tf.nn.relu),
        tf.keras.layers.Dense(100),
        tf.keras.layers.Softmax()
    ])

    (train, train_label), (test, test_label) = \
                tf.keras.datasets.cifar100.load_data()

    model.compile(
        optimizer=tf.keras.optimizers.Adam(learning_rate=learning_rate),
        loss=tf.keras.losses.sparse_categorical_crossentropy,
        metrics=[tf.keras.metrics.sparse_categorical_accuracy]
    )
    model.fit(train, train_label, epochs=num_epochs, batch_size=batch_size)

    tf.saved_model.save(model, "./saved/mlp/2")


def test():

    model = tf.saved_model.load("./saved/mlp/2")

    (_, _), (test, test_label) = \
        tf.keras.datasets.cifar100.load_data()

    y_predict = model(test)

    sparse_categorical_accuracy = tf.keras.metrics.SparseCategoricalAccuracy()
    sparse_categorical_accuracy.update_state(y_true=test_label, y_pred=y_predict)
    print("測試集的準確率爲: %f" % sparse_categorical_accuracy.result())


def client():
    import json
    import numpy as np
    import requests

    (_, _), (test, test_label) = \
        tf.keras.datasets.cifar100.load_data()

    # 1、構造好請求
    data = json.dumps({"instances": test[:10].tolist()})
    headers = {"content-type": "application/json"}

    json_response = requests.post('http://localhost:8501/v1/models/mlp:predict',
                                  data=data, headers=headers)

    predictions = np.array(json.loads(json_response.text)['predictions'])
    print(predictions)
    print(np.argmax(predictions, axis=-1))
    print(test_label[:10])


if __name__ == '__main__':
    # main()
    # test()
    client()

 

 

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