6.2.3 支持向量机
**支持向量机(Support Vector Machine, SVM)**由Vapnik于1995年提出,是强大的分类和回归算法。其核心思想是寻找最优超平面,使得两类样本之间的间隔最大化。
硬间隔SVM假设数据线性可分,优化问题为:
软间隔SVM通过引入松弛变量处理噪声和近似线性可分情况:
其中为惩罚参数,控制对误分类的容忍度。
**核技巧(Kernel Trick)**将数据映射到高维特征空间,使非线性可分问题转化为线性可分。常用核函数包括:
- 线性核:
- 多项式核:
- RBF核(高斯核):
- Sigmoid核:
核技巧的关键在于:无需显式计算高维映射,直接通过核函数计算内积。
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个最近邻居,通过投票(分类)或平均(回归)确定预测结果。
距离度量是关键设计选择:
- 欧氏距离:
- 曼哈顿距离:
- 闵可夫斯基距离:
K值的选择需要权衡:较小的K值对噪声敏感,模型复杂度高;较大的K值决策边界平滑,但可能欠拟合。通常通过交叉验证选择最优K值。
**朴素贝叶斯(Naive Bayes)**基于贝叶斯定理和特征条件独立性假设:
由于分母对所有类别相同,只需比较分子:
条件概率的估计方法决定了朴素贝叶斯的变体:
- 高斯朴素贝叶斯:假设连续特征服从高斯分布
- 多项式朴素贝叶斯:适用于离散计数特征(如文本词频)
- 伯努利朴素贝叶斯:适用于二元特征
朴素贝叶斯的优势在于训练速度快、对缺失数据不敏感、适合高维数据;缺点在于特征独立性假设在实际中很少成立。
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 常见问题与解决方案
数据不平衡问题
类别不平衡是分类任务中的常见问题,当某类样本远多于其他类时,模型倾向于预测多数类。
解决方案:
-
重采样方法:
- 过采样:SMOTE(合成少数类样本)
- 欠采样:随机减少多数类样本
-
类别权重:在模型训练中为少数类分配更高权重
-
阈值调整:根据业务需求调整分类阈值
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):,适用于近似正态分布的特征
- 归一化(Normalization):,适用于有界特征
- 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、高斯过程 | 泛化能力强 |
本章系统介绍了经典机器学习的核心算法和实践方法。从问题定义、特征工程到模型选择、评估部署,每个环节都对项目成功至关重要。随着深度学习的发展,传统机器学习算法因其可解释性强、训练成本低、在小样本上表现好等优势,仍然在实际应用中占据重要地位。掌握这些经典算法,将为后续深度学习的学习打下坚实基础。