统计学习方法笔记---k近邻

基本了解

缺点:

  1. 不适用与数据量大的情况:K近邻需要计算目标实例与所有样本实例的距离,计算量大,耗时长,需要保存所有的训练数据,所需大量的内存空间。
  2. 无法给出给出任何数据的数据结构,不能知晓典型实例样本和平均实例样本具有什么样的特征。

优点:

  1. 最简单的分类算法。
  2. 对异常值不敏感。
  3. 精度高
  4. 无数据输入假定

注意点:

在求解距离的过程中,数值较大的特征,如取值为1000和10的两个特征,取值为1000的特征会对分类结果产生比较大的影响,但这并不意味着该特征在模型中具有较强的影响力,所以需要对数据进行归一化处理。

工作原理

k近邻模型的主要原理:计算目标实例与所有样本实例的距离,对距离进行排序,选择出前K个距离最小的样本实例,通常采用投票表决机制,即选择出类别最多的那一种类别。

本章概要

  1. k近邻是基本且简单的分类与回归方法,既可用于二分类,又可用于多分类。支持线性不可分的数据。K近邻法的基本做法是:对给定的训练实例点和输入实例点,首先确定输入实例点的K个最近邻训练实例点,然后利用这K个训练实例点的类的多数来预测输入实例点的类。

  2. k近邻模型对应于基于训练数据集对特征空间的一个划分。K近邻法中,当训练集、距离度量、K值及分类决策规则确定后,其结果唯一确定。

  3. K邻近法三要素:距离度量、K值的选择和分类决策规则。常用的距离度量是欧氏距离及更一般的 L p L_p 距离。K值小时,k近邻模型更复杂,即会发生过拟合;k值大时,k近邻模型更简单,即会发生欠拟合。k值的选择反应了对近似误差(训练集的误差)和估计误差(预测集的误差)之间的权衡,通常首先选择较小的k值,再由交叉验证选择最优的k。常用的分类决策是多数表决,对应于经验风险最小化。

  4. K近邻的误分类率:
    1 k x i N k ( x ) I ( y i c j ) = 1 1 k x i N k ( x ) I ( y i = c j ) \frac 1 k \sum_{x_i \in N_k (x) } I(y_i \not= c_j) = 1 - \frac 1 k \sum_{x_i \in N_k (x) } I(y_i = c_j)

  5. K近邻法的实现需要考虑如何快速搜索K个最近邻点。因为当进行一个未知样本点的预测时,需要遍历整个训练集样本找到k个近邻点,时间复杂度高,所以需要优化其存储结构,加速搜索速度。

  6. kd树是一种便于对k维空间中的数据进行快速检索的数据结构。kd树是二叉树,表示对k维空间的一个划分,其每个节点对应于k维空间划分中的一个超矩形区域。利用kd树可以省去对大部分数据点的搜索,从而减少搜索的计算量。

基本算法

在这里插入图片描述
以下图为例,进行简单说明:
(1) 找到包含目标点S的最小超矩形中的叶节点D,D是近似最近邻
(2) 以S为圆心,D在圆上形成一个圆形区域;
(3) 找到D的父节点B,搜索B的另一边区域是否与圆相交,且在交集中有节点;
(4) 若没有则继续查找B的父节点A,搜索A的另一区域内搜索最近邻;
(5) 节点C的区域与圆相交;且查找交集内存在节点E,所以E是真正最近邻
在这里插入图片描述

作业

1.思考k近邻算法的模型复杂度体现在哪里?什么情况下会造成过拟合?

模型复杂度体现在k值的选择,k值越小,模型复杂度越高,容易造成过拟合,k值越小,模型复杂度越低。

2.给定一个二维空间的数据集T={正实例:(5,4),(9,6),(4,7);负实例:(2,3), (8,1),(7,2)},试基于欧氏距离,找到数据点S(5,3)的最近邻(k=1),并对S点进行分类预测。

(1)用“线性扫描”算法自编程实现。

(2)试调用sklearn.neighbors的KNeighborsClassifier模块,对S点进行分类预测,并对比近邻数k取值不同,对分类预测结果的影响。

k取值不同,最终的分类预测结果也不同

(3)思考题:思考“线性扫描”算法和“kd树”算法的时间复杂度。

只考虑样本N的情况下:
线性扫描:O(N)
kd树算法:O(logN)
当维度d接近样本量N时,线性扫描与kd树算法的线性复杂度相当。

(1)用“线性扫描”算法自编程实现。

import numpy as np
import matplotlib.pyplot as plt
import collections

def main():
    x_train = np.array([[5,4],[9,6],[4,7],[2,3],[8,1],[7,2]])
    y_train = np.array([1,1,1,-1,-1,-1])
    s = np.array([5,3])
    k = 1
    myKNN = KNN()
    prediction = myKNN.predict(x_train, y_train, s, k)
    
    print("目标点S的类别为:{}".format(prediction))

class KNN:
    # 核心算法实现: 距离度量采用欧式距离,并利用线性检索,找到离s点最近的样本点
    def predict(self, x_train, y_train, s, k):
        distance = []
        labels = []
        for i in range(x_train.shape[0]):
            x = x_train[i]
            # 计算x_test与s的距离度量/欧式距离
            distance.append(np.sqrt(pow(x[0]-s[0],2) + pow(x[1]-s[1],2)))
        # print("s到各点的距离度量:{}".format(distance))

        # 对欧式距离进行排序,找出欧氏距离最小的k个节点对应的标签
        sorted_distance = sorted(distance)
        for i in range(k):
            labels.append(y_train[distance.index(sorted_distance[i])])
        
        # 对K个y值进行统计,输出出现频率最高的y值
        map_label = collections.Counter(labels)
        return max(map_label.keys(), key=map_label.get)
main()

优化方法一:
主要是列表生成式的使用和通过np.linalg.norm计算欧式距离。

class KNN:
    def __init__(self, x_train, y_train, k):
        self.x_train = x_train
        self.y_train = y_train
        self.k = k
    # 核心算法实现: 距离度量采用欧式距离,并利用线性检索,找到离s点最近的样本点
    def predict(self, s):
        dist_list = [(np.linalg.norm(s-self.x_train[i],ord=2), self.y_train[i]) 
        for i in range(self.x_train.shape[0])]
        # 输出为: [(d0, 1), (d1, -1)...]        
        #print("s到各点的距离度量:{}".format(dist_list))

        # 对欧式距离进行排序
        dist_list.sort(key=lambda  x: x[0])
        
        # 取前k个最小的距离的标签
        y_list = [dist_list[i][-1] for i in range(self.k)]
        
        # 对K个y值进行统计,输出出现频率最高的y值   
        """
        另一种方法:
        y_count = Counter(y_list).most_common() 
        return y_count[0][0]
        """
        y_count = Counter(y_list)
        return max(y_count.keys(), key=y_count.get)

if __name__=="__main__":
    main()

(2) 调用sklearn.neighbors的KNeighborsClassifier模块,对S点进行分类预测,并对比近邻数k取值不同,对分类预测结果的影响。
在这里插入图片描述

  • kd_tree和ball_tree在数据量较大时使用;
  • 当维度上升时,kd树效率下降,一般当特征维度大于20时,使用ball_tree算法;
方法 描述
fit(self, X, y) Fit the model using X as training data and y as target values;训练模型,确定使用算法,即确定选择哪一种算法。
get_params(self[, deep]) Get parameters for this estimator. 获取评估器的参数
kneighbors(self[, X, n_neighbors, …]) Finds the K-neighbors of a point. 找到目标点的K个近邻
kneighbors_graph(self[, X, n_neighbors, mode]) Computes the (weighted) graph of k-Neighbors for points in X. 计算X中节点的(加权)图
predict(self, X) Predict the class labels for the provided data. 预测给定数据的类别
predict_proba(self, X) Return probability estimates for the test data X. 对测试点属于不同分类的概率
score(self, X, y[, sample_weight]) Return the mean accuracy on the given test data and labels. 返回给定测试数据和标签的平均准确率
set_params(self, **params) Set the parameters of this estimator. 设置评估器的参数
import numpy as np
from sklearn.neighbors import  KNeighborsClassifier

def main():
    x_train = np.array([[5,4],[9,6],[4,7],[2,3],[8,1],[7,2]])
    y_train = np.array([1,1,1,-1,-1,-1])
    s = np.array([[5,3]])
    for k in range(1,6,2):
        kneighbor = KNeighborsClassifier(n_neighbors=k).fit(x_train,y_train)
        
        prediction = kneighbor.predict(X = s)
        # 输出结果为一个数组,分别表示属于该分类的概率;如[0,1]表示属于‘-1’的概率为0,属于‘1’的概率为1。
        print("目标点S属于不同分类的概率:{}".format(kneighbor.predict_proba(s)))
        print("目标点S[5,3]的类别为:{}".format(prediction))

        accuracy = kneighbor.score(x_train,y_train)
        print("模型预测的准确率:{:.0%}".format(accuracy))

if __name__=="__main__":
    main()

输出值:

目标点S属于不同分类的概率:[[0. 1.]]
目标点S[5,3]的类别为:[1]
模型预测的准确率:100%
目标点S属于不同分类的概率:[[0.66666667 0.33333333]]
目标点S[5,3]的类别为:[-1]
模型预测的准确率:83%
目标点S属于不同分类的概率:[[0.6 0.4]]
目标点S[5,3]的类别为:[-1]
模型预测的准确率:83%
发布了21 篇原创文章 · 获赞 0 · 访问量 393

猜你喜欢

转载自blog.csdn.net/leemusk/article/details/104826195