1.介绍
深度卷积神经网络极大地改变了图像分类的研究前景[1]。
随着更多层的添加,模型的表达能力增强;它能够学习更复杂的表示法。在某种程度上,网络的深度与模型的准确性之间似乎存在正相关关系。
另一方面,随着网络的深入,逐渐消失/爆炸的梯度问题变得更加严重。规范化初始化和规范化层最终解决了这个问题,深度网络开始收敛。
然而,与随后的实验之前的直觉推理不同,随着深度的增加,模型的准确性开始饱和,然后实际上迅速下降。这不是由于过拟合,而是由于用于优化模型的当前解算器的局限性[2]。
引入ResNet解决了退化问题[2]。它引入了一种系统的方法来使用跳跃连接,即跳过一个或多个层的连接。这些短连接只是执行标识映射,它们的输出被添加到堆叠层的输出中(这不会增加额外的参数或计算复杂性)。其背后的想法是,如果多个非线性层可以渐近逼近复杂函数(仍在理论上研究,但是深入学习的基础),那么残差函数也可能发生同样的情况。其优点是,同时简化了求解器的工作。在[3]中研究了其他类型的连接,如缩放、1x1卷积的跳跃连接。
我们的任务是对一系列带标签的图像进行分类。
我们想比较两种不同方法的准确性;第一种是经典的卷积神经网络,第二种是残差网络。我们的目标是展示残差网络的力量,即使在不太深的网络中。
这是一个很好的方法来帮助优化过程,同时解决退化问题。我们对残差网络进行了经验测试,发现它更容易过拟合。为了解决这一问题,我们采用数据扩充策略对数据集进行了综合扩充。
我们使用辛普森字符数据集[4]。我们只过滤数据集以包含包含100多个图像的类(字符)。在对训练、验证和测试数据集进行分割后,数据集的结果大小如下:12411个用于训练的图像、3091个用于验证的图像和950个用于测试的图像。
代码和数据也像往常一样在我的GitHub上可用。
https://github.com/luisroque/deep-learning-articles
2.数据预处理
我们创建生成器将数据提供给模型。我们还应用了一个转换来规范化数据,在训练数据集和验证数据集之间分割数据,并定义32的批大小(请参见[5],以便更好地理解预处理和生成器)。
import tensorflow as tf
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Layer, BatchNormalization, Conv2D, Dense, Flatten, Add, Dropout, BatchNormalization
import numpy as np
from tensorflow.keras.datasets import fashion_mnist
from tensorflow.keras.utils import to_categorical
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from tensorflow.keras.preprocessing.image import ImageDataGenerator
import os
from tensorflow.keras import Input, layers
from tensorflow.keras.layers import Dense, Flatten, Conv2D, MaxPooling2D
import time
directory_train = "./simpsons_data_split/train/"
directory_test = "./simpsons_data_split/test/"
def get_ImageDataGenerator(validation_split=None):
image_generator = ImageDataGenerator(rescale=(1/255.),
validation_split=validation_split)
return image_generator
image_gen_train = get_ImageDataGenerator(validation_split=0.2)
def get_generator(image_data_generator, directory, train_valid=None, seed=None):
train_generator = image_data_generator.flow_from_directory(directory,
batch_size=32,
class_mode='categorical',
target_size=(128,128),
subset=train_valid,
seed=seed)
return train_generator
train_generator = get_generator(image_gen_train, directory_train, train_valid='training', seed=1)
validation_generator = get_generator(image_gen_train, directory_train, train_valid='validation')
Found 12411 images belonging to 19 classes.
Found 3091 images belonging to 19 classes.
我们还创建了一个增强数据集,通过应用一组几何和光度变换来减少过拟合的可能性。
几何变换改变图像的几何结构,使CNN在位置和方向上保持不变。光度变换通过调整图像的颜色通道,使CNN对颜色和照明的变化保持不变。
def get_ImageDataGenerator_augmented(validation_split=None):
image_generator = ImageDataGenerator(rescale=(1/255.),
rotation_range=40,
width_shift_range=0.2,
height_shift_range=0.2,
shear_range=0.2,
zoom_range=0.1,
brightness_range=[0.8,1.2],
horizontal_flip=True,
validation_split=validation_split)
return image_generator
image_gen_train_aug = get_ImageDataGenerator_augmented(validation_split=0.2)
train_generator_aug = get_generator(image_gen_train_aug, directory_train, train_valid='training', seed=1)
validation_generator_aug = get_generator(image_gen_train_aug, directory_train, train_valid='validation')
Found 12411 images belonging to 19 classes.
Found 3091 images belonging to 19 classes.
我们可以遍历生成器,得到一组大小等于上面定义的批量大小的图像。
target_labels = next(os.walk(directory_train))[1]
target_labels.sort()
batch = next(train_generator)
batch_images = np.array(batch[0])
batch_labels = np.array(batch[1])
target_labels = np.asarray(target_labels)
plt.figure(figsize=(15,10))
for n, i in enumerate(np.arange(10)):
ax = plt.subplot(3,5,n+1)
plt.imshow(batch_images[i])
plt.title(target_labels[np.where(batch_labels[i]==1)[0][0]])
plt.axis('off')
3.基准模型
我们定义了一个简单的CNN作为基准模型。
它使用2D卷积层(对图像执行空间卷积)和最大池操作。紧随其后的是具有128个单元和ReLU激活功能的Dense层,以及Dropout率为0.5的Dropout层。最后,最后一层产生我们网络的输出,该网络的单元数等于目标标签的数量,并使用softmax激活函数。
该模型使用Adam优化器编译,具有默认设置和分类交叉熵损失。
def get_benchmark_model(input_shape):
x = Input(shape=input_shape)
h = Conv2D(32, padding='same', kernel_size=(3,3), activation='relu')(x)
h = Conv2D(32, padding='same', kernel_size=(3,3), activation='relu')(x)
h = MaxPooling2D(pool_size=(2,2))(h)
h = Conv2D(64, padding='same', kernel_size=(3,3), activation='relu')(h)
h = Conv2D(64, padding='same'