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

机器学习的项目,跟着网上的教程和AI的代码,也学了2个了,但是总不能都不是自己的,所以必须要基于代码做一些总结和深化。

这种项目的第一步,一般都是观察数据。有哪些字段、各字段的业务含义是什么、哪些有缺失值、各字段的取值是什么样的,就是所谓的know your business,这个就像我们干IT的,不懂业务肯定不行,连基本常识都缺乏是干不好项目的,但是这个暂时不是本文的讨论范围。

本文将从处理数据的第一步开始,也就是补全缺失值。先看下列代码。

python 复制代码
# 2.1 LotFrontage 按 Neighborhood(社区)分组的中位数进行缺失填补
# 原因:不同社区的临街长度分布不同,分组填补往往比全局中位数更合理
def impute_lotfrontage_by_neighborhood(df):
    df = df.copy()  # copy 防止原表被就地修改
    if "LotFrontage" in df.columns and "Neighborhood" in df.columns:
        # 对每条记录,取其所在 Neighborhood 的 LotFrontage 中位数
        med = df.groupby("Neighborhood")["LotFrontage"].transform("median")
        # 用该中位数填补缺失值;若个别社区全缺失,依然会留下 NaN(后续流程再兜底)
        df["LotFrontage"] = df["LotFrontage"].fillna(med)
    return df

对于数值类型的缺失来讲,我们不会直接给个0了事,常见的处理方式是给平均值。本文是房价预测,粗暴点的方式就是把所有房价取个平均赋给缺失字段。但是有更精细一点的做法。作为房价来讲,不同的街区房价一般都有差异,所以可以按街区取平均后赋给同街区缺失房价的数据,这样就更合理点,所以上述代码就是这样的精细的补全缺失值。

处理完缺失值,我们就进入了本项目最有价值的地方之一:根据现有字段构建强特征字段,这个也是促使我写下《增强篇》的直接原因。我们先看如下代码。

python 复制代码
# 2.2 常识型强特征构造
# 这些特征在房价题上非常常见且有效(面积合成、浴室总量/密度、门廊合并、房龄等)
def add_strong_features(df):
    df = df.copy()
    # 总面积:地上居住面积 + 地下室总面积(未完工面积也纳入)
    df["TotalSF"] = df.get("GrLivArea", 0) + df.get("TotalBsmtSF", 0)
    # 总"有效浴室数":半卫按 0.5 计入(地上、地下分别统计后合并)
    df["BathTotal"] = (df.get("FullBath", 0) + 0.5 * df.get("HalfBath", 0) +
                       df.get("BsmtFullBath", 0) + 0.5 * df.get("BsmtHalfBath", 0))
    # 浴室密度(每 1000 平方英尺的浴室数),刻画"是否拥挤/舒适"
    # replace(0, np.nan) 避免除以 0,/1000 仅做尺度调整
    df["BathPer1kSF"] = df["BathTotal"] / (df["TotalSF"].replace(0, np.nan) / 1000.0)
    # 门廊/露台面积合并:把多个门廊面积相加,降低稀疏性
    df["PorchSF"] = (df.get("OpenPorchSF", 0) + df.get("EnclosedPorch", 0) +
                     df.get("3SsnPorch", 0) + df.get("ScreenPorch", 0))
    # 房龄、翻新年距、是否"新房"(售出年等于建造年)
    if {"YrSold", "YearBuilt"}.issubset(df.columns):
        df["HouseAge"] = (df["YrSold"] - df["YearBuilt"]).clip(lower=0)  # clip 保证非负
    if {"YrSold", "YearRemodAdd"}.issubset(df.columns):
        df["SinceRemod"] = (df["YrSold"] - df["YearRemodAdd"]).clip(lower=0)
    if {"YrSold", "YearBuilt"}.issubset(df.columns):
        df["IsNew"] = (df["YrSold"] == df["YearBuilt"]).astype(int)
    # 三个"是否存在"类布尔变量,很多模型对"有/无"的信号很敏感
    df["HasGarage"] = (df.get("GarageArea", 0) > 0).astype(int)
    df["HasFireplace"] = (df.get("Fireplaces", 0) > 0).astype(int)
    df["HasPool"] = (df.get("PoolArea", 0) > 0).astype(int)
    return df
  • 可以看到,创造出来的第一个新字段是"总面积":
python 复制代码
    # 总面积:地上居住面积 + 地下室总面积(未完工面积也纳入)
    df["TotalSF"] = df.get("GrLivArea", 0) + df.get("TotalBsmtSF", 0)

我们平时讨论房子,说有120平大三房啥的,这120平就是房屋总面积,很少有人说我买了套客厅40平、主卧40平、客卧30平、卫生间10平的房子,因此,总面积似乎更有意义,也是影响房价的关键因素,为模型的学习减小了代价。

  • 创造出来的第二个新字段是"总有效浴室数":
python 复制代码
    # 总"有效浴室数":半卫按 0.5 计入(地上、地下分别统计后合并)
    df["BathTotal"] = (df.get("FullBath", 0) + 0.5 * df.get("HalfBath", 0) +
                       df.get("BsmtFullBath", 0) + 0.5 * df.get("BsmtHalfBath", 0))

有点类似总面积,总有效浴室数也是衡量房屋价值的一个关键因素,这个字段创造出来的原因也是因为其能统一浴室元素,简化参数,降低学习成本。

  • 生造出来的第三个新字段是"每1000平方英尺浴室数":
python 复制代码
    # 浴室密度(每 1000 平方英尺的浴室数),刻画"是否拥挤/舒适"
    # replace(0, np.nan) 避免除以 0,/1000 仅做尺度调整
    df["BathPer1kSF"] = df["BathTotal"] / (df["TotalSF"].replace(0, np.nan) / 1000.0)

光有浴室总数,但是和总面积相比,每1000平方英尺的浴室数量越大,很明显幸福指数就越高,这样就把浴室个数和总面积联系起来了,不是孤立看待,所以,这又是一个有意义的指标。

  • 创造的第四个字段是"门廊总面积":
python 复制代码
    # 门廊/露台面积合并:把多个门廊面积相加,降低稀疏性
    df["PorchSF"] = (df.get("OpenPorchSF", 0) + df.get("EnclosedPorch", 0) +
                     df.get("3SsnPorch", 0) + df.get("ScreenPorch", 0))

美国的房子,随着房子类型的不同,门廊类型也各不相同,每个种类的门廊不是每栋房屋都有,很多都是0,单独衡量门廊面积的话,可以看到大量的0存在。因此,创造门廊总面积的原因,除了和上面几个字段一样能统一指标降低复杂性,还有一个目的是降低稀疏性。

这里就牵扯到为什么稀疏性会影响模型,稀疏性的影响如下:

对机器学习模型的影响

  • 信息利用率低:大量零值使得模型难以学习有效模式
  • 维度诅咒:过多稀疏特征增加计算复杂度
  • 过拟合风险:稀疏特征容易在训练集上产生偶然相关性

对特征表达的影响

  • 噪声干扰:零值可能掩盖真实信号
  • 权重分散:同一概念被分散到多个特征中

因此,这种类型的字段,就需要合并起来,生成一个统一的新字段,减少稀疏性的影响。

  • 创造的第五个字段是"是否新房":
python 复制代码
    # 房龄、翻新年距、是否"新房"(售出年等于建造年)
    if {"YrSold", "YearBuilt"}.issubset(df.columns):
        df["HouseAge"] = (df["YrSold"] - df["YearBuilt"]).clip(lower=0)  # clip 保证非负
    if {"YrSold", "YearRemodAdd"}.issubset(df.columns):
        df["SinceRemod"] = (df["YrSold"] - df["YearRemodAdd"]).clip(lower=0)
    if {"YrSold", "YearBuilt"}.issubset(df.columns):
        df["IsNew"] = (df["YrSold"] == df["YearBuilt"]).astype(int)

这个就不难理解了,新房总是比二手房贵,这个是和房价非常相关的因素。"翻新年距"也即上次翻新到现在的年限,也是个影响房价的指标,看上去新的也比破破烂烂的值钱。

  • 创造的最后三个新字段就是"是否有车库 "、"是否有壁炉 "、"是否有游泳池":
python 复制代码
    # 三个"是否存在"类布尔变量,很多模型对"有/无"的信号很敏感
    df["HasGarage"] = (df.get("GarageArea", 0) > 0).astype(int)
    df["HasFireplace"] = (df.get("Fireplaces", 0) > 0).astype(int)
    df["HasPool"] = (df.get("PoolArea", 0) > 0).astype(int)

这个也不难理解,以上三个东西有没有,确实影响房价。

好的,到了这里,大家是不是有个疑问,为什么我们要生造这些新的字段?毕竟这些字段的值的来源,就是从原数据集中来的,模型为什么不能自己学习,而需要我们人类来告诉它呢?这个我们下篇文章再分析。

相关推荐
N 年 后2 小时前
cursor和传统idea的区别是什么?
java·人工智能·intellij-idea
AI Echoes2 小时前
LangChain 使用语义路由选择不同的Prompt模板
人工智能·python·langchain·prompt·agent
Wilber的技术分享2 小时前
【大模型实战笔记 6】Prompt Engineering 提示词工程
人工智能·笔记·llm·prompt·大语言模型·提示词工程
小高不会迪斯科2 小时前
大话大模型应用(二)--让大模型听话:Prompt Engineering&Context Engineering
人工智能·prompt
JJJJ_iii2 小时前
【机器学习16】连续状态空间、深度Q网络DQN、经验回放、探索与利用
人工智能·笔记·python·机器学习·强化学习
leafff1232 小时前
AI研究:大语言模型(LLMs)需要怎样的硬件算力
大数据·人工智能·语言模型
Wu Liuqi2 小时前
【大模型学习】第一章:自然语言处理(NLP)核心概念
人工智能·学习·自然语言处理·大模型·大模型转行
新智元2 小时前
全球十大AI杀入美股!最新战况曝光,第一名太意外
人工智能·openai
新智元3 小时前
ICML 2026史上最严新规:LLM不得列为作者,滥用AI直接退稿
人工智能·openai