您可能已经听说过图像或人脸识别,或自动驾驶汽车。这些都是真实场景下卷积神经网络(CNN)的应用。在这篇博文中,您将学习并理解如何在 Keras 中实现这种深层、前馈的人工神经网络,同时学习如何使用名为 "dropout" 的正则化技术来克服过拟合。
更具体地说,今天的教程将涵盖以下主题:
首先介绍卷积神经网络;
接着,先理解数据。您将使用 Python 及其库来加载、探索和分析数据,
然后,您将进行数据预处理:学习如何调整尺寸、缩放、将标签转换为独热编码向量,并将数据拆分为训练集和验证集;
完成以上步骤后,您就可以构建神经网络模型:学习如何对数据建模并搭建网络。随后,编译、训练并评估模型,并可视化准确率与损失曲线;
接下来,您将了解过拟合的概念,以及如何通过添加 dropout 层来缓解它;
基于这些信息,您可以回到最初的模型并重新训练。您还将重新评估新模型,并比较两个模型的结果;
然后,您将对测试数据进行预测,将概率转换为类别标签,并绘制一些模型分类正确和错误的测试样本;
最后,您将可视化分类报告,从中更深入地理解模型在哪些类别上(不)准确。
想在 Python 中系统学习 Keras 与深度学习吗?不妨考虑 DataCamp 的Python 深度学习入门 课程!
另外别错过我们的Keras 速查表 ,它通过代码示例展示用 Python 构建神经网络所需经历的六个步骤!
卷积神经网络:简介
此时,您或许已经了解了机器学习和深度学习——一门研究能学习的算法设计的计算机科学分支。深度学习是机器学习的一个子领域,受人工神经网络启发,而人工神经网络又受生物神经网络启发。
这类深度神经网络中的一种就是卷积网络,通常称为 CNN 或 ConvNet。它是一种深层、前馈的人工神经网络。请记住 ,前馈神经网络也称为多层感知机(MLP),是典型的深度学习模型。之所以称为 "前馈",是因为信息自左向右流经模型,没有将模型输出反馈回自身的反馈连接。
CNN 特别受生物视觉皮层启发。视觉皮层有对视觉场中特定区域敏感的小细胞群。这一思想在 Hubel 和 Wiesel 于 1962 年进行的一项引人入胜的实验中得到扩展(若想了解更多,请看这个视频 )。在实验中,研究者发现大脑中的一些单个神经元只在出现某种取向的边缘(如垂直或水平边缘)时才会被激活。例如,有些神经元在呈现垂直边时会触发,有些在呈现水平边时会触发。Hubel 和 Wiesel 发现所有这些神经元按柱状方式有序排列,并能共同产生视觉感知。这个关于系统内部的专门化组件各司其职的理念也被机器所借鉴,并能在 CNN 中找到体现。
卷积神经网络是计算机视觉领域最具影响力的创新之一。它们的表现远超传统计算机视觉方法,并产出了最先进的结果。这些网络在许多真实案例与应用中证明了成功,例如:
图像分类、目标检测、分割、人脸识别;
使用基于 CNN 的视觉系统的自动驾驶汽车;
利用卷积神经网络进行晶体结构分类;
当然,还有更多!
要理解这种成功,您需要回到 2012 年,那一年 Alex Krizhevsky 使用卷积神经网络赢得了当年的 ImageNet 比赛,将分类错误率从 26% 降至 15%。
注意 ,ImageNet 大规模视觉识别挑战赛(ILSVRC)始于 2010 年,是一项年度竞赛,研究团队在给定数据集上评估其算法,并在多个视觉识别任务上竞争更高的准确率。
也是在那个时期,神经网络在一段时间后再次崛起 。这常被称为“神经网络的第三波”。前两波分别发生在 20 世纪 40 至 60 年代以及 70 至 80 年代。
好的,您知道我们将使用受生物视觉皮层启发的前馈网络,但这究竟意味着什么?
看看下面的图片:
图中显示,您将图像作为输入喂入网络,图像经过多次卷积、下采样、全连接层,最后输出结果。
但这些概念都是什么?
卷积层计算与输入中的局部区域(感受野)相连接的神经元输出,每个输出是其权重与输入体中小感受野的点积。每次计算都会从输入图像中提取一个特征图。换言之,想象您有一幅用 5x5 数值矩阵表示的图像,取一个 3x3 的矩阵,将这个 3x3 的窗口(或称核)在图像上滑动。在该矩阵的每个位置,您将 3x3 窗口的数值与当前窗口覆盖的图像数值逐一相乘。结果得到一个数字,代表该图像窗口中的所有数值。您用这一层来做滤波:窗口在图像上移动时,您在该区域内检查某些模式。这得益于滤波器,它与卷积输出的数值相乘。
下采样的目标是通过降低维度来获得输入的表示,有助于减少过拟合。下采样的一种技术是最大池化。该技术会根据区域大小选择其中的最高像素值。换言之,最大池化从当前由核覆盖的图像窗口中取最大值。例如,大小为 2 x 2 的最大池化层会从 2 x 2 区域中选取像素强度的最大值。您没错,池化层的工作方式与卷积层很像!同样取一个核或窗口并在图像上移动;唯一的区别在于应用于核与图像窗口的函数不是线性的。
全连接层的目标是将卷积层学到的高级特征展平并加以组合。它将展平的输出传递到输出层,在那里您使用 softmax 分类器或 sigmoid 来预测输入的类别标签。
想了解更多信息,请前往这里 。
Fashion-MNIST 数据集
在继续加载数据之前,先看看您将处理的究竟是什么!Fashion-MNIST 数据集由 Zalando 的商品图片组成,包含 10 个类别共 70,000 张 28x28 的灰度图像,每类 7,000 张。训练集包含 60,000 张,测试集包含 10,000 张。稍后加载数据后,您可以再次核对这些信息!;)
Fashion-MNIST 与您可能已熟悉的 MNIST 数据集相似,后者用于手写数字分类。这意味着图像尺寸以及训练/测试划分与 MNIST 类似。提示 :如果您想学习如何用后者数据集实现用于分类任务的多层感知机(MLP),请查看这篇 Keras 深度学习教程 。
您可以在这里 找到 Fashion-MNIST 数据集,但也可以借助特定的 TensorFlow 和 Keras 模块直接加载。下一节将展示其工作方式!
加载数据
Keras 自带一个名为 datasets 的库,可开箱即用地加载数据集:您从服务器下载数据,从而加速流程,因为无需手动下载到本地。训练与测试图像及其标签分别加载并存储在 train_X、train_Y、test_X、test_Y 变量中。
from keras. datasets import fashion_mnist
( train_X, train_Y) , ( test_X, test_Y) = fashion_mnist. load_data( )
技术支持
Using TensorFlow backend.
技术支持
太好了!是不是很简单?
到现在为止,您可能已经做过无数次了,但这始终是入手的关键一步。现在,您已经完全准备好开始分析、处理和建模数据!
分析数据
现在我们来看看数据集中图像的样子。尽管此时您已经知道图像的尺寸,但仍值得以编程方式分析它:您可能需要对像素进行缩放并调整图像尺寸。
import numpy as np
from keras. utils import to_categorical
import matplotlib. pyplot as plt
% matplotlib inline
print ( 'Training data shape : ' , train_X. shape, train_Y. shape)
print ( 'Testing data shape : ' , test_X. shape, test_Y. shape)
技术支持
( 'Training data shape : ' , ( 60000 , 28 , 28 ) , ( 60000 , ) )
( 'Testing data shape : ' , ( 10000 , 28 , 28 ) , ( 10000 , ) )
技术支持
从上述输出可见,训练数据形状为 60000 x 28 x 28,因为共有 60,000 个训练样本,每个样本尺寸为 28 x 28。类似地,测试数据形状为 10000 x 28 x 28,因为共有 10,000 个测试样本。
classes = np. unique( train_Y)
nClasses = len ( classes)
print ( 'Total number of outputs : ' , nClasses)
print ( 'Output classes : ' , classes)
技术支持
( 'Total number of outputs : ' , 10 )
( 'Output classes : ' , array ( [ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ] , dtype= uint8) )
技术支持
总共有从 0 到 9 的十个输出类别。
另外,别忘了看看数据集中图像的样子:
plt. figure( figsize= [ 5 , 5 ] )
plt. subplot( 121 )
plt. imshow( train_X[ 0 , : , : ] , cmap= 'gray' )
plt. title( "Ground Truth : {}" . format ( train_Y[ 0 ] ) )
plt. subplot( 122 )
plt. imshow( test_X[ 0 , : , : ] , cmap= 'gray' )
plt. title( "Ground Truth : {}" . format ( test_Y[ 0 ] ) )
技术支持
Text ( 0.5 , 1 , u'Ground Truth : 9' )
技术支持
上面两幅图的输出看起来像是一双踝靴,其类别标签为 9。类似地,其他服饰会有不同标签,但相似的商品会有相同标签。这意味着所有 7,000 张踝靴图片的类别标签都为 9。
数据预处理
如上图所示,图像为灰度图,像素值范围为 0 到 255。此外,这些图像的尺寸为 28 x 28。因此,在将数据喂入模型前需要进行预处理。
第一步,将训练集和测试集中的每张 28 x 28 图像转换为 28 x 28 x 1 的矩阵,以便输入网络。
train_X = train_X. reshape( - 1 , 28 , 28 , 1 )
test_X = test_X. reshape( - 1 , 28 , 28 , 1 )
train_X. shape, test_X. shape
技术支持
( ( 60000 , 28 , 28 , 1 ) , ( 10000 , 28 , 28 , 1 ) )
技术支持
当前数据类型为 int8,因此在输入网络前需要将其转换为 float32,并将像素值缩放到 0 - 1(含)范围。我们来处理一下!
train_X = train_X. astype( 'float32' )
test_X = test_X. astype( 'float32' )
train_X = train_X / 255.
test_X = test_X / 255.
技术支持
在独热编码中,您将分类数据转换为一个数值向量。之所以进行独热编码,是因为机器学习算法无法直接处理分类数据。您会为每个类别生成一个布尔列。对于每个样本,只有其中一列取值为 1。因此称为独热编码。
在您的问题中,独热编码为行向量,对每张图像,维度为 1 x 10。这里要注意的是,向量除代表其类别的位置为 1 外,其余均为 0。例如,上面绘制的踝靴图片标签为 9,因此所有踝靴图片的独热编码向量为 [0 0 0 0 0 0 0 0 1 0]。
我们将训练与测试标签转换为独热编码向量:
train_Y_one_hot = to_categorical( train_Y)
test_Y_one_hot = to_categorical( test_Y)
print ( 'Original label:' , train_Y[ 0 ] )
print ( 'After conversion to one-hot:' , train_Y_one_hot[ 0 ] )
技术支持
( 'Original label:' , 9 )
( 'After conversion to one-hot:' , array ( [ 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. , 1. ] ) )
技术支持
很清楚,对吧?注意 ,您也可以打印 train_Y_one_hot,它将显示一个 60000 x 10 的矩阵,每行对应一张图像的独热编码。
最后一步至关重要。在机器学习或任何与数据相关的任务中,您都应该正确划分数据。为使模型具有良好的泛化能力,您需要将训练数据分为两部分,一部分用于训练,另一部分用于验证。在本例中,您将在 80\% 的训练数据上训练模型,并在剩余 20\% 的训练数据上进行验证。这样也有助于减少过拟合,因为您将在训练阶段未见过的数据上验证模型,有利于提升测试性能。
from sklearn. model_selection import train_test_split
train_X, valid_X, train_label, valid_label = train_test_split( train_X, train_Y_one_hot, test_size= 0.2 , random_state= 13 )
技术支持
最后再检查一下训练集和验证集的形状。
train_X. shape, valid_X. shape, train_label. shape, valid_label. shape
技术支持
( ( 48000 , 28 , 28 , 1 ) , ( 12000 , 28 , 28 , 1 ) , ( 48000 , 10 ) , ( 12000 , 10 ) )
技术支持
网络结构
图像尺寸为 28 x 28。您将图像矩阵转换为数组,将其缩放到 0 到 1 之间,重塑为 28 x 28 x 1,并将其作为输入喂入网络。
您将使用三个卷积层:
第一层含有 32 个 3 x 3 的滤波器,
第二层含有 64 个 3 x 3 的滤波器,
第三层含有 128 个 3 x 3 的滤波器。
此外,还有三个 2 x 2 的最大池化层。
图:模型架构
对数据建模
首先,导入训练模型所需的所有模块。
import keras
from keras. models import Sequential, Input, Model
from keras. layers import Dense, Dropout, Flatten
from keras. layers import Conv2D, MaxPooling2D
from keras. layers. normalization import BatchNormalization
from keras. layers. advanced_activations import LeakyReLU
技术支持
您将使用 64 的批大小。也可以选择更大的批大小如 128 或 256,具体取决于内存。这对学习参数的确定影响很大,并会影响预测精度。您将训练 20 个轮次(epochs)。
batch_size = 64
epochs = 20
num_classes = 10
技术支持
神经网络架构
在 Keras 中,您可以通过逐个添加所需层来堆叠网络层。您将在这里这么做:首先用 Conv2D() 添加第一个卷积层。注意使用该函数是因为您在处理图像!接着添加 Leaky ReLU 激活函数,帮助网络学习非线性决策边界。由于您有十个不同的类别,需要一个能分隔这十个线性不可分类别的非线性决策边界。
更具体地说,您添加 Leaky ReLU 是为了解决“ReLU 失活”问题。ReLU 激活函数在神经网络架构中被广泛使用,尤其在卷积网络中,其效果优于曾经广泛使用的 logistic sigmoid 函数。截至 2017 年,它是深度神经网络中最流行的激活函数。ReLU 函数使激活在零处被阈值化。然而在训练过程中,ReLU 单元可能会“失活”。当一个较大的梯度通过 ReLU 神经元时,可能导致权重更新使得该神经元不再对任何数据点激活。一旦发生,从那以后流经该单元的梯度将永远为零。Leaky ReLU 试图解决这一点:函数不为零,而是具有一个很小的负斜率。
接下来,您将添加最大池化层 MaxPooling2D(),依此类推。最后一层是具有 10 个单元并带有 softmax 激活函数的 Dense 层,这是该多分类问题所需的。
fashion_model = Sequential( )
fashion_model. add( Conv2D( 32 , kernel_size= ( 3 , 3 ) , activation= 'linear' , input_shape= ( 28 , 28 , 1 ) , padding= 'same' ) )
fashion_model. add( LeakyReLU( alpha= 0.1 ) )
fashion_model. add( MaxPooling2D( ( 2 , 2 ) , padding= 'same' ) )
fashion_model. add( Conv2D( 64 , ( 3 , 3 ) , activation= 'linear' , padding= 'same' ) )
fashion_model. add( LeakyReLU( alpha= 0.1 ) )
fashion_model. add( MaxPooling2D( pool_size= ( 2 , 2 ) , padding= 'same' ) )
fashion_model. add( Conv2D( 128 , ( 3 , 3 ) , activation= 'linear' , padding= 'same' ) )
fashion_model. add( LeakyReLU( alpha= 0.1 ) )
fashion_model. add( MaxPooling2D( pool_size= ( 2 , 2 ) , padding= 'same' ) )
fashion_model. add( Flatten( ) )
fashion_model. add( Dense( 128 , activation= 'linear' ) )
fashion_model. add( LeakyReLU( alpha= 0.1 ) )
fashion_model. add( Dense( num_classes, activation= 'softmax' ) )
技术支持
编译模型
创建模型后,使用 Adam 优化器对其进行编译,这是最受欢迎的优化算法之一。您可以在这里 了解更多。此外,您将损失类型指定为用于多分类的类别交叉熵(也可以使用二元交叉熵作为损失函数)。最后,指定需要在模型训练时分析的指标为准确率。
fashion_model. compile ( loss= keras. losses. categorical_crossentropy, optimizer= keras. optimizers. Adam( ) , metrics= [ 'accuracy' ] )
技术支持
让我们用 summary 函数可视化您在上一步创建的各层。它会显示每一层中的一些参数(权重和偏置),以及模型的参数总数。
fashion_model. summary( )
技术支持
_________________________________________________________________
Layer ( type) Output Shape Param #
=== === === === === === === === === === === === === === === === === === === === === ==
conv2d_51 ( Conv2D) ( None, 28 , 28 , 32 ) 320
_________________________________________________________________
leaky_re_lu_57 ( LeakyReLU) ( None, 28 , 28 , 32 ) 0
_________________________________________________________________
max_pooling2d_49 ( MaxPooling ( None, 14 , 14 , 32 ) 0
_________________________________________________________________
conv2d_52 ( Conv2D) ( None, 14 , 14 , 64 ) 18496
_________________________________________________________________
leaky_re_lu_58 ( LeakyReLU) ( None, 14 , 14 , 64 ) 0
_________________________________________________________________
max_pooling2d_50 ( MaxPooling ( None, 7 , 7 , 64 ) 0
_________________________________________________________________
conv2d_53 ( Conv2D) ( None, 7 , 7 , 128 ) 73856
_________________________________________________________________
leaky_re_lu_59 ( LeakyReLU) ( None, 7 , 7 , 128 ) 0
_________________________________________________________________
max_pooling2d_51 ( MaxPooling ( None, 4 , 4 , 128 ) 0
_________________________________________________________________
flatten_17 ( Flatten) ( None, 2048 ) 0
_________________________________________________________________
dense_33 ( Dense) ( None, 128 ) 262272
_________________________________________________________________
leaky_re_lu_60 ( LeakyReLU) ( None, 128 ) 0
_________________________________________________________________
dense_34 ( Dense) ( None, 10 ) 1290
=== === === === === === === === === === === === === === === === === === === === === ==
Total params: 356 , 234
Trainable params: 356 , 234
Non- trainable params: 0
_________________________________________________________________
技术支持
训练模型
终于到了用 Keras 的 fit() 函数训练模型的时候!模型将训练 20 个 epoch。fit() 会返回一个 history 对象;将其结果存为 fashion_train,您随后可以用它来绘制训练与验证的准确率和损失曲线,从而直观分析模型表现。
fashion_train = fashion_model. fit( train_X, train_label, batch_size= batch_size, epochs= epochs, verbose= 1 , validation_data= ( valid_X, valid_label) )
技术支持
Train on 48000 samples, validate on 12000 samples
Epoch 1 / 20
48000 / 48000 [ === === === === === === === === === === ] - 60s 1ms/ step - loss: 0.4661 - acc: 0.8311 - val_loss: 0.3320 - val_acc: 0.8809
Epoch 2 / 20
48000 / 48000 [ === === === === === === === === === === ] - 60s 1ms/ step - loss: 0.2874 - acc: 0.8951 - val_loss: 0.2781 - val_acc: 0.8963
Epoch 3 / 20
48000 / 48000 [ === === === === === === === === === === ] - 60s 1ms/ step - loss: 0.2420 - acc: 0.9111 - val_loss: 0.2501 - val_acc: 0.9077
Epoch 4 / 20
48000 / 48000 [ === === === === === === === === === === ] - 59s 1ms/ step - loss: 0.2088 - acc: 0.9226 - val_loss: 0.2369 - val_acc: 0.9147
Epoch 5 / 20
48000 / 48000 [ === === === === === === === === === === ] - 59s 1ms/ step - loss: 0.1838 - acc: 0.9324 - val_loss: 0.2602 - val_acc: 0.9070
Epoch 6 / 20
48000 / 48000 [ === === === === === === === === === === ] - 59s 1ms/ step - loss: 0.1605 - acc: 0.9396 - val_loss: 0.2264 - val_acc: 0.9193
Epoch 7 / 20
48000 / 48000 [ === === === === === === === === === === ] - 59s 1ms/ step - loss: 0.1356 - acc: 0.9488 - val_loss: 0.2566 - val_acc: 0.9180
Epoch 8 / 20
48000 / 48000 [ === === === === === === === === === === ] - 59s 1ms/ step - loss: 0.1186 - acc: 0.9553 - val_loss: 0.2556 - val_acc: 0.9149
Epoch 9 / 20
48000 / 48000 [ === === === === === === === === === === ] - 59s 1ms/ step - loss: 0.0985 - acc: 0.9634 - val_loss: 0.2681 - val_acc: 0.9204
Epoch 10 / 20
48000 / 48000 [ === === === === === === === === === === ] - 59s 1ms/ step - loss: 0.0873 - acc: 0.9670 - val_loss: 0.2712 - val_acc: 0.9221
Epoch 11 / 20
48000 / 48000 [ === === === === === === === === === === ] - 59s 1ms/ step - loss: 0.0739 - acc: 0.9721 - val_loss: 0.2757 - val_acc: 0.9202
Epoch 12 / 20
48000 / 48000 [ === === === === === === === === === === ] - 60s 1ms/ step - loss: 0.0628 - acc: 0.9767 - val_loss: 0.3126 - val_acc: 0.9132
Epoch 13 / 20
48000 / 48000 [ === === === === === === === === === === ] - 61s 1ms/ step - loss: 0.0569 - acc: 0.9789 - val_loss: 0.3556 - val_acc: 0.9081
Epoch 14 / 20
48000 / 48000 [ === === === === === === === === === === ] - 60s 1ms/ step - loss: 0.0452 - acc: 0.9833 - val_loss: 0.3441 - val_acc: 0.9189
Epoch 15 / 20
48000 / 48000 [ === === === === === === === === === === ] - 60s 1ms/ step - loss: 0.0421 - acc: 0.9847 - val_loss: 0.3400 - val_acc: 0.9165
Epoch 16 / 20
48000 / 48000 [ === === === === === === === === === === ] - 60s 1ms/ step - loss: 0.0379 - acc: 0.9861 - val_loss: 0.3876 - val_acc: 0.9195
Epoch 17 / 20
48000 / 48000 [ === === === === === === === === === === ] - 60s 1ms/ step - loss: 0.0405 - acc: 0.9855 - val_loss: 0.4112 - val_acc: 0.9164
Epoch 18 / 20
48000 / 48000 [ === === === === === === === === === === ] - 60s 1ms/ step - loss: 0.0285 - acc: 0.9897 - val_loss: 0.4150 - val_acc: 0.9181
Epoch 19 / 20
48000 / 48000 [ === === === === === === === === === === ] - 61s 1ms/ step - loss: 0.0322 - acc: 0.9877 - val_loss: 0.4584 - val_acc: 0.9196
Epoch 20 / 20
48000 / 48000 [ === === === === === === === === === === ] - 61s 1ms/ step - loss: 0.0262 - acc: 0.9906 - val_loss: 0.4396 - val_acc: 0.9205
技术支持
终于!您在 Fashion-MNIST 上训练了 20 个 epoch。观察训练准确率与损失,可以说模型完成得不错:20 个 epoch 后训练准确率达 99%,训练损失也很低。
不过,模型似乎过拟合了,因为验证损失为 0.4396,验证准确率为 92%。过拟合表明网络很好地记住了训练数据,但并不能保证在未见过的数据上表现良好,这也是训练与验证准确率存在差异的原因。
您可能需要处理这个问题。接下来,您将学习如何在保持其他层不变的情况下,通过在网络中添加 Dropout 层让模型表现更好。
但在下结论之前,先在测试集上评估模型表现。
在测试集上评估模型
test_eval = fashion_model. evaluate( test_X, test_Y_one_hot, verbose= 0 )
技术支持
print ( 'Test loss:' , test_eval[ 0 ] )
print ( 'Test accuracy:' , test_eval[ 1 ] )
技术支持
( 'Test loss:' , 0.46366268818555401 )
( 'Test accuracy:' , 0.91839999999999999 )
技术支持
测试准确率看起来相当不错。结果显示,您的分类器优于此处 报告的基线(平均准确率 0.897 的 SVM 分类器)。同时,相比在 GitHub 上(Fashion-MNIST 数据集作者)提到的一些深度学习模型,您的模型表现也不差。
然而,您已经看到模型似乎过拟合了。这些结果真的就足够好吗?
让我们结合可视化来评估模型,绘制训练与验证数据之间的准确率和损失曲线:
accuracy = fashion_train. history[ 'acc' ]
val_accuracy = fashion_train. history[ 'val_acc' ]
loss = fashion_train. history[ 'loss' ]
val_loss = fashion_train. history[ 'val_loss' ]
epochs = range ( len ( accuracy) )
plt. plot( epochs, accuracy, 'bo' , label= 'Training accuracy' )
plt. plot( epochs, val_accuracy, 'b' , label= 'Validation accuracy' )
plt. title( 'Training and validation accuracy' )
plt. legend( )
plt. figure( )
plt. plot( epochs, loss, 'bo' , label= 'Training loss' )
plt. plot( epochs, val_loss, 'b' , label= 'Validation loss' )
plt. title( 'Training and validation loss' )
plt. legend( )
plt. show( )
技术支持
从上述两幅图可见,验证准确率在第 4-5 个 epoch 后几乎停滞,只有在某些 epoch 稍有上升。起初验证准确率与损失线性上升,但随后增幅不大。
验证损失表明出现了过拟合的迹象,与验证准确率类似,它先线性下降,但在第 4-5 个 epoch 后开始上升。这意味着模型尝试记住数据并且成功了。
考虑到这一点,现在是时候在模型中引入一些 dropout,看看是否有助于减少过拟合。
在网络中加入 Dropout
您可以添加一个 dropout 层,在一定程度上克服过拟合问题。Dropout 会在训练过程中随机关闭一部分神经元,从而在一定程度上降低对训练集的依赖。关闭多少比例的神经元由一个可调的超参数决定。通过关闭部分神经元,网络将无法记住训练数据,因为并非所有神经元会同时处于激活状态,未激活的神经元也无法学习任何东西。
现在我们重新创建、编译并训练网络,这次加入 dropout。并以批大小 64 训练 20 个 epoch。
batch_size = 64
epochs = 20
num_classes = 10
技术支持
fashion_model = Sequential( )
fashion_model. add( Conv2D( 32 , kernel_size= ( 3 , 3 ) , activation= 'linear' , padding= 'same' , input_shape= ( 28 , 28 , 1 ) ) )
fashion_model. add( LeakyReLU( alpha= 0.1 ) )
fashion_model. add( MaxPooling2D( ( 2 , 2 ) , padding= 'same' ) )
fashion_model. add( Dropout( 0.25 ) )
fashion_model. add( Conv2D( 64 , ( 3 , 3 ) , activation= 'linear' , padding= 'same' ) )
fashion_model. add( LeakyReLU( alpha= 0.1 ) )
fashion_model. add( MaxPooling2D( pool_size= ( 2 , 2 ) , padding= 'same' ) )
fashion_model. add( Dropout( 0.25 ) )
fashion_model. add( Conv2D( 128 , ( 3 , 3 ) , activation= 'linear' , padding= 'same' ) )
fashion_model. add( LeakyReLU( alpha= 0.1 ) )
fashion_model. add( MaxPooling2D( pool_size= ( 2 , 2 ) , padding= 'same' ) )
fashion_model. add( Dropout( 0.4 ) )
fashion_model. add( Flatten( ) )
fashion_model. add( Dense( 128 , activation= 'linear' ) )
fashion_model. add( LeakyReLU( alpha= 0.1 ) )
fashion_model. add( Dropout( 0.3 ) )
fashion_model. add( Dense( num_classes, activation= 'softmax' ) )
技术支持
fashion_model. summary( )
技术支持
_________________________________________________________________
Layer ( type) Output Shape Param #
=== === === === === === === === === === === === === === === === === === === === === ==
conv2d_54 ( Conv2D) ( None, 28 , 28 , 32 ) 320
_________________________________________________________________
leaky_re_lu_61 ( LeakyReLU) ( None, 28 , 28 , 32 ) 0
_________________________________________________________________
max_pooling2d_52 ( MaxPooling ( None, 14 , 14 , 32 ) 0
_________________________________________________________________
dropout_29 ( Dropout) ( None, 14 , 14 , 32 ) 0
_________________________________________________________________
conv2d_55 ( Conv2D) ( None, 14 , 14 , 64 ) 18496
_________________________________________________________________
leaky_re_lu_62 ( LeakyReLU) ( None, 14 , 14 , 64 ) 0
_________________________________________________________________
max_pooling2d_53 ( MaxPooling ( None, 7 , 7 , 64 ) 0
_________________________________________________________________
dropout_30 ( Dropout) ( None, 7 , 7 , 64 ) 0
_________________________________________________________________
conv2d_56 ( Conv2D) ( None, 7 , 7 , 128 ) 73856
_________________________________________________________________
leaky_re_lu_63 ( LeakyReLU) ( None, 7 , 7 , 128 ) 0
_________________________________________________________________
max_pooling2d_54 ( MaxPooling ( None, 4 , 4 , 128 ) 0
_________________________________________________________________
dropout_31 ( Dropout) ( None, 4 , 4 , 128 ) 0
_________________________________________________________________
flatten_18 ( Flatten) ( None, 2048 ) 0
_________________________________________________________________
dense_35 ( Dense) ( None, 128 ) 262272
_________________________________________________________________
leaky_re_lu_64 ( LeakyReLU) ( None, 128 ) 0
_________________________________________________________________
dropout_32 ( Dropout) ( None, 128 ) 0
_________________________________________________________________
dense_36 ( Dense) ( None, 10 ) 1290
=== === === === === === === === === === === === === === === === === === === === === ==
Total params: 356 , 234
Trainable params: 356 , 234
Non- trainable params: 0
_________________________________________________________________
技术支持
fashion_model. compile ( loss= keras. losses. categorical_crossentropy, optimizer= keras. optimizers. Adam( ) , metrics= [ 'accuracy' ] )
技术支持
fashion_train_dropout = fashion_model. fit( train_X, train_label, batch_size= batch_size, epochs= epochs, verbose= 1 , validation_data= ( valid_X, valid_label) )
技术支持
Train on 48000 samples, validate on 12000 samples
Epoch 1 / 20
48000 / 48000 [ === === === === === === === === === === ] - 66s 1ms/ step - loss: 0.5954 - acc: 0.7789 - val_loss: 0.3788 - val_acc: 0.8586
Epoch 2 / 20
48000 / 48000 [ === === === === === === === === === === ] - 64s 1ms/ step - loss: 0.3797 - acc: 0.8591 - val_loss: 0.3150 - val_acc: 0.8832
Epoch 3 / 20
48000 / 48000 [ === === === === === === === === === === ] - 64s 1ms/ step - loss: 0.3302 - acc: 0.8787 - val_loss: 0.2836 - val_acc: 0.8961
Epoch 4 / 20
48000 / 48000 [ === === === === === === === === === === ] - 64s 1ms/ step - loss: 0.3034 - acc: 0.8868 - val_loss: 0.2663 - val_acc: 0.9002
Epoch 5 / 20
48000 / 48000 [ === === === === === === === === === === ] - 64s 1ms/ step - loss: 0.2843 - acc: 0.8936 - val_loss: 0.2481 - val_acc: 0.9083
Epoch 6 / 20
48000 / 48000 [ === === === === === === === === === === ] - 64s 1ms/ step - loss: 0.2699 - acc: 0.9002 - val_loss: 0.2469 - val_acc: 0.9032
Epoch 7 / 20
48000 / 48000 [ === === === === === === === === === === ] - 65s 1ms/ step - loss: 0.2561 - acc: 0.9049 - val_loss: 0.2422 - val_acc: 0.9095
Epoch 8 / 20
48000 / 48000 [ === === === === === === === === === === ] - 65s 1ms/ step - loss: 0.2503 - acc: 0.9068 - val_loss: 0.2429 - val_acc: 0.9098
Epoch 9 / 20
48000 / 48000 [ === === === === === === === === === === ] - 65s 1ms/ step - loss: 0.2437 - acc: 0.9096 - val_loss: 0.2230 - val_acc: 0.9173
Epoch 10 / 20
48000 / 48000 [ === === === === === === === === === === ] - 65s 1ms/ step - loss: 0.2307 - acc: 0.9126 - val_loss: 0.2170 - val_acc: 0.9187
Epoch 11 / 20
48000 / 48000 [ === === === === === === === === === === ] - 65s 1ms/ step - loss: 0.2307 - acc: 0.9135 - val_loss: 0.2265 - val_acc: 0.9193
Epoch 12 / 20
48000 / 48000 [ === === === === === === === === === === ] - 65s 1ms/ step - loss: 0.2229 - acc: 0.9160 - val_loss: 0.2136 - val_acc: 0.9229
Epoch 13 / 20
48000 / 48000 [ === === === === === === === === === === ] - 65s 1ms/ step - loss: 0.2202 - acc: 0.9162 - val_loss: 0.2173 - val_acc: 0.9187
Epoch 14 / 20
48000 / 48000 [ === === === === === === === === === === ] - 64s 1ms/ step - loss: 0.2161 - acc: 0.9188 - val_loss: 0.2142 - val_acc: 0.9211
Epoch 15 / 20
48000 / 48000 [ === === === === === === === === === === ] - 65s 1ms/ step - loss: 0.2119 - acc: 0.9196 - val_loss: 0.2133 - val_acc: 0.9233
Epoch 16 / 20
48000 / 48000 [ === === === === === === === === === === ] - 65s 1ms/ step - loss: 0.2073 - acc: 0.9222 - val_loss: 0.2159 - val_acc: 0.9213
Epoch 17 / 20
48000 / 48000 [ === === === === === === === === === === ] - 65s 1ms/ step - loss: 0.2050 - acc: 0.9231 - val_loss: 0.2123 - val_acc: 0.9233
Epoch 18 / 20
48000 / 48000 [ === === === === === === === === === === ] - 64s 1ms/ step - loss: 0.2016 - acc: 0.9238 - val_loss: 0.2191 - val_acc: 0.9235
Epoch 19 / 20
48000 / 48000 [ === === === === === === === === === === ] - 65s 1ms/ step - loss: 0.2001 - acc: 0.9244 - val_loss: 0.2110 - val_acc: 0.9258
Epoch 20 / 20
48000 / 48000 [ === === === === === === === === === === ] - 64s 1ms/ step - loss: 0.1972 - acc: 0.9255 - val_loss: 0.2092 - val_acc: 0.9269
技术支持
我们将模型保存下来,以便您可以直接加载而不必再次训练 20 个 epoch。这样,若之后需要,您可以加载模型并修改架构;或者基于此已保存模型重新开始训练。保存模型(以及模型权重!)总是个好主意——它能节省时间。注意,您也可以在每个 epoch 之后保存模型,这样一旦某个 epoch 出现问题导致训练中断,您不必从头开始。
fashion_model. save( "fashion_model_dropout.h5py" )
技术支持
在测试集上评估模型
最后,也来评估一下新模型的表现!
test_eval = fashion_model. evaluate( test_X, test_Y_one_hot, verbose= 1 )
技术支持
10000 / 10000 [ === === === === === === === === === === ] - 5s 461us/ step
技术支持
print ( 'Test loss:' , test_eval[ 0 ] )
print ( 'Test accuracy:' , test_eval[ 1 ] )
技术支持
( 'Test loss:' , 0.21460009642243386 )
( 'Test accuracy:' , 0.92300000000000004 )
技术支持
哇!看起来在模型中加入 Dropout 的确起作用了,尽管测试准确率提升不大,但与之前的结果相比测试损失降低了。 现在,最后一次绘制训练与验证数据之间的准确率和损失曲线。
accuracy = fashion_train_dropout. history[ 'acc' ]
val_accuracy = fashion_train_dropout. history[ 'val_acc' ]
loss = fashion_train_dropout. history[ 'loss' ]
val_loss = fashion_train_dropout. history[ 'val_loss' ]
epochs = range ( len ( accuracy) )
plt. plot( epochs, accuracy, 'bo' , label= 'Training accuracy' )
plt. plot( epochs, val_accuracy, 'b' , label= 'Validation accuracy' )
plt. title( 'Training and validation accuracy' )
plt. legend( )
plt. figure( )
plt. plot( epochs, loss, 'bo' , label= 'Training loss' )
plt. plot( epochs, val_loss, 'b' , label= 'Validation loss' )
plt. title( 'Training and validation loss' )
plt. legend( )
plt. show( )
技术支持
最终您可以看到,验证损失与验证准确率与训练损失和训练准确率基本同步。尽管验证损失与准确率的曲线并非线性,但它显示出模型没有过拟合:验证损失在下降而不是上升,且训练与验证准确率之间的差距不大。
因此可以说,模型的泛化能力显著提升,因为测试集与验证集上的损失仅略高于训练损失。
预测标签
predicted_classes = fashion_model. predict( test_X)
技术支持
由于得到的预测是浮点数值,直接与真实测试标签比较并不可行。因此,您将对输出进行四舍五入,将浮点数转换为整数。进一步地,您将使用 np.argmax() 来选择每一行中取值更高的索引号。
例如,假设某个测试图像的预测为 0 1 0 0 0 0 0 0 0 0,则输出应为类别标签 1。
predicted_classes = np. argmax( np. round ( predicted_classes) , axis= 1 )
技术支持
predicted_classes. shape, test_Y. shape
技术支持
( ( 10000 , ) , ( 10000 , ) )
技术支持
correct = np. where( predicted_classes== test_Y) [ 0 ]
print "Found %d correct labels" % len ( correct)
for i, correct in enumerate ( correct[ : 9 ] ) :
plt. subplot( 3 , 3 , i+ 1 )
plt. imshow( test_X[ correct] . reshape( 28 , 28 ) , cmap= 'gray' , interpolation= 'none' )
plt. title( "Predicted {}, Class {}" . format ( predicted_classes[ correct] , test_Y[ correct] ) )
plt. tight_layout( )
技术支持
Found 9188 correct labels
技术支持
incorrect = np. where( predicted_classes!= test_Y) [ 0 ]
print "Found %d incorrect labels" % len ( incorrect)
for i, incorrect in enumerate ( incorrect[ : 9 ] ) :
plt. subplot( 3 , 3 , i+ 1 )
plt. imshow( test_X[ incorrect] . reshape( 28 , 28 ) , cmap= 'gray' , interpolation= 'none' )
plt. title( "Predicted {}, Class {}" . format ( predicted_classes[ incorrect] , test_Y[ incorrect] ) )
plt. tight_layout( )
技术支持
Found 812 incorrect labels
技术支持
仅凭观察几张图片,您很难确定模型为何无法正确分类上述图像,但似乎多个类别中存在的相似模式会影响分类器的表现,尽管 CNN 是一种健壮的架构。例如,图 5 与图 6 属于不同类别,但看起来有点相似,可能是夹克,也可能是长袖衬衫。
分类报告
分类报告将帮助我们更详细地识别被误分类的类别。您将能观察到在这十个给定类别中,模型在哪些类别上的表现较差。
from sklearn. metrics import classification_report
target_names = [ "Class {}" . format ( i) for i in range ( num_classes) ]
print ( classification_report( test_Y, predicted_classes, target_names= target_names) )
技术支持
precision recall f1- score support
Class 0 0.77 0.90 0.83 1000
Class 1 0.99 0.98 0.99 1000
Class 2 0.88 0.88 0.88 1000
Class 3 0.94 0.92 0.93 1000
Class 4 0.88 0.87 0.88 1000
Class 5 0.99 0.98 0.98 1000
Class 6 0.82 0.72 0.77 1000
Class 7 0.94 0.99 0.97 1000
Class 8 0.99 0.98 0.99 1000
Class 9 0.98 0.96 0.97 1000
avg / total 0.92 0.92 0.92 10000
技术支持
您可以看到,分类器在类别 6 上的精确率与召回率均表现欠佳。在类别 0 与类别 2 上,分类器的精确率不足。此外,在类别 4 上,分类器的精确率与召回率都略显不足。
继续探索!
本教程是用 Keras 在 Python 中入门卷积神经网络的一个良好起点。如果您能轻松跟上,或稍加努力就能完成,干得漂亮!试着做些实验,比如用相同的模型架构,配合不同的公开数据集。
仍有很多内容值得学习,何不试试 DataCamp 的Python 深度学习入门 课程?同时,务必查看 Keras 文档 ,如果您还没看过的话。您会在其中找到更多示例及所有函数、参数、更多层等信息。当您学习如何在 Python 中使用神经网络时,这无疑将是不可或缺的资源!
如果您更愿意读一本讲解深度学习(配合 Keras)基础并介绍其实践应用的书,那么一定要读一读 François Chollet 的Deep Learning in Python 。
您可能已经听说过图像或人脸识别,或自动驾驶汽车。这些都是真实场景下卷积神经网络(CNN)的应用。在这篇博文中,您将学习并理解如何在 Keras 中实现这种深层、前馈的人工神经网络,同时学习如何使用名为 "dropout" 的正则化技术来克服过拟合。
更具体地说,今天的教程将涵盖以下主题:
想在 Python 中系统学习 Keras 与深度学习吗?不妨考虑 DataCamp 的Python 深度学习入门课程!
另外别错过我们的Keras 速查表,它通过代码示例展示用 Python 构建神经网络所需经历的六个步骤!
卷积神经网络:简介
此时,您或许已经了解了机器学习和深度学习——一门研究能学习的算法设计的计算机科学分支。深度学习是机器学习的一个子领域,受人工神经网络启发,而人工神经网络又受生物神经网络启发。
这类深度神经网络中的一种就是卷积网络,通常称为 CNN 或 ConvNet。它是一种深层、前馈的人工神经网络。请记住,前馈神经网络也称为多层感知机(MLP),是典型的深度学习模型。之所以称为 "前馈",是因为信息自左向右流经模型,没有将模型输出反馈回自身的反馈连接。
CNN 特别受生物视觉皮层启发。视觉皮层有对视觉场中特定区域敏感的小细胞群。这一思想在 Hubel 和 Wiesel 于 1962 年进行的一项引人入胜的实验中得到扩展(若想了解更多,请看这个视频)。在实验中,研究者发现大脑中的一些单个神经元只在出现某种取向的边缘(如垂直或水平边缘)时才会被激活。例如,有些神经元在呈现垂直边时会触发,有些在呈现水平边时会触发。Hubel 和 Wiesel 发现所有这些神经元按柱状方式有序排列,并能共同产生视觉感知。这个关于系统内部的专门化组件各司其职的理念也被机器所借鉴,并能在 CNN 中找到体现。
卷积神经网络是计算机视觉领域最具影响力的创新之一。它们的表现远超传统计算机视觉方法,并产出了最先进的结果。这些网络在许多真实案例与应用中证明了成功,例如:
要理解这种成功,您需要回到 2012 年,那一年 Alex Krizhevsky 使用卷积神经网络赢得了当年的 ImageNet 比赛,将分类错误率从 26% 降至 15%。
注意,ImageNet 大规模视觉识别挑战赛(ILSVRC)始于 2010 年,是一项年度竞赛,研究团队在给定数据集上评估其算法,并在多个视觉识别任务上竞争更高的准确率。
也是在那个时期,神经网络在一段时间后再次崛起。这常被称为“神经网络的第三波”。前两波分别发生在 20 世纪 40 至 60 年代以及 70 至 80 年代。
好的,您知道我们将使用受生物视觉皮层启发的前馈网络,但这究竟意味着什么?
看看下面的图片:
图中显示,您将图像作为输入喂入网络,图像经过多次卷积、下采样、全连接层,最后输出结果。
但这些概念都是什么?
想了解更多信息,请前往这里。
Fashion-MNIST 数据集
在继续加载数据之前,先看看您将处理的究竟是什么!Fashion-MNIST 数据集由 Zalando 的商品图片组成,包含 10 个类别共 70,000 张 28x28 的灰度图像,每类 7,000 张。训练集包含 60,000 张,测试集包含 10,000 张。稍后加载数据后,您可以再次核对这些信息!;)
Fashion-MNIST 与您可能已熟悉的 MNIST 数据集相似,后者用于手写数字分类。这意味着图像尺寸以及训练/测试划分与 MNIST 类似。提示:如果您想学习如何用后者数据集实现用于分类任务的多层感知机(MLP),请查看这篇 Keras 深度学习教程。
您可以在这里找到 Fashion-MNIST 数据集,但也可以借助特定的 TensorFlow 和 Keras 模块直接加载。下一节将展示其工作方式!
加载数据
Keras 自带一个名为
datasets的库,可开箱即用地加载数据集:您从服务器下载数据,从而加速流程,因为无需手动下载到本地。训练与测试图像及其标签分别加载并存储在train_X、train_Y、test_X、test_Y变量中。太好了!是不是很简单?
到现在为止,您可能已经做过无数次了,但这始终是入手的关键一步。现在,您已经完全准备好开始分析、处理和建模数据!
分析数据
现在我们来看看数据集中图像的样子。尽管此时您已经知道图像的尺寸,但仍值得以编程方式分析它:您可能需要对像素进行缩放并调整图像尺寸。
从上述输出可见,训练数据形状为 60000 x 28 x 28,因为共有 60,000 个训练样本,每个样本尺寸为 28 x 28。类似地,测试数据形状为 10000 x 28 x 28,因为共有 10,000 个测试样本。
总共有从 0 到 9 的十个输出类别。
另外,别忘了看看数据集中图像的样子:
上面两幅图的输出看起来像是一双踝靴,其类别标签为 9。类似地,其他服饰会有不同标签,但相似的商品会有相同标签。这意味着所有 7,000 张踝靴图片的类别标签都为 9。
数据预处理
如上图所示,图像为灰度图,像素值范围为 0 到 255。此外,这些图像的尺寸为 28 x 28。因此,在将数据喂入模型前需要进行预处理。
在独热编码中,您将分类数据转换为一个数值向量。之所以进行独热编码,是因为机器学习算法无法直接处理分类数据。您会为每个类别生成一个布尔列。对于每个样本,只有其中一列取值为 1。因此称为独热编码。
在您的问题中,独热编码为行向量,对每张图像,维度为 1 x 10。这里要注意的是,向量除代表其类别的位置为 1 外,其余均为 0。例如,上面绘制的踝靴图片标签为 9,因此所有踝靴图片的独热编码向量为
[0 0 0 0 0 0 0 0 1 0]。我们将训练与测试标签转换为独热编码向量:
很清楚,对吧?注意,您也可以打印
train_Y_one_hot,它将显示一个 60000 x 10 的矩阵,每行对应一张图像的独热编码。最后再检查一下训练集和验证集的形状。
网络结构
图像尺寸为 28 x 28。您将图像矩阵转换为数组,将其缩放到 0 到 1 之间,重塑为 28 x 28 x 1,并将其作为输入喂入网络。
您将使用三个卷积层:
此外,还有三个 2 x 2 的最大池化层。
对数据建模
首先,导入训练模型所需的所有模块。
您将使用 64 的批大小。也可以选择更大的批大小如 128 或 256,具体取决于内存。这对学习参数的确定影响很大,并会影响预测精度。您将训练 20 个轮次(epochs)。
神经网络架构
在 Keras 中,您可以通过逐个添加所需层来堆叠网络层。您将在这里这么做:首先用
Conv2D()添加第一个卷积层。注意使用该函数是因为您在处理图像!接着添加 Leaky ReLU 激活函数,帮助网络学习非线性决策边界。由于您有十个不同的类别,需要一个能分隔这十个线性不可分类别的非线性决策边界。更具体地说,您添加 Leaky ReLU 是为了解决“ReLU 失活”问题。ReLU 激活函数在神经网络架构中被广泛使用,尤其在卷积网络中,其效果优于曾经广泛使用的 logistic sigmoid 函数。截至 2017 年,它是深度神经网络中最流行的激活函数。ReLU 函数使激活在零处被阈值化。然而在训练过程中,ReLU 单元可能会“失活”。当一个较大的梯度通过 ReLU 神经元时,可能导致权重更新使得该神经元不再对任何数据点激活。一旦发生,从那以后流经该单元的梯度将永远为零。Leaky ReLU 试图解决这一点:函数不为零,而是具有一个很小的负斜率。
接下来,您将添加最大池化层
MaxPooling2D(),依此类推。最后一层是具有 10 个单元并带有 softmax 激活函数的 Dense 层,这是该多分类问题所需的。编译模型
创建模型后,使用 Adam 优化器对其进行编译,这是最受欢迎的优化算法之一。您可以在这里了解更多。此外,您将损失类型指定为用于多分类的类别交叉熵(也可以使用二元交叉熵作为损失函数)。最后,指定需要在模型训练时分析的指标为准确率。
让我们用 summary 函数可视化您在上一步创建的各层。它会显示每一层中的一些参数(权重和偏置),以及模型的参数总数。
训练模型
终于到了用 Keras 的
fit()函数训练模型的时候!模型将训练 20 个 epoch。fit()会返回一个history对象;将其结果存为fashion_train,您随后可以用它来绘制训练与验证的准确率和损失曲线,从而直观分析模型表现。终于!您在 Fashion-MNIST 上训练了 20 个 epoch。观察训练准确率与损失,可以说模型完成得不错:20 个 epoch 后训练准确率达 99%,训练损失也很低。
不过,模型似乎过拟合了,因为验证损失为 0.4396,验证准确率为 92%。过拟合表明网络很好地记住了训练数据,但并不能保证在未见过的数据上表现良好,这也是训练与验证准确率存在差异的原因。
您可能需要处理这个问题。接下来,您将学习如何在保持其他层不变的情况下,通过在网络中添加 Dropout 层让模型表现更好。
但在下结论之前,先在测试集上评估模型表现。
在测试集上评估模型
测试准确率看起来相当不错。结果显示,您的分类器优于此处报告的基线(平均准确率 0.897 的 SVM 分类器)。同时,相比在 GitHub 上(Fashion-MNIST 数据集作者)提到的一些深度学习模型,您的模型表现也不差。
然而,您已经看到模型似乎过拟合了。这些结果真的就足够好吗?
让我们结合可视化来评估模型,绘制训练与验证数据之间的准确率和损失曲线:
从上述两幅图可见,验证准确率在第 4-5 个 epoch 后几乎停滞,只有在某些 epoch 稍有上升。起初验证准确率与损失线性上升,但随后增幅不大。
验证损失表明出现了过拟合的迹象,与验证准确率类似,它先线性下降,但在第 4-5 个 epoch 后开始上升。这意味着模型尝试记住数据并且成功了。
考虑到这一点,现在是时候在模型中引入一些 dropout,看看是否有助于减少过拟合。
在网络中加入 Dropout
您可以添加一个 dropout 层,在一定程度上克服过拟合问题。Dropout 会在训练过程中随机关闭一部分神经元,从而在一定程度上降低对训练集的依赖。关闭多少比例的神经元由一个可调的超参数决定。通过关闭部分神经元,网络将无法记住训练数据,因为并非所有神经元会同时处于激活状态,未激活的神经元也无法学习任何东西。
现在我们重新创建、编译并训练网络,这次加入 dropout。并以批大小 64 训练 20 个 epoch。
我们将模型保存下来,以便您可以直接加载而不必再次训练 20 个 epoch。这样,若之后需要,您可以加载模型并修改架构;或者基于此已保存模型重新开始训练。保存模型(以及模型权重!)总是个好主意——它能节省时间。注意,您也可以在每个 epoch 之后保存模型,这样一旦某个 epoch 出现问题导致训练中断,您不必从头开始。
在测试集上评估模型
最后,也来评估一下新模型的表现!
哇!看起来在模型中加入 Dropout 的确起作用了,尽管测试准确率提升不大,但与之前的结果相比测试损失降低了。
现在,最后一次绘制训练与验证数据之间的准确率和损失曲线。
最终您可以看到,验证损失与验证准确率与训练损失和训练准确率基本同步。尽管验证损失与准确率的曲线并非线性,但它显示出模型没有过拟合:验证损失在下降而不是上升,且训练与验证准确率之间的差距不大。
因此可以说,模型的泛化能力显著提升,因为测试集与验证集上的损失仅略高于训练损失。
预测标签
由于得到的预测是浮点数值,直接与真实测试标签比较并不可行。因此,您将对输出进行四舍五入,将浮点数转换为整数。进一步地,您将使用
np.argmax()来选择每一行中取值更高的索引号。例如,假设某个测试图像的预测为
0 1 0 0 0 0 0 0 0 0,则输出应为类别标签1。仅凭观察几张图片,您很难确定模型为何无法正确分类上述图像,但似乎多个类别中存在的相似模式会影响分类器的表现,尽管 CNN 是一种健壮的架构。例如,图 5 与图 6 属于不同类别,但看起来有点相似,可能是夹克,也可能是长袖衬衫。
分类报告
分类报告将帮助我们更详细地识别被误分类的类别。您将能观察到在这十个给定类别中,模型在哪些类别上的表现较差。
您可以看到,分类器在类别 6 上的精确率与召回率均表现欠佳。在类别 0 与类别 2 上,分类器的精确率不足。此外,在类别 4 上,分类器的精确率与召回率都略显不足。
继续探索!
本教程是用 Keras 在 Python 中入门卷积神经网络的一个良好起点。如果您能轻松跟上,或稍加努力就能完成,干得漂亮!试着做些实验,比如用相同的模型架构,配合不同的公开数据集。
仍有很多内容值得学习,何不试试 DataCamp 的Python 深度学习入门课程?同时,务必查看 Keras 文档,如果您还没看过的话。您会在其中找到更多示例及所有函数、参数、更多层等信息。当您学习如何在 Python 中使用神经网络时,这无疑将是不可或缺的资源!
如果您更愿意读一本讲解深度学习(配合 Keras)基础并介绍其实践应用的书,那么一定要读一读 François Chollet 的Deep Learning in Python。