K近邻与朴素贝叶斯

机器学习专题 · 掌握KNN和朴素贝叶斯分类算法

专题:Python机器学习系统学习

关键词:Python, 机器学习, KNN, K近邻, 朴素贝叶斯, 贝叶斯定理, 高斯NB, 文本分类, scikit-learn

一、K近邻算法(KNN)

K近邻算法(K-Nearest Neighbors,简称KNN)是机器学习中最简单直观的算法之一,由Cover和Hart于1967年提出。它属于基于实例的学习(Instance-based Learning),也称为懒惰学习(Lazy Learning),因为它在训练阶段并不显式地构建模型,而是将所有训练样本直接存储下来,等到预测时再进行计算。

核心思想

KNN的核心思想用一句中国古语概括就是"近朱者赤"——如果一个样本在特征空间中与某类的大多数样本最相似(距离最近),那么它就属于这个类别。换句话说,事物的性质由其邻居决定。这种思想非常朴素且符合人类的直觉认知:我们通常会根据一个人周围的朋友来判断他的倾向。

算法步骤

KNN算法的执行流程可以分为以下四个步骤:

  1. 计算距离:对于待分类的测试样本,计算它与训练集中每一个样本之间的距离。
  2. 选择K个邻居:根据计算出的距离,找出距离最近的K个训练样本。
  3. 投票决策:对于分类任务,统计K个邻居中各个类别的出现频率,将出现频率最高的类别作为预测结果。
  4. 输出结果:返回预测的类别标签(或回归预测值)。

K值的选择

K值是KNN算法最重要的超参数,它决定了需要参考多少个邻居来进行决策。K值的选择对模型性能有显著影响:

距离度量

距离度量是KNN算法中另一个关键因素,不同的距离度量方式会得到不同的邻居集合。常用的距离度量包括:

权重策略

在基本的KNN中,所有K个邻居对最终决策的贡献是相同的(uniform权重)。但有时我们希望距离更近的邻居拥有更大的话语权:

scikit-learn实现

在scikit-learn中,KNN分类器通过KNeighborsClassifier类实现,使用非常方便:

from sklearn.neighbors import KNeighborsClassifier from sklearn.model_selection import train_test_split from sklearn.datasets import load_iris from sklearn.preprocessing import StandardScaler # 加载数据 iris = load_iris() X, y = iris.data, iris.target # 数据标准化(重要!) scaler = StandardScaler() X_scaled = scaler.fit_transform(X) # 划分训练集和测试集 X_train, X_test, y_train, y_test = train_test_split( X_scaled, y, test_size=0.3, random_state=42 ) # 创建KNN分类器(K=5,距离加权) knn = KNeighborsClassifier(n_neighbors=5, weights='distance') # 训练(实际上只是存储数据) knn.fit(X_train, y_train) # 预测 y_pred = knn.predict(X_test) # 评估 accuracy = knn.score(X_test, y_test) print(f'准确率: {accuracy:.4f}')

二、KNN的优缺点

优点

缺点

优化方法

为了克服KNN预测速度慢的问题,研究者提出了多种优化数据结构:

三、KNN回归

KNN不仅可以用于分类任务,也可以用于回归任务。KNN回归的核心思想与分类完全一致:对于一个待预测的测试样本,找到特征空间中离它最近的K个训练样本,然后将这K个样本的目标值进行平均(或加权平均)作为预测结果。

在scikit-learn中,KNN回归器通过KNeighborsRegressor类实现:

from sklearn.neighbors import KNeighborsRegressor import numpy as np # 生成示例数据 X = np.array([[1], [2], [3], [4], [5], [6]]) y = np.array([1.5, 2.0, 2.8, 4.1, 5.2, 5.9]) # 创建KNN回归器 knn_r = KNeighborsRegressor(n_neighbors=3, weights='distance') # 训练 knn_r.fit(X, y) # 预测新样本 X_new = np.array([[3.5]]) y_pred = knn_r.predict(X_new) print(f'预测值: {y_pred[0]:.2f}') # 输出:预测值: 3.45(取决于距离加权结果)

KNN回归与分类的区别:分类任务是对K个邻居的类别标签进行投票(取众数),而回归任务是对K个邻居的数值标签取平均(或加权平均)。两者的算法框架完全一致,只是最终的聚合策略不同。KNN回归适用于目标值为连续变量的场景,如房价预测、温度预测等。

四、朴素贝叶斯概述

朴素贝叶斯(Naive Bayes)是一种基于贝叶斯定理的分类算法,它以概率论为基础,通过计算样本属于各个类别的概率来进行分类决策。朴素贝叶斯因其简单、高效的特点,在文本分类、垃圾邮件过滤等场景中得到了广泛应用。

朴素假设

朴素贝叶斯的核心假设是:特征之间在给定类别条件下相互独立。也就是说,每个特征对分类结果的影响是独立的,特征之间没有关联和依赖关系。例如,在判断一封邮件是否为垃圾邮件时,"包含'免费'一词"和"包含'点击'一词"这两个特征被认为是相互独立的。

为什么称为"朴素"

之所以称为"朴素"(Naive),正是因为"特征之间条件独立"这个假设在现实世界中几乎不可能成立。在实际数据中,特征之间往往存在一定程度的相关性。例如,在文本分类中,"买"和"便宜"这两个词经常同时出现,它们并非完全独立。然而,令人惊讶的是,即使这个假设非常强烈且通常不成立,朴素贝叶斯在实际应用中仍然表现出色,尤其是在文本分类领域。

"朴素贝叶斯之所以有效,是因为分类决策通常只需要正确的相对概率,而不是精确的概率值。即使概率估计有偏差,只要各类别概率的相对大小保持正确,分类结果就不会受影响。"

五、贝叶斯定理

贝叶斯定理是朴素贝叶斯算法的数学基础,它描述了在已知某些条件的情况下,如何更新对事件的概率估计。贝叶斯定理的公式如下:

P(y|x) = P(x|y) * P(y) / P(x)

其中:

P(y|x) —— 后验概率(Posterior):给定特征x的情况下,样本属于类别y的概率

P(y) —— 先验概率(Prior):在观察到特征之前,样本属于类别y的概率

P(x|y) —— 似然(Likelihood):在类别y的条件下,观察到特征x的概率

P(x) —— 证据(Evidence):观察到特征x的概率,用于归一化

各分量详解

最大化后验概率(MAP)

在朴素贝叶斯分类中,我们选择使后验概率最大的类别作为预测结果,这被称为最大后验概率估计(Maximum A Posteriori,简称MAP)。由于P(x)对所有类别都是常数,MAP决策规则简化为:

y_pred = argmax P(y) * P(x1|y) * P(x2|y) * ... * P(xn|y)

即:选择先验概率与条件概率乘积最大的那个类别。在实际计算中,由于概率值可能非常小(大量小数相乘),通常会取对数将乘法转换为加法,以防止数值下溢:y_pred = argmax [log P(y) + sum(log P(xi|y))]。

六、朴素贝叶斯的三种变体

根据特征的不同分布假设,朴素贝叶斯算法发展出了三种主要的变体。选择合适的变体取决于特征的数据类型:

高斯朴素贝叶斯(GaussianNB)

高斯朴素贝叶斯假设连续特征服从正态分布(高斯分布)。对于每个类别下的每个特征,它都会估计该特征在该类别下的均值和方差,然后根据高斯分布的概率密度函数来计算条件概率。GaussianNB适用于连续数值型特征,如身高、体重、温度、房价等。在scikit-learn中使用GaussianNB类实现。

多项式朴素贝叶斯(MultinomialNB)

多项式朴素贝叶斯假设特征服从多项分布,适用于离散计数特征。它假设每个特征代表某个事件发生的次数,如文本中某个词出现的频次。MultinomialNB是文本分类中最常用的变体,广泛应用于垃圾邮件过滤、新闻分类、情感分析等任务。在scikit-learn中使用MultinomialNB类实现,常见的参数包括alpha(拉普拉斯平滑系数,防止零概率问题)。

伯努利朴素贝叶斯(BernoulliNB)

伯努利朴素贝叶斯假设特征服从伯努利分布,适用于二值特征(0或1)。它关注的是特征是否出现,而不是出现的频次。例如,在文本分类中,BernoulliNB只关心某个词是否出现在文档中(出现为1,不出现为0),而不关心它出现了多少次。BernoulliNB在某些短文本分类任务中可能比MultinomialNB表现更好。在scikit-learn中使用BernoulliNB类实现。

三种变体的对比

变体 特征类型 分布假设 典型应用
GaussianNB 连续数值 正态分布 医疗诊断、鸢尾花分类
MultinomialNB 离散计数 多项分布 文本分类、垃圾邮件过滤
BernoulliNB 二值(0/1) 伯努利分布 短文本分类、特征出现与否

选择建议:如果特征是连续数值型(如身高、温度),使用GaussianNB;如果特征是离散计数(如词频),使用MultinomialNB;如果特征是二值型(如是否出现),使用BernoulliNB。对于文本分类,MultinomialNB通常是首选,因为它利用了词频信息。

七、朴素贝叶斯的应用

文本分类

朴素贝叶斯在文本分类领域有着非常广泛的应用,是其最主要的应用场景之一。常见的文本分类任务包括:

文档分类

将文档按主题或内容自动归类,是朴素贝叶斯的另一个重要应用。与文本分类类似,文档分类通过分析文档中词汇的出现概率来判断文档所属的类别。

实时预测

朴素贝叶斯的一个突出优势是训练和预测速度都非常快。训练阶段仅需计算类别的先验概率和各特征的条件概率(一次扫描即可完成),预测阶段也只需要进行简单的乘法或加法运算。因此,朴素贝叶斯非常适合需要实时预测的场景。

scikit-learn实现示例

from sklearn.naive_bayes import GaussianNB, MultinomialNB, BernoulliNB from sklearn.feature_extraction.text import CountVectorizer from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report # 示例数据:简单文本分类 texts = [ '便宜好用的产品推荐', '免费领取大奖', '质量很好非常满意', '点击链接领取红包', '物流很快包装精美', '恭喜中奖免费领取' ] labels = [0, 1, 0, 1, 0, 1] # 0:正常 1:垃圾 # 文本向量化(词频) vectorizer = CountVectorizer() X = vectorizer.fit_transform(texts) # 划分数据集 X_train, X_test, y_train, y_test = train_test_split( X, labels, test_size=0.3, random_state=42 ) # 使用多项式朴素贝叶斯 mnb = MultinomialNB(alpha=1.0) mnb.fit(X_train, y_train) # 预测 y_pred = mnb.predict(X_test) print(classification_report(y_test, y_pred))

八、算法对比

KNN vs 朴素贝叶斯

对比维度 K近邻(KNN) 朴素贝叶斯
模型类型 基于实例(懒惰学习) 基于概率(生成式模型)
训练速度 极快(仅存储数据) 快(计算先验和条件概率)
预测速度 慢(需计算所有距离) 极快(简单概率计算)
对特征缩放 敏感(必须标准化) 不敏感(基于概率)
高维数据处理 差(维度灾难) 较好(特征独立假设简化计算)
可解释性 好(可查看邻居样本) 较好(概率可解释)
对小样本 较好 较差(概率估计不准确)
缺失值处理 不支持 可忽略缺失特征

适用场景建议

选择KNN的场景:数据量适中(几万条以内)、特征维度较低(几十维以内)、数据需要频繁更新、需要查看相似样本的可解释性。KNN非常适合于推荐系统(如找相似用户)、图像识别中的相似度比较等。

选择朴素贝叶斯的场景:文本分类(垃圾邮件、新闻分类)、特征维度很高(如上万维的词袋特征)、需要快速实时预测、数据量较大(百万级以上)。朴素贝叶斯在NLP领域的文本分类任务中几乎是基线标配。

小结:KNN和朴素贝叶斯是两种风格迥异但同样重要的分类算法。KNN依靠样本之间的相似性进行分类,直观易懂但预测速度慢;朴素贝叶斯依靠概率模型进行分类,计算高效但对特征独立性假设要求严格。在实际项目中,建议根据数据特性和业务需求选择合适的算法,也可以同时尝试两种算法,通过交叉验证选择表现更好的一方。