text: import copy
  import random
  import sys
  import numpy as np
  import matplotlib.pyplot as plt
  import torch
  from sklearn.preprocessing import MinMaxScaler
  from ch4.model.lstm import LSTM
  from ch4.training_datasets import get_training_datasets, get_ni_timeseries
  
  random.seed(1)
  torch.manual_seed(1)
  
  # PARAMETERS
  
  # length of sliding window
  features = 240
  # length of test dataset
  test_ts_len = 300
  # size of LSTM hidden state
  lstm_hidden_size = 24
  # Optimizer learning rate
  learning_rate = 0.02
  
  training_epochs = 100
  
  # Preparing datasets for Training
  ts = get_ni_timeseries()
  scaler = MinMaxScaler()
  scaled_ts = scaler.fit_transform(ts)
  x_train, x_val, x_test, y_train, y_val, y_test =\
      get_training_datasets(scaled_ts, features, test_ts_len)
  
  # Initializing the model
  model = LSTM(hidden_size = lstm_hidden_size)
  model.train()
  
  # Training
  optimizer = torch.optim.Adam(params = model.parameters(), lr = learning_rate)
  mse_loss = torch.nn.MSELoss()
  
  best_model = None
  min_val_loss = sys.maxsize
  
  training_loss = []
  validation_loss = []
  
  for t in range(training_epochs):
  
      prediction, _ = model(x_train)
      loss = mse_loss(prediction, y_train)
  
      optimizer.zero_grad()
      loss.backward()
      optimizer.step()
  
      val_prediction, _ = model(x_val)
      val_loss = mse_loss(val_prediction, y_val)
  
      training_loss.append(loss.item())
      validation_loss.append(val_loss.item())
  
      if val_loss.item() < min_val_loss:
          best_model = copy.deepcopy(model)
          min_val_loss = val_loss.item()
  
      if t % 10 == 0:
          print(f'epoch {t}: train - {round(loss.item(), 4)}, '
                f'val: - {round(val_loss.item(), 4)}')
  
  best_model.eval()
  with torch.no_grad():
      _, h_list = best_model(x_val)
      # warm hidden and cell state
      h = tuple([(h[-1, -1, :]).unsqueeze(-2).unsqueeze(-2)
                 for h in h_list])
  
      predicted = []
      for test_seq in x_test.tolist():
          x = torch.Tensor(data = [test_seq])
          # passing hidden state and cell through each iteration
          y, h = best_model(x, h)
          unscaled = scaler.inverse_transform(
              np.array(y.item()).reshape(-1, 1))[0][0]
          predicted.append(unscaled)
  
  real = scaler.inverse_transform(y_test.tolist())
  plt.title("Test dataset")
  plt.plot(real, label = 'real')
  plt.plot(predicted, label = 'predicted')
  plt.legend()
  plt.show()
  
  plt.title('Training')
  plt.yscale('log')
  plt.plot(training_loss, label = 'training')
  plt.plot(validation_loss, label = 'validation')
  plt.ylabel('Loss')
  plt.xlabel('Epoch')
  plt.legend()
  plt.show()
  