| import torch
|
| import torch.nn as nn
|
| import torch.nn.functional as F
|
|
|
| class GraphConvLayer(nn.Module):
|
| def __init__(self, in_features, out_features, dropout=0.5):
|
| super(GraphConvLayer, self).__init__()
|
| self.linear = nn.Linear(in_features, out_features, bias=False)
|
| self.bias = nn.Parameter(torch.zeros(out_features))
|
| self.dropout = nn.Dropout(dropout)
|
|
|
| def forward(self, H, A):
|
| """
|
| H: (batch, N, in_features)
|
| A: (batch, N, N) - Adjacency matrix (assumed to be normalized or with self-loops)
|
| """
|
| H = self.dropout(H)
|
|
|
| H_trans = self.linear(H)
|
|
|
| H_next = torch.bmm(A, H_trans) + self.bias
|
| return H_next
|
|
|
| class GNNEmbedding(nn.Module):
|
| def __init__(self, vocab_size=256, embedding_dim=64, hidden_dim=64, output_dim=128, dropout=0.5):
|
| """
|
| Lightweight Graph Convolutional Network (GCN) for DOM Structure representation.
|
| Takes node indices and dense adjacency matrices.
|
| """
|
| super(GNNEmbedding, self).__init__()
|
| self.embedding = nn.Embedding(vocab_size, embedding_dim, padding_idx=0)
|
|
|
|
|
| self.gcn1 = GraphConvLayer(embedding_dim, hidden_dim, dropout=dropout)
|
| self.gcn2 = GraphConvLayer(hidden_dim, hidden_dim, dropout=dropout)
|
|
|
|
|
| self.fc = nn.Linear(hidden_dim, output_dim)
|
|
|
| def forward(self, nodes, adj):
|
| """
|
| nodes: (batch_size, max_nodes) - Node features (byte/char encoded indices)
|
| adj: (batch_size, max_nodes, max_nodes) - Dense adjacency matrix
|
| Returns: feature vector of size (batch_size, output_dim)
|
| """
|
|
|
| I = torch.eye(adj.size(-1), device=adj.device).unsqueeze(0).expand_as(adj)
|
| A_tilde = adj + I
|
|
|
|
|
| D_tilde = torch.sum(A_tilde, dim=-1)
|
| D_inv_sqrt = torch.pow(D_tilde, -0.5)
|
| D_inv_sqrt = torch.where(torch.isinf(D_inv_sqrt), torch.zeros_like(D_inv_sqrt), D_inv_sqrt)
|
| D_inv_sqrt_mat = torch.diag_embed(D_inv_sqrt)
|
|
|
| A_hat = torch.bmm(torch.bmm(D_inv_sqrt_mat, A_tilde), D_inv_sqrt_mat)
|
|
|
|
|
| H = self.embedding(nodes)
|
|
|
|
|
| H = self.gcn1(H, A_hat)
|
| H = F.relu(H)
|
|
|
|
|
| H = self.gcn2(H, A_hat)
|
| H = F.relu(H)
|
|
|
|
|
|
|
| H_permuted = H.permute(0, 2, 1)
|
| pooled = F.max_pool1d(H_permuted, H_permuted.size(2)).squeeze(-1)
|
|
|
|
|
| embeddings = self.fc(pooled)
|
|
|
| return embeddings
|
|
|
| if __name__ == "__main__":
|
|
|
| model = GNNEmbedding(vocab_size=256, embedding_dim=64, hidden_dim=64, output_dim=128, dropout=0.5)
|
| batch_size = 4
|
| max_nodes = 100
|
|
|
| mock_nodes = torch.randint(0, 256, (batch_size, max_nodes))
|
| mock_adj = torch.randint(0, 2, (batch_size, max_nodes, max_nodes)).float()
|
|
|
| device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| model = model.to(device)
|
| mock_nodes = mock_nodes.to(device)
|
| mock_adj = mock_adj.to(device)
|
|
|
| output = model(mock_nodes, mock_adj)
|
| print(f"Device: {device}")
|
| print(f"Nodes shape: {mock_nodes.shape}")
|
| print(f"Adj shape: {mock_adj.shape}")
|
| print(f"Output embedding shape: {output.shape}")
|
|
|