K近邻算法

K近邻算法

k近邻算法:预测样本根据附近k个已知标签样本的投票情况,给出预测样本的结果。

k近邻算法是非常特殊的,可以被认为是没有模型的算法,仅统计当前样本中前k个样本的票数。

这样做仅仅是考虑样本个数问题,谁多我就挺谁?这样不一定是最好的,假设k=5,其中有两种类别的样本都是2票,此时产生平票的问题,当然我们可以随便选一个返回,这样做就过于简单直接了。

缓解上面平票情况,可以考虑距离的因素,离得近他的票就应该占比大,实际上这也是合理的,这种考虑距离的因素就是为前k个样本赋予了权重。
在这里插入图片描述
如图 k=5

1)不考虑距离因素,绿色样本应该被标记为蓝色类别。

2)考虑距离因素,绿色样本获得蓝色样本投票 1 3 + 1 4 = 0.5833 \frac{1}{3} + \frac{1}{4} =0.5833 31+41=0.5833,获得黄色样本投票 1 1 1,所以绿色样本应该被标记为黄色类别,其他类别的样本投票分别为 1 5 \frac{1}{5} 51 1 6 \frac{1}{6} 61

knn中两个重要超参数

  1. k
  2. 距离的参数

超参数:算法运行前需要的决定的参数(学习率、batchsize、dropout等)
模型参数:算法过程中学习的参数(各种权重和偏置等)

常见距离公式
1) 欧拉距离 ∑ i = 1 n ( X i a − X i b ) 2 \sqrt{\sum^{n}_{i=1}(X_{i}^{a}-X_{i}^{b})^{2}} i=1n(XiaXib)2

2)曼哈顿距离 ∑ i = 1 n ∣ X i a − X i b ∣ \sum^{n}_{i=1}|X^{a}_{i} - X^{b}_{i}| i=1nXiaXib

3)明可夫斯基距离 ( ∑ i = 1 n ∣ X i a − X i b ∣ p ) 1 p (\sum^{n}_{i=1}|X^{a}_{i} - X^{b}_{i}|^{p})^{\frac{1}{p}} (i=1nXiaXibp)p1

如果想要使用距离作为权重,从公式中可以看出来,不同特征可能属于不同的数量级,很容易使结果倾向这些特征,因此需要对原始数据进行归一化,消除量纲和样本取值范围的影响。

实际使用knn时,往往需要根据业务场景对参数进行网格搜索,以便获取一组最佳的参数组合,本文只是knn算法学习理解,暂不涉及sklearn中网格搜索的内容。

数据归一化

目的是将所有数据映射到同一尺度。

常见的数据归一化

最值归一化 normalization:把所有数据映射到0-1之间,适用于有明显分界的数据。
x s c a l e = x − x m i n x m a x − x m i n x_{scale} = \frac{x-x_{min}}{x_{max}-x_{min}} xscale=xmaxxminxxmin
均值方差归一化 standardization:把所有数据归一化到均值为0方差为1的分布中。
x s c a l e = x − x m e a n s , s 是方差 x_{scale}=\frac{x-x_{mean}}{s}, \quad s是方差 xscale=sxxmean,s是方差

代码实现

import numpy as np
from math import sqrt
from collections import Counter
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score


class KNNClassifier:

    def __init__(self, k, weights=None):
        """初始化"""
        assert k >= 1
        self.k = k
        self._X_train = None
        self._y_train = None
        self.weights = weights

    def fit(self, X_train, y_train):
        """根据训练数据集X_train和y_train训练kNN分类器"""
        assert X_train.shape[0] == y_train.shape[0]
        assert self.k <= X_train.shape[0]
        # 仅仅保存传入的数据
        self._X_train = X_train
        self._y_train = y_train
        return self

    def predict(self, X_predict):
        """给定待预测数据集X_predict,返回表示X_predict的结果向量"""
        assert self._X_train is not None and self._y_train is not None
        assert X_predict.shape[1] == self._X_train.shape[1]
        if self.weights is None:
            y_predict = [self._predict(x) for x in X_predict]
        elif self.weights == "distance":
            y_predict = [self._predict_with_distance(x) for x in X_predict]
        else:
            pass

        return np.array(y_predict)

    def _predict(self, x):
        """ 核心接口
            计算欧式距离
            升序后返回前k个中占比较多的样本
        """
        # 断言预测样本的特征维度必须和训练样本的特征维度相同
        assert x.shape[0] == self._X_train.shape[1]

        # 计算每一个样本和预测样本的欧氏距离
        distances = [sqrt(np.sum((x_train - x) ** 2))
                     for x_train in self._X_train]
        # 升序后的索引,即离预测样本x距离从小到大的排列。
        nearest = np.argsort(distances)
        # 取前k个样本的标签
        topK_y = [self._y_train[i] for i in nearest[:self.k]]
        # 投票
        votes = Counter(topK_y)
        # 取票数最多的

        # ! votes.most_common(1) 类型为list, 值的类型为tuple(元素, 个数) 如 [(0, 2)]
        return votes.most_common(1)[0][0]

    def _predict_with_distance(self, x):
        """ 核心接口
            预测样本到前k个训练样本距离的倒数作为权重
        """
        # 断言预测样本的特征维度必须和训练样本的特征维度相同
        assert x.shape[0] == self._X_train.shape[1]

        # 计算每一个样本和预测样本的欧氏距离
        distances = [sqrt(np.sum((x_train - x) ** 2))
                     for x_train in self._X_train]

        nearest = np.argsort(distances)
        nearest_k = nearest[:self.k]
        # topK_y是前k个样本所对应的标签
        topK_y = [self._y_train[i] for i in nearest_k]
        result = {}
        for y, k in zip(topK_y, nearest_k):
            if y in result:
                dist = result.get(y)
                dist += 1 / distances[k]
                result[y] = dist

            else:
                dist = 1 / distances[k]
                result[y] = dist
        # 找出最好的结果best_score是最结果的权重值
        best_score = -1
        best_label = None
        for key, value in result.items():
            if value > best_score:
                best_score = value
                best_label = key

        return best_label

    def score(self, X_test, y_test):
        """根据测试数据集 X_test 和 y_test 确定当前模型的准确度"""

        y_predict = self.predict(X_test)
        return accuracy_score(y_test, y_predict)


if __name__ == "__main__":
    data = load_iris()
    X_train = data.data
    y_train = data.target
    X_train, X_test, y_train, y_test = train_test_split(X_train, y_train, random_state=1)
    # 图片中测试数据
    # X_train = np.array([[3], [4], [5], [6], [1]])
    # y_train = np.array([0, 0, 2, 3, 1])
    # X_test = np.array([[0]])

    # 前k近个样本进行投票判断新样本标签
    # knn = KNNClassifier(k=5)

    # 考虑欧式距离作为权重
    knn = KNNClassifier(k=5, weights="distance")
    knn.fit(X_train, y_train)
    y_pred1 = knn.predict(X_test)
    print(accuracy_score(y_test, y_pred1)) # 1

    # p=2指定使用欧式距离
    knn2 = KNeighborsClassifier(n_neighbors=5, weights="distance", p=2)
    knn2.fit(X_train, y_train)
    y_pred2 = knn2.predict(X_test)
    print(accuracy_score(y_test, y_pred2)) # 1

代码和sklearn中封装的knn结果相同,可以证明代码暂时无问题,sklearn中实现的knn稍复杂一点。实验代码中没有加入数据归一化的操作,原因是load_iris()数据集合比较简单,没有明显的特征倾斜。

knn优缺点

优点
1)解决分类问题和多分类问题
2)思想简单,效果强大
3)可以解决回归问题

缺点
1)效率低,假设一个训练集有 m m m个样本 n n n个特征,则预测每一个新数据需要 O ( m ∗ n ) O(m*n) O(mn)
2)高度数据相关
3)预测结果不具有可解释性
4)维度的灾难

随着维度的增加,看似比较近的点,其两者之间的距离也会越来越大。

维度点坐标欧式距离
10到1的距离1
2(0,0)到(1,1)1.414
3(0,0,0)到(1,1,1)1.73
64(0,0,…,0)到(1,1,…,1)8
  • 19
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值