Понимание == применительно к массиву NumPy

Я новичок в Python, и я изучаю TensorFlow. В учебнике, использующем набор данных notMNIST, они приводят примерный код, чтобы преобразовать матрицу меток в один-на-n закодированный массив.

Цель состоит в том, чтобы взять массив, состоящий из целых чисел меток 0... 9, и вернуть матрицу, где каждое целое число было преобразовано в один из n закодированных массивов, таких как:

0 -> [1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
1 -> [0, 1, 0, 0, 0, 0, 0, 0, 0, 0]
2 -> [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
...

Код, который они дают для этого:

# Map 0 to [1.0, 0.0, 0.0 ...], 1 to [0.0, 1.0, 0.0 ...]
labels = (np.arange(num_labels) == labels[:,None]).astype(np.float32)

Однако я не понимаю, как этот код делает это вообще. Похоже, он просто генерирует массив целых чисел в диапазоне от 0 до 9, а затем сравнивает это с матрицей меток и преобразует результат в float. Как оператор == приводит к кодировке с одним кодированием?

Ответ 1

Здесь происходит несколько вещей: numpy vector ops, добавление одинарной оси и трансляция.

Во-первых, вы должны уметь видеть, как == делает магию.

Скажем, мы начинаем с простого массива меток. == ведет себя векторизованно, что означает, что мы можем сравнить весь массив со скаляром и получить массив, состоящий из значений каждого элементарного сравнения. Например:

>>> labels = np.array([1,2,0,0,2])
>>> labels == 0
array([False, False,  True,  True, False], dtype=bool)
>>> (labels == 0).astype(np.float32)
array([ 0.,  0.,  1.,  1.,  0.], dtype=float32)

Сначала мы получаем булевский массив, а затем принудительно плаваем: False == 0 в Python и True == 1. Таким образом, мы завершаем массив, который равен 0, где labels не равно 0 и 1, где он есть.

Но нет ничего особенного в сравнении с 0, мы могли бы сравнить с 1 или 2 или 3 вместо аналогичных результатов:

>>> (labels == 2).astype(np.float32)
array([ 0.,  1.,  0.,  0.,  1.], dtype=float32)

Фактически, мы могли бы перебрать все возможные метки и сгенерировать этот массив. Мы могли бы использовать listcomp:

>>> np.array([(labels == i).astype(np.float32) for i in np.arange(3)])
array([[ 0.,  0.,  1.,  1.,  0.],
       [ 1.,  0.,  0.,  0.,  0.],
       [ 0.,  1.,  0.,  0.,  1.]], dtype=float32)

но это действительно не использует numpy. Мы хотим, чтобы каждая возможная метка сравнивалась с каждым элементом, IOW для сравнения

>>> np.arange(3)
array([0, 1, 2])

с

>>> labels
array([1, 2, 0, 0, 2])

И здесь, где приходит волшебство вещания numpy. Прямо сейчас labels является одномерным объектом формы (5,). Если мы сделаем его двумерным объектом формы (5,1), то операция будет "транслироваться" по последней оси, и мы получим результат формы (5,3) с результатами сравнения каждой записи в диапазон с каждым элементом меток.

Сначала мы можем добавить "дополнительную" ось к labels с помощью None (или np.newaxis), изменив ее форму:

>>> labels[:,None]
array([[1],
       [2],
       [0],
       [0],
       [2]])
>>> labels[:,None].shape
(5, 1)

И тогда мы сможем провести сравнение (это транспонирование договоренности, которую мы смотрели ранее, но это не имеет большого значения).

>>> np.arange(3) == labels[:,None]
array([[False,  True, False],
       [False, False,  True],
       [ True, False, False],
       [ True, False, False],
       [False, False,  True]], dtype=bool)
>>> (np.arange(3) == labels[:,None]).astype(np.float32)
array([[ 0.,  1.,  0.],
       [ 0.,  0.,  1.],
       [ 1.,  0.,  0.],
       [ 1.,  0.,  0.],
       [ 0.,  0.,  1.]], dtype=float32)

Вещание в numpy очень мощное и хорошо стоит читать.