Подробное объяснение нейронной сети BP и реализации Python

Python

Обзор

Структура нейронной сети состоит из входного слоя, скрытого слоя и выходного слоя.Каждый узел в нейронной сети связан со всеми узлами в предыдущем слое, который мы называем полностью связанным, как показано на рисунке ниже.

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

прямое распространение

Рассчитайте данные входного слоя для передачи скрытому слою:

Как видно из рисунка ниже, мы можем вычислить первый нейрон скрытого слояZ_1Значение:

Z_1 = X_1 * W_{11} + X_2 * W_{12} + X_3 * W_{13} + b_{1}

\alpha_{1} = f(Z_1)

вf(.)это функция активации

Как видно из рисунка ниже, мы можем вычислить второй нейрон скрытого слояZ_2Значение:

Z_2 = X_1 * W_{21} + X_2 * W_{22} + X_3 * W_{23} + b_{1}

\alpha_{2} = f(Z_2)

Как видно из рисунка ниже, мы можем вычислить третий нейрон скрытого слояZ_3Значение:

Z_3 = X_1 * W_{31} + X_2 * W_{32} + X_3 * W_{33} + b_{1}

\alpha_{3} = f(Z_2)

Пока что мы вычислили все значения от входного слоя до скрытого слоя.

Процесс вычисления скрытого слоя в выходной слой:

Как видно из рисунка ниже, мы можем вычислить скрытый слой до первого выходного нейронаZ_{4}Значение:

Z_{4} = \alpha_{1} * W_{41} + \alpha_{2} * W_{42} + \alpha_{3} * W_{43} + b_{2}

\alpha_{4} = f(Z_{4})

Аналогично можно сделать вывод, чтоZ_{5},Z_{6}Значение:

Z_{5} = \alpha_{1} * W_{51} + \alpha_{2} * W_{52} + \alpha_{3} * W_{53} + b_{2}

\alpha_{5} = f(Z_{5})

Z_{6} = \alpha_{1} * W_{61} + \alpha_{2} * W_{62} + \alpha_{3} * W_{63} + b_{2}

\alpha_{6} = f(Z_{6})

Чтобы упростить наш будущий процесс обработки данных, сейчас мы устанавливаемlВходными данными слоя является вектор\alpha^{l}, весW^{l}, переменная смещенияb^{l}. Тогда мы можем получить из приведенного выше процесса решенияl+1Данные для слоя:

z^{l+1} = \alpha^{l} * W^{l} + b^{l} \cdots(1)

\alpha^{l+1} = f(z^{l+1}) \cdots(2)

На данный момент процесс прямого распространения нейронной сети завершен.

обратное распространение

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

Предположим, у нас есть фиксированная выборка\{ (x^{(1)}, y^{(1)}), \ldots, (x^{(m)}, y^{(m)})\}, который содержитmобразец. Мы можем использовать пакетный градиентный спуск для решения нейронных сетей. В частности, для одного образца(x,y), а его функция стоимости выглядит следующим образом (взято из сети):

Мы можем определить общую функцию затрат, как показано ниже (взято из сети):

\begin{align} \\ J(W,b) &= \left[ \frac{1}{m} \sum_{i=1}^mJ(W,b;x^{(i),y^{(i)}}) \right] + \frac{\lambda}{2}\sum_{l=1}^{n_l-1} \sum_{i=1}^{s_l + 1} \sum_{j=1}^{s_{l}}(W_{ij}^{(l)})^{2} \\    &= \left[ \frac{1}{m} \sum_{i=1}^m\frac{1}{2} (h_{W,b}(x^{(i)}) - y^{(i)})^{2} \right] + \frac{\lambda}{2} \sum_{l=1}^{n_l-1} \sum_{i=1}^{s_l + 1} \sum_{j=1}^{s_{l}}(W_{ij}^{(l)})^{2} \\ \end{align}

выше оJ(W,b)Первый член в определении представляет собой среднеквадратичную ошибку. Второй член - это член регуляризации (также называемый членом затухания веса), целью которого является уменьшение величины весов и предотвращение переобучения.

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

W^{l}_{ij} = W^{l}_{ij} - \eta \frac{\partial J(W, b)}{\partial W_{ij}}

b^{l}_{i} = b^{l}_{i} - \eta \frac{\partial J(W, b)}{\partial b_i}

Возьмем частную производную от общей функции затрат:

\begin{align} \\  \frac{\partial J(W, b)}{\partial W^{l}_{ij}} &= \left[ \frac{1}{m}\frac{\partial}{\partial W^{l}_{ij}} \sum_{i=1}^mJ(W,b;x^{(i),y^{(i)}}) \right] + \lambda W^{l}_{ij} \\  &= \left[ \frac{1}{m} \sum_{i=1}^m \frac{\partial}{\partial W^{l}_{ij}}J(W,b;x^{(i),y^{(i)}}) \right] + \lambda W^{l}_{ij} \\ \end{align}

\begin{align} \\  \frac{\partial J(W, b)}{\partial b^{l}_{i}} &= \left[ \frac{1}{m}\frac{\partial}{\partial b^{l}_{i}} \sum_{i=1}^mJ(W,b;x^{(i),y^{(i)}}) \right] \\  &= \left[ \frac{1}{m} \sum_{i=1}^m \frac{\partial}{\partial b^{l}_{i}}J(W,b;x^{(i),y^{(i)}}) \right] \\ \end{align}

Как видно из приведенных выше двух уравнений, мы преобразуем задачу в\frac{\partial}{\partial W^{l}_{ij}}J(W,b;x^{(i),y^{(i)}})и\frac{\partial}{\partial b^{l}_{i}}J(W,b;x^{(i),y^{(i)}})значение

Итак, для первогоn_lкаждый выходной блок слоя (выходной слой)i, рассчитываем остатки по следующей формуле (рисунок ниже взят из сети):

правильноl = n_l-1, n_l-2, n_l-3, \ldots, 2различные слоиlпервоеiОстаточный метод расчета каждого узла следующий (рисунок ниже взят из сети):

Вышеупомянутый процесс вывода сзади вперед является первоначальным значением «обратной проводимости».

Для вычисления необходимых нам частных производных метод расчета следующий:

Реализация нейронной сети BP на Python

# -*- coding: utf-8 -*-
'''
Created on

@author: Belle
'''
from numpy.random.mtrand import randint
import numpy as np


'''双曲函数'''
def tanh(value):
    return (1 / (1 + np.math.e ** (-value)))

'''双曲函数的导数'''
def tanhDer(value):
    tanhValue = tanh(value)
    return tanhValue * (1 - tanhValue)

'''
Bp神经网络model
'''
class BpNeuralNetWorkModel:
    def __init__(self, trainningSet, label, layerOfNumber, studyRate):
        '''学习率'''
        self.studyRate = studyRate
        '''计算隐藏层神经元的数量'''
        self.hiddenNeuronNum = int(np.sqrt(trainningSet.shape[1] + label.shape[1]) + randint(1, 10))
        '''层数据'''
        self.layers = []
        '''创建输出层'''
        currentLayer = Layer()
        currentLayer.initW(trainningSet.shape[1], self.hiddenNeuronNum)
        self.layers.append(currentLayer)
        
        '''创建隐藏层'''
        for index in range(layerOfNumber - 1):
            currentLayer = Layer()
            self.layers.append(currentLayer)
            '''输出层后面不需要求权重值'''
            if index == layerOfNumber - 2:
                break
            nextLayNum = 0
            
            '''初始化各个层的权重置'''
            if index == layerOfNumber - 3:
                '''隐藏层到输出层'''
                nextLayNum = label.shape[1]
            else:
                '''隐藏层到隐藏层'''
                nextLayNum = self.hiddenNeuronNum
            currentLayer.initW(self.hiddenNeuronNum, nextLayNum)
        '''输出层的分类值'''
        currentLayer = self.layers[len(self.layers) - 1]
        currentLayer.label = label
    
    '''神经网络前向传播'''
    def forward(self, trainningSet):
        '''计算输入层的输出值'''
        currentLayer = self.layers[0]
        currentLayer.alphas = trainningSet
        currentLayer.caculateOutPutValues()
        
        preLayer = currentLayer
        for index in range(1, len(self.layers)):
            currentLayer = self.layers[index]
            '''上一层的out put values就是这一层的zValues'''
            currentLayer.zValues = preLayer.outPutValues
            '''计算alphas'''
            currentLayer.caculateAlphas()
            '''最后一层不需要求输出值,只要求出alpha'''
            if index == len(self.layers) - 1:
                break
            '''输入层计算out puts'''
            currentLayer.caculateOutPutValues()
            '''指向上一层的layer'''
            preLayer = currentLayer
    
    '''神经网络后向传播'''
    def backPropogation(self):
        layerCount = len(self.layers)
        
        '''输出层的残差值'''
        currentLayer = self.layers[layerCount - 1]
        currentLayer.caculateOutPutLayerError()
        
        '''输出层到隐藏层'''
        preLayer = currentLayer
        layerCount = layerCount - 1
        while layerCount >= 1:
            '''当前层'''
            currentLayer = self.layers[layerCount - 1]
            '''更新权重'''
            currentLayer.updateWeight(preLayer.errors, self.studyRate)
            if layerCount != 1:
                currentLayer.culateLayerError(preLayer.errors)
            layerCount = layerCount - 1
            preLayer = currentLayer
            
'''
创建层
'''
class Layer:
    def __init__(self):
        self.b = 0
    
    '''使用正态分布的随机值初始化w的值'''
    def initW(self, numOfAlpha, nextLayNumOfAlpha):
        self.w = np.mat(np.random.randn(nextLayNumOfAlpha, numOfAlpha))
    
    '''计算当前层的alphas'''
    def caculateAlphas(self):
        '''alpha = f(z)'''
        self.alphas = np.mat([tanh(self.zValues[row1,0]) for row1 in range(len(self.zValues))])
        '''求f'(z)的值(即f的导数值)'''
        self.zDerValues = np.mat([tanhDer(self.zValues[row1,0]) for row1 in range(len(self.zValues))])
    
    '''计算out puts'''
    def caculateOutPutValues(self):
        '''计算当前层z = w * alpha的的下一层的输入值'''
        self.outPutValues = self.w * self.alphas.T + self.b
    
    '''计算输出层的残差'''
    def caculateOutPutLayerError(self):
        self.errors = np.multiply(-(self.label - self.alphas), self.zDerValues)
        print("out put layer alphas ..." + str(self.alphas))
    
    '''计算其它层的残差'''
    def culateLayerError(self, preErrors):
        self.errors = np.mat([(self.w[:,column].T * preErrors.T * self.zDerValues[:,column])[0,0] for column in range(self.w.shape[1])])
    
    '''更新权重'''
    def updateWeight(self, preErrors, studyRate):
        data = np.zeros((preErrors.shape[1], self.alphas.shape[1]))
        for index in range(preErrors.shape[1]):
            data[index,:] = self.alphas * (preErrors[:,index][0,0])
        self.w = self.w - studyRate * data

'''
训练神经网络模型
@param train_set: 训练样本
@param labelOfNumbers: 训练总类别
@param layerOfNumber:  神经网络层数,包括输出层,隐藏层和输出层(默认只有一个输入层,隐藏层和输出层)
'''
def train(train_set, label, layerOfNumber = 3, sampleTrainningTime = 5000, studyRate = 0.6):
    neuralNetWork = BpNeuralNetWorkModel(train_set, label, layerOfNumber, studyRate)
    '''训练数据'''
    for row in range(train_set.shape[0]):
        '''当个样本使用梯度下降的方法训练sampleTrainningTime次'''
        for time in range(sampleTrainningTime):
            '''前向传播 '''
            neuralNetWork.forward(train_set[row,:])
            '''反向传播'''
            neuralNetWork.backPropogation()
            


тестовый код

# -*- coding: utf-8 -*-
'''
Created on 2018��5��27��

@author: Belle
'''

import BpNeuralNetWork
import numpy as np

train_set = np.mat([[0.05, 0.1], [0.3, 0.2]])
labelOfNumbers = np.mat([0.1, 0.99, 0.3])
layerOfNumber = 4

bpNeuralNetWork = BpNeuralNetWork.train(train_set, labelOfNumbers, layerOfNumber)

Ниже приведено выходное значение тестового кода.