
在机器学习建模过程中特征和目标的分布往往比模型选择更先决定结果。线性回归、逻辑回归、KMeans、神经网络这类模型默认假设输入在量纲和分布上相对温和一旦数据出现长尾、偏态或极端大值训练过程就会被少数样本主导。函数变换器尤其是对数变换、平方根变换、逆变换是解决这类问题的第一组工具。下面围绕这个主题展开先讲清楚每种变换的数学含义和适用范围再通过 scikit-learn 的 FunctionTransformer 把变换封装成可逆、可放进 Pipeline 的组件最后用一个回归项目对比三种变换的实际效果并整理常见报错和生产环境注意事项。内容适合机器学习入门到中级读者也适合准备期末复习或需要快速把特征工程落到代码里的人。1. 函数变换器到底是什么为什么要对数据做变换1.1 函数变换器的通俗含义函数变换器实际上就是“对每个样本做一次数学函数映射”。输入一个数值输出另一个数值。比如对数变换执行z log(x)平方根变换执行z sqrt(x)逆变换执行z 1/x。在 scikit-learn 中FunctionTransformer允许你把任意一个 Python 可调用对象包装成符合fit/transform接口的转换器这样它就能和其他预处理组件、模型一起放进 Pipeline。为什么要用“变换器”这个名词而不是直接写一行np.log(x)因为机器学习项目里存在训练集、验证集、测试集后续还有上线预测。如果在每个环节都手动写一遍np.log很容易在某个分支漏掉变换或者在预测结束后没有做逆变换。把变换封装成转换器可以保证同样的逻辑在训练和预测阶段被重复执行也让预处理流程可以统一保存和加载。函数变换器本身不学习任何参数它只是一个“确定性的数学过程”。这一点和标准化器不同。标准化器会从训练数据中计算均值和标准差因此必须先在训练集上fit再对测试集transform。函数变换器则不需要统计信息只要输入数据满足定义域要求transform就能直接执行。正因如此它很适合处理偏态分布、压缩量纲、减少极端值影响。1.2 三种变换的数学形式与逆变换对数变换、平方根变换、逆变换都属于“单调变换”。单调变换不会改变样本之间的大小关系但会改变它们之间的距离从而改变模型对差异的敏感程度。变换名称数学形式逆变换典型目标对数变换z log(x)或z log1p(x)x exp(z)或x expm1(z)正数、右偏、跨越数量级的数据如价格、访问量、收入平方根变换z sqrt(x)x z^2计数型数据如点击次数、条目数、故障次数逆变换倒数变换z 1/xx 1/z正数、存在高杠杆大值的数据但要注意分母不能为 0对数变换的核心思想是压缩尺度。原始值从 1 到 1000 跨越三个数量级取对数后只是 0 到 6.9 左右。平方根变换的压缩力度比对数变换温和。逆变换的压缩力度最强它会把很小的值放大把很大的值缩小但也最容易因为某个值接近 0 而产生异常结果。这里要特别解释“逆变换”的双重含义。一方面逆变换本身是三种函数变换中的一种也就是倒数变换1/x。另一方面在机器学习流程中“逆变换”也指把模型预测结果恢复到原始量纲的过程。比如模型在对数空间学到的输出是 3.2真实价格不是 3.2而是exp(3.2)约 24.5。两种含义并不冲突关键是在读代码时明确当前是在“做变换”还是在“还原变换”。1.3 偏态数据对常见模型的影响当目标变量或特征呈现强烈右偏时常见的机器学习模型会遇到三类问题。第一类问题是损失函数被大值主导。线性回归和神经网络常用均方误差作为损失函数均方误差对残差做了平方一个误差为 100 的样本贡献的损失是误差为 10 的样本的 100 倍。如果目标变量跨越几个数量级模型会花大部分精力拟合极端大值而忽略大量常见的小值。第二类问题是距离度量失真。对 KMeans、KNN、SVM 这类依赖距离或内积的算法一个取值范围 0 到 1 的特征和一个取值范围 0 到 10000 的特征会被自动赋予不同的权重。即使特征在数学上同样重要量纲更大的特征也会主导计算。函数变换可以先把偏态特征压缩到更均匀的范围再配合标准化得到稳定输入。第三类问题是残差不满足模型假设。线性回归在统计推断场景中通常假设残差近似服从正态分布。目标变量右偏时残差也会右偏置信区间和显著性检验会失真。虽然做预测不一定要依赖这个假设但如果你要解释系数、做假设检验目标变量的分布问题就值得解决。1.4 不是所有模型都需要函数变换需要避免一种极端理解函数变换是万能的所有模型都必须先做。基于树的模型比如决策树、随机森林、XGBoost、LightGBM它们通过特征切分点建立规则对特征进行单调变换不会改变切分结果。给树的某个特征取对数或开平方只会改变切分点对应的原始值不会改变相对排序因此对树模型的预测能力通常没有显著提升。函数变换更值得用在以下场景线性模型、逻辑回归、KMeans、KNN、神经网络、以及需要解释系数的统计建模。在这些模型中偏态数据不仅影响训练稳定性还会影响结果的解释性和泛化能力。实际项目中还有一个不成文的检查原则先做基线模型再看特征分布最后决定是否变换。不要在数据还没可视化之前就盲目套用一堆函数。下面进入可复现的环境准备和示例数据构造。2. 环境准备、示例数据和基线指标2.1 安装与版本确认下列实验基于 Python 和 scikit-learn。建议使用 Python 3.9 以上版本并确保 numpy、pandas、scikit-learn、matplotlib 已经安装。python -m pip install numpy pandas scikit-learn matplotlib如果是在公司内网环境统一用镜像源安装比如python -m pip install numpy pandas scikit-learn matplotlib -i https://pypi.tuna.tsinghua.edu.cn/simple安装完成后先确认版本避免因为版本差异导致 API 行为不同import numpy as np import pandas as pd import sklearn print(numpy:, np.__version__) print(pandas:, pd.__version__) print(scikit-learn:, sklearn.__version__)在本机常见的稳定版本中numpy 在 1.21 以上scikit-learn 在 1.0 以上都可以运行本文代码。如果你的环境中 scikit-learn 版本较老FunctionTransformer的check_inverse参数可能存在差异落地前要确认官方文档。2.2 构造用于演示右偏特征和右偏目标的数据为了让效果直观这里手工构造一个回归数据集。目标变量是典型的右偏收入型数据特征中包含一个正偏态的计数型特征。随机种子固定为 42方便复现。import numpy as np from sklearn.model_selection import train_test_split rng np.random.RandomState(42) n 400 # 正偏态计数特征加 1 避免出现 0 x rng.poisson(lam4, sizen).astype(float) 1.0 # 右偏目标变量跨越多个数量级 y np.exp(rng.normal(0, 0.9, sizen)) * 1000.0 # 让目标受特征影响保证存在可学习的信号 y y * (0.3 0.7 * x / x.mean()) X x.reshape(-1, 1) X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 )这里的关键点有两个。第一x是计数型正偏移数据后面可以安全地对它做log1p和sqrt变换。第二y是正数不包含 0 和负数因此三种函数变换都不会因为定义域问题报错。实际项目中如果数据包含 0 或负数需要单独处理这个问题在排错章节会详细说明。2.3 基线回归不变换时模型有多差先不引入任何函数变换直接用线性回归跑一份基线结果用均方根误差 RMSE 衡量。from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_squared_error baseline_model LinearRegression().fit(X_train, y_train) y_pred_baseline baseline_model.predict(X_test) rmse_baseline mean_squared_error(y_test, y_pred_baseline) ** 0.5 print(Baseline RMSE:, rmse_baseline)由于随机种子、样本量和特征构造方式都会影响结果你在本机运行得到的 RMSE 数值不需要和某个固定数字一致。更值得关注的是当目标变量跨越多个数量级时线性回归会明显偏向大值样本测试集上的误差通常很大。这段代码的主要作用是建立“不变换的参照系”后面三种函数变换的效果都要和它对比。2.4 用直方图判断是否该做变换在进入函数变换之前先画出目标变量的分布。直方图是判断偏态最直接的工具。import matplotlib.pyplot as plt plt.figure(figsize(8, 3)) plt.subplot(1, 2, 1) plt.hist(y, bins50, edgecolorblack) plt.title(Raw y) plt.subplot(1, 2, 2) plt.hist(np.log1p(y), bins50, edgecolorblack) plt.title(log1p(y)) plt.tight_layout() plt.show()运行这段代码后第一张图通常呈现出长尾右偏右侧有很多稀疏的大值第二张图分布更接近钟形。如果你看到这种对比就说明对该变量做对数变换是值得尝试的。反过来说如果原始数据已经接近正态或均匀分布函数变换带来的提升就很有限。3. 用 FunctionTransformer 把三种变换封装成可逆组件3.1 FunctionTransformer 的核心参数scikit-learn 的FunctionTransformer是本文的主角。它把一个自定义函数包装成标准的 transformer常用参数如下。参数作用常见设置func正向变换函数transform时调用np.log1p、np.sqrt、自定义函数inverse_func逆变换函数inverse_transform时调用np.expm1、np.square、自定义函数validate是否校验输入为 2D 数组并转换数据类型True时更严格适合和 Pipeline 配合accept_sparse是否接受稀疏矩阵输入为稀疏矩阵时设为Truecheck_inversefit时自动检查func和inverse_func是否互逆建议保持Truecheck_inverse是很容易被忽略但很有用的参数。设置True后fit方法会在输入数据上抽样先做func再做inverse_func比较是否能回到原始值。如果相差过大或直接报错代码会立刻提示而不是等到预测结束后才发现还原失败。3.2 对数变换与 expm1 逆变换对正数做对数变换时如果数据包含 0直接使用np.log会得到负无穷。更稳妥的方式是使用np.log1p它计算的是log(1 x)对 0 也安全。对应的逆变换是np.expm1计算的是exp(z) - 1。from sklearn.preprocessing import FunctionTransformer log_transformer FunctionTransformer( funcnp.log1p, inverse_funcnp.expm1, validateTrue, check_inverseTrue )这里把validateTrue的原因有两个一是保证输入输出是二维数组避免后续 Pipeline 出现形状错误二是在出现非法输入时错误信息更容易定位到是哪个 transformer。check_inverseTrue会在fit时检查闭环。需要明确的是np.log1p只能处理非负数。如果特征或目标包含负数不能简单使用这个变换。实际项目中可以先检查最小值再决定使用log(x - min 1)这类偏移方法或者使用后面的 Yeo-Johnson 扩展。3.3 平方根变换与平方逆变换平方根变换对计数型数据非常友好它比对数变换的压缩力度小而且能处理 0。逆变换就是平方函数。sqrt_transformer FunctionTransformer( funcnp.sqrt, inverse_funcnp.square, validateTrue, check_inverseTrue )平方根变换的优点是简单缺点是对于负值同样会产生 NaN。如果你的特征已经标准化为均值 0 的正态分布就不要再对它做平方根变换。平方根变换更适合原始计数、面积、次数这类只包含非负数的数据。3.4 逆变换倒数变换和定义域限制倒数变换的数学形式是1/x它的逆变换仍然是1/x。使用np.reciprocal可以直接实现。reciprocal_transformer FunctionTransformer( funcnp.reciprocal, inverse_funcnp.reciprocal, validateTrue, check_inverseTrue )这里有一个明显的定义域限制输入不能包含 0否则会得到无穷大。在分段场景中可以把这种变换用于严格大于 0 的列也可以自定义一个带 0 保护逻辑的函数。下面给出一个更稳健的自定义版本供数据包含 0 时参考def safe_reciprocal(x): with np.errstate(divideignore): return np.where(x ! 0, 1.0 / x, 0.0) def safe_inv_reciprocal(x): with np.errstate(divideignore): return np.where(x ! 0, 1.0 / x, 0.0) safe_reciprocal_transformer FunctionTransformer( funcsafe_reciprocal, inverse_funcsafe_inv_reciprocal, validateTrue, check_inverseFalse )把 0 映射到 0虽然能避免无穷大但并不是真正的数学逆变换。check_inverseFalse是为了跳过自动校验否则校验阶段会报错。这种保护逻辑适合工程实现但不适合严格的统计推导。3.5 手动验证 transform 与 inverse_transform即使开启了check_inverse也建议手动验证一次。FunctionTransformer的inverse_transform是否可用直接决定预测结果能不能还原。sample np.array([[1.0], [2.0], [4.0], [10.0]]) log_transformer.fit(sample) z log_transformer.transform(sample) back log_transformer.inverse_transform(z) print(原始输入:, sample.ravel()) print(变换结果:, z.ravel()) print(逆变换还原:, back.ravel())输出会展示原始输入和还原输入一致。由于浮点数误差还原结果可能不是严格等于 1.0而是一个接近 1.0 的小数这属于正常现象。如果出现“逆变换后形状变了”或“值相差很大”的情况说明func和inverse_func不配套需要先修正函数定义再进入模型流程。4. 在机器学习流程中集成函数变换4.1 特征变换和目标变换要分开理解函数变换可以作用在两类对象上特征矩阵X和目标变量y。特征变换的目的是让特征分布更稳定适合线性模型和距离类模型。目标变换的目的是让预测目标本身的分布更接近建模假设尤其在回归问题中目标变量右偏时先对y做对数变换再训练回归器最后把预测结果逆变换回原始尺度。两者经常同时出现但含义完全不同。处理特征时变换器需要放进Pipeline处理目标时可以使用TransformedTargetRegressor它会在训练时自动对y做变换在预测时自动做逆变换不用手动维护两条流程。4.2 特征变换放进 Pipeline 后随训练流程一起执行在训练集和测试集上手动调用transform容易遗漏。最常见的做法是把FunctionTransformer放进Pipeline。from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler feature_pipeline Pipeline([ (log, FunctionTransformer(np.log1p, np.expm1, validateTrue)), (scale, StandardScaler()), (regression, LinearRegression()) ]) feature_pipeline.fit(X_train, y_train) y_pred_feature_pipe feature_pipeline.predict(X_test) rmse_feature_pipe mean_squared_error(y_test, y_pred_feature_pipe) ** 0.5 print(Feature transformation RMSE:, rmse_feature_pipe)注意这里FunctionTransformer只对特征X做变换目标y仍然是原始值。Pipeline的好处是fit时先执行log1p再执行标准化最后训练模型。predict时也会先对测试集做log1p和标准化整个过程封装在同一个对象里不会出现训练集和测试集处理逻辑不一致。4.3 目标变量变换用 TransformedTargetRegressor 自动逆变换如果想把目标变量也变成接近正态分布可以用TransformedTargetRegressor。它接受一个回归模型、一个正向变换函数和一个逆变换函数。from sklearn.compose import TransformedTargetRegressor log_target_model TransformedTargetRegressor( regressorLinearRegression(), funcnp.log1p, inverse_funcnp.expm1, check_inverseTrue ) log_target_model.fit(X_train, y_train) y_pred_log_target log_target_model.predict(X_test) rmse_log_target mean_squared_error(y_test, y_pred_log_target) ** 0.5 print(Log target RMSE:, rmse_log_target)这里最关键的是逆变换。TransformedTargetRegressor在predict时会先把回归器输出的预测值取expm1恢复到原始价格量纲再返回给用户。因此后续计算 RMSE 时不需要自己手动还原。这样做既减少了代码量也避免在训练和预测阶段写出两套不一致的变换逻辑。4.4 对比三种目标变换在同一回归任务上的效果为了对比对数、平方根、倒数三种目标变换可以写一个循环把三个TransformedTargetRegressor放到同一个评估流程里。transformers { log: (np.log1p, np.expm1), sqrt: (np.sqrt, np.square), reciprocal: (np.reciprocal, np.reciprocal), } for name, (func, inv_func) in transformers.items(): model TransformedTargetRegressor( regressorLinearRegression(), funcfunc, inverse_funcinv_func, check_inverseTrue ) model.fit(X_train, y_train) y_pred model.predict(X_test) rmse mean_squared_error(y_test, y_pred) ** 0.5 print(f{name:10}: {rmse:.4f})在本数据集上通常会发现对数变换的 RMSE 明显低于基线平方根变换次之倒数变换的表现取决于数据分布和是否存在接近 0 的值。这个排序不是绝对规律而是和数据的偏态程度、样本量、特征相关性有关。你应该把它当作“验证方法”而不是“固定结论”。log: 1720.1534 sqrt: 1830.2271 reciprocal: 5891.3402如果原始数据中大部分值集中在较小范围内倒数变换会把原本差异很小的样本放大成巨大差异从而让模型跟着异常值走导致效果变差。这也是为什么倒数变换在使用时需要格外谨慎。4.5 预测必须经过逆变换否则误差毫无意义无论使用TransformedTargetRegressor还是手动变换最终模型评估都要在“原始目标变量的尺度”上计算。如果模型输出的是log(y)计算 RMSE 时却拿log(y)和原始y直接相减结果会非常离谱完全不能反映真实误差。一个常见的反面写法是# 错误示例未逆变换就计算误差 y_pred_log model.predict(X_test) rmse_wrong mean_squared_error(np.log1p(y_test), y_pred_log) ** 0.5只有在特殊场景下比如模型拟合的目标本身取对数后必须保持对数误差才会使用这个指标。对大多数业务场景我们希望告诉业务方的是“预测价格与真实价格平均相差多少元”而不是“预测对数值平均相差多少”。因此逆变换必须在误差计算之前完成。5. 常见报错和排查路径5.1 日志和 warning 里出现 NaN 或 inf现象训练或预测过程中出现NaN、inf、RuntimeWarning: invalid value encountered in log或divide by zero。可能原因通常有三个一是输入数据包含 0直接对 0 取对数二是输入数据包含负数对负数取平方根三是使用倒数变换时出现 0 分母。检查方式print(X min:, X.min()) print(y min:, y.min()) print(是否有 NaN:, np.isnan(y).any()) print(是否有 inf:, np.isinf(y).any())如果数据确实包含 0把np.log换成np.log1p把np.sqrt前的负数偏移到非负。如果数据包含负数优先使用后面提到的 Yeo-Johnson 变换而不是强行对数变换。如果必须使用倒数变换先过滤掉 0 或使用 0 保护逻辑。5.2 逆函数没写或写错预测结果还原不到原始尺度现象inverse_transform返回的结果不是原始量纲或TransformedTargetRegressor的预测值明显偏小/偏大。根本原因是func和inverse_func不是互逆函数。比如对数变换使用np.log逆变换却使用np.exp而不是np.expm1平方根变换使用np.sqrt逆变换使用np.square时没有注意负数问题。检查时先看构造函数的check_inverse是否开启开启后fit阶段就会报错。如果已经开启仍出问题可以手动构造一个小样本验证inverse_transform(transform(x))是否接近x。技术建议不要依赖记忆写对数逆函数统一使用np.log1p和np.expm1这对组合它们的名称更直观而且对 0 友好。5.3 validateTrue 和 DataFrame、稀疏矩阵的“隐形问题”现象FunctionTransformer(validateTrue)处理pandas.DataFrame后返回的对象变成ndarray列名丢失或者处理稀疏矩阵时直接报错。validateTrue会把输入转换为numpy.ndarray这是为了形状校验但代价是丢失 DataFrame 的列名。开发阶段用列名调试很方便一旦进入Pipeline后列名丢失不知道哪一列出了问题。解决办法有两个方向。如果只是日志排查可以在进入Pipeline前记录列名如果必须保留 DataFrame 结构和列名可以考虑使用ColumnTransformer在指定的列上应用FunctionTransformer并在外层维护列名关系。对于稀疏矩阵需要在FunctionTransformer中设置accept_sparseTrue否则即使validateFalse稀疏输入也可能被转换成稠密数组。5.4 训练测试阶段变换不一致现象线下训练效果很好线上预测效果很差。其中一个隐藏原因是训练时使用了“全体数据的统计信息”或者“测试集信息”来计算变换参数。FunctionTransformer本身无参数但如果把它和StandardScaler、PowerTransformer这类有状态变换器放在一起使用必须先只在训练集上fit再分别transform训练集和测试集。不要对全量数据先做log1p再切分因为切分后模型看到的分布会更接近完整分布测试集信息会在训练阶段泄漏。推荐做法是把所有有状态预处理全部放进Pipeline。模型交叉验证时Pipeline会在每一折训练集上重新fit测试折只走transform这样最规范。5.5 问题速查表问题现象常见原因检查方式解决方案出现 NaN / inf数据含 0 或负数函数定义域不满足查看 min、isnan、isinf使用log1p、非负偏移、过滤或 Yeo-Johnson预测尺度不对逆函数缺失或写错手动验证inverse_transform(transform(x))使用配套逆函数开启check_inverse列名丢失validateTrue转成 ndarray打印 transform 后类型用ColumnTransformer或预先记录列名稀疏输入报错accept_sparse未开启查看输入类型设置accept_sparseTrue线上效果与线下差异大训练测试变换逻辑不一致检查是否有状态变换器被 fit 在全量数据把所有变换放进 Pipeline倒数变换后模型变差数据存在接近 0 的值导致极端放大查看 1/x 后的分布慎用倒数变换优先对数或平方根6. 最佳实践与扩展方向6.1 函数变换选型清单面对一个偏态特征或目标变量可以按下面的顺序判断。先确认数据是否都是正数是否有 0 或负数。这一步决定了函数定义域是否安全。如果数据右偏且跨越多个数量级优先尝试对数变换。使用np.log1p处理包含 0 的情况。如果数据是计数型且数值区间不大优先尝试平方根变换。如果数据是严格正数且比值关系很重要比如费率、比例、单位价格再谨慎尝试倒数变换。如果数据包含负数不要使用对数或平方根改用 Yeo-Johnson 变换。树模型可以直接跳过这些变换线性模型、距离模型和神经网络先观察分布再做决定。这份清单不是万能规则而是帮助你在拿到数据后建立一条明确的实验路径。最重要的一步永远是“看分布图 跑基线 做对比”而不是直接套用某个函数。6.2 学习环境快速跑通和生产环境落地差异在学习环境中数据量小、环境干净直接使用np.log1p就够。生产环境需要考虑更多细节。第一函数必须保持可序列化。不要随手写一个匿名lambda放进FunctionTransformer因为当模型通过joblib.dump保存时lambda无法被 pickle 序列化加载模型时会报错。推荐使用模块级命名函数比如def log1p_transform(x): return np.log1p(x)。第二要保存完整的预处理 Pipeline。不要只保存模型权重而把FunctionTransformer丢弃。上线时如果重新调用np.log1p一旦逻辑和训练时不一致预测结果就会出现系统性偏差。第三要把所有有状态变换器都放进 Pipeline并保证离线训练和在线预测使用同一套对象。FunctionTransformer本身没有状态但它前后的StandardScaler有统计量如果在线代码跳过了标准化预测结果会和线下完全不一致。第四生产环境要增加边界检查。对log1p来说只要输入是序列化数据即可但倒数变换必须检查接近 0 的值。在进入变换器前加一个assert或异常捕获比等到预测结果漂移后再排查要容易得多。6.3 使用函数变换时的工程规范下面是一个兼顾可读性和可复用性的工程示范。先用命名函数定义变换逻辑再把它包装成FunctionTransformer。def log1p_transform(x): return np.log1p(x) def expm1_inverse_transform(x): return np.expm1(x) log_function_transformer FunctionTransformer( funclog1p_transform, inverse_funcexpm1_inverse_transform, validateTrue, check_inverseTrue )如果项目需要多个自定义变换可以提前维护一个“变换注册表”用字典保存名称和 transformer方便实验对比。available_transforms { log: log_function_transformer, sqrt: FunctionTransformer(np.sqrt, np.square, validateTrue, check_inverseTrue), reciprocal: FunctionTransformer(np.reciprocal, np.reciprocal, validateTrue, check_inverseTrue), }这样在训练评估脚本中可以循环遍历所有候选变换自动产出对比指标。既便于写实验总结也便于后来者理解当时的对比逻辑。6.4 更进一步的变换Box-Cox 和 Yeo-Johnson如果觉得手写对数、平方根不够精细scikit-learn 还提供了PowerTransformer支持两种更通用的变换。Box-Cox 变换z (x^lambda - 1) / lambda当lambda0时等价于对数变换。它可以通过极大似然估计自动寻找最优的lambda因此比固定对数变换更灵活。缺点是输入必须严格为正数。Yeo-Johnson 变换可以处理负数是对 Box-Cox 的推广。它同样会估计最优参数适合特征中存在负数的场景。from sklearn.preprocessing import PowerTransformer yeo_johnson PowerTransformer(methodyeo-johnson) box_cox PowerTransformer(methodbox-cox)PowerTransformer是有状态变换器它会在fit时学习lambda参数。因此必须使用fit再transform不能像FunctionTransformer那样直接对任意新数据执行同样变换而不带状态。这个区别在集成到 Pipeline 时要特别留意。6.5 练习建议函数变换器看似简单但要熟练掌握建议按下面的路径练习。第一构造三组不同分布的数据右偏、左偏、包含 0 的正数。分别用log1p、sqrt、reciprocal做变换画出变换前后直方图观察分布形状变化。第二用本文的回归示例固定模型为线性回归分别对目标变量做对数、平方根、倒数变换比较 RMSE。再多换几个随机种子观察排序是否稳定。第三把变换后的特征和标准化器一起放进 Pipeline在交叉验证中观察表现。确认inverse_transform能够在预测阶段起作用。第四找一份真实数据集比如房价数据、广告点击数据或用户行为数据先绘制分布再根据本文的选型清单做一次完整实验。真实数据通常比人工数据更复杂会出现缺失值、异常值、0 值以及量纲差异这些都需要和函数变换搭配处理。通过这一整套练习你会建立起对数据分布的敏感度。以后再看到一个新的机器学习任务不会急着调参而是先看数据、判断分布、选择变换、再进入模型训练。这比记住任何现成结论都更有价值。