#![allow(dead_code)]
type Result<T, E = torsh_core::error::TorshError> = std::result::Result<T, E>;
use crate::parameter::Parameter;
use std::collections::HashMap;
use torsh_tensor::{
creation::{randn, zeros},
Tensor,
};
pub type NodeType = String;
pub type EdgeType = (NodeType, String, NodeType);
#[derive(Debug, Clone)]
pub struct HeteroGraphData {
pub node_features: HashMap<NodeType, Tensor>,
pub edge_indices: HashMap<EdgeType, Tensor>,
pub edge_attributes: HashMap<EdgeType, Option<Tensor>>,
pub num_nodes: HashMap<NodeType, usize>,
}
impl HeteroGraphData {
pub fn new() -> Self {
Self {
node_features: HashMap::new(),
edge_indices: HashMap::new(),
edge_attributes: HashMap::new(),
num_nodes: HashMap::new(),
}
}
pub fn add_node_type(&mut self, node_type: NodeType, features: Tensor) -> &mut Self {
let num_nodes = features.shape().dims()[0];
self.node_features.insert(node_type.clone(), features);
self.num_nodes.insert(node_type, num_nodes);
self
}
pub fn add_edge_type(
&mut self,
edge_type: EdgeType,
edge_index: Tensor,
edge_attr: Option<Tensor>,
) -> &mut Self {
self.edge_indices.insert(edge_type.clone(), edge_index);
self.edge_attributes.insert(edge_type, edge_attr);
self
}
pub fn node_types(&self) -> Vec<&NodeType> {
self.node_features.keys().collect()
}
pub fn edge_types(&self) -> Vec<&EdgeType> {
self.edge_indices.keys().collect()
}
}
#[derive(Debug)]
pub struct HeteroGNN {
node_types: Vec<NodeType>,
edge_types: Vec<EdgeType>,
node_transformations: HashMap<NodeType, Parameter>,
edge_transformations: HashMap<EdgeType, Parameter>,
out_features: usize,
bias: bool,
biases: HashMap<NodeType, Option<Parameter>>,
}
impl HeteroGNN {
pub fn new(
node_type_dims: HashMap<NodeType, usize>,
edge_types: Vec<EdgeType>,
out_features: usize,
bias: bool,
) -> Result<Self> {
let mut node_transformations = HashMap::new();
let mut biases = HashMap::new();
for (node_type, in_features) in &node_type_dims {
let weight = Parameter::new(randn(&[*in_features, out_features])?);
node_transformations.insert(node_type.clone(), weight);
let bias_param = if bias {
Some(Parameter::new(zeros(&[out_features])?))
} else {
None
};
biases.insert(node_type.clone(), bias_param);
}
let mut edge_transformations = HashMap::new();
for edge_type in &edge_types {
let weight = Parameter::new(randn(&[out_features, out_features])?);
edge_transformations.insert(edge_type.clone(), weight);
}
Ok(Self {
node_types: node_type_dims.keys().cloned().collect(),
edge_types,
node_transformations,
edge_transformations,
out_features,
bias,
biases,
})
}
pub fn forward(&self, hetero_graph: &HeteroGraphData) -> Result<HeteroGraphData> {
let mut output_features = HashMap::new();
let mut transformed_features = HashMap::new();
for node_type in &self.node_types {
if let Some(features) = hetero_graph.node_features.get(node_type) {
if let Some(transform) = self.node_transformations.get(node_type) {
let mut transformed = features.matmul(&transform.clone_data())?;
if let Some(Some(bias)) = self.biases.get(node_type) {
transformed = transformed.add(&bias.clone_data())?;
}
transformed_features.insert(node_type.clone(), transformed);
}
}
}
let mut aggregated_messages = HashMap::new();
for edge_type in &self.edge_types {
let (src_type, relation, dst_type) = edge_type;
if let (Some(edge_index), Some(src_features), Some(edge_transform)) = (
hetero_graph.edge_indices.get(edge_type),
transformed_features.get(src_type),
self.edge_transformations.get(edge_type),
) {
let edge_flat = edge_index.to_vec()?;
let num_edges = edge_flat.len() / 2;
if num_edges > 0 {
let src_indices = &edge_flat[0..num_edges];
let dst_indices = &edge_flat[num_edges..];
let dst_num_nodes = hetero_graph.num_nodes.get(dst_type).unwrap_or(&0);
let messages = zeros(&[*dst_num_nodes, self.out_features])?;
for edge_idx in 0..num_edges {
let src_node = src_indices[edge_idx] as usize;
let dst_node = dst_indices[edge_idx] as usize;
let src_feat = src_features
.slice_tensor(0, src_node, src_node + 1)?
.squeeze_tensor(0)?;
let message = src_feat
.unsqueeze_tensor(0)?
.matmul(&edge_transform.clone_data())?
.squeeze_tensor(0)?;
let mut dst_slice = messages.slice_tensor(0, dst_node, dst_node + 1)?;
let current_msg = dst_slice.squeeze_tensor(0)?;
let updated_msg = current_msg.add(&message)?;
let _ = dst_slice.copy_(&updated_msg.unsqueeze_tensor(0)?);
}
aggregated_messages.insert(
(src_type.clone(), relation.clone(), dst_type.clone()),
messages,
);
}
}
}
for node_type in &self.node_types {
let mut node_output = if let Some(self_features) = transformed_features.get(node_type) {
self_features.clone()
} else {
continue;
};
for edge_type in &self.edge_types {
let (_, _, dst_type) = edge_type;
if dst_type == node_type {
if let Some(messages) = aggregated_messages.get(edge_type) {
node_output = node_output.add(messages)?;
}
}
}
let zero_tensor = zeros(node_output.shape().dims())?;
node_output = node_output.maximum(&zero_tensor)?;
output_features.insert(node_type.clone(), node_output);
}
let mut output = HeteroGraphData::new();
output.node_features = output_features;
output.edge_indices = hetero_graph.edge_indices.clone();
output.edge_attributes = hetero_graph.edge_attributes.clone();
output.num_nodes = hetero_graph.num_nodes.clone();
Ok(output)
}
pub fn parameters(&self) -> Vec<Tensor> {
let mut params = Vec::new();
for transform in self.node_transformations.values() {
params.push(transform.clone_data());
}
for transform in self.edge_transformations.values() {
params.push(transform.clone_data());
}
for bias_opt in self.biases.values() {
if let Some(bias) = bias_opt {
params.push(bias.clone_data());
}
}
params
}
}
#[derive(Debug)]
pub struct HeteroGAT {
node_types: Vec<NodeType>,
edge_types: Vec<EdgeType>,
query_transforms: HashMap<NodeType, Parameter>,
key_transforms: HashMap<NodeType, Parameter>,
value_transforms: HashMap<NodeType, Parameter>,
relation_attentions: HashMap<EdgeType, Parameter>,
heads: usize,
out_features: usize,
dropout: f32,
}
impl HeteroGAT {
pub fn new(
node_type_dims: HashMap<NodeType, usize>,
edge_types: Vec<EdgeType>,
out_features: usize,
heads: usize,
dropout: f32,
) -> Result<Self> {
let mut query_transforms = HashMap::new();
let mut key_transforms = HashMap::new();
let mut value_transforms = HashMap::new();
for (node_type, in_features) in &node_type_dims {
let q = Parameter::new(randn(&[*in_features, heads * out_features])?);
let k = Parameter::new(randn(&[*in_features, heads * out_features])?);
let v = Parameter::new(randn(&[*in_features, heads * out_features])?);
query_transforms.insert(node_type.clone(), q);
key_transforms.insert(node_type.clone(), k);
value_transforms.insert(node_type.clone(), v);
}
let mut relation_attentions = HashMap::new();
for edge_type in &edge_types {
let attention = Parameter::new(randn(&[heads, 2 * out_features])?);
relation_attentions.insert(edge_type.clone(), attention);
}
Ok(Self {
node_types: node_type_dims.keys().cloned().collect(),
edge_types,
query_transforms,
key_transforms,
value_transforms,
relation_attentions,
heads,
out_features,
dropout,
})
}
pub fn forward(&self, hetero_graph: &HeteroGraphData) -> Result<HeteroGraphData> {
let mut output_features = HashMap::new();
let mut queries = HashMap::new();
let mut keys = HashMap::new();
let mut values = HashMap::new();
for node_type in &self.node_types {
if let Some(features) = hetero_graph.node_features.get(node_type) {
let q = features.matmul(&self.query_transforms[node_type].clone_data())?;
let k = features.matmul(&self.key_transforms[node_type].clone_data())?;
let v = features.matmul(&self.value_transforms[node_type].clone_data())?;
let num_nodes = features.shape().dims()[0];
let q_reshaped = q.view(&[
num_nodes as i32,
self.heads as i32,
self.out_features as i32,
])?;
let k_reshaped = k.view(&[
num_nodes as i32,
self.heads as i32,
self.out_features as i32,
])?;
let v_reshaped = v.view(&[
num_nodes as i32,
self.heads as i32,
self.out_features as i32,
])?;
queries.insert(node_type.clone(), q_reshaped);
keys.insert(node_type.clone(), k_reshaped);
values.insert(node_type.clone(), v_reshaped);
}
}
for dst_type in &self.node_types {
let dst_num_nodes = hetero_graph.num_nodes.get(dst_type).unwrap_or(&0);
let aggregated_output = zeros(&[*dst_num_nodes, self.heads * self.out_features])?;
for edge_type in &self.edge_types {
let (src_type, _relation, target_type) = edge_type;
if target_type != dst_type {
continue;
}
if let (
Some(edge_index),
Some(_src_queries),
Some(_dst_keys),
Some(src_values),
Some(_attention_params),
) = (
hetero_graph.edge_indices.get(edge_type),
queries.get(src_type),
keys.get(dst_type),
values.get(src_type),
self.relation_attentions.get(edge_type),
) {
let edge_flat = edge_index.to_vec()?;
let num_edges = edge_flat.len() / 2;
if num_edges > 0 {
let src_indices = &edge_flat[0..num_edges];
let dst_indices = &edge_flat[num_edges..];
for edge_idx in 0..num_edges {
let src_node = src_indices[edge_idx] as usize;
let dst_node = dst_indices[edge_idx] as usize;
let src_value = src_values
.slice_tensor(0, src_node, src_node + 1)?
.view(&[1, (self.heads * self.out_features) as i32])?
.squeeze_tensor(0)?;
let mut dst_slice =
aggregated_output.slice_tensor(0, dst_node, dst_node + 1)?;
let current = dst_slice.squeeze_tensor(0)?;
let updated = current.add(&src_value)?;
let _ = dst_slice.copy_(&updated.unsqueeze_tensor(0)?);
}
}
}
}
output_features.insert(dst_type.clone(), aggregated_output);
}
let mut output = HeteroGraphData::new();
output.node_features = output_features;
output.edge_indices = hetero_graph.edge_indices.clone();
output.edge_attributes = hetero_graph.edge_attributes.clone();
output.num_nodes = hetero_graph.num_nodes.clone();
Ok(output)
}
pub fn parameters(&self) -> Vec<Tensor> {
let mut params = Vec::new();
for transform in self.query_transforms.values() {
params.push(transform.clone_data());
}
for transform in self.key_transforms.values() {
params.push(transform.clone_data());
}
for transform in self.value_transforms.values() {
params.push(transform.clone_data());
}
for attention in self.relation_attentions.values() {
params.push(attention.clone_data());
}
params
}
}
#[derive(Debug)]
pub struct KnowledgeGraphEmbedding {
entity_types: Vec<NodeType>,
relation_types: Vec<String>,
entity_embeddings: HashMap<NodeType, Parameter>,
relation_embeddings: HashMap<String, Parameter>,
embedding_dim: usize,
}
impl KnowledgeGraphEmbedding {
pub fn new(
entity_types: Vec<NodeType>,
relation_types: Vec<String>,
num_entities: HashMap<NodeType, usize>,
embedding_dim: usize,
) -> Result<Self> {
let mut entity_embeddings = HashMap::new();
let mut relation_embeddings = HashMap::new();
for entity_type in &entity_types {
let num = num_entities.get(entity_type).unwrap_or(&100);
let embeddings = Parameter::new(randn(&[*num, embedding_dim])?);
entity_embeddings.insert(entity_type.clone(), embeddings);
}
for relation in &relation_types {
let embeddings = Parameter::new(randn(&[embedding_dim, embedding_dim])?);
relation_embeddings.insert(relation.clone(), embeddings);
}
Ok(Self {
entity_types,
relation_types,
entity_embeddings,
relation_embeddings,
embedding_dim,
})
}
pub fn get_entity_embedding(
&self,
entity_type: &NodeType,
entity_id: usize,
) -> Result<Option<Tensor>> {
if let Some(embeddings) = self.entity_embeddings.get(entity_type) {
Ok(Some(
embeddings
.clone_data()
.slice_tensor(0, entity_id, entity_id + 1)?
.squeeze_tensor(0)?,
))
} else {
Ok(None)
}
}
pub fn triple_score(
&self,
head_type: &NodeType,
head_id: usize,
relation: &String,
tail_type: &NodeType,
tail_id: usize,
) -> Result<Option<f64>> {
if let (Some(head_emb), Some(tail_emb), Some(rel_emb)) = (
self.get_entity_embedding(head_type, head_id)?,
self.get_entity_embedding(tail_type, tail_id)?,
self.relation_embeddings.get(relation),
) {
let head_plus_rel = head_emb
.unsqueeze_tensor(0)?
.matmul(&rel_emb.clone_data())?
.squeeze_tensor(0)?;
let diff = head_plus_rel.sub(&tail_emb)?;
let score_tensor = diff.dot(&diff)?;
let score = score_tensor.to_vec()?[0] as f64;
Ok(Some(-score)) } else {
Ok(None)
}
}
pub fn parameters(&self) -> Vec<Tensor> {
let mut params = Vec::new();
for emb in self.entity_embeddings.values() {
params.push(emb.clone_data());
}
for emb in self.relation_embeddings.values() {
params.push(emb.clone_data());
}
params
}
}
#[cfg(test)]
mod tests {
use super::*;
use torsh_core::device::DeviceType;
use torsh_tensor::creation::from_vec;
#[test]
fn test_hetero_graph_creation() {
let mut hetero_graph = HeteroGraphData::new();
let user_features = from_vec(vec![1.0, 2.0, 3.0, 4.0], &[2, 2], DeviceType::Cpu)
.expect("from vec should succeed");
hetero_graph.add_node_type("user".to_string(), user_features);
let item_features = from_vec(
vec![5.0, 6.0, 7.0, 8.0, 9.0, 10.0],
&[2, 3],
DeviceType::Cpu,
)
.expect("operation should succeed");
hetero_graph.add_node_type("item".to_string(), item_features);
let edge_index = from_vec(vec![0.0, 1.0, 0.0, 1.0], &[2, 2], DeviceType::Cpu)
.expect("from vec should succeed");
hetero_graph.add_edge_type(
("user".to_string(), "likes".to_string(), "item".to_string()),
edge_index,
None,
);
assert_eq!(hetero_graph.node_types().len(), 2);
assert_eq!(hetero_graph.edge_types().len(), 1);
}
#[test]
fn test_hetero_gnn_creation() {
let mut node_dims = HashMap::new();
node_dims.insert("user".to_string(), 2);
node_dims.insert("item".to_string(), 3);
let edge_types = vec![("user".to_string(), "likes".to_string(), "item".to_string())];
let hetero_gnn = HeteroGNN::new(node_dims, edge_types, 8, true);
let params = hetero_gnn.expect("operation should succeed").parameters();
assert!(params.len() >= 4);
}
#[test]
fn test_knowledge_graph_embeddings() {
let entity_types = vec!["person".to_string(), "company".to_string()];
let relation_types = vec!["works_at".to_string(), "founded".to_string()];
let mut num_entities = HashMap::new();
num_entities.insert("person".to_string(), 10);
num_entities.insert("company".to_string(), 5);
let kg_emb = KnowledgeGraphEmbedding::new(entity_types, relation_types, num_entities, 50)
.expect("operation should succeed");
let person_emb = kg_emb
.get_entity_embedding(&"person".to_string(), 0)
.expect("operation should succeed");
assert!(person_emb.is_some());
let emb = person_emb;
assert_eq!(emb.expect("operation should succeed").shape().dims(), &[50]);
let score = kg_emb
.triple_score(
&"person".to_string(),
0,
&"works_at".to_string(),
&"company".to_string(),
0,
)
.expect("operation should succeed");
assert!(score.is_some());
assert!(score.expect("operation should succeed").is_finite());
}
}