热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

机器学习(五)——模型泛化

引言众所周知,考试前会刷题。但是考试大部分又不是原题,那考前刷题有什么用?我们考前做的题目的当然不是为了赌考试有一模一样的题(有可能也是。。。),我们是为了从题目中学到一般的知识,




引言

众所周知,考试前会刷题。但是考试大部分又不是原题,那考前刷题有什么用?我们考前做的题目的当然不是为了赌考试有一模一样的题(有可能也是。。。),我们是为了从题目中学到一般的知识,这样我们在遇到新题目的时候也可以根据知识来做出题目。其实在机器学习中,考前刷的题就是训练集,考试中的题就是我们模型之后遇到的新样本,而泛化就是我们的模型遇到新样本的表现(也就对应着考试的分数)。




交叉验证

在我们训练完模型之后,我们肯定是想看模型的泛化是怎么样的。一般的做法就是把数据集分为训练集和测试集,我们用训练集训练模型,用测试集测试模型的泛化能力。我们还会根据测试集的准确率来调整模型。

但这样又遇到一个问题,那就是如果根据测试集来调整模型,那么模型很可能就会在测试集上过拟合,或者说这样做的话,就无法体现模型的泛化能力了。
所以更多的做法,是将数据集分为训练集、验证集、测试集。通过训练集训练模型,验证集调整模型,测试集测试模型的泛化能力。

但是验证集也有随机性,很可能因为这一份验证集而产生过拟合,所以又产生了交叉验证。
交叉验证
我们将训练数据随机分为k份,上图中分为k=3份,将任意两种组合作为训练集,剩下的一组作为验证集,这样就得到k个模型,然后在将k个模型的均值作为结果调参。显然这种方式要比随机只用一份数据集作为验证集要靠谱的多。
下面用实际的例子,来了解一下如何使用交叉验证调参:


  • 第一种情况:只使用训练集测试集测试:

import numpy as np
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
digits = datasets.load_digits()
x = digits.data
y = digits.target
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.4, random_state=666)
best_score, best_p, best_k = 0, 0, 0
for k in range(2, 11):
for p in range(1, 6):
knn_clf = KNeighborsClassifier(weights="distance", n_neighbors=k, p=p)
knn_clf.fit(x_train, y_train)
score = knn_clf.score(x_test, y_test)
if score > best_score:
best_score, best_p, best_k = score, p, k
print("Best K=", best_k)
print("Best P=", best_p)
print("Best score=", best_score)

输出结果

Best K= 3
Best P= 2
Best score= 0.9860917941585535

  • 第二种情况:使用交叉验证

from sklearn.model_selection import cross_val_score
knn_clf = KNeighborsClassifier()
cross_val_score(knn_clf, x_train, y_train)
# array([0.99537037, 0.98148148, 0.97685185, 0.97674419, 0.97209302])

根据输出结果,默认情况下 sklearn 包的交叉验证是分为 5 份,也可以通过 cv 参数修改。

best_score, best_p, best_k = 0, 0, 0
for k in range(2, 11):
for p in range(1, 6):
knn_clf = KNeighborsClassifier(weights="distance", n_neighbors=k, p=p)
scores = cross_val_score(knn_clf, x_train, y_train)
score = np.mean(scores)
if score > best_score:
best_score, best_p, best_k = score, p, k
print("Best K=", best_k)
print("Best P=", best_p)
print("Best score=", best_score)

输出结果

Best K= 2
Best P= 2
Best score= 0.9851507321274763

从输出结果可以看出,选择的参数和之前不一样了,虽然分数下降了点,但使用交叉验证的更可靠。当然这里不是模型的测试分数。

best_knn_clf = KNeighborsClassifier(weights='distance', n_neighbors=2, p=2)
best_knn_clf.fit(x_train, y_train)
best_knn_clf.score(x_test, y_test)

输出结果 0.980528511821975 才是测试分数。




偏差方差权衡

偏差方差权衡(Bias Variance Trade off),当我们的模型表现不佳时,通常是出现两种问题,一种是 高偏差 问题,另一种是
高方差 问题。识别它们有助于选择正确的优化方式,所以我们先来看下 偏差 与 方差 的意义。



  • 偏差: 描述模型输出结果的期望与样本真实结果的差距。
  • 方差: 描述模型对于给定值的输出稳定性。 方差越大模型的泛华能力越弱。


就像打靶一样,偏差描述了我们的射击总体是否偏离了我们的目标,而方差描述了射击准不准。左一是方差跟偏差都很小,都比较靠近中心且集中,右一分散在中心附近,但比较散,因此方差较大。这样结合下面这两幅图就可以大概理解,偏差描述的是描述模型输出结果的期望与样本真实结果的差距。而方差则是对于输出结果是否集中,描述模型对于给定值的输出稳定性。

模型误差 = 偏差 + 方差 + 不可避免的误差


  • 导致偏差大的原因:对问题本身的假设不正确!如非线性数据使用线性回归。或者特征对应标记高度不相关也会导致高偏差,不过这是对应着特征选择,跟算法没有关系,对于算法而言基本属于欠拟合问题underfitting
  • 导致方差大的原因:数据的一点点扰动都会极大地影响模型。通常原因就是使用的模型太复杂,如高阶多项式回归。这就是所说的过拟合(overfitting
  • 总结:有些算法天生就是高方差的算法,如KNN,非参数学习的算法通常都是高方差的,因为不对数据进行任何假设。还有一些算法天生就是高偏差的,如线性回归。参数学习通常都是高偏差算法,因为对数据具有极强的假设。大多数算法具有相应的算法可以调整偏差和方差,如KNN中的k,如线性回归中使用多项式回归中的degree,偏差和方差通常是矛盾的,降低偏差,会提高方差,降低方差,会提高偏差,因此在实际应用中需要进行权衡。机器学习的主要挑战,在于方差。这句话只针对算法,并不针对实际问题。因为大多数机器学习需要解决过拟合问题。
    解决手段
  • 降低模型复杂度
  • 减少数据维度;降噪
  • 增加样本数量
  • 使用验证集
  • 模型正则化



模型正则化

模型正则化(Regularization):限制参数的大小。常常用来解决过拟合问题。
先看一下多项式回归过拟合的情况:

from sklearn.linear_model import LinearRegression
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import mean_squared_error
import numpy as np
import matplotlib.pyplot as plt
def PolynomiaRegression(degree):
return Pipeline([
('poly', PolynomialFeatures(degree=degree)),
('std_scale', StandardScaler()),
('lin_reg', LinearRegression()),
])
np.random.seed(666)
x = np.random.uniform(-3.0, 3.0, size=100)
X = x.reshape(-1, 1)
y = 0.5 * x**2 + x + 2 + np.random.normal(0, 1, size=100)
poly100_reg = PolynomiaRegression(degree=100)
poly100_reg.fit(X, y)
y100_predict = poly100_reg.predict(X)
mean_squared_error(y, y100_predict)
# 0.687293250556113
x_plot = np.linspace(-3, 3, 100).reshape(100, 1)
y_plot = poly100_reg.predict(x_plot)
plt.scatter(x, y)
plt.plot(x_plot[:,0], y_plot, color='r')
plt.axis([-3, 3, 0, 10])
plt.show()

过拟合

lin_reg.coef_
array([ 1.21093453e+12, 1.19203091e+01, 1.78867645e+02, -2.95982349e+02,
-1.79531458e+04, -1.54155027e+04, 8.34383276e+05, 8.19774042e+05,
-2.23627851e+07, -1.44771550e+07, 3.87211418e+08, 1.13421075e+08,
-4.61600312e+09, -1.25081501e+08, 3.93150405e+10, -5.47576783e+09,
-2.44176251e+11, 5.46288687e+10, 1.11421043e+12, -2.76406464e+11,
-3.71329259e+12, 8.55454910e+11, 8.80960804e+12, -1.60748867e+12,
-1.39204160e+13, 1.49444708e+12, 1.19236879e+13, 2.47473079e+11,
4.42409192e+11, -1.64280931e+12, -1.05153597e+13, -1.80898849e+11,
3.00205050e+12, 2.75573418e+12, 8.74124346e+12, -1.36695399e+12,
-1.22671920e+12, -7.00432918e+11, -8.24895441e+12, -8.66296096e+11,
-2.75689092e+12, 1.39625207e+12, 6.26145077e+12, -3.47996080e+11,
6.29123725e+12, 1.33768276e+12, -6.11902468e+11, 2.92339251e+11,
-6.59758587e+12, -1.85663192e+12, -4.13408727e+12, -9.72012430e+11,
-3.99030817e+11, -7.53702123e+11, 5.49214630e+12, 2.18518119e+12,
5.24341931e+12, 7.50251523e+11, 5.50252585e+11, 1.70649474e+12,
-2.26789998e+12, -1.84570078e+11, -5.47302714e+12, -2.86219945e+12,
-3.88076411e+12, -1.19593780e+12, 1.16315909e+12, -1.41082803e+12,
3.56349186e+12, 7.12308678e+11, 4.76397106e+12, 2.60002465e+12,
1.84222952e+12, 3.06319895e+12, -1.33316498e+12, 6.18544545e+11,
-2.64567691e+12, -1.01424838e+12, -4.76743525e+12, -3.59230293e+12,
-1.68055178e+12, -3.57480827e+12, 2.06629318e+12, -6.07564696e+11,
3.40446395e+12, 3.42181387e+12, 3.31399498e+12, 4.92290870e+12,
3.79985951e+11, 1.34189037e+12, -3.34878352e+12, -2.07865615e+12,
-3.24634078e+12, -5.48903768e+12, 5.87242630e+11, -2.27318874e+12,
2.60023097e+12, 8.21820883e+12, 4.79532121e+10, -3.11436610e+12,
-6.27736909e+11])

通过查看多项式回归的系数可以发现,有些系数能差13个数量级,其实这就是过拟合了!而模型正则化就是为了解决这个问题。先来回顾一下多项式回归的目标。

通过加入的正则项来控制系数不要太大,从而使曲线不要那么陡峭,变化的那么剧烈。在这里有几个细节需要注意。


  • 第一点:θ从1开始,只包含系数不包括截距,这是因为截距只决定曲线的高低,并不会影响曲线的陡峭和缓和。
  • 第2点:就是这个 1/2,是为了求导之后能够将2消去,为了方便计算。不过其实这个有没有都是可以的,因为在正则化前有一个系数,我们可以把这个 1/2 可以考虑到 ɑ中去。
  • 第3点:系数ɑ,它表示正则化项在整个损失函数中所占的比例。极端一下,ɑ=0时,相当于模型没有加入正则化,但如果ɑ= 正无穷,此时其实主要的优化任务就变成了需要所有的ɑ都尽可能的小,最优的情况就是全为0。至于ɑ的取值就需要尝试了。

岭回归(Ridege Regression)


测试用例:

import numpy as np
import matplotlib.pyplot as plt
np.random.seed(42)
x = np.random.uniform(-3.0, 3.0, size=100)
X = x.reshape(-1, 1)
y = 0.5 * x + 3 + np.random.normal(0, 1, size=100)
plt.scatter(x, y)
plt.show()

from sklearn.linear_model import LinearRegression
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
def PolynomiaRegression(degree):
return Pipeline([
('poly', PolynomialFeatures(degree=degree)),
('std_scale', StandardScaler()),
('lin_reg', LinearRegression()),
])
np.random.seed(666)
x_train, x_test, y_train, y_test = train_test_split(X, y)
poly_reg = PolynomiaRegression(degree=20)
poly_reg.fit(x_train, y_train)
y_poly_predict = poly_reg.predict(x_test)
mean_squared_error(y_test, y_poly_predict)
# 167.9401085999025
import matplotlib.pyplot as plt
x_plot = np.linspace(-3, 3, 100).reshape(100, 1)
y_plot = poly_reg.predict(x_plot)
plt.scatter(x, y)
plt.plot(x_plot[:,0], y_plot, color='r')
plt.axis([-3, 3, 0, 6])
plt.show()


把画图这些操作封装成一个函数,方便后面调用:

def plot_model(model):
x_plot = np.linspace(-3, 3, 100).reshape(100, 1)
y_plot = model.predict(x_plot)
plt.scatter(x, y)
plt.plot(x_plot[:,0], y_plot, color='r')
plt.axis([-3, 3, 0, 6])
plt.show()

使用岭回归:

from sklearn.linear_model import Ridge
def RidgeRegression(degree, alpha):
return Pipeline([
('poly', PolynomialFeatures(degree=degree)),
('std_scale', StandardScaler()),
('lin_reg', Ridge(alpha=alpha)),
])
ridege1_reg = RidgeRegression(20, alpha=0.0001)
ridege1_reg.fit(x_train, y_train)
y1_predict = ridege1_reg.predict(x_test)
mean_squared_error(y_test, y1_predict)
# 1.3233492754136291
# 跟之前的136.相比小了很多
plot_model(ridege1_reg)


ridege2_reg = RidgeRegression(20, alpha=1)
ridege2_reg.fit(x_train, y_train)
y2_predict = ridege2_reg.predict(x_test)
mean_squared_error(y_test, y2_predict)
# 1.1888759304218461
plot_model(ridege2_reg)

ridege3_reg = RidgeRegression(20, alpha=100)
ridege3_reg.fit(x_train, y_train)
y3_predict = ridege3_reg.predict(x_test)
mean_squared_error(y_test, y3_predict)
# 1.3196456113086197
# 此时相比alpha=1时均方误差上升了,说明可能正则过头了
plot_model(ridege3_reg)

ridege4_reg = RidgeRegression(20, alpha=1000000)
ridege4_reg.fit(x_train, y_train)
y4_predict = ridege4_reg.predict(x_test)
mean_squared_error(y_test, y4_predict)
# 1.8404103153255003
plot_model(ridege4_reg)


​ 这也跟之前分析,如果 ɑ=正无穷 时,为了使损失函数最小,就需要所有的系数的平方和最小,即 θ 都趋于0。通过上面几种alpha的取值可以看出我们可以在1-100进行更加细致的搜索,找到最合适的一条相对比较平滑的曲线去拟合。这就是L2正则。


LASSO Regularization


LASSO: Least Absolute Shrinkage and Selection Operator Regression
Shrinkage:收缩,缩小,收缩量。特征缩减。重点在于Selection Operator


使用lasso回归:

from sklearn.linear_model import Lasso
def LassoRegression(degree, alpha):
return Pipeline([
('poly', PolynomialFeatures(degree=degree)),
('std_scale', StandardScaler()),
('lin_reg', Lasso(alpha=alpha)),
])
lasso1_reg = LassoRegression(20, 0.01)
#这里相比Ridge的alpha小了很多,这是因为在Ridge中是平方项
lasso1_reg.fit(x_train, y_train)
y1_predict = lasso1_reg.predict(x_test)
mean_squared_error(y_test, y1_predict)
# 1.149608084325997
plot_model(lasso1_reg)

lasso2_reg = LassoRegression(20, 0.1)
lasso2_reg.fit(x_train, y_train)
y2_predict = lasso2_reg.predict(x_test)
mean_squared_error(y_test, y2_predict)
# 1.1213911351818648
plot_model(lasso2_reg)

lasso3_reg = LassoRegression(20, 1)
lasso3_reg.fit(x_train, y_train)
y3_predict = lasso3_reg.predict(x_test)
mean_squared_error(y_test, y3_predict)
# 1.8408939659515595
plot_model(lasso3_reg)


解释Ridge和LASSO



通过这两幅图进行对比发现,LASSO拟合的模型更倾向于是一条直线,而Ridge拟合的模型更趋向与一条曲线。这是因为两个正则的本质不同,Ridge是趋向于使所有 θ 的加和尽可能的小,而 Lasso 则是趋向于使得一部分 θ 的值变为0,因此可作为特征选择用,这也是为什么叫 Selection Operation 的原因。
下面就对上面这两句话尝试着进行一下解释:


导数中的 θ 都是有值的,顺着梯度方向下降。Ridge是趋向于使所有 θ 的加和尽可能的小,而不是像lasso 一样直接为0。


所以,当如果从上图的一点开始进行梯度下降的话,就不能想 Ridge 一样曲线地去逼近 0,而是只能使用这些非常规则的方式去逼近零点。在这种路径的梯度下降中,就会达到某些轴的零点,Lasso 则是趋向于使得一部分 θ 的值变为 0。所以可以作为特征选择用。不过也正是因为这样的特性,使得 Lasso这种方法有可能会错误将原来有用的特征的系数变为 0,所以相对 Ridge 来说,准确率还是 Ridge 相对较好一些,但是当特征特别大时候,此时使用 Lasso 也能将模型的特征变少的作用。


弹性网


既然两者各有优势,就把他们结合起来,这就是弹性网(Elastic Net)。



推荐阅读
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • 本文介绍了机器学习手册中关于日期和时区操作的重要性以及其在实际应用中的作用。文章以一个故事为背景,描述了学童们面对老先生的教导时的反应,以及上官如在这个过程中的表现。同时,文章也提到了顾慎为对上官如的恨意以及他们之间的矛盾源于早年的结局。最后,文章强调了日期和时区操作在机器学习中的重要性,并指出了其在实际应用中的作用和意义。 ... [详细]
  • 超级简单加解密工具的方案和功能
    本文介绍了一个超级简单的加解密工具的方案和功能。该工具可以读取文件头,并根据特定长度进行加密,加密后将加密部分写入源文件。同时,该工具也支持解密操作。加密和解密过程是可逆的。本文还提到了一些相关的功能和使用方法,并给出了Python代码示例。 ... [详细]
  • tcpdump 4.5.1 crash 深入分析
    tcpdump 4.5.1 crash 深入分析 ... [详细]
  • 前言:拿到一个案例,去分析:它该是做分类还是做回归,哪部分该做分类,哪部分该做回归,哪部分该做优化,它们的目标值分别是什么。再挑影响因素,哪些和分类有关的影响因素,哪些和回归有关的 ... [详细]
  • 本文介绍了Python对Excel文件的读取方法,包括模块的安装和使用。通过安装xlrd、xlwt、xlutils、pyExcelerator等模块,可以实现对Excel文件的读取和处理。具体的读取方法包括打开excel文件、抓取所有sheet的名称、定位到指定的表单等。本文提供了两种定位表单的方式,并给出了相应的代码示例。 ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • 怀疑是每次都在新建文件,具体代码如下 ... [详细]
  • 本文介绍了Composer依赖管理的重要性及使用方法。对于现代语言而言,包管理器是标配,而Composer作为PHP的包管理器,解决了PEAR的问题,并且使用简单,方便提交自己的包。文章还提到了使用Composer能够避免各种include的问题,避免命名空间冲突,并且能够方便地安装升级扩展包。 ... [详细]
  • 开源Keras Faster RCNN模型介绍及代码结构解析
    本文介绍了开源Keras Faster RCNN模型的环境需求和代码结构,包括FasterRCNN源码解析、RPN与classifier定义、data_generators.py文件的功能以及损失计算。同时提供了该模型的开源地址和安装所需的库。 ... [详细]
  • Python使用Pillow包生成验证码图片的方法
    本文介绍了使用Python中的Pillow包生成验证码图片的方法。通过随机生成数字和符号,并添加干扰象素,生成一幅验证码图片。需要配置好Python环境,并安装Pillow库。代码实现包括导入Pillow包和随机模块,定义随机生成字母、数字和字体颜色的函数。 ... [详细]
  • java drools5_Java Drools5.1 规则流基础【示例】(中)
    五、规则文件及规则流EduInfoRule.drl:packagemyrules;importsample.Employ;ruleBachelorruleflow-group ... [详细]
  • 如何使用Python从工程图图像中提取底部的方法?
    本文介绍了使用Python从工程图图像中提取底部的方法。首先将输入图片转换为灰度图像,并进行高斯模糊和阈值处理。然后通过填充潜在的轮廓以及使用轮廓逼近和矩形核进行过滤,去除非矩形轮廓。最后通过查找轮廓并使用轮廓近似、宽高比和轮廓区域进行过滤,隔离所需的底部轮廓,并使用Numpy切片提取底部模板部分。 ... [详细]
  • 颜色迁移(reinhard VS welsh)
    不要谈什么天分,运气,你需要的是一个截稿日,以及一个不交稿就能打爆你狗头的人,然后你就会被自己的才华吓到。------ ... [详细]
  • Window10+anaconda+python3.5.4+ tensorflow1.5+ keras(GPU版本)安装教程 ... [详细]
author-avatar
木色雪魂K
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有