Introduzione a Pytorch: immettere funzioni non lineari |  di Ivo Bernardo |  Gennaio 2024

 | Intelligenza-Artificiale

Con i nostri dati a posto, è il momento di addestrare la nostra prima rete neurale. Utilizzeremo un’architettura simile a quella che abbiamo fatto nell’ultimo post del blog della serie, utilizzando una versione lineare della nostra rete neurale con la capacità di gestire modelli lineari:

from torch import nn

class LinearModel(nn.Module):
def __init__(self):
super().__init__()
self.layer_1 = nn.Linear(in_features=12, out_features=5)
self.layer_2 = nn.Linear(in_features=5, out_features=1)

def forward(self, x):
return self.layer_2(self.layer_1(x))

Questa rete neurale utilizza il nn.Linearmodulo da pytorch per creare una rete neurale con 1 strato profondo (uno strato di input, uno strato profondo e uno strato di output).

Sebbene possiamo creare la nostra classe ereditando da nn.Module possiamo anche usare (più elegantemente) the nn.Sequential costruttore per fare lo stesso:

model_0 = nn.Sequential(
nn.Linear(in_features=12, out_features=5),
nn.Linear(in_features=5, out_features=1)
)
model_0 Architettura della rete neurale — Immagine dell’autore

Freddo! Quindi la nostra rete neurale contiene un singolo strato interno con 5 neuroni (questo può essere visto dal out_features=5 sul primo strato).

Questo strato interno riceve lo stesso numero di connessioni da ciascun neurone di input. Il 12 pollici in_features nel primo livello riflette il numero di funzioni e 1 pollice out_features del secondo livello riflette l’output (un singolo valore compreso tra 0 e 1).

Per addestrare la nostra rete neurale, definiremo una funzione di perdita e un ottimizzatore. Definiremo BCEWithLogitsLoss (Documentazione PyTorch 2.1) poiché questa funzione di perdita (implementazione della torcia dell’entropia incrociata binaria, appropriata per problemi di classificazione) e la discesa del gradiente stocastico come ottimizzatore (utilizzando torch.optim.SGD ).

# Binary Cross entropy
loss_fn = nn.BCEWithLogitsLoss()

# Stochastic Gradient Descent for Optimizer
optimizer = torch.optim.SGD(params=model_0.parameters(),
lr=0.01)

Infine, poiché voglio calcolare anche la precisione per ogni epoca del processo di allenamento, progetteremo una funzione per calcolarlo:

def compute_accuracy(y_true, y_pred):
tp_tn = torch.eq(y_true, y_pred).sum().item()
acc = (tp_tn / len(y_pred)) * 100
return acc

È ora di addestrare il nostro modello! Addestriamo il nostro modello per 1000 epoche e vediamo come una semplice rete lineare è in grado di gestire questi dati:

torch.manual_seed(42)

epochs = 1000

train_acc_ev = ()
test_acc_ev = ()

# Build training and evaluation loop
for epoch in range(epochs):

model_0.train()

y_logits = model_0(X_train).squeeze()

loss = loss_fn(y_logits,
y_train)
# Calculating accuracy using predicted logists
acc = compute_accuracy(y_true=y_train,
y_pred=torch.round(torch.sigmoid(y_logits)))

train_acc_ev.append(acc)

# Training steps
optimizer.zero_grad()
loss.backward()
optimizer.step()
model_0.eval()

# Inference mode for prediction on the test data
with torch.inference_mode():

test_logits = model_0(X_test).squeeze()
test_loss = loss_fn(test_logits,
y_test)
test_acc = compute_accuracy(y_true=y_test,
y_pred=torch.round(torch.sigmoid(test_logits)))
test_acc_ev.append(test_acc)

# Print out accuracy and loss every 100 epochs
if epoch % 100 == 0:
print(f"Epoch: {epoch}, Loss: {loss:.5f}, Accuracy: {acc:.2f}% | Test loss: {test_loss:.5f}, Test acc: {test_acc:.2f}%")

Sfortunatamente la rete neurale che abbiamo appena costruito non è abbastanza buona per risolvere questo problema. Vediamo l’evoluzione dell’addestramento e l’accuratezza dei test:

Addestrare e testare la precisione attraverso le epoche — Immagine dell’autore

(Sto tracciando l’accuratezza invece della perdita poiché è più facile da interpretare in questo problema)

È interessante notare che la nostra rete neurale non è in grado di migliorare gran parte della precisione del set di test.

Con la conoscenza acquisita dai post precedenti del blog, possiamo provare ad aggiungere più strati e neuroni alla nostra rete neurale. Proviamo a fare entrambe le cose e vediamo il risultato:

deeper_model = nn.Sequential(
nn.Linear(in_features=12, out_features=20),
nn.Linear(in_features=20, out_features=20),
nn.Linear(in_features=20, out_features=1)
)
deep_model Architettura della rete neurale — Immagine dell’autore

Sebbene il nostro modello più profondo sia un po’ più complesso con uno strato aggiuntivo e più neuroni, ciò non si traduce in maggiori prestazioni nella rete:

Addestra e testa la precisione attraverso le epoche per un modello più profondo: immagine dell’autore

Anche se il nostro modello è più complesso, ciò non apporta maggiore precisione al nostro problema di classificazione.

Per poter ottenere maggiori prestazioni, dobbiamo sbloccare una nuova funzionalità delle reti neurali: le funzioni di attivazione!

Se rendere il nostro modello sempre più grande non ha portato molti miglioramenti, ci deve essere qualcos’altro che possiamo fare con le reti neurali che sarà in grado di migliorarne le prestazioni, giusto?

È qui che possono essere utilizzate le funzioni di attivazione! Nel nostro esempio, torneremo al nostro modello più semplice, ma questa volta con una svolta:

model_non_linear = nn.Sequential(
nn.Linear(in_features=12, out_features=5),
nn.ReLU(),
nn.Linear(in_features=5, out_features=1)
)

Qual è la differenza tra questo modello e il primo? La differenza è che abbiamo aggiunto un nuovo blocco alla nostra rete neurale: nn.ReLU . IL unità lineare rettificata è una funzione di attivazione che cambierà il calcolo in ciascuno dei pesi della Rete Neurale:

Esempio illustrativo ReLU – Immagine dell’autore

Ogni valore che passa attraverso i nostri pesi nella rete neurale verrà calcolato rispetto a questa funzione. Se il valore della caratteristica moltiplicato per il peso è negativo, il valore viene impostato su 0, altrimenti viene assunto il valore calcolato. Solo questo piccolo cambiamento aggiunge molta potenza a un’architettura di rete neurale: in torch abbiamo diverse funzioni di attivazione che possiamo usare come nn.ReLU , nn.Tanh O nn.ELU . Per una panoramica di tutte le funzioni di attivazione, controlla qui collegamento.

La nostra architettura di rete neurale contiene una piccola svolta, al momento:

Architettura della rete neurale — ReLU — Immagine dell’autore

Con questa piccola svolta nella Rete Neurale, ogni valore proveniente dal primo strato (rappresentato da nn.Linear(in_features=12, out_features=5) ) dovrà superare il test “ReLU”.

Vediamo l’impatto dell’adattamento di questa architettura ai nostri dati:

Addestrare e testare la precisione attraverso le epoche per il modello non lineare — Immagine dell’autore

Freddo! Sebbene si osservi un calo delle prestazioni dopo 800 epoche, questo modello non mostra un adattamento eccessivo come i precedenti. Tieni presente che il nostro set di dati è molto piccolo, quindi è possibile che i nostri risultati siano migliori solo per casualità. Tuttavia, aggiungendo funzioni di attivazione al tuo torch hanno sicuramente un enorme impatto in termini di prestazioni, formazione e generalizzazione, in particolare quando si hanno molti dati su cui allenarsi.

Ora che conosci la potenza delle funzioni di attivazione non lineare, è anche importante sapere:

Fonte: towardsdatascience.com

Lascia un commento

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