k近邻算法实例

2021-03-14  本文已影响0人  李星河110

目录

算法简介[1]通俗解释算法实现原理谁才是“我”的邻居?sklearn的KNN算法算法实例步骤数据获取读取数据数据处理特征工程模型算法交叉验证与网格搜索交叉验证(cross-validation)k折交叉验证网格搜索算法优劣优点缺点结尾

算法简介[1]

在模式识别领域中,最近邻居法KNN算法,又译K-近邻算法)是一种用于分类回归非参数统计方法[1]。在这两种情况下,输入包含特征空间(Feature Space)中的k个最接近的训练样本。

通俗解释

模型对样本数据进行处理,新数据的样本特征到达模型里面之后,根据附近的几个样本特征的结果判断新数据的类型。举个例子:你是个图书管理员,图书馆新到了一本《红楼梦》,你脑子里对图书的分类多种多样,你根据了解到的《红楼梦》的一些特征,比如爱情、小说、明清,再决定放到哪一栏架子上去,这本质上就是通过新数据的特征去旧集合里找类似,然后归类。

算法实现原理

谁才是“我”的邻居?

既然是根据附近的样本判断新数据所属结果,那么就一定存在一个它的距离公式,无论什么库的KNN算法,原理上是采用下面这几个公式来测算距离:

image-20210313105022132

<figcaption style="line-height: inherit; margin: 0px; padding: 0px; margin-top: 10px; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em;">image-20210313105022132</figcaption>

在KNN中,通过计算对象间距离来作为各个对象之间的非相似性指标,避免了对象之间的匹配问题,在这里距离一般使用欧氏距离或曼哈顿距离,在sklearn中,默认使用欧式距离计算,曼哈顿距离因为是取了差值的绝对值,所有也称为绝对值距离

根据机器学习库中自有的算法,即可计算出谁才是“我”真正邻居。

sklearn的KNN算法

在sklearn中提供了KNN算法的api:sklearn.neighbors.KNeighborsClassifier 其参数如下:

image

<figcaption style="line-height: inherit; margin: 0px; padding: 0px; margin-top: 10px; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em;"></figcaption>

算法实例步骤

数据获取

kaggle 是一个专门提供机器学习和深度学习数据集的网站,同时也是一个竞赛社区,可以使用官方提供的数据进行竞赛。

本次使用的是Facebook官方给出的虚拟数据,根据用户手机签到的位置坐标等信息,预测出可推荐该用户的周边业务。这样的意义就是帮助企业给用户推送附近吃喝玩乐的项目。

读取数据

import pandas as pd from sklearn.neighbors import KNeighborsClassifierdata = pd.read_csv('/Users/xinghe/OneDrive/machine learning/kneighbors/train.csv')print(data.info())  # 查看数据基本信息print(data.isnull().any()) #检查是否有缺失值data.head(10)
image

<figcaption style="line-height: inherit; margin: 0px; padding: 0px; margin-top: 10px; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em;"></figcaption>

数据处理

我的个人思路:

  1. row_id 是个非特征值,不影响位置结果,先删除。
  2. place_id 的数量只有2次以下出现的可能是错误数据,我们可以将place_id&lt;2 的删除。
  3. time 是个时间戳类型,需要转化成年月日等,增加特征值信息。

这是个人的数据初步处理的思路,每个人的理解不一样,可以按照自己的理解进行处理,机器学习的处理过程没有标准答案。

new_data = data.groupby('place_id').count() # 分组计数new_data = new_data[new_data.row_id > 2].reset_index() #将大于2的取出data = data[data.place_id.isin(new_data.place_id)] # 取出大于2的数据data
image

<figcaption style="line-height: inherit; margin: 0px; padding: 0px; margin-top: 10px; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em;"></figcaption>

data = data.drop(columns=['row_id'])
time = pd.to_datetime(data['time'],unit='s') # 将时间戳转为年-月-日-时-分格式time = pd.DatetimeIndex(time) # 将日期转为字典格式data.loc[:,'day'] = time.day # 新增特征“日”data.loc[:,'hour'] = time.hour # 新增特征“时”data.loc[:,'week'] = time.weekday # 新增特征“周”data.drop(columns='time') # 删除原有时间戳列data.head(10) 
image

<figcaption style="line-height: inherit; margin: 0px; padding: 0px; margin-top: 10px; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em;"></figcaption>

from sklearn.model_selection import train_test_splity = data['place_id'] # 选择目标值x = data.drop(columns=['place_id']) #选择特征值x_train,x_test,y_train,y_test = train_test_split(x,y,test_size=0.3) #划分训练集与测试集x_test

特征工程

标准化让数据落在规范区间内,这种特征处理称为无量纲化

from sklearn.preprocessing import StandardScalerstd = StandardScaler()x_train = std.fit_transform(x_train) # 将训练集的特征值标准化x_test = std.transform(x_test) # 将测试集的特征值标准化

关于这段代码有以下两个问题:

questions:        1. 标准化这一步为什么不能和上一步分割数据步骤进行交换?        2. 为什么第二次使用 transform 而不是 fit_transform ?

问题1

在我初次接触机器学习时也犯过同样错误,找我往期教程仍能看到:

image-20210314095546723

<figcaption style="line-height: inherit; margin: 0px; padding: 0px; margin-top: 10px; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em;">image-20210314095546723</figcaption>

在进行无量纲化时,如果对整个数据集无论是特征值还是目标值,不管测试集还是训练集,都统一用一个计算标准进行标准化,那么这一步操作是多余的,因为大家都变了等于没变。因此问题2也变得好理解起来。

问题2

理解原因之前还需要知道fit fit_transform transform 三者的区别:

Method calculates the parameters μ and σ and saves them as internal objects.

因此,在将训练集的特征值进行标准化后,必须将测试集的特征值进行标准化,并且只用使用训练集的数据标准对其进行处理,否则标准不一致会导致预测不准确。人和数据都不能双标。

模型算法

knn = KNeighborsClassifier(n_neighbors=5)knn.fit(x_train,y_train)y_predict = knn.predict(x_test) # 预测测试集结果
knn = KNeighborsClassifier(n_neighbors=5)knn.fit(x_train,y_train)knn.score(x_test,y_test)

score 需要两个参数,翻译成人类语言就是:用测试集的特征值去预测出结果再跟真实的结果去对比,算出准确率。

交叉验证与网格搜索

交叉验证(cross-validation)

目的[2]

学习预测函数的参数,并在相同数据集上进行测试是一种错误的做法: 一个仅给出测试用例标签的模型将会获得极高的分数,但对于尚未出现过的数据它则无法预测出任何有用的信息。 这种情况称为 overfitting(过拟合)

因此才需要进行训练集与测试集的数据分割,下面是加入交叉验证后的流程:

image

<figcaption style="line-height: inherit; margin: 0px; padding: 0px; margin-top: 10px; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em;"></figcaption>

交叉验证本质上是一种模型验证方式,为了让模型准确率更加可信,会采用交叉验证的方式进行验证。它的基本原理是:

  1. 将训练集分为几个等分(具体根据自己需求而定)
  2. 将其中某一等分作为验证集
  3. 这里的验证集和之后的测试集是两回事,验证集是目的是为了验证不同参数的可信效果。

k折交叉验证

将训练集分为K个等分,就称为K折交叉验证,K折交叉验证流程如下:

k折交叉验证

<figcaption style="line-height: inherit; margin: 0px; padding: 0px; margin-top: 10px; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em;">k折交叉验证</figcaption>

网格搜索

网格搜出多与交叉验证共同使用,以获得一个最优参数。

至此,你也完成了一个机器学习项目。

算法优劣

优点

  1. 简单易用,参数少
  2. 可用性高,不仅适用分类还适用回归

缺点

  1. 性能差,每来一个新数据都需要重新进行步骤,并且本质上是通过测试的特征值放到训练集内一个个进行距离计算的,所以会大量消耗内存并且时间极长,以至于我写完这篇文档也没跑出结果。
  2. 当样本不平衡时,对新出现的数据预测不准确。

结尾

无论是sklearn还是tensorflow,都只是人工智能的一个工具,尽管像如上一样写代码怎么看都像调包侠,但是机器学习与深度学习乐趣并非在此,如果想通过自己之手重写算法模型,大可学习C++或者golang。

机器学习对我而言的最大乐趣在于每次的调参、完善特征工程,以获得更高准确率的过程,并且通过对算法模型的原理了解,对人类学习的认知也会有新的理解。作为工具的sklearn,即使使用的人不同,同一份数据,不同的人根据自己的理解进行的特征工程也会使得最种的准确率不同。不信可以看看kaggle上这份Facebook竞赛的排名:

image-20210314150502926

<figcaption style="line-height: inherit; margin: 0px; padding: 0px; margin-top: 10px; text-align: center; color: rgb(153, 153, 153); font-size: 0.7em;">image-20210314150502926</figcaption>

准确率基本都是接近的,因为算法就那么几种,天花板在特征工程和调参上。

江湖上将这些反复调整算法参数、每次都在训练数据的程序员们称为炼丹师

「新青年TALKS」回复knn下载本文档、元数据和教程源码

参考:

[1]:维基百科-KNN算法 https://zh.wikipedia.org/wiki/K-%E8%BF%91%E9%82%BB%E7%AE%97%E6%B3%95
<small class="footnote" id="footnote-2" style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">[2]: scikit-learn官方文档 https://scikit-learn.org/stable/modules/cross_validation.html</small>

上一篇下一篇

猜你喜欢

热点阅读