Exercise Sheet 11
Exercise 1: Activation Maximization (20 P)
Consider the linear model f(x) = w⊤x + b mapping some input x to an output f(x). We would like to interpret the function f by building a prototype x⋆ in the input domain which produces a large value f. Activation maximization produces such interpretation by optimizing
max f (x) − Ω(x). x
- (a) Find the prototype x⋆ obtained by activation maximization subject to the penalty Ω(x) = λ∥x∥2.
- (b) Find the prototype x⋆ obtained by activation maximization subject to the penalty Ω(x) = − log p(x) with
x ∼ N (μ, Σ) where μ and Σ are the mean and covariance.
- (c) Find the prototype x⋆ obtained when the data is generated as (i) z ∼ N (0, I) and (ii) x = Az + c, with A and c the parameters of the generator. Here, we optimize f w.r.t. the code z subject to the penalty Ω(z) = λ∥z∥2.
Exercise 2: Layer-Wise Relevance Propagation (30 P)
We would like to test the dependence of layer-wise relevance propagation (LRP) on the structure of the neural network. For this, we consider the function y = min(a1,a2), where a1,a2 ∈ R+ are the input activations. This function can be implemented as a ReLU network in multiple ways. Two examples are given below.
(i) a 1 a (ii) a -1 a
1 31 1 3-1
1y
a2 -1 a4 -1 a2 1 a4 1
- (a) Show that these two networks implement the ‘min’ function on the relevant domain.
- (b) We consider the LRP-γ propagation rule:
1y
aj·(wjk+γw+) jk
Rj = aj ·(wjk +γw+)Rk kj jk
where ()+ denotes the positive part. For each network, give for the case a1 = a2 an analytic solution for the scores R1 obtained by application this propagation rule at each layer. More specifically, express R1 as a function of the input activations.
Exercise 3: Neuralization (20 P)
Consider the one-class SVM that predicts for every new data point x the ‘inlierness’ score:
M
f(x) = αik(x,ui)
i=1
where (ui)Mi=1 is the collection of support vectors, and αi > 0 are their weightings. We use the Gaussian
kernel k(x, x′) = exp(−γ∥x − x′∥2).
Because we are typically interested in the degree of anomaly of a particular data point, we can also define the score o(x) = − γ1 log f (x) which grows with the degree of anomaly of the data point.
(a) Show that the outlier score o(x) can be rewritten as a two-layer neural network:
hi = ∥x − ui∥2 − γ−1 log αi (layer 1)
o(x) = −γ1 log Mi=1 exp(−γhi) (layer 2)
(b) Show that the layer 2 converges to a min-pooling (i.e. o(x) = minNi=1{hi}) in the limit of γ → ∞.
Exercise 4: Programming (30 P)
Download the programming files on ISIS and follow the instructions.
Exercise sheet 11 (programming) [SoSe 2021] Machine Learning 2
Explaining the Predictions of the VGG-16 Network
In this homework, we would like to implement different explanation methods on the VGG-16 network used for image classification. As a test example, we take some image of a castle
and would like to explain why the VGG:16 neuron castle activates for this image. The code below loads the image and normalizes
it, loads the model, and identifies the output neuron corresponding to the class
castle .
In [1]:
import numpy import cv2 import torch import torch.nn import utils
# Load image
img = numpy.array(cv2.imread('castle.jpg'))[...,::-1]/255.0
mean = torch.Tensor([0.485, 0.456, 0.406]).reshape(1,-1,1,1)
std = torch.Tensor([0.229, 0.224, 0.225]).reshape(1,-1,1,1)
X = (torch.FloatTensor(img[numpy.newaxis].transpose([0,3,1,2])*1) - mean) / std
# Load VGG-16 network
import torchvision
model = torchvision.models.vgg16(pretrained=True); model.eval();
# Identify neuron "castle"
cl = 483
Gradient × Input (15 P)
A simple method for explanation is Gradient × Input. Denoting f the function corresponding to the activation of the desired output neuron, and x the data point for which we would like to explain the prediction, the method assigns feature relevance using the formula
Ri = [∇f(x)]i ⋅ xi
for all i = 1 … d. When the neural network is piecewise linear and positively homogeneous, the method delivers the same result as
one would get with a Taylor expansion at reference point x ̃ = ε ⋅ x with ε almost zero. Task:
Implement Gradient × Input, i.e. write a function that produces a tensor of same dimensions as the input data and that contains the scores (Ri)i, test it on the given input image, and visualize the result using the function
utils.visualize .
In [2]: %matplotlib inline
# ————————————– # TODO: Replace by your code
# ————————————– import solution solution.gradinput(model,X,cl)
# --------------------------------------
We observe that the explanation is noisy and has a large amount of positive and negative evidence. To produce a more robust explanation, we would like to use instead LRP, and implement it using the trick described in the slides. The trick consists of detaching certain terms from the differentiation graph, so that the explanation can obtained by performing Gradient × Input with the modified gradient.
LRP rules are typically set different at each layer. In VGG-16, we distinguish the following three types of layers of parameters:
First convolution: This convolution receives pixels as input, and it requires a special rewrite rule that we given below.
Next convolutions: All remaining convolutions receives activations as input, and we can treat them using LRP-γ.
Dense layers: For these layers, we want to let the standard gradient signal flow, so we can simply leave these layers intact.
LRP in First Convolution
This convolution implements the propagation rule:
xw −lw+−hw− R=∑ iij iij iij R
i ∑ xiwij −liw+ −hiw− j j0,i ijij
where (⋅)+ and (⋅)− are shortcut notations for max(0, ⋅) and min(0, ⋅), xi the value of pixel i, and where li and hi denote lower
and upper-bounds on pixel values. To implement this rule using the proposed approach, we define the quantities
pj = xi wij − li w+ − hi w− and zj = xi wij and perform the reassignation:
zj ←pj ⋅[zj/pj]cst.
This is done by the code below
In [3]: class ConvPx(torch.nn.Module): def __init__(self, conv):
torch.nn.Module.__init__(self)
self.conv = conv
self.pconv = utils.newlayer(conv, lambda p: p.clamp(min=0)) self.nconv = utils.newlayer(conv, lambda p: p.clamp(max=0))
def forward(self, X): X, L, H = X
z = self.conv.forward(X)
zp = z – self.pconv.forward(L) – self.nconv.forward(H) return zp * (z / zp).data
ij ij
LRP in Next Convolutions (15 P)
In the next convolutions, we would like to apply the LRP-γ rule given by:
R=∑ aj(wjk+γw+) R
jk
j ∑ aj(wjk+γw+) k
k0,j jk
To implement this rule using the proposed approach, we define the quantities pk = aj(wjk + γw+ ) and zk = ajwjk and
perform the reassignation:
zk ←pk ⋅[zk/pk]cst.
Inspired by the code for the first convolution, implement a class for the next convolutions that is equiped to perform the
Task:
LRP-γ propagation when calling the gradient.
In [4]: class Conv(torch.nn.Module):
def __init__(self, conv, gamma):
# ————————————– # TODO: Replace by your code
# ————————————– import solution solution.convinit(self,conv,gamma)
# ————————————–
def forward(self, X):
# ————————————– # TODO: Replace by your code
# ————————————– import solution
return solution.convforward(self,X)
# ————————————–
Now, we can create the LRP-enabled model by replacing the convolution layers with their modified versions.
jk
In [5]:
Note that for the layer 0 and for the subsequent layers, we have used two different classes. Also, the parameter γ is set high in the lower layers and goes increasingly closer to zero as we reach the top convolutions.
We then proceed like Gradient × Input, except for the fact that in the modified first convolution the inputs and gradients not only comprise the pixels x but also the lower and upper bounds l and h. The code below implements this extended Gradient × Input and visualizes the resulting explanation.
lrpmodel = torchvision.models.vgg16(pretrained=True); model.eval() features = lrpmodel._modules[‘features’]
for i in [0]:
for i in [2]:
for i in [5,7]:
for i in [10,12,14]: features[i] = Conv(features[i],10**(-1.0)) for i in [17,19,21]: features[i] = Conv(features[i],10**(-1.5)) for i in [24,26,28]: features[i] = Conv(features[i],10**(-2.0))
features[i] = ConvPx(features[i]) features[i] = Conv(features[i],10**(-0.0)) features[i] = Conv(features[i],10**(-0.5))
In [6]: # Prepare data and lower/upper bounds X.grad = None
L = (X*0+((0-mean)/std).view(1,3,1,1)).data H = (X*0+((1-mean)/std).view(1,3,1,1)).data X.requires_grad_(True) L.requires_grad_(True) H.requires_grad_(True)
# Apply the modfied gradient x input
lrpmodel.forward((X,L,H))[0,cl].backward()
R = (X*X.grad+L*L.grad+H*H.grad)
%matplotlib inline utils.visualize(R)
We observe that most of the noise has disappeared, and we can clearly see in red which patterns in the data the neural network has used to predict ‘castle’. We also see in blue which patterns contribute negatively to that class, e.g. the corner of the roof and the traffic sign.




