Семестр 3 · Модуль 1 · Неделя 1
Введение в NumPy. Jupyter Notebook
Зачем NumPy · ndarray и dtype · создание массивов ·
векторизация vs циклы · Jupyter
2 ч теории. Презентация — 30 мин мини-лекция, далее live-coding
и задачи семинара.
Прежде чем начать: откройте Jupyter Notebook — сегодня вы
впервые увидите, что Python умеет считать числа «в промышленном масштабе».
Зачем вам NumPy на 2–3 курсе
- Почти все downstream-курсы начинаются строкой
import numpy as np:
Анализ данных, Математическая статистика, ML, ИИ, NLP, Методики вычислений.
- Реальные данные — это массивы: пиксели фото
(H, W),
признаки объектов (n, m), временные ряды (T,).
- Без векторизации вы не справитесь с объёмами: 10⁷ чисел циклом — секунды,
векторизованно — миллисекунды.
- Навык «векторное мышление» переносится на Pandas, SciPy, scikit-learn.
Какая структура данных описывает чёрно-белое фото размером
1920×1080? Какого типа её элементы?
Проблема Python-списков для чисел
- Список хранит объекты-ссылки, а не числа: каждый элемент —
отдельный объект в куче (unboxing).
- Хранение разреженное: элементы разбросаны по памяти, кеш процессора не работает.
- Операции идут через интерпретатор на каждый элемент — медленный
for.
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 — тип элементов массива
- Целые:
int8 … int64, беззнаковые uint
- Вещественные:
float32, float64 (по умолчанию)
- Логические:
bool, комплексные: complex
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
- Список вместо массива:
a = [1,2,3], затем
a + a — конкатенация, а не поэлементное сложение.
- Забыли
np.: zeros(3) → NameError.
- Неверный dtype: деление целых без перевода в
float.
- arange для дробной сетки:
arange(0,1,0.1) — «кривой» конец; брать linspace.
- Мутация ожидается, а её нет: операции создают новый массив,
исходный не меняется (кроме in-place операторов).
Студент написал a + a и получил список длиной 6. Что он забыл?
Вопросы для проверки понимания
- Чем ndarray принципиально отличается от Python-списка?
- Что вернёт
np.linspace(0, 1, 5) и какой у него shape?
- Почему
x**2 на массиве из 10⁷ элементов быстрее цикла?
- Какая разница между
arange и linspace?
Ответьте себе письменно за 2 минуты — это и есть мини-самооценка перед семинаром.
Переход: на семинаре вы примените всё это на практике —
создадите массивы, измерите ускорение векторизации через %timeit.
Что дальше
- Семинар недели 1: три уровня задач — «Первая ячейка +
создание массивов» (базовый), «Векторизация vs цикл + базовые операции»
(стандартный), «Transform + Challenge: порог ускорения» (продвинутый).
- ДЗ недели 1: 10 задач на создание и преобразование массивов
(autograder, task-01).
- К концу недели вы сможете запускать Jupyter, создавать
массивы любых форм и dtype и измерять выигрыш векторизации.
Готовы? Откройте семинар-1.md и выполните задачу базового уровня.