I recently migrated to pytorch from TF, and now I’m facing a very stupid and embarrassing issue.
I’m trying to do a binary classification on an Xray dataset. The directory structure is as follows:
-test
---Normal
---Pneumonia
-train
---Normal
---Pneumonia
So I’m using the ImageFolder from torchvision. And here’s the augmentation and dataset class:
train_data = torchvision.datasets.ImageFolder('/kaggle/input/covid19-xray-dataset-train-test-sets/xray_dataset_covid19/train/')
val_data = torchvision.datasets.ImageFolder('/kaggle/input/covid19-xray-dataset-train-test-sets/xray_dataset_covid19/test/')
IMG_SIZE = 512
aug = A.Compose([A.Resize(IMG_SIZE, IMG_SIZE),
A.RandomCrop(IMG_SIZE, IMG_SIZE),
A.HorizontalFlip(p=0.5),
A.VerticalFlip(p=0.5),
A.Rotate(10),
A.Blur(),
A.RandomGamma(),
A.Sharpen(),
A.GaussNoise(p=0.1),
A.CLAHE(),
A.Normalize(mean=0, std=1),
ToTensorV2()])
class DataReader(Dataset):
def __init__(self, dataset, transform):
self.dataset = dataset
self.transform = transform
def __getitem__(self, index):
image = self.dataset[index][0]
label = self.dataset[index][1]
image = np.array(image)
# image = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
image = self.transform(image=image)['image']
return image, label
def __len__(self):
return len(self.dataset)
The model class is as follows (I omitted the on_validation_epoch_end and on_train_epoch_end; nothing crazy going on there):
class XrayModel(LightningModule):
def __init__(self):
super(XrayModel, self).__init__()
self.model = models.efficientnet_b0(pretrained=True)
self.model.classifier[1] = nn.Linear(in_features=1280, out_features=1)
self.lr = 1e-4
self.batch_size = 32
self.numworker = 2
self.criterion = nn.BCEWithLogitsLoss()
self.acc = torchmetrics.Accuracy(task='binary')
self.trainacc, self.valacc = [], []
self.trainloss, self.valloss = [], []
def forward(self, x):
x = self.model(x)
return x
def configure_optimizers(self):
opt = torch.optim.AdamW(params=self.parameters(), lr=self.lr)
scheduler=ReduceLROnPlateau(opt,mode='min', factor=0.75, patience=5)
return {'optimizer': opt, 'lr_scheduler':scheduler, 'monitor':'val_loss'}
def train_dataloader(self):
train_loader = DataLoader(DataReader(train_data, aug), shuffle=True,
batch_size=self.batch_size, num_workers=self.numworker)
return train_loader
def training_step(self, batch, batch_idx):
image, label = batch
pred = self(image)
loss = self.criterion(pred.flatten(), label.float())
acc = self.acc(pred.flatten(), label)
return loss
def val_dataloader(self):
val_loader = DataLoader(DataReader(val_data, aug), shuffle=False, batch_size=self.batch_size, num_workers=self.numworker)
return val_loader
def validation_step(self, batch, batch_idx):
image, label = batch
pred = self(image)
loss = self.criterion(pred.flatten(), label.float())
acc = self.acc(pred.flatten(), label)
self.log('val_loss', loss)
self.log('val_acc', acc)
return loss
The problem: It’s as if the model doesn’t learn class ‘1’, and only predicts class ‘0’. Or maybe I’m doing something wrong when getting the preds?!
I mean, the acc for both training and validation is somewhat good, but the confusion matrix and ROCAUC score are at 0.5! Here’s the confusion matrix and code:
val_loader = DataLoader(DataReader(val_data, aug), shuffle=False, batch_size=20)
y_pred, y_true, y_probs = [], [], []
with torch.no_grad():
for batch in val_loader:
x, labels = batch
outputs = model(x)
_, predicted = torch.max(outputs, 1)
y_pred.extend(predicted.tolist())
y_true.extend(labels.tolist())
probabilities = torch.softmax(outputs, dim=1)
y_probs.extend(probabilities.tolist())
confusion = confusion_matrix(y_true, y_pred)
auroc = roc_auc_score(y_true, y_probs)
print(auroc)
print(confusion)
>>> 0.5000
>>> [[20, 0],
[20, 0]]
if the actual label is ‘0’, the model predicts 100% correctly. But if it’s ‘0’, then its 0%.
maybe I’m doing sth wrong in the DataReader class when returning the label?!
Maybe I should one-hot encode them?