NumPy для Machine Learning: Фундаментальный курс

Углубленный курс, охватывающий все аспекты библиотеки NumPy, необходимые для машинного обучения: от архитектуры массивов [ru.hexlet.io] до линейной алгебры [github.com] и оптимизации вычислений [skillbox.ru].

1. Архитектура ndarray: создание массивов, типы данных и управление памятью

Архитектура ndarray: создание массивов, типы данных и управление памятью

NumPy (Numerical Python) — это фундамент, на котором стоит практически вся экосистема машинного обучения в Python. Библиотеки Pandas, Scikit-learn, TensorFlow и PyTorch либо построены поверх NumPy, либо копируют его синтаксис и логику работы. Понимание того, как устроен основной объект NumPy — ndarray, отличает новичка от инженера, способного оптимизировать обучение нейросети и устранять ошибки размерности данных.

В этой статье мы разберем архитектуру массива, способы его эффективного создания для задач ML, работу с типами данных и нюансы управления памятью, которые критически влияют на производительность.

Что такое ndarray и почему он быстрее списков

Основной объект NumPy — это ndarray (N-dimensional array), многомерный массив элементов одного типа. В отличие от стандартных списков Python (list), которые являются массивами ссылок на объекты, разбросанные в памяти, ndarray хранит данные в непрерывном блоке памяти.

Это архитектурное решение дает два ключевых преимущества для Machine Learning:

  • Локальность данных. Процессор загружает данные в кэш блоками. При работе с ndarray следующие элементы массива с высокой вероятностью уже находятся в кэше процессора (CPU cache), что минимизирует задержки. Это называется механизмом предвыборки (prefetching) otus.ru.
  • Векторизация (SIMD). Благодаря строгому типу данных и непрерывному хранению, процессор может применять одну инструкцию сразу к нескольким элементам данных (Single Instruction, Multiple Data).
  • !Слева: Python List, где элементы ссылаются на объекты, разбросанные в памяти. Справа: NumPy Array, где значения (числа) лежат в памяти плотно друг за другом в едином блоке.

    Создание массивов для задач ML

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

    Базовая инициализация

    Самый простой способ — конвертация списка Python:

    Генерация массивов для инициализации моделей

    При создании нейронных сетей или алгоритмов кластеризации нам часто нужны специфические начальные значения.

    1. Нулевые и единичные матрицы Используются для инициализации смещений (bias) или масок.

    2. Диапазоны и сетки Полезны для создания графиков и визуализации решающих границ.

    3. Случайные числа (Random Sampling) Критически важно для инициализации весов нейросетей. Если инициализировать веса нулями, сеть может не обучиться из-за симметрии градиентов.

    Типы данных (dtype) и их влияние на память

    В Python тип int имеет динамический размер и может занимать 28 байт и более. В NumPy тип данных фиксирован для всего массива. Это определяется атрибутом dtype.

    Основные типы в ML: * float64 (по умолчанию в NumPy): Двойная точность. Обеспечивает высокую точность вычислений, но занимает много памяти. * float32: Одинарная точность. Стандарт де-факто в глубоком обучении (Deep Learning). Занимает в 2 раза меньше памяти, чем float64, и быстрее обрабатывается на GPU. * int8 / uint8: Используются для хранения изображений (значения пикселей 0-255) или масок классов.

    Расчет потребления памяти

    Объем памяти, занимаемый массивом, можно рассчитать по формуле:

    где — общий объем памяти в байтах, — количество элементов (size), — размер одного элемента в байтах (itemsize).

    Пример: Массив из 1 миллиона элементов типа float64 (8 байт):

    Если изменить тип на float32 (4 байта), размер уменьшится до ~3.8 МБ. При работе с гигабайтами данных это критично.

    Атрибуты массива: Shape, Ndim, Size

    Понимание формы массива — это 90% успеха в отладке нейросетей. Ошибки несовпадения размерностей (Dimension Mismatch) — самые частые в ML.

    * ndarray.ndim: Количество измерений (осей/ранг). 1 — вектор, 2 — матрица, 3+ — тензор. * ndarray.shape: Кортеж с размером по каждому измерению. Например, (100, 5) означает 100 объектов и 5 признаков. * ndarray.size: Общее количество элементов.

    !3D массив (тензор) с осями: Axis 0 (глубина/batch), Axis 1 (строки/высота), Axis 2 (столбцы/ширина).

    Управление памятью: Views (Представления) vs Copies (Копии)

    Это один из самых важных аспектов архитектуры NumPy, незнание которого приводит к скрытым багам.

    NumPy старается не копировать данные без явной необходимости. Операции среза (slicing) и изменения формы (reshaping) часто возвращают View (представление) — новый объект массива, который ссылается на те же самые данные в памяти.

    Опасность изменения View

    Если вам нужна независимая копия данных, используйте метод .copy():

    Изменение формы (Reshaping)

    В ML часто нужно менять форму данных: вытягивать изображение в вектор или добавлять ось для батча. Метод reshape не меняет данные, а меняет только метаданные (stride), определяющие, как читать этот блок памяти.

    Использование -1 в reshape крайне удобно: NumPy сам посчитает недостающую размерность, исходя из общего количества элементов (size).

    Итоги

  • Архитектура: ndarray хранит данные в непрерывном блоке памяти, что обеспечивает высокую скорость за счет кэширования процессора и векторизации.
  • Типы данных: Выбор правильного dtype (например, float32 вместо float64) критичен для экономии памяти в ML, особенно при работе с GPU.
  • Инициализация: Используйте np.zeros, np.ones и np.random для создания начальных состояний весов и данных.
  • Управление памятью: Срезы (slicing) в NumPy создают ссылки (views), а не копии. Изменение среза меняет исходный массив. Для независимости данных используйте .copy().
  • Форма: Атрибут shape — главный инструмент контроля размерностей. Метод reshape позволяет менять интерпретацию данных без их физического перемещения в памяти.
  • 2. Манипуляции с данными: продвинутая индексация, срезы, маскирование и изменение формы

    Манипуляции с данными: продвинутая индексация, срезы, маскирование и изменение формы

    В предыдущей лекции мы разобрали архитектуру ndarray и базовое создание массивов. Однако в реальных задачах Machine Learning (ML) данные редко приходят в идеальном виде. Вам придется вырезать валидационные выборки, фильтровать выбросы, менять размерность изображений для сверточных сетей и объединять признаки из разных источников.

    Умение виртуозно манипулировать индексами и формой массива — это навык, который отделяет написание медленного, громоздкого кода на Python от создания высокопроизводительных пайплайнов на NumPy.

    Многомерная индексация и срезы

    В отличие от стандартных списков Python, где для доступа к элементу матрицы нужно писать matrix[i][j], NumPy поддерживает более эффективный синтаксис через запятую: matrix[i, j]. Это не просто синтаксический сахар, это способ избежать создания промежуточных объектов.

    Базовые срезы (Slicing)

    Срезы в NumPy работают по стандарту Python: start:stop:step. Главное отличие — они работают по всем измерениям одновременно.

    Рассмотрим пример выборки данных. Допустим, у нас есть матрица X (объекты-признаки), где строки — это примеры, а столбцы — параметры.

    !Визуализация среза data[1:3, 1:3, показывающая выбор подматрицы]

    > Операция среза — это удобный и распространенный способ получить некоторое подмножество элементов. ru.hexlet.io

    Важно: Как мы помним, срезы возвращают View (представление). Изменение center_block изменит и исходный массив data.

    Продвинутая индексация (Fancy Indexing)

    Это механизм, позволяющий выбирать элементы не по порядку (как в срезах), а в произвольном порядке, передавая списки или массивы индексов.

    Ключевое отличие от срезов: Fancy Indexing всегда создает копию данных, а не представление.

    Индексация целочисленными массивами

    Используется, когда нужно переставить строки местами (shuffle) или выбрать конкретные примеры по их ID.

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

    Булево маскирование (Boolean Masking)

    В ML это основной инструмент для фильтрации данных: удаление выбросов, выбор объектов определенного класса или обнуление весов (Dropout).

    Маскирование работает в два этапа:

  • Создание булевой маски (массив True/False той же формы).
  • Применение маски к массиву.
  • Синтаксис и логические операторы

    Стандартные Python-операторы and, or, not не работают с массивами NumPy. Вместо них нужно использовать побитовые операторы: * & (и) * | (или) * ~ (не)

    !Принцип работы булевой фильтрации

    > Для фильтрации значений массива numpy.ndarray по определенному условию используют булевы маски. ru.hexlet.io

    Практический пример: Очистка данных

    Допустим, у нас есть массив значений возраста, и нам нужно убрать аномалии (отрицательные значения или > 100).

    Изменение размерности: Reshape, Transpose, Flatten

    Нейронные сети крайне чувствительны к форме входных тензоров. Ошибка ValueError: shapes (32, 10) and (32,) not aligned — классика жанра.

    Добавление осей (Broadcasting preparation)

    Часто вектор размера нужно превратить в матрицу или , чтобы корректно выполнить матричное умножение или сложение с батчем.

    Для этого используют np.newaxis (или None в индексе) и np.expand_dims.

    Транспонирование

    Операция .T или np.transpose() меняет оси местами. Для 2D матрицы это замена строк на столбцы.

    В Deep Learning (например, в PyTorch) часто нужно менять порядок каналов изображения: из сделать .

    Выпрямление массива (Flattening)

    Преобразование многомерного тензора в одномерный вектор (например, перед подачей в полносвязный слой нейросети).

    * arr.flatten(): Возвращает копию. Безопасно, но медленнее и требует памяти. * arr.ravel(): Возвращает View (если возможно). Быстрее, но нужно быть осторожным с изменением данных.

    Объединение массивов (Concatenation)

    Сборка датасета часто требует объединения признаков.

    * np.concatenate((a1, a2), axis=...): Универсальный метод. * np.hstack(): Горизонтальное объединение (по столбцам/второй оси). * np.vstack(): Вертикальное объединение (по строкам/первой оси).

    Итоги

  • Срезы (Slicing): Используйте arr[start:stop] для выборки подмассивов. Помните, что это создает View (ссылку), а не копию.
  • Fancy Indexing: Передача списков индексов arr[[1, 5]] позволяет выбирать элементы произвольно, но всегда создает копию данных.
  • Маскирование: Используйте булевы условия и операторы &, |, ~ для фильтрации данных (например, удаления выбросов).
  • Размерность: np.newaxis и reshape критически важны для согласования размерностей тензоров перед подачей в модели.
  • Транспонирование: Метод .transpose() позволяет менять порядок осей, что часто требуется при работе с изображениями в нейросетях.