mirror of
https://github.com/storytold/storyteller-ml.git
synced 2026-10-27 08:58:13 +00:00
Add files via upload
This commit is contained in:
Binary file not shown.
@@ -0,0 +1,257 @@
|
|||||||
|
import os
|
||||||
|
import cv2
|
||||||
|
import yaml
|
||||||
|
import numpy as np
|
||||||
|
import warnings
|
||||||
|
from skimage import img_as_ubyte
|
||||||
|
import safetensors
|
||||||
|
import safetensors.torch
|
||||||
|
warnings.filterwarnings('ignore')
|
||||||
|
|
||||||
|
|
||||||
|
import imageio
|
||||||
|
import torch
|
||||||
|
import torchvision
|
||||||
|
|
||||||
|
|
||||||
|
from src.facerender.modules.keypoint_detector import HEEstimator, KPDetector
|
||||||
|
from src.facerender.modules.mapping import MappingNet
|
||||||
|
from src.facerender.modules.generator import OcclusionAwareGenerator, OcclusionAwareSPADEGenerator
|
||||||
|
from src.facerender.modules.make_animation import make_animation
|
||||||
|
|
||||||
|
from pydub import AudioSegment
|
||||||
|
from src.utils.face_enhancer import enhancer_generator_with_len, enhancer_list
|
||||||
|
from src.utils.paste_pic import paste_pic
|
||||||
|
from src.utils.videoio import save_video_with_watermark
|
||||||
|
|
||||||
|
try:
|
||||||
|
import webui # in webui
|
||||||
|
in_webui = True
|
||||||
|
except:
|
||||||
|
in_webui = False
|
||||||
|
|
||||||
|
class AnimateFromCoeff():
|
||||||
|
|
||||||
|
def __init__(self, sadtalker_path, device):
|
||||||
|
|
||||||
|
with open(sadtalker_path['facerender_yaml']) as f:
|
||||||
|
config = yaml.safe_load(f)
|
||||||
|
|
||||||
|
generator = OcclusionAwareSPADEGenerator(**config['model_params']['generator_params'],
|
||||||
|
**config['model_params']['common_params'])
|
||||||
|
kp_extractor = KPDetector(**config['model_params']['kp_detector_params'],
|
||||||
|
**config['model_params']['common_params'])
|
||||||
|
he_estimator = HEEstimator(**config['model_params']['he_estimator_params'],
|
||||||
|
**config['model_params']['common_params'])
|
||||||
|
mapping = MappingNet(**config['model_params']['mapping_params'])
|
||||||
|
|
||||||
|
generator.to(device)
|
||||||
|
kp_extractor.to(device)
|
||||||
|
he_estimator.to(device)
|
||||||
|
mapping.to(device)
|
||||||
|
for param in generator.parameters():
|
||||||
|
param.requires_grad = False
|
||||||
|
for param in kp_extractor.parameters():
|
||||||
|
param.requires_grad = False
|
||||||
|
for param in he_estimator.parameters():
|
||||||
|
param.requires_grad = False
|
||||||
|
for param in mapping.parameters():
|
||||||
|
param.requires_grad = False
|
||||||
|
|
||||||
|
if sadtalker_path is not None:
|
||||||
|
if 'checkpoint' in sadtalker_path: # use safe tensor
|
||||||
|
self.load_cpk_facevid2vid_safetensor(sadtalker_path['checkpoint'], kp_detector=kp_extractor, generator=generator, he_estimator=None)
|
||||||
|
else:
|
||||||
|
self.load_cpk_facevid2vid(sadtalker_path['free_view_checkpoint'], kp_detector=kp_extractor, generator=generator, he_estimator=he_estimator)
|
||||||
|
else:
|
||||||
|
raise AttributeError("Checkpoint should be specified for video head pose estimator.")
|
||||||
|
|
||||||
|
if sadtalker_path['mappingnet_checkpoint'] is not None:
|
||||||
|
self.load_cpk_mapping(sadtalker_path['mappingnet_checkpoint'], mapping=mapping)
|
||||||
|
else:
|
||||||
|
raise AttributeError("Checkpoint should be specified for video head pose estimator.")
|
||||||
|
|
||||||
|
self.kp_extractor = kp_extractor
|
||||||
|
self.generator = generator
|
||||||
|
self.he_estimator = he_estimator
|
||||||
|
self.mapping = mapping
|
||||||
|
|
||||||
|
self.kp_extractor.eval()
|
||||||
|
self.generator.eval()
|
||||||
|
self.he_estimator.eval()
|
||||||
|
self.mapping.eval()
|
||||||
|
|
||||||
|
self.device = device
|
||||||
|
|
||||||
|
def load_cpk_facevid2vid_safetensor(self, checkpoint_path, generator=None,
|
||||||
|
kp_detector=None, he_estimator=None,
|
||||||
|
device="cpu"):
|
||||||
|
|
||||||
|
checkpoint = safetensors.torch.load_file(checkpoint_path)
|
||||||
|
|
||||||
|
if generator is not None:
|
||||||
|
x_generator = {}
|
||||||
|
for k,v in checkpoint.items():
|
||||||
|
if 'generator' in k:
|
||||||
|
x_generator[k.replace('generator.', '')] = v
|
||||||
|
generator.load_state_dict(x_generator)
|
||||||
|
if kp_detector is not None:
|
||||||
|
x_generator = {}
|
||||||
|
for k,v in checkpoint.items():
|
||||||
|
if 'kp_extractor' in k:
|
||||||
|
x_generator[k.replace('kp_extractor.', '')] = v
|
||||||
|
kp_detector.load_state_dict(x_generator)
|
||||||
|
if he_estimator is not None:
|
||||||
|
x_generator = {}
|
||||||
|
for k,v in checkpoint.items():
|
||||||
|
if 'he_estimator' in k:
|
||||||
|
x_generator[k.replace('he_estimator.', '')] = v
|
||||||
|
he_estimator.load_state_dict(x_generator)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def load_cpk_facevid2vid(self, checkpoint_path, generator=None, discriminator=None,
|
||||||
|
kp_detector=None, he_estimator=None, optimizer_generator=None,
|
||||||
|
optimizer_discriminator=None, optimizer_kp_detector=None,
|
||||||
|
optimizer_he_estimator=None, device="cpu"):
|
||||||
|
checkpoint = torch.load(checkpoint_path, map_location=torch.device(device))
|
||||||
|
if generator is not None:
|
||||||
|
generator.load_state_dict(checkpoint['generator'])
|
||||||
|
if kp_detector is not None:
|
||||||
|
kp_detector.load_state_dict(checkpoint['kp_detector'])
|
||||||
|
if he_estimator is not None:
|
||||||
|
he_estimator.load_state_dict(checkpoint['he_estimator'])
|
||||||
|
if discriminator is not None:
|
||||||
|
try:
|
||||||
|
discriminator.load_state_dict(checkpoint['discriminator'])
|
||||||
|
except:
|
||||||
|
print ('No discriminator in the state-dict. Dicriminator will be randomly initialized')
|
||||||
|
if optimizer_generator is not None:
|
||||||
|
optimizer_generator.load_state_dict(checkpoint['optimizer_generator'])
|
||||||
|
if optimizer_discriminator is not None:
|
||||||
|
try:
|
||||||
|
optimizer_discriminator.load_state_dict(checkpoint['optimizer_discriminator'])
|
||||||
|
except RuntimeError as e:
|
||||||
|
print ('No discriminator optimizer in the state-dict. Optimizer will be not initialized')
|
||||||
|
if optimizer_kp_detector is not None:
|
||||||
|
optimizer_kp_detector.load_state_dict(checkpoint['optimizer_kp_detector'])
|
||||||
|
if optimizer_he_estimator is not None:
|
||||||
|
optimizer_he_estimator.load_state_dict(checkpoint['optimizer_he_estimator'])
|
||||||
|
|
||||||
|
return checkpoint['epoch']
|
||||||
|
|
||||||
|
def load_cpk_mapping(self, checkpoint_path, mapping=None, discriminator=None,
|
||||||
|
optimizer_mapping=None, optimizer_discriminator=None, device='cpu'):
|
||||||
|
checkpoint = torch.load(checkpoint_path, map_location=torch.device(device))
|
||||||
|
if mapping is not None:
|
||||||
|
mapping.load_state_dict(checkpoint['mapping'])
|
||||||
|
if discriminator is not None:
|
||||||
|
discriminator.load_state_dict(checkpoint['discriminator'])
|
||||||
|
if optimizer_mapping is not None:
|
||||||
|
optimizer_mapping.load_state_dict(checkpoint['optimizer_mapping'])
|
||||||
|
if optimizer_discriminator is not None:
|
||||||
|
optimizer_discriminator.load_state_dict(checkpoint['optimizer_discriminator'])
|
||||||
|
|
||||||
|
return checkpoint['epoch']
|
||||||
|
|
||||||
|
def generate(self, x, video_save_dir, pic_path, crop_info, enhancer=None, background_enhancer=None, preprocess='crop', img_size=256):
|
||||||
|
|
||||||
|
source_image=x['source_image'].type(torch.FloatTensor)
|
||||||
|
source_semantics=x['source_semantics'].type(torch.FloatTensor)
|
||||||
|
target_semantics=x['target_semantics_list'].type(torch.FloatTensor)
|
||||||
|
source_image=source_image.to(self.device)
|
||||||
|
source_semantics=source_semantics.to(self.device)
|
||||||
|
target_semantics=target_semantics.to(self.device)
|
||||||
|
if 'yaw_c_seq' in x:
|
||||||
|
yaw_c_seq = x['yaw_c_seq'].type(torch.FloatTensor)
|
||||||
|
yaw_c_seq = x['yaw_c_seq'].to(self.device)
|
||||||
|
else:
|
||||||
|
yaw_c_seq = None
|
||||||
|
if 'pitch_c_seq' in x:
|
||||||
|
pitch_c_seq = x['pitch_c_seq'].type(torch.FloatTensor)
|
||||||
|
pitch_c_seq = x['pitch_c_seq'].to(self.device)
|
||||||
|
else:
|
||||||
|
pitch_c_seq = None
|
||||||
|
if 'roll_c_seq' in x:
|
||||||
|
roll_c_seq = x['roll_c_seq'].type(torch.FloatTensor)
|
||||||
|
roll_c_seq = x['roll_c_seq'].to(self.device)
|
||||||
|
else:
|
||||||
|
roll_c_seq = None
|
||||||
|
|
||||||
|
frame_num = x['frame_num']
|
||||||
|
|
||||||
|
predictions_video = make_animation(source_image, source_semantics, target_semantics,
|
||||||
|
self.generator, self.kp_extractor, self.he_estimator, self.mapping,
|
||||||
|
yaw_c_seq, pitch_c_seq, roll_c_seq, use_exp = True)
|
||||||
|
|
||||||
|
predictions_video = predictions_video.reshape((-1,)+predictions_video.shape[2:])
|
||||||
|
predictions_video = predictions_video[:frame_num]
|
||||||
|
|
||||||
|
video = []
|
||||||
|
for idx in range(predictions_video.shape[0]):
|
||||||
|
image = predictions_video[idx]
|
||||||
|
image = np.transpose(image.data.cpu().numpy(), [1, 2, 0]).astype(np.float32)
|
||||||
|
video.append(image)
|
||||||
|
result = img_as_ubyte(video)
|
||||||
|
|
||||||
|
### the generated video is 256x256, so we keep the aspect ratio,
|
||||||
|
original_size = crop_info[0]
|
||||||
|
if original_size:
|
||||||
|
result = [ cv2.resize(result_i,(img_size, int(img_size * original_size[1]/original_size[0]) )) for result_i in result ]
|
||||||
|
|
||||||
|
video_name = x['video_name'] + '.mp4'
|
||||||
|
path = os.path.join(video_save_dir, 'temp_'+video_name)
|
||||||
|
|
||||||
|
imageio.mimsave(path, result, fps=float(25))
|
||||||
|
|
||||||
|
av_path = os.path.join(video_save_dir, video_name)
|
||||||
|
return_path = av_path
|
||||||
|
|
||||||
|
audio_path = x['audio_path']
|
||||||
|
audio_name = os.path.splitext(os.path.split(audio_path)[-1])[0]
|
||||||
|
new_audio_path = os.path.join(video_save_dir, audio_name+'.wav')
|
||||||
|
start_time = 0
|
||||||
|
# cog will not keep the .mp3 filename
|
||||||
|
sound = AudioSegment.from_file(audio_path)
|
||||||
|
frames = frame_num
|
||||||
|
end_time = start_time + frames*1/25*1000
|
||||||
|
word1=sound.set_frame_rate(16000)
|
||||||
|
word = word1[start_time:end_time]
|
||||||
|
word.export(new_audio_path, format="wav")
|
||||||
|
|
||||||
|
save_video_with_watermark(path, new_audio_path, av_path, watermark= False)
|
||||||
|
print(f'The generated video is named {video_save_dir}/{video_name}')
|
||||||
|
|
||||||
|
if 'full' in preprocess.lower():
|
||||||
|
# only add watermark to the full image.
|
||||||
|
video_name_full = x['video_name'] + '_full.mp4'
|
||||||
|
full_video_path = os.path.join(video_save_dir, video_name_full)
|
||||||
|
return_path = full_video_path
|
||||||
|
paste_pic(path, pic_path, crop_info, new_audio_path, full_video_path, extended_crop= True if 'ext' in preprocess.lower() else False)
|
||||||
|
print(f'The generated video is named {video_save_dir}/{video_name_full}')
|
||||||
|
else:
|
||||||
|
full_video_path = av_path
|
||||||
|
|
||||||
|
#### paste back then enhancers
|
||||||
|
if enhancer:
|
||||||
|
video_name_enhancer = x['video_name'] + '_enhanced.mp4'
|
||||||
|
enhanced_path = os.path.join(video_save_dir, 'temp_'+video_name_enhancer)
|
||||||
|
av_path_enhancer = os.path.join(video_save_dir, video_name_enhancer)
|
||||||
|
return_path = av_path_enhancer
|
||||||
|
|
||||||
|
try:
|
||||||
|
enhanced_images_gen_with_len = enhancer_generator_with_len(full_video_path, method=enhancer, bg_upsampler=background_enhancer)
|
||||||
|
imageio.mimsave(enhanced_path, enhanced_images_gen_with_len, fps=float(25))
|
||||||
|
except:
|
||||||
|
enhanced_images_gen_with_len = enhancer_list(full_video_path, method=enhancer, bg_upsampler=background_enhancer)
|
||||||
|
imageio.mimsave(enhanced_path, enhanced_images_gen_with_len, fps=float(25))
|
||||||
|
|
||||||
|
save_video_with_watermark(enhanced_path, new_audio_path, av_path_enhancer, watermark= False)
|
||||||
|
print(f'The generated video is named {video_save_dir}/{video_name_enhancer}')
|
||||||
|
os.remove(enhanced_path)
|
||||||
|
|
||||||
|
os.remove(path)
|
||||||
|
os.remove(new_audio_path)
|
||||||
|
|
||||||
|
return return_path
|
||||||
|
|
||||||
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,121 @@
|
|||||||
|
from torch import nn
|
||||||
|
import torch.nn.functional as F
|
||||||
|
import torch
|
||||||
|
from src.facerender.modules.util import Hourglass, make_coordinate_grid, kp2gaussian
|
||||||
|
|
||||||
|
from src.facerender.sync_batchnorm import SynchronizedBatchNorm3d as BatchNorm3d
|
||||||
|
|
||||||
|
|
||||||
|
class DenseMotionNetwork(nn.Module):
|
||||||
|
"""
|
||||||
|
Module that predicting a dense motion from sparse motion representation given by kp_source and kp_driving
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, block_expansion, num_blocks, max_features, num_kp, feature_channel, reshape_depth, compress,
|
||||||
|
estimate_occlusion_map=False):
|
||||||
|
super(DenseMotionNetwork, self).__init__()
|
||||||
|
# self.hourglass = Hourglass(block_expansion=block_expansion, in_features=(num_kp+1)*(feature_channel+1), max_features=max_features, num_blocks=num_blocks)
|
||||||
|
self.hourglass = Hourglass(block_expansion=block_expansion, in_features=(num_kp+1)*(compress+1), max_features=max_features, num_blocks=num_blocks)
|
||||||
|
|
||||||
|
self.mask = nn.Conv3d(self.hourglass.out_filters, num_kp + 1, kernel_size=7, padding=3)
|
||||||
|
|
||||||
|
self.compress = nn.Conv3d(feature_channel, compress, kernel_size=1)
|
||||||
|
self.norm = BatchNorm3d(compress, affine=True)
|
||||||
|
|
||||||
|
if estimate_occlusion_map:
|
||||||
|
# self.occlusion = nn.Conv2d(reshape_channel*reshape_depth, 1, kernel_size=7, padding=3)
|
||||||
|
self.occlusion = nn.Conv2d(self.hourglass.out_filters*reshape_depth, 1, kernel_size=7, padding=3)
|
||||||
|
else:
|
||||||
|
self.occlusion = None
|
||||||
|
|
||||||
|
self.num_kp = num_kp
|
||||||
|
|
||||||
|
|
||||||
|
def create_sparse_motions(self, feature, kp_driving, kp_source):
|
||||||
|
bs, _, d, h, w = feature.shape
|
||||||
|
identity_grid = make_coordinate_grid((d, h, w), type=kp_source['value'].type())
|
||||||
|
identity_grid = identity_grid.view(1, 1, d, h, w, 3)
|
||||||
|
coordinate_grid = identity_grid - kp_driving['value'].view(bs, self.num_kp, 1, 1, 1, 3)
|
||||||
|
|
||||||
|
# if 'jacobian' in kp_driving:
|
||||||
|
if 'jacobian' in kp_driving and kp_driving['jacobian'] is not None:
|
||||||
|
jacobian = torch.matmul(kp_source['jacobian'], torch.inverse(kp_driving['jacobian']))
|
||||||
|
jacobian = jacobian.unsqueeze(-3).unsqueeze(-3).unsqueeze(-3)
|
||||||
|
jacobian = jacobian.repeat(1, 1, d, h, w, 1, 1)
|
||||||
|
coordinate_grid = torch.matmul(jacobian, coordinate_grid.unsqueeze(-1))
|
||||||
|
coordinate_grid = coordinate_grid.squeeze(-1)
|
||||||
|
|
||||||
|
|
||||||
|
driving_to_source = coordinate_grid + kp_source['value'].view(bs, self.num_kp, 1, 1, 1, 3) # (bs, num_kp, d, h, w, 3)
|
||||||
|
|
||||||
|
#adding background feature
|
||||||
|
identity_grid = identity_grid.repeat(bs, 1, 1, 1, 1, 1)
|
||||||
|
sparse_motions = torch.cat([identity_grid, driving_to_source], dim=1) #bs num_kp+1 d h w 3
|
||||||
|
|
||||||
|
# sparse_motions = driving_to_source
|
||||||
|
|
||||||
|
return sparse_motions
|
||||||
|
|
||||||
|
def create_deformed_feature(self, feature, sparse_motions):
|
||||||
|
bs, _, d, h, w = feature.shape
|
||||||
|
feature_repeat = feature.unsqueeze(1).unsqueeze(1).repeat(1, self.num_kp+1, 1, 1, 1, 1, 1) # (bs, num_kp+1, 1, c, d, h, w)
|
||||||
|
feature_repeat = feature_repeat.view(bs * (self.num_kp+1), -1, d, h, w) # (bs*(num_kp+1), c, d, h, w)
|
||||||
|
sparse_motions = sparse_motions.view((bs * (self.num_kp+1), d, h, w, -1)) # (bs*(num_kp+1), d, h, w, 3) !!!!
|
||||||
|
sparse_deformed = F.grid_sample(feature_repeat, sparse_motions)
|
||||||
|
sparse_deformed = sparse_deformed.view((bs, self.num_kp+1, -1, d, h, w)) # (bs, num_kp+1, c, d, h, w)
|
||||||
|
return sparse_deformed
|
||||||
|
|
||||||
|
def create_heatmap_representations(self, feature, kp_driving, kp_source):
|
||||||
|
spatial_size = feature.shape[3:]
|
||||||
|
gaussian_driving = kp2gaussian(kp_driving, spatial_size=spatial_size, kp_variance=0.01)
|
||||||
|
gaussian_source = kp2gaussian(kp_source, spatial_size=spatial_size, kp_variance=0.01)
|
||||||
|
heatmap = gaussian_driving - gaussian_source
|
||||||
|
|
||||||
|
# adding background feature
|
||||||
|
zeros = torch.zeros(heatmap.shape[0], 1, spatial_size[0], spatial_size[1], spatial_size[2]).type(heatmap.type())
|
||||||
|
heatmap = torch.cat([zeros, heatmap], dim=1)
|
||||||
|
heatmap = heatmap.unsqueeze(2) # (bs, num_kp+1, 1, d, h, w)
|
||||||
|
return heatmap
|
||||||
|
|
||||||
|
def forward(self, feature, kp_driving, kp_source):
|
||||||
|
bs, _, d, h, w = feature.shape
|
||||||
|
|
||||||
|
feature = self.compress(feature)
|
||||||
|
feature = self.norm(feature)
|
||||||
|
feature = F.relu(feature)
|
||||||
|
|
||||||
|
out_dict = dict()
|
||||||
|
sparse_motion = self.create_sparse_motions(feature, kp_driving, kp_source)
|
||||||
|
deformed_feature = self.create_deformed_feature(feature, sparse_motion)
|
||||||
|
|
||||||
|
heatmap = self.create_heatmap_representations(deformed_feature, kp_driving, kp_source)
|
||||||
|
|
||||||
|
input_ = torch.cat([heatmap, deformed_feature], dim=2)
|
||||||
|
input_ = input_.view(bs, -1, d, h, w)
|
||||||
|
|
||||||
|
# input = deformed_feature.view(bs, -1, d, h, w) # (bs, num_kp+1 * c, d, h, w)
|
||||||
|
|
||||||
|
prediction = self.hourglass(input_)
|
||||||
|
|
||||||
|
|
||||||
|
mask = self.mask(prediction)
|
||||||
|
mask = F.softmax(mask, dim=1)
|
||||||
|
out_dict['mask'] = mask
|
||||||
|
mask = mask.unsqueeze(2) # (bs, num_kp+1, 1, d, h, w)
|
||||||
|
|
||||||
|
zeros_mask = torch.zeros_like(mask)
|
||||||
|
mask = torch.where(mask < 1e-3, zeros_mask, mask)
|
||||||
|
|
||||||
|
sparse_motion = sparse_motion.permute(0, 1, 5, 2, 3, 4) # (bs, num_kp+1, 3, d, h, w)
|
||||||
|
deformation = (sparse_motion * mask).sum(dim=1) # (bs, 3, d, h, w)
|
||||||
|
deformation = deformation.permute(0, 2, 3, 4, 1) # (bs, d, h, w, 3)
|
||||||
|
|
||||||
|
out_dict['deformation'] = deformation
|
||||||
|
|
||||||
|
if self.occlusion:
|
||||||
|
bs, c, d, h, w = prediction.shape
|
||||||
|
prediction = prediction.view(bs, -1, h, w)
|
||||||
|
occlusion_map = torch.sigmoid(self.occlusion(prediction))
|
||||||
|
out_dict['occlusion_map'] = occlusion_map
|
||||||
|
|
||||||
|
return out_dict
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
from torch import nn
|
||||||
|
import torch.nn.functional as F
|
||||||
|
from facerender.modules.util import kp2gaussian
|
||||||
|
import torch
|
||||||
|
|
||||||
|
|
||||||
|
class DownBlock2d(nn.Module):
|
||||||
|
"""
|
||||||
|
Simple block for processing video (encoder).
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, in_features, out_features, norm=False, kernel_size=4, pool=False, sn=False):
|
||||||
|
super(DownBlock2d, self).__init__()
|
||||||
|
self.conv = nn.Conv2d(in_channels=in_features, out_channels=out_features, kernel_size=kernel_size)
|
||||||
|
|
||||||
|
if sn:
|
||||||
|
self.conv = nn.utils.spectral_norm(self.conv)
|
||||||
|
|
||||||
|
if norm:
|
||||||
|
self.norm = nn.InstanceNorm2d(out_features, affine=True)
|
||||||
|
else:
|
||||||
|
self.norm = None
|
||||||
|
self.pool = pool
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
out = x
|
||||||
|
out = self.conv(out)
|
||||||
|
if self.norm:
|
||||||
|
out = self.norm(out)
|
||||||
|
out = F.leaky_relu(out, 0.2)
|
||||||
|
if self.pool:
|
||||||
|
out = F.avg_pool2d(out, (2, 2))
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
class Discriminator(nn.Module):
|
||||||
|
"""
|
||||||
|
Discriminator similar to Pix2Pix
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, num_channels=3, block_expansion=64, num_blocks=4, max_features=512,
|
||||||
|
sn=False, **kwargs):
|
||||||
|
super(Discriminator, self).__init__()
|
||||||
|
|
||||||
|
down_blocks = []
|
||||||
|
for i in range(num_blocks):
|
||||||
|
down_blocks.append(
|
||||||
|
DownBlock2d(num_channels if i == 0 else min(max_features, block_expansion * (2 ** i)),
|
||||||
|
min(max_features, block_expansion * (2 ** (i + 1))),
|
||||||
|
norm=(i != 0), kernel_size=4, pool=(i != num_blocks - 1), sn=sn))
|
||||||
|
|
||||||
|
self.down_blocks = nn.ModuleList(down_blocks)
|
||||||
|
self.conv = nn.Conv2d(self.down_blocks[-1].conv.out_channels, out_channels=1, kernel_size=1)
|
||||||
|
if sn:
|
||||||
|
self.conv = nn.utils.spectral_norm(self.conv)
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
feature_maps = []
|
||||||
|
out = x
|
||||||
|
|
||||||
|
for down_block in self.down_blocks:
|
||||||
|
feature_maps.append(down_block(out))
|
||||||
|
out = feature_maps[-1]
|
||||||
|
prediction_map = self.conv(out)
|
||||||
|
|
||||||
|
return feature_maps, prediction_map
|
||||||
|
|
||||||
|
|
||||||
|
class MultiScaleDiscriminator(nn.Module):
|
||||||
|
"""
|
||||||
|
Multi-scale (scale) discriminator
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, scales=(), **kwargs):
|
||||||
|
super(MultiScaleDiscriminator, self).__init__()
|
||||||
|
self.scales = scales
|
||||||
|
discs = {}
|
||||||
|
for scale in scales:
|
||||||
|
discs[str(scale).replace('.', '-')] = Discriminator(**kwargs)
|
||||||
|
self.discs = nn.ModuleDict(discs)
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
out_dict = {}
|
||||||
|
for scale, disc in self.discs.items():
|
||||||
|
scale = str(scale).replace('-', '.')
|
||||||
|
key = 'prediction_' + scale
|
||||||
|
feature_maps, prediction_map = disc(x[key])
|
||||||
|
out_dict['feature_maps_' + scale] = feature_maps
|
||||||
|
out_dict['prediction_map_' + scale] = prediction_map
|
||||||
|
return out_dict
|
||||||
@@ -0,0 +1,255 @@
|
|||||||
|
import torch
|
||||||
|
from torch import nn
|
||||||
|
import torch.nn.functional as F
|
||||||
|
from src.facerender.modules.util import ResBlock2d, SameBlock2d, UpBlock2d, DownBlock2d, ResBlock3d, SPADEResnetBlock
|
||||||
|
from src.facerender.modules.dense_motion import DenseMotionNetwork
|
||||||
|
|
||||||
|
|
||||||
|
class OcclusionAwareGenerator(nn.Module):
|
||||||
|
"""
|
||||||
|
Generator follows NVIDIA architecture.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, image_channel, feature_channel, num_kp, block_expansion, max_features, num_down_blocks, reshape_channel, reshape_depth,
|
||||||
|
num_resblocks, estimate_occlusion_map=False, dense_motion_params=None, estimate_jacobian=False):
|
||||||
|
super(OcclusionAwareGenerator, self).__init__()
|
||||||
|
|
||||||
|
if dense_motion_params is not None:
|
||||||
|
self.dense_motion_network = DenseMotionNetwork(num_kp=num_kp, feature_channel=feature_channel,
|
||||||
|
estimate_occlusion_map=estimate_occlusion_map,
|
||||||
|
**dense_motion_params)
|
||||||
|
else:
|
||||||
|
self.dense_motion_network = None
|
||||||
|
|
||||||
|
self.first = SameBlock2d(image_channel, block_expansion, kernel_size=(7, 7), padding=(3, 3))
|
||||||
|
|
||||||
|
down_blocks = []
|
||||||
|
for i in range(num_down_blocks):
|
||||||
|
in_features = min(max_features, block_expansion * (2 ** i))
|
||||||
|
out_features = min(max_features, block_expansion * (2 ** (i + 1)))
|
||||||
|
down_blocks.append(DownBlock2d(in_features, out_features, kernel_size=(3, 3), padding=(1, 1)))
|
||||||
|
self.down_blocks = nn.ModuleList(down_blocks)
|
||||||
|
|
||||||
|
self.second = nn.Conv2d(in_channels=out_features, out_channels=max_features, kernel_size=1, stride=1)
|
||||||
|
|
||||||
|
self.reshape_channel = reshape_channel
|
||||||
|
self.reshape_depth = reshape_depth
|
||||||
|
|
||||||
|
self.resblocks_3d = torch.nn.Sequential()
|
||||||
|
for i in range(num_resblocks):
|
||||||
|
self.resblocks_3d.add_module('3dr' + str(i), ResBlock3d(reshape_channel, kernel_size=3, padding=1))
|
||||||
|
|
||||||
|
out_features = block_expansion * (2 ** (num_down_blocks))
|
||||||
|
self.third = SameBlock2d(max_features, out_features, kernel_size=(3, 3), padding=(1, 1), lrelu=True)
|
||||||
|
self.fourth = nn.Conv2d(in_channels=out_features, out_channels=out_features, kernel_size=1, stride=1)
|
||||||
|
|
||||||
|
self.resblocks_2d = torch.nn.Sequential()
|
||||||
|
for i in range(num_resblocks):
|
||||||
|
self.resblocks_2d.add_module('2dr' + str(i), ResBlock2d(out_features, kernel_size=3, padding=1))
|
||||||
|
|
||||||
|
up_blocks = []
|
||||||
|
for i in range(num_down_blocks):
|
||||||
|
in_features = max(block_expansion, block_expansion * (2 ** (num_down_blocks - i)))
|
||||||
|
out_features = max(block_expansion, block_expansion * (2 ** (num_down_blocks - i - 1)))
|
||||||
|
up_blocks.append(UpBlock2d(in_features, out_features, kernel_size=(3, 3), padding=(1, 1)))
|
||||||
|
self.up_blocks = nn.ModuleList(up_blocks)
|
||||||
|
|
||||||
|
self.final = nn.Conv2d(block_expansion, image_channel, kernel_size=(7, 7), padding=(3, 3))
|
||||||
|
self.estimate_occlusion_map = estimate_occlusion_map
|
||||||
|
self.image_channel = image_channel
|
||||||
|
|
||||||
|
def deform_input(self, inp, deformation):
|
||||||
|
_, d_old, h_old, w_old, _ = deformation.shape
|
||||||
|
_, _, d, h, w = inp.shape
|
||||||
|
if d_old != d or h_old != h or w_old != w:
|
||||||
|
deformation = deformation.permute(0, 4, 1, 2, 3)
|
||||||
|
deformation = F.interpolate(deformation, size=(d, h, w), mode='trilinear')
|
||||||
|
deformation = deformation.permute(0, 2, 3, 4, 1)
|
||||||
|
return F.grid_sample(inp, deformation)
|
||||||
|
|
||||||
|
def forward(self, source_image, kp_driving, kp_source):
|
||||||
|
# Encoding (downsampling) part
|
||||||
|
out = self.first(source_image)
|
||||||
|
for i in range(len(self.down_blocks)):
|
||||||
|
out = self.down_blocks[i](out)
|
||||||
|
out = self.second(out)
|
||||||
|
bs, c, h, w = out.shape
|
||||||
|
# print(out.shape)
|
||||||
|
feature_3d = out.view(bs, self.reshape_channel, self.reshape_depth, h ,w)
|
||||||
|
feature_3d = self.resblocks_3d(feature_3d)
|
||||||
|
|
||||||
|
# Transforming feature representation according to deformation and occlusion
|
||||||
|
output_dict = {}
|
||||||
|
if self.dense_motion_network is not None:
|
||||||
|
dense_motion = self.dense_motion_network(feature=feature_3d, kp_driving=kp_driving,
|
||||||
|
kp_source=kp_source)
|
||||||
|
output_dict['mask'] = dense_motion['mask']
|
||||||
|
|
||||||
|
if 'occlusion_map' in dense_motion:
|
||||||
|
occlusion_map = dense_motion['occlusion_map']
|
||||||
|
output_dict['occlusion_map'] = occlusion_map
|
||||||
|
else:
|
||||||
|
occlusion_map = None
|
||||||
|
deformation = dense_motion['deformation']
|
||||||
|
out = self.deform_input(feature_3d, deformation)
|
||||||
|
|
||||||
|
bs, c, d, h, w = out.shape
|
||||||
|
out = out.view(bs, c*d, h, w)
|
||||||
|
out = self.third(out)
|
||||||
|
out = self.fourth(out)
|
||||||
|
|
||||||
|
if occlusion_map is not None:
|
||||||
|
if out.shape[2] != occlusion_map.shape[2] or out.shape[3] != occlusion_map.shape[3]:
|
||||||
|
occlusion_map = F.interpolate(occlusion_map, size=out.shape[2:], mode='bilinear')
|
||||||
|
out = out * occlusion_map
|
||||||
|
|
||||||
|
# output_dict["deformed"] = self.deform_input(source_image, deformation) # 3d deformation cannot deform 2d image
|
||||||
|
|
||||||
|
# Decoding part
|
||||||
|
out = self.resblocks_2d(out)
|
||||||
|
for i in range(len(self.up_blocks)):
|
||||||
|
out = self.up_blocks[i](out)
|
||||||
|
out = self.final(out)
|
||||||
|
out = F.sigmoid(out)
|
||||||
|
|
||||||
|
output_dict["prediction"] = out
|
||||||
|
|
||||||
|
return output_dict
|
||||||
|
|
||||||
|
|
||||||
|
class SPADEDecoder(nn.Module):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
ic = 256
|
||||||
|
oc = 64
|
||||||
|
norm_G = 'spadespectralinstance'
|
||||||
|
label_nc = 256
|
||||||
|
|
||||||
|
self.fc = nn.Conv2d(ic, 2 * ic, 3, padding=1)
|
||||||
|
self.G_middle_0 = SPADEResnetBlock(2 * ic, 2 * ic, norm_G, label_nc)
|
||||||
|
self.G_middle_1 = SPADEResnetBlock(2 * ic, 2 * ic, norm_G, label_nc)
|
||||||
|
self.G_middle_2 = SPADEResnetBlock(2 * ic, 2 * ic, norm_G, label_nc)
|
||||||
|
self.G_middle_3 = SPADEResnetBlock(2 * ic, 2 * ic, norm_G, label_nc)
|
||||||
|
self.G_middle_4 = SPADEResnetBlock(2 * ic, 2 * ic, norm_G, label_nc)
|
||||||
|
self.G_middle_5 = SPADEResnetBlock(2 * ic, 2 * ic, norm_G, label_nc)
|
||||||
|
self.up_0 = SPADEResnetBlock(2 * ic, ic, norm_G, label_nc)
|
||||||
|
self.up_1 = SPADEResnetBlock(ic, oc, norm_G, label_nc)
|
||||||
|
self.conv_img = nn.Conv2d(oc, 3, 3, padding=1)
|
||||||
|
self.up = nn.Upsample(scale_factor=2)
|
||||||
|
|
||||||
|
def forward(self, feature):
|
||||||
|
seg = feature
|
||||||
|
x = self.fc(feature)
|
||||||
|
x = self.G_middle_0(x, seg)
|
||||||
|
x = self.G_middle_1(x, seg)
|
||||||
|
x = self.G_middle_2(x, seg)
|
||||||
|
x = self.G_middle_3(x, seg)
|
||||||
|
x = self.G_middle_4(x, seg)
|
||||||
|
x = self.G_middle_5(x, seg)
|
||||||
|
x = self.up(x)
|
||||||
|
x = self.up_0(x, seg) # 256, 128, 128
|
||||||
|
x = self.up(x)
|
||||||
|
x = self.up_1(x, seg) # 64, 256, 256
|
||||||
|
|
||||||
|
x = self.conv_img(F.leaky_relu(x, 2e-1))
|
||||||
|
# x = torch.tanh(x)
|
||||||
|
x = F.sigmoid(x)
|
||||||
|
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
class OcclusionAwareSPADEGenerator(nn.Module):
|
||||||
|
|
||||||
|
def __init__(self, image_channel, feature_channel, num_kp, block_expansion, max_features, num_down_blocks, reshape_channel, reshape_depth,
|
||||||
|
num_resblocks, estimate_occlusion_map=False, dense_motion_params=None, estimate_jacobian=False):
|
||||||
|
super(OcclusionAwareSPADEGenerator, self).__init__()
|
||||||
|
|
||||||
|
if dense_motion_params is not None:
|
||||||
|
self.dense_motion_network = DenseMotionNetwork(num_kp=num_kp, feature_channel=feature_channel,
|
||||||
|
estimate_occlusion_map=estimate_occlusion_map,
|
||||||
|
**dense_motion_params)
|
||||||
|
else:
|
||||||
|
self.dense_motion_network = None
|
||||||
|
|
||||||
|
self.first = SameBlock2d(image_channel, block_expansion, kernel_size=(3, 3), padding=(1, 1))
|
||||||
|
|
||||||
|
down_blocks = []
|
||||||
|
for i in range(num_down_blocks):
|
||||||
|
in_features = min(max_features, block_expansion * (2 ** i))
|
||||||
|
out_features = min(max_features, block_expansion * (2 ** (i + 1)))
|
||||||
|
down_blocks.append(DownBlock2d(in_features, out_features, kernel_size=(3, 3), padding=(1, 1)))
|
||||||
|
self.down_blocks = nn.ModuleList(down_blocks)
|
||||||
|
|
||||||
|
self.second = nn.Conv2d(in_channels=out_features, out_channels=max_features, kernel_size=1, stride=1)
|
||||||
|
|
||||||
|
self.reshape_channel = reshape_channel
|
||||||
|
self.reshape_depth = reshape_depth
|
||||||
|
|
||||||
|
self.resblocks_3d = torch.nn.Sequential()
|
||||||
|
for i in range(num_resblocks):
|
||||||
|
self.resblocks_3d.add_module('3dr' + str(i), ResBlock3d(reshape_channel, kernel_size=3, padding=1))
|
||||||
|
|
||||||
|
out_features = block_expansion * (2 ** (num_down_blocks))
|
||||||
|
self.third = SameBlock2d(max_features, out_features, kernel_size=(3, 3), padding=(1, 1), lrelu=True)
|
||||||
|
self.fourth = nn.Conv2d(in_channels=out_features, out_channels=out_features, kernel_size=1, stride=1)
|
||||||
|
|
||||||
|
self.estimate_occlusion_map = estimate_occlusion_map
|
||||||
|
self.image_channel = image_channel
|
||||||
|
|
||||||
|
self.decoder = SPADEDecoder()
|
||||||
|
|
||||||
|
def deform_input(self, inp, deformation):
|
||||||
|
_, d_old, h_old, w_old, _ = deformation.shape
|
||||||
|
_, _, d, h, w = inp.shape
|
||||||
|
if d_old != d or h_old != h or w_old != w:
|
||||||
|
deformation = deformation.permute(0, 4, 1, 2, 3)
|
||||||
|
deformation = F.interpolate(deformation, size=(d, h, w), mode='trilinear')
|
||||||
|
deformation = deformation.permute(0, 2, 3, 4, 1)
|
||||||
|
return F.grid_sample(inp, deformation)
|
||||||
|
|
||||||
|
def forward(self, source_image, kp_driving, kp_source):
|
||||||
|
# Encoding (downsampling) part
|
||||||
|
out = self.first(source_image)
|
||||||
|
for i in range(len(self.down_blocks)):
|
||||||
|
out = self.down_blocks[i](out)
|
||||||
|
out = self.second(out)
|
||||||
|
bs, c, h, w = out.shape
|
||||||
|
# print(out.shape)
|
||||||
|
feature_3d = out.view(bs, self.reshape_channel, self.reshape_depth, h ,w)
|
||||||
|
feature_3d = self.resblocks_3d(feature_3d)
|
||||||
|
|
||||||
|
# Transforming feature representation according to deformation and occlusion
|
||||||
|
output_dict = {}
|
||||||
|
if self.dense_motion_network is not None:
|
||||||
|
dense_motion = self.dense_motion_network(feature=feature_3d, kp_driving=kp_driving,
|
||||||
|
kp_source=kp_source)
|
||||||
|
output_dict['mask'] = dense_motion['mask']
|
||||||
|
|
||||||
|
# import pdb; pdb.set_trace()
|
||||||
|
|
||||||
|
if 'occlusion_map' in dense_motion:
|
||||||
|
occlusion_map = dense_motion['occlusion_map']
|
||||||
|
output_dict['occlusion_map'] = occlusion_map
|
||||||
|
else:
|
||||||
|
occlusion_map = None
|
||||||
|
deformation = dense_motion['deformation']
|
||||||
|
out = self.deform_input(feature_3d, deformation)
|
||||||
|
|
||||||
|
bs, c, d, h, w = out.shape
|
||||||
|
out = out.view(bs, c*d, h, w)
|
||||||
|
out = self.third(out)
|
||||||
|
out = self.fourth(out)
|
||||||
|
|
||||||
|
# occlusion_map = torch.where(occlusion_map < 0.95, 0, occlusion_map)
|
||||||
|
|
||||||
|
if occlusion_map is not None:
|
||||||
|
if out.shape[2] != occlusion_map.shape[2] or out.shape[3] != occlusion_map.shape[3]:
|
||||||
|
occlusion_map = F.interpolate(occlusion_map, size=out.shape[2:], mode='bilinear')
|
||||||
|
out = out * occlusion_map
|
||||||
|
|
||||||
|
# Decoding part
|
||||||
|
out = self.decoder(out)
|
||||||
|
|
||||||
|
output_dict["prediction"] = out
|
||||||
|
|
||||||
|
return output_dict
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
from torch import nn
|
||||||
|
import torch
|
||||||
|
import torch.nn.functional as F
|
||||||
|
|
||||||
|
from src.facerender.sync_batchnorm import SynchronizedBatchNorm2d as BatchNorm2d
|
||||||
|
from src.facerender.modules.util import KPHourglass, make_coordinate_grid, AntiAliasInterpolation2d, ResBottleneck
|
||||||
|
|
||||||
|
|
||||||
|
class KPDetector(nn.Module):
|
||||||
|
"""
|
||||||
|
Detecting canonical keypoints. Return keypoint position and jacobian near each keypoint.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, block_expansion, feature_channel, num_kp, image_channel, max_features, reshape_channel, reshape_depth,
|
||||||
|
num_blocks, temperature, estimate_jacobian=False, scale_factor=1, single_jacobian_map=False):
|
||||||
|
super(KPDetector, self).__init__()
|
||||||
|
|
||||||
|
self.predictor = KPHourglass(block_expansion, in_features=image_channel,
|
||||||
|
max_features=max_features, reshape_features=reshape_channel, reshape_depth=reshape_depth, num_blocks=num_blocks)
|
||||||
|
|
||||||
|
# self.kp = nn.Conv3d(in_channels=self.predictor.out_filters, out_channels=num_kp, kernel_size=7, padding=3)
|
||||||
|
self.kp = nn.Conv3d(in_channels=self.predictor.out_filters, out_channels=num_kp, kernel_size=3, padding=1)
|
||||||
|
|
||||||
|
if estimate_jacobian:
|
||||||
|
self.num_jacobian_maps = 1 if single_jacobian_map else num_kp
|
||||||
|
# self.jacobian = nn.Conv3d(in_channels=self.predictor.out_filters, out_channels=9 * self.num_jacobian_maps, kernel_size=7, padding=3)
|
||||||
|
self.jacobian = nn.Conv3d(in_channels=self.predictor.out_filters, out_channels=9 * self.num_jacobian_maps, kernel_size=3, padding=1)
|
||||||
|
'''
|
||||||
|
initial as:
|
||||||
|
[[1 0 0]
|
||||||
|
[0 1 0]
|
||||||
|
[0 0 1]]
|
||||||
|
'''
|
||||||
|
self.jacobian.weight.data.zero_()
|
||||||
|
self.jacobian.bias.data.copy_(torch.tensor([1, 0, 0, 0, 1, 0, 0, 0, 1] * self.num_jacobian_maps, dtype=torch.float))
|
||||||
|
else:
|
||||||
|
self.jacobian = None
|
||||||
|
|
||||||
|
self.temperature = temperature
|
||||||
|
self.scale_factor = scale_factor
|
||||||
|
if self.scale_factor != 1:
|
||||||
|
self.down = AntiAliasInterpolation2d(image_channel, self.scale_factor)
|
||||||
|
|
||||||
|
def gaussian2kp(self, heatmap):
|
||||||
|
"""
|
||||||
|
Extract the mean from a heatmap
|
||||||
|
"""
|
||||||
|
shape = heatmap.shape
|
||||||
|
heatmap = heatmap.unsqueeze(-1)
|
||||||
|
grid = make_coordinate_grid(shape[2:], heatmap.type()).unsqueeze_(0).unsqueeze_(0)
|
||||||
|
value = (heatmap * grid).sum(dim=(2, 3, 4))
|
||||||
|
kp = {'value': value}
|
||||||
|
|
||||||
|
return kp
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
if self.scale_factor != 1:
|
||||||
|
x = self.down(x)
|
||||||
|
|
||||||
|
feature_map = self.predictor(x)
|
||||||
|
prediction = self.kp(feature_map)
|
||||||
|
|
||||||
|
final_shape = prediction.shape
|
||||||
|
heatmap = prediction.view(final_shape[0], final_shape[1], -1)
|
||||||
|
heatmap = F.softmax(heatmap / self.temperature, dim=2)
|
||||||
|
heatmap = heatmap.view(*final_shape)
|
||||||
|
|
||||||
|
out = self.gaussian2kp(heatmap)
|
||||||
|
|
||||||
|
if self.jacobian is not None:
|
||||||
|
jacobian_map = self.jacobian(feature_map)
|
||||||
|
jacobian_map = jacobian_map.reshape(final_shape[0], self.num_jacobian_maps, 9, final_shape[2],
|
||||||
|
final_shape[3], final_shape[4])
|
||||||
|
heatmap = heatmap.unsqueeze(2)
|
||||||
|
|
||||||
|
jacobian = heatmap * jacobian_map
|
||||||
|
jacobian = jacobian.view(final_shape[0], final_shape[1], 9, -1)
|
||||||
|
jacobian = jacobian.sum(dim=-1)
|
||||||
|
jacobian = jacobian.view(jacobian.shape[0], jacobian.shape[1], 3, 3)
|
||||||
|
out['jacobian'] = jacobian
|
||||||
|
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
class HEEstimator(nn.Module):
|
||||||
|
"""
|
||||||
|
Estimating head pose and expression.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, block_expansion, feature_channel, num_kp, image_channel, max_features, num_bins=66, estimate_jacobian=True):
|
||||||
|
super(HEEstimator, self).__init__()
|
||||||
|
|
||||||
|
self.conv1 = nn.Conv2d(in_channels=image_channel, out_channels=block_expansion, kernel_size=7, padding=3, stride=2)
|
||||||
|
self.norm1 = BatchNorm2d(block_expansion, affine=True)
|
||||||
|
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
|
||||||
|
|
||||||
|
self.conv2 = nn.Conv2d(in_channels=block_expansion, out_channels=256, kernel_size=1)
|
||||||
|
self.norm2 = BatchNorm2d(256, affine=True)
|
||||||
|
|
||||||
|
self.block1 = nn.Sequential()
|
||||||
|
for i in range(3):
|
||||||
|
self.block1.add_module('b1_'+ str(i), ResBottleneck(in_features=256, stride=1))
|
||||||
|
|
||||||
|
self.conv3 = nn.Conv2d(in_channels=256, out_channels=512, kernel_size=1)
|
||||||
|
self.norm3 = BatchNorm2d(512, affine=True)
|
||||||
|
self.block2 = ResBottleneck(in_features=512, stride=2)
|
||||||
|
|
||||||
|
self.block3 = nn.Sequential()
|
||||||
|
for i in range(3):
|
||||||
|
self.block3.add_module('b3_'+ str(i), ResBottleneck(in_features=512, stride=1))
|
||||||
|
|
||||||
|
self.conv4 = nn.Conv2d(in_channels=512, out_channels=1024, kernel_size=1)
|
||||||
|
self.norm4 = BatchNorm2d(1024, affine=True)
|
||||||
|
self.block4 = ResBottleneck(in_features=1024, stride=2)
|
||||||
|
|
||||||
|
self.block5 = nn.Sequential()
|
||||||
|
for i in range(5):
|
||||||
|
self.block5.add_module('b5_'+ str(i), ResBottleneck(in_features=1024, stride=1))
|
||||||
|
|
||||||
|
self.conv5 = nn.Conv2d(in_channels=1024, out_channels=2048, kernel_size=1)
|
||||||
|
self.norm5 = BatchNorm2d(2048, affine=True)
|
||||||
|
self.block6 = ResBottleneck(in_features=2048, stride=2)
|
||||||
|
|
||||||
|
self.block7 = nn.Sequential()
|
||||||
|
for i in range(2):
|
||||||
|
self.block7.add_module('b7_'+ str(i), ResBottleneck(in_features=2048, stride=1))
|
||||||
|
|
||||||
|
self.fc_roll = nn.Linear(2048, num_bins)
|
||||||
|
self.fc_pitch = nn.Linear(2048, num_bins)
|
||||||
|
self.fc_yaw = nn.Linear(2048, num_bins)
|
||||||
|
|
||||||
|
self.fc_t = nn.Linear(2048, 3)
|
||||||
|
|
||||||
|
self.fc_exp = nn.Linear(2048, 3*num_kp)
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
out = self.conv1(x)
|
||||||
|
out = self.norm1(out)
|
||||||
|
out = F.relu(out)
|
||||||
|
out = self.maxpool(out)
|
||||||
|
|
||||||
|
out = self.conv2(out)
|
||||||
|
out = self.norm2(out)
|
||||||
|
out = F.relu(out)
|
||||||
|
|
||||||
|
out = self.block1(out)
|
||||||
|
|
||||||
|
out = self.conv3(out)
|
||||||
|
out = self.norm3(out)
|
||||||
|
out = F.relu(out)
|
||||||
|
out = self.block2(out)
|
||||||
|
|
||||||
|
out = self.block3(out)
|
||||||
|
|
||||||
|
out = self.conv4(out)
|
||||||
|
out = self.norm4(out)
|
||||||
|
out = F.relu(out)
|
||||||
|
out = self.block4(out)
|
||||||
|
|
||||||
|
out = self.block5(out)
|
||||||
|
|
||||||
|
out = self.conv5(out)
|
||||||
|
out = self.norm5(out)
|
||||||
|
out = F.relu(out)
|
||||||
|
out = self.block6(out)
|
||||||
|
|
||||||
|
out = self.block7(out)
|
||||||
|
|
||||||
|
out = F.adaptive_avg_pool2d(out, 1)
|
||||||
|
out = out.view(out.shape[0], -1)
|
||||||
|
|
||||||
|
yaw = self.fc_roll(out)
|
||||||
|
pitch = self.fc_pitch(out)
|
||||||
|
roll = self.fc_yaw(out)
|
||||||
|
t = self.fc_t(out)
|
||||||
|
exp = self.fc_exp(out)
|
||||||
|
|
||||||
|
return {'yaw': yaw, 'pitch': pitch, 'roll': roll, 't': t, 'exp': exp}
|
||||||
|
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
from scipy.spatial import ConvexHull
|
||||||
|
import torch
|
||||||
|
import torch.nn.functional as F
|
||||||
|
import numpy as np
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
def normalize_kp(kp_source, kp_driving, kp_driving_initial, adapt_movement_scale=False,
|
||||||
|
use_relative_movement=False, use_relative_jacobian=False):
|
||||||
|
if adapt_movement_scale:
|
||||||
|
source_area = ConvexHull(kp_source['value'][0].data.cpu().numpy()).volume
|
||||||
|
driving_area = ConvexHull(kp_driving_initial['value'][0].data.cpu().numpy()).volume
|
||||||
|
adapt_movement_scale = np.sqrt(source_area) / np.sqrt(driving_area)
|
||||||
|
else:
|
||||||
|
adapt_movement_scale = 1
|
||||||
|
|
||||||
|
kp_new = {k: v for k, v in kp_driving.items()}
|
||||||
|
|
||||||
|
if use_relative_movement:
|
||||||
|
kp_value_diff = (kp_driving['value'] - kp_driving_initial['value'])
|
||||||
|
kp_value_diff *= adapt_movement_scale
|
||||||
|
kp_new['value'] = kp_value_diff + kp_source['value']
|
||||||
|
|
||||||
|
if use_relative_jacobian:
|
||||||
|
jacobian_diff = torch.matmul(kp_driving['jacobian'], torch.inverse(kp_driving_initial['jacobian']))
|
||||||
|
kp_new['jacobian'] = torch.matmul(jacobian_diff, kp_source['jacobian'])
|
||||||
|
|
||||||
|
return kp_new
|
||||||
|
|
||||||
|
def headpose_pred_to_degree(pred):
|
||||||
|
device = pred.device
|
||||||
|
idx_tensor = [idx for idx in range(66)]
|
||||||
|
idx_tensor = torch.FloatTensor(idx_tensor).type_as(pred).to(device)
|
||||||
|
pred = F.softmax(pred)
|
||||||
|
degree = torch.sum(pred*idx_tensor, 1) * 3 - 99
|
||||||
|
return degree
|
||||||
|
|
||||||
|
def get_rotation_matrix(yaw, pitch, roll):
|
||||||
|
yaw = yaw / 180 * 3.14
|
||||||
|
pitch = pitch / 180 * 3.14
|
||||||
|
roll = roll / 180 * 3.14
|
||||||
|
|
||||||
|
roll = roll.unsqueeze(1)
|
||||||
|
pitch = pitch.unsqueeze(1)
|
||||||
|
yaw = yaw.unsqueeze(1)
|
||||||
|
|
||||||
|
pitch_mat = torch.cat([torch.ones_like(pitch), torch.zeros_like(pitch), torch.zeros_like(pitch),
|
||||||
|
torch.zeros_like(pitch), torch.cos(pitch), -torch.sin(pitch),
|
||||||
|
torch.zeros_like(pitch), torch.sin(pitch), torch.cos(pitch)], dim=1)
|
||||||
|
pitch_mat = pitch_mat.view(pitch_mat.shape[0], 3, 3)
|
||||||
|
|
||||||
|
yaw_mat = torch.cat([torch.cos(yaw), torch.zeros_like(yaw), torch.sin(yaw),
|
||||||
|
torch.zeros_like(yaw), torch.ones_like(yaw), torch.zeros_like(yaw),
|
||||||
|
-torch.sin(yaw), torch.zeros_like(yaw), torch.cos(yaw)], dim=1)
|
||||||
|
yaw_mat = yaw_mat.view(yaw_mat.shape[0], 3, 3)
|
||||||
|
|
||||||
|
roll_mat = torch.cat([torch.cos(roll), -torch.sin(roll), torch.zeros_like(roll),
|
||||||
|
torch.sin(roll), torch.cos(roll), torch.zeros_like(roll),
|
||||||
|
torch.zeros_like(roll), torch.zeros_like(roll), torch.ones_like(roll)], dim=1)
|
||||||
|
roll_mat = roll_mat.view(roll_mat.shape[0], 3, 3)
|
||||||
|
|
||||||
|
rot_mat = torch.einsum('bij,bjk,bkm->bim', pitch_mat, yaw_mat, roll_mat)
|
||||||
|
|
||||||
|
return rot_mat
|
||||||
|
|
||||||
|
def keypoint_transformation(kp_canonical, he, wo_exp=False):
|
||||||
|
kp = kp_canonical['value'] # (bs, k, 3)
|
||||||
|
yaw, pitch, roll= he['yaw'], he['pitch'], he['roll']
|
||||||
|
yaw = headpose_pred_to_degree(yaw)
|
||||||
|
pitch = headpose_pred_to_degree(pitch)
|
||||||
|
roll = headpose_pred_to_degree(roll)
|
||||||
|
|
||||||
|
if 'yaw_in' in he:
|
||||||
|
yaw = he['yaw_in']
|
||||||
|
if 'pitch_in' in he:
|
||||||
|
pitch = he['pitch_in']
|
||||||
|
if 'roll_in' in he:
|
||||||
|
roll = he['roll_in']
|
||||||
|
|
||||||
|
rot_mat = get_rotation_matrix(yaw, pitch, roll) # (bs, 3, 3)
|
||||||
|
|
||||||
|
t, exp = he['t'], he['exp']
|
||||||
|
if wo_exp:
|
||||||
|
exp = exp*0
|
||||||
|
|
||||||
|
# keypoint rotation
|
||||||
|
kp_rotated = torch.einsum('bmp,bkp->bkm', rot_mat, kp)
|
||||||
|
|
||||||
|
# keypoint translation
|
||||||
|
t[:, 0] = t[:, 0]*0
|
||||||
|
t[:, 2] = t[:, 2]*0
|
||||||
|
t = t.unsqueeze(1).repeat(1, kp.shape[1], 1)
|
||||||
|
kp_t = kp_rotated + t
|
||||||
|
|
||||||
|
# add expression deviation
|
||||||
|
exp = exp.view(exp.shape[0], -1, 3)
|
||||||
|
kp_transformed = kp_t + exp
|
||||||
|
|
||||||
|
return {'value': kp_transformed}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def make_animation(source_image, source_semantics, target_semantics,
|
||||||
|
generator, kp_detector, he_estimator, mapping,
|
||||||
|
yaw_c_seq=None, pitch_c_seq=None, roll_c_seq=None,
|
||||||
|
use_exp=True, use_half=False):
|
||||||
|
with torch.no_grad():
|
||||||
|
predictions = []
|
||||||
|
|
||||||
|
kp_canonical = kp_detector(source_image)
|
||||||
|
he_source = mapping(source_semantics)
|
||||||
|
kp_source = keypoint_transformation(kp_canonical, he_source)
|
||||||
|
|
||||||
|
for frame_idx in tqdm(range(target_semantics.shape[1]), 'Face Renderer:'):
|
||||||
|
# still check the dimension
|
||||||
|
# print(target_semantics.shape, source_semantics.shape)
|
||||||
|
target_semantics_frame = target_semantics[:, frame_idx]
|
||||||
|
he_driving = mapping(target_semantics_frame)
|
||||||
|
if yaw_c_seq is not None:
|
||||||
|
he_driving['yaw_in'] = yaw_c_seq[:, frame_idx]
|
||||||
|
if pitch_c_seq is not None:
|
||||||
|
he_driving['pitch_in'] = pitch_c_seq[:, frame_idx]
|
||||||
|
if roll_c_seq is not None:
|
||||||
|
he_driving['roll_in'] = roll_c_seq[:, frame_idx]
|
||||||
|
|
||||||
|
kp_driving = keypoint_transformation(kp_canonical, he_driving)
|
||||||
|
|
||||||
|
kp_norm = kp_driving
|
||||||
|
out = generator(source_image, kp_source=kp_source, kp_driving=kp_norm)
|
||||||
|
'''
|
||||||
|
source_image_new = out['prediction'].squeeze(1)
|
||||||
|
kp_canonical_new = kp_detector(source_image_new)
|
||||||
|
he_source_new = he_estimator(source_image_new)
|
||||||
|
kp_source_new = keypoint_transformation(kp_canonical_new, he_source_new, wo_exp=True)
|
||||||
|
kp_driving_new = keypoint_transformation(kp_canonical_new, he_driving, wo_exp=True)
|
||||||
|
out = generator(source_image_new, kp_source=kp_source_new, kp_driving=kp_driving_new)
|
||||||
|
'''
|
||||||
|
predictions.append(out['prediction'])
|
||||||
|
predictions_ts = torch.stack(predictions, dim=1)
|
||||||
|
return predictions_ts
|
||||||
|
|
||||||
|
class AnimateModel(torch.nn.Module):
|
||||||
|
"""
|
||||||
|
Merge all generator related updates into single model for better multi-gpu usage
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, generator, kp_extractor, mapping):
|
||||||
|
super(AnimateModel, self).__init__()
|
||||||
|
self.kp_extractor = kp_extractor
|
||||||
|
self.generator = generator
|
||||||
|
self.mapping = mapping
|
||||||
|
|
||||||
|
self.kp_extractor.eval()
|
||||||
|
self.generator.eval()
|
||||||
|
self.mapping.eval()
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
|
||||||
|
source_image = x['source_image']
|
||||||
|
source_semantics = x['source_semantics']
|
||||||
|
target_semantics = x['target_semantics']
|
||||||
|
yaw_c_seq = x['yaw_c_seq']
|
||||||
|
pitch_c_seq = x['pitch_c_seq']
|
||||||
|
roll_c_seq = x['roll_c_seq']
|
||||||
|
|
||||||
|
predictions_video = make_animation(source_image, source_semantics, target_semantics,
|
||||||
|
self.generator, self.kp_extractor,
|
||||||
|
self.mapping, use_exp = True,
|
||||||
|
yaw_c_seq=yaw_c_seq, pitch_c_seq=pitch_c_seq, roll_c_seq=roll_c_seq)
|
||||||
|
|
||||||
|
return predictions_video
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import numpy as np
|
||||||
|
|
||||||
|
import torch
|
||||||
|
import torch.nn as nn
|
||||||
|
import torch.nn.functional as F
|
||||||
|
|
||||||
|
|
||||||
|
class MappingNet(nn.Module):
|
||||||
|
def __init__(self, coeff_nc, descriptor_nc, layer, num_kp, num_bins):
|
||||||
|
super( MappingNet, self).__init__()
|
||||||
|
|
||||||
|
self.layer = layer
|
||||||
|
nonlinearity = nn.LeakyReLU(0.1)
|
||||||
|
|
||||||
|
self.first = nn.Sequential(
|
||||||
|
torch.nn.Conv1d(coeff_nc, descriptor_nc, kernel_size=7, padding=0, bias=True))
|
||||||
|
|
||||||
|
for i in range(layer):
|
||||||
|
net = nn.Sequential(nonlinearity,
|
||||||
|
torch.nn.Conv1d(descriptor_nc, descriptor_nc, kernel_size=3, padding=0, dilation=3))
|
||||||
|
setattr(self, 'encoder' + str(i), net)
|
||||||
|
|
||||||
|
self.pooling = nn.AdaptiveAvgPool1d(1)
|
||||||
|
self.output_nc = descriptor_nc
|
||||||
|
|
||||||
|
self.fc_roll = nn.Linear(descriptor_nc, num_bins)
|
||||||
|
self.fc_pitch = nn.Linear(descriptor_nc, num_bins)
|
||||||
|
self.fc_yaw = nn.Linear(descriptor_nc, num_bins)
|
||||||
|
self.fc_t = nn.Linear(descriptor_nc, 3)
|
||||||
|
self.fc_exp = nn.Linear(descriptor_nc, 3*num_kp)
|
||||||
|
|
||||||
|
def forward(self, input_3dmm):
|
||||||
|
out = self.first(input_3dmm)
|
||||||
|
for i in range(self.layer):
|
||||||
|
model = getattr(self, 'encoder' + str(i))
|
||||||
|
out = model(out) + out[:,:,3:-3]
|
||||||
|
out = self.pooling(out)
|
||||||
|
out = out.view(out.shape[0], -1)
|
||||||
|
#print('out:', out.shape)
|
||||||
|
|
||||||
|
yaw = self.fc_yaw(out)
|
||||||
|
pitch = self.fc_pitch(out)
|
||||||
|
roll = self.fc_roll(out)
|
||||||
|
t = self.fc_t(out)
|
||||||
|
exp = self.fc_exp(out)
|
||||||
|
|
||||||
|
return {'yaw': yaw, 'pitch': pitch, 'roll': roll, 't': t, 'exp': exp}
|
||||||
@@ -0,0 +1,564 @@
|
|||||||
|
from torch import nn
|
||||||
|
|
||||||
|
import torch.nn.functional as F
|
||||||
|
import torch
|
||||||
|
|
||||||
|
from src.facerender.sync_batchnorm import SynchronizedBatchNorm2d as BatchNorm2d
|
||||||
|
from src.facerender.sync_batchnorm import SynchronizedBatchNorm3d as BatchNorm3d
|
||||||
|
|
||||||
|
import torch.nn.utils.spectral_norm as spectral_norm
|
||||||
|
|
||||||
|
|
||||||
|
def kp2gaussian(kp, spatial_size, kp_variance):
|
||||||
|
"""
|
||||||
|
Transform a keypoint into gaussian like representation
|
||||||
|
"""
|
||||||
|
mean = kp['value']
|
||||||
|
|
||||||
|
coordinate_grid = make_coordinate_grid(spatial_size, mean.type())
|
||||||
|
number_of_leading_dimensions = len(mean.shape) - 1
|
||||||
|
shape = (1,) * number_of_leading_dimensions + coordinate_grid.shape
|
||||||
|
coordinate_grid = coordinate_grid.view(*shape)
|
||||||
|
repeats = mean.shape[:number_of_leading_dimensions] + (1, 1, 1, 1)
|
||||||
|
coordinate_grid = coordinate_grid.repeat(*repeats)
|
||||||
|
|
||||||
|
# Preprocess kp shape
|
||||||
|
shape = mean.shape[:number_of_leading_dimensions] + (1, 1, 1, 3)
|
||||||
|
mean = mean.view(*shape)
|
||||||
|
|
||||||
|
mean_sub = (coordinate_grid - mean)
|
||||||
|
|
||||||
|
out = torch.exp(-0.5 * (mean_sub ** 2).sum(-1) / kp_variance)
|
||||||
|
|
||||||
|
return out
|
||||||
|
|
||||||
|
def make_coordinate_grid_2d(spatial_size, type):
|
||||||
|
"""
|
||||||
|
Create a meshgrid [-1,1] x [-1,1] of given spatial_size.
|
||||||
|
"""
|
||||||
|
h, w = spatial_size
|
||||||
|
x = torch.arange(w).type(type)
|
||||||
|
y = torch.arange(h).type(type)
|
||||||
|
|
||||||
|
x = (2 * (x / (w - 1)) - 1)
|
||||||
|
y = (2 * (y / (h - 1)) - 1)
|
||||||
|
|
||||||
|
yy = y.view(-1, 1).repeat(1, w)
|
||||||
|
xx = x.view(1, -1).repeat(h, 1)
|
||||||
|
|
||||||
|
meshed = torch.cat([xx.unsqueeze_(2), yy.unsqueeze_(2)], 2)
|
||||||
|
|
||||||
|
return meshed
|
||||||
|
|
||||||
|
|
||||||
|
def make_coordinate_grid(spatial_size, type):
|
||||||
|
d, h, w = spatial_size
|
||||||
|
x = torch.arange(w).type(type)
|
||||||
|
y = torch.arange(h).type(type)
|
||||||
|
z = torch.arange(d).type(type)
|
||||||
|
|
||||||
|
x = (2 * (x / (w - 1)) - 1)
|
||||||
|
y = (2 * (y / (h - 1)) - 1)
|
||||||
|
z = (2 * (z / (d - 1)) - 1)
|
||||||
|
|
||||||
|
yy = y.view(1, -1, 1).repeat(d, 1, w)
|
||||||
|
xx = x.view(1, 1, -1).repeat(d, h, 1)
|
||||||
|
zz = z.view(-1, 1, 1).repeat(1, h, w)
|
||||||
|
|
||||||
|
meshed = torch.cat([xx.unsqueeze_(3), yy.unsqueeze_(3), zz.unsqueeze_(3)], 3)
|
||||||
|
|
||||||
|
return meshed
|
||||||
|
|
||||||
|
|
||||||
|
class ResBottleneck(nn.Module):
|
||||||
|
def __init__(self, in_features, stride):
|
||||||
|
super(ResBottleneck, self).__init__()
|
||||||
|
self.conv1 = nn.Conv2d(in_channels=in_features, out_channels=in_features//4, kernel_size=1)
|
||||||
|
self.conv2 = nn.Conv2d(in_channels=in_features//4, out_channels=in_features//4, kernel_size=3, padding=1, stride=stride)
|
||||||
|
self.conv3 = nn.Conv2d(in_channels=in_features//4, out_channels=in_features, kernel_size=1)
|
||||||
|
self.norm1 = BatchNorm2d(in_features//4, affine=True)
|
||||||
|
self.norm2 = BatchNorm2d(in_features//4, affine=True)
|
||||||
|
self.norm3 = BatchNorm2d(in_features, affine=True)
|
||||||
|
|
||||||
|
self.stride = stride
|
||||||
|
if self.stride != 1:
|
||||||
|
self.skip = nn.Conv2d(in_channels=in_features, out_channels=in_features, kernel_size=1, stride=stride)
|
||||||
|
self.norm4 = BatchNorm2d(in_features, affine=True)
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
out = self.conv1(x)
|
||||||
|
out = self.norm1(out)
|
||||||
|
out = F.relu(out)
|
||||||
|
out = self.conv2(out)
|
||||||
|
out = self.norm2(out)
|
||||||
|
out = F.relu(out)
|
||||||
|
out = self.conv3(out)
|
||||||
|
out = self.norm3(out)
|
||||||
|
if self.stride != 1:
|
||||||
|
x = self.skip(x)
|
||||||
|
x = self.norm4(x)
|
||||||
|
out += x
|
||||||
|
out = F.relu(out)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
class ResBlock2d(nn.Module):
|
||||||
|
"""
|
||||||
|
Res block, preserve spatial resolution.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, in_features, kernel_size, padding):
|
||||||
|
super(ResBlock2d, self).__init__()
|
||||||
|
self.conv1 = nn.Conv2d(in_channels=in_features, out_channels=in_features, kernel_size=kernel_size,
|
||||||
|
padding=padding)
|
||||||
|
self.conv2 = nn.Conv2d(in_channels=in_features, out_channels=in_features, kernel_size=kernel_size,
|
||||||
|
padding=padding)
|
||||||
|
self.norm1 = BatchNorm2d(in_features, affine=True)
|
||||||
|
self.norm2 = BatchNorm2d(in_features, affine=True)
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
out = self.norm1(x)
|
||||||
|
out = F.relu(out)
|
||||||
|
out = self.conv1(out)
|
||||||
|
out = self.norm2(out)
|
||||||
|
out = F.relu(out)
|
||||||
|
out = self.conv2(out)
|
||||||
|
out += x
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
class ResBlock3d(nn.Module):
|
||||||
|
"""
|
||||||
|
Res block, preserve spatial resolution.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, in_features, kernel_size, padding):
|
||||||
|
super(ResBlock3d, self).__init__()
|
||||||
|
self.conv1 = nn.Conv3d(in_channels=in_features, out_channels=in_features, kernel_size=kernel_size,
|
||||||
|
padding=padding)
|
||||||
|
self.conv2 = nn.Conv3d(in_channels=in_features, out_channels=in_features, kernel_size=kernel_size,
|
||||||
|
padding=padding)
|
||||||
|
self.norm1 = BatchNorm3d(in_features, affine=True)
|
||||||
|
self.norm2 = BatchNorm3d(in_features, affine=True)
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
out = self.norm1(x)
|
||||||
|
out = F.relu(out)
|
||||||
|
out = self.conv1(out)
|
||||||
|
out = self.norm2(out)
|
||||||
|
out = F.relu(out)
|
||||||
|
out = self.conv2(out)
|
||||||
|
out += x
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
class UpBlock2d(nn.Module):
|
||||||
|
"""
|
||||||
|
Upsampling block for use in decoder.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, in_features, out_features, kernel_size=3, padding=1, groups=1):
|
||||||
|
super(UpBlock2d, self).__init__()
|
||||||
|
|
||||||
|
self.conv = nn.Conv2d(in_channels=in_features, out_channels=out_features, kernel_size=kernel_size,
|
||||||
|
padding=padding, groups=groups)
|
||||||
|
self.norm = BatchNorm2d(out_features, affine=True)
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
out = F.interpolate(x, scale_factor=2)
|
||||||
|
out = self.conv(out)
|
||||||
|
out = self.norm(out)
|
||||||
|
out = F.relu(out)
|
||||||
|
return out
|
||||||
|
|
||||||
|
class UpBlock3d(nn.Module):
|
||||||
|
"""
|
||||||
|
Upsampling block for use in decoder.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, in_features, out_features, kernel_size=3, padding=1, groups=1):
|
||||||
|
super(UpBlock3d, self).__init__()
|
||||||
|
|
||||||
|
self.conv = nn.Conv3d(in_channels=in_features, out_channels=out_features, kernel_size=kernel_size,
|
||||||
|
padding=padding, groups=groups)
|
||||||
|
self.norm = BatchNorm3d(out_features, affine=True)
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
# out = F.interpolate(x, scale_factor=(1, 2, 2), mode='trilinear')
|
||||||
|
out = F.interpolate(x, scale_factor=(1, 2, 2))
|
||||||
|
out = self.conv(out)
|
||||||
|
out = self.norm(out)
|
||||||
|
out = F.relu(out)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
class DownBlock2d(nn.Module):
|
||||||
|
"""
|
||||||
|
Downsampling block for use in encoder.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, in_features, out_features, kernel_size=3, padding=1, groups=1):
|
||||||
|
super(DownBlock2d, self).__init__()
|
||||||
|
self.conv = nn.Conv2d(in_channels=in_features, out_channels=out_features, kernel_size=kernel_size,
|
||||||
|
padding=padding, groups=groups)
|
||||||
|
self.norm = BatchNorm2d(out_features, affine=True)
|
||||||
|
self.pool = nn.AvgPool2d(kernel_size=(2, 2))
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
out = self.conv(x)
|
||||||
|
out = self.norm(out)
|
||||||
|
out = F.relu(out)
|
||||||
|
out = self.pool(out)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
class DownBlock3d(nn.Module):
|
||||||
|
"""
|
||||||
|
Downsampling block for use in encoder.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, in_features, out_features, kernel_size=3, padding=1, groups=1):
|
||||||
|
super(DownBlock3d, self).__init__()
|
||||||
|
'''
|
||||||
|
self.conv = nn.Conv3d(in_channels=in_features, out_channels=out_features, kernel_size=kernel_size,
|
||||||
|
padding=padding, groups=groups, stride=(1, 2, 2))
|
||||||
|
'''
|
||||||
|
self.conv = nn.Conv3d(in_channels=in_features, out_channels=out_features, kernel_size=kernel_size,
|
||||||
|
padding=padding, groups=groups)
|
||||||
|
self.norm = BatchNorm3d(out_features, affine=True)
|
||||||
|
self.pool = nn.AvgPool3d(kernel_size=(1, 2, 2))
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
out = self.conv(x)
|
||||||
|
out = self.norm(out)
|
||||||
|
out = F.relu(out)
|
||||||
|
out = self.pool(out)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
class SameBlock2d(nn.Module):
|
||||||
|
"""
|
||||||
|
Simple block, preserve spatial resolution.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, in_features, out_features, groups=1, kernel_size=3, padding=1, lrelu=False):
|
||||||
|
super(SameBlock2d, self).__init__()
|
||||||
|
self.conv = nn.Conv2d(in_channels=in_features, out_channels=out_features,
|
||||||
|
kernel_size=kernel_size, padding=padding, groups=groups)
|
||||||
|
self.norm = BatchNorm2d(out_features, affine=True)
|
||||||
|
if lrelu:
|
||||||
|
self.ac = nn.LeakyReLU()
|
||||||
|
else:
|
||||||
|
self.ac = nn.ReLU()
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
out = self.conv(x)
|
||||||
|
out = self.norm(out)
|
||||||
|
out = self.ac(out)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
class Encoder(nn.Module):
|
||||||
|
"""
|
||||||
|
Hourglass Encoder
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, block_expansion, in_features, num_blocks=3, max_features=256):
|
||||||
|
super(Encoder, self).__init__()
|
||||||
|
|
||||||
|
down_blocks = []
|
||||||
|
for i in range(num_blocks):
|
||||||
|
down_blocks.append(DownBlock3d(in_features if i == 0 else min(max_features, block_expansion * (2 ** i)),
|
||||||
|
min(max_features, block_expansion * (2 ** (i + 1))),
|
||||||
|
kernel_size=3, padding=1))
|
||||||
|
self.down_blocks = nn.ModuleList(down_blocks)
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
outs = [x]
|
||||||
|
for down_block in self.down_blocks:
|
||||||
|
outs.append(down_block(outs[-1]))
|
||||||
|
return outs
|
||||||
|
|
||||||
|
|
||||||
|
class Decoder(nn.Module):
|
||||||
|
"""
|
||||||
|
Hourglass Decoder
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, block_expansion, in_features, num_blocks=3, max_features=256):
|
||||||
|
super(Decoder, self).__init__()
|
||||||
|
|
||||||
|
up_blocks = []
|
||||||
|
|
||||||
|
for i in range(num_blocks)[::-1]:
|
||||||
|
in_filters = (1 if i == num_blocks - 1 else 2) * min(max_features, block_expansion * (2 ** (i + 1)))
|
||||||
|
out_filters = min(max_features, block_expansion * (2 ** i))
|
||||||
|
up_blocks.append(UpBlock3d(in_filters, out_filters, kernel_size=3, padding=1))
|
||||||
|
|
||||||
|
self.up_blocks = nn.ModuleList(up_blocks)
|
||||||
|
# self.out_filters = block_expansion
|
||||||
|
self.out_filters = block_expansion + in_features
|
||||||
|
|
||||||
|
self.conv = nn.Conv3d(in_channels=self.out_filters, out_channels=self.out_filters, kernel_size=3, padding=1)
|
||||||
|
self.norm = BatchNorm3d(self.out_filters, affine=True)
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
out = x.pop()
|
||||||
|
# for up_block in self.up_blocks[:-1]:
|
||||||
|
for up_block in self.up_blocks:
|
||||||
|
out = up_block(out)
|
||||||
|
skip = x.pop()
|
||||||
|
out = torch.cat([out, skip], dim=1)
|
||||||
|
# out = self.up_blocks[-1](out)
|
||||||
|
out = self.conv(out)
|
||||||
|
out = self.norm(out)
|
||||||
|
out = F.relu(out)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
class Hourglass(nn.Module):
|
||||||
|
"""
|
||||||
|
Hourglass architecture.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, block_expansion, in_features, num_blocks=3, max_features=256):
|
||||||
|
super(Hourglass, self).__init__()
|
||||||
|
self.encoder = Encoder(block_expansion, in_features, num_blocks, max_features)
|
||||||
|
self.decoder = Decoder(block_expansion, in_features, num_blocks, max_features)
|
||||||
|
self.out_filters = self.decoder.out_filters
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
return self.decoder(self.encoder(x))
|
||||||
|
|
||||||
|
|
||||||
|
class KPHourglass(nn.Module):
|
||||||
|
"""
|
||||||
|
Hourglass architecture.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, block_expansion, in_features, reshape_features, reshape_depth, num_blocks=3, max_features=256):
|
||||||
|
super(KPHourglass, self).__init__()
|
||||||
|
|
||||||
|
self.down_blocks = nn.Sequential()
|
||||||
|
for i in range(num_blocks):
|
||||||
|
self.down_blocks.add_module('down'+ str(i), DownBlock2d(in_features if i == 0 else min(max_features, block_expansion * (2 ** i)),
|
||||||
|
min(max_features, block_expansion * (2 ** (i + 1))),
|
||||||
|
kernel_size=3, padding=1))
|
||||||
|
|
||||||
|
in_filters = min(max_features, block_expansion * (2 ** num_blocks))
|
||||||
|
self.conv = nn.Conv2d(in_channels=in_filters, out_channels=reshape_features, kernel_size=1)
|
||||||
|
|
||||||
|
self.up_blocks = nn.Sequential()
|
||||||
|
for i in range(num_blocks):
|
||||||
|
in_filters = min(max_features, block_expansion * (2 ** (num_blocks - i)))
|
||||||
|
out_filters = min(max_features, block_expansion * (2 ** (num_blocks - i - 1)))
|
||||||
|
self.up_blocks.add_module('up'+ str(i), UpBlock3d(in_filters, out_filters, kernel_size=3, padding=1))
|
||||||
|
|
||||||
|
self.reshape_depth = reshape_depth
|
||||||
|
self.out_filters = out_filters
|
||||||
|
|
||||||
|
def forward(self, x):
|
||||||
|
out = self.down_blocks(x)
|
||||||
|
out = self.conv(out)
|
||||||
|
bs, c, h, w = out.shape
|
||||||
|
out = out.view(bs, c//self.reshape_depth, self.reshape_depth, h, w)
|
||||||
|
out = self.up_blocks(out)
|
||||||
|
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class AntiAliasInterpolation2d(nn.Module):
|
||||||
|
"""
|
||||||
|
Band-limited downsampling, for better preservation of the input signal.
|
||||||
|
"""
|
||||||
|
def __init__(self, channels, scale):
|
||||||
|
super(AntiAliasInterpolation2d, self).__init__()
|
||||||
|
sigma = (1 / scale - 1) / 2
|
||||||
|
kernel_size = 2 * round(sigma * 4) + 1
|
||||||
|
self.ka = kernel_size // 2
|
||||||
|
self.kb = self.ka - 1 if kernel_size % 2 == 0 else self.ka
|
||||||
|
|
||||||
|
kernel_size = [kernel_size, kernel_size]
|
||||||
|
sigma = [sigma, sigma]
|
||||||
|
# The gaussian kernel is the product of the
|
||||||
|
# gaussian function of each dimension.
|
||||||
|
kernel = 1
|
||||||
|
meshgrids = torch.meshgrid(
|
||||||
|
[
|
||||||
|
torch.arange(size, dtype=torch.float32)
|
||||||
|
for size in kernel_size
|
||||||
|
]
|
||||||
|
)
|
||||||
|
for size, std, mgrid in zip(kernel_size, sigma, meshgrids):
|
||||||
|
mean = (size - 1) / 2
|
||||||
|
kernel *= torch.exp(-(mgrid - mean) ** 2 / (2 * std ** 2))
|
||||||
|
|
||||||
|
# Make sure sum of values in gaussian kernel equals 1.
|
||||||
|
kernel = kernel / torch.sum(kernel)
|
||||||
|
# Reshape to depthwise convolutional weight
|
||||||
|
kernel = kernel.view(1, 1, *kernel.size())
|
||||||
|
kernel = kernel.repeat(channels, *[1] * (kernel.dim() - 1))
|
||||||
|
|
||||||
|
self.register_buffer('weight', kernel)
|
||||||
|
self.groups = channels
|
||||||
|
self.scale = scale
|
||||||
|
inv_scale = 1 / scale
|
||||||
|
self.int_inv_scale = int(inv_scale)
|
||||||
|
|
||||||
|
def forward(self, input):
|
||||||
|
if self.scale == 1.0:
|
||||||
|
return input
|
||||||
|
|
||||||
|
out = F.pad(input, (self.ka, self.kb, self.ka, self.kb))
|
||||||
|
out = F.conv2d(out, weight=self.weight, groups=self.groups)
|
||||||
|
out = out[:, :, ::self.int_inv_scale, ::self.int_inv_scale]
|
||||||
|
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
class SPADE(nn.Module):
|
||||||
|
def __init__(self, norm_nc, label_nc):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.param_free_norm = nn.InstanceNorm2d(norm_nc, affine=False)
|
||||||
|
nhidden = 128
|
||||||
|
|
||||||
|
self.mlp_shared = nn.Sequential(
|
||||||
|
nn.Conv2d(label_nc, nhidden, kernel_size=3, padding=1),
|
||||||
|
nn.ReLU())
|
||||||
|
self.mlp_gamma = nn.Conv2d(nhidden, norm_nc, kernel_size=3, padding=1)
|
||||||
|
self.mlp_beta = nn.Conv2d(nhidden, norm_nc, kernel_size=3, padding=1)
|
||||||
|
|
||||||
|
def forward(self, x, segmap):
|
||||||
|
normalized = self.param_free_norm(x)
|
||||||
|
segmap = F.interpolate(segmap, size=x.size()[2:], mode='nearest')
|
||||||
|
actv = self.mlp_shared(segmap)
|
||||||
|
gamma = self.mlp_gamma(actv)
|
||||||
|
beta = self.mlp_beta(actv)
|
||||||
|
out = normalized * (1 + gamma) + beta
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
class SPADEResnetBlock(nn.Module):
|
||||||
|
def __init__(self, fin, fout, norm_G, label_nc, use_se=False, dilation=1):
|
||||||
|
super().__init__()
|
||||||
|
# Attributes
|
||||||
|
self.learned_shortcut = (fin != fout)
|
||||||
|
fmiddle = min(fin, fout)
|
||||||
|
self.use_se = use_se
|
||||||
|
# create conv layers
|
||||||
|
self.conv_0 = nn.Conv2d(fin, fmiddle, kernel_size=3, padding=dilation, dilation=dilation)
|
||||||
|
self.conv_1 = nn.Conv2d(fmiddle, fout, kernel_size=3, padding=dilation, dilation=dilation)
|
||||||
|
if self.learned_shortcut:
|
||||||
|
self.conv_s = nn.Conv2d(fin, fout, kernel_size=1, bias=False)
|
||||||
|
# apply spectral norm if specified
|
||||||
|
if 'spectral' in norm_G:
|
||||||
|
self.conv_0 = spectral_norm(self.conv_0)
|
||||||
|
self.conv_1 = spectral_norm(self.conv_1)
|
||||||
|
if self.learned_shortcut:
|
||||||
|
self.conv_s = spectral_norm(self.conv_s)
|
||||||
|
# define normalization layers
|
||||||
|
self.norm_0 = SPADE(fin, label_nc)
|
||||||
|
self.norm_1 = SPADE(fmiddle, label_nc)
|
||||||
|
if self.learned_shortcut:
|
||||||
|
self.norm_s = SPADE(fin, label_nc)
|
||||||
|
|
||||||
|
def forward(self, x, seg1):
|
||||||
|
x_s = self.shortcut(x, seg1)
|
||||||
|
dx = self.conv_0(self.actvn(self.norm_0(x, seg1)))
|
||||||
|
dx = self.conv_1(self.actvn(self.norm_1(dx, seg1)))
|
||||||
|
out = x_s + dx
|
||||||
|
return out
|
||||||
|
|
||||||
|
def shortcut(self, x, seg1):
|
||||||
|
if self.learned_shortcut:
|
||||||
|
x_s = self.conv_s(self.norm_s(x, seg1))
|
||||||
|
else:
|
||||||
|
x_s = x
|
||||||
|
return x_s
|
||||||
|
|
||||||
|
def actvn(self, x):
|
||||||
|
return F.leaky_relu(x, 2e-1)
|
||||||
|
|
||||||
|
class audio2image(nn.Module):
|
||||||
|
def __init__(self, generator, kp_extractor, he_estimator_video, he_estimator_audio, train_params):
|
||||||
|
super().__init__()
|
||||||
|
# Attributes
|
||||||
|
self.generator = generator
|
||||||
|
self.kp_extractor = kp_extractor
|
||||||
|
self.he_estimator_video = he_estimator_video
|
||||||
|
self.he_estimator_audio = he_estimator_audio
|
||||||
|
self.train_params = train_params
|
||||||
|
|
||||||
|
def headpose_pred_to_degree(self, pred):
|
||||||
|
device = pred.device
|
||||||
|
idx_tensor = [idx for idx in range(66)]
|
||||||
|
idx_tensor = torch.FloatTensor(idx_tensor).to(device)
|
||||||
|
pred = F.softmax(pred)
|
||||||
|
degree = torch.sum(pred*idx_tensor, 1) * 3 - 99
|
||||||
|
|
||||||
|
return degree
|
||||||
|
|
||||||
|
def get_rotation_matrix(self, yaw, pitch, roll):
|
||||||
|
yaw = yaw / 180 * 3.14
|
||||||
|
pitch = pitch / 180 * 3.14
|
||||||
|
roll = roll / 180 * 3.14
|
||||||
|
|
||||||
|
roll = roll.unsqueeze(1)
|
||||||
|
pitch = pitch.unsqueeze(1)
|
||||||
|
yaw = yaw.unsqueeze(1)
|
||||||
|
|
||||||
|
roll_mat = torch.cat([torch.ones_like(roll), torch.zeros_like(roll), torch.zeros_like(roll),
|
||||||
|
torch.zeros_like(roll), torch.cos(roll), -torch.sin(roll),
|
||||||
|
torch.zeros_like(roll), torch.sin(roll), torch.cos(roll)], dim=1)
|
||||||
|
roll_mat = roll_mat.view(roll_mat.shape[0], 3, 3)
|
||||||
|
|
||||||
|
pitch_mat = torch.cat([torch.cos(pitch), torch.zeros_like(pitch), torch.sin(pitch),
|
||||||
|
torch.zeros_like(pitch), torch.ones_like(pitch), torch.zeros_like(pitch),
|
||||||
|
-torch.sin(pitch), torch.zeros_like(pitch), torch.cos(pitch)], dim=1)
|
||||||
|
pitch_mat = pitch_mat.view(pitch_mat.shape[0], 3, 3)
|
||||||
|
|
||||||
|
yaw_mat = torch.cat([torch.cos(yaw), -torch.sin(yaw), torch.zeros_like(yaw),
|
||||||
|
torch.sin(yaw), torch.cos(yaw), torch.zeros_like(yaw),
|
||||||
|
torch.zeros_like(yaw), torch.zeros_like(yaw), torch.ones_like(yaw)], dim=1)
|
||||||
|
yaw_mat = yaw_mat.view(yaw_mat.shape[0], 3, 3)
|
||||||
|
|
||||||
|
rot_mat = torch.einsum('bij,bjk,bkm->bim', roll_mat, pitch_mat, yaw_mat)
|
||||||
|
|
||||||
|
return rot_mat
|
||||||
|
|
||||||
|
def keypoint_transformation(self, kp_canonical, he):
|
||||||
|
kp = kp_canonical['value'] # (bs, k, 3)
|
||||||
|
yaw, pitch, roll = he['yaw'], he['pitch'], he['roll']
|
||||||
|
t, exp = he['t'], he['exp']
|
||||||
|
|
||||||
|
yaw = self.headpose_pred_to_degree(yaw)
|
||||||
|
pitch = self.headpose_pred_to_degree(pitch)
|
||||||
|
roll = self.headpose_pred_to_degree(roll)
|
||||||
|
|
||||||
|
rot_mat = self.get_rotation_matrix(yaw, pitch, roll) # (bs, 3, 3)
|
||||||
|
|
||||||
|
# keypoint rotation
|
||||||
|
kp_rotated = torch.einsum('bmp,bkp->bkm', rot_mat, kp)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# keypoint translation
|
||||||
|
t = t.unsqueeze_(1).repeat(1, kp.shape[1], 1)
|
||||||
|
kp_t = kp_rotated + t
|
||||||
|
|
||||||
|
# add expression deviation
|
||||||
|
exp = exp.view(exp.shape[0], -1, 3)
|
||||||
|
kp_transformed = kp_t + exp
|
||||||
|
|
||||||
|
return {'value': kp_transformed}
|
||||||
|
|
||||||
|
def forward(self, source_image, target_audio):
|
||||||
|
pose_source = self.he_estimator_video(source_image)
|
||||||
|
pose_generated = self.he_estimator_audio(target_audio)
|
||||||
|
kp_canonical = self.kp_extractor(source_image)
|
||||||
|
kp_source = self.keypoint_transformation(kp_canonical, pose_source)
|
||||||
|
kp_transformed_generated = self.keypoint_transformation(kp_canonical, pose_generated)
|
||||||
|
generated = self.generator(source_image, kp_source=kp_source, kp_driving=kp_transformed_generated)
|
||||||
|
return generated
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# File : __init__.py
|
||||||
|
# Author : Jiayuan Mao
|
||||||
|
# Email : maojiayuan@gmail.com
|
||||||
|
# Date : 27/01/2018
|
||||||
|
#
|
||||||
|
# This file is part of Synchronized-BatchNorm-PyTorch.
|
||||||
|
# https://github.com/vacancy/Synchronized-BatchNorm-PyTorch
|
||||||
|
# Distributed under MIT License.
|
||||||
|
|
||||||
|
from .batchnorm import SynchronizedBatchNorm1d, SynchronizedBatchNorm2d, SynchronizedBatchNorm3d
|
||||||
|
from .replicate import DataParallelWithCallback, patch_replication_callback
|
||||||
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,315 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# File : batchnorm.py
|
||||||
|
# Author : Jiayuan Mao
|
||||||
|
# Email : maojiayuan@gmail.com
|
||||||
|
# Date : 27/01/2018
|
||||||
|
#
|
||||||
|
# This file is part of Synchronized-BatchNorm-PyTorch.
|
||||||
|
# https://github.com/vacancy/Synchronized-BatchNorm-PyTorch
|
||||||
|
# Distributed under MIT License.
|
||||||
|
|
||||||
|
import collections
|
||||||
|
|
||||||
|
import torch
|
||||||
|
import torch.nn.functional as F
|
||||||
|
|
||||||
|
from torch.nn.modules.batchnorm import _BatchNorm
|
||||||
|
from torch.nn.parallel._functions import ReduceAddCoalesced, Broadcast
|
||||||
|
|
||||||
|
from .comm import SyncMaster
|
||||||
|
|
||||||
|
__all__ = ['SynchronizedBatchNorm1d', 'SynchronizedBatchNorm2d', 'SynchronizedBatchNorm3d']
|
||||||
|
|
||||||
|
|
||||||
|
def _sum_ft(tensor):
|
||||||
|
"""sum over the first and last dimention"""
|
||||||
|
return tensor.sum(dim=0).sum(dim=-1)
|
||||||
|
|
||||||
|
|
||||||
|
def _unsqueeze_ft(tensor):
|
||||||
|
"""add new dementions at the front and the tail"""
|
||||||
|
return tensor.unsqueeze(0).unsqueeze(-1)
|
||||||
|
|
||||||
|
|
||||||
|
_ChildMessage = collections.namedtuple('_ChildMessage', ['sum', 'ssum', 'sum_size'])
|
||||||
|
_MasterMessage = collections.namedtuple('_MasterMessage', ['sum', 'inv_std'])
|
||||||
|
|
||||||
|
|
||||||
|
class _SynchronizedBatchNorm(_BatchNorm):
|
||||||
|
def __init__(self, num_features, eps=1e-5, momentum=0.1, affine=True):
|
||||||
|
super(_SynchronizedBatchNorm, self).__init__(num_features, eps=eps, momentum=momentum, affine=affine)
|
||||||
|
|
||||||
|
self._sync_master = SyncMaster(self._data_parallel_master)
|
||||||
|
|
||||||
|
self._is_parallel = False
|
||||||
|
self._parallel_id = None
|
||||||
|
self._slave_pipe = None
|
||||||
|
|
||||||
|
def forward(self, input):
|
||||||
|
# If it is not parallel computation or is in evaluation mode, use PyTorch's implementation.
|
||||||
|
if not (self._is_parallel and self.training):
|
||||||
|
return F.batch_norm(
|
||||||
|
input, self.running_mean, self.running_var, self.weight, self.bias,
|
||||||
|
self.training, self.momentum, self.eps)
|
||||||
|
|
||||||
|
# Resize the input to (B, C, -1).
|
||||||
|
input_shape = input.size()
|
||||||
|
input = input.view(input.size(0), self.num_features, -1)
|
||||||
|
|
||||||
|
# Compute the sum and square-sum.
|
||||||
|
sum_size = input.size(0) * input.size(2)
|
||||||
|
input_sum = _sum_ft(input)
|
||||||
|
input_ssum = _sum_ft(input ** 2)
|
||||||
|
|
||||||
|
# Reduce-and-broadcast the statistics.
|
||||||
|
if self._parallel_id == 0:
|
||||||
|
mean, inv_std = self._sync_master.run_master(_ChildMessage(input_sum, input_ssum, sum_size))
|
||||||
|
else:
|
||||||
|
mean, inv_std = self._slave_pipe.run_slave(_ChildMessage(input_sum, input_ssum, sum_size))
|
||||||
|
|
||||||
|
# Compute the output.
|
||||||
|
if self.affine:
|
||||||
|
# MJY:: Fuse the multiplication for speed.
|
||||||
|
output = (input - _unsqueeze_ft(mean)) * _unsqueeze_ft(inv_std * self.weight) + _unsqueeze_ft(self.bias)
|
||||||
|
else:
|
||||||
|
output = (input - _unsqueeze_ft(mean)) * _unsqueeze_ft(inv_std)
|
||||||
|
|
||||||
|
# Reshape it.
|
||||||
|
return output.view(input_shape)
|
||||||
|
|
||||||
|
def __data_parallel_replicate__(self, ctx, copy_id):
|
||||||
|
self._is_parallel = True
|
||||||
|
self._parallel_id = copy_id
|
||||||
|
|
||||||
|
# parallel_id == 0 means master device.
|
||||||
|
if self._parallel_id == 0:
|
||||||
|
ctx.sync_master = self._sync_master
|
||||||
|
else:
|
||||||
|
self._slave_pipe = ctx.sync_master.register_slave(copy_id)
|
||||||
|
|
||||||
|
def _data_parallel_master(self, intermediates):
|
||||||
|
"""Reduce the sum and square-sum, compute the statistics, and broadcast it."""
|
||||||
|
|
||||||
|
# Always using same "device order" makes the ReduceAdd operation faster.
|
||||||
|
# Thanks to:: Tete Xiao (http://tetexiao.com/)
|
||||||
|
intermediates = sorted(intermediates, key=lambda i: i[1].sum.get_device())
|
||||||
|
|
||||||
|
to_reduce = [i[1][:2] for i in intermediates]
|
||||||
|
to_reduce = [j for i in to_reduce for j in i] # flatten
|
||||||
|
target_gpus = [i[1].sum.get_device() for i in intermediates]
|
||||||
|
|
||||||
|
sum_size = sum([i[1].sum_size for i in intermediates])
|
||||||
|
sum_, ssum = ReduceAddCoalesced.apply(target_gpus[0], 2, *to_reduce)
|
||||||
|
mean, inv_std = self._compute_mean_std(sum_, ssum, sum_size)
|
||||||
|
|
||||||
|
broadcasted = Broadcast.apply(target_gpus, mean, inv_std)
|
||||||
|
|
||||||
|
outputs = []
|
||||||
|
for i, rec in enumerate(intermediates):
|
||||||
|
outputs.append((rec[0], _MasterMessage(*broadcasted[i*2:i*2+2])))
|
||||||
|
|
||||||
|
return outputs
|
||||||
|
|
||||||
|
def _compute_mean_std(self, sum_, ssum, size):
|
||||||
|
"""Compute the mean and standard-deviation with sum and square-sum. This method
|
||||||
|
also maintains the moving average on the master device."""
|
||||||
|
assert size > 1, 'BatchNorm computes unbiased standard-deviation, which requires size > 1.'
|
||||||
|
mean = sum_ / size
|
||||||
|
sumvar = ssum - sum_ * mean
|
||||||
|
unbias_var = sumvar / (size - 1)
|
||||||
|
bias_var = sumvar / size
|
||||||
|
|
||||||
|
self.running_mean = (1 - self.momentum) * self.running_mean + self.momentum * mean.data
|
||||||
|
self.running_var = (1 - self.momentum) * self.running_var + self.momentum * unbias_var.data
|
||||||
|
|
||||||
|
return mean, bias_var.clamp(self.eps) ** -0.5
|
||||||
|
|
||||||
|
|
||||||
|
class SynchronizedBatchNorm1d(_SynchronizedBatchNorm):
|
||||||
|
r"""Applies Synchronized Batch Normalization over a 2d or 3d input that is seen as a
|
||||||
|
mini-batch.
|
||||||
|
|
||||||
|
.. math::
|
||||||
|
|
||||||
|
y = \frac{x - mean[x]}{ \sqrt{Var[x] + \epsilon}} * gamma + beta
|
||||||
|
|
||||||
|
This module differs from the built-in PyTorch BatchNorm1d as the mean and
|
||||||
|
standard-deviation are reduced across all devices during training.
|
||||||
|
|
||||||
|
For example, when one uses `nn.DataParallel` to wrap the network during
|
||||||
|
training, PyTorch's implementation normalize the tensor on each device using
|
||||||
|
the statistics only on that device, which accelerated the computation and
|
||||||
|
is also easy to implement, but the statistics might be inaccurate.
|
||||||
|
Instead, in this synchronized version, the statistics will be computed
|
||||||
|
over all training samples distributed on multiple devices.
|
||||||
|
|
||||||
|
Note that, for one-GPU or CPU-only case, this module behaves exactly same
|
||||||
|
as the built-in PyTorch implementation.
|
||||||
|
|
||||||
|
The mean and standard-deviation are calculated per-dimension over
|
||||||
|
the mini-batches and gamma and beta are learnable parameter vectors
|
||||||
|
of size C (where C is the input size).
|
||||||
|
|
||||||
|
During training, this layer keeps a running estimate of its computed mean
|
||||||
|
and variance. The running sum is kept with a default momentum of 0.1.
|
||||||
|
|
||||||
|
During evaluation, this running mean/variance is used for normalization.
|
||||||
|
|
||||||
|
Because the BatchNorm is done over the `C` dimension, computing statistics
|
||||||
|
on `(N, L)` slices, it's common terminology to call this Temporal BatchNorm
|
||||||
|
|
||||||
|
Args:
|
||||||
|
num_features: num_features from an expected input of size
|
||||||
|
`batch_size x num_features [x width]`
|
||||||
|
eps: a value added to the denominator for numerical stability.
|
||||||
|
Default: 1e-5
|
||||||
|
momentum: the value used for the running_mean and running_var
|
||||||
|
computation. Default: 0.1
|
||||||
|
affine: a boolean value that when set to ``True``, gives the layer learnable
|
||||||
|
affine parameters. Default: ``True``
|
||||||
|
|
||||||
|
Shape:
|
||||||
|
- Input: :math:`(N, C)` or :math:`(N, C, L)`
|
||||||
|
- Output: :math:`(N, C)` or :math:`(N, C, L)` (same shape as input)
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
>>> # With Learnable Parameters
|
||||||
|
>>> m = SynchronizedBatchNorm1d(100)
|
||||||
|
>>> # Without Learnable Parameters
|
||||||
|
>>> m = SynchronizedBatchNorm1d(100, affine=False)
|
||||||
|
>>> input = torch.autograd.Variable(torch.randn(20, 100))
|
||||||
|
>>> output = m(input)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _check_input_dim(self, input):
|
||||||
|
if input.dim() != 2 and input.dim() != 3:
|
||||||
|
raise ValueError('expected 2D or 3D input (got {}D input)'
|
||||||
|
.format(input.dim()))
|
||||||
|
super(SynchronizedBatchNorm1d, self)._check_input_dim(input)
|
||||||
|
|
||||||
|
|
||||||
|
class SynchronizedBatchNorm2d(_SynchronizedBatchNorm):
|
||||||
|
r"""Applies Batch Normalization over a 4d input that is seen as a mini-batch
|
||||||
|
of 3d inputs
|
||||||
|
|
||||||
|
.. math::
|
||||||
|
|
||||||
|
y = \frac{x - mean[x]}{ \sqrt{Var[x] + \epsilon}} * gamma + beta
|
||||||
|
|
||||||
|
This module differs from the built-in PyTorch BatchNorm2d as the mean and
|
||||||
|
standard-deviation are reduced across all devices during training.
|
||||||
|
|
||||||
|
For example, when one uses `nn.DataParallel` to wrap the network during
|
||||||
|
training, PyTorch's implementation normalize the tensor on each device using
|
||||||
|
the statistics only on that device, which accelerated the computation and
|
||||||
|
is also easy to implement, but the statistics might be inaccurate.
|
||||||
|
Instead, in this synchronized version, the statistics will be computed
|
||||||
|
over all training samples distributed on multiple devices.
|
||||||
|
|
||||||
|
Note that, for one-GPU or CPU-only case, this module behaves exactly same
|
||||||
|
as the built-in PyTorch implementation.
|
||||||
|
|
||||||
|
The mean and standard-deviation are calculated per-dimension over
|
||||||
|
the mini-batches and gamma and beta are learnable parameter vectors
|
||||||
|
of size C (where C is the input size).
|
||||||
|
|
||||||
|
During training, this layer keeps a running estimate of its computed mean
|
||||||
|
and variance. The running sum is kept with a default momentum of 0.1.
|
||||||
|
|
||||||
|
During evaluation, this running mean/variance is used for normalization.
|
||||||
|
|
||||||
|
Because the BatchNorm is done over the `C` dimension, computing statistics
|
||||||
|
on `(N, H, W)` slices, it's common terminology to call this Spatial BatchNorm
|
||||||
|
|
||||||
|
Args:
|
||||||
|
num_features: num_features from an expected input of
|
||||||
|
size batch_size x num_features x height x width
|
||||||
|
eps: a value added to the denominator for numerical stability.
|
||||||
|
Default: 1e-5
|
||||||
|
momentum: the value used for the running_mean and running_var
|
||||||
|
computation. Default: 0.1
|
||||||
|
affine: a boolean value that when set to ``True``, gives the layer learnable
|
||||||
|
affine parameters. Default: ``True``
|
||||||
|
|
||||||
|
Shape:
|
||||||
|
- Input: :math:`(N, C, H, W)`
|
||||||
|
- Output: :math:`(N, C, H, W)` (same shape as input)
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
>>> # With Learnable Parameters
|
||||||
|
>>> m = SynchronizedBatchNorm2d(100)
|
||||||
|
>>> # Without Learnable Parameters
|
||||||
|
>>> m = SynchronizedBatchNorm2d(100, affine=False)
|
||||||
|
>>> input = torch.autograd.Variable(torch.randn(20, 100, 35, 45))
|
||||||
|
>>> output = m(input)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _check_input_dim(self, input):
|
||||||
|
if input.dim() != 4:
|
||||||
|
raise ValueError('expected 4D input (got {}D input)'
|
||||||
|
.format(input.dim()))
|
||||||
|
super(SynchronizedBatchNorm2d, self)._check_input_dim(input)
|
||||||
|
|
||||||
|
|
||||||
|
class SynchronizedBatchNorm3d(_SynchronizedBatchNorm):
|
||||||
|
r"""Applies Batch Normalization over a 5d input that is seen as a mini-batch
|
||||||
|
of 4d inputs
|
||||||
|
|
||||||
|
.. math::
|
||||||
|
|
||||||
|
y = \frac{x - mean[x]}{ \sqrt{Var[x] + \epsilon}} * gamma + beta
|
||||||
|
|
||||||
|
This module differs from the built-in PyTorch BatchNorm3d as the mean and
|
||||||
|
standard-deviation are reduced across all devices during training.
|
||||||
|
|
||||||
|
For example, when one uses `nn.DataParallel` to wrap the network during
|
||||||
|
training, PyTorch's implementation normalize the tensor on each device using
|
||||||
|
the statistics only on that device, which accelerated the computation and
|
||||||
|
is also easy to implement, but the statistics might be inaccurate.
|
||||||
|
Instead, in this synchronized version, the statistics will be computed
|
||||||
|
over all training samples distributed on multiple devices.
|
||||||
|
|
||||||
|
Note that, for one-GPU or CPU-only case, this module behaves exactly same
|
||||||
|
as the built-in PyTorch implementation.
|
||||||
|
|
||||||
|
The mean and standard-deviation are calculated per-dimension over
|
||||||
|
the mini-batches and gamma and beta are learnable parameter vectors
|
||||||
|
of size C (where C is the input size).
|
||||||
|
|
||||||
|
During training, this layer keeps a running estimate of its computed mean
|
||||||
|
and variance. The running sum is kept with a default momentum of 0.1.
|
||||||
|
|
||||||
|
During evaluation, this running mean/variance is used for normalization.
|
||||||
|
|
||||||
|
Because the BatchNorm is done over the `C` dimension, computing statistics
|
||||||
|
on `(N, D, H, W)` slices, it's common terminology to call this Volumetric BatchNorm
|
||||||
|
or Spatio-temporal BatchNorm
|
||||||
|
|
||||||
|
Args:
|
||||||
|
num_features: num_features from an expected input of
|
||||||
|
size batch_size x num_features x depth x height x width
|
||||||
|
eps: a value added to the denominator for numerical stability.
|
||||||
|
Default: 1e-5
|
||||||
|
momentum: the value used for the running_mean and running_var
|
||||||
|
computation. Default: 0.1
|
||||||
|
affine: a boolean value that when set to ``True``, gives the layer learnable
|
||||||
|
affine parameters. Default: ``True``
|
||||||
|
|
||||||
|
Shape:
|
||||||
|
- Input: :math:`(N, C, D, H, W)`
|
||||||
|
- Output: :math:`(N, C, D, H, W)` (same shape as input)
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
>>> # With Learnable Parameters
|
||||||
|
>>> m = SynchronizedBatchNorm3d(100)
|
||||||
|
>>> # Without Learnable Parameters
|
||||||
|
>>> m = SynchronizedBatchNorm3d(100, affine=False)
|
||||||
|
>>> input = torch.autograd.Variable(torch.randn(20, 100, 35, 45, 10))
|
||||||
|
>>> output = m(input)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _check_input_dim(self, input):
|
||||||
|
if input.dim() != 5:
|
||||||
|
raise ValueError('expected 5D input (got {}D input)'
|
||||||
|
.format(input.dim()))
|
||||||
|
super(SynchronizedBatchNorm3d, self)._check_input_dim(input)
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# File : comm.py
|
||||||
|
# Author : Jiayuan Mao
|
||||||
|
# Email : maojiayuan@gmail.com
|
||||||
|
# Date : 27/01/2018
|
||||||
|
#
|
||||||
|
# This file is part of Synchronized-BatchNorm-PyTorch.
|
||||||
|
# https://github.com/vacancy/Synchronized-BatchNorm-PyTorch
|
||||||
|
# Distributed under MIT License.
|
||||||
|
|
||||||
|
import queue
|
||||||
|
import collections
|
||||||
|
import threading
|
||||||
|
|
||||||
|
__all__ = ['FutureResult', 'SlavePipe', 'SyncMaster']
|
||||||
|
|
||||||
|
|
||||||
|
class FutureResult(object):
|
||||||
|
"""A thread-safe future implementation. Used only as one-to-one pipe."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._result = None
|
||||||
|
self._lock = threading.Lock()
|
||||||
|
self._cond = threading.Condition(self._lock)
|
||||||
|
|
||||||
|
def put(self, result):
|
||||||
|
with self._lock:
|
||||||
|
assert self._result is None, 'Previous result has\'t been fetched.'
|
||||||
|
self._result = result
|
||||||
|
self._cond.notify()
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
with self._lock:
|
||||||
|
if self._result is None:
|
||||||
|
self._cond.wait()
|
||||||
|
|
||||||
|
res = self._result
|
||||||
|
self._result = None
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
_MasterRegistry = collections.namedtuple('MasterRegistry', ['result'])
|
||||||
|
_SlavePipeBase = collections.namedtuple('_SlavePipeBase', ['identifier', 'queue', 'result'])
|
||||||
|
|
||||||
|
|
||||||
|
class SlavePipe(_SlavePipeBase):
|
||||||
|
"""Pipe for master-slave communication."""
|
||||||
|
|
||||||
|
def run_slave(self, msg):
|
||||||
|
self.queue.put((self.identifier, msg))
|
||||||
|
ret = self.result.get()
|
||||||
|
self.queue.put(True)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
class SyncMaster(object):
|
||||||
|
"""An abstract `SyncMaster` object.
|
||||||
|
|
||||||
|
- During the replication, as the data parallel will trigger an callback of each module, all slave devices should
|
||||||
|
call `register(id)` and obtain an `SlavePipe` to communicate with the master.
|
||||||
|
- During the forward pass, master device invokes `run_master`, all messages from slave devices will be collected,
|
||||||
|
and passed to a registered callback.
|
||||||
|
- After receiving the messages, the master device should gather the information and determine to message passed
|
||||||
|
back to each slave devices.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, master_callback):
|
||||||
|
"""
|
||||||
|
|
||||||
|
Args:
|
||||||
|
master_callback: a callback to be invoked after having collected messages from slave devices.
|
||||||
|
"""
|
||||||
|
self._master_callback = master_callback
|
||||||
|
self._queue = queue.Queue()
|
||||||
|
self._registry = collections.OrderedDict()
|
||||||
|
self._activated = False
|
||||||
|
|
||||||
|
def __getstate__(self):
|
||||||
|
return {'master_callback': self._master_callback}
|
||||||
|
|
||||||
|
def __setstate__(self, state):
|
||||||
|
self.__init__(state['master_callback'])
|
||||||
|
|
||||||
|
def register_slave(self, identifier):
|
||||||
|
"""
|
||||||
|
Register an slave device.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
identifier: an identifier, usually is the device id.
|
||||||
|
|
||||||
|
Returns: a `SlavePipe` object which can be used to communicate with the master device.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if self._activated:
|
||||||
|
assert self._queue.empty(), 'Queue is not clean before next initialization.'
|
||||||
|
self._activated = False
|
||||||
|
self._registry.clear()
|
||||||
|
future = FutureResult()
|
||||||
|
self._registry[identifier] = _MasterRegistry(future)
|
||||||
|
return SlavePipe(identifier, self._queue, future)
|
||||||
|
|
||||||
|
def run_master(self, master_msg):
|
||||||
|
"""
|
||||||
|
Main entry for the master device in each forward pass.
|
||||||
|
The messages were first collected from each devices (including the master device), and then
|
||||||
|
an callback will be invoked to compute the message to be sent back to each devices
|
||||||
|
(including the master device).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
master_msg: the message that the master want to send to itself. This will be placed as the first
|
||||||
|
message when calling `master_callback`. For detailed usage, see `_SynchronizedBatchNorm` for an example.
|
||||||
|
|
||||||
|
Returns: the message to be sent back to the master device.
|
||||||
|
|
||||||
|
"""
|
||||||
|
self._activated = True
|
||||||
|
|
||||||
|
intermediates = [(0, master_msg)]
|
||||||
|
for i in range(self.nr_slaves):
|
||||||
|
intermediates.append(self._queue.get())
|
||||||
|
|
||||||
|
results = self._master_callback(intermediates)
|
||||||
|
assert results[0][0] == 0, 'The first result should belongs to the master.'
|
||||||
|
|
||||||
|
for i, res in results:
|
||||||
|
if i == 0:
|
||||||
|
continue
|
||||||
|
self._registry[i].result.put(res)
|
||||||
|
|
||||||
|
for i in range(self.nr_slaves):
|
||||||
|
assert self._queue.get() is True
|
||||||
|
|
||||||
|
return results[0][1]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def nr_slaves(self):
|
||||||
|
return len(self._registry)
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# File : replicate.py
|
||||||
|
# Author : Jiayuan Mao
|
||||||
|
# Email : maojiayuan@gmail.com
|
||||||
|
# Date : 27/01/2018
|
||||||
|
#
|
||||||
|
# This file is part of Synchronized-BatchNorm-PyTorch.
|
||||||
|
# https://github.com/vacancy/Synchronized-BatchNorm-PyTorch
|
||||||
|
# Distributed under MIT License.
|
||||||
|
|
||||||
|
import functools
|
||||||
|
|
||||||
|
from torch.nn.parallel.data_parallel import DataParallel
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'CallbackContext',
|
||||||
|
'execute_replication_callbacks',
|
||||||
|
'DataParallelWithCallback',
|
||||||
|
'patch_replication_callback'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class CallbackContext(object):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def execute_replication_callbacks(modules):
|
||||||
|
"""
|
||||||
|
Execute an replication callback `__data_parallel_replicate__` on each module created by original replication.
|
||||||
|
|
||||||
|
The callback will be invoked with arguments `__data_parallel_replicate__(ctx, copy_id)`
|
||||||
|
|
||||||
|
Note that, as all modules are isomorphism, we assign each sub-module with a context
|
||||||
|
(shared among multiple copies of this module on different devices).
|
||||||
|
Through this context, different copies can share some information.
|
||||||
|
|
||||||
|
We guarantee that the callback on the master copy (the first copy) will be called ahead of calling the callback
|
||||||
|
of any slave copies.
|
||||||
|
"""
|
||||||
|
master_copy = modules[0]
|
||||||
|
nr_modules = len(list(master_copy.modules()))
|
||||||
|
ctxs = [CallbackContext() for _ in range(nr_modules)]
|
||||||
|
|
||||||
|
for i, module in enumerate(modules):
|
||||||
|
for j, m in enumerate(module.modules()):
|
||||||
|
if hasattr(m, '__data_parallel_replicate__'):
|
||||||
|
m.__data_parallel_replicate__(ctxs[j], i)
|
||||||
|
|
||||||
|
|
||||||
|
class DataParallelWithCallback(DataParallel):
|
||||||
|
"""
|
||||||
|
Data Parallel with a replication callback.
|
||||||
|
|
||||||
|
An replication callback `__data_parallel_replicate__` of each module will be invoked after being created by
|
||||||
|
original `replicate` function.
|
||||||
|
The callback will be invoked with arguments `__data_parallel_replicate__(ctx, copy_id)`
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
> sync_bn = SynchronizedBatchNorm1d(10, eps=1e-5, affine=False)
|
||||||
|
> sync_bn = DataParallelWithCallback(sync_bn, device_ids=[0, 1])
|
||||||
|
# sync_bn.__data_parallel_replicate__ will be invoked.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def replicate(self, module, device_ids):
|
||||||
|
modules = super(DataParallelWithCallback, self).replicate(module, device_ids)
|
||||||
|
execute_replication_callbacks(modules)
|
||||||
|
return modules
|
||||||
|
|
||||||
|
|
||||||
|
def patch_replication_callback(data_parallel):
|
||||||
|
"""
|
||||||
|
Monkey-patch an existing `DataParallel` object. Add the replication callback.
|
||||||
|
Useful when you have customized `DataParallel` implementation.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
> sync_bn = SynchronizedBatchNorm1d(10, eps=1e-5, affine=False)
|
||||||
|
> sync_bn = DataParallel(sync_bn, device_ids=[0, 1])
|
||||||
|
> patch_replication_callback(sync_bn)
|
||||||
|
# this is equivalent to
|
||||||
|
> sync_bn = SynchronizedBatchNorm1d(10, eps=1e-5, affine=False)
|
||||||
|
> sync_bn = DataParallelWithCallback(sync_bn, device_ids=[0, 1])
|
||||||
|
"""
|
||||||
|
|
||||||
|
assert isinstance(data_parallel, DataParallel)
|
||||||
|
|
||||||
|
old_replicate = data_parallel.replicate
|
||||||
|
|
||||||
|
@functools.wraps(old_replicate)
|
||||||
|
def new_replicate(module, device_ids):
|
||||||
|
modules = old_replicate(module, device_ids)
|
||||||
|
execute_replication_callbacks(modules)
|
||||||
|
return modules
|
||||||
|
|
||||||
|
data_parallel.replicate = new_replicate
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# File : unittest.py
|
||||||
|
# Author : Jiayuan Mao
|
||||||
|
# Email : maojiayuan@gmail.com
|
||||||
|
# Date : 27/01/2018
|
||||||
|
#
|
||||||
|
# This file is part of Synchronized-BatchNorm-PyTorch.
|
||||||
|
# https://github.com/vacancy/Synchronized-BatchNorm-PyTorch
|
||||||
|
# Distributed under MIT License.
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from torch.autograd import Variable
|
||||||
|
|
||||||
|
|
||||||
|
def as_numpy(v):
|
||||||
|
if isinstance(v, Variable):
|
||||||
|
v = v.data
|
||||||
|
return v.cpu().numpy()
|
||||||
|
|
||||||
|
|
||||||
|
class TorchTestCase(unittest.TestCase):
|
||||||
|
def assertTensorClose(self, a, b, atol=1e-3, rtol=1e-3):
|
||||||
|
npa, npb = as_numpy(a), as_numpy(b)
|
||||||
|
self.assertTrue(
|
||||||
|
np.allclose(npa, npb, atol=atol),
|
||||||
|
'Tensor close check failed\n{}\n{}\nadiff={}, rdiff={}'.format(a, b, np.abs(npa - npb).max(), np.abs((npa - npb) / np.fmax(npa, 1e-5)).max())
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user