深度生成模型 ------ 知识点详解与代码实现
一、玻尔兹曼机(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)∇θlogpθ(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)logpθ(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):
minGmaxDV(D,G)=Ex∼pdatalogD(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∼pdatalogpθ(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 | 模型质量评估 | 取决于具体指标 |