0
点赞
收藏
分享

微信扫一扫

ML 自实现/KNN/分类/无权重

慎壹 2022-02-08 阅读 34

简介

KNN(K Nearest Neighbors)

  • 可用于分类问题也可用于回归问题
  • 分类问题和回归问题又划分为是否带权重

举例

在这里插入图片描述
介绍

  • 已经有的类别红色的三角形和蓝色的正方形
  • 新输入的这个绿色点应该属于什么类型(红色的三角形还是蓝色的正方形)
  • 当K=3时,找到1个蓝色的正方形和2个红色的三角形,少数服从多数,认为新输入的绿色点是红色三角形类别
  • 极端情况,当K=1时,距离新输入的绿色点最近的那个图形是什么形状,新输入的绿色点就是什么形状
  • 极端情况,当K=训练集样本数时,整个训练集样本那个类别的图形数量最多,新输入的绿色点就属于这个形状

原理

KNN(K Nearest Neighbors)

  • K个最近的邻居算法
  • 先找到K个最近的邻居,再少数服从多数
  • 新的输入实例,在训练数据集中找到与该实例最相似(邻近)的K个实例,这K个实例的多数属于某个类别,就把该输入实例分类到这个类别

相似度衡量

similarity

  • 相似度通过距离衡量
  • 越相似,代表输入实例与训练实例之间的距离越近

距离定义
设特征空间 X X X是m维实数向量空间 R n R^n Rn x i , x j ∈ X x_i,x_j\in X xi,xjX x i = ( x i ( 1 ) , x i ( 2 ) , . . . , x i ( m ) ) T x_i=(x_i^{(1)},x_i^{(2)},...,x_i^{(m)})^T xi=(xi(1),xi(2),...,xi(m))T x j = ( x j ( 1 ) , x j ( 2 ) , . . . , x j ( m ) ) T x_j=(x_j^{(1)},x_j^{(2)},...,x_j^{(m)})^T xj=(xj(1),xj(2),...,xj(m))T

  • x i , x j x_i,x_j xi,xj L p L_p Lp距离: L p ( x i , x j ) = ( ∑ l = 1 m ∣ x i ( l ) − x j ( l ) ∣ p ) 1 p L_p(x_i,x_j)=\Big(\sum\limits_{l=1}^m|x_i^{(l)}-x_j^{(l)}|^p\Big)^{\frac{1}{p}} Lp(xi,xj)=(l=1mxi(l)xj(l)p)p1
  • p = 2 p=2 p=2欧式距离: L 2 ( x i , x j ) = ( ∑ l = 1 m ∣ x i ( l ) − x j ( l ) ∣ 2 ) 1 2 L_2(x_i,x_j)=\Big(\sum\limits_{l=1}^m|x_i^{(l)}-x_j^{(l)}|^2\Big)^{\frac{1}{2}} L2(xi,xj)=(l=1mxi(l)xj(l)2)21

K值确定

  • 根据举例可知,K=1和K=训练集样本数时都不是合适的K值,那么如何确定1个合适大小的K值
  • 在应用中,K值一般取一个比较小的数值,通常采用交叉验证法来选取最优的K值。

代码

介绍

  • 使用sklearn提供的数字数据集,它由1797幅手写数字图像组成,每个数字由8x8的像素值向量表示。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split

np.random.seed(1)


def get_data_set():
    # 数字数据集,每个数字由8x8的像素点组成
    digits = load_digits()
    # data:8x8像素的图像;target:图像代表的数字
    X, y = digits.data, digits.target

    # 样例展示 0~9的图片数字
    # figure width=10, height=8》在figure上添加子图,10个axes,每个横轴跨度=2,纵轴跨度=5
    # 可知pyplot的使用:figure》figure上axes(可以画多个)》在axes上画图
    fig = plt.figure(figsize=(10, 8))
    for i in range(10):
        # 将坐标轴添加到fig中 rows=2,column=5,index=i+1:表示从左到右边从上到下第几个ax
        ax = fig.add_subplot(2, 5, i + 1)
        # 在axes上展示图片,imshow=imageshow,cmap=Colormap
        plt.imshow(X[i].reshape((8, 8)), cmap='gray')
    plt.show()

    # 划分数据集为训练集和测试集
    # 例如:X_train.shape=(1347,64); y_train.shape=(1347,); X_test.shape=(450,64); y_test.shape=(450,)
    X_train, X_test, y_train, y_test = train_test_split(X, y)
    print('X_train.shape=', X_train.shape)
    print('y_train.shape=', y_train.shape)
    print('X_test.shape=', X_test.shape)
    print('y_test.shape=', y_test.shape)

    data_set = DataSet(X_train, y_train, X_test, y_test)
    return data_set


class DataSet(object):
    """
    X_train 训练集样本
    y_train 训练集样本值
    X_test 测试集样本
    y_test 测试集样本值
    """

    def __init__(self, X_train, y_train, X_test, y_test):
        self.X_train = X_train
        self.y_train = y_train
        self.X_test = X_test
        self.y_test = y_test


class K_NN():
    """
    k-nearest-neighbor 类
    """

    def __init__(self, X, y):
        """
        :param X: X_train
        :param y: y_train
        """
        self.X = X
        self.y = y

    def euclidean_distance(self, X):
        """
        X和X_train的欧式距离
        """
        # X.shape结果(n,64)即n_samples=n
        print("X.shape=", X.shape)
        m, _ = X.shape
        # axis=1,行求和;axis=0,列求和
        # L2是(n,1)的矩阵
        L2 = [np.sqrt(np.sum((self.X - X[i]) ** 2, axis=1)) for i in range(m)]
        # array转ndarray
        return np.array(L2)

    def hypothesis(self, X, k=1):
        """
        X:待预测数据,矩阵
        k:距离X最近的k个对象
        """
        # step1:计算欧式距离
        dists = self.euclidean_distance(X)

        # step 2: 找到k个最近的邻居和这些邻居所属的类别
        # 每列从小到达排序,再取每列的k个元素的下标
        idxk = np.argsort(dists)[:, :k]
        print("idxk.shape=", idxk.shape)
        # y_idxk是矩阵(n,k)
        y_idxk = self.y[idxk]
        print("y_idxk.shape=", y_idxk.shape)

        if k == 1:
            # 切换成行向量,便于展示
            return y_idxk.T
        else:
            m, _ = X.shape
            # y_idxk是数组(n,k)》max_votes是数组(n,1)
            # 投票 key是1个匿名函数,参数为y_idxk》统计y_idxk[i]中每个元素出现的次数》max则返回出现次数最多的元素(整个过程就是少数服从多数)
            max_votes = [max(y_idxk[i], key=list(y_idxk[i]).count) for i in range(m)]
            return max_votes


def evaluate_model(knn, X_test, y_test):
    y_p_test1 = knn.hypothesis(X_test, k=1)
    test_acc1 = np.sum(y_p_test1[0] == y_test) / len(y_p_test1[0]) * 100
    print("k=1时,测试精度:", test_acc1)
    print("---------------------")

    y_p_test3 = knn.hypothesis(X_test, k=3)
    test_acc3 = np.sum(y_p_test3 == y_test) / len(y_p_test3) * 100
    print("k=3时,测试精度:", test_acc3)
    print("---------------------")

    y_p_test5 = knn.hypothesis(X_test, k=5)
    test_acc5 = np.sum(y_p_test5 == y_test) / len(y_p_test5) * 100
    print("k=5时,测试精度:", test_acc5)
    print("---------------------")


def show_result(knn, data_set):
    """
    展示训练结果
    """
    print("k=1,1个最近邻居")
    # data_set.X_test[0]是tuple类型
    n = data_set.X_test[0].shape[0]
    # data_set.X_test[0].reshape(-1,n)将(64,)转为(1,64)矩阵
    print("预测类别:", knn.hypothesis(data_set.X_test[0].reshape(-1, n), k=1))
    print("真实类别:", data_set.y_test[0])
    print("---------------------")

    print("k=5,5个最近邻居")
    n = data_set.X_test[20].shape[0]
    print("预测类别:", knn.hypothesis(data_set.X_test[20].reshape(-1, n), k=5))
    print("真实类别:", data_set.y_test[20])
    print("---------------------")

    print("测试10行数据x5~x14;k=1,1个最近邻居")
    print("预测类别们:", knn.hypothesis(data_set.X_test[5:15], k=1))
    print("真实类别们:", data_set.y_test[5:15])
    print("---------------------")

    print("测试10行数据x5~x14;k=4,4个最近邻居")
    print("预测类别们:", knn.hypothesis(data_set.X_test[5:15], k=4))
    print("真实类别们:", data_set.y_test[5:15])
    print("---------------------")


def main():
    # 获取数据集》划分数据集》展示数据集
    data_set = get_data_set()
    # 构造KNN
    knn = K_NN(data_set.X_train, data_set.y_train)
    # 使用测试集评估模型
    evaluate_model(knn, data_set.X_test, data_set.y_test)
    # 展示结果
    show_result(knn, data_set)


if __name__ == "__main__":
    main();

举报

相关推荐

0 条评论