Suche…


Erste Schritte: Ein einfaches ANN mit Python

Die folgende Codeauflistung versucht, handschriftliche Ziffern aus dem MNIST-Dataset zu klassifizieren. Die Ziffern sehen so aus:

MNIST

Der Code verarbeitet diese Ziffern vor, konvertiert jedes Bild in ein 2D-Array aus 0 und 1 und verwendet diese Daten, um ein neuronales Netzwerk mit bis zu 97% Genauigkeit (50 Epochen) zu trainieren.

"""
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)) + "%"

Dies ist ein eigenständiges Codebeispiel, das ohne weitere Änderungen ausgeführt werden kann. Stellen Sie sicher, dass Sie numpy und scikit Learn für Ihre Python-Version installiert haben.

Backpropagation - Das Herz neuronaler Netze

Das Ziel der Backpropagation besteht darin, die Gewichte so zu optimieren, dass das neuronale Netzwerk lernen kann, beliebige Eingaben korrekt auf die Ausgänge abzubilden.

Jede Ebene verfügt über einen eigenen Satz von Gewichtungen, und diese Gewichtungen müssen abgestimmt werden, um die richtige Ausgabe bei gegebener Eingabe genau vorhersagen zu können.

Eine allgemeine Übersicht über die Rückwärtsausbreitung ist wie folgt:

  1. Vorwärtsdurchlauf - Die Eingabe wird in eine Ausgabe umgewandelt. In jeder Schicht wird die Aktivierung mit einem Punktprodukt zwischen der Eingabe und den Gewichtungen berechnet, gefolgt vom Summieren des Ergebnisses mit der Verzerrung. Schließlich wird dieser Wert durch eine Aktivierungsfunktion geleitet, um die Aktivierung der Ebene zu erhalten, die zur Eingabe der nächsten Ebene wird.
  2. In der letzten Schicht wird die Ausgabe mit der tatsächlichen Bezeichnung verglichen, die dieser Eingabe entspricht, und der Fehler wird berechnet. Normalerweise handelt es sich um den mittleren quadratischen Fehler.
  3. Rückwärtsdurchlauf - der in Schritt 2 berechnete Fehler wird zurück zu den inneren Schichten übertragen und die Gewichte aller Schichten werden angepasst, um diesen Fehler zu berücksichtigen.

1. Gewichte Initialisierung

Ein vereinfachtes Beispiel für die Initialisierung von Gewichten ist unten dargestellt:

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:]])
  • Die verborgene Schicht 1 hat das Gewicht der Abmessung [64, 784] und die Neigung der Abmessung 64.

  • Die Ausgabeschicht hat das Gewicht der Dimension [10, 64] und das Maß der Dimension

Sie fragen sich vielleicht, was passiert, wenn Sie Gewichte im obigen Code initialisieren. Dies wird als Xavier-Initialisierung bezeichnet. Dies ist ein Schritt, der besser ist als die zufällige Initialisierung Ihrer Gewichtsmatrizen. Ja, die Initialisierung ist wichtig. Basierend auf Ihrer Initialisierung können Sie möglicherweise ein besseres lokales Minimum während des Gradientenabstiegs finden (die Rückwärtsausbreitung ist eine verherrlichte Version des Gradientenabstiegs).

2. Pass weiterleiten

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))

Dieser Code führt die oben beschriebene Transformation durch. hidden_activations[-1] enthält Softmax-Wahrscheinlichkeiten - Vorhersagen für alle Klassen, deren Summe 1 ist. Wenn wir Ziffern vorhersagen, ist der Ausgang ein Vektor von Wahrscheinlichkeiten der Dimension 10, dessen Summe 1 ist.

3. Rückwärtspass

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

Die ersten 2 Zeilen initialisieren die Farbverläufe. Diese Gradienten werden berechnet und zur späteren Aktualisierung der Gewichtungen und Verzerrungen verwendet.

Die nächsten 3 Zeilen berechnen den Fehler durch Subtraktion der Vorhersage vom Ziel. Der Fehler wird dann zu den inneren Schichten zurückgesandt.

Verfolgen Sie nun sorgfältig die Arbeitsweise der Schleife. Die Zeilen 2 und 3 transformieren den Fehler von layer[i] in layer[i - 1] . Verfolgen Sie die Formen der Matrizen, die zum Verständnis multipliziert werden.

4. Gewichte / Parameteraktualisierung

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 gibt die Rate an, mit der das Netzwerk lernt. Sie möchten nicht, dass es zu schnell lernt, weil es möglicherweise nicht konvergiert. Ein sanfter Abstieg wird bevorzugt, um ein gutes Minimum zu finden. Normalerweise gelten Raten zwischen 0.01 und 0.1 als gut.

Aktivierungsfunktionen

Aktivierungsfunktionen, die auch als Übertragungsfunktion bezeichnet werden, werden verwendet, um Eingabeknoten auf bestimmte Weise Ausgabeknoten zuzuordnen.

Sie werden verwendet, um dem Ausgang einer neuronalen Netzwerkschicht eine Nichtlinearität zu verleihen.

Nachfolgend sind einige häufig verwendete Funktionen und deren Kurven aufgeführt: Aktivierungsfunktionen


Sigmoid-Funktion

Das Sigmoid ist eine Squash-Funktion, deren Ausgabe im Bereich [0, 1] .

Geben Sie hier die Bildbeschreibung ein

Der Code zum Implementieren von Sigmoid zusammen mit seiner Ableitung mit numpy ist unten dargestellt:

def sigmoid(z):
    return 1.0 / (1.0 + np.exp(-z))

def sigmoid_prime(z):
    return sigmoid(z) * (1 - sigmoid(z))

Hyperbolische Tangentenfunktion (Tanh)

Der grundlegende Unterschied zwischen den tanh- und sigmoidischen Funktionen besteht darin, dass tanh 0 zentriert ist, wodurch die Eingaben im Bereich [-1, 1] gestaucht werden und die Berechnung effizienter ist.

Geben Sie hier die Bildbeschreibung ein

Sie können die Funktionen np.tanh oder math.tanh einfach verwenden, um die Aktivierung einer verborgenen Ebene zu berechnen.

ReLU-Funktion

Eine gleichgerichtete lineare Einheit macht einfach max(0,x) . Dies ist eine der häufigsten Optionen für Aktivierungsfunktionen von neuronalen Netzwerkeinheiten.

Geben Sie hier die Bildbeschreibung ein

ReLUs adressieren das verschwindende Gradientenproblem von sigmoid / hyperbolischen Tangentialeinheiten und ermöglichen so eine effiziente Gradientenausbreitung in tiefen Netzwerken.

Der Name ReLU stammt von Nair und Hinton's Artikel Rectified Linear Units Improve Restricted Boltzmann Machines .

Es gibt einige Variationen, zum Beispiel undichte ReLUs (LReLUs) und Exponential Lineare Einheiten (ELUs).

Der Code für die Implementierung von Vanilla ReLU zusammen mit ihrer Ableitung mit " numpy ist unten dargestellt:

def relU(z):
    return z * (z > 0)

def relU_prime(z):
    return z > 0

Softmax-Funktion

Softmax-Regression (oder multinomiale logistische Regression) ist eine Verallgemeinerung der logistischen Regression auf den Fall, dass mehrere Klassen behandelt werden sollen. Dies ist besonders nützlich für neuronale Netzwerke, in denen eine nicht-binäre Klassifizierung angewendet werden soll. In diesem Fall reicht eine einfache logistische Regression nicht aus. Wir benötigen eine Wahrscheinlichkeitsverteilung auf alle Labels, die uns Softmax gibt.

Softmax wird mit der folgenden Formel berechnet:

Formel

___________________________Wo passt es hinein? _____________________________

Softmax Um einen Vektor zu normalisieren, indem Sie die softmax-Funktion mit numpy auf ihn numpy , verwenden Sie:

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

Dabei ist x die Aktivierung von der letzten Schicht des ANN.



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow