【神经网络】(6) 卷积神经网络(VGG16),案例:鸟类图片4分类
各位同學(xué)好,今天和大家分享一下TensorFlow2.0中的VGG16卷積神經(jīng)網(wǎng)絡(luò)模型,案例:現(xiàn)在有四種鳥類的圖片各200張,構(gòu)建卷積神經(jīng)網(wǎng)絡(luò),預(yù)測圖片屬于哪個分類。
1. 數(shù)據(jù)加載
將鳥類圖片按類分開存放,使用tf.keras.preprocessing.image_dataset_from_directory()函數(shù)分批次讀取圖片數(shù)據(jù),統(tǒng)一指定圖片加載進(jìn)來的大小224*224,指定參數(shù)label_model,'int'代表目標(biāo)值y是數(shù)值類型,即0, 1, 2, 3等;'categorical'代表onehot類型,對應(yīng)索引的值為1,如圖像屬于第二類則表示為0,1,0,0,0;'binary'代表二分類。
import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import Sequential, optimizers, layers, Model#(1)數(shù)據(jù)加載
def get_data(height, width, batchsz):# 獲取訓(xùn)練集數(shù)據(jù)filepath1 = 'C:/Users/admin/.spyder-py3/test/數(shù)據(jù)集/4種鳥分類/new_data/train'train_ds = tf.keras.preprocessing.image_dataset_from_directory(filepath1, # 指定訓(xùn)練集數(shù)據(jù)路徑label_mode = 'categorical', # 進(jìn)行onehot編碼image_size = (height, width), # 對圖像risizebatch_size = batchsz, # 每次迭代取32個數(shù)據(jù) )# 獲取驗(yàn)證集數(shù)據(jù)filepath2 = 'C:/Users/admin/.spyder-py3/test/數(shù)據(jù)集/4種鳥分類/new_data/val'val_ds = tf.keras.preprocessing.image_dataset_from_directory(filepath1, # 指定訓(xùn)練集數(shù)據(jù)路徑label_mode = 'categorical', image_size = (height, width), # 對圖像risizebatch_size = batchsz, # 每次迭代取32個數(shù)據(jù) ) # 獲取測試集數(shù)據(jù)filepath2 = 'C:/Users/admin/.spyder-py3/test/數(shù)據(jù)集/4種鳥分類/new_data/test'test_ds = tf.keras.preprocessing.image_dataset_from_directory(filepath1, # 指定訓(xùn)練集數(shù)據(jù)路徑label_mode = 'categorical', image_size = (height, width), # 對圖像risizebatch_size = batchsz, # 每次迭代取32個數(shù)據(jù) ) # 返回?cái)?shù)據(jù)集return train_ds, val_ds, test_ds# 數(shù)據(jù)讀取函數(shù),返回訓(xùn)練集、驗(yàn)證集、測試集
train_ds, val_ds, test_ds = get_data(height=224, width=224, batchsz=32)
# 查看有哪些分類
class_names = train_ds.class_names
print('類別有:', class_names)
# 類別有: ['Bananaquit', 'Black Skimmer', 'Black Throated Bushtiti', 'Cockatoo']# 查看數(shù)據(jù)集信息
sample = next(iter(train_ds)) #每次取出一個batch的訓(xùn)練數(shù)據(jù)
print('x_batch.shape:', sample[0].shape, 'y_batch.shape:',sample[1].shape)
# x_batch.shape: (128, 224, 224, 3) y_batch.shape: (128, 4)
print('y[:5]:', sample[1][:5]) # 查看前五個目標(biāo)值
2. 數(shù)據(jù)預(yù)處理
使用.map()函數(shù)轉(zhuǎn)換數(shù)據(jù)集中所有x和y的類型,并將每張圖象的像素值映射到[-1,1]之間,打亂訓(xùn)練集數(shù)據(jù)的順序.shuffle(),但不改變特征值x和標(biāo)簽值y之間的對應(yīng)關(guān)系。iter()生成迭代器,配合next()每次運(yùn)行取出訓(xùn)練集中的一個batch數(shù)據(jù)。
#(2)顯示圖像
import matplotlib.pyplot as plt
for i in range(15):plt.subplot(3,5,i+1)plt.imshow(sample[0][i]/255.0) # sample[0]代表取出的一個batch的所有圖像信息,映射到[0,1]之間顯示圖像plt.xticks([]) # 不顯示xy軸坐標(biāo)刻度plt.yticks([])
plt.show()#(3)數(shù)據(jù)預(yù)處理
# 定義預(yù)處理函數(shù)
def processing(x, y):x = 2 * tf.cast(x, dtype=tf.float32)/255.0 - 1 #映射到[-1,1]之間y = tf.cast(y, dtype=tf.int32) # 轉(zhuǎn)換數(shù)據(jù)類型return x,y# 對所有數(shù)據(jù)集預(yù)處理
train_ds = train_ds.map(processing).shuffle(10000)
val_ds = val_ds.map(processing)
test_ds = test_ds.map(processing)# 再次查看數(shù)據(jù)信息
sample = next(iter(train_ds)) #每次取出一個batch的訓(xùn)練數(shù)據(jù)
print('x_batch.shape:', sample[0].shape, 'y_batch.shape:',sample[1].shape)
# x_batch.shape: (128, 224, 224, 3) y_batch.shape: (128, 4)
print('y[:5]:', sample[1][:5]) # 查看前五個目標(biāo)值
# [[0 0 1 0], [1 0 0 0], [0 1 0 0], [0 1 0 0], [1 0 0 0]]
鳥類圖像如下:
3. VGG16網(wǎng)絡(luò)構(gòu)造
VGG16的模型框架如下圖所示,原理見下文:深度學(xué)習(xí)-VGG16原理詳解?。
1)輸入圖像尺寸為224x224x3,經(jīng)64個通道為3的3x3的卷積核,步長為1,padding=same填充,卷積兩次,再經(jīng)ReLU激活,輸出的尺寸大小為224x224x64
2)經(jīng)max pooling(最大化池化),濾波器為2x2,步長為2,圖像尺寸減半,池化后的尺寸變?yōu)?12x112x64
3)經(jīng)128個3x3的卷積核,兩次卷積,ReLU激活,尺寸變?yōu)?12x112x128
4)max pooling池化,尺寸變?yōu)?6x56x128
5)經(jīng)256個3x3的卷積核,三次卷積,ReLU激活,尺寸變?yōu)?6x56x256
6)max pooling池化,尺寸變?yōu)?8x28x256
7)經(jīng)512個3x3的卷積核,三次卷積,ReLU激活,尺寸變?yōu)?8x28x512
8)max pooling池化,尺寸變?yōu)?4x14x512
9)經(jīng)512個3x3的卷積核,三次卷積,ReLU,尺寸變?yōu)?4x14x512
10)max pooling池化,尺寸變?yōu)?x7x512
11)然后Flatten(),將數(shù)據(jù)拉平成向量,變成一維51277=25088。
11)再經(jīng)過兩層1x1x4096,一層1x1x1000的全連接層(共三層),經(jīng)ReLU激活
12)最后通過softmax輸出1000個預(yù)測結(jié)果
?
下面通過代碼來實(shí)現(xiàn),這里我們需要的是4分類,因此把最后的1000個預(yù)測結(jié)果改為4既可。
#(4)構(gòu)建CNN-VGG16
def VGG16(input_shape=(224,224,3), output_shape=4):# 輸入層input_tensor = keras.Input(shape=input_shape)# unit1# 卷積層x = layers.Conv2D(64, (3,3), activation='relu', strides=1, padding='same')(input_tensor) # [224,224,64]# 卷積層x = layers.Conv2D(64, (3,3), activation='relu' , strides=1, padding='same')(x) #[224,224,64]# 池化層,size變成1/2x = layers.MaxPool2D(pool_size=(2,2), strides=(2,2))(x) #[112,112,64]# unit2# 卷積層x = layers.Conv2D(128, (3,3), activation='relu', strides=1, padding='same')(x) #[112,112,128]# 卷積層x = layers.Conv2D(128, (3,3), activation='relu', strides=1, padding='same')(x) #[112,112,128]# 池化層x = layers.MaxPool2D(pool_size=(2,2), strides=(2,2))(x) #[56,56,128]# unit3# 卷積層x = layers.Conv2D(256, (3,3), activation='relu', strides=1, padding='same')(x) #[56,56,256]# 卷積層x = layers.Conv2D(256, (3,3), activation='relu', strides=1, padding='same')(x) #[56,56,256]# 卷積層x = layers.Conv2D(256, (3,3), activation='relu', strides=1, padding='same')(x) #[56,56,256]# 池化層x = layers.MaxPool2D(pool_size=(2,2), strides=(2,2))(x) #[28,28,256]# unit4# 卷積層x = layers.Conv2D(512, (3,3), activation='relu', strides=1, padding='same')(x) #[28,28,512]# 卷積層x = layers.Conv2D(512, (3,3), activation='relu', strides=1, padding='same')(x) #[28,28,512] # 卷積層x = layers.Conv2D(512, (3,3), activation='relu', strides=1, padding='same')(x) #[28,28,512]# 池化層x = layers.MaxPool2D(pool_size=(2,2), strides=(2,2))(x) #[14,14,512]# unit5# 卷積層x = layers.Conv2D(512, (3,3), activation='relu', strides=1, padding='same')(x) #[14,14,512]# 卷積層x = layers.Conv2D(512, (3,3), activation='relu', strides=1, padding='same')(x) #[14,14,512]# 卷積層x = layers.Conv2D(512, (3,3), activation='relu', strides=1, padding='same')(x) #[14,14,512]# 池化層x = layers.MaxPool2D(pool_size=(2,2), strides=(2,2))(x) #[7,7,512]# uint6# Flatten層x = layers.Flatten()(x) #壓平[None,4096]# 全連接層x = layers.Dense(4096, activation='relu')(x) #[None,4096]# 全連接層x = layers.Dense(4096, activation='relu')(x) #[None,4096]# 輸出層,輸出結(jié)果不做softmaxoutput_tensor = layers.Dense(output_shape)(x) #[None,4]# 構(gòu)建模型model = Model(inputs=input_tensor, outputs=output_tensor)# 返回模型return model# 構(gòu)建VGG16模型
model = VGG16()
# 查看模型結(jié)構(gòu)
model.summary()
該網(wǎng)絡(luò)構(gòu)架如下
Model: "model"
_________________________________________________________________Layer (type) Output Shape Param #
=================================================================input_1 (InputLayer) [(None, 224, 224, 3)] 0 conv2d (Conv2D) (None, 224, 224, 64) 1792 conv2d_1 (Conv2D) (None, 224, 224, 64) 36928 max_pooling2d (MaxPooling2D (None, 112, 112, 64) 0 ) conv2d_2 (Conv2D) (None, 112, 112, 128) 73856 conv2d_3 (Conv2D) (None, 112, 112, 128) 147584 max_pooling2d_1 (MaxPooling (None, 56, 56, 128) 0 2D) conv2d_4 (Conv2D) (None, 56, 56, 256) 295168 conv2d_5 (Conv2D) (None, 56, 56, 256) 590080 conv2d_6 (Conv2D) (None, 56, 56, 256) 590080 max_pooling2d_2 (MaxPooling (None, 28, 28, 256) 0 2D) conv2d_7 (Conv2D) (None, 28, 28, 512) 1180160 conv2d_8 (Conv2D) (None, 28, 28, 512) 2359808 conv2d_9 (Conv2D) (None, 28, 28, 512) 2359808 max_pooling2d_3 (MaxPooling (None, 14, 14, 512) 0 2D) conv2d_10 (Conv2D) (None, 14, 14, 512) 2359808 conv2d_11 (Conv2D) (None, 14, 14, 512) 2359808 conv2d_12 (Conv2D) (None, 14, 14, 512) 2359808 max_pooling2d_4 (MaxPooling (None, 7, 7, 512) 0 2D) flatten (Flatten) (None, 25088) 0 dense (Dense) (None, 4096) 102764544 dense_1 (Dense) (None, 4096) 16781312 dense_2 (Dense) (None, 4) 16388 =================================================================
Total params: 134,276,932
Trainable params: 134,276,932
Non-trainable params: 0
_________________________________________________________________
4. 網(wǎng)絡(luò)編譯
在網(wǎng)絡(luò)編譯時.compile(),指定損失loss采用交叉熵?fù)p失,設(shè)置參數(shù)from_logits=True,由于網(wǎng)絡(luò)的輸出層沒有使用softmax函數(shù)將輸出的實(shí)數(shù)轉(zhuǎn)為概率,參數(shù)設(shè)置為True時,會自動將logits的實(shí)數(shù)轉(zhuǎn)為概率值,再和真實(shí)值計(jì)算損失,這里的真實(shí)值y是經(jīng)過onehot編碼之后的結(jié)果。
#(5)模型配置
# 設(shè)置優(yōu)化器
opt = optimizers.Adam(learning_rate=1e-4) # 學(xué)習(xí)率model.compile(optimizer=opt, #學(xué)習(xí)率loss=keras.losses.CategoricalCrossentropy(from_logits=True), #損失metrics=['accuracy']) #評價指標(biāo)# 訓(xùn)練,給定訓(xùn)練集、驗(yàn)證集
history = model.fit(train_ds, validation_data=val_ds, epochs=30) #迭代30次#(6)循環(huán)結(jié)束后繪制損失和準(zhǔn)確率的曲線
# ==1== 準(zhǔn)確率
train_acc = history.history['accuracy'] #訓(xùn)練集準(zhǔn)確率
val_acc = history.history['val_accuracy'] #驗(yàn)證集準(zhǔn)確率
# ==2== 損失
train_loss = history.history['loss'] #訓(xùn)練集損失
val_loss = history.history['val_loss'] #驗(yàn)證集損失
# ==3== 繪圖
epochs_range = range(len(train_acc))
plt.figure(figsize=(10,5))
# 準(zhǔn)確率
plt.subplot(1,2,1)
plt.plot(epochs_range, train_acc, label='train_acc')
plt.plot(epochs_range, val_acc, label='val_acc')
plt.legend()
# 損失曲線
plt.subplot(1,2,2)
plt.plot(epochs_range, train_loss, label='train_loss')
plt.plot(epochs_range, val_loss, label='val_loss')
plt.legend()
5. 結(jié)果展示
如圖可見網(wǎng)絡(luò)效果預(yù)測較好,在迭代至25次左右時網(wǎng)絡(luò)準(zhǔn)確率達(dá)到99%左右,如果迭代次數(shù)較多的話,可考慮在編譯時使用early stopping保存最優(yōu)權(quán)重,若后續(xù)網(wǎng)絡(luò)效果都沒有提升就可以提早停止網(wǎng)絡(luò),節(jié)約訓(xùn)練時間。
訓(xùn)練過程中的損失和準(zhǔn)確率如下
Epoch 1/30
13/13 [==============================] - 7s 293ms/step - loss: 1.3627 - accuracy: 0.3116 - val_loss: 1.3483 - val_accuracy: 0.5075
Epoch 2/30
13/13 [==============================] - 3s 173ms/step - loss: 1.1267 - accuracy: 0.5251 - val_loss: 1.0235 - val_accuracy: 0.5226
------------------------------------------------------------------------------------------
省略N行
------------------------------------------------------------------------------------------
Epoch 26/30
13/13 [==============================] - 2s 174ms/step - loss: 0.1184 - accuracy: 0.9874 - val_loss: 0.1093 - val_accuracy: 0.9774
Epoch 27/30
13/13 [==============================] - 2s 174ms/step - loss: 0.3208 - accuracy: 0.9196 - val_loss: 0.2678 - val_accuracy: 0.9347
Epoch 28/30
13/13 [==============================] - 2s 172ms/step - loss: 0.2366 - accuracy: 0.9322 - val_loss: 0.1247 - val_accuracy: 0.9648
Epoch 29/30
13/13 [==============================] - 3s 173ms/step - loss: 0.1027 - accuracy: 0.9648 - val_loss: 0.0453 - val_accuracy: 0.9849
Epoch 30/30
13/13 [==============================] - 3s 171ms/step - loss: 0.0491 - accuracy: 0.9849 - val_loss: 0.0250 - val_accuracy: 0.9925
6. 其他方法
如果想更靈活的計(jì)算損失和準(zhǔn)確率,可以不使用.compile(),.fit()函數(shù)。在模型構(gòu)建完之后,自己敲一下代碼實(shí)現(xiàn)前向傳播,同樣能實(shí)現(xiàn)模型訓(xùn)練效果。下面的代碼可以代替第4小節(jié)中的第(5)步
# 指定優(yōu)化器
optimizer = optimizers.Adam(learning_rate=1e-5)
# 記錄訓(xùn)練和測試過程中的每個batch的準(zhǔn)確率和損失
train_acc = []
train_loss = []
val_acc = []
val_loss = []# 大循環(huán)
for epochs in range(30): #循環(huán)30次train_total_sum=0train_total_loss=0train_total_correct=0val_total_sum=0val_total_loss=0val_total_correct=0 #(5)網(wǎng)絡(luò)訓(xùn)練for step, (x,y) in enumerate(train_ds): #每次從訓(xùn)練集中取出一個batch# 梯度跟蹤with tf.GradientTape() as tape:# 前向傳播logits = model(x) # 輸出屬于每個分類的實(shí)數(shù)值# 計(jì)算準(zhǔn)確率prob = tf.nn.softmax(logits, axis=1) # 計(jì)算概率predict = tf.argmax(prob, axis=1, output_type=tf.int32) # 概率最大值的下標(biāo)correct = tf.cast(tf.equal(predict, y), dtype=tf.int32) # 對比預(yù)測值和真實(shí)值,將結(jié)果從布爾類型轉(zhuǎn)變?yōu)?和0correct = tf.reduce_sum(correct) # 計(jì)算一共預(yù)測對了幾個total = x.shape[0] # 每次迭代有多少參與進(jìn)來train_total_sum += total #記錄一共有多少個樣本參與了循環(huán)train_total_correct += correct #記錄一整次循環(huán)下來預(yù)測對了幾個acc = correct/total # 每一個batch的準(zhǔn)確率train_acc.append(acc) # 將每一個batch的準(zhǔn)確率保存下來# 計(jì)算損失y = tf.one_hot(y, depth=4) # 對真實(shí)值進(jìn)行onehot編碼,分為4類loss = tf.losses.categorical_crossentropy(y, logits, from_logits=True) # 將預(yù)測值放入softmax種再計(jì)算損失# 求每個batch的損失均值loss_avg = tf.reduce_mean(loss, axis=1)# 記錄總損失train_total_loss += tf.reduce_sum(loss) #記錄每個batch的損失 # 梯度計(jì)算,因變量為loss損失,自變量為模型中所有的權(quán)重和偏置grads = tape.gradient(loss_avg, model.trainable_variables)# 梯度更新,對所有的權(quán)重和偏置更新梯度optimizer.apply_gradients(zip(grads, model.trainable_variables))# 每20個batch打印一次損失和準(zhǔn)確率if step%20 == 0:print('train', 'step:', step, 'loss:', loss_avg, 'acc:', acc)# 記錄每次循環(huán)的損失和準(zhǔn)確率train_acc.append(train_total_correct/train_total_sum) # 總預(yù)測對的個數(shù)除以總個數(shù),平均準(zhǔn)確率train_loss.append(train_total_loss/train_total_sum) # 總損失處于總個數(shù),得平均損失#(6)網(wǎng)絡(luò)測試for step, (x, y) in enumerate(val_ds): #每次取出一個batch的驗(yàn)證數(shù)據(jù)# 前向傳播logits = model(x)# 計(jì)算準(zhǔn)確率prob = tf.nn.softmax(logits, axis=1) # 計(jì)算概率predict = tf.argmax(prob, axis=1, output_type=tf.int32) # 概率最大值的下標(biāo)correct = tf.cast(tf.equal(predict, y), dtype=tf.int32) # 對比預(yù)測值和真實(shí)值,將結(jié)果從布爾類型轉(zhuǎn)變?yōu)?和0correct = tf.reduce_sum(correct) # 計(jì)算一共預(yù)測對了幾個val_total_correct += correct # 計(jì)算整個循環(huán)預(yù)測對了幾個total = x.shape[0] # 每次迭代有多少參與進(jìn)來val_total_sum += total # 整個循環(huán)有多少參與進(jìn)來acc = correct/total # 每一個batch的準(zhǔn)確率val_acc.append(acc) # 將每一個batch的準(zhǔn)確率保存下來# 計(jì)算損失y = tf.one_hot(y, depth=4) # 對真實(shí)值進(jìn)行onehot編碼,分為4類loss = tf.losses.categorical_crossentropy(y, logits, from_logits=True) # 將預(yù)測值放入softmax種再計(jì)算損失# 求每個batch的損失均值loss_avg = tf.reduce_mean(loss, axis=1)# 記錄總損失val_total_loss += tf.reduce_sum(loss) # 每10個btch打印一次準(zhǔn)確率和損失if step%10 == 0:print('val', 'step:', step, 'loss:', loss_avg, 'acc:', acc)# 記錄每次循環(huán)的損失和準(zhǔn)確率val_acc.append(val_total_correct/val_total_sum) # 總預(yù)測對的個數(shù)除以總個數(shù),平均準(zhǔn)確率val_loss.append(val_total_loss/val_total_sum) # 總損失處于總個數(shù),得平均損失
總結(jié)
以上是生活随笔為你收集整理的【神经网络】(6) 卷积神经网络(VGG16),案例:鸟类图片4分类的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【神经网络】(4) 卷积神经网络(CNN
- 下一篇: 【神经网络】(7) 迁移学习(CNN-M