7.8. Modélisation d’un pendule double par IA#
Marc Buffat, dpt mécanique, Lyon 1
inspiré de Deep Learning with Real Data and the Chaotic Double Pendulum on https://cloud4scieng.org
from scipy.integrate import odeint
import time
import math
import numpy as np
import pylab as py
from math import *
import numpy as np
from scipy.optimize import newton
from matplotlib import animation, rc
from matplotlib import pyplot as plt
import pandas as pd
import torch
import torch.nn as nn
from torch.autograd import Variable
from sklearn.preprocessing import MinMaxScaler
from IPython.display import HTML
7.8.1. Problème#
Expérience du double pendule
from IPython.display import YouTubeVideo
YouTubeVideo('DVr7KnpDAOc', width=800, height=500)
7.8.2. Modélisation mécanique#
On doit donc définir:
les parametres du problème: longueur et masse des 2 pendules \(l_1,m_1,l_2,m_2\)
les 2 ddl du problème: \(\theta_1(t)\) et \(\theta_2(t)\)
Système conservatif: équation de Lagrange
7.8.2.1. Simulation numérique#
Pour résoudre numériquement ce système, nous devons le transformer en un système d’équations différentielles du premier ordre.
On choisit comme vecteur d’état \(U = [\theta_1, \dot{\theta_1},\theta_2, \dot{\theta_2}]\), et on écrit le système sous la forme $\( \dot{U} = \mathbf{F} U\)$
7.8.2.2. Modèle numérique#
\(u_0 =[\theta_1(0),0.,\theta_2(0),0.]\) conditions initiales (dim=4)
vecteur d’état \(V ~=~ [\theta_1, \dot{\theta_1}, \theta_2, \dot{\theta_2} ]\)
u[0] = angle du premier pendule
u[1] = vitesse angulaire du premier pendule
u[2] = angle du second pendule
u[3] = vitesse angulaire du second pendule
Le vector \(V ~=~ [\theta_1, u_1, \theta_2, u_2 ]\) contient les 2 angles \(\theta_1\) et \(\theta_2\) et les vitesses angulaires \(u_1\) and \(u_2\)
En posant \(c=cos(\theta_1 - \theta_2 )\) et \(s = sin(\theta_1 - \theta_2 )\)
7.8.2.3. Paramètres de l’étude#
paramètres \(m_1,m_2,L_1,L_2,g\)
calcul de la solution en N temps équi-répartis
m1 = 2 # mass of pendulum 1 (in kg)
m2 = 1 # mass of pendulum 2 (in kg)
L1 = 1.4 # length of pendulum 1 (in meter)
L2 = 1 # length of pendulum 2 (in meter)
g = 9.8 # gravitatioanl acceleration constant (m/s^2)
tfinal = 25.0 # Final time. Simulation time = 0 to tfinal.
Nt = 751
t = np.linspace(0, tfinal, Nt)
7.8.2.4. Résolution numérique#
# Differential equations describing the system: function F(u)
def double_pendulum(u,t,m1,m2,L1,L2,g):
# du = derivatives
# u = variables
# p = parameters
# t = time variable
return
# calcul solution pour une valeur initiale theta1,theta2 (en degré)
def calcul_sol(theta1, theta2):
tfinal = 25.0 # Final time. Simulation time = 0 to tfinal.
Nt = 751
sol = None
return sol
7.8.2.5. Calcul de la solution#
# tracer d'une solution
def plot_sol(sol):
global t,L1,L2
theta1 = sol[:,0]
theta2 = sol[:,2]
x1 = L1*np.sin(theta1); # First Pendulum
y1 = -L1*np.cos(theta1);
x2 = x1 + L2*np.sin(theta2); # Second Pendulum
y2 = y1 - L2*np.cos(theta2);
# tracer
plt.figure(figsize=(12,6))
plt.subplot(1,2,1)
plt.plot(t,theta1,label="$\\theta_1$")
plt.plot(t,theta2,label="$\\theta_2$")
plt.legend()
plt.xlabel('t')
plt.subplot(1,2,2)
plt.plot(x1,y1,label="masse1")
plt.plot(x2,y2,label="masse2")
plt.legend()
plt.xlabel('x')
plt.axis('equal');
return
# solution cas stable theta1=-20 deg. theta2 = 35 deg.
### BEGIN SOLUTION
### END SOLUTION
# solution cas plus instable theta=10 deg. theta2=80 deg.
### BEGIN SOLUTION
### END SOLUTION
7.8.3. Objectif#
Peut on prédire la trajectoire du système sans faire la simulation, en utilisant un apprentissage à partir d’une base de données issue de la simulation.
7.8.3.1. prédiction#
En se donnant la valeur de solution sur 10 valeurs en temps consécutives, peut on prédire l’évolution de la solution sur des temps longs: p.e. sur les 100 pas en temps suivants
7.8.3.2. base de données#
Pour créer la base de données, on découpe la simulation avec une fenêtre glissante de 10 valeurs en temps consécutives pour prédire la valeur de la solution à l’instant t
7.8.4. Base de données d’apprentissage#
train_seq: données (data) tableau dim (10,4)
train_label: résultat dim (4)
création de la BD avec une technique de fenêtre glissante et d’adimensionalisation
# creation du training set
# taille de la fenetre
train_window = 10
def create_inout_sequences(input_data, tw):
inout_seq = []
# normalisation de la base de données (avec scikit learn MinMaxScaler )
def preprocess_sol(sol, train_window):
return
# solution numérique pour calculer la base de donnees
sol = calcul_sol(-20., 35.)
# generation de la base de données
train_inout_seq = preprocess_sol(sol, train_window)
print("BD:",len(train_inout_seq))
print("valeurs BD:",train_inout_seq[0])
7.8.5. Modèle LSTM#
réseau de neuronnes récurrents: LSTM « neural network » nn de pyTorch
paramétres
taille des données élémentaires input_size= 4 : 4 données / temps = \(\theta_1, \theta_2, \dot{\theta_1},\dot{\theta_2} \)
nbre de couches de neuronnes cachés (depliement de la récurrence en temps) hidden_size=100 pas en temps
optimisation: méthode d’optimisation par gradient, algorithme stochastique de gradient d’ADAM
utilisation des états précédents (états cn (long-term memory) et états cachés hn (short-term memory))
lstm = nn.LSTM(input_size, hidden_size, num_LSTM=1) output, (hn,cn) = lstm(input, (h0,c0))
attention pytorch a sa propre version des tableaux (torch tensor)
conversion données: tableau 1D 100 -> sortie dim 4
Linear(100, 4)
apprentissage
à partir de la base de données initiale: simulation (751,4)
fenêtrage: 741 fenêtres glissantes (10,5) -> (4)
on choisit au hasard 500 fenêtres pour faire l’apprentissage ( pour 1 passage)
on réitère le processus complet
epochs
fois.epochs
est un hyperparamètre qui définit le nombre de fois que l’algorithme d’apprentissage parcours l’ensemble des données d’entraînement. Ce paramétre peut etre très grand !
# model LSTM
class MyLSTM(nn.Module):
def __init__(self):
"""initialisation """
super().__init__()
# reseau LSTM
self.lstm = nn.LSTM(4, 100)
# transformation lineaire des données
self.linear = nn.Linear(100, 4)
# h0,c0 valeurs initiales des etats internes (c0) et des etats cachés (h0)
self.c_h = (torch.zeros(1,1,100),
torch.zeros(1,1,100))
return
def forward(self, x):
"""prediction """
h, self.c_h= self.lstm(x.view(len(x) ,1, -1), self.c_h)
predictions = self.linear(h.view(len(x), -1))
return predictions[-1]
7.8.5.1. Apprentissage#
# apprentissage (False): attention très tres long !!!
model = MyLSTM()
use_saved_model = True
use_saved_model = False # recalcul
#
if use_saved_model == False:
loss_function = nn.MSELoss()
hidden_layer_size = 100
optimizer = torch.optim.Adam(model.parameters(), lr=0.0005)
epochs = 2002 #
epochs = 52 # calcul + simple
model_file = 'lstm-pend1'
print("recalcul model:{} nbepochs={}".format(model_file, epochs))
# lecture du model sauvegardé
if use_saved_model:
model = MyLSTM()
sol = calcul_sol(-20., 35.)
model.load_state_dict(torch.load('lstm-pend2')) #this model is in the stable region
#sol = calcul_sol(-50.,140.)
#model.load_state_dict(torch.load('lstm-pend3')) #this model is from chaotic region
7.8.5.2. Prédiction du model#
à partir de données data, on utilise les train_window=10 données initiales pour prédire la solution aux steps=100 pas en temps suivants
test sur la base de données d’apprentissage avec steps=100 et steps=200
test sur une autre base de données
# calcul de la prediction sur steps pas a partir de data
def OneStep(data, steps = 100):
print('data set length =', len(data), "pred=",steps)
train_window = 10
# mise a l'echelle
scaler = MinMaxScaler(feature_range=(-1, 1))
train_data_normalized = scaler.fit_transform(data.reshape(-1, 4))
test_inputs = train_data_normalized[0:train_window].reshape(train_window,4).tolist()
s2 = train_data_normalized.reshape(len(data),4).tolist()
realdata = data
model.eval()
preds = test_inputs.copy()
t2 = test_inputs
hidden_layer_size = 100
x = 0
#fut_pred = len(data) - train_window
fut_pred = steps
for i in range(fut_pred):
seq = torch.FloatTensor(np.array(t2[i:]))
# etat interne et cachés
model.c_h = (torch.zeros(1, 1, hidden_layer_size),
torch.zeros(1, 1, hidden_layer_size))
x = model(seq)
preds.append(x.detach().numpy())
t2.append(x.detach().numpy())
actual_predictions = scaler.inverse_transform(np.array(preds).reshape(-1,4))
# estimation erreur
err = 0.0
cnt = steps
maxerr = 0.0
maxloc = 0
# estimation erreur sur theta2
for i in range(cnt):
er =np.abs(realdata[i,2]-actual_predictions[i,2])
err += er
if er > maxerr:
maxerr = er
maxloc = i
print("mean error =", err/cnt)
print('maxerr =', maxerr, ' at ', maxloc)
return actual_predictions
## calcul de la prediction a partir de la bd de test
### BEGIN SOLUTION
### END SOLUTION
# tracer de la solution
def plot_traj(data,predictions,steps=100):
# the following will plot the lower mass path for steps using
# the actual ODE solver and the predicitons
global t
u0 = data[:,0] # theta_1
u1 = data[:,1] # omega 1
u2 = data[:,2] # theta_2
u3 = data[:,3] # omega_2
up0 = predictions[:,0] # theta_1
up1 = predictions[:,1] # omega 1
up2 = predictions[:,2] # theta_2
up3 = predictions[:,3] # omega_2
x1 = L1*np.sin(u0); # First Pendulum
y1 = -L1*np.cos(u0);
x2 = x1 + L2*np.sin(u2); # Second Pendulum
y2 = y1 - L2*np.cos(u2);
xp1 = L1*np.sin(up0); # First Pendulum
yp1 = -L1*np.cos(up0);
xp2 = xp1 + L2*np.sin(up2); # Second Pendulum
yp2 = yp1 - L2*np.cos(up2);
#
plt.figure(figsize=(12,6))
plt.subplot(1,2,1)
plt.plot(x2[0:steps], y2[0:steps], color='r',label="test")
plt.plot(xp2[0:steps],yp2[0:steps] , color='g',label="pred.")
plt.plot([x2[0]],[y2[0]],'ok')
plt.legend()
plt.axis('equal')
plt.title("trajectoire masse 2")
plt.subplot(1,2,2)
plt.plot(t[:steps],u2[:steps]-up2[:steps])
plt.xlabel('t')
plt.title("erreur sur $\\theta_2(t)$ ");
return
7.8.5.3. test sur base de donnée de test#
# prediction 1 sur la base de données d'apprentissage steps=100
### BEGIN SOLUTION
### END SOLUTION
# prediction 2 sur un temps plus long: steps=200
### BEGIN SOLUTION
### END SOLUTION
7.8.5.4. prédiction d’une nouvelle simulation#
## calcul pour theta1=-25 deg. , theta2 = 30 deg.
# base entrainement
#sol1 = calcul_sol(-20., 35.)
sol1 = None
### BEGIN SOLUTION
# base entrainement
#sol1 = calcul_sol(-20., 35.)
### END SOLUTION
## calcul pour theta1=-30 deg. , theta2 = 40 deg.
sol1 = None
### BEGIN SOLUTION
### END SOLUTION
7.8.6. Conclusion#
Nous remarquons que l’apprentissage LSTM avec une unique solution permettait au LSTM de générer des solutions raisonnablement bonnes à partir de points proches du point initial de la solution.
7.8.7. Amélioration: Physics-Informed Neural Networks (PINN)#
contrainte sur l’évolutionn du système :
système conservatif: Ec + Ep = cste
minimisation sous contrainte
Réseaux de neuronnes inspiré par la physique !!!
c’est nouvelle classe de réseaux de neurones qui hybride apprentissage automatique et lois physiques permettant une « meilleur » prédiction des réseaux de neuronnes (Deep Learning) pour des applications physiques gérées par des équations (ODE ou PDE):
voir le site https://metalblog.ctif.com/2022/01/17/physics-informed-neural-networks/
attention: ce ne sont que la prise en compte de contraintes liées aux propriétés physiques du système étudié aucune connaissance (compréhension) de la physique dans ces algorithmes.
7.8.8. FIN#