Skip to content

机器学习 速查表

机器学习概念、算法及 Python 实现。

01

入门

什么是机器学习?

机器学习构建的是能从数据中泛化的系统,而非遵循手写规则。三种范式(监督、无监督、强化)覆盖了大多数实际问题。遵循结构化工作流——尤其是保留一份干净的测试集——能避免过拟合到评估指标上这一最大忌讳。

machine-learning
# Machine Learning: systems that learn patterns from data
# instead of being explicitly programmed.

# Three main paradigms:
# - Supervised:    learn f: X -> y from labeled (X, y) pairs
# - Unsupervised:  find structure in unlabeled X
# - Reinforcement: learn actions that maximize reward via trial-and-error

# Typical workflow:
# 1. Define problem & collect data
# 2. Preprocess & feature engineering
# 3. Split train / validation / test
# 4. Train model, tune hyperparameters
# 5. Evaluate on test set
# 6. Deploy & monitor

核心库

scikit-learn 是表格/经典机器学习的主力——所有算法都有统一的 fit/predict/transform API。NumPy 和 Pandas 处理数据清洗;Matplotlib/Seaborn 用于可视化。深度学习可选 PyTorch(研究友好)或 TensorFlow/Keras(生产友好)——两者覆盖范围相同。

machine-learning
# The standard ML stack in Python
import numpy as np          # numerical arrays
import pandas as pd         # tabular data
import matplotlib.pyplot as plt  # plotting
import seaborn as sns       # statistical plots

# scikit-learn: classical ML (trees, SVM, linear models, preprocessing)
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score

# Deep learning (pick one):
import torch                # PyTorch
# import tensorflow as tf   # TensorFlow/Keras

# Install:
# pip install numpy pandas matplotlib seaborn scikit-learn torch

训练 / 验证 / 测试集划分

在最终评估前绝不要碰测试集——用它来调参会泄露信息并虚高报告指标。stratify=y 保持类别平衡,对不平衡分类至关重要。random_state 使划分可复现。小数据集建议用 k 折交叉验证代替固定验证集。

machine-learning
from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(
    X, y,
    test_size=0.2,      # 20% held out for test
    random_state=42,    # reproducibility
    stratify=y,         # keep class proportions (classification only)
)

# For a third validation set:
X_train, X_temp, y_train, y_temp = train_test_split(
    X, y, test_size=0.4, stratify=y, random_state=42)
X_val, X_test, y_val, y_test = train_test_split(
    X_temp, y_temp, test_size=0.5, stratify=y_temp, random_state=42)
# Now: 60% train, 20% val, 20% test

第一个模型(Iris)

Iris 数据集是机器学习的『Hello World』——150 个花样本,4 个特征,3 个类别。这段代码展示了标准的 sklearn 流程:加载数据、划分、实例化模型、拟合、预测、评估。classification_report 给出每类的精确率/召回率/F1,对不平衡问题远比单独看准确率更有信息量。

machine-learning
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, classification_report

iris = load_iris()
X, y = iris.data, iris.target

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, stratify=y, random_state=42)

model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)

print("Accuracy:", accuracy_score(y_test, y_pred))
print(classification_report(y_test, y_pred, target_names=iris.target_names))

可复现性

为 numpy、random 和深度学习框架设置种子,使权重初始化和洗牌可复现。为每个随机的 sklearn 估计器传入 random_state。完全确定性(尤其在 GPU 上)还需要确定性算法和环境变量标志——当调试『本应』匹配却对不上的结果时很有用。

machine-learning
import numpy as np
import random
import torch

SEED = 42
np.random.seed(SEED)
random.seed(SEED)
torch.manual_seed(SEED)
if torch.cuda.is_available():
    torch.cuda.manual_seed_all(SEED)

# For sklearn, pass random_state to each estimator:
# RandomForestClassifier(random_state=SEED)

# Force deterministic operations (slower but fully reproducible):
torch.use_deterministic_algorithms(True)

# Optional: pin GPU non-determinism
import os
os.environ["CUBLAS_WORKSPACE_CONFIG"] = ":4096:8"
02

数据预处理

处理缺失值

在整个数据集上拟合填充器会把测试统计量泄露进训练——务必只在训练集上拟合。中位数比均值对异常值更稳健。类别特征用 'most_frequent' 或 'constant'。KNN 填充能捕捉多变量结构但在大数据上开销大。考虑缺失本身是否有信息量(加一个 'is_missing' 指示列)。

machine-learning
import pandas as pd
from sklearn.impute import SimpleImputer, KNNImputer

df = pd.DataFrame({"age": [25, np.nan, 35, 45],
                   "income": [50000, 60000, np.nan, 80000]})

# Drop rows with any missing values (use sparingly)
df_drop = df.dropna()

# Mean/median/mode imputation
imp = SimpleImputer(strategy="median")  # 'mean','most_frequent','constant'
df_imputed = pd.DataFrame(imp.fit_transform(df), columns=df.columns)

# KNN imputer (uses similar samples)
knn_imp = KNNImputer(n_neighbors=3)
df_knn = pd.DataFrame(knn_imp.fit_transform(df), columns=df.columns)

# Always fit imputers on TRAINING data only, then transform test
imp.fit(X_train)
X_train_imp = imp.transform(X_train)
X_test_imp  = imp.transform(X_test)   # uses train medians

缩放与归一化

许多算法(SVM、KNN、逻辑回归、神经网络、PCA)对尺度敏感——树模型通常不敏感。StandardScaler 是默认选择;MinMaxScaler 用于需要限定范围时(如图像);RobustScaler 当异常值会扭曲 StandardScaler 时使用。务必在训练集拟合、用同一拟合器转换测试集——绝不要在测试集上 fit_transform。

machine-learning
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler

# Standardization: mean 0, std 1 (most common)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled  = scaler.transform(X_test)   # use train mean/std!

# Min-max: scale to [0, 1]
minmax = MinMaxScaler()
X_mm = minmax.fit_transform(X_train)

# Robust to outliers: uses median & IQR
robust = RobustScaler()
X_robust = robust.fit_transform(X_train)

编码类别变量

LabelEncoder 用于目标变量;OneHotEncoder 用于名义特征(无内在顺序——one-hot 避免暗示红<绿<蓝)。OrdinalEncoder 用于有序类别如尺寸。handle_unknown='ignore' 防止测试时遇到未见类别报错。高基数类别(如邮编)最好用目标/计数编码,避免维度爆炸。

machine-learning
from sklearn.preprocessing import LabelEncoder, OneHotEncoder, OrdinalEncoder

# Label encode TARGET (classification): strings -> integers
le = LabelEncoder()
y = le.fit_transform(["cat", "dog", "cat", "bird"])  # [0, 1, 0, 2]
le.inverse_transform([0, 1, 2])                       # back to strings

# One-hot encode FEATURES (nominal, no order)
ohe = OneHotEncoder(sparse_output=False, handle_unknown="ignore")
X_ohe = ohe.fit_transform(df[["color"]])  # red/green/blue -> 3 cols

# Ordinal encode (ordered categories)
oe = OrdinalEncoder(categories=[["low", "medium", "high"]])
X_ord = oe.fit_transform(df[["size"]])

# For high-cardinality features, consider target encoding
# or just drop them if they're IDs.

处理不平衡数据

不平衡数据使模型偏向多数类。最干净的修正方法是 class_weight='balanced'(无需数据复制)。SMOTE 合成新的少数类样本但可能制造噪声——只用于训练集。过采样和欠采样都有效但可能过拟合或丢数据。始终用精确率、召回率、F1 和 AUC 评估——不平衡数据上绝不要只看准确率。

machine-learning
from sklearn.utils import resample
from imblearn.over_sampling import SMOTE
from sklearn.utils.class_weight import compute_class_weight

# 1. Class weights (no resampling — preferred for trees/linear)
classes = np.unique(y_train)
weights = compute_class_weight("balanced", classes=classes, y=y_train)
class_weight = dict(zip(classes, weights))
model = RandomForestClassifier(class_weight=class_weight)

# 2. Oversample minority
df_majority = df[df.y == 0]
df_minority = df[df.y == 1]
df_minority_up = resample(df_minority, replace=True,
                          n_samples=len(df_majority), random_state=42)

# 3. SMOTE: synthesize new minority samples via k-nearest neighbors
smote = SMOTE(random_state=42)
X_res, y_res = smote.fit_resample(X_train, y_train)

# Always resample on TRAINING data only — never the test set!

异常值检测

异常值会扭曲均值、标准差和线性模型。Z 分数快但假设正态分布且自身会被异常值扭曲(改用稳健的 IQR)。Isolation Forest 处理高维和非线性边界。根据领域决定是丢弃、截断(winsorize)还是保留异常值——有时异常值恰恰是你关心的案例(欺诈、异常)。

machine-learning
import numpy as np
from sklearn.ensemble import IsolationForest

# Z-score: |x - mean| / std > 3 is an outlier
z_scores = np.abs((X - X.mean()) / X.std())
outliers = (z_scores > 3).any(axis=1)

# IQR method (more robust to outliers than z-score)
Q1, Q3 = X.quantile(0.25), X.quantile(0.75)
IQR = Q3 - Q1
outliers_iqr = ((X < (Q1 - 1.5 * IQR)) | (X > (Q3 + 1.5 * IQR))).any(axis=1)

# Isolation Forest (good for high-dimensional data)
iso = IsolationForest(contamination=0.05, random_state=42)
outliers_iso = iso.fit_predict(X)  # -1 = outlier, 1 = inlier

# Remove or cap (winsorize) outliers before training
X_clean = X[~outliers]
03

特征工程

多项式与交互特征

PolynomialFeatures 让线性模型通过添加 x^2、x*y 等新特征来捕捉非线性关系。interaction_only 避免纯平方项,只需交叉项时有用。特征数量会组合爆炸——务必搭配正则化(Ridge/Lasso)防止过拟合,或在降维后使用。

machine-learning
from sklearn.preprocessing import PolynomialFeatures

# Generate degree-2 features including interactions
poly = PolynomialFeatures(degree=2, interaction_only=False,
                          include_bias=False)
X_poly = poly.fit_transform(X)
# For X = [a, b], produces: [a, b, a^2, a*b, b^2]

# interaction_only=True keeps just [a, b, a*b] — no squares
poly_int = PolynomialFeatures(degree=2, interaction_only=True,
                              include_bias=False)
X_int = poly_int.fit_transform(X)

# Useful for linear models to capture non-linearity
# WARNING: feature count grows quickly — use with regularization

分箱与离散化

分箱将连续特征转为离散桶,帮助模型捕捉非线性效应并降低对异常值的敏感度。等频分箱(分位数)避免等宽分箱的空桶问题。领域特定分箱(如年龄组)通常优于自动分箱。对分箱特征做 one-hot 编码,让模型把每个桶当作独立类别处理。

machine-learning
from sklearn.preprocessing import KBinsDiscretizer
import pandas as pd

# Equal-width bins (uniform spacing)
df["age_bin"] = pd.cut(df["age"], bins=5, labels=False)

# Equal-frequency bins (quantiles — same count per bin)
df["age_quantile"] = pd.qcut(df["age"], q=5, labels=False,
                              duplicates="drop")

# KBinsDiscretizer with strategy
kbd = KBinsDiscretizer(n_bins=5, encode="onehot-dense",
                        strategy="quantile")  # 'uniform','kmeans'
X_binned = kbd.fit_transform(X[["age"]])

# Custom bins with domain knowledge
df["age_group"] = pd.cut(df["age"],
                          bins=[0, 18, 35, 60, 100],
                          labels=["child","young","adult","senior"])

文本特征提取

词袋计数简单但所有词权重相同;TF-IDF 下调在多文档中常见的词。ngram_range=(1,2) 捕捉短词组。现代 NLP 用预训练嵌入(sentence-transformers、BERT)——它们能捕捉词袋无法捕捉的语义。务必设置 max_features 和 min_df 控制词表大小和噪声。

machine-learning
from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer

corpus = [
    "machine learning is fun",
    "deep learning is a subset of machine learning",
    "python is great for data science",
]

# Bag of words (counts)
cv = CountVectorizer(max_features=1000, stop_words="english",
                     ngram_range=(1, 2))
X_counts = cv.fit_transform(corpus)

# TF-IDF: downweights common words
tfidf = TfidfVectorizer(max_features=1000, stop_words="english",
                        ngram_range=(1, 2), min_df=2)
X_tfidf = tfidf.fit_transform(corpus)

# For production, consider sentence-transformers for embeddings:
# from sentence_transformers import SentenceTransformer
# model = SentenceTransformer("all-MiniLM-L6-v2")
# embeddings = model.encode(corpus)

日期时间特征

原始 datetime 列对模型无用——提取有意义的部分(小时、周几、月份)。循环特征(sin/cos)保持连续性:23 点接近 0 点,原始整数编码会丢失这一点。is_weekend 和节假日捕捉人类周期模式。days_since 是时间序列的简单趋势特征。

machine-learning
import pandas as pd

df["date"] = pd.to_datetime(df["date"])

# Extract calendar parts
df["year"]      = df["date"].dt.year
df["month"]     = df["date"].dt.month
df["day"]       = df["date"].dt.day
df["dayofweek"] = df["date"].dt.dayofweek    # 0=Mon
df["dayofyear"] = df["date"].dt.dayofyear
df["hour"]      = df["date"].dt.hour
df["is_weekend"] = df["date"].dt.dayofweek >= 5

# Cyclic encoding (preserves continuity: hour 23 -> 0)
df["hour_sin"] = np.sin(2 * np.pi * df["hour"] / 24)
df["hour_cos"] = np.cos(2 * np.pi * df["hour"] / 24)
df["month_sin"] = np.sin(2 * np.pi * df["month"] / 12)
df["month_cos"] = np.cos(2 * np.pi * df["month"] / 12)

# Time elapsed (useful as a trend feature)
df["days_since"] = (df["date"] - df["date"].min()).dt.days

特征选择

特征多不等于好——无关特征增加噪声和过拟合风险。SelectKBest 快且统计;RFE 彻底但慢(反复重训模型);SelectFromModel 用树的特征重要性做一次性筛选。相关性筛选廉价地移除冗余特征。务必只在训练数据上做选择,并将同一选择器应用到测试集。

machine-learning
from sklearn.feature_selection import (SelectKBest, f_classif,
    RFE, SelectFromModel)
from sklearn.ensemble import RandomForestClassifier

# 1. Univariate: keep top-k by statistical test
selector = SelectKBest(f_classif, k=10)
X_new = selector.fit_transform(X, y)
selected = selector.get_support(indices=True)

# 2. Recursive Feature Elimination (model-based, iterative)
rfe = RFE(RandomForestClassifier(random_state=42),
          n_features_to_select=10)
X_rfe = rfe.fit_transform(X, y)

# 3. Select from model importance
sfm = SelectFromModel(RandomForestClassifier(random_state=42),
                      threshold="median")
X_sfm = sfm.fit_transform(X, y)

# 4. Correlation filter — drop one of highly-correlated pairs
corr = pd.DataFrame(X).corr().abs()
to_drop = [c for c in corr.columns
           if any(corr[c] > 0.95)]
X_filtered = X.drop(columns=to_drop)
04

监督学习

线性与逻辑回归

线性模型快、可解释,是强基线。LogisticRegression 默认用 L2 正则化;C 越小正则越强。多分类用 softmax。系数显示特征重要性,但需先缩放——未缩放的特征使系数不可比。在尝试复杂模型前,永远先从线性基线开始。

machine-learning
from sklearn.linear_model import LinearRegression, LogisticRegression

# Regression: predict a continuous value
lr = LinearRegression()
lr.fit(X_train, y_train)
y_pred = lr.predict(X_test)

# Inspect coefficients
print("Coef:", lr.coef_, "Intercept:", lr.intercept_)

# Classification: predict a class (uses softmax for multiclass)
clf = LogisticRegression(
    penalty="l2",            # 'l1' (Lasso), 'elasticnet', None
    C=1.0,                   # inverse of regularization strength
    max_iter=1000,
    multi_class="multinomial",
)
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
y_proba = clf.predict_proba(X_test)  # class probabilities

决策树与随机森林

决策树可解释但方差高——数据小幅变化就产生截然不同的树。随机森林通过对自助采样和随机特征子集训练的多棵树取平均来修复此问题(bagging)。max_features='sqrt' 是分类的标准选择。森林的特征重要性偏向高基数特征——试试 permutation_importance 获得更公平的视图。

machine-learning
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier

# Single tree (high variance, easy to overfit)
tree = DecisionTreeClassifier(
    max_depth=5,              # limit depth to prevent overfitting
    min_samples_leaf=10,      # min samples per leaf
    random_state=42,
)
tree.fit(X_train, y_train)

# Random forest: ensemble of trees on bootstrapped samples
rf = RandomForestClassifier(
    n_estimators=200,         # number of trees
    max_depth=None,
    max_features="sqrt",      # features per split
    n_jobs=-1,                # use all CPUs
    random_state=42,
)
rf.fit(X_train, y_train)

# Feature importance (averaged across trees)
importances = pd.Series(rf.feature_importances_, index=X.columns)

梯度提升(XGBoost / LightGBM)

梯度提升按序构建树,每棵修正前一棵的误差——通常在表格数据上表现最佳。sklearn 的 GradientBoosting 慢;HistGradientBoosting 是其快速直方图版本。XGBoost 和 LightGBM 是生产/竞赛标准,原生支持缺失值和早停。务必用验证集的 early_stopping 找到最优树数量。

machine-learning
from sklearn.ensemble import GradientBoostingClassifier, HistGradientBoostingClassifier
import lightgbm as lgb
import xgboost as xgb

# sklearn (slow on large data)
gbm = GradientBoostingClassifier(
    n_estimators=200, learning_rate=0.1, max_depth=3, random_state=42)
gbm.fit(X_train, y_train)

# sklearn's faster histogram-based version (like LightGBM)
hgbm = HistGradientBoostingClassifier(
    max_iter=200, learning_rate=0.1, max_leaf_nodes=31, random_state=42)
hgbm.fit(X_train, y_train)

# XGBoost (popular in competitions)
xgb_clf = xgb.XGBClassifier(
    n_estimators=200, learning_rate=0.1, max_depth=6,
    subsample=0.8, colsample_bytree=0.8, n_jobs=-1,
    eval_metric="logloss", random_state=42)
xgb_clf.fit(X_train, y_train, eval_set=[(X_val, y_val)], early_stopping_rounds=10)

# LightGBM (fastest on large data)
lgb_clf = lgb.LGBMClassifier(n_estimators=200, learning_rate=0.1, num_leaves=31)
lgb_clf.fit(X_train, y_train, eval_set=(X_val, y_val))

支持向量机

SVM 寻找最大间隔分离超平面——对边界清晰的小到中等数据集强大。特征缩放是必须的(SVM 基于距离)。RBF 核处理非线性;通过网格搜索调 C(正则化)和 gamma(核宽度)。SVC 随样本数扩展性差(O(n^2)–O(n^3))——超 5 万样本用 LinearSVC 或树集成。

machine-learning
from sklearn.svm import SVC, SVR
from sklearn.preprocessing import StandardScaler

# SVC requires scaling!
scaler = StandardScaler()
X_train_s = scaler.fit_transform(X_train)
X_test_s  = scaler.transform(X_test)

# Kernels: 'linear', 'rbf' (Gaussian), 'poly', 'sigmoid'
svm = SVC(
    kernel="rbf",
    C=1.0,              # regularization (higher = fit harder)
    gamma="scale",      # kernel width
    probability=True,   # enable predict_proba (slower)
    random_state=42,
)
svm.fit(X_train_s, y_train)
y_pred = svm.predict(X_test_s)

# SVR for regression
svr = SVR(kernel="rbf", C=1.0, epsilon=0.1)
svr.fit(X_train_s, y_train)

k-近邻

KNN 是最简单的机器学习算法:存训练数据,预测时在 k 个最近邻中投票。缩放必不可少(基于距离)。k 小=低偏差高方差(过拟合);k 大=高偏差低方差(欠拟合)。KNN 在大数据集上预测慢——生产用近似最近邻(FAISS、HNSW)。

machine-learning
from sklearn.neighbors import KNeighborsClassifier, KNeighborsRegressor

# KNN: classify by majority vote of k nearest training samples
knn = KNeighborsClassifier(
    n_neighbors=5,        # k — try odd values to break ties
    weights="uniform",    # or 'distance' (closer = more weight)
    metric="minkowski",   # 'euclidean','manhattan', etc.
    p=2,                  # 2=euclidean, 1=manhattan
    n_jobs=-1,
)
knn.fit(X_train_scaled, y_train)
y_pred = knn.predict(X_test_scaled)

# Choose k via cross-validation
from sklearn.model_selection import cross_val_score
for k in [1, 3, 5, 7, 9, 11]:
    score = cross_val_score(KNeighborsClassifier(n_neighbors=k),
                            X_train_scaled, y_train, cv=5).mean()
    print(f"k={k}: {score:.3f}")
05

无监督学习

K-Means 聚类

K-Means 通过最小化簇内方差将数据划分为 k 个球形簇。n_init=10 从 10 个随机初始化运行并保留最佳(sklearn 1.4 改了默认值)。用肘部法(惯性)或轮廓系数选 k(轮廓更有原则)。K-Means 假设球形等大簇——非球形簇用 DBSCAN 或高斯混合。

machine-learning
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score

# Choose k via the elbow method or silhouette score
inertias, silhouettes = [], []
for k in range(2, 11):
    km = KMeans(n_clusters=k, n_init=10, random_state=42)
    labels = km.fit_predict(X)
    inertias.append(km.inertia_)
    silhouettes.append(silhouette_score(X, labels))

# Final model with chosen k
kmeans = KMeans(n_clusters=4, n_init=10, random_state=42)
clusters = kmeans.fit_predict(X)
centers = kmeans.cluster_centers_

# Predict cluster for new points
new_labels = kmeans.predict(X_new)

DBSCAN(基于密度)

DBSCAN 发现任意形状的簇并自动检测异常值(标记 -1)——无需指定 k。eps 是关键参数;用 k-距离图(排序后的 k 近邻距离的『肘部』)调它。min_samples 控制噪声敏感度。DBSCAN 在密度变化时表现差——HDBSCAN 更好。它无法直接预测新点;需重拟合或用 HDBSCAN 的 approximate_predict。

machine-learning
from sklearn.cluster import DBSCAN

# DBSCAN groups densely-connected points; marks sparse ones as noise (-1)
db = DBSCAN(
    eps=0.5,            # max distance between points in a cluster
    min_samples=5,      # min points to form a dense region
    metric="euclidean",
)
labels = db.fit_predict(X)
n_clusters = len(set(labels)) - (1 if -1 in labels else 0)
n_noise = list(labels).count(-1)

# Tune eps via the k-distance plot (k = min_samples)
from sklearn.neighbors import NearestNeighbors
nn = NearestNeighbors(n_neighbors=5).fit(X)
distances, _ = nn.kneighbors(X)
distances = np.sort(distances[:, -1])

# DBSCAN doesn't have predict() — refit to predict on new data
# Or use HDBSCAN for a more robust, parameter-light alternative

层次聚类

层次聚类构建嵌套簇的树(树状图)——当你想理解多粒度的簇结构时有用。树状图直观展示在何处切割得到任意数量的簇。'ward' 连接(最小化簇内方差)最常用;'single' 脆弱(链式)。AgglomerativeClustering 超约 1 万样本扩展性差——大数据用 K-Means 或 HDBSCAN。

machine-learning
from sklearn.cluster import AgglomerativeClustering
from scipy.cluster.hierarchy import dendrogram, linkage
import matplotlib.pyplot as plt

# Compute linkage matrix (for dendrogram)
Z = linkage(X, method="ward")  # 'ward','complete','average','single'

# Plot dendrogram to choose number of clusters
plt.figure(figsize=(10, 5))
dendrogram(Z, truncate_mode="level", p=5)
plt.axhline(y=Z[-4, 2], color="r", linestyle="--")  # cut at 4 clusters
plt.show()

# Agglomerative clustering with chosen k
agg = AgglomerativeClustering(
    n_clusters=4,
    linkage="ward",   # minimizes variance
    metric="euclidean",
)
labels = agg.fit_predict(X)

高斯混合模型

GMM 是 K-Means 的概率对应物:每个簇是带自身均值和协方差的高斯,所以能处理椭圆簇并给出软分配(概率)。covariance_type='full' 最灵活但参数多;'diag' 是好折中。用 BIC 选 k(越低越好)。GMM 还能生成新样本——对数据增强有用。

machine-learning
from sklearn.mixture import GaussianMixture

# GMM: probabilistic clustering — each cluster is a Gaussian
gmm = GaussianMixture(
    n_components=4,
    covariance_type="full",  # 'spherical','tied','diag','full'
    n_init=10,
    random_state=42,
)
gmm.fit(X)

labels = gmm.predict(X)
probs = gmm.predict_proba(X)   # soft clustering (responsibilities)

# Generative: sample new data
X_sampled, y_sampled = gmm.sample(100)

# Choose n_components via BIC (lower is better)
bics = []
for k in range(1, 11):
    g = GaussianMixture(n_components=k, n_init=10, random_state=42).fit(X)
    bics.append(g.bic(X))

异常检测

异常检测识别稀有、可疑项——欺诈、缺陷、入侵。Isolation Forest 是通用首选(快、扩展好、处理高维)。One-Class SVM 在有干净『正常』训练集时效果好。LOF 捕捉全局方法漏掉的局部异常。contamination 设定预期异常率;根据领域知识调它。

machine-learning
from sklearn.ensemble import IsolationForest
from sklearn.svm import OneClassSVM
from sklearn.neighbors import LocalOutlierFactor

# Isolation Forest: isolates anomalies with shallow trees
iso = IsolationForest(contamination=0.05, random_state=42)
labels = iso.fit_predict(X)  # -1 anomaly, 1 normal
scores = iso.decision_function(X)  # lower = more anomalous

# One-Class SVM: learns a boundary around normal data
ocsvm = OneClassSVM(kernel="rbf", nu=0.05, gamma="scale")
labels = ocsvm.fit_predict(X_train_normal)

# Local Outlier Factor: density-based (good for local anomalies)
lof = LocalOutlierFactor(n_neighbors=20, contamination=0.05)
labels = lof.fit_predict(X)
# For new data: lof.negative_outlier_factor_ (lower = more anomalous)
06

回归

线性回归与正则化变体

OLS 最小化平方误差无正则——在共线性数据上方差高。Ridge(L2)平滑收缩系数,适合多特征都重要时。Lasso(L1)把部分系数驱动到零,执行特征选择。ElasticNet 融合两者,特征相关时有用。正则回归前务必缩放特征,使惩罚在各特征间公平。

machine-learning
from sklearn.linear_model import LinearRegression, Ridge, Lasso, ElasticNet

# Ordinary Least Squares
ols = LinearRegression()
ols.fit(X_train, y_train)

# Ridge (L2): shrinks coefficients, keeps all features
ridge = Ridge(alpha=1.0)
ridge.fit(X_train_scaled, y_train)

# Lasso (L1): shrinks some coefficients to 0 (feature selection)
lasso = Lasso(alpha=0.1, max_iter=10000)
lasso.fit(X_train_scaled, y_train)
print("Non-zero coefs:", (lasso.coef_ != 0).sum())

# ElasticNet: mix of L1 and L2
en = ElasticNet(alpha=0.1, l1_ratio=0.5)  # l1_ratio=1 -> pure Lasso
en.fit(X_train_scaled, y_train)

回归评估指标

RMSE 惩罚大误差(平方)且与 y 同单位——最常用指标。MAE 更易解释且对异常值稳健。R2 是解释的方差比例;0 表示模型不比预测均值好。MAPE 给百分比误差但 y=0 时无定义。按业务成本选指标:大误差灾难性用 RMSE;所有误差等价用 MAE。

machine-learning
from sklearn.metrics import (mean_squared_error, mean_absolute_error,
    r2_score, mean_absolute_percentage_error)

y_pred = model.predict(X_test)

mse  = mean_squared_error(y_test, y_pred)         # sensitive to outliers
rmse = np.sqrt(mse)                                # same units as y
mae  = mean_absolute_error(y_test, y_pred)         # robust to outliers
r2   = r2_score(y_test, y_pred)                    # 1=perfect, 0=mean, <0=worse
mape = mean_absolute_percentage_error(y_test, y_pred)  # % error

print(f"RMSE: {rmse:.3f}")
print(f"MAE:  {mae:.3f}")
print(f"R2:   {r2:.3f}")
print(f"MAPE: {mape:.1%}")

# Adjusted R2 accounts for number of features
n, p = X_test.shape
adj_r2 = 1 - (1 - r2) * (n - 1) / (n - p - 1)

决策树回归

基于树的回归器做分段常数预测——无法外推到训练数据范围之外。随机森林通过平均降低方差;梯度提升通过序贯修正降低偏差。分位数回归(loss='quantile')预测分位数而非均值——组合上下分位数模型得预测区间,需要不确定性估计时有用。

machine-learning
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor

# Single tree (piecewise constant predictions)
dt = DecisionTreeRegressor(max_depth=5, min_samples_leaf=10,
                            random_state=42)
dt.fit(X_train, y_train)

# Random forest regressor (averages trees)
rf = RandomForestRegressor(n_estimators=200, max_features="sqrt",
                            n_jobs=-1, random_state=42)
rf.fit(X_train, y_train)

# Gradient boosting regressor
gb = GradientBoostingRegressor(n_estimators=200, learning_rate=0.1,
                                max_depth=3, random_state=42)
gb.fit(X_train, y_train)

# Quantile regression — predict ranges instead of points
from sklearn.ensemble import GradientBoostingRegressor
gbr_lower = GradientBoostingRegressor(loss="quantile", alpha=0.1)
gbr_upper = GradientBoostingRegressor(loss="quantile", alpha=0.9)
gbr_lower.fit(X_train, y_train)
gbr_upper.fit(X_train, y_train)

正则化路径

正则化路径展示 Lasso 系数随 alpha 增大如何收缩到零——可视化不同惩罚强度下的特征重要性。在高 alpha 下存活的特征最具预测力。这是理解哪些特征重要、以及交叉验证前选合理 alpha 范围的绝佳诊断工具。

machine-learning
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import lasso_path

# Compute Lasso coefficients for many alpha values
alphas, coefs, _ = lasso_path(X_train_scaled, y_train,
                               alphas=np.logspace(-3, 1, 100))

# Plot: each line is a coefficient vs alpha
plt.figure(figsize=(8, 5))
for coef in coefs:
    plt.plot(alphas, coef)
plt.xscale("log")
plt.xlabel("alpha (regularization strength)")
plt.ylabel("coefficient")
plt.title("Lasso regularization path")
plt.axhline(0, color="k", lw=0.5)
plt.show()

残差分析

残差图诊断模型误设。围绕 0 的随机云表示拟合良好;U 形提示缺失非线性(加多项式特征或用树);漏斗形表示异方差(试对 y 做对数变换)。残差正态性对推断(置信区间)重要但对预测精度不重要。

machine-learning
import matplotlib.pyplot as plt
import seaborn as sns

residuals = y_test - y_pred

fig, axes = plt.subplots(1, 2, figsize=(12, 5))

# 1. Residuals vs predicted — should look random around 0
axes[0].scatter(y_pred, residuals, alpha=0.5)
axes[0].axhline(0, color="r", linestyle="--")
axes[0].set_xlabel("Predicted"); axes[0].set_ylabel("Residual")

# 2. Residual distribution — should be ~normal
sns.histplot(residuals, kde=True, ax=axes[1])
axes[1].set_xlabel("Residual")

plt.show()

# Patterns in residuals suggest model issues:
# - U-shape: missing non-linearity (add polynomial features)
# - Funnel: heteroscedasticity (try log-transforming y)
# - Skew: try transforming y or use a different loss
07

分类

二分类基础

predict 返回硬类标签(阈值 0.5);predict_proba 返回概率——通常更有用。默认 0.5 阈值很少最优:降低它得更高召回(多抓正例,如癌症筛查),提高它得更高精确率(少误报,如垃圾邮件过滤)。ROC-AUC 衡量概率把正例排在负例之上的好坏,与阈值无关。

machine-learning
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import (confusion_matrix, classification_report,
    roc_auc_score, precision_recall_curve)

clf = LogisticRegression(max_iter=1000)
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
y_proba = clf.predict_proba(X_test)[:, 1]  # P(class=1)

# Confusion matrix: TN FP / FN TP
cm = confusion_matrix(y_test, y_pred)
print(cm)
print(classification_report(y_test, y_pred))

# Adjust threshold (default 0.5) to trade precision for recall
y_pred_custom = (y_proba > 0.3).astype(int)  # higher recall

# ROC-AUC: probability ranking quality (threshold-independent)
print("ROC-AUC:", roc_auc_score(y_test, y_proba))

混淆矩阵与衍生指标

准确率在不平衡数据上误导——99% 负样本的数据集全预测负例就得 99% 准确率。精确率和召回率揭示精确率-召回率权衡:精确率回答『预测正例中多少是真的?』,召回率回答『实际正例中抓到多少?』。F1 是两者调和平均。类别应等权用 macro 平均,应反映频率用 weighted。

machine-learning
from sklearn.metrics import (confusion_matrix, precision_score,
    recall_score, f1_score)

# Binary: confusion_matrix returns [[TN, FP], [FN, TP]]
tn, fp, fn, tp = confusion_matrix(y_test, y_pred).ravel()

accuracy  = (tp + tn) / (tp + tn + fp + fn)
precision = tp / (tp + fp)    # of predicted positives, how many correct
recall    = tp / (tp + fn)    # of actual positives, how many caught
f1        = 2 * precision * recall / (precision + recall)  # harmonic mean
specificity = tn / (tn + fp)  # true negative rate

# sklearn shortcuts
print("Precision:", precision_score(y_test, y_pred))
print("Recall:    ", recall_score(y_test, y_pred))
print("F1:        ", f1_score(y_test, y_pred))

# Multi-class: average='macro' (unweighted) or 'weighted' (by class size)
print("Macro F1:", f1_score(y_test, y_pred, average="macro"))

ROC 与 PR 曲线

ROC 曲线在所有阈值上绘制召回率(TPR)对假正率;曲线下面积(AUC)概括排序质量(0.5=随机,1.0=完美)。PR 曲线在不平衡数据上比 ROC 更有信息量——负样本占优时 ROC 看起来虚假地好。ROC 朝左上角、PR 朝右上角努力。

machine-learning
from sklearn.metrics import (roc_curve, precision_recall_curve,
    auc, RocCurveDisplay, PrecisionRecallDisplay)
import matplotlib.pyplot as plt

# ROC curve: TPR (recall) vs FPR at all thresholds
fpr, tpr, thresholds = roc_curve(y_test, y_proba)
roc_auc = auc(fpr, tpr)

# PR curve: precision vs recall (better for imbalanced data)
prec, rec, thresholds = precision_recall_curve(y_test, y_proba)
pr_auc = auc(rec, prec)

fig, axes = plt.subplots(1, 2, figsize=(12, 5))
RocCurveDisplay.from_predictions(y_test, y_proba, ax=axes[0])
PrecisionRecallDisplay.from_predictions(y_test, y_proba, ax=axes[1])
axes[0].set_title(f"ROC (AUC={roc_auc:.3f})")
axes[1].set_title(f"PR (AUC={pr_auc:.3f})")
plt.show()

多分类

N>2 类时,LogisticRegression 用 softmax(multi_class='multinomial')。每类精确率/召回率显示模型混淆哪些类。按行(真实类)归一化混淆矩阵看每类召回——找系统性误分类类别有用。Cohen's kappa 对偶然一致性校正准确率;log_loss 衡量预测概率质量(越低越好)。

machine-learning
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import (cohen_kappa_score, log_loss,
    classification_report, confusion_matrix, ConfusionMatrixDisplay)

# Multiclass: logistic regression uses softmax by default
clf = LogisticRegression(multi_class="multinomial", max_iter=1000)
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
y_proba = clf.predict_proba(X_test)

# Per-class report
print(classification_report(y_test, y_pred))

# Confusion matrix (now NxN)
disp = ConfusionMatrixDisplay.from_predictions(
    y_test, y_pred, display_labels=clf.classes_,
    cmap="Blues", normalize="true")  # normalize per true class

# Multiclass metrics
print("Cohen's kappa:", cohen_kappa_score(y_test, y_pred))
print("Log loss:     ", log_loss(y_test, y_proba))

概率校准

模型预测 80% 概率时应 80% 的时间正确——但许多分类器(随机森林、SVM、朴素贝叶斯)输出未校准分数。CalibratedClassifierCV 通过在留出集上拟合等渗回归或 sigmoid 修复此问题。当你基于概率行动时(风险评分、阈值调优)校准很重要——只用硬类预测则不需要。

machine-learning
from sklearn.calibration import CalibratedClassifierCV, calibration_curve
import matplotlib.pyplot as plt

# Many classifiers output poorly-calibrated probabilities
# (e.g. Random Forest, SVM). Calibrate them.

# Method 1: isotonic regression (more flexible, needs more data)
# Method 2: sigmoid (Platt scaling) — parametric, works with less data
calibrated = CalibratedClassifierCV(rf, method="isotonic", cv=5)
calibrated.fit(X_train, y_train)
y_proba = calibrated.predict_proba(X_test)[:, 1]

# Calibration curve: predicted prob vs observed frequency
prob_true, prob_pred = calibration_curve(y_test, y_proba, n_bins=10)
plt.plot([0, 1], [0, 1], "k--")
plt.plot(prob_pred, prob_true, "s-")
plt.xlabel("Predicted probability"); plt.ylabel("Observed frequency")
plt.show()
08

聚类深入

选 k:肘部法与轮廓

肘部法找惯性下降变缓的 k——主观且有时看不见。轮廓系数更有原则:衡量每个点与自身簇相比最近邻簇的相似度(范围 -1 到 1)。轮廓 >0.5 是合理结构;>0.7 是强结构。两者都可视化,肘部不清时优先轮廓。

machine-learning
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score, silhouette_samples
import matplotlib.pyplot as plt

# Elbow method: inertia (sum of squared distances to centroid)
inertias = []
for k in range(1, 11):
    km = KMeans(n_clusters=k, n_init=10, random_state=42).fit(X)
    inertias.append(km.inertia_)

plt.plot(range(1, 11), inertias, "o-")
plt.xlabel("k"); plt.ylabel("Inertia"); plt.title("Elbow method")
plt.show()

# Silhouette: -1 (bad) to 1 (well-clustered), 0 = on boundary
silhouettes = []
for k in range(2, 11):
    labels = KMeans(n_clusters=k, n_init=10, random_state=42).fit_predict(X)
    silhouettes.append(silhouette_score(X, labels))

轮廓图

轮廓图展示每个样本的轮廓系数,按簇分组。等宽『刀片』形状表示簇平衡;宽度不均表示某些簇占主导。红色虚线是平均值——平均值低于此线的簇分离差。用此图直观比较不同 k 值。

machine-learning
from sklearn.metrics import silhouette_samples
import matplotlib.pyplot as plt
import numpy as np

k = 4
km = KMeans(n_clusters=k, n_init=10, random_state=42).fit(X)
labels = km.labels_
sil_values = silhouette_samples(X, labels)

fig, ax = plt.subplots(figsize=(8, 6))
y_lower = 10
for i in range(k):
    ith = np.sort(sil_values[labels == i])
    y_upper = y_lower + len(ith)
    ax.fill_betweenx(np.arange(y_lower, y_upper), 0, ith, alpha=0.7)
    ax.text(-0.05, (y_lower + y_upper) / 2, str(i))
    y_lower = y_upper + 10
ax.axvline(sil_values.mean(), color="red", linestyle="--")
ax.set_xlabel("Silhouette coefficient"); ax.set_ylabel("Cluster")
plt.show()

HDBSCAN(层次 DBSCAN)

HDBSCAN 修复 DBSCAN 最大弱点——需调 eps。它在不同密度层级上构建层次并提取稳定簇,所以适用于密度变化的簇。min_cluster_size 是主参数且直观(你关心的最小簇大小)。稀疏区域的点标 -1(噪声)。HDBSCAN 在真实数据上常产生比 K-Means 更有意义的簇。

machine-learning
import hdbscan  # pip install hdbscan

# HDBSCAN: DBSCAN that automatically chooses eps via hierarchy
clusterer = hdbscan.HDBSCAN(
    min_cluster_size=5,   # min points to form a cluster
    min_samples=None,     # how conservative to be about noise
    metric="euclidean",
    cluster_selection_method="eom",  # 'leaf' for fine-grained
)
labels = clusterer.fit_predict(X)

# Soft clustering: probability of cluster membership
probs = clusterer.probabilities_

# Compare clusters across parameter settings
clusterer.condensed_tree_.plot()

聚类评估

内部指标(轮廓、Davies-Bouldin、Calinski-Harabasz)无真值评估簇质量——用于模型选择。外部指标(ARI、NMI)将聚类与已知标签比较——用于标注数据基准测试。ARI 和 NMI 都校正偶然性且对标签排列不变。无单一最优指标——用多个并可视化簇。

machine-learning
from sklearn.metrics import (silhouette_score, davies_bouldin_score,
    calinski_harabasz_score, adjusted_rand_score, normalized_mutual_info_score)

# Internal metrics (no ground truth)
print("Silhouette:        ", silhouette_score(X, labels))      # higher = better
print("Davies-Bouldin:    ", davies_bouldin_score(X, labels))   # lower = better
print("Calinski-Harabasz: ", calinski_harabasz_score(X, labels)) # higher = better

# External metrics (when you have ground-truth labels)
true_labels = y  # e.g. the actual iris species
print("Adjusted Rand:    ", adjusted_rand_score(true_labels, labels))
print("NMI:              ", normalized_mutual_info_score(true_labels, labels))

# ARI and NMI: 0 = random, 1 = perfect match
# Both are invariant to label permutations (cluster 1 vs cluster 2 doesn't matter)

聚类的降维

高维聚类受维度诅咒——距离变得无意义。先用 PCA(线性、快)或 UMAP(非线性、保局部结构)降到 5-50 维。t-SNE 仅用于可视化(对新数据无 transform 方法,失真使其不适合作为聚类输入)。始终用 t-SNE 或 UMAP 在 2D 可视化簇。

machine-learning
from sklearn.decomposition import PCA, UMAP  # umap-learn package
from sklearn.manifold import TSNE
from sklearn.cluster import KMeans

# Reduce dimensions BEFORE clustering high-dim data
# PCA: linear, fast, preserves global structure
pca = PCA(n_components=10, random_state=42).fit(X)
X_pca = pca.transform(X)

# UMAP: non-linear, fast, preserves local+global structure
reducer = UMAP(n_components=5, random_state=42)
X_umap = reducer.fit_transform(X)

# t-SNE: visualization only (no transform on new data)
X_tsne = TSNE(n_components=2, perplexity=30, random_state=42).fit_transform(X)

# Cluster on the embeddings
labels = KMeans(n_clusters=4, n_init=10, random_state=42).fit_predict(X_umap)

# Plot 2D embedding colored by cluster
plt.scatter(X_tsne[:, 0], X_tsne[:, 1], c=labels, cmap="tab10", s=10)
plt.show()
09

降维

PCA(主成分分析)

PCA 找到捕获最大方差的正交轴(主成分),让你在保留大部分信息的同时压缩高维数据。缩放重要——PCA 对特征尺度敏感。用 n_components=0.95 自动保留 95% 方差。PCA 线性且快;它假设方差=信息,这不总是成立(低方差特征可能是判别性的)。

machine-learning
from sklearn.decomposition import PCA

# PCA finds orthogonal axes of maximum variance
pca = PCA(n_components=2, random_state=42)
X_pca = pca.fit_transform(X_scaled)   # scaling matters!

# Explained variance: how much info each PC retains
print("Explained variance ratio:", pca.explained_variance_ratio_)
print("Total retained:", pca.explained_variance_ratio_.sum())

# Choose n_components to retain 95% of variance
pca_full = PCA(n_components=0.95, random_state=42).fit(X_scaled)
print("Components for 95%:", pca_full.n_components_)
X_reduced = pca_full.transform(X_scaled)

# Reconstruct (lossy) from compressed representation
X_reconstructed = pca_full.inverse_transform(X_reduced)

t-SNE 可视化

t-SNE 擅长高维数据的 2D/3D 可视化,保留局部邻域使簇视觉上显现。perplexity(通常 5-50)控制局部与全局结构的平衡。t-SNE 失真:簇大小和簇间距离无意义,只有局部邻域有意义。大数据上慢——先用 PCA 降维(如到 50)再 t-SNE。

machine-learning
from sklearn.manifold import TSNE
import matplotlib.pyplot as plt

# t-SNE is for 2D/3D visualization, NOT as preprocessing
tsne = TSNE(
    n_components=2,
    perplexity=30,         # try 5-50; ~ sqrt(n_samples) is a heuristic
    learning_rate="auto",
    n_iter=1000,
    init="pca",            # better than random init
    random_state=42,
)
X_tsne = tsne.fit_transform(X_scaled)

plt.scatter(X_tsne[:, 0], X_tsne[:, 1], c=y, cmap="tab10", s=10)
plt.colorbar(); plt.show()

UMAP

UMAP 是 t-SNE 的现代替代:更快、扩展更好、同时保局部和全局结构,且关键支持对新数据 transform,所以可作为管道步骤。n_neighbors 控制局部与全局权衡(小=局部细节,大=全局结构);min_dist 控制点聚得多紧。机器学习管道用 UMAP 压缩到 5-50 维,不是 2。

machine-learning
from umap import UMAP  # pip install umap-learn

# UMAP: faster than t-SNE, preserves more global structure,
# and supports transform on new data (good for ML pipelines)
reducer = UMAP(
    n_components=2,
    n_neighbors=15,    # smaller = local, larger = global
    min_dist=0.1,      # how tightly points pack
    metric="euclidean",
    random_state=42,
)
X_umap = reducer.fit_transform(X_scaled)

# Can transform new data (unlike t-SNE)
X_new_umap = reducer.transform(X_new)

# Use in a pipeline as a preprocessor before clustering/classification
from sklearn.pipeline import Pipeline
pipe = Pipeline([
    ("umap", UMAP(n_components=5, random_state=42)),
    ("clf",  RandomForestClassifier(random_state=42)),
])

LDA(线性判别分析)

与 PCA(无监督)不同,LDA 用类标签找最大化类间方差同时最小化类内方差的投影。最大 n_components 为 n_classes-1,所以只对低类数问题有用。LDA 兼作线性分类器(假设各类共享协方差的高斯模型)。有标签且想最大化类别可分性时用 LDA。

machine-learning
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis

# LDA is SUPERVISED dimensionality reduction — uses class labels
# to find axes that best separate classes
lda = LinearDiscriminantAnalysis(n_components=2)
X_lda = lda.fit_transform(X_train, y_train)  # note: y_train passed in

# Also a classifier (Gaussian model per class)
y_pred = lda.predict(X_test)

# Max n_components = n_classes - 1
# For 3 classes, you get at most 2 LDA components

# Compare: PCA ignores class labels (unsupervised);
# LDA uses labels to maximize class separability

截断 SVD(用于稀疏数据)

TruncatedSVD(文本上称 LSA)是 PCA 的稀疏数据替代——不中心化数据所以适用于 TF-IDF 等稀疏矩阵。与 TF-IDF 结合产生『主题』——共现词组。更好的主题建模试 NMF 或现代嵌入方法。文本保留约 100-300 个分量;合适数量取决于语料大小和下游任务。

machine-learning
from sklearn.decomposition import TruncatedSVD

# PCA doesn't work on sparse matrices (it needs to center data)
# Use TruncatedSVD instead — works directly on sparse TF-IDF, etc.

from scipy.sparse import csr_matrix
X_sparse = csr_matrix(X_tfidf)  # e.g. from TfidfVectorizer

svd = TruncatedSVD(n_components=100, random_state=42)
X_reduced = svd.fit_transform(X_sparse)

# Often combined with TF-IDF for LSA (Latent Semantic Analysis)
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.decomposition import TruncatedSVD

vectorizer = TfidfVectorizer(max_features=10000)
lsa = Pipeline([
    ("tfidf", vectorizer),
    ("svd",   TruncatedSVD(n_components=100, random_state=42)),
])
X_topics = lsa.fit_transform(documents)
10

模型评估

交叉验证

交叉验证通过对多个训练/测试划分取平均给出稳健的性能估计。StratifiedKFold 为分类保持类别平衡(不平衡数据必需)。RepeatedStratifiedKFold 以更高算力运行多次不同划分的 CV——估计更稳定。模型选择始终用 CV 而非单次训练/测试划分。

machine-learning
from sklearn.model_selection import (cross_val_score, cross_validate,
    StratifiedKFold, KFold, RepeatedStratifiedKFold)

# Basic 5-fold CV
scores = cross_val_score(model, X, y, cv=5, scoring="accuracy")
print(f"{scores.mean():.3f} +/- {scores.std():.3f}")

# Stratified (preserves class balance — use for classification)
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(model, X, y, cv=cv, scoring="f1_macro")

# Multiple metrics at once
results = cross_validate(model, X, y, cv=cv,
    scoring=["accuracy", "f1_macro", "roc_auc_ovr"],
    return_train_score=True)
print(results["test_f1_macro"].mean())

# Repeated CV for more stable estimates
rcv = RepeatedStratifiedKFold(n_splits=5, n_repeats=3, random_state=42)

学习曲线

学习曲线绘制训练和验证性能随数据集大小的变化——这是偏差与方差的最佳诊断。高训练分与低验证分的大间隙表示过拟合(加数据、正则、简化)。两者都低且接近表示欠拟合(用更复杂模型或加特征)。曲线右边缘仍在改善时,加数据有帮助。

machine-learning
from sklearn.model_selection import learning_curve
import matplotlib.pyplot as plt

train_sizes, train_scores, val_scores = learning_curve(
    model, X, y,
    train_sizes=np.linspace(0.1, 1.0, 10),
    cv=5, scoring="accuracy", n_jobs=-1, random_state=42,
)

train_mean = train_scores.mean(axis=1)
val_mean   = val_scores.mean(axis=1)

plt.plot(train_sizes, train_mean, "o-", label="Train")
plt.plot(train_sizes, val_mean,   "o-", label="Validation")
plt.xlabel("Training samples"); plt.ylabel("Accuracy")
plt.legend(); plt.show()

# Diagnose:
# - High train, low val  -> overfitting (high variance) -> more data or regularize
# - Both low and close   -> underfitting (high bias) -> more complex model/features
# - Gap closing with data -> more data helps

验证曲线(超参数)

验证曲线绘制性能随单一超参数值的变化——用于理解模型行为并在网格搜索前找合理范围。训练分随复杂度持续上升而验证分先升后降。峰值是甜点;之前欠拟合,之后过拟合。

machine-learning
from sklearn.model_selection import validation_curve
import matplotlib.pyplot as plt

param_range = [1, 2, 4, 8, 16, 32, 64]
train_scores, val_scores = validation_curve(
    RandomForestClassifier(random_state=42),
    X, y,
    param_name="max_depth",
    param_range=param_range,
    cv=5, scoring="accuracy", n_jobs=-1,
)

plt.plot(param_range, train_scores.mean(axis=1), "o-", label="Train")
plt.plot(param_range, val_scores.mean(axis=1),   "o-", label="Validation")
plt.xlabel("max_depth"); plt.ylabel("Accuracy")
plt.legend(); plt.show()

# Sweet spot is where validation peaks before overfitting

分类报告解读

classification_report 的 macro 平均等权对待所有类(稀有类重要时好);weighted 平均反映类频率(多数类性能主导时好)。根据漏掉每类的业务成本选平均策略。严重不平衡时,补充 PR-AUC 和每类召回——准确率甚至 F1 都可能掩盖只预测多数类的模型。

machine-learning
from sklearn.metrics import classification_report

report = classification_report(y_test, y_pred, output_dict=True)

# Per-class metrics + macro/weighted averages
import pandas as pd
df_report = pd.DataFrame(report).T
print(df_report[["precision", "recall", "f1-score", "support"]])

# 'macro' avg     = unweighted mean across classes (treats all equally)
# 'weighted' avg  = support-weighted mean (reflects class imbalance)
# Focus on:
# - macro F1 if all classes matter equally (e.g. digit recognition)
# - weighted F1 if rare classes matter less
# - recall of the positive class if it's the costly one (fraud, disease)

# For imbalanced classification, also report:
# - Precision-Recall AUC
# - Brier score (probability calibration)

回归指标深入

按业务成本选回归指标。RMSE 当大误差不成比例地糟糕时(平方惩罚);MAE 当等大误差等价时。R2 无单位可跨数据集比较。max_error 显示最坏情况。自定义非对称损失(如惩罚欠预测更多)匹配真实成本:缺货成本与积压成本不同。

machine-learning
from sklearn.metrics import (mean_squared_error, mean_absolute_error,
    r2_score, explained_variance_score, max_error)

# RMSE — penalizes large errors; same units as y
rmse = mean_squared_error(y_test, y_pred, squared=False)

# MAE — robust to outliers; same units as y
mae = mean_absolute_error(y_test, y_pred)

# R^2 — fraction of variance explained (1.0 = perfect, 0 = predict mean)
r2 = r2_score(y_test, y_pred)

# Explained variance — like R^2 but ignores mean offset
evs = explained_variance_score(y_test, y_pred)

# Max error — single largest absolute error (worst-case)
max_err = max_error(y_test, y_pred)

# Custom asymmetric loss: penalize under-prediction more
def asymmetric_error(y_true, y_pred, under_penalty=2.0):
    error = y_true - y_pred
    return np.where(error > 0, under_penalty * error**2, error**2).mean()
11

交叉验证策略

K-折变体

选对 CV 策略至关重要。KFold 是默认。StratifiedKFold 用于分类(保持类别平衡)。GroupKFold 当样本相关时(每用户/患者多行——防止训练验证间泄露)。TimeSeriesSplit 用于时序数据(始终在过去训练、在未来验证)。用错 CV 策略会给过于乐观的估计。

machine-learning
from sklearn.model_selection import (KFold, StratifiedKFold,
    GroupKFold, TimeSeriesSplit, RepeatedKFold)

# Standard K-fold (regression or balanced classification)
kf = KFold(n_splits=5, shuffle=True, random_state=42)

# Stratified (classification — preserves class proportions)
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)

# Group K-fold (when samples are not independent — e.g. multiple rows per patient)
gkf = GroupKFold(n_splits=5)  # pass groups=patient_ids to CV

# Time series split (no shuffle — preserves temporal order)
tscv = TimeSeriesSplit(n_splits=5)

# Use with cross_val_score:
scores = cross_val_score(model, X, y, cv=gkf, groups=groups, scoring="f1")

时间序列交叉验证

TimeSeriesSplit 尊重时间顺序:每折在过去训练、在未来验证,绝不反过来。这匹配真实预测并防止泄露。当邻近样本自相关时(如明天价格与今天相关)用 gap 参数。在时序数据上用标准洗牌 KFold 会给出极其乐观的估计——常见的初学者错误。

machine-learning
from sklearn.model_selection import TimeSeriesSplit
import matplotlib.pyplot as plt
import numpy as np

tscv = TimeSeriesSplit(n_splits=5, test_size=100)
# gap parameter (sklearn 1.0+): prevent leakage from train to test
tscv = TimeSeriesSplit(n_splits=5, test_size=100, gap=10)

for fold, (train_idx, test_idx) in enumerate(tscv.split(X)):
    X_tr, X_te = X[train_idx], X[test_idx]
    y_tr, y_te = y[train_idx], y[test_idx]
    model.fit(X_tr, y_tr)
    score = model.score(X_te, y_te)
    print(f"Fold {fold}: train={len(train_idx)}, test={len(test_idx)}, score={score:.3f}")

# NEVER shuffle time-series data for CV — that leaks future into past
# Use gap to avoid leakage from autocorrelated near-future points

组感知 CV

当数据有组(每患者/用户/会话多样本)时,标准随机划分泄露信息——模型在训练测试中看到同一实体,虚高分数。GroupKFold 把每组完整保留在一个折。典型场景:医疗数据(每患者多次扫描)、推荐系统(每用户多次评分)、图像数据(每场景多帧)。不用组感知 CV 是机器学习中最常见的静默 bug 之一。

machine-learning
from sklearn.model_selection import GroupKFold, GroupShuffleSplit

# When the same entity appears in multiple rows (e.g. multiple X-rays per patient),
# random splits leak: the model memorizes patient-specific features
# instead of learning generalizable patterns.

# Solution: keep all rows of a group in the same split
gkf = GroupKFold(n_splits=5)
scores = cross_val_score(model, X, y, cv=gkf, groups=df["patient_id"])

# GroupShuffleSplit: random train/test split respecting groups
gss = GroupShuffleSplit(n_splits=5, test_size=0.2, random_state=42)
for train_idx, test_idx in gss.split(X, groups=df["patient_id"]):
    ...

# Always pass the groups argument to CV functions
# Common scenarios: patients, users, sessions, image batches

嵌套 CV(无偏超参数调优)

标准 GridSearchCV 报告的 CV 分数偏乐观——它用打分用的同一折选了最优超参数。嵌套 CV 修复此问题:外层 CV 循环在真正留出的折上评估整个调优流程。结果是你完整建模流程(含调优)在新数据上表现的无偏估计。用于诚实基准测试,尤其比较模型时。

machine-learning
from sklearn.model_selection import (GridSearchCV, cross_val_score,
    StratifiedKFold)

# Outer loop: estimate generalization performance
# Inner loop: tune hyperparameters
# Without nesting, GridSearchCV's CV score is optimistic (it selected the best
# hyperparameters using the same data)

inner_cv = StratifiedKFold(n_splits=3, shuffle=True, random_state=42)
outer_cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)

grid = GridSearchCV(
    RandomForestClassifier(random_state=42),
    param_grid={"max_depth": [3, 5, 10], "n_estimators": [50, 100]},
    cv=inner_cv, scoring="f1", n_jobs=-1,
)

nested_scores = cross_val_score(grid, X, y, cv=outer_cv, scoring="f1")
print(f"Nested CV F1: {nested_scores.mean():.3f} +/- {nested_scores.std():.3f}")

# Fit the final model on all data
grid.fit(X, y)
best_model = grid.best_estimator_

带预处理的 CV(管道)

预处理必须只在训练折上拟合——否则把测试折统计量(均值、中位数、缩放因子)泄露进训练,给出乐观 CV 分数。修复方法是把一切包进 Pipeline:scikit-learn 确保每折 CV 在训练部分上拟合自己的预处理。这是诚实机器学习评估最重要的最佳实践。绝不要在 CV 前对全数据集拟合预处理。

machine-learning
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import cross_val_score
from sklearn.impute import SimpleImputer

# RIGHT: wrap preprocessing in a Pipeline so each fold fits
# its own scaler/imputer on the training portion only
pipe = Pipeline([
    ("impute",  SimpleImputer(strategy="median")),
    ("scale",   StandardScaler()),
    ("model",   RandomForestClassifier(random_state=42)),
])

scores = cross_val_score(pipe, X, y, cv=5, scoring="f1")

# WRONG: fit scaler on the whole dataset, then CV
# (leaks test-fold statistics into training)
# scaler.fit(X)              # NO - includes future test folds
# X_scaled = scaler.transform(X)
# cross_val_score(model, X_scaled, y, cv=5)  # optimistic scores

# Pipelines also make deployment easier (one object, no leakage)
12

超参数调优

网格搜索

GridSearchCV 穷举评估每个组合——彻底但昂贵(组合爆炸)。参数网格小且精选时用。务必传合适的评分指标(默认准确率,不平衡分类上错误)。refit=True 在全数据上重训最优模型,使返回的估计器即用。

machine-learning
from sklearn.model_selection import GridSearchCV

# Exhaustive search over all combinations
param_grid = {
    "n_estimators": [50, 100, 200],
    "max_depth":    [3, 5, 10, None],
    "min_samples_leaf": [1, 5, 10],
}
# 3 * 4 * 3 = 36 combinations, * 5 folds = 180 fits

grid = GridSearchCV(
    RandomForestClassifier(random_state=42),
    param_grid=param_grid,
    cv=5,
    scoring="f1",
    n_jobs=-1,
    refit=True,         # refit best model on full data
    return_train_score=True,
)
grid.fit(X_train, y_train)

print("Best params: ", grid.best_params_)
print("Best CV F1:  ", grid.best_score_)
best_model = grid.best_estimator_

随机搜索

RandomizedSearchCV 从分布采样 n_iter 个组合——参数多时远比网格搜索高效。学习率和正则化强度用 loguniform(跨数量级)。50-100 次迭代通常找到近优配置。随机性也有助跳出网格搜索可能漏掉的局部最优。

machine-learning
from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import randint, loguniform

# Sample from distributions instead of enumerating all combos
# Much more efficient when the grid is large
param_dist = {
    "n_estimators":     randint(50, 500),     # discrete uniform
    "max_depth":        [3, 5, 10, None],
    "min_samples_leaf": randint(1, 20),
    "learning_rate":    loguniform(1e-3, 1e0),  # log-uniform
}

rand_search = RandomizedSearchCV(
    GradientBoostingClassifier(random_state=42),
    param_distributions=param_dist,
    n_iter=50,           # number of parameter settings sampled
    cv=5,
    scoring="f1",
    n_jobs=-1,
    random_state=42,
)
rand_search.fit(X_train, y_train)
print("Best params:", rand_search.best_params_)

贝叶斯优化(Optuna)

Optuna 用贝叶斯优化根据之前试验结果智能采样下一组超参数——比网格/随机搜索样本效率高得多。还支持剪枝(坏试验早停)、条件参数空间、多目标优化。study 对象存储所有试验,可可视化参数重要性和优化历史。最适合超参数多且模型昂贵的场景。

machine-learning
import optuna

def objective(trial):
    # Suggest hyperparameters
    n_estimators = trial.suggest_int("n_estimators", 50, 500)
    max_depth    = trial.suggest_int("max_depth", 3, 20)
    lr           = trial.suggest_float("learning_rate", 1e-3, 1e0, log=True)
    min_samples  = trial.suggest_int("min_samples_leaf", 1, 20)

    model = GradientBoostingClassifier(
        n_estimators=n_estimators, max_depth=max_depth,
        learning_rate=lr, min_samples_leaf=min_samples,
        random_state=42,
    )
    # 5-fold CV F1
    score = cross_val_score(model, X_train, y_train, cv=5,
                            scoring="f1", n_jobs=-1).mean()
    return score  # Optuna maximizes by default

study = optuna.create_study(direction="maximize")
study.optimize(objective, n_trials=100, timeout=600)

print("Best trial:", study.best_trial.params)
print("Best F1:    ", study.best_trial.value)

折减搜索(连续减半)

折减搜索是迭代资源分配策略:用少量数据评估许多候选,淘汰最差,用更多数据重评存活者。比完整网格搜索快得多,常找到相似解。更大空间用 HalvingRandomSearchCV。'factor' 控制淘汰激进程度。超参数多且时间有限时的好默认选择。

machine-learning
from sklearn.experimental import enable_halving_search_cv  # noqa
from sklearn.model_selection import HalvingGridSearchCV, HalvingRandomSearchCV

# Allocate more resources to promising candidates iteratively
halving = HalvingGridSearchCV(
    RandomForestClassifier(random_state=42),
    param_grid={"max_depth": [3, 5, 10, None],
                "n_estimators": [50, 100, 200]},
    cv=5,
    scoring="f1",
    factor=3,           # eliminate bottom 2/3 each round
    aggressive_elimination=False,
    n_jobs=-1,
)
halving.fit(X_train, y_train)
print("Best params:", halving.best_params_)
print("Best score: ", halving.best_score_)

# Much faster than full GridSearchCV — evaluates many candidates
# cheaply first, then invests more resources in the promising ones.

调优最佳实践

最大调优罪过:在测试集上优化、调优到过拟合验证集、把 CV 分数当最终性能报告。在训练数据上用 CV 调优,然后在干净留出测试集上一次性评估最终模型。检查完整 cv_results_ 确认所选超参数稳健(折间标准差小)而非幸运随机种子。先粗后细——大部分增益来自少数关键参数。

machine-learning
# 1. Tune on a VALIDATION set or via CV — never on the test set
# 2. Use the metric you actually care about (scoring=...)
# 3. Set random_state for reproducibility
# 4. Start coarse (randomized, few iterations), then refine around the best
# 5. Don't over-tune — small improvements may be noise

# Inspect full results to understand hyperparameter sensitivity
import pandas as pd
results = pd.DataFrame(grid.cv_results_)
cols = ["param_max_depth", "param_n_estimators",
        "mean_test_score", "std_test_score", "rank_test_score"]
print(results[cols].sort_values("rank_test_score").head(10))

# Refit the best model on the FULL training set (automatic with refit=True)
# Then evaluate ONCE on the held-out test set
test_score = grid.best_estimator_.score(X_test, y_test)
print(f"Test score: {test_score:.3f}")
13

集成学习

Bagging(自助聚合)

Bagging 在自助采样(有放回)上训练多模型并平均预测——不增偏差降方差。随机森林在每次分裂加随机特征子集使树更多样。袋外(OOB)分数用每棵树未训练的约 37% 样本评估——免费验证估计无需单独集。OOB 适合无法留验证集的小数据集。

machine-learning
from sklearn.ensemble import BaggingClassifier, RandomForestClassifier

# Bagging: train multiple models on bootstrap samples (with replacement)
bag = BaggingClassifier(
    estimator=DecisionTreeClassifier(),  # any base estimator
    n_estimators=100,
    max_samples=1.0,         # fraction of samples per bootstrap
    max_features=1.0,        # fraction of features per estimator
    bootstrap=True,
    n_jobs=-1,
    random_state=42,
)
bag.fit(X_train, y_train)

# Random Forest is bagging + random feature subsets at each split
# Out-of-bag (OOB) score: evaluate each tree on samples it didn't see
rf = RandomForestClassifier(n_estimators=200, oob_score=True,
                             random_state=42)
rf.fit(X_train, y_train)
print("OOB score:", rf.oob_score_)  # ~ validation accuracy for free

Boosting

Boosting 按序训练模型,每棵修正前一棵误差——降偏差(及部分方差)。AdaBoost 重加权样本;梯度提升直接拟合残差。learning_rate 与 n_estimators 权衡:低学习率需更多估计器但常泛化更好——务必用早停。HistGradientBoosting 是 sklearn 的快速直方图实现,与 LightGBM 相当。

machine-learning
from sklearn.ensemble import (AdaBoostClassifier,
    GradientBoostingClassifier, HistGradientBoostingClassifier)

# AdaBoost: weight misclassified samples higher each round
ada = AdaBoostClassifier(
    estimator=DecisionTreeClassifier(max_depth=1),
    n_estimators=200, learning_rate=0.5, random_state=42,
)
ada.fit(X_train, y_train)

# Gradient Boosting: fit each new tree to the residuals of the previous
gb = GradientBoostingClassifier(
    n_estimators=200, learning_rate=0.1, max_depth=3,
    subsample=0.8,        # stochastic GB — adds randomness
    random_state=42,
)
gb.fit(X_train, y_train)

# HistGradientBoosting: faster, native NaN handling, large-data friendly
hgb = HistGradientBoostingClassifier(
    max_iter=200, learning_rate=0.1, max_leaf_nodes=31,
    l2_regularization=0.0, early_stopping=True,
    random_state=42,
)
hgb.fit(X_train, y_train)

Stacking(元学习)

投票集成平均多样模型预测——软投票(概率)通常胜过硬投票(多数)。Stacking 更进一步:元模型学习如何最佳组合基模型预测,常挤出额外性能。元模型在基模型的折外预测上训练以避免泄露(通过 cv 设置)。用多样基模型(不同算法)获最大增益——组合三个随机森林无意义。

machine-learning
from sklearn.ensemble import StackingClassifier, VotingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC

# Voting: simple average of predictions (hard=majority, soft=probabilities)
voting = VotingClassifier(
    estimators=[
        ("rf",  RandomForestClassifier(n_estimators=100, random_state=42)),
        ("svc", SVC(probability=True, random_state=42)),
        ("lr",  LogisticRegression(max_iter=1000)),
    ],
    voting="soft",     # use predict_proba
    weights=[2, 1, 1], # weight each model
)

# Stacking: train a meta-model on base model predictions
stacking = StackingClassifier(
    estimators=[
        ("rf",  RandomForestClassifier(n_estimators=100, random_state=42)),
        ("svc", SVC(probability=True, random_state=42)),
        ("lr",  LogisticRegression(max_iter=1000)),
    ],
    final_estimator=LogisticRegression(),  # meta-model
    cv=5,            # use CV predictions to train meta-model (avoids leakage)
    n_jobs=-1,
)
stacking.fit(X_train, y_train)

投票 vs Stacking vs Blending

三种集成策略都以递增复杂度组合多样模型:投票(最简,平均)、blending(留出集上的元模型)、stacking(CV 预测上的元模型,更稳健)。实践中 Stacking 首选——基于 CV 的元特征用全训练数据且降方差。Blending 更简快但元模型用数据少。都需多样基模型才能增值。

machine-learning
# Voting:  simple average of predictions
# Stacking: meta-model trained on CV predictions of base models
# Blending: like stacking but meta-model trained on a single holdout set

# Blending (manual implementation):
# 1. Split train into train' and holdout
# 2. Train base models on train'
# 3. Get predictions on holdout
# 4. Train meta-model on (holdout_predictions, holdout_y)
# 5. Retrain base models on full train for final ensemble

from sklearn.model_selection import train_test_split
X_tr, X_hold, y_tr, y_hold = train_test_split(X_train, y_train, test_size=0.3,
                                               random_state=42)

# Train base models on X_tr
rf.fit(X_tr, y_tr); svc.fit(X_tr, y_tr); lr.fit(X_tr, y_tr)

# Get holdout predictions as new features
import numpy as np
holdout_preds = np.column_stack([
    rf.predict_proba(X_hold)[:, 1],
    svc.predict_proba(X_hold)[:, 1],
    lr.predict_proba(X_hold)[:, 1],
])
meta_model = LogisticRegression().fit(holdout_preds, y_hold)

多样性与相关性

集成胜过成员仅当模型犯不相关错误。若三个模型犯同样错,集成也犯。测量预测相关性——越低越好。通过混合算法族(树、线性、距离)、不同特征子集、不同超参数区间提升多样性。同算法不同种子的多样性弱;算法多样性强。

machine-learning
import numpy as np
from scipy.stats import spearmanr

# An ensemble only helps if models make DIFFERENT errors
# Measure prediction correlation between base models
preds = np.column_stack([rf.predict_proba(X_test)[:, 1],
                          svc.predict_proba(X_test)[:, 1],
                          lr.predict_proba(X_test)[:, 1]])
corr = spearmanr(preds).correlation
print("Prediction correlation matrix:")
print(corr)

# Low correlation = diverse models = better ensemble
# High correlation = redundant models = no benefit

# Ways to encourage diversity:
# 1. Different algorithms (tree + linear + nearest-neighbor)
# 2. Different feature subsets
# 3. Different hyperparameters
# 4. Different training samples (bagging)
# 5. Different random seeds (weakest form of diversity)
14

深度学习基础

PyTorch 张量与自动求导

PyTorch 张量是类 numpy 数组,可追踪梯度做自动求导。requires_grad=True 启用张量的 autograd;对标量调用 .backward() 计算所有上游张量梯度。用 .to(device) 把张量移到 GPU 加速。.detach() 把张量移出梯度图(用于日志或转 numpy)。

machine-learning
import torch

# Tensors: like numpy arrays but on GPU and with autograd
x = torch.randn(3, 4, requires_grad=True)
y = torch.randn(4, 2, requires_grad=True)
z = x @ y               # matrix multiply
loss = z.sum()

# Automatic differentiation
loss.backward()         # populate .grad
print(x.grad.shape)     # torch.Size([3, 4])
print(y.grad.shape)     # torch.Size([4, 2])

# Move to GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
x = x.to(device)

# Detach from graph (e.g. for logging)
numpy_array = z.detach().cpu().numpy()

训练循环结构

每个 PyTorch 训练循环结构相同:zero_grad、forward、loss、backward、step。model.train()/model.eval() 切换 dropout 和 batchnorm 行为——忘切换是静默 bug。torch.no_grad() 禁用推理的梯度追踪(省内存和算力)。务必把批次移到与模型相同的设备。dataloader 处理批次和洗牌;循环只迭代。

machine-learning
import torch
import torch.nn as nn
import torch.optim as optim

model = nn.Sequential(
    nn.Linear(784, 128), nn.ReLU(),
    nn.Linear(128, 64),  nn.ReLU(),
    nn.Linear(64, 10),
).to(device)

criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=1e-3)

for epoch in range(epochs):
    model.train()                            # enable dropout/batchnorm
    for X_batch, y_batch in train_loader:
        X_batch, y_batch = X_batch.to(device), y_batch.to(device)

        optimizer.zero_grad()                # clear old gradients
        logits = model(X_batch)              # forward
        loss = criterion(logits, y_batch)    # compute loss
        loss.backward()                      # backprop
        optimizer.step()                     # update weights

    # Validation
    model.eval()                             # disable dropout/batchnorm
    with torch.no_grad():                    # no gradient tracking
        val_loss, correct = 0, 0
        for X_batch, y_batch in val_loader:
            logits = model(X_batch.to(device))
            val_loss += criterion(logits, y_batch.to(device))
            correct += (logits.argmax(1).cpu() == y_batch).sum().item()
    print(f"Epoch {epoch}: val_loss={val_loss/len(val_loader):.4f}, "
          f"val_acc={correct/len(val_loader.dataset):.4f}")

常见层

Linear+ReLU 是主食隐藏层。Conv2d+MaxPool 用于图像;LSTM/GRU 用于短序列;Transformer 用于长序列或文本。BatchNorm 稳定训练;Dropout 正则化。注意:PyTorch 的 CrossEntropyLoss 已含 log-softmax,所以别在分类模型末尾加 Softmax 层——输出原始 logits。

machine-learning
import torch.nn as nn

# Linear (fully connected)
nn.Linear(in_features=128, out_features=64)

# Activations
nn.ReLU()        # default for hidden layers
nn.GELU()        # smoother, used in transformers
nn.Sigmoid()     # outputs in (0, 1)
nn.Softmax(dim=1)# multi-class probabilities (built into CrossEntropyLoss)

# Convolution (images)
nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3, padding=1)
nn.MaxPool2d(kernel_size=2)   # downsample by 2
nn.BatchNorm2d(32)            # stabilize training
nn.Dropout(p=0.5)             # regularization

# Recurrent (sequences)
nn.LSTM(input_size=64, hidden_size=128, num_layers=2, batch_first=True)

# Transformer (attention)
nn.TransformerEncoderLayer(d_model=512, nhead=8, batch_first=True)

优化器

Adam 是最流行的优化器——自适应每参数学习率、收敛快、调参少。AdamW 修复 Adam 的权重衰减 bug,transformer 首选。带冲量的 SGD 在 CNN 上可能泛化更好但需更多调参。学习率调度器(余弦退火、one-cycle)常免费加 1-2% 准确率。学习率是最重要的超参数——永远先调它。

machine-learning
import torch.optim as optim

# SGD with momentum — robust default
optim.SGD(model.parameters(), lr=0.01, momentum=0.9, weight_decay=1e-4)

# Adam — adaptive, often faster convergence
optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-4)

# AdamW — proper weight decay (preferred over Adam for transformers)
optim.AdamW(model.parameters(), lr=1e-3, weight_decay=0.01)

# Learning rate scheduler — reduce LR over training
from torch.optim.lr_scheduler import CosineAnnealingLR, OneCycleLR
scheduler = CosineAnnealingLR(optimizer, T_max=epochs)
# or OneCycleLR for super-convergence (needs careful setup)

# In training loop:
optimizer.step()
scheduler.step()  # update LR each epoch (or batch for OneCycleLR)

过拟合与正则化

过拟合:训练损失持续降而验证损失升。Dropout 训练时随机置零单元,强迫冗余表示。权重衰减(L2)把权重收缩向零。早停监控验证损失并在平台期停止——务必存最佳检查点。数据增强是图像/文本最强正则化。其他方法失效时减少模型容量是最简单的修复。

machine-learning
# 1. Dropout — randomly zero out units during training
nn.Dropout(p=0.5)

# 2. Weight decay (L2 regularization) via optimizer
optim.AdamW(model.parameters(), lr=1e-3, weight_decay=0.01)

# 3. Early stopping — stop when validation loss stops improving
best_val, patience, counter = float("inf"), 5, 0
for epoch in range(epochs):
    val_loss = validate(model, val_loader)
    if val_loss < best_val:
        best_val, counter = val_loss, 0
        torch.save(model.state_dict(), "best.pt")  # save best
    else:
        counter += 1
        if counter >= patience:
            print("Early stopping at epoch", epoch)
            break

# 4. Data augmentation (especially for images/text)
# 5. Batch normalization (mild regularizing effect)
# 6. Reduce model capacity (fewer layers/units)
15

评估指标深入

精确率、召回率与 F1

精确率和召回率捕捉假正例与假负例之间的权衡。按业务成本选:精确率用于垃圾邮件(假正例=丢重要邮件),召回率用于癌症筛查(假负例=漏诊)。F1 等权平衡两者;F-beta 可倾斜——F2 权重召回两倍,F0.5 权重精确率两倍。始终同时报告精确率和召回率,不只 F1。

machine-learning
from sklearn.metrics import (precision_score, recall_score,
    f1_score, fbeta_score)

# Precision: TP / (TP + FP) — of predicted positives, how many are real
# Recall:    TP / (TP + FN) — of actual positives, how many we caught
# F1:        harmonic mean of precision and recall
# F-beta:    weighted (beta > 1 favors recall, < 1 favors precision)

precision = precision_score(y_test, y_pred)
recall    = recall_score(y_test, y_pred)
f1        = f1_score(y_test, y_pred)
f2        = fbeta_score(y_test, y_pred, beta=2.0)   # recall-weighted
f05       = fbeta_score(y_test, y_pred, beta=0.5)   # precision-weighted

# When to use which:
# - Precision matters when false positives are costly (spam filter)
# - Recall matters when false negatives are costly (cancer screening)
# - F2 when recall is more important; F0.5 when precision is more important

ROC-AUC 与 PR-AUC

ROC-AUC 衡量模型把正例排在负例之上的好坏——与阈值无关,所以有用。PR-AUC(average_precision_score)是精确率-召回率曲线下面积,在不平衡数据上比 ROC-AUC 更有信息量(ROC-AUC 看起来可能很好而 PR-AUC 很糟)。多分类时 'ovr'(一对多)和 'ovo'(一对一)计算 AUC 方式不同;不确定就都报。用 Youden's J 选操作阈值。

machine-learning
from sklearn.metrics import (roc_auc_score, average_precision_score,
    roc_curve, precision_recall_curve)

# ROC-AUC: probability that model ranks a random positive
#          above a random negative. Threshold-independent.
roc_auc = roc_auc_score(y_test, y_proba)

# PR-AUC (average precision): area under precision-recall curve.
# More informative than ROC-AUC on imbalanced data.
pr_auc = average_precision_score(y_test, y_proba)

# Multiclass ROC-AUC
roc_auc_ovr = roc_auc_score(y_test, y_proba_multi,
                            multi_class="ovr", average="macro")
roc_auc_ovo = roc_auc_score(y_test, y_proba_multi,
                            multi_class="ovo", average="macro")

# Find the optimal threshold (Youden's J = TPR - FPR)
fpr, tpr, thresholds = roc_curve(y_test, y_proba)
j_scores = tpr - fpr
best_threshold = thresholds[j_scores.argmax()]

混淆矩阵可视化

始终可视化混淆矩阵,不只看聚合指标——它显示哪些类互相混淆,揭示模型弱点(如数字分类器混淆 4 和 9)。按行(真实类)归一化看每类召回——类别不平衡时重要。ConfusionMatrixDisplay 使其一行代码。非对角单元格是错误集中处。

machine-learning
from sklearn.metrics import (confusion_matrix,
    ConfusionMatrixDisplay)
import matplotlib.pyplot as plt

cm = confusion_matrix(y_test, y_pred)

# Display with sklearn
disp = ConfusionMatrixDisplay(confusion_matrix=cm,
                              display_labels=class_names)
fig, ax = plt.subplots(figsize=(8, 6))
disp.plot(cmap="Blues", ax=ax, values_format="d")  # or ".2%" for normalized
plt.title("Confusion matrix")
plt.show()

# Normalized per row (true class) — see per-class recall
cm_norm = cm.astype(float) / cm.sum(axis=1, keepdims=True)
disp = ConfusionMatrixDisplay(confusion_matrix=cm_norm,
                              display_labels=class_names)
disp.plot(cmap="Blues", values_format=".2%")

回归指标回顾

报告多个指标:RMSE 对大误差敏感、MAE 典型误差、中位绝对误差对异常值稳健、max_error 最坏情况 SLA、R2 无单位可解释。偏斜目标(收入、价格)试对 y 做对数变换并在对数空间报指标——匹配乘性误差并稳定方差。始终用散点图对预测值与实际值做合理性检查。

machine-learning
from sklearn.metrics import (mean_squared_error, mean_absolute_error,
    r2_score, median_absolute_error, max_error,
    explained_variance_score, d2_tweedie_score)

# RMSE (penalizes large errors, same units as y)
rmse = mean_squared_error(y_test, y_pred, squared=False)

# MAE (robust to outliers, same units as y)
mae = mean_absolute_error(y_test, y_pred)

# Median AE (extremely robust to outliers)
med_ae = median_absolute_error(y_test, y_pred)

# Max error (worst single prediction — useful for SLAs)
max_err = max_error(y_test, y_pred)

# R^2 (1=perfect, 0=mean prediction, <0=worse than mean)
r2 = r2_score(y_test, y_pred)

# Explained variance (like R^2 but ignores bias)
evs = explained_variance_score(y_test, y_pred)

业务对齐指标

机器学习指标(准确率、F1、AUC)是你真正关心之物——业务结果——的代理。把业务成本转成自定义指标:欺诈假负例花 X 元,假正例花 Y 元,排名 k 的广告点击赚 Z 元。为真实目标(利润、拯救生命、防流失)优化而非代理。自定义阈值优化常比挤 0.01 AUC 更有价值。

machine-learning
# Sometimes the right metric isn't in sklearn — define your own.

# Example 1: Cost matrix (asymmetric costs)
def cost_weighted_error(y_true, y_pred, cost_fp=1, cost_fn=10):
    cm = confusion_matrix(y_true, y_pred)
    tn, fp, fn, tp = cm.ravel()
    return (cost_fp * fp + cost_fn * fn) / len(y_true)

# Example 2: Profit at K (top-K precision weighted by revenue)
def profit_at_k(y_true, y_proba, revenue_per_hit, k=100):
    top_k_idx = np.argsort(y_proba)[::-1][:k]
    hits = y_true[top_k_idx].sum()
    return hits * revenue_per_hit - k * cost_per_contact

# Example 3: Custom threshold optimization
def optimize_threshold_for_profit(y_true, y_proba, profit_matrix):
    best_t, best_profit = 0.5, -float("inf")
    for t in np.linspace(0.01, 0.99, 99):
        y_pred = (y_proba >= t).astype(int)
        profit = profit_matrix(y_true, y_pred)
        if profit > best_profit:
            best_t, best_profit = t, profit
    return best_t, best_profit
16

管道

基础管道

Pipeline 把预处理和建模链成单一估计器,确保 CV 时预处理只在训练折拟合(防泄露)。也简化部署——一个对象而非分离的缩放器、填充器、模型。用命名步骤以便清晰。Pipeline 是诚实、可复现机器学习最干净的方式——让它成为默认而非优化。

machine-learning
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.impute import SimpleImputer
from sklearn.ensemble import RandomForestClassifier

pipe = Pipeline([
    ("impute", SimpleImputer(strategy="median")),
    ("scale",  StandardScaler()),
    ("clf",    RandomForestClassifier(random_state=42)),
])

# Use like any estimator
pipe.fit(X_train, y_train)
y_pred = pipe.predict(X_test)
score = pipe.score(X_test, y_test)

# Access steps
pipe.named_steps["clf"]
pipe[:-1].get_feature_names_out()  # see transformed feature names

# Pipelines prevent leakage: each CV fold fits its own preprocessing

ColumnTransformer(异构数据)

ColumnTransformer 对数值列与类别列应用不同预处理——真实表格数据必需。每个子管道处理自己的填充和编码。remainder='drop' 丢弃未列列;'passthrough' 原样保留。把 ColumnTransformer 包进带模型的 Pipeline 得到异构预处理的无泄露 CV。这是表格机器学习的生产就绪模式。

machine-learning
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import (StandardScaler, OneHotEncoder)
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer

numeric_features     = ["age", "income", "score"]
categorical_features = ["city", "occupation"]

# Different preprocessing for different column types
preprocessor = ColumnTransformer(
    transformers=[
        ("num", Pipeline([
            ("impute", SimpleImputer(strategy="median")),
            ("scale",  StandardScaler()),
        ]), numeric_features),
        ("cat", Pipeline([
            ("impute", SimpleImputer(strategy="most_frequent")),
            ("onehot", OneHotEncoder(handle_unknown="ignore")),
        ]), categorical_features),
    ],
    remainder="drop",  # or 'passthrough' to keep other columns
)

full_pipe = Pipeline([
    ("preprocess", preprocessor),
    ("clf", RandomForestClassifier(random_state=42)),
])
full_pipe.fit(X_train, y_train)

带调优的管道

Pipeline 与 GridSearchCV 组合优美——可用 'step__param' 语法联合调优预处理和模型超参数。这找到此模型的最佳预处理组合,常异于孤立的最佳预处理。始终调优完整管道,不只模型。联合调优更诚实(不偷看测试数据选预处理)且常找到更好配置。

machine-learning
from sklearn.model_selection import GridSearchCV

# Reference nested parameters with 'step__param' syntax
param_grid = {
    "preprocess__num__impute__strategy": ["mean", "median"],
    "clf__n_estimators": [50, 100, 200],
    "clf__max_depth":    [3, 5, 10, None],
}

grid = GridSearchCV(full_pipe, param_grid, cv=5, scoring="f1", n_jobs=-1)
grid.fit(X_train, y_train)
print("Best params:", grid.best_params_)

# Tune preprocessing AND model together — finds the best combo
# e.g. median imputation + 100 trees + max_depth=10

自定义转换器

自定义转换器让你把领域特定预处理(对数变换、日期特征提取、文本清洗)封装成可复用、管道兼容的组件。继承 BaseEstimator 和 TransformerMixin,实现 fit(常只返回 self)和 transform。这使特征工程版本化、可测试、无泄露——远胜把逻辑散落各 notebook。一次性函数用 FunctionTransformer。

machine-learning
from sklearn.base import BaseEstimator, TransformerMixin
import numpy as np

class LogTransformer(BaseEstimator, TransformerMixin):
    def __init__(self, columns=None):
        self.columns = columns

    def fit(self, X, y=None):
        return self  # nothing to fit

    def transform(self, X):
        X = X.copy()
        if self.columns is None:
            return np.log1p(X)
        X[self.columns] = np.log1p(X[self.columns])
        return X

# Use in a pipeline
pipe = Pipeline([
    ("log",  LogTransformer(columns=["income", "price"])),
    ("scale", StandardScaler()),
    ("clf",   LogisticRegression()),
])

# For more complex logic, use FunctionTransformer or write a full class

保存与加载管道

joblib(非 pickle)是序列化 sklearn 管道的标准——高效处理 numpy 数组。生产中锁定 sklearn 版本:一个版本存的模型不保证在另一版本加载。跨语言部署(从 Java/Go 服务 Python 模型)用 skl2onnx 转 ONNX。始终存完整管道而非只模型,使推理时预处理一致。

machine-learning
import joblib

# Save the entire fitted pipeline
joblib.dump(full_pipe, "model.pkl")

# Later (e.g. in a web service)
loaded = joblib.load("model.pkl")
predictions = loaded.predict(new_data)

# Why joblib over pickle?
# - More efficient for numpy arrays (which sklearn models contain)
# - Handles objects with large numerical arrays via compression

# Version compatibility:
# - sklearn versions must match between save and load in production
# - Pin sklearn in requirements.txt: scikit-learn==1.4.2

# For cross-language deployment, use ONNX:
# import skl2onnx
# onx = skl2onnx.convert_sklearn(full_pipe, ...)
17

模型持久化

joblib vs pickle

sklearn 模型用 joblib——对 numpy 数组优化且原生支持压缩。pickle 是任意 Python 对象的兜底。两者有相同安全警告:绝不要加载不可信的 pickle/joblib 文件——反序列化时可执行任意代码。向不可信环境服务模型用 ONNX 或带输入验证的模型服务器。

machine-learning
import joblib
import pickle

# joblib — preferred for sklearn (efficient with numpy arrays)
joblib.dump(model, "model.joblib", compress=3)
model = joblib.load("model.joblib")

# pickle — standard library, works for any picklable object
with open("model.pkl", "wb") as f:
    pickle.dump(model, f)
with open("model.pkl", "rb") as f:
    model = pickle.load(f)

# Compress to save disk space (joblib supports it natively)
joblib.dump(model, "model.joblib", compress=("xz", 3))

# Load partially for inspection without full deserialize
# (advanced, not commonly needed)

模型版本管理

始终用元数据版本化模型:框架版本(sklearn、python)、特征列表、训练数据哈希、指标、git 提交、时间戳。这使生产问题调试可行——当模型行为不同时,可比较元数据看变化。用语义化版本:补丁为重训,次为新特征,主为破坏性变更(破坏旧预测的新预处理)。

machine-learning
# Save metadata alongside the model artifact
import joblib, json
from datetime import datetime

metadata = {
    "model_type": "RandomForestClassifier",
    "sklearn_version": sklearn.__version__,
    "python_version": sys.version,
    "features": list(X_train.columns),
    "target": "churn",
    "metrics": {"f1": 0.842, "roc_auc": 0.913},
    "trained_at": datetime.utcnow().isoformat(),
    "data_hash": hashlib.md5(pd.util.hash_pandas_object(X_train).values).hexdigest(),
    "git_commit": subprocess.check_output(["git", "rev-parse", "HEAD"]).decode().strip(),
}

joblib.dump({"model": model, "metadata": metadata}, "model_v1.2.0.joblib")

# Convention: semantic versioning
# v1.0.0 -> initial release
# v1.1.0 -> retrained on new data (backward compatible)
# v2.0.0 -> breaking change (new features, new preprocessing)

ONNX 导出

ONNX 是跨平台模型格式——转换 sklearn 管道一次,在任何语言(Python、C++、Java、JavaScript、Go)运行无需 Python 依赖。生产理想之选:训练环境(Python)与服务环境(如 Go 微服务)可不同。ONNX runtime 对许多模型比 sklearn 快。注意:并非所有 sklearn 转换器和模型有 ONNX 等价物——承诺前查 skl2onnx 支持。

machine-learning
# pip install skl2onnx onnxruntime
from skl2onnx import convert_sklearn
from skl2onnx.common.data_types import FloatTensorType
import onnxruntime as rt

# Convert sklearn pipeline to ONNX
initial_type = [("float_input", FloatTensorType([None, X_train.shape[1]]))]
onnx_model = convert_sklearn(full_pipe, initial_types=initial_type,
                              target_opset=15)

with open("model.onnx", "wb") as f:
    f.write(onnx_model.SerializeToString())

# Run inference in any language (Python, C++, Java, JS, Go, ...)
sess = rt.InferenceSession("model.onnx")
input_name = sess.get_inputs()[0].name
predictions = sess.run(None, {input_name: X_test.astype(np.float32)})[0]

# Benefits: no Python at inference, fast, cross-platform

模型监控基础

模型在生产中随世界变化而退化——监控漂移。数据漂移:输入特征分布变(如用户人口结构变)。概念漂移:特征与目标关系变(如欺诈模式演化)。PSI(群体稳定性指数)是简单可解释的漂移指标——>0.25 值需重训。结合漂移告警与周期重训计划。始终记录预测以支持事后分析。

machine-learning
# After deployment, monitor for:
# 1. Data drift: input distribution changes
# 2. Concept drift: P(y|X) changes
# 3. Prediction drift: output distribution changes
# 4. Performance degradation: metric drops over time

# Simple data drift check: PSI (Population Stability Index)
def psi(expected, actual, bins=10):
    expected_pct = np.histogram(expected, bins=bins)[0] / len(expected)
    actual_pct   = np.histogram(actual,   bins=bins)[0] / len(actual)
    # Avoid log(0)
    expected_pct = np.clip(expected_pct, 1e-4, None)
    actual_pct   = np.clip(actual_pct,   1e-4, None)
    return np.sum((actual_pct - expected_pct) * np.log(actual_pct / expected_pct))

# PSI < 0.1: stable, 0.1-0.25: minor drift, > 0.25: significant drift
for col in X.columns:
    score = psi(X_train[col], X_new[col])
    if score > 0.25:
        print(f"Drift detected in {col}: PSI={score:.3f}")

重训策略

按数据变化速度选重训策略。定时(最简、可预测)。性能触发(反应式,仅需要时重训)。漂移触发(性能降前早期预警)。在线学习(持续更新,适合欺诈/推荐等快变数据)。始终在近期数据上验证新模型胜过旧模型,影子部署对比线上预测,指标退化时自动回滚。

machine-learning
# 1. Scheduled retraining (simplest)
#    Retrain weekly/monthly regardless of performance
schedule = "0 2 * * 0"  # every Sunday at 2am

# 2. Performance-triggered retraining
#    Retrain when a metric drops below threshold
if current_f1 < production_f1 * 0.95:  # 5% drop
    trigger_retrain()

# 3. Drift-triggered retraining
#    Retrain when PSI exceeds threshold
if max_psi > 0.25:
    trigger_retrain()

# 4. Online learning (incremental updates)
from sklearn.linear_model import SGDClassifier
model = SGDClassifier()
for X_batch, y_batch in stream:
    model.partial_fit(X_batch, y_batch, classes=[0, 1])

# Always:
# - Validate new model beats old on recent data before promoting
# - Shadow deploy: run new model in parallel, compare predictions
# - Roll back automatically if metrics regress
# - Keep training data fresh (drop old samples)
18

正则化

L1(Lasso)vs L2(Ridge)

L1(Lasso)加绝对值惩罚把部分系数驱动到精确零——内置特征选择,适合多特征无关时。L2(Ridge)加平方惩罚平滑收缩系数——多特征各贡献一点时更好。ElasticNet 融合两者,特征相关时首选(Lasso 单独随机挑一个并置零其余)。务必先缩放特征使惩罚公平。

machine-learning
from sklearn.linear_model import Lasso, Ridge, ElasticNet
import numpy as np

# L1 (Lasso): penalty = alpha * sum(|coef|)
# Drives some coefficients to exactly 0 -> sparse -> feature selection
lasso = Lasso(alpha=0.1)
lasso.fit(X_train_scaled, y_train)
print("Non-zero coefs:", (lasso.coef_ != 0).sum(), "/", len(lasso.coef_))

# L2 (Ridge): penalty = alpha * sum(coef^2)
# Shrinks coefficients smoothly toward 0, keeps all features
ridge = Ridge(alpha=1.0)
ridge.fit(X_train_scaled, y_train)

# ElasticNet: mix of L1 and L2
# penalty = alpha * (l1_ratio * L1 + (1 - l1_ratio) * L2)
en = ElasticNet(alpha=0.1, l1_ratio=0.5)
en.fit(X_train_scaled, y_train)

# Rule of thumb:
# - L1: many features, only a few matter (sparse)
# - L2: many features, all matter a little
# - ElasticNet: correlated features (Lasso picks one randomly)

树的正则化

对树,max_depth 和 min_samples_leaf 是最强正则化器——防止树记住单个样本。ccp_alpha 做成本复杂度剪枝(更有原则的事后剪枝)。随机森林用 max_features 和 bootstrap 通过随机性加正则。梯度提升用 learning_rate 和 early_stopping 关键(低 LR+早停+足够树通常胜出)。subsample 和 colsample_bytree 通过随机性加进一步正则。

machine-learning
from sklearn.ensemble import RandomForestClassifier
from sklearn.tree import DecisionTreeClassifier

# Decision tree regularization hyperparameters
tree = DecisionTreeClassifier(
    max_depth=5,             # limit tree depth (strongest lever)
    min_samples_split=20,    # min samples to split a node
    min_samples_leaf=10,     # min samples per leaf
    max_leaf_nodes=20,       # cap total leaves
    max_features="sqrt",     # features considered per split
    ccp_alpha=0.01,          # cost-complexity pruning
)

# Random forest adds more regularization via:
rf = RandomForestClassifier(
    n_estimators=200,
    max_depth=None,
    min_samples_leaf=5,
    max_features="sqrt",     # random feature subset per split
    bootstrap=True,          # bagging
    max_samples=0.8,         # subsample ratio
)

# For boosting (XGBoost), also use:
# - learning_rate (smaller = more regularization)
# - subsample, colsample_bytree (randomness)
# - reg_lambda, reg_alpha (L2/L1 on leaf weights)
# - gamma (minimum loss reduction to split)
# - early_stopping_rounds

Dropout 与 BatchNorm(深度学习)

Dropout 训练时随机置零激活,强迫网络学冗余表示——强正则化。大层高 dropout、小层低、输出前常零。BatchNorm 按批次归一化激活,稳定训练并加轻度正则效果(批次统计加噪声)。记住:dropout 和 batchnorm 在训练与评估时行为不同——推理务必调 model.eval(),恢复训练调 model.train()。

machine-learning
import torch.nn as nn

model = nn.Sequential(
    nn.Linear(784, 256),
    nn.BatchNorm1d(256),     # normalize activations -> faster, stable training
    nn.ReLU(),
    nn.Dropout(0.5),         # randomly zero 50% of activations during training

    nn.Linear(256, 128),
    nn.BatchNorm1d(128),
    nn.ReLU(),
    nn.Dropout(0.3),         # less dropout closer to output

    nn.Linear(128, 10),      # no dropout before output (or use very low)
)

# IMPORTANT: dropout is OFF during inference (model.eval())
# BatchNorm uses batch stats in training and running stats in eval

# Common patterns:
# - Higher dropout in big layers, lower in small layers
# - No dropout right after input or right before output
# - BatchNorm before activation (debated; try both)

早停

早停在验证损失停止改善时停止训练,防过拟合且省算力。务必存最佳模型(最低验证损失),非最终模型——最终模型可能已过拟合。patience 控制容忍多少个非改善轮次。梯度提升的 early_stopping_rounds 在树级别做同样事。早停是最高价值、最低成本的正则化器之一——深度学习始终使用。

machine-learning
import torch

best_val_loss = float("inf")
patience, counter = 5, 0
best_state = None

for epoch in range(max_epochs):
    train_loss = train_one_epoch(model, train_loader, optimizer, criterion)
    val_loss   = validate(model, val_loader, criterion)

    if val_loss < best_val_loss:
        best_val_loss = val_loss
        counter = 0
        best_state = {k: v.clone() for k, v in model.state_dict().items()}
    else:
        counter += 1
        if counter >= patience:
            print(f"Early stopping at epoch {epoch}")
            break

# Restore the best model
model.load_state_dict(best_state)

# For XGBoost/LightGBM, use early_stopping_rounds:
# model.fit(X_train, y_train,
#           eval_set=[(X_val, y_val)],
#           early_stopping_rounds=20)

数据增强

数据增强是标注数据有限时最强正则化器——通过保标签变换合成扩展数据集。图像:翻转、裁剪、颜色抖动、旋转(领域适宜——别翻转医学 X 光,翻转自然图像可以)。文本:同义词替换、回译、随机删除。表格:SMOTE(不平衡)或 mixup(回归)。增强有效给你免费标注数据。

machine-learning
# Images (torchvision)
import torchvision.transforms as T

train_transform = T.Compose([
    T.RandomResizedCrop(224),
    T.RandomHorizontalFlip(),
    T.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2),
    T.RandomRotation(10),
    T.ToTensor(),
    T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])

# Text (NLPAug or simple Python)
import random
def augment_text(text):
    # Synonym replacement, random deletion, back-translation
    words = text.split()
    if random.random() < 0.3:
        idx = random.randrange(len(words))
        words[idx] = synonym(words[idx])
    if random.random() < 0.2:
        words = [w for w in words if random.random() > 0.1]
    return " ".join(words)

# Tabular (SMOTE for imbalance, mixup for regression)
# mixup: X_mix = alpha*X1 + (1-alpha)*X2, y_mix = alpha*y1 + (1-alpha)*y2

这篇内容对您有帮助吗?

学习路径

从零开始学习

通过结构化课程从头学习这个语言。