Hi Bruno!
I do see what appears to be an error in your conversion from a
multi-class to a binary model (although I can’t explain the entirety
of your results).
Specifically, I believe your use of argmax() to convert your probabilistic
predictions into “hard” yes-no binary predictions is incorrect, and will
always yield 0.



 CasellaJr:

This is my net:
class VGG16(nn.Module):

    def __init__(self, num_classes):
        ...
        self.classifier = nn.Sequential(
            ...
            nn.Linear(4096, num_classes),
            nn.Sigmoid() 
        )



When you create your net, presumably by instantiating your VGG16,
you don’t show us what value you use for num_classes.  I assume
that you use num_classes = 1.  (Otherwise I would expect that
BCELoss would throw an error when you call it.)
Therefore I assume that the output of your model has shape
[nBatch, 1].  Note, even though you are performing a binary
classification, I assume that you do not use num_classes = 2
and that your output does not have shape [nBatch, 2].
(In the case of a ten-class classifier, your model output would have
shape [nBatch, 10].)


                    pred = net(input)
                    labels = labels.unsqueeze(1)
                    labels = labels.float()
                    loss = criterion(pred, labels)



Here I believe that labels starts out having shape [nBatch], with no
class dimension, as would be appropriate for a multi-class problem
using CrossEntropyLoss.  However, BCELoss requires that pred
and labels have the same shape, and I believe that pred has
shape [nBatch, 1] (where the singleton dimension is not necessary,
but is also not incorrect).
That is, pred has shape [nBatch, num_classes], which is appropriate
for both a multi-class problem with CrossEntropyLoss and for a plain
binary problem (or a multi-label, multi-class problem) with BCELoss,
with, in this case, num_classes = 1.
Therefore you unsqueeze() labels in order that labels have the
same shape as pred, namely, [nBatch, 1].
So far, so good.


                    _,pred_labels = pred.max(1)
                    batch_accuracy = (pred_labels == labels).sum().item()/input.size(0)



Just for clarity, note that _,pred_labels = pred.max(1) is the same
as pred_labels = pred.argmax(1).
So your predicted class label is the index of the largest predicted
value along the class dimension.  pred_labels takes on integer
values in [0, num_classes - 1] (inclusive).  This is correct for
the multi-class case, but in the binary case (implemented with
num_classes = 1), pred_labels will always have value 0.
So the predictions you use for computing your accuracy are always
predicting “0”.  (Note that this issue does not affect the predictions
you use for computing your loss.  As far as I can tell, your loss should
be correct and your training should work.)
What’s odd here is that your computed accuracy of about 31% suggests
that your dataset is rather unbalanced.  If my theory here is right, this
means that your dataset consists of about 31% even-number digits,
and about 69% odd-number digits.  This isn’t impossible, but it does
seem a bit peculiar.


Edit: increasing batch size from 64 to 128, accuracy reaches to 60% and remains constant…


That your computed accuracy depends on your batch size is very
strange.  I don’t see any batch-size-dependent error in your accuracy
calculation.  You don’t show us your loaders, so one could speculate
that something weird is going on there.
A couple of minor comments:
First, when you get this otherwise working, you should consider
using BCEWithLogitsLoss and getting rid of the final Sigmoid
layer in your classifier block.  BCELoss with the Sigmoid and
BCEWithLogitsLoss without are mathematically equivalent, but
BCEWithLogitsLoss is numerically more stable.
Second, this following code is not incorrect, but it’s unnecessarily
complicated:


Here I change the targets into binary:
for i in range(10):
  idx = (train_set.targets==i)
  if (i == 0) or ((i % 2) == 0): train_set.targets[idx] = 0

  else: train_set.targets[idx] = 1



This all can be simplified down to:
train_set.targets = train_set.targets % 2

Best.
K. Frank