文章

多标签分类实战-二分类和XGBoost

多标签分类实战-二分类和XGBoost

多标签分类实战-二分类和XGBoost

深入理解多标签分类问题,掌握 Binary Relevance 方案和 XGBoost 模型的训练与使用。从第一性原理出发,理解为什么需要多标签分类,以及如何训练和评估多标签分类模型。

目录

  1. 多标签分类问题
  2. 多标签分类方案对比
  3. XGBoost 算法深入理解
  4. XGBoost 参数配置
  5. 多标签分类训练流程
  6. 类别不平衡处理
  7. 模型预测与评估
  8. 模型保存与加载
  9. 最佳实践与注意事项

1. 多标签分类问题

1.1 什么是多标签分类?

多分类 vs 多标签分类的本质区别

多分类(Multi-class)

  • 定义:类别互斥,一个样本只能属于一个类别
  • 例子:图像分类 ``` 一张图片:
    • 可能是”猫”(概率90%)
    • 可能是”狗”(概率8%)
    • 可能是”鸟”(概率2%) → 最终分类:猫(最高概率) → 一张图只能是一种动物 ```

多标签分类(Multi-label)

  • 定义:类别非互斥,一个样本可以属于多个类别
  • 例子:文本标签 ``` 一篇文章:
    • 是”科技”(概率85%)
    • 是”AI”(概率75%)
    • 是”深度学习”(概率60%) → 最终标签:[‘科技’, ‘AI’, ‘深度学习’](多个标签) → 一篇文章可以同时有多个标签 ```

实际业务场景举例

多标签分类场景

1
2
3
4
5
6
7
对象A:
- 类别1:是(概率85%)
- 类别2:是(概率60%)
- 类别3:否(概率10%)

最终结果:对象A同时属于"类别1"和"类别2"
→ 一个对象可以同时属于多个类别

为什么需要多标签?

  • 类别之间不是互斥的
  • 需要为每个类别输出独立的概率
  • 业务需要知道”这个对象属于哪些类别”

1.2 多标签分类的挑战

挑战1:标签相关性

问题:不同标签之间可能存在相关性。

例子

1
2
3
4
5
6
标签A:类别1
标签B:类别2

相关性:
- 如果一个对象是"类别1",更可能是"类别2"?
- 还是两者独立?

影响

  • 如果忽略相关性,可能丢失信息
  • 如果考虑相关性,模型复杂度增加

Binary Relevance 方案的处理

  • 假设标签之间独立
  • 每个标签独立训练模型
  • 简单高效,但可能忽略相关性

挑战2:类别不平衡

问题:每个标签的正负样本比例可能不同。

例子

1
2
3
4
5
6
7
8
9
标签"类别1":
- 正样本(是):100个
- 负样本(否):900个
- 比例:9:1(严重不平衡)

标签"类别2":
- 正样本(是):50个
- 负样本(否):950个
- 比例:19:1(更不平衡)

影响

  • 模型可能偏向多数类(负样本)
  • 准确率会误导(99%准确率但漏掉所有正样本)
  • 需要特殊处理

挑战3:特征重要性分配

问题:不同标签可能依赖不同的特征。

例子

1
2
3
4
5
6
7
标签"类别1":
- 重要特征:特征A、特征B
- 特征重要性:特征A(0.3), 特征B(0.25), ...

标签"类别2":
- 重要特征:出入境频率、地点
- 特征重要性:出入境频率(0.35), 地点(0.28), ...

影响

  • 每个标签需要独立的特征重要性
  • 不能用一个模型的特征重要性代表所有标签

2. 多标签分类方案对比

2.1 Binary Relevance (BR) 方案

核心思想

定义:为每个标签训练独立的二分类模型。

流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
原始问题:多标签分类
    输入:对象特征
    输出:['类别1', '类别2'](多标签)
    ↓
分解为多个独立的二分类问题:
    问题1:是否属于"类别1"?(二分类)
    问题2:是否属于"类别2"?(二分类)
    ↓
训练:
    模型1:学习"类别1" vs "非类别1"
    模型2:学习"类别2" vs "非类别2"
    ↓
预测:
    模型1 → 概率1(是否类别1)
    模型2 → 概率2(是否类别2)
    ↓
组合结果:
    多标签 = [概率1 > 阈值, 概率2 > 阈值, ...]

优点

  1. 简单高效
    • 每个模型独立训练,互不干扰
    • 训练速度快,易于实现
  2. 支持多标签
    • 一个对象可以同时属于多个类别
    • 每个模型独立输出概率
  3. 易于扩展
    • 新增类别只需训练一个新模型
    • 不影响已有模型
  4. 特征重要性清晰
    • 每个模型学习该类别的特征模式
    • 特征重要性针对性强

缺点

  1. 忽略标签相关性
    • 假设标签之间独立
    • 可能丢失标签之间的关联信息
  2. 可能产生矛盾预测
    • 如果标签有互斥关系,可能同时预测为正
    • 需要后处理规则

2.2 其他方案简介

Classifier Chains (CC)

核心思想:链式分类器,考虑标签顺序。

流程

1
2
3
4
5
6
7
模型1:预测标签1
    ↓
模型2:预测标签2(使用标签1的预测结果作为特征)
    ↓
模型3:预测标签3(使用标签1和标签2的预测结果作为特征)
    ↓
...

问题

  • 标签顺序敏感(顺序不同,结果不同)
  • 训练复杂(需要按顺序训练)
  • 错误传播(前面的错误会影响后面)

Label Powerset (LP)

核心思想:将多标签转换为多分类。

例子

1
2
3
4
5
6
7
8
9
10
11
原始标签组合:
- ['类别1']
- ['类别2']
- ['类别1', '类别2']
- []

转换为多分类:
- 类别A:'类别1'
- 类别B:'类别2'
- 类别C:'类别1+类别2'
- 类别D:'无标签'

问题

  • 类别爆炸(2^n个类别,n是标签数)
  • 数据稀疏(很多类别组合没有样本)
  • 难以扩展(新增标签需要重新训练)

神经网络多标签学习

核心思想:端到端学习,自动学习标签相关性。

问题

  • 需要大量数据
  • 可解释性差
  • 训练复杂

2.3 为什么选择 BR 方案?

业务场景匹配度

类别相对独立

1
2
3
4
类别1和类别2:
- 可能有相关性,但不强
- 可以独立判断
- BR方案的独立性假设基本成立

需要清晰的解释

1
2
3
4
每个类别需要独立的解释:
- 为什么是"类别1"?
- 为什么是"类别2"?
- BR方案提供独立的特征重要性

实施复杂度

BR方案

  • 简单:每个模型独立训练
  • 快速:可以并行训练
  • 易于调试:问题容易定位

其他方案

  • CC:顺序训练,复杂
  • LP:类别爆炸,不可行
  • 神经网络:需要大量数据,复杂

可解释性要求

BR方案

  • 每个模型的特征重要性清晰
  • 可以解释每个标签的预测原因

其他方案

  • CC:难以解释(依赖前面的预测)
  • LP:难以解释(类别组合)
  • 神经网络:黑盒,难以解释

结论:BR方案最适合当前场景。


3. XGBoost 算法深入理解

3.1 决策树基础回顾

什么是决策树?

定义:通过一系列规则(IF-THEN)进行决策的树形结构。

可视化理解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
决策树示例:
                   根节点(所有样本)
                         |
            ┌─────────────┴─────────────┐
            | 年龄 < 30?                |
            ↓                           ↓
        是(左)                    否(右)
            |                           |
    ┌───────┴───────┐         ┌─────────┴─────────┐
    | 收入 > 50000? |         | 历史记录 > 5?     |
    ↓               ↓         ↓                   ↓
  是              否        是                  否
  ↓               ↓         ↓                   ↓
类别1          类别2      类别1              类别2

决策过程

1
2
3
4
5
6
7
8
样本:年龄=25, 收入=60000, 历史记录=3

决策路径:
1. 年龄 < 30? → 是
2. 收入 > 50000? → 是
3. 预测 = 类别1

最终预测:类别1

决策树如何做决策?

核心机制:通过特征值比较,逐步缩小范围。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
特征空间(2维):
  收入
    ↑
 高 |    区域1(类别1)
    |  ┌─────┐
    |  │     │
    |  └─────┘
    |    区域2(低风险)
    └─────────→ 年龄

决策树的分裂:
- 第1次分裂:年龄 < 30(垂直分割)
- 第2次分裂:收入 > 50000(水平分割)
- 结果:将特征空间分成4个区域

关键理解

  • 每个节点 = 一个判断条件
  • 每个叶子 = 一个预测结果
  • 从根到叶的路径 = 一条决策规则

3.2 集成学习策略

单独决策树 vs 集成学习

单独决策树的问题

1
2
3
4
5
6
7
8
9
10
11
问题1:容易过拟合
    - 树太深 → 记住训练数据的每个细节
    - 测试集表现差

问题2:不稳定
    - 训练数据微小变化 → 树结构完全不同
    - 预测结果不稳定

问题3:精度有限
    - 单棵树的学习能力有限
    - 难以捕捉复杂模式

集成学习的优势

1
2
3
4
多个模型组合:
    - 降低过拟合风险(平均多个模型的预测)
    - 提高稳定性(减少方差)
    - 提高精度(组合多个模型的优势)

Bagging vs Boosting 的区别

Bagging(如随机森林)的本质

核心思想:并行训练多棵树,投票决策。

流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
训练阶段(并行):
    树1(数据子集1 + 特征子集1) → 训练 → 模型1
    树2(数据子集2 + 特征子集2) → 训练 → 模型2
    树3(数据子集3 + 特征子集3) → 训练 → 模型3
    ...
    树100(数据子集100 + 特征子集100) → 训练 → 模型100

预测阶段:
    样本 → 模型1预测 → 预测1
    样本 → 模型2预测 → 预测2
    ...
    样本 → 模型100预测 → 预测100
    ↓
    最终预测 = 多数投票(预测1, 预测2, ..., 预测100)

本质

  • 降低方差(通过平均多个模型的预测)
  • 适合高方差模型(如深度决策树)

Boosting(如XGBoost)的本质

核心思想:串行训练多棵树,每棵树学习前一轮的残差。

流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
第1轮:
    树1 → 预测1
    残差1 = 真实值 - 预测1
    ↓
第2轮:
    树2 → 学习残差1 → 预测2
    残差2 = 残差1 - 预测2
    ↓
第3轮:
    树3 → 学习残差2 → 预测3
    残差3 = 残差2 - 预测3
    ↓
...

最终预测 = 预测1 + 预测2 + 预测3 + ...

本质

  • 降低偏差(通过逐步纠错)
  • 适合高偏差模型(如浅层决策树)

为什么选择 Boosting?

小数据集的特点

1
2
3
4
5
6
7
8
9
数据量:中等(几百到几千样本)
    ↓
通常面临的问题:高偏差(模型太简单)
    - 模型无法捕捉复杂模式
    - 训练集和测试集表现都差
    ↓
解决方案:Boosting
    - 通过逐步纠错降低偏差
    - 逐步改进模型性能

对比

1
2
3
4
5
6
7
8
9
10
11
Bagging(随机森林):
    - 主要降低方差
    - 适合高方差问题
    - 小数据集可能过拟合

Boosting(XGBoost):
    - 主要降低偏差
    - 适合高偏差问题
    - 小数据集通过正则化防止过拟合
    ↓
结论:Boosting更适合小数据集

3.3 XGBoost 的核心优势

XGBoost的特征处理要求

重要理解:XGBoost可以支持多种弱学习器,是否需要归一化取决于我们采用的弱学习器类型。

常见实现:选择决策树作为弱学习器(默认情况)。

当使用决策树作为弱学习器时

1. 特征无需归一化

原理

  • 树模型的分裂方式:通过遍历特征所有取值来选择划分点
  • 排序而非距离:树模型关注的是特征值的排序关系,而不是绝对数值大小
  • 归一化不影响排序:归一化不会改变特征值的相对大小关系

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
原始数据:
    特征1(年龄):[20, 25, 30, 35, 40]
    特征2(收入):[1000, 5000, 10000, 20000, 50000]

归一化后:
    特征1:[0, 0.25, 0.5, 0.75, 1.0]
    特征2:[0, 0.1, 0.2, 0.4, 1.0]

XGBoost分裂(决策树):
    原始数据:按"年龄 < 30"分裂
    归一化后:按"年龄 < 0.5"分裂
    
结果:分裂效果相同 ✅
    归一化不影响排序关系
    树模型的分裂结果不变

结论:使用决策树作为弱学习器时,特征无需归一化。

2. 需要ONE-HOT处理(类别特征)

为什么需要ONE-HOT?

XGBoost的特征处理机制

  • 特征排序:在模型训练之前,XGBoost会对所有特征按取值进行排序并加载到内存
  • 要求特征有序:XGBoost要求特征是有序的(数值型特征天然有序)

问题场景

1
2
3
4
5
6
7
8
9
10
11
12
13
离散特征(类别特征):
    地点:["北京", "上海", "深圳"]
    
如果直接k值编码(Label Encoding):
    北京 = 0
    上海 = 1
    深圳 = 2
    ↓
    问题:XGBoost会认为 0 < 1 < 2(有序关系)
    ↓
    但实际上:北京、上海、深圳之间没有大小关系(无序)
    ↓
    结果:模型会错误地认为"深圳 > 上海 > 北京" ❌

解决方案:ONE-HOT编码

ONE-HOT编码

1
2
3
4
5
6
7
8
9
10
11
12
原始特征:地点 = "北京"

ONE-HOT编码后:
    地点_北京 = 1
    地点_上海 = 0
    地点_深圳 = 0
    ↓
    每个类别变成一个独立的二值特征
    ↓
    二值特征(0或1)被当成有序特征处理
    ↓
    但二值特征的有序/无序处理结果相同 ✅

为什么ONE-HOT可以?

原理

  • 二值特征的特殊性:对于二值特征(0或1),无论当成有序还是无序处理,结果都相同
  • 有序处理:0 < 1(数值大小关系)
  • 无序处理:0 ≠ 1(类别不同)
  • 结果:两种处理方式在二值特征上等价

XGBoost特征处理总结

特征类型处理方式原因
数值特征无需归一化树模型基于排序分裂,不受尺度影响
类别特征ONE-HOT编码避免k值编码被误认为有序关系
缺失值可自动处理XGBoost可以自动学习如何处理缺失值

关键理解

  • 归一化:使用决策树弱学习器时不需要
  • ONE-HOT:类别特征必须使用,避免有序性误解
  • 缺失值:可以自动处理,但建议在特征工程阶段统一处理

优势1:二阶梯度优化

传统梯度提升

1
2
3
4
5
只使用一阶梯度(梯度方向)
    ↓
更新参数:参数 = 参数 - 学习率 × 梯度
    ↓
收敛速度:慢

XGBoost

1
2
3
4
5
使用二阶梯度(梯度 + 二阶导数)
    ↓
更新参数:参数 = 参数 - 学习率 × (梯度 / 二阶导数)
    ↓
收敛速度:快(更精确的步长)

本质:二阶梯度提供了更精确的步长信息,收敛更快。

优势2:正则化机制

L1/L2 正则化

1
2
3
4
5
6
7
L1正则化(Lasso):
    - 惩罚参数的绝对值
    - 作用:特征选择(将不重要特征的权重设为0)

L2正则化(Ridge):
    - 惩罚参数的平方
    - 作用:防止参数过大(平滑权重)

剪枝(Pruning)

1
2
3
4
5
树构建后:
    - 计算每个节点的增益
    - 如果增益 < 阈值 → 剪掉该节点
    ↓
作用:防止树过深,降低过拟合风险

采样(Sampling)

1
2
3
4
5
6
7
行采样(subsample):
    - 每棵树只用部分样本训练
    - 作用:增加随机性,降低过拟合

列采样(colsample_bytree):
    - 每棵树只用部分特征
    - 作用:增加随机性,降低过拟合

本质:多重正则化机制,防止过拟合。

优势3:特征交互自动学习

传统方法

1
2
3
4
5
6
需要手工创建交互特征:
    - 特征A × 特征B
    - 特征A + 特征B
    - ...
    ↓
工作量大,容易遗漏

XGBoost

1
2
3
4
5
6
树结构自动学习特征交互:
    树路径:[特征A < 10, 特征B > 5]
    ↓
    自动捕获:特征A和特征B的交互
    ↓
    无需手工创建交互特征

本质:树结构天然支持特征交互,自动学习。

优势4:类别不平衡处理

scale_pos_weight 参数

问题:正负样本不平衡。

例子

1
2
3
正样本:100个
负样本:900个
比例:9:1

解决方案

1
2
3
4
5
6
7
8
scale_pos_weight = 负样本数 / 正样本数
                = 900 / 100 = 9

含义:
    - 正样本的梯度权重 × 9
    - 负样本的梯度权重 × 1
    ↓
效果:正负样本的梯度贡献平衡

本质:通过调整梯度权重,平衡正负样本的贡献。


4. XGBoost 参数配置

4.1 核心参数

n_estimators(树的数量)

定义:Boosting中树的数量。

作用

1
2
3
4
5
6
7
8
9
树的数量越多:
    - 模型越复杂
    - 可能过拟合
    - 训练时间越长

树的数量越少:
    - 模型越简单
    - 可能欠拟合
    - 训练时间越短

如何选择?

1
2
3
4
5
方法:早停(Early Stopping)
    - 监控验证集性能
    - 如果性能不再提升 → 停止训练
    ↓
避免:树太多导致过拟合

典型值:50-200(根据数据量调整)

max_depth(树的最大深度)

定义:每棵树的最大深度。

作用

1
2
3
4
5
6
7
8
9
深度越深:
    - 模型越复杂
    - 可能过拟合
    - 训练时间越长

深度越浅:
    - 模型越简单
    - 可能欠拟合
    - 训练时间越短

如何选择?

1
2
3
4
5
6
7
8
小数据集(< 1000样本):
    - max_depth = 3-6(防止过拟合)

中等数据集(1000-10000样本):
    - max_depth = 6-10

大数据集(> 10000样本):
    - max_depth = 10-15

典型值:6-8(平衡复杂度和性能)

learning_rate(学习率)

定义:每棵树的贡献权重。

作用

1
2
3
4
5
6
7
学习率越大:
    - 每棵树的贡献越大
    - 收敛越快,但可能不稳定

学习率越小:
    - 每棵树的贡献越小
    - 收敛越慢,但更稳定

与n_estimators的关系

1
2
3
4
5
6
learning_rate × n_estimators ≈ 常数

例子:
    - learning_rate=0.1, n_estimators=100
    - learning_rate=0.05, n_estimators=200
    → 最终效果相似,但后者更稳定

典型值:0.01-0.3(常用0.1)

4.2 正则化参数

subsample(行采样)

定义:每棵树使用的样本比例。

作用

1
2
3
4
subsample = 0.8:
    - 每棵树只用80%的样本训练
    - 增加随机性
    - 降低过拟合风险

典型值:0.6-0.9(常用0.8)

colsample_bytree(列采样)

定义:每棵树使用的特征比例。

作用

1
2
3
4
colsample_bytree = 0.8:
    - 每棵树只用80%的特征
    - 增加随机性
    - 降低过拟合风险

典型值:0.6-0.9(常用0.8)

reg_lambda(L2 正则化)

定义:L2正则化系数。

作用

1
2
3
4
reg_lambda越大:
    - 惩罚参数平方越大
    - 参数值越小(平滑)
    - 降低过拟合风险

典型值:0.1-10(常用1.0)

reg_alpha(L1 正则化)

定义:L1正则化系数。

作用

1
2
3
4
reg_alpha越大:
    - 惩罚参数绝对值越大
    - 更多参数变为0(特征选择)
    - 降低过拟合风险

典型值:0-10(常用0,不做特征选择)

4.3 类别不平衡处理

scale_pos_weight 的作用

定义:正样本的权重倍数。

计算

1
2
3
4
5
6
scale_pos_weight = 负样本数 / 正样本数

例子:
    正样本:100个
    负样本:900个
    scale_pos_weight = 900 / 100 = 9

作用机制

1
2
3
4
5
6
7
训练时:
    正样本的梯度贡献 × scale_pos_weight
    负样本的梯度贡献 × 1
    ↓
    平衡正负样本的梯度贡献
    ↓
    模型更关注正样本(少数类)

效果

1
2
3
4
5
6
7
不使用scale_pos_weight:
    - 模型偏向负样本(多数类)
    - 召回率低(漏掉很多正样本)

使用scale_pos_weight:
    - 模型平衡关注正负样本
    - 召回率提高(找到更多正样本)

如何计算合适的权重?

方法1:基于样本比例

1
2
3
4
scale_pos_weight = 负样本数 / 正样本数

优点:简单直接
缺点:可能不够精确

方法2:基于业务需求

1
2
3
4
5
6
7
如果漏报成本高(如风险检测):
    - 增加scale_pos_weight
    - 例如:scale_pos_weight × 1.5

如果误报成本高(如垃圾邮件):
    - 减少scale_pos_weight
    - 例如:scale_pos_weight × 0.8

方法3:交叉验证调优

1
2
3
4
5
6
尝试不同的scale_pos_weight值:
    - 1.0, 2.0, 5.0, 10.0, ...
    ↓
    在验证集上评估性能
    ↓
    选择最优值

5. 多标签分类训练流程

5.1 完整流程图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
原始数据(分表)
    ↓
┌─────────────────────────────────┐
│  步骤1:数据加载与预处理        │
│  - 加载原始数据表                │
│  - 数据清洗与转换                │
└─────────────────────────────────┘
    ↓
┌─────────────────────────────────┐
│  步骤2:特征工程                │
│  - Featuretools自动特征生成      │
│  - 特征矩阵构建                  │
│  - 特征定义保存                  │
└─────────────────────────────────┘
    ↓
┌─────────────────────────────────┐
│  步骤3:标签提取与对齐          │
│  - 从汇总表提取标签              │
│  - 特征矩阵与标签对齐            │
└─────────────────────────────────┘
    ↓
┌─────────────────────────────────┐
│  步骤4:创建二分类标签           │
│  - 为每个类别创建独立标签        │
│  - 正负样本定义                  │
└─────────────────────────────────┘
    ↓
┌─────────────────────────────────┐
│  步骤5:数据分割                │
│  - 训练集 vs 测试集              │
│  - 分层采样(保持比例)          │
└─────────────────────────────────┘
    ↓
┌─────────────────────────────────┐
│  步骤6:类别平衡处理             │
│  - 下采样负样本(可选)           │
│  - 计算scale_pos_weight          │
└─────────────────────────────────┘
    ↓
┌─────────────────────────────────┐
│  步骤7:模型训练                 │
│  - 为每个类别训练XGBoost模型     │
│  - 提取特征重要性                │
│  - 归一化权重                    │
└─────────────────────────────────┘
    ↓
┌─────────────────────────────────┐
│  步骤8:模型评估                 │
│  - 训练集 vs 测试集性能          │
│  - 评估指标计算                  │
│  - 问题诊断                      │
└─────────────────────────────────┘
    ↓
┌─────────────────────────────────┐
│  步骤9:模型保存                 │
│  - 保存模型文件                  │
│  - 保存特征权重                  │
│  - 保存特征定义                  │
│  - 保存特征数据库                │
└─────────────────────────────────┘

5.2 数据准备

特征矩阵构建

输入:原始数据表(多表结构)

流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
对象信息表(objects)
    ↓
行为记录表(behavior_records)
    ↓
事件记录表(event_records)
    ↓
分类记录表(category_records)
    ↓
特征工程(Featuretools)
    ↓
特征矩阵(feature_matrix)
    - 每个人员一行
    - 每个特征一列
    - 例如:26个特征

例子

1
2
3
4
5
6
特征矩阵:
人员ID | COUNT(border) | MODE(地点) | MEAN(间隔) | ...
-------|--------------|-----------|-----------|----
001    | 5            | 北京      | 28        | ...
002    | 3            | 上海      | 35        | ...
003    | 8            | 深圳      | 22        | ...

标签提取与对齐

输入:汇总表(包含标签信息)

流程

1
2
3
4
5
6
7
8
9
10
11
汇总表(summary)
    ↓
提取"命中模型"列
    ↓
解析标签(支持多标签,用逗号分隔)
    ↓
标签字典:{人员ID: [标签列表]}
    ↓
转换为DataFrame
    ↓
与特征矩阵对齐(按人员ID合并)

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
汇总表:
对象ID | 标签
-------|----------
001    | 类别1,类别2
002    | 类别1
003    | (空)

标签提取后:
对象ID | labels
-------|-------
001    | ['类别1', '类别2']
002    | ['类别1']
003    | []

与特征矩阵对齐:
特征矩阵 + 标签 → 合并数据框
    - 特征列:26个特征
    - 标签列:labels(多标签格式)

5.3 二分类标签创建

核心思想

多标签 → 多个独立的二分类标签

流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
原始标签(多标签格式):
    对象001:['类别1', '类别2']
    对象002:['类别1']
    对象003:[]
    ↓
为每个类别创建独立的二分类标签:

类别"类别1":
    对象001:1(包含"类别1")
    对象002:1(包含"类别1")
    对象003:0(不包含"类别1")
    ↓
类别"类别2":
    对象001:1(包含"类别2")
    对象002:0(不包含"类别2")
    对象003:0(不包含"类别2")

正负样本的定义

关键理解:正负样本是针对每个类别独立定义的

例子

1
2
3
4
5
训练数据:
    对象001:['类别1', '类别2']
    对象002:['类别1']
    对象003:['类别2']
    对象004:[]

为”类别1”创建二分类标签

1
2
3
4
5
6
7
对象001:标签 = 1(正样本,因为包含"类别1")
对象002:标签 = 1(正样本,因为包含"类别1")
对象003:标签 = 0(负样本,因为不包含"类别1")
对象004:标签 = 0(负样本,因为不包含"类别1")

正样本:2个(对象001, 对象002)
负样本:2个(对象003, 对象004)

为”类别2”创建二分类标签

1
2
3
4
5
6
7
对象001:标签 = 1(正样本,因为包含"类别2")
对象002:标签 = 0(负样本,因为不包含"类别2")
对象003:标签 = 1(正样本,因为包含"类别2")
对象004:标签 = 0(负样本,因为不包含"类别2")

正样本:2个(对象001, 对象003)
负样本:2个(对象002, 对象004)

关键点

  • 每个风险类型的正负样本是独立定义的
  • 多标签人员(如人员001)在不同模型中可能都是正样本
  • 正常人员(如人员004)在所有模型中都是负样本

类别不平衡问题

问题:正样本通常远少于负样本。

例子

1
2
3
4
类别"类别1":
    正样本:100个(属于"类别1")
    负样本:900个(不属于"类别1")
    比例:9:1(严重不平衡)

影响

  • 模型可能偏向负样本(多数类)
  • 准确率高但召回率低(漏掉很多正样本)
  • 需要特殊处理

5.4 模型训练

训练数据分割

目的:评估模型的泛化能力。

流程

1
2
3
4
5
6
7
8
9
完整数据集(1000个样本)
    ↓
随机分割(stratify=标签,保持比例)
    ↓
训练集(800个样本,80%)
    - 用于训练模型
    ↓
测试集(200个样本,20%)
    - 用于评估模型(模型从未见过)

分层采样(stratify)

1
2
3
4
5
6
7
8
9
10
目的:保持正负样本比例一致

不使用stratify:
    训练集:正样本80个,负样本720个(比例9:1)
    测试集:正样本20个,负样本180个(比例9:1)
    → 比例一致 ✅

使用stratify:
    确保训练集和测试集的正负样本比例相同
    → 评估更准确

类别平衡采样策略

问题:正负样本不平衡。

解决方案1:下采样(Under-sampling)

流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
计算当前比例:
    负样本数 / 正样本数 = 900 / 100 = 9
    ↓
判断:比例 > 阈值(如3.0)?
    ├─→ 是 → 进行下采样
    └─→ 否 → 不采样
    ↓
下采样:
    目标比例 = 1.5(负样本/正样本)
    需要的负样本数 = 正样本数 × 1.5 = 100 × 1.5 = 150
    ↓
从900个负样本中随机选择150个
    ↓
合并:
    正样本:100个
    负样本:150个(采样后)
    比例:1.5:1 ✅

优点

  • 减少训练数据量,训练更快
  • 平衡正负样本比例

缺点

  • 丢失负样本信息(可能包含有用信息)

解决方案2:scale_pos_weight

流程

1
2
3
4
5
6
7
8
9
计算权重:
    scale_pos_weight = 负样本数 / 正样本数
                    = 900 / 100 = 9
    ↓
训练时:
    正样本的梯度贡献 × 9
    负样本的梯度贡献 × 1
    ↓
效果:正负样本的梯度贡献平衡

优点

  • 不丢失数据(使用全部样本)
  • 自动平衡梯度贡献

缺点

  • 可能不如采样效果好(取决于数据)

组合策略

1
2
3
4
5
6
7
8
9
10
11
步骤1:下采样
    负样本:900 → 150(下采样)
    ↓
步骤2:计算scale_pos_weight
    基于采样后的数据:
    scale_pos_weight = 150 / 100 = 1.5
    ↓
步骤3:训练模型
    使用采样后的数据 + scale_pos_weight
    ↓
效果:双重平衡(采样 + 权重)

缺失值处理

问题:特征矩阵中可能有缺失值。

处理流程

1
2
3
4
5
6
7
8
9
10
11
12
13
检查缺失值
    ↓
数值特征:
    缺失值 → 填充中位数(推荐)或均值
    - 中位数:对异常值更稳健
    - 均值:适合正态分布数据
    ↓
类别特征:
    缺失值 → 填充"未知"类别
    - 创建"未知"类别,而不是用众数
    - 保留缺失值的信息
    ↓
确保训练和预测时处理方式一致

为什么数值特征用中位数而不是均值?

问题:均值受异常值影响大。

例子

1
2
3
4
5
6
7
8
9
10
11
数据:[20, 25, 30, 35, 100](100是异常值)

均值 = (20 + 25 + 30 + 35 + 100) / 5 = 42
    ↓
    被异常值100拉高
    不能代表数据的真实中心 ⚠️

中位数 = 30
    ↓
    不受异常值影响
    更能代表数据的真实中心 ✅

为什么类别特征用”未知”而不是众数?

问题:缺失值可能包含信息。

例子

1
2
3
4
5
6
7
8
9
10
11
12
缺失值可能的原因:
    - 数据未收集(信息缺失)
    - 该特征不适用(业务逻辑)
    - 数据错误(需要标记)
    ↓
如果用众数填充:
    - 丢失了"缺失"这个信息
    - 模型无法区分"真实值=众数"和"缺失值=众数"
    ↓
如果用"未知"类别:
    - 保留了"缺失"这个信息
    - 模型可以学习"缺失值"的模式 ✅

XGBoost的缺失值处理

注意:XGBoost可以自动处理缺失值,但显式处理更可控。

两种方式对比

1
2
3
4
5
6
7
方式1:显式填充
    优点:可控、可解释
    缺点:需要选择填充策略
    
方式2:XGBoost自动处理
    优点:简单、自动学习
    缺点:不够透明、可能不一致

建议:在特征工程阶段统一处理缺失值,确保训练和预测时一致。

关键:训练和预测时的缺失值处理必须一致。

模型训练与评估

训练流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
为每个风险类型:
    步骤1:准备数据
        - 特征矩阵(X)
        - 二分类标签(y)
        ↓
    步骤2:数据分割
        - 训练集(X_train, y_train)
        - 验证集(X_val, y_val)
        - 测试集(X_test, y_test)
        ↓
    步骤3:类别平衡处理
        - 下采样(可选)
        - 计算scale_pos_weight
        ↓
    步骤4:缺失值处理
        - 数值特征:中位数填充(对异常值更稳健)
        - 类别特征:"未知"类别填充
        ↓
    步骤5:特征归一化(可选,树模型不强制要求)
        - 使用MinMaxScaler或StandardScaler
        - 保存归一化参数,用于预测时使用
        - 注意:树模型基于排序分裂,归一化不是必需的
        ↓
    步骤6:训练XGBoost模型
        - 设置参数(n_estimators, max_depth, ...)
        - 使用验证集进行早停(early_stopping_rounds)
        - 训练模型
        ↓
    步骤7:提取特征重要性
        - 模型自动计算
        - 归一化为权重
        ↓
    步骤8:评估模型
        - 训练集性能
        - 验证集性能(用于早停和调参)
        - 测试集性能(最终评估)
        - 对比分析

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
类别"类别1":
    训练集:
        - 样本数:800
        - 正样本:80,负样本:720
        - 下采样后:正样本80,负样本120
        - scale_pos_weight = 1.5
    ↓
    训练XGBoost模型:
        - n_estimators = 100
        - max_depth = 6
        - learning_rate = 0.1
        - scale_pos_weight = 1.5
    ↓
    评估结果:
        - 训练集准确率:95%
        - 测试集准确率:90%
        - 训练集F1:0.88
        - 测试集F1:0.82
    ↓
    分析:
        - 训练集和测试集性能接近 → 泛化能力好 ✅

6. 类别不平衡处理

6.1 问题描述

为什么类别不平衡会影响模型性能?

核心原因:模型优化目标与业务目标不一致。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
数据分布:
    正样本:100个(10%)
    负样本:900个(90%)

模型优化目标:最小化错误率
    ↓
策略:预测所有样本为负样本
    ↓
结果:
    - 准确率:90%(很高!)
    - 但召回率:0%(漏掉所有正样本)❌
    ↓
业务目标:找到所有正样本(高风险人员)
    - 漏报成本高
    - 准确率90%但召回率0% → 完全失败

本质

  • 模型被多数类(负样本)”绑架”
  • 优化目标(准确率)与业务目标(召回率)不一致
  • 需要特殊处理

6.2 解决方案

方案1:下采样(Under-sampling)

定义:减少多数类(负样本)的数量。

流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
原始数据:
    正样本:100个
    负样本:900个
    比例:9:1
    ↓
计算目标:
    目标比例:1.5:1
    需要的负样本数:100 × 1.5 = 150
    ↓
随机选择150个负样本
    ↓
采样后数据:
    正样本:100个
    负样本:150个
    比例:1.5:1 ✅

优点

  • 平衡正负样本比例
  • 训练更快(数据量减少)

缺点

  • 丢失负样本信息(750个负样本被丢弃)
  • 可能丢失有用信息

方案2:上采样(Over-sampling)

定义:增加少数类(正样本)的数量。

方法

1
2
3
4
5
6
7
8
9
10
方法1:简单复制
    正样本:100个 → 复制 → 900个
    ↓
    问题:过拟合(重复样本)

方法2:SMOTE(合成少数类过采样)
    生成新的合成正样本
    ↓
    优点:样本多样性
    缺点:可能生成噪声样本

在当前项目中

  • 通常不使用上采样
  • 原因:正样本数量已经足够,主要是负样本太多

方案3:scale_pos_weight 参数调整

定义:调整正样本的权重。

计算

1
2
scale_pos_weight = 负样本数 / 正样本数
                = 900 / 100 = 9

作用机制

1
2
3
4
5
6
7
训练时:
    正样本的梯度贡献 × 9
    负样本的梯度贡献 × 1
    ↓
    平衡正负样本的梯度贡献
    ↓
    模型更关注正样本

优点

  • 不丢失数据(使用全部样本)
  • 自动平衡梯度贡献
  • 简单高效

缺点

  • 可能不如采样效果好(取决于数据分布)

6.3 组合策略

采样 + 权重调整的结合使用

流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
步骤1:下采样
    负样本:900 → 150(下采样)
    比例:9:1 → 1.5:1
    ↓
步骤2:计算scale_pos_weight
    基于采样后的数据:
    scale_pos_weight = 150 / 100 = 1.5
    ↓
步骤3:训练模型
    使用采样后的数据 + scale_pos_weight
    ↓
效果:
    - 数据层面平衡(采样)
    - 梯度层面平衡(权重)
    - 双重保障

为什么组合使用?

  • 采样:快速平衡数据分布
  • 权重:进一步平衡梯度贡献
  • 组合:效果更好,更稳健

7. 模型预测与评估

7.1 为什么需要评估?评估的本质是什么?

评估的目的

目的1:验证模型的泛化能力

核心问题:在训练数据上表现好 ≠ 在新数据上表现好

例子

1
2
3
4
5
6
7
训练集:1000个样本
    模型准确率:99%
    ↓
测试集:200个新样本
    模型准确率:70%
    ↓
问题:过拟合(模型"死记硬背"训练数据)

目的2:模型选择

场景:有多个候选模型,选择哪个?

流程

1
2
3
4
5
模型A:测试集F1 = 0.75
模型B:测试集F1 = 0.82
模型C:测试集F1 = 0.78
    ↓
选择:模型B(性能最好)

目的3:模型优化

场景:模型表现不够好,如何改进?

流程

1
2
3
4
5
6
7
8
9
10
评估结果:
    训练集F1:0.95
    测试集F1:0.70
    ↓
诊断:过拟合
    ↓
改进措施:
    - 增加正则化
    - 减少模型复杂度
    - 增加数据量

评估的本质

定义:评估是对模型真实性能的估计。

核心挑战

1
2
3
4
5
6
7
真实性能:模型在无限数据上的表现(未知)
    ↓
我们需要:在有限数据上估计真实性能
    ↓
方法:划分数据集
    - 训练集:学习模式
    - 测试集:估计性能

关键理解

  • 测试集性能是对真实性能的估计
  • 估计的准确性取决于测试集的大小和质量
  • 测试集越大,估计越准确

7.2 数据集划分的原理

为什么需要划分数据集?

训练集的作用:让模型学习数据中的模式(拟合)

流程

1
2
3
4
5
6
7
训练集(800个样本)
    ↓
模型学习:
    - 哪些特征组合 → 高风险?
    - 哪些特征组合 → 低风险?
    ↓
模型"记住"了训练数据中的模式

验证集的作用:调整超参数,选择模型(防止过拟合)

流程

1
2
3
4
5
6
7
8
验证集(100个样本)
    ↓
尝试不同的超参数:
    - max_depth = 3 → 验证集F1 = 0.75
    - max_depth = 6 → 验证集F1 = 0.82
    - max_depth = 10 → 验证集F1 = 0.70
    ↓
选择:max_depth = 6(验证集性能最好)

超参数调优策略

为什么需要调优?

问题:默认参数可能不是最优的。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
默认参数:
    max_depth = 6
    learning_rate = 0.1
    reg_lambda = 1.0
    ↓
    验证集F1 = 0.80

调优后:
    max_depth = 4
    learning_rate = 0.05
    reg_lambda = 2.0
    ↓
    验证集F1 = 0.85(提升5%)✅

调优方法

方法1:手动调优(推荐小数据集)

流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
步骤1:设置基础参数
    n_estimators = 100
    learning_rate = 0.1
    
步骤2:调整树结构
    尝试 max_depth = [3, 4, 5, 6, 7]
    在验证集上评估
    选择最优值
    
步骤3:调整正则化
    尝试 reg_lambda = [0.5, 1.0, 2.0, 5.0]
    在验证集上评估
    选择最优值
    
步骤4:调整学习率
    如果过拟合 → 降低learning_rate
    如果欠拟合 → 提高learning_rate
    
步骤5:微调其他参数
    subsample, colsample_bytree等

方法2:网格搜索(Grid Search)

流程

1
2
3
4
5
6
7
8
9
10
定义参数网格:
    max_depth = [3, 4, 5, 6]
    learning_rate = [0.05, 0.1, 0.15]
    reg_lambda = [0.5, 1.0, 2.0]
    ↓
尝试所有组合(3×3×3 = 27种)
    ↓
在验证集上评估每种组合
    ↓
选择验证集性能最好的组合

优点:系统全面

缺点:计算成本高(需要训练27个模型)

方法3:随机搜索(Random Search)

流程

1
2
3
4
5
6
7
8
9
10
定义参数范围:
    max_depth = [3, 10](随机选择)
    learning_rate = [0.01, 0.3](随机选择)
    reg_lambda = [0.1, 5.0](随机选择)
    ↓
随机选择N个组合(如20个)
    ↓
在验证集上评估
    ↓
选择验证集性能最好的组合

优点:计算成本低,通常能找到好参数

缺点:可能遗漏最优组合

调优优先级

优先级1:核心参数

1
2
3
max_depth:树深度(影响模型复杂度)
learning_rate:学习率(影响收敛速度和精度)
reg_lambda:L2正则化(防止过拟合)

优先级2:正则化参数

1
2
reg_alpha:L1正则化(特征选择)
gamma:最小损失减少量(控制分裂)

优先级3:采样参数

1
2
subsample:行采样比例
colsample_bytree:列采样比例

调优建议

  • 小数据集(< 500样本):手动调优,重点调max_depth和reg_lambda
  • 中等数据集(500-5000样本):可以使用网格搜索或随机搜索
  • 大数据集(> 5000样本):可以使用贝叶斯优化(Optuna等)

交叉验证(Cross Validation)

为什么需要交叉验证?

问题:单次划分可能不稳定。

例子

1
2
3
4
5
6
7
8
9
10
单次划分:
    训练集:800个样本
    测试集:200个样本
    ↓
    测试集F1 = 0.82
    
问题:如果换一个划分方式呢?
    测试集F1 = 0.78(差异大)⚠️
    ↓
    单次划分的结果不稳定

交叉验证的原理

K折交叉验证(K-Fold CV)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
数据集(1000个样本)
    ↓
分成K折(如5折):
    折1:200个样本(测试集)
    折2-5:800个样本(训练集)
    ↓
    训练模型,评估折1 → F1_1
    
    折2:200个样本(测试集)
    折1,3-5:800个样本(训练集)
    ↓
    训练模型,评估折2 → F1_2
    
    ...(重复K次)
    ↓
最终结果:
    平均F1 = (F1_1 + F1_2 + ... + F1_K) / K
    标准差 = std([F1_1, F1_2, ..., F1_K])

例子(5折交叉验证)

1
2
3
4
5
6
7
8
9
10
折1:F1 = 0.82
折2:F1 = 0.78
折3:F1 = 0.85
折4:F1 = 0.80
折5:F1 = 0.83
    ↓
平均F1 = 0.816
标准差 = 0.025
    ↓
结论:模型性能约为0.816 ± 0.025

交叉验证的优势

  • 更稳定:多次评估,结果更可靠
  • 充分利用数据:每个样本都参与训练和测试
  • 评估方差:标准差反映模型稳定性

适用场景

  • 小数据集(< 500样本):使用3-5折交叉验证
  • 中等数据集(500-5000样本):使用5-10折交叉验证
  • 大数据集(> 5000样本):可以使用单次划分(数据量足够)

注意事项

  • 计算成本:需要训练K个模型,计算成本高
  • 时间序列数据:不能随机划分,需要使用时间序列交叉验证
  • 类别不平衡:需要使用分层交叉验证(Stratified K-Fold)

测试集的作用:最终评估模型性能(无偏估计)

流程

1
2
3
4
5
6
7
测试集(100个样本)
    - 模型从未见过这些数据
    ↓
最终评估:
    - 测试集F1 = 0.80
    ↓
结论:模型的真实性能约为0.80

划分的本质

本质1:模拟模型在实际应用中的表现

1
2
3
4
5
6
7
8
9
10
11
12
13
实际应用:
    新数据(模型从未见过)
    ↓
    模型预测
    ↓
    评估性能

测试集:
    模拟"新数据"
    ↓
    模型预测(从未在训练中见过)
    ↓
    评估性能(估计真实性能)

本质2:为什么不能只用训练集评估?

问题:过拟合的风险

例子

1
2
3
4
5
6
7
8
9
只用训练集评估:
    训练集准确率:99%
    ↓
    结论:模型很好 ✅
    ↓
    实际应用:
        新数据准确率:60% ❌
    ↓
    问题:过拟合(模型"死记硬背"训练数据)

解决:使用独立的测试集

1
2
3
4
训练集评估:99%(可能过拟合)
测试集评估:85%(更接近真实性能)
    ↓
    结论:模型性能约为85%,存在过拟合风险

如何划分数据集?

方法1:随机划分(三划分)

流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
完整数据集(1000个样本)
    ↓
随机打乱
    ↓
第一次划分:
    - 训练集:700个(70%)
    - 临时集:300个(30%)
    ↓
第二次划分(从临时集):
    - 验证集:150个(15%)
    - 测试集:150个(15%)
    ↓
最终划分:
    - 训练集:700个(70%)
    - 验证集:150个(15%)
    - 测试集:150个(15%)

为什么需要三划分?

问题:如果只有训练集和测试集,无法同时进行早停和最终评估。

两划分的问题

1
2
3
4
5
6
7
8
训练集(80%)+ 测试集(20%)
    ↓
问题1:早停需要验证集
    - 如果用测试集早停 → 测试集被"污染"(参与了训练过程)
    - 如果用训练集早停 → 无法检测过拟合
    
问题2:最终评估需要独立测试集
    - 如果测试集用于早停 → 不能用于最终评估

三划分的优势

1
2
3
4
5
6
7
8
9
10
训练集(70%)+ 验证集(15%)+ 测试集(15%)
    ↓
验证集:用于早停和超参数调优
    - 监控训练过程
    - 选择最优参数
    - 不参与最终评估
    
测试集:仅用于最终评估
    - 完全独立,从未参与训练
    - 无偏估计模型真实性能

适用场景

  • 数据是独立同分布的(i.i.d.)
  • 没有时间顺序依赖
  • 需要早停机制时(推荐三划分)

方法2:时间序列划分

流程

1
2
3
4
按时间顺序:
    - 训练集:2023年1-8月的数据
    - 验证集:2023年9月的数据
    - 测试集:2023年10月的数据

适用场景

  • 数据有时间顺序
  • 需要模拟未来预测

分层采样(stratify)

目的:保持类别分布一致

流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
原始数据:
    正样本:100个(10%)
    负样本:900个(90%)
    ↓
分层采样:
    训练集:
        正样本:80个(10%)
        负样本:720个(90%)
    ↓
    测试集:
        正样本:20个(10%)
        负样本:180个(90%)
    ↓
    比例一致 ✅

划分比例

70-20-10(训练-验证-测试):

  • 适用于:数据量充足(> 10000样本)
  • 优点:测试集足够大,估计准确

80-10-10(训练-验证-测试):

  • 适用于:数据量中等(1000-10000样本)
  • 优点:训练集更多,模型性能更好

90-5-5(训练-验证-测试):

  • 适用于:数据量少(< 1000样本)
  • 优点:最大化训练数据
  • 缺点:测试集小,估计可能不准确

7.3 评估指标的第一性原理

为什么需要多个评估指标?

核心原因:不同的指标关注不同的方面。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
场景:风险检测

指标1:准确率
    - 关注:整体预测正确率
    - 问题:类别不平衡时会被多数类主导

指标2:精确率
    - 关注:预测为正的样本中,真正为正的比例
    - 适用:误报成本高

指标3:召回率
    - 关注:真正的正样本中,被找到的比例
    - 适用:漏报成本高

指标4:F1分数
    - 关注:精确率和召回率的平衡
    - 适用:需要平衡两者

关键理解

  • 没有”完美”的指标
  • 只有”适合”的指标
  • 业务目标决定指标选择

准确率(Accuracy)的本质

定义:预测正确的样本数 / 总样本数

公式

1
2
3
4
5
6
7
准确率 = (TP + TN) / (TP + TN + FP + FN)

其中:
    TP(True Positive):真正例(预测为正,实际为正)
    TN(True Negative):真负例(预测为负,实际为负)
    FP(False Positive):假正例(预测为正,实际为负)
    FN(False Negative):假负例(预测为负,实际为正)

例子

1
2
3
4
5
6
7
8
9
10
预测结果:
    预测为正:150个
        其中:真正为正:100个(TP)
              假正:50个(FP)
    
    预测为负:850个
        其中:真负:800个(TN)
              假负:50个(FN)

准确率 = (100 + 800) / 1000 = 90%

适用场景:类别平衡的数据

例子

1
2
3
4
5
数据分布:
    正样本:500个(50%)
    负样本:500个(50%)
    ↓
    准确率有意义 ✅

局限性:类别不平衡时,准确率会误导

例子

1
2
3
4
5
6
7
8
9
10
11
数据分布:
    正样本:10个(1%)
    负样本:990个(99%)

策略:预测所有样本为负样本
    ↓
    准确率:99%(很高!)
    ↓
    但召回率:0%(漏掉所有正样本)❌
    ↓
    结论:准确率99%但模型完全失败

为什么会有这个问题?

  • 准确率忽略了类别分布
  • 多数类(负样本)主导了准确率
  • 少数类(正样本)的影响被忽略

精确率(Precision)的本质

定义:预测为正的样本中,真正为正的比例

公式

1
2
3
精确率 = TP / (TP + FP)

含义:预测为正的样本中,有多少是真的正样本?

本质含义:模型的”严谨程度”(宁缺毋滥)

例子

1
2
3
4
5
6
7
8
9
10
预测结果:
    预测为正:100个
        其中:真正为正:90个(TP)
              假正:10个(FP)

精确率 = 90 / 100 = 90%
    ↓
含义:预测为正的100个样本中,90个是真的正样本
    ↓
模型很"严谨":只有很确定才预测为正

适用场景:误报成本高

例子

1
2
3
4
5
6
垃圾邮件过滤:
    误报成本高(正常邮件被误判为垃圾邮件)
    ↓
    关注精确率:预测为垃圾邮件的,有多少真的是垃圾邮件?
    ↓
    精确率高 → 误报少 ✅

可视化理解

1
2
3
4
5
预测为正的样本:
    ┌─────────────────┐
    │ 真正为正(TP)   │ ← 精确率关注这部分
    │ 假正(FP)       │ ← 误报,成本高
    └─────────────────┘

召回率(Recall)的本质

定义:真正的正样本中,被正确预测为正的比例

公式

1
2
3
召回率 = TP / (TP + FN)

含义:真正的正样本中,有多少被找到了?

本质含义:模型的”覆盖率”(宁滥勿缺)

例子

1
2
3
4
5
6
7
8
9
实际正样本:100个
    其中:被找到:80个(TP)
          被漏掉:20个(FN)

召回率 = 80 / 100 = 80%
    ↓
含义:100个真正的正样本中,找到了80个
    ↓
模型"覆盖率":80%

适用场景:漏报成本高

例子

1
2
3
4
5
6
疾病诊断:
    漏报成本高(有病但没诊断出来)
    ↓
    关注召回率:真正的病人中,有多少被诊断出来了?
    ↓
    召回率高 → 漏报少 ✅

可视化理解

1
2
3
4
5
实际正样本:
    ┌─────────────────┐
    │ 被找到(TP)     │ ← 召回率关注这部分
    │ 被漏掉(FN)     │ ← 漏报,成本高
    └─────────────────┘

F1 分数的本质

定义:精确率和召回率的调和平均

公式

1
2
3
4
F1 = 2 × (精确率 × 召回率) / (精确率 + 召回率)

或:
F1 = 2 × TP / (2 × TP + FP + FN)

为什么用调和平均而不是算术平均?

算术平均

1
2
3
4
5
6
7
算术平均 = (精确率 + 召回率) / 2

例子:
    精确率 = 0.9,召回率 = 0.1
    算术平均 = (0.9 + 0.1) / 2 = 0.5
    ↓
    问题:即使召回率很低,算术平均仍然较高

调和平均

1
2
3
4
5
6
7
调和平均 = 2 × (精确率 × 召回率) / (精确率 + 召回率)

例子:
    精确率 = 0.9,召回率 = 0.1
    调和平均 = 2 × (0.9 × 0.1) / (0.9 + 0.1) = 0.18
    ↓
    优点:只有当两者都高时,F1才高

本质:平衡精确率和召回率的权衡

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
场景1:高精确率,低召回率
    精确率 = 0.95,召回率 = 0.30
    F1 = 2 × (0.95 × 0.30) / (0.95 + 0.30) = 0.46
    ↓
    含义:很严谨,但覆盖率低

场景2:低精确率,高召回率
    精确率 = 0.50,召回率 = 0.90
    F1 = 2 × (0.50 × 0.90) / (0.50 + 0.90) = 0.64
    ↓
    含义:覆盖率高,但不够严谨

场景3:高精确率,高召回率
    精确率 = 0.85,召回率 = 0.80
    F1 = 2 × (0.85 × 0.80) / (0.85 + 0.80) = 0.82
    ↓
    含义:既严谨又全面 ✅

适用场景:需要同时考虑精确率和召回率

F1的局限性

  • 假设精确率和召回率同等重要
  • 但实际可能不是(如漏报成本更高)

解决方案:F-beta分数

1
2
3
4
5
F-beta = (1 + β²) × (精确率 × 召回率) / (β² × 精确率 + 召回率)

β > 1:更关注召回率(漏报成本高)
β < 1:更关注精确率(误报成本高)
β = 1:F1(两者同等重要)

ROC-AUC 的本质

ROC曲线的本质:不同阈值下的真阳性率 vs 假阳性率

定义

1
2
3
4
5
6
真阳性率(TPR)= TP / (TP + FN) = 召回率
假阳性率(FPR)= FP / (FP + TN)

ROC曲线:
    X轴:假阳性率(FPR)
    Y轴:真阳性率(TPR)

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
不同阈值下的性能:

阈值 = 0.9(很严格):
    TPR = 0.3(只找到30%的正样本)
    FPR = 0.01(只有1%的负样本被误判)
    ↓
    点:(0.01, 0.3)

阈值 = 0.5(中等):
    TPR = 0.8(找到80%的正样本)
    FPR = 0.2(20%的负样本被误判)
    ↓
    点:(0.2, 0.8)

阈值 = 0.1(很宽松):
    TPR = 0.95(找到95%的正样本)
    FPR = 0.5(50%的负样本被误判)
    ↓
    点:(0.5, 0.95)

连接所有点 → ROC曲线

AUC的本质:模型区分正负样本的能力

定义:ROC曲线下的面积(Area Under Curve)

含义

1
2
3
AUC = 1.0:完美区分(所有正样本的分数 > 所有负样本的分数)
AUC = 0.5:随机猜测(无法区分)
AUC = 0.0:完全错误(所有预测都反了)

例子

1
2
3
4
5
6
7
模型A:AUC = 0.95
    - 区分能力强
    - 正样本和负样本的分数分布分离明显

模型B:AUC = 0.65
    - 区分能力弱
    - 正样本和负样本的分数分布重叠较多

适用场景

  • 关注模型的排序能力
  • 而非特定阈值下的性能
  • 适合类别不平衡场景

与F1的区别

1
2
3
4
5
6
7
8
9
F1:
    - 关注特定阈值下的性能
    - 需要设置阈值(如0.5)
    - 阈值不同,F1不同

AUC:
    - 关注整体排序能力
    - 不需要设置阈值
    - 评估所有可能的阈值

如何选择合适的指标?

从业务目标出发

场景1:误报成本高

1
2
3
4
5
6
7
例子:垃圾邮件过滤
    - 误报:正常邮件被误判为垃圾邮件(用户不满)
    - 漏报:垃圾邮件没被过滤(影响较小)
    ↓
    关注:精确率
    - 预测为垃圾邮件的,有多少真的是垃圾邮件?
    - 精确率高 → 误报少 ✅

场景2:漏报成本高

1
2
3
4
5
6
7
例子:疾病诊断、风险检测
    - 漏报:有病但没诊断出来(生命危险)
    - 误报:没病但被诊断出来(可以复查)
    ↓
    关注:召回率
    - 真正的病人中,有多少被诊断出来了?
    - 召回率高 → 漏报少 ✅

场景3:需要平衡

1
2
3
4
5
6
例子:一般分类任务
    - 误报和漏报成本相当
    ↓
    关注:F1分数
    - 平衡精确率和召回率
    - F1高 → 整体性能好 ✅

类别不平衡场景

不能用准确率

1
2
3
4
5
6
7
8
9
数据分布:
    正样本:10个(1%)
    负样本:990个(99%)

准确率:99%(被多数类主导)
    ↓
    但召回率:0%(完全失败)
    ↓
    结论:准确率误导 ❌

用F1或AUC

1
2
3
4
F1:关注少数类(正样本)的性能
AUC:关注排序能力(不受类别分布影响)
    ↓
    都能反映真实性能 ✅

多标签场景

每个标签单独计算指标

1
2
3
4
5
6
7
8
9
标签"类别1":
    精确率:0.85
    召回率:0.80
    F1:0.82

标签"类别2":
    精确率:0.75
    召回率:0.70
    F1:0.72

然后平均

1
2
3
4
5
6
7
宏平均(Macro Average):
    平均F1 = (0.82 + 0.72) / 2 = 0.77
    - 每个标签同等重要

微平均(Micro Average):
    将所有标签的TP、FP、FN合并后计算
    - 样本多的标签权重更大

7.4 模型预测

概率预测(predict_proba)的本质

定义:输出概率值,而非类别标签。

例子

1
2
3
4
5
6
7
8
输入:对象A的特征向量
    ↓
模型预测:
    类别1:0.85(85%概率)
    类别2:0.60(60%概率)
    类别3:0.10(10%概率)
    ↓
输出:概率值(0到1之间)

概率的含义:模型对预测的”信心程度”

例子

1
2
3
4
5
6
7
8
9
高置信度:
    类别1:0.95(95%概率)
    ↓
    含义:模型非常确定

低置信度:
    类别1:0.55(55%概率)
    ↓
    含义:模型不太确定(接近随机猜测)

为什么需要概率?

原因1:阈值的选择

流程

1
2
3
4
5
6
7
8
模型输出概率:
    类别1:0.72(72%概率)
    ↓
设置阈值:
    阈值 = 0.5 → 预测为"是"(0.72 > 0.5)
    阈值 = 0.8 → 预测为"否"(0.72 < 0.8)
    ↓
    阈值不同,预测结果不同

原因2:业务决策

流程

1
2
3
4
5
6
7
8
9
10
概率 = 0.95:
    → 高风险,立即处理

概率 = 0.60:
    → 中等风险,进一步调查

概率 = 0.30:
    → 低风险,暂时不处理
    ↓
    概率值帮助业务决策

多标签预测结果的融合

流程

1
2
3
4
5
6
7
8
9
10
11
12
每个标签独立的概率预测:
    模型1(类别1) → 概率1 = 0.85
    模型2(类别2) → 概率2 = 0.60
    模型3(类别3) → 概率3 = 0.10
    ↓
设置阈值(如0.5):
    概率1 = 0.85 > 0.5 → 预测为"是"
    概率2 = 0.60 > 0.5 → 预测为"是"
    概率3 = 0.10 < 0.5 → 预测为"否"
    ↓
最终结果:
    ['类别1', '类别2']

如何设置阈值?

方法1:固定阈值

1
2
3
4
所有标签使用相同阈值:
    阈值 = 0.5
    ↓
    简单,但可能不够灵活

方法2:标签特定阈值

1
2
3
4
5
6
不同标签使用不同阈值:
    类别1:阈值 = 0.7(更严格)
    类别2:阈值 = 0.5(中等)
    类别3:阈值 = 0.3(更宽松)
    ↓
    灵活,但需要调优

阈值选择的本质:平衡精确率和召回率

流程

1
2
3
4
5
6
7
8
9
阈值越高:
    - 精确率越高(更严谨)
    - 召回率越低(覆盖率低)
    ↓
阈值越低:
    - 精确率越低(不够严谨)
    - 召回率越高(覆盖率高)
    ↓
    需要在精确率和召回率之间权衡

7.5 评估结果的分析与问题诊断

模型表现差的可能原因(从原理出发思考)

原因1:数据问题

问题1.1:训练集和测试集分布不一致

表现

1
2
3
4
训练集性能:95%
测试集性能:60%
    ↓
    性能差距大,但可能不是过拟合

可能原因

  • 数据泄露:测试集包含训练时的信息
  • 选择偏差:训练集和测试集来自不同分布
  • 时间漂移:训练数据是旧的,测试数据是新的

问题1.2:数据质量差

表现

1
2
3
4
训练集性能:70%
测试集性能:65%
    ↓
    性能都不高,可能不是过拟合

可能原因

  • 噪声数据:数据中包含错误信息
  • 缺失值:大量缺失值影响模型学习
  • 错误标签:标签标注错误

问题1.3:数据量不足

表现

1
2
3
4
训练集性能:80%
测试集性能:75%
    ↓
    性能都不高,可能欠拟合

可能原因

  • 样本太少:无法学习有效模式
  • 特征太多:维度灾难

原因2:特征问题

问题2.1:特征与目标无关

表现

1
2
3
特征重要性都很低(< 0.01)
    ↓
    特征没有信息量

可能原因

  • 特征与目标变量不相关
  • 特征选择不当

问题2.2:特征冗余

表现

1
2
3
特征A和特征B高度相关(相关系数 > 0.9)
    ↓
    信息重复

可能原因

  • 特征工程产生冗余特征
  • 需要特征选择

问题2.3:特征缺失

表现

1
2
3
模型性能达到上限,无法进一步提升
    ↓
    可能缺少关键特征

可能原因

  • 特征工程不充分
  • 缺少重要业务特征

原因3:模型问题

问题3.1:模型太简单(欠拟合)

表现

1
2
3
4
训练集性能:60%
测试集性能:58%
    ↓
    训练集和测试集性能都差

可能原因

  • 模型复杂度不够(max_depth太小)
  • 正则化太强
  • 特征不足

问题3.2:模型太复杂(过拟合)

表现

1
2
3
4
训练集性能:99%
测试集性能:70%
    ↓
    训练集好,测试集差

可能原因

  • 模型复杂度太高(max_depth太大)
  • 正则化不够
  • 数据量不足

原因4:优化问题

问题4.1:损失函数不合适

表现

1
2
3
优化目标与业务目标不一致
    ↓
    模型优化了错误的目标

问题4.2:训练不充分

表现

1
2
3
训练集性能还在提升
    ↓
    可能需要更多迭代

如何诊断问题?

方法1:对比训练集和测试集表现

诊断流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
训练集好,测试集差:
    → 过拟合
    ↓
    改进:增加正则化、减少复杂度、增加数据量

训练集差,测试集差:
    → 欠拟合或数据问题
    ↓
    改进:增加复杂度、特征工程、检查数据质量

训练集差,测试集好:
    → 可能数据划分有问题
    ↓
    检查:数据划分是否正确

方法2:分析混淆矩阵

混淆矩阵

1
2
3
4
实际\预测 | 预测为正 | 预测为负
---------|---------|---------
实际为正 | TP      | FN
实际为负 | FP      | TN

分析

1
2
3
4
5
6
7
8
哪些类别容易混淆?
    - TP多,FP少 → 模型表现好
    - FP多 → 误报多(精确率低)
    - FN多 → 漏报多(召回率低)

是否有系统性错误?
    - 某些类别总是被误判
    - 可能特征不足或数据问题

方法3:分析错误样本

流程

1
2
3
4
5
6
7
8
9
收集错误预测的样本
    ↓
分析共同特征:
    - 错误样本有什么共同点?
    - 是噪声还是模式未学习到?
    ↓
判断:
    - 如果是噪声 → 数据清洗
    - 如果是模式 → 特征工程或模型改进

改进措施(从原理出发)

措施1:如果是过拟合

原理:模型太复杂,记住了训练数据的噪声

改进方法

方法1:增加正则化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
增加L2正则化(reg_lambda):
    reg_lambda: 1.0 → 5.0
    ↓
    惩罚参数过大

增加L1正则化(reg_alpha):
    reg_alpha: 0 → 1.0
    ↓
    特征选择,减少参数

增加采样(subsample, colsample_bytree):
    subsample: 0.8 → 0.6
    ↓
    增加随机性

方法2:增加数据量

1
2
3
4
5
6
7
数据增强:
    - 生成更多训练样本
    - 增加数据多样性

收集更多数据:
    - 扩大训练集规模
    - 提高数据质量

方法3:降低模型复杂度

1
2
3
4
5
6
7
8
9
减少树的数量(n_estimators):
    100 → 50
    ↓
    模型更简单

减少树深度(max_depth):
    8 → 4
    ↓
    防止树过深

方法4:早停(Early Stopping)

早停的原理

1
2
3
4
5
6
7
8
9
10
训练过程:
    第1轮:验证集F1 = 0.75
    第2轮:验证集F1 = 0.78
    第3轮:验证集F1 = 0.82
    第4轮:验证集F1 = 0.82
    第5轮:验证集F1 = 0.81(下降)
    ...
    第10轮:验证集F1 = 0.80(连续10轮不提升)
    ↓
    早停:停止训练,使用第3轮的模型 ✅

为什么需要早停?

问题:树太多会导致过拟合。

例子

1
2
3
4
5
6
7
8
9
不使用早停:
    训练100棵树
    训练集F1:0.95(很好)
    测试集F1:0.75(过拟合)❌

使用早停(10轮不提升就停止):
    训练30棵树(自动停止)
    训练集F1:0.88(稍低)
    测试集F1:0.85(泛化更好)✅

使用方法

1
2
3
4
5
6
model.fit(
    X_train, y_train,
    eval_set=[(X_val, y_val)],  # 验证集用于监控
    early_stopping_rounds=10,   # 10轮不提升就停止
    verbose=False
)

关键理解

  • 验证集独立:验证集不能参与训练,只用于监控性能
  • 早停轮数:通常设置为10-20轮(根据数据量调整)
  • 自动优化:自动找到最优树数量,防止过拟合

措施2:如果是欠拟合

原理:模型太简单,无法捕捉数据中的模式

改进方法

方法1:增加模型复杂度

1
2
3
4
5
6
7
8
9
增加树的数量(n_estimators):
    50 → 200
    ↓
    模型更复杂

增加树深度(max_depth):
    3 → 8
    ↓
    允许更深的树

方法2:特征工程

1
2
3
4
5
6
7
8
增加有用特征:
    - 构造新特征
    - 特征组合
    - 特征变换

改进特征质量:
    - 去除噪声特征
    - 选择重要特征

方法3:减少正则化

1
2
3
4
5
6
7
8
9
减少L2正则化(reg_lambda):
    5.0 → 1.0
    ↓
    允许参数更大

减少采样(subsample):
    0.6 → 0.8
    ↓
    使用更多数据

措施3:如果是数据问题

改进方法

方法1:数据清洗

1
2
3
4
5
6
7
去除噪声:
    - 识别异常值
    - 删除错误数据

处理缺失值:
    - 填充缺失值
    - 或删除缺失值过多的样本

方法2:数据增强

1
2
3
4
生成更多样本:
    - SMOTE(合成少数类)
    - 数据变换
    - 数据组合

方法3:重新收集数据

1
2
3
4
确保数据质量:
    - 正确的标签
    - 完整的数据
    - 代表性样本

措施4:如果是特征问题

改进方法

方法1:特征选择

1
2
3
4
5
6
7
8
去除无关特征:
    - 基于重要性
    - 基于相关性
    - 基于Permutation Importance

去除冗余特征:
    - 相关性分析
    - 特征聚类

方法2:特征工程

1
2
3
4
5
6
7
8
构造新特征:
    - 特征组合
    - 特征变换
    - 特征交互

改进特征质量:
    - 更好的特征提取
    - 领域知识融入

措施5:如果是类别不平衡

改进方法

方法1:使用合适的评估指标

1
2
3
4
5
6
不用准确率:
    - 会被多数类主导

用F1或AUC:
    - 关注少数类性能
    - 不受类别分布影响

方法2:使用类别权重

1
2
3
4
计算scale_pos_weight:
    scale_pos_weight = 负样本数 / 正样本数
    ↓
    平衡正负样本的梯度贡献

方法3:采样策略

1
2
3
4
5
6
7
下采样:
    - 减少多数类样本
    - 平衡正负样本比例

上采样:
    - 增加少数类样本
    - SMOTE生成合成样本

方法4:使用合适的损失函数

1
2
3
4
5
Focal Loss:
    - 关注难分类样本
    - 降低易分类样本的权重
    ↓
    更适合类别不平衡

8. 模型保存与加载

8.1 模型文件组织

每个标签的独立模型文件

文件结构

1
2
3
4
5
6
7
model_dir/
├── multilabel_model_类别1.pkl   # 类别1模型
├── multilabel_model_类别2.pkl   # 类别2模型
├── feature_weights_类别1.yaml   # 类别1特征权重
├── feature_weights_类别2.yaml   # 类别2特征权重
├── feature_definitions.pkl         # 特征定义(所有模型共享)
└── feature_database.pkl            # 特征数据库(所有模型共享)

为什么每个标签独立文件?

原因1:易于扩展

1
2
3
4
5
6
新增风险类型:
    只需训练新模型
    ↓
    保存为新文件
    ↓
    不影响已有模型 ✅

原因2:易于更新

1
2
3
4
更新某个标签的模型:
    只需替换对应的模型文件
    ↓
    其他标签的模型不受影响 ✅

原因3:易于管理

1
2
3
4
删除某个标签:
    只需删除对应的模型文件
    ↓
    其他标签的模型不受影响 ✅

特征定义文件的重要性

为什么需要保存特征定义?

问题:训练时和预测时特征必须一致

场景

1
2
3
4
5
6
7
8
9
10
11
训练时:
    特征1:COUNT(border_records)
    特征2:MODE(border_records.地点)
    特征3:MEAN(border_records.间隔天数)
    ↓
    训练模型

预测时:
    如果特征顺序或名称不一致
    ↓
    模型无法正确预测 ❌

解决方案:保存特征定义文件

流程

1
2
3
4
5
6
7
8
9
10
11
12
13
训练时:
    生成特征定义(feature_definitions)
    ↓
    保存到文件(feature_definitions.pkl)
    ↓
    文件包含:特征名称、特征函数、特征结构

预测时:
    加载特征定义文件
    ↓
    使用相同的特征定义生成特征
    ↓
    确保特征与训练时完全一致 ✅

特征数据库的作用

特征数据库包含

1
2
3
4
5
feature_database.pkl:
    - 特征矩阵:所有训练样本的特征向量
    - 标签信息:每个样本的风险类型标签
    - 户籍地信息:从身份证号提取的行政区划
    - 轨迹信息:出入境轨迹数据

用途

用途1:相似度检索

1
2
3
4
5
6
7
查询人员
    ↓
    提取特征向量
    ↓
    与特征数据库中的特征向量计算相似度
    ↓
    找到相似人员

用途2:特征对齐

1
2
3
4
5
6
查询时:
    确保特征格式与数据库一致
    ↓
    可以直接使用数据库中的特征向量
    ↓
    避免重复提取

用途3:轨迹匹配

1
2
3
4
5
查询条件:同轨迹
    ↓
    从特征数据库查找轨迹信息
    ↓
    匹配相同轨迹的人员

8.2 模型加载机制

加载流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
查找模型文件
    - 模式:multilabel_model_*.pkl
    ↓
加载每个模型文件
    - 每个文件包含:模型 + 元数据
    ↓
从第一个模型获取共享元数据
    - feature_names(特征名称列表)
    - label_types(标签类型列表)
    - categorical_categories(类别编码)
    ↓
完成加载
    - 所有模型可用
    - 元数据完整

增量更新支持

场景:新增风险类型

流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
已有模型:
    - multilabel_model_类别1.pkl
    - multilabel_model_类别2.pkl
    ↓
新增类别:"类别3"
    ↓
训练新模型
    ↓
保存新文件:
    - multilabel_model_其他风险.pkl
    ↓
加载时:
    - 自动发现新文件
    - 加载新模型
    - 不影响已有模型 ✅

9. 最佳实践与注意事项

9.1 特征一致性保证

为什么需要特征一致性?

核心原因:训练时和预测时特征必须完全一致。

不一致的后果

1
2
3
4
5
6
7
8
训练时:
    特征顺序:[特征1, 特征2, 特征3]
    特征名称:['COUNT(...)', 'MODE(...)', 'MEAN(...)']

预测时:
    特征顺序:[特征2, 特征1, 特征3](顺序不同)
    ↓
    模型预测错误 ❌

如何保证一致性?

方法1:保存特征定义文件

1
2
3
4
5
6
7
8
训练时:
    保存特征定义(feature_definitions.pkl)
    ↓
预测时:
    加载特征定义
    使用相同的定义生成特征
    ↓
    确保一致性 ✅

方法2:特征对齐检查

1
2
3
4
5
6
7
8
预测前检查:
    - 特征名称是否一致?
    - 特征顺序是否一致?
    - 特征类型是否一致?
    ↓
    不一致 → 对齐
    ↓
    一致 → 预测

方法3:缺失特征处理

1
2
3
如果预测时缺少某个特征:
    - 填充默认值(0或均值)
    - 或报错(更安全)

9.2 缺失值处理的一致性

问题:训练和预测时处理方式不一致

场景

1
2
3
4
5
6
7
训练时:
    缺失值 → 填充0

预测时:
    缺失值 → 填充均值
    ↓
    处理方式不一致 → 预测错误 ❌

解决方案

统一处理规则

1
2
3
4
5
6
7
8
9
规则1:数值特征
    - 缺失值 → 填充0(训练和预测一致)

规则2:类别特征
    - 缺失值 → 填充"未知"(训练和预测一致)

规则3:记录处理规则
    - 在模型元数据中记录处理规则
    - 预测时使用相同的规则

9.3 类别编码的一致性

问题:Categorical特征编码不一致

场景

1
2
3
4
5
6
7
8
9
10
11
训练时:
    类别特征"地点":
        '北京' → 编码为 0
        '上海' → 编码为 1
        '深圳' → 编码为 2

预测时:
    如果编码顺序不同:
        '北京' → 编码为 1(错误!)
    ↓
    预测错误 ❌

解决方案

保存类别编码映射

1
2
3
4
5
6
7
8
9
10
11
12
训练时:
    记录类别到编码的映射:
        {'北京': 0, '上海': 1, '深圳': 2}
    ↓
    保存到模型元数据

预测时:
    加载类别编码映射
    ↓
    使用相同的映射编码
    ↓
    确保一致性 ✅

9.4 模型版本管理

为什么需要版本管理?

场景

1
2
3
4
5
6
7
版本1:使用特征A、B、C训练模型1
版本2:使用特征A、B、D训练模型2(特征C改为D)
    ↓
问题:
    - 模型1需要特征C
    - 模型2需要特征D
    - 如何管理不同版本?

解决方案

方法1:特征定义文件版本化

1
2
3
4
feature_definitions_v1.pkl  # 版本1的特征定义
feature_definitions_v2.pkl  # 版本2的特征定义
    ↓
    每个版本的特征定义独立保存

方法2:模型元数据中包含特征定义

1
2
3
4
5
6
每个模型文件包含:
    - 模型本身
    - 特征定义(该模型使用的特征)
    - 版本号
    ↓
    模型文件自包含,包含所有必要信息

方法3:特征数据库

1
2
3
4
5
将特征定义存储在数据库中
    ↓
    支持版本管理
    - 不同版本的特征定义
    - 版本查询和切换

总结

10. XGBoost训练最佳实践总结

10.1 数据预处理最佳实践

缺失值处理

数值特征

  • 推荐:使用中位数填充(对异常值更稳健)
  • 备选:使用均值填充(适合正态分布数据)
  • 注意:XGBoost可以自动处理缺失值,但显式处理更可控

类别特征

  • 推荐:使用”未知”类别填充(保留缺失值信息)
  • 备选:使用众数填充(简单但可能丢失信息)

特征归一化

树模型(XGBoost使用决策树弱学习器)

  • 不需要归一化:树模型基于排序分裂,不受尺度影响
  • 可选归一化:可能提升计算效率或特征公平性

其他模型

  • 梯度下降类模型:必须归一化
  • 距离计算类模型:必须归一化

10.2 数据划分最佳实践

三划分数据集

推荐划分

1
2
3
训练集:70%(用于训练模型)
验证集:15%(用于早停和超参数调优)
测试集:15%(仅用于最终评估)

为什么需要三划分?

  • 验证集:用于早停和超参数调优,不能用于最终评估
  • 测试集:完全独立,仅用于最终评估,无偏估计

分层采样

适用场景:类别不平衡的数据

作用:确保训练集、验证集、测试集的类别分布一致

10.3 训练过程最佳实践

早停机制(Early Stopping)

原理:监控验证集性能,如果不再提升就停止训练

使用方法

1
2
3
4
5
6
model.fit(
    X_train, y_train,
    eval_set=[(X_val, y_val)],
    early_stopping_rounds=10,
    verbose=False
)

优势

  • 防止过拟合
  • 自动找到最优树数量
  • 节省训练时间

类别不平衡处理

方法1:scale_pos_weight

1
scale_pos_weight = 负样本数 / 正样本数

方法2:下采样 + scale_pos_weight

1
先下采样平衡数据,再使用scale_pos_weight

10.4 超参数调优最佳实践

调优优先级

优先级1:核心参数

  • max_depth:树深度(影响模型复杂度)
  • learning_rate:学习率(影响收敛速度和精度)
  • reg_lambda:L2正则化(防止过拟合)

优先级2:正则化参数

  • reg_alpha:L1正则化(特征选择)
  • gamma:最小损失减少量(控制分裂)

优先级3:采样参数

  • subsample:行采样比例
  • colsample_bytree:列采样比例

调优方法

小数据集(< 500样本)

  • 手动调优,重点调核心参数

中等数据集(500-5000样本)

  • 网格搜索或随机搜索

大数据集(> 5000样本)

  • 贝叶斯优化(Optuna等)

10.5 评估最佳实践

交叉验证

适用场景

  • 小数据集(< 500样本):使用3-5折交叉验证
  • 中等数据集(500-5000样本):使用5-10折交叉验证
  • 大数据集(> 5000样本):可以使用单次划分

优势

  • 更稳定的性能评估
  • 充分利用数据
  • 评估模型稳定性(通过标准差)

10.6 完整训练流程(优化版)

优化后的训练流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
步骤1:数据准备
    - 特征矩阵(X)
    - 二分类标签(y)
    ↓
步骤2:数据划分(三划分)
    - 训练集(70%)
    - 验证集(15%)
    - 测试集(15%)
    ↓
步骤3:数据预处理
    - 缺失值处理(中位数/"未知"类别)
    - 特征归一化(可选,树模型不强制要求)
    ↓
步骤4:类别平衡处理
    - 下采样(可选)
    - 计算scale_pos_weight
    ↓
步骤5:模型训练(带早停)
    - 设置参数
    - 使用验证集早停
    - 训练模型
    ↓
步骤6:超参数调优(可选)
    - 在验证集上尝试不同参数
    - 选择最优参数
    ↓
步骤7:最终评估
    - 在测试集上评估(仅一次)
    - 分析性能
    ↓
步骤8:特征重要性分析
    - 提取特征重要性
    - 归一化为权重

关键原则

  • 验证集独立:验证集不能参与训练,只用于早停和调参
  • 测试集独立:测试集完全独立,仅用于最终评估
  • 一致性:训练和预测时使用相同的预处理方法

本文深入介绍了多标签分类和XGBoost模型训练的核心概念和实践方法。关键要点:

  1. 多标签分类的本质:类别非互斥,一个样本可以属于多个类别
  2. Binary Relevance方案:将多标签问题分解为多个独立的二分类问题
  3. XGBoost的优势:二阶梯度优化、正则化机制、特征交互自动学习
  4. 类别不平衡处理:下采样 + scale_pos_weight的组合策略
  5. 评估指标的选择:根据业务目标选择合适的指标(精确率、召回率、F1、AUC)
  6. 问题诊断:从数据、特征、模型、优化四个维度系统分析
  7. 特征一致性:训练和预测时特征必须完全一致
本文由作者按照 CC BY 4.0 进行授权