Как работают встроенные методы numpy: core, multiarray, umath и реализация array function?

Обзор библиотеки NumPy и ее значения для научных вычислений

NumPy — это фундаментальная библиотека для научных вычислений на Python. Она предоставляет мощные инструменты для работы с многомерными массивами и выполнения математических операций над ними. NumPy лежит в основе многих других библиотек для анализа данных, машинного обучения и визуализации, таких как Pandas, Scikit-learn и Matplotlib. Ее эффективность, благодаря C-реализации ключевых компонентов, позволяет существенно ускорить вычисления по сравнению с использованием стандартных списков Python.

Архитектура NumPy: Core, Multiarray, Umath и Array Function

Архитектура NumPy состоит из нескольких ключевых компонентов:

  • Core: Ядро библиотеки, определяющее основные типы данных и структуры. Здесь находится ndarray – основной объект NumPy, представляющий собой N-мерный массив.
  • Multiarray: Модуль, отвечающий за работу с многомерными массивами, включая операции broadcasting и векторизацию.
  • Umath: Модуль, содержащий универсальные математические функции (ufunc), которые работают поэлементно над массивами.
  • Array Function: Механизм, позволяющий расширять функциональность NumPy и оптимизировать выполнение операций над массивами, используя dispatching для выбора наиболее подходящей реализации функции.

Ядро NumPy (Core): Основа библиотеки

Описание основных типов данных NumPy (dtype)

NumPy предоставляет широкий набор числовых типов данных (dtype), включая целые числа, числа с плавающей точкой и комплексные числа различной разрядности. Явное указание типа данных позволяет эффективно использовать память и оптимизировать вычисления.

import numpy as np

# Создание массива целых чисел типа int32
arr = np.array([1, 2, 3], dtype=np.int32)
print(arr.dtype)  # Вывод: int32

# Создание массива чисел с плавающей точкой типа float64
arr_float = np.array([1.0, 2.0, 3.0], dtype=np.float64)
print(arr_float.dtype) # Вывод: float64

# Создание массива строк
arr_str = np.array(['apple', 'banana', 'cherry'], dtype=np.string_)
print(arr_str.dtype) # Вывод: |S6 (bytes)

Управление памятью и структура ndarray

ndarray – это N-мерный массив, который хранит элементы одного и того же типа данных в непрерывном блоке памяти. Такая организация позволяет NumPy эффективно выполнять операции над массивами. Важно понимать, что NumPy не копирует данные без необходимости, а предоставляет view на существующие массивы, что позволяет экономить память и ускорять вычисления.

Базовые функции ядра: создание, изменение формы и индексация массивов

Ядро NumPy предоставляет функции для создания массивов, изменения их формы и индексации. Например:

import numpy as np

# Создание массива нулей
arr_zeros = np.zeros((2, 3))
print(arr_zeros)

# Создание массива единиц
arr_ones = np.ones((3, 2))
print(arr_ones)

# Создание массива из списка
arr_from_list = np.array([1, 2, 3, 4, 5])
print(arr_from_list)

# Изменение формы массива
arr_reshaped = arr_from_list.reshape((5, 1))
print(arr_reshaped)

# Индексация массива
print(arr_reshaped[0, 0]) # Вывод: 1

Модуль Multiarray: Многомерные массивы

Работа с многомерными массивами: оси, форма, размер

Многомерные массивы NumPy позволяют представлять данные в виде таблиц, тензоров и других сложных структур. Каждый массив имеет форму (shape), определяющую размерность по каждой оси, и размер (size), определяющий общее количество элементов.

import numpy as np

arr = np.array([[1, 2, 3], [4, 5, 6]])

print(arr.shape)  # Вывод: (2, 3)
print(arr.size)   # Вывод: 6
print(arr.ndim) # Вывод: 2 (количество осей)

Операции над массивами: broadcasting, векторизация

Broadcasting – это механизм, позволяющий выполнять операции над массивами разной формы, при условии, что их формы совместимы. Векторизация позволяет выполнять операции над массивами поэлементно, без использования циклов, что значительно ускоряет вычисления. Это достигается за счет использования SIMD (Single Instruction, Multiple Data) инструкций процессора.

import numpy as np

arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])

# Поэлементное сложение (векторизация)
arr_sum = arr1 + arr2
print(arr_sum) # Вывод: [5 7 9]

# Broadcasting: сложение массива и скаляра
arr_plus_scalar = arr1 + 10
print(arr_plus_scalar) # Вывод: [11 12 13]

Функции для манипуляции массивами: reshape, transpose, concatenate, split

NumPy предоставляет широкий набор функций для манипулирования массивами:

  • reshape: Изменяет форму массива.
  • transpose: Транспонирует массив (меняет местами оси).
  • concatenate: Объединяет массивы вдоль указанной оси.
  • split: Разделяет массив на несколько подмассивов.
import numpy as np

arr = np.array([[1, 2], [3, 4]])

# Транспонирование
arr_transposed = np.transpose(arr)
print(arr_transposed)

# Объединение массивов
arr1 = np.array([1, 2])
arr2 = np.array([3, 4])
arr_concatenated = np.concatenate((arr1, arr2))
print(arr_concatenated) # Вывод: [1 2 3 4]
Реклама

Модуль Umath: Универсальные математические функции

Обзор универсальных функций (ufunc): понятие и преимущества

Universal functions (ufuncs) – это функции, которые работают поэлементно над массивами NumPy. Они векторизованы и, следовательно, выполняются очень быстро. Ufuncs поддерживают broadcasting и могут работать с массивами разной формы. Примеры ufuncs: np.sin, np.cos, np.exp, np.log.

Типы ufunc: поэлементные операции, редукция, аккумулирование

Ufuncs можно разделить на несколько типов:

  • Поэлементные операции: Выполняют операции над каждым элементом массива (например, np.sin, np.cos).
  • Редукция: Выполняют операцию над массивом вдоль указанной оси, возвращая один скаляр или массив меньшей размерности (например, np.sum, np.max).
  • Аккумулирование: Применяют функцию к элементам массива и накапливают результат (например, np.cumsum, np.cumprod).
import numpy as np

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

# Поэлементное применение синуса
arr_sin = np.sin(arr)
print(arr_sin)

# Суммирование элементов массива
arr_sum = np.sum(arr)
print(arr_sum) # Вывод: 6

# Накопительная сумма
arr_cumsum = np.cumsum(arr)
print(arr_cumsum) # Вывод: [1 3 6]

Пользовательские ufunc: создание и использование

NumPy позволяет создавать собственные ufuncs с помощью функции np.frompyfunc. Это позволяет векторизовать пользовательские функции, написанные на Python, и применять их к массивам NumPy.

import numpy as np

def my_func(x, y):
    return x + y * 2

my_ufunc = np.frompyfunc(my_func, 2, 1)  # 2 аргумента, 1 возвращаемое значение

arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])

arr_result = my_ufunc(arr1, arr2)
print(arr_result) # Вывод: [ 9 12 15]

Array Function: Реализация и оптимизация

Понятие array function и их роль в NumPy

Array functions – это обобщенный механизм для реализации функций, работающих с массивами NumPy. Они позволяют выбирать наиболее подходящую реализацию функции в зависимости от типа данных и других параметров массивов. Это обеспечивает гибкость и оптимизацию вычислений. В частности, array function важны для поддержки duck typing и возможности работы с различными подклассами ndarray.

Механизм диспетчеризации array function

Механизм диспетчеризации array function позволяет NumPy выбирать наиболее эффективную реализацию функции в зависимости от типов входных данных. Это достигается за счет использования атрибута __array_function__ у классов ndarray и dispatch-логики, встроенной в NumPy. При вызове array function, NumPy проверяет, определен ли метод __array_function__ у входных аргументов. Если да, то он вызывается, и ему передается управление. Этот механизм позволяет переопределять поведение стандартных функций NumPy для пользовательских типов массивов.

Примеры реализации array function и их оптимизация

Реализация array function включает определение функции, которая принимает массивы в качестве аргументов и возвращает результат. Оптимизация может включать использование Cython или Numba для ускорения вычислений.

Пример (абстрактный):

import numpy as np

def my_array_function(func, types, args, kwargs):
    # func - вызываемая функция (например, np.sum)
    # types - типы аргументов
    # args - аргументы
    # kwargs - именованные аргументы

    if func is np.sum:
        # Проверяем, является ли один из аргументов нашим кастомным типом
        for arg in args:
            if isinstance(arg, CustomArray):
                # Обрабатываем случай для CustomArray
                print("Custom sum implementation")
                return custom_sum(arg)
        # Иначе, вызываем стандартную реализацию
        return np.sum(*args, **kwargs)
    else:
        return NotImplemented # говорим, что не умеем обрабатывать эту функцию

class CustomArray(np.ndarray):
    def __array_function__(self, func, types, args, kwargs):
        if func not in handled_functions: # Опциональная проверка, поддерживается ли функция
            return NotImplemented
        # Delegate to the implementation.
        return my_array_function(func, types, args, kwargs)

handled_functions = {np.sum: ...}

def custom_sum(array):
  print("Кастомная функция суммирования")
  return np.sum(array)

arr = np.arange(5)

c_arr = CustomArray(arr)

print(np.sum(c_arr))

Использование array function для расширения функциональности NumPy

Array function позволяют расширять функциональность NumPy, добавляя поддержку новых типов массивов и оптимизируя выполнение операций над ними. Это особенно полезно при работе с большими данными и сложными вычислениями. С помощью array function можно, например, реализовать поддержку разреженных массивов или массивов, хранящихся на GPU.


Добавить комментарий