Skip to content

Commit

Permalink
Merge pull request #30 from vadim-voloshchuk/main
Browse files Browse the repository at this point in the history
Simple integration of Bellman-Ford algorithm into PyTorch
  • Loading branch information
bda82 authored Dec 15, 2024
2 parents f559af3 + 419c456 commit e407a14
Show file tree
Hide file tree
Showing 6 changed files with 406 additions and 0 deletions.
44 changes: 44 additions & 0 deletions raw_bellman_ford/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# BellmanFordLayerModified

`BellmanFordLayer` - это PyTorch-слой, предоставляющий результаты алгоритма Беллмана-Форда для анализа графовых данных. Он возвращает матрицу расстояний и матрицу предшественников, которые могут быть использованы для поиска кратчайших путей в графе. Также этот слой определяет наличие отрицательных циклов в графе.

`BellmanFordLayerModified` - это PyTorch слой, реализующий модифицированный алгоритм Беллмана-Форда для анализа свойств графов и извлечения признаков из графовой структуры. Этот слой может использоваться в задачах графового машинного обучения, таких как предсказание путей и анализ графовых структур.

## Использование

```python
import torch
from layers.bellman_ford_modified import BellmanFordLayerModified

# Инициализация слоя с указанием количества узлов и числа признаков
num_nodes = 4
num_features = 5
bellman_ford_layer = BellmanFordLayerModified(num_nodes, num_features)

# Определение матрицы смежности графа и начального узла
adj_matrix = torch.tensor([[0, 2, float('inf'), 1],
[float('inf'), 0, -1, float('inf')],
[float('inf'), float('inf'), 0, -2],
[float('inf'), float('inf'), float('inf'), 0]])
source_node = 0

# Вычисление признаков графа, диаметра и эксцентриситета
node_features, diameter, eccentricity = bellman_ford_layer(adj_matrix, source_node)

print("Node Features:")
print(node_features)
print("Graph Diameter:", diameter)
print("Graph Eccentricity:", eccentricity)
```

## Параметры слоя

- `num_nodes`: Количество узлов в графе.
- `num_features`: Количество признаков, извлекаемых из графа.
- `edge_weights`: Веса ребер между узлами (обучаемые параметры).
- `node_embedding`: Вложение узлов для извлечения признаков.

## Применение:

- BellmanFordLayer полезен, когда вам нужны результаты алгоритма Беллмана-Форда для выполнения других операций или анализа графа.
- BellmanFordLayerModified полезен, когда вас помимо путей интересуют дополнительные характеристики графа, такие как диаметр и эксцентриситет.
97 changes: 97 additions & 0 deletions raw_bellman_ford/layers/bellman_ford_modified.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import torch
import torch.nn as nn

class BellmanFordLayerModified(nn.Module):
def __init__(self, num_nodes, num_features):
super(BellmanFordLayerModified, self).__init__()
self.num_nodes = num_nodes
self.num_features = num_features

self.edge_weights = nn.Parameter(torch.rand(num_nodes, num_nodes))
self.node_embedding = nn.Embedding(num_nodes, num_features)

def forward(self, adj_matrix, source_node):
distances = torch.full((self.num_nodes,), float('inf'))
predecessors = torch.full((self.num_nodes,), -1)
distances[source_node] = 0

for _ in range(self.num_nodes - 1):
for s in range(self.num_nodes):
for d in range(self.num_nodes):
if s != d and adj_matrix[s][d] != float('inf'):
if distances[s] + adj_matrix[s][d] < distances[d]:
distances[d] = distances[s] + adj_matrix[s][d]
predecessors[d] = s

graph_diameter = torch.max(distances).item()
graph_eccentricity = torch.max(distances[source_node]).item()

node_features = self.node_embedding(torch.arange(self.num_nodes))
node_features = torch.cat([node_features, distances.unsqueeze(1)], dim=1)

return node_features, graph_diameter, graph_eccentricity

if __name__ == "__main__":
num_nodes_1 = 4
adj_matrix_1 = torch.tensor([[0, 2, float('inf'), 1],
[float('inf'), 0, -1, float('inf')],
[float('inf'), float('inf'), 0, -2],
[float('inf'), float('inf'), float('inf'), 0]])
source_node_1 = 0

bellman_ford_layer_1 = BellmanFordLayerModified(num_nodes_1, num_features=5)
node_features_1, diameter_1, eccentricity_1 = bellman_ford_layer_1(adj_matrix_1, source_node_1)

print("Example 1:")
print("Node Features:")
print(node_features_1)
print("Graph Diameter:", diameter_1)
print("Graph Eccentricity:", eccentricity_1)

num_nodes_2 = 4
adj_matrix_2 = torch.tensor([[0, 2, 1, float('inf')],
[float('inf'), 0, -1, float('inf')],
[float('inf'), float('inf'), 0, -2],
[float('inf'), float('inf'), float('inf'), 0]])
source_node_2 = 0

bellman_ford_layer_2 = BellmanFordLayerModified(num_nodes_2, num_features=5)
node_features_2, diameter_2, eccentricity_2 = bellman_ford_layer_2(adj_matrix_2, source_node_2)

print("\nExample 2:")
print("Node Features:")
print(node_features_2)
print("Graph Diameter:", diameter_2)
print("Graph Eccentricity:", eccentricity_2)

num_nodes_3 = 4
adj_matrix_3 = torch.tensor([[0, 2, 1, 3],
[-1, 0, -1, 4],
[5, 2, 0, -2],
[2, 3, 1, 0]])
source_node_3 = 0

bellman_ford_layer_3 = BellmanFordLayerModified(num_nodes_3, num_features=5)
node_features_3, diameter_3, eccentricity_3 = bellman_ford_layer_3(adj_matrix_3, source_node_3)

print("\nExample 3:")
print("Node Features:")
print(node_features_3)
print("Graph Diameter:", diameter_3)
print("Graph Eccentricity:", eccentricity_3)

num_nodes_4 = 4
adj_matrix_4 = torch.tensor([[0, 2, 0, 1],
[0, 0, 0, 0],
[0, 0, 0, 2],
[0, 0, 0, 0]])
source_node_4 = 0

bellman_ford_layer_4 = BellmanFordLayerModified(num_nodes_4, num_features=5)
node_features_4, diameter_4, eccentricity_4 = bellman_ford_layer_4(adj_matrix_4, source_node_4)

print("\nExample 4:")
print("Node Features:")
print(node_features_4)
print("Graph Diameter:", diameter_4)
print("Graph Eccentricity:", eccentricity_4)
73 changes: 73 additions & 0 deletions raw_bellman_ford/layers/bellman_ford_orig.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import torch
import torch.nn as nn

class BellmanFordLayer(nn.Module):
def __init__(self, num_nodes):
super(BellmanFordLayer, self).__init__()
self.num_nodes = num_nodes

def forward(self, adj_matrix, source_node):
distances = torch.full((self.num_nodes, self.num_nodes), float('inf'))
predecessors = torch.zeros((self.num_nodes, self.num_nodes), dtype=torch.long)

distances[source_node, 0] = 0

for i in range(1, self.num_nodes):
for u in range(self.num_nodes):
for v in range(self.num_nodes):
w = adj_matrix[u, v]
if distances[u, i - 1] + w < distances[v, i]:
distances[v, i] = distances[u, i - 1] + w
predecessors[v, i] = u

has_negative_cycle = False
for u in range(self.num_nodes):
for v in range(self.num_nodes):
w = adj_matrix[u, v]
if distances[u, self.num_nodes - 1] + w < distances[v, self.num_nodes - 1]:
has_negative_cycle = True
break
if has_negative_cycle:
break

return distances, predecessors, has_negative_cycle

if __name__ == "__main__":
num_nodes = 4
source_node = 0

adj_matrix1 = torch.tensor([[0, 2, 1, float('inf')],
[float('inf'), 0, -1, float('inf')],
[float('inf'), float('inf'), 0, -2],
[float('inf'), float('inf'), float('inf'), 0]])

adj_matrix2 = torch.tensor([[0, 2, float('inf'), 1],
[1, 0, -1, float('inf')],
[float('inf'), float('inf'), 0, -2],
[float('inf'), 1, float('inf'), 0]])

adj_matrix3 = torch.tensor([[0, 2, 1, float('inf')],
[float('inf'), 0, -1, float('inf')],
[3, float('inf'), 0, -2],
[float('inf'), 1, float('inf'), 0]])

bellman_ford_layer = BellmanFordLayer(num_nodes)

distances1, predecessors1, has_negative_cycle1 = bellman_ford_layer(adj_matrix1, source_node)
distances2, predecessors2, has_negative_cycle2 = bellman_ford_layer(adj_matrix2, source_node)
distances3, predecessors3, has_negative_cycle3 = bellman_ford_layer(adj_matrix3, source_node)

if has_negative_cycle1:
print("Example 1: The graph contains a negative weight cycle")
else:
print("Example 1: Shortest distances from the source node:", distances1[source_node, -1])

if has_negative_cycle2:
print("Example 2: The graph contains a negative weight cycle")
else:
print("Example 2: Shortest distances from the source node:", distances2[source_node, -1])

if has_negative_cycle3:
print("Example 3: The graph contains a negative weight cycle")
else:
print("Example 3: Shortest distances from the source node:", distances3[source_node, -1])
62 changes: 62 additions & 0 deletions raw_bellman_ford/node_classification_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import torch
import torch.nn as nn
import torch.optim as optim
from layers.bellman_ford_orig import BellmanFordLayer

class NodeClassificationGNN(nn.Module):
def __init__(self, num_nodes, num_features, num_classes):
super(NodeClassificationGNN, self).__init__()
self.num_nodes = num_nodes
self.num_features = num_features
self.num_classes = num_classes

self.bellman_ford_layer = BellmanFordLayer(num_nodes)
self.node_embedding = nn.Embedding(num_nodes, num_features)
self.fc = nn.Linear(num_features + num_nodes, num_classes)

def forward(self, adj_matrix, source_node):
distances, predecessors, has_negative_cycle = self.bellman_ford_layer(adj_matrix, source_node)

node_features = self.node_embedding(torch.arange(self.num_nodes))
node_features = torch.cat([node_features, distances], dim=1)

output = self.fc(node_features)

return output, has_negative_cycle

if __name__ == "__main__":
num_nodes = 4
source_node = 0

adj_matrix = torch.tensor([[0, 2, 0, 1],
[0, 0, -1, 0],
[0, 0, 0, -2],
[0, 0, 0, 0]])

gnn_model = NodeClassificationGNN(num_nodes, num_features=5, num_classes=2)

criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(gnn_model.parameters(), lr=0.001)

num_epochs = 100
for epoch in range(num_epochs):
optimizer.zero_grad()
output, has_negative_cycle = gnn_model(adj_matrix, source_node)

labels = torch.tensor([0, 1, 0, 1], dtype=torch.long)

loss = criterion(output, labels)
loss.backward()
optimizer.step()

print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {loss.item():.4f}, Negative Cycle: {has_negative_cycle}')

test_adj_matrix = torch.tensor([[0, 1, 1, 0],
[0, 0, 0, 1],
[1, 0, 0, 0],
[0, 0, 1, 0]])

test_source_node = 0

test_output, has_negative_cycle = gnn_model(test_adj_matrix, test_source_node)
print("Test Output:", test_output, "Negative Cycle:", has_negative_cycle)
57 changes: 57 additions & 0 deletions raw_bellman_ford/predict_gnn_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import torch
import torch.nn as nn
import torch.optim as optim
from layers.bellman_ford_modified import BellmanFordLayerModified

class GraphPathPredictionModel(nn.Module):
def __init__(self, num_nodes, num_features, hidden_dim):
super(GraphPathPredictionModel, self).__init__()

self.bellman_ford_layer = BellmanFordLayerModified(num_nodes, num_features)
self.linear = nn.Linear(num_features + 1, hidden_dim)

def forward(self, adj_matrix, source_node):
node_features, _, _ = self.bellman_ford_layer(adj_matrix, source_node)

predictions = self.linear(node_features)

return predictions

if __name__ == "__main__":
num_nodes = 6
num_features = 5
hidden_dim = 2

adj_matrix = torch.tensor([[0, 1, 1, 0, 0, 0],
[0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0]])
source_node = 0

model = GraphPathPredictionModel(num_nodes, num_features, hidden_dim)

criterion = nn.BCEWithLogitsLoss()
optimizer = optim.SGD(model.parameters(), lr=0.1)

labels = torch.tensor([1, 1, 1, 1, 0, 0], dtype=torch.float32).view(-1, 1).repeat(1, 2)

num_epochs = 1000
for epoch in range(num_epochs):
predictions = model(adj_matrix, source_node)

predictions = torch.sigmoid(predictions)

loss = criterion(predictions, labels)

optimizer.zero_grad()
loss.backward()
optimizer.step()

print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {loss.item():.4f}')

with torch.no_grad():
predictions = model(adj_matrix, source_node)
predicted_labels = (predictions > 0.5).type(torch.float32)
print("Predicted Labels:", predicted_labels)
Loading

0 comments on commit e407a14

Please sign in to comment.