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?