Dropout and weight decay
Two standard ways to fight overfitting change how training works, not the data.
Dropout sets a random share p of a layer's outputs to zero at every training step, so no unit can rely on one particular other. The survivors are divided by 1 - p, so each output keeps the same average.
import torch
from torch import nn
torch.manual_seed(0)
drop = nn.Dropout(p=0.5)
x = torch.ones(8)
drop.train()
print(drop(x)) # zeros and twos
drop.eval()
print(drop(x)) # all onesWhen you measure or predict, dropout must be off. model.train() and model.eval() switch every layer of a model between the two modes; a new model starts in training mode.
Weight decay pulls every weight a little toward zero at each step, so the model keeps large weights only where they really lower the loss. Optimizers take it as an argument: torch.optim.SGD(params, lr=0.1, weight_decay=0.01).
Your task
Write dropout(x, p, training): when training is true, zero each element with probability p (use torch.rand_like) and divide the rest by 1 - p, otherwise return x unchanged. Write fit(x, y, decay, steps=200) training an nn.Linear with torch.optim.SGD at lr=0.1 and weight_decay=decay on the mean squared error, and predict(model, x) that switches model to eval mode and returns its output without gradients.
The explanation opens here once every check passes.