共计 2978 个字符,预计需要花费 8 分钟才能阅读完成。
从 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)
关键清洗步骤:
-
缺失值标记 :将特殊值(-999) 转为 np.nan
df.replace(-999, np.nan, inplace=True) -
异常值过滤:用 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))))] -
文本特征提取:正则表达式提取数字
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
正文完
发表至: 未分类
近一天内
