尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

逻辑回归实战:类别型特征编码与不平衡分类处理全流程

逻辑回归实战:类别型特征编码与不平衡分类处理全流程 我最早接触 Car Evaluation 这个项目是因为一份实验目录里躺着一个叫car-evaluation/逻辑回归的文件夹。一开始我的反应是又一个拿公共数据集刷模型的练手题。但真正跑完一段之后我才意识到这个数据结构非常“刁钻”——六列全是类别型变量目标四分类里有一类占到了七成逻辑回归这个线性模型在里面既要对抗数量不均又要处理低区分度的少数类。这篇就把我从读数据、做编码、训练调参到看混淆矩阵的完整过程写下来包括那些只在实操中才能碰见的坑给刚开始用逻辑回归做表格分类的同学当一份参考。1. 为什么拿汽车评估练逻辑回归这个数据集没那么简单1.1 一条样本就是一个购车决策底层结构拆解Car Evaluation 这个数据集不需要做特征工程就已经很贴近真实场景了每一条记录代表一辆车的六个属性最终标记这辆车“可不可接受”。我第一次把这个表展开看的时候发现它像极了现实中售车顾问评分卡的样子买价、维护成本、车门数、可载人数、后备箱尺寸、安全性一个不多一个不少。具体来说六个输入特征以及它们的取值范围是这样的特征含义候选取值buying买入价格档位vhigh / high / med / lowmaint维护费用档位vhigh / high / med / lowdoors车门数量2 / 3 / 4 / 5morepersons可载人数2 / 4 / morelug_boot后备箱容量small / med / bigsafety安全等级low / med / high目标变量是四档有序评估unacc不可接受、acc可接受、good好、vgood很好。你可以把每个特征想象成一位评审打出的分项分数四个目标等级就是把分项分汇总后的最终结论。这个结构对做逻辑回归特别友好因为特征之间没有强相关性每个维度都有明确的业务解释。逻辑回归输出的系数背后可以直接读出“安全性每提升一档可接受概率上升多少”这种可解释的结论。相比树模型给出一堆复杂的分叉规则线性模型在业务汇报场景里更容易讲清楚。1.2 四个目标等级与明显倾斜的类别分布数据总量不大只有 1728 条没有缺失值这份“干净”让它看起来人畜无害。但只要你统计一下目标变量的频次就会发现事情不单纯unacc不可接受约 1210 条占比 70%acc可接受约 384 条占比 22%good好约 69 条占比 4%vgood很好约 65 条占比 4%四分类任务头重脚轻其中两个“好”类合计不到样本量的 10%。这跟许多真实业务场景很像——比如风控里的违约样本、设备故障样本都是少数类决定业务价值。如果在训练时不做任何处理模型只要学会把所有样本都判定成 unacc准确率就有 70%。这个数字看着还行但业务上完全没有意义。所以练习逻辑回归的进阶价值恰恰体现在这里你不仅要会调用模型还要会处理不平衡分布、会选择合适的评估指标、会在少数类召回和整体准确率之间做取舍。这些经验放到真实项目里是直接能用的。1.3 逻辑回归在这个数据集上的先天条件为什么偏偏用逻辑回归而不是一上来就堆 XGBoost这个数据集的六列特征全是类别型并且每个类别背后的决策机制大概率是规则性、可线性切分的。你可以把它想象成一张打分表每项达到某个档位就累计一定分数最终按总分划档。这种结构意味着线性边界已经能解释大部分样本逻辑回归作为第一个模型非常合适。我当时给自己定的目标很明确先让线性模型在这个数据集上达到一个可用的、可解释的基线再决定要不要升级到更复杂的模型。实际上跑完一轮就能看到逻辑回归在判别 unacc 和 acc 上表现很好真正拉低分数的是 good 和 vgood 两类。这也验证了一件事很多分类项目的性能上限不在模型复杂度而在少数类本身的特征区分度。2. 类别型特征怎么喂进线性模型编码选型比调参更关键2.1 为什么不能直接给低价/中价/高价标成 0/1/2第一次跑这类数据的时候很容易犯一个直觉性错误把 vhigh、high、med、low 直接映射成 0、1、2、3。这样最省事模型也能跑起来但里面埋了一个大坑——这些数字被模型当作有实际意义的“距离”。买价从 low 到 med 的差距是 1从 med 到 high 的差距也是 1从 high 到 vhigh 还是 1。但现实中的价格梯度根本不可能是等距的高档车和次高档之间的价差可能远大于中档和低档之间的价差。一旦你这么编码逻辑回归就会认为它们之间的边际效应是均匀的系数自然会被带偏。我当时用一个简单的实验验证了这个陷阱同一份数据标签编码方案下训练和独热编码方案下训练模型系数和预测结果差异明显。原因在数据量小、特征少的时候会被放大。所以对于没有内在顺序信息、或者顺序间距不可靠的类别型特征最稳妥的做法就是不排序、不编号采用独热编码让模型自己从数据里学习每个档位的影响。2.2 One-Hot 编码后的真实维度从 6 列到 21 列以 Car Evaluation 为例六个特征的取值数量加总独热编码后大约会生成 21 到 22 个二值列。数据变成了一张很稀疏的表每行大部分位置是 0少数位置是 1。有一个细节值得注意逻辑回归对特征量纲和稀疏程度其实是比较宽容的因为 0/1 编码的量纲天然一致不会像连续变量那样受到“一列值范围 0 到 1另一列值范围 0 到 10000”的影响。所以在这个项目中我没有对独热编码后的特征再做标准化。如果你引入了某些统计特征比如后续我提到的目标编码那就需要重新考虑尺度问题。独热编码之后模型系数变得更容易解释每个系数对应“该类别取值存在”这件事。比如safety_high1的系数很大且为正说明高安全性对“车辆可接受”有强烈正向贡献。这是逻辑回归和树模型最大的不同树模型给的是分裂路径线性模型给的是直接的方向和强度。2.3 目标变量当成四分类还是保留顺序Car Evaluation 的目标等级其实是有自然顺序的unacc acc good vgood。看到这个有序结构第一反应可能是用某种有序回归模型Cumulative Link Model来拟合。但实际工程中绝大多数实践者会直接把它当作普通多分类问题用 softmax 逻辑回归或一对多方案解决。原因有两个。第一主流机器学习库默认提供的多分类逻辑回归不支持有序假设手写累积连接函数的成本不低第二业务上我们更关心“是否能准确区分好车和差车”而不是精确预测它在四个等级里的排序位置。直接把目标变量用 LabelEncoder 转成 0-3 的数字然后当作回归问题处理是绝对错误的那会让模型学到人造的“数值距离”把 unacc 和 vgood 之间的差距等同于数字 3 和数字 0 的差距。我在项目里的做法是目标变量只做类别索引映射不参与任何特征计算模型采用多分类逻辑回归的 softmax 变体让概率自然分布到四个类上。3. 一对多还是 softmax多分类逻辑回归的内部骨架3.1 OvR拆成两两胜出的武斗会逻辑回归本质上只能回答“是/否”问题。要让一个二分类器处理四分类任务最经典的做法是 OvROne-vs-Rest训练四个独立分类器每个分类器负责区分“属于这一类”和“不属于这一类”两种阵营。预测时把样本分别送入四个分类器取输出置信度最高的那个作为最终结果。这个方案在旧版机器学习库中几乎是默认行为因为四个独立模型可以在小数据下并行训练速度较快。同时模型的可解释性也更直观每个类别都有一组系数你直接看“识别 acc 的分类器”里哪些特征最重要。但这个方案有一个隐藏缺陷四个分类器都在“不对称”的数据上训练比如“识别 good”的分类器正样本只有 69 条负样本却有 1659 条极大不平衡会影响每个子分类器的边界学习。这也是它在少数类表现不佳的根源之一。3.2 softmax所有选项一起打分另一种多分类方案是直接把最后输出层改造为 softmax模型一次性计算出四个类别的概率且概率值之和恒为 1。它和逻辑回归的核心思想一脉相承线性组合加上非线性激活函数。在 Car Evaluation 项目里我推荐用这种方案因为它共享了所有类别的特征信息模型参数规模也小一些。当你是四分类的时候softmax 让每个样本的真实分布更明确——一个样本的“unacc 概率”升高必然意味着其他三个类别的概率总和下降这种竞争机制对类别之间有相似性的任务更有利。实践中如果用某个带 liblinear 求解器的库你会发现它不支持 multinomial/softmax这时你只能退回到 OvR。当时我的模型就出现了“softmax 比 OvR 的 macro F1 高 2-3 个百分点”的现象所以优先选择支持多分类的求解器是值得的。3.3 线性决策边界为什么能拿到八成以上正确率我最初有些怀疑一个线性模型面对四分类数据凭什么能拿接近九成的准确率跑完代码后我意识到Car Evaluation 数据本身的决策边界就是接近线性的。你可以把每个特征想象成一根标尺车辆评分是各标尺读数的加权总和最终和阈值比较这天然就是线性分类器擅长的事情。另外一个辅助因素是独热编码带来的“分而治之”效果。每个取值都被单独记了一列模型不用自己做特征交互就能让某个强相关档位获得更高的权重。假如买价是 vhigh那一列的显著负系数就直接压低了可接受概率不需要组合其他特征才能表达这条规则。当然这不是说逻辑回归永远够用。如果换一个特征之间高度耦合的数据集线性模型会迅速碰壁。但在 Car Evaluation 这个具体场景里它先验证了“越简单的模型越适合当基线”这句话。4. 用代码完整走一遍从读到评估4.1 数据读入与编码的一条龙代码我先把完整流程的代码骨架放出来再逐段解释为什么这么写。这个项目目录叫car-evaluation/逻辑回归我习惯把数据处理、建模、评估拆成三个文件但为了阅读方便这里合并展示核心部分。import pandas as pd import numpy as np from sklearn.linear_model import LogisticRegression from sklearn.model_selection import StratifiedKFold, cross_val_score, train_test_split from sklearn.preprocessing import OneHotEncoder, LabelEncoder from sklearn.compose import ColumnTransformer from sklearn.metrics import classification_report, confusion_matrix, f1_score columns [buying, maint, doors, persons, lug_boot, safety, label] df pd.read_csv(car.data, headerNone, namescolumns) X df.drop(columns[label]) y df[label] # 先对目标变量做索引化映射 label_enc LabelEncoder() y_encoded label_enc.fit_transform(y) print(label_enc.classes_) # 类别型特征列 categorical_cols [buying, maint, doors, persons, lug_boot, safety] encoder OneHotEncoder(handle_unknownignore, sparse_outputTrue) preprocessor ColumnTransformer( transformers[(cat, encoder, categorical_cols)] ) X_encoded preprocessor.fit_transform(X) print(编码后特征维度:, X_encoded.shape[1]) # 直接使用多分类 softmax 逻辑回归 model LogisticRegression( multi_classmultinomial, solverlbfgs, max_iter1000, class_weightNone, random_state42 ) skf StratifiedKFold(n_splits5, shuffleTrue, random_state42) scores_acc cross_val_score(model, X_encoded, y_encoded, cvskf, scoringaccuracy) scores_f1 cross_val_score(model, X_encoded, y_encoded, cvskf, scoringf1_macro) print(5折准确率: %.4f (/- %.4f) % (scores_acc.mean(), scores_acc.std())) print(5折 macro-F1: %.4f (/- %.4f) % (scores_f1.mean(), scores_f1.std()))有几个点容易踩单独提醒一下。car.data文件的表头原本是不存在的所以读文件时必须自己指定headerNone和列名否则第一行会被当成数据表头丢掉。列名里尽量不要用class作为数据列名否则后面访问属性时容易和 Python 关键字混淆我干脆用了label。编码器OneHotEncoder使用handle_unknownignore是为了防止训练集和验证集类别不一致时直接报错属于行业习惯。交叉验证用StratifiedKFold而不是普通的KFold主要为了让每一折的训练和验证集保持和目标变量同样的四分类比例这对不平衡数据非常重要。4.2 训练结果与第一个反直觉发现用上面的基准配置跑完得到的准确率大概是 86% 左右但 macro-F1 却只有 0.55 到 0.60。初看准确率很漂亮再往里看问题很大。这说明准确率在这个数据集上是一个非常会骗人的指标。我又单独做了一次 80/20 的数据拆分打印完整的分类报告结果如下数值大致是这个量级不同随机种子会有小幅浮动目标类别样本数PrecisionRecallF1unacc12100.920.970.94acc3840.720.480.58good690.470.030.06vgood650.800.060.11可以看到unacc 的 F1 高达 0.94acc 勉强能用good 和 vgood 几乎处于“猜谜”状态。这直接证明了类别不平衡的影响样本少的两个类别模型基本没学到有效规律全被千量级的 unacc 带跑了。这其实也是我特别想强调的一点f1_macro比accuracy更能暴露模型好坏。你在跑任何一个表格分类项目时都应该把这两个指标同时打印出来。如果准确率和 macro-F1 差距过大说明少数类大概率是废掉的必须处理不平衡问题。4.3 调整类别权重后的实际效果既然问题出在类别不平衡最简单的干预方式是把class_weight调成balanced。sklearn 的这个参数会自动根据每个类别的样本量反比设置损失函数里的惩罚权重样本量少的类别犯错被放得更大。model_balanced LogisticRegression( multi_classmultinomial, solverlbfgs, max_iter1000, class_weightbalanced, random_state42 )调整后的表现是准确率降到 80% 左右但 macro-F1 提升到 0.72 左右。再细看混淆矩阵good 的召回率从 0.03 涨到 0.42vgood 也从 0.06 涨到 0.28。这个结果就非常有意思我们牺牲了 6 个百分点的整体准确率换来了少数类识别能力的大幅上升。在真实业务里这个取舍是否值得完全取决于场景。如果任务是一万辆车里找出那几百台“很好”的车作为候选推荐那么提升 good/vgood 的召回率远比让普通车判定得更准更有价值。反过来如果只是做大规模初筛保持较高的 unacc 识别准确率也说得通。所以在实际项目中不要机械追求准确率而是先回答一个问题哪一类的判断失误成本最高5. 简单模型也有自己的脾气实测里那些说明书不会写的坑5.1 max_iter 收敛警告与独热编码的稀疏性第一次训练时控制台弹出了ConvergenceWarning: Maximum number of iterations reached我当时第一反应是调大max_iter。这个操作本身没错但如果不理解背后的原因将来换数据集还会卡住。lbfgs求解器在默认 100 次迭代下收敛不了通常是因为参数搜索空间较大或者某个特征范围跨度偏大。在这个项目里独热编码生成的稀疏矩阵并不会带来量纲灾难真正拖慢收敛的是多分类 softmax 下参数矩阵比二分类大好几倍。把max_iter从默认的 100 加到 1000问题立刻消失。有一点值得注意有人看到收敛警告后会顺手对稀疏矩阵做StandardScaler标准化这反而是个坏习惯。标准化会把 0/1 稀疏矩阵变成稠密矩阵内存翻好几倍而且逻辑回归对 0/1 编码并不需要缩放白忙活一场。如果将来你引入连续型特征应该只用ColumnTransformer对连续列单独做缩放不要动独热编码那一块。5.2 交叉验证里最容易忽略的数据泄漏目标编码的诱惑我实际跑的时候还尝试过一种“聪明”做法按safety分组统计目标变量均值生成一个新特征比如“safety 为 high 时 label 为 vgood 的比例”然后把这个统计特征丢进逻辑回归。理论上这可以给模型补充很多信息效果也确实立竿见影——准确率直接跳到 95% 以上。但这个结果是个陷阱。问题出在统计特征的生成方式如果我在整个数据集上用groupby计算出均值再去做交叉验证那么训练集计算出的均值已经包含了验证集的目标信息模型在验证集上的高分开属于作弊。这个行为就叫数据泄漏。正确做法是把统计特征的生成过程放进每一折交叉验证的内部每一折里只用训练集计算目标均值然后用这个均值给验证集映射。为了省事也可以换成专门做类别目标编码的库组件它会自动处理好交叉验证内部编码。这个小坑我印象很深因为它在实际项目里非常隐蔽出现“训练时 99%上线后崩盘”的诡异现象八成就是这一类问题。5.3 如果还想继续往上提先别急着换模型很多人跑到 macro-F1 0.72 就想换随机森林或者 XGBoost。我建议先做两件事再决定。第一检查有没有业务规则可以直接转化为特征。比如 Car Evaluation 里有大量样本的特征组合本身就是规则的一个分支把这些组合显式编码为新的二值特征很多模型都能获得提升。第二用两阶段建模思路第一阶段先区分 unacc 和非 unacc第二阶段在非 unacc 的子集上细分 acc / good / vgood。这样每个阶段的类别比例更均衡逻辑回归的优势能发挥得更充分。我当时手动实现了两阶段方案第二阶段只对 500 多条样本建模由于类别数从 4 降到 3且排除掉第一阶段的困难样本干扰整体 macro-F1 又涨了几个点。这算是一次“不增加模型复杂度但改变任务结构”的收益。6. 我对这个小项目最后的一点体会跑完整个car-evaluation/逻辑回归项目我最大的收获不是学会了调用逻辑回归而是搞清楚了评估指标、类别失衡和特征编码这三个因素如何联合影响一个模型的可信度。以后不管拿到什么表格分类任务我都会先打印准确率和 macro-F1 的差距再看一眼混淆矩阵里损失最大的那个类最后再回头审视自己的特征工程有没有把某些信息泄漏到验证集里。这种“先怀疑结果、再审视过程”的习惯比任何一套固定代码模板都重要。逻辑回归在这个数据集上既给了我们一个优秀的基线也逼着我们面对所有真实分类任务都会遇到的不平衡问题。如果你想找一个既能快速上手又值得反复琢磨的小项目把这份数据从头到尾亲手跑一遍你损失不了多少时间却能省下未来在更大项目里填坑的不少力气。
返回列表