machine-learning
Neurale netwerken
Zoeken…
Aan de slag: een eenvoudige ANN met Python
De onderstaande codelijst probeert handgeschreven cijfers uit de MNIST-gegevensset te classificeren. De cijfers zien er zo uit:
De code zal deze cijfers voorbewerken, waarbij elke afbeelding wordt omgezet in een 2D-reeks van 0'en en 1'en en vervolgens deze gegevens gebruiken om een neuraal netwerk te trainen met een nauwkeurigheid tot 97% (50 epochs).
"""
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)) + "%"
Dit is een zelfstandig codevoorbeeld en kan zonder verdere wijzigingen worden uitgevoerd. Zorg ervoor dat je numpy
en scikit
learn hebt geïnstalleerd voor je versie van python.
Backpropagation - The Heart of Neural Networks
Het doel van backpropagation is het optimaliseren van de gewichten zodat het neurale netwerk kan leren hoe willekeurige ingangen correct kunnen worden toegewezen aan uitgangen.
Elke laag heeft zijn eigen set gewichten en deze gewichten moeten worden afgestemd om de juiste output gegeven input nauwkeurig te kunnen voorspellen.
Een overzicht op hoog niveau van rugpropagatie is als volgt:
- Voorwaartse doorgang - de invoer wordt omgezet in een uitvoer. Bij elke laag wordt de activering berekend met een puntproduct tussen de invoer en de gewichten, gevolgd door het optellen van de resultante met de voorspanning. Ten slotte wordt deze waarde door een activeringsfunctie geleid om de activering van die laag te krijgen die de invoer voor de volgende laag wordt.
- In de laatste laag wordt de uitvoer vergeleken met het werkelijke label dat overeenkomt met die invoer en wordt de fout berekend. Meestal is dit de gemiddelde kwadratische fout.
- Achterwaartse doorgang - de fout berekend in stap 2 wordt teruggevoerd naar de binnenste lagen en de gewichten van alle lagen worden aangepast om deze fout te verklaren.
1. Initialisatie van gewichten
Een vereenvoudigd voorbeeld van initialisatie van gewichten wordt hieronder weergegeven:
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:]])
Verborgen laag 1 heeft het gewicht van afmeting [64, 784] en voorspanning van afmeting 64.
De uitvoerlaag heeft het gewicht van dimensie [10, 64] en een voorspanning van dimensie
Je vraagt je misschien af wat er aan de hand is bij het initialiseren van gewichten in de bovenstaande code. Dit wordt Xavier-initialisatie genoemd en het is een stap beter dan het willekeurig initialiseren van uw gewichtsmatrices. Ja, initialisatie is belangrijk. Op basis van uw initialisatie, kunt u mogelijk een betere lokale minima vinden tijdens verloopdaling (rugvoortplanting is een verheerlijkte versie van verloopdaling).
2. Voorwaartse pas
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))
Deze code voert de hierboven beschreven transformatie uit. hidden_activations[-1]
bevat softmax-kansen - voorspellingen van alle klassen, waarvan de som 1 is. Als we cijfers voorspellen, is de uitvoer een vector van kansen van dimensie 10, waarvan de som 1 is.
3. Achteruitgang
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
De eerste 2 lijnen initialiseren de verlopen. Deze gradiënten worden berekend en zullen later worden gebruikt om de gewichten en vooroordelen bij te werken.
De volgende 3 regels berekenen de fout door de voorspelling af te trekken van het doel. De fout wordt vervolgens terug verspreid naar de binnenste lagen.
Volg nu voorzichtig de werking van de lus. Lijnen 2 en 3 transformeren de fout van layer[i]
naar layer[i - 1]
. Volg de vormen van de matrices die worden vermenigvuldigd om te begrijpen.
4. Update gewichten / parameters
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
geeft de snelheid aan waarmee het netwerk leert. Je wilt niet dat het te snel leert, omdat het misschien niet convergeert. Een soepele afdaling wordt aanbevolen voor het vinden van een goede minima. Gewoonlijk worden snelheden tussen 0.01
en 0.1
als goed beschouwd.
Activeringsfuncties
Activeringsfuncties, ook wel overdrachtsfunctie genoemd, worden gebruikt om ingangsknopen op een bepaalde manier aan uitgangsknopen toe te wijzen.
Ze worden gebruikt om niet-lineariteit te verlenen aan de output van een neurale netwerklaag.
Enkele veelgebruikte functies en hun curven worden hieronder gegeven:
Sigmoïde functie
De sigmoïde is een squashfunctie waarvan de uitvoer in het bereik [0, 1]
.
De code voor het implementeren van sigmoïde samen met de afgeleide met numpy
wordt hieronder weergegeven:
def sigmoid(z):
return 1.0 / (1.0 + np.exp(-z))
def sigmoid_prime(z):
return sigmoid(z) * (1 - sigmoid(z))
Hyperbolische tangensfunctie (tanh)
Het basisverschil tussen de functies tanh en sigmoïde is dat tanh 0 gecentreerd is, ingangen platdrukken in het bereik [-1, 1] en efficiënter is om te berekenen.
U kunt eenvoudig de functies np.tanh
of math.tanh
gebruiken om de activering van een verborgen laag te berekenen.
ReLU-functie
Een gerectificeerde lineaire eenheid doet eenvoudig max(0,x)
. Het is een van de meest voorkomende keuzes voor activeringsfuncties van neurale netwerkeenheden.
ReLU's pakken het verdwijnende gradiëntprobleem van sigmoïde / hyperbolische tangenseenheden aan, waardoor efficiënte gradiëntvoortplanting in diepe netwerken mogelijk wordt gemaakt.
De naam ReLU komt van het papier van Nair en Hinton, Rectified Linear Units Improve Restricted Boltzmann Machines .
Het heeft enkele variaties, bijvoorbeeld lekkende ReLU's (LReLU's) en Exponential Linear Units (ELU's).
De code voor het implementeren van vanille ReLU samen met de afgeleide met numpy
wordt hieronder weergegeven:
def relU(z):
return z * (z > 0)
def relU_prime(z):
return z > 0
Softmax-functie
Softmax-regressie (of multinomiale logistieke regressie) is een generalisatie van logistieke regressie naar het geval waarin we meerdere klassen willen afhandelen. Het is vooral handig voor neurale netwerken waar we niet-binaire classificatie willen toepassen. In dit geval is eenvoudige logistieke regressie niet voldoende. We zouden een waarschijnlijkheidsverdeling over alle labels nodig hebben, wat softmax ons geeft.
Softmax wordt berekend met de onderstaande formule:
___________________________ Waar past het in? _____________________________
Om een vector te normaliseren door de softmax-functie erop toe te passen met numpy
, gebruikt u:
np.exp(x) / np.sum(np.exp(x))
Waarbij x
de activering van de laatste laag van de ANN is.