Appearance
2. NumPy: как устроена библиотека и как мыслить массивами
Философия векторизации и архитектура библиотеки
Проблема «600 методов» и ментальная карта библиотеки
Новичкам NumPy часто кажется перегруженным из-за огромного количества функций. Чтобы не утонуть в документации, достаточно разделить возможности библиотеки на четыре основные логические группы:
- Конструкторы (Constructors): Функции для создания данных с нуля (например,
numpy.array,numpy.arange). - Архитекторы формы (Shape Architects): Инструменты, меняющие геометрию и структуру массивов без изменения самих данных (например,
.reshape(), транспонирование). - Статистики-редукторы (Statistical Reducers): Функции, которые сжимают многомерные данные до агрегированных показателей (например,
.sum(),.mean()). - Векторизаторы (Vectorizers): Математические операции, применяемые ко всему массиву сразу.
Почему циклы в AI — это табу
В искусственном интеллекте веса нейросетей и датасеты состоят из миллионов чисел. Написание классического цикла for на Python для обработки таких объемов приводит к катастрофическому падению производительности. Цикл на Python вынужден динамически проверять типы данных на каждой итерации. Векторизация в NumPy переносит обход элементов на уровень скомпилированного C-кода, где тип данных известен заранее, а вычисления распараллеливаются на уровне процессора.
Универсальные функции (ufuncs)
В основе векторизации NumPy лежат универсальные функции - ufuncs. Это объекты-функции, которые выполняют поэлементные операции над массивами фиксированной размерности. Примерами ufuncs являются базовые арифметические операторы, а также тригонометрические и экспоненциальные функции. Главная особенность ufunc — поддержка механизма приведения типов и оптимизированный проход по непрерывным участкам памяти.
Анатомия и фундаментальные характеристики массива (ndarray)
Как устроен объект ndarray «под капотом»
Каждый массив NumPy — это не просто набор чисел, а структура данных на C, состоящая из двух частей: сырого блока памяти с данными (data buffer) и заголовка (metadata), описывающего, как эти данные интерпретировать. Из-за этого массив обладает жесткими физическими и геометрическими свойствами, которые критически важны для AI-инженера при расчете веса моделей.
Ключевые свойства (атрибутов) и характеристики массива
Для контроля структуры данных на практике используются следующие встроенные свойства (атрибуты) объекта numpy.ndarray:
1. Форма и геометрия (Shape & Geometry)
shape— размеры массива по осям (кортеж целых чисел). Используется перед любой математической операцией или передачей данных в нейросети, чтобы убедиться, что структуры данных совпадают и программа не упадет из-за несовместимости размеров (например, при умножении матриц).ndim— количество измерений/осей массива. Помогает быстро понять структуру объекта в условияхif/else(1 — вектор, 2 — таблица, 3 — цветное RGB-изображение, 4 — пачка картинок).size— общее количество элементов в массиве. В отличие от стандартногоlen(), который возвращает только количество строк,sizeчестно считает абсолютно все ячейки. Полезно для контроля общего объема считанных данных.strides— шаги в байтах, необходимые для перехода к следующему элементу по каждой оси. Показывает, как массив развернут в памяти компьютера. Новичку это редко нужно менять вручную, но именно благодаря этому свойствуNumPyумеет мгновенно транспонировать матрицы без реального копирования чисел.
python
import numpy as np
# Initialize a 2D array (2 rows, 3 columns) of 32-bit integers
arr = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.int32)
print(f"Shape: {arr.shape}") # Output: (2, 3) -> 2 rows, 3 columns
print(f"Dimensions: {arr.ndim}") # Output: 2 -> two-dimensional array
print(f"Total size: {arr.size}") # Output: 6 -> 6 elements in total
print(f"Strides: {arr.strides}") # Output: (12, 4) -> 12 bytes to next row, 4 bytes to next column2. Память и типы данных (Memory & Data Types)
dtype— тип данных элементов в массиве. Все элементы вNumPyдолжны быть одного типа. Проверкаdtypeкритически важна при чтении данных из файлов: если числа случайно считались как текст (string), математика работать не будет.itemsize— размер одного элемента массива в байтах. Помогает оценить "тяжесть" выбранного типа данных. Например,int8занимает 1 байт, аfloat64— 8 байт.nbytes— суммарный размер всех элементов массива в байтах (size * itemsize). Позволяет точно узнать, сколько оперативной памяти (RAM) занимает ваш датасет. Если программа выдает ошибкуMemoryError, проверкаnbytesподскажет, нужно ли перевести массив на более легкий тип (например, сfloat64наfloat32).base— ссылка на базовый массив, если текущий является его «представлением» (view). Защищает от багов при создании срезов. Еслиbase is not None, значит, текущий массив зависит от другого. Изменение элемента в нем испортит данные и в оригинальном массиве.data— низкоуровневый буфер (указатель в памяти), где хранятся элементы массива. Прямой адрес первого элемента в оперативной памяти компьютера. Используется для низкоуровневой магии и интеграции на экспертном уровне.
python
import numpy as np
# Initialize a 2D array and create a view (slice)
arr = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.int32)
view_arr = arr[0, :] # Create a view of the first row
print(f"Data type: {arr.dtype}") # Output: int32
print(f"Item size: {arr.itemsize}") # Output: 4 -> since int32 takes 4 bytes
print(f"Total bytes: {arr.nbytes}") # Output: 24 -> 6 elements * 4 bytes
print(f"View base: {view_arr.base}") # Output: [[1 2 3] [4 5 6]] -> points to the original array
print(f"Array base: {arr.base}") # Output: None -> original array owns its data
print(f"Data pointer: {arr.data}") # Output: <memory at 0x...> -> memory address3. Комплексные числа (Complex Numbers)
real— массив, содержащий только вещественную (действительную) часть элементов. Извлекает чистые значения без мнимой компоненты. Применяется в продвинутом анализе сигналов, обработке аудио, радиоволн или при фильтрации изображений (Фурье-преобразования).imag— массив, содержащий только мнимую часть элементов. Позволяет изолировать фазовые или мнимые характеристики комплексных чисел для их отдельного математического анализа в научных вычислениях.
python
import numpy as np
# Initialize an array of complex numbers
complex_arr = np.array([1 + 2j, 3 + 4j])
print(f"Real part: {complex_arr.real}") # Output: [1. 3.]
print(f"Imaginary part: {complex_arr.imag}") # Output: [2. 4.]4. Представления и итераторы (Views & Iterators)
T— транспонированный массив (оси меняются местами). Поворачивает матрицу набок (строки становятся столбцами). Это базовая операция линейной алгебры, которая постоянно применяется при перемножении матриц и в алгоритмах Data Science / Machine Learning.flat— плоский (1D) итератор по всем элементам массива. Позволяет обойти любой многомерный массив (даже 3D или 4D) в один простой циклforбез написания громоздких вложенных циклов.ctypes— интерфейс для взаимодействия с функциями и библиотеками на языкеC. Служит мостом для передачи данных из Python напрямую в готовые быстрые программы, написанные наC/C++, когда требуется максимальная оптимизация кода по скорости.
python
import numpy as np
# Initialize a 2D array
arr = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.int32)
# 1. Transpose the array (swap axes)
print("Transposed array:")
print(arr.T) # Output: a 3x2 matrix
# 2. Iterate through multidimensional array as 1D without copying memory
print("Flat iteration:")
for element in arr.flat:
print(element, end=' ') # Output: 1 2 3 4 5 6
print()
# 3. Get C-compatible data pointer address
print(f"Ctypes data pointer: {arr.ctypes.data}")Эволюция NumPy 2.0+ и Array API
Что навсегда устарело в NumPy 2.0+
С релизом NumPy 2.0+ архитектура библиотеки была жестко очищена от устаревшего legacy-кода:
- Удалены старые псевдонимы типов: Больше нельзя использовать
np.float,np.intилиnp.bool. Теперь необходимо явно указывать стандартные типы Python (float,int,bool) или использовать точные низкоуровневые типы (например,numpy.float32,numpy.int64). - Полный отказ от
numpy.matrix: Старый классnumpy.matrix(который всегда строго оставался двумерным) полностью удален. Вся работа с матрицами теперь ведется исключительно через стандартные многомерные массивыnumpy.ndarray.
Вызов функции (numpy.sum(a)) против метода объекта (a.sum())
В NumPy большинство операций можно вызвать двумя способами: через функцию модуля (numpy.sum(arr)) или как метод самого объекта (arr.sum()). Использование методов объекта делает цепочки вычислений более лаконичными, в то время как функции модуля полезны при работе в рамках строгого функционального программирования или стандартов Array API. Встроенные функцию Python, например sum() использовать с массивами NumPy не рекомендуется, ввиду более медленной скорости работы.
Упражнения базовые (5 шт) (только с опорой на объясненную в данном уроке теорию)
1. Полный аудит характеристик вектора
Задача: Создайте массив NumPy из списка [1.5, 2.5, 3.5, 4.5]. Напишите код, который последовательно выведет в консоль: сам массив, тип контейнера, тип элементов, размерность, форму (shape) и общее количество элементов.
Пример вывода:
text
Array: [1.5 2.5 3.5 4.5]
Container type: <class 'numpy.ndarray'>
Elements type: float64
Dimensions count: 1
Shape geometry: (4,)
Total amount elements: 4Посмотреть решение
python
import numpy
target_list = [1.5, 2.5, 3.5, 4.5]
features_array = numpy.array(target_list)
print("Array:", features_array)
print("Container type:", type(features_array))
print("Elements type:", features_array.dtype)
print("Dimensions count:", features_array.ndim)
print("Shape geometry:", features_array.shape)
print("Total size:", features_array.size)2. Расчет физического веса и размера ячейки
Задача: Создайте массив целых чисел от 1 до 100 с помощью numpy.arange(1, 101). Выведите на экран два значения: сколько байт занимает одно число в этом массиве (.itemsize) и сколько байт весит весь массив целиком (.nbytes).
Посмотреть решение
python
import numpy
large_series = numpy.arange(1, 101)
# Inspecting memory meta-information
bytes_per_element = large_series.itemsize
total_bytes_weight = large_series.nbytes
print("Bytes per item:", bytes_per_element)
print("Total array bytes:", total_bytes_weight)3. Сравнение способов вычисления суммы
Задача: Создайте массив из чисел [10, 20, 30]. Вычислите сумму его элементов двумя способами: с помощью функции из модуля numpy и с помощью метода самого объекта массива. Выведите оба результата, убедившись в их идентичности.
Посмотреть решение
python
import numpy
data_samples = numpy.array([10, 20, 30])
# Way 1: Global module function
sum_via_module = numpy.sum(data_samples)
# Way 2: Internal object method
sum_via_method = data_samples.sum()
print("Module sum:", sum_via_module)
print("Method sum:", sum_via_method)4. Исследование шага памяти (Strides) одномерного массива
Задача: Создайте массив из чисел [1, 2, 3, 4], явно указав тип данных dtype=numpy.int32. Выведите значение атрибута .strides. Прочитайте в комментарии к решению, что означает полученное число.
Посмотреть решение
python
import numpy
# Explicit 32-bit integer array specification
strict_integers = numpy.array([1, 2, 3, 4], dtype=numpy.int32)
# Strides tells how many bytes to skip in RAM to move to the next item
print("Array strides:", strict_integers.strides) # Outputs: (4,)
# Meaning: Since each int32 occupies exactly 4 bytes, the step to the next element is 4 bytes.5. Логическая проверка ufunc-природы операции
Задача: Импортируйте numpy и выведите тип объекта универсальной функции умножения numpy.multiply с помощью встроенной функции type(). Убедитесь, что операция принадлежит к базовой архитектуре ufunc.
Посмотреть решение
python
import numpy
# Identifying core vectorized function components
math_operation_type = type(numpy.multiply)
print("Type of multiply operation:", math_operation_type) # Outputs: <class 'numpy.ufunc'>