神经网络反向传播未完全训练

Posted

技术标签:

【中文标题】神经网络反向传播未完全训练【英文标题】:Neural network backprop not fully training 【发布时间】:2018-08-04 19:52:01 【问题描述】:

我有这个我训练过的神经网络,如下所示,它可以工作,或者至少看起来可以工作,但问题在于训练。我正在尝试训练它充当或门,但它似乎永远不会到达那里,输出看起来像这样:

prior to training:

 [[0.50181624]
 [0.50183743]
 [0.50180414]
 [0.50182533]]

post training:

 [[0.69641759]
 [0.754652  ]
 [0.75447178]
 [0.79431198]]

expected output:

 [[0]
 [1]
 [1]
 [1]]

我有这个损失图:

奇怪的是,它似乎正在训练,但同时还没有达到预期的输出。我知道它永远不会真正实现 0 和 1,但同时我希望它能够管理并获得更接近预期输出的东西。

我在试图弄清楚如何支持错误时遇到了一些问题,因为我想让这个网络有任意数量的隐藏层,所以我将局部梯度与权重一起存储在一个层中,然后发送错误从头到尾。

我怀疑罪魁祸首的主要功能是 NeuralNetwork.train 和两种前向方法。

import sys
import math
import numpy as np
import matplotlib.pyplot as plt
from itertools import product


class NeuralNetwork:
    class __Layer:
        def __init__(self,args):
            self.__epsilon = 1e-6
            self.localGrad = 0
            self.__weights = np.random.randn(
                args["previousLayerHeight"],
                args["height"]
            )*0.01
            self.__biases = np.zeros(
                (args["biasHeight"],1)
            )

        def __str__(self):
            return str(self.__weights)

        def forward(self,X):
            a = np.dot(X, self.__weights) + self.__biases
            self.localGrad = np.dot(X.T,self.__sigmoidPrime(a))
            return self.__sigmoid(a)

        def adjustWeights(self, err):
            self.__weights -= (err * self.__epsilon)

        def __sigmoid(self, z):
            return 1/(1 + np.exp(-z))

        def __sigmoidPrime(self, a):
            return self.__sigmoid(a)*(1 - self.__sigmoid(a))

    def __init__(self,args):
        self.__inputDimensions = args["inputDimensions"]
        self.__outputDimensions = args["outputDimensions"]
        self.__hiddenDimensions = args["hiddenDimensions"]
        self.__layers = []
        self.__constructLayers()

    def __constructLayers(self):
        self.__layers.append(
            self.__Layer(
                
                    "biasHeight": self.__inputDimensions[0],
                    "previousLayerHeight": self.__inputDimensions[1],
                    "height": self.__hiddenDimensions[0][0] 
                        if len(self.__hiddenDimensions) > 0 
                        else self.__outputDimensions[0]
                
            )
        )

        for i in range(len(self.__hiddenDimensions)):
            self.__layers.append(
                self.__Layer(
                    
                        "biasHeight": self.__hiddenDimensions[i + 1][0] 
                            if i + 1 < len(self.__hiddenDimensions)
                            else self.__outputDimensions[0],
                        "previousLayerHeight": self.__hiddenDimensions[i][0],
                        "height": self.__hiddenDimensions[i + 1][0] 
                            if i + 1 < len(self.__hiddenDimensions)
                            else self.__outputDimensions[0]
                    
                )
            )

    def forward(self,X):
        out = self.__layers[0].forward(X)
        for i in range(len(self.__layers) - 1):
            out = self.__layers[i+1].forward(out)
        return out  

    def train(self,X,Y,loss,epoch=5000000):
        for i in range(epoch):
            YHat = self.forward(X)
            delta = -(Y-YHat)
            loss.append(sum(Y-YHat))
            err = np.sum(np.dot(self.__layers[-1].localGrad,delta.T), axis=1)
            err.shape = (self.__hiddenDimensions[-1][0],1)
            self.__layers[-1].adjustWeights(err)
            i=0
            for l in reversed(self.__layers[:-1]):
                err = np.dot(l.localGrad, err)
                l.adjustWeights(err)
                i += 1

    def printLayers(self):
        print("Layers:\n")
        for l in self.__layers:
            print(l)
            print("\n")

def main(args):
    X = np.array([[x,y] for x,y in product([0,1],repeat=2)])
    Y = np.array([[0],[1],[1],[1]])
    nn = NeuralNetwork(
        
            #(height,width)
            "inputDimensions": (4,2),
            "outputDimensions": (1,1),
            "hiddenDimensions":[
                (6,1)
            ]
        
    )

    print("input:\n\n",X,"\n")
    print("expected output:\n\n",Y,"\n")
    nn.printLayers()
    print("prior to training:\n\n",nn.forward(X), "\n")
    loss = []
    nn.train(X,Y,loss)
    print("post training:\n\n",nn.forward(X), "\n")
    nn.printLayers()
    fig,ax = plt.subplots()

    x = np.array([x for x in range(5000000)])
    loss = np.array(loss)
    ax.plot(x,loss)
    ax.set(xlabel="epoch",ylabel="loss",title="logic gate training")

    plt.show()

if(__name__=="__main__"):
    main(sys.argv[1:])

有人能指出我在这里做错了什么吗,我强烈怀疑这与我处理矩阵的方式有关,但同时我一点也不知道发生了什么。

感谢您花时间阅读我的问题,并花时间回复(如果相关)。

编辑: 实际上这有很多问题,但我仍然对如何解决它感到有些困惑。虽然损失图看起来像它的训练,而且有点像,但我上面所做的数学是错误的。

看看训练函数。

def train(self,X,Y,loss,epoch=5000000):
        for i in range(epoch):
            YHat = self.forward(X)
            delta = -(Y-YHat)
            loss.append(sum(Y-YHat))
            err = np.sum(np.dot(self.__layers[-1].localGrad,delta.T), axis=1)
            err.shape = (self.__hiddenDimensions[-1][0],1)
            self.__layers[-1].adjustWeights(err)
            i=0
            for l in reversed(self.__layers[:-1]):
                err = np.dot(l.localGrad, err)
                l.adjustWeights(err)
                i += 1

注意我是如何得到 delta = -(Y-Yhat) 的,然后将其与最后一层的“局部梯度”进行点积。 “局部梯度”是局部 W 梯度。

def forward(self,X):
    a = np.dot(X, self.__weights) + self.__biases
    self.localGrad = np.dot(X.T,self.__sigmoidPrime(a))
    return self.__sigmoid(a)

我跳过了链式规则中的一步。我真的应该首先乘以 W* sigprime(XW + b),因为这是 X 的局部梯度,然后是局部 W 梯度。我试过了,但我仍然遇到问题,这是新的转发方法(注意需要为新的变量初始化层的 __init__,我将激活函数更改为 tanh)

def forward(self, X):
    a = np.dot(X, self.__weights) + self.__biases
    self.localPartialGrad = self.__tanhPrime(a)
    self.localWGrad = np.dot(X.T, self.localPartialGrad)
    self.localXGrad = np.dot(self.localPartialGrad,self.__weights.T)            
    return self.__tanh(a)

并将训练方法更新为如下所示:

def train(self, X, Y, loss, epoch=5000):
    for e in range(epoch):
        Yhat = self.forward(X)
        err = -(Y-Yhat)
        loss.append(sum(err))
        print("loss:\n",sum(err))
        for l in self.__layers[::-1]:
            l.adjustWeights(err)
            if(l != self.__layers[0]):
                err = np.multiply(err,l.localPartialGrad)
                err = np.multiply(err,l.localXGrad)

我得到的新图表到处都是,我不知道发生了什么。这是我更改的最后一点代码:

def adjustWeights(self, err):
    perr = np.multiply(err, self.localPartialGrad)  
    werr = np.sum(np.dot(self.__weights,perr.T),axis=1)
    werr = werr * self.__epsilon
    werr.shape = (self.__weights.shape[0],1)
    self.__weights = self.__weights - werr

【问题讨论】:

只是另一条评论。您是否尝试过不同的学习率? 1e-6 非常小......它也应该适用于 sigmoid...... 我实际上重新审视了代码并试图证明我对反向传播所做的事情是正确的。原来我搞砸了很多。看起来我乘以权重的局部导数,而不是乘以相对于输入的局部导数,这是错误的。希望今晚结束时我能修好它。 更不用说我忘了实施与更新偏差有关的任何事情。 【参考方案1】:

您的网络正在学习,从损失图中可以看出,因此反向传播实现是正确的(恭喜!)。这种特定架构的主要问题是激活函数的选择:sigmoid。我已经用tanh 替换了sigmoid,它立即工作得更好。

来自this discussion on CV.SE:

这个选择有两个原因(假设你已经标准化 您的数据,这非常重要):

具有更强的梯度:由于数据以 0 为中心,因此 衍生品更高。要看到这一点,请计算 tanh 函数并注意输入值在 [0,1] 范围内。这 tanh 函数的范围是 [-1,1] 和 sigmoid 函数的范围 是 [0,1]

避免梯度偏差。这在 论文,值得一读以了解这些问题。

虽然我确信基于 sigmoid 的 NN 也可以被训练,但看起来它对输入值更加敏感(请注意,它们不是以零为中心的),因为激活本身不是以零为中心的。 tanh 无论如何都比sigmoid 好,所以更简单的方法就是使用那个激活函数。

关键变化是这样的:

def __tanh(self, z):
  return np.tanh(z)

def __tanhPrime(self, a):
  return 1 - self.__tanh(a) ** 2

...而不是__sigmoid__sigmoidPrime

我还稍微调整了超参数,这样网络现在可以在 100k epoch 中学习,而不是 5m:

prior to training:

 [[ 0.        ]
 [-0.00056925]
 [-0.00044885]
 [-0.00101794]] 

post training:

 [[0.        ]
 [0.97335842]
 [0.97340917]
 [0.98332273]] 

完整的代码在this gist。

【讨论】:

谢谢!我以为我搞砸了矩阵乘法,因为这是我第一次存储局部梯度并在 back prop 中使用它,但显然就像你说的那样很好:)。谢谢哥们。我真的应该确保我正确理解激活函数,这可能是我的缺点。【参考方案2】:

好吧,我是个白痴。我错了是对的,但我错了。让我解释一下。

在反向训练方法中,我正确训练了最后一层,但之后的所有层都没有正确训练,因此为什么上面的网络会得出结果,它确实是训练,但只有一层。

那么我做错了什么?好吧,我只是乘以权重相对于输出的局部梯度,因此链式法则部分正确。

假设损失函数是这样的:

t = Y-X2

损失 = 1/2*(t)^2

a2 = X1W2 + b

X2 = 激活(a2)

a1 = X0W1 + b

X1 = 激活(a1)

我们知道损失对 W2 的导数是 -(Y-X2)*X1。这是在我的训练功能的第一部分完成的:

def train(self,X,Y,loss,epoch=5000000):
    for i in range(epoch):
        #First part
        YHat = self.forward(X)
        delta = -(Y-YHat)
        loss.append(sum(Y-YHat))
        err = np.sum(np.dot(self.__layers[-1].localGrad,delta.T), axis=1)
        err.shape = (self.__hiddenDimensions[-1][0],1)
        self.__layers[-1].adjustWeights(err)
        i=0
        #Second part
        for l in reversed(self.__layers[:-1]):
            err = np.dot(l.localGrad, err)
            l.adjustWeights(err)
            i += 1

但是第二部分是我搞砸的地方。为了计算关于 W1 的损失,我必须将原始误差 -(Y-X2) 乘以 W2,因为 W2 是最后一层的局部 X 梯度,并且由于链式法则,这必须首先完成。 那么我可以乘以局部 W 梯度 (X1) 以获得相对于 W1 的损失。我没有先做局部 X 梯度的乘法,所以最后一层确实在训练,但是之后的所有层都有一个错误,随着层数的增加而放大。

为了解决这个问题,我更新了 train 方法:

def train(self,X,Y,loss,epoch=10000):
    for i in range(epoch):
        YHat = self.forward(X)
        err = -(Y-YHat)
        loss.append(sum(Y-YHat))
        werr = np.sum(np.dot(self.__layers[-1].localWGrad,err.T), axis=1)
        werr.shape = (self.__hiddenDimensions[-1][0],1)
        self.__layers[-1].adjustWeights(werr)
        for l in reversed(self.__layers[:-1]):
            err = np.multiply(err, l.localXGrad)
            werr = np.sum(np.dot(l.weights,err.T),axis=1)
            l.adjustWeights(werr)

现在我得到的损失图是这样的:

【讨论】:

你应该edit你的帖子有任何更新,像这样。 Answer 部分仅供审阅者发布建议的答案。

以上是关于神经网络反向传播未完全训练的主要内容,如果未能解决你的问题,请参考以下文章

神经网络-反向传播,训练误差

如何将多线程应用于反向传播神经网络训练?

神经网络 MNIST:反向传播是正确的,但训练/测试精度非常低

如何通过反向传播训练卷积神经网络中的过滤器?

第二节,神经网络中反向传播四个基本公式证明——BackPropagation

深度学习基础-基于Numpy的循环神经网络(RNN)实现和反向传播训练