Skip to content

监督学习:从高维数据中预测输出变量

监督学习解决的问题

监督学习 包括学习两个数据集之间的联系:观测数据X和我们试图预测的外部变量y,通常称为“目标”或“标签”。通常情况下,y是长度为n_samples的一维数组。

scikit-learn中的所有有监督估计器都有一个拟合模型的fit(X, y),和给定未标记的观察值X,返回预测的标记ypredict(X)方法。

词汇:分类与回归

如果预测任务是将观测值分类到一组有限的标签集合中,换句话说就是“命名”观察到的物体,则称为分类任务。如果是预测连续目标变量,称为回归任务。

在scikit learn中进行分类时,y是整数或字符型向量。

注:请参阅 使用scikit-learn的机器学习简介教程 快速浏览 scikit-learn 中使用的基础机器学习词汇。

最近邻分类器与维度惩罚

irises(鸢尾花)分类:

https://scikit-learn.org/stable/_images/sphx_glr_plot_iris_dataset_001.png

iris(鸢尾花)数据集是一个分类任务,包括识别3种不同类型的鸢尾花(Setosa, Versicolour, and Virginica)的花瓣和萼片长度和宽度:

>>> import numpy as np
>>> from sklearn import datasets
>>> iris_X, iris_y = datasets.load_iris(return_X_y=True)
>>> np.unique(iris_y)
array([0, 1, 2])

k近邻分类器

最简单的分类器是 最近邻分类器: 给定一个新的观测数据X_test,在训练集(即训练估计器所用的数据)中找到具有最接近特征向量的观测值。(有关此分类器的更多详细信息,请参阅 Scikit-learn 文档的最近邻分类器章节。)

训练集和测试集

在尝试使用任何学习算法进行实验时,重要的是不要在用于拟合估计器的数据上测试估计器的预测值,因为这没有办法体现出估计器在新数据上的性能。这就是为什么数据集经常被分成_train_和_test_数据的原因。

KNN (k近邻) 分类示例 :

https://scikit-learn.org/stable/_images/sphx_glr_plot_classification_001.png

>>> # 将iris(鸢尾花)数据分成训练数据和测试数据
>>> # 随机排列,使得数据随机划分
>>> np.random.seed(0)
>>> indices = np.random.permutation(len(iris_X))
>>> iris_X_train = iris_X[indices[:-10]]
>>> iris_y_train = iris_y[indices[:-10]]
>>> iris_X_test = iris_X[indices[-10:]]
>>> iris_y_test = iris_y[indices[-10:]]
>>> # 创建和训练最近邻分类器
>>> from sklearn.neighbors import KNeighborsClassifier
>>> knn = KNeighborsClassifier()
>>> knn.fit(iris_X_train, iris_y_train)
KNeighborsClassifier()
>>> knn.predict(iris_X_test)
array([1, 2, 1, 0, 0, 0, 2, 1, 2, 0])
>>> iris_y_test
array([1, 1, 1, 0, 0, 0, 2, 1, 2, 0])

维度惩罚

为了使估计有效,需要相邻点之间的距离小于某个值d,这取决于问题本身。在一维中,这平均需要n ~ 1/d个点。在上述k-NN示例中,如果数据仅由一个0到1的特征值和n个训练观测数据所描述。因此,与类间特征变化范围相比,当1/n较小时,最近邻决策规则是很有效的。

如果特征数量为p,则现在需要n ~ 1/d^p点。 假设我们在一维中需要10个点:那么在p维中需要10^p个点才能铺平[0,1]空间。一个好的估计器随着p变大,所需的训练点的数量会呈指数增长。

例如,如果每个点仅是但个数字(8个字节),则有效的k-NN估计器仅在p~20维度下将比整个互联网当前估计大小(±1000艾字节(Exabytes)左右)需要更多的训练数据。

这被称为 维度惩罚,是机器学习解决的核心问题

线性模型: 从回归到稀疏

diabetes(糖尿病)数据集

diabetes(糖尿病)数据集由442位患者的10个生理变量(年龄,性别,体重,血压)组成,和一年后疾病级别指标:

>>> diabetes_X, diabetes_y = datasets.load_diabetes(return_X_y=True)
>>> diabetes_X_train = diabetes_X[:-20]
>>> diabetes_X_test  = diabetes_X[-20:]
>>> diabetes_y_train = diabetes_y[:-20]
>>> diabetes_y_test  = diabetes_y[-20:]

当前的任务是根据生理变量预测疾病的级别。

线性模型

线性回归,以其最简单拟合线性模型的形式,通过调整一系列参数使模型的残差平方和尽可能小。

https://scikit-learn.org/stable/_images/sphx_glr_plot_ols_001.png

线性模型: y = X β + ε

  • X: 数据
  • y: 目标变量
  • β: 系数
  • ε: 观测噪声
>>> from sklearn import linear_model
>>> regr = linear_model.LinearRegression()
>>> regr.fit(diabetes_X_train, diabetes_y_train)
LinearRegression()
>>> print(regr.coef_)  
[   0.30349955 -237.63931533  510.53060544  327.73698041 -814.13170937
    492.81458798  102.84845219  184.60648906  743.51961675   76.09517222]

>>> # 均方误差
>>> np.mean((regr.predict(diabetes_X_test) - diabetes_y_test)**2)
2004.56760268...

>>> # 解释方差得分:1为完美预测
>>> # 0表示X和y之间没有线性关系
>>> regr.score(diabetes_X_test, diabetes_y_test)
0.5850753022690...

收缩

如果每个维度的数据点很少,则观测噪声会引起很大的方差:

>>> X = np.c_[ .5, 1].T
>>> y = [.5, 1]
>>> test = np.c_[ 0, 2].T
>>> regr = linear_model.LinearRegression()

>>> import matplotlib.pyplot as plt 
>>> plt.figure() 

>>> np.random.seed(0)
>>> for _ in range(6): 
...     this_X = .1 * np.random.normal(size=(2, 1)) + X
...     regr.fit(this_X, y)
...     plt.plot(test, regr.predict(test)) 
...     plt.scatter(this_X, y, s=3)  

高维统计学习的一种解决方案是将回归系数收缩为零:任意两个随机选择的观察值可能不相关。 这称为回归:

https://scikit-learn.org/stable/_images/sphx_glr_plot_ols_ridge_variance_002.png

>>> regr = linear_model.Ridge(alpha=.1)
>>> plt.figure() 

>>> np.random.seed(0)
>>> for _ in range(6): 
...     this_X = .1 * np.random.normal(size=(2, 1)) + X
...     regr.fit(this_X, y)
...     plt.plot(test, regr.predict(test)) 
...     plt.scatter(this_X, y, s=3)   

这是一个bias/variance tradeoff的例子:岭参数(alpha)越大,偏差越大,方差越小。

我们可以选择alpha以最小化遗漏错误,这次使用的是diabetes(糖尿病)数据集而不是synthetic(合成)数据:

>>> alphas = np.logspace(-4, -1, 6)
>>> print([regr.set_params(alpha=alpha)
...            .fit(diabetes_X_train, diabetes_y_train)
...            .score(diabetes_X_test, diabetes_y_test)
...        for alpha in alphas])
[0.5851110683883..., 0.5852073015444..., 0.5854677540698...,
0.5855512036503..., 0.5830717085554..., 0.57058999437...]

:捕获拟合参数的噪声(该噪声阻止模型将其推广到新数据)称为 过拟合。岭回归产生的偏差称为 正则化

稀疏

仅拟合特征1和特征2

diabetes_ols_1

diabetes_ols_3

diabetes_ols_2

注:完整diabetes(糖尿病)数据集包含11个维度(10个特征维度和1个目标变量)。很难直观地表示这种数据集,但要记住,这种比较空的空间,可能会很有用。

我们可以看到,尽管特征2在整个模型上具有很强的系数,但当与特征1一起考虑时,它对y的影响很小。

为了改善此类问题(即减轻维度惩罚),只选择信息性特征并设置非信息性特征(如特征2到0)将是有趣的。岭回归将减少它们的值,但不会将它们设置为零。另一种惩罚方法,叫做 Lasso (最小绝对收缩和选择算子),可以将某些系数设置为零。这种方法被称为稀疏法,稀疏可以看作是Occam’s razor(奥卡姆剃刀)的一种应用:更喜欢简单的模型。

>>> regr = linear_model.Lasso()
>>> scores = [regr.set_params(alpha=alpha)
...               .fit(diabetes_X_train, diabetes_y_train)
...               .score(diabetes_X_test, diabetes_y_test)
...           for alpha in alphas]
>>> best_alpha = alphas[scores.index(max(scores))]
>>> regr.alpha = best_alpha
>>> regr.fit(diabetes_X_train, diabetes_y_train)
Lasso(alpha=0.025118864315095794)
>>> print(regr.coef_)
[   0.         -212.43764548  517.19478111  313.77959962 -160.8303982    -0.
-187.19554705   69.38229038  508.66011217   71.84239008]

同一问题的不同算法

可以使用不同的算法来解决相同的数学问题。 例如,scikit-learn中的Lasso对象使用 coordinate descent(坐标下降) 法来解决lasso回归问题,这对大型数据集非常有效。 但是,scikit-learn还使用LARS算法提供了一个LassoLars 对象,这对于估计的权重向量非常稀疏的问题(如观测值很少的问题)非常有效。

分类

https://scikit-learn.org/stable/_images/sphx_glr_plot_logistic_001.png

对于分类,在标记鸢尾花任务中,线性回归不是正确的方法,因为它会给远离决策边界的数据赋予太多的权重。线性方法是用于拟合sigmoid函数或logistic函数:

y = sigmoid(X β - offset) + ε = 1 / (1 + exp(-X β + offset) + ε

>>> log = linear_model.LogisticRegression(C=1e5)
>>> log.fit(iris_X_train, iris_y_train)
LogisticRegression(C=100000.0)

这就是著名的逻辑回归(LogisticRegression).

https://scikit-learn.org/stable/_images/sphx_glr_plot_iris_logistic_001.png

多分类

如果您要预测多个类别,则通常是拟合一个one-versus-all(一对多)分类器,然后通过投票进行最终决策。

逻辑回归的收缩和稀疏

C参数控制 逻辑回归(LogisticRegression) 对象中的正则化量:C值较大会导致正则化程度降低。penalty = "l2" 提供收缩率(即非稀疏系数),而 penalty = "l1" 提供稀疏率。

练习

尝试使用最近邻分类器和线性模型对digits(数字)数据集进行分类。留出最后10%的数据测试模型在这些测试数据集上的预测性能。

from sklearn import datasets, neighbors, linear_model

X_digits, y_digits = datasets.load_digits(return_X_y=True)
X_digits = X_digits / X_digits.max()

答案: https://scikit-learn.org/stable/auto_examples/exercises/plot_digits_classification_exercise.py

支持向量机 (SVMs)

线性支持向量机

支持向量机 属于判别模型族:它们试图找到一个组合的样本,以建立一个平面最大限度地提高这两个类之间的margin。正则化由参数C设置:C 的值较小,意味着使用分隔线周围的许多或所有观测样例来计算margin (更多的正则化); C 的值较大,意味着使用靠近分隔线的观测样例来计算margin (较少的正则化)。

非正则支持向量机(Unregularized SVM)

svm_margin_unreg

正则化支持向量机(默认)(Regularized SVM (default))

svm_margin_reg

示例:

支持向量机可以被用于回归–SVR (支持向量回归)–, 或者分类–SVC (支持向量分类).

>>> from sklearn import svm
>>> svc = svm.SVC(kernel='linear')
>>> svc.fit(iris_X_train, iris_y_train)
SVC(kernel='linear')

警告:规范化数据

对于包括SVM在内的许多估算器而言,数据集的每个特征具有单位标准差这一特性对于获得良好的预测至关重要。

使用核(kernels)

类在特征空间中并不总是线性可分的。解决办法是建立一个不是线性的,但可以是多项式的决策函数。

这是使用核技巧(kernel trick)完成的,该技巧可以看作是通过将核置于观察值上来创建决策能量:

线性核

svm_kernel_linear

>>> svc = svm.SVC(kernel='linear')

多项式核

svm_kernel_poly

>>> svc = svm.SVC(kernel='poly',degree=3)
>>> # degree: 多项式次数

RBF 核 (径向基函数)

svm_kernel_rbf

>>> svc = svm.SVC(kernel='rbf')
>>> # gamma:径向核大小的逆

交互式示例

请参阅 SVM GUI(图形用户界面) 下载 svm_gui.py; 用右键和左键添加两类数据点,拟合模型并更改参数和数据。

https://scikit-learn.org/stable/_images/sphx_glr_plot_iris_dataset_001.png

练习

根据特征1和特征2,尝试使用SVM把类1和类2iris从(鸢尾花)数据集中分出来。每个类留下10%数据,将其作为测试数据,测试模型的预测性能。

警告 : 这些类是有序的,不要留下最后10%的数据,否则您将只测试一个类。

提示:您可以在网格上使用decision_function方法来获取直观显示。

iris = datasets.load_iris()
X = iris.data
y = iris.target

X = X[y != 0, :2]
y = y[y != 0]    

答案: https://scikit-learn.org/stable/auto_examples/exercises/plot_iris_exercise.py

(C) 2007 - 2019, scikit-learn 开发人员(BSD许可证). 查看此页源代码