(四)卷积神经网络 -- 6 AlexNet

6. 深度卷积神经网络(AlexNet)

在LeNet提出后的近20年,神经网络一度被其他机器学习方法超越,如支持向量机。

虽然LeNet可以在早期的小数据集上取得好的成绩,但在更大的真实数据集上的表现并不尽如人意,原因主要有以下两点:
(1) 神经网络计算复杂。虽然20世纪90年代也有过一些针对神经网络的加速硬件,但并没有像之后GPU那样大量普及。因此,训练一个多通道、多层且有大量参数的卷积神经网络在当年很难完成。
(2) 当年研究者还没有大量深入研究参数初始化和非凸优化算法等领域,使得复杂的神经网络的训练通常较困难。

基于上节的内容可以发现,神经网络能直接基于图像的原始像素进行分类。
这种称为端到端(end-to-end)的方法,节省了很多中间步骤。

然而,在很长一段时间,更流行的是研究者通过勤劳与智慧设计生成的手工特征。
这类图像分类研究的主要流程包括:
(1) 获取图像数据集;
(2) 使用已有的特征提取函数生成图像的特征;
(3) 使用机器学习模型对图像的特征分类。

当时认为,机器学习的部分仅限最后这一步。那时的机器学习研究者认为,机器学习既重要又优美(优雅的定理证明了许多分类器的性质)。

而计算机视觉研究者认为,计算机视觉流程中真正重要的是数据和特征。也就是说,使用较干净的数据集和较有效的特征,甚至比机器学习模型的选择对图像分类结果的影响更大。


6.1 特征表示

在相当长的时间里,特征都是基于各式各样手工设计的函数从数据中提取的。
事实上,不少研究者通过提出新的特征提取函数不断改进图像分类结果。这一度为计算机视觉的发展做出了重要贡献。

然而,另一些研究者持异议,认为特征本身也应该由学习得来。此外,为了表征足够复杂的输入,特征本身应该分级表示。
即,多层神经网络可能可以学得数据的多级表征,并逐级表示越来越抽象的概念或模式。

以图像分类为例,结合 二维卷积层 小节中物体边缘检测的应用:
在多层神经网络中,图像第一级的表示,可以是在特定的位置和⻆度是否出现边缘;
第二级的表示,可能将这些边缘组合出有趣的模式(如,花纹);
第三级的表示,可能将上一级的花纹进一步汇合成对应物体特定部位的模式。
这样逐级表示下去,最终,模型能够较容易根据最后一级的表示完成分类任务。
注:输入的逐级表示由多层模型中的参数决定,而参数为学得。

尽管一直有研究者试图学习视觉数据的逐级表征,但很长一段时间都未能实现。其原因主要在于,缺失以下两种关键要素:

(1) 缺失数据
包含许多特征的深度模型,需要大量的有标签数据才能表现得比其他经典方法更好。
限于早期计算机有限的存储和90年代有限的研究预算,大部分研究只基于小的公开数据集(如,仅几百至几千张图像)。
该状况在2010年前后兴起的大数据浪潮中得到改善。特别是,2009年诞生的ImageNet数据集(包含1,000大类物体,每类多达数千张不同图像),同时推动计算机视觉和机器学习研究进入新的阶段,使此前的传统方法不再有优势。

(2) 缺失硬件
深度学习对计算资源要求很高。早期的硬件计算能力有限,使得训练较复杂的神经网络变得很困难。
通用GPU的到来改变了这一格局。(很久以来,GPU都是为图像处理和计算机游戏设计的,尤其是针对大吞吐量的矩阵和向量乘法从而服务于基本的图形变换。而这其中的数学表达与深度网络中的卷积层的表达类似。)
通用GPU的概念在2001年开始兴起,涌现出诸如OpenCL和CUDA之类的编程框架,使得GPU在2010年前后开始被机器学习社区使用。



6.2 AlexNet

6.2.1 概念

2012年,AlexNet横空出世。模型的名字,来源于论文第一作者的姓名Alex Krizhevsky。
AlexNet使用了8层卷积神经网络,并以很大的优势赢得了ImageNet 2012图像识别挑战赛。它首次证明了学习到的特征可以超越手工设计的特征,从而一举打破计算机视觉研究的前状。

AlexNet与LeNet的设计理念非常相似,但也有显著的区别:

(1) 与相对较小的LeNet相比,AlexNet包含8层变换。其中,有5层卷积、2层全连接隐藏层,以及1个全连接输出层。具体设计如下:
第一层中的卷积窗口形状是 11 × 11 11\times11 11×11(ImageNet中绝大多数图像的高和宽均比MNIST图像的高和宽大10倍以上,即,占用更多的像素,因此需要更大的卷积窗口来捕获物体)。
第二层中的卷积窗口形状减小到 5 × 5 5\times5 5×5,之后全采用 3 × 3 3\times3 3×3
第一、第二和第五个卷积层之后都使用了窗口形状为 3 × 3 3\times3 3×3、步幅为2的最大池化层,且使用的卷积通道数也大于LeNet中的卷积通道数数十倍。
紧接着最后一个卷积层的是,两个输出个数为4096的全连接层(这两个巨大的全连接层,带来将近1 GB的模型参数)。

(2) AlexNet将sigmoid激活函数改为更加简单的ReLU激活函数。
一方面,ReLU激活函数的计算更简单(如,并没有sigmoid激活函数中的求幂运算)。
另一方面,ReLU激活函数在不同的参数初始化方法下使模型更容易训练。(当sigmoid激活函数输出极接近0或1时,这些区域的梯度几乎为0,从而造成反向传播无法继续更新部分模型参数;而ReLU激活函数在正区间的梯度恒为1。因此,若模型参数初始化不当,sigmoid函数可能在正区间得到几乎为0的梯度,从而令模型无法得到有效训练。)

(3) AlexNet通过丢弃法(参见 丢弃法 一节)来控制全连接层的模型复杂度,而LeNet没有使用丢弃法。

(4) AlexNet引入了大量的图像增广(如翻转、裁剪和颜色变化),从而进一步扩大数据集来缓解过拟合。


6.2.2 模型构造

实现稍微简化过的AlexNet。
注:以下示例代码,均在 Kaggle Notebooks 执行。(使用GPU的环境配置参见 GPU计算 小节)

import tensorflow as tf
print(tf.__version__)

import numpy as np
2.3.0
(1) 设置memory_growth
gpus = tf.config.experimental.list_physical_devices(device_type='GPU')
print(gpus)

for gpu in gpus:
    tf.config.experimental.set_memory_growth(gpu, True)

输出:

[PhysicalDevice(name='/physical_device:GPU:0', device_type='GPU')]
(2) AlexNet模型
net = tf.keras.models.Sequential([
    tf.keras.layers.Conv2D(filters=96, kernel_size=11, strides=4, padding='same', activation='relu'),
    tf.keras.layers.MaxPool2D(pool_size=3, strides=2),
    tf.keras.layers.Conv2D(filters=256, kernel_size=5, strides=5, padding='same', activation='relu'),
    tf.keras.layers.MaxPool2D(pool_size=3, strides=2),
    tf.keras.layers.Conv2D(filters=384, kernel_size=3, strides=3, padding='same', activation='relu'),
    tf.keras.layers.Conv2D(filters=384, kernel_size=3, strides=3, padding='same', activation='relu'),
    tf.keras.layers.Conv2D(filters=256, kernel_size=3, strides=3, padding='same', activation='relu'),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(units=4096, activation='relu'),
    tf.keras.layers.Dropout(0.5),
    tf.keras.layers.Dense(units=4096, activation='relu'),
    tf.keras.layers.Dropout(0.5),
    tf.keras.layers.Dense(units=10, activation='sigmoid')
])
(3) 数据样本/输出形状

构造一个高和宽均为224的单通道数据样本,来观察每一层的输出形状:

X = tf.random.uniform(shape=(1, 224, 224, 1))

for layer in net.layers:
    X = layer(X)
    print(layer.name, "output shape: ", X.shape)

输出:

conv2d_5 output shape:  (1, 54, 54, 96)
max_pooling2d_3 output shape:  (1, 26, 26, 96)
conv2d_6 output shape:  (1, 26, 26, 256)
max_pooling2d_4 output shape:  (1, 12, 12, 256)
conv2d_7 output shape:  (1, 12, 12, 384)
conv2d_8 output shape:  (1, 12, 12, 384)
conv2d_9 output shape:  (1, 12, 12, 256)
max_pooling2d_5 output shape:  (1, 5, 5, 256)
flatten_1 output shape:  (1, 6400)
dense_3 output shape:  (1, 4096)
dropout_2 output shape:  (1, 4096)
dense_4 output shape:  (1, 4096)
dropout_3 output shape:  (1, 4096)
dense_5 output shape:  (1, 10)

6.2.3 模型训练

数据集

因ImageNet数据集训练时间较长,故沿用Fashion-MNIST数据集来进行演示。

读取数据时,额外将图像高和宽扩大到AlexNet使用的图像高和宽224。该操作可以通过tf.image.resize_with_pad实现。

class DataLoader():
    
    def __init__(self):
                
#         fashion_mnist = tf.keras.datasets.fashion_mnist
#         (self.train_images, self.train_labels), (self.test_images, self.test_labels) = fashion_mnist.load_data()

        # load data from local
        
        with open("../input/fashionmnist/train-labels-idx1-ubyte", 'rb') as f:
            self.train_labels = np.frombuffer(f.read(), np.uint8, offset=8)
    
        with open("../input/fashionmnist/train-images-idx3-ubyte", 'rb') as f:
            self.train_images = np.frombuffer(f.read(), np.uint8, offset=16).reshape(len(self.train_labels), 28, 28)
            
        with open("../input/fashionmnist/t10k-labels-idx1-ubyte", 'rb') as f:
            self.test_labels = np.frombuffer(f.read(), np.uint8, offset=8)
            
        with open("../input/fashionmnist/t10k-images-idx3-ubyte", 'rb') as f:
            self.test_images = np.frombuffer(f.read(), np.uint8, offset=16).reshape(len(self.test_labels), 28, 28)
        
        
        # np.expand_dims(images, axis=-1) -- convert (10000, 28, 28) into (10000, 28, 28, 1)
        
        self.train_images = np.expand_dims(self.train_images.astype(np.float32)/255.0,axis=-1)
        self.test_images = np.expand_dims(self.test_images.astype(np.float32)/255.0,axis=-1)
        
        self.train_labels = self.train_labels.astype(np.int32)
        self.test_labels = self.test_labels.astype(np.int32)
        
        self.num_train, self.num_test = self.train_images.shape[0], self.test_images.shape[0]

        
    def get_batch_train(self, batch_size):
        """
        Examples
        --------
        >>> np.random.randint(0, 10, size=2) 
        array([5, 7])
        
        """
        index = np.random.randint(0, np.shape(self.train_images)[0], batch_size)
        resized_images = tf.image.resize_with_pad(self.train_images[index],224,224)
        return resized_images.numpy(), self.train_labels[index]

    
    def get_batch_test(self, batch_size):
        index = np.random.randint(0, np.shape(self.test_images)[0], batch_size)
        resized_images = tf.image.resize_with_pad(self.test_images[index],224,224)
        return resized_images.numpy(), self.test_labels[index]

    
batch_size = 128
dataLoader = DataLoader()
x_batch, y_batch = dataLoader.get_batch_train(batch_size)
print("x_batch shape:",x_batch.shape,"y_batch shape:", y_batch.shape)

输出:

x_batch shape: (128, 224, 224, 1) y_batch shape: (128,)

训练
def train_alexnet():
    epoch = 5
    num_iter = dataLoader.num_train//batch_size
    for e in range(epoch):
        for n in range(num_iter):
            x_batch, y_batch = dataLoader.get_batch_train(batch_size)
            net.fit(x_batch, y_batch)
            if n%20 == 0:
                net.save_weights("5.6_alexnet_weights.h5")
                
optimizer = tf.keras.optimizers.SGD(learning_rate=0.01, momentum=0.0, nesterov=False)

net.compile(optimizer=optimizer,
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

train_alexnet()

输出:

4/4 [==============================] - 0s 17ms/step - loss: 0.2534 - accuracy: 0.8984
4/4 [==============================] - 0s 17ms/step - loss: 0.2950 - accuracy: 0.8906
4/4 [==============================] - 0s 16ms/step - loss: 0.3390 - accuracy: 0.8984
4/4 [==============================] - 0s 16ms/step - loss: 0.3205 - accuracy: 0.8984
4/4 [==============================] - 0s 17ms/step - loss: 0.2469 - accuracy: 0.8828
4/4 [==============================] - 0s 16ms/step - loss: 0.2529 - accuracy: 0.9219
4/4 [==============================] - 0s 17ms/step - loss: 0.3139 - accuracy: 0.8750
4/4 [==============================] - 0s 17ms/step - loss: 0.2944 - accuracy: 0.8906
4/4 [==============================] - 0s 17ms/step - loss: 0.3641 - accuracy: 0.8906
4/4 [==============================] - 0s 17ms/step - loss: 0.2920 - accuracy: 0.9062
4/4 [==============================] - 0s 17ms/step - loss: 0.1994 - accuracy: 0.9141
4/4 [==============================] - 0s 16ms/step - loss: 0.3823 - accuracy: 0.8906
4/4 [==============================] - 0s 16ms/step - loss: 0.2974 - accuracy: 0.8438
4/4 [==============================] - 0s 17ms/step - loss: 0.2418 - accuracy: 0.9062
4/4 [==============================] - 0s 17ms/step - loss: 0.2980 - accuracy: 0.8594
4/4 [==============================] - 0s 17ms/step - loss: 0.4521 - accuracy: 0.8750
4/4 [==============================] - 0s 17ms/step - loss: 0.2806 - accuracy: 0.8672
4/4 [==============================] - 0s 16ms/step - loss: 0.3294 - accuracy: 0.8828
4/4 [==============================] - 0s 25ms/step - loss: 0.2677 - accuracy: 0.8828
4/4 [==============================] - 0s 18ms/step - loss: 0.2299 - accuracy: 0.9062
4/4 [==============================] - 0s 18ms/step - loss: 0.3232 - accuracy: 0.8750
4/4 [==============================] - 0s 19ms/step - loss: 0.5073 - accuracy: 0.8047
4/4 [==============================] - 0s 17ms/step - loss: 0.2525 - accuracy: 0.9062
4/4 [==============================] - 0s 17ms/step - loss: 0.3440 - accuracy: 0.8828
4/4 [==============================] - 0s 19ms/step - loss: 0.3833 - accuracy: 0.8672
4/4 [==============================] - 0s 17ms/step - loss: 0.3379 - accuracy: 0.8984
4/4 [==============================] - 0s 18ms/step - loss: 0.3130 - accuracy: 0.8828
4/4 [==============================] - 0s 17ms/step - loss: 0.3373 - accuracy: 0.9062
4/4 [==============================] - 0s 18ms/step - loss: 0.3243 - accuracy: 0.8750
4/4 [==============================] - 0s 17ms/step - loss: 0.3358 - accuracy: 0.8672
4/4 [==============================] - 0s 18ms/step - loss: 0.2895 - accuracy: 0.8906
4/4 [==============================] - 0s 18ms/step - loss: 0.1780 - accuracy: 0.9453
4/4 [==============================] - 0s 20ms/step - loss: 0.2232 - accuracy: 0.9453
4/4 [==============================] - 0s 17ms/step - loss: 0.2395 - accuracy: 0.9141
4/4 [==============================] - 0s 17ms/step - loss: 0.3208 - accuracy: 0.8672
4/4 [==============================] - 0s 17ms/step - loss: 0.3595 - accuracy: 0.8594
4/4 [==============================] - 0s 16ms/step - loss: 0.2626 - accuracy: 0.8906
4/4 [==============================] - 0s 16ms/step - loss: 0.3474 - accuracy: 0.8828
4/4 [==============================] - 0s 16ms/step - loss: 0.3512 - accuracy: 0.8672
4/4 [==============================] - 0s 17ms/step - loss: 0.3803 - accuracy: 0.8750
4/4 [==============================] - 0s 17ms/step - loss: 0.3640 - accuracy: 0.8984
4/4 [==============================] - 0s 17ms/step - loss: 0.2280 - accuracy: 0.9297
4/4 [==============================] - 0s 17ms/step - loss: 0.3372 - accuracy: 0.8984
4/4 [==============================] - 0s 18ms/step - loss: 0.2151 - accuracy: 0.9375
4/4 [==============================] - 0s 17ms/step - loss: 0.3686 - accuracy: 0.8594
4/4 [==============================] - 0s 18ms/step - loss: 0.3178 - accuracy: 0.8984
4/4 [==============================] - 0s 19ms/step - loss: 0.3098 - accuracy: 0.9062
4/4 [==============================] - 0s 26ms/step - loss: 0.2634 - accuracy: 0.9141
4/4 [==============================] - 0s 23ms/step - loss: 0.2594 - accuracy: 0.9453
4/4 [==============================] - 0s 18ms/step - loss: 0.1836 - accuracy: 0.9375
4/4 [==============================] - 0s 18ms/step - loss: 0.3415 - accuracy: 0.8125
4/4 [==============================] - 0s 17ms/step - loss: 0.2373 - accuracy: 0.9219
4/4 [==============================] - 0s 17ms/step - loss: 0.3735 - accuracy: 0.8906
4/4 [==============================] - 0s 17ms/step - loss: 0.2722 - accuracy: 0.9219
4/4 [==============================] - 0s 17ms/step - loss: 0.2925 - accuracy: 0.8672
4/4 [==============================] - 0s 18ms/step - loss: 0.3108 - accuracy: 0.9141
4/4 [==============================] - 0s 17ms/step - loss: 0.2356 - accuracy: 0.9141
4/4 [==============================] - 0s 17ms/step - loss: 0.2588 - accuracy: 0.9062
4/4 [==============================] - 0s 17ms/step - loss: 0.3716 - accuracy: 0.8438
4/4 [==============================] - 0s 17ms/step - loss: 0.3621 - accuracy: 0.8672

评估

读入训练好的参数,取测试数据计算测试准确率:

net.load_weights("5.6_alexnet_weights.h5")

x_test, y_test = dataLoader.get_batch_test(2000)
net.evaluate(x_test, y_test, verbose=2)

输出:

63/63 - 1s - loss: 0.3624 - accuracy: 0.8710
[0.362375944852829, 0.8709999918937683]


参考

《动手学深度学习》(TF2.0版)
A. Krizhevsky, I. Sutskever, and G. Hinton. Imagenet classification with deep convolutional neural networks. In NIPS, 2012.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值