1. 预处理
加载训练集和测试集:
# 加载数据
train_data = pd.read_csv('/kaggle/input/house-prices-advanced-regression-techniques/train.csv')
test_data = pd.read_csv('/kaggle/input/house-prices-advanced-regression-techniques/test.csv')
# 查看数据
print(train_data.head())
print(test_data.head())
pd.read_csv()
:这是Pandas库中用于读取CSV文件的方法。
-
参数是一个文件路径,指向存储数据的CSV文件。
- 该方法会将CSV文件加载为一个Pandas的DataFrame对象。
/kaggle/input/house-prices-advanced-regression-techniques/test.csv
是测试集的路径。
这些路径通常用于Kaggle竞赛中,/kaggle/input/
是Kaggle平台提供的数据存储路径。
head()
:这是Pandas DataFrame对象的一个方法,用于查看数据的前几行(默认前5行)。参数可以指定要查看的行数,例如 head(10)
会显示前10行。
数据查看与清洗
查看数据信息
# 查看数据基本信息
print(train_data.info())
print(train_data.describe())
info()
:这是Pandas库中DataFrame对象的一个方法,用于显示数据集的基本信息。
它会输出以下内容:
-
数据集的形状:包括行数和列数。
-
列名:数据集中的每一列的名称。
-
数据类型:每一列的数据类型(如整数
int
、浮点数float
、字符串object
等)。 -
非空值的数量:每列中非空值的个数,可以帮助你了解数据是否存在缺失值。
-
内存使用情况:数据集占用的内存大小。
describe()
:这是Pandas库中DataFrame对象的另一个方法,用于生成数据的统计描述,帮助了解数据的分布情况。
它会输出以下内容:
-
计数(count):每列的非空值数量。
-
平均值(mean):每列的平均值。
-
标准差(std):每列的标准差,表示数据的离散程度。
-
最小值(min):每列的最小值。
-
四分位数(25%、50%、75%):分别表示第1四分位数(25%)、中位数(50%)和第3四分位数(75%)。
-
最大值(max):每列的最大值。
检查缺失值
print(train_data.isnull().sum().sort_values(ascending=False))
-
isnull()
:这是Pandas DataFrame对象的一个方法,用于检测数据中的缺失值(NaN
)。它会返回一个布尔值的DataFrame,其中缺失值的位置为True
,非缺失值的位置为False
。 -
sum()
:对布尔值DataFrame按列求和。由于True
被视为1
,False
被视为0
,因此sum()
会计算每一列中缺失值的数量。 -
sort_values(ascending=False)
:将结果按缺失值数量降序排列,方便快速查看哪些列的缺失值最多。ascending=True
表示按照升序排序(从小到大)。
填充缺失值
经典错误
在填充缺失值之前,确保只选择训练集和测试集都存在的列。
train_data.fillna(train_data.median(), inplace=True)
test_data.fillna(test_data.median(), inplace=True)
fillna()
:这是 pandas
库中 DataFrame
类的一个方法,用于填充数据集中的缺失值(NaN
)。
median()
:这是 pandas
库中 DataFrame
类的一个方法,用于计算数据集中每一列的中位数。中位数的优点是它对异常值不敏感,比均值更稳健,因此常用于填充缺失值。median()
方法只能应用于数值型数据,而字符串类型,无法直接计算中位数。
inplace=True
:这是一个参数,表示直接在原数据集上修改,而不是返回一个新的数据集。
inplace=True
表示直接在原数据集上修改,不返回新的数据集。如果不设置 inplace=True
,则需要将结果赋值给一个新的变量,例如:
train_data = train_data.fillna(train_data.median())
test_data = test_data.fillna(test_data.median())
在填充缺失值之前,需要区分数据集中的数值型列和非数值型列,并分别处理。
情况一:训练集和测试集的列和列数相同
(1)只对数值型列填充中位数
选择数值型的列
# 法一:
numeric_columns = train_data.select_dtypes(include=['number']).columns
# 法二:
numeric_columns = train_data.select_dtypes(include=['int64', 'float64']).columns
select_dtypes(include=['number'])
-
含义:选择数据集中所有数值型列,包括整数型(
int
)、浮点型(float
)以及其他可以被视为数值的类型(如uint8
、float32
等)。 -
范围:
'number'
是一个更广泛的类别,它涵盖了所有可以进行数值运算的数据类型。
select_dtypes(include=['int64', 'float64'])
-
含义:仅选择数据集中数据类型为
int64
和float64
的列。 -
范围:
'int64'
和'float64'
是 Pandas 中常见的数值类型,但它们并不包括其他数值类型(如int32
、float32
、uint8
等)。
.columns
:
-
这个属性返回一个索引对象,包含所有被选中的数值型列的列名。
-
这些列名存储在变量
numeric_columns
中,后续将用于对这些列进行操作。
train_data.select_dtypes(include=['number'])
返回一个新的 DataFrame,该 DataFrame 只包含数值型列。
train_data.select_dtypes(include=['number']).columns
返回一个包含数值型列名的 Index 对象。
对数值型的列填充中位数
# 法一:
train_data[numeric_columns] = train_data[numeric_columns].fillna(train_data[numeric_columns].median())
test_data[numeric_columns] = test_data[numeric_columns].fillna(test_data[numeric_columns].median())
# 法二:inplace=True
train_data[numeric_columns].fillna(train_data[numeric_columns].median(), inplace=True)
test_data[numeric_columns].fillna(test_data[numeric_columns].median(),inplace=True
两者是否完全等价?
在大多数情况下,这两种写法是等价的,都会达到相同的结果:用中位数填充数值型列的缺失值。然而,它们在实现细节上略有不同:
第一种写法:通过赋值操作更新数据,可能会产生额外的内存开销。
第二种写法:直接在原数据上修改,更节省内存。
(2)对非数值型列(分类变量)填充其他值
可以使用以下方法填充缺失值:
-
众数填充:用该列出现频率最高的值填充缺失值。
-
固定值填充:用一个固定的值(如
'Unknown'
或'None'
)填充缺失值。
# 法一:
categorical_columns = train_data.select_dtypes(exclude=['number']).columns
# 法二:
categorical_cols = train_data.select_dtypes(include=['object']).columns
# 法一:
train_data[categorical_columns] = train_data[categorical_columns].fillna(train_data[categorical_columns].mode().iloc[0])
test_data[categorical_columns] = test_data[categorical_columns].fillna(test_data[categorical_columns].mode().iloc[0])
# 法二:
train_data[categorical_columns].fillna(train_data[categorical_columns].mode().iloc[0],inplace=True)
test_data[categorical_columns].fillna(test_data[categorical_columns].mode().iloc[0],inplace=True)
select_dtypes(exclude=['number'])
,这是 Pandas 提供的一个方法,用于选择数据集中不包含指定数据类型的列。也可以使用 include=['object'].columns
mode()
是 Pandas 提供的一个方法,用于计算每列的众数(出现频率最高的值)。如果某列有多个众数,mode()
会返回一个 DataFrame,其中每一列的众数作为值。
.iloc[0]
用于选择每列的第一个众数(即使某列有多个众数,也只取第一个)。
情况二:训练集和测试集的列和列数不同
在处理缺失值时,训练集和测试集的列除了 SalePrice
不一样,其他都一样,这是一个常见的情况。
是否需要删除训练集的 SalePrice
列?
(1) 在处理缺失值时
-
不需要删除
SalePrice
列:-
在数据清洗和特征工程阶段,
SalePrice
列是目标变量,应该保留。 -
缺失值处理通常只针对特征列(即输入变量),而不是目标变量。
-
(2) 在训练模型时
-
需要分离
SalePrice
列:-
在训练模型时,
SalePrice
是目标变量,需要从特征中分离出来。 -
特征(
X
)是输入,目标变量(y
)是输出。
-
为什么测试集需要处理缺失值?
(1) 模型的一致性
-
模型在训练时使用的特征(包括缺失值处理方式)必须与测试时一致。
-
如果测试集中存在缺失值,而模型在训练时没有处理缺失值,模型可能会崩溃或产生错误结果。
(2) 数据完整性
-
缺失值是数据中的常见问题,如果不处理,模型可能无法正常运行。
-
测试集中的缺失值需要以与训练集相同的方式处理,以确保数据的完整性。
正确代码:
# 分离目标变量
X_train = train_data.drop(columns=['SalePrice', 'Id']) # 移除目标变量和 'Id' 列
y_train = train_data['SalePrice']
X_test = test_data.drop(columns=['Id']) # 测试集不包含 'SalePrice'
# 处理数值型特征
numerical_cols = X_train.select_dtypes(include=['number']).columns
X_train[numerical_cols] = X_train[numerical_cols].fillna(X_train[numerical_cols].median())
X_test[numerical_cols] = X_test[numerical_cols].fillna(X_test[numerical_cols].median())
# 处理分类特征
categorical_cols = X_train.select_dtypes(exclude=['number']).columns
X_train[categorical_cols] = X_train[categorical_cols].fillna(X_train[categorical_cols].mode().iloc[0])
X_test[categorical_cols] = X_test[categorical_cols].fillna(X_train[categorical_cols].mode().iloc[0])
drop(columns=['SalePrice', 'Id'])
:这是pandas
库中的drop
方法,用于删除指定的列。
train_data['SalePrice']
:从训练数据集中提取目标变量SalePrice
列。
为什么删除 Id
列?
无关特征:Id
列通常是一个无关特征,对目标变量的预测没有帮助。
可能引入噪声:如果将 Id
列作为特征输入模型,可能会引入不必要的噪声,影响模型的性能。
独热编码问题:如果 Id
列被误认为是分类变量并进行独热编码,会导致特征维度大幅增加,这在实际应用中是不可取的。
分离目标变量的原因包括:
-
防止数据泄露:确保模型不会在训练过程中“看到”目标值。
-
特征工程的需要:避免目标变量被错误处理。
-
模型训练的需要:大多数算法需要明确区分输入特征和目标变量。
特征编码
在机器学习中,特征编码是将非数值型特征转换为数值型特征的过程。独热编码是特征编码中的一种常见方法,用于处理分类特征。它的作用是将分类特征转换为数值形式,以便模型能够理解和处理这些特征。
独热编码的作用:将分类特征转换为数值形式,机器学习模型通常只能处理数值型数据。独热编码将每个分类特征的类别转换为一个独立的二进制列(0 或 1),从而让模型能够理解这些特征。
代码如下:
# 对分类特征进行独热编码
X_train = pd.get_dummies(X_train, columns=categorical_cols, drop_first=True)
X_test = pd.get_dummies(X_test, columns=categorical_cols, drop_first=True)
# 确保训练集和测试集的列名一致
# 法一:
missing_cols = set(X_train.columns) - set(X_test.columns)
for col in missing_cols:
X_test[col] = 0
X_test = X_test[X_train.columns]
# 法二:
X_test = X_test.reindex(columns=X_train.columns, fill_value=0)
pd.get_dummies()
是 Pandas 提供的一个非常强大的函数,用于将分类特征转换为独热编码形式。
columns=categorical_cols
作用:指定需要进行独热编码的列。
drop_first=True
作用:在独热编码后,去掉第一个类别对应的列。
假设我们有以下数据集:
ID Color 1 Red 2 Blue 3 Green 4 Red
1. 不使用
drop_first=True
如果不使用
drop_first=True
,独热编码会为每个类别生成一个独立的列。结果如下:
ID Color Color_Red Color_Blue Color_Green 1 Red 1 0 0 2 Blue 0 1 0 3 Green 0 0 1 4 Red 1 0 0 解释:
每个类别(
Red
、Blue
、Green
)都对应一个独立的列。如果
Color
是Red
,则Color_Red
为 1,其他列(Color_Blue
和Color_Green
)为 0。如果
Color
是Blue
,则Color_Blue
为 1,其他列为 0。如果
Color
是Green
,则Color_Green
为 1,其他列为 0。
2. 使用
drop_first=True
当设置
drop_first=True
时,pd.get_dummies()
会去掉独热编码后的第一个类别列。具体来说:
如果类别是按字典顺序排列的(
Red
、Blue
、Green
),drop_first=True
会去掉第一个类别列(Color_Red
)。因此,结果中只保留
Color_Blue
和Color_Green
两列。最终结果如下:
ID Color Color_Blue Color_Green 1 Red 0 0 2 Blue 1 0 3 Green 0 1 4 Red 0 0 解释:
Color_Blue
:如果Color
是Blue
,则Color_Blue
为 1;否则为 0。
Color_Green
:如果Color
是Green
,则Color_Green
为 1;否则为 0。
Red
的表示:由于drop_first=True
去掉了Color_Red
列,Red
的表示可以通过Color_Blue
和Color_Green
都为 0 来推断。
3. 为什么使用
drop_first=True
?使用
drop_first=True
的主要目的是为了避免多重共线性。在独热编码中,所有类别列的和总是等于 1(即Color_Red + Color_Blue + Color_Green = 1
)。这种线性相关性会导致模型训练时出现数值不稳定问题,尤其是在线性回归等模型中。通过去掉第一个类别列(如
Color_Red
),我们可以用其他列来表示所有类别,同时避免这种线性相关性:
如果
Color_Blue = 0
且Color_Green = 0
,则Color
是Red
。如果
Color_Blue = 1
,则Color
是Blue
。如果
Color_Green = 1
,则Color
是Green
。不使用
drop_first=True
:生成所有类别的独热编码列(Color_Red
、Color_Blue
、Color_Green
)。使用
drop_first=True
:去掉第一个类别列(Color_Red
),只保留Color_Blue
和Color_Green
,从而避免多重共线性。
在进行独热编码之后,训练集和测试集的列名不一致是一个常见问题。
为什么训练集和测试集的列名会不一致?
独热编码的类别不一致:训练集和测试集中的分类特征可能包含不同的类别。例如,训练集中某个分类特征有类别 A
、B
、C
,而测试集中只有 A
和 B
。独热编码后,训练集会有列 C
,而测试集不会有列 C
。
法一:
set(X_train.columns)
:将训练集的列名转换为集合(set),集合是一个无序且不重复的元素集合。
set(X_test.columns)
:将测试集的列名转换为集合。
set(X_train.columns) - set(X_test.columns)
:计算两个集合的差集,找出训练集中有但测试集中没有的列名。这些列名存储在 missing_cols
中。
X_test[col] = 0 对于每个缺失的列名 col
,在测试集 X_test
中添加该列,并将所有值填充为 0。这样可以确保测试集包含训练集中所有的列,即使这些列在测试集中没有对应的样本。
X_test = X_test[X_train.columns] 这一步的作用是确保测试集的列顺序与训练集完全一致。
X_train.columns
是一个列名的列表,表示训练集的列顺序。
X_test[X_train.columns]
会按照训练集的列顺序重新排列测试集的列。如果测试集中有训练集中没有的列,这些列会被丢弃;如果测试集中缺少某些列,上一步已经通过填充 0 来处理。
法二:
reindex
是 Pandas 中的一个方法,用于重新排列数据框(DataFrame)的索引或列。它有两个主要功能:
-
重新排列列的顺序:按照指定的列名列表对数据框的列进行排序。
-
处理缺失的列:如果指定的列名在原数据框中不存在,
reindex
会自动创建这些列,并用指定的值填充。
columns=X_train.columns
-
这个参数告诉
reindex
方法,按照训练集X_train
的列顺序来重新排列测试集X_test
的列。 -
X_train.columns
是一个列名的列表,表示训练集的列顺序。
fill_value=0
-
这个参数指定了如何处理那些在测试集中不存在但在训练集中存在的列。
-
如果测试集中缺少某些列,
reindex
方法会自动创建这些列,并将它们的值填充为0
。
标准化数值特征
# 标准化数值特征
scaler = StandardScaler()
X_train[numerical_cols] = scaler.fit_transform(X_train[numerical_cols])
X_test[numerical_cols] = scaler.transform(X_test[numerical_cols])
StandardScaler
是 sklearn.preprocessing
模块中的一个类,用于对数据进行标准化处理。标准化的目的是将数据转换为均值为 0、标准差为 1 的分布。
scaler.fit_transform(X_train[numerical_cols])
-
fit_transform
方法:对训练集数据进行标准化处理。-
fit
部分:计算每个数值型特征列的均值 μ 和标准差 σ。 -
transform
部分:使用上述均值和标准差对数据进行标准化转换。
-
-
X_train[numerical_cols]
:选择训练集中所有数值型特征列,numerical_cols
是一个包含数值型特征列名的列表。 -
结果:
X_train[numerical_cols]
中的数值型特征被标准化为均值为 0、标准差为 1 的分布。
scaler.transform(X_test[numerical_cols])
-
transform
方法:对测试集数据进行标准化处理。-
使用之前在训练集上计算得到的均值 μ 和标准差 σ,对测试集中的数值型特征进行标准化。
-
-
X_test[numerical_cols]
:选择测试集中所有数值型特征列。 -
结果:
X_test[numerical_cols]
中的数值型特征被标准化为与训练集相同的分布。
为什么需要标准化数值特征?
标准化的核心目的是让所有特征在相同的尺度上,这样可以:
- 防止特征数值差异过大,避免某些特征主导模型学习。
- 加快梯度下降的收敛速度,提高模型训练效率。
- 提高模型的稳定性,避免数值过大导致的计算误差。
假设有两个特征:
房屋面积 (SquareFeet)
:数值范围 [500, 4000]房间数量 (Bedrooms)
:数值范围 [1, 10]如果不做标准化,
房屋面积
数值远大于房间数量
,在某些模型(如 线性回归、KNN、神经网络等)中,面积的影响会远远超过房间数量,导致模型偏向面积特征。标准化后:
房屋面积
转换为 均值 0,标准差 1 的数值房间数量
也转换为 均值 0,标准差 1- 这样两个特征对模型的贡献在同一数量级,不会被数值大小影响。
分类特征经过独热编码后不也是数值类型吗,为什么不需要进行标准化?
分类特征经过独热编码后确实变成了数值型数据,但它们与原始的数值型特征在性质上有所不同,因此通常不需要进行标准化。
2. 模型建模和交叉验证
使用XGBoost模型
# 初始化XGBoost模型
model = XGBRegressor(n_estimators=1000, learning_rate=0.05, max_depth=3, random_state=42)
这段代码的作用是使用 XGBoost 模型对训练数据进行建模,并通过 交叉验证 来评估模型的性能。
这行代码只是定义了模型的结构和参数,但并没有开始训练模型。
XGBRegressor
是 XGBoost 库中的回归模型类,用于解决回归问题。
n_estimators=1000
:表示模型将训练 1000 棵决策树。更多的树可能会提高模型性能,但也会增加计算成本。XGBoost 是一个基于梯度提升的集成学习算法,它通过逐步添加新的决策树来改进模型的性能。
为什么设置成 1000?
性能提升:增加决策树的数量可以提高模型的性能,因为更多的树可以捕捉到数据中更复杂的模式和关系。
平衡性能与计算成本:虽然更多的树可以提升性能,但同时也会显著增加计算成本和训练时间。选择 1000 棵树是一个折中的选择,既能获得较好的性能,又不会使训练过程过于漫长。
避免欠拟合:如果树的数量太少,模型可能会欠拟合,无法充分学习数据中的规律。1000 棵树通常能够提供足够的复杂度来拟合数据。
learning_rate=0.05
:学习率,控制每棵树对最终结果的贡献。较小的学习率会使模型学习得更慢,但可能更稳定。一般为较小的数值(如0.1或0.05)
max_depth=3
:每棵决策树的最大深度。限制树的深度可以防止过拟合。
random_state=42
:随机种子,确保每次运行代码时模型的初始化和训练过程是可复现的。
42 是一个常用的随机种子值,来源于《银河系漫游指南》中“生命、宇宙以及任何事情的终极答案”。它只是一个约定俗成的值,也可以选择其他整数值。
上述的参数为什么这么设置??
调整XGBoost模型参数的过程是一个迭代优化的过程,通常需要结合交叉验证、学习曲线分析以及对模型性能的评估来逐步调整。
每次调整后,使用交叉验证评估模型性能,逐步优化参数。
1. 调整
n_estimators
(决策树的数量)和learning_rate(学习率)
这两个参数是XGBoost中最关键的参数之一,它们相互影响。
方法:
固定一个参数,调整另一个:
先固定
learning_rate
为一个较小的值(如0.1或0.05),然后逐步增加n_estimators
(如从100增加到1000),观察模型的性能变化。如果发现增加
n_estimators
后,模型性能不再提升或提升非常缓慢,说明当前的learning_rate
可能过小,需要适当增大learning_rate
。学习曲线分析:
绘制学习曲线(训练集和验证集的性能随
n_estimators
增加的变化)。如果训练集性能很好但验证集性能较差,说明模型过拟合,可以减小learning_rate
并增加n_estimators
。如果训练集和验证集性能都很差,说明模型欠拟合,可以增加
learning_rate
或减少n_estimators
。2. 调整
max_depth
max_depth
控制每棵树的最大深度,影响模型的复杂度。方法:
从浅到深逐步调整:
先设置一个较小的
max_depth
(如3或4),观察模型性能。如果模型欠拟合(训练集和验证集性能都很差),可以逐步增加
max_depth
。如果模型过拟合(训练集性能很好但验证集性能差),可以减小
max_depth
。3. 调整正则化参数
XGBoost提供了多种正则化参数,如
gamma
、lambda
(reg_lambda
)和alpha
(reg_alpha
),用于控制模型的复杂度。方法:
gamma
:
控制叶子节点的分裂阈值。较大的
gamma
会减少叶子节点的数量,防止过拟合。可以从0开始逐步增加
gamma
,观察模型性能的变化。
lambda
和alpha
:
lambda
(L2正则化)和alpha
(L1正则化)用于控制模型的复杂度。可以通过网格搜索或随机搜索来调整这些参数。
交叉验证
# 使用交叉验证评估模型
kf = KFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(model, X_train, y_train, cv=kf, scoring='neg_mean_squared_error')
rmse_scores = np.sqrt(-scores)
print("交叉验证RMSE:", rmse_scores)
print("平均RMSE:", rmse_scores.mean())
KFold
是 Scikit-Learn 提供的一个类,用于实现交叉验证。
n_splits=5
:将数据集分成 5 份(5 折交叉验证)。这意味着数据会被分成 5 个子集,其中每个子集依次作为验证集,其余部分作为训练集。
为什么选择5折交叉验证??
折数越多,计算成本越高:折数越多,意味着模型需要训练和验证的次数越多。例如,10折交叉验证需要训练10次模型,而5折只需要训练5次。
5折交叉验证通常足够精确:5折交叉验证在大多数情况下能够提供较为准确的模型性能估计。它既不会因为折数太少而导致评估不准确,也不会因为折数太多而导致计算成本过高。
5折是常见的默认选择!
在5折交叉验证中,每个子集都会轮流作为验证集,而剩下的4个子集则作为训练集。
具体过程如下:
第1轮:
验证集:子集1(样本1到样本20)
训练集:子集2、子集3、子集4、子集5(样本21到样本100)
第2轮:
验证集:子集2(样本21到样本40)
训练集:子集1、子集3、子集4、子集5(样本1到样本20,样本41到样本100)
第3轮:
验证集:子集3(样本41到样本60)
训练集:子集1、子集2、子集4、子集5(样本1到样本40,样本61到样本100)
第4轮:
验证集:子集4(样本61到样本80)
训练集:子集1、子集2、子集3、子集5(样本1到样本60,样本81到样本100)
第5轮:
验证集:子集5(样本81到样本100)
训练集:子集1、子集2、子集3、子集4(样本1到样本80)
通过将数据分成多个子集,并轮流使用每个子集作为验证集,可以确保每个样本都被用于验证模型的性能,从而充分利用有限的数据。
这种方法特别适用于数据量较小的情况,因为它最大限度地利用了数据的多样性。
在5折交叉验证中,每次有80%的数据用于训练,20%用于验证。这种比例既保证了模型有足够的数据进行训练,又确保了验证集足够大,可以有效评估模型性能。
shuffle=True
:在分割数据之前打乱数据顺序。这一步非常重要,尤其是当数据是按某种顺序排列时(例如按时间顺序或类别顺序)。如果不打乱数据,可能会导致某些折的验证集与训练集的分布差异较大,从而影响模型评估的准确性。
random_state=42
:设置随机种子,确保每次运行代码时数据的打乱方式是相同的,从而保证结果的可复现性。
cross_val_score
是 Scikit-Learn 提供的一个函数,用于执行交叉验证并返回模型的性能指标。
model
:要评估的模型(在这里是 XGBoost 模型)。
X_train
和 y_train
:训练集的特征和目标变量。
cv=kf
:指定交叉验证策略,使用之前定义的 KFold
对象。
scoring='neg_mean_squared_error'
:指定评分指标为负均方误差。均方误差是回归任务中常用的性能指标,表示预测值与真实值之间的平方误差的平均值。
均方误差MSE
在Scikit-Learn中,
GridSearchCV
和cross_val_score
等函数默认是最大化评分指标的。然而,MSE是一个需要最小化的指标,因此Scikit-Learn通过取负值(neg_mean_squared_error
)来将其转换为一个最大化问题。
np.sqrt
:计算平方根。
-scores
:因为 cross_val_score
返回的是负均方误差(NMSE),所以需要取负值来还原为正的均方误差(MSE)。MSE 越小,模型的预测精度越高。
RMSE(均方根误差)是 MSE 的平方根,其单位与目标变量相同。相比 MSE,RMSE 更直观地反映了模型的预测误差大小,更容易解释和理解。
rmse_scores.mean()
:所有折的 RMSE 值的平均值,表示模型在训练集上的整体性能。
根据问题的上下文,判断RMSE的值是否在合理的范围内。
不同的问题和数据集可能有不同的RMSE量级。例如,对于房价预测问题,如果房价的范围是几万美元到几十万美元,那么平均RMSE在几千美元可能是一个合理的值。而对于股票价格预测,如果股票价格的范围是几美元到几百美元,那么平均RMSE在几美元可能就是合理的。
3. 训练模型
在整个训练集上重新训练模型
在之前的代码中,我们使用了交叉验证来评估模型的性能,但这并不意味着模型已经“训练完成”。交叉验证的目的是为了评估模型的泛化能力,而不是直接用于最终模型的训练。接下来的代码段则是为了在整个训练集上重新训练模型,以便用于最终的预测。
在交叉验证过程中,虽然模型被多次训练,但每次训练的模型仅用于评估性能,而不是用于实际预测。交叉验证的输出是模型在不同验证集上的性能指标(如RMSE),而不是一个可以直接用于预测的模型。
之前的代码训练了什么:
- 之前的代码通过交叉验证多次训练和验证模型,目的是评估模型的泛化能力。
- 每次训练的模型仅用于评估性能,而不是用于实际预测。
为什么需要在训练集上重新训练模型:
- 交叉验证的目的是评估模型性能,而不是产生最终模型。
- 最终模型需要在整个训练集上重新训练,以充分利用所有数据,从而在实际预测时表现更好。
# 在整个训练集上训练模型
model.fit(X_train, y_train)
# 在训练集上预测
y_pred_train = model.predict(X_train)
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
print("训练集RMSE:", train_rmse)
model.fit()
:这是模型训练的核心步骤。作用:使用整个训练集 X_train
和目标变量 y_train
来训练模型。这是生成最终模型的步骤,最终模型将用于对测试集或实际数据进行预测。
这行代码的作用是 训练模型,即让模型从训练数据(
X_train
和y_train
)中学习。在调用
fit
方法时,模型会根据输入的特征(X_train
)和目标变量(y_train
)调整内部参数,以最小化预测误差。只有在调用fit
方法后,模型才能用于预测。
model.predict()
:使用训练好的模型对训练集进行预测。生成模型对训练集的预测值 y_pred_train
。
-
评估拟合效果:通过比较预测值和实际值,可以了解模型在训练集上的表现。
-
检测过拟合:如果模型在训练集上表现很好,但在验证集或测试集上表现较差,可能表明模型过拟合了训练数据。
mean_squared_error
:计算均方误差(MSE),即预测值与实际值之间误差的平方的平均值。
np.sqrt
:计算均方误差的平方根,得到均方根误差(RMSE)。RMSE 的单位与目标变量相同,更直观地反映了预测误差的大小。
测试集预测
# 对测试集进行预测
y_pred_test = model.predict(X_test)
# 创建提交文件
submission = pd.DataFrame({'Id': test_data['Id'], 'SalePrice': y_pred_test})
submission.to_csv('submission.csv', index=False)
print("提交文件已生成: submission.csv")
这段代码的作用是在测试集上进行预测,并将预测结果保存为一个 CSV 文件,用于提交到 Kaggle 或其他竞赛平台。
model.predict(X_test)
:使用训练好的模型对测试集 X_test
进行预测。
y_pred_test
:预测结果,是一个数组,包含测试集中每个样本的预测值。
作用:生成测试集的预测结果,这些结果将用于评估模型在未见数据上的表现。
pd.DataFrame
:创建一个新的 Pandas 数据框。{'Id': test_data['Id'], 'SalePrice': y_pred_test}
:定义数据框的列。
作用:将测试集的预测结果整理成一个数据框,方便后续保存为 CSV 文件。
to_csv
:将数据框保存为 CSV 文件。
'submission.csv'
:保存的文件名。
index=False
:在保存的 CSV 文件中不包含行索引(index
)。通常在 Kaggle 提交时,文件中只需要包含 Id
和预测值两列。
4. 可视化分析
# 目标变量分布
plt.figure(figsize=(10, 6))
sns.histplot(y_train, kde=True)
plt.title('SalePrice Distribution')
plt.show()
这段代码的作用是可视化目标变量(SalePrice
)的分布情况。通过绘制直方图和核密度估计(KDE),可以直观地了解目标变量的分布特征。这对于数据探索和模型选择非常重要。
核密度估计的核心思想是通过在每个数据点周围放置一个“核”(即一个平滑的曲线),然后将这些核叠加起来,形成一个平滑的概率密度曲线。
plt.figure
:创建一个新的图形窗口。
figsize=(10, 6)
:设置图形的大小为宽度 10 英寸,高度 6 英寸。这使得图形在显示时更加清晰,适合展示细节。
sns.histplot
:Seaborn 库中的函数,用于绘制直方图。
kde=True
:在直方图的基础上绘制核密度估计。KDE 是一种非参数方法,用于估计数据的概率密度函数,可以帮助我们更平滑地观察数据的分布。显示一条曲线!
plt.title
:为图形添加标题。'SalePrice Distribution'
:标题内容,表示图形展示的是目标变量(SalePrice
)的分布情况。
plt.show()
:显示图形。如果没有这行代码,图形不会在屏幕上显示。
为什么这么做?
了解目标变量的分布:
- 目标变量(
SalePrice
)的分布情况对于模型选择和特征工程非常重要。 - 例如:如果目标变量的分布偏斜,可能需要进行数据转换(如对数变换)以改善模型性能。
- 如果目标变量的分布接近正态分布,线性模型可能表现较好。
检测异常值:通过观察直方图和核密度估计,可以直观地发现目标变量中的异常值或极端值。
特征重要性
# 特征重要性
feature_importance = pd.DataFrame({'Feature': X_train.columns, 'Importance': model.feature_importances_})
feature_importance = feature_importance.sort_values(by='Importance', ascending=False)
plt.figure(figsize=(12, 8))
sns.barplot(x='Importance', y='Feature', data=feature_importance.head(20))
plt.title('Top 20 Feature Importance')
plt.show()
这段代码的作用是提取和可视化模型的特征重要性,帮助你了解哪些特征对模型的预测贡献最大。
特征重要性可以帮助你了解哪些特征对模型的预测贡献最大。这有助于解释模型的行为,选择关键特征,甚至进行特征工程。
通过可视化特征重要性,可以识别出对模型贡献较小的特征,从而考虑删除这些特征以简化模型。
pd.DataFrame
:创建一个新的 Pandas 数据框。{'Feature': X_train.columns, 'Importance': model.feature_importances_}
:定义数据框的列。
Feature
:特征名称,从 X_train.columns
获取。
Importance
:特征重要性,从模型的 feature_importances_
属性获取。model.feature_importances_
:这是 XGBoost 模型的一个属性,返回每个特征的重要性分数。这些分数表示特征对模型预测的贡献程度。
sort_values(by='Importance', ascending=False)
:按 Importance
列的值降序排序。
作用:将特征按重要性从高到低排序,便于后续可视化和分析。
plt.figure
:创建一个新的图形窗口。figsize=(12, 8)
:设置图形的大小为宽度 12 英寸,高度 8 英寸。这使得图形在显示时更加清晰,适合展示细节。
sns.barplot
:Seaborn 库中的函数,用于绘制条形图。
x='Importance'
:条形图的 x 轴表示特征重要性。
y='Feature'
:条形图的 y 轴表示特征名称。
data=feature_importance.head(20)
:使用排序后的特征重要性数据框的前 20 行,即显示最重要的 20 个特征。
5. 完整代码
# This Python 3 environment comes with many helpful analytics libraries installed
# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python
# For example, here's several helpful packages to load
import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
# Input data files are available in the read-only "../input/" directory
# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory
import os
for dirname, _, filenames in os.walk('/kaggle/input'):
for filename in filenames:
print(os.path.join(dirname, filename))
# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using "Save & Run All"
# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split, cross_val_score, KFold
from sklearn.metrics import mean_squared_error
from sklearn.preprocessing import StandardScaler
from xgboost import XGBRegressor
import matplotlib.pyplot as plt
import seaborn as sns
# 加载数据
train_data = pd.read_csv('/kaggle/input/house-prices-advanced-regression-techniques/train.csv') # 训练集
test_data = pd.read_csv('/kaggle/input/house-prices-advanced-regression-techniques/test.csv') # 测试集
print(train_data.isnull().sum().sort_values(ascending=False))
# 分离目标变量
X_train = train_data.drop(columns=['SalePrice', 'Id']) # 移除目标变量和 'Id' 列
y_train = train_data['SalePrice']
X_test = test_data.drop(columns=['Id']) # 测试集不包含 'SalePrice'
# 处理数值型特征
numerical_cols = X_train.select_dtypes(include=['number']).columns
X_train[numerical_cols] = X_train[numerical_cols].fillna(X_train[numerical_cols].median())
X_test[numerical_cols] = X_test[numerical_cols].fillna(X_test[numerical_cols].median())
# 处理分类特征(也就是处理非数值型特征)
categorical_cols = X_train.select_dtypes(exclude=['number']).columns
X_train[categorical_cols] = X_train[categorical_cols].fillna(X_train[categorical_cols].mode().iloc[0])
X_test[categorical_cols] = X_test[categorical_cols].fillna(X_train[categorical_cols].mode().iloc[0])
# 检查分类特征列是否存在
missing_cols_in_train = set(categorical_cols) - set(X_train.columns)
missing_cols_in_test = set(categorical_cols) - set(X_test.columns)
print("训练集中缺失的列:", missing_cols_in_train)
print("测试集中缺失的列:", missing_cols_in_test)
# 对分类特征进行独热编码
X_train = pd.get_dummies(X_train, columns=categorical_cols, drop_first=True)
X_test = pd.get_dummies(X_test, columns=categorical_cols, drop_first=True)
# 1. 确保训练集和测试集的列名一致
missing_cols = set(X_train.columns) - set(X_test.columns)
for col in missing_cols:
X_test[col] = 0
X_test = X_test[X_train.columns]
# 2. 确保训练集和测试集的列名一致
X_test = X_test.reindex(columns=X_train.columns, fill_value=0)
# 标准化数值特征
scaler = StandardScaler()
X_train[numerical_cols] = scaler.fit_transform(X_train[numerical_cols])
X_test[numerical_cols] = scaler.transform(X_test[numerical_cols])
print("\n====================XGBoost训练=======================\n")
# 初始化XGBoost模型
# model = XGBRegressor(n_estimators=500, learning_rate=0.05, max_depth=2, random_state=42)
# 调整后的XGBoost参数
model = XGBRegressor(
n_estimators=100, # 进一步减少树的数量
learning_rate=0.05,
max_depth=1, # 保持较低的树深度
gamma=2.0, # 增加分裂条件的严格性
reg_alpha=3.0, # 增加 L1 正则化
reg_lambda=3.0, # 增加 L2 正则化
random_state=42
)
# 使用交叉验证评估模型
kf = KFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(model, X_train, y_train, cv=kf, scoring='neg_mean_squared_error')
rmse_scores = np.sqrt(-scores)
print("交叉验证RMSE:", rmse_scores)
print("平均RMSE:", rmse_scores.mean())
print("\n=====================整个训练集上训练=======================\n")
# 在整个训练集上训练模型
model.fit(X_train, y_train)
# 在训练集上预测
y_pred_train = model.predict(X_train)
train_rmse = np.sqrt(mean_squared_error(y_train, y_pred_train))
print("训练集RMSE:", train_rmse)
print("\n=====================对测试集进行预测=======================\n")
# 对测试集进行预测
y_pred_test = model.predict(X_test)
# 创建提交文件
submission = pd.DataFrame({'Id': test_data['Id'], 'SalePrice': y_pred_test})
submission.to_csv('submission.csv', index=False)
print("提交文件已生成: submission.csv")
print("\n=====================可视化分析=======================\n")
# 目标变量分布
plt.figure(figsize=(10, 6))
sns.histplot(y_train, kde=True)
plt.title('SalePrice Distribution')
plt.show()
# 特征重要性
feature_importance = pd.DataFrame({'Feature': X_train.columns, 'Importance': model.feature_importances_})
feature_importance = feature_importance.sort_values(by='Importance', ascending=False)
plt.figure(figsize=(12, 8))
sns.barplot(x='Importance', y='Feature', data=feature_importance.head(20))
plt.title('Top 20 Feature Importance')
plt.show()
print("\nVICTORY!!!\n")