k近邻

强颜欢笑 提交于 2020-05-05 16:09:56
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import mglearn
  • k-NN算法是最简单的机器学习算法。构建模型只需要保存训练数据集即可。想要对新数据点进行预测,算法会在训练数据中找到最近的数据点,也就是它的“最近邻”。

  • k-NN算法最简单的版本只考虑一个最近邻,也就是与我们想要预测的点的数据点最近的训练数据点。预测结果就是这个训练数据点的已知输出。

mglearn.plots.plot_knn_classification(n_neighbors=1)

  • 除了考虑最近邻,还可以考虑任意个(k个)邻居。这也是k近邻算法名字的来历。在考虑多于一个邻居的情况时,我们用投票法来指定标签。对于每个测试点,我们数一数多少个邻居分别属于什么类别,将出现次数多的邻居的类别(k个近邻中占多数的类别)作为预测结果。
mglearn.plots.plot_knn_classification(n_neighbors=3)

# 使用scikit-learn实现k近邻算法

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)

# 设置邻居为3
clf = KNeighborsClassifier(n_neighbors=3)

# 开始训练
clf.fit(X_train, y_train)

# 预测
print("Test set prediction: {}".format(clf.predict(X_test)))

# 评估模型
print("Test set accuracy:{:.2f}".format(clf.score(X_test, y_test)))
Test set prediction: [1 0 1 0 1 0 0]
Test set accuracy:0.86
  • 分析KNeighborsClassifier
    • 对于二维数据集,我们还可以在xy平面上画出所有可能的测试点的预测结果。我们根据平面中每个点所属的类别对平面进行着色。这样可以查看决策边界(decision boundary),即算法对类别0和1的分界线
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)

  • 从第一张图可以看出,使用单一邻居绘制的决策边界紧跟着训练数据。随着邻居越来越多,决策边界也越来越平滑。更平滑的边界对应更简单的模型。换句话说,使用更少的邻居对应更高的模型复杂度,使用更多的邻居对应更低的模型复杂度。假如考虑极端情况,即邻居个数等于训练集中所有数据点的个数,那么每个测试点的邻居都完全相同(即所有训练点),所有预测结果也完全相同(即训练集中出现次数更多的类别)
# 利用乳腺癌数据集对不同的邻居个数的训练集和测试集的性能进行评估

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_beighbors取值从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()
![](https://img2020.cnblogs.com/blog/1736534/202005/1736534-20200504151503745-1025351025.png)
  • 图像的x轴是n_neighbors,y轴是训练集精度和测试集精度。虽然现实世界的图像很少有非常平滑的,但我们仍可以看出过拟合和欠拟合的一些特征(注意,由于更少的邻居对应更复杂的模型,x轴左边代表过拟合,右边代表欠拟合)。仅考虑单一近邻时,训练集上的预测结果十分完美。但随着邻居个数的增多,模型变得更简单,训练集精度也随之下降。单一邻居时的测试集精度比使用更多邻居时要低,这表示单一近邻的模型过于复杂。与之相反,当考虑10个邻居时,模型又过于简单,性能甚至变得更差。最佳性能在中间的某处,邻居个数大约为6。

  • k近邻回归

# k=1
mglearn.plots.plot_knn_regression(n_neighbors=1)

k=3
mglearn.plots.plot_knn_regression(n_neighbors=3)

# 使用scikit-learn构建k近邻回归算法

from sklearn.neighbors import KNeighborsRegressor

X, y = mglearn.datasets.make_wave(n_samples=40)

# 分割数据
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)

# 模型实例化,将邻居设置为3
reg = KNeighborsRegressor(n_neighbors=3)

# 训练
reg.fit(X_train, y_train)

# 预测
print("Test set predictions:\n{}".format(reg.predict(X_test)))

print("Test set R^2: {:.2f}".format(reg.score(X_test, y_test)))
  • 对于回归问题,score方法返回的是R^2 分数。R^2 分数也叫决定系数,是回归模型预测的优度度量,位于0到1之间。R^2 等于1对应完美预测,R^2 等于0对应常数模型,即总是预测训练集响应(y_train)的平均值

    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 R^2: 0.83


# 分析KNeighborsRegressor

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):
    # 使用不同的邻居个数进行训练
    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')

  • 从图中可以看出,仅使用单一邻居,训练集中的每个点都对预测结果有显著影响,预测结果的图像经过所有数据点。这导致预测结果非常不稳定。考虑更多的邻居之后,预测结果变得更加平滑,但对训练数据的拟合也不好。

kNN总结

  • 一般来说,KNeighbors分类器有两个重要参数:邻居个数与数据点之间距离的度量方法。在实践中,使用较少的邻居个数(比如3个或5个)往往可以得到比较好的结果,但是应该调节这个参数。距离度量默认使用欧式距离k-NN的优点之一就是模型很容易理解,通常不需要过多调节就可以得到不错的性能。构建最近邻模型的速度通常很快,但如果训练集很大(特征数很多或者样本数很大),预测速度可能会比较慢。使用k-NN算法时,对数据预处理是很重要的。这一算法对于很多特征(几百或更多)的数据集往往效果不好,对于大多数特征的大多数取值为0的数据集(洗漱数据集)来说,效果尤其不好。
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!