#![allow(dead_code)]
use crate::parameter::Parameter;
use crate::{GraphData, GraphLayer};
use torsh_tensor::{
creation::{randn, zeros},
Tensor,
};
#[derive(Debug)]
pub struct GATConv {
in_features: usize,
out_features: usize,
heads: usize,
weight: Parameter,
attention: Parameter,
bias: Option<Parameter>,
dropout: f32,
}
impl GATConv {
pub fn new(
in_features: usize,
out_features: usize,
heads: usize,
dropout: f32,
bias: bool,
) -> Self {
let weight = Parameter::new(
randn(&[in_features, heads * out_features]).expect("failed to create weight tensor"),
);
let attention = Parameter::new(
randn(&[heads, 2 * out_features]).expect("failed to create attention tensor"),
);
let bias = if bias {
Some(Parameter::new(
zeros(&[heads * out_features]).expect("failed to create bias tensor"),
))
} else {
None
};
Self {
in_features,
out_features,
heads,
weight,
attention,
bias,
dropout,
}
}
pub fn forward(&self, graph: &GraphData) -> GraphData {
let num_nodes = graph.num_nodes;
let x_transformed = graph
.x
.matmul(&self.weight.clone_data())
.expect("operation should succeed");
let x_reshaped = x_transformed
.view(&[
num_nodes as i32,
self.heads as i32,
self.out_features as i32,
])
.expect("view should succeed");
let edge_flat = graph
.edge_index
.to_vec()
.expect("conversion should succeed");
let num_edges = graph.num_edges;
let src_nodes: Vec<usize> = (0..num_edges).map(|i| edge_flat[i] as usize).collect();
let dst_nodes: Vec<usize> = (0..num_edges)
.map(|i| edge_flat[i + num_edges] as usize)
.collect();
let mut output = zeros(&[num_nodes, self.heads * self.out_features])
.expect("failed to create output tensor");
for head in 0..self.heads {
let attention_head = self
.attention
.clone_data()
.slice_tensor(0, head, head + 1)
.expect("failed to slice attention tensor")
.squeeze_tensor(0)
.expect("failed to squeeze attention tensor");
let mut attention_scores = Vec::with_capacity(num_edges);
for edge_idx in 0..num_edges {
let src = src_nodes[edge_idx];
let dst = dst_nodes[edge_idx];
let src_feat = x_reshaped
.slice_tensor(0, src, src + 1)
.expect("failed to slice source node")
.slice_tensor(1, head, head + 1)
.expect("failed to slice head dimension")
.squeeze_tensor(0)
.expect("failed to squeeze node dimension")
.squeeze_tensor(0)
.expect("failed to squeeze head dimension");
let dst_feat = x_reshaped
.slice_tensor(0, dst, dst + 1)
.expect("failed to slice destination node")
.slice_tensor(1, head, head + 1)
.expect("failed to slice head dimension")
.squeeze_tensor(0)
.expect("failed to squeeze node dimension")
.squeeze_tensor(0)
.expect("failed to squeeze head dimension");
let concat_feat = Tensor::cat(&[&src_feat, &dst_feat], 0)
.expect("failed to concatenate features");
let attention_coeff = attention_head
.mul(&concat_feat)
.expect("operation should succeed")
.sum()
.expect("reduction should succeed");
let coeff_val =
attention_coeff.to_vec().expect("conversion should succeed")[0] as f64;
let activated_val = if coeff_val > 0.0 {
coeff_val
} else {
0.2 * coeff_val };
attention_scores.push((src, dst, activated_val));
}
let mut normalized_scores = vec![0.0; num_edges];
for node in 0..num_nodes {
let mut node_edge_indices = Vec::new();
let mut node_scores = Vec::new();
for (edge_idx, (_, dst, score)) in attention_scores.iter().enumerate() {
if *dst == node {
node_edge_indices.push(edge_idx);
node_scores.push(*score);
}
}
if !node_scores.is_empty() {
let max_score = node_scores.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
let exp_scores: Vec<f64> =
node_scores.iter().map(|s| (*s - max_score).exp()).collect();
let sum_exp: f64 = exp_scores.iter().sum();
for (i, &edge_idx) in node_edge_indices.iter().enumerate() {
normalized_scores[edge_idx] = exp_scores[i] / sum_exp;
}
}
}
let head_output = zeros(&[num_nodes, self.out_features])
.expect("failed to create head output tensor");
for node in 0..num_nodes {
let mut node_output =
zeros(&[self.out_features]).expect("failed to create node output tensor");
for (edge_idx, (src, dst, _)) in attention_scores.iter().enumerate() {
if *dst == node {
let weight = normalized_scores[edge_idx];
if weight > 0.0 {
let src_feat = x_reshaped
.slice_tensor(0, *src, *src + 1)
.expect("failed to slice source node")
.slice_tensor(1, head, head + 1)
.expect("failed to slice head dimension")
.squeeze_tensor(0)
.expect("failed to squeeze node dimension")
.squeeze_tensor(0)
.expect("failed to squeeze head dimension");
let weighted_feat = src_feat
.mul_scalar(weight as f32)
.expect("failed to scale features");
node_output = node_output
.add(&weighted_feat)
.expect("operation should succeed");
}
}
}
let mut node_slice = head_output
.slice_tensor(0, node, node + 1)
.expect("failed to slice node output");
let _ = node_slice.copy_(
&node_output
.unsqueeze_tensor(0)
.expect("failed to unsqueeze node output"),
);
}
let start_feat = head * self.out_features;
let end_feat = (head + 1) * self.out_features;
let mut output_slice = output
.slice_tensor(1, start_feat, end_feat)
.expect("failed to slice output tensor");
let _ = output_slice.copy_(&head_output);
}
if let Some(ref bias) = self.bias {
output = output
.add(&bias.clone_data())
.expect("operation should succeed");
}
if self.dropout > 0.0 {
}
GraphData {
x: output,
edge_index: graph.edge_index.clone(),
edge_attr: graph.edge_attr.clone(),
batch: graph.batch.clone(),
num_nodes: graph.num_nodes,
num_edges: graph.num_edges,
}
}
}
impl GraphLayer for GATConv {
fn forward(&self, graph: &GraphData) -> GraphData {
self.forward(graph)
}
fn parameters(&self) -> Vec<Tensor> {
let mut params = vec![self.weight.clone_data(), self.attention.clone_data()];
if let Some(ref bias) = self.bias {
params.push(bias.clone_data());
}
params
}
}
#[cfg(test)]
mod tests {
use super::*;
use torsh_core::device::DeviceType;
use torsh_tensor::creation::from_vec;
#[test]
fn test_gat_creation() {
let gat = GATConv::new(16, 8, 4, 0.1, true);
let params = gat.parameters();
assert_eq!(params.len(), 3); assert_eq!(gat.heads, 4);
}
#[test]
fn test_gat_forward() {
let gat = GATConv::new(3, 4, 2, 0.0, false);
let x = from_vec(
vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0],
&[3, 3],
DeviceType::Cpu,
)
.expect("operation should succeed");
let edge_index = from_vec(vec![0.0, 1.0, 2.0, 1.0, 2.0, 0.0], &[2, 3], DeviceType::Cpu)
.expect("from vec should succeed");
let graph = GraphData::new(x, edge_index);
let output = gat.forward(&graph);
assert_eq!(output.x.shape().dims(), &[3, 8]); assert_eq!(output.num_nodes, 3);
}
}