KNN鄰近分類算法

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的電影,這樣該怎麼辦呢?

沒有萬能的算法,只有在必定使用環境中最優的算法。

1.1 算法指導思想

kNN算法的指導思想是「近朱者赤,近墨者黑」,由你的鄰居來推斷出你的類別。

先計算待分類樣本與已知類別的訓練樣本之間的距離,找到距離與待分類樣本數據最近的k個鄰居;再根據這些鄰居所屬的類別來判斷待分類樣本數據的類別。

 

1.2類似性度量

用空間內兩個點的距離來度量。距離越大,表示兩個點越不類似。距離的選擇有不少[13],一般用比較簡單的歐式距離。

歐式距離

 

馬氏距離:馬氏距離可以緩解因爲屬性的線性組合帶來的距離失真,是數據的協方差矩陣。

 

曼哈頓距離

 

切比雪夫距離

 

閔氏距離:r取值爲2時:曼哈頓距離;r取值爲1時:歐式距離。

 

 

平均距離

 

弦距離

 

測地距離

 

 

1.2 類別的斷定

投票決定:少數服從多數,近鄰中哪一個類別的點最多就分爲該類。

加權投票法:根據距離的遠近,對近鄰的投票進行加權,距離越近則權重越大(權重爲距離平方的倒數)

 優缺點

1.2.1              優勢

  1. 簡單,易於理解,易於實現,無需估計參數,無需訓練;
  2. 適合對稀有事件進行分類;
  3. 特別適合於多分類問題(multi-modal,對象具備多個類別標籤), kNN比SVM的表現要好。
  4. 懶惰算法,對測試樣本分類時的計算量大,內存開銷大,評分慢;
  5. 當樣本不平衡時,如一個類的樣本容量很大,而其餘類樣本容量很小時,有可能致使當輸入一個新樣本時,該樣本的K個鄰居中大容量類的樣本佔多數;
  6. 可解釋性較差,沒法給出決策樹那樣的規則。

1.2.2              缺點

1.3 常見問題

1.3.1              k值的設定

k值選擇太小,獲得的近鄰數過少,會下降分類精度,同時也會放大噪聲數據的干擾;而若是k值選擇過大,而且待分類樣本屬於訓練集中包含數據數較少的類,那麼在選擇k個近鄰的時候,實際上並不類似的數據亦被包含進來,形成噪聲增長而致使分類效果的下降。

如何選取恰當的K值也成爲KNN的研究熱點。k值一般是採用交叉檢驗來肯定(以k=1爲基準)。

經驗規則:k通常低於訓練樣本數的平方根。

1.3.2              類別的斷定方式

投票法沒有考慮近鄰的距離的遠近,距離更近的近鄰也許更應該決定最終的分類,因此加權投票法更恰當一些。

1.3.3              距離度量方式的選擇

高維度對距離衡量的影響:衆所周知當變量數越多,歐式距離的區分能力就越差。

變量值域對距離的影響:值域越大的變量經常會在距離計算中佔據主導做用,所以應先對變量進行標準化。

1.3.4              訓練樣本的參考原則

學者們對於訓練樣本的選擇進行研究,以達到減小計算的目的,這些算法大體可分爲兩類。第一類,減小訓練集的大小。KNN算法存儲的樣本數據,這些樣本數據包含了大量冗餘數據,這些冗餘的數據增了存儲的開銷和計算代價。縮小訓練樣本的方法有:在原有的樣本中刪掉一部分與分類相關不大的樣本樣本,將剩下的樣本做爲新的訓練樣本;或在原來的訓練樣本集中選取一些表明樣本做爲新的訓練樣本;或經過聚類,將聚類所產生的中心點做爲新的訓練樣本。

在訓練集中,有些樣本多是更值得依賴的。能夠給不一樣的樣本施加不一樣的權重,增強依賴樣本的權重,下降不可信賴樣本的影響。

1.3.5              性能問題

kNN是一種懶惰算法,而懶惰的後果:構造模型很簡單,但在對測試樣本分類地的系統開銷大,由於要掃描所有訓練樣本並計算距離。

已經有一些方法提升計算的效率,例如壓縮訓練樣本量等。

1.4 算法流程

  1. 準備數據,對數據進行預處理
  2. 選用合適的數據結構存儲訓練數據和測試元組
  3. 設定參數,如k
  4. 維護一個大小爲k的的按距離由大到小的優先級隊列,用於存儲最近鄰訓練元組。隨機從訓練元組中選取k個元組做爲初始的最近鄰元組,分別計算測試元組到這k個元組的距離,將訓練元組標號和距離存入優先級隊列
  5. 遍歷訓練元組集,計算當前訓練元組與測試元組的距離,將所得距離L 與優先級隊列中的最大距離Lmax
  6. 進行比較。若L>=Lmax,則捨棄該元組,遍歷下一個元組。若L < Lmax,刪除優先級隊列中最大距離的元
  7. 組,將當前訓練元組存入優先級隊列。
  8. 遍歷完畢,計算優先級隊列中k 個元組的多數類,並將其做爲測試元組的類別。

      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();
    }
}
相關文章
相關標籤/搜索