How to use LSTM for a time-series classification task?

jagandecapri: <p><strong>Problem</strong></p>
<p>Given a dataset consisting of 48-hour sequence of hospital records and a binary target determining whether the patient survives or not, when the model is given a test sequence of 48 hours record, it needs to predict whether the patient survives or not.</p>
<p><strong>Data</strong></p>
<p>I have constructed a dummy dataset as following:</p>
<pre><code class="lang-auto">input_ = torch.randn(100, 48, 76)
target_ = torch.randint(0, 2, (100,))
</code></pre>
<p>and loading the training data as following:</p>
<pre><code class="lang-auto">trainloader = DataLoader(TensorDataset(input_, target_), 
                         batch_size=50, 
                         shuffle=True)
</code></pre>
<p><strong>Model</strong></p>
<p>I have constructed an LSTM based model as following:</p>
<pre><code class="lang-auto">class LSTMClassification(nn.Module):

    def __init__(self, input_dim, hidden_dim, target_size):
        super(LSTMClassification, self).__init__()
        self.lstm = nn.LSTM(input_dim, hidden_dim, batch_first=True)
        self.fc = nn.Linear(hidden_dim, target_size)

    def forward(self, input_):
        lstm_out, (h, c) = self.lstm(input_)
        logits = self.fc(lstm_out[-1])
        scores = F.sigmoid(logits)
        return scores
</code></pre>
<p>with training function as following:</p>
<pre><code class="lang-auto">def train(model, n_epochs):
    loss_function = nn.BCEWithLogitsLoss()
    optimizer = optim.SGD(model.parameters(), lr=0.1)

    history = {
        'loss': []
    }
    for epoch in range(n_epochs):
        losses = []
        for i, data in enumerate(trainloader, 0):
            inputs, labels = data

            model.zero_grad()

            tag_scores = model(inputs)

            labels = labels.unsqueeze(1)
            
            loss = loss_function(tag_scores, labels)
            
            loss.backward()
            optimizer.step()
            losses.append(float(loss))
        avg_loss = np.mean(losses)
        history['loss'].append(avg_loss)
        print("Epoch {} / {}: Loss = {:.3f}".format(epoch+1, n_epochs, avg_loss))
    return history
</code></pre>
<p>The model construction is as following:</p>
<pre><code class="lang-auto">model = LSTMClassification(input_.shape[2], 
                        hidden_dim=6, 
                        target_size=1)
</code></pre>
<p>However, when I train the model, I’m getting an error</p>
<pre><code class="lang-auto">ValueError: Target size (torch.Size([50, 1])) must be the same as input size (torch.Size([48, 1]))
</code></pre>
<p>I’m not sure how to get my model to yield a tensor of size (50,1) whereby for each group of time series data, it yields an output of 0 or 1.</p>

ptrblck: <p>I assume you want to index the last time step in this line of code:</p>
<aside class="quote no-group" data-username="jagandecapri" data-post="1" data-topic="130559">
<div class="title">
<div class="quote-controls"></div>
<img loading="lazy" alt="" width="24" height="24" src="https://discuss.pytorch.org/letter_avatar_proxy/v4/letter/j/65b543/48.png" class="avatar"> jagandecapri:</div>
<blockquote>
<p><code>logits = self.fc(lstm_out[-1])</code></p>
</blockquote>
</aside>
<p>which is wrong, since you are using <code>batch_first=True</code> and according to the <a href="https://pytorch.org/docs/stable/generated/torch.nn.LSTM.html?highlight=lstm#torch.nn.LSTM">docs</a> the <code>output</code> shape would be <code>[batch_size, seq_len, num_directions * hidden_size]</code>, so you might want to use <code>self.fc(lstm_out[:, -1])</code> instead.</p>

jagandecapri: <p>Thank you <a class="mention" href="/u/ptrblck">@ptrblck</a>. A few follow up questions referring to the following code snippet</p>
<pre><code class="lang-auto">    def forward(self, input_):
        lstm_out, (h, c) = self.lstm(input_)
        logits = self.fc(lstm_out[-1])
        scores = F.sigmoid(logits)
        return scores
</code></pre>
<ol>
<li>lstm_out[:, -1] would be the same as h[-1]<strong>?</strong> I checked it with torch.equal(lstm_out[:, -1], h[-1]) and it is True. Just want to confirm.</li>
<li>Since I’m using BCEWithLogitsLoss, do I need to have the sigmoid activation at the end of the model as BCEWithLogitsLoss has in-built sigmoid activation <strong>?</strong><br>
2.1. Only during prediction phase, I need to have a sigmoid activation for classification, as following <strong>?</strong>
</li>
</ol>
<pre><code class="lang-auto">def binary_acc(y_pred, y_test):
    y_pred_tag = torch.round(torch.sigmoid(y_pred))

    correct_results_sum = (y_pred_tag == y_test).sum().float()
    acc = correct_results_sum/y_test.shape[0]
    acc = torch.round(acc * 100)
    
    return acc
</code></pre>

ptrblck: <ol>
<li>For <code>bidirectional=False</code> layers, the tensors should be equal, yes.</li>
<li>
<code>nn.BCEWithLogitsLoss</code> expects raw logits as the model output, so you should not apply a sigmoid on the output before passing it to the loss function.</li>
<li>During the prediction phase you could apply a sigmoid and use a threshold to get the class labels, e.g.:</li>
</ol>
<pre><code class="lang-python">threshold = 0.5
pred = torch.sigmoid(output) &gt; threshold
</code></pre>
<p>Your rounding approach would also work, but the threshold would allow you to pick a point on the ROC curve.</p>

jagandecapri: <p>Thank you <a class="mention" href="/u/ptrblck">@ptrblck</a>.</p>
<p>Number (3) would be the same for multiclass prediction also, right <strong>?</strong></p>
<p>Whereby, the output of the last layer in the model would be an array of logits for each class and during prediction, a sigmoid is applied to get the probabilities for each class.</p>

ptrblck: <p>Yes, you could apply the <code>sigmoid</code> also for a multi-class classification where zero, one, or multiple classes can be “active”.</p>