CHAPTER 09

机器学习入门(scikit-learn)

掌握机器学习的核心概念和工作流程。scikit-learn 提供了统一的 API,让你用几行代码就能训练和评估模型。

机器学习核心概念

核心概念

什么是机器学习?

机器学习是让计算机从数据中自动学习规律,而不是手动编写规则。传统编程:输入数据 + 规则 → 输出结果;机器学习:输入数据 + 期望结果 → 学出规则(模型)。

  • 特征(Feature)与标签(Label) 特征是模型的输入变量(如房屋面积、位置、层数),标签是要预测的目标值(如房价)。在代码中通常用 X 表示特征矩阵,y 表示标签向量。
  • 训练集 / 验证集 / 测试集 训练集用于模型学习参数;验证集用于调整超参数和早停;测试集用于最终性能评估(只用一次!)。常见比例:训练 70% / 验证 15% / 测试 15%。
  • 过拟合(Overfitting) 模型在训练集上表现很好,在测试集上表现差——记住了训练数据的噪声而不是真正的规律。应对方法:正则化、Dropout、数据增强、减小模型复杂度。
  • 欠拟合(Underfitting) 模型连训练集都学不好,通常因为模型太简单或训练不足。应对方法:增大模型复杂度、增加特征、减少正则化、更长时间训练。
  • 偏差-方差权衡(Bias-Variance Tradeoff) 偏差高=欠拟合(模型太简单,系统性地预测错误);方差高=过拟合(模型太复杂,对训练数据过于敏感)。理想模型需要平衡两者。
  • 超参数(Hyperparameter) 不由训练数据学到,需要人工设定的参数,如学习率、决策树深度、随机森林的树的数量。超参数调整是提升模型性能的重要手段。
类型 特点 典型算法 应用场景
监督学习 有标签数据 线性回归、随机森林 房价预测、垃圾邮件分类
无监督学习 无标签数据 K-Means、PCA 用户聚类、降维
强化学习 通过奖惩学习 PPO、DQN 游戏 AI、机器人控制
关键概念

过拟合(Overfitting)与欠拟合(Underfitting)

过拟合:模型在训练集上准确率极高,但在新数据上表现差——死记硬背,不会举一反三。欠拟合:模型太简单,连训练集都学不好。理想状态是训练集和验证集准确率都高且相近。

scikit-learn 的统一 API

scikit-learn 最大的优点是所有算法都有统一的接口fit() 训练,predict() 预测,score() 评分。换算法只需改一行代码。

PYTHON · scikit-learn 基础流程
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, classification_report

# 1. 加载数据集(鸢尾花,经典入门数据集)
iris = load_iris()
X, y = iris.data, iris.target
print(f"特征形状: {X.shape}, 类别数: {len(set(y))}")

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

# 3. 特征标准化(零均值,单位方差)
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)   # 在训练集上 fit
X_test  = scaler.transform(X_test)         # 测试集只用 transform!

# 4. 训练模型
model = LogisticRegression(max_iter=1000)
model.fit(X_train, y_train)   # 两行搞定训练

# 5. 评估
y_pred = model.predict(X_test)
print(f"准确率: {accuracy_score(y_test, y_pred):.4f}")
print(classification_report(y_test, y_pred))
⚠️

数据泄露(Data Leakage)陷阱

StandardScaler 必须在训练集上 fit,然后用同样的参数 transform 测试集。如果用测试集做 fit_transform,测试集的信息就"泄露"给了模型,评估结果会虚高。这是初学者最常犯的错误之一。

常用算法一览

scikit-learn 统一的 API 让换算法变得极其简单。了解各算法的特点,在合适的场景选对工具:

PYTHON · 换算法只需一行
from sklearn.linear_model    import LogisticRegression, LinearRegression
from sklearn.tree             import DecisionTreeClassifier
from sklearn.ensemble         import RandomForestClassifier, GradientBoostingClassifier
from sklearn.svm              import SVC
from sklearn.neighbors        import KNeighborsClassifier

# 所有算法接口完全一样!
models = {
    "逻辑回归":   LogisticRegression(),
    "决策树":     DecisionTreeClassifier(max_depth=5),
    "随机森林":   RandomForestClassifier(n_estimators=100),
    "梯度提升":   GradientBoostingClassifier(),
    "SVM":       SVC(kernel="rbf"),
    "KNN":       KNeighborsClassifier(n_neighbors=5),
}

# 快速对比所有算法的效果
for name, clf in models.items():
    clf.fit(X_train, y_train)
    acc = clf.score(X_test, y_test)
    print(f"{name:10s}: {acc:.4f}")
算法 优点 缺点 适用场景
逻辑回归 可解释性强、速度快 只能线性决策边界 二分类基线模型
随机森林 强大、抗过拟合 黑盒、速度慢 结构化数据首选
梯度提升 (XGBoost) Kaggle 竞赛冠军 参数多、训练慢 结构化数据最强
SVM 小数据集效果好 大数据慢、参数敏感 文本分类、小数据集

模型评估与交叉验证

单次划分训练/测试集的结果受随机性影响。交叉验证(Cross-Validation)通过多次不同划分取平均,得到更可靠的评估结果。

PYTHON · 评估指标与交叉验证
from sklearn.model_selection import cross_val_score, StratifiedKFold
from sklearn.metrics import (
    accuracy_score, precision_score,
    recall_score, f1_score, roc_auc_score,
    confusion_matrix
)

model = RandomForestClassifier(n_estimators=100, random_state=42)

# 5折交叉验证
cv_scores = cross_val_score(model, X, y, cv=5, scoring="accuracy")
print(f"CV 准确率: {cv_scores.mean():.4f} ± {cv_scores.std():.4f}")

# 评估各指标(在测试集上)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)

print(f"准确率 (Accuracy):  {accuracy_score(y_test, y_pred):.4f}")
print(f"精确率 (Precision): {precision_score(y_test, y_pred, average='macro'):.4f}")
print(f"召回率 (Recall):    {recall_score(y_test, y_pred, average='macro'):.4f}")
print(f"F1 Score:          {f1_score(y_test, y_pred, average='macro'):.4f}")

# 混淆矩阵:可视化预测错误的分布
cm = confusion_matrix(y_test, y_pred)
print("混淆矩阵:\n", cm)
🤖

准确率不是万能的

如果正负样本比例严重不均(比如 99% 负样本),模型只要全预测负就有 99% 准确率,但毫无用处。这时应该用 F1 Score 或 AUC-ROC 来评估。在医疗 AI(疾病检测)中,召回率比准确率更重要——漏诊代价更高。

超参数调优

PYTHON · 网格搜索调参
from sklearn.model_selection import GridSearchCV

model = RandomForestClassifier(random_state=42)

# 定义要搜索的参数范围
param_grid = {
    "n_estimators": [50, 100, 200],
    "max_depth":    [None, 5, 10],
    "min_samples_split": [2, 5],
}

# 5折交叉验证 + 网格搜索
grid_search = GridSearchCV(
    model, param_grid,
    cv=5, scoring="f1_macro",
    n_jobs=-1  # 用所有 CPU 核心并行
)
grid_search.fit(X_train, y_train)

print(f"最佳参数: {grid_search.best_params_}")
print(f"最佳得分: {grid_search.best_score_:.4f}")

# 用最佳参数的模型进行预测
best_model = grid_search.best_estimator_
final_score = best_model.score(X_test, y_test)
print(f"测试集得分: {final_score:.4f}")

调参的核心原则

不要在测试集上调参!正确流程:训练集训练 → 验证集(或交叉验证)调参 → 确定模型后最终用测试集评估一次。测试集必须保持"纯洁",代表真实未见数据。

常见误区与实战注意事项

⚠️

数据泄露(Data Leakage)是最严重的错误

将测试集的信息泄露给训练过程,会导致虚高的评估结果但在生产中失败。常见形式:在全量数据上做归一化(应只在训练集 fit);使用未来数据预测过去(时序数据);特征中包含目标变量的代理特征。StandardScaler 必须在训练集 fit_transform,在测试集只 transform

⚠️

类别不平衡:准确率不是万能指标

如果数据中 99% 是负样本,全部预测负就有 99% 准确率,但毫无用处。对于不平衡数据集,使用 F1 Score、AUC-ROC 或 PR 曲线。scikit-learn 的 train_test_split 提供 stratify=y 参数,保证切分后各类别比例与原始数据相同。

PYTHON · Pipeline —— 防止数据泄露的正确做法
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score

# Pipeline 确保每次交叉验证折叠中,
# scaler 只在训练折上 fit,不会泄露验证折数据
pipeline = Pipeline([
    ("scaler",  StandardScaler()),         # 步骤1:标准化
    ("model",   RandomForestClassifier(      # 步骤2:分类器
        n_estimators=100,
        random_state=42
    ))
])

# 交叉验证时 Pipeline 自动处理数据泄露问题
cv_scores = cross_val_score(pipeline, X, y, cv=5)
print(f"CV 准确率: {cv_scores.mean():.4f} ± {cv_scores.std():.4f}")

# 训练完整管道并预测
pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)

# 用 GridSearchCV 优化 Pipeline 参数
from sklearn.model_selection import GridSearchCV
param_grid = {
    # Pipeline 参数格式:步骤名__参数名
    "model__n_estimators": [50, 100, 200],
    "model__max_depth":    [None, 5, 10],
}
grid_search = GridSearchCV(pipeline, param_grid, cv=5)
grid_search.fit(X_train, y_train)
💡

本章小结

机器学习的核心流程:加载数据 → 切分数据集(含 stratify)→ 特征处理(StandardScaler)→ 训练模型(fit)→ 评估(score / cross_val_score)→ 调参(GridSearchCV)。scikit-learn 统一 API 让换算法只需改一行代码。关键误区:数据泄露(Scaler 只能在训练集 fit)、准确率不适用于不平衡数据集(用 F1 或 AUC-ROC)、不能在测试集上调参。推荐用 Pipeline 将预处理和模型串联,从根本上防止数据泄露。