Il carcinoma della pelle si configura come la forma di neoplasia maligna più comune nell'uomo, spesso diagnosticato mediante osservazione visiva e screening clinico iniziale, seguiti da eventuali analisi dermoscopiche, biopsie ed esami istopatologici.

L'automazione della classificazione delle lesioni cutanee attraverso l'analisi di immagini rappresenta una sfida significativa, data la complessità e la variabilità nell'aspetto delle lesioni.

Il dataset utilizzato in questo contesto proviene dall'archivio ISIC (https://www.kaggle.com/datasets/drscarlat/melanoma) e comprende 8903 immagini di nei benigni e 8902 immagini di nei classificati come maligni. Tutte le immagini sono state uniformemente ridimensionate in formato RGB a bassa risoluzione. L'obiettivo di questo kernel è sviluppare un modello in grado di effettuare una classificazione visiva di un nevo come benigno o maligno.

Le due classi di cancro della pelle oggetto di rilevamento sono:

  1. Benigno
  2. Maligno

In questo kernel, sarà implementata una rete neurale convoluzionale utilizzando Keras con TensorFlow come backend. Successivamente, verrà condotta un'analisi dei risultati al fine di valutare l'utilità pratica del modello nell'identificazione di lesioni cutanee.

TensorFlow è una libreria software open-source per l'apprendimento automatico, sviluppata da Google Brain Team. È utilizzata per implementare algoritmi di reti neurali, sia per scopi di ricerca che per applicazioni pratiche.

TensorFlow offre un'ampia gamma di strumenti e risorse che permettono di costruire e addestrare modelli di machine learning complessi. In questo articolo esploreremo il processo di creazione di un modello di classificazione binaria utilizzando TensorFlow. L'obiettivo sarà distinguere melanoma benigno e melanoma maligno nelle immagini, prendendo ispirazione da un noto dataset su Kaggle.

🎙️
Questa esperienza ci fornirà l'opportunità di apprendere diversi aspetti importanti:

- Scaricare e decomprimere un dataset dal web
- Assemblare un modello di classificazione incorporando livelli di convoluzione e max pooling
- Implementare un ImageDataGenerator per ottimizzare la gestione delle immagini durante le fasi di training e validazione
- Compilare e allenare il modello
- Esaminare le modifiche effettuate alle immagini attraverso i vari strati della rete neurale
- Effettuare previsioni su immagini inedite

Affronteremo queste sfide tenendo presente che il deep learning richiede risorse computazionali significative. Per questo, sfrutteremo Google Colab, impostando il runtime su GPU, per gestire efficientemente i calcoli necessari.

L'indirizzo pubblico del dataset è questo

melanoma
Augmented dermoscopic pigmented skin lesions from HAM10k

e fa riferimento al relativo progetto sulla banca dati di Harvard.

Se ti interessa fare un ripasso di come funziona TensorFlow o vuoi muovere i tuoi primi passi con la libreria di Google, segui questo link

Introduzione a Tensorflow 2.0 - API e modello sequenziale di deep learning
Tensorflow è il framework creato da Google che permette ai praticanti di machine learning di creare modelli di deep learning ed è spesso la prima soluzione che viene proposta agli analisti che si approcciano per la prima volta al deep learning.

Informazioni sul set di dati

Il set di dati originale è costituito dalle immagini HAM10k, acronimo di "Human Against Machine with 10,000 images," disponibile gratuitamente. Le immagini dermoscopiche all'interno del set di dati HAM10k sono state curate e normalizzate in termini di luminosità, colori, risoluzione, eccetera. La diagnosi effettiva è stata convalidata tramite istopatologia (come fonte di verità) in oltre il 50% dei casi, rappresentando il doppio rispetto ai set di dati precedenti sulle lesioni cutanee. Per le restanti lesioni, la diagnosi è stata basata sul consenso dei dermatologi.

Invece di affrontare la classificazione di sette tipi di lesioni cutanee in un insieme fortemente sbilanciato, si è semplificato il compito concentrandosi sulla diagnosi di melanoma rispetto a non melanoma. Riassumendo le categorie sopra menzionate in due gruppi, il risultato è comunque un set di dati sbilanciato con:

  • 1113 immagini di melanoma
  • 8902 immagini di non melanoma

Per affrontare questo squilibrio, è stato applicato l'aumento dei dati al gruppo di melanoma, portando il numero di immagini in modo che fosse simile al gruppo di non melanoma.

L'aumento dei dati consente di esporre il modello a varie modifiche di un'immagine di melanoma. Ciò, a sua volta, permette al modello di apprendere e successivamente generalizzare, ad esempio, che un melanoma che punta a sinistra è comunque un melanoma se punta a destra. Il risultato è un set di dati bilanciato di 17805 immagini dopo l'aumento dei dati nel gruppo di melanoma:

  • 8903 immagini di melanoma
  • 8902 immagini di non melanoma

Ulteriori dettagli sul processo di potenziamento possono essere trovati sulla pagina Kaggle dedicata al dataset.

298869151-955559ae-a127-44dd-9c6a-b2dd885280c8.png

Breve introduzione alle convoluzioni e max pooling

Le convoluzioni e il max pooling sono tecniche di elaborazione delle immagini fondamentali per i modelli di computer vision, specialmente nelle reti neurali convoluzionali (CNNs). Questi strumenti aiutano il modello a catturare e enfatizzare le caratteristiche salienti delle immagini, facilitando l'apprendimento di pattern complessi.

Convoluzione: Questo processo coinvolge l'applicazione di un filtro, o kernel, che passa attraverso l'immagine. Immaginate ogni pixel di un'immagine come una cellula in un vasto tessuto di informazioni visive. Un filtro convoluzionale esamina una cellula (pixel) insieme alle sue vicine immediate, calcolando un nuovo valore per la cellula centrale. Questo si ottiene attraverso una griglia, tipicamente di dimensione 3x3, che pesa il valore di ogni pixel e i suoi vicini secondo i valori specificati nel filtro. Il risultato è una trasformazione del pixel centrale che riflette non solo la sua intensità originale ma anche la relazione spaziale con i suoi vicini. Questo processo di filtraggio evidenzia i pattern locali, come bordi o angoli, che sono essenziali per la comprensione visiva.

297007514-8a051c3f-3d47-4fb7-965b-1eb73803f68f.gif
https://www.developersmaggioli.it

La procedura di convoluzione in elaborazione delle immagini si basa sull'uso di un particolare array, noto come kernel o filtro. Questo kernel, solitamente più piccolo dell'immagine da elaborare, viene fatto scorrere su tutta l'immagine.

Un esempio tipico è un kernel di dimensione 3x3 che si muove su un'immagine 5x5. Durante questo processo, ogni elemento dell'immagine viene combinato con il kernel, producendo una nuova immagine, chiamata mappa delle caratteristiche (o feature map), che qui è illustrata come una matrice 5x5 di colore verde.

Un dettaglio importante in questo processo è il cosiddetto padding, che è l'aggiunta di bordi extra (qui mostrati in bianco) attorno all'immagine originale. Il padding è usato per mantenere le dimensioni desiderate nell'immagine di output (nella nostra esemplificazione, una matrice 5x5). Senza il padding, l'immagine risultante sarebbe ridotta nelle sue dimensioni (nel nostro caso, a 3x3).

Il padding non è solo una soluzione tecnica per conservare le dimensioni, ma aiuta anche a preservare le informazioni ai bordi dell'immagine, che altrimenti potrebbero essere perse nei livelli successivi di elaborazione.

La concezione fondamentale di una convoluzione si focalizza sull'accentuare specifici attributi di un'immagine, per esempio esaltando i bordi e i contorni per renderli più distinti rispetto allo sfondo ecco un esempio.

image
https://www.developersmaggioli.it

Max Pooling: Dopo la convoluzione, spesso segue il pooling, che riduce le dimensioni dell'immagine mantenendo le caratteristiche più prominenti. Il max pooling, in particolare, scorre un filtro attraverso l'immagine e seleziona il valore massimo all'interno di una finestra di valori (ad esempio, all'interno di una griglia 2x2). Questo non solo riduce la complessità computazionale per le operazioni successive ma conserva anche le caratteristiche più rilevanti che sono state evidenziate dalla convoluzione.

Queste operazioni sono essenziali per permettere alle reti neurali di apprendere gerarchie di caratteristiche visive, da semplici texture e forme a oggetti complessi all'interno di immagini.

298082763-01ef33b5-01c0-49c1-aa72-2e55ebcb64c6.png

Creazione del modello

È cruciale notare che è fondamentale fornire al modello immagini con dimensioni uniformi. La scelta di queste dimensioni è arbitraria, e per questo specifico modello abbiamo optato per immagini ridimensionate a 150x150 pixel, rendendole così tutte quadrate.

Considerando che lavoriamo con immagini a colori, è essenziale incorporare questa informazione nella nostra progettazione. Pertanto, la forma di input sarà (150, 150, 3), dove il valore 3 rappresenta i tre canali di colore RGB. Successivamente, vedremo come garantire che tutte le nostre immagini siano conformi a questa dimensione quando sfruttiamo ImageDataGenerator.

Ora procediamo con l'implementazione dell'architettura della rete neurale.

Installiamo la libreria opendataset

298102372-b1e097e4-71b5-48b2-9f99-553db1b07014.png

Scarichiamo il dataset attraverso API di Kaggle. Documentazione per accedervi qui.

298102971-65972a27-f2d1-4485-aa04-7a2a9f71d7da.png

Ecco il codice per scrivere l'architettura del modello TensorFlow

import os
# importante rinominare path melanoma/DermMel/train_sep in :
# /content/melanoma/DermMel/train

current_path = '/content/melanoma/DermMel/train_sep'
new_path = '/content/melanoma/DermMel/train'

if not os.path.exists(new_path):
    os.rename(current_path, new_path)
    print(f"La cartella è stata rinominata da {current_path} a {new_path}")
else:
    print(f"Il percorso di destinazione {new_path} esiste già. Non è necessario rinominare la cartella.")

# cartelle di apprendimento e validazione

base_dir = '/content/melanoma/DermMel'
train_dir = os.path.join(base_dir, 'train')
validation_dir = os.path.join(base_dir, 'valid')

# puntiamo alle cartelle di Melanoma e NotMelanoma per il training
train_Melanoma_dir = os.path.join(train_dir, 'Melanoma')
train_NotMelanoma_dir = os.path.join(train_dir, 'NotMelanoma')

# puntiamo alle cartelle di Melanoma e NotMelanoma per la validazione
validation_Melanoma_dir = os.path.join(validation_dir, 'Melanoma')
validation_NotMelanoma_dir = os.path.join(validation_dir, 'NotMelanoma')

import tensorflow as tf

# Creiamo un modello sequenziale
model = tf.keras.models.Sequential([
    # Primo strato di convoluzione con 16 filtri 3x3, attivazione ReLU e dimensione dell'input 150x150x3
    tf.keras.layers.Conv2D(16, (3,3), activation='relu', input_shape=(150, 150, 3)),

    # Strato di MaxPooling 2x2
    tf.keras.layers.MaxPooling2D(2,2),

    # Secondo strato di convoluzione con 32 filtri 3x3 e attivazione ReLU
    tf.keras.layers.Conv2D(32, (3,3), activation='relu'),

    # Altro strato di MaxPooling 2x2
    tf.keras.layers.MaxPooling2D(2,2),

    # Terzo strato di convoluzione con 64 filtri 3x3 e attivazione ReLU
    tf.keras.layers.Conv2D(64, (3,3), activation='relu'),

    # Ulteriore strato di MaxPooling 2x2
    tf.keras.layers.MaxPooling2D(2,2),

    # Appiattiamo il risultato per passarlo ad uno strato denso
    tf.keras.layers.Flatten(),

    # Strato denso con 512 neuroni e attivazione ReLU
    tf.keras.layers.Dense(512, activation='relu'),

    # Strato di output con un singolo neurone e attivazione sigmoide
    tf.keras.layers.Dense(1, activation='sigmoid')
    #output 0 benigno 1 maligno
])

# Visualizziamo una panoramica del modello
model.summary()

All'esecuzione di questo codice, vedremo un riassunto con model.summary() che appare così

298295182-633540a0-28f0-4492-9cd1-d97a6d365485.png

L'immagine in input è di dimensioni 150x150. La riduzione di 1 pixel su ciascun lato dell'immagine durante la convoluzione è dovuta all'utilizzo di un filtro (kernel) di dimensione 3x3. Questo filtro scorre sull'immagine con passi (stride) di default, solitamente 1 pixel alla volta, riducendo così la dimensione dell'output.

Per capire meglio, consideriamo il centro del filtro 3x3. Se immaginiamo questo filtro posizionato sull'immagine, noteremo che il suo centro coincide con un punto sull'immagine. Tuttavia, nei bordi dell'immagine, non c'è spazio sufficiente per posizionare completamente il filtro senza superare i bordi. Quindi, per mantenere la dimensione dell'output compatibile con l'input, perdiamo un pixel su ciascun lato.

Quanto al numero 64 nell'output shape, rappresenta il numero di filtri (o kernel) applicati. Ogni filtro estrae differenti caratteristiche dall'immagine. Quindi, in questo caso, abbiamo 64 filtri convoluzionali applicati all'immagine di input, ognuno dei quali produce un'immagine di output 148x148.

In breve, l'output shape 148x148x64 nel primo strato convoluzionale indica che stiamo ottenendo 64 diverse immagini di output (o feature maps), ciascuna di dimensioni 148x148, applicando filtri 3x3 all'immagine di input di dimensioni 150x150. Questo processo di convoluzione aiuta la rete a identificare e catturare diverse caratteristiche dell'immagine durante le fasi iniziali del processo di apprendimento.

Dopo l'applicazione della prima convoluzione, osserviamo la successiva riduzione delle dimensioni dell'immagine attraverso uno strato di max pooling, che dimezza la sua dimensione. Questo processo continua iterativamente fino a raggiungere lo strato di flatten, il quale prende l'output a quel punto e lo trasforma in un singolo vettore.

Successivamente, questo vettore viene passato attraverso uno strato denso composto da 512 neuroni. Infine, la rete termina con un singolo neurone di output, che produce un valore binario: 0 o 1 (benigno o maligno).

In questo caso, stiamo utilizzando l'ottimizzatore Adam, una funzione di loss di crossentropia binaria e l'accuratezza come metrica di performance.

Compiliamo il modello in questo modo

# Compilazione del modello
model.compile(optimizer='adam',
              loss='binary_crossentropy',
              metrics=['accuracy'])

La funzione di perdita binaria o "binary_crossentropy" è una metrica comune utilizzata nei problemi di classificazione binaria, in cui l'obiettivo è predire se un'istanza appartiene a una delle due classi, di solito etichettate come 0 e 1 (benigno maligno). Questa funzione di perdita è particolarmente adatta per i modelli di machine learning che utilizzano l'output sigmoideo.

Ecco una breve spiegazione dei principali concetti di "binary_crossentropy"

  • Problema di Classificazione Binaria: La binary crossentropy è specificamente progettata per problemi di classificazione binaria, in cui ogni esempio di addestramento può appartenere solo a una delle due classi
  • Modello Sigmoideo: La binary crossentropy è spesso associata a modelli che utilizzano la funzione di attivazione sigmoidea nell'ultimo strato. La funzione sigmoidea comprime i valori in un intervallo tra 0 e 1, producendo probabilità che possono essere interpretate come la probabilità che un'istanza appartenga alla classe positiva (1)
  • Calcolo della Perdita: La binary crossentropy misura la discrepanza tra la distribuzione di probabilità prevista dal modello e la distribuzione di probabilità reale dei dati di addestramento. Per un singolo esempio, la formula della binary crossentropy è:
298588469-14511964-78ac-4815-a063-b3297403939f.png

dove:

    • \( y \) è l'etichetta reale (0 o 1)
    • \( p \) è la probabilità prevista dal modello che l'esempio appartenga alla classe 1
  • Ottimizzazione: L'obiettivo durante l'addestramento del modello è minimizzare la binary crossentropy. Questo viene fatto aggiustando i pesi della rete neurale utilizzando tecniche di ottimizzazione come l'algoritmo di ottimizzazione Adam
  • Interpretazione: Un valore basso di binary crossentropy indica che le previsioni del modello sono vicine alle etichette reali, mentre un valore alto indica una discrepanza significativa. L'obiettivo è ridurre la perdita durante l'addestramento, migliorando così la capacità del modello di fare previsioni accurate.

In sintesi, la binary crossentropy è una misura della discrepanza tra le previsioni del modello e le etichette reali in problemi di classificazione binaria. Riducendo questa perdita durante l'addestramento, si migliora la capacità del modello di fare previsioni precise.

Preprocessing delle Immagini con ImageDataGenerator in TensorFlow

La fase successiva del processo coinvolge il preprocessing delle immagini per garantire la loro idoneità al modello. Le immagini saranno ridimensionate in modo uniforme, convertite in formato float64 e associate alle rispettive etichette (cioè, "benigno" o "maligno"). Successivamente, queste informazioni verranno fornite al modello.

Saranno creati due generatori distinti: uno dedicato all'addestramento e l'altro alla validazione. Ciascun generatore effettuerà la conversione delle immagini in valori numerici normalizzati nell'intervallo compreso tra 0 e 255. Considerando che 255 rappresenta il massimo valore di intensità di un pixel, questo processo comporterà che un pixel con intensità massima di 255 diventerà 1, mentre un pixel "spento" sarà rappresentato da 0. I valori intermedi saranno opportunamente scalati tra 0 e 1.

In TensorFlow, l'utilizzo di ImageDataGenerator semplifica notevolmente queste operazioni. La sua potenza risiede nella capacità di generare automaticamente le etichette per le immagini, basandosi sulla struttura gerarchica e sulla nomenclatura delle cartelle contenenti le immagini.

In sintesi, l'intero processo di preprocessing delle immagini si basa su una standardizzazione delle dimensioni, la conversione dei pixel in formato float64 e la normalizzazione dei valori pixel nell'intervallo [0, 1]. ImageDataGenerator svolge un ruolo fondamentale, semplificando ulteriormente il processo grazie alla sua capacità di generare etichette in modo automatico, considerando la struttura delle seguenti cartelle.

Senza titolo.png
from tensorflow.keras.preprocessing.image import ImageDataGenerator


# riscaliamo tutte le nostre immagini con il parametro rescale
train_datagen = ImageDataGenerator(rescale = 1.0/255)
test_datagen  = ImageDataGenerator(rescale = 1.0/255)

# utilizziamo flow_from_directory per creare un generatore per il training
train_generator = train_datagen.flow_from_directory(train_dir,
                                                    batch_size=20,
                                                    class_mode='binary',
                                                    target_size=(150, 150))

# utilizziamo flow_from_directory per creare un generatore per la validazione
validation_generator =  test_datagen.flow_from_directory(
    validation_dir, 
    batch_size=20, 
    class_mode='binary', 
    target_size=(150, 150)
)

Il Viaggio dell'Addestramento: 15 Epoche di Apprendimento Profondo

L'avvincente fase di addestramento del nostro modello ha inizio, con l'obiettivo di plasmare le sue abilità predittive. Questo processo coinvolge 334 steps per epoca, basati sul batch size del nostro generatore di addestramento. Nel corso di 15 epoche, il modello assorbirà le informazioni dal set di 8903 immagini, affinando le sue capacità predittive.

Il codice di addestramento si configura nel seguente modo:

history = model.fit(
    train_generator,  # Generatore per il training
    steps_per_epoch=334,  # Numero di steps per epoca basato sul batch size
    epochs=15,  # Numero di epoche
    validation_data=validation_generator,  # Generatore per la validazione
    validation_steps=111,  # Numero di steps per epoca di validazione basato sul batch size
    verbose=2  # Livello di verbosità
)

Esplorare le Rappresentazioni Neurali attraverso la Visualizzazione

Un aspetto affascinante nell'analisi delle reti neurali convoluzionali (CNN) è l'osservazione delle informazioni chiave estratte dalle immagini e delle rappresentazioni generate durante il passaggio attraverso i differenti strati della rete. In questo contesto, useremo un modello fornito da Keras per esplorare questo processo, fornendo all'architettura neurale input precedentemente utilizzati per l'addestramento.

L'analisi delle rappresentazioni neurali offre una finestra privilegiata sull'elaborazione delle informazioni all'interno della rete. Ogni strato di una CNN svolge un ruolo specifico nell'identificare pattern e caratteristiche distintive. Possiamo osservare come l'immagine di input viene trasformata e rappresentata in maniera più astratta via via che attraversa i vari strati della rete.

Questo processo di estrazione di feature è fondamentale nel comprendere come una CNN interpreta e categorizza le informazioni visive. Ad esempio, le prime layers possono essere specializzate nel riconoscere bordi e dettagli di basso livello, mentre le layers più profonde catturano feature complesse e astratte come forme e pattern specifici.

Per esplorare ulteriormente le rappresentazioni neurali, possiamo utilizzare tecniche di visualizzazione come mappe di attivazione e filtri convoluzionali. Le mappe di attivazione evidenziano le regioni dell'immagine che hanno contribuito maggiormente all'attivazione di specifici neuroni, mentre i filtri convoluzionali ci permettono di visualizzare cosa la rete ha appreso durante l'addestramento.

import numpy as np
import matplotlib.pyplot as plt
from tensorflow.keras.preprocessing import image

# Estrai i modelli per gli strati convoluzionali
layer_outputs = [layer.output for layer in model.layers[:4]] 
activation_model = tf.keras.models.Model(inputs=model.input, outputs=layer_outputs)
base_dir = '/content/melanoma/DermMel/valid/Melanoma/AUG_0_1833.jpeg'
# Carica l'immagine da visualizzare
img = image.load_img(base_dir, target_size=(150, 150))
img_tensor = image.img_to_array(img)
img_tensor = np.expand_dims(img_tensor, axis=0)
img_tensor /= 255.0

# Calcola le attivazioni
activations = activation_model.predict(img_tensor)

# Funzione per visualizzare le attivazioni
def display_activation(activations, col_size, row_size, act_index):
    activation = activations[act_index]
    # Ottieni il numero di filtri nello strato
    num_filters = activation.shape[-1]

    fig, ax = plt.subplots(row_size, col_size, figsize=(row_size*2.5,col_size*1.5))
    for row in range(row_size):
        for col in range(col_size):
            ax[row][col].imshow(activation[0, :, :, row * col_size + col], cmap='viridis')
            if row * col_size + col + 1 == num_filters:
                # Interrompi se hai raggiunto il numero di filtri

                return
# visualizzare le attivazioni
display_activation(activations, col_size=4, row_size=4, act_index=1)

La rete neurale, con la sua sofisticata capacità di analisi, interpreta il melanoma attraverso un intricato sguardo al microscopio digitale, discernendo minuziosamente le caratteristiche morfologiche e pigmentarie, trasformando l'arte della diagnosi dermatologica in un processo tecnologicamente avanzato e preciso.

Da notare come vengono messi in risalto assimmetria, bordi, colore, diametro.
Queste caratteristiche estratte attraverso gli strati della rete giocano un ruolo cruciale nel consentire alla rete neurale di comprendere e identificare melanomi.

La visualizzazione delle rappresentazioni neurali è una pratica essenziale poiché ci consente di esplorare ciò che viene messo in evidenza durante le convoluzioni e le aggregazioni nei vari strati.

Questo approccio fornisce un insight fondamentale per comprendere come la rete neurale interpreta le informazioni visive legate ai melanomi. La possibilità di individuare eventuali discrepanze o rilevare feature non pertinenti consente di affrontare potenziali errori nella predizione.

Se la rete neurale, ad esempio, mettesse in risalto caratteristiche non rilevanti che contribuiscono a predizioni errate, questa analisi delle rappresentazioni diventa il primo punto di ispezione. In tal caso, è necessario eseguire un'analisi manuale per identificare e correggere queste distorsioni.

In seguito, anche l'ottimizzazione dell'architettura della rete diventa importante per garantire che le rappresentazioni neurali siano in linea con le informazioni salienti per una corretta classificazione dei melanomi.

Valutazione del modello

La valutazione del modello può essere fatta con il seguente pezzo di codice

import matplotlib.pyplot as plt

acc = history.history['accuracy']
val_acc = history.history['val_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(len(acc))

# Plottiamo la accuracy con un grafico 
plt.scatter(epochs, acc, label='Training Accuracy')
plt.scatter(epochs, val_acc, label='Validation Accuracy')
plt.title('Accuracy in training e validazione')
plt.legend()
plt.show()

# Creiamo un nuovo grafico per la loss 
plt.scatter(epochs, loss, label='Training Loss')
plt.scatter(epochs, val_loss, label='Validation Loss')
plt.title('Loss in training e validazione')
plt.legend()
plt.show()

La precisione misura la frazione di previsioni corrette fatte dal modello rispetto al totale delle previsioni effettuate. È una metrica utile per problemi di classificazione, ma può essere ingannevole quando le classi sono sbilanciate. Ad esempio, se hai un set di dati in cui il 95% delle immagini è non maligno e solo il 5% è maligno, un modello che prevede sempre "non maligno" otterrebbe una precisione del 95%, ma non sarebbe molto utile. In tali casi, altre metriche come la sensibilità e la specificità possono essere più informative.

I problemi comuni nella valutazione di modelli di reti neurali includono l'overfitting, che si verifica quando il modello impara i dati di allenamento troppo bene ma non generalizza bene su nuovi dati, e l'underfitting, che si verifica quando il modello non impara abbastanza dai dati di allenamento e non fa previsioni accurate.

Esempio di overfitting

Il grafico mostra l'andamento dell'errore quadratico medio (Mean Squared Error, MSE) sia per il dataset di training che per quello di test, al variare delle epoche di addestramento.

All'inizio, entrambi gli errori diminuiscono man mano che la rete impara dai dati. Tuttavia, dopo un certo numero di epoche, l'errore di test inizia a peggiorare mentre quello di training continua a diminuire. Questo è un classico segnale di overfitting: la rete neurale sta imparando perfettamente i dettagli (e anche il rumore) del dataset di training a discapito della sua capacità di generalizzare su dati non visti (il dataset di test).

L'overfitting è un problema comune nell'addestramento di modelli di machine learning, soprattutto in presenza di reti neurali profonde e complesse. Combatte l'overfitting richiede strategie come l'early stopping, la regolarizzazione (ad esempio, L1/L2), il dropout, o l'ottenimento di più dati di training. ​

Esempio di underfitting

Il grafico illustra l'underfitting durante l'addestramento di una rete neurale. In questo scenario, sia l'errore di training che quello di test rimangono elevati e non migliorano significativamente con l'aumentare delle epoche. Questo suggerisce che il modello è troppo semplice per catturare la complessità dei dati.

L'underfitting è evidente quando un modello non riesce a ridurre l'errore di training, indicando che non sta imparando adeguatamente dai dati. A differenza dell'overfitting, dove il modello impara troppo bene i dati di training a scapito della generalizzazione, nell'underfitting il modello non riesce nemmeno a imparare i dati di training in modo efficace.

Per affrontare l'underfitting, potremmo considerare l'uso di un modello più complesso con più neuroni o strati, l'aggiunta di più feature che possono aiutare il modello a imparare meglio dai dati, o la revisione del preprocessing dei dati per assicurarsi che non stiamo perdendo informazioni importanti.

Per un approfondimento del seguente argomento leggi l'articolo di Andrea D'Agostino

Il più grande ostacolo nel machine learning: l’overfitting
L’overfitting è un concetto nella data science che si manifesta quando un modello predittivo impara bene a generalizzare i dati di training ma non quelli di test

Fare predizioni sui melanomi

Ora possiamo passare una immagine di test di melanoma benigno o maligno per la predizione, in questo caso passo l'immagine di melanoma maligno come test.

import numpy as np
from keras.preprocessing import image
import tensorflow as tf



# Inserimento manuale del percorso dell'immagine
path = input("Inserisci il percorso dell'immagine: ")

# Caricamento e preparazione dell'immagine
img = image.load_img(path, target_size=(150, 150))  # Scala l'immagine alla dimensione target
x = image.img_to_array(img)  # Converte l'immagine in un array
x /= 255  # Normalizza i valori dei pixel
x = np.expand_dims(x, axis=0)  # Aggiungi una dimensione per adattarsi all'input del modello
model_path = '/content/save_model/rete-neurale_model.h5'

# model = load_model('/content/save_model/rete-neurale_model.h5')
model = tf.keras.models.load_model(model_path)

# Esecuzione della predizione
classes = model.predict(x, batch_size=10)

print(classes[0])

# Interpretazione del risultato
if classes[0] > 0.5:
    print(path.split("/")[-1] + " è benigno!")
else:
    print(path.split("/")[-1] + " è maligno!")

Output

Secondo test

Seconda immagine di test melanoma benigno.

298888961-b1b80f27-5495-4f6e-8501-defbe8dc8241.jpg


Output

298889198-082945e4-1a66-4729-bb29-df49528ac18e.png

Siamo giunti alla conclusione dell'articolo e desidero mettere a disposizione il repository GitHub per coloro che desiderano contribuire o apportare miglioramenti al codice.

GitHub - GiaStra92/TensorFlow-Melanoma-detected
Contribute to GiaStra92/TensorFlow-Melanoma-detected development by creating an account on GitHub.