Introduzione

L’apprendimento automatico (Machine Learning) sta trasformando numerosi settori, e la biomedicina non fa eccezione. Una delle sfide principali nell’implementazione di modelli di apprendimento automatico in ambito medico è la disponibilità di dati etichettati.

L’etichettatura dei dati medici richiede spesso l’intervento di specialisti, rendendola un’operazione costosa e dispendiosa in termini di tempo.

Tuttavia, gli algoritmi di apprendimento semi-supervisionato offrono una soluzione promettente combinando l’uso di dati etichettati e non etichettati.

Questo articolo esplora come tali algoritmi possono essere utilizzati per migliorare la diagnosi delle malattie tramite immagini mediche e presenta un esempio pratico di implementazione in Python, inclusi i grafici dei risultati.

Un caso d’uso: supporto alla diagnosi di malattie tramite immagini mediche

La diagnosi delle malattie attraverso l’analisi delle immagini mediche è un campo in cui l’apprendimento semi-supervisionato può avere un impatto significativo.

Ad esempio, l’identificazione di anomalie polmonari in radiografie può essere notevolmente migliorata utilizzando algoritmi avanzati.

Esempio di Applicazione: Immagini radiografiche per la diagnosi di anomalie polmonari.

  • Obiettivo: Utilizzare un piccolo set di dati etichettati e un grande set di dati non etichettati per addestrare un modello che possa identificare noduli o lesioni polmonari.
  • Algoritmo: Reti neurali convoluzionali (CNN) combinate con tecniche di self-training.
  • Esigenze del Mercato: Gli ospedali e i laboratori di diagnostica necessitano di strumenti che aumentino la precisione delle diagnosi e riducano il tempo necessario per analizzare le immagini.

Implementazione in Python

Setup e importazione delle librerie

Iniziamo importando le librerie necessarie e configurando le impostazioni di base per l’elaborazione delle immagini.


import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Input, Conv2D, MaxPooling2D, Flatten, Dense, Dropout
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from sklearn.model_selection import train_test_split

# Impostazioni di base
IMG_SIZE = (128, 128)
BATCH_SIZE = 32
EPOCHS = 20

Preparazione dei Dati

Carichiamo e preprocessiamo le immagini mediche. Utilizziamo dati etichettati per l’addestramento iniziale e dati non etichettati per migliorare il modello attraverso il self-training.


# Funzione per caricare e preprocessare le immagini
def load_and_preprocess_images(image_paths, img_size):
images = []
for path in image_paths:
img = tf.keras.preprocessing.image.load_img(path, target_size=img_size)
img = tf.keras.preprocessing.image.img_to_array(img)
img = img / 255.0 # Normalizzazione
images.append(img)
return np.array(images)

# Caricamento dei percorsi delle immagini
# Nota: image_paths_labeled e image_paths_unlabeled devono essere definiti con i percorsi delle immagini rispettive
image_paths_labeled = ["path_to_labeled_image1.jpg", "path_to_labeled_image2.jpg", ...]
image_paths_unlabeled = ["path_to_unlabeled_image1.jpg", "path_to_unlabeled_image2.jpg", ...]

# Caricamento delle etichette
# Nota: labels deve essere definito con le etichette corrispondenti (0 per normale, 1 per anomalia)
labels = [0, 1, ...]

# Pre-elaborazione delle immagini
images_labeled = load_and_preprocess_images(image_paths_labeled, IMG_SIZE)
images_unlabeled = load_and_preprocess_images(image_paths_unlabeled, IMG_SIZE)

Creazione del Modello

Creiamo un modello di rete neurale convoluzionale (CNN) per l’analisi delle immagini.


def create_cnn_model(input_shape):
inputs = Input(shape=input_shape)
x = Conv2D(32, (3, 3), activation='relu')(inputs)
x = MaxPooling2D((2, 2))(x)
x = Conv2D(64, (3, 3), activation='relu')(x)
x = MaxPooling2D((2, 2))(x)
x = Conv2D(128, (3, 3), activation='relu')(x)
x = MaxPooling2D((2, 2))(x)
x = Flatten()(x)
x = Dense(128, activation='relu')(x)
x = Dropout(0.5)(x)
outputs = Dense(1, activation='sigmoid')(x)

model = Model(inputs, outputs)
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
return model

model = create_cnn_model(IMG_SIZE + (3,))

Addestramento del Modello con Dati Etichettati

Addestriamo il modello inizialmente sui dati etichettati.


# Suddivisione dei dati etichettati in training e validation set
X_train, X_val, y_train, y_val = train_test_split(images_labeled, labels, test_size=0.2, random_state=42)

# Creazione di generatori di dati per l'addestramento e la validazione
train_datagen = ImageDataGenerator(rotation_range=20, width_shift_range=0.2, height_shift_range=0.2, horizontal_flip=True)
val_datagen = ImageDataGenerator()

train_generator = train_datagen.flow(X_train, y_train, batch_size=BATCH_SIZE)
val_generator = val_datagen.flow(X_val, y_val, batch_size=BATCH_SIZE)

# Addestramento del modello
history = model.fit(train_generator, epochs=EPOCHS, validation_data=val_generator)

Visualizzazione delle Performance del Modello

Generiamo grafici per visualizzare le prestazioni del modello durante l’addestramento.


# Plot della funzione di perdita
plt.plot(history.history['loss'], label='Training Loss')
plt.plot(history.history['val_loss'], label='Validation Loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.title('Training and Validation Loss')
plt.show()

Training And Validation Loss

Descrizione:

  • L’asse delle y rappresenta la funzione di perdita (loss), che misura quanto bene il modello sta facendo durante l’addestramento e la validazione.
  • L’asse delle x rappresenta il numero di epoche (epochs), cioè quante volte l’intero set di dati di addestramento è stato passato attraverso il modello.

Interpretazione:

  • Training Loss: La curva del training loss mostra come la perdita del modello cambia nel corso dell’addestramento. In un buon modello, questa curva dovrebbe diminuire costantemente con il passare delle epoche, indicando che il modello sta imparando a minimizzare l’errore sui dati di addestramento.
  • Validation Loss: La curva del validation loss mostra come la perdita del modello cambia sui dati di validazione, che non sono usati per l’addestramento. In un buon modello, questa curva dovrebbe anche diminuire, ma se inizia ad aumentare mentre la training loss continua a diminuire, potrebbe indicare overfitting, ovvero che il modello sta imparando troppo bene i dettagli del set di addestramento a scapito della generalizzazione ai nuovi dati.

# Plot dell'accuratezza
plt.plot(history.history['accuracy'], label='Training Accuracy')
plt.plot(history.history['val_accuracy'], label='Validation Accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend()
plt.title('Training and Validation Accuracy')
plt.show()

Training And Validation Accuracy

Descrizione:

  • L’asse delle y rappresenta l’accuratezza (accuracy), che misura la percentuale di predizioni corrette rispetto al totale delle predizioni.
  • L’asse delle x rappresenta il numero di epoche.

Interpretazione:

  • Training Accuracy: La curva dell’accuratezza di addestramento mostra quanto accuratamente il modello sta predicendo sui dati di addestramento. Questa curva dovrebbe aumentare con il passare delle epoche.
  • Validation Accuracy: La curva dell’accuratezza di validazione mostra quanto accuratamente il modello sta predicendo sui dati di validazione. Questa curva dovrebbe anche aumentare, ma se inizia a diminuire mentre l’accuratezza di addestramento continua ad aumentare, potrebbe indicare overfitting.

Applicazione del Self-Training

Utilizziamo il modello addestrato per etichettare i dati non etichettati e riaddestriamo il modello con questi nuovi dati etichettati.


# Predizioni sui dati non etichettati
predictions = model.predict(images_unlabeled)
pseudo_labels = (predictions > 0.5).astype(np.int).flatten()

# Combinazione dei dati etichettati con i nuovi dati pseudo-etichettati
X_combined = np.concatenate((images_labeled, images_unlabeled), axis=0)
y_combined = np.concatenate((labels, pseudo_labels), axis=0)

# Riaddestramento del modello con i dati combinati
train_generator = train_datagen.flow(X_combined, y_combined, batch_size=BATCH_SIZE)
history_combined = model.fit(train_generator, epochs=EPOCHS, validation_data=val_generator)

Visualizzazione delle Performance del Modello Riaddestrato

Generiamo grafici per visualizzare le prestazioni del modello riaddestrato.


# Plot della funzione di perdita
plt.plot(history_combined.history['loss'], label='Training Loss')
plt.plot(history_combined.history['val_loss'], label='Validation Loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.title('Training and Validation Loss (Combined Data)')
plt.show()

Training And Validation Loss (Combined Data)

Descrizione:

  • Questo grafico rappresenta la perdita durante il riaddestramento del modello con i dati combinati (sia etichettati che pseudo-etichettati).

Interpretazione:

  • Training Loss (Combined Data): La curva dovrebbe mostrare una perdita decrescente, indicando che il modello sta migliorando nell’apprendere dai dati combinati.
  • Validation Loss (Combined Data): Simile al grafico precedente, la curva del validation loss dovrebbe diminuire, ma bisogna fare attenzione ad eventuali segni di overfitting.

# Plot dell'accuratezza
plt.plot(history_combined.history['accuracy'], label='Training Accuracy')
plt.plot(history_combined.history['val_accuracy'], label='Validation Accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend()
plt.title('Training and Validation Accuracy (Combined Data)')
plt.show()

Training And Validation Accuracy (Combined Data)

Descrizione:

  • Questo grafico rappresenta l’accuratezza durante il riaddestramento del modello con i dati combinati.

Interpretazione:

  • Training Accuracy (Combined Data): Dovrebbe mostrare un incremento, indicando che il modello sta migliorando la sua capacità di predizione sui dati combinati.
  • Validation Accuracy (Combined Data): Dovrebbe anche mostrare un incremento. Una buona performance del modello sui dati di validazione combinati suggerisce che l’aggiunta dei dati non etichettati ha effettivamente migliorato la capacità del modello di generalizzare.

L’analisi dei grafici ci fornisce una comprensione visiva di come il modello sta performando. Durante l’addestramento iniziale, è fondamentale monitorare entrambe le curve di perdita e accuratezza per evitare l’overfitting e garantire una buona generalizzazione. Con l’apprendimento semi-supervisionato, l’inclusione di dati non etichettati dovrebbe portare a miglioramenti significativi, come indicato dai grafici dei dati combinati. Tuttavia, è importante continuare a monitorare i segni di overfitting e adattare il modello di conseguenza.

Questi grafici e le loro interpretazioni possono aiutare i ricercatori e i clinici a comprendere meglio le performance del modello e a prendere decisioni informate sul miglioramento continuo del sistema di diagnosi medica.

Conclusione

L’apprendimento semi-supervisionato offre enormi potenzialità per migliorare le diagnosi mediche utilizzando immagini. L’implementazione di un’applicazione basata su questa tecnologia può aiutare i medici a identificare le malattie in modo più rapido ed efficace, riducendo il carico di lavoro e migliorando i risultati per i pazienti.

Questo articolo ha illustrato come creare un’applicazione di apprendimento semi-supervisionato utilizzando Python e librerie di machine learning come TensorFlow. I grafici generati dimostrano l’efficacia del modello nel migliorare la sua precisione utilizzando sia dati etichettati che non etichettati. Speriamo che questo articolo possa ispirare ulteriori ricerche e applicazioni in ambito biomedico.