# gaitsetpy.classification.models.lstm

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 LSTMNet(nn.Module):
     13    def __init__(self, input_size, hidden_size, num_layers, num_classes, dropout=0.2):
     14        super(LSTMNet, self).__init__()
     15        self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True, dropout=dropout)
     16        self.fc = nn.Linear(hidden_size, 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 LSTMModel(BaseClassificationModel):
     24    """
     25    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="lstm",
     31            description="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 = LSTMNet(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        # Reshape X for LSTM: (samples, sequence_length, input_size)
     53        # Here, treat each feature vector as a sequence of length 1
     54        X = X.reshape((X.shape[0], 1, X.shape[1]))
     55        self.feature_names = [f"feature_{i}" for i in range(X.shape[2])]
     56        self.class_names = list(set(y))
     57        test_size = kwargs.get('test_size', 0.2)
     58        validation_split = kwargs.get('validation_split', True)
     59        if validation_split:
     60            X_train, X_test, y_train, y_test = train_test_split(
     61                X, y, test_size=test_size, random_state=42
     62            )
     63            self.X_test = X_test
     64            self.y_test = y_test
     65        else:
     66            X_train, y_train = X, y
     67        X_train = torch.tensor(X_train, dtype=torch.float32).to(self.device)
     68        y_train = torch.tensor(y_train, dtype=torch.long).to(self.device)
     69        criterion = nn.CrossEntropyLoss()
     70        optimizer = optim.Adam(self.model.parameters(), lr=self.config['lr'])
     71        for epoch in range(self.epochs):
     72            self.model.train()
     73            optimizer.zero_grad()
     74            outputs = self.model(X_train)
     75            loss = criterion(outputs, y_train)
     76            loss.backward()
     77            optimizer.step()
     78            if (epoch+1) % 5 == 0 or epoch == 0:
     79                print(f"Epoch [{epoch+1}/{self.epochs}], Loss: {loss.item():.4f}")
     80        self.trained = True
     81        print("LSTM model trained successfully.")
     82
     83    def predict(self, features: List[Dict], **kwargs) -> np.ndarray:
     84        if not self.trained:
     85            raise ValueError("Model must be trained before making predictions")
     86        X, _ = preprocess_features(features)
     87        X = X.reshape((X.shape[0], 1, X.shape[1]))
     88        X = torch.tensor(X, dtype=torch.float32).to(self.device)
     89        self.model.eval()
     90        with torch.no_grad():
     91            outputs = self.model(X)
     92            _, predicted = torch.max(outputs.data, 1)
     93        return predicted.cpu().numpy()
     94
     95    def evaluate(self, features: List[Dict], **kwargs) -> Dict[str, float]:
     96        if not self.trained:
     97            raise ValueError("Model must be trained before evaluation")
     98        if hasattr(self, 'X_test') and hasattr(self, 'y_test'):
     99            X_test, y_test = self.X_test, self.y_test
    100        else:
    101            X_test, y_test = preprocess_features(features)
    102            X_test = X_test.reshape((X_test.shape[0], 1, X_test.shape[1]))
    103        X_test = torch.tensor(X_test, dtype=torch.float32).to(self.device)
    104        y_test = np.array(y_test)
    105        self.model.eval()
    106        with torch.no_grad():
    107            outputs = self.model(X_test)
    108            _, y_pred = torch.max(outputs.data, 1)
    109        y_pred = y_pred.cpu().numpy()
    110        accuracy = accuracy_score(y_test, y_pred)
    111        conf_matrix = confusion_matrix(y_test, y_pred)
    112        metrics = {
    113            'accuracy': accuracy,
    114            'confusion_matrix': conf_matrix.tolist()
    115        }
    116        detailed_report = kwargs.get('detailed_report', False)
    117        if detailed_report:
    118            class_report = classification_report(y_test, y_pred, output_dict=True)
    119            metrics['classification_report'] = class_report
    120        return metrics
    121
    122    def save_model(self, filepath: str):
    123        if not self.trained:
    124            raise ValueError("Model must be trained before saving")
    125        torch.save({
    126            'model_state_dict': self.model.state_dict(),
    127            'config': self.config,
    128            'feature_names': self.feature_names,
    129            'class_names': self.class_names,
    130            'trained': self.trained
    131        }, filepath)
    132        print(f"LSTM model saved to {filepath}")
    133
    134    def load_model(self, filepath: str):
    135        checkpoint = torch.load(filepath, map_location=self.device)
    136        self.model = LSTMNet(
    137            self.config['input_size'],
    138            self.config['hidden_size'],
    139            self.config['num_layers'],
    140            self.config['num_classes']
    141        ).to(self.device)
    142        self.model.load_state_dict(checkpoint['model_state_dict'])
    143        self.config = checkpoint.get('config', self.config)
    144        self.feature_names = checkpoint.get('feature_names', [])
    145        self.class_names = checkpoint.get('class_names', [])
    146        self.trained = checkpoint.get('trained', True)
    147        print(f"LSTM model loaded from {filepath}")

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

    13class LSTMNet(nn.Module):
    14    def __init__(self, input_size, hidden_size, num_layers, num_classes, dropout=0.2):
    15        super(LSTMNet, self).__init__()
    16        self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True, dropout=dropout)
    17        self.fc = nn.Linear(hidden_size, 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

LSTMNet(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(LSTMNet, self).__init__()
    16        self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True, dropout=dropout)
    17        self.fc = nn.Linear(hidden_size, 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 LSTMModel(gaitsetpy.core.base_classes.BaseClassificationModel): View Source

     24class LSTMModel(BaseClassificationModel):
     25    """
     26    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="lstm",
     32            description="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 = LSTMNet(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        # Reshape X for LSTM: (samples, sequence_length, input_size)
     54        # Here, treat each feature vector as a sequence of length 1
     55        X = X.reshape((X.shape[0], 1, X.shape[1]))
     56        self.feature_names = [f"feature_{i}" for i in range(X.shape[2])]
     57        self.class_names = list(set(y))
     58        test_size = kwargs.get('test_size', 0.2)
     59        validation_split = kwargs.get('validation_split', True)
     60        if validation_split:
     61            X_train, X_test, y_train, y_test = train_test_split(
     62                X, y, test_size=test_size, random_state=42
     63            )
     64            self.X_test = X_test
     65            self.y_test = y_test
     66        else:
     67            X_train, y_train = X, y
     68        X_train = torch.tensor(X_train, dtype=torch.float32).to(self.device)
     69        y_train = torch.tensor(y_train, dtype=torch.long).to(self.device)
     70        criterion = nn.CrossEntropyLoss()
     71        optimizer = optim.Adam(self.model.parameters(), lr=self.config['lr'])
     72        for epoch in range(self.epochs):
     73            self.model.train()
     74            optimizer.zero_grad()
     75            outputs = self.model(X_train)
     76            loss = criterion(outputs, y_train)
     77            loss.backward()
     78            optimizer.step()
     79            if (epoch+1) % 5 == 0 or epoch == 0:
     80                print(f"Epoch [{epoch+1}/{self.epochs}], Loss: {loss.item():.4f}")
     81        self.trained = True
     82        print("LSTM model trained successfully.")
     83
     84    def predict(self, features: List[Dict], **kwargs) -> np.ndarray:
     85        if not self.trained:
     86            raise ValueError("Model must be trained before making predictions")
     87        X, _ = preprocess_features(features)
     88        X = X.reshape((X.shape[0], 1, X.shape[1]))
     89        X = torch.tensor(X, dtype=torch.float32).to(self.device)
     90        self.model.eval()
     91        with torch.no_grad():
     92            outputs = self.model(X)
     93            _, predicted = torch.max(outputs.data, 1)
     94        return predicted.cpu().numpy()
     95
     96    def evaluate(self, features: List[Dict], **kwargs) -> Dict[str, float]:
     97        if not self.trained:
     98            raise ValueError("Model must be trained before evaluation")
     99        if hasattr(self, 'X_test') and hasattr(self, 'y_test'):
    100            X_test, y_test = self.X_test, self.y_test
    101        else:
    102            X_test, y_test = preprocess_features(features)
    103            X_test = X_test.reshape((X_test.shape[0], 1, X_test.shape[1]))
    104        X_test = torch.tensor(X_test, dtype=torch.float32).to(self.device)
    105        y_test = np.array(y_test)
    106        self.model.eval()
    107        with torch.no_grad():
    108            outputs = self.model(X_test)
    109            _, y_pred = torch.max(outputs.data, 1)
    110        y_pred = y_pred.cpu().numpy()
    111        accuracy = accuracy_score(y_test, y_pred)
    112        conf_matrix = confusion_matrix(y_test, y_pred)
    113        metrics = {
    114            'accuracy': accuracy,
    115            'confusion_matrix': conf_matrix.tolist()
    116        }
    117        detailed_report = kwargs.get('detailed_report', False)
    118        if detailed_report:
    119            class_report = classification_report(y_test, y_pred, output_dict=True)
    120            metrics['classification_report'] = class_report
    121        return metrics
    122
    123    def save_model(self, filepath: str):
    124        if not self.trained:
    125            raise ValueError("Model must be trained before saving")
    126        torch.save({
    127            'model_state_dict': self.model.state_dict(),
    128            'config': self.config,
    129            'feature_names': self.feature_names,
    130            'class_names': self.class_names,
    131            'trained': self.trained
    132        }, filepath)
    133        print(f"LSTM model saved to {filepath}")
    134
    135    def load_model(self, filepath: str):
    136        checkpoint = torch.load(filepath, map_location=self.device)
    137        self.model = LSTMNet(
    138            self.config['input_size'],
    139            self.config['hidden_size'],
    140            self.config['num_layers'],
    141            self.config['num_classes']
    142        ).to(self.device)
    143        self.model.load_state_dict(checkpoint['model_state_dict'])
    144        self.config = checkpoint.get('config', self.config)
    145        self.feature_names = checkpoint.get('feature_names', [])
    146        self.class_names = checkpoint.get('class_names', [])
    147        self.trained = checkpoint.get('trained', True)
    148        print(f"LSTM model loaded from {filepath}")

LSTM classification model using PyTorch. Implements the BaseClassificationModel interface.

LSTMModel( 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="lstm",
    32            description="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 = LSTMNet(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        # Reshape X for LSTM: (samples, sequence_length, input_size)
    54        # Here, treat each feature vector as a sequence of length 1
    55        X = X.reshape((X.shape[0], 1, X.shape[1]))
    56        self.feature_names = [f"feature_{i}" for i in range(X.shape[2])]
    57        self.class_names = list(set(y))
    58        test_size = kwargs.get('test_size', 0.2)
    59        validation_split = kwargs.get('validation_split', True)
    60        if validation_split:
    61            X_train, X_test, y_train, y_test = train_test_split(
    62                X, y, test_size=test_size, random_state=42
    63            )
    64            self.X_test = X_test
    65            self.y_test = y_test
    66        else:
    67            X_train, y_train = X, y
    68        X_train = torch.tensor(X_train, dtype=torch.float32).to(self.device)
    69        y_train = torch.tensor(y_train, dtype=torch.long).to(self.device)
    70        criterion = nn.CrossEntropyLoss()
    71        optimizer = optim.Adam(self.model.parameters(), lr=self.config['lr'])
    72        for epoch in range(self.epochs):
    73            self.model.train()
    74            optimizer.zero_grad()
    75            outputs = self.model(X_train)
    76            loss = criterion(outputs, y_train)
    77            loss.backward()
    78            optimizer.step()
    79            if (epoch+1) % 5 == 0 or epoch == 0:
    80                print(f"Epoch [{epoch+1}/{self.epochs}], Loss: {loss.item():.4f}")
    81        self.trained = True
    82        print("LSTM 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

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

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

    123    def save_model(self, filepath: str):
    124        if not self.trained:
    125            raise ValueError("Model must be trained before saving")
    126        torch.save({
    127            'model_state_dict': self.model.state_dict(),
    128            'config': self.config,
    129            'feature_names': self.feature_names,
    130            'class_names': self.class_names,
    131            'trained': self.trained
    132        }, filepath)
    133        print(f"LSTM 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

    135    def load_model(self, filepath: str):
    136        checkpoint = torch.load(filepath, map_location=self.device)
    137        self.model = LSTMNet(
    138            self.config['input_size'],
    139            self.config['hidden_size'],
    140            self.config['num_layers'],
    141            self.config['num_classes']
    142        ).to(self.device)
    143        self.model.load_state_dict(checkpoint['model_state_dict'])
    144        self.config = checkpoint.get('config', self.config)
    145        self.feature_names = checkpoint.get('feature_names', [])
    146        self.class_names = checkpoint.get('class_names', [])
    147        self.trained = checkpoint.get('trained', True)
    148        print(f"LSTM 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