kNN原理与实现

本节首先介绍KNN的基本原理,包括距离公式,kd树等。然后对kNN进行python实现。接着我们阅读分析sklearn中的knn源码。比较自己的代码和人家的差距。最后用成熟的KNN代码做一些实验。

从我们的玩具代码的实验来看,影响knn精度的因素主要有两个
1. k值的选取。一般来讲k值越大,则模型越简单,泛化能力越好,但容易欠拟合;k值越小,模型越复杂,拟合效果好,但是泛化能力不够。一般的,我们可以通过交叉验证的方法选取合适的k值。
2. 距离度量方案。可以看到,在我们的数据集中,使用欧式距离,精度可以达到70%,而使用cosin距离精度只有36%。

实验数据到这里下载
在进行实验之前,我们需要先对txt数据进行一个转换,代码如下(import numpy as np):

raw_data = np.loadtxt("./knnTestData.txt")
np.save('data.npy',raw_data)

接下来,我们首先编写一个玩具代码。之所以说我们写的代码是玩具代码,原因在于我们还没有在knn类中加入kd树的部分。因此对于规模较大的数据,实验时间会很长。在这个代码中,我们设计了两种度量距离的方案,欧式距离(euclidean,默认距离)和余弦距离(cosin)。你可以分别使用两种距离方案,看一看实际的效果。从这个数据集来看,用前者效果要比后者好。
在主函数部分,你可以打印分类出错的部分,也可以稍微修改后打印所有的分类结果(这部分代码被注释掉)。我们在主函数中,主要想要探讨一下K取值从10到200之间,分类的精度变化情况。最后给出一个测试图。

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@author: XiangguoSun
@contact: sunxiangguodut@qq.com
@file: KNN.py
@time: 2017/3/28 8:40
@software: PyCharm
"""
from sklearn.model_selection import train_test_split
from collections import Counter
from numpy.linalg import norm

import numpy as np
import matplotlib.pyplot as plt


class KNN(object):
    '''
    self.k = kwargs.pop('k',1)
    self.distance = kwargs.pop('distance','cosin')
    self.train_data = kwargs.pop('train_data',None)
    self.train_label = kwargs.pop('train_label',None)
    self.test_data = kwargs.pop('test_data',None)
    self.test_label
    self.neighbor_data
    self.neighbor_label
    '''

    def __init__(self,**kwargs):
        self.k = kwargs.pop('k',1)
        self.distance = kwargs.pop('distance','euclidean')
        self.train_data = kwargs.pop('train_data',None)
        self.train_label = kwargs.pop('train_label',None)
        self.test_data = kwargs.pop('test_data',None)
        if kwargs:
            raise TypeError('you give an unexpected keyword''argument "{0}"'.format(list(kwargs.keys())[0]))

    def get_input(self,test_data):
        self.test_data = test_data

    def get_neighbor(self):
        if self.distance == 'cosin':
            vector1 = self.train_data
            vector2 = self.test_data
            dominator = norm(vector1, axis=1).reshape((vector1.shape[0], 1)) * norm(vector2)
            dominator = np.where(dominator == 0,float('inf'),dominator)
            distance = np.dot(vector1, vector2).reshape((vector1.shape[0], 1)) / dominator
            min_index = distance.reshape((distance.shape[0],)).argsort()[0:self.k]
            self.neighbor_data,self.neighbor_label = train_data[min_index],train_label[min_index]

        elif self.distance == 'euclidean':
            vector1 = self.train_data  #(100L,3)
            vector2 = self.test_data  # (3L,)
            distance = np.sqrt(((vector1 - vector2) ** 2).sum(1))
            min_index = distance.reshape((distance.shape[0],)).argsort()[0:self.k]
            self.neighbor_data, self.neighbor_label = train_data[min_index], train_label[min_index]

    def get_label(self):
        self.get_neighbor()
        self.test_label = Counter(self.neighbor_label.reshape((self.neighbor_label.shape[0],))).most_common(1)[0][0]
        return self.test_label


if __name__ == '__main__':

    # raw_data = np.loadtxt("./knnTestData.txt")
    # np.save('data.npy',raw_data)

    fulldata = np.load('data.npy')
    data, label = np.split(fulldata, (3,), axis=1)
    train_data, test_data, train_label, test_label = train_test_split(data, label, random_state=1, train_size=0.2)

    precise = []
    for k in xrange(10, 200, 5):
        knn = KNN(k=k, train_data=train_data, train_label=train_label)
        erro = 0
        num = test_data.shape[0]
        for ite in xrange(0,num):
            knn.get_input(test_data[ite])
            guess = knn.get_label()
            if test_label[ite][0]!= guess:
                #print erro," actual label: ",test_label[ite][0]," || guess label: ",guess
                erro = erro + 1
            #print ite, " actual label: ", test_label[ite][0], " || guess label: ", guess

        print "total tests:  ",num,"\n erro: ",erro
        precise.append((k,(num-erro)*1.0/num*100))
        print "precise:\n",(num-erro)*1.0/num*100,"%"

    K = []
    P = []
    for it in precise:
        K.append(it[0])
        P.append(it[1])
        print 'K= ', it[0]," precise: ",it[1]

    plt.figure()
    plt.plot(K, P)
    plt.show()

这里写图片描述

事实上,K取125时,可以得到最高的准确率83.375%
你可以将上面的代码稍作修改,就可以得到K=125时的分类情况:

这里写图片描述

接下来,本文将要实现kd树部分。并且在原来的数据集上,比较采用kd树策略和不采用kd树策略时运行的时间差异。在做完这项工作后,我们开始分析一些KNN的优秀开源代码(事实上,在sklearn中有专用的knn包:from sklearn.neighbors import KNeighborsClassifier;还有有封装的包可以直接返回最近邻from sklearn.neighbors import NearestNeighbors)。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值