๐ŸŽ“ A1Learner
๐Ÿ”

PyTorch Weighted Sampling

Weighted sampling draws rows with unequal chances, so rare rows appear more often. A WeightedRandomSampler takes one weight per row and draws by those weights.

The imbalance itself

torch.bincount counts the rows per label. Class 0 holds ninety rows and class 2 holds two, so plain sampling mostly serves class 0.

labels = torch.tensor([0] * 90 + [1] * 8 + [2] * 2)
counts = torch.bincount(labels, minlength=3)
print("Rows per class:", counts.tolist())

One weight per row

The weight is one over the class count. Rare rows weigh more, and each class then weighs the same in total.

class_weights = 1.0 / counts.float()
print("Class weights:", [round(float(w), 4) for w in class_weights])
row_weights = class_weights[labels]
print("Weight of a class 0 row:", round(float(row_weights[0]), 4))
print("Weight of a class 2 row:", round(float(row_weights[-1]), 4))

A seeded generator repeats the draws

Most drawn indices point at the rare rows at the end of the dataset. The same seed repeats the same draws.

def first_draws():
    gen = torch.Generator().manual_seed(7)
    sampler = WeightedRandomSampler(
        weights=row_weights.double(), num_samples=10, replacement=True, generator=gen
    )
    return list(sampler)

print("First run: ", first_draws())
print("Second run:", first_draws())
print("Identical:", first_draws() == first_draws())

Example

import torch
from torch.utils.data import WeightedRandomSampler

torch.manual_seed(0)

# 100 rows. Class 0 is common. Classes 1 and 2 are rare.
labels = torch.tensor([0] * 90 + [1] * 8 + [2] * 2)

counts = torch.bincount(labels, minlength=3)
print("Rows per class:", counts.tolist())

draws = 300

plain_gen = torch.Generator().manual_seed(7)
plain = WeightedRandomSampler(
    weights=torch.ones(len(labels), dtype=torch.double),
    num_samples=draws,
    replacement=True,
    generator=plain_gen,
)
plain_counts = torch.bincount(labels[list(plain)], minlength=3)
print("Drawn without weights:", plain_counts.tolist())

# One weight per row. Rare rows weigh more, so each class weighs the same in total.
class_weights = 1.0 / counts.float()
row_weights = class_weights[labels]
weighted_gen = torch.Generator().manual_seed(7)
weighted = WeightedRandomSampler(
    weights=row_weights.double(),
    num_samples=draws,
    replacement=True,
    generator=weighted_gen,
)
weighted_counts = torch.bincount(labels[list(weighted)], minlength=3)
print("Drawn with weights:", weighted_counts.tolist())

Key points

  • WeightedRandomSampler: a sampler that draws each row with a chance set by its weight
  • Use one weight per row, often one over the row's class count
  • Replacement: a drawn row goes back, so it can be drawn again
  • Pass a seeded generator so the draws repeat exactly

In this example, the labels describe 100 rows. Class 0 holds 90 rows, class 1 holds 8, and class 2 holds 2. The first sampler gives every row weight 1, so 300 draws land close to the row counts. Class 0 takes 263 draws, and class 2 takes 7. The second sampler weights each row by one over its class count. The same 300 draws now spread across the classes. Class 0 takes 99, class 1 takes 90, and class 2 takes 111. Both samplers use a seeded generator, so the counts repeat exactly on every run.

Note

Weighted sampling changes which rows training sees, but it does not create new data.

Try it Yourself

Edit the code, then press Run.

Running is simulated ยท Python Playground

Exercise

A dataset holds 90 rows of class 0 and 2 rows of class 2. What weight should class 2 rows get, relative to class 0 rows?