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)

總結

總覺得這種方法並不夠好,甚至會有隱患,但目前並沒有發現危害也沒有找到更好的解決方法,就先這樣吧。

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