机器学习基础 目录
S1 回归模型
S1.1 线性回归
下面,我们使用sklearn的线性回归实例来演示 函数官方文档:
from sklearn import linear_model # 引入线性回归方法
lin_reg = linear_model.LinearRegression() # 创建线性回归的类
lin_reg.fit(X,y) # 输入特征X和因变量y进行训练
print("模型系数:",lin_reg.coef_) # 输出模型的系数
print("模型得分:",lin_reg.score(X,y)) # 输出模型的决定系数R^2
模型系数: [-1.08011358e-01 4.64204584e-02 2.05586264e-02 2.68673382e+00
-1.77666112e+01 3.80986521e+00 6.92224640e-04 -1.47556685e+00
3.06049479e-01 -1.23345939e-02 -9.52747232e-01 9.31168327e-03
-5.24758378e-01]
模型得分: 0.7406426641094095
- 线性回归的推广
在线性回归中,我们假设因变量与特征之间的关系是线性关系,这样的假设使得模型很简单,但是缺点也是显然的,那就是当数据存在非线性关系时,我们使用线性回归模型进行预测会导致预测性能极其低下,因为模型的形式本身是线性的,无法表达数据中的非线性关系。我们一个很自然的想法就是去推广线性回归模型,使得推广后的模型更能表达非线性的关系。
(a) 多项式回归:
为了体现因变量和特征的非线性关系,一个很自然而然的想法就是将标准的线性回归模型:
y i = w 0 + w 1 x i + ϵ i y_i = w_0 + w_1x_i + \epsilon_i yi=w0+w1xi+ϵi
换成一个多项式函数:
y i = w 0 + w 1 x i + w 2 x i 2 + . . . + w d x i d + ϵ y_i = w_0 + w_1x_i + w_2x_i^2 + ...+w_dx_i^d + \epsilon yi=w0+w1xi+w2xi2+...+wdxid+ϵ
对于多项式的阶数d不能取过大,一般不大于3或者4,因为d越大,多项式曲线就会越光滑,在X的边界处有异常的波动。(图中的边界处的4阶多项式拟合曲线的置信区间(虚线表示置信区间)明显增大,预测效果的稳定性下降。)
(b) 广义可加模型(GAM):
广义可加模型GAM实际上是线性模型推广至非线性模型的一个框架,在这个框架中,每一个变量都用一个非线性函数来代替,但是模型本身保持整体可加性。GAM模型不仅仅可以用在线性回归的推广,还可以将线性分类模型进行推广。具体的推广形式是:
标准的线性回归模型:
y
i
=
w
0
+
w
1
x
i
1
+
.
.
.
+
w
p
x
i
p
+
ϵ
i
y_i = w_0 + w_1x_{i1} +...+w_px_{ip} + \epsilon_i
yi=w0+w1xi1+...+wpxip+ϵi
GAM模型框架:
y
i
=
w
0
+
∑
j
=
1
p
f
j
(
x
i
j
)
+
ϵ
i
y_i = w_0 + \sum\limits_{j=1}^{p}f_{j}(x_{ij}) + \epsilon_i
yi=w0+j=1∑pfj(xij)+ϵi
GAM模型的优点与不足:
- 优点:简单容易操作,能够很自然地推广线性回归模型至非线性模型,使得模型的预测精度有所上升;由于模型本身是可加的,因此GAM还是能像线性回归模型一样把其他因素控制不变的情况下单独对某个变量进行推断,极大地保留了线性回归的易于推断的性质。
- 缺点:GAM模型会经常忽略一些有意义的交互作用,比如某两个特征共同影响因变量,不过GAM还是能像线性回归一样加入交互项
x
(
i
)
×
x
(
j
)
x^{(i)} \times x^{(j)}
x(i)×x(j)的形式进行建模;但是GAM模型本质上还是一个可加模型,如果我们能摆脱可加性模型形式,可能还会提升模型预测精度,详情请看后面的算法。
(a) 多项式回归实例介绍:
官方文档介绍
sklearn.preprocessing.PolynomialFeatures(degree=2, *, interaction_only=False, include_bias=True, order=‘C’):
- 参数:
degree:特征转换的阶数。
interaction_onlyboolean:是否只包含交互项,默认False 。
include_bias:是否包含截距项,默认True。
order:str in {‘C’, ‘F’}, default ‘C’,输出数组的顺序。
from sklearn.preprocessing import PolynomialFeatures
X_arr = np.arange(6).reshape(3, 2)
print("原始X为:\n",X_arr)
poly = PolynomialFeatures(2)
print("2次转化X:\n",poly.fit_transform(X_arr))
poly = PolynomialFeatures(interaction_only=True)
print("2次转化X:\n",poly.fit_transform(X_arr))
(b) GAM模型实例介绍:
安装pygam:pip install pygam
[pygam程序文档] (https://github.com/dswah/pyGAM/blob/master/doc/source/notebooks/quick_start.ipynb )
from pygam import LinearGAM
gam = LinearGAM().fit(boston_data[boston.feature_names], y)
gam.summary()
S1.2 非线性回归
- 回归树:
基于树的回归方法主要是依据分层和分割的方式将特征空间划分为一系列简单的区域。对某个给定的待预测的自变量,用他所属区域中训练集的平均数或者众数对其进行预测。由于划分特征空间的分裂规则可以用树的形式进行概括,因此这类方法称为决策树方法。决策树由结点(node)和有向边(diredcted edge)组成。结点有两种类型:内部结点(internal node)和叶结点(leaf node)。内部结点表示一个特征或属性,叶结点表示一个类别或者某个值。区域 R 1 , R 2 R_1,R_2 R1,R2等称为叶节点,将特征空间分开的点为内部节点。
建立回归树的过程大致可以分为以下两步:- 将自变量的特征空间(即 x ( 1 ) , x ( 2 ) , x ( 3 ) , . . . , x ( p ) x^{(1)},x^{(2)},x^{(3)},...,x^{(p)} x(1),x(2),x(3),...,x(p))的可能取值构成的集合分割成J个互不重叠的区域 R 1 , R 2 , . . . , R j R_1,R_2,...,R_j R1,R2,...,Rj。
- 对落入区域
R
j
R_j
Rj的每个观测值作相同的预测,预测值等于
R
j
R_j
Rj上训练集的因变量的简单算术平均。
具体来说,就是:
a. 选择最优切分特征j以及该特征上的最优点s:
遍历特征j以及固定j后遍历切分点s,选择使得下式最小的(j,s) m i n j , s [ m i n c 1 ∑ x i ∈ R 1 ( j , s ) ( y i − c 1 ) 2 + m i n c 2 ∑ x i ∈ R 2 ( j , s ) ( y i − c 2 ) 2 ] min_{j,s}[min_{c_1}\sum\limits_{x_i\in R_1(j,s)}(y_i-c_1)^2 + min_{c_2}\sum\limits_{x_i\in R_2(j,s)}(y_i-c_2)^2 ] minj,s[minc1xi∈R1(j,s)∑(yi−c1)2+minc2xi∈R2(j,s)∑(yi−c2)2]
b. 按照(j,s)分裂特征空间: R 1 ( j , s ) = { x ∣ x j ≤ s } 和 R 2 ( j , s ) = { x ∣ x j > s } , c ^ m = 1 N m ∑ x ∈ R m ( j , s ) y i , m = 1 , 2 R_1(j,s) = \{x|x^{j} \le s \}和R_2(j,s) = \{x|x^{j} > s \},\hat{c}_m = \frac{1}{N_m}\sum\limits_{x \in R_m(j,s)}y_i,\;m=1,2 R1(j,s)={x∣xj≤s}和R2(j,s)={x∣xj>s},c^m=Nm1x∈Rm(j,s)∑yi,m=1,2
c. 继续调用步骤1,2直到满足停止条件,就是每个区域的样本数小于等于5。
d. 将特征空间划分为J个不同的区域,生成回归树: f ( x ) = ∑ m = 1 J c ^ m I ( x ∈ R m ) f(x) = \sum\limits_{m=1}^{J}\hat{c}_mI(x \in R_m) f(x)=m=1∑Jc^mI(x∈Rm)
如以下生成的关于运动员在棒球大联盟数据的回归树:
回归树与线性模型的比较:
线性模型的模型形式与树模型的模型形式有着本质的区别,具体而言,线性回归对模型形式做了如下假定:$f(x) = w_0 + \sum\limits_{j=1}^{p}w_jx^{(j)}$,而回归树则是$f(x) = \sum\limits_{m=1}^{J}\hat{c}_mI(x \in R_m)$。那问题来了,哪种模型更优呢?这个要视具体情况而言,如果特征变量与因变量的关系能很好的用线性关系来表达,那么线性回归通常有着不错的预测效果,拟合效果则优于不能揭示线性结构的回归树。反之,如果特征变量与因变量的关系呈现高度复杂的非线性,那么树方法比传统方法更优。
树模型的优缺点:
- 树模型的解释性强,在解释性方面可能比线性回归还要方便。
- 树模型更接近人的决策方式。
- 树模型可以用图来表示,非专业人士也可以轻松解读。
- 树模型可以直接做定性的特征而不需要像线性回归一样哑元化。
- 树模型能很好处理缺失值和异常值,对异常值不敏感,但是这个对线性模型来说却是致命的。
- 树模型的预测准确性一般无法达到其他回归模型的水平,但是改进的方法很多。
sklearn使用回归树的实例:
回归树官方文档
sklearn.tree.DecisionTreeRegressor(*, criterion=‘mse’, splitter=‘best’, max_depth=None, min_samples_split=2, min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_features=None, random_state=None, max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, presort=‘deprecated’, ccp_alpha=0.0)
- 参数:(列举几个重要的,常用的,详情请看上面的官网)
criterion:{“ mse”,“ friedman_mse”,“ mae”},默认=“ mse”。衡量分割标准的函数 。
splitter:{“best”, “random”}, default=”best”。分割方式。
max_depth:树的最大深度。
min_samples_split:拆分内部节点所需的最少样本数,默认是2。
min_samples_leaf:在叶节点处需要的最小样本数。默认是1。
min_weight_fraction_leaf:在所有叶节点处(所有输入样本)的权重总和中的最小加权分数。如果未提供sample_weight,则样本的权重相等。默认是0。
from sklearn.tree import DecisionTreeRegressor
reg_tree = DecisionTreeRegressor(criterion = "mse",min_samples_leaf = 5)
reg_tree.fit(X,y)
reg_tree.score(X,y)
S1.3 SVM 与 SVR
SVM模型:
支持向量回归SVR
在线性回归的理论中,每个样本点都要计算平方损失,但是SVR却是不一样的。SVR认为:落在
f
(
x
)
f(x)
f(x)的
ϵ
\epsilon
ϵ邻域空间中的样本点不需要计算损失,这些都是预测正确的,其余的落在
ϵ
\epsilon
ϵ邻域空间以外的样本才需要计算损失,因此:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oK0xpb8X-1616068270490)(./1.12.png)]
m
i
n
w
,
b
,
ξ
i
,
ξ
^
i
1
2
∣
∣
w
∣
∣
2
+
C
∑
i
=
1
N
(
ξ
i
,
ξ
^
i
)
s
.
t
.
f
(
x
i
)
−
y
i
≤
ϵ
+
ξ
i
y
i
−
f
(
x
i
)
≤
ϵ
+
ξ
^
i
ξ
i
,
ξ
^
i
≤
0
,
i
=
1
,
2
,
.
.
.
,
N
min_{w,b,\xi_i,\hat{\xi}_i} \frac{1}{2}||w||^2 +C \sum\limits_{i=1}^{N}(\xi_i,\hat{\xi}_i)\\ s.t.\;\;\; f(x_i) - y_i \le \epsilon + \xi_i\\ \;\;\;\;\;y_i - f(x_i) \le \epsilon +\hat{\xi}_i\\ \;\;\;\;\; \xi_i,\hat{\xi}_i \le 0,i = 1,2,...,N
minw,b,ξi,ξ^i21∣∣w∣∣2+Ci=1∑N(ξi,ξ^i)s.t.f(xi)−yi≤ϵ+ξiyi−f(xi)≤ϵ+ξ^iξi,ξ^i≤0,i=1,2,...,N
引入拉格朗日函数:
L
(
w
,
b
,
α
,
α
^
,
ξ
,
ξ
,
μ
,
μ
^
)
=
1
2
∥
w
∥
2
+
C
∑
i
=
1
N
(
ξ
i
+
ξ
^
i
)
−
∑
i
=
1
N
ξ
i
μ
i
−
∑
i
=
1
N
ξ
^
i
μ
^
i
+
∑
i
=
1
N
α
i
(
f
(
x
i
)
−
y
i
−
ϵ
−
ξ
i
)
+
∑
i
=
1
N
α
^
i
(
y
i
−
f
(
x
i
)
−
ϵ
−
ξ
^
i
)
\begin{array}{l} L(w, b, \alpha, \hat{\alpha}, \xi, \xi, \mu, \hat{\mu}) \\ \quad=\frac{1}{2}\|w\|^{2}+C \sum_{i=1}^{N}\left(\xi_{i}+\widehat{\xi}_{i}\right)-\sum_{i=1}^{N} \xi_{i} \mu_{i}-\sum_{i=1}^{N} \widehat{\xi}_{i} \widehat{\mu}_{i} \\ \quad+\sum_{i=1}^{N} \alpha_{i}\left(f\left(x_{i}\right)-y_{i}-\epsilon-\xi_{i}\right)+\sum_{i=1}^{N} \widehat{\alpha}_{i}\left(y_{i}-f\left(x_{i}\right)-\epsilon-\widehat{\xi}_{i}\right) \end{array}
L(w,b,α,α^,ξ,ξ,μ,μ^)=21∥w∥2+C∑i=1N(ξi+ξ
i)−∑i=1Nξiμi−∑i=1Nξ
iμ
i+∑i=1Nαi(f(xi)−yi−ϵ−ξi)+∑i=1Nα
i(yi−f(xi)−ϵ−ξ
i)
再令
L
(
w
,
b
,
α
,
α
^
,
ξ
,
ξ
,
μ
,
μ
^
)
L(w, b, \alpha, \hat{\alpha}, \xi, \xi, \mu, \hat{\mu})
L(w,b,α,α^,ξ,ξ,μ,μ^)对
w
,
b
,
ξ
,
ξ
^
w,b,\xi,\hat{\xi}
w,b,ξ,ξ^求偏导等于0,得:
w
=
∑
i
=
1
N
(
α
^
i
−
α
i
)
x
i
w=\sum_{i=1}^{N}\left(\widehat{\alpha}_{i}-\alpha_{i}\right) x_{i}
w=∑i=1N(α
i−αi)xi。
上述过程中需满足KKT条件,即要求:
{
α
i
(
f
(
x
i
)
−
y
i
−
ϵ
−
ξ
i
)
=
0
α
i
^
(
y
i
−
f
(
x
i
)
−
ϵ
−
ξ
^
i
)
=
0
α
i
α
^
i
=
0
,
ξ
i
ξ
^
i
=
0
(
C
−
α
i
)
ξ
i
=
0
,
(
C
−
α
^
i
)
ξ
^
i
=
0
\left\{\begin{array}{c} \alpha_{i}\left(f\left(x_{i}\right)-y_{i}-\epsilon-\xi_{i}\right)=0 \\ \hat{\alpha_{i}}\left(y_{i}-f\left(x_{i}\right)-\epsilon-\hat{\xi}_{i}\right)=0 \\ \alpha_{i} \widehat{\alpha}_{i}=0, \xi_{i} \hat{\xi}_{i}=0 \\ \left(C-\alpha_{i}\right) \xi_{i}=0,\left(C-\widehat{\alpha}_{i}\right) \hat{\xi}_{i}=0 \end{array}\right.
⎩⎪⎪⎪⎨⎪⎪⎪⎧αi(f(xi)−yi−ϵ−ξi)=0αi^(yi−f(xi)−ϵ−ξ^i)=0αiα
i=0,ξiξ^i=0(C−αi)ξi=0,(C−α
i)ξ^i=0
SVR的解形如:
f
(
x
)
=
∑
i
=
1
N
(
α
^
i
−
α
i
)
x
i
T
x
+
b
f(x)=\sum_{i=1}^{N}\left(\widehat{\alpha}_{i}-\alpha_{i}\right) x_{i}^{T} x+b
f(x)=∑i=1N(α
i−αi)xiTx+b
sklearn中使用SVR实例:
sklearn.svm.SVR(*, kernel=‘rbf’, degree=3, gamma=‘scale’, coef0=0.0, tol=0.001, C=1.0, epsilon=0.1, shrinking=True, cache_size=200, verbose=False, max_iter=-1)
SVR说明文档
- 参数:
kernel:核函数,{‘linear’, ‘poly’, ‘rbf’, ‘sigmoid’, ‘precomputed’}, 默认=’rbf’。(后面会详细介绍)
degree:多项式核函数的阶数。默认 = 3。
C:正则化参数,默认=1.0。(后面会详细介绍)
epsilon:SVR模型允许的不计算误差的邻域大小。默认0.1。
from sklearn.svm import SVR
from sklearn.preprocessing import StandardScaler # 标准化数据
from sklearn.pipeline import make_pipeline # 使用管道,把预处理和模型形成一个流程
reg_svr = make_pipeline(StandardScaler(), SVR(C=1.0, epsilon=0.2))
reg_svr.fit(X, y)
reg_svr.score(X,y)
S2 模型调优
S2.1 优化基础
在使用训练集时,能通过改变参数w,使得目标函数降到最小。我们建立机器学习的目的并不是为了在已有的数据集,我们希望建立的机器学习模型在未知且情况复杂的测试数据上表现优异,我们称这样的未出现在训练集的未知数据集成为测试数据集,简称测试集。
在回归中,我们最常用的评价指标为均方误差,即:
M
S
E
=
1
N
∑
i
=
1
N
(
y
i
−
f
^
(
x
i
)
)
2
MSE = \frac{1}{N}\sum\limits_{i=1}^{N}(y_i -\hat{ f}(x_i))^2
MSE=N1i=1∑N(yi−f^(xi))2,其中
f
^
(
x
i
)
\hat{ f}(x_i)
f^(xi)是样本
x
i
x_i
xi应用建立的模型
f
^
\hat{f}
f^预测的结果。如果我们所用的数据是训练集上的数据,那么这个误差为训练均方误差,如果我们使用测试集的数据计算的均方误差,我们称为测试均方误差。一般而言,我们并不关心模型在训练集上的训练均方误差,我们关心的是模型面对未知的样本集,即测试集上的测试误差,我们的目标是使得我们建立的模型在测试集上的测试误差最小。那我们如何选择一个测试误差最小的模型呢?这是个棘手的问题,因为在模型建立阶段,我们是不能得到测试数据的,比如:我们在模型未上线之前是不能拿到未知且真实的测试数据来验证我们的模型的。在这种情况下,为了简便起见,一些观点认为通过训练误差最小化来选择模型也是可行的。这种观点表面看上去是可行的,但是存在一个致命的缺点,那就是:一个模型的训练均方误差最小时,不能保证测试均方误差同时也很小。对于这种想法构造的模型,一般在训练误差达到最小时,测试均方误差一般很大!如图:
可以看到:当我们的模型的训练均方误差达到很小时,测试均方误差反而很大,但是我们寻找的最优的模型是测试均方误差达到最小时对应的模型,因此基于训练均方误差达到最小选择模型本质上是行不同的。正如上右图所示:模型在训练误差很小,但是测试均方误差很大时,我们称这种情况叫模型的过拟合。
S2.2 训练均方误差与测试均方误差
从上图的测试均方误差曲线可以看到:测试均方误差曲线呈现U型曲线,这表明了在测试误差曲线中有两种力量在互相博弈。可以证明:
E
(
y
0
−
f
^
(
x
0
)
)
2
=
Var
(
f
^
(
x
0
)
)
+
[
Bias
(
f
^
(
x
0
)
)
]
2
+
Var
(
ε
)
E\left(y_{0}-\hat{f}\left(x_{0}\right)\right)^{2}=\operatorname{Var}\left(\hat{f}\left(x_{0}\right)\right)+\left[\operatorname{Bias}\left(\hat{f}\left(x_{0}\right)\right)\right]^{2}+\operatorname{Var}(\varepsilon)
E(y0−f^(x0))2=Var(f^(x0))+[Bias(f^(x0))]2+Var(ε)
也就是说,我们的测试均方误差的期望值可以分解为
f
^
(
x
0
)
\hat{f}(x_0)
f^(x0)的方差、
f
^
(
x
0
)
\hat{f}(x_0)
f^(x0)的偏差平方和误差项
ϵ
\epsilon
ϵ的方差。为了使得模型的测试均方误差达到最小值,也就是同时最小化偏差的平方和方差。由于我们知道偏差平方和方差本身是非负的,因此测试均方误差的期望不可能会低于误差的方差,因此我们称
Var
(
ε
)
\operatorname{Var}(\varepsilon)
Var(ε)为建模任务的难度,这个量在我们的任务确定后是无法改变的,也叫做不可约误差。那么模型的方差和偏差的平方和究竟是什么呢?所谓模型的方差就是:用不同的数据集去估计
f
f
f时,估计函数的改变量。举个例子:我们想要建立一个线性回归模型,可以通过输入中国人身高去预测我们的体重。但是显然我们没有办法把全中国13亿人做一次人口普查,拿到13亿人的身高体重去建立模型。我们能做的就是从13亿中抽1000个样本进行建模,我们对这个抽样的过程重复100遍,就会得到100个1000人的样本集。我们使用线性回归模型估计参数就能得到100个线性回归模型。由于样本抽取具有随机性,我们得到的100个模型不可能参数完全一样,那么这100个模型之间的差异就叫做方差。显然,我们希望得到一个稳定的模型,也就是在不同的样本集估计的模型都不会相差太大,即要求f的方差越小越好。一般来说,模型的复杂度越高,f的方差就会越大。 如加入二次项的模型的方差比线性回归模型的方差要大。
另一方面,模型的偏差是指:为了选择一个简单的模型去估计真实函数所带入的误差。假如真实的数据X与Y的关系是二次关系,但是我们选择了线性模型进行建模,那由于模型的复杂度引起的这种误差我们称为偏差,它的构成时复杂的。偏差度量了学习算法的期望预测与真实结果的偏离程度,即刻画了学习算法本身的拟合能力。偏差度量的是单个模型的学习能力,而方差度量的是同一个模型在不同数据集上的稳定性。“偏差-方差分解”说明:泛化性能是由学习算法的能力、数据的充分性以及学习任务本身的难度所共同决定的。给定学习任务,为了取得好的泛化性能,则需使偏差较小,即能够充分拟合数据,并且使方差较小,即使得数据扰动产生的影响小。
一般而言,增加模型的复杂度,会增加模型的方差,但是会减少模型的偏差,我们要找到一个方差–偏差的权衡,使得测试均方误差最。
S2.3 偏差-方差的权衡:
在前面的讨论中,我们已经明确一个目标,就是:我们要选择一个测试误差达到最小的模型。但是实际上我们很难对实际的测试误差做精确的计算,因此我们要对测试误差进行估计,估计的方式有两种:训练误差修正与交叉验证。
- 训练误差修正:
前面的讨论我们已经知道,模型越复杂,训练误差越小,测试误差先减后增。因此,我们先构造一个特征较多的模型使其过拟合,此时训练误差很小而测试误差很大,那这时我们加入关于特征个数的惩罚。因此,当我们的训练误差随着特征个数的增加而减少时,惩罚项因为特征数量的增加而增大,抑制了训练误差随着特征个数的增加而无休止地减小。具体的数学量如下:
C
p
=
1
N
(
R
S
S
+
2
d
σ
^
2
)
C_p = \frac{1}{N}(RSS + 2d\hat{\sigma}^2)
Cp=N1(RSS+2dσ^2),其中d为模型特征个数,
R
S
S
=
∑
i
=
1
N
(
y
i
−
f
^
(
x
i
)
)
2
RSS = \sum\limits_{i=1}^{N}(y_i-\hat{f}(x_i))^2
RSS=i=1∑N(yi−f^(xi))2,
σ
^
2
\hat{\sigma}^2
σ^2为模型预测误差的方差的估计值,即残差的方差。
AIC赤池信息量准则:
A
I
C
=
1
d
σ
^
2
(
R
S
S
+
2
d
σ
^
2
)
AIC = \frac{1}{d\hat{\sigma}^2}(RSS + 2d\hat{\sigma}^2)
AIC=dσ^21(RSS+2dσ^2)
BIC贝叶斯信息量准则:
B
I
C
=
1
n
(
R
S
S
+
l
o
g
(
n
)
d
σ
^
2
)
BIC = \frac{1}{n}(RSS + log(n)d\hat{\sigma}^2)
BIC=n1(RSS+log(n)dσ^2)
- 交叉验证:
前面讨论的对训练误差修正得到测试误差的估计是间接方法,这种方法的桥梁是训练误差,而交叉验证则是对测试误差的直接估计。交叉验证比训练误差修正的优势在于:能够给出测试误差的一个直接估计。在这里只介绍K折交叉验证:我们把训练样本分成K等分,然后用K-1个样本集当做训练集,剩下的一份样本集为验证集去估计由K-1个样本集得到的模型的精度,这个过程重复K次取平均值得到测试误差的一个估计
C
V
(
K
)
=
1
K
∑
i
=
1
K
M
S
E
i
CV_{(K)} = \frac{1}{K}\sum\limits_{i=1}^{K}MSE_i
CV(K)=K1i=1∑KMSEi。5折交叉验证如下图:(蓝色的是训练集,黄色的是验证集)
S2.4 特征提取
在测试误差能够被合理的估计出来以后,我们做特征选择的目标就是:从p个特征中选择m个特征,使得对应的模型的测试误差的估计最小。对应的方法有:
- 最优子集选择:
(i) 记不含任何特征的模型为
M
0
M_0
M0,计算这个
M
0
M_0
M0的测试误差。
(ii) 在
M
0
M_0
M0基础上增加一个变量,计算p个模型的RSS,选择RSS最小的模型记作
M
1
M_1
M1,并计算该模型
M
1
M_1
M1的测试误差。
(iii) 再增加变量,计算p-1个模型的RSS,并选择RSS最小的模型记作
M
2
M_2
M2,并计算该模型
M
2
M_2
M2的测试误差。
(iv) 重复以上过程知道拟合的模型有p个特征为止,并选择p+1个模型
{
M
0
,
M
1
,
.
.
.
,
M
p
}
\{M_0,M_1,...,M_p \}
{M0,M1,...,Mp}中测试误差最小的模型作为最优模型。
- 向前逐步选择:
最优子集选择虽然在原理上很直观,但是随着数据特征维度p的增加,子集的数量为
2
p
2^p
2p,计算效率非常低下且需要的计算内存也很高,在大数据的背景下显然不适用。因此,我们需要把最优子集选择的运算效率提高,因此向前逐步选择算法的过程如下:
(i) 记不含任何特征的模型为
M
0
M_0
M0,计算这个
M
0
M_0
M0的测试误差。
(ii) 在
M
0
M_0
M0基础上增加一个变量,计算p个模型的RSS,选择RSS最小的模型记作
M
1
M_1
M1,并计算该模型
M
1
M_1
M1的测试误差。
(iii) 在最小的RSS模型下继续增加一个变量,选择RSS最小的模型记作
M
2
M_2
M2,并计算该模型
M
2
M_2
M2的测试误差。
(iv) 以此类推,重复以上过程知道拟合的模型有p个特征为止,并选择p+1个模型
{
M
0
,
M
1
,
.
.
.
,
M
p
}
\{M_0,M_1,...,M_p \}
{M0,M1,...,Mp}中测试误差最小的模型作为最优模型。
向前逐步选择例程:
import random
import numpy as np
import pandas as pd
from sklearn.datasets import fetch_california_housing as fch #加载加利福尼亚房屋价值数据
#加载线性回归需要的模块和库
import statsmodels.api as sm #最小二乘
from statsmodels.formula.api import ols #加载ols模型
data=fch() #导入数据
house_data=pd.DataFrame(data.data) #将自变量转换成dataframe格式,便于查看
house_data.columns=data.feature_names #命名自变量
house_data.loc[:,"value"]=data.target #合并自变量,因变量数据
house_data.shape #查看数据量
house_data.head(10) #查看前10行数据
#分训练集测试集
random.seed(123) #设立随机数种子
a=random.sample(range(len(house_data)),round(len(house_data)*0.3))
house_test=[]
for i in a:
house_test.append(house_data.iloc[i])
house_test=pd.DataFrame(house_test)
house_train=house_data.drop(a)
#定义向前逐步回归函数
def forward_select(data,target):
variate=set(data.columns) #将字段名转换成字典类型
variate.remove(target) #去掉因变量的字段名
selected=[]
current_score,best_new_score=float('inf'),float('inf') #目前的分数和最好分数初始值都为无穷大(因为AIC越小越好)
#循环筛选变量
while variate:
aic_with_variate=[]
for candidate in variate: #逐个遍历自变量
formula="{}~{}".format(target,"+".join(selected+[candidate])) #将自变量名连接起来
aic=ols(formula=formula,data=data).fit().aic #利用ols训练模型得出aic值
aic_with_variate.append((aic,candidate)) #将第每一次的aic值放进空列表
aic_with_variate.sort(reverse=True) #降序排序aic值
best_new_score,best_candidate=aic_with_variate.pop() #最好的aic值等于删除列表的最后一个值,以及最好的自变量等于列表最后一个自变量
if current_score>best_new_score: #如果目前的aic值大于最好的aic值
variate.remove(best_candidate) #移除加进来的变量名,即第二次循环时,不考虑此自变量了
selected.append(best_candidate) #将此自变量作为加进模型中的自变量
current_score=best_new_score #最新的分数等于最好的分数
print("aic is {},continuing!".format(current_score)) #输出最小的aic值
else:
print("for selection over!")
break
formula="{}~{}".format(target,"+".join(selected)) #最终的模型式子
print("final formula is {}".format(formula))
model=ols(formula=formula,data=data).fit()
return(model)
forward_select(data=house_train,target="value")
lm=ols("Price~LSTAT+RM+PTRATIO+DIS+NOX+CHAS+B+ZN+CRIM+RAD+TAX",data=boston_data).fit()
lm.summary()
S2.5 压缩估计(正则化):
除了刚刚讨论的直接对特征自身进行选择以外,我们还可以对回归的系数进行约束或者加罚的技巧对p个特征的模型进行拟合,显著降低模型方差,这样也会提高模型的拟合效果。具体来说,就是将回归系数往零的方向压缩,这也就是为什么叫压缩估计的原因了。
- 岭回归(L2正则化的例子):
在线性回归中,我们的损失函数为
J
(
w
)
=
∑
i
=
1
N
(
y
i
−
w
0
−
∑
j
=
1
p
w
j
x
i
j
)
2
J(w) = \sum\limits_{i=1}^{N}(y_i-w_0-\sum\limits_{j=1}^{p}w_jx_{ij})^2
J(w)=i=1∑N(yi−w0−j=1∑pwjxij)2,我们在线性回归的损失函数的基础上添加对系数的约束或者惩罚,即:
J
(
w
)
=
∑
i
=
1
N
(
y
i
−
w
0
−
∑
j
=
1
p
w
j
x
i
j
)
2
+
λ
∑
j
=
1
p
w
j
2
,
其
中
,
λ
≥
0
w
^
=
(
X
T
X
+
λ
I
)
−
1
X
T
Y
J(w) = \sum\limits_{i=1}^{N}(y_i-w_0-\sum\limits_{j=1}^{p}w_jx_{ij})^2 + \lambda\sum\limits_{j=1}^{p}w_j^2,\;\;其中,\lambda \ge 0\\ \hat{w} = (X^TX + \lambda I)^{-1}X^TY
J(w)=i=1∑N(yi−w0−j=1∑pwjxij)2+λj=1∑pwj2,其中,λ≥0w^=(XTX+λI)−1XTY
调节参数
λ
\lambda
λ的大小是影响压缩估计的关键,
λ
\lambda
λ越大,惩罚的力度越大,系数则越趋近于0,反之,选择合适的
λ
\lambda
λ对模型精度来说十分重要。岭回归通过牺牲线性回归的无偏性降低方差,有可能使得模型整体的测试误差较小,提高模型的泛化能力。
- Lasso回归(L1正则化的例子):
岭回归的一个很显著的特点是:将模型的系数往零的方向压缩,但是岭回归的系数只能呢个趋于0但无法等于0,换句话说,就是无法做特征选择。能否使用压缩估计的思想做到像特征最优子集选择那样提取出重要的特征呢?答案是肯定的!我们只需要对岭回归的优化函数做小小的调整就行了,我们使用系数向量的L1范数替换岭回归中的L2范数:
J
(
w
)
=
∑
i
=
1
N
(
y
i
−
w
0
−
∑
j
=
1
p
w
j
x
i
j
)
2
+
λ
∑
j
=
1
p
∣
w
j
∣
,
其
中
,
λ
≥
0
J(w) = \sum\limits_{i=1}^{N}(y_i-w_0-\sum\limits_{j=1}^{p}w_jx_{ij})^2 + \lambda\sum\limits_{j=1}^{p}|w_j|,\;\;其中,\lambda \ge 0
J(w)=i=1∑N(yi−w0−j=1∑pwjxij)2+λj=1∑p∣wj∣,其中,λ≥0
为什么Losso能做到特征选择而岭回归却不能呢个做到呢?(如图:左边为lasso,右边为岭回归)
椭圆形曲线为RSS等高线,菱形和圆形区域分别代表了L1和L2约束,Lsaao回归和岭回归都是在约束下的回归,因此最优的参数为椭圆形曲线与菱形和圆形区域相切的点。但是Lasso回归的约束在每个坐标轴上都有拐角,因此当RSS曲线与坐标轴相交时恰好回归系数中的某一个为0,这样就实现了特征提取。反观岭回归的约束是一个圆域,没有尖点,因此与RSS曲线相交的地方一般不会出现在坐标轴上,因此无法让某个特征的系
S2.6 降维
到目前为止,我们所讨论的方法对方差的控制有两种方式:一种是使用原始变量的子集,另一种是将变量系数压缩至零。但是这些方法都是基于原始特征
x
1
,
.
.
.
,
x
p
x_1,...,x_p
x1,...,xp得到的,现在我们探讨一类新的方法:将原始的特征空间投影到一个低维的空间实现变量的数量变少,如:将二维的平面投影至一维空间。机器学习领域中所谓的降维就是指采用某种映射方法,将原高维空间中的数据点映射到低维度的空间中。降维的本质是学习一个映射函数 f : x->y,其中x是原始数据点的表达,目前最多使用向量表达形式。 y是数据点映射后的低维向量表达,通常y的维度小于x的维度(当然提高维度也是可以的)。f可能是显式的或隐式的、线性的或非线性的。目前大部分降维算法处理向量表达的数据,也有一些降维算法处理高阶张量表达的数据。之所以使用降维后的数据表示是因为在原始的高维空间中,包含有冗余信息以及噪音信息,在实际应用例如图像识别中造成了误差,降低了准确率;而通过降维,我们希望减少 冗余信息 所造成的误差,提高识别(或其他应用)的精度。又或者希望通过降维算法来寻找数据内部的本质结构特征。在很多算法中,降维算法成为了数据预处理的一部分,如PCA。事实上,有一些算法如果没有降维预处理,其实是很难得到很好的效果的。 (摘自:rosenor1博客)
主成分分析(PCA):
主成分分析的思想:通过最大投影方差 将原始空间进行重构,即由特征相关重构为无关,即落在某个方向上的点(投影)的方差最大。在进行下一步推导之前,我们先把样本均值和样本协方差矩阵推广至矩阵形式:
样本均值Mean:
x
ˉ
=
1
N
∑
i
=
1
N
x
i
=
1
N
X
T
1
N
,
其
中
1
N
=
(
1
,
1
,
.
.
.
,
1
)
N
T
\bar{x} = \frac{1}{N}\sum\limits_{i=1}^{N}x_i = \frac{1}{N}X^T1_N,\;\;\;其中1_N = (1,1,...,1)_{N}^T
xˉ=N1i=1∑Nxi=N1XT1N,其中1N=(1,1,...,1)NT
样本协方差矩阵
S
2
=
1
N
∑
i
=
1
N
(
x
i
−
x
ˉ
)
(
x
i
−
x
ˉ
)
T
=
1
N
X
T
H
X
,
其
中
,
H
=
I
N
−
1
N
1
N
1
N
T
S^2 = \frac{1}{N}\sum\limits_{i=1}^{N}(x_i-\bar{x})(x_i-\bar{x})^T = \frac{1}{N}X^THX,\;\;\;其中,H = I_N - \frac{1}{N}1_N1_N^T
S2=N1i=1∑N(xi−xˉ)(xi−xˉ)T=N1XTHX,其中,H=IN−N11N1NT
最大投影方差的步骤:
(i) 中心化:
x
i
−
x
ˉ
x_i - \bar{x}
xi−xˉ
(ii) 计算每个点
x
1
,
.
.
.
,
x
N
x_1,...,x_N
x1,...,xN至
u
⃗
1
\vec{u}_1
u1方向上的投影:
(
x
i
−
x
ˉ
)
u
⃗
1
,
∣
∣
u
⃗
1
∣
∣
=
1
(x_i-\bar{x})\vec{u}_1,\;\;\;||\vec{u}_1|| = 1
(xi−xˉ)u1,∣∣u1∣∣=1
(iii) 计算投影方差:
J
=
1
N
∑
i
=
1
N
[
(
x
i
−
x
ˉ
)
T
u
⃗
1
]
2
,
∣
∣
u
⃗
1
∣
∣
=
1
J = \frac{1}{N}\sum\limits_{i=1}^{N}[(x_i-\bar{x})^T\vec{u}_1]^2,\;\;\;||\vec{u}_1|| = 1
J=N1i=1∑N[(xi−xˉ)Tu1]2,∣∣u1∣∣=1
(iv) 最大化投影方差求
u
⃗
1
\vec{u}_1
u1:
u
ˉ
1
=
a
r
g
m
a
x
u
1
1
N
∑
i
=
1
N
[
(
x
i
−
x
ˉ
)
T
u
⃗
1
]
2
s
.
t
.
u
⃗
1
T
u
⃗
1
=
1
(
u
⃗
1
往
后
不
带
向
量
符
号
)
\bar{u}_1 = argmax_{u_1}\;\;\frac{1}{N}\sum\limits_{i=1}^{N}[(x_i-\bar{x})^T\vec{u}_1]^2 \\ \;\;\;s.t. \vec{u}_1^T\vec{u}_1 = 1 (\vec{u}_1往后不带向量符号)
uˉ1=argmaxu1N1i=1∑N[(xi−xˉ)Tu1]2s.t.u1Tu1=1(u1往后不带向量符号)
得到:
J
=
1
N
∑
i
=
1
N
[
(
x
i
−
x
ˉ
)
T
u
⃗
1
]
2
=
1
N
∑
i
=
1
N
[
u
1
T
(
x
i
−
x
ˉ
)
(
x
i
−
x
ˉ
)
T
u
1
]
=
u
1
T
[
1
N
∑
i
=
1
N
(
x
i
−
x
ˉ
)
(
x
i
−
x
ˉ
)
T
]
u
1
=
u
1
T
S
2
u
1
J = \frac{1}{N}\sum\limits_{i=1}^{N}[(x_i-\bar{x})^T\vec{u}_1]^2 = \frac{1}{N}\sum\limits_{i=1}^{N}[u_1^T(x_i-\bar{x})(x_i-\bar{x})^Tu_1]\\ \; = u_1^T[\frac{1}{N}\sum\limits_{i=1}^{N}(x_i-\bar{x})(x_i - \bar{x})^T]u_1 = u_1^TS^2u_1
J=N1i=1∑N[(xi−xˉ)Tu1]2=N1i=1∑N[u1T(xi−xˉ)(xi−xˉ)Tu1]=u1T[N1i=1∑N(xi−xˉ)(xi−xˉ)T]u1=u1TS2u1
即:
u
^
1
=
a
r
g
m
a
x
u
1
u
1
T
S
2
u
1
,
s
.
t
.
u
1
T
u
1
=
1
L
(
u
1
,
λ
)
=
u
1
T
S
2
u
1
+
λ
(
1
−
u
1
T
u
1
)
∂
L
∂
u
1
=
2
S
2
u
1
−
2
λ
u
1
=
0
即
:
S
2
u
1
=
λ
u
1
\hat{u}_1 = argmax_{u_1}u_1^TS^2u_1,\;\;\;s.t.u_1^Tu_1 = 1\\ L(u_1,\lambda) = u_1^TS^2u_1 + \lambda (1-u_1^Tu_1)\\ \frac{\partial L}{\partial u_1} = 2S^2u_1-2\lambda u_1 = 0\\ 即:S^2u_1 = \lambda u_1
u^1=argmaxu1u1TS2u1,s.t.u1Tu1=1L(u1,λ)=u1TS2u1+λ(1−u1Tu1)∂u1∂L=2S2u1−2λu1=0即:S2u1=λu1
可以看到:
λ
\lambda
λ为
S
2
S^2
S2的特征值,
u
1
u_1
u1为
S
2
S^2
S2的特征向量。因此我们只需要对中心化后的协方差矩阵进行特征值分解,得到的特征向量即为投影方向。如果需要进行降维,那么只需要取p的前M个特征向量即可。
岭回归实例分享:
sklearn.linear_model.ridge_regression(X, y, alpha, *, sample_weight=None, solver=‘auto’, max_iter=None, tol=0.001, verbose=0, random_state=None, return_n_iter=False, return_intercept=False, check_input=True)
岭回归文档
- 参数:
alpha:较大的值表示更强的正则化。浮点数
sample_weight:样本权重,默认无。
solver:求解方法,{‘auto’, ‘svd’, ‘cholesky’, ‘lsqr’, ‘sparse_cg’, ‘sag’, ‘saga’}, 默认=’auto’。“ svd”使用X的奇异值分解来计算Ridge系数。'cholesky’使用标准的scipy.linalg.solve函数通过dot(XT,X)的Cholesky分解获得封闭形式的解。'sparse_cg’使用scipy.sparse.linalg.cg中的共轭梯度求解器。作为一种迭代算法,对于大规模数据(可能设置tol和max_iter),此求解器比“ Cholesky”更合适。 lsqr”使用专用的正则化最小二乘例程scipy.sparse.linalg.lsqr。它是最快的,并且使用迭代过程。“ sag”使用随机平均梯度下降,“ saga”使用其改进的无偏版本SAGA。两种方法都使用迭代过程,并且当n_samples和n_features都很大时,通常比其他求解器更快。请注意,只有在比例大致相同的要素上才能确保“ sag”和“ saga”快速收敛。您可以使用sklearn.preprocessing中的缩放器对数据进行预处理。最后五个求解器均支持密集和稀疏数据。但是,当fit_intercept为True时,仅’sag’和’sparse_cg’支持稀疏输入。
from sklearn import linear_model
reg_rid = linear_model.Ridge(alpha=.5)
reg_rid.fit(X,y)
reg_rid.score(X,y)
Lasso实例分享:
class sklearn.linear_model.Lasso(alpha=1.0, *, fit_intercept=True, normalize=False, precompute=False, copy_X=True, max_iter=1000, tol=0.0001, warm_start=False, positive=False, random_state=None, selection=‘cyclic’)
Lasso官方文档
- 参数:
alpha:正则化强度,1.0代表标准最小二乘。
fit_intercept:是否计算模型截距。默认true。
normalize:是否标准化,默认false。
positive:是否强制系数为正,默认false。
from sklearn import linear_model
reg_lasso = linear_model.Lasso(alpha = 0.5)
reg_lasso.fit(X,y)
reg_lasso.score(X,y)
S2.7 模型超参数调优
对模型超参数进行调优(调参):
在刚刚的讨论中,我们似乎对模型的优化都是对模型算法本身的改进,比如:岭回归对线性回归的优化在于在线性回归的损失函数中加入L2正则化项从而牺牲无偏性降低方差。但是,大家是否想过这样的问题:在L2正则化中参数
λ
\lambda
λ应该选择多少?是0.01、0.1、还是1?到目前为止,我们只能凭经验或者瞎猜,能不能找到一种方法找到最优的参数
λ
\lambda
λ?事实上,找到最佳参数的问题本质上属于最优化的内容,因为从一个参数集合中找到最佳的值本身就是最优化的任务之一,我们脑海中浮现出来的算法无非就是:梯度下降法、牛顿法等无约束优化算法或者约束优化算法,但是在具体验证这个想法是否可行之前,我们必须先认识两个最本质概念的区别。
- 参数与超参数:
我们很自然的问题就是岭回归中的参数 λ \lambda λ和参数w之间有什么不一样?事实上,参数w是我们通过设定某一个具体的 λ \lambda λ后使用类似于最小二乘法、梯度下降法等方式优化出来的,我们总是设定了 λ \lambda λ是多少后才优化出来的参数w。因此,类似于参数w一样,使用最小二乘法或者梯度下降法等最优化算法优化出来的数我们称为参数,类似于 λ \lambda λ一样,我们无法使用最小二乘法或者梯度下降法等最优化算法优化出来的数我们称为超参数。
模型参数是模型内部的配置变量,其值可以根据数据进行估计。- 进行预测时需要参数。
- 它参数定义了可使用的模型。
- 参数是从数据估计或获悉的。
- 参数通常不由编程者手动设置。
- 参数通常被保存为学习模型的一部分。
- 参数是机器学习算法的关键,它们通常由过去的训练数据中总结得出 。
模型超参数是模型外部的配置,其值无法从数据中估计。 - 超参数通常用于帮助估计模型参数。
- 超参数通常由人工指定。
- 超参数通常可以使用启发式设置。
- 超参数经常被调整为给定的预测建模问题。
我们前面(4)部分的优化都是基于模型本身的具体形式的优化,那本次(5)调整的内容是超参数,也就是取不同的超参数的值对于模型的性能有不同的影响。
- 网格搜索GridSearchCV():
网格搜索:https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.GridSearchCV.html?highlight=gridsearchcv#sklearn.model_selection.GridSearchCV
网格搜索结合管道:https://scikit-learn.org/stable/auto_examples/compose/plot_compare_reduction.html?highlight=gridsearchcv
网格搜索的思想非常简单,比如你有2个超参数需要去选择,那你就把所有的超参数选择列出来分别做排列组合。举个例子: λ = 0.01 , 0.1 , 1.0 \lambda = 0.01,0.1,1.0 λ=0.01,0.1,1.0和 α = 0.01 , 0.1 , 1.0 \alpha = 0.01,0.1,1.0 α=0.01,0.1,1.0,你可以做一个排列组合,即:{[0.01,0.01],[0.01,0.1],[0.01,1],[0.1,0.01],[0.1,0.1],[0.1,1.0],[1,0.01],[1,0.1],[1,1]} ,然后针对每组超参数分别建立一个模型,然后选择测试误差最小的那组超参数。换句话说,我们需要从超参数空间中寻找最优的超参数,很像一个网格中找到一个最优的节点,因此叫网格搜索。 - 随机搜索 RandomizedSearchCV() :
https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.RandomizedSearchCV.html?highlight=randomizedsearchcv#sklearn.model_selection.RandomizedSearchCV
网格搜索相当于暴力地从参数空间中每个都尝试一遍,然后选择最优的那组参数,这样的方法显然是不够高效的,因为随着参数类别个数的增加,需要尝试的次数呈指数级增长。有没有一种更加高效的调优方式呢?那就是使用随机搜索的方式,这种方式不仅仅高校,而且实验证明,随机搜索法结果比稀疏化网格法稍好(有时候也会极差,需要权衡)。参数的随机搜索中的每个参数都是从可能的参数值的分布中采样的。与网格搜索相比,这有两个主要优点:- 可以独立于参数数量和可能的值来选择计算成本。
- 添加不影响性能的参数不会降低效率。
SVR的管道调优方案
# 我们先来对未调参的SVR进行评价:
from sklearn.svm import SVR # 引入SVR类
from sklearn.pipeline import make_pipeline # 引入管道简化学习流程
from sklearn.preprocessing import StandardScaler # 由于SVR基于距离计算,引入对数据进行标准化的类
from sklearn.model_selection import GridSearchCV # 引入网格搜索调优
from sklearn.model_selection import cross_val_score # 引入K折交叉验证
from sklearn import datasets
boston = datasets.load_boston() # 返回一个类似于字典的类
X = boston.data
y = boston.target
features = boston.feature_names
pipe_SVR = make_pipeline(StandardScaler(),
SVR())
score1 = cross_val_score(estimator=pipe_SVR,
X = X,
y = y,
scoring = 'r2',
cv = 10) # 10折交叉验证
print("CV accuracy: %.3f +/- %.3f" % ((np.mean(score1)),np.std(score1)))
# 下面我们使用网格搜索来对SVR调参:
from sklearn.pipeline import Pipeline
pipe_svr = Pipeline([("StandardScaler",StandardScaler()),
("svr",SVR())])
param_range = [0.0001,0.001,0.01,0.1,1.0,10.0,100.0,1000.0]
param_grid = [{"svr__C":param_range,"svr__kernel":["linear"]}, # 注意__是指两个下划线,一个下划线会报错的
{"svr__C":param_range,"svr__gamma":param_range,"svr__kernel":["rbf"]}]
gs = GridSearchCV(estimator=pipe_svr,
param_grid = param_grid,
scoring = 'r2',
cv = 10) # 10折交叉验证
gs = gs.fit(X,y)
print("网格搜索最优得分:",gs.best_score_)
print("网格搜索最优参数组合:\n",gs.best_params_)
# 下面我们使用随机搜索来对SVR调参:
from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import uniform # 引入均匀分布设置参数
pipe_svr = Pipeline([("StandardScaler",StandardScaler()),
("svr",SVR())])
distributions = dict(svr__C=uniform(loc=1.0, scale=4), # 构建连续参数的分布
svr__kernel=["linear","rbf"], # 离散参数的集合
svr__gamma=uniform(loc=0, scale=4))
rs = RandomizedSearchCV(estimator=pipe_svr,
param_distributions = distributions,
scoring = 'r2',
cv = 10) # 10折交叉验证
rs = rs.fit(X,y)
print("随机搜索最优得分:",rs.best_score_)
print("随机搜索最优参数组合:\n",rs.best_params_)
经过我们不懈的努力,从收集数据集并选择合适的特征、选择度量模型性能的指标、选择具体的模型并进行训练以优化模型到评估模型的性能并调参,我们认识到了如何使用sklearn构建简单回归模型。在本章的最后,我们会给出一个具体的案例,整合回归的内容。下面我们来看看机器学习另外一类大问题:分类。与回归一样,分类问题在机器学习的地位非常重要,甚至有的地方用的比回归问题还要多,因此分类问题是十分重要的!
S2.8 评估模型的性能并调参:
方式1:网格搜索GridSearchCV()
# 使用网格搜索进行超参数调优:
from sklearn.model_selection import GridSearchCV
from sklearn.svm import SVC
import time
start_time = time.time()
pipe_svc = make_pipeline(StandardScaler(),SVC(random_state=1))
param_range = [0.0001,0.001,0.01,0.1,1.0,10.0,100.0,1000.0]
param_grid = [{'svc__C':param_range,'svc__kernel':['linear']},{'svc__C':param_range,'svc__gamma':param_range,'svc__kernel':['rbf']}]
gs = GridSearchCV(estimator=pipe_svc,param_grid=param_grid,scoring='accuracy',cv=10,n_jobs=-1)
gs = gs.fit(X,y)
end_time = time.time()
print("网格搜索经历时间:%.3f S" % float(end_time-start_time))
print(gs.best_score_)
print(gs.best_params_)
方式2:随机网格搜索RandomizedSearchCV()
from sklearn.model_selection import RandomizedSearchCV
from sklearn.svm import SVC
import time
start_time = time.time()
pipe_svc = make_pipeline(StandardScaler(),SVC(random_state=1))
param_range = [0.0001,0.001,0.01,0.1,1.0,10.0,100.0,1000.0]
param_grid = [{'svc__C':param_range,'svc__kernel':['linear']},{'svc__C':param_range,'svc__gamma':param_range,'svc__kernel':['rbf']}]
# param_grid = [{'svc__C':param_range,'svc__kernel':['linear','rbf'],'svc__gamma':param_range}]
gs = RandomizedSearchCV(estimator=pipe_svc, param_distributions=param_grid,scoring='accuracy',cv=10,n_jobs=-1)
gs = gs.fit(X,y)
end_time = time.time()
print("随机网格搜索经历时间:%.3f S" % float(end_time-start_time))
print(gs.best_score_)
print(gs.best_params_)
绘制混淆矩阵与ROC曲线
# 混淆矩阵:
# 加载数据
df = pd.read_csv("http://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/wdbc.data",header=None)
'''
乳腺癌数据集:569个恶性和良性肿瘤细胞的样本,M为恶性,B为良性
'''
# 做基本的数据预处理
from sklearn.preprocessing import LabelEncoder
X = df.iloc[:,2:].values
y = df.iloc[:,1].values
le = LabelEncoder() #将M-B等字符串编码成计算机能识别的0-1
y = le.fit_transform(y)
le.transform(['M','B'])
# 数据切分8:2
from sklearn.model_selection import train_test_split
X_train,X_test,y_train,y_test = train_test_split(X,y,test_size=0.2,stratify=y,random_state=1)
from sklearn.svm import SVC
pipe_svc = make_pipeline(StandardScaler(),SVC(random_state=1))
from sklearn.metrics import confusion_matrix
pipe_svc.fit(X_train,y_train)
y_pred = pipe_svc.predict(X_test)
confmat = confusion_matrix(y_true=y_test,y_pred=y_pred)
fig,ax = plt.subplots(figsize=(2.5,2.5))
ax.matshow(confmat, cmap=plt.cm.Blues,alpha=0.3)
for i in range(confmat.shape[0]):
for j in range(confmat.shape[1]):
ax.text(x=j,y=i,s=confmat[i,j],va='center',ha='center')
plt.xlabel('predicted label')
plt.ylabel('true label')
plt.show()
# 绘制ROC曲线:
from sklearn.metrics import roc_curve,auc
from sklearn.metrics import make_scorer,f1_score
scorer = make_scorer(f1_score,pos_label=0)
gs = GridSearchCV(estimator=pipe_svc,param_grid=param_grid,scoring=scorer,cv=10)
y_pred = gs.fit(X_train,y_train).decision_function(X_test)
#y_pred = gs.predict(X_test)
fpr,tpr,threshold = roc_curve(y_test, y_pred) ###计算真阳率和假阳率
roc_auc = auc(fpr,tpr) ###计算auc的值
plt.figure()
lw = 2
plt.figure(figsize=(7,5))
plt.plot(fpr, tpr, color='darkorange',
lw=lw, label='ROC curve (area = %0.2f)' % roc_auc) ###假阳率为横坐标,真阳率为纵坐标做曲线
plt.plot([0, 1], [0, 1], color='navy', lw=lw, linestyle='--')
plt.xlim([-0.05, 1.0])
plt.ylim([-0.05, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver operating characteristic ')
plt.legend(loc="lower right")
plt.show()
S3 课后习题
为了巩固本章的理解,在这里给个小任务,大家结合sklearn的fetch_lfw_people数据集,进行一次实战。fetch_lfw_people数据集是一个图像数据集,详细内容可以参照:
https://scikit-learn.org/stable/modules/generated/sklearn.datasets.fetch_lfw_people.html
案例的内容是对图像进行识别并分类。
参考资料:
https://blog.csdn.net/cwlseu/article/details/52356665
https://blog.csdn.net/jasonzhoujx/article/details/81905923
#下载数据
import matplotlib.pyplot as plt
from sklearn.datasets import fetch_lfw_people
faces = fetch_lfw_people(data_home = './EnsembleLearning/CH2-机器学习基础模型回顾',
min_faces_per_person=60)
print(faces.target_names)
print(faces.images.shape)
#画一些人脸,看看需要处理的数据
import matplotlib.pyplot as plt
import seaborn as sns;sns.set()
fig, ax = plt.subplots(3,5)
fig.subplots_adjust(left=0.0625, right=1.2, wspace=1)
for i, axi in enumerate(ax.flat):
axi.imshow(faces.images[i], cmap='bone')
axi.set(xticks=[], yticks=[], xlabel=faces.target_names[faces.target[i]])
#使用预处理来提取更有意义的特征。这里使用主成份分析来提取150个基本元素,然后将其提供给支持向量机分类器。
#将这个预处理和分类器打包成管道
from sklearn.svm import SVC
from sklearn.decomposition import PCA
from sklearn.pipeline import make_pipeline
pca = PCA(n_components=150, whiten=True, random_state=42)
svc = SVC(kernel='rbf', class_weight='balanced')
model = make_pipeline(pca, svc)
#为了测试分类器的训练效果,将数据集分解成训练集和测试集进行交叉检验
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(faces.data, faces.target, random_state=42)
#用网络搜索交叉检验来寻找最优参数组合。通过不断调整C(松弛变量)和参数gamma(控制径向基函数核的大小),确定最优模型
from sklearn.model_selection import GridSearchCV
param_grid = {'svc__C': [1,5,10, 20, 30], 'svc__gamma':[0.0001, 0.0005, 0.001, 0.005]}
grid = GridSearchCV(model, param_grid)
grid.fit(x_train, y_train)
print(grid.best_params_)
参考资料: