Семестр 3 · Модуль 1 · Неделя 3

Линейная алгебра и случайные числа

Матричные операции · solve · SVD и сжатие · воспроизводимые случайные числа · Монте-Карло

Мотивация недели: «линейная регрессия руками» (проект модуля) — первый шаг к ML. SVD сжимает изображения, Монте-Карло моделирует случайные процессы.

Матричные операции: @ vs *

A = np.array([[3., 1.], [1., 2.]]) B = np.array([[1., 2.], [3., 1.]]) A * B # поэлементное произведение (Hadamard) A @ B # матричное умножение A.T # транспонирование np.trace(A) # след (сумма диагонали) → 5.0 np.diag(A) # диагональ → [3. 2.]

Акцент: * — поэлементное, @ — матричное. Путаница между ними — самая частая ошибка недели 3.

Какой оператор даст «обычное» перемножение матриц?

Решение СЛАУ: np.linalg.solve

A = np.array([[3., 1.], [1., 2.]]) b = np.array([9., 8.]) x = np.linalg.solve(A, b) x array([2., 3.]) A @ x # проверка: должно быть ≈ b array([9., 8.])

Предупреждение: НЕ решайте через np.linalg.inv(A) @ b — численно неустойчиво и медленнее. solve использует разложение LU — устойчиво.

Почему inv(A) @ b хуже, чем solve(A, b)?

inv, det — когда нужны, когда нет

np.linalg.inv(A) array([[ 0.4, -0.2], [-0.2, 0.6]]) np.linalg.det(A) 5.0 np.linalg.cond(A) 2.618033988749896
Как оценить «устойчивость» системы линейных уравнений?

Собственные значения: np.linalg.eig

Собственный вектор при умножении на матрицу не меняет направление, только масштаб — на собственное значение.

# A = [[3, 1], [1, 2]] (из слайда solve) vals, vecs = np.linalg.eig(A) vals array([3.61803399, 1.38196601]) vecs array([[ 0.85065081, -0.52573111], [ 0.52573111, 0.85065081]])

Для симметричных матриц — eigh (быстрее, гарантированно вещественные).

Что такое собственное значение геометрически?

SVD: A = U Σ Vᵀ

U, S, Vt = np.linalg.svd(img) U.shape, S.shape, Vt.shape ((200, 200), (200,), (200, 200))

Сингулярные числа S — «важность» каждого направления матрицы; они быстро убывают.

# сингулярные числа реального «изображения»: [42791.6, 5507.3, 1097.2, 1040.6, 1017.2, 1002.5, 998.5, 978.8, ...]

Первые числа огромны, дальше — «хвост»: значит, большую часть информации несут первые компоненты.

Что такое сингулярные числа и как они упорядочены?

Усечённое SVD — сжатие изображения

def compress(a, k): U, S, Vt = np.linalg.svd(a) return (U[:, :k] * S[:k]) @ Vt[:k, :] for k in [5, 20, 50, 100]: rec = compress(img, k) mse = np.mean((img - rec)**2) print(f"k={k}: mse={mse:.2f}")
k=5: mse=995.18 k=20: mse=678.50 k=50: mse=297.88 k=100: mse=45.35

Восстановление по k компонентам: качество растёт с k, но «вес» хранение — k × (200+200) вместо 200×200. При k=100 это уже 2× сжатие почти без потерь.

Почему ошибка восстановления падает с ростом k?

Воспроизводимые случайные числа

rng = np.random.default_rng(42) # современный API rng.random(3) array([0.77395605, 0.43887844, 0.85859792])

Зачем seed: при фиксированном seed последовательность одинакова каждый раз — эксперимент воспроизводим.

rng2 = np.random.default_rng(42) rng2.random(3) array([0.77395605, 0.43887844, 0.85859792]) # тот же поток

Рекомендуется default_rng, а не глобальный np.random.seed. Без seed — новый поток при каждом запуске.

Как сделать случайный эксперимент воспроизводимым?

Функции rng

rng.random((2, 3)) # uniform [0, 1) rng.standard_normal(5) # N(0, 1) rng.normal(5.0, 2.0, 3) # N(μ=5, σ=2) rng.integers(1, 7, size=5) # целые 1..6 rng.choice(['о', 'р'], size=5, p=[0.3, 0.7]) # с весами rng.shuffle(deck) # перемешивание
# пример: integers(1, 7, 5) при seed=1 [3 4 5 6 1]
Чем rng.choice(x, p=...) отличается от rng.integers?

Монте-Карло: оценка π

Бросаем точки в квадрат [−1,1]²; доля попавших в круг × 4 → π.

def estimate_pi(n, seed=42): rng = np.random.default_rng(seed) pts = rng.random((n, 2)) # n точек в [0,1]² inside = np.sum(pts**2, axis=1) <= 1.0 return 4 * inside.mean() estimate_pi(10**6) 3.1430

Точность растёт как ~1/√N: чтобы получить ещё один знак, нужно в 100 раз больше точек.

Почему точность Монте-Карло растёт как 1/√N?

Монте-Карло: интеграл hit-and-miss

def mc_integral(n, seed=42): rng = np.random.default_rng(seed) pts = rng.random((n, 2)) # x, y в [0,1] return np.mean(pts[:, 1] <= pts[:, 0]**2) mc_integral(10**6) 0.3327 # точное ∫₀¹ x² dx = 1/3 ≈ 0.3333

Доля точек «под кривой» — оценка площади = интеграла.

Как оценить ∫₀¹ x² dx методом Монте-Карло?

Типичные ошибки недели 3

  1. A * B вместо A @ B — поэлементное вместо матричного.
  2. Решение СЛАУ через inv — численно неустойчиво.
  3. Нет seed — случайный эксперимент невоспроизводим.
  4. eig на несимметричной матрице даёт комплексные значения — пугаются, хотя это нормально.
  5. Путаница глобального np.random и default_rng.
Какую функцию использовать для устойчивого решения СЛАУ?

Вопросы для проверки понимания

  1. Как устойчиво решить СЛАУ и почему не через inv?
  2. Что такое сингулярные числа в SVD и как их используют для сжатия?
  3. Как сделать случайный эксперимент воспроизводимым?
  4. Как оценить π методом Монте-Карло?
Переход: на семинаре — решение СЛАУ, SVD-сжатие и Монте-Карло. А дальше — проект модуля: линейная регрессия на NumPy.

Что дальше: проект модуля

Готовы? Начните с задачи 3.1 — решение системы и проверка подстановкой.