机器学习理论基础
本节概要
因地制宜(模型选择与模型评估)
模型的成本及成本函数的含义
评价一个模型好坏的标准
学习曲线,以及用学习曲线来对模型进行诊断
通用的模型优化方法
其他模型评价标准
模型选择与模型评估
模型选择
世间万物千变万化,问题方法万千多样。我们应如何去面对和选择相应的算法和调整参数呢?这就是机器学习中的 模型选择(model selection) 问题。理想情况当然是我们的模型在面对新数据的时候能够做出完全正确的判断,但退而求其次,我们只能去寻找泛化能力最强,泛化误差最小的模型。这时,我们 模型的选择标准 就等价于 选择一个泛化误差最小的模型 ,但由于上节我们提到的过拟合问题,这个等价问题就明显不成立了。这就很头疼了!
一、要泛化误差小的模型。
二、又不能太过,防止出现过拟合问题。
这个问题就需要一个专门的模型评估方法来解决了。
过拟合和欠拟合问题
过拟合(overfitting) 是指模型能够很好的拟合训练样本,但对新数据的预测准确性很差。
原因:经常是由于学习能力过强大,以至于把训练样本所包含的几乎所有特性都学了。(即囊括了数据中几乎所有细节,但一般多多少少会有一些无关紧要的数据特性)
解决方法:将在例子结尾提出。
欠拟合 (underfitting)是指模型不能很好的拟合训练样本,且对新数据的预测准确性也不好。
原因:学习能力低下,模型太过简单了。
解决方法:将在例子结尾提出。
import numpy as np
生成20个点
n_dots = 20
x = np.linspace(0, 1, n_dots) # [0,1]之间创建20个点
y = np.sqrt(x) + 0.2 * np.random.rand(n_dots) - 0.1
训练样本是 y=x−−√+ry=x+r, 其中r是[-0.1,0.1]之间的一个随机数。
然后分别用一阶多项式、三阶多项式和十阶多项式3个模型来拟合这个数据集,得到的结果如图3-1所示。
左边是欠拟合(underfitting),也称为高偏差(high bias),因为我们尝试用一条线来拟合样本数据。右边是过拟合(overfitting),也称高方差(high varivance),用来十阶多项式来拟合数据,虽然模型对现有的数据集拟合得很好,但对新数据预测误差却很大。只有中间的模型较好的拟合了数据集,可以看出虚线和实现基本拟合。
通过例子是为了读者理解过拟合(高方差)和欠拟合(高偏差)。
def plot_polynomial_fit(x, y, order):
p = np.poly1d(np.polyfit(x, y, order))
# 画出拟合出来的多项式所表达的曲线以及原始的点
t = np.linspace(0, 1, 200)
plt.plot(x, y, 'ro', t, p(t), '-', t, np.sqrt(t), 'r--')
return p
plt.figure(figsize=(18, 4), dpi=200)
titles = ['Under Fitting', 'Fitting', 'Over Fitting']
models = [None, None, None]
for index, order in enumerate([1, 3, 10]):
plt.subplot(1, 3, index + 1)
models[index] = plot_polynomial_fit(x, y, order)
plt.title(titles[index], fontsize=20)
for m in models:
print('model coeffs: {0}'.format(m.coeffs))
针对一阶多项式的模型,不同的参数拟合出来的直线和训练样本对应的位置关系
coeffs_1d = [0.2, 0.6]
plt.figure(figsize=(9, 6), dpi=200)
t = np.linspace(0, 1, 200)
plt.plot(x, y, 'ro', t, models[0](t), '-', t, np.poly1d(coeffs_1d)(t), 'r-')
plt.annotate(r'L1: $y = {1} + {0}x$'.format(coeffs_1d[0], coeffs_1d[1]),
xy=(0.8, np.poly1d(coeffs_1d)(0.8)), xycoords='data',
xytext=(-90, -50), textcoords='offset points', fontsize=16,
arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=.2"))
plt.annotate(r'L2: $y = {1} + {0}x$'.format(models[0].coeffs[0], models[0].coeffs[1]),
xy=(0.3, models[0](0.3)), xycoords='data',
xytext=(-90, -50), textcoords='offset points', fontsize=16,
arrowprops=dict(arrowstyle="->", connectionstyle="arc3,rad=.2"))
3.2成本函数
成本是衡量模型与训练样本符合程度的指标。简单地理解,成本针对所有的训练样本,模型拟合出来的值与训练样本的真实值得误差平均值。而成本函数就是成本与模型参数的函数。模型训练的过程,就是找出适合的模型参数,使得成本函数的值最小。成本函数记为J(θ),其中θ表示模型参数。
针对上一节中的例子,我们用一阶多项式来拟合数据,则得到的模型是y=θ1+θ1xy=θ1+θ1x。此时,[θ0,θ1][θ0,θ1]使得所有的点到这条直线上的距离最短。 图 3-2 模型参数 如图 3-2 所示,不同的模型参数θ对应不同的直线,明显可以看出来L1比L1更好的拟合数据集。根据成本函数的定义,我们可以容地得出模型的成本函数公式: 图 其中,m是训练样本个数,在我们的例子里,20个点,而h(x(i))h(x(i))就是模型对每个样本的预测值,y(i)y(i)是每个样本的真实值。这个公式实际上就是线性回归算法的成本函数的简化表达。
一个数据集可能有多个模型可以用来拟合它,而一个模型有无穷个模型参数,针对特定的数据集和特定的模型,只有一个模型参数能最好的拟合这个数据集,这就是模型和模型参数的关系。回到本章开头的例里的三张图片,针对生成的20多个训练样本,我们用3个模型来拟合这个数据集,分别是一阶多项式,三阶多项式和十阶多项式。模型训练的目的就是为了找出一组最优的模型参数,使得这个模型参数所代表的一阶多项式对应的成本最低,同理,理解三阶多项式和十阶多项式。
问题来了,多个模型之间怎么评价好坏呢?针对我们的例子,一阶多项式,三阶多项式和十阶多项式,到底哪个模型更好呢?针对训练样本成本最小的模型就是最好的吗?在我们的例子里,十阶多项式针对训练样本的成本最小,因为它的预测曲线几乎穿过的了所有的点,训练样本到曲线的距离的平均值最小。那是不是意味着十阶多项式是最好的模型吗?答案是否定的,因为它过拟合了。
过拟合到底有么不好?我们要用什么样的标准来评价一个模型的好坏?
3.3 模型准确度
测试数据集 的成本,即Jtest(θ)是评估模型准确度的最直观的指标,Jtest(θ)值越小说明模型预测出来的值与实际值差异越小,对新数据的预测准确性就越好。 那么我们怎么计算测试数据集的误差呢?简单地说,就是用测试数据集和训练出来的模型参数代入相应的成本函数里,计算测试数据集的成本。
针对上文我们介绍的线性回归算法,可以使用下面的公式计算测试数据集的误差,其中m是测试数据集的个数:
3.3.1 模型性能的不同表达方式
在scikit-learn里,不使用成本函数来表达模型的性能,而使用分数来表达,这个分数总是在[0,1]之间,数值越大说明模型的准确性越好。当模型训练完成后,调用模型的score(Xtest,ytest)score(Xtest,ytest)即可算出模型的分数值,其中XtestXtest 和 ytestytest 是测试数据集样本.
模型分数(准确性)与成本成反比。即分数越大,准确性越高,误差越小,成本越低;反之,分数越小,准确性越低,误差越大,成本越高。
3.3.2交叉验证数据集
另外一个更科学的方法便是将数据集分成3分,分别是训练集,交叉验证数据集,测试数据集合,推荐比例是6:2:2.
在模型选择时,我们使用了交验证数据集,所以筛选模型多项式阶数d的过程中,实际上并没有使用测试数据集。这样保证了使用测试集来计算成本衡量模型的准确性,我们选择出来的模型是没有“见过”测试数据,即测试数据集没有参与模型选择的过程。
在实际过程中,很多人直接把数据集分成训练数集和测试数据集,而没有分出交叉验证数据集。这是因为多少时候并不需要横向去对比不同的模型。在工程上,大多数时候我们最主要的工作不是选择模型,而是获取更多数据、分析数据、挖掘数据。
3.4学习曲线
我们可以把Jtrain(θ)Jtrain(θ)和Jcv(θ)Jcv(θ)作为纵坐标,画出与训练数据集m的大小关系,这就是学习曲线。通过学习曲线,可以直观地观察到模型的准确性与训练数据集的大小关系。
如果数据集的大小为m,则通过下面的流程则可画出学习曲线:
把数据集分成训练数据集和交叉验证数据集。
取训练数据集的20%作为训练样本,训练出模型参数。
使用交叉验证数据集来计算训练出来的模型的准确性。
以训练数据集打的准确性,交叉验证的准确性作为纵坐标,训练数据集个数作为横坐标,在坐标轴上画出上述步骤计算出来的模型准确性。
训练数据集囎加10%,跳到步骤3继续执行,直到训练数据集大小为100%为止。
学习曲线要表达的内容是,当训练数据集增加时,模型对训练数据集拟合的准确以及对交叉验证数据集预测的准确性的变化规律。
3.4.1实例:画出学习曲线
通过一个例子来看看在scikit-learn里如何画出模型的学习曲线,从而判断模型的准确性及优化方向。
我们继续还是生成一个在 y=根号x附近波动的点来作为训练样本,不过这次哟多生成一些,因为要考虑当训练样本数量增加的时候,模型的准确性是怎么变化的。
import numpy as np
n_dots = 200
X = np.linspace(0,1,n_dots)
y = np.sqrt(X) + 0.2np.random.rand(n_dots) - 0.1
#因为sklearn的接口里,需要用到n_sample x n_feature的矩阵
#所以需要转化为200 x 1 的矩阵
X = X.reshape(-1, 1)
y = y.reshape(-1, 1)
(1)需要构造一个多项式模型。在scikit-learn里,需要用Pipeline来构造多项式模型,Pipeline的意思是流水线,即这个流水线里可以包含多个数据处理模型,前一个模型处理完,转到下一个模型处理。
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
def polynomial_model(degree = 1):
polynomial_features=PolynomialFeatures(degree=degree,include_bias=False)
linear_regression=LinearRegression()
#这是一个流水线,先增加多项式阶数,然后在用线性回归算法来拟合数据
pipeline = Pipeline([("polynomail_features",polynomial_features),("linear_regression",linear_regression)])
return pipeline
polynomial_model()函数生成一个多多项式模型,其中参数degree表示多项式的阶数,比如polynomial_model(3)将生成一个三阶多项式的模型。
在scikit-learn里面,我们不用去实现学习曲线算法,直接使用sklearn.model_selection.learning_curve()函数来画出学习曲线,他会自动把训练样本的数量按照预定的规则逐渐增加,然后画出不同训练样本数量的模型准确性。其中train_sizes参数就是按照指定训练仰样本数量的变化规则,比如train_size参数就是指定训练样本数量的变化规则,比如train_sizes=np.linspace(.1,1.0,表示把训练样本数量从0.1~1分成五等分,生成[0.1,0.325,0.55,0.75,1]的序列,从序列中取出训练样本数量百分比,逐个计算在当前训练样本数据量情况下训练出来的模型准确性
from sklearn.model_selection import learning_curve
from sklearn.model_selection import ShuffleSplit
def plot_learning_curve(estimator, title, X, y, ylim=None, cv=None,n_jobs=1, train_sizes=np.linspace(.1, 1.0, 5)):
plt.title(title)
if ylim is not None:
plt.ylim(*ylim)
plt.xlabel("Training examples")
plt.ylabel("Score")
train_sizes, train_scores, test_scores = learning_curve(
estimator, X, y, cv=cv, n_jobs=n_jobs, train_sizes=train_sizes)
train_scores_mean = np.mean(train_scores, axis=1)
train_scores_std = np.std(train_scores, axis=1)
test_scores_mean = np.mean(test_scores, axis=1)
test_scores_std = np.std(test_scores, axis=1)
plt.grid()
plt.fill_between(train_sizes, train_scores_mean - train_scores_std,
train_scores_mean + train_scores_std, alpha=0.1,
color="r")
plt.fill_between(train_sizes, test_scores_mean - test_scores_std,
test_scores_mean + test_scores_std, alpha=0.1, color="g")
plt.plot(train_sizes, train_scores_mean, 'o--', color="r",
label="Training score")
plt.plot(train_sizes, test_scores_mean, 'o-', color="g",
label="Cross-validation score")
plt.legend(loc="best")
return plt
该函数实现的功能便是画出了模型的学习曲线。其中有个细节需要注意,当计算模型的准确性时,是随机从数据集中分配出训练样本和交叉验证样本,这样会导致数据分布不均匀。即同样训练样本数量的模型,由于随机分配,导致每次计算出来的准确性都不一样。为解决这个问题,我们在计算模型的准确性时,多次计算,并求准确性的平均值和方差。上述代码中plt.fill_between()函数会把模型准确性的平均值和上下方差的空间里用颜色填充。然后用plt.plot()函数画出模型准确性的平均值。上述函数画出了训练样本的准确性,也画出了交叉验证样本的准确性。
(2)使用polynomial_model()函数构造出3个模型,分别是一阶多项式,三阶多项式,十阶多项式,分别画出这三个模型的学习曲线。
为了让学习曲线更平滑,交叉验证数据集的得分计算 10 次,每次都重新选中 20% 的数据计算一遍
cv = ShuffleSplit(n_splits=10, test_size=0.2, random_state=0)
titles = ['Learning Curves (Under Fitting)',
'Learning Curves',
'Learning Curves (Over Fitting)']
degrees = [1, 3, 10]
plt.figure(figsize=(18, 4), dpi=200)
for i in range(len(degrees)):
plt.subplot(1, 3, i + 1)
plot_learning_curve(polynomial_model(degrees[i]), titles[i], X, y, ylim=(0.75, 1.01), cv=cv)
plt.show()
最终得出学习曲线如图3-3所示
左图:一阶多项式,欠拟合; 中图:三阶多项式,较好的拟合了数据集; 右图;十阶多项式,过拟合; 虚线:针对训练数据集计算出来的分数,即针对训练数据集拟合得准确性; 实线:针对交叉验证数据集计算出来的分数,即针对交叉验证数据集预测的准确性。
从左图我们可以观察到,当模型欠拟合(High Bias,Under Fitting)时,随着训练数据集的增加,交叉验证数据集的准确性(实线)逐渐增大,逐渐和训练数据集的准确性(虚线)靠近,但总体水平比较低,收敛在0.88左右。其训练数据集的准确性也比较低,收敛在0.99左右。这就是过拟合的表现。从这个关系可以看出来,当发生高偏差时,增加训练样本数量不会对算法准确性有较大的改善。
从右图我们可以观察得到,当模型过拟合(High Variance,Over Fitting)时,随着训练数据集的增加,交叉验证数据集的准确性(实线)也在增加,逐渐和训练数据集的准确性(虚线)靠近 ,但两者之间的间隔比较大。训练数据集的准确性很高,收敛在0.95左右,是三者中最高的,但其交叉验证数据集的准确性值却比较低,最终收敛在0.91左右。
中图,我们选择的三阶多项式较好地拟合了数据,最终训练数据集的准确性(虚线)和交叉验证数据集的准确性(实线)靠得很近,最终交叉验证数据集收敛在0.93附近,训练数据集的准确性收敛在0.94附近。
因而综上,3个模型对比,这个模型的准确性最好。
当需要改进学习算法时,可以画出学习曲线,以便判断算法是处在高偏差还是高方差问题。读者可根据代码修该参数,观察学习曲线的变化规则。学习曲线是诊断算法准确性得一个非常重要的一个工具。
3.4.2 过拟合和欠拟合得特征
总结: 过拟合:模型对训练数据集的准确性比较高,其成本Jtrain(θ)比较低,对交叉验证数据集的准确性比较低,其成本Jcv(θ)比较高。
欠拟合:模型对训练数据集的准确性比较低,其成本Jtrain(θ)比较高,对交叉验证数据集Jcv(θ)也比较高。
一个好的机器学习算法应该是对训练数据集准确性高,成本低,即较准确地拟合数据,同时对交叉验证数据集准确性高、成本低、误差小,即对未知数据有良好的预测性。
3.5算法模型性能优化
当我们辛苦开发出来的机器学习算法不能较好的预测新数据时,该怎么办呢?一般情况下,需要先判断这个算法模型是欠拟合还是过拟合。
过拟合:
获取更多的训练数据:从学习曲线的规律来看,更多的数据有助于改善过拟合问题。
减少输入的特征数量:比如,针对书写识别系统,原来使用200 x 200的图片,总共40000个特征。优化后,我们可以把图片等比例缩为10 x 10的图片,总共100个特征。这样可以大大减少模型的计算量,同时也减少模型的复杂度,改善过拟合问题。
欠拟合:(模型太简单,增加模型的复杂度)
增加有价值的特征:重新解读并理解训练数据。比如针对一个房产价格预测的学习任务,原来只根据房子面积来预测价格,结果模型出现了欠拟合。优化后,我们增加了其他的特征,如房子的朝向、户型、年代、房子旁边的学校质量(我们熟悉的学区房)、房子的开发商、房子周边商业街个数、房子周边公园个数等等。
增加多项式特征:有时候,从已知数据里挖掘出更多的特征不是件容易地事情,这个时候,可以用纯数学方法,增加多项式特征。比如,原来的输入特征只有x1,x2,优化后可以增加特征,增参,指数等都可以增加模型复杂度,从而改善欠拟合问题。回顾上一节的例子,当用一阶多项式拟合数据集时,使用的只有一个特征,而我们最终采用三阶多项式来拟合数据时,用的其实就是增加多项式特征这个方法。
3.6查准率和召回率 有时候,模型准确性并不能评价一个算法的好坏。比如针对癌症筛查算法,根据统计,普通肿瘤中中癌症的概率是0.5%。有个机器学习算法,测试得出的准确率是99.2%,错误率是0.8%。这个算法到底是好还是坏呢?如果,努力改进算法,最终得出的准确率是99.5%,错误率是0.5%,模型到底是变好了还是变坏了呢?
坦白讲,如果单纯从模型准确性得指标上很难判断到底是变好了还是变化了。因为这个事情的先验概率太低了,假如写了一个超级简单的预测函数,总是返回0,即总是不会得癌症,那么这个函数的准确率是99.5%,错误率只有0.5%。因为总体而言只有那0.5%真正得癌症的却被我们误判了。
那么怎么样来评价这类问题的模型的好坏呢?我们引入了另外两个概念,查准率(Precision)和召回率(Recall)。还是以癌症筛选为例:
预测数据/实际数据 实际恶性肿瘤 实际良性肿瘤
预测恶性肿瘤 TruePositive FalsePositive
预测良性肿瘤 FalseNegative TrueNegative
查准率和召回率的定义如下:
Precision=TruePosition/TruePosition+FalsePositivePrecision=TruePosition/TruePosition+FalsePositive
Recall=TruePositive/TruePositive+FalseNegativeRecall=TruePositive/TruePositive+FalseNegative
如何理解True / False 和 Positive / Negative?True/False表示预测结果是否正确,而Positive/Negative表示预测结果是1(恶性肿瘤)或0(良性肿瘤)。所以,TruePositive表示正确地预测出恶性肿瘤的数量;FalsePositive表示错误地预测恶性肿瘤的数量;FalseNegative表示错误地预测出良性肿瘤的数量。
在处理先验概率低的问题时,我们总是把概率较低的时间定义为1,并且把y=1作为Positive的预测结果。针对上文介绍的,对总是返回0的超级简单的肿瘤筛查预测函数,我们用查准率和召回率来检验模型性能时,会发现查准率和召回率都是0,这是因为它永远无法正确地预测出恶性肿瘤,即TruePositive永远为0.
在scikit-learn里,评估模型性能的算法都在sklearn.metrics包里。其中,计算查准率和召回率的API分别为sklearn.metrics.precision_score()
和sklearn.metrics.recall_score()。
3.7 F1 Score
由于现在有两个指标————查准率和召回率,如果有一个算法的查准率是0.5,召回率是0.4;另外一个算法查准率是0.02,召回率是1.0;那么两个算法到底哪一个好?
F1Score了解一下
图
其中P是查准率,R是召回率。这样就可以用一个数值直接判断哪个算法性能更好。典型地如果查准率或召回率有一个为0,那么F1Score将会为0。而理想情况下,查准率和召回率都为1,则算出来的F1Score为1.
在scikit-learn里,计算F1Score的函数是sklearn.metrics.f1_score()
。
复习 1、什么是过拟合?什么是欠拟合?怎么样去诊断算法是否过拟合或欠拟合? 2、模型的拟合成本是什么意思?它和模型的准确性有什么关系? 3、我们有哪些指标来评价一个模型的好坏? 4、为什么需要交叉验证数据集? 5、什么是学习曲线?为什么要画学习曲线? 6、尝试换成随机森林回归算法sklearn.ensemble.RandomForestRegressor来拟合曲线,并画出学习曲线。 7、为什么需要查准率和召回率来评估模型的好坏? 查准率的召回率适合哪些问题领域?