tahamajs's picture
download
raw
19.1 kB
"""
Neural Reasoning for Knowledge Graphs
Implements multi-hop reasoning, path-based reasoning, and neural-symbolic integration
"""
import torch
import torch.nn as nn
import torch.nn.functional as F
from typing import Dict, List, Tuple, Optional, Set
import numpy as np
from collections import defaultdict, deque
import sys
import os
sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
from core.knowledge_graph import KnowledgeGraph, Triple
class MultiHopReasoning(nn.Module):
"""
Multi-hop reasoning module for knowledge graphs
Performs reasoning over multiple hops to infer new facts
"""
def __init__(
self,
num_entities: int,
num_relations: int,
embedding_dim: int,
max_hops: int = 3,
dropout: float = 0.3,
):
super(MultiHopReasoning, self).__init__()
self.num_entities = num_entities
self.num_relations = num_relations
self.embedding_dim = embedding_dim
self.max_hops = max_hops
# Entity and relation embeddings
self.entity_embeddings = nn.Embedding(num_entities, embedding_dim)
self.relation_embeddings = nn.Embedding(num_relations, embedding_dim)
# Hop-specific transformations
self.hop_transforms = nn.ModuleList(
[nn.Linear(embedding_dim, embedding_dim) for _ in range(max_hops)]
)
# Attention mechanism for path aggregation
self.path_attention = nn.MultiheadAttention(
embedding_dim, num_heads=4, dropout=dropout
)
# Path composition
self.path_composer = nn.LSTM(
embedding_dim,
embedding_dim,
num_layers=2,
batch_first=True,
dropout=dropout,
)
# Final scoring
self.scorer = nn.Sequential(
nn.Linear(embedding_dim * 2, embedding_dim),
nn.ReLU(),
nn.Dropout(dropout),
nn.Linear(embedding_dim, 1),
)
self._init_weights()
def _init_weights(self):
nn.init.xavier_uniform_(self.entity_embeddings.weight)
nn.init.xavier_uniform_(self.relation_embeddings.weight)
def forward(self, head_idx, relation_path, tail_idx):
"""
Args:
head_idx: Starting entity indices (batch_size,)
relation_path: Sequence of relations (batch_size, path_length)
tail_idx: Target entity indices (batch_size,)
"""
batch_size = head_idx.size(0)
# Get initial embeddings
current_emb = self.entity_embeddings(head_idx)
tail_emb = self.entity_embeddings(tail_idx)
# Traverse the path
path_embeddings = [current_emb]
for hop in range(relation_path.size(1)):
rel_emb = self.relation_embeddings(relation_path[:, hop])
# Apply hop-specific transformation
if hop < self.max_hops:
rel_emb = self.hop_transforms[hop](rel_emb)
# Compose with current embedding
current_emb = current_emb + rel_emb
path_embeddings.append(current_emb)
# Stack path embeddings
path_stack = torch.stack(path_embeddings, dim=1) # (batch, path_len+1, dim)
# Apply attention over path
attended_path, _ = self.path_attention(path_stack, path_stack, path_stack)
# Apply LSTM for path composition
lstm_out, _ = self.path_composer(attended_path)
final_path_emb = lstm_out[:, -1, :] # Take last output
# Combine with tail and score
combined = torch.cat([final_path_emb, tail_emb], dim=-1)
scores = self.scorer(combined).squeeze(-1)
return scores
def find_paths(
self,
kg: KnowledgeGraph,
start_entity: str,
end_entity: str,
max_length: int = 3,
) -> List[List[str]]:
"""
Find all paths between two entities using BFS
Args:
kg: Knowledge graph
start_entity: Starting entity ID
end_entity: Target entity ID
max_length: Maximum path length
Returns:
List of paths (each path is a list of relation IDs)
"""
if start_entity not in kg.entities or end_entity not in kg.entities:
return []
paths = []
queue = deque([(start_entity, [])])
visited = set()
while queue:
current, path = queue.popleft()
if len(path) > max_length:
continue
if current == end_entity and len(path) > 0:
paths.append(path)
continue
if (current, tuple(path)) in visited:
continue
visited.add((current, tuple(path)))
# Explore neighbors
for relation, neighbors in kg.entity_adjacency[current].items():
if relation.startswith("inv_"):
continue
for neighbor in neighbors:
new_path = path + [relation]
queue.append((neighbor, new_path))
return paths
class PathRankingReasoning(nn.Module):
"""
Path Ranking Algorithm for Knowledge Graph Reasoning
Learns to rank paths based on their relevance for link prediction
"""
def __init__(
self,
num_relations: int,
embedding_dim: int,
max_path_length: int = 4,
dropout: float = 0.3,
):
super(PathRankingReasoning, self).__init__()
self.num_relations = num_relations
self.embedding_dim = embedding_dim
self.max_path_length = max_path_length
# Relation embeddings
self.relation_embeddings = nn.Embedding(num_relations, embedding_dim)
# Path encoder (Bi-LSTM)
self.path_encoder = nn.LSTM(
embedding_dim,
embedding_dim // 2,
num_layers=2,
batch_first=True,
bidirectional=True,
dropout=dropout,
)
# Path ranker
self.path_ranker = nn.Sequential(
nn.Linear(embedding_dim, embedding_dim // 2),
nn.ReLU(),
nn.Dropout(dropout),
nn.Linear(embedding_dim // 2, 1),
nn.Sigmoid(),
)
self._init_weights()
def _init_weights(self):
nn.init.xavier_uniform_(self.relation_embeddings.weight)
def forward(self, paths):
"""
Args:
paths: List of paths, each path is a tensor of relation indices (batch_size, max_path_len)
"""
# Get relation embeddings for paths
path_embs = self.relation_embeddings(paths) # (batch, path_len, dim)
# Encode paths
encoded_paths, _ = self.path_encoder(path_embs) # (batch, path_len, dim)
# Pool path representations (mean pooling)
pooled_paths = encoded_paths.mean(dim=1) # (batch, dim)
# Rank paths
path_scores = self.path_ranker(pooled_paths).squeeze(-1) # (batch,)
return path_scores, pooled_paths
class NeuralSymbolicReasoning(nn.Module):
"""
Neural-Symbolic Integration for Knowledge Graph Reasoning
Combines neural learning with symbolic logic rules
"""
def __init__(
self,
num_entities: int,
num_relations: int,
embedding_dim: int,
num_rules: int = 50,
dropout: float = 0.3,
):
super(NeuralSymbolicReasoning, self).__init__()
self.num_entities = num_entities
self.num_relations = num_relations
self.embedding_dim = embedding_dim
self.num_rules = num_rules
# Entity and relation embeddings
self.entity_embeddings = nn.Embedding(num_entities, embedding_dim)
self.relation_embeddings = nn.Embedding(num_relations, embedding_dim)
# Rule embeddings (learnable logic rules)
self.rule_embeddings = nn.Parameter(torch.randn(num_rules, embedding_dim))
# Rule attention
self.rule_attention = nn.MultiheadAttention(
embedding_dim, num_heads=4, dropout=dropout
)
# Logic operators (AND, OR, NOT)
self.logic_and = nn.Sequential(
nn.Linear(embedding_dim * 2, embedding_dim), nn.Tanh()
)
self.logic_or = nn.Sequential(
nn.Linear(embedding_dim * 2, embedding_dim), nn.Tanh()
)
self.logic_not = nn.Sequential(
nn.Linear(embedding_dim, embedding_dim), nn.Tanh()
)
# Rule applicator
self.rule_applicator = nn.Sequential(
nn.Linear(embedding_dim * 3, embedding_dim * 2),
nn.ReLU(),
nn.Dropout(dropout),
nn.Linear(embedding_dim * 2, embedding_dim),
nn.ReLU(),
nn.Dropout(dropout),
nn.Linear(embedding_dim, 1),
)
self._init_weights()
def _init_weights(self):
nn.init.xavier_uniform_(self.entity_embeddings.weight)
nn.init.xavier_uniform_(self.relation_embeddings.weight)
nn.init.xavier_uniform_(self.rule_embeddings)
def forward(self, head_idx, relation_idx, tail_idx):
"""
Args:
head_idx: Head entity indices (batch_size,)
relation_idx: Relation indices (batch_size,)
tail_idx: Tail entity indices (batch_size,)
"""
batch_size = head_idx.size(0)
# Get embeddings
head_emb = self.entity_embeddings(head_idx)
rel_emb = self.relation_embeddings(relation_idx)
tail_emb = self.entity_embeddings(tail_idx)
# Prepare for attention
triple_emb = torch.stack(
[head_emb, rel_emb, tail_emb], dim=1
) # (batch, 3, dim)
rules = self.rule_embeddings.unsqueeze(0).expand(
batch_size, -1, -1
) # (batch, num_rules, dim)
# Apply rule attention
attended_rules, attention_weights = self.rule_attention(
triple_emb, rules, rules, average_attn_weights=True
)
# Aggregate attended rules
aggregated_rules = attended_rules.mean(dim=1) # (batch, dim)
# Combine with triple information
combined = torch.cat([head_emb, rel_emb, tail_emb], dim=-1)
# Apply rules
scores = self.rule_applicator(combined).squeeze(-1)
return scores, attention_weights
def apply_logic_operator(self, emb1, emb2, operator="and"):
"""Apply logic operators to embeddings"""
if operator == "and":
return self.logic_and(torch.cat([emb1, emb2], dim=-1))
elif operator == "or":
return self.logic_or(torch.cat([emb1, emb2], dim=-1))
elif operator == "not":
return self.logic_not(emb1)
else:
raise ValueError(f"Unknown operator: {operator}")
class ReasoningChain(nn.Module):
"""
Chain-of-Thought Reasoning for Knowledge Graphs
Implements step-by-step reasoning with intermediate steps
"""
def __init__(
self,
num_entities: int,
num_relations: int,
embedding_dim: int,
max_reasoning_steps: int = 5,
dropout: float = 0.3,
):
super(ReasoningChain, self).__init__()
self.num_entities = num_entities
self.num_relations = num_relations
self.embedding_dim = embedding_dim
self.max_reasoning_steps = max_reasoning_steps
# Entity and relation embeddings
self.entity_embeddings = nn.Embedding(num_entities, embedding_dim)
self.relation_embeddings = nn.Embedding(num_relations, embedding_dim)
# Reasoning step encoder
self.step_encoder = nn.TransformerEncoder(
nn.TransformerEncoderLayer(
d_model=embedding_dim,
nhead=8,
dim_feedforward=embedding_dim * 4,
dropout=dropout,
batch_first=True,
),
num_layers=3,
)
# Step predictor
self.step_predictor = nn.Sequential(
nn.Linear(embedding_dim, embedding_dim),
nn.ReLU(),
nn.Dropout(dropout),
nn.Linear(embedding_dim, num_relations),
)
# Confidence estimator
self.confidence_estimator = nn.Sequential(
nn.Linear(embedding_dim, embedding_dim // 2),
nn.ReLU(),
nn.Dropout(dropout),
nn.Linear(embedding_dim // 2, 1),
nn.Sigmoid(),
)
self._init_weights()
def _init_weights(self):
nn.init.xavier_uniform_(self.entity_embeddings.weight)
nn.init.xavier_uniform_(self.relation_embeddings.weight)
def forward(self, start_entity, target_relation, target_entity):
"""
Args:
start_entity: Starting entity indices (batch_size,)
target_relation: Target relation indices (batch_size,)
target_entity: Target entity indices (batch_size,)
"""
batch_size = start_entity.size(0)
# Get initial embeddings
current_emb = self.entity_embeddings(start_entity)
target_rel_emb = self.relation_embeddings(target_relation)
target_ent_emb = self.entity_embeddings(target_entity)
# Reasoning chain
reasoning_steps = [current_emb]
predicted_relations = []
confidences = []
for step in range(self.max_reasoning_steps):
# Stack reasoning history
step_sequence = torch.stack(reasoning_steps, dim=1) # (batch, step+1, dim)
# Encode reasoning steps
encoded_steps = self.step_encoder(step_sequence)
# Get current state
current_state = encoded_steps[:, -1, :] # (batch, dim)
# Predict next relation
next_relation_logits = self.step_predictor(current_state)
predicted_relations.append(next_relation_logits)
# Estimate confidence
confidence = self.confidence_estimator(current_state)
confidences.append(confidence)
# Update current embedding
next_rel_idx = next_relation_logits.argmax(dim=-1)
next_rel_emb = self.relation_embeddings(next_rel_idx)
current_emb = current_emb + next_rel_emb
reasoning_steps.append(current_emb)
# Final score
final_emb = reasoning_steps[-1]
score = F.cosine_similarity(final_emb, target_ent_emb, dim=-1)
return {
"score": score,
"reasoning_steps": reasoning_steps,
"predicted_relations": predicted_relations,
"confidences": confidences,
}
class ExplainableReasoning(nn.Module):
"""
Explainable Reasoning Module
Provides interpretable explanations for predictions
"""
def __init__(
self,
num_entities: int,
num_relations: int,
embedding_dim: int,
dropout: float = 0.3,
):
super(ExplainableReasoning, self).__init__()
self.num_entities = num_entities
self.num_relations = num_relations
self.embedding_dim = embedding_dim
# Entity and relation embeddings
self.entity_embeddings = nn.Embedding(num_entities, embedding_dim)
self.relation_embeddings = nn.Embedding(num_relations, embedding_dim)
# Attention for explanation
self.explanation_attention = nn.MultiheadAttention(
embedding_dim, num_heads=4, dropout=dropout
)
# Evidence aggregator
self.evidence_aggregator = nn.Sequential(
nn.Linear(embedding_dim * 3, embedding_dim),
nn.ReLU(),
nn.Dropout(dropout),
nn.Linear(embedding_dim, embedding_dim // 2),
nn.ReLU(),
nn.Linear(embedding_dim // 2, 1),
)
self._init_weights()
def _init_weights(self):
nn.init.xavier_uniform_(self.entity_embeddings.weight)
nn.init.xavier_uniform_(self.relation_embeddings.weight)
def forward(self, head_idx, relation_idx, tail_idx, evidence_triples=None):
"""
Args:
head_idx: Head entity indices (batch_size,)
relation_idx: Relation indices (batch_size,)
tail_idx: Tail entity indices (batch_size,)
evidence_triples: Supporting evidence triples (batch_size, num_evidence, 3)
"""
# Get query triple embeddings
head_emb = self.entity_embeddings(head_idx)
rel_emb = self.relation_embeddings(relation_idx)
tail_emb = self.entity_embeddings(tail_idx)
query_emb = torch.stack([head_emb, rel_emb, tail_emb], dim=1) # (batch, 3, dim)
if evidence_triples is not None:
batch_size, num_evidence, _ = evidence_triples.shape
# Get evidence embeddings
evidence_heads = self.entity_embeddings(evidence_triples[:, :, 0])
evidence_rels = self.relation_embeddings(evidence_triples[:, :, 1])
evidence_tails = self.entity_embeddings(evidence_triples[:, :, 2])
evidence_emb = torch.stack(
[evidence_heads, evidence_rels, evidence_tails], dim=2
)
evidence_emb = evidence_emb.view(batch_size, num_evidence * 3, -1)
# Apply attention to find relevant evidence
attended_evidence, attention_weights = self.explanation_attention(
query_emb, evidence_emb, evidence_emb, average_attn_weights=False
)
# Aggregate evidence
combined = torch.cat(
[head_emb, rel_emb, tail_emb], dim=-1
) # (batch, dim*3)
score = self.evidence_aggregator(combined).squeeze(-1)
return score, attention_weights
else:
# No evidence, just score the triple
combined = torch.cat([head_emb, rel_emb, tail_emb], dim=-1)
score = self.evidence_aggregator(combined).squeeze(-1)
return score, None
def explain_prediction(
self, kg: KnowledgeGraph, head: str, relation: str, tail: str, top_k: int = 5
) -> List[Tuple[Triple, float]]:
"""
Generate explanation for a prediction
Args:
kg: Knowledge graph
head: Head entity ID
relation: Relation ID
tail: Tail entity ID
top_k: Number of supporting triples to return
Returns:
List of (triple, relevance_score) tuples
"""
# Find supporting triples (simplified version)
supporting_triples = []
# Find triples involving the head entity
head_triples = kg.query_triples(subject=head)
for triple in head_triples[:top_k]:
supporting_triples.append((triple, 1.0)) # Simplified relevance score
# Find triples involving the tail entity
tail_triples = kg.query_triples(object=tail)
for triple in tail_triples[:top_k]:
if len(supporting_triples) < top_k:
supporting_triples.append((triple, 0.8))
return supporting_triples[:top_k]

Xet Storage Details

Size:
19.1 kB
·
Xet hash:
202a1bfe6cc779e6614dfd426f1d5fb2a73e81a65a5d1deefbb9d6c5d981459c

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.