【问题标题】:Numerical errors in Keras vs NumpyKeras vs Numpy 中的数值错误
【发布时间】:2020-03-10 14:40:27
【问题描述】:

为了真正理解卷积层,我在basic numpy中重新实现了单个keras Conv2D层的前向方法。两个接缝的输出几乎相同,但有一些细微差别。

获取 keras 输出:

inp = K.constant(test_x)
true_output = model.layers[0].call(inp).numpy()

我的输出:

def relu(x):
    return np.maximum(0, x)


def forward(inp, filter_weights, filter_biases):

    result = np.zeros((1, 64, 64, 32))

    inp_with_padding = np.zeros((1, 66, 66, 1))
    inp_with_padding[0, 1:65, 1:65, :] = inp

    for filter_num in range(32):
        single_filter_weights = filter_weights[:, :, 0, filter_num]

        for i in range(64):
            for j in range(64):
                prod = single_filter_weights * inp_with_padding[0, i:i+3, j:j+3, 0]
                filter_sum = np.sum(prod) + filter_biases[filter_num]
                result[0, i, j, filter_num] = relu(filter_sum)
    return result


my_output = forward(test_x, filter_weights, biases_weights)

结果大致相同,但以下是一些差异示例:

Mine: 2.6608338356018066
Keras: 2.660834312438965

Mine: 1.7892705202102661
Keras: 1.7892701625823975

Mine: 0.007190803997218609
Keras: 0.007190565578639507

Mine: 4.970898151397705
Keras: 4.970897197723389

我尝试将所有内容都转换为 float32,但这并不能解决问题。有什么想法吗?

编辑: 我绘制了错误的分布,它可能会对正在发生的事情有所了解。可以看出,误差都具有非常相似的值,分为四组。然而,这些误差并不完全是这四个值,而几乎都是围绕这四个峰值的唯一值。

我对如何让我的实现与 keras 完全匹配非常感兴趣。不幸的是,当实现多层时,错误似乎成倍增加。任何见解都会对我有很大帮助!

【问题讨论】:

  • 你在 Keras 中使用padding='same' 吗?
  • 是的,没错!

标签: python numpy keras floating-point precision


【解决方案1】:

鉴于差异很小,我会说它们是舍入误差。
我建议使用np.isclose(或math.isclose)来检查浮点数是否“相等”。

【讨论】:

  • 是的,非常真实!但是,我非常有兴趣了解究竟为什么会发生这种情况、keras 内部发生了什么以及让我的函数输出与 keras 输出完美匹配。
【解决方案2】:

浮点运算不可交换。这是一个例子:

In [19]: 1.2 - 1.0 - 0.2
Out[19]: -5.551115123125783e-17

In [21]: 1.2 - 0.2 -  1.0
Out[21]: 0.0

因此,如果您想要完全相同的结果,您不仅需要在分析上进行相同的计算。但是您还需要以完全相同的顺序执行它们,使用相同的数据类型和舍入实现。

调试这个。从 Keras 代码开始,并根据您的代码逐行更改,直到您发现不同为止。

【讨论】:

    【解决方案3】:

    首先要检查您是否使用padding='same'。您似乎在实现中使用了相同的填充。

    如果您使用其他类型的填充,包括默认值padding='valid',则会有所不同。

    另一种可能性是,由于小和的三重循环,您可能会累积错误。

    您可以立即执行此操作,看看是否有所不同。将此实现与您自己的实现进行比较,例如:

    def forward2(inp, filter_weights, filter_biases):
    
        #inp: (batch, 64, 64, in)
        #w: (3, 3, in, out)
        #b: (out,)
    
        padded_input = np.pad(inp, ((0,0), (1,1), (1,1), (0,0))) #(batch, 66, 66, in)
        stacked_input = np.stack([
            padded_input[:,  :-2], 
            padded_input[:, 1:-1],
            padded_input[:, 2:  ]], axis=1) #(batch, 3, 64, 64, in)
    
        stacked_input = np.stack([
            stacked_input[:, :, :,  :-2],
            stacked_input[:, :, :, 1:-1],
            stacked_input[:, :, :, 2:  ]], axis=2) #(batch, 3, 3, 64, 64, in)
    
    
        stacked_input = stacked_input.reshape((-1, 3, 3, 64, 64, 1,   1))
        w =            filter_weights.reshape(( 1, 3, 3,  1,  1, 1, 32))
        b =            filter_biases.reshape (( 1, 1, 1, 32))
    
    
        result = stacked_input * w #(-1, 3, 3, 64, 64, 1, 32)
        result = result.sum(axis=(1,2,-2)) #(-1, 64, 64, 32)
        result += b
    
        result = relu(result)
    
        return result
    

    第三种可能性是检查您是否使用 GPU 并将所有内容切换到 CPU 进行测试。 GPU 的一些算法甚至是不确定的。


    对于任何内核大小:

    def forward3(inp, filter_weights, filter_biases):
    
        inShape = inp.shape           #(batch, imgX, imgY, ins)
        wShape = filter_weights.shape #(wx, wy, ins, out)
        bShape = filter_biases.shape  #(out,)
    
        ins = inShape[-1]
        out = wShape[-1]
    
        wx = wShape[0]
        wy = wShape[1]
    
        imgX = inShape[1]
        imgY = inShape[2]
    
        assert imgX >= wx
        assert imgY >= wy
    
        assert inShape[-1] == wShape[-2]
        assert bShape[-1] == wShape[-1]
    
    
        #you may need to invert this padding, exchange L with R
        loseX = wx - 1
        padXL = loseX // 2
        padXR = padXL + (1 if loseX % 2 > 0 else 0)
    
        loseY = wy - 1
        padYL = loseY // 2
        padYR = padYL + (1 if loseY % 2 > 0 else 0)
    
        padded_input = np.pad(inp, ((0,0), (padXL,padXR), (padYL,padYR), (0,0)))
            #(batch, paddedX, paddedY, in)
    
    
        stacked_input = np.stack([padded_input[:, i:imgX + i] for i in range(wx)],
                                 axis=1) #(batch, wx, imgX, imgY, in)
    
        stacked_input = np.stack([stacked_input[:,:,:,i:imgY + i] for i in range(wy)],
                                 axis=2) #(batch, wx, wy, imgX, imgY, in)
    
        stacked_input = stacked_input.reshape((-1, wx, wy, imgX, imgY, ins,   1))
        w =            filter_weights.reshape(( 1, wx, wy,    1,    1, ins, out))
        b =             filter_biases.reshape(( 1,   1,  1, out))
    
        result = stacked_input * w
        result = result.sum(axis=(1,2,-2))
        result += b
    
        result = relu(result)
    
        return result
    

    【讨论】:

    • 感谢您的回答!填充确实是“相同的”,所以一切都很好。老实说,我真的不明白你的代码。什么是堆叠输入?我们能以如此简单的形式将卷积表示为简单的乘法吗?此外,这部分代码以不同的方式粘贴了两次。
    • 关于 gpu 的好消息,但两个代码都在 cpu 上运行 :)
    • @EliteKaffee 第二次粘贴应该使用stacked_input 抱歉。固定的。查看每个堆栈之后的形状,看看它们在做什么。
    • 我更正了我的代码,错误几乎和你的一样。不过,我的实现有很大的速度提升。更好的解释是,堆叠的输入通过滑动复制图像,生成一个用于乘法的 kernel_size 矩阵,就好像内核在滑动一样。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-10-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-04
    • 2021-05-26
    相关资源
    最近更新 更多