machine-learning
Нейронные сети
Поиск…
Начало работы: простой ANN с Python
В приведенном ниже списке кода делается попытка классифицировать рукописные цифры из набора данных 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 является оптимизация весов, чтобы нейронная сеть могла научиться правильно отображать произвольные входные данные для выходов.
Каждый слой имеет свой собственный набор весов, и эти веса должны быть настроены так, чтобы быть в состоянии точно предсказать правильный выход, данный вход.
Высокий уровень обзора обратного распространения выглядит следующим образом:
- Forward pass - вход преобразуется в некоторый вывод. На каждом уровне активация вычисляется с помощью точечного продукта между входным и весовым коэффициентами, а затем суммирует результат с смещением. Наконец, это значение передается через функцию активации, чтобы получить активацию этого слоя, который станет входом на следующий уровень.
- В последнем слое выход сравнивается с фактической меткой, соответствующей этому входу, и вычисляется ошибка. Обычно это средняя квадратичная ошибка.
- Обратный проход - ошибка, вычисленная на шаге 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.