swift-algorithm-club 朴素贝叶斯分类器实战:从贝叶斯定理到 Gaussian / Multinomial 双变体的完整 Swift 实现

发布时间:2026/9/20 17:37:07
swift-algorithm-club 朴素贝叶斯分类器实战:从贝叶斯定理到 Gaussian / Multinomial 双变体的完整 Swift 实现 swift-algorithm-club 朴素贝叶斯分类器实战从贝叶斯定理到 Gaussian / Multinomial 双变体的完整 Swift 实现【免费下载链接】swift-algorithm-clubAlgorithms and data structures in Swift, with explanations!项目地址: https://gitcode.com/gh_mirrors/sw/swift-algorithm-club本文以 swift-algorithm-club 仓库中的 Naive Bayes Classifier 文档及其源码为核心系统讲解朴素贝叶斯分类器Naive Bayes Classifier的原理与 Swift 实现先通过一张表格厘清类别与特征再从贝叶斯定理出发推导出高斯Gaussian与多项式Multinomial两种变体的完整公式最后结合仓库内源码与 Playground 数据集演示如何用不到百行代码完成训练、分类与概率输出。读完本文你将能独立使用这套 Swift 实现处理连续特征与分类特征两类数据并理解其背后的概率计算链路。![贝叶斯定理公式](https://raw.gitcode.com/gh_mirrors/sw/swift-algorithm-club/raw/e592ed665973fda36df3efa6d7c20ee08705d8db/Naive Bayes Classifier/images/bayes.gif?utm_sourcegitcode_repo_files)分类器是什么用一张表格理解类别与特征分类器Classifier的目标是根据先前喂入的数据及其特征预测给定数据条目所属的类别。朴素贝叶斯分类器自然也不例外它要做的事就是分类。文档用一张经典的性别数据集来直观解释类别class与特征feature的概念——该数据集用身高、体重、鞋码三个人体指标来说明这些数值与性别之间的关系Sexheight (feet)weight (lbs)foot size (inches)male618012male5.9219011male5.5817012male5.9216510female51006female5.51508female5.421307female5.751509类别classes即表中 sex 列的数据male / female。分类的过程就是把其余列的数据绑定到某个性别上。特征features即其余各列的标签height、weight、foot size以及标签下方对应的具体数值。朴素贝叶斯分类器有何特别分类器有很多种朴素贝叶斯的特别之处在于只需要很小的数据集就能获得不错的效果。相比之下随机森林Random Forests等算法通常需要非常庞大的数据集才能收敛到好的结果。准确率存在天花板。在精度上它通常会被随机森林Random Forests或提升树Boosted Trees超越因此在实际工程中往往作为基线模型或小样本场景下的首选。这也是文档反复强调的两点朴素贝叶斯不是最强的分类器但它是数据量有限时最实用的选择之一而且数学门槛极低——理解下面的推导只需要基础的算术知识。理论核心贝叶斯定理朴素贝叶斯分类器如名称所示利用的是贝叶斯定理P(A | B) P(B | A) · P(A) / P(B)其中P始终表示某件事发生的概率A是类别B是依赖于特征的数据|pipe符号读作给定P(A | B)即给定数据该数据依赖于特征时类别发生的概率。贝叶斯定理的完整公式我们只需要理解到这一层。关键在于弄清楚公式右侧的三个变量分别如何计算——把它们算出来代入公式就能对数据完成分类。P(A)类别的先验概率P(A) 是类别自身的概率即该类别在训练数据中的占比。回到性别示例假设我们要分类这样一条新数据height (feet)weight (lbs)foot size (inches)61308朴素贝叶斯分类器会遍历每一个可能的类别本例中是 male 与 female统计原表中男、女各自的样本数再除以样本总数P(male) 4 / 8 0.5P(female) 4 / 8 0.5先验概率本质上就是每个类别占整体数据的比例计算非常简单。P(B)为什么可以直接丢弃P(B) 是数据本身的概率。在朴素贝叶斯分类器中这个变量并不需要——因为对同一个待分类样本而言P(B) 是一个不随类别变化的常数。而常数在比较各类别后验概率大小时可以被直接约掉丢弃它既能节省时间也能节省代码。这一决策在源码中得到了印证分类计算全程没有计算 P(B)只比较各类别归一化后的似然乘积详见下文源码解读。P(B | A)数据在给定类别下的似然P(B | A) 是给定类别时数据出现的概率也是整个算法中唯一有变体之分的地方。根据待分类数据的类型我们需要选择朴素贝叶斯的不同子类型来计算它。Gaussian Naive Bayes连续特征的解法如果你的数据集像上面的性别表一样特征是连续的数值即Double就必须使用高斯朴素贝叶斯Gaussian Naive Bayes。计算 P(B | A) 需要三个公式。第一步均值meanμ (1/n) · Σ xᵢμ 就是数据的平均数把所有数据点相加再除以数据个数。第二步标准差standard deviationσ √( Σ (xᵢ − μ)² / (n − 1) )σ 是标准差即每个数据点减去均值后平方求和除以样本数减一再开平方根。除以 n−1 是对样本标准差的无偏估计。第三步高斯正态分布P(xᵢ | y) (1 / (σ · √(2π))) · e^( −(xᵢ − μ)² / (2σ²) )并且有P(x | y) P(B | A)即用正态分布的概率密度函数作为给定类别 y 时特征 x 的似然。之所以用高斯分布是因为我们假设与每个类别相关联的连续特征值服从高斯分布——朴素贝叶斯的朴素也正体现于此假设各特征在给定类别下相互独立从而把联合似然拆成各特征似然的连乘。在 swift-algorithm-club 的实现中这三个公式被原样翻译成了 Swift均值的实现位于 NaiveBayes.swiftreduce(0, ) / Double(count)一行完成求和与平均标准差的实现位于 NaiveBayes.swift对每个值求(next - mean)²累加后sqrt(sum / Double(count - 1))注意分母是count - 1与文档公式中的 n−1 严格对应高斯似然的计算位于 NaiveBayes.swiftpow(M_E, -1 * pow(input - mean, 2) / (2 * pow(stDev, 2)))求出指数部分再除以sqrt(2 * .pi)与stDev与上文公式逐项对应。Multinomial Naive Bayes分类特征的解法如果特征不是连续数值而是像下面这样的分类数据如 sunny / overcast / rainy就不能直接对晴天、阴天、雨天求均值了。这时需要分类模型——多项式朴素贝叶斯Multinomial Naive Bayes。这正是文档中经典的网球/高尔夫数据集![网球数据集Outlook、Temperature、Humidity、Wind 与是否打球](https://raw.gitcode.com/gh_mirrors/sw/swift-algorithm-club/raw/e592ed665973fda36df3efa6d7c20ee08705d8db/Naive Bayes Classifier/images/tennis_dataset.png?utm_sourcegitcode_repo_files)该数据集共 14 条记录特征是 OutlookSunny/Overcast/Rain、TemperatureHot/Mild/Cool、HumidityHigh/Normal、WindWeak/Strong目标列是是否打网球Play Tennis?。多项式朴素贝叶斯使用的似然公式如下θ(y,i) (N(y,i) α) / (N(y) α · n)其中N(y,i)类别 y 的样本中特征 i 出现的次数N(y)类别 y 的样本总数n特征的总数θ(y,i)即 P(B | A)表示给定类别 y 时特征 i 的概率。公式中的α解决了一个关键问题——零频率问题zero-frequency problem如果某个类别 y 的样本里从未出现过特征 i那么0 / something 0整个方程的似然会直接归零进而把该类别的后验概率压成 0。解决办法很简单给每个计数都加 1即取 α 1这就是经典的拉普拉斯平滑Laplace smoothing。需要说明的是从仓库源码结构看NaiveBayes.swift当前实现的多项式分支直接以count / count统计各分类值的占比并未显式写入 α 平滑参数对于在训练集中未出现过的输入值calcLikelihood会返回nil进而在分类时按 0.0 处理。这与文档理论部分描述的 α 平滑是互补的关系——理论是平滑方案的完整形态实现则选择了最简形式。读者在真实业务中使用时可自行在train(values:)中按公式加入 α 平滑。源码实现训练与分类的完整数据流仓库根目录下的 NaiveBayes.swift 是整个算法的核心实现Playground 内 Sources/NaiveBayes.swift 是带public修饰符的同源版本。整体结构分为三部分1. 数学工具扩展extension Array where Element Double { func mean() - Double { return self.reduce(0, ) / Double(count) } func standardDeviation() - Double { let calculatedMean mean() let sum self.reduce(0.0) { (previous, next) in return previous pow(next - calculatedMean, 2) } return sqrt(sum / Double(count - 1)) } }对应高斯变体的均值与标准差公式extension Array where Element Int则提供uniques()用于提取类别集合。2. NBType 枚举变体分派NBType 枚举定义了两种变体源码注释中还预留了bernoulli作为 TODO并承载两个核心方法train(values:)高斯变体返回[均值, 标准差]多项式变体返回[(分类值, 概率)]的数组calcLikelihood(variables:input:)高斯变体代入正态分布公式计算密度多项式变体按输入值查找对应的类别概率。3. NaiveBayes 泛型类训练与分类public class NaiveBayesT { var variables: [Int: [(feature: Int, variables: [Any])]] var type: NBType var data: [[T]] var classes: [Int] ... }关键设计点初始化时的类型强校验init中强制要求高斯变体的泛型参数必须是Double连续特征、多项式变体必须是Int分类特征否则抛出带文案的错误NaiveBayes.swift。这从类型系统层面杜绝了拿字符串喂给高斯模型这类误用。train()的按类分组训练对每个类别先筛出该类别下的全部样本行再逐特征调用type.train(values:)生成统计量最终得到variables类别 → 各特征的统计参数列表NaiveBayes.swift。classifyProba(with:)的完整贝叶斯流水线NaiveBayes.swift文档给出的代码示例图正是此方法![classifyProba 方法先验、似然、后验与归一化](https://raw.gitcode.com/gh_mirrors/sw/swift-algorithm-club/raw/e592ed665973fda36df3efa6d7c20ee08705d8db/Naive Bayes Classifier/images/code_example.png?utm_sourcegitcode_repo_files)它按四步完成计算计算先验 P(A)遍历classes统计每个类别出现次数除以样本总数得到probaClass[class]计算似然 P(B|A)对每个类别逐一取出各特征对应的统计参数调用type.calcLikelihood得到该特征在此类别下的似然计算失败时以?? 0.0兜底后验 先验 × 连乘似然distribution.reduce(1, *)把所有特征的似然连乘朴素贝叶斯的特征独立假设就体现在这里再乘上先验probaClass[class]归一化将各类别的未归一化分数求和再逐一相除把结果转换为所有类别概率之和为 1的人类可读概率。classify(with:)返回最终类别在classifyProba的结果中取概率最大的那个类别标签NaiveBayes.swift若结果为空则返回-1。注意一个细节贝叶斯定理中的分母 P(B) 在上述流水线中从未出现——正如文档所述它对所有类别是同一个常数归一化步骤已经隐式完成了等价处理。Playground 实战一Gaussian 变体与葡萄酒数据集仓库在 NaiveBayes.playground 中提供了可直接运行的完整示例入口为 Contents.swift数据来自 Resources 目录下的 wine.csv共 178 行样本每行 14 列第 1 列是类别标签 1/2/3后 13 列是酒精、苹果酸、灰分等连续化学指标。数据加载与预处理的关键代码如下guard let wineCSV Bundle.main.path(forResource: wine, ofType: csv) else { print(Resource could not be found!) exit(0) } let rows csv.characters.split(separator: \r\n).map { String($0) } let wineData rows.map { row - [Double] in let split row.characters.split(separator: ;) return split.map { Double(String($0))! } } let rowOfClasses 0 let classes wineData.map { Int($0[rowOfClasses]) } let data wineData.map { row in return row.enumerated().filter { $0.offset ! rowOfClasses }.map { $0.element } }要点数据以;分隔classes与data被显式分离这样能给训练带来很大的性能提升!强制解包仅用于演示真实应用中应使用guard或try?谨慎转换。训练与分类let wineBayes try! NaiveBayes(type: .gaussian, data: data, classes: classes).train() let result wineBayes.classifyProba(with: [12.85, 1.6, 2.52, 17.8, 95, 2.48, 2.37, 0.26, 1.46, 3.93, 1.09, 3.63, 1015])这里的输入是作者从 wine.csv 中刻意移除的一条原始样本类别为 1。分类结果中类别 1 的概率高达99.99%与真实标签完全一致——一个只有 178 条样本的数据集就能让高斯朴素贝叶斯给出如此高置信度的判别。Playground 实战二Multinomial 变体与高尔夫数据集Playground 的第二部分演示多项式变体使用的是上文的网球/高尔夫数据集14 条样本并以整数编码所有分类特征Outlook0 rainy1 overcast2 sunnyTemperature0 hot1 mild2 coolHumidity0 high1 normalWindy0 false1 true类别0 不打高尔夫1 打高尔夫数据构造与训练Contents.swiftlet golfData [ [0, 0, 0, 0], [0, 0, 0, 1], [1, 0, 0, 0], [2, 1, 0, 0], [2, 2, 1, 0], [2, 2, 1, 1], [1, 2, 1, 1], [0, 1, 0, 0], [0, 2, 1, 0], [2, 1, 1, 0], [0, 1, 1, 1], [1, 1, 0, 1], [1, 0, 1, 0], [2, 1, 0, 1] ] let golfClasses [0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0] let golfNaive try! NaiveBayes(type: .multinomial, data: golfData, classes: golfClasses).train()现在假设天气为Outlook rainy0、Temperature cool2、Humidity high0、Windy true1调用分类let golfResult golfNaive.classifyProba(with: [0, 2, 0, 1])朴素贝叶斯给出的结论是这位高尔夫球手不会去打球且该结论的概率接近80%——与数据集中的真实规律完全吻合。这个例子同时展示了多项式变体处理纯分类特征的典型姿势一切非数值特征必须先映射为整数。小结与使用建议结合文档、源码与 Playground可以将这套 Swift 朴素贝叶斯实现的使用要点归纳如下按特征类型选变体连续数值特征Double选NBType.gaussian离散分类特征Int编码选NBType.multinomial。选错类型会在init阶段直接被类型检查拦截并抛出错误。数据组织将样本矩阵data与类别数组classes分离传入二者按行下标一一对应train()会按类别分组并逐特征生成统计参数。两个输出粒度classifyProba(with:)返回各类别及其归一化概率便于解释、调阈值classify(with:)直接返回最大概率类别便于快速集成。适用场景小数据集、高维稀疏特征、需要可解释概率输出的场景是它的主场若追求极致准确率且数据量充足文档明确指出随机森林Random Forests与提升树Boosted Trees通常表现更好。扩展空间源码注释中预留了bernoulli伯努利变体的 TODO多项式分支尚未显式实现文档理论部分的 α 平滑在真实项目中处理训练时未见过的新特征值时需留意calcLikelihood返回nil后被按 0.0 处理的行为。在 swift-algorithm-club 中本文所讲的朴素贝叶斯与 3Sum 与 4Sum、K-Means 等机器学习/算法模块相互独立又互为补充——前者关注算法原理的极简呈现后者提供工程化的算法集合。如果你想从零调试这段实现直接打开 NaiveBayes.playground 运行两个示例即可在时间轴面板中逐步观察训练与分类的中间结果。【免费下载链接】swift-algorithm-clubAlgorithms and data structures in Swift, with explanations!项目地址: https://gitcode.com/gh_mirrors/sw/swift-algorithm-club创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考