Предположим, мы берем np.dot
два 'float32'
2D-массива:
res = np.dot(a, b) # see CASE 1
print(list(res[0])) # list shows more digits
[-0.90448684, -1.1708503, 0.907136, 3.5594249, 1.1374011, -1.3826287]
Числа. Кроме того, они могут измениться:
Случай 1 : срезa
np.random.seed(1)
a = np.random.randn(9, 6).astype('float32')
b = np.random.randn(6, 6).astype('float32')
for i in range(1, len(a)):
print(list(np.dot(a[:i], b)[0])) # full shape: (i, 6)
[-0.9044868, -1.1708502, 0.90713596, 3.5594249, 1.1374012, -1.3826287]
[-0.90448684, -1.1708503, 0.9071359, 3.5594249, 1.1374011, -1.3826288]
[-0.90448684, -1.1708503, 0.9071359, 3.5594249, 1.1374011, -1.3826288]
[-0.90448684, -1.1708503, 0.907136, 3.5594249, 1.1374011, -1.3826287]
[-0.90448684, -1.1708503, 0.907136, 3.5594249, 1.1374011, -1.3826287]
[-0.90448684, -1.1708503, 0.907136, 3.5594249, 1.1374011, -1.3826287]
[-0.90448684, -1.1708503, 0.907136, 3.5594249, 1.1374011, -1.3826287]
[-0.90448684, -1.1708503, 0.907136, 3.5594249, 1.1374011, -1.3826287]
Результаты отличаются, даже если напечатанный фрагмент получен из тех же самых умноженных чисел.
Случай 2 : сгладить
a
, взять 1D версию b
, затем нарезать a
:
np.random.seed(1)
a = np.random.randn(9, 6).astype('float32')
b = np.random.randn(1, 6).astype('float32')
for i in range(1, len(a)):
a_flat = np.expand_dims(a[:i].flatten(), -1) # keep 2D
print(list(np.dot(a_flat, b)[0])) # full shape: (i*6, 6)
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]
[-0.3393164, 0.9528787, 1.3627989, 1.5124314, 0.46389243, 1.437775]
СЛУЧАЙ 3 : более сильный контроль; установите все не вовлеченные цели в ноль : добавьте a[1:] = 0
к коду 1. Результат: расхождения сохраняются.
СЛУЧАЙ 4 : проверить индексы, кроме [0]
; Например [0]
, результаты начинают стабилизировать фиксированное количество расширений массива с момента их создания. Вывод
np.random.seed(1)
a = np.random.randn(9, 6).astype('float32')
b = np.random.randn(6, 6).astype('float32')
for j in range(len(a) - 2):
for i in range(1, len(a)):
res = np.dot(a[:i], b)
try: print(list(res[j]))
except: pass
print()
Следовательно, для случая 2D * 2D результаты отличаются - но согласуются для 1D * 1D. Судя по некоторым моим прочтениям, это происходит от 1D-1D с использованием простого сложения, тогда как 2D-2D использует «более красивое», повышающее производительность сложение, которое может быть менее точным (например, парное сложение делает противоположное). Тем не менее, я не могу понять, почему несоответствия исчезают в случае, если 1 раз a
обрезается за установленный «порог»; чем больше a
и b
чем позже этот порог кажется ложным, но он всегда существует.
Все говорили: почему np.dot
неточно (и противоречиво) для массивов ND-ND? Соответствующий Git
Дополнительная информация :
- Среда : ОС Win-10, Python 3.7.4, IDE Spyder 3.3.6, Anaconda 3.0 2019/10
- Процессор : i7-7700HQ 2,8 ГГц
- Numpy v1.16.5
Библиотека возможных виновников : Numpy MKL - также библиотеки BLASS; спасибо Би Рико за внимание
Код стресс-теста : как уже отмечалось, расхождения усугубляются по частоте с большими массивами; если выше не воспроизводится, ниже должно быть (если нет, попробуйте большие тусклые). Мой вывод
np.random.seed(1)
a = (0.01*np.random.randn(9, 9999)).astype('float32') # first multiply then type-cast
b = (0.01*np.random.randn(9999, 6)).astype('float32') # *0.01 to bound mults to < 1
for i in range(1, len(a)):
print(list(np.dot(a[:i], b)[0]))
Серьезность проблемы : показанные несоответствия являются «небольшими», но больше не так при работе в нейронной сети с миллиардами чисел, умноженных за несколько секунд, и триллионами за все время выполнения; Сообщенная точность модели отличается на целые 10 процентов, по этой теме .
Ниже приведен gif массивов, полученных в результате подачи к модели, что в основном a[0]
, по len(a)==1
сравнению с len(a)==32
:
ДРУГИЕ ПЛАТФОРМЫ Результаты, в соответствии с и благодаря тестированию Пола :
Случай 1 воспроизведен (частично) :
- Google Colab VM - Intel Xeon 2.3 G-Hz - Jupyter - Python 3.6.8
- Рабочий стол Win-10 Pro Docker - Intel i7-8700K - ноутбук с jupyter / scipy - Python 3.7.3
- Ubuntu 18.04.2 LTS + Docker - AMD FX-8150 - jupyter / scipy-notebook - Python 3.7.3
Примечание : они дают намного меньшую ошибку, чем показано выше; две записи в первой строке выключены на 1 в младшей значащей цифре из соответствующих записей в других строках.
Случай 1 не воспроизведен :
- Ubuntu 18.04.3 LTS - Intel i7-8700K - IPython 5.5.0 - Python 2.7.15+ и 3.6.8 (2 теста)
- Ubuntu 18.04.3 LTS - Intel i5-3320M - IPython 5.5.0 - Python 2.7.15+
- Ubuntu 18.04.2 LTS - AMD FX-8150 - IPython 5.5.0 - Python 2.7.15rc1
Примечания :
- В связанных Colab ноутбуков и jupyter сред показывают гораздо меньшее расхождение (и только для первых двух строк) , чем наблюдается в моей системе. Кроме того, Случай 2 (пока) никогда не показывал неточность.
- В этом очень ограниченном примере текущая (Dockerized) среда Jupyter более восприимчива, чем среда IPython.
np.show_config()
слишком длинный для публикации, но в итоге: envs IPython основаны на BLAS / LAPACK; Colab основан на OpenBLAS. В IPython Linux envs библиотеки BLAS устанавливаются в системе - в Jupyter и Colab они берутся из / opt / conda / lib
ОБНОВЛЕНИЕ : принятый ответ является точным, но широким и неполным. Вопрос остается открытым для всех, кто может объяснить поведение на уровне кода, а именно, точный алгоритм, используемый им np.dot
, и как он объясняет «непоследовательные несоответствия», наблюдаемые в приведенных выше результатах (также см. Комментарии). Вот несколько прямых реализаций за пределами моего расшифровки: sdot.c - arraytypes.c.src
ndarrays
обычно игнорируют потерю точности чисел. Поскольку для простоты они расположены reduce-sum
вдоль каждой оси, порядок операций может быть не оптимальным ... Обратите внимание, что если вы не возражаете против погрешности точности, вы также можете использоватьfloat64