K鄰近(k-Nearest Neighbor,KNN)分類算法是最簡單的機器學習算法了。它採用測量不一樣特徵值之間的距離方法進行分類。它的思想很簡單:計算一個點A與其餘全部點之間的距離,取出與該點最近的k個點,而後統計這k個點裏面所屬分類比例最大的,則點A屬於該分類。node
下面用一個例子來講明一下:算法
電影名稱數組 |
打鬥次數數據結構 |
接吻次數dom |
電影類型機器學習 |
California Man函數 |
3性能 |
104學習 |
Romance測試 |
He’s Not Really into Dudes |
2 |
100 |
Romance |
Beautiful Woman |
1 |
81 |
Romance |
Kevin Longblade |
101 |
10 |
Action |
Robo Slayer 3000 |
99 |
5 |
Action |
Amped II |
98 |
2 |
Action |
簡單說一下這個數據的意思:這裏用打鬥次數和接吻次數來界定電影類型,如上,接吻多的是Romance類型的,而打鬥多的是動做電影。還有一部名字未知(這裏名字未知是爲了防止能從名字中猜出電影類型),打鬥次數爲18次,接吻次數爲90次的電影,它到底屬於哪一種類型的電影呢?
KNN算法要作的,就是先用打鬥次數和接吻次數做爲電影的座標,而後計算其餘六部電影與未知電影之間的距離,取得前K個距離最近的電影,而後統計這k個距離最近的電影裏,屬於哪一種類型的電影最多,好比Action最多,則說明未知的這部電影屬於動做片類型。
在實際使用中,有幾個問題是值得注意的:K值的選取,選多大合適呢?計算二者間距離,用哪一種距離會更好呢?計算量太大怎麼辦?假設樣本中,類型分佈很是不均,好比Action的電影有200部,可是Romance的電影只有20部,這樣計算起來,即便不是Action的電影,也會由於Action的樣本太多,致使k個最近鄰居里有很多Action的電影,這樣該怎麼辦呢?
沒有萬能的算法,只有在必定使用環境中最優的算法。
kNN算法的指導思想是「近朱者赤,近墨者黑」,由你的鄰居來推斷出你的類別。
先計算待分類樣本與已知類別的訓練樣本之間的距離,找到距離與待分類樣本數據最近的k個鄰居;再根據這些鄰居所屬的類別來判斷待分類樣本數據的類別。
用空間內兩個點的距離來度量。距離越大,表示兩個點越不類似。距離的選擇有不少[13],一般用比較簡單的歐式距離。
歐式距離:
馬氏距離:馬氏距離可以緩解因爲屬性的線性組合帶來的距離失真,是數據的協方差矩陣。
曼哈頓距離:
切比雪夫距離:
閔氏距離:r取值爲2時:曼哈頓距離;r取值爲1時:歐式距離。
平均距離:
弦距離:
測地距離:
投票決定:少數服從多數,近鄰中哪一個類別的點最多就分爲該類。
加權投票法:根據距離的遠近,對近鄰的投票進行加權,距離越近則權重越大(權重爲距離平方的倒數)
k值選擇太小,獲得的近鄰數過少,會下降分類精度,同時也會放大噪聲數據的干擾;而若是k值選擇過大,而且待分類樣本屬於訓練集中包含數據數較少的類,那麼在選擇k個近鄰的時候,實際上並不類似的數據亦被包含進來,形成噪聲增長而致使分類效果的下降。
如何選取恰當的K值也成爲KNN的研究熱點。k值一般是採用交叉檢驗來肯定(以k=1爲基準)。
經驗規則:k通常低於訓練樣本數的平方根。
投票法沒有考慮近鄰的距離的遠近,距離更近的近鄰也許更應該決定最終的分類,因此加權投票法更恰當一些。
高維度對距離衡量的影響:衆所周知當變量數越多,歐式距離的區分能力就越差。
變量值域對距離的影響:值域越大的變量經常會在距離計算中佔據主導做用,所以應先對變量進行標準化。
學者們對於訓練樣本的選擇進行研究,以達到減小計算的目的,這些算法大體可分爲兩類。第一類,減小訓練集的大小。KNN算法存儲的樣本數據,這些樣本數據包含了大量冗餘數據,這些冗餘的數據增了存儲的開銷和計算代價。縮小訓練樣本的方法有:在原有的樣本中刪掉一部分與分類相關不大的樣本樣本,將剩下的樣本做爲新的訓練樣本;或在原來的訓練樣本集中選取一些表明樣本做爲新的訓練樣本;或經過聚類,將聚類所產生的中心點做爲新的訓練樣本。
在訓練集中,有些樣本多是更值得依賴的。能夠給不一樣的樣本施加不一樣的權重,增強依賴樣本的權重,下降不可信賴樣本的影響。
kNN是一種懶惰算法,而懶惰的後果:構造模型很簡單,但在對測試樣本分類地的系統開銷大,由於要掃描所有訓練樣本並計算距離。
已經有一些方法提升計算的效率,例如壓縮訓練樣本量等。
9.測試元組集測試完畢後計算偏差率,繼續設定不一樣的k 值從新進行訓練,最後取偏差率最小的k 值。
Java代碼實現
public class KNN { /** * 設置優先級隊列的比較函數,距離越大,優先級越高 */ private Comparator<KNNNode> comparator =new Comparator<KNNNode>(){ public int compare(KNNNode o1, KNNNode o2) { if (o1.getDistance() >= o2.getDistance()) return -1; else return 1; } }; /** * 獲取K個不一樣的隨機數 * @param k 隨機數的個數 * @param max 隨機數最大的範圍 * @return 生成的隨機數數組 */ public List<Integer> getRandKNum(int k, int max) { List<Integer> rand = new ArrayList<Integer>(k); for (int i = 0; i < k; i++) { int temp = (int) (Math.random() * max); if (!rand.contains(temp)) rand.add(temp); else i--; } return rand; }
/* 計算測試元組與訓練元組以前的距離 * @param d1 測試元組 * @param d2 訓練元組 * @return 距離值 */ public double calDistance(List<Double> d1, List<Double> d2) { double distance = 0.00; for (int i = 0; i < d1.size(); i++) distance += (d1.get(i) - d2.get(i)) *(d1.get(i)-d2.get(i)); return distance; } /** * 執行KNN算法,獲取測試元組的類別 * @param datas 訓練數據集 * @param testData 測試元組 * @param k 設定的K值 * @return 測試元組的類別 */ public String knn(List<List<Double>> datas, List<Double> testData, int k) { PriorityQueue<KNNNode> pq = new PriorityQueue<KNNNode> (k,comparator); List<Integer> randNum = getRandKNum(k, datas.size()); for (int i = 0; i < k; i++) { int index = randNum.get(i); List<Double> currData = datas.get(index); String c = currData.get(currData.size() - 1).toString(); KNNNode node = new KNNNode(index, calDistance(testData, currData), c); pq.add(node); } for (int i = 0; i < datas.size(); i++) { List<Double> t = datas.get(i); double distance = calDistance(testData, t); KNNNode top = pq.peek(); if (top.getDistance() > distance) { pq.remove(); pq.add(new KNNNode(i, distance, t.get(t.size() - 1). toString())); } } return getMostClass(pq);
}
/** * 獲取所獲得的k個最近鄰元組的多數類 * @param pq 存儲k個最近近鄰元組的優先級隊列 * @return 多數類的名稱 */ private String getMostClass(PriorityQueue<KNNNode> pq) { Map<String, Integer> classCount=new HashMap<String,Integer>(); int pqsize = pq.size(); for (int i = 0; i < pqsize; i++) { KNNNode node = pq.remove(); String c = node.getC(); if (classCount.containsKey(c)) classCount.put(c, classCount.get(c) + 1); else classCount.put(c, 1); } int maxIndex = -1; int maxCount = 0; Object[] classes = classCount.keySet().toArray(); for (int i = 0; i < classes.length; i++) { if (classCount.get(classes[i]) > maxCount) maxIndex = i; maxCount = classCount.get(classes[i]); } return classes[maxIndex].toString(); } }