From 826bcb639aea7680ee9504120c94e78c3844b7e5 Mon Sep 17 00:00:00 2001 From: JeanAnNess Date: Mon, 23 Oct 2023 01:51:16 +0200 Subject: [PATCH 01/11] expand on gradcam --- src/gradient.jl | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/gradient.jl b/src/gradient.jl index 1dfdeff..8ec2b3e 100644 --- a/src/gradient.jl +++ b/src/gradient.jl @@ -68,3 +68,37 @@ Analyze model by using the Integrated Gradients method. - $REF_SUNDARARAJAN_AXIOMATIC """ IntegratedGradients(model, n=50) = InterpolationAugmentation(Gradient(model), n) + +""" + GradCam + +# +""" +struct GradCam{C1,C2} <: AbstractXAIMethod + analyzed_layer::C1 #layer with activations to be analyzed + reversed_layer::C2 #layer on which the heatmap might be created + #Empty constructor tbd + GradCam(C1,C2) = new{typeof(C1),typeof(C2)}(C1,C2) +end +function (analyzer::GradCam)(input, ns::AbstractNeuronSelector)::Explanation + # Forward pass + activation=analyzer.analyzed_layer(input) + + # Gradient for specificly selected neuron (=class) + # Backpropagation + grad,output,output_indices=gradient_wrt_input(analyzer.reversed_layer,activation,ns) + + # Paper : "This signal is then backpropagated to the rectified convolutional feature maps of interest, which we combine to compute the coarse Grad-CAM localization (blue heatmap)." + + # Determine neuron importance α_k^c = 1/Z * ∑_i ∑_j ∂y^c / ∂A_ij^k via GAP + importance = mean(grad, dims=(1, 2)) # Compute the weights for each feature map + + #ReLU since we are only interested on areas with positive impact on selected class ; or is RELU needed on grad ? + activation_relu = max.(activation, 0) + + #calculate GradCam while considering that gradients are set to zero except for the relevant class + relevances = sum(importance .* activation_relu, dims=3) + + #return Explanation + return Explanation(relevances, output, output_indices, :GradCam, nothing) +end \ No newline at end of file From 9c4c8853e31b044d1f9852f985d563eae56c7cca Mon Sep 17 00:00:00 2001 From: janes Date: Mon, 23 Oct 2023 19:16:27 +0200 Subject: [PATCH 02/11] expand GradCam (additional 2nd approach) --- src/ExplainableAI.jl | 2 +- src/gradient.jl | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/ExplainableAI.jl b/src/ExplainableAI.jl index 25dee3f..88308e0 100644 --- a/src/ExplainableAI.jl +++ b/src/ExplainableAI.jl @@ -37,7 +37,7 @@ export analyze # Analyzers export AbstractXAIMethod, Explanation -export Gradient, InputTimesGradient +export Gradient, InputTimesGradient, GradCam, GradCam2 export NoiseAugmentation, SmoothGrad export InterpolationAugmentation, IntegratedGradients export LRP diff --git a/src/gradient.jl b/src/gradient.jl index 8ec2b3e..7ddba6b 100644 --- a/src/gradient.jl +++ b/src/gradient.jl @@ -101,4 +101,40 @@ function (analyzer::GradCam)(input, ns::AbstractNeuronSelector)::Explanation #return Explanation return Explanation(relevances, output, output_indices, :GradCam, nothing) +end + +function find_gradcam_target_layer(model) + for layer in reverse(model.layers) + params = Flux.params(layer) + if !isempty(params) && length(size(params[1])) == 4 #reversed order: find first layer with 4D Output (WHCB) + return layer + end + end + error("No 4D Output found. GradCam cannot be called") +end +struct GradCam2{M} <: AbstractXAIMethod + model::M + GradCam2(model) = new{typeof(model)}(model) + GradCam2(model::Chain) = new{typeof(model)}(Flux.testmode!(check_output_softmax(model))) +end +function (analyzer::GradCam2)(input, ns::AbstractNeuronSelector)::Explanation + # Forward pass + analyzed_layer = find_gradcam_target_layer(model) #Änderung: analyzed_layer berechnen + activation=analyzed_layer(input) #feature maps + + # Gradient for specificly selected neuron (=class) + # Backpropagation + grad,output,output_indices=gradient_wrt_input(analyzer.model,activation,ns) #Änderung: analyzer.reversed_layer >> analyzer.model + + # Determine neuron importance α_k^c = 1/Z * ∑_i ∑_j ∂y^c / ∂A_ij^k via GAP + importance = mean(grad, dims=(1, 2)) # Compute the weights for each feature map + + #ReLU since we are only interested on areas with positive impact on selected class ; or is RELU needed on grad ? + activation_relu = max.(activation, 0) + + #calculate GradCam while considering that gradients are set to zero except for the relevant class + relevances = sum(importance .* activation_relu, dims=3) + + #return Explanation + return Explanation(relevances, output, output_indices, :GradCam2, nothing) end \ No newline at end of file From 5ca2ae897f5da754bc77f1830247fb36eb56df64 Mon Sep 17 00:00:00 2001 From: janes Date: Tue, 24 Oct 2023 11:31:35 +0200 Subject: [PATCH 03/11] harmonize variable names --- src/gradient.jl | 43 +++++++++++++------------------------------ 1 file changed, 13 insertions(+), 30 deletions(-) diff --git a/src/gradient.jl b/src/gradient.jl index 7ddba6b..242789f 100644 --- a/src/gradient.jl +++ b/src/gradient.jl @@ -74,32 +74,22 @@ IntegratedGradients(model, n=50) = InterpolationAugmentation(Gradient(model), n) # """ -struct GradCam{C1,C2} <: AbstractXAIMethod - analyzed_layer::C1 #layer with activations to be analyzed - reversed_layer::C2 #layer on which the heatmap might be created - #Empty constructor tbd +struct GradCAM{C1,C1} <: AbstractXAIMethod + feature_layers::C1 + adaptation_layers::C2 GradCam(C1,C2) = new{typeof(C1),typeof(C2)}(C1,C2) end function (analyzer::GradCam)(input, ns::AbstractNeuronSelector)::Explanation # Forward pass - activation=analyzer.analyzed_layer(input) - - # Gradient for specificly selected neuron (=class) + A = analyzer.feature_layers(input) # Backpropagation - grad,output,output_indices=gradient_wrt_input(analyzer.reversed_layer,activation,ns) + grad,output,output_indices=gradient_wrt_input(analyzer.adaptation_layers,A,ns) - # Paper : "This signal is then backpropagated to the rectified convolutional feature maps of interest, which we combine to compute the coarse Grad-CAM localization (blue heatmap)." - # Determine neuron importance α_k^c = 1/Z * ∑_i ∑_j ∂y^c / ∂A_ij^k via GAP - importance = mean(grad, dims=(1, 2)) # Compute the weights for each feature map - - #ReLU since we are only interested on areas with positive impact on selected class ; or is RELU needed on grad ? - activation_relu = max.(activation, 0) - - #calculate GradCam while considering that gradients are set to zero except for the relevant class - relevances = sum(importance .* activation_relu, dims=3) + αᶜ = mean(grad, dims=(1, 2)) + #ReLU since we are only interested on areas with positive impact on selected class + Lᶜ = max.(sum(αᶜ .* A, dims=3),0) - #return Explanation return Explanation(relevances, output, output_indices, :GradCam, nothing) end @@ -119,22 +109,15 @@ struct GradCam2{M} <: AbstractXAIMethod end function (analyzer::GradCam2)(input, ns::AbstractNeuronSelector)::Explanation # Forward pass - analyzed_layer = find_gradcam_target_layer(model) #Änderung: analyzed_layer berechnen - activation=analyzed_layer(input) #feature maps - - # Gradient for specificly selected neuron (=class) + feature_layers = find_gradcam_target_layer(model) #Änderung: analyzed_layer berechnen + activation=feature_layers(input) #feature maps # Backpropagation grad,output,output_indices=gradient_wrt_input(analyzer.model,activation,ns) #Änderung: analyzer.reversed_layer >> analyzer.model # Determine neuron importance α_k^c = 1/Z * ∑_i ∑_j ∂y^c / ∂A_ij^k via GAP - importance = mean(grad, dims=(1, 2)) # Compute the weights for each feature map - - #ReLU since we are only interested on areas with positive impact on selected class ; or is RELU needed on grad ? - activation_relu = max.(activation, 0) - - #calculate GradCam while considering that gradients are set to zero except for the relevant class - relevances = sum(importance .* activation_relu, dims=3) + αᶜ = mean(grad, dims=(1, 2)) # Compute the weights for each feature map + #ReLU since we are only interested on areas with positive impact on selected class + Lᶜ = max.(sum(αᶜ .* A, dims=3),0) - #return Explanation return Explanation(relevances, output, output_indices, :GradCam2, nothing) end \ No newline at end of file From 2074a8c7d21df9b66f63bee1fb1c912562c152f0 Mon Sep 17 00:00:00 2001 From: janes Date: Tue, 24 Oct 2023 11:35:39 +0200 Subject: [PATCH 04/11] typo fix --- src/gradient.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gradient.jl b/src/gradient.jl index 242789f..a401f04 100644 --- a/src/gradient.jl +++ b/src/gradient.jl @@ -74,7 +74,7 @@ IntegratedGradients(model, n=50) = InterpolationAugmentation(Gradient(model), n) # """ -struct GradCAM{C1,C1} <: AbstractXAIMethod +struct GradCAM{C1,C2} <: AbstractXAIMethod feature_layers::C1 adaptation_layers::C2 GradCam(C1,C2) = new{typeof(C1),typeof(C2)}(C1,C2) From cb684bbb4f60c2ef2dfd76c08761ad48a248f80c Mon Sep 17 00:00:00 2001 From: janes Date: Tue, 24 Oct 2023 14:11:12 +0200 Subject: [PATCH 05/11] rename + replace mean with calculation using sum --- src/ExplainableAI.jl | 2 +- src/gradient.jl | 45 ++++++++++++++++++++------------------------ 2 files changed, 21 insertions(+), 26 deletions(-) diff --git a/src/ExplainableAI.jl b/src/ExplainableAI.jl index 88308e0..c1d8a87 100644 --- a/src/ExplainableAI.jl +++ b/src/ExplainableAI.jl @@ -37,7 +37,7 @@ export analyze # Analyzers export AbstractXAIMethod, Explanation -export Gradient, InputTimesGradient, GradCam, GradCam2 +export Gradient, InputTimesGradient, GradCAM, GradCAM2 export NoiseAugmentation, SmoothGrad export InterpolationAugmentation, IntegratedGradients export LRP diff --git a/src/gradient.jl b/src/gradient.jl index a401f04..030cb90 100644 --- a/src/gradient.jl +++ b/src/gradient.jl @@ -77,20 +77,17 @@ IntegratedGradients(model, n=50) = InterpolationAugmentation(Gradient(model), n) struct GradCAM{C1,C2} <: AbstractXAIMethod feature_layers::C1 adaptation_layers::C2 - GradCam(C1,C2) = new{typeof(C1),typeof(C2)}(C1,C2) + GradCAM(C1,C2) = new{typeof(C1),typeof(C2)}(C1,C2) end -function (analyzer::GradCam)(input, ns::AbstractNeuronSelector)::Explanation - # Forward pass - A = analyzer.feature_layers(input) - # Backpropagation - grad,output,output_indices=gradient_wrt_input(analyzer.adaptation_layers,A,ns) - - # Determine neuron importance α_k^c = 1/Z * ∑_i ∑_j ∂y^c / ∂A_ij^k via GAP - αᶜ = mean(grad, dims=(1, 2)) - #ReLU since we are only interested on areas with positive impact on selected class - Lᶜ = max.(sum(αᶜ .* A, dims=3),0) +function (analyzer::GradCAM)(input, ns::AbstractNeuronSelector) + A = analyzer.feature_layers(input) # Forward pass + grad,output,output_indices=gradient_wrt_input(analyzer.adaptation_layers,A,ns) # Backpropagation - return Explanation(relevances, output, output_indices, :GradCam, nothing) + # Determine neuron importance αₖᶜ = 1/Z * ∑_i ∑_j ∂yᶜ / ∂A_ij^k + αᶜ = sum(grad, dims=(1,2)) / (size(grad,1)*size(grad,2)) + Lᶜ = max.(sum(αᶜ .* A, dims=3),0) + + return Explanation(Lᶜ, output, output_indices, :GradCAM, nothing) end function find_gradcam_target_layer(model) @@ -102,22 +99,20 @@ function find_gradcam_target_layer(model) end error("No 4D Output found. GradCam cannot be called") end -struct GradCam2{M} <: AbstractXAIMethod +struct GradCAM2{M} <: AbstractXAIMethod model::M - GradCam2(model) = new{typeof(model)}(model) - GradCam2(model::Chain) = new{typeof(model)}(Flux.testmode!(check_output_softmax(model))) + GradCAM2(model) = new{typeof(model)}(model) + GradCAM2(model::Chain) = new{typeof(model)}(Flux.testmode!(check_output_softmax(model))) end -function (analyzer::GradCam2)(input, ns::AbstractNeuronSelector)::Explanation - # Forward pass - feature_layers = find_gradcam_target_layer(model) #Änderung: analyzed_layer berechnen - activation=feature_layers(input) #feature maps - # Backpropagation - grad,output,output_indices=gradient_wrt_input(analyzer.model,activation,ns) #Änderung: analyzer.reversed_layer >> analyzer.model - - # Determine neuron importance α_k^c = 1/Z * ∑_i ∑_j ∂y^c / ∂A_ij^k via GAP - αᶜ = mean(grad, dims=(1, 2)) # Compute the weights for each feature map +function (analyzer::GradCAM2)(input, ns::AbstractNeuronSelector)::Explanation + feature_layers = find_gradcam_target_layer(model) + activation=feature_layers(input) # Forward pass + grad,output,output_indices=gradient_wrt_input(analyzer.adaptation_layers,A,ns) # Backpropagation + + # Determine neuron importance αₖᶜ = 1/Z * ∑_i ∑_j ∂yᶜ / ∂A_ij^k + αᶜ = sum(grad, dims=(1,2)) / (size(grad,1)*size(grad,2)) #ReLU since we are only interested on areas with positive impact on selected class Lᶜ = max.(sum(αᶜ .* A, dims=3),0) - return Explanation(relevances, output, output_indices, :GradCam2, nothing) + return Explanation(Lᶜ, output, output_indices, :GradCAM2, nothing) end \ No newline at end of file From 0d32b9ec16f79b90733d41ebc5017a0a7321aed7 Mon Sep 17 00:00:00 2001 From: janes Date: Mon, 6 Nov 2023 15:10:01 +0100 Subject: [PATCH 06/11] docstring, inner const, dict entry, export, rm v2 --- src/ExplainableAI.jl | 3 ++- src/gradient.jl | 35 ++++------------------------------- src/heatmap.jl | 1 + 3 files changed, 7 insertions(+), 32 deletions(-) diff --git a/src/ExplainableAI.jl b/src/ExplainableAI.jl index c1d8a87..48fb434 100644 --- a/src/ExplainableAI.jl +++ b/src/ExplainableAI.jl @@ -37,9 +37,10 @@ export analyze # Analyzers export AbstractXAIMethod, Explanation -export Gradient, InputTimesGradient, GradCAM, GradCAM2 +export Gradient, InputTimesGradient export NoiseAugmentation, SmoothGrad export InterpolationAugmentation, IntegratedGradients +export GradCAM export LRP # LRP rules diff --git a/src/gradient.jl b/src/gradient.jl index 030cb90..de276b4 100644 --- a/src/gradient.jl +++ b/src/gradient.jl @@ -72,47 +72,20 @@ IntegratedGradients(model, n=50) = InterpolationAugmentation(Gradient(model), n) """ GradCam -# +Analyze model by calculating the gradient of a neuron activation with respect to the input. +This gradient is then used to calculate the Class Activation Map. """ struct GradCAM{C1,C2} <: AbstractXAIMethod feature_layers::C1 adaptation_layers::C2 - GradCAM(C1,C2) = new{typeof(C1),typeof(C2)}(C1,C2) + GradCAM(C1,C2) = new{typeof(C1),typeof(C2)}(testmode!(C1),testmode!(C2)) end function (analyzer::GradCAM)(input, ns::AbstractNeuronSelector) A = analyzer.feature_layers(input) # Forward pass grad,output,output_indices=gradient_wrt_input(analyzer.adaptation_layers,A,ns) # Backpropagation - - # Determine neuron importance αₖᶜ = 1/Z * ∑_i ∑_j ∂yᶜ / ∂A_ij^k + # Determine neuron importance αₖᶜ = 1/Z * ∑ᵢ ∑ⱼ ∂yᶜ / ∂Aᵢⱼᵏ αᶜ = sum(grad, dims=(1,2)) / (size(grad,1)*size(grad,2)) Lᶜ = max.(sum(αᶜ .* A, dims=3),0) return Explanation(Lᶜ, output, output_indices, :GradCAM, nothing) -end - -function find_gradcam_target_layer(model) - for layer in reverse(model.layers) - params = Flux.params(layer) - if !isempty(params) && length(size(params[1])) == 4 #reversed order: find first layer with 4D Output (WHCB) - return layer - end - end - error("No 4D Output found. GradCam cannot be called") -end -struct GradCAM2{M} <: AbstractXAIMethod - model::M - GradCAM2(model) = new{typeof(model)}(model) - GradCAM2(model::Chain) = new{typeof(model)}(Flux.testmode!(check_output_softmax(model))) -end -function (analyzer::GradCAM2)(input, ns::AbstractNeuronSelector)::Explanation - feature_layers = find_gradcam_target_layer(model) - activation=feature_layers(input) # Forward pass - grad,output,output_indices=gradient_wrt_input(analyzer.adaptation_layers,A,ns) # Backpropagation - - # Determine neuron importance αₖᶜ = 1/Z * ∑_i ∑_j ∂yᶜ / ∂A_ij^k - αᶜ = sum(grad, dims=(1,2)) / (size(grad,1)*size(grad,2)) - #ReLU since we are only interested on areas with positive impact on selected class - Lᶜ = max.(sum(αᶜ .* A, dims=3),0) - - return Explanation(Lᶜ, output, output_indices, :GradCAM2, nothing) end \ No newline at end of file diff --git a/src/heatmap.jl b/src/heatmap.jl index eb98e35..71c7571 100644 --- a/src/heatmap.jl +++ b/src/heatmap.jl @@ -6,6 +6,7 @@ const HEATMAPPING_PRESETS = Dict{Symbol,Tuple{ColorScheme,Symbol,Symbol}}( :CRP => (ColorSchemes.seismic, :sum, :centered), # attribution :InputTimesGradient => (ColorSchemes.seismic, :sum, :centered), # attribution :Gradient => (ColorSchemes.grays, :norm, :extrema), # gradient + :GradCAM => (ColorSchemes.jet1, :norm, :extrema), #gradient ) """ From c0866b05b6765ae3dc2dbc864e058ee53511e84f Mon Sep 17 00:00:00 2001 From: Adrian Hill Date: Wed, 31 Jan 2024 22:41:57 +0100 Subject: [PATCH 07/11] Minor fixes, run JuliaFormatter --- src/gradient.jl | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/gradient.jl b/src/gradient.jl index eccdd15..43967ab 100644 --- a/src/gradient.jl +++ b/src/gradient.jl @@ -68,22 +68,22 @@ Analyze model by using the Integrated Gradients method. IntegratedGradients(model, n=50) = InterpolationAugmentation(Gradient(model), n) """ - GradCam + GradCAM Analyze model by calculating the gradient of a neuron activation with respect to the input. This gradient is then used to calculate the Class Activation Map. -""" -struct GradCAM{C1,C2} <: AbstractXAIMethod - feature_layers::C1 - adaptation_layers::C2 - GradCAM(C1,C2) = new{typeof(C1),typeof(C2)}(testmode!(C1),testmode!(C2)) +""" +struct GradCAM{F,A} <: AbstractXAIMethod + feature_layers::F + adaptation_layers::A end function (analyzer::GradCAM)(input, ns::AbstractNeuronSelector) - A = analyzer.feature_layers(input) # Forward pass - grad,output,output_indices=gradient_wrt_input(analyzer.adaptation_layers,A,ns) # Backpropagation - # Determine neuron importance αₖᶜ = 1/Z * ∑ᵢ ∑ⱼ ∂yᶜ / ∂Aᵢⱼᵏ - αᶜ = sum(grad, dims=(1,2)) / (size(grad,1)*size(grad,2)) - Lᶜ = max.(sum(αᶜ .* A, dims=3),0) - - return Explanation(Lᶜ, output, output_indices, :GradCAM, nothing) -end \ No newline at end of file + A = analyzer.feature_layers(input) # feature map + feature_map_size = size(A, 1) * size(A, 2) + + # Determine neuron importance αₖᶜ = 1/Z * ∑ᵢ ∑ⱼ ∂yᶜ / ∂Aᵢⱼᵏ + grad, output, output_indices = gradient_wrt_input(analyzer.adaptation_layers, A, ns) + αᶜ = sum(grad; dims=(1, 2)) / feature_map_size + Lᶜ = max.(sum(αᶜ .* A; dims=3), 0) + return Explanation(Lᶜ, output, output_indices, :GradCAM, :cam, nothing) +end From 56a912b805d82300f4cdfba7dbf4418ce873b7c2 Mon Sep 17 00:00:00 2001 From: Adrian Hill Date: Wed, 31 Jan 2024 22:42:12 +0100 Subject: [PATCH 08/11] Add tests --- test/references/cnn/GradCAM_max.jld2 | Bin 0 -> 1057 bytes test/references/cnn/GradCAM_ns1.jld2 | Bin 0 -> 1057 bytes test/test_batches.jl | 3 ++- test/test_cnn.jl | 5 +---- 4 files changed, 3 insertions(+), 5 deletions(-) create mode 100644 test/references/cnn/GradCAM_max.jld2 create mode 100644 test/references/cnn/GradCAM_ns1.jld2 diff --git a/test/references/cnn/GradCAM_max.jld2 b/test/references/cnn/GradCAM_max.jld2 new file mode 100644 index 0000000000000000000000000000000000000000..5031171ba544469025d62cb5495ddaba7f2d8d05 GIT binary patch literal 1057 zcmeZpaWmCTN-R!IQSd6w$xKvmNi0cJaLX^sO)Sw-C`&CW&dkqKFwis9Gh|TEfG7d7 z4fG5Y%uIBXGD{SETs0X+!B7o>P7fD1UM?vvCJqh;1}2Cv{zHKx3xwar2%(E>57zm6 zxCAjV`Z2Ns)iQH3ssJ^yFfc+DFu-V_SzrL8nMD{F4He`WAOurPwe z7!!46t+spWSe;1vVwsnmWW`a!YNhqZ*lNy~vzCkOHdt0Y@wIANmTblMEyRkuti(#6 zsm;pWw8%>P&UMS%rNUMPZIi6FNb6dKuyt8^?{KqH>G!r`u>WWIS0wz{yg#_IF-1gm*_ zQmt+?v{)?&UT>N3f4SAXb7@uuWfv{$WJ0X;BNtiNZ&tUOv#rxA>+m$o0+X3moYy0* za?;zaG{VkWHnT0UGIG(d(mLB^b^FyKiyodvs|)@WR$iHJEVa9~TCudBv)p?@(aM#1 zpOw~EA*)v_&%OYMEJF{I5GdqeQ2?X4z)`~{01PVuMwZlyf*b>I00RjZ7=uC@Xf`^i Im?Gc~00jr6KL7v# literal 0 HcmV?d00001 diff --git a/test/references/cnn/GradCAM_ns1.jld2 b/test/references/cnn/GradCAM_ns1.jld2 new file mode 100644 index 0000000000000000000000000000000000000000..d42a67b002e04887124d6369f6eb00e4cae481d0 GIT binary patch literal 1057 zcmeZpaWmCTN-R!IQSd6w$xKvmNi0cJaLX^sO)Sw-C`&CW&dkqKFwis9Gh|TEfG7d7 z4fG5Y%uIBXGD{SETs0X+!B7o>P7fD1UM?vvCJqh;1}2Cv{zHKx3xwar2%(E>57zm6 zxCAjV`Z2Ns)iQH3ssJ^yFfc+DFu-V_SzrL8nMD{F4He`WAOurPwe zVA`O%Mjq8Hazh>*vJ5>;LLl$Mq5wv7fun{^02o#Rj4Y`Y1vv(=P;!AWD5QaAql1bm G0`36lm^%Of literal 0 HcmV?d00001 diff --git a/test/test_batches.jl b/test/test_batches.jl index 7922c8c..b6d6f3a 100644 --- a/test/test_batches.jl +++ b/test/test_batches.jl @@ -8,7 +8,7 @@ ins = 20 outs = 10 batchsize = 15 -model = Chain(Dense(ins, outs, relu; init=pseudorand)) +model = Chain(Dense(ins, 15, relu; init=pseudorand), Dense(15, outs, relu; init=pseudorand)) # Input 1 w/o batch dimension input1_no_bd = rand(MersenneTwister(1), Float32, ins) @@ -24,6 +24,7 @@ ANALYZERS = Dict( "InputTimesGradient" => InputTimesGradient, "SmoothGrad" => m -> SmoothGrad(m, 5, 0.1, MersenneTwister(123)), "IntegratedGradients" => m -> IntegratedGradients(m, 5), + "GradCAM" => m -> GradCAM(m[1], m[2]), ) for (name, method) in ANALYZERS diff --git a/test/test_cnn.jl b/test/test_cnn.jl index ad42d98..3dc5602 100644 --- a/test/test_cnn.jl +++ b/test/test_cnn.jl @@ -6,6 +6,7 @@ const GRADIENT_ANALYZERS = Dict( "InputTimesGradient" => InputTimesGradient, "SmoothGrad" => m -> SmoothGrad(m, 5, 0.1, MersenneTwister(123)), "IntegratedGradients" => m -> IntegratedGradients(m, 5), + "GradCAM" => m -> GradCAM(m[1], m[2]), ) input_size = (32, 32, 3, 1) @@ -67,8 +68,6 @@ function test_cnn(name, method) println("Timing $name...") print("cold:") @time expl = analyze(input, analyzer) - - @test size(expl.val) == size(input) @test_reference "references/cnn/$(name)_max.jld2" Dict("expl" => expl.val) by = (r, a) -> isapprox(r["expl"], a["expl"]; rtol=0.05) end @@ -76,8 +75,6 @@ function test_cnn(name, method) analyzer = method(model) print("warm:") @time expl = analyze(input, analyzer, 1) - - @test size(expl.val) == size(input) @test_reference "references/cnn/$(name)_ns1.jld2" Dict("expl" => expl.val) by = (r, a) -> isapprox(r["expl"], a["expl"]; rtol=0.05) end From 133ac65ed1cd2bc48dea6719323a2565fdb64681 Mon Sep 17 00:00:00 2001 From: Adrian Hill Date: Wed, 31 Jan 2024 22:47:30 +0100 Subject: [PATCH 09/11] Move to new `gradcam.jl` source file --- src/ExplainableAI.jl | 1 + src/gradcam.jl | 20 ++++++++++++++++++++ src/gradient.jl | 21 --------------------- 3 files changed, 21 insertions(+), 21 deletions(-) create mode 100644 src/gradcam.jl diff --git a/src/ExplainableAI.jl b/src/ExplainableAI.jl index c0e88ab..19e3149 100644 --- a/src/ExplainableAI.jl +++ b/src/ExplainableAI.jl @@ -12,6 +12,7 @@ include("compat.jl") include("bibliography.jl") include("input_augmentation.jl") include("gradient.jl") +include("gradcam.jl") export Gradient, InputTimesGradient export NoiseAugmentation, SmoothGrad diff --git a/src/gradcam.jl b/src/gradcam.jl new file mode 100644 index 0000000..f552e41 --- /dev/null +++ b/src/gradcam.jl @@ -0,0 +1,20 @@ +""" + GradCAM + +Analyze model by calculating the gradient of a neuron activation with respect to the input. +This gradient is then used to calculate the Class Activation Map. +""" +struct GradCAM{F,A} <: AbstractXAIMethod + feature_layers::F + adaptation_layers::A +end +function (analyzer::GradCAM)(input, ns::AbstractNeuronSelector) + A = analyzer.feature_layers(input) # feature map + feature_map_size = size(A, 1) * size(A, 2) + + # Determine neuron importance αₖᶜ = 1/Z * ∑ᵢ ∑ⱼ ∂yᶜ / ∂Aᵢⱼᵏ + grad, output, output_indices = gradient_wrt_input(analyzer.adaptation_layers, A, ns) + αᶜ = sum(grad; dims=(1, 2)) / feature_map_size + Lᶜ = max.(sum(αᶜ .* A; dims=3), 0) + return Explanation(Lᶜ, output, output_indices, :GradCAM, :cam, nothing) +end diff --git a/src/gradient.jl b/src/gradient.jl index 43967ab..7744d60 100644 --- a/src/gradient.jl +++ b/src/gradient.jl @@ -66,24 +66,3 @@ Analyze model by using the Integrated Gradients method. - $REF_SUNDARARAJAN_AXIOMATIC """ IntegratedGradients(model, n=50) = InterpolationAugmentation(Gradient(model), n) - -""" - GradCAM - -Analyze model by calculating the gradient of a neuron activation with respect to the input. -This gradient is then used to calculate the Class Activation Map. -""" -struct GradCAM{F,A} <: AbstractXAIMethod - feature_layers::F - adaptation_layers::A -end -function (analyzer::GradCAM)(input, ns::AbstractNeuronSelector) - A = analyzer.feature_layers(input) # feature map - feature_map_size = size(A, 1) * size(A, 2) - - # Determine neuron importance αₖᶜ = 1/Z * ∑ᵢ ∑ⱼ ∂yᶜ / ∂Aᵢⱼᵏ - grad, output, output_indices = gradient_wrt_input(analyzer.adaptation_layers, A, ns) - αᶜ = sum(grad; dims=(1, 2)) / feature_map_size - Lᶜ = max.(sum(αᶜ .* A; dims=3), 0) - return Explanation(Lᶜ, output, output_indices, :GradCAM, :cam, nothing) -end From 36133411bf239d22e6f90b780eefee2c28c4bbec Mon Sep 17 00:00:00 2001 From: Adrian Hill Date: Wed, 31 Jan 2024 22:48:35 +0100 Subject: [PATCH 10/11] Add `GradCAM` to README and API reference --- README.md | 1 + docs/src/api.md | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index d7863d9..9a04f2c 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,7 @@ Currently, the following analyzers are implemented: * `InputTimesGradient` * `SmoothGrad` * `IntegratedGradients` +* `GradCAM` One of the design goals of the [Julia-XAI ecosystem][juliaxai-docs] is extensibility. To implement an XAI method, take a look at the [common interface diff --git a/docs/src/api.md b/docs/src/api.md index 31269f5..dad40fe 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -12,6 +12,7 @@ Gradient InputTimesGradient SmoothGrad IntegratedGradients +GradCAM ``` # Input augmentations From 26ea2353a697baa4c2b0893cd8054aa4ae988b84 Mon Sep 17 00:00:00 2001 From: janes Date: Thu, 8 Feb 2024 17:09:07 +0100 Subject: [PATCH 11/11] Add Reference, Update docstring for GradCAM --- src/bibliography.jl | 1 + src/gradcam.jl | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/bibliography.jl b/src/bibliography.jl index 4e88e35..83919da 100644 --- a/src/bibliography.jl +++ b/src/bibliography.jl @@ -1,3 +1,4 @@ # Gradient methods: const REF_SMILKOV_SMOOTHGRAD = "Smilkov et al., *SmoothGrad: removing noise by adding noise*" const REF_SUNDARARAJAN_AXIOMATIC = "Sundararajan et al., *Axiomatic Attribution for Deep Networks*" +const REF_SELVARAJU_GRADCAM = "Selvaraju et al., *Grad-CAM: Visual Explanations from Deep Networks via Gradient-based Localization*" \ No newline at end of file diff --git a/src/gradcam.jl b/src/gradcam.jl index f552e41..c44ac86 100644 --- a/src/gradcam.jl +++ b/src/gradcam.jl @@ -1,8 +1,19 @@ """ - GradCAM + GradCAM(feature_layers, adaptation_layers) -Analyze model by calculating the gradient of a neuron activation with respect to the input. -This gradient is then used to calculate the Class Activation Map. +Calculates the Gradient-weighted Class Activation Map (GradCAM). +GradCAM provides a visual explanation of the regions with significant neuron importance for the model's classification decision. + +# Parameters +- `feature_layers`: The layers of a convolutional neural network (CNN) responsible for extracting feature maps. +- `adaptation_layers`: The layers of the CNN used for adaptation and classification. + +# Note +Flux is not required for GradCAM. +GradCAM is compatible with a wide variety of CNN model-families. + +# References +- $REF_SELVARAJU_GRADCAM """ struct GradCAM{F,A} <: AbstractXAIMethod feature_layers::F