From eb1daa18bfbd742f5a2c8ff22ea62200e779c188 Mon Sep 17 00:00:00 2001 From: fazilaltinel Date: Fri, 4 Dec 2020 16:15:41 +0300 Subject: [PATCH] ResNet changes for Office dataset --- README.md | 6 +- core/train.py | 30 ++++-- datasets/office.py | 2 +- experiments/office.py | 30 ++++-- models/alexnet.py | 3 +- models/model.py | 44 ++++++++- models/resnet.py | 213 ++++++++++++++++++++++++++++++++++++++++++ utils/utils.py | 15 +++ 8 files changed, 319 insertions(+), 24 deletions(-) create mode 100644 models/resnet.py diff --git a/README.md b/README.md index 7ba59b9..268499c 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ A PyTorch implementation for paper *[Unsupervised Domain Adaptation by Backpropa ## Environment -- Python 3.6 -- PyTorch 1.0 +- Python 3.8.5 +- PyTorch 1.6.0 ## Note @@ -26,6 +26,8 @@ Before running the training code, make sure that `DATASETDIR` environment variab - `GTSRBmodel()` - `AlexModel` - not successful, mainly due to the pretrained model difference +- `ResNet50` + - Better and more stable results than AlexNet. ## Result diff --git a/core/train.py b/core/train.py index 99407be..1659a1c 100644 --- a/core/train.py +++ b/core/train.py @@ -126,20 +126,30 @@ def train_dann(model, params, src_data_loader, tgt_data_loader, tgt_data_loader_ optimizer = optim.SGD(model.parameters(), lr=params.lr, momentum=params.momentum, weight_decay=params.weight_decay) else: print("training office task") + # parameter_list = [{ + # "params": model.features.parameters(), + # "lr": 0.001 + # }, { + # "params": model.fc.parameters(), + # "lr": 0.001 + # }, { + # "params": model.bottleneck.parameters() + # }, { + # "params": model.classifier.parameters() + # }, { + # "params": model.discriminator.parameters() + # }] parameter_list = [{ - "params": model.features.parameters(), - "lr": 0.001 + "params": model.feature_extractor.parameters(), + "lr": 0.0001 }, { - "params": model.fc.parameters(), - "lr": 0.001 + "params": model.classifier.parameters(), + "lr": 0.0001 }, { - "params": model.bottleneck.parameters() - }, { - "params": model.classifier.parameters() - }, { - "params": model.discriminator.parameters() + "params": model.discriminator.parameters(), + "lr": 0.0001 }] - optimizer = optim.SGD(parameter_list, lr=0.001, momentum=0.9) + optimizer = optim.SGD(parameter_list, momentum=0.9, weight_decay=1e-4) criterion = nn.CrossEntropyLoss() diff --git a/datasets/office.py b/datasets/office.py index 610aac5..0d54dc2 100644 --- a/datasets/office.py +++ b/datasets/office.py @@ -17,7 +17,7 @@ def get_office(dataset_root, batch_size, category): # datasets and data_loader office_dataset = datasets.ImageFolder( - os.path.join(dataset_root, 'office', category, 'images'), transform=pre_process) + os.path.join(dataset_root, 'office31', category, 'images'), transform=pre_process) office_dataloader = torch.utils.data.DataLoader( dataset=office_dataset, batch_size=batch_size, shuffle=True, num_workers=0) diff --git a/experiments/office.py b/experiments/office.py index 74b36d9..ca7dd9f 100644 --- a/experiments/office.py +++ b/experiments/office.py @@ -1,19 +1,28 @@ import os import sys - import torch - -sys.path.append('../') +sys.path.append(os.path.abspath('.')) from core.train import train_dann from core.test import test from models.model import AlexModel +from models.model import ResNet50 from utils.utils import get_data_loader, init_model, init_random_seed +from utils.altutils import setLogger + +# To avoid proxy issues while downloading pretrained model +import ssl +ssl._create_default_https_context = ssl._create_unverified_context class Config(object): # params for path - dataset_root = os.path.expanduser(os.path.join('~', 'Datasets')) - model_root = os.path.expanduser(os.path.join('~', 'Models', 'pytorch-dann')) + currentDir = os.path.dirname(os.path.realpath(__file__)) + dataset_root = os.environ["DATASETDIR"] + model_root = os.path.join(currentDir, 'checkpoints') + + finetune_flag = True + lr_adjust_flag = 'non-simple' + src_only_flag = False # params for datasets and data loader batch_size = 32 @@ -52,6 +61,10 @@ class Config(object): params = Config() +currentDir = os.path.dirname(os.path.realpath(__file__)) +logFile = os.path.join(currentDir+'/../', 'dann-{}-{}.log'.format(params.src_dataset, params.tgt_dataset)) +loggi = setLogger(logFile) + # init random seed init_random_seed(params.manual_seed) @@ -63,12 +76,13 @@ src_data_loader = get_data_loader(params.src_dataset, params.dataset_root, param tgt_data_loader = get_data_loader(params.tgt_dataset, params.dataset_root, params.batch_size) # load dann model -dann = init_model(net=AlexModel(), restore=None) +# dann = init_model(net=AlexModel(), restore=None) +dann = init_model(net=ResNet50(), restore=None) # train dann model print("Start training dann model.") -if not (dann.restored and params.dann_restore): - dann = train_dann(dann, params, src_data_loader, tgt_data_loader, tgt_data_loader, device) +# if not (dann.restored and params.dann_restore): +dann = train_dann(dann, params, src_data_loader, tgt_data_loader, tgt_data_loader, device, loggi) print('done') \ No newline at end of file diff --git a/models/alexnet.py b/models/alexnet.py index 840cdfb..0f9ec26 100644 --- a/models/alexnet.py +++ b/models/alexnet.py @@ -80,9 +80,10 @@ def alexnet(pretrained=False, **kwargs): Args: pretrained (bool): If True, returns a model pre-trained on ImageNet """ + model_root = os.environ["MODELDIR"] model = AlexNet(**kwargs) if pretrained: - model_path = '/home/wogong/Models/alexnet.pth.tar' + model_path = os.path.join(model_root, 'alexnet.pth.tar') pretrained_model = torch.load(model_path) model.load_state_dict(pretrained_model['state_dict']) return model diff --git a/models/model.py b/models/model.py index 9f3fc6a..13b70a5 100644 --- a/models/model.py +++ b/models/model.py @@ -1,9 +1,12 @@ """DANN model.""" - +import os +import torch import torch.nn as nn from .functions import ReverseLayerF from torchvision import models from .alexnet import alexnet +from .resnet import resnet50 +from utils.utils import weights_init class Classifier(nn.Module): @@ -260,7 +263,8 @@ class AlexModel(nn.Module): def __init__(self): super(AlexModel, self).__init__() self.restored = False - model_alexnet = models.alexnet(pretrained=True) + # model_alexnet = models.alexnet(pretrained=True) + model_alexnet = alexnet(pretrained=True) self.features = model_alexnet.features @@ -302,3 +306,39 @@ class AlexModel(nn.Module): domain_output = self.discriminator(reverse_bottleneck) return class_output, domain_output + + +class ResNet50(nn.Module): + def __init__(self): + super(ResNet50, self).__init__() + + resnetModel = models.resnet50(pretrained=True) + feature_map = list(resnetModel.children()) + feature_map.pop() + self.feature_extractor = nn.Sequential(*feature_map) + + self.classifier = nn.Sequential( + nn.Linear(2048, 31), + ) + + self.discriminator = nn.Sequential( + nn.Linear(2048, 1024), + nn.ReLU(inplace=True), + nn.Dropout(), + nn.Linear(1024, 1024), + nn.ReLU(inplace=True), + nn.Dropout(), + nn.Linear(1024, 2), + ) + + def forward(self, input_data, alpha=-1, sln='dann'): + input_data = input_data.expand(input_data.data.shape[0], 3, 227, 227) + feature = self.feature_extractor(input_data) + feature = feature.view(-1, 2048) + + reverse_bottleneck = ReverseLayerF.apply(feature, alpha) + + class_output = self.classifier(feature) + domain_output = self.discriminator(reverse_bottleneck) + + return class_output, domain_output diff --git a/models/resnet.py b/models/resnet.py new file mode 100644 index 0000000..06d39e4 --- /dev/null +++ b/models/resnet.py @@ -0,0 +1,213 @@ +import torch.nn as nn +import math +import torch.utils.model_zoo as model_zoo + + +__all__ = ['ResNet', 'resnet18', 'resnet34', 'resnet50', 'resnet101', + 'resnet152'] + + +model_urls = { + 'resnet18': 'https://download.pytorch.org/models/resnet18-5c106cde.pth', + 'resnet34': 'https://download.pytorch.org/models/resnet34-333f7ec4.pth', + 'resnet50': 'https://download.pytorch.org/models/resnet50-19c8e357.pth', + 'resnet101': 'https://download.pytorch.org/models/resnet101-5d3b4d8f.pth', + 'resnet152': 'https://download.pytorch.org/models/resnet152-b121ed2d.pth', +} + + +def conv3x3(in_planes, out_planes, stride=1): + "3x3 convolution with padding" + return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride, + padding=1, bias=False) + + +class BasicBlock(nn.Module): + expansion = 1 + + def __init__(self, inplanes, planes, stride=1, downsample=None): + super(BasicBlock, self).__init__() + self.conv1 = conv3x3(inplanes, planes, stride) + self.bn1 = nn.BatchNorm2d(planes) + self.relu = nn.ReLU(inplace=True) + self.conv2 = conv3x3(planes, planes) + self.bn2 = nn.BatchNorm2d(planes) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + + if self.downsample is not None: + residual = self.downsample(x) + + out += residual + out = self.relu(out) + + return out + + +class Bottleneck(nn.Module): + expansion = 4 + + def __init__(self, inplanes, planes, stride=1, downsample=None): + super(Bottleneck, self).__init__() + self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False) + self.bn1 = nn.BatchNorm2d(planes) + self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride, + padding=1, bias=False) + self.bn2 = nn.BatchNorm2d(planes) + self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False) + self.bn3 = nn.BatchNorm2d(planes * 4) + self.relu = nn.ReLU(inplace=True) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + out = self.relu(out) + + out = self.conv3(out) + out = self.bn3(out) + + if self.downsample is not None: + residual = self.downsample(x) + + out += residual + out = self.relu(out) + + return out + + +class ResNet(nn.Module): + + def __init__(self, block, layers, num_classes=1000): + self.inplanes = 64 + super(ResNet, self).__init__() + self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, + bias=False) + self.bn1 = nn.BatchNorm2d(64) + self.relu = nn.ReLU(inplace=True) + self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) + self.layer1 = self._make_layer(block, 64, layers[0]) + self.layer2 = self._make_layer(block, 128, layers[1], stride=2) + self.layer3 = self._make_layer(block, 256, layers[2], stride=2) + self.layer4 = self._make_layer(block, 512, layers[3], stride=2) + self.avgpool = nn.AvgPool2d(7, stride=1) + # self.fc = nn.Linear(512 * block.expansion, num_classes) + + for m in self.modules(): + if isinstance(m, nn.Conv2d): + n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels + m.weight.data.normal_(0, math.sqrt(2. / n)) + elif isinstance(m, nn.BatchNorm2d): + m.weight.data.fill_(1) + m.bias.data.zero_() + + def _make_layer(self, block, planes, blocks, stride=1): + downsample = None + if stride != 1 or self.inplanes != planes * block.expansion: + downsample = nn.Sequential( + nn.Conv2d(self.inplanes, planes * block.expansion, + kernel_size=1, stride=stride, bias=False), + nn.BatchNorm2d(planes * block.expansion), + ) + + layers = [] + layers.append(block(self.inplanes, planes, stride, downsample)) + self.inplanes = planes * block.expansion + for i in range(1, blocks): + layers.append(block(self.inplanes, planes)) + + return nn.Sequential(*layers) + + def forward(self, x): + x = self.conv1(x) + x = self.bn1(x) + x = self.relu(x) + x = self.maxpool(x) + + x = self.layer1(x) + x = self.layer2(x) + x = self.layer3(x) + x = self.layer4(x) + + x = self.avgpool(x) + # x = x.view(x.size(0), -1) + # x = self.fc(x) + + return x + + +def resnet18(pretrained=False, **kwargs): + """Constructs a ResNet-18 model. + + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + """ + model = ResNet(BasicBlock, [2, 2, 2, 2], **kwargs) + if pretrained: + model.load_state_dict(model_zoo.load_url(model_urls['resnet18'])) + return model + + +def resnet34(pretrained=False, **kwargs): + """Constructs a ResNet-34 model. + + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + """ + model = ResNet(BasicBlock, [3, 4, 6, 3], **kwargs) + if pretrained: + model.load_state_dict(model_zoo.load_url(model_urls['resnet34'])) + return model + + +def resnet50(pretrained=False, **kwargs): + """Constructs a ResNet-50 model. + + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + """ + model = ResNet(Bottleneck, [3, 4, 6, 3], **kwargs) + if pretrained: + model.load_state_dict(model_zoo.load_url(model_urls['resnet50'])) + return model + + +def resnet101(pretrained=False, **kwargs): + """Constructs a ResNet-101 model. + + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + """ + model = ResNet(Bottleneck, [3, 4, 23, 3], **kwargs) + if pretrained: + model.load_state_dict(model_zoo.load_url(model_urls['resnet101'])) + return model + + +def resnet152(pretrained=False, **kwargs): + """Constructs a ResNet-152 model. + + Args: + pretrained (bool): If True, returns a model pre-trained on ImageNet + """ + model = ResNet(Bottleneck, [3, 8, 36, 3], **kwargs) + if pretrained: + model.load_state_dict(model_zoo.load_url(model_urls['resnet152'])) + return model diff --git a/utils/utils.py b/utils/utils.py index 324d2a9..9e39a29 100644 --- a/utils/utils.py +++ b/utils/utils.py @@ -35,6 +35,19 @@ def init_weights(layer): layer.bias.data.fill_(0) +def weights_init(m): + classname = m.__class__.__name__ + if classname.find('Conv') != -1: + m.weight.data.normal_(0.0, 0.02) + elif classname.find('BatchNorm') != -1: + m.weight.data.normal_(1.0, 0.02) + m.bias.data.fill_(0) + elif classname.find('Linear') != -1: + size = m.weight.size() + m.weight.data.normal_(0.0, 0.1) + m.bias.data.fill_(0) + + def init_random_seed(manual_seed): """Init random seed.""" seed = None @@ -61,6 +74,8 @@ def get_data_loader(name, dataset_root, batch_size, train=True): return get_office(dataset_root, batch_size, 'amazon') elif name == "webcam31": return get_office(dataset_root, batch_size, 'webcam') + elif name == "dslr31": + return get_office(dataset_root, batch_size, 'dslr') elif name == "webcam10": return get_officecaltech(dataset_root, batch_size, 'webcam') elif name == "syndigits":