用Python实战解锁机器学习核心算法:从决策树到神经网络的代码化复习
当厚厚的复习资料铺满书桌,那些抽象的概念和公式仿佛在眼前跳动——信息增益、核函数、反向传播、正则化...与其在纸面上反复誊写定义,不如打开Jupyter Notebook,让代码替你"理解"这些算法。本文将带你用Python和Scikit-learn,把枯燥的考试重点转化为可运行的代码实验,在模型训练过程中自然掌握那些易混淆的理论要点。
1. 环境准备与数据工程
工欲善其事,必先利其器。我们先搭建一个可复现的机器学习实验环境:
# 基础环境配置 import numpy as np import pandas as pd import matplotlib.pyplot as plt from sklearn import datasets from sklearn.model_selection import train_test_split # 示例数据集加载 iris = datasets.load_iris() X = iris.data[:, 2:] # 只取后两个特征方便可视化 y = iris.target # 数据集划分的最佳实践 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y)注意:stratify参数确保训练集和测试集的类别分布一致,这在样本不平衡时尤为重要
机器学习三要素中,数据质量往往比算法选择更重要。考试中常出现的几个数据预处理考点:
- 特征缩放:MinMaxScaler vs StandardScaler
- 类别编码:OneHotEncoder vs LabelEncoder
- 缺失值处理:简单填充 vs 模型预测填充
from sklearn.preprocessing import StandardScaler from sklearn.impute import SimpleImputer # 典型的数据预处理流水线 preprocessor = Pipeline([ ('imputer', SimpleImputer(strategy='median')), # 中位数填充缺失值 ('scaler', StandardScaler()) # 标准化处理 ])2. 决策树:从信息论到可视化
决策树是考试中的常客,特别是关于节点划分标准的各种计算题。让我们用代码实现三种划分准则:
from sklearn.tree import DecisionTreeClassifier, export_graphviz import graphviz # 三种划分准则对比 criteria = ['gini', 'entropy', 'log_loss'] for criterion in criteria: tree = DecisionTreeClassifier(criterion=criterion, max_depth=3, random_state=42) tree.fit(X_train, y_train) print(f"{criterion}准则的测试集准确率:{tree.score(X_test, y_test):.2f}") # 可视化决策边界 plt.figure() x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1 y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1 xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02), np.arange(y_min, y_max, 0.02)) Z = tree.predict(np.c_[xx.ravel(), yy.ravel()]) Z = Z.reshape(xx.shape) plt.contourf(xx, yy, Z, alpha=0.4) plt.scatter(X[:, 0], X[:, 1], c=y, s=20, edgecolor='k') plt.title(f"Decision Tree ({criterion})")考试重点中的剪枝处理,在实际代码中对应着这些关键参数:
| 参数 | 预剪枝作用 | 对应考试概念 |
|---|---|---|
| max_depth | 限制树的最大深度 | 控制模型复杂度 |
| min_samples_split | 节点分裂的最小样本数 | 防止过拟合 |
| min_samples_leaf | 叶节点的最小样本数 | 保证统计显著性 |
| max_leaf_nodes | 最大叶节点数量 | 直接限制树规模 |
后剪枝的实现相对复杂,Scikit-learn中需要通过cost_complexity_pruning_path计算最佳α值:
path = tree.cost_complexity_pruning_path(X_train, y_train) ccp_alphas = path.ccp_alphas # 为每个alpha值训练一棵树 trees = [] for ccp_alpha in ccp_alphas: tree = DecisionTreeClassifier(random_state=42, ccp_alpha=ccp_alpha) tree.fit(X_train, y_train) trees.append(tree)3. 神经网络:从感知机到深度学习
感知机是神经网络的基石,但考试中常混淆它与逻辑回归的区别。让我们用代码揭示它们的联系:
from sklearn.linear_model import Perceptron, LogisticRegression # 感知机实现 perceptron = Perceptron(eta0=0.1, random_state=42) perceptron.fit(X_train, y_train) # 逻辑回归实现 log_reg = LogisticRegression(multi_class='multinomial', solver='lbfgs') log_reg.fit(X_train, y_train) # 可视化决策边界对比 models = [('Perceptron', perceptron), ('Logistic Regression', log_reg)] fig, axes = plt.subplots(1, 2, figsize=(10, 4)) for idx, (name, model) in enumerate(models): Z = model.predict(np.c_[xx.ravel(), yy.ravel()]) Z = Z.reshape(xx.shape) axes[idx].contourf(xx, yy, Z, alpha=0.4) axes[idx].scatter(X[:, 0], X[:, 1], c=y, s=20, edgecolor='k') axes[idx].set_title(name)当遇到线性不可分问题时,多层神经网络的优势就显现出来了。以下是使用MLPClassifier实现的三层神经网络:
from sklearn.neural_network import MLPClassifier # 三层神经网络配置 mlp = MLPClassifier( hidden_layer_sizes=(10,), # 单隐层10个神经元 activation='relu', # ReLU激活函数 solver='adam', # 优化算法 alpha=0.001, # L2正则化系数 batch_size=32, # 小批量梯度下降 learning_rate_init=0.001, max_iter=500, random_state=42 ) # 训练过程可视化 mlp.fit(X_train, y_train) plt.plot(mlp.loss_curve_) plt.xlabel('Epochs') plt.ylabel('Loss') plt.title('Training Loss over Epochs')BP算法中的关键参数对应着这些考试重点:
- 学习率:影响参数更新的步长,太大导致震荡,太小收敛慢
- 批量大小:全批量 vs 小批量 vs 随机梯度下降
- 激活函数:Sigmoid的梯度消失问题,ReLU的优势
- 正则化:L2权重衰减与Dropout的实现
4. 支持向量机:从最大间隔到核技巧
SVM的核心思想是寻找最大间隔超平面,这在代码中体现为:
from sklearn.svm import SVC # 线性SVM svm_linear = SVC(kernel='linear', C=1.0) svm_linear.fit(X_train, y_train) # 高斯核SVM svm_rbf = SVC(kernel='rbf', gamma=0.7, C=1.0) svm_rbf.fit(X_train, y_train) # 可视化对比 svm_models = [('Linear SVM', svm_linear), ('RBF Kernel SVM', svm_rbf)] fig, axes = plt.subplots(1, 2, figsize=(10, 4)) for idx, (name, model) in enumerate(svm_models): Z = model.predict(np.c_[xx.ravel(), yy.ravel()]) Z = Z.reshape(xx.shape) axes[idx].contourf(xx, yy, Z, alpha=0.4) axes[idx].scatter(X[:, 0], X[:, 1], c=y, s=20, edgecolor='k') axes[idx].set_title(name)考试中常考的核函数选择与参数影响:
| 核类型 | 数学形式 | 适用场景 | 关键参数 |
|---|---|---|---|
| 线性 | K(x,y)=x·y | 线性可分 | C(惩罚系数) |
| 多项式 | K(x,y)=(γx·y+r)^d | 适度非线性 | d(次数) |
| 高斯 | K(x,y)=exp(-γ | x-y | |
| Sigmoid | K(x,y)=tanh(γx·y+r) | 特定场景 | γ,r |
软间隔SVM的正则化参数C对模型的影响可以通过网格搜索验证:
from sklearn.model_selection import GridSearchCV param_grid = {'C': [0.1, 1, 10, 100], 'gamma': [0.01, 0.1, 1, 10]} grid_search = GridSearchCV(SVC(kernel='rbf'), param_grid, cv=5) grid_search.fit(X_train, y_train) # 最佳参数组合 print(f"Best parameters: {grid_search.best_params_}")5. 模型评估与过拟合诊断
考试中模型评估部分常出现的几个关键指标,我们用代码实现它们的计算:
from sklearn.metrics import (accuracy_score, precision_score, recall_score, f1_score, roc_auc_score, confusion_matrix, classification_report) # 以决策树为例 y_pred = tree.predict(X_test) y_proba = tree.predict_proba(X_test) print("准确率:", accuracy_score(y_test, y_pred)) print("精确率(宏平均):", precision_score(y_test, y_pred, average='macro')) print("召回率(宏平均):", recall_score(y_test, y_pred, average='macro')) print("F1分数(宏平均):", f1_score(y_test, y_pred, average='macro')) print("\n分类报告:\n", classification_report(y_test, y_pred)) print("\n混淆矩阵:\n", confusion_matrix(y_test, y_pred))过拟合诊断的实用技巧:
- 学习曲线分析:观察训练/验证误差随数据量变化
- 验证曲线:分析超参数对性能的影响
- 特征重要性:识别冗余或无关特征
from sklearn.model_selection import learning_curve train_sizes, train_scores, test_scores = learning_curve( DecisionTreeClassifier(max_depth=5), X, y, cv=5, scoring='accuracy', train_sizes=np.linspace(0.1, 1.0, 10)) plt.plot(train_sizes, np.mean(train_scores, axis=1), 'o-', label="Training score") plt.plot(train_sizes, np.mean(test_scores, axis=1), 'o-', label="Cross-validation score") plt.xlabel("Training examples") plt.ylabel("Accuracy") plt.legend()在项目实践中发现,很多同学容易混淆验证集和测试集的用途。验证集用于模型选择和调参,而测试集只应在最终评估时使用一次。这种数据划分策略在Scikit-learn中可以通过train_test_split多次实现:
# 完整的数据流划分示例 X_train_val, X_test, y_train_val, y_test = train_test_split(X, y, test_size=0.2, random_state=42) X_train, X_val, y_train, y_val = train_test_split(X_train_val, y_train_val, test_size=0.25, random_state=42) print(f"训练集: {X_train.shape[0]}样本, 验证集: {X_val.shape[0]}样本, 测试集: {X_test.shape[0]}样本")