-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #30 from vadim-voloshchuk/main
Simple integration of Bellman-Ford algorithm into PyTorch
- Loading branch information
Showing
6 changed files
with
406 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 полезен, когда вас помимо путей интересуют дополнительные характеристики графа, такие как диаметр и эксцентриситет. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
Oops, something went wrong.