kaggle比赛入门 - House Prices - Advanced Regression Techniques(第四部分)
本文承接上一篇。
1. 特征工程
from sklearn.preprocessing import FunctionTransformer
# feature engineering functions
def custom_features(df):
df_out = df.copy()
df_out['PropertyAge'] = df_out['YrSold'] - df_out['YearBuilt']
df_out['TotalSF'] = df_out['TotalBsmtSF'] + df_out['1stFlrSF'] + df_out['2ndFlrSF']
df_out['TotalBath'] = df_out['FullBath'] + 0.5*df_out['HalfBath'] + df_out['BsmtFullBath'] + 0.5*df_out['BsmtHalfBath']
df_out['HasRemodeled'] = (df_out['YearRemodAdd'] != df_out['YearBuilt']).astype(object)
df_out['Has2ndFloor'] = (df_out['2ndFlrSF'] > 0).astype(object)
df_out['HasGarage'] = (df_out['GarageArea'] > 0).astype(object)
df_out['YrSold_cat'] = df_out['YrSold'].astype(object)
df_out['MoSold_cat'] = df_out['MoSold'].astype(object)
df_out['YearBuilt_cat'] = df_out['YearBuilt'].astype(object)
df_out['MSSubClass_cat'] = df_out['MSSubClass'].astype(object)
return df_out
feature_engineering_transformer = FunctionTransformer(custom_features)
这段代码的目的是通过自定义函数 custom_features
来完成特征工程,并使用 FunctionTransformer
将其转换为适合机器学习流水线的步骤。
代码逐步解释
1. 导入必要模块
from sklearn.preprocessing import FunctionTransformer
FunctionTransformer
:- 用于将任意自定义函数转换为适合
Pipeline
或特征转换器的形式。 - 它的主要用途是将自定义的数据转换逻辑嵌入到机器学习工作流中。
- 用于将任意自定义函数转换为适合
2. 定义自定义特征工程函数
def custom_features(df):
df_out = df.copy()
...
return df_out
- 输入:
df
:传入的特征数据,通常是 PandasDataFrame
。
- 逻辑:
- 在输入数据的基础上添加或修改新特征,以增强模型的表达能力。
- 输出:
- 返回一个经过特征工程处理后的
DataFrame
。
- 返回一个经过特征工程处理后的
具体操作:
-
复制数据:
df_out = df.copy()
- 确保对原始数据不进行修改。
-
添加新特征:
PropertyAge
:计算房产的年龄。df_out['PropertyAge'] = df_out['YrSold'] - df_out['YearBuilt']
TotalSF
:计算房屋的总面积,包括地下室、第一层和第二层。df_out['TotalSF'] = df_out['TotalBsmtSF'] + df_out['1stFlrSF'] + df_out['2ndFlrSF']
TotalBath
:计算总浴室数,半浴室按 0.5 个计。df_out['TotalBath'] = (df_out['FullBath'] + 0.5 * df_out['HalfBath'] + df_out['BsmtFullBath'] + 0.5 * df_out['BsmtHalfBath'])
-
添加逻辑型特征(布尔值):
HasRemodeled
:是否有重新装修。df_out['HasRemodeled'] = (df_out['YearRemodAdd'] != df_out['YearBuilt']).astype(object)
- 如果重新装修年份与建造年份不同,则表示有翻修。
Has2ndFloor
:是否有第二层楼。df_out['Has2ndFloor'] = (df_out['2ndFlrSF'] > 0).astype(object)
HasGarage
:是否有车库。df_out['HasGarage'] = (df_out['GarageArea'] > 0).astype(object)
-
添加分类型特征:
- 将一些数值型特征转换为分类类型,以便模型可以更好地处理离散值:
df_out['YrSold_cat'] = df_out['YrSold'].astype(object) df_out['MoSold_cat'] = df_out['MoSold'].astype(object) df_out['YearBuilt_cat'] = df_out['YearBuilt'].astype(object) df_out['MSSubClass_cat'] = df_out['MSSubClass'].astype(object)
- 将一些数值型特征转换为分类类型,以便模型可以更好地处理离散值:
3. 将特征工程函数转换为 Transformer
feature_engineering_transformer = FunctionTransformer(custom_features)
FunctionTransformer
:- 允许将自定义函数(如
custom_features
)包装成可以直接在Pipeline
或ColumnTransformer
中使用的形式。
- 允许将自定义函数(如
- 作用:
- 它会在管道中调用
custom_features(df)
,并将返回值作为后续步骤的输入。
- 它会在管道中调用
总结
这段代码完成以下功能:
- 定义了一个
custom_features
函数,用于基于输入数据生成新特征(如房产年龄、总面积等),并转换部分数值特征为分类特征。 - 使用
FunctionTransformer
将该函数封装为可复用的 Transformer,使其适用于机器学习流水线。
优点:
- 易于集成到机器学习管道中,统一特征处理逻辑。
- 提高特征表达能力,为模型提供更多信息,可能提升预测性能。
应用场景
- 特征工程:生成新的特征或改进现有特征以增强模型性能。
- 机器学习流水线:作为预处理步骤应用于数据集。
- 分类与回归任务:特别适合像房屋价格预测这种需要从原始数据提取有意义特征的任务。
2. 将新增的特征划分为类别型特征和数值型特征
# identify categorical and numerical columns
new_cols_categorical = pd.Index(['HasRemodeled', 'Has2ndFloor', 'HasGarage'])
new_cols_numeric = pd.Index(['PropertyAge',
'TotalSF',
'TotalBath',
'YrSold_cat',
'MoSold_cat',
'YearBuilt_cat',
'MSSubClass_cat'])
这段代码的目的是将新增的特征划分为类别型特征和数值型特征,以便在后续的处理(例如预处理、特征选择、建模)中分别处理它们。
代码逐步解释
1. 新增的类别型特征
new_cols_categorical = pd.Index(['HasRemodeled', 'Has2ndFloor', 'HasGarage'])
- 使用
pd.Index
定义了一个 类别型特征的索引列表,这些特征是通过布尔逻辑计算得出的,是离散值。 - 特征解释:
HasRemodeled
:- 表示是否重新装修,取值为布尔类型(
True/False
),通常会被转换为分类变量。
- 表示是否重新装修,取值为布尔类型(
Has2ndFloor
:- 表示是否有第二层楼,取值为布尔类型(
True/False
)。
- 表示是否有第二层楼,取值为布尔类型(
HasGarage
:- 表示是否有车库,取值为布尔类型(
True/False
)。
- 表示是否有车库,取值为布尔类型(
2. 新增的数值型特征
new_cols_numeric = pd.Index([
'PropertyAge',
'TotalSF',
'TotalBath',
'YrSold_cat',
'MoSold_cat',
'YearBuilt_cat',
'MSSubClass_cat'
])
- 使用
pd.Index
定义了一个 数值型特征的索引列表,这些特征包含了连续型和某些分类型特征。 - 特征解释:
PropertyAge
:- 房产年龄,基于年份计算的数值型特征(例如:出售年份减去建造年份)。
TotalSF
:- 房屋的总面积,单位为平方英尺,是连续型特征。
TotalBath
:- 房屋的总浴室数量,半浴室按 0.5 个计,是连续型特征。
YrSold_cat
、MoSold_cat
、YearBuilt_cat
、MSSubClass_cat
:- 原本是数值型特征,但通过
.astype(object)
转换为分类特征。尽管是分类变量,它们可能仍然以数值的形式存储,因此被包含在这里。
- 原本是数值型特征,但通过
核心概念
-
类别型特征(Categorical Features):
- 包含有限个离散值,例如布尔值(
True/False
)或分类标签(A, B, C
)。 - 常用于分类任务或需要进行独热编码(One-Hot Encoding)、频率编码等操作。
- 包含有限个离散值,例如布尔值(
-
数值型特征(Numeric Features):
- 包含连续值或离散值,通常用于回归任务或直接输入模型。
- 即使有些特征看起来是分类变量(例如年份分类),它们也可以被处理为数值型特征(如在树模型中)。
为什么要区分类别型和数值型特征?
在机器学习任务中,类别型和数值型特征的预处理方式往往不同:
- 类别型特征:
- 通常需要转换为数值形式(例如:独热编码、目标编码)。
- 可能需要进行缺失值填充(用最常见值填补)。
- 数值型特征:
- 通常需要进行标准化或归一化。
- 可能需要处理异常值或填补缺失值(用均值、中位数或其他方法)。
通过提前划分,后续的特征工程和建模流程会更高效且易于维护。
总结
new_cols_categorical
:包含新增的类别型特征(布尔值或分类变量)。new_cols_numeric
:包含新增的数值型特征(包括连续值和某些分类型变量)。- 这一步是为了方便后续的预处理(例如,分开对数值特征进行归一化,对类别特征进行编码等)。
3. 更新原始数据集中类别型特征和数值型特征的列索引,并将新增的特征纳入其中
# Update categorical and numerical columns
categorical_columns = df.select_dtypes(include=['object', 'category']).columns.append(new_cols_categorical)
numerical_columns = df.select_dtypes(include=['int64', 'float64']).columns.append(new_cols_numeric)
# Remove target variable from numerical columns
numerical_columns = numerical_columns.drop('SalePrice')
4. 通过 特征工程 和 数据预处理 构建一个完整的 Pipeline
# combine transformers using ColumnTransformer
preprocessor = ColumnTransformer(
transformers=[
('num', numerical_transformer, numerical_columns),
('cat', categorical_transformer, categorical_columns)
], remainder = 'passthrough'
)
# create a pipeline with the preprocessor
pipeline_fe = Pipeline(steps=[
('fe', feature_engineering_transformer),
('preprocessor', preprocessor),
('pca', pca)
])
# apply the pipeline to your dataset
X = df.drop('SalePrice', axis=1)
y = np.log(df['SalePrice'])
X_preprocessed_fe = pipeline_fe.fit_transform(X)
这段代码的目的是通过 特征工程 和 数据预处理 构建一个完整的 Pipeline,并将其应用到数据集上以生成适合建模的输入特征(X_preprocessed_fe
)。以下是代码的逐步解析:
1. 创建 preprocessor
(预处理器)
preprocessor = ColumnTransformer(
transformers=[
('num', numerical_transformer, numerical_columns),
('cat', categorical_transformer, categorical_columns)
], remainder='passthrough'
)
-
ColumnTransformer
:- 它允许对不同的列应用不同的预处理操作。
transformers
参数定义了对数值型和类别型列分别使用的转换器。
-
数值型转换器:
('num', numerical_transformer, numerical_columns)
- 对数值型列
numerical_columns
应用数值型转换器numerical_transformer
(如标准化、缺失值处理等)。
- 对数值型列
-
类别型转换器:
('cat', categorical_transformer, categorical_columns)
- 对类别型列
categorical_columns
应用类别型转换器categorical_transformer
(如独热编码、目标编码等)。
- 对类别型列
-
remainder='passthrough'
:- 对未在
numerical_columns
和categorical_columns
中指定的列,保持原样传递到输出中(不进行任何转换)。
- 对未在
2. 创建完整的处理流水线 (pipeline_fe
)
pipeline_fe = Pipeline(steps=[
('fe', feature_engineering_transformer),
('preprocessor', preprocessor),
('pca', pca)
])
-
Pipeline
:- 定义了一个顺序执行的工作流,其中每一步都处理数据的一部分。
-
步骤 1:
feature_engineering_transformer
:- 首先应用自定义特征工程函数
custom_features
(已在之前定义),生成新增的特征列(如PropertyAge
,TotalSF
,TotalBath
等)。
- 首先应用自定义特征工程函数
-
步骤 2:
preprocessor
:- 对处理后的数据应用前面定义的
preprocessor
,将数值型和类别型特征分别转换为合适的格式。
- 对处理后的数据应用前面定义的
-
步骤 3:
pca
:- 使用主成分分析(PCA)对预处理后的数据进行降维,将高维数据映射到更低的维度,同时尽量保留数据的主要特性。
3. 应用流水线到数据集
X = df.drop('SalePrice', axis=1)
y = np.log(df['SalePrice'])
X_preprocessed_fe = pipeline_fe.fit_transform(X)
(1) 数据准备
-
X
:X
是输入特征,剔除了目标变量SalePrice
。- 这是模型训练的输入数据。
-
y
:- 目标变量
SalePrice
被取对数(np.log
),可能是为了将其分布转换为接近正态分布。 - 对数变换可以改善模型的性能,特别是当目标值的范围较大或分布偏斜时。
- 目标变量
(2) 应用流水线
pipeline_fe.fit_transform(X)
:- 依次执行流水线中的步骤:
- 特征工程(
feature_engineering_transformer
)。 - 数据预处理(
preprocessor
)。 - 主成分分析(
pca
)。
- 特征工程(
- 最终输出处理后的特征矩阵
X_preprocessed_fe
。
- 依次执行流水线中的步骤:
代码的目的与作用
-
集成多个预处理步骤:
- 将特征工程、数据预处理、降维等步骤整合到一个
Pipeline
中,使数据处理流程更加模块化和易维护。
- 将特征工程、数据预处理、降维等步骤整合到一个
-
灵活的特征转换:
- 针对数值型和类别型特征分别应用适合的转换器,同时保留未处理的列。
-
降维处理:
- 使用 PCA 将高维数据降到较低的维度,减少特征冗余,提高模型的训练速度和性能。
-
生成建模数据:
X_preprocessed_fe
是处理后的特征矩阵,已经可以直接用于模型的训练。
总结
- 特征工程:通过自定义函数生成新特征(如
PropertyAge
,TotalSF
等)。 - 数据预处理:对数值和类别型特征分别进行适当的转换。
- 降维:通过 PCA 提取主要特征,降低数据的复杂性。
- 输出:最终生成了预处理后的特征矩阵
X_preprocessed_fe
,并对目标变量y
做了对数变换,准备用于模型训练。
5. 对数据集进行训练/测试集划分,并定义多种模型及其超参数网格
# split the data into training and testing sets
X_train_fe, X_test_fe, y_train_fe, y_test_fe = train_test_split(X_preprocessed_fe, y, test_size=0.2, random_state = 42)
# define the models
models = {
'LinearRegression': LinearRegression(),
'RandomForest': RandomForestRegressor(random_state=42),
'XGBoost': XGBRegressor(random_state=42)
}
# define the hyperparameter grids for each model
param_grids = {
'LinearRegression': {},
'RandomForest': {
'n_estimators': [100, 200, 500],
'max_depth': [None, 10, 30],
'min_samples_split': [2, 5, 10],
},
'XGBoost':{
'n_estimators': [100, 200, 500],
'learning_rate': [0.01, 0.1, 0.3],
'max_depth': [3, 6, 10],
}
}
# 3-fold cross-validation
cv = KFold(n_splits=3, shuffle=True, random_state=42)
这段代码的目的是对数据集进行训练/测试集划分,并定义多种模型及其超参数网格,为后续的模型训练和优化(如超参数调优)做准备。以下是代码的逐步解释:
1. 数据集划分
X_train_fe, X_test_fe, y_train_fe, y_test_fe = train_test_split(X_preprocessed_fe, y, test_size=0.2, random_state=42)
-
train_test_split
:- 将数据集划分为 训练集 (training set) 和 测试集 (testing set)。
-
参数解释:
X_preprocessed_fe
:预处理后的特征矩阵(特征工程和降维后的结果)。y
:目标变量(对数变换后的房价)。test_size=0.2
:将数据的 20% 用作测试集,其余 80% 用作训练集。random_state=42
:固定随机种子,以确保结果可复现。
-
输出:
X_train_fe
和y_train_fe
:训练集特征和目标变量。X_test_fe
和y_test_fe
:测试集特征和目标变量。
2. 定义模型
models = {
'LinearRegression': LinearRegression(),
'RandomForest': RandomForestRegressor(random_state=42),
'XGBoost': XGBRegressor(random_state=42)
}
-
目的:创建一个字典,包含要评估的模型及其实例化对象。
-
模型列表:
LinearRegression
:- 线性回归模型。
- 适用于特征和目标变量呈线性关系的情况。
RandomForest
:- 随机森林回归模型。
- 使用多棵决策树的集合进行预测,能处理非线性关系并具有较强的抗过拟合能力。
XGBoost
:- 极端梯度提升模型(XGBoost 回归)。
- 高性能的梯度提升方法,擅长处理复杂的非线性关系,并提供对超参数的灵活控制。
-
固定随机种子:
random_state=42
保证模型训练的随机过程具有可复现性。
3. 定义超参数网格
param_grids = {
'LinearRegression': {},
'RandomForest': {
'n_estimators': [100, 200, 500],
'max_depth': [None, 10, 30],
'min_samples_split': [2, 5, 10],
},
'XGBoost':{
'n_estimators': [100, 200, 500],
'learning_rate': [0.01, 0.1, 0.3],
'max_depth': [3, 6, 10],
}
}
-
目的:为每个模型定义可调节的超参数及其取值范围,供超参数优化(如网格搜索)使用。
-
超参数网格:
LinearRegression
:- 没有额外的超参数,因此网格为空
{}
。
- 没有额外的超参数,因此网格为空
RandomForest
:n_estimators
:树的数量(100、200 或 500)。max_depth
:树的最大深度(无限制、10 或 30)。min_samples_split
:分裂节点所需的最小样本数(2、5 或 10)。
XGBoost
:n_estimators
:弱学习器(树)的数量(100、200 或 500)。learning_rate
:学习率(0.01、0.1 或 0.3)。max_depth
:树的最大深度(3、6 或 10)。
4. 定义交叉验证策略
cv = KFold(n_splits=3, shuffle=True, random_state=42)
-
KFold
:- 定义 k折交叉验证 策略,将数据划分为
k
个折叠(subsets),在训练时每次使用一个折叠作为验证集,其余作为训练集。
- 定义 k折交叉验证 策略,将数据划分为
-
参数解释:
n_splits=3
:将数据划分为 3 个折叠。shuffle=True
:在划分前打乱数据,避免因数据排序导致的偏差。random_state=42
:固定随机种子,确保折叠划分的一致性。
-
目的:
- 在模型评估过程中对数据多次训练和验证,减少单次划分可能带来的误差。
总结
- 数据划分:
- 将数据集分为训练集(80%)和测试集(20%)。
- 模型定义:
- 包含线性回归、随机森林回归和 XGBoost 回归三种模型。
- 超参数网格:
- 为随机森林和 XGBoost 定义了常用的可调参数范围。
- 交叉验证:
- 使用 3 折交叉验证来评估模型性能,保证评估结果更加稳健和准确。
这些步骤是为后续的 模型训练、超参数优化 和 性能评估 打下基础。
6. 训练并调优多个模型
# train and tune the models
grids_fe = {}
for model_name, model in models.items():
print(f'Training and tuning {model_name}...')
grids_fe[model_name] = GridSearchCV(estimator=model,
param_grid=param_grids[model_name],
cv=cv,
scoring='neg_mean_squared_error',
n_jobs=-1,
verbose=2)
grids_fe[model_name].fit(X_train_fe, y_train_fe)
best_params = grids_fe[model_name].best_params_
best_score = np.sqrt(-1 * grids_fe[model_name].best_score_)
print(f'Best parameters for {model_name}: {best_params}')
print(f'Best RMSE for {model_name}: {best_score}\n')
这段代码的目的是 训练并调优多个模型,通过网格搜索(GridSearchCV
)找到每个模型的最佳超参数配置,并报告最佳参数和对应的模型性能(以 RMSE 衡量)。以下是代码的逐步解释:
1. 创建一个空字典存储网格搜索结果
grids_fe = {}
- 目的:
- 用来存储每个模型对应的
GridSearchCV
对象,方便后续调用最佳模型或参数。
- 用来存储每个模型对应的
2. 遍历模型进行训练和调参
for model_name, model in models.items():
- 循环过程:
- 遍历之前定义的
models
字典,其中包含模型名称 (model_name
) 和对应的实例化模型对象 (model
)。 - 示例:
- 第一轮:
model_name = 'LinearRegression'
,model = LinearRegression()
- 第二轮:
model_name = 'RandomForest'
,model = RandomForestRegressor(random_state=42)
- 第三轮:
model_name = 'XGBoost'
,model = XGBRegressor(random_state=42)
- 第一轮:
- 遍历之前定义的
3. 打印正在处理的模型名称
print(f'Training and tuning {model_name}...')
- 目的:
- 在每次处理新模型时,打印消息,让用户知道当前正在训练和调参的模型。
4. 定义网格搜索对象
grids_fe[model_name] = GridSearchCV(
estimator=model,
param_grid=param_grids[model_name],
cv=cv,
scoring='neg_mean_squared_error',
n_jobs=-1,
verbose=2
)
GridSearchCV
参数解释:estimator=model
:指定要调参的模型。param_grid=param_grids[model_name]
:模型的超参数搜索空间。- 示例:对于随机森林,搜索空间包括
n_estimators
,max_depth
,min_samples_split
。
- 示例:对于随机森林,搜索空间包括
cv=cv
:交叉验证策略(之前定义为 3 折交叉验证)。scoring='neg_mean_squared_error'
:优化目标为负均方误差(neg_mean_squared_error
是 MSE 的负值)。- 取负值是因为 Scikit-learn 中大部分评估指标是越大越好,而 MSE 越小越好。
n_jobs=-1
:并行化处理,使用所有可用的 CPU 核心以加快搜索速度。verbose=2
:输出详细的搜索进度信息。
5. 训练网格搜索模型
grids_fe[model_name].fit(X_train_fe, y_train_fe)
fit
方法:- 执行网格搜索:
- 对每种超参数组合进行交叉验证。
- 计算每种组合的平均性能(基于验证集的评分)。
- 选择评分最高(MSE 最小)的参数组合作为最佳参数。
- 执行网格搜索:
6. 提取最佳参数和模型性能
best_params = grids_fe[model_name].best_params_
best_params_
:- 获取网格搜索找到的最佳超参数组合。
best_score = np.sqrt(-1 * grids_fe[model_name].best_score_)
best_score_
:- 获取最佳模型在交叉验证中的评分(负均方误差)。
- 使用
np.sqrt(-1 * ...)
将其转换为均方根误差(RMSE),这是更直观的误差衡量指标。
7. 打印最佳参数和 RMSE
print(f'Best parameters for {model_name}: {best_params}')
print(f'Best RMSE for {model_name}: {best_score}\n')
代码的整体逻辑总结
-
模型训练与调参:
- 使用
GridSearchCV
对每个模型进行超参数搜索,并通过交叉验证评估每种参数组合的性能。 - 优化目标为最小化 RMSE(均方根误差)。
- 使用
-
结果存储与报告:
- 保存网格搜索结果到字典
grids_fe
。 - 打印最佳超参数和对应的交叉验证 RMSE。
- 保存网格搜索结果到字典
-
后续步骤:
- 使用
grids_fe
中的最佳模型对测试集进行预测和性能评估。 - 选择性能最好的模型进行部署或进一步优化。
- 使用
Training and tuning LinearRegression...
Fitting 3 folds for each of 1 candidates, totalling 3 fits
Best parameters for LinearRegression: {}
Best RMSE for LinearRegression: 0.1642910860128547
Training and tuning RandomForest...
Fitting 3 folds for each of 27 candidates, totalling 81 fits
Best parameters for RandomForest: {'max_depth': None, 'min_samples_split': 5, 'n_estimators': 500}
Best RMSE for RandomForest: 0.1497654132981663
Training and tuning XGBoost...
Fitting 3 folds for each of 27 candidates, totalling 81 fits
Best parameters for XGBoost: {'learning_rate': 0.1, 'max_depth': 3, 'n_estimators': 200}
Best RMSE for XGBoost: 0.13615940472209742
7. 使用多层感知器回归模型对超参数进行调优并评估模型性能
X_train_scaled_fe = X_train_fe.copy()
X_test_scaled_fe = X_test_fe.copy()
# create a MLPRegressor instance
mlp = MLPRegressor(random_state = 42,
max_iter=10000,
n_iter_no_change=3,
verbose=True)
# define the parameter grid for tuning
param_grid = {
'hidden_layer_sizes': [(10,), (10, 10), (10, 25)],
'activation': ['relu', 'tanh', 'sigmoid'],
'solver': ['adam', 'sgd'],
'alpha': [.1, .5, 1, 10, 100],
'learning_rate': ['constant', 'invscaling', 'adaptive'],
'learning_rate_init': [0.1]
}
# create the GridSearchCV object
grid_search_mlp_fe = GridSearchCV(mlp, param_grid,
scoring='neg_mean_squared_error',
cv=3, n_jobs=-1, verbose=1)
# fit the model on the training data
grid_search_mlp_fe.fit(X_train_scaled_fe, y_train_fe)
# print the best parameters found during the search
print("Best parameters found: ", grid_search_mlp_fe.best_params_)
# evaluate the model
best_score = np.sqrt(-1 * grid_search_mlp_fe.best_score_)
print("Best score: ", best_score)
Best parameters found: {'activation': 'tanh', 'alpha': 0.5, 'hidden_layer_sizes': (10, 10), 'learning_rate': 'adaptive', 'learning_rate_init': 0.1, 'solver': 'sgd'}
Best score: 0.12851167944712377
这段代码使用 多层感知器回归模型(MLPRegressor),结合网格搜索(GridSearchCV
),对超参数进行调优并评估模型性能。以下是详细的解释:
1. 拷贝训练和测试数据
X_train_scaled_fe = X_train_fe.copy()
X_test_scaled_fe = X_test_fe.copy()
- 目的:
- 为了避免修改原始数据,创建训练和测试数据的副本。
- 此处没有直接提到标准化(Scaling),但对多层感知器(MLP)而言,通常需要对数据进行标准化。假设后续会进行这一步。
2. 创建多层感知器回归模型(MLPRegressor)实例
mlp = MLPRegressor(
random_state=42,
max_iter=10000,
n_iter_no_change=3,
verbose=True
)
MLPRegressor
参数解释:random_state=42
:设置随机种子以确保结果可重复。max_iter=10000
:最大迭代次数为 10,000,确保模型有足够时间收敛。n_iter_no_change=3
:如果连续 3 次迭代后验证集误差未改善,提前停止训练(防止过拟合)。verbose=True
:显示训练过程中的详细信息(如每次迭代的损失)。
3. 定义超参数搜索空间
param_grid = {
'hidden_layer_sizes': [(10,), (10, 10), (10, 25)],
'activation': ['relu', 'tanh', 'sigmoid'],
'solver': ['adam', 'sgd'],
'alpha': [.1, .5, 1, 10, 100],
'learning_rate': ['constant', 'invscaling', 'adaptive'],
'learning_rate_init': [0.1]
}
- 超参数搜索空间解释:
hidden_layer_sizes
:- 定义隐藏层的结构。
- 示例:
(10,)
表示一个隐藏层,包含 10 个神经元;(10, 25)
表示两层,分别有 10 和 25 个神经元。
activation
:- 激活函数类型:
relu
:整流线性单元(常用,收敛快)。tanh
:双曲正切函数(适合对称分布数据)。sigmoid
:S 形函数(可能导致梯度消失问题)。
- 激活函数类型:
solver
:- 优化器类型:
adam
:自适应矩估计(默认值,通常表现最好)。sgd
:随机梯度下降(需要学习率调整)。
- 优化器类型:
alpha
:- 正则化强度(L2 范数惩罚系数)。
learning_rate
:- 学习率调整策略:
constant
:固定学习率。invscaling
:学习率随迭代次数减小。adaptive
:当损失停止减小时降低学习率。
- 学习率调整策略:
learning_rate_init
:- 初始学习率设置为 0.1。
4. 创建 GridSearchCV 对象
grid_search_mlp_fe = GridSearchCV(
mlp,
param_grid,
scoring='neg_mean_squared_error',
cv=3,
n_jobs=-1,
verbose=1
)
GridSearchCV
参数解释:mlp
:待调参的模型实例(多层感知器回归)。param_grid
:超参数搜索空间。scoring='neg_mean_squared_error'
:目标是最小化均方误差(MSE)。cv=3
:3 折交叉验证(将数据分为 3 个子集,每次使用一个子集作为验证集,其余作为训练集)。n_jobs=-1
:并行化处理,使用所有可用 CPU 核心加速计算。verbose=1
:显示搜索过程的进度。
5. 训练模型并调参
grid_search_mlp_fe.fit(X_train_scaled_fe, y_train_fe)
- 执行流程:
- 对每组超参数组合执行 3 折交叉验证。
- 计算验证集上的负均方误差(
neg_mean_squared_error
)。 - 找到平均误差最小的超参数组合。
6. 打印最佳超参数
print("Best parameters found: ", grid_search_mlp_fe.best_params_)
- 功能:
- 打印网格搜索找到的最佳超参数组合。
7. 打印最佳模型的 RMSE
best_score = np.sqrt(-1 * grid_search_mlp_fe.best_score_)
print("Best score: ", best_score)
- 功能:
- 计算并打印最佳模型的 RMSE(均方根误差)。
总结
这段代码完成了以下任务:
- 模型创建:实例化 MLP 回归器。
- 超参数调优:使用
GridSearchCV
在定义的超参数网格中搜索最佳组合。 - 模型评估:通过交叉验证计算最佳模型的均方根误差(RMSE)。
- 输出结果:打印最佳超参数和对应的模型性能。
最终,这些结果可以用于测试数据集的预测,或进一步用于实际应用中。
8. 使用train中的测试部分进行评估
for i in grids_fe.keys():
print(i + ': ' + str(np.sqrt(mean_squared_error(
grids_fe[i].predict(X_test_fe), y_test
))))
LinearRegression: 0.142949208211511
RandomForest: 0.15209538909973422
XGBoost: 0.1406883107858585
print(str(np.sqrt(mean_squared_error(
grid_search_mlp_fe.predict(X_test_scaled_fe), y_test
))))
0.13475255938798586
9. 使用测试数据集
df_test = pd.read_csv('house-prices-advanced-regression-techniques/test.csv')
df_test_preprocessed = pipeline_fe.transform(df_test)
# xgboost submission
y_xgboost = np.exp(grids_fe['XGBoost'].predict(df_test_preprocessed))
df_xgboost_out = df_test[['Id']].copy()
df_xgboost_out['SalePrice'] = y_xgboost
df_xgboost_out.to_csv('submission_xgboost_new_features_normalized.csv', index=False)
# RandomForest submission
y_rf = np.exp(grids_fe['RandomForest'].predict(df_test_preprocessed))
df_rf_out = df_test[['Id']].copy()
df_rf_out['SalePrice'] = y_rf
df_rf_out.to_csv('submission_rf_new_features_normalized.csv', index=False)
# LinearRegression submission
y_lr = np.exp(grids_fe['LinearRegression'].predict(df_test_preprocessed))
df_lr_out = df_test[['Id']].copy()
df_lr_out['SalePrice'] = y_lr
df_lr_out.to_csv('submission_lr_new_features_normalized.csv', index=False)
# MLP submission
y_mlp = np.exp(grid_search_mlp_fe.predict(df_test_preprocessed))
df_mlp_out = df_test[['Id']].copy()
df_mlp_out['SalePrice'] = y_mlp
df_mlp_out.to_csv('submission_mlp_new_features_normalized.csv', index=False)
y_avg_ens = (y_rf + y_xgboost + y_mlp) / 3
df_avg_ens_out = df_test[['Id']].copy()
df_avg_ens_out['SalePrice'] = y_avg_ens
df_avg_ens_out.to_csv('submission_avg_ens_new_features_normalized.csv', index=False)
10. 通过超参数调优在多个元模型(meta-model)上找到最佳模型
from sklearn.ensemble import StackingRegressor
grids_fe['MLP'] = grid_search_mlp_fe
grids_fe.items()
输出:
dict_items([('LinearRegression', GridSearchCV(cv=KFold(n_splits=3, random_state=42, shuffle=True),
estimator=LinearRegression(), n_jobs=-1, param_grid={},
scoring='neg_mean_squared_error', verbose=2)), ('RandomForest', GridSearchCV(cv=KFold(n_splits=3, random_state=42, shuffle=True),
estimator=RandomForestRegressor(random_state=42), n_jobs=-1,
param_grid={'max_depth': [None, 10, 30],
'min_samples_split': [2, 5, 10],
'n_estimators': [100, 200, 500]},
scoring='neg_mean_squared_error', verbose=2)), ('XGBoost', GridSearchCV(cv=KFold(n_splits=3, random_state=42, shuffle=True),
estimator=XGBRegressor(base_score=None, booster=None,
callbacks=None, colsample_bylevel=None,
colsample_bynode=None,
colsample_bytree=None, device=None,
early_stopping_rounds=None,
enable_categorical=False, eval_metric=None,
feature_types=None, gamma=None,
grow_policy=None, importance_type=None,
inte...
max_cat_to_onehot=None, max_delta_step=None,
max_depth=None, max_leaves=None,
min_child_weight=None, missing=nan,
monotone_constraints=None,
multi_strategy=None, n_estimators=None,
n_jobs=None, num_parallel_tree=None,
random_state=42, ...),
n_jobs=-1,
param_grid={'learning_rate': [0.01, 0.1, 0.3],
'max_depth': [3, 6, 10],
'n_estimators': [100, 200, 500]},
scoring='neg_mean_squared_error', verbose=2)), ('MLP', GridSearchCV(cv=3,
estimator=MLPRegressor(max_iter=10000, n_iter_no_change=3,
random_state=42, verbose=True),
n_jobs=-1,
param_grid={'activation': ['relu', 'tanh', 'sigmoid'],
'alpha': [0.1, 0.5, 1, 10, 100],
'hidden_layer_sizes': [(10,), (10, 10), (10, 25)],
'learning_rate': ['constant', 'invscaling',
'adaptive'],
'learning_rate_init': [0.1],
'solver': ['adam', 'sgd']},
scoring='neg_mean_squared_error', verbose=1))])
best_estimators = [(model_name, grid.best_estimator_) for model_name, grid in grids_fe.items()]
# define the candidate meta-models
meta_models = {
'MLP': MLPRegressor(random_state = 42,
max_iter = 10000,
n_iter_no_change = 3,
learning_rate_init = 0.001),
'LinearRegression': LinearRegression(),
'XGBoost': XGBRegressor(random_state=42)
}
# define the hyperparameter grids for each meta-model
meta_param_grids = {
'MLP': {
'final_estimator__hidden_layer_sizes': [(10,), (10, 10)],
'final_estimator__activation': ['relu', 'tanh'],
'final_estimator__solver': ['adam', 'sgd'],
'final_estimator__alpha': [0.001, 0.01, .1, .5],
'final_estimator__learning_rate': ['constant', 'invscaling', 'adaptive'],
},
'LinearRegression': {},
'XGBoost': {
'final_estimator__n_estimators': [100, 200, 500],
'final_estimator__learning_rate': [0.01, 0.1, 0.3],
'final_estimator__max_depth': [3, 6, 10],
}
}
best_estimators
输出:
[('LinearRegression', LinearRegression()),
('RandomForest',
RandomForestRegressor(min_samples_split=5, n_estimators=500, random_state=42)),
('XGBoost',
XGBRegressor(base_score=None, booster=None, callbacks=None,
colsample_bylevel=None, colsample_bynode=None,
colsample_bytree=None, device=None, early_stopping_rounds=None,
enable_categorical=False, eval_metric=None, feature_types=None,
gamma=None, grow_policy=None, importance_type=None,
interaction_constraints=None, learning_rate=0.1, max_bin=None,
max_cat_threshold=None, max_cat_to_onehot=None,
max_delta_step=None, max_depth=3, max_leaves=None,
min_child_weight=None, missing=nan, monotone_constraints=None,
multi_strategy=None, n_estimators=500, n_jobs=None,
num_parallel_tree=None, random_state=42, ...)),
('MLP',
MLPRegressor(activation='tanh', alpha=0.5, hidden_layer_sizes=(10, 10),
learning_rate='adaptive', learning_rate_init=0.1, max_iter=10000,
n_iter_no_change=3, random_state=42, solver='sgd', verbose=True))]
# 3-fold cross-validation
cv = KFold(n_splits=3, shuffle=True, random_state=42)
# train and tune the stacking ensemble
best_score = float('inf')
best_model = None
for meta_name, meta_model in meta_models.items():
print(f'Training and tuning {meta_name} as the meta-model...')
stacking_regressor = StackingRegressor(estimators = best_estimators,
final_estimator = meta_model,
cv = cv)
grid_search = GridSearchCV(estimator = stacking_regressor,
param_grid = meta_param_grids[meta_name],
cv = cv,
scoring = 'neg_mean_squared_error',
n_jobs = -1,
verbose = 1)
grid_search.fit(X_train_fe, y_train_fe)
best_params = grid_search.best_params_
best_rmse = np.sqrt(-1 * grid_search.best_score_)
print(f'Best parameters for {meta_name}: {best_params}')
print(f'Best RMSE for {meta_name}: {best_rmse}\n')
if best_rmse < best_score:
best_score = best_rmse
best_model = grid_search
这段代码用于使用堆叠回归(stacking regression)构建和优化集成模型,通过超参数调优(GridSearchCV)在多个元模型(meta-model)上找到最佳模型。
代码分解与解释
1. 3折交叉验证
cv = KFold(n_splits=3, shuffle=True, random_state=42)
- 功能:
- 使用
KFold
创建 3 折交叉验证,确保训练数据划分为 3 个子集,每个子集分别用于验证一次,其余用于训练。 - 参数:
n_splits=3
:数据划分为 3 个子集。shuffle=True
:在划分数据之前随机打乱顺序。random_state=42
:设置随机种子,确保划分结果一致。
- 使用
- 用途:
- 在
StackingRegressor
和GridSearchCV
中实现交叉验证。
- 在
2. 初始化变量
best_score = float('inf')
best_model = None
- 功能:
best_score
:初始化为正无穷大,用于存储当前最佳模型的 RMSE(均方根误差)。best_model
:初始化为空,用于存储当前最佳模型对象。
3. 遍历元模型并进行训练与调优
for meta_name, meta_model in meta_models.items():
print(f'Training and tuning {meta_name} as the meta-model...')
- 功能:
- 遍历
meta_models
(元模型字典),对每个元模型(如MLP
、LinearRegression
、XGBoost
)依次进行堆叠训练和超参数调优。
- 遍历
4. 创建堆叠回归器
stacking_regressor = StackingRegressor(
estimators=best_estimators,
final_estimator=meta_model,
cv=cv
)
- 功能:
- 创建一个
StackingRegressor
对象。 - 参数:
estimators=best_estimators
:堆叠模型的基础学习器列表(如之前定义的最佳模型best_estimators
)。final_estimator=meta_model
:当前元模型(最终预测器)。cv=cv
:用于堆叠的交叉验证方法。
- 创建一个
5. 网格搜索超参数调优
grid_search = GridSearchCV(
estimator=stacking_regressor,
param_grid=meta_param_grids[meta_name],
cv=cv,
scoring='neg_mean_squared_error',
n_jobs=-1,
verbose=1
)
- 功能:
- 使用
GridSearchCV
对堆叠回归器进行超参数调优。 - 参数:
estimator=stacking_regressor
:用于调优的堆叠回归器。param_grid=meta_param_grids[meta_name]
:对应元模型的超参数搜索网格。cv=cv
:交叉验证方法。scoring='neg_mean_squared_error'
:使用负均方误差(neg_mean_squared_error
)作为评价指标(越接近零越好)。n_jobs=-1
:并行运行以加速搜索。verbose=1
:显示调优进度。
- 使用
6. 训练与超参数调优
grid_search.fit(X_train_fe, y_train_fe)
- 功能:
- 使用训练数据(
X_train_fe
和y_train_fe
)对堆叠回归器进行训练和超参数调优。
- 使用训练数据(
7. 提取最佳参数和评估结果
best_params = grid_search.best_params_
best_rmse = np.sqrt(-1 * grid_search.best_score_)
- 功能:
grid_search.best_params_
:提取网格搜索中找到的最佳超参数组合。grid_search.best_score_
:最佳负均方误差,取负值后开平方得到最佳均方根误差(RMSE)。
8. 打印结果
print(f'Best parameters for {meta_name}: {best_params}')
print(f'Best RMSE for {meta_name}: {best_rmse}\n')
- 功能:
- 输出当前元模型的最佳超参数组合和对应的最佳 RMSE。
9. 更新最佳模型
if best_rmse < best_score:
best_score = best_rmse
best_model = grid_search
- 功能:
- 比较当前元模型的 RMSE 是否优于全局最佳 RMSE:
- 如果更优,则更新
best_score
和best_model
,以记录全局最佳结果。
- 如果更优,则更新
- 比较当前元模型的 RMSE 是否优于全局最佳 RMSE:
总结
- 目的:
- 使用堆叠回归器(StackingRegressor)结合多个基础模型(如 MLP、Linear Regression、XGBoost)。
- 为不同元模型(meta-model)通过网格搜索找到最佳超参数组合。
- 流程:
- 遍历元模型,分别训练堆叠回归器并调优超参数。
- 计算和比较 RMSE,保留最佳模型及其参数。
- 最终结果:
best_model
存储最佳堆叠回归器。best_score
保存其对应的最小 RMSE。
Best parameters for MLP: {'final_estimator__activation': 'relu', 'final_estimator__alpha': 0.5, 'final_estimator__hidden_layer_sizes': (10,), 'final_estimator__learning_rate': 'adaptive', 'final_estimator__solver': 'sgd'}
Best RMSE for MLP: 0.14130938670585158
Best parameters for LinearRegression: {}
Best RMSE for LinearRegression: 0.14494773906795216
Best parameters for XGBoost: {'final_estimator__learning_rate': 0.01, 'final_estimator__max_depth': 3, 'final_estimator__n_estimators': 500}
Best RMSE for XGBoost: 0.1351180102687235
11. 使用test数据进行预测
y_stack = np.exp(best_model.predict(df_test_preprocessed))
# xgboost submission
df_stack_out = df_test[['Id']].copy()
df_stack_out['SalePrice'] = y_stack
df_stack_out.to_csv('submission_stack_new_features_normalized.csv', index=False)
np.exp():对预测值取指数还原(因为目标变量在训练时经过对数变换)。