Il Neurone Artificiale Adaline: Introduzione

Parte (1/2)

Potrebbe interessarti:

Il progetto Python, che comprende gli script mostrati in questo post, e quelli relativi alla creazione dei grafici Matplotlib e Plotly, è disponibile assieme al corso Introduzione al Machine Learning.

Dimmi di Più

Cos’è un Neurone Artificiale

Adaline (Adaptive Linear Neuron) è uno dei primi modelli di neurone artificiale, sviluppato da Bernard Widrow e Ted Hoff nel 1960. Adaline nasce pochi anni dopo il Percettrone, sviluppato da Frank Rosenblatt alla fine degli anni ‘50, e introduce un’importante innovazione nel modo in cui vengono appresi i parametri del modello. Le origini del concetto di neurone artificiale risalgono però ancora più indietro: nel 1943 Warren McCulloch e Walter Pitts proposero uno dei primi modelli matematici di neurone artificiale, ponendo alcune delle basi teoriche per lo sviluppo delle successive reti neurali.

Un neurone artificiale è una singola unità di calcolo che combina i valori delle feature di input attraverso una somma pesata:

$$ z = \mathbf{w}\cdot\mathbf{x} + b $$

e utilizza il risultato per produrre un output. Nel caso di Adaline, il modello può essere utilizzato per svolgere compiti di classificazione binaria, nei quali le osservazioni vengono assegnate, ad esempio, alla classe $-1$ oppure alla classe $1$ (o, a seconda dell’implementazione, alle classi $0$ e $1$). Una singola unità come Adaline costituisce un modello estremamente semplice, ma il principio di combinare input pesati e apprendere i pesi dai dati è alla base di modelli molto più complessi1. Possiamo quindi pensare ad Adaline come a uno dei precursori dei moderni classificatori e delle reti neurali: un modello semplice e con importanti limitazioni, ma storicamente e didatticamente molto significativo.

Adaline fu sviluppato in un periodo nel quale le risorse di calcolo erano estremamente limitate e molti degli strumenti oggi comunemente associati alle reti neurali non erano ancora utilizzati nella loro forma moderna. La semplicità computazionale del modello era quindi estremamente importante. Per questo motivo, useremo Adaline come strumento didattico per comprendere alcuni dei meccanismi fondamentali dell’apprendimento supervisionato: la somma pesata degli input, la funzione di costo, l’aggiornamento dei parametri e la Discesa del Gradiente. Successivamente passeremo a modelli di classificazione più moderni e funzionali, come la Regressione Logistica.

L’Algoritmo di Classificazione

Adaline estende l’implementazione del nostro modello di regressione lineare, riutilizzando molti concetti che già conosciamo, come la funzione di ipotesi, la funzione di costo e il training iterativo del modello, e aggiungendo alcune nuove funzionalità che vedremo di seguito. Il diagramma delle sue componenti è mostrato nell’immagine seguente:

Diagramma di Adaline e delle sue parti costituenti
Figura 1: Diagramma di Adaline (Adaptive Linear Neuron)

Nel contesto del Machine Learning, si incontrano spesso affermazioni secondo cui “un neurone artificiale è un’approssimazione matematica di un neurone biologico”, oppure “un’unità di calcolo che imita il comportamento di un neurone biologico”.

Questi parallelismi possono essere utili dal punto di vista intuitivo e storico, ma non devono essere interpretati in modo letterale. Un neurone artificiale è infatti un modello matematico estremamente semplificato: alcune sue caratteristiche sono state originariamente ispirate al funzionamento dei neuroni biologici, ma non cerca di riprodurne fedelmente la struttura o il comportamento. Il funzionamento dei neuroni biologici e, più in generale, del cervello è molto più complesso di quello rappresentato dai modelli utilizzati nelle reti neurali artificiali.

In dettaglio, Adaline è definito dai seguenti componenti:

Le Feature di Input

Adaline riceve in input una o più variabili indipendenti, o feature, $x^{(i)}_1$, …, $x^{(i)}_n$, che descrivono l’$i$-esima osservazione del dataset.

I Pesi

A ogni variabile indipendente $x^{(i)}_j$ è associato un peso $w_j$ che, tramite la somma pesata (weighted sum), determina il contributo di tale feature al valore di output del neurone.

I pesi sono parametri regolabili, che l’algoritmo di apprendimento modifica durante la fase di training. A parità delle altre feature, un peso positivo indica che all’aumentare della feature corrispondente aumenta il valore della somma pesata, mentre un peso negativo produce l’effetto opposto. Il valore assoluto del peso determina invece quanto una variazione della feature influenza la somma pesata: maggiore è $|w_j|$, maggiore è l’effetto prodotto da una variazione di $x_j$. Bisogna tuttavia ricordare che la grandezza dei pesi dipende anche dalla scala delle feature. Per questo motivo, i valori assoluti dei pesi di feature misurate su scale diverse non possono essere confrontati direttamente per determinarne l’importanza.

Il Bias

Adaline utilizza inoltre un termine di bias, $b$, anch’esso regolato durante il processo di training. Il bias permette di traslare la funzione lineare rispetto all’origine e consente quindi al modello di adattarsi a dati per i quali la relazione da apprendere non passa necessariamente per l’origine dello spazio.

La Somma Pesata

La somma pesata delle feature di input, $\eqref{wsum}$, definisce la funzione di ipotesi2 del modello, $h\left(\textbf{x}^{(i)}\right)$, che produce un valore continuo:

$$ \begin{align*} z^{(i)} & = w_1x^{(i)}_1 + w_2x^{(i)}_2 + \dots + w_nx^{(i)}_n + b\label{wsum}\tag{1}\\[6pt] & = \sum_{j=1}^{n} w_jx^{(i)}_j + b\\[6pt] & = \textbf{w} \cdot \textbf{x}^{(i)} + b\label{wsum-vect}\tag{2}\\[6pt] & = h\left(\textbf{x}^{(i)}\right)\label{hypo}\tag{3}\\[6pt] \end{align*} $$

A livello geometrico, come abbiamo già visto, la funzione di ipotesi può essere rappresentata come un iperpiano in uno spazio di $n+1$ dimensioni, dove $n$ è il numero di feature e la dimensione aggiuntiva rappresenta l’output $h$. Ad esempio, con due feature $x_1$ e $x_2$, possiamo rappresentare $\eqref{hypo}$ nello spazio tridimensionale $(x_1,x_2,h)$ come un piano. Durante il training, i parametri $\mathbf{w}$ e $b$ vengono modificati in modo che questo piano approssimi il più possibile i valori del dataset di training, secondo la funzione di costo utilizzata dal modello, come mostrato in Figura 2:

Figura 2: Funzione di ipotesi in uno spazio 3D

La Funzione di Attivazione

La somma pesata $z^{(i)}$ viene passata in input a una funzione di attivazione $\sigma$ (sigma) per produrre il valore di uscita del neurone associato al vettore delle feature $\textbf{x}^{(i)}$:

$$ \text{out}^{(i)} = \sigma\left(z^{(i)}\right)\label{activation_identity}\tag{4} $$

In una moderna rete neurale multi-layer, la funzione di attivazione svolge un ruolo fondamentale: riceve in input la somma pesata calcolata dal neurone e, generalmente, applica a essa una trasformazione non lineare. Questa non-linearità è essenziale perché, senza di essa, la composizione di più layer lineari rimane comunque una trasformazione lineare. Le funzioni di attivazione non lineari permettono invece alla rete di apprendere relazioni e pattern complessi nei dati, rendendo possibile affrontare problemi di classificazione o regressione caratterizzati da relazioni non lineari.

Ad esempio, la funzione di attivazione sigmoide applica una trasformazione non lineare che permette di mappare un valore reale arbitrario appartenente all’intervallo $\left(-\infty,\infty\right)$ in un valore compreso nell’intervallo $\left(0,1\right)$. Nel contesto della classificazione binaria, questo valore può essere interpretato come una probabilità.

Nel modello Adaline, invece, la funzione di attivazione non introduce alcuna non-linearità. Si tratta infatti di una semplice funzione identità, che restituisce in output il valore di ingresso $z^{(i)}$ senza applicare alcuna trasformazione:

$$ \begin{align*} \text{out}^{(i)} & = \sigma\left(z^{(i)}\right) = z^{(i)} \label{identity}\tag{5}\\[6pt] \end{align*} $$

Adaline produce quindi un output reale e continuo. Questa caratteristica è particolarmente importante durante il training. A differenza del Percettrone, che aggiorna i propri parametri sulla base dell’output discreto prodotto da una Step function, Adaline calcola la funzione di costo direttamente sull’output lineare continuo $\text{out}^{(i)}$. È quindi possibile utilizzare come funzione di costo l’errore quadratico medio, MSE, e ottimizzare i parametri mediante la discesa del gradiente.

L’utilizzo dell’output lineare continuo $\text{out}^{(i)}=z^{(i)}$ nel calcolo della MSE evita di introdurre nella funzione di costo la soglia discreta utilizzata per effettuare la classificazione.

Poiché $z^{(i)}$ è una funzione lineare dei parametri $\mathbf{w}$ e $b$, la MSE risultante è una funzione quadratica, convessa e differenziabile rispetto a tali parametri. È quindi possibile calcolarne il gradiente e utilizzare la Discesa del Gradiente per cercare il suo minimo globale.

La funzione di soglia necessaria per trasformare l’output continuo in una classe viene utilizzata successivamente per effettuare la classificazione e non interviene nel calcolo della MSE.

A differenza della Regressione Logistica, Adaline non restituisce una probabilità. Il valore $\text{out}^{(i)}=z^{(i)}$ è un punteggio reale, che può assumere qualsiasi valore nell’intervallo $\left(-\infty,+\infty\right)$. Per ottenere una classe, questo punteggio deve essere successivamente trasformato attraverso una funzione di soglia.

L’output della funzione di attivazione può essere interpretato anche geometricamente. Per un qualsiasi vettore di feature $\mathbf{x}^{(i)}$, il modello produce il valore:

$$ \text{out}^{(i)} = \mathbf{w}\cdot\mathbf{x}^{(i)}+b $$

Il punto $\left(\mathbf{x}^{(i)},\text{out}^{(i)}\right)$ giace quindi sull’iperpiano definito dalla funzione di ipotesi $\eqref{hypo}$. Ad esempio:

Figura 3: Funzione di attivazione in uno spazio vettoriale 3D

La Funzione di Soglia

La funzione di soglia permette di trasformare l’output continuo prodotto dalla funzione di attivazione $\eqref{identity}$ in una classe, utilizzata come previsione finale del classificatore. Dato un valore di soglia $\theta$ (theta), possiamo definire:

$$ \hat{y}\left(z^{(i)}\right) = \label{threshold_1}\tag{6} \begin{cases} \text{ClassA} & \text{se $z^{(i)} \ge \theta$}\\[6pt] \text{ClassB} & \text{altrimenti}\\[6pt] \end{cases} $$

La funzione $\eqref{threshold_1}$ indica che, se l’output continuo $z^{(i)}$ è maggiore o uguale alla soglia $\theta$, l’osservazione viene assegnata alla classe $\text{ClassA}$; altrimenti viene assegnata alla classe $\text{ClassB}$.

Adaline viene tradizionalmente formulato utilizzando le label $-1$ e $+1$ e una soglia $\theta=0$. È tuttavia possibile utilizzare anche label $0$ e $1$. Se il modello viene addestrato utilizzando questi valori come target, possiamo ad esempio scegliere $\theta=0.5$ e definire:

$$ \hat{y}\left(z^{(i)}\right) = \label{threshold_2}\tag{7} \begin{cases} 1 & \text{se $z^{(i)} \ge 0.5$}\\[6pt] 0 & \text{altrimenti}\\[6pt] \end{cases} $$

È importante ricordare che, in Adaline, $z^{(i)}$ non rappresenta una probabilità. La soglia $0.5$ rappresenta semplicemente il valore scelto per separare le due classi quando queste sono codificate come $0$ e $1$. In relazione al grafico sottostante, Figura 4, la funzione di soglia assegna alla classe $1$ le osservazioni per le quali $z^{(i)} \ge 0.5$, e alla classe $0$ quelle per le quali $z^{(i)} < 0.5$. La soglia determina quindi due regioni decisionali nello spazio delle feature: una nella quale il modello predice $\hat{y}=0$ e l’altra nella quale predice $\hat{y}=1$. La superficie che separa queste due regioni prende il nome di confine decisionale (decision boundary).

Nel caso di Adaline, il confine decisionale è lineare. Infatti, poiché:

$$ z = \mathbf{w}\cdot\mathbf{x}+b $$

il passaggio da una classe all’altra avviene quando:

$$ z=\theta $$

e quindi:

$$ \mathbf{w}\cdot\mathbf{x}+b=\theta $$

Questa equazione definisce un iperpiano nello spazio delle feature. Con due feature $x_1$ e $x_2$, ad esempio, il confine decisionale è una retta.

Ritornando a Figura 4, per un dato vettore di feature $\mathbf{x}^{(i)}=\left(x^{(i)}_1,x^{(i)}_2\right)$, i punti verdi trasparenti

$$ \left(x^{(i)}_1,x^{(i)}_2,\text{out}^{(i)}\right) $$

rappresentano gli output continui prodotti dalla funzione di attivazione, mentre i punti rossi e blu

$$ \left(x^{(i)}_1,x^{(i)}_2,\hat{y}^{(i)}\right) $$

rappresentano le corrispondenti classi prodotte dalla funzione di soglia. In questo esempio, le osservazioni appartenenti alla regione decisionale “rossa” vengono assegnate alla classe $0$, mentre quelle appartenenti alla regione decisionale “blu” vengono assegnate alla classe $1$:

Figura 4: Funzione di soglia in 3D

La Funzione di Costo

Adaline utilizza la stessa funzione di costo che abbiamo già incontrato nella regressione lineare: la Mean Squared Error (MSE), o errore quadratico medio. Durante il training, la MSE misura la differenza quadratica media tra i valori target $y^{(i)}$ e gli output continui $\text{out}^{(i)}$ prodotti dalla funzione di attivazione. Per Adaline, invece di indicare l’output continuo con $\hat{y}^{(i)}$, utilizziamo il termine $\text{out}^{(i)}$, in modo da distinguerlo dalla previsione di classe $\hat{y}^{(i)}$ prodotta successivamente dalla funzione di soglia:

$$ \begin{align*} \text{MSE}(\textbf{w}, b) & = \frac{1}{m} \sum_{i=1}^{m}\left(y^{(i)} - \text{out}^{(i)}\right)^2 \label{mse_loss}\tag{8}\\[6pt] & = \frac{1}{m} \sum_{i=1}^{m}\left[y^{(i)} - \left(\textbf{w} \cdot \textbf{x}^{(i)} + b\right)\right]^2\\[6pt] \end{align*} $$

La funzione di costo viene quindi calcolata sull’output continuo del modello, prima dell’applicazione della funzione di soglia.

Come classificatore, Adaline utilizza la $\text{MSE}$ (errore quadratico medio), una funzione di costo più naturale per problemi di regressione che per problemi di classificazione.

La MSE può essere applicata anche quando i target sono valori discreti, come $0$ e $1$ oppure $-1$ e $+1$. Tuttavia, li tratta numericamente come target di regressione: durante il training, Adaline cerca quindi di rendere l’output continuo $\text{out}^{(i)}$ il più vicino possibile al valore numerico della label $y^{(i)}$. L’obiettivo dell’ottimizzazione non è quindi direttamente quello di trovare un confine decisionale che separi nel modo migliore le due classi, ma quello di trovare l’iperpiano lineare che approssima nel modo migliore i valori numerici assegnati alle label, minimizzando l’errore quadratico medio.

La classificazione viene ottenuta solo successivamente, applicando una funzione di soglia all’output continuo del modello.

L’Algoritmo di Training

Anche per il training, Adaline utilizza lo stesso algoritmo che abbiamo già incontrato nella regressione lineare: la Discesa del Gradiente (Gradient Descent, GD). Dopo aver inizializzato i parametri $\mathbf{w}$ e $b$, a ogni iterazione del Gradient Descent viene calcolato il gradiente della funzione di costo rispetto ai pesi e al bias:

$$ \begin{align*} \require{physics} \mathbf{g}_w &= \pdv{\text{MSE}}{\mathbf{w}} = \frac{2}{m} \sum_{i=1}^{m} \mathbf{x}^{(i)}\left(\text{out}^{(i)} - y^{(i)}\right) \label{g_w}\tag{9}\\[6pt] g_b &= \pdv{\text{MSE}}{b} = \frac{2}{m} \sum_{i=1}^{m} \left(\text{out}^{(i)} - y^{(i)}\right) \label{g_b}\tag{10} \end{align*} $$

dove $\mathbf{g}_w$ è il vettore delle derivate parziali rispetto ai pesi $w_1,\ldots,w_n$, mentre $g_b$ è la derivata parziale rispetto al bias. I parametri vengono quindi aggiornati nella direzione opposta al gradiente:

$$ \begin{align*} \require{physics} \textbf{w} & = \textbf{w} - \eta \textbf{g}_w\label{update_w}\tag{11}\\[6pt] b & = b - \eta g_b\label{update_b}\tag{12}\\[6pt] \end{align*} $$

dove $\eta$ è il learning rate, che determina l’ampiezza degli aggiornamenti effettuati a ogni iterazione. Ripetendo questo procedimento, il Gradient Descent modifica progressivamente $\mathbf{w}$ e $b$ con l’obiettivo di ridurre la MSE e avvicinarsi al minimo della funzione di costo.

Implementazione del Modello

Vediamo ora com’è possibile implementare il modello Adaline in Python. Il codice seguente, rispetto agli esempi precedenti, utilizza il paradigma della programmazione orientata agli oggetti (OOP). Andremo a creare la classe Python AdalineGD che implementi il comportamento di Adaline, ed useremo una sua istanza per testarne la capacità di classificazione. Questo moderato aumento della complessità nella scrittura del codice porta alcuni vantaggi. In particolare:

  • OOP consente la creazione di codice modulare, in cui i diversi modelli ML possono essere incapsulati in classi, le quali possono essere riutilizzate in progetti diversi. Questo riduce la ridondanza del codice e i relativi tempi di sviluppo. L’incapsulamento del codice garantisce inoltre che lo stato interno di un oggetto sia “nascosto” al codice esterno alla classe, rendendo il codice più robusto e più facile da mantenere.

  • è possibile definire modelli con la stessa interfaccia, rendendo più facile cambiare un modello con un altro, tenendo costante il codice di training e valutazione delle metriche di performance. Questo fatto ci tornerà utile quando cominceremo a confrontare alcuni dei nostri modelli “giocattolo” con quelli messi a disposizione dalla libreria Scikit-Learn.

  • possiamo identificare i metodi privati dando loro un nome che inizi con doppio carattere underscore __, come ad esempio __gradient, oppure __loss. L’interfaccia pubblica utilizzabile dal codice client sarà quindi definita da tutti gli altri metodi, come ad esempio: fit, predict, predict_regression.

Il Costruttore della Classe

Il costruttore della classe, definito dal metodo __init__, definisce lo stato dell’istanza di classe, e consente di settare i seguenti parametri:

  • eta: il tasso di apprendimento, o “learning rate”, utilizzato nella fase di training, che controlla la grandezza dei “passi” compiuti dalla discesa del gradiente verso il minimo della funzione di costo. La scelta del giusto valore di “eta” è cruciale, in quanto esso controlla quanto i pesi del modello vengano aggiustati rispetto al gradiente della funzione di costo. Un valore di “eta” troppo basso può rallentare la convergenza3 del modello verso la soluzione ottimale, mentre un valore troppo alto può muovere il processo di training lontano dal minimo della funzione di costo.

  • epochs: il numero di iterazioni usate dalla funzione di training. Avendo scelto un valore corretto per eta, più è alto il valore di epochs, migliori saranno le metriche di prestazione del modello, a scapito di un tempo di training più lungo.

import numpy as np

class AdalineGD:
    """ADAptive LInear NEuron classifier"""

    def __init__(self, eta=0.01, epochs=50):
        """Initialize this object."""
        self.losses = None
        self.bias = None
        self.weights = None
        self.eta = eta
        self.epochs = epochs
        self.random_state = random_state

Il Metodo di Training

Il metodo fit4 implementa l’algoritmo di training del modello, usando la discesa del gradiente. Il codice è uguale a quello del metodo train usato dalla regressione lineare multipla. Il nome del metodo è stato cambiato da train a fit per avere un’interfaccia OOP simile a quella dei modelli di classificazione proposti da Scikit-Learn:

    def fit(self, X, y):
        """Fit training data. That is, compute the best values for weights and bias parameters
        that minimize the Loss Function associated with this model."""

        # clone the label data and reshape it as a 2D array
        y = np.copy(y).reshape(-1, 1)

        # initialize weights with a normal distribution, and bias with 0
        rgen = np.random.default_rng(seed=self.random_state)
        self.weights = rgen.normal(loc=0.0, scale=0.01, size=(X.shape[1], 1))
        self.bias = np.float64(0.)
        self.losses = []

        # for each training epoch...
        for i in range(self.epochs):
            # compute the gradient vectors:
            #
            #   ∂L/∂w = 2/m * sum(i=1 to m) { x^(i) * (out^(i) - y^(i)) }
            #         = 2/m * (X.T @ (out - y))
            #
            #   ∂L/∂b = 2/m * sum(i=1 to m) { out^(i) - y^(i) }
            #         = 2/m * (1^T)(out - y)
            #
            z = self.__net_input(X)                    # shape: (n_examples, 1)
            out = self.__activation(z)                 # shape: (n_examples, 1)
            error = out - y                            # shape: (n_examples, 1)
            grad_w = 2.0 * (X.T @ error) / X.shape[0]  # shape: (n_features, 1)
            grad_b = 2.0 * np.mean(error)              # float

            # calculate the steps in the direction of the w-axis and b-axis axis
            # used to reach the flat point on the loss curve for this iteration
            step_w = self.eta * grad_w                 # shape: (n_features, 1)
            step_b = self.eta * grad_b                 # float

            # by convention, the gradient at a certain point is an arrow that points
            # directly uphill from that point. Because we need to go into the opposite direction,
            # we have to subtract "step_w" from the current value of "w", and do the same for "b"
            self.weights -= step_w                     # shape: (n_features, 1)
            self.bias -= step_b                        # bias: float

            # compute the loss for the current iteration
            loss = np.average(error ** 2)
            self.losses.append(loss)

        return self

Confrontando questo codice con quello del modello della regressione lineare multipla, troviamo una differenza nel calcolo dei gradienti e del valore di costo. In Adaline, l’errore per la singola osservazione viene calcolato come la differenza tra l’uscita della funzione di attivazione $\sigma$, e la label $y^{(i)}$, ovvero:

# Adaline:
z = self.__net_input(X)     # weighted sum
out = self.__activation(z)  # activation function
error = out - y             # error

# Linear Regression:
y_hat = predict(X, w, b)    # weighted sum
error = y_hat - y           # error

In pratica, visto che la funzione di attivazione $\sigma$ è una funzione di identità, e quindi $\sigma\left(z^{(i)}\right) = z^{(i)}$, il calcolo dell’errore è equivalente. Il codice completo è indicato di seguito:

    def fit(self, X, y):
        # ...

        # for each training epoch...
        for i in range(self.epochs):
            # compute the gradient vectors:
            #
            #   ∂L/∂w = 2/m * sum(i=1 to m) { x^(i) * (out^(i) - y^(i)) }
            #         = 2/m * (X.T @ (out - y))
            #
            #   ∂L/∂b = 2/m * sum(i=1 to m) { out^(i) - y^(i) }
            #         = 2/m * (1^T)(out - y)
            #
            z = self.__net_input(X)                    # shape: (n_examples, 1)
            out = self.__activation(z)                 # shape: (n_examples, 1)
            error = out - y                            # shape: (n_examples, 1)
            grad_w = 2.0 * (X.T @ error) / X.shape[0]  # shape: (n_features, 1)
            grad_b = 2.0 * np.mean(error)              # float

            # ...

            # compute the loss for the current iteration
            loss = np.average(error ** 2)
            self.losses.append(loss)

        return self

I Metodi di Predizione

Adaline fonisce due metodi di predizione: predict_regression per predirre numeri reali continui, e predict per eseguire predizioni categoriche.

Il metodo predict_regression calcola la somma pesata $z^{(i)}$, che passa in ingresso alla funzione di attivazione $\sigma$, che restituisce il valore di uscita del neurone:

    def predict_regression(self, X):
        """Predict the regression value for the input data."""
        z = self.__net_input(X)
        out = self.__activation(z)
        return out

Il metodo predict calcola il valore di uscita del neurone tramite predict_regression, e poi passa tale valore alla funzione di soglia __threshold, che lo trasforma in un valore categorico: classe “$0$”, o classe “$1$”:

    def predict(self, X):
        """Predict the class label for the input data."""
        out = self.predict_regression(X)
        y_hat = self.__threshold(out)
        return y_hat

I restanti metodi privati sono elencati di seguito. Da notare che, in Adaline, il metodo __activation implementa una funzione d’identità, e quindi non fa nulla di utile. Teniamo comunque la definizione di tale metodo come “placeholder”, in quanto esso verrà reimplementato da altri modelli di neurone, che applicheranno al suo valore di ingresso una data funzione di trasformazione non lineare:

    def __net_input(self, X):
        """Calculate the weighted sum plus the bias."""
        return X @ self.weights + self.bias

    def __activation(self, X):
        """Apply an Identity Function to the input data and return it."""
        return X

    def __threshold(self, out):
        """Transform a regression value into a classification value,
        taking the continuous value returned by the activation function
        and converting it into a categorical value, used as a classification prediction."""
        return np.where(out >= 0.5, 1, 0)

  1. come le reti neurali multi-layer usate nel Deep Learning ↩︎

  2. la funzione di ipotesi definisce la relazione appresa dal modello tra le variabili di input, o feature, e la variabile di output, cercando di approssimare la funzione target sconosciuta. ↩︎

  3. Quando diciamo che un modello ML “converge”, intendiamo che il processo di addestramento ha raggiunto un punto in cui l’ulteriore addestramento non cambia significativamente le metriche di prestazione del modello. ↩︎

  4. Nel contesto ML, il termine “fit” indica il processo di addestramento del modello, mediante un dataset di training. L’addestramento regola i parametri del modello per minimizzare una funzione di perdita specificata e migliorare le previsioni del modello. ↩︎

Caricamento
  • Rendering delle formule LaTeX...
  • Rendering dei grafici Plotly...