Encoding Layer: a learnable residual encoder. Input is of shape (batch_size, channels, height, width). Output is of shape (batch_size, num_codes, channels). Args: channels: dimension of the features or feature channels num_codes: number of code words
| 5 | |
| 6 | |
| 7 | class Encoding(nn.Module): |
| 8 | """Encoding Layer: a learnable residual encoder. |
| 9 | |
| 10 | Input is of shape (batch_size, channels, height, width). |
| 11 | Output is of shape (batch_size, num_codes, channels). |
| 12 | |
| 13 | Args: |
| 14 | channels: dimension of the features or feature channels |
| 15 | num_codes: number of code words |
| 16 | """ |
| 17 | |
| 18 | def __init__(self, channels, num_codes): |
| 19 | super(Encoding, self).__init__() |
| 20 | # init codewords and smoothing factor |
| 21 | self.channels, self.num_codes = channels, num_codes |
| 22 | std = 1.0 / ((num_codes * channels) ** 0.5) |
| 23 | # [num_codes, channels] |
| 24 | self.codewords = nn.Parameter( |
| 25 | torch.empty(num_codes, channels, dtype=torch.float).uniform_(-std, std), requires_grad=True |
| 26 | ) |
| 27 | # [num_codes] |
| 28 | self.scale = nn.Parameter(torch.empty(num_codes, dtype=torch.float).uniform_(-1, 0), requires_grad=True) |
| 29 | |
| 30 | @staticmethod |
| 31 | def scaled_l2(x, codewords, scale): |
| 32 | num_codes, channels = codewords.size() |
| 33 | batch_size = x.size(0) |
| 34 | reshaped_scale = scale.view((1, 1, num_codes)) |
| 35 | expanded_x = x.unsqueeze(2).expand((batch_size, x.size(1), num_codes, channels)) |
| 36 | reshaped_codewords = codewords.view((1, 1, num_codes, channels)) |
| 37 | |
| 38 | scaled_l2_norm = reshaped_scale * (expanded_x - reshaped_codewords).pow(2).sum(dim=3) |
| 39 | return scaled_l2_norm |
| 40 | |
| 41 | @staticmethod |
| 42 | def aggregate(assignment_weights, x, codewords): |
| 43 | num_codes, channels = codewords.size() |
| 44 | reshaped_codewords = codewords.view((1, 1, num_codes, channels)) |
| 45 | batch_size = x.size(0) |
| 46 | |
| 47 | expanded_x = x.unsqueeze(2).expand((batch_size, x.size(1), num_codes, channels)) |
| 48 | encoded_feat = (assignment_weights.unsqueeze(3) * (expanded_x - reshaped_codewords)).sum(dim=1) |
| 49 | return encoded_feat |
| 50 | |
| 51 | def forward(self, x): |
| 52 | assert x.dim() == 4 and x.size(1) == self.channels |
| 53 | # [batch_size, channels, height, width] |
| 54 | batch_size = x.size(0) |
| 55 | # [batch_size, height x width, channels] |
| 56 | x = x.view(batch_size, self.channels, -1).transpose(1, 2).contiguous() |
| 57 | # assignment_weights: [batch_size, channels, num_codes] |
| 58 | assignment_weights = F.softmax(self.scaled_l2(x, self.codewords, self.scale), dim=2) |
| 59 | # aggregate |
| 60 | encoded_feat = self.aggregate(assignment_weights, x, self.codewords) |
| 61 | return encoded_feat |
| 62 | |
| 63 | def __repr__(self): |
| 64 | repr_str = self.__class__.__name__ |