Régression linéaire PyTorch

La régression linéaire est l'un des algorithmes d'apprentissage automatique les plus fondamentaux, utilisé pour prédire une valeur continue. C'est une méthode d'analyse de régression simple et courante qui vise à prédire la sortie en ajustant une fonction linéaire.

Pour un problème de régression linéaire simple, le modèle peut être exprimé comme suit :

  • y est la valeur prédite (valeur cible).
  • \(x_1\), \(x_2\), \(x_n\) sont des caractéristiques d'entrée.
  • \(w_1\), \(w_2\), \(w_n\) sont les poids à apprendre (paramètres du modèle).
  • b est le terme de biais.

Dans PyTorch, un modèle de régression linéaire peut être implémenté en héritant denn.Modulela classe. Nous utiliserons un exemple simple pour expliquer en détail comment implémenter un modèle de régression linéaire à l'aide de PyTorch.


Préparation des données

Nous préparons d'abord quelques données factices pour entraîner notre modèle de régression linéaire. Ici, nous pouvons générer un jeu de données avec une relation linéaire simple, où chaque échantillon possède deux caractéristiques \(x_1\), \(x_2\).

Exemple

import torch
import numpy as np
import matplotlib.pyplot as plt

# Graine aléatoire pour garantir des résultats cohérents d'une exécution à l'autre
torch.manual_seed(42)

# Générer des données d'entraînement
X = torch.randn(100, 2)  # 100 échantillons, chacun avec 2 caractéristiques
true_w = torch.tensor([2.0, 3.0])  # Supposons les vrais poids
true_b = 4.0  # Terme de biais
Y = X @ true_w + true_b + torch.randn(100) * 0.1  # Ajouter du bruit

# Afficher quelques données
print(X[:5])
print(Y[:5])

La sortie est la suivante :

tensor([[ 1.9269,  1.4873],
        [ 0.9007, -2.1055],
        [ 0.6784, -1.2345],
        [-0.0431, -1.6047],
        [-0.7521,  1.6487]])
tensor([12.4460, -0.4663,  1.7666, -0.9357,  7.4781])

Ce code crée un jeu de données linéaire avec du bruit.

  • L'entrée X est une matrice 100x2, chaque échantillon a deux caractéristiques.
  • La sortie Y est générée à partir des vrais poids et du biais, avec un peu de bruit aléatoire ajouté.
  • Utilisationtorch.manual_seed(42)garantit des résultats cohérents entre les exécutions, ce qui facilite le débogage et la reproduction.

Définir le modèle de régression linéaire

Nous pouvons définir un modèle de régression linéaire simple en héritantnn.ModuleDans PyTorch, le cœur de la régression linéaire est lenn.Linear()couche, qui gère automatiquement l'initialisation des poids et des biais.

Exemple

import torch.nn as nn

# Définir le modèle de régression linéaire
class LinearRegressionModel(nn.Module):
    def __init__(self):
        super(LinearRegressionModel, self).__init__()
        # Définir une couche linéaire, l'entrée comporte 2 caractéristiques, la sortie est 1 prédiction
        self.linear = nn.Linear(2, 1)  # Dimension d'entrée 2, dimension de sortie 1

    def forward(self, x):
        return self.linear(x)  # Propagation avant, retourner le résultat de prédiction

# Créer une instance du modèle
model = LinearRegressionModel()

Ici,nn.Linear(2, 1)représente une couche linéaire, qui possède 2 caractéristiques d'entrée et 1 sortie.forwardLa méthode définit comment effectuer la propagation avant à travers cette couche.

Remarque : nn.Linearcrée automatiquement la matrice de poids et le vecteur de biais, sans avoir besoin de les définir manuellement.


Définition de la fonction de perte et de l'optimiseur

La fonction de perte courante pour la régression linéaire estMean Squared Error Loss (MSELoss), utilisée pour mesurer la différence entre les valeurs prédites et les valeurs réelles. PyTorch fournit une fonction MSELoss prête à l'emploi.

Nous utiliseronsSGD (descente de gradient stochastique)ouAdamcomme optimiseur pour minimiser la fonction de perte.

Exemple

# Fonction de perte (erreur quadratique moyenne)
criterion = nn.MSELoss()

# Optimiseur (avec SGD ou Adam)
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)  # Taux d'apprentissage défini à 0.01

# Vous pouvez aussi utiliser l'optimiseur Adam
# optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
Composant Description
MSELoss Calculez l'erreur quadratique moyenne entre les valeurs prédites et réelles, formule : \(\frac{1}{n}\sum(y_{pred} - y_{true})^2\)
SGD Utilisez la descente de gradient stochastique pour mettre à jour les paramètres, le taux d'apprentissage contrôle la taille du pas de chaque mise à jour
Adam Optimiseur à taux d'apprentissage adaptatif, converge généralement plus rapidement

Entraînement du modèle

Pendant l'entraînement, nous effectuerons les étapes suivantes :

  1. Utilisez les données d'entrée X pour la propagation avant afin d'obtenir des prédictions.
  2. Calculer la perte (différence entre les valeurs prédites et réelles).
  3. Utiliser la rétropropagation pour calculer les gradients.
  4. Mettre à jour les paramètres du modèle (poids et biais).

Nous allons entraîner le modèle pendant 1000 époques et afficher la perte toutes les 100 époques.

Exemple

# Entraîner le modèle
num_epochs = 1000  # Entraîner pendant 1000 époques
for epoch in range(num_epochs):
    model.train()  # Passer le modèle en mode entraînement

    # Propagation avant
    predictions = model(X)  # Le modèle produit des prédictions
    loss = criterion(predictions.squeeze(), Y)  # Calculer la perte (notez que les prédictions doivent être réduites à 1D)

    # Rétropropagation
    optimizer.zero_grad()  # Effacer les gradients précédents
    loss.backward()  # Calculer les gradients
    optimizer.step()  # Mettre à jour les paramètres du modèle

    # Afficher la perte
    if (epoch + 1) % 100 == 0:
        print(f'Epoch [{epoch + 1}/1000], Loss: {loss.item():.4f}')
  • predictions.squeeze(): Réduire la sortie du modèle d'un tenseur 2D à 1D, car la valeur cibleYest un tableau à une dimension.
  • optimizer.zero_grad(): Avant chaque rétropropagation, vous devez effacer les gradients précédents, sinon les gradients s'accumuleront.
  • loss.backward(): Calcule automatiquement les gradients de tous les paramètres entraînables.
  • optimizer.step(): Met à jour les poids et le biais en fonction des gradients calculés.

Mode entraînement vs mode évaluation :Appel dans la boucle d'entraînementmodel.train()est nécessaire. Bien que cet exemple n'utilise pas Dropout ou BatchNorm, développer cette habitude est important pour les modèles complexes.


Évaluation du modèle

Après l'entraînement, nous pouvons évaluer les performances du modèle en inspectant ses poids et son biais. Nous pouvons également faire des prédictions sur de nouvelles données et les comparer avec les valeurs réelles.

Exemple

# Afficher les poids et le biais entraînés
print(f'Poids prédit: {model.linear.weight.data.numpy()}')
print(f'Biais prédit: {model.linear.bias.data.numpy()}')

# Faire des prédictions sur de nouvelles données
with torch.no_grad():  # Pas besoin de calculer les gradients pendant l'évaluation
    predictions = model(X)

# Visualiser les prédictions et les valeurs réelles
plt.scatter(X[:, 0], Y, color='blue', label='Valeurs réelles')
plt.scatter(X[:, 0], predictions, color='red', label='Prédictions')
plt.legend()
plt.show()
  • model.linear.weight.dataetmodel.linear.bias.data: Ces attributs stockent les poids et le biais du modèle.
  • torch.no_grad(): En mode évaluation, il n'est pas nécessaire de calculer les gradients, ce qui économise de la mémoire et améliore la vitesse d'inférence.

Exemple de code complet

Voici le code complet pour toutes les étapes ci-dessus, intégré et prêt à être exécuté :

Exemple

import torch
import torch.nn as nn
import matplotlib.pyplot as plt

# 1. Préparer les données
torch.manual_seed(42)
X = torch.randn(100, 2)  # 100 échantillons, 2 caractéristiques
true_w = torch.tensor([2.0, 3.0])
true_b = 4.0
Y = X @ true_w + true_b + torch.randn(100) * 0.1

# 2. Définir le modèle
class LinearRegressionModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.linear = nn.Linear(2, 1)

    def forward(self, x):
        return self.linear(x)

model = LinearRegressionModel()

# 3. Définir la fonction de perte et l'optimiseur
criterion = nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

# 4. Entraîner le modèle
num_epochs = 1000
for epoch in range(num_epochs):
    model.train()

    predictions = model(X)
    loss = criterion(predictions.squeeze(), Y)

    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    if (epoch + 1) % 100 == 0:
        print(f'Époque [{epoch + 1}/{num_epochs}], Perte : {loss.item():.4f}')

# 5. Évaluer le modèle
print(f'\nPoids entraînés : {model.linear.weight.data.numpy()}')
print(f'Biais entraîné : {model.linear.bias.data.numpy()}')
print(f'Poids réels : {true_w.numpy()}')
print(f'Biais réel : {true_b}')

Analyse des résultats d'exécution

Après avoir exécuté le code ci-dessus, vous verrez une sortie similaire à ce qui suit :

Epoch [100/1000], Loss: 0.0654
Epoch [200/1000], Loss: 0.0398
Epoch [300/1000], Loss: 0.0243
Epoch [400/1000], Loss: 0.0148
Epoch [500/1000], Loss: 0.0090
Epoch [600/1000], Loss: 0.0055
Epoch [700/1000], Loss: 0.0033
Epoch [800/1000], Loss: 0.0020
Epoch [900/1000], Loss: 0.0012
Epoch [1000/1000], Loss: 0.0008

训练后的权重: [[2.0016 2.9973]]
训练后的偏置: [4.0015]
真实权重: [2. 3.]
真实偏置: 4.0

On peut voir que, à mesure que le nombre d'époques d'entraînement augmente, la valeur de perte diminue continuellement. Finalement, les poids et le biais du modèle sont très proches des valeurs réelles, ce qui indique que le modèle a appris avec succès la relation linéaire des données.


Extension : utilisation d'optimiseurs plus complexes

En plus de SGD, PyTorch fournit de nombreux autres optimiseurs. Voici un exemple utilisant l'optimiseur Adam :

Exemple

# Utiliser l'optimiseur Adam (converge généralement plus rapidement)
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)

# Entraîner le modèle
for epoch in range(num_epochs):
    model.train()

    predictions = model(X)
    loss = criterion(predictions.squeeze(), Y)

    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    if (epoch + 1) % 100 == 0:
        print(f'Époque [{epoch + 1}/{num_epochs}], Perte : {loss.item():.4f}')
Optimiseur Fonctionnalités Scénarios applicables
SGD Simple et direct, nécessite un réglage manuel du taux d'apprentissage Grands ensembles de données, scénarios classiques
Adam Taux d'apprentissage adaptatif, convergence rapide La plupart des scénarios, choix par défaut recommandé
RMSprop Adapté aux réseaux de neurones récurrents Modèles de séquence tels que RNN, LSTM

Problèmes courants

Problème 1 : La valeur de perte ne diminue pas

Si la valeur de perte ne diminue pas pendant l'entraînement, les raisons possibles incluent :

  • Taux d'apprentissage trop petit, ce qui rend les mises à jour des paramètres trop petites.
  • Taux d'apprentissage trop grand, ce qui fait sauter la solution optimale.
  • Problèmes avec les données, comme des plages de valeurs des caractéristiques trop disparates.

Solution :

  • Essayez d'ajuster le taux d'apprentissage (commencez par 0.1, 0.01, 0.001).
  • Standardisez les données d'entrée.
  • Vérifiez les valeurs aberrantes ou manquantes dans les données.

Problème 2 : Incompatibilité de forme des prédictions

Si une erreur d'incompatibilité de forme se produit lors du calcul de la perte, vous devrez peut-être utilisersqueeze()ouunsqueeze()Ajustez la forme du tenseur.

Exemple

# Vérifier la forme de sortie
predictions = model(X)
print(f'Forme de sortie du modèle : {predictions.shape}')  # Devrait être [100, 1]

# Réduire à 1D
predictions = predictions.squeeze()
print(f'Forme après squeeze : {predictions.shape}')  # Devrait être

# Calculer la perte
loss = criterion(predictions, Y)

Problème 3 : Comment améliorer la précision du modèle

  • Augmenter le nombre d'époques d'entraînement.
  • Ajuster le taux d'apprentissage.
  • Utiliser une structure de modèle plus complexe (par ex., un réseau de neurones multicouche).
  • Augmenter la quantité de données d'entraînement.
Autres extensions