一、关于KNN算法

     KNN(K Near Neighbor):k个最近的邻居,即每个样本都可以用它最接近的k个邻居来代表。KNN算法属于监督学习方式的分类算法,我的理解就是计算某给点到每个点的距离作为相似度的反馈。
     KNN就是“近朱者赤,近墨者黑”的一种分类算法。
     KNN是一种基于实例的学习,属于懒惰学习,即没有显式学习过程。

KNN 原理

  1. 假设有一个带有标签的样本数据集(训练样本集),其中包含每条数据与所属分类的对应关系。
  2. 输入没有标签的新数据后,将新数据的每个特征与样本集中数据对应的特征进行比较。
    1. 计算新数据与样本数据集中每条数据的距离。
    2. 对求得的所有距离进行排序(从小到大,越小表示越相似)。
    3. 取前 k (k 一般小于等于 20 )个样本数据对应的分类标签。
  3. 求 k 个数据中出现次数最多的分类标签作为新数据的分类。

简单来说 K-近邻算法采用测量不同特征值之间的距离方法进行分类。

距离计算

在KNN中,通过计算对象间距离来作为各个对象之间的非相似性指标,避免了对象之间的匹配问题,在这里距离一般使用欧式距离或者曼哈顿距离:

二、问题引入

海伦一直使用在线约会网站寻找适合自己的约会对象。她曾交往过三种类型的人:

- 不喜欢的人

- 一般喜欢的人

- 非常喜欢的人

这些人包含以下三种特征:

1. 每年获得的飞行的客里程数

2. 玩视频游戏所耗时间百分比

3. 每周消费的冰淇淋公升数

该网站现在需要尽可能向海伦推荐她喜欢的人,需要我们设计一个分类器,根据用户的以上三种特征,识别出是否该向海伦推荐。

三、需求概要分析

根据问题,我们可知,样本特征个数为3,样本标签为三类。现需要实现将一个待分类样本的三个特征值输入程序后,能够识别该样本的类别,并且将该类别输出。

四、程序设计和使用工具导入

 1.程序流程设计思路

        (1)实现K近邻分类器并训练模型存储数据

        (2)生成模拟的约会数据集

        (3)对生成的数据集进行可视化处理

        (4)计算最佳K值和准确率

        (5)对K值和准确率的关系进行可视化处理

        (6)设计几个样本测试

        (7)对用户输入进行预测

2.使用工具包导入及其作用

        (1)numpy:负责底层的数据存储和数学计算

import numpy as np

        (2) matplotlib相关包:负责结果的可视化展示

import matplotlib.pyplot as plt

        (3)operatorcollections:负责算法中的排序和统计功能

import operator
from collections import Counter

3.在设计过程中可能遇到的问题

        可视化处理后的数据图像不能正确显示中文

        显示文字为方格乱码,推测为字体问题

        解决方式:导入 platform 包(负责系统兼容性处理)并设置中文字体

import platform

def setup_chinese_font():
    """设置中文字体"""
    try:
        # 根据操作系统设置字体
        system = platform.system()
        if system == "Windows":
            plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei']
        elif system == "Darwin":  # macOS
            plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'Heiti TC']
        else:  # Linux
            plt.rcParams['font.sans-serif'] = ['WenQuanYi Micro Hei', 'DejaVu Sans']

        plt.rcParams['axes.unicode_minus'] = False
        return True
    except:
        return False

五、部分功能展示

      生成模拟的约会数据集

数据生成 

  • 模拟真实数据分布,为算法提供训练和测试数据

  • 通过设置不同的均值和方差,创建可区分的类别特征

  • 为后续的特征归一化和距离计算奠定基础

def load_dating_data():
    """生成模拟的约会数据集"""
    np.random.seed(42)

    n_samples = 300

    # 不喜欢的人(类别1)
    X1 = np.column_stack([
        np.random.normal(10000, 3000, n_samples // 3),  # 飞行里程较少
        np.random.normal(15, 5, n_samples // 3),  # 游戏时间中等偏多
        np.random.normal(0.3, 0.1, n_samples // 3)  # 冰淇淋消费较少
    ])
    y1 = np.ones(n_samples // 3)  # 类别1

    # 一般喜欢的人(类别2)
    X2 = np.column_stack([
        np.random.normal(50000, 15000, n_samples // 3),  # 飞行里程中等
        np.random.normal(8, 3, n_samples // 3),  # 游戏时间中等
        np.random.normal(0.6, 0.2, n_samples // 3)  # 冰淇淋消费中等
    ])
    y2 = np.ones(n_samples // 3) * 2  # 类别2

    # 非常喜欢的人(类别3)
    X3 = np.column_stack([
        np.random.normal(120000, 40000, n_samples // 3),  # 飞行里程较多
        np.random.normal(3, 2, n_samples // 3),  # 游戏时间较少
        np.random.normal(1.0, 0.3, n_samples // 3)  # 冰淇淋消费较多
    ])
    y3 = np.ones(n_samples // 3) * 3  # 类别3

    # 合并数据
    X = np.vstack([X1, X2, X3])
    y = np.hstack([y1, y2, y3])

    # 打乱数据
    indices = np.random.permutation(len(X))
    X = X[indices]
    y = y[indices]

    return X, y

        K近邻分类器

KNN分类器类

包含初始化,特征归一化,距离计算,单样本预测

  1. 归一化处理:确保特征尺度一致

  2. 距离计算:找到最相似的样本

  3. 邻居选择:选取距离最小的K个样本

  4. 多数投票:根据邻居的类别决定预测结果

class KNNClassifier:
    """K近邻分类器实现"""

    def __init__(self, k=3):
        """
        初始化KNN分类器

        参数:
        k: 最近邻居的数量,默认为3
        """
        self.k = k
        self.X_train = None
        self.y_train = None
        self.feature_ranges = None

    def fit(self, X, y):
        """
        训练模型(KNN没有显式的训练过程,只是存储数据)

        参数:
        X: 训练特征,形状为(n_samples, n_features)
        y: 训练标签,形状为(n_samples,)
        """
        self.X_train = np.array(X)
        self.y_train = np.array(y)

        # 计算特征范围用于归一化
        self.feature_ranges = {
            'min': np.min(self.X_train, axis=0),
            'max': np.max(self.X_train, axis=0)
        }

    def normalize_features(self, X):
        """
        归一化特征值到0-1范围

        参数:
        X: 需要归一化的特征数据

        返回:
        归一化后的特征数据
        """
        X_normalized = np.zeros(X.shape)
        for i in range(X.shape[1]):
            min_val = self.feature_ranges['min'][i]
            max_val = self.feature_ranges['max'][i]
            if max_val - min_val == 0:  # 避免除零
                X_normalized[:, i] = 0
            else:
                X_normalized[:, i] = (X[:, i] - min_val) / (max_val - min_val)
        return X_normalized

    def euclidean_distance(self, x1, x2):
        """
        计算两个样本之间的欧几里得距离

        参数:
        x1, x2: 两个样本的特征向量

        返回:
        欧几里得距离
        """
        return np.sqrt(np.sum((x1 - x2) ** 2))

    def predict_single(self, x):
        """
        预测单个样本的类别

        参数:
        x: 单个样本的特征向量

        返回:
        预测的类别
        """
        # 归一化输入样本
        x_normalized = self.normalize_features(np.array([x]))[0]

        # 计算与所有训练样本的距离
        distances = []
        for i, train_sample in enumerate(self.X_train):
            train_sample_normalized = self.normalize_features(np.array([train_sample]))[0]
            dist = self.euclidean_distance(x_normalized, train_sample_normalized)
            distances.append((dist, self.y_train[i]))

        # 按距离排序并选择K个最近邻居
        distances.sort(key=operator.itemgetter(0))
        k_nearest = distances[:self.k]

        # 统计K个最近邻居的类别
        k_labels = [label for (_, label) in k_nearest]
        most_common = Counter(k_labels).most_common(1)

        return most_common[0][0]

    def predict(self, X):
        """
        预测多个样本的类别

        参数:
        X: 需要预测的样本特征,形状为(n_samples, n_features)

        返回:
        预测的类别数组
        """
        predictions = []
        for sample in X:
            predictions.append(self.predict_single(sample))
        return np.array(predictions)

    def accuracy(self, X_test, y_test):
        """
        计算模型在测试集上的准确率

        参数:
        X_test: 测试特征
        y_test: 测试标签

        返回:
        准确率
        """
        predictions = self.predict(X_test)
        accuracy = np.sum(predictions == y_test) / len(y_test)
        return accuracy

关于归一化

归一化特征值,消除特征之间量级不同导致的影响。

在统计学中,归一化的具体作用是归纳统一样本的统计分布性。归一化在0-1之间是统计的概率分布,归一化在-1–+1之间是统计的坐标分布。

这里使用的是最小-最大归一化,即 v_new = (v_old - min) / (max - min)

关于K值设定:

K值设定为多大?

  K太小,分类结果易受噪声点影响;K太大,近邻中又可能包含太多的其他类别的点。(对距离加权,可以降低K值设定的影响)

  K值通常是采用交叉检验来确定,平衡偏差和方差

def find_best_k(X_train, y_train, X_test, y_test, k_range=range(1, 21)):
    """寻找最佳的K值"""
    best_k = 1
    best_accuracy = 0
    accuracies = []

    for k in k_range:
        knn = KNNClassifier(k=k)
        knn.fit(X_train, y_train)
        accuracy = knn.accuracy(X_test, y_test)
        accuracies.append(accuracy)

        if accuracy > best_accuracy:
            best_accuracy = accuracy
            best_k = k

    return best_k, best_accuracy
 

        可视化处理

数据分布可视化

  • 直观展示数据的可分性

  • 帮助理解特征与类别之间的关系

  • 验证特征工程的有效性

def visualize_data(X, y):
    """可视化数据集"""
    plt.figure(figsize=(15, 5))

    # 类别标签映射
    labels = {1: '不喜欢', 2: '一般喜欢', 3: '非常喜欢'}
    colors = {1: 'red', 2: 'orange', 3: 'green'}

    # 特征1 vs 特征2
    plt.subplot(1, 3, 1)
    for label in [1, 2, 3]:
        mask = y == label
        plt.scatter(X[mask, 0], X[mask, 1],
                    c=colors[label], label=labels[label], alpha=0.6)
    plt.xlabel('飞行里程数')
    plt.ylabel('游戏时间百分比')
    plt.legend()
    plt.title('飞行里程 vs 游戏时间')

    # 特征1 vs 特征3
    plt.subplot(1, 3, 2)
    for label in [1, 2, 3]:
        mask = y == label
        plt.scatter(X[mask, 0], X[mask, 2],
                    c=colors[label], label=labels[label], alpha=0.6)
    plt.xlabel('飞行里程数')
    plt.ylabel('冰淇淋消费量')
    plt.legend()
    plt.title('飞行里程 vs 冰淇淋消费')

    # 特征2 vs 特征3
    plt.subplot(1, 3, 3)
    for label in [1, 2, 3]:
        mask = y == label
        plt.scatter(X[mask, 1], X[mask, 2],
                    c=colors[label], label=labels[label], alpha=0.6)
    plt.xlabel('游戏时间百分比')
    plt.ylabel('冰淇淋消费量')
    plt.legend()
    plt.title('游戏时间 vs 冰淇淋消费')

    plt.tight_layout()
    plt.show()

包括在最佳K值中的关系图实现

 # 绘制K值与准确率的关系图
    plt.figure(figsize=(10, 6))
    plt.plot(k_range, accuracies, 'bo-', linewidth=2, markersize=8)
    plt.xlabel('K值')
    plt.ylabel('准确率')
    plt.title('K值与分类准确率的关系')
    plt.grid(True, alpha=0.3)
    plt.xticks(k_range)
    plt.show()

六、KNN优缺点及适用场景

优点

简单直观:易于理解和实现

无需训练:直接存储数据,新数据可随时加入

适应性强:适用于多分类和复杂决策边界

可解释性好:预测结果可通过最近邻居解释


缺点

计算量大:预测时需要计算所有训练样本距离

内存消耗高:需要存储全部训练数据

对特征尺度敏感:必须进行特征归一化

K值选择困难:需要交叉验证确定最佳K值


适用场景

        多分类问题
        稀有事件分类问题
        文本分类问题
        模式识别
        聚类分析
        样本数量较少的分类问题

关键要点

  1. 必须特征归一化 - 避免某些特征主导距离计算

  2. K值需要调优 - 平衡过拟合和欠拟合

  3. 适合中等规模数据 - 在数据量适中时效果最佳

  4. 重视可解释性 - 在需要透明决策的场景有优势

站内参考链接

https://blog.csdn.net/qq_16763983/article/details/114235062

https://blog.csdn.net/qq_41398418/article/details/111086433?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522d16fda40f2bdec178a365ade368b40a0%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=d16fda40f2bdec178a365ade368b40a0&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~baidu_landing_v2~default-9-111086433-null-null.142^v102^pc_search_result_base7&utm_term=k%E8%BF%91%E9%82%BB&spm=1018.2226.3001.4187

https://blog.csdn.net/qq_16763983/article/details/114235062

更多推荐