-
Notifications
You must be signed in to change notification settings - Fork 82
/
05-visualizing-heatmaps-of-class-activation.Rmd
executable file
·101 lines (91 loc) · 3.54 KB
/
05-visualizing-heatmaps-of-class-activation.Rmd
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
---
title: "Computer vision & CNNs: Visualizing heatmaps of class activation"
output: html_notebook
---
```{r setup, include=FALSE}
library(keras)
```
```{r}
model <- application_vgg16(weights = "imagenet")
```
```{r}
# The local path to our target image
img_path <- here::here("materials", "04-computer-vision-CNNs", "heatmap", "creative_commons_elephant.jpg")
# Start witih image of size 224 × 224
img <- image_load(img_path, target_size = c(224, 224)) %>%
# Array of shape (224, 224, 3)
image_to_array() %>%
# Adds a dimension to transform the array into a batch of size (1, 224, 224, 3)
array_reshape(dim = c(1, 224, 224, 3)) %>%
# Preprocesses the batch (this does channel-wise color normalization)
imagenet_preprocess_input()
```
```{r}
preds <- model %>% predict(img)
imagenet_decode_predictions(preds, top = 3)[[1]]
```
```{r}
which.max(preds[1,])
```
```{r}
# This is the "african elephant" entry in the prediction vector
african_elephant_output <- model$output[, 387]
# The is the output feature map of the `block5_conv3` layer,
# the last convolutional layer in VGG16
last_conv_layer <- model %>% get_layer("block5_conv3")
# This is the gradient of the "african elephant" class with regard to
# the output feature map of `block5_conv3`
grads <- k_gradients(african_elephant_output, last_conv_layer$output)[[1]]
# This is a vector of shape (512,), where each entry
# is the mean intensity of the gradient over a specific feature map channel
pooled_grads <- k_mean(grads, axis = c(1, 2, 3))
# This function allows us to access the values of the quantities we just defined:
# `pooled_grads` and the output feature map of `block5_conv3`,
# given a sample image
iterate <- k_function(list(model$input),
list(pooled_grads, last_conv_layer$output[1,,,]))
# These are the values of these two quantities, as arrays,
# given our sample image of two elephants
c(pooled_grads_value, conv_layer_output_value) %<-% iterate(list(img))
# We multiply each channel in the feature map array
# by "how important this channel is" with regard to the elephant class
for (i in 1:512) {
conv_layer_output_value[,,i] <-
conv_layer_output_value[,,i] * pooled_grads_value[[i]]
}
# The channel-wise mean of the resulting feature map
# is our heatmap of class activation
heatmap <- apply(conv_layer_output_value, c(1,2), mean)
```
```{r}
heatmap <- pmax(heatmap, 0)
heatmap <- heatmap / max(heatmap)
write_heatmap <- function(heatmap, filename, width = 224, height = 224,
bg = "white", col = terrain.colors(12)) {
png(filename, width = width, height = height, bg = bg)
op = par(mar = c(0,0,0,0))
on.exit({par(op); dev.off()}, add = TRUE)
rotate <- function(x) t(apply(x, 2, rev))
image(rotate(heatmap), axes = FALSE, asp = 1, col = col)
}
write_heatmap(heatmap, "heatmap/elephant_heatmap.png")
```
```{r}
library(magick)
library(viridis)
# Read the original elephant image and it's geometry
image <- image_read(img_path)
info <- image_info(image)
geometry <- sprintf("%dx%d!", info$width, info$height)
# Create a blended / transparent version of the heatmap image
pal <- col2rgb(viridis(20), alpha = TRUE)
alpha <- floor(seq(0, 255, length = ncol(pal)))
pal_col <- rgb(t(pal), alpha = alpha, maxColorValue = 255)
write_heatmap(heatmap, "heatmap/elephant_overlay.png",
width = 14, height = 14, bg = NA, col = pal_col)
# Overlay the heatmap
image_read("heatmap/elephant_overlay.png") %>%
image_resize(geometry, filter = "quadratic") %>%
image_composite(image, operator = "blend", compose_args = "20") %>%
plot()
```