machine-learning
Neuronale Netze
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:
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:
- 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.
- 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.
- 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:
Sigmoid-Funktion
Das Sigmoid ist eine Squash-Funktion, deren Ausgabe im Bereich [0, 1]
.
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.
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.
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:
___________________________Wo passt es hinein? _____________________________
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.