4. 数值特征变换:标准化、归一化、对数变换与Box-Cox变换
聊到数值特征,很多同学上来就扔进模型。我早期也这么干过,结果模型收敛慢得让人抓狂。后来才明白——不同尺度的数值特征,对模型来说就是鸡同鸭讲。
这一章,咱们就把数值特征变换的四种核心手段讲透。标准化、归一化、对数变换、Box-Cox,什么时候用、怎么用、坑在哪,我都会结合项目经验说清楚。
4.1 标准化(StandardScaler)
标准化,也叫Z-score归一化。公式很简单:z = (x - μ) / σ。处理后数据均值为0,标准差为1。
我个人习惯在以下场景优先用标准化:
- SVM、PCA、LDA这类依赖距离或方差的模型
- 神经网络,尤其是深层网络,标准化能加速收敛
- 线性回归、逻辑回归,当特征尺度差异大时
核心要点:标准化不改变数据分布形状,只是平移和缩放。它假设数据近似正态分布时效果最好。
from sklearn.preprocessing import StandardScaler
import numpy as np
# 模拟数据:年龄和收入,尺度差异巨大
data = np.array([[25, 50000],
[35, 80000],
[45, 120000],
[55, 60000]])
scaler = StandardScaler()
data_scaled = scaler.fit_transform(data)
print("原始数据:\n", data)
print("\n标准化后:\n", data_scaled)
print("\n均值:", data_scaled.mean(axis=0))
print("标准差:", data_scaled.std(axis=0))
我曾经踩过的坑:标准化对异常值非常敏感。有一次做用户消费金额标准化,有个用户消费了100万,结果其他用户的标准化值全被压缩到-0.1到0.1之间。后来我改用RobustScaler(基于中位数和四分位距),才解决问题。
4.2 归一化(MinMaxScaler)
归一化把数据缩放到[0, 1]区间。公式:x_scaled = (x - min) / (max - min)。
你想想看,什么时候用归一化?
- KNN、K-Means等基于距离的算法
- 神经网络输入层,尤其是使用Sigmoid/Tanh激活函数时
- 图像处理中像素值归一化到[0,1]
from sklearn.preprocessing import MinMaxScaler
scaler_mm = MinMaxScaler()
data_mm = scaler_mm.fit_transform(data)
print("MinMax归一化后:\n", data_mm)
print("\n最小值:", data_mm.min(axis=0))
print("最大值:", data_mm.max(axis=0))
小技巧:如果数据中有异常值,MinMaxScaler会把正常数据压缩到很小的区间。我一般先做异常值处理,再做归一化。或者直接用RobustScaler。
4.3 标准化 vs 归一化:怎么选?
很多同学问过我这个问题。我直接给个对比表:
| 对比维度 | 标准化(StandardScaler) | 归一化(MinMaxScaler) |
|---|---|---|
| 输出范围 | 无固定范围(理论±∞) | 固定[0,1] |
| 对异常值敏感度 | 高 | 高 |
| 保留分布形状 | 是 | 是 |
| 适用模型 | SVM、PCA、线性模型、神经网络 | KNN、K-Means、神经网络 |
| 数据要求 | 近似正态分布效果更佳 | 无特殊要求 |
| 新数据扩展性 | 好(用训练集的μ和σ) | 需记录训练集的min和max |
我的经验法则:不确定时,优先用标准化。因为很多模型(尤其是线性模型和神经网络)假设数据服从正态分布,标准化更符合这个假设。
4.4 对数变换
对数变换是处理长尾分布的利器。公式:x_log = log(x + c),其中c是常数,用于处理零值。
我在项目中遇到过很多右偏分布的数据——用户消费金额、网页停留时间、文章阅读量。这些数据直接喂给模型,效果往往很差。
import numpy as np
import matplotlib.pyplot as plt
# 模拟右偏分布数据(如用户消费金额)
np.random.seed(42)
data_skewed = np.random.exponential(scale=100, size=1000)
# 对数变换
data_log = np.log1p(data_skewed) # log1p = log(1+x)
print("原始数据偏度:", np.mean((data_skewed - np.mean(data_skewed))**3) / np.std(data_skewed)**3)
print("对数变换后偏度:", np.mean((data_log - np.mean(data_log))**3) / np.std(data_log)**3)
对数变换的效果:压缩大值,拉伸小值,让右偏分布更接近正态分布。偏度值越接近0,说明分布越对称。
注意:对数变换只能处理正数。如果数据包含0或负数,需要先做平移(如log1p)。我习惯用np.log1p(),它等价于log(1+x),能处理零值。
4.5 Box-Cox变换
Box-Cox变换是对数变换的升级版。它通过一个参数λ自动寻找最优变换方式:
Box-Cox公式:
y(λ) = (x^λ - 1) / λ, 当 λ ≠ 0
y(λ) = log(x), 当 λ = 0
说白了,Box-Cox在λ=0时就是对数变换,λ=1时是线性变换,λ=0.5时是平方根变换。它通过最大似然估计自动选择最优λ。
from scipy import stats
import numpy as np
# 模拟数据
data = np.random.exponential(scale=100, size=1000)
# Box-Cox变换(要求数据为正)
data_boxcox, lambda_opt = stats.boxcox(data)
print("最优λ值:", lambda_opt)
print("变换后偏度:", stats.skew(data_boxcox))
# 逆变换还原
data_recovered = stats.boxcox(data_boxcox, lambda_opt)
print("还原误差:", np.max(np.abs(data_recovered - data)))
实用建议:Box-Cox变换要求数据严格为正。如果数据包含零或负数,可以用Yeo-Johnson变换(Box-Cox的扩展,支持负数)。我在做信用评分模型时,就用Yeo-Johnson处理过负的信用额度变化值。
4.6 四种变换的对比与选择
| 变换方法 | 适用场景 | 数据要求 | 优点 | 缺点 |
|---|---|---|---|---|
| 标准化 | SVM、PCA、神经网络、线性模型 | 无特殊要求 | 保留分布形状,适合多数模型 | 对异常值敏感 |
| 归一化 | KNN、K-Means、神经网络 | 无特殊要求 | 固定输出范围,便于解释 | 受异常值影响大 |
| 对数变换 | 右偏分布、长尾数据 | 数据为正 | 简单有效,压缩大值 | 只能处理正数 |
| Box-Cox变换 | 需要自动寻找最优变换 | 数据为正 | 自动优化,比对数更灵活 | 计算成本高,数据必须为正 |
我的选择流程:
- 先看数据分布——如果偏度值大于1,考虑对数或Box-Cox
- 再看模型类型——树模型通常不需要变换,线性模型和神经网络需要
- 最后看异常值——如果异常值多,用RobustScaler替代标准化
记住:特征变换不是万能的。我见过太多人盲目做变换,结果模型反而变差。关键是要理解你的数据和模型,对症下药。
嗯,数值特征变换就讲到这里。四种方法各有千秋,没有银弹。多试、多对比、多思考,你慢慢就会形成自己的判断。