Pytorch Generator

A code generator for PyTorch is also provided by BESSER. PyTorch is an open-source machine learning framework developed by Meta (Facebook) that is widely used for deep learning. Our Generator transforms B-UML Neural Network models into PyTorch code, allowing you to create neural networks based on your B-UML specifications.

To use the PyTorch generator, you need to create a PytorchGenerator object, provide the B-UML Neural Network model, and use the generate method as follows:

from besser.generators.nn.pytorch.pytorch_code_generator import PytorchGenerator

pytorch_model = PytorchGenerator(
    model=nn_model, output_dir="output_folder", generation_type="subclassing"
)
pytorch_model.generate()

Parameters

  • model: The neural network model.

  • output_dir: The name of the output directory where the generated file will be placed.

  • generation_type: The type of NN architecture. Either subclassing or sequential.

  • channel_last: When True, PyTorch convolutional layers permute their input and output to match the TensorFlow channel-last convention. Default False.

The filename embeds the generation type, so a PytorchGenerator invoked with generation_type="subclassing" produces pytorch_nn_subclassing.py, and generation_type="sequential" produces pytorch_nn_sequential.py.

Web Modeling Editor Support

Neural networks can also be designed visually in the BESSER Web Modeling Editor using the NNDiagram type. The backend converts the diagram into an NN metamodel instance and passes it to the PyTorch generator. From the editor’s Generate menu you can choose between the Subclassing and Sequential variants; the diagram is validated through NN.validate() before code is produced.

Output

The generated file pytorch_nn_<generation_type>.py contains:

  • Imports: PyTorch and supporting modules required by the generated code.

  • Network architecture: in subclassing mode, a NeuralNetwork(nn.Module) class with an __init__ that instantiates the layers and a forward method that chains them. In sequential mode, an nn.Sequential definition.

  • Training and evaluation block (emitted only when a Training Dataset is attached to the NN): dataset loading, loss function and optimizer setup, the training loop, evaluation against the test dataset, and saving the trained model.

The generated output for the tutorial example is shown below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
"""PyTorch code generated based on BUML."""
import torch
from datetime import datetime

from torch import nn
import torch.nn.functional as F

from torchvision import datasets, transforms

from sklearn.metrics import classification_report 

# Define the network architecture
class NeuralNetwork(nn.Module):
    def __init__(self):
        super().__init__()
        self.l1 = nn.Conv2d(in_channels=3, out_channels=32, kernel_size=(3, 3), stride=(1, 1), padding=0, dilation=1, groups=1, bias=True)
        self.actv_func_relu = nn.ReLU()
        self.l2 = nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2), padding=0)
        self.l3 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=(3, 3), stride=(1, 1), padding=0, dilation=1, groups=1, bias=True)
        self.l4 = nn.MaxPool2d(kernel_size=(2, 2), stride=(2, 2), padding=0)
        self.l5 = nn.Conv2d(in_channels=64, out_channels=64, kernel_size=(3, 3), stride=(1, 1), padding=0, dilation=1, groups=1, bias=True)
        self.l6 = nn.Flatten(start_dim=1, end_dim=-1)
        self.l7 = nn.Linear(in_features=1024, out_features=64, bias=True)
        self.l8 = nn.Linear(in_features=64, out_features=10, bias=True)

    def forward(self, x):
        x = self.l1(x)
        x = self.actv_func_relu(x)
        x = self.l2(x)
        x = self.l3(x)
        x = self.actv_func_relu(x)
        x = self.l4(x)
        x = self.l5(x)
        x = self.actv_func_relu(x)
        x = self.l6(x)
        x = self.l7(x)
        x = self.actv_func_relu(x)
        x = self.l8(x)
        return x

def prepare_datasets():
    """Prepare training and testing datasets."""
    IMAGE_SIZE = (32, 32)
    transform = transforms.Compose([
        transforms.Resize(IMAGE_SIZE),
    	transforms.ToTensor()
        ])

    # Load the training dataset
    # Directory structure: root/class1/img1.jpg, root/class1/img2.jpg,
    # root/class2/img1.jpg, ...
    train_dataset = datasets.ImageFolder(
        root="dataset/cifar10/train", transform=transform)

    # Load the testing dataset that is in a similar directory structure
    test_dataset = datasets.ImageFolder(
        root="dataset/cifar10/test", transform=transform)

    # Create data loaders
    train_loader = torch.utils.data.DataLoader(
        dataset=train_dataset, batch_size=32, shuffle=True)
    test_loader = torch.utils.data.DataLoader(
        dataset=test_dataset, batch_size=32, shuffle=False)

    return train_loader, test_loader

def train(model, train_loader, criterion, optimizer, epochs):
    """Train the neural network."""
    print('##### Training the model')
    for epoch in range(epochs):
        # Initialize the running loss for the current epoch
        running_loss = 0.0
        total_loss = 0.0
        # Iterate over mini-batches of training data
        for i, data in enumerate(train_loader, 0):
            inputs, labels = data
            # Zero the gradients to prepare for backward pass
            optimizer.zero_grad()
            outputs = model(inputs)
            # Compute the loss
            loss = criterion(outputs, labels)
            loss.backward()
            # Update model parameters based on computed gradients
            optimizer.step()
            running_loss += loss.item()
            total_loss += loss.item()
            if i % 200 == 199:    # Print every 200 mini-batches
                print(
                    f"[{epoch + 1}, {i + 1:5d}] loss: {running_loss / 200:.3f}"
                )
                running_loss = 0.0
        print(
            f"[{epoch + 1}] overall loss for epoch: "
            f"{total_loss / len(train_loader):.3f}"
        )
    print('Training finished')

def evaluate(model, test_loader, criterion):
    """Evaluate the neural network."""
    print('##### Evaluating the model')
    # Disable gradient calculation during inference
    with torch.no_grad():
        # Initialize lists to store predicted and true labels
        predicted_labels = []
        true_labels = []
        test_loss = 0.0
        for data in test_loader:
            # Extract inputs and labels from the data batch
            inputs, labels = data
            true_labels.extend(labels)
            # Forward pass
            outputs = model(inputs)
            _, predicted = torch.max(outputs.data, 1)
            predicted_labels.extend(predicted)
            test_loss += criterion(outputs, labels).item()

    average_loss = test_loss / len(test_loader)
    print(f"Test Loss: {average_loss:.3f}")

    # Calculate the metrics
    metrics = ['f1-score']
    report = classification_report(true_labels, predicted_labels, output_dict=True)
    for metric in metrics:
        metric_list = []
        for class_label in report.keys():
            if class_label not in ('macro avg', 'weighted avg', 'accuracy'):
                print(f"{metric.capitalize()} for class {class_label}:",
                      report[class_label][metric])
                metric_list.append(report[class_label][metric])
        metric_value = sum(metric_list) / len(metric_list)
        print(f"Average {metric.capitalize()}: {metric_value:.2f}")
        print(f"Accuracy: {report['accuracy']}")

if __name__ == "__main__":
    # Prepare datasets
    train_loader, test_loader = prepare_datasets()

    # Define the network, loss function, and optimizer
    my_model = NeuralNetwork()
    criterion = nn.CrossEntropyLoss()
    optimizer = torch.optim.Adam(my_model.parameters(), lr=0.001)

    # Train the neural network
    train(my_model, train_loader, criterion, optimizer, 10)

    # Evaluate the neural network
    evaluate(my_model, test_loader, criterion)

    # Save the neural network
    print('##### Saving the model')
    torch.save(my_model, f"my_model_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pth")
    print("The model is saved successfully")