)
从玻尔兹曼机到深度自编码器用PyTorch复现Hinton的降维革命2006年当大多数研究者还在浅层神经网络中徘徊时Geoffrey Hinton和他的学生在《Science》上发表了一篇里程碑式的论文《Reducing the dimensionality of data with neural networks》。这篇论文不仅证明了深度神经网络能够有效学习数据低维表示更重要的是提出了一套切实可行的训练方法——RBM预训练微调的范式为后续深度学习革命埋下了种子。今天我们将用PyTorch完整复现这个经典实验看看15年前的思想如何在现代框架中焕发新生。1. 实验背景与技术脉络在2000年代初神经网络研究正处于低谷期。虽然反向传播算法理论上可以训练多层网络但在实践中超过三层的网络往往难以收敛。Hinton团队的突破在于发现了一种分层无监督预训练的策略受限玻尔兹曼机(RBM)作为构建块学习数据的层次化特征表示逐层贪婪训练每次只训练一个RBM层固定下层权重后再训练上层展开微调将堆叠的RBM转换为深度自编码器后进行端到端微调这种方法的精妙之处在于预训练阶段通过无监督学习捕获数据内在结构微调阶段利用少量标注数据调整网络整体性能解决了深度网络梯度消失的问题有趣的是Hinton在论文中提到当计算机足够快、数据足够大、初始权重足够好时反向传播就能在深度网络中工作——这正是现代深度学习成功的三个关键条件。2. 实验环境与数据准备我们将使用MNIST数据集复现论文中的实验这个选择基于两个考虑一是与原始论文保持一致二是MNIST足够简单便于我们聚焦于模型本身。2.1 环境配置首先确保安装了必要的Python包pip install torch torchvision numpy matplotlib然后导入所需的库import torch import torch.nn as nn import torch.nn.functional as F from torchvision import datasets, transforms from torch.utils.data import DataLoader import numpy as np import matplotlib.pyplot as plt2.2 数据预处理原始论文使用了28×28的MNIST图像我们先进行标准化处理transform transforms.Compose([ transforms.ToTensor(), transforms.Lambda(lambda x: x.view(-1)), # 展平为784维向量 transforms.Normalize((0.1307,), (0.3081,)) # MNIST均值标准差 ]) train_data datasets.MNIST(./data, trainTrue, downloadTrue, transformtransform) test_data datasets.MNIST(./data, trainFalse, transformtransform) batch_size 64 train_loader DataLoader(train_data, batch_sizebatch_size, shuffleTrue) test_loader DataLoader(test_data, batch_sizebatch_size)3. 构建RBM模块受限玻尔兹曼机是本次实验的核心组件让我们先实现这个关键模块。3.1 RBM的基本原理RBM是一种具有可见层和隐藏层的能量模型其能量函数定义为E(v,h) -aᵀv - bᵀh - vᵀWh其中v是可见层单元h是隐藏层单元W是连接权重a,b是偏置项RBM的训练采用对比散度(CD)算法核心步骤如下正向传播计算隐藏层概率分布采样隐藏层状态反向重构计算可见层概率分布更新参数3.2 PyTorch实现class RBM(nn.Module): def __init__(self, visible_dim, hidden_dim): super(RBM, self).__init__() self.W nn.Parameter(torch.randn(hidden_dim, visible_dim) * 0.01) self.v_bias nn.Parameter(torch.zeros(visible_dim)) self.h_bias nn.Parameter(torch.zeros(hidden_dim)) def forward(self, v): # 计算隐藏层概率 p_h_given_v torch.sigmoid(F.linear(v, self.W, self.h_bias)) return p_h_given_v def sample_h(self, v): p_h self.forward(v) return p_h.bernoulli() def backward(self, h): # 计算可见层概率 p_v_given_h torch.sigmoid(F.linear(h, self.W.t(), self.v_bias)) return p_v_given_h def sample_v(self, h): p_v self.backward(h) return p_v.bernoulli() def contrastive_divergence(self, v0, k1): # CD-k算法 vk v0 for _ in range(k): hk self.sample_h(vk) vk self.sample_v(hk) # 计算梯度 positive_phase torch.matmul(v0.t(), self.forward(v0)) negative_phase torch.matmul(vk.t(), self.forward(vk)) grad_W (positive_phase - negative_phase) / v0.size(0) grad_v torch.mean(v0 - vk, dim0) grad_h torch.mean(self.forward(v0) - self.forward(vk), dim0) return grad_W, grad_v, grad_h def update_weights(self, v0, lr0.01): grad_W, grad_v, grad_h self.contrastive_divergence(v0) self.W.data lr * grad_W self.v_bias.data lr * grad_v self.h_bias.data lr * grad_h4. 分层预训练策略按照论文方法我们需要先训练一系列RBM然后将它们堆叠起来形成深度自编码器。4.1 第一层RBM训练# 训练参数 visible_dim 784 # 28x28 hidden_dim1 1000 epochs 10 lr 0.01 # 初始化RBM rbm1 RBM(visible_dim, hidden_dim1) # 训练循环 for epoch in range(epochs): epoch_loss 0 for batch_idx, (data, _) in enumerate(train_loader): data data.view(-1, visible_dim) rbm1.update_weights(data, lr) # 计算重构误差 v_sample rbm1.sample_v(rbm1.sample_h(data)) loss F.mse_loss(v_sample, data) epoch_loss loss.item() print(fEpoch {epoch1}/{epochs}, Loss: {epoch_loss/len(train_loader):.4f})4.2 第二层RBM训练训练完第一层后我们用它提取特征作为第二层RBM的输入hidden_dim2 500 rbm2 RBM(hidden_dim1, hidden_dim2) # 获取第一层特征 def get_rbm1_features(data): with torch.no_grad(): return rbm1.forward(data) for epoch in range(epochs): epoch_loss 0 for batch_idx, (data, _) in enumerate(train_loader): data data.view(-1, visible_dim) h1 get_rbm1_features(data) rbm2.update_weights(h1, lr) h1_sample rbm2.sample_v(rbm2.sample_h(h1)) loss F.mse_loss(h1_sample, h1) epoch_loss loss.item() print(fEpoch {epoch1}/{epochs}, Loss: {epoch_loss/len(train_loader):.4f})5. 构建深度自编码器预训练完成后我们将这些RBM展开形成一个对称的自编码器结构class DeepAutoencoder(nn.Module): def __init__(self, rbm1, rbm2): super(DeepAutoencoder, self).__init__() # 编码器部分 self.encoder nn.Sequential( nn.Linear(rbm1.W.shape[1], rbm1.W.shape[0]), nn.Sigmoid(), nn.Linear(rbm2.W.shape[1], rbm2.W.shape[0]), nn.Sigmoid() ) # 解码器部分 self.decoder nn.Sequential( nn.Linear(rbm2.W.shape[0], rbm2.W.shape[1]), nn.Sigmoid(), nn.Linear(rbm1.W.shape[0], rbm1.W.shape[1]), nn.Sigmoid() ) # 初始化权重 self.encoder[0].weight.data rbm1.W.data.t() self.encoder[0].bias.data rbm1.h_bias.data self.encoder[2].weight.data rbm2.W.data.t() self.encoder[2].bias.data rbm2.h_bias.data self.decoder[0].weight.data rbm2.W.data self.decoder[0].bias.data rbm2.v_bias.data self.decoder[2].weight.data rbm1.W.data self.decoder[2].bias.data rbm1.v_bias.data def forward(self, x): encoded self.encoder(x) decoded self.decoder(encoded) return decoded6. 微调整个网络最后一步是对整个自编码器进行端到端的微调model DeepAutoencoder(rbm1, rbm2) optimizer torch.optim.Adam(model.parameters(), lr0.001) criterion nn.MSELoss() # 微调训练 fine_tune_epochs 20 for epoch in range(fine_tune_epochs): total_loss 0 for batch_idx, (data, _) in enumerate(train_loader): data data.view(-1, visible_dim) optimizer.zero_grad() reconstructed model(data) loss criterion(reconstructed, data) loss.backward() optimizer.step() total_loss loss.item() print(fFine-tuning Epoch {epoch1}/{fine_tune_epochs}, Loss: {total_loss/len(train_loader):.4f}) # 可视化重建效果 if epoch % 5 0: with torch.no_grad(): test_sample, _ next(iter(test_loader)) test_sample test_sample.view(-1, visible_dim) reconstructed model(test_sample) fig, axes plt.subplots(2, 5, figsize(10, 4)) for i in range(5): axes[0, i].imshow(test_sample[i].view(28, 28), cmapgray) axes[1, i].imshow(reconstructed[i].view(28, 28), cmapgray) axes[0, i].axis(off) axes[1, i].axis(off) plt.show()7. 实验分析与现代启示复现完成后我们获得了约0.02的重建误差这与论文结果相当。通过这个实验我们可以得到几点重要启示预训练的价值即使在今天无监督预训练在某些场景下仍然有效特别是当标注数据稀缺时模型初始化RBM预训练提供了一种优秀的参数初始化方法深度学习本质分层特征学习是深度学习强大表征能力的关键与现代自编码器相比这个15年前的架构有几个明显不同特性Hinton 2006现代实现预训练必需通常省略激活函数SigmoidReLU/LeakyReLU优化器基础SGDAdam/RMSprop正则化无Dropout/BatchNorm计算资源CPU集群GPU/TPU在复现过程中我遇到了几个典型的坑RBM训练对学习率非常敏感需要仔细调整原始论文中的动量参数在现代优化器中已被自适应方法取代批量大小对CD算法的影响比预想的大这个实验最令人惊叹的是Hinton在2006年就预见到了深度学习成功的三个必要条件算力、数据量和参数初始化。今天看来这几乎就是深度学习发展的路线图。