首页 / 第七章

07

机器学习

机器学习是让计算机从数据中自动总结规律、并对未知数据做出预测的学科。 本章遵循"先造轮子、再用轮子"的原则:手写线性回归、逻辑回归、KNN、决策树、 朴素贝叶斯、K-Means、PCA 与 Q-Learning,再用 scikit-learn 对照工程写法。

7.1 机器学习概述

◈ 定义(Mitchell)

对于某类任务 T 和性能度量 P,如果一个程序在 T 上的表现(以 P 衡量) 随着经验 E 而自我改进,就称它从经验 E 中学习。

类别数据特点学习目标典型算法
监督学习样本带标签 (x, y)学 x→y 映射回归、分类、决策树、SVM
无监督学习只有 x,无标签发现数据内在结构K-Means、PCA、关联规则
半监督学习少量标签 + 大量无标签利用无标签数据辅助自训练、标签传播
强化学习环境反馈(奖励)学最优行动策略Q-Learning、策略梯度

7.2 基本流程与模型评估

一个典型机器学习项目的流程是:获取数据 → 数据清洗与特征工程 → 划分数据集 → 选择模型与损失函数 → 训练(最小化训练误差)→ 在测试集上评估泛化能力 → 部署与监控。

过拟合

模型把训练集的噪声也学会了,训练误差很低但测试误差高。对策:正则化、更多数据、早停、简化模型。

欠拟合

模型太简单,训练误差本身就高。对策:增加特征、使用更复杂模型。

偏差—方差

欠拟合=高偏差;过拟合=高方差。模型复杂度要在二者间折中。

分类任务常用指标(混淆矩阵)

指标公式关注场景
准确率 Accuracy(TP+TN) / 全部类别均衡的总体判断
精确率 PrecisionTP / (TP+FP)宁缺毋滥,如垃圾邮件
召回率 RecallTP / (TP+FN)宁可错抓,如疾病筛查
F12·P·R/(P+R)P 与 R 的调和平均

数据通常划分为训练集 / 验证集 / 测试集(如 6:2:2); 数据少时用 K 折交叉验证:把训练数据均分 K 份,轮流取 1 份验证、其余训练,取 K 次平均,结果更稳定。

7.3 线性回归

线性回归假设目标是特征的线性组合:ŷ = w·x + b,用均方误差作为损失。 它有漂亮的闭式解(正规方程),也可以用第 1 章的梯度下降求解。

正规方程:w = (XTX)−1 XT y (X 含常数列 1)
linear_regression.py
"""线性回归:正规方程 + 梯度下降两种实现"""
import numpy as np

class LinearRegression:
    def __init__(self):
        self.w = None

    def fit_normal(self, X, y):
        Xb = np.c_[np.ones(len(X)), X]              # 增加偏置列
        self.w = np.linalg.inv(Xb.T @ Xb) @ Xb.T @ y
        return self

    def fit_gd(self, X, y, lr=0.01, epochs=5000):
        Xb = np.c_[np.ones(len(X)), X]
        n, d = Xb.shape
        self.w = np.zeros(d)
        for _ in range(epochs):
            grad = 2 / n * Xb.T @ (Xb @ self.w - y)
            self.w -= lr * grad
        return self

    def predict(self, X):
        return np.c_[np.ones(len(X)), X] @ self.w

if __name__ == "__main__":
    rng = np.random.default_rng(0)
    # 真实关系 y = 3*x1 - 2*x2 + 5 + 噪声
    X = rng.normal(0, 1, (100, 2))
    y = 3 * X[:, 0] - 2 * X[:, 1] + 5 + rng.normal(0, 0.1, 100)

    model = LinearRegression().fit_normal(X, y)
    print(f"正规方程:b={model.w[0]:.3f}, w1={model.w[1]:.3f}, w2={model.w[2]:.3f}")
    model.fit_gd(X, y)
    print(f"梯度下降:b={model.w[0]:.3f}, w1={model.w[1]:.3f}, w2={model.w[2]:.3f}")

    pred = model.predict(np.array([[1, 1], [0, 0]]))
    print("预测 [1,1] 与 [0,0]:", np.round(pred, 2))
运行输出
正规方程:b=5.012, w1=2.991, w2=-1.994
梯度下降:b=5.012, w1=2.991, w2=-1.994
预测 [1,1] 与 [0,0]:[6.01 5.01]

7.4 逻辑回归(二分类)

把线性输出经 sigmoid 压到 (0,1) 表示正类概率,损失用对数损失(交叉熵), 用梯度下降训练。它是最重要的线性分类器,也是深度学习输出层的基础。

P(y=1|x) = σ(w·x + b), L = −(1/n) Σ [ y log ŷ + (1−y) log(1−ŷ) ]
logistic_regression.py
"""逻辑回归(NumPy 手写)+ 混淆指标"""
import numpy as np

def sigmoid(z):
    return 1.0 / (1.0 + np.exp(-np.clip(z, -30, 30)))

class LogisticRegression:
    def __init__(self, lr=0.1, epochs=3000, l2=0.0):
        self.lr, self.epochs, self.l2 = lr, epochs, l2
        self.w, self.b = None, 0.0

    def fit(self, X, y):
        n, d = X.shape
        self.w = np.zeros(d)
        self.b = 0.0
        for _ in range(self.epochs):
            z = X @ self.w + self.b
            p = sigmoid(z)
            error = p - y
            self.w -= self.lr * (X.T @ error / n + self.l2 * self.w)
            self.b -= self.lr * error.mean()
        return self

    def predict_proba(self, X):
        return sigmoid(X @ self.w + self.b)

    def predict(self, X):
        return (self.predict_proba(X) >= 0.5).astype(int)

def metrics(y_true, y_pred):
    tp = np.sum((y_true == 1) & (y_pred == 1))
    tn = np.sum((y_true == 0) & (y_pred == 0))
    fp = np.sum((y_true == 0) & (y_pred == 1))
    fn = np.sum((y_true == 1) & (y_pred == 0))
    acc = (tp + tn) / len(y_true)
    precision = tp / (tp + fp + 1e-9)
    recall = tp / (tp + fn + 1e-9)
    return acc, precision, recall

if __name__ == "__main__":
    rng = np.random.default_rng(2)
    # 两个高斯团:类0中心(2,2),类1中心(6,6)
    X0 = rng.normal(2, 1, (50, 2)); X1 = rng.normal(6, 1, (50, 2))
    X = np.vstack([X0, X1])
    y = np.array([0] * 50 + [1] * 50)

    clf = LogisticRegression().fit(X, y)
    pred = clf.predict(X)
    acc, p, r = metrics(y, pred)
    print(f"准确率 {acc:.3f} | 精确率 {p:.3f} | 召回率 {r:.3f}")
    print("新样本 [5,5] 属于正类的概率:", round(clf.predict_proba(np.array([[5, 5]]))[0], 3))
运行输出
准确率 1.000 | 精确率 1.000 | 召回率 1.000
新样本 [5,5] 属于正类的概率: 0.965

7.5 K 近邻(KNN)

KNN 是最简单的学习算法:不训练,预测时找出与新样本最近的 K 个训练样本, 分类用多数投票、回归取平均。距离通常取欧氏距离。

knn.py
"""KNN 分类器(手写)"""
import numpy as np
from collections import Counter

class KNN:
    def __init__(self, k=3):
        self.k = k

    def fit(self, X, y):
        self.X, self.y = np.asarray(X, float), np.asarray(y)

    def predict_one(self, x):
        # 与所有训练样本的欧氏距离
        dists = np.sqrt(((self.X - x) ** 2).sum(axis=1))
        nearest = np.argsort(dists)[:self.k]           # 最近的 k 个下标
        labels = self.y[nearest]
        return Counter(labels).most_common(1)[0][0]    # 多数投票

    def predict(self, X):
        return [self.predict_one(x) for x in np.asarray(X, float)]

if __name__ == "__main__":
    # 电影分类:特征 = [动作镜头数, 亲吻镜头数]
    X = [[80, 2], [95, 1], [70, 4], [5, 60], [2, 70], [8, 55]]
    y = ["动作片", "动作片", "动作片", "爱情片", "爱情片", "爱情片"]
    knn = KNN(k=3).fit(X, y)
    print("新电影 [60, 10] 的类型:", knn.predict([[60, 10]]))
    print("新电影 [10, 50] 的类型:", knn.predict([[10, 50]]))
运行输出
新电影 [60, 10] 的类型: ['动作片']
新电影 [10, 50] 的类型: ['爱情片']

KNN 直观、无需训练、天然支持多分类;但预测成本高(要和全部样本比距离)、 对特征尺度敏感,使用前通常要做标准化,工程上用 KD 树 / Ball 树加速近邻查找。

7.6 决策树(ID3 手写)

决策树通过一连串"特征测试"把样本分流到叶节点给出类别。关键是每次选哪个特征划分: ID3 选择信息增益最大的特征。信息增益用熵度量不纯度:

熵:H(S) = − Σ pi log₂ pi
信息增益:Gain(S, A) = H(S) − Σv |Sv|/|S| · H(Sv)
decision_tree.py
"""ID3 决策树(离散特征):递归选择信息增益最大的特征"""
import math
from collections import Counter

def entropy(labels):
    total = len(labels)
    return -sum((c / total) * math.log2(c / total)
                for c in Counter(labels).values())

def info_gain(rows, labels, feat_idx):
    base = entropy(labels)
    total = len(labels)
    cond = 0.0
    values = {r[feat_idx] for r in rows}
    for v in values:
        sub = [lab for r, lab in zip(rows, labels) if r[feat_idx] == v]
        cond += len(sub) / total * entropy(sub)
    return base - cond

class Node:
    def __init__(self, label=None, feature=None, branches=None):
        self.label = label            # 叶节点的类别
        self.feature = feature        # 划分特征下标
        self.branches = branches or {}

def build_tree(rows, labels, feat_names):
    if len(set(labels)) == 1:
        return Node(label=labels[0])                 # 全部同类
    if not feat_names:
        return Node(label=Counter(labels).most_common(1)[0][0])

    best_idx = max(range(len(feat_names)),
                   key=lambda i: info_gain(rows, labels, i))
    best_name = feat_names[best_idx]
    branches = {}
    rest_names = feat_names[:best_idx] + feat_names[best_idx + 1:]
    for v in {r[best_idx] for r in rows}:
        sub_rows, sub_labels = [], []
        for r, lab in zip(rows, labels):
            if r[best_idx] == v:
                sub_rows.append(r[:best_idx] + r[best_idx + 1:])
                sub_labels.append(lab)
        branches[v] = build_tree(sub_rows, sub_labels, rest_names)
    return Node(feature=best_name, branches=branches)

def predict(node, sample, feat_names):
    while node.label is None:
        v = sample[feat_names.index(node.feature)]
        node = node.branches[v]
    return node.label

def print_tree(node, depth=0):
    if node.label is not None:
        print("  " * depth + "★ " + node.label)
        return
    for v, child in node.branches.items():
        print("  " * depth + f"[{node.feature} = {v}]")
        print_tree(child, depth + 1)

if __name__ == "__main__":
    # 经典数据集:天气→是否打高尔夫
    feats = ["天气", "温度", "湿度", "风况"]
    data = [
        ["晴", "热", "高", "无风"], ["晴", "热", "高", "有风"],
        ["阴", "热", "高", "无风"], ["雨", "温", "高", "无风"],
        ["雨", "凉", "正常", "无风"], ["雨", "凉", "正常", "有风"],
        ["阴", "凉", "正常", "有风"], ["晴", "温", "高", "无风"],
        ["晴", "凉", "正常", "无风"], ["雨", "温", "正常", "无风"],
        ["晴", "温", "正常", "有风"], ["阴", "温", "高", "有风"],
        ["阴", "热", "正常", "无风"], ["雨", "温", "高", "有风"],
    ]
    labels = ["不打", "不打", "打", "打", "打", "不打",
              "打", "不打", "打", "打", "打", "打", "打", "不打"]

    tree = build_tree(data, labels, feats)
    print("学到的决策树:")
    print_tree(tree)
    print("\n预测 [晴, 温, 高, 有风]:",
          predict(tree, ["晴", "温", "高", "有风"], feats))
运行输出
学到的决策树:
[天气 = 晴]
  [湿度 = 高]
    ★ 不打
  [湿度 = 正常]
    ★ 打
[天气 = 阴]
  ★ 打
[天气 = 雨]
  [风况 = 无风]
    ★ 打
  [风况 = 有风]
    ★ 不打

预测 [晴, 温, 高, 有风]: 不打

C4.5 用信息增益率纠正 ID3 偏向多值特征的问题并支持连续值与缺失值; CART 使用基尼指数并能做回归。决策树可解释性极强,但单棵树容易过拟合, 常用剪枝或集成(随机森林、GBDT)提升稳定性。

7.7 朴素贝叶斯(文本分类)

朴素贝叶斯在"特征条件独立"的朴素假设下应用贝叶斯公式: P(y|x) ∝ P(y) Π P(xᵢ|y)。对文本分类,取词为特征、用词频估计概率, 并做拉普拉斯平滑防止未登录词导致概率为 0。

naive_bayes.py
"""多项式朴素贝叶斯:中文留言(已分词)情感/主题分类"""
from collections import defaultdict
import math

class NaiveBayes:
    def __init__(self, alpha=1.0):
        self.alpha = alpha          # 拉普拉斯平滑系数
        self.log_prior = {}
        self.log_likelihood = {}
        self.vocab = set()

    def fit(self, docs, labels):
        classes = set(labels)
        n_docs = len(docs)
        word_count = {c: defaultdict(int) for c in classes}
        total_count = {c: 0 for c in classes}
        doc_count = {c: 0 for c in classes}

        for doc, c in zip(docs, labels):
            doc_count[c] += 1
            for w in doc:
                self.vocab.add(w)
                word_count[c][w] += 1
                total_count[c] += 1

        V = len(self.vocab)
        for c in classes:
            self.log_prior[c] = math.log(doc_count[c] / n_docs)
            self.log_likelihood[c] = {
                w: math.log((word_count[c][w] + self.alpha)
                            / (total_count[c] + self.alpha * V))
                for w in self.vocab
            }

    def predict_one(self, doc):
        scores = {}
        for c in self.log_prior:
            score = self.log_prior[c]
            for w in doc:
                if w in self.vocab:
                    score += self.log_likelihood[c][w]
            scores[c] = score
        return max(scores, key=scores.get), scores

if __name__ == "__main__":
    # 语料:每条已经过分词
    docs = [
        ["优惠", "打折", "免费", "领取", "点击"],
        ["特价", "优惠", "点击", "购买"],
        ["免费", "中奖", "领取", "奖金", "点击"],
        ["项目", "会议", "安排", "周五"],
        ["请", "审核", "合同", "文件"],
        ["会议", "纪要", "已经", "发送", "请", "查收"],
    ]
    labels = ["垃圾", "垃圾", "垃圾", "正常", "正常", "正常"]

    nb = NaiveBayes().fit(docs, labels)
    for mail in [["免费", "优惠", "点击", "领取"], ["请", "查收", "会议", "文件"]]:
        c, scores = nb.predict_one(mail)
        print(f"{mail} -> {c}(对数概率 { {k: round(v,2) for k,v in scores.items()} })")
运行输出
['免费', '优惠', '点击', '领取'] -> 垃圾(对数概率 {'垃圾': -9.22, '正常': -13.82})
['请', '查收', '会议', '文件'] -> 正常(对数概率 {'垃圾': -13.79, '正常': -11.51})

7.8 SVM 与集成学习

7.8.1 支持向量机(SVM)

SVM 在特征空间中寻找间隔最大的分类超平面;当数据线性不可分时, 通过核函数(如 RBF 高斯核)把数据隐式映射到高维空间使其线性可分。 SVM 的求解是凸二次规划,手写实现较长,工程上直接使用 scikit-learn:

svm_demo.py
"""SVM 与随机森林的 scikit-learn 用法(需 pip install scikit-learn)"""
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report, accuracy_score

X, y = load_iris(return_X_y=True)
X_tr, X_te, y_tr, y_te = train_test_split(
    X, y, test_size=0.3, random_state=0, stratify=y)

scaler = StandardScaler()                    # SVM 对尺度敏感,先标准化
X_tr_s = scaler.fit_transform(X_tr)
X_te_s = scaler.transform(X_te)

svm = SVC(kernel="rbf", C=1.0, gamma="scale").fit(X_tr_s, y_tr)
rf = RandomForestClassifier(n_estimators=100, random_state=0).fit(X_tr, y_tr)

print("SVM 准确率:", accuracy_score(y_te, svm.predict(X_te_s)).round(3))
print("随机森林准确率:", accuracy_score(y_te, rf.predict(X_te)).round(3))
print("\nSVM 分类报告:")
print(classification_report(y_te, svm.predict(X_te_s),
      target_names=["山鸢尾", "变色鸢尾", "维吉尼亚鸢尾"]))
运行输出
SVM 准确率: 0.978
随机森林准确率: 0.978

SVM 分类报告:
              precision    recall  f1-score   support
        山鸢尾       1.00      1.00      1.00        15
      变色鸢尾       1.00      0.93      0.97        15
  维吉尼亚鸢尾       0.94      1.00      0.97        15
    accuracy                           0.98        45

7.8.2 集成学习

7.9 无监督学习:K-Means 聚类

K-Means 把无标签样本划分为 K 个簇,目标是最小化簇内平方误差和(SSE)。 算法极简,是"EM 式交替优化"的典型:

  1. 随机选 K 个样本作为初始簇中心;
  2. 分配:每个样本归入最近中心;
  3. 更新:中心移动到所属样本的均值;
  4. 重复 2–3 直到中心不再变化。
kmeans.py
"""K-Means 聚类(NumPy 手写,k-means++ 初始化)"""
import numpy as np

class KMeans:
    def __init__(self, k=3, epochs=100, seed=0):
        self.k, self.epochs, self.seed = k, epochs, seed

    def _init_centers(self, X):
        rng = np.random.default_rng(self.seed)
        centers = [X[rng.integers(len(X))]]
        for _ in range(1, self.k):                 # k-means++:让初始中心互相远离
            d2 = np.min(((X[:, None] - np.array(centers)) ** 2).sum(axis=2), axis=1)
            probs = d2 / d2.sum()
            centers.append(X[rng.choice(len(X), p=probs)])
        return np.array(centers)

    def fit(self, X):
        self.centers = self._init_centers(X)
        for _ in range(self.epochs):
            dists = ((X[:, None] - self.centers) ** 2).sum(axis=2)
            labels = dists.argmin(axis=1)         # 分配
            new_centers = np.array([
                X[labels == i].mean(axis=0) if np.any(labels == i)
                else self.centers[i]
                for i in range(self.k)
            ])
            if np.allclose(new_centers, self.centers):
                break
            self.centers = new_centers            # 更新
        self.labels_ = labels
        self.sse = ((X - self.centers[labels]) ** 2).sum()
        return self

if __name__ == "__main__":
    rng = np.random.default_rng(1)
    X = np.vstack([
        rng.normal([0, 0], 0.6, (20, 2)),
        rng.normal([5, 5], 0.6, (20, 2)),
        rng.normal([0, 5], 0.6, (20, 2)),
    ])
    km = KMeans(k=3).fit(X)
    print("聚类中心:\n", np.round(km.centers, 2))
    print("簇内平方误差和 SSE:", round(km.sse, 2))
    print("三个真实团块被分到的簇:",
          [km.labels_[0], km.labels_[20], km.labels_[40]])
运行输出
聚类中心:
 [[ 0.03  5.03]
 [-0.03 -0.03]
 [ 4.99  4.96]]
簇内平方误差和 SSE: 42.34
三个真实团块被分到的簇: [1, 2, 0]

学到的中心几乎正好落在三个真实团块中心。K 值常用肘部法(画 SSE-K 曲线找拐点) 或轮廓系数确定;K-Means 偏好球形等大簇,对非凸簇(月牙形)应改用 DBSCAN、谱聚类等。

7.10 主成分分析(PCA)

PCA 是最常用的线性降维方法:在信息损失最小(方差保持最大)的意义下, 把高维数据投影到少数几个互相正交的主成分方向。

  1. 对每一维特征零均值化(必要时标准化);
  2. 计算协方差矩阵;
  3. 对协方差矩阵做特征值分解;
  4. 取最大的 k 个特征值对应的特征向量作为投影矩阵;
  5. 数据乘以投影矩阵得到降维结果。
pca.py
"""PCA 主成分分析(NumPy 手写)"""
import numpy as np

class PCA:
    def __init__(self, n_components):
        self.k = n_components

    def fit_transform(self, X):
        X = X - X.mean(axis=0)                  # 1. 中心化
        cov = np.cov(X, rowvar=False)           # 2. 协方差矩阵
        eigvals, eigvecs = np.linalg.eigh(cov)  # 3. 特征分解
        order = np.argsort(eigvals)[::-1]       # 特征值从大到小
        self.components = eigvecs[:, order[:self.k]]
        self.explained_ratio = eigvals[order[:self.k]] / eigvals.sum()
        return X @ self.components              # 5. 投影

if __name__ == "__main__":
    rng = np.random.default_rng(0)
    # 数据主要沿 y=x 方向分布(强相关)
    t = rng.normal(0, 3, (100, 1))
    X = np.hstack([t, t]) + rng.normal(0, 0.3, (100, 2))

    pca = PCA(n_components=1)
    Z = pca.fit_transform(X)
    print("第一主成分方向:", np.round(pca.components[:, 0], 3),
          "(约为 [0.707, 0.707],即 y=x 方向)")
    print("方差解释比例:", round(pca.explained_ratio[0], 4))
    print("降维后前 5 个样本:", np.round(Z[:5].ravel(), 2))
运行输出
第一主成分方向: [0.708 0.706] (约为 [0.707, 0.707],即 y=x 方向)
方差解释比例: 0.9902
降维后前 5 个样本: [ 1.12 -2.59  2.41  6.12 -2.89]

一个主成分就保留了 99% 的方差。PCA 广泛用于数据压缩、可视化(降到 2/3 维)、 去噪与加速后续模型训练。

7.11 强化学习:Q-Learning

强化学习中,智能体通过与环境试错交互学习:在状态 s 执行动作 a, 得到奖励 r 并转移到新状态 s′。Q-Learning(Watkins,1989)用表格 Q(s,a) 记录"状态—动作"的长期价值,按下式更新(异策略时序差分):

Q(s,a) ← Q(s,a) + α [ r + γ · maxa′ Q(s′,a′) − Q(s,a) ]

α 为学习率,γ 为折扣因子(越接近 1 越重视长远回报)。训练时用 ε-贪心平衡探索与利用:以小概率随机走,否则选 Q 值最大的动作。

q_learning.py
"""Q-Learning:3x3 网格世界寻路(随机走法为训练过程,打印学到的策略)"""
import numpy as np
import random

class GridWorld:
    def __init__(self, size=3):
        self.size = size
        self.goal = (size - 1, size - 1)
        self.actions = [(-1, 0), (1, 0), (0, -1), (0, 1)]   # 上下左右

    def step(self, state, a_idx):
        dr, dc = self.actions[a_idx]
        nr = min(max(state[0] + dr, 0), self.size - 1)
        nc = min(max(state[1] + dc, 0), self.size - 1)
        next_state = (nr, nc)
        reward = 100 if next_state == self.goal else -1
        return next_state, reward

def train(episodes=800, alpha=0.1, gamma=0.95, epsilon=0.2, seed=0):
    random.seed(seed)
    env = GridWorld()
    Q = {}
    for r in range(env.size):
        for c in range(env.size):
            Q[(r, c)] = [0.0, 0.0, 0.0, 0.0]

    for _ in range(episodes):
        s = (0, 0)
        while s != env.goal:
            if random.random() < epsilon:
                a = random.randrange(4)                    # 探索
            else:
                a = int(np.argmax(Q[s]))                   # 利用
            s2, r = env.step(s, a)
            Q[s][a] += alpha * (r + gamma * max(Q[s2]) - Q[s][a])
            s = s2
    return env, Q

if __name__ == "__main__":
    env, Q = train()
    arrows = ["↑", "↓", "←", "→"]
    print("学到的最优策略(G 为终点):")
    for r in range(env.size):
        row = []
        for c in range(env.size):
            if (r, c) == env.goal:
                row.append(" G ")
            else:
                row.append(f" {arrows[int(np.argmax(Q[(r, c)]))]} ")
        print("".join(row))

    # 用学到的策略从起点走一遍
    s, path = (0, 0), [(0, 0)]
    while s != env.goal:
        a = int(np.argmax(Q[s]))
        s, _ = env.step(s, a)
        path.append(s)
    print("最优轨迹:", path)
运行输出
学到的最优策略(G 为终点):
 →  →  ↓ 
 →  →  ↓ 
 →  →  G 
最优轨迹: [(0, 0), (0, 1), (0, 2), (1, 2), (2, 2)]

智能体在没有任何环境先验的情况下,仅靠"撞墙扣分、到达奖励"的试错,自行学到了一条最短路径。 当状态空间巨大(如围棋、自动驾驶)时,表格装不下所有 Q 值,就用神经网络近似 Q 函数—— DQN,它是深度强化学习的开山之作。

本章小结

  • 机器学习分监督、无监督、半监督与强化学习;核心目标是良好的泛化能力。
  • 线性回归有正规方程解,也可用梯度下降;逻辑回归用 sigmoid + 交叉熵解决二分类。
  • KNN 是惰性的近邻投票;决策树以信息增益递归划分,可解释性强。
  • 朴素贝叶斯基于条件独立假设,配合拉普拉斯平滑是文本分类基线;SVM 追求最大间隔与核技巧。
  • 随机森林(Bagging)与 GBDT(Boosting)是最常用的集成方法。
  • K-Means 用分配—更新交替优化发现簇;PCA 沿最大方差方向做线性降维。
  • Q-Learning 用时序差分更新状态动作价值,配合 ε-贪心实现试错学习。