1.神经网络图像(CNNs)分类简介
本文将重点关注卷积神经网络,也被称为CNNs或Convnets。CNNs是一种特殊类型的神经网络,特别适合于图像数据。自2012年以来,ImageNet竞赛(ImageNet)一直由CNN架构赢得。
在本文中,涉及以下内容:
前馈网络的局限性
卷积层(conv layer)
池化层、退出(dropout)和批处理规范化(batchnorm)
构建一个自定义架构来对交通标志进行分类
现代架构
增强(Augmentations)
2.前馈神经网络的局限性
将图像扁平化需要将其用作全连接层的输入。在一层m个神经元之后的n个神经元的完全连接层有(m+1)xn个参数。
如果网络单一层有10个神经元,使用分辨率为64x64x3的图像,这一层将有122880个权重和10个偏差值。如果有更多的层,更多的神经元和更高的图像分辨率,就不能很好地扩展了。幸好,卷积层将帮助解决这个问题!
现在将图像视为一个输入容积,而不是将图像扁平化。与完全连接层相反,卷积层是就地连接的。事实上,卷积层中的每个神经元只会连接到输入容积的一小部分。
3.引入卷积层
卷积层由过滤器组成。这种过滤器是可学习权重的HxWxD数组,在输入容积上滑动或卷积。每个过滤器对整个输入容积进行卷积。这种过滤器的卷积创建了一个2D输出数组,称为特征映射。因为卷积层有多个过滤器,所以它输出多个特征映射。它们将被堆叠在一起以创建输出容积。
卷积层中的过滤器由两个超参数定义:高度和宽度。它们通常很小,最常见的过滤器尺寸是3x3或5x5。
卷积操作
除了过滤器大小F,卷积层还有额外的超参数。步长S控制过滤器移动的步长。填充P通过在输入边界上添加零(或其他值)来控制输出的大小。
输出容积的空间尺寸可以用下式计算。公式用于计算输出容积的宽度,同样也适用于高度。
4.Convnet层和参数
在卷积层中,过滤器充当特征检测器,层越深,过滤器的特征性就越强。例如,当训练一个算法来分类狗和猫的图像时,深层的过滤器可能是一个左耳检测器。
卷积层中参数的数量由过滤器的大小、过滤器的数量和输入容积D的深度决定。如果我们考虑一个3x3大小的过滤器,包含32个过滤器:
每个过滤器有3x3xD + 1个可学习的参数
整个层有(3x3xD+1)x32可学习参数
CNNs通常有数百万个参数,但一些面向移动的架构几乎不到100万个。
5.池化层
池化层在CNNs中很常见,通过聚集空间信息来减少网络中参数的数量,通常是取平均值(平均池化)或max(最大池化),没有任何可学习的参数。
6.练习1 -池化层
目标
在本练习中,实现一个简化版本的最大池化层。
细节
实现两个函数和一个小脚本。第一个函数是填充函数。使用输入大小和池化层参数(步长和过滤器大小),这个函数找到填充wpad和hpad(宽度和高度填充),这样输入尺寸就会被填充。
另一个函数在池化后计算输出维度,给定填充的数组维度和池化参数(步长和过滤器大小)。
最后,脚本计算池化层输出。
运行python pools .py来检查你的实现——注意,检查输出将需要输入3x3过滤器和步长为3。
提示
池化只影响空间维度,并保留批大小(填充数组的第一个轴)和通道数量(最后一个轴)。
参考代码如下:
import argparse
import numpy as np
from utils import check_output
def get_paddings(array, pool_size, pool_stride):
"""
get padding sizes
args:
- array [array]: input np array NxwxHxC
- pool_size [int]: window size
- pool_stride [int]: stride
returns:
- paddings [list[list]]: paddings in np.pad format
"""
_, w, h, _ = array.shape
wpad = (w // pool_stride) * pool_stride + pool_size - w
hpad = (h // pool_stride) * pool_stride + pool_size - h
return [[0, 0], [0, wpad], [0, hpad], [0, 0]]
def get_output_size(shape, pool_size, pool_stride):
"""
given input shape, pooling window and stride, output shape
args:
- shape [list]: input shape
- pool_size [int]: window size
- pool_stride [int]: stride
returns
- output_shape [list]: output array shape
"""
w = shape[1]
h = shape[2]
new_w = (w - pool_size) / pool_stride + 1
new_h = (h - pool_size) / pool_stride + 1
return [shape[0], int(new_w), int(new_h), shape[3]]
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Download and process tf files')
parser.add_argument('-f', '--pool_size', required=True, type=int, default=3,
help='pool filter size')
parser.add_argument('-s', '--stride', required=True, type=int, default=3,
help='stride size')
args = parser.parse_args()
input_array = np.random.rand(1, 224, 224, 16)
pool_size = args.pool_size
pool_stride = args.stride
# padd the input layer
paddings = get_paddings(input_array, pool_size, pool_stride)
padded = np.pad(input_array, paddings, mode='constant', constant_values=0)
# get output size
output_size = get_output_size(padded.shape, pool_size, pool_stride)
output = np.zeros(output_size)
idx = 0
for i in range(0, input_array.shape[1], pool_stride):
jdx = 0
for j in range(0, input_array.shape[2], pool_stride):
local = padded[:, i: i + pool_stride, j: j + pool_stride, :]
local_max = np.max(local, axis=(1, 2))
output[:, idx, jdx, :] = local_max
jdx += 1
idx += 1
check_output(output)
其它资源:
https://machinelearningmastery.com/pooling-layers-for-convolutional-neural-networks/
7.典型的卷积网络架构
以下模式在CNNs架构中很常见:
卷积的层
激活
池化
然后重复这样的层块。另一个常见的策略是,随着深度增加卷积层中的过滤器数量。
最后一层卷积的输出扁平化,并输入至一个经典的前馈NN,称为分类器classifier。由于分类器的第一个全连接层需要固定大小的输入,因此这种设计的CNNs需要固定分辨率的输入图像。
8.退出(Dropout)和批处理规范化
Dropout
在2014年的一篇论文(https://jmlr.org/papers/volume15/srivastava14a/srivastava14a.pdf)中,Dropout作为一种防止过拟合的方法被引入。Dropout可以用于完全连接或卷积层,并简单地在训练期间随机禁用神经元。Dropout没有任何可学习的参数,只有一个超参数,即神经元被禁用的概率p。
需要注意的是,Dropout在训练和测试期间的行为并不类似。事实上,因为我们希望模型的行为在生产中是确定性的,dropout被关闭,神经元不再是随机禁用的。这样做的主要结果是需要缩放神经元输出。为了理解这一点,考虑一个包含10个神经元的简单的全连接层,使用的dropout概率是0.5 。在训练过程中,下一个完全连接层的神经元平均会收到5个神经元的输入。然而,在测试过程中,相同的神经元将接收来自10个神经元的输入。为了在训练和测试中获得类似的行为,需要在测试期间将神经元的输出置为0.5。
在实践中,我们使用了一种叫做“反向dropout”的方法,即在训练过程中发生缩放。为什么?因为我们希望模型在部署时尽可能快,所以我们宁愿让这个小操作发生在训练期间,而不是在推断时。
批处理规范化
批处理规范化或Batchnorm在2015年的一篇论文(https://arxiv.org/pdf/1502.03167.pdf)中首次被引入。batchnorm层计算批处理统计信息,并使用这些统计信息拓展输入。通过这样做,Batchnorm提升了神经网络的收敛时间。与Dropout类似,Batchnorm在训练和测试期间表现不同。在训练期间,该层计算批统计数据的指数移动平均值。在测试期间,使用这些统计信息,而不是来自测试批次的批量统计信息。
9.构建一个用户架构
AlexNet
Alexnet架构是深度学习计算机视觉领域的一个关键突破。在这篇2012年的论文(https://proceedings.neurips.cc/paper/2012/file/c399862d3b9d6b76c8436e924a68c45b-Paper.pdf)中,作者成功地使用GPU训练的CNN对图像进行分类。
VGG
在2015年的这篇论文(https://arxiv.org/pdf/1409.1556.pdf)中,作者介绍了两个非常重要的概念:
使用更小的过滤器尺寸,在这种情况下是3x3过滤器。
使用卷积块:2或3个卷积层的块,然后是一个池化层。
通过使用小过滤器叠加多个卷积层,创建一个比使用大过滤器尺寸的单层更少参数的块,具有相同的有效接收域和更多的非线性。
ResNet
在2015年发表的另一篇非常重要的论文(https://arxiv.org/pdf/1512.03385.pdf)中,作者引入了Resnet架构。他们认识到,拥有更多层的网络表现不尽人意。为了解决这个问题并考虑到更深层次的网络,作者引入了剩余连接(residual connections )或跨步连接(skip connections)的概念。跨步连接只是将一个层的输入或一个层块添加到其输出中。通过这样做,它将缓解与激活函数相关的梯度传播(gradient propagation)问题。
Inception
Inception网络,在2014年的这篇论文(https://arxiv.org/pdf/1409.4842.pdf)中介绍,有一些有趣和开创性的研究,例如在一个“层”内使用多个不同大小的卷积过滤器,甚至使用1x1过滤器,以通常更少的参数实现深度网络。有兴趣可以深入阅读这篇论文。
10.练习2 -CNN
目标
在本练习中,使用Keras API创建第一个CNN。
细节
使用Keras API,创建一个少于15层的小型卷积神经网络,其中至少包含一个卷积层、一个池化层和一个全连接层(dense)。可以在这里找到不同层的列表:https://www.tensorflow.org/api_docs/python/tf/keras/layers。
尝试不同的设计(层数、池化类型、过滤器大小、完全连接层数、神经元数量)。
使用-d将图像目录输入到training.py (GTSRB/Final_Training/Images/),以便查看最终指标可视化。
提示
对于小型网络来说,LeNet5是一个很好的起点。可以在网上找到许多现成的实现。
不要忘记convnet的基本结构:卷积层、激活和池化。
可以使用Keras模型API的总结方法来打印模型的描述。
参考代码如下:
import argparse
import logging
import tensorflow as tf
from tensorflow.keras.layers import Dense, Flatten, Conv2D, MaxPooling2D
from utils import get_datasets, get_module_logger, display_metrics
def create_network():
net = tf.keras.models.Sequential()
input_shape = [32, 32, 3]
net.add(Conv2D(6, kernel_size=(3, 3), strides=(1, 1), activation='relu',
input_shape=input_shape))
net.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
net.add(Conv2D(16, kernel_size=(3, 3), strides=(1, 1), activation='relu'))
net.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
net.add(Flatten())
net.add(Dense(120, activation='relu'))
net.add(Dense(84, activation='relu'))
net.add(Dense(43))
return net
if __name__ == '__main__':
logger = get_module_logger(__name__)
parser = argparse.ArgumentParser(description='Download and process tf files')
parser.add_argument('-d', '--imdir', required=True, type=str,
help='data directory')
parser.add_argument('-e', '--epochs', default=10, type=int,
help='Number of epochs')
args = parser.parse_args()
logger.info(f'Training for {args.epochs} epochs using {args.imdir} data')
# get the datasets
train_dataset, val_dataset = get_datasets(args.imdir)
model = create_network()
model.compile(optimizer='adam',
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
metrics=['accuracy'])
history = model.fit(x=train_dataset,
epochs=args.epochs,
validation_data=val_dataset)
display_metrics(history)
其它资源:
https://www.tensorflow.org/tutorials/images/cnn
https://www.tensorflow.org/tutorials/images/classification
11.迁移学习
使用迁移学习的四种主要案例
迁移学习包括采用预先训练的神经网络,并使神经网络适应新的、不同的数据集。
由以下两种情形决定:
新数据集的大小,以及
新数据集与原始数据集的相似度
使用迁移学习的方法将是不同的。主要有四种案例:
1.新数据集小,新数据与原始训练数据相似
2.新数据集小,新数据不同于原始训练数据
3.新数据集大,新数据与原始训练数据相似
4.新数据集大,新数据不同于原始训练d
一个大型数据集可能有100万张图像。一个小数据可能有2000张图像。大数据集和小数据集之间的分界线有些主观。在使用小数据集进行迁移学习时,过拟合是一个问题。
狗和狼的图像被认为是相似的;这些图像具有共同的特征。而花的图像数据集不同于狗的图像数据集。
四种迁移学习情况都有各自的达成方法。在下面的部分中,我们将逐一分析每种情况。
示范网络
为了解释每种情况是如何工作的,我们将从一个通用的预训练卷积神经网络开始,并解释如何针对每种情况调整网络。我们的示例网络包含三个卷积层和三个全连接层:
以下是卷积神经网络功能的概括概述:
第一层将检测图像中的边缘
第二层将检测形状
第三个卷积层检测更高级别的特征
每个迁移学习案例将以不同的方式使用预先训练的卷积神经网络。
案例1:小数据集,相似数据
如果新数据集较小且与原始训练数据相似:
切掉神经网络的末端
添加一个新的全连接层,该层与新数据集中的类数量相匹配
随机化新全连通层的权值;冻结来自预训练网络的所有权重
训练网络更新新的全连通层的权值
为了避免在小数据集上过拟合,原网络的权值保持不变,而不是重新训练权值。
由于数据集是相似的,每个数据集的图像将具有相似的高级特征。因此,大部分或所有预先训练的神经网络层都已经包含了关于新数据集的相关信息,应予以保留。
下面是如何可视化这种方法:
案例2:小数据集,不同数据
如果新数据集较小,且与原始训练数据不同:
在网络开始的地方切掉大部分预先训练的层
在剩余的预训练层中添加一个新的完全连接层,该层与新数据集中的类数量相匹配
随机化新全连通层的权值;冻结来自预训练网络的所有权重
训练网络更新新的全连通层的权值
由于数据集很小,过拟合仍然是一个问题。为了对抗过拟合,原始神经网络的权值将保持不变,就像第一种情况一样。
但是原始训练集和新数据集不共享更高层次的特征。在这种情况下,新的网络将只使用包含较低级别特性的层。
下面是如何可视化这种方法:
案例3:大数据集,相似数据
如果新数据集较大且与原始训练数据相似:
删除最后一个完全连接的层,并替换为与新数据集中类数量匹配的层
在新的全连接层中随机初始化权重
使用预先训练的权重初始化其余的权重
重新训练整个神经网络
在大数据集上进行训练时,过拟合不太受关注;因此,可以重新训练所有的权重。
由于原始训练集和新数据集共享更高的层次特征,因此也使用了整个神经网络。
下面是如何可视化这种方法:
案例4:大数据集,不同数据
如果新数据集较大,且与原始训练数据不同:
删除最后一个完全连接的层,并替换为与新数据集中类数量匹配的层
用随机初始化的权值从头重新训练网络
或者,您也可以使用与“数据集较大和训练数据类似”数据案例相同的策略
即使数据集与训练数据不同,从预先训练的网络初始化权值可能会使训练速度更快。这种情况与“数据集较大和训练数据类似”的情况完全相同。
如果使用预训练的网络作为起点不能产生成功的模型,另一种方法是随机初始化卷积神经网络的权值,从头开始训练网络。
下面是如何可视化这种方法:
其它资源:
https://www.tensorflow.org/tutorials/images/transfer_learning
12.增强(Augmentations)
数据增强是一种无需捕捉更多图像就能增加训练数据集可变性的方法。通过在训练过程中应用像素级和几何变换,我们可以人工模拟不同的环境。例如,模糊滤镜可以用来模仿运动模糊。应谨慎选择增强方案。
可以按照本教程(https://www.tensorflow.org/tutorials/images/data_augmentation)使用Tensorflow中的数据增强。这种方法使用Keras API来创建增强的组合。在这里可以找到可用的增强列表。
下面我们将介绍Albumentations,一个与框架无关的数据增强库。
Albumentations
有许多图像增强库可用,但其中最受欢迎的是albumentations(Albumentations Documentation)。它提供了对图像和标签(包括边界框)应用像素级和几何变换的快速而简单的方法。
13.Exercise 3 - 增强
目标
在本练习中,尝试使用Albumentations库来执行不同的数据增强。
细节
写下相关的增强列表,并将它们存储在transforms变量中。还应该实现一个快速脚本来可视化批处理,并用于检查增强。
运行python augment .py来显示增强后的图像。
提示
使用Compose API来使用多个增强,可以在这里 (https://albumentations.ai/docs/examples/example/#define-an-augmentation-pipeline-using-compose-pass-the-image-to-it-and-receive-the-augmented-image) 找到使用Compose的增强管道的示例。
这个Github存储库 (GitHub - albumentations-team/albumentations_examples: Augmentations usage examples for albumentations library) 包含了不同的增强示例。
参考代码如下:
augmentations.py
import argparse
from functools import partial
import albumentations as A
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
from tensorflow.keras.preprocessing import image_dataset_from_directory
from utils import plot_batch
transforms = A.Compose([A.Rotate(limit=30, p=0.5),
A.Blur(blur_limit=5, p=0.5)])
def aug_fn(image):
""" augment an image """
aug_data = transforms(image=image.squeeze())
aug_img = aug_data["image"]
aug_img = tf.cast(aug_img/255.0, tf.float32)
return aug_img
def process_data(image, label):
""" wrapper function to apply augmentation """
aug_img = tf.numpy_function(func=aug_fn, inp=[image], Tout=tf.float32)
return aug_img, label
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Augment dataset')
parser.add_argument('-d', '--imdir', required=True, type=str,
help='data directory')
args = parser.parse_args()
dataset = image_dataset_from_directory(args.imdir,
image_size=(32, 32),
validation_split=0.1,
subset='training',
seed=123,
batch_size=1)
dataset = dataset.map(process_data).batch(256)
for X,Y in dataset:
batch_np = X.numpy()
plot_batch(batch_np)
break
其它资源:
https://www.tensorflow.org/tutorials/images/data_augmentation
14.总结
本文包括以下内容:
前馈网络的局限性,以及为什么只有完全连接层的神经网络不适用于图像数据。
卷积层是神经网络的一种新层,具有不同的参数。
池化层,退出(dropout)和批处理规范化,不同的层,在CNNs中很常见。
建立一个自定义的架构来分类交通标志,以及如何在CNN中排序不同的层。
现代架构及其设计。
增强,一种人为增加数据集可变性的方法。
15.术语表
- Augmentations: 一种通过应用像素级和几何变换人为地增加数据集多样性的方法。
- Batch Normalization: 该层使用批量统计来拓展其输入,并加速神经网络的整体收敛。
- Convolutional layer: 一种特别适合于分析图像数据的神经网络层,由过滤器在输入体积上卷积而成。
- Dropout: 一种随机关闭神经元以防止过拟合的技术。
- Feature map:过滤器对输入容积进行卷积的二维阵列。
- Padding:将整数(通常为零)加到图像边界的行为。
- Pooling layer: 在空间上聚集信息的层。
- Stride: 计算窗口被平移的步长。
- Transfer Learning: 使用预先训练好的网络作为训练新神经网络的起点。