Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
jingzhengli committed Oct 8, 2022
0 parents commit b29aa5c
Show file tree
Hide file tree
Showing 224 changed files with 45,831 additions and 0 deletions.
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
## NaCL:Noise-Robust Cross-Domain Contrastive Learning for Unsupervised Domain Adaptation

### Introduction
This is a PyTorch implementation of ["NaCL:Noise-Robust Cross-Domain Contrastive Learning for Unsupervised Domain Adaptation"].

### Requirements
* Python 3.7
* torchvision 0.9.0
* PyTorch 1.8.0


### Train:

- Unsupervised DA on `Office31`,`OfficeHome`, and `VisDA2017` datasets:
```bash
sh runUDA.sh
```
- Semi-supervised DA on `COVID-19` dataset:

```bash
sh runSSDA.sh
```
### Log:

- The training log will be generated in the folder with ``--log_dir``. We can visualize the training process through `tensorboard` as follows.

```bash
tensorboard --logdir=/log_dir/ --host= `host address`
```

### Usage

- We uploaded the file `PythonGraphPers_withCompInfo.so` for computing the `connected components`. If you need to generate it, you can compile the C++ code in folder `ref`, run `./compile_pers_lib.sh` (by default it requires Python 3.7. If you are using other Python versions, modify the command inside `compile_pers_lib.sh`).

- `pybind11` is a lightweight header-only library that exposes C++ types in Python and vice versa, mainly to create Python bindings of existing C++ code. Our code will be further improved to make it cleaner and easier to use.


***Note***: Place the datasets in the corresponding data path.



1 change: 1 addition & 0 deletions common/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__all__ = ['modules', 'vision']
Binary file added common/__pycache__/__init__.cpython-37.pyc
Binary file not shown.
3 changes: 3 additions & 0 deletions common/modules/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .classifier import *

__all__ = ['classifier']
Binary file added common/modules/__pycache__/__init__.cpython-37.pyc
Binary file not shown.
Binary file not shown.
125 changes: 125 additions & 0 deletions common/modules/classifier.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
from typing import Tuple, Optional, List, Dict
import torch.nn as nn
import torch
import torch.nn.functional as F
import math

__all__ = ['Classifier']
def initialize_layer(layer):
for m in layer.modules():
if isinstance(m, (nn.BatchNorm2d, nn.BatchNorm1d)):
nn.init.constant_(m.weight, 1)
nn.init.constant_(m.bias, 0)
elif isinstance(m, nn.Linear):
nn.init.kaiming_normal_(m.weight)
nn.init.constant_(m.bias, 0)

def initialize_layer2(layer):
for m in layer.modules():
if isinstance(m, (nn.BatchNorm2d, nn.BatchNorm1d)):
nn.init.constant_(m.weight, 1)
# nn.init.constant_(m.bias, 0)
elif isinstance(m, nn.Linear):
nn.init.kaiming_normal_(m.weight)
# nn.init.constant_(m.bias, 0)

class Classifier(nn.Module):
"""A generic Classifier class for domain adaptation.
Args:
backbone (torch.nn.Module): Any backbone to extract 2-d features from data
num_classes (int): Number of classes
bottleneck (torch.nn.Module, optional): Any bottleneck layer. Use no bottleneck by default
bottleneck_dim (int, optional): Feature dimension of the bottleneck layer. Default: -1
head (torch.nn.Module, optional): Any classifier head. Use :class:`torch.nn.Linear` by default
finetune (bool): Whether finetune the classifier or train from scratch. Default: True
.. note::
Different classifiers are used in different domain adaptation algorithms to achieve better accuracy
respectively, and we provide a suggested `Classifier` for different algorithms.
Remember they are not the core of algorithms. You can implement your own `Classifier` and combine it with
the domain adaptation algorithm in this algorithm library.
.. note::
The learning rate of this classifier is set 10 times to that of the feature extractor for better accuracy
by default. If you have other optimization strategies, please over-ride :meth:`~Classifier.get_parameters`.
Inputs:
- x (tensor): input data fed to `backbone`
Outputs:
- predictions: classifier's predictions
- features: features after `bottleneck` layer and before `head` layer
Shape:
- Inputs: (minibatch, *) where * means, any number of additional dimensions
- predictions: (minibatch, `num_classes`)
- features: (minibatch, `features_dim`)
"""

def __init__(self, backbone: nn.Module, num_classes: int,
bottleneck_dim: Optional[int] = -1, head: Optional[nn.Module] = None, finetune=True, pool_layer=None):
super(Classifier, self).__init__()
self.backbone = backbone
self.bottleneck_dim = bottleneck_dim
self.parameter_list = [{"params": self.backbone.parameters(), "lr": 1}]
self.num_classes = num_classes
if pool_layer is None:
self.pool_layer = nn.Sequential(
nn.AdaptiveAvgPool2d(output_size=(1, 1)),
nn.Flatten()
)
else:
self.pool_layer = pool_layer
self.bottleneck = nn.Sequential(
nn.Linear(self.backbone.out_features, 256),
nn.BatchNorm1d(256),
nn.ReLU()
)
initialize_layer(self.bottleneck)
self.parameter_list += [{"params": self.bottleneck.parameters(), "lr": 10}]
self.contrast_layer = nn.Sequential(
nn.Dropout(0.5),
nn.Linear(256, self.bottleneck_dim),
)
initialize_layer(self.contrast_layer)
self.parameter_list += [{"params": self.contrast_layer.parameters(), "lr": 10}]

self.classifier = nn.Sequential(
nn.Dropout(0.5),
nn.Linear(256, num_classes)
)

initialize_layer(self.classifier)
self.parameter_list += [{"params": self.classifier.parameters(), "lr": 10}]

@property
def features_dim(self) -> int:
"""The dimension of features before the final `head` layer"""
return self._features_dim

def forward(self, x: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
""""""
end_points = {}
features = self.pool_layer(self.backbone(x))
end_points['features'] = features
features=self.bottleneck(features)
end_points['norm_features'] = features

contrast_features = self.contrast_layer(features)
contrast_features = F.normalize(contrast_features, p=2, dim=1)
end_points['contrast_features'] = contrast_features
logits = self.classifier(features)
end_points['logits'] = logits

return end_points

def weight_norm(self):
w = self.classifier[1].weight.data
norm = w.norm(p=2, dim=1, keepdim=True)
self.classifier[1].weight.data = w.div(norm.expand_as(w))


def get_parameter_list(self):
return self.parameter_list
1 change: 1 addition & 0 deletions common/vision/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__all__ = ['models', 'transforms']
Binary file added common/vision/__pycache__/__init__.cpython-37.pyc
Binary file not shown.
3 changes: 3 additions & 0 deletions common/vision/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .resnet import *

__all__ = ['resnet']
Binary file not shown.
Binary file not shown.
180 changes: 180 additions & 0 deletions common/vision/models/resnet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@

import torch.nn as nn
from torchvision import models
from torchvision.models.utils import load_state_dict_from_url
from torchvision.models.resnet import BasicBlock, Bottleneck, model_urls
import copy

__all__ = ['ResNet', 'resnet18', 'resnet34', 'resnet50', 'resnet101',
'resnet152', 'resnext50_32x4d', 'resnext101_32x8d',
'wide_resnet50_2', 'wide_resnet101_2']


class ResNet(models.ResNet):
"""ResNets without fully connected layer"""

def __init__(self, *args, **kwargs):
super(ResNet, self).__init__(*args, **kwargs)
self._out_features = self.fc.in_features

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 = torch.flatten(x, 1)
# x = x.view(-1, self._out_features)
return x

@property
def out_features(self) -> int:
"""The dimension of output features"""
return self._out_features

def copy_head(self) -> nn.Module:
"""Copy the origin fully connected layer"""
return copy.deepcopy(self.fc)


def _resnet(arch, block, layers, pretrained, progress, **kwargs):
model = ResNet(block, layers, **kwargs)
if pretrained:
model_dict = model.state_dict()
pretrained_dict = load_state_dict_from_url(model_urls[arch],
progress=progress)
# remove keys from pretrained dict that doesn't appear in model dict
pretrained_dict = {k: v for k, v in pretrained_dict.items() if k in model_dict}
model.load_state_dict(pretrained_dict, strict=False)
return model


def resnet18(pretrained=False, progress=True, **kwargs):
r"""ResNet-18 model from
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
return _resnet('resnet18', BasicBlock, [2, 2, 2, 2], pretrained, progress,
**kwargs)


def resnet34(pretrained=False, progress=True, **kwargs):
r"""ResNet-34 model from
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
return _resnet('resnet34', BasicBlock, [3, 4, 6, 3], pretrained, progress,
**kwargs)


def resnet50(pretrained=False, progress=True, **kwargs):
r"""ResNet-50 model from
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
return _resnet('resnet50', Bottleneck, [3, 4, 6, 3], pretrained, progress,
**kwargs)


def resnet101(pretrained=False, progress=True, **kwargs):
r"""ResNet-101 model from
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
return _resnet('resnet101', Bottleneck, [3, 4, 23, 3], pretrained, progress,
**kwargs)


def resnet152(pretrained=False, progress=True, **kwargs):
r"""ResNet-152 model from
`"Deep Residual Learning for Image Recognition" <https://arxiv.org/pdf/1512.03385.pdf>`_
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
return _resnet('resnet152', Bottleneck, [3, 8, 36, 3], pretrained, progress,
**kwargs)


def resnext50_32x4d(pretrained=False, progress=True, **kwargs):
r"""ResNeXt-50 32x4d model from
`"Aggregated Residual Transformation for Deep Neural Networks" <https://arxiv.org/pdf/1611.05431.pdf>`_
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
kwargs['groups'] = 32
kwargs['width_per_group'] = 4
return _resnet('resnext50_32x4d', Bottleneck, [3, 4, 6, 3],
pretrained, progress, **kwargs)


def resnext101_32x8d(pretrained=False, progress=True, **kwargs):
r"""ResNeXt-101 32x8d model from
`"Aggregated Residual Transformation for Deep Neural Networks" <https://arxiv.org/pdf/1611.05431.pdf>`_
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
kwargs['groups'] = 32
kwargs['width_per_group'] = 8
return _resnet('resnext101_32x8d', Bottleneck, [3, 4, 23, 3],
pretrained, progress, **kwargs)


def wide_resnet50_2(pretrained=False, progress=True, **kwargs):
r"""Wide ResNet-50-2 model from
`"Wide Residual Networks" <https://arxiv.org/pdf/1605.07146.pdf>`_
The model is the same as ResNet except for the bottleneck number of channels
which is twice larger in every block. The number of channels in outer 1x1
convolutions is the same, e.g. last block in ResNet-50 has 2048-512-2048
channels, and in Wide ResNet-50-2 has 2048-1024-2048.
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
kwargs['width_per_group'] = 64 * 2
return _resnet('wide_resnet50_2', Bottleneck, [3, 4, 6, 3],
pretrained, progress, **kwargs)


def wide_resnet101_2(pretrained=False, progress=True, **kwargs):
r"""Wide ResNet-101-2 model from
`"Wide Residual Networks" <https://arxiv.org/pdf/1605.07146.pdf>`_
The model is the same as ResNet except for the bottleneck number of channels
which is twice larger in every block. The number of channels in outer 1x1
convolutions is the same, e.g. last block in ResNet-50 has 2048-512-2048
channels, and in Wide ResNet-50-2 has 2048-1024-2048.
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
kwargs['width_per_group'] = 64 * 2
return _resnet('wide_resnet101_2', Bottleneck, [3, 4, 23, 3],
pretrained, progress, **kwargs)
Loading

0 comments on commit b29aa5c

Please sign in to comment.