4. 数值特征变换:标准化、归一化、对数变换与Box-Cox变换

聊到数值特征,很多同学上来就扔进模型。我早期也这么干过,结果模型收敛慢得让人抓狂。后来才明白——不同尺度的数值特征,对模型来说就是鸡同鸭讲。

这一章,咱们就把数值特征变换的四种核心手段讲透。标准化、归一化、对数变换、Box-Cox,什么时候用、怎么用、坑在哪,我都会结合项目经验说清楚。

数值特征变换知识体系 数值特征变换 标准化 归一化 对数变换 Box-Cox变换 均值=0, 标准差=1 适合SVM、PCA、神经网络 对异常值敏感 缩放到[0,1]区间 适合KNN、神经网络 受异常值影响大 处理右偏分布 只能处理正数 压缩大值、拉伸小值 自动寻找最优变换 要求数据为正 比对数变换更灵活 核心原则:根据数据分布和模型类型选择变换方法 无万能方法,需结合业务场景和模型需求

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. 先看数据分布——如果偏度值大于1,考虑对数或Box-Cox
  2. 再看模型类型——树模型通常不需要变换,线性模型和神经网络需要
  3. 最后看异常值——如果异常值多,用RobustScaler替代标准化

记住:特征变换不是万能的。我见过太多人盲目做变换,结果模型反而变差。关键是要理解你的数据和模型,对症下药。

嗯,数值特征变换就讲到这里。四种方法各有千秋,没有银弹。多试、多对比、多思考,你慢慢就会形成自己的判断。