【手撕机器学习经典算法-01】KNN(K-邻近)算法

前言

最近在上一门《机器学习算法实验》的课程,有一些课堂小实验,还要写实验报告,笔者已经两三年没有写过实验报告了,梦回本科了属于是。刚好借此机会,开启一个“手撕机器学习十大经典算法”的系列,通过动手实践,掌握算法的原理。今天要分享的是KNN(K-邻近)算法。

问题描述

编写实现KNN分类器,利用钞票鉴别数据集测试KNN分类器效果,并对比sklearn工具包的分类结果。

基本原理

K-近邻算法采取多数表决,无需求解计算模型参数,也无需训练过程。只需要提供一组带标签的训练数据,因此属于监督学习算法。

算法原理:给定一组训练数据(包括特征和标签)和一个参数k,计算输入特征向量到训练数据各样本的特征向量的距离,选取距离最近的k个样本,根据这k个样本中的标签数进行投票表决,票数最多的标签即为预测结果。

KNN算法三要素:

  • k的取值:应当适中
  • 距离度量:欧式距离、马氏距离等
  • 分类决策:多数表决投票决策

KNN算法描述

给定n个训练样本(xi, yi),i=1,…,n,其中xi为样本i的特征向量,yi为标签值,设定参数k。待分类样本的特征向量为x,则KNN算法预测其标签的算法步骤如下:

  1. 迭代所有训练样本

  2. 对于每个样本i,计算特征向量xi与x之间的距离di

  3. 从n个样本中选取d最小的k个样本,作为邻近样本

  4. 统计k个邻近样本中每个标签出现的次数

  5. 选择出现频率最高的标签作为待分类样本的预测结果类别

代码详解

需要提前安装的包有:

  • numpy
  • pandas
  • sklearn:
  • matplotlib:用来绘图
  • tqdm:可选,用来展示模型训练/预测进度

step1:引入需要使用的包

import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
import matplotlib.pyplot as plt
from tqdm import tqdm

step2:定义一个class,封装KNN模型的拟合和预测方法

class KNN(object):
    def __init__(self, k):
        self.k = k
        self.X_train = None
        self.y_train = None

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

    def predict(self, X_test):
        X_test = np.array(X_test)
        X_train_len = len(self.X_train)
        x_test_len = len(X_test)
        pred_label = []		# 用来存储每个待测样本的预测结果标签

        for test_len in range(x_test_len):		# 遍历所有待测样本
            dis = []							# 用来存储所有训练样本到当前待测样本的距离
            for train_len in range(X_train_len):# 遍历所有的训练样本
                dis.append(np.sqrt(np.sum((self.X_train[train_len] - X_test[test_len]) ** 2)))	# 计算欧式距离
            dis = np.array(dis)					# 转为np.array是为了使用numpy的argsort方法
            index = np.argsort(dis)				# argsort方法计算排序后的元素在原数组中的下标值
            label = []							# 用来记录k个最邻近样本的标签
            for i in range(self.k):				
                label.append(self.y_train[index[i]])
            pred_label.append(max(set(label), key=label.count))	# 将label中出现次数最多的元素作为分类结果加入到pred_label中
        return pred_label
  • k值作为非常重要的参数,我们在模型创建时就初始化
  • 因为KNN算法完全不需要训练,所以实际上fit函数,只是保存下了训练样本(或者可以理解成训练样本就是模型参数)
  • predict函数,对一组测试样本进行分类,详细解释参考代码注释。

    这里稍微解释一下pred_label.append(max(set(label), key=label.count))这行代码
    max函数有两个参数:

    • set(label):将label转化为集合,以去重
    • label.count:是一个函数,接受一个参数,返回label中该值出现的次数

    label.count作为max函数的key参数,则在对 set(label)求最大值的过程中,会基于该值出现的次数来排序

step3:声明一些工具方法

# 声明一个计算准确率的方法
def get_accracy(y_true, y_pred):
    accracy = 0
    for i in range(len(y_true)):
        if y_true[i] == y_pred[i]:
            accracy += 1
    return accracy / len(y_true)

# 使用指定的模型和数据,预测和计算准确率
def pred_and_accracy(model, X_train, X_test, y_train, y_test):
    model.fit(X_train, y_train)
    pred = model.predict(X_test)
    return pred, get_accracy(y_test, pred)

step4:编写主函数

本代码使用的银行钞票鉴别数据集,可点击进入网站下载
该数据集一共5列,前4列为特征,最后一列为类别(真/伪)

def main():
    # 输入数据和预处理
    filename = 'data_banknote_authentication.txt'
    col_name = ['variance', 'skewness', 'curtosis', 'entropy', 'class']
    dataset = pd.read_csv(filename, header=None, names=col_name)
    data = dataset.values
    feat = data[:,:-1]
    label = data[:,-1]
    # 划分训练集和验证集
    X_train, X_test, y_train, y_test = train_test_split(feat, label, test_size=0.2, random_state=100)
    print(X_train.shape, X_test.shape, y_train.shape, y_test.shape)
    
    my_accs = []    # 记录我的KNN模型的准确率
    sk_accs = []    # 记录sklearn库提供的KNN模型的准确率

    # 遍历不同的k,分别计算两个模型的准确率
    k_range = range(1, 50)
    for k in tqdm(k_range):		# 使用tqdm可以在控制台中展示当前的迭代进度
        # 我的KNN模型
        my_knn = KNN(k)
        pred, acc = pred_and_accracy(my_knn, X_train, X_test, y_train, y_test)
        my_accs.append(acc)
        # sklearn库提供的KNN模型
        sklearn_knn = KNeighborsClassifier(n_neighbors=k)
        pred, acc = pred_and_accracy(sklearn_knn, X_train, X_test, y_train, y_test)
        sk_accs.append(acc)
    
    # 使用折线图绘制准确率随k取值的变化曲线
    fig = plt.figure()
    ax = fig.add_subplot(121)
    ax.set_title('My KNN')
    ax.set_xlabel('k')
    ax.set_ylabel('Accuracy')
    plt.plot(k_range, my_accs)
    ax = fig.add_subplot(122)
    ax.set_title('sklearn KNN')
    ax.set_xlabel('k')
    plt.plot(k_range, sk_accs)
    plt.show()


if __name__ == '__main__':
    main()

案例测试结果

在这里插入图片描述
当k的取值大于一定值后,算法的准确率开始下降和波动。

一些思考和问题

  1. 时间复杂度优化

    如果分析一下本代码中的时间复杂度,可以发现,由于进行了全排序,假设采用快速排序,时间复杂度为O(nlogn)。但实际上不需要进行全排序,只需要排出前k个样本即可。那么可以考虑插入排序或者选择排序,那么时间复杂度为O(kn),通常k<<n。如果再极端一点,可以用堆排序,建堆的时间复杂度为O(n),排k个最小值的时间复杂度为O(klogn)。

  2. 本代码中使用欧式距离作为距离度量,不同距离度量之间有什么区别?如何选择?
    这个问题笔者懒得研究了,先贴出chatgpt的回答,后续有需要时可作为参考:
    在这里插入图片描述
    在这里插入图片描述

课程简介:  本项目课程是一门极具综合性和完整性的大型项目课程;课程项目的业务背景源自各类互联网公司对海量用户浏览行为数据和业务数据分析的需求及企业数据管理、数据运营需求。 本课程项目涵盖数据采集与预处理、数据仓库体系建设、用户画像系统建设、数据治理(元数据管理、数据质量管理)、任务调度系统、数据服务层建设、OLAP即席分析系统建设等大量模块,力求原汁原味重现一个完备的企业级大型数据运营系统。  拒绝demo,拒绝宏观抽象,拒绝只讲不练,本课程高度揉和理论与实战,并兼顾各层次的学员,真正从0开始,循序渐进,每一个步骤每一个环节,都会带领学员从需求分析开始,到逻辑设计,最后落实到每一行代码,所有流程都采用企业级解决方案,并带领学员一一实现,拒绝复制粘贴,拒绝demo化的实现。并且会穿插大量的原创图解,来帮助学员理解复杂逻辑,掌握关键流程,熟悉核心架构。   跟随项目课程,历经接近100+小时的时间,从需求分析开始,到数据埋点采集,到预处理程序代码编写,到数仓体系搭建......逐渐展开整个项目的宏大视图,构建起整个项目的摩天大厦。  由于本课程不光讲解项目的实现,还会在实现过程中反复揉和各种技术细节,各种设计思想,各种最佳实践思维,学完本项目并勤于实践的话,学员的收获将远远超越一个项目的具体实现,更能对大型数据系统开发产生深刻体悟,对很多技术的应用将感觉豁然开朗,并带来融会贯通能力的巨大飞跃。当然,最直接的收获是,学完本课程,你将很容易就拿到大数据数仓建设或用户画像建设等岗位的OFFER课程模块: 1. 数据采集:涉及到埋点日志flume采集系统,sqoop业务数据抽取系统等; 2. 数据预处理:涉及到各类字典数据构建,复杂结构数据清洗解析,数据集成,数据修正,以及多渠道数据的用户身份标识打通:ID-MAPPING等;3. 数据仓库:涉及到hive数仓基础设施搭建,数仓分层体系设计,数仓分析主题设计,多维分析实现,ETL任务脚本开发,ETL任务调度,数据生命周期管理等;4. 数据治理:涉及数据资产查询管理,数据质量监控管理,atlas元数据管理系统,atlas数据血缘管理等;5. 用户画像系统:涉及画像标签体系设计,标签体系层级关系设计,各类标签计算实现,兴趣类标签的衰减合并,模型标签的机器学习算法应用及特征提取、模型训练等;6. OLAP即席分析平台:涉及OLAP平台的整体架构设计,技术选型,底层存储实现,Presto查询引擎搭建,数据服务接口开发等;7. 数据服务:涉及数据服务的整体设计理念,架构搭建,各类数据访问需求的restapi开发等;课程所涉及的技术: 整个项目课程中,将涉及到一个大型数据系统中所用到的几乎所有主要技术,具体来说,包含但不限于如下技术组件:l Hadoopl Hivel HBasel SparkCore /SparkSQL/ Spark GRAPHX / Spark Mllibl Sqoopl Azkabanl Flumel lasal Kafkal Zookeeperl Solrl Prestop
### 关于头歌平台中KNN算法机器学习教程与实例 #### 头歌平台概述 头歌(Tougo)是一个专注于计算机科学教育的学习平台,提供丰富的在线课程资源和实践环境。对于机器学习领域的内容,尤其是像KNN这样经典的算法,通常会通过理论讲解、代码实现以及实际应用案例相结合的方式进行教学。 #### KNN算法简介 KNN(K-Nearest Neighbors)是一种基于实例的学习方法,既可用于分类也可用于回归分析。其核心思想是:给定一个测试样本,在训练集中找到与其最近的K个邻居,并依据这K个邻居的信息来进行决策[^2]。 #### KNN算法的主要步骤 1. 数据预处理阶段,包括标准化或归一化操作以消除不同特征间量纲差异的影响。 2. 计算待测样本到所有已知样本的距离,常用欧氏距离或其他形式的距离度量方式。 3. 找出距离最小的前K个样本作为近邻点集合。 4. 对于分类任务采用投票机制决定最终类别;而对于回归任务则取平均值或者加权平均值得出结果。 #### 距离计算公式示例 以下是两种常见距离公式的Python实现: ```python import numpy as np def euclidean_distance(x, y): """欧几里得距离""" return np.sqrt(np.sum((np.array(x) - np.array(y)) ** 2)) def manhattan_distance(x, y): """曼哈顿距离""" return np.sum(abs(np.array(x) - np.array(y))) ``` 上述函数分别实现了欧氏距离和曼哈顿距离的计算过程。 #### 实际应用场景举例 假设我们有一个简单的电影分类场景,其中每部影片由两个属性描述:“拥抱次数”和“打斗次数”。利用已有标注的数据集可以构建模型并预测未知标签的新样例所属类型[^4]。 #### 可能存在的挑战及优化方向 尽管KNN易于理解和实现,但在大规模数据集上的性能可能较差,因为每次都需要遍历整个数据库寻找最接近的邻居。因此可以通过KD树索引结构加速查询效率,或是引入降维技术减少维度灾难带来的影响[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ryan2k

请作者喝一杯咖啡吧~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值