Skip to content

Latest commit

 

History

History
178 lines (117 loc) · 7.17 KB

ch-03.md

File metadata and controls

178 lines (117 loc) · 7.17 KB

Un primo esempio di una Rete Neurale "Fully Connected" (FC)

Introduzione.

Le prime reti neurali che sono state pensate, quelle che troviamo descritte nei capitoli iniziali di un qualsiasi libro delle serie "Deep Learning in Python", sono le cosidette reti "Fully Connected".

Una rete Fully Connected è costituita da una serie consecutiva di layer, ciascuno dei quali contiene N neuroni identici.

E' possibile utilizzare una FC anche per elaborare immagini. Ma il risultato che si ottiene è tutt'altro che ottimale.

Infatti, un grande progresso è stato compiuto quando si è iniziato ad utilizzare le CNN: Convolutional Neural Network.

Tuttavia, esaminare l'implementazione di una FC è utile, in quanto semplici, per capire come si costruiscono le Reti Neurali in PyTorch.

In alcuni testi al posto del termine "Fully Connected" si utilizza Multi-layer Perceptron )MLP).

Ah, "last but not the least": per definizione una rete Deep è una rete con molti layer tra l'input ed il layer che fornisce il risultato. Nelle applicazioni pratiche oggi si usano reti con centinaia di layer e centinaia di milioni, se non miliardi di parametri, da apprendere.

L'esempio.

In questo capitolo mostrerò:

  • come implementare passo passo una rete FC
  • come applicarla per costruire un modello di classificazione su dati strutturati (tabellari)

Il modello sarà tutt'altro che ottimale, ma l'esempio è utile per iniziare a comprendere tutti i "mattoncini" per lavorare, in PyTorch, sulle Reti Neurali.

L'efficacia delle reti Fully Connected

L'efficacia delle FC in modelli per dati tabellari è limitata. Oltretutto, vi sono una serie di complicazioni che comportano spesso un tuning difficile degli iper-parametri. E' per queste ragioni che sono state identificate architetture specializzate, come quella adottata in TabNet, utilizzata sullo stesso dataset in un altro capitolo.

L'esempio di questo capitolo non vuole fornire uno strumento "serio" per trattare dati tabellari, ma un esempio di una rete dall'architettura molto semplice, su cui vogliamo spiegare le basi di PyTorch.

Quindi, se eseguendo i Notebook le prestazioni non risulteranno ... di nostra soddisfazione, nessuna sorpresa.

Implementazione di una rete Fully Connected.

Una rete FC è una sequenza di layer, su cui è applicata una activation function

Per implementare in PyTorch una rete FC dobbiamo definire una classe, subclassando la classe nn.Module

Nella classe dobbiamo ridefinire i metodi

  • __init__
  • forward

Nel metodo init definiamo la struttura della rete, elencando tutti i layer che hanno parametri. E' fondamentale, come nell'esempio sotto riportato, che anche il dropout sia definito come attributo della classe

Nel metodo forward definiamo la sequenza di operazioni che, nel forward pass, portano dall'input all'output della rete

class FirstFC(nn.Module):
    # here we define the structure
    def __init__(self):
        super(FirstFC, self).__init__()
        self.fc1 = nn.Linear(30, 30)
        self.fc2 = nn.Linear(30, 30)
        self.fc3 = nn.Linear(30, 30)
        self.fc4 = nn.Linear(30, 10)
        self.fc5 = nn.Linear(10, 1)
        # fondamentale memorizzare anche il dropout tra gli attributi 
        # per disattivarlo in modalità eval
        # altrimenti in eval i risultati non sono pienamente riproducibili
        self.drop = nn.Dropout(0.1)
    
    # here we implement the forward pass
    def forward(self, x):
        x = self.fc1(x)
        x = F.relu(x)
        x = self.drop(x)
        
        x = self.fc2(x)
        x = F.relu(x)
        x = self.drop(x)
        
        x = F.relu(self.fc3(x))
        x = F.relu(self.fc4(x))
        
        # the last layer must give the prob that it is positive... so we use sigmoid
        # so we must use nn.BCELoss
        x = torch.sigmoid(self.fc5(x))
        
        return x

Dropout e valutazione del modello.

Il dropout è inserito, sopratutto se si teme che la rete possa andare in overfitting. In sintesi: durante il training un layer di dropout casualmente "spegne" alcuni degli output. L'esperienza ha mostrato che, sopratutto se vi è overfitting, l'introduzione di questa casualità può aiutare la rete ad apprendere e generalizzare meglio.

Il dropout è stato proposto da G. Hinton nel 2012. Un articolo di review, per approfondimenti, è il seguente.

Vi è un dettaglio di implementazione che non va assolutamente dimenticato: noi vogliamo che il dropout sia attivo durante il training del modello, ma assolutamente non durante la validazione. Altrimenti i risultati ottenuti, in fase di validazione, presenterebbero una certa "casualità" e "variabilità".

Per assicurarci che in fase di validazione i layer di dropout siano disattivati devono essere soddisfate queste due condizioni:

  • i layer di dropout devono essere definiti come attributi della classe (self.drop nell'esempio precedente)
  • come primo passo della validazione si deve invocare model.eval()

Il training loop.

PyTorch, se escludiamo Lightning, è un framework che opera un più a basso livello di Keras.

Quindi, storicamente, chi ha iniziato ad usare PyTorch si è abituato a scrivere tutto il codice del training loop.

In genere, si parte da codice di esempio e si adatta alle proprie esigenze.

Nel Notebook first_fc.ipynb vi è tutto il codice per:

  • caricare il dataset da un file csv, in un PyTorch Dataset
  • costruire la rete
  • implementare ed eseguire il training loop
  • valutare le prestazioni del modello addestrato su un validation dataset

Il training loop è implementato nella funzione train()

Analisi del training loop.

Innanzitutto, vi è un ciclo su tutte le epochs

for epoch in range(epochs):

Per ogni epoch, la prima parte applica l'algoritmo di back-propagation e calcola la training loss.

        model.train()
        training_loss = 0.0
        
        for batch in train_loader:
            optimizer.zero_grad()
            inputs, targets = batch
            
            # move to device
            inputs = inputs.to(device)
            targets = targets.to(device)
            
            # apply the model
            outputs = model(inputs)
            
            # we need to add this !
            targets = targets.unsqueeze(1)
            
            loss = loss_fn(outputs, targets)
            
            # here we do the back propagation !!
            loss.backward()
            optimizer.step()
            
            training_loss += loss.data.item() * inputs.size(0)
        
        training_loss /= len(train_loader.dataset)

La seconda parte calcola la validation loss:

        model.eval()
        val_loss = 0.0
        
        for batch in validation_loader:
            inputs, targets = batch
            
            inputs = inputs.to(device)
            targets = targets.to(device)
            
            outputs = model(inputs)
    
            targets = targets.unsqueeze(1)
            
            loss = loss_fn(outputs, targets)
            
            val_loss += loss.data.item() * inputs.size(0)
            
        val_loss /= len(validation_loader.dataset)