Численные ошибки в Керасе против Numpy - PullRequest
1 голос
/ 10 марта 2020

Для того, чтобы по-настоящему понять сверточные слои, я переопределил метод forward для одного слоя kev Conv2D в базисе c numpy. Выходы обоих швов практически идентичны, но есть небольшие отличия.

Получение вывода 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, но это не решает проблему. Любые идеи?

Редактировать: Я построил распределение по ошибкам, и это может дать некоторое представление о том, что происходит. Как видно, все ошибки имеют очень сходные значения и делятся на четыре группы Однако эти ошибки не являются точно этими четырьмя значениями, но являются почти всеми уникальными значениями вокруг этих четырех пиков. enter image description here

Я очень заинтересован в том, чтобы сделать мою реализацию точно совпадающей с реализацией keras. К сожалению, ошибки, кажется, возрастают экспоненциально при реализации нескольких слоев Любое понимание очень помогло бы мне!

Ответы [ 3 ]

2 голосов
/ 10 марта 2020

Учитывая, насколько малы различия, я бы сказал, что они являются ошибками округления.
Я рекомендую использовать np.isclose (или math.isclose ), чтобы проверить, является ли число с плавающей точкой "равны".

1 голос
/ 19 марта 2020

Операции с плавающей точкой не коммутируемые. Вот пример:

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 и меняйте его построчно по отношению к вашему коду, пока не увидите разницу.

1 голос
/ 13 марта 2020

Прежде всего, проверьте, используете ли вы padding='same'. Вы, кажется, используете в своей реализации то же самое.

Если вы используете другие типы заполнения, включая значение по умолчанию padding='valid', разница будет.

Другая возможность состоит в том, что вы можете накапливать ошибки из-за тройного l oop небольших сумм.

Вы можете сделать это сразу и посмотреть, станет ли это иначе. Сравните эту реализацию с вашей, например:

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 даже не определены c.


Для любого размера ядра:

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...