machine-learning
Neurala nätverk
Sök…
Komma igång: En enkel ANN med Python
Kodlistan nedan försöker klassificera handskrivna siffror från MNIST-datasättet. Siffrorna ser så här ut:
Koden förbehandlar dessa siffror, konverterar varje bild till en 2D-grupp med 0s och 1s, och använder sedan dessa data för att träna ett neuralt nätverk med upp till 97% noggrannhet (50 epokar).
"""
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)) + "%"
Detta är ett fristående kodeksempel och kan köras utan ytterligare ändringar. Se till att du har numpy
och scikit
installerat för din version av python.
Backpropagation - Hjärtat av neurala nätverk
Målet med bakpropagering är att optimera vikterna så att det neurala nätverket kan lära sig hur man korrekt kartlägger godtyckliga ingångar till utgångar.
Varje lager har sin egen uppsättning vikter, och dessa vikter måste vara inställda för att kunna exakt förutsäga rätt utgång som ges ingång.
En översikt på hög nivå av ryggförökning är som följer:
- Framåtpass - ingången omvandlas till viss utgång. Vid varje lager beräknas aktiveringen med en punktprodukt mellan ingången och vikterna, följt av summeringen av den resulterande med förspänningen. Slutligen överförs detta värde genom en aktiveringsfunktion för att få aktiveringen av det lagret som kommer att bli ingången till nästa lager.
- I det sista lagret jämförs utgången med den faktiska etiketten som motsvarar den ingången, och felet beräknas. Vanligtvis är det det genomsnittliga kvadratfelet.
- Bakåtpassering - felet som beräknas i steg 2 förökas tillbaka till de inre skikten, och vikten på alla lager justeras för att ta hänsyn till detta fel.
1. Initiering av vikter
Ett förenklat exempel på viktinitialisering visas nedan:
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:]])
Dold skikt 1 har dimensionen [64, 784] och förspänningen i dimension 64.
Utmatningsskiktet har dimensionen [10, 64] och dimensionen är förspänd
Du kanske undrar vad som händer när du initierar vikter i koden ovan. Detta kallas Xavier initialisering, och det är ett steg bättre än att slumpmässigt initialisera dina viktmatriser. Ja, initialisering spelar ingen roll. Baserat på din initialisering kan du kanske hitta en bättre lokal minima under lutningens nedgång (tillbakautbredning är en förhärlig version av lutningens nedstigning).
2. Framåtpass
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))
Denna kod utför den omvandling som beskrivs ovan. hidden_activations[-1]
innehåller softmax-sannolikheter - förutsägelser för alla klasser, vars summa är 1. Om vi förutsäger siffror, kommer output att vara en vektor med sannolikheter för dimension 10, vars summa är 1.
3. Bakåtpassering
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 första två linjerna initialiserar lutningarna. Dessa lutningar beräknas och kommer att användas för att uppdatera vikterna och förspänningarna senare.
De nästa tre raderna beräknar felet genom att subtrahera förutsägelsen från målet. Felet sprids sedan tillbaka till de inre skikten.
Spår nu noggrant arbetet med slingan. Linjer 2 och 3 omvandlar felet från layer[i]
till layer[i - 1]
. Spåra formerna på matriserna som multipliceras för att förstå.
4. Uppdatering av vikter / parametrar
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
anger hastigheten som nätverket lär sig. Du vill inte att det ska lära sig för snabbt, eftersom det kanske inte konvergerar. En jämn härkomst föredras för att hitta en bra minima. Vanligtvis betraktas priser mellan 0.01
och 0.1
som bra.
Aktiveringsfunktioner
Aktiveringsfunktioner, även kända som överföringsfunktion, används för att kartlägga ingångsnoder för att mata ut noder på visst sätt.
De används för att förmedla icke-linearitet till utgången från ett neuralt nätverkslager.
Några vanliga funktioner och deras kurvor ges nedan:
Sigmoid-funktion
Sigmoid är en squashfunktion vars utgång ligger i intervallet [0, 1]
.
Koden för implementering av sigmoid tillsammans med dess derivat med numpy
visas nedan:
def sigmoid(z):
return 1.0 / (1.0 + np.exp(-z))
def sigmoid_prime(z):
return sigmoid(z) * (1 - sigmoid(z))
Hyperbolisk tangentfunktion (tanh)
Den grundläggande skillnaden mellan tanh- och sigmoidfunktionerna är att tanh är 0 centrerad, kretsar ingångar i intervallet [-1, 1] och är mer effektiv att beräkna.
Du kan enkelt använda np.tanh
eller math.tanh
att beräkna aktiveringen av ett doldt lager.
ReLU-funktion
En likriktad linjär enhet gör helt enkelt max(0,x)
. Det är ett av de vanligaste valen för aktiveringsfunktioner för nervnätverk.
ReLU: er tar upp det försvinnande lutningsproblemet för sigmoid / hyperboliska tangentenheter, vilket möjliggör effektiv gradientutbredning i djupa nätverk.
Namnet ReLU kommer från Nair och Hintons papper, Rectified Linear Units Improve Restricted Boltzmann Machines .
Det har vissa variationer, till exempel läckande ReLU: er (LReLU: er) och exponentiella linjära enheter (ELU: er).
Koden för att implementera vanilj ReLU tillsammans med dess derivat med numpy
visas nedan:
def relU(z):
return z * (z > 0)
def relU_prime(z):
return z > 0
Softmax-funktion
Softmax-regression (eller multinomial logistisk regression) är en generalisering av logistisk regression till det fall där vi vill hantera flera klasser. Det är särskilt användbart för nervnätverk där vi vill tillämpa icke-binär klassificering. I detta fall är enkel logistisk regression inte tillräcklig. Vi behöver en sannolikhetsfördelning över alla etiketter, vilket är vad softmax ger oss.
Softmax beräknas med följande formel:
___________________________ Var passar det in? _____________________________
För att normalisera en vektor genom att använda softmax-funktionen på den med numpy
, använd:
np.exp(x) / np.sum(np.exp(x))
Där x
är aktiveringen från det sista lagret i ANN.