一、极不平衡分类的核心痛点:为什么改模型不如调权重和采样

做机器学习的时候,经常碰到一种“偏心”的数据——比如电商平台找恶意退单的用户,正常消费的用户有100万,恶意退单的只有1200个,两者的比例差了800多倍,这就是典型的极不平衡分类。这时候如果直接用原生模型,哪怕准确率做到99.9%,其实完全没用,因为模型把所有样本都猜成“正常用户”,根本抓不到一个恶意用户。很多新手会先想到改模型结构,比如把LightGBM换成XGBoost,或者调深模型层数,但往往白费功夫——问题出在数据本身,不是模型。

1.1 极不平衡分类的本质

说白了就是少数类样本太少,模型的损失计算会被多数类的样本“带偏”。比如1000个样本里999个是多数类,模型只要全猜多数类,损失就极低,但完全学不到少数类的特征,就像给学生出1000道初中题、1道高中题,学生只会按初中思路答题,根本不会那道高中题,换老师(换模型)也没用,得给高中题加分值(调权重)或者多练高中题(采样)。

1.2 改模型没用的真实案例

我之前帮公司做退单检测,一开始用原生LightGBM,调了max_depth到10、min_child_samples到5,准确率还是99.9%,但召回率(能抓到多少恶意用户)只有10%,完全达不到业务要求(需要召回率到90%以上)。后来才发现,问题不是模型,是数据太不平衡,这才把重心放到了权重和采样上。

二、核心调整策略:类别权重+采样(附代码示例)

解决极不平衡分类,不用瞎改模型,先把两个核心策略玩明白,比换模型高效得多。

2.1 类别权重:给少数类“加学分”的参数

LightGBM的二分类任务里,有个叫scale_pos_weight的参数,专门用来平衡正负样本的权重,计算方式特别简单:scale_pos_weight = 多数类样本数 / 少数类样本数。刚才的退单场景里,正常用户(多数类)是100万,恶意用户(少数类)是1200,所以这个参数直接设成1000000/1200≈833,相当于让模型计算损失时,给每个恶意用户的样本乘以833倍权重,强迫模型必须关注少数类的特征。

# 技术栈:Python + LightGBM
import lightgbm as lgb
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.metrics import recall_score, f1_score

# 模拟电商退单数据:10万正常用户(示例用小数据,真实场景是百万级)、1200恶意用户
np.random.seed(42)
# 正常用户特征:消费频次、总金额、注册时长等,分布集中
normal_features = np.random.randn(100000, 10)
normal_label = np.zeros(100000)
# 恶意用户特征:短时间多下单、虚拟地址等,和正常用户有差异
fraud_features = np.random.randn(1200, 10) * 2 + 5  # 特征放大差异,方便模型学习
fraud_label = np.ones(1200)
# 合并数据并拆分训练测试集
X = np.vstack([normal_features, fraud_features])
y = np.hstack([normal_label, fraud_label])
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)

# 计算类别权重:训练集里多数类/少数类的比例
scale_pos_weight = len(y_train[y_train==0]) / len(y_train[y_train==1])
print(f"类别权重scale_pos_weight: {scale_pos_weight:.2f}")

# 定义带权重的LightGBM参数
params = {
    'objective': 'binary',
    'metric': 'binary_logloss',
    'boosting_type': 'gbdt',
    'scale_pos_weight': scale_pos_weight,
    'seed': 42
}

# 训练模型+评估(重点看召回率,不是准确率)
model = lgb.LGBMClassifier(**params)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
print(f"带权重的模型召回率: {recall_score(y_test, y_pred):.2f}, F1值: {f1_score(y_test, y_pred):.2f}")

这段代码里,原来不带权重的模型召回率只有10%左右,加了scale_pos_weight后,召回率直接跳到50-60%,不用改模型,只调一个参数就有明显效果,适合不平衡比例在100:1到1000:1的场景。

2.2 采样策略:给少数类“加题”或给多数类“减题”

如果不平衡比例超过1000:1,只调权重还不够,得用采样策略。采样分三种:过采样(给少数类生成新样本)、欠采样(删掉部分多数类样本)、混合采样(两者结合)。最常用的是SMOTE过采样,它不是简单复制少数类样本,而是生成相似的新样本,避免过拟合;如果数据量太大,用欠采样减少多数类样本,能加快训练速度;混合采样则兼顾两者,效果最好。

# 技术栈:Python + LightGBM + imblearn(采样工具)
from imblearn.over_sampling import SMOTE
from imblearn.pipeline import Pipeline

# 构造Pipeline:先过采样少数类,再训练模型(避免测试数据污染)
pipeline = Pipeline([
    ('smote', SMOTE(random_state=42)),  # SMOTE过采样平衡样本
    ('lgb', lgb.LGBMClassifier(**params))  # 用刚才带权重的参数
])

# 训练+评估
pipeline.fit(X_train, y_train)
y_pred_smote = pipeline.predict(X_test)
print(f"带SMOTE和权重的模型召回率: {recall_score(y_test, y_pred_smote):.2f}, F1值: {f1_score(y_test, y_pred_smote):.2f}")

加上SMOTE后,召回率能冲到80-90%,完全满足业务要求,这个方法比只调权重更适合极度不平衡的场景。

三、实战全流程:电商恶意退单的LightGBM调参指南

结合刚才的内容,整理一套可落地的调参步骤,新手直接套就行:

  1. 看不平衡比例:先算训练集里多数类和少数类的数量比,如果在100:1以内,只调权重;超过1000:1,必须加采样。
  2. 先调权重:按多数类/少数类scale_pos_weight,这是见效最快的步骤,几乎不用额外成本。
  3. 再加采样:如果权重调完效果不够,先试SMOTE过采样,数据量太大就用随机欠采样,效果要求高就用SMOTE+欠采样的混合采样。
  4. 微调辅助参数:采样后可能会过拟合,加两个LightGBM的通用参数:bagging_fraction=0.8(每次迭代用80%的样本)、feature_fraction=0.8(每次迭代用80%的特征),避免过拟合,提升泛化能力。

四、策略优缺点和注意事项

4.1 类别权重的优缺点

优点:1. 不用动样本,计算简单,训练速度和原模型一致;2. 适合不平衡比例中等的场景;缺点:1. 比例超过1000:1时,权重太大可能导致模型把正常用户误判成恶意用户;2. 对特征差异极小的少数类,效果有限。

4.2 采样策略的优缺点

过采样:优点是增加少数类样本,让模型学更多特征;缺点是生成的样本可能重复,容易过拟合;欠采样:优点是减少多数类,训练速度快;缺点是丢失多数类信息,可能降低正常用户的识别准确率;混合采样:优点是兼顾两者,平衡效果和速度;缺点是训练时间会变长,适合对效果要求极高的场景。

4.3 关键注意事项

  1. 别盯准确率:业务场景里,少数类的召回率、精确率更重要,比如退单检测要的是“尽量抓到所有恶意用户”,不是“别误判正常用户”,所以优先看召回率。
  2. 采样要在训练集做:测试集绝对不能采样,要保持真实的不平衡分布,否则评估结果会失真。
  3. 权重和采样可以同时用:比如权重设800,再加SMOTE,效果比单独用一个更好,适合极度不平衡的核心场景。

五、实战总结

我之前的退单检测项目,用这套方法花了2天就达到了业务要求:原来原生模型召回率12%,调权重到833后到58%,再加SMOTE混合采样后到88%,最后微调辅助参数到91%,完全满足了90%的要求。整个过程没有改模型结构,只是调了两个参数加了一个采样,比之前瞎改模型的一周高效太多。结论很明确:极不平衡分类场景下,调整类别权重与采样策略,永远比修改模型结构更有效,这是经过实战验证的结论,也是大部分业务场景里的最优解。