2. 特征工程基础:特征工程的重要性、特征类型与流程
大家好,欢迎来到第二章。说实话,很多刚入门的朋友会问我:「模型结构调一调,损失函数换一换,效果不就上去了吗?为什么非要搞特征工程?」
嗯,这个问题我当年也问过。直到我在一个电商推荐项目里,花了三周调模型,AUC 死活卡在 0.72 上不去。后来一个老大哥过来看了一眼数据,说:「你这些时间戳直接丢进去,模型能学到啥?」他帮我做了几个简单的特征——用户最近 1 小时点击次数、商品被收藏的时段分布——结果你猜怎么着?AUC 直接跳到 0.81。从那以后,我再也不敢小看特征工程了。
2.1 特征工程到底有多重要?
说白了,特征工程就是把原始数据变成模型能听懂的语言。你想想看,模型本质上就是个数学函数,它只能处理数字。如果你给它一堆文本、日期、类别标签,它根本不知道该怎么办。
我个人习惯把特征工程比作「给模型喂饭」。你喂的是精细加工过的营养餐,模型就学得快、学得好;你喂的是带壳的稻谷,模型可能啃都啃不动。
核心观点:数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限。
我在项目中遇到过很多次,同样的数据,同样的模型,不同的人做特征工程,最终效果能差 10% 以上。这不是模型的问题,是特征工程没做到位。
2.2 特征类型:你得先认识你的数据
做特征工程之前,先搞清楚你手里有什么类型的特征。我一般把它们分成三大类:
2.2.1 数值特征
这个最好理解。年龄、价格、温度、点击次数……这些都是数值。模型可以直接拿来做加减乘除。
但要注意,数值特征不是直接丢进去就完事了。我曾经在一个房价预测项目里,直接把「房屋面积」和「卧室数量」丢进模型,结果模型完全被面积这个特征带偏了——因为面积的数值范围是 50-500,而卧室数量只有 1-5。模型会默认面积更重要,其实不一定。
常见的处理方式:
- 归一化:把数值缩放到 [0,1] 区间
- 标准化:让数据符合均值为 0、方差为 1 的分布
- 分箱:把连续值变成离散区间,比如年龄分成 0-18、18-35、35-60、60+
2.2.2 类别特征
颜色、城市、职业、用户等级……这些是类别特征。模型不认识「红色」「北京」「工程师」这些词,你得把它们转成数字。
我建议你记住一个原则:类别特征没有大小关系。「北京」不比「上海」大,「工程师」也不比「医生」小。所以你不能直接标成 1、2、3,那样模型会以为 3 比 1 大。
常用的编码方式:
| 编码方式 | 适用场景 | 注意事项 |
|---|---|---|
| 独热编码 (One-Hot) | 类别数量少(< 20 个) | 类别太多会导致特征维度爆炸 |
| 标签编码 (Label Encoding) | 类别有顺序关系 | 无序类别慎用,会引入错误的大小关系 |
| 嵌入 (Embedding) | 类别数量多(如用户 ID、商品 ID) | 需要训练,适合深度学习 |
我的小技巧:如果类别特征有几千个取值,别用独热编码。我试过一次,特征维度从 10 直接膨胀到 5000,模型训练慢得要命,效果还变差了。这时候用 Embedding 或者目标编码更合适。
2.2.3 文本特征
评论、标题、描述……这些是文本。模型不认识汉字,你得把它变成向量。
我记得最早做文本分类的时候,用的是词袋模型(Bag of Words)。就是把所有文章里出现的词统计一遍,每个词是一个维度,文章里这个词出现几次就填几。简单粗暴,但效果还行。
现在更常用的方法:
- TF-IDF:不仅看词频,还看这个词在其他文章里出现得少不少。出现得越少越重要。
- Word2Vec / GloVe:把每个词映射成一个稠密向量,能捕捉词与词之间的语义关系。
- BERT 等预训练模型:效果最好,但计算成本也最高。
嗯,这里要注意:文本特征的处理往往是最耗时的。我建议你先用简单的 TF-IDF 试试水,如果效果不够再上 BERT。别一上来就搞大模型,容易把自己搞崩溃。
2.3 特征工程的标准流程
做特征工程不是瞎搞,我一般按这个流程走:
- 数据探索:先看看数据长什么样。有多少缺失值?分布是否均匀?有没有异常值?
- 数据清洗:处理缺失值、去重、修正错误数据。这一步最枯燥,但也最重要。
- 特征构建:根据业务理解创造新特征。比如从「注册时间」和「当前时间」算出「用户注册天数」。
- 特征转换:把原始特征变成模型能用的形式。数值归一化、类别编码、文本向量化。
- 特征选择:去掉冗余特征,保留最有用的。特征太多反而会引入噪声。
- 特征监控:上线后还要持续监控特征分布有没有变化。我曾经遇到过,模型上线一个月后效果暴跌,查了半天发现是某个特征的分布变了。
避坑指南:我曾经在一个金融风控项目里,直接用了全部 200 多个特征训练模型。结果模型在测试集上表现很好,上线后却一塌糊涂。后来发现很多特征之间存在多重共线性,模型学到的权重完全不可靠。所以特征选择这步千万别省。
2.4 一个简单的代码示例
说了这么多,来点实际的。下面我用 TensorFlow 的 Feature Columns 做一个简单的特征工程演示:
import tensorflow as tf
# 假设我们有这些原始数据
# 数值特征:年龄、收入
# 类别特征:城市、职业
# 文本特征:用户简介
# 1. 数值特征处理
age = tf.feature_column.numeric_column('age')
income = tf.feature_column.numeric_column('income')
# 2. 类别特征处理(独热编码)
city = tf.feature_column.categorical_column_with_vocabulary_list(
'city', ['北京', '上海', '广州', '深圳'])
city_one_hot = tf.feature_column.indicator_column(city)
# 3. 类别特征处理(嵌入)
occupation = tf.feature_column.categorical_column_with_hash_bucket(
'occupation', hash_bucket_size=100)
occupation_embedding = tf.feature_column.embedding_column(
occupation, dimension=8)
# 4. 组合特征:年龄分箱
age_buckets = tf.feature_column.bucketized_column(
age, boundaries=[18, 25, 35, 45, 60])
# 把所有特征列组合起来
feature_columns = [
age, income,
city_one_hot,
occupation_embedding,
age_buckets
]
# 创建 DenseFeatures 层
feature_layer = tf.keras.layers.DenseFeatures(feature_columns)
# 模拟输入数据
input_data = {
'age': [28, 35, 22],
'income': [15000, 25000, 8000],
'city': ['北京', '上海', '广州'],
'occupation': ['工程师', '医生', '教师']
}
# 看看转换后的结果
output = feature_layer(input_data)
print(output.numpy())
你看,代码其实不复杂。关键是你得想清楚:每个特征应该用什么方式处理?为什么要这么处理?
2.5 总结一下
特征工程不是花架子,它是模型效果的基础。我见过太多人把时间花在调参上,却不愿意花两天好好做特征工程。结果呢?调参调出来的 0.01 提升,还不如加一个有效特征带来的 0.05 提升。
下一章我们会深入讲 TensorFlow 的 Feature Columns API,到时候我会手把手带你做更多实战案例。今天就先到这里,记住:好特征胜过好模型。
课后思考:你手头的数据集里,有哪些特征是可以组合成新特征的?比如「点击次数/曝光次数」可以算出点击率,「购买时间-注册时间」可以算出用户活跃天数。试试看,说不定会有惊喜。