|
|
|
|
|
|
|
import argparse
|
|
|
|
import time
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
|
|
try:
|
|
|
|
from apex import amp
|
|
|
|
from apex.parallel import DistributedDataParallel as DDP
|
|
|
|
has_apex = True
|
|
|
|
except ImportError:
|
|
|
|
has_apex = False
|
|
|
|
|
|
|
|
from data import Dataset, create_loader, resolve_data_config, FastCollateMixup, mixup_target
|
|
|
|
from models import create_model, resume_checkpoint
|
|
|
|
from utils import *
|
|
|
|
from loss import LabelSmoothingCrossEntropy, SparseLabelCrossEntropy
|
|
|
|
from optim import create_optimizer
|
|
|
|
from scheduler import create_scheduler
|
|
|
|
|
|
|
|
import torch
|
|
|
|
import torch.nn as nn
|
|
|
|
import torch.distributed as dist
|
|
|
|
import torchvision.utils
|
|
|
|
|
|
|
|
torch.backends.cudnn.benchmark = True
|
|
|
|
|
|
|
|
parser = argparse.ArgumentParser(description='Training')
|
|
|
|
parser.add_argument('data', metavar='DIR',
|
|
|
|
help='path to dataset')
|
|
|
|
parser.add_argument('--model', default='resnet101', type=str, metavar='MODEL',
|
|
|
|
help='Name of model to train (default: "countception"')
|
|
|
|
parser.add_argument('--num-classes', type=int, default=1000, metavar='N',
|
|
|
|
help='number of label classes (default: 1000)')
|
|
|
|
parser.add_argument('--opt', default='sgd', type=str, metavar='OPTIMIZER',
|
|
|
|
help='Optimizer (default: "sgd"')
|
|
|
|
parser.add_argument('--opt-eps', default=1e-8, type=float, metavar='EPSILON',
|
|
|
|
help='Optimizer Epsilon (default: 1e-8)')
|
|
|
|
parser.add_argument('--gp', default='avg', type=str, metavar='POOL',
|
|
|
|
help='Type of global pool, "avg", "max", "avgmax", "avgmaxc" (default: "avg")')
|
|
|
|
parser.add_argument('--tta', type=int, default=0, metavar='N',
|
|
|
|
help='Test/inference time augmentation (oversampling) factor. 0=None (default: 0)')
|
|
|
|
parser.add_argument('--pretrained', action='store_true', default=False,
|
|
|
|
help='Start with pretrained version of specified network (if avail)')
|
|
|
|
parser.add_argument('--img-size', type=int, default=224, metavar='N',
|
|
|
|
help='Image patch size (default: 224)')
|
|
|
|
parser.add_argument('--mean', type=float, nargs='+', default=None, metavar='MEAN',
|
|
|
|
help='Override mean pixel value of dataset')
|
|
|
|
parser.add_argument('--std', type=float, nargs='+', default=None, metavar='STD',
|
|
|
|
help='Override std deviation of of dataset')
|
|
|
|
parser.add_argument('--interpolation', default='', type=str, metavar='NAME',
|
|
|
|
help='Image resize interpolation type (overrides model)')
|
|
|
|
parser.add_argument('-b', '--batch-size', type=int, default=32, metavar='N',
|
|
|
|
help='input batch size for training (default: 32)')
|
|
|
|
parser.add_argument('-s', '--initial-batch-size', type=int, default=0, metavar='N',
|
|
|
|
help='initial input batch size for training (default: 0)')
|
|
|
|
parser.add_argument('--epochs', type=int, default=200, metavar='N',
|
|
|
|
help='number of epochs to train (default: 2)')
|
|
|
|
parser.add_argument('--start-epoch', default=None, type=int, metavar='N',
|
|
|
|
help='manual epoch number (useful on restarts)')
|
|
|
|
parser.add_argument('--decay-epochs', type=int, default=30, metavar='N',
|
|
|
|
help='epoch interval to decay LR')
|
|
|
|
parser.add_argument('--warmup-epochs', type=int, default=3, metavar='N',
|
|
|
|
help='epochs to warmup LR, if scheduler supports')
|
|
|
|
parser.add_argument('--decay-rate', '--dr', type=float, default=0.1, metavar='RATE',
|
|
|
|
help='LR decay rate (default: 0.1)')
|
|
|
|
parser.add_argument('--sched', default='step', type=str, metavar='SCHEDULER',
|
|
|
|
help='LR scheduler (default: "step"')
|
|
|
|
parser.add_argument('--drop', type=float, default=0.0, metavar='DROP',
|
|
|
|
help='Dropout rate (default: 0.)')
|
|
|
|
parser.add_argument('--reprob', type=float, default=0., metavar='PCT',
|
|
|
|
help='Random erase prob (default: 0.)')
|
|
|
|
parser.add_argument('--remode', type=str, default='const',
|
|
|
|
help='Random erase mode (default: "const")')
|
|
|
|
parser.add_argument('--lr', type=float, default=0.01, metavar='LR',
|
|
|
|
help='learning rate (default: 0.01)')
|
|
|
|
parser.add_argument('--warmup-lr', type=float, default=0.0001, metavar='LR',
|
|
|
|
help='warmup learning rate (default: 0.0001)')
|
|
|
|
parser.add_argument('--momentum', type=float, default=0.9, metavar='M',
|
|
|
|
help='SGD momentum (default: 0.9)')
|
|
|
|
parser.add_argument('--weight-decay', type=float, default=0.0001,
|
|
|
|
help='weight decay (default: 0.0001)')
|
|
|
|
parser.add_argument('--mixup', type=float, default=0.0,
|
|
|
|
help='mixup alpha, mixup enabled if > 0. (default: 0.)')
|
|
|
|
parser.add_argument('--mixup-off-epoch', default=0, type=int, metavar='N',
|
|
|
|
help='turn off mixup after this epoch, disabled if 0 (default: 0)')
|
|
|
|
parser.add_argument('--smoothing', type=float, default=0.1,
|
|
|
|
help='label smoothing (default: 0.1)')
|
|
|
|
parser.add_argument('--bn-tf', action='store_true', default=False,
|
|
|
|
help='Use Tensorflow BatchNorm defaults for models that support it (default: False)')
|
|
|
|
parser.add_argument('--bn-momentum', type=float, default=None,
|
|
|
|
help='BatchNorm momentum override (if not None)')
|
|
|
|
parser.add_argument('--bn-eps', type=float, default=None,
|
|
|
|
help='BatchNorm epsilon override (if not None)')
|
|
|
|
parser.add_argument('--seed', type=int, default=42, metavar='S',
|
|
|
|
help='random seed (default: 42)')
|
|
|
|
parser.add_argument('--log-interval', type=int, default=50, metavar='N',
|
|
|
|
help='how many batches to wait before logging training status')
|
|
|
|
parser.add_argument('--recovery-interval', type=int, default=1000, metavar='N',
|
|
|
|
help='how many batches to wait before writing recovery checkpoint')
|
|
|
|
parser.add_argument('-j', '--workers', type=int, default=4, metavar='N',
|
|
|
|
help='how many training processes to use (default: 1)')
|
|
|
|
parser.add_argument('--num-gpu', type=int, default=1,
|
|
|
|
help='Number of GPUS to use')
|
|
|
|
parser.add_argument('--initial-checkpoint', default='', type=str, metavar='PATH',
|
|
|
|
help='path to init checkpoint (default: none)')
|
|
|
|
parser.add_argument('--resume', default='', type=str, metavar='PATH',
|
|
|
|
help='path to latest checkpoint (default: none)')
|
|
|
|
parser.add_argument('--save-images', action='store_true', default=False,
|
|
|
|
help='save images of input bathes every log interval for debugging')
|
|
|
|
parser.add_argument('--amp', action='store_true', default=False,
|
|
|
|
help='use NVIDIA amp for mixed precision training')
|
|
|
|
parser.add_argument('--no-prefetcher', action='store_true', default=False,
|
|
|
|
help='disable fast prefetcher')
|
|
|
|
parser.add_argument('--output', default='', type=str, metavar='PATH',
|
|
|
|
help='path to output folder (default: none, current dir)')
|
|
|
|
parser.add_argument('--eval-metric', default='prec1', type=str, metavar='EVAL_METRIC',
|
|
|
|
help='Best metric (default: "prec1"')
|
|
|
|
parser.add_argument("--local_rank", default=0, type=int)
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
|
args.prefetcher = not args.no_prefetcher
|
|
|
|
args.distributed = False
|
|
|
|
if 'WORLD_SIZE' in os.environ:
|
|
|
|
args.distributed = int(os.environ['WORLD_SIZE']) > 1
|
|
|
|
if args.distributed and args.num_gpu > 1:
|
|
|
|
print('Using more than one GPU per process in distributed mode is not allowed. Setting num_gpu to 1.')
|
|
|
|
args.num_gpu = 1
|
|
|
|
|
|
|
|
args.device = 'cuda:0'
|
|
|
|
args.world_size = 1
|
|
|
|
r = -1
|
|
|
|
if args.distributed:
|
|
|
|
args.num_gpu = 1
|
|
|
|
args.device = 'cuda:%d' % args.local_rank
|
|
|
|
torch.cuda.set_device(args.local_rank)
|
|
|
|
torch.distributed.init_process_group(backend='nccl',
|
|
|
|
init_method='env://')
|
|
|
|
args.world_size = torch.distributed.get_world_size()
|
|
|
|
r = torch.distributed.get_rank()
|
|
|
|
|
|
|
|
if args.distributed:
|
|
|
|
print('Training in distributed mode with multiple processes, 1 GPU per process. Process %d, total %d.'
|
|
|
|
% (r, args.world_size))
|
|
|
|
else:
|
|
|
|
print('Training with a single process on %d GPUs.' % args.num_gpu)
|
|
|
|
|
|
|
|
# FIXME seed handling for multi-process distributed?
|
|
|
|
torch.manual_seed(args.seed)
|
|
|
|
|
|
|
|
output_dir = ''
|
|
|
|
if args.local_rank == 0:
|
|
|
|
if args.output:
|
|
|
|
output_base = args.output
|
|
|
|
else:
|
|
|
|
output_base = './output'
|
|
|
|
exp_name = '-'.join([
|
|
|
|
datetime.now().strftime("%Y%m%d-%H%M%S"),
|
|
|
|
args.model,
|
|
|
|
str(args.img_size)])
|
|
|
|
output_dir = get_outdir(output_base, 'train', exp_name)
|
|
|
|
|
|
|
|
model = create_model(
|
|
|
|
args.model,
|
|
|
|
pretrained=args.pretrained,
|
|
|
|
num_classes=args.num_classes,
|
|
|
|
drop_rate=args.drop,
|
|
|
|
global_pool=args.gp,
|
|
|
|
bn_tf=args.bn_tf,
|
|
|
|
bn_momentum=args.bn_momentum,
|
|
|
|
bn_eps=args.bn_eps,
|
|
|
|
checkpoint_path=args.initial_checkpoint)
|
|
|
|
|
|
|
|
print('Model %s created, param count: %d' %
|
|
|
|
(args.model, sum([m.numel() for m in model.parameters()])))
|
|
|
|
|
|
|
|
data_config = resolve_data_config(model, args, verbose=args.local_rank == 0)
|
|
|
|
|
|
|
|
# optionally resume from a checkpoint
|
|
|
|
start_epoch = 0
|
|
|
|
optimizer_state = None
|
|
|
|
if args.resume:
|
|
|
|
optimizer_state, start_epoch = resume_checkpoint(model, args.resume, args.start_epoch)
|
|
|
|
|
|
|
|
if args.num_gpu > 1:
|
|
|
|
if args.amp:
|
|
|
|
print('Warning: AMP does not work well with nn.DataParallel, disabling. '
|
|
|
|
'Use distributed mode for multi-GPU AMP.')
|
|
|
|
args.amp = False
|
|
|
|
model = nn.DataParallel(model, device_ids=list(range(args.num_gpu))).cuda()
|
|
|
|
else:
|
|
|
|
model.cuda()
|
|
|
|
|
|
|
|
optimizer = create_optimizer(args, model)
|
|
|
|
if optimizer_state is not None:
|
|
|
|
optimizer.load_state_dict(optimizer_state)
|
|
|
|
|
|
|
|
if has_apex and args.amp:
|
|
|
|
model, optimizer = amp.initialize(model, optimizer, opt_level='O1')
|
|
|
|
use_amp = True
|
|
|
|
print('AMP enabled')
|
|
|
|
else:
|
|
|
|
use_amp = False
|
|
|
|
print('AMP disabled')
|
|
|
|
|
|
|
|
if args.distributed:
|
|
|
|
model = DDP(model, delay_allreduce=True)
|
|
|
|
|
|
|
|
lr_scheduler, num_epochs = create_scheduler(args, optimizer)
|
|
|
|
if start_epoch > 0:
|
|
|
|
lr_scheduler.step(start_epoch)
|
|
|
|
if args.local_rank == 0:
|
|
|
|
print('Scheduled epochs: ', num_epochs)
|
|
|
|
|
|
|
|
train_dir = os.path.join(args.data, 'train')
|
|
|
|
if not os.path.exists(train_dir):
|
|
|
|
print('Error: training folder does not exist at: %s' % train_dir)
|
|
|
|
exit(1)
|
|
|
|
dataset_train = Dataset(train_dir)
|
|
|
|
|
|
|
|
collate_fn = None
|
|
|
|
if args.prefetcher and args.mixup > 0:
|
|
|
|
collate_fn = FastCollateMixup(args.mixup, args.smoothing, args.num_classes)
|
|
|
|
|
|
|
|
loader_train = create_loader(
|
|
|
|
dataset_train,
|
|
|
|
input_size=data_config['input_size'],
|
|
|
|
batch_size=args.batch_size,
|
|
|
|
is_training=True,
|
|
|
|
use_prefetcher=args.prefetcher,
|
|
|
|
rand_erase_prob=args.reprob,
|
|
|
|
rand_erase_mode=args.remode,
|
|
|
|
interpolation='random', # FIXME cleanly resolve this? data_config['interpolation'],
|
|
|
|
mean=data_config['mean'],
|
|
|
|
std=data_config['std'],
|
|
|
|
num_workers=args.workers,
|
|
|
|
distributed=args.distributed,
|
|
|
|
collate_fn=collate_fn,
|
|
|
|
)
|
|
|
|
|
|
|
|
eval_dir = os.path.join(args.data, 'validation')
|
|
|
|
if not os.path.isdir(eval_dir):
|
|
|
|
print('Error: validation folder does not exist at: %s' % eval_dir)
|
|
|
|
exit(1)
|
|
|
|
dataset_eval = Dataset(eval_dir)
|
|
|
|
|
|
|
|
loader_eval = create_loader(
|
|
|
|
dataset_eval,
|
|
|
|
input_size=data_config['input_size'],
|
|
|
|
batch_size=4 * args.batch_size,
|
|
|
|
is_training=False,
|
|
|
|
use_prefetcher=args.prefetcher,
|
|
|
|
interpolation=data_config['interpolation'],
|
|
|
|
mean=data_config['mean'],
|
|
|
|
std=data_config['std'],
|
|
|
|
num_workers=args.workers,
|
|
|
|
distributed=args.distributed,
|
|
|
|
)
|
|
|
|
|
|
|
|
if args.mixup > 0.:
|
|
|
|
# smoothing is handled with mixup label transform
|
|
|
|
train_loss_fn = SparseLabelCrossEntropy().cuda()
|
|
|
|
validate_loss_fn = nn.CrossEntropyLoss().cuda()
|
|
|
|
elif args.smoothing:
|
|
|
|
train_loss_fn = LabelSmoothingCrossEntropy(smoothing=args.smoothing).cuda()
|
|
|
|
validate_loss_fn = nn.CrossEntropyLoss().cuda()
|
|
|
|
else:
|
|
|
|
train_loss_fn = nn.CrossEntropyLoss().cuda()
|
|
|
|
validate_loss_fn = train_loss_fn
|
|
|
|
|
|
|
|
eval_metric = args.eval_metric
|
|
|
|
saver = None
|
|
|
|
if output_dir:
|
|
|
|
decreasing = True if eval_metric == 'loss' else False
|
|
|
|
saver = CheckpointSaver(checkpoint_dir=output_dir, decreasing=decreasing)
|
|
|
|
best_metric = None
|
|
|
|
best_epoch = None
|
|
|
|
try:
|
|
|
|
for epoch in range(start_epoch, num_epochs):
|
|
|
|
if args.distributed:
|
|
|
|
loader_train.sampler.set_epoch(epoch)
|
|
|
|
|
|
|
|
train_metrics = train_epoch(
|
|
|
|
epoch, model, loader_train, optimizer, train_loss_fn, args,
|
|
|
|
lr_scheduler=lr_scheduler, saver=saver, output_dir=output_dir, use_amp=use_amp)
|
|
|
|
|
|
|
|
eval_metrics = validate(
|
|
|
|
model, loader_eval, validate_loss_fn, args)
|
|
|
|
|
|
|
|
if lr_scheduler is not None:
|
|
|
|
lr_scheduler.step(epoch, eval_metrics[eval_metric])
|
|
|
|
|
|
|
|
update_summary(
|
|
|
|
epoch, train_metrics, eval_metrics, os.path.join(output_dir, 'summary.csv'),
|
|
|
|
write_header=best_metric is None)
|
|
|
|
|
|
|
|
if saver is not None:
|
|
|
|
# save proper checkpoint with eval metric
|
|
|
|
best_metric, best_epoch = saver.save_checkpoint({
|
|
|
|
'epoch': epoch + 1,
|
|
|
|
'arch': args.model,
|
|
|
|
'state_dict': model.state_dict(),
|
|
|
|
'optimizer': optimizer.state_dict(),
|
|
|
|
'args': args,
|
|
|
|
},
|
|
|
|
epoch=epoch + 1,
|
|
|
|
metric=eval_metrics[eval_metric])
|
|
|
|
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
pass
|
|
|
|
if best_metric is not None:
|
|
|
|
print('*** Best metric: {0} (epoch {1})'.format(best_metric, best_epoch))
|
|
|
|
|
|
|
|
|
|
|
|
def train_epoch(
|
|
|
|
epoch, model, loader, optimizer, loss_fn, args,
|
|
|
|
lr_scheduler=None, saver=None, output_dir='', use_amp=False):
|
|
|
|
|
|
|
|
if args.prefetcher and args.mixup > 0 and loader.mixup_enabled:
|
|
|
|
if args.mixup_off_epoch and epoch >= args.mixup_off_epoch:
|
|
|
|
loader.mixup_enabled = False
|
|
|
|
|
|
|
|
batch_time_m = AverageMeter()
|
|
|
|
data_time_m = AverageMeter()
|
|
|
|
losses_m = AverageMeter()
|
|
|
|
|
|
|
|
model.train()
|
|
|
|
|
|
|
|
end = time.time()
|
|
|
|
last_idx = len(loader) - 1
|
|
|
|
num_updates = epoch * len(loader)
|
|
|
|
for batch_idx, (input, target) in enumerate(loader):
|
|
|
|
last_batch = batch_idx == last_idx
|
|
|
|
data_time_m.update(time.time() - end)
|
|
|
|
if not args.prefetcher:
|
|
|
|
input = input.cuda()
|
|
|
|
target = target.cuda()
|
|
|
|
if args.mixup > 0.:
|
|
|
|
lam = 1.
|
|
|
|
if not args.mixup_off_epoch or epoch < args.mixup_off_epoch:
|
|
|
|
lam = np.random.beta(args.mixup, args.mixup)
|
|
|
|
input.mul_(lam).add_(1 - lam, input.flip(0))
|
|
|
|
target = mixup_target(target, args.num_classes, lam, args.smoothing)
|
|
|
|
|
|
|
|
output = model(input)
|
|
|
|
|
|
|
|
loss = loss_fn(output, target)
|
|
|
|
if not args.distributed:
|
|
|
|
losses_m.update(loss.item(), input.size(0))
|
|
|
|
|
|
|
|
optimizer.zero_grad()
|
|
|
|
if use_amp:
|
|
|
|
with amp.scale_loss(loss, optimizer) as scaled_loss:
|
|
|
|
scaled_loss.backward()
|
|
|
|
else:
|
|
|
|
loss.backward()
|
|
|
|
optimizer.step()
|
|
|
|
|
|
|
|
torch.cuda.synchronize()
|
|
|
|
num_updates += 1
|
|
|
|
|
|
|
|
batch_time_m.update(time.time() - end)
|
|
|
|
if last_batch or batch_idx % args.log_interval == 0:
|
|
|
|
lrl = [param_group['lr'] for param_group in optimizer.param_groups]
|
|
|
|
lr = sum(lrl) / len(lrl)
|
|
|
|
|
|
|
|
if args.distributed:
|
|
|
|
reduced_loss = reduce_tensor(loss.data, args.world_size)
|
|
|
|
losses_m.update(reduced_loss.item(), input.size(0))
|
|
|
|
|
|
|
|
if args.local_rank == 0:
|
|
|
|
print('Train: {} [{}/{} ({:.0f}%)] '
|
|
|
|
'Loss: {loss.val:.6f} ({loss.avg:.4f}) '
|
|
|
|
'Time: {batch_time.val:.3f}s, {rate:.3f}/s '
|
|
|
|
'({batch_time.avg:.3f}s, {rate_avg:.3f}/s) '
|
|
|
|
'LR: {lr:.4f} '
|
|
|
|
'Data: {data_time.val:.3f} ({data_time.avg:.3f})'.format(
|
|
|
|
epoch,
|
|
|
|
batch_idx, len(loader),
|
|
|
|
100. * batch_idx / last_idx,
|
|
|
|
loss=losses_m,
|
|
|
|
batch_time=batch_time_m,
|
|
|
|
rate=input.size(0) * args.world_size / batch_time_m.val,
|
|
|
|
rate_avg=input.size(0) * args.world_size / batch_time_m.avg,
|
|
|
|
lr=lr,
|
|
|
|
data_time=data_time_m))
|
|
|
|
|
|
|
|
if args.save_images and output_dir:
|
|
|
|
torchvision.utils.save_image(
|
|
|
|
input,
|
|
|
|
os.path.join(output_dir, 'train-batch-%d.jpg' % batch_idx),
|
|
|
|
padding=0,
|
|
|
|
normalize=True)
|
|
|
|
|
|
|
|
if args.local_rank == 0 and (
|
|
|
|
saver is not None and last_batch or (batch_idx + 1) % args.recovery_interval == 0):
|
|
|
|
save_epoch = epoch + 1 if last_batch else epoch
|
|
|
|
saver.save_recovery({
|
|
|
|
'epoch': save_epoch,
|
|
|
|
'arch': args.model,
|
|
|
|
'state_dict': model.state_dict(),
|
|
|
|
'optimizer': optimizer.state_dict(),
|
|
|
|
'args': args,
|
|
|
|
},
|
|
|
|
epoch=save_epoch,
|
|
|
|
batch_idx=batch_idx)
|
|
|
|
|
|
|
|
if lr_scheduler is not None:
|
|
|
|
lr_scheduler.step_update(num_updates=num_updates, metric=losses_m.avg)
|
|
|
|
|
|
|
|
end = time.time()
|
|
|
|
|
|
|
|
return OrderedDict([('loss', losses_m.avg)])
|
|
|
|
|
|
|
|
|
|
|
|
def validate(model, loader, loss_fn, args):
|
|
|
|
batch_time_m = AverageMeter()
|
|
|
|
losses_m = AverageMeter()
|
|
|
|
prec1_m = AverageMeter()
|
|
|
|
prec5_m = AverageMeter()
|
|
|
|
|
|
|
|
model.eval()
|
|
|
|
|
|
|
|
end = time.time()
|
|
|
|
last_idx = len(loader) - 1
|
|
|
|
with torch.no_grad():
|
|
|
|
for batch_idx, (input, target) in enumerate(loader):
|
|
|
|
last_batch = batch_idx == last_idx
|
|
|
|
|
|
|
|
output = model(input)
|
|
|
|
if isinstance(output, (tuple, list)):
|
|
|
|
output = output[0]
|
|
|
|
|
|
|
|
# augmentation reduction
|
|
|
|
reduce_factor = args.tta
|
|
|
|
if reduce_factor > 1:
|
|
|
|
output = output.unfold(0, reduce_factor, reduce_factor).mean(dim=2)
|
|
|
|
target = target[0:target.size(0):reduce_factor]
|
|
|
|
|
|
|
|
loss = loss_fn(output, target)
|
|
|
|
prec1, prec5 = accuracy(output, target, topk=(1, 5))
|
|
|
|
|
|
|
|
if args.distributed:
|
|
|
|
reduced_loss = reduce_tensor(loss.data, args.world_size)
|
|
|
|
prec1 = reduce_tensor(prec1, args.world_size)
|
|
|
|
prec5 = reduce_tensor(prec5, args.world_size)
|
|
|
|
else:
|
|
|
|
reduced_loss = loss.data
|
|
|
|
|
|
|
|
torch.cuda.synchronize()
|
|
|
|
|
|
|
|
losses_m.update(reduced_loss.item(), input.size(0))
|
|
|
|
prec1_m.update(prec1.item(), output.size(0))
|
|
|
|
prec5_m.update(prec5.item(), output.size(0))
|
|
|
|
|
|
|
|
batch_time_m.update(time.time() - end)
|
|
|
|
end = time.time()
|
|
|
|
if args.local_rank == 0 and (last_batch or batch_idx % args.log_interval == 0):
|
|
|
|
print('Test: [{0}/{1}]\t'
|
|
|
|
'Time {batch_time.val:.3f} ({batch_time.avg:.3f}) '
|
|
|
|
'Loss {loss.val:.4f} ({loss.avg:.4f}) '
|
|
|
|
'Prec@1 {top1.val:.4f} ({top1.avg:.4f}) '
|
|
|
|
'Prec@5 {top5.val:.4f} ({top5.avg:.4f})'.format(
|
|
|
|
batch_idx, last_idx,
|
|
|
|
batch_time=batch_time_m, loss=losses_m,
|
|
|
|
top1=prec1_m, top5=prec5_m))
|
|
|
|
|
|
|
|
metrics = OrderedDict([('loss', losses_m.avg), ('prec1', prec1_m.avg), ('prec5', prec5_m.avg)])
|
|
|
|
|
|
|
|
return metrics
|
|
|
|
|
|
|
|
|
|
|
|
def reduce_tensor(tensor, n):
|
|
|
|
rt = tensor.clone()
|
|
|
|
dist.all_reduce(rt, op=dist.ReduceOp.SUM)
|
|
|
|
rt /= n
|
|
|
|
return rt
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|