本篇文章承接上篇,链接:监督学习算法——一些样本的数据集
k近邻
1.k近邻分类
k-NN算法最简单的版本只考虑一个最近邻,也就是与我们想要的数据点最近的训练数据点。预测结果就是这个训练数据点的已知输出。给出了这种分类方法forge数据集上的应用:
mglearn.plots.plot_knn_classification(n_neighbors=1)
这里我们添加了三个新数据点,对于每个新数据点,我们标记了训练集中与他最近的点,单一最近邻算法所测结果就是那个点的标签(对应五角星的颜色)
除了考虑临近,还可以考虑任一个(k个)邻居,这也是k近邻算法名字的来历。在考虑多余一个邻居的情况时,我们用投票法来指定标签。也就是说,对于每一个测试点,我们数一数多少个邻居属于类别0,多少个邻居属于类别1。
mglearn.plots.plot_knn_classification(n_neighbors=3)
预测结果可以从五角星的颜色看出,你可以发现,左上角新数据点的预测结果与只用一个邻居时的预测结果不同。
虽然这张图对应的是一个二分类问题,但方法同样适用于多分类的数据集。对于多分类问题,我们数一数每个类别分别有多少个邻居,然后将最后最常见类别作为预测结果。
from sklearn.model_selection import train_test_split
X,y=mglearn.datasets.make_forge()
X_train,X_test,y_train,y_test=train_test_split(X,y,random_state=0)
from sklearn.neighbors import KNeighborsClassifier
clf=KNeighborsClassifier(n_neighbors=3)
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]
print("Test set accuracy:{:.2f}".format(clf.score(X_test,y_test)))
Test set accuracy:0.86
2.分析KNeighborsClassifier
对于二位数据集,我们还可以在xy平面上画出所有可能的测试点的预测结果。我们根据平面中每个点所属的类别对平面进行着色。这样可以查看决策边界,即算法对类别0和类别1的分界线。
fig,axes=plt.subplots(1,3,figsize=(10,3))
for n_neighbors,ax in zip([1,3,9],axes):
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("{}neighbors(s)".format(n_neighbors))
ax.set_xlabel("feature 0")
ax.set_ylabel("feature 1")
axes[0].legend(loc=3)
<matplotlib.legend.Legend at 0x27a8ae5f070>
从左图可以看出,使用单一邻居绘制的决策边界紧跟着训练数据。随着邻居个数越来越多,决策边界也越来越平滑。更平滑的边界对应更简单的模型。换句话说,使用更少的邻居对应更高的模型复杂度,而使用更多的邻居对应更低的模型复杂度。加入考虑极端情况,即邻居个数等于训练集中所有数据点的个数,那么每个测试点的邻居完全相同,所有预测结果也完全相同。
我们来研究一下能否证实之前讨论过的模型复杂度和泛化能力之间的关系。我们将在现实世界的乳腺癌数据集上继续进行研究,先将数据集划分为训练集和测试集,然后用不同的邻居个数对训练集和测试集的性能进行评估。
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=[]
neighbors_settings=range(1,11)
for n_neighbors in neighbors_settings:
clf=KNeighborsClassifier(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()
<matplotlib.legend.Legend at 0x27a8ae2ed10>
图像的x轴是n_neighbors,y轴是训练集精度和测试集精度。虽然现实世界的图像很少有非常平滑的,但我们仍然可以看出过拟合与欠拟合的一些特征。仅考虑单一近邻时,训练集上的预测结果十分完美。但随着邻居个数增多,模型也变得更简单,训练集精度也随之下降。单一邻居时的测试集精度比使用更多邻居时要低,这表示单一近邻的模型过于复杂。与之相反,当考虑10个邻居时,模型又过于简单,性能甚至会变得更差。最佳的性能约为88%的精度,这个结果仍然可以接受。
3.k近邻回归
mglearn.plots.plot_knn_regression(n_neighbors=1)
mglearn.plots.plot_knn_regression(n_neighbors=3)
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)
reg=KNeighborsRegressor(n_neighbors=3)
reg.fit(X_train,y_train)
print("Test set predictions:{}\n".format(reg.predict(X_test)))
Test set predictions:[-0.05396539 0.35686046 1.13671923 -1.89415682 -1.13881398 -1.63113382
0.35686046 0.91241374 -0.44680446 -1.13881398]
print("Test set R^2:{:.2f}".format(reg.score(X_test,y_test)))
Test set R^2:0.83
4.分析KNeighborsRegressor
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
line = np.linspace(-3, 3, 1000).reshape(-1, 1)
#创建1000个数据点,在-3和3之间分布
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, '^', c=mglearn.cm2(0), 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()
从图中可以看出,只使用单一邻居,训练集中的每个点都对预测结果有显著影响,预测结果的图像经过所有数据点。这倒是预测结果非常不稳定。考虑更多的邻居之后,预测结果变得更加平滑,但对训练数据的拟合也不好。
5.优点、缺点和参数
一般来说,KNeighbors分类器有两个重要参数:邻居个数和数据点之间的距离的度量方法。在实践中,使用较小的邻居个数往往可以得到比较好的结果,但你应该调节这个参数。
可以使用欧氏距离,它在默认情况下,效果都很好。
k-NN的优点之一就是模型很容易理解,通常不需要过多调节就可以得到不错的性能。使用k-NN算法,对数据进行预处理很重要的。这一算法对于有很多特征的数据集往往效果不好,
对于大多数特征的大多数取值都为0的数据集(所谓的稀疏数据集)来说,这一算法的效果尤其不好。