0%

回顾-DCGAN

自动摘要: title:回顾DCGAN tags: DCGAN categories: 框架 pytorch GAN toc:false date:201812 ……..

title: 回顾-DCGAN
tags:
- DCGAN
categories:
- 框架
- pytorch
- GAN
toc: false
date: 2018-12-24 03:38:57

1. DCGAN 简介:

如何把CNN与GAN结合?
DCGAN是这方面最好的尝试之一,DCGAN的原理和GAN是一样的,这里就不在赘述。
它只是把经典GAN中的G和D换成了两个卷积神经网络(CNN)。
但是,并不是直接替换就可以了, DCGAN 对卷积神经网络的结构做了一些改变,以提高样本的质量和收敛的速度。用了哪些方法!我们简单了解下,原文地址:点我


2. 网络结构:

  • 首先看图:

    网络改进点:
    简单解释下:
    GAN 的第一层采用均匀噪声分布Z作为输入,因为它只是一个矩阵乘法操作所以可以称为全连接,但结果被重组为一个4维的张量,并作为卷积叠加(convolution stack)的开始。对于判别网络,最后卷积层是平滑的然后送入单个sigmoid输出。

    • 1.使用步幅卷积替代确定性空间池化函数(比如最大池化),允许网络学习自身的空间下采样的方法应用在生成网络当中,在判别网络中允许它学习自己的空间上采样。
    • 2.在卷积层的中间地带直接分别连接到生成网络的输入和判别网络的输出系统也可以很好的工作。
      • 其次一个趋势是在最顶层的卷积后面去除全连接层特点。发现全局平均池化虽然增加了模型的稳定性但却影响了收敛速度。
    • 3.最后是批量规范化:通过将输入的每个单元标准化为0均值与单位方差来稳定学习。有助于处理初始化不良导致的训练问题另外还有助于梯度流向更深的网络。
      • 不要批量规范化(batchnorm)将应用到生成网络的输出层和判别网络输入层可以避免直接对所有的层采取批量归一化,导致采样的振荡和模型的不稳定问题
    • 4.使用有界激活可以让模型更快学习达到饱和并覆盖训练分布的颜色空间。在判别器中,发现LeakyReLU激活函数能够很好地工作,特别是对于更高分辨率的模型。

总结:


训练细节上的改进:

  • 1.训练图像除了缩放到tanh激活函数的[-1,1]范围之外,没有经过其他的预处理。
  • 2.所有的模型都是通过小批量随机梯度下降法进行训练的(小批量的大小是 128)
  • 3.所有权重的初始化为均值为 0 和方差为 0.02的正态分布。
  • 4.在LeakyReLU, 所有模型的leak的斜率设置为0.2。
  • 5.DCGAN是使用Adam优化程序调整超参数。建议使用的学习率是0.001,太高的话使用0.0002代替。
  • 6.优化器参数beta1,在建议的0.9训练动荡且不稳定,但降低到0.5是有利于模型的稳定。

3. pytorch 实现:

** network.py**

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
'''
跟着图走

'''
import torch.nn as nn

#我们首先建立G
class G(nn.Module):
def __init__(self,args):
super(G,self).__init__()
ngf = args.ngf #ndf 设置为128 卷积一般扩大两倍 参数为4,2,1
self.G_layer= nn.Sequential(
# 输入的相当于nz*1*1
nn.ConvTranspose2d (args.nz, ngf * 8, 4, 1, 0, bias=False),
nn.BatchNorm2d (ngf * 8),
nn.ReLU (True),
# (ngf*8) x 4 x 4
nn.ConvTranspose2d (ngf * 8, ngf * 4, 4, 2, 1, bias=False),
nn.BatchNorm2d (ngf * 4),
nn.ReLU (True),
# (ngf*4) x 8 x 8
nn.ConvTranspose2d (ngf * 4, ngf * 2, 4, 2, 1, bias=False),
nn.BatchNorm2d (ngf * 2),
nn.ReLU (True),
# (ngf*2) x 16 x 16
nn.ConvTranspose2d (ngf * 2, ngf, 4, 2, 1, bias=False),
nn.BatchNorm2d (ngf),
nn.ReLU (True),
# (ngf) x 32 x 32
nn.ConvTranspose2d (ngf, 3, 4, 2, 1, bias=False),
nn.Tanh ())
# 3 x 64 x 64



#前向传播
def forward(self,x):
out=self.G_layer(x)
return out

#建立D
class D(nn.Module):
def __init__(self,args):
super(D,self).__init__()
ndf = args.ndf # ndf 128
self.D_layer= nn.Sequential(
# 输入 3 x 64 x 64,
nn.Conv2d(3,ndf,4,2,1),
nn.BatchNorm2d (ndf),
nn.LeakyReLU (True),
#输出 (ndf)*32*32
nn.Conv2d(ndf,ndf*2,4,2,1),
nn.BatchNorm2d (ndf*2),
nn.LeakyReLU (True),
# 输出 (ndf*2)*16*16
nn.Conv2d (ndf*2, ndf*4, 4, 2, 1),
nn.BatchNorm2d (ndf*4),
nn.LeakyReLU (True),
# 输出 (ndf*4)*8*8
nn.Conv2d (ndf*4, ndf*8, 4, 2, 1),
nn.BatchNorm2d (ndf*8),
nn.LeakyReLU (True),
# 输出 (ndf*8)*4*4
nn.Conv2d (ndf * 8, 1, 4, 1, 0),
# 输出 1*0*0
nn.Sigmoid())#告诉D概率

#前向传播
def forward(self,x):
out=self.D_layer(x)
return out


** train.py **

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
import torch
import torch.nn as nn
import torchvision.datasets as datasets
import torchvision.transforms as transforms
from torch.autograd import Variable
import torch.backends.cudnn as cudnn
# 工具包
import argparse
# 载入网络
from network import G, D

#############参数设置#############
####命令行设置########
parser = argparse.ArgumentParser (description='GANs') # 导入命令行模块
# 对关于函数add_argumen()第一个是选项,第二个是数据类型,第三个默认值,第四个是help命令时的说明
#关于训练参数
parser.add_argument ('--batch_size', type=int, default=64,
help='训练batch-size大小 (default: 64)')
parser.add_argument ('--imageSize', type=int, default=64,
help='图片尺寸')
parser.add_argument ('--max_epoch', type=int, default=20,
help='最大迭代数 (default: 12)')
#关于网络参数
parser.add_argument ('--lr_g', type=float, default=2e-4,
help='生成器学习率 (default: 2e-4)')
parser.add_argument ('--lr_d', type=float, default=2e-4,
help='判别器学习率 (default: 2e-4)')
parser.add_argument ('--ngf', type=int, default=128,
help='生成器feature map数')
parser.add_argument ('--ndf', type=int, default=128,
help='判别器feature map数')
parser.add_argument ('--d_every', type=int, default=1,
help='每几个batch训练一次判别器')
parser.add_argument ('--g_every', type=int, default=4,
help='每几个batch训练一次生成器')
parser.add_argument ('--nz', type=int, default=100,
help='噪声维度')

#关于优化器参数
parser.add_argument ('--beta1', type=int, default=0.5,
help='Adam优化器的beta1参数')
#路径
parser.add_argument ('--dataset', default='data/',
help='数据集路径')

parser.add_argument ('--save_data', default='save/',
help='保存路径')

#可视化
parser.add_argument ('--vis', action='store_true',
help='使用visdom可视化')
parser.add_argument ('--plot_every', type=int, default=1,
help='每间隔_batch,visdom画图一次')
# 其他

parser.add_argument ('--cuda', action='store_true',
help='开启cuda训练')
parser.add_argument ('--plt', action='store_true',
help='开启画图')
parser.add_argument ('--test', action='store_true',
help='开启测试生成')
parser.add_argument ('--save_every', type=int, default=5,
help='几个epoch保存一次模型 (default: 2)')
parser.add_argument ('--seed', type=int, default=1,
help='随机种子 (default: 1)')
args = parser.parse_args () # 相当于激活命令


#训练过程
def train():
###############判断gpu#############
device = torch.device ('cuda') if args.cuda else torch.device ('cpu')

####### 为CPU设置种子用于生成随机数,以使得结果是确定的##########
torch.manual_seed (args.seed)
if args.cuda:
torch.cuda.manual_seed (args.seed)
cudnn.benchmark = True

#################可视化###############
if args.vis:
vis = Visualizer ('GANs')
##########数据转换#####################
data_transforms = transforms.Compose ([transforms.Scale (args.imageSize), # 通过调整比例调整大小,会报警
transforms.CenterCrop (args.imageSize), # 在中心裁剪给指定大小方形PIL图像
transforms.ToTensor (),# 转换成pytorch 变量tensor
transforms.Normalize ((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
###############数据载入################
# train_dataset = datasets.ImageFolder (root=args.dataset, # 数据路径目录
# transform=data_transforms) # 把数据转换成上面约束样子

train_dataset = datasets.CIFAR10(root=args.dataset,
train=True,
transform=data_transforms,
download=False)
# test_dataset = datasets.ImageFolder (root=args.dataset,
# transform=data_transforms)

##############数据装载###############
train_loader = torch.utils.data.DataLoader (dataset=train_dataset, # 装载数据
batch_size=args.batch_size, # 设置批大小
shuffle=True) # 是否随机打乱
# test_loader = torch.utils.data.DataLoader (dataset=test_dataset,
# batch_size=args.batch_size,
# shuffle=True)

#############模型载入#################
netG ,netD= G (args),D (args)
netG.to (device)
netD.to (device)
print (netD, netG)

###############损失函数##################
criterion = torch.nn.BCELoss ().to (device)
optimizerD = torch.optim.Adam (netD.parameters (), lr=args.lr_d,betas=(0.5, 0.999)) # Adam优化器
optimizerG = torch.optim.Adam (netG.parameters (), lr=args.lr_g,betas=(0.5, 0.999)) # Adam优化器
###############画图参数保存##################
G_losses = []
D_losses = []
img_list = []
#############训练过程#####################
import tqdm
# Tqdm是一个快速,可扩展的Python进度条,可以在Python
# 长循环中添加一个进度提示信息,用户只需要封装任意的迭代器
# tqdm (iterator)。
for epoch in range (args.max_epoch):
for i, (images, labels) in tqdm.tqdm(enumerate (train_loader)): # 枚举出来
#数据处理
images = images.to (device)
# 装箱
images = Variable (images)
noises=Variable(torch.randn(images.size(0), args.nz, 1, 1).to(device))

#遍历每张图片,并且根据指定的训练机制训练
if i % args.d_every==0:#满足此条件训练D
#D前向传播
optimizerD.zero_grad ()
#D网络输出
output_r = netD (images).view(-1)
#定义真张量
#返回第一个参数为大小 第二参数为值 的张量
true_label = torch.full ((images.size(0),), 1, device=device)
#判别真的损失
d_real_loss = criterion(output_r, true_label)
#反向传播
d_real_loss.backward()
#G网络输出
noises.data.copy_ (torch.randn (images.size(0), args.nz, 1, 1))
fake = netG (noises).detach () # 根据噪声生成假图
#把假图给d
output_f = netD (fake).view(-1)
#d假的损失
#print(fake.size(),output_f.size(),output_r.size())
# 返回第一个参数为大小 第二参数为值 的张量
fake_label = torch.full ((images.size (0),), 0, device=device)
d_fake_loss= criterion(output_f,fake_label)
#D假反向传播
d_fake_loss.backward ()
# 总损失
D_loss=d_fake_loss+d_real_loss
#D更新参数
optimizerD.step ()


# 度量
D_x = output_r.mean ().item ()
D_G_z1 = output_f.mean ().item ()

if i % args.g_every==0:#满足此条件训练G
#G前向传播
optimizerG.zero_grad ()
#G网络输出
fake = netG (noises) # 根据噪声生成假图
#把假图给G
output_f = netD (fake).view(-1)
#G的损失
true_label = torch.full ((images.size (0),), 1, device=device)
G_loss = criterion(output_f,true_label)
#G反向传播
G_loss.backward ()
#度量
D_G_z2 = output_f.mean ().item ()
#D更新参数
optimizerG.step ()

###########################################
##########可视化(可选)#####################
if args.vis and i % args.plot_every == args.plot_every - 1:
fake = netG (noises)
vis.images (fake.detach ().cpu ().numpy () [:64] * 0.5 + 0.5, win='fixfake')
vis.images (images.data.cpu ().numpy () [:64] * 0.5 + 0.5, win='real')
vis.plot ('errord', D_loss.item ())
vis.plot ('errorg', G_loss.item ())
#######################################
############打印记录###################
if i % 1== 0:
print ('[%d/%d][%d/%d]\tLoss_D: %.4f\tLoss_G: %.4f\tD(x): %.4f\tD(G(z)): %.4f / %.4f'
% (epoch, args.max_epoch, i, len (train_loader),
D_loss.item (), G_loss.item (), D_x, D_G_z1, D_G_z2))
########添加画图参数########
G_losses.append (G_loss.item ())
D_losses.append (D_loss.item ())
with torch.no_grad ():
noises = torch.randn (args.batch_size, args.nz, 1, 1).to (device)
fake = netG (noises).detach ().cpu ()
import torchvision.utils as vutils
img_list.append (vutils.make_grid (fake, padding=2, normalize=True))



#######################################
############保存模型###################

if (epoch + 1) % args.save_every == 0:
import torchvision as tv
# 保存模型、图片
tv.utils.save_image (fake.data [:64], '%s/%s.png' % (args.save_data, epoch), normalize=True,range=(-1, 1))
torch.save (netD.state_dict (), 'checkpoints/netd_%s.pth' % epoch)
torch.save (netG.state_dict (), 'checkpoints/netg_%s.pth' % epoch)
print('完成%s的模型保存'%epoch)

#######################################
############画图###################

if args.plt:
import matplotlib.pyplot as plt
import numpy as np
import torchvision.utils as vutils

plt.figure (figsize=(10, 5))
plt.title ("GAN")
plt.plot (G_losses, label="G")
plt.plot (D_losses, label="D")
plt.xlabel ("迭代次数",fontproperties='SimHei')
plt.ylabel ("损失",fontproperties='SimHei')
plt.legend ()
plt.show ()

# 从数据集加载
real_batch = next (iter (train_dataset))

# 画出真图
plt.figure (figsize=(15, 10))
plt.subplot (1, 2, 1)
plt.axis ("off")
plt.title ("真图",fontproperties='SimHei')
plt.imshow (np.transpose (
vutils.make_grid (real_batch [0].to (device) [:64], padding=5, normalize=True).cpu (),
(1, 2, 0)))

# 画出假图
plt.subplot (1, 2, 2)
plt.axis ("off")
plt.title ("假图",fontproperties='SimHei')
plt.imshow (np.transpose (img_list [-1], (1, 2, 0)))
plt.show ()






@torch.no_grad()#禁用梯度计算
def test():
#判断Gpu
device = torch.device ('cuda') if args.cuda else torch.device ('cpu')
#初始化网络
netg, netd = netG (args).eval (), netD (args).eval ()
#定义噪声
noises = torch.randn (args.batch_size, args.nz, 1, 1).to (device)
#载入网络
netd.load_state_dict (torch.load ('checkpoints/netd_%s.pth'%args.max_epoch))
netg.load_state_dict (torch.load ('checkpoints/netg_%s.pth'%args.max_epoch))
#设备化
netd.to (device)
netg.to (device)
# 生成图片,并计算图片在判别器的分数
fake_img = netg (noises)
scores = netd (fake_img).detach ()

# 挑选最好的某几张
indexs = scores.topk (5) [1]
result = []
for i in indexs:
result.append (fake_img.data [i])

# 保存图片
import torchvision as tv
tv.utils.save_image (torch.stack (result), 5, normalize=True, range=(-1, 1))












###################可视化类##################################
import visdom
import time
import torchvision as tv
import numpy as np


class Visualizer ():
"""
封装了visdom的基本操作,但是你仍然可以通过`self.vis.function`
调用原生的visdom接口
"""

def __init__ (self, env='default', **kwargs):
import visdom
self.vis = visdom.Visdom (env=env, use_incoming_socket=False, **kwargs)

# 画的第几个数,相当于横座标
# 保存(’loss',23) 即loss的第23个点
self.index = {}
self.log_text = ''

def reinit (self, env='default', **kwargs):
"""
修改visdom的配置
"""
self.vis = visdom.Visdom (env=env, use_incoming_socket=False, **kwargs)
return self

def plot_many (self, d):
"""
一次plot多个
@params d: dict (name,value) i.e. ('loss',0.11)
"""
for k, v in d.items ():
self.plot (k, v)

def img_many (self, d):
for k, v in d.items ():
self.img (k, v)

def plot (self, name, y):
"""
self.plot('loss',1.00)
"""
x = self.index.get (name, 0)
self.vis.line (Y=np.array ([y]), X=np.array ([x]),
win=(name),
opts=dict (title=name),
update=None if x == 0 else 'append'
)
self.index [name] = x + 1

def img (self, name, img_):
"""
self.img('input_img',t.Tensor(64,64))
"""

if len (img_.size ()) < 3:
img_ = img_.cpu ().unsqueeze (0)
self.vis.image (img_.cpu (),
win=(name),
opts=dict (title=name)
)

def img_grid_many (self, d):
for k, v in d.items ():
self.img_grid (k, v)

def img_grid (self, name, input_3d):
"""
一个batch的图片转成一个网格图,i.e. input(36,64,64)
会变成 6*6 的网格图,每个格子大小64*64
"""
self.img (name, tv.utils.make_grid (
input_3d.cpu () [0].unsqueeze (1).clamp (max=1, min=0)))

def log (self, info, win='log_text'):
"""
self.log({'loss':1,'lr':0.0001})
"""

self.log_text += ('[{time}] {info} <br>'.format (
time=time.strftime ('%m%d_%H%M%S'),
info=info))
self.vis.text (self.log_text, win=win)

def __getattr__ (self, name):
return getattr (self.vis, name)



if __name__ == '__main__':
if args.test:
test()
else:
train()
  • 注 :数据集用的cifar-10-batches-py
  • 结果:
  • 迭代一次
  • 迭代二次
  • 本文作者: SindreYang
  • 本文链接: http://blog.mviai.com/2018/回顾-DCGAN/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

欢迎关注我的其它发布渠道