ML–K最近邻算法
主要涉及的要点如下:
- K最近邻算法的原理
- K最近邻算法在分类任务中的应用
- K最近邻算法在回归分析中的应用
- 使用K最近邻算法对酒的分类进行建模
一.K最近邻算法的原理
K最近邻算法的原理,正如–近朱者赤,近墨者黑。想象一个我们的数据集里面有一半是浅色的点,另一半是深色的点。现在我们有了一个新的数据点,颜色未知,怎样判断它属于哪一个分类呢?
对于K最近邻算法来说,这个问题就很简单:新数据点离谁最近,就和谁属于一类
看起来,K最近邻算真是够简单的,这么轻松完成了分类的工作。别急,我们还没说完。刚才只是举的最简单的例子,选的最近邻等于1。但如果我们在模型训练过程中让最近邻数等于1的话,那么非常可能会犯"一叶障目不见泰山
"的错误。试想一下,万一和新数据点最近的数据恰好是一个测定错误的点呢?
所以需要我们增加最近邻的数量,例如把最近邻数增加到3,然后让新数据点的分类和3个当中最多的数据点所处的分类保持一致
当我们令新数据点的最近邻数等于3的时候,也就是找出离新数据点最近的3个点,这时我们发现与新数据点距离最近的3个点中,有2个是深色,而只有1个是浅色。这样一来,K最近邻算法就会把新数据点放进深色的分类当中
以上就是K最近邻算法在分类任务中的基本原理,实际上K这个字母的含义就是最近邻的个数。在scikit-learn
中,K最近邻算法的K值是通过n_neighbors
参数来调节的,默认值是5
注意:K最近算法也可以用于回归,原理和其用于分类是相同的。当我们使用K最近邻回归计算某个数据点的预测值时,模型会选择离该数据点最近的若干个训练数据集中的点,并且将它们的y值取平均值,并把该平均值作为新数据点的预测值
二.K最近邻算法的用法
K最近邻算法在分类任务中的应用
在scikit-learn
中,内置了若干个玩具数据集(Toy Datasets),还有一些API让我们可以自己动手生成一些数据集。接下来我们会使用生成数据集的方式来进行展示,使用jupyter-notebook
输入代码如下:
# 导入数据集生成器
from sklearn.datasets import make_blobs
# 导入KNN分类器
from sklearn.neighbors import KNeighborsClassifier
# 导入画图工具
import matplotlib.pyplot as plt
# 导入数据集拆分工具
from sklearn.model_selection import train_test_split
# 生成样本数为200,分类为2的数据集
data=make_blobs(n_samples=200,centers=2,random_state=8)
X,y=data
# 将生成的数据集进行可视化
plt.scatter(X[:,0],X[:,1],c=y,cmap=plt.cm.spring,edgecolor='k')
plt.show()
在这段代码中,我们使用了scikit-learn
的make_blobs
函数来生成一个样本数量为200,分类数量为2的数据集,并将其赋值给X和y,然后我们用matplotlib
将数据用图形表示出来,运行代码,运行结果如下:
[结果分析] 从结果可以看出,make_blobs
生成的数据集一共有两类,其中一类用深色表示,而另外一类用浅色表示。这里可能有点疑惑:这不是已经进行好分类了吗?我们还需要K最近邻算法做什么呢?
答案是这样的—我们这里生成的数据集,可以看作机器学习的训练数据集,是已知的数据。我们就是基于这些数据用算法进行模型的训练,然后再对新的未知数据进行分类或者回归
下面我们就使用K最近邻算法来拟合这些数据,输入代码如下:
import numpy as np
clf=KNeighborsClassifier()
clf.fit(X,y)
# 下面的代码用于画图
x_min,x_max=X[:,0].min()-1,X[:,0].max()+1
y_min,y_max=X[:,1].min()-1,X[:,1].max()+1
xx,yy=np.meshgrid(np.arange(x_min,x_max,.02),np.arange(y_min,y_max,.02))
Z=clf.predict(np.c_[xx.ravel(),yy.ravel()])
Z=Z.reshape(xx.shape)
plt.pcolormesh(xx,yy,Z,cmap=plt.cm.Pastel1)
plt.scatter(X[:,0],X[:,1],c=y,cmap=plt.cm.spring,edgecolors='k')
plt.xlim(xx.min(),xx.max())
plt.ylim(yy.min(),yy.max())
plt.title("Classifier:KNN")
plt.show()
运行代码,结果如下:
[结果分析] K最近邻算法基于数据集创建了一个分类模型,就是图中粉色区域和灰色区域组成的部分。那么如果有新的数据输入的话,模型就会自动将数据分到对应的分类中
例如,我们假设有一个数据点,它的两个特征值分别是6.75和4.82,我们来试验下模型能不能将它放到正确的分类中,首先我们可以在上面那段代码中,plt.show()
之前加一行代码如下:
# 新的数据点用五星表示出来
plt.scatter(6.75,4.82,marker="*",c="red",s=200)
运行结果如下:
[结果分析] 五角星就代表了新的数据点所在的位置,可以看到k最近邻算法将它放在了下方的区域,和浅色的数据点归为了一类
下面我们再验证一下,输入代码如下:
# 对新数据点分类进行判断
print("代码运行结果:")
print("====================")
print("新数据点的分类是:",clf.predict([[6.75,4.82]]))
print("====================")
代码运行结果:
====================
新数据点的分类是: [1]
====================
[结果分析] 看起来,K最近邻算法的工作成果还是很不错的,不过这可能是因为我们这次的任务有点太简单了。下面我们给它增加一点难度—处理多元分类任务
K最近邻算法处理多元分类任务
接下来,我们要先生成多元分类任务所使用的数据集,为了让难度足够大,这次我们通过修改make_blobs
的centers
参数,把数据类型的数量增加到5个,同时修改n_samples
参数,把样本量也增加到500个,输入代码如下:
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt
data2=make_blobs(n_samples=500,centers=5,random_state=8)
X2,y2=data2
# 用散点图将数据集进行可视化
plt.scatter(X2[:,0],X2[:,1],c=y2,cmap=plt.cm.spring,edgecolors='k')
plt.show()
运行结果如下:
[结果分析]:从上图可以看到,新的数据集的分类数量变成了5个,而其中有两类数据还有一些重合(图片中心位置的点)
让我们再次用K最近邻算法建立模型来拟合这些数据,输入代码如下:
from sklearn.neighbors import KNeighborsClassifier
clf=KNeighborsClassifier()
clf.fit(X2,y2)
# 下面的代码用于画图
x_min,x_max=X2[:,0].min()-1,X2[:,0].max()+1
y_min,y_max=X2[:,1].min()-1,X2[:,1].max()+1
xx,yy=np.meshgrid(np.arange(x_min,x_max,.02),np.arange(y_min,y_max,.02))
Z=clf.predict(np.c_[xx.ravel(),yy.ravel()])
Z=Z.reshape(xx.shape)
plt.pcolormesh(xx,yy,Z,cmap=plt.cm.Pastel1)
plt.scatter(X2[:,0],X2[:,1],c=y2,cmap=plt.cm.spring,edgecolors='k')
plt.xlim(xx.min(),xx.max())
plt.ylim(yy.min(),yy.max())
plt.title("Classifier:KNN")
plt.show()
运行结果如下:
[结果分析] K最近邻算法仍然可以把大部分数据点放置于正确的分类中,但有一小部分数据还是放入了错误的分类中,这些分类错误的数据点基本都是互相重合的位于图像中心位置的数据点
那么模型的正确率究竟有多高?我们用下面的代码来进行一下评分:
print("代码运行结果:")
print("====================")
print("模型正确率:{:.2f}".format(clf.score(X2,y2)))
print("====================")
运行结果:
代码运行结果:
====================
模型正确率:0.96
====================
K最近邻算法用于回归分析
在scikit-learn
的数据集生成器,有一个非常好的用于回归分析的数据集生成器,make_regression
函数,这里我们使用make_regression
生成数据集来进行实验,演示K最近邻算法在回归分析中的表现
首先我们还是先生成数据集,代码如下:
# 导入make_regression数据生成器
from sklearn.datasets import make_regression
# 生成特征数量为1,噪音为50的数据集
X,y=make_regression(n_features=1,n_informative=1,noise=50,random_state=8)
# 用散点图将数据点进行可视化
plt.scatter(X,y,c='orange',edgecolors='k')
plt.show()
为了方便画图,我们选择样本的特征数量仅为1个,同时为了增加难度。我们添加标准差为50的noise,运行代码:
下面我们使用K最近邻算法来进行回归分析,输入代码如下:
# 导入用于回归分析的KNN模型
from sklearn.neighbors import KNeighborsRegressor
reg=KNeighborsRegressor()
#用KNN模型拟合数据
reg.fit(X,y)
# 把预测结果用图像进行可视化
z=np.linspace(-3,3,200).reshape(-1,1)
plt.scatter(X,y,c='orange',edgecolors='k')
plt.plot(z,reg.predict(z),c='k',linewidth=3)
# 向图像添加标题
plt.title("KNN Regressor")
plt.show()
运行代码:
[结果分析] 黑色的曲线代表的就是K最近邻算法拟合make_regression
生成数据所进行的模型。直观来看,模型的拟合程度并不是很好,有大量的数据点都没有被模型覆盖到
现在我们尝试给模型进行评分,看看结果如何,输入代码如下:
print("代码运行结果:")
print("====================")
print("模型评分:{:.2f}".format(reg.score(X,y)))
print("====================")
运行代码:
代码运行结果:
====================
模型评分:0.77
====================
[结果分析] 模型的得分只有0.77,这是一个差强人意的结果。为了提高模型的分数,我们将K最近邻算法的近邻数进行调整。用于默认的情况下,K最近邻算法的n_neighbors
为5,我们尝试将它减少
输入代码如下:
from sklearn.neighbors import KNeighborsRegressor
# 减少模型的n_neighbors参数为2
reg2=KNeighborsRegressor(n_neighbors=2)
reg2.fit(X,y)
# 重新进行可视化
plt.scatter(X,y,c='orange',edgecolors='k')
plt.plot(z,reg2.predict(z),c='k',linewidth=3)
plt.title("KNN Regression:n_neighbors=2")
plt.show()
我们将K最近邻算法的n_neighbors
参数降低为2,再次运行代码,结果如下图:
[结果分析] 相对于上图来说,黑色曲线更加积极地视图覆盖更多的数据点,也就是说,模型变得更复杂
再次进行评分,运行结果:
代码运行结果:
====================
模型评分:0.86
====================
[结果分析] 模型的评分从0.77提升到了0.86,可以说是有明显的提升
三.K最近邻算法项目实战–酒的分类
1.对数据集进行分析
我们将使用scikit-learn
内置的酒数据集来进行实验,这个数据集也包含在scikit-learn
的datasets
模块当中
首先,我们要把酒的数据集载人项目中,输入代码如下:
from sklearn.datasets import load_wine
# 从sklearn的datasets模块载人数据集
wine_dataset=load_wine()
我们可能比较好奇这个酒数据集中的数据都包含些什么。实际上,使用load_wine
函数载人的酒数据集,是一种Bunch
对象,它包括键(keys)和数值(values),下面我们查看一个就数据集都有哪些键,代码如下:
print("红酒数据集中的键:
{}".format(wine_dataset.keys()))
红酒数据集中的键:
dict_keys(['data', 'target', 'target_names', 'DESCR', 'feature_names'])
[结果分析] 酒数据集中包括数据’data’,目标分类’target’,目标分类名称’target_names’,数据描述’DESCR’,以及特征变量的名称’feature_names’
那么这个数据集中究竟有多少样本(samples),又有多少变量(features)呢?可以使用shape
告诉我们数据的大概轮廓,代码如下:
print("数据概况:{}".format(wine_dataset['data'].shape))
数据概况:(178, 13)
更细节的信息,我们可以通过打印DESCR
键来获得,代码如下:
# 打印酒的数据集中的简短描述
print(wine_dataset['DESCR'])
.. _wine_dataset:
Wine recognition dataset
------------------------
**Data Set Characteristics:**
:Number of Instances: 178 (50 in each of three classes)
:Number of Attributes: 13 numeric, predictive attributes and the class
:Attribute Information:
- Alcohol
- Malic acid
- Ash
- Alcalinity of ash
- Magnesium
- Total phenols
- Flavanoids
- Nonflavanoid phenols
- Proanthocyanins
- Color intensity
- Hue
- OD280/OD315 of diluted wines
- Proline
- class:
- class_0
- class_1
- class_2
[结果分析] 酒数据集中的178个样本被归纳3个类别中,分别是class_0
,class_1
和class_2
,其中class_0
中包含59个样本,class_1
中包含71个样本,class_2
中包含48个样本。而从Alcohol至Proline分别是13个特征变量,包括酒精含量,苹果酸,镁含量,青花素含量,色彩饱和度等
2.生成训练数据集和测试数据集
在我们创建一个能够自动将酒进行分类的机器学习的算法模型之前,先要能够对模型的可信度进行评判,否则我们无法知道它对于新的酒所进行的分类是否准确
所以我们现在要做的工作是,把数据集分为两个部分:一部分称为训练数据集;另一部分称为测试数据集。训练数据集就好比我们缝制衣服时所用到的模特的身材,而测试数据集则是用来测试这件衣服,对于别人来说究竟有多合身的模特
在scikit-learn
中,有一个train_test_split
函数,它是用来帮助用户把数据集拆分的工具,其工作原理是:train_test_split
函数将数据集进行随机排列,在默认情况下将其中75%的数据及所对应的标签划归到训练数据集,并将其余25%的数据和所对应的标签划归到测试数据集
注意:我们一般用大写的X表示数据的特征,而用小写的y表示数据对应的标签。这是因为X是一个二维数组,也称为矩阵;而y是一个一维数组,或者说是一个向量
接下来,我们使用train_test_split
函数将酒的数据集中的数据分为训练数据集和测试数据集。代码如下:
# 导入数据集拆分工具
from sklearn.model_selection import train_test_split
# 将数据集拆分为训练数据集和测试数据集
X_train,X_test,y_train,y_test=train_test_split(wine_dataset['data'],wine_dataset['target'],random_state=0)
此时,我们已经对酒数据集完成了拆分。在上述代码中,我们看到了一个参数称为random_state
,并且我们将它指定为0,这是因为train_test_split
函数会生成一个伪随机数,并根据这个伪随机数对数据集进行拆分。而我们有时候需要在一个项目中,让多次生成的伪随机数相同,方法就是通过固定random_state
参数的数值,相同的random_state
参数会一直生成同样的伪随机数,但当这个值我们设为0,或者保持缺省的时候,则每次生成的伪随机数均不同
下面查看一个train_test_split
函数拆分后的数据集大概是什么样的:
print("X_train shape:{}".format(X_train.shape))
print("X_test shape:{}".format(X_test.shape))
print("y_train shape:{}".format(y_train.shape))
print("y_test shape:{}".format(y_test.shape))
X_train shape:(133, 13)
X_test shape:(45, 13)
y_train shape:(133,)
y_test shape:(45,)
[结果分析] 在训练数据集中,样本X数量和其对应的标签y数量均为133个,约占样本总量的74.7%,而测试数据集中的样本X数量和标签y数量均为45个,约占样本总数的25.3%。同时,无论是在训练数据集中,还是在测试数据集中,特征变量都是13个
3.使用K最近邻算法进行建模
K最近邻算法根据训练数据集进行建模,在训练数据集中寻找和新输入的数据最近的数据点,然后把这个数据点的标签分配给新的数据点,以此对新的样本进行分类
# 导入KNN分类模型
from sklearn.neighbors import KNeighborsClassifier
# 指定模型的n_neighbors参数值为1
knn=KNeighborsClassifier(n_neighbors=1)
在scikit-learn
中,机器学习模块都是在其固定的类中运行的,而K最近邻算法是在neighbors
模块中的KNeighborsClassifier
类中运行。而我们从一个类中创建对象的时候,就需要给模型指定一个参数。对于KNeighborsClassifier
类来说,最关键的参数就是近邻的数量,也就是n_neighbors
。而knn则是我们在KNeighborsClassifier
类中创建的一个对象
接下来我们要使用这个叫作knn的对象中称为"拟合(fit)
"的方法来进行建模,建模的依据就是训练数据集中的样本数据X-train和其对应的标签y_tranin,所以我们输入代码如下:
# 用模型对数据进行拟合
knn.fit(X_train,y_train)
print(knn)
KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
metric_params=None, n_jobs=None, n_neighbors=1, p=2,
weights='uniform')
4.使用模型对新样本进行预测
现在我们可以使用刚刚建好的模型对新的样本分类进行预测了,不过在这之前,先用测试数据集对模型进行打分。测试数据集并不参与建模,但是我们可以用模型对测试数据集进行分类,然后和测试数据集中的样本实际分类进行对比,看吻合度有多高。吻合度越高,模型的得分越高,说明模型的预测越准确,满分是1.0
print("测试数据集得分:{:.2f}".format(knn.score(X_test,y_test)))
测试数据集得分:0.76
下面假设我们得到了一瓶新的酒,它的特征变量值:
Alcohol | 13.2 |
---|---|
Malic acid | 2.77 |
Ash | 2.51 |
Alcalinity of ash | 18.5 |
Magnesium | 96.6 |
Total phenols | 1.04 |
Flavanoids | 2.55 |
Nonflavonoid phenols | 0.57 |
Proanthocyanins | 1.47 |
Color intensity | 6.2 |
Hue | 1.05 |
OD280/OD315 of diluted wines | 3.33 |
Proline | 820 |
现在我们用建好的模型对新酒做出分类预测,输入代码:
import numpy as np
# 输入新的数据点
X_new=np.array([[13.2,2.77,2.51,18.5,96.6,1.04,2.55,0.57,1.47,6.2,1.05,3.33,820]])
# 使用predict 进行预测
prediction=knn.predict(X_new)
print("预测新红酒的分类为:{}".format(wine_dataset['target_names'][prediction]))
预测新红酒的分类为:['class_2']
[结果分析] 模型把新酒的分类预测为class_2
,虽然准确率只有76%,但对于我们的项目来说,还是相当不错的
四.小结
K最近邻算法在实际使用当中会有很多问题,例如它需要对数据集认真地进行预处理,对规模超大的数据集拟合的时间较长,对高维数据集拟合欠佳,以及对于稀疏数据集束手无策等