监督学习具有挑战性,尽管这种挑战的深度通常在学习后被遗忘或有意忽略。
这一定是真的,因为过久地沉溺于这种挑战可能会导致悲观情绪。尽管面临挑战,我们仍然运用监督学习算法,并且它们在实践中表现良好。
监督学习挑战的核心是以下问题:
- 需要多少数据才能合理地近似输入到输出的未知底层映射函数?
- 需要多少数据才能合理地估计映射函数近似值的性能?
通常,众所周知,过少的训练数据会导致较差的近似。一个过度约束的模型将欠拟合小型训练数据集,而一个欠约束的模型则可能过拟合训练数据,两者都会导致性能不佳。过少的测试数据将导致模型性能的估计过于乐观且方差较大。
通过实际例子将这种“常识”具体化至关重要。
在这篇文章中,我们将通过一个详细的案例研究,解决一个简单的两类分类问题,开发一个多层感知器神经网络。您将发现,在实践中,我们没有足够的数据来学习映射函数或评估模型,但神经网络等监督学习算法仍然非常有效。
- 如何分析两圆分类问题并测量神经网络学习算法引入的方差。
- 训练数据集大小的变化如何直接影响神经网络近似的映射函数的质量。
- 测试数据集大小的变化如何直接影响拟合神经网络模型性能的估计质量。
用我的新书《更好的深度学习》来启动你的项目,书中包含分步教程和所有示例的 Python 源代码文件。
让我们开始吧。

数据集大小对深度学习模型技能和性能估计的影响
照片来源 Eneas De Troya,保留部分权利。
教程概述
本教程分为五个部分;它们是:
- 监督学习的挑战
- 两圆问题介绍
- 神经网络模型方差
- 研究测试精度与训练集大小
- 研究测试集大小与测试集精度
两圆问题介绍
作为我们探索的基础,我们将使用一个非常简单的两类或二元分类问题。
scikit-learn 库提供了 make_circles() 函数,可用于创建具有规定样本数量和统计噪声的二元分类问题。
每个示例有两个输入变量,它们定义了二维平面上点的 *x* 和 *y* 坐标。这些点以两个同心圆(它们具有相同的中心)排列,用于两个类别。
数据集中点的数量由一个参数指定,其中一半将从每个圆中抽取。可以通过“*noise*”参数添加高斯噪声,该参数定义了噪声的标准偏差,其中 0.0 表示没有噪声或点正好从圆中抽取。可以通过“*random_state*”参数指定 伪随机数生成器 的种子,这允许每次调用函数时都抽取完全相同的点。
下面的示例从两个圆中生成 100 个示例,没有噪声,并将伪随机数生成器的种子设置为 1。
1 2 3 4 5 6 7 8 9 |
# 生成圆数据集的示例 from sklearn.datasets import make_circles # 生成圆 X, y = make_circles(n_samples=100, noise=0.0, random_state=1) # 显示数据集大小 print(X.shape, y.shape) # 显示前几个示例 for i in range(5): print(X[i], y[i]) |
运行示例会生成点并打印样本的输入(*X*)和输出(*y*)组件的形状。我们可以看到有 100 个输入示例,每个示例有两个特征,用于 *x* 和 *y* 坐标,以及匹配的 100 个输出变量或类值示例,每个示例有 1 个变量。
显示了数据集中的前五个示例。我们可以看到输入变量的 *x* 和 *y* 分量以 0.0 为中心,范围为 [-1, 1]。我们还可以看到类值为 0 或 1 的整数,并且示例在类之间进行了洗牌。
1 2 3 4 5 6 |
(100, 2) (100,) [-0.6472136 -0.4702282] 1 [-0.34062343 -0.72386164] 1 [-0.53582679 -0.84432793] 0 [-0.5831749 -0.54763768] 1 [ 0.50993919 -0.61641059] 1 |
绘制数据集
我们可以重新运行示例,并始终获得相同的“随机生成”点,前提是使用相同的伪随机数生成器种子。
下面的示例生成相同的点,并使用散点图绘制样本的输入变量。我们可以使用 scatter() matplotlib 函数 来创建绘图,并传入 *X* 数组的所有行,其中第一个变量用于 *x* 坐标,第二个变量用于 *y* 坐标。
1 2 3 4 5 6 7 8 |
# 创建圆数据集散点图的示例 from sklearn.datasets import make_circles from matplotlib import pyplot # 生成圆 X, y = make_circles(n_samples=100, noise=0.0, random_state=1) # 生成数据集的散点图 pyplot.scatter(X[:, 0], X[:, 1]) pyplot.show() |
运行示例会创建一个散点图,清楚地显示数据集的同心圆。

圆数据集输入变量的散点图
我们可以重新创建散点图,但将所有类别 0 的输入样本绘制为蓝色,所有类别 1 的点绘制为红色。
我们可以使用 where() NumPy 函数 选择 y 数组中具有给定值的样本索引,然后使用这些索引选择 *X* 数组中的行。完整的示例如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# 带有按类别着色的点的圆数据集散点图 from sklearn.datasets import make_circles from numpy import where from matplotlib import pyplot # 生成圆 X, y = make_circles(n_samples=100, noise=0.0, random_state=1) # 选择每个类别标签的点的索引 zero_ix, one_ix = where(y == 0), where(y == 1) # 类别零的点 pyplot.scatter(X[zero_ix, 0], X[zero_ix, 1], color='red') # 类别一的点 pyplot.scatter(X[one_ix, 0], X[one_ix, 1], color='blue') pyplot.show() |
运行示例,我们可以看到类别 0 的样本是蓝色的内圈,类别 1 的样本属于红色的外圈。

按类别值着色的圆数据集输入变量散点图
不同噪声下的图
所有真实数据都具有统计噪声。统计噪声越多意味着问题对学习算法来说更具挑战性,因为它需要将输入变量映射到输出或目标变量。
圆数据集允许我们通过“noise”参数模拟向样本添加噪声。
我们可以创建一个名为 *scatter_plot_circles_problem()* 的新函数,该函数使用给定量的噪声创建数据集,并创建一个散点图,其中点的颜色按其类别值着色。
1 2 3 4 5 6 7 8 9 10 |
# 创建一个带有给定噪声量的圆数据集散点图 def scatter_plot_circles_problem(noise_value): # 生成圆 X, y = make_circles(n_samples=100, noise=noise_value, random_state=1) # 选择每个类别标签的点的索引 zero_ix, one_ix = where(y == 0), where(y == 1) # 类别零的点 pyplot.scatter(X[zero_ix, 0], X[zero_ix, 1], color='red') # 类别一的点 pyplot.scatter(X[one_ix, 0], X[one_ix, 1], color='blue') |
我们可以多次调用此函数,并使用不同的噪声量来查看其对问题复杂度的影响。
我们将创建四个散点图作为子图,通过 subplot() matplotlib 函数 以 4x4 矩阵的形式,噪声值为 [0.0, 0.1, 0.2, 0.3]。完整的示例如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# 带有不同噪声量的圆数据集散点图 from sklearn.datasets import make_circles from numpy import where from matplotlib import pyplot # 创建一个带有给定噪声量的圆数据集散点图 def scatter_plot_circles_problem(noise_value): # 生成圆 X, y = make_circles(n_samples=100, noise=noise_value, random_state=1) # 选择每个类别标签的点的索引 zero_ix, one_ix = where(y == 0), where(y == 1) # 类别零的点 pyplot.scatter(X[zero_ix, 0], X[zero_ix, 1], color='red') # 类别一的点 pyplot.scatter(X[one_ix, 0], X[one_ix, 1], color='blue') # 改变噪声并绘图 values = [0.0, 0.1, 0.2, 0.3] for i in range(len(values)): value = 220 + (i+1) pyplot.subplot(value) scatter_plot_circles_problem(values[i]) pyplot.show() |
运行示例后,将创建一个包含四个子图的绘图,分别对应左上、右上、左下和右下角的四个不同噪声值 [0.0, 0.1, 0.2, 0.3]。
我们可以看到,少量噪声(0.1)使问题具有挑战性,但仍然可以区分。噪声值为 0.0 是不现实的,如此完美的数据集不需要机器学习。噪声值为 0.2 使问题非常具有挑战性,而值为 0.3 可能使问题过于复杂而无法学习。

带有不同统计噪声量的圆数据集的四个散点图
不同样本大小下的图
我们可以创建类似的问题图,但样本数量不同。
更多的样本为学习算法提供了更多的机会来理解输入到输出的底层映射,进而产生性能更好的模型。
我们可以更新 *scatter_plot_circles_problem()* 函数,使其接受要生成的样本数量作为参数,以及噪声量,并将噪声默认设置为 0.1,这会使问题嘈杂,但又不太嘈杂。
1 2 3 4 5 6 7 8 9 10 |
# 创建圆问题散点图 def scatter_plot_circles_problem(n_samples, noise_value=0.1): # 生成圆 X, y = make_circles(n_samples=n_samples, noise=noise_value, random_state=1) # 选择每个类别标签的点的索引 zero_ix, one_ix = where(y == 0), where(y == 1) # 类别零的点 pyplot.scatter(X[zero_ix, 0], X[zero_ix, 1], color='red') # 类别一的点 pyplot.scatter(X[one_ix, 0], X[one_ix, 1], color='blue') |
我们可以调用此函数来创建多个散点图,其中包含不同数量的点,这些点均匀分布在两个圆或类别之间。
我们将尝试以下样本大小的问题版本:[50, 100, 500, 1000]。完整的示例如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# 带有不同样本大小的圆数据集散点图 from sklearn.datasets import make_circles from numpy import where from matplotlib import pyplot # 创建圆问题散点图 def scatter_plot_circles_problem(n_samples, noise_value=0.1): # 生成圆 X, y = make_circles(n_samples=n_samples, noise=noise_value, random_state=1) # 选择每个类别标签的点的索引 zero_ix, one_ix = where(y == 0), where(y == 1) # 类别零的点 pyplot.scatter(X[zero_ix, 0], X[zero_ix, 1], color='red') # 类别一的点 pyplot.scatter(X[one_ix, 0], X[one_ix, 1], color='blue') # 改变样本大小并绘图 values = [50, 100, 500, 1000] for i in range(len(values)): value = 220 + (i+1) pyplot.subplot(value) scatter_plot_circles_problem(values[i]) pyplot.show() |
运行示例会创建一个包含四个子图的绘图,分别对应左上、右上、左下和右下角的 [50, 100, 500, 1000] 不同大小的样本。
我们可以看到,50 个示例可能太少,甚至 100 个点也不足以真正学习这个问题。这些图表明 500 和 1,000 个示例可能更容易学习,尽管它们隐藏了一个事实,即许多“异常值”点导致两个圆之间存在重叠。

按样本数量变化的圆数据集的四个散点图
想要通过深度学习获得更好的结果吗?
立即参加我为期7天的免费电子邮件速成课程(附示例代码)。
点击注册,同时获得该课程的免费PDF电子书版本。
神经网络模型方差
我们可以用神经网络模拟圆问题。
具体来说,我们可以训练一个多层感知器模型,简称 MLP,为模型提供从圆问题生成的输入和输出示例。一旦学习完成,我们可以通过使用模型对新示例进行预测并评估准确性来评估模型对问题的学习效果。
我们可以使用 Keras 深度学习库 为该问题开发一个小型 MLP,其中包含两个输入、隐藏层中的 25 个节点和一个输出。隐藏层中的节点可以使用修正线性激活函数。由于该问题是一个二元分类问题,模型可以使用输出层上的 sigmoid 激活函数来预测样本属于类别 0 或类别 1 的概率。
该模型可以使用一种高效的小批量随机梯度下降版本——Adam 进行训练,其中模型中的每个权重都有自己的自适应学习率。二元交叉熵损失函数可以用作优化基础,其中较小的损失值表示更好的模型拟合。
1 2 3 4 5 |
# 定义模型 model = Sequential() model.add(Dense(25, input_dim=2, activation='relu')) model.add(Dense(1, activation='sigmoid')) model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) |
我们需要从圆问题中生成的样本来训练模型,以及一个独立的、未用于训练模型的测试集,该测试集可用于估计模型在对新数据进行预测时平均表现如何。
下面的 *create_dataset()* 函数将创建给定大小的训练集和测试集,并使用默认噪声 0.1。
1 2 3 4 5 6 7 8 9 10 |
# 创建测试数据集 def create_dataset(n_train, n_test, noise=0.1): # 生成样本 n_samples = n_train + n_test X, y = make_circles(n_samples=n_samples, noise=noise, random_state=1) # 分割为训练集和测试集 trainX, testX = X[:n_train, :], X[n_train:, :] trainy, testy = y[:n_train], y[n_train:] # 返回样本 return trainX, trainy, testX, testy |
我们可以调用此函数来准备训练集和测试集,并将其拆分为输入和输出组件。
1 2 |
# 创建数据集 trainX, trainy, testX, testy = create_dataset(500, 500) |
一旦我们定义了数据集和模型,我们就可以训练模型。我们将在训练数据集上训练模型 500 个 epoch。
1 2 |
# 拟合模型 history = model.fit(trainX, trainy, epochs=500, verbose=0) |
一旦拟合,我们使用模型对测试集的输入示例进行预测,并将其与测试集的真实输出值进行比较,然后计算准确度分数。
*evaluate()* 函数执行此操作,返回模型在测试数据集上的损失和准确性。我们可以忽略损失,并显示准确性,以了解模型在将来自圆域的随机示例映射到类别 0 或类别 1 时学习得有多好。
1 2 3 |
# 评估模型 _, test_acc = model.evaluate(testX, testy, verbose=0) print('Test Accuracy: %.3f' % (test_acc*100)) |
将所有这些联系在一起,完整的示例如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
# 在圆数据集上评估的 mlp from sklearn.datasets import make_circles from keras.layers import Dense 从 keras.models 导入 Sequential # 创建测试数据集 def create_dataset(n_train, n_test, noise=0.1): # 生成样本 n_samples = n_train + n_test X, y = make_circles(n_samples=n_samples, noise=noise, random_state=1) # 分割为训练集和测试集 trainX, testX = X[:n_train, :], X[n_train:, :] trainy, testy = y[:n_train], y[n_train:] # 返回样本 return trainX, trainy, testX, testy # 创建数据集 trainX, trainy, testX, testy = create_dataset(500, 500) # 定义模型 model = Sequential() model.add(Dense(25, input_dim=2, activation='relu')) model.add(Dense(1, activation='sigmoid')) model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) # 拟合模型 history = model.fit(trainX, trainy, epochs=500, verbose=0) # 评估模型 _, test_acc = model.evaluate(testX, testy, verbose=0) print('Test Accuracy: %.3f' % (test_acc*100)) |
运行示例会生成数据集,在训练数据集上拟合模型,并在测试数据集上评估模型。
注意:由于算法或评估过程的随机性,或数值精度的差异,您的 结果可能有所不同。请考虑多次运行示例并比较平均结果。
在这种情况下,模型获得了大约 84.4% 的估计准确度。
1 |
测试准确率:84.400 |
随机学习算法
这个例子的问题在于你的结果可能会有所不同。
每次运行示例时,你可能会看到不同的估计准确度。
再次运行示例,我看到估计准确度约为 84.8%。同样,您的具体结果预计会有所不同。
1 |
测试准确率:84.800 |
每次运行示例时,数据集中的示例都是相同的。这是因为我们在创建样本时固定了伪随机数生成器。样本确实有噪声,但每次我们得到的噪声都是相同的。
神经网络是一种非线性学习算法,这意味着它们可以学习输入变量和输出变量之间复杂的非线性关系。它们可以近似具有挑战性的非线性函数。
因此,我们将神经网络模型称为具有低偏差和高方差。它们具有低偏差,因为该方法对映射函数的数学函数形式做出了很少的假设。它们具有高方差,因为它们对用于训练模型的特定示例很敏感。训练示例的差异可能意味着结果模型非常不同,进而导致技能差异。
尽管神经网络是一种高方差低偏差的方法,但这并不是在相同生成数据点上运行相同算法时估计性能差异的原因。
相反,我们看到多次运行之间的性能差异是因为学习算法是随机的。
学习算法利用随机性元素,帮助模型平均地很好地学习如何将训练数据集中的输入变量映射到输出变量。随机性的例子包括用于初始化模型权重的微小随机值,以及在每个训练时期之前对训练数据集中示例进行的随机排序。
这是一种有用的随机性,因为它允许模型自动“发现”映射函数的良好解决方案。
这可能令人沮丧,因为它在每次运行学习算法时通常会找到不同的解决方案,有时解决方案之间的差异很大,导致在对新数据进行预测时模型估计性能的差异。
平均模型性能
我们可以通过总结该方法在多次运行中的性能来抵消特定神经网络找到的解决方案中的方差。
这涉及在相同数据集上多次拟合相同算法,但每次运行算法时都允许学习算法中使用的随机性有所不同。模型在每次运行中都在相同的测试集上进行评估,并记录分数。在所有重复结束后,使用均值和标准差总结分数的分布。
模型在多次运行中的性能平均值可以了解特定模型在特定数据集上的平均性能。分数的分布或标准差可以了解学习算法引入的方差。
我们可以将单个 MLP 的评估移动到一个函数中,该函数接受数据集并返回测试集上的准确率。下面的 *evaluate_model()* 函数实现了这种行为。
1 2 3 4 5 6 7 8 9 10 11 12 |
# 评估一个 mlp 模型 def evaluate_model(trainX, trainy, testX, testy): # 定义模型 model = Sequential() model.add(Dense(25, input_dim=2, activation='relu')) model.add(Dense(1, activation='sigmoid')) model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) # 拟合模型 model.fit(trainX, trainy, epochs=500, verbose=0) # 评估模型 _, test_acc = model.evaluate(testX, testy, verbose=0) return test_acc |
然后我们可以多次调用此函数,例如 10 次、30 次或更多次。
根据大数定律,运行次数越多,估计越准确。为了保持运行时间适中,我们将重复运行 10 次。
1 2 3 4 5 6 7 8 9 10 |
# 评估模型 n_repeats = 10 scores = list() for i in range(n_repeats): # 评估模型 score = evaluate_model(trainX, trainy, testX, testy) # 存储分数 scores.append(score) # 总结本次运行的分数 print('>%d: %.3f' % (i+1, score*100)) |
然后,在这些运行结束时,将报告分数的平均值和标准差。我们还可以通过 boxplot() matplotlib 函数 使用箱线图来总结分布。
1 2 3 4 5 6 |
# 报告分数分布 mean_score, std_score = mean(scores)*100, std(scores)*100 print('Score Over %d Runs: %.3f (%.3f)' % (n_repeats, mean_score, std_score)) # 绘制分布图 pyplot.boxplot(scores) pyplot.show() |
将所有这些元素结合起来,下面列出了在圆数据集上重复评估 MLP 模型的示例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
# 在圆数据集上重复评估 mlp from sklearn.datasets import make_circles from keras.layers import Dense from keras.models import Sequential from numpy import mean from numpy import std from matplotlib import pyplot # 创建测试数据集 def create_dataset(n_train, n_test, noise=0.1): # 生成样本 n_samples = n_train + n_test X, y = make_circles(n_samples=n_samples, noise=noise, random_state=1) # 分割为训练集和测试集 trainX, testX = X[:n_train, :], X[n_train:, :] trainy, testy = y[:n_train], y[n_train:] # 返回样本 return trainX, trainy, testX, testy # 评估一个 mlp 模型 def evaluate_model(trainX, trainy, testX, testy): # 定义模型 model = Sequential() model.add(Dense(25, input_dim=2, activation='relu')) model.add(Dense(1, activation='sigmoid')) model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) # 拟合模型 model.fit(trainX, trainy, epochs=500, verbose=0) # 评估模型 _, test_acc = model.evaluate(testX, testy, verbose=0) return test_acc # 创建数据集 trainX, trainy, testX, testy = create_dataset(500, 500) # 评估模型 n_repeats = 10 scores = list() for i in range(n_repeats): # 评估模型 score = evaluate_model(trainX, trainy, testX, testy) # 存储分数 scores.append(score) # 总结本次运行的分数 print('>%d: %.3f' % (i+1, score*100)) # 报告分数分布 mean_score, std_score = mean(scores)*100, std(scores)*100 print('Score Over %d Runs: %.3f (%.3f)' % (n_repeats, mean_score, std_score)) # 绘制分布图 pyplot.boxplot(scores) pyplot.show() |
运行示例首先报告模型在每次重复评估中的得分。
注意:由于算法或评估过程的随机性,或数值精度的差异,您的 结果可能有所不同。请考虑多次运行示例并比较平均结果。
在重复运行结束时,平均分报告为约 84.7%,标准差约为 0.5%。这意味着对于在这个特定训练集上训练并在特定测试集上评估的特定模型,99% 的运行将导致测试准确率在 83.2% 到 86.2% 之间(假设与均值相距三个标准差)。
毫无疑问,10 个小样本量导致这些估计存在一些误差。
1 2 3 4 5 6 7 8 9 10 11 |
>1: 84.600 >2: 84.800 >3: 85.400 >4: 85.000 >5: 83.600 >6: 85.600 >7: 84.400 >8: 84.600 >9: 84.600 >10: 84.400 10 次运行的得分:84.700 (0.531) |
创建了一个测试准确率分数的箱线图,显示了分数中间 50%(称为四分位距),由方框表示,范围从略低于 84.5% 到略低于 85%。
我们还可以看到,观察到的 83% 的值可能是一个异常值,因为它被标记为一个点。

MLP 在圆问题上的测试准确率箱线图
研究测试精度与训练集大小
给定固定的统计噪声量和固定但合理配置的模型,需要多少示例才能学习圆问题?
我们可以通过评估在不同大小的训练数据集上拟合的 MLP 模型的性能来研究这个问题。
作为基础,我们可以定义大量我们认为足以学习问题的示例,例如 100,000 个。我们可以将其用作训练示例数量的上限,并在测试集中使用这么多示例。我们将在这个数据集上表现良好的模型定义为已有效学习两圆问题的模型。
然后,我们可以通过拟合不同大小训练数据集的模型并在测试集上评估其性能来进行实验。
太少的示例会导致较低的测试准确率,可能是因为所选模型过拟合了训练集,或者训练集不能充分代表问题。
过多的示例将导致良好但可能略低于理想的测试准确率,这可能是因为所选模型没有能力学习如此大型训练数据集的细微之处,或者数据集过度代表了问题。
训练数据集大小与模型测试准确率的折线图应显示出递增趋势,直至达到收益递减点,甚至可能最终略有下降。
我们可以使用上一节中定义的 *create_dataset()* 函数来创建训练和测试数据集,并将测试集大小参数的默认值设置为 100,000 个示例,同时允许训练集大小在每次调用时指定和变化。重要的是,我们希望对每个不同大小的训练数据集使用相同的测试集。
1 2 3 4 5 6 7 8 9 10 |
# 创建训练和测试数据集 def create_dataset(n_train, n_test=100000, noise=0.1): # 生成样本 n_samples = n_train + n_test X, y = make_circles(n_samples=n_samples, noise=noise, random_state=1) # 拆分为训练和测试,前 n 个用于测试 trainX, testX = X[n_test:, :], X[:n_test, :] trainy, testy = y[n_test:], y[:n_test] # 返回样本 return trainX, trainy, testX, testy |
我们可以直接使用上一节中相同的 *evaluate_model()* 函数来拟合和评估给定训练集和测试集上的 MLP 模型。
1 2 3 4 5 6 7 8 9 10 11 12 |
# 评估一个 mlp 模型 def evaluate_model(trainX, trainy, testX, testy): # 定义模型 model = Sequential() model.add(Dense(25, input_dim=2, activation='relu')) model.add(Dense(1, activation='sigmoid')) model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) # 拟合模型 model.fit(trainX, trainy, epochs=500, verbose=0) # 评估模型 _, test_acc = model.evaluate(testX, testy, verbose=0) return test_acc |
我们可以创建一个新函数来对给定模型进行重复评估,以考虑随机学习算法。
下面的 *evaluate_size()* 函数接受训练集大小作为参数,以及重复次数(默认为 5 次以减少运行时间)。*create_dataset()* 函数用于创建训练集和测试集,然后重复调用 *evaluate_model()* 函数来评估模型。该函数然后返回重复运行的分数列表。
1 2 3 4 5 6 7 8 9 10 11 |
# 重复评估给定大小数据集的 mlp 模型 def evaluate_size(n_train, n_repeats=5): # 创建数据集 trainX, trainy, testX, testy = create_dataset(n_train) # 重复评估带有数据集的模型 scores = list() for _ in range(n_repeats): # 评估模型大小 score = evaluate_model(trainX, trainy, testX, testy) scores.append(score) return scores |
然后可以重复调用此函数。我猜测 1,000 到 10,000 个问题示例足以学习该问题,其中“足够”意味着测试准确率只有很小的分数差异。
因此,我们将研究 100、1,000、5,000 和 10,000 个示例的训练集大小。将报告每个测试大小的平均测试准确率,以了解进展情况。
1 2 3 4 5 6 7 8 9 10 11 |
# 定义要评估的数据集大小 sizes = [100, 1000, 5000, 10000] score_sets, means = list(), list() for n_train in sizes: # 重复评估训练集大小的模型 scores = evaluate_size(n_train) score_sets.append(scores) # 总结该大小的分数 mean_score = mean(scores) means.append(mean_score) print('Train Size=%d, Test Accuracy %.3f' % (n_train, mean_score*100)) |
在运行结束时,将创建一个折线图,显示训练集大小与模型测试集准确率之间的关系。我们预计会看到从低准确率到收益递减点呈指数曲线增长。
还创建了每个测试集大小得分分布的箱线图。我们预计随着训练集大小的增加,测试准确率的分布范围会缩小。
完整的示例如下所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
# 研究 MLP 在圆问题上的训练集大小 from sklearn.datasets import make_circles from keras.layers import Dense from keras.models import Sequential from numpy import mean from matplotlib import pyplot # 创建训练和测试数据集 def create_dataset(n_train, n_test=100000, noise=0.1): # 生成样本 n_samples = n_train + n_test X, y = make_circles(n_samples=n_samples, noise=noise, random_state=1) # 拆分为训练和测试,前 n 个用于测试 trainX, testX = X[n_test:, :], X[:n_test, :] trainy, testy = y[n_test:], y[:n_test] # 返回样本 return trainX, trainy, testX, testy # 评估一个 mlp 模型 def evaluate_model(trainX, trainy, testX, testy): # 定义模型 model = Sequential() model.add(Dense(25, input_dim=2, activation='relu')) model.add(Dense(1, activation='sigmoid')) model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) # 拟合模型 model.fit(trainX, trainy, epochs=500, verbose=0) # 评估模型 _, test_acc = model.evaluate(testX, testy, verbose=0) return test_acc # 重复评估给定大小数据集的 mlp 模型 def evaluate_size(n_train, n_repeats=5): # 创建数据集 trainX, trainy, testX, testy = create_dataset(n_train) # 重复评估带有数据集的模型 scores = list() for _ in range(n_repeats): # 评估模型大小 score = evaluate_model(trainX, trainy, testX, testy) scores.append(score) 返回 分数 # 定义要评估的数据集大小 sizes = [100, 1000, 5000, 10000] score_sets, means = list(), list() for n_train in sizes: # 重复评估训练集大小的模型 scores = evaluate_size(n_train) score_sets.append(scores) # 总结该大小的分数 mean_score = mean(scores) means.append(mean_score) print('Train Size=%d, Test Accuracy %.3f' % (n_train, mean_score*100)) # 总结训练集大小与测试准确率的关系 pyplot.plot(sizes, means, marker='o') pyplot.show() # 绘制不同训练集大小的测试准确率分布 pyplot.boxplot(score_sets, labels=sizes) pyplot.show() |
在现代硬件上运行该示例可能需要几分钟。
注意:由于算法或评估过程的随机性,或数值精度的差异,您的 结果可能有所不同。请考虑多次运行示例并比较平均结果。
报告了每个训练集大小的平均模型性能,显示测试准确率随着训练集的增加而稳步提高,正如我们所预期的那样。我们还看到从 5,000 个示例到 10,000 个示例,平均模型性能略有下降,这很可能表明数据样本中的方差已超出了所选模型配置(层数和节点数)的能力。
1 2 3 4 |
训练集大小=100,测试准确率 72.041 训练集大小=1000,测试准确率 83.719 训练集大小=5000,测试准确率 84.062 训练集大小=10000,测试准确率 84.025 |
创建了测试准确率与训练集大小的折线图。
我们可以看到从 100 到 1,000 个示例,测试准确率急剧增加,之后性能似乎趋于平稳。

MLP 模型在圆问题上的训练集大小与测试集准确率的折线图
创建了一个箱线图,显示了每个大小训练集的测试准确率得分分布。正如预期,我们可以看到随着训练集大小的增加,测试集准确率得分的分布范围急剧缩小,尽管在给定所选刻度的情况下,该图上的范围仍然很小。

MLP 在圆问题上使用不同大小训练集训练的测试集准确率箱线图
结果表明,所选的 MLP 模型配置能够以 1,000 个示例相当好地学习该问题,而在 5,000 和 10,000 个示例下,性能仅有适度提高。也许 2,500 个示例是一个最佳点,可以实现 84% 的测试集准确率,而无需超过 5,000 个示例。
神经网络的性能可以随着提供给模型的更多数据而不断提高,但模型的容量必须进行调整以支持数据的增加。最终,会出现一个收益递减点,即更多数据将无法提供更多关于如何最好地建模映射问题的见解。对于像两圆这样更简单的问题,这个点将比在更复杂的问题(例如对物体照片进行分类)中更早达到。
这项研究强调了应用机器学习的一个基本方面,特别是你需要足够的问题示例来学习未知底层映射函数的有用近似值。
我们几乎从不拥有过多的训练数据。因此,我们的重点通常是如何最经济地利用可用数据,以及如何避免过拟合训练数据集中存在的统计噪声。
研究测试集大小与测试集精度
给定固定的模型和固定的训练数据集,需要多少测试数据才能准确估计模型性能?
我们可以通过拟合一个固定大小训练集的 MLP 并使用不同大小测试集评估模型来研究这个问题。
我们可以沿用上一节研究的策略。我们将训练集大小固定为 1,000 个示例,因为它在 100,000 个示例上评估时,模型表现相当有效,估计准确率约为 83.7%。我们期望存在一个较小的测试集大小,可以合理地近似这个值。
可以更新 *create_dataset()* 函数以指定测试集大小,并将训练集大小默认设置为 1,000 个示例。重要的是,每次测试集大小变化时,都使用相同的 1,000 个示例作为训练集。
1 2 3 4 5 6 7 8 9 10 |
# 创建数据集 def create_dataset(n_test, n_train=1000, noise=0.1): # 生成样本 n_samples = n_train + n_test X, y = make_circles(n_samples=n_samples, noise=noise, random_state=1) # 拆分为训练和测试,前 n 个用于测试 trainX, testX = X[:n_train, :], X[n_train:, :] trainy, testy = y[:n_train], y[n_train:] # 返回样本 return trainX, trainy, testX, testy |
我们可以使用相同的 *fit_model()* 函数来拟合模型。因为我们使用相同的训练数据集并改变测试数据集,所以我们可以一次性创建和拟合模型,并将其重新用于每个不同大小的测试集。在这种情况下,我们将在相同的训练数据集上拟合 10 个模型,以模拟 10 次重复。
1 2 3 4 5 6 |
# 创建固定的训练数据集 trainX, trainy, _, _ = create_dataset(10) # 为每次重复拟合一个模型 n_repeats = 10 models = [fit_model(trainX, trainy) for _ in range(n_repeats)] print('Fit %d models' % n_repeats) |
一旦拟合,我们可以使用给定大小的测试数据集评估每个模型。下面的 *evaluate_test_set_size()* 函数实现了这种行为,它返回拟合模型的测试集准确率分数列表和给定测试集大小。
1 2 3 4 5 6 7 8 9 10 |
# 在拟合模型上评估给定大小的测试集 def evaluate_test_set_size(models, n_test): # 创建数据集 _, _, testX, testy = create_dataset(n_test) scores = list() for model in models: # 评估模型 _, test_acc = model.evaluate(testX, testy, verbose=0) scores.append(test_acc) return scores |
我们将评估四个不同大小的测试集,分别包含 100、1,000、5,000 和 10,000 个示例。然后我们可以报告每个大小测试集的平均分数,并创建相同的折线图和箱线图。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# 定义要评估的测试集大小 sizes = [100, 1000, 5000, 10000] score_sets, means = list(), list() for n_test in sizes: # 在模型上评估给定大小的测试集 scores = evaluate_test_set_size(models, n_test) score_sets.append(scores) # 总结该大小的分数 mean_score = mean(scores) means.append(mean_score) print('Test Size=%d, Test Accuracy %.3f' % (n_test, mean_score*100)) # 总结测试集大小与测试准确率的关系 pyplot.plot(sizes, means, marker='o') pyplot.show() # 绘制测试集大小与测试准确率的分布图 pyplot.boxplot(score_sets, labels=sizes) pyplot.show() |
将这些元素联系起来,完整的示例如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
# 研究 MLP 在圆问题上的测试集大小 from sklearn.datasets import make_circles from keras.layers import Dense from keras.models import Sequential from numpy import mean from matplotlib import pyplot # 创建数据集 def create_dataset(n_test, n_train=1000, noise=0.1): # 生成样本 n_samples = n_train + n_test X, y = make_circles(n_samples=n_samples, noise=noise, random_state=1) # 拆分为训练和测试,前 n 个用于测试 trainX, testX = X[:n_train, :], X[n_train:, :] trainy, testy = y[:n_train], y[n_train:] # 返回样本 return trainX, trainy, testX, testy # 拟合一个 mlp 模型 def fit_model(trainX, trainy): # 定义模型 model = Sequential() model.add(Dense(25, input_dim=2, activation='relu')) model.add(Dense(1, activation='sigmoid')) model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy']) # 拟合模型 model.fit(trainX, trainy, epochs=500, verbose=0) return model # 在拟合模型上评估给定大小的测试集 def evaluate_test_set_size(models, n_test): # 创建数据集 _, _, testX, testy = create_dataset(n_test) scores = list() for model in models: # 评估模型 _, test_acc = model.evaluate(testX, testy, verbose=0) scores.append(test_acc) 返回 分数 # 创建固定的训练数据集 trainX, trainy, _, _ = create_dataset(10) # 为每次重复拟合一个模型 n_repeats = 10 models = [fit_model(trainX, trainy) for _ in range(n_repeats)] print('Fit %d models' % n_repeats) # 定义要评估的测试集大小 sizes = [100, 1000, 5000, 10000] score_sets, means = list(), list() for n_test in sizes: # 在模型上评估给定大小的测试集 scores = evaluate_test_set_size(models, n_test) score_sets.append(scores) # 总结该大小的分数 mean_score = mean(scores) means.append(mean_score) print('Test Size=%d, Test Accuracy %.3f' % (n_test, mean_score*100)) # 总结测试集大小与测试准确率的关系 pyplot.plot(sizes, means, marker='o') pyplot.show() # 绘制测试集大小与测试准确率的分布图 pyplot.boxplot(score_sets, labels=sizes) pyplot.show() |
运行示例报告了每个不同大小测试集的测试集准确率,这些准确率是在同一数据集上训练的 10 个模型中取平均值得到的。
注意:由于算法或评估过程的随机性,或数值精度的差异,您的 结果可能有所不同。请考虑多次运行示例并比较平均结果。
如果我们以上一节中在 100,000 个示例上评估时得到的 83.7% 的结果作为真实值的估计,那么我们可以看到较小尺寸的测试集在这个估计值上下浮动。
1 2 3 4 5 |
拟合 10 个模型 测试集大小=100,测试准确率 86.100 测试集大小=1000,测试准确率 82.410 测试集大小=5000,测试准确率 84.228 测试集大小=10000,测试准确率 83.760 |
创建了测试集大小与测试集准确率的折线图。
该图显示,100 和 1,000 个示例的较小测试集分别高估和低估了模型的准确率。5,000 和 10,000 个测试集的结果更接近,后者可能显示出最佳匹配。
这令人惊讶,因为一个朴素的期望可能是,与训练集大小相同的测试集将是模型准确率的合理近似值,例如,就像我们将 2,000 个示例的数据集进行 50%/50% 分割一样。

MLP 在圆问题上的测试集大小与测试集准确率的折线图
箱线图显示,较小的测试集显示出较大的分数分布,所有这些分数都过于乐观。

不同测试集大小在圆问题上的测试集准确率分布箱线图
应该指出的是,我们不是在报告所选模型在从领域中抽取的随机示例上的平均性能。模型和示例是固定的,变化的唯一来源来自学习算法。
该研究表明了模型的估计准确度对测试数据集大小的敏感程度。这是一个重要的考虑因素,因为通常很少考虑测试集大小,而是使用熟悉的 70%/30% 或 80%/20% 的训练/测试分割。
正如前一节所指出的,我们通常很少有足够的数据,而将宝贵的数据用于测试甚至验证数据集通常很少被深入考虑。结果强调了交叉验证方法(例如留一法交叉验证)的重要性,它们允许模型对数据集中的每个示例进行测试预测,但却带来了巨大的计算成本,几乎需要为数据集中的每个示例训练一个模型。
在实践中,我们必须努力解决训练数据集太小而无法学习未知的底层映射函数,以及测试数据集太小而无法合理近似模型在问题上的性能。
进一步阅读
如果您想深入了解,本节提供了更多关于该主题的资源。
- sklearn.datasets.make_circles API
- matplotlib.pyplot.scatter API
- matplotlib.pyplot.subplot API
- numpy.where API
- Keras 主页
- Keras 序列模型 API
总结
在本教程中,您发现实际上我们没有足够的数据来学习映射函数或评估模型,但神经网络等监督学习算法仍然非常有效。
具体来说,你学到了:
- 如何分析两圆分类问题并测量神经网络学习算法引入的方差。
- 训练数据集大小的变化如何直接影响神经网络近似的映射函数的质量。
- 测试数据集大小的变化如何直接影响拟合神经网络模型性能的估计质量。
你有什么问题吗?
在下面的评论中提出你的问题,我会尽力回答。
感谢杰森一如既往的精彩文章。
我对二元分类的小数据集有一个问题,我们应该使用哪种最佳的预处理实现?
我们需要将数据归零还是仅对数据集进行归一化?
如果我的问题不够清楚,请原谅。
谢谢
尝试一系列预处理步骤,看看哪种方法能带来更快的学习速度或更好的性能。
我有一个包含两年交易记录的数据集,我决定使用该分布的最后两个月作为测试集,其余的用于训练集和开发集。这是一个好主意吗?我也意识到测试集中可能缺少标签。问题是多标签分类。
谢谢!
我建议测试不同数量的历史输入,以发现哪种方法最适合您的特定数据集。
早上好,Jason,
感谢这篇文章!
有没有什么特别的原因,为什么在 ANN 初始化时不能设置伪随机数生成器种子?(就像我们对数据分割或许多机器学习算法所做的那样)。我猜初始 ANN 权重和 SGD 也可以使用这个种子。你知道为什么这些实现会忽略/省略这个功能吗?
此致!
是的,这是一个糟糕的策略。
https://machinelearning.org.cn/randomness-in-machine-learning/
相反,我们通过重复的模型评估来控制学习算法的随机性。
https://machinelearning.org.cn/evaluate-skill-deep-learning-models/
嗨
非常感谢这篇详细的教程。
请问您是否有关于维度与准确率以及准确率增益何时会随维度减小的信息?
谢谢
Vel
这取决于模型和数据集。
你可以使用你自己的模型和数据集进行研究,以发现这种关系。
你好,特征数量与 DL 模型的训练或测试时间之间是否存在任何相关性?训练一个包含 50 个特征的模型会比训练一个包含 10 个特征的模型花费更长时间吗?谢谢
是的,特征越多意味着模型速度越慢。
你好,
我想问一下,对于我的数据集的 2000 个样本的二元分类……每个样本有 475 个数据点……我的算法在 Jupyter Notebook 上训练速度非常快,并显示 90% 的准确率……我做错了什么?它在 50 个 epoch 内训练不到一分钟……我正在使用 Keras 和 TensorFlow……我以为神经网络需要很长时间才能训练……我是这个主题的新手,如果这看起来很愚蠢,请原谅我……我做错了什么吗?
问题究竟是什么?
它训练得很快?
它达到了 90% 的准确率?
我目前正在研究图像分类问题(二元)。我对两个类别都有相同数量的图像,但实际上,其中一个类别出现的可能性远低于另一个。有没有办法在训练过程中反映这一点?此外,我还有与这里大多数人相同的问题:如何找出足够的数据量以防止过拟合?目前,我每个类别有大约 250 张原始图像,并且我已经实施了数据增强,将每个类别的大小增加到大约 750 张。如何找出还需要多少张图像?提前感谢。
是的,理想情况下,训练数据集和测试数据集应代表底层问题。也许您可以获得一个更好地匹配领域随机样本的不同样本?
我目前正在研究作物推荐问题,我正在考虑 7 个特征来预测最合适的作物。我有 2200 行数据集。预测 22 种作物。如何知道这是否足以进行预测。如果需要更多数据实例,如果是,请帮助我如何在数值数据集的情况下增加数据集的大小
也许可以评估几个模型,看看现有数据在开发预测模型方面有多大用处。
非常感谢这个很好的例子。我了解到训练数据的大小是多么重要。
不客气。
感谢这个教程。我需要使用CNN来确定5个不同的类别,我的数据集中有不同数量的图像。这会对CNN的学习产生不利影响吗?还是所有数据集的图像数量必须完全相同?
只要每个类别的数量大致相同,就应该没有问题。如果你有一个类别的图像数量比另一个类别多100倍,模型可能会盲目选择多数类别,仍然能获得一个好的准确率分数。这是数据不平衡的问题。如果你的情况没有那么夸张,那应该没问题。
感谢详细的例子。
我目前正在处理一个文本分类问题,我有很多原始数据,但都没有被标记。因此,我正在对数据进行标记,同时对转换器模型进行微调。我正在对数据集进行版本控制,但拥有150个样本的数据集(版本3)优于拥有350个样本的最新数据集。
性能差异大约为5%,我一直在监测一些指标(准确率、F1、召回率和精确率,以及Matthews相关系数),当我继续向数据集3添加更多标记数据后,性能急剧下降。
划分是分层的,测试集比例为20%。我的结论是由于测试集大小导致的过高估计,是否还有其他情况需要考虑?
你好 Efe… 感谢你的反馈!以下是开始改进模型性能的好地方
https://machinelearning.org.cn/better-deep-learning-neural-networks-crash-course/