手把手带你疾速入门超过GAN的Normalizing Flow
作者:Aryansh Omray,微软数据迷信工程师,Medium技术博主
机器学习畛域的一个根本问题就是如何学习简单数据的表征是机器学习。
这项工作的重要性在于,现存的大量非结构化和无标签的数据,只有通过无监督式学习能力了解。密度估计、异样检测、文本总结、数据聚类、生物信息学、DNA建模等各方面的利用均须要实现这项工作。
多年来,钻研人员创造了许多办法来学习大型数据集的概率分布,包含生成反抗网络(GAN)、变分自编码器(VAE)和Normalizing Flow等。
本文即向大家介绍Normalizing Flow这一为了克服GAN和VAE的有余而提出的办法。
Glow模型的输入样例 (Source)
GAN和VAE的能力本已非常惊人,它们都能通过简略的推理方法学习十分复杂的数据分布。
然而,GAN和VAE都不足对概率分布的准确评估和推理,这往往导致VAE中的含糊后果品质不高,GAN训练也面临着如模式解体和后置解体等挑战。
因而,Normalizing Flow应运而生,试图通过应用可逆函数来解决目前GAN和VAE存在的许多问题。
Normalizing Flow
简略地说,Normalizing Flow就是一系列的可逆函数,或者说这些函数的解析逆是能够计算的。例如,f(x)=x+2是一个可逆函数,因为每个输出都有且仅有一个惟一的输入,并且反之亦然,而f(x)=x²则不是一个可逆函数。这样的函数也被称为双射函数。
图源作者
从上图能够看出,Normalizing Flow能够将简单的数据点(如MNIST中的图像)转化为简略的高斯分布,反之亦然。和GAN十分不一样的中央是,GAN输出的是一个随机向量,而输入的是一个图像,基于流(Flow)的模型则是将数据点转化为简略散布。在上图的MNIST一例中,咱们从高斯分布中抽取随机样本,均可从新取得其对应的MNIST图像。
基于流的模型应用负对数可能性损失函数进行训练,其中p(z)是概率函数。上面的损失函数就是应用统计学中的变量变动公式失去的。
(Source)
Normalizing Flow的劣势
与GAN和VAE相比,Normalizing Flow具备各种劣势,包含:
- Normalizing Flow模型不须要在输入中放入噪声,因而能够有更弱小的部分方差模型(local variance model);
- 与GAN相比,基于流的模型训练过程十分稳固,GAN则须要认真调整生成器和判断器的超参数;
- 与GAN和VAE相比,Normalizing Flow更容易收敛。
Normalizing Flow的有余
尽管基于流的模型有其劣势,但它们也有一些毛病:
- 基于流的模型在密度估计等工作上的体现不尽如人意;
- 基于流的模型要求保留变换的体积(volume preservation over transformations),这往往会产生十分高维的潜在空间,通常会导致解释性变差;
- 基于流的模型产生的样本通常没有GAN和VAE的好。
为了更好地了解Normalizing Flow,咱们以Glow架构为例进行解释。Glow是OpenAI在2018年提出的一个基于流的模型。下图展现了Glow的架构。
Glow的架构(Source)
Glow架构由多个表层(superficial layers)组合而成。首先咱们来看看Glow模型的多尺度框架。Glow模型由一系列的反复层(命名为尺度)组成。每个尺度包含一个挤压函数和一个流步骤,每个流步骤蕴含ActNorm、1x1 Convolution和Coupling Layer,流步骤后是宰割函数。宰割函数在通道维度上将输出分成两个相等的局部。其中一半进入之后的层,另一半则进入损失函数。宰割是为了缩小梯度隐没的影响,梯度隐没会在模型以端到端形式(end-to-end)训练时呈现。
如下图所示,挤压函数(squeeze function)通过横向重塑张量,将大小为[c, h, w]的输出张量转换为大小为[4c, h/2, w/2]的张量。此外,在测试阶段能够采纳重塑函数,将输出的[4c, h/2, w/2]重塑为大小为[c, h, w]的张量。
(Source)
其余层,如ActNorm、1x1 Convolution和Affine Coupling层,能够从下表了解。该表展现了每层的性能(包含正向和反向)。
(Source)
实现
在理解了Normalizing Flow和Glow模型的基础知识后,咱们将介绍如何应用PyTorch实现该模型,并在MNIST数据集上进行训练。
Glow模型
首先,咱们将应用PyTorch和nflows实现Glow架构。为了节省时间,咱们应用nflows蕴含所有层的实现。
import torchimport torch.nn as nnimport torch.nn.functional as Ffrom nflows import transformsimport numpy as npfrom torchvision.transforms.functional import resizefrom nflows.transforms.base import Transformclass Net(nn.Module): def __init__(self, in_channel, out_channels): super().__init__() self.net = nn.Sequential( nn.Conv2d(in_channel, 64, 3, padding=1), nn.ReLU(inplace=True), nn.Conv2d(64, 64, 1), nn.ReLU(inplace=True), ZeroConv2d(64, out_channels), ) def forward(self, inp, context=None): return self.net(inp)def getGlowStep(num_channels, crop_size, i): mask = [1] * num_channels if i % 2 == 0: mask[::2] = [-1] * (len(mask[::2])) else: mask[1::2] = [-1] * (len(mask[1::2])) def getNet(in_channel, out_channels): return Net(in_channel, out_channels) return transforms.CompositeTransform([ transforms.ActNorm(num_channels), transforms.OneByOneConvolution(num_channels), transforms.coupling.AffineCouplingTransform(mask, getNet) ])def getGlowScale(num_channels, num_flow, crop_size): z = [getGlowStep(num_channels, crop_size, i) for i in range(num_flow)] return transforms.CompositeTransform([ transforms.SqueezeTransform(), *z ])def getGLOW(): num_channels = 1 * 4 num_flow = 32 num_scale = 3 crop_size = 28 // 2 transform = transforms.MultiscaleCompositeTransform(num_scale) for i in range(num_scale): next_input = transform.add_transform(getGlowScale(num_channels, num_flow, crop_size), [num_channels, crop_size, crop_size]) num_channels *= 2 crop_size //= 2 return transformGlow_model = getGLOW()
咱们能够用各种数据集来训练Glow模型,如MNIST、CIFAR-10、ImageNet等。本文为了演示不便,应用的是MNIST数据集。
像MNIST这样的数据集能够很容易地从格物钛公开数据集平台获取,该平台蕴含了机器学习中所有罕用的凋谢数据集,如分类、密度估计、物体检测和基于文本的分类数据集等。
要拜访数据集,咱们只须要在格物钛的平台上创立账户,就能够间接fork想要的数据集,能够间接下载或者应用格物钛提供的pipeline导入数据集。根本的代码和相干文档可在TensorBay的反对网页上取得。
联合格物钛TensorBay的Python SDK,咱们能够很不便地导入MNIST数据集到PyTorch中:
from PIL import Imagefrom torch.utils.data import DataLoader, Datasetfrom torchvision import transformsfrom tensorbay import GASfrom tensorbay.dataset import Dataset as TensorBayDatasetclass MNISTSegment(Dataset): def __init__(self, gas, segment_name, transform): super().__init__() self.dataset = TensorBayDataset("MNIST", gas) self.segment = self.dataset[segment_name] self.category_to_index = self.dataset.catalog.classification.get_category_to_index() self.transform = transform def __len__(self): return len(self.segment) def __getitem__(self, idx): data = self.segment[idx] with data.open() as fp: image_tensor = self.transform(Image.open(fp)) return image_tensor, self.category_to_index[data.label.classification.category]
模型训练
模型训练能够通过上面的代码简略开始。该代码应用格物钛TensorBay提供的Pipeline创立数据加载器,其中的ACCESS_KEY能够在TensorBay的账户设置中取得。
from nflows.distributions import normalACCESS_KEY = "Accesskey-*****"EPOCH = 100to_tensor = transforms.ToTensor()normalization = transforms.Normalize(mean=[0.485], std=[0.229])my_transforms = transforms.Compose([to_tensor, normalization])train_segment = MNISTSegment(GAS(ACCESS_KEY), segment_name="train", transform=my_transforms)train_dataloader = DataLoader(train_segment, batch_size=4, shuffle=True, num_workers=4)optimizer = torch.optim.Adam(Glow_model.parameters(), 1e-3)for epoch in range(EPOCH): for index, (image, label) in enumerate(train_dataloader): if index == 0: image_size = image.shaape[2] channels = image.shape[1] image = image.cuda() output, logabsdet = Glow_model._transform(image) shape = output.shape[1:] log_z = normal.StandardNormal(shape=shape).log_prob(output) loss = log_z + logabsdet loss = -loss.mean()/(image_size * image_size * channels) optimizer.zero_grad() loss.backward() optimizer.step() print(f"Epoch:{epoch+1}/{EPOCH} Loss:{loss}")
下面代码用的是MNIST数据集,要想应用其余数据集咱们能够间接替换该数据集的数据加载器。
样例生成
模型训练实现之后,咱们能够通过上面的代码来生成样例:
samples = Glow_model.sample(25)display(samples)
应用nflows库之后,咱们只须要用一行代码就能够生成样例,而display函数则能在一个网格中显示生成的样本。
用MNIST训练模型之后生成的样例
结语
本文向大家介绍了Normalizing Flow的基本知识,并与GAN和VAE进行了比拟,同时向大家展现了Glow模型的根本工作形式。咱们还解说了如何简略实现Glow模型,并应用MNIST数据集进行训练。在格物钛公开数据集平台的帮忙下,数据集拜访变得非常便捷。
【对于格物钛】
格物钛智能科技专一打造人工智能新型基础设施,通过非结构化数据平台和公开数据集社区,帮忙机器学习团队和集体更好地开释非结构化数据后劲,让AI利用开发更快、性能体现更优,继续为人工智能赋能千行百业、驱动产业降级、推动科技普惠打造坚实基础。目前已取得红杉、云启、真格、风和、耀途资本以及奇绩创坛的千万美金投资。