《统计学习》--K近邻算法

KNN

简介:k近邻法(KNN)是一种基本分类与回归方法。目前仅介绍分类部分。k近邻法的输入为实例的特征向量,对应于特征空间的点,输出为实例的类别,可以取多类。k近邻法假设给定一个训练集,其中的实例类别已定。分类时,对新的实例,根据其k个最近邻的训练实例的类别,通过多数表决等方式进行预测。因此,k近邻法不具有显示的学习过程。k近邻法实际上利用训练集对特征向量空间进行划分,并作为其分类的“模型”。k值的选择、距离度量、分类决策规则是k近邻法的三大基本要素。本博客主要介绍三大基本要素,以及一个实现的方法,kd树。

模型

k近邻法中,当训练集、距离度量(如欧式距离)、k值及分类决策规则(如多数表决)确定后,对于任何一个新输入的实例,它属于的类是唯一地确定。这相当于根据上述要素将特征空间划分为一些子空间,确定子空间的每个点所属的类。这个和最近邻算法很相似。

距离度量

特征空间中两个点的距离是两个点的相似度的反映。特征空间一般是n维实数向量空间R**n。使用的距离一般是欧式距离,也可以是闵可夫斯基距离、曼哈顿距离、切比雪夫距离。

闵可夫斯基距离

image.png

曼哈顿距离

image.png

欧式距离

image.png

切比雪夫距离

image.png

k值的选择

  • k值的选择会对k近邻法的结果产生重大影响。
  • 如果k值选择较小,相当于使用较小的邻域中的训练实例进行预测,近似误差会减小,但是会对噪声敏感,- 容易发生过拟合;
  • 如果k值选择较大,缺点是近似误差会变大,距离输入点较远的点也会对最后结果产生作用,k值的增大意味着模型的简单。
  • 在应用中,k值一般取一个比较下的值。通常采用交叉验证法来选择最优的k值。

分类决策规则

  • 分类决策规则一般是采用多数表决,即由输入实例的k个邻近实例中的多数决定输入实例的类别。


    image.png

要使得误分类率最小,也就是经验风险最小,就要使得最右边公式最大,所以多数表决规则等价于经验最小化。

k近邻法python样例:

# -*- coding: UTF-8 -*-
"""
=================================================
@Project :ML
@File   :  knn_base
@Desc   :k近邻法基础版本
==================================================
"""
import numpy as np
from collections import Counter

class KnnBase():
    def __init__(self,n=4,p=2):
        self.n = n # 邻近点个数
        self.p = p # 距离度量

    def fit(self,trainX,trainY):
        self.trainX = trainX
        self.trainY = trainY

    def predict(self,X):
        knn_list = []
        for i in range(self.n):
            dist = np.linalg.norm(X-self.trainX[i],ord=self.p)
            knn_list.append([dist,self.trainY[i]])
        for i in range(self.n,len(self.trainX)):
            max_index = knn_list.index(max(knn_list,key=lambda x:x[0]))
            dist = np.linalg.norm(X-self.trainX[i],ord=self.p)
            if dist < knn_list[max_index][0]:
                knn_list[max_index] = [dist,self.trainY[i]]
        knn = [x[-1] for x in knn_list]
        return Counter(knn).most_common()


if __name__ == '__main__':
    trainX = np.array([[0,2,3],[1,2,3],[2,3,4],[3,4,5],[4,5,6],[5,6,7],[6,7,8],[8,9,10]])
    trainY = np.array([1,1,1,1,2,2,2,2])
    knn = KnnBase()
    knn.fit(trainX,trainY)
    X = np.array([4,6,8])
    result = knn.predict(X)
    print(result)

kd树

简介:kd树是一种对n维空间的实例点进行存储,以便对其进行快速检索的树形结构。kd树是二叉树,构造kd树相当于不断的用垂直于坐标轴的超平面将n维空间进行划分,构成一系列的n维超矩阵区域。
下面的流程图更加清晰的描述了kd树的构建过程:


image.png

kd树python代码如下:(明天补充)

import numpy as np
from collections import Counter

class KnnBase():
    def __init__(self,n=4,p=2):
        self.n = n # 邻近点个数
        self.p = p # 距离度量

    def fit(self,trainX,trainY):
        self.trainX = trainX
        self.trainY = trainY

    def predict(self,X):
        knn_list = []
        for i in range(self.n):
            dist = np.linalg.norm(X-self.trainX[i],ord=self.p)
            knn_list.append([dist,self.trainY[i]])
        for i in range(self.n,len(self.trainX)):
            max_index = knn_list.index(max(knn_list,key=lambda x:x[0]))
            dist = np.linalg.norm(X-self.trainX[i],ord=self.p)
            if dist < knn_list[max_index][0]:
                knn_list[max_index] = [dist,self.trainY[i]]
        knn = [x[-1] for x in knn_list]
        return Counter(knn).most_common()



import time
from collections import Counter

# kd-tree每个结点中主要包含的数据结构如下
class Node:
    def __init__(self, data, label, depth=0, lchild=None, rchild=None):
        self.data = data
        self.depth = depth
        self.lchild = lchild
        self.rchild = rchild
        self.label = label


class KdTree:
    def __init__(self, dataSet, label):
        self.KdTree = None
        self.n = 0
        self.nearest = None
        self.create(dataSet, label)

    # 建立kdtree
    def create(self, dataSet, label, depth=0):
        if len(dataSet) > 0:
            m, n = np.shape(dataSet)
            self.n = n
            axis = depth % self.n
            mid = int(m / 2)
            dataSetcopy = sorted(dataSet, key=lambda x: x[axis])
            node = Node(dataSetcopy[mid], label[mid], depth)
            if depth == 0:
                self.KdTree = node
            node.lchild = self.create(dataSetcopy[:mid], label, depth+1)
            node.rchild = self.create(dataSetcopy[mid+1:], label, depth+1)
            return node
        return None

    # 前序遍历
    def preOrder(self, node):
        if node is not None:
            print(node.depth, node.data)
            self.preOrder(node.lchild)
            self.preOrder(node.rchild)

    # 搜索kdtree的前count个近的点
    def search(self, x, count = 1):
        nearest = []
        for i in range(count):
            nearest.append([-1, None])
        # 初始化n个点,nearest是按照距离递减的方式
        self.nearest = np.array(nearest)

        def recurve(node):
            if node is not None:
                # 计算当前点的维度axis
                axis = node.depth % self.n
                # 计算测试点和当前点在axis维度上的差
                daxis = x[axis] - node.data[axis]
                # 如果小于进左子树,大于进右子树
                if daxis < 0:
                    recurve(node.lchild)
                else:
                    recurve(node.rchild)
                # 计算预测点x到当前点的距离dist
                dist = np.sqrt(np.sum(np.square(x - node.data)))
                for i, d in enumerate(self.nearest):
                    # 如果有比现在最近的n个点更近的点,更新最近的点
                    if d[0] < 0 or dist < d[0]:
                        # 插入第i个位置的点
                        self.nearest = np.insert(self.nearest, i, [dist, node], axis=0)
                        # 删除最后一个多出来的点
                        self.nearest = self.nearest[:-1]
                        break

                # 统计距离为-1的个数n
                n = list(self.nearest[:, 0]).count(-1)
                '''
                self.nearest[-n-1, 0]是当前nearest中已经有的最近点中,距离最大的点。
                self.nearest[-n-1, 0] > abs(daxis)代表以x为圆心,self.nearest[-n-1, 0]为半径的圆与axis
                相交,说明在左右子树里面有比self.nearest[-n-1, 0]更近的点
                '''
                if self.nearest[-n-1, 0] > abs(daxis):
                    if daxis < 0:
                        recurve(node.rchild)
                    else:
                        recurve(node.lchild)

        recurve(self.KdTree)

        # nodeList是最近n个点的
        nodeList = self.nearest[:, 1]

        # knn是n个点的标签
        knn = [node.label for node in nodeList]
        return self.nearest[:, 1], Counter(knn).most_common()[0][0]


class KNNKdTree(KnnBase):
    def __init__(self, n_neighbors=3, p=2):
        super(KNNKdTree, self).__init__(n=n_neighbors, p=p)


    def fit(self, X_train, y_train):
        self.X_train = np.array(X_train)
        self.y_train = np.array(y_train)
        self.kdTree = KdTree(self.X_train, self.y_train)


    def predict(self, point):
        nearest, label = self.kdTree.search(point, self.n)
        # print("nearest", [node.data for node in nearest])
        return nearest, label



    def score(self, X_test, y_test):
        right_count = 0
        for X, y in zip(X_test, y_test):
            _, label = self.predict(X)
            if label == y:
                right_count += 1
        return right_count / len(X_test)


if __name__ == '__main__':
    trainX = np.array([[0.8,2,3],[1.9,2.9,3],[2,3,4],[3,4.8,5],[4,5.6,6],[5,6.7,7.9],[6.9,7,8],[8,9,10]])
    trainY = np.array([1,1,1,1,2,2,2,2])
    knn = KnnBase()
    knn.fit(trainX,trainY)
    X = np.array([4,6,8])
    result = knn.predict(X)
    print(result)
    kdt = KdTree(trainX,trainY)
    print("END")

你可能感兴趣的:(《统计学习》--K近邻算法)