Add more algorithms
This commit is contained in:
13
lib/config_utils/__init__.py
Normal file
13
lib/config_utils/__init__.py
Normal file
@@ -0,0 +1,13 @@
|
||||
##################################################
|
||||
# Copyright (c) Xuanyi Dong [GitHub D-X-Y], 2019 #
|
||||
##################################################
|
||||
from .configure_utils import load_config, dict2config, configure2str
|
||||
from .basic_args import obtain_basic_args
|
||||
from .attention_args import obtain_attention_args
|
||||
from .random_baseline import obtain_RandomSearch_args
|
||||
from .cls_kd_args import obtain_cls_kd_args
|
||||
from .cls_init_args import obtain_cls_init_args
|
||||
from .search_single_args import obtain_search_single_args
|
||||
from .search_args import obtain_search_args
|
||||
# for network pruning
|
||||
from .pruning_args import obtain_pruning_args
|
||||
25
lib/config_utils/attention_args.py
Normal file
25
lib/config_utils/attention_args.py
Normal file
@@ -0,0 +1,25 @@
|
||||
##################################################
|
||||
# Copyright (c) Xuanyi Dong [GitHub D-X-Y], 2019 #
|
||||
##################################################
|
||||
import os, sys, time, random, argparse
|
||||
from .share_args import add_shared_args
|
||||
|
||||
def obtain_attention_args():
|
||||
parser = argparse.ArgumentParser(description='Train a classification model on typical image classification datasets.', formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument('--resume' , type=str, help='Resume path.')
|
||||
parser.add_argument('--init_model' , type=str, help='The initialization model path.')
|
||||
parser.add_argument('--model_config', type=str, help='The path to the model configuration')
|
||||
parser.add_argument('--optim_config', type=str, help='The path to the optimizer configuration')
|
||||
parser.add_argument('--procedure' , type=str, help='The procedure basic prefix.')
|
||||
parser.add_argument('--att_channel' , type=int, help='.')
|
||||
parser.add_argument('--att_spatial' , type=str, help='.')
|
||||
parser.add_argument('--att_active' , type=str, help='.')
|
||||
add_shared_args( parser )
|
||||
# Optimization options
|
||||
parser.add_argument('--batch_size', type=int, default=2, help='Batch size for training.')
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.rand_seed is None or args.rand_seed < 0:
|
||||
args.rand_seed = random.randint(1, 100000)
|
||||
assert args.save_dir is not None, 'save-path argument can not be None'
|
||||
return args
|
||||
23
lib/config_utils/basic_args.py
Normal file
23
lib/config_utils/basic_args.py
Normal file
@@ -0,0 +1,23 @@
|
||||
##################################################
|
||||
# Copyright (c) Xuanyi Dong [GitHub D-X-Y], 2019 #
|
||||
##################################################
|
||||
import os, sys, time, random, argparse
|
||||
from .share_args import add_shared_args
|
||||
|
||||
def obtain_basic_args():
|
||||
parser = argparse.ArgumentParser(description='Train a classification model on typical image classification datasets.', formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument('--resume' , type=str, help='Resume path.')
|
||||
parser.add_argument('--init_model' , type=str, help='The initialization model path.')
|
||||
parser.add_argument('--model_config', type=str, help='The path to the model configuration')
|
||||
parser.add_argument('--optim_config', type=str, help='The path to the optimizer configuration')
|
||||
parser.add_argument('--procedure' , type=str, help='The procedure basic prefix.')
|
||||
parser.add_argument('--model_source', type=str, default='normal',help='The source of model defination.')
|
||||
add_shared_args( parser )
|
||||
# Optimization options
|
||||
parser.add_argument('--batch_size', type=int, default=2, help='Batch size for training.')
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.rand_seed is None or args.rand_seed < 0:
|
||||
args.rand_seed = random.randint(1, 100000)
|
||||
assert args.save_dir is not None, 'save-path argument can not be None'
|
||||
return args
|
||||
23
lib/config_utils/cls_init_args.py
Normal file
23
lib/config_utils/cls_init_args.py
Normal file
@@ -0,0 +1,23 @@
|
||||
##################################################
|
||||
# Copyright (c) Xuanyi Dong [GitHub D-X-Y], 2019 #
|
||||
##################################################
|
||||
import os, sys, time, random, argparse
|
||||
from .share_args import add_shared_args
|
||||
|
||||
def obtain_cls_init_args():
|
||||
parser = argparse.ArgumentParser(description='Train a classification model on typical image classification datasets.', formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument('--resume' , type=str, help='Resume path.')
|
||||
parser.add_argument('--init_model' , type=str, help='The initialization model path.')
|
||||
parser.add_argument('--model_config', type=str, help='The path to the model configuration')
|
||||
parser.add_argument('--optim_config', type=str, help='The path to the optimizer configuration')
|
||||
parser.add_argument('--procedure' , type=str, help='The procedure basic prefix.')
|
||||
parser.add_argument('--init_checkpoint', type=str, help='The checkpoint path to the initial model.')
|
||||
add_shared_args( parser )
|
||||
# Optimization options
|
||||
parser.add_argument('--batch_size', type=int, default=2, help='Batch size for training.')
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.rand_seed is None or args.rand_seed < 0:
|
||||
args.rand_seed = random.randint(1, 100000)
|
||||
assert args.save_dir is not None, 'save-path argument can not be None'
|
||||
return args
|
||||
26
lib/config_utils/cls_kd_args.py
Normal file
26
lib/config_utils/cls_kd_args.py
Normal file
@@ -0,0 +1,26 @@
|
||||
##################################################
|
||||
# Copyright (c) Xuanyi Dong [GitHub D-X-Y], 2019 #
|
||||
##################################################
|
||||
import os, sys, time, random, argparse
|
||||
from .share_args import add_shared_args
|
||||
|
||||
def obtain_cls_kd_args():
|
||||
parser = argparse.ArgumentParser(description='Train a classification model on typical image classification datasets.', formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument('--resume' , type=str, help='Resume path.')
|
||||
parser.add_argument('--init_model' , type=str, help='The initialization model path.')
|
||||
parser.add_argument('--model_config', type=str, help='The path to the model configuration')
|
||||
parser.add_argument('--optim_config', type=str, help='The path to the optimizer configuration')
|
||||
parser.add_argument('--procedure' , type=str, help='The procedure basic prefix.')
|
||||
parser.add_argument('--KD_checkpoint', type=str, help='The teacher checkpoint in knowledge distillation.')
|
||||
parser.add_argument('--KD_alpha' , type=float, help='The alpha parameter in knowledge distillation.')
|
||||
parser.add_argument('--KD_temperature', type=float, help='The temperature parameter in knowledge distillation.')
|
||||
#parser.add_argument('--KD_feature', type=float, help='Knowledge distillation at the feature level.')
|
||||
add_shared_args( parser )
|
||||
# Optimization options
|
||||
parser.add_argument('--batch_size', type=int, default=2, help='Batch size for training.')
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.rand_seed is None or args.rand_seed < 0:
|
||||
args.rand_seed = random.randint(1, 100000)
|
||||
assert args.save_dir is not None, 'save-path argument can not be None'
|
||||
return args
|
||||
105
lib/config_utils/configure_utils.py
Normal file
105
lib/config_utils/configure_utils.py
Normal file
@@ -0,0 +1,105 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
# All rights reserved.
|
||||
#
|
||||
# This source code is licensed under the license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
#
|
||||
import os, sys, json
|
||||
from os import path as osp
|
||||
from pathlib import Path
|
||||
from collections import namedtuple
|
||||
|
||||
support_types = ('str', 'int', 'bool', 'float', 'none')
|
||||
|
||||
|
||||
def convert_param(original_lists):
|
||||
assert isinstance(original_lists, list), 'The type is not right : {:}'.format(original_lists)
|
||||
ctype, value = original_lists[0], original_lists[1]
|
||||
assert ctype in support_types, 'Ctype={:}, support={:}'.format(ctype, support_types)
|
||||
is_list = isinstance(value, list)
|
||||
if not is_list: value = [value]
|
||||
outs = []
|
||||
for x in value:
|
||||
if ctype == 'int':
|
||||
x = int(x)
|
||||
elif ctype == 'str':
|
||||
x = str(x)
|
||||
elif ctype == 'bool':
|
||||
x = bool(int(x))
|
||||
elif ctype == 'float':
|
||||
x = float(x)
|
||||
elif ctype == 'none':
|
||||
assert x == 'None', 'for none type, the value must be None instead of {:}'.format(x)
|
||||
x = None
|
||||
else:
|
||||
raise TypeError('Does not know this type : {:}'.format(ctype))
|
||||
outs.append(x)
|
||||
if not is_list: outs = outs[0]
|
||||
return outs
|
||||
|
||||
|
||||
def load_config(path, extra, logger):
|
||||
path = str(path)
|
||||
if hasattr(logger, 'log'): logger.log(path)
|
||||
assert os.path.exists(path), 'Can not find {:}'.format(path)
|
||||
# Reading data back
|
||||
with open(path, 'r') as f:
|
||||
data = json.load(f)
|
||||
content = { k: convert_param(v) for k,v in data.items()}
|
||||
assert extra is None or isinstance(extra, dict), 'invalid type of extra : {:}'.format(extra)
|
||||
if isinstance(extra, dict): content = {**content, **extra}
|
||||
Arguments = namedtuple('Configure', ' '.join(content.keys()))
|
||||
content = Arguments(**content)
|
||||
if hasattr(logger, 'log'): logger.log('{:}'.format(content))
|
||||
return content
|
||||
|
||||
|
||||
def configure2str(config, xpath=None):
|
||||
if not isinstance(config, dict):
|
||||
config = config._asdict()
|
||||
def cstring(x):
|
||||
return "\"{:}\"".format(x)
|
||||
def gtype(x):
|
||||
if isinstance(x, list): x = x[0]
|
||||
if isinstance(x, str) : return 'str'
|
||||
elif isinstance(x, bool) : return 'bool'
|
||||
elif isinstance(x, int): return 'int'
|
||||
elif isinstance(x, float): return 'float'
|
||||
elif x is None : return 'none'
|
||||
else: raise ValueError('invalid : {:}'.format(x))
|
||||
def cvalue(x, xtype):
|
||||
if isinstance(x, list): is_list = True
|
||||
else:
|
||||
is_list, x = False, [x]
|
||||
temps = []
|
||||
for temp in x:
|
||||
if xtype == 'bool' : temp = cstring(int(temp))
|
||||
elif xtype == 'none': temp = cstring('None')
|
||||
else : temp = cstring(temp)
|
||||
temps.append( temp )
|
||||
if is_list:
|
||||
return "[{:}]".format( ', '.join( temps ) )
|
||||
else:
|
||||
return temps[0]
|
||||
|
||||
xstrings = []
|
||||
for key, value in config.items():
|
||||
xtype = gtype(value)
|
||||
string = ' {:20s} : [{:8s}, {:}]'.format(cstring(key), cstring(xtype), cvalue(value, xtype))
|
||||
xstrings.append(string)
|
||||
Fstring = '{\n' + ',\n'.join(xstrings) + '\n}'
|
||||
if xpath is not None:
|
||||
parent = Path(xpath).resolve().parent
|
||||
parent.mkdir(parents=True, exist_ok=True)
|
||||
if osp.isfile(xpath): os.remove(xpath)
|
||||
with open(xpath, "w") as text_file:
|
||||
text_file.write('{:}'.format(Fstring))
|
||||
return Fstring
|
||||
|
||||
|
||||
def dict2config(xdict, logger):
|
||||
assert isinstance(xdict, dict), 'invalid type : {:}'.format( type(xdict) )
|
||||
Arguments = namedtuple('Configure', ' '.join(xdict.keys()))
|
||||
content = Arguments(**xdict)
|
||||
if hasattr(logger, 'log'): logger.log('{:}'.format(content))
|
||||
return content
|
||||
29
lib/config_utils/pruning_args.py
Normal file
29
lib/config_utils/pruning_args.py
Normal file
@@ -0,0 +1,29 @@
|
||||
##################################################
|
||||
# Copyright (c) Xuanyi Dong [GitHub D-X-Y], 2019 #
|
||||
##################################################
|
||||
import os, sys, time, random, argparse
|
||||
from .share_args import add_shared_args
|
||||
|
||||
def obtain_pruning_args():
|
||||
parser = argparse.ArgumentParser(description='Train a classification model on typical image classification datasets.', formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument('--resume' , type=str, help='Resume path.')
|
||||
parser.add_argument('--init_model' , type=str, help='The initialization model path.')
|
||||
parser.add_argument('--model_config', type=str, help='The path to the model configuration')
|
||||
parser.add_argument('--optim_config', type=str, help='The path to the optimizer configuration')
|
||||
parser.add_argument('--procedure' , type=str, help='The procedure basic prefix.')
|
||||
parser.add_argument('--keep_ratio' , type=float, help='The left channel ratio compared to the original network.')
|
||||
parser.add_argument('--model_version', type=str, help='The network version.')
|
||||
parser.add_argument('--KD_alpha' , type=float, help='The alpha parameter in knowledge distillation.')
|
||||
parser.add_argument('--KD_temperature', type=float, help='The temperature parameter in knowledge distillation.')
|
||||
parser.add_argument('--Regular_W_feat', type=float, help='The .')
|
||||
parser.add_argument('--Regular_W_conv', type=float, help='The .')
|
||||
add_shared_args( parser )
|
||||
# Optimization options
|
||||
parser.add_argument('--batch_size', type=int, default=2, help='Batch size for training.')
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.rand_seed is None or args.rand_seed < 0:
|
||||
args.rand_seed = random.randint(1, 100000)
|
||||
assert args.save_dir is not None, 'save-path argument can not be None'
|
||||
assert args.keep_ratio > 0 and args.keep_ratio <= 1, 'invalid keep ratio : {:}'.format(args.keep_ratio)
|
||||
return args
|
||||
27
lib/config_utils/random_baseline.py
Normal file
27
lib/config_utils/random_baseline.py
Normal file
@@ -0,0 +1,27 @@
|
||||
##################################################
|
||||
# Copyright (c) Xuanyi Dong [GitHub D-X-Y], 2019 #
|
||||
##################################################
|
||||
import os, sys, time, random, argparse
|
||||
from .share_args import add_shared_args
|
||||
|
||||
|
||||
def obtain_RandomSearch_args():
|
||||
parser = argparse.ArgumentParser(description='Train a classification model on typical image classification datasets.', formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument('--resume' , type=str, help='Resume path.')
|
||||
parser.add_argument('--init_model' , type=str, help='The initialization model path.')
|
||||
parser.add_argument('--expect_flop', type=float, help='The expected flop keep ratio.')
|
||||
parser.add_argument('--arch_nums' , type=int, help='The maximum number of running random arch generating..')
|
||||
parser.add_argument('--model_config', type=str, help='The path to the model configuration')
|
||||
parser.add_argument('--optim_config', type=str, help='The path to the optimizer configuration')
|
||||
parser.add_argument('--random_mode', type=str, choices=['random', 'fix'], help='The path to the optimizer configuration')
|
||||
parser.add_argument('--procedure' , type=str, help='The procedure basic prefix.')
|
||||
add_shared_args( parser )
|
||||
# Optimization options
|
||||
parser.add_argument('--batch_size', type=int, default=2, help='Batch size for training.')
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.rand_seed is None or args.rand_seed < 0:
|
||||
args.rand_seed = random.randint(1, 100000)
|
||||
assert args.save_dir is not None, 'save-path argument can not be None'
|
||||
#assert args.flop_ratio_min < args.flop_ratio_max, 'flop-ratio {:} vs {:}'.format(args.flop_ratio_min, args.flop_ratio_max)
|
||||
return args
|
||||
35
lib/config_utils/search_args.py
Normal file
35
lib/config_utils/search_args.py
Normal file
@@ -0,0 +1,35 @@
|
||||
##################################################
|
||||
# Copyright (c) Xuanyi Dong [GitHub D-X-Y], 2019 #
|
||||
##################################################
|
||||
import os, sys, time, random, argparse
|
||||
from .share_args import add_shared_args
|
||||
|
||||
|
||||
def obtain_search_args():
|
||||
parser = argparse.ArgumentParser(description='Train a classification model on typical image classification datasets.', formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument('--resume' , type=str, help='Resume path.')
|
||||
parser.add_argument('--model_config' , type=str, help='The path to the model configuration')
|
||||
parser.add_argument('--optim_config' , type=str, help='The path to the optimizer configuration')
|
||||
parser.add_argument('--split_path' , type=str, help='The split file path.')
|
||||
#parser.add_argument('--arch_para_pure', type=int, help='The architecture-parameter pure or not.')
|
||||
parser.add_argument('--gumbel_tau_max', type=float, help='The maximum tau for Gumbel.')
|
||||
parser.add_argument('--gumbel_tau_min', type=float, help='The minimum tau for Gumbel.')
|
||||
parser.add_argument('--procedure' , type=str, help='The procedure basic prefix.')
|
||||
parser.add_argument('--FLOP_ratio' , type=float, help='The expected FLOP ratio.')
|
||||
parser.add_argument('--FLOP_weight' , type=float, help='The loss weight for FLOP.')
|
||||
parser.add_argument('--FLOP_tolerant' , type=float, help='The tolerant range for FLOP.')
|
||||
# ablation studies
|
||||
parser.add_argument('--ablation_num_select', type=int, help='The number of randomly selected channels.')
|
||||
add_shared_args( parser )
|
||||
# Optimization options
|
||||
parser.add_argument('--batch_size' , type=int, default=2, help='Batch size for training.')
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.rand_seed is None or args.rand_seed < 0:
|
||||
args.rand_seed = random.randint(1, 100000)
|
||||
assert args.save_dir is not None, 'save-path argument can not be None'
|
||||
assert args.gumbel_tau_max is not None and args.gumbel_tau_min is not None
|
||||
assert args.FLOP_tolerant is not None and args.FLOP_tolerant > 0, 'invalid FLOP_tolerant : {:}'.format(FLOP_tolerant)
|
||||
#assert args.arch_para_pure is not None, 'arch_para_pure is not None: {:}'.format(args.arch_para_pure)
|
||||
#args.arch_para_pure = bool(args.arch_para_pure)
|
||||
return args
|
||||
34
lib/config_utils/search_single_args.py
Normal file
34
lib/config_utils/search_single_args.py
Normal file
@@ -0,0 +1,34 @@
|
||||
##################################################
|
||||
# Copyright (c) Xuanyi Dong [GitHub D-X-Y], 2019 #
|
||||
##################################################
|
||||
import os, sys, time, random, argparse
|
||||
from .share_args import add_shared_args
|
||||
|
||||
|
||||
def obtain_search_single_args():
|
||||
parser = argparse.ArgumentParser(description='Train a classification model on typical image classification datasets.', formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument('--resume' , type=str, help='Resume path.')
|
||||
parser.add_argument('--model_config' , type=str, help='The path to the model configuration')
|
||||
parser.add_argument('--optim_config' , type=str, help='The path to the optimizer configuration')
|
||||
parser.add_argument('--split_path' , type=str, help='The split file path.')
|
||||
parser.add_argument('--search_shape' , type=str, help='The shape to be searched.')
|
||||
#parser.add_argument('--arch_para_pure', type=int, help='The architecture-parameter pure or not.')
|
||||
parser.add_argument('--gumbel_tau_max', type=float, help='The maximum tau for Gumbel.')
|
||||
parser.add_argument('--gumbel_tau_min', type=float, help='The minimum tau for Gumbel.')
|
||||
parser.add_argument('--procedure' , type=str, help='The procedure basic prefix.')
|
||||
parser.add_argument('--FLOP_ratio' , type=float, help='The expected FLOP ratio.')
|
||||
parser.add_argument('--FLOP_weight' , type=float, help='The loss weight for FLOP.')
|
||||
parser.add_argument('--FLOP_tolerant' , type=float, help='The tolerant range for FLOP.')
|
||||
add_shared_args( parser )
|
||||
# Optimization options
|
||||
parser.add_argument('--batch_size' , type=int, default=2, help='Batch size for training.')
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.rand_seed is None or args.rand_seed < 0:
|
||||
args.rand_seed = random.randint(1, 100000)
|
||||
assert args.save_dir is not None, 'save-path argument can not be None'
|
||||
assert args.gumbel_tau_max is not None and args.gumbel_tau_min is not None
|
||||
assert args.FLOP_tolerant is not None and args.FLOP_tolerant > 0, 'invalid FLOP_tolerant : {:}'.format(FLOP_tolerant)
|
||||
#assert args.arch_para_pure is not None, 'arch_para_pure is not None: {:}'.format(args.arch_para_pure)
|
||||
#args.arch_para_pure = bool(args.arch_para_pure)
|
||||
return args
|
||||
20
lib/config_utils/share_args.py
Normal file
20
lib/config_utils/share_args.py
Normal file
@@ -0,0 +1,20 @@
|
||||
##################################################
|
||||
# Copyright (c) Xuanyi Dong [GitHub D-X-Y], 2019 #
|
||||
##################################################
|
||||
import os, sys, time, random, argparse
|
||||
|
||||
def add_shared_args( parser ):
|
||||
# Data Generation
|
||||
parser.add_argument('--dataset', type=str, help='The dataset name.')
|
||||
parser.add_argument('--data_path', type=str, help='The dataset name.')
|
||||
parser.add_argument('--cutout_length', type=int, help='The cutout length, negative means not use.')
|
||||
# Printing
|
||||
parser.add_argument('--print_freq', type=int, default=100, help='print frequency (default: 200)')
|
||||
parser.add_argument('--print_freq_eval', type=int, default=100, help='print frequency (default: 200)')
|
||||
# Checkpoints
|
||||
parser.add_argument('--eval_frequency', type=int, default=1, help='evaluation frequency (default: 200)')
|
||||
parser.add_argument('--save_dir', type=str, help='Folder to save checkpoints and log.')
|
||||
# Acceleration
|
||||
parser.add_argument('--workers', type=int, default=8, help='number of data loading workers (default: 8)')
|
||||
# Random Seed
|
||||
parser.add_argument('--rand_seed', type=int, default=-1, help='manual seed')
|
||||
126
lib/datasets/DownsampledImageNet.py
Normal file
126
lib/datasets/DownsampledImageNet.py
Normal file
@@ -0,0 +1,126 @@
|
||||
import os, sys, hashlib, torch
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
import torch.utils.data as data
|
||||
if sys.version_info[0] == 2:
|
||||
import cPickle as pickle
|
||||
else:
|
||||
import pickle
|
||||
|
||||
|
||||
def calculate_md5(fpath, chunk_size=1024 * 1024):
|
||||
md5 = hashlib.md5()
|
||||
with open(fpath, 'rb') as f:
|
||||
for chunk in iter(lambda: f.read(chunk_size), b''):
|
||||
md5.update(chunk)
|
||||
return md5.hexdigest()
|
||||
|
||||
|
||||
def check_md5(fpath, md5, **kwargs):
|
||||
return md5 == calculate_md5(fpath, **kwargs)
|
||||
|
||||
|
||||
def check_integrity(fpath, md5=None):
|
||||
if not os.path.isfile(fpath): return False
|
||||
if md5 is None: return True
|
||||
else : return check_md5(fpath, md5)
|
||||
|
||||
|
||||
class ImageNet16(data.Dataset):
|
||||
# http://image-net.org/download-images
|
||||
# A Downsampled Variant of ImageNet as an Alternative to the CIFAR datasets
|
||||
# https://arxiv.org/pdf/1707.08819.pdf
|
||||
|
||||
train_list = [
|
||||
['train_data_batch_1', '27846dcaa50de8e21a7d1a35f30f0e91'],
|
||||
['train_data_batch_2', 'c7254a054e0e795c69120a5727050e3f'],
|
||||
['train_data_batch_3', '4333d3df2e5ffb114b05d2ffc19b1e87'],
|
||||
['train_data_batch_4', '1620cdf193304f4a92677b695d70d10f'],
|
||||
['train_data_batch_5', '348b3c2fdbb3940c4e9e834affd3b18d'],
|
||||
['train_data_batch_6', '6e765307c242a1b3d7d5ef9139b48945'],
|
||||
['train_data_batch_7', '564926d8cbf8fc4818ba23d2faac7564'],
|
||||
['train_data_batch_8', 'f4755871f718ccb653440b9dd0ebac66'],
|
||||
['train_data_batch_9', 'bb6dd660c38c58552125b1a92f86b5d4'],
|
||||
['train_data_batch_10','8f03f34ac4b42271a294f91bf480f29b'],
|
||||
]
|
||||
valid_list = [
|
||||
['val_data', '3410e3017fdaefba8d5073aaa65e4bd6'],
|
||||
]
|
||||
|
||||
def __init__(self, root, train, transform, use_num_of_class_only=None):
|
||||
self.root = root
|
||||
self.transform = transform
|
||||
self.train = train # training set or valid set
|
||||
if not self._check_integrity(): raise RuntimeError('Dataset not found or corrupted.')
|
||||
|
||||
if self.train: downloaded_list = self.train_list
|
||||
else : downloaded_list = self.valid_list
|
||||
self.data = []
|
||||
self.targets = []
|
||||
|
||||
# now load the picked numpy arrays
|
||||
for i, (file_name, checksum) in enumerate(downloaded_list):
|
||||
file_path = os.path.join(self.root, file_name)
|
||||
#print ('Load {:}/{:02d}-th : {:}'.format(i, len(downloaded_list), file_path))
|
||||
with open(file_path, 'rb') as f:
|
||||
if sys.version_info[0] == 2:
|
||||
entry = pickle.load(f)
|
||||
else:
|
||||
entry = pickle.load(f, encoding='latin1')
|
||||
self.data.append(entry['data'])
|
||||
self.targets.extend(entry['labels'])
|
||||
self.data = np.vstack(self.data).reshape(-1, 3, 16, 16)
|
||||
self.data = self.data.transpose((0, 2, 3, 1)) # convert to HWC
|
||||
if use_num_of_class_only is not None:
|
||||
assert isinstance(use_num_of_class_only, int) and use_num_of_class_only > 0 and use_num_of_class_only < 1000, 'invalid use_num_of_class_only : {:}'.format(use_num_of_class_only)
|
||||
new_data, new_targets = [], []
|
||||
for I, L in zip(self.data, self.targets):
|
||||
if 1 <= L <= use_num_of_class_only:
|
||||
new_data.append( I )
|
||||
new_targets.append( L )
|
||||
self.data = new_data
|
||||
self.targets = new_targets
|
||||
# self.mean.append(entry['mean'])
|
||||
#self.mean = np.vstack(self.mean).reshape(-1, 3, 16, 16)
|
||||
#self.mean = np.mean(np.mean(np.mean(self.mean, axis=0), axis=1), axis=1)
|
||||
#print ('Mean : {:}'.format(self.mean))
|
||||
#temp = self.data - np.reshape(self.mean, (1, 1, 1, 3))
|
||||
#std_data = np.std(temp, axis=0)
|
||||
#std_data = np.mean(np.mean(std_data, axis=0), axis=0)
|
||||
#print ('Std : {:}'.format(std_data))
|
||||
|
||||
def __getitem__(self, index):
|
||||
img, target = self.data[index], self.targets[index] - 1
|
||||
|
||||
img = Image.fromarray(img)
|
||||
|
||||
if self.transform is not None:
|
||||
img = self.transform(img)
|
||||
|
||||
return img, target
|
||||
|
||||
def __len__(self):
|
||||
return len(self.data)
|
||||
|
||||
def _check_integrity(self):
|
||||
root = self.root
|
||||
for fentry in (self.train_list + self.valid_list):
|
||||
filename, md5 = fentry[0], fentry[1]
|
||||
fpath = os.path.join(root, filename)
|
||||
if not check_integrity(fpath, md5):
|
||||
return False
|
||||
return True
|
||||
|
||||
#
|
||||
if __name__ == '__main__':
|
||||
train = ImageNet16('/data02/dongxuanyi/.torch/cifar.python/ImageNet16', True , None)
|
||||
valid = ImageNet16('/data02/dongxuanyi/.torch/cifar.python/ImageNet16', False, None)
|
||||
|
||||
print ( len(train) )
|
||||
print ( len(valid) )
|
||||
image, label = train[111]
|
||||
trainX = ImageNet16('/data02/dongxuanyi/.torch/cifar.python/ImageNet16', True , None, 200)
|
||||
validX = ImageNet16('/data02/dongxuanyi/.torch/cifar.python/ImageNet16', False , None, 200)
|
||||
print ( len(trainX) )
|
||||
print ( len(validX) )
|
||||
#import pdb; pdb.set_trace()
|
||||
191
lib/datasets/LandmarkDataset.py
Normal file
191
lib/datasets/LandmarkDataset.py
Normal file
@@ -0,0 +1,191 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
# All rights reserved.
|
||||
#
|
||||
# This source code is licensed under the license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
#
|
||||
from os import path as osp
|
||||
from copy import deepcopy as copy
|
||||
from tqdm import tqdm
|
||||
import warnings, time, random, numpy as np
|
||||
|
||||
from pts_utils import generate_label_map
|
||||
from xvision import denormalize_points
|
||||
from xvision import identity2affine, solve2theta, affine2image
|
||||
from .dataset_utils import pil_loader
|
||||
from .landmark_utils import PointMeta2V
|
||||
from .augmentation_utils import CutOut
|
||||
import torch
|
||||
import torch.utils.data as data
|
||||
|
||||
|
||||
class LandmarkDataset(data.Dataset):
|
||||
|
||||
def __init__(self, transform, sigma, downsample, heatmap_type, shape, use_gray, mean_file, data_indicator, cache_images=None):
|
||||
|
||||
self.transform = transform
|
||||
self.sigma = sigma
|
||||
self.downsample = downsample
|
||||
self.heatmap_type = heatmap_type
|
||||
self.dataset_name = data_indicator
|
||||
self.shape = shape # [H,W]
|
||||
self.use_gray = use_gray
|
||||
assert transform is not None, 'transform : {:}'.format(transform)
|
||||
self.mean_file = mean_file
|
||||
if mean_file is None:
|
||||
self.mean_data = None
|
||||
warnings.warn('LandmarkDataset initialized with mean_data = None')
|
||||
else:
|
||||
assert osp.isfile(mean_file), '{:} is not a file.'.format(mean_file)
|
||||
self.mean_data = torch.load(mean_file)
|
||||
self.reset()
|
||||
self.cutout = None
|
||||
self.cache_images = cache_images
|
||||
print ('The general dataset initialization done : {:}'.format(self))
|
||||
warnings.simplefilter( 'once' )
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return ('{name}(point-num={NUM_PTS}, shape={shape}, sigma={sigma}, heatmap_type={heatmap_type}, length={length}, cutout={cutout}, dataset={dataset_name}, mean={mean_file})'.format(name=self.__class__.__name__, **self.__dict__))
|
||||
|
||||
|
||||
def set_cutout(self, length):
|
||||
if length is not None and length >= 1:
|
||||
self.cutout = CutOut( int(length) )
|
||||
else: self.cutout = None
|
||||
|
||||
|
||||
def reset(self, num_pts=-1, boxid='default', only_pts=False):
|
||||
self.NUM_PTS = num_pts
|
||||
if only_pts: return
|
||||
self.length = 0
|
||||
self.datas = []
|
||||
self.labels = []
|
||||
self.NormDistances = []
|
||||
self.BOXID = boxid
|
||||
if self.mean_data is None:
|
||||
self.mean_face = None
|
||||
else:
|
||||
self.mean_face = torch.Tensor(self.mean_data[boxid].copy().T)
|
||||
assert (self.mean_face >= -1).all() and (self.mean_face <= 1).all(), 'mean-{:}-face : {:}'.format(boxid, self.mean_face)
|
||||
#assert self.dataset_name is not None, 'The dataset name is None'
|
||||
|
||||
|
||||
def __len__(self):
|
||||
assert len(self.datas) == self.length, 'The length is not correct : {}'.format(self.length)
|
||||
return self.length
|
||||
|
||||
|
||||
def append(self, data, label, distance):
|
||||
assert osp.isfile(data), 'The image path is not a file : {:}'.format(data)
|
||||
self.datas.append( data ) ; self.labels.append( label )
|
||||
self.NormDistances.append( distance )
|
||||
self.length = self.length + 1
|
||||
|
||||
|
||||
def load_list(self, file_lists, num_pts, boxindicator, normalizeL, reset):
|
||||
if reset: self.reset(num_pts, boxindicator)
|
||||
else : assert self.NUM_PTS == num_pts and self.BOXID == boxindicator, 'The number of point is inconsistance : {:} vs {:}'.format(self.NUM_PTS, num_pts)
|
||||
if isinstance(file_lists, str): file_lists = [file_lists]
|
||||
samples = []
|
||||
for idx, file_path in enumerate(file_lists):
|
||||
print (':::: load list {:}/{:} : {:}'.format(idx, len(file_lists), file_path))
|
||||
xdata = torch.load(file_path)
|
||||
if isinstance(xdata, list) : data = xdata # image or video dataset list
|
||||
elif isinstance(xdata, dict): data = xdata['datas'] # multi-view dataset list
|
||||
else: raise ValueError('Invalid Type Error : {:}'.format( type(xdata) ))
|
||||
samples = samples + data
|
||||
# samples is a dict, where the key is the image-path and the value is the annotation
|
||||
# each annotation is a dict, contains 'points' (3,num_pts), and various box
|
||||
print ('GeneralDataset-V2 : {:} samples'.format(len(samples)))
|
||||
|
||||
#for index, annotation in enumerate(samples):
|
||||
for index in tqdm( range( len(samples) ) ):
|
||||
annotation = samples[index]
|
||||
image_path = annotation['current_frame']
|
||||
points, box = annotation['points'], annotation['box-{:}'.format(boxindicator)]
|
||||
label = PointMeta2V(self.NUM_PTS, points, box, image_path, self.dataset_name)
|
||||
if normalizeL is None: normDistance = None
|
||||
else : normDistance = annotation['normalizeL-{:}'.format(normalizeL)]
|
||||
self.append(image_path, label, normDistance)
|
||||
|
||||
assert len(self.datas) == self.length, 'The length and the data is not right {} vs {}'.format(self.length, len(self.datas))
|
||||
assert len(self.labels) == self.length, 'The length and the labels is not right {} vs {}'.format(self.length, len(self.labels))
|
||||
assert len(self.NormDistances) == self.length, 'The length and the NormDistances is not right {} vs {}'.format(self.length, len(self.NormDistance))
|
||||
print ('Load data done for LandmarkDataset, which has {:} images.'.format(self.length))
|
||||
|
||||
|
||||
def __getitem__(self, index):
|
||||
assert index >= 0 and index < self.length, 'Invalid index : {:}'.format(index)
|
||||
if self.cache_images is not None and self.datas[index] in self.cache_images:
|
||||
image = self.cache_images[ self.datas[index] ].clone()
|
||||
else:
|
||||
image = pil_loader(self.datas[index], self.use_gray)
|
||||
target = self.labels[index].copy()
|
||||
return self._process_(image, target, index)
|
||||
|
||||
|
||||
def _process_(self, image, target, index):
|
||||
|
||||
# transform the image and points
|
||||
image, target, theta = self.transform(image, target)
|
||||
(C, H, W), (height, width) = image.size(), self.shape
|
||||
|
||||
# obtain the visiable indicator vector
|
||||
if target.is_none(): nopoints = True
|
||||
else : nopoints = False
|
||||
if index == -1: __path = None
|
||||
else : __path = self.datas[index]
|
||||
if isinstance(theta, list) or isinstance(theta, tuple):
|
||||
affineImage, heatmaps, mask, norm_trans_points, THETA, transpose_theta = [], [], [], [], [], []
|
||||
for _theta in theta:
|
||||
_affineImage, _heatmaps, _mask, _norm_trans_points, _theta, _transpose_theta \
|
||||
= self.__process_affine(image, target, _theta, nopoints, 'P[{:}]@{:}'.format(index, __path))
|
||||
affineImage.append(_affineImage)
|
||||
heatmaps.append(_heatmaps)
|
||||
mask.append(_mask)
|
||||
norm_trans_points.append(_norm_trans_points)
|
||||
THETA.append(_theta)
|
||||
transpose_theta.append(_transpose_theta)
|
||||
affineImage, heatmaps, mask, norm_trans_points, THETA, transpose_theta = \
|
||||
torch.stack(affineImage), torch.stack(heatmaps), torch.stack(mask), torch.stack(norm_trans_points), torch.stack(THETA), torch.stack(transpose_theta)
|
||||
else:
|
||||
affineImage, heatmaps, mask, norm_trans_points, THETA, transpose_theta = self.__process_affine(image, target, theta, nopoints, 'S[{:}]@{:}'.format(index, __path))
|
||||
|
||||
torch_index = torch.IntTensor([index])
|
||||
torch_nopoints = torch.ByteTensor( [ nopoints ] )
|
||||
torch_shape = torch.IntTensor([H,W])
|
||||
|
||||
return affineImage, heatmaps, mask, norm_trans_points, THETA, transpose_theta, torch_index, torch_nopoints, torch_shape
|
||||
|
||||
|
||||
def __process_affine(self, image, target, theta, nopoints, aux_info=None):
|
||||
image, target, theta = image.clone(), target.copy(), theta.clone()
|
||||
(C, H, W), (height, width) = image.size(), self.shape
|
||||
if nopoints: # do not have label
|
||||
norm_trans_points = torch.zeros((3, self.NUM_PTS))
|
||||
heatmaps = torch.zeros((self.NUM_PTS+1, height//self.downsample, width//self.downsample))
|
||||
mask = torch.ones((self.NUM_PTS+1, 1, 1), dtype=torch.uint8)
|
||||
transpose_theta = identity2affine(False)
|
||||
else:
|
||||
norm_trans_points = apply_affine2point(target.get_points(), theta, (H,W))
|
||||
norm_trans_points = apply_boundary(norm_trans_points)
|
||||
real_trans_points = norm_trans_points.clone()
|
||||
real_trans_points[:2, :] = denormalize_points(self.shape, real_trans_points[:2,:])
|
||||
heatmaps, mask = generate_label_map(real_trans_points.numpy(), height//self.downsample, width//self.downsample, self.sigma, self.downsample, nopoints, self.heatmap_type) # H*W*C
|
||||
heatmaps = torch.from_numpy(heatmaps.transpose((2, 0, 1))).type(torch.FloatTensor)
|
||||
mask = torch.from_numpy(mask.transpose((2, 0, 1))).type(torch.ByteTensor)
|
||||
if self.mean_face is None:
|
||||
#warnings.warn('In LandmarkDataset use identity2affine for transpose_theta because self.mean_face is None.')
|
||||
transpose_theta = identity2affine(False)
|
||||
else:
|
||||
if torch.sum(norm_trans_points[2,:] == 1) < 3:
|
||||
warnings.warn('In LandmarkDataset after transformation, no visiable point, using identity instead. Aux: {:}'.format(aux_info))
|
||||
transpose_theta = identity2affine(False)
|
||||
else:
|
||||
transpose_theta = solve2theta(norm_trans_points, self.mean_face.clone())
|
||||
|
||||
affineImage = affine2image(image, theta, self.shape)
|
||||
if self.cutout is not None: affineImage = self.cutout( affineImage )
|
||||
|
||||
return affineImage, heatmaps, mask, norm_trans_points, theta, transpose_theta
|
||||
@@ -1,122 +0,0 @@
|
||||
import os
|
||||
import torch
|
||||
|
||||
from collections import Counter
|
||||
|
||||
|
||||
class Dictionary(object):
|
||||
def __init__(self):
|
||||
self.word2idx = {}
|
||||
self.idx2word = []
|
||||
self.counter = Counter()
|
||||
self.total = 0
|
||||
|
||||
def add_word(self, word):
|
||||
if word not in self.word2idx:
|
||||
self.idx2word.append(word)
|
||||
self.word2idx[word] = len(self.idx2word) - 1
|
||||
token_id = self.word2idx[word]
|
||||
self.counter[token_id] += 1
|
||||
self.total += 1
|
||||
return self.word2idx[word]
|
||||
|
||||
def __len__(self):
|
||||
return len(self.idx2word)
|
||||
|
||||
|
||||
class Corpus(object):
|
||||
def __init__(self, path):
|
||||
self.dictionary = Dictionary()
|
||||
self.train = self.tokenize(os.path.join(path, 'train.txt'))
|
||||
self.valid = self.tokenize(os.path.join(path, 'valid.txt'))
|
||||
self.test = self.tokenize(os.path.join(path, 'test.txt'))
|
||||
|
||||
def tokenize(self, path):
|
||||
"""Tokenizes a text file."""
|
||||
assert os.path.exists(path)
|
||||
# Add words to the dictionary
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
tokens = 0
|
||||
for line in f:
|
||||
words = line.split() + ['<eos>']
|
||||
tokens += len(words)
|
||||
for word in words:
|
||||
self.dictionary.add_word(word)
|
||||
|
||||
# Tokenize file content
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
ids = torch.LongTensor(tokens)
|
||||
token = 0
|
||||
for line in f:
|
||||
words = line.split() + ['<eos>']
|
||||
for word in words:
|
||||
ids[token] = self.dictionary.word2idx[word]
|
||||
token += 1
|
||||
|
||||
return ids
|
||||
|
||||
class SentCorpus(object):
|
||||
def __init__(self, path):
|
||||
self.dictionary = Dictionary()
|
||||
self.train = self.tokenize(os.path.join(path, 'train.txt'))
|
||||
self.valid = self.tokenize(os.path.join(path, 'valid.txt'))
|
||||
self.test = self.tokenize(os.path.join(path, 'test.txt'))
|
||||
|
||||
def tokenize(self, path):
|
||||
"""Tokenizes a text file."""
|
||||
assert os.path.exists(path)
|
||||
# Add words to the dictionary
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
tokens = 0
|
||||
for line in f:
|
||||
words = line.split() + ['<eos>']
|
||||
tokens += len(words)
|
||||
for word in words:
|
||||
self.dictionary.add_word(word)
|
||||
|
||||
# Tokenize file content
|
||||
sents = []
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
if not line:
|
||||
continue
|
||||
words = line.split() + ['<eos>']
|
||||
sent = torch.LongTensor(len(words))
|
||||
for i, word in enumerate(words):
|
||||
sent[i] = self.dictionary.word2idx[word]
|
||||
sents.append(sent)
|
||||
|
||||
return sents
|
||||
|
||||
class BatchSentLoader(object):
|
||||
def __init__(self, sents, batch_size, pad_id=0, cuda=False, volatile=False):
|
||||
self.sents = sents
|
||||
self.batch_size = batch_size
|
||||
self.sort_sents = sorted(sents, key=lambda x: x.size(0))
|
||||
self.cuda = cuda
|
||||
self.volatile = volatile
|
||||
self.pad_id = pad_id
|
||||
|
||||
def __next__(self):
|
||||
if self.idx >= len(self.sort_sents):
|
||||
raise StopIteration
|
||||
|
||||
batch_size = min(self.batch_size, len(self.sort_sents)-self.idx)
|
||||
batch = self.sort_sents[self.idx:self.idx+batch_size]
|
||||
max_len = max([s.size(0) for s in batch])
|
||||
tensor = torch.LongTensor(max_len, batch_size).fill_(self.pad_id)
|
||||
for i in range(len(batch)):
|
||||
s = batch[i]
|
||||
tensor[:s.size(0),i].copy_(s)
|
||||
if self.cuda:
|
||||
tensor = tensor.cuda()
|
||||
|
||||
self.idx += batch_size
|
||||
|
||||
return tensor
|
||||
|
||||
next = __next__
|
||||
|
||||
def __iter__(self):
|
||||
self.idx = 0
|
||||
return self
|
||||
@@ -1,65 +0,0 @@
|
||||
# coding=utf-8
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
|
||||
class MetaBatchSampler(object):
|
||||
|
||||
def __init__(self, labels, classes_per_it, num_samples, iterations):
|
||||
'''
|
||||
Initialize MetaBatchSampler
|
||||
Args:
|
||||
- labels: an iterable containing all the labels for the current dataset
|
||||
samples indexes will be infered from this iterable.
|
||||
- classes_per_it: number of random classes for each iteration
|
||||
- num_samples: number of samples for each iteration for each class (support + query)
|
||||
- iterations: number of iterations (episodes) per epoch
|
||||
'''
|
||||
super(MetaBatchSampler, self).__init__()
|
||||
self.labels = labels.copy()
|
||||
self.classes_per_it = classes_per_it
|
||||
self.sample_per_class = num_samples
|
||||
self.iterations = iterations
|
||||
|
||||
self.classes, self.counts = np.unique(self.labels, return_counts=True)
|
||||
assert len(self.classes) == np.max(self.classes) + 1 and np.min(self.classes) == 0
|
||||
assert classes_per_it < len(self.classes), '{:} vs. {:}'.format(classes_per_it, len(self.classes))
|
||||
self.classes = torch.LongTensor(self.classes)
|
||||
|
||||
# create a matrix, indexes, of dim: classes X max(elements per class)
|
||||
# fill it with nans
|
||||
# for every class c, fill the relative row with the indices samples belonging to c
|
||||
# in numel_per_class we store the number of samples for each class/row
|
||||
self.indexes = { x.item() : [] for x in self.classes }
|
||||
indexes = { x.item() : [] for x in self.classes }
|
||||
|
||||
for idx, label in enumerate(self.labels):
|
||||
indexes[ label.item() ].append( idx )
|
||||
for key, value in indexes.items():
|
||||
self.indexes[ key ] = torch.LongTensor( value )
|
||||
|
||||
|
||||
def __iter__(self):
|
||||
# yield a batch of indexes
|
||||
spc = self.sample_per_class
|
||||
cpi = self.classes_per_it
|
||||
|
||||
for it in range(self.iterations):
|
||||
batch_size = spc * cpi
|
||||
batch = torch.LongTensor(batch_size)
|
||||
assert cpi < len(self.classes), '{:} vs. {:}'.format(cpi, len(self.classes))
|
||||
c_idxs = torch.randperm(len(self.classes))[:cpi]
|
||||
|
||||
for i, cls in enumerate(self.classes[c_idxs]):
|
||||
s = slice(i * spc, (i + 1) * spc)
|
||||
num = self.indexes[ cls.item() ].nelement()
|
||||
assert spc < num, '{:} vs. {:}'.format(spc, num)
|
||||
sample_idxs = torch.randperm( num )[:spc]
|
||||
batch[s] = self.indexes[ cls.item() ][sample_idxs]
|
||||
|
||||
batch = batch[torch.randperm(len(batch))]
|
||||
yield batch
|
||||
|
||||
def __len__(self):
|
||||
# returns the number of iterations (episodes) per epoch
|
||||
return self.iterations
|
||||
26
lib/datasets/SearchDatasetWrap.py
Normal file
26
lib/datasets/SearchDatasetWrap.py
Normal file
@@ -0,0 +1,26 @@
|
||||
import torch, copy, random
|
||||
import torch.utils.data as data
|
||||
|
||||
|
||||
class SearchDataset(data.Dataset):
|
||||
|
||||
def __init__(self, name, data, train_split, valid_split):
|
||||
self.datasetname = name
|
||||
self.data = data
|
||||
self.train_split = train_split.copy()
|
||||
self.valid_split = valid_split.copy()
|
||||
self.length = len(self.train_split)
|
||||
|
||||
def __repr__(self):
|
||||
return ('{name}(name={datasetname}, length={length})'.format(name=self.__class__.__name__, **self.__dict__))
|
||||
|
||||
def __len__(self):
|
||||
return self.length
|
||||
|
||||
def __getitem__(self, index):
|
||||
assert index >= 0 and index < self.length, 'invalid index = {:}'.format(index)
|
||||
train_index = self.train_split[index]
|
||||
valid_index = random.choice( self.valid_split )
|
||||
train_image, train_label = self.data[train_index]
|
||||
valid_image, valid_label = self.data[valid_index]
|
||||
return train_image, train_label, valid_image, valid_label
|
||||
@@ -1,84 +0,0 @@
|
||||
from __future__ import print_function
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
import pickle as pkl
|
||||
import os, cv2, csv, glob
|
||||
import torch
|
||||
import torch.utils.data as data
|
||||
|
||||
|
||||
class TieredImageNet(data.Dataset):
|
||||
|
||||
def __init__(self, root_dir, split, transform=None):
|
||||
self.split = split
|
||||
self.root_dir = root_dir
|
||||
self.transform = transform
|
||||
splits = split.split('-')
|
||||
|
||||
images, labels, last = [], [], 0
|
||||
for split in splits:
|
||||
labels_name = '{:}/{:}_labels.pkl'.format(self.root_dir, split)
|
||||
images_name = '{:}/{:}_images.npz'.format(self.root_dir, split)
|
||||
# decompress images if npz not exits
|
||||
if not os.path.exists(images_name):
|
||||
png_pkl = images_name[:-4] + '_png.pkl'
|
||||
if os.path.exists(png_pkl):
|
||||
decompress(images_name, png_pkl)
|
||||
else:
|
||||
raise ValueError('png_pkl {:} not exits'.format( png_pkl ))
|
||||
assert os.path.exists(images_name) and os.path.exists(labels_name), '{:} & {:}'.format(images_name, labels_name)
|
||||
print ("Prepare {:} done".format(images_name))
|
||||
try:
|
||||
with open(labels_name) as f:
|
||||
data = pkl.load(f)
|
||||
label_specific = data["label_specific"]
|
||||
except:
|
||||
with open(labels_name, 'rb') as f:
|
||||
data = pkl.load(f, encoding='bytes')
|
||||
label_specific = data[b'label_specific']
|
||||
with np.load(images_name, mmap_mode="r", encoding='latin1') as data:
|
||||
image_data = data["images"]
|
||||
images.append( image_data )
|
||||
label_specific = label_specific + last
|
||||
labels.append( label_specific )
|
||||
last = np.max(label_specific) + 1
|
||||
print ("Load {:} done, with image shape = {:}, label shape = {:}, [{:} ~ {:}]".format(images_name, image_data.shape, label_specific.shape, np.min(label_specific), np.max(label_specific)))
|
||||
images, labels = np.concatenate(images), np.concatenate(labels)
|
||||
|
||||
self.images = images
|
||||
self.labels = labels
|
||||
self.n_classes = int( np.max(labels) + 1 )
|
||||
self.dict_index_label = {}
|
||||
for cls in range(self.n_classes):
|
||||
idxs = np.where(labels==cls)[0]
|
||||
self.dict_index_label[cls] = idxs
|
||||
self.length = len(labels)
|
||||
print ("There are {:} images, {:} labels [{:} ~ {:}]".format(images.shape, labels.shape, np.min(labels), np.max(labels)))
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return ('{name}(length={length}, classes={n_classes})'.format(name=self.__class__.__name__, **self.__dict__))
|
||||
|
||||
def __len__(self):
|
||||
return self.length
|
||||
|
||||
def __getitem__(self, index):
|
||||
assert index >= 0 and index < self.length, 'invalid index = {:}'.format(index)
|
||||
image = self.images[index].copy()
|
||||
label = int(self.labels[index])
|
||||
image = Image.fromarray(image[:,:,::-1].astype('uint8'), 'RGB')
|
||||
if self.transform is not None:
|
||||
image = self.transform( image )
|
||||
return image, label
|
||||
|
||||
|
||||
|
||||
|
||||
def decompress(path, output):
|
||||
with open(output, 'rb') as f:
|
||||
array = pkl.load(f, encoding='bytes')
|
||||
images = np.zeros([len(array), 84, 84, 3], dtype=np.uint8)
|
||||
for ii, item in enumerate(array):
|
||||
im = cv2.imdecode(item, 1)
|
||||
images[ii] = im
|
||||
np.savez(path, images=images)
|
||||
@@ -1,7 +1,5 @@
|
||||
##################################################
|
||||
# Copyright (c) Xuanyi Dong [GitHub D-X-Y], 2019 #
|
||||
##################################################
|
||||
from .MetaBatchSampler import MetaBatchSampler
|
||||
from .TieredImageNet import TieredImageNet
|
||||
from .LanguageDataset import Corpus
|
||||
from .get_dataset_with_transform import get_datasets
|
||||
from .SearchDatasetWrap import SearchDataset
|
||||
|
||||
@@ -3,75 +3,181 @@
|
||||
##################################################
|
||||
import os, sys, torch
|
||||
import os.path as osp
|
||||
import numpy as np
|
||||
import torchvision.datasets as dset
|
||||
import torch.backends.cudnn as cudnn
|
||||
import torchvision.transforms as transforms
|
||||
|
||||
from utils import Cutout
|
||||
from .TieredImageNet import TieredImageNet
|
||||
from PIL import Image
|
||||
from .DownsampledImageNet import ImageNet16
|
||||
|
||||
|
||||
Dataset2Class = {'cifar10' : 10,
|
||||
'cifar100': 100,
|
||||
'tiered' : -1,
|
||||
'imagenet-1k-s':1000,
|
||||
'imagenet-1k' : 1000,
|
||||
'imagenet-100': 100}
|
||||
'ImageNet16' : 1000,
|
||||
'ImageNet16-150': 150,
|
||||
'ImageNet16-120': 120,
|
||||
'ImageNet16-200': 200}
|
||||
|
||||
|
||||
class CUTOUT(object):
|
||||
|
||||
def __init__(self, length):
|
||||
self.length = length
|
||||
|
||||
def __repr__(self):
|
||||
return ('{name}(length={length})'.format(name=self.__class__.__name__, **self.__dict__))
|
||||
|
||||
def __call__(self, img):
|
||||
h, w = img.size(1), img.size(2)
|
||||
mask = np.ones((h, w), np.float32)
|
||||
y = np.random.randint(h)
|
||||
x = np.random.randint(w)
|
||||
|
||||
y1 = np.clip(y - self.length // 2, 0, h)
|
||||
y2 = np.clip(y + self.length // 2, 0, h)
|
||||
x1 = np.clip(x - self.length // 2, 0, w)
|
||||
x2 = np.clip(x + self.length // 2, 0, w)
|
||||
|
||||
mask[y1: y2, x1: x2] = 0.
|
||||
mask = torch.from_numpy(mask)
|
||||
mask = mask.expand_as(img)
|
||||
img *= mask
|
||||
return img
|
||||
|
||||
|
||||
imagenet_pca = {
|
||||
'eigval': np.asarray([0.2175, 0.0188, 0.0045]),
|
||||
'eigvec': np.asarray([
|
||||
[-0.5675, 0.7192, 0.4009],
|
||||
[-0.5808, -0.0045, -0.8140],
|
||||
[-0.5836, -0.6948, 0.4203],
|
||||
])
|
||||
}
|
||||
|
||||
|
||||
class Lighting(object):
|
||||
def __init__(self, alphastd,
|
||||
eigval=imagenet_pca['eigval'],
|
||||
eigvec=imagenet_pca['eigvec']):
|
||||
self.alphastd = alphastd
|
||||
assert eigval.shape == (3,)
|
||||
assert eigvec.shape == (3, 3)
|
||||
self.eigval = eigval
|
||||
self.eigvec = eigvec
|
||||
|
||||
def __call__(self, img):
|
||||
if self.alphastd == 0.:
|
||||
return img
|
||||
rnd = np.random.randn(3) * self.alphastd
|
||||
rnd = rnd.astype('float32')
|
||||
v = rnd
|
||||
old_dtype = np.asarray(img).dtype
|
||||
v = v * self.eigval
|
||||
v = v.reshape((3, 1))
|
||||
inc = np.dot(self.eigvec, v).reshape((3,))
|
||||
img = np.add(img, inc)
|
||||
if old_dtype == np.uint8:
|
||||
img = np.clip(img, 0, 255)
|
||||
img = Image.fromarray(img.astype(old_dtype), 'RGB')
|
||||
return img
|
||||
|
||||
def __repr__(self):
|
||||
return self.__class__.__name__ + '()'
|
||||
|
||||
|
||||
def get_datasets(name, root, cutout):
|
||||
|
||||
# Mean + Std
|
||||
if name == 'cifar10':
|
||||
mean = [x / 255 for x in [125.3, 123.0, 113.9]]
|
||||
std = [x / 255 for x in [63.0, 62.1, 66.7]]
|
||||
std = [x / 255 for x in [63.0, 62.1, 66.7]]
|
||||
elif name == 'cifar100':
|
||||
mean = [x / 255 for x in [129.3, 124.1, 112.4]]
|
||||
std = [x / 255 for x in [68.2, 65.4, 70.4]]
|
||||
elif name == 'tiered':
|
||||
std = [x / 255 for x in [68.2, 65.4, 70.4]]
|
||||
elif name.startswith('imagenet-1k'):
|
||||
mean, std = [0.485, 0.456, 0.406], [0.229, 0.224, 0.225]
|
||||
elif name == 'imagenet-1k' or name == 'imagenet-100':
|
||||
mean, std = [0.485, 0.456, 0.406], [0.229, 0.224, 0.225]
|
||||
else: raise TypeError("Unknow dataset : {:}".format(name))
|
||||
|
||||
elif name.startswith('ImageNet16'):
|
||||
mean = [x / 255 for x in [122.68, 116.66, 104.01]]
|
||||
std = [x / 255 for x in [63.22, 61.26 , 65.09]]
|
||||
else:
|
||||
raise TypeError("Unknow dataset : {:}".format(name))
|
||||
|
||||
# Data Argumentation
|
||||
if name == 'cifar10' or name == 'cifar100':
|
||||
lists = [transforms.RandomHorizontalFlip(), transforms.RandomCrop(32, padding=4), transforms.ToTensor(),
|
||||
transforms.Normalize(mean, std)]
|
||||
if cutout > 0 : lists += [Cutout(cutout)]
|
||||
lists = [transforms.RandomHorizontalFlip(), transforms.RandomCrop(32, padding=4), transforms.ToTensor(), transforms.Normalize(mean, std)]
|
||||
if cutout > 0 : lists += [CUTOUT(cutout)]
|
||||
train_transform = transforms.Compose(lists)
|
||||
test_transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize(mean, std)])
|
||||
xshape = (1, 3, 32, 32)
|
||||
elif name.startswith('ImageNet16'):
|
||||
lists = [transforms.RandomHorizontalFlip(), transforms.RandomCrop(16, padding=2), transforms.ToTensor(), transforms.Normalize(mean, std)]
|
||||
if cutout > 0 : lists += [CUTOUT(cutout)]
|
||||
train_transform = transforms.Compose(lists)
|
||||
test_transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize(mean, std)])
|
||||
xshape = (1, 3, 16, 16)
|
||||
elif name == 'tiered':
|
||||
lists = [transforms.RandomHorizontalFlip(), transforms.RandomCrop(80, padding=4), transforms.ToTensor(), transforms.Normalize(mean, std)]
|
||||
if cutout > 0 : lists += [Cutout(cutout)]
|
||||
if cutout > 0 : lists += [CUTOUT(cutout)]
|
||||
train_transform = transforms.Compose(lists)
|
||||
test_transform = transforms.Compose([transforms.CenterCrop(80), transforms.ToTensor(), transforms.Normalize(mean, std)])
|
||||
elif name == 'imagenet-1k' or name == 'imagenet-100':
|
||||
xshape = (1, 3, 32, 32)
|
||||
elif name.startswith('imagenet-1k'):
|
||||
normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
|
||||
train_transform = transforms.Compose([
|
||||
transforms.RandomResizedCrop(224),
|
||||
transforms.RandomHorizontalFlip(),
|
||||
transforms.ColorJitter(
|
||||
if name == 'imagenet-1k':
|
||||
xlists = [transforms.RandomResizedCrop(224)]
|
||||
xlists.append(
|
||||
transforms.ColorJitter(
|
||||
brightness=0.4,
|
||||
contrast=0.4,
|
||||
saturation=0.4,
|
||||
hue=0.2),
|
||||
transforms.ToTensor(),
|
||||
normalize,
|
||||
])
|
||||
test_transform = transforms.Compose([transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), normalize])
|
||||
else: raise TypeError("Unknow dataset : {:}".format(name))
|
||||
hue=0.2))
|
||||
xlists.append( Lighting(0.1))
|
||||
elif name == 'imagenet-1k-s':
|
||||
xlists = [transforms.RandomResizedCrop(224, scale=(0.2, 1.0))]
|
||||
else: raise ValueError('invalid name : {:}'.format(name))
|
||||
xlists.append( transforms.RandomHorizontalFlip(p=0.5) )
|
||||
xlists.append( transforms.ToTensor() )
|
||||
xlists.append( normalize )
|
||||
train_transform = transforms.Compose(xlists)
|
||||
test_transform = transforms.Compose([transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), normalize])
|
||||
xshape = (1, 3, 224, 224)
|
||||
else:
|
||||
raise TypeError("Unknow dataset : {:}".format(name))
|
||||
|
||||
if name == 'cifar10':
|
||||
train_data = dset.CIFAR10 (root, train=True , transform=train_transform, download=True)
|
||||
test_data = dset.CIFAR10 (root, train=False, transform=test_transform , download=True)
|
||||
assert len(train_data) == 50000 and len(test_data) == 10000
|
||||
elif name == 'cifar100':
|
||||
train_data = dset.CIFAR100(root, train=True , transform=train_transform, download=True)
|
||||
test_data = dset.CIFAR100(root, train=False, transform=test_transform , download=True)
|
||||
elif name == 'imagenet-1k' or name == 'imagenet-100':
|
||||
assert len(train_data) == 50000 and len(test_data) == 10000
|
||||
elif name.startswith('imagenet-1k'):
|
||||
train_data = dset.ImageFolder(osp.join(root, 'train'), train_transform)
|
||||
test_data = dset.ImageFolder(osp.join(root, 'val'), test_transform)
|
||||
assert len(train_data) == 1281167 and len(test_data) == 50000, 'invalid number of images : {:} & {:} vs {:} & {:}'.format(len(train_data), len(test_data), 1281167, 50000)
|
||||
elif name == 'ImageNet16':
|
||||
train_data = ImageNet16(root, True , train_transform)
|
||||
test_data = ImageNet16(root, False, test_transform)
|
||||
assert len(train_data) == 1281167 and len(test_data) == 50000
|
||||
elif name == 'ImageNet16-120':
|
||||
train_data = ImageNet16(root, True , train_transform, 120)
|
||||
test_data = ImageNet16(root, False, test_transform , 120)
|
||||
assert len(train_data) == 151700 and len(test_data) == 6000
|
||||
elif name == 'ImageNet16-150':
|
||||
train_data = ImageNet16(root, True , train_transform, 150)
|
||||
test_data = ImageNet16(root, False, test_transform , 150)
|
||||
assert len(train_data) == 190272 and len(test_data) == 7500
|
||||
elif name == 'ImageNet16-200':
|
||||
train_data = ImageNet16(root, True , train_transform, 200)
|
||||
test_data = ImageNet16(root, False, test_transform , 200)
|
||||
assert len(train_data) == 254775 and len(test_data) == 10000
|
||||
else: raise TypeError("Unknow dataset : {:}".format(name))
|
||||
|
||||
class_num = Dataset2Class[name]
|
||||
return train_data, test_data, class_num
|
||||
return train_data, test_data, xshape, class_num
|
||||
|
||||
#if __name__ == '__main__':
|
||||
# train_data, test_data, xshape, class_num = dataset = get_datasets('cifar10', '/data02/dongxuanyi/.torch/cifar.python/', -1)
|
||||
# import pdb; pdb.set_trace()
|
||||
|
||||
1
lib/datasets/landmark_utils/__init__.py
Normal file
1
lib/datasets/landmark_utils/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .point_meta import PointMeta2V, apply_affine2point, apply_boundary
|
||||
116
lib/datasets/landmark_utils/point_meta.py
Normal file
116
lib/datasets/landmark_utils/point_meta.py
Normal file
@@ -0,0 +1,116 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
# All rights reserved.
|
||||
#
|
||||
# This source code is licensed under the license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
#
|
||||
import copy, math, torch, numpy as np
|
||||
from xvision import normalize_points
|
||||
from xvision import denormalize_points
|
||||
|
||||
|
||||
class PointMeta():
|
||||
# points : 3 x num_pts (x, y, oculusion)
|
||||
# image_size: original [width, height]
|
||||
def __init__(self, num_point, points, box, image_path, dataset_name):
|
||||
|
||||
self.num_point = num_point
|
||||
if box is not None:
|
||||
assert (isinstance(box, tuple) or isinstance(box, list)) and len(box) == 4
|
||||
self.box = torch.Tensor(box)
|
||||
else: self.box = None
|
||||
if points is None:
|
||||
self.points = points
|
||||
else:
|
||||
assert len(points.shape) == 2 and points.shape[0] == 3 and points.shape[1] == self.num_point, 'The shape of point is not right : {}'.format( points )
|
||||
self.points = torch.Tensor(points.copy())
|
||||
self.image_path = image_path
|
||||
self.datasets = dataset_name
|
||||
|
||||
def __repr__(self):
|
||||
if self.box is None: boxstr = 'None'
|
||||
else : boxstr = 'box=[{:.1f}, {:.1f}, {:.1f}, {:.1f}]'.format(*self.box.tolist())
|
||||
return ('{name}(points={num_point}, '.format(name=self.__class__.__name__, **self.__dict__) + boxstr + ')')
|
||||
|
||||
def get_box(self, return_diagonal=False):
|
||||
if self.box is None: return None
|
||||
if return_diagonal == False:
|
||||
return self.box.clone()
|
||||
else:
|
||||
W = (self.box[2]-self.box[0]).item()
|
||||
H = (self.box[3]-self.box[1]).item()
|
||||
return math.sqrt(H*H+W*W)
|
||||
|
||||
def get_points(self, ignore_indicator=False):
|
||||
if ignore_indicator: last = 2
|
||||
else : last = 3
|
||||
if self.points is not None: return self.points.clone()[:last, :]
|
||||
else : return torch.zeros((last, self.num_point))
|
||||
|
||||
def is_none(self):
|
||||
#assert self.box is not None, 'The box should not be None'
|
||||
return self.points is None
|
||||
#if self.box is None: return True
|
||||
#else : return self.points is None
|
||||
|
||||
def copy(self):
|
||||
return copy.deepcopy(self)
|
||||
|
||||
def visiable_pts_num(self):
|
||||
with torch.no_grad():
|
||||
ans = self.points[2,:] > 0
|
||||
ans = torch.sum(ans)
|
||||
ans = ans.item()
|
||||
return ans
|
||||
|
||||
def special_fun(self, indicator):
|
||||
if indicator == '68to49': # For 300W or 300VW, convert the default 68 points to 49 points.
|
||||
assert self.num_point == 68, 'num-point must be 68 vs. {:}'.format(self.num_point)
|
||||
self.num_point = 49
|
||||
out = torch.ones((68), dtype=torch.uint8)
|
||||
out[[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,60,64]] = 0
|
||||
if self.points is not None: self.points = self.points.clone()[:, out]
|
||||
else:
|
||||
raise ValueError('Invalid indicator : {:}'.format( indicator ))
|
||||
|
||||
def apply_horizontal_flip(self):
|
||||
#self.points[0, :] = width - self.points[0, :] - 1
|
||||
# Mugsy spefic or Synthetic
|
||||
if self.datasets.startswith('HandsyROT'):
|
||||
ori = np.array(list(range(0, 42)))
|
||||
pos = np.array(list(range(21,42)) + list(range(0,21)))
|
||||
self.points[:, pos] = self.points[:, ori]
|
||||
elif self.datasets.startswith('face68'):
|
||||
ori = np.array(list(range(0, 68)))
|
||||
pos = np.array([17,16,15,14,13,12,11,10, 9, 8,7,6,5,4,3,2,1, 27,26,25,24,23,22,21,20,19,18, 28,29,30,31, 36,35,34,33,32, 46,45,44,43,48,47, 40,39,38,37,42,41, 55,54,53,52,51,50,49,60,59,58,57,56,65,64,63,62,61,68,67,66])-1
|
||||
self.points[:, ori] = self.points[:, pos]
|
||||
else:
|
||||
raise ValueError('Does not support {:}'.format(self.datasets))
|
||||
|
||||
|
||||
|
||||
# shape = (H,W)
|
||||
def apply_affine2point(points, theta, shape):
|
||||
assert points.size(0) == 3, 'invalid points shape : {:}'.format(points.size())
|
||||
with torch.no_grad():
|
||||
ok_points = points[2,:] == 1
|
||||
assert torch.sum(ok_points).item() > 0, 'there is no visiable point'
|
||||
points[:2,:] = normalize_points(shape, points[:2,:])
|
||||
|
||||
norm_trans_points = ok_points.unsqueeze(0).repeat(3, 1).float()
|
||||
|
||||
trans_points, ___ = torch.gesv(points[:, ok_points], theta)
|
||||
|
||||
norm_trans_points[:, ok_points] = trans_points
|
||||
|
||||
return norm_trans_points
|
||||
|
||||
|
||||
|
||||
def apply_boundary(norm_trans_points):
|
||||
with torch.no_grad():
|
||||
norm_trans_points = norm_trans_points.clone()
|
||||
oks = torch.stack((norm_trans_points[0]>-1, norm_trans_points[0]<1, norm_trans_points[1]>-1, norm_trans_points[1]<1, norm_trans_points[2]>0))
|
||||
oks = torch.sum(oks, dim=0) == 5
|
||||
norm_trans_points[2, :] = oks
|
||||
return norm_trans_points
|
||||
@@ -1,10 +0,0 @@
|
||||
import os, sys, torch
|
||||
|
||||
from LanguageDataset import SentCorpus, BatchSentLoader
|
||||
|
||||
if __name__ == '__main__':
|
||||
path = '../../data/data/penn'
|
||||
corpus = SentCorpus( path )
|
||||
loader = BatchSentLoader(corpus.test, 10)
|
||||
for i, d in enumerate(loader):
|
||||
print('{:} :: {:}'.format(i, d.size()))
|
||||
@@ -1,33 +0,0 @@
|
||||
import os, sys, torch
|
||||
import torchvision.transforms as transforms
|
||||
|
||||
from TieredImageNet import TieredImageNet
|
||||
from MetaBatchSampler import MetaBatchSampler
|
||||
|
||||
root_dir = os.environ['TORCH_HOME'] + '/tiered-imagenet'
|
||||
print ('root : {:}'.format(root_dir))
|
||||
means, stds = [0.485, 0.456, 0.406], [0.229, 0.224, 0.225]
|
||||
|
||||
lists = [transforms.RandomHorizontalFlip(), transforms.RandomCrop(84, padding=8), transforms.ToTensor(), transforms.Normalize(means, stds)]
|
||||
transform = transforms.Compose(lists)
|
||||
|
||||
dataset = TieredImageNet(root_dir, 'val-test', transform)
|
||||
image, label = dataset[111]
|
||||
print ('image shape = {:}, label = {:}'.format(image.size(), label))
|
||||
print ('image : min = {:}, max = {:} ||| label : {:}'.format(image.min(), image.max(), label))
|
||||
|
||||
|
||||
sampler = MetaBatchSampler(dataset.labels, 250, 100, 10)
|
||||
|
||||
dataloader = torch.utils.data.DataLoader(dataset, batch_sampler=sampler)
|
||||
|
||||
print ('the length of dataset : {:}'.format( len(dataset) ))
|
||||
print ('the length of loader : {:}'.format( len(dataloader) ))
|
||||
|
||||
for images, labels in dataloader:
|
||||
print ('images : {:}'.format( images.size() ))
|
||||
print ('labels : {:}'.format( labels.size() ))
|
||||
for i in range(3):
|
||||
print ('image-value-[{:}] : {:} ~ {:}, mean={:}, std={:}'.format(i, images[:,i].min(), images[:,i].max(), images[:,i].mean(), images[:,i].std()))
|
||||
|
||||
print('-----')
|
||||
14
lib/datasets/test_utils.py
Normal file
14
lib/datasets/test_utils.py
Normal file
@@ -0,0 +1,14 @@
|
||||
def test_imagenet_data(imagenet):
|
||||
total_length = len(imagenet)
|
||||
assert total_length == 1281166 or total_length == 50000, 'The length of ImageNet is wrong : {}'.format(total_length)
|
||||
map_id = {}
|
||||
for index in range(total_length):
|
||||
path, target = imagenet.imgs[index]
|
||||
folder, image_name = os.path.split(path)
|
||||
_, folder = os.path.split(folder)
|
||||
if folder not in map_id:
|
||||
map_id[folder] = target
|
||||
else:
|
||||
assert map_id[folder] == target, 'Class : {} is not {}'.format(folder, target)
|
||||
assert image_name.find(folder) == 0, '{} is wrong.'.format(path)
|
||||
print ('Check ImageNet Dataset OK')
|
||||
7
lib/log_utils/__init__.py
Normal file
7
lib/log_utils/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
##################################################
|
||||
# Copyright (c) Xuanyi Dong [GitHub D-X-Y], 2019 #
|
||||
##################################################
|
||||
from .logger import Logger
|
||||
from .print_logger import PrintLogger
|
||||
from .meter import AverageMeter
|
||||
from .time_utils import time_for_file, time_string, time_string_short, time_print, convert_size2str, convert_secs2time
|
||||
140
lib/log_utils/logger.py
Normal file
140
lib/log_utils/logger.py
Normal file
@@ -0,0 +1,140 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
# All rights reserved.
|
||||
#
|
||||
# This source code is licensed under the license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
#
|
||||
from pathlib import Path
|
||||
import importlib, warnings
|
||||
import os, sys, time, numpy as np
|
||||
if sys.version_info.major == 2: # Python 2.x
|
||||
from StringIO import StringIO as BIO
|
||||
else: # Python 3.x
|
||||
from io import BytesIO as BIO
|
||||
|
||||
if importlib.util.find_spec('tensorflow'):
|
||||
import tensorflow as tf
|
||||
|
||||
|
||||
class Logger(object):
|
||||
|
||||
def __init__(self, log_dir, seed, create_model_dir=True, use_tf=False):
|
||||
"""Create a summary writer logging to log_dir."""
|
||||
self.seed = int(seed)
|
||||
self.log_dir = Path(log_dir)
|
||||
self.model_dir = Path(log_dir) / 'checkpoint'
|
||||
self.log_dir.mkdir (parents=True, exist_ok=True)
|
||||
if create_model_dir:
|
||||
self.model_dir.mkdir(parents=True, exist_ok=True)
|
||||
#self.meta_dir.mkdir(mode=0o775, parents=True, exist_ok=True)
|
||||
|
||||
self.use_tf = bool(use_tf)
|
||||
self.tensorboard_dir = self.log_dir / ('tensorboard-{:}'.format(time.strftime( '%d-%h', time.gmtime(time.time()) )))
|
||||
#self.tensorboard_dir = self.log_dir / ('tensorboard-{:}'.format(time.strftime( '%d-%h-at-%H:%M:%S', time.gmtime(time.time()) )))
|
||||
self.logger_path = self.log_dir / 'seed-{:}-T-{:}.log'.format(self.seed, time.strftime('%d-%h-at-%H-%M-%S', time.gmtime(time.time())))
|
||||
self.logger_file = open(self.logger_path, 'w')
|
||||
|
||||
if self.use_tf:
|
||||
self.tensorboard_dir.mkdir(mode=0o775, parents=True, exist_ok=True)
|
||||
self.writer = tf.summary.FileWriter(str(self.tensorboard_dir))
|
||||
else:
|
||||
self.writer = None
|
||||
|
||||
def __repr__(self):
|
||||
return ('{name}(dir={log_dir}, use-tf={use_tf}, writer={writer})'.format(name=self.__class__.__name__, **self.__dict__))
|
||||
|
||||
def path(self, mode):
|
||||
valids = ('model', 'best', 'info', 'log')
|
||||
if mode == 'model': return self.model_dir / 'seed-{:}-basic.pth'.format(self.seed)
|
||||
elif mode == 'best' : return self.model_dir / 'seed-{:}-best.pth'.format(self.seed)
|
||||
elif mode == 'info' : return self.log_dir / 'seed-{:}-last-info.pth'.format(self.seed)
|
||||
elif mode == 'log' : return self.log_dir
|
||||
else: raise TypeError('Unknow mode = {:}, valid modes = {:}'.format(mode, valids))
|
||||
|
||||
def extract_log(self):
|
||||
return self.logger_file
|
||||
|
||||
def close(self):
|
||||
self.logger_file.close()
|
||||
if self.writer is not None:
|
||||
self.writer.close()
|
||||
|
||||
def log(self, string, save=True, stdout=False):
|
||||
if stdout:
|
||||
sys.stdout.write(string); sys.stdout.flush()
|
||||
else:
|
||||
print (string)
|
||||
if save:
|
||||
self.logger_file.write('{:}\n'.format(string))
|
||||
self.logger_file.flush()
|
||||
|
||||
def scalar_summary(self, tags, values, step):
|
||||
"""Log a scalar variable."""
|
||||
if not self.use_tf:
|
||||
warnings.warn('Do set use-tensorflow installed but call scalar_summary')
|
||||
else:
|
||||
assert isinstance(tags, list) == isinstance(values, list), 'Type : {:} vs {:}'.format(type(tags), type(values))
|
||||
if not isinstance(tags, list):
|
||||
tags, values = [tags], [values]
|
||||
for tag, value in zip(tags, values):
|
||||
summary = tf.Summary(value=[tf.Summary.Value(tag=tag, simple_value=value)])
|
||||
self.writer.add_summary(summary, step)
|
||||
self.writer.flush()
|
||||
|
||||
def image_summary(self, tag, images, step):
|
||||
"""Log a list of images."""
|
||||
import scipy
|
||||
if not self.use_tf:
|
||||
warnings.warn('Do set use-tensorflow installed but call scalar_summary')
|
||||
return
|
||||
|
||||
img_summaries = []
|
||||
for i, img in enumerate(images):
|
||||
# Write the image to a string
|
||||
try:
|
||||
s = StringIO()
|
||||
except:
|
||||
s = BytesIO()
|
||||
scipy.misc.toimage(img).save(s, format="png")
|
||||
|
||||
# Create an Image object
|
||||
img_sum = tf.Summary.Image(encoded_image_string=s.getvalue(),
|
||||
height=img.shape[0],
|
||||
width=img.shape[1])
|
||||
# Create a Summary value
|
||||
img_summaries.append(tf.Summary.Value(tag='{}/{}'.format(tag, i), image=img_sum))
|
||||
|
||||
# Create and write Summary
|
||||
summary = tf.Summary(value=img_summaries)
|
||||
self.writer.add_summary(summary, step)
|
||||
self.writer.flush()
|
||||
|
||||
def histo_summary(self, tag, values, step, bins=1000):
|
||||
"""Log a histogram of the tensor of values."""
|
||||
if not self.use_tf: raise ValueError('Do not have tensorflow')
|
||||
import tensorflow as tf
|
||||
|
||||
# Create a histogram using numpy
|
||||
counts, bin_edges = np.histogram(values, bins=bins)
|
||||
|
||||
# Fill the fields of the histogram proto
|
||||
hist = tf.HistogramProto()
|
||||
hist.min = float(np.min(values))
|
||||
hist.max = float(np.max(values))
|
||||
hist.num = int(np.prod(values.shape))
|
||||
hist.sum = float(np.sum(values))
|
||||
hist.sum_squares = float(np.sum(values**2))
|
||||
|
||||
# Drop the start of the first bin
|
||||
bin_edges = bin_edges[1:]
|
||||
|
||||
# Add bin edges and counts
|
||||
for edge in bin_edges:
|
||||
hist.bucket_limit.append(edge)
|
||||
for c in counts:
|
||||
hist.bucket.append(c)
|
||||
|
||||
# Create and write Summary
|
||||
summary = tf.Summary(value=[tf.Summary.Value(tag=tag, histo=hist)])
|
||||
self.writer.add_summary(summary, step)
|
||||
self.writer.flush()
|
||||
@@ -1,26 +1,29 @@
|
||||
##################################################
|
||||
# Copyright (c) Xuanyi Dong [GitHub D-X-Y], 2019 #
|
||||
##################################################
|
||||
import os, sys, time
|
||||
import time, sys
|
||||
import numpy as np
|
||||
import random
|
||||
|
||||
class AverageMeter(object):
|
||||
"""Computes and stores the average and current value"""
|
||||
def __init__(self):
|
||||
|
||||
class AverageMeter(object):
|
||||
"""Computes and stores the average and current value"""
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
|
||||
def reset(self):
|
||||
self.val = 0
|
||||
self.avg = 0
|
||||
self.sum = 0
|
||||
self.count = 0
|
||||
|
||||
def update(self, val, n=1):
|
||||
self.val = val
|
||||
self.sum += val * n
|
||||
self.val = 0.0
|
||||
self.avg = 0.0
|
||||
self.sum = 0.0
|
||||
self.count = 0.0
|
||||
|
||||
def update(self, val, n=1):
|
||||
self.val = val
|
||||
self.sum += val * n
|
||||
self.count += n
|
||||
self.avg = self.sum / self.count
|
||||
self.avg = self.sum / self.count
|
||||
|
||||
def __repr__(self):
|
||||
return ('{name}(val={val}, avg={avg}, count={count})'.format(name=self.__class__.__name__, **self.__dict__))
|
||||
|
||||
|
||||
class RecorderMeter(object):
|
||||
@@ -29,12 +32,11 @@ class RecorderMeter(object):
|
||||
self.reset(total_epoch)
|
||||
|
||||
def reset(self, total_epoch):
|
||||
assert total_epoch > 0
|
||||
assert total_epoch > 0, 'total_epoch should be greater than 0 vs {:}'.format(total_epoch)
|
||||
self.total_epoch = total_epoch
|
||||
self.current_epoch = 0
|
||||
self.epoch_losses = np.zeros((self.total_epoch, 2), dtype=np.float32) # [epoch, train/val]
|
||||
self.epoch_losses = self.epoch_losses - 1
|
||||
|
||||
self.epoch_accuracy= np.zeros((self.total_epoch, 2), dtype=np.float32) # [epoch, train/val]
|
||||
self.epoch_accuracy= self.epoch_accuracy
|
||||
|
||||
@@ -98,43 +100,3 @@ class RecorderMeter(object):
|
||||
fig.savefig(save_path, dpi=dpi, bbox_inches='tight')
|
||||
print ('---- save figure {} into {}'.format(title, save_path))
|
||||
plt.close(fig)
|
||||
|
||||
def print_log(print_string, log):
|
||||
print ("{:}".format(print_string))
|
||||
if log is not None:
|
||||
log.write('{}\n'.format(print_string))
|
||||
log.flush()
|
||||
|
||||
def time_file_str():
|
||||
ISOTIMEFORMAT='%Y-%m-%d'
|
||||
string = '{}'.format(time.strftime( ISOTIMEFORMAT, time.gmtime(time.time()) ))
|
||||
return string + '-{}'.format(random.randint(1, 10000))
|
||||
|
||||
def time_string():
|
||||
ISOTIMEFORMAT='%Y-%m-%d-%X'
|
||||
string = '[{}]'.format(time.strftime( ISOTIMEFORMAT, time.gmtime(time.time()) ))
|
||||
return string
|
||||
|
||||
def convert_secs2time(epoch_time, return_str=False):
|
||||
need_hour = int(epoch_time / 3600)
|
||||
need_mins = int((epoch_time - 3600*need_hour) / 60)
|
||||
need_secs = int(epoch_time - 3600*need_hour - 60*need_mins)
|
||||
if return_str == False:
|
||||
return need_hour, need_mins, need_secs
|
||||
else:
|
||||
return '[Need: {:02d}:{:02d}:{:02d}]'.format(need_hour, need_mins, need_secs)
|
||||
|
||||
def test_imagenet_data(imagenet):
|
||||
total_length = len(imagenet)
|
||||
assert total_length == 1281166 or total_length == 50000, 'The length of ImageNet is wrong : {}'.format(total_length)
|
||||
map_id = {}
|
||||
for index in range(total_length):
|
||||
path, target = imagenet.imgs[index]
|
||||
folder, image_name = os.path.split(path)
|
||||
_, folder = os.path.split(folder)
|
||||
if folder not in map_id:
|
||||
map_id[folder] = target
|
||||
else:
|
||||
assert map_id[folder] == target, 'Class : {} is not {}'.format(folder, target)
|
||||
assert image_name.find(folder) == 0, '{} is wrong.'.format(path)
|
||||
print ('Check ImageNet Dataset OK')
|
||||
18
lib/log_utils/print_logger.py
Normal file
18
lib/log_utils/print_logger.py
Normal file
@@ -0,0 +1,18 @@
|
||||
##################################################
|
||||
# Copyright (c) Xuanyi Dong [GitHub D-X-Y], 2019 #
|
||||
##################################################
|
||||
import importlib, warnings
|
||||
import os, sys, time, numpy as np
|
||||
|
||||
|
||||
class PrintLogger(object):
|
||||
|
||||
def __init__(self):
|
||||
"""Create a summary writer logging to log_dir."""
|
||||
self.name = 'PrintLogger'
|
||||
|
||||
def log(self, string):
|
||||
print (string)
|
||||
|
||||
def close(self):
|
||||
print ('-'*30 + ' close printer ' + '-'*30)
|
||||
52
lib/log_utils/time_utils.py
Normal file
52
lib/log_utils/time_utils.py
Normal file
@@ -0,0 +1,52 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
# All rights reserved.
|
||||
#
|
||||
# This source code is licensed under the license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
#
|
||||
import time, sys
|
||||
import numpy as np
|
||||
|
||||
def time_for_file():
|
||||
ISOTIMEFORMAT='%d-%h-at-%H-%M-%S'
|
||||
return '{}'.format(time.strftime( ISOTIMEFORMAT, time.gmtime(time.time()) ))
|
||||
|
||||
def time_string():
|
||||
ISOTIMEFORMAT='%Y-%m-%d %X'
|
||||
string = '[{}]'.format(time.strftime( ISOTIMEFORMAT, time.gmtime(time.time()) ))
|
||||
return string
|
||||
|
||||
def time_string_short():
|
||||
ISOTIMEFORMAT='%Y%m%d'
|
||||
string = '{}'.format(time.strftime( ISOTIMEFORMAT, time.gmtime(time.time()) ))
|
||||
return string
|
||||
|
||||
def time_print(string, is_print=True):
|
||||
if (is_print):
|
||||
print('{} : {}'.format(time_string(), string))
|
||||
|
||||
def convert_size2str(torch_size):
|
||||
dims = len(torch_size)
|
||||
string = '['
|
||||
for idim in range(dims):
|
||||
string = string + ' {}'.format(torch_size[idim])
|
||||
return string + ']'
|
||||
|
||||
def convert_secs2time(epoch_time, return_str=False):
|
||||
need_hour = int(epoch_time / 3600)
|
||||
need_mins = int((epoch_time - 3600*need_hour) / 60)
|
||||
need_secs = int(epoch_time - 3600*need_hour - 60*need_mins)
|
||||
if return_str:
|
||||
str = '[{:02d}:{:02d}:{:02d}]'.format(need_hour, need_mins, need_secs)
|
||||
return str
|
||||
else:
|
||||
return need_hour, need_mins, need_secs
|
||||
|
||||
def print_log(print_string, log):
|
||||
#if isinstance(log, Logger): log.log('{:}'.format(print_string))
|
||||
if hasattr(log, 'log'): log.log('{:}'.format(print_string))
|
||||
else:
|
||||
print("{:}".format(print_string))
|
||||
if log is not None:
|
||||
log.write('{:}\n'.format(print_string))
|
||||
log.flush()
|
||||
105
lib/models/CifarDenseNet.py
Normal file
105
lib/models/CifarDenseNet.py
Normal file
@@ -0,0 +1,105 @@
|
||||
##################################################
|
||||
# Copyright (c) Xuanyi Dong [GitHub D-X-Y], 2019 #
|
||||
##################################################
|
||||
import math, torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from .initialization import initialize_resnet
|
||||
|
||||
|
||||
class Bottleneck(nn.Module):
|
||||
def __init__(self, nChannels, growthRate):
|
||||
super(Bottleneck, self).__init__()
|
||||
interChannels = 4*growthRate
|
||||
self.bn1 = nn.BatchNorm2d(nChannels)
|
||||
self.conv1 = nn.Conv2d(nChannels, interChannels, kernel_size=1, bias=False)
|
||||
self.bn2 = nn.BatchNorm2d(interChannels)
|
||||
self.conv2 = nn.Conv2d(interChannels, growthRate, kernel_size=3, padding=1, bias=False)
|
||||
|
||||
def forward(self, x):
|
||||
out = self.conv1(F.relu(self.bn1(x)))
|
||||
out = self.conv2(F.relu(self.bn2(out)))
|
||||
out = torch.cat((x, out), 1)
|
||||
return out
|
||||
|
||||
|
||||
class SingleLayer(nn.Module):
|
||||
def __init__(self, nChannels, growthRate):
|
||||
super(SingleLayer, self).__init__()
|
||||
self.bn1 = nn.BatchNorm2d(nChannels)
|
||||
self.conv1 = nn.Conv2d(nChannels, growthRate, kernel_size=3, padding=1, bias=False)
|
||||
|
||||
def forward(self, x):
|
||||
out = self.conv1(F.relu(self.bn1(x)))
|
||||
out = torch.cat((x, out), 1)
|
||||
return out
|
||||
|
||||
|
||||
class Transition(nn.Module):
|
||||
def __init__(self, nChannels, nOutChannels):
|
||||
super(Transition, self).__init__()
|
||||
self.bn1 = nn.BatchNorm2d(nChannels)
|
||||
self.conv1 = nn.Conv2d(nChannels, nOutChannels, kernel_size=1, bias=False)
|
||||
|
||||
def forward(self, x):
|
||||
out = self.conv1(F.relu(self.bn1(x)))
|
||||
out = F.avg_pool2d(out, 2)
|
||||
return out
|
||||
|
||||
|
||||
class DenseNet(nn.Module):
|
||||
def __init__(self, growthRate, depth, reduction, nClasses, bottleneck):
|
||||
super(DenseNet, self).__init__()
|
||||
|
||||
if bottleneck: nDenseBlocks = int( (depth-4) / 6 )
|
||||
else : nDenseBlocks = int( (depth-4) / 3 )
|
||||
|
||||
self.message = 'CifarDenseNet : block : {:}, depth : {:}, reduction : {:}, growth-rate = {:}, class = {:}'.format('bottleneck' if bottleneck else 'basic', depth, reduction, growthRate, nClasses)
|
||||
|
||||
nChannels = 2*growthRate
|
||||
self.conv1 = nn.Conv2d(3, nChannels, kernel_size=3, padding=1, bias=False)
|
||||
|
||||
self.dense1 = self._make_dense(nChannels, growthRate, nDenseBlocks, bottleneck)
|
||||
nChannels += nDenseBlocks*growthRate
|
||||
nOutChannels = int(math.floor(nChannels*reduction))
|
||||
self.trans1 = Transition(nChannels, nOutChannels)
|
||||
|
||||
nChannels = nOutChannels
|
||||
self.dense2 = self._make_dense(nChannels, growthRate, nDenseBlocks, bottleneck)
|
||||
nChannels += nDenseBlocks*growthRate
|
||||
nOutChannels = int(math.floor(nChannels*reduction))
|
||||
self.trans2 = Transition(nChannels, nOutChannels)
|
||||
|
||||
nChannels = nOutChannels
|
||||
self.dense3 = self._make_dense(nChannels, growthRate, nDenseBlocks, bottleneck)
|
||||
nChannels += nDenseBlocks*growthRate
|
||||
|
||||
self.act = nn.Sequential(
|
||||
nn.BatchNorm2d(nChannels), nn.ReLU(inplace=True),
|
||||
nn.AvgPool2d(8))
|
||||
self.fc = nn.Linear(nChannels, nClasses)
|
||||
|
||||
self.apply(initialize_resnet)
|
||||
|
||||
def get_message(self):
|
||||
return self.message
|
||||
|
||||
def _make_dense(self, nChannels, growthRate, nDenseBlocks, bottleneck):
|
||||
layers = []
|
||||
for i in range(int(nDenseBlocks)):
|
||||
if bottleneck:
|
||||
layers.append(Bottleneck(nChannels, growthRate))
|
||||
else:
|
||||
layers.append(SingleLayer(nChannels, growthRate))
|
||||
nChannels += growthRate
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
def forward(self, inputs):
|
||||
out = self.conv1( inputs )
|
||||
out = self.trans1(self.dense1(out))
|
||||
out = self.trans2(self.dense2(out))
|
||||
out = self.dense3(out)
|
||||
features = self.act(out)
|
||||
features = features.view(features.size(0), -1)
|
||||
out = self.fc(features)
|
||||
return features, out
|
||||
160
lib/models/CifarResNet.py
Normal file
160
lib/models/CifarResNet.py
Normal file
@@ -0,0 +1,160 @@
|
||||
##################################################
|
||||
# Copyright (c) Xuanyi Dong [GitHub D-X-Y], 2019 #
|
||||
##################################################
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from .initialization import initialize_resnet
|
||||
from .SharedUtils import additive_func
|
||||
|
||||
|
||||
class Downsample(nn.Module):
|
||||
|
||||
def __init__(self, nIn, nOut, stride):
|
||||
super(Downsample, self).__init__()
|
||||
assert stride == 2 and nOut == 2*nIn, 'stride:{} IO:{},{}'.format(stride, nIn, nOut)
|
||||
self.in_dim = nIn
|
||||
self.out_dim = nOut
|
||||
self.avg = nn.AvgPool2d(kernel_size=2, stride=2, padding=0)
|
||||
self.conv = nn.Conv2d(nIn, nOut, kernel_size=1, stride=1, padding=0, bias=False)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.avg(x)
|
||||
out = self.conv(x)
|
||||
return out
|
||||
|
||||
|
||||
class ConvBNReLU(nn.Module):
|
||||
|
||||
def __init__(self, nIn, nOut, kernel, stride, padding, bias, relu):
|
||||
super(ConvBNReLU, self).__init__()
|
||||
self.conv = nn.Conv2d(nIn, nOut, kernel_size=kernel, stride=stride, padding=padding, bias=bias)
|
||||
self.bn = nn.BatchNorm2d(nOut)
|
||||
if relu: self.relu = nn.ReLU(inplace=True)
|
||||
else : self.relu = None
|
||||
self.out_dim = nOut
|
||||
self.num_conv = 1
|
||||
|
||||
def forward(self, x):
|
||||
conv = self.conv( x )
|
||||
bn = self.bn( conv )
|
||||
if self.relu: return self.relu( bn )
|
||||
else : return bn
|
||||
|
||||
|
||||
class ResNetBasicblock(nn.Module):
|
||||
expansion = 1
|
||||
def __init__(self, inplanes, planes, stride):
|
||||
super(ResNetBasicblock, self).__init__()
|
||||
assert stride == 1 or stride == 2, 'invalid stride {:}'.format(stride)
|
||||
self.conv_a = ConvBNReLU(inplanes, planes, 3, stride, 1, False, True)
|
||||
self.conv_b = ConvBNReLU( planes, planes, 3, 1, 1, False, False)
|
||||
if stride == 2:
|
||||
self.downsample = Downsample(inplanes, planes, stride)
|
||||
elif inplanes != planes:
|
||||
self.downsample = ConvBNReLU(inplanes, planes, 1, 1, 0, False, False)
|
||||
else:
|
||||
self.downsample = None
|
||||
self.out_dim = planes
|
||||
self.num_conv = 2
|
||||
|
||||
def forward(self, inputs):
|
||||
|
||||
basicblock = self.conv_a(inputs)
|
||||
basicblock = self.conv_b(basicblock)
|
||||
|
||||
if self.downsample is not None:
|
||||
residual = self.downsample(inputs)
|
||||
else:
|
||||
residual = inputs
|
||||
out = additive_func(residual, basicblock)
|
||||
return F.relu(out, inplace=True)
|
||||
|
||||
|
||||
|
||||
class ResNetBottleneck(nn.Module):
|
||||
expansion = 4
|
||||
def __init__(self, inplanes, planes, stride):
|
||||
super(ResNetBottleneck, self).__init__()
|
||||
assert stride == 1 or stride == 2, 'invalid stride {:}'.format(stride)
|
||||
self.conv_1x1 = ConvBNReLU(inplanes, planes, 1, 1, 0, False, True)
|
||||
self.conv_3x3 = ConvBNReLU( planes, planes, 3, stride, 1, False, True)
|
||||
self.conv_1x4 = ConvBNReLU(planes, planes*self.expansion, 1, 1, 0, False, False)
|
||||
if stride == 2:
|
||||
self.downsample = Downsample(inplanes, planes*self.expansion, stride)
|
||||
elif inplanes != planes*self.expansion:
|
||||
self.downsample = ConvBNReLU(inplanes, planes*self.expansion, 1, 1, 0, False, False)
|
||||
else:
|
||||
self.downsample = None
|
||||
self.out_dim = planes * self.expansion
|
||||
self.num_conv = 3
|
||||
|
||||
def forward(self, inputs):
|
||||
|
||||
bottleneck = self.conv_1x1(inputs)
|
||||
bottleneck = self.conv_3x3(bottleneck)
|
||||
bottleneck = self.conv_1x4(bottleneck)
|
||||
|
||||
if self.downsample is not None:
|
||||
residual = self.downsample(inputs)
|
||||
else:
|
||||
residual = inputs
|
||||
out = additive_func(residual, bottleneck)
|
||||
return F.relu(out, inplace=True)
|
||||
|
||||
|
||||
|
||||
class CifarResNet(nn.Module):
|
||||
|
||||
def __init__(self, block_name, depth, num_classes, zero_init_residual):
|
||||
super(CifarResNet, self).__init__()
|
||||
|
||||
#Model type specifies number of layers for CIFAR-10 and CIFAR-100 model
|
||||
if block_name == 'ResNetBasicblock':
|
||||
block = ResNetBasicblock
|
||||
assert (depth - 2) % 6 == 0, 'depth should be one of 20, 32, 44, 56, 110'
|
||||
layer_blocks = (depth - 2) // 6
|
||||
elif block_name == 'ResNetBottleneck':
|
||||
block = ResNetBottleneck
|
||||
assert (depth - 2) % 9 == 0, 'depth should be one of 164'
|
||||
layer_blocks = (depth - 2) // 9
|
||||
else:
|
||||
raise ValueError('invalid block : {:}'.format(block_name))
|
||||
|
||||
self.message = 'CifarResNet : Block : {:}, Depth : {:}, Layers for each block : {:}'.format(block_name, depth, layer_blocks)
|
||||
self.num_classes = num_classes
|
||||
self.channels = [16]
|
||||
self.layers = nn.ModuleList( [ ConvBNReLU(3, 16, 3, 1, 1, False, True) ] )
|
||||
for stage in range(3):
|
||||
for iL in range(layer_blocks):
|
||||
iC = self.channels[-1]
|
||||
planes = 16 * (2**stage)
|
||||
stride = 2 if stage > 0 and iL == 0 else 1
|
||||
module = block(iC, planes, stride)
|
||||
self.channels.append( module.out_dim )
|
||||
self.layers.append ( module )
|
||||
self.message += "\nstage={:}, ilayer={:02d}/{:02d}, block={:03d}, iC={:3d}, oC={:3d}, stride={:}".format(stage, iL, layer_blocks, len(self.layers)-1, iC, module.out_dim, stride)
|
||||
|
||||
self.avgpool = nn.AvgPool2d(8)
|
||||
self.classifier = nn.Linear(module.out_dim, num_classes)
|
||||
assert sum(x.num_conv for x in self.layers) + 1 == depth, 'invalid depth check {:} vs {:}'.format(sum(x.num_conv for x in self.layers)+1, depth)
|
||||
|
||||
self.apply(initialize_resnet)
|
||||
if zero_init_residual:
|
||||
for m in self.modules():
|
||||
if isinstance(m, ResNetBasicblock):
|
||||
nn.init.constant_(m.conv_b.bn.weight, 0)
|
||||
elif isinstance(m, ResNetBottleneck):
|
||||
nn.init.constant_(m.conv_1x4.bn.weight, 0)
|
||||
|
||||
def get_message(self):
|
||||
return self.message
|
||||
|
||||
def forward(self, inputs):
|
||||
x = inputs
|
||||
for i, layer in enumerate(self.layers):
|
||||
x = layer( x )
|
||||
features = self.avgpool(x)
|
||||
features = features.view(features.size(0), -1)
|
||||
logits = self.classifier(features)
|
||||
return features, logits
|
||||
94
lib/models/CifarWideResNet.py
Normal file
94
lib/models/CifarWideResNet.py
Normal file
@@ -0,0 +1,94 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from .initialization import initialize_resnet
|
||||
|
||||
|
||||
class WideBasicblock(nn.Module):
|
||||
def __init__(self, inplanes, planes, stride, dropout=False):
|
||||
super(WideBasicblock, self).__init__()
|
||||
|
||||
self.bn_a = nn.BatchNorm2d(inplanes)
|
||||
self.conv_a = nn.Conv2d(inplanes, planes, kernel_size=3, stride=stride, padding=1, bias=False)
|
||||
|
||||
self.bn_b = nn.BatchNorm2d(planes)
|
||||
if dropout:
|
||||
self.dropout = nn.Dropout2d(p=0.5, inplace=True)
|
||||
else:
|
||||
self.dropout = None
|
||||
self.conv_b = nn.Conv2d(planes, planes, kernel_size=3, stride=1, padding=1, bias=False)
|
||||
|
||||
if inplanes != planes:
|
||||
self.downsample = nn.Conv2d(inplanes, planes, kernel_size=1, stride=stride, padding=0, bias=False)
|
||||
else:
|
||||
self.downsample = None
|
||||
|
||||
def forward(self, x):
|
||||
|
||||
basicblock = self.bn_a(x)
|
||||
basicblock = F.relu(basicblock)
|
||||
basicblock = self.conv_a(basicblock)
|
||||
|
||||
basicblock = self.bn_b(basicblock)
|
||||
basicblock = F.relu(basicblock)
|
||||
if self.dropout is not None:
|
||||
basicblock = self.dropout(basicblock)
|
||||
basicblock = self.conv_b(basicblock)
|
||||
|
||||
if self.downsample is not None:
|
||||
x = self.downsample(x)
|
||||
|
||||
return x + basicblock
|
||||
|
||||
|
||||
class CifarWideResNet(nn.Module):
|
||||
"""
|
||||
ResNet optimized for the Cifar dataset, as specified in
|
||||
https://arxiv.org/abs/1512.03385.pdf
|
||||
"""
|
||||
def __init__(self, depth, widen_factor, num_classes, dropout):
|
||||
super(CifarWideResNet, self).__init__()
|
||||
|
||||
#Model type specifies number of layers for CIFAR-10 and CIFAR-100 model
|
||||
assert (depth - 4) % 6 == 0, 'depth should be one of 20, 32, 44, 56, 110'
|
||||
layer_blocks = (depth - 4) // 6
|
||||
print ('CifarPreResNet : Depth : {} , Layers for each block : {}'.format(depth, layer_blocks))
|
||||
|
||||
self.num_classes = num_classes
|
||||
self.dropout = dropout
|
||||
self.conv_3x3 = nn.Conv2d(3, 16, kernel_size=3, stride=1, padding=1, bias=False)
|
||||
|
||||
self.message = 'Wide ResNet : depth={:}, widen_factor={:}, class={:}'.format(depth, widen_factor, num_classes)
|
||||
self.inplanes = 16
|
||||
self.stage_1 = self._make_layer(WideBasicblock, 16*widen_factor, layer_blocks, 1)
|
||||
self.stage_2 = self._make_layer(WideBasicblock, 32*widen_factor, layer_blocks, 2)
|
||||
self.stage_3 = self._make_layer(WideBasicblock, 64*widen_factor, layer_blocks, 2)
|
||||
self.lastact = nn.Sequential(nn.BatchNorm2d(64*widen_factor), nn.ReLU(inplace=True))
|
||||
self.avgpool = nn.AvgPool2d(8)
|
||||
self.classifier = nn.Linear(64*widen_factor, num_classes)
|
||||
|
||||
self.apply(initialize_resnet)
|
||||
|
||||
def get_message(self):
|
||||
return self.message
|
||||
|
||||
def _make_layer(self, block, planes, blocks, stride):
|
||||
|
||||
layers = []
|
||||
layers.append(block(self.inplanes, planes, stride, self.dropout))
|
||||
self.inplanes = planes
|
||||
for i in range(1, blocks):
|
||||
layers.append(block(self.inplanes, planes, 1, self.dropout))
|
||||
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.conv_3x3(x)
|
||||
x = self.stage_1(x)
|
||||
x = self.stage_2(x)
|
||||
x = self.stage_3(x)
|
||||
x = self.lastact(x)
|
||||
x = self.avgpool(x)
|
||||
features = x.view(x.size(0), -1)
|
||||
outs = self.classifier(features)
|
||||
return features, outs
|
||||
172
lib/models/ImagenetResNet.py
Normal file
172
lib/models/ImagenetResNet.py
Normal file
@@ -0,0 +1,172 @@
|
||||
# Deep Residual Learning for Image Recognition, CVPR 2016
|
||||
import torch.nn as nn
|
||||
from .initialization import initialize_resnet
|
||||
|
||||
def conv3x3(in_planes, out_planes, stride=1, groups=1):
|
||||
return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride, padding=1, groups=groups, bias=False)
|
||||
|
||||
|
||||
def conv1x1(in_planes, out_planes, stride=1):
|
||||
return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False)
|
||||
|
||||
|
||||
class BasicBlock(nn.Module):
|
||||
expansion = 1
|
||||
|
||||
def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1, base_width=64):
|
||||
super(BasicBlock, self).__init__()
|
||||
if groups != 1 or base_width != 64:
|
||||
raise ValueError('BasicBlock only supports groups=1 and base_width=64')
|
||||
# Both self.conv1 and self.downsample layers downsample the input when stride != 1
|
||||
self.conv1 = conv3x3(inplanes, planes, stride)
|
||||
self.bn1 = nn.BatchNorm2d(planes)
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
self.conv2 = conv3x3(planes, planes)
|
||||
self.bn2 = nn.BatchNorm2d(planes)
|
||||
self.downsample = downsample
|
||||
self.stride = stride
|
||||
|
||||
def forward(self, x):
|
||||
identity = x
|
||||
|
||||
out = self.conv1(x)
|
||||
out = self.bn1(out)
|
||||
out = self.relu(out)
|
||||
|
||||
out = self.conv2(out)
|
||||
out = self.bn2(out)
|
||||
|
||||
if self.downsample is not None:
|
||||
identity = self.downsample(x)
|
||||
|
||||
out += identity
|
||||
out = self.relu(out)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
class Bottleneck(nn.Module):
|
||||
expansion = 4
|
||||
|
||||
def __init__(self, inplanes, planes, stride=1, downsample=None, groups=1, base_width=64):
|
||||
super(Bottleneck, self).__init__()
|
||||
width = int(planes * (base_width / 64.)) * groups
|
||||
# Both self.conv2 and self.downsample layers downsample the input when stride != 1
|
||||
self.conv1 = conv1x1(inplanes, width)
|
||||
self.bn1 = nn.BatchNorm2d(width)
|
||||
self.conv2 = conv3x3(width, width, stride, groups)
|
||||
self.bn2 = nn.BatchNorm2d(width)
|
||||
self.conv3 = conv1x1(width, planes * self.expansion)
|
||||
self.bn3 = nn.BatchNorm2d(planes * self.expansion)
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
self.downsample = downsample
|
||||
self.stride = stride
|
||||
|
||||
def forward(self, x):
|
||||
identity = x
|
||||
|
||||
out = self.conv1(x)
|
||||
out = self.bn1(out)
|
||||
out = self.relu(out)
|
||||
|
||||
out = self.conv2(out)
|
||||
out = self.bn2(out)
|
||||
out = self.relu(out)
|
||||
|
||||
out = self.conv3(out)
|
||||
out = self.bn3(out)
|
||||
|
||||
if self.downsample is not None:
|
||||
identity = self.downsample(x)
|
||||
|
||||
out += identity
|
||||
out = self.relu(out)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
class ResNet(nn.Module):
|
||||
|
||||
def __init__(self, block_name, layers, deep_stem, num_classes, zero_init_residual, groups, width_per_group):
|
||||
super(ResNet, self).__init__()
|
||||
|
||||
#planes = [int(width_per_group * groups * 2 ** i) for i in range(4)]
|
||||
if block_name == 'BasicBlock' : block= BasicBlock
|
||||
elif block_name == 'Bottleneck': block= Bottleneck
|
||||
else : raise ValueError('invalid block-name : {:}'.format(block_name))
|
||||
|
||||
if not deep_stem:
|
||||
self.conv = nn.Sequential(
|
||||
nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False),
|
||||
nn.BatchNorm2d(64), nn.ReLU(inplace=True))
|
||||
else:
|
||||
self.conv = nn.Sequential(
|
||||
nn.Conv2d( 3, 32, kernel_size=3, stride=2, padding=1, bias=False),
|
||||
nn.BatchNorm2d(32), nn.ReLU(inplace=True),
|
||||
nn.Conv2d(32, 32, kernel_size=3, stride=1, padding=1, bias=False),
|
||||
nn.BatchNorm2d(32), nn.ReLU(inplace=True),
|
||||
nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1, bias=False),
|
||||
nn.BatchNorm2d(64), nn.ReLU(inplace=True))
|
||||
self.inplanes = 64
|
||||
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
|
||||
self.layer1 = self._make_layer(block, 64 , layers[0], stride=1, groups=groups, base_width=width_per_group)
|
||||
self.layer2 = self._make_layer(block, 128, layers[1], stride=2, groups=groups, base_width=width_per_group)
|
||||
self.layer3 = self._make_layer(block, 256, layers[2], stride=2, groups=groups, base_width=width_per_group)
|
||||
self.layer4 = self._make_layer(block, 512, layers[3], stride=2, groups=groups, base_width=width_per_group)
|
||||
self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
|
||||
self.fc = nn.Linear(512 * block.expansion, num_classes)
|
||||
self.message = 'block = {:}, layers = {:}, deep_stem = {:}, num_classes = {:}'.format(block, layers, deep_stem, num_classes)
|
||||
|
||||
self.apply( initialize_resnet )
|
||||
|
||||
# Zero-initialize the last BN in each residual branch,
|
||||
# so that the residual branch starts with zeros, and each residual block behaves like an identity.
|
||||
# This improves the model by 0.2~0.3% according to https://arxiv.org/abs/1706.02677
|
||||
if zero_init_residual:
|
||||
for m in self.modules():
|
||||
if isinstance(m, Bottleneck):
|
||||
nn.init.constant_(m.bn3.weight, 0)
|
||||
elif isinstance(m, BasicBlock):
|
||||
nn.init.constant_(m.bn2.weight, 0)
|
||||
|
||||
def _make_layer(self, block, planes, blocks, stride, groups, base_width):
|
||||
downsample = None
|
||||
if stride != 1 or self.inplanes != planes * block.expansion:
|
||||
if stride == 2:
|
||||
downsample = nn.Sequential(
|
||||
nn.AvgPool2d(kernel_size=2, stride=2, padding=0),
|
||||
conv1x1(self.inplanes, planes * block.expansion, 1),
|
||||
nn.BatchNorm2d(planes * block.expansion),
|
||||
)
|
||||
elif stride == 1:
|
||||
downsample = nn.Sequential(
|
||||
conv1x1(self.inplanes, planes * block.expansion, stride),
|
||||
nn.BatchNorm2d(planes * block.expansion),
|
||||
)
|
||||
else: raise ValueError('invalid stride [{:}] for downsample'.format(stride))
|
||||
|
||||
layers = []
|
||||
layers.append(block(self.inplanes, planes, stride, downsample, groups, base_width))
|
||||
self.inplanes = planes * block.expansion
|
||||
for _ in range(1, blocks):
|
||||
layers.append(block(self.inplanes, planes, 1, None, groups, base_width))
|
||||
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
def get_message(self):
|
||||
return self.message
|
||||
|
||||
def forward(self, x):
|
||||
x = self.conv(x)
|
||||
x = self.maxpool(x)
|
||||
|
||||
x = self.layer1(x)
|
||||
x = self.layer2(x)
|
||||
x = self.layer3(x)
|
||||
x = self.layer4(x)
|
||||
|
||||
features = self.avgpool(x)
|
||||
features = features.view(features.size(0), -1)
|
||||
logits = self.fc(features)
|
||||
|
||||
return features, logits
|
||||
101
lib/models/MobileNet.py
Normal file
101
lib/models/MobileNet.py
Normal file
@@ -0,0 +1,101 @@
|
||||
# MobileNetV2: Inverted Residuals and Linear Bottlenecks, CVPR 2018
|
||||
from torch import nn
|
||||
from .initialization import initialize_resnet
|
||||
|
||||
|
||||
class ConvBNReLU(nn.Module):
|
||||
def __init__(self, in_planes, out_planes, kernel_size=3, stride=1, groups=1):
|
||||
super(ConvBNReLU, self).__init__()
|
||||
padding = (kernel_size - 1) // 2
|
||||
self.conv = nn.Conv2d(in_planes, out_planes, kernel_size, stride, padding, groups=groups, bias=False)
|
||||
self.bn = nn.BatchNorm2d(out_planes)
|
||||
self.relu = nn.ReLU6(inplace=True)
|
||||
|
||||
def forward(self, x):
|
||||
out = self.conv( x )
|
||||
out = self.bn ( out )
|
||||
out = self.relu( out )
|
||||
return out
|
||||
|
||||
|
||||
class InvertedResidual(nn.Module):
|
||||
def __init__(self, inp, oup, stride, expand_ratio):
|
||||
super(InvertedResidual, self).__init__()
|
||||
self.stride = stride
|
||||
assert stride in [1, 2]
|
||||
|
||||
hidden_dim = int(round(inp * expand_ratio))
|
||||
self.use_res_connect = self.stride == 1 and inp == oup
|
||||
|
||||
layers = []
|
||||
if expand_ratio != 1:
|
||||
# pw
|
||||
layers.append(ConvBNReLU(inp, hidden_dim, kernel_size=1))
|
||||
layers.extend([
|
||||
# dw
|
||||
ConvBNReLU(hidden_dim, hidden_dim, stride=stride, groups=hidden_dim),
|
||||
# pw-linear
|
||||
nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
|
||||
nn.BatchNorm2d(oup),
|
||||
])
|
||||
self.conv = nn.Sequential(*layers)
|
||||
|
||||
def forward(self, x):
|
||||
if self.use_res_connect:
|
||||
return x + self.conv(x)
|
||||
else:
|
||||
return self.conv(x)
|
||||
|
||||
|
||||
class MobileNetV2(nn.Module):
|
||||
def __init__(self, num_classes, width_mult, input_channel, last_channel, block_name, dropout):
|
||||
super(MobileNetV2, self).__init__()
|
||||
if block_name == 'InvertedResidual':
|
||||
block = InvertedResidual
|
||||
else:
|
||||
raise ValueError('invalid block name : {:}'.format(block_name))
|
||||
inverted_residual_setting = [
|
||||
# t, c, n, s
|
||||
[1, 16 , 1, 1],
|
||||
[6, 24 , 2, 2],
|
||||
[6, 32 , 3, 2],
|
||||
[6, 64 , 4, 2],
|
||||
[6, 96 , 3, 1],
|
||||
[6, 160, 3, 2],
|
||||
[6, 320, 1, 1],
|
||||
]
|
||||
|
||||
# building first layer
|
||||
input_channel = int(input_channel * width_mult)
|
||||
self.last_channel = int(last_channel * max(1.0, width_mult))
|
||||
features = [ConvBNReLU(3, input_channel, stride=2)]
|
||||
# building inverted residual blocks
|
||||
for t, c, n, s in inverted_residual_setting:
|
||||
output_channel = int(c * width_mult)
|
||||
for i in range(n):
|
||||
stride = s if i == 0 else 1
|
||||
features.append(block(input_channel, output_channel, stride, expand_ratio=t))
|
||||
input_channel = output_channel
|
||||
# building last several layers
|
||||
features.append(ConvBNReLU(input_channel, self.last_channel, kernel_size=1))
|
||||
# make it nn.Sequential
|
||||
self.features = nn.Sequential(*features)
|
||||
|
||||
# building classifier
|
||||
self.classifier = nn.Sequential(
|
||||
nn.Dropout(dropout),
|
||||
nn.Linear(self.last_channel, num_classes),
|
||||
)
|
||||
self.message = 'MobileNetV2 : width_mult={:}, in-C={:}, last-C={:}, block={:}, dropout={:}'.format(width_mult, input_channel, last_channel, block_name, dropout)
|
||||
|
||||
# weight initialization
|
||||
self.apply( initialize_resnet )
|
||||
|
||||
def get_message(self):
|
||||
return self.message
|
||||
|
||||
def forward(self, inputs):
|
||||
features = self.features(inputs)
|
||||
vectors = features.mean([2, 3])
|
||||
predicts = self.classifier(vectors)
|
||||
return features, predicts
|
||||
31
lib/models/SharedUtils.py
Normal file
31
lib/models/SharedUtils.py
Normal file
@@ -0,0 +1,31 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
|
||||
def additive_func(A, B):
|
||||
assert A.dim() == B.dim() and A.size(0) == B.size(0), '{:} vs {:}'.format(A.size(), B.size())
|
||||
C = min(A.size(1), B.size(1))
|
||||
if A.size(1) == B.size(1):
|
||||
return A + B
|
||||
elif A.size(1) < B.size(1):
|
||||
out = B.clone()
|
||||
out[:,:C] += A
|
||||
return out
|
||||
else:
|
||||
out = A.clone()
|
||||
out[:,:C] += B
|
||||
return out
|
||||
|
||||
|
||||
def change_key(key, value):
|
||||
def func(m):
|
||||
if hasattr(m, key):
|
||||
setattr(m, key, value)
|
||||
return func
|
||||
|
||||
|
||||
def parse_channel_info(xstring):
|
||||
blocks = xstring.split(' ')
|
||||
blocks = [x.split('-') for x in blocks]
|
||||
blocks = [[int(_) for _ in x] for x in blocks]
|
||||
return blocks
|
||||
133
lib/models/ShuffleNetV2.py
Normal file
133
lib/models/ShuffleNetV2.py
Normal file
@@ -0,0 +1,133 @@
|
||||
import functools
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
__all__ = ['ShuffleNetV2']
|
||||
|
||||
|
||||
def channel_shuffle(x, groups):
|
||||
batchsize, num_channels, height, width = x.data.size()
|
||||
channels_per_group = num_channels // groups
|
||||
|
||||
# reshape
|
||||
x = x.view(batchsize, groups, channels_per_group, height, width)
|
||||
|
||||
x = torch.transpose(x, 1, 2).contiguous()
|
||||
|
||||
# flatten
|
||||
x = x.view(batchsize, -1, height, width)
|
||||
|
||||
return x
|
||||
|
||||
|
||||
class InvertedResidual(nn.Module):
|
||||
def __init__(self, inp, oup, stride):
|
||||
super(InvertedResidual, self).__init__()
|
||||
|
||||
if not (1 <= stride <= 3):
|
||||
raise ValueError('illegal stride value')
|
||||
self.stride = stride
|
||||
|
||||
branch_features = oup // 2
|
||||
assert (self.stride != 1) or (inp == branch_features << 1)
|
||||
|
||||
pw_conv11 = functools.partial(nn.Conv2d, kernel_size=1, stride=1, padding=0, bias=False)
|
||||
dw_conv33 = functools.partial(self.depthwise_conv, kernel_size=3, stride=self.stride, padding=1)
|
||||
|
||||
if self.stride > 1:
|
||||
self.branch1 = nn.Sequential(
|
||||
dw_conv33(inp, inp),
|
||||
nn.BatchNorm2d(inp),
|
||||
pw_conv11(inp, branch_features),
|
||||
nn.BatchNorm2d(branch_features),
|
||||
nn.ReLU(inplace=True),
|
||||
)
|
||||
|
||||
self.branch2 = nn.Sequential(
|
||||
pw_conv11(inp if (self.stride > 1) else branch_features, branch_features),
|
||||
nn.BatchNorm2d(branch_features),
|
||||
nn.ReLU(inplace=True),
|
||||
dw_conv33(branch_features, branch_features),
|
||||
nn.BatchNorm2d(branch_features),
|
||||
pw_conv11(branch_features, branch_features),
|
||||
nn.BatchNorm2d(branch_features),
|
||||
nn.ReLU(inplace=True),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def depthwise_conv(i, o, kernel_size, stride=1, padding=0, bias=False):
|
||||
return nn.Conv2d(i, o, kernel_size, stride, padding, bias=bias, groups=i)
|
||||
|
||||
def forward(self, x):
|
||||
if self.stride == 1:
|
||||
x1, x2 = x.chunk(2, dim=1)
|
||||
out = torch.cat((x1, self.branch2(x2)), dim=1)
|
||||
else:
|
||||
out = torch.cat((self.branch1(x), self.branch2(x)), dim=1)
|
||||
|
||||
out = channel_shuffle(out, 2)
|
||||
return out
|
||||
|
||||
|
||||
class ShuffleNetV2(nn.Module):
|
||||
def __init__(self, num_classes, stages):
|
||||
super(ShuffleNetV2, self).__init__()
|
||||
|
||||
self.stage_out_channels = stages
|
||||
assert len(stages) == 5, 'invalid stages : {:}'.format(stages)
|
||||
self.message = 'stages: ' + ' '.join([str(x) for x in stages])
|
||||
|
||||
input_channels = 3
|
||||
output_channels = self.stage_out_channels[0]
|
||||
self.conv1 = nn.Sequential(
|
||||
nn.Conv2d(input_channels, output_channels, 3, 2, 1, bias=False),
|
||||
nn.BatchNorm2d(output_channels),
|
||||
nn.ReLU(inplace=True),
|
||||
)
|
||||
input_channels = output_channels
|
||||
|
||||
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
|
||||
|
||||
stage_names = ['stage{:}'.format(i) for i in [2, 3, 4]]
|
||||
stage_repeats = [4, 8, 4]
|
||||
for name, repeats, output_channels in zip(
|
||||
stage_names, stage_repeats, self.stage_out_channels[1:]):
|
||||
seq = [InvertedResidual(input_channels, output_channels, 2)]
|
||||
for i in range(repeats - 1):
|
||||
seq.append(InvertedResidual(output_channels, output_channels, 1))
|
||||
setattr(self, name, nn.Sequential(*seq))
|
||||
input_channels = output_channels
|
||||
|
||||
output_channels = self.stage_out_channels[-1]
|
||||
self.conv5 = nn.Sequential(
|
||||
nn.Conv2d(input_channels, output_channels, 1, 1, 0, bias=False),
|
||||
nn.BatchNorm2d(output_channels),
|
||||
nn.ReLU(inplace=True),
|
||||
)
|
||||
|
||||
self.fc = nn.Linear(output_channels, num_classes)
|
||||
|
||||
def get_message(self):
|
||||
return self.message
|
||||
|
||||
def forward(self, inputs):
|
||||
x = self.conv1( inputs )
|
||||
x = self.maxpool(x)
|
||||
x = self.stage2(x)
|
||||
x = self.stage3(x)
|
||||
x = self.stage4(x)
|
||||
x = self.conv5(x)
|
||||
features = x.mean([2, 3]) # globalpool
|
||||
predicts = self.fc(features)
|
||||
return features, predicts
|
||||
|
||||
#@staticmethod
|
||||
#def _getStages(mult):
|
||||
# stages = {
|
||||
# '0.5': [24, 48, 96 , 192, 1024],
|
||||
# '1.0': [24, 116, 232, 464, 1024],
|
||||
# '1.5': [24, 176, 352, 704, 1024],
|
||||
# '2.0': [24, 244, 488, 976, 2048],
|
||||
# }
|
||||
# return stages[str(mult)]
|
||||
123
lib/models/__init__.py
Normal file
123
lib/models/__init__.py
Normal file
@@ -0,0 +1,123 @@
|
||||
##################################################
|
||||
# Copyright (c) Xuanyi Dong [GitHub D-X-Y], 2019 #
|
||||
##################################################
|
||||
import torch
|
||||
from os import path as osp
|
||||
# our modules
|
||||
from config_utils import dict2config
|
||||
from .SharedUtils import change_key
|
||||
from .clone_weights import init_from_model
|
||||
|
||||
|
||||
def get_cifar_models(config):
|
||||
from .CifarResNet import CifarResNet
|
||||
from .CifarDenseNet import DenseNet
|
||||
from .CifarWideResNet import CifarWideResNet
|
||||
|
||||
super_type = getattr(config, 'super_type', 'basic')
|
||||
if super_type == 'basic':
|
||||
if config.arch == 'resnet':
|
||||
return CifarResNet(config.module, config.depth, config.class_num, config.zero_init_residual)
|
||||
elif config.arch == 'densenet':
|
||||
return DenseNet(config.growthRate, config.depth, config.reduction, config.class_num, config.bottleneck)
|
||||
elif config.arch == 'wideresnet':
|
||||
return CifarWideResNet(config.depth, config.wide_factor, config.class_num, config.dropout)
|
||||
else:
|
||||
raise ValueError('invalid module type : {:}'.format(config.arch))
|
||||
elif super_type.startswith('infer'):
|
||||
from .infers import InferWidthCifarResNet
|
||||
from .infers import InferDepthCifarResNet
|
||||
from .infers import InferCifarResNet
|
||||
assert len(super_type.split('-')) == 2, 'invalid super_type : {:}'.format(super_type)
|
||||
infer_mode = super_type.split('-')[1]
|
||||
if infer_mode == 'width':
|
||||
return InferWidthCifarResNet(config.module, config.depth, config.xchannels, config.class_num, config.zero_init_residual)
|
||||
elif infer_mode == 'depth':
|
||||
return InferDepthCifarResNet(config.module, config.depth, config.xblocks, config.class_num, config.zero_init_residual)
|
||||
elif infer_mode == 'shape':
|
||||
return InferCifarResNet(config.module, config.depth, config.xblocks, config.xchannels, config.class_num, config.zero_init_residual)
|
||||
else:
|
||||
raise ValueError('invalid infer-mode : {:}'.format(infer_mode))
|
||||
else:
|
||||
raise ValueError('invalid super-type : {:}'.format(super_type))
|
||||
|
||||
|
||||
def get_imagenet_models(config):
|
||||
super_type = getattr(config, 'super_type', 'basic')
|
||||
if super_type == 'basic':
|
||||
return get_imagenet_models_basic(config)
|
||||
# NAS searched architecture
|
||||
elif super_type.startswith('infer'):
|
||||
assert len(super_type.split('-')) == 2, 'invalid super_type : {:}'.format(super_type)
|
||||
infer_mode = super_type.split('-')[1]
|
||||
if infer_mode == 'shape':
|
||||
from .infers import InferImagenetResNet
|
||||
from .infers import InferMobileNetV2
|
||||
if config.arch == 'resnet':
|
||||
return InferImagenetResNet(config.block_name, config.layers, config.xblocks, config.xchannels, config.deep_stem, config.class_num, config.zero_init_residual)
|
||||
elif config.arch == "MobileNetV2":
|
||||
return InferMobileNetV2(config.class_num, config.xchannels, config.xblocks, config.dropout)
|
||||
else:
|
||||
raise ValueError('invalid arch-mode : {:}'.format(config.arch))
|
||||
else:
|
||||
raise ValueError('invalid infer-mode : {:}'.format(infer_mode))
|
||||
else:
|
||||
raise ValueError('invalid super-type : {:}'.format(super_type))
|
||||
|
||||
|
||||
def get_imagenet_models_basic(config):
|
||||
from .ImagenetResNet import ResNet
|
||||
from .MobileNet import MobileNetV2
|
||||
from .ShuffleNetV2 import ShuffleNetV2
|
||||
if config.arch == 'resnet':
|
||||
return ResNet(config.block_name, config.layers, config.deep_stem, config.class_num, config.zero_init_residual, config.groups, config.width_per_group)
|
||||
elif config.arch == 'MobileNetV2':
|
||||
return MobileNetV2(config.class_num, config.width_mult, config.input_channel, config.last_channel, config.block_name, config.dropout)
|
||||
elif config.arch == 'ShuffleNetV2':
|
||||
return ShuffleNetV2(config.class_num, config.stages)
|
||||
else:
|
||||
raise ValueError('invalid arch : {:}'.format( config.arch ))
|
||||
|
||||
|
||||
def obtain_model(config):
|
||||
if config.dataset == 'cifar':
|
||||
return get_cifar_models(config)
|
||||
elif config.dataset == 'imagenet':
|
||||
return get_imagenet_models(config)
|
||||
else:
|
||||
raise ValueError('invalid dataset in the model config : {:}'.format(config))
|
||||
|
||||
|
||||
def obtain_search_model(config):
|
||||
if config.dataset == 'cifar':
|
||||
if config.arch == 'resnet':
|
||||
from .searchs import SearchWidthCifarResNet
|
||||
from .searchs import SearchDepthCifarResNet
|
||||
from .searchs import SearchShapeCifarResNet
|
||||
if config.search_mode == 'width':
|
||||
return SearchWidthCifarResNet(config.module, config.depth, config.class_num)
|
||||
elif config.search_mode == 'depth':
|
||||
return SearchDepthCifarResNet(config.module, config.depth, config.class_num)
|
||||
elif config.search_mode == 'shape':
|
||||
return SearchShapeCifarResNet(config.module, config.depth, config.class_num)
|
||||
else: raise ValueError('invalid search mode : {:}'.format(config.search_mode))
|
||||
else:
|
||||
raise ValueError('invalid arch : {:} for dataset [{:}]'.format(config.arch, config.dataset))
|
||||
elif config.dataset == 'imagenet':
|
||||
from .searchs import SearchShapeImagenetResNet
|
||||
assert config.search_mode == 'shape', 'invalid search-mode : {:}'.format( config.search_mode )
|
||||
if config.arch == 'resnet':
|
||||
return SearchShapeImagenetResNet(config.block_name, config.layers, config.deep_stem, config.class_num)
|
||||
else:
|
||||
raise ValueError('invalid model config : {:}'.format(config))
|
||||
else:
|
||||
raise ValueError('invalid dataset in the model config : {:}'.format(config))
|
||||
|
||||
|
||||
def load_net_from_checkpoint(checkpoint):
|
||||
assert osp.isfile(checkpoint), 'checkpoint {:} does not exist'.format(checkpoint)
|
||||
checkpoint = torch.load(checkpoint)
|
||||
model_config = dict2config(checkpoint['model-config'], None)
|
||||
model = obtain_model(model_config)
|
||||
model.load_state_dict(checkpoint['base-model'])
|
||||
return model
|
||||
62
lib/models/clone_weights.py
Normal file
62
lib/models/clone_weights.py
Normal file
@@ -0,0 +1,62 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
|
||||
def copy_conv(module, init):
|
||||
assert isinstance(module, nn.Conv2d), 'invalid module : {:}'.format(module)
|
||||
assert isinstance(init , nn.Conv2d), 'invalid module : {:}'.format(init)
|
||||
new_i, new_o = module.in_channels, module.out_channels
|
||||
module.weight.copy_( init.weight.detach()[:new_o, :new_i] )
|
||||
if module.bias is not None:
|
||||
module.bias.copy_( init.bias.detach()[:new_o] )
|
||||
|
||||
def copy_bn (module, init):
|
||||
assert isinstance(module, nn.BatchNorm2d), 'invalid module : {:}'.format(module)
|
||||
assert isinstance(init , nn.BatchNorm2d), 'invalid module : {:}'.format(init)
|
||||
num_features = module.num_features
|
||||
if module.weight is not None:
|
||||
module.weight.copy_( init.weight.detach()[:num_features] )
|
||||
if module.bias is not None:
|
||||
module.bias.copy_( init.bias.detach()[:num_features] )
|
||||
if module.running_mean is not None:
|
||||
module.running_mean.copy_( init.running_mean.detach()[:num_features] )
|
||||
if module.running_var is not None:
|
||||
module.running_var.copy_( init.running_var.detach()[:num_features] )
|
||||
|
||||
def copy_fc (module, init):
|
||||
assert isinstance(module, nn.Linear), 'invalid module : {:}'.format(module)
|
||||
assert isinstance(init , nn.Linear), 'invalid module : {:}'.format(init)
|
||||
new_i, new_o = module.in_features, module.out_features
|
||||
module.weight.copy_( init.weight.detach()[:new_o, :new_i] )
|
||||
if module.bias is not None:
|
||||
module.bias.copy_( init.bias.detach()[:new_o] )
|
||||
|
||||
def copy_base(module, init):
|
||||
assert type(module).__name__ in ['ConvBNReLU', 'Downsample'], 'invalid module : {:}'.format(module)
|
||||
assert type( init).__name__ in ['ConvBNReLU', 'Downsample'], 'invalid module : {:}'.format( init)
|
||||
if module.conv is not None:
|
||||
copy_conv(module.conv, init.conv)
|
||||
if module.bn is not None:
|
||||
copy_bn (module.bn, init.bn)
|
||||
|
||||
def copy_basic(module, init):
|
||||
copy_base(module.conv_a, init.conv_a)
|
||||
copy_base(module.conv_b, init.conv_b)
|
||||
if module.downsample is not None:
|
||||
if init.downsample is not None:
|
||||
copy_base(module.downsample, init.downsample)
|
||||
#else:
|
||||
# import pdb; pdb.set_trace()
|
||||
|
||||
|
||||
def init_from_model(network, init_model):
|
||||
with torch.no_grad():
|
||||
copy_fc(network.classifier, init_model.classifier)
|
||||
for base, target in zip(init_model.layers, network.layers):
|
||||
assert type(base).__name__ == type(target).__name__, 'invalid type : {:} vs {:}'.format(base, target)
|
||||
if type(base).__name__ == 'ConvBNReLU':
|
||||
copy_base(target, base)
|
||||
elif type(base).__name__ == 'ResNetBasicblock':
|
||||
copy_basic(target, base)
|
||||
else:
|
||||
raise ValueError('unknown type name : {:}'.format( type(base).__name__ ))
|
||||
166
lib/models/infers/InferCifarResNet.py
Normal file
166
lib/models/infers/InferCifarResNet.py
Normal file
@@ -0,0 +1,166 @@
|
||||
import math, torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from ..initialization import initialize_resnet
|
||||
from ..SharedUtils import additive_func
|
||||
|
||||
|
||||
class ConvBNReLU(nn.Module):
|
||||
|
||||
def __init__(self, nIn, nOut, kernel, stride, padding, bias, has_avg, has_bn, has_relu):
|
||||
super(ConvBNReLU, self).__init__()
|
||||
if has_avg : self.avg = nn.AvgPool2d(kernel_size=2, stride=2, padding=0)
|
||||
else : self.avg = None
|
||||
self.conv = nn.Conv2d(nIn, nOut, kernel_size=kernel, stride=stride, padding=padding, dilation=1, groups=1, bias=bias)
|
||||
if has_bn : self.bn = nn.BatchNorm2d(nOut)
|
||||
else : self.bn = None
|
||||
if has_relu: self.relu = nn.ReLU(inplace=True)
|
||||
else : self.relu = None
|
||||
|
||||
def forward(self, inputs):
|
||||
if self.avg : out = self.avg( inputs )
|
||||
else : out = inputs
|
||||
conv = self.conv( out )
|
||||
if self.bn : out = self.bn( conv )
|
||||
else : out = conv
|
||||
if self.relu: out = self.relu( out )
|
||||
else : out = out
|
||||
|
||||
return out
|
||||
|
||||
|
||||
class ResNetBasicblock(nn.Module):
|
||||
num_conv = 2
|
||||
expansion = 1
|
||||
def __init__(self, iCs, stride):
|
||||
super(ResNetBasicblock, self).__init__()
|
||||
assert stride == 1 or stride == 2, 'invalid stride {:}'.format(stride)
|
||||
assert isinstance(iCs, tuple) or isinstance(iCs, list), 'invalid type of iCs : {:}'.format( iCs )
|
||||
assert len(iCs) == 3,'invalid lengths of iCs : {:}'.format(iCs)
|
||||
|
||||
self.conv_a = ConvBNReLU(iCs[0], iCs[1], 3, stride, 1, False, has_avg=False, has_bn=True, has_relu=True)
|
||||
self.conv_b = ConvBNReLU(iCs[1], iCs[2], 3, 1, 1, False, has_avg=False, has_bn=True, has_relu=False)
|
||||
residual_in = iCs[0]
|
||||
if stride == 2:
|
||||
self.downsample = ConvBNReLU(iCs[0], iCs[2], 1, 1, 0, False, has_avg=True, has_bn=False, has_relu=False)
|
||||
residual_in = iCs[2]
|
||||
elif iCs[0] != iCs[2]:
|
||||
self.downsample = ConvBNReLU(iCs[0], iCs[2], 1, 1, 0, False, has_avg=False,has_bn=True , has_relu=False)
|
||||
else:
|
||||
self.downsample = None
|
||||
#self.out_dim = max(residual_in, iCs[2])
|
||||
self.out_dim = iCs[2]
|
||||
|
||||
def forward(self, inputs):
|
||||
basicblock = self.conv_a(inputs)
|
||||
basicblock = self.conv_b(basicblock)
|
||||
|
||||
if self.downsample is not None:
|
||||
residual = self.downsample(inputs)
|
||||
else:
|
||||
residual = inputs
|
||||
out = residual + basicblock
|
||||
return F.relu(out, inplace=True)
|
||||
|
||||
|
||||
|
||||
class ResNetBottleneck(nn.Module):
|
||||
expansion = 4
|
||||
num_conv = 3
|
||||
def __init__(self, iCs, stride):
|
||||
super(ResNetBottleneck, self).__init__()
|
||||
assert stride == 1 or stride == 2, 'invalid stride {:}'.format(stride)
|
||||
assert isinstance(iCs, tuple) or isinstance(iCs, list), 'invalid type of iCs : {:}'.format( iCs )
|
||||
assert len(iCs) == 4,'invalid lengths of iCs : {:}'.format(iCs)
|
||||
self.conv_1x1 = ConvBNReLU(iCs[0], iCs[1], 1, 1, 0, False, has_avg=False, has_bn=True, has_relu=True)
|
||||
self.conv_3x3 = ConvBNReLU(iCs[1], iCs[2], 3, stride, 1, False, has_avg=False, has_bn=True, has_relu=True)
|
||||
self.conv_1x4 = ConvBNReLU(iCs[2], iCs[3], 1, 1, 0, False, has_avg=False, has_bn=True, has_relu=False)
|
||||
residual_in = iCs[0]
|
||||
if stride == 2:
|
||||
self.downsample = ConvBNReLU(iCs[0], iCs[3], 1, 1, 0, False, has_avg=True , has_bn=False, has_relu=False)
|
||||
residual_in = iCs[3]
|
||||
elif iCs[0] != iCs[3]:
|
||||
self.downsample = ConvBNReLU(iCs[0], iCs[3], 1, 1, 0, False, has_avg=False, has_bn=False, has_relu=False)
|
||||
residual_in = iCs[3]
|
||||
else:
|
||||
self.downsample = None
|
||||
#self.out_dim = max(residual_in, iCs[3])
|
||||
self.out_dim = iCs[3]
|
||||
|
||||
def forward(self, inputs):
|
||||
|
||||
bottleneck = self.conv_1x1(inputs)
|
||||
bottleneck = self.conv_3x3(bottleneck)
|
||||
bottleneck = self.conv_1x4(bottleneck)
|
||||
|
||||
if self.downsample is not None:
|
||||
residual = self.downsample(inputs)
|
||||
else:
|
||||
residual = inputs
|
||||
out = residual + bottleneck
|
||||
return F.relu(out, inplace=True)
|
||||
|
||||
|
||||
|
||||
class InferCifarResNet(nn.Module):
|
||||
|
||||
def __init__(self, block_name, depth, xblocks, xchannels, num_classes, zero_init_residual):
|
||||
super(InferCifarResNet, self).__init__()
|
||||
|
||||
#Model type specifies number of layers for CIFAR-10 and CIFAR-100 model
|
||||
if block_name == 'ResNetBasicblock':
|
||||
block = ResNetBasicblock
|
||||
assert (depth - 2) % 6 == 0, 'depth should be one of 20, 32, 44, 56, 110'
|
||||
layer_blocks = (depth - 2) // 6
|
||||
elif block_name == 'ResNetBottleneck':
|
||||
block = ResNetBottleneck
|
||||
assert (depth - 2) % 9 == 0, 'depth should be one of 164'
|
||||
layer_blocks = (depth - 2) // 9
|
||||
else:
|
||||
raise ValueError('invalid block : {:}'.format(block_name))
|
||||
assert len(xblocks) == 3, 'invalid xblocks : {:}'.format(xblocks)
|
||||
|
||||
self.message = 'InferWidthCifarResNet : Depth : {:} , Layers for each block : {:}'.format(depth, layer_blocks)
|
||||
self.num_classes = num_classes
|
||||
self.xchannels = xchannels
|
||||
self.layers = nn.ModuleList( [ ConvBNReLU(xchannels[0], xchannels[1], 3, 1, 1, False, has_avg=False, has_bn=True, has_relu=True) ] )
|
||||
last_channel_idx = 1
|
||||
for stage in range(3):
|
||||
for iL in range(layer_blocks):
|
||||
num_conv = block.num_conv
|
||||
iCs = self.xchannels[last_channel_idx:last_channel_idx+num_conv+1]
|
||||
stride = 2 if stage > 0 and iL == 0 else 1
|
||||
module = block(iCs, stride)
|
||||
last_channel_idx += num_conv
|
||||
self.xchannels[last_channel_idx] = module.out_dim
|
||||
self.layers.append ( module )
|
||||
self.message += "\nstage={:}, ilayer={:02d}/{:02d}, block={:03d}, iCs={:}, oC={:3d}, stride={:}".format(stage, iL, layer_blocks, len(self.layers)-1, iCs, module.out_dim, stride)
|
||||
if iL + 1 == xblocks[stage]: # reach the maximum depth
|
||||
out_channel = module.out_dim
|
||||
for iiL in range(iL+1, layer_blocks):
|
||||
last_channel_idx += num_conv
|
||||
self.xchannels[last_channel_idx] = module.out_dim
|
||||
break
|
||||
|
||||
self.avgpool = nn.AvgPool2d(8)
|
||||
self.classifier = nn.Linear(self.xchannels[-1], num_classes)
|
||||
|
||||
self.apply(initialize_resnet)
|
||||
if zero_init_residual:
|
||||
for m in self.modules():
|
||||
if isinstance(m, ResNetBasicblock):
|
||||
nn.init.constant_(m.conv_b.bn.weight, 0)
|
||||
elif isinstance(m, ResNetBottleneck):
|
||||
nn.init.constant_(m.conv_1x4.bn.weight, 0)
|
||||
|
||||
def get_message(self):
|
||||
return self.message
|
||||
|
||||
def forward(self, inputs):
|
||||
x = inputs
|
||||
for i, layer in enumerate(self.layers):
|
||||
x = layer( x )
|
||||
features = self.avgpool(x)
|
||||
features = features.view(features.size(0), -1)
|
||||
logits = self.classifier(features)
|
||||
return features, logits
|
||||
149
lib/models/infers/InferCifarResNet_depth.py
Normal file
149
lib/models/infers/InferCifarResNet_depth.py
Normal file
@@ -0,0 +1,149 @@
|
||||
import math, torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from ..initialization import initialize_resnet
|
||||
from ..SharedUtils import additive_func
|
||||
|
||||
|
||||
class ConvBNReLU(nn.Module):
|
||||
|
||||
def __init__(self, nIn, nOut, kernel, stride, padding, bias, has_avg, has_bn, has_relu):
|
||||
super(ConvBNReLU, self).__init__()
|
||||
if has_avg : self.avg = nn.AvgPool2d(kernel_size=2, stride=2, padding=0)
|
||||
else : self.avg = None
|
||||
self.conv = nn.Conv2d(nIn, nOut, kernel_size=kernel, stride=stride, padding=padding, dilation=1, groups=1, bias=bias)
|
||||
if has_bn : self.bn = nn.BatchNorm2d(nOut)
|
||||
else : self.bn = None
|
||||
if has_relu: self.relu = nn.ReLU(inplace=True)
|
||||
else : self.relu = None
|
||||
|
||||
def forward(self, inputs):
|
||||
if self.avg : out = self.avg( inputs )
|
||||
else : out = inputs
|
||||
conv = self.conv( out )
|
||||
if self.bn : out = self.bn( conv )
|
||||
else : out = conv
|
||||
if self.relu: out = self.relu( out )
|
||||
else : out = out
|
||||
|
||||
return out
|
||||
|
||||
|
||||
class ResNetBasicblock(nn.Module):
|
||||
num_conv = 2
|
||||
expansion = 1
|
||||
def __init__(self, inplanes, planes, stride):
|
||||
super(ResNetBasicblock, self).__init__()
|
||||
assert stride == 1 or stride == 2, 'invalid stride {:}'.format(stride)
|
||||
|
||||
self.conv_a = ConvBNReLU(inplanes, planes, 3, stride, 1, False, has_avg=False, has_bn=True, has_relu=True)
|
||||
self.conv_b = ConvBNReLU( planes, planes, 3, 1, 1, False, has_avg=False, has_bn=True, has_relu=False)
|
||||
if stride == 2:
|
||||
self.downsample = ConvBNReLU(inplanes, planes, 1, 1, 0, False, has_avg=True, has_bn=False, has_relu=False)
|
||||
elif inplanes != planes:
|
||||
self.downsample = ConvBNReLU(inplanes, planes, 1, 1, 0, False, has_avg=False,has_bn=True , has_relu=False)
|
||||
else:
|
||||
self.downsample = None
|
||||
self.out_dim = planes
|
||||
|
||||
def forward(self, inputs):
|
||||
basicblock = self.conv_a(inputs)
|
||||
basicblock = self.conv_b(basicblock)
|
||||
|
||||
if self.downsample is not None:
|
||||
residual = self.downsample(inputs)
|
||||
else:
|
||||
residual = inputs
|
||||
out = residual + basicblock
|
||||
return F.relu(out, inplace=True)
|
||||
|
||||
|
||||
|
||||
class ResNetBottleneck(nn.Module):
|
||||
expansion = 4
|
||||
num_conv = 3
|
||||
def __init__(self, inplanes, planes, stride):
|
||||
super(ResNetBottleneck, self).__init__()
|
||||
assert stride == 1 or stride == 2, 'invalid stride {:}'.format(stride)
|
||||
self.conv_1x1 = ConvBNReLU(inplanes, planes, 1, 1, 0, False, has_avg=False, has_bn=True, has_relu=True)
|
||||
self.conv_3x3 = ConvBNReLU( planes, planes, 3, stride, 1, False, has_avg=False, has_bn=True, has_relu=True)
|
||||
self.conv_1x4 = ConvBNReLU(planes, planes*self.expansion, 1, 1, 0, False, has_avg=False, has_bn=True, has_relu=False)
|
||||
if stride == 2:
|
||||
self.downsample = ConvBNReLU(inplanes, planes*self.expansion, 1, 1, 0, False, has_avg=True , has_bn=False, has_relu=False)
|
||||
elif inplanes != planes*self.expansion:
|
||||
self.downsample = ConvBNReLU(inplanes, planes*self.expansion, 1, 1, 0, False, has_avg=False, has_bn=False, has_relu=False)
|
||||
else:
|
||||
self.downsample = None
|
||||
self.out_dim = planes*self.expansion
|
||||
|
||||
def forward(self, inputs):
|
||||
|
||||
bottleneck = self.conv_1x1(inputs)
|
||||
bottleneck = self.conv_3x3(bottleneck)
|
||||
bottleneck = self.conv_1x4(bottleneck)
|
||||
|
||||
if self.downsample is not None:
|
||||
residual = self.downsample(inputs)
|
||||
else:
|
||||
residual = inputs
|
||||
out = residual + bottleneck
|
||||
return F.relu(out, inplace=True)
|
||||
|
||||
|
||||
|
||||
class InferDepthCifarResNet(nn.Module):
|
||||
|
||||
def __init__(self, block_name, depth, xblocks, num_classes, zero_init_residual):
|
||||
super(InferDepthCifarResNet, self).__init__()
|
||||
|
||||
#Model type specifies number of layers for CIFAR-10 and CIFAR-100 model
|
||||
if block_name == 'ResNetBasicblock':
|
||||
block = ResNetBasicblock
|
||||
assert (depth - 2) % 6 == 0, 'depth should be one of 20, 32, 44, 56, 110'
|
||||
layer_blocks = (depth - 2) // 6
|
||||
elif block_name == 'ResNetBottleneck':
|
||||
block = ResNetBottleneck
|
||||
assert (depth - 2) % 9 == 0, 'depth should be one of 164'
|
||||
layer_blocks = (depth - 2) // 9
|
||||
else:
|
||||
raise ValueError('invalid block : {:}'.format(block_name))
|
||||
assert len(xblocks) == 3, 'invalid xblocks : {:}'.format(xblocks)
|
||||
|
||||
self.message = 'InferWidthCifarResNet : Depth : {:} , Layers for each block : {:}'.format(depth, layer_blocks)
|
||||
self.num_classes = num_classes
|
||||
self.layers = nn.ModuleList( [ ConvBNReLU(3, 16, 3, 1, 1, False, has_avg=False, has_bn=True, has_relu=True) ] )
|
||||
self.channels = [16]
|
||||
for stage in range(3):
|
||||
for iL in range(layer_blocks):
|
||||
iC = self.channels[-1]
|
||||
planes = 16 * (2**stage)
|
||||
stride = 2 if stage > 0 and iL == 0 else 1
|
||||
module = block(iC, planes, stride)
|
||||
self.channels.append( module.out_dim )
|
||||
self.layers.append ( module )
|
||||
self.message += "\nstage={:}, ilayer={:02d}/{:02d}, block={:03d}, iC={:}, oC={:3d}, stride={:}".format(stage, iL, layer_blocks, len(self.layers)-1, planes, module.out_dim, stride)
|
||||
if iL + 1 == xblocks[stage]: # reach the maximum depth
|
||||
break
|
||||
|
||||
self.avgpool = nn.AvgPool2d(8)
|
||||
self.classifier = nn.Linear(self.channels[-1], num_classes)
|
||||
|
||||
self.apply(initialize_resnet)
|
||||
if zero_init_residual:
|
||||
for m in self.modules():
|
||||
if isinstance(m, ResNetBasicblock):
|
||||
nn.init.constant_(m.conv_b.bn.weight, 0)
|
||||
elif isinstance(m, ResNetBottleneck):
|
||||
nn.init.constant_(m.conv_1x4.bn.weight, 0)
|
||||
|
||||
def get_message(self):
|
||||
return self.message
|
||||
|
||||
def forward(self, inputs):
|
||||
x = inputs
|
||||
for i, layer in enumerate(self.layers):
|
||||
x = layer( x )
|
||||
features = self.avgpool(x)
|
||||
features = features.view(features.size(0), -1)
|
||||
logits = self.classifier(features)
|
||||
return features, logits
|
||||
159
lib/models/infers/InferCifarResNet_width.py
Normal file
159
lib/models/infers/InferCifarResNet_width.py
Normal file
@@ -0,0 +1,159 @@
|
||||
import math, torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from ..initialization import initialize_resnet
|
||||
from ..SharedUtils import additive_func
|
||||
|
||||
|
||||
class ConvBNReLU(nn.Module):
|
||||
|
||||
def __init__(self, nIn, nOut, kernel, stride, padding, bias, has_avg, has_bn, has_relu):
|
||||
super(ConvBNReLU, self).__init__()
|
||||
if has_avg : self.avg = nn.AvgPool2d(kernel_size=2, stride=2, padding=0)
|
||||
else : self.avg = None
|
||||
self.conv = nn.Conv2d(nIn, nOut, kernel_size=kernel, stride=stride, padding=padding, dilation=1, groups=1, bias=bias)
|
||||
if has_bn : self.bn = nn.BatchNorm2d(nOut)
|
||||
else : self.bn = None
|
||||
if has_relu: self.relu = nn.ReLU(inplace=True)
|
||||
else : self.relu = None
|
||||
|
||||
def forward(self, inputs):
|
||||
if self.avg : out = self.avg( inputs )
|
||||
else : out = inputs
|
||||
conv = self.conv( out )
|
||||
if self.bn : out = self.bn( conv )
|
||||
else : out = conv
|
||||
if self.relu: out = self.relu( out )
|
||||
else : out = out
|
||||
|
||||
return out
|
||||
|
||||
|
||||
class ResNetBasicblock(nn.Module):
|
||||
num_conv = 2
|
||||
expansion = 1
|
||||
def __init__(self, iCs, stride):
|
||||
super(ResNetBasicblock, self).__init__()
|
||||
assert stride == 1 or stride == 2, 'invalid stride {:}'.format(stride)
|
||||
assert isinstance(iCs, tuple) or isinstance(iCs, list), 'invalid type of iCs : {:}'.format( iCs )
|
||||
assert len(iCs) == 3,'invalid lengths of iCs : {:}'.format(iCs)
|
||||
|
||||
self.conv_a = ConvBNReLU(iCs[0], iCs[1], 3, stride, 1, False, has_avg=False, has_bn=True, has_relu=True)
|
||||
self.conv_b = ConvBNReLU(iCs[1], iCs[2], 3, 1, 1, False, has_avg=False, has_bn=True, has_relu=False)
|
||||
residual_in = iCs[0]
|
||||
if stride == 2:
|
||||
self.downsample = ConvBNReLU(iCs[0], iCs[2], 1, 1, 0, False, has_avg=True, has_bn=False, has_relu=False)
|
||||
residual_in = iCs[2]
|
||||
elif iCs[0] != iCs[2]:
|
||||
self.downsample = ConvBNReLU(iCs[0], iCs[2], 1, 1, 0, False, has_avg=False,has_bn=True , has_relu=False)
|
||||
else:
|
||||
self.downsample = None
|
||||
#self.out_dim = max(residual_in, iCs[2])
|
||||
self.out_dim = iCs[2]
|
||||
|
||||
def forward(self, inputs):
|
||||
basicblock = self.conv_a(inputs)
|
||||
basicblock = self.conv_b(basicblock)
|
||||
|
||||
if self.downsample is not None:
|
||||
residual = self.downsample(inputs)
|
||||
else:
|
||||
residual = inputs
|
||||
out = residual + basicblock
|
||||
return F.relu(out, inplace=True)
|
||||
|
||||
|
||||
|
||||
class ResNetBottleneck(nn.Module):
|
||||
expansion = 4
|
||||
num_conv = 3
|
||||
def __init__(self, iCs, stride):
|
||||
super(ResNetBottleneck, self).__init__()
|
||||
assert stride == 1 or stride == 2, 'invalid stride {:}'.format(stride)
|
||||
assert isinstance(iCs, tuple) or isinstance(iCs, list), 'invalid type of iCs : {:}'.format( iCs )
|
||||
assert len(iCs) == 4,'invalid lengths of iCs : {:}'.format(iCs)
|
||||
self.conv_1x1 = ConvBNReLU(iCs[0], iCs[1], 1, 1, 0, False, has_avg=False, has_bn=True, has_relu=True)
|
||||
self.conv_3x3 = ConvBNReLU(iCs[1], iCs[2], 3, stride, 1, False, has_avg=False, has_bn=True, has_relu=True)
|
||||
self.conv_1x4 = ConvBNReLU(iCs[2], iCs[3], 1, 1, 0, False, has_avg=False, has_bn=True, has_relu=False)
|
||||
residual_in = iCs[0]
|
||||
if stride == 2:
|
||||
self.downsample = ConvBNReLU(iCs[0], iCs[3], 1, 1, 0, False, has_avg=True , has_bn=False, has_relu=False)
|
||||
residual_in = iCs[3]
|
||||
elif iCs[0] != iCs[3]:
|
||||
self.downsample = ConvBNReLU(iCs[0], iCs[3], 1, 1, 0, False, has_avg=False, has_bn=False, has_relu=False)
|
||||
residual_in = iCs[3]
|
||||
else:
|
||||
self.downsample = None
|
||||
#self.out_dim = max(residual_in, iCs[3])
|
||||
self.out_dim = iCs[3]
|
||||
|
||||
def forward(self, inputs):
|
||||
|
||||
bottleneck = self.conv_1x1(inputs)
|
||||
bottleneck = self.conv_3x3(bottleneck)
|
||||
bottleneck = self.conv_1x4(bottleneck)
|
||||
|
||||
if self.downsample is not None:
|
||||
residual = self.downsample(inputs)
|
||||
else:
|
||||
residual = inputs
|
||||
out = residual + bottleneck
|
||||
return F.relu(out, inplace=True)
|
||||
|
||||
|
||||
|
||||
class InferWidthCifarResNet(nn.Module):
|
||||
|
||||
def __init__(self, block_name, depth, xchannels, num_classes, zero_init_residual):
|
||||
super(InferWidthCifarResNet, self).__init__()
|
||||
|
||||
#Model type specifies number of layers for CIFAR-10 and CIFAR-100 model
|
||||
if block_name == 'ResNetBasicblock':
|
||||
block = ResNetBasicblock
|
||||
assert (depth - 2) % 6 == 0, 'depth should be one of 20, 32, 44, 56, 110'
|
||||
layer_blocks = (depth - 2) // 6
|
||||
elif block_name == 'ResNetBottleneck':
|
||||
block = ResNetBottleneck
|
||||
assert (depth - 2) % 9 == 0, 'depth should be one of 164'
|
||||
layer_blocks = (depth - 2) // 9
|
||||
else:
|
||||
raise ValueError('invalid block : {:}'.format(block_name))
|
||||
|
||||
self.message = 'InferWidthCifarResNet : Depth : {:} , Layers for each block : {:}'.format(depth, layer_blocks)
|
||||
self.num_classes = num_classes
|
||||
self.xchannels = xchannels
|
||||
self.layers = nn.ModuleList( [ ConvBNReLU(xchannels[0], xchannels[1], 3, 1, 1, False, has_avg=False, has_bn=True, has_relu=True) ] )
|
||||
last_channel_idx = 1
|
||||
for stage in range(3):
|
||||
for iL in range(layer_blocks):
|
||||
num_conv = block.num_conv
|
||||
iCs = self.xchannels[last_channel_idx:last_channel_idx+num_conv+1]
|
||||
stride = 2 if stage > 0 and iL == 0 else 1
|
||||
module = block(iCs, stride)
|
||||
last_channel_idx += num_conv
|
||||
self.xchannels[last_channel_idx] = module.out_dim
|
||||
self.layers.append ( module )
|
||||
self.message += "\nstage={:}, ilayer={:02d}/{:02d}, block={:03d}, iCs={:}, oC={:3d}, stride={:}".format(stage, iL, layer_blocks, len(self.layers)-1, iCs, module.out_dim, stride)
|
||||
|
||||
self.avgpool = nn.AvgPool2d(8)
|
||||
self.classifier = nn.Linear(self.xchannels[-1], num_classes)
|
||||
|
||||
self.apply(initialize_resnet)
|
||||
if zero_init_residual:
|
||||
for m in self.modules():
|
||||
if isinstance(m, ResNetBasicblock):
|
||||
nn.init.constant_(m.conv_b.bn.weight, 0)
|
||||
elif isinstance(m, ResNetBottleneck):
|
||||
nn.init.constant_(m.conv_1x4.bn.weight, 0)
|
||||
|
||||
def get_message(self):
|
||||
return self.message
|
||||
|
||||
def forward(self, inputs):
|
||||
x = inputs
|
||||
for i, layer in enumerate(self.layers):
|
||||
x = layer( x )
|
||||
features = self.avgpool(x)
|
||||
features = features.view(features.size(0), -1)
|
||||
logits = self.classifier(features)
|
||||
return features, logits
|
||||
169
lib/models/infers/InferImagenetResNet.py
Normal file
169
lib/models/infers/InferImagenetResNet.py
Normal file
@@ -0,0 +1,169 @@
|
||||
import math, torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from ..initialization import initialize_resnet
|
||||
from ..SharedUtils import additive_func
|
||||
|
||||
|
||||
class ConvBNReLU(nn.Module):
|
||||
|
||||
num_conv = 1
|
||||
def __init__(self, nIn, nOut, kernel, stride, padding, bias, has_avg, has_bn, has_relu):
|
||||
super(ConvBNReLU, self).__init__()
|
||||
if has_avg : self.avg = nn.AvgPool2d(kernel_size=2, stride=2, padding=0)
|
||||
else : self.avg = None
|
||||
self.conv = nn.Conv2d(nIn, nOut, kernel_size=kernel, stride=stride, padding=padding, dilation=1, groups=1, bias=bias)
|
||||
if has_bn : self.bn = nn.BatchNorm2d(nOut)
|
||||
else : self.bn = None
|
||||
if has_relu: self.relu = nn.ReLU(inplace=True)
|
||||
else : self.relu = None
|
||||
|
||||
def forward(self, inputs):
|
||||
if self.avg : out = self.avg( inputs )
|
||||
else : out = inputs
|
||||
conv = self.conv( out )
|
||||
if self.bn : out = self.bn( conv )
|
||||
else : out = conv
|
||||
if self.relu: out = self.relu( out )
|
||||
else : out = out
|
||||
|
||||
return out
|
||||
|
||||
|
||||
class ResNetBasicblock(nn.Module):
|
||||
num_conv = 2
|
||||
expansion = 1
|
||||
def __init__(self, iCs, stride):
|
||||
super(ResNetBasicblock, self).__init__()
|
||||
assert stride == 1 or stride == 2, 'invalid stride {:}'.format(stride)
|
||||
assert isinstance(iCs, tuple) or isinstance(iCs, list), 'invalid type of iCs : {:}'.format( iCs )
|
||||
assert len(iCs) == 3,'invalid lengths of iCs : {:}'.format(iCs)
|
||||
|
||||
self.conv_a = ConvBNReLU(iCs[0], iCs[1], 3, stride, 1, False, has_avg=False, has_bn=True, has_relu=True)
|
||||
self.conv_b = ConvBNReLU(iCs[1], iCs[2], 3, 1, 1, False, has_avg=False, has_bn=True, has_relu=False)
|
||||
residual_in = iCs[0]
|
||||
if stride == 2:
|
||||
self.downsample = ConvBNReLU(iCs[0], iCs[2], 1, 1, 0, False, has_avg=True, has_bn=True, has_relu=False)
|
||||
residual_in = iCs[2]
|
||||
elif iCs[0] != iCs[2]:
|
||||
self.downsample = ConvBNReLU(iCs[0], iCs[2], 1, 1, 0, False, has_avg=False,has_bn=True , has_relu=False)
|
||||
else:
|
||||
self.downsample = None
|
||||
#self.out_dim = max(residual_in, iCs[2])
|
||||
self.out_dim = iCs[2]
|
||||
|
||||
def forward(self, inputs):
|
||||
basicblock = self.conv_a(inputs)
|
||||
basicblock = self.conv_b(basicblock)
|
||||
|
||||
if self.downsample is not None:
|
||||
residual = self.downsample(inputs)
|
||||
else:
|
||||
residual = inputs
|
||||
out = residual + basicblock
|
||||
return F.relu(out, inplace=True)
|
||||
|
||||
|
||||
|
||||
class ResNetBottleneck(nn.Module):
|
||||
expansion = 4
|
||||
num_conv = 3
|
||||
def __init__(self, iCs, stride):
|
||||
super(ResNetBottleneck, self).__init__()
|
||||
assert stride == 1 or stride == 2, 'invalid stride {:}'.format(stride)
|
||||
assert isinstance(iCs, tuple) or isinstance(iCs, list), 'invalid type of iCs : {:}'.format( iCs )
|
||||
assert len(iCs) == 4,'invalid lengths of iCs : {:}'.format(iCs)
|
||||
self.conv_1x1 = ConvBNReLU(iCs[0], iCs[1], 1, 1, 0, False, has_avg=False, has_bn=True, has_relu=True)
|
||||
self.conv_3x3 = ConvBNReLU(iCs[1], iCs[2], 3, stride, 1, False, has_avg=False, has_bn=True, has_relu=True)
|
||||
self.conv_1x4 = ConvBNReLU(iCs[2], iCs[3], 1, 1, 0, False, has_avg=False, has_bn=True, has_relu=False)
|
||||
residual_in = iCs[0]
|
||||
if stride == 2:
|
||||
self.downsample = ConvBNReLU(iCs[0], iCs[3], 1, 1, 0, False, has_avg=True , has_bn=True, has_relu=False)
|
||||
residual_in = iCs[3]
|
||||
elif iCs[0] != iCs[3]:
|
||||
self.downsample = ConvBNReLU(iCs[0], iCs[3], 1, 1, 0, False, has_avg=False, has_bn=True, has_relu=False)
|
||||
residual_in = iCs[3]
|
||||
else:
|
||||
self.downsample = None
|
||||
#self.out_dim = max(residual_in, iCs[3])
|
||||
self.out_dim = iCs[3]
|
||||
|
||||
def forward(self, inputs):
|
||||
|
||||
bottleneck = self.conv_1x1(inputs)
|
||||
bottleneck = self.conv_3x3(bottleneck)
|
||||
bottleneck = self.conv_1x4(bottleneck)
|
||||
|
||||
if self.downsample is not None:
|
||||
residual = self.downsample(inputs)
|
||||
else:
|
||||
residual = inputs
|
||||
out = residual + bottleneck
|
||||
return F.relu(out, inplace=True)
|
||||
|
||||
|
||||
|
||||
class InferImagenetResNet(nn.Module):
|
||||
|
||||
def __init__(self, block_name, layers, xblocks, xchannels, deep_stem, num_classes, zero_init_residual):
|
||||
super(InferImagenetResNet, self).__init__()
|
||||
|
||||
#Model type specifies number of layers for CIFAR-10 and CIFAR-100 model
|
||||
if block_name == 'BasicBlock':
|
||||
block = ResNetBasicblock
|
||||
elif block_name == 'Bottleneck':
|
||||
block = ResNetBottleneck
|
||||
else:
|
||||
raise ValueError('invalid block : {:}'.format(block_name))
|
||||
assert len(xblocks) == len(layers), 'invalid layers : {:} vs xblocks : {:}'.format(layers, xblocks)
|
||||
|
||||
self.message = 'InferImagenetResNet : Depth : {:} -> {:}, Layers for each block : {:}'.format(sum(layers)*block.num_conv, sum(xblocks)*block.num_conv, xblocks)
|
||||
self.num_classes = num_classes
|
||||
self.xchannels = xchannels
|
||||
if not deep_stem:
|
||||
self.layers = nn.ModuleList( [ ConvBNReLU(xchannels[0], xchannels[1], 7, 2, 3, False, has_avg=False, has_bn=True, has_relu=True) ] )
|
||||
last_channel_idx = 1
|
||||
else:
|
||||
self.layers = nn.ModuleList( [ ConvBNReLU(xchannels[0], xchannels[1], 3, 2, 1, False, has_avg=False, has_bn=True, has_relu=True)
|
||||
,ConvBNReLU(xchannels[1], xchannels[2], 3, 1, 1, False, has_avg=False, has_bn=True, has_relu=True) ] )
|
||||
last_channel_idx = 2
|
||||
self.layers.append( nn.MaxPool2d(kernel_size=3, stride=2, padding=1) )
|
||||
for stage, layer_blocks in enumerate(layers):
|
||||
for iL in range(layer_blocks):
|
||||
num_conv = block.num_conv
|
||||
iCs = self.xchannels[last_channel_idx:last_channel_idx+num_conv+1]
|
||||
stride = 2 if stage > 0 and iL == 0 else 1
|
||||
module = block(iCs, stride)
|
||||
last_channel_idx += num_conv
|
||||
self.xchannels[last_channel_idx] = module.out_dim
|
||||
self.layers.append ( module )
|
||||
self.message += "\nstage={:}, ilayer={:02d}/{:02d}, block={:03d}, iCs={:}, oC={:3d}, stride={:}".format(stage, iL, layer_blocks, len(self.layers)-1, iCs, module.out_dim, stride)
|
||||
if iL + 1 == xblocks[stage]: # reach the maximum depth
|
||||
out_channel = module.out_dim
|
||||
for iiL in range(iL+1, layer_blocks):
|
||||
last_channel_idx += num_conv
|
||||
self.xchannels[last_channel_idx] = module.out_dim
|
||||
break
|
||||
assert last_channel_idx + 1 == len(self.xchannels), '{:} vs {:}'.format(last_channel_idx, len(self.xchannels))
|
||||
self.avgpool = nn.AdaptiveAvgPool2d((1,1))
|
||||
self.classifier = nn.Linear(self.xchannels[-1], num_classes)
|
||||
|
||||
self.apply(initialize_resnet)
|
||||
if zero_init_residual:
|
||||
for m in self.modules():
|
||||
if isinstance(m, ResNetBasicblock):
|
||||
nn.init.constant_(m.conv_b.bn.weight, 0)
|
||||
elif isinstance(m, ResNetBottleneck):
|
||||
nn.init.constant_(m.conv_1x4.bn.weight, 0)
|
||||
|
||||
def get_message(self):
|
||||
return self.message
|
||||
|
||||
def forward(self, inputs):
|
||||
x = inputs
|
||||
for i, layer in enumerate(self.layers):
|
||||
x = layer( x )
|
||||
features = self.avgpool(x)
|
||||
features = features.view(features.size(0), -1)
|
||||
logits = self.classifier(features)
|
||||
return features, logits
|
||||
119
lib/models/infers/InferMobileNetV2.py
Normal file
119
lib/models/infers/InferMobileNetV2.py
Normal file
@@ -0,0 +1,119 @@
|
||||
# MobileNetV2: Inverted Residuals and Linear Bottlenecks, CVPR 2018
|
||||
from torch import nn
|
||||
from ..initialization import initialize_resnet
|
||||
from ..SharedUtils import additive_func, parse_channel_info
|
||||
|
||||
|
||||
class ConvBNReLU(nn.Module):
|
||||
def __init__(self, in_planes, out_planes, kernel_size, stride, groups, has_bn=True, has_relu=True):
|
||||
super(ConvBNReLU, self).__init__()
|
||||
padding = (kernel_size - 1) // 2
|
||||
self.conv = nn.Conv2d(in_planes, out_planes, kernel_size, stride, padding, groups=groups, bias=False)
|
||||
if has_bn: self.bn = nn.BatchNorm2d(out_planes)
|
||||
else : self.bn = None
|
||||
if has_relu: self.relu = nn.ReLU6(inplace=True)
|
||||
else : self.relu = None
|
||||
|
||||
def forward(self, x):
|
||||
out = self.conv( x )
|
||||
if self.bn: out = self.bn ( out )
|
||||
if self.relu: out = self.relu( out )
|
||||
return out
|
||||
|
||||
|
||||
class InvertedResidual(nn.Module):
|
||||
def __init__(self, channels, stride, expand_ratio, additive):
|
||||
super(InvertedResidual, self).__init__()
|
||||
self.stride = stride
|
||||
assert stride in [1, 2], 'invalid stride : {:}'.format(stride)
|
||||
assert len(channels) in [2, 3], 'invalid channels : {:}'.format(channels)
|
||||
|
||||
if len(channels) == 2:
|
||||
layers = []
|
||||
else:
|
||||
layers = [ConvBNReLU(channels[0], channels[1], 1, 1, 1)]
|
||||
layers.extend([
|
||||
# dw
|
||||
ConvBNReLU(channels[-2], channels[-2], 3, stride, channels[-2]),
|
||||
# pw-linear
|
||||
ConvBNReLU(channels[-2], channels[-1], 1, 1, 1, True, False),
|
||||
])
|
||||
self.conv = nn.Sequential(*layers)
|
||||
self.additive = additive
|
||||
if self.additive and channels[0] != channels[-1]:
|
||||
self.shortcut = ConvBNReLU(channels[0], channels[-1], 1, 1, 1, True, False)
|
||||
else:
|
||||
self.shortcut = None
|
||||
self.out_dim = channels[-1]
|
||||
|
||||
def forward(self, x):
|
||||
out = self.conv(x)
|
||||
# if self.additive: return additive_func(out, x)
|
||||
if self.shortcut: return out + self.shortcut(x)
|
||||
else : return out
|
||||
|
||||
|
||||
class InferMobileNetV2(nn.Module):
|
||||
def __init__(self, num_classes, xchannels, xblocks, dropout):
|
||||
super(InferMobileNetV2, self).__init__()
|
||||
block = InvertedResidual
|
||||
inverted_residual_setting = [
|
||||
# t, c, n, s
|
||||
[1, 16 , 1, 1],
|
||||
[6, 24 , 2, 2],
|
||||
[6, 32 , 3, 2],
|
||||
[6, 64 , 4, 2],
|
||||
[6, 96 , 3, 1],
|
||||
[6, 160, 3, 2],
|
||||
[6, 320, 1, 1],
|
||||
]
|
||||
assert len(inverted_residual_setting) == len(xblocks), 'invalid number of layers : {:} vs {:}'.format(len(inverted_residual_setting), len(xblocks))
|
||||
for block_num, ir_setting in zip(xblocks, inverted_residual_setting):
|
||||
assert block_num <= ir_setting[2], '{:} vs {:}'.format(block_num, ir_setting)
|
||||
xchannels = parse_channel_info(xchannels)
|
||||
#for i, chs in enumerate(xchannels):
|
||||
# if i > 0: assert chs[0] == xchannels[i-1][-1], 'Layer[{:}] is invalid {:} vs {:}'.format(i, xchannels[i-1], chs)
|
||||
self.xchannels = xchannels
|
||||
self.message = 'InferMobileNetV2 : xblocks={:}'.format(xblocks)
|
||||
# building first layer
|
||||
features = [ConvBNReLU(xchannels[0][0], xchannels[0][1], 3, 2, 1)]
|
||||
last_channel_idx = 1
|
||||
|
||||
# building inverted residual blocks
|
||||
for stage, (t, c, n, s) in enumerate(inverted_residual_setting):
|
||||
for i in range(n):
|
||||
stride = s if i == 0 else 1
|
||||
additv = True if i > 0 else False
|
||||
module = block(self.xchannels[last_channel_idx], stride, t, additv)
|
||||
features.append(module)
|
||||
self.message += "\nstage={:}, ilayer={:02d}/{:02d}, block={:03d}, Cs={:}, stride={:}, expand={:}, original-C={:}".format(stage, i, n, len(features), self.xchannels[last_channel_idx], stride, t, c)
|
||||
last_channel_idx += 1
|
||||
if i + 1 == xblocks[stage]:
|
||||
out_channel = module.out_dim
|
||||
for iiL in range(i+1, n):
|
||||
last_channel_idx += 1
|
||||
self.xchannels[last_channel_idx][0] = module.out_dim
|
||||
break
|
||||
# building last several layers
|
||||
features.append(ConvBNReLU(self.xchannels[last_channel_idx][0], self.xchannels[last_channel_idx][1], 1, 1, 1))
|
||||
assert last_channel_idx + 2 == len(self.xchannels), '{:} vs {:}'.format(last_channel_idx, len(self.xchannels))
|
||||
# make it nn.Sequential
|
||||
self.features = nn.Sequential(*features)
|
||||
|
||||
# building classifier
|
||||
self.classifier = nn.Sequential(
|
||||
nn.Dropout(dropout),
|
||||
nn.Linear(self.xchannels[last_channel_idx][1], num_classes),
|
||||
)
|
||||
|
||||
# weight initialization
|
||||
self.apply( initialize_resnet )
|
||||
|
||||
def get_message(self):
|
||||
return self.message
|
||||
|
||||
def forward(self, inputs):
|
||||
features = self.features(inputs)
|
||||
vectors = features.mean([2, 3])
|
||||
predicts = self.classifier(vectors)
|
||||
return features, predicts
|
||||
5
lib/models/infers/__init__.py
Normal file
5
lib/models/infers/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from .InferCifarResNet_width import InferWidthCifarResNet
|
||||
from .InferImagenetResNet import InferImagenetResNet
|
||||
from .InferCifarResNet_depth import InferDepthCifarResNet
|
||||
from .InferCifarResNet import InferCifarResNet
|
||||
from .InferMobileNetV2 import InferMobileNetV2
|
||||
7
lib/models/infers/shared_utils.py
Normal file
7
lib/models/infers/shared_utils.py
Normal file
@@ -0,0 +1,7 @@
|
||||
# Xuanyi Dong
|
||||
|
||||
def parse_channel_info(xstring):
|
||||
blocks = xstring.split(' ')
|
||||
blocks = [x.split('-') for x in blocks]
|
||||
blocks = [[int(_) for _ in x] for x in blocks]
|
||||
return blocks
|
||||
18
lib/models/initialization.py
Normal file
18
lib/models/initialization.py
Normal file
@@ -0,0 +1,18 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
|
||||
def initialize_resnet(m):
|
||||
if isinstance(m, nn.Conv2d):
|
||||
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
|
||||
if m.bias is not None:
|
||||
nn.init.constant_(m.bias, 0)
|
||||
elif isinstance(m, nn.BatchNorm2d):
|
||||
nn.init.constant_(m.weight, 1)
|
||||
if m.bias is not None:
|
||||
nn.init.constant_(m.bias, 0)
|
||||
elif isinstance(m, nn.Linear):
|
||||
nn.init.normal_(m.weight, 0, 0.01)
|
||||
nn.init.constant_(m.bias, 0)
|
||||
|
||||
|
||||
502
lib/models/searchs/SearchCifarResNet.py
Normal file
502
lib/models/searchs/SearchCifarResNet.py
Normal file
@@ -0,0 +1,502 @@
|
||||
import math, torch
|
||||
from collections import OrderedDict
|
||||
from bisect import bisect_right
|
||||
import torch.nn as nn
|
||||
from ..initialization import initialize_resnet
|
||||
from ..SharedUtils import additive_func
|
||||
from .SoftSelect import select2withP, ChannelWiseInter
|
||||
from .SoftSelect import linear_forward
|
||||
from .SoftSelect import get_width_choices
|
||||
|
||||
|
||||
def get_depth_choices(nDepth, return_num):
|
||||
if nDepth == 2:
|
||||
choices = (1, 2)
|
||||
elif nDepth == 3:
|
||||
choices = (1, 2, 3)
|
||||
elif nDepth > 3:
|
||||
choices = list(range(1, nDepth+1, 2))
|
||||
if choices[-1] < nDepth: choices.append(nDepth)
|
||||
else:
|
||||
raise ValueError('invalid nDepth : {:}'.format(nDepth))
|
||||
if return_num: return len(choices)
|
||||
else : return choices
|
||||
|
||||
|
||||
|
||||
|
||||
def conv_forward(inputs, conv, choices):
|
||||
iC = conv.in_channels
|
||||
fill_size = list(inputs.size())
|
||||
fill_size[1] = iC - fill_size[1]
|
||||
filled = torch.zeros(fill_size, device=inputs.device)
|
||||
xinputs = torch.cat((inputs, filled), dim=1)
|
||||
outputs = conv(xinputs)
|
||||
selecteds = [outputs[:,:oC] for oC in choices]
|
||||
return selecteds
|
||||
|
||||
|
||||
class ConvBNReLU(nn.Module):
|
||||
num_conv = 1
|
||||
def __init__(self, nIn, nOut, kernel, stride, padding, bias, has_avg, has_bn, has_relu):
|
||||
super(ConvBNReLU, self).__init__()
|
||||
self.InShape = None
|
||||
self.OutShape = None
|
||||
self.choices = get_width_choices(nOut)
|
||||
self.register_buffer('choices_tensor', torch.Tensor( self.choices ))
|
||||
|
||||
if has_avg : self.avg = nn.AvgPool2d(kernel_size=2, stride=2, padding=0)
|
||||
else : self.avg = None
|
||||
self.conv = nn.Conv2d(nIn, nOut, kernel_size=kernel, stride=stride, padding=padding, dilation=1, groups=1, bias=bias)
|
||||
#if has_bn : self.bn = nn.BatchNorm2d(nOut)
|
||||
#else : self.bn = None
|
||||
self.has_bn = has_bn
|
||||
self.BNs = nn.ModuleList()
|
||||
for i, _out in enumerate(self.choices):
|
||||
self.BNs.append(nn.BatchNorm2d(_out))
|
||||
if has_relu: self.relu = nn.ReLU(inplace=True)
|
||||
else : self.relu = None
|
||||
self.in_dim = nIn
|
||||
self.out_dim = nOut
|
||||
self.search_mode = 'basic'
|
||||
|
||||
def get_flops(self, channels, check_range=True, divide=1):
|
||||
iC, oC = channels
|
||||
if check_range: assert iC <= self.conv.in_channels and oC <= self.conv.out_channels, '{:} vs {:} | {:} vs {:}'.format(iC, self.conv.in_channels, oC, self.conv.out_channels)
|
||||
assert isinstance(self.InShape, tuple) and len(self.InShape) == 2, 'invalid in-shape : {:}'.format(self.InShape)
|
||||
assert isinstance(self.OutShape, tuple) and len(self.OutShape) == 2, 'invalid out-shape : {:}'.format(self.OutShape)
|
||||
#conv_per_position_flops = self.conv.kernel_size[0] * self.conv.kernel_size[1] * iC * oC / self.conv.groups
|
||||
conv_per_position_flops = (self.conv.kernel_size[0] * self.conv.kernel_size[1] * 1.0 / self.conv.groups)
|
||||
all_positions = self.OutShape[0] * self.OutShape[1]
|
||||
flops = (conv_per_position_flops * all_positions / divide) * iC * oC
|
||||
if self.conv.bias is not None: flops += all_positions / divide
|
||||
return flops
|
||||
|
||||
def get_range(self):
|
||||
return [self.choices]
|
||||
|
||||
def forward(self, inputs):
|
||||
if self.search_mode == 'basic':
|
||||
return self.basic_forward(inputs)
|
||||
elif self.search_mode == 'search':
|
||||
return self.search_forward(inputs)
|
||||
else:
|
||||
raise ValueError('invalid search_mode = {:}'.format(self.search_mode))
|
||||
|
||||
def search_forward(self, tuple_inputs):
|
||||
assert isinstance(tuple_inputs, tuple) and len(tuple_inputs) == 5, 'invalid type input : {:}'.format( type(tuple_inputs) )
|
||||
inputs, expected_inC, probability, index, prob = tuple_inputs
|
||||
index, prob = torch.squeeze(index).tolist(), torch.squeeze(prob)
|
||||
probability = torch.squeeze(probability)
|
||||
assert len(index) == 2, 'invalid length : {:}'.format(index)
|
||||
# compute expected flop
|
||||
#coordinates = torch.arange(self.x_range[0], self.x_range[1]+1).type_as(probability)
|
||||
expected_outC = (self.choices_tensor * probability).sum()
|
||||
expected_flop = self.get_flops([expected_inC, expected_outC], False, 1e6)
|
||||
if self.avg : out = self.avg( inputs )
|
||||
else : out = inputs
|
||||
# convolutional layer
|
||||
out_convs = conv_forward(out, self.conv, [self.choices[i] for i in index])
|
||||
out_bns = [self.BNs[idx](out_conv) for idx, out_conv in zip(index, out_convs)]
|
||||
# merge
|
||||
out_channel = max([x.size(1) for x in out_bns])
|
||||
outA = ChannelWiseInter(out_bns[0], out_channel)
|
||||
outB = ChannelWiseInter(out_bns[1], out_channel)
|
||||
out = outA * prob[0] + outB * prob[1]
|
||||
#out = additive_func(out_bns[0]*prob[0], out_bns[1]*prob[1])
|
||||
|
||||
if self.relu: out = self.relu( out )
|
||||
else : out = out
|
||||
return out, expected_outC, expected_flop
|
||||
|
||||
def basic_forward(self, inputs):
|
||||
if self.avg : out = self.avg( inputs )
|
||||
else : out = inputs
|
||||
conv = self.conv( out )
|
||||
if self.has_bn:out= self.BNs[-1]( conv )
|
||||
else : out = conv
|
||||
if self.relu: out = self.relu( out )
|
||||
else : out = out
|
||||
if self.InShape is None:
|
||||
self.InShape = (inputs.size(-2), inputs.size(-1))
|
||||
self.OutShape = (out.size(-2) , out.size(-1))
|
||||
return out
|
||||
|
||||
|
||||
class ResNetBasicblock(nn.Module):
|
||||
expansion = 1
|
||||
num_conv = 2
|
||||
def __init__(self, inplanes, planes, stride):
|
||||
super(ResNetBasicblock, self).__init__()
|
||||
assert stride == 1 or stride == 2, 'invalid stride {:}'.format(stride)
|
||||
self.conv_a = ConvBNReLU(inplanes, planes, 3, stride, 1, False, has_avg=False, has_bn=True, has_relu=True)
|
||||
self.conv_b = ConvBNReLU( planes, planes, 3, 1, 1, False, has_avg=False, has_bn=True, has_relu=False)
|
||||
if stride == 2:
|
||||
self.downsample = ConvBNReLU(inplanes, planes, 1, 1, 0, False, has_avg=True, has_bn=False, has_relu=False)
|
||||
elif inplanes != planes:
|
||||
self.downsample = ConvBNReLU(inplanes, planes, 1, 1, 0, False, has_avg=False,has_bn=True , has_relu=False)
|
||||
else:
|
||||
self.downsample = None
|
||||
self.out_dim = planes
|
||||
self.search_mode = 'basic'
|
||||
|
||||
def get_range(self):
|
||||
return self.conv_a.get_range() + self.conv_b.get_range()
|
||||
|
||||
def get_flops(self, channels):
|
||||
assert len(channels) == 3, 'invalid channels : {:}'.format(channels)
|
||||
flop_A = self.conv_a.get_flops([channels[0], channels[1]])
|
||||
flop_B = self.conv_b.get_flops([channels[1], channels[2]])
|
||||
if hasattr(self.downsample, 'get_flops'):
|
||||
flop_C = self.downsample.get_flops([channels[0], channels[-1]])
|
||||
else:
|
||||
flop_C = 0
|
||||
if channels[0] != channels[-1] and self.downsample is None: # this short-cut will be added during the infer-train
|
||||
flop_C = channels[0] * channels[-1] * self.conv_b.OutShape[0] * self.conv_b.OutShape[1]
|
||||
return flop_A + flop_B + flop_C
|
||||
|
||||
def forward(self, inputs):
|
||||
if self.search_mode == 'basic' : return self.basic_forward(inputs)
|
||||
elif self.search_mode == 'search': return self.search_forward(inputs)
|
||||
else: raise ValueError('invalid search_mode = {:}'.format(self.search_mode))
|
||||
|
||||
def search_forward(self, tuple_inputs):
|
||||
assert isinstance(tuple_inputs, tuple) and len(tuple_inputs) == 5, 'invalid type input : {:}'.format( type(tuple_inputs) )
|
||||
inputs, expected_inC, probability, indexes, probs = tuple_inputs
|
||||
assert indexes.size(0) == 2 and probs.size(0) == 2 and probability.size(0) == 2
|
||||
out_a, expected_inC_a, expected_flop_a = self.conv_a( (inputs, expected_inC , probability[0], indexes[0], probs[0]) )
|
||||
out_b, expected_inC_b, expected_flop_b = self.conv_b( (out_a , expected_inC_a, probability[1], indexes[1], probs[1]) )
|
||||
if self.downsample is not None:
|
||||
residual, _, expected_flop_c = self.downsample( (inputs, expected_inC , probability[1], indexes[1], probs[1]) )
|
||||
else:
|
||||
residual, expected_flop_c = inputs, 0
|
||||
out = additive_func(residual, out_b)
|
||||
return out, expected_inC_b, sum([expected_flop_a, expected_flop_b, expected_flop_c])
|
||||
|
||||
def basic_forward(self, inputs):
|
||||
basicblock = self.conv_a(inputs)
|
||||
basicblock = self.conv_b(basicblock)
|
||||
if self.downsample is not None: residual = self.downsample(inputs)
|
||||
else : residual = inputs
|
||||
out = additive_func(residual, basicblock)
|
||||
return nn.functional.relu(out, inplace=True)
|
||||
|
||||
|
||||
|
||||
class ResNetBottleneck(nn.Module):
|
||||
expansion = 4
|
||||
num_conv = 3
|
||||
def __init__(self, inplanes, planes, stride):
|
||||
super(ResNetBottleneck, self).__init__()
|
||||
assert stride == 1 or stride == 2, 'invalid stride {:}'.format(stride)
|
||||
self.conv_1x1 = ConvBNReLU(inplanes, planes, 1, 1, 0, False, has_avg=False, has_bn=True, has_relu=True)
|
||||
self.conv_3x3 = ConvBNReLU( planes, planes, 3, stride, 1, False, has_avg=False, has_bn=True, has_relu=True)
|
||||
self.conv_1x4 = ConvBNReLU(planes, planes*self.expansion, 1, 1, 0, False, has_avg=False, has_bn=True, has_relu=False)
|
||||
if stride == 2:
|
||||
self.downsample = ConvBNReLU(inplanes, planes*self.expansion, 1, 1, 0, False, has_avg=True, has_bn=False, has_relu=False)
|
||||
elif inplanes != planes*self.expansion:
|
||||
self.downsample = ConvBNReLU(inplanes, planes*self.expansion, 1, 1, 0, False, has_avg=False,has_bn=True , has_relu=False)
|
||||
else:
|
||||
self.downsample = None
|
||||
self.out_dim = planes * self.expansion
|
||||
self.search_mode = 'basic'
|
||||
|
||||
def get_range(self):
|
||||
return self.conv_1x1.get_range() + self.conv_3x3.get_range() + self.conv_1x4.get_range()
|
||||
|
||||
def get_flops(self, channels):
|
||||
assert len(channels) == 4, 'invalid channels : {:}'.format(channels)
|
||||
flop_A = self.conv_1x1.get_flops([channels[0], channels[1]])
|
||||
flop_B = self.conv_3x3.get_flops([channels[1], channels[2]])
|
||||
flop_C = self.conv_1x4.get_flops([channels[2], channels[3]])
|
||||
if hasattr(self.downsample, 'get_flops'):
|
||||
flop_D = self.downsample.get_flops([channels[0], channels[-1]])
|
||||
else:
|
||||
flop_D = 0
|
||||
if channels[0] != channels[-1] and self.downsample is None: # this short-cut will be added during the infer-train
|
||||
flop_D = channels[0] * channels[-1] * self.conv_1x4.OutShape[0] * self.conv_1x4.OutShape[1]
|
||||
return flop_A + flop_B + flop_C + flop_D
|
||||
|
||||
def forward(self, inputs):
|
||||
if self.search_mode == 'basic' : return self.basic_forward(inputs)
|
||||
elif self.search_mode == 'search': return self.search_forward(inputs)
|
||||
else: raise ValueError('invalid search_mode = {:}'.format(self.search_mode))
|
||||
|
||||
def basic_forward(self, inputs):
|
||||
bottleneck = self.conv_1x1(inputs)
|
||||
bottleneck = self.conv_3x3(bottleneck)
|
||||
bottleneck = self.conv_1x4(bottleneck)
|
||||
if self.downsample is not None: residual = self.downsample(inputs)
|
||||
else : residual = inputs
|
||||
out = additive_func(residual, bottleneck)
|
||||
return nn.functional.relu(out, inplace=True)
|
||||
|
||||
def search_forward(self, tuple_inputs):
|
||||
assert isinstance(tuple_inputs, tuple) and len(tuple_inputs) == 5, 'invalid type input : {:}'.format( type(tuple_inputs) )
|
||||
inputs, expected_inC, probability, indexes, probs = tuple_inputs
|
||||
assert indexes.size(0) == 3 and probs.size(0) == 3 and probability.size(0) == 3
|
||||
out_1x1, expected_inC_1x1, expected_flop_1x1 = self.conv_1x1( (inputs, expected_inC , probability[0], indexes[0], probs[0]) )
|
||||
out_3x3, expected_inC_3x3, expected_flop_3x3 = self.conv_3x3( (out_1x1,expected_inC_1x1, probability[1], indexes[1], probs[1]) )
|
||||
out_1x4, expected_inC_1x4, expected_flop_1x4 = self.conv_1x4( (out_3x3,expected_inC_3x3, probability[2], indexes[2], probs[2]) )
|
||||
if self.downsample is not None:
|
||||
residual, _, expected_flop_c = self.downsample( (inputs, expected_inC , probability[2], indexes[2], probs[2]) )
|
||||
else:
|
||||
residual, expected_flop_c = inputs, 0
|
||||
out = additive_func(residual, out_1x4)
|
||||
return out, expected_inC_1x4, sum([expected_flop_1x1, expected_flop_3x3, expected_flop_1x4, expected_flop_c])
|
||||
|
||||
|
||||
|
||||
class SearchShapeCifarResNet(nn.Module):
|
||||
|
||||
def __init__(self, block_name, depth, num_classes):
|
||||
super(SearchShapeCifarResNet, self).__init__()
|
||||
|
||||
#Model type specifies number of layers for CIFAR-10 and CIFAR-100 model
|
||||
if block_name == 'ResNetBasicblock':
|
||||
block = ResNetBasicblock
|
||||
assert (depth - 2) % 6 == 0, 'depth should be one of 20, 32, 44, 56, 110'
|
||||
layer_blocks = (depth - 2) // 6
|
||||
elif block_name == 'ResNetBottleneck':
|
||||
block = ResNetBottleneck
|
||||
assert (depth - 2) % 9 == 0, 'depth should be one of 164'
|
||||
layer_blocks = (depth - 2) // 9
|
||||
else:
|
||||
raise ValueError('invalid block : {:}'.format(block_name))
|
||||
|
||||
self.message = 'SearchShapeCifarResNet : Depth : {:} , Layers for each block : {:}'.format(depth, layer_blocks)
|
||||
self.num_classes = num_classes
|
||||
self.channels = [16]
|
||||
self.layers = nn.ModuleList( [ ConvBNReLU(3, 16, 3, 1, 1, False, has_avg=False, has_bn=True, has_relu=True) ] )
|
||||
self.InShape = None
|
||||
self.depth_info = OrderedDict()
|
||||
self.depth_at_i = OrderedDict()
|
||||
for stage in range(3):
|
||||
cur_block_choices = get_depth_choices(layer_blocks, False)
|
||||
assert cur_block_choices[-1] == layer_blocks, 'stage={:}, {:} vs {:}'.format(stage, cur_block_choices, layer_blocks)
|
||||
self.message += "\nstage={:} ::: depth-block-choices={:} for {:} blocks.".format(stage, cur_block_choices, layer_blocks)
|
||||
block_choices, xstart = [], len(self.layers)
|
||||
for iL in range(layer_blocks):
|
||||
iC = self.channels[-1]
|
||||
planes = 16 * (2**stage)
|
||||
stride = 2 if stage > 0 and iL == 0 else 1
|
||||
module = block(iC, planes, stride)
|
||||
self.channels.append( module.out_dim )
|
||||
self.layers.append ( module )
|
||||
self.message += "\nstage={:}, ilayer={:02d}/{:02d}, block={:03d}, iC={:3d}, oC={:3d}, stride={:}".format(stage, iL, layer_blocks, len(self.layers)-1, iC, module.out_dim, stride)
|
||||
# added for depth
|
||||
layer_index = len(self.layers) - 1
|
||||
if iL + 1 in cur_block_choices: block_choices.append( layer_index )
|
||||
if iL + 1 == layer_blocks:
|
||||
self.depth_info[layer_index] = {'choices': block_choices,
|
||||
'stage' : stage,
|
||||
'xstart' : xstart}
|
||||
self.depth_info_list = []
|
||||
for xend, info in self.depth_info.items():
|
||||
self.depth_info_list.append( (xend, info) )
|
||||
xstart, xstage = info['xstart'], info['stage']
|
||||
for ilayer in range(xstart, xend+1):
|
||||
idx = bisect_right(info['choices'], ilayer-1)
|
||||
self.depth_at_i[ilayer] = (xstage, idx)
|
||||
|
||||
self.avgpool = nn.AvgPool2d(8)
|
||||
self.classifier = nn.Linear(module.out_dim, num_classes)
|
||||
self.InShape = None
|
||||
self.tau = -1
|
||||
self.search_mode = 'basic'
|
||||
#assert sum(x.num_conv for x in self.layers) + 1 == depth, 'invalid depth check {:} vs {:}'.format(sum(x.num_conv for x in self.layers)+1, depth)
|
||||
|
||||
# parameters for width
|
||||
self.Ranges = []
|
||||
self.layer2indexRange = []
|
||||
for i, layer in enumerate(self.layers):
|
||||
start_index = len(self.Ranges)
|
||||
self.Ranges += layer.get_range()
|
||||
self.layer2indexRange.append( (start_index, len(self.Ranges)) )
|
||||
assert len(self.Ranges) + 1 == depth, 'invalid depth check {:} vs {:}'.format(len(self.Ranges) + 1, depth)
|
||||
|
||||
self.register_parameter('width_attentions', nn.Parameter(torch.Tensor(len(self.Ranges), get_width_choices(None))))
|
||||
self.register_parameter('depth_attentions', nn.Parameter(torch.Tensor(3, get_depth_choices(layer_blocks, True))))
|
||||
nn.init.normal_(self.width_attentions, 0, 0.01)
|
||||
nn.init.normal_(self.depth_attentions, 0, 0.01)
|
||||
self.apply(initialize_resnet)
|
||||
|
||||
def arch_parameters(self, LR=None):
|
||||
if LR is None:
|
||||
return [self.width_attentions, self.depth_attentions]
|
||||
else:
|
||||
return [
|
||||
{"params": self.width_attentions, "lr": LR},
|
||||
{"params": self.depth_attentions, "lr": LR},
|
||||
]
|
||||
|
||||
def base_parameters(self):
|
||||
return list(self.layers.parameters()) + list(self.avgpool.parameters()) + list(self.classifier.parameters())
|
||||
|
||||
def get_flop(self, mode, config_dict, extra_info):
|
||||
if config_dict is not None: config_dict = config_dict.copy()
|
||||
# select channels
|
||||
channels = [3]
|
||||
for i, weight in enumerate(self.width_attentions):
|
||||
if mode == 'genotype':
|
||||
with torch.no_grad():
|
||||
probe = nn.functional.softmax(weight, dim=0)
|
||||
C = self.Ranges[i][ torch.argmax(probe).item() ]
|
||||
elif mode == 'max':
|
||||
C = self.Ranges[i][-1]
|
||||
elif mode == 'fix':
|
||||
C = int( math.sqrt( extra_info ) * self.Ranges[i][-1] )
|
||||
elif mode == 'random':
|
||||
assert isinstance(extra_info, float), 'invalid extra_info : {:}'.format(extra_info)
|
||||
with torch.no_grad():
|
||||
prob = nn.functional.softmax(weight, dim=0)
|
||||
approximate_C = int( math.sqrt( extra_info ) * self.Ranges[i][-1] )
|
||||
for j in range(prob.size(0)):
|
||||
prob[j] = 1 / (abs(j - (approximate_C-self.Ranges[i][j])) + 0.2)
|
||||
C = self.Ranges[i][ torch.multinomial(prob, 1, False).item() ]
|
||||
else:
|
||||
raise ValueError('invalid mode : {:}'.format(mode))
|
||||
channels.append( C )
|
||||
# select depth
|
||||
if mode == 'genotype':
|
||||
with torch.no_grad():
|
||||
depth_probs = nn.functional.softmax(self.depth_attentions, dim=1)
|
||||
choices = torch.argmax(depth_probs, dim=1).cpu().tolist()
|
||||
elif mode == 'max' or mode == 'fix':
|
||||
choices = [depth_probs.size(1)-1 for _ in range(depth_probs.size(0))]
|
||||
elif mode == 'random':
|
||||
with torch.no_grad():
|
||||
depth_probs = nn.functional.softmax(self.depth_attentions, dim=1)
|
||||
choices = torch.multinomial(depth_probs, 1, False).cpu().tolist()
|
||||
else:
|
||||
raise ValueError('invalid mode : {:}'.format(mode))
|
||||
selected_layers = []
|
||||
for choice, xvalue in zip(choices, self.depth_info_list):
|
||||
xtemp = xvalue[1]['choices'][choice] - xvalue[1]['xstart'] + 1
|
||||
selected_layers.append(xtemp)
|
||||
flop = 0
|
||||
for i, layer in enumerate(self.layers):
|
||||
s, e = self.layer2indexRange[i]
|
||||
xchl = tuple( channels[s:e+1] )
|
||||
if i in self.depth_at_i:
|
||||
xstagei, xatti = self.depth_at_i[i]
|
||||
if xatti <= choices[xstagei]: # leave this depth
|
||||
flop+= layer.get_flops(xchl)
|
||||
else:
|
||||
flop+= 0 # do not use this layer
|
||||
else:
|
||||
flop+= layer.get_flops(xchl)
|
||||
# the last fc layer
|
||||
flop += channels[-1] * self.classifier.out_features
|
||||
if config_dict is None:
|
||||
return flop / 1e6
|
||||
else:
|
||||
config_dict['xchannels'] = channels
|
||||
config_dict['xblocks'] = selected_layers
|
||||
config_dict['super_type'] = 'infer-shape'
|
||||
config_dict['estimated_FLOP'] = flop / 1e6
|
||||
return flop / 1e6, config_dict
|
||||
|
||||
def get_arch_info(self):
|
||||
string = "for depth and width, there are {:} + {:} attention probabilities.".format(len(self.depth_attentions), len(self.width_attentions))
|
||||
string+= '\n{:}'.format(self.depth_info)
|
||||
discrepancy = []
|
||||
with torch.no_grad():
|
||||
for i, att in enumerate(self.depth_attentions):
|
||||
prob = nn.functional.softmax(att, dim=0)
|
||||
prob = prob.cpu() ; selc = prob.argmax().item() ; prob = prob.tolist()
|
||||
prob = ['{:.3f}'.format(x) for x in prob]
|
||||
xstring = '{:03d}/{:03d}-th : {:}'.format(i, len(self.depth_attentions), ' '.join(prob))
|
||||
logt = ['{:.4f}'.format(x) for x in att.cpu().tolist()]
|
||||
xstring += ' || {:17s}'.format(' '.join(logt))
|
||||
prob = sorted( [float(x) for x in prob] )
|
||||
disc = prob[-1] - prob[-2]
|
||||
xstring += ' || discrepancy={:.2f} || select={:}/{:}'.format(disc, selc, len(prob))
|
||||
discrepancy.append( disc )
|
||||
string += '\n{:}'.format(xstring)
|
||||
string += '\n-----------------------------------------------'
|
||||
for i, att in enumerate(self.width_attentions):
|
||||
prob = nn.functional.softmax(att, dim=0)
|
||||
prob = prob.cpu() ; selc = prob.argmax().item() ; prob = prob.tolist()
|
||||
prob = ['{:.3f}'.format(x) for x in prob]
|
||||
xstring = '{:03d}/{:03d}-th : {:}'.format(i, len(self.width_attentions), ' '.join(prob))
|
||||
logt = ['{:.3f}'.format(x) for x in att.cpu().tolist()]
|
||||
xstring += ' || {:52s}'.format(' '.join(logt))
|
||||
prob = sorted( [float(x) for x in prob] )
|
||||
disc = prob[-1] - prob[-2]
|
||||
xstring += ' || dis={:.2f} || select={:}/{:}'.format(disc, selc, len(prob))
|
||||
discrepancy.append( disc )
|
||||
string += '\n{:}'.format(xstring)
|
||||
return string, discrepancy
|
||||
|
||||
def set_tau(self, tau_max, tau_min, epoch_ratio):
|
||||
assert epoch_ratio >= 0 and epoch_ratio <= 1, 'invalid epoch-ratio : {:}'.format(epoch_ratio)
|
||||
tau = tau_min + (tau_max-tau_min) * (1 + math.cos(math.pi * epoch_ratio)) / 2
|
||||
self.tau = tau
|
||||
|
||||
def get_message(self):
|
||||
return self.message
|
||||
|
||||
def forward(self, inputs):
|
||||
if self.search_mode == 'basic':
|
||||
return self.basic_forward(inputs)
|
||||
elif self.search_mode == 'search':
|
||||
return self.search_forward(inputs)
|
||||
else:
|
||||
raise ValueError('invalid search_mode = {:}'.format(self.search_mode))
|
||||
|
||||
def search_forward(self, inputs):
|
||||
flop_width_probs = nn.functional.softmax(self.width_attentions, dim=1)
|
||||
flop_depth_probs = nn.functional.softmax(self.depth_attentions, dim=1)
|
||||
flop_depth_probs = torch.flip( torch.cumsum( torch.flip(flop_depth_probs, [1]), 1 ), [1] )
|
||||
selected_widths, selected_width_probs = select2withP(self.width_attentions, self.tau)
|
||||
selected_depth_probs = select2withP(self.depth_attentions, self.tau, True)
|
||||
with torch.no_grad():
|
||||
selected_widths = selected_widths.cpu()
|
||||
|
||||
x, last_channel_idx, expected_inC, flops = inputs, 0, 3, []
|
||||
feature_maps = []
|
||||
for i, layer in enumerate(self.layers):
|
||||
selected_w_index = selected_widths [last_channel_idx: last_channel_idx+layer.num_conv]
|
||||
selected_w_probs = selected_width_probs[last_channel_idx: last_channel_idx+layer.num_conv]
|
||||
layer_prob = flop_width_probs [last_channel_idx: last_channel_idx+layer.num_conv]
|
||||
x, expected_inC, expected_flop = layer( (x, expected_inC, layer_prob, selected_w_index, selected_w_probs) )
|
||||
feature_maps.append( x )
|
||||
last_channel_idx += layer.num_conv
|
||||
if i in self.depth_info: # aggregate the information
|
||||
choices = self.depth_info[i]['choices']
|
||||
xstagei = self.depth_info[i]['stage']
|
||||
#print ('iL={:}, choices={:}, stage={:}, probs={:}'.format(i, choices, xstagei, selected_depth_probs[xstagei].cpu().tolist()))
|
||||
#for A, W in zip(choices, selected_depth_probs[xstagei]):
|
||||
# print('Size = {:}, W = {:}'.format(feature_maps[A].size(), W))
|
||||
possible_tensors = []
|
||||
max_C = max( feature_maps[A].size(1) for A in choices )
|
||||
for tempi, A in enumerate(choices):
|
||||
xtensor = ChannelWiseInter(feature_maps[A], max_C)
|
||||
#drop_ratio = 1-(tempi+1.0)/len(choices)
|
||||
#xtensor = drop_path(xtensor, drop_ratio)
|
||||
possible_tensors.append( xtensor )
|
||||
weighted_sum = sum( xtensor * W for xtensor, W in zip(possible_tensors, selected_depth_probs[xstagei]) )
|
||||
x = weighted_sum
|
||||
|
||||
if i in self.depth_at_i:
|
||||
xstagei, xatti = self.depth_at_i[i]
|
||||
x_expected_flop = flop_depth_probs[xstagei, xatti] * expected_flop
|
||||
else:
|
||||
x_expected_flop = expected_flop
|
||||
flops.append( x_expected_flop )
|
||||
flops.append( expected_inC * (self.classifier.out_features*1.0/1e6) )
|
||||
features = self.avgpool(x)
|
||||
features = features.view(features.size(0), -1)
|
||||
logits = linear_forward(features, self.classifier)
|
||||
return logits, torch.stack( [sum(flops)] )
|
||||
|
||||
def basic_forward(self, inputs):
|
||||
if self.InShape is None: self.InShape = (inputs.size(-2), inputs.size(-1))
|
||||
x = inputs
|
||||
for i, layer in enumerate(self.layers):
|
||||
x = layer( x )
|
||||
features = self.avgpool(x)
|
||||
features = features.view(features.size(0), -1)
|
||||
logits = self.classifier(features)
|
||||
return features, logits
|
||||
337
lib/models/searchs/SearchCifarResNet_depth.py
Normal file
337
lib/models/searchs/SearchCifarResNet_depth.py
Normal file
@@ -0,0 +1,337 @@
|
||||
import math, torch
|
||||
from collections import OrderedDict
|
||||
from bisect import bisect_right
|
||||
import torch.nn as nn
|
||||
from ..initialization import initialize_resnet
|
||||
from ..SharedUtils import additive_func
|
||||
from .SoftSelect import select2withP, ChannelWiseInter
|
||||
from .SoftSelect import linear_forward
|
||||
from .SoftSelect import get_width_choices
|
||||
|
||||
|
||||
def get_depth_choices(nDepth, return_num):
|
||||
if nDepth == 2:
|
||||
choices = (1, 2)
|
||||
elif nDepth == 3:
|
||||
choices = (1, 2, 3)
|
||||
elif nDepth > 3:
|
||||
choices = list(range(1, nDepth+1, 2))
|
||||
if choices[-1] < nDepth: choices.append(nDepth)
|
||||
else:
|
||||
raise ValueError('invalid nDepth : {:}'.format(nDepth))
|
||||
if return_num: return len(choices)
|
||||
else : return choices
|
||||
|
||||
|
||||
|
||||
class ConvBNReLU(nn.Module):
|
||||
num_conv = 1
|
||||
def __init__(self, nIn, nOut, kernel, stride, padding, bias, has_avg, has_bn, has_relu):
|
||||
super(ConvBNReLU, self).__init__()
|
||||
self.InShape = None
|
||||
self.OutShape = None
|
||||
self.choices = get_width_choices(nOut)
|
||||
self.register_buffer('choices_tensor', torch.Tensor( self.choices ))
|
||||
|
||||
if has_avg : self.avg = nn.AvgPool2d(kernel_size=2, stride=2, padding=0)
|
||||
else : self.avg = None
|
||||
self.conv = nn.Conv2d(nIn, nOut, kernel_size=kernel, stride=stride, padding=padding, dilation=1, groups=1, bias=bias)
|
||||
if has_bn : self.bn = nn.BatchNorm2d(nOut)
|
||||
else : self.bn = None
|
||||
if has_relu: self.relu = nn.ReLU(inplace=False)
|
||||
else : self.relu = None
|
||||
self.in_dim = nIn
|
||||
self.out_dim = nOut
|
||||
|
||||
def get_flops(self, divide=1):
|
||||
iC, oC = self.in_dim, self.out_dim
|
||||
assert iC <= self.conv.in_channels and oC <= self.conv.out_channels, '{:} vs {:} | {:} vs {:}'.format(iC, self.conv.in_channels, oC, self.conv.out_channels)
|
||||
assert isinstance(self.InShape, tuple) and len(self.InShape) == 2, 'invalid in-shape : {:}'.format(self.InShape)
|
||||
assert isinstance(self.OutShape, tuple) and len(self.OutShape) == 2, 'invalid out-shape : {:}'.format(self.OutShape)
|
||||
#conv_per_position_flops = self.conv.kernel_size[0] * self.conv.kernel_size[1] * iC * oC / self.conv.groups
|
||||
conv_per_position_flops = (self.conv.kernel_size[0] * self.conv.kernel_size[1] * 1.0 / self.conv.groups)
|
||||
all_positions = self.OutShape[0] * self.OutShape[1]
|
||||
flops = (conv_per_position_flops * all_positions / divide) * iC * oC
|
||||
if self.conv.bias is not None: flops += all_positions / divide
|
||||
return flops
|
||||
|
||||
def forward(self, inputs):
|
||||
if self.avg : out = self.avg( inputs )
|
||||
else : out = inputs
|
||||
conv = self.conv( out )
|
||||
if self.bn : out = self.bn( conv )
|
||||
else : out = conv
|
||||
if self.relu: out = self.relu( out )
|
||||
else : out = out
|
||||
if self.InShape is None:
|
||||
self.InShape = (inputs.size(-2), inputs.size(-1))
|
||||
self.OutShape = (out.size(-2) , out.size(-1))
|
||||
return out
|
||||
|
||||
|
||||
class ResNetBasicblock(nn.Module):
|
||||
expansion = 1
|
||||
num_conv = 2
|
||||
def __init__(self, inplanes, planes, stride):
|
||||
super(ResNetBasicblock, self).__init__()
|
||||
assert stride == 1 or stride == 2, 'invalid stride {:}'.format(stride)
|
||||
self.conv_a = ConvBNReLU(inplanes, planes, 3, stride, 1, False, has_avg=False, has_bn=True, has_relu=True)
|
||||
self.conv_b = ConvBNReLU( planes, planes, 3, 1, 1, False, has_avg=False, has_bn=True, has_relu=False)
|
||||
if stride == 2:
|
||||
self.downsample = ConvBNReLU(inplanes, planes, 1, 1, 0, False, has_avg=True, has_bn=False, has_relu=False)
|
||||
elif inplanes != planes:
|
||||
self.downsample = ConvBNReLU(inplanes, planes, 1, 1, 0, False, has_avg=False,has_bn=True , has_relu=False)
|
||||
else:
|
||||
self.downsample = None
|
||||
self.out_dim = planes
|
||||
self.search_mode = 'basic'
|
||||
|
||||
def get_flops(self, divide=1):
|
||||
flop_A = self.conv_a.get_flops(divide)
|
||||
flop_B = self.conv_b.get_flops(divide)
|
||||
if hasattr(self.downsample, 'get_flops'):
|
||||
flop_C = self.downsample.get_flops(divide)
|
||||
else:
|
||||
flop_C = 0
|
||||
return flop_A + flop_B + flop_C
|
||||
|
||||
def forward(self, inputs):
|
||||
basicblock = self.conv_a(inputs)
|
||||
basicblock = self.conv_b(basicblock)
|
||||
if self.downsample is not None: residual = self.downsample(inputs)
|
||||
else : residual = inputs
|
||||
out = additive_func(residual, basicblock)
|
||||
return nn.functional.relu(out, inplace=True)
|
||||
|
||||
|
||||
|
||||
class ResNetBottleneck(nn.Module):
|
||||
expansion = 4
|
||||
num_conv = 3
|
||||
def __init__(self, inplanes, planes, stride):
|
||||
super(ResNetBottleneck, self).__init__()
|
||||
assert stride == 1 or stride == 2, 'invalid stride {:}'.format(stride)
|
||||
self.conv_1x1 = ConvBNReLU(inplanes, planes, 1, 1, 0, False, has_avg=False, has_bn=True, has_relu=True)
|
||||
self.conv_3x3 = ConvBNReLU( planes, planes, 3, stride, 1, False, has_avg=False, has_bn=True, has_relu=True)
|
||||
self.conv_1x4 = ConvBNReLU(planes, planes*self.expansion, 1, 1, 0, False, has_avg=False, has_bn=True, has_relu=False)
|
||||
if stride == 2:
|
||||
self.downsample = ConvBNReLU(inplanes, planes*self.expansion, 1, 1, 0, False, has_avg=True, has_bn=False, has_relu=False)
|
||||
elif inplanes != planes*self.expansion:
|
||||
self.downsample = ConvBNReLU(inplanes, planes*self.expansion, 1, 1, 0, False, has_avg=False,has_bn=True , has_relu=False)
|
||||
else:
|
||||
self.downsample = None
|
||||
self.out_dim = planes * self.expansion
|
||||
self.search_mode = 'basic'
|
||||
|
||||
def get_range(self):
|
||||
return self.conv_1x1.get_range() + self.conv_3x3.get_range() + self.conv_1x4.get_range()
|
||||
|
||||
def get_flops(self, divide):
|
||||
flop_A = self.conv_1x1.get_flops(divide)
|
||||
flop_B = self.conv_3x3.get_flops(divide)
|
||||
flop_C = self.conv_1x4.get_flops(divide)
|
||||
if hasattr(self.downsample, 'get_flops'):
|
||||
flop_D = self.downsample.get_flops(divide)
|
||||
else:
|
||||
flop_D = 0
|
||||
return flop_A + flop_B + flop_C + flop_D
|
||||
|
||||
def forward(self, inputs):
|
||||
bottleneck = self.conv_1x1(inputs)
|
||||
bottleneck = self.conv_3x3(bottleneck)
|
||||
bottleneck = self.conv_1x4(bottleneck)
|
||||
if self.downsample is not None: residual = self.downsample(inputs)
|
||||
else : residual = inputs
|
||||
out = additive_func(residual, bottleneck)
|
||||
return nn.functional.relu(out, inplace=True)
|
||||
|
||||
|
||||
class SearchDepthCifarResNet(nn.Module):
|
||||
|
||||
def __init__(self, block_name, depth, num_classes):
|
||||
super(SearchDepthCifarResNet, self).__init__()
|
||||
|
||||
#Model type specifies number of layers for CIFAR-10 and CIFAR-100 model
|
||||
if block_name == 'ResNetBasicblock':
|
||||
block = ResNetBasicblock
|
||||
assert (depth - 2) % 6 == 0, 'depth should be one of 20, 32, 44, 56, 110'
|
||||
layer_blocks = (depth - 2) // 6
|
||||
elif block_name == 'ResNetBottleneck':
|
||||
block = ResNetBottleneck
|
||||
assert (depth - 2) % 9 == 0, 'depth should be one of 164'
|
||||
layer_blocks = (depth - 2) // 9
|
||||
else:
|
||||
raise ValueError('invalid block : {:}'.format(block_name))
|
||||
|
||||
self.message = 'SearchShapeCifarResNet : Depth : {:} , Layers for each block : {:}'.format(depth, layer_blocks)
|
||||
self.num_classes = num_classes
|
||||
self.channels = [16]
|
||||
self.layers = nn.ModuleList( [ ConvBNReLU(3, 16, 3, 1, 1, False, has_avg=False, has_bn=True, has_relu=True) ] )
|
||||
self.InShape = None
|
||||
self.depth_info = OrderedDict()
|
||||
self.depth_at_i = OrderedDict()
|
||||
for stage in range(3):
|
||||
cur_block_choices = get_depth_choices(layer_blocks, False)
|
||||
assert cur_block_choices[-1] == layer_blocks, 'stage={:}, {:} vs {:}'.format(stage, cur_block_choices, layer_blocks)
|
||||
self.message += "\nstage={:} ::: depth-block-choices={:} for {:} blocks.".format(stage, cur_block_choices, layer_blocks)
|
||||
block_choices, xstart = [], len(self.layers)
|
||||
for iL in range(layer_blocks):
|
||||
iC = self.channels[-1]
|
||||
planes = 16 * (2**stage)
|
||||
stride = 2 if stage > 0 and iL == 0 else 1
|
||||
module = block(iC, planes, stride)
|
||||
self.channels.append( module.out_dim )
|
||||
self.layers.append ( module )
|
||||
self.message += "\nstage={:}, ilayer={:02d}/{:02d}, block={:03d}, iC={:3d}, oC={:3d}, stride={:}".format(stage, iL, layer_blocks, len(self.layers)-1, iC, module.out_dim, stride)
|
||||
# added for depth
|
||||
layer_index = len(self.layers) - 1
|
||||
if iL + 1 in cur_block_choices: block_choices.append( layer_index )
|
||||
if iL + 1 == layer_blocks:
|
||||
self.depth_info[layer_index] = {'choices': block_choices,
|
||||
'stage' : stage,
|
||||
'xstart' : xstart}
|
||||
self.depth_info_list = []
|
||||
for xend, info in self.depth_info.items():
|
||||
self.depth_info_list.append( (xend, info) )
|
||||
xstart, xstage = info['xstart'], info['stage']
|
||||
for ilayer in range(xstart, xend+1):
|
||||
idx = bisect_right(info['choices'], ilayer-1)
|
||||
self.depth_at_i[ilayer] = (xstage, idx)
|
||||
|
||||
self.avgpool = nn.AvgPool2d(8)
|
||||
self.classifier = nn.Linear(module.out_dim, num_classes)
|
||||
self.InShape = None
|
||||
self.tau = -1
|
||||
self.search_mode = 'basic'
|
||||
#assert sum(x.num_conv for x in self.layers) + 1 == depth, 'invalid depth check {:} vs {:}'.format(sum(x.num_conv for x in self.layers)+1, depth)
|
||||
|
||||
|
||||
self.register_parameter('depth_attentions', nn.Parameter(torch.Tensor(3, get_depth_choices(layer_blocks, True))))
|
||||
nn.init.normal_(self.depth_attentions, 0, 0.01)
|
||||
self.apply(initialize_resnet)
|
||||
|
||||
def arch_parameters(self):
|
||||
return [self.depth_attentions]
|
||||
|
||||
def base_parameters(self):
|
||||
return list(self.layers.parameters()) + list(self.avgpool.parameters()) + list(self.classifier.parameters())
|
||||
|
||||
def get_flop(self, mode, config_dict, extra_info):
|
||||
if config_dict is not None: config_dict = config_dict.copy()
|
||||
# select depth
|
||||
if mode == 'genotype':
|
||||
with torch.no_grad():
|
||||
depth_probs = nn.functional.softmax(self.depth_attentions, dim=1)
|
||||
choices = torch.argmax(depth_probs, dim=1).cpu().tolist()
|
||||
elif mode == 'max':
|
||||
choices = [depth_probs.size(1)-1 for _ in range(depth_probs.size(0))]
|
||||
elif mode == 'random':
|
||||
with torch.no_grad():
|
||||
depth_probs = nn.functional.softmax(self.depth_attentions, dim=1)
|
||||
choices = torch.multinomial(depth_probs, 1, False).cpu().tolist()
|
||||
else:
|
||||
raise ValueError('invalid mode : {:}'.format(mode))
|
||||
selected_layers = []
|
||||
for choice, xvalue in zip(choices, self.depth_info_list):
|
||||
xtemp = xvalue[1]['choices'][choice] - xvalue[1]['xstart'] + 1
|
||||
selected_layers.append(xtemp)
|
||||
flop = 0
|
||||
for i, layer in enumerate(self.layers):
|
||||
if i in self.depth_at_i:
|
||||
xstagei, xatti = self.depth_at_i[i]
|
||||
if xatti <= choices[xstagei]: # leave this depth
|
||||
flop+= layer.get_flops()
|
||||
else:
|
||||
flop+= 0 # do not use this layer
|
||||
else:
|
||||
flop+= layer.get_flops()
|
||||
# the last fc layer
|
||||
flop += self.classifier.in_features * self.classifier.out_features
|
||||
if config_dict is None:
|
||||
return flop / 1e6
|
||||
else:
|
||||
config_dict['xblocks'] = selected_layers
|
||||
config_dict['super_type'] = 'infer-depth'
|
||||
config_dict['estimated_FLOP'] = flop / 1e6
|
||||
return flop / 1e6, config_dict
|
||||
|
||||
def get_arch_info(self):
|
||||
string = "for depth, there are {:} attention probabilities.".format(len(self.depth_attentions))
|
||||
string+= '\n{:}'.format(self.depth_info)
|
||||
discrepancy = []
|
||||
with torch.no_grad():
|
||||
for i, att in enumerate(self.depth_attentions):
|
||||
prob = nn.functional.softmax(att, dim=0)
|
||||
prob = prob.cpu() ; selc = prob.argmax().item() ; prob = prob.tolist()
|
||||
prob = ['{:.3f}'.format(x) for x in prob]
|
||||
xstring = '{:03d}/{:03d}-th : {:}'.format(i, len(self.depth_attentions), ' '.join(prob))
|
||||
logt = ['{:.4f}'.format(x) for x in att.cpu().tolist()]
|
||||
xstring += ' || {:17s}'.format(' '.join(logt))
|
||||
prob = sorted( [float(x) for x in prob] )
|
||||
disc = prob[-1] - prob[-2]
|
||||
xstring += ' || discrepancy={:.2f} || select={:}/{:}'.format(disc, selc, len(prob))
|
||||
discrepancy.append( disc )
|
||||
string += '\n{:}'.format(xstring)
|
||||
return string, discrepancy
|
||||
|
||||
def set_tau(self, tau_max, tau_min, epoch_ratio):
|
||||
assert epoch_ratio >= 0 and epoch_ratio <= 1, 'invalid epoch-ratio : {:}'.format(epoch_ratio)
|
||||
tau = tau_min + (tau_max-tau_min) * (1 + math.cos(math.pi * epoch_ratio)) / 2
|
||||
self.tau = tau
|
||||
|
||||
def get_message(self):
|
||||
return self.message
|
||||
|
||||
def forward(self, inputs):
|
||||
if self.search_mode == 'basic':
|
||||
return self.basic_forward(inputs)
|
||||
elif self.search_mode == 'search':
|
||||
return self.search_forward(inputs)
|
||||
else:
|
||||
raise ValueError('invalid search_mode = {:}'.format(self.search_mode))
|
||||
|
||||
def search_forward(self, inputs):
|
||||
flop_depth_probs = nn.functional.softmax(self.depth_attentions, dim=1)
|
||||
flop_depth_probs = torch.flip( torch.cumsum( torch.flip(flop_depth_probs, [1]), 1 ), [1] )
|
||||
selected_depth_probs = select2withP(self.depth_attentions, self.tau, True)
|
||||
|
||||
x, flops = inputs, []
|
||||
feature_maps = []
|
||||
for i, layer in enumerate(self.layers):
|
||||
layer_i = layer( x )
|
||||
feature_maps.append( layer_i )
|
||||
if i in self.depth_info: # aggregate the information
|
||||
choices = self.depth_info[i]['choices']
|
||||
xstagei = self.depth_info[i]['stage']
|
||||
possible_tensors = []
|
||||
for tempi, A in enumerate(choices):
|
||||
xtensor = feature_maps[A]
|
||||
possible_tensors.append( xtensor )
|
||||
weighted_sum = sum( xtensor * W for xtensor, W in zip(possible_tensors, selected_depth_probs[xstagei]) )
|
||||
x = weighted_sum
|
||||
else:
|
||||
x = layer_i
|
||||
|
||||
if i in self.depth_at_i:
|
||||
xstagei, xatti = self.depth_at_i[i]
|
||||
#print ('layer-{:03d}, stage={:}, att={:}, prob={:}, flop={:}'.format(i, xstagei, xatti, flop_depth_probs[xstagei, xatti].item(), layer.get_flops(1e6)))
|
||||
x_expected_flop = flop_depth_probs[xstagei, xatti] * layer.get_flops(1e6)
|
||||
else:
|
||||
x_expected_flop = layer.get_flops(1e6)
|
||||
flops.append( x_expected_flop )
|
||||
flops.append( (self.classifier.in_features * self.classifier.out_features*1.0/1e6) )
|
||||
|
||||
features = self.avgpool(x)
|
||||
features = features.view(features.size(0), -1)
|
||||
logits = linear_forward(features, self.classifier)
|
||||
return logits, torch.stack( [sum(flops)] )
|
||||
|
||||
def basic_forward(self, inputs):
|
||||
if self.InShape is None: self.InShape = (inputs.size(-2), inputs.size(-1))
|
||||
x = inputs
|
||||
for i, layer in enumerate(self.layers):
|
||||
x = layer( x )
|
||||
features = self.avgpool(x)
|
||||
features = features.view(features.size(0), -1)
|
||||
logits = self.classifier(features)
|
||||
return features, logits
|
||||
391
lib/models/searchs/SearchCifarResNet_width.py
Normal file
391
lib/models/searchs/SearchCifarResNet_width.py
Normal file
@@ -0,0 +1,391 @@
|
||||
import math, torch
|
||||
import torch.nn as nn
|
||||
from ..initialization import initialize_resnet
|
||||
from ..SharedUtils import additive_func
|
||||
from .SoftSelect import select2withP, ChannelWiseInter
|
||||
from .SoftSelect import linear_forward
|
||||
from .SoftSelect import get_width_choices as get_choices
|
||||
|
||||
|
||||
def conv_forward(inputs, conv, choices):
|
||||
iC = conv.in_channels
|
||||
fill_size = list(inputs.size())
|
||||
fill_size[1] = iC - fill_size[1]
|
||||
filled = torch.zeros(fill_size, device=inputs.device)
|
||||
xinputs = torch.cat((inputs, filled), dim=1)
|
||||
outputs = conv(xinputs)
|
||||
selecteds = [outputs[:,:oC] for oC in choices]
|
||||
return selecteds
|
||||
|
||||
|
||||
class ConvBNReLU(nn.Module):
|
||||
num_conv = 1
|
||||
def __init__(self, nIn, nOut, kernel, stride, padding, bias, has_avg, has_bn, has_relu):
|
||||
super(ConvBNReLU, self).__init__()
|
||||
self.InShape = None
|
||||
self.OutShape = None
|
||||
self.choices = get_choices(nOut)
|
||||
self.register_buffer('choices_tensor', torch.Tensor( self.choices ))
|
||||
|
||||
if has_avg : self.avg = nn.AvgPool2d(kernel_size=2, stride=2, padding=0)
|
||||
else : self.avg = None
|
||||
self.conv = nn.Conv2d(nIn, nOut, kernel_size=kernel, stride=stride, padding=padding, dilation=1, groups=1, bias=bias)
|
||||
#if has_bn : self.bn = nn.BatchNorm2d(nOut)
|
||||
#else : self.bn = None
|
||||
self.has_bn = has_bn
|
||||
self.BNs = nn.ModuleList()
|
||||
for i, _out in enumerate(self.choices):
|
||||
self.BNs.append(nn.BatchNorm2d(_out))
|
||||
if has_relu: self.relu = nn.ReLU(inplace=True)
|
||||
else : self.relu = None
|
||||
self.in_dim = nIn
|
||||
self.out_dim = nOut
|
||||
self.search_mode = 'basic'
|
||||
|
||||
def get_flops(self, channels, check_range=True, divide=1):
|
||||
iC, oC = channels
|
||||
if check_range: assert iC <= self.conv.in_channels and oC <= self.conv.out_channels, '{:} vs {:} | {:} vs {:}'.format(iC, self.conv.in_channels, oC, self.conv.out_channels)
|
||||
assert isinstance(self.InShape, tuple) and len(self.InShape) == 2, 'invalid in-shape : {:}'.format(self.InShape)
|
||||
assert isinstance(self.OutShape, tuple) and len(self.OutShape) == 2, 'invalid out-shape : {:}'.format(self.OutShape)
|
||||
#conv_per_position_flops = self.conv.kernel_size[0] * self.conv.kernel_size[1] * iC * oC / self.conv.groups
|
||||
conv_per_position_flops = (self.conv.kernel_size[0] * self.conv.kernel_size[1] * 1.0 / self.conv.groups)
|
||||
all_positions = self.OutShape[0] * self.OutShape[1]
|
||||
flops = (conv_per_position_flops * all_positions / divide) * iC * oC
|
||||
if self.conv.bias is not None: flops += all_positions / divide
|
||||
return flops
|
||||
|
||||
def get_range(self):
|
||||
return [self.choices]
|
||||
|
||||
def forward(self, inputs):
|
||||
if self.search_mode == 'basic':
|
||||
return self.basic_forward(inputs)
|
||||
elif self.search_mode == 'search':
|
||||
return self.search_forward(inputs)
|
||||
else:
|
||||
raise ValueError('invalid search_mode = {:}'.format(self.search_mode))
|
||||
|
||||
def search_forward(self, tuple_inputs):
|
||||
assert isinstance(tuple_inputs, tuple) and len(tuple_inputs) == 5, 'invalid type input : {:}'.format( type(tuple_inputs) )
|
||||
inputs, expected_inC, probability, index, prob = tuple_inputs
|
||||
index, prob = torch.squeeze(index).tolist(), torch.squeeze(prob)
|
||||
probability = torch.squeeze(probability)
|
||||
assert len(index) == 2, 'invalid length : {:}'.format(index)
|
||||
# compute expected flop
|
||||
#coordinates = torch.arange(self.x_range[0], self.x_range[1]+1).type_as(probability)
|
||||
expected_outC = (self.choices_tensor * probability).sum()
|
||||
expected_flop = self.get_flops([expected_inC, expected_outC], False, 1e6)
|
||||
if self.avg : out = self.avg( inputs )
|
||||
else : out = inputs
|
||||
# convolutional layer
|
||||
out_convs = conv_forward(out, self.conv, [self.choices[i] for i in index])
|
||||
out_bns = [self.BNs[idx](out_conv) for idx, out_conv in zip(index, out_convs)]
|
||||
# merge
|
||||
out_channel = max([x.size(1) for x in out_bns])
|
||||
outA = ChannelWiseInter(out_bns[0], out_channel)
|
||||
outB = ChannelWiseInter(out_bns[1], out_channel)
|
||||
out = outA * prob[0] + outB * prob[1]
|
||||
#out = additive_func(out_bns[0]*prob[0], out_bns[1]*prob[1])
|
||||
|
||||
if self.relu: out = self.relu( out )
|
||||
else : out = out
|
||||
return out, expected_outC, expected_flop
|
||||
|
||||
def basic_forward(self, inputs):
|
||||
if self.avg : out = self.avg( inputs )
|
||||
else : out = inputs
|
||||
conv = self.conv( out )
|
||||
if self.has_bn:out= self.BNs[-1]( conv )
|
||||
else : out = conv
|
||||
if self.relu: out = self.relu( out )
|
||||
else : out = out
|
||||
if self.InShape is None:
|
||||
self.InShape = (inputs.size(-2), inputs.size(-1))
|
||||
self.OutShape = (out.size(-2) , out.size(-1))
|
||||
return out
|
||||
|
||||
|
||||
class ResNetBasicblock(nn.Module):
|
||||
expansion = 1
|
||||
num_conv = 2
|
||||
def __init__(self, inplanes, planes, stride):
|
||||
super(ResNetBasicblock, self).__init__()
|
||||
assert stride == 1 or stride == 2, 'invalid stride {:}'.format(stride)
|
||||
self.conv_a = ConvBNReLU(inplanes, planes, 3, stride, 1, False, has_avg=False, has_bn=True, has_relu=True)
|
||||
self.conv_b = ConvBNReLU( planes, planes, 3, 1, 1, False, has_avg=False, has_bn=True, has_relu=False)
|
||||
if stride == 2:
|
||||
self.downsample = ConvBNReLU(inplanes, planes, 1, 1, 0, False, has_avg=True, has_bn=False, has_relu=False)
|
||||
elif inplanes != planes:
|
||||
self.downsample = ConvBNReLU(inplanes, planes, 1, 1, 0, False, has_avg=False,has_bn=True , has_relu=False)
|
||||
else:
|
||||
self.downsample = None
|
||||
self.out_dim = planes
|
||||
self.search_mode = 'basic'
|
||||
|
||||
def get_range(self):
|
||||
return self.conv_a.get_range() + self.conv_b.get_range()
|
||||
|
||||
def get_flops(self, channels):
|
||||
assert len(channels) == 3, 'invalid channels : {:}'.format(channels)
|
||||
flop_A = self.conv_a.get_flops([channels[0], channels[1]])
|
||||
flop_B = self.conv_b.get_flops([channels[1], channels[2]])
|
||||
if hasattr(self.downsample, 'get_flops'):
|
||||
flop_C = self.downsample.get_flops([channels[0], channels[-1]])
|
||||
else:
|
||||
flop_C = 0
|
||||
if channels[0] != channels[-1] and self.downsample is None: # this short-cut will be added during the infer-train
|
||||
flop_C = channels[0] * channels[-1] * self.conv_b.OutShape[0] * self.conv_b.OutShape[1]
|
||||
return flop_A + flop_B + flop_C
|
||||
|
||||
def forward(self, inputs):
|
||||
if self.search_mode == 'basic' : return self.basic_forward(inputs)
|
||||
elif self.search_mode == 'search': return self.search_forward(inputs)
|
||||
else: raise ValueError('invalid search_mode = {:}'.format(self.search_mode))
|
||||
|
||||
def search_forward(self, tuple_inputs):
|
||||
assert isinstance(tuple_inputs, tuple) and len(tuple_inputs) == 5, 'invalid type input : {:}'.format( type(tuple_inputs) )
|
||||
inputs, expected_inC, probability, indexes, probs = tuple_inputs
|
||||
assert indexes.size(0) == 2 and probs.size(0) == 2 and probability.size(0) == 2
|
||||
out_a, expected_inC_a, expected_flop_a = self.conv_a( (inputs, expected_inC , probability[0], indexes[0], probs[0]) )
|
||||
out_b, expected_inC_b, expected_flop_b = self.conv_b( (out_a , expected_inC_a, probability[1], indexes[1], probs[1]) )
|
||||
if self.downsample is not None:
|
||||
residual, _, expected_flop_c = self.downsample( (inputs, expected_inC , probability[1], indexes[1], probs[1]) )
|
||||
else:
|
||||
residual, expected_flop_c = inputs, 0
|
||||
out = additive_func(residual, out_b)
|
||||
return out, expected_inC_b, sum([expected_flop_a, expected_flop_b, expected_flop_c])
|
||||
|
||||
def basic_forward(self, inputs):
|
||||
basicblock = self.conv_a(inputs)
|
||||
basicblock = self.conv_b(basicblock)
|
||||
if self.downsample is not None: residual = self.downsample(inputs)
|
||||
else : residual = inputs
|
||||
out = additive_func(residual, basicblock)
|
||||
return nn.functional.relu(out, inplace=True)
|
||||
|
||||
|
||||
|
||||
class ResNetBottleneck(nn.Module):
|
||||
expansion = 4
|
||||
num_conv = 3
|
||||
def __init__(self, inplanes, planes, stride):
|
||||
super(ResNetBottleneck, self).__init__()
|
||||
assert stride == 1 or stride == 2, 'invalid stride {:}'.format(stride)
|
||||
self.conv_1x1 = ConvBNReLU(inplanes, planes, 1, 1, 0, False, has_avg=False, has_bn=True, has_relu=True)
|
||||
self.conv_3x3 = ConvBNReLU( planes, planes, 3, stride, 1, False, has_avg=False, has_bn=True, has_relu=True)
|
||||
self.conv_1x4 = ConvBNReLU(planes, planes*self.expansion, 1, 1, 0, False, has_avg=False, has_bn=True, has_relu=False)
|
||||
if stride == 2:
|
||||
self.downsample = ConvBNReLU(inplanes, planes*self.expansion, 1, 1, 0, False, has_avg=True, has_bn=False, has_relu=False)
|
||||
elif inplanes != planes*self.expansion:
|
||||
self.downsample = ConvBNReLU(inplanes, planes*self.expansion, 1, 1, 0, False, has_avg=False,has_bn=True , has_relu=False)
|
||||
else:
|
||||
self.downsample = None
|
||||
self.out_dim = planes * self.expansion
|
||||
self.search_mode = 'basic'
|
||||
|
||||
def get_range(self):
|
||||
return self.conv_1x1.get_range() + self.conv_3x3.get_range() + self.conv_1x4.get_range()
|
||||
|
||||
def get_flops(self, channels):
|
||||
assert len(channels) == 4, 'invalid channels : {:}'.format(channels)
|
||||
flop_A = self.conv_1x1.get_flops([channels[0], channels[1]])
|
||||
flop_B = self.conv_3x3.get_flops([channels[1], channels[2]])
|
||||
flop_C = self.conv_1x4.get_flops([channels[2], channels[3]])
|
||||
if hasattr(self.downsample, 'get_flops'):
|
||||
flop_D = self.downsample.get_flops([channels[0], channels[-1]])
|
||||
else:
|
||||
flop_D = 0
|
||||
if channels[0] != channels[-1] and self.downsample is None: # this short-cut will be added during the infer-train
|
||||
flop_D = channels[0] * channels[-1] * self.conv_1x4.OutShape[0] * self.conv_1x4.OutShape[1]
|
||||
return flop_A + flop_B + flop_C + flop_D
|
||||
|
||||
def forward(self, inputs):
|
||||
if self.search_mode == 'basic' : return self.basic_forward(inputs)
|
||||
elif self.search_mode == 'search': return self.search_forward(inputs)
|
||||
else: raise ValueError('invalid search_mode = {:}'.format(self.search_mode))
|
||||
|
||||
def basic_forward(self, inputs):
|
||||
bottleneck = self.conv_1x1(inputs)
|
||||
bottleneck = self.conv_3x3(bottleneck)
|
||||
bottleneck = self.conv_1x4(bottleneck)
|
||||
if self.downsample is not None: residual = self.downsample(inputs)
|
||||
else : residual = inputs
|
||||
out = additive_func(residual, bottleneck)
|
||||
return nn.functional.relu(out, inplace=True)
|
||||
|
||||
def search_forward(self, tuple_inputs):
|
||||
assert isinstance(tuple_inputs, tuple) and len(tuple_inputs) == 5, 'invalid type input : {:}'.format( type(tuple_inputs) )
|
||||
inputs, expected_inC, probability, indexes, probs = tuple_inputs
|
||||
assert indexes.size(0) == 3 and probs.size(0) == 3 and probability.size(0) == 3
|
||||
out_1x1, expected_inC_1x1, expected_flop_1x1 = self.conv_1x1( (inputs, expected_inC , probability[0], indexes[0], probs[0]) )
|
||||
out_3x3, expected_inC_3x3, expected_flop_3x3 = self.conv_3x3( (out_1x1,expected_inC_1x1, probability[1], indexes[1], probs[1]) )
|
||||
out_1x4, expected_inC_1x4, expected_flop_1x4 = self.conv_1x4( (out_3x3,expected_inC_3x3, probability[2], indexes[2], probs[2]) )
|
||||
if self.downsample is not None:
|
||||
residual, _, expected_flop_c = self.downsample( (inputs, expected_inC , probability[2], indexes[2], probs[2]) )
|
||||
else:
|
||||
residual, expected_flop_c = inputs, 0
|
||||
out = additive_func(residual, out_1x4)
|
||||
return out, expected_inC_1x4, sum([expected_flop_1x1, expected_flop_3x3, expected_flop_1x4, expected_flop_c])
|
||||
|
||||
|
||||
|
||||
class SearchWidthCifarResNet(nn.Module):
|
||||
|
||||
def __init__(self, block_name, depth, num_classes):
|
||||
super(SearchWidthCifarResNet, self).__init__()
|
||||
|
||||
#Model type specifies number of layers for CIFAR-10 and CIFAR-100 model
|
||||
if block_name == 'ResNetBasicblock':
|
||||
block = ResNetBasicblock
|
||||
assert (depth - 2) % 6 == 0, 'depth should be one of 20, 32, 44, 56, 110'
|
||||
layer_blocks = (depth - 2) // 6
|
||||
elif block_name == 'ResNetBottleneck':
|
||||
block = ResNetBottleneck
|
||||
assert (depth - 2) % 9 == 0, 'depth should be one of 164'
|
||||
layer_blocks = (depth - 2) // 9
|
||||
else:
|
||||
raise ValueError('invalid block : {:}'.format(block_name))
|
||||
|
||||
self.message = 'SearchWidthCifarResNet : Depth : {:} , Layers for each block : {:}'.format(depth, layer_blocks)
|
||||
self.num_classes = num_classes
|
||||
self.channels = [16]
|
||||
self.layers = nn.ModuleList( [ ConvBNReLU(3, 16, 3, 1, 1, False, has_avg=False, has_bn=True, has_relu=True) ] )
|
||||
self.InShape = None
|
||||
for stage in range(3):
|
||||
for iL in range(layer_blocks):
|
||||
iC = self.channels[-1]
|
||||
planes = 16 * (2**stage)
|
||||
stride = 2 if stage > 0 and iL == 0 else 1
|
||||
module = block(iC, planes, stride)
|
||||
self.channels.append( module.out_dim )
|
||||
self.layers.append ( module )
|
||||
self.message += "\nstage={:}, ilayer={:02d}/{:02d}, block={:03d}, iC={:3d}, oC={:3d}, stride={:}".format(stage, iL, layer_blocks, len(self.layers)-1, iC, module.out_dim, stride)
|
||||
|
||||
self.avgpool = nn.AvgPool2d(8)
|
||||
self.classifier = nn.Linear(module.out_dim, num_classes)
|
||||
self.InShape = None
|
||||
self.tau = -1
|
||||
self.search_mode = 'basic'
|
||||
#assert sum(x.num_conv for x in self.layers) + 1 == depth, 'invalid depth check {:} vs {:}'.format(sum(x.num_conv for x in self.layers)+1, depth)
|
||||
|
||||
# parameters for width
|
||||
self.Ranges = []
|
||||
self.layer2indexRange = []
|
||||
for i, layer in enumerate(self.layers):
|
||||
start_index = len(self.Ranges)
|
||||
self.Ranges += layer.get_range()
|
||||
self.layer2indexRange.append( (start_index, len(self.Ranges)) )
|
||||
assert len(self.Ranges) + 1 == depth, 'invalid depth check {:} vs {:}'.format(len(self.Ranges) + 1, depth)
|
||||
|
||||
self.register_parameter('width_attentions', nn.Parameter(torch.Tensor(len(self.Ranges), get_choices(None))))
|
||||
nn.init.normal_(self.width_attentions, 0, 0.01)
|
||||
self.apply(initialize_resnet)
|
||||
|
||||
def arch_parameters(self):
|
||||
return [self.width_attentions]
|
||||
|
||||
def base_parameters(self):
|
||||
return list(self.layers.parameters()) + list(self.avgpool.parameters()) + list(self.classifier.parameters())
|
||||
|
||||
def get_flop(self, mode, config_dict, extra_info):
|
||||
if config_dict is not None: config_dict = config_dict.copy()
|
||||
#weights = [F.softmax(x, dim=0) for x in self.width_attentions]
|
||||
channels = [3]
|
||||
for i, weight in enumerate(self.width_attentions):
|
||||
if mode == 'genotype':
|
||||
with torch.no_grad():
|
||||
probe = nn.functional.softmax(weight, dim=0)
|
||||
C = self.Ranges[i][ torch.argmax(probe).item() ]
|
||||
elif mode == 'max':
|
||||
C = self.Ranges[i][-1]
|
||||
elif mode == 'fix':
|
||||
C = int( math.sqrt( extra_info ) * self.Ranges[i][-1] )
|
||||
elif mode == 'random':
|
||||
assert isinstance(extra_info, float), 'invalid extra_info : {:}'.format(extra_info)
|
||||
with torch.no_grad():
|
||||
prob = nn.functional.softmax(weight, dim=0)
|
||||
approximate_C = int( math.sqrt( extra_info ) * self.Ranges[i][-1] )
|
||||
for j in range(prob.size(0)):
|
||||
prob[j] = 1 / (abs(j - (approximate_C-self.Ranges[i][j])) + 0.2)
|
||||
C = self.Ranges[i][ torch.multinomial(prob, 1, False).item() ]
|
||||
else:
|
||||
raise ValueError('invalid mode : {:}'.format(mode))
|
||||
channels.append( C )
|
||||
flop = 0
|
||||
for i, layer in enumerate(self.layers):
|
||||
s, e = self.layer2indexRange[i]
|
||||
xchl = tuple( channels[s:e+1] )
|
||||
flop+= layer.get_flops(xchl)
|
||||
# the last fc layer
|
||||
flop += channels[-1] * self.classifier.out_features
|
||||
if config_dict is None:
|
||||
return flop / 1e6
|
||||
else:
|
||||
config_dict['xchannels'] = channels
|
||||
config_dict['super_type'] = 'infer-width'
|
||||
config_dict['estimated_FLOP'] = flop / 1e6
|
||||
return flop / 1e6, config_dict
|
||||
|
||||
def get_arch_info(self):
|
||||
string = "for width, there are {:} attention probabilities.".format(len(self.width_attentions))
|
||||
discrepancy = []
|
||||
with torch.no_grad():
|
||||
for i, att in enumerate(self.width_attentions):
|
||||
prob = nn.functional.softmax(att, dim=0)
|
||||
prob = prob.cpu() ; selc = prob.argmax().item() ; prob = prob.tolist()
|
||||
prob = ['{:.3f}'.format(x) for x in prob]
|
||||
xstring = '{:03d}/{:03d}-th : {:}'.format(i, len(self.width_attentions), ' '.join(prob))
|
||||
logt = ['{:.3f}'.format(x) for x in att.cpu().tolist()]
|
||||
xstring += ' || {:52s}'.format(' '.join(logt))
|
||||
prob = sorted( [float(x) for x in prob] )
|
||||
disc = prob[-1] - prob[-2]
|
||||
xstring += ' || dis={:.2f} || select={:}/{:}'.format(disc, selc, len(prob))
|
||||
discrepancy.append( disc )
|
||||
string += '\n{:}'.format(xstring)
|
||||
return string, discrepancy
|
||||
|
||||
def set_tau(self, tau_max, tau_min, epoch_ratio):
|
||||
assert epoch_ratio >= 0 and epoch_ratio <= 1, 'invalid epoch-ratio : {:}'.format(epoch_ratio)
|
||||
tau = tau_min + (tau_max-tau_min) * (1 + math.cos(math.pi * epoch_ratio)) / 2
|
||||
self.tau = tau
|
||||
|
||||
def get_message(self):
|
||||
return self.message
|
||||
|
||||
def forward(self, inputs):
|
||||
if self.search_mode == 'basic':
|
||||
return self.basic_forward(inputs)
|
||||
elif self.search_mode == 'search':
|
||||
return self.search_forward(inputs)
|
||||
else:
|
||||
raise ValueError('invalid search_mode = {:}'.format(self.search_mode))
|
||||
|
||||
def search_forward(self, inputs):
|
||||
flop_probs = nn.functional.softmax(self.width_attentions, dim=1)
|
||||
selected_widths, selected_probs = select2withP(self.width_attentions, self.tau)
|
||||
with torch.no_grad():
|
||||
selected_widths = selected_widths.cpu()
|
||||
|
||||
x, last_channel_idx, expected_inC, flops = inputs, 0, 3, []
|
||||
for i, layer in enumerate(self.layers):
|
||||
selected_w_index = selected_widths[last_channel_idx: last_channel_idx+layer.num_conv]
|
||||
selected_w_probs = selected_probs[last_channel_idx: last_channel_idx+layer.num_conv]
|
||||
layer_prob = flop_probs[last_channel_idx: last_channel_idx+layer.num_conv]
|
||||
x, expected_inC, expected_flop = layer( (x, expected_inC, layer_prob, selected_w_index, selected_w_probs) )
|
||||
last_channel_idx += layer.num_conv
|
||||
flops.append( expected_flop )
|
||||
flops.append( expected_inC * (self.classifier.out_features*1.0/1e6) )
|
||||
features = self.avgpool(x)
|
||||
features = features.view(features.size(0), -1)
|
||||
logits = linear_forward(features, self.classifier)
|
||||
return logits, torch.stack( [sum(flops)] )
|
||||
|
||||
def basic_forward(self, inputs):
|
||||
if self.InShape is None: self.InShape = (inputs.size(-2), inputs.size(-1))
|
||||
x = inputs
|
||||
for i, layer in enumerate(self.layers):
|
||||
x = layer( x )
|
||||
features = self.avgpool(x)
|
||||
features = features.view(features.size(0), -1)
|
||||
logits = self.classifier(features)
|
||||
return features, logits
|
||||
483
lib/models/searchs/SearchImagenetResNet.py
Normal file
483
lib/models/searchs/SearchImagenetResNet.py
Normal file
@@ -0,0 +1,483 @@
|
||||
import math, torch
|
||||
from collections import OrderedDict
|
||||
from bisect import bisect_right
|
||||
import torch.nn as nn
|
||||
from ..initialization import initialize_resnet
|
||||
from ..SharedUtils import additive_func
|
||||
from .SoftSelect import select2withP, ChannelWiseInter
|
||||
from .SoftSelect import linear_forward
|
||||
from .SoftSelect import get_width_choices
|
||||
|
||||
|
||||
def get_depth_choices(layers):
|
||||
min_depth = min(layers)
|
||||
info = {'num': min_depth}
|
||||
for i, depth in enumerate(layers):
|
||||
choices = []
|
||||
for j in range(1, min_depth+1):
|
||||
choices.append( int( float(depth)*j/min_depth ) )
|
||||
info[i] = choices
|
||||
return info
|
||||
|
||||
|
||||
def conv_forward(inputs, conv, choices):
|
||||
iC = conv.in_channels
|
||||
fill_size = list(inputs.size())
|
||||
fill_size[1] = iC - fill_size[1]
|
||||
filled = torch.zeros(fill_size, device=inputs.device)
|
||||
xinputs = torch.cat((inputs, filled), dim=1)
|
||||
outputs = conv(xinputs)
|
||||
selecteds = [outputs[:,:oC] for oC in choices]
|
||||
return selecteds
|
||||
|
||||
|
||||
class ConvBNReLU(nn.Module):
|
||||
num_conv = 1
|
||||
def __init__(self, nIn, nOut, kernel, stride, padding, bias, has_avg, has_bn, has_relu, last_max_pool=False):
|
||||
super(ConvBNReLU, self).__init__()
|
||||
self.InShape = None
|
||||
self.OutShape = None
|
||||
self.choices = get_width_choices(nOut)
|
||||
self.register_buffer('choices_tensor', torch.Tensor( self.choices ))
|
||||
|
||||
if has_avg : self.avg = nn.AvgPool2d(kernel_size=2, stride=2, padding=0)
|
||||
else : self.avg = None
|
||||
self.conv = nn.Conv2d(nIn, nOut, kernel_size=kernel, stride=stride, padding=padding, dilation=1, groups=1, bias=bias)
|
||||
#if has_bn : self.bn = nn.BatchNorm2d(nOut)
|
||||
#else : self.bn = None
|
||||
self.has_bn = has_bn
|
||||
self.BNs = nn.ModuleList()
|
||||
for i, _out in enumerate(self.choices):
|
||||
self.BNs.append(nn.BatchNorm2d(_out))
|
||||
if has_relu: self.relu = nn.ReLU(inplace=True)
|
||||
else : self.relu = None
|
||||
|
||||
if last_max_pool: self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
|
||||
else : self.maxpool = None
|
||||
self.in_dim = nIn
|
||||
self.out_dim = nOut
|
||||
self.search_mode = 'basic'
|
||||
|
||||
def get_flops(self, channels, check_range=True, divide=1):
|
||||
iC, oC = channels
|
||||
if check_range: assert iC <= self.conv.in_channels and oC <= self.conv.out_channels, '{:} vs {:} | {:} vs {:}'.format(iC, self.conv.in_channels, oC, self.conv.out_channels)
|
||||
assert isinstance(self.InShape, tuple) and len(self.InShape) == 2, 'invalid in-shape : {:}'.format(self.InShape)
|
||||
assert isinstance(self.OutShape, tuple) and len(self.OutShape) == 2, 'invalid out-shape : {:}'.format(self.OutShape)
|
||||
#conv_per_position_flops = self.conv.kernel_size[0] * self.conv.kernel_size[1] * iC * oC / self.conv.groups
|
||||
conv_per_position_flops = (self.conv.kernel_size[0] * self.conv.kernel_size[1] * 1.0 / self.conv.groups)
|
||||
all_positions = self.OutShape[0] * self.OutShape[1]
|
||||
flops = (conv_per_position_flops * all_positions / divide) * iC * oC
|
||||
if self.conv.bias is not None: flops += all_positions / divide
|
||||
return flops
|
||||
|
||||
def get_range(self):
|
||||
return [self.choices]
|
||||
|
||||
def forward(self, inputs):
|
||||
if self.search_mode == 'basic':
|
||||
return self.basic_forward(inputs)
|
||||
elif self.search_mode == 'search':
|
||||
return self.search_forward(inputs)
|
||||
else:
|
||||
raise ValueError('invalid search_mode = {:}'.format(self.search_mode))
|
||||
|
||||
def search_forward(self, tuple_inputs):
|
||||
assert isinstance(tuple_inputs, tuple) and len(tuple_inputs) == 5, 'invalid type input : {:}'.format( type(tuple_inputs) )
|
||||
inputs, expected_inC, probability, index, prob = tuple_inputs
|
||||
index, prob = torch.squeeze(index).tolist(), torch.squeeze(prob)
|
||||
probability = torch.squeeze(probability)
|
||||
assert len(index) == 2, 'invalid length : {:}'.format(index)
|
||||
# compute expected flop
|
||||
#coordinates = torch.arange(self.x_range[0], self.x_range[1]+1).type_as(probability)
|
||||
expected_outC = (self.choices_tensor * probability).sum()
|
||||
expected_flop = self.get_flops([expected_inC, expected_outC], False, 1e6)
|
||||
if self.avg : out = self.avg( inputs )
|
||||
else : out = inputs
|
||||
# convolutional layer
|
||||
out_convs = conv_forward(out, self.conv, [self.choices[i] for i in index])
|
||||
out_bns = [self.BNs[idx](out_conv) for idx, out_conv in zip(index, out_convs)]
|
||||
# merge
|
||||
out_channel = max([x.size(1) for x in out_bns])
|
||||
outA = ChannelWiseInter(out_bns[0], out_channel)
|
||||
outB = ChannelWiseInter(out_bns[1], out_channel)
|
||||
out = outA * prob[0] + outB * prob[1]
|
||||
#out = additive_func(out_bns[0]*prob[0], out_bns[1]*prob[1])
|
||||
|
||||
if self.relu : out = self.relu( out )
|
||||
if self.maxpool: out = self.maxpool(out)
|
||||
return out, expected_outC, expected_flop
|
||||
|
||||
def basic_forward(self, inputs):
|
||||
if self.avg : out = self.avg( inputs )
|
||||
else : out = inputs
|
||||
conv = self.conv( out )
|
||||
if self.has_bn:out= self.BNs[-1]( conv )
|
||||
else : out = conv
|
||||
if self.relu: out = self.relu( out )
|
||||
else : out = out
|
||||
if self.InShape is None:
|
||||
self.InShape = (inputs.size(-2), inputs.size(-1))
|
||||
self.OutShape = (out.size(-2) , out.size(-1))
|
||||
if self.maxpool: out = self.maxpool(out)
|
||||
return out
|
||||
|
||||
|
||||
class ResNetBasicblock(nn.Module):
|
||||
expansion = 1
|
||||
num_conv = 2
|
||||
def __init__(self, inplanes, planes, stride):
|
||||
super(ResNetBasicblock, self).__init__()
|
||||
assert stride == 1 or stride == 2, 'invalid stride {:}'.format(stride)
|
||||
self.conv_a = ConvBNReLU(inplanes, planes, 3, stride, 1, False, has_avg=False, has_bn=True, has_relu=True)
|
||||
self.conv_b = ConvBNReLU( planes, planes, 3, 1, 1, False, has_avg=False, has_bn=True, has_relu=False)
|
||||
if stride == 2:
|
||||
self.downsample = ConvBNReLU(inplanes, planes, 1, 1, 0, False, has_avg=True, has_bn=True, has_relu=False)
|
||||
elif inplanes != planes:
|
||||
self.downsample = ConvBNReLU(inplanes, planes, 1, 1, 0, False, has_avg=False,has_bn=True, has_relu=False)
|
||||
else:
|
||||
self.downsample = None
|
||||
self.out_dim = planes
|
||||
self.search_mode = 'basic'
|
||||
|
||||
def get_range(self):
|
||||
return self.conv_a.get_range() + self.conv_b.get_range()
|
||||
|
||||
def get_flops(self, channels):
|
||||
assert len(channels) == 3, 'invalid channels : {:}'.format(channels)
|
||||
flop_A = self.conv_a.get_flops([channels[0], channels[1]])
|
||||
flop_B = self.conv_b.get_flops([channels[1], channels[2]])
|
||||
if hasattr(self.downsample, 'get_flops'):
|
||||
flop_C = self.downsample.get_flops([channels[0], channels[-1]])
|
||||
else:
|
||||
flop_C = 0
|
||||
if channels[0] != channels[-1] and self.downsample is None: # this short-cut will be added during the infer-train
|
||||
flop_C = channels[0] * channels[-1] * self.conv_b.OutShape[0] * self.conv_b.OutShape[1]
|
||||
return flop_A + flop_B + flop_C
|
||||
|
||||
def forward(self, inputs):
|
||||
if self.search_mode == 'basic' : return self.basic_forward(inputs)
|
||||
elif self.search_mode == 'search': return self.search_forward(inputs)
|
||||
else: raise ValueError('invalid search_mode = {:}'.format(self.search_mode))
|
||||
|
||||
def search_forward(self, tuple_inputs):
|
||||
assert isinstance(tuple_inputs, tuple) and len(tuple_inputs) == 5, 'invalid type input : {:}'.format( type(tuple_inputs) )
|
||||
inputs, expected_inC, probability, indexes, probs = tuple_inputs
|
||||
assert indexes.size(0) == 2 and probs.size(0) == 2 and probability.size(0) == 2
|
||||
#import pdb; pdb.set_trace()
|
||||
out_a, expected_inC_a, expected_flop_a = self.conv_a( (inputs, expected_inC , probability[0], indexes[0], probs[0]) )
|
||||
out_b, expected_inC_b, expected_flop_b = self.conv_b( (out_a , expected_inC_a, probability[1], indexes[1], probs[1]) )
|
||||
if self.downsample is not None:
|
||||
residual, _, expected_flop_c = self.downsample( (inputs, expected_inC , probability[1], indexes[1], probs[1]) )
|
||||
else:
|
||||
residual, expected_flop_c = inputs, 0
|
||||
out = additive_func(residual, out_b)
|
||||
return out, expected_inC_b, sum([expected_flop_a, expected_flop_b, expected_flop_c])
|
||||
|
||||
def basic_forward(self, inputs):
|
||||
basicblock = self.conv_a(inputs)
|
||||
basicblock = self.conv_b(basicblock)
|
||||
if self.downsample is not None: residual = self.downsample(inputs)
|
||||
else : residual = inputs
|
||||
out = additive_func(residual, basicblock)
|
||||
return nn.functional.relu(out, inplace=True)
|
||||
|
||||
|
||||
|
||||
class ResNetBottleneck(nn.Module):
|
||||
expansion = 4
|
||||
num_conv = 3
|
||||
def __init__(self, inplanes, planes, stride):
|
||||
super(ResNetBottleneck, self).__init__()
|
||||
assert stride == 1 or stride == 2, 'invalid stride {:}'.format(stride)
|
||||
self.conv_1x1 = ConvBNReLU(inplanes, planes, 1, 1, 0, False, has_avg=False, has_bn=True, has_relu=True)
|
||||
self.conv_3x3 = ConvBNReLU( planes, planes, 3, stride, 1, False, has_avg=False, has_bn=True, has_relu=True)
|
||||
self.conv_1x4 = ConvBNReLU(planes, planes*self.expansion, 1, 1, 0, False, has_avg=False, has_bn=True, has_relu=False)
|
||||
if stride == 2:
|
||||
self.downsample = ConvBNReLU(inplanes, planes*self.expansion, 1, 1, 0, False, has_avg=True, has_bn=True, has_relu=False)
|
||||
elif inplanes != planes*self.expansion:
|
||||
self.downsample = ConvBNReLU(inplanes, planes*self.expansion, 1, 1, 0, False, has_avg=False,has_bn=True, has_relu=False)
|
||||
else:
|
||||
self.downsample = None
|
||||
self.out_dim = planes * self.expansion
|
||||
self.search_mode = 'basic'
|
||||
|
||||
def get_range(self):
|
||||
return self.conv_1x1.get_range() + self.conv_3x3.get_range() + self.conv_1x4.get_range()
|
||||
|
||||
def get_flops(self, channels):
|
||||
assert len(channels) == 4, 'invalid channels : {:}'.format(channels)
|
||||
flop_A = self.conv_1x1.get_flops([channels[0], channels[1]])
|
||||
flop_B = self.conv_3x3.get_flops([channels[1], channels[2]])
|
||||
flop_C = self.conv_1x4.get_flops([channels[2], channels[3]])
|
||||
if hasattr(self.downsample, 'get_flops'):
|
||||
flop_D = self.downsample.get_flops([channels[0], channels[-1]])
|
||||
else:
|
||||
flop_D = 0
|
||||
if channels[0] != channels[-1] and self.downsample is None: # this short-cut will be added during the infer-train
|
||||
flop_D = channels[0] * channels[-1] * self.conv_1x4.OutShape[0] * self.conv_1x4.OutShape[1]
|
||||
return flop_A + flop_B + flop_C + flop_D
|
||||
|
||||
def forward(self, inputs):
|
||||
if self.search_mode == 'basic' : return self.basic_forward(inputs)
|
||||
elif self.search_mode == 'search': return self.search_forward(inputs)
|
||||
else: raise ValueError('invalid search_mode = {:}'.format(self.search_mode))
|
||||
|
||||
def basic_forward(self, inputs):
|
||||
bottleneck = self.conv_1x1(inputs)
|
||||
bottleneck = self.conv_3x3(bottleneck)
|
||||
bottleneck = self.conv_1x4(bottleneck)
|
||||
if self.downsample is not None: residual = self.downsample(inputs)
|
||||
else : residual = inputs
|
||||
out = additive_func(residual, bottleneck)
|
||||
return nn.functional.relu(out, inplace=True)
|
||||
|
||||
def search_forward(self, tuple_inputs):
|
||||
assert isinstance(tuple_inputs, tuple) and len(tuple_inputs) == 5, 'invalid type input : {:}'.format( type(tuple_inputs) )
|
||||
inputs, expected_inC, probability, indexes, probs = tuple_inputs
|
||||
assert indexes.size(0) == 3 and probs.size(0) == 3 and probability.size(0) == 3
|
||||
out_1x1, expected_inC_1x1, expected_flop_1x1 = self.conv_1x1( (inputs, expected_inC , probability[0], indexes[0], probs[0]) )
|
||||
out_3x3, expected_inC_3x3, expected_flop_3x3 = self.conv_3x3( (out_1x1,expected_inC_1x1, probability[1], indexes[1], probs[1]) )
|
||||
out_1x4, expected_inC_1x4, expected_flop_1x4 = self.conv_1x4( (out_3x3,expected_inC_3x3, probability[2], indexes[2], probs[2]) )
|
||||
if self.downsample is not None:
|
||||
residual, _, expected_flop_c = self.downsample( (inputs, expected_inC , probability[2], indexes[2], probs[2]) )
|
||||
else:
|
||||
residual, expected_flop_c = inputs, 0
|
||||
out = additive_func(residual, out_1x4)
|
||||
return out, expected_inC_1x4, sum([expected_flop_1x1, expected_flop_3x3, expected_flop_1x4, expected_flop_c])
|
||||
|
||||
|
||||
|
||||
class SearchShapeImagenetResNet(nn.Module):
|
||||
|
||||
def __init__(self, block_name, layers, deep_stem, num_classes):
|
||||
super(SearchShapeImagenetResNet, self).__init__()
|
||||
|
||||
#Model type specifies number of layers for CIFAR-10 and CIFAR-100 model
|
||||
if block_name == 'BasicBlock':
|
||||
block = ResNetBasicblock
|
||||
elif block_name == 'Bottleneck':
|
||||
block = ResNetBottleneck
|
||||
else:
|
||||
raise ValueError('invalid block : {:}'.format(block_name))
|
||||
|
||||
self.message = 'SearchShapeCifarResNet : Depth : {:} , Layers for each block : {:}'.format(sum(layers)*block.num_conv, layers)
|
||||
self.num_classes = num_classes
|
||||
if not deep_stem:
|
||||
self.layers = nn.ModuleList( [ ConvBNReLU(3, 64, 7, 2, 3, False, has_avg=False, has_bn=True, has_relu=True, last_max_pool=True) ] )
|
||||
self.channels = [64]
|
||||
else:
|
||||
self.layers = nn.ModuleList( [ ConvBNReLU(3, 32, 3, 2, 1, False, has_avg=False, has_bn=True, has_relu=True)
|
||||
,ConvBNReLU(32,64, 3, 1, 1, False, has_avg=False, has_bn=True, has_relu=True, last_max_pool=True) ] )
|
||||
self.channels = [32, 64]
|
||||
|
||||
meta_depth_info = get_depth_choices(layers)
|
||||
self.InShape = None
|
||||
self.depth_info = OrderedDict()
|
||||
self.depth_at_i = OrderedDict()
|
||||
for stage, layer_blocks in enumerate(layers):
|
||||
cur_block_choices = meta_depth_info[stage]
|
||||
assert cur_block_choices[-1] == layer_blocks, 'stage={:}, {:} vs {:}'.format(stage, cur_block_choices, layer_blocks)
|
||||
block_choices, xstart = [], len(self.layers)
|
||||
for iL in range(layer_blocks):
|
||||
iC = self.channels[-1]
|
||||
planes = 64 * (2**stage)
|
||||
stride = 2 if stage > 0 and iL == 0 else 1
|
||||
module = block(iC, planes, stride)
|
||||
self.channels.append( module.out_dim )
|
||||
self.layers.append ( module )
|
||||
self.message += "\nstage={:}, ilayer={:02d}/{:02d}, block={:03d}, iC={:3d}, oC={:3d}, stride={:}".format(stage, iL, layer_blocks, len(self.layers)-1, iC, module.out_dim, stride)
|
||||
# added for depth
|
||||
layer_index = len(self.layers) - 1
|
||||
if iL + 1 in cur_block_choices: block_choices.append( layer_index )
|
||||
if iL + 1 == layer_blocks:
|
||||
self.depth_info[layer_index] = {'choices': block_choices,
|
||||
'stage' : stage,
|
||||
'xstart' : xstart}
|
||||
self.depth_info_list = []
|
||||
for xend, info in self.depth_info.items():
|
||||
self.depth_info_list.append( (xend, info) )
|
||||
xstart, xstage = info['xstart'], info['stage']
|
||||
for ilayer in range(xstart, xend+1):
|
||||
idx = bisect_right(info['choices'], ilayer-1)
|
||||
self.depth_at_i[ilayer] = (xstage, idx)
|
||||
|
||||
self.avgpool = nn.AdaptiveAvgPool2d((1,1))
|
||||
self.classifier = nn.Linear(module.out_dim, num_classes)
|
||||
self.InShape = None
|
||||
self.tau = -1
|
||||
self.search_mode = 'basic'
|
||||
#assert sum(x.num_conv for x in self.layers) + 1 == depth, 'invalid depth check {:} vs {:}'.format(sum(x.num_conv for x in self.layers)+1, depth)
|
||||
|
||||
# parameters for width
|
||||
self.Ranges = []
|
||||
self.layer2indexRange = []
|
||||
for i, layer in enumerate(self.layers):
|
||||
start_index = len(self.Ranges)
|
||||
self.Ranges += layer.get_range()
|
||||
self.layer2indexRange.append( (start_index, len(self.Ranges)) )
|
||||
|
||||
self.register_parameter('width_attentions', nn.Parameter(torch.Tensor(len(self.Ranges), get_width_choices(None))))
|
||||
self.register_parameter('depth_attentions', nn.Parameter(torch.Tensor(len(layers), meta_depth_info['num'])))
|
||||
nn.init.normal_(self.width_attentions, 0, 0.01)
|
||||
nn.init.normal_(self.depth_attentions, 0, 0.01)
|
||||
self.apply(initialize_resnet)
|
||||
|
||||
def arch_parameters(self, LR=None):
|
||||
if LR is None:
|
||||
return [self.width_attentions, self.depth_attentions]
|
||||
else:
|
||||
return [
|
||||
{"params": self.width_attentions, "lr": LR},
|
||||
{"params": self.depth_attentions, "lr": LR},
|
||||
]
|
||||
|
||||
def base_parameters(self):
|
||||
return list(self.layers.parameters()) + list(self.avgpool.parameters()) + list(self.classifier.parameters())
|
||||
|
||||
def get_flop(self, mode, config_dict, extra_info):
|
||||
if config_dict is not None: config_dict = config_dict.copy()
|
||||
# select channels
|
||||
channels = [3]
|
||||
for i, weight in enumerate(self.width_attentions):
|
||||
if mode == 'genotype':
|
||||
with torch.no_grad():
|
||||
probe = nn.functional.softmax(weight, dim=0)
|
||||
C = self.Ranges[i][ torch.argmax(probe).item() ]
|
||||
else:
|
||||
raise ValueError('invalid mode : {:}'.format(mode))
|
||||
channels.append( C )
|
||||
# select depth
|
||||
if mode == 'genotype':
|
||||
with torch.no_grad():
|
||||
depth_probs = nn.functional.softmax(self.depth_attentions, dim=1)
|
||||
choices = torch.argmax(depth_probs, dim=1).cpu().tolist()
|
||||
else:
|
||||
raise ValueError('invalid mode : {:}'.format(mode))
|
||||
selected_layers = []
|
||||
for choice, xvalue in zip(choices, self.depth_info_list):
|
||||
xtemp = xvalue[1]['choices'][choice] - xvalue[1]['xstart'] + 1
|
||||
selected_layers.append(xtemp)
|
||||
flop = 0
|
||||
for i, layer in enumerate(self.layers):
|
||||
s, e = self.layer2indexRange[i]
|
||||
xchl = tuple( channels[s:e+1] )
|
||||
if i in self.depth_at_i:
|
||||
xstagei, xatti = self.depth_at_i[i]
|
||||
if xatti <= choices[xstagei]: # leave this depth
|
||||
flop+= layer.get_flops(xchl)
|
||||
else:
|
||||
flop+= 0 # do not use this layer
|
||||
else:
|
||||
flop+= layer.get_flops(xchl)
|
||||
# the last fc layer
|
||||
flop += channels[-1] * self.classifier.out_features
|
||||
if config_dict is None:
|
||||
return flop / 1e6
|
||||
else:
|
||||
config_dict['xchannels'] = channels
|
||||
config_dict['xblocks'] = selected_layers
|
||||
config_dict['super_type'] = 'infer-shape'
|
||||
config_dict['estimated_FLOP'] = flop / 1e6
|
||||
return flop / 1e6, config_dict
|
||||
|
||||
def get_arch_info(self):
|
||||
string = "for depth and width, there are {:} + {:} attention probabilities.".format(len(self.depth_attentions), len(self.width_attentions))
|
||||
string+= '\n{:}'.format(self.depth_info)
|
||||
discrepancy = []
|
||||
with torch.no_grad():
|
||||
for i, att in enumerate(self.depth_attentions):
|
||||
prob = nn.functional.softmax(att, dim=0)
|
||||
prob = prob.cpu() ; selc = prob.argmax().item() ; prob = prob.tolist()
|
||||
prob = ['{:.3f}'.format(x) for x in prob]
|
||||
xstring = '{:03d}/{:03d}-th : {:}'.format(i, len(self.depth_attentions), ' '.join(prob))
|
||||
logt = ['{:.4f}'.format(x) for x in att.cpu().tolist()]
|
||||
xstring += ' || {:17s}'.format(' '.join(logt))
|
||||
prob = sorted( [float(x) for x in prob] )
|
||||
disc = prob[-1] - prob[-2]
|
||||
xstring += ' || discrepancy={:.2f} || select={:}/{:}'.format(disc, selc, len(prob))
|
||||
discrepancy.append( disc )
|
||||
string += '\n{:}'.format(xstring)
|
||||
string += '\n-----------------------------------------------'
|
||||
for i, att in enumerate(self.width_attentions):
|
||||
prob = nn.functional.softmax(att, dim=0)
|
||||
prob = prob.cpu() ; selc = prob.argmax().item() ; prob = prob.tolist()
|
||||
prob = ['{:.3f}'.format(x) for x in prob]
|
||||
xstring = '{:03d}/{:03d}-th : {:}'.format(i, len(self.width_attentions), ' '.join(prob))
|
||||
logt = ['{:.3f}'.format(x) for x in att.cpu().tolist()]
|
||||
xstring += ' || {:52s}'.format(' '.join(logt))
|
||||
prob = sorted( [float(x) for x in prob] )
|
||||
disc = prob[-1] - prob[-2]
|
||||
xstring += ' || dis={:.2f} || select={:}/{:}'.format(disc, selc, len(prob))
|
||||
discrepancy.append( disc )
|
||||
string += '\n{:}'.format(xstring)
|
||||
return string, discrepancy
|
||||
|
||||
def set_tau(self, tau_max, tau_min, epoch_ratio):
|
||||
assert epoch_ratio >= 0 and epoch_ratio <= 1, 'invalid epoch-ratio : {:}'.format(epoch_ratio)
|
||||
tau = tau_min + (tau_max-tau_min) * (1 + math.cos(math.pi * epoch_ratio)) / 2
|
||||
self.tau = tau
|
||||
|
||||
def get_message(self):
|
||||
return self.message
|
||||
|
||||
def forward(self, inputs):
|
||||
if self.search_mode == 'basic':
|
||||
return self.basic_forward(inputs)
|
||||
elif self.search_mode == 'search':
|
||||
return self.search_forward(inputs)
|
||||
else:
|
||||
raise ValueError('invalid search_mode = {:}'.format(self.search_mode))
|
||||
|
||||
def search_forward(self, inputs):
|
||||
flop_width_probs = nn.functional.softmax(self.width_attentions, dim=1)
|
||||
flop_depth_probs = nn.functional.softmax(self.depth_attentions, dim=1)
|
||||
flop_depth_probs = torch.flip( torch.cumsum( torch.flip(flop_depth_probs, [1]), 1 ), [1] )
|
||||
selected_widths, selected_width_probs = select2withP(self.width_attentions, self.tau)
|
||||
selected_depth_probs = select2withP(self.depth_attentions, self.tau, True)
|
||||
with torch.no_grad():
|
||||
selected_widths = selected_widths.cpu()
|
||||
|
||||
x, last_channel_idx, expected_inC, flops = inputs, 0, 3, []
|
||||
feature_maps = []
|
||||
for i, layer in enumerate(self.layers):
|
||||
selected_w_index = selected_widths [last_channel_idx: last_channel_idx+layer.num_conv]
|
||||
selected_w_probs = selected_width_probs[last_channel_idx: last_channel_idx+layer.num_conv]
|
||||
layer_prob = flop_width_probs [last_channel_idx: last_channel_idx+layer.num_conv]
|
||||
x, expected_inC, expected_flop = layer( (x, expected_inC, layer_prob, selected_w_index, selected_w_probs) )
|
||||
feature_maps.append( x )
|
||||
last_channel_idx += layer.num_conv
|
||||
if i in self.depth_info: # aggregate the information
|
||||
choices = self.depth_info[i]['choices']
|
||||
xstagei = self.depth_info[i]['stage']
|
||||
#print ('iL={:}, choices={:}, stage={:}, probs={:}'.format(i, choices, xstagei, selected_depth_probs[xstagei].cpu().tolist()))
|
||||
#for A, W in zip(choices, selected_depth_probs[xstagei]):
|
||||
# print('Size = {:}, W = {:}'.format(feature_maps[A].size(), W))
|
||||
possible_tensors = []
|
||||
max_C = max( feature_maps[A].size(1) for A in choices )
|
||||
for tempi, A in enumerate(choices):
|
||||
xtensor = ChannelWiseInter(feature_maps[A], max_C)
|
||||
possible_tensors.append( xtensor )
|
||||
weighted_sum = sum( xtensor * W for xtensor, W in zip(possible_tensors, selected_depth_probs[xstagei]) )
|
||||
x = weighted_sum
|
||||
|
||||
if i in self.depth_at_i:
|
||||
xstagei, xatti = self.depth_at_i[i]
|
||||
x_expected_flop = flop_depth_probs[xstagei, xatti] * expected_flop
|
||||
else:
|
||||
x_expected_flop = expected_flop
|
||||
flops.append( x_expected_flop )
|
||||
flops.append( expected_inC * (self.classifier.out_features*1.0/1e6) )
|
||||
features = self.avgpool(x)
|
||||
features = features.view(features.size(0), -1)
|
||||
logits = linear_forward(features, self.classifier)
|
||||
return logits, torch.stack( [sum(flops)] )
|
||||
|
||||
def basic_forward(self, inputs):
|
||||
if self.InShape is None: self.InShape = (inputs.size(-2), inputs.size(-1))
|
||||
x = inputs
|
||||
for i, layer in enumerate(self.layers):
|
||||
x = layer( x )
|
||||
features = self.avgpool(x)
|
||||
features = features.view(features.size(0), -1)
|
||||
logits = self.classifier(features)
|
||||
return features, logits
|
||||
108
lib/models/searchs/SoftSelect.py
Normal file
108
lib/models/searchs/SoftSelect.py
Normal file
@@ -0,0 +1,108 @@
|
||||
import math, torch
|
||||
import torch.nn as nn
|
||||
|
||||
|
||||
def select2withP(logits, tau, just_prob=False, num=2, eps=1e-7):
|
||||
if tau <= 0:
|
||||
new_logits = logits
|
||||
probs = nn.functional.softmax(new_logits, dim=1)
|
||||
else :
|
||||
while True: # a trick to avoid the gumbels bug
|
||||
gumbels = -torch.empty_like(logits).exponential_().log()
|
||||
new_logits = (logits + gumbels) / tau
|
||||
probs = nn.functional.softmax(new_logits, dim=1)
|
||||
if (not torch.isinf(gumbels).any()) and (not torch.isinf(probs).any()) and (not torch.isnan(probs).any()): break
|
||||
|
||||
if just_prob: return probs
|
||||
|
||||
#with torch.no_grad(): # add eps for unexpected torch error
|
||||
# probs = nn.functional.softmax(new_logits, dim=1)
|
||||
# selected_index = torch.multinomial(probs + eps, 2, False)
|
||||
with torch.no_grad(): # add eps for unexpected torch error
|
||||
probs = probs.cpu()
|
||||
selected_index = torch.multinomial(probs + eps, num, False).to(logits.device)
|
||||
selected_logit = torch.gather(new_logits, 1, selected_index)
|
||||
selcted_probs = nn.functional.softmax(selected_logit, dim=1)
|
||||
return selected_index, selcted_probs
|
||||
|
||||
|
||||
def ChannelWiseInter(inputs, oC, mode='v2'):
|
||||
if mode == 'v1':
|
||||
return ChannelWiseInterV1(inputs, oC)
|
||||
elif mode == 'v2':
|
||||
return ChannelWiseInterV2(inputs, oC)
|
||||
else:
|
||||
raise ValueError('invalid mode : {:}'.format(mode))
|
||||
|
||||
|
||||
def ChannelWiseInterV1(inputs, oC):
|
||||
assert inputs.dim() == 4, 'invalid dimension : {:}'.format(inputs.size())
|
||||
def start_index(a, b, c):
|
||||
return int( math.floor(float(a * c) / b) )
|
||||
def end_index(a, b, c):
|
||||
return int( math.ceil(float((a + 1) * c) / b) )
|
||||
batch, iC, H, W = inputs.size()
|
||||
outputs = torch.zeros((batch, oC, H, W), dtype=inputs.dtype, device=inputs.device)
|
||||
if iC == oC: return inputs
|
||||
for ot in range(oC):
|
||||
istartT, iendT = start_index(ot, oC, iC), end_index(ot, oC, iC)
|
||||
values = inputs[:, istartT:iendT].mean(dim=1)
|
||||
outputs[:, ot, :, :] = values
|
||||
return outputs
|
||||
|
||||
|
||||
def ChannelWiseInterV2(inputs, oC):
|
||||
assert inputs.dim() == 4, 'invalid dimension : {:}'.format(inputs.size())
|
||||
batch, C, H, W = inputs.size()
|
||||
if C == oC: return inputs
|
||||
else : return nn.functional.adaptive_avg_pool3d(inputs, (oC,H,W))
|
||||
#inputs_5D = inputs.view(batch, 1, C, H, W)
|
||||
#otputs_5D = nn.functional.interpolate(inputs_5D, (oC,H,W), None, 'area', None)
|
||||
#otputs = otputs_5D.view(batch, oC, H, W)
|
||||
#otputs_5D = nn.functional.interpolate(inputs_5D, (oC,H,W), None, 'trilinear', False)
|
||||
#return otputs
|
||||
|
||||
|
||||
def linear_forward(inputs, linear):
|
||||
if linear is None: return inputs
|
||||
iC = inputs.size(1)
|
||||
weight = linear.weight[:, :iC]
|
||||
if linear.bias is None: bias = None
|
||||
else : bias = linear.bias
|
||||
return nn.functional.linear(inputs, weight, bias)
|
||||
|
||||
|
||||
def get_width_choices(nOut):
|
||||
xsrange = [0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
|
||||
if nOut is None:
|
||||
return len(xsrange)
|
||||
else:
|
||||
Xs = [int(nOut * i) for i in xsrange]
|
||||
#xs = [ int(nOut * i // 10) for i in range(2, 11)]
|
||||
#Xs = [x for i, x in enumerate(xs) if i+1 == len(xs) or xs[i+1] > x+1]
|
||||
Xs = sorted( list( set(Xs) ) )
|
||||
return tuple(Xs)
|
||||
|
||||
|
||||
def get_depth_choices(nDepth):
|
||||
if nDepth is None:
|
||||
return 3
|
||||
else:
|
||||
assert nDepth >= 3, 'nDepth should be greater than 2 vs {:}'.format(nDepth)
|
||||
if nDepth == 1 : return (1, 1, 1)
|
||||
elif nDepth == 2: return (1, 1, 2)
|
||||
elif nDepth >= 3:
|
||||
return (nDepth//3, nDepth*2//3, nDepth)
|
||||
else:
|
||||
raise ValueError('invalid Depth : {:}'.format(nDepth))
|
||||
|
||||
|
||||
def drop_path(x, drop_prob):
|
||||
if drop_prob > 0.:
|
||||
keep_prob = 1. - drop_prob
|
||||
mask = x.new_zeros(x.size(0), 1, 1, 1)
|
||||
mask = mask.bernoulli_(keep_prob)
|
||||
x = x * (mask / keep_prob)
|
||||
#x.div_(keep_prob)
|
||||
#x.mul_(mask)
|
||||
return x
|
||||
4
lib/models/searchs/__init__.py
Normal file
4
lib/models/searchs/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from .SearchCifarResNet_width import SearchWidthCifarResNet
|
||||
from .SearchCifarResNet_depth import SearchDepthCifarResNet
|
||||
from .SearchCifarResNet import SearchShapeCifarResNet
|
||||
from .SearchImagenetResNet import SearchShapeImagenetResNet
|
||||
17
lib/models/searchs/test.py
Normal file
17
lib/models/searchs/test.py
Normal file
@@ -0,0 +1,17 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from SoftSelect import ChannelWiseInter
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
tensors = torch.rand((16, 128, 7, 7))
|
||||
|
||||
for oc in range(200, 210):
|
||||
out_v1 = ChannelWiseInter(tensors, oc, 'v1')
|
||||
out_v2 = ChannelWiseInter(tensors, oc, 'v2')
|
||||
assert (out_v1 == out_v2).any().item() == 1
|
||||
for oc in range(48, 160):
|
||||
out_v1 = ChannelWiseInter(tensors, oc, 'v1')
|
||||
out_v2 = ChannelWiseInter(tensors, oc, 'v2')
|
||||
assert (out_v1 == out_v2).any().item() == 1
|
||||
139
lib/models/sphereface.py
Normal file
139
lib/models/sphereface.py
Normal file
@@ -0,0 +1,139 @@
|
||||
# SphereFace: Deep Hypersphere Embedding for Face Recognition
|
||||
#
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
import math
|
||||
|
||||
def myphi(x,m):
|
||||
x = x * m
|
||||
return 1-x**2/math.factorial(2)+x**4/math.factorial(4)-x**6/math.factorial(6) + \
|
||||
x**8/math.factorial(8) - x**9/math.factorial(9)
|
||||
|
||||
class AngleLinear(nn.Module):
|
||||
def __init__(self, in_features, out_features, m = 4, phiflag=True):
|
||||
super(AngleLinear, self).__init__()
|
||||
self.in_features = in_features
|
||||
self.out_features = out_features
|
||||
self.weight = nn.Parameter(torch.Tensor(in_features,out_features))
|
||||
self.weight.data.uniform_(-1, 1).renorm_(2,1,1e-5).mul_(1e5)
|
||||
self.phiflag = phiflag
|
||||
self.m = m
|
||||
self.mlambda = [
|
||||
lambda x: x**0,
|
||||
lambda x: x**1,
|
||||
lambda x: 2*x**2-1,
|
||||
lambda x: 4*x**3-3*x,
|
||||
lambda x: 8*x**4-8*x**2+1,
|
||||
lambda x: 16*x**5-20*x**3+5*x
|
||||
]
|
||||
|
||||
def forward(self, input):
|
||||
x = input # size=(B,F) F is feature len
|
||||
w = self.weight # size=(F,Classnum) F=in_features Classnum=out_features
|
||||
|
||||
ww = w.renorm(2,1,1e-5).mul(1e5)
|
||||
xlen = x.pow(2).sum(1).pow(0.5) # size=B
|
||||
wlen = ww.pow(2).sum(0).pow(0.5) # size=Classnum
|
||||
|
||||
cos_theta = x.mm(ww) # size=(B,Classnum)
|
||||
cos_theta = cos_theta / xlen.view(-1,1) / wlen.view(1,-1)
|
||||
cos_theta = cos_theta.clamp(-1,1)
|
||||
|
||||
if self.phiflag:
|
||||
cos_m_theta = self.mlambda[self.m](cos_theta)
|
||||
with torch.no_grad():
|
||||
theta = cos_theta.acos()
|
||||
k = (self.m*theta/3.14159265).floor()
|
||||
n_one = k*0.0 - 1
|
||||
phi_theta = (n_one**k) * cos_m_theta - 2*k
|
||||
else:
|
||||
theta = cos_theta.acos()
|
||||
phi_theta = myphi(theta,self.m)
|
||||
phi_theta = phi_theta.clamp(-1*self.m,1)
|
||||
|
||||
cos_theta = cos_theta * xlen.view(-1,1)
|
||||
phi_theta = phi_theta * xlen.view(-1,1)
|
||||
output = (cos_theta,phi_theta)
|
||||
return output # size=(B,Classnum,2)
|
||||
|
||||
|
||||
class SphereFace20(nn.Module):
|
||||
def __init__(self, classnum=10574):
|
||||
super(SphereFace20, self).__init__()
|
||||
self.classnum = classnum
|
||||
#input = B*3*112*96
|
||||
self.conv1_1 = nn.Conv2d(3,64,3,2,1) #=>B*64*56*48
|
||||
self.relu1_1 = nn.PReLU(64)
|
||||
self.conv1_2 = nn.Conv2d(64,64,3,1,1)
|
||||
self.relu1_2 = nn.PReLU(64)
|
||||
self.conv1_3 = nn.Conv2d(64,64,3,1,1)
|
||||
self.relu1_3 = nn.PReLU(64)
|
||||
|
||||
self.conv2_1 = nn.Conv2d(64,128,3,2,1) #=>B*128*28*24
|
||||
self.relu2_1 = nn.PReLU(128)
|
||||
self.conv2_2 = nn.Conv2d(128,128,3,1,1)
|
||||
self.relu2_2 = nn.PReLU(128)
|
||||
self.conv2_3 = nn.Conv2d(128,128,3,1,1)
|
||||
self.relu2_3 = nn.PReLU(128)
|
||||
|
||||
self.conv2_4 = nn.Conv2d(128,128,3,1,1) #=>B*128*28*24
|
||||
self.relu2_4 = nn.PReLU(128)
|
||||
self.conv2_5 = nn.Conv2d(128,128,3,1,1)
|
||||
self.relu2_5 = nn.PReLU(128)
|
||||
|
||||
|
||||
self.conv3_1 = nn.Conv2d(128,256,3,2,1) #=>B*256*14*12
|
||||
self.relu3_1 = nn.PReLU(256)
|
||||
self.conv3_2 = nn.Conv2d(256,256,3,1,1)
|
||||
self.relu3_2 = nn.PReLU(256)
|
||||
self.conv3_3 = nn.Conv2d(256,256,3,1,1)
|
||||
self.relu3_3 = nn.PReLU(256)
|
||||
|
||||
self.conv3_4 = nn.Conv2d(256,256,3,1,1) #=>B*256*14*12
|
||||
self.relu3_4 = nn.PReLU(256)
|
||||
self.conv3_5 = nn.Conv2d(256,256,3,1,1)
|
||||
self.relu3_5 = nn.PReLU(256)
|
||||
|
||||
self.conv3_6 = nn.Conv2d(256,256,3,1,1) #=>B*256*14*12
|
||||
self.relu3_6 = nn.PReLU(256)
|
||||
self.conv3_7 = nn.Conv2d(256,256,3,1,1)
|
||||
self.relu3_7 = nn.PReLU(256)
|
||||
|
||||
self.conv3_8 = nn.Conv2d(256,256,3,1,1) #=>B*256*14*12
|
||||
self.relu3_8 = nn.PReLU(256)
|
||||
self.conv3_9 = nn.Conv2d(256,256,3,1,1)
|
||||
self.relu3_9 = nn.PReLU(256)
|
||||
|
||||
self.conv4_1 = nn.Conv2d(256,512,3,2,1) #=>B*512*7*6
|
||||
self.relu4_1 = nn.PReLU(512)
|
||||
self.conv4_2 = nn.Conv2d(512,512,3,1,1)
|
||||
self.relu4_2 = nn.PReLU(512)
|
||||
self.conv4_3 = nn.Conv2d(512,512,3,1,1)
|
||||
self.relu4_3 = nn.PReLU(512)
|
||||
|
||||
self.fc5 = nn.Linear(512*7*6,512)
|
||||
self.fc6 = AngleLinear(512, self.classnum)
|
||||
|
||||
|
||||
def forward(self, x):
|
||||
x = self.relu1_1(self.conv1_1(x))
|
||||
x = x + self.relu1_3(self.conv1_3(self.relu1_2(self.conv1_2(x))))
|
||||
|
||||
x = self.relu2_1(self.conv2_1(x))
|
||||
x = x + self.relu2_3(self.conv2_3(self.relu2_2(self.conv2_2(x))))
|
||||
x = x + self.relu2_5(self.conv2_5(self.relu2_4(self.conv2_4(x))))
|
||||
|
||||
x = self.relu3_1(self.conv3_1(x))
|
||||
x = x + self.relu3_3(self.conv3_3(self.relu3_2(self.conv3_2(x))))
|
||||
x = x + self.relu3_5(self.conv3_5(self.relu3_4(self.conv3_4(x))))
|
||||
x = x + self.relu3_7(self.conv3_7(self.relu3_6(self.conv3_6(x))))
|
||||
x = x + self.relu3_9(self.conv3_9(self.relu3_8(self.conv3_8(x))))
|
||||
|
||||
x = self.relu4_1(self.conv4_1(x))
|
||||
x = x + self.relu4_3(self.conv4_3(self.relu4_2(self.conv4_2(x))))
|
||||
|
||||
x = x.view(x.size(0),-1)
|
||||
features = self.fc5(x)
|
||||
logits = self.fc6(features)
|
||||
return features, logits
|
||||
@@ -1,89 +0,0 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from .construct_utils import Cell, Transition
|
||||
|
||||
class AuxiliaryHeadCIFAR(nn.Module):
|
||||
|
||||
def __init__(self, C, num_classes):
|
||||
"""assuming input size 8x8"""
|
||||
super(AuxiliaryHeadCIFAR, self).__init__()
|
||||
self.features = nn.Sequential(
|
||||
nn.ReLU(inplace=True),
|
||||
nn.AvgPool2d(5, stride=3, padding=0, count_include_pad=False), # image size = 2 x 2
|
||||
nn.Conv2d(C, 128, 1, bias=False),
|
||||
nn.BatchNorm2d(128),
|
||||
nn.ReLU(inplace=True),
|
||||
nn.Conv2d(128, 768, 2, bias=False),
|
||||
nn.BatchNorm2d(768),
|
||||
nn.ReLU(inplace=True)
|
||||
)
|
||||
self.classifier = nn.Linear(768, num_classes)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.features(x)
|
||||
x = self.classifier(x.view(x.size(0),-1))
|
||||
return x
|
||||
|
||||
|
||||
class NetworkCIFAR(nn.Module):
|
||||
|
||||
def __init__(self, C, num_classes, layers, auxiliary, genotype):
|
||||
super(NetworkCIFAR, self).__init__()
|
||||
self._layers = layers
|
||||
|
||||
stem_multiplier = 3
|
||||
C_curr = stem_multiplier*C
|
||||
self.stem = nn.Sequential(
|
||||
nn.Conv2d(3, C_curr, 3, padding=1, bias=False),
|
||||
nn.BatchNorm2d(C_curr)
|
||||
)
|
||||
|
||||
C_prev_prev, C_prev, C_curr = C_curr, C_curr, C
|
||||
self.cells = nn.ModuleList()
|
||||
reduction_prev = False
|
||||
for i in range(layers):
|
||||
if i in [layers//3, 2*layers//3]:
|
||||
C_curr *= 2
|
||||
reduction = True
|
||||
else:
|
||||
reduction = False
|
||||
if reduction and genotype.reduce is None:
|
||||
cell = Transition(C_prev_prev, C_prev, C_curr, reduction_prev)
|
||||
else:
|
||||
cell = Cell(genotype, C_prev_prev, C_prev, C_curr, reduction, reduction_prev)
|
||||
reduction_prev = reduction
|
||||
self.cells.append( cell )
|
||||
C_prev_prev, C_prev = C_prev, cell.multiplier*C_curr
|
||||
if i == 2*layers//3:
|
||||
C_to_auxiliary = C_prev
|
||||
|
||||
if auxiliary:
|
||||
self.auxiliary_head = AuxiliaryHeadCIFAR(C_to_auxiliary, num_classes)
|
||||
else:
|
||||
self.auxiliary_head = None
|
||||
self.global_pooling = nn.AdaptiveAvgPool2d(1)
|
||||
self.classifier = nn.Linear(C_prev, num_classes)
|
||||
self.drop_path_prob = -1
|
||||
|
||||
def update_drop_path(self, drop_path_prob):
|
||||
self.drop_path_prob = drop_path_prob
|
||||
|
||||
def auxiliary_param(self):
|
||||
if self.auxiliary_head is None: return []
|
||||
else: return list( self.auxiliary_head.parameters() )
|
||||
|
||||
def forward(self, inputs):
|
||||
s0 = s1 = self.stem(inputs)
|
||||
for i, cell in enumerate(self.cells):
|
||||
s0, s1 = s1, cell(s0, s1, self.drop_path_prob)
|
||||
if i == 2*self._layers//3:
|
||||
if self.auxiliary_head and self.training:
|
||||
logits_aux = self.auxiliary_head(s1)
|
||||
out = self.global_pooling(s1)
|
||||
out = out.view(out.size(0), -1)
|
||||
logits = self.classifier(out)
|
||||
|
||||
if self.auxiliary_head and self.training:
|
||||
return logits, logits_aux
|
||||
else:
|
||||
return logits
|
||||
@@ -1,104 +0,0 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from .construct_utils import Cell, Transition
|
||||
|
||||
class AuxiliaryHeadImageNet(nn.Module):
|
||||
|
||||
def __init__(self, C, num_classes):
|
||||
"""assuming input size 14x14"""
|
||||
super(AuxiliaryHeadImageNet, self).__init__()
|
||||
self.features = nn.Sequential(
|
||||
nn.ReLU(inplace=True),
|
||||
nn.AvgPool2d(5, stride=2, padding=0, count_include_pad=False),
|
||||
nn.Conv2d(C, 128, 1, bias=False),
|
||||
nn.BatchNorm2d(128),
|
||||
nn.ReLU(inplace=True),
|
||||
nn.Conv2d(128, 768, 2, bias=False),
|
||||
# NOTE: This batchnorm was omitted in my earlier implementation due to a typo.
|
||||
# Commenting it out for consistency with the experiments in the paper.
|
||||
# nn.BatchNorm2d(768),
|
||||
nn.ReLU(inplace=True)
|
||||
)
|
||||
self.classifier = nn.Linear(768, num_classes)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.features(x)
|
||||
x = self.classifier(x.view(x.size(0),-1))
|
||||
return x
|
||||
|
||||
|
||||
|
||||
|
||||
class NetworkImageNet(nn.Module):
|
||||
|
||||
def __init__(self, C, num_classes, layers, auxiliary, genotype):
|
||||
super(NetworkImageNet, self).__init__()
|
||||
self._layers = layers
|
||||
|
||||
self.stem0 = nn.Sequential(
|
||||
nn.Conv2d(3, C // 2, kernel_size=3, stride=2, padding=1, bias=False),
|
||||
nn.BatchNorm2d(C // 2),
|
||||
nn.ReLU(inplace=True),
|
||||
nn.Conv2d(C // 2, C, 3, stride=2, padding=1, bias=False),
|
||||
nn.BatchNorm2d(C),
|
||||
)
|
||||
|
||||
self.stem1 = nn.Sequential(
|
||||
nn.ReLU(inplace=True),
|
||||
nn.Conv2d(C, C, 3, stride=2, padding=1, bias=False),
|
||||
nn.BatchNorm2d(C),
|
||||
)
|
||||
|
||||
C_prev_prev, C_prev, C_curr = C, C, C
|
||||
|
||||
self.cells = nn.ModuleList()
|
||||
reduction_prev = True
|
||||
for i in range(layers):
|
||||
if i in [layers // 3, 2 * layers // 3]:
|
||||
C_curr *= 2
|
||||
reduction = True
|
||||
else:
|
||||
reduction = False
|
||||
if reduction and genotype.reduce is None:
|
||||
cell = Transition(C_prev_prev, C_prev, C_curr, reduction_prev)
|
||||
else:
|
||||
cell = Cell(genotype, C_prev_prev, C_prev, C_curr, reduction, reduction_prev)
|
||||
reduction_prev = reduction
|
||||
self.cells += [cell]
|
||||
C_prev_prev, C_prev = C_prev, cell.multiplier * C_curr
|
||||
if i == 2 * layers // 3:
|
||||
C_to_auxiliary = C_prev
|
||||
|
||||
if auxiliary:
|
||||
self.auxiliary_head = AuxiliaryHeadImageNet(C_to_auxiliary, num_classes)
|
||||
else:
|
||||
self.auxiliary_head = None
|
||||
self.global_pooling = nn.AvgPool2d(7)
|
||||
self.classifier = nn.Linear(C_prev, num_classes)
|
||||
self.drop_path_prob = -1
|
||||
|
||||
def update_drop_path(self, drop_path_prob):
|
||||
self.drop_path_prob = drop_path_prob
|
||||
|
||||
def get_drop_path(self):
|
||||
return self.drop_path_prob
|
||||
|
||||
def auxiliary_param(self):
|
||||
if self.auxiliary_head is None: return []
|
||||
else: return list( self.auxiliary_head.parameters() )
|
||||
|
||||
def forward(self, input):
|
||||
s0 = self.stem0(input)
|
||||
s1 = self.stem1(s0)
|
||||
for i, cell in enumerate(self.cells):
|
||||
s0, s1 = s1, cell(s0, s1, self.drop_path_prob)
|
||||
#print ('{:} : {:} - {:}'.format(i, s0.size(), s1.size()))
|
||||
if i == 2 * self._layers // 3:
|
||||
if self.auxiliary_head and self.training:
|
||||
logits_aux = self.auxiliary_head(s1)
|
||||
out = self.global_pooling(s1)
|
||||
logits = self.classifier(out.view(out.size(0), -1))
|
||||
if self.auxiliary_head and self.training:
|
||||
return logits, logits_aux
|
||||
else:
|
||||
return logits
|
||||
@@ -1,27 +0,0 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
# Squeeze and Excitation module
|
||||
|
||||
class SqEx(nn.Module):
|
||||
|
||||
def __init__(self, n_features, reduction=16):
|
||||
super(SqEx, self).__init__()
|
||||
|
||||
if n_features % reduction != 0:
|
||||
raise ValueError('n_features must be divisible by reduction (default = 16)')
|
||||
|
||||
self.linear1 = nn.Linear(n_features, n_features // reduction, bias=True)
|
||||
self.nonlin1 = nn.ReLU(inplace=True)
|
||||
self.linear2 = nn.Linear(n_features // reduction, n_features, bias=True)
|
||||
self.nonlin2 = nn.Sigmoid()
|
||||
|
||||
def forward(self, x):
|
||||
|
||||
y = F.avg_pool2d(x, kernel_size=x.size()[2:4])
|
||||
y = y.permute(0, 2, 3, 1)
|
||||
y = self.nonlin1(self.linear1(y))
|
||||
y = self.nonlin2(self.linear2(y))
|
||||
y = y.permute(0, 3, 1, 2)
|
||||
y = x * y
|
||||
return y
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
##################################################
|
||||
# Copyright (c) Xuanyi Dong [GitHub D-X-Y], 2019 #
|
||||
##################################################
|
||||
from .CifarNet import NetworkCIFAR
|
||||
from .ImageNet import NetworkImageNet
|
||||
|
||||
# genotypes
|
||||
from .genotypes import model_types
|
||||
|
||||
from .construct_utils import return_alphas_str
|
||||
@@ -1,152 +0,0 @@
|
||||
import random
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from .operations import OPS, FactorizedReduce, ReLUConvBN, Identity
|
||||
|
||||
|
||||
def random_select(length, ratio):
|
||||
clist = []
|
||||
index = random.randint(0, length-1)
|
||||
for i in range(length):
|
||||
if i == index or random.random() < ratio:
|
||||
clist.append( 1 )
|
||||
else:
|
||||
clist.append( 0 )
|
||||
return clist
|
||||
|
||||
|
||||
def all_select(length):
|
||||
return [1 for i in range(length)]
|
||||
|
||||
|
||||
def drop_path(x, drop_prob):
|
||||
if drop_prob > 0.:
|
||||
keep_prob = 1. - drop_prob
|
||||
mask = x.new_zeros(x.size(0), 1, 1, 1)
|
||||
mask = mask.bernoulli_(keep_prob)
|
||||
x.div_(keep_prob)
|
||||
x.mul_(mask)
|
||||
return x
|
||||
|
||||
|
||||
def return_alphas_str(basemodel):
|
||||
string = 'normal : {:}'.format( F.softmax(basemodel.alphas_normal, dim=-1) )
|
||||
if hasattr(basemodel, 'alphas_reduce'):
|
||||
string = string + '\nreduce : {:}'.format( F.softmax(basemodel.alphas_reduce, dim=-1) )
|
||||
return string
|
||||
|
||||
|
||||
class Cell(nn.Module):
|
||||
|
||||
def __init__(self, genotype, C_prev_prev, C_prev, C, reduction, reduction_prev):
|
||||
super(Cell, self).__init__()
|
||||
print(C_prev_prev, C_prev, C)
|
||||
|
||||
if reduction_prev:
|
||||
self.preprocess0 = FactorizedReduce(C_prev_prev, C)
|
||||
else:
|
||||
self.preprocess0 = ReLUConvBN(C_prev_prev, C, 1, 1, 0)
|
||||
self.preprocess1 = ReLUConvBN(C_prev, C, 1, 1, 0)
|
||||
|
||||
if reduction:
|
||||
op_names, indices, values = zip(*genotype.reduce)
|
||||
concat = genotype.reduce_concat
|
||||
else:
|
||||
op_names, indices, values = zip(*genotype.normal)
|
||||
concat = genotype.normal_concat
|
||||
self._compile(C, op_names, indices, values, concat, reduction)
|
||||
|
||||
def _compile(self, C, op_names, indices, values, concat, reduction):
|
||||
assert len(op_names) == len(indices)
|
||||
self._steps = len(op_names) // 2
|
||||
self._concat = concat
|
||||
self.multiplier = len(concat)
|
||||
|
||||
self._ops = nn.ModuleList()
|
||||
for name, index in zip(op_names, indices):
|
||||
stride = 2 if reduction and index < 2 else 1
|
||||
op = OPS[name](C, stride, True)
|
||||
self._ops.append( op )
|
||||
self._indices = indices
|
||||
self._values = values
|
||||
|
||||
def forward(self, s0, s1, drop_prob):
|
||||
s0 = self.preprocess0(s0)
|
||||
s1 = self.preprocess1(s1)
|
||||
|
||||
states = [s0, s1]
|
||||
for i in range(self._steps):
|
||||
h1 = states[self._indices[2*i]]
|
||||
h2 = states[self._indices[2*i+1]]
|
||||
op1 = self._ops[2*i]
|
||||
op2 = self._ops[2*i+1]
|
||||
h1 = op1(h1)
|
||||
h2 = op2(h2)
|
||||
if self.training and drop_prob > 0.:
|
||||
if not isinstance(op1, Identity):
|
||||
h1 = drop_path(h1, drop_prob)
|
||||
if not isinstance(op2, Identity):
|
||||
h2 = drop_path(h2, drop_prob)
|
||||
|
||||
s = h1 + h2
|
||||
|
||||
states += [s]
|
||||
return torch.cat([states[i] for i in self._concat], dim=1)
|
||||
|
||||
|
||||
|
||||
class Transition(nn.Module):
|
||||
|
||||
def __init__(self, C_prev_prev, C_prev, C, reduction_prev, multiplier=4):
|
||||
super(Transition, self).__init__()
|
||||
if reduction_prev:
|
||||
self.preprocess0 = FactorizedReduce(C_prev_prev, C)
|
||||
else:
|
||||
self.preprocess0 = ReLUConvBN(C_prev_prev, C, 1, 1, 0)
|
||||
self.preprocess1 = ReLUConvBN(C_prev, C, 1, 1, 0)
|
||||
self.multiplier = multiplier
|
||||
|
||||
self.reduction = True
|
||||
self.ops1 = nn.ModuleList(
|
||||
[nn.Sequential(
|
||||
nn.ReLU(inplace=False),
|
||||
nn.Conv2d(C, C, (1, 3), stride=(1, 2), padding=(0, 1), groups=8, bias=False),
|
||||
nn.Conv2d(C, C, (3, 1), stride=(2, 1), padding=(1, 0), groups=8, bias=False),
|
||||
nn.BatchNorm2d(C, affine=True),
|
||||
nn.ReLU(inplace=False),
|
||||
nn.Conv2d(C, C, 1, stride=1, padding=0, bias=False),
|
||||
nn.BatchNorm2d(C, affine=True)),
|
||||
nn.Sequential(
|
||||
nn.ReLU(inplace=False),
|
||||
nn.Conv2d(C, C, (1, 3), stride=(1, 2), padding=(0, 1), groups=8, bias=False),
|
||||
nn.Conv2d(C, C, (3, 1), stride=(2, 1), padding=(1, 0), groups=8, bias=False),
|
||||
nn.BatchNorm2d(C, affine=True),
|
||||
nn.ReLU(inplace=False),
|
||||
nn.Conv2d(C, C, 1, stride=1, padding=0, bias=False),
|
||||
nn.BatchNorm2d(C, affine=True))])
|
||||
|
||||
self.ops2 = nn.ModuleList(
|
||||
[nn.Sequential(
|
||||
nn.MaxPool2d(3, stride=2, padding=1),
|
||||
nn.BatchNorm2d(C, affine=True)),
|
||||
nn.Sequential(
|
||||
nn.MaxPool2d(3, stride=2, padding=1),
|
||||
nn.BatchNorm2d(C, affine=True))])
|
||||
|
||||
|
||||
def forward(self, s0, s1, drop_prob = -1):
|
||||
s0 = self.preprocess0(s0)
|
||||
s1 = self.preprocess1(s1)
|
||||
|
||||
X0 = self.ops1[0] (s0)
|
||||
X1 = self.ops1[1] (s1)
|
||||
if self.training and drop_prob > 0.:
|
||||
X0, X1 = drop_path(X0, drop_prob), drop_path(X1, drop_prob)
|
||||
|
||||
#X2 = self.ops2[0] (X0+X1)
|
||||
X2 = self.ops2[0] (s0)
|
||||
X3 = self.ops2[1] (s1)
|
||||
if self.training and drop_prob > 0.:
|
||||
X2, X3 = drop_path(X2, drop_prob), drop_path(X3, drop_prob)
|
||||
return torch.cat([X0, X1, X2, X3], dim=1)
|
||||
@@ -1,245 +0,0 @@
|
||||
from collections import namedtuple
|
||||
|
||||
Genotype = namedtuple('Genotype', 'normal normal_concat reduce reduce_concat')
|
||||
|
||||
PRIMITIVES = [
|
||||
'none',
|
||||
'max_pool_3x3',
|
||||
'avg_pool_3x3',
|
||||
'skip_connect',
|
||||
'sep_conv_3x3',
|
||||
'sep_conv_5x5',
|
||||
'dil_conv_3x3',
|
||||
'dil_conv_5x5'
|
||||
]
|
||||
|
||||
NASNet = Genotype(
|
||||
normal = [
|
||||
('sep_conv_5x5', 1, 1.0),
|
||||
('sep_conv_3x3', 0, 1.0),
|
||||
('sep_conv_5x5', 0, 1.0),
|
||||
('sep_conv_3x3', 0, 1.0),
|
||||
('avg_pool_3x3', 1, 1.0),
|
||||
('skip_connect', 0, 1.0),
|
||||
('avg_pool_3x3', 0, 1.0),
|
||||
('avg_pool_3x3', 0, 1.0),
|
||||
('sep_conv_3x3', 1, 1.0),
|
||||
('skip_connect', 1, 1.0),
|
||||
],
|
||||
normal_concat = [2, 3, 4, 5, 6],
|
||||
reduce = [
|
||||
('sep_conv_5x5', 1, 1.0),
|
||||
('sep_conv_7x7', 0, 1.0),
|
||||
('max_pool_3x3', 1, 1.0),
|
||||
('sep_conv_7x7', 0, 1.0),
|
||||
('avg_pool_3x3', 1, 1.0),
|
||||
('sep_conv_5x5', 0, 1.0),
|
||||
('skip_connect', 3, 1.0),
|
||||
('avg_pool_3x3', 2, 1.0),
|
||||
('sep_conv_3x3', 2, 1.0),
|
||||
('max_pool_3x3', 1, 1.0),
|
||||
],
|
||||
reduce_concat = [4, 5, 6],
|
||||
)
|
||||
|
||||
AmoebaNet = Genotype(
|
||||
normal = [
|
||||
('avg_pool_3x3', 0, 1.0),
|
||||
('max_pool_3x3', 1, 1.0),
|
||||
('sep_conv_3x3', 0, 1.0),
|
||||
('sep_conv_5x5', 2, 1.0),
|
||||
('sep_conv_3x3', 0, 1.0),
|
||||
('avg_pool_3x3', 3, 1.0),
|
||||
('sep_conv_3x3', 1, 1.0),
|
||||
('skip_connect', 1, 1.0),
|
||||
('skip_connect', 0, 1.0),
|
||||
('avg_pool_3x3', 1, 1.0),
|
||||
],
|
||||
normal_concat = [4, 5, 6],
|
||||
reduce = [
|
||||
('avg_pool_3x3', 0, 1.0),
|
||||
('sep_conv_3x3', 1, 1.0),
|
||||
('max_pool_3x3', 0, 1.0),
|
||||
('sep_conv_7x7', 2, 1.0),
|
||||
('sep_conv_7x7', 0, 1.0),
|
||||
('avg_pool_3x3', 1, 1.0),
|
||||
('max_pool_3x3', 0, 1.0),
|
||||
('max_pool_3x3', 1, 1.0),
|
||||
('conv_7x1_1x7', 0, 1.0),
|
||||
('sep_conv_3x3', 5, 1.0),
|
||||
],
|
||||
reduce_concat = [3, 4, 6]
|
||||
)
|
||||
|
||||
DARTS_V1 = Genotype(
|
||||
normal=[
|
||||
('sep_conv_3x3', 1, 1.0),
|
||||
('sep_conv_3x3', 0, 1.0),
|
||||
('skip_connect', 0, 1.0),
|
||||
('sep_conv_3x3', 1, 1.0),
|
||||
('skip_connect', 0, 1.0),
|
||||
('sep_conv_3x3', 1, 1.0),
|
||||
('sep_conv_3x3', 0, 1.0),
|
||||
('skip_connect', 2, 1.0)],
|
||||
normal_concat=[2, 3, 4, 5],
|
||||
reduce=[
|
||||
('max_pool_3x3', 0, 1.0),
|
||||
('max_pool_3x3', 1, 1.0),
|
||||
('skip_connect', 2, 1.0),
|
||||
('max_pool_3x3', 0, 1.0),
|
||||
('max_pool_3x3', 0, 1.0),
|
||||
('skip_connect', 2, 1.0),
|
||||
('skip_connect', 2, 1.0),
|
||||
('avg_pool_3x3', 0, 1.0)],
|
||||
reduce_concat=[2, 3, 4, 5]
|
||||
)
|
||||
|
||||
DARTS_V2 = Genotype(
|
||||
normal=[
|
||||
('sep_conv_3x3', 0, 1.0),
|
||||
('sep_conv_3x3', 1, 1.0),
|
||||
('sep_conv_3x3', 0, 1.0),
|
||||
('sep_conv_3x3', 1, 1.0),
|
||||
('sep_conv_3x3', 1, 1.0),
|
||||
('skip_connect', 0, 1.0),
|
||||
('skip_connect', 0, 1.0),
|
||||
('dil_conv_3x3', 2, 1.0)],
|
||||
normal_concat=[2, 3, 4, 5],
|
||||
reduce=[
|
||||
('max_pool_3x3', 0, 1.0),
|
||||
('max_pool_3x3', 1, 1.0),
|
||||
('skip_connect', 2, 1.0),
|
||||
('max_pool_3x3', 1, 1.0),
|
||||
('max_pool_3x3', 0, 1.0),
|
||||
('skip_connect', 2, 1.0),
|
||||
('skip_connect', 2, 1.0),
|
||||
('max_pool_3x3', 1, 1.0)],
|
||||
reduce_concat=[2, 3, 4, 5]
|
||||
)
|
||||
|
||||
PNASNet = Genotype(
|
||||
normal = [
|
||||
('sep_conv_5x5', 0, 1.0),
|
||||
('max_pool_3x3', 0, 1.0),
|
||||
('sep_conv_7x7', 1, 1.0),
|
||||
('max_pool_3x3', 1, 1.0),
|
||||
('sep_conv_5x5', 1, 1.0),
|
||||
('sep_conv_3x3', 1, 1.0),
|
||||
('sep_conv_3x3', 4, 1.0),
|
||||
('max_pool_3x3', 1, 1.0),
|
||||
('sep_conv_3x3', 0, 1.0),
|
||||
('skip_connect', 1, 1.0),
|
||||
],
|
||||
normal_concat = [2, 3, 4, 5, 6],
|
||||
reduce = [
|
||||
('sep_conv_5x5', 0, 1.0),
|
||||
('max_pool_3x3', 0, 1.0),
|
||||
('sep_conv_7x7', 1, 1.0),
|
||||
('max_pool_3x3', 1, 1.0),
|
||||
('sep_conv_5x5', 1, 1.0),
|
||||
('sep_conv_3x3', 1, 1.0),
|
||||
('sep_conv_3x3', 4, 1.0),
|
||||
('max_pool_3x3', 1, 1.0),
|
||||
('sep_conv_3x3', 0, 1.0),
|
||||
('skip_connect', 1, 1.0),
|
||||
],
|
||||
reduce_concat = [2, 3, 4, 5, 6],
|
||||
)
|
||||
|
||||
# https://arxiv.org/pdf/1802.03268.pdf
|
||||
ENASNet = Genotype(
|
||||
normal = [
|
||||
('sep_conv_3x3', 1, 1.0),
|
||||
('skip_connect', 1, 1.0),
|
||||
('sep_conv_5x5', 1, 1.0),
|
||||
('skip_connect', 0, 1.0),
|
||||
('avg_pool_3x3', 0, 1.0),
|
||||
('sep_conv_3x3', 1, 1.0),
|
||||
('sep_conv_3x3', 0, 1.0),
|
||||
('avg_pool_3x3', 1, 1.0),
|
||||
('sep_conv_5x5', 1, 1.0),
|
||||
('avg_pool_3x3', 0, 1.0),
|
||||
],
|
||||
normal_concat = [2, 3, 4, 5, 6],
|
||||
reduce = [
|
||||
('sep_conv_5x5', 0, 1.0),
|
||||
('sep_conv_3x3', 1, 1.0), # 2
|
||||
('sep_conv_3x3', 1, 1.0),
|
||||
('avg_pool_3x3', 1, 1.0), # 3
|
||||
('sep_conv_3x3', 1, 1.0),
|
||||
('avg_pool_3x3', 1, 1.0), # 4
|
||||
('avg_pool_3x3', 1, 1.0),
|
||||
('sep_conv_5x5', 4, 1.0), # 5
|
||||
('sep_conv_3x3', 5, 1.0),
|
||||
('sep_conv_5x5', 0, 1.0),
|
||||
],
|
||||
reduce_concat = [2, 3, 4, 5, 6],
|
||||
)
|
||||
|
||||
DARTS = DARTS_V2
|
||||
|
||||
# Search by normal and reduce
|
||||
GDAS_V1 = Genotype(
|
||||
normal=[('skip_connect', 0, 0.13017432391643524), ('skip_connect', 1, 0.12947972118854523), ('skip_connect', 0, 0.13062666356563568), ('sep_conv_5x5', 2, 0.12980839610099792), ('sep_conv_3x3', 3, 0.12923765182495117), ('skip_connect', 0, 0.12901571393013), ('sep_conv_5x5', 4, 0.12938997149467468), ('sep_conv_3x3', 3, 0.1289220005273819)],
|
||||
normal_concat=range(2, 6),
|
||||
reduce=[('sep_conv_5x5', 0, 0.12862831354141235), ('sep_conv_3x3', 1, 0.12783904373645782), ('sep_conv_5x5', 2, 0.12725995481014252), ('sep_conv_5x5', 1, 0.12705285847187042), ('dil_conv_5x5', 2, 0.12797553837299347), ('sep_conv_3x3', 1, 0.12737272679805756), ('sep_conv_5x5', 0, 0.12833961844444275), ('sep_conv_5x5', 1, 0.12758426368236542)],
|
||||
reduce_concat=range(2, 6)
|
||||
)
|
||||
|
||||
# Search by normal and fixing reduction
|
||||
GDAS_F1 = Genotype(
|
||||
normal=[('skip_connect', 0, 0.16), ('skip_connect', 1, 0.13), ('skip_connect', 0, 0.17), ('sep_conv_3x3', 2, 0.15), ('skip_connect', 0, 0.17), ('sep_conv_3x3', 2, 0.15), ('skip_connect', 0, 0.16), ('sep_conv_3x3', 2, 0.15)],
|
||||
normal_concat=[2, 3, 4, 5],
|
||||
reduce=None,
|
||||
reduce_concat=[2, 3, 4, 5],
|
||||
)
|
||||
|
||||
# Combine DMS_V1 and DMS_F1
|
||||
GDAS_GF = Genotype(
|
||||
normal=[('skip_connect', 0, 0.13017432391643524), ('skip_connect', 1, 0.12947972118854523), ('skip_connect', 0, 0.13062666356563568), ('sep_conv_5x5', 2, 0.12980839610099792), ('sep_conv_3x3', 3, 0.12923765182495117), ('skip_connect', 0, 0.12901571393013), ('sep_conv_5x5', 4, 0.12938997149467468), ('sep_conv_3x3', 3, 0.1289220005273819)],
|
||||
normal_concat=range(2, 6),
|
||||
reduce=None,
|
||||
reduce_concat=range(2, 6)
|
||||
)
|
||||
GDAS_FG = Genotype(
|
||||
normal=[('skip_connect', 0, 0.16), ('skip_connect', 1, 0.13), ('skip_connect', 0, 0.17), ('sep_conv_3x3', 2, 0.15), ('skip_connect', 0, 0.17), ('sep_conv_3x3', 2, 0.15), ('skip_connect', 0, 0.16), ('sep_conv_3x3', 2, 0.15)],
|
||||
normal_concat=range(2, 6),
|
||||
reduce=[('sep_conv_5x5', 0, 0.12862831354141235), ('sep_conv_3x3', 1, 0.12783904373645782), ('sep_conv_5x5', 2, 0.12725995481014252), ('sep_conv_5x5', 1, 0.12705285847187042), ('dil_conv_5x5', 2, 0.12797553837299347), ('sep_conv_3x3', 1, 0.12737272679805756), ('sep_conv_5x5', 0, 0.12833961844444275), ('sep_conv_5x5', 1, 0.12758426368236542)],
|
||||
reduce_concat=range(2, 6)
|
||||
)
|
||||
|
||||
PDARTS = Genotype(
|
||||
normal=[
|
||||
('skip_connect', 0, 1.0),
|
||||
('dil_conv_3x3', 1, 1.0),
|
||||
('skip_connect', 0, 1.0),
|
||||
('sep_conv_3x3', 1, 1.0),
|
||||
('sep_conv_3x3', 1, 1.0),
|
||||
('sep_conv_3x3', 3, 1.0),
|
||||
('sep_conv_3x3', 0, 1.0),
|
||||
('dil_conv_5x5', 4, 1.0)],
|
||||
normal_concat=range(2, 6),
|
||||
reduce=[
|
||||
('avg_pool_3x3', 0, 1.0),
|
||||
('sep_conv_5x5', 1, 1.0),
|
||||
('sep_conv_3x3', 0, 1.0),
|
||||
('dil_conv_5x5', 2, 1.0),
|
||||
('max_pool_3x3', 0, 1.0),
|
||||
('dil_conv_3x3', 1, 1.0),
|
||||
('dil_conv_3x3', 1, 1.0),
|
||||
('dil_conv_5x5', 3, 1.0)],
|
||||
reduce_concat=range(2, 6)
|
||||
)
|
||||
|
||||
|
||||
model_types = {'DARTS_V1': DARTS_V1,
|
||||
'DARTS_V2': DARTS_V2,
|
||||
'NASNet' : NASNet,
|
||||
'PNASNet' : PNASNet,
|
||||
'AmoebaNet': AmoebaNet,
|
||||
'ENASNet' : ENASNet,
|
||||
'PDARTS' : PDARTS,
|
||||
'GDAS_V1' : GDAS_V1,
|
||||
'GDAS_F1' : GDAS_F1,
|
||||
'GDAS_GF' : GDAS_GF,
|
||||
'GDAS_FG' : GDAS_FG}
|
||||
@@ -1,19 +0,0 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
|
||||
class ImageNetHEAD(nn.Sequential):
|
||||
def __init__(self, C, stride=2):
|
||||
super(ImageNetHEAD, self).__init__()
|
||||
self.add_module('conv1', nn.Conv2d(3, C // 2, kernel_size=3, stride=2, padding=1, bias=False))
|
||||
self.add_module('bn1' , nn.BatchNorm2d(C // 2))
|
||||
self.add_module('relu1', nn.ReLU(inplace=True))
|
||||
self.add_module('conv2', nn.Conv2d(C // 2, C, kernel_size=3, stride=stride, padding=1, bias=False))
|
||||
self.add_module('bn2' , nn.BatchNorm2d(C))
|
||||
|
||||
|
||||
class CifarHEAD(nn.Sequential):
|
||||
def __init__(self, C):
|
||||
super(CifarHEAD, self).__init__()
|
||||
self.add_module('conv', nn.Conv2d(3, C, kernel_size=3, padding=1, bias=False))
|
||||
self.add_module('bn', nn.BatchNorm2d(C))
|
||||
@@ -1,122 +0,0 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
OPS = {
|
||||
'none' : lambda C, stride, affine: Zero(stride),
|
||||
'avg_pool_3x3' : lambda C, stride, affine: nn.Sequential(
|
||||
nn.AvgPool2d(3, stride=stride, padding=1, count_include_pad=False),
|
||||
nn.BatchNorm2d(C, affine=False) ),
|
||||
'max_pool_3x3' : lambda C, stride, affine: nn.Sequential(
|
||||
nn.MaxPool2d(3, stride=stride, padding=1),
|
||||
nn.BatchNorm2d(C, affine=False) ),
|
||||
'skip_connect' : lambda C, stride, affine: Identity() if stride == 1 else FactorizedReduce(C, C, affine=affine),
|
||||
'sep_conv_3x3' : lambda C, stride, affine: SepConv(C, C, 3, stride, 1, affine=affine),
|
||||
'sep_conv_5x5' : lambda C, stride, affine: SepConv(C, C, 5, stride, 2, affine=affine),
|
||||
'sep_conv_7x7' : lambda C, stride, affine: SepConv(C, C, 7, stride, 3, affine=affine),
|
||||
'dil_conv_3x3' : lambda C, stride, affine: DilConv(C, C, 3, stride, 2, 2, affine=affine),
|
||||
'dil_conv_5x5' : lambda C, stride, affine: DilConv(C, C, 5, stride, 4, 2, affine=affine),
|
||||
'conv_7x1_1x7' : lambda C, stride, affine: Conv717(C, C, stride, affine),
|
||||
}
|
||||
|
||||
class Conv717(nn.Module):
|
||||
|
||||
def __init__(self, C_in, C_out, stride, affine):
|
||||
super(Conv717, self).__init__()
|
||||
self.op = nn.Sequential(
|
||||
nn.ReLU(inplace=False),
|
||||
nn.Conv2d(C_in , C_out, (1,7), stride=(1, stride), padding=(0, 3), bias=False),
|
||||
nn.Conv2d(C_out, C_out, (7,1), stride=(stride, 1), padding=(3, 0), bias=False),
|
||||
nn.BatchNorm2d(C_out, affine=affine)
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
return self.op(x)
|
||||
|
||||
|
||||
class ReLUConvBN(nn.Module):
|
||||
|
||||
def __init__(self, C_in, C_out, kernel_size, stride, padding, affine=True):
|
||||
super(ReLUConvBN, self).__init__()
|
||||
self.op = nn.Sequential(
|
||||
nn.ReLU(inplace=False),
|
||||
nn.Conv2d(C_in, C_out, kernel_size, stride=stride, padding=padding, bias=False),
|
||||
nn.BatchNorm2d(C_out, affine=affine)
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
return self.op(x)
|
||||
|
||||
|
||||
class DilConv(nn.Module):
|
||||
|
||||
def __init__(self, C_in, C_out, kernel_size, stride, padding, dilation, affine=True):
|
||||
super(DilConv, self).__init__()
|
||||
self.op = nn.Sequential(
|
||||
nn.ReLU(inplace=False),
|
||||
nn.Conv2d(C_in, C_in, kernel_size=kernel_size, stride=stride, padding=padding, dilation=dilation, groups=C_in, bias=False),
|
||||
nn.Conv2d(C_in, C_out, kernel_size=1, padding=0, bias=False),
|
||||
nn.BatchNorm2d(C_out, affine=affine),
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
return self.op(x)
|
||||
|
||||
|
||||
class SepConv(nn.Module):
|
||||
|
||||
def __init__(self, C_in, C_out, kernel_size, stride, padding, affine=True):
|
||||
super(SepConv, self).__init__()
|
||||
self.op = nn.Sequential(
|
||||
nn.ReLU(inplace=False),
|
||||
nn.Conv2d(C_in, C_in, kernel_size=kernel_size, stride=stride, padding=padding, groups=C_in, bias=False),
|
||||
nn.Conv2d(C_in, C_in, kernel_size=1, padding=0, bias=False),
|
||||
nn.BatchNorm2d(C_in, affine=affine),
|
||||
nn.ReLU(inplace=False),
|
||||
nn.Conv2d(C_in, C_in, kernel_size=kernel_size, stride=1, padding=padding, groups=C_in, bias=False),
|
||||
nn.Conv2d(C_in, C_out, kernel_size=1, padding=0, bias=False),
|
||||
nn.BatchNorm2d(C_out, affine=affine),
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
return self.op(x)
|
||||
|
||||
|
||||
class Identity(nn.Module):
|
||||
|
||||
def __init__(self):
|
||||
super(Identity, self).__init__()
|
||||
|
||||
def forward(self, x):
|
||||
return x
|
||||
|
||||
|
||||
class Zero(nn.Module):
|
||||
|
||||
def __init__(self, stride):
|
||||
super(Zero, self).__init__()
|
||||
self.stride = stride
|
||||
|
||||
def forward(self, x):
|
||||
if self.stride == 1:
|
||||
return x.mul(0.)
|
||||
return x[:,:,::self.stride,::self.stride].mul(0.)
|
||||
|
||||
|
||||
class FactorizedReduce(nn.Module):
|
||||
|
||||
def __init__(self, C_in, C_out, affine=True):
|
||||
super(FactorizedReduce, self).__init__()
|
||||
assert C_out % 2 == 0
|
||||
self.relu = nn.ReLU(inplace=False)
|
||||
self.conv_1 = nn.Conv2d(C_in, C_out // 2, 1, stride=2, padding=0, bias=False)
|
||||
self.conv_2 = nn.Conv2d(C_in, C_out // 2, 1, stride=2, padding=0, bias=False)
|
||||
self.bn = nn.BatchNorm2d(C_out, affine=affine)
|
||||
self.pad = nn.ConstantPad2d((0, 1, 0, 1), 0)
|
||||
|
||||
|
||||
def forward(self, x):
|
||||
x = self.relu(x)
|
||||
y = self.pad(x)
|
||||
out = torch.cat([self.conv_1(x), self.conv_2(y[:,:,1:,1:])], dim=1)
|
||||
out = self.bn(out)
|
||||
return out
|
||||
76
lib/nas_infer_model/DXYs/CifarNet.py
Normal file
76
lib/nas_infer_model/DXYs/CifarNet.py
Normal file
@@ -0,0 +1,76 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from .construct_utils import drop_path
|
||||
from .head_utils import CifarHEAD, AuxiliaryHeadCIFAR
|
||||
from .base_cells import InferCell
|
||||
|
||||
|
||||
class NetworkCIFAR(nn.Module):
|
||||
|
||||
def __init__(self, C, N, stem_multiplier, auxiliary, genotype, num_classes):
|
||||
super(NetworkCIFAR, self).__init__()
|
||||
self._C = C
|
||||
self._layerN = N
|
||||
self._stem_multiplier = stem_multiplier
|
||||
|
||||
C_curr = self._stem_multiplier * C
|
||||
self.stem = CifarHEAD(C_curr)
|
||||
|
||||
layer_channels = [C ] * N + [C*2 ] + [C*2 ] * N + [C*4 ] + [C*4 ] * N
|
||||
layer_reductions = [False] * N + [True] + [False] * N + [True] + [False] * N
|
||||
block_indexs = [0 ] * N + [-1 ] + [1 ] * N + [-1 ] + [2 ] * N
|
||||
block2index = {0:[], 1:[], 2:[]}
|
||||
|
||||
C_prev_prev, C_prev, C_curr = C_curr, C_curr, C
|
||||
reduction_prev, spatial, dims = False, 1, []
|
||||
self.auxiliary_index = None
|
||||
self.auxiliary_head = None
|
||||
self.cells = nn.ModuleList()
|
||||
for index, (C_curr, reduction) in enumerate(zip(layer_channels, layer_reductions)):
|
||||
cell = InferCell(genotype, C_prev_prev, C_prev, C_curr, reduction, reduction_prev)
|
||||
reduction_prev = reduction
|
||||
self.cells.append( cell )
|
||||
C_prev_prev, C_prev = C_prev, cell._multiplier*C_curr
|
||||
if reduction and C_curr == C*4:
|
||||
if auxiliary:
|
||||
self.auxiliary_head = AuxiliaryHeadCIFAR(C_prev, num_classes)
|
||||
self.auxiliary_index = index
|
||||
|
||||
if reduction: spatial *= 2
|
||||
dims.append( (C_prev, spatial) )
|
||||
|
||||
self._Layer= len(self.cells)
|
||||
|
||||
|
||||
self.global_pooling = nn.AdaptiveAvgPool2d(1)
|
||||
self.classifier = nn.Linear(C_prev, num_classes)
|
||||
self.drop_path_prob = -1
|
||||
|
||||
def update_drop_path(self, drop_path_prob):
|
||||
self.drop_path_prob = drop_path_prob
|
||||
|
||||
def auxiliary_param(self):
|
||||
if self.auxiliary_head is None: return []
|
||||
else: return list( self.auxiliary_head.parameters() )
|
||||
|
||||
def get_message(self):
|
||||
return self.extra_repr()
|
||||
|
||||
def extra_repr(self):
|
||||
return ('{name}(C={_C}, N={_layerN}, L={_Layer}, stem={_stem_multiplier}, drop-path={drop_path_prob})'.format(name=self.__class__.__name__, **self.__dict__))
|
||||
|
||||
def forward(self, inputs):
|
||||
stem_feature, logits_aux = self.stem(inputs), None
|
||||
cell_results = [stem_feature, stem_feature]
|
||||
for i, cell in enumerate(self.cells):
|
||||
cell_feature = cell(cell_results[-2], cell_results[-1], self.drop_path_prob)
|
||||
cell_results.append( cell_feature )
|
||||
|
||||
if self.auxiliary_index is not None and i == self.auxiliary_index and self.training:
|
||||
logits_aux = self.auxiliary_head( cell_results[-1] )
|
||||
out = self.global_pooling( cell_results[-1] )
|
||||
out = out.view(out.size(0), -1)
|
||||
logits = self.classifier(out)
|
||||
|
||||
if logits_aux is None: return out, logits
|
||||
else : return out, [logits, logits_aux]
|
||||
77
lib/nas_infer_model/DXYs/ImageNet.py
Normal file
77
lib/nas_infer_model/DXYs/ImageNet.py
Normal file
@@ -0,0 +1,77 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from .construct_utils import drop_path
|
||||
from .base_cells import InferCell
|
||||
from .head_utils import ImageNetHEAD, AuxiliaryHeadImageNet
|
||||
|
||||
|
||||
class NetworkImageNet(nn.Module):
|
||||
|
||||
def __init__(self, C, N, auxiliary, genotype, num_classes):
|
||||
super(NetworkImageNet, self).__init__()
|
||||
self._C = C
|
||||
self._layerN = N
|
||||
layer_channels = [C ] * N + [C*2 ] + [C*2 ] * N + [C*4 ] + [C*4] * N
|
||||
layer_reductions = [False] * N + [True] + [False] * N + [True] + [False] * N
|
||||
self.stem0 = nn.Sequential(
|
||||
nn.Conv2d(3, C // 2, kernel_size=3, stride=2, padding=1, bias=False),
|
||||
nn.BatchNorm2d(C // 2),
|
||||
nn.ReLU(inplace=True),
|
||||
nn.Conv2d(C // 2, C, 3, stride=2, padding=1, bias=False),
|
||||
nn.BatchNorm2d(C),
|
||||
)
|
||||
|
||||
self.stem1 = nn.Sequential(
|
||||
nn.ReLU(inplace=True),
|
||||
nn.Conv2d(C, C, 3, stride=2, padding=1, bias=False),
|
||||
nn.BatchNorm2d(C),
|
||||
)
|
||||
|
||||
C_prev_prev, C_prev, C_curr, reduction_prev = C, C, C, True
|
||||
|
||||
self.cells = nn.ModuleList()
|
||||
self.auxiliary_index = None
|
||||
for i, (C_curr, reduction) in enumerate(zip(layer_channels, layer_reductions)):
|
||||
cell = InferCell(genotype, C_prev_prev, C_prev, C_curr, reduction, reduction_prev)
|
||||
reduction_prev = reduction
|
||||
self.cells += [cell]
|
||||
C_prev_prev, C_prev = C_prev, cell._multiplier * C_curr
|
||||
if reduction and C_curr == C*4:
|
||||
C_to_auxiliary = C_prev
|
||||
self.auxiliary_index = i
|
||||
|
||||
self._NNN = len(self.cells)
|
||||
if auxiliary:
|
||||
self.auxiliary_head = AuxiliaryHeadImageNet(C_to_auxiliary, num_classes)
|
||||
else:
|
||||
self.auxiliary_head = None
|
||||
self.global_pooling = nn.AvgPool2d(7)
|
||||
self.classifier = nn.Linear(C_prev, num_classes)
|
||||
self.drop_path_prob = -1
|
||||
|
||||
def update_drop_path(self, drop_path_prob):
|
||||
self.drop_path_prob = drop_path_prob
|
||||
|
||||
def extra_repr(self):
|
||||
return ('{name}(C={_C}, N=[{_layerN}, {_NNN}], aux-index={auxiliary_index}, drop-path={drop_path_prob})'.format(name=self.__class__.__name__, **self.__dict__))
|
||||
|
||||
def get_message(self):
|
||||
return self.extra_repr()
|
||||
|
||||
def auxiliary_param(self):
|
||||
if self.auxiliary_head is None: return []
|
||||
else: return list( self.auxiliary_head.parameters() )
|
||||
|
||||
def forward(self, inputs):
|
||||
s0 = self.stem0(inputs)
|
||||
s1 = self.stem1(s0)
|
||||
logits_aux = None
|
||||
for i, cell in enumerate(self.cells):
|
||||
s0, s1 = s1, cell(s0, s1, self.drop_path_prob)
|
||||
if i == self.auxiliary_index and self.auxiliary_head and self.training:
|
||||
logits_aux = self.auxiliary_head(s1)
|
||||
out = self.global_pooling(s1)
|
||||
logits = self.classifier(out.view(out.size(0), -1))
|
||||
|
||||
if logits_aux is None: return out, logits
|
||||
else : return out, [logits, logits_aux]
|
||||
4
lib/nas_infer_model/DXYs/__init__.py
Normal file
4
lib/nas_infer_model/DXYs/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# Performance-Aware Template Network for One-Shot Neural Architecture Search
|
||||
from .CifarNet import NetworkCIFAR as CifarNet
|
||||
from .ImageNet import NetworkImageNet as ImageNet
|
||||
from .genotypes import Networks
|
||||
173
lib/nas_infer_model/DXYs/base_cells.py
Normal file
173
lib/nas_infer_model/DXYs/base_cells.py
Normal file
@@ -0,0 +1,173 @@
|
||||
import math
|
||||
from copy import deepcopy
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from .construct_utils import drop_path
|
||||
from ..operations import OPS, Identity, FactorizedReduce, ReLUConvBN
|
||||
|
||||
|
||||
class MixedOp(nn.Module):
|
||||
|
||||
def __init__(self, C, stride, PRIMITIVES):
|
||||
super(MixedOp, self).__init__()
|
||||
self._ops = nn.ModuleList()
|
||||
self.name2idx = {}
|
||||
for idx, primitive in enumerate(PRIMITIVES):
|
||||
op = OPS[primitive](C, C, stride, False)
|
||||
self._ops.append(op)
|
||||
assert primitive not in self.name2idx, '{:} has already in'.format(primitive)
|
||||
self.name2idx[primitive] = idx
|
||||
|
||||
def forward(self, x, weights, op_name):
|
||||
if op_name is None:
|
||||
if weights is None:
|
||||
return [op(x) for op in self._ops]
|
||||
else:
|
||||
return sum(w * op(x) for w, op in zip(weights, self._ops))
|
||||
else:
|
||||
op_index = self.name2idx[op_name]
|
||||
return self._ops[op_index](x)
|
||||
|
||||
|
||||
|
||||
class SearchCell(nn.Module):
|
||||
|
||||
def __init__(self, steps, multiplier, C_prev_prev, C_prev, C, reduction, reduction_prev, PRIMITIVES, use_residual):
|
||||
super(SearchCell, self).__init__()
|
||||
self.reduction = reduction
|
||||
self.PRIMITIVES = deepcopy(PRIMITIVES)
|
||||
|
||||
if reduction_prev:
|
||||
self.preprocess0 = FactorizedReduce(C_prev_prev, C, 2, affine=False)
|
||||
else:
|
||||
self.preprocess0 = ReLUConvBN(C_prev_prev, C, 1, 1, 0, affine=False)
|
||||
self.preprocess1 = ReLUConvBN(C_prev, C, 1, 1, 0, affine=False)
|
||||
self._steps = steps
|
||||
self._multiplier = multiplier
|
||||
self._use_residual = use_residual
|
||||
|
||||
self._ops = nn.ModuleList()
|
||||
for i in range(self._steps):
|
||||
for j in range(2+i):
|
||||
stride = 2 if reduction and j < 2 else 1
|
||||
op = MixedOp(C, stride, self.PRIMITIVES)
|
||||
self._ops.append(op)
|
||||
|
||||
def extra_repr(self):
|
||||
return ('{name}(residual={_use_residual}, steps={_steps}, multiplier={_multiplier})'.format(name=self.__class__.__name__, **self.__dict__))
|
||||
|
||||
def forward(self, S0, S1, weights, connect, adjacency, drop_prob, modes):
|
||||
if modes[0] is None:
|
||||
if modes[1] == 'normal':
|
||||
output = self.__forwardBoth(S0, S1, weights, connect, adjacency, drop_prob)
|
||||
elif modes[1] == 'only_W':
|
||||
output = self.__forwardOnlyW(S0, S1, drop_prob)
|
||||
else:
|
||||
test_genotype = modes[0]
|
||||
if self.reduction: operations, concats = test_genotype.reduce, test_genotype.reduce_concat
|
||||
else : operations, concats = test_genotype.normal, test_genotype.normal_concat
|
||||
s0, s1 = self.preprocess0(S0), self.preprocess1(S1)
|
||||
states, offset = [s0, s1], 0
|
||||
assert self._steps == len(operations), '{:} vs. {:}'.format(self._steps, len(operations))
|
||||
for i, (opA, opB) in enumerate(operations):
|
||||
A = self._ops[offset + opA[1]](states[opA[1]], None, opA[0])
|
||||
B = self._ops[offset + opB[1]](states[opB[1]], None, opB[0])
|
||||
state = A + B
|
||||
offset += len(states)
|
||||
states.append(state)
|
||||
output = torch.cat([states[i] for i in concats], dim=1)
|
||||
if self._use_residual and S1.size() == output.size():
|
||||
return S1 + output
|
||||
else: return output
|
||||
|
||||
def __forwardBoth(self, S0, S1, weights, connect, adjacency, drop_prob):
|
||||
s0, s1 = self.preprocess0(S0), self.preprocess1(S1)
|
||||
states, offset = [s0, s1], 0
|
||||
for i in range(self._steps):
|
||||
clist = []
|
||||
for j, h in enumerate(states):
|
||||
x = self._ops[offset+j](h, weights[offset+j], None)
|
||||
if self.training and drop_prob > 0.:
|
||||
x = drop_path(x, math.pow(drop_prob, 1./len(states)))
|
||||
clist.append( x )
|
||||
connection = torch.mm(connect['{:}'.format(i)], adjacency[i]).squeeze(0)
|
||||
state = sum(w * node for w, node in zip(connection, clist))
|
||||
offset += len(states)
|
||||
states.append(state)
|
||||
return torch.cat(states[-self._multiplier:], dim=1)
|
||||
|
||||
def __forwardOnlyW(self, S0, S1, drop_prob):
|
||||
s0, s1 = self.preprocess0(S0), self.preprocess1(S1)
|
||||
states, offset = [s0, s1], 0
|
||||
for i in range(self._steps):
|
||||
clist = []
|
||||
for j, h in enumerate(states):
|
||||
xs = self._ops[offset+j](h, None, None)
|
||||
clist += xs
|
||||
if self.training and drop_prob > 0.:
|
||||
xlist = [drop_path(x, math.pow(drop_prob, 1./len(states))) for x in clist]
|
||||
else: xlist = clist
|
||||
state = sum(xlist) * 2 / len(xlist)
|
||||
offset += len(states)
|
||||
states.append(state)
|
||||
return torch.cat(states[-self._multiplier:], dim=1)
|
||||
|
||||
|
||||
|
||||
class InferCell(nn.Module):
|
||||
|
||||
def __init__(self, genotype, C_prev_prev, C_prev, C, reduction, reduction_prev):
|
||||
super(InferCell, self).__init__()
|
||||
print(C_prev_prev, C_prev, C)
|
||||
|
||||
if reduction_prev is None:
|
||||
self.preprocess0 = Identity()
|
||||
elif reduction_prev:
|
||||
self.preprocess0 = FactorizedReduce(C_prev_prev, C, 2)
|
||||
else:
|
||||
self.preprocess0 = ReLUConvBN(C_prev_prev, C, 1, 1, 0)
|
||||
self.preprocess1 = ReLUConvBN(C_prev, C, 1, 1, 0)
|
||||
|
||||
if reduction: step_ops, concat = genotype.reduce, genotype.reduce_concat
|
||||
else : step_ops, concat = genotype.normal, genotype.normal_concat
|
||||
self._steps = len(step_ops)
|
||||
self._concat = concat
|
||||
self._multiplier = len(concat)
|
||||
self._ops = nn.ModuleList()
|
||||
self._indices = []
|
||||
for operations in step_ops:
|
||||
for name, index in operations:
|
||||
stride = 2 if reduction and index < 2 else 1
|
||||
if reduction_prev is None and index == 0:
|
||||
op = OPS[name](C_prev_prev, C, stride, True)
|
||||
else:
|
||||
op = OPS[name](C , C, stride, True)
|
||||
self._ops.append( op )
|
||||
self._indices.append( index )
|
||||
|
||||
def extra_repr(self):
|
||||
return ('{name}(steps={_steps}, concat={_concat})'.format(name=self.__class__.__name__, **self.__dict__))
|
||||
|
||||
def forward(self, S0, S1, drop_prob):
|
||||
s0 = self.preprocess0(S0)
|
||||
s1 = self.preprocess1(S1)
|
||||
|
||||
states = [s0, s1]
|
||||
for i in range(self._steps):
|
||||
h1 = states[self._indices[2*i]]
|
||||
h2 = states[self._indices[2*i+1]]
|
||||
op1 = self._ops[2*i]
|
||||
op2 = self._ops[2*i+1]
|
||||
h1 = op1(h1)
|
||||
h2 = op2(h2)
|
||||
if self.training and drop_prob > 0.:
|
||||
if not isinstance(op1, Identity):
|
||||
h1 = drop_path(h1, drop_prob)
|
||||
if not isinstance(op2, Identity):
|
||||
h2 = drop_path(h2, drop_prob)
|
||||
|
||||
state = h1 + h2
|
||||
states += [state]
|
||||
output = torch.cat([states[i] for i in self._concat], dim=1)
|
||||
return output
|
||||
60
lib/nas_infer_model/DXYs/construct_utils.py
Normal file
60
lib/nas_infer_model/DXYs/construct_utils.py
Normal file
@@ -0,0 +1,60 @@
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
|
||||
def drop_path(x, drop_prob):
|
||||
if drop_prob > 0.:
|
||||
keep_prob = 1. - drop_prob
|
||||
mask = x.new_zeros(x.size(0), 1, 1, 1)
|
||||
mask = mask.bernoulli_(keep_prob)
|
||||
x = torch.div(x, keep_prob)
|
||||
x.mul_(mask)
|
||||
return x
|
||||
|
||||
|
||||
def return_alphas_str(basemodel):
|
||||
if hasattr(basemodel, 'alphas_normal'):
|
||||
string = 'normal [{:}] : \n-->>{:}'.format(basemodel.alphas_normal.size(), F.softmax(basemodel.alphas_normal, dim=-1) )
|
||||
else: string = ''
|
||||
if hasattr(basemodel, 'alphas_reduce'):
|
||||
string = string + '\nreduce : {:}'.format( F.softmax(basemodel.alphas_reduce, dim=-1) )
|
||||
|
||||
if hasattr(basemodel, 'get_adjacency'):
|
||||
adjacency = basemodel.get_adjacency()
|
||||
for i in range( len(adjacency) ):
|
||||
weight = F.softmax( basemodel.connect_normal[str(i)], dim=-1 )
|
||||
adj = torch.mm(weight, adjacency[i]).view(-1)
|
||||
adj = ['{:3.3f}'.format(x) for x in adj.cpu().tolist()]
|
||||
string = string + '\nnormal--{:}-->{:}'.format(i, ', '.join(adj))
|
||||
for i in range( len(adjacency) ):
|
||||
weight = F.softmax( basemodel.connect_reduce[str(i)], dim=-1 )
|
||||
adj = torch.mm(weight, adjacency[i]).view(-1)
|
||||
adj = ['{:3.3f}'.format(x) for x in adj.cpu().tolist()]
|
||||
string = string + '\nreduce--{:}-->{:}'.format(i, ', '.join(adj))
|
||||
|
||||
if hasattr(basemodel, 'alphas_connect'):
|
||||
weight = F.softmax(basemodel.alphas_connect, dim=-1).cpu()
|
||||
ZERO = ['{:.3f}'.format(x) for x in weight[:,0].tolist()]
|
||||
IDEN = ['{:.3f}'.format(x) for x in weight[:,1].tolist()]
|
||||
string = string + '\nconnect [{:}] : \n ->{:}\n ->{:}'.format( list(basemodel.alphas_connect.size()), ZERO, IDEN )
|
||||
else:
|
||||
string = string + '\nconnect = None'
|
||||
|
||||
if hasattr(basemodel, 'get_gcn_out'):
|
||||
outputs = basemodel.get_gcn_out(True)
|
||||
for i, output in enumerate(outputs):
|
||||
string = string + '\nnormal:[{:}] : {:}'.format(i, F.softmax(output, dim=-1) )
|
||||
|
||||
return string
|
||||
|
||||
|
||||
def remove_duplicate_archs(all_archs):
|
||||
archs = []
|
||||
str_archs = ['{:}'.format(x) for x in all_archs]
|
||||
for i, arch_x in enumerate(str_archs):
|
||||
choose = True
|
||||
for j in range(i):
|
||||
if arch_x == str_archs[j]:
|
||||
choose = False; break
|
||||
if choose: archs.append(all_archs[i])
|
||||
return archs
|
||||
172
lib/nas_infer_model/DXYs/genotypes.py
Normal file
172
lib/nas_infer_model/DXYs/genotypes.py
Normal file
@@ -0,0 +1,172 @@
|
||||
##################################################
|
||||
# Copyright (c) Xuanyi Dong [GitHub D-X-Y], 2019 #
|
||||
##################################################
|
||||
from collections import namedtuple
|
||||
|
||||
Genotype = namedtuple('Genotype', 'normal normal_concat reduce reduce_concat connectN connects')
|
||||
#Genotype = namedtuple('Genotype', 'normal normal_concat reduce reduce_concat')
|
||||
|
||||
PRIMITIVES_small = [
|
||||
'max_pool_3x3',
|
||||
'avg_pool_3x3',
|
||||
'skip_connect',
|
||||
'sep_conv_3x3',
|
||||
'sep_conv_5x5',
|
||||
'conv_3x1_1x3',
|
||||
]
|
||||
|
||||
PRIMITIVES_large = [
|
||||
'max_pool_3x3',
|
||||
'avg_pool_3x3',
|
||||
'skip_connect',
|
||||
'sep_conv_3x3',
|
||||
'sep_conv_5x5',
|
||||
'dil_conv_3x3',
|
||||
'dil_conv_5x5',
|
||||
'conv_3x1_1x3',
|
||||
]
|
||||
|
||||
PRIMITIVES_huge = [
|
||||
'skip_connect',
|
||||
'nor_conv_1x1',
|
||||
'max_pool_3x3',
|
||||
'avg_pool_3x3',
|
||||
'nor_conv_3x3',
|
||||
'sep_conv_3x3',
|
||||
'dil_conv_3x3',
|
||||
'conv_3x1_1x3',
|
||||
'sep_conv_5x5',
|
||||
'dil_conv_5x5',
|
||||
'sep_conv_7x7',
|
||||
'conv_7x1_1x7',
|
||||
'att_squeeze',
|
||||
]
|
||||
|
||||
PRIMITIVES = {'small': PRIMITIVES_small,
|
||||
'large': PRIMITIVES_large,
|
||||
'huge' : PRIMITIVES_huge}
|
||||
|
||||
NASNet = Genotype(
|
||||
normal = [
|
||||
(('sep_conv_5x5', 1), ('sep_conv_3x3', 0)),
|
||||
(('sep_conv_5x5', 0), ('sep_conv_3x3', 0)),
|
||||
(('avg_pool_3x3', 1), ('skip_connect', 0)),
|
||||
(('avg_pool_3x3', 0), ('avg_pool_3x3', 0)),
|
||||
(('sep_conv_3x3', 1), ('skip_connect', 1)),
|
||||
],
|
||||
normal_concat = [2, 3, 4, 5, 6],
|
||||
reduce = [
|
||||
(('sep_conv_5x5', 1), ('sep_conv_7x7', 0)),
|
||||
(('max_pool_3x3', 1), ('sep_conv_7x7', 0)),
|
||||
(('avg_pool_3x3', 1), ('sep_conv_5x5', 0)),
|
||||
(('skip_connect', 3), ('avg_pool_3x3', 2)),
|
||||
(('sep_conv_3x3', 2), ('max_pool_3x3', 1)),
|
||||
],
|
||||
reduce_concat = [4, 5, 6],
|
||||
connectN=None, connects=None,
|
||||
)
|
||||
|
||||
PNASNet = Genotype(
|
||||
normal = [
|
||||
(('sep_conv_5x5', 0), ('max_pool_3x3', 0)),
|
||||
(('sep_conv_7x7', 1), ('max_pool_3x3', 1)),
|
||||
(('sep_conv_5x5', 1), ('sep_conv_3x3', 1)),
|
||||
(('sep_conv_3x3', 4), ('max_pool_3x3', 1)),
|
||||
(('sep_conv_3x3', 0), ('skip_connect', 1)),
|
||||
],
|
||||
normal_concat = [2, 3, 4, 5, 6],
|
||||
reduce = [
|
||||
(('sep_conv_5x5', 0), ('max_pool_3x3', 0)),
|
||||
(('sep_conv_7x7', 1), ('max_pool_3x3', 1)),
|
||||
(('sep_conv_5x5', 1), ('sep_conv_3x3', 1)),
|
||||
(('sep_conv_3x3', 4), ('max_pool_3x3', 1)),
|
||||
(('sep_conv_3x3', 0), ('skip_connect', 1)),
|
||||
],
|
||||
reduce_concat = [2, 3, 4, 5, 6],
|
||||
connectN=None, connects=None,
|
||||
)
|
||||
|
||||
|
||||
DARTS_V1 = Genotype(
|
||||
normal=[
|
||||
(('sep_conv_3x3', 1), ('sep_conv_3x3', 0)), # step 1
|
||||
(('skip_connect', 0), ('sep_conv_3x3', 1)), # step 2
|
||||
(('skip_connect', 0), ('sep_conv_3x3', 1)), # step 3
|
||||
(('sep_conv_3x3', 0), ('skip_connect', 2)) # step 4
|
||||
],
|
||||
normal_concat=[2, 3, 4, 5],
|
||||
reduce=[
|
||||
(('max_pool_3x3', 0), ('max_pool_3x3', 1)), # step 1
|
||||
(('skip_connect', 2), ('max_pool_3x3', 0)), # step 2
|
||||
(('max_pool_3x3', 0), ('skip_connect', 2)), # step 3
|
||||
(('skip_connect', 2), ('avg_pool_3x3', 0)) # step 4
|
||||
],
|
||||
reduce_concat=[2, 3, 4, 5],
|
||||
connectN=None, connects=None,
|
||||
)
|
||||
|
||||
# DARTS: Differentiable Architecture Search, ICLR 2019
|
||||
DARTS_V2 = Genotype(
|
||||
normal=[
|
||||
(('sep_conv_3x3', 0), ('sep_conv_3x3', 1)), # step 1
|
||||
(('sep_conv_3x3', 0), ('sep_conv_3x3', 1)), # step 2
|
||||
(('sep_conv_3x3', 1), ('skip_connect', 0)), # step 3
|
||||
(('skip_connect', 0), ('dil_conv_3x3', 2)) # step 4
|
||||
],
|
||||
normal_concat=[2, 3, 4, 5],
|
||||
reduce=[
|
||||
(('max_pool_3x3', 0), ('max_pool_3x3', 1)), # step 1
|
||||
(('skip_connect', 2), ('max_pool_3x3', 1)), # step 2
|
||||
(('max_pool_3x3', 0), ('skip_connect', 2)), # step 3
|
||||
(('skip_connect', 2), ('max_pool_3x3', 1)) # step 4
|
||||
],
|
||||
reduce_concat=[2, 3, 4, 5],
|
||||
connectN=None, connects=None,
|
||||
)
|
||||
|
||||
|
||||
# One-Shot Neural Architecture Search via Self-Evaluated Template Network, ICCV 2019
|
||||
SETN = Genotype(
|
||||
normal=[
|
||||
(('skip_connect', 0), ('sep_conv_5x5', 1)),
|
||||
(('sep_conv_5x5', 0), ('sep_conv_3x3', 1)),
|
||||
(('sep_conv_5x5', 1), ('sep_conv_5x5', 3)),
|
||||
(('max_pool_3x3', 1), ('conv_3x1_1x3', 4))],
|
||||
normal_concat=[2, 3, 4, 5],
|
||||
reduce=[
|
||||
(('sep_conv_3x3', 0), ('sep_conv_5x5', 1)),
|
||||
(('avg_pool_3x3', 0), ('sep_conv_5x5', 1)),
|
||||
(('avg_pool_3x3', 0), ('sep_conv_5x5', 1)),
|
||||
(('avg_pool_3x3', 0), ('skip_connect', 1))],
|
||||
reduce_concat=[2, 3, 4, 5],
|
||||
connectN=None, connects=None
|
||||
)
|
||||
|
||||
|
||||
# Searching for A Robust Neural Architecture in Four GPU Hours, CVPR 2019
|
||||
GDAS_V1 = Genotype(
|
||||
normal=[
|
||||
(('skip_connect', 0), ('skip_connect', 1)),
|
||||
(('skip_connect', 0), ('sep_conv_5x5', 2)),
|
||||
(('sep_conv_3x3', 3), ('skip_connect', 0)),
|
||||
(('sep_conv_5x5', 4), ('sep_conv_3x3', 3))],
|
||||
normal_concat=[2, 3, 4, 5],
|
||||
reduce=[
|
||||
(('sep_conv_5x5', 0), ('sep_conv_3x3', 1)),
|
||||
(('sep_conv_5x5', 2), ('sep_conv_5x5', 1)),
|
||||
(('dil_conv_5x5', 2), ('sep_conv_3x3', 1)),
|
||||
(('sep_conv_5x5', 0), ('sep_conv_5x5', 1))],
|
||||
reduce_concat=[2, 3, 4, 5],
|
||||
connectN=None, connects=None
|
||||
)
|
||||
|
||||
|
||||
|
||||
Networks = {'DARTS_V1': DARTS_V1,
|
||||
'DARTS_V2': DARTS_V2,
|
||||
'DARTS' : DARTS_V2,
|
||||
'NASNet' : NASNet,
|
||||
'GDAS_V1' : GDAS_V1,
|
||||
'PNASNet' : PNASNet,
|
||||
'SETN' : SETN,
|
||||
}
|
||||
65
lib/nas_infer_model/DXYs/head_utils.py
Normal file
65
lib/nas_infer_model/DXYs/head_utils.py
Normal file
@@ -0,0 +1,65 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
|
||||
class ImageNetHEAD(nn.Sequential):
|
||||
def __init__(self, C, stride=2):
|
||||
super(ImageNetHEAD, self).__init__()
|
||||
self.add_module('conv1', nn.Conv2d(3, C // 2, kernel_size=3, stride=2, padding=1, bias=False))
|
||||
self.add_module('bn1' , nn.BatchNorm2d(C // 2))
|
||||
self.add_module('relu1', nn.ReLU(inplace=True))
|
||||
self.add_module('conv2', nn.Conv2d(C // 2, C, kernel_size=3, stride=stride, padding=1, bias=False))
|
||||
self.add_module('bn2' , nn.BatchNorm2d(C))
|
||||
|
||||
|
||||
class CifarHEAD(nn.Sequential):
|
||||
def __init__(self, C):
|
||||
super(CifarHEAD, self).__init__()
|
||||
self.add_module('conv', nn.Conv2d(3, C, kernel_size=3, padding=1, bias=False))
|
||||
self.add_module('bn', nn.BatchNorm2d(C))
|
||||
|
||||
|
||||
class AuxiliaryHeadCIFAR(nn.Module):
|
||||
|
||||
def __init__(self, C, num_classes):
|
||||
"""assuming input size 8x8"""
|
||||
super(AuxiliaryHeadCIFAR, self).__init__()
|
||||
self.features = nn.Sequential(
|
||||
nn.ReLU(inplace=True),
|
||||
nn.AvgPool2d(5, stride=3, padding=0, count_include_pad=False), # image size = 2 x 2
|
||||
nn.Conv2d(C, 128, 1, bias=False),
|
||||
nn.BatchNorm2d(128),
|
||||
nn.ReLU(inplace=True),
|
||||
nn.Conv2d(128, 768, 2, bias=False),
|
||||
nn.BatchNorm2d(768),
|
||||
nn.ReLU(inplace=True)
|
||||
)
|
||||
self.classifier = nn.Linear(768, num_classes)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.features(x)
|
||||
x = self.classifier(x.view(x.size(0),-1))
|
||||
return x
|
||||
|
||||
|
||||
class AuxiliaryHeadImageNet(nn.Module):
|
||||
|
||||
def __init__(self, C, num_classes):
|
||||
"""assuming input size 14x14"""
|
||||
super(AuxiliaryHeadImageNet, self).__init__()
|
||||
self.features = nn.Sequential(
|
||||
nn.ReLU(inplace=True),
|
||||
nn.AvgPool2d(5, stride=2, padding=0, count_include_pad=False),
|
||||
nn.Conv2d(C, 128, 1, bias=False),
|
||||
nn.BatchNorm2d(128),
|
||||
nn.ReLU(inplace=True),
|
||||
nn.Conv2d(128, 768, 2, bias=False),
|
||||
nn.BatchNorm2d(768),
|
||||
nn.ReLU(inplace=True)
|
||||
)
|
||||
self.classifier = nn.Linear(768, num_classes)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.features(x)
|
||||
x = self.classifier(x.view(x.size(0),-1))
|
||||
return x
|
||||
16
lib/nas_infer_model/__init__.py
Normal file
16
lib/nas_infer_model/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
||||
##################################################
|
||||
# Copyright (c) Xuanyi Dong [GitHub D-X-Y], 2019 #
|
||||
##################################################
|
||||
import torch
|
||||
|
||||
def obtain_nas_infer_model(config):
|
||||
if config.arch == 'dxys':
|
||||
from .DXYs import CifarNet, ImageNet, Networks
|
||||
genotype = Networks[config.genotype]
|
||||
if config.dataset == 'cifar':
|
||||
return CifarNet(config.ichannel, config.layers, config.stem_multi, config.auxiliary, genotype, config.class_num)
|
||||
elif config.dataset == 'imagenet':
|
||||
return ImageNet(config.ichannel, config.layers, config.auxiliary, genotype, config.class_num)
|
||||
else: raise ValueError('invalid dataset : {:}'.format(config.dataset))
|
||||
else:
|
||||
raise ValueError('invalid nas arch type : {:}'.format(config.arch))
|
||||
180
lib/nas_infer_model/operations.py
Normal file
180
lib/nas_infer_model/operations.py
Normal file
@@ -0,0 +1,180 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
OPS = {
|
||||
'none' : lambda C_in, C_out, stride, affine: Zero(stride),
|
||||
'avg_pool_3x3' : lambda C_in, C_out, stride, affine: POOLING(C_in, C_out, stride, 'avg'),
|
||||
'max_pool_3x3' : lambda C_in, C_out, stride, affine: POOLING(C_in, C_out, stride, 'max'),
|
||||
'nor_conv_7x7' : lambda C_in, C_out, stride, affine: ReLUConvBN(C_in, C_out, (7,7), (stride,stride), (3,3), affine),
|
||||
'nor_conv_3x3' : lambda C_in, C_out, stride, affine: ReLUConvBN(C_in, C_out, (3,3), (stride,stride), (1,1), affine),
|
||||
'nor_conv_1x1' : lambda C_in, C_out, stride, affine: ReLUConvBN(C_in, C_out, (1,1), (stride,stride), (0,0), affine),
|
||||
'skip_connect' : lambda C_in, C_out, stride, affine: Identity() if stride == 1 and C_in == C_out else FactorizedReduce(C_in, C_out, stride, affine),
|
||||
'sep_conv_3x3' : lambda C_in, C_out, stride, affine: SepConv(C_in, C_out, 3, stride, 1, affine=affine),
|
||||
'sep_conv_5x5' : lambda C_in, C_out, stride, affine: SepConv(C_in, C_out, 5, stride, 2, affine=affine),
|
||||
'sep_conv_7x7' : lambda C_in, C_out, stride, affine: SepConv(C_in, C_out, 7, stride, 3, affine=affine),
|
||||
'dil_conv_3x3' : lambda C_in, C_out, stride, affine: DilConv(C_in, C_out, 3, stride, 2, 2, affine=affine),
|
||||
'dil_conv_5x5' : lambda C_in, C_out, stride, affine: DilConv(C_in, C_out, 5, stride, 4, 2, affine=affine),
|
||||
'conv_7x1_1x7' : lambda C_in, C_out, stride, affine: Conv717(C_in, C_out, stride, affine),
|
||||
'conv_3x1_1x3' : lambda C_in, C_out, stride, affine: Conv313(C_in, C_out, stride, affine)
|
||||
}
|
||||
|
||||
|
||||
class POOLING(nn.Module):
|
||||
|
||||
def __init__(self, C_in, C_out, stride, mode):
|
||||
super(POOLING, self).__init__()
|
||||
if C_in == C_out:
|
||||
self.preprocess = None
|
||||
else:
|
||||
self.preprocess = ReLUConvBN(C_in, C_out, 1, 1, 0)
|
||||
if mode == 'avg' : self.op = nn.AvgPool2d(3, stride=stride, padding=1, count_include_pad=False)
|
||||
elif mode == 'max': self.op = nn.MaxPool2d(3, stride=stride, padding=1)
|
||||
|
||||
def forward(self, inputs):
|
||||
if self.preprocess is not None:
|
||||
x = self.preprocess(inputs)
|
||||
else: x = inputs
|
||||
return self.op(x)
|
||||
|
||||
|
||||
class Conv313(nn.Module):
|
||||
|
||||
def __init__(self, C_in, C_out, stride, affine):
|
||||
super(Conv313, self).__init__()
|
||||
self.op = nn.Sequential(
|
||||
nn.ReLU(inplace=False),
|
||||
nn.Conv2d(C_in , C_out, (1,3), stride=(1, stride), padding=(0, 1), bias=False),
|
||||
nn.Conv2d(C_out, C_out, (3,1), stride=(stride, 1), padding=(1, 0), bias=False),
|
||||
nn.BatchNorm2d(C_out, affine=affine)
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
return self.op(x)
|
||||
|
||||
|
||||
class Conv717(nn.Module):
|
||||
|
||||
def __init__(self, C_in, C_out, stride, affine):
|
||||
super(Conv717, self).__init__()
|
||||
self.op = nn.Sequential(
|
||||
nn.ReLU(inplace=False),
|
||||
nn.Conv2d(C_in , C_out, (1,7), stride=(1, stride), padding=(0, 3), bias=False),
|
||||
nn.Conv2d(C_out, C_out, (7,1), stride=(stride, 1), padding=(3, 0), bias=False),
|
||||
nn.BatchNorm2d(C_out, affine=affine)
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
return self.op(x)
|
||||
|
||||
|
||||
class ReLUConvBN(nn.Module):
|
||||
|
||||
def __init__(self, C_in, C_out, kernel_size, stride, padding, affine=True):
|
||||
super(ReLUConvBN, self).__init__()
|
||||
self.op = nn.Sequential(
|
||||
nn.ReLU(inplace=False),
|
||||
nn.Conv2d(C_in, C_out, kernel_size, stride=stride, padding=padding, bias=False),
|
||||
nn.BatchNorm2d(C_out, affine=affine)
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
return self.op(x)
|
||||
|
||||
|
||||
class DilConv(nn.Module):
|
||||
|
||||
def __init__(self, C_in, C_out, kernel_size, stride, padding, dilation, affine=True):
|
||||
super(DilConv, self).__init__()
|
||||
self.op = nn.Sequential(
|
||||
nn.ReLU(inplace=False),
|
||||
nn.Conv2d(C_in, C_in, kernel_size=kernel_size, stride=stride, padding=padding, dilation=dilation, groups=C_in, bias=False),
|
||||
nn.Conv2d(C_in, C_out, kernel_size=1, padding=0, bias=False),
|
||||
nn.BatchNorm2d(C_out, affine=affine),
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
return self.op(x)
|
||||
|
||||
|
||||
class SepConv(nn.Module):
|
||||
|
||||
def __init__(self, C_in, C_out, kernel_size, stride, padding, affine=True):
|
||||
super(SepConv, self).__init__()
|
||||
self.op = nn.Sequential(
|
||||
nn.ReLU(inplace=False),
|
||||
nn.Conv2d(C_in, C_in, kernel_size=kernel_size, stride=stride, padding=padding, groups=C_in, bias=False),
|
||||
nn.Conv2d(C_in, C_in, kernel_size=1, padding=0, bias=False),
|
||||
nn.BatchNorm2d(C_in, affine=affine),
|
||||
nn.ReLU(inplace=False),
|
||||
nn.Conv2d(C_in, C_in, kernel_size=kernel_size, stride= 1, padding=padding, groups=C_in, bias=False),
|
||||
nn.Conv2d(C_in, C_out, kernel_size=1, padding=0, bias=False),
|
||||
nn.BatchNorm2d(C_out, affine=affine),
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
return self.op(x)
|
||||
|
||||
|
||||
class Identity(nn.Module):
|
||||
|
||||
def __init__(self):
|
||||
super(Identity, self).__init__()
|
||||
|
||||
def forward(self, x):
|
||||
return x
|
||||
|
||||
|
||||
class Zero(nn.Module):
|
||||
|
||||
def __init__(self, stride):
|
||||
super(Zero, self).__init__()
|
||||
self.stride = stride
|
||||
|
||||
def forward(self, x):
|
||||
if self.stride == 1:
|
||||
return x.mul(0.)
|
||||
return x[:,:,::self.stride,::self.stride].mul(0.)
|
||||
|
||||
def extra_repr(self):
|
||||
return 'stride={stride}'.format(**self.__dict__)
|
||||
|
||||
|
||||
class FactorizedReduce(nn.Module):
|
||||
|
||||
def __init__(self, C_in, C_out, stride, affine=True):
|
||||
super(FactorizedReduce, self).__init__()
|
||||
self.stride = stride
|
||||
self.C_in = C_in
|
||||
self.C_out = C_out
|
||||
self.relu = nn.ReLU(inplace=False)
|
||||
if stride == 2:
|
||||
#assert C_out % 2 == 0, 'C_out : {:}'.format(C_out)
|
||||
C_outs = [C_out // 2, C_out - C_out // 2]
|
||||
self.convs = nn.ModuleList()
|
||||
for i in range(2):
|
||||
self.convs.append( nn.Conv2d(C_in, C_outs[i], 1, stride=stride, padding=0, bias=False) )
|
||||
self.pad = nn.ConstantPad2d((0, 1, 0, 1), 0)
|
||||
elif stride == 4:
|
||||
assert C_out % 4 == 0, 'C_out : {:}'.format(C_out)
|
||||
self.convs = nn.ModuleList()
|
||||
for i in range(4):
|
||||
self.convs.append( nn.Conv2d(C_in, C_out // 4, 1, stride=stride, padding=0, bias=False) )
|
||||
self.pad = nn.ConstantPad2d((0, 3, 0, 3), 0)
|
||||
else:
|
||||
raise ValueError('Invalid stride : {:}'.format(stride))
|
||||
|
||||
self.bn = nn.BatchNorm2d(C_out, affine=affine)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.relu(x)
|
||||
y = self.pad(x)
|
||||
if self.stride == 2:
|
||||
out = torch.cat([self.convs[0](x), self.convs[1](y[:,:,1:,1:])], dim=1)
|
||||
else:
|
||||
out = torch.cat([self.convs[0](x), self.convs[1](y[:,:,1:-2,1:-2]),
|
||||
self.convs[2](y[:,:,2:-1,2:-1]), self.convs[3](y[:,:,3:,3:])], dim=1)
|
||||
out = self.bn(out)
|
||||
return out
|
||||
|
||||
def extra_repr(self):
|
||||
return 'C_in={C_in}, C_out={C_out}, stride={stride}'.format(**self.__dict__)
|
||||
@@ -1,9 +0,0 @@
|
||||
# utils
|
||||
from .utils import batchify, get_batch, repackage_hidden
|
||||
# models
|
||||
from .model_search import RNNModelSearch
|
||||
from .model_search import DARTSCellSearch
|
||||
from .basemodel import DARTSCell, RNNModel
|
||||
# architecture
|
||||
from .genotypes import DARTS_V1, DARTS_V2
|
||||
from .genotypes import GDAS
|
||||
@@ -1,181 +0,0 @@
|
||||
import math
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from .genotypes import STEPS
|
||||
from .utils import mask2d, LockedDropout, embedded_dropout
|
||||
|
||||
|
||||
INITRANGE = 0.04
|
||||
|
||||
def none_func(x):
|
||||
return x * 0
|
||||
|
||||
|
||||
class DARTSCell(nn.Module):
|
||||
|
||||
def __init__(self, ninp, nhid, dropouth, dropoutx, genotype):
|
||||
super(DARTSCell, self).__init__()
|
||||
self.nhid = nhid
|
||||
self.dropouth = dropouth
|
||||
self.dropoutx = dropoutx
|
||||
self.genotype = genotype
|
||||
|
||||
# genotype is None when doing arch search
|
||||
steps = len(self.genotype.recurrent) if self.genotype is not None else STEPS
|
||||
self._W0 = nn.Parameter(torch.Tensor(ninp+nhid, 2*nhid).uniform_(-INITRANGE, INITRANGE))
|
||||
self._Ws = nn.ParameterList([
|
||||
nn.Parameter(torch.Tensor(nhid, 2*nhid).uniform_(-INITRANGE, INITRANGE)) for i in range(steps)
|
||||
])
|
||||
|
||||
def forward(self, inputs, hidden, arch_probs):
|
||||
T, B = inputs.size(0), inputs.size(1)
|
||||
|
||||
if self.training:
|
||||
x_mask = mask2d(B, inputs.size(2), keep_prob=1.-self.dropoutx)
|
||||
h_mask = mask2d(B, hidden.size(2), keep_prob=1.-self.dropouth)
|
||||
else:
|
||||
x_mask = h_mask = None
|
||||
|
||||
hidden = hidden[0]
|
||||
hiddens = []
|
||||
for t in range(T):
|
||||
hidden = self.cell(inputs[t], hidden, x_mask, h_mask, arch_probs)
|
||||
hiddens.append(hidden)
|
||||
hiddens = torch.stack(hiddens)
|
||||
return hiddens, hiddens[-1].unsqueeze(0)
|
||||
|
||||
def _compute_init_state(self, x, h_prev, x_mask, h_mask):
|
||||
if self.training:
|
||||
xh_prev = torch.cat([x * x_mask, h_prev * h_mask], dim=-1)
|
||||
else:
|
||||
xh_prev = torch.cat([x, h_prev], dim=-1)
|
||||
c0, h0 = torch.split(xh_prev.mm(self._W0), self.nhid, dim=-1)
|
||||
c0 = c0.sigmoid()
|
||||
h0 = h0.tanh()
|
||||
s0 = h_prev + c0 * (h0-h_prev)
|
||||
return s0
|
||||
|
||||
def _get_activation(self, name):
|
||||
if name == 'tanh':
|
||||
f = torch.tanh
|
||||
elif name == 'relu':
|
||||
f = torch.relu
|
||||
elif name == 'sigmoid':
|
||||
f = torch.sigmoid
|
||||
elif name == 'identity':
|
||||
f = lambda x: x
|
||||
elif name == 'none':
|
||||
f = none_func
|
||||
else:
|
||||
raise NotImplementedError
|
||||
return f
|
||||
|
||||
def cell(self, x, h_prev, x_mask, h_mask, _):
|
||||
s0 = self._compute_init_state(x, h_prev, x_mask, h_mask)
|
||||
|
||||
states = [s0]
|
||||
for i, (name, pred) in enumerate(self.genotype.recurrent):
|
||||
s_prev = states[pred]
|
||||
if self.training:
|
||||
ch = (s_prev * h_mask).mm(self._Ws[i])
|
||||
else:
|
||||
ch = s_prev.mm(self._Ws[i])
|
||||
c, h = torch.split(ch, self.nhid, dim=-1)
|
||||
c = c.sigmoid()
|
||||
fn = self._get_activation(name)
|
||||
h = fn(h)
|
||||
s = s_prev + c * (h-s_prev)
|
||||
states += [s]
|
||||
output = torch.mean(torch.stack([states[i] for i in self.genotype.concat], -1), -1)
|
||||
return output
|
||||
|
||||
|
||||
class RNNModel(nn.Module):
|
||||
"""Container module with an encoder, a recurrent module, and a decoder."""
|
||||
def __init__(self, ntoken, ninp, nhid, nhidlast,
|
||||
dropout=0.5, dropouth=0.5, dropoutx=0.5, dropouti=0.5, dropoute=0.1,
|
||||
cell_cls=None, genotype=None):
|
||||
super(RNNModel, self).__init__()
|
||||
self.lockdrop = LockedDropout()
|
||||
self.encoder = nn.Embedding(ntoken, ninp)
|
||||
|
||||
assert ninp == nhid == nhidlast
|
||||
if cell_cls == DARTSCell:
|
||||
assert genotype is not None
|
||||
rnns = [cell_cls(ninp, nhid, dropouth, dropoutx, genotype)]
|
||||
else:
|
||||
assert genotype is None
|
||||
rnns = [cell_cls(ninp, nhid, dropouth, dropoutx)]
|
||||
|
||||
self.rnns = torch.nn.ModuleList(rnns)
|
||||
self.decoder = nn.Linear(ninp, ntoken)
|
||||
self.decoder.weight = self.encoder.weight
|
||||
self.init_weights()
|
||||
self.arch_weights = None
|
||||
|
||||
self.ninp = ninp
|
||||
self.nhid = nhid
|
||||
self.nhidlast = nhidlast
|
||||
self.dropout = dropout
|
||||
self.dropouti = dropouti
|
||||
self.dropoute = dropoute
|
||||
self.ntoken = ntoken
|
||||
self.cell_cls = cell_cls
|
||||
# acceleration
|
||||
self.tau = None
|
||||
self.use_gumbel = False
|
||||
|
||||
def set_gumbel(self, use_gumbel, set_check):
|
||||
self.use_gumbel = use_gumbel
|
||||
for i, rnn in enumerate(self.rnns):
|
||||
rnn.set_check(set_check)
|
||||
|
||||
def set_tau(self, tau):
|
||||
self.tau = tau
|
||||
|
||||
def get_tau(self):
|
||||
return self.tau
|
||||
|
||||
def init_weights(self):
|
||||
self.encoder.weight.data.uniform_(-INITRANGE, INITRANGE)
|
||||
self.decoder.bias.data.fill_(0)
|
||||
self.decoder.weight.data.uniform_(-INITRANGE, INITRANGE)
|
||||
|
||||
def forward(self, input, hidden, return_h=False):
|
||||
batch_size = input.size(1)
|
||||
|
||||
emb = embedded_dropout(self.encoder, input, dropout=self.dropoute if self.training else 0)
|
||||
emb = self.lockdrop(emb, self.dropouti)
|
||||
|
||||
raw_output = emb
|
||||
new_hidden = []
|
||||
raw_outputs = []
|
||||
outputs = []
|
||||
if self.arch_weights is None:
|
||||
arch_probs = None
|
||||
else:
|
||||
if self.use_gumbel: arch_probs = F.gumbel_softmax(self.arch_weights, self.tau, False)
|
||||
else : arch_probs = F.softmax(self.arch_weights, dim=-1)
|
||||
|
||||
for l, rnn in enumerate(self.rnns):
|
||||
current_input = raw_output
|
||||
raw_output, new_h = rnn(raw_output, hidden[l], arch_probs)
|
||||
new_hidden.append(new_h)
|
||||
raw_outputs.append(raw_output)
|
||||
hidden = new_hidden
|
||||
|
||||
output = self.lockdrop(raw_output, self.dropout)
|
||||
outputs.append(output)
|
||||
|
||||
logit = self.decoder(output.view(-1, self.ninp))
|
||||
log_prob = nn.functional.log_softmax(logit, dim=-1)
|
||||
model_output = log_prob
|
||||
model_output = model_output.view(-1, batch_size, self.ntoken)
|
||||
|
||||
if return_h: return model_output, hidden, raw_outputs, outputs
|
||||
else : return model_output, hidden
|
||||
|
||||
def init_hidden(self, bsz):
|
||||
weight = next(self.parameters()).clone()
|
||||
return [weight.new(1, bsz, self.nhid).zero_()]
|
||||
@@ -1,55 +0,0 @@
|
||||
from collections import namedtuple
|
||||
|
||||
Genotype = namedtuple('Genotype', 'recurrent concat')
|
||||
|
||||
PRIMITIVES = [
|
||||
'none',
|
||||
'tanh',
|
||||
'relu',
|
||||
'sigmoid',
|
||||
'identity'
|
||||
]
|
||||
STEPS = 8
|
||||
CONCAT = 8
|
||||
|
||||
ENAS = Genotype(
|
||||
recurrent = [
|
||||
('tanh', 0),
|
||||
('tanh', 1),
|
||||
('relu', 1),
|
||||
('tanh', 3),
|
||||
('tanh', 3),
|
||||
('relu', 3),
|
||||
('relu', 4),
|
||||
('relu', 7),
|
||||
('relu', 8),
|
||||
('relu', 8),
|
||||
('relu', 8),
|
||||
],
|
||||
concat = [2, 5, 6, 9, 10, 11]
|
||||
)
|
||||
|
||||
DARTS_V1 = Genotype(
|
||||
recurrent = [
|
||||
('relu', 0),
|
||||
('relu', 1),
|
||||
('tanh', 2),
|
||||
('relu', 3), ('relu', 4), ('identity', 1), ('relu', 5), ('relu', 1)
|
||||
],
|
||||
concat=range(1, 9)
|
||||
)
|
||||
|
||||
DARTS_V2 = Genotype(
|
||||
recurrent = [
|
||||
('sigmoid', 0), ('relu', 1), ('relu', 1),
|
||||
('identity', 1), ('tanh', 2), ('sigmoid', 5),
|
||||
('tanh', 3), ('relu', 5)
|
||||
],
|
||||
concat=range(1, 9)
|
||||
)
|
||||
|
||||
GDAS = Genotype(
|
||||
recurrent=[('relu', 0), ('relu', 0), ('identity', 1), ('relu', 1), ('tanh', 0), ('relu', 2), ('identity', 4), ('identity', 2)],
|
||||
concat=range(1, 9)
|
||||
)
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
import copy, torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from collections import namedtuple
|
||||
from .genotypes import PRIMITIVES, STEPS, CONCAT, Genotype
|
||||
from .basemodel import DARTSCell, RNNModel
|
||||
|
||||
|
||||
class DARTSCellSearch(DARTSCell):
|
||||
|
||||
def __init__(self, ninp, nhid, dropouth, dropoutx):
|
||||
super(DARTSCellSearch, self).__init__(ninp, nhid, dropouth, dropoutx, genotype=None)
|
||||
self.bn = nn.BatchNorm1d(nhid, affine=False)
|
||||
self.check_zero = False
|
||||
|
||||
def set_check(self, check_zero):
|
||||
self.check_zero = check_zero
|
||||
|
||||
def cell(self, x, h_prev, x_mask, h_mask, arch_probs):
|
||||
s0 = self._compute_init_state(x, h_prev, x_mask, h_mask)
|
||||
s0 = self.bn(s0)
|
||||
if self.check_zero:
|
||||
arch_probs_cpu = arch_probs.cpu().tolist()
|
||||
#arch_probs = F.softmax(self.weights, dim=-1)
|
||||
|
||||
offset = 0
|
||||
states = s0.unsqueeze(0)
|
||||
for i in range(STEPS):
|
||||
if self.training:
|
||||
masked_states = states * h_mask.unsqueeze(0)
|
||||
else:
|
||||
masked_states = states
|
||||
ch = masked_states.view(-1, self.nhid).mm(self._Ws[i]).view(i+1, -1, 2*self.nhid)
|
||||
c, h = torch.split(ch, self.nhid, dim=-1)
|
||||
c = c.sigmoid()
|
||||
|
||||
s = torch.zeros_like(s0)
|
||||
for k, name in enumerate(PRIMITIVES):
|
||||
if name == 'none':
|
||||
continue
|
||||
fn = self._get_activation(name)
|
||||
unweighted = states + c * (fn(h) - states)
|
||||
if self.check_zero:
|
||||
INDEX, INDDX = [], []
|
||||
for jj in range(offset, offset+i+1):
|
||||
if arch_probs_cpu[jj][k] > 0:
|
||||
INDEX.append(jj)
|
||||
INDDX.append(jj-offset)
|
||||
if len(INDEX) == 0: continue
|
||||
s += torch.sum(arch_probs[INDEX, k].unsqueeze(-1).unsqueeze(-1) * unweighted[INDDX, :, :], dim=0)
|
||||
else:
|
||||
s += torch.sum(arch_probs[offset:offset+i+1, k].unsqueeze(-1).unsqueeze(-1) * unweighted, dim=0)
|
||||
s = self.bn(s)
|
||||
states = torch.cat([states, s.unsqueeze(0)], 0)
|
||||
offset += i+1
|
||||
output = torch.mean(states[-CONCAT:], dim=0)
|
||||
return output
|
||||
|
||||
|
||||
class RNNModelSearch(RNNModel):
|
||||
|
||||
def __init__(self, *args):
|
||||
super(RNNModelSearch, self).__init__(*args)
|
||||
self._args = copy.deepcopy( args )
|
||||
|
||||
k = sum(i for i in range(1, STEPS+1))
|
||||
self.arch_weights = nn.Parameter(torch.Tensor(k, len(PRIMITIVES)))
|
||||
nn.init.normal_(self.arch_weights, 0, 0.001)
|
||||
|
||||
def base_parameters(self):
|
||||
lists = list(self.lockdrop.parameters())
|
||||
lists += list(self.encoder.parameters())
|
||||
lists += list(self.rnns.parameters())
|
||||
lists += list(self.decoder.parameters())
|
||||
return lists
|
||||
|
||||
def arch_parameters(self):
|
||||
return [self.arch_weights]
|
||||
|
||||
def genotype(self):
|
||||
|
||||
def _parse(probs):
|
||||
gene = []
|
||||
start = 0
|
||||
for i in range(STEPS):
|
||||
end = start + i + 1
|
||||
W = probs[start:end].copy()
|
||||
#j = sorted(range(i + 1), key=lambda x: -max(W[x][k] for k in range(len(W[x])) if k != PRIMITIVES.index('none')))[0]
|
||||
j = sorted(range(i + 1), key=lambda x: -max(W[x][k] for k in range(len(W[x])) ))[0]
|
||||
k_best = None
|
||||
for k in range(len(W[j])):
|
||||
#if k != PRIMITIVES.index('none'):
|
||||
# if k_best is None or W[j][k] > W[j][k_best]:
|
||||
# k_best = k
|
||||
if k_best is None or W[j][k] > W[j][k_best]:
|
||||
k_best = k
|
||||
gene.append((PRIMITIVES[k_best], j))
|
||||
start = end
|
||||
return gene
|
||||
|
||||
with torch.no_grad():
|
||||
gene = _parse(F.softmax(self.arch_weights, dim=-1).cpu().numpy())
|
||||
genotype = Genotype(recurrent=gene, concat=list(range(STEPS+1)[-CONCAT:]))
|
||||
return genotype
|
||||
@@ -1,66 +0,0 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import os, shutil
|
||||
import numpy as np
|
||||
|
||||
|
||||
def repackage_hidden(h):
|
||||
if isinstance(h, torch.Tensor):
|
||||
return h.detach()
|
||||
else:
|
||||
return tuple(repackage_hidden(v) for v in h)
|
||||
|
||||
|
||||
def batchify(data, bsz, use_cuda):
|
||||
nbatch = data.size(0) // bsz
|
||||
data = data.narrow(0, 0, nbatch * bsz)
|
||||
data = data.view(bsz, -1).t().contiguous()
|
||||
if use_cuda: return data.cuda()
|
||||
else : return data
|
||||
|
||||
|
||||
def get_batch(source, i, seq_len):
|
||||
seq_len = min(seq_len, len(source) - 1 - i)
|
||||
data = source[i:i+seq_len].clone()
|
||||
target = source[i+1:i+1+seq_len].clone()
|
||||
return data, target
|
||||
|
||||
|
||||
|
||||
def embedded_dropout(embed, words, dropout=0.1, scale=None):
|
||||
if dropout:
|
||||
mask = embed.weight.data.new().resize_((embed.weight.size(0), 1)).bernoulli_(1 - dropout).expand_as(embed.weight) / (1 - dropout)
|
||||
mask.requires_grad_(True)
|
||||
masked_embed_weight = mask * embed.weight
|
||||
else:
|
||||
masked_embed_weight = embed.weight
|
||||
if scale:
|
||||
masked_embed_weight = scale.expand_as(masked_embed_weight) * masked_embed_weight
|
||||
|
||||
padding_idx = embed.padding_idx
|
||||
if padding_idx is None:
|
||||
padding_idx = -1
|
||||
X = torch.nn.functional.embedding(
|
||||
words, masked_embed_weight,
|
||||
padding_idx, embed.max_norm, embed.norm_type,
|
||||
embed.scale_grad_by_freq, embed.sparse)
|
||||
return X
|
||||
|
||||
|
||||
class LockedDropout(nn.Module):
|
||||
def __init__(self):
|
||||
super(LockedDropout, self).__init__()
|
||||
|
||||
def forward(self, x, dropout=0.5):
|
||||
if not self.training or not dropout:
|
||||
return x
|
||||
m = x.data.new(1, x.size(1), x.size(2)).bernoulli_(1 - dropout)
|
||||
mask = m.div_(1 - dropout).detach()
|
||||
mask = mask.expand_as(x)
|
||||
return mask * x
|
||||
|
||||
|
||||
def mask2d(B, D, keep_prob, cuda=True):
|
||||
m = torch.floor(torch.rand(B, D) + keep_prob) / keep_prob
|
||||
if cuda: return m.cuda()
|
||||
else : return m
|
||||
22
lib/procedures/__init__.py
Normal file
22
lib/procedures/__init__.py
Normal file
@@ -0,0 +1,22 @@
|
||||
##################################################
|
||||
# Copyright (c) Xuanyi Dong [GitHub D-X-Y], 2019 #
|
||||
##################################################
|
||||
from .starts import prepare_seed, prepare_logger, get_machine_info, save_checkpoint, copy_checkpoint
|
||||
from .optimizers import get_optim_scheduler
|
||||
|
||||
def get_procedures(procedure):
|
||||
from .basic_main import basic_train, basic_valid
|
||||
from .search_main import search_train, search_valid
|
||||
from .search_main_v2 import search_train_v2
|
||||
from .simple_KD_main import simple_KD_train, simple_KD_valid
|
||||
|
||||
train_funcs = {'basic' : basic_train, \
|
||||
'search': search_train,'Simple-KD': simple_KD_train, \
|
||||
'search-v2': search_train_v2}
|
||||
valid_funcs = {'basic' : basic_valid, \
|
||||
'search': search_valid,'Simple-KD': simple_KD_valid, \
|
||||
'search-v2': search_valid}
|
||||
|
||||
train_func = train_funcs[procedure]
|
||||
valid_func = valid_funcs[procedure]
|
||||
return train_func, valid_func
|
||||
75
lib/procedures/basic_main.py
Normal file
75
lib/procedures/basic_main.py
Normal file
@@ -0,0 +1,75 @@
|
||||
##################################################
|
||||
# Copyright (c) Xuanyi Dong [GitHub D-X-Y], 2019 #
|
||||
##################################################
|
||||
import os, sys, time, torch
|
||||
from log_utils import AverageMeter, time_string
|
||||
from utils import obtain_accuracy
|
||||
|
||||
|
||||
def basic_train(xloader, network, criterion, scheduler, optimizer, optim_config, extra_info, print_freq, logger):
|
||||
loss, acc1, acc5 = procedure(xloader, network, criterion, scheduler, optimizer, 'train', optim_config, extra_info, print_freq, logger)
|
||||
return loss, acc1, acc5
|
||||
|
||||
|
||||
def basic_valid(xloader, network, criterion, optim_config, extra_info, print_freq, logger):
|
||||
with torch.no_grad():
|
||||
loss, acc1, acc5 = procedure(xloader, network, criterion, None, None, 'valid', None, extra_info, print_freq, logger)
|
||||
return loss, acc1, acc5
|
||||
|
||||
|
||||
def procedure(xloader, network, criterion, scheduler, optimizer, mode, config, extra_info, print_freq, logger):
|
||||
data_time, batch_time, losses, top1, top5 = AverageMeter(), AverageMeter(), AverageMeter(), AverageMeter(), AverageMeter()
|
||||
if mode == 'train':
|
||||
network.train()
|
||||
elif mode == 'valid':
|
||||
network.eval()
|
||||
else: raise ValueError("The mode is not right : {:}".format(mode))
|
||||
|
||||
#logger.log('[{:5s}] config :: auxiliary={:}, message={:}'.format(mode, config.auxiliary if hasattr(config, 'auxiliary') else -1, network.module.get_message()))
|
||||
logger.log('[{:5s}] config :: auxiliary={:}'.format(mode, config.auxiliary if hasattr(config, 'auxiliary') else -1))
|
||||
end = time.time()
|
||||
for i, (inputs, targets) in enumerate(xloader):
|
||||
if mode == 'train': scheduler.update(None, 1.0 * i / len(xloader))
|
||||
# measure data loading time
|
||||
data_time.update(time.time() - end)
|
||||
# calculate prediction and loss
|
||||
targets = targets.cuda(non_blocking=True)
|
||||
|
||||
if mode == 'train': optimizer.zero_grad()
|
||||
|
||||
features, logits = network(inputs)
|
||||
if isinstance(logits, list):
|
||||
assert len(logits) == 2, 'logits must has {:} items instead of {:}'.format(2, len(logits))
|
||||
logits, logits_aux = logits
|
||||
else:
|
||||
logits, logits_aux = logits, None
|
||||
loss = criterion(logits, targets)
|
||||
if config is not None and hasattr(config, 'auxiliary') and config.auxiliary > 0:
|
||||
loss_aux = criterion(logits_aux, targets)
|
||||
loss += config.auxiliary * loss_aux
|
||||
|
||||
if mode == 'train':
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
|
||||
# record
|
||||
prec1, prec5 = obtain_accuracy(logits.data, targets.data, topk=(1, 5))
|
||||
losses.update(loss.item(), inputs.size(0))
|
||||
top1.update (prec1.item(), inputs.size(0))
|
||||
top5.update (prec5.item(), inputs.size(0))
|
||||
|
||||
# measure elapsed time
|
||||
batch_time.update(time.time() - end)
|
||||
end = time.time()
|
||||
|
||||
if i % print_freq == 0 or (i+1) == len(xloader):
|
||||
Sstr = ' {:5s} '.format(mode.upper()) + time_string() + ' [{:}][{:03d}/{:03d}]'.format(extra_info, i, len(xloader))
|
||||
if scheduler is not None:
|
||||
Sstr += ' {:}'.format(scheduler.get_min_info())
|
||||
Tstr = 'Time {batch_time.val:.2f} ({batch_time.avg:.2f}) Data {data_time.val:.2f} ({data_time.avg:.2f})'.format(batch_time=batch_time, data_time=data_time)
|
||||
Lstr = 'Loss {loss.val:.3f} ({loss.avg:.3f}) Prec@1 {top1.val:.2f} ({top1.avg:.2f}) Prec@5 {top5.val:.2f} ({top5.avg:.2f})'.format(loss=losses, top1=top1, top5=top5)
|
||||
Istr = 'Size={:}'.format(list(inputs.size()))
|
||||
logger.log(Sstr + ' ' + Tstr + ' ' + Lstr + ' ' + Istr)
|
||||
|
||||
logger.log(' **{mode:5s}** Prec@1 {top1.avg:.2f} Prec@5 {top5.avg:.2f} Error@1 {error1:.2f} Error@5 {error5:.2f} Loss:{loss:.3f}'.format(mode=mode.upper(), top1=top1, top5=top5, error1=100-top1.avg, error5=100-top5.avg, loss=losses.avg))
|
||||
return losses.avg, top1.avg, top5.avg
|
||||
201
lib/procedures/optimizers.py
Normal file
201
lib/procedures/optimizers.py
Normal file
@@ -0,0 +1,201 @@
|
||||
##################################################
|
||||
# Copyright (c) Xuanyi Dong [GitHub D-X-Y], 2019 #
|
||||
##################################################
|
||||
import math, torch
|
||||
import torch.nn as nn
|
||||
from bisect import bisect_right
|
||||
from torch.optim import Optimizer
|
||||
|
||||
|
||||
class _LRScheduler(object):
|
||||
|
||||
def __init__(self, optimizer, warmup_epochs, epochs):
|
||||
if not isinstance(optimizer, Optimizer):
|
||||
raise TypeError('{:} is not an Optimizer'.format(type(optimizer).__name__))
|
||||
self.optimizer = optimizer
|
||||
for group in optimizer.param_groups:
|
||||
group.setdefault('initial_lr', group['lr'])
|
||||
self.base_lrs = list(map(lambda group: group['initial_lr'], optimizer.param_groups))
|
||||
self.max_epochs = epochs
|
||||
self.warmup_epochs = warmup_epochs
|
||||
self.current_epoch = 0
|
||||
self.current_iter = 0
|
||||
|
||||
def extra_repr(self):
|
||||
return ''
|
||||
|
||||
def __repr__(self):
|
||||
return ('{name}(warmup={warmup_epochs}, max-epoch={max_epochs}, current::epoch={current_epoch}, iter={current_iter:.2f}'.format(name=self.__class__.__name__, **self.__dict__)
|
||||
+ ', {:})'.format(self.extra_repr()))
|
||||
|
||||
def state_dict(self):
|
||||
return {key: value for key, value in self.__dict__.items() if key != 'optimizer'}
|
||||
|
||||
def load_state_dict(self, state_dict):
|
||||
self.__dict__.update(state_dict)
|
||||
|
||||
def get_lr(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_min_info(self):
|
||||
lrs = self.get_lr()
|
||||
return '#LR=[{:.6f}~{:.6f}] epoch={:03d}, iter={:4.2f}#'.format(min(lrs), max(lrs), self.current_epoch, self.current_iter)
|
||||
|
||||
def get_min_lr(self):
|
||||
return min( self.get_lr() )
|
||||
|
||||
def update(self, cur_epoch, cur_iter):
|
||||
if cur_epoch is not None:
|
||||
assert isinstance(cur_epoch, int) and cur_epoch>=0, 'invalid cur-epoch : {:}'.format(cur_epoch)
|
||||
self.current_epoch = cur_epoch
|
||||
if cur_iter is not None:
|
||||
assert isinstance(cur_iter, float) and cur_iter>=0, 'invalid cur-iter : {:}'.format(cur_iter)
|
||||
self.current_iter = cur_iter
|
||||
for param_group, lr in zip(self.optimizer.param_groups, self.get_lr()):
|
||||
param_group['lr'] = lr
|
||||
|
||||
|
||||
|
||||
class CosineAnnealingLR(_LRScheduler):
|
||||
|
||||
def __init__(self, optimizer, warmup_epochs, epochs, T_max, eta_min):
|
||||
self.T_max = T_max
|
||||
self.eta_min = eta_min
|
||||
super(CosineAnnealingLR, self).__init__(optimizer, warmup_epochs, epochs)
|
||||
|
||||
def extra_repr(self):
|
||||
return 'type={:}, T-max={:}, eta-min={:}'.format('cosine', self.T_max, self.eta_min)
|
||||
|
||||
def get_lr(self):
|
||||
lrs = []
|
||||
for base_lr in self.base_lrs:
|
||||
if self.current_epoch >= self.warmup_epochs:
|
||||
last_epoch = self.current_epoch - self.warmup_epochs
|
||||
if last_epoch < self.T_max:
|
||||
lr = self.eta_min + (base_lr - self.eta_min) * (1 + math.cos(math.pi * last_epoch / self.T_max)) / 2
|
||||
else:
|
||||
lr = self.eta_min + (base_lr - self.eta_min) * (1 + math.cos(math.pi * (self.T_max-1.0) / self.T_max)) / 2
|
||||
else:
|
||||
lr = (self.current_epoch / self.warmup_epochs + self.current_iter / self.warmup_epochs) * base_lr
|
||||
lrs.append( lr )
|
||||
return lrs
|
||||
|
||||
|
||||
|
||||
class MultiStepLR(_LRScheduler):
|
||||
|
||||
def __init__(self, optimizer, warmup_epochs, epochs, milestones, gammas):
|
||||
assert len(milestones) == len(gammas), 'invalid {:} vs {:}'.format(len(milestones), len(gammas))
|
||||
self.milestones = milestones
|
||||
self.gammas = gammas
|
||||
super(MultiStepLR, self).__init__(optimizer, warmup_epochs, epochs)
|
||||
|
||||
def extra_repr(self):
|
||||
return 'type={:}, milestones={:}, gammas={:}, base-lrs={:}'.format('multistep', self.milestones, self.gammas, self.base_lrs)
|
||||
|
||||
def get_lr(self):
|
||||
lrs = []
|
||||
for base_lr in self.base_lrs:
|
||||
if self.current_epoch >= self.warmup_epochs:
|
||||
last_epoch = self.current_epoch - self.warmup_epochs
|
||||
idx = bisect_right(self.milestones, last_epoch)
|
||||
lr = base_lr
|
||||
for x in self.gammas[:idx]: lr *= x
|
||||
else:
|
||||
lr = (self.current_epoch / self.warmup_epochs + self.current_iter / self.warmup_epochs) * base_lr
|
||||
lrs.append( lr )
|
||||
return lrs
|
||||
|
||||
|
||||
class ExponentialLR(_LRScheduler):
|
||||
|
||||
def __init__(self, optimizer, warmup_epochs, epochs, gamma):
|
||||
self.gamma = gamma
|
||||
super(ExponentialLR, self).__init__(optimizer, warmup_epochs, epochs)
|
||||
|
||||
def extra_repr(self):
|
||||
return 'type={:}, gamma={:}, base-lrs={:}'.format('exponential', self.gamma, self.base_lrs)
|
||||
|
||||
def get_lr(self):
|
||||
lrs = []
|
||||
for base_lr in self.base_lrs:
|
||||
if self.current_epoch >= self.warmup_epochs:
|
||||
last_epoch = self.current_epoch - self.warmup_epochs
|
||||
assert last_epoch >= 0, 'invalid last_epoch : {:}'.format(last_epoch)
|
||||
lr = base_lr * (self.gamma ** last_epoch)
|
||||
else:
|
||||
lr = (self.current_epoch / self.warmup_epochs + self.current_iter / self.warmup_epochs) * base_lr
|
||||
lrs.append( lr )
|
||||
return lrs
|
||||
|
||||
|
||||
class LinearLR(_LRScheduler):
|
||||
|
||||
def __init__(self, optimizer, warmup_epochs, epochs, max_LR, min_LR):
|
||||
self.max_LR = max_LR
|
||||
self.min_LR = min_LR
|
||||
super(LinearLR, self).__init__(optimizer, warmup_epochs, epochs)
|
||||
|
||||
def extra_repr(self):
|
||||
return 'type={:}, max_LR={:}, min_LR={:}, base-lrs={:}'.format('LinearLR', self.max_LR, self.min_LR, self.base_lrs)
|
||||
|
||||
def get_lr(self):
|
||||
lrs = []
|
||||
for base_lr in self.base_lrs:
|
||||
if self.current_epoch >= self.warmup_epochs:
|
||||
last_epoch = self.current_epoch - self.warmup_epochs
|
||||
assert last_epoch >= 0, 'invalid last_epoch : {:}'.format(last_epoch)
|
||||
ratio = (self.max_LR - self.min_LR) * last_epoch / self.max_epochs / self.max_LR
|
||||
lr = base_lr * (1-ratio)
|
||||
else:
|
||||
lr = (self.current_epoch / self.warmup_epochs + self.current_iter / self.warmup_epochs) * base_lr
|
||||
lrs.append( lr )
|
||||
return lrs
|
||||
|
||||
|
||||
|
||||
class CrossEntropyLabelSmooth(nn.Module):
|
||||
|
||||
def __init__(self, num_classes, epsilon):
|
||||
super(CrossEntropyLabelSmooth, self).__init__()
|
||||
self.num_classes = num_classes
|
||||
self.epsilon = epsilon
|
||||
self.logsoftmax = nn.LogSoftmax(dim=1)
|
||||
|
||||
def forward(self, inputs, targets):
|
||||
log_probs = self.logsoftmax(inputs)
|
||||
targets = torch.zeros_like(log_probs).scatter_(1, targets.unsqueeze(1), 1)
|
||||
targets = (1 - self.epsilon) * targets + self.epsilon / self.num_classes
|
||||
loss = (-targets * log_probs).mean(0).sum()
|
||||
return loss
|
||||
|
||||
|
||||
|
||||
def get_optim_scheduler(parameters, config):
|
||||
assert hasattr(config, 'optim') and hasattr(config, 'scheduler') and hasattr(config, 'criterion'), 'config must have optim / scheduler / criterion keys instead of {:}'.format(config)
|
||||
if config.optim == 'SGD':
|
||||
optim = torch.optim.SGD(parameters, config.LR, momentum=config.momentum, weight_decay=config.decay, nesterov=config.nesterov)
|
||||
elif config.optim == 'RMSprop':
|
||||
optim = torch.optim.RMSprop(parameters, config.LR, momentum=config.momentum, weight_decay=config.decay)
|
||||
else:
|
||||
raise ValueError('invalid optim : {:}'.format(config.optim))
|
||||
|
||||
if config.scheduler == 'cos':
|
||||
T_max = getattr(config, 'T_max', config.epochs)
|
||||
scheduler = CosineAnnealingLR(optim, config.warmup, config.epochs, T_max, config.eta_min)
|
||||
elif config.scheduler == 'multistep':
|
||||
scheduler = MultiStepLR(optim, config.warmup, config.epochs, config.milestones, config.gammas)
|
||||
elif config.scheduler == 'exponential':
|
||||
scheduler = ExponentialLR(optim, config.warmup, config.epochs, config.gamma)
|
||||
elif config.scheduler == 'linear':
|
||||
scheduler = LinearLR(optim, config.warmup, config.epochs, config.LR, config.LR_min)
|
||||
else:
|
||||
raise ValueError('invalid scheduler : {:}'.format(config.scheduler))
|
||||
|
||||
if config.criterion == 'Softmax':
|
||||
criterion = torch.nn.CrossEntropyLoss()
|
||||
elif config.criterion == 'SmoothSoftmax':
|
||||
criterion = CrossEntropyLabelSmooth(config.class_num, config.label_smooth)
|
||||
else:
|
||||
raise ValueError('invalid criterion : {:}'.format(config.criterion))
|
||||
return optim, scheduler, criterion
|
||||
126
lib/procedures/search_main.py
Normal file
126
lib/procedures/search_main.py
Normal file
@@ -0,0 +1,126 @@
|
||||
##################################################
|
||||
# Copyright (c) Xuanyi Dong [GitHub D-X-Y], 2019 #
|
||||
##################################################
|
||||
import os, sys, time, torch
|
||||
from log_utils import AverageMeter, time_string
|
||||
from utils import obtain_accuracy
|
||||
from models import change_key
|
||||
|
||||
|
||||
def get_flop_loss(expected_flop, flop_cur, flop_need, flop_tolerant):
|
||||
expected_flop = torch.mean( expected_flop )
|
||||
|
||||
if flop_cur < flop_need - flop_tolerant: # Too Small FLOP
|
||||
loss = - torch.log( expected_flop )
|
||||
#elif flop_cur > flop_need + flop_tolerant: # Too Large FLOP
|
||||
elif flop_cur > flop_need: # Too Large FLOP
|
||||
loss = torch.log( expected_flop )
|
||||
else: # Required FLOP
|
||||
loss = None
|
||||
if loss is None: return 0, 0
|
||||
else : return loss, loss.item()
|
||||
|
||||
|
||||
def search_train(search_loader, network, criterion, scheduler, base_optimizer, arch_optimizer, optim_config, extra_info, print_freq, logger):
|
||||
data_time, batch_time = AverageMeter(), AverageMeter()
|
||||
base_losses, arch_losses, top1, top5 = AverageMeter(), AverageMeter(), AverageMeter(), AverageMeter()
|
||||
arch_cls_losses, arch_flop_losses = AverageMeter(), AverageMeter()
|
||||
epoch_str, flop_need, flop_weight, flop_tolerant = extra_info['epoch-str'], extra_info['FLOP-exp'], extra_info['FLOP-weight'], extra_info['FLOP-tolerant']
|
||||
|
||||
network.train()
|
||||
logger.log('[Search] : {:}, FLOP-Require={:.2f} MB, FLOP-WEIGHT={:.2f}'.format(epoch_str, flop_need, flop_weight))
|
||||
end = time.time()
|
||||
network.apply( change_key('search_mode', 'search') )
|
||||
for step, (base_inputs, base_targets, arch_inputs, arch_targets) in enumerate(search_loader):
|
||||
scheduler.update(None, 1.0 * step / len(search_loader))
|
||||
# calculate prediction and loss
|
||||
base_targets = base_targets.cuda(non_blocking=True)
|
||||
arch_targets = arch_targets.cuda(non_blocking=True)
|
||||
# measure data loading time
|
||||
data_time.update(time.time() - end)
|
||||
|
||||
# update the weights
|
||||
base_optimizer.zero_grad()
|
||||
logits, expected_flop = network(base_inputs)
|
||||
#network.apply( change_key('search_mode', 'basic') )
|
||||
#features, logits = network(base_inputs)
|
||||
base_loss = criterion(logits, base_targets)
|
||||
base_loss.backward()
|
||||
base_optimizer.step()
|
||||
# record
|
||||
prec1, prec5 = obtain_accuracy(logits.data, base_targets.data, topk=(1, 5))
|
||||
base_losses.update(base_loss.item(), base_inputs.size(0))
|
||||
top1.update (prec1.item(), base_inputs.size(0))
|
||||
top5.update (prec5.item(), base_inputs.size(0))
|
||||
|
||||
# update the architecture
|
||||
arch_optimizer.zero_grad()
|
||||
logits, expected_flop = network(arch_inputs)
|
||||
flop_cur = network.module.get_flop('genotype', None, None)
|
||||
flop_loss, flop_loss_scale = get_flop_loss(expected_flop, flop_cur, flop_need, flop_tolerant)
|
||||
acls_loss = criterion(logits, arch_targets)
|
||||
arch_loss = acls_loss + flop_loss * flop_weight
|
||||
arch_loss.backward()
|
||||
arch_optimizer.step()
|
||||
|
||||
# record
|
||||
arch_losses.update(arch_loss.item(), arch_inputs.size(0))
|
||||
arch_flop_losses.update(flop_loss_scale, arch_inputs.size(0))
|
||||
arch_cls_losses.update (acls_loss.item(), arch_inputs.size(0))
|
||||
|
||||
# measure elapsed time
|
||||
batch_time.update(time.time() - end)
|
||||
end = time.time()
|
||||
if step % print_freq == 0 or (step+1) == len(search_loader):
|
||||
Sstr = '**TRAIN** ' + time_string() + ' [{:}][{:03d}/{:03d}]'.format(epoch_str, step, len(search_loader))
|
||||
Tstr = 'Time {batch_time.val:.2f} ({batch_time.avg:.2f}) Data {data_time.val:.2f} ({data_time.avg:.2f})'.format(batch_time=batch_time, data_time=data_time)
|
||||
Lstr = 'Base-Loss {loss.val:.3f} ({loss.avg:.3f}) Prec@1 {top1.val:.2f} ({top1.avg:.2f}) Prec@5 {top5.val:.2f} ({top5.avg:.2f})'.format(loss=base_losses, top1=top1, top5=top5)
|
||||
Vstr = 'Acls-loss {aloss.val:.3f} ({aloss.avg:.3f}) FLOP-Loss {floss.val:.3f} ({floss.avg:.3f}) Arch-Loss {loss.val:.3f} ({loss.avg:.3f})'.format(aloss=arch_cls_losses, floss=arch_flop_losses, loss=arch_losses)
|
||||
logger.log(Sstr + ' ' + Tstr + ' ' + Lstr + ' ' + Vstr)
|
||||
#Istr = 'Bsz={:} Asz={:}'.format(list(base_inputs.size()), list(arch_inputs.size()))
|
||||
#logger.log(Sstr + ' ' + Tstr + ' ' + Lstr + ' ' + Vstr + ' ' + Istr)
|
||||
#print(network.module.get_arch_info())
|
||||
#print(network.module.width_attentions[0])
|
||||
#print(network.module.width_attentions[1])
|
||||
|
||||
logger.log(' **TRAIN** Prec@1 {top1.avg:.2f} Prec@5 {top5.avg:.2f} Error@1 {error1:.2f} Error@5 {error5:.2f} Base-Loss:{baseloss:.3f}, Arch-Loss={archloss:.3f}'.format(top1=top1, top5=top5, error1=100-top1.avg, error5=100-top5.avg, baseloss=base_losses.avg, archloss=arch_losses.avg))
|
||||
return base_losses.avg, arch_losses.avg, top1.avg, top5.avg
|
||||
|
||||
|
||||
|
||||
def search_valid(xloader, network, criterion, extra_info, print_freq, logger):
|
||||
data_time, batch_time, losses, top1, top5 = AverageMeter(), AverageMeter(), AverageMeter(), AverageMeter(), AverageMeter()
|
||||
|
||||
network.eval()
|
||||
network.apply( change_key('search_mode', 'search') )
|
||||
end = time.time()
|
||||
#logger.log('Starting evaluating {:}'.format(epoch_info))
|
||||
with torch.no_grad():
|
||||
for i, (inputs, targets) in enumerate(xloader):
|
||||
# measure data loading time
|
||||
data_time.update(time.time() - end)
|
||||
# calculate prediction and loss
|
||||
targets = targets.cuda(non_blocking=True)
|
||||
|
||||
logits, expected_flop = network(inputs)
|
||||
loss = criterion(logits, targets)
|
||||
# record
|
||||
prec1, prec5 = obtain_accuracy(logits.data, targets.data, topk=(1, 5))
|
||||
losses.update(loss.item(), inputs.size(0))
|
||||
top1.update (prec1.item(), inputs.size(0))
|
||||
top5.update (prec5.item(), inputs.size(0))
|
||||
|
||||
# measure elapsed time
|
||||
batch_time.update(time.time() - end)
|
||||
end = time.time()
|
||||
|
||||
if i % print_freq == 0 or (i+1) == len(xloader):
|
||||
Sstr = '**VALID** ' + time_string() + ' [{:}][{:03d}/{:03d}]'.format(extra_info, i, len(xloader))
|
||||
Tstr = 'Time {batch_time.val:.2f} ({batch_time.avg:.2f}) Data {data_time.val:.2f} ({data_time.avg:.2f})'.format(batch_time=batch_time, data_time=data_time)
|
||||
Lstr = 'Loss {loss.val:.3f} ({loss.avg:.3f}) Prec@1 {top1.val:.2f} ({top1.avg:.2f}) Prec@5 {top5.val:.2f} ({top5.avg:.2f})'.format(loss=losses, top1=top1, top5=top5)
|
||||
Istr = 'Size={:}'.format(list(inputs.size()))
|
||||
logger.log(Sstr + ' ' + Tstr + ' ' + Lstr + ' ' + Istr)
|
||||
|
||||
logger.log(' **VALID** Prec@1 {top1.avg:.2f} Prec@5 {top5.avg:.2f} Error@1 {error1:.2f} Error@5 {error5:.2f} Loss:{loss:.3f}'.format(top1=top1, top5=top5, error1=100-top1.avg, error5=100-top5.avg, loss=losses.avg))
|
||||
|
||||
return losses.avg, top1.avg, top5.avg
|
||||
87
lib/procedures/search_main_v2.py
Normal file
87
lib/procedures/search_main_v2.py
Normal file
@@ -0,0 +1,87 @@
|
||||
##################################################
|
||||
# Copyright (c) Xuanyi Dong [GitHub D-X-Y], 2019 #
|
||||
##################################################
|
||||
import os, sys, time, torch
|
||||
from log_utils import AverageMeter, time_string
|
||||
from utils import obtain_accuracy
|
||||
from models import change_key
|
||||
|
||||
|
||||
def get_flop_loss(expected_flop, flop_cur, flop_need, flop_tolerant):
|
||||
expected_flop = torch.mean( expected_flop )
|
||||
|
||||
if flop_cur < flop_need - flop_tolerant: # Too Small FLOP
|
||||
loss = - torch.log( expected_flop )
|
||||
#elif flop_cur > flop_need + flop_tolerant: # Too Large FLOP
|
||||
elif flop_cur > flop_need: # Too Large FLOP
|
||||
loss = torch.log( expected_flop )
|
||||
else: # Required FLOP
|
||||
loss = None
|
||||
if loss is None: return 0, 0
|
||||
else : return loss, loss.item()
|
||||
|
||||
|
||||
def search_train_v2(search_loader, network, criterion, scheduler, base_optimizer, arch_optimizer, optim_config, extra_info, print_freq, logger):
|
||||
data_time, batch_time = AverageMeter(), AverageMeter()
|
||||
base_losses, arch_losses, top1, top5 = AverageMeter(), AverageMeter(), AverageMeter(), AverageMeter()
|
||||
arch_cls_losses, arch_flop_losses = AverageMeter(), AverageMeter()
|
||||
epoch_str, flop_need, flop_weight, flop_tolerant = extra_info['epoch-str'], extra_info['FLOP-exp'], extra_info['FLOP-weight'], extra_info['FLOP-tolerant']
|
||||
|
||||
network.train()
|
||||
logger.log('[Search] : {:}, FLOP-Require={:.2f} MB, FLOP-WEIGHT={:.2f}'.format(epoch_str, flop_need, flop_weight))
|
||||
end = time.time()
|
||||
network.apply( change_key('search_mode', 'search') )
|
||||
for step, (base_inputs, base_targets, arch_inputs, arch_targets) in enumerate(search_loader):
|
||||
scheduler.update(None, 1.0 * step / len(search_loader))
|
||||
# calculate prediction and loss
|
||||
base_targets = base_targets.cuda(non_blocking=True)
|
||||
arch_targets = arch_targets.cuda(non_blocking=True)
|
||||
# measure data loading time
|
||||
data_time.update(time.time() - end)
|
||||
|
||||
# update the weights
|
||||
base_optimizer.zero_grad()
|
||||
logits, expected_flop = network(base_inputs)
|
||||
base_loss = criterion(logits, base_targets)
|
||||
base_loss.backward()
|
||||
base_optimizer.step()
|
||||
# record
|
||||
prec1, prec5 = obtain_accuracy(logits.data, base_targets.data, topk=(1, 5))
|
||||
base_losses.update(base_loss.item(), base_inputs.size(0))
|
||||
top1.update (prec1.item(), base_inputs.size(0))
|
||||
top5.update (prec5.item(), base_inputs.size(0))
|
||||
|
||||
# update the architecture
|
||||
arch_optimizer.zero_grad()
|
||||
logits, expected_flop = network(arch_inputs)
|
||||
flop_cur = network.module.get_flop('genotype', None, None)
|
||||
flop_loss, flop_loss_scale = get_flop_loss(expected_flop, flop_cur, flop_need, flop_tolerant)
|
||||
acls_loss = criterion(logits, arch_targets)
|
||||
arch_loss = acls_loss + flop_loss * flop_weight
|
||||
arch_loss.backward()
|
||||
arch_optimizer.step()
|
||||
|
||||
# record
|
||||
arch_losses.update(arch_loss.item(), arch_inputs.size(0))
|
||||
arch_flop_losses.update(flop_loss_scale, arch_inputs.size(0))
|
||||
arch_cls_losses.update (acls_loss.item(), arch_inputs.size(0))
|
||||
|
||||
# measure elapsed time
|
||||
batch_time.update(time.time() - end)
|
||||
end = time.time()
|
||||
if step % print_freq == 0 or (step+1) == len(search_loader):
|
||||
Sstr = '**TRAIN** ' + time_string() + ' [{:}][{:03d}/{:03d}]'.format(epoch_str, step, len(search_loader))
|
||||
Tstr = 'Time {batch_time.val:.2f} ({batch_time.avg:.2f}) Data {data_time.val:.2f} ({data_time.avg:.2f})'.format(batch_time=batch_time, data_time=data_time)
|
||||
Lstr = 'Base-Loss {loss.val:.3f} ({loss.avg:.3f}) Prec@1 {top1.val:.2f} ({top1.avg:.2f}) Prec@5 {top5.val:.2f} ({top5.avg:.2f})'.format(loss=base_losses, top1=top1, top5=top5)
|
||||
Vstr = 'Acls-loss {aloss.val:.3f} ({aloss.avg:.3f}) FLOP-Loss {floss.val:.3f} ({floss.avg:.3f}) Arch-Loss {loss.val:.3f} ({loss.avg:.3f})'.format(aloss=arch_cls_losses, floss=arch_flop_losses, loss=arch_losses)
|
||||
logger.log(Sstr + ' ' + Tstr + ' ' + Lstr + ' ' + Vstr)
|
||||
#num_bytes = torch.cuda.max_memory_allocated( next(network.parameters()).device ) * 1.0
|
||||
#logger.log(Sstr + ' ' + Tstr + ' ' + Lstr + ' ' + Vstr + ' GPU={:.2f}MB'.format(num_bytes/1e6))
|
||||
#Istr = 'Bsz={:} Asz={:}'.format(list(base_inputs.size()), list(arch_inputs.size()))
|
||||
#logger.log(Sstr + ' ' + Tstr + ' ' + Lstr + ' ' + Vstr + ' ' + Istr)
|
||||
#print(network.module.get_arch_info())
|
||||
#print(network.module.width_attentions[0])
|
||||
#print(network.module.width_attentions[1])
|
||||
|
||||
logger.log(' **TRAIN** Prec@1 {top1.avg:.2f} Prec@5 {top5.avg:.2f} Error@1 {error1:.2f} Error@5 {error5:.2f} Base-Loss:{baseloss:.3f}, Arch-Loss={archloss:.3f}'.format(top1=top1, top5=top5, error1=100-top1.avg, error5=100-top5.avg, baseloss=base_losses.avg, archloss=arch_losses.avg))
|
||||
return base_losses.avg, arch_losses.avg, top1.avg, top5.avg
|
||||
94
lib/procedures/simple_KD_main.py
Normal file
94
lib/procedures/simple_KD_main.py
Normal file
@@ -0,0 +1,94 @@
|
||||
##################################################
|
||||
# Copyright (c) Xuanyi Dong [GitHub D-X-Y], 2019 #
|
||||
##################################################
|
||||
import os, sys, time, torch
|
||||
import torch.nn.functional as F
|
||||
# our modules
|
||||
from log_utils import AverageMeter, time_string
|
||||
from utils import obtain_accuracy
|
||||
|
||||
|
||||
def simple_KD_train(xloader, teacher, network, criterion, scheduler, optimizer, optim_config, extra_info, print_freq, logger):
|
||||
loss, acc1, acc5 = procedure(xloader, teacher, network, criterion, scheduler, optimizer, 'train', optim_config, extra_info, print_freq, logger)
|
||||
return loss, acc1, acc5
|
||||
|
||||
def simple_KD_valid(xloader, teacher, network, criterion, optim_config, extra_info, print_freq, logger):
|
||||
with torch.no_grad():
|
||||
loss, acc1, acc5 = procedure(xloader, teacher, network, criterion, None, None, 'valid', optim_config, extra_info, print_freq, logger)
|
||||
return loss, acc1, acc5
|
||||
|
||||
|
||||
def loss_KD_fn(criterion, student_logits, teacher_logits, studentFeatures, teacherFeatures, targets, alpha, temperature):
|
||||
basic_loss = criterion(student_logits, targets) * (1. - alpha)
|
||||
log_student= F.log_softmax(student_logits / temperature, dim=1)
|
||||
sof_teacher= F.softmax (teacher_logits / temperature, dim=1)
|
||||
KD_loss = F.kl_div(log_student, sof_teacher, reduction='batchmean') * (alpha * temperature * temperature)
|
||||
return basic_loss + KD_loss
|
||||
|
||||
|
||||
def procedure(xloader, teacher, network, criterion, scheduler, optimizer, mode, config, extra_info, print_freq, logger):
|
||||
data_time, batch_time, losses, top1, top5 = AverageMeter(), AverageMeter(), AverageMeter(), AverageMeter(), AverageMeter()
|
||||
Ttop1, Ttop5 = AverageMeter(), AverageMeter()
|
||||
if mode == 'train':
|
||||
network.train()
|
||||
elif mode == 'valid':
|
||||
network.eval()
|
||||
else: raise ValueError("The mode is not right : {:}".format(mode))
|
||||
teacher.eval()
|
||||
|
||||
logger.log('[{:5s}] config :: auxiliary={:}, KD :: [alpha={:.2f}, temperature={:.2f}]'.format(mode, config.auxiliary if hasattr(config, 'auxiliary') else -1, config.KD_alpha, config.KD_temperature))
|
||||
end = time.time()
|
||||
for i, (inputs, targets) in enumerate(xloader):
|
||||
if mode == 'train': scheduler.update(None, 1.0 * i / len(xloader))
|
||||
# measure data loading time
|
||||
data_time.update(time.time() - end)
|
||||
# calculate prediction and loss
|
||||
targets = targets.cuda(non_blocking=True)
|
||||
|
||||
if mode == 'train': optimizer.zero_grad()
|
||||
|
||||
student_f, logits = network(inputs)
|
||||
if isinstance(logits, list):
|
||||
assert len(logits) == 2, 'logits must has {:} items instead of {:}'.format(2, len(logits))
|
||||
logits, logits_aux = logits
|
||||
else:
|
||||
logits, logits_aux = logits, None
|
||||
with torch.no_grad():
|
||||
teacher_f, teacher_logits = teacher(inputs)
|
||||
|
||||
loss = loss_KD_fn(criterion, logits, teacher_logits, student_f, teacher_f, targets, config.KD_alpha, config.KD_temperature)
|
||||
if config is not None and hasattr(config, 'auxiliary') and config.auxiliary > 0:
|
||||
loss_aux = criterion(logits_aux, targets)
|
||||
loss += config.auxiliary * loss_aux
|
||||
|
||||
if mode == 'train':
|
||||
loss.backward()
|
||||
optimizer.step()
|
||||
|
||||
# record
|
||||
sprec1, sprec5 = obtain_accuracy(logits.data, targets.data, topk=(1, 5))
|
||||
losses.update(loss.item(), inputs.size(0))
|
||||
top1.update (sprec1.item(), inputs.size(0))
|
||||
top5.update (sprec5.item(), inputs.size(0))
|
||||
# teacher
|
||||
tprec1, tprec5 = obtain_accuracy(teacher_logits.data, targets.data, topk=(1, 5))
|
||||
Ttop1.update (tprec1.item(), inputs.size(0))
|
||||
Ttop5.update (tprec5.item(), inputs.size(0))
|
||||
|
||||
# measure elapsed time
|
||||
batch_time.update(time.time() - end)
|
||||
end = time.time()
|
||||
|
||||
if i % print_freq == 0 or (i+1) == len(xloader):
|
||||
Sstr = ' {:5s} '.format(mode.upper()) + time_string() + ' [{:}][{:03d}/{:03d}]'.format(extra_info, i, len(xloader))
|
||||
if scheduler is not None:
|
||||
Sstr += ' {:}'.format(scheduler.get_min_info())
|
||||
Tstr = 'Time {batch_time.val:.2f} ({batch_time.avg:.2f}) Data {data_time.val:.2f} ({data_time.avg:.2f})'.format(batch_time=batch_time, data_time=data_time)
|
||||
Lstr = 'Loss {loss.val:.3f} ({loss.avg:.3f}) Prec@1 {top1.val:.2f} ({top1.avg:.2f}) Prec@5 {top5.val:.2f} ({top5.avg:.2f})'.format(loss=losses, top1=top1, top5=top5)
|
||||
Lstr+= ' Teacher : acc@1={:.2f}, acc@5={:.2f}'.format(Ttop1.avg, Ttop5.avg)
|
||||
Istr = 'Size={:}'.format(list(inputs.size()))
|
||||
logger.log(Sstr + ' ' + Tstr + ' ' + Lstr + ' ' + Istr)
|
||||
|
||||
logger.log(' **{:5s}** accuracy drop :: @1={:.2f}, @5={:.2f}'.format(mode.upper(), Ttop1.avg - top1.avg, Ttop5.avg - top5.avg))
|
||||
logger.log(' **{mode:5s}** Prec@1 {top1.avg:.2f} Prec@5 {top5.avg:.2f} Error@1 {error1:.2f} Error@5 {error5:.2f} Loss:{loss:.3f}'.format(mode=mode.upper(), top1=top1, top5=top5, error1=100-top1.avg, error5=100-top5.avg, loss=losses.avg))
|
||||
return losses.avg, top1.avg, top5.avg
|
||||
67
lib/procedures/starts.py
Normal file
67
lib/procedures/starts.py
Normal file
@@ -0,0 +1,67 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
# All rights reserved.
|
||||
#
|
||||
# This source code is licensed under the license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
#
|
||||
import os, sys, time, torch, random, PIL, copy, numpy as np
|
||||
from os import path as osp
|
||||
from shutil import copyfile
|
||||
|
||||
|
||||
def prepare_seed(rand_seed):
|
||||
random.seed(rand_seed)
|
||||
np.random.seed(rand_seed)
|
||||
torch.manual_seed(rand_seed)
|
||||
torch.cuda.manual_seed(rand_seed)
|
||||
torch.cuda.manual_seed_all(rand_seed)
|
||||
|
||||
|
||||
def prepare_logger(xargs):
|
||||
args = copy.deepcopy( xargs )
|
||||
from log_utils import Logger
|
||||
logger = Logger(args.save_dir, args.rand_seed)
|
||||
logger.log('Main Function with logger : {:}'.format(logger))
|
||||
logger.log('Arguments : -------------------------------')
|
||||
for name, value in args._get_kwargs():
|
||||
logger.log('{:16} : {:}'.format(name, value))
|
||||
logger.log("Python Version : {:}".format(sys.version.replace('\n', ' ')))
|
||||
logger.log("Pillow Version : {:}".format(PIL.__version__))
|
||||
logger.log("PyTorch Version : {:}".format(torch.__version__))
|
||||
logger.log("cuDNN Version : {:}".format(torch.backends.cudnn.version()))
|
||||
logger.log("CUDA available : {:}".format(torch.cuda.is_available()))
|
||||
logger.log("CUDA GPU numbers : {:}".format(torch.cuda.device_count()))
|
||||
logger.log("CUDA_VISIBLE_DEVICES : {:}".format(os.environ['CUDA_VISIBLE_DEVICES'] if 'CUDA_VISIBLE_DEVICES' in os.environ else 'None'))
|
||||
return logger
|
||||
|
||||
|
||||
def get_machine_info():
|
||||
info = "Python Version : {:}".format(sys.version.replace('\n', ' '))
|
||||
info+= "\nPillow Version : {:}".format(PIL.__version__)
|
||||
info+= "\nPyTorch Version : {:}".format(torch.__version__)
|
||||
info+= "\ncuDNN Version : {:}".format(torch.backends.cudnn.version())
|
||||
info+= "\nCUDA available : {:}".format(torch.cuda.is_available())
|
||||
info+= "\nCUDA GPU numbers : {:}".format(torch.cuda.device_count())
|
||||
if 'CUDA_VISIBLE_DEVICES' in os.environ:
|
||||
info+= "\nCUDA_VISIBLE_DEVICES={:}".format(os.environ['CUDA_VISIBLE_DEVICES'])
|
||||
else:
|
||||
info+= "\nDoes not set CUDA_VISIBLE_DEVICES"
|
||||
return info
|
||||
|
||||
|
||||
def save_checkpoint(state, filename, logger):
|
||||
if osp.isfile(filename):
|
||||
if hasattr(logger, 'log'): logger.log('Find {:} exist, delete is at first before saving'.format(filename))
|
||||
os.remove(filename)
|
||||
torch.save(state, filename)
|
||||
assert osp.isfile(filename), 'save filename : {:} failed, which is not found.'.format(filename)
|
||||
if hasattr(logger, 'log'): logger.log('save checkpoint into {:}'.format(filename))
|
||||
return filename
|
||||
|
||||
|
||||
def copy_checkpoint(src, dst, logger):
|
||||
if osp.isfile(dst):
|
||||
if hasattr(logger, 'log'): logger.log('Find {:} exist, delete is at first before saving'.format(dst))
|
||||
os.remove(dst)
|
||||
copyfile(src, dst)
|
||||
if hasattr(logger, 'log'): logger.log('copy the file from {:} into {:}'.format(src, dst))
|
||||
@@ -1,5 +0,0 @@
|
||||
##################################################
|
||||
# Copyright (c) Xuanyi Dong [GitHub D-X-Y], 2019 #
|
||||
##################################################
|
||||
from .utils import load_config
|
||||
from .scheduler import MultiStepLR, obtain_scheduler
|
||||
@@ -1,32 +0,0 @@
|
||||
##################################################
|
||||
# Copyright (c) Xuanyi Dong [GitHub D-X-Y], 2019 #
|
||||
##################################################
|
||||
import torch
|
||||
from bisect import bisect_right
|
||||
|
||||
|
||||
class MultiStepLR(torch.optim.lr_scheduler._LRScheduler):
|
||||
|
||||
def __init__(self, optimizer, milestones, gammas, last_epoch=-1):
|
||||
if not list(milestones) == sorted(milestones):
|
||||
raise ValueError('Milestones should be a list of'
|
||||
' increasing integers. Got {:}', milestones)
|
||||
assert len(milestones) == len(gammas), '{:} vs {:}'.format(milestones, gammas)
|
||||
self.milestones = milestones
|
||||
self.gammas = gammas
|
||||
super(MultiStepLR, self).__init__(optimizer, last_epoch)
|
||||
|
||||
def get_lr(self):
|
||||
LR = 1
|
||||
for x in self.gammas[:bisect_right(self.milestones, self.last_epoch)]: LR = LR * x
|
||||
return [base_lr * LR for base_lr in self.base_lrs]
|
||||
|
||||
|
||||
def obtain_scheduler(config, optimizer):
|
||||
if config.type == 'multistep':
|
||||
scheduler = MultiStepLR(optimizer, milestones=config.milestones, gammas=config.gammas)
|
||||
elif config.type == 'cosine':
|
||||
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, config.epochs)
|
||||
else:
|
||||
raise ValueError('Unknown learning rate scheduler type : {:}'.format(config.type))
|
||||
return scheduler
|
||||
@@ -1,42 +0,0 @@
|
||||
##################################################
|
||||
# Copyright (c) Xuanyi Dong [GitHub D-X-Y], 2019 #
|
||||
##################################################
|
||||
import os, sys, json
|
||||
from pathlib import Path
|
||||
from collections import namedtuple
|
||||
|
||||
support_types = ('str', 'int', 'bool', 'float')
|
||||
|
||||
def convert_param(original_lists):
|
||||
assert isinstance(original_lists, list), 'The type is not right : {:}'.format(original_lists)
|
||||
ctype, value = original_lists[0], original_lists[1]
|
||||
assert ctype in support_types, 'Ctype={:}, support={:}'.format(ctype, support_types)
|
||||
is_list = isinstance(value, list)
|
||||
if not is_list: value = [value]
|
||||
outs = []
|
||||
for x in value:
|
||||
if ctype == 'int':
|
||||
x = int(x)
|
||||
elif ctype == 'str':
|
||||
x = str(x)
|
||||
elif ctype == 'bool':
|
||||
x = bool(int(x))
|
||||
elif ctype == 'float':
|
||||
x = float(x)
|
||||
else:
|
||||
raise TypeError('Does not know this type : {:}'.format(ctype))
|
||||
outs.append(x)
|
||||
if not is_list: outs = outs[0]
|
||||
return outs
|
||||
|
||||
def load_config(path):
|
||||
path = str(path)
|
||||
assert os.path.exists(path), 'Can not find {:}'.format(path)
|
||||
# Reading data back
|
||||
with open(path, 'r') as f:
|
||||
data = json.load(f)
|
||||
f.close()
|
||||
content = { k: convert_param(v) for k,v in data.items()}
|
||||
Arguments = namedtuple('Configure', ' '.join(content.keys()))
|
||||
content = Arguments(**content)
|
||||
return content
|
||||
@@ -1,16 +1,6 @@
|
||||
##################################################
|
||||
# Copyright (c) Xuanyi Dong [GitHub D-X-Y], 2019 #
|
||||
##################################################
|
||||
from .utils import AverageMeter, RecorderMeter, convert_secs2time
|
||||
from .utils import time_file_str, time_string
|
||||
from .utils import test_imagenet_data
|
||||
from .utils import print_log
|
||||
from .evaluation_utils import obtain_accuracy
|
||||
#from .draw_pts import draw_points
|
||||
from .gpu_manager import GPUManager
|
||||
|
||||
from .save_meta import Save_Meta
|
||||
|
||||
from .model_utils import count_parameters_in_MB
|
||||
from .model_utils import Cutout
|
||||
from .flop_benchmark import print_FLOPs
|
||||
from .gpu_manager import GPUManager
|
||||
from .flop_benchmark import get_model_infos
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import os, sys, time
|
||||
import numpy as np
|
||||
import matplotlib
|
||||
import random
|
||||
matplotlib.use('agg')
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.cm as cm
|
||||
|
||||
def draw_points(points, labels, save_path):
|
||||
title = 'the visualized features'
|
||||
dpi = 100
|
||||
width, height = 1000, 1000
|
||||
legend_fontsize = 10
|
||||
figsize = width / float(dpi), height / float(dpi)
|
||||
fig = plt.figure(figsize=figsize)
|
||||
|
||||
classes = np.unique(labels).tolist()
|
||||
colors = cm.rainbow(np.linspace(0, 1, len(classes)))
|
||||
|
||||
legends = []
|
||||
legendnames = []
|
||||
|
||||
for cls, c in zip(classes, colors):
|
||||
|
||||
indexes = labels == cls
|
||||
ptss = points[indexes, :]
|
||||
x = ptss[:,0]
|
||||
y = ptss[:,1]
|
||||
if cls % 2 == 0: marker = 'x'
|
||||
else: marker = 'o'
|
||||
legend = plt.scatter(x, y, color=c, s=1, marker=marker)
|
||||
legendname = '{:02d}'.format(cls+1)
|
||||
legends.append( legend )
|
||||
legendnames.append( legendname )
|
||||
|
||||
plt.legend(legends, legendnames, scatterpoints=1, ncol=5, fontsize=8)
|
||||
|
||||
if save_path is not None:
|
||||
fig.savefig(save_path, dpi=dpi, bbox_inches='tight')
|
||||
print ('---- save figure {} into {}'.format(title, save_path))
|
||||
plt.close(fig)
|
||||
@@ -3,21 +3,44 @@
|
||||
##################################################
|
||||
# modified from https://github.com/warmspringwinds/pytorch-segmentation-detection/blob/master/pytorch_segmentation_detection/utils/flops_benchmark.py
|
||||
import copy, torch
|
||||
import torch.nn as nn
|
||||
import numpy as np
|
||||
|
||||
def print_FLOPs(model, shape, logs):
|
||||
print_log, log = logs
|
||||
model = copy.deepcopy( model )
|
||||
|
||||
def count_parameters_in_MB(model):
|
||||
if isinstance(model, nn.Module):
|
||||
return np.sum(np.prod(v.size()) for v in model.parameters())/1e6
|
||||
else:
|
||||
return np.sum(np.prod(v.size()) for v in model)/1e6
|
||||
|
||||
|
||||
def get_model_infos(model, shape):
|
||||
#model = copy.deepcopy( model )
|
||||
|
||||
model = add_flops_counting_methods(model)
|
||||
model = model.cuda()
|
||||
#model = model.cuda()
|
||||
model.eval()
|
||||
|
||||
cache_inputs = torch.zeros(*shape).cuda()
|
||||
#cache_inputs = torch.zeros(*shape).cuda()
|
||||
#cache_inputs = torch.zeros(*shape)
|
||||
cache_inputs = torch.rand(*shape)
|
||||
if next(model.parameters()).is_cuda: cache_inputs = cache_inputs.cuda()
|
||||
#print_log('In the calculating function : cache input size : {:}'.format(cache_inputs.size()), log)
|
||||
_ = model(cache_inputs)
|
||||
with torch.no_grad():
|
||||
_____ = model(cache_inputs)
|
||||
FLOPs = compute_average_flops_cost( model ) / 1e6
|
||||
print_log('FLOPs : {:} MB'.format(FLOPs), log)
|
||||
Param = count_parameters_in_MB(model)
|
||||
|
||||
if hasattr(model, 'auxiliary_param'):
|
||||
aux_params = count_parameters_in_MB(model.auxiliary_param())
|
||||
print ('The auxiliary params of this model is : {:}'.format(aux_params))
|
||||
print ('We remove the auxiliary params from the total params ({:}) when counting'.format(Param))
|
||||
Param = Param - aux_params
|
||||
|
||||
#print_log('FLOPs : {:} MB'.format(FLOPs), log)
|
||||
torch.cuda.empty_cache()
|
||||
model.apply( remove_hook_function )
|
||||
return FLOPs, Param
|
||||
|
||||
|
||||
# ---- Public functions
|
||||
@@ -37,8 +60,11 @@ def compute_average_flops_cost(model):
|
||||
"""
|
||||
batches_count = model.__batch_counter__
|
||||
flops_sum = 0
|
||||
#or isinstance(module, torch.nn.AvgPool2d) or isinstance(module, torch.nn.MaxPool2d) \
|
||||
for module in model.modules():
|
||||
if isinstance(module, torch.nn.Conv2d) or isinstance(module, torch.nn.Linear):
|
||||
if isinstance(module, torch.nn.Conv2d) or isinstance(module, torch.nn.Linear) \
|
||||
or isinstance(module, torch.nn.Conv1d) \
|
||||
or hasattr(module, 'calculate_flop_self'):
|
||||
flops_sum += module.__flops__
|
||||
return flops_sum / batches_count
|
||||
|
||||
@@ -54,6 +80,11 @@ def pool_flops_counter_hook(pool_module, inputs, output):
|
||||
pool_module.__flops__ += overall_flops
|
||||
|
||||
|
||||
def self_calculate_flops_counter_hook(self_module, inputs, output):
|
||||
overall_flops = self_module.calculate_flop_self(inputs[0].shape, output.shape)
|
||||
self_module.__flops__ += overall_flops
|
||||
|
||||
|
||||
def fc_flops_counter_hook(fc_module, inputs, output):
|
||||
batch_size = inputs[0].size(0)
|
||||
xin, xout = fc_module.in_features, fc_module.out_features
|
||||
@@ -64,7 +95,24 @@ def fc_flops_counter_hook(fc_module, inputs, output):
|
||||
fc_module.__flops__ += overall_flops
|
||||
|
||||
|
||||
def conv_flops_counter_hook(conv_module, inputs, output):
|
||||
def conv1d_flops_counter_hook(conv_module, inputs, outputs):
|
||||
batch_size = inputs[0].size(0)
|
||||
outL = outputs.shape[-1]
|
||||
[kernel] = conv_module.kernel_size
|
||||
in_channels = conv_module.in_channels
|
||||
out_channels = conv_module.out_channels
|
||||
groups = conv_module.groups
|
||||
conv_per_position_flops = kernel * in_channels * out_channels / groups
|
||||
|
||||
active_elements_count = batch_size * outL
|
||||
overall_flops = conv_per_position_flops * active_elements_count
|
||||
|
||||
if conv_module.bias is not None:
|
||||
overall_flops += out_channels * active_elements_count
|
||||
conv_module.__flops__ += overall_flops
|
||||
|
||||
|
||||
def conv2d_flops_counter_hook(conv_module, inputs, output):
|
||||
batch_size = inputs[0].size(0)
|
||||
output_height, output_width = output.shape[2:]
|
||||
|
||||
@@ -97,14 +145,20 @@ def add_batch_counter_hook_function(module):
|
||||
|
||||
def add_flops_counter_variable_or_reset(module):
|
||||
if isinstance(module, torch.nn.Conv2d) or isinstance(module, torch.nn.Linear) \
|
||||
or isinstance(module, torch.nn.AvgPool2d) or isinstance(module, torch.nn.MaxPool2d):
|
||||
or isinstance(module, torch.nn.Conv1d) \
|
||||
or isinstance(module, torch.nn.AvgPool2d) or isinstance(module, torch.nn.MaxPool2d) \
|
||||
or hasattr(module, 'calculate_flop_self'):
|
||||
module.__flops__ = 0
|
||||
|
||||
|
||||
def add_flops_counter_hook_function(module):
|
||||
if isinstance(module, torch.nn.Conv2d):
|
||||
if not hasattr(module, '__flops_handle__'):
|
||||
handle = module.register_forward_hook(conv_flops_counter_hook)
|
||||
handle = module.register_forward_hook(conv2d_flops_counter_hook)
|
||||
module.__flops_handle__ = handle
|
||||
elif isinstance(module, torch.nn.Conv1d):
|
||||
if not hasattr(module, '__flops_handle__'):
|
||||
handle = module.register_forward_hook(conv1d_flops_counter_hook)
|
||||
module.__flops_handle__ = handle
|
||||
elif isinstance(module, torch.nn.Linear):
|
||||
if not hasattr(module, '__flops_handle__'):
|
||||
@@ -114,3 +168,18 @@ def add_flops_counter_hook_function(module):
|
||||
if not hasattr(module, '__flops_handle__'):
|
||||
handle = module.register_forward_hook(pool_flops_counter_hook)
|
||||
module.__flops_handle__ = handle
|
||||
elif hasattr(module, 'calculate_flop_self'): # self-defined module
|
||||
if not hasattr(module, '__flops_handle__'):
|
||||
handle = module.register_forward_hook(self_calculate_flops_counter_hook)
|
||||
module.__flops_handle__ = handle
|
||||
|
||||
|
||||
def remove_hook_function(module):
|
||||
hookers = ['__batch_counter_handle__', '__flops_handle__']
|
||||
for hooker in hookers:
|
||||
if hasattr(module, hooker):
|
||||
handle = getattr(module, hooker)
|
||||
handle.remove()
|
||||
keys = ['__flops__', '__batch_counter__', '__flops__'] + hookers
|
||||
for ckey in keys:
|
||||
if hasattr(module, ckey): delattr(module, ckey)
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import numpy as np
|
||||
|
||||
|
||||
def count_parameters_in_MB(model):
|
||||
if isinstance(model, nn.Module):
|
||||
return np.sum(np.prod(v.size()) for v in model.parameters())/1e6
|
||||
else:
|
||||
return np.sum(np.prod(v.size()) for v in model)/1e6
|
||||
|
||||
|
||||
class Cutout(object):
|
||||
def __init__(self, length):
|
||||
self.length = length
|
||||
|
||||
def __repr__(self):
|
||||
return ('{name}(length={length})'.format(name=self.__class__.__name__, **self.__dict__))
|
||||
|
||||
def __call__(self, img):
|
||||
h, w = img.size(1), img.size(2)
|
||||
mask = np.ones((h, w), np.float32)
|
||||
y = np.random.randint(h)
|
||||
x = np.random.randint(w)
|
||||
|
||||
y1 = np.clip(y - self.length // 2, 0, h)
|
||||
y2 = np.clip(y + self.length // 2, 0, h)
|
||||
x1 = np.clip(x - self.length // 2, 0, w)
|
||||
x2 = np.clip(x + self.length // 2, 0, w)
|
||||
|
||||
mask[y1: y2, x1: x2] = 0.
|
||||
mask = torch.from_numpy(mask)
|
||||
mask = mask.expand_as(img)
|
||||
img *= mask
|
||||
return img
|
||||
@@ -1,53 +0,0 @@
|
||||
##################################################
|
||||
# Copyright (c) Xuanyi Dong [GitHub D-X-Y], 2019 #
|
||||
##################################################
|
||||
import torch
|
||||
import os, sys
|
||||
import os.path as osp
|
||||
import numpy as np
|
||||
|
||||
def tensor2np(x):
|
||||
if isinstance(x, np.ndarray): return x
|
||||
if x.is_cuda: x = x.cpu()
|
||||
return x.numpy()
|
||||
|
||||
class Save_Meta():
|
||||
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
||||
def __repr__(self):
|
||||
return ('{name}'.format(name=self.__class__.__name__)+'(number of data = {})'.format(len(self)))
|
||||
|
||||
def reset(self):
|
||||
self.predictions = []
|
||||
self.groundtruth = []
|
||||
|
||||
def __len__(self):
|
||||
return len(self.predictions)
|
||||
|
||||
def append(self, _pred, _ground):
|
||||
_pred, _ground = tensor2np(_pred), tensor2np(_ground)
|
||||
assert _ground.shape[0] == _pred.shape[0] and len(_pred.shape) == 2 and len(_ground.shape) == 1, 'The shapes are wrong : {} & {}'.format(_pred.shape, _ground.shape)
|
||||
self.predictions.append(_pred)
|
||||
self.groundtruth.append(_ground)
|
||||
|
||||
def save(self, save_dir, filename, test=True):
|
||||
meta = {'predictions': self.predictions,
|
||||
'groundtruth': self.groundtruth}
|
||||
filename = osp.join(save_dir, filename)
|
||||
torch.save(meta, filename)
|
||||
if test:
|
||||
predictions = np.concatenate(self.predictions)
|
||||
groundtruth = np.concatenate(self.groundtruth)
|
||||
predictions = np.argmax(predictions, axis=1)
|
||||
accuracy = np.sum(groundtruth==predictions) * 100.0 / predictions.size
|
||||
else:
|
||||
accuracy = None
|
||||
print ('save save_meta into {} with accuracy = {}'.format(filename, accuracy))
|
||||
|
||||
def load(self, filename):
|
||||
assert os.path.isfile(filename), '{} is not a file'.format(filename)
|
||||
checkpoint = torch.load(filename)
|
||||
self.predictions = checkpoint['predictions']
|
||||
self.groundtruth = checkpoint['groundtruth']
|
||||
1
lib/xvision/__init__.py
Normal file
1
lib/xvision/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .affine_utils import normalize_points, denormalize_points
|
||||
132
lib/xvision/affine_utils.py
Normal file
132
lib/xvision/affine_utils.py
Normal file
@@ -0,0 +1,132 @@
|
||||
# Copyright (c) Facebook, Inc. and its affiliates.
|
||||
# All rights reserved.
|
||||
#
|
||||
# This source code is licensed under the license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
#
|
||||
#
|
||||
# functions for affine transformation
|
||||
import math, torch
|
||||
import numpy as np
|
||||
import torch.nn.functional as F
|
||||
|
||||
def identity2affine(full=False):
|
||||
if not full:
|
||||
parameters = torch.zeros((2,3))
|
||||
parameters[0, 0] = parameters[1, 1] = 1
|
||||
else:
|
||||
parameters = torch.zeros((3,3))
|
||||
parameters[0, 0] = parameters[1, 1] = parameters[2, 2] = 1
|
||||
return parameters
|
||||
|
||||
def normalize_L(x, L):
|
||||
return -1. + 2. * x / (L-1)
|
||||
|
||||
def denormalize_L(x, L):
|
||||
return (x + 1.0) / 2.0 * (L-1)
|
||||
|
||||
def crop2affine(crop_box, W, H):
|
||||
assert len(crop_box) == 4, 'Invalid crop-box : {:}'.format(crop_box)
|
||||
parameters = torch.zeros(3,3)
|
||||
x1, y1 = normalize_L(crop_box[0], W), normalize_L(crop_box[1], H)
|
||||
x2, y2 = normalize_L(crop_box[2], W), normalize_L(crop_box[3], H)
|
||||
parameters[0,0] = (x2-x1)/2
|
||||
parameters[0,2] = (x2+x1)/2
|
||||
|
||||
parameters[1,1] = (y2-y1)/2
|
||||
parameters[1,2] = (y2+y1)/2
|
||||
parameters[2,2] = 1
|
||||
return parameters
|
||||
|
||||
def scale2affine(scalex, scaley):
|
||||
parameters = torch.zeros(3,3)
|
||||
parameters[0,0] = scalex
|
||||
parameters[1,1] = scaley
|
||||
parameters[2,2] = 1
|
||||
return parameters
|
||||
|
||||
def offset2affine(offx, offy):
|
||||
parameters = torch.zeros(3,3)
|
||||
parameters[0,0] = parameters[1,1] = parameters[2,2] = 1
|
||||
parameters[0,2] = offx
|
||||
parameters[1,2] = offy
|
||||
return parameters
|
||||
|
||||
def horizontalmirror2affine():
|
||||
parameters = torch.zeros(3,3)
|
||||
parameters[0,0] = -1
|
||||
parameters[1,1] = parameters[2,2] = 1
|
||||
return parameters
|
||||
|
||||
# clockwise rotate image = counterclockwise rotate the rectangle
|
||||
# degree is between [0, 360]
|
||||
def rotate2affine(degree):
|
||||
assert degree >= 0 and degree <= 360, 'Invalid degree : {:}'.format(degree)
|
||||
degree = degree / 180 * math.pi
|
||||
parameters = torch.zeros(3,3)
|
||||
parameters[0,0] = math.cos(-degree)
|
||||
parameters[0,1] = -math.sin(-degree)
|
||||
parameters[1,0] = math.sin(-degree)
|
||||
parameters[1,1] = math.cos(-degree)
|
||||
parameters[2,2] = 1
|
||||
return parameters
|
||||
|
||||
# shape is a tuple [H, W]
|
||||
def normalize_points(shape, points):
|
||||
assert (isinstance(shape, tuple) or isinstance(shape, list)) and len(shape) == 2, 'invalid shape : {:}'.format(shape)
|
||||
assert isinstance(points, torch.Tensor) and (points.shape[0] == 2), 'points are wrong : {:}'.format(points.shape)
|
||||
(H, W), points = shape, points.clone()
|
||||
points[0, :] = normalize_L(points[0,:], W)
|
||||
points[1, :] = normalize_L(points[1,:], H)
|
||||
return points
|
||||
|
||||
# shape is a tuple [H, W]
|
||||
def normalize_points_batch(shape, points):
|
||||
assert (isinstance(shape, tuple) or isinstance(shape, list)) and len(shape) == 2, 'invalid shape : {:}'.format(shape)
|
||||
assert isinstance(points, torch.Tensor) and (points.size(-1) == 2), 'points are wrong : {:}'.format(points.shape)
|
||||
(H, W), points = shape, points.clone()
|
||||
x = normalize_L(points[...,0], W)
|
||||
y = normalize_L(points[...,1], H)
|
||||
return torch.stack((x,y), dim=-1)
|
||||
|
||||
# shape is a tuple [H, W]
|
||||
def denormalize_points(shape, points):
|
||||
assert (isinstance(shape, tuple) or isinstance(shape, list)) and len(shape) == 2, 'invalid shape : {:}'.format(shape)
|
||||
assert isinstance(points, torch.Tensor) and (points.shape[0] == 2), 'points are wrong : {:}'.format(points.shape)
|
||||
(H, W), points = shape, points.clone()
|
||||
points[0, :] = denormalize_L(points[0,:], W)
|
||||
points[1, :] = denormalize_L(points[1,:], H)
|
||||
return points
|
||||
|
||||
# shape is a tuple [H, W]
|
||||
def denormalize_points_batch(shape, points):
|
||||
assert (isinstance(shape, tuple) or isinstance(shape, list)) and len(shape) == 2, 'invalid shape : {:}'.format(shape)
|
||||
assert isinstance(points, torch.Tensor) and (points.shape[-1] == 2), 'points are wrong : {:}'.format(points.shape)
|
||||
(H, W), points = shape, points.clone()
|
||||
x = denormalize_L(points[...,0], W)
|
||||
y = denormalize_L(points[...,1], H)
|
||||
return torch.stack((x,y), dim=-1)
|
||||
|
||||
# make target * theta = source
|
||||
def solve2theta(source, target):
|
||||
source, target = source.clone(), target.clone()
|
||||
oks = source[2, :] == 1
|
||||
assert torch.sum(oks).item() >= 3, 'valid points : {:} is short'.format(oks)
|
||||
if target.size(0) == 2: target = torch.cat((target, oks.unsqueeze(0).float()), dim=0)
|
||||
source, target = source[:, oks], target[:, oks]
|
||||
source, target = source.transpose(1,0), target.transpose(1,0)
|
||||
assert source.size(1) == target.size(1) == 3
|
||||
#X, residual, rank, s = np.linalg.lstsq(target.numpy(), source.numpy())
|
||||
#theta = torch.Tensor(X.T[:2, :])
|
||||
X_, qr = torch.gels(source, target)
|
||||
theta = X_[:3, :2].transpose(1, 0)
|
||||
return theta
|
||||
|
||||
# shape = [H,W]
|
||||
def affine2image(image, theta, shape):
|
||||
C, H, W = image.size()
|
||||
theta = theta[:2, :].unsqueeze(0)
|
||||
grid_size = torch.Size([1, C, shape[0], shape[1]])
|
||||
grid = F.affine_grid(theta, grid_size)
|
||||
affI = F.grid_sample(image.unsqueeze(0), grid, mode='bilinear', padding_mode='border')
|
||||
return affI.squeeze(0)
|
||||
Reference in New Issue
Block a user