返回学习路线
6模型进阶

第 6 周:GBDT 与特征工程

XGBoost/LightGBM、类别特征处理、SHAP 解释

6.2.3 支持向量机

**支持向量机(Support Vector Machine, SVM)**由Vapnik于1995年提出,是强大的分类和回归算法。其核心思想是寻找最优超平面,使得两类样本之间的间隔最大化。

硬间隔SVM假设数据线性可分,优化问题为:

minw,b12w2\min_{w,b} \frac{1}{2}\|w\|^2 s.t.yi(wTxi+b)1,i=1,...,ms.t. \quad y_i(w^T x_i + b) \geq 1, \quad i=1,...,m

软间隔SVM通过引入松弛变量处理噪声和近似线性可分情况:

minw,b,ξ12w2+Ci=1mξi\min_{w,b,\xi} \frac{1}{2}\|w\|^2 + C\sum_{i=1}^{m}\xi_i s.t.yi(wTxi+b)1ξi,ξi0s.t. \quad y_i(w^T x_i + b) \geq 1 - \xi_i, \quad \xi_i \geq 0

其中CC为惩罚参数,控制对误分类的容忍度。

**核技巧(Kernel Trick)**将数据映射到高维特征空间,使非线性可分问题转化为线性可分。常用核函数包括:

  • 线性核K(xi,xj)=xiTxjK(x_i, x_j) = x_i^T x_j
  • 多项式核K(xi,xj)=(γxiTxj+r)dK(x_i, x_j) = (\gamma x_i^T x_j + r)^d
  • RBF核(高斯核)K(xi,xj)=exp(γxixj2)K(x_i, x_j) = \exp(-\gamma\|x_i - x_j\|^2)
  • Sigmoid核K(xi,xj)=tanh(γxiTxj+r)K(x_i, x_j) = \tanh(\gamma x_i^T x_j + r)

核技巧的关键在于:无需显式计算高维映射ϕ(x)\phi(x),直接通过核函数计算内积K(xi,xj)=ϕ(xi)Tϕ(xj)K(x_i, x_j) = \phi(x_i)^T \phi(x_j)

from sklearn.svm import SVC

# 线性SVM
svm_linear = SVC(kernel='linear', C=1.0)
svm_linear.fit(X_train, y_train)

# RBF核SVM
svm_rbf = SVC(kernel='rbf', C=1.0, gamma='scale')
svm_rbf.fit(X_train, y_train)

print(f"Linear SVM Accuracy: {svm_linear.score(X_test, y_test):.4f}")
print(f"RBF SVM Accuracy: {svm_rbf.score(X_test, y_test):.4f}")

6.2.4 K近邻与朴素贝叶斯

**K近邻算法(K-Nearest Neighbors, KNN)**是一种惰性学习算法,无需显式训练过程。预测时,找到测试样本在特征空间中的K个最近邻居,通过投票(分类)或平均(回归)确定预测结果。

距离度量是关键设计选择:

  • 欧氏距离d(x,y)=i=1n(xiyi)2d(x, y) = \sqrt{\sum_{i=1}^{n}(x_i - y_i)^2}
  • 曼哈顿距离d(x,y)=i=1nxiyid(x, y) = \sum_{i=1}^{n}|x_i - y_i|
  • 闵可夫斯基距离d(x,y)=(i=1nxiyip)1/pd(x, y) = (\sum_{i=1}^{n}|x_i - y_i|^p)^{1/p}

K值的选择需要权衡:较小的K值对噪声敏感,模型复杂度高;较大的K值决策边界平滑,但可能欠拟合。通常通过交叉验证选择最优K值。

**朴素贝叶斯(Naive Bayes)**基于贝叶斯定理和特征条件独立性假设:

P(ykx)=P(xyk)P(yk)P(x)P(y_k|x) = \frac{P(x|y_k)P(y_k)}{P(x)}

由于分母对所有类别相同,只需比较分子:

y^=argmaxykP(yk)i=1nP(xiyk)\hat{y} = \arg\max_{y_k} P(y_k)\prod_{i=1}^{n}P(x_i|y_k)

条件概率P(xiyk)P(x_i|y_k)的估计方法决定了朴素贝叶斯的变体:

  • 高斯朴素贝叶斯:假设连续特征服从高斯分布
  • 多项式朴素贝叶斯:适用于离散计数特征(如文本词频)
  • 伯努利朴素贝叶斯:适用于二元特征

朴素贝叶斯的优势在于训练速度快、对缺失数据不敏感、适合高维数据;缺点在于特征独立性假设在实际中很少成立。

from sklearn.neighbors import KNeighborsClassifier
from sklearn.naive_bayes import GaussianNB

# KNN
knn = KNeighborsClassifier(n_neighbors=5, metric='euclidean')
knn.fit(X_train, y_train)
print(f"KNN Accuracy: {knn.score(X_test, y_test):.4f}")

# 朴素贝叶斯
nb = GaussianNB()
nb.fit(X_train, y_train)
print(f"Naive Bayes Accuracy: {nb.score(X_test, y_test):.4f}")

决策边界对比

图6-3:六种经典分类算法在不同数据集上的决策边界对比。从左到右分别为线性可分数据、月亮形数据(非线性)和环形数据(非线性)。可以观察到:逻辑回归产生线性边界;SVM(RBF)能够适应复杂非线性模式;决策树产生轴对齐的矩形边界;随机森林边界更平滑;KNN边界不规则;朴素贝叶斯产生平滑的二次决策边界。

表6-1:监督学习算法特性对比

算法训练速度预测速度可解释性处理非线性对异常值敏感适用数据规模
线性回归极快大规模
逻辑回归极快大规模
决策树中小规模
随机森林大规模
SVM中小规模
KNN极快小规模
朴素贝叶斯极快极快大规模

上表总结了各算法的核心特性,可作为算法选择的参考依据。实际应用中,建议从简单模型开始,逐步尝试更复杂的算法,通过交叉验证选择最优方案。

6.5 机器学习实践

6.5.1 Scikit-learn使用指南

Scikit-learn是Python生态系统中最为广泛使用的机器学习库,提供了统一且简洁的API设计。其核心设计原则包括:

  • 一致性:所有估计器(Estimator)都实现了fit()方法,所有预测器都实现了predict()方法
  • 可检验:所有超参数都可通过公共属性访问
  • 标准输入:接受NumPy数组或Pandas DataFrame作为输入
  • 可组合:通过Pipeline机制组合多个处理步骤

核心API模式

# 通用使用模式
from sklearn.module import ModelClass

# 1. 实例化模型
model = ModelClass(param1=value1, param2=value2)

# 2. 训练模型
model.fit(X_train, y_train)

# 3. 进行预测
predictions = model.predict(X_test)

# 4. 评估性能
score = model.score(X_test, y_test)

Pipeline机制将数据预处理和模型训练串联成工作流,确保交叉验证时预处理步骤的正确性:

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA
from sklearn.svm import SVC

# 构建Pipeline
pipeline = Pipeline([
    ('scaler', StandardScaler()),      # 步骤1: 标准化
    ('pca', PCA(n_components=10)),     # 步骤2: PCA降维
    ('classifier', SVC(C=1.0))         # 步骤3: SVM分类
])

# 使用Pipeline
pipeline.fit(X_train, y_train)
predictions = pipeline.predict(X_test)

# 交叉验证
from sklearn.model_selection import cross_val_score
scores = cross_val_score(pipeline, X, y, cv=5)
print(f"CV Accuracy: {scores.mean():.4f} (+/- {scores.std()*2:.4f})")

ColumnTransformer用于对不同列应用不同预处理:

from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder, StandardScaler

# 定义数值和类别特征
numeric_features = ['age', 'income', 'score']
categorical_features = ['gender', 'city', 'category']

# 构建预处理器
preprocessor = ColumnTransformer(
    transformers=[
        ('num', StandardScaler(), numeric_features),
        ('cat', OneHotEncoder(handle_unknown='ignore'), categorical_features)
    ])

# 完整Pipeline
clf = Pipeline([
    ('preprocessor', preprocessor),
    ('classifier', RandomForestClassifier())
])

6.5.2 端到端项目流程

以下通过一个完整的分类项目展示从数据到部署的完整流程。

项目:客户流失预测

步骤1:数据加载与探索

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split, GridSearchCV, cross_val_score
from sklearn.preprocessing import StandardScaler, LabelEncoder
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import (classification_report, roc_auc_score, 
                             confusion_matrix, roc_curve)
import matplotlib.pyplot as plt
import seaborn as sns

# 加载数据
df = pd.read_csv('customer_churn.csv')

# 数据探索
print(f"数据集形状: {df.shape}")
print(f"\n数据类型:\n{df.dtypes}")
print(f"\n缺失值:\n{df.isnull().sum()}")
print(f"\n目标变量分布:\n{df['churn'].value_counts(normalize=True)}")

# 描述性统计
print(df.describe())

步骤2:数据预处理

# 处理缺失值
df['total_charges'] = pd.to_numeric(df['total_charges'], errors='coerce')
df['total_charges'].fillna(df['total_charges'].median(), inplace=True)

# 编码类别变量
categorical_cols = df.select_dtypes(include=['object']).columns.tolist()
categorical_cols.remove('customer_id')  # 保留ID列
categorical_cols.remove('churn')        # 目标变量单独处理

# 二元编码
df['churn'] = df['churn'].map({'Yes': 1, 'No': 0})
df['gender'] = df['gender'].map({'Male': 1, 'Female': 0})
df['partner'] = df['partner'].map({'Yes': 1, 'No': 0})
df['dependents'] = df['dependents'].map({'Yes': 1, 'No': 0})

# One-Hot编码其他类别变量
df = pd.get_dummies(df, columns=[c for c in categorical_cols if c not in 
                                  ['gender', 'partner', 'dependents']], 
                    drop_first=True)

# 分离特征和目标
X = df.drop(['customer_id', 'churn'], axis=1)
y = df['churn']

# 划分数据集
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y)

步骤3:特征工程

# 创建新特征
X_train['avg_monthly_charge'] = X_train['total_charges'] / (X_train['tenure'] + 1)
X_test['avg_monthly_charge'] = X_test['total_charges'] / (X_test['tenure'] + 1)

# 特征交互
X_train['tenure_contract'] = X_train['tenure'] * X_train['contract_One year']
X_test['tenure_contract'] = X_test['tenure'] * X_test['contract_One year']

# 标准化数值特征
scaler = StandardScaler()
numeric_cols = ['tenure', 'monthly_charges', 'total_charges', 'avg_monthly_charge']
X_train[numeric_cols] = scaler.fit_transform(X_train[numeric_cols])
X_test[numeric_cols] = scaler.transform(X_test[numeric_cols])

步骤4:模型训练与调优

# 定义模型
models = {
    'Logistic Regression': LogisticRegression(max_iter=1000, random_state=42),
    'Random Forest': RandomForestClassifier(random_state=42),
    'Gradient Boosting': GradientBoostingClassifier(random_state=42)
}

# 定义参数网格
param_grids = {
    'Logistic Regression': {
        'C': [0.01, 0.1, 1, 10],
        'class_weight': [None, 'balanced']
    },
    'Random Forest': {
        'n_estimators': [50, 100, 200],
        'max_depth': [5, 10, None],
        'min_samples_split': [2, 5, 10]
    },
    'Gradient Boosting': {
        'n_estimators': [50, 100, 200],
        'learning_rate': [0.01, 0.1, 0.2],
        'max_depth': [3, 5, 7]
    }
}

# 网格搜索
best_models = {}
for name, model in models.items():
    print(f"\n训练 {name}...")
    grid_search = GridSearchCV(
        model, param_grids[name], 
        cv=5, scoring='roc_auc', 
        n_jobs=-1, verbose=1
    )
    grid_search.fit(X_train, y_train)
    best_models[name] = grid_search.best_estimator_
    print(f"最佳参数: {grid_search.best_params_}")
    print(f"最佳CV AUC: {grid_search.best_score_:.4f}")

步骤5:模型评估

# 评估所有模型
results = []
for name, model in best_models.items():
    y_pred = model.predict(X_test)
    y_prob = model.predict_proba(X_test)[:, 1]
    
    results.append({
        'Model': name,
        'Accuracy': accuracy_score(y_test, y_pred),
        'Precision': precision_score(y_test, y_pred),
        'Recall': recall_score(y_test, y_pred),
        'F1': f1_score(y_test, y_pred),
        'AUC': roc_auc_score(y_test, y_prob)
    })

results_df = pd.DataFrame(results)
print(results_df.to_string(index=False))

# 绘制ROC曲线
plt.figure(figsize=(10, 8))
for name, model in best_models.items():
    y_prob = model.predict_proba(X_test)[:, 1]
    fpr, tpr, _ = roc_curve(y_test, y_prob)
    auc_score = roc_auc_score(y_test, y_prob)
    plt.plot(fpr, tpr, label=f'{name} (AUC = {auc_score:.3f})')

plt.plot([0, 1], [0, 1], 'k--', label='Random')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('ROC Curves Comparison')
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()

# 最佳模型详细评估
best_model = best_models['Gradient Boosting']
y_pred = best_model.predict(X_test)
print("\n最佳模型分类报告:")
print(classification_report(y_test, y_pred))

# 混淆矩阵
plt.figure(figsize=(8, 6))
cm = confusion_matrix(y_test, y_pred)
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues')
plt.xlabel('Predicted')
plt.ylabel('Actual')
plt.title('Confusion Matrix')
plt.show()

步骤6:特征重要性分析

# 特征重要性
feature_importance = pd.DataFrame({
    'feature': X_train.columns,
    'importance': best_model.feature_importances_
}).sort_values('importance', ascending=False)

plt.figure(figsize=(10, 8))
sns.barplot(data=feature_importance.head(15), x='importance', y='feature')
plt.title('Top 15 Feature Importance')
plt.tight_layout()
plt.show()

步骤7:模型保存与部署

import joblib

# 保存模型
joblib.dump(best_model, 'churn_prediction_model.pkl')
joblib.dump(scaler, 'scaler.pkl')

# 加载模型进行预测
loaded_model = joblib.load('churn_prediction_model.pkl')
loaded_scaler = joblib.load('scaler.pkl')

# 新数据预测
def predict_churn(new_customer_data):
    # 预处理
    new_customer_data[numeric_cols] = loaded_scaler.transform(
        new_customer_data[numeric_cols])
    # 预测
    churn_prob = loaded_model.predict_proba(new_customer_data)[:, 1]
    return churn_prob

6.5.3 常见问题与解决方案

数据不平衡问题

类别不平衡是分类任务中的常见问题,当某类样本远多于其他类时,模型倾向于预测多数类。

解决方案:

  1. 重采样方法

    • 过采样:SMOTE(合成少数类样本)
    • 欠采样:随机减少多数类样本
  2. 类别权重:在模型训练中为少数类分配更高权重

  3. 阈值调整:根据业务需求调整分类阈值

from imblearn.over_sampling import SMOTE
from imblearn.under_sampling import RandomUnderSampler
from sklearn.utils.class_weight import compute_class_weight

# SMOTE过采样
smote = SMOTE(random_state=42)
X_resampled, y_resampled = smote.fit_resample(X_train, y_train)

# 类别权重
class_weights = compute_class_weight('balanced', classes=np.unique(y_train), y=y_train)
class_weight_dict = dict(enumerate(class_weights))
model = RandomForestClassifier(class_weight=class_weight_dict)

# 阈值调整
y_prob = model.predict_proba(X_test)[:, 1]
y_pred_adjusted = (y_prob > 0.3).astype(int)  # 降低阈值提高召回率

特征缩放问题

不同特征的数值范围差异会影响基于距离的算法(KNN、SVM、神经网络)和梯度下降优化。

常用缩放方法:

  • 标准化(Standardization)x=xμσx' = \frac{x - \mu}{\sigma},适用于近似正态分布的特征
  • 归一化(Normalization)x=xxminxmaxxminx' = \frac{x - x_{min}}{x_{max} - x_{min}},适用于有界特征
  • Robust Scaling:使用中位数和四分位数,对异常值更鲁棒
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler

# 标准化
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# 归一化
normalizer = MinMaxScaler()
X_normalized = normalizer.fit_transform(X)

# Robust Scaling
robust_scaler = RobustScaler()
X_robust = robust_scaler.fit_transform(X)

过拟合与欠拟合

  • 过拟合:模型在训练集上表现好但在测试集上差,解决方案包括增加数据、正则化、简化模型、早停
  • 欠拟合:模型在训练集和测试集上都表现差,解决方案包括增加特征、使用更复杂模型、减少正则化
# 正则化示例
from sklearn.linear_model import Ridge, Lasso, ElasticNet

# L2正则化(Ridge)
ridge = Ridge(alpha=1.0)

# L1正则化(Lasso)- 同时进行特征选择
lasso = Lasso(alpha=0.1)

# 弹性网络(L1+L2)
elastic = ElasticNet(alpha=0.1, l1_ratio=0.5)

# 树模型正则化
rf = RandomForestClassifier(
    max_depth=10,           # 限制树深度
    min_samples_split=5,    # 节点分裂最小样本数
    min_samples_leaf=2,     # 叶子节点最小样本数
    max_features='sqrt'     # 每次分裂考虑的特征数
)

表6-4:常见问题与解决方案速查

问题诊断方法解决方案
数据不平衡类别分布分析SMOTE、类别权重、阈值调整
特征尺度差异特征统计描述标准化、归一化、Robust Scaling
过拟合学习曲线分析正则化、交叉验证、简化模型
欠拟合训练/测试误差高增加特征、复杂模型、减少正则化
高维数据特征数>>样本数PCA、特征选择、正则化
缺失值缺失率统计删除、填充、插值、模型预测
异常值箱线图、Z-score删除、截断、Robust方法

表6-5:算法选择决策树

场景推荐算法理由
需要可解释性逻辑回归、决策树模型透明,易于解释
大规模数据随机森林、SGD并行训练,效率高
高维稀疏数据线性SVM、朴素贝叶斯在高维空间表现好
非线性关系SVM(RBF)、梯度提升捕捉复杂模式
实时预测朴素贝叶斯、KNN预测速度快
特征重要性分析随机森林、XGBoost内置特征重要性
小样本数据SVM、高斯过程泛化能力强

本章系统介绍了经典机器学习的核心算法和实践方法。从问题定义、特征工程到模型选择、评估部署,每个环节都对项目成功至关重要。随着深度学习的发展,传统机器学习算法因其可解释性强、训练成本低、在小样本上表现好等优势,仍然在实际应用中占据重要地位。掌握这些经典算法,将为后续深度学习的学习打下坚实基础。