machine-learning
Sieci neuronowe
Szukaj…
Pierwsze kroki: prosty ANN za pomocą Pythona
Poniższy kod zawiera próbę sklasyfikowania odręcznych cyfr z zestawu danych MNIST. Cyfry wyglądają tak:
Kod wstępnie przetworzy te cyfry, konwertując każdy obraz na tablicę 2D 0 i 1 s, a następnie wykorzysta te dane do trenowania sieci neuronowej z dokładnością do 97% (50 epok).
"""
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)) + "%"
Jest to samodzielny przykładowy kod, który można uruchomić bez żadnych dalszych modyfikacji. Upewnij się, że masz numpy
i scikit
learn dla twojej wersji Pythona.
Propagacja wsteczna - serce sieci neuronowych
Celem propagacji wstecznej jest optymalizacja wag, aby sieć neuronowa mogła nauczyć się, jak poprawnie mapować dowolne dane wejściowe na dane wyjściowe.
Każda warstwa ma swój własny zestaw wag, które muszą być dostrojone, aby móc dokładnie przewidzieć prawidłowy wynik na podstawie danych wejściowych.
Ogólny przegląd propagacji wstecznej jest następujący:
- Forward pass - wejście jest przekształcane w jakieś wyjście. Na każdej warstwie aktywacja jest obliczana z iloczynem iloczynu między wejściem a wagami, a następnie zsumowana wynikowa z odchyleniem. Na koniec ta wartość jest przekazywana przez funkcję aktywacji, aby uzyskać aktywację tej warstwy, która stanie się wejściem do następnej warstwy.
- W ostatniej warstwie dane wyjściowe są porównywane z rzeczywistą etykietą odpowiadającą temu wejściu, a błąd jest obliczany. Zwykle jest to średni błąd kwadratu.
- Przekazywanie wstecz - błąd obliczony w kroku 2 jest przenoszony z powrotem do warstw wewnętrznych, a wagi wszystkich warstw są dostosowywane w celu uwzględnienia tego błędu.
1. Inicjalizacja wag
Uproszczony przykład inicjalizacji wag pokazano poniżej:
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:]])
Ukryta warstwa 1 ma masę wymiaru [64, 784] i stronniczość wymiaru 64.
Warstwa wyjściowa ma wagę wymiaru [10, 64] i odchylenie wymiaru
Być może zastanawiasz się, co się dzieje podczas inicjowania wag w powyższym kodzie. Nazywa się to inicjalizacją Xaviera i jest to krok lepszy niż przypadkowa inicjalizacja macierzy masy. Tak, inicjalizacja ma znaczenie. W oparciu o twoją inicjalizację możesz znaleźć lepsze lokalne minima podczas opadania gradientu (propagacja wsteczna to uwielbiona wersja spadku gradientu).
2. Przekaż do przodu
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))
Ten kod dokonuje transformacji opisanej powyżej. hidden_activations[-1]
zawiera softmax prawdopodobieństwa - przewidywania wszystkich klas, których suma wynosi 1. Jeśli przewidujemy cyfry, wówczas wyjście będzie wektorem prawdopodobieństw o wymiarze 10, którego suma wynosi 1.
3. Przepustka wsteczna
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
Pierwsze 2 linie inicjują gradienty. Te gradienty są obliczane i zostaną później wykorzystane do aktualizacji wag i odchyleń.
Następne 3 wiersze obliczają błąd, odejmując prognozę od celu. Błąd jest następnie propagowany z powrotem do warstw wewnętrznych.
Teraz dokładnie prześledzić działanie pętli. Linie 2 i 3 przekształcają błąd z layer[i]
na layer[i - 1]
. Śledź kształty mnożonych macierzy, aby zrozumieć.
4. Aktualizacja wag / parametrów
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
określa szybkość, z jaką sieć się uczy. Nie chcesz, aby uczył się zbyt szybko, ponieważ może się nie zbiegać. Płynne zejście jest zalecane do znalezienia dobrych minimów. Zwykle wartości od 0.01
do 0.1
są uważane za dobre.
Funkcje aktywacji
Funkcje aktywacji znane również jako funkcja przesyłania są używane do mapowania węzłów wejściowych do węzłów wyjściowych w określony sposób.
Służą do nadawania nieliniowości wyjściu warstwy sieci neuronowej.
Niektóre często używane funkcje i ich krzywe podano poniżej:
Funkcja sigmoidalna
Sigmoid jest funkcją zgniatania, której moc wyjściowa mieści się w zakresie [0, 1]
.
Kod implementujący sigmoid wraz z jego pochodną z numpy
pokazano poniżej:
def sigmoid(z):
return 1.0 / (1.0 + np.exp(-z))
def sigmoid_prime(z):
return sigmoid(z) * (1 - sigmoid(z))
Funkcja stycznej hiperbolicznej (tanh)
Podstawowa różnica między funkcjami tanh i sigmoid polega na tym, że tanh jest wyśrodkowany na 0, zgniatając dane wejściowe w zakresie [-1, 1] i jest bardziej wydajny w obliczeniach.
Możesz łatwo użyć funkcji np.tanh
lub math.tanh
aby obliczyć aktywację ukrytej warstwy.
Funkcja ReLU
Zrektyfikowana jednostka liniowa robi po prostu max(0,x)
. Jest to jeden z najczęstszych wyborów funkcji aktywacyjnych jednostek sieci neuronowej.
ReLU rozwiązują problem znikającego gradientu jednostek stycznych sigmoidalnych / hiperbolicznych, umożliwiając w ten sposób efektywną propagację gradientu w głębokich sieciach.
Nazwa ReLU pochodzi od artykułu Naira i Hintona „ Rectified Linear Units Improve Restricted Machines Boltzmann” .
Ma pewne odmiany, na przykład nieszczelne ReLU (LReLU) i wykładnicze jednostki liniowe (ELU).
Kod implementujący waniliową ReLU wraz z jego pochodną z numpy
pokazano poniżej:
def relU(z):
return z * (z > 0)
def relU_prime(z):
return z > 0
Funkcja Softmax
Regresja Softmax (lub wielomianowa regresja logistyczna) jest uogólnieniem regresji logistycznej w przypadku, gdy chcemy obsłużyć wiele klas. Jest to szczególnie przydatne w sieciach neuronowych, w których chcemy zastosować klasyfikację niebinarną. W takim przypadku prosta regresja logistyczna nie jest wystarczająca. Potrzebowalibyśmy rozkładu prawdopodobieństwa dla wszystkich etykiet, co daje nam softmax.
Softmax jest obliczany według poniższego wzoru:
___________________________Gdzie to pasuje? _____________________________
Aby znormalizować wektor poprzez zastosowanie do niego funkcji softmax za pomocą numpy
, użyj:
np.exp(x) / np.sum(np.exp(x))
Gdzie x
to aktywacja z ostatniej warstwy ANN.