Dalle onde cerebrali alle protesi robotiche con il deep learning: un'introduzione


Il sistema nervoso umano è una struttura incredibilmente complessa. In ogni istante, oltre centomila chilometri di terminazioni nervose connettono ciascuna parte del vostro corpo con il midollo spinale ed il cervello. Per ogni movimento che fate, è attraverso questa enorme "rete" che vengono trasmessi gli impulsi elettrici che coordinano e gesticono l'attività dei muscoli. E ciascuno di questi commandi ha a sua volta origine dal cervello, una struttura ancora più formidabile, nel quale miliardi di neuroni comunicano tra di loro e verso l'esterno tramite l'invio di continui segnali elettrici. Comprendere ed interpretare gli schemi che sottendono questa continua attività cerebrale è il sogno proibito di qualunque neuroscienziato e neurobiologo, una sfida che si è rivelata negli anni sempre più impegnativa.

Questo articolo è una traduzione italiana dell'originale su Towards Data Science. Potete trovare tutto il codice Python su un notebook di Google Colaboratory o in un repository GitHub.

L'elettroencefalografia (EEG) è uno dei metodi meno invasivi per monitorare l'attività cerebrale di un soggetto, registrando le fluttuazioni nel voltaggio elettrico del cervello con una serie di elettrodi. In genere, per avere un quadro complessivo dell'attività cerebrale fino a 30 di questi elettrodi sono posizionati ad intervalli regolari sul capo del paziente. Purtroppo, al di fuori dei test in laboratorio, la relazione che intercorre tra queste registrazioni e l'attività cerebrale stessa è complessa e molto meno compresa di quanto vorremmo. "Decodificare" in qualche modo questi segnali è quindi una sfida enorme, che permetterebbe di controllare protesi artificiali ed altri apparati attraverso l'uso di interfacce uomo-macchina (brain-computer interface, BCI) a loro volta poco invasive.

Esempio di onde cerebrali registrate tramite EEG
Esempio di onde cerebrali registrate tramite EEG (CC BY-SA 2.0, Commons Wikipedia).

In questa ottica, i recenti progressi nel campo del deep learning in problemi simili aprono nuove prospettive per analizzare questi segnali con l'uso di reti neurali. Questo post è un'introduzione a questo tema estremamente affascinante. Per prima cosa, leggeremo dei dati EEG forniti da una competizione su Kaggle, con l'obiettivo di capire quali pattern corrispondono a determinati gesti del braccio e della mano come, ad esempio, afferrare un oggetto o sollevarlo. In seguito, progetteremo una rete neurale allenata allo scopo di riconoscere questi movimenti, dopo aver processato i dati con diverse tecniche. Nel mentre, vi mostrerò anche alcune visualizzazioni dell'attività cerebrale, per avere un'idea dei dati su cui andiamo a lavorare.

L'obiettivo ultimo di questa area di ricerca è di sviluppare protesi artificiali accessibili a tutti, che chiunque possa controllare tramite impulsi nervosi, per permettere a chi ha perso un arto di tornare a poter eseguire facilmente tutti quei compiti che oggi gli sono preclusi. In futuro, tecniche simili potranno essere usate anche per misurare l'attività elettrica nei muscoli, decodificando in automatico il movimento che la persona stava eseguendo.

Che dati utilizzeremo

Potete scaricare liberamente i dati da questo link (è sufficiente un account Kaggle). Il dataset è composto da numerosi file .csv, tra cui:

  • Dati EEG registrati con 32 elettrodi posizionati sul capo del paziente e registrati a 500 Hz.

  • Etichette (per ogni istante temporale) del movimento che il soggetto sta compiendo (scelto tra 6 possibilità).

I dati sono collezionati chiedendo ai soggetti di compiere diverse azioni semplici, come afferrare un oggetto o sollevarlo con un braccio. Il dataset è diviso da un lato fra i vari soggetti, e per ciascun soggetto in una serie di episodi. Le onde cerebrali sono molto soggettive: un modello ben allenato può facilmente identificare nuovi episodi della stessa persona, ma può faticare molto a fare lo stesso su nuovi soggetti se il dataset non è stato variegato a sufficienza.

Ricapitolando, l'obiettivo è di creare una rete neurale che prenda come input le registrazioni EEG e fornisca in output una distribuzione di probabilità sulle sei azioni che il soggetto sta compiendo. Nel dataset non è considerato il caso in cui il soggetto non stia compiendo un'azione: in questo caso, possiamo aggiungerlo come classe aggiuntiva, o considerare ogni azione come una predizione indipendente, scegliendone una solo se supera una certa soglia. Se tutte le azioni sono sotto tale soglia, lo consideriamo un caso di "no-action".

Posizione degli elettrodi
Posizione degli elettrodi (presa da https://www.kaggle.com/c/grasp-and-lift-eeg-detection/data).

Per cominciare, ho realizzato un'animazione dell'attività degli elettrodi. Poiché la frequenza di campionamento è molto alta (500 Hz), ho usato un filtro passa basso per diminuire queste variazioni, ed ho creato un'animazione con i primi 100 frame, che corrispondono a 1/5 di secondo circa.

Animazione degli elettrodi
Animazione dei 32 elettrodi nei primi 200 ms.

Possiamo visualizzare la stessa informazione come una heatmap a due dimensioni, dove l'asse verticale è il tempo (che scorre "verso il basso"), e l'asse orizzontale separa i 32 elettrodi.

Heatmap dell'EEG
Heatmap temporale dell'attività EEG (sull'asse verticale, il tempo scorre dall'alto verso il basso).

Questa visualizzazione è molto utile poiché, come vedremo, ci permette di lavorare con convoluzioni spazio-temporali.

Processare i dati

Per agevolare il classificatore, possiamo processare i dati per rendere più semplice la fase successiva di allenamento. Ad esempio, la frequenza di campionamento molto alta dell'EEG contrasta con il fatto che le azioni cambiano con una frequenza molto bassa: i dati in input cambiano molto velocemente, mentre le azioni rimangono costanti, ed alcune di queste fluttuazioni sono indistinguibili da puro rumore. Un modello che lavori nel tempo riceverebbe quindi dati in continuo cambiamento, mentre l'output rimarrebbe costante.

Come accennato prima, il primo passo è quello di filtrare i dati con un filtro passa basso. In questo caso, anche una semplice media a finestra mobile aiuta: in questo modo mitighiamo il cambiamento nelle frequenze troppo alte, mantenendo allo stesso tempo l'informazione utile contenuta nelle frequenze più basse (la frequenza del cambio di azione è di circa 1 Hz). A questo punto, possiamo anche sottocampionare i dati, ovvero tenere un solo punto ogni 10, 100, ecc. Questo aiuta a ridurre la dimensionalità nel tempo e diminuisce la correlazione nei dati.

Anche se potremmo utilizzare moltre altre tecniche, per semplicità in questa introduzione ci fermiamo qui ed andiamo avanti con la progettazione della rete neurale.

Progettare la rete neurale (e testarla)

Quando lavoriamo con dati nel tempo, una delle architetture più utilizzate sono le reti neurali ricorrenti. Queste reti hanno una struttura dinamica, con uno stato interno che permette di codificare informazione tra gli istanti temporali in modo da prendere decisioni basate sull'intera sequenza degli input. Inizialmente ho progettato una LSTM in Keras, fornendole i dati in input dei quali ho mantenuto la struttura temporale. I risultati erano buoni, ma in questa introduzione mi interessa di più mostrare l'utilizzo di una più semplice rete neurale convolutiva (convolutional neural network, CNN) che, anche se usata classicamente per le immagini, funziona molto bene anche in queste situazioni.

Come detto prima, in un certo senso stiamo lavorando con informazioni spazio-temporali: l'asse verticale della heatmap rappresenta l'evoluzione temporale, mentre l'asse orizzontale mostra i vari elettrodi. Più vicino sono gli elettrodi sulla heatmap, più vicini li ritroviamo anche nel loro posizionamento sul capo (quasi sempre). Questo rende la heatmap perfetta per estrarre feature con delle convoluzioni: in effetti, un kernel 2D permette di estrarre informazioni sia nel tempo che nello spazio. Immaginate un classico filtro convolutivo 3x3: dalla heatmap, questo estrarrebbe feature facendo una media pesata di tre istanti temporali diversi (tre righe della heatmap), ma anche da tre diversi elettrodi (tre colonne della heatmap). Grazie a questo, una CNN con molti filtri può trovare feature che descrivono come cambiano le attivazioni degli elettrodi su un certo intervallo di tempo in relazione al movimento desiderato.

Per testare questo, ho implementato una semplice CNN in Keras per valutarne le prestazioni. Potete trovare tutto il codice Python dell'articolo su un notebook di Google Colaboratory o sulla repository GitHub. Qui sotto trovate il codice necessario per creare ed ottimizzare la rete neurale.

# Import da Keras
import keras
from keras.models import Sequential
from keras.layers import Dense, Dropout
from keras.layers import Embedding
from keras.layers import LSTM, CuDNNLSTM, BatchNormalization, Conv2D, Flatten, MaxPooling2D, Dropout
from keras.optimizers import Adam

# Costruzione del modello
model = Sequential()
model.add(Conv2D(filters = 64, kernel_size = (7,7), padding = "same", activation = "relu", input_shape = (time_steps//subsample, 32, 1)))
model.add(BatchNormalization())
model.add(Conv2D(filters = 64, kernel_size = (5,5), padding = "same", activation = "relu", input_shape = (time_steps//subsample, 32, 1)))
model.add(BatchNormalization())
model.add(Conv2D(filters = 64, kernel_size = (3,3), padding = "same", activation = "relu", input_shape = (time_steps//subsample, 32, 1)))
model.add(BatchNormalization())
model.add(Flatten())
model.add(Dense(32, activation = "relu"))
model.add(BatchNormalization())
model.add(Dense(6, activation = "sigmoid"))

# Ottimizzazione
adam = Adam(lr = 0.001)
model.compile(optimizer = adam, loss = "categorical_crossentropy", metrics = ["accuracy"])
model.summary()

Per valutare le prestazioni del modello, come suggerito anche nella competizione Kaggle, andiamo a calcolare l'area under the curve (AUC) (se non siete familiari con l'AUC, qui ne trovate una spiegazione intuitiva). Come potete vedere nel notebook, la rete raggiunge rapidamente uno score AUC di 0.85, con una fase di allenamento molto veloce.

Potremmo migliorare queste prestazioni complicando a piacere la fase di preprocessing o l'architettura della rete. In generale, però, questa prova introduttiva mostra già la capacità incredibile delle reti neurali di apprendere da questo genere di dati.

Conclusione

Questo articolo era un'introduzione all'analisi di EEG con tecniche di deep learning. Abbiamo visto alcune visualizzazioni intuitive dei dati, e come usare le reti neurali per estrarre feature e riconoscere i movimenti umani. Credo che queste tecnologie (protesi robotiche, interfacce uomo-macchina) saranno profondamente influenzate dal deep learning man mano che le varie tecniche, piattaforme e competizioni cresceranno negli anni.

L'impatto di queste tecnologie sarà incredibile. Avere a disposizione protesi a basso costo che possono essere controllate intuitivamente può migliorare in modo significativo la vita di milioni di persone nel mondo.

Se siete interessati, vi suggerisco di guardare il Symbionic Project, un progetto partito da poco con l'obiettivo di creare un braccio artificiale intelligente ed a basso costo, che possa essere controllato con l'attivazione dei muscoli, per rendere realmente "democratico" l'accesso a questi sistemi.

Qualcosa su di me

Norman Di Palo è uno studente di AI e Robotica all'Università Sapienza di Roma. Ha partecipato al primo batch della Pi School of Artificial Intelligence, dove ora è AI & Robotics Advisor. Lavora nel campo del deep learning da circa un anno, con progetti che vanno dalle self-driving car alla robotica. È co-founder di una startup nel campo AI incubata a LUISS Enlabs. Potete contattarlo via mail (normandipalo@me.com) o su LinkedIn.


Se questo articolo ti è piaciuto e vuoi tenerti aggiornato sulle nostre attività, ricordati che l'iscrizione all'Italian Association for Machine Learning è gratuita! Puoi seguirci anche su Facebook e su LinkedIn.

Previous Post Next Post