2025, Oct 31 20:16

Dense в Keras и NumPy: расхождения из-за CPU/GPU и как их устранить

Почему Dense в Keras дает отличие от NumPy: CPU и GPU вызывают микросдвиги ~1e-5. Минимальный пример, объяснение причин и способ заставить результаты совпасть.

Сопоставить простой слой Dense из TensorFlow/Keras с «чистым» прямым проходом на NumPy кажется простым. Перемножаете входную матрицу с матрицей весов, смещение не учитываете — и результаты должны совпасть. На практике же можно увидеть крошечные расхождения порядка 1e-5, что кажется странным, когда единственная видимая операция — умножение матриц.

Минимальный пример, где проявляется расхождение

import numpy as np
import keras
from keras import layers

print("Keras version:", keras.__version__)
print("Backend", keras.backend.backend())

# Build a tiny model
src = layers.Input((2,), name='inp')
dense_out = layers.Dense(5, kernel_initializer='random_normal', use_bias=False, name='dense')(src)
toy_net = keras.Model(inputs=src, outputs=dense_out)

# Random input
feed = np.random.random(size=(5, 2)).astype(np.float32)

# Keras forward pass
y_keras = toy_net.predict(feed)

# Extract the Dense kernel
[weight_mat] = toy_net.layers[1].get_weights()

# NumPy forward pass
y_np = np.matmul(feed, weight_mat)

# Compare
print("Keras result:\n", y_keras)
print("NumPy result:\n", y_np)
print("Same result:", np.allclose(y_keras, y_np))

Что на самом деле вызывает разницу

Наблюдаемая разница возникает не из-за особой математики внутри Dense и не из-за дополнительной скрытой операции. Дело в том, где выполняются вычисления. NumPy работает на CPU. TensorFlow/Keras может выполнять тот же matmul на GPU. Разные аппаратные пути используют разные реализации одной и той же математической операции, поэтому результаты с плавающей запятой слегка различаются. Этого достаточно, чтобы в идентичных по сути вычислениях появлялись небольшие отклонения.

Даже при небольшом входе формы (5, 2) и ядре формы (2, 5) накопление операций с плавающей запятой на разных вычислительных блоках может давать различия порядка 1e-5 на элемент.

Как привести Keras и NumPy к一致ию

Если запустить модель Keras на CPU, в этом случае вы получите те же числа, что и в NumPy. Отключение CUDA принудит TensorFlow/Keras использовать CPU‑ветку и устранит расхождение.

import os
os.environ['CUDA_VISIBLE_DEVICES'] = '-1'

import numpy as np
import keras
from keras import layers

print("Keras version:", keras.__version__)
print("Backend", keras.backend.backend())

src = layers.Input((2,), name='inp')
dense_out = layers.Dense(5, kernel_initializer='random_normal', use_bias=False, name='dense')(src)
toy_net = keras.Model(inputs=src, outputs=dense_out)

feed = np.random.random(size=(5, 2)).astype(np.float32)

y_keras = toy_net.predict(feed)
[weight_mat] = toy_net.layers[1].get_weights()

y_np = np.matmul(feed, weight_mat)

print("Same result:", np.allclose(y_keras, y_np))

Почему это важно практикам

Незначительные численные сдвиги могут распространяться по слоям и влиять на тесты, проверки воспроизводимости или пороги регрессий. Если вы сверяете прямой проход с эталонной реализацией на NumPy, вычислительное устройство под вашим фреймворком глубокого обучения напрямую влияет на побитовое сравнение и сравнение с допуском.

Выводы

Если вам нужна эквивалентность между TensorFlow/Keras и NumPy для линейных слоёв, запускайте оба на CPU или оба на GPU. Когда это невозможно, сравнивайте с допуском и ожидайте микроскопические различия — даже в простом умножении матриц. Понимание того, что расхождение связано с выполнением на CPU против GPU, помогает выбрать правильную среду для отладки, валидации и написания воспроизводимых тестов.

Статья основана на вопросе на StackOverflow от el_grezeq и ответе el_grezeq.