TensorFlow學習筆記01:使用tf.keras訓練模型
Keras入門案例
使用單神經元完成線性迴歸
使用Keras創建模型的過程爲:定義模型→編譯模型→訓練模型.以一個單神經元線性迴歸的例子演示如下:
xs = np.array([1, 2, 3, 4, 5, 6])
ys = np.array([1, 1.5, 2, 2.5, 3, 3.5])
model = tf.keras.Sequential([keras.layers.Dense(units=1, input_shape=[1])]) # 定義模型
model.compile(optimizer='sgd', loss='mean_squared_error') # 編譯模型
model.fit(xs, ys, epochs=500) # 訓練模型
model.predict([10.0]) # 得到 [5.5]
tf.keras.Sequential
類表示序列模型,多層神經元之間順序堆疊.
keras.layers.Dense
類表示全連接層,其主要屬性有:
units
: 該層的神經元個數.activation
: 激活函數,默認爲線性函數(也就是說沒有激活函數).input_shape
: 用在序列模型的第一層,表示輸入數據的形狀.
model.compile()
函數用於編譯模型,在編譯模型時指定了優化器和損失函數.
使用Keras搭建卷積神經網絡
例子1: 使用神經網絡進行圖片分類
獲取數據集
對fashion_mnist
數據集進行圖片分類,該數據集被包含在Keras的datasets
模塊中,獲取數據集的代碼如下:
# 獲取數據
mnist = tf.keras.datasets.fashion_mnist
(training_images, training_labels), (test_images, test_labels) = mnist.load_data()
# 展示數據
import matplotlib.pyplot as plt
plt.imshow(training_images[0])
print(training_labels[0])
print(training_images[0])
對於圖片數據的一個常規操作是將其歸一化,將每個點的像素值歸一化到[0, 1]
之間:
training_images = training_images.reshape(60000, 28, 28, 1)
test_images = test_images.reshape(10000, 28, 28, 1)
training_images = training_images / 255.0
test_images = test_images / 255.0
定義並訓練神經網絡
下面定義並訓練神經網絡模型進行圖片分類:
# 定義模型
model = tf.keras.models.Sequential([
tf.keras.layers.Conv2D(32, (3,3), activation='relu', input_shape=(28, 28, 1)),
tf.keras.layers.MaxPooling2D(2, 2),
tf.keras.layers.Conv2D(64, (3,3), activation='relu'),
tf.keras.layers.MaxPooling2D(2,2),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(128, activation='relu'),
tf.keras.layers.Dense(10, activation='softmax')
])
# 編譯模型
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
# 訓練模型,並將每輪訓練的歷史信息保存在變量 history 中
history = model.fit(x=training_images, y=training_labels, epochs=10, validation_data=(test_images, test_labels))
# 計算損失
test_loss = model.evaluate(test_images, test_labels)
上述神經網絡的結構如下:
調用model.summary()
查看模型的結構如下:
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d (Conv2D) (None, 26, 26, 32) 320
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 13, 13, 32) 0
_________________________________________________________________
conv2d_1 (Conv2D) (None, 11, 11, 64) 18496
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 5, 5, 64) 0
_________________________________________________________________
flatten (Flatten) (None, 1600) 0
_________________________________________________________________
dense (Dense) (None, 128) 204928
_________________________________________________________________
dense_1 (Dense) (None, 10) 1290
=================================================================
Total params: 225,034
Trainable params: 225,034
Non-trainable params: 0
_________________________________________________________________
定義回調函數
通過定義回調函數,可以增強訓練過程(比如在達到一定準確度後提前停止).
回調函數本質上是一個繼承自tf.keras.callbacks.Callback
的Python類,在這裏,們定義一個回調函數,在每輪訓練結束後檢查模型的準確率,若準確率大於99.8%則提前停止訓練.回調函數的epoch
參數代表當前訓練的輪數,logs
參數保存模型當前的一些指標信息.
class myCallback(tf.keras.callbacks.Callback):
def on_epoch_end(self, epoch, logs={}):
if (logs.get('acc')>0.998):
print('\nReached 99.8% accuracy so cancelling training!')
self.model.stop_training = True
callback = myCallback()
在調用fit()
方法向callbacks
參數傳入一個包含回調函數實例類的列表即可在訓練模型時調用回調函數.
callback = myCallback()
history = model.fit(x=training_images, y=training_labels, epochs=10,
callbacks=[callback], validation_data=(test_images, test_labels))
執行訓練過程,可以發現在第7輪訓練結束後達到了99.8%的準確度,訓練過程提前終止.
歷史訓練指標的可視化
我們將每輪訓練中的歷史指標的值保存在變量history
中,其history
屬性保存了每輪訓練的損失函數值以及其它的指標.
下面代碼獲取這些指標並繪製相應的曲線:
import matplotlib.pyplot as plt
# 獲取歷史信息
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(len(acc)) # 訓練輪數
# 繪製準確率曲線
plt.plot(epochs, acc, label=['acc'])
plt.plot(epochs, val_acc, label=['val_acc'])
plt.title('Training and validation accuracy')
plt.legend()
plt.figure()
# 繪製損失函數曲線
plt.plot(epochs, loss, label='loss')
plt.plot(epochs, val_loss, label='val_loss')
plt.legend()
plt.title('Training and validation loss')
各層的輸出的可視化
使用下面代碼對上述神經網絡各卷積層進行可視化
import matplotlib.pyplot as plt
f, axarr = plt.subplots(3,4)
FIRST_IMAGE=0
SECOND_IMAGE=7
THIRD_IMAGE=26
CONVOLUTION_NUMBER = 1
from tensorflow.keras import models
layer_outputs = [layer.output for layer in model.layers]
activation_model = tf.keras.models.Model(inputs = model.input, outputs = layer_outputs)
for x in range(0,4):
f1 = activation_model.predict(test_images[FIRST_IMAGE].reshape(1, 28, 28, 1))[x]
axarr[0,x].imshow(f1[0, : , :, CONVOLUTION_NUMBER], cmap='inferno')
axarr[0,x].grid(False)
f2 = activation_model.predict(test_images[SECOND_IMAGE].reshape(1, 28, 28, 1))[x]
axarr[1,x].imshow(f2[0, : , :, CONVOLUTION_NUMBER], cmap='inferno')
axarr[1,x].grid(False)
f3 = activation_model.predict(test_images[THIRD_IMAGE].reshape(1, 28, 28, 1))[x]
axarr[2,x].imshow(f3[0, : , :, CONVOLUTION_NUMBER], cmap='inferno')
axarr[2,x].grid(False)
例子2: 使用實際圖片進行訓練
在前面的例子中,我們一直是對現成的圖片數據集進行訓練,這些圖片已經被裁剪和標註好.在這個例子中,我們使用tensorflow.keras.preprocessing.image.ImageDataGenerator
類對實際圖片進行標註和數據擴充(data augmentation).
獲取數據集
在這裏,我們使用kaggle的貓狗大戰數據集模擬實際圖片數據,獲取數據的步驟如下:
-
使用
wget
命令下載數據集!wget --no-check-certificate \ https://storage.googleapis.com/mledu-datasets/cats_and_dogs_filtered.zip \ -O /tmp/cats_and_dogs_filtered.zip
-
解壓壓縮包
import os import zipfile local_zip = '/tmp/cats_and_dogs_filtered.zip' zip_ref = zipfile.ZipFile(local_zip, 'r') zip_ref.extractall('/tmp') zip_ref.close()
經過上述操作,我們將數據存入/tmp/cats_and_dogs_filtered/
目錄下,該目錄下的文件結構樹如下:
/tmp/cats_and_dogs_filtered
├── train
│ ├── cats
│ │ ├── cat.0.jpg
│ │ ├── cat.1.jpg
│ │ ├── cat.2.jpg
│ │ ├── ...
│ │ └── cat.999.jpg
│ └── dogs
│ ├── dog.0.jpg
│ ├── dog.1.jpg
│ ├── dog.2.jpg
│ ├── ...
│ └── dog.999.jpg
├── validation
│ ├── cats
│ │ ├── cat.2000.jpg
│ │ ├── cat.2001.jpg
│ │ ├── ...
│ │ └── cat.2499.jpg
│ └── dogs
│ ├── dog.2000.jpg
│ ├── dog.2001.jpg
│ ├── ...
│ └── dog.2499.jpg
└── vectorize.py
其中部分數據如下:
構造ImageDataGenerator
ImageDataGenerator
通過將圖片文件打標籤爲圖片文件的上一層目錄名.在這裏要注意,對於下面的目錄結構,生成數據流的directory
參數應爲Training
和Validation
目錄而非Images
目錄.ImageDataGenerator
會自動爲我們給圖片標上Horses
或Humans
標籤.
通過向ImageDataGenerator
構造函數傳入一系列參數,可以實現數據擴充,在這一步中,我們暫時先只定義rescale
參數對數值進行縮放,而不做數據擴充.
我們使用flow_from_directory()
方法從目錄中生成數據流,代碼如下:
值得注意的是,flow_from_directory()
方法的class_mode
表示當前的分類任務的類別數量.可選值有'binary'
(表示二分類,使用0-1編碼)和'categorical'
(表示多分類,使用one-hot編碼).
# 定義目錄路徑
base_dir = '/tmp/cats_and_dogs_filtered'
train_dir = os.path.join(base_dir, 'train')
validation_dir = os.path.join(base_dir, 'validation')
train_cats_dir = os.path.join(train_dir, 'cats')
train_dogs_dir = os.path.join(train_dir, 'dogs')
validation_cats_dir = os.path.join(validation_dir, 'cats')
validation_dogs_dir = os.path.join(validation_dir, 'dogs')
# 創建 ImageDataGenerator
train_datagen = ImageDataGenerator(rescale=1./255)
test_datagen = ImageDataGenerator(rescale=1./255)
# 從目錄生成數據流
train_generator = train_datagen.flow_from_directory(
directory=train_dir, # 指定數據源的目錄
target_size=(150, 150), # 將圖片縮放到 target_size 的尺寸
batch_size=20, # 設置每批數據的個數
class_mode='binary' # 二分類,生成的標籤使用0-1編碼而非one-hot編碼
)
validation_generator = test_datagen.flow_from_directory(
directory=validation_dir,
target_size=(150, 150),
batch_size=20,
class_mode='binary'
)
定義並訓練神經網絡
在這裏,我們從Generator
而非矩陣中獲取數據,因此應使用fit_generator()
方法而非fit()
方法訓練模型.
model = tf.keras.models.Sequential([
tf.keras.layers.Conv2D(32, (3,3), activation='relu', input_shape=(150, 150, 3)),
tf.keras.layers.MaxPooling2D(2, 2),
tf.keras.layers.Conv2D(64, (3,3), activation='relu'),
tf.keras.layers.MaxPooling2D(2,2),
tf.keras.layers.Conv2D(128, (3,3), activation='relu'),
tf.keras.layers.MaxPooling2D(2,2),
tf.keras.layers.Conv2D(128, (3,3), activation='relu'),
tf.keras.layers.MaxPooling2D(2,2),
tf.keras.layers.Dropout(0.5),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(512, activation='relu'),
tf.keras.layers.Dense(1, activation='sigmoid')
])
model.compile(loss='binary_crossentropy',optimizer='adam',metrics=['acc'])
# 使用 fit_generator方法 訓練模型
history = model.fit_generator(
train_generator,
steps_per_epoch=100, # 2000 images = batch_size * steps
epochs=100,
validation_data=validation_generator,
validation_steps=50, # 1000 images = batch_size * steps
)
fit_generator()
方法的steps_per_epoch
和validation_steps
參數分別指定每輪訓練和驗證時生成的數據批數.應儘量滿足(),這樣保證了每輪訓練(驗證)中幾乎所有樣本都被訓練(驗證)了一次.
畫出訓練過程中各指標的曲線如下,可以看到,發生了過擬合.這是因爲訓練集過小,需要我們進行數據擴充:
對原圖像進行數據擴充
通過向ImageDataGenerator
構造函數傳入一系列參數,可以實現數據擴充,具體的參數信息可以查看Keras官方文檔.
下面代碼演示構造ImageDataGenerator
對訓練集進行數據擴充:
# 創建ImageDataGenerator是傳入參數進行數據擴充
train_datagen = ImageDataGenerator(
rescale=1./255,
rotation_range=40,
width_shift_range=0.2,
height_shift_range=0.2,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True,
fill_mode='nearest'
)
test_datagen = ImageDataGenerator(rescale=1./255)
使用進行了數據擴充的ImageDataGenerator
進行訓練後,得到的歷史指標曲線如下.可以看到,進行數據擴充有效地克服了過擬合現象.
例子3: 使用遷移學習(Transfer Learning)複用原有模型
在這個例子中,我們基於現有的InceptionV3
網絡構建我們自己的貓狗識別網絡.模型的搭建過程分爲以下幾步:
-
導入訓練好的的
InceptionV3
網絡:在Keras中內置了
InceptionV3
網絡模型,我們可以輕鬆導入它:from tensorflow.keras.applications.inception_v3 import InceptionV3 pre_trained_model = InceptionV3( input_shape = (150, 150, 3), include_top = False, # 不包含網絡頂端的全連接層 weights = None # 我們會從網絡上下載訓練好的網絡參數 )
從網上下載訓練好的網絡參數:
wget --no-check-certificate \ https://storage.googleapis.com/mledu-datasets/inception_v3_weights_tf_dim_ordering_tf_kernels_notop.h5 \ -O /tmp/inception_v3_weights_tf_dim_ordering_tf_kernels_notop.h5
將該參數賦給網絡:
local_weights_file = '/tmp/inception_v3_weights_tf_dim_ordering_tf_kernels_notop.h5' pre_trained_model.load_weights(local_weights_file)
這樣,我們就構建好了一個訓練好的
InceptionV3
網絡. -
鎖定網絡的參數,這樣我們在訓練時不會更新對應層上的網絡權重:
for layer in pre_trained_model.layers: layer.trainable = False
-
基於
pre_trained_model
網絡構建自己的新網絡:# 定位原有網絡的某一層 last_layer = pre_trained_model.get_layer('mixed7') last_output = last_layer.output # 在該層基礎上添加新層構建網絡 x = layers.Flatten()(last_output) x = layers.Dense(1024, activation='relu')(x) x = layers.Dropout(0.2)(x) x = layers.Dense (1, activation='sigmoid')(x) # 將對應的層次封裝爲新的網絡模型 model = Model(input=pre_trained_model.input, output=x)
經過上面三步,我們基於InceptionV3
網絡構建了我們的新網絡model
,且原本來源於InceptionV3
層的網絡參數都被鎖定,在訓練過程中不會被更新,這樣大大降低了模型訓練的運算量.
剩下的對於model
的操作過程與之前的例子中幾乎一樣.