机器学习实践项目(二)- 房价预测增强篇 - 特征工程四

好的,上一篇文章我们提到了独热编码(One-Hot)是一个非常妙的对于分类类型字段的处理方式,它把模型不能识别的字符型的类别字段,转换成了0/1为值的新列,既解决了模型不能识别分类值的问题,又不去"错误暗示"分类之间有大小关系。但是妙则妙矣,这么多0/1值,其实也挺难理解的,而且多了n个字段,其中只有一个字段是1,其余全部为0,有点傻傻的。

解决这个问题的方法之一就是分层目标编码。老规矩,解释知识让AI来:

「目标编码(Target Encoding)」的安全实现版本(带 OOF + 平滑)

我们来一步步拆解,让你完全理解它的作用、原理👇

🌟 一句话解释:

它用目标值(SalePrice)的平均水平来数值化一个类别特征,并且通过 K 折分层来避免"信息泄漏"。

换句话说:

这个函数的任务就是把一个类别型变量(如 Neighborhood)

转换成一个与房价相关的数值特征(类别对应的平均房价)

并保证这个过程不会在训练中"偷看答案"。


🧠 一、目标编码(Target Encoding)是啥?

假设我们有一个类别特征:

Neighborhood SalePrice
CollgCr 200000
CollgCr 220000
OldTown 120000
NridgHt 320000

传统的 One-Hot 会生成三列 0/1 矩阵,而目标编码更聪明:

用该类别的目标平均值代替它。

得到新特征:

Neighborhood SalePrice Neighborhood_te
CollgCr 200000 210000
CollgCr 220000 210000
OldTown 120000 120000
NridgHt 320000 320000

这就让模型直接"知道":某个社区的平均房价水平。

对于高基数特征(比如上百个类别),比 One-Hot 更高效、解释性更强。


🧩 二、问题:这样会"偷看答案"

如果我们直接用全量 y 算均值(比如上面 210000),

那模型在训练时已经看到了目标 SalePrice ------

这就是数据泄漏(data leakage)

会让交叉验证看起来分数高,但在真实预测中掉分。


🚧 三、解决方案:Out-Of-Fold(OOF)编码

这个函数做的就是 OOF Target Encoding

原理是:

1️⃣ 把训练集分成 n_splits(默认 5 折)。

2️⃣ 每次只用"训练折"的数据去计算类别均值。

3️⃣ 再把均值应用到"验证折"。

这样每一行的编码都来自模型未见过的折,就不会泄漏目标信息。


🔢 四、代码逻辑逐行解释

python 复制代码
y_log = np.log1p(y)
global_mean = y_log.mean()

👉 对房价取对数(Kaggle 房价比赛的标准做法),

在 log 空间计算类别平均价格,稳定性更高。
global_mean 是全局平均房价,用作平滑与缺失的兜底值。


python 复制代码
kf = KFold(n_splits=n_splits, shuffle=True, random_state=42)
te = pd.Series(index=X.index, dtype=float)

👉 初始化 5 折交叉验证 + 存放结果的空列。


python 复制代码
for tr_idx, va_idx in kf.split(X):
    tr, va = X.iloc[tr_idx], X.iloc[va_idx]
    y_tr = y_log.iloc[tr_idx]

👉 进入每一折:

  • tr 是"训练折";
  • va 是"验证折";
  • 只在训练折上计算编码,避免泄漏验证折的 y

python 复制代码
means = tr.groupby(col).apply(lambda d: y_tr[d.index].mean())
counts = tr[col].value_counts()

👉 在训练折中:

  • means 是每个类别的平均 log(房价);
  • counts 是每个类别出现次数(用于平滑)。

python 复制代码
enc = (means * counts + global_mean * smooth) / (counts + smooth)

👉 平滑公式

当类别样本数小(如只出现一次),直接取均值会不稳定;

通过加上全局均值 global_mean 按比例混合,稳定效果。
smooth 越大,越偏向全局均值。


python 复制代码
te.iloc[va_idx] = X.iloc[va_idx][col].map(enc).fillna(global_mean)

👉 把这一折的验证数据用训练折的编码映射;

未见过的类别用全局均值代替。

这样得到的 te 是一个与原训练集行数相同的新特征,

但它的每一行值都来自"别的折"的均值 → 完全避免泄漏。


🔮 五、测试集的处理

python 复制代码
means_full = X.groupby(col).apply(lambda d: y_log[d.index].mean())
counts_full = X[col].value_counts()
enc_full = (means_full*counts_full + global_mean*smooth) / (counts_full + smooth)
te_test = X_test[col].map(enc_full).fillna(global_mean)

👉 对测试集可以安全地用全量训练数据来算编码,因为测试集没有目标泄漏风险。


🧩 六、最后一步:加新列

python 复制代码
X[f"{col}_te"] = te.values
X_test[f"{col}_te"] = te_test.values
return X, X_test

这一步生成一个新的特征列,比如 Neighborhood_te

表示该样本所在社区的"平均 log 房价水平"。


✅ 七、效果总结

特征 转换前 转换后
Neighborhood CollgCr / OldTown / ... 数值(代表类别平均房价)
技术点 作用
log1p(y) 稳定波动,贴合 RMSE 评测
KFold OOF 避免目标泄漏
平滑 防止稀有类别导致过拟合
训练/测试分别编码 保证一致性与鲁棒性

💡 举个直观例子

假设:

Neighborhood SalePrice
A 100000
A 120000
B 250000
C 500000

全局均值(log1p 后)大约等于 log(242500)。

折分后:

  • 第1折训练 A、B → 验证 C → C_te = log(平均B/A价);
  • 第2折训练 A、C → 验证 B → B_te = log(平均A/C价)。

训练集每一行都没看自己的真实价格。

最终模型能利用这些 *_te 特征捕捉"地区价格等级",提升预测力。


🏁 八、总结一句话

oof_target_encode() 的作用是:

✅ 将类别特征数值化为"类别对应的平均目标值";

✅ 使用 K 折 OOF 机制防止信息泄漏;

✅ 用平滑控制稀有类别稳定性;

✅ 最终输出训练/测试都包含的安全编码特征。


其实看到这里,什么是"目标编码"就很好理解了,但是我第一次看这个解释时,并没有明白何为"分层",即K-Fold,那我们继续请教爱老师。


KFold(K 折交叉验证)其实一点都不复杂

我用生活比喻 + 图解的方式,帮你彻底搞懂它👇

🌰 一、直白解释:

KFold 就是"把训练集切成几块,轮流用一块来测试,其他几块来训练"的办法。

举个生活例子 🍰

你有一个蛋糕(数据集),要知道它的味道(模型好不好)。

如果你只尝一口,可能不准;

所以你把蛋糕切成 K 份(比如 5 份):

折数 用来训练的部分 用来验证的部分
第1折 第2~5份 第1份
第2折 第1,3,4,5份 第2份
第3折 第1,2,4,5份 第3份
第4折 第1,2,3,5份 第4份
第5折 第1~4份 第5份

然后你做 5 次实验,每次:

  • 在"训练部分"训练模型;
  • 用"验证部分"测试模型的表现。

最后,把这 5 次的表现(比如 RMSE)取平均值

就得到了一个更稳定的结果。


📊 二、图解理解(假设 K=5)

复制代码
数据集索引: [1 2 3 4 5 6 7 8 9 10]

第1折:  训练=[3,4,5,6,7,8,9,10], 验证=[1,2]
第2折:  训练=[1,2,5,6,7,8,9,10], 验证=[3,4]
第3折:  训练=[1,2,3,4,7,8,9,10], 验证=[5,6]
第4折:  训练=[1,2,3,4,5,6,9,10], 验证=[7,8]
第5折:  训练=[1,2,3,4,5,6,7,8],   验证=[9,10]

你看,每一折都会轮流当一次"验证集",

其他数据则都用来训练。

所以每个样本都被模型预测过一次 ,但不会参与预测自己。

这就能避免模型"作弊"看到答案。


💡 三、为什么要这么麻烦?

如果你只随机分一次训练/验证:

  • 运气好:分得刚好平衡 → 分数看起来很好;
  • 运气差:验证集偏、含噪声多 → 分数很差。

而 KFold 多次轮流验证,就像:

"我不看一次考试成绩,而看五次平均分。"

这样更稳定、更可靠。

它能告诉你模型整体表现,而不是"某一次偶然分割"的结果。


🧮 四、在 oof_target_encode 里的作用

回想我们在做"目标编码"时:

  • 我们要用 y(房价)去算类别均值;
  • 但又不能"偷看自己"。

KFold 就帮我们做到这点👇

用来计算均值的行(训练折) 被赋值的行(验证折)
1 折2~5 的数据 折1 的数据
2 折1,3,4,5 的数据 折2 的数据
... ... ...

所以对于每个样本,它得到的编码值,
都来自其它样本 ,而不是自己那一折的目标值。

这就完全避免了"信息泄漏"。

简单说:KFold 让我们"用别人家的答案算平均",

而不是"用自己的答案作弊"。


🧩 五、为什么叫 "Out-Of-Fold" (OOF)

  • "Fold" = 折。
  • "Out-Of-Fold" = 不在这一折里的数据。

即:

每个样本的预测/编码,来自于它不在的那几折 的模型或均值。

所以叫 Out-of-Fold 编码


🚀 六、类比理解

场景 类比
你考试 5 次,每次都换不同试卷(验证集)。 → KFold
老师不让你看自己卷子的答案,但可以看同学们的平均分来推测难度。 → Out-of-Fold Target Encoding
最后你拿 5 次考试的平均分作为期末成绩。 → 平均 RMSE 作为模型得分

🧠 七、总结一句话

KFold 就是反复切分数据,让模型在"看不到自己"的情况下多次验证,从而得到稳健、无泄漏的结果。


好了,那到此为止,大家明白什么是分层目标编码了吧?

相关推荐
gCode Teacher 格码致知2 小时前
Python教学基础:用Python和openpyxl结合Word模板域写入数据-由Deepseek产生
python·word
饼干,2 小时前
第5天python内容
开发语言·python
ZhengEnCi2 小时前
P3E-Python Lambda表达式完全指南-什么是匿名函数?为什么90%程序员都在用?怎么快速掌握函数式编程利器?
后端·python
Ace_31750887762 小时前
京东商品详情接口深度解析:从反爬绕过到数据结构化重构
数据结构·python·重构
尤利乌斯.X2 小时前
在Java中调用MATLAB函数的完整流程:从打包-jar-到服务器部署
java·服务器·python·matlab·ci/cd·jar·个人开发
听风吟丶2 小时前
Java 9 + 模块化系统实战:从 Jar 地狱到模块解耦的架构升级
开发语言·python·pycharm
飞哥数智坊2 小时前
TRAE SOLO 正式版上线,限时免费活动开启
人工智能·trae·solo
Danceful_YJ2 小时前
34.来自Transformers的双向编码器表示(BERT)
人工智能·深度学习·bert
love530love3 小时前
【笔记】xFormers版本与PyTorch、CUDA对应关系及正确安装方法详解
人工智能·pytorch·windows·笔记·python·深度学习·xformers