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, помогает выбрать правильную среду для отладки, валидации и написания воспроизводимых тестов.