Векторизация Numpy - странная проблема

Я выполняю некоторые векторизованные вычисления, используя numpy. Я исследовал ошибку, которая у меня возникла, и я закончил этой строкой:

(vertices[:,:,:,0]+vertices[:,:,:,1]*256)*4

Ожидалось, что результат будет 100728 для индекса vertices[0,0,17], однако я получаю 35192. Когда я попытался изменить его на 4.0 вместо 4, я получил правильное значение 100728 и, таким образом, исправил свою ошибку.

Я хотел бы понять, почему здесь важна плавающая запятая, особенно потому, что я использую python 3.7, и это умножение, а не деление.

Дополнительная информация:

vertices.shape=(203759, 12, 32, 3)
python==3.7
numpy==1.16.1

Редактировать 1:

  • тип вершин "numpy.uint8"
  • вершины[0, 0, 17] => [94, 98, 63]

Каков тип значений в вершинах?

Noam Peled 21.02.2019 17:18

Кроме того, если бы вы могли предоставить некоторые образцы данных, где это не удается, это было бы полезно. Какие значения в vertices[0,0,17]?

jdehesa 21.02.2019 17:19

тип вершин "numpy.uint8"

Kasparov92 21.02.2019 17:20

вершины [0, 0, 17] равны [94, 98, 63]

Kasparov92 21.02.2019 17:21

как подсказка: 100728% 2^16 = 35192

Aaron 21.02.2019 17:28

@Kasparov92, что ты имеешь ввиду под vertices[0, 0, 17] => [94, 98, 63] ?

fountainhead 21.02.2019 17:32

@Aaron дал вам решение: попробуйте изменить тип на uint16 или uint32.

Noam Peled 21.02.2019 17:32

Я использовал «4.0», и меня это устраивает, я искал объяснение, и @Aaron заставил меня добраться до момента ахааа :D

Kasparov92 21.02.2019 19:25
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
8
43
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Проблема здесь в том, что вы используете слишком маленькие целые числа, и число переполняется и переносится, потому что numpy использует целые числа фиксированной ширины, а не бесконечную точность, как у python int. Numpy будет "продвигать" типом результата на основе входных данных, но не будет продвигать результат в зависимости от того, произошло ли переполнение или нет (это делается до фактического вычисления.

В этом случае, когда вы умножаете: vertices[:,:,:,1]*256 (я буду называть это A), 256 не может содержаться в uint8, поэтому он переходит к следующему более высокому типу: uint16 это позволяет результату умножения содержать правильное значение в этом случае, потому что максимально возможное значение любого элемента в verticies равно 255, поэтому максимально возможное значение равно 255 * 256, что отлично подходит для 16-битного uint.

Затем вы добавляете vertices[:,:,:,0] + A (я буду называть это B). если наибольшее значение A было 255 * 256, а наибольшее значение vertices[:,:,:,0] равно 255 (опять же наибольшее значение uint8), наибольшая сумма двух равна 216-1 (наибольшее значение, которое вы можете хранить в 16-битное целое число без знака). Это все еще хорошо, пока вы не перейдете к последнему умножению.

Когда вы доберетесь до B * 4, numpy снова должен решить, каким должен быть тип возвращаемого значения. Целое число 4 легко помещается в uint16, поэтому numpy не продвигает тип еще выше до uint32 или uint64, потому что он не предотвращает упреждающее переполнение, как описано ранее. Это приводит к тому, что любые произведения умножения, превышающие 216-1, возвращаются по модулю 216.

Если вместо этого вы используете число с плавающей запятой (4. or 4.0), numpy рассматривает это как «более высокий» тип значения, который не может поместиться внутри uint16, поэтому он переводит результат в число с плавающей запятой, которое может вмещать гораздо более высокие числа без переполнения.

Если вы не хотите менять весь массив: verticies на больший dtype, вы можете просто взять результат B и преобразовать его, прежде чем умножать на 4 как таковой: B.astype(np.uint64) * 4. Это позволит вам хранить большие значения много без переполнения (хотя на самом деле это не устраняет проблему, если значение больше 4).

ссылка, на которую вы указали, говорит: «Когда используются как скаляры, так и массивы, тип массива имеет приоритет». Разве это не означает, что в (A * 256)uint8 из A будет иметь приоритет над uint16 скаляра 256? Не уверен, что здесь означает «имеет приоритет».

fountainhead 21.02.2019 18:53

Спасибо за подробное объяснение. Я ценю ваше время, объясняя и указывая на причины (Y)

Kasparov92 21.02.2019 19:24

@fountainhead Я думаю, они имеют в виду, что имеют приоритет в том, как они определяют int, float или другое. В примере показана ситуация, когда число с плавающей запятой может быть приведено к типу int, если это можно сделать точно, что подчеркивает отличие от правил приведения типов c/c++. Это определенно можно было бы объяснить немного яснее.

Aaron 21.02.2019 20:11

Другие вопросы по теме