├── .gitignore ├── do_train.sh ├── constant.py ├── utils ├── __init__.py ├── eval.py ├── util.py ├── logger.py └── keras_generic_utils.py ├── models ├── __init__.py ├── lenet.py ├── alexnet.py ├── mobilenet.py ├── googlenet.py ├── resnext.py ├── densenet.py ├── dpn.py ├── shufflenet.py ├── senet.py ├── resnet.py ├── preact_resnet.py └── vgg.py ├── data_provider.py ├── resize.py ├── folder.py ├── predict.py ├── augmentation.py ├── trainer.py └── log_visual.ipynb /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /do_train.sh: -------------------------------------------------------------------------------- 1 | 2 | check=checkpoint 3 | 4 | python trainer.py -a $1 -c $check 5 | -------------------------------------------------------------------------------- /constant.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | ROOT_PATH = os.path.join(os.environ['HOME'], 'VisualSearch') 4 | 5 | -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from .logger import * 3 | from .eval import * 4 | from .util import * 5 | 6 | -------------------------------------------------------------------------------- /models/__init__.py: -------------------------------------------------------------------------------- 1 | from .vgg import * 2 | from .dpn import * 3 | from .lenet import * 4 | from .senet import * 5 | from .alexnet import * 6 | from .resnet import * 7 | from .resnext import * 8 | from .densenet import * 9 | from .googlenet import * 10 | from .mobilenet import * 11 | from .shufflenet import * 12 | from .preact_resnet import * 13 | -------------------------------------------------------------------------------- /utils/eval.py: -------------------------------------------------------------------------------- 1 | 2 | __all__ = ['accuracy'] 3 | 4 | def accuracy(output, target, topk=(1,)): 5 | """Computes the precision@k for the specified values of k""" 6 | maxk = max(topk) 7 | batch_size = target.size(0) 8 | 9 | _, pred = output.topk(maxk, 1, True, True) 10 | pred = pred.t() 11 | correct = pred.eq(target.view(1, -1).expand_as(pred)) 12 | 13 | res = [] 14 | for k in topk: 15 | correct_k = correct[:k].view(-1).float().sum(0, keepdim=True) 16 | res.append(correct_k.mul_(100.0 / batch_size)) 17 | return res 18 | -------------------------------------------------------------------------------- /models/lenet.py: -------------------------------------------------------------------------------- 1 | '''LeNet in PyTorch.''' 2 | import torch.nn as nn 3 | import torch.nn.functional as F 4 | 5 | __all = ['lenet'] 6 | 7 | class LeNet(nn.Module): 8 | def __init__(self, num_classes=10): 9 | super(LeNet, self).__init__() 10 | self.conv1 = nn.Conv2d(3, 6, 5) 11 | self.conv2 = nn.Conv2d(6, 16, 5) 12 | self.fc1 = nn.Linear(16*5*5, 120) 13 | self.fc2 = nn.Linear(120, 84) 14 | self.fc3 = nn.Linear(84, num_classes) 15 | 16 | def forward(self, x): 17 | out = F.relu(self.conv1(x)) 18 | out = F.max_pool2d(out, 2) 19 | out = F.relu(self.conv2(out)) 20 | out = F.max_pool2d(out, 2) 21 | out = out.view(out.size(0), -1) 22 | out = F.relu(self.fc1(out)) 23 | out = F.relu(self.fc2(out)) 24 | out = self.fc3(out) 25 | return out 26 | 27 | def alexnet(**kwargs): 28 | model = LeNet(**kwargs) 29 | return model 30 | 31 | 32 | -------------------------------------------------------------------------------- /utils/util.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function, division 2 | 3 | import torch 4 | import shutil 5 | import os 6 | 7 | DEFAULT_MODEL_DIR = '/home/xcx/VisualSearch/checkpoint' 8 | 9 | def save_checkpoint(state, is_best, filename='checkpoint.pth.tar', checkpoint=DEFAULT_MODEL_DIR): 10 | if not os.path.exists(checkpoint): 11 | os.makedirs(checkpoint) 12 | newfile = os.path.join(checkpoint, filename) 13 | torch.save(state, newfile) 14 | print ('model saved at {}'.format(newfile)) 15 | if is_best: 16 | shutil.copyfile(newfile, os.path.join(checkpoint,'model_best.pth.tar')) 17 | 18 | 19 | def load_checkpoint(filename='model_best.pth.tar', modeldir=DEFAULT_MODEL_DIR): 20 | checkpoint = torch.load(os.path.join(modeldir,filename)) 21 | return checkpoint 22 | 23 | 24 | class AverageMeter(object): 25 | """Computes and stores the average and current value""" 26 | def __init__(self): 27 | self.reset() 28 | 29 | def reset(self): 30 | self.val = 0 31 | self.avg = 0 32 | self.sum = 0 33 | self.count = 0 34 | 35 | def update(self, val, n=1): 36 | self.val = val 37 | self.sum += val * n 38 | self.count += n 39 | self.avg = self.sum / self.count 40 | 41 | def mkdir_p(path): 42 | '''make dir if not exist''' 43 | try: 44 | os.makedirs(path) 45 | except OSError as exc: # Python >2.5 46 | if exc.errno == errno.EEXIST and os.path.isdir(path): 47 | pass 48 | else: 49 | raise 50 | -------------------------------------------------------------------------------- /models/alexnet.py: -------------------------------------------------------------------------------- 1 | '''AlexNet for CIFAR10. FC layers are removed. Paddings are adjusted. 2 | Without BN, the start learning rate should be 0.01 3 | (c) YANG, Wei 4 | ''' 5 | import torch.nn as nn 6 | 7 | 8 | __all__ = ['alexnet'] 9 | 10 | 11 | class AlexNet(nn.Module): 12 | 13 | def __init__(self, num_classes=10): 14 | super(AlexNet, self).__init__() 15 | self.features = nn.Sequential( 16 | nn.Conv2d(3, 64, kernel_size=11, stride=4, padding=5), 17 | nn.ReLU(inplace=True), 18 | nn.MaxPool2d(kernel_size=2, stride=2), 19 | nn.Conv2d(64, 192, kernel_size=5, padding=2), 20 | nn.ReLU(inplace=True), 21 | nn.MaxPool2d(kernel_size=2, stride=2), 22 | nn.Conv2d(192, 384, kernel_size=3, padding=1), 23 | nn.ReLU(inplace=True), 24 | nn.Conv2d(384, 256, kernel_size=3, padding=1), 25 | nn.ReLU(inplace=True), 26 | nn.Conv2d(256, 256, kernel_size=3, padding=1), 27 | nn.ReLU(inplace=True), 28 | nn.MaxPool2d(kernel_size=2, stride=2), 29 | ) 30 | self.classifier = nn.Linear(256, num_classes) 31 | 32 | def forward(self, x): 33 | x = self.features(x) 34 | x = x.view(x.size(0), -1) 35 | x = self.classifier(x) 36 | return x 37 | 38 | 39 | def alexnet(**kwargs): 40 | r"""AlexNet model architecture from the 41 | `"One weird trick..." `_ paper. 42 | """ 43 | model = AlexNet(**kwargs) 44 | return model 45 | -------------------------------------------------------------------------------- /data_provider.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function, division 2 | 3 | import torch 4 | from torchvision import datasets, transforms 5 | import os 6 | import math 7 | 8 | data_transforms = { 9 | 'train': transforms.Compose([ 10 | transforms.RandomCrop(32, padding=4), 11 | transforms.RandomHorizontalFlip(), 12 | transforms.ToTensor(), 13 | transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) 14 | ]), 15 | 'val': transforms.Compose([ 16 | transforms.ToTensor(), 17 | transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) 18 | ]), 19 | } 20 | 21 | data_dir = "/home/xcx/code/AI_class/Machine_l/net/data" 22 | batch_size = 64 23 | image_datasets = {x: datasets.CIFAR10(root=data_dir, train=(x=='train'), download=False, transform=data_transforms[x]) 24 | for x in ['train', 'val']} 25 | 26 | dataloders = {x: torch.utils.data.DataLoader(image_datasets[x], batch_size=batch_size, 27 | shuffle=(x=='train'), num_workers=2) 28 | for x in ['train', 'val']} 29 | 30 | dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'val']} 31 | 32 | batch_nums = {x: int(math.ceil(dataset_sizes[x]/batch_size)) for x in ['train', 'val']} 33 | 34 | print ('batch_size: ', batch_size, '\nbatch_nums: ', batch_nums) 35 | print('dataset_sizes: ', dataset_sizes) 36 | 37 | class_names = ('plane', 'car', 'bird', 'cat', 38 | 'deer', 'dog', 'frog', 'horse', 'ship', 'truck') 39 | 40 | if __name__ == '__main__': 41 | #train_loader = dataloders['train'] 42 | print(len(image_datasets['train'])) 43 | for index, data in enumerate(image_datasets['train']): 44 | #inputs, labels = data 45 | print(data) 46 | break 47 | -------------------------------------------------------------------------------- /models/mobilenet.py: -------------------------------------------------------------------------------- 1 | '''MobileNet in PyTorch. 2 | 3 | See the paper "MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications" 4 | for more details. 5 | ''' 6 | import torch 7 | import torch.nn as nn 8 | import torch.nn.functional as F 9 | 10 | from torch.autograd import Variable 11 | 12 | 13 | class Block(nn.Module): 14 | '''Depthwise conv + Pointwise conv''' 15 | def __init__(self, in_planes, out_planes, stride=1): 16 | super(Block, self).__init__() 17 | self.conv1 = nn.Conv2d(in_planes, in_planes, kernel_size=3, stride=stride, padding=1, groups=in_planes, bias=False) 18 | self.bn1 = nn.BatchNorm2d(in_planes) 19 | self.conv2 = nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=1, padding=0, bias=False) 20 | self.bn2 = nn.BatchNorm2d(out_planes) 21 | 22 | def forward(self, x): 23 | out = F.relu(self.bn1(self.conv1(x))) 24 | out = F.relu(self.bn2(self.conv2(out))) 25 | return out 26 | 27 | 28 | class MobileNet(nn.Module): 29 | # (128,2) means conv planes=128, conv stride=2, by default conv stride=1 30 | cfg = [64, (128,2), 128, (256,2), 256, (512,2), 512, 512, 512, 512, 512, (1024,2), 1024] 31 | 32 | def __init__(self, num_classes=10): 33 | super(MobileNet, self).__init__() 34 | self.conv1 = nn.Conv2d(3, 32, kernel_size=3, stride=1, padding=1, bias=False) 35 | self.bn1 = nn.BatchNorm2d(32) 36 | self.layers = self._make_layers(in_planes=32) 37 | self.linear = nn.Linear(1024, num_classes) 38 | 39 | def _make_layers(self, in_planes): 40 | layers = [] 41 | for x in self.cfg: 42 | out_planes = x if isinstance(x, int) else x[0] 43 | stride = 1 if isinstance(x, int) else x[1] 44 | layers.append(Block(in_planes, out_planes, stride)) 45 | in_planes = out_planes 46 | return nn.Sequential(*layers) 47 | 48 | def forward(self, x): 49 | out = F.relu(self.bn1(self.conv1(x))) 50 | out = self.layers(out) 51 | out = F.avg_pool2d(out, 2) 52 | out = out.view(out.size(0), -1) 53 | out = self.linear(out) 54 | return out 55 | 56 | 57 | def test(): 58 | net = MobileNet() 59 | x = torch.randn(1,3,32,32) 60 | y = net(Variable(x)) 61 | print(y.size()) 62 | 63 | # test() 64 | -------------------------------------------------------------------------------- /utils/logger.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import sys 4 | import numpy as np 5 | import matplotlib.pyplot as plt 6 | 7 | def plot_overlap(logger, names=None): 8 | names = logger.names if names == None else names 9 | numbers = logger.numbers 10 | for _, name in enumerate(names): 11 | x = np.arange(len(numbers[name])) 12 | plt.plot(x, np.asarray(numbers[name])) 13 | return [logger.title + '(' + name + ')' for name in names] 14 | 15 | class Logger(object): 16 | '''Save training process to log file with simple plot function.''' 17 | def __init__(self, fpath, title=None, resume=False): 18 | self.file = None 19 | self.resume = resume 20 | self.title = '' if title == None else title 21 | if fpath is not None: 22 | if resume: 23 | self.file = open(fpath, 'r') 24 | name = self.file.readline() 25 | self.names = name.rstrip().split(',') 26 | self.numbers = {} 27 | for _, name in enumerate(self.names): 28 | self.numbers[name] = [] 29 | 30 | for numbers in self.file: 31 | numbers = numbers.rstrip().split(',') 32 | for i in range(0, len(numbers)): 33 | if numbers[i] != '': 34 | self.numbers[self.names[i]].append(numbers[i]) 35 | self.file.close() 36 | self.file = open(fpath, 'a') 37 | else: 38 | self.file = open(fpath, 'w') 39 | 40 | def set_names(self, names): 41 | if self.resume: 42 | pass 43 | # initialize numbers as empty list 44 | self.numbers = {} 45 | self.names = names 46 | for i, name in enumerate(self.names): 47 | self.file.write(name) 48 | if i < len(self.names)-1: 49 | self.file.write(',') 50 | self.numbers[name] = [] 51 | self.file.write('\n') 52 | self.file.flush() 53 | 54 | def append(self, numbers): 55 | assert len(self.names) == len(numbers), 'Numbers do not match names' 56 | for index, num in enumerate(numbers): 57 | if num == None or num == '': 58 | self.file.write(',') 59 | continue 60 | self.file.write("{0:.6f}".format(num)) 61 | self.file.write(',') 62 | self.numbers[self.names[index]].append(num) 63 | self.file.write('\n') 64 | self.file.flush() 65 | 66 | def plot(self, names=None): 67 | names = self.names if names == None else names 68 | numbers = self.numbers 69 | for _, name in enumerate(names): 70 | x = np.arange(len(numbers[name])) 71 | plt.plot(x, np.asarray(numbers[name])) 72 | plt.legend([self.title + '(' + name + ')' for name in names]) 73 | plt.grid(True) 74 | 75 | def close(self): 76 | if self.file is not None: 77 | self.file.close() 78 | 79 | class LoggerMonitor(object): 80 | '''Load and visualize multiple logs.''' 81 | def __init__ (self, paths): 82 | '''paths is a distionary with {name:filepath} pair''' 83 | self.loggers = [] 84 | for title, path in paths.items(): 85 | logger = Logger(path, title=title, resume=True) 86 | self.loggers.append(logger) 87 | 88 | def plot(self, names=None): 89 | plt.figure() 90 | plt.subplot(121) 91 | legend_text = [] 92 | for logger in self.loggers: 93 | legend_text += plot_overlap(logger, names) 94 | plt.legend(legend_text, bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.) 95 | plt.grid(True) 96 | -------------------------------------------------------------------------------- /resize.py: -------------------------------------------------------------------------------- 1 | import os, sys 2 | 3 | from PIL import Image 4 | try: 5 | import accimage 6 | except ImportError: 7 | accimage = None 8 | 9 | from constant import ROOT_PATH 10 | 11 | 12 | def _is_pil_image(img): 13 | if accimage is not None: 14 | return isinstance(img, (Image.Image, accimage.Image)) 15 | else: 16 | return isinstance(img, Image.Image) 17 | 18 | def resize(img, size, interpolation=Image.ANTIALIAS): 19 | """Resize the input PIL.Image to the given size. 20 | Args: 21 | img (PIL.Image): Image to be resized. 22 | size (sequence or int): Desired output size. If size is a sequence like 23 | (h, w), the output size will be matched to this. If size is an int, 24 | the smaller edge of the image will be matched to this number maintaing 25 | the aspect ratio. i.e, if height > width, then image will be rescaled to 26 | (size * height / width, size) 27 | interpolation (int, optional): Desired interpolation. Default is 28 | ``PIL.Image.ANTIALIAS`` 29 | Returns: 30 | PIL.Image: Resized image. 31 | """ 32 | if not _is_pil_image(img): 33 | raise TypeError('img should be PIL Image. Got {}'.format(type(img))) 34 | if not (isinstance(size, int) or (isinstance(size, collections.Iterable) and len(size) == 2)): 35 | raise TypeError('Got inappropriate size arg: {}'.format(size)) 36 | 37 | if isinstance(size, int): 38 | w, h = img.size 39 | if (w <= h and w == size) or (h <= w and h == size): 40 | return img 41 | if w < h: 42 | ow = size 43 | oh = int(size * h / w) 44 | return img.resize((ow, oh), interpolation) 45 | else: 46 | oh = size 47 | ow = int(size * w / h) 48 | return img.resize((ow, oh), interpolation) 49 | else: 50 | return img.resize(size[::-1], interpolation) 51 | 52 | 53 | def process(options, collection): 54 | rootpath = options.rootpath 55 | size = options.image_size 56 | id_path_file = os.path.join(rootpath, collection, 'id.imagepath.txt') 57 | data = [x.strip().split() for x in open(id_path_file).readlines() if x.strip()] 58 | num_images = len(data) 59 | 60 | for i,(imgid,impath) in enumerate(data): 61 | resfile = impath.replace('ImageData/', 'ImageData{}/'.format(size)) 62 | output_dir = os.path.split(resfile)[0] 63 | if not os.path.exists(output_dir): 64 | os.makedirs(output_dir) 65 | if os.path.exists(resfile): 66 | continue 67 | with open(impath, 'r+b') as f: 68 | with Image.open(f) as img: 69 | img = resize(img, size) 70 | img.save(resfile, img.format) 71 | if i % 1000 == 0: 72 | print ("[%d/%d] Resized the images and saved into '%s'." 73 | %(i, num_images, output_dir)) 74 | 75 | def main(argv=None): 76 | if argv is None: 77 | argv = sys.argv[1:] 78 | 79 | from optparse import OptionParser 80 | parser = OptionParser(usage="""usage: %prog [options] collection""") 81 | parser.add_option("--overwrite", default=0, type="int", help="overwrite existing file (default: 0)") 82 | parser.add_option("--rootpath", default=ROOT_PATH, type="string", help="rootpath (default: %s)" % ROOT_PATH) 83 | parser.add_option("--image_size", default=256, type="int", help="size for image after processing (default: 256)") 84 | 85 | (options, args) = parser.parse_args(argv) 86 | if len(args) < 1: 87 | parser.print_help() 88 | return 1 89 | 90 | return process(options, args[0]) 91 | 92 | 93 | 94 | if __name__ == '__main__': 95 | sys.exit(main()) 96 | 97 | -------------------------------------------------------------------------------- /models/googlenet.py: -------------------------------------------------------------------------------- 1 | '''GoogLeNet with PyTorch.''' 2 | import torch 3 | import torch.nn as nn 4 | import torch.nn.functional as F 5 | 6 | from torch.autograd import Variable 7 | 8 | __all__ = ['googlenet'] 9 | 10 | class Inception(nn.Module): 11 | def __init__(self, in_planes, n1x1, n3x3red, n3x3, n5x5red, n5x5, pool_planes): 12 | super(Inception, self).__init__() 13 | # 1x1 conv branch 14 | self.b1 = nn.Sequential( 15 | nn.Conv2d(in_planes, n1x1, kernel_size=1), 16 | nn.BatchNorm2d(n1x1), 17 | nn.ReLU(True), 18 | ) 19 | 20 | # 1x1 conv -> 3x3 conv branch 21 | self.b2 = nn.Sequential( 22 | nn.Conv2d(in_planes, n3x3red, kernel_size=1), 23 | nn.BatchNorm2d(n3x3red), 24 | nn.ReLU(True), 25 | nn.Conv2d(n3x3red, n3x3, kernel_size=3, padding=1), 26 | nn.BatchNorm2d(n3x3), 27 | nn.ReLU(True), 28 | ) 29 | 30 | # 1x1 conv -> 5x5 conv branch 31 | self.b3 = nn.Sequential( 32 | nn.Conv2d(in_planes, n5x5red, kernel_size=1), 33 | nn.BatchNorm2d(n5x5red), 34 | nn.ReLU(True), 35 | nn.Conv2d(n5x5red, n5x5, kernel_size=3, padding=1), 36 | nn.BatchNorm2d(n5x5), 37 | nn.ReLU(True), 38 | nn.Conv2d(n5x5, n5x5, kernel_size=3, padding=1), 39 | nn.BatchNorm2d(n5x5), 40 | nn.ReLU(True), 41 | ) 42 | 43 | # 3x3 pool -> 1x1 conv branch 44 | self.b4 = nn.Sequential( 45 | nn.MaxPool2d(3, stride=1, padding=1), 46 | nn.Conv2d(in_planes, pool_planes, kernel_size=1), 47 | nn.BatchNorm2d(pool_planes), 48 | nn.ReLU(True), 49 | ) 50 | 51 | def forward(self, x): 52 | y1 = self.b1(x) 53 | y2 = self.b2(x) 54 | y3 = self.b3(x) 55 | y4 = self.b4(x) 56 | return torch.cat([y1,y2,y3,y4], 1) 57 | 58 | 59 | class GoogLeNet(nn.Module): 60 | def __init__(self, num_classes=10): 61 | super(GoogLeNet, self).__init__() 62 | self.pre_layers = nn.Sequential( 63 | nn.Conv2d(3, 192, kernel_size=3, padding=1), 64 | nn.BatchNorm2d(192), 65 | nn.ReLU(True), 66 | ) 67 | 68 | self.a3 = Inception(192, 64, 96, 128, 16, 32, 32) 69 | self.b3 = Inception(256, 128, 128, 192, 32, 96, 64) 70 | 71 | self.maxpool = nn.MaxPool2d(3, stride=2, padding=1) 72 | 73 | self.a4 = Inception(480, 192, 96, 208, 16, 48, 64) 74 | self.b4 = Inception(512, 160, 112, 224, 24, 64, 64) 75 | self.c4 = Inception(512, 128, 128, 256, 24, 64, 64) 76 | self.d4 = Inception(512, 112, 144, 288, 32, 64, 64) 77 | self.e4 = Inception(528, 256, 160, 320, 32, 128, 128) 78 | 79 | self.a5 = Inception(832, 256, 160, 320, 32, 128, 128) 80 | self.b5 = Inception(832, 384, 192, 384, 48, 128, 128) 81 | 82 | self.avgpool = nn.AvgPool2d(8, stride=1) 83 | self.linear = nn.Linear(1024, num_classes) 84 | 85 | def forward(self, x): 86 | out = self.pre_layers(x) 87 | out = self.a3(out) 88 | out = self.b3(out) 89 | out = self.maxpool(out) 90 | out = self.a4(out) 91 | out = self.b4(out) 92 | out = self.c4(out) 93 | out = self.d4(out) 94 | out = self.e4(out) 95 | out = self.maxpool(out) 96 | out = self.a5(out) 97 | out = self.b5(out) 98 | out = self.avgpool(out) 99 | out = out.view(out.size(0), -1) 100 | out = self.linear(out) 101 | return out 102 | 103 | def googlenet(**kwargs): 104 | 105 | model = GoogLeNet(**kwargs) 106 | return model 107 | -------------------------------------------------------------------------------- /models/resnext.py: -------------------------------------------------------------------------------- 1 | '''ResNeXt in PyTorch. 2 | 3 | See the paper "Aggregated Residual Transformations for Deep Neural Networks" for more details. 4 | ''' 5 | import torch 6 | import torch.nn as nn 7 | import torch.nn.functional as F 8 | 9 | from torch.autograd import Variable 10 | 11 | 12 | class Block(nn.Module): 13 | '''Grouped convolution block.''' 14 | expansion = 2 15 | 16 | def __init__(self, in_planes, cardinality=32, bottleneck_width=4, stride=1): 17 | super(Block, self).__init__() 18 | group_width = cardinality * bottleneck_width 19 | self.conv1 = nn.Conv2d(in_planes, group_width, kernel_size=1, bias=False) 20 | self.bn1 = nn.BatchNorm2d(group_width) 21 | self.conv2 = nn.Conv2d(group_width, group_width, kernel_size=3, stride=stride, padding=1, groups=cardinality, bias=False) 22 | self.bn2 = nn.BatchNorm2d(group_width) 23 | self.conv3 = nn.Conv2d(group_width, self.expansion*group_width, kernel_size=1, bias=False) 24 | self.bn3 = nn.BatchNorm2d(self.expansion*group_width) 25 | 26 | self.shortcut = nn.Sequential() 27 | if stride != 1 or in_planes != self.expansion*group_width: 28 | self.shortcut = nn.Sequential( 29 | nn.Conv2d(in_planes, self.expansion*group_width, kernel_size=1, stride=stride, bias=False), 30 | nn.BatchNorm2d(self.expansion*group_width) 31 | ) 32 | 33 | def forward(self, x): 34 | out = F.relu(self.bn1(self.conv1(x))) 35 | out = F.relu(self.bn2(self.conv2(out))) 36 | out = self.bn3(self.conv3(out)) 37 | out += self.shortcut(x) 38 | out = F.relu(out) 39 | return out 40 | 41 | 42 | class ResNeXt(nn.Module): 43 | def __init__(self, num_blocks, cardinality, bottleneck_width, num_classes=10): 44 | super(ResNeXt, self).__init__() 45 | self.cardinality = cardinality 46 | self.bottleneck_width = bottleneck_width 47 | self.in_planes = 64 48 | 49 | self.conv1 = nn.Conv2d(3, 64, kernel_size=1, bias=False) 50 | self.bn1 = nn.BatchNorm2d(64) 51 | self.layer1 = self._make_layer(num_blocks[0], 1) 52 | self.layer2 = self._make_layer(num_blocks[1], 2) 53 | self.layer3 = self._make_layer(num_blocks[2], 2) 54 | # self.layer4 = self._make_layer(num_blocks[3], 2) 55 | self.linear = nn.Linear(cardinality*bottleneck_width*8, num_classes) 56 | 57 | def _make_layer(self, num_blocks, stride): 58 | strides = [stride] + [1]*(num_blocks-1) 59 | layers = [] 60 | for stride in strides: 61 | layers.append(Block(self.in_planes, self.cardinality, self.bottleneck_width, stride)) 62 | self.in_planes = Block.expansion * self.cardinality * self.bottleneck_width 63 | # Increase bottleneck_width by 2 after each stage. 64 | self.bottleneck_width *= 2 65 | return nn.Sequential(*layers) 66 | 67 | def forward(self, x): 68 | out = F.relu(self.bn1(self.conv1(x))) 69 | out = self.layer1(out) 70 | out = self.layer2(out) 71 | out = self.layer3(out) 72 | # out = self.layer4(out) 73 | out = F.avg_pool2d(out, 8) 74 | out = out.view(out.size(0), -1) 75 | out = self.linear(out) 76 | return out 77 | 78 | 79 | def ResNeXt29_2x64d(): 80 | return ResNeXt(num_blocks=[3,3,3], cardinality=2, bottleneck_width=64) 81 | 82 | def ResNeXt29_4x64d(): 83 | return ResNeXt(num_blocks=[3,3,3], cardinality=4, bottleneck_width=64) 84 | 85 | def ResNeXt29_8x64d(): 86 | return ResNeXt(num_blocks=[3,3,3], cardinality=8, bottleneck_width=64) 87 | 88 | def ResNeXt29_32x4d(): 89 | return ResNeXt(num_blocks=[3,3,3], cardinality=32, bottleneck_width=4) 90 | 91 | def test_resnext(): 92 | net = ResNeXt29_2x64d() 93 | x = torch.randn(1,3,32,32) 94 | y = net(Variable(x)) 95 | print(y.size()) 96 | 97 | # test_resnext() 98 | -------------------------------------------------------------------------------- /models/densenet.py: -------------------------------------------------------------------------------- 1 | '''DenseNet in PyTorch.''' 2 | import math 3 | 4 | import torch 5 | import torch.nn as nn 6 | import torch.nn.functional as F 7 | 8 | from torch.autograd import Variable 9 | 10 | __all__ = ['densenet_cifar'] 11 | 12 | class Bottleneck(nn.Module): 13 | def __init__(self, in_planes, growth_rate): 14 | super(Bottleneck, self).__init__() 15 | self.bn1 = nn.BatchNorm2d(in_planes) 16 | self.conv1 = nn.Conv2d(in_planes, 4*growth_rate, kernel_size=1, bias=False) 17 | self.bn2 = nn.BatchNorm2d(4*growth_rate) 18 | self.conv2 = nn.Conv2d(4*growth_rate, growth_rate, kernel_size=3, padding=1, bias=False) 19 | 20 | def forward(self, x): 21 | out = self.conv1(F.relu(self.bn1(x))) 22 | out = self.conv2(F.relu(self.bn2(out))) 23 | out = torch.cat([out,x], 1) 24 | return out 25 | 26 | 27 | class Transition(nn.Module): 28 | def __init__(self, in_planes, out_planes): 29 | super(Transition, self).__init__() 30 | self.bn = nn.BatchNorm2d(in_planes) 31 | self.conv = nn.Conv2d(in_planes, out_planes, kernel_size=1, bias=False) 32 | 33 | def forward(self, x): 34 | out = self.conv(F.relu(self.bn(x))) 35 | out = F.avg_pool2d(out, 2) 36 | return out 37 | 38 | 39 | class DenseNet(nn.Module): 40 | def __init__(self, block, nblocks, growth_rate=12, reduction=0.5, num_classes=10): 41 | super(DenseNet, self).__init__() 42 | self.growth_rate = growth_rate 43 | 44 | num_planes = 2*growth_rate 45 | self.conv1 = nn.Conv2d(3, num_planes, kernel_size=3, padding=1, bias=False) 46 | 47 | self.dense1 = self._make_dense_layers(block, num_planes, nblocks[0]) 48 | num_planes += nblocks[0]*growth_rate 49 | out_planes = int(math.floor(num_planes*reduction)) 50 | self.trans1 = Transition(num_planes, out_planes) 51 | num_planes = out_planes 52 | 53 | self.dense2 = self._make_dense_layers(block, num_planes, nblocks[1]) 54 | num_planes += nblocks[1]*growth_rate 55 | out_planes = int(math.floor(num_planes*reduction)) 56 | self.trans2 = Transition(num_planes, out_planes) 57 | num_planes = out_planes 58 | 59 | self.dense3 = self._make_dense_layers(block, num_planes, nblocks[2]) 60 | num_planes += nblocks[2]*growth_rate 61 | out_planes = int(math.floor(num_planes*reduction)) 62 | self.trans3 = Transition(num_planes, out_planes) 63 | num_planes = out_planes 64 | 65 | self.dense4 = self._make_dense_layers(block, num_planes, nblocks[3]) 66 | num_planes += nblocks[3]*growth_rate 67 | 68 | self.bn = nn.BatchNorm2d(num_planes) 69 | self.linear = nn.Linear(num_planes, num_classes) 70 | 71 | def _make_dense_layers(self, block, in_planes, nblock): 72 | layers = [] 73 | for i in range(nblock): 74 | layers.append(block(in_planes, self.growth_rate)) 75 | in_planes += self.growth_rate 76 | return nn.Sequential(*layers) 77 | 78 | def forward(self, x): 79 | out = self.conv1(x) 80 | out = self.trans1(self.dense1(out)) 81 | out = self.trans2(self.dense2(out)) 82 | out = self.trans3(self.dense3(out)) 83 | out = self.dense4(out) 84 | out = F.avg_pool2d(F.relu(self.bn(out)), 4) 85 | out = out.view(out.size(0), -1) 86 | out = self.linear(out) 87 | return out 88 | 89 | def DenseNet121(): 90 | return DenseNet(Bottleneck, [6,12,24,16], growth_rate=32) 91 | 92 | def DenseNet169(): 93 | return DenseNet(Bottleneck, [6,12,32,32], growth_rate=32) 94 | 95 | def DenseNet201(): 96 | return DenseNet(Bottleneck, [6,12,48,32], growth_rate=32) 97 | 98 | def DenseNet161(): 99 | return DenseNet(Bottleneck, [6,12,36,24], growth_rate=48) 100 | 101 | def densenet_cifar(**kwargs): 102 | return DenseNet(Bottleneck, [6,12,24,16], growth_rate=12, **kwargs) 103 | 104 | -------------------------------------------------------------------------------- /models/dpn.py: -------------------------------------------------------------------------------- 1 | '''Dual Path Networks in PyTorch.''' 2 | import torch 3 | import torch.nn as nn 4 | import torch.nn.functional as F 5 | 6 | from torch.autograd import Variable 7 | 8 | 9 | class Bottleneck(nn.Module): 10 | def __init__(self, last_planes, in_planes, out_planes, dense_depth, stride, first_layer): 11 | super(Bottleneck, self).__init__() 12 | self.out_planes = out_planes 13 | self.dense_depth = dense_depth 14 | 15 | self.conv1 = nn.Conv2d(last_planes, in_planes, kernel_size=1, bias=False) 16 | self.bn1 = nn.BatchNorm2d(in_planes) 17 | self.conv2 = nn.Conv2d(in_planes, in_planes, kernel_size=3, stride=stride, padding=1, groups=32, bias=False) 18 | self.bn2 = nn.BatchNorm2d(in_planes) 19 | self.conv3 = nn.Conv2d(in_planes, out_planes+dense_depth, kernel_size=1, bias=False) 20 | self.bn3 = nn.BatchNorm2d(out_planes+dense_depth) 21 | 22 | self.shortcut = nn.Sequential() 23 | if first_layer: 24 | self.shortcut = nn.Sequential( 25 | nn.Conv2d(last_planes, out_planes+dense_depth, kernel_size=1, stride=stride, bias=False), 26 | nn.BatchNorm2d(out_planes+dense_depth) 27 | ) 28 | 29 | def forward(self, x): 30 | out = F.relu(self.bn1(self.conv1(x))) 31 | out = F.relu(self.bn2(self.conv2(out))) 32 | out = self.bn3(self.conv3(out)) 33 | x = self.shortcut(x) 34 | d = self.out_planes 35 | out = torch.cat([x[:,:d,:,:]+out[:,:d,:,:], x[:,d:,:,:], out[:,d:,:,:]], 1) 36 | out = F.relu(out) 37 | return out 38 | 39 | 40 | class DPN(nn.Module): 41 | def __init__(self, cfg): 42 | super(DPN, self).__init__() 43 | in_planes, out_planes = cfg['in_planes'], cfg['out_planes'] 44 | num_blocks, dense_depth = cfg['num_blocks'], cfg['dense_depth'] 45 | 46 | self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False) 47 | self.bn1 = nn.BatchNorm2d(64) 48 | self.last_planes = 64 49 | self.layer1 = self._make_layer(in_planes[0], out_planes[0], num_blocks[0], dense_depth[0], stride=1) 50 | self.layer2 = self._make_layer(in_planes[1], out_planes[1], num_blocks[1], dense_depth[1], stride=2) 51 | self.layer3 = self._make_layer(in_planes[2], out_planes[2], num_blocks[2], dense_depth[2], stride=2) 52 | self.layer4 = self._make_layer(in_planes[3], out_planes[3], num_blocks[3], dense_depth[3], stride=2) 53 | self.linear = nn.Linear(out_planes[3]+(num_blocks[3]+1)*dense_depth[3], 10) 54 | 55 | def _make_layer(self, in_planes, out_planes, num_blocks, dense_depth, stride): 56 | strides = [stride] + [1]*(num_blocks-1) 57 | layers = [] 58 | for i,stride in enumerate(strides): 59 | layers.append(Bottleneck(self.last_planes, in_planes, out_planes, dense_depth, stride, i==0)) 60 | self.last_planes = out_planes + (i+2) * dense_depth 61 | return nn.Sequential(*layers) 62 | 63 | def forward(self, x): 64 | out = F.relu(self.bn1(self.conv1(x))) 65 | out = self.layer1(out) 66 | out = self.layer2(out) 67 | out = self.layer3(out) 68 | out = self.layer4(out) 69 | out = F.avg_pool2d(out, 4) 70 | out = out.view(out.size(0), -1) 71 | out = self.linear(out) 72 | return out 73 | 74 | 75 | def DPN26(): 76 | cfg = { 77 | 'in_planes': (96,192,384,768), 78 | 'out_planes': (256,512,1024,2048), 79 | 'num_blocks': (2,2,2,2), 80 | 'dense_depth': (16,32,24,128) 81 | } 82 | return DPN(cfg) 83 | 84 | def DPN92(): 85 | cfg = { 86 | 'in_planes': (96,192,384,768), 87 | 'out_planes': (256,512,1024,2048), 88 | 'num_blocks': (3,4,20,3), 89 | 'dense_depth': (16,32,24,128) 90 | } 91 | return DPN(cfg) 92 | 93 | 94 | def test(): 95 | net = DPN92() 96 | x = Variable(torch.randn(1,3,32,32)) 97 | y = net(x) 98 | print(y) 99 | 100 | # test() 101 | -------------------------------------------------------------------------------- /models/shufflenet.py: -------------------------------------------------------------------------------- 1 | '''ShuffleNet in PyTorch. 2 | 3 | See the paper "ShuffleNet: An Extremely Efficient Convolutional Neural Network for Mobile Devices" for more details. 4 | ''' 5 | import torch 6 | import torch.nn as nn 7 | import torch.nn.functional as F 8 | 9 | from torch.autograd import Variable 10 | 11 | 12 | class ShuffleBlock(nn.Module): 13 | def __init__(self, groups): 14 | super(ShuffleBlock, self).__init__() 15 | self.groups = groups 16 | 17 | def forward(self, x): 18 | '''Channel shuffle: [N,C,H,W] -> [N,g,C/g,H,W] -> [N,C/g,g,H,w] -> [N,C,H,W]''' 19 | N,C,H,W = x.size() 20 | g = self.groups 21 | return x.view(N,g,C/g,H,W).permute(0,2,1,3,4).contiguous().view(N,C,H,W) 22 | 23 | 24 | class Bottleneck(nn.Module): 25 | def __init__(self, in_planes, out_planes, stride, groups): 26 | super(Bottleneck, self).__init__() 27 | self.stride = stride 28 | 29 | mid_planes = out_planes/4 30 | g = 1 if in_planes==24 else groups 31 | self.conv1 = nn.Conv2d(in_planes, mid_planes, kernel_size=1, groups=g, bias=False) 32 | self.bn1 = nn.BatchNorm2d(mid_planes) 33 | self.shuffle1 = ShuffleBlock(groups=g) 34 | self.conv2 = nn.Conv2d(mid_planes, mid_planes, kernel_size=3, stride=stride, padding=1, groups=mid_planes, bias=False) 35 | self.bn2 = nn.BatchNorm2d(mid_planes) 36 | self.conv3 = nn.Conv2d(mid_planes, out_planes, kernel_size=1, groups=groups, bias=False) 37 | self.bn3 = nn.BatchNorm2d(out_planes) 38 | 39 | self.shortcut = nn.Sequential() 40 | if stride == 2: 41 | self.shortcut = nn.Sequential(nn.AvgPool2d(3, stride=2, padding=1)) 42 | 43 | def forward(self, x): 44 | out = F.relu(self.bn1(self.conv1(x))) 45 | out = self.shuffle1(out) 46 | out = F.relu(self.bn2(self.conv2(out))) 47 | out = self.bn3(self.conv3(out)) 48 | res = self.shortcut(x) 49 | out = F.relu(torch.cat([out,res], 1)) if self.stride==2 else F.relu(out+res) 50 | return out 51 | 52 | 53 | class ShuffleNet(nn.Module): 54 | def __init__(self, cfg): 55 | super(ShuffleNet, self).__init__() 56 | out_planes = cfg['out_planes'] 57 | num_blocks = cfg['num_blocks'] 58 | groups = cfg['groups'] 59 | 60 | self.conv1 = nn.Conv2d(3, 24, kernel_size=1, bias=False) 61 | self.bn1 = nn.BatchNorm2d(24) 62 | self.in_planes = 24 63 | self.layer1 = self._make_layer(out_planes[0], num_blocks[0], groups) 64 | self.layer2 = self._make_layer(out_planes[1], num_blocks[1], groups) 65 | self.layer3 = self._make_layer(out_planes[2], num_blocks[2], groups) 66 | self.linear = nn.Linear(out_planes[2], 10) 67 | 68 | def _make_layer(self, out_planes, num_blocks, groups): 69 | layers = [] 70 | for i in range(num_blocks): 71 | stride = 2 if i == 0 else 1 72 | cat_planes = self.in_planes if i == 0 else 0 73 | layers.append(Bottleneck(self.in_planes, out_planes-cat_planes, stride=stride, groups=groups)) 74 | self.in_planes = out_planes 75 | return nn.Sequential(*layers) 76 | 77 | def forward(self, x): 78 | out = F.relu(self.bn1(self.conv1(x))) 79 | out = self.layer1(out) 80 | out = self.layer2(out) 81 | out = self.layer3(out) 82 | out = F.avg_pool2d(out, 4) 83 | out = out.view(out.size(0), -1) 84 | out = self.linear(out) 85 | return out 86 | 87 | 88 | def ShuffleNetG2(): 89 | cfg = { 90 | 'out_planes': [200,400,800], 91 | 'num_blocks': [4,8,4], 92 | 'groups': 2 93 | } 94 | return ShuffleNet(cfg) 95 | 96 | def ShuffleNetG3(): 97 | cfg = { 98 | 'out_planes': [240,480,960], 99 | 'num_blocks': [4,8,4], 100 | 'groups': 3 101 | } 102 | return ShuffleNet(cfg) 103 | 104 | 105 | def test(): 106 | net = ShuffleNetG2() 107 | x = Variable(torch.randn(1,3,32,32)) 108 | y = net(x) 109 | print(y) 110 | 111 | # test() 112 | -------------------------------------------------------------------------------- /folder.py: -------------------------------------------------------------------------------- 1 | import torch.utils.data as data 2 | 3 | from PIL import Image 4 | import os 5 | import os.path 6 | 7 | IMG_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.ppm', '.bmp', '.pgm'] 8 | 9 | 10 | def is_image_file(filename): 11 | """Checks if a file is an image. 12 | 13 | Args: 14 | filename (string): path to a file 15 | 16 | Returns: 17 | bool: True if the filename ends with a known image extension 18 | """ 19 | filename_lower = filename.lower() 20 | return any(filename_lower.endswith(ext) for ext in IMG_EXTENSIONS) 21 | 22 | 23 | def find_classes(dir): 24 | classes = [d for d in os.listdir(dir) if os.path.isdir(os.path.join(dir, d))] 25 | classes.sort() 26 | class_to_idx = {classes[i]: i for i in range(len(classes))} 27 | return classes, class_to_idx 28 | 29 | 30 | def make_dataset(dir, class_to_idx): 31 | images = [] 32 | dir = os.path.expanduser(dir) 33 | for target in sorted(os.listdir(dir)): 34 | d = os.path.join(dir, target) 35 | if not os.path.isdir(d): 36 | continue 37 | 38 | for root, _, fnames in sorted(os.walk(d)): 39 | for fname in sorted(fnames): 40 | if is_image_file(fname): 41 | path = os.path.join(root, fname) 42 | item = (path, class_to_idx[target]) 43 | images.append(item) 44 | 45 | return images 46 | 47 | 48 | def pil_loader(path): 49 | # open path as file to avoid ResourceWarning (https://github.com/python-pillow/Pillow/issues/835) 50 | with open(path, 'rb') as f: 51 | img = Image.open(f) 52 | return img.convert('RGB') 53 | 54 | 55 | def accimage_loader(path): 56 | import accimage 57 | try: 58 | return accimage.Image(path) 59 | except IOError: 60 | # Potentially a decoding problem, fall back to PIL.Image 61 | return pil_loader(path) 62 | 63 | 64 | def default_loader(path): 65 | from torchvision import get_image_backend 66 | if get_image_backend() == 'accimage': 67 | return accimage_loader(path) 68 | else: 69 | return pil_loader(path) 70 | 71 | 72 | class ImageFolder(data.Dataset): 73 | """A generic data loader where the images are arranged in this way: :: 74 | 75 | root/dog/xxx.png 76 | root/dog/xxy.png 77 | root/dog/xxz.png 78 | 79 | root/cat/123.png 80 | root/cat/nsdf3.png 81 | root/cat/asd932_.png 82 | 83 | Args: 84 | root (string): Root directory path. 85 | transform (callable, optional): A function/transform that takes in an PIL image 86 | and returns a transformed version. E.g, ``transforms.RandomCrop`` 87 | target_transform (callable, optional): A function/transform that takes in the 88 | target and transforms it. 89 | loader (callable, optional): A function to load an image given its path. 90 | 91 | Attributes: 92 | classes (list): List of the class names. 93 | class_to_idx (dict): Dict with items (class_name, class_index). 94 | imgs (list): List of (image path, class_index) tuples 95 | """ 96 | 97 | def __init__(self, root, transform=None, target_transform=None, 98 | loader=default_loader): 99 | classes, class_to_idx = find_classes(root) 100 | imgs = make_dataset(root, class_to_idx) 101 | if len(imgs) == 0: 102 | raise(RuntimeError("Found 0 images in subfolders of: " + root + "\n" 103 | "Supported image extensions are: " + ",".join(IMG_EXTENSIONS))) 104 | 105 | self.root = root 106 | self.imgs = imgs 107 | self.classes = classes 108 | self.class_to_idx = class_to_idx 109 | self.transform = transform 110 | self.target_transform = target_transform 111 | self.loader = loader 112 | 113 | def __getitem__(self, index): 114 | """ 115 | Args: 116 | index (int): Index 117 | 118 | Returns: 119 | tuple: (image, target) where target is class_index of the target class. 120 | """ 121 | path, target = self.imgs[index] 122 | img = self.loader(path) 123 | if self.transform is not None: 124 | img = self.transform(img) 125 | if self.target_transform is not None: 126 | target = self.target_transform(target) 127 | 128 | return img, target 129 | 130 | def __len__(self): 131 | return len(self.imgs) 132 | -------------------------------------------------------------------------------- /models/senet.py: -------------------------------------------------------------------------------- 1 | '''SENet in PyTorch. 2 | 3 | SENet is the winner of ImageNet-2017. The paper is not released yet. 4 | ''' 5 | import torch 6 | import torch.nn as nn 7 | import torch.nn.functional as F 8 | 9 | from torch.autograd import Variable 10 | 11 | 12 | class BasicBlock(nn.Module): 13 | def __init__(self, in_planes, planes, stride=1): 14 | super(BasicBlock, self).__init__() 15 | self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=3, stride=stride, padding=1, bias=False) 16 | self.bn1 = nn.BatchNorm2d(planes) 17 | self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=1, padding=1, bias=False) 18 | self.bn2 = nn.BatchNorm2d(planes) 19 | 20 | self.shortcut = nn.Sequential() 21 | if stride != 1 or in_planes != planes: 22 | self.shortcut = nn.Sequential( 23 | nn.Conv2d(in_planes, planes, kernel_size=1, stride=stride, bias=False), 24 | nn.BatchNorm2d(planes) 25 | ) 26 | 27 | # SE layers 28 | self.fc1 = nn.Conv2d(planes, planes//16, kernel_size=1) # Use nn.Conv2d instead of nn.Linear 29 | self.fc2 = nn.Conv2d(planes//16, planes, kernel_size=1) 30 | 31 | def forward(self, x): 32 | out = F.relu(self.bn1(self.conv1(x))) 33 | out = self.bn2(self.conv2(out)) 34 | 35 | # Squeeze 36 | w = F.avg_pool2d(out, out.size(2)) 37 | w = F.relu(self.fc1(w)) 38 | w = F.sigmoid(self.fc2(w)) 39 | # Excitation 40 | out = out * w # New broadcasting feature from v0.2! 41 | 42 | out += self.shortcut(x) 43 | out = F.relu(out) 44 | return out 45 | 46 | 47 | class PreActBlock(nn.Module): 48 | def __init__(self, in_planes, planes, stride=1): 49 | super(PreActBlock, self).__init__() 50 | self.bn1 = nn.BatchNorm2d(in_planes) 51 | self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=3, stride=stride, padding=1, bias=False) 52 | self.bn2 = nn.BatchNorm2d(planes) 53 | self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=1, padding=1, bias=False) 54 | 55 | if stride != 1 or in_planes != planes: 56 | self.shortcut = nn.Sequential( 57 | nn.Conv2d(in_planes, planes, kernel_size=1, stride=stride, bias=False) 58 | ) 59 | 60 | # SE layers 61 | self.fc1 = nn.Conv2d(planes, planes//16, kernel_size=1) 62 | self.fc2 = nn.Conv2d(planes//16, planes, kernel_size=1) 63 | 64 | def forward(self, x): 65 | out = F.relu(self.bn1(x)) 66 | shortcut = self.shortcut(out) if hasattr(self, 'shortcut') else x 67 | out = self.conv1(out) 68 | out = self.conv2(F.relu(self.bn2(out))) 69 | 70 | # Squeeze 71 | w = F.avg_pool2d(out, out.size(2)) 72 | w = F.relu(self.fc1(w)) 73 | w = F.sigmoid(self.fc2(w)) 74 | # Excitation 75 | out = out * w 76 | 77 | out += shortcut 78 | return out 79 | 80 | 81 | class SENet(nn.Module): 82 | def __init__(self, block, num_blocks, num_classes=10): 83 | super(SENet, self).__init__() 84 | self.in_planes = 64 85 | 86 | self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False) 87 | self.bn1 = nn.BatchNorm2d(64) 88 | self.layer1 = self._make_layer(block, 64, num_blocks[0], stride=1) 89 | self.layer2 = self._make_layer(block, 128, num_blocks[1], stride=2) 90 | self.layer3 = self._make_layer(block, 256, num_blocks[2], stride=2) 91 | self.layer4 = self._make_layer(block, 512, num_blocks[3], stride=2) 92 | self.linear = nn.Linear(512, num_classes) 93 | 94 | def _make_layer(self, block, planes, num_blocks, stride): 95 | strides = [stride] + [1]*(num_blocks-1) 96 | layers = [] 97 | for stride in strides: 98 | layers.append(block(self.in_planes, planes, stride)) 99 | self.in_planes = planes 100 | return nn.Sequential(*layers) 101 | 102 | def forward(self, x): 103 | out = F.relu(self.bn1(self.conv1(x))) 104 | out = self.layer1(out) 105 | out = self.layer2(out) 106 | out = self.layer3(out) 107 | out = self.layer4(out) 108 | out = F.avg_pool2d(out, 4) 109 | out = out.view(out.size(0), -1) 110 | out = self.linear(out) 111 | return out 112 | 113 | 114 | def SENet18(): 115 | return SENet(PreActBlock, [2,2,2,2]) 116 | 117 | 118 | def test(): 119 | net = SENet18() 120 | y = net(Variable(torch.randn(1,3,32,32))) 121 | print(y.size()) 122 | 123 | # test() 124 | -------------------------------------------------------------------------------- /models/resnet.py: -------------------------------------------------------------------------------- 1 | '''ResNet in PyTorch. 2 | 3 | For Pre-activation ResNet, see 'preact_resnet.py'. 4 | 5 | Reference: 6 | [1] Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun 7 | Deep Residual Learning for Image Recognition. arXiv:1512.03385 8 | ''' 9 | import torch 10 | import torch.nn as nn 11 | import torch.nn.functional as F 12 | 13 | from torch.autograd import Variable 14 | 15 | __all__ = ['resnet18', 'resnet34', 'resnet50', 'resnet101', 'resnet152'] 16 | 17 | class BasicBlock(nn.Module): 18 | expansion = 1 19 | 20 | def __init__(self, in_planes, planes, stride=1): 21 | super(BasicBlock, self).__init__() 22 | self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=3, stride=stride, padding=1, bias=False) 23 | self.bn1 = nn.BatchNorm2d(planes) 24 | self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=1, padding=1, bias=False) 25 | self.bn2 = nn.BatchNorm2d(planes) 26 | 27 | self.shortcut = nn.Sequential() 28 | if stride != 1 or in_planes != self.expansion*planes: 29 | self.shortcut = nn.Sequential( 30 | nn.Conv2d(in_planes, self.expansion*planes, kernel_size=1, stride=stride, bias=False), 31 | nn.BatchNorm2d(self.expansion*planes) 32 | ) 33 | 34 | def forward(self, x): 35 | out = F.relu(self.bn1(self.conv1(x))) 36 | out = self.bn2(self.conv2(out)) 37 | out += self.shortcut(x) 38 | out = F.relu(out) 39 | return out 40 | 41 | 42 | class Bottleneck(nn.Module): 43 | expansion = 4 44 | 45 | def __init__(self, in_planes, planes, stride=1): 46 | super(Bottleneck, self).__init__() 47 | self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=1, bias=False) 48 | self.bn1 = nn.BatchNorm2d(planes) 49 | self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride, padding=1, bias=False) 50 | self.bn2 = nn.BatchNorm2d(planes) 51 | self.conv3 = nn.Conv2d(planes, self.expansion*planes, kernel_size=1, bias=False) 52 | self.bn3 = nn.BatchNorm2d(self.expansion*planes) 53 | 54 | self.shortcut = nn.Sequential() 55 | if stride != 1 or in_planes != self.expansion*planes: 56 | self.shortcut = nn.Sequential( 57 | nn.Conv2d(in_planes, self.expansion*planes, kernel_size=1, stride=stride, bias=False), 58 | nn.BatchNorm2d(self.expansion*planes) 59 | ) 60 | 61 | def forward(self, x): 62 | out = F.relu(self.bn1(self.conv1(x))) 63 | out = F.relu(self.bn2(self.conv2(out))) 64 | out = self.bn3(self.conv3(out)) 65 | out += self.shortcut(x) 66 | out = F.relu(out) 67 | return out 68 | 69 | 70 | class ResNet(nn.Module): 71 | def __init__(self, block, num_blocks, num_classes=10): 72 | super(ResNet, self).__init__() 73 | self.in_planes = 64 74 | 75 | self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False) 76 | self.bn1 = nn.BatchNorm2d(64) 77 | self.layer1 = self._make_layer(block, 64, num_blocks[0], stride=1) 78 | self.layer2 = self._make_layer(block, 128, num_blocks[1], stride=2) 79 | self.layer3 = self._make_layer(block, 256, num_blocks[2], stride=2) 80 | self.layer4 = self._make_layer(block, 512, num_blocks[3], stride=2) 81 | self.linear = nn.Linear(512*block.expansion, num_classes) 82 | 83 | def _make_layer(self, block, planes, num_blocks, stride): 84 | strides = [stride] + [1]*(num_blocks-1) 85 | layers = [] 86 | for stride in strides: 87 | layers.append(block(self.in_planes, planes, stride)) 88 | self.in_planes = planes * block.expansion 89 | return nn.Sequential(*layers) 90 | 91 | def forward(self, x): 92 | out = F.relu(self.bn1(self.conv1(x))) 93 | out = self.layer1(out) 94 | out = self.layer2(out) 95 | out = self.layer3(out) 96 | out = self.layer4(out) 97 | out = F.avg_pool2d(out, 4) 98 | out = out.view(out.size(0), -1) 99 | out = self.linear(out) 100 | return out 101 | 102 | 103 | def resnet18(**kwargs): 104 | return ResNet(BasicBlock, [2,2,2,2], **kwargs) 105 | 106 | def resnet34(**kwargs): 107 | return ResNet(BasicBlock, [3,4,6,3], **kwargs) 108 | 109 | def resnet50(**kwargs): 110 | return ResNet(Bottleneck, [3,4,6,3], **kwargs) 111 | 112 | def resnet101(**kwargs): 113 | return ResNet(Bottleneck, [3,4,23,3], **kwargs) 114 | 115 | def resnet152(**kwargs): 116 | return ResNet(Bottleneck, [3,8,36,3], **kwargs) 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /models/preact_resnet.py: -------------------------------------------------------------------------------- 1 | '''Pre-activation ResNet in PyTorch. 2 | 3 | Reference: 4 | [1] Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun 5 | Identity Mappings in Deep Residual Networks. arXiv:1603.05027 6 | ''' 7 | import torch 8 | import torch.nn as nn 9 | import torch.nn.functional as F 10 | 11 | from torch.autograd import Variable 12 | 13 | 14 | class PreActBlock(nn.Module): 15 | '''Pre-activation version of the BasicBlock.''' 16 | expansion = 1 17 | 18 | def __init__(self, in_planes, planes, stride=1): 19 | super(PreActBlock, self).__init__() 20 | self.bn1 = nn.BatchNorm2d(in_planes) 21 | self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=3, stride=stride, padding=1, bias=False) 22 | self.bn2 = nn.BatchNorm2d(planes) 23 | self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=1, padding=1, bias=False) 24 | 25 | if stride != 1 or in_planes != self.expansion*planes: 26 | self.shortcut = nn.Sequential( 27 | nn.Conv2d(in_planes, self.expansion*planes, kernel_size=1, stride=stride, bias=False) 28 | ) 29 | 30 | def forward(self, x): 31 | out = F.relu(self.bn1(x)) 32 | shortcut = self.shortcut(out) if hasattr(self, 'shortcut') else x 33 | out = self.conv1(out) 34 | out = self.conv2(F.relu(self.bn2(out))) 35 | out += shortcut 36 | return out 37 | 38 | 39 | class PreActBottleneck(nn.Module): 40 | '''Pre-activation version of the original Bottleneck module.''' 41 | expansion = 4 42 | 43 | def __init__(self, in_planes, planes, stride=1): 44 | super(PreActBottleneck, self).__init__() 45 | self.bn1 = nn.BatchNorm2d(in_planes) 46 | self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=1, bias=False) 47 | self.bn2 = nn.BatchNorm2d(planes) 48 | self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride, padding=1, bias=False) 49 | self.bn3 = nn.BatchNorm2d(planes) 50 | self.conv3 = nn.Conv2d(planes, self.expansion*planes, kernel_size=1, bias=False) 51 | 52 | if stride != 1 or in_planes != self.expansion*planes: 53 | self.shortcut = nn.Sequential( 54 | nn.Conv2d(in_planes, self.expansion*planes, kernel_size=1, stride=stride, bias=False) 55 | ) 56 | 57 | def forward(self, x): 58 | out = F.relu(self.bn1(x)) 59 | shortcut = self.shortcut(out) if hasattr(self, 'shortcut') else x 60 | out = self.conv1(out) 61 | out = self.conv2(F.relu(self.bn2(out))) 62 | out = self.conv3(F.relu(self.bn3(out))) 63 | out += shortcut 64 | return out 65 | 66 | 67 | class PreActResNet(nn.Module): 68 | def __init__(self, block, num_blocks, num_classes=10): 69 | super(PreActResNet, self).__init__() 70 | self.in_planes = 64 71 | 72 | self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False) 73 | self.layer1 = self._make_layer(block, 64, num_blocks[0], stride=1) 74 | self.layer2 = self._make_layer(block, 128, num_blocks[1], stride=2) 75 | self.layer3 = self._make_layer(block, 256, num_blocks[2], stride=2) 76 | self.layer4 = self._make_layer(block, 512, num_blocks[3], stride=2) 77 | self.linear = nn.Linear(512*block.expansion, num_classes) 78 | 79 | def _make_layer(self, block, planes, num_blocks, stride): 80 | strides = [stride] + [1]*(num_blocks-1) 81 | layers = [] 82 | for stride in strides: 83 | layers.append(block(self.in_planes, planes, stride)) 84 | self.in_planes = planes * block.expansion 85 | return nn.Sequential(*layers) 86 | 87 | def forward(self, x): 88 | out = self.conv1(x) 89 | out = self.layer1(out) 90 | out = self.layer2(out) 91 | out = self.layer3(out) 92 | out = self.layer4(out) 93 | out = F.avg_pool2d(out, 4) 94 | out = out.view(out.size(0), -1) 95 | out = self.linear(out) 96 | return out 97 | 98 | 99 | def PreActResNet18(): 100 | return PreActResNet(PreActBlock, [2,2,2,2]) 101 | 102 | def PreActResNet34(): 103 | return PreActResNet(PreActBlock, [3,4,6,3]) 104 | 105 | def PreActResNet50(): 106 | return PreActResNet(PreActBottleneck, [3,4,6,3]) 107 | 108 | def PreActResNet101(): 109 | return PreActResNet(PreActBottleneck, [3,4,23,3]) 110 | 111 | def PreActResNet152(): 112 | return PreActResNet(PreActBottleneck, [3,8,36,3]) 113 | 114 | 115 | def test(): 116 | net = PreActResNet18() 117 | y = net(Variable(torch.randn(1,3,32,32))) 118 | print(y.size()) 119 | 120 | # test() 121 | -------------------------------------------------------------------------------- /models/vgg.py: -------------------------------------------------------------------------------- 1 | '''VGG for CIFAR10. FC layers are removed. 2 | (c) YANG, Wei 3 | ''' 4 | import torch.nn as nn 5 | import torch.utils.model_zoo as model_zoo 6 | import math 7 | 8 | 9 | __all__ = [ 10 | 'VGG', 'vgg11', 'vgg11_bn', 'vgg13', 'vgg13_bn', 'vgg16', 'vgg16_bn', 11 | 'vgg19_bn', 'vgg19', 12 | ] 13 | 14 | 15 | model_urls = { 16 | 'vgg11': 'https://download.pytorch.org/models/vgg11-bbd30ac9.pth', 17 | 'vgg13': 'https://download.pytorch.org/models/vgg13-c768596a.pth', 18 | 'vgg16': 'https://download.pytorch.org/models/vgg16-397923af.pth', 19 | 'vgg19': 'https://download.pytorch.org/models/vgg19-dcbb9e9d.pth', 20 | } 21 | 22 | 23 | class VGG(nn.Module): 24 | 25 | def __init__(self, features, num_classes=1000): 26 | super(VGG, self).__init__() 27 | self.features = features 28 | self.classifier = nn.Linear(512, num_classes) 29 | self._initialize_weights() 30 | 31 | def forward(self, x): 32 | x = self.features(x) 33 | x = x.view(x.size(0), -1) 34 | x = self.classifier(x) 35 | return x 36 | 37 | def _initialize_weights(self): 38 | for m in self.modules(): 39 | if isinstance(m, nn.Conv2d): 40 | n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels 41 | m.weight.data.normal_(0, math.sqrt(2. / n)) 42 | if m.bias is not None: 43 | m.bias.data.zero_() 44 | elif isinstance(m, nn.BatchNorm2d): 45 | m.weight.data.fill_(1) 46 | m.bias.data.zero_() 47 | elif isinstance(m, nn.Linear): 48 | n = m.weight.size(1) 49 | m.weight.data.normal_(0, 0.01) 50 | m.bias.data.zero_() 51 | 52 | 53 | def make_layers(cfg, batch_norm=False): 54 | layers = [] 55 | in_channels = 3 56 | for v in cfg: 57 | if v == 'M': 58 | layers += [nn.MaxPool2d(kernel_size=2, stride=2)] 59 | else: 60 | conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1) 61 | if batch_norm: 62 | layers += [conv2d, nn.BatchNorm2d(v), nn.ReLU(inplace=True)] 63 | else: 64 | layers += [conv2d, nn.ReLU(inplace=True)] 65 | in_channels = v 66 | return nn.Sequential(*layers) 67 | 68 | 69 | cfg = { 70 | 'A': [64, 'M', 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'], 71 | 'B': [64, 64, 'M', 128, 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'], 72 | 'D': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M'], 73 | 'E': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 256, 'M', 512, 512, 512, 512, 'M', 512, 512, 512, 512, 'M'], 74 | } 75 | 76 | 77 | def vgg11(**kwargs): 78 | """VGG 11-layer model (configuration "A") 79 | 80 | Args: 81 | pretrained (bool): If True, returns a model pre-trained on ImageNet 82 | """ 83 | model = VGG(make_layers(cfg['A']), **kwargs) 84 | return model 85 | 86 | 87 | def vgg11_bn(**kwargs): 88 | """VGG 11-layer model (configuration "A") with batch normalization""" 89 | model = VGG(make_layers(cfg['A'], batch_norm=True), **kwargs) 90 | return model 91 | 92 | 93 | def vgg13(**kwargs): 94 | """VGG 13-layer model (configuration "B") 95 | 96 | Args: 97 | pretrained (bool): If True, returns a model pre-trained on ImageNet 98 | """ 99 | model = VGG(make_layers(cfg['B']), **kwargs) 100 | return model 101 | 102 | 103 | def vgg13_bn(**kwargs): 104 | """VGG 13-layer model (configuration "B") with batch normalization""" 105 | model = VGG(make_layers(cfg['B'], batch_norm=True), **kwargs) 106 | return model 107 | 108 | 109 | def vgg16(**kwargs): 110 | """VGG 16-layer model (configuration "D") 111 | 112 | Args: 113 | pretrained (bool): If True, returns a model pre-trained on ImageNet 114 | """ 115 | model = VGG(make_layers(cfg['D']), **kwargs) 116 | return model 117 | 118 | 119 | def vgg16_bn(**kwargs): 120 | """VGG 16-layer model (configuration "D") with batch normalization""" 121 | model = VGG(make_layers(cfg['D'], batch_norm=True), **kwargs) 122 | return model 123 | 124 | 125 | def vgg19(**kwargs): 126 | """VGG 19-layer model (configuration "E") 127 | 128 | Args: 129 | pretrained (bool): If True, returns a model pre-trained on ImageNet 130 | """ 131 | model = VGG(make_layers(cfg['E']), **kwargs) 132 | return model 133 | 134 | 135 | def vgg19_bn(**kwargs): 136 | """VGG 19-layer model (configuration 'E') with batch normalization""" 137 | model = VGG(make_layers(cfg['E'], batch_norm=True), **kwargs) 138 | return model 139 | -------------------------------------------------------------------------------- /predict.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function, division 2 | import torch 3 | import torch.nn as nn 4 | from torch.autograd import Variable 5 | from torchvision import models, transforms 6 | import time 7 | import os 8 | import shutil 9 | import argparse 10 | from PIL import Image 11 | from tqdm import tqdm 12 | 13 | from folder import pil_loader 14 | 15 | #from data_provider import class_names 16 | from util import load_checkpoint 17 | from constant import ROOT_PATH 18 | 19 | 20 | model_names = sorted(name for name in models.__dict__ 21 | if name.islower() and not name.startswith("__") 22 | and callable(models.__dict__[name])) 23 | os.environ["CUDA_VISIBLE_DEVICES"] = "1" 24 | 25 | data_transforms = transforms.Compose([ 26 | transforms.RandomSizedCrop(360), 27 | transforms.ToTensor(), 28 | transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) 29 | ]) 30 | 31 | data_dir = os.path.join(ROOT_PATH, 'pigtest', 'ImageData') 32 | class_names = [line.strip() for line in open(os.path.join(ROOT_PATH, 'pigtrain', 'classnames.txt')).readlines()] 33 | print(len(class_names)) 34 | 35 | def parse_args(): 36 | parser = argparse.ArgumentParser(description='') 37 | parser.add_argument('--arch', '-a', metavar='ARCH', default='resnet', 38 | choices=model_names, help='model architecture: ' + 39 | ' | '.join(model_names) + ' (default: resnet18)') 40 | parser.add_argument('--resume', default='', type=str, metavar='PATH', 41 | help='path to latest checkpoint (default: none)') 42 | args = parser.parse_args() 43 | return args 44 | 45 | use_gpu = torch.cuda.is_available() 46 | 47 | def test(model, res_dir): 48 | 49 | imgset = os.path.join(ROOT_PATH, 'pigtest', 'ImageSets', 'pigtest.txt') 50 | img_ids = [line.strip() for line in open(imgset).readlines()] 51 | res = {} 52 | if not os.path.exists(res_dir): 53 | os.makedirs(res_dir) 54 | res_file = os.path.join(res_dir, 'submission.csv') 55 | m = nn.Softmax() 56 | fw = open(res_file, 'w') 57 | with tqdm(total=len(img_ids)) as pbar: 58 | for i, p_id in enumerate(img_ids): 59 | img_path = os.path.join(data_dir, p_id) 60 | im_data = pil_loader(img_path) 61 | im_data = data_transforms(im_data) 62 | im_data.unsqueeze_(0) 63 | 64 | 65 | if use_gpu: 66 | im_data = Variable(im_data.cuda()) 67 | else: 68 | im_data = Variable(im_data) 69 | 70 | output = model(im_data) 71 | output = m(output) 72 | max_p, index = torch.max(output.data, 1) 73 | 74 | res[p_id.split('.')[0]] = output.data 75 | for k in range(len(class_names)): 76 | fw.write('{},{},{}\n'.format(p_id.split('.')[0], class_names[k], output.data[0][k])) 77 | 78 | pbar.update() 79 | 80 | fw.close() 81 | with open('submission.csv', 'w') as f: 82 | for k in res: 83 | for i in range(len(class_names)): 84 | f.write('{},{},{}\n'.format(k, class_names[i],res[k][0][i])) 85 | 86 | #_, preds = torch.max(outputs.data, 1) 87 | 88 | def main(rootpath=ROOT_PATH): 89 | args = parse_args() 90 | 91 | 92 | if args.arch == 'resnet101': 93 | model = models.resnet101(pretrained=False) 94 | num_ftrs = model.fc.in_features 95 | model.fc = nn.Linear(num_ftrs, len(class_names)) 96 | elif args.arch == 'resnet152': 97 | model = models.resnet152(pretrained=False) 98 | num_ftrs = model.fc.in_features 99 | model.fc = nn.Linear(num_ftrs, len(class_names)) 100 | elif args.arch == 'denset121': 101 | model = models.desnet121(pretrained=False) 102 | num_ftrs = model.classifer.in_features 103 | model.classifier = nn.Linear(num_ftrs, len(class_names)) 104 | elif args.arch == 'inception_v3': 105 | model = models.inception_v3(pretrained=False) 106 | num_ftrs = model.fc.in_features 107 | model.fc = nn.Linear(num_ftrs, len(class_names)) 108 | 109 | checkpoint = load_checkpoint(resume) 110 | 111 | start_epoch = checkpoint['epoch'] 112 | best_acc = checkpoint['best_prec1'] 113 | model.load_state_dict(checkpoint['state_dict']) 114 | 115 | #optimizer.load_state_dict(checkpoint['optimizer']) 116 | print("=> loaded checkpoint '{}' (epoch {}, val acc {})".format(resume, checkpoint['epoch'], best_acc)) 117 | 118 | if use_gpu: 119 | model = model.cuda() 120 | model.train(False) 121 | 122 | test(model, res_dir) 123 | 124 | if __name__ == '__main__': 125 | main() 126 | -------------------------------------------------------------------------------- /augmentation.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import cv2 3 | 4 | def enhance(im): 5 | lab= cv2.cvtColor(np.array(im), cv2.COLOR_BGR2LAB) 6 | l, a, b = cv2.split(lab) 7 | clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(20,30)) 8 | cl = clahe.apply(l) 9 | limg = cv2.merge((cl,a,b)) 10 | im_new = cv2.cvtColor(limg, cv2.COLOR_LAB2BGR) 11 | return im_new 12 | 13 | class Augmentation(object): 14 | def resize(self,im,size=(512,512),e=1e-15): 15 | if im.dtype != 'float32': 16 | im = im.astype(np.float32) 17 | im_min, im_max = np.min(im),np.max(im) 18 | im_std = (im - im_min) / (im_max - im_min + e) 19 | resized_std = cv2.resize(im_std, size) 20 | resized_im = resized_std * (im_max - im_min) + im_min 21 | return resized_im 22 | 23 | def rotate(self,im,rotation_param,keep_aspect_ratio): 24 | h,w,_ = im.shape 25 | cX,cY = w // 2, h // 2 26 | M = cv2.getRotationMatrix2D((cX,cY),-rotation_param,1.0) 27 | cos = np.abs(M[0,0]) 28 | sin = np.abs(M[0,1]) 29 | 30 | if not keep_aspect_ratio: 31 | M[0,2] += (w // 2) - cX 32 | M[1,2] += (h // 2) - cY 33 | im_new = cv2.warpAffine(np.array(im,dtype=np.float32),M,(w,h)) 34 | return im_new 35 | 36 | nW = int(h*sin + w*cos) 37 | nH = int(h*cos + w*sin) 38 | M[0,2] += (nW // 2) - cX 39 | M[1,2] += (nH // 2) - cY 40 | im_new = cv2.warpAffine(np.array(im,dtype=np.float32),M,(nW,nH)) 41 | 42 | x0 = int(max(0,(nW-w)/2)) 43 | x1 = int(min((nW+w)/2,nW)) 44 | y0 = int(max(0,(nH-h)/2)) 45 | y1 = int(min((nH+h)/2,nH)) 46 | return im_new[y0:y1, x0:x1] 47 | 48 | 49 | def flip(self,im): 50 | return cv2.flip(im,1) 51 | 52 | def zoom(self,im,w_dev): 53 | h,w,_ = im.shape 54 | #TODO 55 | #h_dev = int(..) keep aspect ratio 56 | return im[w_dev:h-w_dev, w_dev:w-w_dev] 57 | 58 | def crop(self,im,w0=0,w1=0,h0=0,h1=0): 59 | h,w,_ = im.shape 60 | return im[h0:h-h1, w0:w-w1] 61 | 62 | def enhance(self,im): 63 | lab= cv2.cvtColor(np.array(im), cv2.COLOR_BGR2LAB) 64 | l, a, b = cv2.split(lab) 65 | clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(20,30)) 66 | cl = clahe.apply(l) 67 | limg = cv2.merge((cl,a,b)) 68 | im_new = cv2.cvtColor(limg, cv2.COLOR_LAB2BGR) 69 | return im_new 70 | 71 | def clip(self,im): 72 | im[im>255] = 255 73 | im[im<0] = 0 74 | return im 75 | 76 | def brightness(self,im,alpha): 77 | im *= alpha 78 | return im 79 | 80 | def contrast(self,im,alpha): 81 | coef = np.array([[[0.299, 0.587,0.114]]]) 82 | gray = im * coef 83 | gray = (3.0 * (1.0 - alpha) / gray.size) * np.sum(gray) 84 | im *= alpha 85 | im += gray 86 | return im 87 | 88 | def saturation(self,im,alpha): 89 | coef = np.array([[[0.299, 0.587,0.114]]]) 90 | gray = im * coef 91 | gray = np.sum(gray, axis=2, keepdims=True) 92 | gray *= (1.0 - alpha) 93 | im *= alpha 94 | im += gray 95 | return im 96 | 97 | def gamma_trans(self,im,alpha): 98 | if im.dtype != 'float32': 99 | im = im.astype(np.float32) 100 | im /= 255 101 | f = lambda x:x**alpha 102 | im_trans = f(im) 103 | return im_trans*255 104 | 105 | def multiple_rgb(self,im,alphas): 106 | for i in xrange(len(alphas)): 107 | im[:,:,[i]] *= alphas[i] 108 | return im 109 | 110 | class OurAug(Augmentation): 111 | 112 | def __init__(self, params): 113 | self.aug_cfg = params 114 | 115 | def process(self, img, rand_values=None): 116 | chosen_value = {} 117 | 118 | im = np.copy(img) 119 | output_shape = (self.aug_cfg['out_h'],self.aug_cfg['out_w']) 120 | if self.aug_cfg.get('rotation',False): 121 | if rand_values: 122 | rotate_params = rand_values['rotate_params'] 123 | else: 124 | rotate_params = np.random.randint(self.aug_cfg['rotation_range'][0], 125 | self.aug_cfg['rotation_range'][1]) 126 | chosen_value['rotate_params'] = rotate_params 127 | im = self.rotate(im,rotate_params,self.aug_cfg["keep_aspect_ratio"]) 128 | 129 | 130 | if self.aug_cfg.get('crop',False): 131 | if rand_values: 132 | do_crop = rand_values['do_crop'] 133 | else: 134 | do_crop = self.aug_cfg['crop_prob'] > np.random.rand() 135 | chosen_value['do_crop'] = do_crop 136 | 137 | if do_crop: 138 | 139 | if rand_values: 140 | w0, w1 = rand_values['w0'], rand_values['w1'] 141 | h0, h1 = rand_values['h0'], rand_values['h1'] 142 | else: 143 | h,w,_ = im.shape 144 | w_dev = int(self.aug_cfg['crop_w'] * w) 145 | h_dev = int(self.aug_cfg['crop_h'] * h) 146 | 147 | w0 = np.random.randint(0, w_dev + 1) 148 | w1 = np.random.randint(0, w_dev + 1) 149 | h0 = np.random.randint(0, h_dev + 1) 150 | h1 = np.random.randint(0, h_dev + 1) 151 | 152 | chosen_value['w0'] = w0 153 | chosen_value['w1'] = w1 154 | chosen_value['h0'] = h0 155 | chosen_value['h1'] = h1 156 | im = self.crop(im,w0,w1,h0,h1) 157 | 158 | if self.aug_cfg.get('gamma',False): 159 | if rand_values: 160 | pass 161 | else: 162 | gamma_options = self.aug_cfg['gamma_options'] 163 | rand = np.random.randint(0,len(gamma_options)) 164 | gamma_param = gamma_options[rand] 165 | chosen_value['gamma_param'] = gamma_param 166 | im = self.gamma_trans(im,gamma_param) 167 | 168 | if self.aug_cfg.get('contrast',False): 169 | if rand_values: 170 | pass 171 | else: 172 | contrast_param = np.random.uniform(self.aug_cfg['contrast_range'][0], 173 | self.aug_cfg['contrast_range'][1]) 174 | chosen_value['contrast_param'] = contrast_param 175 | im = self.contrast(im,contrast_param) 176 | 177 | 178 | if self.aug_cfg.get('brightness',False): 179 | if rand_values: 180 | pass 181 | else: 182 | 183 | brightness_param = np.random.uniform(self.aug_cfg['brightness_range'][0], 184 | self.aug_cfg['brightness_range'][1]) 185 | chosen_value['brightness_param'] = brightness_param 186 | im = self.brightness(im,brightness_param) 187 | 188 | if self.aug_cfg.get('saturation',False): 189 | if rand_values: 190 | pass 191 | else: 192 | saturation_param = np.random.uniform(self.aug_cfg['saturation_range'][0], 193 | self.aug_cfg['saturation_range'][1]) 194 | chosen_value['saturation_param'] = saturation_param 195 | im = self.saturation(im, saturation_param) 196 | 197 | if self.aug_cfg.get('flip',False): 198 | if rand_values: 199 | do_flip = rand_values['do_flip'] 200 | else: 201 | do_flip = self.aug_cfg['flip_prob'] > np.random.rand() 202 | chosen_value['do_flip'] = do_flip 203 | 204 | if do_flip: 205 | im = self.flip(im) 206 | 207 | if self.aug_cfg.get('zoom',False): 208 | if rand_values: 209 | do_zoom = rand_values['do_zoom'] 210 | else: 211 | do_zoom = self.aug_cfg['zoom_prob'] > np.random.rand() 212 | chosen_value['do_zoom'] = do_zoom 213 | 214 | if do_zoom: 215 | if rand_values: 216 | w_dev = rand_values['w_dev'] 217 | else: 218 | zoom_min, zoom_max = self.aug_cfg['zoom_range'] 219 | h,w,_ = im.shape 220 | w_dev = int(np.random.uniform(zoom_min, zoom_max) / 2 * w) 221 | chosen_value['w_dev'] = w_dev 222 | im = self.zoom(im,w_dev) 223 | 224 | if tuple(im.shape[:2]) != output_shape: 225 | im = self.resize(im,output_shape) 226 | return im, chosen_value -------------------------------------------------------------------------------- /trainer.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function, division 2 | 3 | import torch 4 | import torch.nn as nn 5 | import torch.optim as optim 6 | from torch.optim import lr_scheduler 7 | from torch.autograd import Variable 8 | import numpy as np 9 | import torchvision 10 | import models 11 | import time 12 | import os 13 | import sys 14 | import shutil 15 | import argparse 16 | import logging 17 | from utils.keras_generic_utils import Progbar 18 | from utils import save_checkpoint, load_checkpoint, AverageMeter, Logger, accuracy, mkdir_p 19 | 20 | from constant import ROOT_PATH 21 | from data_provider import dataloders, class_names, batch_nums 22 | 23 | logprint = logging.getLogger(__file__) 24 | logging.basicConfig( 25 | format="[%(asctime)s - %(filename)s:line %(lineno)s] %(message)s", 26 | datefmt='%d %b %H:%M:%S') 27 | logprint.setLevel(logging.INFO) 28 | 29 | model_names = sorted(name for name in models.__dict__ 30 | if name.islower() and not name.startswith("__") 31 | and callable(models.__dict__[name])) 32 | print(model_names) 33 | #os.environ["CUDA_VISIBLE_DEVICES"] = "0" 34 | 35 | use_gpu = torch.cuda.is_available() 36 | print ('use gpu? {}'.format(use_gpu)) 37 | 38 | def parse_args(): 39 | parser = argparse.ArgumentParser(description='PyTorch CIFAR10 Training') 40 | parser.add_argument('--arch', '-a', metavar='ARCH', default='resnet18', 41 | choices=model_names, help='model architecture: ' + 42 | ' | '.join(model_names) + ' (default: resnet18)') 43 | parser.add_argument('--epochs', default=300, type=int, metavar='N', 44 | help='number of total epochs to run') 45 | parser.add_argument('--lr', '--learning-rate', default=0.001, type=float, 46 | metavar='LR', help='initial learning rate') 47 | parser.add_argument('--gamma', type=float, default=0.1, help='LR is multiplied by gamma on schedule.') 48 | parser.add_argument('--momentum', default=0.9, type=float, metavar='M', 49 | help='momentum') 50 | parser.add_argument('-c', '--checkpoint', default='checkpoint', type=str, metavar='PATH', 51 | help='path to save checkpoint (default: checkpoint)') 52 | parser.add_argument('--resume', default='', type=str, metavar='PATH', 53 | help='path to latest checkpoint (default: none)') 54 | parser.add_argument('-e', '--evaluate', dest='evaluate', action='store_true', 55 | help='evaluate model on validation set') 56 | parser.add_argument('--start-epoch', default=0, type=int, metavar='N', 57 | help='manual epoch number (useful on restarts)') 58 | parser.add_argument('--overwrite', dest='overwrite', action='store_true', 59 | help='overwrite checkpoint ') 60 | args = parser.parse_args() 61 | return args 62 | 63 | 64 | def validate(val_loader, model, criterion, logger): 65 | batch_time = AverageMeter() 66 | losses = AverageMeter() 67 | top1 = AverageMeter() 68 | top5 = AverageMeter() 69 | 70 | # switch to evaluate mode 71 | model.eval() 72 | 73 | end = time.time() 74 | 75 | print() 76 | progbar = Progbar(batch_nums['val']) 77 | for i, (inputs, target) in enumerate(val_loader): 78 | if use_gpu: 79 | input_var = Variable(inputs.cuda(), volatile=True) 80 | target_var = Variable(target.cuda()) 81 | else: 82 | input_var, target_var = Variable(inputs), Variable(target) 83 | 84 | # compute output 85 | output = model(input_var) 86 | loss = criterion(output, target_var) 87 | 88 | # measure accuracy and record loss 89 | prec1, prec5 = accuracy(output.data, target_var.data, topk=(1, 5)) 90 | losses.update(loss.data[0], inputs.size(0)) 91 | top1.update(prec1[0], inputs.size(0)) 92 | top5.update(prec5[0], inputs.size(0)) 93 | 94 | # measure elapsed time 95 | batch_time.update(time.time() - end) 96 | end = time.time() 97 | 98 | if args.evaluate: 99 | logger.append([losses.avg, top1.avg]) 100 | else: 101 | logger.append([None, None, losses.avg, None, top1.avg]) 102 | progbar.add(1, values=[("p1", top1.avg), ("p5", top5.avg), ("loss", losses.avg)]) 103 | 104 | ''' 105 | if i % print_freq == 0: 106 | print('Test: [{0}/{1}]\t' 107 | 'Time {batch_time.val:.3f} ({batch_time.avg:.3f})\t' 108 | 'Loss {loss.val:.4f} ({loss.avg:.4f})\t' 109 | 'Prec@1 {top1.val:.3f} ({top1.avg:.3f})\t' 110 | 'Prec@5 {top5.val:.3f} ({top5.avg:.3f})'.format( 111 | i, len(val_loader), batch_time=batch_time, loss=losses, 112 | top1=top1, top5=top5)) 113 | ''' 114 | print() 115 | print(' * Current Prec@1 {top1.avg:.3f} Prec@5 {top5.avg:.3f}' 116 | .format(top1=top1, top5=top5)) 117 | 118 | return top1.avg 119 | 120 | def train_model(model, criterion, optimizer, scheduler): 121 | since = time.time() 122 | 123 | losses = AverageMeter() 124 | top1 = AverageMeter() 125 | top5 = AverageMeter() 126 | 127 | best_model_wts = model.state_dict() 128 | best_prec1 = 0 129 | train_loader = dataloders['train'] 130 | train_batch_num = batch_nums['train'] 131 | start_epoch = args.start_epoch 132 | 133 | stop = 0 134 | 135 | log_file = os.path.join(args.checkpoint, 'log.txt') 136 | 137 | title = 'cifar-10-' + args.arch 138 | if args.resume: 139 | print('==> Resuming from checkpoint...') 140 | print(args.resume) 141 | assert os.path.isfile(args.resume), 'Error: no checkpoint directory found!' 142 | args.checkpoint = os.path.dirname(args.resume) 143 | checkpoint = torch.load(args.resume) 144 | best_prec1 = checkpoint['best_prec1'] 145 | start_epoch = checkpoint['epoch']+1 146 | model.load_state_dict(checkpoint['state_dict']) 147 | optimizer.load_state_dict(checkpoint['optimizer']) 148 | logger = Logger(log_file, title=title, resume=True) 149 | else: 150 | if os.path.exists(log_file) and not args.overwrite: 151 | logprint.info('%s exists. skip', log_file) 152 | return 0 153 | logger = Logger(log_file, title=title) 154 | logger.set_names(['Learning Rate', 'Train Loss', 'Valid Loss', 'Train Acc.', 'Valid Acc.']) 155 | 156 | for epoch in range(start_epoch, args.epochs): 157 | 158 | scheduler.step() 159 | model.train() # Set model to training mode 160 | 161 | print('Epoch [{} | {}] LR: {}'.format(epoch, args.epochs - 1, scheduler.get_lr()[0])) 162 | print('-' * 10) 163 | progbar = Progbar(train_batch_num) 164 | 165 | # Iterate over data. 166 | for batch_index, data in enumerate(train_loader): 167 | # get the inputs 168 | inputs, labels = data 169 | # wrap them in Variable 170 | if use_gpu: 171 | inputs = Variable(inputs.cuda()) 172 | labels = Variable(labels.cuda(async=True)) 173 | else: 174 | inputs, labels = Variable(inputs), Variable(labels) 175 | 176 | # forward 177 | outputs = model(inputs) 178 | loss = criterion(outputs, labels) 179 | 180 | prec1, prec5 = accuracy(outputs.data, labels.data, topk=(1, 5)) 181 | losses.update(loss.data[0], inputs.size(0)) 182 | top1.update(prec1[0], inputs.size(0)) 183 | top5.update(prec5[0], inputs.size(0)) 184 | 185 | # backward + optimize 186 | optimizer.zero_grad() 187 | loss.backward() 188 | optimizer.step() 189 | 190 | # statistics 191 | logger.append([scheduler.get_lr()[0], losses.avg, None, top1.avg, None]) 192 | progbar.add(1, values=[("p1", top1.avg), ("p5", top5.avg), ("loss", losses.avg)]) 193 | # end of an epoch 194 | print() 195 | print('Train Loss epoch {epoch} {loss.val:.4f} ({loss.avg:.4f})\t' 196 | 'Prec@1 {top1.val:.3f} ({top1.avg:.3f})\t' 197 | 'Prec@5 {top5.val:.3f} ({top5.avg:.3f})'.format( 198 | epoch=epoch, loss=losses, top1=top1, top5=top5)) 199 | 200 | # evaluate on validation set 201 | prec1 = validate(dataloders['val'], model, criterion, logger) 202 | is_best = prec1 > best_prec1 203 | best_prec1 = max(prec1, best_prec1) 204 | 205 | stop += 1 206 | print(' * Best Prec@1 {top1:.3f}'.format(top1=best_prec1)) 207 | 208 | save_checkpoint({ 209 | 'epoch': epoch, 210 | 'state_dict': model.state_dict(), 211 | 'best_prec1': best_prec1, 212 | 'optimizer' : optimizer.state_dict(), 213 | }, is_best, filename='checkpoint_epoch{epoch}.pth.tar'.format(epoch=epoch), checkpoint=args.checkpoint) 214 | # deep copy the model 215 | if is_best: 216 | best_model_wts = model.state_dict() 217 | print ('better model obtained at epoch {epoch}'.format(epoch=epoch)) 218 | stop = 0 219 | if(stop >= 20): 220 | print("\nEarly stop happend at {}\n".format(epoch)) 221 | break 222 | 223 | print() 224 | 225 | time_elapsed = time.time() - since 226 | print('Training complete in {:.0f}m {:.0f}s'.format( 227 | time_elapsed // 60, time_elapsed % 60)) 228 | print('Best val Acc: {:4f}'.format(best_prec1)) 229 | 230 | # load best model weights 231 | model.load_state_dict(best_model_wts) 232 | return model 233 | 234 | 235 | 236 | def main(argv=None): 237 | 238 | global args 239 | args = parse_args() 240 | 241 | args.checkpoint = os.path.join(args.checkpoint, args.arch+'_lr_'+str(args.lr)) 242 | 243 | if not os.path.isdir(args.checkpoint): 244 | mkdir_p(args.checkpoint) 245 | 246 | print("==> creating model '{}'".format(args.arch)) 247 | model = models.__dict__[args.arch](num_classes=len(class_names)) 248 | 249 | if use_gpu: 250 | model = model.cuda() 251 | 252 | # Observe that all parameters are being optimized 253 | optimizer = optim.SGD(model.parameters(), lr=args.lr, momentum=args.momentum) 254 | 255 | print(' Total params: %.2fM' % (sum(p.numel() for p in model.parameters())/1000000.0)) 256 | 257 | 258 | criterion = nn.CrossEntropyLoss() 259 | 260 | if args.evaluate: 261 | print('\nEvaluating only ...') 262 | if args.resume: 263 | print('==> Resuming from checkpoint...') 264 | print(args.resume) 265 | assert os.path.isfile(args.resume), 'Error: no checkpoint directory found!' 266 | args.checkpoint = os.path.dirname(args.resume) 267 | checkpoint = torch.load(args.resume) 268 | best_prec1 = checkpoint['best_prec1'] 269 | model.load_state_dict(checkpoint['state_dict']) 270 | print ('best_prec1 hit@1 {acc:.4f}'.format(acc=best_prec1)) 271 | 272 | title = 'cifar-10-' + args.arch 273 | logger = Logger(os.path.join(args.checkpoint, 'evaluate-log.txt'), title=title) 274 | logger.set_names(['Valid Loss', 'Valid Acc.']) 275 | 276 | val_acc = validate(dataloders['val'], model, criterion, logger) 277 | print ('val hit@1 {acc:.4f}'.format(acc=val_acc)) 278 | print ('-'*10) 279 | return 280 | 281 | # Decay LR by a factor of 0.1 every 10 epochs 282 | exp_lr_scheduler = lr_scheduler.StepLR(optimizer, step_size=10, gamma=args.gamma) 283 | 284 | model = train_model(model, criterion, optimizer, exp_lr_scheduler) 285 | 286 | 287 | if __name__ == '__main__': 288 | sys.exit(main()) 289 | 290 | -------------------------------------------------------------------------------- /utils/keras_generic_utils.py: -------------------------------------------------------------------------------- 1 | """Python utilities required by Keras.""" 2 | from __future__ import absolute_import 3 | 4 | import numpy as np 5 | 6 | import time 7 | import sys 8 | import six 9 | import marshal 10 | import types as python_types 11 | import inspect 12 | 13 | _GLOBAL_CUSTOM_OBJECTS = {} 14 | 15 | 16 | class CustomObjectScope(object): 17 | """Provides a scope that changes to `_GLOBAL_CUSTOM_OBJECTS` cannot escape. 18 | 19 | Code within a `with` statement will be able to access custom objects 20 | by name. Changes to global custom objects persist 21 | within the enclosing `with` statement. At end of the `with` statement, 22 | global custom objects are reverted to state 23 | at beginning of the `with` statement. 24 | 25 | # Example 26 | 27 | Consider a custom object `MyObject` 28 | 29 | ```python 30 | with CustomObjectScope({'MyObject':MyObject}): 31 | layer = Dense(..., kernel_regularizer='MyObject') 32 | # save, load, etc. will recognize custom object by name 33 | ``` 34 | """ 35 | 36 | def __init__(self, *args): 37 | self.custom_objects = args 38 | self.backup = None 39 | 40 | def __enter__(self): 41 | self.backup = _GLOBAL_CUSTOM_OBJECTS.copy() 42 | for objects in self.custom_objects: 43 | _GLOBAL_CUSTOM_OBJECTS.update(objects) 44 | return self 45 | 46 | def __exit__(self, *args, **kwargs): 47 | _GLOBAL_CUSTOM_OBJECTS.clear() 48 | _GLOBAL_CUSTOM_OBJECTS.update(self.backup) 49 | 50 | 51 | def custom_object_scope(*args): 52 | """Provides a scope that changes to `_GLOBAL_CUSTOM_OBJECTS` cannot escape. 53 | 54 | Convenience wrapper for `CustomObjectScope`. 55 | Code within a `with` statement will be able to access custom objects 56 | by name. Changes to global custom objects persist 57 | within the enclosing `with` statement. At end of the `with` statement, 58 | global custom objects are reverted to state 59 | at beginning of the `with` statement. 60 | 61 | # Example 62 | 63 | Consider a custom object `MyObject` 64 | 65 | ```python 66 | with custom_object_scope({'MyObject':MyObject}): 67 | layer = Dense(..., kernel_regularizer='MyObject') 68 | # save, load, etc. will recognize custom object by name 69 | ``` 70 | 71 | # Arguments 72 | *args: Variable length list of dictionaries of name, 73 | class pairs to add to custom objects. 74 | 75 | # Returns 76 | Object of type `CustomObjectScope`. 77 | """ 78 | return CustomObjectScope(*args) 79 | 80 | 81 | def get_custom_objects(): 82 | """Retrieves a live reference to the global dictionary of custom objects. 83 | 84 | Updating and clearing custom objects using `custom_object_scope` 85 | is preferred, but `get_custom_objects` can 86 | be used to directly access `_GLOBAL_CUSTOM_OBJECTS`. 87 | 88 | # Example 89 | 90 | ```python 91 | get_custom_objects().clear() 92 | get_custom_objects()['MyObject'] = MyObject 93 | ``` 94 | 95 | # Returns 96 | Global dictionary of names to classes (`_GLOBAL_CUSTOM_OBJECTS`). 97 | """ 98 | return _GLOBAL_CUSTOM_OBJECTS 99 | 100 | 101 | def serialize_keras_object(instance): 102 | if instance is None: 103 | return None 104 | if hasattr(instance, 'get_config'): 105 | return { 106 | 'class_name': instance.__class__.__name__, 107 | 'config': instance.get_config() 108 | } 109 | if hasattr(instance, '__name__'): 110 | return instance.__name__ 111 | else: 112 | raise ValueError('Cannot serialize', instance) 113 | 114 | 115 | def deserialize_keras_object(identifier, module_objects=None, 116 | custom_objects=None, 117 | printable_module_name='object'): 118 | if isinstance(identifier, dict): 119 | # In this case we are dealing with a Keras config dictionary. 120 | config = identifier 121 | if 'class_name' not in config or 'config' not in config: 122 | raise ValueError('Improper config format: ' + str(config)) 123 | class_name = config['class_name'] 124 | if custom_objects and class_name in custom_objects: 125 | cls = custom_objects[class_name] 126 | elif class_name in _GLOBAL_CUSTOM_OBJECTS: 127 | cls = _GLOBAL_CUSTOM_OBJECTS[class_name] 128 | else: 129 | module_objects = module_objects or {} 130 | cls = module_objects.get(class_name) 131 | if cls is None: 132 | raise ValueError('Unknown ' + printable_module_name + 133 | ': ' + class_name) 134 | if hasattr(cls, 'from_config'): 135 | custom_objects = custom_objects or {} 136 | if has_arg(cls.from_config, 'custom_objects'): 137 | return cls.from_config(config['config'], 138 | custom_objects=dict(list(_GLOBAL_CUSTOM_OBJECTS.items()) + 139 | list(custom_objects.items()))) 140 | with CustomObjectScope(custom_objects): 141 | return cls.from_config(config['config']) 142 | else: 143 | # Then `cls` may be a function returning a class. 144 | # in this case by convention `config` holds 145 | # the kwargs of the function. 146 | custom_objects = custom_objects or {} 147 | with CustomObjectScope(custom_objects): 148 | return cls(**config['config']) 149 | elif isinstance(identifier, six.string_types): 150 | function_name = identifier 151 | if custom_objects and function_name in custom_objects: 152 | fn = custom_objects.get(function_name) 153 | elif function_name in _GLOBAL_CUSTOM_OBJECTS: 154 | fn = _GLOBAL_CUSTOM_OBJECTS[function_name] 155 | else: 156 | fn = module_objects.get(function_name) 157 | if fn is None: 158 | raise ValueError('Unknown ' + printable_module_name + 159 | ':' + function_name) 160 | return fn 161 | else: 162 | raise ValueError('Could not interpret serialized ' + 163 | printable_module_name + ': ' + identifier) 164 | 165 | 166 | def func_dump(func): 167 | """Serializes a user defined function. 168 | 169 | # Arguments 170 | func: the function to serialize. 171 | 172 | # Returns 173 | A tuple `(code, defaults, closure)`. 174 | """ 175 | code = marshal.dumps(func.__code__).decode('raw_unicode_escape') 176 | defaults = func.__defaults__ 177 | if func.__closure__: 178 | closure = tuple(c.cell_contents for c in func.__closure__) 179 | else: 180 | closure = None 181 | return code, defaults, closure 182 | 183 | 184 | def func_load(code, defaults=None, closure=None, globs=None): 185 | """Deserializes a user defined function. 186 | 187 | # Arguments 188 | code: bytecode of the function. 189 | defaults: defaults of the function. 190 | closure: closure of the function. 191 | globs: dictionary of global objects. 192 | 193 | # Returns 194 | A function object. 195 | """ 196 | if isinstance(code, (tuple, list)): # unpack previous dump 197 | code, defaults, closure = code 198 | if isinstance(defaults, list): 199 | defaults = tuple(defaults) 200 | code = marshal.loads(code.encode('raw_unicode_escape')) 201 | if globs is None: 202 | globs = globals() 203 | return python_types.FunctionType(code, globs, 204 | name=code.co_name, 205 | argdefs=defaults, 206 | closure=closure) 207 | 208 | 209 | def has_arg(fn, name, accept_all=False): 210 | """Checks if a callable accepts a given keyword argument. 211 | 212 | For Python 2, checks if there is an argument with the given name. 213 | 214 | For Python 3, checks if there is an argument with the given name, and 215 | also whether this argument can be called with a keyword (i.e. if it is 216 | not a positional-only argument). 217 | 218 | # Arguments 219 | fn: Callable to inspect. 220 | name: Check if `fn` can be called with `name` as a keyword argument. 221 | accept_all: What to return if there is no parameter called `name` 222 | but the function accepts a `**kwargs` argument. 223 | 224 | # Returns 225 | bool, whether `fn` accepts a `name` keyword argument. 226 | """ 227 | if sys.version_info < (3,): 228 | arg_spec = inspect.getargspec(fn) 229 | if accept_all and arg_spec.keywords is not None: 230 | return True 231 | return (name in arg_spec.args) 232 | elif sys.version_info < (3, 3): 233 | arg_spec = inspect.getfullargspec(fn) 234 | if accept_all and arg_spec.varkw is not None: 235 | return True 236 | return (name in arg_spec.args or 237 | name in arg_spec.kwonlyargs) 238 | else: 239 | signature = inspect.signature(fn) 240 | parameter = signature.parameters.get(name) 241 | if parameter is None: 242 | if accept_all: 243 | for param in signature.parameters.values(): 244 | if param.kind == inspect.Parameter.VAR_KEYWORD: 245 | return True 246 | return False 247 | return (parameter.kind in (inspect.Parameter.POSITIONAL_OR_KEYWORD, 248 | inspect.Parameter.KEYWORD_ONLY)) 249 | 250 | 251 | class Progbar(object): 252 | """Displays a progress bar. 253 | 254 | # Arguments 255 | target: Total number of steps expected, None if unknown. 256 | interval: Minimum visual progress update interval (in seconds). 257 | """ 258 | 259 | def __init__(self, target, width=30, verbose=1, interval=0.05): 260 | self.width = width 261 | if target is None: 262 | target = -1 263 | self.target = target 264 | self.sum_values = {} 265 | self.unique_values = [] 266 | self.start = time.time() 267 | self.last_update = 0 268 | self.interval = interval 269 | self.total_width = 0 270 | self.seen_so_far = 0 271 | self.verbose = verbose 272 | 273 | def update(self, current, values=None, force=False): 274 | """Updates the progress bar. 275 | 276 | # Arguments 277 | current: Index of current step. 278 | values: List of tuples (name, value_for_last_step). 279 | The progress bar will display averages for these values. 280 | force: Whether to force visual progress update. 281 | """ 282 | values = values or [] 283 | for k, v in values: 284 | if k not in self.sum_values: 285 | self.sum_values[k] = [v * (current - self.seen_so_far), 286 | current - self.seen_so_far] 287 | self.unique_values.append(k) 288 | else: 289 | self.sum_values[k][0] += v * (current - self.seen_so_far) 290 | self.sum_values[k][1] += (current - self.seen_so_far) 291 | self.seen_so_far = current 292 | 293 | now = time.time() 294 | if self.verbose == 1: 295 | if not force and (now - self.last_update) < self.interval: 296 | return 297 | 298 | prev_total_width = self.total_width 299 | sys.stdout.write('\b' * prev_total_width) 300 | sys.stdout.write('\r') 301 | 302 | if self.target is not -1: 303 | numdigits = int(np.floor(np.log10(self.target))) + 1 304 | barstr = '%%%dd/%%%dd [' % (numdigits, numdigits) 305 | bar = barstr % (current, self.target) 306 | prog = float(current) / self.target 307 | prog_width = int(self.width * prog) 308 | if prog_width > 0: 309 | bar += ('=' * (prog_width - 1)) 310 | if current < self.target: 311 | bar += '>' 312 | else: 313 | bar += '=' 314 | bar += ('.' * (self.width - prog_width)) 315 | bar += ']' 316 | sys.stdout.write(bar) 317 | self.total_width = len(bar) 318 | 319 | if current: 320 | time_per_unit = (now - self.start) / current 321 | else: 322 | time_per_unit = 0 323 | eta = time_per_unit * (self.target - current) 324 | info = '' 325 | if current < self.target and self.target is not -1: 326 | info += ' - ETA: %ds' % eta 327 | else: 328 | info += ' - %ds' % (now - self.start) 329 | for k in self.unique_values: 330 | info += ' - %s:' % k 331 | if isinstance(self.sum_values[k], list): 332 | avg = np.mean(self.sum_values[k][0] / max(1, self.sum_values[k][1])) 333 | if abs(avg) > 1e-3: 334 | info += ' %.4f' % avg 335 | else: 336 | info += ' %.4e' % avg 337 | else: 338 | info += ' %s' % self.sum_values[k] 339 | 340 | self.total_width += len(info) 341 | if prev_total_width > self.total_width: 342 | info += ((prev_total_width - self.total_width) * ' ') 343 | 344 | sys.stdout.write(info) 345 | sys.stdout.flush() 346 | 347 | if current >= self.target: 348 | sys.stdout.write('\n') 349 | 350 | if self.verbose == 2: 351 | if current >= self.target: 352 | info = '%ds' % (now - self.start) 353 | for k in self.unique_values: 354 | info += ' - %s:' % k 355 | avg = np.mean(self.sum_values[k][0] / max(1, self.sum_values[k][1])) 356 | if avg > 1e-3: 357 | info += ' %.4f' % avg 358 | else: 359 | info += ' %.4e' % avg 360 | sys.stdout.write(info + "\n") 361 | 362 | self.last_update = now 363 | 364 | def add(self, n, values=None): 365 | self.update(self.seen_so_far + n, values) 366 | -------------------------------------------------------------------------------- /log_visual.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "%matplotlib inline" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 45, 15 | "metadata": {}, 16 | "outputs": [], 17 | "source": [ 18 | "from utils import Logger\n", 19 | "from utils import LoggerMonitor\n", 20 | "import os" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": 48, 26 | "metadata": {}, 27 | "outputs": [], 28 | "source": [ 29 | "arch = 'vgg16'\n", 30 | "model_dir = 'checkpoint/{}_lr_0.01'.format(arch)\n", 31 | "title = 'cifar-10-' + arch" 32 | ] 33 | }, 34 | { 35 | "cell_type": "code", 36 | "execution_count": 49, 37 | "metadata": {}, 38 | "outputs": [ 39 | { 40 | "data": { 41 | "image/png": "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\n", 42 | "text/plain": [ 43 | "" 44 | ] 45 | }, 46 | "metadata": {}, 47 | "output_type": "display_data" 48 | } 49 | ], 50 | "source": [ 51 | "logger = Logger(os.path.join(model_dir, 'log.txt'), title=title, resume=True)\n", 52 | "logger.close()\n", 53 | "logger.plot(names=['Train Loss'])" 54 | ] 55 | }, 56 | { 57 | "cell_type": "code", 58 | "execution_count": 50, 59 | "metadata": { 60 | "scrolled": true 61 | }, 62 | "outputs": [ 63 | { 64 | "data": { 65 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXQAAAD8CAYAAABn919SAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3XecVPWh/vHPd3uZ7Q1kUYpIs1CFRKTYwJIAlrixcUUD\nFm6IMYlG+d2rN+UGNVevl1w1ikpMBGPvGilruBaUKm1pouxSlmV7b/P9/bHDusB2ZnfmzD7v12te\ne+bMOTMPh91nz37nnDPGWouIiDhfkK8DiIiId6jQRUQChApdRCRAqNBFRAKECl1EJECo0EVEAoQK\nXUQkQKjQRUQChApdRCRAhHTniyUnJ9t+/fp1at3y8nKio6O9G6iLKKv3OSUnKGtX6clZ161bd8Ra\nm9LmgtbabruNHj3adtaqVas6vW53U1bvc0pOa5W1q/TkrMBa246O1ZCLiEiAUKGLiAQIFbqISIDo\n1jdFm1NbW0tOTg5VVVWtLhcXF8f27du7KdXJUVbv6+6cERERpKenExoa2m2vKXKyfF7oOTk5xMTE\n0K9fP4wxLS5XWlpKTExMNybrPGX1vu7Maa0lPz+fnJwc+vfv3y2vKeINPh9yqaqqIikpqdUyF+lO\nxhiSkpLa/KtRxN/4vNABlbn4HX1PihP5fMhFRMTJquvqKa+up6yqjtLqWsqq6th4uI7ijfspq66j\nrKqO8uo6rhyVTr/krj0xSoXegieffJKoqChuuukmsrKyyMjIwBjDK6+8wsCBAzv1nNOmTePzzz9n\nwoQJvPPOO43z9+7dS0ZGBvn5+YwePZoXXniBsLAwb/1TWs0yfvx4Pvjgg8b51loWLFjAyy+/THBw\nMLfffjs//elPAXjjjTf46quvCA4O5uWXXwZg8+bNnHXWWQDMnj27cdm2rFmzhmXLlvHoo492KPfa\ntWsZO3YsH330ERdddFGH1m3ONddcw8KFCxkwYMBJP5c4R73bUl7zXdmWeoq3rLqusYQbp5ver2pY\ntrzJ/Jp6d/Mvsn5j46QxMPK0BBW6r9x2222N02+88QZXX301CxYsaPf69fX1BAcHHzPvl7/8JRUV\nFTz11FPHzL/nnnu46667yMjI4LbbbmPx4sXcfvvtJ/cPaMPRLH/605+Omf/888+TnZ1NVlYWQUFB\nHD58uPGxhx56iLfeeovk5GTuv/9+AFwuFxs3bqQ5dXV1hIQ0/y02btw4xo0b1+HcS5cuZcKECSxd\nutQrhX7bbbfx8MMP88QTT5z0c0n3sNZSXlNPcWUtJZW1jV9LquqaTNdSUllHaVVtY3GXNtlbLq+p\nb9drRYQG4QoPxRUejCsiBFd4CH3iI5vc9zwWHoIrIrTha3gIO7duYuL3xxETEUJ0eAhRocEEBXX9\nMJ4KHfjLX/7CI488gjGGs88+mxdeeIEHHngAl8vFsGHDeOyxxwgODmbFihWsWrWKGTNmkJ2dTVVV\nFfPnz2fOnDlAQ7nNnTuXf/zjHzzxxBNMmDDhmNe58MILyczMPGaetZaVK1fy4osvAjBr1iweeOCB\nEwr9gw8+YPHixY17xpmZmTzyyCO88847LF68mIULFxIfH88555xDeHg4ixYtYs+ePVx//fWUl5cz\nffp0HnvsMcrKylrMAvDEE0/w4osvEhTU8PZKamoqADt37iQ8PJzk5ORWt+UNN9xATEwM69atY/Lk\nyVx55ZXcddddVFVVERUVxfPPP8+gQYNYvnw5ixYt4o033mDBggUcPHiQ3bt3k52dzd13382dd955\nwnO73W5effVVVq5cyYQJE6ipqWn8S+a5557j0UcfxRjDqFGjeO655zh06BBz585l7969GGP485//\nfMIvkcmTJ3Prrbc2+wtYulZtvZuiilqKKmooqqylsLym4X7l0a/flXVRRS25hRXU/PMflFTW4rat\nP3dMeAixkaHEeEo4ITqM9MQoYjyFGx0e0viYy1O6MZ5pV5NlQoM79zZj3f5gTk91dWrdk+FXhf7g\n21vZdqCk2cc6+wM37JRY/v0Hw1t8fOvWrfz2t7/l008/JTk5mYKCgmMev+yyy7jttttwuVz84he/\nAODZZ58lMTGRyspKxo4dy1VXXUVSUhLl5eWMGzeOBx54oN2H2OXn5xMfH9+4J5uens7+/ftPWO6i\niy5izpw5jRf9eemll8jIyODAgQP85je/Yf369cTExHDBBRdwzjnnADB//nzmz5/Pj3/8Y5588sl2\n5dmzZw8vvfQSr7/+OikpKTz++OMMGjSITz75hFGjRrXrOQ4ePMjnn39OUFAQxcXFrF69mpCQED74\n4AMWLFjASy+9dMI6O3fuZMWKFRQVFTF06FBuu+22E/6/V69ezeDBgxkwYAATJkzg/fffZ/r06Wza\ntImFCxfy6aefkpiY2Ph/eOedd3LxxRczb9486urqqKioOOF1g4OD6devH1u2bGncbtJxtfVuCitq\nKCyvpaC8hqKKGgoqaigsr6GwotbztYaCJtOlVXUtPl9IkCEuMpS4yFBiI0NJjA4j2h3EoNNOITYy\npGF+xHePN73vigghuBv2hv2RXxW6L6xcuZJrrrmmcc8zMTGxzXUef/xxXn/9dQCys7PZtWsXSUlJ\nBAcHc9VVVzVbHCcrJCSEadOm8fbbb3P11Vfz7rvv8tBDD7FixQomTZrUmPuaa65h586dAHz22We8\n8cYbAFx33XWNv5BaU11dTUREBGvXruW1115j9uzZrF69moMHD5KS0vbF3o5mOLqHX1RUxE033cSe\nPXtaXeeKK64gLCyM1NRUEhMTycvLo1evXscss3TpUjIyMgDIyMhg6dKlTJ8+nZUrV3Lttdc2boOj\nXzMzM1m2bFnj9ouNjW32tVNTUzlw4IAKvQm321JYUcORshqOlFWTX15Dflk1+WU1jdMF5TUUlDfc\nL66sbfG5osOCSYgOIzE6jPioMPolRZEQFdZwiw4lPiqMhKhQ4iPDiI8KJSE6jOiw4BOONMrMzGTy\n5DO7+p/uaH5V6K3tSfvLCTCZmZksX76czz77jKioKCZPntx4vHJERETjXuWaNWuYO3cuAP/xH//B\nD3/4w2afLykpiaKiosbx5pycHPr06QPA1KlTyc3NZcyYMTzzzDNkZGSwaNEiEhMTGTNmTJdsj/T0\ndK688koAZs6cyc033wxAZGQkxcXF7XqOppcNvf/++5k6dSp33HEHu3fvZtq0ac2uEx4e3jgdHBxM\nXd2xe2+1tbW89tprvPvuuzz44IO43W6KioooLy9vNUt7Dj+sqqoiMjKyzeUCQWVNPYdLqzhcWk1e\naTWffFvLFx9kNd7PK61uLPD6ZsY1ggwkeso5MTqMoafEkuSZTooOayjuqLAmBR5KeIiGsrqLXxW6\nL1xwwQXMnDmTn//85yQlJVFQUNDqXnpxcTEJCQlERUWRlZXF559/3uxy48aNa/HNwqaMMUyZMoVX\nXnmFjIwMlixZwvTp0wH48MMPj1l20qRJzJ49m6effrpxT3Xs2LH87Gc/o7CwkJiYGF599dXGo07G\njx/Pq6++yrXXXtu4p9qWGTNmsGrVKvr378/HH3/MGWecAcDQoUP561//2q7naKq4uLjxF9Tzzz/f\n4fWPWrlyJWPHjuXdd99tnHf99dfz5ptvcsEFF3Dttdcyf/78xiGXxMREpkyZwpNPPsm8efOor6+n\nvLy82b30Xbt2MXx4yzsTTlBb7ya3pIrckioOFVc3TJdWcbikmkPFDdN5JdWUVp84zBG842tSXOGk\nxITTKy6Cs/rEkRwTRoornOSYcJKiw0l2hZHkCic+MrRb3tyTzunxhT58+HDuv/9+Jk2aRHBwMCNH\njmy1eKZNm8aTTz7J0KFDGTx4MOPHj2/3a51//vlkZWVRVlZGeno6ixcvZurUqSxcuJCMjAwWLFjA\nyJEjueWWW5pdPzg4mCuuuILnn3+eJUuWANCnTx/uu+8+zj33XBITExkyZAhxcXEAPPbYY9xwww38\n7ne/Y9q0aY3zW8ty7733cv311/Poo4/icrl45plnAJg4cSJ333031toOnXRzzz33MHv2bB588EEu\nvfTSdq93vFdeeYWZM2ceM++qq67iueee4+233+ZXv/oVEydOJCQkhNGjR7N48WIWLVrET37yE556\n6ilCQkJ46qmnOPfcc5k6dSovvPBC41BLXFxcu4eTfMFaS15ZNdkFleQUVnCwuIpDxVUcKKrkUEkV\nB4urOFJWjT1uhzosOIjU2HDSYiMY0iuGiYNSSIkJJzUmnNTYCFJjwtn11TquuHiySjpAGHv8d0EX\nGjNmjF27du0x87Zv387QoUPbXNdfhlzao7uzlpWV4XK5qKurY+bMmcyePZuZM2dSUVFBZGQkxhiW\nLVvG0qVLefPNNzuddf78+fzgBz/wyuGCHdVV2/Thhx8mNTWVWbNmnfBYe783j9cw1ju53ctbaymu\nrG0s7OzCCrILKskurCCnsGFeVe2xxzrHRIRwSlwkveIi6B0XQVpsBL3iIugV+910QlRom798O5rV\nl3pyVmPMOmvtmLaW6/F76IHggQceYPny5VRVVXHJJZcwY8YMANatW8e8efOw1hIfH8+zzz57Uq9z\n3333sWbNGm9E9htJSUnccMMNXf465dV1jUWd06Swswsq2F9YecJQSFxkKOkJkZye4mLK4BTSE6Lo\nmxhJekIUp8RH4grXj66cSN8VAeCRRx5pdv7555/Ppk2bvPY6aWlpLb6561SzZ8/22nMVlNfwdV4Z\nq3Nq+fLDLL7NbyjsfQUVFFYcexRIVFgwfROiSE+IZPyAJNITIo8p7bhIXbZXOs4vCr2j47IiXa2l\noch6tyW7oILdh8vYk1fG13nl7MlrmG5a2sFBX9M3IZK+iVFcelZv+jYp674JkSRGh+l7XrzO54Ue\nERFBfn6+LqErfsNay5EjR7DBoSzflkvWoRJ25paxy1PiNXXfjWcnu8IYkOJi2pm9GZgSzYCUaA7v\n2cpV0yZ3+ixDkc5qV6EbY+4CbgUssBm4GYgCXgL6Ad8AP7LWFnY0QHp6Ojk5OeTl5bW6XFVVFRER\nER19ep9QVu/rypxua6mrt9TWu6mtd1NTb/m6oIZHP8unpLqhvNMTIhmU6mLC6UkMSo1hYGo0A1Nc\nxEedeBG1zEPbVebiE20WujGmD/BTYJi1ttIY83cgAxgGrLDW/sEYcy9wL3BPRwOEhoa261NhMjMz\nGTlyZEef3ieU1fu8kdNay4HiKjbnFLH9YClZh0rIOlTKt/nfndnrCg9hSK8YhvaO5Z7L0hjSK5bB\nvWL0JqQ4Qnu/S0OASGNMLQ175geAXwOTPY8vATLpRKGLdJWq2no27y9mw75C1n9bxIbsQnJLqoGG\ny5n2T47mzFPiuHpUOkN6xzKkVwzpCZEa+hPHatdx6MaY+cDvgErgH9ba640xRdbaeM/jBig8ev+4\ndecAcwDS0tJGt/eMxeMdPdbaCZTV+9rKaa0lr9Kyp8jN7qJ6vi5ys6/UTb3n2zsl0jAwPojT44MZ\nEBdEn5ggwoO7pridsk1BWbuKt7NOmTLFO8ehG2MSgOlAf6AIeNkYc8yBu9Zaa4xp9jeDtfbPwJ+h\n4cSizh5s35NPKuhKTsl6fM7y6jq+yilm/b5CNuwrYmN2IUfKaoCGQwLPTo/n0lEJjDw1gRF940mJ\nCW/hmbs+qz9T1q7hq6ztGXK5CNhrrc0DMMa8BnwfyDXG9LbWHjTG9AYOt/YkIp1lreVQuZtX1+U0\nFnjWoZLGa2IPSI5m0hmpjDw1npGnxjM4LYYQvSkpPVB7Cn0fMN4YE0XDkMuFwFqgHJgF/MHz9c0W\nn0GkA+rq3WzeX8yavQV8sbeA9fsKKaqoBTYREx7CiFPjmTfl9Ma974Torv24PhGnaLPQrbVrjDGv\nAOuBOmADDUMoLuDvxphbgG+BH3VlUAlc1lq+PlLOxzvy+GT3EdbsLaDMcyr8wJRoLhmWRnTlYX58\nyXgGprh67IcXiLSlXUe5WGv/Hfj342ZX07C3LtJhpVW1fLI7n3/uyuPjHXnsL6oEoF9SFD8ccQrf\nH5jEuP5JjWPfmZmZnJHmjIuzifiKDq6VbuF2W7YdLOHjnQ0Fvn5fIXVuiys8hO8PTOKOKQOZOCiF\nvolRvo4q4lgqdOkylTX1rN6Vx4dbc8nccZj88oajUIafEsuciQOYdEYKo05L0FmVIl6iQhevKqmq\nZeX2w3yw5RCZOw9TVesmNiKEKUNSmXRGCud7PmRBRLxPhS4nLb+smuXbc3l/yyE+2X2E2npLWmw4\nPxrTl6nDe3Fu/0TthYt0AxW6dEptvZvMHXn8fW02q7IOU+e29E2M5Obz+jN1eC9G9o3Xx5qJdDMV\nunTI7sOl/H1tDq+t38+RsmqSXeHcMqE/PxxxCsN6x+o6KCI+pEKXNpVW1fLOVwf5+9psNuwrIiTI\ncMGQVH40pi+TBqdoOEXET6jQpVlut2XN3gJeXpfNe5sPUlXrZlCqi/svG8qMkX30xqaIH1KhyzHy\ny6pZ9mU2L32Zzb6CCmLCQ7hyVDrXjE5nRN94DamI+DEVugCw9UAxz3/yDW9uOkBNnZvxAxK56+JB\nTBvem8iwYF/HE5F2UKH3YNZatuXXs3jxGlbvOkJUWDA/GpPOrO/1Y5BOsxdxHBV6D+R2W/6x7RD/\nm7mHr3KqSHZZ7pk2hOvGnUpcZKiv44lIJ6nQe5B6t+XdzQdZtHIXO3PLOC0pin8ZHsa9GVOICNWw\niojTqdB7AGstH2w5xB8/2snuw2UMSnXx+I9HcvlZvVn9z49V5iIBQoUe4NZ+U8Dv39vO+n1FnJ7q\nYtF1I7nszN46i1MkAKnQA9TXeWUs/CCLD7fmkhoTzsKrzuKqUen6aDaRAKZCDzBFFTU8tnwXf/38\nW8JDgrj74jO45fz+RIXpv1ok0OmnPEC43ZZlX2bz0IdZlFTW8uNzT+VnF52hMzpFehAVegDIOlTC\nr1/bzIZ9RYzrn8iD04czpFesr2OJSDdToTtYdV09f1q1h/9dtZuYiBD+eM05XDmqj07PF+mhVOgO\n9cXeAu57fTO7D5dx5cg+LLhiGInRYb6OJSI+pEJ3mKKKGv7zvSxeWptNn/hInrt5LFMGp/o6loj4\nARW6Q7jdltc37Of3722nqLKWuZMGMP/CQTp6RUQaqQ0cYMv+Yv7fm1vYsK+IEX3jeWHmWQw7RW96\nisixVOh+rKy6joc/yOKFz78lISqMh68+m6tGpessTxFplgrdT328M4/7XtvMgeJKbhp/Gj+/ZLCu\nhCgirVKh+5miihp+8852Xl2fw+mpLl69/fuMOjXB17FExAFU6H5k+bZcfv36ZgrKa5g35XT+9cLT\nCQ/RlRBFpH1U6H6gvLqO3767jaVfZDO0dyzP/ctYzuwT5+tYIuIwKnQf23aghHkvrmdvfjlzJw3g\n7osHExaiKyKKSMep0H3EWsvSL7J54O2txEeG8uKt4/newCRfxxIRB1Oh+0BpVS33vb6Ftzcd4PxB\nyTx67QiSXboqooicHBV6N9t+sIQ7/raeb/PL+eXUwdw+aaCOKxcRr1Chd6OPtuXy06UbiIkIYelP\nxjNugIZYRMR7VOjdwFrLs598w2/f3cbZfeJ4+qYxpMZG+DqWiAQYFXoXq6t38+Db23jh82+ZNrwX\nj147gsgwHVsuIt6nQu9CFTV13Pm39azakcfciQO4Z9oQjZeLSJdRoXeR4spabn7uCzZmF/G7mWdy\n/bjTfB1JRAJcu85gMcbEG2NeMcZkGWO2G2O+Z4xJNMZ8ZIzZ5fmqC454VNZZZj37BZv3F/O/149S\nmYtIt2jvKYn/DXxgrR0CnANsB+4FVlhrBwErPPd7vIqaOh5dV8WW/cX86bpRTDuzt68jiUgP0Wah\nG2PigInAYgBrbY21tgiYDizxLLYEmNFVIZ2iqraeW5esZVehm//OGMklw3v5OpKI9CDt2UPvD+QB\nzxljNhhjnjHGRANp1tqDnmUOAWldFdIJquvqmfvCOj77Op+fnB3O5Wdrz1xEupex1ra+gDFjgM+B\n86y1a4wx/w2UAP9qrY1vslyhtfaEcXRjzBxgDkBaWtroZcuWdSpoWVkZLperU+t2tTq35U8bq9lw\nuJ6bzwxjdHy132Y9nj9v16ackhOUtav05KxTpkxZZ60d0+aC1tpWb0Av4Jsm988H3gV2AL0983oD\nO9p6rtGjR9vOWrVqVafX7Uq1dfX2jr+us6fd8479y6d7rbX+m7U5TsnqlJzWKmtX6clZgbW2jX61\n1rY95GKtPQRkG2MGe2ZdCGwD3gJmeebNAt5s/++bwFDvtvzi5U28u/kgCy4fyo3f6+frSCLSg7X3\nOPR/Bf5mjAkDvgZupmH8/e/GmFuAb4EfdU1E/2St5f7XN/PGxgP8atpgbj1/gK8jiUgP165Ct9Zu\nBJobv7nQu3Gc47lPvmHZl9nMm3I6d0w+3ddxRETafRy6NPHpniP87r3tXDwsjZ9ffIav44iIACr0\nDssuqGDeixvolxTFo9eO0LVZRMRvqNA7oKbOzR1/W09tvZunbxqDK1yXwhER/6FG6oCHP8xi8/5i\nnrpxNANSnHE8rIj0HNpDb6eVWbk8vXovN4w/lak6pV9E/JAKvR0Ol1bxi5e/YmjvWBZcPszXcURE\nmqVCb4O1ll++/BXl1XU8njGCiFB92pCI+CcVehv+umYfH+/M4/7LhzIoLcbXcUREWqRCb0V2QQX/\n+d52zh+UzI3j9SEVIuLfVOgtcLstv3xlE0HGsPCqszFGx5uLiH9Tobfgb1/s4/OvC1hw+VBOiY/0\ndRwRkTap0JuRW1LFwvezmHB6MteO7evrOCIi7aJCb8bv39tOTb2b3844U0MtIuIYKvTjfPlNAW9u\nPMDciQPolxzt6zgiIu2mQm/C7bY8+PZWesVGcPvkgb6OIyLSISr0Jl5el82W/SX8+rIhRIXpMjci\n4iwqdI/Sqloe/nAHo09L4IfnnOLrOCIiHaZC93gicw9Hymr4tyuG6Y1QEXEkFTqwv6iSxf+3lxkj\nTuGcvvG+jiMi0ikqdOCRD3cA8MtpQ3ycRESk83p8oW87UMLrG/Zzy4T+9NEZoSLiYD2+0P9n5S5i\nwkOYO0mHKYqIs/XoQs86VML7Ww5x83n9iIsM9XUcEZGT0qML/bGPGvbOZ0/o7+soIiInrccW+t4j\n5Xy47RD/cl4/4qPCfB1HROSk9dhCf+6TvYQGBXHj9/TBFSISGHpkoRdX1PLy2hx+OOIUUmMifB1H\nRMQremShv/jFPipr67lFY+ciEkB6XKHX1rtZ8uk3nHd6EkN7x/o6joiI1/S4Qn9v80EOlVRx64QB\nvo4iIuJVParQrbU8vfprBqZEM+mMFF/HERHxqh5V6F/sLWDL/hJmT+hPUJCuqCgigaVHFfri/9tL\nQlQoV45M93UUERGv6zGFfqCoko+253LduFOJDAv2dRwREa/rMYX+2vocrIWMsaf6OoqISJfoEYVu\nreXV9fsZPyCRvolRvo4jItIlekShr99XyN4j5Vw1SmPnIhK4ekShv7Iuh6iwYC47q7evo4iIdJl2\nF7oxJtgYs8EY847nfqIx5iNjzC7P14Sui9l5VbX1vLPpINPO7EV0eIiv44iIdJmO7KHPB7Y3uX8v\nsMJaOwhY4bnvdz7alktpdR1Xa7hFRAJcuwrdGJMOXA4802T2dGCJZ3oJMMO70bzj/S0HSY0JZ/yA\nJF9HERHpUu3dQ38M+BXgbjIvzVp70DN9CEjzZjBvqKypZ1VWHlOH99KZoSIS8Iy1tvUFjLkCuMxa\ne4cxZjLwC2vtFcaYImttfJPlCq21J4yjG2PmAHMA0tLSRi9btqxTQcvKynC5XB1aZ11uHf+zoZpf\njY1gWFL3nUzUmay+4pSsTskJytpVenLWKVOmrLPWjmlzQWttqzfgP4Ec4Bsa9sQrgL8CO4DenmV6\nAzvaeq7Ro0fbzlq1alWH15n34no74sEPbW1dfadftzM6k9VXnJLVKTmtVdau0pOzAmttG/1qrW17\nyMVa+2trbbq1th+QAay01t4AvAXM8iw2C3izI79xulpFTR3Lt+Vy6Vm9CQnuEUdnikgPdzJN9wfg\nYmPMLuAiz32/kbkjj8raeq44W8eei0jP0KEDs621mUCmZzofuND7kbzj4x15xEaEcG6/RF9HERHp\nFgE5FlFX72ZFVi4Tz0jRcIuI9BgB2XZr9hZwpKyGy3Wqv4j0IAFZ6G9u3E9MeAhThqT6OoqISLcJ\nuEKvq3fzj225XDg0lYhQfZCFiPQcAVfo674tpKiilqnDe/k6iohItwq4Qv94Zx7BQYbzBiX7OoqI\nSLcKuEJfmXWYMaclEBsR6usoIiLdKqAKPbuggqxDpVw4VG+GikjPE1CFnrnjMAAXDfW7Cz+KiHS5\ngCr01buOkJ4QSf/kaF9HERHpdgFT6LX1bj7bk8/5g1IwRtc+F5GeJ2AKfVN2EaXVdUzU0S0i0kMF\nTKGv3nWEIAPfH6hCF5GeKWAK/ZPdRzgrPZ64KB2uKCI9U0AUelVtPZtyihg/QJfKFZGeKyAKfe03\nhdTWW8b1V6GLSM8VEIW+elceocGGcf2TfB1FRMRnAqLQv/imgBF944kO79AHMImIBBTHF3p1XT1b\n95cw6tQEX0cREfEpxxf6tgMl1NS7GXlqvK+jiIj4lOMLfWN2EQAj+moPXUR6NscX+oZ9RfSOi6BX\nXISvo4iI+JTzCz27kBF9NdwiIuLoQj9SVk12QaXGz0VEcHihb9yn8XMRkaOcXejZRQQHGc7qE+fr\nKCIiPufoQt+QXciQXjFEhgX7OoqIiM85ttDr3ZZN2cUaPxcR8XBsoe/JK6Osuk7j5yIiHo4t9A37\nCgG0hy4i4uHYQt+YXUxcZCgD9IHQIiKAgwt9U3YRZ6fH6QOhRUQ8HFnoVbX17Mgt5ex0Ha4oInKU\nIwt975Fy6t2Wwb1ifR1FRMRvOLLQdx8uA2BgisbPRUSOcmShZx0qITjIMDDF5esoIiJ+w5GFvvVA\nCYNSXUSE6gxREZGjHFvow07R+LmISFNtFroxpq8xZpUxZpsxZqsxZr5nfqIx5iNjzC7P1245ZTOv\ntJq80mon2l23AAAI20lEQVSG9Vahi4g01Z499DrgbmvtMGA8cKcxZhhwL7DCWjsIWOG53+V25ZYC\ncEZaTHe8nIiIY7RZ6Nbag9ba9Z7pUmA70AeYDizxLLYEmNFVIZvalFMMQH+dISoicowOjaEbY/oB\nI4E1QJq19qDnoUNAmleTtSCvtBqAPvGR3fFyIiKOYay17VvQGBfwMfA7a+1rxpgia218k8cLrbUn\njKMbY+YAcwDS0tJGL1u2rFNBy8rKcLlc/HFtFSU1lge/77+FfjSrEzglq1NygrJ2lZ6cdcqUKeus\ntWPaXNBa2+YNCAU+BH7eZN4OoLdnujewo63nGT16tO2sVatWWWutPe8PK+xPl67v9PN0h6NZncAp\nWZ2S01pl7So9OSuw1rajq9tzlIsBFgPbrbX/1eSht4BZnulZwJvt/W3TWRU1deQUVpLsCu/qlxIR\ncZyQdixzHnAjsNkYs9Ez7z7gD8DfjTG3AN8CP+qaiN/ZcajhCBdXeHtii4j0LG02o7X2/4CWrlF7\noXfjtK6wogaAyYNTuvNlRUQcwVFniu4vrATgFB3hIiJyAkcVem5JNcFBRmPoIiLNcFShHyqpIsUV\nTnCQPqVIROR4jir03JIq0uIifB1DRMQvOarQD5dUkxaj4RYRkeY4qtBzS6tIi9UeuohIcxxT6DX1\nlqKKWtJitYcuItIcxxR6cXXDNWdStYcuItIsxxR6kafQNeQiItI8xxR6YWOha8hFRKQ5jin0oipP\nocdoD11EpDmOKfRNeXUAxEeF+jiJiIh/ckyhb813A9BwNV8RETmeY65De05KMPVh+hxREZGWOGYP\nvbLOEhOu4RYRkZY4qNDBFeGYPyhERLqdYwq9otYSo0IXEWmRIwo9t6SK/CpLRXW9r6OIiPgtRxT6\nuN+vABquhy4iIs1zRKEflV1Q4esIIiJ+y1GF/tLc7/k6goiI33JEoc+dNACA/sk6Dl1EpCWOKPTI\n0GAA9FGiIiItc0Shu90NF+bSaf8iIi1zRqFb7Z2LiLTFIYVuUZ+LiLTOIYUOGm0REWmdIwrdWuuM\noCIiPuSInswvr6HG7esUIiL+zRGF/sq6HF9HEBHxe44odBERaZsKXUQkQDii0G8cfxoufViRiEir\nHFHogI5DFxFpg2MKXUREWueIz3Q7s08s32Q7IqqIiM84oiWvHXsqaeVf+zqGiIhf05CLiEiAUKGL\niASIkyp0Y8w0Y8wOY8xuY8y93golIiId1+lCN8YEA38CLgWGAT82xgzzVjAREemYk9lDPxfYba39\n2lpbAywDpnsnloiIdJSx1nZuRWOuBqZZa2/13L8RGGetnXfccnOAOQBpaWmjly1b1qnXKysrw+Vy\ndWrd7qas3ueUnKCsXaUnZ50yZco6a+2YNhe01nbqBlwNPNPk/o3AotbWGT16tO2sVatWdXrd7qas\n3ueUnNYqa1fpyVmBtbYdvXwyQy77gb5N7qd75omIiA+czJBLCLATuJCGIv8SuM5au7WVdfKAbzv1\ngpAMHOnkut1NWb3PKTlBWbtKT856mrU2pa2FOn2mqLW2zhgzD/gQCAaeba3MPeu0Gaglxpi1tj1j\nSH5AWb3PKTlBWbuKsrbtpE79t9a+B7znpSwiInISdKaoiEiAcFKh/9nXATpAWb3PKTlBWbuKsrah\n02+KioiIf3HSHrqIiLTCEYXuDxcBM8Z8Y4zZbIzZaIxZ65mXaIz5yBizy/M1ocnyv/bk3WGMmdpk\n/mjP8+w2xjxujDnpT9czxjxrjDlsjNnSZJ7Xshljwo0xL3nmrzHG9PNy1geMMfs923ajMeYyX2c1\nxvQ1xqwyxmwzxmw1xsz3zPe77dpKVn/crhHGmC+MMZs8WR/0zPer7dpKTr/bpsdoz9lHvrzRcEjk\nHmAAEAZsAob5IMc3QPJx8x4C7vVM3wss9EwP8+QMB/p78gd7HvsCGE/Dx6S+D1zqhWwTgVHAlq7I\nBtwBPOmZzgBe8nLWB4BfNLOsz7ICvYFRnukYGs65GOaP27WVrP64XQ3g8kyHAms8r+dX27WVnH63\nTZvenLCH7s8XAZsOLPFMLwFmNJm/zFpbba3dC+wGzjXG9AZirbWf24b/xb80WafTrLX/BAq6MFvT\n53oFuPDoXoaXsrbEZ1mttQettes906XAdqAPfrhdW8naEl9mtdbaMs/dUM/N4mfbtZWcLfHpz9VR\nTij0PkB2k/s5tP7N2lUssNwYs840XHAMIM1ae9AzfQhI80y3lLmPZ/r4+V3Bm9ka17HW1gHFQJKX\n8/6rMeYr0zAkc/TPbb/I6vlTeCQNe2l+vV2Pywp+uF2NMcHGmI3AYeAja61fbtcWcoIfbtOjnFDo\n/mKCtXYEDdd/v9MYM7Hpg57fvn55yJA/Z/N4goYhtRHAQeCPvo3zHWOMC3gV+Jm1tqTpY/62XZvJ\n6pfb1Vpb7/lZSqdhL/bM4x73i+3aQk6/3KZHOaHQ/eIiYNba/Z6vh4HXaRgKyvX8SYXn62HP4i1l\n3u+ZPn5+V/BmtsZ1TMM1fOKAfG8Ftdbmen543MDTNGxbn2c1xoTSUJB/s9a+5pntl9u1uaz+ul2P\nstYWAauAafjpdj0+p79vUycU+pfAIGNMf2NMGA1vHrzVnQGMMdHGmJij08AlwBZPjlmexWYBb3qm\n3wIyPO9i9wcGAV94/qQsMcaM94yV3dRkHW/zZramz3U1sNKzF+UVR3+QPWbSsG19mtXzvIuB7dba\n/2rykN9t15ay+ul2TTHGxHumI4GLgSz8bLu2lNMft+kx2vvuqS9vwGU0vHO/B7jfB68/gIZ3sDcB\nW49moGG8awWwC1gOJDZZ535P3h00OZIFGOP5JtgDLMJzctdJ5ltKw59/tTSM0d3izWxABPAyDW/0\nfAEM8HLWF4DNwFc0fJP39nVWYAINf/Z/BWz03C7zx+3aSlZ/3K5nAxs8mbYA/+btnyVvZG0lp99t\n06Y3nSkqIhIgnDDkIiIi7aBCFxEJECp0EZEAoUIXEQkQKnQRkQChQhcRCRAqdBGRAKFCFxEJEP8f\nbZjJbaFlfJ8AAAAASUVORK5CYII=\n", 66 | "text/plain": [ 67 | "" 68 | ] 69 | }, 70 | "metadata": {}, 71 | "output_type": "display_data" 72 | } 73 | ], 74 | "source": [ 75 | "logger = Logger(os.path.join(model_dir, 'log.txt'), title=title, resume=True)\n", 76 | "logger.close()\n", 77 | "logger.plot(names=['Train Acc.'])" 78 | ] 79 | }, 80 | { 81 | "cell_type": "code", 82 | "execution_count": 51, 83 | "metadata": {}, 84 | "outputs": [ 85 | { 86 | "data": { 87 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXcAAAD8CAYAAACMwORRAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJztnXl8FEXax7+VO4HITUSihFNAIEDCoQgkgIqAJx7gq67u\nCuu5rq6rKF7rtd6ir6uIyrL6uuCK6KqgKEdABUFOue8r3DcEyF3vH3Okp6enM0lmksnM8/188slM\nVXXVb3qmn65+quoppbVGEARBCC+ialqAIAiCEHjEuAuCIIQhYtwFQRDCEDHugiAIYYgYd0EQhDBE\njLsgCEIYIsZdEAQhDBHjLgiCEIaIcRcEQQhDYmqq4caNG+u0tLRKHXvq1Cnq1KkTWEFBpDbpFa3B\nQbQGh0jUunTp0kNa6yblFtRa18hfRkaGrixz586t9LE1QW3SK1qDg2gNDpGoFVii/bCx4pYRBEEI\nQ8S4C4IghCFi3AVBEMKQGhtQFYRQoKioiNzcXPLz84PaTr169Vi3bl1Q2wgUojU4VFRrQkICqamp\nxMbGVqo9Me5CRJObm0tycjJpaWkopYLWzsmTJ0lOTg5a/YFEtAaHimjVWnP48GFyc3Np2bJlpdoT\nt4wQ0eTn59OoUaOgGnZBqChKKRo1alSlJ0ox7kLEI4ZdCEWq+rusdca9sLiU+blFlJbK9oCCIAi+\nqHXG/e05m5i4upAZq/fWtBRBCBrjx4/no48+AmD9+vV07dqVbt26sWXLlkrXOXjwYOrXr8+wYcM8\n0rdt20avXr1o06YNN954I4WFhVXSXhUtWmvGjh1Lu3bt6NChA2+99ZY778svv+SZZ55h3rx5XHjh\nhR7HFRcXk5KSwp49e3y2+fTTT/Pqq68C8OSTTzJr1iyvMjk5OV6a7NKryqpVq7jtttsCXi/UQuPu\nelS599/La1iJIASPO++8k1tvvRVwGLXrrruO5cuX07p1a7+OLykp8Ur761//yscff+yV/sgjj/DA\nAw+wefNmGjRo4L6pBBNfWiZNmsSuXbtYv34969atY8SIEe68l19+mbvvvpu+ffuSm5vLjh073Hmz\nZs3iggsu4JxzzvGr/WeeeYZBgwZV/YNUkc6dO5Obm8vOnTsDXnetM+69WjWsaQmCEFA++ugjunTp\nQnp6OrfccgtQ1sucMWMG48aN49133yU7OxuAq6++moyMDC644AImTJjgrqdu3br85S9/IT09nYUL\nF3q1M3DgQK/ZGlpr5syZw3XXXQfA7373O7755huvY7/77juuv/5693tjT/bDDz+kXbt29OzZk1Gj\nRnHvvfcCsGXLFnr37k3nzp15/PHHqVu3rq0WgHfffZcnn3ySqCiHaWratCkAGzduJD4+nsaNGxMV\nFcUNN9zAlClT3MdNmTKFkSNHAvD+++/To0cP0tPTGT58OKdPn/Zq57bbbmPq1Knuz9a+fXu6d+/O\ntGnTvMraMXv2bLp160bnzp35/e9/T0FBAQBjxoyhY8eOdOnShYceegiAL774gk6dOpGenk6/fv3c\ndVxxxRUenyVQ1LqpkGcKy3okX6/cQ8M6cfRp07gGFQnhwt++XsPaPScCWmfHc87iqSsu8Jm/Zs0a\nnnvuORYsWEDjxo05cuSIR/6QIUO48847qVu3rttITJw4kYYNG3LmzBl69OjB8OHDadSoEadOnaJX\nr1689tprfus7fPgw9evXJybGYQpSU1PZu9fb5Tlo0CBGjx7tDn716aefMmLECPbs2cOzzz7LsmXL\nSE5OZsCAAaSnpwNw//33c//99zNy5EjGjx/vl54tW7bw6aef8sUXX9CkSRPeeust2rZty88//0z3\n7t3d5UaOHMmoUaO4++67KSgoYMaMGbz++usAXHvttYwaNQqAxx9/nA8//JD77rvPsr38/HxGjRrF\nnDlz3G4pf8nPz+e2225j9uzZtGvXjltvvZV3332XW265hS+++IL169ejlOLYsWMAvPTSS3z//fc0\nb97cnQaQmZnJiy++yMMPP+x32/5Q63ruMdFlku+bvJz/+WAR+47nM3vdfv48ZTkb95+sQXWCUDHm\nzJnD9ddfT+PGjg5Kw4blP5m+9dZbpKen07t3b3bt2sWmTZsAiI6OZvjw4UHRGRMTw+DBg/n6668p\nLi5m+vTpXHXVVSxevJj+/fvTsGFDYmNjPXr3CxcudL+/6aab/GqnoKCAhIQElixZwqhRo/j9738P\nwN69e2nSpCwQYmZmJnl5eWzatIlvv/2WXr16uc/d6tWr6du3L507d+aTTz5hzZo1Pttbv349LVu2\npG3btiiluPnmm/0+Jxs2bKBly5a0a9cOcDz1zJ8/n3r16pGQkMAf/vAHpk2bRlJSEgC9e/fmtttu\n4/333/dwmzVt2tR2rKCy1LqeuxW9/z7b/frLFXvY/uLQGlQj1FbsetihQk5ODrNmzWLhwoUkJSWR\n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\n", 88 | "text/plain": [ 89 | "" 90 | ] 91 | }, 92 | "metadata": {}, 93 | "output_type": "display_data" 94 | } 95 | ], 96 | "source": [ 97 | "logger = Logger(os.path.join(model_dir, 'log.txt'), title=title, resume=True)\n", 98 | "logger.close()\n", 99 | "logger.plot(names=['Valid Loss'])" 100 | ] 101 | }, 102 | { 103 | "cell_type": "code", 104 | "execution_count": 52, 105 | "metadata": {}, 106 | "outputs": [ 107 | { 108 | "data": { 109 | "image/png": "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\nOHTokNtjPvPMM7z22msATJkyhQULFrisk5qa6hKTvYceeojWrVtTVg1jYTIyMhg1alTFK1ZBQCT0\nULuz1tSxfYgMDyXtxUv49PZ+fHTbeVyR0KoWoxOi+tx1113cfPPNgCWRXXPNNaxbt46OHTt6tX1p\nqWvz4+OPP84nn3zisnzixIk8/PDD7Nq1i5iYGKZPn35mwXvBXSwfffQR+/fvZ9u2bWzdupXrr7/e\nVvbKK69wzz33MGjQIA4cOMC+fftsZQsWLKBHjx60auVdDnj22WcZMWJEpWIuKyvj22+/5eyzz2bx\n4sWV2tZMbGwsLVu2ZOnSpWe8L2cBkdCtlyGJbZs4LBsY35yhXVrUVlhCnJGPP/6Y3r17k5CQwE03\n3QSU1yZ//vln3njjDd59912Sk5MBGD16NImJifTo0YNp06bZ9hMdHc2jjz5KQkICy5cvdznO8OHD\nXW7Maa1ZtGgR11xzDQDjxo1jzpw5LtvOnTuXa6+91vbeviY7ffp0OnfuzPnnn8/48eO57777ANi9\nezf9+/enV69ePPXUU0RHR3uMBeDdd99lypQphBg9IFq0sPxd79ixg3r16tG8eXNCQkIYO3Yss2fP\ntm03a9Ysxo0bB8D777/PeeedR0JCAldffTX5+fkux7n11lv5+uuvbT9b165d6du3L998843LuvY/\nc48ePbj77ruZOXOmbXlubi633XYbvXr1onfv3ra45s6dS9++fUlISODyyy833efo0aP57LPP3B6z\nqgKjl4tRQQ8LkTZPUf3++cNmthw6Va377N6qEY8MPcdt+ebNm3n++edZtmwZzZs3Jysry6H8kksu\n4a677iI6OprHHnsMgA8//JCmTZty+vRpzjvvPK6++mqaNWtGXl4e/fr14/XXX/c6vszMTGJiYggL\ns6SA1q1bc/DgQZf1RowYwYQJE8jLyyMqKoovvviC66+/nkOHDvHcc8+xdu1aGjZsyLBhw0hIsDxL\n/sEHH+TBBx9k3LhxvPfee17Fs3v3br744gu+/fZbYmNjeeutt4iPj2fp0qX07dvXtt64ceO4/fbb\nmTJlCoWFhfz8889MnToVgDFjxjB+/HgAnnrqKaZPn879999veryCggLGjx/PokWL6NSpE9ddd53b\n2GbOnMm4ceO48sormTx5MsXFxYSHh/Pcc8/RuHFjNm7cCEB2djYZGRmMHz+e3377jfbt27N3717T\nfSYlJfHUU0959dlURkDV0OUelggWixYt4tprr6V58+YANG3atMJt3nrrLRISEujfvz/79+9n586d\nAISGhnL11Vf7JM6wsDBGjRrFDz/8QElJCT/99BNXXnklq1atYsiQITRt2pTw8HCHWvzy5ctt7//6\n1796dZzCwkIiIyNZvXo148eP529/+xsAhw8fJjY21rZeUlISubm5bN++nV9++YV+/frZPrtNmzYx\naNAgevXqxWeffcbmzZvdHm/btm20b9+e+Ph4lFLceOONpusVFRXx888/M3r0aBo1akS/fv2YN28e\nYGnuuffee23rNmnShBUrVjB48GBbt0N332uLFi08tvtXVUDU0Bdts/RiWbEnq4I1hai8f1zewyf7\nzcnJqbZ9paamsmDBApYvX06DBg0YOnSorXtbZGQkoaGhAKxcuZI777wTsLQXX3HFFab7a9asGSdO\nnKCkpISwsDAOHjxI69atAbjooos4evQoSUlJfPDBB1x//fW8/fbbNG3alKSkJJ/0AW/Tpg1jxowB\n4KqrruK2224DoH79+pw8edJh3WuuuYZZs2axdetWW3MLWJpT5syZQ0JCAh999BGpqalnHNe8efM4\nceIEvXpZxrzk5+dTv359jzdQvVFQUED9+vXPOD5nAVFDz8rz/d13IWrSsGHD+Oqrr8jMzARwaXJx\ndvLkSZo0aUKDBg3Ytm0bK1asMF2vX79+rF+/nvXr17tN5mC56k1OTra1J8+cOZMrr7wSsCSx9evX\n88EHHwAwZMgQ1q5dy/vvv2+7WXneeeexePFisrOzKSkpcWjX7t+/v+39rFmzvPk4GD16NCkpllHg\nixcvpnPnzgB069aNXbt2Oax77bXX8umnn7Jo0SJbzGA5gbZs2ZLi4uIK26e7du1Kenq6rfeQfdu4\nvZkzZ/LBBx+Qnp5Oeno6aWlpzJ8/n/z8fEaOHMk777xjWzc7O5v+/fvz22+/kZaWBrj/Xnfs2EHP\nnj09xlgVAZHQbx/YAYCuZwXG6DAhKtKjRw+efPJJhgwZQkJCAo888ojH9UeNGkVJSQndunVj0qRJ\n9O/f3+tjDRo0iGuvvZaFCxfSpk0bW5PByy+/zNSpU+nUqRNZWVncfvvtptuHhoZy2WWX8csvv9hq\npq1bt2by5Mmcf/75XHDBBbRr147GjS1TcbzxxhtMnTqV3r17s2vXLttyT7FMmjSJ2bNn06tXL554\n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\n", 110 | "text/plain": [ 111 | "" 112 | ] 113 | }, 114 | "metadata": {}, 115 | "output_type": "display_data" 116 | } 117 | ], 118 | "source": [ 119 | "logger = Logger(os.path.join(model_dir, 'log.txt'), title=title, resume=True)\n", 120 | "logger.close()\n", 121 | "logger.plot(names=['Valid Acc.'])" 122 | ] 123 | }, 124 | { 125 | "cell_type": "code", 126 | "execution_count": 47, 127 | "metadata": {}, 128 | "outputs": [ 129 | { 130 | "data": { 131 | "image/png": "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\n", 132 | "text/plain": [ 133 | "" 134 | ] 135 | }, 136 | "metadata": {}, 137 | "output_type": "display_data" 138 | } 139 | ], 140 | "source": [ 141 | "models = ['alexnet', 'vgg16','vgg16_bn', 'resnet18']\n", 142 | "paths = [os.path.join('checkpoint', m+'_lr_0.01', 'log.txt') for m in models]\n", 143 | "\n", 144 | "m_p = dict(zip(models, paths))\n", 145 | "\n", 146 | "field = ['Valid Acc.']\n", 147 | "\n", 148 | "monitor = LoggerMonitor(m_p)\n", 149 | "monitor.plot(names=field)" 150 | ] 151 | }, 152 | { 153 | "cell_type": "code", 154 | "execution_count": null, 155 | "metadata": {}, 156 | "outputs": [], 157 | "source": [] 158 | } 159 | ], 160 | "metadata": { 161 | "kernelspec": { 162 | "display_name": "Python 2", 163 | "language": "python", 164 | "name": "python2" 165 | }, 166 | "language_info": { 167 | "codemirror_mode": { 168 | "name": "ipython", 169 | "version": 2 170 | }, 171 | "file_extension": ".py", 172 | "mimetype": "text/x-python", 173 | "name": "python", 174 | "nbconvert_exporter": "python", 175 | "pygments_lexer": "ipython2", 176 | "version": "2.7.13" 177 | } 178 | }, 179 | "nbformat": 4, 180 | "nbformat_minor": 2 181 | } 182 | --------------------------------------------------------------------------------