Tutorials

This section provides step-by-step tutorials to help you get started with Bio Transformations. We’ll cover basic usage, integration with existing models, and how to use specific bio-inspired features.

Basic Usage

Converting a Simple Model

In this tutorial, we’ll convert a simple PyTorch model to use Bio Transformations.

import torch
import torch.nn as nn
from bio_transformations import BioConverter

# Define a simple model
class SimpleModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc1 = nn.Linear(10, 20)
        self.fc2 = nn.Linear(20, 1)

    def forward(self, x):
        x = torch.relu(self.fc1(x))
        return self.fc2(x)

# Create an instance of the model
model = SimpleModel()

# Create a BioConverter with default parameters
converter = BioConverter()

# Convert the model
bio_model = converter(model)

# Now bio_model can be used like a regular PyTorch model
x = torch.randn(1, 10)
output = bio_model(x)
print(output)

Customizing the Bio-inspired Features

You can customize the bio-inspired features by passing parameters to the BioConverter.

# Create a BioConverter with custom parameters
converter = BioConverter(
    fuzzy_learning_rate_factor_nu=0.16,  # Controls the diversity in learning rates
    dampening_factor=0.6,                # Controls the stability increase during crystallization
    crystal_thresh=4.5e-05,              # Threshold for identifying weights to crystallize
    rejuvenation_parameter_dre=8.0,      # Controls the rate of weight rejuvenation
    weight_splitting_Gamma=2,            # Number of sub-synapses per connection (0 = disabled)
    apply_dales_principle=False          # Whether to enforce Dale's principle
)

# Convert the model with custom parameters
bio_model = converter(model)

Training a Bio-Transformed Model

Here’s how to train a model using Bio Transformations:

import torch.optim as optim

# Assume we have our bio_model from the previous example

# Define loss function and optimizer
criterion = nn.MSELoss()
optimizer = optim.Adam(bio_model.parameters(), lr=0.01)

# Training data (example)
x_train = torch.randn(100, 10)
y_train = torch.randn(100, 1)

# Training loop
for epoch in range(100):  # 100 epochs
    # Forward pass
    optimizer.zero_grad()
    outputs = bio_model(x_train)
    loss = criterion(outputs, y_train)
    loss.backward()

    # Apply bio-inspired modifications
    bio_model.volume_dependent_lr()   # Adjust learning rates based on weight size
    bio_model.fuzzy_learning_rates()  # Apply diverse learning rates
    bio_model.crystallize()           # Stabilize well-optimized weights

    # Update weights
    optimizer.step()

    # Periodically apply weight rejuvenation (e.g., every 10 epochs)
    if epoch % 10 == 0:
        bio_model.rejuvenate_weights()

    if epoch % 10 == 0:
        print(f'Epoch [{epoch+1}/100], Loss: {loss.item():.4f}')

Using Different Distribution Strategies

Bio Transformations supports various distribution strategies for fuzzy learning rates:

from bio_transformations import BioConverter, BioConfig
from bio_transformations.bio_config import Distribution

# Create a model
model = SimpleModel()

# Create a BioConverter with Normal distribution
normal_config = BioConfig(
    fuzzy_lr_distribution=Distribution.NORMAL,
    fuzzy_learning_rate_factor_nu=0.16  # Standard deviation for normal distribution
)
converter = BioConverter(config=normal_config)
bio_model = converter(model)

# Train with normal distribution (just like previous example)
# ...

# Create a BioConverter with Weight-adaptive distribution
weight_config = BioConfig(
    fuzzy_lr_distribution=Distribution.WEIGHT_ADAPTIVE,
    fuzzy_learning_rate_factor_nu=0.16
)
converter = BioConverter(config=weight_config)
bio_model = converter(model)

# Train with weight-adaptive distribution
# (smaller weights get more variability than larger weights)
# ...

Using Activity-Dependent Learning Rates

For activity-dependent learning rates, you need to pass the input tensor to the update_fuzzy_learning_rates method:

from bio_transformations import BioConverter, BioConfig
from bio_transformations.bio_config import Distribution

# Create a model
model = SimpleModel()

# Create a BioConverter with Activity-dependent distribution
activity_config = BioConfig(
    fuzzy_lr_distribution=Distribution.ACTIVITY,
    fuzzy_lr_dynamic=True,  # Important: must be True for activity-dependent rates
    fuzzy_learning_rate_factor_nu=0.16
)
converter = BioConverter(config=activity_config)
bio_model = converter(model)

# Training loop
for epoch in range(100):
    # Forward pass
    optimizer.zero_grad()
    outputs = bio_model(x_train)  # Pass input through the model
    loss = criterion(outputs, y_train)
    loss.backward()

    # Update fuzzy learning rates based on activity
    # This reads the activation patterns from the last forward pass
    bio_model.update_fuzzy_learning_rates(x_train)

    # Apply other bio-inspired modifications
    bio_model.fuzzy_learning_rates()
    optimizer.step()

Applying Dale’s Principle

To enforce Dale’s principle (neurons are either excitatory or inhibitory):

from bio_transformations import BioConverter, BioConfig

# Create a model
model = SimpleModel()

# Create a BioConverter with Dale's principle enabled
dales_config = BioConfig(
    apply_dales_principle=True
)
converter = BioConverter(config=dales_config)
bio_model = converter(model)

# Training loop
for epoch in range(100):
    # Standard training steps
    # ...

    # Enforce Dale's principle after each update
    bio_model.enforce_dales_principle()

    # Continue with other modifications
    # ...

Using Bio Transformations with CNNs

Bio Transformations works with convolutional neural networks too:

import torch.nn as nn
from bio_transformations import BioConverter

# Define a simple CNN
class SimpleCNN(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Conv2d(1, 16, kernel_size=3, padding=1)
        self.pool = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(16, 32, kernel_size=3, padding=1)
        self.fc1 = nn.Linear(32 * 7 * 7, 128)
        self.fc2 = nn.Linear(128, 10)

    def forward(self, x):
        x = self.pool(torch.relu(self.conv1(x)))
        x = self.pool(torch.relu(self.conv2(x)))
        x = x.view(-1, 32 * 7 * 7)
        x = torch.relu(self.fc1(x))
        return self.fc2(x)

# Create and convert the CNN
cnn_model = SimpleCNN()
converter = BioConverter()
bio_cnn = converter(cnn_model)

# Use bio_cnn just like a regular CNN

Troubleshooting Common Issues

Here are solutions to common issues you might encounter when using Bio Transformations:

RuntimeError: “No gradients found for the weights”

This error occurs when calling fuzzy_learning_rates(), volume_dependent_lr(), or crystallize() before performing a backward pass:

# Problem:
outputs = bio_model(inputs)
bio_model.fuzzy_learning_rates()  # Error! No gradients yet

# Solution:
outputs = bio_model(inputs)
loss = criterion(outputs, targets)
optimizer.zero_grad()
loss.backward()  # This generates gradients
bio_model.fuzzy_learning_rates()  # Now works correctly

ValueError: “weight_splitting_Gamma must evenly divide the number of features”

This occurs when the number of features in a layer is not divisible by weight_splitting_Gamma:

# Problem:
model = nn.Linear(10, 15)  # 15 is not divisible by weight_splitting_Gamma=2
converter = BioConverter(weight_splitting_Gamma=2)
bio_model = converter(model)  # Error!

# Solution 1: Choose a compatible weight_splitting_Gamma
converter = BioConverter(weight_splitting_Gamma=3)  # 15 is divisible by 3

# Solution 2: Adjust your model architecture
model = nn.Linear(10, 16)  # 16 is divisible by 2
converter = BioConverter(weight_splitting_Gamma=2)

AttributeError: “Can not enforce dales principle without apply_dales_principle set True”

This occurs when trying to call enforce_dales_principle() but Dale’s principle is not enabled:

# Problem:
converter = BioConverter()  # apply_dales_principle defaults to False
bio_model = converter(model)
bio_model.enforce_dales_principle()  # Error!

# Solution:
converter = BioConverter(apply_dales_principle=True)
bio_model = converter(model)
bio_model.enforce_dales_principle()  # Now works correctly

These tutorials should help you get started with Bio Transformations. For more advanced usage and customization options, please refer to the Advanced Usage guide.