5dimension's picture
Deploy sentinel_gnn_app.py
e905fbe verified
Raw
History Blame Contribute Delete
6.27 kB
import gradio as gr
import numpy as np
import torch
import torch.nn as nn
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
class SechGraphConv(nn.Module):
def __init__(self, in_ch, out_ch):
super().__init__()
self.linear = nn.Linear(in_ch, out_ch)
self.inv_e = 1.0 / np.e
def forward(self, x, edge_index, edge_weight=None):
N = x.size(0)
if edge_weight is None:
edge_weight = torch.ones(edge_index.size(1))
sech_w = 1.0 / torch.cosh(edge_weight)
row, col = edge_index
out = torch.zeros_like(x)
for i in range(N):
mask = row == i
if mask.sum() > 0:
out[i] = (x[col[mask]] * sech_w[mask].view(-1,1)).sum(0)
deg = torch.zeros(N)
for i in range(N):
deg[i] = (row == i).sum().float()
deg_inv_sqrt = deg.pow(-0.5)
deg_inv_sqrt[deg_inv_sqrt == float('inf')] = 0
out = deg_inv_sqrt.view(-1,1) * out
out = self.linear(out)
return out * (1.0 / torch.cosh(self.inv_e * out))
class SentinelGNN(nn.Module):
def __init__(self, in_ch, hidden, out_ch, layers=2):
super().__init__()
self.convs = nn.ModuleList()
self.convs.append(SechGraphConv(in_ch, hidden))
for _ in range(layers-2):
self.convs.append(SechGraphConv(hidden, hidden))
self.convs.append(SechGraphConv(hidden, out_ch))
def forward(self, x, edge_index, ew=None):
for i, conv in enumerate(self.convs):
x = conv(x, edge_index, ew)
return x
def generate_graph(n_nodes, p_edge, n_features, n_classes):
"""Generate random graph."""
x = torch.randn(n_nodes, n_features)
edge_list = []
for i in range(n_nodes):
for j in range(i+1, n_nodes):
if np.random.rand() < p_edge:
edge_list.append([i, j])
edge_list.append([j, i])
edge_index = torch.tensor(edge_list, dtype=torch.long).t() if edge_list else torch.zeros(2,0, dtype=torch.long)
y = torch.randint(0, n_classes, (n_nodes,))
return x, edge_index, y
def run_gnn(n_nodes, p_edge, n_features, n_classes, n_layers, hidden):
"""Run Sentinel GNN."""
x, edge_index, y = generate_graph(n_nodes, p_edge, n_features, n_classes)
model = SentinelGNN(n_features, hidden, n_classes, n_layers)
out = model(x, edge_index)
pred = out.argmax(dim=1)
# Visualize
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# Graph layout (simple spring)
pos = np.random.randn(n_nodes, 2) * 2
for _ in range(50):
forces = np.zeros_like(pos)
for i in range(n_nodes):
for j in range(n_nodes):
if i != j:
diff = pos[i] - pos[j]
dist = np.linalg.norm(diff) + 1e-8
if dist < 3:
forces[i] += diff / dist * (3 - dist) * 0.1
pos += forces
# Draw graph
ax = axes[0]
for i in range(edge_index.size(1)):
u, v = edge_index[0, i].item(), edge_index[1, i].item()
if u < v:
ax.plot([pos[u,0], pos[v,0]], [pos[u,1], pos[v,1]], 'gray', alpha=0.3, linewidth=0.5)
colors = plt.cm.tab10(np.linspace(0, 1, n_classes))
for c in range(n_classes):
mask = y.numpy() == c
ax.scatter(pos[mask,0], pos[mask,1], c=[colors[c]], s=100, label=f'Class {c}', edgecolors='black')
ax.set_title(f'Graph (N={n_nodes}, E={edge_index.size(1)//2})')
ax.legend()
ax.set_aspect('equal')
ax.axis('off')
# Embedding visualization
ax = axes[1]
emb = out.detach().numpy()
for c in range(n_classes):
mask = y.numpy() == c
if mask.sum() > 0:
ax.scatter(emb[mask, 0], emb[mask, 1] if emb.shape[1] > 1 else emb[mask, 0] * 0,
c=[colors[c]], s=100, label=f'Class {c}', edgecolors='black', alpha=0.7)
ax.set_title('Sentinel GNN Embeddings')
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('/tmp/gnn_viz.png', dpi=150)
plt.close()
stats = f"""
## Sentinel GNN Results
| Property | Value |
|----------|-------|
| Nodes | {n_nodes} |
| Edges | {edge_index.size(1)//2} |
| Features | {n_features} |
| Classes | {n_classes} |
| Layers | {n_layers} |
| Hidden | {hidden} |
| Parameters | {sum(p.numel() for p in model.parameters()):,} |
### Key Innovation
**Hyperbolic message passing**: sech(‖x−y‖) is the natural distance kernel
for hyperbolic geometry — matching brain connectome structure.
"""
return '/tmp/gnn_viz.png', stats
with gr.Blocks(title="Sentinel Graph Neural Network") as demo:
gr.Markdown("""
# 🌐 Sentinel Graph Neural Network
**Hyperbolic message passing with sech kernel.**
Brain connectomes and social networks have hyperbolic structure.
The sech kernel is the natural distance function in hyperbolic space.
""")
with gr.Row():
with gr.Column():
n_nodes = gr.Slider(10, 500, value=100, step=10, label="Nodes")
p_edge = gr.Slider(0.01, 0.5, value=0.05, label="Edge Probability")
n_features = gr.Slider(2, 64, value=8, step=2, label="Node Features")
n_classes = gr.Slider(2, 10, value=3, step=1, label="Classes")
n_layers = gr.Slider(1, 5, value=2, step=1, label="GNN Layers")
hidden = gr.Slider(8, 128, value=32, step=8, label="Hidden Dim")
with gr.Column():
btn = gr.Button("Generate & Run", variant="primary")
output_img = gr.Image()
output_stats = gr.Markdown()
btn.click(run_gnn, [n_nodes, p_edge, n_features, n_classes, n_layers, hidden], [output_img, output_stats])
gr.Markdown("""
## About Sentinel GNN
- **Message kernel**: sech(‖x−y‖) (hyperbolic geometry)
- **Activation**: σ(x) = x·sech(x/e) (theorem-backed gradient)
- **Applications**: Brain connectomes, social networks, molecules, knowledge graphs
[Model Repo](https://huggingface.co/5dimension/sentinel-graph-neural-network)
""")
if __name__ == "__main__":
demo.launch()