由於筆者水平有限,如有錯,歡迎指正。
起源
在CV領域中,卷積神經網絡大放異彩。而VGG與GoogLenet正是近些年較為熱門的結構。
VGG是Oxford的Visual Geometry Group 和 Google Deep MInd共同研發、提出的CNN經典模型之一。2014年,ILSVRC大賽中GoogLenet與VGG分別奪得冠亞軍。
VGG結構與LeNet及AlexNet有所相似,結構也比較簡單;而GoogLenet則引入了新的Inception網絡結構,性能更加優越,結構較為復雜。這兩種結構的成功也說明:用更多的卷積,更深的層次可以得到更好的結構。
在這里作為入門學習者,選擇先學習VGG。
VGG網絡結構
下圖為VGG不同版本的網絡模型,網絡結構大同小異,只有層數有所區分,接下來主要分析VGG16。

VGG16整體結構如圖所示:
總體由5層卷積層,3層全連接層組成,所有隱層的激活單元都采用ReLU函數。

對應圖中序號:
-
輸入3通道224x224彩色圖像;
-
64個3x3的卷積核作兩次卷積+ReLU,卷積后的尺寸變為224x224x64;
-
經過步長為2的maxpooling(圖像尺寸減半),得到112x112x64的尺寸,128個3x3的卷積核作兩次卷積+ReLU,尺寸變為112x112x128;
-
經過步長為2的maxpooling,尺寸變為56x56x128,256個3x3的卷積核作三次卷積+ReLU,尺寸變為56x56x256;
-
經過步長為2的maxpooling,尺寸變為28x28x256,512個3x3的卷積核作三次卷積+ReLU,尺寸變為28x28x512;
-
經過步長為2的maxpooling,尺寸變為14x14x512,512個3x3的卷積核作三次卷積+ReLU,尺寸變為14x14x512
-
經過步長為2的maxpooling,尺寸變為7x7x512;
-
Flatten層把輸入一維化,然后經過兩個1x1x4096全連接層,一個1x1x1000全連接層+ReLU;
-
通過softmax分類;
為什么使用多個3x3卷積合代替AlexNet中的較大卷積核?
(摘自其他博客)因為多個卷積層與非線性的激活層交替的結構,比單一卷積層的結構更能提取出深層的更好的特征。
假設所有的數據有C個通道,那么單獨的7x7卷積層將會包含7x7xC=49C2個參數,而3個3x3的卷積層的組合僅有個3x(3x3*C)=27C2個參數。
總之,最好選擇帶有小濾波器的卷積層組合,而不是用一個帶有大的濾波器的卷積層。前者可以表達出輸入數據中更多個強力特征,使用的參數也更少。唯一的不足是,在進行反向傳播時,中間的卷積層可能會導致占用更多的內存。( 圖源水印 )
![]()
特點:
- 結構簡潔,網絡結構很規整
- 小卷積核和多卷積子層,相較AlexNet性能提升
- 小池化層
- 模型更深更寬,隨着深度增加,分類性能逐漸提高
代碼實現:
model = Sequential()
model.add(Conv2D(64, (3, 3), input_ shape=input_ shape, padding='same', activation='relu'))
model.add(Conv2D(64, (3, 3), activation='relu', padding='same'))
model.add(MaxPooling2D(pool_ size=(2, 2), strides=(2, 2)))
model.add(Flatten())
model.add(Dense(4096, activation='relu'))
model.add(Dense(4096, activation='relu'))
model.add(Dense(1000, activation= 'softmax'))
keras框架下的pokemon圖像識別代碼:
from keras import layers
from keras import models
from keras import optimizers
from PIL import Image
from keras.preprocessing.image import ImageDataGenerator
import os
import matplotlib.pyplot as plt
import random
import shutil
import numpy as np
#讀取數據並預處理
#將jpg轉化為png
#將路徑定位到train的子文件夾
path = "/Users/apple/PycharmProjects/pokemon/data/train/"
print(os.listdir(path))
for name in os.listdir(path):
if name != '.DS_Store':
pic_list = os.path.join(path, name+'/')
for name in os.listdir(pic_list):
pic_name,pic_type = os.path.splitext(name) #os.path.spiltext 分離文件名和后
if pic_type == ".jpg":
pic = Image.open(pic_list+name)
pic.save(pic_list+"%s.png"%(pic_name)) #.save保存到原來路徑並改為.png
os.remove(pic_list+"%s.jpg"%(pic_name)) #os.remove 刪除之前的.jpg
#將路徑定位到test
pic2_list = "/Users/apple/PycharmProjects/pokemon/data/test/"
for name in os.listdir(pic2_list):
pic2_name, pic2_type = os.path.splitext(name) #os.path.spiltext 分離文件名和后
if pic2_type == ".jpg":
pic2 = Image.open(pic2_list+name)
pic2.save(pic2_list+"%s.png"%(pic2_name)) #.save保存到原來路徑並改為.png
os.remove(pic2_list+"%s.jpg"%(pic2_name)) #os.remove 刪除之前的.jpg
# In[ ]:
#從1050張抽取10%驗證集,手動新建val及其子文件夾
def moveFile(fileDir):
pathDir = os.listdir(fileDir) # 取圖片名
filenumber = len(pathDir)
rate = 0.1 # 抽取10%圖片
picknumber = int(filenumber * rate) # 取出的圖片總數
sample = random.sample(pathDir, picknumber) # 隨機選取的picknumber個圖片
for name in sample:
shutil.move(os.path.join(fileDir, name), os.path.join(fileDir2, name))
return
if __name__ == '__main__':
new_dir = '/Users/apple/desktop/pic/val/' # val的文件夾路徑
old_dir = '/Users/apple/desktop/pic/train/' #train的文件夾路徑
for firstPath, secondPath in zip(os.listdir(old_dir), os.listdir(new_dir)) :
#遍歷val train 各個標簽
if firstPath != '.DS_Store':
fileDir = os.path.join(old_dir, firstPath) # train當前標簽文件夾路徑
fileDir2 = os.path.join(new_dir, secondPath) # val當前標簽文件夾路徑
moveFile(fileDir)
# In[ ]:
#圖片生成器ImageDataGenerator
train_datagen = ImageDataGenerator(
rescale=1./255, #把像素值縮放到 [0, 1]
rotation_range=40, #圖像隨機旋轉的角度范圍
width_shift_range=0.2, #圖像在水平或垂直方向上平移的范圍
height_shift_range=0.2,)
#不能增強驗證數據
val_datagen = ImageDataGenerator(rescale=1./255) #把像素值縮放到 [0, 1]
# flow_from_directory 讀取路徑下圖片 同時處理數據(歸一化 統一尺寸)
train_generator = train_datagen.flow_from_directory(
'/Users/apple/PycharmProjects/pokemon/data/train',
target_size=(300, 300),
batch_size=20,
shuffle=True,
class_mode='categorical') #單標簽多分類
val_generator = val_datagen.flow_from_directory(
'/Users/apple/PycharmProjects/pokemon/data/val',
target_size=(300, 300),
batch_size=20,
shuffle=True,
class_mode='categorical')
#構建網絡
model = models.Sequential()
#第一個卷積層,32個卷積核,每個卷積核大小3*3,激活函數RELU
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(300, 300, 3)))
model.add(layers.MaxPooling2D((2, 2)))
#第二個卷積層,64個卷積核,每個卷積核大小3*3,添加一個 Dropout 層
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Dropout(0.5))
#第三個卷積層,128個卷積核,每個卷積核大小3*3。
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Dropout(0.5))
model.add(layers.Flatten())
model.add(layers.Dense(46, activation='relu'))
model.add(layers.Dense(5, activation='softmax')) #多分類、單標簽問題Softmax分類,5類別
#單標簽多分類問題,二元交叉熵作為損失函數,使用 RMSprop 優化器
model.compile(optimizer='rmsprop',
loss='categorical_crossentropy',
metrics=['accuracy'])
# In[ ]:
model = Sequential()
model.add(Conv2D(64, (3, 3), input_ shape=input_ shape, padding='same', activation='relu'))
model.add(Conv2D(64, (3, 3), activation='relu', padding='same'))
model.add(MaxPooling2D(pool_ size=(2, 2), strides=(2, 2)))
model.add(Flatten())
model.add(Dense(32, activation='relu'))
model.add(Dense(32, activation='relu'))
model.add(Dense(5, activation= 'softmax'))
# In[ ]:
model=Sequential()
model.add(Conv2D(64,(3,3),padding='same',input_shape=(size,size,3),activation='relu'))
model.add(Conv2D(64,(3,3),padding='same',activation='relu'))
model.add(MaxPooling2D((2,2),strides=2))
model.add(Conv2D(128,(3,3),padding='same',activation='relu'))
model.add(Conv2D(128,(3,3),padding='same',activation='relu'))
model.add(MaxPooling2D((2,2),strides=2))
model.add(Conv2D(256,(3,3),padding='same',activation='relu'))
model.add(Conv2D(256,(3,3),padding='same',activation='relu'))
model.add(MaxPooling2D((2,2),strides=2))
model.add(Conv2D(512,(3,3),padding='same',activation='relu'))
model.add(Conv2D(512,(3,3),padding='same',activation='relu'))
model.add(MaxPooling2D((2,2),strides=2))
model.add(Conv2D(512,(3,3),padding='same',activation='relu'))
model.add(Conv2D(512,(3,3),padding='same',activation='relu'))
model.add(MaxPooling2D((2,2),strides=2))
model.add(Flatten())
model.add(Dense(1024,activation='relu'))
model.add(Dense(1024,activation='relu'))
model.add(Dense(1024,activation='relu'))
model.add(Dense(5,activation='softmax'))
# In[ ]:
#訓練網絡
history = model.fit_generator(
train_generator,
epochs=20,
validation_data=val_generator)
history_dict = history.history
loss = history_dict['loss']
val_loss = history_dict['val_loss']
acc = history_dict['accuracy']
val_acc = history_dict['val_accuracy']
epochs = range(1,len(acc)+1)
plt.plot(epochs, loss, 'b', label='train_loss') #'訓練損失 藍色實線
plt.plot(epochs, val_loss, 'r', label='val_loss') #'驗證損失 紅色實線
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.show()
plt.plot(epochs, acc, 'b:', label='train_acc') #訓練精度 藍色虛線
plt.plot(epochs, val_acc, 'r:', label='val_acc') #驗證精度 紅色虛線
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend()
plt.show()