├── .gitignore
├── .idea
├── .gitignore
├── Pooling.iml
├── inspectionProfiles
│ └── profiles_settings.xml
├── misc.xml
└── modules.xml
├── Pooling
├── __pycache__
│ └── get_pooling.cpython-37.pyc
├── get_pooling.py
└── pooling_method
│ ├── AvgPool.py
│ ├── LpPool.py
│ ├── MaxPool.py
│ ├── MixedPool.py
│ ├── StochasticPool.py
│ ├── __init__.py
│ ├── __pycache__
│ ├── AvgPool.cpython-37.pyc
│ ├── LpPool.cpython-37.pyc
│ ├── MaxPool.cpython-37.pyc
│ ├── MixedPool.cpython-37.pyc
│ ├── StochasticPool.cpython-37.pyc
│ ├── __init__.cpython-37.pyc
│ └── lip_pooling.cpython-37.pyc
│ ├── lip_pooling.py
│ ├── median_pool.py
│ └── softpool.py
├── README.md
├── __pycache__
├── configs.cpython-37.pyc
└── dataset_loader.cpython-37.pyc
├── configs.py
├── counter.py
├── dataset_loader.py
├── models
├── get_network.py
└── net
│ ├── __init__.py
│ ├── __pycache__
│ ├── __init__.cpython-37.pyc
│ ├── resnet.cpython-37.pyc
│ └── vgg.cpython-37.pyc
│ ├── densenet.py
│ ├── resnet.py
│ └── vgg.py
├── train.py
└── utils
├── __pycache__
├── train_engine.cpython-37.pyc
└── util.cpython-37.pyc
├── train_engine.py
└── util.py
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea
2 | /log
3 | /ckpts
4 | /runs
5 | /__pycache__
6 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Datasource local storage ignored files
5 | /../../../../:\Github\Pooling\.idea/dataSources/
6 | /dataSources.local.xml
7 | # Editor-based HTTP Client requests
8 | /httpRequests/
9 |
--------------------------------------------------------------------------------
/.idea/Pooling.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Pooling/__pycache__/get_pooling.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rentainhe/pytorch-pooling/f050e157adde95b2721136812158b014e993eee8/Pooling/__pycache__/get_pooling.cpython-37.pyc
--------------------------------------------------------------------------------
/Pooling/get_pooling.py:
--------------------------------------------------------------------------------
1 | import sys
2 | from importlib import import_module
3 | import torch
4 |
5 | def get_pooling(__C):
6 | try:
7 | poolmethod_path = 'Pooling.pooling_method'
8 | pool_method = getattr(import_module(poolmethod_path), __C.pooling)
9 | return pool_method()
10 | except ImportError:
11 | print('the pool method name you entered is not supported yet')
12 | sys.exit()
13 |
14 | # class config:
15 | # def __init__(self):
16 | # self.pooling = 'stochastic'
17 | #
18 | # c = config()
19 | # pool = get_pooling(c)
20 | # p = pool(2,2)
21 | # x = torch.randn(1,128,4,4)
22 | # print(p(x).size())
--------------------------------------------------------------------------------
/Pooling/pooling_method/AvgPool.py:
--------------------------------------------------------------------------------
1 | import torch
2 | import torch.nn as nn
3 | import torch.nn.functional as F
4 |
5 | class AvgPool(nn.Module):
6 | def __init__(self, kernel_size, stride, padding=0):
7 | super(AvgPool,self).__init__()
8 | self.kernel_size = kernel_size
9 | self.stride = stride
10 | self.padding = padding
11 |
12 | def forward(self, x):
13 | x = F.avg_pool2d(x, kernel_size=self.kernel_size, stride=self.stride, padding=self.padding)
14 | return x
15 |
16 | def avg():
17 | print("You are using Avg Pooling Method")
18 | return AvgPool
--------------------------------------------------------------------------------
/Pooling/pooling_method/LpPool.py:
--------------------------------------------------------------------------------
1 | import torch
2 | import torch.nn as nn
3 | import torch.nn.functional as F
4 |
5 | class LpPool(nn.Module):
6 | def __init__(self, kernel_size, stride, padding=0, p=3):
7 | super(LpPool,self).__init__()
8 | self.kernel_size = kernel_size
9 | self.stride = stride
10 | assert self.kernel_size == self.stride, "we only support kernel_size=stride now"
11 | self.padding = padding
12 | self.p = p
13 |
14 | def lp_pool2d(self,input, p):
15 | return torch.mean(input ** p, (4, 5)) ** (1 / p)
16 |
17 | def forward(self, x):
18 | b,c,h,w = x.size()
19 | kh,kw = self.kernel_size,self.stride
20 | h = h//kh
21 | w = w//kw
22 | x = x.view(b,c,h,kh,w,kw).permute(0,1,2,4,3,5).contiguous()
23 | x = self.lp_pool2d(x,self.p)
24 | return x
25 |
26 | def Lp():
27 | print("You are using Lp Pooling Method")
28 | return LpPool
29 |
--------------------------------------------------------------------------------
/Pooling/pooling_method/MaxPool.py:
--------------------------------------------------------------------------------
1 | import torch
2 | import torch.nn as nn
3 | import torch.nn.functional as F
4 |
5 | class MaxPool(nn.Module):
6 | def __init__(self, kernel_size, stride, padding=0):
7 | super(MaxPool,self).__init__()
8 | self.kernel_size = kernel_size
9 | self.stride = stride
10 | self.padding = padding
11 |
12 | def forward(self, x):
13 | x = F.max_pool2d(x, kernel_size=self.kernel_size, stride=self.stride, padding=self.padding)
14 | return x
15 |
16 | def max():
17 | print("You are using Max Pooling Method")
18 | return MaxPool
--------------------------------------------------------------------------------
/Pooling/pooling_method/MixedPool.py:
--------------------------------------------------------------------------------
1 | import torch
2 | import torch.nn as nn
3 | import torch.nn.functional as F
4 |
5 | class mixedPool(nn.Module):
6 | def __init__(self,kernel_size, stride, padding=0, alpha=0.5):
7 | # nn.Module.__init__(self)
8 | super(mixedPool, self).__init__()
9 | alpha = torch.FloatTensor([alpha])
10 | self.alpha = nn.Parameter(alpha) # nn.Parameter is special Variable
11 | self.kernel_size = kernel_size
12 | self.stride = stride
13 | self.padding = padding
14 |
15 | def forward(self, x):
16 | x = self.alpha * F.max_pool2d(x, self.kernel_size, self.stride, self.padding) + (
17 | 1 - self.alpha) * F.avg_pool2d(x, self.kernel_size, self.stride, self.padding)
18 | return x
19 |
20 | def mixed():
21 | print("You are using Mixed Pooling Method")
22 | return mixedPool
--------------------------------------------------------------------------------
/Pooling/pooling_method/StochasticPool.py:
--------------------------------------------------------------------------------
1 | import torch
2 | import torch.nn as nn
3 | import torch.nn.functional as F
4 | from torch.nn.modules.utils import _pair, _quadruple
5 |
6 | class StochasticPool2d(nn.Module):
7 | """ Stochastic 2D pooling, where prob(selecting index)~value of the activation
8 | IM_SIZE should be divisible by 2, not best implementation.
9 | based off:
10 | https://gist.github.com/rwightman/f2d3849281624be7c0f11c85c87c1598#file-median_pool-py-L5
11 | Args:
12 | kernel_size: size of pooling kernel, int or 2-tuple
13 | stride: pool stride, int or 2-tuple
14 | padding: pool padding, int or 4-tuple (l, r, t, b) as in pytorch F.pad
15 | same: override padding and enforce same padding, boolean
16 | """
17 |
18 | def __init__(self, kernel_size=2, stride=2, padding=0, same=False):
19 | super(StochasticPool2d, self).__init__()
20 | self.kernel_size = _pair(kernel_size) # I don't know what this is but it works
21 | self.stride = _pair(stride)
22 | self.padding = _quadruple(padding) # convert to l, r, t, b
23 | self.same = same
24 |
25 | def _padding(self, x):
26 | if self.same:
27 | ih, iw = x.size()[2:]
28 | if ih % self.stride[0] == 0:
29 | ph = max(self.k[0] - self.stride[0], 0)
30 | else:
31 | ph = max(self.k[0] - (ih % self.stride[0]), 0)
32 | if iw % self.stride[1] == 0:
33 | pw = max(self.k[1] - self.stride[1], 0)
34 | else:
35 | pw = max(self.k[1] - (iw % self.stride[1]), 0)
36 | pl = pw // 2
37 | pr = pw - pl
38 | pt = ph // 2
39 | pb = ph - pt
40 | padding = (pl, pr, pt, pb)
41 | else:
42 | padding = self.padding
43 | return padding
44 |
45 | def forward(self, x):
46 | # because multinomial likes to fail on GPU when all values are equal
47 | # Try randomly sampling without calling the get_random function a million times
48 | init_size = x.shape
49 |
50 | # x = F.pad(x, self._padding(x), mode='reflect')
51 | x = x.unfold(2, self.kernel_size[0], self.stride[0]).unfold(3, self.kernel_size[1], self.stride[1])
52 | x = x.contiguous().view(-1, 4)
53 | idx = torch.randint(0, x.shape[1], size=(x.shape[0],)).type(torch.cuda.LongTensor)
54 | x = x.contiguous().view(-1)
55 | x = torch.take(x, idx)
56 | x = x.contiguous().view(init_size[0], init_size[1], int(init_size[2] / 2), int(init_size[3] / 2))
57 | return x
58 |
59 | def stochastic():
60 | print("You are using Stochatic Pooling Method")
61 | return StochasticPool2d
--------------------------------------------------------------------------------
/Pooling/pooling_method/__init__.py:
--------------------------------------------------------------------------------
1 | from Pooling.pooling_method.AvgPool import *
2 | from Pooling.pooling_method.MaxPool import *
3 | from Pooling.pooling_method.MixedPool import *
4 | from Pooling.pooling_method.LpPool import *
5 | from Pooling.pooling_method.lip_pooling import *
6 | from Pooling.pooling_method.StochasticPool import *
7 | from Pooling.pooling_method.softpool import soft
--------------------------------------------------------------------------------
/Pooling/pooling_method/__pycache__/AvgPool.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rentainhe/pytorch-pooling/f050e157adde95b2721136812158b014e993eee8/Pooling/pooling_method/__pycache__/AvgPool.cpython-37.pyc
--------------------------------------------------------------------------------
/Pooling/pooling_method/__pycache__/LpPool.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rentainhe/pytorch-pooling/f050e157adde95b2721136812158b014e993eee8/Pooling/pooling_method/__pycache__/LpPool.cpython-37.pyc
--------------------------------------------------------------------------------
/Pooling/pooling_method/__pycache__/MaxPool.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rentainhe/pytorch-pooling/f050e157adde95b2721136812158b014e993eee8/Pooling/pooling_method/__pycache__/MaxPool.cpython-37.pyc
--------------------------------------------------------------------------------
/Pooling/pooling_method/__pycache__/MixedPool.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rentainhe/pytorch-pooling/f050e157adde95b2721136812158b014e993eee8/Pooling/pooling_method/__pycache__/MixedPool.cpython-37.pyc
--------------------------------------------------------------------------------
/Pooling/pooling_method/__pycache__/StochasticPool.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rentainhe/pytorch-pooling/f050e157adde95b2721136812158b014e993eee8/Pooling/pooling_method/__pycache__/StochasticPool.cpython-37.pyc
--------------------------------------------------------------------------------
/Pooling/pooling_method/__pycache__/__init__.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rentainhe/pytorch-pooling/f050e157adde95b2721136812158b014e993eee8/Pooling/pooling_method/__pycache__/__init__.cpython-37.pyc
--------------------------------------------------------------------------------
/Pooling/pooling_method/__pycache__/lip_pooling.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rentainhe/pytorch-pooling/f050e157adde95b2721136812158b014e993eee8/Pooling/pooling_method/__pycache__/lip_pooling.cpython-37.pyc
--------------------------------------------------------------------------------
/Pooling/pooling_method/lip_pooling.py:
--------------------------------------------------------------------------------
1 | import torch
2 | import torch.nn as nn
3 | import torch.nn.functional as F
4 | from collections import OrderedDict
5 |
6 | def lip2d(x, logit, kernel=2, stride=2, padding=0):
7 | weight = logit.exp()
8 | return F.avg_pool2d(x*weight, kernel, stride, padding)/F.avg_pool2d(weight, kernel, stride, padding)
9 |
10 | class SoftGate(nn.Module):
11 | def __init__(self, coeff=12):
12 | super(SoftGate, self).__init__()
13 | self.coeff = coeff
14 |
15 | def forward(self, x):
16 | return torch.sigmoid(x).mul(self.coeff)
17 |
18 | class SimplifiedLIP(nn.Module):
19 | def __init__(self, channels):
20 | super(SimplifiedLIP, self).__init__()
21 |
22 | rp = channels
23 | # nn.Sequential + OrderedDict 可以为每一个层赋予一个名字,来替代掉本身的 index 命名, 但是只能通过index访问
24 | self.logit = nn.Sequential(
25 | OrderedDict((
26 | ('conv', nn.Conv2d(channels, channels, 3, padding=1, bias=False)),
27 | ('bn', nn.InstanceNorm2d(channels, affine=True)),
28 | ('gate', SoftGate()),
29 | ))
30 | )
31 |
32 | def init_layer(self):
33 | self.logit[0].weight.data.fill_(0.0)
34 |
35 | def forward(self, x):
36 | frac = lip2d(x, self.logit(x))
37 | return frac
38 |
39 | def lip():
40 | print("You are using Lip Pooling Method")
41 | return SimplifiedLIP
42 |
--------------------------------------------------------------------------------
/Pooling/pooling_method/median_pool.py:
--------------------------------------------------------------------------------
1 | import math
2 | import torch
3 | import torch.nn as nn
4 | import torch.nn.functional as F
5 | from torch.nn.modules.utils import _pair, _quadruple
6 |
7 |
8 | class MedianPool2d(nn.Module):
9 | """ Median pool (usable as median filter when stride=1) module.
10 | Args:
11 | kernel_size: size of pooling kernel, int or 2-tuple
12 | stride: pool stride, int or 2-tuple
13 | padding: pool padding, int or 4-tuple (l, r, t, b) as in pytorch F.pad
14 | same: override padding and enforce same padding, boolean
15 | """
16 | def __init__(self, kernel_size=3, stride=1, padding=0, same=False):
17 | super(MedianPool2d, self).__init__()
18 | self.k = _pair(kernel_size)
19 | self.stride = _pair(stride)
20 | self.padding = _quadruple(padding) # convert to l, r, t, b
21 | self.same = same
22 |
23 | def _padding(self, x):
24 | if self.same:
25 | ih, iw = x.size()[2:]
26 | if ih % self.stride[0] == 0:
27 | ph = max(self.k[0] - self.stride[0], 0)
28 | else:
29 | ph = max(self.k[0] - (ih % self.stride[0]), 0)
30 | if iw % self.stride[1] == 0:
31 | pw = max(self.k[1] - self.stride[1], 0)
32 | else:
33 | pw = max(self.k[1] - (iw % self.stride[1]), 0)
34 | pl = pw // 2
35 | pr = pw - pl
36 | pt = ph // 2
37 | pb = ph - pt
38 | padding = (pl, pr, pt, pb)
39 | else:
40 | padding = self.padding
41 | return padding
42 |
43 | def forward(self, x):
44 | x = F.pad(x, self._padding(x), mode='reflect')
45 | x = x.unfold(2, self.k[0], self.stride[0]).unfold(3, self.k[1], self.stride[1])
46 | x = x.contiguous().view(x.size()[:4] + (-1,)).median(dim=-1)[0]
47 | return x
--------------------------------------------------------------------------------
/Pooling/pooling_method/softpool.py:
--------------------------------------------------------------------------------
1 | import torch
2 | import torch.nn as nn
3 | import torch.nn.functional as F
4 | from torch.nn.modules.utils import _triple, _pair, _single
5 |
6 | class SoftPool2d(nn.Module):
7 | def __init__(self, kernel_size, stride, padding=0):
8 | super(SoftPool2d,self).__init__()
9 | self.kernel_size = kernel_size
10 | self.stride = stride
11 | self.padding = padding
12 |
13 | def forward(self, x):
14 | x = self.soft_pool2d(x, kernel_size=self.kernel_size, stride=self.stride)
15 | return x
16 |
17 | def soft_pool2d(self, x, kernel_size=2, stride=None, force_inplace=False):
18 | kernel_size = _pair(kernel_size)
19 | if stride is None:
20 | stride = kernel_size
21 | else:
22 | stride = _pair(stride)
23 | _, c, h, w = x.size()
24 | e_x = torch.sum(torch.exp(x),dim=1,keepdim=True)
25 | return F.avg_pool2d(x.mul(e_x), kernel_size, stride=stride).mul_(sum(kernel_size)).div_(F.avg_pool2d(e_x, kernel_size, stride=stride).mul_(sum(kernel_size)))
26 |
27 | def soft():
28 | print("You are using Soft Pooling Method")
29 | return SoftPool2d
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Pooling
2 | This is a collection of different pooling methods used in `image classification` `segmentation` `detection`
3 |
4 | ## Features
5 | * Multi-GPU support
6 | * Easy and Useful Training log file
7 | * Easy to test different pooling method on classification task
8 |
9 | ## Requirements
10 | * python3.6
11 | * pytorch1.6.0 + cuda10.1
12 | * tensorboard 2.3.0
13 |
14 | ## Installation
15 | * clone
16 | ```
17 | git clone https://github.com/rentainhe/pytorch-pooling.git
18 | ```
19 | * make data directory for cifar100
20 | ```bash
21 | $ cd pytorch-pooling
22 | $ mkdir data
23 | ```
24 |
25 | ## Usage
26 |
27 | ### 1. enter directory
28 | ```bash
29 | $ cd pytorch-pooling
30 | ```
31 |
32 | ### 2. dataset
33 | * Only support cifar100 now (Will support Imagenet Later)
34 | * Using cifar100 dataset from torchvision since it's more convinient
35 |
36 | ### 3. run tensorboard
37 | Install tensorboard
38 | ```bash
39 | $ pip install tensorboard
40 | Run tensorboard
41 | $ tensorboard --logdir runs --port 6006 --host localhost
42 | ```
43 |
44 | ### 4. training
45 | Our base backbone is `vgg16` with `batch_normalization`
46 | ```bash
47 | $ python3 train.py --run train --name test --pooling max
48 | ```
49 |
50 | - ```--run={'train','test','visual'}``` to set the mode to be executed
51 |
52 | - ```--name=str``` to set the name of this training
53 |
54 | - ```--pooling=str```, e.g, `--pooling='max'` to set the __pooling method__ in `vgg16` to be `max_pool2d`
55 |
56 | - ```--gpu=str```, e.g, `--gpu='1'` to set the specified GPU for training
57 |
58 | The supported pooling args are
59 | ```
60 | max pooling
61 | average pooling
62 | mixed pooling
63 | Lp pooling
64 | lip pooling
65 | soft pooling
66 | ```
67 |
68 | ### 5. Add a new pooling method
69 | You should add a new pooling method `pool.py` in `"/Pooling/pooling_method"` and update the `__init__.py` file
70 |
71 | ### 6. Addition
72 | - lip pooling: the backbone in original paper is `ResNet`, But I use `VggNet` in this repo, so there might be something wrong with the accuracy
73 |
74 | ## Results
75 | The result I can get from this repo, I train every model with the same hyperparam and I don't use any tricks in this repo.
76 |
77 | |dataset|backbone|pooling|acc|epoch(lr = 0.1)|epoch(lr = 0.02)|epoch(lr = 0.004)|epoch(lr = 0.0008)|total epoch|
78 | |:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:
79 | |cifar100|vgg16_bn|max|70.89%|60|60|40|40|200|
80 | |cifar100|vgg16_bn|avg|70.56%|60|60|40|40|200|
81 | |cifar100|vgg16_bn|mixed|71.19%|60|60|40|40|200|
82 | |cifar100|vgg16_bn|Lp(p=2)|70.65%|60|60|40|40|200|
83 | |cifar100|vgg16_bn|Lp(p=3)|70.67%|60|60|40|40|200|
84 | |cifar100|vgg16_bn|lip|71.23%|60|60|40|40|200|
85 | |cifar100|vgg16_bn|softpool|__71.39%__|60|60|40|40|200|
86 |
87 | ## Implementated Pooling
88 |
89 | - mixed pooling [Mixed pooling for convolutional neural networks](https://rd.springer.com/chapter/10.1007/978-3-319-11740-9_34)
90 | - Lp pooling [Convolutional neural networks applied to house numbers digit
91 | classification](https://arxiv.org/abs/1204.3968)
92 | - lip pooling [LIP: Local Importance-based Pooling](https://arxiv.org/abs/1908.04156)
93 | - soft pooling [Refining activation downsampling with SoftPool](https://arxiv.org/abs/2101.00440)
--------------------------------------------------------------------------------
/__pycache__/configs.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rentainhe/pytorch-pooling/f050e157adde95b2721136812158b014e993eee8/__pycache__/configs.cpython-37.pyc
--------------------------------------------------------------------------------
/__pycache__/dataset_loader.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rentainhe/pytorch-pooling/f050e157adde95b2721136812158b014e993eee8/__pycache__/dataset_loader.cpython-37.pyc
--------------------------------------------------------------------------------
/configs.py:
--------------------------------------------------------------------------------
1 | from types import MethodType
2 | from datetime import datetime
3 | import os
4 | import torch
5 | import numpy as np
6 | import random
7 |
8 | class Configs():
9 | def __init__(self):
10 | self.epoch = 200
11 | self.milestones = [60, 120, 160]
12 | self.save_epoch = 20
13 | self.gpu='0'
14 | self.batch_size=64
15 | self.mean = (0.5070751592371323, 0.48654887331495095, 0.4409178433670343)
16 | self.std = (0.2673342858792401, 0.2564384629170883, 0.27615047132568404)
17 | self.num_workers = 8
18 | self.pin_memory = True
19 | self.eval_every_epoch = True
20 | self.time = datetime.now().strftime('%A_%d_%B_%Y_%Hh_%Mm_%Ss')
21 | self.seed = random.randint(0, 9999999)
22 | self.version = str(self.seed)
23 | self.batch_size = 128
24 | self.gradient_accumulation_steps = 1
25 | self.ckpts_dir = 'ckpts'
26 | self.result_log_dir = 'log'
27 | self.tensorboard_log_dir = 'runs'
28 |
29 | def parse_to_dict(self, args):
30 | args_dict = {}
31 | for arg in dir(args):
32 | if not arg.startswith('_') and not isinstance(getattr(args, arg), MethodType):
33 | if getattr(args, arg) is not None:
34 | args_dict[arg] = getattr(args, arg)
35 | return args_dict
36 |
37 | def add_args(self, args_dict):
38 | for arg in args_dict:
39 | setattr(self, arg, args_dict[arg])
40 |
41 | def training_init(self):
42 | os.environ['CUDA_VISIBLE_DEVICES'] = self.gpu
43 | self.n_gpu = len(self.gpu.split(','))
44 | self.devices = [_ for _ in range(self.n_gpu)]
45 | torch.set_num_threads(2)
46 |
47 | # fix seed
48 | torch.manual_seed(self.seed)
49 | if self.n_gpu < 2:
50 | torch.cuda.manual_seed(self.seed)
51 | else:
52 | torch.cuda.manual_seed_all(self.seed)
53 | torch.backends.cudnn.deterministic = True
54 | np.random.seed(self.seed)
55 | random.seed(self.seed)
56 |
57 | # Gradient accumulate setup
58 | assert self.batch_size % self.gradient_accumulation_steps == 0
59 | self.sub_batch_size = int(self.batch_size / self.gradient_accumulation_steps)
60 | self.eval_batch_size = int(self.sub_batch_size / 2)
61 |
62 | def path_init(self):
63 | for attr in dir(self):
64 | if 'dir' in attr and not attr.startswith('__'):
65 | if getattr(self,attr) not in os.listdir('./'):
66 | os.makedirs(getattr(self, attr))
67 |
68 |
69 | def __str__(self):
70 | # print Hyper Parameters
71 | settings_str = ''
72 | for attr in dir(self):
73 | if not 'np' in attr and not 'random' in attr and not attr.startswith('__') and not isinstance(getattr(self, attr), MethodType):
74 | settings_str += '{ %-17s }->' % attr + str(getattr(self, attr)) + '\n'
75 | return settings_str
76 |
77 | configs = Configs()
78 |
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/counter.py:
--------------------------------------------------------------------------------
1 | import torch
2 | import torch.nn as nn
3 | from ptflops import get_model_complexity_info
4 | from models.get_network import get_network
5 | import argparse
6 | from models.net.vgg import vgg16_bn
7 |
8 |
9 | def count_parameters(net):
10 | params = sum([param.nelement() for param in net.parameters() if param.requires_grad])
11 | print("Params: %f M" % (params / 1000000))
12 |
13 |
14 | def parse_args():
15 | parser = argparse.ArgumentParser(description='MAC_Classification Args')
16 | parser.add_argument('--pooling', type=str,
17 | choices=[
18 | 'max',
19 | 'avg',
20 | 'mixed',
21 | 'Lp',
22 | 'lip',
23 | 'stochastic',
24 | 'soft'
25 | ], default='max', help='choose one pooling method to use', required=True)
26 | parser.add_argument('--gpu', type=int, default=0, help="choose a gpu for testing")
27 | args = parser.parse_args()
28 | return args
29 |
30 |
31 | if __name__ == '__main__':
32 |
33 | device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
34 | args = parse_args()
35 |
36 | if device == 'cpu':
37 | net = vgg16_bn(args)
38 | count_parameters(net)
39 | else:
40 | with torch.cuda.device(args.gpu):
41 | net = vgg16_bn(args)
42 | macs, params = get_model_complexity_info(net, (3, 224, 224), as_strings=True,
43 | print_per_layer_stat=True, verbose=True)
44 | print('{:<30} {:<8}'.format('Computational complexity: ', macs))
45 | print('{:<30} {:<8}'.format('Number of parameters: ', params))
46 |
--------------------------------------------------------------------------------
/dataset_loader.py:
--------------------------------------------------------------------------------
1 | from torchvision import transforms, datasets
2 | from torch.utils.data import DataLoader, RandomSampler, SequentialSampler
3 |
4 | def get_train_loader(configs):
5 | # data transforms
6 | transform_train = transforms.Compose([
7 | transforms.RandomCrop(configs.img_size, padding=4),
8 | transforms.ToTensor(),
9 | transforms.Normalize(configs.mean, configs.std)
10 | ])
11 | trainset = datasets.CIFAR100(root='./data/cifar100',
12 | train=True,
13 | download=True,
14 | transform=transform_train)
15 | # get dataloader
16 | train_sampler = RandomSampler(trainset)
17 | train_loader = DataLoader(trainset,
18 | sampler=train_sampler,
19 | batch_size=configs.batch_size,
20 | num_workers=configs.num_workers,
21 | pin_memory=configs.pin_memory)
22 | return train_loader
23 |
24 | def get_test_loader(configs):
25 | # data transforms
26 | transform_test = transforms.Compose([
27 | transforms.Resize((configs.img_size, configs.img_size)),
28 | transforms.ToTensor(),
29 | transforms.Normalize(configs.mean, configs.std)
30 | ])
31 | testset = datasets.CIFAR100(root='./data/cifar100',
32 | train=False,
33 | download=True,
34 | transform=transform_test)
35 |
36 | # get dataloader
37 | test_sampler = SequentialSampler(testset)
38 | test_loader = DataLoader(testset,
39 | sampler=test_sampler,
40 | batch_size=configs.eval_batch_size,
41 | num_workers=configs.num_workers,
42 | pin_memory=configs.pin_memory)
43 | return test_loader
44 |
--------------------------------------------------------------------------------
/models/get_network.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import importlib
4 | from importlib import import_module
5 |
6 | def get_network(__C):
7 | try:
8 | model_path = 'models.net'
9 | net = getattr(import_module(model_path),__C.model)
10 | return net()
11 | except ImportError:
12 | print('the network name you have entered is not supported yet')
13 | sys.exit()
14 |
--------------------------------------------------------------------------------
/models/net/__init__.py:
--------------------------------------------------------------------------------
1 | from models.net.resnet import *
2 | from models.net.vgg import *
--------------------------------------------------------------------------------
/models/net/__pycache__/__init__.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rentainhe/pytorch-pooling/f050e157adde95b2721136812158b014e993eee8/models/net/__pycache__/__init__.cpython-37.pyc
--------------------------------------------------------------------------------
/models/net/__pycache__/resnet.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rentainhe/pytorch-pooling/f050e157adde95b2721136812158b014e993eee8/models/net/__pycache__/resnet.cpython-37.pyc
--------------------------------------------------------------------------------
/models/net/__pycache__/vgg.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rentainhe/pytorch-pooling/f050e157adde95b2721136812158b014e993eee8/models/net/__pycache__/vgg.cpython-37.pyc
--------------------------------------------------------------------------------
/models/net/densenet.py:
--------------------------------------------------------------------------------
1 | import torch
2 | import torch.nn as nn
3 | import torch.nn.functional as F
4 | import math
5 |
6 |
7 | __all__ = ['densenet']
8 |
9 |
10 | from torch.autograd import Variable
11 |
12 | class Bottleneck(nn.Module):
13 | def __init__(self, inplanes, expansion=4, growthRate=12, dropRate=0):
14 | super(Bottleneck, self).__init__()
15 | planes = expansion * growthRate
16 | self.bn1 = nn.BatchNorm2d(inplanes)
17 | self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False)
18 | self.bn2 = nn.BatchNorm2d(planes)
19 | self.conv2 = nn.Conv2d(planes, growthRate, kernel_size=3,
20 | padding=1, bias=False)
21 | self.relu = nn.ReLU(inplace=True)
22 | self.dropRate = dropRate
23 |
24 | def forward(self, x):
25 | out = self.bn1(x)
26 | out = self.relu(out)
27 | out = self.conv1(out)
28 | out = self.bn2(out)
29 | out = self.relu(out)
30 | out = self.conv2(out)
31 | if self.dropRate > 0:
32 | out = F.dropout(out, p=self.dropRate, training=self.training)
33 |
34 | out = torch.cat((x, out), 1)
35 |
36 | return out
37 |
38 |
39 | class BasicBlock(nn.Module):
40 | def __init__(self, inplanes, expansion=1, growthRate=12, dropRate=0):
41 | super(BasicBlock, self).__init__()
42 | planes = expansion * growthRate
43 | self.bn1 = nn.BatchNorm2d(inplanes)
44 | self.conv1 = nn.Conv2d(inplanes, growthRate, kernel_size=3,
45 | padding=1, bias=False)
46 | self.relu = nn.ReLU(inplace=True)
47 | self.dropRate = dropRate
48 |
49 | def forward(self, x):
50 | out = self.bn1(x)
51 | out = self.relu(out)
52 | out = self.conv1(out)
53 | if self.dropRate > 0:
54 | out = F.dropout(out, p=self.dropRate, training=self.training)
55 |
56 | out = torch.cat((x, out), 1)
57 |
58 | return out
59 |
60 |
61 | class Transition(nn.Module):
62 | def __init__(self, inplanes, outplanes):
63 | super(Transition, self).__init__()
64 | self.bn1 = nn.BatchNorm2d(inplanes)
65 | self.conv1 = nn.Conv2d(inplanes, outplanes, kernel_size=1,
66 | bias=False)
67 | self.relu = nn.ReLU(inplace=True)
68 |
69 | def forward(self, x):
70 | out = self.bn1(x)
71 | out = self.relu(out)
72 | out = self.conv1(out)
73 | out = F.avg_pool2d(out, 2)
74 | return out
75 |
76 |
77 | class DenseNet(nn.Module):
78 |
79 | def __init__(self, depth=22, block=Bottleneck,
80 | dropRate=0, num_classes=10, growthRate=12, compressionRate=2):
81 | super(DenseNet, self).__init__()
82 |
83 | assert (depth - 4) % 3 == 0, 'depth should be 3n+4'
84 | n = (depth - 4) / 3 if block == BasicBlock else (depth - 4) // 6
85 |
86 | self.growthRate = growthRate
87 | self.dropRate = dropRate
88 |
89 | # self.inplanes is a global variable used across multiple
90 | # helper functions
91 | self.inplanes = growthRate * 2
92 | self.conv1 = nn.Conv2d(3, self.inplanes, kernel_size=3, padding=1,
93 | bias=False)
94 | self.dense1 = self._make_denseblock(block, n)
95 | self.trans1 = self._make_transition(compressionRate)
96 | self.dense2 = self._make_denseblock(block, n)
97 | self.trans2 = self._make_transition(compressionRate)
98 | self.dense3 = self._make_denseblock(block, n)
99 | self.bn = nn.BatchNorm2d(self.inplanes)
100 | self.relu = nn.ReLU(inplace=True)
101 | self.avgpool = nn.AvgPool2d(8)
102 | self.fc = nn.Linear(self.inplanes, num_classes)
103 |
104 | # Weight initialization
105 | for m in self.modules():
106 | if isinstance(m, nn.Conv2d):
107 | n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
108 | m.weight.data.normal_(0, math.sqrt(2. / n))
109 | elif isinstance(m, nn.BatchNorm2d):
110 | m.weight.data.fill_(1)
111 | m.bias.data.zero_()
112 |
113 | def _make_denseblock(self, block, blocks):
114 | layers = []
115 | for i in range(blocks):
116 | # Currently we fix the expansion ratio as the default value
117 | layers.append(block(self.inplanes, growthRate=self.growthRate, dropRate=self.dropRate))
118 | self.inplanes += self.growthRate
119 |
120 | return nn.Sequential(*layers)
121 |
122 | def _make_transition(self, compressionRate):
123 | inplanes = self.inplanes
124 | outplanes = int(math.floor(self.inplanes // compressionRate))
125 | self.inplanes = outplanes
126 | return Transition(inplanes, outplanes)
127 |
128 |
129 | def forward(self, x):
130 | x = self.conv1(x)
131 |
132 | x = self.trans1(self.dense1(x))
133 | x = self.trans2(self.dense2(x))
134 | x = self.dense3(x)
135 | x = self.bn(x)
136 | x = self.relu(x)
137 |
138 | x = self.avgpool(x)
139 | x = x.view(x.size(0), -1)
140 | x = self.fc(x)
141 |
142 | return x
143 |
144 |
145 | def densenet(**kwargs):
146 | """
147 | Constructs a ResNet model.
148 | """
149 | return DenseNet(**kwargs)
--------------------------------------------------------------------------------
/models/net/resnet.py:
--------------------------------------------------------------------------------
1 | import torch.nn as nn
2 |
3 | class BasicBlock(nn.Module):
4 | # Residual block for resnet18 and resnet 34
5 | expansion = 1 # 维度拓展
6 | def __init__(self, in_channels, out_channels, stride=1):
7 | super().__init__()
8 | # residual function
9 | self.residual_function = nn.Sequential(
10 | nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False),
11 | nn.BatchNorm2d(out_channels),
12 | nn.ReLU(inplace=True),
13 | nn.Conv2d(out_channels, out_channels * BasicBlock.expansion, kernel_size=3, padding=1, bias=False),
14 | nn.BatchNorm2d(out_channels * BasicBlock.expansion) # 注意 expansion 的用法
15 | )
16 | self.shortcut = nn.Sequential()
17 |
18 | # the shortcut output dimension is not the same with residual function
19 | # use 1*1 convolution to match the dimension
20 | if stride!=1 or in_channels!=BasicBlock.expansion * out_channels:
21 | self.shortcut = nn.Sequential(
22 | nn.Conv2d(in_channels, out_channels * BasicBlock.expansion, kernel_size=1, stride=stride, bias=False),
23 | nn.BatchNorm2d(out_channels * BasicBlock.expansion)
24 | )
25 |
26 | def forward(self, x):
27 | return nn.ReLU(inplace=True)(self.residual_function(x) + self.shortcut(x))
28 |
29 | class BottleNeck(nn.Module):
30 | # Residual block for resnet over 50 layers
31 | # 瓶颈层,因为用了 1*1 的卷积,比较方便改变网络的大小,先降维后升维,将高频噪声消除,并且减少计算量,在深层网络上常用
32 | expansion=4
33 | def __init__(self, in_channels, out_channels, stride=1):
34 | super().__init__()
35 | self.residual_function = nn.Sequential(
36 | nn.Conv2d(in_channels, out_channels, kernel_size=1, bias=False),
37 | nn.BatchNorm2d(out_channels),
38 | nn.ReLU(inplace=True),
39 | nn.Conv2d(out_channels, out_channels, stride=stride, kernel_size=3, padding=1, bias=False),
40 | nn.BatchNorm2d(out_channels),
41 | nn.ReLU(inplace=True),
42 | nn.Conv2d(out_channels, out_channels * BottleNeck.expansion, kernel_size=1, bias=False),
43 | nn.BatchNorm2d(out_channels * BottleNeck.expansion)
44 | )
45 | self.shortcut = nn.Sequential()
46 |
47 | # 如果 shortcut 的维度和 residual function 的维度不相等的话,需要用 1*1 的卷积改变一下维度大小
48 | if stride!=1 or in_channels!=out_channels*BottleNeck.expansion:
49 | self.shortcut = nn.Sequential(
50 | nn.Conv2d(in_channels, out_channels * BottleNeck.expansion, stride=stride, kernel_size=1, bias=False),
51 | nn.BatchNorm2d(out_channels * BottleNeck.expansion)
52 | )
53 |
54 | def forward(self, x):
55 | return nn.ReLU(inplace=True)(self.residual_function(x) + self.shortcut(x))
56 |
57 | class ResNet(nn.Module):
58 | def __init__(self, block, num_block, num_classes=100):
59 | super().__init__()
60 | self.in_channels = 64
61 |
62 | self.conv1 = nn.Sequential(
63 | nn.Conv2d(3, 64, kernel_size=3, padding=1, bias=False),
64 | nn.BatchNorm2d(64),
65 | nn.ReLU(inplace=True)
66 | )
67 |
68 | self.conv2_x = self._make_layer(block, 64, num_block[0], 1)
69 | self.conv3_x = self._make_layer(block, 128, num_block[1], 2)
70 | self.conv4_x = self._make_layer(block, 256, num_block[2], 2)
71 | self.conv5_x = self._make_layer(block, 512, num_block[3], 2)
72 | self.avg_pool = nn.AdaptiveAvgPool2d((1,1))
73 | self.fc = nn.Linear(512*block.expansion, num_classes)
74 |
75 | def _make_layer(self, block, out_channels, num_blocks, stride):
76 | """make resnet layers(by layer i didn't mean this 'layer' was the
77 | same as a neuron network layer, ex. conv layer), one layer may
78 | contain more than one residual block
79 |
80 | Args:
81 | block: block type, basic block or bottle neck block
82 | out_channels: output depth channel number of this layer
83 | num_blocks: how many blocks per layer
84 | stride: the stride of the first block of this layer
85 |
86 | Return:
87 | return a resnet layer
88 | """
89 | strides = [stride] + [1]*(num_blocks-1)
90 | layers = []
91 | for stride in strides:
92 | layers.append(block(self.in_channels, out_channels, stride))
93 | self.in_channels = out_channels * block.expansion
94 |
95 | return nn.Sequential(*layers)
96 |
97 | def forward(self, x):
98 | output = self.conv1(x)
99 | output = self.conv2_x(output)
100 | output = self.conv3_x(output)
101 | output = self.conv4_x(output)
102 | output = self.conv5_x(output)
103 | output = self.avg_pool(output)
104 | output = output.view(output.size(0),-1)
105 | output = self.fc(output)
106 |
107 | return output
108 |
109 |
110 | def resnet18():
111 | # return a resnet18 object
112 | return ResNet(BasicBlock, [2,2,2,2])
113 |
114 |
115 | def resnet34():
116 | # return a resnet34 object
117 | return ResNet(BasicBlock, [3,4,6,3])
118 |
119 |
120 | def resnet50():
121 | # return a resnet50 object
122 | return ResNet(BottleNeck, [3,4,6,3])
--------------------------------------------------------------------------------
/models/net/vgg.py:
--------------------------------------------------------------------------------
1 | """vgg in pytorch
2 | [1] Karen Simonyan, Andrew Zisserman
3 |
4 | Very Deep Convolutional Networks for Large-Scale Image Recognition.
5 | https://arxiv.org/abs/1409.1556v6
6 | """
7 | '''VGG11/13/16/19 in Pytorch.'''
8 |
9 | import torch
10 | import torch.nn as nn
11 | from Pooling.get_pooling import get_pooling
12 |
13 |
14 | cfg = {
15 | 'A' : [64, 'P', 128, 'P', 256, 256, 'P', 512, 512, 'P', 512, 512, 'P'],
16 | 'B' : [64, 64, 'P', 128, 128, 'P', 256, 256, 'P', 512, 512, 'P', 512, 512, 'P'],
17 | 'D' : [64, 64, 'P', 128, 128, 'P', 256, 256, 256, 'P', 512, 512, 512, 'P', 512, 512, 512, 'P'],
18 | 'E' : [64, 64, 'P', 128, 128, 'P', 256, 256, 256, 256, 'P', 512, 512, 512, 512, 'P', 512, 512, 512, 512, 'P']
19 | }
20 |
21 | class VGG(nn.Module):
22 |
23 | def __init__(self, features, num_class=100):
24 | super().__init__()
25 | self.features = features
26 |
27 | self.classifier = nn.Sequential(
28 | nn.Linear(512, 4096),
29 | nn.ReLU(inplace=True),
30 | nn.Dropout(),
31 | nn.Linear(4096, 4096),
32 | nn.ReLU(inplace=True),
33 | nn.Dropout(),
34 | nn.Linear(4096, num_class)
35 | )
36 |
37 | def forward(self, x):
38 | output = self.features(x)
39 | output = output.view(output.size()[0], -1)
40 | output = self.classifier(output)
41 |
42 | return output
43 |
44 | def make_layers(cfg, __C, batch_norm=False):
45 | layers = []
46 |
47 | input_channel = 3
48 | for i,l in enumerate(cfg):
49 | if l == 'P':
50 |
51 | if __C.pooling == 'lip':
52 | layers += [get_pooling(__C)(cfg[i-1])]
53 |
54 | else: layers += [get_pooling(__C)(kernel_size=2,stride=2)]
55 | continue
56 |
57 | layers += [nn.Conv2d(input_channel, l, kernel_size=3, padding=1)]
58 |
59 | if batch_norm:
60 | layers += [nn.BatchNorm2d(l)]
61 |
62 | layers += [nn.ReLU(inplace=True)]
63 | input_channel = l
64 |
65 | return nn.Sequential(*layers)
66 |
67 | def vgg11_bn(__C):
68 | return VGG(make_layers(cfg['A'], __C, batch_norm=True))
69 |
70 | def vgg13_bn(__C):
71 | return VGG(make_layers(cfg['B'], __C, batch_norm=True))
72 |
73 | def vgg16_bn(__C):
74 | return VGG(make_layers(cfg['D'], __C, batch_norm=True))
75 |
76 | def vgg19_bn(__C):
77 | return VGG(make_layers(cfg['E'], __C, batch_norm=True))
78 |
79 | def vgg11(__C):
80 | return VGG(make_layers(cfg['A'], __C, batch_norm=False))
81 |
82 | def vgg13(__C):
83 | return VGG(make_layers(cfg['B'], __C, batch_norm=False))
84 |
85 | def vgg16(__C):
86 | return VGG(make_layers(cfg['D'], __C, batch_norm=False))
87 |
88 | def vgg19(__C):
89 | return VGG(make_layers(cfg['E'], __C, batch_norm=False))
90 |
91 |
92 |
--------------------------------------------------------------------------------
/train.py:
--------------------------------------------------------------------------------
1 | import argparse
2 | from configs import configs
3 | from utils.train_engine import train_engine
4 |
5 | def parse_args():
6 | parser = argparse.ArgumentParser(description='Pooling Survey Args')
7 | parser.add_argument('--gpu', type=str, help="gpu choose, eg. '0,1,2,...' ")
8 | parser.add_argument('--run', type=str, dest='run_mode',choices=['train','test'])
9 | parser.add_argument('--name', type=str, required=True, help='the name of this training')
10 | parser.add_argument('--img_size', type=int, default=32, help='Resolution size')
11 | parser.add_argument('--batch_size', type=int, default=128, help='batch size for dataloader')
12 | parser.add_argument('--lr', type=float, default=0.1, help='initial learning rate')
13 | parser.add_argument('--lr_decay_rate', type=float, default=0.2, help="learning rate decay rate")
14 | parser.add_argument('--warmup_epoch', type=int, default=1, help='warmup epochs')
15 | parser.add_argument('--epoch', type=int, default=200, help='total epochs')
16 | parser.add_argument('--save_epoch', type=int, default=20, help="save model after every 20 epoch")
17 | parser.add_argument('--eval_every_epoch', action='store_true', default=True, help='evaluate the model every epoch')
18 | parser.add_argument('--pooling', type=str,
19 | choices=[
20 | 'max',
21 | 'avg',
22 | 'mixed',
23 | 'Lp',
24 | 'lip',
25 | 'stochastic',
26 | 'soft'
27 | ], default='max',help='choose one pooling method to use', required=True)
28 | args = parser.parse_args()
29 | return args
30 |
31 | if __name__ == '__main__':
32 | args = parse_args()
33 | args_dict = configs.parse_to_dict(args)
34 | configs.add_args(args_dict)
35 |
36 | configs.training_init()
37 | configs.path_init()
38 |
39 | print("Hyper parameters:")
40 | print(configs)
41 |
42 | if configs.run_mode == 'train':
43 | train_engine(configs)
44 |
--------------------------------------------------------------------------------
/utils/__pycache__/train_engine.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rentainhe/pytorch-pooling/f050e157adde95b2721136812158b014e993eee8/utils/__pycache__/train_engine.cpython-37.pyc
--------------------------------------------------------------------------------
/utils/__pycache__/util.cpython-37.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rentainhe/pytorch-pooling/f050e157adde95b2721136812158b014e993eee8/utils/__pycache__/util.cpython-37.pyc
--------------------------------------------------------------------------------
/utils/train_engine.py:
--------------------------------------------------------------------------------
1 | from models.net.vgg import vgg16_bn
2 | from models.net.vgg import vgg16
3 | from dataset_loader import get_train_loader
4 | from dataset_loader import get_test_loader
5 | import os
6 | import torch
7 | import torch.nn as nn
8 | import torch.optim as optim
9 | from utils.util import WarmUpLR
10 | from torch.utils.tensorboard import SummaryWriter
11 | import time
12 | def train_engine(__C):
13 |
14 | net = vgg16_bn(__C)
15 | net = net.cuda()
16 |
17 | # define dataloader
18 | train_loader = get_train_loader(__C)
19 | test_loader = get_test_loader(__C)
20 |
21 | # define optimizer and loss function
22 | loss_function = nn.CrossEntropyLoss()
23 | optimizer = optim.SGD(params=net.parameters(), lr=__C.lr, momentum=0.9, weight_decay=5e-4)
24 |
25 | # define optimizer scheduler
26 | train_scheduler = optim.lr_scheduler.MultiStepLR(optimizer, milestones=__C.milestones, gamma=__C.lr_decay_rate)
27 | iter_per_epoch = len(train_loader)
28 | warmup_schedule = WarmUpLR(optimizer, iter_per_epoch * __C.warmup_epoch)
29 |
30 | # define tensorboard writer
31 | writer = SummaryWriter(log_dir=os.path.join(__C.tensorboard_log_dir,__C.name,__C.version))
32 |
33 | # define model save dir
34 | checkpoint_path = os.path.join(__C.ckpts_dir, __C.name, __C.version)
35 | if not os.path.exists(checkpoint_path):
36 | os.makedirs(checkpoint_path)
37 | checkpoint_path = os.path.join(checkpoint_path, '{net}-{epoch}-{type}.pth')
38 |
39 | # define the log save dir
40 | log_path = os.path.join(__C.result_log_dir, __C.name)
41 | if not os.path.exists(log_path):
42 | os.makedirs(log_path)
43 | log_path = os.path.join(log_path, __C.version + '.txt')
44 |
45 | # write the hyper parameters to log
46 | logfile = open(log_path, 'a+')
47 | logfile.write(str(__C))
48 | logfile.close()
49 |
50 | best_acc = 0.0
51 | loss_sum = 0
52 | for epoch in range(1, __C.epoch):
53 | if epoch > __C.warmup_epoch:
54 | train_scheduler.step(epoch)
55 |
56 | start = time.time()
57 | net.train()
58 | for step, (images, labels) in enumerate(train_loader):
59 | if epoch <= __C.warmup_epoch:
60 | warmup_schedule.step()
61 | images = images.cuda()
62 | labels = labels.cuda()
63 | # using gradient accumulation step
64 |
65 | optimizer.zero_grad()
66 | loss_tmp = 0
67 | for accu_step in range(__C.gradient_accumulation_steps):
68 | loss_tmp = 0
69 | sub_images = images[accu_step * __C.sub_batch_size:
70 | (accu_step + 1) * __C.sub_batch_size]
71 | sub_labels = labels[accu_step * __C.sub_batch_size:
72 | (accu_step + 1) * __C.sub_batch_size]
73 | outputs = net(sub_images)
74 | loss = loss_function(outputs, sub_labels)
75 | loss.backward()
76 | # loss_tmp += loss.cpu().data.numpy() * __C.gradient_accumulation_steps
77 | # loss_sum += loss.cpu().data.numpy() * __C.gradient_accumulation_steps
78 | loss_tmp += loss.cpu().data.numpy()
79 | loss_sum += loss.cpu().data.numpy()
80 |
81 | optimizer.step()
82 | n_iter = (epoch-1) * len(train_loader) + step + 1
83 | print(
84 | '[{Version}] [{Name}] Training Epoch: {epoch} [{trained_samples}/{total_samples}]\tLoss: {:0.4f}\tLR: {:0.6f}'.format(
85 | loss_tmp,
86 | optimizer.param_groups[0]['lr'],
87 | Version=__C.version,
88 | Name=__C.name,
89 | epoch=epoch,
90 | trained_samples=step * __C.batch_size + len(images),
91 | total_samples=len(train_loader.dataset)
92 | ))
93 | # update training loss for each iteration
94 |
95 | writer.add_scalar('[Epoch] Train/loss', loss_tmp, n_iter)
96 | if epoch <= __C.warmup_epoch:
97 | writer.add_scalar('[Epoch] Train/lr', warmup_schedule.get_lr()[0], epoch)
98 | else:
99 | writer.add_scalar('[Epoch] Train/lr', train_scheduler.get_lr()[0], epoch)
100 |
101 | # update the result logfile
102 | logfile = open(log_path, 'a+')
103 | logfile.write(
104 | 'Epoch: ' + str(epoch) +
105 | ', Train Average Loss: {:.4f}'.format(loss_sum/len(train_loader.dataset)) +
106 | ', Lr: {:.6f}'.format(optimizer.param_groups[0]['lr']) +
107 | ', '
108 | )
109 | logfile.close()
110 | finish = time.time()
111 | print('epoch {} training time consumed: {:.2f}s'.format(epoch, finish - start))
112 |
113 | if __C.eval_every_epoch:
114 | start = time.time()
115 | net.eval()
116 | test_loss = 0.0
117 | correct = 0.0
118 | for (images, labels) in test_loader:
119 | images = images.cuda()
120 | labels = labels.cuda()
121 | eval_outputs = net(images)
122 | eval_loss = loss_function(eval_outputs, labels)
123 | test_loss += eval_loss.item()
124 | _, preds = eval_outputs.max(1)
125 | correct += preds.eq(labels).sum()
126 | finish = time.time()
127 |
128 | test_average_loss = test_loss / len(test_loader.dataset) # 测试平均 loss
129 | acc = correct.float() / len(test_loader.dataset) # 测试准确率
130 |
131 | # save model after every "save_epoch" epoches and model with the best acc
132 | if epoch > __C.milestones[1] and best_acc < acc:
133 | torch.save(net.state_dict(), checkpoint_path.format(net=__C.name, epoch=epoch, type='best'))
134 | best_acc = acc
135 | continue
136 | if not epoch % __C.save_epoch:
137 | torch.save(net.state_dict(), checkpoint_path.format(net=__C.name, epoch=epoch, type='regular'))
138 |
139 | # print the testing information
140 | print('Evaluating Network.....')
141 | print('Test set: Average loss: {:.4f}, Accuracy: {:.4f}, Time consumed:{:.2f}s'.format(
142 | test_average_loss,
143 | acc,
144 | finish - start
145 | ))
146 | print()
147 |
148 | # update the result logfile
149 | logfile = open(log_path, 'a+')
150 | logfile.write(
151 | 'Test Average loss: {:.4f}'.format(test_average_loss) +
152 | ', Accuracy: {:.4f}'.format(acc) +
153 | '\n'
154 | )
155 | logfile.close()
156 |
157 | # update the tensorboard log file
158 | writer.add_scalar('[Epoch] Test/Average loss', test_average_loss, epoch)
159 | writer.add_scalar('[Epoch] Test/Accuracy', acc, epoch)
160 |
161 |
--------------------------------------------------------------------------------
/utils/util.py:
--------------------------------------------------------------------------------
1 | """ helper function
2 |
3 | author baiyu
4 | """
5 | import os
6 |
7 | import re
8 | import datetime
9 | import numpy
10 | import torch.nn as nn
11 | from torch.optim.lr_scheduler import _LRScheduler
12 |
13 |
14 | def split_weights(net):
15 | """split network weights into to categlories,
16 | one are weights in conv layer and linear layer,
17 | others are other learnable paramters(conv bias,
18 | bn weights, bn bias, linear bias)
19 |
20 | Args:
21 | net: network architecture
22 |
23 | Returns:
24 | a dictionary of params splite into to categlories
25 | """
26 |
27 | decay = []
28 | no_decay = []
29 |
30 | for m in net.modules():
31 | if isinstance(m, nn.Conv2d) or isinstance(m, nn.Linear):
32 | decay.append(m.weight)
33 |
34 | if m.bias is not None:
35 | no_decay.append(m.bias)
36 |
37 | else:
38 | if hasattr(m, 'weight'):
39 | no_decay.append(m.weight)
40 | if hasattr(m, 'bias'):
41 | no_decay.append(m.bias)
42 |
43 | assert len(list(net.parameters())) == len(decay) + len(no_decay)
44 |
45 | return [dict(params=decay), dict(params=no_decay, weight_decay=0)]
46 |
47 | def compute_mean_std(cifar100_dataset):
48 | """compute the mean and std of cifar100 dataset
49 | Args:
50 | cifar100_training_dataset or cifar100_test_dataset
51 | witch derived from class torch.utils.data
52 |
53 | Returns:
54 | a tuple contains mean, std value of entire dataset
55 | """
56 |
57 | data_r = numpy.dstack([cifar100_dataset[i][1][:, :, 0] for i in range(len(cifar100_dataset))])
58 | data_g = numpy.dstack([cifar100_dataset[i][1][:, :, 1] for i in range(len(cifar100_dataset))])
59 | data_b = numpy.dstack([cifar100_dataset[i][1][:, :, 2] for i in range(len(cifar100_dataset))])
60 | mean = numpy.mean(data_r), numpy.mean(data_g), numpy.mean(data_b)
61 | std = numpy.std(data_r), numpy.std(data_g), numpy.std(data_b)
62 |
63 | return mean, std
64 |
65 | class WarmUpLR(_LRScheduler):
66 | """warmup_training learning rate scheduler
67 | Args:
68 | optimizer: optimzier(e.g. SGD)
69 | total_iters: totoal_iters of warmup phase
70 | """
71 | def __init__(self, optimizer, total_iters, last_epoch=-1):
72 |
73 | self.total_iters = total_iters
74 | super().__init__(optimizer, last_epoch)
75 |
76 | def get_lr(self):
77 | """we will use the first m batches, and set the learning
78 | rate to base_lr * m / total_iters
79 | """
80 | return [base_lr * self.last_epoch / (self.total_iters + 1e-8) for base_lr in self.base_lrs]
81 |
82 |
83 | def most_recent_folder(net_weights, fmt):
84 | """
85 | return most recent created folder under net_weights
86 | if no none-empty folder were found, return empty folder
87 | """
88 | # get subfolders in net_weights
89 | folders = os.listdir(net_weights)
90 |
91 | # filter out empty folders
92 | folders = [f for f in folders if len(os.listdir(os.path.join(net_weights, f)))]
93 | if len(folders) == 0:
94 | return ''
95 |
96 | # sort folders by folder created time
97 | folders = sorted(folders, key=lambda f: datetime.datetime.strptime(f, fmt))
98 | return folders[-1]
99 |
100 | def most_recent_weights(weights_folder):
101 | """
102 | return most recent created weights file
103 | if folder is empty return empty string
104 | """
105 | weight_files = os.listdir(weights_folder)
106 | if len(weights_folder) == 0:
107 | return ''
108 |
109 | regex_str = r'([A-Za-z0-9]+)-([0-9]+)-(regular|best)'
110 |
111 | # sort files by epoch
112 | weight_files = sorted(weight_files, key=lambda w: int(re.search(regex_str, w).groups()[1]))
113 |
114 | return weight_files[-1]
115 |
116 | def last_epoch(weights_folder):
117 | weight_file = most_recent_weights(weights_folder)
118 | if not weight_file:
119 | raise Exception('no recent weights were found')
120 | resume_epoch = int(weight_file.split('-')[1])
121 |
122 | return resume_epoch
123 |
124 | def best_acc_weights(weights_folder):
125 | """
126 | return the best acc .pth file in given folder, if no
127 | best acc weights file were found, return empty string
128 | """
129 | files = os.listdir(weights_folder)
130 | if len(files) == 0:
131 | return ''
132 |
133 | regex_str = r'([A-Za-z0-9]+)-([0-9]+)-(regular|best)'
134 | best_files = [w for w in files if re.search(regex_str, w).groups()[2] == 'best']
135 | if len(best_files) == 0:
136 | return ''
137 |
138 | best_files = sorted(best_files, key=lambda w: int(re.search(regex_str, w).groups()[1]))
139 | return best_files[-1]
--------------------------------------------------------------------------------