机器学习算法之kNN

  邻近算法,或者说K最近邻(kNN,k-NearestNeighbor)分类算法是数据挖掘分类技术中最简单的方法之一。所谓K最近邻,就是k个最近的邻居的意思,说的是每个样本都可以用它最接近的k个邻居来代表。
  举个例子:下图中,绿色圆要被决定赋予哪个类,是红色三角形还是蓝色四方形?如果K=3,由于红色三角形所占比例为2/3,绿色圆将被赋予红色三角形那个类,如果K=5,由于蓝色四方形比例为3/5,因此绿色圆被赋予蓝色四方形类。


knn.png

  kNN算法的核心思想是如果一个样本在特征空间中的k个最相邻的样本中的大多数属于某一个类别,则该样本也属于这个类别,并具有这个类别上样本的特性。该方法在确定分类决策上只依据最邻近的一个或者几个样本的类别来决定待分样本所属的类别。 kNN方法在类别决策时,只与极少量的相邻样本有关。由于kNN方法主要靠周围有限的邻近的样本,而不是靠判别类域的方法来确定所属类别的,因此对于类域的交叉或重叠较多的待分样本集来说,kNN方法较其他方法更为适合。
  KNN算法不仅可以用于分类,还可以用于回归。通过找出一个样本的k个最近邻居,将这些邻居的属性的平均值赋给该样本,就可以得到该样本的属性。更有用的方法是将不同距离的邻居对该样本产生的影响给予不同的权值(weight),如权值与距离成反比。
简单的kNN源码实现:


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

/**
 * kNN算法思想:
 * 找出与当前节点距离(这里用最简单的欧式距离)最近的k个节点,然后通过这k的节点的所属类型进行投票分类。少数服从多数。
 * 约定原始数据为等长度的double类型数组,最后一位表示数据的class类别属性(默认二分类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);
    }
    

    //欧式距离的计算
    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;
    }

    //维护一个大小为k的有序的中间节点链表(根据distance排序)
    private void insertNode(LinkedList<KNN_Node> list, KNN_Node node, int k){

        //插入排序,并移除最后一个节点
        int orig = list.size();
        for(int i = 0; i< list.size(); i++){
            if(list.get(i).dist >= node.dist){
                list.add(i, node);
                break;
            }
        }

        //判断当前节点是否加入list中
        if(orig == list.size())
            list.addLast(node);

        //判断list是否超过长度k
        if(list.size() > k){
            list.removeLast();
        }
    }

    //获取分类结果
    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表示当前样本分类为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 + "]";
        }
    }

}

测试数据及分类结果:

public static void main(String[] args) {
        //测试数据
        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));
        
    }


分类结果:
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值选定和分类决策规则来确定。

距离度量

  特征空间中两个点实例之间的距离是两个实例相似程度的反应。kNN一般使用的距离是欧式距离,但也可以是其他距离,如更一般的\small L_p距离。
  这里的\small p \geqslant 1。当\small p=1时,称为曼哈顿距离,即:
L_1(x_i,x_j)=\sum_{l=1}^n|x_i^{n}-x_j^n|  当\small p=2时,称为欧氏距离,即:
L_2(x_i,x_j)=(\sum_{l=1}^n|x_i^{n}-x_j^n|^2)^{\frac{1}{2}}

k值选择

  k值得选择会之间对kNN模型的结果产生影响。k值较小时,只选择较小的领域内的训练实例进行预测,学习的近似误差会比较小,但是学习的估计误差会比价大,因为预测的结果会对邻近的点比较敏感,k值越小意味着整体模型的复杂度较高,容易发生过拟合。
  如果选择的k值较大,相当于用较大领域的数据进行预测。优点是可以减少估计误差,但是近似误差会增大。k值越大意味着模型的复杂度越低,模型相对越简单。在实际应用中,k值一般是一个比较小的值,通常可以通过交叉验证大来选择最优的k值

分类决策规则

  kNN的分类决策规则一般是少数服从多数,即多数表决规则。多数表决规则等价于经验风险最小化。
  以上就是kNN的全部内容,在实际实施过程中,kNN需要考虑如何针对训练数据快速地进行kNN检索。最简单的方法是线性扫描,但是这种方法在数据量特别大的时候,计算非常耗时。一种较快的kNN检索的方式称为k-d Tree,可以使用k-d树对训练数据进行存储,并在k-d树的基础上进行kNN检索。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,752评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,100评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,244评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,099评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,210评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,307评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,346评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,133评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,546评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,849评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,019评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,702评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,331评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,030评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,260评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,871评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,898评论 2 351

推荐阅读更多精彩内容