深度学习学习教程,从入门到精通,深度生成模型 —— 知识点详解与代码实现(20)

深度生成模型 ------ 知识点详解与代码实现


一、玻尔兹曼机(Boltzmann Machine)

1.1 核心知识点

玻尔兹曼机是一种基于能量的随机生成模型,包含可见单元 (v)和隐藏单元(h)。其联合分布为:

P(v,h)=1Zexp⁡(−E(v,h))P(v, h) = \frac{1}{Z} \exp(-E(v, h))P(v,h)=Z1exp(−E(v,h))

能量函数为:

E(v,h)=−vTWh−vTbv−hTbhE(v, h) = -v^T W h - v^T b_v - h^T b_hE(v,h)=−vTWh−vTbv−hTbh

其中 ZZZ 是配分函数(归一化常数),WWW 为权重矩阵,bvb_vbv、bhb_hbh 为偏置。

核心特征:

  • 使用Gibbs采样从联合分布中采样
  • 使用对比散度(CD) 算法近似训练
  • 所有节点之间可以互相连接(全连接)
  • 训练代价高,难以扩展到大规模问题

1.2 案例代码:基础玻尔兹曼机

python 复制代码
import numpy as np  # 导入numpy库,用于矩阵运算和数值计算

class BoltzmannMachine:
    """
    基础玻尔兹曼机实现
    包含可见层和隐藏层,全连接结构
    """

    def __init__(self, n_visible, n_hidden, learning_rate=0.01):
        """
        初始化玻尔兹曼机参数

        参数:
            n_visible (int): 可见单元的数量
            n_hidden (int): 隐藏单元的数量
            learning_rate (float): 学习率,控制参数更新步长
        """
        self.n_visible = n_visible  # 存储可见单元数
        self.n_hidden = n_hidden  # 存储隐藏单元数
        self.lr = learning_rate  # 存储学习率

        # 使用正态分布初始化权重矩阵,标准差为0.1,形状为(n_visible, n_hidden)
        self.W = np.random.randn(n_visible, n_hidden) * 0.1
        # 初始化可见层偏置为零向量
        self.b_visible = np.zeros(n_visible)
        # 初始化隐藏层偏置为零向量
        self.b_hidden = np.zeros(n_hidden)

    def sigmoid(self, x):
        """
        Sigmoid激活函数,将输入映射到(0,1)区间
        使用clip防止数值溢出

        参数:
            x (np.array): 输入向量或矩阵

        返回:
            np.array: 经过sigmoid变换的输出
        """
        # 将x限制在[-500, 500]之间防止exp溢出
        x = np.clip(x, -500, 500)
        return 1.0 / (1.0 + np.exp(-x))  # sigmoid公式: 1/(1+e^(-x))

    def energy(self, v, h):
        """
        计算能量函数 E(v,h) = -v^T W h - v^T b_v - h^T b_h

        参数:
            v (np.array): 可见层状态
            h (np.array): 隐藏层状态

        返回:
            float: 能量值
        """
        # 计算可见层到隐藏层的交互能量
        vWh = np.dot(np.dot(v, self.W), h)
        # 计算可见层偏置能量
        vb = np.dot(v, self.b_visible)
        # 计算隐藏层偏置能量
        hb = np.dot(h, self.b_hidden)
        return -vWh - vb - hb  # 总能量为各项之和的负值

    def sample_hidden_given_visible(self, v):
        """
        给定可见层状态,计算隐藏层的条件概率并采样
        P(h_j=1|v) = sigmoid(b_hidden_j + sum_i(v_i * W_ij))

        参数:
            v (np.array): 可见层状态向量

        返回:
            tuple: (隐藏层条件概率, 隐藏层采样二值状态)
        """
        # 计算隐藏层的激活值:偏置 + 权重转置乘可见层
        activation = self.b_hidden + np.dot(v, self.W)
        # 通过sigmoid得到隐藏单元为1的概率
        p_h_given_v = self.sigmoid(activation)
        # 使用均匀分布随机采样,与概率比较得到二值状态
        h_sample = (np.random.random(self.n_hidden) < p_h_given_v).astype(float)
        return p_h_given_v, h_sample  # 返回概率和采样结果

    def sample_visible_given_hidden(self, h):
        """
        给定隐藏层状态,计算可见层的条件概率并采样
        P(v_i=1|h) = sigmoid(b_visible_i + sum_j(W_ij * h_j))

        参数:
            h (np.array): 隐藏层状态向量

        返回:
            tuple: (可见层条件概率, 可见层采样二值状态)
        """
        # 计算可见层的激活值:偏置 + 权重乘隐藏层
        activation = self.b_visible + np.dot(self.W, h)
        # 通过sigmoid得到可见单元为1的概率
        p_v_given_h = self.sigmoid(activation)
        # 使用均匀分布随机采样得到二值状态
        v_sample = (np.random.random(self.n_visible) < p_v_given_h).astype(float)
        return p_v_given_v, v_sample  # 返回概率和采样结果

    def gibbs_sampling(self, v_init, n_steps=1):
        """
        Gibbs采样:交替更新隐藏层和可见层,逐步逼近联合分布

        参数:
            v_init (np.array): 初始可见层状态
            n_steps (int): Gibbs采样步数

        返回:
            tuple: (最终可见层概率, 最终可见层状态, 最终隐藏层概率, 最终隐藏层状态)
        """
        v = v_init.copy()  # 复制初始状态,避免修改原始数据
        for step in range(n_steps):  # 执行n_steps步Gibbs采样
            # 步骤1:根据当前可见层采样隐藏层
            p_h, h = self.sample_hidden_given_visible(v)
            # 步骤2:根据当前隐藏层采样可见层
            p_v, v = self.sample_visible_given_hidden(h)
        # 最后再计算一次隐藏层概率和采样(用于梯度计算)
        p_h_final, h_final = self.sample_hidden_given_visible(v)
        return p_v, v, p_h_final, h_final

    def train_step(self, v_data, k=1):
        """
        使用对比散度(CD-k)算法执行一步训练

        对比散度的核心思想:
        1. 从数据开始做k步Gibbs采样得到"负相"样本
        2. 用数据的统计量减去生成样本的统计量来近似梯度

        参数:
            v_data (np.array): 训练数据中的一个样本(可见层状态)
            k (int): CD-k中的k,Gibbs采样步数

        返回:
            float: 重构误差(数据与重构之间的均方误差)
        """
        # === 正相(Positive Phase)===
        # 从数据出发计算隐藏层概率
        p_h_data, _ = self.sample_hidden_given_visible(v_data)
        # 计算正相的统计量:数据与隐藏概率的外积(期望)
        positive_grad = np.outer(v_data, p_h_data)

        # === 负相(Negative Phase)===
        # 从数据开始执行k步Gibbs采样
        _, v_recon, p_h_model, _ = self.gibbs_sampling(v_data, n_steps=k)
        # 计算负相的统计量:重构样本与对应隐藏概率的外积
        negative_grad = np.outer(v_recon, p_h_model)

        # === 参数更新 ===
        # 权重更新:学习率 * (正相统计量 - 负相统计量)
        self.W += self.lr * (positive_grad - negative_grad)
        # 可见偏置更新:学习率 * (数据 - 重构)
        self.b_visible += self.lr * (v_data - v_recon)
        # 隐藏偏置更新:学习率 * (数据的隐藏概率 - 模型的隐藏概率)
        self.b_hidden += self.lr * (p_h_data - p_h_model)

        # 计算重构误差用于监控训练过程
        reconstruction_error = np.mean((v_data - v_recon) ** 2)
        return reconstruction_error  # 返回重构误差

    def fit(self, X, epochs=100, k=1):
        """
        训练玻尔兹曼机

        参数:
            X (np.array): 训练数据矩阵,每行为一个样本
            epochs (int): 训练轮数
            k (int): CD-k的步数
        """
        for epoch in range(epochs):  # 遍历每个训练轮次
            total_error = 0  # 累积误差
            for i in range(X.shape[0]):  # 遍历每个训练样本
                error = self.train_step(X[i], k=k)  # 对单个样本执行CD-k训练
                total_error += error  # 累加误差
            # 每10轮打印一次平均重构误差
            if epoch % 10 == 0:
                avg_error = total_error / X.shape[0]  # 计算平均误差
                print(f"Epoch {epoch}, Average Reconstruction Error: {avg_error:.4f}")


# === 使用示例 ===
if __name__ == "__main__":
    # 创建简单的二值训练数据(4个样本,每个6维)
    training_data = np.array([
        [1, 1, 0, 0, 1, 0],  # 样本1
        [0, 1, 1, 0, 0, 1],  # 样本2
        [1, 0, 1, 1, 0, 0],  # 样本3
        [0, 0, 0, 1, 1, 1],  # 样本4
    ])

    # 创建玻尔兹曼机实例:6个可见单元,3个隐藏单元
    bm = BoltzmannMachine(n_visible=6, n_hidden=3, learning_rate=0.05)
    # 训练模型
    bm.fit(training_data, epochs=50, k=1)

二、受限玻尔兹曼机(Restricted Boltzmann Machine, RBM)

2.1 核心知识点

RBM是玻尔兹曼机的受限版本,层内无连接,只有可见层和隐藏层之间有连接。

关键特性:

  • 可见层和隐藏层之间条件独立:P(h∣v)=∏jP(hj∣v)P(h|v) = \prod_j P(h_j|v)P(h∣v)=∏jP(hj∣v),P(v∣h)=∏iP(vi∣h)P(v|h) = \prod_i P(v_i|h)P(v∣h)=∏iP(vi∣h)
  • 训练使用 CD-k(对比散度) 算法
  • 可用于降维、特征提取、协同过滤等

条件概率公式:

P(hj=1∣v)=σ(bj+∑iWijvi)P(h_j = 1 | v) = \sigma\left(b_j + \sum_i W_{ij} v_i\right)P(hj=1∣v)=σ(bj+i∑Wijvi)

P(vi=1∣h)=σ(ai+∑jWijhj)P(v_i = 1 | h) = \sigma\left(a_i + \sum_j W_{ij} h_j\right)P(vi=1∣h)=σ(ai+j∑Wijhj)

2.2 案例代码:RBM

python 复制代码
import numpy as np  # 导入numpy用于矩阵运算

class RBM:
    """
    受限玻尔兹曼机(Restricted Boltzmann Machine)实现

    结构特点:
    - 可见层与隐藏层全连接
    - 层内无连接(这是与普通玻尔兹曼机的关键区别)
    - 利用条件独立性实现高效采样
    """

    def __init__(self, n_visible, n_hidden, learning_rate=0.1):
        """
        初始化RBM

        参数:
            n_visible (int): 可见单元数(通常对应输入特征维度)
            n_hidden (int): 隐藏单元数(超参数,控制模型容量)
            learning_rate (float): 学习率
        """
        self.n_visible = n_visible  # 可见单元维度
        self.n_hidden = n_hidden  # 隐藏单元维度
        self.lr = learning_rate  # 学习率

        # 权重矩阵:可见层到隐藏层的连接权重
        # 使用Xavier初始化,使初始激活值的方差保持稳定
        self.W = np.random.randn(n_visible, n_hidden) * np.sqrt(2.0 / (n_visible + n_hidden))
        # 可见层偏置
        self.b_visible = np.zeros(n_visible)
        # 隐藏层偏置
        self.b_hidden = np.zeros(n_hidden)

    def sigmoid(self, x):
        """
        Sigmoid激活函数,用于计算单元激活概率

        参数:
            x: 输入值(标量、向量或矩阵)

        返回:
            激活概率
        """
        return 1.0 / (1.0 + np.exp(-np.clip(x, -500, 500)))  # 带溢出保护的sigmoid

    def sample_bernoulli(self, probs):
        """
        伯努利采样:根据概率生成0/1二值样本
        用于二值数据(如MNIST二值化后的数据)

        参数:
            probs (np.array): 每个单元为1的概率

        返回:
            np.array: 二值采样结果
        """
        # 生成均匀分布随机数,与概率比较得到0/1采样
        return (np.random.random(probs.shape) < probs).astype(np.float64)

    def forward(self, v):
        """
        前向传播:从可见层到隐藏层
        计算 P(h|v) = sigmoid(W^T * v + b_hidden)
        这里利用了RBM的关键性质:给定v时,h的各单元条件独立

        参数:
            v (np.array): 可见层状态,形状 (n_visible,) 或 (batch_size, n_visible)

        返回:
            tuple: (隐藏层概率, 隐藏层采样)
        """
        # 计算隐藏层激活值:v @ W + b_hidden
        # 对于批量数据,v形状为(batch_size, n_visible)
        activation = np.dot(v, self.W) + self.b_hidden  # 广播偏置到每个样本
        p_h = self.sigmoid(activation)  # 得到隐藏单元的激活概率
        h_sample = self.sample_bernoulli(p_h)  # 对概率进行伯努利采样
        return p_h, h_sample  # 返回概率和采样结果

    def backward(self, h):
        """
        反向传播(重构):从隐藏层到可见层
        计算 P(v|h) = sigmoid(W * h + b_visible)
        利用条件独立性:给定h时,v的各单元条件独立

        参数:
            h (np.array): 隐藏层状态

        返回:
            tuple: (可见层概率, 可见层采样)
        """
        # 计算可见层激活值:h @ W^T + b_visible
        activation = np.dot(h, self.W.T) + self.b_visible
        p_v = self.sigmoid(activation)  # 得到可见单元的激活概率
        v_sample = self.sample_bernoulli(p_v)  # 对概率进行伯努利采样
        return p_v, v_sample

    def free_energy(self, v):
        """
        计算自由能 F(v) = -b_v^T v - sum_j log(1 + exp(b_h_j + (W^T v)_j))

        自由能是对隐藏层求和(消去)后的能量,便于计算P(v)

        参数:
            v (np.array): 可见层状态

        返回:
            float: 自由能值
        """
        # 第一项:可见层偏置与状态的点积
        vbias_term = np.dot(v, self.b_visible)
        # 第二项:对隐藏层求和得到的项
        # 计算W^T @ v + b_hidden
        wx_b = np.dot(v, self.W) + self.b_hidden
        # log(1 + exp(x)) 使用softplus函数,加上数值稳定处理
        hidden_term = np.sum(np.logaddexp(0, wx_b), axis=-1)
        return -vbias_term - hidden_term

    def contrastive_divergence(self, v_data, k=1):
        """
        对比散度算法(CD-k)------ RBM训练的核心算法

        算法步骤:
        1. 正相:从数据计算统计量 <v_i * h_j>_data
        2. 从数据开始执行k步Gibbs采样
        3. 负相:用采样结果计算统计量 <v_i * h_j>_model
        4. 梯度 ≈ 正相统计量 - 负相统计量

        参数:
            v_data (np.array): 输入数据(批量样本)
            k (int): Gibbs采样步数(通常k=1即可)

        返回:
            float: 平均重构误差
        """
        batch_size = v_data.shape[0]  # 获取批量大小

        # ========== 正相(Positive Phase)==========
        # 从数据出发计算隐藏层概率(不采样,用概率值即可提高估计质量)
        p_h_data, h_data = self.forward(v_data)
        # 正相统计量:数据可见状态与数据隐藏概率的外积的均值
        positive_associations = np.dot(v_data.T, p_h_data) / batch_size

        # ========== 负相(Negative Phase)==========
        # 从数据开始执行k步Gibbs采样(对比散度的核心)
        v_recon = v_data.copy()  # 初始化重构为数据本身
        for step in range(k):  # 执行k步Gibbs采样
            p_h_recon, h_recon = self.forward(v_recon)  # 可见→隐藏
            p_v_recon, v_recon = self.backward(h_recon)  # 隐藏→可见

        # 用最终的重构计算负相统计量
        # 注意:这里使用最终重构的隐藏层概率
        p_h_model, _ = self.forward(v_recon)
        negative_associations = np.dot(v_recon.T, p_h_model) / batch_size

        # ========== 参数更新 ==========
        # 权重梯度:正相 - 负相
        self.W += self.lr * (positive_associations - negative_associations)
        # 可见偏置:数据均值 - 重构均值
        self.b_visible += self.lr * np.mean(v_data - v_recon, axis=0)
        # 隐藏偏置:数据隐藏概率均值 - 模型隐藏概率均值
        self.b_hidden += self.lr * np.mean(p_h_data - p_h_model, axis=0)

        # 计算并返回平均重构误差(用于监控训练进度)
        error = np.mean((v_data - v_recon) ** 2)
        return error

    def reconstruct(self, v):
        """
        数据重构:给定输入,先编码再解码,得到重构结果
        用于评估模型的表征学习能力

        参数:
            v (np.array): 输入数据

        返回:
            np.array: 重构后的数据(概率值)
        """
        p_h, _ = self.forward(v)  # 编码:可见→隐藏概率
        p_v, _ = self.backward(p_h)  # 解码:隐藏→可见概率
        return p_v  # 返回重构的概率值

    def fit(self, X, epochs=50, batch_size=32, k=1):
        """
        小批量CD-k训练RBM

        参数:
            X (np.array): 训练数据,形状 (n_samples, n_visible)
            epochs (int): 训练轮数
            batch_size (int): 小批量大小
            k (int): CD-k的步数
        """
        n_samples = X.shape[0]  # 样本总数
        n_batches = n_samples // batch_size  # 每轮的批次数

        for epoch in range(epochs):  # 遍历每轮
            # 随机打乱数据顺序
            indices = np.random.permutation(n_samples)
            X_shuffled = X[indices]  # 按打乱的索引重排数据
            epoch_error = 0  # 初始化本轮累积误差

            for batch_idx in range(n_batches):  # 遍历每批
                # 提取当前批次数据
                start = batch_idx * batch_size
                end = start + batch_size
                batch = X_shuffled[start:end]
                # 执行CD-k训练,返回当前批次的重构误差
                error = self.contrastive_divergence(batch, k=k)
                epoch_error += error  # 累加误差

            # 每5轮输出一次平均误差
            if epoch % 5 == 0:
                avg_error = epoch_error / n_batches
                print(f"Epoch {epoch:3d} | Reconstruction Error: {avg_error:.4f}")

    def generate(self, n_samples, n_gibbs_steps=1000):
        """
        从训练好的RBM中生成新样本
        通过长时间Gibbs采样从模型分布中采样

        参数:
            n_samples (int): 要生成的样本数
            n_gibbs_steps (int): Gibbs采样步数(越多越接近真实分布)

        返回:
            np.array: 生成的样本
        """
        # 随机初始化可见层状态(使用均匀分布)
        v = np.random.random((n_samples, self.n_visible))
        # 执行大量Gibbs采样使样本收敛到模型分布
        for step in range(n_gibbs_steps):
            p_h, h = self.forward(v)  # 可见→隐藏
            p_v, v = self.backward(h)  # 隐藏→可见
        return v  # 返回最终的采样结果


# === 案例:RBM用于MNIST特征学习 ===
if __name__ == "__main__":
    np.random.seed(42)  # 设置随机种子保证可复现

    # 模拟MNIST数据(实际使用时可替换为真实数据)
    n_train = 500  # 训练样本数
    n_features = 784  # 28x28图像展平后的维度

    # 生成随机二值化数据模拟手写数字
    X_train = (np.random.random((n_train, n_features)) > 0.7).astype(np.float64)

    # 创建RBM实例:784个可见单元(对应像素),256个隐藏单元
    rbm = RBM(n_visible=784, n_hidden=256, learning_rate=0.05)
    # 训练RBM
    rbm.fit(X_train, epochs=30, batch_size=32, k=1)

    # 重构输入数据,检验学习质量
    X_recon = rbm.reconstruct(X_train[:5])  # 对前5个样本进行重构
    print(f"重构输出形状: {X_recon.shape}")  # 应为(5, 784)
    print(f"重构误差: {np.mean((X_train[:5] - X_recon)**2):.4f}")

    # 生成新样本
    generated = rbm.generate(n_samples=5, n_gibbs_steps=500)
    print(f"生成样本形状: {generated.shape}")  # 应为(5, 784)

三、深度信念网络(Deep Belief Network, DBN)

3.1 核心知识点

DBN由多层RBM堆叠而成,关键思想是逐层无监督预训练

结构特点:

  • 底层为有向图(sigmoid信念网络),其余层为无向(RBM)
  • 训练策略:贪心逐层预训练(自底向上,每层用CD训练RBM)
  • 预训练后可微调(Fine-tuning)用于分类任务

联合概率:

P(v,h1,h2,...,hL)=P(hL−1,hL)∏l=0L−2P(hl∣hl+1)P(v, h_1, h_2, ..., h_L) = P(h_{L-1}, h_L) \prod_{l=0}^{L-2} P(h_l | h_{l+1})P(v,h1,h2,...,hL)=P(hL−1,hL)l=0∏L−2P(hl∣hl+1)

3.2 案例代码:DBN

python 复制代码
import numpy as np  # 导入numpy

class RBMLayer:
    """
    单层RBM封装,作为DBN的构建块
    封装了RBM的训练和前向/后向传播功能
    """

    def __init__(self, n_visible, n_hidden, learning_rate=0.1):
        """
        初始化单层RBM

        参数:
            n_visible (int): 输入维度(上一层的输出维度)
            n_hidden (int): 本层隐藏单元数
            learning_rate (float): 学习率
        """
        self.n_visible = n_visible  # 输入维度
        self.n_hidden = n_hidden  # 输出维度(隐藏单元数)
        self.lr = learning_rate  # 学习率
        # He初始化权重矩阵
        self.W = np.random.randn(n_visible, n_hidden) * np.sqrt(2.0 / n_visible)
        self.b = np.zeros(n_hidden)  # 隐藏层偏置
        self.c = np.zeros(n_visible)  # 可见层偏置

    def sigmoid(self, x):
        """Sigmoid激活函数"""
        return 1.0 / (1.0 + np.exp(-np.clip(x, -500, 500)))

    def sample_binary(self, p):
        """伯努利采样"""
        return (np.random.random(p.shape) < p).astype(np.float64)

    def encode(self, v):
        """
        编码(前向传播):可见层→隐藏层
        计算隐藏层概率并采样

        参数:
            v (np.array): 可见层输入

        返回:
            tuple: (隐藏层概率, 隐藏层二值采样)
        """
        # 计算隐藏层激活值
        activation = np.dot(v, self.W) + self.b
        p_h = self.sigmoid(activation)  # 激活概率
        h = self.sample_binary(p_h)  # 二值采样
        return p_h, h

    def decode(self, h):
        """
        解码(反向传播):隐藏层→可见层

        参数:
            h (np.array): 隐藏层输入

        返回:
            tuple: (可见层概率, 可见层二值采样)
        """
        activation = np.dot(h, self.W.T) + self.c  # 计算可见层激活值
        p_v = self.sigmoid(activation)
        v = self.sample_binary(p_v)
        return p_v, v

    def train(self, v_data, k=1):
        """
        使用CD-k训练本层RBM

        参数:
            v_data (np.array): 本层的输入数据
            k (int): CD步数

        返回:
            float: 重构误差
        """
        batch_size = v_data.shape[0]  # 批量大小

        # 正相:从数据计算统计量
        p_h_data, h_data = self.encode(v_data)
        positive = np.dot(v_data.T, p_h_data) / batch_size

        # 负相:k步Gibbs采样
        v_neg = v_data.copy()
        for _ in range(k):
            p_h_neg, h_neg = self.encode(v_neg)
            p_v_neg, v_neg = self.decode(h_neg)
        p_h_neg_final, _ = self.encode(v_neg)
        negative = np.dot(v_neg.T, p_h_neg_final) / batch_size

        # 参数更新
        self.W += self.lr * (positive - negative)
        self.c += self.lr * np.mean(v_data - v_neg, axis=0)
        self.b += self.lr * np.mean(p_h_data - p_h_neg_final, axis=0)

        return np.mean((v_data - v_neg) ** 2)


class DeepBeliefNetwork:
    """
    深度信念网络(DBN)实现

    由多层RBM堆叠而成,采用贪心逐层预训练策略
    训练流程:
    1. 训练第1层RBM(输入→h1)
    2. 用第1层RBM编码数据得到h1,训练第2层RBM(h1→h2)
    3. 重复,直到所有层训练完成
    4. (可选)微调整个网络
    """

    def __init__(self, layer_sizes, learning_rate=0.1):
        """
        初始化DBN

        参数:
            layer_sizes (list): 每层的单元数,如[784, 512, 256, 128]
                第一个元素是输入维度,后续元素是各隐藏层的维度
            learning_rate (float): 学习率
        """
        self.layer_sizes = layer_sizes  # 存储网络结构
        self.n_layers = len(layer_sizes) - 1  # RBM层数(不包含输入层)
        self.layers = []  # 存储各层RBM

        # 逐层创建RBM
        for i in range(self.n_layers):
            # 第i层RBM:输入为layer_sizes[i],输出为layer_sizes[i+1]
            layer = RBMLayer(
                n_visible=layer_sizes[i],
                n_hidden=layer_sizes[i + 1],
                learning_rate=learning_rate
            )
            self.layers.append(layer)  # 添加到层列表

    def pretrain(self, X, epochs=20, batch_size=32, k=1, verbose=True):
        """
        贪心逐层预训练------DBN的核心训练方法

        算法流程:
        1. 将原始数据作为第1层RBM的输入,训练该层
        2. 用训练好的第1层RBM编码数据,得到第1层的隐藏激活
        3. 将第1层的隐藏激活作为第2层RBM的输入,训练该层
        4. 重复直到所有层训练完成

        这种贪心策略有效的原因:
        - 每层学习上一层输出的特征表示
        - 逐层提取更抽象、更高级的特征
        - 预训练提供好的参数初始化,避免随机初始化的局部最优

        参数:
            X (np.array): 训练数据,形状 (n_samples, n_features)
            epochs (int): 每层RBM训练的轮数
            batch_size (int): 批量大小
            k (int): CD-k的步数
            verbose (int): 是否打印训练信息
        """
        current_input = X.copy()  # 当前层的输入(初始为原始数据)

        for layer_idx in range(self.n_layers):  # 逐层训练
            if verbose:
                print(f"\n{'='*50}")
                print(f"Pretraining Layer {layer_idx + 1}/{self.n_layers}")
                print(f"Input dim: {self.layers[layer_idx].n_visible} → "
                      f"Output dim: {self.layers[layer_idx].n_hidden}")
                print(f"{'='*50}")

            n_samples = current_input.shape[0]  # 样本数
            n_batches = n_samples // batch_size  # 批次数

            for epoch in range(epochs):  # 每层训练多轮
                # 打乱数据顺序
                indices = np.random.permutation(n_samples)
                data_shuffled = current_input[indices]
                epoch_error = 0

                for b in range(n_batches):  # 遍历每个批次
                    batch = data_shuffled[b * batch_size:(b + 1) * batch_size]
                    # 训练当前层RBM
                    error = self.layers[layer_idx].train(batch, k=k)
                    epoch_error += error

                # 打印训练进度
                if verbose and epoch % 5 == 0:
                    avg_err = epoch_error / n_batches
                    print(f"  Epoch {epoch:3d} | Error: {avg_err:.4f}")

            # 训练完当前层后,用该层编码数据,作为下一层的输入
            # 使用编码概率(而非采样值)减少随机性
            p_h, _ = self.layers[layer_idx].encode(current_input)
            current_input = p_h  # 下一层的输入是当前层的隐藏概率

            if verbose:
                print(f"  Layer {layer_idx + 1} pretrained. "
                      f"Output shape: {current_input.shape}")

    def forward(self, X):
        """
        前向传播:依次通过所有层进行编码

        参数:
            X (np.array): 输入数据

        返回:
            list: 每一层的隐藏层概率输出
        """
        activations = [X]  # 存储每层的输出,初始为输入
        current = X
        for layer in self.layers:  # 逐层前向传播
            p_h, _ = layer.encode(current)  # 编码当前层
            activations.append(p_h)  # 存储输出
            current = p_h  # 输出作为下一层输入
        return activations  # 返回所有层的激活值

    def generate_from_top(self, n_samples=1, n_gibbs=100):
        """
        从顶层RBM生成样本,然后逐层解码到底层

        生成过程:
        1. 在顶层RBM中执行Gibbs采样
        2. 将顶层采样结果逐层向下解码
        3. 最终得到可见层的生成样本

        参数:
            n_samples (int): 生成样本数
            n_gibbs (int): 顶层Gibbs采样步数

        返回:
            np.array: 生成的数据样本
        """
        top_layer = self.layers[-1]  # 获取顶层RBM

        # 在顶层RBM中执行Gibbs采样
        # 随机初始化顶层隐藏状态
        h = np.random.random((n_samples, top_layer.n_hidden))
        for _ in range(n_gibbs):  # 执行多步Gibbs采样
            p_v, v = top_layer.decode(h)  # 隐藏→可见(顶层的"可见"即上一层的隐藏)
            p_h, h = top_layer.encode(v)  # 可见→隐藏

        current = p_v  # 使用顶层的可见概率(不采样减少噪声)

        # 逐层解码到底层
        for layer_idx in range(self.n_layers - 2, -1, -1):  # 从倒数第二层到第一层
            current, _ = self.layers[layer_idx].decode(current)  # 解码

        return current  # 返回生成的样本


# === 案例:DBN用于手写数字生成 ===
if __name__ == "__main__":
    np.random.seed(42)  # 固定随机种子

    # 模拟MNIST数据集
    n_samples = 1000
    n_features = 784
    X = np.random.binomial(1, 0.3, (n_samples, n_features)).astype(np.float64)

    # 创建DBN:784→512→256→128 的三层结构
    dbn = DeepBeliefNetwork(
        layer_sizes=[784, 512, 256, 128],
        learning_rate=0.05
    )

    # 执行逐层预训练
    dbn.pretrain(X, epochs=15, batch_size=32, k=1)

    # 通过网络前向传播,获取各层特征表示
    activations = dbn.forward(X[:5])
    for i, act in enumerate(activations):
        print(f"Layer {i} output shape: {act.shape}")

    # 从模型中生成新样本
    generated = dbn.generate_from_top(n_samples=3, n_gibbs=200)
    print(f"Generated samples shape: {generated.shape}")  # (3, 784)

四、深度玻尔兹曼机(Deep Boltzmann Machine, DBM)

4.1 核心知识点

DBM与DBN的区别:

  • DBM 所有层之间都是无向连接(全部是RBM结构)
  • DBN 只有顶层是无向的,其余层是自顶向下的有向连接

DBM的关键特性:

  • 使用平均场近似(Mean Field Approximation) 进行推理
  • 训练使用逐层预训练 + 联合微调
  • 推理时需要迭代更新隐藏层(不像DBN那样一次前向传播)

平均场推理:

反复迭代直到收敛:

μj←σ(bj+∑iWij(1)vi+∑kWjk(2)μk)\mu_j \leftarrow \sigma\left(b_j + \sum_i W_{ij}^{(1)} v_i + \sum_k W_{jk}^{(2)} \mu_k\right)μj←σ(bj+i∑Wij(1)vi+k∑Wjk(2)μk)

4.2 案例代码:DBM

python 复制代码
import numpy as np  # 导入numpy

class DBMLayer:
    """
    DBM中的单个层,包含上行权重和下行权重
    DBM中每层同时接收来自下层和上层的信息
    """

    def __init__(self, n_input, n_output, lr=0.01):
        """
        初始化DBM层

        参数:
            n_input (int): 输入维度
            n_output (int): 输出维度
            lr (float): 学习率
        """
        self.n_input = n_input
        self.n_output = n_output
        self.lr = lr
        # 权重矩阵初始化
        self.W = np.random.randn(n_input, n_output) * np.sqrt(2.0 / (n_input + n_output))
        self.b = np.zeros(n_output)  # 输出偏置

    def sigmoid(self, x):
        """Sigmoid激活函数"""
        return 1.0 / (1.0 + np.exp(-np.clip(x, -500, 500)))


class DeepBoltzmannMachine:
    """
    深度玻尔兹曼机(DBM)实现

    核心区别于DBN:所有层之间都是无向连接

    训练策略:
    1. 逐层预训练:每两层之间训练一个RBM
    2. 联合微调:使用平均场推理 + 随机最大似然

    推理策略:平均场近似
    - 由于层间双向连接,无法直接计算条件概率
    - 使用迭代的平均场方程逼近后验分布
    """

    def __init__(self, layer_sizes, learning_rate=0.01):
        """
        初始化DBM

        参数:
            layer_sizes (list): 网络结构,如[784, 500, 200]
            learning_rate (float): 学习率
        """
        self.layer_sizes = layer_sizes
        self.n_layers = len(layer_sizes) - 1
        self.lr = learning_rate
        self.layers = []

        # 创建各层的权重和偏置
        for i in range(self.n_layers):
            layer = DBMLayer(layer_sizes[i], layer_sizes[i + 1], learning_rate)
            self.layers.append(layer)

        # 可见层偏置
        self.b_visible = np.zeros(layer_sizes[0])

    def sigmoid(self, x):
        """Sigmoid激活函数"""
        return 1.0 / (1.0 + np.exp(-np.clip(x, -500, 500)))

    def mean_field_inference(self, v, n_iterations=20):
        """
        平均场推理------DBM的核心推理算法

        由于DBM的无向性质,隐藏层之间也相互影响,
        无法像DBN那样一步计算出条件概率。
        平均场推理通过迭代更新来逼近真实的后验分布。

        迭代过程(对于2层DBM为例):
        - μ_1 ← σ(b_1 + W_1^T v + W_2 μ_2)
        - μ_2 ← σ(b_2 + W_2 μ_1)

        参数:
            v (np.array): 可见层数据,形状 (batch_size, n_visible)
            n_iterations (int): 平均场迭代次数

        返回:
            list: 各隐藏层的平均场近似值
        """
        batch_size = v.shape[0]  # 批量大小

        # 初始化各隐藏层的平均场值为0.5(均匀先验)
        mu = []
        for layer in self.layers:
            mu.append(np.full((batch_size, layer.n_output), 0.5))

        # 迭代更新平均场值直到收敛
        for iteration in range(n_iterations):
            for l in range(self.n_layers):
                # 计算当前隐藏层的输入
                # 1. 来自下层(可见层或上一隐藏层)的输入
                if l == 0:
                    # 第一层:来自可见层
                    bottom_up = np.dot(v, self.layers[l].W)
                else:
                    # 其他层:来自上一隐藏层的平均场值
                    bottom_up = np.dot(mu[l - 1], self.layers[l].W)

                # 2. 来自上层(下一隐藏层)的输入(DBM特有的双向信息流)
                if l < self.n_layers - 1:
                    # 不是顶层:接收来自上一层的信息
                    top_down = np.dot(mu[l + 1], self.layers[l + 1].W.T)
                else:
                    # 顶层:无上层信息
                    top_down = 0

                # 综合上下信息,计算激活值
                activation = bottom_up + top_down + self.layers[l].b
                mu[l] = self.sigmoid(activation)  # 更新平均场值

        return mu  # 返回所有隐藏层的平均场近似值

    def pretrain(self, X, epochs=20, batch_size=32, k=1):
        """
        逐层预训练DBM

        预训练策略:对每两层之间训练一个RBM
        对于DBM,预训练时需要特殊的处理:
        - 底层RBM需要考虑上层的影响
        - 通常使用均值场近似进行近似训练

        参数:
            X (np.array): 训练数据
            epochs (int): 训练轮数
            batch_size (int): 批量大小
            k (int): CD步数
        """
        current_data = X.copy()  # 当前层的输入数据

        for layer_idx in range(self.n_layers):
            print(f"Pretraining layer {layer_idx + 1}/{self.n_layers}")
            n_samples = current_data.shape[0]
            n_batches = n_samples // batch_size

            for epoch in range(epochs):
                indices = np.random.permutation(n_samples)
                data_shuffled = current_data[indices]
                epoch_error = 0

                for b in range(n_batches):
                    batch = data_shuffled[b * batch_size:(b + 1) * batch_size]
                    batch_size_actual = batch.shape[0]

                    # CD-k 训练当前层
                    # 正相
                    activation_pos = np.dot(batch, self.layers[layer_idx].W) + self.layers[layer_idx].b
                    p_h_pos = self.sigmoid(activation_pos)
                    positive = np.dot(batch.T, p_h_pos) / batch_size_actual

                    # 负相(k步Gibbs采样)
                    v_neg = batch.copy()
                    for _ in range(k):
                        act_neg = np.dot(v_neg, self.layers[layer_idx].W) + self.layers[layer_idx].b
                        p_h_neg = self.sigmoid(act_neg)
                        h_neg = (np.random.random(p_h_neg.shape) < p_h_neg).astype(np.float64)
                        v_recon = np.dot(h_neg, self.layers[layer_idx].W.T) + np.zeros(batch.shape[1])  # 简化的重构
                        v_neg = (np.random.random(v_recon.shape) < self.sigmoid(v_recon)).astype(np.float64)

                    act_neg_final = np.dot(v_neg, self.layers[layer_idx].W) + self.layers[layer_idx].b
                    p_h_neg_final = self.sigmoid(act_neg_final)
                    negative = np.dot(v_neg.T, p_h_neg_final) / batch_size_actual

                    # 更新权重
                    self.layers[layer_idx].W += self.layers[layer_idx].lr * (positive - negative)
                    self.layers[layer_idx].b += self.layers[layer_idx].lr * np.mean(p_h_pos - p_h_neg_final, axis=0)

                    error = np.mean((batch - v_neg) ** 2)
                    epoch_error += error

                if epoch % 5 == 0:
                    print(f"  Epoch {epoch:3d} | Error: {epoch_error / n_batches:.4f}")

            # 用当前层编码数据作为下一层的输入
            p_h, _ = self.encode_layer(current_data, layer_idx)
            current_data = p_h

    def encode_layer(self, data, layer_idx):
        """
        编码单层

        参数:
            data (np.array): 输入数据
            layer_idx (int): 层索引

        返回:
            tuple: (概率输出, 采样输出)
        """
        activation = np.dot(data, self.layers[layer_idx].W) + self.layers[layer_idx].b
        p = self.sigmoid(activation)
        h = (np.random.random(p.shape) < p).astype(np.float64)
        return p, h

    def reconstruct(self, v, mean_field_steps=20):
        """
        重构输入数据

        参数:
            v (np.array): 输入数据
            mean_field_steps (int): 平均场迭代步数

        返回:
            np.array: 重构的数据
        """
        # 平均场推理获取隐藏层表示
        mu = self.mean_field_inference(v, n_iterations=mean_field_steps)

        # 从最顶层开始逐层解码
        current = mu[-1]  # 从顶层的平均场值开始
        for l in range(self.n_layers - 1, 0, -1):
            current = self.sigmoid(np.dot(current, self.layers[l].W.T) + self.layers[l-1].b)

        # 最终解码到可见层
        reconstructed = self.sigmoid(np.dot(current, self.layers[0].W.T) + self.b_visible)
        return reconstructed


# === 使用示例 ===
if __name__ == "__main__":
    np.random.seed(42)

    # 创建模拟数据
    X = np.random.binomial(1, 0.3, (500, 784)).astype(np.float64)

    # 创建DBM:三层结构 784 → 500 → 200
    dbm = DeepBoltzmannMachine(
        layer_sizes=[784, 500, 200],
        learning_rate=0.005
    )

    # 预训练
    dbm.pretrain(X, epochs=10, batch_size=32, k=1)

    # 使用平均场推理
    mu = dbm.mean_field_inference(X[:5], n_iterations=20)
    print(f"Hidden layer 1 mean field shape: {mu[0].shape}")  # (5, 500)
    print(f"Hidden layer 2 mean field shape: {mu[1].shape}")  # (5, 200)

    # 重构
    X_recon = dbm.reconstruct(X[:5])
    print(f"Reconstruction shape: {X_recon.shape}")  # (5, 784)

五、实值数据上的玻尔兹曼机

5.1 核心知识点

当数据为实值(如灰度图像像素值0-1)时,需要修改能量函数:

高斯-伯努利RBM(Gaussian-Bernoulli RBM):

E(v,h)=∑i(vi−ai)22σi2−∑i,jviσiWijhj−∑jbjhjE(v, h) = \sum_i \frac{(v_i - a_i)^2}{2\sigma_i^2} - \sum_{i,j} \frac{v_i}{\sigma_i} W_{ij} h_j - \sum_j b_j h_jE(v,h)=i∑2σi2(vi−ai)2−i,j∑σiviWijhj−j∑bjhj

可见单元变为高斯分布:

P(vi∣h)=N(ai+σi∑jWijhj,σi2)P(v_i | h) = \mathcal{N}\left(a_i + \sigma_i \sum_j W_{ij} h_j, \sigma_i^2\right)P(vi∣h)=N(ai+σij∑Wijhj,σi2)

5.2 案例代码:高斯-伯努利RBM

python 复制代码
import numpy as np  # 导入numpy

class GaussianBernoulliRBM:
    """
    高斯-伯努利RBM

    适用于实值可见数据(如归一化到[0,1]的图像像素值)
    与标准RBM的区别:
    - 可见单元为高斯分布(而非伯努利分布)
    - 隐藏单元仍为伯努利分布
    - 能量函数包含可见单元的二次项
    """

    def __init__(self, n_visible, n_hidden, learning_rate=0.001, sigma=1.0):
        """
        初始化高斯-伯努利RBM

        参数:
            n_visible (int): 可见单元数
            n_hidden (int): 隐藏单元数
            learning_rate (float): 学习率(通常需要比二值RBM小)
            sigma (float): 可见单元高斯分布的标准差
        """
        self.n_visible = n_visible
        self.n_hidden = n_hidden
        self.lr = learning_rate
        self.sigma = sigma  # 高斯分布的标准差参数

        # 权重初始化(使用较小的值,因为实值数据对权重更敏感)
        self.W = np.random.randn(n_visible, n_hidden) * 0.01
        self.b_visible = np.zeros(n_visible)  # 可见层偏置(高斯均值参数)
        self.b_hidden = np.zeros(n_hidden)  # 隐藏层偏置

    def sigmoid(self, x):
        """Sigmoid激活函数"""
        return 1.0 / (1.0 + np.exp(-np.clip(x, -500, 500)))

    def sample_hidden_given_visible(self, v):
        """
        计算隐藏层的条件概率(伯努利分布)
        P(h_j=1|v) = σ(b_h_j + (1/σ²) * W^T_ij * v_i)

        注意:实值情况下需要除以σ²进行缩放

        参数:
            v (np.array): 可见层状态(实值)

        返回:
            tuple: (隐藏层概率, 隐藏层采样)
        """
        # 计算隐藏层激活值,注意W需要按σ²缩放
        activation = self.b_hidden + np.dot(v, self.W) / (self.sigma ** 2)
        p_h = self.sigmoid(activation)  # 隐藏层概率
        # 伯努利采样
        h_sample = (np.random.random(p_h.shape) < p_h).astype(np.float64)
        return p_h, h_sample

    def sample_visible_given_hidden(self, h):
        """
        计算可见层的条件分布(高斯分布)
        P(v_i|h) = N(a_i + σ * Σ_j W_ij h_j, σ²)

        参数:
            h (np.array): 隐藏层状态

        返回:
            tuple: (可见层均值, 可见层采样)
        """
        # 高斯均值:偏置 + σ * W @ h
        mean = self.b_visible + self.sigma * np.dot(h, self.W.T)
        # 高斯采样:均值 + σ * 标准正态噪声
        v_sample = mean + self.sigma * np.random.randn(*mean.shape)
        return mean, v_sample

    def free_energy(self, v):
        """
        计算高斯-伯努利RBM的自由能
        F(v) = Σ_i (v_i - a_i)²/(2σ²) - Σ_j log(1 + exp(b_h_j + (1/σ) * Σ_i W_ij v_i))

        参数:
            v (np.array): 可见层状态

        返回:
            float: 自由能值
        """
        # 高斯项:(v - a)² / (2σ²)
        gaussian_term = np.sum((v - self.b_visible) ** 2, axis=-1) / (2 * self.sigma ** 2)
        # 隐藏层项:softplus(b_h + W^T v / σ)
        wx_b = np.dot(v, self.W) / self.sigma + self.b_hidden
        hidden_term = np.sum(np.logaddexp(0, wx_b), axis=-1)
        return gaussian_term - hidden_term

    def contrastive_divergence(self, v_data, k=1):
        """
        对比散度训练(适配高斯可见单元)

        关键区别:可见层采样使用高斯分布而非伯努利分布

        参数:
            v_data (np.array): 实值训练数据
            k (int): CD步数

        返回:
            float: 重构误差
        """
        batch_size = v_data.shape[0]

        # === 正相 ===
        p_h_data, _ = self.sample_hidden_given_visible(v_data)
        positive_grad = np.dot(v_data.T, p_h_data) / batch_size

        # === 负相(k步Gibbs采样)===
        v_recon = v_data.copy()
        for _ in range(k):
            p_h_recon, h_recon = self.sample_hidden_given_visible(v_recon)
            p_v_recon, v_recon = self.sample_visible_given_hidden(h_recon)

        # 负相的隐藏层概率
        p_h_model, _ = self.sample_hidden_given_visible(v_recon)
        negative_grad = np.dot(v_recon.T, p_h_model) / batch_size

        # === 参数更新 ===
        self.W += self.lr * (positive_grad - negative_grad)
        self.b_visible += self.lr * np.mean(v_data - v_recon, axis=0)
        self.b_hidden += self.lr * np.mean(p_h_data - p_h_model, axis=0)

        # 重构误差(均方误差)
        return np.mean((v_data - v_recon) ** 2)

    def fit(self, X, epochs=50, batch_size=32, k=1):
        """
        训练高斯-伯努利RBM

        参数:
            X (np.array): 实值训练数据(建议归一化到[0,1]或标准化)
            epochs (int): 训练轮数
            batch_size (int): 批量大小
            k (int): CD步数
        """
        n_samples = X.shape[0]
        n_batches = n_samples // batch_size

        for epoch in range(epochs):
            indices = np.random.permutation(n_samples)
            X_shuffled = X[indices]
            epoch_error = 0

            for b in range(n_batches):
                batch = X_shuffled[b * batch_size:(b + 1) * batch_size]
                error = self.contrastive_divergence(batch, k=k)
                epoch_error += error

            if epoch % 5 == 0:
                avg_err = epoch_error / n_batches
                print(f"Epoch {epoch:3d} | MSE: {avg_err:.4f}")

    def reconstruct(self, v):
        """
        重构输入数据

        参数:
            v (np.array): 输入数据

        返回:
            np.array: 重构的均值
        """
        p_h, _ = self.sample_hidden_given_visible(v)  # 编码
        mean, _ = self.sample_visible_given_hidden(p_h)  # 解码
        return mean


# === 使用示例 ===
if __name__ == "__main__":
    np.random.seed(42)

    # 生成模拟的实值数据(如归一化的灰度图像像素)
    X_real = np.random.uniform(0, 1, (300, 64)).astype(np.float64)

    # 创建高斯-伯努利RBM
    grbm = GaussianBernoulliRBM(
        n_visible=64, n_hidden=32,
        learning_rate=0.001, sigma=0.5
    )

    # 训练
    grbm.fit(X_real, epochs=30, batch_size=16, k=1)

    # 重构
    X_recon = grbm.reconstruct(X_real[:5])
    print(f"重构误差: {np.mean((X_real[:5] - X_recon)**2):.4f}")
    print(f"重构范围: [{X_recon.min():.3f}, {X_recon.max():.3f}]")

六、卷积玻尔兹曼机(Convolutional Boltzmann Machine)

6.1 核心知识点

将卷积结构引入RBM,适合处理图像数据:

  • 权重共享:同一个卷积核在整个输入上共享参数
  • 局部连接:每个隐藏单元只连接输入的一个局部区域
  • 池化:可以引入概率最大池化(Probabilistic Max Pooling)

能量函数:

E(v,h)=−∑k∑i,j∑a,bhk,i,jWk,a,bvi+a,j+b−∑kbk∑i,jhk,i,j−c∑i,jvi,jE(v, h) = -\sum_k \sum_{i,j} \sum_{a,b} h_{k,i,j} W_{k,a,b} v_{i+a, j+b} - \sum_k b_k \sum_{i,j} h_{k,i,j} - c \sum_{i,j} v_{i,j}E(v,h)=−k∑i,j∑a,b∑hk,i,jWk,a,bvi+a,j+b−k∑bki,j∑hk,i,j−ci,j∑vi,j

6.2 案例代码:卷积RBM

python 复制代码
import numpy as np  # 导入numpy

class ConvolutionalRBM:
    """
    卷积受限玻尔兹曼机(Convolutional RBM)

    特点:
    1. 使用卷积核作为共享权重,大幅减少参数数量
    2. 保留空间结构信息,适合图像数据
    3. 可学习局部特征模式(如边缘、纹理等)
    """

    def __init__(self, input_shape, n_filters, filter_size, learning_rate=0.01):
        """
        初始化卷积RBM

        参数:
            input_shape (tuple): 输入图像的形状 (height, width)
            n_filters (int): 卷积核(特征图)的数量
            filter_size (int): 卷积核的大小(正方形卷积核)
            learning_rate (float): 学习率
        """
        self.input_h, self.input_w = input_shape  # 输入图像的高度和宽度
        self.n_filters = n_filters  # 卷积核数量(输出特征图数)
        self.filter_size = filter_size  # 卷积核尺寸
        self.lr = learning_rate  # 学习率

        # 计算输出(隐藏层特征图)的尺寸
        # 使用"valid"卷积,输出尺寸 = 输入尺寸 - 卷积核尺寸 + 1
        self.output_h = self.input_h - filter_size + 1
        self.output_w = self.input_w - filter_size + 1

        # 初始化卷积核权重
        # 形状:(n_filters, filter_size, filter_size)
        self.W = np.random.randn(n_filters, filter_size, filter_size) * 0.01
        # 隐藏层偏置(每个特征图一个偏置)
        self.b_hidden = np.zeros(n_filters)
        # 可见层偏置(标量,所有像素共享)
        self.b_visible = 0.0

    def sigmoid(self, x):
        """Sigmoid激活函数"""
        return 1.0 / (1.0 + np.exp(-np.clip(x, -500, 500)))

    def conv2d(self, input_img, kernel):
        """
        2D卷积操作(手动实现,valid模式)

        将卷积核在输入图像上滑动,计算局部区域与卷积核的点积

        参数:
            input_img (np.array): 输入图像,形状 (height, width)
            kernel (np.array): 卷积核,形状 (kh, kw)

        返回:
            np.array: 卷积输出特征图
        """
        kh, kw = kernel.shape  # 卷积核尺寸
        # 输出尺寸
        oh = input_img.shape[0] - kh + 1
        ow = input_img.shape[1] - kw + 1
        output = np.zeros((oh, ow))  # 初始化输出

        # 滑动卷积核
        for i in range(oh):
            for j in range(ow):
                # 提取当前位置的局部区域
                patch = input_img[i:i+kh, j:j+kw]
                # 计算局部区域与卷积核的点积
                output[i, j] = np.sum(patch * kernel)

        return output

    def conv2d_transpose(self, feature_map, kernel):
        """
        转置卷积(反卷积)操作
        用于从隐藏层特征图重构可见层

        转置卷积是卷积的"逆操作":
        卷积将大图→小图,转置卷积将小图→大图

        参数:
            feature_map (np.array): 特征图,形状 (oh, ow)
            kernel (np.array): 卷积核,形状 (kh, kw)

        返回:
            np.array: 上采样后的输出
        """
        oh, ow = feature_map.shape  # 特征图尺寸
        kh, kw = kernel.shape  # 卷积核尺寸
        # 输出尺寸(转置卷积的输出比输入大)
        out_h = oh + kh - 1
        out_w = ow + kw - 1
        output = np.zeros((out_h, out_w))  # 初始化输出

        # 转置卷积实现:相当于将卷积核翻转后做"full"模式卷积
        for i in range(oh):
            for j in range(ow):
                # 将特征图值"广播"到更大的区域
                output[i:i+kh, j:j+kw] += feature_map[i, j] * kernel

        return output

    def forward(self, v):
        """
        前向传播:从可见层(图像)到隐藏层(特征图)

        对每个卷积核k,计算:
        h_k = σ(W_k * v + b_h_k)
        其中 * 表示卷积操作

        参数:
            v (np.array): 输入图像,形状 (batch, height, width)

        返回:
            tuple: (隐藏层概率, 隐藏层采样)
        """
        batch_size = v.shape[0]  # 批量大小
        # 初始化隐藏层激活,形状:(batch, n_filters, output_h, output_w)
        activation = np.zeros((batch_size, self.n_filters, self.output_h, self.output_w))

        for b in range(batch_size):  # 遍历批量中的每个样本
            for k in range(self.n_filters):  # 遍历每个卷积核
                # 对输入图像执行卷积操作
                activation[b, k] = self.conv2d(v[b], self.W[k]) + self.b_hidden[k]

        p_h = self.sigmoid(activation)  # 通过sigmoid得到概率
        # 伯努利采样
        h_sample = (np.random.random(p_h.shape) < p_h).astype(np.float64)
        return p_h, h_sample

    def backward(self, h):
        """
        反向传播(重构):从隐藏层到可见层

        重构过程使用转置卷积:
        v = σ(Σ_k W_k^T ⊛ h_k + b_v)

        参数:
            h (np.array): 隐藏层状态,形状 (batch, n_filters, oh, ow)

        返回:
            tuple: (可见层概率, 可见层采样)
        """
        batch_size = h.shape[0]
        # 初始化可见层重构
        activation = np.zeros((batch_size, self.input_h, self.input_w))

        for b in range(batch_size):
            for k in range(self.n_filters):
                # 使用转置卷积将特征图映射回输入空间
                activation[b] += self.conv2d_transpose(h[b, k], self.W[k])
            activation[b] += self.b_visible  # 加上可见层偏置

        p_v = self.sigmoid(activation)  # 通过sigmoid得到概率
        v_sample = (np.random.random(p_v.shape) < p_v).astype(np.float64)
        return p_v, v_sample

    def train_step(self, v_data, k=1):
        """
        使用CD-k训练卷积RBM

        参数:
            v_data (np.array): 批量输入图像
            k (int): CD步数

        返回:
            float: 重构误差
        """
        batch_size = v_data.shape[0]

        # === 正相 ===
        p_h_data, _ = self.forward(v_data)

        # === 负相 ===
        v_recon = v_data.copy()
        for _ in range(k):
            p_h_recon, h_recon = self.forward(v_recon)
            p_v_recon, v_recon = self.backward(h_recon)

        p_h_model, _ = self.forward(v_recon)

        # === 更新卷积核权重 ===
        for k_idx in range(self.n_filters):
            # 计算每个卷积核的梯度
            dW = np.zeros_like(self.W[k_idx])
            for b in range(batch_size):
                # 正相贡献
                dW += self.conv2d(v_data[b], ...)  # 简化示意
            # 实际更新需要对batch取平均
            # self.W[k_idx] += self.lr * dW / batch_size

        # 更新偏置
        self.b_hidden += self.lr * np.mean(
            np.mean(p_h_data, axis=(2, 3)) - np.mean(p_h_model, axis=(2, 3)),
            axis=0
        )
        self.b_visible += self.lr * np.mean(v_data - v_recon)

        return np.mean((v_data - v_recon) ** 2)

    def learn_filters(self, X, epochs=20, batch_size=16):
        """
        可视化学到的卷积核(特征检测器)

        参数:
            X (np.array): 训练数据
            epochs (int): 训练轮数
            batch_size (int): 批量大小
        """
        n_samples = X.shape[0]
        n_batches = n_samples // batch_size

        for epoch in range(epochs):
            indices = np.random.permutation(n_samples)
            X_shuffled = X[indices]
            epoch_error = 0

            for b in range(n_batches):
                batch = X_shuffled[b * batch_size:(b + 1) * batch_size]
                error = self.train_step(batch, k=1)
                epoch_error += error

            if epoch % 5 == 0:
                print(f"Epoch {epoch:3d} | Error: {epoch_error / n_batches:.4f}")


# === 使用示例 ===
if __name__ == "__main__":
    np.random.seed(42)

    # 模拟28x28的灰度图像数据
    n_samples = 100
    img_size = 28
    X = np.random.uniform(0, 1, (n_samples, img_size, img_size))

    # 创建卷积RBM
    # 输入28x28,16个5x5的卷积核
    crbm = ConvolutionalRBM(
        input_shape=(28, 28),
        n_filters=16,
        filter_size=5,
        learning_rate=0.01
    )

    # 前向传播测试
    p_h, h = crbm.forward(X[:4])
    print(f"Feature maps shape: {p_h.shape}")  # (4, 16, 24, 24)

    # 重构测试
    p_v, v = crbm.backward(h)
    print(f"Reconstruction shape: {p_v.shape}")  # (4, 28, 28)

    # 输出卷积核信息
    print(f"Filter shape: {crbm.W.shape}")  # (16, 5, 5)
    print(f"Output feature map size: {crbm.output_h}x{crbm.output_w}")  # 24x24

七、用于结构化或序列输出的玻尔兹曼机

7.1 核心知识点

玻尔兹曼机可以扩展为条件模型,用于结构化预测和序列生成:

  • 条件RBM(Conditional RBM):隐藏层依赖于额外的条件信息
  • 时间RBM(Temporal RBM):将时间序列的前几个时间步作为条件
  • 用于序列生成:结合LSTM等递归结构

条件能量函数:

E(v,h;c)=−vTWh−vTbv−hTbh−hTUcE(v, h; c) = -v^T W h - v^T b_v - h^T b_h - h^T U cE(v,h;c)=−vTWh−vTbv−hTbh−hTUc

其中 ccc 为条件输入。

7.2 案例代码:条件RBM用于序列生成

python 复制代码
import numpy as np  # 导入numpy

class ConditionalRBM:
    """
    条件受限玻尔兹曼机(Conditional RBM)

    用于序列数据建模,在标准RBM基础上引入条件信息:
    - 将历史时间步的信息作为条件输入
    - 适合建模时间序列、音乐生成、运动序列等

    条件能量函数:
    E(v_t, h_t; v_{t-1},...,v_{t-p}) = -v^T W h - b^T v - c^T h - Σ_p h^T U_p v_{t-p}
    """

    def __init__(self, n_visible, n_hidden, n_context, n_lags=1, learning_rate=0.01):
        """
        初始化条件RBM

        参数:
            n_visible (int): 可见层维度(当前时间步的数据维度)
            n_hidden (int): 隐藏层维度
            n_context (int): 条件输入维度(通常等于n_visible)
            n_lags (int): 使用前几个时间步作为条件(时间窗口大小)
            learning_rate (float): 学习率
        """
        self.n_visible = n_visible
        self.n_hidden = n_hidden
        self.n_context = n_context  # 条件输入维度
        self.n_lags = n_lags  # 时间滞后步数
        self.lr = learning_rate

        # 主权重矩阵(可见层↔隐藏层)
        self.W = np.random.randn(n_visible, n_hidden) * 0.01
        # 条件权重矩阵(每个时间滞后一个权重矩阵)
        # U[l] 表示第l个滞后时间步对隐藏层的影响
        self.U = [np.random.randn(n_context, n_hidden) * 0.01 for _ in range(n_lags)]
        # 偏置
        self.b_visible = np.zeros(n_visible)  # 可见层偏置
        self.b_hidden = np.zeros(n_hidden)  # 隐藏层偏置

    def sigmoid(self, x):
        """Sigmoid激活函数"""
        return 1.0 / (1.0 + np.exp(-np.clip(x, -500, 500)))

    def sample_hidden(self, v, context_list):
        """
        计算条件隐藏层概率
        P(h_j=1|v, context) = σ(b_h_j + Σ_i W_ij v_i + Σ_p Σ_i U_p_ij context_p_i)

        条件信息通过额外的权重矩阵影响隐藏层的激活

        参数:
            v (np.array): 当前时间步的可见层状态
            context_list (list): 条件输入列表 [v_{t-1}, v_{t-2}, ..., v_{t-p}]

        返回:
            tuple: (隐藏层概率, 隐藏层采样)
        """
        # 基础激活:偏置 + 主权重的作用
        activation = self.b_hidden + np.dot(v, self.W)

        # 加上每个时间滞后的条件影响
        for p in range(self.n_lags):
            if p < len(context_list) and context_list[p] is not None:
                # 第p个滞后时间步的影响:context_p @ U_p
                activation += np.dot(context_list[p], self.U[p])

        p_h = self.sigmoid(activation)  # 条件概率
        h_sample = (np.random.random(p_h.shape) < p_h).astype(np.float64)
        return p_h, h_sample

    def sample_visible(self, h):
        """
        从隐藏层重构可见层(不依赖条件信息)

        参数:
            h (np.array): 隐藏层状态

        返回:
            tuple: (可见层概率, 可见层采样)
        """
        activation = self.b_visible + np.dot(h, self.W.T)
        p_v = self.sigmoid(activation)
        v_sample = (np.random.random(p_v.shape) < p_v).astype(np.float64)
        return p_v, v_sample

    def train_step(self, v_current, context_list, k=1):
        """
        条件CD-k训练

        参数:
            v_current (np.array): 当前时间步的数据
            context_list (list): 条件输入列表
            k (int): CD步数

        返回:
            float: 重构误差
        """
        batch_size = v_current.shape[0]

        # === 正相 ===
        p_h_data, _ = self.sample_hidden(v_current, context_list)
        positive_W = np.dot(v_current.T, p_h_data) / batch_size
        positive_U = []
        for p in range(self.n_lags):
            if p < len(context_list) and context_list[p] is not None:
                positive_U.append(np.dot(context_list[p].T, p_h_data) / batch_size)
            else:
                positive_U.append(None)

        # === 负相 ===
        v_neg = v_current.copy()
        for _ in range(k):
            p_h_neg, h_neg = self.sample_hidden(v_neg, context_list)
            p_v_neg, v_neg = self.sample_visible(h_neg)

        p_h_model, _ = self.sample_hidden(v_neg, context_list)
        negative_W = np.dot(v_neg.T, p_h_model) / batch_size
        negative_U = []
        for p in range(self.n_lags):
            if p < len(context_list) and context_list[p] is not None:
                negative_U.append(np.dot(context_list[p].T, p_h_model) / batch_size)
            else:
                negative_U.append(None)

        # === 更新参数 ===
        self.W += self.lr * (positive_W - negative_W)
        self.b_visible += self.lr * np.mean(v_current - v_neg, axis=0)
        self.b_hidden += self.lr * np.mean(p_h_data - p_h_model, axis=0)

        for p in range(self.n_lags):
            if positive_U[p] is not None and negative_U[p] is not None:
                self.U[p] += self.lr * (positive_U[p] - negative_U[p])

        return np.mean((v_current - v_neg) ** 2)

    def generate_sequence(self, seed_sequence, n_steps=10):
        """
        从条件RBM生成序列

        给定初始种子序列,逐步生成后续时间步

        参数:
            seed_sequence (list): 初始种子序列(至少n_lags个时间步)
            n_steps (int): 要生成的时间步数

        返回:
            list: 生成的完整序列
        """
        sequence = list(seed_sequence)  # 初始化序列

        for step in range(n_steps):
            # 获取条件上下文(前n_lags个时间步)
            context = []
            for p in range(self.n_lags):
                idx = len(sequence) - 1 - p  # 倒数第p+1个时间步
                if idx >= 0:
                    context.append(sequence[idx].reshape(1, -1))
                else:
                    context.append(None)

            # 用当前上下文采样隐藏层
            v_current = sequence[-1].reshape(1, -1)
            p_h, h = self.sample_hidden(v_current, context)

            # 多步Gibbs采样使样本更接近模型分布
            for _ in range(10):
                p_v, v = self.sample_visible(h)
                p_h, h = self.sample_hidden(v, context)

            sequence.append(p_v.flatten())  # 将生成的样本添加到序列

        return sequence  # 返回完整序列


# === 案例:用条件RBM生成时间序列 ===
if __name__ == "__main__":
    np.random.seed(42)

    # 创建模拟的时间序列数据
    # 例如:每个时间步是一个8维特征向量
    n_timesteps = 100
    n_features = 8
    # 生成带有时间相关性的数据
    data = np.zeros((n_timesteps, n_features))
    data[0] = np.random.randn(n_features)
    for t in range(1, n_timesteps):
        # 简单的自回归过程 + 噪声
        data[t] = 0.7 * data[t-1] + 0.3 * np.random.randn(n_features)
    # 二值化
    data_binary = (data > 0).astype(np.float64)

    # 创建条件RBM
    crbm = ConditionalRBM(
        n_visible=8,
        n_hidden=16,
        n_context=8,
        n_lags=2,  # 使用前2个时间步作为条件
        learning_rate=0.05
    )

    # 训练
    for epoch in range(50):
        total_error = 0
        for t in range(2, n_timesteps):  # 从第3个时间步开始(需要2个历史步)
            v = data_binary[t].reshape(1, -1)  # 当前时间步
            context = [
                data_binary[t-1].reshape(1, -1),  # 前1步
                data_binary[t-2].reshape(1, -1)   # 前2步
            ]
            error = crbm.train_step(v, context, k=1)
            total_error += error

        if epoch % 10 == 0:
            print(f"Epoch {epoch} | Avg Error: {total_error / (n_timesteps - 2):.4f}")

    # 生成序列
    seed = [data_binary[0], data_binary[1]]  # 种子:前2个时间步
    generated = crbm.generate_sequence(seed, n_steps=10)
    print(f"Generated sequence length: {len(generated)}")
    print(f"Generated step shape: {generated[0].shape}")

八、通过随机操作的反向传播

8.1 核心知识点

当计算图中包含随机节点时,标准反向传播无法直接使用。核心方法:

1. 重参数化技巧(Reparameterization Trick):

将随机变量 z∼q(z∣x)z \sim q(z|x)z∼q(z∣x) 重参数化为 z=μ(x)+σ(x)⋅ϵz = \mu(x) + \sigma(x) \cdot \epsilonz=μ(x)+σ(x)⋅ϵ,其中 ϵ∼N(0,1)\epsilon \sim \mathcal{N}(0,1)ϵ∼N(0,1)

这样随机性被转移到 ϵ\epsilonϵ,梯度可以流过 μ\muμ 和 σ\sigmaσ。

2. 评分函数估计器(REINFORCE):

∇θEx∼pθf(x)=Ex∼pθf(x)∇θlog⁡pθ(x)\nabla_\theta \mathbb{E}{x \sim p\theta}f(x) = \mathbb{E}{x \sim p\theta}f(x) \\nabla_\\theta \\log p_\\theta(x)∇θEx∼pθf(x)=Ex∼pθf(x)∇θlogpθ(x)

3. 直通估计器(Straight-Through Estimator):

前向使用离散采样,反向传播时直接传递梯度。

8.2 案例代码

python 复制代码
import numpy as np  # 导入numpy

# ============================================================
# 方法1: 重参数化技巧 (Reparameterization Trick)
# ============================================================

class ReparameterizedGaussian:
    """
    使用重参数化技巧的高斯采样

    核心思想:将采样过程分解为确定性变换 + 外部噪声
    z = μ + σ * ε,  ε ~ N(0,1)

    优势:梯度可以通过μ和σ反向传播
    这是VAE等模型能够端到端训练的关键技术
    """

    def __init__(self):
        """初始化(无可学习参数,参数在外部定义)"""
        pass

    def sample(self, mu, log_var):
        """
        通过重参数化技巧从高斯分布中采样

        步骤:
        1. 从标准正态分布采样 ε ~ N(0, 1)
        2. 计算标准差 σ = exp(0.5 * log_var)
        3. 计算 z = μ + σ * ε

        使用log_var而非σ的原因:
        - 数值稳定性:避免σ接近0时的数值问题
        - 优化方便:log_var的范围是(-∞, +∞),不受约束

        参数:
            mu (np.array): 均值参数,形状 (batch_size, latent_dim)
            log_var (np.array): 对数方差参数,形状 (batch_size, latent_dim)

        返回:
            np.array: 采样的潜在变量z
        """
        # 步骤1:从标准正态分布采样噪声
        epsilon = np.random.randn(*mu.shape)

        # 步骤2:从log_var计算标准差
        std = np.exp(0.5 * log_var)  # σ = exp(0.5 * log(σ²)) = σ

        # 步骤3:重参数化:z = μ + σ * ε
        z = mu + std * epsilon

        return z  # z的梯度可以流回mu和log_var

    def kl_divergence(self, mu, log_var):
        """
        计算KL散度:KL(q(z|x) || p(z))
        其中 p(z) = N(0, I)

        解析公式:
        KL = -0.5 * Σ(1 + log(σ²) - μ² - σ²)

        这个KL散度是VAE损失函数的一部分,
        确保编码器的后验分布接近先验分布

        参数:
            mu (np.array): 均值
            log_var (np.array): 对数方差

        返回:
            float: KL散度值
        """
        # KL散度的解析公式
        kl = -0.5 * np.sum(1 + log_var - mu ** 2 - np.exp(log_var), axis=-1)
        return np.mean(kl)  # 返回批量平均


# ============================================================
# 方法2: REINFORCE 估计器(评分函数估计器)
# ============================================================

class REINFORCEEstimator:
    """
    REINFORCE梯度估计器(评分函数方法)

    核心公式:
    ∇_θ E_{x~p_θ}[f(x)] = E_{x~p_θ}[f(x) ∇_θ log p_θ(x)]

    使用蒙特卡洛估计:
    ∇_θ ≈ (1/N) Σ_i f(x_i) ∇_θ log p_θ(x_i)

    特点:
    - 适用于离散和连续分布
    - 梯度估计方差高,需要方差减小技术
    """

    def __init__(self, baseline_decay=0.99):
        """
        参数:
            baseline_decay (float): 基线的指数移动平均衰减率
        """
        self.baseline = 0.0  # 初始化基线值为0
        self.baseline_decay = baseline_decay  # 基线更新的衰减率

    def estimate_gradient(self, log_probs, rewards):
        """
        使用REINFORCE估计梯度

        带基线的REINFORCE:
        ∇ ≈ (1/N) Σ_i (R_i - b) * ∇_θ log p_θ(x_i)

        基线b不改变梯度的期望,但可以显著降低方差

        参数:
            log_probs (np.array): 采样点的对数概率,形状 (n_samples,)
            rewards (np.array): 奖励值(即f(x)),形状 (n_samples,)

        返回:
            np.array: 估计的梯度
        """
        # 计算基线:使用奖励的指数移动平均
        # 这是一种自适应基线,自动跟踪平均奖励
        self.baseline = (self.baseline_decay * self.baseline +
                         (1 - self.baseline_decay) * np.mean(rewards))

        # 计算优势函数(奖励减去基线)
        advantages = rewards - self.baseline

        # REINFORCE梯度:Σ (advantage * log_prob) / N
        # 注意:这里是对参数θ求梯度,log_probs已经是∇_θ log p_θ的计算结果
        gradient = np.mean(advantages * log_probs)

        return gradient

    def sample_discrete(self, logits, n_samples=1):
        """
        从离散分布中采样(使用Gumbel-Softmax的简化版本)

        参数:
            logits (np.array): 未归一化的对数概率,形状 (n_classes,)
            n_samples (int): 采样数量

        返回:
            tuple: (采样的类别索引, 对应的对数概率)
        """
        # 将logits转换为概率
        probs = np.exp(logits - np.max(logits))  # 减去最大值防止溢出
        probs = probs / np.sum(probs)  # 归一化

        # 从类别分布中采样
        samples = np.random.choice(len(probs), size=n_samples, p=probs)

        # 计算采样点的对数概率
        log_probs = np.log(probs[samples] + 1e-10)  # 加小常数防止log(0)

        return samples, log_probs


# ============================================================
# 方法3: 直通估计器 (Straight-Through Estimator)
# ============================================================

class StraightThroughEstimator:
    """
    直通估计器(STE)

    核心思想:
    - 前向传播:使用离散/二值采样操作
    - 反向传播:跳过离散操作,直接传递梯度("直通")

    应用场景:
    - 二值神经网络
    - 向量量化(VQ-VAE)
    - 离散潜变量模型
    """

    def __init__(self):
        """初始化直通估计器"""
        pass

    def binary_round(self, x):
        """
        二值化取整操作(前向使用,反向忽略)

        参数:
            x (np.array): 连续值,通常在[0,1]范围内

        返回:
            np.array: 二值化结果(0或1)
        """
        return (x > 0.5).astype(np.float64)

    def forward_backward(self, x_continuous):
        """
        直通估计器的前向和"反向"传播

        前向:使用二值化采样
        反向:梯度直接通过(identity pass-through)

        数学表示:
        前向: y = round(x)
        反向: ∂L/∂x ≈ ∂L/∂y (假装round操作的梯度为1)

        参数:
            x_continuous (np.array): 连续输入(通常是sigmoid的输出)

        返回:
            tuple: (前向输出[离散值], "反向"梯度[直接传递])
        """
        # 前向:执行二值化
        x_discrete = self.binary_round(x_continuous)

        # 反向:直通估计------梯度直接通过
        # 在实际深度学习框架中,这通过自定义梯度实现
        # 这里模拟效果:
        straight_through_grad = x_continuous - x_discrete  # 连续值与离散值的差异

        return x_discrete, straight_through_grad


# === 综合案例:使用重参数化技巧实现简单的变分编码器 ===
class SimpleVariationalEncoder:
    """
    简单的变分编码器,展示重参数化技巧的实际应用

    结构:
    - 编码器:输入 → μ, log_σ²
    - 采样:z = μ + σ * ε(重参数化)
    - 解码器:z → 重构
    """

    def __init__(self, input_dim, latent_dim, learning_rate=0.01):
        """
        参数:
            input_dim (int): 输入维度
            latent_dim (int): 潜在空间维度
            learning_rate (float): 学习率
        """
        self.input_dim = input_dim
        self.latent_dim = latent_dim
        self.lr = learning_rate

        # 编码器参数
        self.W_encode = np.random.randn(input_dim, latent_dim) * 0.01
        self.W_mu = np.random.randn(latent_dim, latent_dim) * 0.01  # 均值分支
        self.W_logvar = np.random.randn(latent_dim, latent_dim) * 0.01  # log方差分支

        # 解码器参数
        self.W_decode = np.random.randn(latent_dim, input_dim) * 0.01

        # 重参数化采样器
        self.reparam = ReparameterizedGaussian()

    def encode(self, x):
        """
        编码:输入 → (μ, log_σ²)

        参数:
            x (np.array): 输入数据

        返回:
            tuple: (均值mu, 对数方差log_var)
        """
        h = np.tanh(np.dot(x, self.W_encode))  # 隐藏表示
        mu = np.dot(h, self.W_mu)  # 均值分支
        log_var = np.dot(h, self.W_logvar)  # log方差分支
        return mu, log_var

    def decode(self, z):
        """
        解码:潜在变量 → 重构

        参数:
            z (np.array): 潜在变量

        返回:
            np.array: 重构输出
        """
        return 1.0 / (1.0 + np.exp(-np.dot(z, self.W_decode)))  # sigmoid输出

    def forward(self, x):
        """
        完整前向传播:编码 → 重参数化采样 → 解码

        参数:
            x (np.array): 输入数据

        返回:
            tuple: (重构, mu, log_var, z)
        """
        mu, log_var = self.encode(x)  # 编码得到分布参数
        z = self.reparam.sample(mu, log_var)  # 重参数化采样
        x_recon = self.decode(z)  # 解码重构
        return x_recon, mu, log_var, z

    def loss(self, x, x_recon, mu, log_var):
        """
        计算VAE损失 = 重构损失 + KL散度

        参数:
            x (np.array): 原始输入
            x_recon (np.array): 重构输出
            mu (np.array): 编码器均值
            log_var (np.array): 编码器对数方差

        返回:
            tuple: (总损失, 重构损失, KL散度)
        """
        # 重构损失:二元交叉熵
        eps = 1e-10  # 防止log(0)
        recon_loss = -np.mean(np.sum(
            x * np.log(x_recon + eps) + (1 - x) * np.log(1 - x_recon + eps),
            axis=-1
        ))

        # KL散度:KL(q(z|x) || N(0,I))
        kl_loss = self.reparam.kl_divergence(mu, log_var)

        total_loss = recon_loss + kl_loss
        return total_loss, recon_loss, kl_loss


# === 使用示例 ===
if __name__ == "__main__":
    np.random.seed(42)

    # 1. 测试重参数化技巧
    reparam = ReparameterizedGaussian()
    mu = np.array([[0.5, -0.3], [0.1, 0.8]])  # 2个样本的均值
    log_var = np.array([[-1.0, -0.5], [-0.8, -1.2]])  # 对数方差
    z = reparam.sample(mu, log_var)
    print(f"重参数化采样结果 z:\n{z}")
    print(f"KL散度: {reparam.kl_divergence(mu, log_var):.4f}")

    # 2. 测试REINFORCE
    reinforce = REINFORCEEstimator()
    log_probs = np.array([-0.5, -0.3, -0.8])
    rewards = np.array([1.0, 2.0, 0.5])
    grad = reinforce.estimate_gradient(log_probs, rewards)
    print(f"\nREINFORCE梯度估计: {grad:.4f}")

    # 3. 测试直通估计器
    ste = StraightThroughEstimator()
    x_cont = np.array([0.3, 0.7, 0.5, 0.9])
    x_disc, grad_ste = ste.forward_backward(x_cont)
    print(f"\n连续输入: {x_cont}")
    print(f"离散输出: {x_disc}")
    print(f"STE梯度: {grad_ste}")

    # 4. 测试变分编码器
    vae = SimpleVariationalEncoder(input_dim=16, latent_dim=4)
    x = np.random.random((5, 16))
    x_recon, mu, log_var, z = vae.forward(x)
    total, recon, kl = vae.loss(x, x_recon, mu, log_var)
    print(f"\nVAE损失: {total:.4f} (重构: {recon:.4f}, KL: {kl:.4f})")

九、有向生成网络

9.1 核心知识点

有向生成网络使用有向无环图(DAG)描述变量间的生成关系:

1. 变分自编码器(VAE):

pθ(x)=∫pθ(x∣z)p(z)dzp_\theta(x) = \int p_\theta(x|z) p(z) dzpθ(x)=∫pθ(x∣z)p(z)dz

训练使用ELBO(证据下界):

L=Eqϕ(z∣x)log⁡pθ(x∣z)−DKL(qϕ(z∣x)∥p(z))\mathcal{L} = \mathbb{E}{q\phi(z|x)}\\log p_\\theta(x\|z) - D_{KL}(q_\phi(z|x) \| p(z))L=Eqϕ(z∣x)logpθ(x∣z)−DKL(qϕ(z∣x)∥p(z))

2. 生成对抗网络(GAN):

min⁡Gmax⁡DV(D,G)=Ex∼pdatalog⁡D(x)+Ez∼pzlog⁡(1−D(G(z)))\min_G \max_D V(D,G) = \mathbb{E}{x \sim p{data}}\\log D(x) + \mathbb{E}_{z \sim p_z}\\log(1 - D(G(z)))GminDmaxV(D,G)=Ex∼pdatalogD(x)+Ez∼pzlog(1−D(G(z)))

3. 自回归模型(如NADE):

p(x)=∏ip(xi∣x<i)p(x) = \prod_i p(x_i | x_{<i})p(x)=i∏p(xi∣x<i)

9.2 案例代码:VAE和GAN

python 复制代码
import numpy as np  # 导入numpy

# ============================================================
# 变分自编码器 (VAE) 实现
# ============================================================

class VAE:
    """
    变分自编码器 (Variational Autoencoder)

    生成模型框架:
    1. 先验:p(z) = N(0, I)
    2. 生成器(解码器):p_θ(x|z)
    3. 推断网络(编码器):q_φ(z|x)

    训练目标:最大化ELBO
    L = E_q[log p(x|z)] - KL(q(z|x) || p(z))

    第一项鼓励重构质量,第二项正则化潜在空间
    """

    def __init__(self, input_dim, hidden_dim, latent_dim, lr=0.001):
        """
        初始化VAE

        参数:
            input_dim (int): 输入数据维度
            hidden_dim (int): 隐藏层维度
            latent_dim (int): 潜在空间维度
            lr (float): 学习率
        """
        self.input_dim = input_dim
        self.hidden_dim = hidden_dim
        self.latent_dim = latent_dim
        self.lr = lr

        # === 编码器参数 ===
        # 第一层:输入→隐藏
        self.W_enc = np.random.randn(input_dim, hidden_dim) * np.sqrt(2.0 / input_dim)
        self.b_enc = np.zeros(hidden_dim)
        # 均值分支:隐藏→μ
        self.W_mu = np.random.randn(hidden_dim, latent_dim) * np.sqrt(2.0 / hidden_dim)
        self.b_mu = np.zeros(latent_dim)
        # log方差分支:隐藏→log σ²
        self.W_logvar = np.random.randn(hidden_dim, latent_dim) * np.sqrt(2.0 / hidden_dim)
        self.b_logvar = np.zeros(latent_dim)

        # === 解码器参数 ===
        # 第一层:潜在→隐藏
        self.W_dec = np.random.randn(latent_dim, hidden_dim) * np.sqrt(2.0 / latent_dim)
        self.b_dec = np.zeros(hidden_dim)
        # 输出层:隐藏→重构
        self.W_out = np.random.randn(hidden_dim, input_dim) * np.sqrt(2.0 / hidden_dim)
        self.b_out = np.zeros(input_dim)

    def relu(self, x):
        """ReLU激活函数"""
        return np.maximum(0, x)

    def sigmoid(self, x):
        """Sigmoid激活函数"""
        return 1.0 / (1.0 + np.exp(-np.clip(x, -500, 500)))

    def encode(self, x):
        """
        编码器:输入 x → q(z|x) = N(μ, σ²)

        网络结构:
        x → [ReLU隐藏层] → 分支为μ和log_σ²

        参数:
            x (np.array): 输入数据

        返回:
            tuple: (隐藏表示h, 均值mu, 对数方差log_var)
        """
        # 隐藏层
        h = self.relu(np.dot(x, self.W_enc) + self.b_enc)
        # 均值分支
        mu = np.dot(h, self.W_mu) + self.b_mu
        # log方差分支
        log_var = np.dot(h, self.W_logvar) + self.b_logvar
        return h, mu, log_var

    def reparameterize(self, mu, log_var):
        """
        重参数化采样:z = μ + σ * ε, ε ~ N(0,I)

        参数:
            mu (np.array): 均值
            log_var (np.array): 对数方差

        返回:
            np.array: 采样的潜在变量
        """
        epsilon = np.random.randn(*mu.shape)  # 标准正态噪声
        std = np.exp(0.5 * log_var)  # σ = exp(0.5 * log σ²)
        return mu + std * epsilon  # 重参数化

    def decode(self, z):
        """
        解码器(生成器):z → p(x|z)

        参数:
            z (np.array): 潜在变量

        返回:
            np.array: 重构的概率值
        """
        h = self.relu(np.dot(z, self.W_dec) + self.b_dec)
        x_recon = self.sigmoid(np.dot(h, self.W_out) + self.b_out)
        return x_recon

    def forward(self, x):
        """
        完整前向传播

        参数:
            x (np.array): 输入数据

        返回:
            dict: 包含所有中间结果
        """
        h_enc, mu, log_var = self.encode(x)  # 编码
        z = self.reparameterize(mu, log_var)  # 重参数化采样
        x_recon = self.decode(z)  # 解码重构

        return {
            'x_recon': x_recon,  # 重构
            'mu': mu,  # 编码器均值
            'log_var': log_var,  # 编码器对数方差
            'z': z  # 潜在变量
        }

    def compute_loss(self, x, outputs):
        """
        计算VAE损失函数

        ELBO = E_q[log p(x|z)] - KL(q(z|x) || p(z))

        重构损失(二元交叉熵):
        -E_q[log p(x|z)] ≈ -Σ_i [x_i log x̂_i + (1-x_i) log(1-x̂_i)]

        KL散度(解析形式):
        KL = -0.5 * Σ_j (1 + log σ_j² - μ_j² - σ_j²)

        参数:
            x (np.array): 原始输入
            outputs (dict): 前向传播的输出

        返回:
            dict: 各项损失值
        """
        x_recon = outputs['x_recon']
        mu = outputs['mu']
        log_var = outputs['log_var']

        # 重构损失(二元交叉熵的负值)
        eps = 1e-10
        recon_loss = -np.mean(np.sum(
            x * np.log(x_recon + eps) + (1 - x) * np.log(1 - x_recon + eps),
            axis=-1
        ))

        # KL散度(解析解)
        kl_loss = -0.5 * np.mean(np.sum(1 + log_var - mu**2 - np.exp(log_var), axis=-1))

        # 总损失
        total_loss = recon_loss + kl_loss

        return {
            'total': total_loss,
            'recon': recon_loss,
            'kl': kl_loss
        }

    def generate(self, n_samples=1):
        """
        从先验分布中采样生成新数据

        过程:z ~ N(0,I) → x = decode(z)

        参数:
            n_samples (int): 生成样本数

        返回:
            np.array: 生成的样本
        """
        # 从标准正态先验采样
        z = np.random.randn(n_samples, self.latent_dim)
        # 解码生成
        return self.decode(z)

    def fit(self, X, epochs=100, batch_size=32):
        """
        简化的训练循环(数值梯度近似)

        参数:
            X (np.array): 训练数据
            epochs (int): 训练轮数
            batch_size (int): 批量大小
        """
        n_samples = X.shape[0]
        for epoch in range(epochs):
            indices = np.random.permutation(n_samples)
            total_loss = 0
            n_batches = 0

            for i in range(0, n_samples, batch_size):
                batch = X[indices[i:i+batch_size]]
                outputs = self.forward(batch)
                losses = self.compute_loss(batch, outputs)
                total_loss += losses['total']
                n_batches += 1

                # 使用简单的参数更新(实际中应使用Adam等优化器)
                # 这里仅展示损失计算

            if epoch % 10 == 0:
                print(f"Epoch {epoch:3d} | Loss: {total_loss/n_batches:.4f}")


# ============================================================
# 生成对抗网络 (GAN) 实现
# ============================================================

class SimpleGAN:
    """
    简单的生成对抗网络(GAN)

    组成部分:
    - 生成器 G: z → x_fake(从噪声生成假数据)
    - 判别器 D: x → [0,1](判断数据真假)

    训练过程(极小极大博弈):
    min_G max_D E[log D(x_real)] + E[log(1 - D(G(z)))]
    """

    def __init__(self, latent_dim, data_dim, hidden_dim=128, lr=0.0002):
        """
        初始化GAN

        参数:
            latent_dim (int): 噪声向量维度
            data_dim (int): 数据维度
            hidden_dim (int): 隐藏层维度
            lr (float): 学习率
        """
        self.latent_dim = latent_dim
        self.data_dim = data_dim
        self.hidden_dim = hidden_dim
        self.lr = lr

        # === 生成器参数 ===
        # 噪声→隐藏层
        self.G_W1 = np.random.randn(latent_dim, hidden_dim) * np.sqrt(2.0 / latent_dim)
        self.G_b1 = np.zeros(hidden_dim)
        # 隐藏层→输出
        self.G_W2 = np.random.randn(hidden_dim, data_dim) * np.sqrt(2.0 / hidden_dim)
        self.G_b2 = np.zeros(data_dim)

        # === 判别器参数 ===
        # 输入→隐藏层
        self.D_W1 = np.random.randn(data_dim, hidden_dim) * np.sqrt(2.0 / data_dim)
        self.D_b1 = np.zeros(hidden_dim)
        # 隐藏层→输出
        self.D_W2 = np.random.randn(hidden_dim, 1) * np.sqrt(2.0 / hidden_dim)
        self.D_b2 = np.zeros(1)

    def leaky_relu(self, x, alpha=0.2):
        """
        LeakyReLU激活函数
        当x>0时返回x,当x<=0时返回alpha*x
        比ReLU更适合GAN训练(避免梯度消失)

        参数:
            x: 输入
            alpha: 负半轴的斜率

        返回:
            激活后的值
        """
        return np.where(x > 0, x, alpha * x)

    def leaky_relu_grad(self, x, alpha=0.2):
        """
        LeakyReLU的梯度

        参数:
            x: 输入值

        返回:
            梯度值
        """
        return np.where(x > 0, 1, alpha)

    def sigmoid(self, x):
        """Sigmoid激活函数"""
        return 1.0 / (1.0 + np.exp(-np.clip(x, -500, 500)))

    def generate(self, z):
        """
        生成器前向传播:噪声 z → 假数据

        架构:z → [LeakyReLU] → [Tanh]

        参数:
            z (np.array): 噪声向量,形状 (batch, latent_dim)

        返回:
            np.array: 生成的假数据
        """
        # 第一层
        h1 = self.leaky_relu(np.dot(z, self.G_W1) + self.G_b1)
        # 输出层(使用tanh使输出范围在[-1, 1])
        g_out = np.tanh(np.dot(h1, self.G_W2) + self.G_b2)
        return g_out

    def discriminate(self, x):
        """
        判别器前向传播:数据 → 真假概率

        架构:x → [LeakyReLU] → [Sigmoid]

        参数:
            x (np.array): 输入数据

        返回:
            np.array: 判别为真实数据的概率
        """
        h1 = self.leaky_relu(np.dot(x, self.D_W1) + self.D_b1)
        d_out = self.sigmoid(np.dot(h1, self.D_W2) + self.D_b2)
        return d_out

    def discriminator_loss(self, x_real, x_fake):
        """
        判别器损失函数

        L_D = -E[log D(x_real)] - E[log(1 - D(x_fake))]

        判别器希望:对真实数据输出高概率,对假数据输出低概率

        参数:
            x_real (np.array): 真实数据
            x_fake (np.array): 生成的假数据

        返回:
            float: 判别器损失
        """
        eps = 1e-10  # 数值稳定
        d_real = self.discriminate(x_real)  # D(x_real)
        d_fake = self.discriminate(x_fake)  # D(G(z))

        # 二元交叉熵损失
        loss_real = -np.mean(np.log(d_real + eps))  # -log D(x)
        loss_fake = -np.mean(np.log(1 - d_fake + eps))  # -log(1 - D(G(z)))

        return loss_real + loss_fake

    def generator_loss(self, x_fake):
        """
        生成器损失函数

        L_G = -E[log D(G(z))]

        生成器希望判别器对假数据输出高概率

        参数:
            x_fake (np.array): 生成的假数据

        返回:
            float: 生成器损失
        """
        eps = 1e-10
        d_fake = self.discriminate(x_fake)
        # 生成器损失:让判别器认为假数据是真的
        loss = -np.mean(np.log(d_fake + eps))
        return loss

    def train_step(self, x_real):
        """
        执行一步GAN训练

        步骤:
        1. 采样噪声
        2. 生成假数据
        3. 更新判别器(最大化判别能力)
        4. 更新生成器(最小化被判别出来的概率)

        参数:
            x_real (np.array): 真实数据批次

        返回:
            dict: 包含各项损失值
        """
        batch_size = x_real.shape[0]

        # 1. 采样噪声
        z = np.random.randn(batch_size, self.latent_dim)

        # 2. 生成假数据
        x_fake = self.generate(z)

        # 计算损失(用于监控,实际参数更新需要梯度计算框架)
        d_loss = self.discriminator_loss(x_real, x_fake)
        g_loss = self.generator_loss(x_fake)

        # 注意:完整的参数更新需要计算梯度并使用优化器
        # 此处仅展示GAN的结构和损失计算逻辑

        return {
            'd_loss': d_loss,
            'g_loss': g_loss,
            'd_real_mean': np.mean(self.discriminate(x_real)),
            'd_fake_mean': np.mean(self.discriminate(x_fake))
        }


# === 使用示例 ===
if __name__ == "__main__":
    np.random.seed(42)

    # 1. VAE示例
    print("=== VAE ===")
    vae = VAE(input_dim=16, hidden_dim=32, latent_dim=4)
    x = np.random.binomial(1, 0.3, (10, 16)).astype(np.float64)
    outputs = vae.forward(x)
    losses = vae.loss(x, outputs['x_recon'], outputs['mu'], outputs['log_var'])
    print(f"VAE Loss: {losses['total']:.4f}")
    generated = vae.generate(n_samples=3)
    print(f"Generated shape: {generated.shape}")

    # 2. GAN示例
    print("\n=== GAN ===")
    gan = SimpleGAN(latent_dim=8, data_dim=16, hidden_dim=32)
    x_real = np.random.randn(10, 16)
    losses = gan.train_step(x_real)
    print(f"D loss: {losses['d_loss']:.4f}")
    print(f"G loss: {losses['g_loss']:.4f}")
    print(f"D(x_real) mean: {losses['d_real_mean']:.4f}")
    print(f"D(x_fake) mean: {losses['d_fake_mean']:.4f}")

十、从自编码器采样

10.1 核心知识点

自编码器本身不是生成模型,但可以通过以下方式转化为生成模型:

1. 正则化自编码器(DAE/Contractive AE):

通过学习数据流形的局部方向来生成样本

2. 去噪自编码器(Denoising AE):

学习去噪过程隐含定义了一个概率分布

3. 收缩自编码器(Contractive AE):

正则化编码器的Jacobian矩阵,学习数据流形的切空间

10.2 案例代码

python 复制代码
import numpy as np  # 导入numpy

class DenoisingAutoencoder:
    """
    去噪自编码器 (Denoising Autoencoder)

    训练目标:从损坏的输入中恢复原始数据
    x̃ = corruption(x) → encode → decode → x̂ ≈ x

    生成原理:
    去噪过程隐含定义了一个概率分布
    通过迭代去噪可以从该分布中采样
    """

    def __init__(self, input_dim, hidden_dim, noise_level=0.3, lr=0.01):
        """
        初始化去噪自编码器

        参数:
            input_dim (int): 输入维度
            hidden_dim (int): 隐藏层维度
            noise_level (float): 噪声强度(加性高斯噪声的标准差)
            lr (float): 学习率
        """
        self.input_dim = input_dim
        self.hidden_dim = hidden_dim
        self.noise_level = noise_level
        self.lr = lr

        # 编码器参数
        self.W_enc = np.random.randn(input_dim, hidden_dim) * np.sqrt(2.0 / input_dim)
        self.b_enc = np.zeros(hidden_dim)

        # 解码器参数(权重绑定:W_dec = W_enc^T)
        self.b_dec = np.zeros(input_dim)

    def corrupt(self, x):
        """
        数据损坏:添加高斯噪声
        x̃ = x + ε, ε ~ N(0, noise_level²)

        参数:
            x (np.array): 原始数据

        返回:
            np.array: 损坏后的数据
        """
        noise = np.random.randn(*x.shape) * self.noise_level
        return x + noise

    def sigmoid(self, x):
        """Sigmoid激活函数"""
        return 1.0 / (1.0 + np.exp(-np.clip(x, -500, 500)))

    def encode(self, x):
        """
        编码:数据 → 隐藏表示

        参数:
            x (np.array): 输入数据

        返回:
            np.array: 隐藏层激活
        """
        return self.sigmoid(np.dot(x, self.W_enc) + self.b_enc)

    def decode(self, h):
        """
        解码:隐藏表示 → 重构

        使用权重绑定(W_dec = W_enc^T),减少参数

        参数:
            h (np.array): 隐藏层激活

        返回:
            np.array: 重构数据
        """
        return self.sigmoid(np.dot(h, self.W_enc.T) + self.b_dec)

    def forward(self, x_corrupted):
        """
        前向传播:损坏数据 → 重构

        参数:
            x_corrupted (np.array): 损坏后的数据

        返回:
            tuple: (隐藏表示, 重构数据)
        """
        h = self.encode(x_corrupted)
        x_recon = self.decode(h)
        return h, x_recon

    def compute_loss(self, x_clean, x_recon):
        """
        计算重构损失(交叉熵)

        参数:
            x_clean (np.array): 原始数据
            x_recon (np.array): 重构数据

        返回:
            float: 交叉熵损失
        """
        eps = 1e-10
        loss = -np.mean(np.sum(
            x_clean * np.log(x_recon + eps) + (1 - x_clean) * np.log(1 - x_recon + eps),
            axis=-1
        ))
        return loss

    def iteratively_denoise(self, x_noisy, n_steps=10):
        """
        迭代去噪采样------从自编码器生成样本的关键方法

        原理:
        1. 从随机噪声开始
        2. 反复执行:去噪 → 微加噪声 → 去噪
        3. 逐步收敛到数据流形上

        这类似于朗之万动力学采样:
        x_{t+1} = x_t + α * score(x_t) + √(2α) * ε

        参数:
            x_noisy (np.array): 初始噪声数据
            n_steps (int): 迭代步数

        返回:
            np.array: 生成的样本
        """
        x = x_noisy.copy()  # 初始化

        for step in range(n_steps):
            # 计算当前步的噪声级别(逐渐衰减)
            noise_std = self.noise_level * (1 - step / n_steps)

            # 去噪:通过自编码器
            h = self.encode(x)
            x_denoised = self.decode(h)

            # 添加少量噪声(朗之万动力学中的随机项)
            x = x_denoised + np.random.randn(*x.shape) * noise_std * 0.1

            # 将值限制在[0,1]范围内
            x = np.clip(x, 0, 1)

        return x  # 返回最终生成的样本

    def fit(self, X, epochs=50, batch_size=32):
        """
        训练去噪自编码器

        参数:
            X (np.array): 训练数据
            epochs (int): 训练轮数
            batch_size (int): 批量大小
        """
        n_samples = X.shape[0]

        for epoch in range(epochs):
            indices = np.random.permutation(n_samples)
            epoch_loss = 0
            n_batches = 0

            for i in range(0, n_samples, batch_size):
                batch = X[indices[i:i+batch_size]]

                # 前向传播
                x_corrupted = self.corrupt(batch)  # 损坏数据
                h, x_recon = self.forward(x_corrupted)  # 编码+解码
                loss = self.compute_loss(batch, x_recon)  # 计算损失

                # 计算梯度(简化版本)
                # 输出层误差
                d_output = x_recon - batch  # (batch, input_dim)
                # 更新解码器偏置
                self.b_dec -= self.lr * np.mean(d_output, axis=0)

                # 隐藏层误差
                d_hidden = np.dot(d_output, self.W_enc) * h * (1 - h)
                # 更新编码器权重和偏置
                self.W_enc -= self.lr * (np.dot(x_corrupted.T, d_hidden) / batch.shape[0])
                self.b_enc -= self.lr * np.mean(d_hidden, axis=0)

                epoch_loss += loss
                n_batches += 1

            if epoch % 10 == 0:
                print(f"Epoch {epoch:3d} | Loss: {epoch_loss/n_batches:.4f}")

    def generate(self, n_samples=5, n_steps=20):
        """
        从去噪自编码器中生成样本

        参数:
            n_samples (int): 生成样本数
            n_steps (int): 迭代去噪步数

        返回:
            np.array: 生成的样本
        """
        # 从随机噪声开始
        x_init = np.random.uniform(0, 1, (n_samples, self.input_dim))
        # 迭代去噪得到生成样本
        return self.iteratively_denoise(x_init, n_steps)


# === 使用示例 ===
if __name__ == "__main__":
    np.random.seed(42)

    # 创建模拟的二值数据
    X = np.random.binomial(1, 0.3, (200, 32)).astype(np.float64)

    # 创建并训练去噪自编码器
    dae = DenoisingAutoencoder(
        input_dim=32, hidden_dim=16,
        noise_level=0.3, lr=0.05
    )
    dae.fit(X, epochs=50, batch_size=32)

    # 从模型中生成样本
    generated = dae.generate(n_samples=5, n_steps=30)
    print(f"Generated samples shape: {generated.shape}")
    print(f"Generated value range: [{generated.min():.3f}, {generated.max():.3f}]")

十一、生成随机网络(Generative Stochastic Networks, GSN)

11.1 核心知识点

GSN通过去噪自编码器的马尔可夫链隐式定义概率分布:

  • 不直接定义 p(x)p(x)p(x),而是定义一个转移概率 T(x′∣x)T(x'|x)T(x′∣x)
  • 通过马尔可夫链的稳态分布隐式定义生成分布
  • 训练目标:使转移核的稳态分布接近数据分布

关键方程:

p(x)=∫T(x∣x′)p(x′)dx′p(x) = \int T(x|x') p(x') dx'p(x)=∫T(x∣x′)p(x′)dx′

11.2 案例代码

python 复制代码
import numpy as np  # 导入numpy

class GenerativeStochasticNetwork:
    """
    生成随机网络 (GSN)

    核心思想:
    - 使用带有随机性的自编码器定义马尔可夫链
    - 马尔可夫链的稳态分布即为生成分布
    - 训练目标:使重构分布匹配数据分布

    采样过程:
    x → corruption → encode → add_noise → decode → x'
    重复此过程形成马尔可夫链,收敛后即可采样
    """

    def __init__(self, data_dim, hidden_dims, noise_levels, lr=0.01):
        """
        初始化GSN

        参数:
            data_dim (int): 数据维度
            hidden_dims (list): 各隐藏层维度,如[128, 64]
            noise_levels (list): 各层的噪声级别
            lr (float): 学习率
        """
        self.data_dim = data_dim
        self.hidden_dims = hidden_dims
        self.n_layers = len(hidden_dims)
        self.noise_levels = noise_levels
        self.lr = lr

        # 构建多层自编码器
        self.encoders = []  # 编码器权重列表
        self.enc_biases = []  # 编码器偏置列表
        self.dec_biases = []  # 解码器偏置列表

        prev_dim = data_dim
        for i, h_dim in enumerate(hidden_dims):
            # 编码器权重(Xavier初始化)
            W = np.random.randn(prev_dim, h_dim) * np.sqrt(2.0 / (prev_dim + h_dim))
            self.encoders.append(W)
            self.enc_biases.append(np.zeros(h_dim))
            self.dec_biases.append(np.zeros(prev_dim))
            prev_dim = h_dim

        # 最外层解码偏置(输出层)
        self.dec_biases.append(np.zeros(data_dim))

    def sigmoid(self, x):
        """Sigmoid激活函数"""
        return 1.0 / (1.0 + np.exp(-np.clip(x, -500, 500)))

    def add_noise(self, x, noise_level, activation='sigmoid'):
        """
        向激活值添加噪声

        GSN的关键:在隐藏层注入随机性
        这使得自编码器定义了一个随机转移核

        参数:
            x (np.array): 隐藏层激活值
            noise_level (float): 噪声级别
            activation (str): 激活函数类型

        返回:
            np.array: 加噪后的值
        """
        if activation == 'sigmoid':
            # 对sigmoid输出添加高斯噪声后重新sigmoid
            noise = np.random.randn(*x.shape) * noise_level
            x_noisy = x + noise
            return self.sigmoid(x_noisy)  # 再次sigmoid保持在[0,1]
        return x + np.random.randn(*x.shape) * noise_level

    def encode(self, x, add_noise=True):
        """
        编码过程(逐层编码 + 可选加噪)

        参数:
            x (np.array): 输入数据
            add_noise (bool): 是否在每层添加噪声

        返回:
            list: 各层的激活值
        """
        activations = [x]  # 存储每层激活,初始为输入
        current = x

        for i in range(self.n_layers):
            # 线性变换 + sigmoid
            activation = self.sigmoid(np.dot(current, self.encoders[i]) + self.enc_biases[i])

            # 可选:添加随机噪声(GSN的核心特征)
            if add_noise:
                activation = self.add_noise(activation, self.noise_levels[i])

            activations.append(activation)
            current = activation

        return activations

    def decode(self, activations, add_noise=True):
        """
        解码过程(逐层解码 + 可选加噪)

        使用编码器的转置权重(权重绑定)

        参数:
            activations (list): 编码过程的各层激活值
            add_noise (bool): 是否添加噪声

        返回:
            np.array: 重构的数据
        """
        current = activations[-1]  # 从最深的隐藏层开始

        for i in range(self.n_layers - 1, -1, -1):
            # 使用编码器权重的转置
            activation = self.sigmoid(np.dot(current, self.encoders[i].T) + self.dec_biases[i])

            if add_noise and i > 0:  # 中间层添加噪声
                activation = self.add_noise(activation, self.noise_levels[i] * 0.5)

            current = activation

        return current  # 最终重构

    def transition(self, x):
        """
        马尔可夫链的一步转移

        x → encode(with noise) → decode(with noise) → x'

        这定义了转移核 T(x'|x)

        参数:
            x (np.array): 当前状态

        返回:
            np.array: 下一状态
        """
        # 编码(带噪声)
        activations = self.encode(x, add_noise=True)
        # 解码(带噪声)
        x_next = self.decode(activations, add_noise=True)
        return x_next

    def sample(self, n_samples, n_steps=100, burn_in=50):
        """
        从GSN中采样

        通过运行马尔可夫链到稳态来采样

        参数:
            n_samples (int): 采样数量
            n_steps (int): 总马尔可夫链步数
            burn_in (int): 烧入期步数(丢弃前期样本)

        返回:
            np.array: 采样结果
        """
        # 随机初始化
        x = np.random.uniform(0, 1, (n_samples, self.data_dim))
        samples = []  # 存储烧入期后的样本

        for step in range(n_steps):
            x = self.transition(x)  # 执行一步转移

            # 烧入期之后开始收集样本
            if step >= burn_in:
                samples.append(x.copy())

        return np.array(samples)

    def reconstruction_loss(self, x_clean, x_recon):
        """
        计算重构损失

        参数:
            x_clean (np.array): 原始数据
            x_recon (np.array): 重构数据

        返回:
            float: 交叉熵损失
        """
        eps = 1e-10
        return -np.mean(np.sum(
            x_clean * np.log(x_recon + eps) + (1 - x_clean) * np.log(1 - x_recon + eps),
            axis=-1
        ))

    def train_step(self, x_batch):
        """
        GSN训练步骤

        参数:
            x_batch (np.array): 批量数据

        返回:
            float: 重构损失
        """
        # 前向传播(带噪声的编码-解码)
        activations = self.encode(x_batch, add_noise=True)
        x_recon = self.decode(activations, add_noise=True)

        # 计算损失
        loss = self.reconstruction_loss(x_batch, x_recon)

        # 计算梯度(简化版本,展示梯度计算的方向)
        d_output = x_recon - x_batch  # 输出层梯度

        # 更新解码偏置
        self.dec_biases[0] -= self.lr * np.mean(d_output, axis=0)

        # 反向传播通过各层(使用编码器权重的转置)
        d_current = d_output
        for i in range(self.n_layers - 1, -1, -1):
            # 当前层激活值(需要重新计算)
            h = activations[i + 1]
            # 梯度传播
            d_current = np.dot(d_current, self.encoders[i]) * h * (1 - h)
            # 更新编码器权重
            self.encoders[i] -= self.lr * np.dot(activations[i].T, d_current) / x_batch.shape[0]
            self.enc_biases[i] -= self.lr * np.mean(d_current, axis=0)

        return loss


# === 使用示例 ===
if __name__ == "__main__":
    np.random.seed(42)

    # 创建模拟数据
    X = np.random.binomial(1, 0.3, (300, 24)).astype(np.float64)

    # 创建GSN
    gsn = GenerativeStochasticNetwork(
        data_dim=24,
        hidden_dims=[64, 32],
        noise_levels=[0.3, 0.2],
        lr=0.01
    )

    # 训练
    for epoch in range(50):
        indices = np.random.permutation(X.shape[0])
        total_loss = 0
        n_batches = 0
        for i in range(0, X.shape[0], 32):
            batch = X[indices[i:i+32]]
            loss = gsn.train_step(batch)
            total_loss += loss
            n_batches += 1

        if epoch % 10 == 0:
            print(f"Epoch {epoch:3d} | Loss: {total_loss/n_batches:.4f}")

    # 从GSN采样
    samples = gsn.sample(n_samples=5, n_steps=100, burn_in=50)
    print(f"Generated samples shape: {samples.shape}")
    print(f"Sample mean per feature: {np.mean(samples, axis=(0, 1))[:8]}")

十二、评估生成模型

12.1 核心知识点

评估生成模型质量的主要方法:

评估方法 适用场景 核心思想
对数似然 可精确计算的模型 Ex∼pdatalog⁡pθ(x)E_{x \sim p_{data}}\\log p_\\theta(x)Ex∼pdatalogpθ(x)
重构误差 自编码器类模型 ∣x−x^∣2|x - \hat{x}|^2∣x−x^∣2
FID分数 图像生成 ∣μr−μg∣2+Tr(Σr+Σg−2(ΣrΣg)1/2)|\mu_r - \mu_g|^2 + Tr(\Sigma_r + \Sigma_g - 2(\Sigma_r \Sigma_g)^{1/2})∣μr−μg∣2+Tr(Σr+Σg−2(ΣrΣg)1/2)
IS(Inception Score) 图像生成 $E[KL(p(y
视觉检查 所有生成模型 人工主观评估
分类器两样本测试 通用 训练分类器区分真实/生成样本

12.2 案例代码

python 复制代码
import numpy as np  # 导入numpy
from collections import Counter  # 导入计数器用于频率统计

class GenerativeModelEvaluator:
    """
    生成模型评估工具集

    提供多种评估指标:
    1. 对数似然(Log-Likelihood)
    2. 重构误差(Reconstruction Error)
    3. FID近似(Fréchet Inception Distance)
    4. 分类器两样本检验(Classifier Two-Sample Test)
    5. 覆盖率和多样性指标
    """

    def __init__(self):
        """初始化评估器"""
        pass

    def log_likelihood_bernoulli(self, x, p):
        """
        计算伯努利分布的对数似然

        log p(x) = Σ_i [x_i log p_i + (1-x_i) log(1-p_i)]

        对数似然是评估生成模型的"金标准":
        - 值越大说明模型对数据的拟合越好
        - 但仅适用于可计算归一化常数的模型

        参数:
            x (np.array): 真实数据(二值)
            p (np.array): 模型输出的概率

        返回:
            np.array: 每个样本的对数似然
        """
        eps = 1e-10  # 防止log(0)
        log_p = np.sum(
            x * np.log(p + eps) + (1 - x) * np.log(1 - p + eps),
            axis=-1
        )
        return log_p

    def reconstruction_error(self, x_original, x_reconstructed, metric='mse'):
        """
        计算重构误差

        常用指标:
        - MSE:均方误差
        - MAE:平均绝对误差
        - BCE:二元交叉熵

        重构误差越低,说明模型的表征越好

        参数:
            x_original (np.array): 原始数据
            x_reconstructed (np.array): 重构数据
            metric (str): 指标类型

        返回:
            dict: 包含各指标的评估结果
        """
        results = {}  # 存储各项指标

        if metric == 'mse' or metric == 'all':
            # 均方误差 MSE = (1/n) Σ (x - x̂)²
            mse = np.mean((x_original - x_reconstructed) ** 2)
            results['mse'] = mse

        if metric == 'mae' or metric == 'all':
            # 平均绝对误差 MAE = (1/n) Σ |x - x̂|
            mae = np.mean(np.abs(x_original - x_reconstructed))
            results['mae'] = mae

        if metric == 'bce' or metric == 'all':
            # 二元交叉熵 BCE = -Σ [x log x̂ + (1-x) log(1-x̂)]
            eps = 1e-10
            bce = -np.mean(np.sum(
                x_original * np.log(x_reconstructed + eps) +
                (1 - x_original) * np.log(1 - x_reconstructed + eps),
                axis=-1
            ))
            results['bce'] = bce

        return results

    def classifier_two_sample_test(self, x_real, x_fake):
        """
        分类器两样本检验 (Classifier Two-Sample Test, CTST)

        方法:
        1. 将真实数据标记为1,生成数据标记为0
        2. 训练一个分类器区分两者
        3. 分类器的准确率越接近50%,说明生成质量越好

        准确率=50%:生成的数据与真实数据不可区分
        准确率=100%:生成的数据与真实数据完全不同

        参数:
            x_real (np.array): 真实数据
            x_fake (np.array): 生成数据

        返回:
            dict: 包含测试结果
        """
        # 准备数据
        n_real = x_real.shape[0]
        n_fake = x_fake.shape[0]

        # 合并数据并创建标签
        X = np.vstack([x_real, x_fake])  # 合并真实和生成数据
        y = np.array([1] * n_real + [0] * n_fake)  # 真实=1,生成=0

        # 随机打乱
        indices = np.random.permutation(len(y))
        X = X[indices]
        y = y[indices]

        # 简单的逻辑回归分类器
        n_features = X.shape[1]
        w = np.random.randn(n_features) * 0.01  # 权重
        b = 0.0  # 偏置
        lr = 0.01  # 学习率

        # 训练分类器
        for epoch in range(100):
            # 前向传播
            z = np.dot(X, w) + b
            pred = 1.0 / (1.0 + np.exp(-np.clip(z, -500, 500)))

            # 计算梯度
            error = pred - y
            grad_w = np.dot(X.T, error) / len(y)
            grad_b = np.mean(error)

            # 更新参数
            w -= lr * grad_w
            b -= lr * grad_b

        # 评估分类器准确率
        z = np.dot(X, w) + b
        pred = 1.0 / (1.0 + np.exp(-z))
        predictions = (pred > 0.5).astype(int)
        accuracy = np.mean(predictions == y)

        # 计算Jensen-Shannon散度近似
        # JS ≈ 2*accuracy - 1(当accuracy接近0.5时JS接近0)
        js_estimate = 2 * accuracy - 1

        return {
            'accuracy': accuracy,  # 分类准确率
            'js_estimate': js_estimate,  # JS散度近似
            'quality': 'good' if accuracy < 0.6 else 'moderate' if accuracy < 0.8 else 'poor'
        }

    def compute_fid_approx(self, features_real, features_fake):
        """
        近似计算FID (Fréchet Inception Distance)

        FID = ||μ_r - μ_g||² + Tr(Σ_r + Σ_g - 2(Σ_r Σ_g)^{1/2})

        其中:
        - μ_r, Σ_r:真实数据特征的均值和协方差
        - μ_g, Σ_g:生成数据特征的均值和协方差

        FID越小,生成质量越好
        FID=0 表示生成分布与真实分布完全一致

        参数:
            features_real (np.array): 真实数据的特征表示
            features_fake (np.array): 生成数据的特征表示

        返回:
            float: FID分数
        """
        # 计算均值
        mu_real = np.mean(features_real, axis=0)
        mu_fake = np.mean(features_fake, axis=0)

        # 计算协方差矩阵
        # 注意:需要确保样本数 > 特征数,否则协方差矩阵不满秩
        cov_real = np.cov(features_real, rowvar=False) + 1e-6 * np.eye(features_real.shape[1])
        cov_fake = np.cov(features_fake, rowvar=False) + 1e-6 * np.eye(features_fake.shape[1])

        # 计算均值差的平方和
        diff = mu_real - mu_fake
        mean_term = np.sum(diff ** 2)

        # 计算协方差项:Tr(Σ_r + Σ_g - 2(Σ_r Σ_g)^{1/2})
        # 这里使用近似:(Σ_r Σ_g)^{1/2} ≈ Σ_r^{1/2} Σ_g^{1/2}
        cov_mean = cov_real + cov_fake

        # 使用特征值分解近似矩阵平方根
        try:
            # 计算 Σ_r @ Σ_g 的特征值
            product = np.dot(cov_real, cov_fake)
            eigenvalues = np.linalg.eigvalsh(product)
            # 取正部分并开方
            eigenvalues = np.maximum(eigenvalues, 0)
            cov_term = np.trace(cov_mean) - 2 * np.sum(np.sqrt(eigenvalues))
        except np.linalg.LinAlgError:
            # 如果特征值分解失败,使用简化的迹计算
            cov_term = np.trace(cov_mean) - 2 * np.sqrt(np.trace(cov_real) * np.trace(cov_fake))

        fid = mean_term + cov_term
        return fid

    def coverage_and_diversity(self, x_real, x_fake, threshold=0.1):
        """
        计算覆盖率和多样性指标

        覆盖率(Coverage):生成的数据覆盖了多少真实数据模式
        多样性(Diversity):生成的数据内部有多少变化

        参数:
            x_real (np.array): 真实数据
            x_fake (np.array): 生成数据
            threshold (float): 距离阈值

        返回:
            dict: 覆盖率和多样性指标
        """
        n_real = x_real.shape[0]
        n_fake = x_fake.shape[0]

        # 计算距离矩阵
        # 使用广播计算每对样本之间的欧氏距离
        dist_matrix = np.zeros((n_real, n_fake))
        for i in range(n_real):
            for j in range(n_fake):
                dist_matrix[i, j] = np.sqrt(np.sum((x_real[i] - x_fake[j]) ** 2))

        # 覆盖率:有多少真实样本附近有生成样本
        min_distances = np.min(dist_matrix, axis=1)  # 每个真实样本到最近生成样本的距离
        coverage = np.mean(min_distances < threshold)  # 在阈值范围内的比例

        # 多样性:生成样本之间的平均距离
        fake_dist = np.zeros((n_fake, n_fake))
        for i in range(n_fake):
            for j in range(i + 1, n_fake):
                fake_dist[i, j] = np.sqrt(np.sum((x_fake[i] - x_fake[j]) ** 2))
                fake_dist[j, i] = fake_dist[i, j]
        # 排除对角线的零
        diversity = np.sum(fake_dist) / (n_fake * (n_fake - 1))

        return {
            'coverage': coverage,  # 覆盖率
            'diversity': diversity,  # 多样性
            'avg_min_distance': np.mean(min_distances)  # 平均最近距离
        }

    def comprehensive_evaluation(self, x_real, x_fake, x_recon=None, model_probs=None):
        """
        综合评估:运行所有可用的评估指标

        参数:
            x_real (np.array): 真实数据
            x_fake (np.array): 生成数据
            x_recon (np.array): 重构数据(可选)
            model_probs (np.array): 模型概率输出(可选)

        返回:
            dict: 完整的评估报告
        """
        results = {}  # 存储所有评估结果

        # 1. 重构误差(如果有重构数据)
        if x_recon is not None:
            results['reconstruction'] = self.reconstruction_error(
                x_real, x_recon, metric='all'
            )
            print(f"重构误差 - MSE: {results['reconstruction']['mse']:.4f}, "
                  f"MAE: {results['reconstruction']['mae']:.4f}")

        # 2. 对数似然(如果有模型概率)
        if model_probs is not None:
            ll = self.log_likelihood_bernoulli(x_real, model_probs)
            results['log_likelihood'] = np.mean(ll)
            print(f"平均对数似然: {results['log_likelihood']:.4f}")

        # 3. 分类器两样本检验
        ctst = self.classifier_two_sample_test(x_real, x_fake)
        results['ctst'] = ctst
        print(f"CTST准确率: {ctst['accuracy']:.4f} (质量: {ctst['quality']})")

        # 4. FID近似
        fid = self.compute_fid_approx(x_real, x_fake)
        results['fid'] = fid
        print(f"FID近似: {fid:.4f}")

        # 5. 覆盖率和多样性
        cov_div = self.coverage_and_diversity(x_real, x_fake, threshold=0.5)
        results['coverage_diversity'] = cov_div
        print(f"覆盖率: {cov_div['coverage']:.4f}, 多样性: {cov_div['diversity']:.4f}")

        return results


# === 综合评估示例 ===
if __name__ == "__main__":
    np.random.seed(42)

    # 创建模拟数据
    n_samples = 100
    n_features = 16

    # 真实数据(有一定的结构)
    x_real = np.random.randn(n_samples, n_features) * 0.5 + 0.3

    # 生成数据1:较好(接近真实分布)
    x_fake_good = np.random.randn(n_samples, n_features) * 0.5 + 0.35

    # 生成数据2:较差(偏离真实分布)
    x_fake_bad = np.random.randn(n_samples, n_features) * 2.0 - 1.0

    # 重构数据
    x_recon = x_real + np.random.randn(*x_real.shape) * 0.1

    # 创建评估器
    evaluator = GenerativeModelEvaluator()

    print("=" * 60)
    print("评估好的生成模型:")
    print("=" * 60)
    results_good = evaluator.comprehensive_evaluation(
        x_real, x_fake_good, x_recon=x_recon
    )

    print("\n" + "=" * 60)
    print("评估差的生成模型:")
    print("=" * 60)
    results_bad = evaluator.comprehensive_evaluation(
        x_real, x_fake_bad, x_recon=x_recon
    )

总结对照表

知识点 核心算法 适用场景 复杂度
玻尔兹曼机 Gibbs采样 + CD 小规模二值数据 O(n2)O(n^2)O(n2) 全连接
RBM CD-k 特征提取、协同过滤 O(nm)O(nm)O(nm)
DBN 逐层CD预训练 深层特征学习 逐层线性叠加
DBM 平均场推理 深层无向建模 迭代推理开销大
高斯-Bernoulli RBM CD + 高斯采样 实值数据(图像) 同RBM
卷积RBM 卷积CD 图像特征 参数共享降低复杂度
条件RBM 条件CD 序列/时序数据 增加条件权重
重参数化技巧 确定性变换+噪声 VAE等连续潜变量 低方差梯度估计
VAE ELBO优化 数据生成、表征学习 需要编码器+解码器
GAN 极小极大博弈 高质量图像生成 训练不稳定
GSN 去噪马尔可夫链 隐式分布建模 采样需要长链
评估指标 FID/IS/CTST 模型质量评估 取决于具体指标
相关推荐
hzxxxz1 小时前
26%的研发交给AI之后-人的位置换到了哪里
人工智能
m0_587383001 小时前
深圳24小时自助健身房系统软件开发实战:架构设计与部署指南
人工智能·数据挖掘·系统架构·需求分析
Joy T1 小时前
Spring AI 2.0 Agent 进阶:Memory、State 与 Context Engineering 常见技术全景
java·人工智能·后端·spring·agent入门·agent state
估值探索者1 小时前
【Python量化系统工程化 #08】关了 SSH 就停?systemd 让脚本开机自启 + 异常自动拉起
java·c++·人工智能·分类·数据挖掘
西柚研究生1234561 小时前
论文分析17:YOLOv11_UAVNet:无人机航拍图像专用目标检测算法
人工智能·python·深度学习·算法·目标检测
m4Rk_1 小时前
【论文阅读】Agent 记忆机制(74):CompassMem——从相似度检索走向事件图上的记忆导航
论文阅读·人工智能·学习·开源·github
镭封1 小时前
短剧多人对白怎么配?一人完成多角色配音的办法
人工智能·音视频·语音识别·媒体
小李不想当小白1 小时前
DMA直接存储器存取(STM32标准库学习笔记)
笔记·stm32·单片机·嵌入式硬件·学习·分享