├── LICENSE ├── MobileNetV2.py ├── Readme ├── eval.py └── mobilenetv2_Top1_71.806_Top2_90.410.pth.tar /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2018, ericsun99 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /MobileNetV2.py: -------------------------------------------------------------------------------- 1 | import torch.nn as nn 2 | import math 3 | 4 | ###V2<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 5 | 6 | def conv_bn(inp, oup, stride): 7 | return nn.Sequential( 8 | nn.Conv2d(inp, oup, 3, stride, 1, bias=False), 9 | nn.BatchNorm2d(oup), 10 | nn.ReLU6(inplace=True) 11 | ) 12 | 13 | 14 | def conv_1x1_bn(inp, oup): 15 | return nn.Sequential( 16 | nn.Conv2d(inp, oup, 1, 1, 0, bias=False), 17 | nn.BatchNorm2d(oup), 18 | nn.ReLU6(inplace=True) 19 | ) 20 | 21 | 22 | class InvertedResidual(nn.Module): 23 | def __init__(self, inp, oup, stride, expand_ratio): 24 | super(InvertedResidual, self).__init__() 25 | self.stride = stride 26 | 27 | self.use_res_connect = self.stride == 1 and inp == oup 28 | 29 | self.conv = nn.Sequential( 30 | # pw 31 | nn.Conv2d(inp, inp * expand_ratio, 1, 1, 0, bias=False), 32 | nn.BatchNorm2d(inp * expand_ratio), 33 | nn.ReLU6(inplace=True), 34 | # dw 35 | nn.Conv2d(inp * expand_ratio, inp * expand_ratio, 3, stride, 1, groups=inp * expand_ratio, bias=False), 36 | nn.BatchNorm2d(inp * expand_ratio), 37 | nn.ReLU6(inplace=True), 38 | # pw-linear 39 | nn.Conv2d(inp * expand_ratio, oup, 1, 1, 0, bias=False), 40 | nn.BatchNorm2d(oup), 41 | ) 42 | 43 | def forward(self, x): 44 | if self.use_res_connect: 45 | return x + self.conv(x) 46 | else: 47 | return self.conv(x) 48 | 49 | 50 | class MobileNetV2(nn.Module): 51 | def __init__(self, n_class=1000, input_size=224, width_mult=1.): 52 | super(MobileNetV2, self).__init__() 53 | # setting of inverted residual blocks 54 | self.interverted_residual_setting = [ 55 | # t, c, n, s 56 | [1, 16, 1, 1], 57 | [6, 24, 2, 2], 58 | [6, 32, 3, 2], 59 | [6, 64, 4, 2], 60 | [6, 96, 3, 1], 61 | [6, 160, 3, 2], 62 | [6, 320, 1, 1], 63 | ] 64 | 65 | # building first layer 66 | input_channel = int(32 * width_mult) 67 | self.last_channel = int(1280 * width_mult) if width_mult > 1.0 else 1280 68 | self.features = [conv_bn(3, input_channel, 2)] 69 | # building inverted residual blocks 70 | for t, c, n, s in self.interverted_residual_setting: 71 | output_channel = int(c * width_mult) 72 | for i in range(n): 73 | if i == 0: 74 | self.features.append(InvertedResidual(input_channel, output_channel, s, t)) 75 | else: 76 | self.features.append(InvertedResidual(input_channel, output_channel, 1, t)) 77 | input_channel = output_channel 78 | # building last several layers 79 | self.features.append(conv_1x1_bn(input_channel, self.last_channel)) 80 | self.features.append(nn.AvgPool2d(input_size/32)) 81 | # make it nn.Sequential 82 | self.features = nn.Sequential(*self.features) 83 | 84 | # building classifier 85 | self.classifier = nn.Sequential( 86 | nn.Dropout(), 87 | nn.Linear(self.last_channel, n_class), 88 | ) 89 | 90 | 91 | def forward(self, x): 92 | x = self.features(x) 93 | x = x.view(-1, self.last_channel) 94 | x = self.classifier(x) 95 | return x 96 | 97 | -------------------------------------------------------------------------------- /Readme: -------------------------------------------------------------------------------- 1 | MobileNet-V2-Pytorch 2 | Introduction 3 | 4 | This is a Pytorch implementation of Google's MobileNet-V2. For details, please read the following papers: 5 | Inverted Residuals and Linear Bottlenecks: Mobile Networks for Classification, Detection and Segmentation 6 | 7 | 8 | Pretrained Models on ImageNet 9 | 10 | We provide pretrained MobileNet-V2 models on ImageNet, which achieve slightly better accuracy rates than the original ones reported in the paper. 11 | 12 | The top-1/5 accuracy rates by using single center crop (crop size: 224x224, image size: 256xN): 13 | Network Top-1 Top-5 Top-1(reported in the paper) 14 | MobileNet v2 71.806 90.410 71.70 15 | 16 | 17 | Evaluate Models 18 | python eval.py -a mobilenetv2 --evaluate ./mobilenetv2_Top1_71.806_Top2_90.410.pth.tar ./ILSVRC2012/ 19 | 20 | Dataset prepare 21 | Refer to https://github.com/facebook/fb.resnet.torch/blob/master/INSTALL.md#download-the-imagenet-dataset 22 | -------------------------------------------------------------------------------- /eval.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import os 3 | import shutil 4 | import time 5 | 6 | import torch 7 | import torch.nn as nn 8 | import torch.nn.parallel 9 | import torch.backends.cudnn as cudnn 10 | import torch.distributed as dist 11 | import torch.optim 12 | import torch.utils.data 13 | import torch.utils.data.distributed 14 | import torchvision.transforms as transforms 15 | import torchvision.datasets as datasets 16 | import torchvision.models as models 17 | from MobileNetV2 import MobileNetV2 18 | 19 | model_names = sorted(name for name in models.__dict__ 20 | if name.islower() and not name.startswith("__") 21 | and callable(models.__dict__[name])) 22 | 23 | model_names.append('mobilenetv1') 24 | model_names.append('mobilenetv2') 25 | 26 | parser = argparse.ArgumentParser(description='PyTorch ImageNet Training') 27 | parser.add_argument('data', metavar='DIR', 28 | help='path to dataset') 29 | parser.add_argument('--arch', '-a', metavar='ARCH', default='resnet18', 30 | choices=model_names, 31 | help='model architecture: ' + 32 | ' | '.join(model_names) + 33 | ' (default: resnet18)') 34 | parser.add_argument('-j', '--workers', default=4, type=int, metavar='N', 35 | help='number of data loading workers (default: 4)') 36 | parser.add_argument('--epochs', default=90, type=int, metavar='N', 37 | help='number of total epochs to run') 38 | parser.add_argument('--start-epoch', default=0, type=int, metavar='N', 39 | help='manual epoch number (useful on restarts)') 40 | parser.add_argument('-b', '--batch-size', default=256, type=int, 41 | metavar='N', help='mini-batch size (default: 256)') 42 | parser.add_argument('--lr', '--learning-rate', default=0.1, type=float, 43 | metavar='LR', help='initial learning rate') 44 | parser.add_argument('--momentum', default=0.9, type=float, metavar='M', 45 | help='momentum') 46 | parser.add_argument('--weight-decay', '--wd', default=1e-4, type=float, 47 | metavar='W', help='weight decay (default: 1e-4)') 48 | parser.add_argument('--print-freq', '-p', default=10, type=int, 49 | metavar='N', help='print frequency (default: 10)') 50 | parser.add_argument('--resume', default='', type=str, metavar='PATH', 51 | help='path to latest checkpoint (default: none)') 52 | parser.add_argument('--evaluate', default='', type=str, metavar='PATH', 53 | help='path to evaluate model (default: none)') 54 | parser.add_argument('--pretrained', dest='pretrained', action='store_true', 55 | help='use pre-trained model') 56 | parser.add_argument('--world-size', default=1, type=int, 57 | help='number of distributed processes') 58 | parser.add_argument('--dist-url', default='tcp://224.66.41.62:23456', type=str, 59 | help='url used to set up distributed training') 60 | parser.add_argument('--dist-backend', default='gloo', type=str, 61 | help='distributed backend') 62 | 63 | best_prec1 = 0 64 | 65 | 66 | def main(): 67 | global args, best_prec1 68 | args = parser.parse_args() 69 | 70 | args.distributed = args.world_size > 1 71 | 72 | if args.distributed: 73 | dist.init_process_group(backend=args.dist_backend, init_method=args.dist_url, 74 | world_size=args.world_size) 75 | 76 | # create model 77 | if args.pretrained: 78 | print("=> using pre-trained model '{}'".format(args.arch)) 79 | model = models.__dict__[args.arch](pretrained=True) 80 | else: 81 | print("=> creating model '{}'".format(args.arch)) 82 | if args.arch.startswith('mobilenetv1'): 83 | model = MobileNetV1() 84 | elif args.arch.startswith('mobilenetv2'): 85 | model = MobileNetV2() 86 | else: 87 | model = models.__dict__[args.arch]() 88 | 89 | print(model) 90 | 91 | if not args.distributed: 92 | if args.arch.startswith('alexnet') or args.arch.startswith('vgg'): 93 | model.features = torch.nn.DataParallel(model.features) 94 | model.cuda() 95 | else: 96 | model = torch.nn.DataParallel(model).cuda() 97 | else: 98 | model.cuda() 99 | model = torch.nn.parallel.DistributedDataParallel(model) 100 | 101 | # define loss function (criterion) and optimizer 102 | criterion = nn.CrossEntropyLoss().cuda() 103 | 104 | optimizer = torch.optim.SGD(model.parameters(), args.lr, 105 | momentum=args.momentum, 106 | weight_decay=args.weight_decay) 107 | 108 | if args.evaluate: 109 | if os.path.isfile(args.evaluate): 110 | print("=> loading model '{}'".format(args.evaluate)) 111 | model.load_state_dict(torch.load(args.evaluate)) 112 | else: 113 | print("=> no model found at '{}'".format(args.evaluate)) 114 | 115 | # optionally resume from a checkpoint 116 | if args.resume: 117 | if os.path.isfile(args.resume): 118 | print("=> loading checkpoint '{}'".format(args.resume)) 119 | checkpoint = torch.load(args.resume) 120 | args.start_epoch = checkpoint['epoch'] 121 | best_prec1 = checkpoint['best_prec1'] 122 | model.load_state_dict(checkpoint['state_dict']) 123 | optimizer.load_state_dict(checkpoint['optimizer']) 124 | print("=> loaded checkpoint '{}' (epoch {})" 125 | .format(args.resume, checkpoint['epoch'])) 126 | else: 127 | print("=> no checkpoint found at '{}'".format(args.resume)) 128 | 129 | cudnn.benchmark = True 130 | 131 | # Data loading code 132 | traindir = os.path.join(args.data, 'train') 133 | valdir = os.path.join(args.data, 'val') 134 | normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], 135 | std=[0.229, 0.224, 0.225]) 136 | 137 | train_dataset = datasets.ImageFolder( 138 | traindir, 139 | transforms.Compose([ 140 | transforms.RandomResizedCrop(224), 141 | transforms.RandomHorizontalFlip(), 142 | transforms.ToTensor(), 143 | normalize, 144 | ])) 145 | 146 | if args.distributed: 147 | train_sampler = torch.utils.data.distributed.DistributedSampler(train_dataset) 148 | else: 149 | train_sampler = None 150 | 151 | train_loader = torch.utils.data.DataLoader( 152 | train_dataset, batch_size=args.batch_size, shuffle=(train_sampler is None), 153 | num_workers=args.workers, pin_memory=True, sampler=train_sampler) 154 | 155 | val_loader = torch.utils.data.DataLoader( 156 | datasets.ImageFolder(valdir, transforms.Compose([ 157 | transforms.Resize(256), 158 | transforms.CenterCrop(224), 159 | transforms.ToTensor(), 160 | normalize, 161 | ])), 162 | batch_size=args.batch_size, shuffle=False, 163 | num_workers=args.workers, pin_memory=True) 164 | 165 | if args.evaluate: 166 | validate(val_loader, model, criterion) 167 | return 168 | 169 | for epoch in range(args.start_epoch, args.epochs): 170 | if args.distributed: 171 | train_sampler.set_epoch(epoch) 172 | adjust_learning_rate(optimizer, epoch) 173 | 174 | # train for one epoch 175 | train(train_loader, model, criterion, optimizer, epoch) 176 | 177 | # evaluate on validation set 178 | prec1 = validate(val_loader, model, criterion) 179 | 180 | # remember best prec@1 and save checkpoint 181 | is_best = prec1 > best_prec1 182 | best_prec1 = max(prec1, best_prec1) 183 | save_checkpoint({ 184 | 'epoch': epoch + 1, 185 | 'arch': args.arch, 186 | 'state_dict': model.state_dict(), 187 | 'best_prec1': best_prec1, 188 | 'optimizer' : optimizer.state_dict(), 189 | }, is_best) 190 | 191 | 192 | def train(train_loader, model, criterion, optimizer, epoch): 193 | batch_time = AverageMeter() 194 | data_time = AverageMeter() 195 | losses = AverageMeter() 196 | top1 = AverageMeter() 197 | top5 = AverageMeter() 198 | 199 | # switch to train mode 200 | model.train() 201 | 202 | end = time.time() 203 | for i, (input, target) in enumerate(train_loader): 204 | # measure data loading time 205 | data_time.update(time.time() - end) 206 | 207 | target = target.cuda(async=True) 208 | input_var = torch.autograd.Variable(input) 209 | target_var = torch.autograd.Variable(target) 210 | 211 | # compute output 212 | output = model(input_var) 213 | loss = criterion(output, target_var) 214 | 215 | # measure accuracy and record loss 216 | prec1, prec5 = accuracy(output.data, target, topk=(1, 5)) 217 | losses.update(loss.data[0], input.size(0)) 218 | top1.update(prec1[0], input.size(0)) 219 | top5.update(prec5[0], input.size(0)) 220 | 221 | # compute gradient and do SGD step 222 | optimizer.zero_grad() 223 | loss.backward() 224 | optimizer.step() 225 | 226 | # measure elapsed time 227 | batch_time.update(time.time() - end) 228 | end = time.time() 229 | 230 | if i % args.print_freq == 0: 231 | print('Epoch: [{0}][{1}/{2}]\t' 232 | 'Time {batch_time.val:.3f} ({batch_time.avg:.3f})\t' 233 | 'Data {data_time.val:.3f} ({data_time.avg:.3f})\t' 234 | 'Loss {loss.val:.4f} ({loss.avg:.4f})\t' 235 | 'Prec@1 {top1.val:.3f} ({top1.avg:.3f})\t' 236 | 'Prec@5 {top5.val:.3f} ({top5.avg:.3f})'.format( 237 | epoch, i, len(train_loader), batch_time=batch_time, 238 | data_time=data_time, loss=losses, top1=top1, top5=top5)) 239 | 240 | 241 | def validate(val_loader, model, criterion): 242 | batch_time = AverageMeter() 243 | losses = AverageMeter() 244 | top1 = AverageMeter() 245 | top5 = AverageMeter() 246 | 247 | # switch to evaluate mode 248 | model.eval() 249 | 250 | end = time.time() 251 | for i, (input, target) in enumerate(val_loader): 252 | target = target.cuda(async=True) 253 | input_var = torch.autograd.Variable(input, volatile=True) 254 | target_var = torch.autograd.Variable(target, volatile=True) 255 | 256 | # compute output 257 | output = model(input_var) 258 | loss = criterion(output, target_var) 259 | 260 | # measure accuracy and record loss 261 | prec1, prec5 = accuracy(output.data, target, topk=(1, 5)) 262 | losses.update(loss.data[0], input.size(0)) 263 | top1.update(prec1[0], input.size(0)) 264 | top5.update(prec5[0], input.size(0)) 265 | 266 | # measure elapsed time 267 | batch_time.update(time.time() - end) 268 | end = time.time() 269 | 270 | if i % args.print_freq == 0: 271 | print('Test: [{0}/{1}]\t' 272 | 'Time {batch_time.val:.3f} ({batch_time.avg:.3f})\t' 273 | 'Loss {loss.val:.4f} ({loss.avg:.4f})\t' 274 | 'Prec@1 {top1.val:.3f} ({top1.avg:.3f})\t' 275 | 'Prec@5 {top5.val:.3f} ({top5.avg:.3f})'.format( 276 | i, len(val_loader), batch_time=batch_time, loss=losses, 277 | top1=top1, top5=top5)) 278 | 279 | print(' * Prec@1 {top1.avg:.3f} Prec@5 {top5.avg:.3f}' 280 | .format(top1=top1, top5=top5)) 281 | 282 | return top1.avg 283 | 284 | 285 | def save_checkpoint(state, is_best, filename='./model/checkpoint.pth.tar'): 286 | torch.save(state, filename) 287 | if is_best: 288 | shutil.copyfile(filename, './model/model_best.pth.tar') 289 | 290 | class AverageMeter(object): 291 | """Computes and stores the average and current value""" 292 | def __init__(self): 293 | self.reset() 294 | 295 | def reset(self): 296 | self.val = 0 297 | self.avg = 0 298 | self.sum = 0 299 | self.count = 0 300 | 301 | def update(self, val, n=1): 302 | self.val = val 303 | self.sum += val * n 304 | self.count += n 305 | self.avg = self.sum / self.count 306 | 307 | 308 | def adjust_learning_rate(optimizer, epoch): 309 | """Sets the learning rate to the initial LR decayed by 10 every 30 epochs""" 310 | lr = args.lr * (0.1 ** (epoch // 30)) 311 | for param_group in optimizer.param_groups: 312 | param_group['lr'] = lr 313 | 314 | 315 | def accuracy(output, target, topk=(1,)): 316 | """Computes the precision@k for the specified values of k""" 317 | maxk = max(topk) 318 | batch_size = target.size(0) 319 | 320 | _, pred = output.topk(maxk, 1, True, True) 321 | pred = pred.t() 322 | correct = pred.eq(target.view(1, -1).expand_as(pred)) 323 | 324 | res = [] 325 | for k in topk: 326 | correct_k = correct[:k].view(-1).float().sum(0, keepdim=True) 327 | res.append(correct_k.mul_(100.0 / batch_size)) 328 | return res 329 | 330 | if __name__ == '__main__': 331 | main() 332 | -------------------------------------------------------------------------------- /mobilenetv2_Top1_71.806_Top2_90.410.pth.tar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ericsun99/MobileNet-V2-Pytorch/eb1a050237d8c461b916f15526c7e643da7a0856/mobilenetv2_Top1_71.806_Top2_90.410.pth.tar --------------------------------------------------------------------------------