使用pytorch构建GAN示例
生成对抗网络(GAN)是一对相互学习的学习引擎。一个有用的类比是把伪造者和专家放在一起,两者都学会了超越对方。伪造者更擅长伪造(生成),而专家更擅长鉴别(鉴别)赝品。
生成和判别网络已经存在了一段时间,它们彼此相互映射产生了一些非常有趣的结果,比教导生成器如何生产更好的东西。
下面的Python代码显示了一个非常基本的GAN,它的目的是显示一种方法来配对两个对手。有一些变体,可以随意尝试,将其作为工作原型使用。
我用了pytorch,这是python里的torch的重建。它使创建您自己的机器学习(ML)应用程序非常容易。
示例代码 - 学习正态分布的1D GAN
导入库
import torch
import torch.nn as nn
import torch.optim as optim
from torch.distributions.normal import Normal
定义我们将学习的分布
我们想学习平均3.0和标准差0.4的正态分布。数据将保存为长度为30的数组。
(数组不会被排序,即打印原始数据看起来会很混乱,这只能学习分布,而不是概率分布曲线!)。
data_mean = 3.0
data_stddev = 0.4
Series_Length = 30
定义生成器网络
取20个随机输入并生成一个分布(如上所述),定义了隐层神经元数。
g_input_size = 20
g_hidden_size = 150
g_output_size = Series_Length
定义判别器网络
输出是一个值
- True(1.0)匹配所需的发行版
- False(0.0)与分布不匹配
更改隐藏大小以查看效果
d_input_size = Series_Length
d_hidden_size = 75
d_output_size = 1
定义如何向流程发送数据
训练分为两个阶段,分别训练判别器和生成器。判别器比生成器“更好”似乎很重要。有时用于训练判别器的批次比用于生成器的批次多。在这种情况下,我们在判别器训练中投入了更多的内容。
注意,真正的训练使用超过5000个epochs。
d_minibatch_size = 15
g_minibatch_size = 10
num_epochs = 5000
print_interval = 1000
设置学习利率
学习率需要多做这些实验。太小会收敛过慢,太大,我们可能会在一个解周围振荡。
d_learning_rate = 3e-3
g_learning_rate = 8e-3
定义两个函数来返回提供真实样本和一些随机噪声的函数。真实样本训练判别器,随机噪声馈送生成器。
制作signal generator函数的本地副本
def get_real_sampler(mu, sigma):
dist = Normal( mu, sigma )
return lambda m, n: dist.sample( (m, n) ).requires_grad_()
def get_noise_sampler():
return lambda m, n: torch.rand(m, n).requires_grad_() # Uniform-dist data into generator, _NOT_ Gaussian
actual_data = get_real_sampler( data_mean, data_stddev )
noise_data = get_noise_sampler()
生成器
重要的是生成器可以输出匹配的装置。小心使用sigmoid之类的东西,输出0..1。用mean 2.0是学不到东西的!
这是一个非常简单的4层网络,接收噪声并产生输出。
xfer是transfer函数
class Generator(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super(Generator, self).__init__()
self.map1 = nn.Linear(input_size, hidden_size)
self.map2 = nn.Linear(hidden_size, hidden_size)
self.map3 = nn.Linear(hidden_size, output_size)
self.xfer = torch.nn.SELU()
def forward(self, x):
x = self.xfer( self.map1(x) )
x = self.xfer( self.map2(x) )
return self.xfer( self.map3( x ) )
请注意,最后一层应限制为0..1,这使我们可以在损失函数中进行更多选择。
判别器
这个网络是一个经典的多层感知器——真的没什么特别的。它根据所学习的函数返回true/false。
class Discriminator(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super(Discriminator, self).__init__()
self.map1 = nn.Linear(input_size, hidden_size)
self.map2 = nn.Linear(hidden_size, hidden_size)
self.map3 = nn.Linear(hidden_size, output_size)
self.elu = torch.nn.ELU()
def forward(self, x):
x = self.elu(self.map1(x))
x = self.elu(self.map2(x))
return torch.sigmoid( self.map3(x) )
创建两个网络
G = Generator(input_size=g_input_size, hidden_size=g_hidden_size, output_size=g_output_size)
D = Discriminator(input_size=d_input_size, hidden_size=d_hidden_size, output_size=d_output_size)
设置学习规则:
- 损失函数
- 每个网络的优化器
在这里您可以自由选择。
criterion = nn.BCELoss()
d_optimizer = optim.SGD(D.parameters(), lr=d_learning_rate )
g_optimizer = optim.SGD(G.parameters(), lr=g_learning_rate )
实际数据的训练函数
应该学习真实数据=> 1.0
def train_D_on_actual() :
real_data = actual_data( d_minibatch_size, d_input_size )
real_decision = D( real_data )
real_error = criterion( real_decision, torch.ones( d_minibatch_size, 1 )) # ones = true
real_error.backward()
生成器数据的训练函数
学习将生成的数据识别为假=> 0.0
def train_D_on_generated() :
noise = noise_data( d_minibatch_size, g_input_size )
fake_data = G( noise )
fake_decision = D( fake_data )
fake_error = criterion( fake_decision, torch.zeros( d_minibatch_size, 1 )) # zeros = fake
fake_error.backward()
判别器的训练函数
假设生成器产生完美的数据(即判别器返回1.0)。然后学习如何根据判别器的实际输出来改善发生器的输出。
这是GAN的关键部分:通过两个网络传递错误,但只更新生成器权重。
def train_G():
noise = noise_data( g_minibatch_size, g_input_size )
fake_data = G( noise )
fake_decision = D( fake_data )
error = criterion( fake_decision, torch.ones( g_minibatch_size, 1 ) )
error.backward()
return error.item(), fake_data
算法
算法的工作原理如下:
第1步是普通的批量学习,如果删除了其余代码,您将拥有一个可以识别所需分发的网络
- 训练鉴别器就像你训练任何网络一样
- 使用真假(生成)样本来学习
第2步是GAN差值
- 训练生成器生产,但不要将输出与良好的样品进行比较
- 通过判别器提供样本生成的输出以发现假的
- 通过判别器和生成器反向传播误差
因此,让我们考虑可能的情况(在所有情况下,仅在步骤2中更新生成器参数)
Discrimator perfect,Generator Perfect Generator生成一个标识为1.0的样本。Error是0.0,没有学习
Discrimator perfect,Generator Rubbish Generator产生的噪声被识别为0.0。Error是1.0,传播错误并且生成器学习
Discrimator rubbish,Generator Perfect Generator生成标识为0.0的样本。Error是1.0,传播错误,发生器不会学到太多,因为error 将被鉴别器吸收
Discrimator rubbish,Generator Rubbish Generator生成标识为0.5的样本。Error为0.5,误差传播到鉴别器和发生器中的梯度将意味着误差在两者之间共享并且学习发生
此步骤可能很慢 - 具体取决于可用的计算能力
losses = []
for epoch in range(num_epochs):
D.zero_grad()
train_D_on_actual()
train_D_on_generated()
d_optimizer.step()
G.zero_grad()
loss,generated = train_G()
g_optimizer.step()
losses.append( loss )
if( epoch % print_interval) == (print_interval-1) :
print( "Epoch %6d. Loss %5.3f" % ( epoch+1, loss ) )
print( "Training complete" )
显示结果
训练完毕后,我们将生成一些样本并绘制它们。很容易看到我们有一个很好的正态分布。这一步完全是可选的,但很高兴看到我们实际工作的内容。
import matplotlib.pyplot as plt
def draw( data ) :
plt.figure()
d = data.tolist() if isinstance(data, torch.Tensor ) else data
plt.plot( d )
plt.show()
d = torch.empty( generated.size(0), 53 )
for i in range( 0, d.size(0) ) :
d[i] = torch.histc( generated[i], min=0, max=5, bins=53 )
draw( d.t() )