【生成模型】解读显式生成模型之完全可见置信网络FVBN
上一期为大家说明了什么是极大似然法,以及如何使用极大似然法搭建生成模型,本期将为大家介绍第一个显式生成模型完全可见置信网络FVBN。
作者&编辑 | 小米粥
1 完全可见置信网络
在完全可见置信网络中,不存在不可观察的潜在变量,观察变量的概率被链式法则从维度上进行分解,对于 n 维观察变量x ,其概率表达式为:
自回归网络是最简单的完全可见置信网络,其中每一个维度的观察变量都构成概率模型的一个节点,而这些所有的节点{x1,x2,...,xn}共同构成一个完全有向图,即图中任意两个节点都存在连接关系,如图所示。
在自回归网络中,因为已经有了随机变量的链式分解关系,那么核心问题便成为如何表达条件概率p(xi|xi-1,xx-2,...,x1) 。最简单的模型是线性自回归网络,即每个条件概率均被定义为线性模型,对实数值数据使用线性回归模型(例如定义 p(xi|xi-1,xx-2,...,x1)= w1x1+w2x2+...+wi-1xi-1 ,对二值数据使用逻辑回归,而对离散数据使用softmax回归,其计算过程如下图。
但线性模型容量有限,拟合函数的能力不足。在神经自回归网络中,使用神经网络代替线性模型,它可以任意增加容量,理论上可以拟合任意联合分布。神经自回归网络还使用了特征重用的技巧,神经网络从观察变量 xi 学习到的隐藏抽象特征 hi 不仅在计算p(xi+1|xi,xi-1,...,x1)时使用,也会在计算p(xi+2|xi+1,xi,...,x1)时进行重用,其计算图如下所示,并且该模型不需要将每个条件概率的计算都分别使用不同神经网络表示,可以将所有神经网络整合为一个,因此只要设计成抽象特征hi只依赖于x1,x2,...,xi即可。而目前的神经自回归密度估计器是神经自回归网络中最具有代表性的方案,它是在神经自回归网络中引入了参数共享的方案,即从观察变量xi到任意隐藏抽象特征 hi+1,hi+2,... 的权值参数是共享的,使用了特征重用、参数共享等深度学习技巧的神经自回归密度估计器具有非常优秀的性能。
PixelRNN和PixelCNN也属于完全可见置信网络,从名字可以看出,这两个模型一般用于图像的生成。它们将图像x的概率p(x)按照像素分解为 n 个条件概率的乘积,其中n为图像的像素点个数,即在每一个像素点上定义了一个条件概率用以表达像素之间的依赖关系,该条件概率分别使用RNN或者CNN进行学习。为了将输出离散化,通常将RNN或CNN的最后一层设置为softmax层,用以表示其输出不同像素值的概率。在PixelRNN中,一般定义从左上角开始沿着右方和下方依次生成每一个像素点,如下图所示。这样,对数似然的表达式便可以得到,训练模型时只需要将其极大化即可。
PixelRNN在其感受野内可能具有无边界的依赖范围,因为待求位置的像素值依赖之前所有已知像素点的像素值,这将需要大量的计算代价,PixelCNN使用标准卷积层来捕获有界的感受野,其训练速度要快于PixelRNN。在PixelCNN中,每个位置的像素值仅与其周围已知像素点的值有关,如下图所示。灰色部分为已知像素,而白色部分为未知像素,计算黑色位置的像素值时,需要把方框区域内的所有灰色像素值传递给CNN,由CNN最后的softmax输出层来表达表在黑色位置取不同像素值的概率,这里可以使用由0和1构成的掩模矩阵将方框区域内的白色位置像素抹掉。PixelRNN和PixelCNN此后仍有非常多改进模型,但由于它是逐个像素点地生成图片,具有串行性,故在实际应用中效率难以保证,这也是FVBN模型的通病。
2 pixelCNN 代码
接下来我们将提供一份完整的pixelCNN的代码讲解,其中训练集为mnist数据集。
首先读取相关python库,设置训练参数:
# 读取相关库
import time
import torch
import torch.nn.functional as F
from torch import nn, optim, cudafrom torch.utils
import datafrom torchvision import datasets, transforms, utils
# 设置训练参数
train_batch_size = 256
generation_batch_size = 48
epoch_number = 25feature_dim = 64
# 是否使用GPU
if torch.cuda.is_available():
device = torch.device('cuda:0')
else:
device = torch.device('cpu')
然后定义二维掩膜卷积,所谓掩膜即使卷积中心的右方和下方的权值为0,如下图所示为3x3掩膜卷积核(A型):
定义二维掩膜卷积核,其中有A与B两种类型,区别之处在于中心位置是否被卷积计算:
class MaskedConv2d(nn.Conv2d):
def __init__(self, mask_type, *args, **kwargs):
super(MaskedConv2d, self).__init__(*args, **kwargs)
assert mask_type in {'A', 'B'}
self.register_buffer('mask', self.weight.data.clone())
bs, o_feature_dim, kH, kW = self.weight.size()
self.mask.fill_(1)
self.mask[:, :, kH // 2, kW // 2 + (mask_type == 'B'):] = 0
self.mask[:, :, kH // 2 + 1:] = 0
def forward(self, x):
self.weight.data *= self.mask
return super(MaskedConv2d, self).forward(x)
我们的pixelCNN网络为多层掩膜卷积的堆叠,即:
network = nn.Sequential(
MaskedConv2d('A',1,feature_dim,7,1,3, bias=False),nn.BatchNorm2d(feature_dim),nn.ReLU(True),
MaskedConv2d('B', feature_dim, feature_dim, 7, 1, 3, bias=False), nn.BatchNorm2d(feature_dim), nn.ReLU(True),
MaskedConv2d('B', feature_dim, feature_dim, 7, 1, 3, bias=False), nn.BatchNorm2d(feature_dim), nn.ReLU(True),
MaskedConv2d('B', feature_dim, feature_dim, 7, 1, 3, bias=False), nn.BatchNorm2d(feature_dim), nn.ReLU(True),
MaskedConv2d('B', feature_dim, feature_dim, 7, 1, 3, bias=False), nn.BatchNorm2d(feature_dim), nn.ReLU(True),
MaskedConv2d('B', feature_dim, feature_dim, 7, 1, 3, bias=False), nn.BatchNorm2d(feature_dim), nn.ReLU(True),
MaskedConv2d('B', feature_dim, feature_dim, 7, 1, 3, bias=False), nn.BatchNorm2d(feature_dim), nn.ReLU(True),
MaskedConv2d('B', feature_dim, feature_dim, 7, 1, 3, bias=False), nn.BatchNorm2d(feature_dim), nn.ReLU(True), nn.Conv2d(feature_dim, 256, 1))
network.to(device)
接着设置dataloader和优化器:
train_data = data.DataLoader(datasets.MNIST('data', train=True, download=True, transform=transforms.ToTensor()), batch_size=train_batch_size, shuffle=True, num_workers=1, pin_memory=True)
test_data = data.DataLoader(datasets.MNIST('data', train=False, download=True, transform=transforms.ToTensor()), batch_size=train_batch_size, shuffle=False, num_workers=1, pin_memory=True)
optimizer = optim.Adam(network.parameters())
开始训练网络,并在每一轮epoch后进行测试和生成样本
if __name__ == "__main__":
for epoch in range(epoch_number):
# 训练
cuda.synchronize()
network.train(True)
for input_image, _ in train_data:
time_tr = time.time()
input_image = input_image.to(device)
output_image = network(input_image)
target = (input_image.data[:, 0] * 255).long().to(device)
loss = F.cross_entropy(output_image, target)
optimizer.zero_grad()
loss.backward()
optimizer.step()
print("train: {} epoch, loss: {}, cost time: {}".format(epoch, loss.item(), time.time() - time_tr)) cuda.synchronize()
# 测试
with torch.no_grad():
cuda.synchronize()
time_te = time.time()
network.train(False)
for input_image, _ in test_data: input_image = input_image.to(device)
target = (input_image.data[:, 0] * 255).long().to(device)
loss = F.cross_entropy(network(input_image), target)
cuda.synchronize()
time_te = time.time() - time_te
print("test: {} epoch, loss: {}, cost time: {}".format(epoch, loss.item(), time_te))
# 生成样本
with torch.no_grad():
image = torch.Tensor(generation_batch_size, 1, 28, 28).to(device)
image.fill_(0)
network.train(False)
for i in range(28):
for j in range(28):
out = network(image)
probs = F.softmax(out[:, :, i, j]).data
image[:, :, i, j] = torch.multinomial(probs, 1).float() / 255.
utils.save_image(image, 'generation-image_{:02d}.png'.format(epoch), nrow=12, padding=0)
[1] Oord A V D , Kalchbrenner N , Kavukcuoglu K . Pixel Recurrent Neural Networks[J]. 2016.
[2] 伊恩·古德费洛, 约书亚·本吉奥, 亚伦·库维尔. 深度学习
总结
本期带大家学习了第一种显式生成模型完全可见置信网络,并对其中的自回归网络和pixelRNN,pixelCNN做了讲解,并讲解了一份完整的pixelCNN代码。下一期我们将对第二个显式模型流模型进行讲解。