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:

MNIST

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:

  1. 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.
  2. 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.
  3. 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: Fonctions d'activation


Fonction Sigmoïde

Le sigmoïde est une fonction de squashing dont la sortie est dans la plage [0, 1] .

entrer la description de l'image ici

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.

entrer la description de l'image ici

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.

entrer la description de l'image ici

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:

Formule

___________________________Où est-ce que ça correspond? _____________________________

Softmax Pour normaliser un vecteur en lui appliquant la fonction softmax avec numpy , utilisez:

np.exp(x) / np.sum(np.exp(x))

x est l'activation de la couche finale de l'ANN.



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow