Fixing the Training Function for an LSTM model built from scratch

lingling36109: <p>So, I’m currently trying to build a custom-built LSTM model. To do so, I’m trying to build the custom LSTM from scratch first and slowly modify it. However, right now I’m having difficultly making the model learn at all.</p>
<p>Here is my code for the my LSTM function.</p>
<pre><code class="lang-auto">class LSTM_Model(nn.Module):
    def __init__(self, input_sz, hidden_sz, out_sz, bs):
        super().__init__()
        self.input_sz = input_sz
        self.hidden_size = hidden_sz
        self.W = nn.Parameter(torch.Tensor(input_sz, hidden_sz * 4))
        self.U = nn.Parameter(torch.Tensor(hidden_sz, hidden_sz * 4))
        self.bias = nn.Parameter(torch.Tensor(hidden_sz * 4))
        self.fc1 = nn.Linear(hidden_sz, out_sz)
        self.init_weights()

    def init_weights(self):
        stdv = 1.0 / math.sqrt(self.hidden_size)
        for weight in self.parameters():
            weight.data.uniform_(-stdv, stdv)

    def forward(self, x, init_states=None):
        bs, seq_sz, _ = x.size()
        hidden_seq = []
        if init_states is None:
            h_t, c_t = (torch.zeros(bs, self.hidden_size).to(x.device),
                        torch.zeros(bs, self.hidden_size).to(x.device))
        else:
            h_t, c_t = init_states

        for t in range(seq_sz):
            x_t = x[:, t, :]
            if x_t.size()[0] != bs:
                fill_in = torch.zeros(bs-x_t[0], seq_sz)
                x_t - torch.cat((x_t, fill_in), 0)

            gates = torch.matmul(x_t, self.W) + torch.matmul(h_t, self.U) + self.bias
            i_t, f_t, g_t, o_t = gates.chunk(4, 1)
            i_t, f_t, g_t, o_t = torch.sigmoid(i_t), torch.sigmoid(f_t), torch.tanh(g_t), torch.sigmoid(i_t)
            c_t = f_t * c_t + i_t * g_t
            h_t = o_t * torch.tanh(c_t)
            hidden_seq.append(h_t)
        out = self.fc1(h_t)
        return out, (h_t, c_t)
</code></pre>
<p>Here is my training function.</p>
<pre><code class="lang-auto">def train_model(data_loader, model, loss_function, optimizer):
    num_batches = len(data_loader)
    total_loss = 0
    model.train()
    hidden = None
    #torch.autograd.set_detect_anomaly(True)
    for X, y in data_loader:
        output, hidden = model(X, hidden)
        loss = loss_function(output, y)

        optimizer.zero_grad()
        loss.backward()
        #loss.backward(retain_graph=True)
        optimizer.step()

        total_loss += loss.item()

    avg_loss = total_loss / num_batches
    print(f"Train loss: {avg_loss}")
    return avg_loss
</code></pre>
<p>Running it gets an initial error message, which requires me to put the retain_graph(True), but after that I get more errors.</p>
<p>I also notice that removing the hidden variable for the training function, the program gives an output, but the model does not learn at all, probably because it’s throwing away the hidden state I think.</p>
<p>Yea, but I have no idea what’s going wrong. Please help.</p>

ptrblck: <p>Detach the hidden states in each iteration or delay the <code>backward</code> call if you want to backpropagate through multiple steps.</p>

lingling36109: <p>Could you explain how to detach the hidden states and what that does? I’ve implemented here at the beginning of the forward function.</p>
<pre><code class="lang-auto">class LSTM_Model(nn.Module):
    def __init__(self, input_sz, hidden_sz, out_sz, bs):
        super().__init__()
        self.input_size = input_sz
        self.hidden_size = hidden_sz
        self.batch_size = bs
        self.W = nn.Parameter(torch.Tensor(input_sz, hidden_sz * 4))
        self.U = nn.Parameter(torch.Tensor(hidden_sz, hidden_sz * 4))
        self.bias = nn.Parameter(torch.Tensor(hidden_sz * 4))
        self.fc1 = nn.Linear(hidden_sz, out_sz)
        self.init_weights()

    def init_weights(self):
        stdv = 1.0 / math.sqrt(self.hidden_size)
        for weight in self.parameters():
            weight.data.uniform_(-stdv, stdv)

    def forward(self, x, init_states=None):
        bs, seq_sz, _ = x.size()
        hidden_seq = []
        if init_states is None:
            h_t, c_t = (torch.zeros(bs, self.hidden_size).to(x.device),
                        torch.zeros(bs, self.hidden_size).to(x.device))
        else:
            h_t, c_t = init_states

        h_t = h_t.detach()
        c_t = c_t.detach()

        for t in range(seq_sz):
            x_t = x[:, t, :]
            if x_t.size()[0] &lt; self.batch_size:
                fill_in = torch.zeros(self.batch_size-x_t.size()[0], self.input_size)
                x_t = torch.cat((x_t, fill_in), 0)
            gates = torch.matmul(x_t, self.W) + torch.matmul(h_t, self.U) + self.bias
            i_t, f_t, g_t, o_t = gates.chunk(4, 1)
            i_t, f_t, g_t, o_t = torch.sigmoid(i_t), torch.sigmoid(f_t), torch.tanh(g_t), torch.sigmoid(i_t)
            c_t = f_t * c_t + i_t * g_t
            h_t = o_t * torch.tanh(c_t)
            hidden_seq.append(h_t)
        out = self.fc1(h_t)
        return out, (h_t, c_t)
</code></pre>
<p>However, this does not change performance of the model at all and it still can’t learn anything.<br>
<div class="lightbox-wrapper"><a class="lightbox" href="https://discuss.pytorch.org/uploads/default/original/3X/7/0/707f75be3a33374b9656813df82e9f02cd370615.png" data-download-href="https://discuss.pytorch.org/uploads/default/707f75be3a33374b9656813df82e9f02cd370615" title="Screen Shot 2024-04-23 at 10.46.21 PM"><img src="https://discuss.pytorch.org/uploads/default/original/3X/7/0/707f75be3a33374b9656813df82e9f02cd370615.png" alt="Screen Shot 2024-04-23 at 10.46.21 PM" data-base62-sha1="g3cyCfRNodO3u59iq0y7Bo02e4R" width="485" height="500" data-dominant-color="232427"><div class="meta"><svg class="fa d-icon d-icon-far-image svg-icon" aria-hidden="true"><use href="#far-image"></use></svg><span class="filename">Screen Shot 2024-04-23 at 10.46.21 PM</span><span class="informations">864×890 24.5 KB</span><svg class="fa d-icon d-icon-discourse-expand svg-icon" aria-hidden="true"><use href="#discourse-expand"></use></svg></div></a></div></p>
<p>Using the LSTM implementation in Pytorch (just the regular one) leads to errors low as 0.0002 or something like that, but here the model is just refusing to learn.</p>

ptrblck: <p>Since the states are attached to the computation graph calling <code>backward</code> in the second iteration will try to backpropagate through them into the first iterations, which does not have the needed intermediate activations for the gradient computation anymore. Detaching states is a common approach to keep the actual values, but to cut the computation graph, making sure the <code>backward</code> call only computes the gradients for the current iteration.<br>
I don’t know why your model is currently performing worse, but would assume you have detached the states in the PyTorch implementation, too?</p>

lingling36109: <p>I’ve heard this from a few people (that you need to detach the hidden state from the computational graph). However, I’m not sure how to implement it into Pytorch. As far as I’m concerned, you can use the <code>detach</code> function, but I’m not sure how and any implementations I have of it are not working. Could you provide some sample code so that I could take a look at it.</p>
<p>In my implementation, I just add these liens near the beginning of the forward function for the LSTM model.</p>
<pre><code class="lang-auto">h_t = h_t.detach()
c_t = c_t.detach()
</code></pre>