# gaitsetpy.classification.models.bilstm

View Source

      1import torch
      2import torch.nn as nn
      3import torch.optim as optim
      4import numpy as np
      5import joblib
      6from typing import List, Dict, Any, Optional, Union
      7from ...core.base_classes import BaseClassificationModel
      8from ..utils.preprocess import preprocess_features
      9from sklearn.model_selection import train_test_split
     10from sklearn.metrics import accuracy_score, confusion_matrix, classification_report
     11
     12class BiLSTMNet(nn.Module):
     13    def __init__(self, input_size, hidden_size, num_layers, num_classes, dropout=0.2):
     14        super(BiLSTMNet, self).__init__()
     15        self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True, dropout=dropout, bidirectional=True)
     16        self.fc = nn.Linear(hidden_size * 2, num_classes)
     17    def forward(self, x):
     18        out, _ = self.lstm(x)
     19        out = out[:, -1, :]
     20        out = self.fc(out)
     21        return out
     22
     23class BiLSTMModel(BaseClassificationModel):
     24    """
     25    Bidirectional LSTM classification model using PyTorch.
     26    Implements the BaseClassificationModel interface.
     27    """
     28    def __init__(self, input_size=10, hidden_size=64, num_layers=1, num_classes=2, lr=0.001, epochs=20, batch_size=32, device=None):
     29        super().__init__(
     30            name="bilstm",
     31            description="Bidirectional LSTM classifier for gait data classification"
     32        )
     33        self.config = {
     34            'input_size': input_size,
     35            'hidden_size': hidden_size,
     36            'num_layers': num_layers,
     37            'num_classes': num_classes,
     38            'lr': lr,
     39            'epochs': epochs,
     40            'batch_size': batch_size
     41        }
     42        self.device = device or ("cuda" if torch.cuda.is_available() else "cpu")
     43        self.model = BiLSTMNet(input_size, hidden_size, num_layers, num_classes).to(self.device)
     44        self.epochs = epochs
     45        self.batch_size = batch_size
     46        self.trained = False
     47        self.feature_names = []
     48        self.class_names = []
     49
     50    def train(self, features: List[Dict], **kwargs):
     51        X, y = preprocess_features(features)
     52        X = X.reshape((X.shape[0], 1, X.shape[1]))
     53        self.feature_names = [f"feature_{i}" for i in range(X.shape[2])]
     54        self.class_names = list(set(y))
     55        test_size = kwargs.get('test_size', 0.2)
     56        validation_split = kwargs.get('validation_split', True)
     57        if validation_split:
     58            X_train, X_test, y_train, y_test = train_test_split(
     59                X, y, test_size=test_size, random_state=42
     60            )
     61            self.X_test = X_test
     62            self.y_test = y_test
     63        else:
     64            X_train, y_train = X, y
     65        X_train = torch.tensor(X_train, dtype=torch.float32).to(self.device)
     66        y_train = torch.tensor(y_train, dtype=torch.long).to(self.device)
     67        criterion = nn.CrossEntropyLoss()
     68        optimizer = optim.Adam(self.model.parameters(), lr=self.config['lr'])
     69        for epoch in range(self.epochs):
     70            self.model.train()
     71            optimizer.zero_grad()
     72            outputs = self.model(X_train)
     73            loss = criterion(outputs, y_train)
     74            loss.backward()
     75            optimizer.step()
     76            if (epoch+1) % 5 == 0 or epoch == 0:
     77                print(f"Epoch [{epoch+1}/{self.epochs}], Loss: {loss.item():.4f}")
     78        self.trained = True
     79        print("BiLSTM model trained successfully.")
     80
     81    def predict(self, features: List[Dict], **kwargs) -> np.ndarray:
     82        if not self.trained:
     83            raise ValueError("Model must be trained before making predictions")
     84        X, _ = preprocess_features(features)
     85        X = X.reshape((X.shape[0], 1, X.shape[1]))
     86        X = torch.tensor(X, dtype=torch.float32).to(self.device)
     87        self.model.eval()
     88        with torch.no_grad():
     89            outputs = self.model(X)
     90            _, predicted = torch.max(outputs.data, 1)
     91        return predicted.cpu().numpy()
     92
     93    def evaluate(self, features: List[Dict], **kwargs) -> Dict[str, float]:
     94        if not self.trained:
     95            raise ValueError("Model must be trained before evaluation")
     96        if hasattr(self, 'X_test') and hasattr(self, 'y_test'):
     97            X_test, y_test = self.X_test, self.y_test
     98        else:
     99            X_test, y_test = preprocess_features(features)
    100            X_test = X_test.reshape((X_test.shape[0], 1, X_test.shape[1]))
    101        X_test = torch.tensor(X_test, dtype=torch.float32).to(self.device)
    102        y_test = np.array(y_test)
    103        self.model.eval()
    104        with torch.no_grad():
    105            outputs = self.model(X_test)
    106            _, y_pred = torch.max(outputs.data, 1)
    107        y_pred = y_pred.cpu().numpy()
    108        accuracy = accuracy_score(y_test, y_pred)
    109        conf_matrix = confusion_matrix(y_test, y_pred)
    110        metrics = {
    111            'accuracy': accuracy,
    112            'confusion_matrix': conf_matrix.tolist()
    113        }
    114        detailed_report = kwargs.get('detailed_report', False)
    115        if detailed_report:
    116            class_report = classification_report(y_test, y_pred, output_dict=True)
    117            metrics['classification_report'] = class_report
    118        return metrics
    119
    120    def save_model(self, filepath: str):
    121        if not self.trained:
    122            raise ValueError("Model must be trained before saving")
    123        torch.save({
    124            'model_state_dict': self.model.state_dict(),
    125            'config': self.config,
    126            'feature_names': self.feature_names,
    127            'class_names': self.class_names,
    128            'trained': self.trained
    129        }, filepath)
    130        print(f"BiLSTM model saved to {filepath}")
    131
    132    def load_model(self, filepath: str):
    133        checkpoint = torch.load(filepath, map_location=self.device)
    134        self.model = BiLSTMNet(
    135            self.config['input_size'],
    136            self.config['hidden_size'],
    137            self.config['num_layers'],
    138            self.config['num_classes']
    139        ).to(self.device)
    140        self.model.load_state_dict(checkpoint['model_state_dict'])
    141        self.config = checkpoint.get('config', self.config)
    142        self.feature_names = checkpoint.get('feature_names', [])
    143        self.class_names = checkpoint.get('class_names', [])
    144        self.trained = checkpoint.get('trained', True)
    145        print(f"BiLSTM model loaded from {filepath}")

class BiLSTMNet(torch.nn.modules.module.Module): View Source

    13class BiLSTMNet(nn.Module):
    14    def __init__(self, input_size, hidden_size, num_layers, num_classes, dropout=0.2):
    15        super(BiLSTMNet, self).__init__()
    16        self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True, dropout=dropout, bidirectional=True)
    17        self.fc = nn.Linear(hidden_size * 2, num_classes)
    18    def forward(self, x):
    19        out, _ = self.lstm(x)
    20        out = out[:, -1, :]
    21        out = self.fc(out)
    22        return out

Base class for all neural network modules.

Your models should also subclass this class.

Modules can also contain other Modules, allowing them to be nested in a tree structure. You can assign the submodules as regular attributes::

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

    class Model(nn.Module):
        def __init__(self) -> None:
            super().__init__()
            self.conv1 = nn.Conv2d(1, 20, 5)
            self.conv2 = nn.Conv2d(20, 20, 5)

        def forward(self, x):
            x = F.relu(self.conv1(x))
            return F.relu(self.conv2(x))

Submodules assigned in this way will be registered, and will also have their parameters converted when you call `to()`, etc.

As per the example above, an `__init__()` call to the parent class must be made before assignment on the child.

:ivar training: Boolean represents whether this module is in training or evaluation mode. :vartype training: bool

BiLSTMNet(input_size, hidden_size, num_layers, num_classes, dropout=0.2) View Source

    14    def __init__(self, input_size, hidden_size, num_layers, num_classes, dropout=0.2):
    15        super(BiLSTMNet, self).__init__()
    16        self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True, dropout=dropout, bidirectional=True)
    17        self.fc = nn.Linear(hidden_size * 2, num_classes)

Initialize internal Module state, shared by both nn.Module and ScriptModule.

lstm
fc
def forward(self, x): View Source

    18    def forward(self, x):
    19        out, _ = self.lstm(x)
    20        out = out[:, -1, :]
    21        out = self.fc(out)
    22        return out

Define the computation performed at every call.

Should be overridden by all subclasses.

Although the recipe for forward pass needs to be defined within this function, one should call the `Module` instance afterwards instead of this since the former takes care of running the registered hooks while the latter silently ignores them.

class BiLSTMModel(gaitsetpy.core.base_classes.BaseClassificationModel): View Source

     24class BiLSTMModel(BaseClassificationModel):
     25    """
     26    Bidirectional LSTM classification model using PyTorch.
     27    Implements the BaseClassificationModel interface.
     28    """
     29    def __init__(self, input_size=10, hidden_size=64, num_layers=1, num_classes=2, lr=0.001, epochs=20, batch_size=32, device=None):
     30        super().__init__(
     31            name="bilstm",
     32            description="Bidirectional LSTM classifier for gait data classification"
     33        )
     34        self.config = {
     35            'input_size': input_size,
     36            'hidden_size': hidden_size,
     37            'num_layers': num_layers,
     38            'num_classes': num_classes,
     39            'lr': lr,
     40            'epochs': epochs,
     41            'batch_size': batch_size
     42        }
     43        self.device = device or ("cuda" if torch.cuda.is_available() else "cpu")
     44        self.model = BiLSTMNet(input_size, hidden_size, num_layers, num_classes).to(self.device)
     45        self.epochs = epochs
     46        self.batch_size = batch_size
     47        self.trained = False
     48        self.feature_names = []
     49        self.class_names = []
     50
     51    def train(self, features: List[Dict], **kwargs):
     52        X, y = preprocess_features(features)
     53        X = X.reshape((X.shape[0], 1, X.shape[1]))
     54        self.feature_names = [f"feature_{i}" for i in range(X.shape[2])]
     55        self.class_names = list(set(y))
     56        test_size = kwargs.get('test_size', 0.2)
     57        validation_split = kwargs.get('validation_split', True)
     58        if validation_split:
     59            X_train, X_test, y_train, y_test = train_test_split(
     60                X, y, test_size=test_size, random_state=42
     61            )
     62            self.X_test = X_test
     63            self.y_test = y_test
     64        else:
     65            X_train, y_train = X, y
     66        X_train = torch.tensor(X_train, dtype=torch.float32).to(self.device)
     67        y_train = torch.tensor(y_train, dtype=torch.long).to(self.device)
     68        criterion = nn.CrossEntropyLoss()
     69        optimizer = optim.Adam(self.model.parameters(), lr=self.config['lr'])
     70        for epoch in range(self.epochs):
     71            self.model.train()
     72            optimizer.zero_grad()
     73            outputs = self.model(X_train)
     74            loss = criterion(outputs, y_train)
     75            loss.backward()
     76            optimizer.step()
     77            if (epoch+1) % 5 == 0 or epoch == 0:
     78                print(f"Epoch [{epoch+1}/{self.epochs}], Loss: {loss.item():.4f}")
     79        self.trained = True
     80        print("BiLSTM model trained successfully.")
     81
     82    def predict(self, features: List[Dict], **kwargs) -> np.ndarray:
     83        if not self.trained:
     84            raise ValueError("Model must be trained before making predictions")
     85        X, _ = preprocess_features(features)
     86        X = X.reshape((X.shape[0], 1, X.shape[1]))
     87        X = torch.tensor(X, dtype=torch.float32).to(self.device)
     88        self.model.eval()
     89        with torch.no_grad():
     90            outputs = self.model(X)
     91            _, predicted = torch.max(outputs.data, 1)
     92        return predicted.cpu().numpy()
     93
     94    def evaluate(self, features: List[Dict], **kwargs) -> Dict[str, float]:
     95        if not self.trained:
     96            raise ValueError("Model must be trained before evaluation")
     97        if hasattr(self, 'X_test') and hasattr(self, 'y_test'):
     98            X_test, y_test = self.X_test, self.y_test
     99        else:
    100            X_test, y_test = preprocess_features(features)
    101            X_test = X_test.reshape((X_test.shape[0], 1, X_test.shape[1]))
    102        X_test = torch.tensor(X_test, dtype=torch.float32).to(self.device)
    103        y_test = np.array(y_test)
    104        self.model.eval()
    105        with torch.no_grad():
    106            outputs = self.model(X_test)
    107            _, y_pred = torch.max(outputs.data, 1)
    108        y_pred = y_pred.cpu().numpy()
    109        accuracy = accuracy_score(y_test, y_pred)
    110        conf_matrix = confusion_matrix(y_test, y_pred)
    111        metrics = {
    112            'accuracy': accuracy,
    113            'confusion_matrix': conf_matrix.tolist()
    114        }
    115        detailed_report = kwargs.get('detailed_report', False)
    116        if detailed_report:
    117            class_report = classification_report(y_test, y_pred, output_dict=True)
    118            metrics['classification_report'] = class_report
    119        return metrics
    120
    121    def save_model(self, filepath: str):
    122        if not self.trained:
    123            raise ValueError("Model must be trained before saving")
    124        torch.save({
    125            'model_state_dict': self.model.state_dict(),
    126            'config': self.config,
    127            'feature_names': self.feature_names,
    128            'class_names': self.class_names,
    129            'trained': self.trained
    130        }, filepath)
    131        print(f"BiLSTM model saved to {filepath}")
    132
    133    def load_model(self, filepath: str):
    134        checkpoint = torch.load(filepath, map_location=self.device)
    135        self.model = BiLSTMNet(
    136            self.config['input_size'],
    137            self.config['hidden_size'],
    138            self.config['num_layers'],
    139            self.config['num_classes']
    140        ).to(self.device)
    141        self.model.load_state_dict(checkpoint['model_state_dict'])
    142        self.config = checkpoint.get('config', self.config)
    143        self.feature_names = checkpoint.get('feature_names', [])
    144        self.class_names = checkpoint.get('class_names', [])
    145        self.trained = checkpoint.get('trained', True)
    146        print(f"BiLSTM model loaded from {filepath}")

Bidirectional LSTM classification model using PyTorch. Implements the BaseClassificationModel interface.

BiLSTMModel( input_size=10, hidden_size=64, num_layers=1, num_classes=2, lr=0.001, epochs=20, batch_size=32, device=None) View Source

    29    def __init__(self, input_size=10, hidden_size=64, num_layers=1, num_classes=2, lr=0.001, epochs=20, batch_size=32, device=None):
    30        super().__init__(
    31            name="bilstm",
    32            description="Bidirectional LSTM classifier for gait data classification"
    33        )
    34        self.config = {
    35            'input_size': input_size,
    36            'hidden_size': hidden_size,
    37            'num_layers': num_layers,
    38            'num_classes': num_classes,
    39            'lr': lr,
    40            'epochs': epochs,
    41            'batch_size': batch_size
    42        }
    43        self.device = device or ("cuda" if torch.cuda.is_available() else "cpu")
    44        self.model = BiLSTMNet(input_size, hidden_size, num_layers, num_classes).to(self.device)
    45        self.epochs = epochs
    46        self.batch_size = batch_size
    47        self.trained = False
    48        self.feature_names = []
    49        self.class_names = []

Initialize the classification model.

Args: name: Name of the classification model description: Description of the classification model

config
device
model
epochs
batch_size
trained
feature_names
class_names
def train(self, features: List[Dict], **kwargs): View Source

    51    def train(self, features: List[Dict], **kwargs):
    52        X, y = preprocess_features(features)
    53        X = X.reshape((X.shape[0], 1, X.shape[1]))
    54        self.feature_names = [f"feature_{i}" for i in range(X.shape[2])]
    55        self.class_names = list(set(y))
    56        test_size = kwargs.get('test_size', 0.2)
    57        validation_split = kwargs.get('validation_split', True)
    58        if validation_split:
    59            X_train, X_test, y_train, y_test = train_test_split(
    60                X, y, test_size=test_size, random_state=42
    61            )
    62            self.X_test = X_test
    63            self.y_test = y_test
    64        else:
    65            X_train, y_train = X, y
    66        X_train = torch.tensor(X_train, dtype=torch.float32).to(self.device)
    67        y_train = torch.tensor(y_train, dtype=torch.long).to(self.device)
    68        criterion = nn.CrossEntropyLoss()
    69        optimizer = optim.Adam(self.model.parameters(), lr=self.config['lr'])
    70        for epoch in range(self.epochs):
    71            self.model.train()
    72            optimizer.zero_grad()
    73            outputs = self.model(X_train)
    74            loss = criterion(outputs, y_train)
    75            loss.backward()
    76            optimizer.step()
    77            if (epoch+1) % 5 == 0 or epoch == 0:
    78                print(f"Epoch [{epoch+1}/{self.epochs}], Loss: {loss.item():.4f}")
    79        self.trained = True
    80        print("BiLSTM model trained successfully.")

Train the classification model.

Args: features: List of feature dictionaries **kwargs: Additional arguments for training

def predict(self, features: List[Dict], **kwargs) -> numpy.ndarray: View Source

    82    def predict(self, features: List[Dict], **kwargs) -> np.ndarray:
    83        if not self.trained:
    84            raise ValueError("Model must be trained before making predictions")
    85        X, _ = preprocess_features(features)
    86        X = X.reshape((X.shape[0], 1, X.shape[1]))
    87        X = torch.tensor(X, dtype=torch.float32).to(self.device)
    88        self.model.eval()
    89        with torch.no_grad():
    90            outputs = self.model(X)
    91            _, predicted = torch.max(outputs.data, 1)
    92        return predicted.cpu().numpy()

Make predictions using the trained model.

Args: features: List of feature dictionaries **kwargs: Additional arguments for prediction

Returns: Array of predictions

def evaluate(self, features: List[Dict], **kwargs) -> Dict[str, float]: View Source

     94    def evaluate(self, features: List[Dict], **kwargs) -> Dict[str, float]:
     95        if not self.trained:
     96            raise ValueError("Model must be trained before evaluation")
     97        if hasattr(self, 'X_test') and hasattr(self, 'y_test'):
     98            X_test, y_test = self.X_test, self.y_test
     99        else:
    100            X_test, y_test = preprocess_features(features)
    101            X_test = X_test.reshape((X_test.shape[0], 1, X_test.shape[1]))
    102        X_test = torch.tensor(X_test, dtype=torch.float32).to(self.device)
    103        y_test = np.array(y_test)
    104        self.model.eval()
    105        with torch.no_grad():
    106            outputs = self.model(X_test)
    107            _, y_pred = torch.max(outputs.data, 1)
    108        y_pred = y_pred.cpu().numpy()
    109        accuracy = accuracy_score(y_test, y_pred)
    110        conf_matrix = confusion_matrix(y_test, y_pred)
    111        metrics = {
    112            'accuracy': accuracy,
    113            'confusion_matrix': conf_matrix.tolist()
    114        }
    115        detailed_report = kwargs.get('detailed_report', False)
    116        if detailed_report:
    117            class_report = classification_report(y_test, y_pred, output_dict=True)
    118            metrics['classification_report'] = class_report
    119        return metrics

Evaluate the model performance.

Args: features: List of feature dictionaries **kwargs: Additional arguments for evaluation

Returns: Dictionary containing evaluation metrics

def save_model(self, filepath: str): View Source

    121    def save_model(self, filepath: str):
    122        if not self.trained:
    123            raise ValueError("Model must be trained before saving")
    124        torch.save({
    125            'model_state_dict': self.model.state_dict(),
    126            'config': self.config,
    127            'feature_names': self.feature_names,
    128            'class_names': self.class_names,
    129            'trained': self.trained
    130        }, filepath)
    131        print(f"BiLSTM model saved to {filepath}")

Save the trained model to a file.

Args: filepath: Path to save the model

def load_model(self, filepath: str): View Source

    133    def load_model(self, filepath: str):
    134        checkpoint = torch.load(filepath, map_location=self.device)
    135        self.model = BiLSTMNet(
    136            self.config['input_size'],
    137            self.config['hidden_size'],
    138            self.config['num_layers'],
    139            self.config['num_classes']
    140        ).to(self.device)
    141        self.model.load_state_dict(checkpoint['model_state_dict'])
    142        self.config = checkpoint.get('config', self.config)
    143        self.feature_names = checkpoint.get('feature_names', [])
    144        self.class_names = checkpoint.get('class_names', [])
    145        self.trained = checkpoint.get('trained', True)
    146        print(f"BiLSTM model loaded from {filepath}")

Load a trained model from a file.

Args: filepath: Path to the saved model

##### Inherited Members

gaitsetpy.core.base_classes.BaseClassificationModel
  name
  description
  configure
  get_info