问题描述:
网络结构用的是u-net,结构大概如下:
目的是将红色矩形标出的latent层结果输出,同时不影响网络的正常训练。
这本来应该是一个很简单的问题,只需要self.latent, self.fake_B = self.netG.forward(self.real_A)即可。但问题在于U-Net的代码原本使用的是嵌套结构,代码如下:
class UnetGenerator(nn.Module):
def __init__(self, input_nc, output_nc, num_downs, ngf=64,
norm_layer=nn.BatchNorm2d, use_dropout=False, gpu_ids=[], use_parallel = True, learn_residual = False):
super(UnetGenerator, self).__init__()
self.gpu_ids = gpu_ids
self.use_parallel = use_parallel
self.learn_residual = learn_residual
assert(input_nc == output_nc)
unet_block = UnetSkipConnectionBlock(ngf * 8, ngf * 8, norm_layer=norm_layer, innermost=True)
for i in range(num_downs - 5):
unet_block = UnetSkipConnectionBlock(ngf * 8, ngf * 8, unet_block, norm_layer=norm_layer, use_dropout=use_dropout) # models/networks.py(306)__init__()
unet_block = UnetSkipConnectionBlock(ngf * 4, ngf * 8, unet_block, norm_layer=norm_layer)
unet_block = UnetSkipConnectionBlock(ngf * 2, ngf * 4, unet_block, norm_layer=norm_layer)
unet_block = UnetSkipConnectionBlock(ngf, ngf * 2, unet_block, norm_layer=norm_layer)
unet_block = UnetSkipConnectionBlock(output_nc, ngf, unet_block, outermost=True, norm_layer=norm_layer)
self.model = unet_block
def forward(self, input):
if self.gpu_ids and isinstance(input.data, torch.cuda.FloatTensor) and self.use_parallel:
output = nn.parallel.data_parallel(self.model, input, self.gpu_ids)
else:
output = self.model(input)
if self.learn_residual:
output = input + output
output = torch.clamp(output,min = -1,max = 1)
return output
# Defines the submodule with skip connection.
# X -------------------identity---------------------- X
# |-- downsampling -- |submodule| -- upsampling --|
class UnetSkipConnectionBlock(nn.Module):
def __init__(self, outer_nc, inner_nc,
submodule=None, outermost=False, innermost=False, norm_layer=nn.BatchNorm2d, use_dropout=False):
super(UnetSkipConnectionBlock, self).__init__()
self.outermost = outermost
self.innermost = innermost
if type(norm_layer) == functools.partial:
use_bias = norm_layer.func == nn.InstanceNorm2d
else:
use_bias = norm_layer == nn.InstanceNorm2d
downconv = nn.Conv2d(outer_nc, inner_nc, kernel_size=4,
stride=2, padding=1, bias=use_bias)
downrelu = nn.LeakyReLU(0.2, True)
downnorm = norm_layer(inner_nc)
uprelu = nn.ReLU(True)
upnorm = norm_layer(outer_nc)
if outermost:
upconv = nn.ConvTranspose2d(inner_nc * 2, outer_nc,
kernel_size=4, stride=2,
padding=1)
down = [downconv]
up = [uprelu, upconv, nn.Tanh()]
model = down + [submodule] + up
elif innermost:
upconv = nn.ConvTranspose2d(inner_nc, outer_nc,
kernel_size=4, stride=2,
padding=1, bias=use_bias)
down = [downrelu, downconv]
up = [uprelu, upconv, upnorm]
model = down + up
else:
upconv = nn.ConvTranspose2d(inner_nc * 2, outer_nc,
kernel_size=4, stride=2,
padding=1, bias=use_bias)
down = [downrelu, downconv, downnorm]
up = [uprelu, upconv, upnorm]
if use_dropout:
model = down + [submodule] + up + [nn.Dropout(0.5)]
else:
model = down + [submodule] + up
self.model = nn.Sequential(*model) # /models/networks.py(348)__init__()->None
def forward(self, x):
if self.outermost: # 最外层模型,直接输出
return self.model(x)
else:
return torch.cat([self.model(x), x], 1)
netG = UnetGenerator(input_nc, output_nc, 8, ngf, norm_layer=norm_layer, use_dropout=use_dropout, gpu_ids=gpu_ids, use_parallel=use_parallel, learn_residual = learn_residual)
可见,整体网络的forward()函数中嵌入了blocks的forward()函数,这就导致latent layer层的输出结果无法直接在整体网络的forward()函数中返回,否则将会报错且报错源是pytorch内部函数,无法轻易改动。
如果将U-Net的嵌套结构彻底更改的话也会非常麻烦。
本文主要目的就是总结对于这一问题的相对方便的解决方法。
解决思路
step1:找到blosks输出恰好为latent层输出的条件。 由代码可知,恰好是当且仅当UnetSkipConnectionBlock.innermost为Ture。
step2:在UnetSkipConnectionBlock类的forward函数中得到每次前向传播时的latent output;
step3:将step2中得到的latent output传到UnetGenerator的forward函数中;
step4:按照常规方法把传到step3的latent output传输到主函数中即可。
在上面四步中,第1,2步并不难,第4步更是常见操作。但第三步花了很长时间,主要是对python的Class不够熟悉。
最终的解决方案为使用全局变量传送该变量。因此,首先完成step1和step2并定义全局变量:
之后再在UnetGenerator的forward函数中按正常方法返回latent value即可:
最终代码
class UnetGenerator(nn.Module):
def __init__(self, input_nc, output_nc, num_downs, ngf=64,
norm_layer=nn.BatchNorm2d, use_dropout=False, gpu_ids=[], use_parallel = True, learn_residual = False):
super(UnetGenerator, self).__init__()
self.gpu_ids = gpu_ids
self.use_parallel = use_parallel
self.learn_residual = learn_residual
assert(input_nc == output_nc)
unet_block = UnetSkipConnectionBlock(ngf * 8, ngf * 8, norm_layer=norm_layer, innermost=True)
for i in range(num_downs - 5):
unet_block = UnetSkipConnectionBlock(ngf * 8, ngf * 8, unet_block, norm_layer=norm_layer, use_dropout=use_dropout) # models/networks.py(306)__init__()
unet_block = UnetSkipConnectionBlock(ngf * 4, ngf * 8, unet_block, norm_layer=norm_layer)
unet_block = UnetSkipConnectionBlock(ngf * 2, ngf * 4, unet_block, norm_layer=norm_layer)
unet_block = UnetSkipConnectionBlock(ngf, ngf * 2, unet_block, norm_layer=norm_layer)
unet_block = UnetSkipConnectionBlock(output_nc, ngf, unet_block, outermost=True, norm_layer=norm_layer)
self.model = unet_block
def forward(self, input):
# pdb.set_trace()
if self.gpu_ids and isinstance(input.data, torch.cuda.FloatTensor) and self.use_parallel:
output = nn.parallel.data_parallel(self.model, input, self.gpu_ids)
else:
output = self.model(input)
# if self.model.outermost
# self.model.latent
# latent
if self.learn_residual:
output = input + output
output = torch.clamp(output,min = -1,max = 1)
return latent, output
# Defines the submodule with skip connection.
# X -------------------identity---------------------- X
# |-- downsampling -- |submodule| -- upsampling --|
class UnetSkipConnectionBlock(nn.Module):
def __init__(self, outer_nc, inner_nc,
submodule=None, outermost=False, innermost=False, norm_layer=nn.BatchNorm2d, use_dropout=False):
super(UnetSkipConnectionBlock, self).__init__()
self.outermost = outermost
self.innermost = innermost
if type(norm_layer) == functools.partial:
use_bias = norm_layer.func == nn.InstanceNorm2d
else:
use_bias = norm_layer == nn.InstanceNorm2d
downconv = nn.Conv2d(outer_nc, inner_nc, kernel_size=4,
stride=2, padding=1, bias=use_bias)
downrelu = nn.LeakyReLU(0.2, True)
downnorm = norm_layer(inner_nc)
uprelu = nn.ReLU(True)
upnorm = norm_layer(outer_nc)
if outermost:
upconv = nn.ConvTranspose2d(inner_nc * 2, outer_nc,
kernel_size=4, stride=2,
padding=1)
down = [downconv]
up = [uprelu, upconv, nn.Tanh()]
model = down + [submodule] + up
elif innermost:
upconv = nn.ConvTranspose2d(inner_nc, outer_nc,
kernel_size=4, stride=2,
padding=1, bias=use_bias)
down = [downrelu, downconv]
up = [uprelu, upconv, upnorm]
model = down + up
else:
upconv = nn.ConvTranspose2d(inner_nc * 2, outer_nc,
kernel_size=4, stride=2,
padding=1, bias=use_bias)
down = [downrelu, downconv, downnorm]
up = [uprelu, upconv, upnorm]
if use_dropout:
model = down + [submodule] + up + [nn.Dropout(0.5)]
else:
model = down + [submodule] + up
self.model = nn.Sequential(*model) # /models/networks.py(348)__init__()->None
def forward(self, x):
# pdb.set_trace()
global latent
if self.outermost: # 最外层模型,直接输出
return self.model(x)
elif self.innermost:
# pdb.set_trace()
self.latent = self.model(x) # [torch.cuda.FloatTensor of size 1x512x2x2 (GPU 6)]
latent = self.model(x)
print('latent get!')
return torch.cat([self.model(x), x], 1)
else:
return torch.cat([self.model(x), x], 1)
总结
总觉得这种方法并不够好,甚至会有隐患,但目前并没有发现危害也没有找到更好的解决方法,就先这样吧。