Skip to content

Commit 4cbe714

Browse files
authored
Refactoring and moving MobileNetV2 to make it reusable (#3177)
* Moving mobilenet.py to mobilenetv2.py * Adding mobilenet.py for BC. * Extending ConvBNReLU for reuse. * Reduce import scope on mobilenet to only the public and versioned classes and methods.
1 parent 91e03b9 commit 4cbe714

File tree

4 files changed

+310
-301
lines changed

4 files changed

+310
-301
lines changed

torchvision/models/mobilenet.py

Lines changed: 1 addition & 207 deletions
Original file line numberDiff line numberDiff line change
@@ -1,207 +1 @@
1-
from torch import nn
2-
from torch import Tensor
3-
from .utils import load_state_dict_from_url
4-
from typing import Callable, Any, Optional, List
5-
6-
7-
__all__ = ['MobileNetV2', 'mobilenet_v2']
8-
9-
10-
model_urls = {
11-
'mobilenet_v2': 'https://download.pytorch.org/models/mobilenet_v2-b0353104.pth',
12-
}
13-
14-
15-
def _make_divisible(v: float, divisor: int, min_value: Optional[int] = None) -> int:
16-
"""
17-
This function is taken from the original tf repo.
18-
It ensures that all layers have a channel number that is divisible by 8
19-
It can be seen here:
20-
https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet.py
21-
:param v:
22-
:param divisor:
23-
:param min_value:
24-
:return:
25-
"""
26-
if min_value is None:
27-
min_value = divisor
28-
new_v = max(min_value, int(v + divisor / 2) // divisor * divisor)
29-
# Make sure that round down does not go down by more than 10%.
30-
if new_v < 0.9 * v:
31-
new_v += divisor
32-
return new_v
33-
34-
35-
class ConvBNReLU(nn.Sequential):
36-
def __init__(
37-
self,
38-
in_planes: int,
39-
out_planes: int,
40-
kernel_size: int = 3,
41-
stride: int = 1,
42-
groups: int = 1,
43-
norm_layer: Optional[Callable[..., nn.Module]] = None
44-
) -> None:
45-
padding = (kernel_size - 1) // 2
46-
if norm_layer is None:
47-
norm_layer = nn.BatchNorm2d
48-
super(ConvBNReLU, self).__init__(
49-
nn.Conv2d(in_planes, out_planes, kernel_size, stride, padding, groups=groups, bias=False),
50-
norm_layer(out_planes),
51-
nn.ReLU6(inplace=True)
52-
)
53-
54-
55-
class InvertedResidual(nn.Module):
56-
def __init__(
57-
self,
58-
inp: int,
59-
oup: int,
60-
stride: int,
61-
expand_ratio: int,
62-
norm_layer: Optional[Callable[..., nn.Module]] = None
63-
) -> None:
64-
super(InvertedResidual, self).__init__()
65-
self.stride = stride
66-
assert stride in [1, 2]
67-
68-
if norm_layer is None:
69-
norm_layer = nn.BatchNorm2d
70-
71-
hidden_dim = int(round(inp * expand_ratio))
72-
self.use_res_connect = self.stride == 1 and inp == oup
73-
74-
layers: List[nn.Module] = []
75-
if expand_ratio != 1:
76-
# pw
77-
layers.append(ConvBNReLU(inp, hidden_dim, kernel_size=1, norm_layer=norm_layer))
78-
layers.extend([
79-
# dw
80-
ConvBNReLU(hidden_dim, hidden_dim, stride=stride, groups=hidden_dim, norm_layer=norm_layer),
81-
# pw-linear
82-
nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
83-
norm_layer(oup),
84-
])
85-
self.conv = nn.Sequential(*layers)
86-
87-
def forward(self, x: Tensor) -> Tensor:
88-
if self.use_res_connect:
89-
return x + self.conv(x)
90-
else:
91-
return self.conv(x)
92-
93-
94-
class MobileNetV2(nn.Module):
95-
def __init__(
96-
self,
97-
num_classes: int = 1000,
98-
width_mult: float = 1.0,
99-
inverted_residual_setting: Optional[List[List[int]]] = None,
100-
round_nearest: int = 8,
101-
block: Optional[Callable[..., nn.Module]] = None,
102-
norm_layer: Optional[Callable[..., nn.Module]] = None
103-
) -> None:
104-
"""
105-
MobileNet V2 main class
106-
107-
Args:
108-
num_classes (int): Number of classes
109-
width_mult (float): Width multiplier - adjusts number of channels in each layer by this amount
110-
inverted_residual_setting: Network structure
111-
round_nearest (int): Round the number of channels in each layer to be a multiple of this number
112-
Set to 1 to turn off rounding
113-
block: Module specifying inverted residual building block for mobilenet
114-
norm_layer: Module specifying the normalization layer to use
115-
116-
"""
117-
super(MobileNetV2, self).__init__()
118-
119-
if block is None:
120-
block = InvertedResidual
121-
122-
if norm_layer is None:
123-
norm_layer = nn.BatchNorm2d
124-
125-
input_channel = 32
126-
last_channel = 1280
127-
128-
if inverted_residual_setting is None:
129-
inverted_residual_setting = [
130-
# t, c, n, s
131-
[1, 16, 1, 1],
132-
[6, 24, 2, 2],
133-
[6, 32, 3, 2],
134-
[6, 64, 4, 2],
135-
[6, 96, 3, 1],
136-
[6, 160, 3, 2],
137-
[6, 320, 1, 1],
138-
]
139-
140-
# only check the first element, assuming user knows t,c,n,s are required
141-
if len(inverted_residual_setting) == 0 or len(inverted_residual_setting[0]) != 4:
142-
raise ValueError("inverted_residual_setting should be non-empty "
143-
"or a 4-element list, got {}".format(inverted_residual_setting))
144-
145-
# building first layer
146-
input_channel = _make_divisible(input_channel * width_mult, round_nearest)
147-
self.last_channel = _make_divisible(last_channel * max(1.0, width_mult), round_nearest)
148-
features: List[nn.Module] = [ConvBNReLU(3, input_channel, stride=2, norm_layer=norm_layer)]
149-
# building inverted residual blocks
150-
for t, c, n, s in inverted_residual_setting:
151-
output_channel = _make_divisible(c * width_mult, round_nearest)
152-
for i in range(n):
153-
stride = s if i == 0 else 1
154-
features.append(block(input_channel, output_channel, stride, expand_ratio=t, norm_layer=norm_layer))
155-
input_channel = output_channel
156-
# building last several layers
157-
features.append(ConvBNReLU(input_channel, self.last_channel, kernel_size=1, norm_layer=norm_layer))
158-
# make it nn.Sequential
159-
self.features = nn.Sequential(*features)
160-
161-
# building classifier
162-
self.classifier = nn.Sequential(
163-
nn.Dropout(0.2),
164-
nn.Linear(self.last_channel, num_classes),
165-
)
166-
167-
# weight initialization
168-
for m in self.modules():
169-
if isinstance(m, nn.Conv2d):
170-
nn.init.kaiming_normal_(m.weight, mode='fan_out')
171-
if m.bias is not None:
172-
nn.init.zeros_(m.bias)
173-
elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)):
174-
nn.init.ones_(m.weight)
175-
nn.init.zeros_(m.bias)
176-
elif isinstance(m, nn.Linear):
177-
nn.init.normal_(m.weight, 0, 0.01)
178-
nn.init.zeros_(m.bias)
179-
180-
def _forward_impl(self, x: Tensor) -> Tensor:
181-
# This exists since TorchScript doesn't support inheritance, so the superclass method
182-
# (this one) needs to have a name other than `forward` that can be accessed in a subclass
183-
x = self.features(x)
184-
# Cannot use "squeeze" as batch-size can be 1 => must use reshape with x.shape[0]
185-
x = nn.functional.adaptive_avg_pool2d(x, (1, 1)).reshape(x.shape[0], -1)
186-
x = self.classifier(x)
187-
return x
188-
189-
def forward(self, x: Tensor) -> Tensor:
190-
return self._forward_impl(x)
191-
192-
193-
def mobilenet_v2(pretrained: bool = False, progress: bool = True, **kwargs: Any) -> MobileNetV2:
194-
"""
195-
Constructs a MobileNetV2 architecture from
196-
`"MobileNetV2: Inverted Residuals and Linear Bottlenecks" <https://arxiv.org/abs/1801.04381>`_.
197-
198-
Args:
199-
pretrained (bool): If True, returns a model pre-trained on ImageNet
200-
progress (bool): If True, displays a progress bar of the download to stderr
201-
"""
202-
model = MobileNetV2(**kwargs)
203-
if pretrained:
204-
state_dict = load_state_dict_from_url(model_urls['mobilenet_v2'],
205-
progress=progress)
206-
model.load_state_dict(state_dict)
207-
return model
1+
from .mobilenetv2 import MobileNetV2, mobilenet_v2

0 commit comments

Comments
 (0)