R機器學習:分類算法之K最鄰進算法(KNN)的原理與實現(xiàn)

從今天開始給大家寫機器學習算法,這個東西并不是大多數(shù)人想象的那么高深恢口,也不是說編程的人,搞計算機的人才能學習使用穷躁,醫(yī)學領(lǐng)域耕肩、社會科學領(lǐng)域的研究越來越多運用機器學習的,在我的理解中每個人都應(yīng)該掌握基本的機器學習思想和基本的編程能力问潭。

這個系列的第一篇文章從簡單的分類算法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最近鄰算法是一種分類算法狡忙,算法思想是在數(shù)據(jù)集中找到與樣本最相似的K個樣本梳虽,如果這K個樣本中的大多數(shù)屬于某一類別,則該樣本也屬于某一類別灾茁。

用例子說明:現(xiàn)在有3個爬行動物窜觉,其中兩個是毒蛇分別叫做:grass snake and the adder是复,一個是蟲子叫做slow worm。這個蟲子和蛇長得挺像竖螃,很多人都會把它們搞混。

現(xiàn)在你參與了一個項目:去調(diào)查一片草地上到底這三種動物到底有多少個逗余,在實地調(diào)查的時候你只能通過動物的身長Body length和攻擊性aggression這兩個指標來對其的類型做出判斷特咆,你和一個動物專家已經(jīng)收集了一些動物數(shù)據(jù)(已經(jīng)有帶標簽的數(shù)據(jù)集了),現(xiàn)在你自己想訓練一個KNN模型對動物進行自動分類录粱。

你收集的數(shù)據(jù)可視化如下圖:

image

圖中有每個爬行動物在身長和攻擊性兩個維度的值腻格,XX就是待分類的動物的身長和攻擊性,我們現(xiàn)在需要用KNN模型去判斷這三個X分別是哪一類啥繁。

KNN算法是如何做的呢?

對于每一個X菜职,算法會去計算這個X到周圍已知點的距離,比如對最上面的X我們計算距離可以如下(所有的線就代表歐幾里得距離):

image

距離有了之后我們對距離進行排序:

image

然后我們?nèi)∏癒個最近距離的點(這個K是自己設(shè)定的旗闽,叫做超參數(shù))酬核,然后這個K個點中類別占比最多的那一類就是我們的X的所屬類別。

我們還是把上面的步驟在例子中給大家走一遍:

我把K分別設(shè)定為1适室,3嫡意,5,就是說我希望我新收集的X的類別和它最鄰近的1捣辆,3蔬螟,5個爬行動物的類別一樣。

那么整個分類過程就如下圖:

image

從圖中可以看出:當K為1時3個X都被KNN認為是grass snake汽畴,當K為3時3個X被KNN模型分為了3個不同的類別旧巾,當K為5時我們的3個X被分為了2個不同的類別。所以忍些,K會影響KNN算法的表現(xiàn)鲁猩,K不同算法結(jié)果不同。

有沒有人好奇如果我把K設(shè)置為4然后就找了前4個最近的已知樣本坐昙,2個是A類绳匀,兩個是B類,A和B打了個平局炸客,這個時候X到底是哪一類呢疾棵?

這個時候,如果我們的分類問題本身是一個兩分類問題我們一定記住把K設(shè)置為奇數(shù)痹仙,就可以避免平局的出現(xiàn)是尔,如果為多分類問題,算法會直接進行隨機分开仰,因為實際操作中K個鄰居中有類別數(shù)量等同的情形還是比較少的拟枚,對整體結(jié)果不會產(chǎn)生實際影響薪铜。

最后給大家回顧一下KNN的實現(xiàn)過程:

  • 構(gòu)建一個已經(jīng)分類好的數(shù)據(jù)集。
  • 計算一個新樣本與數(shù)據(jù)集中所有數(shù)據(jù)的距離恩溅。
  • 按照距離大小進行遞增排序隔箍。
  • 選取距離最小的K個樣本。
  • 確定前K個樣本所在類別出現(xiàn)的頻率脚乡,并輸出出現(xiàn)頻率最高的類別蜒滩。

KNN建模實操

寫完原理,我們繼續(xù)用一個例子給大家寫實際操作奶稠,假如你是一個醫(yī)生俯艰,手上有很多糖尿病病人的診斷數(shù)據(jù),你現(xiàn)在想建立一個KNN模型實現(xiàn)對新病人的診斷锌订,診斷結(jié)果有3類:healthy, chemically diabetic, overtly diabetic竹握。

一個很簡單的3分類問題哈。

我們的數(shù)據(jù)長這樣:

image

4個變量辆飘,第一個變量就是分類結(jié)局啦辐,取值可以為non-diabetic (Normal),chemically

diabetic (Chemical), overtly diabetic (Overt)

訓練模型之前我們可以先畫圖看看數(shù)據(jù)分布:

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()
image

上面的圖是不同的自變量組合下的病人類別分布劈猪,其實可以看出來僅僅使用glucose和insulin兩個變量就可較好地區(qū)分三種類別了昧甘,不過在本例中我們依然使用所有的預(yù)測變量進行模型的訓練,我們用到的包是非常經(jīng)典的mlr包战得,第一步是定義學習任務(wù)makeClassifTask充边,第二部是定義學習器makeLearner,第三步就是訓練模型了常侦,具體代碼如下:

library(mlr)
diabetesTask <- makeClassifTask(data = diabetesTib, target = "class")
knn <- makeLearner("classif.knn", par.vals = list("k" = 2))
listLearners()$class#看究竟有多少學習器
knnModel <- train(knn, diabetesTask)

knnPred <- predict(knnModel, newdata = diabetesTib)

解釋一下上面的代碼過程:我們是用diabetesTib數(shù)據(jù)集訓練的模型浇冰,然后我們做預(yù)測的時候也是用的同樣的數(shù)據(jù)集。

同樣的數(shù)據(jù)集本來是有標簽的(糖尿病類型)聋亡,然后我們訓練的KNN模型會給它在預(yù)測一個標簽肘习,這個時候我們就可以進行真標簽和預(yù)測標簽的比較,就可以形成一個performance metrics坡倔,這個中文叫做性能指標漂佩。

image

性能指標可以用如下代碼得到:

performance(knnPred, measures = list(mmce, acc))

代碼后面的list就是你想要的指標,這兒我想要的是mean misclassification error和accuracy罪塔,其余的性能指標見下圖:

image

運行上面的代碼就可以得到指標的值:

image

可以看到我們的模型表現(xiàn)很好投蝉,畢竟是預(yù)測的本身的數(shù)據(jù)集嘛。

偏差-方差的權(quán)衡bias-variance trade-off

接下來給大家介紹偏方權(quán)衡這個概念征堪,這個在機器學習中很重要瘩缆,在所有的算法中我們都會存在矛盾的兩面,一個是欠擬合underfitted佃蚜,一個是過擬合overfitted庸娱,所帶來的后果就是算法學習不好或者外推行太差着绊,欠擬合造成的問題就是學習不足,會造成偏差熟尉,過擬合造成的問題是模型對新數(shù)據(jù)預(yù)測不穩(wěn)定归露,會造成方程過大,所以我們訓練模型的時候一定要注意兩者的權(quán)衡斤儿,這個就叫偏差-方差的權(quán)衡靶擦。

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

image

用上圖來說明就是,隨著模型復(fù)雜度的提高雇毫,方差越大,偏差越小踩蔚,我們要找的最優(yōu)模型一定是平衡可方差和偏差之后的模型棚放。

那么具體到我們上面例子,我們的K越小就越可能最近的K個中噪聲所占的比例越大馅闽,越容易過擬合飘蚯,K越大則越容易欠擬合。

寫到這問題就又來了福也,這個方差和偏差的度怎么把握呢局骤?

這就引出來了交叉驗證這個方法。

交叉驗證

在我們的例子中暴凑,我用我的數(shù)據(jù)訓練好一個KNN峦甩,然后我又在我原來的數(shù)據(jù)中來看我模型的表現(xiàn),這不是扯淡嘛现喳,結(jié)果肯定不會差啊凯傲。

甚至說結(jié)果你不滿意,你可以使你的模型更為復(fù)雜以至于最終達到能正確分類所有的數(shù)據(jù)嗦篱,完全做得到的冰单,但是這個時候你過擬合了呀,來一批新數(shù)據(jù)肯定就慘不忍睹灸促。

所以說我們評估機器學習的模型表現(xiàn)一定要在未知數(shù)據(jù)中進行诫欠。

未知數(shù)據(jù)怎么來?

再去搜集浴栽?

不用那么麻煩荒叼,常規(guī)的操作就是把原來的數(shù)據(jù)集進行劃分,一部分來訓練吃度,一部分來測試甩挫。然后根據(jù)模型在測試集中的表現(xiàn)我們就可以對模型進行評價了。

上面的過程就叫做交叉驗證cross-validation (CV),這個是每一個有監(jiān)督的學習過程中必須的過程椿每,無監(jiān)督的學習沒法做伊者,因為它沒有l(wèi)abeled data.

通過交叉驗證我們就知道某一個模型表現(xiàn)的具體情況英遭,如果表現(xiàn)的令人滿意,那么我們就用完整數(shù)據(jù)集再訓練一遍亦渗,這樣就得到了最終的模型挖诸,一個機器學習過程也就完成了。

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).

交叉驗證具體怎么做呢法精?繼續(xù)往下看多律。

為了加深大家的理解,我們對剛剛訓練的模型進行交叉驗證搂蜓,我們就用簡單交叉驗證(還有留一驗證和K折驗證狼荞,之后給大家寫)

簡單驗證很好理解,就是把原始數(shù)據(jù)集隨機劃分為一個訓練集一個測試集帮碰,比例可以自己設(shè)相味,一般是7:3

image

簡單驗證的代碼如下:

holdout <- makeResampleDesc(method = "Holdout", split = 7/10,stratify = TRUE)
holdoutCV <- resample(learner = knn, task = diabetesTask,
                      resampling = holdout, measures = list(mmce, acc))

上面的代碼中第一個holdout是生存交叉驗證的數(shù)據(jù)集,按照7:3劃分殉挽,其中令stratify為T保證了類別之間的平衡丰涉,第二個對象就是我們在劃分出來的訓練集中跑的KNN模型。

同樣的我們得到驗證后模型的mean misclassification error和accuracy

image

可以看到模型的mmce變大了斯碌,而acc變小了一死,很好理解,因為多了一個驗證的過程傻唾,實現(xiàn)了偏差和方差的權(quán)衡投慈,這個模型比剛剛的模型肯定外推性更好。

混淆矩陣

掌握了驗證的方法之后冠骄,我其實還想具體看看我這個KNN究竟錯在哪里逛裤。

我想知道訓練的這個KNN究竟幫助我把具體的類別分類對了多少,錯了多少猴抹,我們把對的錯的列成矩陣带族,就叫做混淆矩陣confusion matrix

對剛剛的例子,我們可以通過如下代碼形成混淆矩陣:

calculateConfusionMatrix(holdoutCV$pred, relative = TRUE)
image

可以看到蟀给,我們的混淆矩陣中既有絕對數(shù)又有占比蝙砌,很棒,我剛剛訓練的KNN分類錯了共5+1+6=11個病人跋理,其中這個KNN模型對overt這一類病人分類效果似乎并不太好择克。

超參數(shù)

剛剛寫的所有的東西都是在K為2的基礎(chǔ)上進行的,我也在開篇就說了K會影響模型的表現(xiàn)前普,但是K到底取幾好呢肚邢?

這兒接著引入超參數(shù)的概念,很多機器學習算法需要我們提前指定某些參數(shù),比如KNN的K骡湖,聚類算法中的類別數(shù)等等贱纠,指定好了之后,其他的參數(shù)就是算法自己在數(shù)據(jù)中去學習了响蕴,像這種算法根據(jù)數(shù)據(jù)自己學習的參數(shù)就是我們常常說的參數(shù)谆焊,而我們?nèi)藶橹付ǖ膮?shù)叫做超參數(shù):

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.

超參數(shù)可以根據(jù)常識選,或者一個一個試浦夷,今天給大家寫hyperparameter tuning辖试,中文翻譯為超參調(diào)試,對于我們的例子劈狐,如果我們設(shè)定K很小罐孝,那么類別很容易受到單個噪聲的影響,如果K很大肥缔,就體現(xiàn)不出來數(shù)據(jù)的特征肾档,所以我們需要進行自動化的調(diào)試,調(diào)試的目標就是找到那個交叉驗證表現(xiàn)最好的模型對應(yīng)的K辫继。

首先,我們需要設(shè)定超參取值空間和取值方式:

knnParamSpace <- makeParamSet(makeDiscreteParam("k", values = 1:10))
gridSearch <- makeTuneControlGrid()

然后我們用K折驗證進行不同超參模型的評價:

cvForTuning <- makeResampleDesc("RepCV", folds = 10, reps = 20)

最后就是進行整個調(diào)試過程:

tunedK <- tuneParams("classif.knn", task = diabetesTask,
                     resampling = cvForTuning,
                     par.set = knnParamSpace, control = gridSearch)
image

結(jié)果顯示K為7的時候模型表現(xiàn)最好俗慈,當然了姑宽,這個超參調(diào)試的過程我們也可以進行可視化

knnTuningData <- generateHyperParsEffectData(tunedK)
plotHyperParsEffect(knnTuningData, x = "k", y = "mmce.test.mean",
plot.type = "line") +
theme_bw()
image

那么,我們現(xiàn)在就知道我們的K應(yīng)該是7闺阱,我們就用k=7重新訓練我們的KNN模型:

tunedKnn <- setHyperPars(makeLearner("classif.knn"),
par.vals = tunedK$x)
tunedKnnModel <- train(tunedKnn, diabetesTask)

到這兒炮车,我們的整個KNN的機器學習過程就算完成了,一個好的模型肯定要應(yīng)用嘛酣溃,接下來我們繼續(xù)看使用模型進行未知數(shù)據(jù)的預(yù)測

用訓練好的模型做預(yù)測

現(xiàn)在我們來模擬一些新病人瘦穆,比如3個病人吧,他們的glucose赊豌,insulin扛或,sspg分別如下:

newDiabetesPatients <- tibble(glucose = c(82, 108, 300),
insulin = c(361, 288, 1052),
sspg = c(200, 186, 135))

我們將這些新病人喂給我們的模型,得到預(yù)測這些病人患糖尿病的結(jié)果:

newPatientsPred <- predict(tunedKnnModel, newdata = newDiabetesPatients)
getPredictionResponse(newPatientsPred)

運行上面的代碼我們便可以得到模型給出的預(yù)測結(jié)果:

image

看到?jīng)]碘饼,3個病人KNN告訴我們熙兔,兩個沒糖尿病,1個是Overt類型的糖尿病艾恼。

好了住涉,寫到這兒,相信同學們都會KNN了钠绍,也許這是你接觸的第一個機器學習模型舆声,很簡單吧,關(guān)注我柳爽,之后的文章中我會一一給大家拆解各種各樣的機器學習算法媳握。

小結(jié)

今天給大寫了KNN的原理和和實現(xiàn)碱屁,以及一些常見通用的機器學習知識,感謝大家耐心看完毙芜,自己的文章都寫的很細忽媒,代碼都在原文中,希望大家都可以自己做一做腋粥,請關(guān)注后私信回復(fù)“數(shù)據(jù)鏈接”獲取所有數(shù)據(jù)和本人收集的學習資料晦雨。如果對您有用請先收藏,再點贊轉(zhuǎn)發(fā)隘冲。

也歡迎大家的意見和建議闹瞧,大家想了解什么統(tǒng)計方法都可以在文章下留言,說不定我看見了就會給你寫教程哦展辞。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末奥邮,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子罗珍,更是在濱河造成了極大的恐慌洽腺,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件覆旱,死亡現(xiàn)場離奇詭異蘸朋,居然都是意外死亡,警方通過查閱死者的電腦和手機扣唱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門藕坯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人噪沙,你說我怎么就攤上這事炼彪。” “怎么了正歼?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵辐马,是天一觀的道長。 經(jīng)常有香客問我局义,道長齐疙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任旭咽,我火速辦了婚禮贞奋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘穷绵。我一直安慰自己轿塔,他們只是感情好,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著勾缭,像睡著了一般揍障。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上俩由,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天毒嫡,我揣著相機與錄音,去河邊找鬼幻梯。 笑死兜畸,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的碘梢。 我是一名探鬼主播咬摇,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼煞躬!你這毒婦竟也來了肛鹏?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤恩沛,失蹤者是張志新(化名)和其女友劉穎在扰,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體雷客,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡芒珠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了佛纫。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡总放,死狀恐怖呈宇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情局雄,我是刑警寧澤甥啄,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站炬搭,受9級特大地震影響蜈漓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜宫盔,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一融虽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧灼芭,春花似錦有额、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽茴迁。三九已至,卻和暖如春萤衰,著一層夾襖步出監(jiān)牢的瞬間堕义,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工脆栋, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留倦卖,地道東北人。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓筹吐,卻偏偏與公主長得像糖耸,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子丘薛,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容