Поиск…


Начало работы: простой ANN с Python

В приведенном ниже списке кода делается попытка классифицировать рукописные цифры из набора данных MNIST. Цифры выглядят так:

MNIST

Код будет предварительно обрабатывать эти цифры, преобразовывая каждое изображение в 2D-массив из 0 и 1, а затем использовать эти данные для обучения нейронной сети с точностью до 97% (50 эпох).

"""
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)) + "%"

Это самодостаточный образец кода и может запускаться без каких-либо дополнительных изменений. Убедитесь, что у вас установлена scikit numpy и scikit для вашей версии python.

Backpropagation - Сердце нейронных сетей

Целью backpropagation является оптимизация весов, чтобы нейронная сеть могла научиться правильно отображать произвольные входные данные для выходов.

Каждый слой имеет свой собственный набор весов, и эти веса должны быть настроены так, чтобы быть в состоянии точно предсказать правильный выход, данный вход.

Высокий уровень обзора обратного распространения выглядит следующим образом:

  1. Forward pass - вход преобразуется в некоторый вывод. На каждом уровне активация вычисляется с помощью точечного продукта между входным и весовым коэффициентами, а затем суммирует результат с смещением. Наконец, это значение передается через функцию активации, чтобы получить активацию этого слоя, который станет входом на следующий уровень.
  2. В последнем слое выход сравнивается с фактической меткой, соответствующей этому входу, и вычисляется ошибка. Обычно это средняя квадратичная ошибка.
  3. Обратный проход - ошибка, вычисленная на шаге 2, распространяется обратно во внутренние слои, и веса всех слоев корректируются для учета этой ошибки.

1. Инициализация веса

Ниже приведен упрощенный пример инициализации весов:

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:]])
  • Скрытый слой 1 имеет вес измерения [64, 784] и смещение размера 64.

  • Выходной слой имеет вес измерения [10, 64] и смещение размеров

Возможно, вам интересно, что происходит при инициализации весов в приведенном выше коде. Это называется инициализацией Xavier, и это шаг лучше, чем случайная инициализация весовых матриц. Да, инициализация имеет значение. Основываясь на вашей инициализации, вы можете найти лучшие локальные минимумы во время градиентного спуска (обратное распространение - это прославленная версия градиентного спуска).

2. Передовой перевал

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))

Этот код выполняет описанное выше преобразование. hidden_activations[-1] содержит вероятности softmax - предсказания всех классов, сумма которых равна 1. Если мы предсказываем цифры, то вывод будет вектором вероятностей размерности 10, сумма которого равна 1.

3. Обратный проход

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

Первые 2 строки инициализируют градиенты. Эти градиенты вычисляются и будут использоваться для обновления весов и смещений позже.

Следующие 3 строки вычисляют ошибку, вычитая предсказание из цели. Затем ошибка возвращается во внутренние слои.

Теперь внимательно проследите за работой цикла. Строки 2 и 3 преобразуют ошибку из layer[i] в layer[i - 1] . Проследите, чтобы формы матриц умножались для понимания.

4. Массы / Обновление параметров

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 указывает скорость, с которой узнает сеть. Вы не хотите, чтобы он учился слишком быстро, потому что он не может сходиться. Гладкий спуск благоприятствует поиску хороших минимумов. Обычно ставки между 0.01 и 0.1 считаются хорошими.

Функции активации

Функции активации, также известные как передаточная функция, используются для сопоставления входных узлов с выходными узлами определенным образом.

Они используются для придания нелинейности выходному уровню нейронной сети.

Ниже приведены некоторые часто используемые функции и их кривые: Функции активации


Сигмоидная функция

Сигмоид - это функция сжатия, выход которой находится в диапазоне [0, 1] .

введите описание изображения здесь

Код для реализации сигмоида вместе с его производной с numpy показан ниже:

def sigmoid(z):
    return 1.0 / (1.0 + np.exp(-z))

def sigmoid_prime(z):
    return sigmoid(z) * (1 - sigmoid(z))

Гиперболическая касательная функция (tanh)

Основное различие между функциями tanh и sigmoid состоит в том, что tanh имеет 0 центрированных, подавляет входы в диапазон [-1, 1] и более эффективен для вычисления.

введите описание изображения здесь

Вы можете легко использовать функции np.tanh или math.tanh для вычисления активации скрытого слоя.

Функция ReLU

Выпрямленная линейная единица просто max(0,x) . Это один из наиболее распространенных вариантов активации функций нейронных сетей.

введите описание изображения здесь

ReLUs обращаются к проблеме исчезающего градиента сигмоидных / гиперболических тангенциальных единиц, что позволяет эффективно распространять градиент в глубоких сетях.

Название ReLU происходит от бумаги Nair и Hinton, Rectified Linear Units улучшает ограниченные машины Boltzmann .

Он имеет некоторые варианты, например, протекающие ReLU (LReLU) и экспоненциальные линейные единицы (ELU).

Код для воплощения ванильного ReLU вместе с его производной с numpy показан ниже:

def relU(z):
    return z * (z > 0)

def relU_prime(z):
    return z > 0

Функция Softmax

Регрессия Softmax (или многолинейная логистическая регрессия) является обобщением логистической регрессии в случае, когда мы хотим обрабатывать несколько классов. Это особенно полезно для нейронных сетей, где мы хотим применять недвоичную классификацию. В этом случае простой логистической регрессии недостаточно. Нам нужно распределение вероятности по всем меткам, что дает нам softmax.

Softmax вычисляется по следующей формуле:

формула

___________________________ Где он вписывается? _____________________________

Софтмакс Чтобы нормализовать вектор, применив к нему функцию softmax с помощью numpy , используйте:

np.exp(x) / np.sum(np.exp(x))

Где x - активация из конечного слоя ANN.



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow