📑 本页目录(点开跳转)
附录 B · 代码速查
📌 需要什么
Ctrl+F搜,复制粘贴。不要通读。⚠️ 先确认你要的是哪一份。走错门两边都用不上:
附录 B(这里) 附录 C · 手撕代码速查 定位 能跑的模板 能默写的核心 写法 调 sklearn / PyTorch 高层 API 只用 numpy / 裸 torch,不许调那个 API 本身 什么时候用 要把实验跑起来、要上线 ⭐ 面试官说「白板写一个 MHA」的时候 这里的
nn.LayerNorm(8)一行就完事;面试官要的是那一行里面的四行(有偏方差、eps在sqrt里面)。 ⭐ 两边覆盖的题几乎不重叠,Ctrl+F在这边搜不到的,先去附录 C 搜一遍再说没有。
📦 环境
pip install numpy scikit-learn # 第 1-6 章
pip install torch # 第 7 章起
pip install lightgbm xgboost # 可选,第 4 章
🔪 数据切分(第 5 章)
from sklearn.model_selection import (train_test_split, StratifiedKFold,
KFold, GroupKFold, TimeSeriesSplit)
# 单次切分:分类务必加 stratify
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.2,
random_state=42, stratify=y)
cv = StratifiedKFold(5, shuffle=True, random_state=42) # ⭐ 分类默认
cv = TimeSeriesSplit(n_splits=5) # ⭐ 时序必须
cv = GroupKFold(n_splits=5) # 同用户多条数据
🧪 Pipeline(防泄漏,必用)
from sklearn.pipeline import Pipeline, make_pipeline
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
# ⭐ 所有会"看到数据统计量"的步骤都要放进来
pre = ColumnTransformer([
("num", Pipeline([("imp", SimpleImputer(strategy="median")),
("sc", StandardScaler())]), num_cols),
("cat", Pipeline([("imp", SimpleImputer(strategy="most_frequent")),
("oh", OneHotEncoder(handle_unknown="ignore"))]), cat_cols),
])
pipe = Pipeline([("pre", pre), ("model", LGBMClassifier())])
from sklearn.model_selection import cross_val_score
scores = cross_val_score(pipe, X, y, cv=cv, scoring="roc_auc")
print(f"{scores.mean():.4f} ± {scores.std():.4f}") # ⭐ 永远带标准差
📏 评估指标
from sklearn.metrics import (accuracy_score, precision_score, recall_score,
f1_score, roc_auc_score, average_precision_score,
confusion_matrix, classification_report)
prob = model.predict_proba(X_te)[:, 1]
print(classification_report(y_te, model.predict(X_te)))
print("AUC ", roc_auc_score(y_te, prob))
print("PR-AUC", average_precision_score(y_te, prob)) # ⭐ 不平衡时看这个
# 阈值扫描:找最佳 F1
import numpy as np
best = max(((f1_score(y_te, (prob >= t).astype(int)), t)
for t in np.arange(0.05, 0.95, 0.01)))
print(f"最佳阈值 {best[1]:.2f} F1={best[0]:.3f}")
🌲 树模型(第 4 章)
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
import lightgbm as lgb
rf = RandomForestClassifier(n_estimators=300, n_jobs=-1, random_state=0)
# LightGBM 常用配置 + 早停
m = lgb.LGBMClassifier(
n_estimators=2000, learning_rate=0.05, num_leaves=31,
max_depth=-1, subsample=0.8, colsample_bytree=0.8,
reg_alpha=0.0, reg_lambda=1.0, random_state=0)
m.fit(X_tr, y_tr, eval_set=[(X_va, y_va)], eval_metric="auc",
callbacks=[lgb.early_stopping(100), lgb.log_evaluation(200)])
# ⭐ 比自带 importance 可靠
from sklearn.inspection import permutation_importance
r = permutation_importance(m, X_va, y_va, n_repeats=10, random_state=0)
for i in r.importances_mean.argsort()[::-1][:10]:
print(f"{feature_names[i]:<25}{r.importances_mean[i]:.4f}")
🧠 PyTorch 训练模板(第 15 章,实测可跑)
import numpy as np, torch, torch.nn as nn
from torch.utils.data import TensorDataset, DataLoader
def set_seed(s=42):
import random
random.seed(s); np.random.seed(s)
torch.manual_seed(s); torch.cuda.manual_seed_all(s)
set_seed()
device = "cuda" if torch.cuda.is_available() else "cpu"
model = nn.Sequential(
nn.Linear(30, 64), nn.ReLU(), nn.Dropout(0.2),
nn.Linear(64, 32), nn.ReLU(),
nn.Linear(32, 1), # ⭐ 二分类不加 Sigmoid
).to(device)
criterion = nn.BCEWithLogitsLoss()
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=0.01)
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=60)
loader = DataLoader(TensorDataset(Xtr, ytr), batch_size=32, shuffle=True,
num_workers=4, pin_memory=True)
# ===== 第0步:10样本过拟合检查(第11章)⭐ =====
tx, ty = Xtr[:10].to(device), ytr[:10].to(device)
for _ in range(300):
optimizer.zero_grad(); loss = criterion(model(tx), ty)
loss.backward(); optimizer.step()
assert loss.item() < 0.01, "❌ 10样本都过拟合不了,有bug,别往下走"
# ===== 正式训练 + 早停 =====
best, wait, patience, best_state = float("inf"), 0, 10, None
for ep in range(60):
model.train(); total = 0.0
for xb, yb in loader:
xb, yb = xb.to(device), yb.to(device)
optimizer.zero_grad() # ⭐
loss = criterion(model(xb), yb)
loss.backward()
gn = torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0).item()
optimizer.step(); total += loss.item()*len(xb)
scheduler.step()
model.eval() # ⭐
with torch.no_grad(): # ⭐
vl = criterion(model(Xva.to(device)), yva.to(device)).item()
print(f"ep {ep:<3} train {total/len(Xtr):.4f} | val {vl:.4f} "
f"| lr {optimizer.param_groups[0]['lr']:.2e} | grad {gn:.2f}")
if vl < best:
best, wait = vl, 0
best_state = {k: v.clone() for k, v in model.state_dict().items()}
else:
wait += 1
if wait >= patience:
model.load_state_dict(best_state); print(f"早停 ep{ep}"); break
🔬 梯度检验(第 8、18 章)
def grad_check(f, param, analytic_grad, idx, eps=1e-5):
"""f: 只依赖 param 的损失函数;idx: 要检查的位置"""
orig = param[idx]
param[idx] = orig + eps; lp = f()
param[idx] = orig - eps; lm = f()
param[idx] = orig
num = (lp - lm) / (2*eps)
rel = abs(num - analytic_grad) / (abs(num) + abs(analytic_grad) + 1e-12)
print(f"数值 {num:.8f} | 解析 {analytic_grad:.8f} | 相对误差 {rel:.2e} "
f"{'✅' if rel < 1e-6 else '❌'}")
🖼️ 迁移学习(第 12 章)
import torchvision.models as models, torch.nn as nn
model = models.resnet18(weights="IMAGENET1K_V1")
model.fc = nn.Linear(model.fc.in_features, num_classes)
# 数据少:冻结主干
for p in model.parameters(): p.requires_grad = False
for p in model.fc.parameters(): p.requires_grad = True
# 数据多:全微调,lr 小 10 倍
optimizer = torch.optim.AdamW([
{"params": model.fc.parameters(), "lr": 1e-3},
{"params": [p for n,p in model.named_parameters() if not n.startswith("fc")],
"lr": 1e-4},
])
🧹 特征工程(第 16 章)
import numpy as np, pandas as pd
df["log_amt"] = np.log1p(df["amount"]) # 长尾
df["age_bin"] = pd.qcut(df["age"], 10, labels=False, duplicates="drop")
df["amt_missing"] = df["amount"].isna().astype(int) # ⭐ 缺失本身是信号
df["hour_sin"] = np.sin(2*np.pi*df["hour"]/24) # ⭐ 周期性
df["hour_cos"] = np.cos(2*np.pi*df["hour"]/24)
# 聚合统计(提分最猛)
agg = df.groupby("user_id")["amount"].agg(["count","mean","max","std"])
agg.columns = [f"user_amt_{c}" for c in agg.columns]
df = df.merge(agg, on="user_id", how="left")
# OOF 目标编码(防泄漏)
def target_encode_oof(df, col, target, n_splits=5, smooth=20):
from sklearn.model_selection import KFold
gm = df[target].mean(); oof = np.full(len(df), np.nan)
for tr, va in KFold(n_splits, shuffle=True, random_state=0).split(df):
s = df.iloc[tr].groupby(col)[target].agg(["mean","count"])
sm = (s["mean"]*s["count"] + gm*smooth) / (s["count"]+smooth)
oof[va] = df.iloc[va][col].map(sm).fillna(gm).values
return oof
🐛 常见报错速查
| 报错 / 症状 | 原因 | 修 |
|---|---|---|
loss = nan |
lr 太大 / log(0) / 数据含 nan | lr÷10;log 加 1e-9;np.isnan(X).any() |
| loss 完全不动 | 忘 optimizer.step() / zero_grad() / 梯度断了 |
打印 p.grad.abs().mean() |
expected scalar type Long but found Float |
CrossEntropyLoss 标签要 int64 | y.long() |
Expected all tensors on same device |
模型和数据不在同一设备 | 两边都 .to(device) |
CUDA out of memory |
batch 太大 | 减 batch / 梯度累积 / AMP |
| 验证结果每次不同 | 忘了 model.eval() |
加上 |
| 验证时显存暴涨 | 忘了 torch.no_grad() |
加上 |
| CV 0.95 测试 0.60 | 数据泄漏 | 检查 Pipeline、切分方式(第 5 章) |
| CV 分数好得离谱 | 目标泄漏 | 看特征重要性,找那个异常高的 |
| GPU 利用率忽高忽低 | 数据加载瓶颈 | num_workers=4~8, pin_memory=True |
sklearn penalty 警告 |
1.8+ 改用 l1_ratio |
l1_ratio=1 + solver="saga" |
⚡ 提速
# 混合精度
import torch
scaler = torch.amp.GradScaler()
with torch.amp.autocast("cuda"):
loss = criterion(model(xb), yb)
scaler.scale(loss).backward(); scaler.step(optimizer); scaler.update()
# 编译(PyTorch 2.0+)
model = torch.compile(model)
# 并行 CV
cross_val_score(pipe, X, y, cv=cv, n_jobs=-1)
🔗 这份查不到的,去哪查
| 去哪 | 为什么要去那里 |
|---|---|
| 附录 C · 手撕代码速查 ⭐ | 这里全是调 API。要在白板上默写 Softmax+CE 的反向、MHA 的 view/transpose 顺序、BatchNorm 的 running_var 为什么存无偏、AUC 的秩和公式,那边有 10 题拆开的实现 |
| 11 · 训练调试手册 | 上面「常见报错速查」只给了一行修法。真卡住了去那里按顺序排查(从 10 样本过拟合检查开始) |
| 附录 A · 术语速查卡 | 你搜的是名词不是代码时用那份 |
👉 回到首页