import torch
from IPython import display
from d2l import torch as d2l
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
num_inputs = 784
num_outputs = 10
W = torch.normal(0, 0.01, size=(num_inputs, num_outputs), requires_grad=True)
b = torch.zeros(num_outputs, requires_grad=True)
def softmax(X):
X_exp = torch.exp(X)
somme = X_exp.sum(1, keepdim=True)
return X_exp / somme # The broadcasting mechanism is applied here
## à titre d'exemple
X = torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
X.sum(0, keepdim=True), X.sum(1, keepdim=True)
## tester la fonction softmax
X = torch.normal(0, 1, (2, 5))
X_prob = softmax(X)
X_prob, X_prob.sum(1)
def net(X):
return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b)
y = torch.tensor([0, 2])
y_hat = torch.tensor([[0.1, 0.3, 0.6], [0.3, 0.2, 0.5]])
y_hat[[0, 1], y]
def cross_entropy(y_hat, y):
return - torch.log(y_hat[range(len(y_hat)), y])
cross_entropy(y_hat, y)
def accuracy(y_hat, y): #@save
"""Compute the number of correct predictions."""
if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
y_hat = y_hat.argmax(axis=1)
cmp = y_hat.type(y.dtype) == y
return float(cmp.type(y.dtype).sum())
accuracy(y_hat, y) / len(y)
def evaluate_accuracy(net, data_iter): #@save
"""Compute the accuracy for a model on a dataset."""
if isinstance(net, torch.nn.Module):
net.eval() # Set the model to evaluation mode
metric = Accumulator(2) # No. of correct predictions, no. of predictions
with torch.no_grad():
for X, y in data_iter:
metric.add(accuracy(net(X), y), y.numel())
return metric[0] / metric[1]
Ici, Accumulator est une classe utilitaire permettant d'accumuler des sommes sur plusieurs variables. Dans la fonction evaluate_accuracy ci-dessus, nous créons 2 variables dans l'instance Accumulator pour stocker le nombre de prédictions correctes et le nombre de prédictions, respectivement. Ces deux variables seront accumulées au fur et à mesure que nous itérons sur l'ensemble de données.
class Accumulator: #@save
"""For accumulating sums over `n` variables."""
def __init__(self, n):
self.data = [0.0] * n
def add(self, *args):
self.data = [a + float(b) for a, b in zip(self.data, args)]
def reset(self):
self.data = [0.0] * len(self.data)
def __getitem__(self, idx):
return self.data[idx]
evaluate_accuracy(net, test_iter)
La boucle d'apprentissage de la régression softmax devrait vous sembler étonnamment proche de notre implémentation de la régression linéaire. Ici, nous remanions l'implémentation pour la rendre réutilisable. Tout d'abord, nous définissons une fonction pour s'entraîner pendant une époque (epoch). Notez que updater est une fonction générale pour mettre à jour les paramètres du modèle, qui accepte la taille du minibatch comme argument. Il peut s'agir d'un wrapper de la fonction d2l.sgd ou d'une fonction d'optimisation intégrée.
def train_epoch_ch3(net, train_iter, loss, updater): #@save
"""The training loop"""
# Set the model to training mode
if isinstance(net, torch.nn.Module):
net.train()
# Sum of training loss, sum of training accuracy, no. of examples
metric = Accumulator(3)
for X, y in train_iter:
# Compute gradients and update parameters
y_hat = net(X)
l = loss(y_hat, y)
if isinstance(updater, torch.optim.Optimizer):
# Using PyTorch in-built optimizer & loss criterion
updater.zero_grad()
l.sum().backward()
updater.step()
else:
# Using custom built optimizer & loss criterion
l.sum().backward()
updater(X.shape[0])
metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
# Return training loss and training accuracy
return metric[0] / metric[2], metric[1] / metric[2]
Avant de montrer l'implémentation de la fonction d'entraînement, nous définissons une classe utilitaire qui trace les données en animation. Encore une fois, c'est une classe qui est proposé sur stackoverflow et pas une création personnelle.
class Animator: #@save
"""For plotting data in animation."""
def __init__(self, xlabel=None, ylabel=None, legend=None, xlim=None,
ylim=None, xscale='linear', yscale='linear',
fmts=('-', 'm--', 'g-.', 'r:'), nrows=1, ncols=1,
figsize=(3.5, 2.5)):
# Incrementally plot multiple lines
if legend is None:
legend = []
d2l.use_svg_display()
self.fig, self.axes = d2l.plt.subplots(nrows, ncols, figsize=figsize)
if nrows * ncols == 1:
self.axes = [self.axes, ]
# Use a lambda function to capture arguments
self.config_axes = lambda: d2l.set_axes(
self.axes[0], xlabel, ylabel, xlim, ylim, xscale, yscale, legend)
self.X, self.Y, self.fmts = None, None, fmts
def add(self, x, y):
# Add multiple data points into the figure
if not hasattr(y, "__len__"):
y = [y]
n = len(y)
if not hasattr(x, "__len__"):
x = [x] * n
if not self.X:
self.X = [[] for _ in range(n)]
if not self.Y:
self.Y = [[] for _ in range(n)]
for i, (a, b) in enumerate(zip(x, y)):
if a is not None and b is not None:
self.X[i].append(a)
self.Y[i].append(b)
self.axes[0].cla()
for x, y, fmt in zip(self.X, self.Y, self.fmts):
self.axes[0].plot(x, y, fmt)
self.config_axes()
display.display(self.fig)
display.clear_output(wait=True)
La fonction d'apprentissage suivante entraîne un modèle net sur un jeu de données d'apprentissage auquel on accède via train_iter pendant plusieurs époques (epoch), spécifiées par num_epochs. À la fin de chaque epoch, le modèle est évalué sur un jeu de données de test auquel on accède via test_iter. Nous allons utiliser la classe Animator pour visualiser la progression de l'apprentissage.
def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater): #@save
"""Train a model"""
animator = Animator(xlabel='epoch', xlim=[1, num_epochs], ylim=[0.3, 0.9],
legend=['train loss', 'train acc', 'test acc'])
for epoch in range(num_epochs):
train_metrics = train_epoch_ch3(net, train_iter, loss, updater)
test_acc = evaluate_accuracy(net, test_iter)
animator.add(epoch + 1, train_metrics + (test_acc,))
train_loss, train_acc = train_metrics
assert train_loss < 0.5, train_loss
assert train_acc <= 1 and train_acc > 0.7, train_acc
assert test_acc <= 1 and test_acc > 0.7, test_acc
Pour une implémentation from scratch, nous utilisons la descente de gradient stochastique en minibatchs pour optimiser la fonction de perte du modèle avec un taux d'apprentissage de 0.1.
lr = 0.1
def updater(batch_size):
return d2l.sgd([W, b], lr, batch_size)
num_epochs = 10
train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, updater)
Maintenant que l'apprentissage est terminé, notre modèle est prêt à classifer des images. Pour une série d'images, nous allons comparer leurs étiquettes réelles (première ligne du texte de sortie) et les prédictions du modèle (deuxième ligne du texte de sortie).
def predict_ch3(net, test_iter, n=6): #@save
"""Predict labels"""
for X, y in test_iter:
break
trues = d2l.get_fashion_mnist_labels(y)
preds = d2l.get_fashion_mnist_labels(net(X).argmax(axis=1))
titles = [true +'\n' + pred for true, pred in zip(trues, preds)]
d2l.show_images(
X[0:n].reshape((n, 28, 28)), 1, n, titles=titles[0:n])
predict_ch3(net, test_iter)
Avec la régression softmax, nous pouvons entraîner des modèles pour la classification multiclasse.
La boucle d'entraînement de la régression softmax est très similaire à celle de la régression linéaire : récupérer et lire les données, définir les modèles et les fonctions de perte, puis entraîner les modèles à l'aide d'algorithmes d'optimisation. Comme nous allons le voir bientôt, la plupart des modèles d'apprentissage profond courants ont des procédures d'entraînement similaires.