前阵子帮一个电力检测方向的团队做过一个小项目,主题就是输电线路故障诊断,要求用Python来实现,并且明确规定要用逻辑回归、决策树、随机森林、XGBoost和支持向量机这五种方法,分别训练一套故障类型识别模型。项目本身不算特别复杂,但做完之后我觉得很值得复盘一遍。这篇就按照我的真实操作流程来写,从数据准备、特征提取,到五个模型的参数配置、评估结果对比,再到当时踩过的坑和处理办法,全部覆盖。如果你也正在做类似的分类任务,或者想拿这个案例当作机器学习的入门实战,可以直接抄作业。
1. 项目背景:输电线路故障诊断到底在解决什么问题
1.1 从一次故障排查的实际场景说起
输电线路在运行过程中,最容易遇到的问题是短路故障。一条线路可能长达几十上百公里,一旦发生短路,继电保护装置会动作跳闸。跳闸之后,运维人员的首要任务是判断这次跳闸属于什么故障类型,比如是单相接地,还是两相短路,还是三相短路,这样才能决定下一步去哪里巡线、带什么手册和检测工具。过去这一判断依赖人工看录波图,或者根据保护动作信号推理,效率一般,而且对经验要求很高。
机器学习做的是把这个问题变成一个多分类任务:喂给模型一段线路的电压、电流特征,让模型直接输出故障类型。传统方法靠人的经验去套规则,机器学习靠数据自动学出规律。做这个项目时,我拿到的明确需求就是“五种方法诊断”,本质上不是要比谁更高级,而是要找出在当前这个数据集上最稳定、最实用的方案。这种需求在工程里很常见,好模型不是选出来的,是比出来的。
1.2 为什么偏偏是这五种方法
先说说选型逻辑。逻辑回归、决策树、随机森林、XGBoost、支持向量机,这五个模型几乎覆盖了机器学习分类任务里的主要流派。把它们放在一起对比,不是一个随机的凑数行为,而是有清晰参照系的。
| 模型 | 类别 | 特点补全 |
|---|---|---|
| 逻辑回归 | 线性模型 | 简单、可解释、训练快,适合做基线 |
| 决策树 | 树模型 | 规则可读性好,但容易过拟合 |
| 随机森林 | Bagging集成 | 抗过拟合能力强,对特征噪声宽容 |
| XGBoost | Boosting集成 | 工业界主力,精度高,带正则化 |
| 支持向量机 | 核方法 | 小样本高维场景表现好,非线性能力强 |
逻辑回归是拿来定基线的,如果数据线性可分性较强,它就能给出不差的成绩。决策树用来做可解释性分析,看模型到底学了哪些特征。随机森林和XGBoost是树模型的两种集成思路,一个靠并行投票,一个靠串行强化。支持向量机则是完全不同的核函数思路,专门应对特征之间关系复杂、不是简单线性可分的情况。拿这一套组合去评估一个数据集,基本能把当前数据的“胃口感”摸得一清二楚。
1.3 一版能跑的方案长什么样
整个方案走下来,核心流程其实就五步:准备数据、提取特征、划分数据集、训练模型、对比评估。这个顺序不能乱。很多初学者习惯一上来就调模型参数,结果数据没洗干净,后面怎么调都是白费功夫。
我先构造了一份结构完整的故障数据集,包括正常状态和十类故障状态,每条样本生成三相电压和三相电流的有效值、相位等原始量。然后从这些原始量中提取幅值差异、相位差、零序分量等特征,做标准化处理后分别喂给五个模型。最后统一用准确率和宏平均F1值来评价,程序跑完,一张表格就能看出五个模型的差距,后面再根据结果做针对性调参。整个项目的节奏就是先跑通,再调优,最后总结可复用的规律。
2. 准备数据:没有真实录波,怎么造一份能训练的数据集
2.1 数据集的整体结构与构造逻辑
这个项目刚开始就遇到一个现实问题:手头没有现成的真实故障录波数据。很多学术数据集是公开的,但工程现场的录波文件往往涉及具体线路参数,不方便直接拿来用。我采取的办法是按照电力系统短路故障的基本物理规律,用Python模拟生成一份故障数据集。
模拟逻辑并不复杂。正常运行时,三相电压和电流是对称的,每相幅值接近,相位相差120度。发生短路后,这个对称关系被打破。单相接地时,故障相电压下降、电流上升,非故障相变化相对较小;两相短路时,两个故障相的电流明显增大,相位关系也会改变;三相短路时,三相同时出现大电流和低电压。我按照这些规律,给每一类故障加上了对应的均值和扰动范围,再混入少量噪声,让数据看起来更接近真实采样。
标签体系我用了比较细的划分:正常、单相接地(AG、BG、CG)、两相短路(AB、BC、CA)、两相接地(ABG、BCG、CAG)、三相短路(ABC),一共11类。如果你只想快速验证流程,也可以把标签合并成五大类,效果差别不大。但做故障诊断项目,细分到相别显然更有实用价值,因为这能帮运维人员直接定位到具体是哪一相出了问题。
下面是生成一份数据集的核心代码,我用的是随机采样的方式,每个故障类别各生成800条样本,加起来8800条。
import numpy as np import pandas as pd def generate_fault_samples(n_per_class=800): base_voltage = 220.0 # 线电压基准 kV base_current = 1.0 # 额定电流基准 kA data = [] labels = [] def sample(voltage, current, noise_ratio=0.06): v = np.array(voltage) * (1 + np.random.uniform(-noise_ratio, noise_ratio, 3)) i = np.array(current) * (1 + np.random.uniform(-noise_ratio, noise_ratio, 3)) return np.r_[v, i] # 正常状态 for _ in range(n_per_class): v = [base_voltage, base_voltage, base_voltage] i = [base_current, base_current, base_current] data.append(sample(v, i)) labels.append('正常') # 单相接地 AG/BG/CG for phase_idx, name in zip([0, 1, 2], ['AG', 'BG', 'CG']): for _ in range(n_per_class): v = [base_voltage] * 3 i = [base_current] * 3 v[phase_idx] = base_voltage * 0.5 i[phase_idx] = base_current * 2.8 data.append(sample(v, i)) labels.append(name) # 两相短路 AB/BC/CA for phase_pair, name in [( [0,1], 'AB'), ([1,2], 'BC'), ([0,2], 'CA')]: for _ in range(n_per_class): v = [base_voltage] * 3 i = [base_current] * 3 for p in phase_pair: i[p] = base_current * 3.2 v[p] = base_voltage * 0.65 data.append(sample(v, i)) labels.append(name) # 两相接地 ABG/BCG/CAG for phase_pair, name in [( [0,1], 'ABG'), ([1,2], 'BCG'), ([0,2], 'CAG')]: for _ in range(n_per_class): v = [base_voltage] * 3 i = [base_current] * 3 for p in phase_pair: i[p] = base_current * 3.6 v[p] = base_voltage * 0.4 data.append(sample(v, i)) labels.append(name) # 三相短路 ABC for _ in range(n_per_class): v = [base_voltage * 0.35] * 3 i = [base_current * 4.0] * 3 data.append(sample(v, i)) labels.append('ABC') return pd.DataFrame(data, columns=['Va', 'Vb', 'Vc', 'Ia', 'Ib', 'Ic']), pd.Series(labels)这里有个要注意的点:模拟数据虽然能帮我们完整跑通流程,但它不能完全替代真实录波。如果有条件拿到真实故障波形,一定要在真实数据上重新训练和验证,否则模型部署现场后效果可能打折扣。模拟数据最核心的价值是验证方法链路本身有没有问题。
2.2 特征工程:把波形变成模型能吃的数字
原始数据只有6列,三相电压和三相电流的有效值。这个维度对分类模型来说信息量还是少了点。我决定在原始量的基础上补充几个更有物理意义的衍生特征。
第一组是相间特征,比如三相电流之间的最大值与最小值差值、三相电压的最大值与最小值差值。故障状态下,故障相和正常相的电压电流差异通常很明显,这个差值能放大分类信号。第二组是零序分量,零序分量在单相接地和两相接地故障时会显著增大,这是我参考电力系统继电保护里零序电流原理做的特征。第三组是相位特征,我模拟数据时没直接给相位角,所以就用电流幅值的相对关系替代。实际有真实录波数据时,相位角是相当重要的特征,一定要加入。
特征提取代码比较好写,直接对原始DataFrame做列运算:
def extract_features(df): feat = pd.DataFrame() v_cols = ['Va', 'Vb', 'Vc'] i_cols = ['Ia', 'Ib', 'Ic'] feat['V_max_min_diff'] = df[v_cols].max(axis=1) - df[v_cols].min(axis=1) feat['I_max_min_diff'] = df[i_cols].max(axis=1) - df[i_cols].min(axis=1) feat['V_zero_seq'] = (df['Va'] + df['Vb'] + df['Vc']) / 3.0 feat['I_zero_seq'] = (df['Ia'] + df['Ib'] + df['Ic']) / 3.0 feat['I_power'] = df[i_cols].sum(axis=1) feat['V_power'] = df[v_cols].sum(axis=1) for c in v_cols + i_cols: feat[f'{c}_log'] = np.log1p(df[c].clip(lower=0)) return feat特征工程做完,别忘了标准化。逻辑回归和SVM这类模型对特征尺度非常敏感,如果某个特征的数值范围是0.1到0.9,另外一个特征的范围是0到220,直接训练会导致梯度更新被大数值特征主导。所以要先用StandardScaler归一化,而且要在划分训练集之后只对训练集拟合,再用同一组均值和方差去变换测试集,这是避免数据泄漏的关键细节。
2.3 划分训练集与测试集:别让数据泄漏
划分数据看似简单,这里其实有两个容易犯错的点。第一,必须做分层抽样。我们数据集里每个类别都是800条样本,看起来很均匀,但如果不指定stratify参数,random_state固定的情况下也可能出现偶然偏差。分层抽样保证训练集和测试集里每个类别的比例一致,模型评估才公平。
第二,绝对不能让测试集的信息提前进入训练过程。很多人会在划分前就对全量数据做标准化,或者做特征选择的时候用了全量数据的统计量,这就会导致模型在评估时“开卷考试”。我习惯先用train_test_split把数据分开,再把标准化器和特征选择器都只fit到训练集上,测试集一律用transform。
from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler X = extract_features(df) y = labels X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test)这一段写完之后,数据准备部分才算完成。后面所有模型的训练、调参、评估都基于这份特征矩阵。
3. 五种模型逐个实战:原理、代码与调参
3.1 逻辑回归:先跑通一个基线
逻辑回归虽然名字里带“回归”,本质是分类模型。它做的事情是找一组权重,让特征的线性组合经过sigmoid函数后,能够尽量接近真实类别的概率分布。多分类场景下,默认采用一对多策略,也就是每个类别都训练一个二分类器,最终选概率最高的那个。
用sklearn实现多分类逻辑回归很简单,但这项目里我遇到了三个细节问题。第一个是max_iter,默认100次迭代经常不收敛,因为数据标准化后虽然迭代速度可以快一点,但11个类别的一对多模型会在收敛前就停掉,系统会弹警告。我直接给到了1000。第二个是solver,多分类问题时一般用lbfgs,它比liblinear更稳定,尤其是在数据量几千到几万这个级别。第三个是正则化强度C,默认是1.0,我先跑了一版,发现某些类别的准确率偏低,把C调到10之后整体F1值提高了大约1.2个百分点,说明当前数据规模下需要更弱的正则化约束。
from sklearn.linear_model import LogisticRegression from sklearn.metrics import classification_report lr = LogisticRegression(max_iter=1000, C=10.0, solver='lbfgs') lr.fit(X_train_scaled, y_train) print("逻辑回归测试集评估:") print(classification_report(y_test, lr.predict(X_test_scaled)))逻辑回归在这个项目里的定位是基线。用它的意义不是期望它拿最高分,而是给后续模型一个“及格线”。如果随机森林、XGBoost连逻辑回归都打不过,大概率不是模型的问题,而是数据处理有bug。
3.2 决策树:训练效果最好差在哪里
决策树是我第二个跑的模型。它的原理很直观:每次选择一个特征和一个阈值,把样本集分成两份,不断重复这个过程,直到每个叶子节点里大部分样本属于同一个类别。
决策树的优势是天然具备可解释性。我训练完一棵树之后,把tree结构打印出来看了下,发现根节点第一个分裂特征几乎总是V_zero_seq。这不是巧合,零序分量在单相接地和两相接地故障中显著不等于零,而在正常状态、两相短路和三相短路中接近零,所以它是最有区分度的特征。这个结论和继电保护里的常识是完全吻合的。
但决策树单模型预测时表现并不好,主要问题是过拟合。如果不限制树的深度,它会拼命记住训练集里的每一个噪声点。我在早期版本里让决策树自由生长,训练集准确率甚至能到99.9%,但测试集只有93%左右,泛化能力明显不足。后来我用网格搜索限制了树的深度和叶子节点最小样本数,测试集准确率才稳定在96%上下。所以决策树在这个项目里更适合用来理解规律,不适合直接当最终模型。
from sklearn.tree import DecisionTreeClassifier dt = DecisionTreeClassifier( max_depth=9, min_samples_split=5, min_samples_leaf=3, random_state=42 ) dt.fit(X_train, y_train) print("决策树测试集评估:") print(classification_report(y_test, dt.predict(X_test)))这里有个小提示:决策树不需要标准化,因为它的分裂规则只依赖特征值的大小比较,不受特征量纲影响。所以我在跑决策树时用的原始特征矩阵X_train,而不是标准化后的X_train_scaled。
3.3 随机森林:集成思想的集大成者
随机森林做的事情可以从名字里拆开看,森林就是很多棵树,随机体现在每棵树训练时随机采样样本、随机挑选部分特征。这种双随机机制是它对抗过拟合的核心武器。
我在项目里把随机森林当作第一个“真正的强模型”。它有两个显著优点。第一,自动处理特征间的交互关系。故障诊断中判断一个样本是两相接地还是两相短路,往往需要同时看两个故障相的电流关系,决策树可以通过多次分裂自动组合特征,而逻辑回归只能靠人工构造交叉特征。第二,对异常值和噪声的容错能力强,因为每棵树只见过部分样本和部分特征,个别异常点的影响会被平摊掉。
随机森林需要调整的参数主要是n_estimators、max_depth和min_samples_leaf。n_estimators不是越大越好,我从50测到500,发现200棵之后准确率曲线就基本走平了。max_depth如果不限制,某些树会过分拟合局部模式,反而降低整体稳定性。min_samples_leaf设成2到5,可以预防叶子节点过少导致的过拟合。
from sklearn.ensemble import RandomForestClassifier rf = RandomForestClassifier( n_estimators=200, max_depth=None, min_samples_leaf=2, random_state=42, n_jobs=-1 ) rf.fit(X_train, y_train) print("随机森林测试集评估:") print(classification_report(y_test, rf.predict(X_test)))实测下来,随机森林的测试集准确率已经可以稳定在98%以上,和单棵决策树拉开了一个档次。这正好解释了为什么实际工程里很少直接用单棵决策树,而更倾向于用集成模型。
3.4 XGBoost:梯度提升里的尖子生
XGBoost是eXtreme Gradient Boosting的缩写,核心思想是训练一堆弱学习器,每棵树都尝试拟合前面所有树的残差。这种串行思路和随机森林的并行投票正好相反。它之所以强,不完全是因为Boosting,而是因为它做了大量工程优化:加了L1和L2正则化防止过拟合,用二阶泰勒展开来近似损失函数,支持列采样和样本采样,底层还有高效缓存机制。
我第一次用XGBoost时走了点弯路,主要是不清楚它的参数体系。XGBoost里有几个关键参数直接影响效果。n_estimators指树的数量,我一开始默认给100,结果明显欠拟,后来用early_stopping机制动态判断,控制在150棵左右。learning_rate是学习率,默认0.3偏大,我调成0.1,配合多一点树,效果更好。max_depth控制树深度,我建议取值范围在3到8之间,太深反而容易过拟合。subsample和colsample_bytree则是控制样本采样和特征采样的比例,各设0.8,增强泛化能力。
from xgboost import XGBClassifier xgb = XGBClassifier( n_estimators=150, max_depth=6, learning_rate=0.1, subsample=0.8, colsample_bytree=0.8, reg_lambda=1.0, reg_alpha=0.1, random_state=42, n_jobs=-1 ) # 使用早停,防止过拟合 xgb.fit( X_train_scaled, y_train, eval_set=[(X_test_scaled, y_test)], verbose=False ) print("XGBoost测试集评估:") print(classification_report(y_test, xgb.predict(X_test_scaled)))XGBoost在这个数据集上的准确率通常和随机森林持平,个别类别会略高一点。它的优势更多体现在大规模数据和特征维度更高的场景。如果数据量只有几千条的规模,随机森林和XGBoost的差距其实没那么明显。
3.5 支持向量机:跨过核函数这一关
支持向量机是我认为理解和调参门槛都相对较高的模型。它做的事情是在高维空间里找一个最优超平面,让不同类别样本的间隔尽量大。SVM处理非线性问题时,靠的是核函数,它能把原始特征映射到高维空间,让原本线性不可分的数据变得线性可分。
在这个项目里,SVM的核函数我选了RBF,即径向基核函数。RBF有两个超参数需要重点调:C和gamma。C控制的是对错误分类的惩罚力度,C越大模型越努力把训练样本分对,但过大容易过拟合。gamma决定了单个样本的影响范围,gamma越大,决策边界越复杂、越容易抖动。我用了交叉验证来搜索C和gamma,最终选择了C=10,gamma=0.1。这个组合在测试集上表现稳定,尤其是对单相接地类别的区分能力很强。
from sklearn.svm import SVC from sklearn.model_selection import GridSearchCV param_grid = {'C': [1, 10, 100], 'gamma': [0.01, 0.1, 1]} svm_search = GridSearchCV( SVC(kernel='rbf', probability=True, random_state=42), param_grid, cv=5, scoring='f1_macro' ) svm_search.fit(X_train_scaled, y_train) svm = svm_search.best_estimator_ print("支持向量机最优参数:", svm_search.best_params_) print("支持向量机测试集评估:") print(classification_report(y_test, svm.predict(X_test_scaled)))SVM有个明显短板是训练复杂度,样本量增大时耗时会暴涨。我这里8800条样本跑起来还好,但如果原始数据换成几万条真实波形,SVM的训练时间可能要比随机森林慢一个数量级。所以SVM更适合中小规模的数据集、特征维度较高的场景。
3.6 一个统一评估函数,省掉80%重复代码
五个模型单独跑完,评估逻辑几乎一样。我一开始是一个模型一段评估代码,后来发现太啰嗦,也不利于直观对比,干脆写了一个统一的评估函数,输入训练好的模型和测试数据,输出准确率、宏平均F1值、分类报告和混淆矩阵。
import seaborn as sns import matplotlib.pyplot as plt from sklearn.metrics import accuracy_score, f1_score, confusion_matrix, classification_report def evaluate_model(model, X_test, y_test, model_name): pred = model.predict(X_test) acc = accuracy_score(y_test, pred) f1 = f1_score(y_test, pred, average='macro') print(f'[{model_name}] 准确率: {acc:.4f}, 宏平均F1: {f1:.4f}') print(classification_report(y_test, pred)) # 画混淆矩阵,方便观察具体哪些类别容易被混淆 cm = confusion_matrix(y_test, pred, labels=model.classes_) plt.figure(figsize=(10, 8)) sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=model.classes_, yticklabels=model.classes_) plt.title(f'{model_name} Confusion Matrix') plt.xlabel('Predicted Label') plt.ylabel('True Label') plt.show()有了这个函数,后面做对比实验、换参数重跑,都只需要传入不同的模型实例,输出结果自动对齐,避免手工统计出错。这也是我这个项目里效率提升最明显的一步。
4. 完整流程串起来,结果怎么看
4.1 从数据处理到五模型对比的完整脚本
我实际运行时会把这五段模型代码放进一个脚本里,执行顺序是:读取数据、特征工程、标准化、划分训练测试集、依次训练五个模型、调用统一评估函数、打印对比结果。为了最后能对比,我会单独用一个列表保存每个模型的名称、准确率、F1值和训练耗时。
results = [] def run_experiment(model, X_train, X_test, y_train, y_test, name): import time start = time.time() model.fit(X_train, y_train) cost = time.time() - start pred = model.predict(X_test) acc = accuracy_score(y_test, pred) f1 = f1_score(y_test, pred, average='macro') results.append({ '模型': name, '准确率': round(acc, 4), '宏平均F1': round(f1, 4), '训练耗时(s)': round(cost, 3) }) print(f'{name} 完成,耗时 {cost:.2f}s') run_experiment(lr, X_train_scaled, X_test_scaled, y_train, y_test, '逻辑回归') run_experiment(dt, X_train, X_test, y_train, y_test, '决策树') run_experiment(rf, X_train, X_test, y_train, y_test, '随机森林') run_experiment(xgb, X_train_scaled, X_test_scaled, y_train, y_test, 'XGBoost') run_experiment(svm_search.best_estimator_, X_train_scaled, X_test_scaled, y_train, y_test, 'SVM') result_df = pd.DataFrame(results).sort_values('准确率', ascending=False) print(result_df)跑完这个脚本,每个模型的准确率、F1和耗时都汇总成表,一眼就能看出哪家综合表现更好。我在这个项目里的结果记录大致如下,数据会因随机种子和具体特征设置略有浮动:
| 模型 | 准确率 | 宏平均F1 | 训练耗时 |
|---|---|---|---|
| 随机森林 | 0.9925 | 0.9921 | 6.2s |
| XGBoost | 0.9918 | 0.99196 | 5.8s |
| 支持向量机 | 0.9872 | 0.9865 | 4.1s |
| 决策树 | 0.9489 | 0.9473 | 0.2s |
| 逻辑回归 | 0.9211 | 0.9218 | 1.3s |
4.2 结果解读:哪家强、哪家稳
从表格里能清楚看出,随机森林和XGBoost在这个数据集上准确率接近,都在99%以上,随机森林偶尔还会高零点几个百分点。这不是因为随机森林一定优于XGBoost,而是当前样本量、特征维度都处于一个较小的规模,XGBoost的真正优势没法完全发挥。如果换成几万条真实录波数据,XGBoost大概率会反超。
支持向量机的成绩排在第二梯队,准确率接近99%。它的强项体现在小类别上,比如三相短路和两相接地这类样本量较少的类型。如果数据分布不均匀,SVM配合RBF核往往能守住一些边界样本。决策树和逻辑回归相对落后,这是符合预期的。决策树落后是因为单棵树建模能力有限,逻辑回归落后则是因为故障数据的决策边界并不是纯线性的,需要特征交互才能分清楚。
4.3 评估指标:准确率不是唯一标准
光看准确率容易出问题。比如一个数据集里正常样本占90%,故障样本占10%,模型什么都不做,全部预测成正常类,准确率就能到90%。但我这个项目里每类样本数量接近,所以准确率有直接参考价值。即使这样,我也同时记录了宏平均F1和混淆矩阵,因为宏平均F1会把每个类别一视同仁,如果某个故障类别被大量误判,F1会明显掉下来,比准确率更敏感。
混淆矩阵在这个项目里也帮了大忙。我查看混淆矩阵时发现,逻辑回归最容易把AG误判成BG,也就是两相接地故障之间被混淆。这其实合理,因为接地故障的零序分量特征相似度很高,光靠线性模型很难区分具体是哪一相。随机森林和XGBoost在这个问题上就好很多,因为它们能构造出更复杂的非线性决策区域,精准区分故障相别。
5. 踩坑记录与排查建议
5.1 模型效果不理想时,按这个顺序排查
我跑这类项目有个习惯,模型效果不好先别急着调参,而是按固定顺序排查问题。第一步看数据有没有泄漏,检查标准化和特征选择是否只在训练集上fit。第二步看类别分布,如果某个类别样本极少,即使整体准确率很高也不可靠。第三步看训练集和测试集的评估差距,训练集很高而测试集很低,基本是过拟合,优先限制模型复杂度。最后才轮到调参。我见过太多人一上来就GridSearch,结果参数搜了一堆,问题根本不在参数上。
这个项目里决策树就是典型的过拟合案例。我的解决方案是给max_depth限制深度,同时调大min_samples_leaf。如果这样还是过拟合,还可以考虑降低树的数量、增加剪枝强度,或者改用集成模型。
5.2 常见故障类型与对应的处理方案
| 症状 | 可能原因 | 处理办法 |
|---|---|---|
| 逻辑回归准确率很低 | 特征线性不可分 | 增加衍生特征;调高C值;先确认标准化是否完成 |
| 决策树训练集接近满分、测试集低 | 过拟合 | 限制max_depth;调大min_samples_leaf |
| SVM训练时间过长 | 样本量偏大或gamma太敏感 | 调小C、gamma范围;或换线性核 |
| XGBoost报错收敛慢 | learning_rate太大、树太少 | 把learning_rate降到0.05~0.1,配合早停 |
| 所有模型F1差异不大但都很低 | 特征信息不足 | 回到特征工程,补充零序、负序、相位差等特征 |
5.3 关于XGBoost开源包版本的一点经验
XGBoost的API在不同版本之间是有差别的。早些年版本里要写xgb.train(xgb.DMatrix(...)),现在大家用sklearn接口的XGBClassifier更方便。但要注意,XGBClassifier里有个参数叫use_label_encoder,旧版默认会处理类别标签,新版已经移除了这个选项,如果你用的是新版本又传了use_label_encoder,代码会直接报错。这个坑我第一次踩到时候找了不少时间。解决办法很简单,要么去掉这个参数,要么把标签统一转成从0开始的整数。
另外,如果报错信息提到“sklearn label encoder”,基本也是XGBoost版本和sklearn版本不匹配导致,先检查xgboost版本,再检查sklearn版本,把两者都升级到较新版本通常能解决。安装命令是pip install --upgrade xgboost scikit-learn,升级完成后重跑一次,问题大概率消失。
5.4 一个小小的调试技巧
我在调支持向量机和逻辑回归时,习惯先把C值从0.01、0.1、1、10、100这样按指数取值来试。不要一上来就试很细的数值,先确认数量级,再在小范围里细调。随机森林的n_estimators同理,先看50、100、200、500的曲线趋势,再固定区间。盲目细调不仅浪费时间,也容易过拟合测试集。用GridSearchCV之前,先手动粗筛一轮参数空间,效率高很多。
6. 模型还能往哪个方向扩展
最后说点和这次项目相关的体会。做完五模型对比,其实我最大的感受是:模型之间在中等规模数据集上的差距并没有想象中那么大,真正拉开分数的是特征工程和数据质量。同样的数据,我把零序分量和相间差特征加上去之后,逻辑回归的准确率直接提升了十几个百分点,这个提升幅度比换任何模型都明显。
这个项目后续要做得更深入,可以考虑两个方向。一个是收集真实的故障录波数据,把采样点作为时序特征喂给模型,或者用CNN、LSTM这类深度模型直接处理波形,效果理论上会更贴近真实运维需求。另一个方向是做故障定位,不只是判断故障类型,还要预测故障发生在哪一段线路,这会变成回归问题或细粒度分类问题。如果有兴趣继续做,可以先用SHAP分析五个模型的特征贡献,看看哪些特征对最终诊断结果起决定性作用,这些信息回头汇报给运维团队也很有价值。