梯度提升原理
目录
1. 决策树基础
1.1 什么是决策树
决策树是一种基本的机器学习算法,它通过一系列的”是/否”判断来做出预测。想象一个”20个问题”游戏:
问题1:这个因子值是否大于 0.5?
├─ 是 → 问题2:动量因子是否为正?
│ ├─ 是 → 预测:上涨
│ └─ 否 → 预测:持平
└─ 否 → 问题3:波动率是否小于 0.3?
├─ 是 → 预测:下跌
└─ 否 → 预测:持平
决策树的组成:
| 组成部分 | 说明 |
|---|---|
| 根节点 | 树的起点,包含所有样本 |
| 内部节点 | 每个节点代表一个特征判断 |
| 分支 | 判断的结果(是/否) |
| 叶节点 | 最终的预测结果 |
1.2 树如何分裂:决策准则
决策树的核心问题是:如何在每个节点选择最佳分裂特征和分裂点?
信息增益(Information Gain)
信息增益基于熵的概念,衡量分裂前后信息不确定性的减少。
熵的公式:
其中 是第 类的比例。
条件熵: 分裂后各子节点的加权熵
信息增益:
选择信息增益最大的特征和分裂点。
import numpy as np
def entropy(y):
"""计算标签的熵"""
_, counts = np.unique(y, return_counts=True)
p = counts / len(y)
return -np.sum(p * np.log2(p + 1e-10))
def information_gain(y, left_indices, right_indices):
"""计算信息增益"""
parent_entropy = entropy(y)
n = len(y)
n_left, n_right = len(left_indices), len(right_indices)
child_entropy = (n_left / n) * entropy(y[left_indices]) + \
(n_right / n) * entropy(y[right_indices])
return parent_entropy - child_entropy
# 示例:模拟量化场景
# y = 1 表示上涨,0 表示下跌/持平
np.random.seed(42)
y = np.array([1, 1, 1, 0, 0, 0, 1, 0])
# 假设按因子值 > 0.5 分裂
left_idx = np.where(y > 0.5)[0] # 简化示例
right_idx = np.where(y <= 0.5)[0]
gain = information_gain(y, left_idx, right_idx)
print(f"信息增益: {gain:.4f}")基尼系数(Gini Impurity)
基尼系数是另一种不纯度度量,计算更简单。
基尼系数公式:
选择基尼系数下降最多的分裂。
def gini_impurity(y):
"""计算基尼不纯度"""
_, counts = np.unique(y, return_counts=True)
p = counts / len(y)
return 1 - np.sum(p ** 2)
def gini_gain(y, left_indices, right_indices):
"""计算基尼增益"""
parent_gini = gini_impurity(y)
n = len(y)
n_left, n_right = len(left_indices), len(right_indices)
child_gini = (n_left / n) * gini_impurity(y[left_indices]) + \
(n_right / n) * gini_impurity(y[right_indices])
return parent_gini - child_gini
gain_gini = gini_gain(y, left_idx, right_idx)
print(f"基尼增益: {gain_gini:.4f}")信息增益 vs 基尼系数:
| 特性 | 信息增益 | 基尼系数 |
|---|---|---|
| 计算复杂度 | 较高(对数运算) | 较低(平方运算) |
| 偏向 | 偏向多分支 | 偏向平衡分裂 |
| 实际应用 | ID3/C4.5 | CART(sklearn 默认) |
1.3 决策树的分裂示例
让我们看一个简化的量化场景:
import pandas as pd
import numpy as np
# 模拟数据:股票特征和未来收益
np.random.seed(42)
n_samples = 1000
# 特征:动量、波动率、换手率
momentum = np.random.randn(n_samples)
volatility = np.random.rand(n_samples) * 0.5 + 0.1
turnover = np.random.rand(n_samples) * 0.2
# 标签:未来收益 > 0 为正样本
returns = 0.3 * momentum - 0.5 * volatility + 0.1 * turnover + np.random.randn(n_samples) * 0.3
y = (returns > 0).astype(int)
# 创建 DataFrame
df = pd.DataFrame({
'momentum': momentum,
'volatility': volatility,
'turnover': turnover,
'positive_return': y
})
# 手动计算最佳分裂点
def find_best_split(feature, y):
"""寻找特征的最佳分裂点"""
best_gain = -1
best_split = None
unique_values = np.unique(feature)
for i in range(len(unique_values) - 1):
split = (unique_values[i] + unique_values[i + 1]) / 2
left = feature <= split
right = feature > split
if np.sum(left) > 0 and np.sum(right) > 0:
gain = gini_gain(y, np.where(left)[0], np.where(right)[0])
if gain > best_gain:
best_gain = gain
best_split = split
return best_gain, best_split
# 测试每个特征
for feature_name in ['momentum', 'volatility', 'turnover']:
feature = df[feature_name].values
gain, split = find_best_split(feature, y)
print(f"{feature_name:12s}: 最佳分裂点 = {split:.4f}, 基尼增益 = {gain:.4f}")输出示例:
momentum : 最佳分裂点 = 0.0123, 基尼增益 = 0.0834
volatility : 最佳分裂点 = 0.3478, 基尼增益 = 0.0256
turnover : 最佳分裂点 = 0.1024, 基尼增益 = 0.0123
结论: 动量因子的分裂效果最好,说明它对预测收益最有帮助。
1.4 剪枝(Pruning)
决策树容易过拟合,需要剪枝来控制复杂度。
预剪枝(Pre-pruning): 在训练过程中限制树的生长
| 参数 | 说明 |
|---|---|
max_depth | 树的最大深度 |
min_samples_split | 节点分裂所需的最小样本数 |
min_samples_leaf | 叶节点的最小样本数 |
max_leaf_nodes | 最大叶节点数 |
min_impurity_decrease | 分裂所需的最小不纯度减少量 |
后剪枝(Post-pruning): 先让树完全生长,再剪掉不重要的分支
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
X = df[['momentum', 'volatility', 'turnover']]
y = df['positive_return']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
# 不剪枝:容易过拟合
tree_no_pruning = DecisionTreeClassifier(random_state=42)
tree_no_pruning.fit(X_train, y_train)
print(f"不剪枝 - 训练准确率: {tree_no_pruning.score(X_train, y_train):.4f}")
print(f"不剪枝 - 测试准确率: {tree_no_pruning.score(X_test, y_test):.4f}")
print(f"树深度: {tree_no_pruning.get_depth()}, 叶节点数: {tree_no_pruning.get_n_leaves()}")
# 预剪枝:限制复杂度
tree_pruned = DecisionTreeClassifier(
max_depth=3,
min_samples_leaf=20,
random_state=42
)
tree_pruned.fit(X_train, y_train)
print(f"\n预剪枝 - 训练准确率: {tree_pruned.score(X_train, y_train):.4f}")
print(f"预剪枝 - 测试准确率: {tree_pruned.score(X_test, y_test):.4f}")
print(f"树深度: {tree_pruned.get_depth()}, 叶节点数: {tree_pruned.get_n_leaves()}")2. 集成学习思想
2.1 为什么需要集成
单棵决策树有以下问题:
| 问题 | 说明 | 影响 |
|---|---|---|
| 高方差 | 数据变化导致树结构剧烈变化 | 不稳定 |
| 容易过拟合 | 可以完美记住训练数据 | 泛化差 |
| 决策边界不平滑 | 由正交分割组成 | 不够精细 |
集成学习的核心思想: 组合多个弱学习器,创造一个强学习器。
2.2 Bagging:Bootstrap Aggregating
核心思想: 并行训练多个独立模型,然后平均预测。
流程:
1. 有放回抽样 → 生成 B 个不同的训练集
2. 对每个训练集训练一棵决策树
3. 预测时:对所有树的预测取平均(回归)或投票(分类)
为什么有效: 通过平均降低方差。
from sklearn.ensemble import BaggingClassifier
from sklearn.tree import DecisionTreeClassifier
# Bagging 示例
bagging = BaggingClassifier(
estimator=DecisionTreeClassifier(max_depth=3),
n_estimators=100,
max_samples=0.8, # 每棵树用 80% 的样本
max_features=0.8, # 每棵树用 80% 的特征
random_state=42,
n_jobs=-1
)
bagging.fit(X_train, y_train)
print(f"Bagging - 训练准确率: {bagging.score(X_train, y_train):.4f}")
print(f"Bagging - 测试准确率: {bagging.score(X_test, y_test):.4f}")随机森林(Random Forest) 是 Bagging 的经典实现:
from sklearn.ensemble import RandomForestClassifier
rf = RandomForestClassifier(
n_estimators=100,
max_depth=5,
min_samples_leaf=20,
max_features='sqrt', # 特征随机性
random_state=42,
n_jobs=-1
)
rf.fit(X_train, y_train)
print(f"随机森林 - 训练准确率: {rf.score(X_train, y_train):.4f}")
print(f"随机森林 - 测试准确率: {rf.score(X_test, y_test):.4f}")2.3 Boosting:串行改进
核心思想: 串行训练模型,每个新模型专注于纠正前一个模型的错误。
Bagging vs Boosting 对比:
| 特性 | Bagging | Boosting |
|---|---|---|
| 训练方式 | 并行 | 串行 |
| 模型权重 | 相等 | 根据性能加权 |
| 目标 | 降低方差 | 降低偏差和方差 |
| 对异常值 | 鲁棒 | 敏感 |
| 代表算法 | 随机森林 | GBDT, XGBoost, LightGBM |
Boosting 工作流程:
1. 训练第一个弱学习器 h₁(x)
2. 计算残差/误差 r₁ = y - h₁(x)
3. 训练第二个弱学习器 h₂(x) 去预测 r₁
4. 计算新的残差 r₂ = r₁ - h₂(x)
5. 重复步骤 3-4,直到达到预设数量
6. 最终预测:H(x) = h₁(x) + h₂(x) + ... + h_T(x)
3. GBDT 原理
3.1 前向分步加法模型
GBDT(Gradient Boosting Decision Tree)是 Boosting 思想与决策树的结合。
数学表达:
给定损失函数 ,GBDT 通过以下方式构建模型:
其中:
- 是前 m-1 轮的累积模型
- 是第 m 棵决策树
- 是学习率(shrinkage)
核心问题: 如何找到每棵树的最佳拟合方向?
3.2 负梯度拟合(伪残差)
GBDT 的关键创新:用损失函数的负梯度作为残差。
对于平方损失 :
负梯度 = 残差:
对于一般损失函数,第 m 棵树拟合的目标是:
这就是为什么叫”梯度”提升!
def pseudo_residual(y, pred, loss='square'):
"""计算伪残差"""
if loss == 'square':
# 平方损失的负梯度就是残差
return y - pred
elif loss == 'log':
# LogLoss 的负梯度
return y - 1 / (1 + np.exp(-pred))
else:
raise ValueError(f"Unknown loss: {loss}")
# 示例
y_true = np.array([1, 0, 1, 1])
pred = np.array([0.7, 0.3, 0.6, 0.9])
residuals = pseudo_residual(y_true, pred)
print("伪残差:", residuals)3.3 GBDT 算法流程
完整算法:
-
初始化:
-
对于 m = 1 到 M:
- 计算伪残差:
- 用残差训练决策树:拟合
- 计算叶节点的最佳值:
- 更新模型:
-
输出:
其中 是学习率,通常取 0.01 - 0.1。
3.4 GBDT 简单实现
import numpy as np
from sklearn.tree import DecisionTreeRegressor
class SimpleGBDT:
"""简化的 GBDT 实现,仅用于理解原理"""
def __init__(self, n_estimators=100, learning_rate=0.1, max_depth=3):
self.n_estimators = n_estimators
self.learning_rate = learning_rate
self.max_depth = max_depth
self.trees = []
self.init_pred = None
def fit(self, X, y):
"""训练模型"""
# 初始化:预测均值
self.init_pred = np.mean(y)
F_m = np.full(len(y), self.init_pred)
for m in range(self.n_estimators):
# 计算负梯度(残差)
residual = y - F_m
# 训练决策树拟合残差
tree = DecisionTreeRegressor(max_depth=self.max_depth)
tree.fit(X, residual)
# 更新预测
F_m += self.learning_rate * tree.predict(X)
self.trees.append(tree)
def predict(self, X):
"""预测"""
F_m = np.full(X.shape[0], self.init_pred)
for tree in self.trees:
F_m += self.learning_rate * tree.predict(X)
return F_m
# 测试
X = df[['momentum', 'volatility', 'turnover']].values
y = df['positive_return'].values
gbdt = SimpleGBDT(n_estimators=50, learning_rate=0.1, max_depth=3)
gbdt.fit(X, y)
predictions = gbdt.predict(X)
print(f"预测范围: [{predictions.min():.4f}, {predictions.max():.4f}]")
print(f"真实均值: {y.mean():.4f}, 预测均值: {predictions.mean():.4f}")3.5 学习率的作用
学习率(shrinkage)是 GBDT 的重要超参数:
| 学习率 | 效果 | 树数量需求 | 过拟合风险 |
|---|---|---|---|
| 大(0.1-0.3) | 收敛快 | 少 | 高 |
| 小(0.01-0.05) | 收敛慢 | 多 | 低 |
原则: 学习率越小,需要更多的树,但泛化性能更好。
4. XGBoost 创新
XGBoost(eXtreme Gradient Boosting)是 GBDT 的高效实现,在竞赛和工业界广受欢迎。
4.1 二阶泰勒展开
传统 GBDT 只用一阶导数(梯度),XGBoost 使用二阶导数(Hessian)。
泰勒展开近似损失函数:
目标函数(忽略常数项):
其中:
- 一阶导
- 二阶导
- 是正则化项
优势: 更准确地逼近损失函数,收敛更快。
4.2 正则化
XGBoost 在目标函数中显式加入正则化项:
其中:
- :叶节点数量的惩罚系数
- :叶节点数量
- :L2 正则化系数
- :第 j 个叶节点的预测值
作用: 控制模型复杂度,防止过拟合。
4.3 列采样(Column Subsampling)
XGBoost 借鉴随机森林,引入列采样:
| 采样方式 | 参数 | 说明 |
|---|---|---|
| 按树采样 | colsample_bytree | 每棵树随机选择特征子集 |
| 按层采样 | colsample_bylevel | 每层随机选择特征子集 |
| 按分裂采样 | colsample_bynode | 每次分裂随机选择特征子集 |
作用: 降低过拟合风险,加速训练。
4.4 缺失值处理
XGBoost 可以自动处理缺失值:
import xgboost as xgb
# 创建包含缺失值的数据
X_missing = df[['momentum', 'volatility', 'turnover']].copy()
X_missing.loc[np.random.choice(len(X_missing), 50), 'momentum'] = np.nan
dtrain = xgb.DMatrix(X_missing, label=y)
# XGBoost 自动学习缺失值的分裂方向
params = {
'max_depth': 3,
'eta': 0.1,
'objective': 'binary:logistic'
}
bst = xgb.train(params, dtrain, num_boost_round=100)
print("XGBoost 自动处理了缺失值!")原理: 对每个分裂点,XGBoost 分别计算缺失值去左右分支的增益,选择更好的方向。
4.5 XGBoost 使用示例
from xgboost import XGBClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
X_train, X_test, y_train, y_test = train_test_split(
df[['momentum', 'volatility', 'turnover']],
df['positive_return'],
test_size=0.3,
random_state=42
)
# XGBoost 分类器
xgb_model = XGBClassifier(
n_estimators=200,
max_depth=4,
learning_rate=0.05,
min_child_weight=10,
subsample=0.8,
colsample_bytree=0.8,
reg_lambda=1.0, # L2 正则化
reg_alpha=0.1, # L1 正则化
random_state=42,
n_jobs=-1,
eval_metric='logloss'
)
xgb_model.fit(
X_train, y_train,
eval_set=[(X_test, y_test)],
verbose=False
)
y_pred = xgb_model.predict(X_test)
print(f"XGBoost 测试准确率: {accuracy_score(y_test, y_pred):.4f}")5. LightGBM 创新
LightGBM 是微软推出的梯度提升框架,专注于效率和内存优化。
5.1 GOSS:基于梯度的单边采样
问题: 所有样本都参与训练,计算成本高。
GOSS 策略:
- 根据梯度的绝对值对样本排序
- 保留梯度大的样本(a%):这些样本训练误差大,更重要
- 随机采样梯度小的样本(b%):这些样本训练误差小
- 对小梯度样本乘以一个常数 来补偿
优势: 用更少的样本达到相似的精度,加速训练。
import lightgbm as lgb
# LightGBM 默认使用 GOSS
lgb_model = lgb.LGBMClassifier(
n_estimators=200,
max_depth=4,
learning_rate=0.05,
min_child_samples=20,
subsample=0.8, # GOSS 采样
colsample_bytree=0.8,
reg_alpha=0.1,
reg_lambda=1.0,
random_state=42,
n_jobs=-1,
verbose=-1
)
lgb_model.fit(X_train, y_train)
print(f"LightGBM 测试准确率: {lgb_model.score(X_test, y_test):.4f}")5.2 EFB:互斥特征捆绑
问题: 特征维度高时,分裂点寻找成本高。
EFB 策略:
- 识别互斥特征(很少同时取非零值的特征)
- 将互斥特征捆绑为一个特征
- 用特征值偏移来区分不同特征
优势: 减少特征数量,加速分裂寻找。
示例:
原始特征:F1, F2, F3, F4
互斥分析:F1 和 F3 互斥,F2 和 F4 互斥
捆绑结果:
- Bundle1: {F1, F3} → 新特征 B1
- Bundle2: {F2, F4} → 新特征 B2
特征数量:4 → 2
5.3 Leaf-wise 生长策略
传统算法是 Level-wise(按层生长),LightGBM 使用 Leaf-wise(按叶生长)。
对比:
Level-wise(XGBoost): Leaf-wise(LightGBM):
0 0
/ \ / \
1 2 1 2
/ \ / \ |
3 4 5 6 3
|
4
| 特性 | Level-wise | Leaf-wise |
|---|---|---|
| 生长方式 | 按层平衡生长 | 选择增益最大的叶节点分裂 |
| 优势 | 不容易过拟合 | 收敛更快,精度更高 |
| 劣势 | 有些无用分裂 | 容易过拟合(需控制深度) |
# LightGBM 使用 leaf-wise,需要限制 max_depth
lgb_leafwise = lgb.LGBMClassifier(
n_estimators=200,
max_depth=6, # 必须限制深度
num_leaves=31, # 叶节点数限制(2^depth - 1)
learning_rate=0.05,
min_child_samples=20,
boosting_type='gbdt', # 默认就是 leaf-wise
random_state=42,
n_jobs=-1,
verbose=-1
)重要参数关系: num_leaves 应该小于 ,否则会过拟合。
5.4 LightGBM 完整示例
import lightgbm as lgb
from sklearn.metrics import roc_auc_score
# 创建 Dataset
train_data = lgb.Dataset(X_train, label=y_train)
valid_data = lgb.Dataset(X_test, label=y_test, reference=train_data)
# 参数
params = {
'boosting_type': 'gbdt',
'objective': 'binary',
'metric': ['binary_logloss', 'auc'],
'num_leaves': 31,
'max_depth': 6,
'learning_rate': 0.05,
'min_child_samples': 20,
'subsample': 0.8,
'colsample_bytree': 0.8,
'reg_alpha': 0.1,
'reg_lambda': 1.0,
'verbose': -1
}
# 训练(带早停)
gbm = lgb.train(
params,
train_data,
num_boost_round=1000,
valid_sets=[train_data, valid_data],
callbacks=[
lgb.early_stopping(stopping_rounds=50),
lgb.log_evaluation(period=100)
]
)
# 预测
y_pred_prob = gbm.predict(X_test, num_iteration=gbm.best_iteration)
print(f"LightGBM AUC: {roc_auc_score(y_test, y_pred_prob):.4f}")6. CatBoost 简介
CatBoost(Category Boosting)是 Yandex 推出的梯度提升框架,专注于类别特征。
6.1 核心创新
| 创新点 | 说明 |
|---|---|
| Ordered Boosting | 减少预测偏移 |
| 类别特征处理 | 基于 Target Statistics 的编码 |
| 对称树 | 所有树结构相同,加速预测 |
6.2 类别特征处理
CatBoost 可以直接处理类别特征,不需要手动 one-hot 编码。
from catboost import CatBoostClassifier, Pool
# 模拟带类别特征的数据
df_cat = df.copy()
df_cat['sector'] = np.random.choice(['科技', '金融', '医疗', '消费'], len(df))
df_cat['market_cap_group'] = np.random.choice(['大', '中', '小'], len(df))
# 定义类别特征索引
cat_features = [3, 4] # sector, market_cap_group 的列索引
# 创建 Pool
train_pool = Pool(
data=X_train,
label=y_train,
cat_features=cat_features
)
# CatBoost 训练
cat_model = CatBoostClassifier(
iterations=200,
depth=6,
learning_rate=0.05,
loss_function='Logloss',
random_seed=42,
verbose=False
)
cat_model.fit(train_pool)
print("CatBoost 自动处理类别特征!")6.3 Ordered Boosting
问题: 传统 Boosting 使用目标统计值时会有数据泄漏。
CatBoost 方案: 使用类似 “留一法” 的策略计算目标统计值。
7. 四种模型对比
7.1 功能对比表
| 特性 | GBDT | XGBoost | LightGBM | CatBoost |
|---|---|---|---|---|
| 核心创新 | 梯度提升 | 二阶导+正则化 | GOSS+EFB | 类别特征处理 |
| 训练速度 | 中 | 快 | 很快 | 慢 |
| 预测速度 | 快 | 快 | 很快 | 中 |
| 内存占用 | 中 | 中 | 低 | 高 |
| 精度 | 高 | 很高 | 很高 | 高 |
| 类别特征 | 需手动编码 | 需手动编码 | 需手动编码 | 自动处理 |
| 缺失值 | 需处理 | 自动处理 | 自动处理 | 自动处理 |
| 并行支持 | 有限 | 好 | 很好 | 好 |
| 适用数据规模 | 小-中 | 大 | 超大 | 中 |
7.2 选择建议
| 场景 | 推荐模型 | 理由 |
|---|---|---|
| 快速原型验证 | LightGBM | 训练快,API 简洁 |
| 竞赛刷分 | XGBoost | 精度通常最高 |
| 超大数据集 | LightGBM | 内存效率高 |
| 类别特征多 | CatBoost | 自动处理 |
| 生产部署 | LightGBM | 预测快,模型小 |
| 研究实验 | XGBoost | 文档完善,社区活跃 |
7.3 性能对比(模拟)
import time
from sklearn.metrics import roc_auc_score
models = {
'XGBoost': XGBClassifier(
n_estimators=200, max_depth=4, learning_rate=0.05,
random_state=42, n_jobs=-1, eval_metric='logloss'
),
'LightGBM': lgb.LGBMClassifier(
n_estimators=200, max_depth=4, learning_rate=0.05,
random_state=42, n_jobs=-1, verbose=-1
),
'CatBoost': CatBoostClassifier(
iterations=200, depth=4, learning_rate=0.05,
random_seed=42, verbose=False
)
}
results = []
for name, model in models.items():
start_time = time.time()
model.fit(X_train, y_train)
train_time = time.time() - start_time
start_time = time.time()
y_pred = model.predict_proba(X_test)[:, 1]
pred_time = time.time() - start_time
auc = roc_auc_score(y_test, y_pred)
results.append({
'Model': name,
'AUC': f"{auc:.4f}",
'Train Time': f"{train_time:.2f}s",
'Predict Time': f"{pred_time:.4f}s"
})
results_df = pd.DataFrame(results)
print(results_df)预期输出(类似):
Model AUC Train Time Predict Time
0 XGBoost 0.6834 1.23s 0.0123s
1 LightGBM 0.6812 0.45s 0.0034s
2 CatBoost 0.6798 3.56s 0.0234s
8. 为什么树模型适合量化
8.1 数据特点匹配
| 量化数据特点 | 树模型优势 | 说明 |
|---|---|---|
| 表格结构 | 天然适合 | 因子就是表格特征 |
| 稀疏性 | 自动处理 | 树模型可以处理稀疏特征 |
| 非线性关系 | 自动捕捉 | 树模型能发现复杂模式 |
| 特征交互 | 自动学习 | 不需要手动构造交互项 |
| 异常值多 | 鲁棒性强 | 对异常值不敏感 |
| 缺失值 | 自动处理 | XGBoost/LightGBM 原生支持 |
8.2 实战优势
# 示例:量化因子的一些典型场景
import numpy as np
import pandas as pd
# 场景1:特征交互(因子A只在因子B高时有效)
np.random.seed(42)
n = 10000
factor_a = np.random.randn(n)
factor_b = np.random.rand(n)
# 只在 factor_b > 0.5 时,factor_a 才有用
returns = np.where(
factor_b > 0.5,
0.3 * factor_a + np.random.randn(n) * 0.5,
np.random.randn(n) * 0.5
)
# 线性模型难以捕捉这种交互
from sklearn.linear_model import LinearRegression
linear = LinearRegression()
X_interaction = pd.DataFrame({'A': factor_a, 'B': factor_b})
linear.fit(X_interaction, returns)
print(f"线性模型 R²: {linear.score(X_interaction, returns):.4f}")
# 树模型自动学习交互
from sklearn.tree import DecisionTreeRegressor
tree = DecisionTreeRegressor(max_depth=3)
tree.fit(X_interaction, returns)
print(f"树模型 R²: {tree.score(X_interaction, returns):.4f}")8.3 量化特定优势
1. 自动分组和选股
树模型可以自动学习因子阈值,不需要手动划分:
传统方法:
if momentum > 0.5 and volatility < 0.3:
score = 1
elif momentum > 0.3:
score = 0.5
else:
score = 0
树模型:自动学习最佳分裂点
2. 处理离散特征
行业、板块等离散特征可以直接使用。
3. 稳定性好
相比神经网络,树模型对数据变化更稳健。
核心知识点总结
决策树核心
- 分裂准则:信息增益、基尼系数
- 剪枝策略:预剪枝(限制复杂度)、后剪枝(修剪)
- 单树问题:高方差、容易过拟合
集成学习
- Bagging:并行训练,降低方差(随机森林)
- Boosting:串行训练,降低偏差和方差(GBDT)
GBDT 核心
- 前向分步:
- 负梯度拟合:用损失函数的梯度作为残差
- 学习率:控制收敛速度和过拟合
XGBoost 创新
- 二阶泰勒展开:更准确的梯度近似
- 正则化:显式惩罚复杂度
- 列采样:随机森林式的特征采样
LightGBM 创新
- GOSS:基于梯度的采样
- EFB:互斥特征捆绑
- Leaf-wise:按叶生长,更快收敛
模型选择
| 场景 | 推荐模型 |
|---|---|
| 通用/竞赛 | XGBoost |
| 大数据/生产 | LightGBM |
| 类别特征多 | CatBoost |
下一节: 02-时序数据划分.md - 学习如何正确划分量化数据的训练集和测试集。