前言

KNN算法和鸢尾花都是机器学习入门的算法和案例

最近刚好学习到了这里,就写一下,随便梳理一下思绪

KNN算法

K Nearest Neighbor算法又叫KNN算法,这个算法是机器学习里面一个比较经典的算法, 总体来说KNN算法是相对比较容易理解的算法

  • 定义

如果一个样本在特征空间中的k个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别。

而KNN算法的关键是K值的选取点距离的计算

距离的计算

要度量空间中点距离的话,有好几种度量方式,比如常见的曼哈顿距离计算,欧式距离计算.切比雪夫距离等等。不过通常KNN算法中使用的是欧式距离,这里只是简单说一下

这样我们就明白了如何计算距离,KNN算法最简单粗暴的就是将预测点与所有点距离进行计算,然后保存并排序,选出前面K个值看看哪些类别比较多

k值的选取

通过交叉验证,可以得出一个比较合适的k值,来让被评估的模型更加准确可信

鸢尾花案例

而鸢尾花案例是验证KNN算法的一个经典案例,鸢尾花的数据集数据量较少只有150条,而且数据较为规范,很适合用于验证模型和算法,还有机器学习的完整流程

关于数据集的具体介绍:

机器学习的完整流程为:

  1. 获取数据集
  2. 数据基本处理
  3. 特征工程
  4. 机器学习(模型训练)
  5. 模型评估

而使用鸢尾花数据集和knn算法进行模型训练代码如下

from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier

# 1.获取数据集
# 1.1获取数据
iris = load_iris()

# 2.数据基本处理
# 2.1数据分割
x_train, x_test, y_train, y_test = train_test_split(iris.data, iris.target, test_size=0.2)

# 3.特征工程
# 3.1实例转换器
transfer = StandardScaler()
# 3.2 调用方法进行标准化
x_train = transfer.fit_transform(x_train)
x_test = transfer.fit_transform(x_test)

# 4.机器学习(模型训练)
# 4.1实例化估计器
estimator = KNeighborsClassifier(n_neighbors=5)
# 4.2模型训练
estimator.fit(x_train, y_train)

# 5.模型评估
# 5.1输出预测值
y_pre=estimator.predict(x_test)
print('预测值是',y_pre)

print("预测值和真实值对比: \n" ,y_pre==y_test)
#5.2输出准确率
ret=estimator.score(x_test,y_test)
print("准确率是",ret)

运行结果:

预测值是 [2 0 1 2 0 0 2 2 1 1 2 1 1 2 0 0 2 0 1 2 1 1 1 1 0 0 2 1 0 2]
预测值和真实值对比:
[ True True True True True True True True True True False True
True True True True False True True True True True True True
True True True True True True]
准确率是 0.9333333333333333

(注:每次运行的结果都会不一致,因为数据分割时的随机数种子和样本取样的原因)

代码流程讲解

代码基本流程为:

1.加载数据集

2.对数据进行分割

使用train_test_split()函数进行切分,传入样本的特征集(train_data)和样本的目标值(train_target),以及样本占比(test_size),还有随机数种子(random_state)等参数,这样得出4个返回值,而这四个返回值的顺序是不能改变的,分别为:

x_train:训练集特征值

x_test:测试集特征值

y_train:训练集目标值

y_test::测试集目标值

3.特征工程预处理

将数据集转换为机器易懂的语言,使用sklearn.preprocessing下的StandardScaler进行数据标准化处理,还有一个MinMaxScaler这个是数据的归一化,不过因为鲁棒性与局限性太大,一般只适用于传统精确小数据场景数据处理上,一般都不用归一化处理数据,下面简单提一下归一化和标准化的区别

归一化

归一化处理数据的基本公式为:

mark

mx和mi是指归一化中返回的指定区间,默认为[0,1]

在使用 MinMaxScaler构造转换器是,需要传入feature_range参数来自定义划分mx和mi的值

使用演示:

dating.txt展示

milage,Liters,Consumtime,target
40920,8.326976,0.953952,3
14488,7.153469,1.673904,2
26052,1.441871,0.805124,1
75136,13.147394,0.428964,1
38344,1.669788,0.134296,1
72993,10.141740,1.032955,1
35948,6.830792,1.213192,3
42666,13.276369,0.543880,3
67497,8.631577,0.749278,1
......
from  sklearn.preprocessing import MinMaxScaler,StandardScaler
import pandas as pd
data=pd.read_csv('./data/dating.txt')

transfer=MinMaxScaler(feature_range=(0,1))

MinMax_data=transfer.fit_transform(data.iloc[:,:3])
print("归一化处理后: \n",MinMax_data)

运行结果:

归一化处理后: 
[[0.44832535 0.39805139 0.56233353]
[0.15873259 0.34195467 0.98724416]
[0.28542943 0.06892523 0.47449629]
...
[0.29115949 0.50910294 0.51079493]
[0.52711097 0.43665451 0.4290048 ]
[0.47940793 0.3768091 0.78571804]]
标准化

而标准化的公式为:

mark

mean为平均值,σ为标准差,这样的公式计算可以避免异常值的出现对于标准化结果产生较大的影响

归一化和标准化的对比

  • 对于归一化来说:如果出现异常点,影响了最大值和最小值,那么结果显然会发生改变
  • 对于标准化来说:如果出现异常点,由于具有一定数据量,少量的异常点对于平均值的影响并不大,从而方差改变较小。

对于上述测试文本文本数据进行标准化处理:

from  sklearn.preprocessing import MinMaxScaler,StandardScaler
import pandas as pd
data=pd.read_csv('./data/dating.txt')

transfer1=StandardScaler()
stander_data=transfer1.fit_transform(data.iloc[:,:3])
print("标准化处理后: \n",stander_data)

运行结果为:

标准化处理后: 
[[ 0.33193158 0.41660188 0.24523407]
[-0.87247784 0.13992897 1.69385734]
[-0.34554872 -1.20667094 -0.05422437]
...
[-0.32171752 0.96431572 0.06952649]
[ 0.65959911 0.60699509 -0.20931587]
[ 0.46120328 0.31183342 1.00680598]]

可以看成与归一化结果有较大不同

在一般进行特征工程预处理时都是使用标准化而不使用归一化

4.模型训练与模型评估

当我们处理好我们需要的数据集的时候,这个时候就需要实例化一个估计器来训练模型

使用knn算法的api来实现这一点

api主要有这三个

KNeighborsClassifier()实例化估计器
fit()训练模型
predict()得出预测值

我们使用

KNeighborsClassifier()实例化估计器的时候,需要传入我们的k值,也就是我们的n_neighbors这个参数,默认是5,

还有一个algorithm参数:{‘auto’,‘ball_tree’,‘kd_tree’,‘brute’}

  • 快速k近邻搜索算法,默认参数为auto,可以理解为算法自己决定合适的搜索算法。除此之外,用户也可以自己指定搜索算法ball_tree、kd_tree、brute方法进行搜索,
    • brute是蛮力搜索,也就是线性扫描,当训练集很大时,计算非常耗时。
    • kd_tree,构造kd树存储数据以便对其进行快速检索的树形数据结构,kd树也就是数据结构中的二叉树。以中值切分构造的树,每个结点是一个超矩形,在维数小于20时效率高。
    • ball tree是为了克服kd树高纬失效而发明的,其构造过程是以质心C和半径r分割样本空间,每个节点是一个超球体。

具体了解knn算法的一些细节可以阅读这篇文章https://www.cnblogs.com/pinard/p/6061661.html,本文更多的是讲代码实现

关于k值的选取,我们一般取一个较小的k值,然后通过交叉验证的方式来选择最优的K值

而如果k值过小会容易受到异常点的影响,而k值过大,会受到样本均衡的影响,当k值完全等于样本书n时,则完全没有进行分类,模型过于的简单

一般k值做为超参数都是由我们手动输入的,而在我们自己输入的过程中,我们可以使用交叉验证的方式来多次验证我们的k值的准确性

在上述的鸢尾花案例中,是没有进行交叉验证k值的,上述案例的k值=5是我直接输入的,在下文调优过程中,将会使用交叉验证k值

让我们回到之前的代码,当我们传入n_neighbors=5时,我们的估计器就已经实例化完成了

下面我们就需要进行模型的训练

使用fit()方法,将我们的训练集数据的目标值(x_train)和训练集的特征值(y_train)传入我们实例化好的估计器中

再使用predict()方法,传入我们之前数据切分中,得到的测试集的特征值(x_test),我们就能从模型中,得到预测值y_pre

然后在接下来的模型评估中,将我们的预测值y_pre和真实值y_test进行对比就能得出我们预测的值与真实值的对比情况

在接下来的模型准确率输出中,我们是使用score函数进行输出结果的

使用score函数,将我们的测试集的特征值(x_test)和测试集的目标值(y_test)进行传入来验证模型的准确率

鸢尾花案例交叉验证调优

使用交叉验证需要使用

from sklearn.model_selection import GridSearchCV

这个库

这个库中的参数主要有

  • estimator:估计器对象
  • param_grid:估计器参数(dict){“n_neighbors”:[1,3,5]}
  • cv:指定几折交叉验证

以及结果分析:

  • bestscore__:在交叉验证中验证的最好结果
  • bestestimator:最好的参数模型
  • cvresults:每次交叉验证后的验证集准确率结果和训练集准确率结果

使用交叉验证的鸢尾花案例代码如下:

from sklearn.datasets import load_iris
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import GridSearchCV

iris = load_iris()

x_train, x_test, y_train, y_test = train_test_split(iris.data, iris.target, train_size=0.2)

transfer = StandardScaler()
x_train = transfer.fit_transform(x_train)
x_test = transfer.fit_transform(x_test)

estimator = KNeighborsClassifier()

param_dict = {"n_neighbors": [1, 3, 5, 7, 9]}
estimator = GridSearchCV(estimator, param_grid=param_dict, cv=5)

estimator.fit(x_train, y_train)

y_pre = estimator.predict(x_test)
print("对比预测结果和真实值: \n", y_pre == y_test)

score = estimator.score(x_test, y_test)
print("准确率 \n", score)

print("在交叉验证中验证的最好结果:\n", estimator.best_score_)
print("最好的参数模型:\n", estimator.best_estimator_)
print("每次交叉验证后的准确率结果:\n", estimator.cv_results_)

运行结果:

对比预测结果和真实值: 
[ True True True True True True True True True True True True
True True True True True True True True True True True True
True True True True True True False True True False True True
True True True True True True True True True True True True
True False True True True True True True True True True True
True True True True True True True True True True True True
True True True True True True True True True True True False
True True True True True True True True True True True True
True True True True True True True True True True False True
True False True True True True True True True True True True]
准确率
0.95
在交叉验证中验证的最好结果:
1.0
最好的参数模型:
KNeighborsClassifier(n_neighbors=1)
每次交叉验证后的准确率结果:
{'mean_fit_time': array([0.00040565, 0.00019965, 0.00059824, 0.00019937, 0.00079994]), 'std_fit_time': array([0.00049692, 0.0003993 , 0.00048846, 0.00039873, 0.00074752]), 'mean_score_time': array([0.00118971, 0.0007977 , 0.00079665, 0.00079784, 0.00119519]), 'std_score_time': array([0.0004004 , 0.00039885, 0.00039834, 0.00039892, 0.00074483]), 'param_n_neighbors': masked_array(data=[1, 3, 5, 7, 9],
mask=[False, False, False, False, False],
fill_value='?',
dtype=object), 'params': [{'n_neighbors': 1}, {'n_neighbors': 3}, {'n_neighbors': 5}, {'n_neighbors': 7}, {'n_neighbors': 9}], 'split0_test_score': array([1., 1., 1., 1., 1.]), 'split1_test_score': array([1. , 0.66666667, 0.66666667, 0.66666667, 0.66666667]), 'split2_test_score': array([1. , 0.83333333, 0.83333333, 0.83333333, 1. ]), 'split3_test_score': array([1., 1., 1., 1., 1.]), 'split4_test_score': array([1., 1., 1., 1., 1.]), 'mean_test_score': array([1. , 0.9 , 0.9 , 0.9 , 0.93333333]), 'std_test_score': array([0. , 0.13333333, 0.13333333, 0.13333333, 0.13333333]), 'rank_test_score': array([1, 3, 3, 3, 2])}

这样的展示能够给我们的模型训练结果添加可信度

本文大概就写到这里,本人水平有限,如果出现错误,欢迎指正