KNN算法:从思想到实现(附代码)

引言

K最近邻算法(K Nearest Neighbors, KNN)是一种简单而有效的机器学习算法,用于分类和回归问题。其核心思想基于“近朱者赤,近墨者黑”,即通过测量不同特征值之间的距离来进行分类或预测数值。本文将详细介绍KNN的核心概念、使用方法及其在sklearn中的实现,并展示如何自己动手编写一个简单的KNN算法。

新样本
寻找K个最近邻
分类问题:多数表决
回归问题:均值计算

KNN 核心思想

如何做一个样本的推理?首先需要明确这个问题是分类问题还是回归问题

  • 分类问题
    x0到底属于哪一类呢?
    对于一个新的样本x0,KNN首先找到与其最近的K个邻居,然后统计这些邻居中出现次数最多的类别作为x0的类别。

  • 回归问题
    x0到底是多少?
    对于回归问题,KNN同样找到与新样本x0​最近的K个邻居,但这次它计算的是这K个邻居标签的平均值,以此作为x0的预测值。

算法特点

惰性学习:KNN几乎没有训练过程,主要工作是在推理阶段完成,因此被称为惰性学习算法,(在推理时直接硬计算,这不属于典型的人工智能!)。

sklearn进行KNN操作

  • 分类问题示例
# 分类任务(鸢尾花数据集)
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
# 加载数据集
X, y = load_iris(return_X_y=True)
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)
# 初始化并训练模型
knn = KNeighborsClassifier(n_neighbors=5)
knn.fit(X=X_train, y=y_train)
# 预测及评估
y_pred = knn.predict(X=X_test)
acc = (y_pred == y_test).mean()
print(acc)
  • 回归问题示例
# 回归任务(波士顿房价数据集)
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsRegressor

# 加载数据
data = pd.read_csv(filepath_or_buffer="boston_house_prices.csv", skiprows=1)
X = data.drop(columns=["MEDV"]).to_numpy()
y = data["MEDV"].to_numpy()

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)

# 初始化并训练模型
knn = KNeighborsRegressor(n_neighbors=5)
knn.fit(X=X_train, y=y_train)

# 预测及评估
y_pred = knn.predict(X=X_test)
# 计算的是平均绝对误差(MAE)
# 首先,通过abs(y_pred - y_test)计算预测值与实际值之间的绝对差异,即对于每一个测试样本,计算其预测值减去真实值的绝对值。
# 然后,使用.mean()函数计算这些绝对差异的平均值,得到的就是MAE。
mae = abs(y_pred - y_test).mean()
# 这段代码用于计算均方误差(MSE)
# 首先,通过(y_pred - y_test) ** 2计算预测值与实际值之差的平方,这样做的目的是为了放大较大误差的影响,并且消除负号(因为误差可能是正也可能是负)。
# 之后,使用.mean()函数计算这些平方差的平均值,得到的就是MSE。
mse = ((y_pred - y_test) ** 2).mean()
print(mae, mse)
# 4.756078431372549,51.74387450980392

波士顿房价数据集
在这里插入图片描述
MAE衡量的是预测值与实际值之间差距的平均水平,不考虑差距的方向(正负),只关心差距的大小。MAE 提供了一个直观的平均误差度量,易于理解,因为它直接表示了预测值与真实值之间的平均绝对距离。

MSE同样衡量了预测值与实际值之间的差距,但由于采用了平方,它对较大的误差更加敏感,这意味着如果模型的某些预测非常不准确,MSE会显著增大。

这两个指标都是评估回归模型性能的重要工具,它们帮助我们了解模型预测值与真实值之间的接近程度。选择哪个指标取决于具体的业务需求以及你希望如何权衡不同大小的预测误差。例如,如果你认为较大的误差应该被更重地惩罚,那么MSE可能是一个更好的选择;反之,如果你更关注整体的平均表现,那么MAE可能更适合。

手写KNN算法实现

  • 分类问题实现
from collections import Counter
import numpy as np
class MyKNeighborsClassifier(object):
    """
        自定义KNN分类算法
    """
    def __init__(self, n_neighbors=5):
        """
            初始化方法:
                - 接收 超参数
        """
        self.n_neighbors = n_neighbors

    def fit(self, X, y):
        """
            训练过程
        """
        self.X = X
        self.y = y

    def predict(self, X):
        """
            推理过程
            这段代码实现了 KNN 分类的核心逻辑:
            1.对于输入的新样本集 X 中的每一个样本 x,
            2.计算它与训练集中所有样本之间的欧几里得距离,
            3.找出距离最近的 K 个邻居,
            4.统计这些邻居的目标变量(标签)中哪个类别出现次数最多,
            5.将出现次数最多的类别作为当前新样本 x 的预测类别,
            6.将所有新样本的预测结果汇总成一个数组并返回。
        """
        # X:[batch_size, num_features]
        
        # 第一步:寻找样本的 K个邻居
        # 第二步:对 K 个邻居的标签进行投票
        results = []
        # 循环遍历输入的新样本集 X 中的每一个样本 x。
        for x in X:
        	#计算距离
        	# self.X:训练数据集中的所有样本。
        	# (self.X - x):计算训练集中的每个样本与当前新样本 x 在各个特征上的差异。
        	# ** 2:对差异进行平方操作,确保所有值都是正数。
        	# .sum(axis=1):沿着每个样本的所有特征维度求和,得到每个训练样本到新样本 x 的欧几里得距离的平方和。
        	# ** 0.5:取平方根,得到实际的欧几里得距离。
            distance = ((self.X - x) ** 2).sum(axis=1) ** 0.5
            # 找到最近的 K 个邻居
            # distance.argsort():返回按升序排列的距离索引列表,即距离最近的样本排在前面。
            # [:self.n_neighbors]:选取前 self.n_neighbors 个最小距离对应的索引,即找到最近的 K 个邻居的索引。
            idxes = distance.argsort()[:self.n_neighbors]
            # 获取这些邻居的标签
            # self.y:训练集中对应样本的标签(目标变量)。
            # 使用之前找到的最近邻居的索引 idxes 来获取这些邻居的标签 labels。
            labels = self.y[idxes]
            # 对 K 个邻居的标签进行投票
            # 使用 collections.Counter 对这 K 个邻居的标签进行计数,统计每个标签出现的次数。
            # Counter(labels).most_common(1):返回一个列表,包含最常见的标签及其出现次数,按频率降序排列。most_common(1) 只返回出现次数最多的那个标签及其计数。
            # [0][0]:从上述列表中提取出最常见的标签(第一个元素的第一个值),作为最终的预测标签 final_label。
            final_label = Counter(labels).most_common(1)[0][0]
            results.append(final_label)
        return np.array(results)
## 使用自定义KNN进行分类
my_knn = MyKNeighborsClassifier(n_neighbors=5)
my_knn.fit(X=X_train, y=y_train)
y_pred = my_knn.predict(X=X_test)
print((y_pred == y_test).mean())
  • 回归问题实现
class MyKNeighborsRegressor(object):
    """
        自定义KNN回归算法
    """
    def __init__(self, n_neighbors=5):
        """
            初始化方法:
                - 接收 超参数
        """
        self.n_neighbors = n_neighbors

    def fit(self, X, y):
        """
            训练过程
        """
        self.X = X
        self.y = y

    def predict(self, X):
        """
            推理过程
            这段代码实现了 KNN 回归的核心逻辑:
			1.对于输入的新样本集 X 中的每一个样本 x,
			2.计算它与训练集中所有样本之间的欧几里得距离,
			3.找出距离最近的 K 个邻居,
			4.计算这些邻居的目标变量(标签)的平均值作为预测结果,
			5.将所有新样本的预测结果汇总成一个数组并返回。
        """
        # X:[batch_size, num_features]
        
        # 第一步:寻找样本的 K个邻居
        # 第二步:对K个邻居的标签取均值
        results = []
        # 循环遍历输入的新样本集 X 中的每一个样本 x
        for x in X:
        	# 计算距离
        	# self.X:训练数据集中的所有样本。
        	# (self.X - x):计算训练集中的每个样本与当前新样本 x 在各个特征上的差异。
        	# ** 2:对差异进行平方操作,以确保所有值都是正数。
        	# .sum(axis=1):沿着每个样本的所有特征维度求和,得到每个训练样本到新样本 x 的欧几里得距离的平方和。
        	# ** 0.5:取平方根,得到实际的欧几里得距离。
            distance = ((self.X - x) ** 2).sum(axis=1) ** 0.5
            # 找到最近的 K 个邻居
            # distance.argsort():返回按升序排列的距离索引列表,即距离最近的样本排在前面。
            # [:self.n_neighbors]:选取前 self.n_neighbors 个最小距离对应的索引,即找到最近的 K 个邻居的索引。
            idxes = distance.argsort()[:self.n_neighbors]
            # 获取这些邻居的标签
            # self.y:训练集中对应样本的标签(目标变量)。
            # 使用之前找到的最近邻居的索引 idxes 来获取这些邻居的标签 labels。
            labels = self.y[idxes]
            # 计算最终预测值
            # 对于回归问题,计算这 K 个最近邻居的标签值的平均值作为当前新样本 x 的预测值 final_label。
            final_label = labels.mean()
            results.append(final_label)
        return np.array(results)

# 使用自定义KNN进行回归
knn = MyKNeighborsRegressor(n_neighbors=5)
knn.fit(X=X_train, y=y_train)
y_pred = knn.predict(X=X_test)
mae = abs(y_pred - y_test).mean()
mse = ((y_pred - y_test) ** 2).mean()
print(mae, mse)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/964679.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

学前端框架之前,你需要先理解 MVC

MVC 软件架构设计模式鼎鼎大名,相信你已经听说过了,但你确定自己已经完全理解到 MVC 的精髓了吗? 如果你是新同学,没听过 MVC,那可以到网上搜一些文章来看看,不过你要有心理准备,那些文章大多都…

第十八章 视图

目录 一、概述 二、语法 2.1. 创建视图 2.2. 查询视图 2.3. 修改视图 2.4. 删除视图 2.5. 示例 三、检查选项 3.1. CASCADED(级联) 3.2. LOCAL(本地) 四、视图的更新 五、视图作用 5.1. 简单 5.2. 安全 5.3. 数据独…

【MySQL】第一弹---MySQL 在 Centos 7环境安装

✨个人主页: 熬夜学编程的小林 💗系列专栏: 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】【MySQL】 目录 1. 卸载不要的环境 2. 检查系统安装包 3. 卸载这些默认安装包 4. 获取mysql官方yum源 5. 安装mysql yum 源&am…

实验2 词法分析(一)

实验2 词法分析(一) [实验目的]: 1 . 熟悉给定的词法分析程序; 2 . 改进词法分析程序。 [实验内容]: 1.运行TESE编译演示.exe,观看词法分析程序的分析过程,理解词法分析的原理。并尝试在“TEST源程序输入框”输入一段…

【PyQt】PyQt工具栏

PyQt工具栏 在 PyQt 中创建工具栏主要涉及 QMainWindow、QToolBar 和 QAction 类 界面展示 基本示例 import sys from PyQt5.QtWidgets import QMainWindow, QApplication, QAction from PyQt5.QtGui import QIcon from PyQt5.QtCore import Qtclass MainWindow(QMainWindow…

STM32 串口收发数据包

接线图 HEX数据包接收 文本数据包接收 代码配置 发送HEX数据包 //存储发送或接收的载荷数据 uint8_t TX_Packet[4]; uint8_t RX_Packet[4];void Serial_SendPacket(void) {Serial_SendByte(0xFF);//发送包头Serial_SendArray(TX_Packet, 4);//发送4个载荷数据Serial_SendByte…

zabbix5.0.46版本源码安装

zabbix5.0.46版本源码安装 1.安装环境说明 本例中安装zabbix开源软件和zabbix运行所需的中间件和数据库apache、php和flyingdb,软件版本信息如下: 软件版本zabbix5.0.46apachehttpd-2.4.61aprapr-1.7.5apr-util1.6.3php7.3.24PostgreSQL16.6 主机操作…

[Android] IKTV专享版

[Android] IKTV专享版 链接:https://pan.xunlei.com/s/VOILXXuEd3ASo93c88UW79sxA1?pwd4tsw# 2025年2月最新免费K歌神器!家庭KTV软件,手机平板电视盒子电脑都可用

【OS】AUTOSAR架构下的Interrupt详解(下篇)

目录 3.代码分析 3.1中断配置代码 3.2 OS如何找到中断处理函数 3.3 Os_InitialEnableInterruptSources实现 3.4 Os_EnableInterruptSource 3.5 DisableAllInterrupts 3.5.1Os_IntSuspendCat1 3.5.2 Os_InterruptDisableAllEnter 3.5.3 Disable二类中断 3.5.4 Disable一…

flutter 专题四十七 Flutter 应用启动流程分析

众所周知,任何应用程序的启动都是从main()函数开始的,Flutter也不例外,main.dart文件的main函数开始的,代码如下。 void main() > runApp(MyApp());main函数则调用的是runApp函数,源码如下。 void runApp(Widget …

html中的表格属性以及合并操作

表格用table定义,标签标题用caption标签定义;用tr定义表格的若干行;用td定义若干个单元格;(当单元格是表头时,用th标签定义)(th标签会略粗于td标签) table的整体外观取决…

大语言模型轻量化:知识蒸馏的范式迁移与工程实践

大语言模型轻量化:知识蒸馏的范式迁移与工程实践 🌟 嗨,我是LucianaiB! 🌍 总有人间一两风,填我十万八千梦。 🚀 路漫漫其修远兮,吾将上下而求索。 摘要 在大型语言模型&#xff…

Go语言的转义字符

文章目录 1. Go语言的转义字符(escapechar)2. 小结和提示 1. Go语言的转义字符(escapechar) 说明:常用的转义字符有如下: \t : 表示一个制表符,通常使用它可以排版\n :换行符\\ :一个\\" :一个"\r :一个回…

Docker深度解析:容器与容器局域网

DockerFile 解析: DockerFile 描述出镜像文件需要的一些依赖配置和环境变量执行命令 docker build,将我们的 dockerfile 文件打包成一个镜像文件直接使用我们的容器运行到该镜像文件 CentOS 镜像: 运行镜像: docker run -it cent…

360手机刷机 360手机解Bootloader 360手机ROOT

360手机刷机 360手机解Bootloader 360手机ROOT 问:360手机已停产,现在和以后,能刷机吗? 答:360手机,是肯定能刷机的 360手机资源下载网站 360手机-360手机刷机RootTwrp 360os.top 360rom.github.io 一、…

C++输入输出(上)

cin和cout cin是C中提供的标准输入流对象,一般针对的是键盘,也就是从键盘上输入的字符流,使用 cin来进行数据的提取,cin一般是和 >> (流提取运算符) 配合使用的。 cin的功能和scanf是类似的 cout是C中提供的标准输出流对象,一般针对的是控制台的窗口,也就是将数据以字符…

【沐风老师】3DMAX混沌破碎插件ChaosFracture使用方法

3DMAX混沌破碎插件ChaosFracture,只需一键操作,即可轻松实现物体的破碎效果,同时确保外表面与内部断裂部分保持原有的材质ID和UVs信息,真实呈现细腻的破碎场景。 【适用版本】 3DMax9及更高版本(建议使用3DMax2018以上…

e2studio开发RA2E1(8)----GPT定时器频率与占空比的设置

e2studio开发RA2E1.8--GPT定时器频率与占空比的设置 概述视频教学样品申请硬件准备参考程序源码下载选择计时器时钟源PWM(脉冲宽度调制)R_GPT_PeriodSet()函数说明R_GPT_DutyCycleSet()函数说明R_GPT_Reset()函数说明R_GPT_Close() 函数说明主程序波形情况 概述 GPT&#xff0…

7.PPT:“中国梦”学习实践活动【20】

目录 NO1234​ NO5678​ NO9\10\11 NO1234 考生文件夹下创建一个名为“PPT.pptx”的新演示文稿Word素材文档的文字:复制/挪动→“PPT.pptx”的新演示文稿(蓝色、黑色、红色) 视图→幻灯片母版→重命名:“中国梦母版1”→背景样…

基于Flask的大模型岗位招聘可视化分析系统的设计与实现

【FLask】基于Flask的大模型岗位招聘可视化分析系统的设计与实现(完整系统源码开发笔记详细部署教程)✅ 目录 一、项目简介二、项目界面展示三、项目视频展示 一、项目简介 该系统采用Python作为主要开发语言,结合Echarts可视化库&#xff0…