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

Введение в NumPy. Jupyter Notebook

Зачем NumPy · ndarray и dtype · создание массивов · векторизация vs циклы · Jupyter

2 ч теории. Презентация — 30 мин мини-лекция, далее live-coding и задачи семинара.

Прежде чем начать: откройте Jupyter Notebook — сегодня вы впервые увидите, что Python умеет считать числа «в промышленном масштабе».

Зачем вам NumPy на 2–3 курсе

Какая структура данных описывает чёрно-белое фото размером 1920×1080? Какого типа её элементы?

Проблема Python-списков для чисел

a = [1, 2, 3] b = [4, 5, 6] # скалярное произведение циклом: s = 0 for i in range(3): s += a[i] * b[i] # 5 строк кода, медленно
import numpy as np a = np.array([1, 2, 3]) b = np.array([4, 5, 6]) np.dot(a, b) # → 32
Что вернёт [1,2,3] + [4,5,6]? А если заменить списки на NumPy-массивы?

ndarray — массив в непрерывном блоке памяти

ndarray — однородный N-мерный массив: все элементы одного типа (dtype), лежат подряд в одном блоке памяти, операции выполняются на C.

АтрибутЧто означаетПример
shapeкортеж размерностей(3, 4)
ndimчисло осей (rank)2
sizeвсего элементов12
dtypeтип элементаint64
a = np.array([1, 2, 3]) a.shape, a.ndim, a.size, a.dtype ((3,), 1, 3, dtype('int64'))
Массив формы (2, 3, 4): сколько осей и сколько всего элементов?

Схема памяти: список vs ndarray

Список Python — цепочка ссылок на объекты; ndarray — сплошная лента ячеек:

Список [1, 2, 3]: [объект:1] → [объект:2] → [объект:3] │ (каждый — отдельный объект int)
ndarray int64 [1 2 3]: [ 1 | 2 | 3 ] │ (24 байта подряд, один dtype)

Непрерывность → процессор читает данные «одним куском» → C-операция над всем блоком сразу → скорость. Однородность → размер элемента известен → компактность.

Почему элементы ndarray не могут быть разных типов (например, число и строка)?

dtype — тип элементов массива

np.zeros(3) # по умолчанию float64 array([0., 0., 0.]) # dtype float64 np.zeros(3, dtype=int) array([0, 0, 0]) # dtype int64

Акцент: у целочисленных данных результат операций тоже целочисленный. Для деления/NaN нужен float.

np.array([1, 2]) / np.array([3, 3]) # float — ок array([0.33333333, 0.66666667])
Чем np.zeros(3) отличается от np.zeros(3, dtype=int)?

Создание массивов — конструкторы-фабрики

np.array([[1, 2], [3, 4]]) # из списка np.zeros((2, 3)) # нули np.ones((2, 3)) # единицы np.eye(3) # единичная матрица np.full((2, 2), 7) # заполнение константой np.empty((2, 2)) # «мусор» из памяти (быстро)
array([[1., 0., 0.], [0., 1., 0.], [0., 0., 1.]])

Не путать: zeros заполняет нулями (полезно), empty возвращает неинициализированную память (для скорости).

Как создать массив 3×4 из нулей? А массив, заполненный числом 5?

Порождение последовательностей: arange и linspace

# arange: шаг np.arange(0, 10, 2) array([0, 2, 4, 6, 8])
# linspace: N точек np.linspace(0, 1, 5) array([0. , 0.25, 0.5 , 0.75, 1. ])

Классическая ошибка: np.arange(0, 1, 0.1) — из-за float-арифметики последний элемент «не попадает» в 1.0. Для равномерной сетки из N точек всегда используйте linspace.

np.arange(0, 1, 0.1) array([0. , 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9])
Какая разница между arange(0, 1, 0.25) и linspace(0, 1, 5)?

Преобразование формы: reshape, flatten, transpose

a = np.arange(12).reshape(3, 4) # 12 → (3, 4) a array([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11]]) a.flatten() # обратно в 1D → [ 0 1 ... 11] a.T # транспонирование (3,4) → (4,3) a.reshape(-1, 2) # -1 = «вычисли сам» → (6, 2)

Число элементов сохраняется: 12 = 3×4 = 12×1 = 6×2. Нельзя reshape в форму с другим size.

Какой shape получится у np.arange(6).reshape(2,3).T?

Векторизация vs циклы

Векторизация — применение операции сразу ко всем элементам, без явного for. NumPy передаёт данные C-функции целиком.

# цикл (медленно) x = np.arange(10**7) s = 0.0 for i in range(len(x)): s += x[i]**2 s # ≈ 1.6 c
# векторизованно x = np.arange(10**7) s = np.sum(x**2) s # ≈ 0.06 c

Тот же результат, ускорение десятки раз, код короче. Разрыв растёт с размером массива.

Почему x**2 на массиве из 10⁷ элементов работает быстрее цикла?

Базовые поэлементные операции

x = np.arange(5) x**2 + 2*x + 1 array([ 1, 4, 9, 16, 25]) np.sqrt(np.arange(4)) array([0. , 1. , 1.41421356, 1.73205081]) np.exp([0, 1, 2]) array([1. , 2.71828183, 7.3890561 ]) x > 3 array([False, False, False, False, True])

Важно: операции возвращают новый массив и не меняют исходный (кроме +=, *= — in-place).

Что вернёт np.arange(5) > 3? Какой тип элементов у результата?

Скалярное произведение: np.dot и @

np.dot([1, 2, 3], [4, 5, 6]) 32 np.array([1, 2, 3]) @ np.array([4, 5, 6]) 32

Для векторов @ и np.dot — одно и то же (скалярное произведение). Для матриц — матричное умножение (неделя 3).

Чем + на списках отличается от + на ndarray?

Измеряем скорость: %timeit в Jupyter

Jupyter Notebook — интерактивная среда: ячейки code/markdown, Shift+Enter — выполнить, Ctrl+Enter — выполнить и остаться.

%%timeit # замер всей ячейки np.sum(np.arange(10**6)**2) 1.25 ms ± 0.06 ms per loop
%timeit np.sum(np.arange(10**6)**2) # замер строки

Другие магические команды: %matplotlib inline, %whos (переменные), %debug (отладка).

Какой командой измерить время выполнения фрагмента кода из нескольких строк?

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

  1. Список вместо массива: a = [1,2,3], затем a + a — конкатенация, а не поэлементное сложение.
  2. Забыли np.: zeros(3)NameError.
  3. Неверный dtype: деление целых без перевода в float.
  4. arange для дробной сетки: arange(0,1,0.1) — «кривой» конец; брать linspace.
  5. Мутация ожидается, а её нет: операции создают новый массив, исходный не меняется (кроме in-place операторов).
Студент написал a + a и получил список длиной 6. Что он забыл?

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

  1. Чем ndarray принципиально отличается от Python-списка?
  2. Что вернёт np.linspace(0, 1, 5) и какой у него shape?
  3. Почему x**2 на массиве из 10⁷ элементов быстрее цикла?
  4. Какая разница между arange и linspace?

Ответьте себе письменно за 2 минуты — это и есть мини-самооценка перед семинаром.

Переход: на семинаре вы примените всё это на практике — создадите массивы, измерите ускорение векторизации через %timeit.

Что дальше

Готовы? Откройте семинар-1.md и выполните задачу базового уровня.