9人工智能:一种现代方法(第4版)

机器学习

从数据中学习

监督学习无监督学习神经网络深度学习

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第9章:机器学习(Machine Learning)

一、导读

1.1 本章学习目标

第9章介绍机器学习——让AI系统从数据中自动学习改进的技术。与前面章节中手工设计算法不同,机器学习让系统自己从数据中发现模式和规律。这是现代AI最活跃的领域,也是深度学习、大语言模型等技术的基础。

本章的核心学习目标包括:

  • 理解机器学习的基本概念:监督学习、无监督学习、强化学习的区别和适用场景。
  • 掌握监督学习的基本方法:线性回归、逻辑回归、决策树、K近邻。
  • 理解模型评估:训练集/测试集、交叉验证、过拟合与欠拟合。
  • 掌握神经网络基础:感知机、多层网络、反向传播算法。
  • 了解深度学习:CNN、RNN、Transformer 的基本概念。
  • 了解强化学习:通过与环境交互学习最优策略。

1.2 为什么第九章如此重要

机器学习是现代AI的核心驱动力。从图像识别到语音识别,从自然语言处理到推荐系统,机器学习技术无处不在。特别是深度学习,它在过去十年中彻底改变了AI的面貌。

理解机器学习的基本原理,不仅是学习AI的必经之路,也是理解当前AI技术(如ChatGPT、自动驾驶)的基础。本章将从最基本的概念开始,逐步深入到神经网络和深度学习。

1.3 核心问题引导

在阅读本章时,请带着以下问题思考:

为什么需要机器学习? 手工设计算法有什么局限性?机器学习如何解决这些问题?

过拟合的本质:为什么模型在训练数据上表现好但在测试数据上表现差?如何避免?

神经网络的威力:为什么多层神经网络比浅层网络更强大?深度学习的"深度"指什么?

反向传播的原理:梯度如何从输出层传播到输入层?链式法则在其中扮演什么角色?

强化学习与监督学习的区别:为什么有些问题需要强化学习而不是监督学习?


二、核心概念详解

2.1 机器学习基础

2.1.1 学习的定义

学习是通过经验(数据)改进系统性能的过程。Tom Mitchell 给出了一个经典的定义:

如果一个计算机程序在任务 T 上的性能 P 随着经验 E 而改进,那么我们说这个程序在学习。

python
class Learner:
    def __init__(self):
        self.model = None
        self.experience = []

    def learn(self, data):
        self.experience.extend(data)
        self.model = self.fit(self.experience)

    def fit(self, data):
        raise NotImplementedError

    def predict(self, x):
        return self.model(x)

# 简单的记忆型学习者
class MemorizingLearner(Learner):
    def fit(self, data):
        memory = {tuple(x): y for x, y in data}
        return lambda x: memory.get(tuple(x), None)

data = [([1, 2], 3), ([2, 3], 5), ([3, 4], 7)]
learner = MemorizingLearner()
learner.learn(data)
print(f"预测 [1,2]: {learner.predict([1, 2])}")

2.1.2 监督学习

监督学习从标注数据中学习输入到输出的映射关系。

python
import numpy as np

class SupervisedLearner:
    def __init__(self):
        self.X_train = None
        self.y_train = None

    def fit(self, X, y):
        self.X_train = np.array(X)
        self.y_train = np.array(y)

    def predict(self, X):
        raise NotImplementedError

    def score(self, X, y):
        predictions = self.predict(X)
        return np.mean(predictions == np.array(y))

# 线性回归
class LinearRegression(SupervisedLearner):
    def fit(self, X, y):
        super().fit(X, y)
        X = np.array(X)
        y = np.array(y)
        X_b = np.c_[np.ones(len(X)), X]
        self.weights = np.linalg.lstsq(X_b, y, rcond=None)[0]

    def predict(self, X):
        X = np.array(X)
        X_b = np.c_[np.ones(len(X)), X]
        return X_b @ self.weights

# 测试
X = np.array([[1], [2], [3], [4], [5]])
y = np.array([2, 4, 6, 8, 10])
lr = LinearRegression()
lr.fit(X, y)
print(f"预测 X=6: {lr.predict([[6]])[0]:.2f}")

2.1.3 无监督学习

无监督学习从无标注数据中发现结构。

python
class KMeans:
    def __init__(self, k=3, max_iter=100):
        self.k = k
        self.max_iter = max_iter
        self.centroids = None

    def fit(self, X):
        X = np.array(X)
        n = len(X)
        indices = np.random.choice(n, self.k, replace=False)
        self.centroids = X[indices].copy()

        for _ in range(self.max_iter):
            labels = self._assign_clusters(X)
            new_centroids = np.array([
                X[labels == i].mean(axis=0) if np.sum(labels == i) > 0
                else self.centroids[i]
                for i in range(self.k)
            ])
            if np.allclose(self.centroids, new_centroids):
                break
            self.centroids = new_centroids

    def _assign_clusters(self, X):
        distances = np.array([
            np.linalg.norm(X - c, axis=1) for c in self.centroids
        ])
        return np.argmin(distances, axis=0)

    def predict(self, X):
        return self._assign_clusters(np.array(X))

# 测试 K-Means
np.random.seed(42)
X = np.vstack([
    np.random.randn(50, 2) + [0, 0],
    np.random.randn(50, 2) + [5, 5],
    np.random.randn(50, 2) + [10, 0],
])
kmeans = KMeans(k=3)
kmeans.fit(X)
print(f"聚类中心:\n{kmeans.centroids}")

2.1.4 强化学习

强化学习通过与环境交互学习最优策略。

python
class QLearning:
    def __init__(self, states, actions, lr=0.1, gamma=0.9, epsilon=0.1):
        self.q_table = {s: {a: 0.0 for a in actions} for s in states}
        self.lr = lr
        self.gamma = gamma
        self.epsilon = epsilon
        self.actions = actions

    def choose_action(self, state):
        if np.random.random() < self.epsilon:
            return np.random.choice(self.actions)
        return max(self.q_table[state], key=self.q_table[state].get)

    def update(self, state, action, reward, next_state):
        old_q = self.q_table[state][action]
        max_next_q = max(self.q_table[next_state].values())
        new_q = old_q + self.lr * (reward + self.gamma * max_next_q - old_q)
        self.q_table[state][action] = new_q

# 简单的网格世界
class GridWorld:
    def __init__(self, size=5):
        self.size = size
        self.pos = (0, 0)
        self.goal = (size-1, size-1)

    def step(self, action):
        x, y = self.pos
        if action == 'up' and y > 0: y -= 1
        elif action == 'down' and y < self.size-1: y += 1
        elif action == 'left' and x > 0: x -= 1
        elif action == 'right' and x < self.size-1: x += 1
        self.pos = (x, y)
        reward = 10 if self.pos == self.goal else -1
        return self.pos, reward, self.pos == self.goal

    def reset(self):
        self.pos = (0, 0)
        return self.pos

env = GridWorld()
states = [(x, y) for x in range(5) for y in range(5)]
actions = ['up', 'down', 'left', 'right']
agent = QLearning(states, actions)

for episode in range(1000):
    state = env.reset()
    done = False
    while not done:
        action = agent.choose_action(state)
        next_state, reward, done = env.step(action)
        agent.update(state, action, reward, next_state)
        state = next_state

print(f"Q-table 示例 (0,0): {agent.q_table[(0,0)]}")

2.2 模型评估

2.2.1 训练集与测试集

python
def train_test_split(X, y, test_ratio=0.2):
    n = len(X)
    indices = np.random.permutation(n)
    test_size = int(n * test_ratio)
    test_idx = indices[:test_size]
    train_idx = indices[test_size:]
    return X[train_idx], X[test_idx], y[train_idx], y[test_idx]

# 交叉验证
def cross_validate(model_class, X, y, k=5):
    n = len(X)
    fold_size = n // k
    scores = []

    for i in range(k):
        test_start = i * fold_size
        test_end = test_start + fold_size
        X_test = X[test_start:test_end]
        y_test = y[test_start:test_end]
        X_train = np.vstack([X[:test_start], X[test_end:]])
        y_train = np.concatenate([y[:test_start], y[test_end:]])

        model = model_class()
        model.fit(X_train, y_train)
        scores.append(model.score(X_test, y_test))

    return np.mean(scores), np.std(scores)

2.2.2 过拟合与欠拟合

python
class PolynomialRegression:
    def __init__(self, degree=1):
        self.degree = degree
        self.coeffs = None

    def fit(self, X, y):
        X_poly = self._poly_features(X)
        self.coeffs = np.linalg.lstsq(X_poly, y, rcond=None)[0]

    def predict(self, X):
        X_poly = self._poly_features(X)
        return X_poly @ self.coeffs

    def _poly_features(self, X):
        X = np.array(X).flatten()
        return np.column_stack([X**i for i in range(self.degree + 1)])

# 演示过拟合
np.random.seed(42)
X = np.linspace(0, 1, 20)
y = np.sin(2 * np.pi * X) + np.random.randn(20) * 0.3

for degree in [1, 3, 10]:
    model = PolynomialRegression(degree)
    model.fit(X, y)
    train_pred = model.predict(X)
    train_mse = np.mean((train_pred - y) ** 2)
    print(f"Degree {degree}: Train MSE = {train_mse:.4f}")

2.3 神经网络

2.3.1 感知机

python
class Perceptron:
    def __init__(self, lr=0.01, n_iter=100):
        self.lr = lr
        self.n_iter = n_iter
        self.weights = None
        self.bias = None

    def fit(self, X, y):
        X = np.array(X)
        y = np.array(y)
        self.weights = np.zeros(X.shape[1])
        self.bias = 0.0

        for _ in range(self.n_iter):
            for xi, yi in zip(X, y):
                prediction = self.predict(xi)
                update = self.lr * (yi - prediction)
                self.weights += update * xi
                self.bias += update

    def predict(self, X):
        linear = np.dot(X, self.weights) + self.bias
        return np.where(linear >= 0, 1, 0)

# AND 门
X_and = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y_and = np.array([0, 0, 0, 1])
perceptron = Perceptron()
perceptron.fit(X_and, y_and)
print(f"AND 门预测: {perceptron.predict(X_and)}")

2.3.2 多层神经网络

python
class NeuralNetwork:
    def __init__(self, layers):
        self.layers = layers
        self.weights = []
        self.biases = []
        for i in range(len(layers) - 1):
            w = np.random.randn(layers[i], layers[i+1]) * 0.01
            b = np.zeros((1, layers[i+1]))
            self.weights.append(w)
            self.biases.append(b)

    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]
        self.z_values = []
        current = X
        for i in range(len(self.weights)):
            z = current @ self.weights[i] + self.biases[i]
            self.z_values.append(z)
            current = self.sigmoid(z)
            self.activations.append(current)
        return current

    def backward(self, y, lr=0.01):
        m = len(y)
        y = y.reshape(-1, 1) if len(y.shape) == 1 else y
        delta = self.activations[-1] - y

        for i in reversed(range(len(self.weights))):
            dw = self.activations[i].T @ delta / m
            db = np.sum(delta, axis=0, keepdims=True) / m
            if i > 0:
                delta = delta @ self.weights[i].T * self.sigmoid_derivative(self.z_values[i-1])
            self.weights[i] -= lr * dw
            self.biases[i] -= lr * db

    def fit(self, X, y, epochs=1000, lr=0.01):
        for epoch in range(epochs):
            output = self.forward(X)
            self.backward(y, lr)
            if epoch % 100 == 0:
                loss = np.mean((output - y.reshape(-1, 1)) ** 2)
                if epoch % 500 == 0:
                    print(f"Epoch {epoch}, Loss: {loss:.6f}")

    def predict(self, X):
        return self.forward(X)

# XOR 问题
X_xor = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y_xor = np.array([[0], [1], [1], [0]])
nn = NeuralNetwork([2, 4, 1])
nn.fit(X_xor, y_xor, epochs=2000, lr=0.1)
print(f"XOR 预测:\n{nn.predict(X_xor)}")

2.4 深度学习简介

2.4.1 卷积神经网络(CNN)

python
class Conv2D:
    def __init__(self, in_channels, out_channels, kernel_size):
        self.kernel_size = kernel_size
        self.weights = np.random.randn(out_channels, in_channels, kernel_size, kernel_size) * 0.01
        self.bias = np.zeros(out_channels)

    def forward(self, X):
        batch, c, h, w = X.shape
        out_h = h - self.kernel_size + 1
        out_w = w - self.kernel_size + 1
        output = np.zeros((batch, len(self.weights), out_h, out_w))

        for i in range(out_h):
            for j in range(out_w):
                patch = X[:, :, i:i+self.kernel_size, j:j+self.kernel_size]
                for k in range(len(self.weights)):
                    output[:, k, i, j] = np.sum(patch * self.weights[k], axis=(1, 2, 3)) + self.bias[k]
        return output

# 简单的卷积操作
conv = Conv2D(1, 2, 3)
X = np.random.randn(1, 1, 5, 5)
output = conv.forward(X)
print(f"卷积输出形状: {output.shape}")

2.4.2 循环神经网络(RNN)

python
class SimpleRNN:
    def __init__(self, input_size, hidden_size, output_size):
        self.hidden_size = hidden_size
        self.Wxh = np.random.randn(hidden_size, input_size) * 0.01
        self.Whh = np.random.randn(hidden_size, hidden_size) * 0.01
        self.Why = np.random.randn(output_size, hidden_size) * 0.01
        self.bh = np.zeros(hidden_size)
        self.by = np.zeros(output_size)

    def forward(self, inputs):
        h = np.zeros(self.hidden_size)
        self.h_states = [h]
        outputs = []

        for x in inputs:
            h = np.tanh(self.Wxh @ x + self.Whh @ h + self.bh)
            self.h_states.append(h)
            y = self.Why @ h + self.by
            outputs.append(y)

        return outputs

# 简单的序列预测
rnn = SimpleRNN(input_size=3, hidden_size=4, output_size=2)
inputs = [np.random.randn(3) for _ in range(5)]
outputs = rnn.forward(inputs)
print(f"RNN 输出数量: {len(outputs)}")

2.4.3 Transformer 与注意力机制

python
class SelfAttention:
    def __init__(self, d_model, n_heads):
        self.d_model = d_model
        self.n_heads = n_heads
        self.d_k = d_model // n_heads
        self.W_q = np.random.randn(d_model, d_model) * 0.01
        self.W_k = np.random.randn(d_model, d_model) * 0.01
        self.W_v = np.random.randn(d_model, d_model) * 0.01

    def forward(self, X):
        Q = X @ self.W_q
        K = X @ self.W_k
        V = X @ self.W_v

        scores = Q @ K.T / np.sqrt(self.d_k)
        weights = np.exp(scores - np.max(scores, axis=-1, keepdims=True))
        weights = weights / np.sum(weights, axis=-1, keepdims=True)

        return weights @ V

# 自注意力示例
attention = SelfAttention(d_model=8, n_heads=2)
X = np.random.randn(4, 8)
output = attention.forward(X)
print(f"注意力输出形状: {output.shape}")

三、实战案例

3.1 案例一:完整的机器学习流程

python
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

class MLWorkflow:
    def __init__(self):
        self.scaler = StandardScaler()
        self.model = None

    def load_data(self):
        iris = load_iris()
        X, y = iris.data, iris.target
        return train_test_split(X, y, test_size=0.3, random_state=42)

    def preprocess(self, X_train, X_test):
        X_train_scaled = self.scaler.fit_transform(X_train)
        X_test_scaled = self.scaler.transform(X_test)
        return X_train_scaled, X_test_scaled

    def train(self, X, y):
        self.model = KNN(k=5)
        self.model.fit(X, y)

    def evaluate(self, X, y):
        accuracy = self.model.score(X, y)
        print(f"准确率: {accuracy:.4f}")
        return accuracy

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

    def fit(self, X, y):
        self.X_train = np.array(X)
        self.y_train = np.array(y)

    def predict(self, X):
        predictions = []
        for x in X:
            distances = np.sqrt(np.sum((self.X_train - x) ** 2, axis=1))
            k_indices = np.argsort(distances)[:self.k]
            k_labels = self.y_train[k_indices]
            from collections import Counter
            predictions.append(Counter(k_labels).most_common(1)[0][0])
        return np.array(predictions)

workflow = MLWorkflow()
X_train, X_test, y_train, y_test = workflow.load_data()
X_train, X_test = workflow.preprocess(X_train, X_test)
workflow.train(X_train, y_train)
workflow.evaluate(X_test, y_test)

四、本章小结

本章介绍了机器学习的核心概念和方法。监督学习从标注数据中学习映射关系,无监督学习从无标注数据中发现结构,强化学习通过与环境交互学习最优策略。神经网络是机器学习的核心模型,反向传播是训练神经网络的关键算法。

核心知识点回顾:

  • 监督学习、无监督学习、强化学习的区别
  • 过拟合与欠拟合,正则化的作用
  • 感知机是简单的线性分类器
  • 多层神经网络通过反向传播训练
  • CNN 适合图像处理,RNN 适合序列数据
  • Transformer 的自注意力机制是现代 NLP 的基础

五、思考与练习

思考题

偏差-方差权衡:模型复杂度过高导致过拟合,过低导致欠拟合。如何找到平衡点?

深度学习的本质:为什么深度网络比浅层网络更强大?"深度"带来了什么优势?

编程练习

实现决策树:用基尼指数实现 CART 决策树算法。

实现 SVM:用梯度下降实现线性支持向量机。

实现简单的 GAN:生成对抗网络的基本实现。

9/13机器学习
0%