哈喽,大家好 ~
大家在整理数据的过程中,总是会发现为什么收入、房价、点击量这些数据,总有几个特别大的值?
其实很容易理解,因为现实数据经常不是均匀增长的。极少数大值会拉长数据尾巴,让模型更难学习。
今儿咱们来聊一种非常实用的数据变换方法:对数变换。你可以把它理解成一种“压缩大数、拉开小数”的处理方式,让数据更适合进入线性模型、神经网络和各种机器学习算法。
理解对数变换
假设有两组数据:
-
10 增长到 20,增加了 10; -
1000 增长到 1010,同样增加了 10。
从绝对数来看,它们增加得一样多。但在很多业务场景中,10 到 20 是翻倍,变化非常明显;1000 到 1010 却几乎没有变化。
对数变换关注的不是“增加了多少”,而是“增长了多少倍”。
最常见的形式是:
其中, 是原始数据, 是变换后的数据。对数的底数可以是 、10 或 2,机器学习中最常用自然对数。
即:原始数据相差很大,对数变换后会被压缩到更接近的范围。
解决什么问题?
很多真实数据都有右偏特征,例如用户收入、商品销量、网站访问量和房屋价格。大部分样本数值不大,但少量样本特别大。
这会带来两个问题:
第一,模型容易被极端大值牵着走。第二,数据的增长关系可能不是线性的,而是“越到后面增长越慢”。
例如房屋面积从 50 平方米增加到 100 平方米,影响可能很明显;从 500 平方米增加到 550 平方米,影响就没那么大。
如果特征中存在 0,可以使用:
代码中对应 np.log1p(x)。预测结果需要还原时,再使用:
这里的 1 是为了避免
没有定义。
完整案例
下面我们构造一组“房屋面积”和“房价”的数据。房屋面积服从偏斜分布,面积越大,房价增长越慢。
然后分别使用原始面积和对数面积训练线性回归模型。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score, mean_squared_error
np.random.seed(42)
# 偏斜的房屋面积,少数房子面积特别大
area = np.random.exponential(scale=80, size=500) + 20
price = 80 + 35 * np.log1p(area) + np.random.normal(0, 12, 500)
x_train, x_test, y_train, y_test = train_test_split(
area.reshape(-1, 1), price, test_size=0.25, random_state=42
)
model_raw = LinearRegression().fit(x_train, y_train)
x_log = np.log1p(area).reshape(-1, 1)
xl_train, xl_test, yl_train, yl_test = train_test_split(
x_log, price, test_size=0.25, random_state=42
)
model_log = LinearRegression().fit(xl_train, yl_train)
pred_raw = model_raw.predict(x_test)
pred_log = model_log.predict(xl_test)
print("原始面积 R2:", round(r2_score(y_test, pred_raw), 3))
print("对数面积 R2:", round(r2_score(yl_test, pred_log), 3))
fig, axes = plt.subplots(1, 2, figsize=(13, 5))
# 图1:原始特征与对数特征的拟合效果
idx = np.argsort(area)
axes[0].scatter(area, price, c=price, cmap="turbo",
alpha=0.7, edgecolors="white", linewidths=0.4)
axes[0].plot(area[idx], model_raw.predict(area[idx].reshape(-1, 1)),
color="#ff1744", linewidth=3, label="raw fit")
axes[0].plot(area[idx], model_log.predict(x_log[idx]),
color="#00c853", linewidth=3, label="log fit")
axes[0].set_title("Feature transformation comparison")
axes[0].set_xlabel("House area")
axes[0].set_ylabel("Price")
axes[0].legend()
# 图2:预测值与残差,观察模型在哪些区域出错
raw_res = y_test - pred_raw
log_res = yl_test - pred_log
axes[1].scatter(pred_raw, raw_res, c=x_test.ravel(),
cmap="plasma", alpha=0.65, label="raw residual")
axes[1].scatter(pred_log, log_res, c="#00b0ff",
alpha=0.45, label="log residual")
axes[1].axhline(0, color="black", linestyle="--")
axes[1].set_title("Residual analysis")
axes[1].set_xlabel("Predicted price")
axes[1].set_ylabel("Residual")
axes[1].legend()
plt.tight_layout()
plt.show()
原始面积 R2: 0.621
对数面积 R2: 0.81
第一张图中,蓝紫到黄色代表房价逐渐升高。红线是直接使用面积的线性拟合,绿线是使用对数面积后的拟合。由于真实关系本身接近“对数增长”,绿线通常会更贴近散点。
第二张图是残差分析。横轴是预测房价,纵轴是残差。好的模型会让点云围绕 0 随机分布,而且不同预测区间的波动比较接近。如果原始模型在大房屋区域出现明显偏离,而对数模型的残差更均匀,就说明对数变换缓解了极端值带来的影响。
注意点
对数变换不是所有数据都适合。
如果数据全部大于 0,可以使用 np.log(x);如果包含 0,优先使用 np.log1p(x)。如果包含负数,不能直接取对数,需要先结合业务含义做平移、拆分正负值,或者选择其他变换方法。
另外,对数变换改变了数据尺度,所以模型预测完成后,要记得使用 np.expm1() 还原结果。训练集和测试集也必须使用同一种变换,不能只转换其中一部分。
最后
对数变换的核心作用,是压缩极端大值、减弱右偏,让原本弯曲的增长关系更接近线性。它特别适合收入、销量、访问量、房价等“少数值特别大”的数据。
大家后面还可以继续尝试两个方向:一是比较 log1p 和 PowerTransformer 的效果;二是在随机森林、XGBoost 和神经网络中分别测试变换前后的模型表现。记住,最终不要只看
,还要结合残差图判断模型是否真的学得更稳定。
干货
最近准备了16大块的内容,124个算法问题总结,完整的机器学习小册,免费领取~

