李航《统计学习方法》K近邻学习算法实现

2019-09-12 17:50:47 浏览数 (2)

K近邻学习算法的初始形式

数据集的选取

此次我们选取lris数据集进行实验

Iris也称鸢尾花卉数据集,是一类多重变量分析的数据集。通过花萼长度,花萼宽度,花瓣长度,花瓣宽度4个属性预测鸢尾花卉属于(Setosa,Versicolour,Virginica)三个种类中的哪一类。

数据集特征:多变量 记录数:150 领域:生活

属性特征:实数 属性数目:4 捐赠日期1988-07-01

相关应用:分类 缺失值?无 网站点击数:1507217

这里用到一个更方便的方法,使用scikit-learn自带lris数据集

代码语言:javascript复制
>>> from sklearn.datasets import load_iris
>>> iris = load_iris()
>>> iris.target[[10, 25, 50]]
array([0, 0, 1])
>>> list(data.target_names)
['setosa', 'versicolor', 'virginica']

Python代码

代码语言:javascript复制
import numpy as np
import random
import heapq

#对数据进行预处理,分成训练集和测试集
def Preprocessing():
    from sklearn import datasets

    testset = []       #测试集
    test_labels = []   #测试集标签
    trainset = []      #训练集
    train_labels = []  #训练集标签
    
    #加载scikit-learn中自带的Iris数据集
    iris = datasets.load_iris()
    datas = iris.data[:, 0:3] #获取所有数据,不含标签
    label = iris.target       #获取所有标签
    
    #获取0到149中任意不重复的10的数
    test_list = np.sort([random.randint(0,149) for _ in range(10)])
    #对150个数据进行划分
    for i in range(150):
        if i in test_list:
            testset.append(datas[i])
            test_labels.append(label[i])
        else:
            trainset.append(datas[i])
            train_labels.append(label[i])

    testset = np.mat(testset) #转换为矩阵类型
    test_labels = np.mat(test_labels).transpose() #进行矩阵的转置
    trainset = np.mat(trainset)
    train_labels = np.mat(train_labels).transpose()
    return testset, test_labels, trainset, train_labels

def Predict(testset, trainset, train_labels, n):
    k = n
    predict = []
    #遍历测试数据集中的测试用例坐标
    for  test_vec in testset:
        dist_list = []      #当前测试数据与训练数据的距离
        knn_list = []       #当前k个最邻近点
        
        #遍历训练数据集中的训练用例坐标及标签
        for i in range(len(train_labels)):
            label = train_labels[i]
            train_vec = trainset[i]
            
            dist = np.linalg.norm(train_vec - test_vec)   #计算两个坐标的欧式距离
            dist_list.append((dist, label))
        
        #通过python中堆结构获取k个最近邻
        knn_list = heapq.nsmallest(k, dist_list, key=lambda x: x[0])
        
        # 统计选票
        class_total = 3
        class_count = [0 for i in range(class_total)]
        for dist, label in knn_list:
            label = label.tolist()  #将矩阵类型转换为列表类型
            class_count[label[0][0]]  = 1
        
        # 找出最大选票
        mmax = max(class_count)

        # 找出最大选票标签
        for i in range(class_total):
            if mmax == class_count[i]:
                predict.append(i)
                break
    
    return predict

def main():
    k = 10
    testset, test_labels, trainset, train_labels = Preprocessing()
    predict = Predict(testset, trainset, train_labels, k)
    test_labels = test_labels.tolist()

    error_count = 0
    for i in range(10):
        print("testset:",test_labels[i][0], "predict:", predict[i])
        if test_labels[i][0] != predict[i]:
            error_count  = 1
    print("error_count:",error_count)
   
if __name__ == '__main__':
    main()

最后输出结果:

代码语言:javascript复制
testset: 1 predict: 1
testset: 1 predict: 1
testset: 1 predict: 1
testset: 1 predict: 1
testset: 2 predict: 2
testset: 2 predict: 2
testset: 2 predict: 2
testset: 2 predict: 1
testset: 2 predict: 2
testset: 2 predict: 2
error_count: 1

也许有小伙伴要问了,这个k如何来确定。我们先在看张图

有两类不同的样本数据,分别用蓝色的小正方形和红色的小三角形表示,而图正中间的那个绿色的圆所标示的数据则是待分类的数据。也就是说,现在,我们不知道中间那个绿色的数据是从属于哪一类(蓝色小正方形or红色小三角形),下面,我们就要解决这个问题:给这个绿色的圆分类。 我们常说,物以类聚,人以群分,判别一个人是一个什么样品质特征的人,常常可以从他/她身边的朋友入手,所谓观其友,而识其人。我们不是要判别上图中那个绿色的圆是属于哪一类数据么,所以就从它的邻居下手。但一次性看多少个邻居呢(也就是如何确定k)?从上图中,你还能看到:

  • 如果K=3,绿色圆点的最近的3个邻居是2个红色小三角形和1个蓝色小正方形,少数从属于多数,基于统计的方法,判定绿色的这个待分类点属于红色的三角形一类。
  • 如果K=5,绿色圆点的最近的5个邻居是2个红色三角形和3个蓝色的正方形,还是少数从属于多数,基于统计的方法,判定绿色的这个待分类点属于蓝色的正方形一类。

于此我们看到,不同的k取值可能得到不同的分类,而在应用中,k值一般取一个比较小的值,通常采用交叉验证法来选取最优的k值。July_的博客

写在最后:

KNN并不需要训练,但需要遍历整个训练集,所以预测比较慢。书中提到使用KD树进行优化,来提高k近邻搜索的效率。

在后面的文章中将会对KD树展开实现。

0 人点赞