Hi @ptrblck Thank you for the reply (I owe you more than once at this point!)

When you say replacement=True doubles the length, do you mean it will allow duplication (up-sampling) of the minority classes?
That is what I have understood, although I’m not clear what torch.cat does and why you assign 99% of them to zeros and 1% to ones. I’ll post the code below, including an explanation of the dataloader.
Similarly that was what I understood, although I’m guessing how it works I’d like to be certain. Is the class weight being used to account for imbalance by correctly attributing propagation error in a better ratio? If so, shouldn’t the sampler account for that imbalance and make the use of weights unnecessary?
I have seen snippets of yours were you set the CrossEntropyLoss weights, the reduction='none' but then I am not very clear how you use the criterion and loss to correctly update the algorithm.

The dataloader I have is using IP patent forms (uploaded by users) which are very structured documents, with small deviation (especially after resizing). To give you an idea, I’ve calculated the mean and std for the entire data-set to be (after resizing to 224x224):
RGB mean: 0.9497, 0.9497, 0.9497
RGB std: 0.0363, 0.0363, 0.0363

Which is kinda telling the story (since those values are normalised) most images are white (because of the background being a white page) and some sparse pixels are usually black, or less often coloured (logos, etc.). I don’t know if it would help pre-processing by removing the background or other techniques which may help more.
The loader places data tuples in a list of (input.cuda().float(), output.cuda().long()) where input is the tensorified RGB image in 224x224 which is only resized and normalised. The output is a fixed class C length tensor, where each output activates one value (only one can be active so no multiclass).
The actual labels are saved in an self.index = OrderedDict of tuples to actual output index, e.g., (country, form) => output_index for simplicity’s shake, so at any point I can retrieve the actual label by looking at the output and vice versa, all trivial stuff so far.
I calculate the class samples by using the following:
    def class_samples(self):
        label_samples = [None] * self.num_outputs()
        for i in range(len(self.index)):
            key        = self.get_label(i)
            count      = self.labels.count(key)
            label_samples[i] = count
        return label_samples

    def num_outputs(self):
        output_size = len(self.index)
        assert output_size > 0
        return output_size

    def get_index(self, item: tuple) -> int:
        return self.index.get(item)

    def get_label(self, idx) -> tuple:
        for key, value in self.index.items():
            if value == idx:
                return key
        return None

You’ve already seen how I calculate class weights and then sample weights:
    def weights(self):
        sample_index   = self.class_samples()
        num_samples    = len(self)
        classes_weight = 1. / torch.tensor(sample_index, dtype=torch.float)
        target         = torch.cat((torch.zeros(int(num_samples * 0.99), dtype=torch.long),
                                    torch.ones(int(num_samples * 0.01), dtype=torch.long)))
        #
        # I am unsure what this does, it appears to be populating the samples weight based
        # on the classes weights ? However, I would assume that
        # the correct way of doing this would be to assign each sample, the correct corresponding
        # weight, based on which class it belongs to.
        #
        samples_weight = torch.tensor([classes_weight[t] for t in target])
        return (samples_weight, classes_weight)

Interestingly, when I unit test it, I see that one sample is not indexed/missing:
samples weights: torch.Size([4375]) 
classes weights: torch.Size([13])
total samples: 4376

The weights method is invoked before training:
(samples_w, classes_w) = dataset.weights()
classes_w = classes_w.cuda().float()

In the training script I do allow replacement and I do pass the class weights to CE Loss:
criterion = nn.CrossEntropyLoss(weight=classes_w, reduction='none').cuda()
sampler = torch.utils.data.sampler.WeightedRandomSampler(samples_w,
                                                         len(train_ds),
                                                         replacement=True)

Finally, when I do the back-prop:
for epoch in range(args.epochs):
    # batch/step iteration
    for i, (inputs, labels) in enumerate(train_loader):
        optimizer.zero_grad()
        logits        = net(inputs)
        ideal         = labels.argmax(1)
        loss          = criterion(logits, ideal)
        loss.mean().backward()
        optimizer.step()
    #
    scheduler.step()

Any help is greatly appreciated!