forked from juefeix/pnn.pytorch.update
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlayers.py
295 lines (264 loc) · 11 KB
/
layers.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
import torch
import torch.nn as nn
import torch.nn.functional as F
from utils import act_fn
# define device to cuda if exist
device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
class PerturbLayerFirst(nn.Module):
"""
Treat the first-layer noise module differently from the noise modules in the rest of the layers,
because in the CVPR version of PNN, the first layer uses 3x3 or 7x7 spatial convolution
as feature extraction. All subsequent layers use the perturbation noise modules as described in the paper.
(duplicate class of the perturbation layer)
"""
def __init__(self,
in_channels=None,
out_channels=None,
nmasks=None,
level=None,
filter_size=None,
debug=False,
use_act=False,
act=None,
stride=1,
unique_masks=False,
mix_maps=None,
train_masks=False,
noise_type='uniform',
input_size=None):
"""
:param nmasks: number of perturbation masks per input channel
:param level: noise magnitude
:param filter_size: if filter_size=0, layers=(perturb, conv_1x1) else layers=(conv_NxN), N=filter_size
:param debug: debug mode or not
:param use_act: whether to use activation immediately after perturbing input (set it to False for the first layer)
:param stride: stride
:param unique_masks: same set or different sets of nmasks per input channel
:param mix_maps: whether to apply second 1x1 convolution after perturbation, to mix output feature maps
:param train_masks: whether to treat noise masks as regular trainable parameters of the model
:param noise_type: normal or uniform
:param input_size: input image resolution (28 for MNIST, 32 for CIFAR), needed to construct masks
"""
super(PerturbLayerFirst, self).__init__()
self.nmasks = nmasks
self.unique_masks = unique_masks
self.train_masks = train_masks
self.level = level
self.filter_size = filter_size
self.use_act = use_act
self.act = act_fn('sigmoid')
self.debug = debug
self.noise_type = noise_type
self.in_channels = in_channels
self.input_size = input_size
self.mix_maps = mix_maps
if filter_size == 1:
padding = 0
bias = True
elif filter_size == 3 or filter_size == 5:
padding = 1
bias = False
elif filter_size == 7:
stride = 2
padding = 3
bias = False
if self.filter_size > 0:
self.noise = None
self.layers = nn.Sequential(
nn.Conv2d(in_channels, out_channels, kernel_size=filter_size, padding=padding, stride=stride, bias=bias),
nn.BatchNorm2d(out_channels),
self.act
)
else: # layers=(perturb, conv_1x1)
noise_channels = in_channels if self.unique_masks else 1
shape = (1, noise_channels, self.nmasks, input_size, input_size)
self.noise = nn.Parameter(torch.Tensor(*shape), requires_grad=self.train_masks)
if noise_type == "uniform":
self.noise.data.uniform_(-1, 1)
elif self.noise_type == 'normal':
self.noise.data.normal_()
else:
print('\n\nNoise type {} is not supported / understood\n\n'.format(self.noise_type))
if nmasks != 1:
if out_channels % in_channels != 0:
print('\n\n\nnfilters must be divisible by 3 if using multiple noise masks per input channel\n\n\n')
groups = in_channels
else:
groups = 1
self.layers = nn.Sequential(
nn.BatchNorm2d(in_channels*self.nmasks),
self.act,
nn.Conv2d(in_channels*self.nmasks, out_channels, kernel_size=1, stride=1, groups=groups),
nn.BatchNorm2d(out_channels),
self.act,
)
if self.mix_maps:
self.mix_layers = nn.Sequential(
nn.Conv2d(out_channels, out_channels, kernel_size=1, stride=1, groups=1),
nn.BatchNorm2d(out_channels),
self.act,
)
def forward(self, x):
if self.filter_size > 0:
return self.layers(x) #image, conv, batchnorm, relu
else:
y = torch.add(x.unsqueeze(2), self.noise * self.level)
# (10, 3, 1, 32, 32) + (1, 3, 128, 32, 32) --> (10, 3, 128, 32, 32)
y = y.view(-1, self.in_channels * self.nmasks, self.input_size, self.input_size)
y = self.layers(y)
if self.mix_maps:
y = self.mix_layers(y)
return y #image, perturb, (relu?), conv1x1, batchnorm, relu + mix_maps (conv1x1, batchnorm relu)
class PerturbLayer(nn.Module):
"""
Perturbation layer, as described in the paper.
"""
def __init__(self,
in_channels=None,
out_channels=None,
nmasks=None,
level=None,
filter_size=None,
debug=False,
use_act=False,
stride=1,
act=None,
unique_masks=False,
mix_maps=None,
train_masks=False,
noise_type='uniform',
input_size=None):
super(PerturbLayer, self).__init__()
self.nmasks = nmasks
self.unique_masks = unique_masks
self.train_masks = train_masks
self.level = level
self.filter_size = filter_size
self.use_act = use_act
self.act = act_fn(act)
self.debug = debug
self.noise_type = noise_type
self.in_channels = in_channels
self.input_size = input_size
self.mix_maps = mix_maps
if filter_size == 1:
padding = 0
bias = True
elif filter_size == 3 or filter_size == 5:
padding = 1
bias = False
elif filter_size == 7:
stride = 2
padding = 3
bias = False
if self.filter_size > 0:
self.noise = None
self.layers = nn.Sequential(
nn.Conv2d(in_channels, out_channels, kernel_size=filter_size, padding=padding, stride=stride, bias=bias),
nn.BatchNorm2d(out_channels),
self.act
)
else:
noise_channels = in_channels if self.unique_masks else 1
shape = (1, noise_channels, self.nmasks, input_size, input_size) # can't dynamically reshape masks in forward if we want to train them
self.noise = nn.Parameter(torch.Tensor(*shape), requires_grad=self.train_masks)
if noise_type == "uniform":
self.noise.data.uniform_(-1, 1)
elif self.noise_type == 'normal':
self.noise.data.normal_()
else:
print('\n\nNoise type {} is not supported / understood\n\n'.format(self.noise_type))
if nmasks != 1:
if out_channels % in_channels != 0:
print('\n\n\nnfilters must be divisible by 3 if using multiple noise masks per input channel\n\n\n')
groups = in_channels
else:
groups = 1
self.layers = nn.Sequential(
nn.Conv2d(in_channels*self.nmasks, out_channels, kernel_size=1, stride=1, groups=groups),
nn.BatchNorm2d(out_channels),
self.act,
)
if self.mix_maps:
self.mix_layers = nn.Sequential(
nn.Conv2d(out_channels, out_channels, kernel_size=1, stride=1, groups=1),
nn.BatchNorm2d(out_channels),
self.act,
)
def forward(self, x):
if self.filter_size > 0:
return self.layers(x) #image, conv, batchnorm, relu
else:
y = torch.add(x.unsqueeze(2), self.noise * self.level) # (10, 3, 1, 32, 32) + (1, 3, 128, 32, 32) --> (10, 3, 128, 32, 32)
if self.use_act:
y = self.act(y)
y = y.view(-1, self.in_channels * self.nmasks, self.input_size, self.input_size)
y = self.layers(y)
if self.mix_maps:
y = self.mix_layers(y)
return y #image, perturb, (relu?), conv1x1, batchnorm, relu + mix_maps (conv1x1, batchnorm relu)
class PerturbBasicBlock(nn.Module):
"""
Two PerturbLayer-s with pooling between
Pool type can be assigned by 'pool_type'
"""
expansion = 1
def __init__(self,
in_channels=None,
out_channels=None,
stride=1,
shortcut=None,
nmasks=None,
train_masks=False,
level=None,
use_act=False,
filter_size=None,
act=None,
unique_masks=False,
noise_type=None,
input_size=None,
pool_type=None,
mix_maps=None):
super(PerturbBasicBlock, self).__init__()
self.shortcut = shortcut
if pool_type == 'max':
pool = nn.MaxPool2d
elif pool_type == 'avg':
pool = nn.AvgPool2d
else:
raise ValueError('Pool Type {} is not supported/understood'.format(pool_type))
self.layers = nn.Sequential(
PerturbLayer(in_channels=in_channels,
out_channels=out_channels,
nmasks=nmasks,
input_size=input_size,
level=level,
filter_size=filter_size,
use_act=use_act,
train_masks=train_masks,
act=act,
unique_masks=unique_masks,
noise_type=noise_type,
mix_maps=mix_maps),
pool(stride, stride),
PerturbLayer(in_channels=out_channels,
out_channels=out_channels,
nmasks=nmasks,
input_size=input_size//stride,
level=level,
filter_size=filter_size,
use_act=use_act,
train_masks=train_masks,
act=act,
unique_masks=unique_masks,
noise_type=noise_type,
mix_maps=mix_maps),
)
def forward(self, x):
residual = x
y = self.layers(x)
if self.shortcut:
residual = self.shortcut(x)
y += residual
y = F.relu(y)
return y