在武测学习(二):卷积神经网络CNN——《Python深度学习》学习笔记

卷积神经网络

目录

卷积神经网络

1 卷积神经网络简介

1.1 卷积运算

1.2 最大池化运算

2.2 实例——dogs-vs-cats

2.1 数据准备

2.2 数据生成器及数据增强

2.3 预训练的模型

2 Pytorch实现CNN


1 卷积神经网络简介

卷积神经网络(convnet,CNN)是计算机视觉应用中最常见的深度学习模型,对于图像问题具有很好的性能。

和密集连接层相比,卷积层能够学习到特征空间中的局部模式,这得益于卷积运算的特性。

卷积神经网络具有两个重要性质:

  • 平移不变性。当CNN在图像中学习到某个局部模式,那么这个模式出现在任何一个其它地方,CNN都能够识别。而对于密集连接层,它会将其视作不同的模式,导致从头学习。而视觉世界从根本上具有平移不变性。
  • 能够学到模式的空间层次结构。神经网络可以有效地学习越来越复杂、越来越抽象的视觉概念。而视觉世界从根本上具有空间层次结构。

CNN所处理的图像,包含两个空间轴(高度宽度)和一个深度轴。 

1.1 卷积运算

CNN利用卷积运算,改变图像的深度,从而生成特征图。特征图的每一层具有的含义不再和原始图像相同。卷积运算的通常为3×3或5×5的区域,由于边界效应,图的宽度和高度会缩小。另外,卷积运算还有步幅的概念,即相邻的两个卷积核之间的距离。步幅边界效应以及是否对原图进行填充 ,将决定输出图像的尺寸

1.2 最大池化运算

当模型中只有卷积层时,模型很难学习到图像的空间层级结构(所学习到的窗口会越来越小)。而且所包含的元素会越来越多,导致模型的过拟合。因此引入了下采样的概念。

通过将图像以某种方式缩小大小,则卷积窗口在原始图像中的大小会越来越大,并且特征图的元素个数不会过大。可以通过在卷积层中添加步幅来实现,也可以通过最大池化层。

2.1.3 CNN的基本结构

_________________________________________________________________
 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 (MaxPooling  (None, 5, 5, 64)         0         
 2D)                                                             
                                                                 
 conv2d_2 (Conv2D)           (None, 3, 3, 64)          36928     
                                                                 
 flatten (Flatten)           (None, 576)               0         
                                                                 
 dense (Dense)               (None, 64)                36928     
                                                                 
 dense_1 (Dense)             (None, 10)                650       
                                                                 
=================================================================
Total params: 93,322
Trainable params: 93,322
Non-trainable params: 0
_________________________________________________________________

可以看到,这个示例中的CNN由两部分组成:

①卷积层和最大池化层的堆叠

②Dense层堆叠得到的分类器

两者之间还有一个flattern层,用于将多维数据展平成一维向量,可供密集连接层处理。

2.2 实例——dogs-vs-cats

2.1 数据准备

图像数据处理的步骤较为繁琐,主要有以下几步(keras):

  1. 划分训练集和测试集,为每张图像贴上标签(生成标签向量)
  2. 将图像编码为width×height×para的三维张量,并标准化到0-1之间
  3. 利用数据生成器进行训练。当训练数据较少时,可以使用数据增强

首先是数据准备。书作者的方法比较便于查看,将数据根据文件名划分到不同文件夹里,train和test,其内分别包含cat和dog两个文件夹。这样在后续读取的时候很方便去贴标签,也容易检查数据个数是否正确。

original_dataset_dir = 'D:/DeepLearning/kaggle_original_data/train'

# 生成存放新数据的文件夹
base_dir = 'D:/DeepLearning/cats_and_dogs_small'
os.mkdir(base_dir)

# 生成子文件夹(训练集,验证集,测试集)
train_dir = os.path.join(base_dir, 'train')
os.mkdir(train_dir)
validation_dir = os.path.join(base_dir, 'validation')
os.mkdir(validation_dir)
test_dir = os.path.join(base_dir, 'test')
os.mkdir(test_dir)

# 训练集子文件夹(训练集猫,训练集狗)
train_cats_dir = os.path.join(train_dir, 'cats')
os.mkdir(train_cats_dir)

# Directory with our training dog pictures
train_dogs_dir = os.path.join(train_dir, 'dogs')
os.mkdir(train_dogs_dir)

# 生成验证级 猫,狗文件夹
validation_cats_dir = os.path.join(validation_dir, 'cats')
os.mkdir(validation_cats_dir)

validation_dogs_dir = os.path.join(validation_dir, 'dogs')
os.mkdir(validation_dogs_dir)

# 生成测试集 猫,狗文件夹
test_cats_dir = os.path.join(test_dir, 'cats')
os.mkdir(test_cats_dir)

test_dogs_dir = os.path.join(test_dir, 'dogs')
os.mkdir(test_dogs_dir)

# 利用名字来检索1000只猫
fnames = ['cat.{}.jpg'.format(i) for i in range(1000)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(train_cats_dir, fname)
    #将图像从路径src复制到dst
    shutil.copyfile(src, dst)

# 将之后的500只猫复制到验证集下的猫文件夹
fnames = ['cat.{}.jpg'.format(i) for i in range(1000, 1500)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(validation_cats_dir, fname)
    shutil.copyfile(src, dst)
    
# 将最后的500只猫复制到训练集下的狗文件夹
fnames = ['cat.{}.jpg'.format(i) for i in range(1500, 2000)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(test_cats_dir, fname)
    shutil.copyfile(src, dst)
    
# 重复上述操作,把猫改成狗
fnames = ['dog.{}.jpg'.format(i) for i in range(1000)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(train_dogs_dir, fname)
    shutil.copyfile(src, dst)

fnames = ['dog.{}.jpg'.format(i) for i in range(1000, 1500)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(validation_dogs_dir, fname)
    shutil.copyfile(src, dst)    

fnames = ['dog.{}.jpg'.format(i) for i in range(1500, 2000)]
for fname in fnames:
    src = os.path.join(original_dataset_dir, fname)
    dst = os.path.join(test_dogs_dir, fname)
    shutil.copyfile(src, dst)

2.2 数据生成器及数据增强

数据生成器的概念类似于pytorch中的dataloader。可以在数据生成器中加入旋转、平移、缩放等操作,增加训练数据量,也就是数据增强(data augment)。

keras中的imageDataGenarator能够定义生成器,flow_from_directory则从文件进行读取和分类

需要注意的是,batch_size和steps_per_epoch应当乘积为数据总量,或者可以不定义其中一个,模型会自动计算另外一个。

from keras.preprocessing.image import ImageDataGenerator

# 利用imageDataGenerator创建生成器,读取图像的同时进行数据的缩放
train_datagen = ImageDataGenerator(rescale=1./255)
test_datagen = ImageDataGenerator(rescale=1./255)

train_generator = train_datagen.flow_from_directory(
        # 从目标路径读取数据,根据子文件夹来分类
        train_dir,
        # All images will be resized to 150x150
        target_size=(150, 150),
        batch_size=20,
        # 使用二进制标签
        class_mode='binary')

validation_generator = test_datagen.flow_from_directory(
        validation_dir,
        target_size=(150, 150),
        batch_size=20,
        class_mode='binary')

# 利用生成器进行训练和验证
history = model.fit_generator(
      train_generator,
      steps_per_epoch=100,#一次抽取20个,总共2000个,因此抽取100次
      epochs=30, #迭代30次
      validation_data=validation_generator,
      validation_steps=50)

绘制得到精度曲线图:

在武测学习(二):卷积神经网络CNN——《Python深度学习》学习笔记_第1张图片

在武测学习(二):卷积神经网络CNN——《Python深度学习》学习笔记_第2张图片

模型出现了典型的过拟合

下面给出利用数据增强来训练的实例:

#训练模型生成器(包括数据增强)
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,)

# 测试数据生成器(没有数据增强)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
test_datagen = ImageDataGenerator(rescale=1./255)

#训练数据生成器实例化
train_generator = train_datagen.flow_from_directory(
        train_dir,
        target_size=(150, 150),
        batch_size=32,
        class_mode='binary')

#测试数据生成器实例化
validation_generator = test_datagen.flow_from_directory(
        validation_dir,
        target_size=(150, 150),
        batch_size=32,
        class_mode='binary')

#利用生成器拟合
history = model.fit_generator(
      train_generator,
      # steps_per_epoch=63,
      epochs=100,
      validation_data=validation_generator,
      # validation_steps=32
      )

这里虽然使用了数据增强,但输入的steps_per_epoch参数和原来一样。也就是说数据增强的过程在这其中已经加入了。

得到精度图像

 在武测学习(二):卷积神经网络CNN——《Python深度学习》学习笔记_第3张图片

 在武测学习(二):卷积神经网络CNN——《Python深度学习》学习笔记_第4张图片

 模型不再过拟合,数据增强起到了作用。

2.3 预训练的模型

前面说过,卷积层能够学习到局部特征,并且具有平移不变性,因此其学习的特征比密集连接层要更通用,也就更适合重复使用。

有许多在ImageNet上已经训练好的模型,如VGG16,VGG19,Xception,Inception V3,ResNet50,MobileNet等。书中介绍了利用预训练网络来提取图像特征,再输入到密集连接分类器中学习的方法。主要包括以下几种:

①利用VGG网络predict训练数据,得到输出特征图,再输入到密集分类器

首先初始化基础模型,用于生成特征图

from keras.applications import VGG16

# 初始化一个VGG16模型
conv_base = VGG16(weights='imagenet',
                  include_top=False,
                  input_shape=(150, 150, 3))

import os
import numpy as np
from keras.preprocessing.image import ImageDataGenerator

base_dir = 'D:/DeepLearning/cats_and_dogs_small'

train_dir = os.path.join(base_dir, 'train')
validation_dir = os.path.join(base_dir, 'validation')
test_dir = os.path.join(base_dir, 'test')

然后定义函数,来用于处理数据得到特征图数据

#定义数据生成器
datagen = ImageDataGenerator(rescale=1./255)
batch_size = 20

#定义函数,用于生成特征图,并规定数量上限sample_count
def extract_features(directory, sample_count):
    #初始化特征图和标签(全为0)
    features = np.zeros(shape=(sample_count, 4, 4, 512))
    labels = np.zeros(shape=(sample_count))
    generator = datagen.flow_from_directory(
        directory,
        target_size=(150, 150),
        batch_size=batch_size,
        class_mode='binary')

    i = 0
    #循环输出
    for inputs_batch, labels_batch in generator:
        #predict生成特征批量
        features_batch = conv_base.predict(inputs_batch)
        #给特征图的第i个批量赋值,label无需处理
        features[i * batch_size : (i + 1) * batch_size] = features_batch
        labels[i * batch_size : (i + 1) * batch_size] = labels_batch
        i += 1
        #当读取的数据大于样本数时退出
        if i * batch_size >= sample_count:
            break
    return features, labels

#生成训练数据和验证数据和测试数据

train_features, train_labels = extract_features(train_dir, 2000)
validation_features, validation_labels = extract_features(validation_dir, 1000)
test_features, test_labels = extract_features(test_dir, 1000)

剩下的就是将数据展平然后训练和验证,不再赘述。

这种计算方法的代价很低,因为只对一个密集连接层做训练。它的缺点在于无法使用数据增强,因为数据增强需要在每轮拟合输入不同的随机增强的样本,而在这个方法中每轮必定是一样的样本,否则便需要不断运行卷积基,导致巨大的计算代价。也因此导致模型迅速过拟合,效果不够理想。

②在VGG之后添加一个密集连接分类器

这种方法直接在预训练模型的后面增加分类器,这会导致计算代价非常大,但好处在于可以使用数据增强来降低过拟合。

不仅如此,训练模型的时候可以选择将预训练模型冻结,从而实现只对密集连接分类器的权重进行训练。

③微调预训练模型

微调模型将预训练模型部分解冻,来对预训练模型的权重进行更新,使其更适合具体的问题。但需要注意的是,在微调之前必须先将分类器的权重训练好,而不是随机初始化,否则会将解冻的几层严重破坏。

其中,通常解冻的是卷积基的底部几层。因为卷积基中更靠顶部的层编码的是更专业化的特征,而靠近底部的是更普通使用的特征。同时训练过多层会导致参数过多,过拟合的风险增加。

三种方法思路很清晰,以下附上书本的代码和自己添加的注释。

from keras import models
from keras import layers
from keras.preprocessing.image import ImageDataGenerator

# 在卷积基后面添加密集连接分类器
model = models.Sequential()
model.add(conv_base)
model.add(layers.Flatten())
model.add(layers.Dense(256, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))

# 冻结卷积基
conv_base.trainable = False

# 对训练数据进行数据增强
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)

# 通过文件夹定义数据生成器
train_generator = train_datagen.flow_from_directory(
        train_dir,
        target_size=(150, 150),
        batch_size=20,
        class_mode='binary')

validation_generator = test_datagen.flow_from_directory(
        validation_dir,
        target_size=(150, 150),
        batch_size=20,
        class_mode='binary')

# 编译模型
model.compile(loss='binary_crossentropy',
              optimizer=optimizers.RMSprop(lr=2e-5),
              metrics=['acc'])

# 训练模型
history = model.fit_generator(
      train_generator,
      steps_per_epoch=100,
      epochs=30,
      validation_data=validation_generator,
      validation_steps=50,
      verbose=2)

在武测学习(二):卷积神经网络CNN——《Python深度学习》学习笔记_第5张图片

 在武测学习(二):卷积神经网络CNN——《Python深度学习》学习笔记_第6张图片

conv_base.trainable = True
# 循环遍历模型中的层,定义冻结层
set_trainable = False
for layer in conv_base.layers:
    # 微调的层很少,直接用if语句判断
    if layer.name == 'block5_conv1':
        set_trainable = True
    if set_trainable:
        layer.trainable = True
    else:
        layer.trainable = False

# 编译模型
model.compile(loss='binary_crossentropy',
              optimizer=optimizers.RMSprop(lr=1e-5),
              metrics=['acc'])

# 训练模型(模型是之前保存了的,意味着密集连接分类器的参数已经训练过)
history = model.fit_generator(
      train_generator,
      steps_per_epoch=100,
      epochs=100,
      validation_data=validation_generator,
      validation_steps=50)

 在武测学习(二):卷积神经网络CNN——《Python深度学习》学习笔记_第7张图片

 在武测学习(二):卷积神经网络CNN——《Python深度学习》学习笔记_第8张图片

 书中提到一个有意思的现象:在这两张图中,验证损失一直上升,而验证精度却保持在一个水平,这打破了之前我以为的“验证损失就是验证精度的反面”的观念。实际上验证损失是损失值的平均值,而验证精度和损失的分布有关,两者并不能互相替代。

也正因此,即使平均损失增大,模型也可能在进步。换言之,精度才是模型好坏的标准。损失值只能用于调整权重。

2 Pytorch实现CNN

class CNNnet(torch.nn.Module):
    def __init__(self):
        super(CNNnet,self).__init__()
        self.conv1 = torch.nn.Sequential(
            torch.nn.Conv2d(in_channels=1,
                            out_channels=16,
                            kernel_size=3,
                            stride=1,
                            padding=1),
            torch.nn.ReLU()
            nn.MaxPool2d(kernel_size=2)
        )
        self.conv2 = torch.nn.Sequential(
            torch.nn.Conv2d(in_channels=16,
                            out_channels=32,
                            kernel_size=3,
                            stride=1,
                            padding=1),
            torch.nn.ReLU()
            nn.MaxPool2d(kernel_size=2)       
        )
        self.mlp1 = torch.nn.Sequential(
                    torch.nn.Linear(2*2*64,100)
                    torch.nn.ReLU()
                    )
        self.mlp2 = torch.nn.Sequential(
                    torch.nn.Linear(100,10)
                    torch.nn.ReLU()
                    )
    def forward(self, x):
        x = self.conv1(x)
        x = self.conv2(x)
        x = self.mlp1(x)
        x = self.mlp2(x)
        return x

这里的代码仅作模型构建示意,结构也很简单。pytorch实际上也只是多了一个Relu需要单独写出来,并且反向传播等过程需要一步步写,其它方面并没有显著区别。

你可能感兴趣的:(深度学习,计算机视觉,神经网络,学习)