随机森林回归实战:从数据预处理到模型评估的完整避坑指南

1次阅读
没有评论

共计 2978 个字符,预计需要花费 8 分钟才能阅读完成。

image.webp

从 NaN 指标看模型可信度危机

上周帮学弟检查房价预测项目时,模型输出全为 NaN 的评估指标让我印象深刻:

随机森林回归实战:从数据预处理到模型评估的完整避坑指南

print(f"MSE: {mse:.2f}, R²: {r2:.2f}")
# 输出: MSE: nan, R²: nan

这种‘模型看似运行但实际失效’的情况,比直接报错更危险。经过排查发现是数据中存在 -999 表示的缺失值,而随机森林 (Random Forest) 默认不进行缺失值自动处理。

树模型处理缺失值的差异

不同树模型对缺失值的容忍度差异显著:

  • 单棵决策树:可通过 surrogate splits 技术处理
  • GBDT 梯度提升树:默认通过梯度方向推断缺失值
  • 随机森林:必须显式处理缺失值(这也是本文问题的根源)

完整解决方案

1. 数据清洗实战

先创建包含典型问题的演示数据:

import pandas as pd
import numpy as np

data = {'面积': [88, np.nan, 120, 75, -999],
    '楼层': [5, 3, 99, 2, 1],  # 99 是异常值
    '房龄': ['10 年', '5 年', '未知', '20 年', '3 年']  # 文本型数据
}
df = pd.DataFrame(data)

关键清洗步骤:

  1. 缺失值标记 :将特殊值(-999) 转为 np.nan

    df.replace(-999, np.nan, inplace=True)

  2. 异常值过滤:用 IQR 方法处理

    Q1 = df['楼层'].quantile(0.25)
    Q3 = df['楼层'].quantile(0.75)
    df = df[~((df['楼层'] < (Q1 - 1.5*(Q3-Q1))) | 
               (df['楼层'] > (Q3 + 1.5*(Q3-Q1))))]

  3. 文本特征提取:正则表达式提取数字

    df['房龄'] = df['房龄'].str.extract('(\d+)').astype(float)

2. 特征工程标准化流程

使用 ColumnTransformer 构建处理管道:

from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import OneHotEncoder

# 数值型特征处理
num_transformer = Pipeline(steps=[('imputer', SimpleImputer(strategy='median')),
    ('scaler', StandardScaler()) 
])

# 类别型特征处理
cat_transformer = Pipeline(steps=[('imputer', SimpleImputer(strategy='constant', fill_value='missing')),
    ('onehot', OneHotEncoder(handle_unknown='ignore'))
])

preprocessor = ColumnTransformer(
    transformers=[('num', num_transformer, ['面积', '房龄']),
        ('cat', cat_transformer, ['区域'])  # 假设有类别特征
    ])

3. 关键超参数解析

随机森林有两大核心参数组:

  • 森林结构参数
  • n_estimators:树的数量(建议 100-500)
  • max_features:每棵树使用的特征比例(常用 sqrt 或 log2)

  • 单树参数

  • max_depth:控制树复杂度(防止过拟合)
  • min_samples_leaf:叶节点最小样本数

推荐使用 Optuna 进行自动化调参:

import optuna

def objective(trial):
    params = {'n_estimators': trial.suggest_int('n_estimators', 100, 500),
        'max_depth': trial.suggest_int('max_depth', 3, 10),
        'min_samples_leaf': trial.suggest_int('min_samples_leaf', 1, 10)
    }
    model = RandomForestRegressor(**params)
    return -cross_val_score(model, X, y, cv=5, 
                           scoring='neg_mean_squared_error').mean()

study = optuna.create_study()
study.optimize(objective, n_trials=50)

4. 健壮性评估代码

添加指标计算时的异常保护:

from sklearn.metrics import mean_squared_error, r2_score
import sys

def safe_evaluate(y_true, y_pred):
    try:
        mse = mean_squared_error(y_true, y_pred)
        r2 = r2_score(y_true, y_pred)

        if np.isnan(mse) or np.isinf(mse):
            raise ValueError("Invalid MSE value")

        return {'mse': mse, 'r2': r2}
    except Exception as e:
        print(f"评估失败: {str(e)}", file=sys.stderr)
        return None

性能优化技巧

特征重要性分析

可视化 TOP20 重要特征:

import matplotlib.pyplot as plt

feature_importances = model.feature_importances_
indices = np.argsort(feature_importances)[-20:]

plt.figure(figsize=(10,6))
plt.title('Feature Importances')
plt.barh(range(len(indices)), 
         feature_importances[indices],
         color='b', align='center')
plt.yticks(range(len(indices)), 
           [features[i] for i in indices])
plt.show()

并行计算加速

设置 n_jobs 参数利用多核:

# 使用所有 CPU 核心
model = RandomForestRegressor(n_jobs=-1) 

# 内存不足时可限制核心数
# model = RandomForestRegressor(n_jobs=4)

思考与总结

当 R²出现负值时,通常意味着:
1. 模型预测比直接使用均值更差
2. 可能出现了特征与目标完全无关的情况
3. 数据存在严重非线性或离群点

建议检查数据分布:

import seaborn as sns
sns.jointplot(x=df['重要特征'], y=df['目标值'], 
              kind='reg', height=8)

通过本文的完整流程,我们系统解决了随机森林回归中的 NaN 指标问题。记住三个关键点:
1. 数据质量决定模型上限
2. 树模型不代表不需要特征工程
3. 评估指标需要异常保护

下一步可以尝试:
– 用 SHAP 值解释模型预测
– 实验不同树模型集成方法
– 探索自动特征生成工具 featuretools

正文完
 0
评论(没有评论)