云计算百科
云计算领域专业知识百科平台

kaggle新手入门房价预测:Pytorch代码-超详细基础讲解,保证你看完也会!(数据处理与特征工程篇)

这个竞赛分两个部分讲解:一是数据处理与特征工程,二是网络搭建与训练

讲解代码分为3个步骤:有什么用,为什么需要他,如何使用

保证大家耐心看完一定大有裨益!如果有懂的可以跳过。

现在开始吧!

项目目标

我们的目标是根据房子的信息(如地段、面积等),预测房子的价格。这是一个典型的二元分类问题。现在我们开始吧,这是原版测试集和训练集的下载地址:

通过网盘分享的文件:home-data-for-ml-course 链接: https://pan.baidu.com/s/16UWI_WIFMYIEjUquLQJllg?pwd=6688 提取码: 6688 

第一步:先导入一些必要的库

这一段大家应该都知道,就是为了使用库里面的模块。

# ————————————————————————–
# 1. 导入所有需要的库
# ————————————————————————–
import pandas as pd
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import TensorDataset, DataLoader, Subset
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import KFold
import warnings
import os

# 忽略一些未来版本的警告,让输出更干净
warnings.filterwarnings('ignore', category=FutureWarning)

# ————————————————————————–

warnings:用于控制程序如何处理警告信息。代码中的 warnings.filterwarnings('ignore', category=FutureWarning) 意思是“请忽略掉那些关于未来版本特性变更的警告”,目的是让程序输出更干净,不受干扰。

第二步,数据加载与预处理

# 2. 数据加载与预处理
# ————————————————————————–
print("— 1. Loading and Preprocessing Data —")

# 加载数据
try:
train_df = pd.read_csv('train.csv')
test_df = pd.read_csv('test.csv')
except FileNotFoundError:
print("错误:请确保 train.csv 和 test.csv 文件在当前目录下。")
exit()

这段代码有什么用?

这段代码的核心作用是加载数据文件。

具体来说,它做了两件事:

读取数据:它尝试从你的电脑硬盘中读取名为 train.csv test.csv 的两个文件。

存入内存:将这两个文件中的表格数据,加载到程序内存里,并分别存储在 train_dftest_df 这两个变量中,以便后续进行分析和处理。

你可以把 train.csv 想象成是给模型的教科书和练习册(带有问题和答案),test.csv 则是最终考试的试卷(只有问题,没有答案)。这段代码就是把教科书和考卷发到模型手上。

此外,它还包含了一个安全检查机制:如果找不到这两个文件,程序不会崩溃报错,而是会提示你需要做什么,然后正常退出。

为什么用它?

一切分析始于数据:任何数据分析或机器学习项目的第一步,都是获取数据。没有数据,模型就是无源之水。所以,加载数据是必须的起始步骤。

使用Pandas是最优选择:CSV (Comma-Separated Values, 逗号分隔值) 是一种极其常见的存储表格数据的文件格式。而我们之前导入的 pandas 库,正是Python生态中处理这类表格数据的标准。它的 read_csv 函数功能强大、速度快,能轻松将CSV文件转换成它特有的、便于操作的 DataFrame 格式。

为什么要用 try…except 结构?

避免程序崩溃:如果直接写 train_df = pd.read_csv('train.csv') 而不加 try…except,一旦 train.csv 文件不存在或放错了位置,整个程序就会立即抛出一个 FileNotFoundError 异常并崩溃。这会显示一大堆红色的错误信息,对用户不友好。

提供清晰的指引:通过 try…except 捕获这个特定的错误,我们可以给用户一个清晰、人性化的提示(“错误:请确保 train.csv 和 test.csv 文件在当前目录下。”),然后使用 exit() 函数退出程序。这让脚本变得更可靠、更易于使用。

语法如何使用?

1.try: … except FileNotFoundError: …

这是一个异常处理结构,是Python中非常重要的一个概念。

try:关键字,它后面的代码块(这里是两行pd.read_csv)是尝试执行区。Python会正常尝试运行这里的代码。

except FileNotFoundError::关键字,它后面跟着一个具体的错误类型。如果 try 代码块在执行时恰好发生了 FileNotFoundError (文件未找到错误),那么Python会立刻停止执行 try 里的代码,转而执行 except 后面的代码块。如果 try 块里发生了其他类型的错误,这个 except 块是不会被触发的。

2.train_df = pd.read_csv('train.csv'):

train_df我们定义的一个变量名。按照惯例,用 _df 作为结尾,可以清晰地表明这个变量存储的是一个Pandas DataFrame。

pd.read_csv(…):调用我们之前导入的 pandas 库(别名为 pd)中的 read_csv 函数。

'train.csv'传递给 read_csv 函数的参数,它是一个字符串,代表要读取的文件名。因为这里只写了文件名而没有写完整的路径(比如 C:/Users/YourName/Documents/train.csv),Python会在当前运行脚本的那个目录下寻找这个文件。

3.print(…)exit()

print(…):在except块中,我们用它来打印自定义的错误提示信息。

exit():一个内置函数,调用它会立即终止整个程序的运行。

现在我们查看一下训练集和测试集:

可以发现训练集有很多缺失值,接下来我们处理一下他们 

首先我们先把ID列移除并且保持起来,因为ID对于训练来说毫无用处:

# 保存测试集的ID用于后续提交
test_ids = test_df['Id']

# 移除训练数据中的ID列
train_df = train_df.drop('Id', axis=1)
test_df = test_df.drop('Id', axis=1)

这段代码有什么用? 这段代码的核心作用是处理ID列——这是一个在数据集中没有预测价值,但对于提交结果又必不可少的列。

它主要完成了两件事:

备份测试集ID:将测试数据集 test_df 中的 Id 列单独保存到 test_ids 变量中。

清理训练集和测试集:从训练集 train_df 和测试集 test_df 中,都彻底移除 Id 这一列。

经过这番操作后,train_dftest_df 都不再含有Id列,只剩下与房价相关的特征(Features)以及(在训练集中还包含的)房价标签(Label)。而 test_ids 就像一个存根,以备后用。

为什么用它? 是特征工程(Feature Engineering)中的一个基本原则:

ID列没有预测能力:Id 列通常只是一个从1开始的顺序编号(1, 2, 3, …)。一栋房子的ID是5还是500,与它的最终售价没有任何关系。它不像“房屋面积”或“建造年份”那样,包含可以帮助模型判断房价的有效信息。

把这种无效信息喂给模型,不仅没用,有时会成为噪声,干扰模型的学习,可能导致模型性能下降。因此,在训练开始前,将它剔除。

Kaggle提交需要ID:既然ID没用,为什么不直接扔掉,还要备份 test_ids 呢?因为Kaggle竞赛平台在评判你的结果时,需要知道你预测的每一个房价分别对应的是哪一栋房子。你最终提交的 submission.csv 文件,通常需要包含两列:Id 和 SalePrice(你预测的房价)。所以,我们必须先把测试集的ID保存下来,等模型预测出所有房价后,再把 test_ids 和预测结果拼在一起,生成最终的提交文件。

保持数据结构一致:模型训练时用到的数据(train_df)有什么样的列,那么在预测时喂给它的数据(test_df)也必须有完全相同的列结构。既然我们从训练数据中删除了Id,那么也必须从测试数据中删除Id,以确保两者结构统一。

语法如何使用? 我们来详细看一下这两行代码的语法。

1.test_ids = test_df['Id'] 这个语法我们已经见过,它是从test_df这个DataFrame中选取名为'Id'的单列。

test_df['Id'] 的结果是一个Pandas Series(一维数据系列),然后通过 = 赋值给了新变量 test_ids

2.train_df = train_df.drop('Id', axis=1)

train_df.drop(…):调用train_df这个DataFrame的 .drop() 方法。这个方法的作用就是删除行或列。

'Id':这是传递给 .drop() 方法的第一个参数,表示我们想要删除的标签名(label)。在这里,就是列名'Id'。

axis=1:这是 .drop() 方法中极其重要的参数。

axis=0 代表行轴(上下方向)。

axis=1 代表列轴(左右方向)。

通过指定 axis=1,我们明确告诉Pandas:“我给你的标签名 'Id' 是一个列的名称,请你去删除这一整列。” 如果不写或者写成axis=0,Pandas会试图去寻找一个索引名叫'Id'的行,那通常会报错。

train_df = …:.drop() :

方法默认会返回一个新的、删除了指定列的DataFrame,而不会修改原始的 train_df。因此,我们必须用赋值语句 train_df = …,将这个删除了列的新DataFrame重新赋值给 train_df 变量,从而实现“更新” train_df 的效果。test_df.drop(…) 那一行同理。

接下来继续预处理:

# 处理异常值
train_df = train_df.drop(train_df[(train_df['GrLivArea']>4000) &
(train_df['SalePrice']<300000)].index)

# 从训练数据中分离目标变量 SalePrice
y = np.log1p(train_df['SalePrice'])
train_df = train_df.drop('SalePrice', axis=1)

# 合并训练集和测试集以便于统一处理
all_data = pd.concat([train_df, test_df], axis=0).reset_index(drop=True)

1.处理异常值

train_df = train_df.drop(train_df[(train_df['GrLivArea']>4000) & (train_df['SalePrice']<300000)].index)

这行代码的作用是识别并删除训练数据中的特定异常值(Outliers)。

具体来说,它会找到那些同时满足以下两个条件的房子(行),然后将它们从训练集 train_df 中删除:

地面以上居住面积 (GrLivArea) 大于 4000平方英尺。

并且 (&),房屋售价 (SalePrice) 低于 300,000美元。

这就像是在说:“找出那些房子大得离谱,但价格却便宜得不正常的样本,然后把它们扔掉。”

主要是这种过于夸张的样本会严重影响学习效果

为什么用它? 异常值会严重扭曲模型的学习。

为什么这些是异常值? 在艾姆斯房价这个数据集中,通常房屋面积越大,价格越高。通过数据可视化(比如画散点图),数据科学家发现有几个数据点严重偏离了这个趋势——它们的面积非常大,但价格却异常低。这可能是数据录入错误,或者是一些非常特殊的情况(比如房子状况极差)。

有什么危害? 模型(尤其是线性模型)在学习时,会尽力去拟合所有的数据点,包括这些异常点。为了照顾这些“不合群”的点,模型可能会被“带偏”,导致它对正常数据的预测能力下降。想象一下,为了让一条直线同时穿过一片密集的点云和远处一个孤立的点,这条直线势必会发生倾斜,从而无法很好地代表那片密集的点云。

为什么要删除? 删除这些被确认是异常或错误的数据点,可以让模型专注于学习数据中普遍存在的、更具代表性的规律,从而提高模型的泛化能力(对新数据的预测能力)。

语法如何使用? 这一行代码嵌套比较多,我们从里到外拆解:

train_df['GrLivArea'] > 4000: 这会产生一个布尔值,对于GrLivArea大于4000的行,值为True,否则为False。

train_df['SalePrice'] < 300000: 同理,这会产生另一个布尔值。

(…) & (…): & 符号是Pandas中用于对两个布尔进行与操作的。只有当两个条件在同一行都为True时,结果行才为True。

train_df[…]: 将上面得到的最终布尔Series放回train_df[…]中,这是一种布尔索引的方法。它会筛选出所有对应值为True的行。

.index: 在筛选出的这些异常行上调用.index,获取它们的索引标签(比如行号)。

train_df.drop(…): 最后,调用我们熟悉的.drop()方法,把这些索引对应的行从train_df中删除。这里默认就是按行删除(axis=0),所以可以省略。

2. 目标变量转换与分离 y = np.log1p(train_df['SalePrice']) train_df = train_df.drop('SalePrice', axis=1)

这段代码有什么用?

这两行代码完成了两项任务:

对数转换:它没有直接使用原始的SalePrice作为目标,而是对它进行了log1p转换,并将结果存储在变量 y 中。

分离标签:从train_df中移除了原始的SalePrice列,确保训练数据中只剩下特征。

为什么用它? 为什么要进行对数转换 (log1p)?

缓解数据偏态(Skewness):房价这类数据通常是右偏分布的,意味着大多数房子价格集中在较低的范围,而有少数豪宅价格极高,拖出一条长长的“右尾巴”。这种偏态分布对很多模型的性能不利。取对数可以有效地“压缩”数据尺度,特别是压缩高端值,使得转换后的数据分布更接近正态分布(钟形曲线)。许多模型(如线性回归)在目标变量服从正态分布时表现得更好。

稳定方差:对数转换还有助于使数据的方差更加稳定,不受数值大小的影响。

log1p vs log:log1p 计算的是 log(1 + x)。使用它而不是直接用np.log(x)是为了数值稳定性。如果某个x值非常小或者为0,log(x)会得到负无穷大或错误,而log(1 + x)则可以很好地处理这种情况。

为什么要分离标签?

这个理由和我们之前讨论过的一样:SalePrice是我们要预测的结果,不是特征。必须将其从特征数据train_df中分离出来,避免数据泄露。

语法如何使用? np.log1p(…): 调用Numpy库(别名为np)的 log1p 函数,对传入的Pandas Series(train_df['SalePrice'])中的每一个元素执行 log(1+x) 计算。

train_df.drop('SalePrice', axis=1): 我们已经很熟悉了,从train_df中删除名为'SalePrice'的列 (axis=1)。

3. 数据集合并

all_data = pd.concat([train_df, test_df], axis=0).reset_index(drop=True)

这段代码有什么用? 这一行将处理过的训练集(已移除异常值和SalePrice列)和测试集(已移除Id列)垂直拼接成一个大的数据集all_data。并且,它还重置了这个新数据集的索引。

为什么用它?

统一处理:目的和之前一样,将训练集和测试集的特征放在一起,是为了能对它们进行完全一致的后续处理(如填充缺失值、特征编码、特征缩放等)。

为什么要.reset_index(drop=True)

pd.concat 在拼接时,会保留原始的索引。比如,如果训练集有1460行(索引0-1459),测试集有1459行(索引0-1458),拼接后,all_data的索引就会是 [0, 1, …, 1459, 0, 1, …, 1458]。你会发现索引有大量重复。

这种重复的索引在后续操作中可能会导致意想不到的问题。

.reset_index() 会生成一套新的、从0开始连续递增的索引(0, 1, 2, …, 2917)。

参数 drop=True 的作用是丢弃旧的、混乱的索引,而不是把它当成一个新列保留下来。这是一个非常干净利落的操作。

语法如何使用? pd.concat([train_df, test_df], axis=0): 我们已经熟悉,按行 (axis=0) 拼接。

.reset_index(drop=True): 这是一个链式调用。pd.concat返回一个新的DataFrame,我们紧接着在这个返回的DataFrame上调用.reset_index()方法。

第三步,特征工程

我们现在进入了整个项目中技术含量最高、最能体现经验的部分——特征工程(Feature Engineering)。这段代码专注于处理机器学习中最常见也最棘手的问题之一:缺失值(Missing Values)。

这段代码的整体目标是:用合理的方式,有策略地填补数据中的空白格,让数据变得完整、干净。

# 3. 特征工程
# —————————————————

# 处理缺失值
for col in ('PoolQC', 'MiscFeature', 'Alley', 'Fence', 'FireplaceQu', 'GarageType',
'GarageFinish', 'GarageQual', 'GarageCond', 'BsmtQual', 'BsmtCond',
'BsmtExposure', 'BsmtFinType1', 'BsmtFinType2', 'MasVnrType'):
all_data[col] = all_data[col].fillna('None')

for col in ('GarageYrBlt', 'GarageArea', 'GarageCars', 'BsmtFinSF1', 'BsmtFinSF2',
'BsmtUnfSF','TotalBsmtSF', 'BsmtFullBath', 'BsmtHalfBath', 'MasVnrArea'):
all_data[col] = all_data[col].fillna(0)

all_data['LotFrontage'] = all_data.groupby('Neighborhood')['LotFrontage'].transform(
lambda x: x.fillna(x.median()))

for col in ('MSZoning', 'Electrical', 'KitchenQual', 'Exterior1st', 'Exterior2nd', 'SaleType', 'Functional'):
all_data[col] = all_data[col].fillna(all_data[col].mode()[0])

# 删除高缺失率的列
all_data = all_data.drop(['Utilities'], axis=1)

策略一:用"None"填充表示“没有”的类别

for col in ('PoolQC', 'MiscFeature', 'Alley', 'Fence', 'FireplaceQu', 'GarageType', 'GarageFinish', 'GarageQual', 'GarageCond', 'BsmtQual', 'BsmtCond', 'BsmtExposure', 'BsmtFinType1', 'BsmtFinType2', 'MasVnrType'):

all_data[col] = all_data[col].fillna('None')

它在做什么? 这段代码遍历一个包含多个类别型特征名称的列表。对于列表中的每一列,它都使用字符串'None'来填充该列中所有的缺失值(在Pandas中通常是NaN)。

为什么要这么做? 这里的核心思想是:缺失本身就是一种信息。

在这些特定的列中,一个缺失值(NaN)的含义并不是“数据未知”或“忘了填”,而是代表“这栋房子没有这个设施”。

PoolQC(游泳池质量)缺失,意味着没有游泳池。

GarageType(车库类型)缺失,意味着没有车库。

Alley(小巷通道)缺失,意味着没有小巷通道。

因此,用'None'这个新的类别来填充,是完全符合逻辑的。我们不仅填补了空白,还创造了一个有实际意义的新类别,模型可以从中学习到“没有某项设施”对房价的影响。

语法讲解 for col in (…): 一个标准的for循环,col变量在每次循环中会依次成为元组(…)中的一个列名字符串。

all_data[col]: 选取all_data这个DataFrame中名为col的整列。(all_data是之前我们合并的训练集与测试集)

.fillna('None'): 这是Pandas Series(列)的一个方法,它会找到所有NaN值,并用括号里提供的值(这里是字符串'None')来填充它们。

策略二:用 0 填充表示“没有”的数值

for col in ('GarageYrBlt', 'GarageArea', 'GarageCars', 'BsmtFinSF1', 'BsmtFinSF2',              'BsmtUnfSF','TotalBsmtSF', 'BsmtFullBath', 'BsmtHalfBath', 'MasVnrArea'):     all_data[col] = all_data[col].fillna(0)

它在做什么? 这段代码遍历一个数值型特征的列表,并将这些列中的所有缺失值填充为0。

为什么要这么做? 这个策略与上一个紧密相连。如果一栋房子没有车库(GarageType是'None'),那么它的车库面积(GarageArea)、车库容量(GarageCars)和车库建造年份(GarageYrBlt)自然就应该是0。同理,没有地下室的房子,其地下室相关的面积和浴室数量也应该是0。

所以,这里的0不是一个随意的填充值,而是逻辑推断出的真实数值。

策略三:用分组中位数填充

all_data['LotFrontage'] = all_data.groupby('Neighborhood')['LotFrontage'].transform(     lambda x: x.fillna(x.median()))

它在做什么? 这行代码用一种更智能的方式填充LotFrontage(与街道相连的线性英尺数)的缺失值。它不是用一个全局的统一值来填充,而是根据每栋房子所在的社区(Neighborhood),用该社区内所有房子的LotFrontage的中位数来填充。

为什么要这么做? 这体现了“具体问题具体分析”的思想。房子的临街宽度很可能与它所在的社区规划有关。同一个社区的房子,其临街宽度往往比较相似。

简单方法:用所有房子的LotFrontage的平均值或中位数来填充。这种方法太粗糙,可能会给一个高档社区的房子分配一个来自贫民区的中位数,不合理。

聪明方法:我们假设,一栋房子缺失的临街宽度,最可能接近它邻居们的普遍水平。这种基于分组的填充方式,使得填充值更具上下文相关性,也更精确。

语法讲解

这行链式调用是Pandas高级用法,我们把它拆开看:

all_data.groupby('Neighborhood'): 将整个数据集按照'Neighborhood'列的值进行分组。

['LotFrontage']: 在每个分组内部,选取'LotFrontage'这一列。

.transform(…): 它会对每个分组执行括号里的函数,然后返回一个和原始all_data一样长、索引也完全对齐的Series,可以直接赋值回去。

lambda x: x.fillna(x.median()): 这是一个匿名函数。

x在每次调用时,代表一个分组(即某个社区所有房子的LotFrontage值)。

x.median(): 计算这个分组(x)的中位数。

x.fillna(…): 用刚算出的这个中位数,来填充这个分组(x)内部的缺失值。

策略四:用众数填充常规类别

for col in ('MSZoning', 'Electrical', 'KitchenQual', 'Exterior1st', 'Exterior2nd', 'SaleType', 'Functional'):     all_data[col] = all_data[col].fillna(all_data[col].mode()[0])

它在做什么? 这段代码对另一些类别型特征的缺失值,使用该列的众数(mode,即出现次数最多的值)来进行填充。

为什么要这么做? 对于这些列,缺失值更可能是随机的数据录入遗漏。最保险、最合理的猜测是,这个缺失的值就是该列最常见的那个值。例如,如果MSZoning(区域分类)缺失了,而数据集中90%的房子都是'RL'(住宅低密度区),那么我们就有理由相信这个缺失值也应该是'RL'。

语法讲解 all_data[col].mode(): 计算col这一列的众数。注意,它返回的是一个Pandas Series,因为一列中可能存在多个众数(出现次数相同)。

[0]: 我们通过索引[0]来取第一个众数。即使只有一个众数,这个操作也能确保我们得到的是一个单一的值而不是一个Series,从而可以用来填充。

策略五:删除无用列

# 删除高缺失率的列 all_data = all_data.drop(['Utilities'], axis=1)

这行代码直接删除了'Utilities'(公共设施)这一整列。

 为什么要这么做?

通过数据探索可以发现,'Utilities'这一列几乎所有行的值都是一样的(比如都是'AllPub'),只有极少数几个例外。一个几乎没有变化的特征,对于模型区分不同房价来说,几乎提供不了任何有用的信息。留着它反而会增加计算的复杂性,所以直接删除是最好的选择。

新特征,为了更好的结果

这部分是特征工程的创新,它不再是修补数据(处理缺失值),而是创造全新的特征。

这整段代码的目标是:通过组合或变换现有的列,生成一些新的、对房价预测更有帮助的特征。

# 创新特征
all_data['TotalSF'] = all_data['TotalBsmtSF'] + all_data['1stFlrSF'] + all_data['2ndFlrSF']
all_data['TotalBath'] = (all_data['FullBath'] +
0.5 * all_data['HalfBath'] +
all_data['BsmtFullBath'] +
0.5 * all_data['BsmtHalfBath'])
all_data['Age'] = all_data['YrSold'] – all_data['YearBuilt']
all_data['RemodelAge'] = all_data['YrSold'] – all_data['YearRemodAdd']
all_data['HasPool'] = all_data['PoolArea'].apply(lambda x: 1 if x > 0 else 0)
all_data['TotalPorchSF'] = (all_data['OpenPorchSF'] +
all_data['EnclosedPorch'] +
all_data['3SsnPorch'] +
all_data['ScreenPorch'])

​​​​​​TotalSF:将地下室、一楼和二楼的面积相加,得到房屋的总居住面积。

TotalBath:将所有浴室(地上的、地下室的、全卫、半卫)的数量相加,得到一个加权的总浴室数。其中,半卫(HalfBath,通常指没有淋浴或浴缸的卫生间)按0.5个计算。

Age:用出售年份减去建造年份,得到房子在售出时的实际年龄。

RemodelAge:用出售年份减去最近一次翻新年份,得到距离上次翻新过了多少年。

HasPool:这是一个二元特征(Binary Feature)。它检查 PoolArea(泳池面积)列,如果面积大于0,就赋值为1(代表“有”);如果面积等于0,就赋值为0(代表“无”)。

TotalPorchSF:将四种不同类型的门廊/阳台面积全部相加,得到一个总的门廊面积。

语法讲解

all_data['PoolArea'].apply(lambda x: 1 if x > 0 else 0)

.apply() 是Pandas的一个非常有用的方法,它可以将一个函数应用到列中的每一个元素上。

lambda x: … 是一个匿名函数。这里的 x 依次代表 PoolArea 列中的每一个值。

1 if x > 0 else 0:这是一个三元表达式。如果 x > 0 这个条件成立,表达式的结果就是1;否则,结果就是0。

合起来,这行代码的意思就是:“遍历PoolArea列的每一个值,如果值大于0,就返回1,否则返回0”,从而生成了一个只包含0和1的新列。

第四步,偏态处理与独热编码

# 处理偏态数值特征
numeric_feats = all_data.dtypes[all_data.dtypes != "object"].index
skewed_feats = all_data[numeric_feats].apply(lambda x: x.skew()).sort_values(ascending=False)
skewness = skewed_feats[abs(skewed_feats) > 0.75]
print(f"\\nFound {len(skewness)} skewed numerical features for log transformation.")

for feat in skewness.index:
if all_data[feat].min() > 0:
all_data[feat] = np.log1p(all_data[feat])
else:
all_data[feat] = np.sign(all_data[feat]) * np.log1p(np.abs(all_data[feat]) + 1)

# One-Hot Encoding
all_data = pd.get_dummies(all_data, drop_first=True)
print(f"Feature engineering complete. Total features: {all_data.shape[1]}")

1.处理数值特征的偏态 (Handling Skew in Numerical Features)

# 处理偏态数值特征
numeric_feats = all_data.dtypes[all_data.dtypes != "object"].index
skewed_feats = all_data[numeric_feats].apply(lambda x: x.skew()).sort_values(ascending=False)
skewness = skewed_feats[abs(skewed_feats) > 0.75]
print(f"\\nFound {len(skewness)} skewed numerical features for log transformation.")

for feat in skewness.index:
if all_data[feat].min() >= 0: # Note: I adjusted this to >= 0 for robustness
all_data[feat] = np.log1p(all_data[feat])
# The 'else' part in your original code is less common; log1p on positive data is the main takeaway.

它在做什么?

这段代码分为两步:

诊断:找出所有数值型的特征,计算它们的偏态系数(skewness),然后筛选出那些偏态程度过高(绝对值大于0.75)的特征。

改变:对这些筛选出来的“歪”特征,应用对数转换(log transformation),来改变它们,使其分布更接近对称的正态分布(钟形曲线)。

为什么要这么做? 许多机器学习模型在数据分布更“乖”(即接近正态分布)时,表现会更好、更稳定。

偏态(Skewness) 是衡量数据分布不对称性的指标。一个严重右偏(有一个长长的右尾巴)的特征,意味着大部分数据点都挤在低数值区,而少数极高的值会不成比例地影响模型,像一个“坏学生”一样把整个模型的“注意力”都拉过去。

通过log转换,我们可以有效地“压缩”高端数值,把长长的尾巴缩短,让整个数据分布看起来更像一个对称的钟形。这有助于模型更好地学习到数据中的普遍规律,而不是被少数极端值带偏。我们之前对SalePrice做这个操作也是出于同样的原因。

语法讲解 numeric_feats = all_data.dtypes[all_data.dtypes != "object"].index

all_data.dtypes:获取每列的数据类型。

[all_data.dtypes != "object"]:一个布尔过滤器,只保留数据类型不等于"object"(即文本类型)的列。

.index:获取这些被选中的数值型列的列名。

skewed_feats = all_data[numeric_feats].apply

(lambda x: x.skew()).sort_values(ascending=False)

1.all_data[numeric_feats]: 仅选择所有数值列构成一个新的DataFrame。

2.apply(lambda x: x.skew()): 对每一列(x)应用.skew()函数来计算其偏态系数。

3.sort_values(ascending=False): 将计算出的偏态系数值从高到低排序。

skewness = skewed_feats[abs(skewed_feats) > 0.75]

abs(…): 取偏态系数的绝对值。

> 0.75: 筛选出那些偏态程度很高的特征(通常0.5-1之间算中度偏态,大于1算高度偏态,0.75是一个常用的阈值)。

for feat in skewness.index: …

遍历每一个被鉴定为“高度偏态”的特征名。

all_data[feat] = np.log1p(all_data[feat]):

用我们熟悉的log(1+x)函数来替换原始的列数据,完成“矫正”。

将类别特征转换为数值 (One-Hot Encoding)

# One-Hot Encoding
all_data = pd.get_dummies(all_data, drop_first=True)
print(f"Feature engineering complete. Total features: {all_data.shape[1]}")

:

它在做什么?

这行代码是预处理的临门一脚。它使用一种名为独热编码(One-Hot Encoding的技术,将数据集中所有剩余的文本类别特征(比如Neighborhood)转换成模型可以理解的0和1。

为什么要这么做? 核心思想:计算机模型只懂数字,不懂文字。

我们不能直接把“A区”、“B区”这样的文本喂给模型。一种简单的想法是把它们映射成A区->1, B区->2。但这样做会引入一个严重的问题:模型会误以为“B区 > A区”,即它们之间存在一种不存在的顺序和大小关系。

One-Hot编码解决了这个问题。假设一个Color列有三个类别['Red', 'Green', 'Blue'],它会这样做:

删除原始的Color列。

创建三个新的列:Color_Red, Color_Green, Color_Blue。

对于原来是Red的行,在新列Color_Red中记为1,其他两个记为0。

对于原来是Green的行,在新列Color_Green中记为1,其他两个记为0。

这样,每个类别都变成了一个独立的0/1特征,它们之间是平等的,没有大小之分。

语法讲解 pd.get_dummies(all_data, …): 这是Pandas中执行One-Hot编码的专用函数。它会自动找到DataFrame中所有object类型或其他类别型数据,并对它们进行转换。

drop_first=True: 这是一个非常重要的参数。以上面的Color例子来说,如果我们知道了Color_Red是0,Color_Green也是0,那我们就能100%推断出Color_Blue必然是1。这意味着其中一列是冗余的。drop_first=True会自动丢弃每个特征的第一个类别所对应的列,从而避免多重共线性(multicollinearity),这会让一些模型(特别是线性模型)运行得更稳定。

至此,整个all_data DataFrame已经被彻底“净化”和“改造”完毕。所有的特征都变成了纯数值类型,偏态特征得到了矫正,类别特征也通过One-Hot编码实现了数值化。

赞(0)
未经允许不得转载:网硕互联帮助中心 » kaggle新手入门房价预测:Pytorch代码-超详细基础讲解,保证你看完也会!(数据处理与特征工程篇)
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!