统计学习方法之k近邻法

声明:图片均来自网络,若有侵权,联系本人后立即删除

1. 算法

  • 输入:
    – 训练数据集
    T = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , . . . , ( x N , y N ) } T=\{(x_1,y_1),(x_2,y_2),...,(x_N,y_N)\} T={(x1,y1),(x2,y2),...,(xN,yN)}
    其中, x i ∈ χ ⊆ R n x_i\in\chi\subseteq\mathsf{R^n} xiχRn 为实例的特征向量, y i ∈ γ = { c 1 , c 2 , . . . , c K } y_i\in\gamma=\{c_1,c_2,...,c_K\} yiγ={c1,c2,...,cK}为实例的类别, i = 1 , 2 , . . . , N i=1,2,...,N i=1,2,...,N;
    – 输入实例特征向量 x x x
  • 输出:实例 x x x所属的类别 y y y
  1. 根据给定的距离度量,在训练集 T T T中找出与 x x x最近邻的 k k k个点,涵盖着 k k k个点的 x x x的领域记作 N k ( x ) N_k(x) Nk(x)
  2. N k ( x ) N_k(x) Nk(x)中根据分类决策规则(如多数表决)决定 x x x的类别 y y y
    y = arg ⁡ max ⁡ c j ∑ x i ∈ N k ( x ) I ( y i = c i ) , i = 1 , 2 , . . . , N ;   j = 1 , 2 , . . . , K y=\mathop{\arg\max_{c_j}}\sum_{x_i\in N_k(x)}I(y_i=c_i), i=1,2,...,N; \ j=1,2,...,K y=argcjmaxxiNk(x)I(yi=ci),i=1,2,...,N; j=1,2,...,K
    上式中, I I I为指示函数,即当 y i = c j y_i=c_j yi=cj I I I为1,否则 I I I为0。

注意: k近邻法没有显式的学习过程。

2. 模型

k近邻法使用的模型实际上对应于对特征空间的划分。模型由三个基本要素——距离度量k值的选择分类决策规则决定。

(1) 距离度量

特征空间中两个实例点的距离是两个实例点相似程度的反映。一般我们使用的距离度量方式是欧式距离,更一般的我们也可以使用 L p L_p Lp距离或闵可夫斯基(Minkowski)距离。
在这里插入图片描述

(2) k值的选择

k值的选择决定了模型的复杂度
如果k值选择的过小,模型比较复杂,此时只有与输入实例较近(相似的)的训练实例才会对预测结果起作用,预测的结果会对近邻的训练实例点非常敏感,如果近邻的实例点恰巧是噪声的话预测就可能会出错。
如果k值选择的过大,模型比较简单,相当于模型在一个比较大的领域内进行预测。此时可能与输入实例较远(不相似的)的训练实例也会对预测结果起作用,使预测发生错误。
实际应用中,k一般取一个较小的数值。通常采用交叉验证法来选取最优的k值。

(3) 分类的决策

k近邻法中的分类决策规则往往是多数表决,也就是说由k个近邻的训练实例中多数的类别来决定输入实例的类别。
其实,多数表决规则等价于经验风险最小化

3. kd树

(1) kd树的引入

  • 根据上面的介绍可知,如果想对一个输入实例进行预测,那么就需要找到与该实例最近的k个训练实例点,此时我们需要遍历所有的训练集(这也是最简单的实现方法:线性扫描),然后选出最近的k个点。这样做有一个很明显的缺点:就是当训练集非常大的时候,计算消耗的时间会非常大(如果你跑了之后实现部分的代码就会有很深的感触)。所以,为了提高k近邻搜索的效率,可以考虑特殊的结构存储训练数据,以减少计算距离的次数。其中一个方法就是下面要介绍的kd树
  • kd树应用在knn中主要分为两步:构造kd树和搜索kd树
  • kd树中的k与knn中的k含义不同,这里的k是指样本特征的维度

(2) 构造kd树

  • 输入: k k k维空间数据集 T = { x 1 , x 2 , . . . , x N } T=\{x_1,x_2,...,x_N\} T={x1,x2,...,xN}
    其中 x i = ( x i ( 1 ) , x i ( 2 ) , . . . , x i ( k ) , ) T , i = 1 , 2 , . . . , N ; x_i=(x_i^{(1)},x_i^{(2)},...,x_i^{(k)},)^T, i =1,2,...,N; xi=(xi(1),xi(2),...,xi(k),)T,i=1,2,...,N;
  • 输出:kd树
  1. 开始:构造根节点,根节点对应于包含 T T T k k k维空间的超矩形区域。
    选择 x ( 1 ) x^{(1)} x(1)为坐标轴,以 T T T中所有实例的 x ( 1 ) x^{(1)} x(1)坐标的中位数为切分点,将根节点对应的超矩形区域切分为两个子区域。切分由通过切分点并与坐标轴 x ( 1 ) x^{(1)} x(1)垂直的超平面实现。
    由根节点生成深度为1的左、右子结点:左子结点对应坐标 x ( 1 ) x^{(1)} x(1)小于切分点的子区域,右子结点对应坐标 x ( 1 ) x^{(1)} x(1)大于切分点的子区域。
    将落在切分超平面上的实例点保存在根节点。
  2. 重复:对深度为 j j j的结点,选择 x ( l ) x^{(l)} x(l)为切分的坐标轴, l = j ( m o d   k ) + 1 l=j(mod\ k)+1 l=j(mod k)+1,以该结点的区域中所有实例的 x ( l ) x^{(l)} x(l)坐标的中位数为切分点,将该结点对应的超矩形区域分为两个子区域。切分由通过切分点并与坐标轴 x ( l ) x^{(l)} x(l)垂直的超平面实现。
    由该结点生成深度为j+1的左、右子结点:左子结点对应坐标 x ( 1 ) x^{(1)} x(1)小于切分点的子区域,右子结点对应坐标 x ( 1 ) x^{(1)} x(1)大于切分点的子区域。
    将落在切分超平面上的实例点保存在该结点。
  3. 直到两个子区域没有实例存在时停止。从而形成kd树的区域划分。

在这里插入图片描述
在这里插入图片描述

(3) 搜索kd树

《统计学习方法》中以最近邻为例加以叙述,同样的方法可以应用到knn中。

  • 输入:已构建的kd树;目标点 x x x
  • 输出: x x x的最近邻
  1. 在kd树中找出包含目标点 x x x的叶结点:从根节点出发,递归地向下访问kd树。若目标点 x x x当前维的坐标小于切分点的坐标,则移动到左子结点,否则移动到右子结点。直到子结点为叶结点为止。
  2. 以此叶结点为“当前最近点”。
  3. 递归地向上回退,在每个结点进行一下操作:
    (a) 如果该结点保存的实例点比当前最近点距离目标点更近,则以该实例点为“当前最近点”。
    (b) 当前最近点一定存在于该结点一个子结点对应的区域。检查该子结点的父结点的另一子结点对应的区域是否有更近的点。具体地,检查另一子结点对应的区域是否与以目标点为球心,以目标点与“当前最近点”间的距离为半径的超球体相交。
    如果相交,可能在另一个子结点对应的区域内存在距目标点更近的点,移动到另一个子结点。接着,递归地进行最近邻搜索;
    如果不相交,向上回退。
  4. 当回退到根节点时,搜索结束。最后的“当前最近点”即为 x x x的最近邻点。

如果实例点事随机分布的,kd树搜索的平均计算复杂度是 O ( l o g N ) O(log N) O(logN),这里 N N N是训练实例数。
在这里插入图片描述

4. 数据集

还是和之前的一样,参考的是wds2006sdo,这里不过多赘述。详情可以看之前感知机的数据集。不过这里与感知机那篇不同的是,这里用的数据集有10个类别,从数字0一直到数字9。特征提取也跟之前所说的一样。

5. 代码实现

代码用python3实现

import pandas as pd
import numpy as np
import cv2
import random
import time
import heapq

from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score


def get_hog_features(trainset):
    features = []
    hog = cv2.HOGDescriptor('../hog.xml')

    for img in trainset:
        img = np.reshape(img, (28, 28))
        cv_img = img.astype(np.uint8)

        hog_features = hog.compute(cv_img)
        features.append(hog_features)

    features = np.array(features)
    features = np.reshape(features, (-1, 324))

    return features


def knn(k, trainset, train_labels, testset):
    test_labels = []
    total_num = len(testset)
    count = 0
    for test_vec in testset:
        count += 1
        flag = count/total_num * 100
        if count % 2000 == 0:
            print('Done:', flag, '%')
        dist = np.linalg.norm(trainset - test_vec, axis=1, keepdims=True)
        mink_index = heapq.nsmallest(k, range(len(dist)), dist.take)
        k_labels = np.array(train_labels[mink_index]).tolist()
        test_label = max(set(k_labels), key=k_labels.count)
        test_labels.append(test_label)
    return np.array(test_labels)


k = 10


if __name__ == '__main__':
    print('----start read data----')

    time_1 = time.time()

    raw_data = pd.read_csv('../data/train.csv', header=0)
    data = raw_data.values

    imgs = data[0:, 1:]
    labels = data[:, 0]

    features = get_hog_features(imgs)

    train_features, test_features, train_labels, test_labels = train_test_split(features,
                                                                                labels, test_size=0.33,
                                                                                random_state=23323)
    time_2 = time.time()
    print('read data cost: ', time_2-time_1, 'second', '\n')

    print('----start training----')
    print('knn does not need to train')
    time_3 = time.time()
    print('training cost: ', time_3-time_2, 'second', '\n')

    print('----start predicting----')
    predict = knn(k, train_features, train_labels, test_features)
    time_4 = time.time()
    print('predicting cost: ', time_4-time_3, 'second', '\n')

    score = accuracy_score(test_labels, predict)
    print("The accuracy score is: ", score)

至于kd树的实现,以后如果有机会的话再补上~

6. 代码运行结果

在这里插入图片描述

参考文献

[1] 《统计学习方法》 李航
[2]   wds2006sdo的博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值