Skip to content

预测建模实战指南

从原始数据到可部署模型——端到端 ML 项目的完整流程


🧭 标准工作流

问题定义 → 数据获取 → EDA → 特征工程 → 建模 → 评估 → 部署
  (1天)    (1-3天)   (2天)   (3-5天)   (2天)   (1天)   (持续)

80/20 法则:特征工程占 80% 的时间,模型选择占 20%。


第一步:问题定义

在写任何代码之前,先回答:

  1. 业务目标是什么?(提升转化率?减少流失?预测库存?)
  2. 这是一个什么类型的问题?(分类 / 回归 / 聚类 / 排序)
  3. 成功的标准是什么?(准确率 > 85%?AUC > 0.8?MAE < 10?)
  4. 用什么指标评估业务价值?(不是准确率——是 ROI、节省的成本、提升的收入)

问题类型速查

你想要什么输出? 问题类型 示例 典型算法
是/否 二分类 用户会流失吗? XGBoost、LightGBM
多选一 多分类 用户属于哪个群体? LightGBM、CatBoost
一个数值 回归 用户会花多少钱? XGBoost、线性回归
分组 聚类 哪些用户行为相似? K-Means、DBSCAN
排序 Learning to Rank 推荐前 10 个商品 LambdaMART
概率 概率预测 用户明天回来的概率? XGBoost + 校准

第二步:探索性数据分析(EDA)

建模前最重要的一步——了解你的数据。

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

# 1. 快速概览
print(f"形状: {df.shape}")
print(f"缺失值:\n{df.isnull().sum()[df.isnull().sum() > 0]}")
print(f"数据类型:\n{df.dtypes.value_counts()}")

# 2. 目标变量分布
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
df['target'].hist(bins=50, ax=axes[0])
axes[0].set_title('目标变量分布')

# 分类问题 → 检查类别平衡
df['target'].value_counts().plot(kind='bar', ax=axes[1])
axes[1].set_title('类别分布')

# 3. 相关性热力图
plt.figure(figsize=(12, 10))
sns.heatmap(df.corr(), annot=False, cmap='RdBu_r', center=0)
plt.title('特征相关性矩阵')

# 4. 特征 vs 目标的关系
# 分类问题 → 箱线图
for col in num_cols[:5]:
    df.boxplot(column=col, by='target', figsize=(6, 4))
    plt.title(f'{col} vs Target')
    plt.show()

EDA 检查清单

  • 数据量是否足够?(经验:特征数 × 50 是最小样本量)
  • 缺失值比例 > 50% 的列 → 考虑删除
  • 目标变量是否严重不平衡?(< 5% 的正样本 → 需要特殊处理)
  • 是否存在数据泄露?(如:用"未来"信息预测"过去")
  • 训练集和测试集的分布是否一致?

第三步:数据预处理

from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler, LabelEncoder
from sklearn.impute import SimpleImputer

# 1. 划分训练/测试集(在一切预处理之前!)
X = df.drop('target', axis=1)
y = df['target']
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y  # 分层抽样
)

# 2. 缺失值处理
num_cols = X_train.select_dtypes(include=np.number).columns
cat_cols = X_train.select_dtypes(include='object').columns

# 数值特征 → 中位数填充
num_imputer = SimpleImputer(strategy='median')
X_train[num_cols] = num_imputer.fit_transform(X_train[num_cols])
X_test[num_cols] = num_imputer.transform(X_test[num_cols])

# 分类特征 → 众数填充
cat_imputer = SimpleImputer(strategy='most_frequent')
X_train[cat_cols] = cat_imputer.fit_transform(X_train[cat_cols])
X_test[cat_cols] = cat_imputer.transform(X_test[cat_cols])

# 3. 分类变量编码
for col in cat_cols:
    le = LabelEncoder()
    X_train[col] = le.fit_transform(X_train[col])
    X_test[col] = le.transform(X_test[col])

# 4. 标准化(用于线性模型、神经网络;树模型不需要!)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

第四步:特征工程

特征工程比算法选择更能决定模型上限。

常用特征变换

# 1. 数学变换(偏态分布 → 正态化)
df['amount_log'] = np.log1p(df['amount'])
df['amount_sqrt'] = np.sqrt(df['amount'])

# 2. 分箱 → 捕捉非线性关系
df['age_bin'] = pd.cut(df['age'], bins=[0, 25, 35, 50, 100],
                        labels=['青年', '壮年', '中年', '老年'])

# 3. 交互特征
df['income_per_age'] = df['income'] / (df['age'] + 1)

# 4. 时间特征
df['day_of_week'] = df['timestamp'].dt.dayofweek
df['hour'] = df['timestamp'].dt.hour
df['is_weekend'] = df['day_of_week'].isin([5, 6]).astype(int)

# 5. 聚合特征(RFM 分析的基础)
user_agg = df.groupby('user_id').agg(
    recency=('timestamp', lambda x: (df['timestamp'].max() - x.max()).days),
    frequency=('order_id', 'count'),
    monetary=('amount', 'sum')
)

特征选择

from sklearn.feature_selection import mutual_info_classif, SelectKBest

# 互信息 → 捕捉非线性关系
mi_scores = mutual_info_classif(X_train, y_train, random_state=42)
mi_df = pd.DataFrame({'feature': X_train.columns, 'mi_score': mi_scores})
mi_df = mi_df.sort_values('mi_score', ascending=False)

# 选择 Top 20 特征
selector = SelectKBest(mutual_info_classif, k=20)
X_train_selected = selector.fit_transform(X_train, y_train)

print("Top 10 特征:")
print(mi_df.head(10))

第五步:建模与调参

基线模型 → 快速试错

from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from xgboost import XGBClassifier
from lightgbm import LGBMClassifier
from sklearn.model_selection import cross_val_score

models = {
    'Logistic Regression': LogisticRegression(max_iter=1000),
    'Random Forest': RandomForestClassifier(n_estimators=100, random_state=42),
    'XGBoost': XGBClassifier(n_estimators=100, random_state=42, eval_metric='logloss'),
    'LightGBM': LGBMClassifier(n_estimators=100, random_state=42, verbose=-1),
}

for name, model in models.items():
    scores = cross_val_score(model, X_train_scaled, y_train,
                             cv=5, scoring='roc_auc')
    print(f"{name}: AUC = {scores.mean():.4f} (+/- {scores.std()*2:.4f})")

超参数调优

from sklearn.model_selection import RandomizedSearchCV

# LightGBM 超参数搜索
param_dist = {
    'num_leaves': [31, 63, 127],
    'max_depth': [-1, 5, 10, 15],
    'learning_rate': [0.01, 0.05, 0.1],
    'min_child_samples': [20, 50, 100],
    'subsample': [0.6, 0.8, 1.0],
    'colsample_bytree': [0.6, 0.8, 1.0],
}

lgbm = LGBMClassifier(n_estimators=500, random_state=42, verbose=-1)
search = RandomizedSearchCV(
    lgbm, param_dist, n_iter=30, cv=5,
    scoring='roc_auc', random_state=42, n_jobs=-1
)
search.fit(X_train, y_train)

print(f"最佳参数: {search.best_params_}")
print(f"最佳 AUC: {search.best_score_:.4f}")

第六步:模型评估

分类问题

from sklearn.metrics import (
    accuracy_score, precision_score, recall_score,
    f1_score, roc_auc_score, confusion_matrix,
    classification_report
)

y_pred = search.best_estimator_.predict(X_test)
y_proba = search.best_estimator_.predict_proba(X_test)[:, 1]

print("=== 分类报告 ===")
print(classification_report(y_test, y_pred))

print(f"AUC: {roc_auc_score(y_test, y_proba):.4f}")

# 混淆矩阵
cm = confusion_matrix(y_test, y_pred)
print(f"混淆矩阵:\n{cm}")

# 关键业务指标
tn, fp, fn, tp = cm.ravel()
print(f"精确率 (Precision): {tp/(tp+fp):.4f} — 预测为正的里面有多少真的正")
print(f"召回率 (Recall):    {tp/(tp+fn):.4f} — 真正的正例被找出了多少")

回归问题

from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score

y_pred = model.predict(X_test)

mae = mean_absolute_error(y_test, y_pred)
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
r2 = r2_score(y_test, y_pred)

print(f"MAE:  {mae:.2f}  — 平均绝对误差")
print(f"RMSE: {rmse:.2f} — 均方根误差(对大误差更敏感)")
print(f"R²:   {r2:.4f}  — 解释的方差比例")

特征重要性

# LightGBM / XGBoost 特征重要性
importance = search.best_estimator_.feature_importances_
feat_imp = pd.DataFrame({
    'feature': X_train.columns,
    'importance': importance
}).sort_values('importance', ascending=False)

plt.figure(figsize=(10, 6))
plt.barh(feat_imp['feature'].head(20)[::-1],
         feat_imp['importance'].head(20)[::-1])
plt.xlabel('Feature Importance')
plt.title('Top 20 特征重要性')
plt.tight_layout()

🚫 常见陷阱

陷阱 1:数据泄露

# ❌ 错误:在划分前做标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)  # 用到了测试集信息!
X_train, X_test = train_test_split(X_scaled, y)

# ✅ 正确
X_train, X_test, y_train, y_test = train_test_split(X, y)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)  # 只用训练集的参数

陷阱 2:用准确率衡量不平衡数据

99% 不流失,1% 流失 → 全部预测"不流失"就能达到 99% 准确率,但完全没用。

正确做法:用 AUC、F1、Precision-Recall 曲线。

陷阱 3:过拟合

训练集 AUC 0.99,测试集 AUC 0.75 → 过拟合。

防御: - 交叉验证 - 正则化(L1/L2) - Early stopping - 减少特征数量


🔗 相关资源