Pytorch 返回U-Net中间latent层结果的一种方法

问题描述:

网络结构用的是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)

总结

总觉得这种方法并不够好,甚至会有隐患,但目前并没有发现危害也没有找到更好的解决方法,就先这样吧。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章