机器学习作为人工智能的核心分支,通过数据驱动的方式使计算机系统能够从经验中自动改进。与基于明确规则编程的传统方法不同,机器学习算法能够从大量历史数据中提取模式,并利用这些模式对新数据进行预测或决策。本章将系统介绍经典机器学习算法的原理、实现与应用,为后续深度学习的学习奠定坚实基础。
6.1 机器学习流程
6.1.1 问题定义与数据准备
机器学习项目的成功始于清晰的问题定义。首先需要明确任务类型:监督学习(Supervised Learning)处理带有标签的数据,包括分类(预测离散类别)和回归(预测连续值)两种主要形式;无监督学习(Unsupervised Learning)处理无标签数据,主要任务包括聚类和降维;强化学习(Reinforcement Learning)则通过与环境交互来学习最优策略。
数据收集阶段需要考虑数据的代表性、质量和规模。代表性确保训练数据能够覆盖实际应用中可能遇到的各种情况;数据质量直接影响模型性能,包括准确性、完整性和一致性;数据规模则需要与模型复杂度相匹配,避免欠拟合或过拟合。
数据准备的核心步骤包括:
数据清洗:处理缺失值(删除、填充或插值)、异常值检测与处理、重复数据删除。对于缺失值,数值型特征常用均值或中位数填充,类别型特征可采用众数填充或创建"未知"类别。
数据转换:类别特征编码(One-Hot编码、标签编码)、数值特征标准化或归一化。标准化将数据转换为均值为0、标准差为1的分布:;归一化则将数据缩放到[0,1]区间:。
数据集划分:典型的划分比例为训练集70%、验证集15%、测试集15%。训练集用于模型参数学习,验证集用于超参数调优和模型选择,测试集用于最终性能评估。
6.1.2 特征工程基础
特征工程是将原始数据转换为更适合机器学习算法处理的特征表示的过程,被业界公认为机器学习项目中最重要的环节之一。良好的特征工程能够显著提升模型性能,有时甚至超过算法选择的影响。
特征选择旨在从原始特征集中筛选出最相关的子集,主要方法包括:
- 过滤法(Filter):基于统计检验选择特征,如方差阈值、卡方检验、互信息等
- 包装法(Wrapper):使用模型性能作为特征子集的评价标准,如递归特征消除(RFE)
- 嵌入法(Embedded):在模型训练过程中自动进行特征选择,如L1正则化、树模型的特征重要性
特征构造通过领域知识或数据变换创建新特征:
- 多项式特征:将特征进行乘积组合,捕捉特征间交互
- 统计特征:基于时间窗口计算均值、方差、最大值等统计量
- 编码特征:对类别特征进行目标编码、频率编码等
6.1.3 模型训练与评估
模型训练是通过优化算法调整模型参数以最小化损失函数的过程。损失函数衡量模型预测与真实值之间的差异,不同任务类型采用不同的损失函数:
- 回归任务:均方误差(MSE)、平均绝对误差(MAE)、Huber损失
- 分类任务:交叉熵损失、 hinge损失
训练过程中需要监控的关键指标包括训练损失、验证损失和学习曲线。训练损失持续下降而验证损失上升是过拟合的典型信号。
6.1.4 交叉验证与调参
**K折交叉验证(K-Fold Cross-Validation)**是评估模型泛化能力的标准方法。将数据集划分为K个子集,每次使用K-1个子集训练,剩余1个子集验证,重复K次确保每个子集都作为验证集一次。最终性能取K次验证结果的平均。

图6-1:5折交叉验证示意图。每行代表一个fold,蓝色点为训练集,红色点为验证集
K折交叉验证的优势在于:充分利用有限数据、减少划分随机性影响、提供更稳健的性能估计。常用K值为5或10,K越大计算成本越高但估计越稳定。
超参数调优的常用方法包括:
- 网格搜索(Grid Search):在预定义的参数空间中进行穷举搜索
- 随机搜索(Random Search):在参数空间中随机采样,效率通常优于网格搜索
- 贝叶斯优化:基于已评估点的结果构建代理模型,智能选择下一个评估点

图6-2:验证曲线和学习曲线。左下为5折交叉验证示意图,中下为学习曲线,右下为验证曲线
6.2 监督学习算法
6.2.1 线性回归与逻辑回归
**线性回归(Linear Regression)**是最基础的回归算法,假设目标变量与特征之间存在线性关系:
其中为截距,为各特征的系数,为误差项。模型参数通过最小二乘法估计,即最小化残差平方和:
最小二乘法的闭式解为:
**逻辑回归(Logistic Regression)**是处理二分类问题的线性模型。通过sigmoid函数将线性组合映射到(0,1)区间:
其中。模型输出可解释为样本属于正类的概率:
参数估计采用最大似然法,损失函数为对数似然的负值(交叉熵损失):
from sklearn.linear_model import LinearRegression, LogisticRegression
from sklearn.datasets import make_regression, make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, accuracy_score
# 线性回归示例
X_reg, y_reg = make_regression(n_samples=1000, n_features=5, noise=10, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X_reg, y_reg, test_size=0.2)
lr_model = LinearRegression()
lr_model.fit(X_train, y_train)
y_pred = lr_model.predict(X_test)
print(f"MSE: {mean_squared_error(y_test, y_pred):.4f}")
print(f"Coefficients: {lr_model.coef_}")
# 逻辑回归示例
X_clf, y_clf = make_classification(n_samples=1000, n_features=10, n_classes=2, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X_clf, y_clf, test_size=0.2)
log_reg = LogisticRegression(max_iter=1000)
log_reg.fit(X_train, y_train)
y_pred = log_reg.predict(X_test)
print(f"Accuracy: {accuracy_score(y_test, y_pred):.4f}")
6.2.2 决策树与集成方法
**决策树(Decision Tree)**通过递归划分特征空间进行预测。主要算法包括ID3、C4.5和CART:
- ID3:使用信息增益选择划分特征,仅处理离散特征
- C4.5:ID3的改进版,处理连续特征,使用信息增益率
- CART:既可分类也可回归,使用基尼指数或均方误差
信息熵衡量数据集的不确定性:
信息增益表示特征对降低不确定性的贡献:
基尼指数衡量从数据集中随机抽取两个样本类别不一致的概率:
**集成学习(Ensemble Learning)**通过组合多个基学习器提升性能:
**随机森林(Random Forest)**由Leo Breiman于2001年提出,是Bagging算法的扩展。它通过以下方式构建多样化的决策树集合:
- Bootstrap采样:从原始数据中有放回地随机抽取样本构建训练集
- 随机特征选择:在每个节点分裂时,随机选择mtry个特征进行最优分裂点搜索
随机森林的最终预测通过投票(分类)或平均(回归)实现。其优势包括:降低过拟合风险、提供特征重要性评估、并行训练效率高。
**梯度提升树(Gradient Boosting)**通过串行训练弱学习器,每个新学习器拟合前面所有学习器的残差。XGBoost和LightGBM是该算法的优化实现,在数据竞赛中表现优异。
from sklearn.tree import DecisionTreeClassifier, plot_tree
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
# 决策树
dt = DecisionTreeClassifier(max_depth=5, min_samples_split=10, random_state=42)
dt.fit(X_train, y_train)
print(f"Decision Tree Accuracy: {dt.score(X_test, y_test):.4f}")
# 随机森林
rf = RandomForestClassifier(n_estimators=100, max_depth=10, random_state=42)
rf.fit(X_train, y_train)
print(f"Random Forest Accuracy: {rf.score(X_test, y_test):.4f}")
# 特征重要性
importances = pd.DataFrame({
'feature': range(X_train.shape[1]),
'importance': rf.feature_importances_
}).sort_values('importance', ascending=False)
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.3 无监督学习算法
无监督学习处理没有标签的数据,目标是发现数据中隐藏的结构和模式。与监督学习相比,无监督学习更具挑战性,因为缺乏明确的评估标准,但也更贴近人类的学习方式。
6.3.1 聚类算法
K-Means算法是最广泛使用的聚类算法,通过迭代优化将数据划分为K个簇。算法步骤如下:
- 随机选择K个样本作为初始簇中心
- 将每个样本分配到距离最近的簇中心
- 重新计算每个簇的中心(所有样本的均值)
- 重复步骤2-3直到收敛
目标函数(惯性)为所有样本到其所属簇中心的距离平方和:
其中为指示变量,当样本属于簇时为1,否则为0。
K-Means的优势在于算法简单、计算效率高;缺点是需要预先指定K值、对初始中心敏感、假设簇为球形分布。
**层次聚类(Hierarchical Clustering)**构建树状的簇结构,分为凝聚式(自底向上)和分裂式(自顶向下)两种策略。凝聚式从每个样本作为独立簇开始,逐步合并最相似的簇,直到所有样本归于一个簇。
簇间距离的计算方法:
- 单链接(Single Linkage):两簇最近样本的距离
- 全链接(Complete Linkage):两簇最远样本的距离
- 平均链接(Average Linkage):两簇所有样本对距离的平均
- Ward法:合并后簇内方差增量最小
**DBSCAN(Density-Based Spatial Clustering of Applications with Noise)**是基于密度的聚类算法,能够发现任意形状的簇并识别噪声点。核心概念包括:
- 核心点:在半径内包含至少MinPts个邻居的点
- 边界点:在核心点的邻域内但自身不是核心点
- 噪声点:既不是核心点也不是边界点
DBSCAN的优势在于:无需预设簇数量、能发现任意形状簇、自动识别噪声;缺点是对参数和MinPts敏感、在密度差异大的数据集上表现不佳。

图6-4:K-Means与DBSCAN在不同数据集上的聚类效果对比。第一行为原始数据,第二行为K-Means结果(红色X标记簇中心),第三行为DBSCAN结果(黑色点为噪声)。可以观察到:K-Means假设簇为球形,在月亮形和环形数据上表现不佳;DBSCAN能够发现任意形状簇,但在密度差异大的数据上可能将不同密度区域划分为多个簇。
from sklearn.cluster import KMeans, DBSCAN, AgglomerativeClustering
from sklearn.metrics import silhouette_score
# K-Means聚类
kmeans = KMeans(n_clusters=3, random_state=42, n_init=10)
labels_kmeans = kmeans.fit_predict(X)
print(f"K-Means Silhouette Score: {silhouette_score(X, labels_kmeans):.4f}")
# DBSCAN聚类
dbscan = DBSCAN(eps=0.5, min_samples=5)
labels_dbscan = dbscan.fit_predict(X)
n_clusters = len(set(labels_dbscan)) - (1 if -1 in labels_dbscan else 0)
print(f"DBSCAN found {n_clusters} clusters")
# 层次聚类
agg = AgglomerativeClustering(n_clusters=3, linkage='ward')
labels_agg = agg.fit_predict(X)
6.3.2 降维方法
高维数据面临"维度灾难"问题:数据稀疏性增加、计算复杂度上升、可视化困难。降维技术将数据映射到低维空间,同时尽可能保留重要信息。
**主成分分析(Principal Component Analysis, PCA)**是最常用的线性降维方法。其核心思想是找到数据方差最大的投影方向(主成分),将数据投影到这些方向上实现降维。
PCA的计算步骤:
- 数据中心化:减去各特征的均值
- 计算协方差矩阵:
- 特征值分解:
- 选择主成分:按特征值大小排序,选择前k个特征向量
- 数据投影:
第个主成分解释的方差比例为:

图6-5:PCA降维分析。左上为各主成分的方差解释比例,中上为二维投影结果,右上为特征载荷(各特征对主成分的贡献)。可以观察到:第一主成分解释了超过90%的方差,花瓣长度和花瓣宽度对主成分的贡献最大。
**t-SNE(t-Distributed Stochastic Neighbor Embedding)**是强大的非线性降维技术,特别适用于高维数据的可视化。它通过保持高维空间中样本间的局部相似性来实现降维。
t-SNE的核心步骤:
- 在高维空间中计算样本对的条件概率(相似度)
- 在低维空间中初始化样本位置
- 通过梯度下降最小化高维和低维分布之间的KL散度
- 迭代优化直到收敛
t-SNE的优势在于能够揭示数据的局部结构,适合可视化;缺点包括计算成本高、结果具有随机性、全局结构可能失真。
**UMAP(Uniform Manifold Approximation and Projection)**是t-SNE的现代替代方案,在保持局部结构的同时更好地保留全局结构,且计算效率更高。
from sklearn.decomposition import PCA
from sklearn.manifold import TSNE
import umap
# PCA降维
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X)
print(f"Explained variance ratio: {pca.explained_variance_ratio_}")
# t-SNE降维
tsne = TSNE(n_components=2, random_state=42, perplexity=30)
X_tsne = tsne.fit_transform(X)
# UMAP降维
reducer = umap.UMAP(n_components=2, random_state=42)
X_umap = reducer.fit_transform(X)
表6-2:降维方法对比
| 方法 | 类型 | 计算复杂度 | 保留全局结构 | 保留局部结构 | 主要用途 |
|---|---|---|---|---|---|
| PCA | 线性 | O(nd²+d³) | 是 | 否 | 预处理、去噪 |
| Kernel PCA | 非线性 | O(n²d+n³) | 是 | 部分 | 非线性降维 |
| t-SNE | 非线性 | O(n²) | 否 | 强 | 可视化 |
| UMAP | 非线性 | O(n^1.14) | 是 | 强 | 可视化、预处理 |
6.3.3 关联规则挖掘
关联规则挖掘发现数据集中项之间的有趣关系,典型应用是购物篮分析,发现"如果购买A,则很可能购买B"这样的规则。
Apriori算法基于"频繁项集的所有子集也必须是频繁的"这一先验性质,通过迭代生成候选集并剪枝来高效发现频繁项集。
关键度量指标:
- 支持度(Support):
- 置信度(Confidence):
- 提升度(Lift):
FP-Growth算法通过构建频繁模式树(FP-Tree)压缩数据库,避免生成大量候选集,效率显著优于Apriori。
from mlxtend.frequent_patterns import apriori, association_rules
from mlxtend.preprocessing import TransactionEncoder
# 示例购物篮数据
dataset = [['牛奶', '面包', '尿布'],
['可乐', '面包', '尿布', '啤酒'],
['牛奶', '尿布', '啤酒', '鸡蛋'],
['面包', '牛奶', '尿布', '啤酒'],
['面包', '牛奶', '尿布', '可乐']]
# 编码
te = TransactionEncoder()
te_ary = te.fit(dataset).transform(dataset)
df = pd.DataFrame(te_ary, columns=te.columns_)
# 发现频繁项集
frequent_itemsets = apriori(df, min_support=0.6, use_colnames=True)
print(frequent_itemsets)
# 生成关联规则
rules = association_rules(frequent_itemsets, metric="confidence", min_threshold=0.7)
print(rules[['antecedents', 'consequents', 'support', 'confidence', 'lift']])
6.4 模型评估与选择
6.4.1 分类评估指标
分类模型的评估需要多维度指标,单一指标往往无法全面反映模型性能。
**混淆矩阵(Confusion Matrix)**是评估分类模型的基础:
| 实际\预测 | 正类 | 负类 |
|---|---|---|
| 正类 | TP | FN |
| 负类 | FP | TN |
基于混淆矩阵的核心指标:
- 准确率(Accuracy):
- 精确率(Precision):
- 召回率(Recall):
- F1分数:
准确率在不平衡数据集上具有误导性。例如,在99%样本为负类的数据集中,始终预测负类即可获得99%准确率,但模型毫无实用价值。
**宏平均(Macro-average)和加权平均(Weighted-average)**用于多分类评估:
- 宏平均:各类别指标简单平均,平等对待每个类别
- 加权平均:按类别样本数加权平均,考虑类别不平衡
6.4.2 回归评估指标
- 均方误差(MSE):
- 均方根误差(RMSE):,与目标变量同量纲
- 平均绝对误差(MAE):,对异常值更鲁棒
- 决定系数(R²):,解释模型对数据变异的解释比例
6.4.3 ROC曲线与PR曲线
**ROC曲线(Receiver Operating Characteristic)**以假正率(FPR)为横轴、真正率(TPR)为纵轴绘制,展示不同分类阈值下的模型性能。
**AUC(Area Under Curve)**是ROC曲线下面积,取值范围为[0,1]:
- AUC = 0.5:模型等同于随机猜测
- AUC > 0.8:模型具有良好区分能力
- AUC = 1.0:完美分类器
AUC的统计解释:随机抽取一个正样本和一个负样本,正样本得分高于负样本的概率。
**PR曲线(Precision-Recall Curve)**以召回率为横轴、精确率为纵轴,在类别不平衡情况下比ROC曲线更具信息量。曲线下面积(AP)反映模型在不同阈值下的综合表现。

图6-6:分类模型评估可视化。左上为ROC曲线对比,SVM和随机森林的AUC最高;右上为PR曲线,在类别不平衡情况下更具参考价值;左下为混淆矩阵热力图;右下为各模型多指标对比。
from sklearn.metrics import (roc_curve, auc, precision_recall_curve,
average_precision_score, classification_report)
# 训练模型并预测概率
model = RandomForestClassifier(random_state=42)
model.fit(X_train, y_train)
y_score = model.predict_proba(X_test)[:, 1]
y_pred = model.predict(X_test)
# ROC曲线
fpr, tpr, thresholds = roc_curve(y_test, y_score)
roc_auc = auc(fpr, tpr)
print(f"AUC: {roc_auc:.4f}")
# PR曲线
precision, recall, _ = precision_recall_curve(y_test, y_score)
ap = average_precision_score(y_test, y_score)
print(f"Average Precision: {ap:.4f}")
# 完整分类报告
print(classification_report(y_test, y_pred))
6.4.4 模型可解释性
随着机器学习在高风险领域(医疗、金融、司法)的应用,模型可解释性变得越来越重要。
特征重要性是理解模型决策的基础。树模型可直接输出特征重要性,基于特征在分裂中带来的不纯度减少:
# 随机森林特征重要性
importances = rf_model.feature_importances_
indices = np.argsort(importances)[::-1]
plt.figure(figsize=(10, 6))
plt.bar(range(X.shape[1]), importances[indices])
plt.xticks(range(X.shape[1]), [f"Feature {i}" for i in indices], rotation=45)
plt.title("Feature Importance")
plt.tight_layout()
**SHAP(SHapley Additive exPlanations)**值基于博弈论中的Shapley值,为每个特征分配对预测的贡献,具有理论上的公平性保证。SHAP值的特点:
- 可加性:所有特征的SHAP值之和等于预测值与基准值的差
- 一致性:当模型改变使得某特征影响力增加时,其SHAP值不会减少
import shap
# 计算SHAP值
explainer = shap.TreeExplainer(rf_model)
shap_values = explainer.shap_values(X_test)
# 全局特征重要性
shap.summary_plot(shap_values, X_test, plot_type="bar")
# 单个预测解释
shap.force_plot(explainer.expected_value[1], shap_values[1][0], X_test.iloc[0])
**LIME(Local Interpretable Model-agnostic Explanations)**通过在预测样本附近采样并拟合可解释模型(如线性模型),解释单个预测的原因。
表6-3:模型可解释性方法对比
| 方法 | 适用范围 | 解释类型 | 计算成本 | 主要优势 |
|---|---|---|---|---|
| 特征重要性 | 树模型 | 全局 | 低 | 直观、高效 |
| SHAP | 所有模型 | 全局+局部 | 高 | 理论基础扎实 |
| LIME | 所有模型 | 局部 | 中 | 模型无关 |
| 偏依赖图 | 所有模型 | 全局 | 中 | 展示特征效应 |
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、高斯过程 | 泛化能力强 |
本章系统介绍了经典机器学习的核心算法和实践方法。从问题定义、特征工程到模型选择、评估部署,每个环节都对项目成功至关重要。随着深度学习的发展,传统机器学习算法因其可解释性强、训练成本低、在小样本上表现好等优势,仍然在实际应用中占据重要地位。掌握这些经典算法,将为后续深度学习的学习打下坚实基础。
附录:算法数学推导
线性回归最小二乘推导
线性回归的目标是找到参数使得残差平方和最小:
展开得:
对求导并令其为零:
解得正规方程:
当可逆时:
逻辑回归梯度推导
逻辑回归的对数似然函数:
其中为sigmoid函数,其导数为。
对求偏导:
简化得:
向量形式:
SVM对偶问题推导
原始优化问题(软间隔SVM):
构造拉格朗日函数:
对求偏导并令其为零:
代入拉格朗日函数得对偶问题:
引入核函数:
决策函数:
信息增益计算
信息熵度量数据集的不确定性:
其中为第类样本所占比例。
特征对数据集的信息增益:
条件熵:
信息增益率(C4.5):
其中为特征的固有值。
基尼指数(CART):
特征的基尼指数:
K-Means目标函数优化
K-Means目标函数(惯性):
其中为指示变量。
E步(分配步):固定,优化
M步(更新步):固定,优化
对求导:
解得:
即簇中心为该簇所有样本的均值。
PCA推导
PCA的目标是找到投影方向使得投影后的方差最大:
其中为协方差矩阵。
构造拉格朗日函数:
求导:
即:
这表明是协方差矩阵的特征向量,为对应的特征值。投影方差为:
因此,第一主成分对应最大特征值的特征向量,第二主成分对应次大特征值的特征向量,以此类推。
扩展阅读:集成学习进阶
Bagging与Boosting对比
**Bagging(Bootstrap Aggregating)**通过并行训练多个基学习器并聚合预测来降低方差。随机森林是Bagging的典型代表,其核心思想是:
- Bootstrap采样:从原始数据集有放回地抽取n个样本构建训练集
- 随机特征选择:每个节点分裂时随机选择mtry个特征
- 聚合预测:分类任务投票,回归任务平均
Bagging的有效性源于:
- 降低方差:
- 通过随机性减少基学习器间的相关性
Boosting通过串行训练基学习器,每个新学习器关注前面学习器的错误。AdaBoost和梯度提升是主要代表:
AdaBoost调整样本权重:
- 初始化样本权重
- 训练弱分类器
- 计算分类误差
- 计算分类器权重
- 更新样本权重
- 归一化权重
最终预测:
梯度提升将Boosting视为函数空间中的梯度下降:
- 初始化
- 对于到:
- 计算伪残差
- 用训练回归树,得到
- 线搜索确定步长
- 更新
XGBoost核心优化
XGBoost在梯度提升基础上引入多项优化:
目标函数:
其中为正则化项,为叶子节点数,为叶子权重。
二阶泰勒展开:
其中,
定义每个叶子节点上的样本集合为,则:
其中,
对求导并令其为零:
代入得最优目标值:
分裂增益:
XGBoost主要优化技术:
- 正则化:L1/L2正则化防止过拟合
- 列采样:类似随机森林的特征随机选择
- 行采样:每棵树使用Bootstrap样本
- 近似算法:分桶统计加速最优分裂点搜索
- 并行计算:特征级并行,列块存储
- 缺失值处理:自动学习缺失值的分裂方向
import xgboost as xgb
# XGBoost分类器
xgb_clf = xgb.XGBClassifier(
n_estimators=100, # 树的数量
max_depth=6, # 树的最大深度
learning_rate=0.1, # 学习率
subsample=0.8, # 样本采样比例
colsample_bytree=0.8, # 特征采样比例
reg_alpha=0.1, # L1正则化
reg_lambda=1.0, # L2正则化
random_state=42
)
xgb_clf.fit(X_train, y_train)
predictions = xgb_clf.predict(X_test)
LightGBM创新
LightGBM采用两种核心技术提升效率:
基于梯度的单边采样(GOSS):保留梯度大的样本,对梯度小的样本随机采样,在减少数据量的同时保持数据分布。
互斥特征捆绑(EFB):将互斥特征(很少同时取非零值)捆绑在一起,减少特征数量。
直方图算法:将连续特征离散化为k个bin,在bin级别寻找最优分裂点,大幅降低计算复杂度。
叶子优先(Leaf-wise)生长策略:与XGBoost的层级优先(Level-wise)不同,LightGBM每次选择分裂增益最大的叶子进行分裂,在相同分裂次数下通常能获得更好的精度。
import lightgbm as lgb
# LightGBM分类器
lgb_clf = lgb.LGBMClassifier(
n_estimators=100,
max_depth=-1, # 无限制
learning_rate=0.1,
num_leaves=31, # 最大叶子数
subsample=0.8,
colsample_bytree=0.8,
random_state=42
)
lgb_clf.fit(X_train, y_train)
表6-6:集成学习方法对比
| 特性 | Bagging | AdaBoost | Gradient Boosting | XGBoost | LightGBM |
|---|---|---|---|---|---|
| 基学习器 | 复杂模型 | 弱模型 | 弱模型 | 弱模型 | 弱模型 |
| 训练方式 | 并行 | 串行 | 串行 | 串行 | 串行 |
| 样本权重 | 相等 | 自适应 | 梯度驱动 | 梯度驱动 | 梯度驱动 |
| 主要目标 | 降方差 | 降偏差 | 降偏差 | 降偏差+正则化 | 降偏差+效率 |
| 异常值敏感 | 低 | 高 | 中 | 低 | 低 |
| 训练速度 | 快 | 中 | 慢 | 快 | 极快 |
| 内存占用 | 高 | 低 | 中 | 中 | 低 |
扩展阅读:核方法与SVM深入
核函数性质
Mercer定理给出了函数成为有效核函数的充要条件:函数是有效核函数当且仅当对于任意有限点集和任意实数,有:
即核矩阵必须是半正定的。
常用核函数:
| 核函数 | 表达式 | 参数 | 适用场景 |
|---|---|---|---|
| 线性核 | 无 | 线性可分数据 | |
| 多项式核 | 多项式关系 | ||
| RBF核 | 通用,非线性 | ||
| Sigmoid核 | 神经网络类似 |
核函数选择指南:
- 特征数量大(>1000):优先尝试线性核
- 样本数量适中、特征数量适中:RBF核通常是首选
- 有领域知识表明多项式关系:多项式核
SVM多分类扩展
原始SVM设计用于二分类,扩展到多分类的常用策略:
One-vs-One(OvO):为每对类别训练一个分类器,共个分类器。预测时投票决定最终类别。
One-vs-Rest(OvR):为每个类别训练一个分类器(该类vs其他所有类),共个分类器。预测时选择置信度最高的类别。
from sklearn.svm import SVC
from sklearn.multiclass import OneVsRestClassifier, OneVsOneClassifier
# One-vs-One
ovo_svm = OneVsOneClassifier(SVC(kernel='rbf'))
# One-vs-Rest
ovr_svm = OneVsRestClassifier(SVC(kernel='rbf'))
扩展阅读:特征工程高级技巧
自动化特征工程
Featuretools是自动化特征工程的Python库,通过深度特征合成(Deep Feature Synthesis)自动构建特征:
import featuretools as ft
# 创建实体集
es = ft.EntitySet(id="customer_data")
es = es.add_dataframe(dataframe_name="customers",
dataframe=customers_df,
index="customer_id")
es = es.add_dataframe(dataframe_name="transactions",
dataframe=transactions_df,
index="transaction_id",
time_index="timestamp")
# 添加关系
es = es.add_relationship("customers", "customer_id",
"transactions", "customer_id")
# 自动特征合成
feature_matrix, feature_defs = ft.dfs(
entityset=es,
target_dataframe_name="customers",
agg_primitives=["mean", "max", "min", "count", "sum"],
trans_primitives=["day", "month", "year"],
max_depth=2
)
特征选择进阶
递归特征消除(RFE):
from sklearn.feature_selection import RFE
# 使用随机森林进行递归特征消除
selector = RFE(estimator=RandomForestClassifier(n_estimators=50),
n_features_to_select=10)
X_selected = selector.fit_transform(X, y)
# 查看特征排名
feature_ranking = pd.DataFrame({
'feature': X.columns,
'ranking': selector.ranking_
}).sort_values('ranking')
基于模型的特征选择:
from sklearn.feature_selection import SelectFromModel
# 使用L1正则化的逻辑回归进行特征选择
selector = SelectFromModel(
LogisticRegression(penalty='l1', solver='liblinear', C=0.1),
threshold='median'
)
X_selected = selector.fit_transform(X, y)
学习建议
- 先经典后深度:先用 Scikit-learn 实现经典 ML 方法,理解特征工程和模型评估流程,再用 PyTorch 实现深度学习模型。
- 动手实验:在 Kaggle 数据集上从头完成一个完整的建模流程:数据探索、特征工程、模型训练、超参数调优、模型评估。
- 对比思考:同一个数据集,分别用逻辑回归、随机森林和神经网络处理,比较它们的表现、训练时间和可解释性。
- 从零实现:用 NumPy 手动实现一个简单的两层神经网络,包括前向传播、反向传播和梯度下降,加深对深度学习的理解。
- 关注前沿:Diffusion 和 Transformer 是当前最活跃的研究方向,建议阅读原始论文并结合代码实现。
推荐资源
- 课程: Stanford CS229(机器学习)+ CS231n(计算机视觉)+ CS224n(NLP)— 吴恩达经典课程体系
- 教材: 《Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow》(Aurelien Geron)— 实践导向,代码丰富
- 论文: 《Attention Is All You Need》(Transformer)— 理解现代 AI 架构的起点
- 实战: Kaggle 入门竞赛(Titanic、House Prices)— 快速建立完整的建模经验
- 框架: PyTorch 官方教程 — 从基础张量操作到构建完整的深度学习模型