监督学习是最常用也是最成功的机器学习类型之一,每当想要根据给定输入预测某个结果,并且还有输入 / 输出对的示例时,都应该使 用监督学习。
监督机器学习问题主要有两种,分别叫作分类(classification)与回归(regression)。区分分类任务和回归任务有一个简单方法,就是问一个问题:输出是否具有某种连续性。
在监督学习中,我们想要在训练数据上构建模型,然后能够对没见过的新数据(这些新数 据与训练集具有相同的特性)做出准确预测。如果一个模型能够对没见过的数据做出准确 预测,我们就说它能够从训练集泛化(generalize)到测试集。
在训练集上 实现 100% 的精度用处不大,判断一个算法在新数据上表现好坏的唯一度量,就是在测试集上的评估。
过拟合:构建一个对现 有信息量来说过于复杂的模型,这被称为过拟合。在拟合模型时过分关注训练集的细节,得到了一个在训练集上表 现很好、但不能泛化到新数据上的模型,那么就存在过拟合。
欠拟合:选择过于简单的模型被称为欠拟合。
我们的模型越复杂,在训练数据上的预测结果就越好。但是,如果我们的模型过于复杂, 我们开始过多关注训练集中每个单独的数据点,模型就不能很好地泛化到新数据上。 二者之间存在一个最佳位置,可以得到最好的泛化性能。这就是我们想要的模型。
图 2-1:模型复杂度与训练精度和测试精度之间的权衡
模型复杂度与数据集大小的关系:模型复杂度与训练数据集中输入的变化密切相关:数据集中包含的数据点的变化范围越大,在不发生过拟合的前提下你可以使用的模型就越复杂。收集更多数据,适当构建更复杂的模型,对监督学习任务往往特别有用。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import mglearn
import matplotlib.pyplot as plt
# TODO 模拟的二分类数据集 forge数据集
# TODO 模拟回归算法数据集 wave数据集
# 生成数据集
X, y = mglearn.datasets.make_forge()
# 数据集绘图
mglearn.discrete_scatter(X[:, 0], X[:, 1], y)
plt.legend(["Class 0", "Class 1"], loc=4) # 添加x、y轴说明,loc说明标签显示区域
plt.xlabel("First feature") # 设置坐标说明label
plt.ylabel("Second feature")
plt.show()
print("X.shape: {}".format(X.shape)) # X.shape: (26, 2) 26个数据点,2个特征(分类)
"""
从特征较少的数据集(也叫低维数据集)中得出的结论可能并不适用于特征较多的数据集(也叫高维数据集)
"""
X, y = mglearn.datasets.make_wave(n_samples=40)
plt.plot(X, y, 'o')
plt.ylim(-3, 3)
plt.xlabel("Feature")
plt.ylabel("Target")
plt.show()
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# TODO 威斯康星州乳腺癌数据集
"""
里面记录 了乳腺癌肿瘤的临床测量数据。每个肿瘤都被标记为“良性”(benign,表示无害肿瘤) 或“恶性”(malignant,表示癌性肿瘤),
其任务是基于人体组织的测量数据来学习预测肿 瘤是否为恶性。这个数据集共包含 569 个数据点,每个数据点有 30 个特征
"""
from sklearn.datasets import load_breast_cancer
import numpy as np
cancer = load_breast_cancer()
print("cancer.keys(): \n{}".format(cancer.keys()))
print("-" * 80 + "\n")
print("Shape of cancer data: {}".format(cancer.data.shape))
print("-" * 80 + "\n")
print("Sample counts per class:\n{}".format( {n: v for n, v in zip(cancer.target_names, np.bincount(cancer.target))}))
print("-" * 80 + "\n")
print("Feature names:\n{}".format(cancer.feature_names))
"""
打印信息
cancer.keys():
dict_keys(['data', 'target', 'target_names', 'DESCR', 'feature_names', 'filename'])
--------------------------------------------------------------------------------
Shape of cancer data: (569, 30)
--------------------------------------------------------------------------------
Sample counts per class:
{'malignant': 212, 'benign': 357}
--------------------------------------------------------------------------------
Feature names:
['mean radius' 'mean texture' 'mean perimeter' 'mean area'
'mean smoothness' 'mean compactness' 'mean concavity'
'mean concave points' 'mean symmetry' 'mean fractal dimension'
'radius error' 'texture error' 'perimeter error' 'area error'
'smoothness error' 'compactness error' 'concavity error'
'concave points error' 'symmetry error' 'fractal dimension error'
'worst radius' 'worst texture' 'worst perimeter' 'worst area'
'worst smoothness' 'worst compactness' 'worst concavity'
'worst concave points' 'worst symmetry' 'worst fractal dimension']
"""
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# TODO 现实世界中的回归数据集,即波士顿房价数据集
"""
与这个数据集相关的 任务是,利用犯罪率、是否邻近查尔斯河、公路可达性等信息,来预测 20 世纪 70 年代波 士顿地区房屋价格的中位数
"""
import mglearn
from sklearn.datasets import load_boston
boston = load_boston()
print("Data shape: {}".format(boston.data.shape))
X, y = mglearn.datasets.load_extended_boston() # 扩展这个数据集
print("X.shape: {}".format(X.shape))
"""
对于我们的目 的而言,我们需要扩展这个数据集,输入特征不仅包括这 13 个测量结果,还包括这些特 征之间的乘积(也叫交互项)。换句话说,
我们不仅将犯罪率和公路可达性作为特征, 还将犯罪率和公路可达性的乘积作为特征。像这样包含导出特征的方法叫作特征工程
打印信息
Data shape: (506, 13) # 506 个数据点和 13 个特征
X.shape: (506, 104) # 扩展后的数据集,104 个特征
"""
交互项、特征工程释义见上述代码
k-NN 算法可以说是最简单的机器学习算法。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# TODO k近邻分类,只考虑一个最近邻,在 forge 数据集上的应用
import mglearn
import matplotlib.pyplot as plt
mglearn.plots.plot_knn_classification(n_neighbors=1)
plt.show()
只考虑一个最近邻,也就是与我们想要预测的数据点最近的 训练数据点。预测结果就是这个训练数据点的已知输出。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# TODO k近邻分类,只考虑一个最近邻,在 forge 数据集上的应用
import mglearn
import matplotlib.pyplot as plt
mglearn.plots.plot_knn_classification(n_neighbors=3)
plt.show()
除了仅考虑最近邻,我还可以考虑任意个(k 个)邻居。这也是 k 近邻算法名字的来 历。在考虑多于一个邻居的情况时,我们用“投票法”(voting)来指定标签。即 k 个邻居中,将出现次数最多的类别作为预测结果。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# TODO 通过 scikit-learn 来应用 k 近邻算法
import mglearn
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
X, y = mglearn.datasets.make_forge()
# 将数据分为训练集和测试集,以便评估泛化性能
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
# 实例化 k 近邻算法类(模型)
clf = KNeighborsClassifier(n_neighbors=3)
# 训练模型 输入参数为 X_train 和 y_train,二者都是 NumPy 数组,前者包含训练数据,后者包含相应的训练标签
clf.fit(X_train, y_train)
# 调用predict 方法对测试数据进行预测,对于测试集中的数据点,大欧哟啊计算它在训练集的最近邻,找出其中出现次数最多的类别
print("Test set predictions: {}".format(clf.predict(X_test))) # Test set predictions: [1 0 1 0 1 0 0]
# 利用score方法评估模型泛化能力(精度)
print("Test set accuracy: {:.2f}".format(clf.score(X_test, y_test))) # Test set accuracy: 0.86
根据平面中每个点所属的类别对平面进行着色。这样可以查看决策边界(decision boundary),即算法对类别 0 和类别 1 的分界线。
下列代码分别将 1 个、3 个和 9 个邻居三种情况的决策边界可视化
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# TODO 通过代码 将 1 个、3 个和 9 个邻居三种情况的决策边界可视化
import mglearn
import matplotlib.pyplot as plt
from sklearn.neighbors import KNeighborsClassifier
X, y = mglearn.datasets.make_forge()
fig, axes = plt.subplots(1, 3, figsize=(10, 3))
for n_neighbors, ax in zip([1, 3, 9], axes):
# fit方法返回对象本身,所以我们可以将实例化和拟合放在一行代码中
clf = KNeighborsClassifier(n_neighbors=n_neighbors).fit(X, y)
mglearn.plots.plot_2d_separator(clf, X, fill=True, eps=0.5, ax=ax, alpha=.4)
mglearn.discrete_scatter(X[:, 0], X[:, 1], y, ax=ax)
ax.set_title("{} neighbor(s)".format(n_neighbors))
ax.set_xlabel("feature 0")
ax.set_ylabel("feature 1")
axes[0].legend(loc=3)
plt.show()
图 2-6:不同 n_neighbors 值的 k 近邻模型的决策边界
使用单一邻居绘制的决策边界紧跟着训练数据。随着邻居个数越来 越多,决策边界也越来越平滑。更平滑的边界对应更简单的模型。换句话说,使用更少的邻居对应更高的模型复杂度(如图 2-1 右侧所示),而使用更多的邻居对应更低 的模型复杂度(如图 2-1 左侧所示)。假如考虑极端情况,即邻居个数等于训练集中 所有数据点的个数,那么每个测试点的邻居都完全相同(即所有训练点),所有预测 结果也完全相同(即训练集中出现次数最多的类别)。
我们来研究一下能否证实之前讨论过的模型复杂度和泛化能力之间的关系。我们将在 现实世界的乳腺癌数据集上进行研究。先将数据集分成训练集和测试集,然后用不同 的邻居个数对训练集和测试集的性能进行评估。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# TODO 来研究能否证实之前讨论过的模型复杂度和泛化能力之间的关系
# 在现实世界的乳腺癌数据集上进行研究。先将数据集分成训练集和测试集,然后用不同的邻居个数对训练集和测试集的性能进行评估
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_breast_cancer
cancer = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target, stratify=cancer.target, random_state=66)
training_accuracy = []
test_accuracy = []
# n_neighbors取值从1到10
neighbors_settings = range(1, 11)
for n_neighbors in neighbors_settings:
# 构建模型
clf = KNeighborsClassifier(n_neighbors=n_neighbors)
clf.fit(X_train, y_train)
# 记录训练集精度
training_accuracy.append(clf.score(X_train, y_train))
# 记录泛化精度
test_accuracy.append(clf.score(X_test, y_test))
plt.plot(neighbors_settings, training_accuracy, label="training accuracy")
plt.plot(neighbors_settings, test_accuracy, label="test accuracy")
plt.ylabel("Accuracy")
plt.xlabel("n_neighbors")
plt.legend()
plt.show()
图 2-7:以 n_neighbors 为自变量,对比训练集精度和测试集精度
图像的 x 轴是 n_neighbors,y 轴是训练集精度和测试集精度。虽然现实世界的图像 很少有非常平滑的,但我们仍可以看出过拟合与欠拟合的一些特征(注意,由于更少的邻居对应更复杂的模型,所以此图相对于图 2-1 做了水平翻转)。仅考虑单一近邻 时,训练集上的预测结果十分完美。但随着邻居个数的增多,模型变得更简单,训练 集精度也随之下降。单一邻居时的测试集精度比使用更多邻居时要低,这表示单一近 邻的模型过于复杂。与之相反,当考虑 10 个邻居时,模型又过于简单,性能甚至变 得更差。最佳性能在中间的某处,邻居个数大约为 6。不过最好记住这张图的坐标轴 刻度。最差的性能约为 88% 的精度,这个结果仍然可以接受。
使用 wave 数据集做 k 近邻回归
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# TODO 用 wave 数据集,将 k 近邻算法用于回归
import mglearn
import matplotlib.pyplot as plt
mglearn.plots.plot_knn_regression(n_neighbors=1) # 一个邻居
mglearn.plots.plot_knn_regression(n_neighbors=3) # 多个邻居
plt.show()
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# TODO 通过 scikit-learn 将 wave 数据集用于回归 k 近邻算法
import mglearn
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsRegressor
X, y = mglearn.datasets.make_wave(n_samples=40)
# 将wave数据集分为训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
# 实例化 k 近邻算法类(模型),邻居个数设为3
reg = KNeighborsRegressor(n_neighbors=3)
# 训练模型 输入参数为 X_train 和 y_train,二者都是 NumPy 数组,前者包含训练数据,后者包含相应的训练标签
reg.fit(X_train, y_train)
# 调用predict 方法对测试数据进行预测,对于测试集中的数据点,都要计算它在训练集的最近邻,找出其中出现次数最多的类别
print("Test set predictions: {}".format(reg.predict(X_test)))
# 利用score方法评估模型泛化能力(精度)
print("Test set accuracy: {:.2f}".format(reg.score(X_test, y_test)))
"""
打印信息
Test set predictions: [-0.05396539 0.35686046 1.13671923 -1.89415682 -1.13881398 -1.63113382
0.35686046 0.91241374 -0.44680446 -1.13881398]
Test set accuracy: 0.83
"""
还可以用 score 方法来评估模型,对于回归问题,这一方法返回的是 R2分数。R2分数也叫作决定系数,是回归模型预测的优度度量,位于 0 到 1 之间。R2 等于 1 对应完美预测,R2 等于 0 对应常数模型,即总是预测训练集响应(y_train)的平 均值。
对于我们的一维数据集,可以查看所有特征取值对应的预测结果(图 2-10)
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# TODO 分析 KNeighborsRegressor
import mglearn
import matplotlib.pyplot as plt
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsRegressor
X, y = mglearn.datasets.make_wave(n_samples=40)
# 将wave数据集分为训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
# 创建1000个数据点,在-3和3之间均匀分布
line = np.linspace(-3, 3, 1000).reshape(-1, 1)
for n_neighbors, ax in zip([1, 3, 9], axes):
# 利用1个、3个或9个邻居分别进行预测
reg = KNeighborsRegressor(n_neighbors=n_neighbors)
reg.fit(X_train, y_train)
ax.plot(line, reg.predict(line))
ax.plot(X_train, y_train, '^', c=mglearn.cm2(0), markersize=8)
ax.plot(X_test, y_test, 'v', c=mglearn.cm2(1), markersize=8)
ax.set_title(
"{} neighbor(s)\n train score: {:.2f} test score: {:.2f}".format(n_neighbors, reg.score(X_train, y_train),
reg.score(X_test, y_test)))
ax.set_xlabel("Feature")
ax.set_ylabel("Target")
axes[0].legend(["Model predictions", "Training data/target", "Test data/target"], loc="best")
plt.show()
图 2-10:不同 n_neighbors 值的 k 近邻回归的预测结果对比
从图中可以看出,仅使用单一邻居,训练集中的每个点都对预测结果有显著影响,预测结果的图像经过所有数据点。这导致预测结果非常不稳定。考虑更多的邻居之后, 预测结果变得更加平滑,但对训练数据的拟合也不好。
一般来说,KNeighbors 分类器有 2 个重要参数:邻居个数与数据点之间距离的度量方法。在实践中,使用较小的邻居个数(比如 3 个或 5 个)往往可以得到比较好的结果,但你应该调节这个参数。选择合适的距离度量方法超出了本书的范围。默认使用 欧式距离,它在许多情况下的效果都很好。
k-NN 的优点:
缺点:
《Python机器学习基础教程》学习记录(四)