Этот пост является продолжением обучающей серии Learn Coding Neural Network in C#. Если вы не знаете, с чего начать, пожалуйста, сначала прочитайте этот пост.

В этом посте мы реализуем обратное распространение для вычисления градиентов параметров слоя, чтобы их можно было оптимизировать с помощью функции оптимизатора. В примерах до сих пор мы использовали 2 полносвязных слоя, переданных функции стоимости для расчета разницы потерь. Когда эти слои и функция стоимости были вызваны, мы перенаправили данные X между этими слоями и, наконец, получили прогноз и значение стоимости. Теперь, когда нам нужно найти градиенты, расчет будет двигаться назад от стоимости ко всем слоям (последний -> первый).

Что такое обратное распространение?

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

Нахождение градиента — это, по сути, нахождение производной функции. Однако в нашем случае, поскольку есть много независимых переменных, которые мы можем настроить (все веса и смещения), мы должны найти производные по каждой переменной. Это известно как частная производная с символом ∂.

Частные производные:

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

Рассмотрим частную производную по x (т. е. как y изменяется при изменении x) в функции f(x,y) = 3x²y. Рассматривая y как константу, мы можем найти часть x:

Точно так же мы можем найти часть y:

Градиент функции f(x,y) = 3x²y представляет собой горизонтальный вектор, состоящий из двух частей:

Есть много постов, в которых подробно объясняется обратное распространение, я покажу вам, как это реализовано в нашем существующем проекте «NeuroSimple».

Откройте проект и перейдите в класс «BaseLayer». Нам нужно добавить новые свойства для хранения рассчитанных градиентов. Ниже приведены дополнительные свойства BaseLayer.

        /// <summary>
        /// Gradient of the Input
        /// </summary>
        public NDArray InputGrad { get; set; }

        /// <summary>
        /// List of all parameters gradients calculated during back propagation.
        /// </summary>
        public Dictionary<string, NDArray> Grads { get; set; }

Добавьте еще один виртуальный метод Назад, который будет реализован отдельным слоем и классом активации. Я добавил вспомогательную функцию PrintParam для визуализации параметров. Параметр град в обратной функции предназначен для получения производной внутренней функции и использования ее для нахождения производной внешней функции. Поскольку полная архитектура нейронной сети вместе с функцией потерь будет одной составной функцией, мы применим цепное правило, чтобы найти производную для отдельного слоя. Подробнее о цепном правиле здесь

        /// <summary>
        /// Calculate the gradient of the layer. Usually a prtial derivative implemenation of the forward algorithm
        /// </summary>
        /// <param name="grad"></param>
        public virtual void Backward(NDArray grad)
        {
            
        }

        public void PrintParams(bool printGrads = true)
        {
            foreach (var item in Parameters)
            {
                item.Value.Print(string.Format("Parameter: {0}", item.Key));
                if(printGrads && Grads.ContainsKey(item.Key))
                {
                    Grads[item.Key].Print(string.Format("Grad: {0}", item.Key));
                }
            }
        }

Обратное распространение для полносвязного слоя

Во время прямого прохода слой FC принимает входные данные X формы N x D и весовую матрицу формы D x M, вычисляя выходные данные, выполняя скалярное произведение Y = X * W. Конечный результат имеет форму N x M. После прямого прохода мы предполагаем, что выходные данные будут использоваться в других частях сети и в конечном итоге будут использованы для вычисления скалярных потерь L. Во время обратного прохода через слой FC мы предполагаем, что производная ∂L /∂Y уже вычислено, что является выводом функции стоимости.

Производная веса по производной внутренней функции, которая в данном случае является потерей, равна

И производная входа X по Loss будет

Давайте реализуем обратную функцию для слоя FC, реализуя следующий код

        /// <summary>
        /// Calculate the gradient of the layer. Usually a prtial derivative implemenation of the forward algorithm
        /// </summary>
        /// <param name="grad"></param>
        public override void Backward(NDArray grad)
        {
            //Activation was invoked in Forward after calculating output. 
            //In backpropagation we need to reverse the flow, so Activation Backward will be invoked first then the layer gradient code.
            if(Activation != null)
            {
                Activation.Backward(grad);
                grad = Activation.InputGrad;
            }

            InputGrad = Dot(grad, Parameters["w"].Transpose());
            Grads["w"] = Dot(Input.Transpose(), grad);
        }

Обратное распространение для функций активации

Мы начнем с реализации обратной функции функций активации, которая представляет собой ReLU и Sigmoid.

Выпрямленная линейная единица (ReLU) определяется как f(x)=max(0,x). Производная:

Итак, если мы откроем класс «ReLU» в папке Layers/Activations и переопределим функцию Backward, мы сможем реализовать следующее:

        public override void Backward(NDArray grad)
        {
            InputGrad = grad * (Input > 0);
        }

Производная сигмоиды хорошо объяснена в этом посте. Итак, если мы упростим, это будет

Давайте реализуем обратный метод для класса Sigmoid, который будет простым, как показано ниже.

        public override void Backward(NDArray grad)
        {
            InputGrad = grad * Output * (1 - Output);
        }

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

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

Исходный код: https://github.com/deepakkumar1984/tech-quantum/tree/master/Learn-NN-CSharp/Post4

Ссылка: https://towardsdatascience.com/step-by-step-the-math-behind-neural-networks-ac15e178bbd

Первоначально опубликовано на https://www.tech-quantum.com 21 марта 2019 г.