def cosine_warmup(epoch, max_epochs, base_lr, warmup=3):
if epoch < warmup:
return base_lr * (epoch + 1) / warmup
t = (epoch - warmup) / (max_epochs - warmup)
return base_lr * 0.5 * (1 + math.cos(math.pi * t))
def mixup(X, y, alpha):
lam = float(torch.distributions.Beta(alpha, alpha).sample())
perm = torch.randperm(X.shape[0], device=X.device)
return lam * X + (1 - lam) * X[perm], y, y[perm], lam
class RecipeTrainer(d2l.Trainer):
"""A Trainer that sets the learning rate from the model's schedule."""
def fit_epoch(self):
for group in self.optim.param_groups:
group['lr'] = cosine_warmup(self.epoch, self.max_epochs,
self.model.lr)
super().fit_epoch()
class ModernConvNeXt(ConvNeXt):
"""ConvNeXt under the modern recipe of the previous section."""
def __init__(self, lr=2e-3, num_classes=10, drop_path_max=0.0):
super().__init__(lr, num_classes, drop_path_max)
def configure_optimizers(self):
return torch.optim.AdamW(self.parameters(), lr=self.lr,
weight_decay=0.05)
def loss(self, y_hat, y):
return F.cross_entropy(y_hat, y, label_smoothing=0.1)
def training_step(self, batch):
X, y_a, y_b, lam = mixup(*batch, alpha=0.2)
y_hat = self(X)
l = lam * self.loss(y_hat, y_a) + (1 - lam) * self.loss(y_hat, y_b)
self.plot('loss', l, train=True)
return l
class CompactResNet18(d2l.Classifier):
"""A parameter-matched ResNet-18 with base width 35."""
def __init__(self, lr=2e-3, num_classes=10, base=35):
super().__init__()
self.save_hyperparameters()
channels = (base, 2 * base, 4 * base, 8 * base)
layers = [nn.Conv2d(1, base, 7, stride=2, padding=3),
nn.BatchNorm2d(base), nn.ReLU(),
nn.MaxPool2d(3, stride=2, padding=1)]
for i, c in enumerate(channels):
for j in range(2):
down = i > 0 and j == 0
layers.append(d2l.Residual(c, use_1x1conv=down,
strides=2 if down else 1))
layers += [nn.AdaptiveAvgPool2d((1, 1)), nn.Flatten(),
nn.Linear(channels[-1], num_classes)]
self.net = nn.Sequential(*layers)
configure_optimizers = ModernConvNeXt.configure_optimizers
loss = ModernConvNeXt.loss
training_step = ModernConvNeXt.training_step