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