Created
March 23, 2021 16:34
-
-
Save bkaankuguoglu/c2ed0dfbcd51a58487549c436f7d65d1 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class Optimization: | |
| """Optimization is a helper class that allows training, validation, prediction. | |
| Optimization is a helper class that takes model, loss function, optimizer function | |
| learning scheduler (optional), early stopping (optional) as inputs. In return, it | |
| provides a framework to train and validate the models, and to predict future values | |
| based on the models. | |
| Attributes: | |
| model (RNNModel, LSTMModel, GRUModel): Model class created for the type of RNN | |
| loss_fn (torch.nn.modules.Loss): Loss function to calculate the losses | |
| optimizer (torch.optim.Optimizer): Optimizer function to optimize the loss function | |
| early_stop (bool, optional): Boolean value that activates early stopping | |
| lr_scheduler (torch.optim.lr_scheduler, optional): Learning rate scheduler | |
| train_losses (list[float]): The loss values from the training | |
| val_losses (list[float]): The loss values from the validation | |
| last_epoch (int): The number of epochs that the models is trained | |
| """ | |
| def __init__(self, model, loss_fn, optimizer, lr_scheduler=None, early_stop=False): | |
| """ | |
| Args: | |
| model (RNNModel, LSTMModel, GRUModel): Model class created for the type of RNN | |
| loss_fn (torch.nn.modules.Loss): Loss function to calculate the losses | |
| optimizer (torch.optim.Optimizer): Optimizer function to optimize the loss function | |
| lr_scheduler (torch.optim.lr_scheduler, optional): Learning rate scheduler | |
| early_stop (bool, optional): Boolean value that activates early stopping | |
| """ | |
| self.model = model | |
| self.loss_fn = loss_fn | |
| self.optimizer = optimizer | |
| self.train_losses = [] | |
| self.val_losses = [] | |
| def train_step(self, x, y): | |
| """The method train_step completes one step of training. | |
| Given the features (x) and the target values (y) tensors, the method completes | |
| one step of the training. First, it activates the train mode to enable back prop. | |
| After generating predicted values (yhat) by doing forward propagation, it calculates | |
| the losses by using the loss function. Then, it computes the gradients by doing | |
| back propagation and updates the weights by calling step() function. | |
| Args: | |
| x (torch.Tensor): Tensor for features to train one step | |
| y (torch.Tensor): Tensor for target values to calculate losses | |
| """ | |
| # Sets model to train mode | |
| self.model.train() | |
| # Makes predictions | |
| yhat = self.model(x) | |
| # Computes loss | |
| loss = self.loss_fn(y, yhat) | |
| # Computes gradients | |
| loss.backward() | |
| # Updates parameters and zeroes gradients | |
| self.optimizer.step() | |
| self.optimizer.zero_grad() | |
| # Returns the loss | |
| return loss.item() | |
| def train(self, train_loader, val_loader, batch_size=64, n_epochs=50, n_features=1): | |
| """The method train performs the model training | |
| The method takes DataLoaders for training and validation datasets, batch size for | |
| mini-batch training, number of epochs to train, and number of features as inputs. | |
| Then, it carries out the training by iteratively calling the method train_step for | |
| n_epochs times. If early stopping is enabled, then it checks the stopping condition | |
| to decide whether the training needs to halt before n_epochs steps. Finally, it saves | |
| the model in a designated file path. | |
| Args: | |
| train_loader (torch.utils.data.DataLoader): DataLoader that stores training data | |
| val_loader (torch.utils.data.DataLoader): DataLoader that stores validation data | |
| batch_size (int): Batch size for mini-batch training | |
| n_epochs (int): Number of epochs, i.e., train steps, to train | |
| n_features (int): Number of feature columns | |
| """ | |
| model_path = f'models/{self.model}_{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}' | |
| for epoch in range(1, n_epochs + 1): | |
| batch_losses = [] | |
| for x_batch, y_batch in train_loader: | |
| x_batch = x_batch.view([batch_size, -1, n_features]).to(device) | |
| y_batch = y_batch.to(device) | |
| loss = self.train_step(x_batch, y_batch) | |
| batch_losses.append(loss) | |
| training_loss = np.mean(batch_losses) | |
| self.train_losses.append(training_loss) | |
| with torch.no_grad(): | |
| batch_val_losses = [] | |
| for x_val, y_val in val_loader: | |
| x_val = x_val.view([batch_size, -1, n_features]).to(device) | |
| y_val = y_val.to(device) | |
| self.model.eval() | |
| yhat = self.model(x_val) | |
| val_loss = self.loss_fn(y_val, yhat).item() | |
| batch_val_losses.append(val_loss) | |
| validation_loss = np.mean(batch_val_losses) | |
| self.val_losses.append(validation_loss) | |
| if (epoch <= 10) | (epoch % 50 == 0): | |
| print( | |
| f"[{epoch}/{n_epochs}] Training loss: {training_loss:.4f}\t Validation loss: {validation_loss:.4f}" | |
| ) | |
| torch.save(self.model.state_dict(), model_path) | |
| def evaluate(self, test_loader, batch_size=1, n_features=1): | |
| """The method evaluate performs the model evaluation | |
| The method takes DataLoaders for the test dataset, batch size for mini-batch testing, | |
| and number of features as inputs. Similar to the model validation, it iteratively | |
| predicts the target values and calculates losses. Then, it returns two lists that | |
| hold the predictions and the actual values. | |
| Note: | |
| This method assumes that the prediction from the previous step is available at | |
| the time of the prediction, and only does one-step prediction into the future. | |
| Args: | |
| test_loader (torch.utils.data.DataLoader): DataLoader that stores test data | |
| batch_size (int): Batch size for mini-batch training | |
| n_features (int): Number of feature columns | |
| Returns: | |
| list[float]: The values predicted by the model | |
| list[float]: The actual values in the test set. | |
| """ | |
| with torch.no_grad(): | |
| predictions = [] | |
| values = [] | |
| for x_test, y_test in test_loader: | |
| x_test = x_test.view([batch_size, -1, n_features]).to(device) | |
| y_test = y_test.to(device) | |
| self.model.eval() | |
| yhat = self.model(x_test) | |
| predictions.append(yhat.to(device).detach().numpy()) | |
| values.append(y_test.to(device).detach().numpy()) | |
| return predictions, values | |
| def forecast(self, test_loader, batch_size=1, n_features=1, n_steps=100): | |
| """The method forecast() forecasts values for RNNs with one-dimensional output | |
| The method takes DataLoader for the test dataset, batch size for mini-batch testing, | |
| number of features and number of steps to predict as inputs. Then it generates the | |
| future values for RNNs with one-dimensional output for the given n_steps. | |
| It uses the last item from the Test DataLoader to create the next input tensor (X). | |
| First, it shifts the tensor by one and adds the actual value from the target tensor (y). | |
| For the given n_steps, it goes on to predict the values for the next step and to update | |
| the input tensor for the next prediction. During each iteration it stores the predicted | |
| values in the list. | |
| Note: | |
| Unlike the method evaluate: This method does not assume that the prediction from | |
| the previous step is available the time of the prediction. Hence, it takes the | |
| first item in the Test DataLoader then only does one-step prediction into the future. | |
| After predicting the value for the next step, it updates the input tensor (X) | |
| by shifting the tensor and then adding the most recent prediction. | |
| Args: | |
| test_loader (torch.utils.data.DataLoader): DataLoader that stores test data | |
| batch_size (int): Batch size for mini-batch training | |
| n_features (int): Number of feature columns | |
| n_steps (int): Number of steps to predict future values | |
| Returns: | |
| list[float]: The values predicted by the model | |
| """ | |
| test_loader_iter = iter(test_loader) | |
| predictions = [] | |
| *_, (X, y) = test_loader_iter | |
| y = y.to(device).detach().numpy() | |
| X = X.view([batch_size, -1, n_features]).to(device) | |
| X = torch.roll(X, shifts=1, dims=2) | |
| X[..., -1, 0] = y.item(0) | |
| with torch.no_grad(): | |
| self.model.eval() | |
| for _ in range(n_steps): | |
| X = X.view([batch_size, -1, n_features]).to(device) | |
| yhat = self.model(X) | |
| yhat = yhat.to(device).detach().numpy() | |
| X = torch.roll(X, shifts=1, dims=2) | |
| X[..., -1, 0] = yhat.item(0) | |
| predictions.append(yhat.item(0)) | |
| return predictions | |
| def plot_losses(self): | |
| """The method plots the calculated loss values for training and validation | |
| """ | |
| plt.plot(self.train_losses, label="Training loss") | |
| plt.plot(self.val_losses, label="Validation loss") | |
| plt.legend() | |
| plt.title("Losses") | |
| plt.show() | |
| plt.close() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment