gaitsetpy.classification.models.bilstm
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}")
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
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.
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.
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.
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
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
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
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
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
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