Skip to content

Commit 4c7793d

Browse files
committed
Added SENet support.
1 parent 78b11e8 commit 4c7793d

File tree

3 files changed

+290
-1
lines changed

3 files changed

+290
-1
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ Layers:
7979
* ConvTranspose2d
8080
* MaxPool2d
8181
* AvgPool2d
82+
* Global average pooling (as special case of AdaptiveAvgPool2d)
8283
* Embedding
8384
* UpsamplingNearest2d
8485

@@ -118,11 +119,13 @@ Misc:
118119
## Models converted with pytorch2keras
119120

120121
* ResNet18
122+
* ResNet34
121123
* ResNet50
122124
* SqueezeNet (with ceil_mode=False)
123125
* DenseNet
124126
* AlexNet
125127
* Inception (v4 only)
128+
* SeNet
126129

127130
## Usage
128131
Look at the `tests` directory.

setup.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,15 @@
1616
reqs = [str(ir.req) for ir in install_reqs]
1717

1818

19+
with open('README.md') as f:
20+
long_description = f.read()
21+
22+
1923
setup(name='pytorch2keras',
20-
version='0.1.1',
24+
version='0.1.3',
2125
description='The deep learning models convertor',
26+
long_description=long_description,
27+
long_description_content_type='text/markdown',
2228
url='https://github.com/nerox8664/pytorch2keras',
2329
author='Grigory Malivenko',
2430
author_email='[email protected]',

tests/senet.py

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
import numpy as np
2+
import torch
3+
from torch import nn
4+
from torch.autograd import Variable
5+
from torchvision.models import ResNet
6+
from pytorch2keras.converter import pytorch_to_keras
7+
8+
9+
class SELayer(nn.Module):
10+
def __init__(self, channel, reduction=16):
11+
super(SELayer, self).__init__()
12+
self.avg_pool = nn.AdaptiveAvgPool2d(1)
13+
self.fc = nn.Sequential(
14+
nn.Linear(channel, channel // reduction),
15+
nn.ReLU(inplace=True),
16+
nn.Linear(channel // reduction, channel),
17+
nn.Sigmoid()
18+
)
19+
20+
def forward(self, x):
21+
b, c, _, _ = x.size()
22+
y = self.avg_pool(x).view(b, c)
23+
y = self.fc(y).view(b, c, 1, 1)
24+
return x * y
25+
26+
27+
def conv3x3(in_planes, out_planes, stride=1):
28+
return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride, padding=1, bias=False)
29+
30+
31+
class SEBasicBlock(nn.Module):
32+
expansion = 1
33+
34+
def __init__(self, inplanes, planes, stride=1, downsample=None, reduction=16):
35+
super(SEBasicBlock, self).__init__()
36+
self.conv1 = conv3x3(inplanes, planes, stride)
37+
self.bn1 = nn.BatchNorm2d(planes)
38+
self.relu = nn.ReLU(inplace=True)
39+
self.conv2 = conv3x3(planes, planes, 1)
40+
self.bn2 = nn.BatchNorm2d(planes)
41+
self.se = SELayer(planes, reduction)
42+
self.downsample = downsample
43+
self.stride = stride
44+
45+
def forward(self, x):
46+
residual = x
47+
out = self.conv1(x)
48+
out = self.bn1(out)
49+
out = self.relu(out)
50+
51+
out = self.conv2(out)
52+
out = self.bn2(out)
53+
out = self.se(out)
54+
55+
if self.downsample is not None:
56+
residual = self.downsample(x)
57+
58+
out += residual
59+
out = self.relu(out)
60+
61+
return out
62+
63+
64+
class SEBottleneck(nn.Module):
65+
expansion = 4
66+
67+
def __init__(self, inplanes, planes, stride=1, downsample=None, reduction=16):
68+
super(SEBottleneck, self).__init__()
69+
self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False)
70+
self.bn1 = nn.BatchNorm2d(planes)
71+
self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride,
72+
padding=1, bias=False)
73+
self.bn2 = nn.BatchNorm2d(planes)
74+
self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False)
75+
self.bn3 = nn.BatchNorm2d(planes * 4)
76+
self.relu = nn.ReLU(inplace=True)
77+
self.se = SELayer(planes * 4, reduction)
78+
self.downsample = downsample
79+
self.stride = stride
80+
81+
def forward(self, x):
82+
residual = x
83+
84+
out = self.conv1(x)
85+
out = self.bn1(out)
86+
out = self.relu(out)
87+
88+
out = self.conv2(out)
89+
out = self.bn2(out)
90+
out = self.relu(out)
91+
92+
out = self.conv3(out)
93+
out = self.bn3(out)
94+
out = self.se(out)
95+
96+
if self.downsample is not None:
97+
residual = self.downsample(x)
98+
99+
out += residual
100+
out = self.relu(out)
101+
102+
return out
103+
104+
105+
def se_resnet18(num_classes):
106+
"""Constructs a ResNet-18 model.
107+
108+
Args:
109+
pretrained (bool): If True, returns a model pre-trained on ImageNet
110+
"""
111+
model = ResNet(SEBasicBlock, [2, 2, 2, 2], num_classes=num_classes)
112+
model.avgpool = nn.AdaptiveAvgPool2d(1)
113+
return model
114+
115+
116+
def se_resnet34(num_classes):
117+
"""Constructs a ResNet-34 model.
118+
119+
Args:
120+
pretrained (bool): If True, returns a model pre-trained on ImageNet
121+
"""
122+
model = ResNet(SEBasicBlock, [3, 4, 6, 3], num_classes=num_classes)
123+
model.avgpool = nn.AdaptiveAvgPool2d(1)
124+
return model
125+
126+
127+
def se_resnet50(num_classes):
128+
"""Constructs a ResNet-50 model.
129+
130+
Args:
131+
pretrained (bool): If True, returns a model pre-trained on ImageNet
132+
"""
133+
model = ResNet(SEBottleneck, [3, 4, 6, 3], num_classes=num_classes)
134+
model.avgpool = nn.AdaptiveAvgPool2d(1)
135+
return model
136+
137+
138+
def se_resnet101(num_classes):
139+
"""Constructs a ResNet-101 model.
140+
141+
Args:
142+
pretrained (bool): If True, returns a model pre-trained on ImageNet
143+
"""
144+
model = ResNet(SEBottleneck, [3, 4, 23, 3], num_classes=num_classes)
145+
model.avgpool = nn.AdaptiveAvgPool2d(1)
146+
return model
147+
148+
149+
def se_resnet152(num_classes):
150+
"""Constructs a ResNet-152 model.
151+
152+
Args:
153+
pretrained (bool): If True, returns a model pre-trained on ImageNet
154+
"""
155+
model = ResNet(SEBottleneck, [3, 8, 36, 3], num_classes=num_classes)
156+
model.avgpool = nn.AdaptiveAvgPool2d(1)
157+
return model
158+
159+
160+
class CifarSEBasicBlock(nn.Module):
161+
def __init__(self, inplanes, planes, stride=1, reduction=16):
162+
super(CifarSEBasicBlock, self).__init__()
163+
self.conv1 = conv3x3(inplanes, planes, stride)
164+
self.bn1 = nn.BatchNorm2d(planes)
165+
self.relu = nn.ReLU(inplace=True)
166+
self.conv2 = conv3x3(planes, planes)
167+
self.bn2 = nn.BatchNorm2d(planes)
168+
self.se = SELayer(planes, reduction)
169+
if inplanes != planes:
170+
self.downsample = nn.Sequential(nn.Conv2d(inplanes, planes, kernel_size=1, stride=stride, bias=False),
171+
nn.BatchNorm2d(planes))
172+
else:
173+
self.downsample = lambda x: x
174+
self.stride = stride
175+
176+
def forward(self, x):
177+
residual = self.downsample(x)
178+
out = self.conv1(x)
179+
out = self.bn1(out)
180+
out = self.relu(out)
181+
182+
out = self.conv2(out)
183+
out = self.bn2(out)
184+
out = self.se(out)
185+
186+
out += residual
187+
out = self.relu(out)
188+
189+
return out
190+
191+
192+
class CifarSEResNet(nn.Module):
193+
def __init__(self, block, n_size, num_classes=10, reduction=16):
194+
super(CifarSEResNet, self).__init__()
195+
self.inplane = 16
196+
self.conv1 = nn.Conv2d(3, self.inplane, kernel_size=3, stride=1, padding=1, bias=False)
197+
self.bn1 = nn.BatchNorm2d(self.inplane)
198+
self.relu = nn.ReLU(inplace=True)
199+
self.layer1 = self._make_layer(block, 16, blocks=n_size, stride=1, reduction=reduction)
200+
self.layer2 = self._make_layer(block, 32, blocks=n_size, stride=2, reduction=reduction)
201+
self.layer3 = self._make_layer(block, 64, blocks=n_size, stride=2, reduction=reduction)
202+
self.avgpool = nn.AdaptiveAvgPool2d(1)
203+
self.fc = nn.Linear(64, num_classes)
204+
self.initialize()
205+
206+
def initialize(self):
207+
for m in self.modules():
208+
if isinstance(m, nn.Conv2d):
209+
nn.init.kaiming_normal(m.weight)
210+
elif isinstance(m, nn.BatchNorm2d):
211+
nn.init.constant(m.weight, 1)
212+
nn.init.constant(m.bias, 0)
213+
214+
def _make_layer(self, block, planes, blocks, stride, reduction):
215+
strides = [stride] + [1] * (blocks - 1)
216+
layers = []
217+
for stride in strides:
218+
layers.append(block(self.inplane, planes, stride, reduction))
219+
self.inplane = planes
220+
221+
return nn.Sequential(*layers)
222+
223+
def forward(self, x):
224+
x = self.conv1(x)
225+
x = self.bn1(x)
226+
x = self.relu(x)
227+
228+
x = self.layer1(x)
229+
x = self.layer2(x)
230+
x = self.layer3(x)
231+
232+
x = self.avgpool(x)
233+
x = x.view(x.size(0), -1)
234+
x = self.fc(x)
235+
236+
return x
237+
238+
239+
class CifarSEPreActResNet(CifarSEResNet):
240+
def __init__(self, block, n_size, num_classes=10, reduction=16):
241+
super(CifarSEPreActResNet, self).__init__(block, n_size, num_classes, reduction)
242+
self.bn1 = nn.BatchNorm2d(self.inplane)
243+
self.initialize()
244+
245+
def forward(self, x):
246+
x = self.conv1(x)
247+
x = self.layer1(x)
248+
x = self.layer2(x)
249+
x = self.layer3(x)
250+
251+
x = self.bn1(x)
252+
x = self.relu(x)
253+
254+
x = self.avgpool(x)
255+
x = x.view(x.size(0), -1)
256+
x = self.fc(x)
257+
258+
259+
if __name__ == '__main__':
260+
max_error = 0
261+
for i in range(10):
262+
model = CifarSEResNet(CifarSEBasicBlock, 3)
263+
for m in model.modules():
264+
m.training = False
265+
266+
input_np = np.random.uniform(0, 1, (1, 3, 224, 224))
267+
input_var = Variable(torch.FloatTensor(input_np))
268+
output = model(input_var)
269+
270+
k_model = pytorch_to_keras(model, input_var, (3, 224, 224,), verbose=True)
271+
272+
pytorch_output = output.data.numpy()
273+
keras_output = k_model.predict(input_np)
274+
275+
error = np.max(pytorch_output - keras_output)
276+
print(error)
277+
if max_error < error:
278+
max_error = error
279+
280+
print('Max error: {0}'.format(max_error))

0 commit comments

Comments
 (0)