machine-learning
Reti neurali
Ricerca…
Per iniziare: una semplice ANN con Python
Il seguente elenco di codici tenta di classificare le cifre scritte a mano dal set di dati MNIST. Le cifre assomigliano a questo:
Il codice eseguirà il preprocesso di queste cifre, convertendo ciascuna immagine in una matrice 2D di 0 e 1 e quindi utilizzando questi dati per addestrare una rete neurale con un'accuratezza del 97% (50 epoche).
"""
Deep Neural Net
(Name: Classic Feedforward)
"""
import numpy as np
import pickle, json
import sklearn.datasets
import random
import time
import os
def sigmoid(z):
return 1.0 / (1.0 + np.exp(-z))
def sigmoid_prime(z):
return sigmoid(z) * (1 - sigmoid(z))
def relU(z):
return np.maximum(z, 0, z)
def relU_prime(z):
return z * (z <= 0)
def tanh(z):
return np.tanh(z)
def tanh_prime(z):
return 1 - (tanh(z) ** 2)
def transform_target(y):
t = np.zeros((10, 1))
t[int(y)] = 1.0
return t
"""--------------------------------------------------------------------------------"""
class NeuralNet:
def __init__(self, layers, learning_rate=0.05, reg_lambda=0.01):
self.num_layers = len(layers)
self.layers = layers
self.biases = [np.zeros((y, 1)) for y in layers[1:]]
self.weights = [np.random.normal(loc=0.0, scale=0.1, size=(y, x)) for x, y in zip(layers[:-1], layers[1:])]
self.learning_rate = learning_rate
self.reg_lambda = reg_lambda
self.nonlinearity = relU
self.nonlinearity_prime = relU_prime
def __feedforward(self, x):
""" Returns softmax probabilities for the output layer """
for w, b in zip(self.weights, self.biases):
x = self.nonlinearity(np.dot(w, np.reshape(x, (len(x), 1))) + b)
return np.exp(x) / np.sum(np.exp(x))
def __backpropagation(self, x, y):
"""
:param x: input
:param y: target
"""
weight_gradients = [np.zeros(w.shape) for w in self.weights]
bias_gradients = [np.zeros(b.shape) for b in self.biases]
# forward pass
activation = x
hidden_activations = [np.reshape(x, (len(x), 1))]
z_list = []
for w, b in zip(self.weights, self.biases):
z = np.dot(w, np.reshape(activation, (len(activation), 1))) + b
z_list.append(z)
activation = self.nonlinearity(z)
hidden_activations.append(activation)
t = hidden_activations[-1]
hidden_activations[-1] = np.exp(t) / np.sum(np.exp(t))
# backward pass
delta = (hidden_activations[-1] - y) * (z_list[-1] > 0)
weight_gradients[-1] = np.dot(delta, hidden_activations[-2].T)
bias_gradients[-1] = delta
for l in range(2, self.num_layers):
z = z_list[-l]
delta = np.dot(self.weights[-l + 1].T, delta) * (z > 0)
weight_gradients[-l] = np.dot(delta, hidden_activations[-l - 1].T)
bias_gradients[-l] = delta
return (weight_gradients, bias_gradients)
def __update_params(self, weight_gradients, bias_gradients):
for i in xrange(len(self.weights)):
self.weights[i] += -self.learning_rate * weight_gradients[i]
self.biases[i] += -self.learning_rate * bias_gradients[i]
def train(self, training_data, validation_data=None, epochs=10):
bias_gradients = None
for i in xrange(epochs):
random.shuffle(training_data)
inputs = [data[0] for data in training_data]
targets = [data[1] for data in training_data]
for j in xrange(len(inputs)):
(weight_gradients, bias_gradients) = self.__backpropagation(inputs[j], targets[j])
self.__update_params(weight_gradients, bias_gradients)
if validation_data:
random.shuffle(validation_data)
inputs = [data[0] for data in validation_data]
targets = [data[1] for data in validation_data]
for j in xrange(len(inputs)):
(weight_gradients, bias_gradients) = self.__backpropagation(inputs[j], targets[j])
self.__update_params(weight_gradients, bias_gradients)
print("{} epoch(s) done".format(i + 1))
print("Training done.")
def test(self, test_data):
test_results = [(np.argmax(self.__feedforward(x[0])), np.argmax(x[1])) for x in test_data]
return float(sum([int(x == y) for (x, y) in test_results])) / len(test_data) * 100
def dump(self, file):
pickle.dump(self, open(file, "wb"))
"""--------------------------------------------------------------------------------"""
if __name__ == "__main__":
total = 5000
training = int(total * 0.7)
val = int(total * 0.15)
test = int(total * 0.15)
mnist = sklearn.datasets.fetch_mldata('MNIST original', data_home='./data')
data = zip(mnist.data, mnist.target)
random.shuffle(data)
data = data[:total]
data = [(x[0].astype(bool).astype(int), transform_target(x[1])) for x in data]
train_data = data[:training]
val_data = data[training:training+val]
test_data = data[training+val:]
print "Data fetched"
NN = NeuralNet([784, 32, 10]) # defining an ANN with 1 input layer (size 784 = size of the image flattened), 1 hidden layer (size 32), and 1 output layer (size 10, unit at index i will predict the probability of the image being digit i, where 0 <= i <= 9)
NN.train(train_data, val_data, epochs=5)
print "Network trained"
print "Accuracy:", str(NN.test(test_data)) + "%"
Questo è un esempio di codice autonomo e può essere eseguito senza ulteriori modifiche. Assicurati di aver installato numpy
e scikit
per la tua versione di python.
Backpropagation - The Heart of Neural Networks
L'obiettivo di backpropagation è ottimizzare i pesi in modo che la rete neurale possa imparare come mappare correttamente gli input e gli output arbitrari.
Ogni livello ha il proprio set di pesi e questi pesi devono essere calibrati per poter prevedere con precisione il giusto input dato in input.
Una panoramica di alto livello della propagazione del retro è la seguente:
- Forward pass - l'input viene trasformato in un output. Ad ogni livello, l'attivazione viene calcolata con un prodotto punto tra l'input e i pesi, seguito dalla somma del risultante con il bias. Infine, questo valore viene passato attraverso una funzione di attivazione, per ottenere l'attivazione di quel livello che diventerà l'input per il livello successivo.
- Nell'ultimo livello, l'output viene confrontato con l'etichetta effettiva corrispondente a quell'input e l'errore viene calcolato. Di solito, è l'errore quadratico medio.
- Passaggio all'indietro: l'errore calcolato nel passaggio 2 viene propagato ai livelli interni e i pesi di tutti i livelli vengono adattati per tenere conto di questo errore.
1. Inizializzazione dei pesi
Di seguito è riportato un esempio semplificato di inizializzazione dei pesi:
layers = [784, 64, 10]
weights = np.array([(np.random.randn(y, x) * np.sqrt(2.0 / (x + y))) for x, y in zip(layers[:-1], layers[1:])])
biases = np.array([np.zeros((y, 1)) for y in layers[1:]])
Il livello nascosto 1 ha il peso della dimensione [64, 784] e il bias della dimensione 64.
Il livello di output ha il peso della dimensione [10, 64] e il bias della dimensione
Ci si potrebbe chiedere cosa sta succedendo quando si inizializzano i pesi nel codice sopra. Questa è chiamata inizializzazione Xavier ed è un passo migliore dell'inizializzazione casuale delle matrici di peso. Sì, l'inizializzazione conta. In base alla tua inizializzazione, potresti essere in grado di trovare un minimo locale migliore durante la discesa del gradiente (la propagazione posteriore è una versione glorificata della discesa del gradiente).
2. Passaggio in avanti
activation = x
hidden_activations = [np.reshape(x, (len(x), 1))]
z_list = []
for w, b in zip(self.weights, self.biases):
z = np.dot(w, np.reshape(activation, (len(activation), 1))) + b
z_list.append(z)
activation = relu(z)
hidden_activations.append(activation)
t = hidden_activations[-1]
hidden_activations[-1] = np.exp(t) / np.sum(np.exp(t))
Questo codice esegue la trasformazione sopra descritta. hidden_activations[-1]
contiene le probabilità softmax - le previsioni di tutte le classi, la cui somma è 1. Se si prevedono cifre, l'output sarà un vettore di probabilità della dimensione 10, la cui somma è 1.
3. Passaggio a ritroso
weight_gradients = [np.zeros(w.shape) for w in self.weights]
bias_gradients = [np.zeros(b.shape) for b in self.biases]
delta = (hidden_activations[-1] - y) * (z_list[-1] > 0) # relu derivative
weight_gradients[-1] = np.dot(delta, hidden_activations[-2].T)
bias_gradients[-1] = delta
for l in range(2, self.num_layers):
z = z_list[-l]
delta = np.dot(self.weights[-l + 1].T, delta) * (z > 0) # relu derivative
weight_gradients[-l] = np.dot(delta, hidden_activations[-l - 1].T)
bias_gradients[-l] = delta
Le prime 2 righe inizializzano i gradienti. Questi gradienti sono calcolati e verranno utilizzati per aggiornare i pesi e i bias successivi.
Le 3 linee successive calcolano l'errore sottraendo la previsione dalla destinazione. L'errore viene quindi nuovamente propagato ai livelli interni.
Ora, traccia attentamente il funzionamento del ciclo. Le righe 2 e 3 trasformano l'errore dal layer[i]
al layer[i - 1]
. Traccia le forme delle matrici moltiplicate per capire.
4. Aggiornamento pesi / parametri
for i in xrange(len(self.weights)):
self.weights[i] += -self.learning_rate * weight_gradients[i]
self.biases[i] += -self.learning_rate * bias_gradients[i]
self.learning_rate
specifica la velocità con cui la rete impara. Non vuoi che impari troppo velocemente, perché potrebbe non convergere. Una discesa morbida è favorita per trovare una buona minima. Di solito, i tassi tra 0.01
e 0.1
sono considerati buoni.
Funzioni di attivazione
Le funzioni di attivazione, note anche come funzione di trasferimento, vengono utilizzate per mappare i nodi di input per i nodi di output in un determinato modo.
Sono utilizzati per impartire non linearità all'output di uno strato di rete neurale.
Di seguito alcune funzioni comunemente utilizzate e le loro curve:
Funzione sigmoide
Il sigmoid è una funzione di compressione il cui output è nell'intervallo [0, 1]
.
Il codice per implementare sigmoid insieme alla sua derivata con numpy
è mostrato di seguito:
def sigmoid(z):
return 1.0 / (1.0 + np.exp(-z))
def sigmoid_prime(z):
return sigmoid(z) * (1 - sigmoid(z))
Funzione iperbolica tangente (tanh)
La differenza fondamentale tra le funzioni tanh e sigmoide è che tanh è 0 centrato, schiaccia gli input nell'intervallo [-1, 1] ed è più efficiente da calcolare.
È possibile utilizzare facilmente le funzioni np.tanh
o math.tanh
per calcolare l'attivazione di un livello nascosto.
Funzione ReLU
Un'unità lineare rettificata non fa altro che max(0,x)
. È una delle scelte più comuni per le funzioni di attivazione delle unità di rete neurali.
Le ReLU risolvono il problema del gradiente di fuga delle unità tangenti sigmoide / iperboliche, consentendo una propagazione graduale efficiente nelle reti profonde.
Il nome ReLU viene dalla carta di Nair e Hinton, le unità rettificate rettificate migliorano le macchine Boltzmann con restrizioni .
Ha alcune variazioni, ad esempio, riLU (leali) (LReLU) e unità lineari esponenziali (ELU).
Il codice per l'implementazione di ReLU vanilla insieme alla sua derivata con numpy
è mostrato di seguito:
def relU(z):
return z * (z > 0)
def relU_prime(z):
return z > 0
Funzione Softmax
La regressione di Softmax (o regressione logistica multinomiale) è una generalizzazione della regressione logistica nel caso in cui vogliamo gestire più classi. È particolarmente utile per le reti neurali in cui vogliamo applicare la classificazione non binaria. In questo caso, la semplice regressione logistica non è sufficiente. Avremmo bisogno di una distribuzione di probabilità su tutte le etichette, che è ciò che ci offre Softmax.
Softmax è calcolato con la seguente formula:
___________________________Dove si adatta? _____________________________
Per normalizzare un vettore applicando la funzione softmax ad esso con numpy
, utilizzare:
np.exp(x) / np.sum(np.exp(x))
Dove x
è l'attivazione dallo strato finale della RNA.