**注:**这些是一个人的想法--有缺陷,容易判断错误。写这篇文章的目的是为了促进对这个话题的讨论,而不是为了正确或反对。如果写作中存在任何明显的错误,请让我知道。
让我在这个可能具有挑衅性的标题前说一下。
这是真的,没有人想要过拟合的终端模型,就像没有人想要欠拟合的终端模型一样。
过度拟合的模型在训练数据上表现很好,但不能很好地归纳到新的实例上。你最终得到的是一个接近于为特定数据集定制的完全硬编码的模型。
不适配的模型不能泛化到新的数据,但它们也不能对原始训练集进行建模。
正确的模型是一个适合数据的模型,它能很好地预测训练、验证和测试集中的数值,以及新的实例。
过度拟合与数据科学家
与过度拟合作斗争是一个焦点,因为它更加虚幻,而且对于新手来说,当他们开始机器学习之旅时,更容易创建过度拟合模型。在整个书籍、博客文章和课程中,有一个常见的场景。
"这个模型的准确率是100%!它是完美的!或者不是。实际上,它只是严重地过度拟合了数据集,在新的实例上测试时,它的表现只有X%,相当于随机猜测。"
在这些章节之后,整本书和课程的章节都致力于与过拟合作斗争,以及如何避免它。这个词本身就被污名化了,被认为是一个普遍的坏东西。而这正是一般概念产生的地方。
"我必须不惜一切代价避免过度拟合"。
它被赋予了比欠拟更多的关注,后者同样是 "坏事"。值得注意的是,"坏 "是一个任意的术语,这些条件没有一个本身是 "好 "或 "坏 "的。有些人可能会声称过拟合模型在技术上更有用,因为它们至少在一些数据上表现良好,而欠拟合模型在没有数据上表现良好,但成功的假象是超过这种好处的好候选。
作为参考,让我们参考一下谷歌趋势和谷歌Ngram浏览器。谷歌趋势显示了搜索数据的趋势,而谷歌Ngram查看器则统计了文学作品中n-grams(n个项目的序列,如单词)的出现次数,解析了历代的大量书籍。
每个人都在谈论过度拟合,而且大多是在避免过度拟合的背景下进行的--这往往会使人们产生一种普遍的观念,即过度拟合本来就是一件坏事。
这是真的,在某种程度上。是的--你不希望最终的模型严重过度,否则,它实际上是无用的。但是,你并不是马上就能得到最终的模型--你要用各种超参数对它进行无数次的调整。在这个过程中,你不应该介意看到过拟合的发生--这是一个 好迹象不过,这不是一个好的结果。
过度拟合如何不象人们说的那样糟糕
一个有能力过度拟合的模型和架构,如果你简化它(和/或调整数据),就更有可能有能力对新的实例进行良好的归纳。
- 有时,这不仅仅是模型的问题,我们稍后会看到。
如果一个模型能够过度拟合,它就有足够的熵能力从数据中提取特征(以有意义和无意义的方式)。从这里看,要么是模型的熵容量超过了要求(复杂性/功率),要么是数据本身不够(非常常见的情况)。
相反的说法也可能是真的,但更少。如果一个给定的模型或架构不适合,你可以尝试调整模型,看看它是否能拾起某些特征,但该类型的模型可能只是完全不适合这项任务,无论你做什么,你都无法用它来拟合数据。有些模型只是停留在某个准确度水平上,因为它们根本无法提取足够的特征来区分某些类别,或预测数值。
在烹饪中--可以建立一个反向的类比。炖菜的时候最好少放点盐,因为你可以在以后随时加盐来调味,但是一旦放了盐就很难再拿走了。
在机器学习中,情况正好相反。最好是让一个模型过度拟合,然后简化它,改变超参数,增加数据,等等,以使其具有良好的概括性,但(在实际设置中)很难做到相反。在过拟合发生之前避免它,很可能使你在较长时间内无法找到正确的模型和/或架构。
在实践中,以及在机器学习和深度学习的一些最吸引人的用例中,你会在数据集上工作,你会遇到过度拟合的问题。这些数据集将是你经常欠拟合的,没有能力找到能够很好地泛化和提取特征的模型和架构。
同样值得注意的是,我所说的真正的过拟合和部分过拟合之间的区别。一个过度拟合数据集的模型,在训练集上达到60%的准确率,而在验证集和测试集上只有40%,这就是过度拟合部分数据。然而,它并不是真正意义上的过度拟合,即让整个数据集黯然失色,达到接近100%(错误)的准确率,而其验证集和测试集的准确率却很低,比如说,~40%。
一个部分过度拟合的模型并不是一个能够通过简化而很好地归纳的模型,因为它没有足够的熵能力来真正(过度)拟合。一旦过拟合,我的论点就适用了,尽管它并不保证成功,这一点在接下来的章节中已经阐明。
案例研究--友好的过拟合论证
由Yann LeCun编制的MNIST手写数字数据集是用于训练分类模型的经典基准数据集之一。
它也是最容易被过度使用的数据集,可能是有史以来。
数据集本身没有问题--它实际上是很好的,但是在同一个数据集上寻找一个又一个的例子是很无聊的。在某一点上--我们看它的时候,自己都会过头。有多大?这是我试图从我的头脑中列出前十个MNIST数字。
5, 0, 4, 1, 9, 2, 2, 4, 3
我做得怎么样?
from tensorflow import keras
import numpy as np
import matplotlib.pyplot as plt
# Import and normalize the images, splitting out a validation set
(X_train_full, Y_train_full), (X_test, Y_test) = keras.datasets.mnist.load_data()
X_valid, X_train = X_train_full[:5000]/255.0, X_train_full[5000:]/255.0
Y_valid, Y_train = Y_train_full[:5000], Y_train_full[5000:]
X_test = X_test/255.0
# Print out the first ten digits
fig, ax = plt.subplots(1, 10, figsize=(10,2))
for i in range(10):
ax[i].imshow(X_train_full[i])
ax[i].axis('off')
plt.subplots_adjust(wspace=1)
plt.show()
差不多了。
我想利用这个机会向所有的内容创作者发出公开呼吁,不要在介绍性的部分之外过度使用这个数据集,因为数据集的简单性可以用来降低入门的门槛。拜托了。
此外,这个数据集让人很难建立一个不适合的模型。它实在是太简单了--即使是一个相当小的**多层感知器(MLP)**分类器,用直观的层数和每层的神经元来构建,在训练、测试和验证集上也能轻松达到98%以上的准确性。这是一个简单的MPL的Jupyter笔记本,在训练、验证和测试集上都达到了~98%的准确率,我用合理的默认值进行了旋转。
我甚至懒得去调整它,使其表现比初始设置更好。
CIFAR10和CIFAR100数据集
让我们使用一个比MNIST手写数字更复杂的数据集,它能让一个简单的MLP变得不拟合,但又简单到能让一个大小适中的CNN真正超拟合。一个很好的候选人是 CIFAR数据集.
CIFAR10有10类图像,CIFAR100有100类。此外,CIFAR100数据集有20个相似类别的家庭,这意味着网络还必须学习相似但不同类别之间的细微差别。这些被称为**"精细标签"(100)和"粗略标签"(20)**,预测这些标签就等于预测具体的类别,或者只是预测它所属的家族。
例如,这里有一个超类(粗标签)和它的子类(细标签)。
| 超类 | 子类 |
| 食品容器 | 瓶子、碗、罐子、杯子、盘子 |
杯子是一个圆柱体,类似于汽水罐,有些瓶子也可能是。由于这些低层次的特征比较相似,所以很容易把它们都夹在*"食品容器 "类别中,但要正确猜测某个东西是"杯 "还是"罐"*,就需要更高层次的抽象。
使这项工作更加困难的是,CIFAR10每类有6000张图像,而CIFAR100每类有600张图像,给网络提供了更少的图像来学习如此微妙的差异。没有把手的杯子是存在的,而没有脊的罐子也是存在的。从外形上看,要把它们区分开来可能不太容易。

在这种情况下,比如说多层感知器根本就没有学习的抽象能力,它注定要失败,因为它的适应性很差。卷积神经网络是基于Neocognitron建立的,它从神经科学和大脑执行的分层模式识别中得到暗示。这些网络能够提取这样的特征,并且擅长这项工作。以至于它们经常严重过拟合,最后不能按原样使用--在这里我们通常会为了泛化能力而牺牲一些准确性。
让我们在CIFAR10和CIFAR100数据集上训练两种不同的网络架构,以说明我的观点。
这也是我们将能够看到,即使一个网络过度拟合,也不能保证网络本身在简化后一定会有很好的泛化能力--它可能在简化后无法泛化,尽管有一种倾向。网络可能是正确的,但数据可能是不够的。
在CIFAR100的案例中--每类只有500张图片用于训练(和100张用于测试),对于一个简单的CNN来说,并不足以在整个100类上真正实现良好的泛化,我们必须进行数据扩增来帮助它完成。即使有了数据增强,我们也不可能得到一个高度准确的网络,因为你可以对数据做的事情太多了。如果同一个架构在CIFAR10上表现良好,但在CIFAR100上却表现不佳--这意味着它根本无法区分一些更细微的细节,例如,我们称之为 "杯"、"罐 "和 "瓶 "的圆柱形物体之间的区别。
绝大多数在CIFAR100数据集上取得高准确率的高级网络架构都会进行数据增强或以其他方式扩展训练集。
他们中的大多数不得不这样做,而这并不是一个糟糕的工程的标志。事实上--我们可以扩展这些数据集并帮助网络更好地泛化,这是工程智慧的一个标志。
此外,我邀请任何人类尝试猜测这些是什么,如果他们确信用32x32这么小的图片进行图像分类并不难的话。
图片4是几个橙子?乒乓球?蛋黄?好吧,可能不是蛋黄,但这需要关于什么是 "鸡蛋 "以及你是否可能找到坐在桌子上的蛋黄的先验知识,而网络不会有这些知识。考虑一下你对这个世界可能有多少先验知识,以及它对你看到的东西有多大影响。
导入数据
我们将使用Keras作为首选的深度学习库,但如果你愿意,也可以使用其他库甚至你的自定义模型。
但首先,让我们把它加载进去,把数据分成训练、测试和验证集,把图像值规范化为0..1 。
from tensorflow import keras
import numpy as np
import matplotlib.pyplot as plt
# Starting with CIFAR10
(X_train_full, Y_train_full), (X_test, Y_test) = keras.datasets.cifar10.load_data()
X_valid, X_train = X_train_full[:5000]/255.0, X_train_full[5000:]/255.0
Y_valid, Y_train = Y_train_full[:5000], Y_train_full[5000:]
X_test = X_test/255.0
然后,让我们把数据集中的一些图像可视化,以了解我们所面临的情况。
fig, ax = plt.subplots(5, 5, figsize=(10, 10))
ax = ax.ravel()
# Labels come as numbers of [0..9], so here are the class names for humans
class_names = ['Airplane', 'Automobile', 'Bird', 'Cat', 'Deer', 'Dog', 'Frog', 'Horse', 'Ship', 'Truck']
for i in range(25):
ax[i].imshow(X_train_full[i])
ax[i].set_title(class_names[Y_train_full[i][0]])
ax[i].axis('off')
plt.subplots_adjust(wspace=1)
plt.show()

低拟合多层感知器
几乎不管我们做什么,MLP的表现都不会太好。它肯定会在原始信息序列的基础上达到一定的准确度--但这个数字是有上限的,可能不会太高。
网络会在某一时刻开始过拟合,学习表示图像的具体数据序列,但即使过拟合,在训练集上的准确率仍然很低,这是停止训练的最佳时机,因为它根本无法很好地适应数据。训练网络是有碳足迹的,你知道。
让我们添加一个EarlyStopping 回调,以避免网络的运行超出常识范围,并将epochs 设置为一个超出我们将运行的数字(这样EarlyStopping 就可以启动)。
我们将使用Sequential API来添加几个带有BatchNormalization 和一点Dropout 的层。它们有助于泛化,我们希望至少尝试让这个模型学习一些东西。
我们在这里可以调整的主要超参数是层数、它们的大小、激活函数、内核初始化器和退出率,这里是一个 "体面 "的执行设置。
checkpoint = keras.callbacks.ModelCheckpoint("simple_dense.h5", save_best_only=True)
early_stopping = keras.callbacks.EarlyStopping(patience=10, restore_best_weights=True)
model = keras.Sequential([
keras.layers.Flatten(input_shape=[32, 32, 3]),
keras.layers.BatchNormalization(),
keras.layers.Dense(75),
keras.layers.Dense((50), activation='elu'),
keras.layers.BatchNormalization(),
keras.layers.Dropout(0.1),
keras.layers.Dense((50), activation='elu'),
keras.layers.BatchNormalization(),
keras.layers.Dropout(0.1),
keras.layers.Dense(10, activation='softmax')
])
model.compile(loss="sparse_categorical_crossentropy",
optimizer=keras.optimizers.Nadam(learning_rate=1e-4),
metrics=["accuracy"])
history = model.fit(X_train,
Y_train,
epochs=150,
validation_data=(X_valid, Y_valid),
callbacks=[checkpoint, early_stopping])
让我们看看开始的假设是否是真的--它开始会在一定程度上学习和归纳,但最终在训练集以及测试和验证集上的准确率都很低,导致整体准确率低。
对于CIFAR10,该网络表现得 "还不错"。
Epoch 1/150
1407/1407 [==============================] - 5s 3ms/step - loss: 1.9706 - accuracy: 0.3108 - val_loss: 1.6841 - val_accuracy: 0.4100
...
Epoch 50/150
1407/1407 [==============================] - 4s 3ms/step - loss: 1.2927 - accuracy: 0.5403 - val_loss: 1.3893 - val_accuracy: 0.5122
让我们看一下它的学习历史。
pd.DataFrame(history.history).plot()
plt.show()
model.evaluate(X_test, Y_test)
313/313 [==============================] - 0s 926us/step - loss: 1.3836 - accuracy: 0.5058
[1.383605718612671, 0.5058000087738037]
总体准确率达到了50%,网络很快就达到了这个水平,并开始趋于平稳。5/10张图片被正确分类听起来像是扔硬币,但请记住,这里有10个类别,所以如果它是随机猜测,它平均会猜中10张图片中的一张。让我们换到CIFAR100数据集,这也需要一个至少有一点点能力的网络,因为每个类的训练实例较少,而且类的数量也非常多。
checkpoint = keras.callbacks.ModelCheckpoint("bigger_dense.h5", save_best_only=True)
early_stopping = keras.callbacks.EarlyStopping(patience=10, restore_best_weights=True)
# Changing the loaded data
(X_train_full, Y_train_full), (X_test, Y_test) = keras.datasets.cifar100.load_data()
# Modify the model
model1 = keras.Sequential([
keras.layers.Flatten(input_shape=[32, 32, 3]),
keras.layers.BatchNormalization(),
keras.layers.Dense(256, activation='relu', kernel_initializer="he_normal"),
keras.layers.Dense(128, activation='relu'),
keras.layers.BatchNormalization(),
keras.layers.Dropout(0.1),
keras.layers.Dense(100, activation='softmax')
])
model1.compile(loss="sparse_categorical_crossentropy",
optimizer=keras.optimizers.Nadam(learning_rate=1e-4),
metrics=["accuracy"])
history = model1.fit(X_train,
Y_train,
epochs=150,
validation_data=(X_valid, Y_valid),
callbacks=[checkpoint, early_stopping])
该网络的表现相当糟糕。
Epoch 1/150
1407/1407 [==============================] - 13s 9ms/step - loss: 4.2260 - accuracy: 0.0836 - val_loss: 3.8682 - val_accuracy: 0.1238
...
Epoch 24/150
1407/1407 [==============================] - 12s 8ms/step - loss: 2.3598 - accuracy: 0.4006 - val_loss: 3.3577 - val_accuracy: 0.2434
让我们绘制一下它的进展历史,并在测试集(可能会和验证集一样表现良好)上进行评估。
pd.DataFrame(history.history).plot()
plt.show()
model.evaluate(X_test, Y_test)
313/313 [==============================] - 0s 2ms/step - loss: 3.2681 - accuracy: 0.2408
[3.2681326866149902, 0.24079999327659607]
正如预期的那样,该网络并不能很好地掌握数据。它最终的过拟合准确率为40%,实际准确率为~24%。
准确率的上限是40%--它并没有真正能够过度拟合数据集,即使它过度拟合了一些它能够辨别的部分,但由于架构有限。为了我的论点,这个模型不具备真正过度拟合所需的必要熵能力。
这个模型和它的架构根本不适合这项任务--虽然我们在技术上可以让它(过度)适应更多,但从长远来看,它还是会有问题。例如,让我们把它变成一个更大的网络,这在理论上会让它识别更复杂的模式。
model2 = keras.Sequential([
keras.layers.Flatten(input_shape=[32, 32, 3]),
keras.layers.BatchNormalization(),
keras.layers.Dense(512, activation='relu', kernel_initializer="he_normal"),
keras.layers.Dense(256, activation='relu'),
keras.layers.BatchNormalization(),
keras.layers.Dropout(0.1),
keras.layers.Dense(128, activation='relu'),
keras.layers.BatchNormalization(),
keras.layers.Dropout(0.1),
keras.layers.Dense(100, activation='softmax')
])
虽然,这根本就没有做得更好。
Epoch 24/150
1407/1407 [==============================] - 28s 20ms/step - loss: 2.1202 - accuracy: 0.4507 - val_loss: 3.2796 - val_accuracy: 0.2528
它更复杂了(密度爆炸),然而它根本无法提取更多的东西。
model1.summary()
model2.summary()
Model: "sequential_17"
...
Total params: 845,284
Trainable params: 838,884
Non-trainable params: 6,400
_________________________________________________________________
Model: "sequential_18"
...
Total params: 1,764,324
Trainable params: 1,757,412
Non-trainable params: 6,912
CIFAR10上的过拟合卷积神经网络
现在,让我们尝试做一些不同的事情。改用CNN将大大有助于从数据集中提取特征,从而使模型能够真正过拟合,达到更高的(虚幻的)精度。
我们将踢出EarlyStopping 回调,让它做它的事情。此外,我们不会使用Dropout ,而是试图迫使网络通过更多的层来学习特征。
**注意:**在试图证明这个论点的背景之外,这将是一个可怕的建议。这与你到最后想要做的事情相反。辍学有助于网络更好地泛化,因为它迫使未辍学的神经元挑起重担。迫使网络通过更多的层来学习,更有可能导致一个过拟合的模型。
我故意这样做的原因是允许网络出现可怕的过拟合,作为**它实际辨别特征的能力的标志,然后再简化它并添加Dropout ,以真正允许它进行泛化。**如果它达到了很高的(虚幻的)准确度,它可以提取比MLP模型多得多的东西,这意味着我们可以开始简单地处理它。
让我们再次使用Sequential API来建立一个CNN,首先在CIFAR10数据集上。
checkpoint = keras.callbacks.ModelCheckpoint("overcomplicated_cnn_cifar10.h5", save_best_only=True)
model = keras.models.Sequential([
keras.layers.Conv2D(64, 3, activation='relu',
kernel_initializer="he_normal",
kernel_regularizer=keras.regularizers.l2(l=0.01),
padding='same',
input_shape=[32, 32, 3]),
keras.layers.Conv2D(64, 3, activation='relu', padding='same'),
keras.layers.MaxPooling2D(2),
keras.layers.Conv2D(128, 2, activation='relu', padding='same'),
keras.layers.Conv2D(128, 2, activation='relu', padding='same'),
keras.layers.MaxPooling2D(2),
keras.layers.Conv2D(256, 3, activation='relu', padding='same'),
keras.layers.BatchNormalization(),
keras.layers.Conv2D(256, 3, activation='relu', padding='same'),
keras.layers.MaxPooling2D(2),
keras.layers.Conv2D(128, 3, activation='relu', padding='same'),
keras.layers.BatchNormalization(),
keras.layers.Conv2D(128, 3, activation='relu', padding='same'),
keras.layers.MaxPooling2D(2),
keras.layers.Conv2D(64, 3, activation='relu', padding='same'),
keras.layers.BatchNormalization(),
keras.layers.Conv2D(64, 3, activation='relu', padding='same'),
keras.layers.MaxPooling2D(2),
keras.layers.Flatten(),
keras.layers.Dense(32, activation='relu'),
keras.layers.Dense(10, activation='softmax')
])
model.compile(loss="sparse_categorical_crossentropy",
optimizer=keras.optimizers.Adam(learning_rate=1e-3),
metrics=["accuracy"])
model.summary()
history = model.fit(X_train,
Y_train,
epochs=150,
batch_size=64,
validation_data=(X_valid, Y_valid),
callbacks=[checkpoint])
太棒了,它很快就过拟合了在短短的几个历时内,它就开始过度拟合数据,到了历时31,它的拟合率达到了98%,验证准确率较低。
Epoch 1/150
704/704 [==============================] - 149s 210ms/step - loss: 1.9561 - accuracy: 0.4683 - val_loss: 2.5060 - val_accuracy: 0.3760
...
Epoch 31/150
704/704 [==============================] - 149s 211ms/step - loss: 0.0610 - accuracy: 0.9841 - val_loss: 1.0433 - val_accuracy: 0.6958
由于只有10个输出类,即使我们试图通过创建一个不必要的大CNN来过度拟合它,验证精度仍然相当高。
简化CIFAR10上的卷积神经网络
现在,让我们简化它,看看它在一个更合理的架构下会怎样。我们将加入BatchNormalization 和Dropout ,因为这两者都有助于泛化。
checkpoint = keras.callbacks.ModelCheckpoint("simplified_cnn_cifar10.h5", save_best_only=True)
early_stopping = keras.callbacks.EarlyStopping(patience=10, restore_best_weights=True)
model = keras.models.Sequential([
keras.layers.Conv2D(32, 3, activation='relu', kernel_initializer="he_normal", kernel_regularizer=keras.regularizers.l2(l=0.01), padding='same', input_shape=[32, 32, 3]),
keras.layers.BatchNormalization(),
keras.layers.Conv2D(32, 3, activation='relu', padding='same'),
keras.layers.BatchNormalization(),
keras.layers.MaxPooling2D(2),
keras.layers.Dropout(0.4),
keras.layers.Conv2D(64, 2, activation='relu', padding='same'),
keras.layers.BatchNormalization(),
keras.layers.Conv2D(64, 2, activation='relu', padding='same'),
keras.layers.BatchNormalization(),
keras.layers.MaxPooling2D(2),
keras.layers.Dropout(0.4),
keras.layers.Conv2D(128, 3, activation='relu', padding='same'),
keras.layers.BatchNormalization(),
keras.layers.Conv2D(128, 3, activation='relu', padding='same'),
keras.layers.BatchNormalization(),
keras.layers.MaxPooling2D(2),
keras.layers.Dropout(0.5),
keras.layers.Flatten(),
keras.layers.Dense(32, activation='relu'),
keras.layers.BatchNormalization(),
keras.layers.Dropout(0.3),
keras.layers.Dense(10, activation='softmax')
])
model.compile(loss="sparse_categorical_crossentropy",
optimizer=keras.optimizers.Adam(learning_rate=1e-3),
metrics=["accuracy"])
model.summary()
history = model.fit(X_train,
Y_train,
epochs=150,
batch_size=64,
validation_data=(X_valid, Y_valid),
callbacks=[checkpoint, early_stopping])
这个模型有323,146个可训练参数(适度),而之前的CNN有1,579,178个。它的表现如何?
Epoch 1/150
704/704 [==============================] - 91s 127ms/step - loss: 2.1327 - accuracy: 0.3910 - val_loss: 1.5495 - val_accuracy: 0.5406
...
Epoch 52/150
704/704 [==============================] - 89s 127ms/step - loss: 0.4091 - accuracy: 0.8648 - val_loss: 0.4694 - val_accuracy: 0.8500
它实际上达到了相当不错的~85%的准确率!奥卡姆剃刀 "再次出击。让我们看一下一些结果。
y_preds = model.predict(X_test)
print(y_preds[1])
print(np.argmax(y_preds[1]))
fig, ax = plt.subplots(6, 6, figsize=(10, 10))
ax = ax.ravel()
for i in range(0, 36):
ax[i].imshow(X_test[i])
ax[i].set_title("Actual: %s\nPred: %s" % (class_names[Y_test[i][0]], class_names[np.argmax(y_preds[i])]))
ax[i].axis('off')
plt.subplots_adjust(wspace=1)
plt.show()

主要的错误分类是这一小组图片中的两张图片--一只狗被错误地归类为鹿(足够令人尊敬),但一只鸸鹋鸟的特写被归类为猫(足够有趣,所以我们就不追究了)。
CIFAR100上的过拟合卷积神经网络
当我们使用CIFAR100数据集时会发生什么?
checkpoint = keras.callbacks.ModelCheckpoint("overcomplicated_cnn_model_cifar100.h5", save_best_only=True)
early_stopping = keras.callbacks.EarlyStopping(patience=10, restore_best_weights=True)
model = keras.models.Sequential([
keras.layers.Conv2D(32, 3, activation='relu', kernel_initializer="he_normal", kernel_regularizer=keras.regularizers.l2(l=0.01), padding='same', input_shape=[32, 32, 3]),
keras.layers.BatchNormalization(),
keras.layers.Conv2D(32, 3, activation='relu', padding='same'),
keras.layers.BatchNormalization(),
keras.layers.MaxPooling2D(2),
keras.layers.Conv2D(64, 2, activation='relu', padding='same'),
keras.layers.BatchNormalization(),
keras.layers.Conv2D(64, 2, activation='relu', padding='same'),
keras.layers.BatchNormalization(),
keras.layers.MaxPooling2D(2),
keras.layers.Conv2D(128, 3, activation='relu', padding='same'),
keras.layers.BatchNormalization(),
keras.layers.Conv2D(128, 3, activation='relu', padding='same'),
keras.layers.BatchNormalization(),
keras.layers.MaxPooling2D(2),
keras.layers.Conv2D(128, 3, activation='relu', padding='same'),
keras.layers.BatchNormalization(),
keras.layers.Conv2D(128, 3, activation='relu', padding='same'),
keras.layers.BatchNormalization(),
keras.layers.MaxPooling2D(2),
keras.layers.Conv2D(64, 3, activation='relu', padding='same'),
keras.layers.BatchNormalization(),
keras.layers.Conv2D(64, 3, activation='relu', padding='same'),
keras.layers.BatchNormalization(),
keras.layers.MaxPooling2D(2),
keras.layers.Flatten(),
keras.layers.Dense(256, activation='relu'),
keras.layers.BatchNormalization(),
keras.layers.Dense(128, activation='relu'),
keras.layers.BatchNormalization(),
keras.layers.Dense(100, activation='softmax')
])
model.compile(loss="sparse_categorical_crossentropy",
optimizer=keras.optimizers.Adam(learning_rate=1e-3),
metrics=["accuracy"])
model.summary()
history = model.fit(X_train,
Y_train,
epochs=150,
batch_size=64,
validation_data=(X_valid, Y_valid),
callbacks=[checkpoint])
Epoch 1/150
704/704 [==============================] - 97s 137ms/step - loss: 4.1752 - accuracy: 0.1336 - val_loss: 3.9696 - val_accuracy: 0.1392
...
Epoch 42/150
704/704 [==============================] - 95s 135ms/step - loss: 0.1543 - accuracy: 0.9572 - val_loss: 4.1394 - val_accuracy: 0.4458
妙极了!训练集的准确率高达96%!先别管44%的验证准确率。让我们快速简化模型,让它更好地归纳。
简化后无法归纳
这时就会发现,过度拟合的能力并不能保证模型在简化后能更好地泛化。在CIFAR100的案例中,每一类的训练实例并不多,这很可能会阻止以前的模型的简化版本学好。让我们来试试。
checkpoint = keras.callbacks.ModelCheckpoint("simplified_cnn_model_cifar100.h5", save_best_only=True)
early_stopping = keras.callbacks.EarlyStopping(patience=10, restore_best_weights=True)
model = keras.models.Sequential([
keras.layers.Conv2D(32, 3, activation='relu', kernel_initializer="he_normal", kernel_regularizer=keras.regularizers.l2(l=0.01), padding='same', input_shape=[32, 32, 3]),
keras.layers.BatchNormalization(),
keras.layers.Conv2D(32, 3, activation='relu', padding='same'),
keras.layers.BatchNormalization(),
keras.layers.MaxPooling2D(2),
keras.layers.Dropout(0.4),
keras.layers.Conv2D(64, 2, activation='relu', padding='same'),
keras.layers.BatchNormalization(),
keras.layers.Conv2D(64, 2, activation='relu', padding='same'),
keras.layers.BatchNormalization(),
keras.layers.MaxPooling2D(2),
keras.layers.Dropout(0.4),
keras.layers.Conv2D(128, 3, activation='relu', padding='same'),
keras.layers.BatchNormalization(),
keras.layers.Conv2D(128, 3, activation='relu', padding='same'),
keras.layers.BatchNormalization(),
keras.layers.MaxPooling2D(2),
keras.layers.Dropout(0.5),
keras.layers.Flatten(),
keras.layers.Dense(256, activation='relu'),
keras.layers.BatchNormalization(),
keras.layers.Dropout(0.3),
keras.layers.Dense(100, activation='softmax')
])
model.compile(loss="sparse_categorical_crossentropy",
optimizer=keras.optimizers.Adam(learning_rate=1e-3),
metrics=["accuracy"])
history = model.fit(X_train,
Y_train,
epochs=150,
batch_size=64,
validation_data=(X_valid, Y_valid),
callbacks=[checkpoint, early_stopping])
Epoch 1/150
704/704 [==============================] - 96s 135ms/step - loss: 4.4432 - accuracy: 0.1112 - val_loss: 3.7893 - val_accuracy: 0.1702
...
Epoch 48/150
704/704 [==============================] - 92s 131ms/step - loss: 1.2550 - accuracy: 0.6370 - val_loss: 1.7147 - val_accuracy: 0.5466
它处于高原期,不能真正地去概括数据。在这种情况下,这可能不是模型的错--也许它正好适合这项任务,特别是考虑到在CIFAR10数据集上的高准确性,该数据集有相同的输入形状和类似的图像。看来,该模型对一般的形状能有合理的准确度,但对细小形状的区分则没有。
在验证准确性方面,较简单的模型实际上比较复杂的模型表现得更好--所以较复杂的CNN根本没有把这些精细的细节弄得很好。在这里,问题很可能在于每个类别只有500张训练图像,这真的是不够的。在更复杂的网络中,这导致了过度拟合,因为没有足够的多样性--当简化以避免过度拟合时,这会导致拟合不足,因为同样没有多样性。
这就是为什么之前链接的绝大部分论文,以及绝大部分网络都是对CIFAR100数据集的数据进行增强的。
这确实不是一个容易获得高准确率的数据集,与MNIST手写数字数据集不同,像我们正在构建的简单CNN可能无法获得高准确率。只要记住相当具体的类的数量,一些图像的信息量有多大,以及人类有多少先验知识来辨别这些图像。
让我们通过增加一些图像和人为地扩大训练数据来尽最大努力,至少尝试获得更高的准确性。请记住,CIFAR100是一个真正难以用简单模型获得高准确率的数据集。最先进的模型使用不同的新技术来消除错误,其中许多模型甚至不是CNN--它们是变形金刚。
如果你想看看这些模型的情况,PapersWithCode已经做了一个漂亮的论文、源代码和结果汇编。
用Keras的ImageDataGenerator类进行数据扩充
数据扩增会有帮助吗?通常情况下,它是有帮助的,但在我们所面临的严重缺乏训练数据的情况下,你可以用随机旋转、翻转、裁剪等方式做很多事情。如果一个架构在一个数据集上不能很好地归纳,你可能会通过数据增强来提高它,但可能不会是很多。
既然如此,让我们使用Keras的ImageDataGenerator 类来尝试生成一些具有随机变化的新训练数据,希望能提高模型的准确性。如果它真的提高了,也不应该是很大的提高,它可能会回到部分过拟合数据集的状态,没有能力很好地泛化或完全过拟合数据。
考虑到数据的持续随机变化,模型在相同数量的历时中不太可能过度拟合,因为这些变化使它不断适应 "新 "数据。让我们运行它,比如说,300个epochs,这明显比我们训练的其他网络要多。这是在没有严重过拟合的情况下实现的,这也是由于在图像流入时对它们进行的随机修改。
checkpoint = keras.callbacks.ModelCheckpoint("augmented_cnn.h5", save_best_only=True)
model = keras.models.Sequential([
keras.layers.Conv2D(64, 3, activation='relu', kernel_initializer="he_normal", kernel_regularizer=keras.regularizers.l2(l=0.01), padding='same', input_shape=[32, 32, 3]),
keras.layers.Conv2D(64, 3, activation='relu', padding='same'),
keras.layers.BatchNormalization(),
keras.layers.MaxPooling2D(2),
keras.layers.Dropout(0.4),
keras.layers.Conv2D(128, 2, activation='relu', padding='same'),
keras.layers.Conv2D(128, 2, activation='relu', padding='same'),
keras.layers.Conv2D(128, 2, activation='relu', padding='same'),
keras.layers.BatchNormalization(),
keras.layers.MaxPooling2D(2),
keras.layers.Dropout(0.4),
keras.layers.Conv2D(256, 3, activation='relu', padding='same'),
keras.layers.Conv2D(256, 3, activation='relu', padding='same'),
keras.layers.Conv2D(256, 3, activation='relu', padding='same'),
keras.layers.BatchNormalization(),
keras.layers.MaxPooling2D(2),
keras.layers.Dropout(0.4),
keras.layers.Flatten(),
keras.layers.Dense(512, activation='relu'),
keras.layers.BatchNormalization(),
keras.layers.Dropout(0.3),
keras.layers.Dense(100, activation='softmax')
])
train_datagen = ImageDataGenerator(rotation_range=30,
height_shift_range=0.2,
width_shift_range=0.2,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True,
vertical_flip=True,
fill_mode='nearest')
valid_datagen = ImageDataGenerator()
train_datagen.fit(X_train)
valid_datagen.fit(X_valid)
train_generator = train_datagen.flow(X_train, Y_train, batch_size=128)
valid_generator = valid_datagen.flow(X_valid, Y_valid, batch_size=128)
model.compile(loss="sparse_categorical_crossentropy",
optimizer=keras.optimizers.Adam(learning_rate=1e-3, decay=1e-6),
metrics=["accuracy"])
history = model.fit(train_generator,
epochs=300,
batch_size=128,
steps_per_epoch=len(X_train)//128,
validation_data=valid_generator,
callbacks=[checkpoint])
Epoch 1/300
351/351 [==============================] - 16s 44ms/step - loss: 5.3788 - accuracy: 0.0487 - val_loss: 5.3474 - val_accuracy: 0.0440
...
Epoch 300/300
351/351 [==============================] - 15s 43ms/step - loss: 1.0571 - accuracy: 0.6895 - val_loss: 2.0005 - val_accuracy: 0.5532
该模型在验证集上的表现为~55%,并且仍然部分地过拟合了数据。val_loss 已经停止下降,而且相当坎坷,即使有更高的batch_size 。
这个网络根本无法以高精确度学习和拟合数据,尽管它的变化确实有熵能力来过度拟合数据。
结论是什么?
过度拟合本身并不是一件坏事--它只是一件事。不,你不想要过拟合的最终模型,但它不应该被当作瘟疫来对待,甚至可以是一个好的迹象,表明如果有更多的数据和简化步骤,模型可以表现得更好。但这并不保证,CIFAR100数据集已经被用作一个不容易归纳的数据集的例子。
这段话的重点是,再次强调,不是为了反驳--而是为了激发对这个话题的讨论,而这个话题似乎并没有什么进展。
我是谁,可以提出这个主张?
只是一个坐在家里练习手艺的人,对明天有着深深的迷恋。
我有能力做错吗?
非常有可能。
你应该如何看待这篇文章?
随你怎么看--自己想想这是否有意义。如果你不认为我注意到这一点是不合时宜的,请告诉我。如果你认为我在这方面是错的--通过各种方式,请让我知道,不要吝啬你的语言。)