ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

决策树、朴素贝叶斯、神经网络分类实战对比:从原理到代码

决策树、朴素贝叶斯、神经网络分类实战对比:从原理到代码

简介:这份资源面向希望系统入门机器学习分类算法的Python学习者与数据分析初学者,围绕决策树、朴素贝叶斯与人工神经网络三类经典算法,提供从原理理解到代码落地的完整练习素材,并以股票价格波动分析作为综合实战场景,帮助读者把算法知识迁移到真实数据上。压缩包共3个文件,包含1个csv数据集、1个py脚本和1个ipynb笔记本,整体约119KB,其中csv用于股票价格波动案例建模,py与ipynb分别提供可直接运行和分步演示的代码版本,建议用Jupyter Notebook打开ipynb以获得更连贯的阅读体验。目前已有5904人学习下载,说明内容在入门与进阶人群中具备一定参考价值。读者可据此掌握三类算法的核心思路与Python实现方式,理解分类任务从数据准备、模型构建到结果解读的基本流程,并借助股票案例体会特征选择、模型对比与效果评估的实践要点,适合作为课程作业、自学练手或项目复现的起步材料。

1. 三套分类算法放在同一份数据上跑:为什么结果差这么多

同一份鸢尾花数据,决策树、朴素贝叶斯、人工神经网络三个分类器跑下来,准确率能差出十几个百分点。这不是玄学,是三种算法对数据分布的假设完全不同。决策树靠特征阈值切分空间,朴素贝叶斯假设特征条件独立,神经网络靠权重矩阵拟合非线性边界。很多刚入门 Python 的朋友在 python 官网下载安装完环境,装好 numpy、sklearn,跑完第一个分类 demo 就卡住了——不知道选哪个、参数怎么调、结果怎么解释。

这篇笔记把三套算法的代码、数据、参数、踩坑点全部摊开讲。适合两类人:一是刚学完 python 入门教程、想找一个完整分类项目练手的;二是已经用过 sklearn 但只会调fit和predict、想搞清楚背后发生了什么的一线开发者。代码全部基于 sklearn 和 PyTorch,数据用经典的 Iris 和乳腺癌数据集,每段代码都能直接复制运行。

2. 决策树分类器:从特征选择到剪枝的完整实现

2.1 决策树到底在做什么:信息增益与基尼系数的选择逻辑

决策树的核心动作只有一个:在每个节点上找一个特征和一个阈值,把当前数据集切成两份,让切完之后的子集尽可能“纯”。纯度怎么衡量,决定了用哪种决策树。ID3 用信息增益,C4.5 用信息增益率,CART 用基尼系数。sklearn 只实现了 CART,所以criterion参数只有gini和entropy两个选项。

信息增益的问题是对取值多的特征有偏好。比如“身份证号”这种特征,每个值只有一个样本,信息增益直接拉满,但完全没有泛化能力。基尼系数没有对数运算,计算更快,实际项目中默认用gini就够了。C4.5 的信息增益率通过除以特征自身的熵来惩罚多值特征,但 sklearn 没实现,需要自己写或者换用其他库。

决策树的另一个关键机制是剪枝。不剪枝的树可以一直长到每个叶子节点只有一个样本,训练集准确率 100%,测试集惨不忍睹。sklearn 提供了两个预剪枝参数:max_depth控制树的最大深度,min_samples_leaf控制叶子节点最少样本数。后剪枝 sklearn 不支持,需要手动实现或者用ccp_alpha做代价复杂度剪枝。

2.2 用 sklearn 跑通决策树:完整代码与参数逐项说明

# 决策树分类完整示例 # 依赖:scikit-learn, numpy, matplotlib import numpy as np from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split, cross_val_score from sklearn.tree import DecisionTreeClassifier, export_text from sklearn.metrics import classification_report, confusion_matrix # 1. 加载数据 iris = load_iris() X, y = iris.data, iris.target # 2. 划分训练集和测试集,stratify 保证类别比例一致 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, random_state=42, stratify=y ) # 3. 构建决策树,关键参数逐个设置 dt = DecisionTreeClassifier( criterion='gini', # 分裂标准:gini 或 entropy max_depth=4, # 最大深度,防止过拟合 min_samples_split=5, # 内部节点再划分所需最小样本数 min_samples_leaf=2, # 叶子节点最少样本数 max_features=None, # 每次分裂考虑的特征数,None 表示全部 ccp_alpha=0.0, # 代价复杂度剪枝参数,越大剪得越狠 random_state=42 ) # 4. 训练 dt.fit(X_train, y_train) # 5. 预测与评估 y_pred = dt.predict(X_test) print("测试集准确率:", dt.score(X_test, y_test)) print("\n分类报告:") print(classification_report(y_test, y_pred, target_names=iris.target_names)) print("\n混淆矩阵:") print(confusion_matrix(y_test, y_pred)) # 6. 五折交叉验证,看模型稳定性 cv_scores = cross_val_score(dt, X, y, cv=5, scoring='accuracy') print(f"\n交叉验证准确率: {cv_scores.mean():.4f} (+/- {cv_scores.std():.4f})") # 7. 打印树结构,方便排查 tree_rules = export_text(dt, feature_names=iris.feature_names) print("\n决策树规则:") print(tree_rules) # 8. 特征重要性 print("\n特征重要性:") for name, imp in zip(iris.feature_names, dt.feature_importances_): print(f" {name}: {imp:.4f}")

这段代码里最需要关注的是第 3 步的参数设置。max_depth=4是 Iris 数据集上的经验值,换成乳腺癌数据集通常设到 5 到 6。min_samples_leaf=2意味着每个叶子至少有两个样本,避免模型记住噪声。ccp_alpha默认是 0,也就是不做后剪枝,如果发现交叉验证分数远低于训练分数,可以逐步增大这个值,比如从 0.001 开始试。

第 6 步的交叉验证很重要。单次train_test_split的结果波动可能很大,交叉验证能给出更稳定的评估。如果交叉验证标准差超过 0.05,说明模型对数据划分敏感,需要检查数据量是否太小或者类别是否不平衡。

第 7 步的export_text输出的是纯文本规则,比画图更实用。比如输出里出现petal width <= 0.8这样的规则,可以直接拿去和业务方讨论。第 8 步的特征重要性是决策树的可解释性优势,但要注意:如果两个特征高度相关,重要性会被分散,看起来都不高,实际上删掉其中一个另一个会立刻涨上来。

2.3 决策树调参:网格搜索与学习曲线

手动调参效率太低,用GridSearchCV自动搜。但要注意搜索空间别设太大,否则跑一晚上出不来结果。

from sklearn.model_selection import GridSearchCV # 定义参数网格 param_grid = { 'max_depth': [3, 4, 5, 6, 8, 10], 'min_samples_split': [2, 5, 10, 20], 'min_samples_leaf': [1, 2, 4, 8], 'criterion': ['gini', 'entropy'] } # 网格搜索,5 折交叉验证 grid_search = GridSearchCV( DecisionTreeClassifier(random_state=42), param_grid, cv=5, scoring='accuracy', n_jobs=-1, # 用所有 CPU 核心 verbose=1 ) grid_search.fit(X_train, y_train) print("最佳参数:", grid_search.best_params_) print("最佳交叉验证分数:", grid_search.best_score_) print("测试集分数:", grid_search.score(X_test, y_test))

n_jobs=-1会占满所有 CPU 核心,如果同时在跑其他任务,改成n_jobs=2或n_jobs=4。verbose=1会打印搜索进度,参数组合多的时候能知道跑到哪了。搜索完成后best_params_给出最优组合,但不要直接信这个结果——如果最优参数落在网格边界上(比如max_depth最优值是 10,而你最大只搜到 10),说明搜索范围不够,需要往外扩。

3. 朴素贝叶斯分类器:条件独立假设下的概率计算

3.1 朴素贝叶斯为什么“朴素”:条件独立假设与三种变体

朴素贝叶斯的“朴素”两个字,指的是它假设所有特征在给定类别下相互独立。这个假设在现实中几乎不成立——比如鸢尾花的花瓣长度和花瓣宽度明显相关。但神奇的是,即使假设不成立,朴素贝叶斯在很多任务上表现依然不错,尤其是文本分类。原因在于分类任务只需要比较后验概率的大小,不需要精确的概率值,条件独立假设带来的误差在比较时可能相互抵消。

sklearn 提供了三种朴素贝叶斯:GaussianNB假设特征服从正态分布,适合连续特征;MultinomialNB适合离散计数特征,比如词频;BernoulliNB适合二值特征,比如词是否出现。选错了变体,效果会差很多。Iris 数据集的特征是连续值,用GaussianNB;文本分类用MultinomialNB或BernoulliNB。

GaussianNB的计算过程很直接:对每个类别,计算每个特征的均值和方差,然后代入高斯公式得到条件概率,再乘以类先验概率,最后取最大的那个类别。var_smoothing参数是 sklearn 加的一个平滑项,防止方差为 0 导致除零错误,默认 1e-9,通常不需要改。

3.2 高斯朴素贝叶斯代码实现与概率输出解读

# 朴素贝叶斯分类完整示例 from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split, cross_val_score from sklearn.naive_bayes import GaussianNB, MultinomialNB from sklearn.metrics import classification_report from sklearn.preprocessing import KBinsDiscretizer import numpy as np # 加载数据 iris = load_iris() X, y = iris.data, iris.target X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, random_state=42, stratify=y ) # 高斯朴素贝叶斯,适合连续特征 gnb = GaussianNB(var_smoothing=1e-9) gnb.fit(X_train, y_train) y_pred = gnb.predict(X_test) y_proba = gnb.predict_proba(X_test) # 输出每个类别的概率 print("高斯朴素贝叶斯准确率:", gnb.score(X_test, y_test)) print("\n前 5 个样本的预测概率:") for i in range(5): print(f" 样本{i}: 真实={iris.target_names[y_test[i]]}, " f"预测={iris.target_names[y_pred[i]]}, " f"概率={y_proba[i].round(3)}") # 查看每个类别的先验概率和特征均值 print("\n类别先验概率:", gnb.class_prior_.round(3)) print("各类别特征均值:") for i, name in enumerate(iris.target_names): print(f" {name}: {gnb.theta_[i].round(2)}") print("各类别特征方差:") for i, name in enumerate(iris.target_names): print(f" {name}: {gnb.sigma_[i].round(3)}") # 交叉验证 cv_scores = cross_val_score(gnb, X, y, cv=5) print(f"\n交叉验证: {cv_scores.mean():.4f} (+/- {cv_scores.std():.4f})")

predict_proba输出的概率值值得仔细看。如果某个样本的预测概率是[0.98, 0.01, 0.01],说明模型很确信;如果是[0.45, 0.40, 0.15],说明模型在犹豫,这种样本在实际部署时需要人工复核。class_prior_是类别的先验概率,Iris 三个类别各占三分之一,所以是[0.333, 0.333, 0.333]。theta_是每个类别下每个特征的均值,sigma_是方差,这两个属性直接反映了模型从数据中学到了什么。

如果特征不服从正态分布,GaussianNB的效果会打折扣。一个常见的做法是先做分箱离散化,然后用MultinomialNB:

# 将连续特征离散化为 5 个区间 kbins = KBinsDiscretizer(n_bins=5, encode='ordinal', strategy='uniform') X_binned = kbins.fit_transform(X) X_train_b, X_test_b, y_train_b, y_test_b = train_test_split( X_binned, y, test_size=0.3, random_state=42, stratify=y ) mnb = MultinomialNB(alpha=1.0) # alpha 是拉普拉斯平滑参数 mnb.fit(X_train_b, y_train_b) print("离散化+多项式朴素贝叶斯准确率:", mnb.score(X_test_b, y_test_b))

alpha=1.0是拉普拉斯平滑的默认值,作用是防止某个特征在某个类别下从未出现导致概率为 0。如果数据量很大,可以调小到 0.1 或 0.01;如果数据量小,保持 1.0 更安全。

3.3 朴素贝叶斯在文本分类上的实战:TF-IDF 与平滑参数

朴素贝叶斯最经典的应用场景是文本分类。下面用 20 类新闻数据集跑一个完整流程:

from sklearn.datasets import fetch_20newsgroups from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.naive_bayes import MultinomialNB from sklearn.pipeline import Pipeline from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report # 加载数据,只取 4 个类别加快速度 categories = ['alt.atheism', 'comp.graphics', 'sci.med', 'soc.religion.christian'] newsgroups = fetch_20newsgroups(subset='all', categories=categories, shuffle=True, random_state=42) X_train, X_test, y_train, y_test = train_test_split( newsgroups.data, newsgroups.target, test_size=0.25, random_state=42, stratify=newsgroups.target ) # 构建管道:TF-IDF 向量化 + 多项式朴素贝叶斯 pipeline = Pipeline([ ('tfidf', TfidfVectorizer( max_features=10000, # 最多保留 10000 个词 stop_words='english', # 去掉英文停用词 ngram_range=(1, 2), # 考虑单词和双词组合 min_df=2 # 至少在 2 个文档中出现才保留 )), ('nb', MultinomialNB(alpha=0.1)) ]) pipeline.fit(X_train, y_train) y_pred = pipeline.predict(X_test) print("文本分类准确率:", pipeline.score(X_test, y_test)) print(classification_report(y_test, y_pred, target_names=newsgroups.target_names))

TfidfVectorizer的参数需要根据数据规模调整。max_features=10000在 4 个类别、几千篇文档的规模下够用,如果类别更多需要增大到 50000 甚至 100000。ngram_range=(1, 2)同时考虑单词和双词组合,能捕捉“not good”这类否定短语,但特征维度会翻倍。min_df=2过滤掉只出现一次的词,减少噪声。alpha=0.1比默认的 1.0 更小,因为文本数据特征多、样本相对少,平滑太强会抹掉有区分度的词。

4. 人工神经网络分类器:从感知机到多层网络的代码落地

4.1 神经网络分类的数学本质:前向传播与反向传播

人工神经网络分类器的核心是一个复合函数:输入层接收特征,隐藏层做线性变换加非线性激活,输出层给出每个类别的得分。前向传播就是把这个复合函数算一遍,反向传播是用链式法则求损失函数对每个权重的梯度,然后用梯度下降更新权重。

对于分类任务,输出层通常用 Softmax 把得分转成概率,损失函数用交叉熵。二分类用 Sigmoid 加二元交叉熵,多分类用 Softmax 加多元交叉熵。隐藏层的激活函数现在主流用 ReLU,因为它计算简单、梯度不会饱和。输出层不能用 ReLU,因为需要概率值。

网络结构的设计没有固定公式。Iris 这种小数据集,一个隐藏层、十几个神经元就够了。图像分类需要卷积层,序列数据需要循环层或注意力机制。过深的网络在小数据集上必然过拟合,过浅的网络拟合能力不够。常见的做法是从一个隐藏层开始,逐步增加复杂度,同时用验证集监控过拟合。

4.2 用 PyTorch 搭建三层神经网络:完整训练循环与参数配置

# 人工神经网络分类完整示例 # 依赖:torch, scikit-learn, numpy import torch import torch.nn as nn import torch.optim as optim from sklearn.datasets import load_breast_cancer from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.metrics import accuracy_score, classification_report import numpy as np # 设置随机种子,保证结果可复现 torch.manual_seed(42) np.random.seed(42) # 1. 加载数据 data = load_breast_cancer() X, y = data.data, data.target # 2. 标准化:神经网络对特征尺度敏感,必须做 scaler = StandardScaler() X_scaled = scaler.fit_transform(X) # 3. 划分数据集 X_train, X_test, y_train, y_test = train_test_split( X_scaled, y, test_size=0.2, random_state=42, stratify=y ) # 4. 转为 PyTorch 张量 X_train_t = torch.FloatTensor(X_train) y_train_t = torch.LongTensor(y_train) X_test_t = torch.FloatTensor(X_test) y_test_t = torch.LongTensor(y_test) # 5. 定义网络结构 class Net(nn.Module): def __init__(self, input_dim, hidden_dim, output_dim): super(Net, self).__init__() self.fc1 = nn.Linear(input_dim, hidden_dim) # 第一层 self.relu = nn.ReLU() # 激活函数 self.dropout = nn.Dropout(0.3) # Dropout 防过拟合 self.fc2 = nn.Linear(hidden_dim, hidden_dim) # 第二层 self.fc3 = nn.Linear(hidden_dim, output_dim) # 输出层 def forward(self, x): x = self.relu(self.fc1(x)) x = self.dropout(x) x = self.relu(self.fc2(x)) x = self.dropout(x) x = self.fc3(x) # 输出层不加激活,CrossEntropyLoss 内部做 Softmax return x # 6. 实例化模型 input_dim = X_train.shape[1] # 30 个特征 hidden_dim = 64 output_dim = 2 # 二分类 model = Net(input_dim, hidden_dim, output_dim) # 7. 定义损失函数和优化器 criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-4) # 8. 训练循环 epochs = 200 train_losses = [] val_accuracies = [] for epoch in range(epochs): model.train() optimizer.zero_grad() outputs = model(X_train_t) loss = criterion(outputs, y_train_t) loss.backward() optimizer.step() train_losses.append(loss.item()) # 每 20 轮评估一次 if (epoch + 1) % 20 == 0: model.eval() with torch.no_grad(): val_outputs = model(X_test_t) _, predicted = torch.max(val_outputs, 1) acc = accuracy_score(y_test_t.numpy(), predicted.numpy()) val_accuracies.append(acc) print(f"Epoch [{epoch+1}/{epochs}], Loss: {loss.item():.4f}, " f"Test Acc: {acc:.4f}") # 9. 最终评估 model.eval() with torch.no_grad(): test_outputs = model(X_test_t) _, test_pred = torch.max(test_outputs, 1) test_proba = torch.softmax(test_outputs, dim=1) print("\n最终测试集准确率:", accuracy_score(y_test_t.numpy(), test_pred.numpy())) print("\n分类报告:") print(classification_report(y_test_t.numpy(), test_pred.numpy(), target_names=data.target_names))

第 2 步的标准化是必须的。神经网络的权重更新对输入尺度非常敏感,如果某个特征范围是 0 到 1000,另一个是 0 到 1,梯度会被大特征主导,小特征几乎学不到东西。StandardScaler把每个特征变成均值 0、方差 1,是最常用的做法。

第 5 步的网络结构里,Dropout(0.3)表示训练时随机丢弃 30% 的神经元,防止过拟合。如果发现训练集准确率远高于测试集,把 Dropout 率提高到 0.5;如果两者都低,说明欠拟合,需要增加隐藏层神经元数量或减少 Dropout。

第 7 步的Adam优化器学习率设 0.001 是通用起点。weight_decay=1e-4是 L2 正则化,和 Dropout 一起用能进一步抑制过拟合。如果训练损失震荡不下降,把学习率降到 0.0001;如果下降太慢,升到 0.01 试试。

第 8 步的训练循环里,optimizer.zero_grad()必须在loss.backward()之前调用,否则梯度会累加。这是 PyTorch 新手最容易踩的坑之一。model.train()和model.eval()的切换也很关键,Dropout 和 BatchNorm 在两种模式下的行为不同,忘了切换会导致评估结果不稳定。

4.3 训练过程监控:损失曲线与早停策略

训练神经网络不能只看最终准确率,要看损失曲线的形状。下面代码画出训练损失和验证准确率的变化:

import matplotlib.pyplot as plt fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4)) ax1.plot(train_losses) ax1.set_xlabel('Epoch') ax1.set_ylabel('Training Loss') ax1.set_title('Training Loss Curve') ax1.grid(True) ax2.plot(range(20, epochs+1, 20), val_accuracies, marker='o') ax2.set_xlabel('Epoch') ax2.set_ylabel('Test Accuracy') ax2.set_title('Test Accuracy Curve') ax2.grid(True) plt.tight_layout() plt.savefig('training_curve.png', dpi=150) plt.show()

正常的损失曲线应该先快速下降,然后逐渐平缓。如果损失一直震荡,检查学习率是否太大;如果损失下降很慢,检查学习率是否太小或者特征是否做了标准化。验证准确率曲线如果在某个点之后不再上升甚至下降,说明开始过拟合,应该在这个点之前停止训练。

早停策略可以用EarlyStopping手动实现:

best_acc = 0.0 patience = 20 # 连续 20 轮没提升就停 counter = 0 best_model_state = None for epoch in range(epochs): model.train() optimizer.zero_grad() outputs = model(X_train_t) loss = criterion(outputs, y_train_t) loss.backward() optimizer.step() model.eval() with torch.no_grad(): val_outputs = model(X_test_t) _, predicted = torch.max(val_outputs, 1) acc = accuracy_score(y_test_t.numpy(), predicted.numpy()) if acc > best_acc: best_acc = acc best_model_state = model.state_dict().copy() counter = 0 else: counter += 1 if counter >= patience: print(f"早停于 Epoch {epoch+1},最佳准确率: {best_acc:.4f}") break # 恢复最佳模型 model.load_state_dict(best_model_state)

patience=20表示连续 20 轮验证准确率没有提升就停止。best_model_state保存最佳轮次的权重,训练结束后恢复。这个策略能省下大量训练时间,同时避免过拟合。

5. 三套算法横向对比与避坑清单

5.1 同一份数据上的表现对比与选型建议

用乳腺癌数据集(30 个特征,二分类)跑三套算法,结果如下:

算法测试集准确率训练时间可解释性调参难度
决策树0.93极快强低
高斯朴素贝叶斯0.94极快中极低
三层神经网络0.97中等弱高

决策树的优势是规则可以直接读出来,适合需要向业务方解释的场景。朴素贝叶斯训练最快,适合作为基线模型。神经网络准确率最高,但需要调参、需要 GPU、需要更多数据。

选型建议:数据量小于 1000 条、特征少于 50 个,优先试决策树和朴素贝叶斯;数据量大于 10000 条、特征维度高、有非线性关系,上神经网络。不要一上来就上深度学习,很多时候决策树调好参就能达到业务要求。

5.2 避坑清单:五个真实踩过的坑

坑一:决策树不加random_state,每次结果不一样。现象是同一份数据跑两次准确率差 3 个百分点。原因是 sklearn 的决策树在特征值相同时随机选择分裂点。解决方法是固定random_state=42,并在交叉验证时也传入。

坑二:朴素贝叶斯用GaussianNB处理文本数据。现象是准确率只有 0.6,换成MultinomialNB后涨到 0.85。原因是文本的词频是离散计数,不服从正态分布。解决方法是文本用MultinomialNB或BernoulliNB,连续特征才用GaussianNB。

坑三:神经网络忘记标准化,损失不下降。现象是训练 100 轮损失还在 0.6 附近震荡。原因是特征尺度差异大,梯度被大特征主导。解决方法是训练前用StandardScaler或MinMaxScaler处理,且标准化参数只能从训练集计算,再应用到测试集。

坑四:PyTorch 忘记optimizer.zero_grad(),梯度累加导致权重爆炸。现象是损失变成 NaN。原因是每次反向传播的梯度没有清零,累加后数值过大。解决方法是在loss.backward()之前调用optimizer.zero_grad()。

坑五:用测试集调参,导致过拟合测试集。现象是测试集准确率 0.98,上线后掉到 0.7。原因是反复用测试集评估并调整参数,测试集信息泄露到了模型选择中。解决方法是划分训练集、验证集、测试集三份,用验证集调参,测试集只在最后评估一次。

6. 把三套模型串成一个可复用的分类流水线

实际项目中不会只跑一个模型,而是把多个模型串成流水线,用统一接口对比。下面这个ClassifierBenchmark类封装了三套算法,支持一键切换和对比:

from sklearn.tree import DecisionTreeClassifier from sklearn.naive_bayes import GaussianNB from sklearn.neural_network import MLPClassifier from sklearn.preprocessing import StandardScaler from sklearn.pipeline import Pipeline from sklearn.model_selection import cross_val_score import numpy as np class ClassifierBenchmark: """三套分类算法的统一对比工具""" def __init__(self, cv=5): self.cv = cv self.models = { 'decision_tree': Pipeline([ ('scaler', StandardScaler()), ('clf', DecisionTreeClassifier( max_depth=5, min_samples_leaf=2, random_state=42)) ]), 'naive_bayes': Pipeline([ ('scaler', StandardScaler()), ('clf', GaussianNB()) ]), 'neural_network': Pipeline([ ('scaler', StandardScaler()), ('clf', MLPClassifier( hidden_layer_sizes=(64, 32), activation='relu', solver='adam', alpha=1e-4, max_iter=500, random_state=42)) ]) } def run(self, X, y): """跑所有模型,返回交叉验证结果""" results = {} for name, model in self.models.items(): scores = cross_val_score(model, X, y, cv=self.cv, scoring='accuracy') results[name] = { 'mean': scores.mean(), 'std': scores.std(), 'scores': scores } print(f"{name}: {scores.mean():.4f} (+/- {scores.std():.4f})") return results def best_model(self, X, y): """返回交叉验证平均分最高的模型""" results = self.run(X, y) best_name = max(results, key=lambda k: results[k]['mean']) print(f"\n最佳模型: {best_name}") self.models[best_name].fit(X, y) return self.models[best_name] # 使用示例 from sklearn.datasets import load_breast_cancer data = load_breast_cancer() benchmark = ClassifierBenchmark(cv=5) results = benchmark.run(data.data, data.target) best = benchmark.best_model(data.data, data.target)

这个类的设计要点:每个模型都包在Pipeline里,标准化和分类器绑定,避免忘记标准化。MLPClassifier是 sklearn 自带的神经网络实现,不需要 PyTorch,适合快速对比。hidden_layer_sizes=(64, 32)表示两个隐藏层,分别有 64 和 32 个神经元。alpha=1e-4是 L2 正则化系数,max_iter=500是最大迭代次数,如果没收敛会报警告,需要增大这个值。

用这个工具跑乳腺癌数据集,通常会看到神经网络略优于决策树和朴素贝叶斯,但差距在 2 到 3 个百分点以内。如果业务对可解释性要求高,决策树是更好的选择;如果追求极致准确率且数据量足够,神经网络值得投入。

我自己的习惯是:新项目先用这个 benchmark 跑一遍,看三套算法的差距。如果决策树和神经网络差距小于 2 个百分点,直接用决策树,省去调参和部署的麻烦。如果差距大于 5 个百分点,再考虑上深度学习。这个判断规则帮我省下了不少时间,希望帮到你。

本文还有配套的精品资源,点击获取

返回列表