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:

MNIST

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:

  1. 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.
  2. 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.
  3. 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: Funkcje aktywacji


Funkcja sigmoidalna

Sigmoid jest funkcją zgniatania, której moc wyjściowa mieści się w zakresie [0, 1] .

wprowadź opis zdjęcia tutaj

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.

wprowadź opis zdjęcia tutaj

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.

wprowadź opis zdjęcia tutaj

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:

Formuła

___________________________Gdzie to pasuje? _____________________________

Softmax 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.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow