参数与特征基础:参数类型、特征分类、优化问题定义
聊参数和特征之前,我想先说说这两者的关系。
你可能觉得参数就是超参数,特征就是数据列。嗯,大体没错。但真正做协同优化时,如果不把它们放在一起看,很容易掉坑里。我早期做过一个推荐系统项目,单独调参数能到AUC 0.78,加上特征筛选反而掉到0.72——后来发现是参数和特征在相互“打架”。
所以这一章,我们先打好地基。把参数是什么、特征是什么、优化问题怎么定义,一件一件理清楚。
参数类型:不只是“调个数字”
先问个问题:你手头要调的东西,到底属于哪一类参数?
我个人习惯把参数分成三种:
- 连续参数:取值范围是实数区间。比如学习率0.001~0.1,Dropout概率0.2~0.8。
- 离散参数:取值来自有限集合。比如树的最大深度{3,5,7,9},优化器选择{Adam,SGD,RMSprop}。
- 条件参数:只在其他参数取特定值时才生效。比如用了Adam优化器,才有β1、β2参数;用RAdam就多一个稳定期参数。
关键点:条件参数经常被忽略,导致搜索空间里产生大量无效组合。我建议在定义参数空间时,显式写出依赖关系。
举个例子,一个简单的XGBoost参数配置,JSON形式是这样的:
{
"n_estimators": {"type": "int", "range": [50, 500]},
"max_depth": {"type": "int", "range": [3, 15]},
"learning_rate": {"type": "float", "range": [0.01, 0.3]},
"subsample": {"type": "float", "range": [0.5, 1.0]},
"grow_policy": {"type": "categorical", "values": ["depthwise", "lossguide"]},
"max_leaves": {
"type": "int",
"range": [8, 256],
"condition": {"param": "grow_policy", "value": "lossguide"}
}
}
看到没?max_leaves 只有在 grow_policy 为 lossguide 时才需要。如果不加条件,搜索算法可能在 depthwise 模式下也调 max_leaves,白白浪费计算。
一个小技巧:用配置管理器(比如OmegaConf、Hydra)定义参数层级,可以自动处理条件逻辑。我曾经一个项目用纯字典定义,后来条件嵌套多了维护成本暴涨,换成Hydra后清爽很多。
特征分类:模型吃的“食材”
特征决定了模型的上限,参数只是逼近这个上限。我见过太多人花80%时间调参,却只在原始特征上折腾,效果自然有限。
从工程角度看,特征大概分这几类:
| 类型 | 例子 | 常用处理 |
|---|---|---|
| 数值特征 | 年龄、收入、温度 | 标准化、对数变换、分箱 |
| 类别特征 | 城市、部门、颜色 | One-hot、Label Encoding、Target Encoding |
| 时序特征 | 时间戳、星期、季节性 | 滞后特征、滚动统计、差分 |
| 文本特征 | 评论、描述、日志 | TF-IDF、Word2Vec、大模型Embedding |
| 交互特征 | 年龄×收入、城市×类别 | 多项式组合、笛卡尔积(谨慎使用) |
你想想看,如果特征数量多但噪声大,参数再优也没用。反过来,特征质量高,简单模型+默认参数都可能碾压复杂模型。
避坑指南:我曾经在风控项目里做特征工程,贪心加了200多个交互特征,导致模型方差飞升,上线后bad rate反而恶化。后来用特征重要性筛选降到30个,再配合正则化参数,效果才稳住。特征不是越多越好。
优化问题定义:把业务放到数学框里
参数和特征都准备好了,接下来要回答:我们到底要优化什么?
一个完整的优化问题定义,包含三要素:
- 决策变量——参数和特征选择(哪些特征要保留,哪些参数要调)
- 目标函数——比如AUC、MSE、业务ROI
- 约束条件——训练时间不超过1小时,模型大小小于100MB,推理延迟低于50ms
协同优化的难点在于,参数和特征会互相影响。举个例子,增加一个高基数类别特征后,树的深度参数可能要从6调到10才能充分利用,但训练时间却翻了一倍。
用数学语言表达,大概是这样的:
Minimize f(θ, F) = -AUC(模型(θ, F))
Subject to g1(θ, F) = 训练时间(θ, F) ≤ T_max
g2(θ, F) = 模型大小(θ, F) ≤ S_max
θ ∈ Θ, F ⊆ 原始特征集
这里 θ 是参数空间,F 是特征子集选择。注意F是离散的,θ里有连续有离散——这就成了混合整数非线性优化问题。嗯,并不简单。
我的经验:在定义目标函数时,尽量用单一指标。如果必须多目标(比如AUC+推理延迟),可以加权求和或设置帕累托前沿。我习惯在探索阶段先跑一个纯性能目标,选定候选后再加上约束。
知识结构图:参数与特征协同优化的全局脉络
下面这张图把这章的核心关系画出来了。你看一眼就能知道每个部分的位置。
用代码感受一下
光说不练假把式。下面是一个用Optuna定义协同优化空间的Python片段,把参数和特征选择放在一起:
import optuna
def objective(trial):
# 参数部分
lr = trial.suggest_float("lr", 1e-4, 1e-1, log=True)
n_layers = trial.suggest_int("n_layers", 1, 5)
optimizer_name = trial.suggest_categorical("optimizer", ["Adam", "SGD"])
# 条件参数
if optimizer_name == "Adam":
beta1 = trial.suggest_float("beta1", 0.8, 0.999)
else:
beta1 = None # 用默认值
# 特征选择部分(假设原有50个特征)
feature_mask = []
for i in range(50):
use = trial.suggest_categorical(f"feat_{i}", [True, False])
feature_mask.append(use)
# 训练模型并返回指标
model = train_model(lr, n_layers, optimizer_name, beta1, feature_mask)
return model.validation_auc()
注意这里特征是否入选也成了优化变量。但50个特征意味着50个二值变量,搜索空间瞬间变成2^50。所以实际项目中我会先用特征重要性或者Filter方法预筛,只留20~30个候选,再和参数一起搜。
实用建议:如果想快速跑第一轮,可以用随机搜索或TPE采样。我习惯先跑50组随机,看参数和特征的大致影响方向,再切到贝叶斯优化。
这一章的核心你已经抓住:参数类型决定了搜索空间的设计,特征分类影响模型的性能天花板,而优化问题定义把这些串成一个可计算的数学问题。后面的章节,我们会一步步在这个基础上叠加技巧。