How to check accuracy on BCELoss Pytorch?
I'm trying to use Pytorch to take a HeartDisease.csv and predict whether the patient has heart disease or not... the .csv provides 13 inputs and 1 target
I'm using BCELoss and I'm having trouble understanding how to write an accuracy check function.
My num_samples is correct but not my num_correct. I think this is a result of not understanding the predictions tensor. Right now my num_correct is usually over 8000 while my num_samples is 303...
Any insight on how to write this check accuracy function is much appreciated
I wrote this on a google co lab
#imports
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
from torch.utils.data import Dataset, DataLoader
import pandas as pd

#create fully connected network
class NN(nn.Module):
  def __init__(self, input_size, num_classes):
    super(NN, self).__init__()
    self.outputs = nn.Linear(input_size, 1)

  def forward(self, x):
     x = self.outputs(x)
     return torch.sigmoid(x) 

#set device
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

#hyperparameters
input_size = 13 # 13 inputs
num_classes = 1 # heartdisease or not
learning_rate = 0.001
batch_size = 64
num_epochs = 1

#load data
class MyDataset(Dataset):
    def __init__(self, root, n_inp):
        self.df = pd.read_csv(root)
        self.data = self.df.to_numpy()
        self.x , self.y = (torch.from_numpy(self.data[:,:n_inp]),
                           torch.from_numpy(self.data[:,n_inp:]))
    def __getitem__(self, idx):
        return self.x[idx, :], self.y[idx,:]
    def __len__(self):
        return len(self.data)

train_dataset = MyDataset(&quot;heart.csv&quot;, input_size)
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle =True)
test_dataset = MyDataset(&quot;heart.csv&quot;, input_size)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle =True)

#initialize network
model = NN(input_size=input_size, num_classes=num_classes).to(device)

#loss and optimizer
criterion = nn.BCELoss()
optimizer = optim.Adam(model.parameters(), lr=learning_rate)

#train network
for epoch in range(num_epochs):
  for batch_idx, (data, targets) in enumerate(train_loader):
    #get data to cuda if possible
    data = data.to(device=device)
    targets = targets.to(device=device)

    #forward
    scores = model(data.float())
    targets = targets.float()
    loss = criterion(scores, targets)

    #backward
    optimizer.zero_grad()
    loss.backward()

    #grad descent or adam step
    optimizer.step()

#check accuracy of model
def check_accuracy(loader, model):
  num_correct = 0
  num_samples = 0
  model.eval()

  with torch.no_grad():
    for x, y in loader:
      x = x.to(device=device)
      y = y.to(device=device)

      scores = model(x.float())
      _, predictions = scores.max(1)
      num_correct += (predictions == y).sum()
      num_samples += predictions.size(0)
    
    print(&quot;Got {} / {} with accuracy {}&quot;.format(num_correct, num_samples, float(num_correct)/float(num_samples)*100))

  model.train()

print(&quot;checking accuracy on training data&quot;)
check_accuracy(train_loader, model)

print(&quot;checking accuracy on test data&quot;)
check_accuracy(test_loader, model)


Note: Don't fool yourself. A single linear layer + a sigmoid + BCE loss = logistic regression. This is a linear model, so just take note of that when referring to it as a &quot;neural network&quot;, which is a term usually reserved for similar networks but with at least one hidden layer and nonlinear activations.
The sigmoid layer at the end of your model's forward() function returns an (N,1)-sized tensor, where N is the batch size. In other words, it returns a scalar for every data point. Each scalar is a value between 0 and 1 (this is the range of the sigmoid function).
The idea is to interpret those scalars as probabilities corresponding to the positive class. Suppose 1 corresponds to heart disease, and 0 corresponds to no heart disease; heart disease is the positive class, and no heart disease is the negative class. Now suppose a score is 0.6. This might be interpreted as a 60% chance that the associated label is heart disease, and a 40% chance that the associated label is no heart disease. This interpretation of the sigmoid output is what motivates the BCE loss to begin with (it's ultimately just a negative log likelihood).
So what you might do is check if your scores are greater than 0.5. If so, predict heart disease. If not, predict no heart disease.
Right now, you're computing maximums from the scores across dimension 1, which does nothing because dimension 1 is already of size 1; taking the maximum of a single value simply gives you that value.
Try something like this:
def check_accuracy(loader, model):
  num_correct = 0
  num_samples = 0
  model.eval()

  with torch.no_grad():
    for x, y in loader:
      x = x.to(device=device)
      y = y.to(device=device)

      scores = model(x.float())
      // Create a Boolean tensor (True for scores &gt; 0.5, False for others)
      // and then cast it to a long tensor (Trues -&gt; 1, Falses -&gt; 0)
      predictions = (scores &gt; 0.5).long()
      num_correct += (predictions == y).sum()
      num_samples += predictions.size(0)
    
    print(&quot;Got {} / {} with accuracy {}&quot;.format(num_correct, num_samples, float(num_correct)/float(num_samples)*100))

  model.train()

You may also want to squeeze your prediction and target tensors to size (N) instead of (N,1), though I'm not sure it's necessary in your case.
