
简介这份资源面向希望系统入门机器学习分类算法的Python学习者与数据分析初学者围绕决策树、朴素贝叶斯与人工神经网络三类经典算法提供从原理到代码落地的完整实践材料并以股票价格波动分析作为综合实战场景帮助读者把算法知识迁移到真实数据上。压缩包共3个文件包含1个csv数据文件、1个py脚本和1个ipynb笔记本整体约119KB其中csv用于股票价格波动案例的数据输入py与ipynb分别提供可直接运行与逐步演示的代码版本建议使用Jupyter Notebook打开ipynb以获得更连贯的阅读体验。资源已有5904人学习下载内容覆盖决策树与贝叶斯分类原理及其Python实现、人工神经网络分类算法实现以及股票价格波动分析的实操流程适合边看边跑、对照调试快速建立分类算法的代码直觉与项目经验。1. 三套分类算法放在同一份数据上跑为什么准确率差出 12 个百分点手上有一份客户流失数据两千多条样本、十几个特征目标是二分类。同一份训练集决策树跑出 0.81 的准确率朴素贝叶斯 0.76人工神经网络调到 0.88。很多人第一反应是「神经网络最强直接上它」。但把这三套代码放在一起对比之后结论往往相反决策树给出的分裂路径能直接拿去和业务方对齐朴素贝叶斯训练耗时不到一秒、适合做基线神经网络精度最高却最难解释、也最容易在小样本上过拟合。这篇笔记讲的就是这三套分类算法的 Python 代码实现和配套数据怎么落地。决策树、朴素贝叶斯、人工神经网络三个模型共用一套数据加载和评估流程各自独立成模块方便你替换数据、调参、对比。适合已经会写基础 Python、想把这三种分类器真正跑通并做横向对比的从业者也适合需要给团队搭一套分类基线的人。下面从数据准备开始一步步把三套代码写出来最后讲清楚调参和踩坑。2. 数据准备与统一评估框架三套算法共用的地基三种算法的输入格式、标签编码、评估指标如果不统一后面的对比就没有意义。所以第一步不是写模型而是把数据管线和评估函数固定下来。这一步做扎实后面换模型只是替换一个类的事。2.1 数据加载与特征标签分离常见做法是用 pandas 读 CSV把特征矩阵和标签向量分开。下面这段代码假设数据文件是data.csv最后一列是标签其余列是特征。数值特征直接保留类别特征做独热编码。import pandas as pd import numpy as np from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler, OneHotEncoder from sklearn.compose import ColumnTransformer from sklearn.pipeline import Pipeline def load_data(pathdata.csv, label_collabel): df pd.read_csv(path) # 标签和特征分离避免标签列混进特征 X df.drop(columns[label_col]) y df[label_col].values # 自动识别数值列和类别列 num_cols X.select_dtypes(include[np.number]).columns.tolist() cat_cols X.select_dtypes(exclude[np.number]).columns.tolist() # 数值标准化类别独热编码统一在一个转换器里 preprocessor ColumnTransformer( transformers[ (num, StandardScaler(), num_cols), (cat, OneHotEncoder(handle_unknownignore), cat_cols), ] ) return X, y, preprocessor X, y, preprocessor load_data() X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) print(训练集:, X_train.shape, 测试集:, X_test.shape)这段代码的关键在ColumnTransformer它把数值列的标准化和类别列的独热编码打包成一个对象后面三个模型都复用同一个preprocessor。stratifyy保证训练集和测试集的标签比例一致样本不均衡时这一步不能省。random_state42固定随机种子保证每次跑出来的划分一样方便复现对比结果。参数上test_size0.2是常见起点样本量小于一千时建议改成 0.3留出更多测试样本。handle_unknownignore处理测试集出现训练集没见过的类别值不加这个参数会直接报错。2.2 统一评估函数准确率之外还要看什么只报准确率是不够的尤其当两类样本比例悬殊时。下面这个评估函数同时输出准确率、精确率、召回率和 F1三个模型共用。from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, confusion_matrix def evaluate(model, X_test, y_test, namemodel): y_pred model.predict(X_test) acc accuracy_score(y_test, y_pred) prec precision_score(y_test, y_pred, averagebinary, zero_division0) rec recall_score(y_test, y_pred, averagebinary, zero_division0) f1 f1_score(y_test, y_pred, averagebinary, zero_division0) print(f[{name}] acc{acc:.4f} prec{prec:.4f} rec{rec:.4f} f1{f1:.4f}) print(混淆矩阵:\n, confusion_matrix(y_test, y_pred)) return {acc: acc, prec: prec, rec: rec, f1: f1}averagebinary适用于二分类多分类要改成macro或weighted。zero_division0避免某类没被预测到时抛警告。混淆矩阵能看出模型是把正类误判成负类多还是反过来这比单一准确率有用得多。评估函数返回字典方便最后把三个模型的结果拼成表格对比。提示数据划分和评估函数一定要在写模型之前定好否则三个模型各用各的划分对比结果没有意义。3. 决策树分类器从分裂准则到剪枝参数决策树是这三套里最容易解释的。它的核心是在每个节点选一个特征和阈值把样本尽量分开。理解分裂准则和剪枝参数比记住 API 更重要。3.1 基尼系数与信息增益的选择scikit-learn 的DecisionTreeClassifier提供两种分裂准则gini基尼不纯度和entropy信息增益。基尼计算快是默认值信息增益在特征取值多时容易偏向取值多的特征。实际项目里两者结果差异通常不大先用gini跑基线如果树的分裂点不符合业务直觉再换entropy对比。from sklearn.tree import DecisionTreeClassifier, export_text from sklearn.pipeline import Pipeline def build_tree(X_train, y_train, X_test, y_test, max_depth5, criteriongini): # 把预处理和模型串成 Pipeline避免测试集单独转换时出错 clf Pipeline(steps[ (prep, preprocessor), (tree, DecisionTreeClassifier( criterioncriterion, max_depthmax_depth, min_samples_split10, min_samples_leaf5, random_state42 )) ]) clf.fit(X_train, y_train) evaluate(clf, X_test, y_test, namefDecisionTree({criterion},depth{max_depth})) return clf tree_model build_tree(X_train, y_train, X_test, y_test, max_depth5)max_depth5是控制过拟合的第一道闸。不限制深度时树会一直分裂到每个叶子只有一个样本训练集准确率接近 1测试集却掉得厉害。min_samples_split10表示节点样本数少于 10 就不再分裂min_samples_leaf5保证每个叶子至少 5 个样本这两个参数配合max_depth一起用比只调一个更稳。3.2 把树的分裂规则打印出来做业务对齐决策树最大的价值是可解释。export_text能把整棵树转成文本规则直接拿给业务方看。# 取出 Pipeline 里的树模型和特征名 tree tree_model.named_steps[tree] prep tree_model.named_steps[prep] feature_names prep.get_feature_names_out() rules export_text(tree, feature_nameslist(feature_names), max_depth3) print(rules)get_feature_names_out()返回经过独热编码后的完整特征名比如cat__gender_M这种。max_depth3只打印前三层避免规则太长。打印出来的规则形如「如果 月费 80 且 合约时长 12 则 流失」这种句子能直接和运营策略挂钩。参数上如果发现树太深、规则太碎优先降max_depth如果发现某些重要特征没被用上检查是不是被独热编码拆得太散可以考虑先做特征分箱。决策树对特征缩放不敏感但放进 Pipeline 里统一处理没坏处方便和另外两个模型共用代码。4. 朴素贝叶斯分类器假设很强但基线极快朴素贝叶斯的核心假设是「特征之间相互独立」。这个假设在真实数据里几乎不成立但它依然常常给出不错的基线结果而且训练速度极快适合做第一版对照。4.1 高斯、多项式、伯努利三种变体怎么选scikit-learn 提供三种朴素贝叶斯GaussianNB适合连续特征MultinomialNB适合计数特征如词频BernoulliNB适合二值特征。选错变体会直接导致效果崩掉这是最常见的翻车点。from sklearn.naive_bayes import GaussianNB, MultinomialNB, BernoulliNB def build_nb(X_train, y_train, X_test, y_test, variantgaussian): if variant gaussian: nb GaussianNB(var_smoothing1e-9) elif variant multinomial: nb MultinomialNB(alpha1.0) else: nb BernoulliNB(alpha1.0) clf Pipeline(steps[(prep, preprocessor), (nb, nb)]) clf.fit(X_train, y_train) evaluate(clf, X_test, y_test, namefNB({variant})) return clf nb_model build_nb(X_train, y_train, X_test, y_test, variantgaussian)GaussianNB的var_smoothing是防止方差为零时除零的平滑项默认 1e-9特征尺度差异大时可以适当调大。MultinomialNB和BernoulliNB的alpha是拉普拉斯平滑系数默认 1.0数据稀疏时调大能防止某个特征概率为零导致整个预测为零。4.2 连续特征离散化后再喂给多项式朴素贝叶斯如果数据里既有连续特征又想用MultinomialNB常见做法是先把连续特征分箱成计数。下面用KBinsDiscretizer做等频分箱。from sklearn.preprocessing import KBinsDiscretizer # 只对数值列做分箱类别列保持独热 num_cols X.select_dtypes(include[np.number]).columns.tolist() cat_cols X.select_dtypes(exclude[np.number]).columns.tolist() preprocessor_nb ColumnTransformer( transformers[ (num, KBinsDiscretizer(n_bins5, encodeonehot-dense, strategyquantile), num_cols), (cat, OneHotEncoder(handle_unknownignore), cat_cols), ] ) clf Pipeline(steps[(prep, preprocessor_nb), (nb, MultinomialNB(alpha1.0))]) clf.fit(X_train, y_train) evaluate(clf, X_test, y_test, nameNB(multinomialbins))n_bins5是分箱数strategyquantile保证每箱样本数接近避免某些箱为空。encodeonehot-dense输出稠密矩阵因为MultinomialNB不接受稀疏输入。分箱数太多会让特征维度爆炸太少会丢失信息5 到 10 之间试。朴素贝叶斯的优势在于训练几乎不耗时样本量再大也能秒级出结果。它的短板是特征独立假设如果特征之间高度相关概率估计会偏。实际用法是把它当基线如果神经网络比它好不了多少说明数据本身线性可分性就强没必要上复杂模型。5. 人工神经网络分类器小样本下的参数与正则化神经网络精度上限高但在几千条样本的数据上稍不注意就过拟合。这一章讲清楚网络结构怎么定、正则化怎么加、训练过程怎么监控。5.1 用 MLPClassifier 搭一个可复现的三层网络scikit-learn 的MLPClassifier足够应付中小规模数据不用上深度学习框架。关键是隐藏层大小、激活函数和正则化参数。from sklearn.neural_network import MLPClassifier def build_mlp(X_train, y_train, X_test, y_test, hidden(64, 32), alpha1e-3, lr1e-3, max_iter500): clf Pipeline(steps[ (prep, preprocessor), (mlp, MLPClassifier( hidden_layer_sizeshidden, activationrelu, solveradam, alphaalpha, learning_rate_initlr, max_itermax_iter, early_stoppingTrue, validation_fraction0.1, n_iter_no_change10, random_state42 )) ]) clf.fit(X_train, y_train) evaluate(clf, X_test, y_test, namefMLP{hidden},alpha{alpha}) return clf mlp_model build_mlp(X_train, y_train, X_test, y_test, hidden(64, 32), alpha1e-3)hidden_layer_sizes(64, 32)表示两层隐藏层第一层 64 个神经元第二层 32 个。样本量几千时这个规模够用再大容易过拟合。alpha1e-3是 L2 正则化系数越大正则越强过拟合时优先调它。early_stoppingTrue会从训练集里切出 10% 做验证连续 10 轮验证分数不提升就停这是防止过拟合最省事的手段。5.2 学习曲线判断过拟合还是欠拟合训练完不能只看测试集分数要看损失曲线。MLPClassifier的loss_curve_记录了每轮训练损失。import matplotlib.pyplot as plt mlp mlp_model.named_steps[mlp] plt.plot(mlp.loss_curve_, labeltrain loss) if hasattr(mlp, validation_scores_): plt.plot(mlp.validation_scores_, labelval score) plt.xlabel(epoch) plt.legend() plt.savefig(mlp_curve.png) print(最终训练损失:, mlp.loss_curve_[-1])训练损失一直降但验证分数不升说明过拟合调大alpha或减小隐藏层。训练损失降不下去说明欠拟合增加隐藏层或提高learning_rate_init。validation_scores_只有在early_stoppingTrue时才有这也是建议开启早停的原因之一。参数上learning_rate_init1e-3是 Adam 的常见起点损失震荡就调小到 1e-4收敛太慢就调到 1e-2。max_iter500配合早停实际往往几十轮就停了。神经网络对特征缩放敏感所以 Pipeline 里的StandardScaler不能省这也是三个模型共用预处理器的好处。6. 三套算法横向对比时的避坑清单把三个模型放在一起跑最容易出的问题不是模型本身而是流程和参数上的细节。下面五条是实际对比时反复踩到的。现象三个模型准确率都异常高接近 0.99。原因标签列被当成特征混进了训练集模型直接看到了答案。 解决加载数据时确认drop(columns[label_col])生效打印X.columns检查有没有标签列。现象朴素贝叶斯报错「Negative values in data passed to MultinomialNB」。原因连续特征标准化后出现负值而MultinomialNB要求非负输入。 解决改用GaussianNB或者先做KBinsDiscretizer分箱成非负计数再喂给多项式变体。现象神经网络每次跑结果都不一样无法复现。原因没固定随机种子或者early_stopping的验证集划分随机。 解决MLPClassifier里设random_state42数据划分也固定random_state两处都要设。现象决策树训练集准确率 1.0测试集只有 0.7。原因树没有限制深度每个叶子只有一个样本完全记住了训练集。 解决设max_depth、min_samples_leaf或者用ccp_alpha做代价复杂度剪枝。现象三个模型对比时某个模型分数明显偏低但单独跑又正常。原因对比时共用了同一个preprocessor对象而它在某个 Pipeline 里被重新拟合过导致状态污染。 解决每个 Pipeline 里传入preprocessor的副本用sklearn.base.clone克隆不要共用同一个实例。注意横向对比时数据划分、评估指标、随机种子三者必须完全一致否则分数差异可能来自流程而非模型。7. 把三个模型串成一条对比流水线一个可复用的技巧单独跑通三个模型只是第一步真正省时间的是把它们串成一条流水线一次跑完出对比表。下面这个函数接收模型字典统一训练、评估最后输出 DataFrame。from sklearn.base import clone import pandas as pd def compare_models(models: dict, X_train, y_train, X_test, y_test): results [] for name, model in models.items(): # 每个模型用独立的预处理器副本避免状态污染 pipe Pipeline(steps[(prep, clone(preprocessor)), (clf, model)]) pipe.fit(X_train, y_train) metrics evaluate(pipe, X_test, y_test, namename) metrics[model] name results.append(metrics) return pd.DataFrame(results).set_index(model) models { tree: DecisionTreeClassifier(max_depth5, min_samples_leaf5, random_state42), nb: GaussianNB(var_smoothing1e-9), mlp: MLPClassifier(hidden_layer_sizes(64, 32), alpha1e-3, early_stoppingTrue, max_iter500, random_state42), } df compare_models(models, X_train, y_train, X_test, y_test) print(df.sort_values(f1, ascendingFalse))关键在clone(preprocessor)每个模型拿到一份独立的预处理器拟合时互不影响。如果直接传同一个preprocessor实例后训练的模型会覆盖前面的拟合状态导致结果错乱这个坑很隐蔽。compare_models返回的 DataFrame 按 F1 排序一眼能看出哪个模型在当前数据上更合适。我自己的习惯是任何分类任务先跑这条流水线出基线表再决定往哪个方向深挖。如果决策树的 F1 和神经网络只差一两个点我会选决策树因为规则能直接讲给业务方听维护成本低得多。神经网络只有在基线明显不够、且样本量撑得住的时候才值得上。这套代码改数据路径就能复用特征工程部分按业务替换ColumnTransformer里的转换器即可。希望帮到你。本文还有配套的精品资源点击获取