CNN经典网络——VGG简析

由于笔者水平有限,如有错,欢迎指正。

论文原文: https://arxiv.org/pdf/1409.1556.pdf

起源


在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函数。


对应图中序号:
  1. 输入3通道224x224彩色图像;

  2. 64个3x3的卷积核作两次卷积+ReLU,卷积后的尺寸变为224x224x64;

  3. 经过步长为2的maxpooling(图像尺寸减半),得到112x112x64的尺寸,128个3x3的卷积核作两次卷积+ReLU,尺寸变为112x112x128;

  4. 经过步长为2的maxpooling,尺寸变为56x56x128,256个3x3的卷积核作三次卷积+ReLU,尺寸变为56x56x256;

  5. 经过步长为2的maxpooling,尺寸变为28x28x256,512个3x3的卷积核作三次卷积+ReLU,尺寸变为28x28x512;

  6. 经过步长为2的maxpooling,尺寸变为14x14x512,512个3x3的卷积核作三次卷积+ReLU,尺寸变为14x14x512

  7. 经过步长为2的maxpooling,尺寸变为7x7x512;

  8. Flatten层把输入一维化,然后经过两个1x1x4096全连接层,一个1x1x1000全连接层+ReLU;

  9. 通过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()

参考:

https://my.oschina.net/u/876354/blog/1634322

https://blog.csdn.net/wcy12341189/article/details/56281618

posted @ 2020-05-02 18:57  _LLLL  阅读(3130)  评论(0编辑  收藏  举报