深入探索Python机器学习算法:(加州房价预测)XGBoost 与贝叶斯优化驱动的鲁棒性集成模型

发布于:2025-03-08 ⋅ 阅读:(154) ⋅ 点赞:(0)

深入探索Python机器学习算法:(加州房价预测)XGBoost 与贝叶斯优化驱动的鲁棒性集成模型

一、引言

在房地产市场分析和预测领域,准确预测房价一直是研究的热点和难点。加州作为美国经济发达且房地产市场活跃的地区,其房价受到多种因素的综合影响,如地理位置、房屋特征、周边配套等。通过机器学习方法构建房价预测模型,可以帮助投资者、购房者和政策制定者更好地了解市场动态,做出合理的决策。

本文将详细介绍一个基于Python实现的加州房价预测全流程模型,涵盖数据加载与预处理、特征工程、特征选择、超参数调优、模型训练、评估以及可视化等多个关键步骤。通过对这些步骤的深入分析,我们可以了解如何运用机器学习技术解决实际的房价预测问题,并学习到一些实用的技巧和方法。

数据文件

20640条数据
在这里插入图片描述

longitude经度	
latitude纬度	
housing_median_age住房年龄中位数	
total_rooms总房间数	
total_bedrooms总卧室数	
population人口数	
households家庭数	
median_income收入中位数	
median_house_value房屋价值中位数	
ocean_proximity海洋邻近性

二、环境准备与依赖库介绍

2.1 环境准备

在开始实现加州房价预测模型之前,我们需要搭建相应的Python环境。建议使用Anaconda来管理Python环境和安装依赖库,因为Anaconda提供了丰富的科学计算和数据分析库,并且可以方便地创建和管理虚拟环境。

以下是创建和激活虚拟环境的命令:

conda create -n housing_prediction python=3.8
conda activate housing_prediction

我用的Pycharm,但是用jupyter notebook会方便操作

2.2 依赖库介绍

本项目使用了多个Python库,下面对这些库的功能和作用进行简要介绍:

  • NumPy:是Python中用于科学计算的基础库,提供了高效的多维数组对象和各种数学函数,可用于处理和计算大规模的数值数据。
  • Pandas:是一个强大的数据处理和分析库,提供了DataFrame和Series等数据结构,方便进行数据的读取、清洗、转换和分析。
  • Matplotlib:是Python中常用的绘图库,可用于创建各种静态、交互式的图表,如折线图、柱状图、散点图等。
  • Seaborn:是基于Matplotlib的高级数据可视化库,提供了更美观、更简洁的绘图风格和接口,能够快速创建高质量的统计图表。
  • SHAP:是一个用于解释机器学习模型预测结果的库,通过计算特征的SHAP值,可以直观地了解每个特征对模型预测结果的影响程度。
  • Joblib:是Python中用于并行计算和持久化的库,可用于保存和加载机器学习模型,方便模型的复用和部署。
  • Scikit-learn:是Python中最常用的机器学习库,提供了丰富的机器学习算法和工具,如数据预处理、特征选择、模型训练、评估等。
  • XGBoost:是一个高效的梯度提升库,在机器学习竞赛和实际应用中都有广泛的应用,具有快速、准确、可扩展性强等优点。
  • Bayes_opt:是一个用于贝叶斯优化的库,可用于自动调整机器学习模型的超参数,提高模型的性能。

以下是安装这些依赖库的命令:
requirements.txt

numpy==2.1.3
pandas==2.2.3
matplotlib==3.10.1
seaborn==0.13.2
shap==0.47.0
joblib==1.4.2
scikit-learn==1.6.1
xgboost==2.1.4
bayes_opt==2.0.3

pip install -r requirements.txt

三、数据加载与预处理

3.1 数据加载

在代码中,我们使用pandas库的read_csv函数从CSV文件中加载加州房价数据集。数据加载部分的代码如下:

def load_data(self) -> pd.DataFrame:
    """数据加载与预处理"""
    try:
        df = pd.read_csv(DATA_PATHS["raw_data"])
        logger.info(" 初始数据行数: %d", len(df))

        # 数据校验
        expected_cols = ['longitude', 'latitude', 'housing_median_age', 'total_rooms',
                         'total_bedrooms', 'population', 'households', 'median_income',
                         'median_house_value', 'ocean_proximity']
        if list(df.columns) != expected_cols:
            raise ValueError(f"CSV列名不匹配,期待顺序:{expected_cols}")

        # 处理异常值(动态分位数)
        df = self._handle_outliers(df)
        logger.info(" 异常值处理后行数: %d", len(df))

        # 填补缺失值
        df = self._impute_missing(df)
        logger.info(" 缺失值填补后行数: %d", len(df))

        return df

    except Exception as e:
        logger.error(" 数据加载失败: %s", str(e))
        raise

在数据加载过程中,我们首先记录了初始数据的行数,然后对数据的列名进行了校验,确保数据的列名与预期一致。如果列名不匹配,将抛出ValueError异常。

3.2 异常值处理

异常值是指数据集中与其他数据点明显不同的值,可能会对模型的训练和预测结果产生负面影响。在本项目中,我们使用动态分位数的方法处理房价的异常值。具体来说,我们计算房价的0.01和0.99分位数,然后过滤掉低于0.01分位数和高于0.99分位数的数据点。异常值处理部分的代码如下:

def _handle_outliers(self, df: pd.DataFrame) -> pd.DataFrame:
    """动态异常值处理"""
    # 房价过滤(基于分位数)
    price_q_low = df['median_house_value'].quantile(0.01)
    price_q_high = df['median_house_value'].quantile(0.99)
    df = df[(df['median_house_value'] >= price_q_low) &
            (df['median_house_value'] <= price_q_high)]
    return df

3.3 缺失值填补

数据集中可能存在缺失值,需要进行填补处理。在本项目中,我们使用IterativeImputer进行多重填补,该方法基于迭代的方式,通过建立一个回归模型来预测缺失值。具体来说,我们使用RandomForestRegressor作为估计器,将max_iter参数从默认的10增加到20,以解决收敛问题。同时,我们还添加了维度检查,确保填补后的数据维度与原始数据维度一致。缺失值填补部分的代码如下:

def _impute_missing(self, df: pd.DataFrame) -> pd.DataFrame:
    """高级缺失值填补(关键修正部分)"""
    if df['total_bedrooms'].isnull().sum() > 0:
        logger.info(" 检测到缺失值,开始多重填补...")

        # 修正1:增加迭代次数解决收敛问题
        imputer = IterativeImputer(
            estimator=RandomForestRegressor(n_estimators=50),
            max_iter=20,  # 从10增加到20
            random_state=42
        )

        # 修正2:添加维度检查
        imputed_values = imputer.fit_transform(
            df[['total_bedrooms', 'total_rooms', 'households']]
        )

        # 检查维度一致性
        if imputed_values.shape[0] != df.shape[0]:
            logger.error(" 填补维度不匹配: 输入数据%d行,输出%d行",
                         df.shape[0], imputed_values.shape[0])
            raise ValueError("填补维度不一致")

        # 修正3:正确维度转换
        df['total_bedrooms'] = imputed_values[:, 0]
        logger.info(" 缺失值填补完成,剩余缺失值数量: %d",
                    df['total_bedrooms'].isnull().sum())
    return df

四、特征工程

特征工程是机器学习中非常重要的一步,它可以通过对原始数据进行转换和组合,提取出更有价值的特征,从而提高模型的性能。在本项目中,我们进行了以下几个方面的特征工程:

4.1 基础特征构建

我们计算了每个家庭的房间数和每个房间的卧室数,以反映房屋的居住密度和空间利用情况。为了防止除零错误,我们对total_roomstotal_bedrooms进行了处理。基础特征构建部分的代码如下:

# 防止除零错误
df['total_rooms'] = df['total_rooms'].replace(0, 1e-6)
df['total_bedrooms'] = df['total_bedrooms'].clip(lower=0.1)

# 基础特征
df['rooms_per_household'] = df['total_rooms'] / df['households']
df['bedrooms_per_room'] = df['total_bedrooms'] / df['total_rooms']

4.2 地理特征计算

地理位置是影响房价的重要因素之一。我们使用Haversine公式计算每个房屋到旧金山的距离,作为地理特征。地理特征计算部分的代码如下:

def _add_geo_features(self, df: pd.DataFrame) -> pd.DataFrame:
    """精确地理特征计算"""
    san_francisco_coord = (37.7749, -122.4194)
    R = 6371  # 地球半径(千米)

    lat1 = np.radians(df['latitude'])
    lon1 = np.radians(df['longitude'])
    lat2, lon2 = np.radians(san_francisco_coord[0]), np.radians(san_francisco_coord[1])

    dlon = lon2 - lon1
    dlat = lat2 - lat1

    a = np.sin(dlat / 2) ** 2 + np.cos(lat1) * np.cos(lat2) * np.sin(dlon / 2) ** 2
    c = 2 * np.arctan2(np.sqrt(a), np.sqrt(1 - a))
    df['dist_to_sf_km'] = R * c

    return df

4.3 分箱处理

分箱处理是将连续变量离散化的一种方法,可以减少数据的噪声和异常值的影响。我们使用KBinsDiscretizer对房屋的中位年龄进行分箱处理,将其分为5个等级。分箱处理部分的代码如下:

def _binning_features(self, df: pd.DataFrame) -> pd.DataFrame:
    """安全分箱处理"""
    try:
        binner = KBinsDiscretizer(n_bins=5, encode='ordinal', strategy='quantile')
        df['housing_age_bin'] = binner.fit_transform(
            df[['housing_median_age']]
        ).astype(int)
    except ValueError as e:
        logger.warning("分箱失败: %s,使用原始特征", str(e))
    return df

4.4 分类变量编码

数据集中的ocean_proximity是一个分类变量,我们使用pandasget_dummies函数对其进行独热编码,将其转换为数值特征。分类变量编码部分的代码如下:

# 分类变量编码
return pd.get_dummies(df, columns=['ocean_proximity'], drop_first=True)

五、特征选择

特征选择是从原始特征中选择出最相关、最有价值的特征,以减少特征维度,提高模型的训练效率和泛化能力。在本项目中,我们使用递归特征消除(Recursive Feature Elimination,RFE)方法进行特征选择。具体来说,我们使用RFECV类,以XGBRegressor作为估计器,通过交叉验证选择最优的特征子集。特征选择部分的代码如下:

def feature_selection(self, X: pd.DataFrame, y: pd.Series) -> pd.DataFrame:
    """递归特征消除(带异常处理)"""
    try:
        self.feature_selector = RFECV(
            estimator=XGBRegressor(),
            step=1,
            cv=5,
            scoring='neg_mean_absolute_error',
            min_features_to_select=10
        )
        X_selected = self.feature_selector.fit_transform(X, y)
        self.feature_names = X.columns[self.feature_selector.support_]
        logger.info("特征选择完成,保留特征数:%d", len(self.feature_names))
        return X_selected
    except Exception as e:
        logger.error("特征选择失败: %s,使用原始特征", str(e))
        return X.values

六、超参数调优

超参数是机器学习模型中需要手动设置的参数,不同的超参数组合会对模型的性能产生显著影响。在本项目中,我们使用贝叶斯优化方法对XGBRegressor的超参数进行调优。贝叶斯优化是一种基于概率模型的优化方法,通过不断地探索超参数空间,找到最优的超参数组合。超参数调优部分的代码如下:

def hyperparameter_tuning(self, X: np.ndarray, y: np.ndarray) -> Dict:
    """鲁棒的贝叶斯优化"""

    def xgb_cv(max_depth, learning_rate, n_estimators, subsample):
        params = {
            'max_depth': int(round(max_depth)),
            'learning_rate': max(learning_rate, 0.001),
            'n_estimators': int(round(n_estimators)),
            'subsample': max(min(subsample, 1), 0.1),
            'eval_metric': 'mae'
        }
        try:
            cv_scores = cross_val_score(
                XGBRegressor(**params), X, y,
                cv=5, scoring='neg_mean_absolute_error', n_jobs=-1
            )
            return cv_scores.mean()
        except Exception as e:
            logger.warning("参数调优失败: %s", str(e))
            return -np.inf

    optimizer = BayesianOptimization(
        f=xgb_cv,
        pbounds={
            'max_depth': (3, 10),
            'learning_rate': (0.01, 0.3),
            'n_estimators': (100, 1000),
            'subsample': (0.5, 1)
        },
        random_state=42,
        allow_duplicate_points=True
    )

    # 移除不支持的参数设置
    # optimizer.set_gp_params(n_jobs=-1)
    optimizer.maximize(init_points=5, n_iter=5)

    if not optimizer.res:
        raise RuntimeError("贝叶斯优化失败,无有效结果")

    self.best_params = optimizer.max['params']
    logger.info("最佳参数:%s", self.best_params)
    return self.best_params

七、模型训练

在完成特征选择和超参数调优后,我们使用集成学习的方法训练一个堆叠模型(Stacking Model)。堆叠模型是一种将多个基础模型的预测结果作为输入,再通过一个元模型进行最终预测的模型。在本项目中,我们使用XGBRegressorRandomForestRegressor作为基础模型,LassoCV作为元模型。模型训练部分的代码如下:

def train_stacking_model(self, X: np.ndarray, y: np.ndarray):
    """训练集成模型"""
    try:
        base_models = [
            ('xgb', XGBRegressor(**{
                'max_depth': int(self.best_params['max_depth']),
                'learning_rate': self.best_params['learning_rate'],
                'n_estimators': int(self.best_params['n_estimators']),
                'subsample': self.best_params['subsample']
            })),
            ('rf', RandomForestRegressor(
                n_estimators=200,
                max_depth=8,
                n_jobs=-1
            ))
        ]
        self.model = StackingRegressor(
            estimators=base_models,
            final_estimator=LassoCV(cv=5),
            cv=3,
            n_jobs=-1
        )
        logger.info("开始训练Stacking模型...")
        self.model.fit(X, y)
        self._is_trained = True
        return self.model
    except Exception as e:
        logger.error("模型训练失败: %s", str(e))
        self._is_trained = False
        raise

八、模型评估

模型评估是衡量模型性能的重要步骤,通过计算不同的评估指标,我们可以了解模型的预测能力和泛化能力。在本项目中,我们使用以下几个评估指标来评估模型的性能:

  • 平均绝对误差(MAE):预测值与真实值之间的平均绝对误差,反映了模型预测的平均偏差程度。
  • 均方根误差(RMSE):预测值与真实值之间的均方根误差,对异常值比较敏感,反映了模型预测的整体误差程度。
  • 决定系数(R²):表示模型对数据的拟合程度,取值范围为[0, 1],越接近1表示模型拟合效果越好。
  • 平均绝对百分比误差(MAPE):预测值与真实值之间的平均绝对百分比误差,反映了模型预测的相对误差程度。
  • 准确率(误差≤20%):预测值与真实值之间的误差在20%以内的样本占总样本的比例,反映了模型的预测准确性。

模型评估部分的代码如下:

def evaluate(self, y_true: np.ndarray, y_pred: np.ndarray) -> Dict:
    """综合模型评估"""
    if not self._is_trained:
        raise RuntimeError("模型未训练")

    metrics = {
            'MAE': mean_absolute_error(y_true, y_pred),
            'RMSE': np.sqrt(mean_squared_error(y_true, y_pred)),
            'R²': r2_score(y_true, y_pred),
            'MAPE': mean_absolute_percentage_error(y_true, y_pred),
            '准确率(误差≤20%)': np.mean(np.abs((y_pred - y_true) / y_true) <= 0.2) * 100
        }

九、运行结果

在这里插入图片描述
在这里插入图片描述

MAE: 28651.010141975672
RMSE: 44915.080348747775: 0.8543913449901033
MAPE: 0.1496618109522733
准确率(误差≤20%): 76.40009782342871
示例预测结果:171846.38 vs 真实值:178100.00

可以调参重新运行使模型准确率更高(本文只给出了部分代码
在这里插入图片描述

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到