Hi All,
I am new to Machine Learning and Pytorch and I was wondering if I could get some inputs from the community about the first LSTM modeI I built in Pytorch.
I am trying to model a loundspeaker response. I have measured data of the voltage applied and the excursion of the diaphragm. The input to the model will be the excursion and the output predicted will be the voltage.
Below is the sample of how the data looks like:
image823×478 56.9 KB
Here is my LSTM model:
class ExcursionRNN(nn.Module):
    def __init__(self, input_dim, n_hidden=256, n_layers=2,drop_prob=0.5):

        super().__init__()
        self.drop_prob = drop_prob
        self.n_layers = n_layers
        self.n_hidden = n_hidden

        ## Define the LSTM
        self.lstm = nn.LSTM(input_dim, n_hidden, n_layers, dropout=drop_prob, batch_first=True)
        ## Define a dropout layer
        self.dropout = nn.Dropout(drop_prob)
        ## Define the final, linear output layer with output size 1
        self.linear = nn.Linear(n_hidden, 1)

    def forward(self, x, hc):

        ''' Forward pass through the network. These inputs are x, and the hidden/cell state `hc`. '''
         ## Get x, and the new hidden state (h, c) from the lstm  
        x, (h, c) = self.lstm(x, hc)
        ## pass x through the dropout layer
        x = self.dropout(x)
        # Stack up LSTM outputs using view
        x = x.contiguous().view(x.size()[0]*x.size()[1], self.n_hidden)
        ## Put x through the linear regression layer
        x = self.linear(x)
        # Return x and the hidden state (h, c)
        return x, (h, c)  

    def init_hidden(self, n_seqs):
        ''' Initializes hidden state '''
        # Create two new tensors with sizes n_layers x n_seqs x n_hidden,
        # Randomize hidden state and cell state of LSTM
        hidden = (torch.randn(self.n_layers, n_seqs, self.n_hidden).double(),torch.randn(self.n_layers, n_seqs, self.n_hidden).double())

        return hidden

def train(net, input_data, target_data, epochs=10, n_seqs=10, n_steps=50, lr=0.001, clip=5, val_frac=0.2, cuda=False, print_every=10):
    ''' Training a network 
    
        Arguments
        ---------
        net: excursionRNN network
        input_data: excursion data to train the network
        target_data: voltage data to train the network
        epochs: Number of epochs to train
        n_seqs: Number of mini-sequences per mini-batch, aka batch size
        n_steps: Number of excursion steps per mini-batch
        lr: learning rate
        clip: gradient clipping
        val_frac: Fraction of data to hold out for validation
        cuda: Train with CUDA on a GPU
        print_every: Number of steps for printing training and validation loss
    
    '''
    net = net.double()
    net.train()
    
    opt = torch.optim.Adam(net.parameters(), lr=lr)
    criterion = nn.MSELoss()
    
    # create training and validation data
    val_idx = int(len(input_data)*(1-val_frac))
    input_data, val_input_data = input_data[:val_idx], input_data[val_idx:]
    target_data, val_target_data = target_data[:val_idx], target_data[val_idx:]
    
    counter = 0
    
    for e in range(epochs):
        # initialize the hidden and cell state with random numbers
        h = net.init_hidden(n_seqs) 
        
        for x, y in get_batches(input_data, target_data, n_seqs, n_steps):
            counter += 1
            
            inputs, targets = torch.from_numpy(x), torch.from_numpy(y)
            inputs = inputs.type(torch.DoubleTensor)
            targets = targets.type(torch.DoubleTensor)

            h = tuple([each.data for each in h])
            # reshape the data such that it's shape is (n_seqs, n_steps, n_features=1)
            inputs.unsqueeze_(-1)
            inputs = inputs.expand(n_seqs,n_steps,1)
            output, h = net.forward(inputs, h)
            
            output = output.view(output.size()[0]*output.size()[1])
            output = output.type(torch.DoubleTensor)
            
            loss = criterion(output, targets.contiguous().view(n_seqs*n_steps))

            opt.zero_grad()
            loss.backward()
            
            # `clip_grad_norm` helps prevent the exploding gradient problem in RNNs / LSTMs.
            nn.utils.clip_grad_norm_(net.parameters(), clip)
            opt.step()
            
            if counter % print_every == 0:
                
                # Get validation loss
                val_h = net.init_hidden(n_seqs)
                val_losses = []
                
                for x, y in get_batches(val_input_data, val_target_data, n_seqs, n_steps):
                    x, y = torch.from_numpy(x), torch.from_numpy(y)
                   
                    val_h = tuple([each.data for each in val_h])
                    
                    inputs, targets = x, y

                    inputs = inputs.type(torch.DoubleTensor)
                    targets = targets.type(torch.DoubleTensor)
                    inputs.unsqueeze_(-1)
                    inputs = inputs.expand(n_seqs,n_steps,1)
                    
                    with torch.no_grad():
                        output, val_h = net.forward(inputs, val_h)
                        output = output.view(output.size()[0]*output.size()[1])
                        val_loss = criterion(output, targets.contiguous().view(n_seqs*n_steps))
                
                    val_losses.append(val_loss.item())
                
                print("Epoch: {}/{}...".format(e+1, epochs),
                      "Step: {}...".format(counter),
                      "Loss: {:.4f}...".format(loss.item()),
                      "Val Loss: {:.4f}".format(np.mean(val_losses)))

net = ExcursionRNN(input_dim = 1, n_hidden=64, n_layers=2, drop_prob=0.5)
n_seqs, n_steps = 10, 28
train(net, excursion_measured_mm, voltage_measured, epochs=10000, n_seqs=n_seqs, n_steps=n_steps, lr=0.001, cuda=False, print_every=10)  

I have 11000 samples of data to train my model on. Out of 11k, 80% are used for training and 20% for validation.
Since I am still learning to understand LSTM and implementing them I am not sure if my implementation is right or not. It would be really helpful if someone could look at the code and provide some feedback whether the implementation is correct or not.
Also, after running the model for 10000 epochs, I get the Mean Squared Error of 0.0046.
I save the model and open it and pass the training data through it and I get the MSE of 0.025, which doesn’t seek like it’s correct.
Below is the script for running the final model on the training dataset:
def get_output(net, data):
    
    net = net.double()
    net.eval()
    
    n_steps = 28
    n_seqs = int(len(data)/n_steps)
    
    data = data.reshape(-1,len(data))
    data = data[:, :n_seqs*n_steps]
    
    h = net.init_hidden(1)
    output = []

    for n in range(0, data.shape[1], n_steps):
        
        h = tuple([each.data for each in h])

        inputs = data[:,n:n+n_steps]
        inputs = np.asarray(inputs)

        inputs = torch.from_numpy(inputs)
        inputs = inputs.type(torch.DoubleTensor)
        
        inputs.unsqueeze_(-1)
        inputs = inputs.expand(1,n_steps,1)
        
        out, h = net.forward(inputs, h)
        out = out.detach().numpy()
        output = np.append(output, out)
    
    return output

I am not able to figure out what’s wrong in my script that runs the final model on the training data that the RME I am getting is an order magnitude higher than the loss I get when I am actually training the model.
I would really appreciate if someone to point out the error in my script/model.
Thanks!