Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений
Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений

В этой статье мы рассмотрим ряд различных упражнений, используя библиотеку NumPy (и сравним с тем, как мы бы реализовали их без неё). В самом конце статьи я приведу ряд упражнений.

t.me/ai_machinelearning_big_data -  моем телеграм канале я публикую актуальные проекты курсы, уроки и примеры с кодом по машинному обучению.

Для этой статьи рекомендуется, чтобы читатель имел средний уровень знаний Python, NumPy, numpy.dtypenumpy.ndarray.strides, и numpy.ndarray.itemsize. Краткое введение в массивы и NumPy см. в разделе ???? Немного предыстории ниже.

Рекомендуемые бесплатные курсы и гайды:

Упражнения расположены по возрастающей сложности. Вот структура каждого из упражнений:

  • Вопрос, показанный в виде диаграммы с вводом массива NumPy

  • Ответ

  • Объяснение

  • Код, в котором присутствует пространство имён as_strided, импортированное из следующего оператора импорта:from numpy.lib.stride_tricks import as_strided

???? Немного предыстории

Как получить доступ к конкретному элементу из блока элементов фиксированного размера, которые (i) размещены рядом друг с другом и (ii) организованы во вложенные подгруппы? Ответ: с помощью шагов. То, что я только что кратко описал, — это N-мерный массив структуры данных NumPy ( ndarray), и мы используем алгоритм, называемый схемой пошагового индексирования , вместе с шагами для её обхода.

Вот 4 коротких момента, которые вы должны знать о массивах NumPy:

1) Элементы в массивах NumPy занимают память. Каждый элемент в массиве NumPy равномерно занимает n байтов. Например, каждый элемент массива с типом данных np.int64 занимает 8 байт.

2) Элементы в массивах NumPy хранятся в памяти непрерывно. Это означает, что они хранятся рядом (в отличие от элементов в списках Python).

3) Существует часть информации, называемая формой, которую вы, вероятно, уже знаете, которая определяет, насколько велик этот массив для каждого измерения.

4) Существует ещё одна часть информации, называемая шагами, которая указывает количество байтов, на которое нужно перейти, чтобы достичь следующего значения в измерении.

С помощью этих 4 частей информации местоположение элемента в памяти можно найти с помощью линейной комбинации измерения с шагами в качестве коэффициентов. Для более подробного объяснения обратитесь к моим ссылкам ниже .

1D упражнения

1) Нарезка первых 3 элементов

Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений
Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений

Ответ

strides = (1,)
shape = (3,)

???? ОбъяснениеСмежные элементы в выходных данных (т. е. 1 → 2, 2 → 3) изначально находились на расстоянии байта друг от друга (= 1 элемент на расстоянии × 1 байт) во входных данных. Форма этого измерения равна .

Код

>>> x = np.asarray(range(1,26), np.int8).reshape(5,5)
>>> as_strided(x, shape=(3,), strides=(1,))
array([1, 2, 3], dtype=int8)

Сравнение

>>> x[0,:3]
array([1, 2, 3], dtype=int8)

2) Нарезка первых 8 элементов

Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений
Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений

Ответ

strides = (1,)
shape = (8,)

???? ОбъяснениеСмежные элементы в выходных данных (например, 1 → 2, 2 → 3, 6 → 7) изначально находились на расстоянии байта друг от друга (= 1 элемент на расстоянии × 1 байт) во входных данных. Форма этого измерения равна .

Код

>>> x = np.asarray(range(1,26), np.int8).reshape(5,5)
>>> as_strided(x, shape=(8,), strides=(1,))
array([1, 2, 3, 4, 5, 6, 7, 8], dtype=int8)

Сравнение

>>> x[0,:8]
array([1, 2, 3, 4, 5, 6, 7, 8], dtype=int8)

3) Сглаживание двумерного массива

Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений
Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений

Ответ

strides = (2,)
shape = (25,)

???? ОбъяснениеСмежные элементы на выходе (например, 1 → 2, 2 → 3, 23 → 24, 24 → 25) изначально находились на расстоянии байта друг от друга (= 1 элемент на расстоянии × 2 байта) на входе. Форма этого измерения равна 25 .

Код

>>> x = np.asarray(range(1,26), np.int16).reshape(5,5)
>>> as_strided(x, shape=(25,), strides=(2,))
array([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25], dtype=int16)

Сравнение

>>> x.ravel()
array([ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25], dtype=int16)

4) Пропуск всех остальных элементов

Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений
Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений

Ответ

strides = (2,)
shape = (3,)

???? ОбъяснениеСмежные элементы в выходных данных (т.е. 1 → 3, 3 → 5) изначально находились на расстоянии байта друг от друга (= 2 элемента на расстоянии × 1 байт) во входных данных. Форма этого измерения равна .

Код

>>> x = np.asarray(range(1,26), np.int8).reshape(5,5)
>>> as_strided(x, shape=(3,), strides=(2,))
array([1, 3, 5], dtype=int8)

Сравнение

>>> x[0,::2]
array([1, 3, 5], dtype=int8)

5) Нарезка первого столбца

Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений
Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений

Ответ

strides = (40,)
shape = (4,)

???? ОбъяснениеСмежные элементы в выходных данных (т. е. 1 → 6, 6 → 11, 11 → 16) изначально находились на расстоянии 40 байт друг от друга (= 5 элементов на расстоянии × 8 байтов) во входных данных. Форма этого измерения равна .

Код

>>> x = np.asarray(range(1,26), np.int64).reshape(5,5)
>>> as_strided(x, shape=(4,), strides=(40,))
array([ 1,  6, 11, 16])

Сравнение

>>> x[:4,0]
array([ 1,  6, 11, 16])

6) Нарезка по диагонали

Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений
Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений

Ответ

strides = (48,)
shape = ( 5,)

???? ОбъяснениеСмежные элементы в выходных данных (т. е. 1 → 7, 7 → 13, 13 → 19, 19 → 25) изначально находились на расстоянии 48 байт друг от друга (= 6 элементов на расстоянии × 8 байтов) во входных данных. Форма этого измерения равна .

Код

>>> x = np.asarray(range(1,26), np.int64).reshape(5,5)
>>> as_strided(x, shape=(5,), strides=(48,))
array([ 1,  7, 13, 19, 25])

Сравнение

>>> x.diagonal()
array([ 1,  7, 13, 19, 25])

7) Повторение первого элемента

Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений
Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений

Ответ

strides = (0,)
shape = (5,)

???? ОбъяснениеСмежные элементы в выходных данных (т. е. 1 → 1) изначально находились на расстоянии 0 байтов друг от друга (= 0 элементов на расстоянии × 8 байтов) во входных данных. Форма этого измерения равна .

Код

>>> x = np.asarray(range(1,26), np.int64).reshape(5,5)
>>> as_strided(x, shape=(5,), strides=(0,))
array([ 1, 1, 1, 1, 1])

Сравнение

>>> np.broadcast_to(x[0,0], (5,))
array([1, 1, 1, 1, 1])

2D упражнения

8) Простая 2D-нарезка

Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений
Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений

Ответ

strides = (40,8)
shape = ( 3,4)

???? Объяснение

Выходное измерение слева направо (ось = -1):Смежные элементы в выходных данных (например, 1 → 2, 2 → 3, 8 → 9) изначально находились на расстоянии байтов друг от друга (= 1 элемент на расстоянии × 8 байтов) во входных данных. Форма этого измерения равна .

Выходное измерение слева направо (ось = -2):Смежные элементы на выходе (например, 1 → 6, 2 → 7, 9 → 14) изначально находились на расстоянии 40 байт друг от друга (= 5 элементов на расстоянии × 8 байтов) на входе. Форма этого измерения равна .

Код

>>> x = np.asarray(range(1,26), np.int64).reshape(5,5)
>>> as_strided(x, shape=(3,4), strides=(40,8))
array([[ 1,  2,  3,  4],
[ 6,  7,  8,  9],
[11, 12, 13, 14]])

Сравнение

>>> x[:3,:4]
array([[ 1,  2,  3,  4],
[ 6,  7,  8,  9],
[11, 12, 13, 14]])

9) Нарезка зигзагом

Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений
Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений

Ответ

strides = (48,8)
shape = ( 4,2)

???? Объяснение

Выходное измерение слева направо (ось = -1):Смежные элементы на выходе (например, 1 → 2, 7 → 8, 13 → 14, 19 → 20) изначально находились на расстоянии байтов друг от друга (= 1 элемент на расстоянии × 8 байтов) во входных данных. Форма этого измерения равна .

Выходное измерение слева направо (ось = -2):Смежные элементы на выходе (например, 1 → 7, 2 → 8, 13 → 19) изначально находились на расстоянии 48 байт друг от друга (= 6 элементов на расстоянии × 8 байтов) на входе. Форма этого измерения равна .

Код

>>> x = np.asarray(range(1,26), np.int64).reshape(5,5)
>>> as_strided(x, shape=(4,2), strides=(48,8))
array([[ 1,  2],
[ 7,  8],
[13, 14],
[19, 20]])

Сравнение

>>> # this may not be achieved concisely

10) Разреженная нарезка

Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений
Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений

Ответ

strides = (80,16)
shape = ( 3, 3)

???? Объяснение

Выходное измерение слева направо (ось = -1):Смежные элементы в выходных данных (например, 1 → 3, 21 → 23, 13 → 15) изначально находились на расстоянии 16 байт друг от друга (= 2 элемента на расстоянии × 8 байтов) во входных данных. Форма этого измерения равна .

Выходное измерение слева направо (ось = -2):Смежные элементы в выходных данных (например, 1 → 11, 13 → 23, 15 → 25) изначально находились на расстоянии 80 байт друг от друга (= 10 элементов на расстоянии × 8 байтов) во входных данных. Форма этого измерения равна .

Код

>>> x = np.asarray(range(1,26), np.int64).reshape(5,5)
>>> as_strided(x, shape=(3,3), strides=(80,16))
array([[ 1,  3,  5],
[11, 13, 15],
[21, 23, 25]])

Сравнение

>>> x[::2,::2]
array([[ 1,  3,  5],
[11, 13, 15],
[21, 23, 25]])

11) Транспонирование 2D-массива

Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений
Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений

Ответ

strides = (1,5)
shape = (3,3)

???? Объяснение

Выходное измерение слева направо (ось = -1):Смежные элементы в выходных данных (например, 1 → 6, 7 → 12, 3 → 8) изначально находились на расстоянии байтов друг от друга (= 5 элементов на расстоянии × 1 байт) во входных данных. Форма этого измерения равна .

Размер вывода сверху направо (ось = -2):Смежные элементы в выходных данных (например, 1 → 2, 2 → 3, 11 → 12) изначально находились на расстоянии 1 байта (= 1 элемент на расстоянии × 1 байт) во входных данных. Форма этого измерения равна .

Код

>>> x = np.asarray(range(1,26), np.int8).reshape(5,5)
>>> as_strided(x, shape=(3,3), strides=(1,5))
array([[ 1,  6, 11],
[ 2,  7, 12],
[ 3,  8, 13]], dtype=int8)

Сравнение

>>> x[:3,:3].T
array([[ 1,  6, 11],
[ 2,  7, 12],
[ 3,  8, 13]], dtype=int8)

12) Повтор первого столбца 4 раза

Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений
Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений

Ответ

strides = (20,0)
shape = ( 5,4)

???? Объяснение

Выходное измерение слева направо (ось = -1):Смежные элементы в выходных данных (например, 1 → 1, 6 → 6, 16 → 16) изначально находились на расстоянии байтов друг от друга (= 0 элементов на расстоянии × 4 байта) во входных данных. Форма этого измерения равна .

Выходное измерение сверху вниз (ось = -2):Смежные элементы на выходе (например, 1 → 6, 6 → 11, 11 → 16) изначально находились на расстоянии 20 байт друг от друга (= 5 элементов на расстоянии × 4 байта) на входе. Форма этого измерения равна .

Код

>>> x = np.asarray(range(1,26), np.int32).reshape(5,5)
>>> as_strided(x, shape=(5,4), strides=(20,0))
array([[ 1,  1,  1,  1],
[ 6,  6,  6,  6],
[11, 11, 11, 11],
[16, 16, 16, 16],
[21, 21, 21, 21]], dtype=int32)

Сравнение

>>> np.broadcast_to(x[:,0,None], (5,4))
array([[ 1,  1,  1,  1],
[ 6,  6,  6,  6],
[11, 11, 11, 11],
[16, 16, 16, 16],
[21, 21, 21, 21]], dtype=int32)

13) Изменение формы массива с 1D на 2D

Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений
Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений

Ответ

strides = (24,8)
shape = ( 4,3)

???? Объяснение

Выходное измерение слева направо (ось = -1):Смежные элементы на выходе (например, 1 → 2, 2 → 3, 7 → 8, 11 → 12) изначально находились на расстоянии байтов друг от друга (= 1 элемент на расстоянии × 8 байтов) во входных данных. Форма этого измерения равна .

Выходное измерение сверху вниз (ось = -2):Смежные элементы в выходных данных (например, 1 → 4, 4 → 7, 7 → 10) изначально находились на расстоянии 24 байта друг от друга (= 3 элемента на расстоянии × 8 байтов) во входных данных. Форма этого измерения равна .

Код

>>> x = np.asarray(range(1,13), np.int64)
>>> as_strided(x, shape=(4,3), strides=(24,8))
array([[ 1,  2,  3],
[ 4,  5,  6],
[ 7,  8,  9],
[10, 11, 12]])

Сравнение

>>> x.reshape(4,3)
array([[ 1,  2,  3],
[ 4,  5,  6],
[ 7,  8,  9],
[10, 11, 12]])

14) Сдвиг одномерного окна

Адаптировано из сообщения StackOverflow [ 1 ]. Аналогично [ 2 ] и [ 3 ].

Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений
Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений

Ответ

strides = (1,1)
shape = (8,3)

???? Объяснение

Выходное измерение слева направо (ось = -1):Смежные элементы в выходных данных (например, 1 → 2, 3 → 4, 4 → 5) изначально находились на расстоянии байта друг от друга (= 1 элемент на расстоянии × 1 байт) во входных данных. Форма этого измерения равна .

Выходное измерение сверху вниз (ось = -2):Смежные элементы в выходных данных (т. е. 1 → 2, 2 → 3, 4 → 5, …, 7 → 8) изначально находились на расстоянии байта друг от друга (= 1 элемент на расстоянии × 1 байт) во входных данных. Форма этого измерения равна .

Код

>>> x = np.asarray(range(1,26), np.int8)
>>> as_strided(x, shape=(8,3), strides=(1,1))
array([[ 1,  2,  3],
[ 2,  3,  4],
[ 3,  4,  5],
[ 4,  5,  6],
[ 5,  6,  7],
[ 6,  7,  8],
[ 7,  8,  9],
[ 8,  9, 10]], dtype=int8)

Сравнение

>>> # this may not be achieved concisely

15) Сдвиг 2D-окна, затем сглаживание

Вопрос взят из сообщения StackOverflow [ 4 ].

Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений
Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений

Ответ

strides = (2,1)
shape = (4,6)

???? Объяснение

Выходное измерение слева направо (ось = -1):Смежные элементы в выходных данных (например, 0 → 1, 1 → 10, 31 → 40) изначально находились на расстоянии байта друг от друга (= 1 элемент на расстоянии × 1 байт) во входных данных. Форма этого измерения равна .

Выходное измерение сверху вниз (ось = -2):Соседние элементы на выходе (например, 0 → 10, 10 → 20, 41 → 51) изначально находились на расстоянии байта друг от друга (= 2 элемента на расстоянии × 1 байт) на входе. Форма этого измерения равна .

Код

>>> x = np.asarray(
...         [0,1,10,11,20,21,30,31,40,41,50,51],
...         np.int8).reshape(6,2)
>>> as_strided(x, shape=(4,6), strides=(2,1))
array([[ 0,  1, 10, 11, 20, 21],
[10, 11, 20, 21, 30, 31],
[20, 21, 30, 31, 40, 41],
[30, 31, 40, 41, 50, 51]], dtype=int8)

Сравнение

>>> # this may not be achieved concisely

16) Сворачивание оси из 3D-массива

Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений
Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений

Ответ

strides = (4,1)
shape = (3,4)

???? Объяснение

Выходное измерение слева направо (ось = -1):Смежные элементы в выходных данных (например, 1 → 2, 3 → 4, 4 → 5) изначально находились на расстоянии байта друг от друга (= 1 элемент на расстоянии × 1 байт) во входных данных. Форма этого измерения равна .

Выходное измерение сверху вниз (ось = -2):Смежные элементы в выходных данных (т.е. 1 → 5, 5 → 9, 2 → 6, 6 → 10) изначально находились на расстоянии байта друг от друга (= 4 элемента на расстоянии × 1 байт) во входных данных. Форма этого измерения равна .

Код

>>> x = np.asarray(range(1,13), np.int8).reshape(3,2,2)
>>> as_strided(x, shape=(3,4), strides=(4,1))
array([[ 1,  2,  3,  4],
[ 5,  6,  7,  8],
[ 9, 10, 11, 12]], dtype=int8)

Сравнение

>>> x.reshape(3,4)
array([[ 1,  2,  3,  4],
[ 5,  6,  7,  8],
[ 9, 10, 11, 12]], dtype=int8)

3D упражнения

17) 2 уголка

Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений
Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений

Ответ

strides = (30,10, 2)
shape = ( 2, 2, 2)

???? Объяснение

Выходное измерение слева направо (ось = -1):Смежные элементы в выходных данных (т.е. 1 → 2, 6 → 7, 16 → 17, 21 → 22) изначально находились на расстоянии байта друг от друга (= 1 элемент на расстоянии × 2 байта) во входных данных. Форма этого измерения равна .

Выходное измерение сверху вниз (ось = -2):Смежные элементы на выходе (т.е. 1 → 6, 2 → 7, 16 → 21, 17 → 22) изначально находились на расстоянии 10 байт друг от друга (= 5 элементов на расстоянии × 2 байта) на входе. Форма этого измерения равна .

Выходное измерение между коробками (ось = -3):Смежные элементы в выходных данных (т. е. 1 → 16, 2 → 17, 6 → 21, 7 → 22) изначально находились на расстоянии 30 байт друг от друга (= 15 элементов на расстоянии × 2 байта) во входных данных. Форма этого измерения равна .

Код

>>> x = np.asarray(range(1,26), np.int16).reshape(5,5)
>>> as_strided(x, shape=(2,2,2), strides=(30,10,2))
array([[[ 1,  2],
[ 6,  7]],
[[16, 17],
[21, 22]]], dtype=int16)

Сравнение

>>> # this may not be achieved concisely

18) Нарезка в шахматном порядке

Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений
Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений

Ответ

strides = (10,6,1)
shape = ( 2,2,3)

???? Объяснение

Измерение внутреннего поля вывода слева направо (ось = -1):Смежные элементы в выходных данных (например, 1 → 2, 2 → 3, 17 → 18) изначально находились на расстоянии байта друг от друга (= 1 элемент на расстоянии × 1 байт) во входных данных. Форма этого измерения равна .

Измерение внутри блока вывода сверху вниз (ось = -2):Смежные элементы в выходных данных (например, 1 → 7, 11 → 17, 12 → 18) изначально находились на расстоянии байтов друг от друга (= 6 элементов на расстоянии × 1 байт) во входных данных. Форма этого измерения равна .

Размер выходного поля слева направо (ось = -3):Смежные элементы в выходных данных (например, 1 → 11, 8 → 18, 9 → 19) изначально находились на расстоянии 10 байт друг от друга (= 10 элементов на расстоянии × 1 байт) во входных данных. Форма этого измерения равна .

Код

>>> x = np.asarray(range(1,26), np.int8).reshape(5,5)
>>> as_strided(x, shape=(2,2,3), strides=(10,6,1))
array([[[ 1,  2,  3],
[ 7,  8,  9]],
[[11, 12, 13],
[17, 18, 19]]], dtype=int8)

Сравнение

>>> # this may not be achieved concisely

19) Повтор двумерного массива

Этот вопрос взят из сообщения StackOverflow здесь [ 5 ].

Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений
Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений

Ответ

strides = (0,10,2)
shape = (3, 2,4)

???? Объяснение

Измерение внутреннего поля вывода слева направо (ось = -1):Соседние элементы на выходе (например, 1 → 2, 2 → 3, 6 → 7) изначально находились на расстоянии байта друг от друга (= 1 элемент на расстоянии × 2 байта) на входе. Форма этого измерения равна .

Измерение внутри блока вывода сверху вниз (ось = -2):Соседние элементы на выходе (например, 1 → 6, 2 → 7, 3 → 8) изначально находились на расстоянии 10 байт друг от друга (= 5 элементов на расстоянии × 2 байта) на входе. Форма этого измерения равна .

Размер выходного поля слева направо (ось = -3):Смежные элементы в выходных данных (например, 1 → 1, 3 → 3, 7 → 7) изначально находились на расстоянии байтов друг от друга (= 0 элементов на расстоянии × 2 байта) во входных данных. Форма этого измерения равна .

Код

>>> x = np.asarray(range(1,26), np.int16).reshape(5,5)
>>> as_strided(x, shape=(3,2,4), strides=(0,10,2))
array([[[1, 2, 3, 4],
[6, 7, 8, 9]],
[[1, 2, 3, 4],
[6, 7, 8, 9]],
[[1, 2, 3, 4],
[6, 7, 8, 9]]], dtype=int16)

Сравнение

>>> np.broadcast_to(x[0:2, 0:-1], (3, 2, 4))

20) 3D транспонирование

Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений
Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений

Ответ

strides = (16,4,8)
shape = ( 3,2,2)

???? Объяснение

Выходное измерение слева направо (ось = -1):Соседние элементы на выходе (например, 1 → 3, 2 → 4, 10 → 12) изначально находились на расстоянии байтов друг от друга (= 2 элемента на расстоянии × 4 байта) на входе. Форма этого измерения равна .

Выходное измерение сверху вниз (ось = -2):Смежные элементы на выходе (например, 1 → 2, 3 → 4, 9 → 10, 11 → 12) изначально находились на расстоянии байта друг от друга (= 1 элемент на расстоянии × 4 байта) на входе. Форма этого измерения равна .

Размер выходного поля слева направо (ось = -3):Соседние элементы на выходе (например, 1 → 5, 5 → 9) изначально находились на расстоянии 16 байтов друг от друга (= 4 элемента на расстоянии × 4 байта) на входе. Форма этого измерения равна .

Код

>>> x = np.asarray(range(1,13), np.int32).reshape(3,2,2)
>>> as_strided(x, shape=(3,2,2), strides=(16,4,8))
array([[[ 1,  3],
[ 2,  4]],
[[ 5,  7],
[ 6,  8]],
[[ 9, 11],
[10, 12]]], dtype=int32)

Сравнение

>>> np.swapaxes(x,1,2)
array([[[ 1,  3],
[ 2,  4]],
[[ 5,  7],
[ 6,  8]],
[[ 9, 11],
[10, 12]]], dtype=int32)

21) Сдвиг 2D-окна

Вопрос адаптирован из конференции SciPy 2008 [ 6 ].

Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений
Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений

Ответ

strides = (40,40,8)
shape = ( 3, 2,5)

???? Объяснение

Измерение внутреннего поля вывода слева направо (ось = -1):Смежные элементы в выходных данных (например, 1 → 2, 12 → 13, 16 → 17) изначально находились на расстоянии байтов друг от друга (= 1 элемент на расстоянии × 8 байтов) во входных данных. Форма этого измерения равна .

Измерение внутри блока вывода сверху вниз (ось = -2):Смежные элементы в выходных данных (например, 1 → 6, 8 → 13, 11 → 16) изначально находились на расстоянии 40 байт друг от друга (= 5 элементов на расстоянии × 8 байтов) во входных данных. Форма этого измерения равна .

Размер выходного поля слева направо (ось = -3):Смежные элементы в выходных данных (например, 9 → 14, 14 → 19) изначально находились на расстоянии 40 байт друг от друга (= 5 элементов на расстоянии × 8 байтов) во входных данных. Форма этого измерения равна .

Код

>>> x = np.asarray(range(1,21), np.int64).reshape(4,5)
>>> as_strided(x, shape=(3,2,5), strides=(40,40,8))
array([[[ 1,  2,  3,  4,  5],
[ 6,  7,  8,  9, 10]],
[[ 6,  7,  8,  9, 10],
[11, 12, 13, 14, 15]],
[[11, 12, 13, 14, 15],
[16, 17, 18, 19, 20]]])

Сравнение

>>> # this may not be achieved concisely

22) Изменение формы массива с 1D на 3D

Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений
Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений

Ответ

strides = (6,3,1)
shape = (2,2,3)

???? Объяснение

Измерение внутреннего поля вывода слева направо (ось = -1):Смежные элементы в выходных данных (например, 1 → 2, 5 → 6, 7 → 8, 10 → 11) изначально находились на расстоянии байта друг от друга (= 1 элемент на расстоянии × 1 байт) во входных данных. Форма этого измерения равна .

Измерение внутри блока вывода сверху вниз (ось = -2):Соседние элементы на выходе (например, 1 → 4, 2 → 5, 8 → 11) изначально находились на расстоянии байта друг от друга (= 3 элемента на расстоянии × 1 байт) на входе. Форма этого измерения равна .

Размер выходного поля слева направо (ось = -3):Смежные элементы в выходных данных (например, 1 → 7, 2 → 8, 3 → 9) изначально находились на расстоянии байтов друг от друга (= 6 элементов на расстоянии × 1 байт) во входных данных. Форма этого измерения равна .

Код

>>> x = np.asarray(range(1,13), np.int8)
>>> as_strided(x, shape=(2,2,3), strides=(6,3,1))
array([[[ 1,  2,  3],
[ 4,  5,  6]],
[[ 7,  8,  9],
[10, 11, 12]]], dtype=int8)

Сравнение

>>> x.reshape(2,2,3)array([[[ 1,  2,  3],        [ 4,  5,  6]],       [[ 7,  8,  9],        [10, 11, 12]]], dtype=int8)

4D упражнения

23) Сдвиг 2D рецептивного поля

Адаптировано из публикации StackOverflow о 2D-свертке здесь [ 7 ].

Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений
Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений

Ответ

strides = (10,2,5,1)
shape = ( 2,2,3,3)

???? Объяснение

Измерение внутреннего поля вывода слева направо (ось = -1):Смежные элементы в выходных данных (например, 1 → 2, 17 → 18, 24 → 25) изначально находились на расстоянии байта друг от друга (= 1 элемент на расстоянии × 1 байт) во входных данных. Форма этого измерения равна .

Измерение внутри блока вывода сверху вниз (ось = -2):Смежные элементы в выходных данных (например, 1 → 6, 2 → 7, 3 → 8) изначально находились на расстоянии байтов друг от друга (= 5 элементов на расстоянии × 1 байт) во входных данных. Форма этого измерения равна .

Размер выходного поля слева направо (ось = -3):Смежные элементы в выводе (например, 1 → 3, 6 → 8, 21 → 23, 23 → 25) изначально находились на расстоянии байта друг от друга (= 2 элемента на расстоянии × 1 байт) во входных данных. Форма этого измерения равна .

Расстояние между верхним и нижним прямоугольниками вывода (ось = -4):Смежные элементы на выходе (например, 1 → 11, 2 → 12, 15 → 25) изначально находились на расстоянии 10 байт друг от друга (= 10 элементов на расстоянии × 1 байт) на входе. Форма этого измерения равна .

Код

>>> x = np.asarray(range(1,26), np.int8).reshape(5,5)
>>> as_strided(x, shape=(2,2,3,3), strides=(10,2,5,1))
array([[[[ 1,  2,  3],
[ 6,  7,  8],
[11, 12, 13]],
[[ 3,  4,  5],
[ 8,  9, 10],
[13, 14, 15]]],
[[[11, 12, 13],
[16, 17, 18],
[21, 22, 23]],
[[13, 14, 15],
[18, 19, 20],
[23, 24, 25]]]], dtype=int8)

Сравнение

>>> # this may not be achieved concisely

24) Повтор трёхмерного тензора

Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений
Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений

Ответ

strides = (48, 0,24, 8)
shape = ( 2, 2, 2, 3)

???? Объяснение

Измерение внутреннего поля вывода слева направо (ось = -1):Смежные элементы в выходных данных (например, 1 → 2, 2 → 3, 4 → 5) изначально находились на расстоянии байтов друг от друга (= 1 элемент на расстоянии × 8 байтов) во входных данных. Форма этого измерения равна .

Измерение внутри блока вывода сверху вниз (ось = -2):Соседние элементы на выходе (например, 1 → 4, 7 → 10, 8 → 11) изначально находились на расстоянии 24 байта друг от друга (= 3 элемента на расстоянии × 8 байтов) на входе. Форма этого измерения равна .

Размер выходного поля слева направо (ось = -3):Смежные элементы в выходных данных (например, 1 → 1, 10 → 10, 12 → 12) изначально находились на расстоянии байтов друг от друга (= 0 элементов на расстоянии × 8 байтов) во входных данных. Форма этого измерения равна .

Расстояние между верхним и нижним прямоугольниками вывода (ось = -4):Смежные элементы в выходных данных (например, 1 → 7, 2 → 8, 3 → 9) изначально находились на расстоянии 48 байт друг от друга (= 6 элементов на расстоянии × 8 байтов) во входных данных. Форма этого измерения равна .

Код

>>> x = np.asarray(range(1,13), np.int64).reshape(2,2,3)
>>> as_strided(x, shape=(2,2,2,3), strides=(48,0,24,8))
array([[[[ 1,  2,  3],
[ 4,  5,  6]],
[[ 1,  2,  3],
[ 4,  5,  6]]],
[[[ 7,  8,  9],
[10, 11, 12]],
[[ 7,  8,  9],
[10, 11, 12]]]])

Сравнение

>>> np.broadcast_to(x,(2,2,2,3)).swapaxes(0,1)
array([[[[ 1,  2,  3],
[ 4,  5,  6]],
[[ 1,  2,  3],
[ 4,  5,  6]]],
[[[ 7,  8,  9],
[10, 11, 12]],
[[ 7,  8,  9],
[10, 11, 12]]]])

25) Изменение формы массива с 1D на 4D

Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений
Продвинутый NumPy: оттачивайте навыки с помощью 25 иллюстрированных упражнений

Ответ

strides = (64,32,16, 8)
shape = ( 2, 2, 2, 2)

???? Объяснение

Измерение внутреннего поля вывода слева направо (ось = -1):Смежные элементы в выходных данных (например, 1 → 2, 2 → 3, 4 → 5) изначально находились на расстоянии байтов друг от друга (= 1 элемент на расстоянии × 8 байтов) во входных данных. Форма этого измерения равна .

Измерение внутри блока вывода сверху вниз (ось = -2):Смежные элементы в выходных данных (например, 1 → 4, 7 → 10, 8 → 11) изначально находились на расстоянии 16 байтов друг от друга (= 2 элемента на расстоянии × 8 байтов) во входных данных. Форма этого измерения равна .

Размер выходного поля слева направо (ось = -3):Смежные элементы на выходе (например, 1 → 1, 10 → 10, 12 → 12) изначально находились на расстоянии 32 байта друг от друга (= 4 элемента на расстоянии × 8 байтов) на входе. Форма этого измерения равна .

Расстояние между верхним и нижним прямоугольниками вывода (ось = -4):Смежные элементы в выходных данных (например, 1 → 7, 2 → 8, 3 → 9) изначально находились на расстоянии 64 байта друг от друга (= 8 элементов на расстоянии × 8 байтов) во входных данных. Форма этого измерения равна .

Код

>>> x = np.asarray(range(1,17), np.int64)
>>> as_strided(x, shape=(2,2,2,2), strides=(64,32,16,8))
array([[[[ 1,  2],
[ 3,  4]],
[[ 5,  6],
[ 7,  8]]],
[[[ 9, 10],
[11, 12]],
[[13, 14],
[15, 16]]]])

Сравнение

>>> x.reshape(2,2,2,2)
array([[[[ 1,  2],
[ 3,  4]],
[[ 5,  6],
[ 7,  8]]],
[[[ 9, 10],
[11, 12]],
[[13, 14],
[15, 16]]]])

⚠️ В то время как трюки с шагами дают вам больше контроля над результирующим представлением NumPy, API не является безопасным для памяти — всё может стать довольно неприятным, если вы неправильно рассчитаете размер элемента (честно говоря, я думаю, что этот API не должен позволять клиентскому коду иметь дело с размером элемента, поскольку я не видел никаких преимуществ в раскрытии этого) или формы или существующих шагов, возвращая данные, которые на самом деле не являются исходным массивом, который вы создали, а из другого массива, который вы, вероятно, определили несколькими строками назад ????. Это понятие известно как переполнение буфера , и с этим нетрудно столкнуться.

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

Что выведет код?

Задача 1

import numpy as np
x = np.array([1, 4, 9, 16])
print(np.mean(x))

Задача 2

import numpy as np

data = np.array([2, 7, 3, 1])

bins = [0, 5, 10]

hist, _ = np.histogram(data, bins)

print(*hist)

#data - набор данных

#bins - набор интервалов, задаваемых граничными значениями

Задача 3

Что выведет код?
import numpy as np
x = np.array([1., 2., np.nan])
print(np.nanstd(x))

Задача 4

import numpy as np
x = np.fmin([1, -5, 6], [0, np.nan, -1])
print(*x)

Задача 5

mport numpy as np
# Данные: каждая строка соответствует корзине для покупок конкретного покупателя
# строка = [товар 1, товар 2, товар 3]
# значение 1 означает, что товар был куплен
basket = np.array([[1, 1, 0],
                   [0, 0, 1],
                   [1, 0, 0],
                   [1, 1, 1],
                   [1, 1, 0]])


copurchases = [(i, j, np.sum(basket[:, i] + basket[:, j] == 2)) for i in range(3) for j in range(i+1, 3)]
result = max(copurchases, key=lambda x:x[2])
# Первые два значения кортежа result - индексы товаров-столбцов. Третье - число раз, когда они покупались вместе.
print(result)

Задача 6

Задача регрессии. Площади квартир соответствует некая их стоимость. Вычислите предположительную стоимость квартиры площадью 30 кв м.
from sklearn.neighbors import KNeighborsRegressor
import numpy as np
X = np.array([[35, 30000], [45, 45000], [40, 50000],
 [35, 35000], [25, 32500], [40, 40000]])
KNN = KNeighborsRegressor(n_neighbors=3).fit(X[:,0].reshape(-1,1), X[:,1])
res = KNN.predict([[30]])
print(int(res[0]))

Задача 7

Массив данных basket содержит по одной строке для каждого покупателя
и по столбцу для каждого товара. Значение 1 в ячейке (i,j) означает, что покупатель i купил товар j.
Дан код:
import numpy as np
basket = np.array([[1, 1, 1, 1], [1, 1, 1, 0]])
co_purchases = np.sum(np.all(basket[:,2:], axis = 1)) / basket.shape[0]
print(co_purchases)

Задача 8

Что выведет код?
import numpy as np
x = np.fmin([1, -5, 6], [0, np.nan, -1])
print(*x)

Задача 9

import numpy as np
a = np.array([1, 2, np.nan, np.nan])
print(np.max(a),  np.nanmax(a))

Задача 10

import numpy as np
basket = np.array([[1, 1, 1, 1], [1, 1, 1, 0]])
x = np.all(basket[:,2:], axis = 1)
print(*x)

На этом все, спасибо , за прочтение, пишите свои ответы в комментариях

Ресурсы

Numpy полный бесплатный курс (YouTube)

N-мерный массив (ndarray) (numpy.org)

5 лучших функций создания массивов в Numpy для начинающих

https://uproger.com/prodvinutyj-numpy-ottachivajte-tryuki-s-shagami/

Расширенный NumPy (scipy-lectures.org)

Иллюстрированное руководство по форме и шагам (ajcr.net)

Использование трюков с шагом с NumPy (ipython-books.github.io)

[1] https://stackoverflow.com/questions/40084931/taking-subarrays-from-numpy-array-with-given-stride-stepsize

[2] https://stackoverflow.com/questions/4923617/efficient-numpy-2d-array-construction-from-1d-array

[3] https://stackoverflow.com/questions/47483579/how-to-use-numpy-as-strided-from-np-stride-tricks-correctly

[4] https://stackoverflow.com/questions/15722324/sliding-window-of-m-by-n-shape-numpy-ndarray

[5] https://stackoverflow.com/questions/23695851/python-repeating-numpy-array-without-replicating-data

[6] https://mentat.za.net/numpy/numpy_advanced_slides/

[7] https://stackoverflow.com/questions/43086557/convolve2d-just-by-using-numpy

Комментарии (11)


  1. adeshere
    08.01.2024 07:09
    +3

    Спасибо за статью! Очень помогает не витать в облаках. Я сам работаю на фортране, но краем уха слышал много хорошего про NumPy. Но глянув на этот синтаксис.... Неужели мне действительно надо каждый раз вспоминать, как мой массив размещается в памяти, чтобы сделать тривиальную операцию над каким-то хитрым сечением? А если у меня массив пятимерный? Или понадобилось поменять тип данных с 8- на 16-байтовый?!

    Нет уж, спасибо ;-) Я лучше как-нибудь по-старинке, через диапазоны индексов ;-) А все остальное пускай компилятор оптимизирует ;-) Чтобы у меня самое большее про порядок индексов спрашивали (для тех алгоритмов, где производительность от этого порядка зависит) ;-)


    1. flx0
      08.01.2024 07:09
      +4

      Неужели мне действительно надо каждый раз вспоминать, как мой массив размещается в памяти, чтобы сделать тривиальную операцию над каким-то хитрым сечением?

      Нет, не надо. Как по мне, если ваш код содержит reshape, вы уже что-то делаете неправильно. Кому и зачем могут понадобиться эти трюки со strided, вообще не преставляю.

      as_strided(x, shape=(3,3), strides=(80,16)) вместо x[::2,::2] - это просто какой-то изощренный мазохизм.


      1. Jury_78
        08.01.2024 07:09
        +1

        Как я понял, сделано унифицировано, поэтому какие то вещи можно сделать проще. Но как срезами сделать, например диагональное считывание, я не знаю.


        1. flx0
          08.01.2024 07:09
          +2

          numpy.diagonal же. Не срезами, но задачу выполняет. Практически любая нужная на практике нарезка массивов там уже реализована. В крайнем случае numpy.take какой-нибудь
          А про as_strided там вообще такое написано:

          if done incorrectly, the array elements can point to invalid memory and can corrupt results or crash your program
          ...
          For these reasons it is advisable to avoid as_strided when possible.


          1. Jury_78
            08.01.2024 07:09
            +1

            Практически любая нужная на практике нарезка массивов там уже реализована.

            Автор вообще извращался... по змейке читал... :) Компиляторы могут использовать простой FOR, а тут приходится выдумывать обходные пути.

            А про as_strided там вообще такое написано

            В статье про это тоже сказано.


        1. adeshere
          08.01.2024 07:09
          +1

          Jury_78как срезами сделать, например диагональное считывание (...)

          flx0 > (...)numpy.diagonal же. Не срезами, но задачу выполняет. 

          Кстати, да. В фортране диагональное сечение тоже, насколько я знаю, не предусмотрено (правда, у меня устаревшая версия языка - стандарт 2005). Тоже приходится вручную писать. Например, через FORALL:

          integer, parameter :: n=42
          real :: A(n), BB(n,n) ! Описали статические массивы
          A=1913; BB=1937 ! Инициализировали их константой
          A=BB(:,13) ! выбрали 13-й столбец массива B
          ForAll(i=1:n) A(i)=BB(i,i) ! выбрали диагональные элементы
          ForAll(i=1:n, (mod(i,2)/=0) .and. (BB(i,i)/=0)) BB(i,i)=i/BB(i,i) ! присвоили значения нечетным диагональным элементам

          Из хорошего

          секция не обязана быть сплошной (от i1 до i2), а может формироваться через

          цикл по индексу:

          subscript-name= subscript-1: subscript-2[: stride]

          The subscript-name is a scalar of type integer. It is valid only within the scope of the FORALL; its value is undefined on completion of the FORALL.

          The subscripts and stride cannot contain a reference to any subscript-name in triplet-spec.

          The stride cannot be zero. If it is omitted, the default value is 1.

          Evaluation of an expression in a triplet specification must not affect the result of evaluating any other expression in another triplet specification

          Например, можно инвертировать столбец при копировании, и т.д. Но это видимо в любом "массивном" языке так?


  1. saege5b
    08.01.2024 07:09
    +10

    Ух, мои "любимые" "магические" примеры!

    Очень красиво, непонятно и абсолютно неприменимо на практике.


    1. nameisBegemot
      08.01.2024 07:09

      Это применяется там, где применяется математика. Все те-же матрицы и векторы


    1. dimnsk
      08.01.2024 07:09
      +1

      думал мне только показалось

      а можно такие статьи метить СПАМ ?


  1. 9avg
    08.01.2024 07:09

    2) Элементы в массивах NumPy хранятся в памяти непрерывно. Это означает, что они хранятся рядом (в отличие от элементов в списках Python).

    Разве элементы в списке Python не находятся непрерывно? Реализация списка в CPython такая же, как в реализация вектора в C++. Обычный динамический массив, где добавление в конец О(1) с амортизацией, инсерт O(n) и удаление O(n). Разве не так?


  1. sokaa2011
    08.01.2024 07:09

    Я хотел бы порекомендовать дополнение к этой статье. Ноутбук 100 Numpy exercises (Russian version), где собраны 100 задач на NumPy с описаниями методов на русском языке. Эти задачи адаптированы к реальным ситуациям в Data Science, и я думаю, что они будут полезны читателям, желающим улучшить свои навыки в NumPy. Кроме этого под каждым упражнением описание использованных методов на русском и ссылка на эти методы на сайте разработчика