machine-learning
Les réseaux de neurones
Recherche…
Pour commencer: un simple ANN avec Python
La liste de codes ci-dessous tente de classer les chiffres manuscrits de l'ensemble de données MNIST. Les chiffres ressemblent à ceci:
Le code prétraitera ces chiffres, convertissant chaque image en un tableau 2D de 0 et de 1, puis utilisera ces données pour former un réseau neuronal avec une précision pouvant atteindre 97% (50 époques).
"""
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)) + "%"
Ceci est un exemple de code autonome, et peut être exécuté sans autres modifications. Assurez-vous que numpy
et scikit
numpy
installés pour votre version de python.
Backpropagation - Le cœur des réseaux de neurones
Le but de la contre-propagande est d'optimiser les poids afin que le réseau neuronal puisse apprendre à mapper correctement les entrées arbitraires aux sorties.
Chaque couche a son propre ensemble de poids, et ces poids doivent être ajustés pour pouvoir prédire avec précision le bon résultat fourni.
Une vue d'ensemble de haut niveau de la propagation arrière est la suivante:
- Pass en avant - l'entrée est transformée en sortie. À chaque couche, l'activation est calculée avec un produit scalaire entre l'entrée et les poids, suivie de la somme du résultat avec le biais. Enfin, cette valeur est transmise via une fonction d'activation pour obtenir l'activation de cette couche qui deviendra l'entrée de la couche suivante.
- Dans la dernière couche, la sortie est comparée à l'étiquette réelle correspondant à cette entrée et l'erreur est calculée. Habituellement, c'est l'erreur quadratique moyenne.
- Passe en arrière - l'erreur calculée à l'étape 2 est renvoyée aux couches internes et les poids de tous les calques sont ajustés pour tenir compte de cette erreur.
1. Initialisation des poids
Un exemple simplifié d'initialisation des poids est présenté ci-dessous:
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:]])
La couche cachée 1 a un poids de dimension [64, 784] et un biais de dimension 64.
La couche de sortie a le poids de la dimension [10, 64] et le biais de la dimension
Vous vous demandez peut-être ce qui se passe lors de l’initialisation des poids dans le code ci-dessus. Cela s'appelle l'initialisation de Xavier, et c'est une étape meilleure que l'initialisation aléatoire de vos matrices de poids. Oui, l'initialisation est importante. En fonction de votre initialisation, vous pourrez peut-être trouver de meilleurs minima locaux pendant la descente de gradient (la propagation arrière est une version glorifiée de la descente de gradient).
2. Pass en avant
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))
Ce code effectue la transformation décrite ci-dessus. hidden_activations[-1]
contient des probabilités softmax - prédictions de toutes les classes, dont la somme est 1. Si nous prédisons des chiffres, alors la sortie sera un vecteur de probabilités de dimension 10, dont la somme est 1.
3. Passage arrière
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
Les 2 premières lignes initialisent les dégradés. Ces gradients sont calculés et seront utilisés pour mettre à jour les poids et les biais ultérieurement.
Les 3 lignes suivantes calculent l'erreur en soustrayant la prédiction de la cible. L'erreur est ensuite propagée aux couches internes.
Maintenant, tracez soigneusement le fonctionnement de la boucle. Les lignes 2 et 3 transforment l'erreur de la layer[i]
en layer[i - 1]
. Tracer les formes des matrices en cours de multiplication pour comprendre.
4. Mise à jour des poids / paramètres
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
spécifie la vitesse à laquelle le réseau apprend. Vous ne voulez pas qu’elle apprenne trop vite, car elle risque de ne pas converger. Une descente en douceur est favorisée pour trouver un bon minimum. Généralement, les taux compris entre 0.01
et 0.1
sont considérés comme bons.
Fonctions d'activation
Les fonctions d'activation, également connues sous le nom de fonction de transfert, sont utilisées pour mapper les nœuds d'entrée vers les nœuds de sortie d'une certaine manière.
Ils sont utilisés pour donner une non-linéarité à la sortie d'une couche de réseau neuronal.
Certaines fonctions couramment utilisées et leurs courbes sont données ci-dessous:
Fonction Sigmoïde
Le sigmoïde est une fonction de squashing dont la sortie est dans la plage [0, 1]
.
Le code pour implémenter sigmoïde avec son dérivé avec numpy
est indiqué ci-dessous:
def sigmoid(z):
return 1.0 / (1.0 + np.exp(-z))
def sigmoid_prime(z):
return sigmoid(z) * (1 - sigmoid(z))
Fonction tangente hyperbolique (tanh)
La différence fondamentale entre les fonctions tanh et sigmoïde est que tanh est centré sur 0, écrasant les entrées dans la plage [-1, 1] et est plus efficace à calculer.
Vous pouvez facilement utiliser les fonctions np.tanh
ou math.tanh
pour calculer l'activation d'une couche masquée.
Fonction ReLU
Une unité linéaire rectifiée fait simplement max(0,x)
. C'est l'un des choix les plus courants pour les fonctions d'activation des unités de réseau neuronal.
Les ReLU traitent le problème du gradient de disparition des unités sigmoïdes / tangentes hyperboliques, permettant ainsi une propagation efficace du gradient dans les réseaux profonds.
Le nom ReLU provient de l'article de Nair et Hinton, Les unités linéaires rectifiées améliorent les machines Boltzmann restreintes .
Il a quelques variantes, par exemple, les ReLUs qui fuient (LReLUs) et les Unités linéaires exponentielles (ELU).
Le code pour implémenter vanilla ReLU avec son dérivé avec numpy
est indiqué ci-dessous:
def relU(z):
return z * (z > 0)
def relU_prime(z):
return z > 0
Fonction Softmax
La régression softmax (ou régression logistique multinomiale) est une généralisation de la régression logistique au cas où nous voulons traiter plusieurs classes. Il est particulièrement utile pour les réseaux de neurones où l'on souhaite appliquer une classification non binaire. Dans ce cas, la simple régression logistique ne suffit pas. Nous aurions besoin d'une distribution de probabilités pour toutes les étiquettes, ce que Softmax nous donne.
Softmax est calculé avec la formule ci-dessous:
___________________________Où est-ce que ça correspond? _____________________________
Pour normaliser un vecteur en lui appliquant la fonction softmax avec numpy
, utilisez:
np.exp(x) / np.sum(np.exp(x))
Où x
est l'activation de la couche finale de l'ANN.