02 - 监督学习算法¶
📊 回归算法¶
线性回归 (Linear Regression)¶
核心思想¶
用一条直线/平面拟合数据点
简单线性回归 (1个特征):
y = wx + b
w: 斜率 (权重)
b: 截距 (偏置)
多元线性回归 (多个特征):
y = w₁x₁ + w₂x₂ + ... + wₙxₙ + b
直观理解¶
房价预测:
房屋面积 (x) → 房价 (y)
训练数据:
面积: 50m² → 150万
面积: 80m² → 220万
面积: 120m² → 320万
学习结果:
房价 = 2.5 × 面积 + 25万
预测:
90m² 房子 → 2.5 × 90 + 25 = 250万
数学原理¶
目标: 找到最优参数 w,b 使预测误差最小
损失函数 (MSE - 均方误差): $\(J(w,b) = \frac{1}{2n}\sum_{i=1}^{n}(y_i - (wx_i + b))^2\)$
注:分母中的 \(\frac{1}{2}\) 是为了求导后消去平方项的系数2,简化梯度表达式。
优化方法: 1. 最小二乘法 (解析解): $\(w = (X^TX)^{-1}X^Ty\)$ - 优点: 数学最优解 - 缺点: 计算量大,不适合大数据
- 梯度下降 (迭代优化): Text Only
初始化 w = 0 重复直到收敛: 计算梯度: ∇J = ∂J/∂w 更新参数: w = w - α∇J (α: 学习率) ``` - 优点: 适合大规模数据 - 缺点: 需要调学习率,对非凸问题可能陷入局部最优(线性回归的MSE是凸函数,无此问题) #### 代码实现 ```python import numpy as np # 从零实现 class LinearRegression: def __init__(self, lr=0.01, n_iters=1000): # __init__构造方法,创建对象时自动调用 self.lr = lr self.n_iters = n_iters self.weights = None self.bias = None def fit(self, X, y): n_samples, n_features = X.shape self.weights = np.zeros(n_features) self.bias = 0 # 梯度下降 for _ in range(self.n_iters): y_pred = np.dot(X, self.weights) + self.bias # np.dot计算点积/矩阵乘法 # 计算梯度 dw = (1/n_samples) * np.dot(X.T, (y_pred - y)) db = (1/n_samples) * np.sum(y_pred - y) # 更新参数 self.weights -= self.lr * dw self.bias -= self.lr * db def predict(self, X): return np.dot(X, self.weights) + self.bias
正则化 (防止过拟合)¶
Ridge回归 (L2正则化): $\(J = \frac{1}{n}\sum(y_i - \hat{y}_i)^2 + \lambda\sum w_j^2\)$ - 特点: 权重趋向于小值,但不为0 - 应用: 特征间有相关性
Lasso回归 (L1正则化): $\(J = \frac{1}{n}\sum(y_i - \hat{y}_i)^2 + \lambda\sum |w_j|\)$ - 特点: 可使部分权重为0 (特征选择) - 应用: 需要筛选重要特征
L1 为何产生稀疏?
几何直觉: L1 约束区域是菱形(各轴截距为 \(C\)),等高线(椭圆)与菱形的交点大概率在坐标轴顶点上,此时某些 \(w_j = 0\);而 L2 约束是圆形,交点一般不在轴上。
次梯度条件(数学证明): \(|w_j|\) 在 \(w_j = 0\) 处不可导,其次梯度为 \(\partial |w_j| = [-1, 1]\)。最优性条件要求:
\[0 \in \frac{\partial J}{\partial w_j} = -\frac{2}{n}\sum_i x_{ij}(y_i - \hat{y}_i) + \lambda \cdot \partial|w_j|\]令 \(g_j = \frac{2}{n}\sum_i x_{ij}(y_i - \hat{y}_i)\big|_{w_j=0}\) 为不含 \(w_j\) 时的梯度分量。当 \(|g_j| \leq \lambda\) 时,存在 \(s \in [-1,1]\) 使上式成立,因此 \(w_j^* = 0\)。即 \(\lambda\) 越大,越多特征的梯度被"压制"为零。
逻辑回归 (Logistic Regression)¶
核心思想¶
用Sigmoid函数将线性输出映射到0-1概率
线性回归输出: z = wx + b (范围: -∞ 到 +∞)
Sigmoid函数: σ(z) = 1/(1 + e^(-z)) (范围: 0 到 1)
预测概率: P(y=1|x) = σ(wx + b)
决策边界: P > 0.5 → 预测为1
P < 0.5 → 预测为0
Sigmoid函数可视化¶
1.0 ─────────────────
| ____σ(z)___
0.5 | ___
| ____
| __
0.0 ─────────────────
-∞ 0 +∞
当 z = 0 时, σ(z) = 0.5
当 z > 0 时, σ(z) > 0.5 (预测为1)
当 z < 0 时, σ(z) < 0.5 (预测为0)
损失函数 (对数损失)¶
直观理解: - 如果真实标签y=1,预测越接近1损失越小 - 如果真实标签y=0,预测越接近0损失越小
梯度推导¶
记 \(\hat{y}_i = \sigma(z_i)\),其中 \(z_i = w^T x_i + b\),\(\sigma(z) = \frac{1}{1+e^{-z}}\)。
Sigmoid的导数有一个优美的性质: $\(\sigma'(z) = \sigma(z)(1 - \sigma(z))\)$
对 \(w\) 求导(完整推导):
由链式法则 \(\frac{\partial \hat{y}_i}{\partial w} = \hat{y}_i(1-\hat{y}_i) x_i\),代入:
向量化形式: $\(\frac{\partial J}{\partial w} = \frac{1}{n}X^T(\hat{y} - y)\)$
同理,\(\frac{\partial J}{\partial b} = \frac{1}{n}\sum_{i=1}^{n}(\hat{y}_i - y_i)\)
注:逻辑回归的梯度形式与线性回归完全一致(\(\frac{1}{n}X^T(\hat{y}-y)\)),但 \(\hat{y}\) 的含义不同——逻辑回归中 \(\hat{y} = \sigma(Xw+b)\) 是非线性的。
代码实现¶
class LogisticRegression:
def __init__(self, lr=0.01, n_iters=1000):
self.lr = lr
self.n_iters = n_iters
self.weights = None
self.bias = None
def _sigmoid(self, z):
return 1 / (1 + np.exp(-z))
def fit(self, X, y):
n_samples, n_features = X.shape
self.weights = np.zeros(n_features)
self.bias = 0
for _ in range(self.n_iters):
# 线性组合
z = np.dot(X, self.weights) + self.bias
# Sigmoid激活
y_pred = self._sigmoid(z)
# 计算梯度
dw = (1/n_samples) * np.dot(X.T, (y_pred - y))
db = (1/n_samples) * np.sum(y_pred - y)
# 更新参数
self.weights -= self.lr * dw
self.bias -= self.lr * db
def predict(self, X):
z = np.dot(X, self.weights) + self.bias
y_pred = self._sigmoid(z)
return (y_pred > 0.5).astype(int)
🌳 决策树与集成算法¶
决策树 (Decision Tree)¶
核心思想¶
通过一系列是/否问题进行分类
判断是否打网球:
┌─────────────────────┐
│ 天气? │
├─────────────────────┤
│ 晴天 → 湿度? │
│ │ 高 → 不打 │
│ │ 正常 → 打 │
│ │
│ 雨天 → 风大? │
│ │ 是 → 不打 │
│ │ 否 → 打 │
│ │
│ 阴天 → 打 │
└─────────────────────┘
关键概念¶
信息增益 (Information Gain): - 衡量分裂前后的"不确定性"减少程度 - 基于熵 (Entropy): \(H = -\sum_{i=1}^{C} p_i \log_2 p_i\)(\(C\) 为类别数) - 熵越大,数据越混乱
信息增益率 (Gain Ratio): - 信息增益 / 分裂信息 - 解决信息增益偏向取值多的特征
基尼不纯度 (Gini Impurity): $\(G = 1 - \sum_{i=1}^{C} p_i^2\)$ - CART算法使用 - 计算更简单
优缺点¶
优点: - 可解释性强 (可视化树) - 不需要特征归一化 - 能处理非线性关系
缺点: - 容易过拟合 - 对小数据变化敏感 (不稳定) - 贪心算法,可能不是全局最优
随机森林 (Random Forest)¶
核心思想¶
构建多棵决策树,综合它们的预测结果
Bagging (Bootstrap Aggregating):
1. 有放回抽样,生成K个训练集
2. 每个训练集训练一棵决策树
3. 预测时综合所有树的投票 (分类) / 平均 (回归)
随机性:
1. 样本随机 (Bootstrap采样)
2. 特征随机 (每节点只考虑部分特征)
直观理解¶
分类问题: 判断水果是苹果还是橙子
树1: 看颜色 → 红色 → 预测苹果
树2: 看形状 → 圆形 → 预测橙子
树3: 看纹理 → 光滑 → 预测苹果
树4: 看味道 → 甜 → 预测苹果
树5: 看大小 → 大 → 预测橙子
投票结果: 苹果 3票, 橙子 2票 → 最终: 苹果
为什么随机森林更好?¶
降低方差: - 单棵树: 高方差 (数据变化大,结果变化大) - 随机森林: 平均后降低方差
减少相关性: - 特征随机性使树之间差异更大 - 不相关的树平均后效果更好
超参数¶
# Scikit-learn示例
from sklearn.ensemble import RandomForestClassifier
rf = RandomForestClassifier(
n_estimators=100, # 树的数量 (越多越好但越慢)
max_depth=10, # 最大深度 (防止过拟合)
min_samples_split=5, # 节点分裂最小样本数
max_features='sqrt', # 每次分裂考虑的特征数
random_state=42
)
优缺点¶
优点: - 准确率高,不易过拟合 - 能评估特征重要性 - 对特征缩放不敏感
缺点: - 模型大,预测慢 - 不如单棵树可解释
XGBoost (eXtreme Gradient Boosting)¶
核心思想¶
串行训练多个弱学习器,每个新模型纠正前一个模型的错误
Boosting vs Bagging:
Bagging (并行):
树1 ─┐
树2 ─┤→ 平均/投票
树3 ─┘
各树独立,减少方差
Boosting (串行):
树1 → 残差 → 树2 → 残差 → 树3 ...
后一棵树关注前一棵树的错误
减少偏差和方差
梯度提升原理¶
步骤: 1. 训练第1个模型,得到预测 \(\hat{y}_1\) 2. 计算残差: \(r_2 = y - \hat{y}_1\) 3. 训练第2个模型预测残差,得到 \(\hat{r}_2\) 4. 更新预测: \(\hat{y}_2 = \hat{y}_1 + \hat{r}_2\) 5. 重复直到达到迭代次数
梯度视角: - 每次新模型拟合损失函数的负梯度 - 类似梯度下降,但用模型代替参数更新
XGBoost vs 传统GBDT¶
XGBoost改进: 1. 二阶导数: 同时利用梯度和海森矩阵,更准确 2. 正则化: 添加L1/L2正则项,防止过拟合 3. 并行化: 特征维度并行,加速训练 4. 缺失值处理: 自动处理缺失数据 5. 剪枝策略: 预剪枝,更高效
代码示例¶
import xgboost as xgb
# 分类
clf = xgb.XGBClassifier(
max_depth=3, # 树深度
learning_rate=0.1, # 收缩率 (步长)
n_estimators=100, # 树的数量
objective='binary:logistic',
eval_metric='logloss',
reg_alpha=0.1, # L1正则
reg_lambda=1.0 # L2正则
)
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
# 特征重要性
import matplotlib.pyplot as plt
xgb.plot_importance(clf)
plt.show()
应用场景¶
- Kaggle竞赛首选算法
- 结构化数据 (表格数据)
- 推荐系统、风控、销售预测
📐 支持向量机 (SVM)¶
核心思想¶
找到最优超平面,使不同类别样本间隔最大
二维空间分类:
🔴🔴 🔵🔵🔵
🔴🔴🔴 🔵🔵
🔴🔴 ────决策边界──── 🔵🔵
🔴🔴 🔵🔵
🔵🔵
间隔 (Margin):
决策边界到最近样本的距离
SVM目标:
最大化这个间隔!
数学原理¶
超平面 (二维中是线): $\(w \cdot x + b = 0\)$
间隔: $\(\text{Margin} = \frac{2}{\|w\|}\)$
优化目标: $\(\min \frac{1}{2}\|w\|^2\)$ $\(s.t. \quad y_i(w \cdot x_i + b) \geq 1\)$
软间隔 (允许误分类): $\(\min \frac{1}{2}\|w\|^2 + C\sum_{i=1}^{n} \xi_i\)$ $\(s.t. \quad y_i(w \cdot x_i + b) \geq 1 - \xi_i, \quad \xi_i \geq 0\)$ - \(C\): 惩罚参数(控制间隔宽度与误分类之间的权衡) - \(\xi_i\): 松弛变量(允许的违反程度)
对偶形式推导¶
引入拉格朗日乘子 \(\alpha_i \geq 0\),构造拉格朗日函数(以硬间隔为例):
对 \(w\) 和 \(b\) 求偏导并令其为零:
将上述条件代入拉格朗日函数,逐步推导对偶问题:
首先将 \(w = \sum_i \alpha_i y_i x_i\) 代入 \(L\) 中的 \(\frac{1}{2}\|w\|^2\) 项:
再代入 \(L\) 中的 \(\sum_i \alpha_i y_i (w \cdot x_i + b)\) 项:
其中 \(\sum_i \alpha_i y_i w \cdot x_i = \sum_i \alpha_i y_i \left(\sum_j \alpha_j y_j x_j\right) \cdot x_i = \sum_i \sum_j \alpha_i \alpha_j y_i y_j (x_i \cdot x_j)\)。
因此:
最终得到对偶问题:
KKT 互补松弛条件: $\(\alpha_i [y_i(w \cdot x_i + b) - 1] = 0\)$
- 若 \(\alpha_i > 0\),则 \(y_i(w \cdot x_i + b) = 1\),即该样本为支持向量
- 若 \(\alpha_i = 0\),则该样本不影响决策边界
对偶形式的关键优势:(1) 目标函数只涉及样本间的内积 \(x_i \cdot x_j\),可用核函数替换实现非线性分类;(2) 约束更简单,便于用 SMO 等算法高效求解。
核技巧 (Kernel Trick)¶
问题: 如果数据不是线性可分怎么办?
解决: 将数据映射到高维空间
线性不可分:
🔴 🔵
🔴 🔵
🔴🔴
映射到高维后:
🔵🔵
🔴 🔵
🔴
常用核函数:
1. 线性核: K(x,y) = x·y
2. 多项式核: K(x,y) = (x·y + c)^d
3. RBF核 (高斯核): K(x,y) = exp(-γ‖x-y‖²)
- 最常用,能处理复杂边界
4. Sigmoid核: K(x,y) = tanh(ax·y + c)
Mercer 定理(核函数的合法性条件):一个对称函数 \(K(x, z)\) 是合法核函数,当且仅当对任意有限样本集 \(\{x_1, \ldots, x_m\}\),其核矩阵(Gram 矩阵)\(K_{ij} = K(x_i, x_j)\) 是半正定的(即所有特征值 \(\geq 0\))。
等价地:存在一个特征映射 \(\phi\) 使得 \(K(x, z) = \phi(x) \cdot \phi(z)\)。
实践意义:使用核函数前需确认其满足 Mercer 条件,否则对偶问题可能无解或不收敛。上述 4 种常用核函数均满足此条件(Sigmoid 核仅在特定参数范围内满足)。
代码示例¶
from sklearn.svm import SVC
# 线性可分数据
svm_linear = SVC(kernel='linear', C=1.0)
# 非线性数据 (RBF核)
svm_rbf = SVC(
kernel='rbf',
C=1.0, # 惩罚参数 (越大越不允许误分类)
gamma='scale', # 核系数 (越大决策边界越复杂)
probability=True # 输出概率
)
svm_rbf.fit(X_train, y_train)
y_pred = svm_rbf.predict(X_test)
优缺点¶
优点: - 在高维空间表现好 - 小数据集效果好 - 通过核函数处理非线性
缺点: - 大数据集训练慢 (O(n³)) - 对噪声敏感 - 参数调优困难
🎲 朴素贝叶斯 (Naive Bayes)¶
核心思想¶
基于贝叶斯定理,假设特征之间相互独立
贝叶斯定理回顾¶
应用到分类: $\(P(类别|特征) = \frac{P(特征|类别) \cdot P(类别)}{P(特征)}\)$
垃圾邮件分类示例:
问题: 邮件含"中奖"二字,是垃圾邮件的概率?
已知:
- P(垃圾邮件) = 50%
- P("中奖"|垃圾邮件) = 40%
- P("中奖"|正常邮件) = 1%
计算:
P(垃圾邮件|"中奖")
= P("中奖"|垃圾邮件) × P(垃圾邮件) / P("中奖")
≈ 0.4 × 0.5 / (0.4×0.5 + 0.01×0.5)
= 0.976 → 97.6%概率是垃圾邮件
"朴素"假设¶
假设: 特征之间相互独立
邮件特征: [中奖, 点击, 免费]
朴素假设: P(中奖,点击,免费|垃圾)
= P(中奖|垃圾) × P(点击|垃圾) × P(免费|垃圾)
实际: 特征可能相关 (中奖和免费常一起出现)
但朴素假设在实践中有 surprisingly good 效果!
三种常见变体¶
1. 高斯朴素贝叶斯: - 特征是连续值,假设服从高斯分布 - 应用: 人体特征分类
2. 多项式朴素贝叶斯: - 特征是计数 (词频) - 应用: 文本分类
3. 伯努利朴素贝叶斯: - 特征是二值 (出现/不出现) - 应用: 短文本分类
代码示例¶
from sklearn.naive_bayes import MultinomialNB
from sklearn.feature_extraction.text import CountVectorizer
# 文本分类
emails = [
"中奖免费点击",
"正常邮件内容",
"点击中奖免费"
]
labels = [1, 0, 1] # 1:垃圾邮件, 0:正常邮件
# 文本向量化
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(emails)
# 训练
nb = MultinomialNB()
nb.fit(X, labels)
# 预测
test_email = ["中奖点击"]
test_X = vectorizer.transform(test_email)
print(nb.predict(test_X)) # 输出: [1] (垃圾邮件)
优缺点¶
优点: - 训练/预测速度快 - 对小数据集效果好 - 易于实现
缺点: - 特征独立假设太强 (实际常不成立) - 对输入数据形式敏感
📊 算法选择指南¶
根据问题类型选择¶
回归问题 (预测连续值):
- 线性关系 → 线性回归 / Ridge / Lasso
- 非线性关系 → 随机森林 / XGBoost
- 时序数据 → ARIMA / LSTM
分类问题 (预测类别):
- 二分类:
- 简单快速 → 逻辑回归 / 朴素贝叶斯
- 高准确率 → XGBoost / 随机森林
- 小数据集 → SVM
- 文本分类 → 朴素贝叶斯
- 多分类:
- 表格数据 → XGBoost / 随机森林
- 图像数据 → CNN
根据数据特征选择¶
数据量:
- 小 (<1000条): SVM, 朴素贝叶斯, 逻辑回归
- 中 (1000-10万): 随机森林, XGBoost
- 大 (>10万): 神经网络, 线性模型
特征数:
- 少 (<10): 逻辑回归, SVM
- 中 (10-1000): 随机森林, XGBoost
- 多 (>1000): 先降维 (PCA) 或 特征选择
特征类型:
- 数值: 线性回归, SVM
- 类别: 朴素贝叶斯
- 混合: 随机森林, XGBoost
- 文本: 朴素贝叶斯, LSTM
- 图像: CNN
数据质量:
- 干净完整: 所有算法
- 缺失值多: XGBoost (自动处理), 随机森林
- 噪声多: 朴素贝叶斯, 鲁棒的集成方法
算法对比¶
| 算法 | 准确率 | 速度 | 可解释性 | 适用场景 |
|---|---|---|---|---|
| 线性回归 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 简单回归 |
| 逻辑回归 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | 基准模型 |
| 决策树 | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | 可视化规则 |
| 随机森林 | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | 表格数据 |
| XGBoost | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ | 竞赛/工业界 |
| SVM | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐ | 小数据集 |
| 朴素贝叶斯 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | 文本分类 |
🚀 实践建议¶
学习路径¶
- 理解原理: 先搞懂数学推导,而不是直接调包
- 从零实现: 用NumPy手写核心算法
- 对比实验: 同一数据集跑多个算法,对比效果
- 参数调优: 理解每个超参数的意义
调参技巧¶
# 不要一开始就网格搜索!
# 推荐步骤:
# 1. 用默认参数建立基准
model = XGBClassifier()
model.fit(X_train, y_train)
# 2. 调整最重要的1-2个参数
model = XGBClassifier(
max_depth=5, # 树深度 (影响最大)
learning_rate=0.1 # 学习率
)
# 3. 然后再精细调优其他参数
下一步¶
继续阅读 03-无监督学习.md,学习聚类和降维算法!