52Hz commited on
Commit
9c33d88
1 Parent(s): 68ae2ef

Create HWMNet.py

Browse files
Files changed (1) hide show
  1. model/HWMNet.py +284 -0
model/HWMNet.py ADDED
@@ -0,0 +1,284 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ from WT import DWT, IWT
4
+
5
+ ##---------- Basic Layers ----------
6
+ def conv3x3(in_chn, out_chn, bias=True):
7
+ layer = nn.Conv2d(in_chn, out_chn, kernel_size=3, stride=1, padding=1, bias=bias)
8
+ return layer
9
+
10
+ def conv(in_channels, out_channels, kernel_size, bias=False, stride=1):
11
+ return nn.Conv2d(
12
+ in_channels, out_channels, kernel_size,
13
+ padding=(kernel_size // 2), bias=bias, stride=stride)
14
+
15
+ def bili_resize(factor):
16
+ return nn.Upsample(scale_factor=factor, mode='bilinear', align_corners=False)
17
+
18
+ ##---------- Basic Blocks ----------
19
+ class UNetConvBlock(nn.Module):
20
+ def __init__(self, in_size, out_size, downsample):
21
+ super(UNetConvBlock, self).__init__()
22
+ self.downsample = downsample
23
+ self.body = [HWB(n_feat=in_size, o_feat=in_size, kernel_size=3, reduction=16, bias=False, act=nn.PReLU())]# for _ in range(wab)]
24
+ self.body = nn.Sequential(*self.body)
25
+
26
+ if downsample:
27
+ self.downsample = PS_down(out_size, out_size, downscale=2)
28
+
29
+ self.tail = nn.Conv2d(in_size, out_size, kernel_size=1)
30
+
31
+ def forward(self, x):
32
+ out = self.body(x)
33
+ out = self.tail(out)
34
+ if self.downsample:
35
+ out_down = self.downsample(out)
36
+ return out_down, out
37
+ else:
38
+ return out
39
+
40
+ class UNetUpBlock(nn.Module):
41
+ def __init__(self, in_size, out_size):
42
+ super(UNetUpBlock, self).__init__()
43
+ self.up = PS_up(in_size, out_size, upscale=2)
44
+ self.conv_block = UNetConvBlock(in_size, out_size, downsample=False)
45
+
46
+ def forward(self, x, bridge):
47
+ up = self.up(x)
48
+ out = torch.cat([up, bridge], dim=1)
49
+ out = self.conv_block(out)
50
+ return out
51
+
52
+ ##---------- Resizing Modules (Pixel(Un)Shuffle) ----------
53
+ class PS_down(nn.Module):
54
+ def __init__(self, in_size, out_size, downscale):
55
+ super(PS_down, self).__init__()
56
+ self.UnPS = nn.PixelUnshuffle(downscale)
57
+ self.conv1 = nn.Conv2d((downscale**2) * in_size, out_size, 1, 1, 0)
58
+
59
+ def forward(self, x):
60
+ x = self.UnPS(x) # h/2, w/2, 4*c
61
+ x = self.conv1(x)
62
+ return x
63
+
64
+ class PS_up(nn.Module):
65
+ def __init__(self, in_size, out_size, upscale):
66
+ super(PS_up, self).__init__()
67
+
68
+ self.PS = nn.PixelShuffle(upscale)
69
+ self.conv1 = nn.Conv2d(in_size//(upscale**2), out_size, 1, 1, 0)
70
+
71
+ def forward(self, x):
72
+ x = self.PS(x) # h/2, w/2, 4*c
73
+ x = self.conv1(x)
74
+ return x
75
+
76
+ ##---------- Selective Kernel Feature Fusion (SKFF) ----------
77
+ class SKFF(nn.Module):
78
+ def __init__(self, in_channels, height=3, reduction=8, bias=False):
79
+ super(SKFF, self).__init__()
80
+
81
+ self.height = height
82
+ d = max(int(in_channels / reduction), 4)
83
+
84
+ self.avg_pool = nn.AdaptiveAvgPool2d(1)
85
+ self.conv_du = nn.Sequential(nn.Conv2d(in_channels, d, 1, padding=0, bias=bias), nn.PReLU())
86
+
87
+ self.fcs = nn.ModuleList([])
88
+ for i in range(self.height):
89
+ self.fcs.append(nn.Conv2d(d, in_channels, kernel_size=1, stride=1, bias=bias))
90
+
91
+ self.softmax = nn.Softmax(dim=1)
92
+
93
+ def forward(self, inp_feats):
94
+ batch_size, n_feats, H, W = inp_feats[1].shape
95
+
96
+ inp_feats = torch.cat(inp_feats, dim=1)
97
+ inp_feats = inp_feats.view(batch_size, self.height, n_feats, inp_feats.shape[2], inp_feats.shape[3])
98
+
99
+ feats_U = torch.sum(inp_feats, dim=1)
100
+ feats_S = self.avg_pool(feats_U)
101
+ feats_Z = self.conv_du(feats_S)
102
+
103
+ attention_vectors = [fc(feats_Z) for fc in self.fcs]
104
+ attention_vectors = torch.cat(attention_vectors, dim=1)
105
+ attention_vectors = attention_vectors.view(batch_size, self.height, n_feats, 1, 1)
106
+
107
+ attention_vectors = self.softmax(attention_vectors)
108
+ feats_V = torch.sum(inp_feats * attention_vectors, dim=1)
109
+
110
+ return feats_V
111
+
112
+
113
+ ##########################################################################
114
+ # Spatial Attention Layer
115
+ class SALayer(nn.Module):
116
+ def __init__(self, kernel_size=5, bias=False):
117
+ super(SALayer, self).__init__()
118
+ self.conv_du = nn.Sequential(
119
+ nn.Conv2d(2, 1, kernel_size=kernel_size, stride=1, padding=(kernel_size - 1) // 2, bias=bias),
120
+ nn.Sigmoid()
121
+ )
122
+
123
+ def forward(self, x):
124
+ # torch.max will output 2 things, and we want the 1st one
125
+ max_pool, _ = torch.max(x, dim=1, keepdim=True)
126
+ avg_pool = torch.mean(x, 1, keepdim=True)
127
+ channel_pool = torch.cat([max_pool, avg_pool], dim=1) # [N,2,H,W] could add 1x1 conv -> [N,3,H,W]
128
+ y = self.conv_du(channel_pool)
129
+
130
+ return x * y
131
+
132
+ ##########################################################################
133
+ # Channel Attention Layer
134
+ class CALayer(nn.Module):
135
+ def __init__(self, channel, reduction=16, bias=False):
136
+ super(CALayer, self).__init__()
137
+ # global average pooling: feature --> point
138
+ self.avg_pool = nn.AdaptiveAvgPool2d(1)
139
+ # feature channel downscale and upscale --> channel weight
140
+ self.conv_du = nn.Sequential(
141
+ nn.Conv2d(channel, channel // reduction, 1, padding=0, bias=bias),
142
+ nn.ReLU(inplace=True),
143
+ nn.Conv2d(channel // reduction, channel, 1, padding=0, bias=bias),
144
+ nn.Sigmoid()
145
+ )
146
+
147
+ def forward(self, x):
148
+ y = self.avg_pool(x)
149
+ y = self.conv_du(y)
150
+ return x * y
151
+
152
+ ##########################################################################
153
+ # Half Wavelet Dual Attention Block (HWB)
154
+ class HWB(nn.Module):
155
+ def __init__(self, n_feat, o_feat, kernel_size, reduction, bias, act):
156
+ super(HWB, self).__init__()
157
+ self.dwt = DWT()
158
+ self.iwt = IWT()
159
+
160
+ modules_body = \
161
+ [
162
+ conv(n_feat*2, n_feat, kernel_size, bias=bias),
163
+ act,
164
+ conv(n_feat, n_feat*2, kernel_size, bias=bias)
165
+ ]
166
+ self.body = nn.Sequential(*modules_body)
167
+
168
+ self.WSA = SALayer()
169
+ self.WCA = CALayer(n_feat*2, reduction, bias=bias)
170
+
171
+ self.conv1x1 = nn.Conv2d(n_feat*4, n_feat*2, kernel_size=1, bias=bias)
172
+ self.conv3x3 = nn.Conv2d(n_feat, o_feat, kernel_size=3, padding=1, bias=bias)
173
+ self.activate = act
174
+ self.conv1x1_final = nn.Conv2d(n_feat, o_feat, kernel_size=1, bias=bias)
175
+
176
+ def forward(self, x):
177
+ residual = x
178
+
179
+ # Split 2 part
180
+ wavelet_path_in, identity_path = torch.chunk(x, 2, dim=1)
181
+
182
+ # Wavelet domain (Dual attention)
183
+ x_dwt = self.dwt(wavelet_path_in)
184
+ res = self.body(x_dwt)
185
+ branch_sa = self.WSA(res)
186
+ branch_ca = self.WCA(res)
187
+ res = torch.cat([branch_sa, branch_ca], dim=1)
188
+ res = self.conv1x1(res) + x_dwt
189
+ wavelet_path = self.iwt(res)
190
+
191
+ out = torch.cat([wavelet_path, identity_path], dim=1)
192
+ out = self.activate(self.conv3x3(out))
193
+ out += self.conv1x1_final(residual)
194
+
195
+ return out
196
+
197
+
198
+ ##########################################################################
199
+ ##---------- HWMNet-LOL ----------
200
+ class HWMNet(nn.Module):
201
+ def __init__(self, in_chn=3, wf=64, depth=4):
202
+ super(HWMNet, self).__init__()
203
+ self.depth = depth
204
+ self.down_path = nn.ModuleList()
205
+ self.bili_down = bili_resize(0.5)
206
+ self.conv_01 = nn.Conv2d(in_chn, wf, 3, 1, 1)
207
+
208
+ # encoder of UNet-64
209
+ prev_channels = 0
210
+ for i in range(depth): # 0,1,2,3
211
+ downsample = True if (i + 1) < depth else False
212
+ self.down_path.append(UNetConvBlock(prev_channels + wf, (2 ** i) * wf, downsample))
213
+ prev_channels = (2 ** i) * wf
214
+
215
+ # decoder of UNet-64
216
+ self.up_path = nn.ModuleList()
217
+ self.skip_conv = nn.ModuleList()
218
+ self.conv_up = nn.ModuleList()
219
+ self.bottom_conv = nn.Conv2d(prev_channels, wf, 3, 1, 1)
220
+ self.bottom_up = bili_resize(2 ** (depth-1))
221
+
222
+ for i in reversed(range(depth - 1)):
223
+ self.up_path.append(UNetUpBlock(prev_channels, (2 ** i) * wf))
224
+ self.skip_conv.append(nn.Conv2d((2 ** i) * wf, (2 ** i) * wf, 3, 1, 1))
225
+ self.conv_up.append(nn.Sequential(*[bili_resize(2 ** i), nn.Conv2d((2 ** i) * wf, wf, 3, 1, 1)]))
226
+ prev_channels = (2 ** i) * wf
227
+
228
+ self.final_ff = SKFF(in_channels=wf, height=depth)
229
+ self.last = conv3x3(prev_channels, in_chn, bias=True)
230
+
231
+ def forward(self, x):
232
+ img = x
233
+ scale_img = img
234
+
235
+ ##### shallow conv #####
236
+ x1 = self.conv_01(img)
237
+ encs = []
238
+ ######## UNet-64 ########
239
+ # Down-path (Encoder)
240
+ for i, down in enumerate(self.down_path):
241
+ if i == 0:
242
+ x1, x1_up = down(x1)
243
+ encs.append(x1_up)
244
+ elif (i + 1) < self.depth:
245
+ scale_img = self.bili_down(scale_img)
246
+ left_bar = self.conv_01(scale_img)
247
+ x1 = torch.cat([x1, left_bar], dim=1)
248
+ x1, x1_up = down(x1)
249
+ encs.append(x1_up)
250
+ else:
251
+ scale_img = self.bili_down(scale_img)
252
+ left_bar = self.conv_01(scale_img)
253
+ x1 = torch.cat([x1, left_bar], dim=1)
254
+ x1 = down(x1)
255
+
256
+ # Up-path (Decoder)
257
+ ms_result = [self.bottom_up(self.bottom_conv(x1))]
258
+ for i, up in enumerate(self.up_path):
259
+ x1 = up(x1, self.skip_conv[i](encs[-i - 1]))
260
+ ms_result.append(self.conv_up[i](x1))
261
+ # Multi-scale selective feature fusion
262
+ msff_result = self.final_ff(ms_result)
263
+
264
+ ##### Reconstruct #####
265
+ out_1 = self.last(msff_result) + img
266
+
267
+ return out_1
268
+
269
+ if __name__ == "__main__":
270
+ from thop import profile
271
+ input = torch.ones(1, 3, 400, 592, dtype=torch.float, requires_grad=False).cuda()
272
+
273
+ model = HWMNet(in_chn=3, wf=96, depth=4).cuda()
274
+ out = model(input)
275
+ flops, params = profile(model, inputs=(input,))
276
+
277
+ # RDBlayer = SK_RDB(in_channels=64, growth_rate=64, num_layers=3)
278
+ # print(RDBlayer)
279
+ # out = RDBlayer(input)
280
+ # flops, params = profile(RDBlayer, inputs=(input,))
281
+ print('input shape:', input.shape)
282
+ print('parameters:', params/1e6)
283
+ print('flops', flops/1e9)
284
+ print('output shape', out.shape)