误差模型:过拟合,交叉验证,偏差-方差权衡
作者Natasha Latysheva;Charles Ravarani
发表于cambridgecoding
介绍
在本文中或许你会掌握机器学习中最核心的概念:偏差-方差权衡.其主要想法是,你想创建尽可能预測准确而且仍能适用于新数据的模型(这是泛化).危急的是,你能够轻松的在你制定的数据中创建过度拟合本地噪音的模型,这样的模型是没用的,而且导致弱泛化能力,由于噪声是随机的,故而在每一个数据集中是不同的.从本质上讲,你希望创建仅捕获数据集中实用成份的模型.还有一方面,泛化能力很好可是对于产生良好预測过于僵化的模型是还有一个极端(这称之为欠拟合).
我们使用k-近邻算法讨论并展示这些概念,k-近邻带有一个简单的參数k,能够用不同的參数清楚的展示欠拟合。过拟合以及泛化能力的思想.同一时候,平衡欠拟合和过拟合之间的相关概念称为偏差-方差权衡.这里有一个表格概括了不管是过拟合或者欠拟合模型中一些不同但相同
我们将解释这些术语的意思,以及他们怎样关联的.相同也会讨论交叉验证,这是评估模型准确率和泛化能力的优秀指标.
你会在未来的全部博文中遇到这些概念,将涵盖模型优化,随机森林,朴素贝叶斯,逻辑回归以及怎样将不同模型组合成为集成元模型.
产生数据
让我们从建立人工数据集開始.你能够轻松的使用sklearn.datasets中的make_classification()函数做到这一点.详细来说,你会生成相对简单的二元分类问题.为了让它更有趣一点,让我们的数据呈现月牙型并加入一些随机噪声.这应该能让其更真实并提高分类观測的难度.
“`
Creating the dataset
e.g. make_moons generates crescent-shaped data
Check out make_classification, which generates linearly-separable data
from sklearn.datasets import make_moons
X, y = make_moons(
n_samples=500, # the number of observations
random_state=1,
noise=0.3
)
Take a peek
print(X[:10,])
print(y[:10])
“`
[[ 0.50316464 0.11135559]
[ 1.06597837 -0.63035547]
[ 0.95663377 0.58199637]
[ 0.33961202 0.40713937]
[ 2.17952333 -0.08488181]
[ 2.00520942 0.7817976 ]
[ 0.12531776 -0.14925731]
[ 1.06990641 0.36447753]
[-0.76391099 -0.6136396 ]
[ 0.55678871 0.8810501 ]]
[1 1 0 0 1 1 1 0 0 0]
你刚生成的数据集例如以下图所看到的:
“`
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColorma
%matplotlib inline # for the plots to appear inline in jupyter notebooks
Plot the first feature against the other, color by class
plt.scatter(X[y == 1, 0], X[y == 1, 1], color=”#EE3D34”, marker=”x”)
plt.scatter(X[y == 0, 0], X[y == 0, 1], color=”#4458A7”, marker=”o”)
“`
接下来,让我们将数据且分为训练集 和測试集 .训练集用于开发和优化模型.測试集全然分离,直到最后在此执行完毕的模型.拥有測试集同意你在之前看不到的数据之外,模型执行良好的预计.
“`
from sklearn.cross_validation import train_test_split
Split into training and test sets
XTrain, XTest, yTrain, yTest = train_test_split(X, y, random_state=1, test_size=0.5)
“`
使用K近邻(KNN)分类器预測数据集类别.Introduction to Statistical Learning第二章提供了关于KNN理论很好介绍.我是ISLR书的脑残粉.你相同能够看看之前文章 how to implement the algorithm from scratch in Python.
介绍KNN中的超參数K
KNN算法的工作原理是,对新数据点利用K近邻信息分配类别标签.仅仅注重于和它最类似数据点的类,并分配这个新数据点到这些近邻中最常见的类.当使用KNN,你须要设定希望算法使用的K值.
假设K很高(k=99),模型在对未知数据点类别做决策是会考虑大量近邻.这意味着模型是相当受限的,由于它分类实例时,考虑了大量信息.换句话说,一个大的k值导致相当”刚性”的模型行为.
相反,假设k很低(k=1,或k=2),在做分类决策时仅仅考虑少量近邻,这是很灵活而且很复杂的模型,它能完美拟合数据的精确形式.因此模型预測更依赖于数据的局部趋势(关键的是,包括噪声).
让我们看一看k=99与k=1时KNN算法分类数据的情况.绿色的线是训练数据的决策边界(算法中的阈值决定一个数据点是否属于蓝或红类).
在本文最后你会学会怎样生成这些图像,可是先让我们先深入理论.
当k=99(左),看起来模型拟合有点太平滑,对于有点接近的数据能够忍受.模型具有低灵活性 和低复杂度 .它描绘了一个笼统的决策边界.它具有比較高的偏差 ,由于对数据建模并不好,模型化数据的底层生成过程太过简单,而且偏离了事实.可是,假设你扔到还有一个略微不同的数据集,决策边界可能看起来很类似.这是不会有很大差异的稳定模型–它具有低方差.
当k=1(右側),你能够看到模型过度拟合噪声.从技术上来说,在训练集生成很完美的预測结果(在右下角的错误等于0.0),可是希望你能够看到这样的拟合方式对于单独数据点过于敏感.牢记你在数据集中加入了噪声.看起来模型拟合对噪声太过重视而且拟合的很紧密.你能够说,k=1的模型具有高灵活性 和高复杂度 ,由于它对数据调优很紧密.相同具有低偏差,假设不出意外,决策边界肯定适合你观測数据的趋势.可是,在略微改变的数据上,拟合的边界会大大改变,这将是很显著的.K=1的模型具有高方差 .
可是模型的泛化能力怎样?
在新数据上表现怎样?
眼下你仅仅能看到训练数据,可是量化训练误差没多大用处.对模型概括刚学习的训练集性能有多好,你不感兴趣.让我们看看在測试集表现怎样,由于这会对模型好坏给你一个更直观的印象.试着使用不同的K值:
from sklearn.neighbors import KNeighborsClassifier
from sklearn import metrics
knn99 = KNeighborsClassifier(n_neighbors = 99)
knn99.fit(XTrain, yTrain)
yPredK99 = knn99.predict(XTest)
print "Overall Error of k=99 Model:", 1 - round(metrics.accuracy_score(yTest, yPredK99), 2)
knn1 = KNeighborsClassifier(n_neighbors = 1)
knn1.fit(XTrain, yTrain)
yPredK1 = knn1.predict(XTest)
print "Overall Error of k=1 Model:", 1 - round(metrics.accuracy_score(yTest, yPredK1), 2)
Overall Error of k=99 Model: 0.15
Overall Error of k=1 Model: 0.15
实际上,看起来这些模型对測试集表现的大约相同出色.以下是通过训练集学习到的决策边界应用于測试集.看是否能找出两个模型错误的预測.
两个模型出错有不同的原因.看起来k=99的模型对捕获月牙形数据特征方面表现不是很好(这是欠拟合),而k=1的模型是对噪声严重的过拟合.记住,过拟合的特点是良好的训练表现和糟糕的測试表现,你能在这里观察到这些.
或许k在1到99的中间值是你想要的?
knn50 = KNeighborsClassifier(n_neighbors = 50)
knn50.fit(XTrain, yTrain)
yPredK50 = knn50.predict(XTest)
print "Overall Error of k=50 Model:", 1 - round(metrics.accuracy_score(yTest, yPredK50), 2)
Overall Error of k=50 Model: 0.11
看起来好了点.让我们检查k=50时模型的决策边界.
不错!模型拟合类似数据集的实际趋势,这样的改善体如今较低的測试误差.
偏差-方差权衡:结论意见
希望你如今对模型的欠拟合和过拟合有良好的理解.看如今是否理解本文开头的全部术语.基本上,发现过拟合和欠拟合之间正确的平衡关系相当于偏差-方差权衡.
总的来说,当你对一个数据集训练机器学习算法,关注模型在一个独立数据模型的表现怎样.对于训练集做好分类是不够的.本质上来讲,仅仅关心构建可泛化的模型–对于训练集获得100%的准确率并不令人印象深刻,仅仅是过拟合的指标.过拟合是紧密拟合模型,而且调优噪声而不是信号的情况.
更清楚的讲,你不是建模数据集中的趋势.而是尝试建模真实世界过程,引导我们研究数据.你恰好使用的详细数据集仅仅是基础事实的一小部分实例,当中包括噪声和自身的特点.
下列汇总图片展示在训练集和測试集上欠拟合(高偏差,低方差),正确拟合,以及过拟合(低偏差,高方差)模型怎样表现:
建立泛化模型这样的想法背后的动机是切分数据集为为一个训练集和測试集(在你分析的最后提供模型性能的准确測量).
可是,它也有可能过拟合測试数据.假设你对測试集尝试很多不同模型,并为了追求精度不断改变它们,然后測试集的信息可能不经意地渗入到模型创建阶段.你须要一个办法解决.
使用K折交叉验证评估模型性能
输入K折交叉验证,这是仅使用训练集衡量模型性能的一个方便技术.该步骤例如以下:你随机划分训练集为k等份;然后,我们在k-1/k的训练集上训练数据;对剩下的一部分评估性能.这给你一些模型性能的指标(如,总体精度).接下来训练在不同的k-1/k训练集训练算法,并在剩下的1部分评估.你反复这个过程k次,得到k个不同的模型性能度量,利用这些值的平均值得到总体性能的度量.继续样例,10折交叉验证背后例如以下:
你能够使用k折交叉验证获得模型精度的评估,相同能够利用这些预计调整你的模型直到令你惬意.这使得你不用最后測试数据,因此避免了过拟合的危急.换句话说,交叉验证提供一种方式模拟比你实际拥有很多其它的数据,因此你不用建模最后才使用測试集.k折交叉验证以及其变种是很流行而且很实用,尤其你尝试很多不同的模型(假设你想測试不同參数模型性能怎样).
比較训练误差,交叉验证误差和測试误差
那么,什么k是最佳的?对训练数据构建模形式尝试不同K值,看对训练集本身和測试集预測类别的结果模型怎样.最后看K折交叉验证怎样支出最好的K.
注:实践中,当扫描这样的參数,使用训练集測试模型是以个糟糕的主意.相同的方式,你不能使用測试集多次浏览一个參数(每一个參数值一次).接下来,你是用这些计算仅仅是作为样例.实践中,仅仅有K折交叉验证是一种安全的方法!
import numpy as np
from sklearn.cross_validation import train_test_split, cross_val_score
knn = KNeighborsClassifier()
# the range of number of neighbors you want to test
n_neighbors = np.arange(1, 141, 2)
# here you store the models for each dataset used
train_scores = list()
test_scores = list()
cv_scores = list()
# loop through possible n_neighbors and try them out
for n in n_neighbors:
knn.n_neighbors = n
knn.fit(XTrain, yTrain)
train_scores.append(1 - metrics.accuracy_score(yTrain, knn.predict(XTrain))) # this will over-estimate the accuracy
test_scores.append(1 - metrics.accuracy_score(yTest, knn.predict(XTest)))
cv_scores.append(1 - cross_val_score(knn, XTrain, yTrain, cv = 10).mean()) # you take the mean of the CV scores
那么最优的k是多少?当多个相同的预測误差,你随便挑一个最小的作为k值.
# what do these different datasets think is the best value of k?
print(
'The best values of k are: n'
'{} according to the Training Setn'
'{} according to the Test Set andn'
'{} according to Cross-Validation'.format(
min(n_neighbors[train_scores == min(train_scores)]),
min(n_neighbors[test_scores == min(test_scores)]),
min(n_neighbors[cv_scores == min(cv_scores)])
)
)
最优K是:
1 according to the Training Set
23 according to the Test Set and
11 according to Cross-Validation
不仅仅是收集最优的k,还须要对一系列測试的K看看预測误差.
# let's plot the error you get with different values of k
plt.figure(figsize=(10,7.5))
plt.plot(n_neighbors, train_scores, c="black", label="Training Set")
plt.plot(n_neighbors, test_scores, c="black", linestyle="--", label="Test Set")
plt.plot(n_neighbors, cv_scores, c="green", label="Cross-Validation")
plt.xlabel('Number of K Nearest Neighbors')
plt.ylabel('Classification Error')
plt.gca().invert_xaxis()
plt.legend(loc = "lower left")
plt.show()
让我们谈谈训练集的分类错误.你考虑少量近邻,训练集会得到低的预測误差.这是有道理的,由于在做新的分类是,逼近每一个点仅仅考虑它本身的情况.測试误差遵循类似的轨迹,可是在某个点后由于过拟合而增长.这样的现象表明,构建的训练集模型拟合在指定測试集样本上建模效果不好.
在该图中能够看到,尤其是对于k的低值。採用k折交叉验证突出參数空间的区域(即k的很低的值)。这是很easy出现过拟合的。虽然交叉验证和測试集的评估导致一些不同的最优解。它们都是相当不错的。而且大致正确。
你也能够看到。交叉验证是測试误差的合理预计。
这样的类型的情节是好的,以获得确定參数怎样影响模型表现的良好感觉。并帮助建立数据集的直觉来学习。
代码展示
这是生成以上全部图片,训练測试不同kNN算法的代码.代码是scikit-learn样例改编的代码,主要处理决策边界的计算并让图片好看.
包括机器学习中拆分数据集。算法拟合以及測试的部分。
def detect_plot_dimension(X, h=0.02, b=0.05):
x_min, x_max = X[:, 0].min() - b, X[:, 0].max() + b
y_min, y_max = X[:, 1].min() - b, X[:, 1].max() + b
xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
dimension = xx, yy
return dimension
def detect_decision_boundary(dimension, model):
xx, yy = dimension # unpack the dimensions
boundary = model.predict(np.c_[xx.ravel(), yy.ravel()])
boundary = boundary.reshape(xx.shape) # Put the result into a color plot
return boundary
def plot_decision_boundary(panel, dimension, boundary, colors=['#DADDED', '#FBD8D8']):
xx, yy = dimension # unpack the dimensions
panel.contourf(xx, yy, boundary, cmap=ListedColormap(colors), alpha=1)
panel.contour(xx, yy, boundary, colors="g", alpha=1, linewidths=0.5) # the decision boundary in green
def plot_dataset(panel, X, y, colors=["#EE3D34", "#4458A7"], markers=["x", "o"]):
panel.scatter(X[y == 1, 0], X[y == 1, 1], color=colors[0], marker=markers[0])
panel.scatter(X[y == 0, 0], X[y == 0, 1], color=colors[1], marker=markers[1])
def calculate_prediction_error(model, X, y):
yPred = model.predict(X)
score = 1 - round(metrics.accuracy_score(y, yPred), 2)
return score
def plot_prediction_error(panel, dimension, score, b=.3):
xx, yy = dimension # unpack the dimensions
panel.text(xx.max() - b, yy.min() + b, ('%.2f' % score).lstrip('0'), size=15, horizontalalignment='right')
def explore_fitting_boundaries(model, n_neighbors, datasets, width):
# determine the height of the plot given the aspect ration of each panel should be equal
height = float(width)/len(n_neighbors) * len(datasets.keys())
nrows = len(datasets.keys())
ncols = len(n_neighbors)
# set up the plot
figure, axes = plt.subplots(
nrows,
ncols,
figsize=(width, height),
sharex=True,
sharey=True
)
dimension = detect_plot_dimension(X, h=0.02) # the dimension each subplot based on the data
# Plotting the dataset and decision boundaries
i = 0
for n in n_neighbors:
model.n_neighbors = n
model.fit(datasets["Training Set"][0], datasets["Training Set"][1])
boundary = detect_decision_boundary(dimension, model)
j = 0
for d in datasets.keys():
try:
panel = axes[j, i]
except (TypeError, IndexError):
if (nrows * ncols) == 1:
panel = axes
elif nrows == 1: # if you only have one dataset
panel = axes[i]
elif ncols == 1: # if you only try one number of neighbors
panel = axes[j]
plot_decision_boundary(panel, dimension, boundary) # plot the decision boundary
plot_dataset(panel, X=datasets[d][0], y=datasets[d][1]) # plot the observations
score = calculate_prediction_error(model, X=datasets[d][0], y=datasets[d][1])
plot_prediction_error(panel, dimension, score, b=0.2) # plot the score
# make compacted layout
panel.set_frame_on(False)
panel.set_xticks([])
panel.set_yticks([])
# format the axis labels
if i == 0:
panel.set_ylabel(d)
if j == 0:
panel.set_title('k={}'.format(n))
j += 1
i += 1
plt.subplots_adjust(hspace=0, wspace=0) # make compacted layout
然后,你能够这样执行代码:
# specify the model and settings
model = KNeighborsClassifier()
n_neighbors = [200, 99, 50, 23, 11, 1]
datasets = {
"Training Set": [XTrain, yTrain],
"Test Set": [XTest, yTest]
}
width = 20
# explore_fitting_boundaries(model, n_neighbors, datasets, width)
explore_fitting_boundaries(model=model, n_neighbors=n_neighbors, datasets=datasets, width=width)
结论
偏差-方差权衡出如今机器学习的不同领域.全部算法都能够觉得具有一定弹性,而且不仅仅是KNN.发现描写叙述良好数据模式而且能够泛化新数据,这样灵活的最佳点的目标适用于基本上全部算法.