神经网络backprop没有完全训练

问题描述 投票:10回答:2

我有这样的神经网络,我训练过看到它,它起作用,或者至少看起来有效,但问题在于训练。我正在尝试训练它作为OR门,但它似乎永远不会到达那里,输出往往看起来像这样:

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]]

我有这个损失图:

enter image description here

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

我有一些问题试图找出如何支持错误,因为我想让这个网络有任意数量的隐藏层,所以我将局部渐变存储在一个层中,沿着权重,并从末尾发送错误背部。

我怀疑的主要功能是神经网络和三种前进方法。

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梯度。我试过了,但是我仍然遇到问题,这里是新的转发方法(请注意,对于新的vars,层的__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
python numpy matrix machine-learning neural-network
2个回答
8
投票

您的网络正在学习,从损失图表中可以看出,所以backprop实现是正确的(恭喜!)。这种特殊架构的主要问题是激活函数的选择:sigmoid。我已经用sigmoid取代了tanh,它立刻变得更好。

来自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

我还调整了一些超参数,以便网络现在可以在10万个时代中学习,而不是5米:

prior to training:

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

post training:

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

plot

完整的代码在this gist


1
投票

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

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

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

让我们说损失函数是这样的:

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梯度进行乘法,所以最后一层确实是训练,但是之后的所有层都有一个随着图层增加而放大的误差。

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

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)

现在我得到的损失图看起来像这样:

enter image description here

© www.soinside.com 2019 - 2024. All rights reserved.