Perform convolution over the input using the squared L2 distance for all prototypes in the layer :param xs: A batch of input images obtained as output from some convolutional neural network F. Following the notation from the paper, let the shape of xs be (batch
(self, xs)
| 24 | self.prototype_vectors = nn.Parameter(torch.randn(prototype_shape), requires_grad=True) |
| 25 | |
| 26 | def forward(self, xs): |
| 27 | """ |
| 28 | Perform convolution over the input using the squared L2 distance for all prototypes in the layer |
| 29 | :param xs: A batch of input images obtained as output from some convolutional neural network F. Following the |
| 30 | notation from the paper, let the shape of xs be (batch_size, D, W, H), where |
| 31 | - D is the number of output channels of the conv net F |
| 32 | - W is the width of the convolutional output of F |
| 33 | - H is the height of the convolutional output of F |
| 34 | :return: a tensor of shape (batch_size, num_prototypes, W, H) obtained from computing the squared L2 distances |
| 35 | for patches of the input using all prototypes |
| 36 | """ |
| 37 | # Adapted from ProtoPNet |
| 38 | # Computing ||xs - ps ||^2 is equivalent to ||xs||^2 + ||ps||^2 - 2 * xs * ps |
| 39 | # where ps is some prototype image |
| 40 | |
| 41 | # So first we compute ||xs||^2 (for all patches in the input image that is. We can do this by using convolution |
| 42 | # with weights set to 1 so each patch just has its values summed) |
| 43 | ones = torch.ones_like(self.prototype_vectors, |
| 44 | device=xs.device) # Shape: (num_prototypes, num_features, w_1, h_1) |
| 45 | xs_squared_l2 = F.conv2d(xs ** 2, weight=ones) # Shape: (bs, num_prototypes, w_in, h_in) |
| 46 | |
| 47 | # Now compute ||ps||^2 |
| 48 | # We can just use a sum here since ||ps||^2 is the same for each patch in the input image when computing the |
| 49 | # squared L2 distance |
| 50 | ps_squared_l2 = torch.sum(self.prototype_vectors ** 2, |
| 51 | dim=(1, 2, 3)) # Shape: (num_prototypes,) |
| 52 | # Reshape the tensor so the dimensions match when computing ||xs||^2 + ||ps||^2 |
| 53 | ps_squared_l2 = ps_squared_l2.view(-1, 1, 1) |
| 54 | |
| 55 | # Compute xs * ps (for all patches in the input image) |
| 56 | xs_conv = F.conv2d(xs, weight=self.prototype_vectors) # Shape: (bs, num_prototypes, w_in, h_in) |
| 57 | |
| 58 | # Use the values to compute the squared L2 distance |
| 59 | distance = xs_squared_l2 + ps_squared_l2 - 2 * xs_conv |
| 60 | distance = torch.sqrt(torch.abs(distance)+1e-14) #L2 distance (not squared). Small epsilon added for numerical stability |
| 61 | |
| 62 | if torch.isnan(distance).any(): |
| 63 | raise Exception('Error: NaN values! Using the --log_probabilities flag might fix this issue') |
| 64 | return distance # Shape: (bs, num_prototypes, w_in, h_in) |