pulsatrix
Loading...
Searching...
No Matches
grad_cam.hpp
Go to the documentation of this file.
1
6#pragma once
7
8#include <stdexcept>
9#include <string>
10#include <vector>
11
12#include "pulsatrix/assert.hpp"
16#include "pulsatrix/tensor.hpp"
17
18namespace pulsatrix {
19
37class GradCAM {
38public:
57 [[nodiscard]] Attribution explain(ExplainerContext& ctx, const Tensor& input, int64_t target_index,
58 DeviceBackend* backend) const {
59 Tensor output = ctx.forward_pass(input);
60 // External boundary (Mission 2, finding 15 systemic sweep; rank check added by
61 // campaign_exai_dl_library_batch_dimension_support) -- escalated from
62 // PULSATRIX_ASSERT-only.
63 if (output.rank() != 2) {
64 throw std::invalid_argument("GradCAM::explain: network output must be rank-2 (N, num_classes)");
65 }
66 if (target_index < 0 || target_index >= output.shape().dim(1)) {
67 throw std::invalid_argument("GradCAM::explain: target_index out of range");
68 }
69 int64_t N = output.shape().dim(0);
70 int64_t num_classes = output.shape().dim(1);
71
72 std::vector<float> seed_values(static_cast<size_t>(output.numel()), 0.0f);
73 for (int64_t n = 0; n < N; ++n) {
74 seed_values[static_cast<size_t>(n * num_classes + target_index)] = 1.0f;
75 }
76 Tensor seed(output.shape(), explainer_detail::backend_beside(output, backend), seed_values,
77 output.device());
78 (void)ctx.backward_pass(seed);
79
80 std::vector<NodeId> conv_nodes = ctx.graph().nodes_by_op_type(OpType::Conv);
81 // External boundary -- whether this ExplainerContext was built with a Conv layer
82 // is a caller-configuration fact, not an internal invariant this library controls.
83 if (conv_nodes.empty()) {
84 throw std::invalid_argument("GradCAM::explain: graph has no Conv layer");
85 }
86 NodeId target_node = conv_nodes.back();
87
88 const Tensor& activation_tensor = ctx.activation(target_node);
89 const Tensor& grad_tensor = ctx.gradient(target_node);
90
91 int64_t channels = activation_tensor.shape().dim(1);
92 int64_t height = activation_tensor.shape().dim(2);
93 int64_t width = activation_tensor.shape().dim(3);
94
95 // Host boundary: one read of each buffer, then the original arithmetic on the copies.
96 const std::vector<float> activation = activation_tensor.to_host_vector();
97 const std::vector<float> grad = grad_tensor.to_host_vector();
98 auto at4 = [&](const std::vector<float>& v, int64_t n, int64_t c, int64_t h, int64_t w) {
99 return v[static_cast<size_t>(((n * channels + c) * height + h) * width + w)];
100 };
101
102 std::vector<float> cam(static_cast<size_t>(N * height * width), 0.0f);
103 for (int64_t n = 0; n < N; ++n) {
104 std::vector<float> alpha(static_cast<size_t>(channels), 0.0f);
105 for (int64_t c = 0; c < channels; ++c) {
106 float sum = 0.0f;
107 for (int64_t h = 0; h < height; ++h) {
108 for (int64_t w = 0; w < width; ++w) {
109 sum += at4(grad, n, c, h, w);
110 }
111 }
112 alpha[static_cast<size_t>(c)] = sum / static_cast<float>(height * width);
113 }
114
115 for (int64_t h = 0; h < height; ++h) {
116 for (int64_t w = 0; w < width; ++w) {
117 float value = 0.0f;
118 for (int64_t c = 0; c < channels; ++c) {
119 value += alpha[static_cast<size_t>(c)] * at4(activation, n, c, h, w);
120 }
121 cam[static_cast<size_t>((n * height + h) * width + w)] = value > 0.0f ? value : 0.0f;
122 }
123 }
124 }
125
126 Tensor cam_tensor(Shape({N, height, width}), explainer_detail::backend_beside(activation_tensor, backend),
127 cam, activation_tensor.device());
128 return Attribution{"grad_cam", std::move(cam_tensor),
129 {{"target_index", std::to_string(target_index)},
130 {"layer_node_id", std::to_string(target_node)}}};
131 }
132};
133
134} // namespace pulsatrix
PULSATRIX_ASSERT – debug-only invariant check for programmer errors, distinct from throw (used for ca...
First-class explanation result type – values, method, and metadata together.
std::vector< NodeId > nodes_by_op_type(OpType op_type) const
Finds every node with the given op type.
Vendor-agnostic compute/memory backend. CPUBackend, CUDABackend (Phase 1.5), and HIPBackend (Phase 1....
Definition device_backend.hpp:219
Wraps an ordered chain of Modules, running them via Module::forward_traced to build a real Computatio...
Definition explainer_context.hpp:67
const Tensor & gradient(NodeId id) const
The cached gradient at a node, from the most recent backward_pass() call.
Definition explainer_context.hpp:294
const ComputationGraph & graph() const
The current graph (from the most recent forward_pass() call).
Definition explainer_context.hpp:272
Tensor backward_pass(const Tensor &output_grad)
Runs Autograd::backward from the most recent forward_pass()'s output node.
Definition explainer_context.hpp:207
Tensor forward_pass(const Tensor &input)
Runs the full module chain forward, building a fresh graph and caching every node's activation value ...
Definition explainer_context.hpp:95
const Tensor & activation(NodeId id) const
The cached activation value at a node, from the most recent forward_pass().
Definition explainer_context.hpp:278
L^c = ReLU(sum_k alpha^c_k * A^k), where alpha^c_k = mean_ij(d(y^c)/d(A^k_ij)) and A is the last OpTy...
Definition grad_cam.hpp:37
Attribution explain(ExplainerContext &ctx, const Tensor &input, int64_t target_index, DeviceBackend *backend) const
Computes the Grad-CAM map for one target class index.
Definition grad_cam.hpp:57
An N-dimensional shape. A plain aggregate of dimensions with no invariant beyond "non-negative dimens...
Definition shape.hpp:24
int64_t dim(size_t index) const
Size of a single dimension.
Definition shape.hpp:93
N-dimensional tensor. Owns its data buffer exclusively; a DeviceBackend* is injected (not owned) – th...
Definition tensor.hpp:29
DeviceType device() const
Which device this tensor's buffer conceptually resides on.
Definition tensor.hpp:122
int64_t rank() const
Number of dimensions – shape().rank().
Definition tensor.hpp:119
int64_t numel() const
Total element count – shape().numel().
Definition tensor.hpp:116
const Shape & shape() const
This tensor's shape.
Definition tensor.hpp:113
std::vector< float > to_host_vector() const
Copies the whole buffer to a host vector through the owning backend, on any device.
Abstract interface isolating vendor-specific memory/compute operations from Tensor/ComputationGraph.
Stable interface every explainer gets, regardless of type (charter Part 2 SS2).
DeviceBackend * backend_beside(const Tensor &like, DeviceBackend *backend)
The backend to allocate a tensor through that must live beside like.
Definition attribution.hpp:49
Definition acquisition_functions.hpp:16
size_t NodeId
Stable identifier for a Node within its owning ComputationGraph.
Definition node.hpp:18
An explanation result: the raw attribution values, the method that produced them, and any relevant me...
Definition attribution.hpp:23
N-dimensional tensor – owns a buffer via DeviceBackend*, RAII (Rule of Five).