34. 经典机器学习基础
LLM/Agent 工程师必备:经典 ML 是深度学习的基石,面试中考察频率极高,且直接关联特征工程、模型评估等日常工程实践
一、线性回归与逻辑回归
1. ⭐ Q: 线性回归的最小二乘法解是怎么推导的?
答:
模型:$\hat{y} = Xw + b$,简化为 $\hat{y} = X\theta$(将 b 并入 θ)
目标函数(MSE):
$$L(\theta) = \frac{1}{2n} |X\theta - y|^2 = \frac{1}{2n}(X\theta - y)^T(X\theta - y)$$
推导:
$$\nabla_\theta L = \frac{1}{n} X^T(X\theta - y) = 0$$
$$X^T X \theta = X^T y$$
$$\theta^* = (X^T X)^{-1} X^T y$$
其中 $(X^T X)^{-1} X^T$ 称为伪逆矩阵(Moore-Penrose Pseudoinverse)。
import numpy as np
class LinearRegression:
def __init__(self):
self.theta = None
def fit(self, X, y):
# 添加偏置列
X_b = np.c_[np.ones((X.shape[0], 1)), X]
# 正规方程求解
self.theta = np.linalg.pinv(X_b.T @ X_b) @ X_b.T @ y
def predict(self, X):
X_b = np.c_[np.ones((X.shape[0], 1)), X]
return X_b @ self.theta
# 对比 sklearn
from sklearn.linear_model import LinearRegression
model = LinearRegression()
model.fit(X, y)
print(model.coef_, model.intercept_)追问:
- 什么时候不能用正规方程? 当 $X^TX$ 不可逆(特征数 > 样本数,或存在共线性)时,需要加正则化:$\theta = (X^TX + \lambda I)^{-1}X^Ty$
- 时间复杂度? 求逆 $O(d^3)$,当维度 d 很大时用梯度下降
2. ⭐⭐ Q: 逻辑回归的损失函数为什么用交叉熵而不是 MSE?
答:
Sigmoid 函数:$\sigma(z) = \frac{1}{1+e^{-z}}$
MSE 损失:$L_{MSE} = \frac{1}{2}(\sigma(w^Tx) - y)^2$
交叉熵损失:$L_{CE} = -[y\log\sigma(w^Tx) + (1-y)\log(1-\sigma(w^Tx))]$
两个关键原因:
① MSE 的梯度问题(非凸 + 梯度消失):
import numpy as np
import matplotlib.pyplot as plt
z = np.linspace(-6, 6, 200)
sigmoid = 1 / (1 + np.exp(-z))
# MSE 对 z 的梯度
mse_grad = (sigmoid - 1) * sigmoid * (1 - sigmoid) # σ'(z) × (σ(z)-y)
# 当 σ(z)→1 且 y=1 时,(σ-y)→0 且 σ'→0,梯度双重衰减
# 交叉熵对 z 的梯度
ce_grad = sigmoid - 1 # 非常简洁!当 y=1 时
# ∂L_CE/∂z = σ(z) - y,梯度只与误差成正比,不会饱和② 从最大似然角度推导交叉熵:
$$P(y|x;w) = [\sigma(w^Tx)]^y [1-\sigma(w^Tx)]^{1-y}$$
$$\log L(w) = \sum_i [y_i\log\sigma(w^Tx_i) + (1-y_i)\log(1-\sigma(w^Tx_i))]$$
最大化对数似然 = 最小化交叉熵损失。从概率建模出发,交叉熵是自然结果。
面试技巧:画出 MSE 和交叉熵的损失曲面,交叉熵是凸函数(对 LR 参数),MSE 对 LR 是非凸的。
3. ⭐⭐ Q: 逻辑回归从零推导,包括梯度下降更新公式
答:
import numpy as np
class LogisticRegression:
def __init__(self, lr=0.01, n_iters=1000):
self.lr = lr
self.n_iters = n_iters
def sigmoid(self, z):
return 1 / (1 + np.exp(-np.clip(z, -500, 500)))
def fit(self, X, y):
n_samples, n_features = X.shape
self.w = np.zeros(n_features)
self.b = 0
for _ in range(self.n_iters):
z = X @ self.w + self.b
y_pred = self.sigmoid(z)
# 梯度
dw = (1/n_samples) * X.T @ (y_pred - y)
db = (1/n_samples) * np.sum(y_pred - y)
# 更新
self.w -= self.lr * dw
self.b -= self.lr * db
def predict(self, X):
z = X @ self.w + self.b
return (self.sigmoid(z) >= 0.5).astype(int)追问:
- LR 能处理非线性问题吗? 原始不能,需要手动做特征交叉/多项式特征
- LR 多分类? 用 Softmax 回归(多项逻辑回归),输出层换成 softmax
4. ⭐⭐ Q: 逻辑回归 vs 线性回归,本质区别是什么?
答:
| 维度 | 线性回归 | 逻辑回归 |
|---|---|---|
| 任务 | 回归 | 分类 |
| 输出 | $w^Tx+b$(实数) | $\sigma(w^Tx+b) \in (0,1)$ |
| 损失 | MSE | 交叉熵 |
| 概率解释 | 无直接概率意义 | 输出是概率(经 Sigmoid 映射) |
| 优化 | 正规方程有闭式解 | 无闭式解,迭代优化 |
本质:逻辑回归 = 线性回归 + Sigmoid 激活 + 交叉熵损失。名字叫"回归",实际是分类器。
二、决策树与集成学习
5. ⭐ Q: 信息增益和基尼系数有什么区别?
答:
信息熵:$H(D) = -\sum_{k=1}^K p_k \log_2 p_k$
条件熵:$H(D|A) = \sum_{v} \frac{|D_v|}{|D|} H(D_v)$
信息增益:$IG(D, A) = H(D) - H(D|A)$(ID3 用)
信息增益比:$IGR(D,A) = \frac{IG(D,A)}{H_A(D)}$(C4.5 用,修正偏好多值特征)
基尼系数:$Gini(D) = 1 - \sum_{k=1}^K p_k^2$(CART 用)
import numpy as np
from collections import Counter
def entropy(y):
counts = np.bincount(y)
probs = counts[counts > 0] / len(y)
return -np.sum(probs * np.log2(probs))
def gini(y):
counts = np.bincount(y)
probs = counts[counts > 0] / len(y)
return 1 - np.sum(probs ** 2)
y = np.array([0, 0, 1, 1, 1])
print(f"Entropy: {entropy(y):.4f}") # 0.9710
print(f"Gini: {gini(y):.4f}") # 0.4800追问:为什么 CART 选基尼系数?计算快(不需要对数),且与信息增益在二分类下行为类似。
6. ⭐⭐ Q: ID3、C4.5、CART 三种决策树有什么区别?
答:
| 特性 | ID3 | C4.5 | CART |
|---|---|---|---|
| 划分标准 | 信息增益 | 信息增益比 | 基尼系数/方差 |
| 树类型 | 多叉树 | 多叉树 | 二叉树 |
| 连续值 | ❌ | ✅(二分法) | ✅ |
| 缺失值 | ❌ | ✅ | ✅ |
| 剪枝 | 无 | 悲观剪枝 | 代价复杂度剪枝 |
| 任务 | 分类 | 分类 | 分类+回归 |
7. ⭐⭐⭐ Q: Bagging 和 Boosting 的区别?各自的代表算法?
答:
| 维度 | Bagging | Boosting |
|---|---|---|
| 采样 | 并行,Bootstrap 有放回采样 | 串行,关注错分样本 |
| 权重 | 所有基学习器等权 | 根据误差加权 |
| 偏差/方差 | 降低方差 | 降低偏差 |
| 过拟合 | 不易过拟合 | 容易过拟合(需调参) |
| 代表 | 随机森林 | GBDT, XGBoost, LightGBM |
类比:Bagging 像「民主投票」——多个独立的专家投票;Boosting 像「师傅带徒弟」——每一轮针对上一轮的错误重点训练。
8. ⭐⭐ Q: 随机森林的"随机"体现在哪里?
答:
两层随机性:
- 样本随机:每棵树用 Bootstrap 采样(有放回),约 63.2% 的样本被选中
- 特征随机:每个节点分裂时,只从 $m = \sqrt{d}$ 个特征中选最优(d 是总特征数)
from sklearn.ensemble import RandomForestClassifier
# 关键参数
rf = RandomForestClassifier(
n_estimators=100, # 树的数量
max_features='sqrt', # 每次选 sqrt(d) 个特征(分类)
# max_features=0.3, # 也可以指定比例
max_depth=None, # 不限深度(靠 Bagging 控制方差)
oob_score=True, # 用袋外样本评估
random_state=42
)追问:什么是 OOB Score?每棵树没被采样到的约 36.8% 样本,可作为该树的验证集,汇总后得到 OOB 误差估计(免费的交叉验证)。
9. ⭐⭐⭐ Q: GBDT 的梯度提升是怎么回事?
答:
核心思想:每一棵新树拟合的是损失函数关于模型输出的负梯度(即残差的近似)。
$$F_m(x) = F_{m-1}(x) + \eta \cdot h_m(x)$$
其中 $h_m(x)$ 拟合 $-\frac{\partial L(y, F(x))}{\partial F(x)}\bigg|{F=F{m-1}}$
# 手写 GBDT(回归,MSE 损失)
class SimpleGBDT:
def __init__(self, n_estimators=100, lr=0.1, max_depth=3):
self.n_estimators = n_estimators
self.lr = lr
self.max_depth = max_depth
def fit(self, X, y):
from sklearn.tree import DecisionTreeRegressor
self.trees = []
# 初始预测:均值
self.init_pred = np.mean(y)
F = np.full(len(y), self.init_pred)
for _ in range(self.n_estimators):
# MSE 损失的负梯度 = 残差
residual = y - F
tree = DecisionTreeRegressor(max_depth=self.max_depth)
tree.fit(X, residual)
F += self.lr * tree.predict(X)
self.trees.append(tree)
def predict(self, X):
F = np.full(X.shape[0], self.init_pred)
for tree in self.trees:
F += self.lr * tree.predict(X)
return F关键理解:对于 MSE 损失,负梯度恰好等于残差 $y - F(x)$;对于其他损失(如 Huber 损失),负梯度是残差的鲁棒近似。
10. ⭐⭐⭐ Q: XGBoost vs LightGBM vs CatBoost 的核心区别?
答:
| 特性 | XGBoost | LightGBM | CatBoost |
|---|---|---|---|
| 树生长 | level-wise(按层) | leaf-wise(按叶) | 对称树 |
| 分裂算法 | pre-sorted / histogram | histogram(更快) | histogram |
| 类别特征 | 需手动编码 | 直接支持 | 原生支持(Ordered TS) |
| 缺失值 | 自动处理 | 自动处理 | 自动处理 |
| GPU | 支持 | 支持 | 支持 |
| 正则化 | L1+L2+树复杂度 | L1+L2 | L2+树对称性约束 |
| 速度 | 快 | 最快 | 中等 |
| 过拟合风险 | 中 | 高(leaf-wise) | 低(有序TS) |
LightGBM 的 leaf-wise vs level-wise:
- level-wise:同一层所有叶子都分裂 → 平衡但可能浪费
- leaf-wise:选增益最大的叶子分裂 → 可能过深,需限制 max_depth
CatBoost 的 Ordered Target Statistics:
# 普通 Target Encoding(有标签泄漏)
# CatBoost:对每个样本,只用排序在它之前的样本来计算目标统计
# 避免了 target leakage,无需额外的 CV fold# 三者 API 对比
import xgboost as xgb
import lightgbm as lgb
from catboost import CatBoostClassifier
xgb_model = xgb.XGBClassifier(n_estimators=100, max_depth=6, learning_rate=0.1)
lgb_model = lgb.LGBMClassifier(n_estimators=100, max_depth=-1, learning_rate=0.1, num_leaves=31)
cat_model = CatBoostClassifier(iterations=100, depth=6, learning_rate=0.1, verbose=0)面试技巧:说出 LightGBM 的 GOSS(基于梯度的单边采样)和 EFB(互斥特征捆绑)两个加速技术加分。
三、支持向量机 SVM
11. ⭐⭐ Q: SVM 的最大间隔直觉是什么?
答:
核心思想:在所有能正确分类的超平面中,选择离最近样本最远的那一个。
$$\min_{w,b} \frac{1}{2}|w|^2 \quad \text{s.t.} \quad y_i(w^Tx_i + b) \geq 1$$
为什么最大化间隔? 间隔越大,对噪声的容忍度越高 → 泛化能力越强。
几何间隔:$\gamma = \frac{2}{|w|}$,所以最小化 $|w|^2$ 等价于最大化间隔。
类比:想象在马路中间画线,要让两侧的电线杆离线越远越好——这条线最"安全"。
12. ⭐⭐⭐ Q: 软间隔 SVM 是什么?对偶问题怎么来的?
答:
硬间隔要求完全线性可分,现实中不可能。软间隔引入松弛变量:
$$\min_{w,b,\xi} \frac{1}{2}|w|^2 + C\sum_{i=1}^n \xi_i$$ $$\text{s.t.} \quad y_i(w^Tx_i + b) \geq 1 - \xi_i, \quad \xi_i \geq 0$$
C 的含义:
- C 大 → 对误分类惩罚大 → 间隔小,可能过拟合
- C 小 → 容忍更多误分类 → 间隔大,可能欠拟合
对偶问题推导:引入拉格朗日乘子 $\alpha_i$:
$$L = \frac{1}{2}|w|^2 + C\sum\xi_i - \sum\alpha_i[y_i(w^Tx_i+b)-(1-\xi_i)] - \sum\mu_i\xi_i$$
对 w, b, ξ 求导并代入,得到对偶问题:
$$\max_\alpha \sum\alpha_i - \frac{1}{2}\sum_{i,j}\alpha_i\alpha_j y_iy_j x_i^Tx_j$$ $$\text{s.t.} \quad 0 \leq \alpha_i \leq C$$
对偶的好处:只涉及 $x_i^Tx_j$(内积),可以替换为核函数 $K(x_i, x_j)$。
13. ⭐⭐ Q: 核函数的作用是什么?常见的有哪些?
答:
核心思想:将数据映射到高维空间使其线性可分,但不需要显式计算映射,只需计算内积。
$$K(x_i, x_j) = \phi(x_i)^T \phi(x_j)$$
| 核函数 | 公式 | 特点 |
|---|---|---|
| 线性核 | $x^Tx'$ | 不升维,线性可分时用 |
| 多项式核 | $(x^Tx' + c)^d$ | 有限维映射 |
| RBF 高斯核 | $\exp(-\gamma|x-x'|^2)$ | 无限维映射,最常用 |
| Sigmoid 核 | $\tanh(\gamma x^Tx' + c)$ | 类似神经网络 |
from sklearn.svm import SVC
# RBF 核(默认)
svm_rbf = SVC(kernel='rbf', C=1.0, gamma='scale')
# 线性核(高维数据,如文本)
svm_linear = SVC(kernel='linear', C=1.0)
# gamma 的含义:单个样本的影响范围
# gamma 大 → 影响范围小 → 决策边界复杂 → 过拟合
# gamma 小 → 影响范围大 → 决策边界平滑 → 欠拟合14. ⭐⭐ Q: SVM vs 逻辑回归,什么时候选哪个?
答:
| 维度 | SVM | 逻辑回归 |
|---|---|---|
| 损失 | Hinge Loss | 交叉熵 |
| 输出 | 决策边界 + 支持向量 | 概率 |
| 核技巧 | ✅ | ❌(需手动特征映射) |
| 样本量 | 小样本表现好 | 大样本更稳定 |
| 稀疏性 | 只依赖支持向量 | 使用所有样本 |
| 可解释性 | 一般 | 系数可解释 |
经验法则:
- 特征数 >> 样本数 → LR 或 Linear SVM
- 样本少 + 非线性 → RBF SVM
- 需要概率输出 → LR(或 SVM + Platt Scaling)
- 大规模数据 → LR(SVM 训练太慢)
四、降维与聚类
15. ⭐⭐ Q: PCA 的数学原理是什么?
答:
目标:找到数据方差最大的方向(主成分),将 d 维数据投影到 k 维。
步骤:
- 数据中心化:$X = X - \bar{X}$
- 计算协方差矩阵:$\Sigma = \frac{1}{n-1}X^TX$
- 特征值分解:$\Sigma = V\Lambda V^T$
- 取前 k 个特征向量:$W = [v_1, v_2, ..., v_k]$
- 投影:$Z = XW$
信息保留率:$\frac{\sum_{i=1}^k \lambda_i}{\sum_{i=1}^d \lambda_i}$
from sklearn.decomposition import PCA
pca = PCA(n_components=0.95) # 保留 95% 方差
X_reduced = pca.fit_transform(X)
print(f"从 {X.shape[1]} 维降到 {X_reduced.shape[1]} 维")
print(f"各主成分方差占比: {pca.explained_variance_ratio_}")追问:PCA 假设数据是线性结构,非线性数据用 Kernel PCA 或 t-SNE/UMAP。
16. ⭐⭐⭐ Q: SVD 和 PCA 的关系是什么?
答:
PCA 通过协方差矩阵的特征值分解实现,但实际计算中常用 SVD(奇异值分解):
$$X = U\Sigma V^T$$
- $U$:左奇异向量(样本在主成分空间的坐标)
- $\Sigma$:奇异值($\sigma_i^2 / (n-1) = \lambda_i$)
- $V$:右奇异向量(主成分方向)
直接对 X 做 SVD 即可得到 PCA 结果,无需显式计算协方差矩阵 $X^TX$(数值更稳定)。
import numpy as np
# 手动 PCA via SVD
X_centered = X - X.mean(axis=0)
U, S, Vt = np.linalg.svd(X_centered, full_matrices=False)
X_pca = U * S # 等价于 X_centered @ Vt.T
# sklearn 内部就是用 SVD 实现的 PCA17. ⭐ Q: K-means 的流程和局限性?
答:
流程:
- 随机初始化 k 个质心
- 分配:每个样本归入最近质心的簇
- 更新:重新计算每个簇的质心
- 重复 2-3 直到收敛
from sklearn.cluster import KMeans
kmeans = KMeans(n_clusters=3, init='k-means++', n_init=10, random_state=42)
labels = kmeans.fit_predict(X)局限性:
- 需要预设 k
- 对初始质心敏感 → K-means++ 解决
- 只能发现球形簇(凸形),非球形不行
- 对离群点敏感
- 只适用于数值特征
18. ⭐⭐ Q: K-means++ 怎么改进初始化?
答:
原始 K-means:随机选 k 个点 → 可能两个初始质心很近 → 收敛到局部最优
K-means++ 初始化:
- 随机选第一个质心
- 对每个样本,计算它到最近质心的距离 $D(x)$
- 以概率 $\frac{D(x)^2}{\sum D(x')^2}$ 选择下一个质心(越远越可能被选中)
- 重复 2-3 直到选够 k 个
直觉:初始质心尽量分散开。
19. ⭐⭐ Q: DBSCAN 相比 K-means 有什么优势?
答:
| 维度 | K-means | DBSCAN |
|---|---|---|
| 簇形状 | 球形 | 任意形状 |
| 需要 k | 是 | 不需要 |
| 噪声处理 | 差 | 好(自动识别噪声点) |
| 参数 | k | ε(邻域半径), MinPts(最小点数) |
核心概念:
- 核心点:ε 邻域内至少有 MinPts 个点
- 边界点:不是核心点,但在某核心点的 ε 邻域内
- 噪声点:既不是核心点也不是边界点
from sklearn.cluster import DBSCAN
dbscan = DBSCAN(eps=0.5, min_samples=5)
labels = dbscan.fit_predict(X)
# labels=-1 表示噪声点20. ⭐⭐ Q: 如何选择最优的聚类数 k?
答:
① 肘部法则(Elbow Method):画 k vs inertia(簇内平方和),找"拐点"
② 轮廓系数(Silhouette Score):
$$s(i) = \frac{b(i) - a(i)}{\max(a(i), b(i))}$$
- $a(i)$:样本 i 到同簇其他点的平均距离(内聚度)
- $b(i)$:样本 i 到最近异簇所有点的平均距离(分离度)
- $s \in [-1, 1]$,越大越好
from sklearn.metrics import silhouette_score
scores = []
for k in range(2, 10):
km = KMeans(n_clusters=k, random_state=42)
labels = km.fit_predict(X)
scores.append(silhouette_score(X, labels))
best_k = range(2, 10)[np.argmax(scores)]五、偏差-方差权衡与模型选择
21. ⭐⭐ Q: 什么是偏差-方差分解?
答:
$$E[(y - \hat{f}(x))^2] = \text{Bias}^2 + \text{Variance} + \text{Noise}$$
- 偏差(Bias):模型预测的期望值与真实值的差距 → 欠拟合
- 方差(Variance):模型对不同训练集的敏感程度 → 过拟合
- 噪声(Irreducible Error):数据本身的随机性,无法消除
# 直观理解
# 高偏差低方差:每次射箭都偏左(系统性偏差,但很集中)
# 低偏差高方差:平均在靶心,但散布很广
# 高偏差高方差:又偏又散
# 低偏差低方差:理想状态,集中靶心| 模型复杂度 | 偏差 | 方差 | 风险 |
|---|---|---|---|
| 低(线性模型) | 高 | 低 | 欠拟合 |
| 高(深度决策树) | 低 | 高 | 过拟合 |
| 集成(RF/GBDT) | 低 | 低 | 最优 |
22. ⭐⭐ Q: 如何诊断过拟合和欠拟合?
答:
from sklearn.model_selection import learning_curve
import matplotlib.pyplot as plt
train_sizes, train_scores, val_scores = learning_curve(
estimator, X, y, cv=5, train_sizes=np.linspace(0.1, 1.0, 10)
)
# 欠拟合:两条线都在低处,且差距小
# 过拟合:训练分很高,验证分很低,差距大
# 理想:两条线收敛到较高的值,差距小诊断清单:
| 症状 | 训练误差 | 验证误差 | 原因 | 对策 |
|---|---|---|---|---|
| 欠拟合 | 高 | 高 | 模型太简单 | 增加特征/复杂度 |
| 过拟合 | 低 | 高 | 模型太复杂 | 正则化/更多数据/简化 |
23. ⭐ Q: K 折交叉验证怎么实现?为什么比留一法好?
答:
from sklearn.model_selection import cross_val_score, KFold
kf = KFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(model, X, y, cv=kf, scoring='accuracy')
print(f"Mean: {scores.mean():.4f}, Std: {scores.std():.4f}")K 折 vs 留一法(LOO):
- LOO:n 折,每次留 1 个样本 → 无偏但方差大,计算量巨大
- 5/10 折:偏差和方差的平衡点
追问:分类任务用 分层 K 折(Stratified K-Fold),保证每折中各类别比例一致。
六、正则化
24. ⭐⭐⭐ Q: L1 正则化为什么能产生稀疏解?从数学和几何两个角度解释。
答:
数学角度:
$$L_{L1} = L_{original} + \lambda\sum|w_i|$$
L1 在 $w_i=0$ 处不可导,次梯度为 $[-1, 1]$。当 $\lambda$ 足够大时,最优解落在 $w_i=0$ 处。
几何角度(关键!面试加分):
L1 约束 $|w_1| + |w_2| \leq c$ 在二维空间是菱形,L2 约束 $w_1^2 + w_2^2 \leq c$ 是圆形。
损失函数的等高线是椭圆。菱形有角点(尖角),椭圆更容易与角点相切 → $w_1$ 或 $w_2$ 为零。圆形没有角点 → 切点几乎不可能在轴上 → 不产生稀疏。
import numpy as np
# L1 稀疏演示
from sklearn.linear_model import Lasso, Ridge
# L1 (Lasso) 产生稀疏系数
lasso = Lasso(alpha=0.1).fit(X, y)
print(f"Lasso 非零系数: {np.sum(lasso.coef_ != 0)}/{len(lasso.coef_)}")
# L2 (Ridge) 不稀疏
ridge = Ridge(alpha=0.1).fit(X, y)
print(f"Ridge 非零系数: {np.sum(ridge.coef_ != 0)}/{len(ridge.coef_)}")25. ⭐⭐ Q: L2 正则化的贝叶斯视角是什么?
答:
频率学派:正则化是约束优化
贝叶斯学派:正则化 = 引入参数的先验分布
| 正则化 | 等价先验 | 公式 |
|---|---|---|
| L1 (Lasso) | Laplace 先验 | $p(w) \propto e^{-\lambda|w|}$ |
| L2 (Ridge) | 高斯先验 | $p(w) \propto e^{-\lambda|w|^2}$ |
MAP 估计 = 最大后验 = 最大似然 + 先验 = 最小化损失 + 正则化
$$\hat{w}_{MAP} = \arg\max_w P(w|D) = \arg\max_w [P(D|w)P(w)]$$
$$= \arg\min_w [-\log P(D|w) - \log P(w)]$$
当 $P(w) = N(0, \frac{1}{\lambda}I)$ 时,$-\log P(w) = \frac{\lambda}{2}|w|^2$ → L2 正则化。
26. ⭐⭐ Q: 弹性网络(Elastic Net)是什么?什么时候用?
答:
$$L = L_{original} + \lambda_1\sum|w_i| + \lambda_2\sum w_i^2$$
等价于 L1 + L2 的组合,用 l1_ratio 控制比例:
from sklearn.linear_model import ElasticNet
# l1_ratio=1 纯 L1, l1_ratio=0 纯 L2
en = ElasticNet(alpha=0.1, l1_ratio=0.5)什么时候用 Elastic Net?
- 特征间有共线性:Lasso 会随机选一个,Elastic Net 会一起选
- 特征数远大于样本数
- 想要稀疏但 Lasso 太激进
七、特征工程
27. ⭐ Q: 缺失值怎么处理?
答:
| 方法 | 适用场景 | 代码 |
|---|---|---|
| 删除行 | 缺失比例小(<5%) | dropna() |
| 均值/中位数填充 | 数值特征,MCAR | SimpleImputer(strategy='median') |
| 众数填充 | 类别特征 | SimpleImputer(strategy='most_frequent') |
| KNN 填充 | 特征间有关联 | KNNImputer(n_neighbors=5) |
| 模型预测填充 | 缺失有规律 | 用其他特征训练模型预测 |
| 标记缺失 | 缺失本身有意义 | 增加 is_null 列 |
from sklearn.impute import SimpleImputer, KNNImputer
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer # MICE 算法
# 基本填充
imp = SimpleImputer(strategy='median')
X_filled = imp.fit_transform(X)
# 高级填充(多重插补)
imp_iter = IterativeImputer(max_iter=10, random_state=42)
X_filled = imp_iter.fit_transform(X)面试技巧:先判断缺失机制(MCAR/MAR/MNAR),再选方法。
28. ⭐ Q: 标准化和归一化有什么区别?
答:
| 方法 | 公式 | 输出范围 | 适用场景 |
|---|---|---|---|
| 标准化(Z-score) | $\frac{x-\mu}{\sigma}$ | 均值0,方差1 | 大多数ML模型 |
| 归一化(Min-Max) | $\frac{x-x_{min}}{x_{max}-x_{min}}$ | [0, 1] | 神经网络、距离模型 |
from sklearn.preprocessing import StandardScaler, MinMaxScaler
scaler = StandardScaler() # 推荐,对异常值鲁棒
X_std = scaler.fit_transform(X)
mms = MinMaxScaler() # 对异常值敏感
X_norm = mms.fit_transform(X)什么时候必须标准化? SVM、KNN、PCA、梯度下降训练的模型。树模型不需要。
29. ⭐⭐ Q: 类别特征有哪些编码方式?
答:
import pandas as pd
from sklearn.preprocessing import LabelEncoder, OrdinalEncoder, OneHotEncoder
# 1. Label Encoding(有序类别)
le = LabelEncoder()
# 低→高:小学<初中<高中<大学
encoded = le.fit_transform(['小学', '高中', '初中', '大学'])
# 2. One-Hot Encoding(无序类别)
ohe = OneHotEncoder(sparse=False)
# 红、绿、蓝 → [1,0,0], [0,1,0], [0,0,1]
# 3. Target Encoding(高基数类别)
# 用该类别下目标变量的均值替代
# 例:城市→该城市的平均房价
# 4. Frequency Encoding
# 用类别出现频率替代
freq = df['city'].value_counts(normalize=True)
df['city_freq'] = df['city'].map(freq)| 编码 | 适用 | 问题 |
|---|---|---|
| Label | 有序类别 | 无序时引入虚假顺序 |
| One-Hot | 低基数(<10) | 高基数时维度爆炸 |
| Target | 高基数 | 有标签泄漏风险,需正则化 |
| Frequency | 高基数 | 丢失类别语义 |
30. ⭐⭐ Q: 特征选择有哪些方法?
答:
三类方法:
① 过滤法(Filter):独立于模型,计算快
from sklearn.feature_selection import SelectKBest, mutual_info_classif
# 方差过滤
from sklearn.feature_selection import VarianceThreshold
sel = VarianceThreshold(threshold=0.01)
# 互信息
selector = SelectKBest(mutual_info_classif, k=10)
X_new = selector.fit_transform(X, y)② 包装法(Wrapper):效果好但慢
from sklearn.feature_selection import RFE
from sklearn.ensemble import RandomForestClassifier
rfe = RFE(RandomForestClassifier(), n_features_to_select=10)
X_new = rfe.fit_transform(X, y)③ 嵌入法(Embedded):模型自带特征重要性
from sklearn.feature_selection import SelectFromModel
sfm = SelectFromModel(RandomForestClassifier(), threshold='median')
X_new = sfm.fit_transform(X, y)八、评估指标体系
31. ⭐ Q: 混淆矩阵和基本指标怎么算?
答:
预测正 预测负
实际正 TP FN
实际负 FP TN| 指标 | 公式 | 含义 |
|---|---|---|
| 精确率 P | $\frac{TP}{TP+FP}$ | 预测为正的里面,真正的比例 |
| 召回率 R | $\frac{TP}{TP+FN}$ | 实际为正的里面,被找到的比例 |
| F1 | $\frac{2PR}{P+R}$ | P 和 R 的调和平均 |
| 准确率 | $\frac{TP+TN}{N}$ | 整体正确率 |
类比:
- 精确率:「宁缺毋滥」—— 搜索结果尽量准确
- 召回率:「宁多勿少」—— 不漏掉任何一个相关结果
- F1:精确率和召回率的平衡
32. ⭐⭐ Q: P-R 曲线和 ROC 曲线有什么区别?什么时候用哪个?
答:
P-R 曲线:以召回率为横轴,精确率为纵轴
ROC 曲线:以 FPR(假正率)为横轴,TPR(真正率=召回率)为纵轴
$$FPR = \frac{FP}{FP + TN}, \quad TPR = \frac{TP}{TP + FN}$$
AUC:ROC 曲线下的面积,越大越好(0.5 = 随机,1.0 = 完美)
关键区别:
- 正负样本均衡 → 用 AUC-ROC
- 正负样本不均衡 → 用 P-R 曲线和 AUC-PR(ROC 在不均衡时会过于乐观)
from sklearn.metrics import (
precision_recall_curve, roc_curve, auc,
average_precision_score, roc_auc_score
)
# ROC
fpr, tpr, _ = roc_curve(y_true, y_score)
roc_auc = auc(fpr, tpr)
# P-R
precision, recall, _ = precision_recall_curve(y_true, y_score)
ap = average_precision_score(y_true, y_score)为什么 ROC 在不均衡时乐观? 当负样本远多于正样本时,FP 的增量对 FPR 影响很小(分母大),但对精确率影响大。
33. ⭐⭐ Q: 多分类指标怎么算?宏平均 vs 微平均 vs 加权平均?
答:
| 平均方式 | 公式 | 特点 |
|---|---|---|
| 宏平均 Macro | 各类指标的简单平均 | 每个类别权重相同,受小类影响大 |
| 微平均 Micro | 全局 TP/FP/FN 计算 | 受大类主导,≈ 准确率 |
| 加权平均 Weighted | 按类别样本数加权 | 考虑类别不平衡 |
from sklearn.metrics import classification_report
print(classification_report(y_true, y_pred, target_names=['cat','dog','bird']))
# precision recall f1-score support
# cat 0.90 0.85 0.87 100
# dog 0.80 0.90 0.85 80
# bird 0.95 0.80 0.87 60
# accuracy 0.85 240
# macro avg 0.88 0.85 0.86 240
# weighted avg 0.88 0.85 0.86 24034. ⭐⭐⭐ Q: 不均衡数据怎么处理?
答:
数据层面:
# 欠采样
from imblearn.under_sampling import RandomUnderSampler
rus = RandomUnderSampler(sampling_strategy=0.5)
# 过采样
from imblearn.over_sampling import SMOTE
smote = SMOTE(sampling_strategy=0.5)
# 组合
from imblearn.combine import SMOTETomek
smt = SMOTETomek()算法层面:
- 调 class_weight:
LogisticRegression(class_weight='balanced') - 调阈值:降低正类阈值(如 0.3)提高召回率
- 使用 F1/AUC-PR 作为优化目标
经验:
- 比例 1:10 → class_weight 即可
- 比例 1:100 → SMOTE + class_weight
- 比例 1:1000+ → 考虑异常检测方法
九、经典 ML 面试高频题汇总
35. ⭐⭐ Q: 【高频 #1】为什么树模型不需要特征标准化?
答:决策树的分裂依据是特征值的排序和阈值,不是数值大小。分裂条件 x < threshold 是序关系,不受缩放影响。而 SVM/KNN/LR 基于距离或梯度,必须标准化。
36. ⭐⭐ Q: 【高频 #2】梯度下降和牛顿法的区别?
答:
| 维度 | 梯度下降 | 牛顿法 |
|---|---|---|
| 信息 | 一阶导数 | 一阶 + 二阶导数(Hessian) |
| 更新 | $w - \eta\nabla L$ | $w - H^{-1}\nabla L$ |
| 收敛 | 线性 | 二阶(更快) |
| 每步成本 | 低 | 高(需计算 Hessian 逆) |
| 适用 | 大规模数据 | 小规模、光滑优化 |
拟牛顿法(L-BFGS):近似 Hessian 逆,兼顾速度和效率,scipy.optimize 默认方法。
37. ⭐⭐ Q: 【高频 #3】特征相关性高(共线性)有什么影响?怎么解决?
答:
影响:
- 线性回归系数不稳定(方差大),符号可能反转
- 模型可解释性下降
- 树模型/集成模型不受影响
解决:
- VIF(方差膨胀因子)检测:VIF > 10 有共线性
- PCA 降维
- L1 正则化(自动选一个)
- 删除相关特征
38. ⭐⭐ Q: 【高频 #4】什么是模型的泛化能力?如何提升?
答:
泛化能力 = 模型在未见过的数据上的表现。
提升方法:
- 更多数据(最有效)
- 正则化(L1/L2/Dropout)
- 交叉验证选模型
- 特征工程(去噪、降维)
- 集成学习(Bagging 降方差、Boosting 降偏差)
- 早停(Early Stopping)
39. ⭐⭐⭐ Q: 【高频 #5】XGBoost 的二阶泰勒展开有什么好处?
答:
XGBoost 的损失函数用二阶泰勒展开:
$$L^{(t)} \approx \sum_{i=1}^n [g_i f_t(x_i) + \frac{1}{2}h_i f_t^2(x_i)] + \Omega(f_t)$$
其中 $g_i = \partial_{\hat{y}i} L$,$h_i = \partial^2{\hat{y}_i} L$(一阶和二阶梯度)。
好处:
- 通用性:只要损失函数二阶可导,就能用同一套框架(自定义损失)
- 精度:比一阶梯度下降更准
- 效率:Hessian 对角近似,计算量可控
- 正则化:自然引出树复杂度的正则项
40. ⭐⭐ Q: 【高频 #6】如何处理高基数类别特征?
答:
高基数(如用户 ID、城市名有上千个不同值):
- Target Encoding(最常用)+ 正则化(加噪声或贝叶斯平滑)
- Frequency Encoding:用频率替代
- Embedding:学习低维向量表示
- Hash Encoding:哈希到固定维度(有冲突但实用)
- 聚合:将低频类别合并为"其他"
# Target Encoding 实现
def target_encode(train, col, target, smoothing=10):
global_mean = train[target].mean()
stats = train.groupby(col)[target].agg(['mean', 'count'])
smooth = (stats['count'] * stats['mean'] + smoothing * global_mean) / (stats['count'] + smoothing)
return train[col].map(smooth)41. ⭐⭐⭐ Q: 【高频 #7】随机森林的特征重要性怎么计算?
答:
① 基于不纯度(Gini Importance):每棵树中,特征用于分裂时带来的不纯度减少量的加权平均。缺点:偏向高基数特征。
② 基于排列(Permutation Importance):打乱某特征的值,观察模型性能下降多少。更可靠。
from sklearn.inspection import permutation_importance
# 不纯度重要性(内置)
rf = RandomForestClassifier().fit(X, y)
imp1 = rf.feature_importances_
# 排列重要性(推荐)
result = permutation_importance(rf, X_val, y_val, n_repeats=10)
imp2 = result.importances_mean42. ⭐⭐⭐ Q: 【高频 #8】模型上线后效果下降,排查思路是什么?
答:
- 数据分布漂移(Data Drift):特征分布变了 → 检测 PSI/KS
- 概念漂移(Concept Drift):特征和标签的关系变了
- 数据质量问题:缺失值增多、编码错误、特征缺失
- 线上/线下特征不一致:特征穿越(Label Leakage)、时间窗口不对
- 评估指标选择不当:线上指标和线下指标不一致
# PSI 检测分布漂移
def psi(expected, actual, buckets=10):
breakpoints = np.percentile(expected, np.linspace(0, 100, buckets + 1))
expected_pct = np.histogram(expected, breakpoints)[0] / len(expected)
actual_pct = np.histogram(actual, breakpoints)[0] / len(actual)
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 稳定, 0.1-0.25 需关注, > 0.25 显著漂移43. ⭐⭐ Q: 【高频 #9】如何用 sklearn Pipeline 构建完整的 ML 工作流?
答:
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.ensemble import GradientBoostingClassifier
# 定义列类型
numeric_features = ['age', 'income']
categorical_features = ['city', 'gender']
# 分别处理数值和类别特征
numeric_transformer = Pipeline([
('imputer', SimpleImputer(strategy='median')),
('scaler', StandardScaler())
])
categorical_transformer = Pipeline([
('imputer', SimpleImputer(strategy='most_frequent')),
('encoder', OneHotEncoder(handle_unknown='ignore'))
])
# 组合
preprocessor = ColumnTransformer([
('num', numeric_transformer, numeric_features),
('cat', categorical_transformer, categorical_features)
])
# 完整 Pipeline
full_pipeline = Pipeline([
('preprocessor', preprocessor),
('classifier', GradientBoostingClassifier())
])
# 训练和预测
full_pipeline.fit(X_train, y_train)
score = full_pipeline.score(X_test, y_test)
# 防止数据泄漏!fit 只在训练集上调用面试加分点:Pipeline 保证了数据处理的一致性,防止训练/推理时的特征工程不一致。
44. ⭐⭐⭐ Q: 【高频 #10】模型可解释性有哪些方法?
答:
| 方法 | 类型 | 适用模型 |
|---|---|---|
| 特征重要性 | 全局 | 树模型 |
| SHAP | 全局+局部 | 任意模型 |
| LIME | 局部 | 任意模型 |
| Partial Dependence | 全局 | 任意模型 |
| 系数分析 | 全局 | 线性模型 |
| 决策树可视化 | 全局 | 决策树 |
import shap
# SHAP 值
explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(X_test)
# 全局重要性
shap.summary_plot(shap_values, X_test)
# 单个样本解释
shap.force_plot(explainer.expected_value, shap_values[0], X_test.iloc[0])面试技巧:SHAP 值基于博弈论的 Shapley 值,有坚实的理论基础(可加性、对称性、零贡献性),是目前最被认可的可解释性工具。
总结:经典 ML 面试核心在于能从数学推导、直觉解释、代码实现三个层面回答问题。重点掌握:逻辑回归推导、决策树原理、集成学习对比、SVM 核技巧、PCA/SVD、正则化(尤其是 L1 稀疏性的几何解释)、评估指标选择(不均衡场景)、特征工程实践。