R机器学习:分类算法之K最邻进算法(KNN)的原理与实现

74 篇文章 363 订阅

从今天开始给大家写机器学习算法,这个东西并不是大多数人想象的那么高深,也不是说编程的人,搞计算机的人才能学习使用,医学领域、社会科学领域的研究越来越多运用机器学习的,在我的理解中每个人都应该掌握基本的机器学习思想和基本的编程能力。

这个系列的第一篇文章从简单的分类算法KNN开始:这个算法真的非常的简单,简单到初中生都可以掌握,所以大家一定要有信心:

kNN is arguably the simplest machine learning algorithm. In spite of its simplicity, kNN can provide surprisingly good classification performance, and its simplicity makes it easy to interpret.

KNN算法思想解释

K最近邻算法是一种分类算法,算法思想是在数据集中找到与样本最相似的K个样本,如果这K个样本中的大多数属于某一类别,则该样本也属于某一类别。

用例子说明:现在有3个爬行动物,其中两个是毒蛇分别叫做:grass snake and the adder,一个是虫子叫做slow worm。这个虫子和蛇长得挺像,很多人都会把它们搞混。

现在你参与了一个项目:去调查一片草地上到底这三种动物到底有多少个,在实地调查的时候你只能通过动物的身长Body length和攻击性aggression这两个指标来对其的类型做出判断,你和一个动物专家已经收集了一些动物数据(已经有带标签的数据集了),现在你自己想训练一个KNN模型对动物进行自动分类。

你收集的数据可视化如下图:

 

图中有每个爬行动物在身长和攻击性两个维度的值,XX就是待分类的动物的身长和攻击性,我们现在需要用KNN模型去判断这三个X分别是哪一类。

KNN算法是如何做的呢?

对于每一个X,算法会去计算这个X到周围已知点的距离,比如对最上面的X我们计算距离可以如下(所有的线就代表欧几里得距离):

 

 

距离有了之后我们对距离进行排序:

 

 

然后我们取前K个最近距离的点(这个K是自己设定的,叫做超参数),然后这个K个点中类别占比最多的那一类就是我们的X的所属类别。

我们还是把上面的步骤在例子中给大家走一遍:

我把K分别设定为1,3,5,就是说我希望我新收集的X的类别和它最邻近的1,3,5个爬行动物的类别一样。

那么整个分类过程就如下图:

 

 

从图中可以看出:当K为1时3个X都被KNN认为是grass snake,当K为3时3个X被KNN模型分为了3个不同的类别,当K为5时我们的3个X被分为了2个不同的类别。所以,K会影响KNN算法的表现,K不同算法结果不同。

有没有人好奇如果我把K设置为4然后就找了前4个最近的已知样本,2个是A类,两个是B类,A和B打了个平局,这个时候X到底是哪一类呢?

这个时候,如果我们的分类问题本身是一个两分类问题我们一定记住把K设置为奇数,就可以避免平局的出现,如果为多分类问题,算法会直接进行随机分,因为实际操作中K个邻居中有类别数量等同的情形还是比较少的,对整体结果不会产生实际影响。

最后给大家回顾一下KNN的实现过程:

  • 构建一个已经分类好的数据集。
  • 计算一个新样本与数据集中所有数据的距离。
  • 按照距离大小进行递增排序。
  • 选取距离最小的K个样本。
  • 确定前K个样本所在类别出现的频率,并输出出现频率最高的类别。

KNN建模实操

写完原理,我们继续用一个例子给大家写实际操作,假如你是一个医生,手上有很多糖尿病病人的诊断数据,你现在想建立一个KNN模型实现对新病人的诊断,诊断结果有3类:healthy, chemically diabetic, overtly diabetic。

一个很简单的3分类问题哈。

我们的数据长这样:

 

4个变量,第一个变量就是分类结局,取值可以为non-diabetic (Normal),chemically

diabetic (Chemical), overtly diabetic (Overt)

训练模型之前我们可以先画图看看数据分布:

<span style="color:#222222"><code>ggplot(diabetesTib, aes(glucose, insulin, col = class)) +
  geom_point() +
  theme_bw()
ggplot(diabetesTib, aes(sspg, insulin, col = class)) +
  geom_point() +
  theme_bw()
ggplot(diabetesTib, aes(sspg, glucose, col = class)) +
  geom_point()+
  theme_bw()</code></span>

 

上面的图是不同的自变量组合下的病人类别分布,其实可以看出来仅仅使用glucose和insulin两个变量就可较好地区分三种类别了,不过在本例中我们依然使用所有的预测变量进行模型的训练,我们用到的包是非常经典的mlr包,第一步是定义学习任务makeClassifTask,第二部是定义学习器makeLearner,第三步就是训练模型了,具体代码如下:

<span style="color:#222222"><code>library(mlr)
diabetesTask <- makeClassifTask(<span style="color:#114ba6">data</span> = diabetesTib, target = <span style="color:#00753b">"class"</span>)
knn <- makeLearner(<span style="color:#00753b">"classif.knn"</span>, par.vals = list(<span style="color:#00753b">"k"</span> = <span style="color:#a82e2e">2</span>))
listLearners()$<span style="color:#114ba6">class</span>#看究竟有多少学习器
knnModel <- train(knn, diabetesTask)

knnPred <- predict(knnModel, newdata = diabetesTib)</code></span>

解释一下上面的代码过程:我们是用diabetesTib数据集训练的模型,然后我们做预测的时候也是用的同样的数据集。

同样的数据集本来是有标签的(糖尿病类型),然后我们训练的KNN模型会给它在预测一个标签,这个时候我们就可以进行真标签和预测标签的比较,就可以形成一个performance metrics,这个中文叫做性能指标。

 

性能指标可以用如下代码得到:

<span style="color:#222222"><code>performance(knnPred, measures = <span style="color:#114ba6">list</span>(mmce, acc))</code></span>

代码后面的list就是你想要的指标,这儿我想要的是mean misclassification error和accuracy,其余的性能指标见下图:

 

运行上面的代码就可以得到指标的值:

 

可以看到我们的模型表现很好,毕竟是预测的本身的数据集嘛。

偏差-方差的权衡bias-variance trade-off

接下来给大家介绍偏方权衡这个概念,这个在机器学习中很重要,在所有的算法中我们都会存在矛盾的两面,一个是欠拟合underfitted,一个是过拟合overfitted,所带来的后果就是算法学习不好或者外推行太差,欠拟合造成的问题就是学习不足,会造成偏差,过拟合造成的问题是模型对新数据预测不稳定,会造成方程过大,所以我们训练模型的时候一定要注意两者的权衡,这个就叫偏差-方差的权衡。

bias-variance are also opposed to each other: somewhere between a model that underfits and has bias, and a model that overfits and has variance, is an optimal model that balances the biasvariance trade-off

 

 

用上图来说明就是,随着模型复杂度的提高,方差越大,偏差越小,我们要找的最优模型一定是平衡可方差和偏差之后的模型。

那么具体到我们上面例子,我们的K越小就越可能最近的K个中噪声所占的比例越大,越容易过拟合,K越大则越容易欠拟合。

写到这问题就又来了,这个方差和偏差的度怎么把握呢?

这就引出来了交叉验证这个方法。

交叉验证

在我们的例子中,我用我的数据训练好一个KNN,然后我又在我原来的数据中来看我模型的表现,这不是扯淡嘛,结果肯定不会差啊

甚至说结果你不满意,你可以使你的模型更为复杂以至于最终达到能正确分类所有的数据,完全做得到的,但是这个时候你过拟合了呀,来一批新数据肯定就惨不忍睹。

所以说我们评估机器学习的模型表现一定要在未知数据中进行。

未知数据怎么来?

再去搜集?

不用那么麻烦,常规的操作就是把原来的数据集进行划分,一部分来训练,一部分来测试。然后根据模型在测试集中的表现我们就可以对模型进行评价了。

上面的过程就叫做交叉验证cross-validation (CV),这个是每一个有监督的学习过程中必须的过程,无监督的学习没法做,因为它没有labeled data.

通过交叉验证我们就知道某一个模型表现的具体情况,如果表现的令人满意,那么我们就用完整数据集再训练一遍,这样就得到了最终的模型,一个机器学习过程也就完成了。

Once we have cross-validated our model and are happy with its performance, we then use all the data we have(including the data in the test set) to train the final model (because typically, the more data we train our model with, the less bias it will have).

交叉验证具体怎么做呢?继续往下看。

为了加深大家的理解,我们对刚刚训练的模型进行交叉验证,我们就用简单交叉验证还有留一验证和K折验证,之后给大家写)

简单验证很好理解,就是把原始数据集随机划分为一个训练集一个测试集,比例可以自己设,一般是7:3

 

简单验证的代码如下:

<span style="color:#222222"><code>holdout <- makeResampleDesc(method = <span style="color:#00753b">"Holdout"</span>, split = <span style="color:#a82e2e">7</span>/<span style="color:#a82e2e">10</span>,stratify = <span style="color:#114ba6">TRUE</span>)
holdoutCV <- resample(learner = knn, task = diabetesTask,
                      resampling = holdout, measures = <span style="color:#114ba6">list</span>(mmce, acc))</code></span>

上面的代码中第一个holdout是生存交叉验证的数据集,按照7:3划分,其中令stratify为T保证了类别之间的平衡,第二个对象就是我们在划分出来的训练集中跑的KNN模型。

同样的我们得到验证后模型的mean misclassification error和accuracy

 

可以看到模型的mmce变大了,而acc变小了,很好理解,因为多了一个验证的过程,实现了偏差和方差的权衡,这个模型比刚刚的模型肯定外推性更好。

混淆矩阵

掌握了验证的方法之后,我其实还想具体看看我这个KNN究竟错在哪里。

我想知道训练的这个KNN究竟帮助我把具体的类别分类对了多少,错了多少,我们把对的错的列成矩阵,就叫做混淆矩阵confusion matrix

对刚刚的例子,我们可以通过如下代码形成混淆矩阵:

<span style="color:#222222"><code>calculateConfusionMatrix(holdoutCV$pred, relative = <span style="color:#114ba6">TRUE</span>)</code></span>

 

 

可以看到,我们的混淆矩阵中既有绝对数又有占比,很棒,我刚刚训练的KNN分类错了共5+1+6=11个病人,其中这个KNN模型对overt这一类病人分类效果似乎并不太好。

超参数

刚刚写的所有的东西都是在K为2的基础上进行的,我也在开篇就说了K会影响模型的表现,但是K到底取几好呢?

这儿接着引入超参数的概念,很多机器学习算法需要我们提前指定某些参数,比如KNN的K,聚类算法中的类别数等等,指定好了之后,其他的参数就是算法自己在数据中去学习了,像这种算法根据数据自己学习的参数就是我们常常说的参数,而我们人为指定的参数叫做超参数:

k is what’s known as a hyperparameter: a variable or option that controls how a model makes predictions but is not estimated from the data.

超参数可以根据常识选,或者一个一个试,今天给大家写hyperparameter tuning,中文翻译为超参调试,对于我们的例子,如果我们设定K很小,那么类别很容易受到单个噪声的影响,如果K很大,就体现不出来数据的特征,所以我们需要进行自动化的调试,调试的目标就是找到那个交叉验证表现最好的模型对应的K。

首先,我们需要设定超参取值空间和取值方式:

<span style="color:#222222"><code><span style="color:#114ba6">knnParamSpace</span> <- makeParamSet(makeDiscreteParam(<span style="color:#00753b">"k"</span>, values = <span style="color:#a82e2e">1</span>:<span style="color:#a82e2e">10</span>))
gridSearch <- makeTuneControlGrid()</code></span>

然后我们用K折验证进行不同超参模型的评价:

<span style="color:#222222"><code>cvForTuning <span style="color:#114ba6"><- makeResampleDesc("RepCV", folds = <span style="color:#00753b">10,</span> reps = <span style="color:#00753b">20)</span></span></code></span>

最后就是进行整个调试过程:

<span style="color:#222222"><code>tunedK <span style="color:#114ba6"><- tuneParams("classif.knn", task = <span style="color:#00753b">diabetesTask,</span>
                     resampling = <span style="color:#00753b">cvForTuning,</span>
                     par.set = <span style="color:#00753b">knnParamSpace,</span> control = <span style="color:#00753b">gridSearch)</span></span></code></span>

 

 

结果显示K为7的时候模型表现最好,当然了,这个超参调试的过程我们也可以进行可视化

<span style="color:#222222"><code>knnTuningData <- generateHyperParsEffectData(tunedK)
plotHyperParsEffect(knnTuningData, x = <span style="color:#00753b">"k"</span>, y = <span style="color:#00753b">"mmce.test.mean"</span>,
plot.<span style="color:#114ba6">type</span> = <span style="color:#00753b">"line"</span>) +
theme_bw()</code></span>

 

 

那么,我们现在就知道我们的K应该是7,我们就用k=7重新训练我们的KNN模型:

<span style="color:#222222"><code><span style="color:#114ba6">tunedKnn</span> <- setHyperPars(makeLearner(<span style="color:#00753b">"classif.knn"</span>),
par.vals = tunedK<span style="color:#d96322">$x</span>)
tunedKnnModel <- train(tunedKnn, diabetesTask)</code></span>

到这儿,我们的整个KNN的机器学习过程就算完成了,一个好的模型肯定要应用嘛,接下来我们继续看使用模型进行未知数据的预测

用训练好的模型做预测

现在我们来模拟一些新病人,比如3个病人吧,他们的glucose,insulin,sspg分别如下:

<span style="color:#222222"><code><span style="color:#00753b">newDiabetesPatients</span> <span style="color:#00753b"><-</span> <span style="color:#00753b">tibble(glucose</span> <span style="color:#00753b">=</span> <span style="color:#00753b">c(82,</span> <span style="color:#a82e2e">108</span><span style="color:#00753b">,</span> <span style="color:#a82e2e">300</span><span style="color:#00753b">),</span>
<span style="color:#00753b">insulin</span> <span style="color:#00753b">=</span> <span style="color:#00753b">c(361,</span> <span style="color:#a82e2e">288</span><span style="color:#00753b">,</span> <span style="color:#a82e2e">1052</span><span style="color:#00753b">),</span>
<span style="color:#00753b">sspg</span> <span style="color:#00753b">=</span> <span style="color:#00753b">c(200,</span> <span style="color:#a82e2e">186</span><span style="color:#00753b">,</span> <span style="color:#a82e2e">135</span><span style="color:#00753b">))</span></code></span>

我们将这些新病人喂给我们的模型,得到预测这些病人患糖尿病的结果:

<span style="color:#222222"><code>newPatientsPred <span style="color:#114ba6"><- predict(tunedKnnModel, newdata = <span style="color:#00753b">newDiabetesPatients)</span>
getPredictionResponse(newPatientsPred)</span></code></span>

运行上面的代码我们便可以得到模型给出的预测结果:

 

 

看到没,3个病人KNN告诉我们,两个没糖尿病,1个是Overt类型的糖尿病。

好了,写到这儿,相信同学们都会KNN了,也许这是你接触的第一个机器学习模型,很简单吧,关注我,之后的文章中我会一一给大家拆解各种各样的机器学习算法

小结

今天给大写了KNN的原理和和实现,以及一些常见通用的机器学习知识,感谢大家耐心看完,自己的文章都写的很细,代码都在原文中,希望大家都可以自己做一做,请关注后私信回复“数据链接”获取所有数据和本人收集的学习资料。如果对您有用请先收藏,再点赞转发。

也欢迎大家的意见和建议,大家想了解什么统计方法都可以在文章下留言,说不定我看见了就会给你写教程哦。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

公众号Codewar原创作者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值