๐ŸŽ“ A1Learner
๐Ÿ”

PyTorch Advanced Capstone

The advanced capstone is one pipeline that trains the transformer classifier and exports it. Accuracy comes from a held-out set, and the artifact size comes from the file on disk.

The loaded model needs no class

torch.jit.load rebuilds the model from the file alone. The ReviewClassifier class is never imported at load time.

print(loaded(test_x[:2]).shape)

Count the parameters

The classifier is small by design. Its parameter total fits in a phone-sized file with room to spare.

total = sum(p.numel() for p in model.parameters())
print(f"Parameters: {total}")

The size in kilobytes

The byte count from the closing example reads as kilobytes with one division. The scripted file holds code and weights together.

print(f"Scripted file size: {os.path.getsize('capstone_model.pt') / 1024:.1f} KB")

Example

import os
import random

import torch
from torch import nn

POSITIVE = ["great", "lovely", "bright", "fun", "warm"]
NEGATIVE = ["awful", "dull", "cold", "boring", "grim"]
FILLER = ["the", "a", "movie", "story", "scene", "really", "very"]
VOCAB = POSITIVE + NEGATIVE + FILLER + ["not"]
WORD_ID = {word: i for i, word in enumerate(VOCAB)}
LENGTH = 5


def make_data(rng, n_plain, n_negated):
    data = []
    for _ in range(n_plain):
        label = rng.randrange(2)
        side = POSITIVE if label == 1 else NEGATIVE
        words = [rng.choice(side) for _ in range(3)]
        words += [rng.choice(FILLER) for _ in range(LENGTH - 3)]
        rng.shuffle(words)
        data.append((words, label))
    for _ in range(n_negated):
        side_label = rng.randrange(2)
        side = POSITIVE if side_label == 1 else NEGATIVE
        rest = [rng.choice(side) for _ in range(2)]
        rest += [rng.choice(FILLER) for _ in range(LENGTH - 3)]
        rng.shuffle(rest)
        data.append((["not"] + rest, 1 - side_label))
    rng.shuffle(data)
    return data


def encode(data):
    x = torch.tensor([[WORD_ID[w] for w in words] for words, _ in data])
    y = torch.tensor([label for _, label in data])
    return x, y


class ReviewClassifier(nn.Module):
    def __init__(self):
        super().__init__()
        self.embedding = nn.Embedding(len(VOCAB), 16)
        self.position = nn.Embedding(LENGTH, 16)
        layer = nn.TransformerEncoderLayer(
            d_model=16, nhead=2, dim_feedforward=32,
            dropout=0.0, batch_first=True)
        self.encoder = nn.TransformerEncoder(layer, num_layers=1)
        self.head = nn.Linear(16, 2)

    def forward(self, ids):
        h = self.encoder(self.embedding(ids) + self.position.weight)
        return self.head(h.mean(dim=1))


rng = random.Random(42)
train_x, train_y = encode(make_data(rng, 96, 64))

torch.manual_seed(0)
model = ReviewClassifier()
optimizer = torch.optim.Adam(model.parameters(), lr=0.02)
loss_fn = nn.CrossEntropyLoss()

for step in range(60):
    optimizer.zero_grad()
    loss = loss_fn(model(train_x), train_y)
    loss.backward()
    optimizer.step()

print(f"Final training loss: {float(loss):.4f}")

test_rng = random.Random(7)
test_x, test_y = encode(make_data(test_rng, 120, 80))

model.eval()
with torch.no_grad():
    predictions = model(test_x).argmax(dim=1)
accuracy = float((predictions == test_y).float().mean())
print(f"Held-out accuracy: {accuracy:.3f}")

scripted = torch.jit.script(model)
scripted.save("capstone_model.pt")
size = os.path.getsize("capstone_model.pt")
print(f"Scripted file size: {size} bytes")

loaded = torch.jit.load("capstone_model.pt")
loaded.eval()
with torch.no_grad():
    loaded_predictions = loaded(test_x).argmax(dim=1)
loaded_accuracy = float((loaded_predictions == test_y).float().mean())
print(f"Loaded model accuracy: {loaded_accuracy:.3f}")
print(f"Predictions match: {bool((loaded_predictions == predictions).all())}")

Key points

  • The classifier is the transformer design from the Advanced transformer entry at a reduced size
  • TorchScript scripting compiles the model's code into a file that loads with no class definition
  • Held-out set: 200 seeded reviews that the training never saw
  • Artifact: the saved scripted model file, capstone_model.pt in the working folder
  • The loaded model is scored again, and its predictions must match the original model's exactly
  • File size is measured with os.path.getsize after the save, never estimated

In this example, the pipeline trains the compact transformer classifier for 60 steps on 160 seeded reviews. The final training loss prints at 0.0001. A held-out set of 200 fresh reviews scores the trained model at 0.985 accuracy. torch.jit.script then compiles the model, and the scripted file saves as capstone_model.pt. Its measured size is 88,816 bytes. The program loads the file back with torch.jit.load, with no class definition involved. The loaded model scores the same 0.985, and its predictions match the original model's on every held-out row. What was verified is the export round trip, since the loaded predictions agree exactly. What was only measured is the accuracy and the byte size, since both belong to this seed and this machine.

Note

Accuracy and file size are measured facts about this run, and a new seed would move both slightly.

Try it Yourself

Edit the code, then press Run.

Running is simulated ยท Python Playground

Exercise

Which claim in the capstone is verified, rather than only measured?