目录
算法简介[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
其参数如下:
<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>
-
n_neighbors
:即为K-近邻算法
中的K值,默认值是5,它的意思是,取几个最近的邻居来参考,假如K=5
就表示要预测的数据放到训练的数据里面,然后取5个最近的邻居,根据这5个邻居中分类情况,占比最高的那个分类名作为最终预测结果。因此,n_neighbors
取值一般为奇数,因为偶数可能存在50%的情况。 - 其他参数均为默认即可。但需要知道以下几个参数含义
-
algorithm
:sklearn中实现KNN算法的具体操作是有很多方式,当=auto
时,会自动根据fit
进来的数据选择最高效率的算法。 -
metric
: 默认参数表示的是使用明可夫斯基距离进行运算,明可夫斯基距离
是对前两种算法的更优推广。当前面一个参数p=1
时,使用的距离测算方法为曼哈顿距离,当p=2
时,使用的是欧氏距离。
算法实例步骤
数据获取
kaggle 是一个专门提供机器学习和深度学习数据集的网站,同时也是一个竞赛社区,可以使用官方提供的数据进行竞赛。
本次使用的是Facebook官方给出的虚拟数据,根据用户手机签到的位置坐标等信息,预测出可推荐该用户的周边业务。这样的意义就是帮助企业给用户推送附近吃喝玩乐的项目。
-
数据下地址:https://www.kaggle.com/c/facebook-v-predicting-check-ins/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>
-
数据的其他下地址:【新青年TALKS】回复“KNN”
读取数据
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>
-
row_id
: 每行数据的索引,该列数据不影响预测结果,可以删除。 -
x
、y
:表示坐标位置,根据xy的值判断位置 -
accuracy
:定位准确度,是否定位准确也影响最终结果 -
time
:数据上传时间戳 -
place_id
:位置结果,也是目标值
数据处理
我的个人思路:
-
row_id
是个非特征值,不影响位置结果,先删除。 -
place_id
的数量只有2次以下出现的可能是错误数据,我们可以将place_id<2
的删除。 -
time
是个时间戳类型,需要转化成年月日等,增加特征值信息。
这是个人的数据初步处理的思路,每个人的理解不一样,可以按照自己的理解进行处理,机器学习的处理过程没有标准答案。
- 将
place_id
出现次数小于两次的数据删除
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>
- **删除
row_id
这列 **
data = data.drop(columns=['row_id'])
- 转化
time
出更多特征:日、时、周
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
三者的区别:
-
fit
: 就是将数据传入,然后计算出会用到的平均值、方差等等数据,但仅仅是计算不进行其他操作。官方解释原文如下:
Method calculates the parameters μ and σ and saves them as internal objects.
-
fit_transform
:在计算出需要数据后,根据已确定的特征处理方法、算法进行数据处理。 -
transform
:根据之前已传入的数据标准(方差、平均值等)进行数据转化(降维、标准化、归一化等)。
因此,在将训练集的特征值进行标准化后,必须将测试集的特征值进行标准化,并且只用使用训练集的数据标准对其进行处理,否则标准不一致会导致预测不准确。人和数据都不能双标。
模型算法
knn = KNeighborsClassifier(n_neighbors=5)knn.fit(x_train,y_train)y_predict = knn.predict(x_test) # 预测测试集结果
-
n_neighbors
:即为k近邻算法的k值,默认是5,k近邻算法是最简单的算法,原因就在于算法只有n_neighbors
一个超参数
,而其他算法中不仅包含超参数还有算法内部的其他参数,无论什么参数,都会影响到预测结果的准确性。 -
在sklearn的所有算法api中都有一个
score
方法,用来评判算法的准确率。
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>
交叉验证本质上是一种模型验证方式,为了让模型准确率更加可信,会采用交叉验证的方式进行验证。它的基本原理是:
- 将训练集分为几个等分(具体根据自己需求而定)
- 将其中某一等分作为验证集
- 这里的验证集和之后的测试集是两回事,验证集是目的是为了验证不同参数的可信效果。
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>
- k折交叉验证意味着一共会进行k次验证,每次取的数据部分都不相同,每次都会得出一个模型准确率,最终的准确率为平均值。
- 根据每次设置不同参数的交叉验证结果,调整超参数,并选择最优参数。
网格搜索
网格搜出多与交叉验证共同使用,以获得一个最优参数。
-
API:
from sklearn.model_selection import GridSearchCV
-
参数含义:
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>
-
estimator
:估计器,即为你的算法模型,本例为KNN。 -
param_grid
:算法参数值,格式为字典格式。 -
cv
: 交叉验证的折数。 -
其他参数默认即可。
grid = {'n_neighbors':[3,5,9,11]} # 要尝试的参数gv = GridSearchCV(knn,param_grid=grid,cv=10) # 一般为10折交叉验证gv.fit(x_train,y_train)print(gv.best_params_) # 显示出最优参数print(gv.best_score_) # 显示最高的准确率
至此,你也完成了一个机器学习项目。
算法优劣
优点
- 简单易用,参数少
- 可用性高,不仅适用分类还适用回归
缺点
- 性能差,每来一个新数据都需要重新进行步骤,并且本质上是通过测试的特征值放到训练集内一个个进行距离计算的,所以会大量消耗内存并且时间极长,以至于我写完这篇文档也没跑出结果。
- 当样本不平衡时,对新出现的数据预测不准确。
结尾
无论是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>
网友评论