235 lines
6.4 KiB
Markdown
235 lines
6.4 KiB
Markdown
|
# Tutorial 4: Customize Models
|
||
|
|
||
|
## Customize optimizer
|
||
|
|
||
|
Assume you want to add a optimizer named as `MyOptimizer`, which has arguments `a`, `b`, and `c`.
|
||
|
You need to first implement the new optimizer in a file, e.g., in `mmseg/core/optimizer/my_optimizer.py`:
|
||
|
|
||
|
```python
|
||
|
from mmcv.runner import OPTIMIZERS
|
||
|
from torch.optim import Optimizer
|
||
|
|
||
|
|
||
|
@OPTIMIZERS.register_module
|
||
|
class MyOptimizer(Optimizer):
|
||
|
|
||
|
def __init__(self, a, b, c)
|
||
|
|
||
|
```
|
||
|
|
||
|
Then add this module in `mmseg/core/optimizer/__init__.py` thus the registry will
|
||
|
find the new module and add it:
|
||
|
|
||
|
```python
|
||
|
from .my_optimizer import MyOptimizer
|
||
|
```
|
||
|
|
||
|
Then you can use `MyOptimizer` in `optimizer` field of config files.
|
||
|
In the configs, the optimizers are defined by the field `optimizer` like the following:
|
||
|
|
||
|
```python
|
||
|
optimizer = dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)
|
||
|
```
|
||
|
|
||
|
To use your own optimizer, the field can be changed as
|
||
|
|
||
|
```python
|
||
|
optimizer = dict(type='MyOptimizer', a=a_value, b=b_value, c=c_value)
|
||
|
```
|
||
|
|
||
|
We already support to use all the optimizers implemented by PyTorch, and the only modification is to change the `optimizer` field of config files.
|
||
|
For example, if you want to use `ADAM`, though the performance will drop a lot, the modification could be as the following.
|
||
|
|
||
|
```python
|
||
|
optimizer = dict(type='Adam', lr=0.0003, weight_decay=0.0001)
|
||
|
```
|
||
|
|
||
|
The users can directly set arguments following the [API doc](https://pytorch.org/docs/stable/optim.html?highlight=optim#module-torch.optim) of PyTorch.
|
||
|
|
||
|
## Customize optimizer constructor
|
||
|
|
||
|
Some models may have some parameter-specific settings for optimization, e.g. weight decay for BatchNoarm layers.
|
||
|
The users can do those fine-grained parameter tuning through customizing optimizer constructor.
|
||
|
|
||
|
```
|
||
|
from mmcv.utils import build_from_cfg
|
||
|
|
||
|
from mmcv.runner import OPTIMIZER_BUILDERS
|
||
|
from .cocktail_optimizer import CocktailOptimizer
|
||
|
|
||
|
|
||
|
@OPTIMIZER_BUILDERS.register_module
|
||
|
class CocktailOptimizerConstructor(object):
|
||
|
|
||
|
def __init__(self, optimizer_cfg, paramwise_cfg=None):
|
||
|
|
||
|
def __call__(self, model):
|
||
|
|
||
|
return my_optimizer
|
||
|
|
||
|
```
|
||
|
|
||
|
## Develop new components
|
||
|
|
||
|
There are mainly 2 types of components in MMSegmentation.
|
||
|
|
||
|
- backbone: usually stacks of convolutional network to extract feature maps, e.g., ResNet, HRNet.
|
||
|
- head: the component for semantic segmentation map decoding.
|
||
|
|
||
|
### Add new backbones
|
||
|
|
||
|
Here we show how to develop new components with an example of MobileNet.
|
||
|
|
||
|
1. Create a new file `mmseg/models/backbones/mobilenet.py`.
|
||
|
|
||
|
```python
|
||
|
import torch.nn as nn
|
||
|
|
||
|
from ..registry import BACKBONES
|
||
|
|
||
|
|
||
|
@BACKBONES.register_module
|
||
|
class MobileNet(nn.Module):
|
||
|
|
||
|
def __init__(self, arg1, arg2):
|
||
|
pass
|
||
|
|
||
|
def forward(self, x): # should return a tuple
|
||
|
pass
|
||
|
|
||
|
def init_weights(self, pretrained=None):
|
||
|
pass
|
||
|
```
|
||
|
|
||
|
2. Import the module in `mmseg/models/backbones/__init__.py`.
|
||
|
|
||
|
```python
|
||
|
from .mobilenet import MobileNet
|
||
|
```
|
||
|
|
||
|
3. Use it in your config file.
|
||
|
|
||
|
```python
|
||
|
model = dict(
|
||
|
...
|
||
|
backbone=dict(
|
||
|
type='MobileNet',
|
||
|
arg1=xxx,
|
||
|
arg2=xxx),
|
||
|
...
|
||
|
```
|
||
|
|
||
|
### Add new heads
|
||
|
|
||
|
In MMSegmentation, we provide a base [BaseDecodeHead](https://github.com/open-mmlab/mmsegmentation/blob/master/mmseg/models/decode_heads/decode_head.py) for all segmentation head.
|
||
|
All newly implemented decode heads should be derived from it.
|
||
|
Here we show how to develop a new head with the example of [PSPNet](https://arxiv.org/abs/1612.01105) as the following.
|
||
|
|
||
|
First, add a new decode head in `mmseg/models/decode_heads/psp_head.py`.
|
||
|
PSPNet implements a decode head for segmentation decode.
|
||
|
To implement a decode head, basically we need to implement three functions of the new module as the following.
|
||
|
|
||
|
```python
|
||
|
@HEADS.register_module()
|
||
|
class PSPHead(BaseDecodeHead):
|
||
|
|
||
|
def __init__(self, pool_scales=(1, 2, 3, 6), **kwargs):
|
||
|
super(PSPHead, self).__init__(**kwargs)
|
||
|
|
||
|
def init_weights(self):
|
||
|
|
||
|
def forward(self, inputs):
|
||
|
|
||
|
```
|
||
|
|
||
|
Next, the users need to add the module in the `mmseg/models/decode_heads/__init__.py` thus the corresponding registry could find and load them.
|
||
|
|
||
|
To config file of PSPNet is as the following
|
||
|
|
||
|
```python
|
||
|
norm_cfg = dict(type='SyncBN', requires_grad=True)
|
||
|
model = dict(
|
||
|
type='EncoderDecoder',
|
||
|
pretrained='pretrain_model/resnet50_v1c_trick-2cccc1ad.pth',
|
||
|
backbone=dict(
|
||
|
type='ResNetV1c',
|
||
|
depth=50,
|
||
|
num_stages=4,
|
||
|
out_indices=(0, 1, 2, 3),
|
||
|
dilations=(1, 1, 2, 4),
|
||
|
strides=(1, 2, 1, 1),
|
||
|
norm_cfg=norm_cfg,
|
||
|
norm_eval=False,
|
||
|
style='pytorch',
|
||
|
contract_dilation=True),
|
||
|
decode_head=dict(
|
||
|
type='PSPHead',
|
||
|
in_channels=2048,
|
||
|
in_index=3,
|
||
|
channels=512,
|
||
|
pool_scales=(1, 2, 3, 6),
|
||
|
dropout_ratio=0.1,
|
||
|
num_classes=19,
|
||
|
norm_cfg=norm_cfg,
|
||
|
align_corners=False,
|
||
|
loss_decode=dict(
|
||
|
type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0)))
|
||
|
|
||
|
```
|
||
|
|
||
|
### Add new loss
|
||
|
|
||
|
Assume you want to add a new loss as `MyLoss` for segmentation decode.
|
||
|
To add a new loss function, the users need implement it in `mmseg/models/losses/my_loss.py`.
|
||
|
The decorator `weighted_loss` enable the loss to be weighted for each element.
|
||
|
|
||
|
```python
|
||
|
import torch
|
||
|
import torch.nn as nn
|
||
|
|
||
|
from ..builder import LOSSES
|
||
|
from .utils import weighted_loss
|
||
|
|
||
|
@weighted_loss
|
||
|
def my_loss(pred, target):
|
||
|
assert pred.size() == target.size() and target.numel() > 0
|
||
|
loss = torch.abs(pred - target)
|
||
|
return loss
|
||
|
|
||
|
@LOSSES.register_module
|
||
|
class MyLoss(nn.Module):
|
||
|
|
||
|
def __init__(self, reduction='mean', loss_weight=1.0):
|
||
|
super(MyLoss, self).__init__()
|
||
|
self.reduction = reduction
|
||
|
self.loss_weight = loss_weight
|
||
|
|
||
|
def forward(self,
|
||
|
pred,
|
||
|
target,
|
||
|
weight=None,
|
||
|
avg_factor=None,
|
||
|
reduction_override=None):
|
||
|
assert reduction_override in (None, 'none', 'mean', 'sum')
|
||
|
reduction = (
|
||
|
reduction_override if reduction_override else self.reduction)
|
||
|
loss = self.loss_weight * my_loss(
|
||
|
pred, target, weight, reduction=reduction, avg_factor=avg_factor)
|
||
|
return loss
|
||
|
```
|
||
|
|
||
|
Then the users need to add it in the `mmseg/models/losses/__init__.py`.
|
||
|
|
||
|
```python
|
||
|
from .my_loss import MyLoss, my_loss
|
||
|
|
||
|
```
|
||
|
|
||
|
To use it, modify the `loss_xxx` field.
|
||
|
Then you need to modify the `loss_decode` field in the head.
|
||
|
`loss_weight` could be used to balance multiple losses.
|
||
|
|
||
|
```python
|
||
|
loss_decode=dict(type='MyLoss', loss_weight=1.0))
|
||
|
```
|