# 机器学习(西瓜书)- 完整知识点详解 **作者:** 周志华 **整理:** Deshill **日期:** 2026-03-18 **难度:** 进阶(适合有基础者深入自学) **特点:** 理论推导 + 代码实现 + 直观理解 --- ## 第 1 章 绪论 ### 1.1 什么是机器学习 #### 形式化定义(Tom Mitchell, 1997) > 如果一个程序在某类**任务 T**(Task)上的**性能 P**(Performance),随着**经验 E**(Experience)的积累而提高,则称该程序从经验 E 中学习。 **示例:** - **任务 T:** 下围棋 - **性能 P:** 胜率 - **经验 E:** 与自己对弈的记录 #### 机器学习的本质 ``` 机器学习 = 从数据中归纳规律 数学表达:f: X → Y 目标:找到 f,使得 f(x) ≈ y ``` **核心问题:** 1. **表示(Representation):** 用什么模型表示 f? 2. **评估(Evaluation):** 如何判断 f 的好坏? 3. **优化(Optimization):** 如何找到最好的 f? ### 1.2 基本术语详解 #### 数据集表示 ```python # 数据集 D = {(x₁, y₁), (x₂, y₂), ..., (xₘ, yₘ)} # 其中: # - m: 样本数量 # - xᵢ ∈ ℝᵈ: 第 i 个样本(d 维特征向量) # - yᵢ: 第 i 个样本的标记 import numpy as np # 示例:西瓜数据集 # 特征:色泽、根蒂、敲声、纹理、脐部、触感 # 标记:好瓜 (1) / 坏瓜 (0) D = { 'X': np.array([ [0.697, 0.460, 1, 2, 1, 1], # 样本 1 [0.774, 0.376, 2, 1, 2, 2], # 样本 2 [0.634, 0.264, 1, 2, 1, 1], # 样本 3 # ... 更多样本 ]), 'y': np.array([1, 1, 0]) # 标记 } # 术语对照 # 示例/实例 (instance) = 样本 (sample) = 一个 xᵢ # 属性 (attribute) = 特征 (feature) = 一个维度 # 属性值 = 特征值 = xᵢ 的某个分量 # 样本空间 (sample space) = 所有可能的样本 # 特征向量 (feature vector) = 一个具体的 xᵢ ``` #### 训练集、验证集、测试集 ```python from sklearn.model_selection import train_test_split # 为什么要划分? # 训练集:用于训练模型(拟合) # 验证集:用于调参和模型选择 # 测试集:用于最终评估(模拟真实场景) X_train, X_temp, y_train, y_temp = train_test_split( D['X'], D['y'], test_size=0.4, # 40% 用于验证 + 测试 random_state=42 ) X_val, X_test, y_val, y_test = train_test_split( X_temp, y_temp, test_size=0.5, # 验证和测试各 20% random_state=42 ) # 典型划分比例: # 小数据集:70% 训练 / 15% 验证 / 15% 测试 # 大数据集:98% 训练 / 1% 验证 / 1% 测试 ``` ### 1.3 学习任务类型详解 #### 1. 分类学习(Classification) **定义:** 预测离散标记 **二分类 vs 多分类:** ```python # 二分类:y ∈ {0, 1} 或 y ∈ {-1, +1} # 示例:好瓜/坏瓜、垃圾邮件/正常邮件 # 多分类:y ∈ {c₁, c₂, ..., cₖ} # 示例:西瓜品种 {黑美人, 麒麟瓜, 特小凤} ``` **二分类的数学表示:** ``` 给定数据集 D = {(x₁, y₁), ..., (xₘ, yₘ)},yᵢ ∈ {-1, +1} 学习函数 f: ℝᵈ → {-1, +1} 目标:最小化分类错误率 ``` #### 2. 回归学习(Regression) **定义:** 预测连续值 **示例:** - 预测西瓜含糖率:y ∈ ℝ - 预测房价:y ∈ ℝ⁺ **回归的数学表示:** ``` 给定数据集 D = {(x₁, y₁), ..., (xₘ, yₘ)},yᵢ ∈ ℝ 学习函数 f: ℝᵈ → ℝ 目标:最小化均方误差 MSE = (1/m) Σ(f(xᵢ) - yᵢ)² ``` #### 3. 聚类学习(Clustering) **定义:** 无监督学习,发现数据的内在结构 **示例:** - 将西瓜分为 {早熟品种, 中熟品种, 晚熟品种} - 用户分群 **聚类 vs 分类:** | 对比项 | 分类 | 聚类 | |--------|------|------| | 标签 | 有监督 | 无监督 | | 目标 | 预测新样本类别 | 发现数据结构 | | 评估 | 准确率、F1 | 轮廓系数、DB 指数 | ### 1.4 归纳偏好(Inductive Bias) #### 为什么需要归纳偏好? **问题:** 给定有限训练数据,可能有多个假设都完美拟合数据,该选哪个? **示例:** 拟合 3 个点 ``` 点:(0,0), (1,1), (2,2) 可能的拟合: 1. 直线:y = x 2. 抛物线:y = x²/2 + x/2 3. 正弦曲线:y = sin(πx/2) + ... 选择哪个?→ 需要偏好 ``` #### 奥卡姆剃刀(Occam's Razor) > 若有多个假设与观察一致,则选最简单的那个。 **数学表达:** ``` argmin_{h∈H} [Error(h) + λ·Complexity(h)] ``` **代码示例:** ```python from sklearn.linear_model import Ridge, Lasso from sklearn.tree import DecisionTreeClassifier # 正则化:偏好简单的模型 # L2 正则化:偏好小权重 ridge = Ridge(alpha=1.0) # α越大,权重越小 # L1 正则化:偏好稀疏权重(特征选择) lasso = Lasso(alpha=0.1) # 决策树剪枝:偏好小树 dt = DecisionTreeClassifier(max_depth=3) # 限制深度 ``` ### 1.5 发展历程 ``` 1950s - 萌芽期 ├─ 1950: 图灵测试(机器能否思考?) └─ 1957: Rosenblatt 提出感知机 1960s-1970s - 第一次低谷 ├─ 1969: Minsky 证明感知机无法解决 XOR 问题 └─ AI 寒冬开始 1980s - 复兴期 ├─ 1986: Rumelhart 提出反向传播算法 └─ 神经网络兴起 1990s - 统计学习时代 ├─ 1992: Vapnik 提出支持向量机 (SVM) ├─ 1995: Freund & Schapire 提出 AdaBoost └─ 统计学习理论成熟 2006 - 深度学习元年 └─ Hinton 提出深度信念网络 (DBN) 2012 - 深度学习爆发 └─ AlexNet 在 ImageNet 竞赛大胜 2016 - 强化学习突破 └─ AlphaGo 击败李世石 2017 - Transformer 革命 └─ "Attention is All You Need" 2020s - 大语言模型时代 ├─ GPT-3 (175B 参数) ├─ ChatGPT └─ GPT-4 ``` --- ## 第 2 章 模型评估与选择 ### 2.1 误差与过拟合 #### 误差的数学定义 ```python # 1. 经验误差(训练误差) # E_train(f) = (1/m) Σ I(f(xᵢ) ≠ yᵢ) # 其中 I(·) 是指示函数 # 2. 泛化误差 # E_general(f) = E_{(x,y)~D}[I(f(x) ≠ y)] # 在未知数据上的期望误差 # 3. 过拟合(Overfitting) # 现象:训练误差低,泛化误差高 # 原因:模型过于复杂,记住了训练数据的噪声 # 4. 欠拟合(Underfitting) # 现象:训练误差和泛化误差都高 # 原因:模型太简单,无法捕捉数据规律 ``` #### 过拟合的直观理解 ``` 训练数据: x: [1, 2, 3, 4, 5] y: [2, 4, 6, 8, 10] # 真实规律:y = 2x 欠拟合模型:y = x + 1 训练误差:高 测试误差:高 合适模型:y = 2x 训练误差:低 测试误差:低 过拟合模型:y = 2x + 0.1·sin(100x) 训练误差:0(完美拟合) 测试误差:高(学到了噪声) ``` #### 防止过拟合的方法 ```python # 1. 正则化 from sklearn.linear_model import Ridge, Lasso # L2 正则化:惩罚大权重 # 损失函数:||y - Xw||² + α||w||² ridge = Ridge(alpha=1.0) # L1 正则化:偏好稀疏解 # 损失函数:||y - Xw||² + α||w||₁ lasso = Lasso(alpha=0.1) # 2. 早停(Early Stopping) # 在验证集误差开始上升时停止训练 # 3. Dropout(神经网络) # 随机丢弃部分神经元 # 4. 数据增强 # 增加训练数据量 # 5. 简化模型 # 减少层数、神经元数、树的深度 ``` ### 2.2 评估方法详解 #### 1. 留出法(Hold-out) **原理:** 直接将数据集 D 划分为训练集 S 和测试集 T **关键问题:** ```python # 1. 分层采样(保持类别比例) from sklearn.model_selection import train_test_split # 错误做法:随机划分 X_train, X_test = train_test_split(X, y, test_size=0.3) # 正确做法:分层采样 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, stratify=y, # 保持类别比例 random_state=42 ) # 示例: # 原始数据:正例 60%,反例 40% # 训练集:正例 60%,反例 40% # 测试集:正例 60%,反例 40% # 2. 多次划分取平均 # 单次划分有偶然性,应多次划分求平均 ``` #### 2. 交叉验证法(Cross Validation) **k 折交叉验证:** ```python from sklearn.model_selection import cross_val_score, KFold # 步骤: # 1. 将数据集分为 k 个大小相似的互斥子集 # 2. 每次用 k-1 个子集训练,1 个子集测试 # 3. 重复 k 次,每个子集都用过一次测试 # 4. 返回 k 个测试结果的平均值 kf = KFold(n_splits=5, shuffle=True, random_state=42) scores = cross_val_score(model, X, y, cv=kf, scoring='accuracy') print(f"5 折交叉验证结果:") print(f"平均准确率:{scores.mean():.4f}") print(f"标准差:{scores.std():.4f}") print(f"95% 置信区间:{scores.mean()} ± {1.96*scores.std():.4f}") ``` **留一法(Leave-One-Out, LOO):** ```python # k = m(样本数) # 优点:几乎无偏差 # 缺点:计算开销大(训练 m 次) from sklearn.model_selection import LeaveOneOut loo = LeaveOneOut() scores = cross_val_score(model, X, y, cv=loo) ``` #### 3. 自助法(Bootstrapping) **原理:** 有放回采样 ```python from sklearn.utils import resample n_samples = len(X) n_iterations = 1000 oob_scores = [] for _ in range(n_iterations): # 有放回采样(bootstrap sample) indices = np.random.choice(n_samples, n_samples, replace=True) X_boot = X[indices] y_boot = y[indices] # 袋外样本(out-of-bag) oob_mask = ~np.isin(np.arange(n_samples), indices) X_oob = X[oob_mask] y_oob = y[oob_mask] # 训练和评估 model.fit(X_boot, y_boot) score = model.score(X_oob, y_oob) oob_scores.append(score) print(f"OOB 估计:{np.mean(oob_scores):.4f} ± {np.std(oob_scores):.4f}") ``` **适用场景:** - 数据集小,难以有效划分训练/测试集 - 集成学习(如随机森林的 OOB 估计) ### 2.3 性能度量详解 #### 分类任务性能度量 **1. 准确率(Accuracy)** ```python # 定义:分类正确的样本占总样本的比例 # Accuracy = (TP + TN) / (TP + TN + FP + FN) from sklearn.metrics import accuracy_score accuracy = accuracy_score(y_test, y_pred) # 问题:类别不平衡时不准确 # 示例:99% 负例,1% 正例 # 全预测为负例,准确率 99%,但毫无意义 ``` **2. 精确率(Precision)** ```python # 定义:预测为正的样本中,真正为正的比例 # Precision = TP / (TP + FP) from sklearn.metrics import precision_score precision = precision_score(y_test, y_pred) # 场景:宁缺毋滥 # 示例:垃圾邮件检测 # 精确率高 → 被标记为垃圾邮件的,大概率真是垃圾邮件 ``` **3. 召回率(Recall)** ```python # 定义:真正为正的样本中,被正确预测的比例 # Recall = TP / (TP + FN) from sklearn.metrics import recall_score recall = recall_score(y_test, y_pred) # 场景:宁滥毋缺 # 示例:癌症筛查 # 召回率高 → 尽可能找出所有癌症患者 ``` **4. F1 分数(F1-Score)** ```python # 定义:精确率和召回率的调和平均 # F1 = 2 × (Precision × Recall) / (Precision + Recall) # = 2TP / (2TP + FP + FN) from sklearn.metrics import f1_score f1 = f1_score(y_test, y_pred) # 为什么用调和平均? # 算术平均:(0.9 + 0.1) / 2 = 0.5 # 调和平均:2×0.9×0.1 / (0.9+0.1) = 0.18 # 调和平均更"惩罚"极端值 ``` **5. ROC 曲线与 AUC** ```python from sklearn.metrics import roc_curve, roc_auc_score import matplotlib.pyplot as plt # ROC 曲线:受试者工作特征曲线 # 横轴:假正例率 FPR = FP / (FP + TN) # 纵轴:真正例率 TPR = TP / (TP + FN) = Recall # AUC:ROC 曲线下的面积 # AUC = 0.5:随机猜测 # AUC = 1.0:完美分类 # AUC ∈ [0.5, 1.0]:越大越好 y_prob = model.predict_proba(X_test)[:, 1] auc = roc_auc_score(y_test, y_prob) fpr, tpr, thresholds = roc_curve(y_test, y_prob) plt.plot(fpr, tpr, label=f'AUC = {auc:.4f}') plt.plot([0, 1], [0, 1], 'k--') plt.xlabel('FPR') plt.ylabel('TPR') plt.legend() plt.show() ``` #### 混淆矩阵详解 ```python from sklearn.metrics import confusion_matrix import seaborn as sns # 混淆矩阵 cm = confusion_matrix(y_test, y_pred) # 可视化 sns.heatmap(cm, annot=True, fmt='d', cmap='Blues') plt.xlabel('Predicted') plt.ylabel('True') plt.show() # 解读 # 预测 # 正例 反例 # 实际 正例 TP FN # 反例 FP TN # TP (True Positive): 真正例 - 实际为正,预测为正 # FN (False Negative): 假反例 - 实际为正,预测为反(漏报) # FP (False Positive): 假正例 - 实际为反,预测为正(误报) # TN (True Negative): 真反例 - 实际为反,预测为反 ``` #### 回归任务性能度量 ```python from sklearn.metrics import ( mean_squared_error, # MSE mean_absolute_error, # MAE r2_score, # R² explained_variance_score # 解释方差 ) # 1. 均方误差 (MSE) # MSE = (1/m) Σ(yᵢ - ŷᵢ)² mse = mean_squared_error(y_test, y_pred) # 2. 均方根误差 (RMSE) # RMSE = √MSE rmse = np.sqrt(mse) # 3. 平均绝对误差 (MAE) # MAE = (1/m) Σ|yᵢ - ŷᵢ| mae = mean_absolute_error(y_test, y_pred) # 4. R² 分数 # R² = 1 - Σ(yᵢ - ŷᵢ)² / Σ(yᵢ - ȳ)² # R² = 1:完美拟合 # R² = 0:相当于预测均值 # R² < 0:比预测均值还差 r2 = r2_score(y_test, y_pred) # MSE vs MAE # MSE:对异常值敏感(平方放大) # MAE:对异常值鲁棒 ``` ### 2.4 比较检验 #### 假设检验原理 ```python from scipy import stats # 问题:算法 A 准确率 85%,算法 B 准确率 83%,A 真的比 B 好吗? # 答案:需要假设检验 # 原假设 H₀:两种算法性能无差异 # 备择假设 H₁:两种算法性能有差异 # 配对 t 检验 algo1_scores = [0.85, 0.82, 0.88, 0.84, 0.86] # 5 个数据集 algo2_scores = [0.83, 0.80, 0.85, 0.82, 0.84] t_stat, p_value = stats.ttest_rel(algo1_scores, algo2_scores) alpha = 0.05 # 显著性水平 if p_value < alpha: print(f"拒绝原假设 (p={p_value:.4f}),两种算法性能有显著差异") else: print(f"无法拒绝原假设 (p={p_value:.4f}),差异不显著") ``` --- ## 第 3 章 线性模型 ### 3.1 线性回归详解 #### 基本形式 ``` 单变量:f(x) = wx + b 多变量:f(x) = w₁x₁ + w₂x₂ + ... + w_d x_d + b = w^T x + b 矩阵形式:f(X) = Xw + b 其中:X ∈ ℝ^(m×d), w ∈ ℝ^d, b ∈ ℝ ``` #### 最小二乘法推导 **目标:** 找到 w 和 b,使得均方误差最小 ``` 损失函数:L(w, b) = Σ(yᵢ - (wxᵢ + b))² 求导并令导数为 0: ∂L/∂w = -2Σ(yᵢ - wxᵢ - b)xᵢ = 0 ∂L/∂b = -2Σ(yᵢ - wxᵢ - b) = 0 解得: w* = (X^T X)^(-1) X^T y b* = ȳ - w*x̄ ``` **代码实现:** ```python import numpy as np class LinearRegressionNormalEquation: def fit(self, X, y): # 添加偏置项 X_b = np.c_[np.ones((X.shape[0], 1)), X] # 正规方程:θ = (X^T X)^(-1) X^T y theta = np.linalg.inv(X_b.T.dot(X_b)).dot(X_b.T).dot(y) self.b = theta[0] self.w = theta[1:] return self def predict(self, X): return X.dot(self.w) + self.b ``` #### 梯度下降法详解 **原理:** 沿负梯度方向更新参数 ```python class LinearRegressionGD: def __init__(self, lr=0.01, n_iterations=1000): self.lr = lr self.n_iterations = n_iterations def fit(self, X, y): n_samples, n_features = X.shape self.w = np.zeros(n_features) self.b = 0 # 梯度下降 for i in range(self.n_iterations): # 前向传播 y_pred = X.dot(self.w) + self.b # 计算梯度 # ∂L/∂w = (1/m) X^T (Xw + b - y) # ∂L/∂b = (1/m) Σ(Xw + b - y) dw = (1/n_samples) * X.T.dot(y_pred - y) db = (1/n_samples) * np.sum(y_pred - y) # 更新参数 self.w -= self.lr * dw self.b -= self.lr * db # 记录损失(可选) if i % 100 == 0: loss = np.mean((y_pred - y) ** 2) print(f"Iteration {i}, Loss: {loss:.4f}") return self ``` **学习率选择:** ```python # 学习率太小:收敛慢 # 学习率太大:可能发散 # 学习率调度 # 1. 固定学习率 lr = 0.01 # 2. 学习率衰减 lr = 0.01 / (1 + 0.01 * epoch) # 3. 自适应学习率(Adam、RMSprop) ``` ### 3.2 对数几率回归(Logistic Regression) #### Sigmoid 函数 ```python def sigmoid(z): """ Sigmoid 函数(对数几率函数) 将任意实数映射到 (0, 1) 区间 """ return 1 / (1 + np.exp(-z)) # 性质: # - sigmoid(0) = 0.5 # - sigmoid(∞) = 1 # - sigmoid(-∞) = 0 # - 导数:sigmoid'(z) = sigmoid(z)(1 - sigmoid(z)) ``` #### 对数几率 ``` # 线性回归:预测实值 y = w^T x + b # 分类任务:需要预测 y ∈ {0, 1} # 思路:用 sigmoid 函数将实值转换为概率 # P(y=1|x) = σ(w^T x + b) = 1 / (1 + exp(-(w^T x + b))) # 对数几率(log odds): # ln(P(y=1|x) / P(y=0|x)) = w^T x + b # 解释: # - 左边是"对数几率" # - 右边是线性函数 # - 模型在预测"对数几率",而非直接预测 y ``` #### 极大似然估计 ```python # 似然函数: # L(w, b) = ∏ P(yᵢ|xᵢ; w, b) # = ∏ [p(xᵢ)]^yᵢ [1-p(xᵢ)]^(1-yᵢ) # 其中 p(xᵢ) = σ(w^T xᵢ + b) # 对数似然: # ln L(w, b) = Σ [yᵢ ln p(xᵢ) + (1-yᵢ) ln(1-p(xᵢ))] # 最大化对数似然 = 最小化负对数似然 # 损失函数:L = -Σ [yᵢ ln p(xᵢ) + (1-yᵢ) ln(1-p(xᵢ))] # 这就是交叉熵损失 ``` **代码实现:** ```python class LogisticRegressionManual: def __init__(self, lr=0.01, n_iterations=1000): self.lr = lr self.n_iterations = n_iterations 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 i in range(self.n_iterations): # 前向传播 linear_model = X.dot(self.w) + self.b y_pred = self.sigmoid(linear_model) # 梯度(交叉熵损失的导数) dw = (1/n_samples) * X.T.dot(y_pred - y) db = (1/n_samples) * np.sum(y_pred - y) # 更新 self.w -= self.lr * dw self.b -= self.lr * db return self def predict_proba(self, X): linear_model = X.dot(self.w) + self.b return self.sigmoid(linear_model) def predict(self, X): return (self.predict_proba(X) >= 0.5).astype(int) ``` ### 3.3 线性判别分析(LDA) #### 核心思想 > 给定训练集,将样本投影到一条直线上,使得: > 1. **类内散度最小**:同类样本的投影点尽可能接近 > 2. **类间散度最大**:异类样本的投影点尽可能远离 #### 数学推导 ```python # 定义: # - μ₀, μ₁: 两类样本的均值向量 # - Σ₀, Σ₁: 两类样本的协方差矩阵 # 类内散度矩阵: # S_w = Σ₀ + Σ₁ # = Σ_{x∈类 0} (x-μ₀)(x-μ₀)^T + Σ_{x∈类 1} (x-μ₁)(x-μ₁)^T # 类间散度矩阵: # S_b = (μ₀ - μ₁)(μ₀ - μ₁)^T # 投影方向 w 应最大化: # J(w) = (w^T S_b w) / (w^T S_w w) # 解得: # w = S_w^(-1) (μ₀ - μ₁) ``` **代码实现:** ```python class LDAManual: def fit(self, X, y): classes = np.unique(y) n_features = X.shape[1] # 计算总体均值 mean_overall = X.mean(axis=0) # 计算类内散度矩阵 S_w S_w = np.zeros((n_features, n_features)) means = {} for c in classes: X_c = X[y == c] means[c] = X_c.mean(axis=0) # 类内散度 X_centered = X_c - means[c] S_w += X_centered.T.dot(X_centered) # 计算类间散度矩阵 S_b S_b = np.zeros((n_features, n_features)) for c in classes: X_c = X[y == c] n_c = X_c.shape[0] # 类间散度 mean_diff = (means[c] - mean_overall).reshape(-1, 1) S_b += n_c * mean_diff.dot(mean_diff.T) # 求解 S_w^(-1) S_b 的特征向量 S_w_inv = np.linalg.inv(S_w) eigenvalues, eigenvectors = np.linalg.eig(S_w_inv.dot(S_b)) # 选择最大特征值对应的特征向量 idx = eigenvalues.argsort()[::-1] self.w = eigenvectors[:, idx[0]] return self def transform(self, X): return X.dot(self.w) ``` ### 3.4 正则化详解 #### 为什么需要正则化? **问题:** 当特征数 > 样本数时,最小二乘法无唯一解 **解决:** 添加正则化项,限制模型复杂度 #### L1 vs L2 正则化 ```python # L2 正则化(Ridge 回归) # 损失函数:L = ||y - Xw||² + α||w||² # 梯度:∂L/∂w = -2X^T(y - Xw) + 2αw # 解:w* = (X^T X + αI)^(-1) X^T y from sklearn.linear_model import Ridge ridge = Ridge(alpha=1.0) # L1 正则化(Lasso 回归) # 损失函数:L = ||y - Xw||² + α||w||₁ # 其中 ||w||₁ = Σ|wᵢ| from sklearn.linear_model import Lasso lasso = Lasso(alpha=0.1) # ElasticNet(L1 + L2) # 损失函数:L = ||y - Xw||² + α₁||w||₁ + α₂||w||² from sklearn.linear_model import ElasticNet elastic = ElasticNet(alpha=0.1, l1_ratio=0.5) ``` #### 几何解释 ``` L2 正则化: - 约束区域:圆形(||w||² ≤ C) - 解:等高线与圆相切的点 - 效果:权重趋向于小,但不为 0 L1 正则化: - 约束区域:菱形(||w||₁ ≤ C) - 解:等高线与菱形顶点相切 - 效果:权重趋向于 0(稀疏解) 应用: - L2:防止过拟合,权重平滑 - L1:特征选择,稀疏模型 ``` --- (由于篇幅限制,这里展示前 3 章的详细内容。完整文档包含所有 16 章的详细理论推导和代码实现) **下载完整文档:** http://129.211.3.54:3923/files/机器学习西瓜书_完整知识点.md --- ## 第 4 章 决策树 ### 4.1 基本流程详解 #### 决策树结构 ``` 决策树 = 根节点 + 内部节点 + 叶节点 - 根节点:包含整个数据集 - 内部节点:对应一个属性测试 - 叶节点:对应一个决策结果(类别标记) 示例:西瓜分类决策树 色泽? / | \ 青绿 乌黑 浅白 / | \ 根蒂? 根蒂? 坏瓜 / \ / \ 蜷缩 稍蜷 蜷缩 硬挺 / \ / \ 好瓜 敲声?好瓜 坏瓜 / \ 浊响 沉闷 / \ 好瓜 坏瓜 ``` #### 递归生成算法 ```python def generate_tree(D, attribute_set): """ 决策树递归生成算法 参数: - D: 当前节点包含的样本集 - attribute_set: 当前可用的属性集 返回: - node: 生成的树节点 """ node = Node() # ========== 递归终止条件 ========== # 条件 1:所有样本属于同一类 if len(np.unique(D['y'])) == 1: node.type = 'leaf' node.label = D['y'][0] return node # 条件 2:属性集为空 if len(attribute_set) == 0: node.type = 'leaf' node.label = find_majority(D['y']) # 众数 return node # 条件 3:样本在所有属性上取值相同 if all_samples_same(D['X']): node.type = 'leaf' node.label = find_majority(D['y']) return node # ========== 选择最优划分属性 ========== best_attr = select_best_attribute(D, attribute_set) node.type = 'internal' node.attribute = best_attr # ========== 递归生成子树 ========== for value in get_attribute_values(D['X'], best_attr): # 获取在该属性上取值为 value 的样本子集 D_v = { 'X': D['X'][D['X'][:, best_attr] == value], 'y': D['y'][D['X'][:, best_attr] == value] } # 如果子集为空,用父节点的众数标记 if len(D_v['y']) == 0: child = Node(type='leaf', label=find_majority(D['y'])) else: # 递归生成子树 child = generate_tree(D_v, attribute_set - {best_attr}) node.children[value] = child return node ``` ### 4.2 划分选择准则详解 #### 1. 信息增益(ID3 算法) **信息熵(Information Entropy):** ``` 定义:度量样本集合纯度最常用的指标 Ent(D) = -Σ_{k=1}^{|Y|} p_k log₂ p_k 其中: - p_k:第 k 类样本所占比例 - |Y|:类别数 性质: - Ent(D) = 0:所有样本属于同一类(最纯) - Ent(D) = log₂|Y|:各类样本均匀分布(最不纯) - Ent(D) 越小,纯度越高 示例: D 有 10 个样本,6 个正例,4 个反例 Ent(D) = -(0.6×log₂0.6 + 0.4×log₂0.4) = -(0.6×(-0.737) + 0.4×(-1.322)) = 0.971 ``` **条件熵(Conditional Entropy):** ``` 定义:在已知属性 a 的条件下,样本集合的不确定性 Ent(D|a) = Σ_{v=1}^{V} (|D^v|/|D|) × Ent(D^v) 其中: - V:属性 a 的可能取值数 - D^v:在属性 a 上取值为 v 的样本子集 - |D^v|/|D|:子集的权重 ``` **信息增益(Information Gain):** ``` 定义:得知属性 a 的信息后,类别不确定性减少的程度 Gain(D, a) = Ent(D) - Ent(D|a) = Ent(D) - Σ_{v=1}^{V} (|D^v|/|D|) × Ent(D^v) 解释: - Gain 越大,说明用属性 a 划分后,纯度提升越多 - 选择 Gain 最大的属性作为划分属性 ``` **代码实现:** ```python def calc_entropy(y): """计算信息熵""" if len(y) == 0: return 0 _, counts = np.unique(y, return_counts=True) probs = counts / len(y) # Ent(D) = -Σ p_k log₂ p_k entropy = -np.sum(probs * np.log2(probs + 1e-10)) return entropy def calc_info_gain(X, y, attribute_idx): """计算信息增益""" # 1. 计算原始熵 entropy_D = calc_entropy(y) # 2. 计算条件熵 values = np.unique(X[:, attribute_idx]) entropy_cond = 0 for value in values: # 获取子集 mask = X[:, attribute_idx] == value y_v = y[mask] # 计算权重 weight = len(y_v) / len(y) # 累加条件熵 entropy_cond += weight * calc_entropy(y_v) # 3. 信息增益 gain = entropy_D - entropy_cond return gain # 选择最优划分属性 def select_best_attribute_id3(X, y, attribute_set): """ID3 算法:选择信息增益最大的属性""" best_gain = -np.inf best_attr = None for attr_idx in attribute_set: gain = calc_info_gain(X, y, attr_idx) if gain > best_gain: best_gain = gain best_attr = attr_idx return best_attr, best_gain ``` **信息增益的偏好:** ``` 问题:信息增益偏好取值数目较多的属性 示例: - 属性 A:每个样本取值都不同(如"编号") - 用 A 划分,每个子集只有 1 个样本 - Ent(D^v) = 0(纯度最高) - Gain(D, A) 最大 解决:用增益率(C4.5 算法) ``` #### 2. 增益率(C4.5 算法) **固有值(Intrinsic Value):** ``` 定义:属性 a 本身的信息量 IV(a) = -Σ_{v=1}^{V} (|D^v|/|D|) log₂ (|D^v|/|D|) 性质: - IV(a) 随 V 增大而增大 - 对取值多的属性有"惩罚" ``` **增益率(Gain Ratio):** ``` 定义:信息增益与固有值的比值 GainRatio(D, a) = Gain(D, a) / IV(a) 解释: - 用 IV(a) 作为"分母",抵消属性取值数的影响 - 偏好取值数目适中的属性 ``` **代码实现:** ```python def calc_intrinsic_value(X, y, attribute_idx): """计算属性的固有值 IV""" values = np.unique(X[:, attribute_idx]) iv = 0 for value in values: mask = X[:, attribute_idx] == value weight = len(y[mask]) / len(y) if weight > 0: iv -= weight * np.log2(weight + 1e-10) return iv def calc_gain_ratio(X, y, attribute_idx): """计算增益率""" gain = calc_info_gain(X, y, attribute_idx) iv = calc_intrinsic_value(X, y, attribute_idx) # 防止除零 return gain / (iv + 1e-10) ``` #### 3. 基尼指数(CART 算法) **基尼值(Gini Impurity):** ``` 定义:从数据集中随机抽取两个样本,其类别标记不一致的概率 Gini(D) = 1 - Σ_{k=1}^{|Y|} p_k² 其中: - p_k:第 k 类样本所占比例 性质: - Gini(D) = 0:所有样本属于同一类(最纯) - Gini(D) = 1 - 1/|Y|:各类样本均匀分布 - Gini(D) 越小,纯度越高 示例: D 有 10 个样本,6 个正例,4 个反例 Gini(D) = 1 - (0.6² + 0.4²) = 1 - (0.36 + 0.16) = 0.48 ``` **基尼指数(Gini Index):** ``` 定义:属性 a 的条件基尼值 GiniIndex(D, a) = Σ_{v=1}^{V} (|D^v|/|D|) × Gini(D^v) 选择:GiniIndex 最小的属性作为划分属性 ``` **代码实现:** ```python def calc_gini(y): """计算基尼值""" if len(y) == 0: return 0 _, counts = np.unique(y, return_counts=True) probs = counts / len(y) # Gini(D) = 1 - Σ p_k² gini = 1 - np.sum(probs ** 2) return gini def calc_gini_index(X, y, attribute_idx): """计算基尼指数""" values = np.unique(X[:, attribute_idx]) gini_index = 0 for value in values: mask = X[:, attribute_idx] == value y_v = y[mask] weight = len(y_v) / len(y) gini_index += weight * calc_gini(y_v) return gini_index def select_best_attribute_cart(X, y, attribute_set): """CART 算法:选择基尼指数最小的属性""" best_gini = np.inf best_attr = None for attr_idx in attribute_set: gini = calc_gini_index(X, y, attr_idx) if gini < best_gini: best_gini = gini best_attr = attr_idx return best_attr, best_gini ``` ### 4.3 剪枝处理详解 #### 为什么需要剪枝? ``` 问题:决策树容易过拟合 - 树生长过深 - 记住了训练数据的噪声 - 泛化性能差 解决:剪枝(Pruning) - 去掉一些"过于特殊"的分支 - 降低模型复杂度 - 提高泛化能力 ``` #### 1. 预剪枝(Pre-Pruning) **策略:** 在树的生长过程中,提前停止 **停止条件:** ```python from sklearn.tree import DecisionTreeClassifier dt = DecisionTreeClassifier( # 1. 最大深度 max_depth=5, # 2. 内部节点再划分所需最小样本数 min_samples_split=10, # 3. 叶节点所需最小样本数 min_samples_leaf=5, # 4. 最大特征数 max_features='sqrt', # 5. 最大叶节点数 max_leaf_nodes=20, # 6. 不纯度下降阈值 min_impurity_decrease=0.01 ) # 解释: # max_depth=5: 树深不超过 5 层 # min_samples_split=10: 节点样本数<10 时不再划分 # min_samples_leaf=5: 划分后子节点样本数<5 则不划分 # max_features='sqrt': 每次划分只考虑 sqrt(d) 个特征 ``` **优点:** - 减少计算开销 - 防止过拟合 **缺点:** - 可能欠拟合("目光短浅") - 有些划分当前看似无用,但后续可能很有用 #### 2. 后剪枝(Post-Pruning) **策略:** 先生成完整树,再自底向上剪枝 **REP(Reduced Error Pruning):** ```python # 算法流程: # 1. 用训练集生成完整决策树 T # 2. 从最底层开始,对每个内部节点: # a. 计算剪枝后的验证集误差 # b. 计算不剪枝的验证集误差 # c. 如果剪枝后误差降低,则剪枝 # 3. 重复直到无法剪枝 # 伪代码 def rep_pruning(tree, X_val, y_val): # 自底向上遍历 for node in tree.bottom_up_order(): if node.type == 'internal': # 保存原分支 old_children = node.children.copy() # 剪枝:变为叶节点,标记为众数 node.type = 'leaf' node.label = find_majority(X_val, y_val, node) # 评估验证集误差 error_pruned = evaluate(tree, X_val, y_val) # 恢复原分支 node.type = 'internal' node.children = old_children error_keep = evaluate(tree, X_val, y_val) # 决策 if error_pruned < error_keep: # 剪枝更好,执行剪枝 node.type = 'leaf' node.children = None ``` **CCTP(Cost Complexity Pruning):** ```python from sklearn.tree import DecisionTreeClassifier, cost_complexity_pruning_path # 1. 训练完整树 dt = DecisionTreeClassifier() dt.fit(X_train, y_train) # 2. 获取剪枝路径 path = cost_complexity_pruning_path(X_train, y_train) ccp_alphas = path.ccp_alphas # 3. 不同 alpha 下的性能 train_scores = [] val_scores = [] for alpha in ccp_alphas: dt_pruned = DecisionTreeClassifier(ccp_alpha=alpha) dt_pruned.fit(X_train, y_train) train_scores.append(dt_pruned.score(X_train, y_train)) val_scores.append(dt_pruned.score(X_val, y_val)) # 4. 选择最优 alpha best_alpha = ccp_alphas[np.argmax(val_scores)] # 5. 用最优 alpha 剪枝 dt_final = DecisionTreeClassifier(ccp_alpha=best_alpha) dt_final.fit(X_train, y_train) ``` **后剪枝 vs 预剪枝:** | 对比项 | 预剪枝 | 后剪枝 | |--------|--------|--------| | 计算开销 | 小 | 大 | | 过拟合风险 | 低 | 低 | | 欠拟合风险 | 高 | 低 | | 泛化性能 | 一般 | 较好 | | 常用程度 | 高 | 中 | ### 4.4 连续值处理 #### 二分法(C4.5) ```python def find_best_threshold(X, y, attribute_idx): """ 对连续属性找最佳划分点 策略:二分法 1. 将属性值排序 2. 取相邻值的中点作为候选划分点 3. 计算每个划分点的信息增益 4. 选择增益最大的划分点 """ # 1. 获取属性值并排序 values = np.sort(np.unique(X[:, attribute_idx])) best_gain = -np.inf best_threshold = None # 2. 遍历候选划分点 for i in range(len(values) - 1): # 中点作为划分点 threshold = (values[i] + values[i+1]) / 2 # 3. 计算信息增益 gain = calc_info_gain_continuous(X, y, attribute_idx, threshold) # 4. 更新最优 if gain > best_gain: best_gain = gain best_threshold = threshold return best_threshold, best_gain def calc_info_gain_continuous(X, y, attribute_idx, threshold): """连续属性的信息增益""" # 1. 二分 left_mask = X[:, attribute_idx] <= threshold right_mask = ~left_mask y_left, y_right = y[left_mask], y[right_mask] # 2. 计算条件熵 if len(y_left) == 0 or len(y_right) == 0: return 0 weight_left = len(y_left) / len(y) weight_right = len(y_right) / len(y) entropy_cond = (weight_left * calc_entropy(y_left) + weight_right * calc_entropy(y_right)) # 3. 信息增益 return calc_entropy(y) - entropy_cond ``` ### 4.5 多变量决策树 #### 斜决策树 ```python # 传统决策树:轴平行划分 # 每个节点:x_i > c ? # 斜决策树:斜划分 # 每个节点:Σ w_i x_i > c ? class ObliqueDecisionTree: """ 斜决策树 每个节点用属性的线性组合划分 """ def __init__(self, max_depth=3): self.max_depth = max_depth def fit(self, X, y): # 每个节点训练一个线性分类器 self.tree = self._build_tree(X, y, depth=0) return self def _build_tree(self, X, y, depth): node = {} # 终止条件 if depth >= self.max_depth or len(np.unique(y)) == 1: node['type'] = 'leaf' node['label'] = np.bincount(y).argmax() return node # 训练线性分类器(如逻辑回归) from sklearn.linear_model import LogisticRegression clf = LogisticRegression() clf.fit(X, y) node['type'] = 'internal' node['classifier'] = clf node['weights'] = clf.coef_[0] node['bias'] = clf.intercept_[0] # 划分 y_pred = clf.predict(X) # 递归 node['left'] = self._build_tree(X[y_pred == 0], y[y_pred == 0], depth+1) node['right'] = self._build_tree(X[y_pred == 1], y[y_pred == 1], depth+1) return node ``` --- ## 第 5 章 神经网络 ### 5.1 神经元模型详解 #### M-P 神经元 ```python # McCulloch-Pitts 神经元(1943) # 数学模型: # y = f(Σ w_i x_i - θ) # 其中: # - x_i: 输入信号 # - w_i: 权重(突触强度) # - θ: 阈值(偏置) # - f(·): 激活函数 class MPNeuron: def __init__(self, n_inputs, threshold=0.5): self.w = np.random.randn(n_inputs) self.threshold = threshold def forward(self, x): # 线性组合 z = np.dot(self.w, x) # 阶跃激活 return 1 if z >= self.threshold else 0 ``` #### 激活函数详解 ```python # 1. Sigmoid 函数 def sigmoid(z): """ σ(z) = 1 / (1 + e^(-z)) 性质: - 输出范围:(0, 1) - 可导:σ'(z) = σ(z)(1-σ(z)) - 问题:梯度消失(两端导数接近 0) """ return 1 / (1 + np.exp(-np.clip(z, -500, 500))) def sigmoid_derivative(z): s = sigmoid(z) return s * (1 - s) # 2. Tanh 函数 def tanh(z): """ tanh(z) = (e^z - e^(-z)) / (e^z + e^(-z)) 性质: - 输出范围:(-1, 1) - 零中心化 - 可导:tanh'(z) = 1 - tanh²(z) """ return np.tanh(z) def tanh_derivative(z): return 1 - np.tanh(z) ** 2 # 3. ReLU 函数 def relu(z): """ ReLU(z) = max(0, z) 性质: - 输出范围:[0, ∞) - 稀疏激活(负值输出 0) - 可导:ReLU'(z) = 1 if z>0 else 0 - 优点:缓解梯度消失 - 问题:Dead ReLU(负区间梯度为 0) """ return np.maximum(0, z) def relu_derivative(z): return (z > 0).astype(float) # 4. Leaky ReLU def leaky_relu(z, alpha=0.01): """ LeakyReLU(z) = max(αz, z) 解决 Dead ReLU 问题 """ return np.where(z > 0, z, alpha * z) def leaky_relu_derivative(z, alpha=0.01): return np.where(z > 0, 1, alpha) # 对比 import matplotlib.pyplot as plt z = np.linspace(-5, 5, 100) plt.figure(figsize=(12, 4)) plt.subplot(1, 4, 1) plt.plot(z, sigmoid(z)) plt.title('Sigmoid') plt.grid(True) plt.subplot(1, 4, 2) plt.plot(z, tanh(z)) plt.title('Tanh') plt.grid(True) plt.subplot(1, 4, 3) plt.plot(z, relu(z)) plt.title('ReLU') plt.grid(True) plt.subplot(1, 4, 4) plt.plot(z, leaky_relu(z)) plt.title('Leaky ReLU') plt.grid(True) plt.tight_layout() plt.show() ``` ### 5.2 感知机详解 #### 感知机算法 ```python # Rosenblatt 感知机(1957) # 模型: # f(x) = sign(w^T x + b) # 其中 sign(z) = 1 if z≥0 else -1 # 学习策略: # 误分类驱动 # 损失函数:L(w, b) = -Σ_{x∈M} yᵢ(w^T xᵢ + b) # 其中 M 是误分类样本集合 # 更新规则: # w ← w + η yᵢ # b ← b + η yᵢ b # 其中 (xᵢ, yᵢ) 是误分类样本 class Perceptron: def __init__(self, learning_rate=0.01, n_iterations=1000): self.lr = learning_rate self.n_iterations = n_iterations self.w = None self.b = None def fit(self, X, y): """ 感知机学习算法 参数: - X: 特征矩阵 (m×d) - y: 标签向量 (m×1), y ∈ {-1, +1} """ n_samples, n_features = X.shape # 初始化 self.w = np.zeros(n_features) self.b = 0 # 迭代 for iteration in range(self.n_iterations): errors = 0 # 遍历所有样本 for idx in range(n_samples): x_i = X[idx] y_i = y[idx] # 线性组合 linear_output = np.dot(x_i, self.w) + self.b # 激活(阶跃函数) y_pred = 1 if linear_output >= 0 else -1 # 如果误分类,更新 if y_i * y_pred <= 0: # 误分类 # 更新规则 update = self.lr * y_i self.w += update * x_i self.b += update errors += 1 # 如果没有错误,收敛 if errors == 0: print(f"收敛于第{iteration}轮") break return self def predict(self, X): linear_output = np.dot(X, self.w) + self.b return np.sign(linear_output) ``` #### 感知机的局限性 ```python # 感知机收敛定理: # 如果数据线性可分,感知机必在有限步内收敛 # 问题:XOR 问题(线性不可分) # XOR 真值表: # x1 x2 | y # 0 0 | 0 # 0 1 | 1 # 1 0 | 1 # 1 1 | 0 # 可视化 X_xor = np.array([[0, 0], [0, 1], [1, 0], [1, 1]]) y_xor = np.array([0, 1, 1, 0]) plt.scatter(X_xor[y_xor==0, 0], X_xor[y_xor==0, 1], c='red', label='0') plt.scatter(X_xor[y_xor==1, 0], X_xor[y_xor==1, 1], c='blue', label='1') plt.xlabel('x1') plt.ylabel('x2') plt.title('XOR 问题:无法用一条直线分开') plt.legend() plt.show() # 结论: # 单层感知机只能解决线性可分问题 # 需要多层网络(隐藏层)解决非线性问题 ``` ### 5.3 多层网络与 BP 算法 #### 网络结构 ```python # 多层前馈神经网络 # 结构: # 输入层 → 隐藏层 1 → 隐藏层 2 → ... → 输出层 # 示例: # 输入层:d 个神经元(d 个特征) # 隐藏层 1:h1 个神经元 # 隐藏层 2:h2 个神经元 # 输出层:k 个神经元(k 个类别) # 前向传播: # a^[0] = x # z^[l] = W^[l] a^[l-1] + b^[l] # a^[l] = f(z^[l]) # y_pred = a^[L] # 其中: # - l: 层索引 # - W^[l]: 第 l 层的权重矩阵 # - b^[l]: 第 l 层的偏置向量 # - f(·): 激活函数 ``` #### BP 算法推导 ```python # 反向传播(BackPropagation)算法 # 核心思想:链式法则 # 符号: # - L: 损失函数 # - δ^[l]: 第 l 层的误差项 = ∂L/∂z^[l] # 输出层误差: # δ^[L] = ∂L/∂a^[L] × f'(z^[L]) # 隐藏层误差(反向传播): # δ^[l] = (W^[l+1])^T δ^[l+1] × f'(z^[l]) # 梯度: # ∂L/∂W^[l] = δ^[l] (a^[l-1])^T # ∂L/∂b^[l] = δ^[l] # 更新: # W^[l] ← W^[l] - η ∂L/∂W^[l] # b^[l] ← b^[l] - η ∂L/∂b^[l] class NeuralNetworkBP: def __init__(self, layer_sizes, learning_rate=0.01): """ 参数: - layer_sizes: 列表,如 [d, h1, h2, k] d: 输入维度 h1, h2: 隐藏层神经元数 k: 输出维度 """ self.lr = learning_rate self.layer_sizes = layer_sizes self.n_layers = len(layer_sizes) # 初始化权重(Xavier 初始化) self.weights = [] self.biases = [] for i in range(self.n_layers - 1): # Xavier 初始化:W ~ N(0, 2/(n_in + n_out)) std = np.sqrt(2.0 / (layer_sizes[i] + layer_sizes[i+1])) w = np.random.randn(layer_sizes[i], layer_sizes[i+1]) * std b = np.zeros((1, layer_sizes[i+1])) self.weights.append(w) self.biases.append(b) def relu(self, z): return np.maximum(0, z) def relu_derivative(self, z): return (z > 0).astype(float) def sigmoid(self, z): return 1 / (1 + np.exp(-np.clip(z, -500, 500))) def sigmoid_derivative(self, z): s = self.sigmoid(z) return s * (1 - s) def forward(self, X): """前向传播""" self.activations = [X] # a^[0] self.z_values = [] # z^[l] current = X for i in range(self.n_layers - 1): # z^[l] = W^[l] a^[l-1] + b^[l] z = np.dot(current, self.weights[i]) + self.biases[i] self.z_values.append(z) # 输出层用 sigmoid,隐藏层用 relu if i == self.n_layers - 2: # 输出层 current = self.sigmoid(z) else: # 隐藏层 current = self.relu(z) self.activations.append(current) return current # y_pred def backward(self, X, y): """反向传播""" m = X.shape[0] # 输出层误差 # δ^[L] = (a^[L] - y) × sigmoid'(z^[L]) output = self.activations[-1] delta = (output - y) * self.sigmoid_derivative(self.z_values[-1]) # 反向传播 for l in range(self.n_layers - 2, -1, -1): # 梯度 # ∂L/∂W^[l] = (1/m) (a^[l-1])^T δ^[l] dw = np.dot(self.activations[l].T, delta) / m db = np.sum(delta, axis=0, keepdims=True) / m # 更新 self.weights[l] -= self.lr * dw self.biases[l] -= self.lr * db # 传播误差到前一层 if l > 0: # δ^[l-1] = (W^[l])^T δ^[l] × relu'(z^[l-1]) delta = np.dot(delta, self.weights[l].T) * self.relu_derivative(self.z_values[l-1]) def fit(self, X, y, n_iterations=1000): """训练""" for i in range(n_iterations): # 前向传播 y_pred = self.forward(X) # 反向传播 self.backward(X, y) # 记录损失 if i % 100 == 0: loss = np.mean((y_pred - y) ** 2) print(f"Iteration {i}, Loss: {loss:.4f}") return self def predict(self, X): output = self.forward(X) return (output >= 0.5).astype(int) ``` ### 5.4 深度学习基础 #### 使用 Keras 构建深度网络 ```python from tensorflow import keras from tensorflow.keras import layers, models, callbacks # 1. 构建模型 model = models.Sequential([ # 输入层 + 隐藏层 1 layers.Dense(128, activation='relu', input_shape=(n_features,)), layers.BatchNormalization(), # 批归一化 layers.Dropout(0.3), # Dropout 防止过拟合 # 隐藏层 2 layers.Dense(64, activation='relu'), layers.BatchNormalization(), layers.Dropout(0.3), # 隐藏层 3 layers.Dense(32, activation='relu'), layers.BatchNormalization(), layers.Dropout(0.2), # 输出层 layers.Dense(n_classes, activation='softmax') ]) # 2. 编译模型 model.compile( optimizer=keras.optimizers.Adam(learning_rate=0.001), loss='categorical_crossentropy', # 多分类 # loss='binary_crossentropy', # 二分类 metrics=['accuracy'] ) # 3. 回调函数 early_stopping = callbacks.EarlyStopping( monitor='val_loss', patience=10, restore_best_weights=True ) reduce_lr = callbacks.ReduceLROnPlateau( monitor='val_loss', factor=0.5, patience=5, min_lr=1e-7 ) # 4. 训练 history = model.fit( X_train, y_train, epochs=100, batch_size=32, validation_split=0.2, callbacks=[early_stopping, reduce_lr], verbose=1 ) # 5. 可视化训练过程 plt.figure(figsize=(12, 4)) plt.subplot(1, 2, 1) plt.plot(history.history['accuracy'], label='Train Acc') plt.plot(history.history['val_accuracy'], label='Val Acc') plt.xlabel('Epoch') plt.ylabel('Accuracy') plt.legend() plt.subplot(1, 2, 2) plt.plot(history.history['loss'], label='Train Loss') plt.plot(history.history['val_loss'], label='Val Loss') plt.xlabel('Epoch') plt.ylabel('Loss') plt.legend() plt.tight_layout() plt.show() ``` --- (后续章节继续详细展开...) **完整文档下载:** http://129.211.3.54:3923/files/机器学习西瓜书_完整知识点.md --- ## 第 6 章 支持向量机 ### 6.1 间隔与支持向量详解 #### 核心思想 ``` 支持向量机(Support Vector Machine, SVM) 目标:找到一个超平面,将不同类别的样本分开,且间隔最大 超平面方程:w^T x + b = 0 其中: - w: 法向量(决定超平面方向) - b: 位移项(决定超平面位置) ``` #### 函数间隔与几何间隔 ```python # 1. 函数间隔(Functional Margin) # γ̂_i = y_i(w^T x_i + b) # 解释: # - y_i ∈ {-1, +1} # - 如果分类正确,γ̂_i > 0 # - 值越大,分类越"确信" # 问题:w 和 b 成比例缩放时,函数间隔也成比例变化 # 例如:w 变为 2w,γ̂_i 也变为 2γ̂_i # 但超平面没变!→ 需要几何间隔 # 2. 几何间隔(Geometric Margin) # γ_i = y_i(w^T x_i + b) / ||w|| # 解释: # - 除以 ||w|| 进行归一化 # - 几何间隔是点到超平面的实际距离 # - 不随 w, b 缩放而改变 # 样本集的几何间隔: # γ = min_i γ_i # 即所有样本中,距离超平面最近的点的距离 ``` #### 最大间隔分类器 ```python # 优化问题: # max_{w,b} γ # s.t. y_i(w^T x_i + b) / ||w|| ≥ γ, ∀i # 令 γ̂ = 1(通过缩放 w, b 实现) # 则几何间隔 γ = 1/||w|| # 优化问题变为: # max_{w,b} 1/||w|| # s.t. y_i(w^T x_i + b) ≥ 1, ∀i # 等价于: # min_{w,b} 1/2 ||w||² # s.t. y_i(w^T x_i + b) ≥ 1, ∀i # 这就是硬间隔 SVM 的基本型 ``` #### 支持向量 ``` 定义:使等号成立的样本点 y_i(w^T x_i + b) = 1 性质: - 支持向量决定了超平面 - 移除支持向量,超平面会改变 - 移除其他样本,超平面不变 - 支持向量通常很少(稀疏性) ``` ### 6.2 对偶问题详解 #### 拉格朗日函数 ```python # 原始问题: # min_{w,b} 1/2 ||w||² # s.t. y_i(w^T x_i + b) ≥ 1, ∀i # 引入拉格朗日乘子 α_i ≥ 0 # 拉格朗日函数: # L(w, b, α) = 1/2 ||w||² + Σ α_i [1 - y_i(w^T x_i + b)] # 其中 α = (α₁, α₂, ..., αₘ) ``` #### 对偶问题推导 ```python # 步骤 1:求 L 对 w, b 的极小值 # ∂L/∂w = w - Σ α_i y_i x_i = 0 # → w = Σ α_i y_i x_i # ∂L/∂b = -Σ α_i y_i = 0 # → Σ α_i y_i = 0 # 步骤 2:代入 L,得到对偶函数 # L(α) = 1/2 ||Σ α_i y_i x_i||² # + Σ α_i [1 - y_i((Σ α_j y_j x_j)^T x_i + b)] # 展开并化简: # L(α) = Σ α_i - 1/2 Σ Σ α_i α_j y_i y_j x_i^T x_j # 步骤 3:求对偶函数的极大值 # max_α Σ α_i - 1/2 Σ Σ α_i α_j y_i y_j x_i^T x_j # s.t. Σ α_i y_i = 0 # α_i ≥ 0, ∀i # 这就是 SVM 的对偶问题 ``` #### SMO 算法 ```python # Sequential Minimal Optimization # 思想:每次只优化两个变量,固定其他变量 from sklearn import svm # sklearn 实现 svc = svm.SVC( kernel='linear', # 线性核 C=1.0, # 正则化参数 max_iter=1000 ) # 手动实现简化版 SMO class SVMSMO: def __init__(self, C=1.0, tol=1e-3, max_iter=1000): self.C = C self.tol = tol self.max_iter = max_iter def fit(self, X, y): m = len(y) self.X = X self.y = y self.alphas = np.zeros(m) self.b = 0 for iteration in range(self.max_iter): alpha_pairs_changed = 0 for i in range(m): # 计算预测值 f_i = np.sum(self.alphas * y * self._kernel(X, X[i])) + self.b # 计算误差 E_i = f_i - y[i] # KKT 条件检查 if ((y[i] * E_i < -self.tol and self.alphas[i] < self.C) or (y[i] * E_i > self.tol and self.alphas[i] > 0)): # 选择第二个变量 j = self._select_j(i) f_j = np.sum(self.alphas * y * self._kernel(X, X[j])) + self.b E_j = f_j - y[j] # 保存旧值 alpha_i_old = self.alphas[i] alpha_j_old = self.alphas[j] # 计算边界 if y[i] != y[j]: L = max(0, self.alphas[j] - self.alphas[i]) H = min(self.C, self.C + self.alphas[j] - self.alphas[i]) else: L = max(0, self.alphas[i] + self.alphas[j] - self.C) H = min(self.C, self.alphas[i] + self.alphas[j]) if L == H: continue # 计算 eta eta = 2 * self._kernel(X[i], X[j]) - self._kernel(X[i], X[i]) - self._kernel(X[j], X[j]) if eta >= 0: continue # 更新 alpha_j self.alphas[j] -= y[j] * (E_i - E_j) / eta # 剪辑 self.alphas[j] = np.clip(self.alphas[j], L, H) if abs(self.alphas[j] - alpha_j_old) < 1e-5: continue # 更新 alpha_i self.alphas[i] += y[i] * y[j] * (alpha_j_old - self.alphas[j]) # 更新 b b1 = self.b - E_i - y[i] * (self.alphas[i] - alpha_i_old) * self._kernel(X[i], X[i]) \ - y[j] * (self.alphas[j] - alpha_j_old) * self._kernel(X[i], X[j]) b2 = self.b - E_j - y[i] * (self.alphas[i] - alpha_i_old) * self._kernel(X[i], X[j]) \ - y[j] * (self.alphas[j] - alpha_j_old) * self._kernel(X[j], X[j]) if 0 < self.alphas[i] < self.C: self.b = b1 elif 0 < self.alphas[j] < self.C: self.b = b2 else: self.b = (b1 + b2) / 2 alpha_pairs_changed += 1 if alpha_pairs_changed == 0: print(f"收敛于第{iteration}轮") break # 计算权重向量 self.w = np.sum(self.alphas[:, np.newaxis] * y[:, np.newaxis] * X, axis=0) return self def _kernel(self, x1, x2): # 线性核 return np.dot(x1, x2.T) def predict(self, X): results = [] for x in X: result = np.sum(self.alphas * self.y * self._kernel(self.X, x)) + self.b results.append(np.sign(result)) return np.array(results) ``` ### 6.3 核函数详解 #### 核技巧 ```python # 问题:线性不可分数据 # 思路:将低维空间映射到高维空间 # φ: ℝᵈ → ℝᴰ (D >> d) # 在高维空间中,数据可能线性可分 # 问题:计算内积 x_i^T x_j 变为 φ(x_i)^T φ(x_j) # 如果 D 很大,计算开销大 # 核技巧:用核函数直接计算高维内积 # K(x_i, x_j) = φ(x_i)^T φ(x_j) # 优点: # 1. 避免显式计算 φ(x) # 2. 可以处理无限维空间 ``` #### 常见核函数 ```python from sklearn.svm import SVC import numpy as np # 1. 线性核(Linear Kernel) # K(x, z) = x^T z svc_linear = SVC(kernel='linear') # 适用:线性可分数据 # 优点:计算快,参数少 # 缺点:无法处理非线性问题 # 2. 多项式核(Polynomial Kernel) # K(x, z) = (γ x^T z + r)^d svc_poly = SVC(kernel='poly', degree=3, gamma='scale', coef0=1) # 参数: # - degree: 多项式次数 d # - gamma: γ 参数 # - coef0: r 参数 # 适用:特征交互重要的场景 # 缺点:参数多,计算复杂 # 3. 高斯核/RBF 核(Radial Basis Function) # K(x, z) = exp(-γ ||x - z||²) svc_rbf = SVC(kernel='rbf', gamma='scale') # 参数: # - gamma: γ = 1/(2σ²) # γ大 → σ小 → 影响范围小 → 决策边界复杂 # γ小 → σ大 → 影响范围大 → 决策边界平滑 # 适用:大多数非线性问题 # 优点:参数少,效果好 # 缺点:解释性差 # 4. Sigmoid 核 # K(x, z) = tanh(γ x^T z + r) svc_sigmoid = SVC(kernel='sigmoid', gamma='scale', coef0=0) # 适用:神经网络激活 # 注意:某些参数下不是合法核函数 ``` #### 核函数选择指南 ```python # 选择策略: # 1. 特征数 >> 样本数 # → 线性核(特征已经足够多) # 2. 特征数 ≈ 样本数 # → 先试线性核,不行再用 RBF # 3. 特征数 << 样本数 # → RBF 核 # 4. 文本分类 # → 线性核(高维稀疏) # 5. 图像分类 # → RBF 核 # 参数调优 from sklearn.model_selection import GridSearchCV param_grid = { 'C': [0.1, 1, 10, 100], 'gamma': ['scale', 0.01, 0.1, 1, 10], 'kernel': ['rbf'] } grid_search = GridSearchCV( SVC(), param_grid, cv=5, scoring='accuracy', n_jobs=-1 ) grid_search.fit(X_train, y_train) print(f"最优参数:{grid_search.best_params_}") print(f"最优得分:{grid_search.best_score_:.4f}") ``` ### 6.4 软间隔与正则化 #### 软间隔 SVM ```python # 问题:硬间隔要求所有样本正确分类 # 现实:数据有噪声,难以线性可分 # 解决:引入松弛变量 ξ_i ≥ 0 # y_i(w^T x_i + b) ≥ 1 - ξ_i # 优化问题: # min_{w,b,ξ} 1/2 ||w||² + C Σ ξ_i # s.t. y_i(w^T x_i + b) ≥ 1 - ξ_i # ξ_i ≥ 0 # 其中 C 是正则化参数 # 解释: # - C 大:惩罚大,ξ_i 小 → 硬间隔(可能过拟合) # - C 小:惩罚小,ξ_i 大 → 软间隔(可能欠拟合) ``` #### 合页损失(Hinge Loss) ```python # SVM 的损失函数视角 # 合页损失: # L(y, f(x)) = max(0, 1 - y f(x)) # 其中 f(x) = w^T x + b # 解释: # - 如果 y f(x) ≥ 1(正确分类且间隔≥1),损失=0 # - 如果 y f(x) < 1,损失 = 1 - y f(x) # 优化问题: # min_{w,b} Σ max(0, 1 - y_i(w^T x_i + b)) + λ/2 ||w||² # 这就是结构风险最小化 # 第一项:经验风险(损失函数) # 第二项:结构风险(正则化) import matplotlib.pyplot as plt # 对比不同损失函数 z = np.linspace(-2, 2, 100) # 合页损失 hinge_loss = np.maximum(0, 1 - z) # 对数损失(逻辑回归) log_loss = np.log(1 + np.exp(-z)) # 指数损失(AdaBoost) exp_loss = np.exp(-z) # 0-1 损失 zero_one_loss = (z < 0).astype(float) plt.figure(figsize=(10, 6)) plt.plot(z, hinge_loss, label='Hinge Loss (SVM)', linewidth=2) plt.plot(z, log_loss, label='Log Loss (LR)', linewidth=2) plt.plot(z, exp_loss, label='Exp Loss (AdaBoost)', linewidth=2) plt.plot(z, zero_one_loss, label='0-1 Loss', linestyle='--') plt.xlabel('y f(x)') plt.ylabel('Loss') plt.title('不同损失函数对比') plt.legend() plt.grid(True) plt.show() ``` ### 6.5 支持向量回归(SVR) ```python from sklearn.svm import SVR # SVR 思想: # 找到一个超平面,使得所有样本到超平面的距离≤ε # ε-不敏感损失: # L_ε(y, f(x)) = 0 if |y-f(x)| ≤ ε else |y-f(x)| - ε # 优化问题: # min_{w,b} 1/2 ||w||² + C Σ L_ε(y_i, f(x_i)) # 引入松弛变量 ξ_i, ξ_i* # min_{w,b,ξ,ξ*} 1/2 ||w||² + C Σ (ξ_i + ξ_i*) # s.t. y_i - (w^T x_i + b) ≤ ε + ξ_i # (w^T x_i + b) - y_i ≤ ε + ξ_i* # ξ_i, ξ_i* ≥ 0 svr = SVR( kernel='rbf', C=100, # 正则化参数 epsilon=0.1, # ε 参数 gamma='scale' ) ``` --- ## 第 7 章 贝叶斯分类器 ### 7.1 贝叶斯决策论详解 #### 贝叶斯公式 ```python # 贝叶斯公式: # P(c|x) = P(x|c) P(c) / P(x) # 其中: # - P(c): 先验概率(prior) # 在看到数据前,类别 c 的概率 # 通常用训练集中 c 的比例估计 # - P(x|c): 类条件概率(likelihood) # 给定类别 c,样本 x 出现的概率 # 需要建模 # - P(c|x): 后验概率(posterior) # 看到样本 x 后,属于类别 c 的概率 # 分类决策依据 # - P(x): 证据因子(evidence) # 归一化常数,P(x) = Σ P(x|c)P(c) # 分类决策: # h*(x) = argmax_c P(c|x) # = argmax_c P(x|c) P(c) ``` #### 最小化错误率 ```python # 贝叶斯最优分类器: # h*(x) = argmax_c P(c|x) # 证明:最小化错误率 # 错误率:R(h) = E[I(h(x) ≠ Y)] # = ∫ P(h(x) ≠ Y | x) P(x) dx # 对于每个 x,最小化 P(h(x) ≠ Y | x) # = 1 - P(h(x) = Y | x) # = 1 - P(c = h(x) | x) # 要最小化错误率,就要最大化 P(c = h(x) | x) # → h*(x) = argmax_c P(c|x) ``` #### 最小化风险 ```python # 考虑不同错误的代价不同 # 损失函数 λ_ij:将类别 i 误判为 j 的代价 # 条件风险: # R(c_i | x) = Σ_j λ_ij P(c_j | x) # 贝叶斯判定准则: # h*(x) = argmin_{c_i} R(c_i | x) # 特例:0-1 损失 # λ_ij = 0 if i=j else 1 # 则 R(c_i | x) = 1 - P(c_i | x) # 最小化风险 = 最大化后验概率 ``` ### 7.2 极大似然估计 #### 原理 ```python # 问题:如何估计 P(x|c)? # 思路:假设概率分布形式,用数据估计参数 # 示例:假设 P(x|c) 服从高斯分布 # P(x|c) ~ N(μ_c, Σ_c) # 参数: # - μ_c: 类别 c 的均值向量 # - Σ_c: 类别 c 的协方差矩阵 # 极大似然估计: # 找到参数 θ,使得训练数据出现的概率最大 # 似然函数: # L(θ) = P(D_c | θ) = ∏_{x∈D_c} P(x | θ) # 对数似然: # LL(θ) = log L(θ) = Σ_{x∈D_c} log P(x | θ) # 最大化 LL(θ) 得到参数估计 ``` #### 高斯分布的 MLE ```python import numpy as np class GaussianNB_MLE: def fit(self, X, y): self.classes = np.unique(y) self.mean = {} self.var = {} self.priors = {} for c in self.classes: X_c = X[y == c] # 极大似然估计均值 # μ_c = (1/|D_c|) Σ_{x∈D_c} x self.mean[c] = X_c.mean(axis=0) # 极大似然估计方差 # σ²_c = (1/|D_c|) Σ_{x∈D_c} (x - μ_c)² self.var[c] = X_c.var(axis=0) + 1e-10 # 防止除零 # 先验概率 # P(c) = |D_c| / |D| self.priors[c] = len(X_c) / len(X) return self def _gaussian_probability(self, x, mean, var): """ 高斯概率密度函数 P(x|μ,σ²) = (1/√(2πσ²)) exp(-(x-μ)²/(2σ²)) """ exponent = np.exp(-np.power(x - mean, 2) / (2 * var)) return (1 / np.sqrt(2 * np.pi * var)) * exponent def _posterior(self, x, c): """ 计算后验概率(未归一化) P(c|x) ∝ P(c) × P(x|c) """ prior = np.log(self.priors[c]) # 假设属性条件独立 # P(x|c) = ∏ P(x_i|c) likelihood = np.sum(np.log( self._gaussian_probability(x, self.mean[c], self.var[c]) )) return prior + likelihood def predict(self, X): return np.array([ self.classes[np.argmax([self._posterior(x, c) for c in self.classes])] for x in X ]) def predict_proba(self, X): probas = [] for x in X: scores = [self._posterior(x, c) for c in self.classes] # softmax 归一化 exp_scores = np.exp(scores - np.max(scores)) probas.append(exp_scores / np.sum(exp_scores)) return np.array(probas) ``` ### 7.3 朴素贝叶斯 #### 属性条件独立性假设 ```python # 朴素贝叶斯假设: # 给定类别,所有属性相互独立 # P(x|c) = P(x₁, x₂, ..., x_d | c) # = ∏_{i=1}^d P(x_i | c) # 优点: # 1. 大大减少参数数量 # 2. 计算简单 # 3. 对小数据集效果好 # 缺点: # 1. 假设太强(实际属性往往相关) # 2. 可能损失精度 ``` #### sklearn 实现 ```python from sklearn.naive_bayes import GaussianNB, MultinomialNB, BernoulliNB # 1. 高斯朴素贝叶斯(连续特征) # 假设 P(x_i|c) ~ N(μ_ci, σ²_ci) gnb = GaussianNB( priors=None, # 先验概率(None 表示从数据学习) var_smoothing=1e-9 # 方差平滑(防止除零) ) # 2. 多项式朴素贝叶斯(离散特征) # 适用于文本分类(词频) mnb = MultinomialNB( alpha=1.0, # 拉普拉斯平滑参数 fit_prior=True, # 是否学习先验 class_prior=None # 先验概率 ) # 3. 伯努利朴素贝叶斯(0/1 特征) # 适用于文本分类(词是否出现) bnb = BernoulliNB( alpha=1.0, # 拉普拉斯平滑 binarize=0.0, # 二值化阈值 fit_prior=True ) # 示例:文本分类 from sklearn.feature_extraction.text import CountVectorizer texts = [ "这个电影很好看", "剧情很精彩", "太难看了", "浪费钱" ] labels = [1, 1, 0, 0] # 1=正面,0=负面 # 词袋模型 vectorizer = CountVectorizer() X = vectorizer.fit_transform(texts) # 训练 mnb.fit(X, labels) # 预测 test_texts = ["很好看", "太难看了"] X_test = vectorizer.transform(test_texts) preds = mnb.predict(X_test) ``` #### 拉普拉斯平滑 ```python # 问题:如果某个属性值在训练集中未出现 # P(x_i|c) = 0 # 则整个连乘为 0 # 解决:拉普拉斯平滑(Laplace Smoothing) # P(x_i = v | c) = (|D_{c,v}| + α) / (|D_c| + α × N) # 其中: # - |D_{c,v}|: 类别 c 中属性取值为 v 的样本数 # - |D_c|: 类别 c 的样本总数 # - N: 属性可能的取值数 # - α: 平滑参数(通常 α=1) # 当 α=1 时,称为拉普拉斯平滑 # 当 α<1 时,称为 Lidstone 平滑 ``` ### 7.4 半朴素贝叶斯 #### TAN 算法 ```python # Tree Augmented Naive Bayes # 思想:允许属性间存在依赖关系,但形成树结构 # 步骤: # 1. 计算属性间的条件互信息 # I(x_i, x_j | c) = Σ P(x_i, x_j, c) log [P(x_i, x_j | c) / (P(x_i|c)P(x_j|c))] # 2. 构建完全图,边权为条件互信息 # 3. 找最大生成树 # 4. 添加类别节点到所有属性 # 5. 计算条件概率 from sklearn.naive_bayes import GaussianNB # sklearn 未直接实现 TAN,可使用 libpymal 等库 ``` --- ## 第 8 章 集成学习 ### 8.1 个体与集成 #### 为什么集成有效? ```python # 直观解释:三个臭皮匠,顶个诸葛亮 # 理论解释: # 假设: # - T 个个体学习器 # - 每个学习器错误率 ε # - 学习器相互独立 # 集成错误率: # P(集成错误) = P(超过 T/2 个学习器错误) # = Σ_{k=⌊T/2⌋+1}^T C(T,k) ε^k (1-ε)^{T-k} # 霍夫丁不等式: # P(集成错误) ≤ exp(-T(1-2ε)²/2) # 结论: # - 当 ε < 0.5 时,集成错误率随 T 指数下降 # - 个体学习器要"好而不同" ``` #### 投票法 ```python from sklearn.ensemble import VotingClassifier from sklearn.linear_model import LogisticRegression from sklearn.tree import DecisionTreeClassifier from sklearn.svm import SVC from sklearn.naive_bayes import GaussianNB # 硬投票(Hard Voting) # H(x) = argmax_c Σ I(h_i(x) = c) voting_hard = VotingClassifier( estimators=[ ('lr', LogisticRegression()), ('dt', DecisionTreeClassifier()), ('svc', SVC(probability=True)), ('gnb', GaussianNB()) ], voting='hard' # 硬投票 ) # 软投票(Soft Voting) # H(x) = argmax_c Σ P(c | x, h_i) voting_soft = VotingClassifier( estimators=[ ('lr', LogisticRegression()), ('dt', DecisionTreeClassifier()), ('svc', SVC(probability=True)), ('gnb', GaussianNB()) ], voting='soft' # 软投票(需要概率) # weights=[1, 2, 1, 1] # 可设置权重 ) # 为什么软投票通常更好? # 软投票考虑了"置信度" # 硬投票只考虑类别 ``` ### 8.2 Boosting 算法详解 #### AdaBoost 推导 ```python # AdaBoost(Adaptive Boosting) # 算法流程: # 输入:训练集 D = {(x₁,y₁),...,(xₘ,yₘ)} # 基学习算法 L # 迭代次数 T # 1. 初始化权重:D₁(i) = 1/m # 2. 对 t = 1,2,...,T: # a) 用权重 D_t 训练基学习器 h_t # b) 计算误差:ε_t = P_{i~D_t}(h_t(x_i) ≠ y_i) # c) 计算权重:α_t = 1/2 ln((1-ε_t)/ε_t) # d) 更新样本权重: # D_{t+1}(i) = D_t(i) × exp(-α_t y_i h_t(x_i)) / Z_t # 其中 Z_t 是归一化因子 # 3. 输出:H(x) = sign(Σ α_t h_t(x)) class AdaBoostManual: def __init__(self, n_estimators=50, learning_rate=1.0): self.n_estimators = n_estimators self.learning_rate = learning_rate self.estimators = [] self.alphas = [] def fit(self, X, y): m = len(y) # 初始化权重 weights = np.ones(m) / m # 转换标签为 {-1, +1} y = np.where(y == 0, -1, 1) for t in range(self.n_estimators): # 用权重采样训练 indices = np.random.choice(m, m, p=weights) X_sample, y_sample = X[indices], y[indices] # 训练基学习器(决策树桩) from sklearn.tree import DecisionTreeClassifier estimator = DecisionTreeClassifier(max_depth=1) estimator.fit(X_sample, y_sample) # 预测 y_pred = estimator.predict(X) # 计算误差 misclassified = (y_pred != y) error = np.sum(weights * misclassified) # 计算学习器权重 if error == 0: error = 1e-10 # 防止除零 alpha = 0.5 * np.log((1 - error) / error) # 更新样本权重 weights *= np.exp(-alpha * y * y_pred) weights /= np.sum(weights) # 归一化 self.estimators.append(estimator) self.alphas.append(alpha) return self def predict(self, X): predictions = np.zeros(len(X)) for alpha, est in zip(self.alphas, self.estimators): predictions += alpha * est.predict(X) return np.sign(predictions).astype(int) ``` #### GBDT 详解 ```python # Gradient Boosting Decision Tree # 核心思想:用负梯度作为残差的近似 # 算法流程: # 1. 初始化:F₀(x) = argmin_c Σ L(y_i, c) # 2. 对 m = 1,2,...,M: # a) 计算负梯度(伪残差) # r_im = -∂L(y_i, F(x_i))/∂F(x_i) # # b) 拟合残差 r_im,得到回归树 h_m(x) # # c) 更新:F_m(x) = F_{m-1}(x) + ν × h_m(x) # 其中 ν 是学习率 # 3. 输出:F_M(x) from sklearn.ensemble import GradientBoostingClassifier, GradientBoostingRegressor # GBDT 分类 gbdt_clf = GradientBoostingClassifier( n_estimators=100, # 树的数量 learning_rate=0.1, # 学习率 max_depth=3, # 树的最大深度 subsample=0.8, # 样本采样比例 min_samples_split=10, # 内部节点再划分最小样本数 min_samples_leaf=5, # 叶节点最小样本数 random_state=42 ) # GBDT 回归 gbdt_reg = GradientBoostingRegressor( n_estimators=100, learning_rate=0.1, max_depth=3, loss='ls' # 损失函数:ls(最小二乘), lad(绝对损失), huber ) # 不同损失函数 # 分类:deviance(对数损失), exponential(指数损失) # 回归:ls(最小二乘), lad(绝对损失), huber(Huber 损失), quantile(分位数损失) ``` #### XGBoost ```python import xgboost as xgb # XGBoost(eXtreme Gradient Boosting) # 改进点: # 1. 二阶泰勒展开(GBDT 是一阶) # 2. 添加正则化项 # 3. 支持并行计算 # 4. 自动处理缺失值 # 5. 支持自定义损失函数 # 目标函数: # Obj = Σ L(y_i, ŷ_i) + Σ Ω(f_k) # 其中 Ω(f) = γT + 1/2 λ||w||² # 泰勒展开: # Obj ≈ Σ [g_i f_t(x_i) + 1/2 h_i f_t²(x_i)] + Ω(f_t) # 其中: # g_i = ∂L/∂ŷ_i (一阶导数) # h_i = ∂²L/∂ŷ_i² (二阶导数) xgb_clf = xgb.XGBClassifier( n_estimators=100, max_depth=6, learning_rate=0.1, subsample=0.8, # 样本采样 colsample_bytree=0.8, # 特征采样 reg_alpha=0.1, # L1 正则 reg_lambda=1.0, # L2 正则 eval_metric='logloss', random_state=42 ) # 重要参数: # - max_depth: 树的最大深度 # - min_child_weight: 子树最小样本权重和 # - gamma: 节点分裂最小损失减少 # - scale_pos_weight: 正负样本不平衡时的权重 ``` ### 8.3 Bagging 与随机森林 #### Bagging ```python from sklearn.ensemble import BaggingClassifier, BaggingRegressor # Bagging(Bootstrap Aggregating) # 算法流程: # 1. 自助采样:从训练集中有放回采样 T 次 # 2. 独立训练:用每个采样集训练一个基学习器 # 3. 结合策略:分类用投票,回归用平均 bagging = BaggingClassifier( base_estimator=None, # None 表示用决策树 n_estimators=50, max_samples=1.0, # 采样比例(1.0 表示与原数据集相同) max_features=1.0, # 特征采样比例 bootstrap=True, # 有放回采样 bootstrap_features=False, n_jobs=-1, random_state=42 ) # 为什么 Bagging 有效? # 1. 降低方差(主要) # 2. 对不稳定学习器效果好(如决策树) # 3. 基学习器独立,误差相互抵消 ``` #### 随机森林 ```python from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor # 随机森林 = Bagging + 属性采样 # 改进点: # 1. 样本采样(Bagging) # 2. 属性采样(每个节点只考虑部分特征) rf = RandomForestClassifier( n_estimators=100, # 树的数量 max_depth=None, # 树的最大深度(None 表示不限制) min_samples_split=2, # 内部节点再划分最小样本数 min_samples_leaf=1, # 叶节点最小样本数 max_features='sqrt', # 最大特征数('sqrt'=√d, 'log2'=log₂d) bootstrap=True, # 自助采样 oob_score=True, # 袋外估计 n_jobs=-1, random_state=42 ) # 特征重要性 rf.fit(X_train, y_train) importances = rf.feature_importances_ # 可视化 import matplotlib.pyplot as plt indices = np.argsort(importances)[::-1] plt.bar(range(len(importances)), importances[indices]) plt.xlabel('Feature') plt.ylabel('Importance') plt.title('Feature Importances') plt.show() # OOB 估计(Out-of-Bag) # 无需交叉验证,直接用袋外样本评估 oob_score = rf.oob_score_ ``` ### 8.4 结合策略 #### Stacking ```python from sklearn.ensemble import StackingClassifier from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier from sklearn.svm import SVC from sklearn.naive_bayes import GaussianNB # Stacking(堆叠法) # 思想:用多个初级学习器的输出作为输入,训练次级学习器 # 步骤: # 1. 训练初级学习器(基学习器) # 2. 用交叉验证生成"元特征"(避免过拟合) # 3. 用元特征训练次级学习器(元学习器) stacking = StackingClassifier( estimators=[ ('rf', RandomForestClassifier(n_estimators=10)), ('svc', SVC(kernel='rbf', probability=True)), ('gnb', GaussianNB()) ], final_estimator=LogisticRegression(), # 元学习器 cv=5, # 交叉验证折数 n_jobs=-1 ) # 为什么用交叉验证生成元特征? # 如果直接用训练集预测作为元特征,会过拟合 # 交叉验证保证元特征是"未见过的"预测 ``` --- ## 第 9 章 聚类 ### 9.1 性能度量详解 #### 外部指标(有真实标签) ```python from sklearn.metrics import ( adjusted_rand_score, # ARI adjusted_mutual_info_score, # AMI normalized_mutual_info_score, # NMI fowlkes_mallows_score # FMI ) # 1. ARI(调整兰德系数) # ARI = (RI - E[RI]) / (max(RI) - E[RI]) # 其中 RI = (a+b) / C(m,2) # a: 同类别且同簇的样本对数 # b: 不同类别且不同簇的样本对数 ari = adjusted_rand_score(y_true, labels) # 范围:[-1, 1],越大越好 # 0: 随机标签 # 1: 完美匹配 # 2. AMI(调整互信息) # AMI = (MI - E[MI]) / (avg(H(U), H(V)) - E[MI]) ami = adjusted_mutual_info_score(y_true, labels) # 范围:[0, 1],越大越好 # 3. NMI(标准化互信息) nmi = normalized_mutual_info_score(y_true, labels) # 范围:[0, 1],越大越好 # 4. FMI(Fowlkes-Mallows 指数) fmi = fowlkes_mallows_score(y_true, labels) # 范围:[0, 1],越大越好 ``` #### 内部指标(无真实标签) ```python from sklearn.metrics import ( silhouette_score, # 轮廓系数 calinski_harabasz_score, # CH 指数 davies_bouldin_score # DB 指数 ) # 1. 轮廓系数(Silhouette Coefficient) # s(i) = (b(i) - a(i)) / max(a(i), b(i)) # 其中: # - a(i): 样本 i 到同簇其他样本的平均距离 # - b(i): 样本 i 到最近其他簇样本的平均距离 silhouette = silhouette_score(X, labels) # 范围:[-1, 1],越大越好 # 1: 样本与自己的簇非常紧密,与其他簇很远 # 0: 样本在两个簇的边界上 # -1: 样本可能被分配到错误的簇 # 2. CH 指数(Calinski-Harabasz Index) # CH = [Tr(B_k) / (k-1)] / [Tr(W_k) / (m-k)] # 其中: # - B_k: 类间散度矩阵 # - W_k: 类内散度矩阵 # - k: 簇数 # - m: 样本数 ch_score = calinski_harabasz_score(X, labels) # 范围:[0, ∞),越大越好 # 3. DB 指数(Davies-Bouldin Index) # DB = (1/k) Σ max_{j≠i} (d_i + d_j) / d_ij # 其中: # - d_i: 簇 i 内样本到簇心的平均距离 # - d_ij: 簇 i 和簇 j 的簇心距离 db_score = davies_bouldin_score(X, labels) # 范围:[0, ∞),越小越好 ``` ### 9.2 KMeans 详解 #### 算法原理 ```python # KMeans 算法 # 目标:最小化簇内平方误差 # min Σ_{i=1}^k Σ_{x∈C_i} ||x - μ_i||² # 其中 μ_i 是簇 C_i 的中心 # 算法流程: # 1. 初始化 k 个簇中心 # 2. 重复直到收敛: # a) 分配:将每个样本分配到最近的簇中心 # b) 更新:重新计算每个簇的中心 # 收敛条件: # - 簇中心不再变化 # - 簇分配不再变化 # - 达到最大迭代次数 ``` #### KMeans++ 初始化 ```python # KMeans++:智能初始化 # 思想:初始中心点应该尽可能分散 # 步骤: # 1. 随机选择第一个中心点 # 2. 对每个样本 x,计算 D(x) = min_{c∈C} ||x-c||² # 3. 按概率 D(x)/Σ D(x) 选择下一个中心点 # 4. 重复直到选择 k 个中心点 # 优点: # 1. 避免陷入局部最优 # 2. 加速收敛 # 3. 结果更稳定 from sklearn.cluster import KMeans kmeans = KMeans( n_clusters=3, init='k-means++', # 智能初始化 n_init=10, # 运行 10 次,选最好的 max_iter=300, random_state=42 ) labels = kmeans.fit_predict(X) ``` #### 手肘法找最优 K ```python import matplotlib.pyplot as plt # 手肘法(Elbow Method) # 思想:随着 K 增大,簇内平方和(inertia)会减小 # 找到"手肘"位置(下降速度突变点) inertias = [] K_range = range(1, 11) for k in K_range: km = KMeans(n_clusters=k, random_state=42) km.fit(X) inertias.append(km.inertia_) plt.figure(figsize=(10, 5)) plt.plot(K_range, inertias, 'bo-') plt.xlabel('Number of Clusters (K)') plt.ylabel('Inertia') plt.title('Elbow Method') plt.grid(True) plt.show() # 选择手肘位置的 K 值 ``` #### 轮廓系数法找最优 K ```python from sklearn.metrics import silhouette_score silhouette_scores = [] K_range = range(2, 11) # K 从 2 开始(K=1 时轮廓系数无定义) for k in K_range: km = KMeans(n_clusters=k, random_state=42) labels = km.fit_predict(X) score = silhouette_score(X, labels) silhouette_scores.append(score) plt.figure(figsize=(10, 5)) plt.plot(K_range, silhouette_scores, 'ro-') plt.xlabel('Number of Clusters (K)') plt.ylabel('Silhouette Score') plt.title('Silhouette Method') plt.grid(True) plt.show() # 选择轮廓系数最大的 K 值 ``` ### 9.3 DBSCAN 详解 ```python from sklearn.cluster import DBSCAN # DBSCAN(Density-Based Spatial Clustering) # 核心概念: # 1. ε-邻域:以样本为中心,半径为ε的区域 # 2. 核心点:ε-邻域内至少有 min_samples 个样本 # 3. 边界点:ε-邻域内样本数 < min_samples,但在核心点的ε-邻域内 # 4. 噪声点:既不是核心点也不是边界点 # 算法流程: # 1. 标记所有核心点 # 2. 从核心点开始,密度可达的样本连成一个簇 # 3. 边界点分配到对应的簇 # 4. 噪声点标记为 -1 dbscan = DBSCAN( eps=0.5, # ε-邻域半径 min_samples=5, # 核心点最少样本数 metric='euclidean', n_jobs=-1 ) labels = dbscan.fit_predict(X) # 优点: # 1. 不需要指定簇数 # 2. 能发现任意形状的簇 # 3. 能识别噪声点 # 4. 对异常值鲁棒 # 缺点: # 1. 对密度差异大的数据集效果差 # 2. 高维数据效果差(维度灾难) # 3. eps 和 min_samples 参数敏感 # 参数选择 # eps: 可以用 k-距离图选择 # min_samples: 通常设为数据维度的 2 倍左右 ``` ### 9.4 层次聚类 ```python from sklearn.cluster import AgglomerativeClustering from scipy.cluster.hierarchy import dendrogram, linkage # 层次聚类(Hierarchical Clustering) # 类型: # 1. 凝聚聚类(自底向上):每个样本一个簇,逐步合并 # 2. 分裂聚类(自顶向下):所有样本一个簇,逐步分裂 # 凝聚聚类算法: # 1. 初始化:每个样本一个簇 # 2. 重复直到只剩一个簇: # a) 找到距离最近的两个簇 # b) 合并这两个簇 # 距离度量(Linkage): # 1. single: 两个簇中最近样本的距离 # 2. complete: 两个簇中最远样本的距离 # 3. average: 两个簇中所有样本对的平均距离 # 4. ward: 合并后簇内方差增加量(最常用) agg = AgglomerativeClustering( n_clusters=3, affinity='euclidean', # 距离度量 linkage='ward' # 连接方式 ) labels = agg.fit_predict(X) # 树状图 linkage_matrix = linkage(X, method='ward') plt.figure(figsize=(12, 6)) dendrogram(linkage_matrix) plt.title('Hierarchical Clustering Dendrogram') plt.xlabel('Sample Index') plt.ylabel('Distance') plt.show() # 优点: # 1. 不需要指定簇数(可以事后选择) # 2. 树状图可视化 # 3. 适用于小数据集 # 缺点: # 1. 计算复杂度高 O(m³) # 2. 不适合大数据集 ``` ### 9.5 高斯混合聚类(GMM) ```python from sklearn.mixture import GaussianMixture # GMM(Gaussian Mixture Model) # 思想:数据由多个高斯分布混合生成 # P(x) = Σ_{k=1}^K π_k N(x | μ_k, Σ_k) # 其中 π_k 是混合系数,Σ π_k = 1 # EM 算法: # E 步:计算后验概率 γ(z_k) = P(z_k|x) # M 步:更新参数 μ_k, Σ_k, π_k gmm = GaussianMixture( n_components=3, covariance_type='full', # 'full', 'tied', 'diag', 'spherical' max_iter=100, random_state=42 ) labels = gmm.fit_predict(X) # 协方差类型: # - full: 每个簇有自己的协方差矩阵 # - tied: 所有簇共享一个协方差矩阵 # - diag: 对角协方差矩阵(属性独立) # - spherical: 球面协方差矩阵(各向同性) # 选择最优 n_components # 用 BIC 或 AIC 准则 bics = [] K_range = range(1, 11) for k in K_range: gmm = GaussianMixture(n_components=k, random_state=42) gmm.fit(X) bics.append(gmm.bic(X)) plt.figure(figsize=(10, 5)) plt.plot(K_range, bics, 'go-') plt.xlabel('Number of Components') plt.ylabel('BIC') plt.title('BIC Method for GMM') plt.grid(True) plt.show() # 选择 BIC 最小的 n_components ``` --- ## 第 10 章 降维与度量学习 ### 10.1 主成分分析(PCA)详解 #### 最大方差理论 ```python # PCA 思想:找到数据方差最大的方向 # 推导: # 1. 数据中心化:X' = X - μ # 2. 投影到方向 w:z = X'w # 3. 最大化方差:max_w Var(z) = max_w w^T Σ w # 其中 Σ = X'^T X' / m 是协方差矩阵 # 4. 约束:||w|| = 1(防止 w 无限大) # 拉格朗日函数: # L(w, λ) = w^T Σ w - λ(w^T w - 1) # 求导: # ∂L/∂w = 2Σw - 2λw = 0 # → Σw = λw # 结论: # w 是协方差矩阵的特征向量 # λ 是对应的特征值 # 特征值越大,方差越大 ``` #### 最小距离理论 ```python # PCA 的另一视角:最小化重构误差 # 原始数据:X # 投影到低维:Z = XW # 重构:X̂ = ZW^T = XWW^T # 最小化重构误差: # min_W ||X - XWW^T||²_F # s.t. W^T W = I # 结论:与最大方差理论等价 # 最优 W 是协方差矩阵的前 k 个特征向量 ``` #### 代码实现 ```python from sklearn.decomposition import PCA import matplotlib.pyplot as plt # sklearn 实现 pca = PCA( n_components=0.95, # 保留 95% 方差 svd_solver='full', # 'auto', 'full', 'arpack', 'randomized' whiten=False # 白化(使各维度方差为 1) ) X_pca = pca.fit_transform(X) # 解释方差比 explained_variance = pca.explained_variance_ratio_ cumulative_variance = np.cumsum(explained_variance) # 可视化 plt.figure(figsize=(10, 5)) plt.plot(range(1, len(explained_variance)+1), explained_variance, 'bo-', label='Individual') plt.plot(range(1, len(cumulative_variance)+1), cumulative_variance, 'ro-', label='Cumulative') plt.xlabel('Principal Component') plt.ylabel('Explained Variance Ratio') plt.title('PCA Explained Variance') plt.legend() plt.grid(True) plt.show() # 手动实现 PCA class PCAManual: def fit(self, X, n_components=2): # 1. 中心化 self.mean_ = X.mean(axis=0) X_centered = X - self.mean_ # 2. 计算协方差矩阵 cov_matrix = np.cov(X_centered.T) # 3. 特征值分解 eigenvalues, eigenvectors = np.linalg.eig(cov_matrix) # 4. 选择前 k 个特征向量 idx = eigenvalues.argsort()[::-1][:n_components] self.components_ = eigenvectors[:, idx] self.explained_variance_ = eigenvalues[idx] self.explained_variance_ratio_ = eigenvalues[idx] / np.sum(eigenvalues) return self def transform(self, X): X_centered = X - self.mean_ return X_centered.dot(self.components_) def fit_transform(self, X, n_components=2): self.fit(X, n_components) return self.transform(X) def inverse_transform(self, X_transformed): # 重构 return X_transformed.dot(self.components_.T) + self.mean_ ``` ### 10.2 流形学习 #### t-SNE ```python from sklearn.manifold import TSNE # t-SNE(t-Distributed Stochastic Neighbor Embedding) # 思想:保持样本的局部邻域结构 # 步骤: # 1. 高维空间:计算样本间的条件概率 # p_{j|i} = exp(-||x_i-x_j||²/2σ²) / Σ_{k≠i} exp(-||x_i-x_k||²/2σ²) # # 2. 低维空间:用 t 分布建模 # q_{ij} = (1 + ||y_i-y_j||²)^{-1} / Σ_{k≠l} (1 + ||y_k-y_l||²)^{-1} # # 3. 最小化 KL 散度 # min Σ Σ p_{ij} log(p_{ij}/q_{ij}) tsne = TSNE( n_components=2, perplexity=30, # 邻域大小 learning_rate=200, n_iter=1000, random_state=42 ) X_tsne = tsne.fit_transform(X) # 可视化 plt.figure(figsize=(8, 6)) plt.scatter(X_tsne[:, 0], X_tsne[:, 1]) plt.title('t-SNE Visualization') plt.show() # 参数说明: # - perplexity: 期望邻域大小,通常 5-50 # - learning_rate: 学习率,通常 100-1000 # - n_iter: 迭代次数,通常 1000 ``` #### LLE ```python from sklearn.manifold import LocallyLinearEmbedding # LLE(Locally Linear Embedding) # 思想:每个样本可以用其邻居线性表示,保持这种线性关系 # 步骤: # 1. 找邻居:对每个样本,找 k 个最近邻 # 2. 计算权重:最小化重构误差 # min_W Σ ||x_i - Σ_j W_{ij} x_j||² # s.t. Σ_j W_{ij} = 1 # 3. 嵌入:保持权重,找低维表示 # min_Y Σ ||y_i - Σ_j W_{ij} y_j||² lle = LocallyLinearEmbedding( n_components=2, n_neighbors=5 ) X_lle = lle.fit_transform(X) ``` #### Isomap ```python from sklearn.manifold import Isomap # Isomap(Isometric Mapping) # 思想:保持测地线距离(流形上的距离) # 步骤: # 1. 构建邻域图 # 2. 计算测地线距离(最短路径) # 3. 用 MDS 降维 isomap = Isomap( n_components=2, n_neighbors=5 ) X_isomap = isomap.fit_transform(X) ``` --- **文档持续更新中...** **完整文档下载:** http://129.211.3.54:3923/files/机器学习西瓜书_完整知识点详解.md --- ## 第 6 章 支持向量机 ### 6.1 间隔与支持向量详解 #### 核心思想 ``` 支持向量机(Support Vector Machine, SVM) 目标:找到一个超平面,将不同类别的样本分开,且间隔最大 超平面方程:w^T x + b = 0 其中: - w: 法向量(决定超平面方向) - b: 位移项(决定超平面位置) ``` #### 函数间隔与几何间隔 ```python # 1. 函数间隔(Functional Margin) # γ̂_i = y_i(w^T x_i + b) # 解释: # - y_i ∈ {-1, +1} # - 如果分类正确,γ̂_i > 0 # - 值越大,分类越"确信" # 问题:w 和 b 成比例缩放时,函数间隔也成比例变化 # 例如:w 变为 2w,γ̂_i 也变为 2γ̂_i # 但超平面没变!→ 需要几何间隔 # 2. 几何间隔(Geometric Margin) # γ_i = y_i(w^T x_i + b) / ||w|| # 解释: # - 除以 ||w|| 进行归一化 # - 几何间隔是点到超平面的实际距离 # - 不随 w, b 缩放而改变 # 样本集的几何间隔: # γ = min_i γ_i # 即所有样本中,距离超平面最近的点的距离 ``` #### 最大间隔分类器 ```python # 优化问题: # max_{w,b} γ # s.t. y_i(w^T x_i + b) / ||w|| ≥ γ, ∀i # 令 γ̂ = 1(通过缩放 w, b 实现) # 则几何间隔 γ = 1/||w|| # 优化问题变为: # max_{w,b} 1/||w|| # s.t. y_i(w^T x_i + b) ≥ 1, ∀i # 等价于: # min_{w,b} 1/2 ||w||² # s.t. y_i(w^T x_i + b) ≥ 1, ∀i # 这就是硬间隔 SVM 的基本型 ``` #### 支持向量 ``` 定义:使等号成立的样本点 y_i(w^T x_i + b) = 1 性质: - 支持向量决定了超平面 - 移除支持向量,超平面会改变 - 移除其他样本,超平面不变 - 支持向量通常很少(稀疏性) ``` ### 6.2 对偶问题详解 #### 拉格朗日函数 ```python # 原始问题: # min_{w,b} 1/2 ||w||² # s.t. y_i(w^T x_i + b) ≥ 1, ∀i # 引入拉格朗日乘子 α_i ≥ 0 # 拉格朗日函数: # L(w, b, α) = 1/2 ||w||² + Σ α_i [1 - y_i(w^T x_i + b)] # 其中 α = (α₁, α₂, ..., αₘ) ``` #### 对偶问题推导 ```python # 步骤 1:求 L 对 w, b 的极小值 # ∂L/∂w = w - Σ α_i y_i x_i = 0 # → w = Σ α_i y_i x_i # ∂L/∂b = -Σ α_i y_i = 0 # → Σ α_i y_i = 0 # 步骤 2:代入 L,得到对偶函数 # L(α) = 1/2 ||Σ α_i y_i x_i||² # + Σ α_i [1 - y_i((Σ α_j y_j x_j)^T x_i + b)] # 展开并化简: # L(α) = Σ α_i - 1/2 Σ Σ α_i α_j y_i y_j x_i^T x_j # 步骤 3:求对偶函数的极大值 # max_α Σ α_i - 1/2 Σ Σ α_i α_j y_i y_j x_i^T x_j # s.t. Σ α_i y_i = 0 # α_i ≥ 0, ∀i # 这就是 SVM 的对偶问题 ``` ### 6.3 核函数详解 ```python from sklearn.svm import SVC # 1. 线性核(Linear Kernel) # K(x, z) = x^T z svc_linear = SVC(kernel='linear') # 2. 多项式核(Polynomial Kernel) # K(x, z) = (γ x^T z + r)^d svc_poly = SVC(kernel='poly', degree=3, gamma='scale', coef0=1) # 3. 高斯核/RBF 核(Radial Basis Function) # K(x, z) = exp(-γ ||x - z||²) svc_rbf = SVC(kernel='rbf', gamma='scale') # 4. Sigmoid 核 # K(x, z) = tanh(γ x^T z + r) svc_sigmoid = SVC(kernel='sigmoid', gamma='scale', coef0=0) ``` ### 6.4 软间隔与正则化 ```python from sklearn import svm # 软间隔 SVM # 优化问题: # min_{w,b,ξ} 1/2 ||w||² + C Σ ξ_i # s.t. y_i(w^T x_i + b) ≥ 1 - ξ_i # ξ_i ≥ 0 # C 参数: # - C 大:惩罚大,ξ_i 小 → 硬间隔(可能过拟合) # - C 小:惩罚小,ξ_i 大 → 软间隔(可能欠拟合) svc = svm.SVC(kernel='rbf', C=1.0) # 合页损失(Hinge Loss) # L(y, f(x)) = max(0, 1 - y f(x)) ``` ### 6.5 支持向量回归(SVR) ```python from sklearn.svm import SVR # SVR 思想: # 找到一个超平面,使得所有样本到超平面的距离≤ε # ε-不敏感损失: # L_ε(y, f(x)) = 0 if |y-f(x)| ≤ ε else |y-f(x)| - ε svr = SVR( kernel='rbf', C=100, # 正则化参数 epsilon=0.1, # ε 参数 gamma='scale' ) ``` --- ## 第 7 章 贝叶斯分类器 ### 7.1 贝叶斯决策论详解 #### 贝叶斯公式 ```python # 贝叶斯公式: # P(c|x) = P(x|c) P(c) / P(x) # 其中: # - P(c): 先验概率(prior) # - P(x|c): 类条件概率(likelihood) # - P(c|x): 后验概率(posterior) # - P(x): 证据因子(evidence) # 分类决策: # h*(x) = argmax_c P(c|x) # = argmax_c P(x|c) P(c) ``` ### 7.2 极大似然估计 ```python class GaussianNB_MLE: def fit(self, X, y): self.classes = np.unique(y) self.mean = {} self.var = {} self.priors = {} for c in self.classes: X_c = X[y == c] # 极大似然估计均值 self.mean[c] = X_c.mean(axis=0) # 极大似然估计方差 self.var[c] = X_c.var(axis=0) + 1e-10 # 先验概率 self.priors[c] = len(X_c) / len(X) return self def _gaussian_probability(self, x, mean, var): """高斯概率密度函数""" exponent = np.exp(-np.power(x - mean, 2) / (2 * var)) return (1 / np.sqrt(2 * np.pi * var)) * exponent def _posterior(self, x, c): """计算后验概率(未归一化)""" prior = np.log(self.priors[c]) likelihood = np.sum(np.log( self._gaussian_probability(x, self.mean[c], self.var[c]) )) return prior + likelihood def predict(self, X): return np.array([ self.classes[np.argmax([self._posterior(x, c) for c in self.classes])] for x in X ]) ``` ### 7.3 朴素贝叶斯 ```python from sklearn.naive_bayes import GaussianNB, MultinomialNB, BernoulliNB # 1. 高斯朴素贝叶斯(连续特征) gnb = GaussianNB() # 2. 多项式朴素贝叶斯(离散特征,如文本分类) mnb = MultinomialNB(alpha=1.0) # 3. 伯努利朴素贝叶斯(0/1 特征) bnb = BernoulliNB(alpha=1.0) # 拉普拉斯平滑: # P(x_i = v | c) = (|D_{c,v}| + α) / (|D_c| + α × N) ``` --- ## 第 8 章 集成学习 ### 8.1 个体与集成 ```python from sklearn.ensemble import VotingClassifier # 硬投票 voting_hard = VotingClassifier( estimators=[ ('lr', LogisticRegression()), ('dt', DecisionTreeClassifier()), ('svc', SVC(probability=True)) ], voting='hard' ) # 软投票 voting_soft = VotingClassifier( estimators=[ ('lr', LogisticRegression()), ('dt', DecisionTreeClassifier()), ('svc', SVC(probability=True)) ], voting='soft' ) ``` ### 8.2 Boosting 算法 ```python from sklearn.ensemble import AdaBoostClassifier, GradientBoostingClassifier import xgboost as xgb # AdaBoost ada = AdaBoostClassifier(n_estimators=50, learning_rate=1.0) # GBDT gbdt = GradientBoostingClassifier( n_estimators=100, learning_rate=0.1, max_depth=3, subsample=0.8 ) # XGBoost xgb_clf = xgb.XGBClassifier( n_estimators=100, max_depth=6, learning_rate=0.1, subsample=0.8, colsample_bytree=0.8, reg_alpha=0.1, reg_lambda=1.0 ) ``` ### 8.3 Bagging 与随机森林 ```python from sklearn.ensemble import RandomForestClassifier, BaggingClassifier # Bagging bagging = BaggingClassifier(n_estimators=50, max_samples=1.0) # 随机森林 rf = RandomForestClassifier( n_estimators=100, max_depth=None, min_samples_split=2, min_samples_leaf=1, max_features='sqrt', bootstrap=True, oob_score=True ) # 特征重要性 rf.fit(X_train, y_train) importances = rf.feature_importances_ ``` ### 8.4 结合策略 ```python from sklearn.ensemble import StackingClassifier # Stacking stacking = StackingClassifier( estimators=[ ('rf', RandomForestClassifier(n_estimators=10)), ('svc', SVC(kernel='rbf', probability=True)), ('gnb', GaussianNB()) ], final_estimator=LogisticRegression(), cv=5 ) ``` --- ## 第 9 章 聚类 ### 9.1 性能度量 ```python from sklearn.metrics import ( silhouette_score, calinski_harabasz_score, davies_bouldin_score, adjusted_rand_score ) # 轮廓系数:[-1, 1],越大越好 silhouette = silhouette_score(X, labels) # CH 指数:越大越好 ch_score = calinski_harabasz_score(X, labels) # DB 指数:越小越好 db_score = davies_bouldin_score(X, labels) # ARI(有标签时):[-1, 1],越大越好 ari = adjusted_rand_score(y_true, labels) ``` ### 9.2 KMeans ```python from sklearn.cluster import KMeans kmeans = KMeans( n_clusters=3, init='k-means++', n_init=10, max_iter=300 ) labels = kmeans.fit_predict(X) # 手肘法找最优 K inertias = [] for k in range(1, 11): km = KMeans(n_clusters=k) km.fit(X) inertias.append(km.inertia_) ``` ### 9.3 DBSCAN ```python from sklearn.cluster import DBSCAN dbscan = DBSCAN( eps=0.5, min_samples=5 ) labels = dbscan.fit_predict(X) # 优点:能发现任意形状的簇,能识别噪声 # 缺点:对密度差异大的数据效果差 ``` ### 9.4 层次聚类 ```python from sklearn.cluster import AgglomerativeClustering from scipy.cluster.hierarchy import dendrogram, linkage agg = AgglomerativeClustering( n_clusters=3, linkage='ward' ) labels = agg.fit_predict(X) # 树状图 linkage_matrix = linkage(X, method='ward') dendrogram(linkage_matrix) ``` ### 9.5 高斯混合聚类 ```python from sklearn.mixture import GaussianMixture gmm = GaussianMixture( n_components=3, covariance_type='full' ) labels = gmm.fit_predict(X) # 用 BIC 选择最优 n_components bics = [] for k in range(1, 11): gmm = GaussianMixture(n_components=k) gmm.fit(X) bics.append(gmm.bic(X)) ``` --- ## 第 10 章 降维与度量学习 ### 10.1 PCA ```python from sklearn.decomposition import PCA pca = PCA(n_components=0.95) # 保留 95% 方差 X_pca = pca.fit_transform(X) # 解释方差比 explained_variance = pca.explained_variance_ratio_ cumulative_variance = np.cumsum(explained_variance) # 手动实现 PCA class PCAManual: def fit(self, X, n_components=2): # 1. 中心化 self.mean_ = X.mean(axis=0) X_centered = X - self.mean_ # 2. 协方差矩阵 cov_matrix = np.cov(X_centered.T) # 3. 特征值分解 eigenvalues, eigenvectors = np.linalg.eig(cov_matrix) # 4. 选择前 k 个特征向量 idx = eigenvalues.argsort()[::-1][:n_components] self.components_ = eigenvectors[:, idx] return self def transform(self, X): X_centered = X - self.mean_ return X_centered.dot(self.components_) ``` ### 10.2 流形学习 ```python from sklearn.manifold import TSNE, LocallyLinearEmbedding, Isomap # t-SNE tsne = TSNE(n_components=2, perplexity=30) X_tsne = tsne.fit_transform(X) # LLE lle = LocallyLinearEmbedding(n_components=2, n_neighbors=5) X_lle = lle.fit_transform(X) # Isomap isomap = Isomap(n_components=2, n_neighbors=5) X_isomap = isomap.fit_transform(X) ``` --- ## 第 11-16 章 核心要点 ### 第 11 章 特征选择 ```python from sklearn.feature_selection import ( SelectKBest, SelectFromModel, RFE, f_classif, mutual_info_classif ) # 过滤法 selector = SelectKBest(score_func=f_classif, k=10) # 包裹法 rfe = RFE(estimator=SVC(kernel='linear'), n_features_to_select=10) # 嵌入法 from sklearn.linear_model import Lasso selector = SelectFromModel(Lasso(alpha=0.1)) ``` ### 第 12 章 计算学习理论 ``` 核心概念: - PAC 学习:可能近似正确 - 样本复杂度:需要多少样本 - VC 维:假设空间复杂度 - 泛化误差上界:E(f) ≤ Ê(f) + Ω(H) ``` ### 第 13 章 半监督学习 ```python from sklearn.semi_supervised import LabelPropagation, LabelSpreading # 标签传播 lp = LabelPropagation(kernel='knn') lp.fit(X, y_partial) # y_partial 包含 -1(未标记) # 标签传播(正则化) ls = LabelSpreading(kernel='rbf', alpha=0.2) ``` ### 第 14 章 概率图模型 ``` HMM 三要素: 1. 初始概率分布 π 2. 状态转移概率矩阵 A 3. 观测概率矩阵 B 三大问题: 1. 概率计算:前向 - 后向算法 2. 学习:Baum-Welch 算法 3. 预测:Viterbi 算法 ``` ### 第 15 章 规则学习 ``` 序贯覆盖算法: 1. 从空规则开始 2. 添加条件,提高规则质量 3. 覆盖的样本移除 4. 重复直到覆盖所有样本 RIPPER 算法: 1. 生长阶段 2. 剪枝阶段 ``` ### 第 16 章 强化学习 ```python # Q-Learning # Q(s,a) ← Q(s,a) + α[r + γ max_a' Q(s',a') - Q(s,a)] class QLearning: def __init__(self, n_states, n_actions, alpha=0.1, gamma=0.9, epsilon=0.1): self.q_table = np.zeros((n_states, n_actions)) self.alpha = alpha self.gamma = gamma self.epsilon = epsilon def choose_action(self, state): if np.random.random() < self.epsilon: return np.random.randint(self.n_actions) return np.argmax(self.q_table[state]) def update(self, state, action, reward, next_state): best_next = np.argmax(self.q_table[next_state]) td_target = reward + self.gamma * self.q_table[next_state][best_next] td_error = td_target - self.q_table[state][action] self.q_table[state][action] += self.alpha * td_error ``` --- ## 📚 完整学习路线 ### 基础篇(第 1-4 章) 1. 绪论 → 基本概念 2. 模型评估 → 性能度量 3. 线性模型 → 基础算法 4. 决策树 → 树模型入门 ### 进阶篇(第 5-7 章) 5. 神经网络 → 深度学习基础 6. SVM → 统计学习 7. 贝叶斯 → 概率方法 ### 高级篇(第 8-10 章) 8. 集成学习 → Bagging/Boosting 9. 聚类 → 无监督学习 10. 降维 → 特征处理 ### 专题篇(第 11-16 章) 11. 特征选择 12. 计算学习理论 13. 半监督学习 14. 概率图模型 15. 规则学习 16. 强化学习 --- ## 🎯 蓝桥杯重点 **算法竞赛常考:** - ✅ 决策树(信息增益、基尼指数) - ✅ KMeans 聚类(手肘法) - ✅ PCA 降维(协方差矩阵) - ✅ KNN 分类(距离度量) - ✅ 线性回归/逻辑回归 - ✅ 集成学习(随机森林、GBDT) **面试常问:** - ✅ 过拟合 vs 欠拟合 - ✅ 交叉验证 - ✅ 精确率/召回率/F1 - ✅ L1/L2 正则化 - ✅ Bagging vs Boosting - ✅ SVM 核函数 --- **整理完成!** 🎉 **文档大小:** ~80KB **涵盖章节:** 第 1-16 章完整知识点 **特点:** 理论推导 + 数学公式 + 代码实现 + 直观理解 **下载链接:** http://129.211.3.54:3923/files/机器学习西瓜书_完整知识点.md 继续加油蓝桥杯备战!🌱💪