K近邻

这篇博客给大家介绍一下k近邻分类算法。k近邻算法可以认为是机器学习中最简单的算法了,其原理简单,直观。

k近邻输入为样本的特征向量;输出为样本类别,不只是二分类,也可以是多分类问题。

k近邻算法的基本原理:当给定一个新的样本时,首先在训练集中寻找距离新样本最邻近的k个样本,这k个样本如果多数属于某个类,则将新样本归类为这个类。简单可以总结为一句话,近朱者赤近墨者黑。

从k近邻算法的基本原理中又引出来一个概念"距离"。距离可以衡量两个样本之间的相似度,那么,如何计算两个样本之间的距离呢?距离的种类有好多种,最常用的可能就是欧氏距离了,但不限于此,也可以使用其他距离,例如更一般的距离,下面具体介绍一下距离的计算公式:

设样本的特征空间是n维实数向量空间,假设在特征空间中存在两个样本点以及,其中,则的定义如下:


p=1:距离又称之为曼哈顿距离,或者为街区距离,此时,如下图所示:


橙色线的总长度,或者是蓝色线的总长度就是点到点的曼哈顿距离。

p=2:距离就是欧氏距离,即,如下图所示:


绿色线的长度就是点到点的欧氏距离。

p=∞:,在这里可能有些难以理解,下面进行简单的推导:


如果存在两个数,则,因此如果两个样本的特征在某个维度上的差值都大于其他维度上的差值,则

因此,原式


如下图


为红色线段和蓝色线段较长的那一条线段的长度。

我们在KNN模型中可以自己选择合适的距离度量方案。

最后,说一下K近邻的总的流程:

step1:寻找距离待预测样本点最近的k个训练样本,这k个样本组成的集合为

step2:寻找集合中所占比例最高的样本类别,作为待预测样本的预测类别(多数表决),如下式所示,最终预测类别y为

,其中为指示函数,即当中的condition为真时,的值为1,否则为0。

k的取值也会对K近邻的预测结果产生较大的影响:

如果选择较小的k,则只有距离待预测样本点较近的少数样本起到了作用,这样容易过拟合;反之如果选择很大的k值,则容易欠拟合,试想如果k大到为所有训练样本的个数,那么对于任意带预测样本,其预测结果都一样,永远为训练样本中所占比例最多的那个类。

如下图所示:


如果k取1或2,那么很有可能待预测样本(图中标问号的样本)被预测为红色那一类别,然而实际上从图上可以看出,它应该更有可能为蓝色类别。

因此,合适的k的取值对模型最终效果起到了关键作用。

最后附上本人使用Python编写的Knn相关程序以供大家参考:

from sklearn.datasets import load_iris, load_digits
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
import numpy as np
np.set_printoptions(linewidth=1000, suppress=True)


class KNN(object):

    def __init__(self, K, test_size, p, **kwargs):
        """

        :param K: 近邻个数K,最好取奇数
        :param test_size: 训练样本占总样本的比例
        :param p: Lp距离中的p,如果为1则为曼哈顿距离或称之为街区距离,如果为2就是常用的欧氏距离
        :param kwargs: 如果不传值,则样本数据为鸢尾花数据,
        如果想使用其他数据,则以关键字参数的形式传入特征向量矩阵x和对应的类标记y
        """
        self.data = load_iris()
        if not kwargs:
            self.x = self.data["data"]
            self.y = self.data["target"]
        else:
            try:
                self.x = kwargs["x"]
                self.y = kwargs["y"]
            except KeyError:
                raise Exception("特征向量必须以x为关键字参数传入,标记必须以y为关键字参数传入")
        # self.classes为所有类别标记
        self.classes = np.unique(self.y)
        self.x_train, self.x_test, self.y_train, self.y_test = train_test_split(self.x, self.y, test_size=test_size, random_state=1)
        self.K = K
        self.p = p

    def calc_distance(self, p_1):
        # 计算点p_1到所有训练样本的距离
        return np.power(np.sum(np.power(np.abs(np.array(p_1) - self.x_train), self.p), axis=1), 1 / self.p)

    def predict(self, predict_samples):
        """
        可以调用此方法对样本进行预测
        :param predict_samples: 待预测的样本
        :return: 预测结果
        """
        # 将每一个待预测样本predict_samples[i]分别与所有的训练样本求距离,放入self.distances中,len(self.distances)应该等于len(predict_samples)
        self.distances = []
        for s in predict_samples:
            self.distances.append(self.calc_distance(s))
        self.distances = np.array(self.distances)
        sort_index = np.argsort(self.distances, axis=1)
        # self.nearest_train_sample_label用于存放与每个待预测样本距离最近的前self.K个训练样本的标记
        self.nearest_train_sample_label = []
        for srt_index in sort_index:
            self.nearest_train_sample_label.append(self.y_train[srt_index][:self.K])
        self.nearest_train_sample_label = np.array(self.nearest_train_sample_label)
        self.y_pred = []
        for i in self.nearest_train_sample_label:
            a = [list(i).count(self.classes[j]) for j in range(len(self.classes))]
            self.y_pred.append(self.classes[a.index(max(a))])
        return np.array(self.y_pred)

    def pred_test(self):
        # 预测测试集
        y_test_pred = self.predict(self.x_test)
        print("测试集真实类别标记:", self.y_test)
        print("测试集预测类别标记:", y_test_pred)
        print("测试集预测准确率:%.2f%s" % (accuracy_score(self.y_test, y_test_pred) * 100, "%"))


def main():
    print("=========鸢尾花数据============")
    knn_flower = KNN(9, 0.3, 2)
    knn_flower.pred_test()
    print("========手写数字数据===========")
    knn_hand_writting = KNN(9, 0.3, 2, x=load_digits()["data"], y=load_digits()["target"])
    knn_hand_writting.pred_test()


if __name__ == "__main__":
    main()


猜你喜欢

转载自blog.csdn.net/yy2050645/article/details/80905876