Risolvere equazioni differenziali con le reti neurali |  di Rodrigo Silva |  Febbraio 2024

 | Intelligenza-Artificiale

Come le reti neurali sono strumenti efficaci per risolvere equazioni differenziali senza l’uso di dati di addestramento

fotografato da Linus Mimietz SU Unsplash

Le equazioni differenziali sono una delle protagoniste delle scienze fisiche, con vaste applicazioni nell’ingegneria, nella biologia, nell’economia e persino nelle scienze sociali. In parole povere ci dicono come varia una quantità nel tempo (o qualche altro parametro, ma di solito a noi interessano le variazioni nel tempo). Possiamo capire come cambia nel tempo una popolazione, o un prezzo di un titolo, o anche come cambia nel tempo l’opinione di alcune società verso determinati temi.

Tipicamente, i metodi utilizzati per risolvere gli DE non sono analitici (cioè non esiste una “formula chiusa” per la soluzione) e dobbiamo ricorrere a metodi numerici. Tuttavia, i metodi numerici possono essere costosi dal punto di vista computazionale e, peggio ancora: l’errore accumulato può essere notevolmente elevato.

Questo articolo mostrerà come una rete neurale può essere un prezioso alleato per risolvere un’equazione differenziale e come possiamo prendere in prestito concetti dalle reti neurali informate dalla fisica per affrontare la domanda: possiamo utilizzare un approccio di apprendimento automatico per risolvere un DE?

In questa sezione parlerò molto brevemente delle reti neurali informate dalla fisica. Suppongo che tu conosca la parte della “rete neurale”, ma cosa li rende informati dalla fisica? Ebbene, non sono esattamente informati dalla fisica, ma piuttosto da un’equazione (differenziale).

Di solito, le reti neurali vengono addestrate per trovare modelli e capire cosa sta succedendo con una serie di dati di addestramento. Tuttavia, quando addestri una rete neurale a obbedire al comportamento dei tuoi dati di addestramento e, si spera, ad adattare dati invisibili, il tuo modello dipende fortemente dai dati stessi e non dalla natura sottostante del tuo sistema. Sembra quasi una questione filosofica, ma è più pratica di così: se i tuoi dati provengono da misurazioni delle correnti oceaniche, queste correnti devono obbedire alle equazioni fisiche che descrivono le correnti oceaniche. Nota, tuttavia, che la tua rete neurale è completamente agnostica riguardo a queste equazioni e sta solo cercando di adattare i punti dati.

È qui che entra in gioco la fisica informata. Se, oltre a imparare come adattare i tuoi dati, il tuo modello impara anche come adattare le equazioni che governano quel sistema, le previsioni della tua rete neurale saranno molto più precise e si generalizzeranno molto meglio, citando solo alcuni vantaggi dei modelli basati sulla fisica .

Nota che le equazioni che governano il tuo sistema non devono necessariamente coinvolgere la fisica, la cosa “informata dalla fisica” è solo nomenclatura (e la tecnica è comunque più utilizzata dai fisici). Se il tuo sistema è il traffico in una città e ti capita di avere un buon modello matematico a cui vuoi che le previsioni della tua rete neurale obbediscano, allora le reti neurali basate sulla fisica sono adatte a te.

Come informiamo questi modelli?

Spero di avervi convinto che vale la pena di rendere il modello consapevole delle equazioni sottostanti che governano il nostro sistema. Tuttavia, come possiamo farlo? Esistono diversi approcci a questo, ma il principale è adattare la funzione di perdita per avere un termine che tenga conto delle equazioni governative, a parte la consueta parte relativa ai dati. Cioè, la funzione di perdita l sarà composto dalla somma

In questo caso, la perdita di dati è la solita: una differenza media quadratica, o qualche altra forma adatta di funzione di perdita; ma la parte dell’equazione è quella affascinante. Immagina che il tuo sistema sia governato dalla seguente equazione differenziale:

Come possiamo inserirlo nella funzione di perdita? Ebbene, poiché il nostro compito quando addestriamo una rete neurale è minimizzare la funzione di perdita, ciò che vogliamo è minimizzare la seguente espressione:

Quindi la nostra funzione di perdita correlata all’equazione risulta essere

cioè è la differenza media al quadrato della nostra DE. Se riusciamo a minimizzarlo (ovvero a rendere questo termine il più vicino possibile allo zero) soddisfiamo automaticamente l’equazione che governa il sistema. Abbastanza intelligente, vero?

Ora, il termine extra L_IC nella funzione di perdita deve essere affrontata: essa rappresenta le condizioni iniziali del sistema. Se non vengono fornite le condizioni iniziali di un sistema, ci sono infinite soluzioni per un’equazione differenziale. Ad esempio, una palla lanciata dal livello del suolo ha la sua traiettoria governata dalla stessa equazione differenziale di una palla lanciata dal 10° piano; sappiamo però per certo che i percorsi tracciati da queste palline non saranno gli stessi. Ciò che cambia qui sono le condizioni iniziali del sistema. Come fa il nostro modello a sapere di quali condizioni iniziali stiamo parlando? È naturale a questo punto applicarlo utilizzando un termine di funzione di perdita! Per il nostro DE, imponiamolo quando t = 0, y = 1. Quindi, vogliamo minimizzare una funzione di perdita della condizione iniziale che recita:

Se minimizziamo questo termine, soddisfiamo automaticamente le condizioni iniziali del nostro sistema. Ora, ciò che resta da capire è come usarlo per risolvere un’equazione differenziale.

Se una rete neurale può essere addestrata sia con il termine relativo ai dati della funzione di perdita (questo è ciò che di solito viene fatto nelle architetture classiche), sia con il termine relativo ai dati e all’equazione (questo è un metodo di fisica reti neurali informate di cui ho appena parlato), deve essere vero che può essere addestrato a minimizzare soltanto il termine correlato all’equazione. Questo è esattamente ciò che faremo! L’unica funzione di perdita utilizzata qui sarà la L_equazione. Si spera che il diagramma seguente illustri ciò che ho appena detto: oggi puntiamo al tipo di modello in basso a destra, il nostro risolutore DE NN.

Figura 1: diagramma che mostra i tipi di reti neurali rispetto alle loro funzioni di perdita. In questo articolo puntiamo a quello in basso a destra. Immagine dell’autore.

Implementazione del codice

Per mostrare le nozioni teoriche appena acquisite, implementerò la soluzione proposta in codice Python, utilizzando la libreria PyTorch per l’apprendimento automatico.

La prima cosa da fare è creare un’architettura di rete neurale:

import torch
import torch.nn as nn

class NeuralNet(nn.Module):
def __init__(self, hidden_size, output_size=1,input_size=1):
super(NeuralNet, self).__init__()
self.l1 = nn.Linear(input_size, hidden_size)
self.relu1 = nn.LeakyReLU()
self.l2 = nn.Linear(hidden_size, hidden_size)
self.relu2 = nn.LeakyReLU()
self.l3 = nn.Linear(hidden_size, hidden_size)
self.relu3 = nn.LeakyReLU()
self.l4 = nn.Linear(hidden_size, output_size)

def forward(self, x):
out = self.l1(x)
out = self.relu1(out)
out = self.l2(out)
out = self.relu2(out)
out = self.l3(out)
out = self.relu3(out)
out = self.l4(out)
return out

Questo è solo un semplice MLP con funzioni di attivazione di LeakyReLU. Quindi, definirò le funzioni di perdita per calcolarle successivamente durante il ciclo di allenamento:

# Create the criterion that will be used for the DE part of the loss
criterion = nn.MSELoss()

# Define the loss function for the initial condition
def initial_condition_loss(y, target_value):
return nn.MSELoss()(y, target_value)

Ora creeremo un array temporale che verrà utilizzato come dati del treno, creeremo un’istanza del modello e sceglieremo anche un algoritmo di ottimizzazione:

# Time vector that will be used as input of our NN
t_numpy = np.arange(0, 5+0.01, 0.01, dtype=np.float32)
t = torch.from_numpy(t_numpy).reshape(len(t_numpy), 1)
t.requires_grad_(True)

# Constant for the model
k = 1

# Instantiate one model with 50 neurons on the hidden layers
model = NeuralNet(hidden_size=50)

# Loss and optimizer
learning_rate = 8e-3
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)

# Number of epochs
num_epochs = int(1e4)

Infine, iniziamo il nostro ciclo di formazione:

for epoch in range(num_epochs):

# Randomly perturbing the training points to have a wider range of times
epsilon = torch.normal(0,0.1, size=(len

Fonte: towardsdatascience.com

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *