機(jī)器學(xué)習(xí)算法之kNN

??鄰近算法,或者說(shuō)K最近鄰(kNN,k-NearestNeighbor)分類算法是數(shù)據(jù)挖掘分類技術(shù)中最簡(jiǎn)單的方法之一。所謂K最近鄰,就是k個(gè)最近的鄰居的意思,說(shuō)的是每個(gè)樣本都可以用它最接近的k個(gè)鄰居來(lái)代表。
??舉個(gè)例子:下圖中,綠色圓要被決定賦予哪個(gè)類,是紅色三角形還是藍(lán)色四方形?如果K=3,由于紅色三角形所占比例為2/3,綠色圓將被賦予紅色三角形那個(gè)類,如果K=5,由于藍(lán)色四方形比例為3/5,因此綠色圓被賦予藍(lán)色四方形類。


knn.png

??kNN算法的核心思想是如果一個(gè)樣本在特征空間中的k個(gè)最相鄰的樣本中的大多數(shù)屬于某一個(gè)類別,則該樣本也屬于這個(gè)類別,并具有這個(gè)類別上樣本的特性。該方法在確定分類決策上只依據(jù)最鄰近的一個(gè)或者幾個(gè)樣本的類別來(lái)決定待分樣本所屬的類別。 kNN方法在類別決策時(shí),只與極少量的相鄰樣本有關(guān)。由于kNN方法主要靠周圍有限的鄰近的樣本,而不是靠判別類域的方法來(lái)確定所屬類別的,因此對(duì)于類域的交叉或重疊較多的待分樣本集來(lái)說(shuō),kNN方法較其他方法更為適合。
??KNN算法不僅可以用于分類,還可以用于回歸。通過(guò)找出一個(gè)樣本的k個(gè)最近鄰居,將這些鄰居的屬性的平均值賦給該樣本,就可以得到該樣本的屬性。更有用的方法是將不同距離的鄰居對(duì)該樣本產(chǎn)生的影響給予不同的權(quán)值(weight),如權(quán)值與距離成反比。
簡(jiǎn)單的kNN源碼實(shí)現(xiàn):


import java.util.LinkedList;
import java.util.List;

/**
 * kNN算法思想:
 * 找出與當(dāng)前節(jié)點(diǎn)距離(這里用最簡(jiǎn)單的歐式距離)最近的k個(gè)節(jié)點(diǎn),然后通過(guò)這k的節(jié)點(diǎn)的所屬類型進(jìn)行投票分類。少數(shù)服從多數(shù)。
 * 約定原始數(shù)據(jù)為等長(zhǎng)度的double類型數(shù)組,最后一位表示數(shù)據(jù)的class類別屬性(默認(rèn)二分類0,1)
 * @author zhaoshiquan 2018年1月24日 下午2:25:12
 *
 */
public class Algorithm_kNN {
    public static double pos = 1.0;
    public static double neg = 0.0;

    public List<Res_Node> kNN(List<double[]> train, List<double[]> sample, int k){
        LinkedList<Res_Node> list = new LinkedList<Res_Node>();
        sample.forEach(s->{
            list.add(kNN(train, s, k));
        });
        return null;
    }
    
    public Res_Node kNN(List<double[]> train, double[] sample, int k){
        LinkedList<KNN_Node> list = new LinkedList<KNN_Node>();
        train.forEach(t->{
            insertNode(list, new KNN_Node(euclideanDistance(t, sample),t[t.length - 1]),k);
        });
        return getResult(list);
    }
    

    //歐式距離的計(jì)算
    private double euclideanDistance(double[] train, double[] sample){
        double sum = 0;
        for(int i = 0; i <sample.length; i++){
            sum += (sample[i] - train[i]) * (sample[i] - train[i]);
        }
        return sum;
    }

    //維護(hù)一個(gè)大小為k的有序的中間節(jié)點(diǎn)鏈表(根據(jù)distance排序)
    private void insertNode(LinkedList<KNN_Node> list, KNN_Node node, int k){

        //插入排序,并移除最后一個(gè)節(jié)點(diǎn)
        int orig = list.size();
        for(int i = 0; i< list.size(); i++){
            if(list.get(i).dist >= node.dist){
                list.add(i, node);
                break;
            }
        }

        //判斷當(dāng)前節(jié)點(diǎn)是否加入list中
        if(orig == list.size())
            list.addLast(node);

        //判斷l(xiāng)ist是否超過(guò)長(zhǎng)度k
        if(list.size() > k){
            list.removeLast();
        }
    }

    //獲取分類結(jié)果
    private Res_Node getResult(LinkedList<KNN_Node> list){
        int count_pos = 0;
        for(KNN_Node n:list){
            if(n.label > 0.5)
                count_pos++;
        }
        double conf = 1.0 * count_pos / list.size();
        return conf>=0.5 ? new Res_Node(pos,conf) : new Res_Node(neg, 1 - conf); 
    }


    class KNN_Node{
        double dist = Double.MAX_VALUE;
        double label;
        public KNN_Node(double dist, double label){
            this.dist = dist;
            this.label = label;
        }
    }

    class Res_Node{

        public double label = neg;

        /**
         * confidence表示當(dāng)前樣本分類為label的置信度
         */
        public double confidence = pos;

        public Res_Node(double label, double confidence){
            this.label = label;
            this.confidence = confidence;
        }

        @Override
        public String toString() {
            return "Res_Node [label=" + label + ", confidence=" + confidence + "]";
        }
    }

}

測(cè)試數(shù)據(jù)及分類結(jié)果:

public static void main(String[] args) {
        //測(cè)試數(shù)據(jù)
        List<double[]> train = new ArrayList<>();
        double[] t1 = {1,1,1,1,1};
        double[] t2 = {1,2,1,0,0};
        double[] t3 = {1,3,1,3,1};
        double[] t4 = {1,2,4,1,0};
        double[] t5 = {1,0,5,1,0};
        double[] t6 = {1,0,9,1,0};
        double[] t7 = {1,1,2,1,1};
        double[] t8 = {1,4,1,1,0};
        double[] t9 = {1,5,0,1,1};
        double[] t10 = {1,8,4.5,1,1};
        train.add(t1);
        train.add(t2);
        train.add(t3);
        train.add(t4);
        train.add(t5);
        train.add(t6);
        train.add(t7);
        train.add(t8);
        
        double[] s1 = {0.0,0.0,0.0,1};
        double[] s2 = {2,6,3,1};
        double[] s3 = {1,1,2,0};
        
        Algorithm_kNN knn = new Algorithm_kNN();
        System.out.println(knn.kNN(train,s1,5));
        System.out.println(knn.kNN(train,s2,7));
        System.out.println(knn.kNN(train,s3,10));
        
    }


分類結(jié)果:
Res_Node [label=1.0, confidence=0.6]
Res_Node [label=0.0, confidence=0.5714285714285714]
Res_Node [label=0.0, confidence=0.625]
kNN三要素

??kNN模型由三要素——距離度量方式、k值選定和分類決策規(guī)則來(lái)確定。

距離度量

??特征空間中兩個(gè)點(diǎn)實(shí)例之間的距離是兩個(gè)實(shí)例相似程度的反應(yīng)。kNN一般使用的距離是歐式距離,但也可以是其他距離,如更一般的\small L_p距離。
??這里的\small p \geqslant 1。當(dāng)\small p=1時(shí),稱為曼哈頓距離,即:
L_1(x_i,x_j)=\sum_{l=1}^n|x_i^{n}-x_j^n|??當(dāng)\small p=2時(shí),稱為歐氏距離,即:
L_2(x_i,x_j)=(\sum_{l=1}^n|x_i^{n}-x_j^n|^2)^{\frac{1}{2}}

k值選擇

??k值得選擇會(huì)之間對(duì)kNN模型的結(jié)果產(chǎn)生影響。k值較小時(shí),只選擇較小的領(lǐng)域內(nèi)的訓(xùn)練實(shí)例進(jìn)行預(yù)測(cè),學(xué)習(xí)的近似誤差會(huì)比較小,但是學(xué)習(xí)的估計(jì)誤差會(huì)比價(jià)大,因?yàn)轭A(yù)測(cè)的結(jié)果會(huì)對(duì)鄰近的點(diǎn)比較敏感,k值越小意味著整體模型的復(fù)雜度較高,容易發(fā)生過(guò)擬合。
??如果選擇的k值較大,相當(dāng)于用較大領(lǐng)域的數(shù)據(jù)進(jìn)行預(yù)測(cè)。優(yōu)點(diǎn)是可以減少估計(jì)誤差,但是近似誤差會(huì)增大。k值越大意味著模型的復(fù)雜度越低,模型相對(duì)越簡(jiǎn)單。在實(shí)際應(yīng)用中,k值一般是一個(gè)比較小的值,通??梢酝ㄟ^(guò)交叉驗(yàn)證大來(lái)選擇最優(yōu)的k值

分類決策規(guī)則

??kNN的分類決策規(guī)則一般是少數(shù)服從多數(shù),即多數(shù)表決規(guī)則。多數(shù)表決規(guī)則等價(jià)于經(jīng)驗(yàn)風(fēng)險(xiǎn)最小化。
??以上就是kNN的全部?jī)?nèi)容,在實(shí)際實(shí)施過(guò)程中,kNN需要考慮如何針對(duì)訓(xùn)練數(shù)據(jù)快速地進(jìn)行kNN檢索。最簡(jiǎn)單的方法是線性掃描,但是這種方法在數(shù)據(jù)量特別大的時(shí)候,計(jì)算非常耗時(shí)。一種較快的kNN檢索的方式稱為k-d Tree,可以使用k-d樹(shù)對(duì)訓(xùn)練數(shù)據(jù)進(jìn)行存儲(chǔ),并在k-d樹(shù)的基礎(chǔ)上進(jìn)行kNN檢索。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容