Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

NumPy

🎯 Цель

После прочтения этой главы:

  • Поймёте отличие NumPy ndarray от Python list и узнаете, когда какой использовать
  • Научитесь писать быстрый код без циклов с помощью vectorized operations
  • Сможете работать с массивами разных размерностей, используя broadcasting
  • Будете знать NumPy-паттерны, встречающиеся в 90% ML-кода

Что нужно изучить

  • Создание ndarray: np.array, np.zeros, np.ones, np.arange, np.linspace, np.random
  • Атрибуты array: shape, dtype, ndim, size
  • Indexing и slicing (1-D, 2-D, boolean, fancy)
  • Reshape, transpose, concatenate, stack, split
  • Arithmetic operations и broadcasting
  • Universal functions (ufuncs): np.sin, np.exp, np.log и т.д.
  • Aggregations: sum, mean, max, min, argmax, параметр axis
  • Linear algebra (np.linalg)
  • Random sampling (np.random)

Библиотеки

pip install numpy

Рекомендуется NumPy версии 1.26+ или 2.x.

Важные темы

Почему NumPy быстрее Python list?

# Python list — каждый элемент — отдельный PyObject (медленно)
py_list = [1, 2, 3, 1_000_000]
# NumPy — единый C-массив в памяти (быстро)
np_arr = np.array([1, 2, 3, 1_000_000], dtype=np.int64)

NumPy:

  • Написан на C, использует SIMD-инструкции
  • Единый dtype (например, всё int64) — cache-friendly
  • Vectorized: arr * 2 — одна операция на весь массив

Bench: умножить 1M элементов на 2 — list ~50ms, NumPy ~1ms (быстрее в 50x).

Broadcasting

Самая мощная фишка NumPy. Работа с массивами разных размерностей:

# Добавление (3,) вектора к матрице (3, 3)
A = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])  # shape (3, 3)
b = np.array([10, 20, 30])                        # shape (3,)
result = A + b
# [[11, 22, 33], [14, 25, 36], [17, 28, 39]]

NumPy автоматически “broadcast” b к каждой строке.

**Правило:**размерности сравниваются с конца. Они должны быть либо равны, либо одна из них должна быть 1.

Концепция axis

Для 2-D array:

  • axis=0 — строка (вертикально, “down the rows”)
  • axis=1 — столбец (горизонтально, “across the columns”)
A = np.array([[1, 2], [3, 4], [5, 6]])  # shape (3, 2)
A.sum(axis=0)  # [9, 12]  — сумма каждого столбца
A.sum(axis=1)  # [3, 7, 11]  — сумма каждой строки

Примеры кода

Создание array и основные операции

import numpy as np

# Способы создания
a = np.array([1, 2, 3, 4])
zeros = np.zeros((3, 4))            # 3×4 нулей
ones = np.ones((2, 2))               # 2×2 единиц
rng = np.arange(0, 10, 2)            # [0, 2, 4, 6, 8]
lin = np.linspace(0, 1, 5)           # 5 равномерно распределённых чисел [0, 0.25, 0.5, 0.75, 1]
random_arr = np.random.rand(3, 3)    # 3×3 random [0, 1)

# Атрибуты
print(a.shape, a.dtype, a.ndim, a.size)  # (4,) int64 1 4

Indexing и boolean filtering

arr = np.array([10, 20, 30, 40, 50, 60])

# Slicing
print(arr[1:4])      # [20, 30, 40]
print(arr[::-1])     # обратный порядок

# Boolean indexing — очень часто используется в ML
mask = arr > 30
print(arr[mask])     # [40, 50, 60]

# Одновременная фильтрация и изменение
arr[arr < 30] = 0
print(arr)           # [0, 0, 30, 40, 50, 60]

# 2-D indexing
M = np.arange(12).reshape(3, 4)
print(M[1, 2])       # 6
print(M[:, 1])       # 2-й столбец
print(M[1:, :2])     # 1-я строка, столбцы 0 и 1

Vectorized operations (без циклов)

# С циклом (МЕДЛЕННО — так не делайте)
arr = np.arange(1_000_000)
result = []
for x in arr:
    result.append(x ** 2 + 3 * x - 5)

# Vectorized (БЫСТРО — всегда так)
arr = np.arange(1_000_000)
result = arr ** 2 + 3 * arr - 5

# Conditional vectorization
prices = np.array([100, 50, 200, 75, 300])
discounted = np.where(prices > 100, prices * 0.9, prices)
# [100, 50, 180, 75, 270]

Интеграция с backend

В backend NumPy удобен в таких местах:

1. Быстрая JSON-агрегация

from fastapi import FastAPI
import numpy as np

app = FastAPI()

@app.post("/metrics/")
def process_metrics(values: list[float]):
    arr = np.array(values, dtype=np.float64)
    # В pure Python для 1M элементов ~500ms, в NumPy ~5ms
    return {
        "sum": float(arr.sum()),
        "mean": float(arr.mean()),
        "p50": float(np.percentile(arr, 50)),
        "p95": float(np.percentile(arr, 95)),
        "p99": float(np.percentile(arr, 99)),
    }

2. Endpoint обработки изображений

import numpy as np
from PIL import Image
from io import BytesIO

@app.post("/image/grayscale/")
async def to_grayscale(file: UploadFile):
    img = Image.open(file.file)
    arr = np.array(img)
    # RGB → grayscale: формула luminance
    gray = (0.299 * arr[..., 0] + 0.587 * arr[..., 1] + 0.114 * arr[..., 2]).astype(np.uint8)
    out = Image.fromarray(gray)
    buf = BytesIO()
    out.save(buf, format="PNG")
    return Response(content=buf.getvalue(), media_type="image/png")

3. Embedding similarity (для RAG)

def cosine_similarity(a: np.ndarray, b: np.ndarray) -> float:
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

# Batch search — сравнение 1000 эмбеддингов с query
def find_similar(query: np.ndarray, embeddings: np.ndarray, top_k: int = 5):
    # embeddings shape: (1000, 384), query shape: (384,)
    sims = embeddings @ query / (np.linalg.norm(embeddings, axis=1) * np.linalg.norm(query))
    top_indices = np.argsort(sims)[::-1][:top_k]
    return top_indices, sims[top_indices]

Ресурсы

🏋️ Упражнения

🟢 Easy

  1. Создайте массив, равный np.arange(0, 100, 5), и отфильтруйте из него нечётные числа.
  2. Создайте случайную матрицу 3x3, найдите максимальный элемент и его индекс (np.argmax).
  3. Объедините два массива [1, 2, 3, 4] и [5, 6, 7, 8] вертикально и горизонтально (np.vstack, np.hstack).

🟡 Medium

  1. Сгенерируйте 10000 случайных чисел и вычислите x^2 + 2x + 1 в варианте Pure Python for loop и в NumPy vectorized. Сравните их с timeit.
  2. Создайте случайную матрицу (50, 50) и имитируйте шахматную доску chess (np.indices + broadcasting).
  3. Normalize: приведите каждый столбец случайной матрицы к диапазону 0..1 (min-max normalization).

🔴 Hard

  1. Cosine similarity API: создайте FastAPI endpoint. Пользователь отправляет query: list[float] и database: list[list[float]]. Верните Top-K наиболее похожих векторов. Всё должно быть NumPy vectorized (без циклов).
  2. Sliding window: для 1-D array вычислите rolling mean с window_size=k memory-efficient через np.lib.stride_tricks.

Capstone

В файле notebooks/month-01/01_numpy_basics.ipynb:

  • Симулируйте матрицу активности 1000 пользователей за 30 дней: shape (1000, 30)
  • Вычислите среднюю недельную активность каждого пользователя (shape (1000, 4))
  • Найдите 10 самых активных пользователей
  • Визуализируйте матрицу активности в виде heatmap (с Matplotlib)

✅ Чек-лист

  • Понимаю разницу между ndarray и Python list
  • Концепции shape, dtype, axis ясны
  • Использую boolean indexing, не пишу for-циклы
  • Знаю правило broadcasting и применяю в небольших примерах
  • Знаю матричные операции через np.linalg (dot product, inverse, eigvals)
  • Измерял, во сколько раз мой vectorized-код быстрее Python loop

Время перейти к Pandas.