Автор оригинала: Aditya Dehal.
Нейронные сети похожи на рабочие аптечки глубокого обучения. С достаточным количеством данных и вычислительной мощностью их можно использовать для решения большинства проблем в глубоком обучении. Очень легко использовать библиотеку Python или R, чтобы создать нейронную сеть и обучить ее на наборе данных и получить большую точность.
Мы можем относиться к нейронным сетям как лишь черный ящик и использовать их без каких-либо сложных. Но даже если это кажется очень легко поступить, гораздо интереснее узнать, что лежит за этими алгоритмами и как они работают.
В этой статье мы попадем в некоторые детали построения нейронной сети. Я собираюсь использовать Python, чтобы написать код для сети. Я также буду использовать Numpy библиотеку Python для выполнения численных вычислений. Я постараюсь избежать сложных математических деталей, но я буду ссылаться на некоторые блестящие ресурсы в конце, если вы хотите узнать больше об этом.
Так что давайте начнем.
Идея
Прежде чем мы начнем написание кода для нашей нейронной сети, давайте просто подождим и понимаем, что именно является нейронной сетью.
На изображении выше вы можете увидеть очень случайную диаграмму нейронной сети. Он имеет некоторые цветные круги, связанные друг с другом со стрелками, указывающими на определенное направление. Эти цветные круги иногда называют Нейроны Отказ
Эти Нейроны Ничего, кроме математических функций, которые, когда дали некоторые вход, генерировать Выход Отказ Выход Нейроны зависит от вход и Параметры из Нейроны Отказ Мы можем обновить эти Параметры получить желаемое значение из сети.
Каждый из этих Нейроны определяются с использованием Сигмовидная функция Отказ А Сигмовидная функция Дает выход между нулем до одного для каждого ввода, который он получает. Эти сигмоидные блоки связаны друг с другом, чтобы сформировать нейронную сеть.
При подключении здесь мы имеем в виду, что вывод одного слоя сигмоидных блоков приведен в качестве входных данных для каждого сигмовидной единицы следующего слоя. Таким образом, наша нейронная сеть производит вывод для любого данного ввода. Процесс продолжается до тех пор, пока мы не достигнем конечного слоя. Последний слой генерирует свой вывод.
Этот процесс нейронной сети, генерируя Выход Для данного вход это Распространение вперед Отказ Выход конечного слоя также называется прогноз нейронной сети. Позже в этой статье мы обсудим, как мы Оцените прогнозы Отказ Эти оценки могут быть использованы, чтобы рассказать, нуждается ли наша нейронная сеть улучшения или нет.
Сразу после того, как последний слой генерирует свой выход, мы рассчитываем Стоимость функции Отказ Функция затрат вычисляет, как далеко наша нейронная сеть от выполнения желаемых предсказаний. Значение функции затрат показывает разницу между Прогнозируемая ценность и ценность правды Отказ
Наша цель здесь – минимизировать ценность Стоимость функции Отказ Процесс минимизации затрат требует алгоритма, который может обновить значения Параметры В сети таким образом, что функция стоимости достигает своей Минимальное значение Отказ
Алгоритмы, такие как Градиентный спуск и Стохастический градиентный спуск используются для обновления Параметры нейронной сети. Эти алгоритмы обновляют значения весов и предубеждений каждого слоя в сети в зависимости от того, как она повлияет на минимизацию затрат. Эффект на минимизацию стоимости функции в отношении каждого из весов и смещений каждого из входных нейронов в сети вычисляется обратная обработка Отказ
Код
Итак, теперь мы знаем основные идеи нейронных сетей. Давайте начнем реализовывать эти идеи в код. Мы начнем, импортируя все необходимые библиотеки.
import numpy as np import matplotlib.pyplot as plt
Как я уже упоминал, мы не собираемся использовать какие-либо из библиотек глубокого обучения. Итак, мы в основном будем эффективно использовать Numpy для выполнения математических вычислений.
Первый шаг в построении нашей нейронной сети будет инициализировать параметры. Нам нужно инициализировать два параметра для каждого из нейронов в каждом слое: 1) Вес и 2) Предвзятость Отказ
Эти веса и предубеждения объявлены в Вектор форма. Это означает, что вместо того, чтобы инициализация весов и предубеждений для каждого отдельного нейрона в каждом слое мы создадим вектор (или матрицу) для весов и другой для предубеждений, для каждого слоя.
Эти Вес и предвзятость Векторы будут объединены с входом к слою. Затем мы применим функцию Sigmoid по этому комбинации и отправьте это в качестве входа на следующий слой.
layer_dims Удерживает размеры каждого слоя. Мы передадим эти размеры слоев к init_parms Функция, которая будет использовать их для инициализации параметров. Эти параметры будут храниться в словаре под названием Пармы Отказ Так что в словаре параметров Пармы [‘w1’] будет представлять матрицу веса для слоя 1.
def init_params(layer_dims): np.random.seed(3) params = {} L = len(layer_dims) for l in range(1, L): params['W'+str(l)] = np.random.randn(layer_dims[l], layer_dims[l-1])*0.01 params['b'+str(l)] = np.zeros((layer_dims[l], 1)) return params
Большой! Мы инициализировали веса и предубеждения, и теперь мы определим Сигмовидная функция Отказ Он будет вычислять значение сигмовидной функции для любого заданного значения Z И также будет хранить это значение как кэш. Мы будем хранить значения кэширования, потому что мы нуждаемся в них для реализации обратной обработки. Z Вот Линейная гипотеза Отказ
Обратите внимание, что сигмовидная функция падает под класс Функции активации в терминологии нейронной сети. Работа Функция активации состоит в том, чтобы сформировать вывод нейрона.
Например, сигмовидная функция принимает вход с дискретных значений и дает значение, которое лежит между нулем и одному. Его цель состоит в том, чтобы преобразовать линейные выходы на нелинейные выходы. Есть разные виды Функции активации Это можно использовать для лучшей производительности, но мы будем придерживаться сигмоида ради простоты.
# Z (linear hypothesis) - Z = W*X + b , # W - weight matrix, b- bias vector, X- Input def sigmoid(Z): A = 1/(1+np.exp(np.dot(-1, Z))) cache = (Z) return A, cache
Теперь давайте начнем написать код для пропаганды вперед. Мы обсудили ранее, что вперед Распространение возьму значения из предыдущего слоя и дают его в качестве ввода на следующий слой. Функция ниже займет Обучение данных и Параметры в качестве входных данных и будет генерировать вывод для одного слоя, а затем он подарит, что вывод на следующий слой и так далее.
def forward_prop(X, params): A = X # input to first layer i.e. training data caches = [] L = len(params)//2 for l in range(1, L+1): A_prev = A # Linear Hypothesis Z = np.dot(params['W'+str(l)], A_prev) + params['b'+str(l)] # Storing the linear cache linear_cache = (A_prev, params['W'+str(l)], params['b'+str(l)]) # Applying sigmoid on linear hypothesis A, activation_cache = sigmoid(Z) # storing the both linear and activation cache cache = (linear_cache, activation_cache) caches.append(cache) return A, caches
A_PREV Я S ввод на первый слой. Мы будем петить все слои сети и выделим линейную гипотезу. После этого это примет ценность Z (Линейная гипотеза) и приведет его к функции активации сигмоиды. Значения кэша хранятся по пути и накапливаются в кэширует Отказ Наконец, функция вернет генерируемый значение и сохраненный кэш.
Давайте теперь определим нашу функцию стоимости.
def cost_function(A, Y): m = Y.shape[1] cost = (-1/m)*(np.dot(np.log(A), Y.T) + np.dot(log(1-A), 1-Y.T)) return cost
По мере того, как стоимость затратного функции уменьшается, производительность нашей модели становится лучше. Значение функции затрат может быть минимизировано путем обновления значений параметров каждого из слоев в нейронной сети. Алгоритмы, такие как Градиентный спуск Используются для обновления этих значений таким образом, чтобы функция стоимости минимизирована.
Спуск градиента обновляет значения с помощью некоторых терминов обновления. Эти обновления условий называются Градиенты рассчитываются с использованием обратной передачи. Значения градиента рассчитываются для каждого нейрона в сети, и она представляет собой изменение окончательного выхода в отношении изменения параметров этого конкретного нейрона.
def one_layer_backward(dA, cache): linear_cache, activation_cache = cache Z = activation_cache dZ = dA*sigmoid(Z)*(1-sigmoid(Z)) # The derivative of the sigmoid function A_prev, W, b = linear_cache m = A_prev.shape[1] dW = (1/m)*np.dot(dZ, A_prev.T) db = (1/m)*np.sum(dZ, axis=1, keepdims=True) dA_prev = np.dot(W.T, dZ) return dA_prev, dW, db
Код выше запускает шаг обратной обработки для одного одного слоя. Он рассчитывает значения градиента для сигмоидных единиц одного слоя с использованием значений кэша, которую мы сохранили ранее. В кэше активации мы сохранили значение Z для этого слоя. Используя это значение, мы рассчитаем DZ , который является производным функции затрат относительно линейного выхода данного нейрона.
Как только мы рассчитали все это, мы можем рассчитать DW , дБ и DA_PREV, которые являются производными затратных функций с уважением весов, предвзятости и предыдущей активации соответственно. Я напрямую использовал формулы в коде. Если вы не знакомы с исчислением, то это может показаться слишком сложным. Но пока подумайте об этом как любую другую формулу математики.
После этого мы будем использовать этот код для реализации обратной обработки для всей нейронной сети. Функция Backprop реализует код для этого. Здесь мы создали словарь для сопоставления градиентов каждому слою. Мы будем петить через модель в обратном направлении и вычислить градиент.
def backprop(AL, Y, caches): grads = {} L = len(caches) m = AL.shape[1] Y = Y.reshape(AL.shape) dAL = -(np.divide(Y, AL) - np.divide(1-Y, 1-AL)) current_cache = caches[L-1] grads['dA'+str(L-1)], grads['dW'+str(L-1)], grads['db'+str(L-1)] = one_layer_backward(dAL, current_cache) for l in reversed(range(L-1)): current_cache = caches[l] dA_prev_temp, dW_temp, db_temp = one_layer_backward(grads["dA" + str(l+1)], current_cache) grads["dA" + str(l)] = dA_prev_temp grads["dW" + str(l + 1)] = dW_temp grads["db" + str(l + 1)] = db_temp return grads
Однажды мы процитировали все слои и вычислили градиенты, мы будем хранить эти значения в Компания Словарь и вернуть его.
Наконец, используя эти значения градиента, мы обновим параметры для каждого слоя. Функция update_parameters проходит все слои и обновляет параметры и возвращает их.
def update_parameters(parameters, grads, learning_rate): L = len(parameters) // 2 for l in range(L): parameters['W'+str(l+1)] = parameters['W'+str(l+1)] -learning_rate*grads['W'+str(l+1)] parameters['b'+str(l+1)] = parameters['b'+str(l+1)] - learning_rate*grads['b'+str(l+1)] return parameters
Наконец, пришло время поставить все это вместе. Мы создадим функцию под названием Поезд Для обучения нашей нейронной сети.
def train(X, Y, layer_dims, epochs, lr): params = init_params(layer_dims) cost_history = [] for i in range(epochs): Y_hat, caches = forward_prop(X, params) cost = cost_function(Y_hat, Y) cost_history.append(cost) grads = backprop(Y_hat, Y, caches) params = update_parameters(params, grads, lr) return params, cost_history
Эта функция пройдет через все функции шаг за счет заданного количества эпохи Отказ После завершения того, это вернет окончательные обновленные параметры и история стоимости. История стоимости может быть использована для оценки производительности вашей сетевой архитектуры.
Заключение
Если вы все еще читаете это, спасибо! Эта статья была немного сложной, так что я предлагаю вам сделать, это попробовать играть с кодом. Вы можете получить еще несколько представлений из этого, и, возможно, вы можете найти некоторые ошибки в коде тоже. Если это так или если у вас есть вопросы или оба, не стесняйтесь ударить меня на Twitter Отказ Я сделаю все возможное, чтобы помочь вам.
Ресурсы
- Плейлист нейронных сетей – на 3Blue1Brown
- Нейронные сети и глубокое обучение – Майклом А. Нильсен
- Градиентный спуск и стохастический градиентный спуск