【问题标题】:XOR neural network backpropXOR 神经网络反向传播
【发布时间】:2015-05-14 16:16:45
【问题描述】:

我正在尝试在 Python 中实现具有 1 个隐藏层的基本 XOR NN。我没有特别理解反向传播算法,所以我一直坚持获取 delta2 和更新权重...帮助?

import numpy as np

def sigmoid(x):
    return 1.0 / (1.0 + np.exp(-x))

vec_sigmoid = np.vectorize(sigmoid)

theta1 = np.matrix(np.random.rand(3,3))
theta2 = np.matrix(np.random.rand(3,1))

def fit(x, y, theta1, theta2, learn_rate=.001):
    #forward pass
    layer1 = np.matrix(x, dtype='f')
    layer1 = np.c_[np.ones(1), layer1]
    layer2 = vec_sigmoid(layer1*theta1)
    layer3 = sigmoid(layer2*theta2)

    #backprop
    delta3 = y - layer3
    delta2 = (theta2*delta3) * np.multiply(layer2, 1 - layer2) #??

    #update weights
    theta2 += learn_rate * delta3 #??
    theta1 += learn_rate * delta2 #??

def train(X, Y):
    for _ in range(10000):
        for i in range(4):
            x = X[i]
            y = Y[i]
            fit(x, y, theta1, theta2)


X = [(0,0), (1,0), (0,1), (1,1)]
Y = [0, 1, 1, 0]    
train(X, Y)

【问题讨论】:

    标签: python machine-learning neural-network backpropagation


    【解决方案1】:

    好的,首先,这是修改后的代码,让您的代码正常工作。

    #! /usr/bin/python
    
    import numpy as np
    
    def sigmoid(x):
        return 1.0 / (1.0 + np.exp(-x))
    
    vec_sigmoid = np.vectorize(sigmoid)
    
    # Binesh - just cleaning it up, so you can easily change the number of hiddens.
    # Also, initializing with a heuristic from Yoshua Bengio.
    # In many places you were using matrix multiplication and elementwise multiplication
    # interchangably... You can't do that.. (So I explicitly changed everything to be
    # dot products and multiplies so it's clear.)
    input_sz = 2;
    hidden_sz = 3;
    output_sz = 1;
    theta1 = np.matrix(0.5 * np.sqrt(6.0 / (input_sz+hidden_sz)) * (np.random.rand(1+input_sz,hidden_sz)-0.5))
    theta2 = np.matrix(0.5 * np.sqrt(6.0 / (hidden_sz+output_sz)) * (np.random.rand(1+hidden_sz,output_sz)-0.5))
    
    def fit(x, y, theta1, theta2, learn_rate=.1):
        #forward pass
        layer1 = np.matrix(x, dtype='f')
        layer1 = np.c_[np.ones(1), layer1]
        # Binesh - for layer2 we need to add a bias term.
        layer2 = np.c_[np.ones(1), vec_sigmoid(layer1.dot(theta1))]
        layer3 = sigmoid(layer2.dot(theta2))
    
        #backprop
        delta3 = y - layer3
        # Binesh - In reality, this is the _negative_ derivative of the cross entropy function
        # wrt the _input_ to the final sigmoid function.
    
        delta2 = np.multiply(delta3.dot(theta2.T), np.multiply(layer2, (1-layer2)))
        # Binesh - We actually don't use the delta for the bias term. (What would be the point?
        # it has no inputs. Hence the line below.
        delta2 = delta2[:,1:]
    
        # But, delta's are just derivatives wrt the inputs to the sigmoid.
        # We don't add those to theta directly. We have to multiply these by
        # the preceding layer to get the theta2d's and theta1d's
        theta2d = np.dot(layer2.T, delta3)
        theta1d = np.dot(layer1.T, delta2)
    
        #update weights
        # Binesh - here you had delta3 and delta2... Those are not the
        # the derivatives wrt the theta's, they are the derivatives wrt
        # the inputs to the sigmoids.. (As I mention above)
        theta2 += learn_rate * theta2d #??
        theta1 += learn_rate * theta1d #??
    
    def train(X, Y):
        for _ in range(10000):
            for i in range(4):
                x = X[i]
                y = Y[i]
                fit(x, y, theta1, theta2)
    
    
    # Binesh - Here's a little test function to see that it actually works
    def test(X):
        for i in range(4):
            layer1 = np.matrix(X[i],dtype='f')
            layer1 = np.c_[np.ones(1), layer1]
            layer2 = np.c_[np.ones(1), vec_sigmoid(layer1.dot(theta1))]
            layer3 = sigmoid(layer2.dot(theta2))
            print "%d xor %d = %.7f" % (layer1[0,1], layer1[0,2], layer3[0,0])
    
    X = [(0,0), (1,0), (0,1), (1,1)]
    Y = [0, 1, 1, 0]    
    train(X, Y)
    
    # Binesh - Alright, let's see!
    test(X)
    

    还有,现在解释一下。原谅粗制滥造的图画。拍照比在 gimp 中绘图更容易。


    (来源:binesh at cablemodem.hex21.com

    所以。首先,我们有我们的误差函数。我们将其称为 CE(用于交叉熵。我会尽可能使用您的变量,不过,我将使用 L1、L2 和 L3 而不是 layer1、layer2 和 layer3。sigh(我不知道如何在这里做乳胶。它似乎在统计堆栈交换上工作。奇怪。)

    CE = -(Y log(L3) + (1-Y) log(1-L3))
    

    我们需要对这个 L3 求导,这样我们就可以看到如何移动 L3 从而减少这个值。

    dCE/dL3 = -((Y/L3) - (1-Y)/(1-L3))
            = -((Y(1-L3) - (1-Y)L3) / (L3(1-L3)))
            = -(((Y-Y*L3) - (L3-Y*L3)) / (L3(1-L3)))
            = -((Y-Y3*L3 + Y3*L3 - L3) / (L3(1-L3)))
            = -((Y-L3) / (L3(1-L3)))
            = ((L3-Y) / (L3(1-L3)))
    

    很好,但实际上,我们不能随意更改 L3。 L3 是 Z3 的函数(看我的图)。

    L3      = sigmoid(Z3)
    dL3/dZ3 = L3(1-L3)
    

    我不是在这里推导这个,(sigmoid 的导数)但是,它实际上并不难证明)。

    但是,无论如何,这是 L3 对 Z3 的导数,但我们想要 CE 对 Z3 的导数。

    dCE/dZ3 = (dCE/dL3) * (dL3/dZ3)
            = ((L3-Y)/(L3(1-L3)) * (L3(1-L3)) # Hey, look at that. The denominator gets cancelled out and
            = (L3-Y) # This is why in my comments I was saying what you are computing is the _negative_ derivative.
    

    我们将 Z 的导数称为“增量”。因此,在您的代码中,这对应于 delta3。

    很好,但我们也不能随心所欲地更改 Z3。我们需要计算它对 L2 的导数。

    但这更复杂。

    Z3 = theta2(0) + theta2(1) * L2(1) + theta2(2) * L2(2) + theta2(3) * L2(3)
    

    因此,我们需要对 wrt 进行偏导数。 L2(1)、L2(2) 和 L2(3)

    dZ3/dL2(1) = theta2(1)
    dZ3/dL2(2) = theta2(2)
    dZ3/dL2(3) = theta2(3)
    

    请注意,偏差实际上是

    dZ3/dBias  = theta2(0)
    

    但是偏差永远不会改变,它总是 1,所以我们可以放心地忽略它。但是,我们的 layer2 包含了偏差,所以我们暂时保留它。

    但是,再一次,我们想要 Z2(0)、Z2(1)、Z2(2) 的导数(不幸的是,看起来我画得很糟糕。看看图表,它会更清楚,我想想。)

    dL2(1)/dZ2(0) = L2(1) * (1-L2(1))
    dL2(2)/dZ2(1) = L2(2) * (1-L2(2))
    dL2(3)/dZ2(2) = L2(3) * (1-L2(3))
    

    现在是 dCE/dZ2(0..2)

    dCE/dZ2(0) = dCE/dZ3 * dZ3/dL2(1) * dL2(1)/dZ2(0)
               = (L3-Y)  * theta2(1)  * L2(1) * (1-L2(1))
    
    dCE/dZ2(1) = dCE/dZ3 * dZ3/dL2(2) * dL2(2)/dZ2(1)
               = (L3-Y)  * theta2(2)  * L2(2) * (1-L2(2))
    
    dCE/dZ2(2) = dCE/dZ3 * dZ3/dL2(3) * dL2(3)/dZ2(2)
               = (L3-Y)  * theta2(3)  * L2(3) * (1-L2(3))
    

    但是,实际上我们可以将其表示为 (delta3 * Transpose[theta2]) 元素乘以 (L2 * (1-L2)) (其中 L2 是向量)

    这些是我们的 delta2 层。我删除了它的第一个条目,因为正如我上面提到的,它对应于偏差的增量(我在图表上标记为 L2(0)。)

    所以。现在,我们有了 Z 的导数,但实际上,我们可以修改的只是我们的 theta。

    Z3 = theta2(0) + theta2(1) * L2(1) + theta2(2) * L2(2) + theta2(3) * L2(3)
    dZ3/dtheta2(0) = 1
    dZ3/dtheta2(1) = L2(1)
    dZ3/dtheta2(2) = L2(2)
    dZ3/dtheta2(3) = L2(3)
    

    再一次,我们想要 dCE/dtheta2(0) tho,这样就变成了

    dCE/dtheta2(0) = dCE/dZ3 * dZ3/dtheta2(0)
                   = (L3-Y) * 1
    dCE/dtheta2(1) = dCE/dZ3 * dZ3/dtheta2(1)
                   = (L3-Y) * L2(1)
    dCE/dtheta2(2) = dCE/dZ3 * dZ3/dtheta2(2)
                   = (L3-Y) * L2(2)
    dCE/dtheta2(3) = dCE/dZ3 * dZ3/dtheta2(3)
                   = (L3-Y) * L2(3)
    

    嗯,这只是 np.dot(layer2.T, delta3),这就是我在 theta2d 中所拥有的

    而且,类似地: Z2(0) = theta1(0,0) + theta1(1,0) * L1(1) + theta1(2,0) * L1(2) dZ2(0)/dtheta1(0,0) = 1 dZ2(0)/dtheta1(1,0) = L1(1) dZ2(0)/dtheta1(2,0) = L1(2)

    Z2(1) = theta1(0,1) + theta1(1,1) * L1(1) + theta1(2,1) * L1(2)
    dZ2(1)/dtheta1(0,1) = 1
    dZ2(1)/dtheta1(1,1) = L1(1)
    dZ2(1)/dtheta1(2,1) = L1(2)
    
    Z2(2) = theta1(0,2) + theta1(1,2) * L1(1) + theta1(2,2) * L1(2)
    dZ2(2)/dtheta1(0,2) = 1
    dZ2(2)/dtheta1(1,2) = L1(1)
    dZ2(2)/dtheta1(2,2) = L1(2)
    

    而且,我们必须乘以 dCE/dZ2(0)、dCE/dZ2(1) 和 dCE/dZ2(2)(对于上面的三个组中的每一个。但是,如果您考虑一下,然后就变成了 np.dot(layer1.T, delta2),这就是我在 theta1d 中所拥有的。

    现在,因为您在代码中执行了 Y-L3,所以您将添加到 theta1 和 theta2...但是,原因如下。我们上面刚刚计算的是 CE 对权重的导数。所以,这意味着,增加权重将增加 CE。但是,我们真的想减少 CE。所以,我们减去(通常)。但是,因为在您的代码中,您正在计算负导数,所以添加是正确的。

    这有意义吗?

    【讨论】:

    • 非常感谢,我也会在纸上完成这个。是否很难对输入进行矢量化以避免通过 X 的内部 for 循环?
    • 哦,绝对不是。在这里,您将 X 作为 1x2 向量(它变成 1x3 的偏差)传递,并且您将 (dot product) 乘以 3x3 矩阵,以获得 1x3 输出的三个节点第2层,对吧?相反,您要做的就是将 X 的所有 4 项作为 4x2 向量传递(您必须添加一个偏置向量以使其成为 4x3),然后将乘积乘以 3x3 矩阵以获得 4x3 输出对于 layer2 中的三个节点。你也在第 3 层上做同样的事情,你会得到一个 4x1,这将是所有目标同时发生......
    • 嗯。显然我不能在这里发布整个代码。但是,矢量化真的很简单。首先在 layer1 = np.matrix(x, dtype='f') 之后添加 y = np.matrix(y, dtype='f').T (m,sz) = layer1.shape 改变 np.ones(1 ) 到 np.ones(m) 在这两个地方。并使 train 成为: def train(X, Y): for _ in range(10000): fit(X,Y, theta1, theta2) 我没有更改测试,但这应该很容易。 (评论的字符用完了,嘿嘿...)
    • 呃。我猜你不能在评论中写代码。希望如何改变仍然足够清楚。
    猜你喜欢
    • 2010-10-23
    • 2015-03-03
    • 2012-02-21
    • 2011-01-05
    • 1970-01-01
    • 1970-01-01
    • 2013-04-26
    • 2016-09-19
    相关资源
    最近更新 更多