For classification we change two things. The output layer has one unit per class, and the loss is the cross-entropy.
In torch the two are combined. CrossEntropyLoss expects the raw outputs, without a softmax, and applies the softmax internally. This is numerically more stable than doing it in two steps.
The network
import torchmodel = torch.nn.Sequential( torch.nn.Linear(784, 128), torch.nn.ReLU(), torch.nn.Linear(128, 64), torch.nn.ReLU(), torch.nn.Linear(64, 10), # 10 outputs, one per digit, no softmax)loss_fn = torch.nn.CrossEntropyLoss()opt = torch.optim.Adam(model.parameters(), lr=1e-3)
Xt = torch.tensor(X_train)yt = torch.tensor(y_train)loader = torch.utils.data.DataLoader( torch.utils.data.TensorDataset(Xt, yt), batch_size=128, shuffle=True)for epoch inrange(10):for xb, yb in loader: opt.zero_grad() loss = loss_fn(model(xb), yb) # raw outputs go in loss.backward() opt.step()print(epoch, round(float(loss), 4))
Evaluating
with torch.no_grad(): logits = model(torch.tensor(X_test)) pred = logits.argmax(dim=1).numpy()print("test accuracy:", (pred == y_test).mean())
A network of this size reaches about 98 percent on MNIST. A multinomial logistic regression reaches about 92 percent. The difference is the learned features.
The confusion matrix
from sklearn.metrics import confusion_matriximport pandas as pdpd.DataFrame(confusion_matrix(y_test, pred))
Look at where the errors are. Fours confused with nines, threes with fives. These are the pairs that people also confuse, which is a sign that the network has learned something about shape rather than about pixel positions.
A small demonstration
The code above needs a download and a few minutes of training, so it is not run on this page. Here is a smaller version on the digits data set that ships with sklearn, which shows the same steps.