Zero to Hero, лекция 6: строим WaveNet (makemore, часть 5)
Подробный конспект лекции Карпатого: MLP-модель имён превращается в иерархическую сеть в духе WaveNet, а по дороге появляются свой мини-torch.nn, коварный баг в BatchNorm и связь со свёртками.
Коротко
Заголовок раздела «Коротко»- Карпатый берёт MLP-модель имён из третьей лекции (три символа контекста, один скрытый слой) и делает её глубже. Символы теперь сливаются не все разом, а попарно: пары, потом пары пар и так далее. Получается дерево, как в WaveNet — модели DeepMind 2016 года для генерации звука.
- По дороге код «пайторчизируется»: слои
Embedding,FlattenConsecutiveи контейнерSequentialпишутся вручную и повторяют интерфейсtorch.nn. После этой лекции Карпатый считаетtorch.nn«открытым» и дальше берёт готовые слои. - Главный технический приём: оператор
@в PyTorch умножает только по последней оси, а все оси левее считает батчем. Поэтому группы символов можно обрабатывать параллельно, аviewсклеивает соседей без копирования памяти. - Ловится тонкий баг: самописный BatchNorm на трёхмерном входе считает статистики не по тем осям — и при этом не падает.
- Итог по валидации: 2.105 → 1.993 при 76 тыс. параметров. Сам Карпатый предупреждает: без экспериментальной обвязки это «угадывание», а не исследование.
- Свёртки в WaveNet — не другая модель, а быстрый способ применить то же дерево сразу ко всем позициям текста.
Как смотреть вместе с курсом
Заголовок раздела «Как смотреть вместе с курсом»Смотрите после урока 4.1.4 «Нейросетевая языковая модель: MLP по Бенджио».
Урок курса повторяет вторую лекцию makemore на русских именах, а эта лекция — её прямое продолжение (четвёртую лекцию,
про ручной backprop, Карпатый сам называет отступлением). Пригодятся ещё два урока: 3.3.2 про nn.Module
и 3.4.1 про инициализацию и BatchNorm. Свёртки — причинные
и расширенные — курс разбирает в уроке 3.5.1, там
уже есть отсылка к этой лекции.
Новичку можно бегло пролистать середину главы про баг BatchNorm. Главное в лекции — следить за формами тензоров и понять,
что делает FlattenConsecutive.
Код лекции — ноутбук makemore_part5_cnn1.ipynb
(лицензия MIT). Файл с именами names.txt лежит в репозитории karpathy/makemore.
Если читаете по-китайски, есть учебная версия курса на китайском (репозиторий karpathy-zero-to-hero-zh-transcripts):
ноутбук 006_makemore_WaveNet с переведёнными
пояснениями и комментариями.
Карта лекции
Заголовок раздела «Карта лекции»Таймкоды — из глав в описании видео.
| Время | Глава в оригинале | О чём |
|---|---|---|
| 0:00 | intro | куда движемся: от MLP к WaveNet |
| 1:40 | starter code walkthrough | код из третьей лекции: данные, слои, обучение |
| 6:56 | let’s fix the learning rate plot | сглаживаем график loss через view |
| 9:16 | pytorchifying our code | свои Embedding, Flatten, Sequential; баг с режимом BatchNorm |
| 17:11 | overview: WaveNet | идея постепенного слияния контекста |
| 19:33 | dataset bump the context size to 8 | контекст 3 → 8 |
| 19:55 | re-running baseline code on block_size 8 | плоская сеть на 8 символах: 2.027 |
| 21:36 | implementing WaveNet | умножение по батч-осям, FlattenConsecutive |
| 37:41 | training the WaveNet: first pass | иерархия при том же числе параметров: 2.029 |
| 38:50 | fixing batchnorm1d bug | статистики по осям (0, 1) |
| 45:21 | re-training WaveNet with bug fix | 2.022 |
| 46:07 | scaling up our WaveNet | 76 тыс. параметров: 1.993 |
| 46:58 | experimental harness | почему без обвязки мы «в темноте» |
| 47:44 | WaveNet but with “dilated causal convolutions” | свёртка как цикл по позициям |
| 51:34 | torch.nn | мы переписали torch.nn — дальше пользуемся им |
| 52:28 | the development process of building deep neural nets | документация, формы, Jupyter → репозиторий |
| 54:17 | going forward | что будет в следующих лекциях |
| 55:26 | improve on my loss! | задание: побить 1.993 |
1. Отправная точка: MLP из третьей лекции
Заголовок раздела «1. Отправная точка: MLP из третьей лекции»Данные те же, что во всей серии makemore: 32 033 английских имени, самое длинное — 15 букв. Словарь — 26 букв и точка
., которая отмечает начало и конец имени, всего 27 символов. Имена перемешиваются (random.seed(42)) и делятся
на обучение, валидацию и тест в пропорции 80/10/10. Каждое имя режется на примеры «контекст → следующий символ»;
при контексте в три символа получается около 182 тыс. обучающих примеров.
Слои скопированы из третьей лекции: Linear (веса делятся на , это инициализация в духе Kaiming),
BatchNorm1d и Tanh. Карпатый ещё раз перечисляет, чем неудобен BatchNormпакетная нормализация (BatchNorm)EN batch normalizationНормирует каждый признак по примерам батча, хранит скользящие статистики для инференса (Ioffe, Szegedy, 2015). Стандарт CNN; в LLM не используется.:
- у слоя есть состояние — скользящие среднее и дисперсия. Они обучаются не градиентом, а экспоненциальным усреднением прямо в прямом проходе;
- поведение на обучении и на инференсе разное, и легко забыть переключить режим;
- слой связывает между собой примеры в батче, хотя обычно батч — просто способ считать быстрее.
Вывод Карпатого — «состояние обычно вредно». Чуть позже в лекции это подтверждается на практике.
Стартовая сеть: эмбеддинги размером 10, скрытый слой из 200 нейронов, около 12 тыс. параметров. Веса последнего слоя умножены на 0.1, чтобы на старте модель не была «уверенно неправа». Обучение — 200 тыс. шагов SGD с батчем 32; learning rate 0.1, после 150 тыс. шагов — 0.01. Результат: train 2.058, val 2.105. Сгенерированные имена уже похожи на настоящие, но запас для улучшения есть.
2. Приводим в порядок график loss
Заголовок раздела «2. Приводим в порядок график loss»График loss по шагам выглядит как «ёж»: батч из 32 примеров слишком мал, и loss сильно скачет от батча к батчу. Карпатый сглаживает его одной строкой. Список из 200 тыс. значений превращается в тензор, тензор переразбивается на 200 строк по 1000 соседних значений, и по каждой строке берётся среднее.
import torch
lossi = torch.rand(200_000) # заглушка: 200 тыс. значений loss по шагамsmooth = lossi.view(-1, 1000).mean(dim=1) # (200, 1000) -> среднее по строкеprint(smooth.shape) # torch.Size([200])view не копирует данные, а лишь иначе на них смотрит: тензор из 10 чисел можно увидеть как 2×5 или 5×2, а -1
просит PyTorch вычислить этот размер самому. На сглаженном графике хорошо видна ступенька на 150-тысячном шаге:
снижение learning rate «забирает энергию» у оптимизации, и модель оседает в минимуме.
3. Свой маленький torch.nn
Заголовок раздела «3. Свой маленький torch.nn»Прямой проход в стартовом коде неряшлив: часть слоёв лежит в списке layers, а таблица эмбеддингов и склейка
векторов окна записаны отдельно. Карпатый заворачивает их в слои — и называет так же, как в PyTorch:
Embeddingхранит матрицуweightи в прямом проходе просто индексирует её:self.weight[IX];Flattenвытягивает векторы символов окна в одну строку черезview. Это та же склейка (конкатенация), только бесплатная: память не копируется;Sequential— контейнер. Он хранит список слоёв, по очереди прогоняет через них вход и собирает параметры всех вложенных слоёв.
После этого весь прямой проход — одна строка logits = model(Xb), а сэмплирование и оценка loss упрощаются так же.
Курс показывает настоящие nn.Module и nn.Sequential в уроке 3.3.2;
здесь полезно увидеть, что внутри нет никакой магии.
Баг, которого не видно
Заголовок раздела «Баг, которого не видно»После переделки Карпатый запускает генерацию и получает мусор. Причина: ячейку, которая переводит слои в режим
оценки (layer.training = False), не перезапустили. BatchNorm остался в режиме обучения и при генерации получил
«батч» из одного примера. Дисперсия одного числа не определена:
torch.tensor([5.0]).var() # tensor(nan) и предупреждение: разброс одного числа не определёнnan расползся по всей сети. Хуже то, что при оценке loss на большом батче ошибки не было бы вовсе: код вернул бы
число, просто неправильное — посчитанное по статистикам батча вместо накопленных. Это и есть цена «состояния» в слое.
4. Идея WaveNet
Заголовок раздела «4. Идея WaveNet»Чтобы улучшить модель, хочется дать ей больше контекста и сделать её глубже. Но в нашей сети все символы сразу сплющиваются в один скрытый слой. Даже если сделать этот слой шире, сжимать столько информации за один шаг неразумно.
В WaveNet сделано иначе: контекст сливается постепенно. Два соседних символа объединяются в представление «биграммы», две биграммы — в представление четырёх символов, и так далее, пока не останется один вектор. На каждом уровне сливаются только два соседних элемента.
flowchart BT c1["c1"] --> p1["пара 1–2"] c2["c2"] --> p1 c3["c3"] --> p2["пара 3–4"] c4["c4"] --> p2 c5["c5"] --> p3["пара 5–6"] c6["c6"] --> p3 c7["c7"] --> p4["пара 7–8"] c8["c8"] --> p4 p1 --> q1["символы 1–4"] p2 --> q1 p3 --> q2["символы 5–8"] p4 --> q2 q1 --> r["вектор контекста"] q2 --> r r --> out["логиты следующего символа"]
В статье WaveNet эта схема нарисована как стопка «расширенных причинных свёрток» (dilated causal convolutions). Звучит страшно, но Карпатый подчёркивает: свёртки здесь — деталь реализации ради скорости. К ним он вернётся в конце лекции, а пока важна сама идея постепенного слияния. Сама WaveNet предсказывает следующий отсчёт звука, а не символ, но постановка та же: авторегрессионная модель угадывает следующий элемент последовательности.
5. Сначала — просто длиннее контекст
Заголовок раздела «5. Сначала — просто длиннее контекст»Прежде чем строить дерево, Карпатый проверяет «ленивую» базовую линию: тот же плоский MLP, но с контекстом из восьми символов. Первый линейный слой теперь получает чисел вместо 30, поэтому параметров становится около 22 тыс. Уже это даёт заметный выигрыш: val 2.105 → 2.027, и имена на глаз становятся лучше. Этот результат — точка сравнения для иерархической сети.
6. Реализация: умножение по батч-осям и FlattenConsecutive
Заголовок раздела «6. Реализация: умножение по батч-осям и FlattenConsecutive»Карпатый заводит «черновик»: батч из четырёх примеров и печать формы после каждого слоя. Сейчас картина такая:
| Слой | Форма выхода | Что произошло |
|---|---|---|
вход Xb | 4×8 | 4 примера по 8 номеров символов |
Embedding | 4×8×10 | у каждого символа вектор из 10 чисел |
Flatten | 4×80 | 8 векторов склеены в строку |
Linear | 4×200 | матричным умножением |
Ключевое наблюдение
Заголовок раздела «Ключевое наблюдение»Матричное умножение в PyTorch не требует двумерного входа. Умножение идёт только по последней оси, а все оси левее считаются батчем и остаются как есть:
x = torch.randn(4, 5, 80)W = torch.randn(80, 200)print((x @ W).shape) # torch.Size([4, 5, 200]) — слева можно добавить сколько угодно осейЗначит, на первом уровне дерева в Linear нужно подавать не 80 чисел, а 20 — векторы двух соседних символов.
Восемь символов дают четыре такие пары, и их можно обработать параллельно, как вторую ось батча: нужен вход
4×4×20 вместо 4×80.
Склейка соседей через view
Заголовок раздела «Склейка соседей через view»Пары можно собрать явно: взять символы на чётных позициях, символы на нечётных и склеить по последней оси.
Но view даёт ровно тот же результат — соседние векторы в памяти и так лежат подряд:
e = torch.randn(4, 8, 10) # выход Embeddingexplicit = torch.cat([e[:, ::2, :], e[:, 1::2, :]], dim=2) # чётные + нечётные позицииprint(explicit.shape) # torch.Size([4, 4, 20])print((e.view(4, 4, 20) == explicit).all()) # tensor(True)Отсюда новый слой. Он склеивает n соседних векторов и кладёт их в последнюю ось. Карпатый называет его
FlattenConsecutive, а не Flatten, потому что поведение уже отличается от torch.flatten. Код из ноутбука лекции
(MIT), комментарии переведены:
class FlattenConsecutive:
def __init__(self, n): self.n = n # сколько соседних векторов склеивать
def __call__(self, x): B, T, C = x.shape x = x.view(B, T // self.n, C * self.n) # T/n групп, в каждой n векторов подряд if x.shape[1] == 1: # если осталась одна группа, x = x.squeeze(1) # убираем лишнюю ось: (B, 1, C) -> (B, C) self.out = x return self.out
def parameters(self): return [] # параметров у слоя нетКарпатый сознательно не пишет -1 в view: при явных размерах ошибка в формах сразу даст исключение, а не тихо
пройдёт дальше. С FlattenConsecutive(8) получается старое поведение, с FlattenConsecutive(2) — дерево:
model = Sequential([ Embedding(vocab_size, n_embd), FlattenConsecutive(2), Linear(n_embd * 2, n_hidden, bias=False), BatchNorm1d(n_hidden), Tanh(), FlattenConsecutive(2), Linear(n_hidden * 2, n_hidden, bias=False), BatchNorm1d(n_hidden), Tanh(), FlattenConsecutive(2), Linear(n_hidden * 2, n_hidden, bias=False), BatchNorm1d(n_hidden), Tanh(), Linear(n_hidden, vocab_size),])Формы по слоям (батч 4, эмбеддинг 10, скрытый размер 200): 4×8×10 → 4×4×20 → 4×4×200 → 4×2×400 → 4×2×200 → 4×400
→ 4×200 → 4×27. Последний FlattenConsecutive убирает ось размера 1, и логиты получаются той же формы, что и раньше.
У нашей сети три уровня, то есть рецептивное полерецептивное полеEN receptive fieldОбласть входа, от которой зависит один нейрон. Две свёртки 3×3 подряд видят 5×5, три — 7×7: глубина расширяет «взгляд». — 8 символов; на картинке из статьи
WaveNet уровней четыре и поле — 16.
Чтобы сравнение было честным, Карпатый уменьшает скрытый слой до 68 нейронов: тогда параметров снова около 22 тыс. Результат — val 2.029 против 2.027 у плоской сети. При равном числе параметров иерархия пока ничего не дала. Карпатый объясняет это двумя причинами: архитектуру никто не подбирал, и, возможно, внутри ещё сидит баг.
7. Баг в BatchNorm1d
Заголовок раздела «7. Баг в BatchNorm1d»Баг действительно есть. На вход первого BatchNorm теперь приходит тензор 32×4×68 (батч × группы × каналы), а слой писался для двумерного входа и считает среднее только по нулевой оси:
x = torch.randn(32, 4, 68)print(x.mean(0, keepdim=True).shape) # torch.Size([1, 4, 68]) — 4×68 отдельных среднихprint(x.mean((0, 1), keepdim=True).shape) # torch.Size([1, 1, 68]) — по одному на каналОшибки не возникает: broadcastingbroadcasting (растягивание форм)EN broadcastingПравило, по которому NumPy и PyTorch выполняют операции над массивами разных форм без копирования данных. молча согласует формы. Но слой ведёт себя не так,
как задумано. Он хранит отдельные статистики для каждой из четырёх групп, и каждая оценена всего по 32 числам.
Буфер running_mean получается формы 1×4×68 вместо 68 чисел. Нужно, чтобы ось групп считалась батчем, как нулевая.
Функции mean и var принимают кортеж осей, поэтому исправление короткое:
if x.ndim == 2: dim = 0 # (N, C): статистики по батчуelif x.ndim == 3: dim = (0, 1) # (N, L, C): батч и группы — обе оси «батчевые»xmean = x.mean(dim, keepdim=True)xvar = x.var(dim, keepdim=True)Теперь каждое среднее оценивается по числам, статистики стабильнее, и val улучшается: 2.029 → 2.022. Карпатый честно оговаривается: разница маленькая, и неясно, значима ли она статистически.
8. Масштабирование и журнал результатов
Заголовок раздела «8. Масштабирование и журнал результатов»С исправленной архитектурой сеть можно растить. Эмбеддинг — 24, скрытый слой — 128, всего 76 579 параметров. Обучение идёт заметно дольше, зато val впервые опускается ниже 2.0. Журнал из ноутбука лекции:
| Шаг | Параметров | train | val |
|---|---|---|---|
| исходная MLP: контекст 3, 200 нейронов | 12 тыс. | 2.058 | 2.105 |
| контекст 3 → 8 | 22 тыс. | 1.918 | 2.027 |
| плоская сеть → иерархическая | 22 тыс. | 1.941 | 2.029 |
| исправлен BatchNorm | 22 тыс. | 1.912 | 2.022 |
| эмбеддинг 24, скрытый слой 128 | 76 тыс. | 1.769 | 1.993 |
Примеры имён в конце ноутбука: hendrix, jamylie, marianah, astavia, jaylene, aubreana.
В главе про экспериментальную обвязку Карпатый сбивает пафос. Улучшение с 2.1 до 1.99 — не главное. Эксперименты стали долгими, гиперпараметры подобраны наугад, мы смотрели в основном на кривую обучения — «мы в темноте». В настоящей работе нужна обвязка: скрипт с аргументами, много параллельных запусков, графики train и val loss рядом, поиск гиперпараметров. Кроме того, из статьи WaveNet реализована только структура: в оригинале есть ещё гейтированные активации, а также остаточные и skip-связи.
9. При чём тут свёртки
Заголовок раздела «9. При чём тут свёртки»Возьмём из обучающей выборки одно имя — в ноутбуке это «diondre». В нём семь букв, значит, восемь независимых примеров: от «… → d» до «.diondre → .». Можно восемь раз вызвать модель на каждой строке. Свёртка делает то же самое, но лучше по двум причинам:
- Цикл уходит внутрь ядра CUDA. Вместо восьми вызовов из Python одно и то же «дерево» скользит по последовательности, как фильтр, а цикл выполняется на видеокарте.
- Промежуточные значения переиспользуются. Узел дерева для одной позиции одновременно служит левым потомком соседнего дерева. При восьми отдельных вызовах он пересчитывается заново, а в свёртке — один раз.
Линейные слои дерева становятся фильтрами свёртки шириной 2. На первом уровне фильтр смотрит на соседние позиции, на втором — на позиции через одну (расширение 2), на третьем — через три (расширение 4). Отсюда и название: расширеннаярасширенная («дырявая») свёрткаEN dilated (atrous) convolutionЯдро с промежутками: при dilation = d между соседними элементами ядра d − 1 пропущенных пикселей. Ядро 3×3 с d = 2 охватывает окно 5×5 без новых параметров. причинная свёрткапричинная свёрткаEN causal convolutionОдномерная свёртка по времени, которая видит только текущий и прошлые шаги: рамку из нулей добавляют только слева (k − 1 нулей). Так устроены WaveNet и короткие свёртки в слоях линейного внимания.. Причинная — потому что смотрит только назад, в прошлое.
Проверим сами
Заголовок раздела «Проверим сами»Карпатый показывает эту связь на словах и обещает свёрточную версию в будущих лекциях. Её легко проверить кодом. Ниже мы считаем одни и те же три слоя (без BatchNorm, для простоты) двумя способами: восемь вызовов «дерева» на скользящем окне и три причинные свёртки с расширениями 1, 2, 4. Ответы совпадают.
import torchimport torch.nn.functional as Ftorch.manual_seed(0)
T, C, H = 8, 10, 16 # длина контекста, размер эмбеддинга, скрытый размерx = torch.randn(T, C) # эмбеддинги 8 символов одного примера
# три «линейных слоя дерева»: каждый склеивает пару соседей и проецируетW1, b1 = torch.randn(2 * C, H) * 0.3, torch.randn(H) * 0.1W2, b2 = torch.randn(2 * H, H) * 0.3, torch.randn(H) * 0.1W3, b3 = torch.randn(2 * H, H) * 0.3, torch.randn(H) * 0.1
def tree(window): # window: (8, C) -> вектор (H,) — одно «дерево», как в лекции h = window.view(4, 2 * C) @ W1 + b1 # 4 пары символов h = torch.tanh(h).view(2, 2 * H) @ W2 + b2 # 2 пары «биграмм» h = torch.tanh(h).view(1, 2 * H) @ W3 + b3 # 1 пара «четырёхграмм» return torch.tanh(h)[0]
def as_conv(W, Cin): # тот же линейный слой как веса свёртки: (out, in, ширина 2) return torch.stack([W[:Cin].T, W[Cin:].T], dim=2)
def causal_conv(seq, W, b, Cin, dilation): # seq: (Cin, L) seq = F.pad(seq, (dilation, 0)) # причинность: дополняем только слева return torch.tanh(F.conv1d(seq[None], as_conv(W, Cin), b, dilation=dilation)[0])
# способ 1: восемь отдельных вызовов дерева на скользящем окнеxp = torch.cat([torch.zeros(7, C), x]) # 7 «пустых» позиций слева, как точки в makemorenaive = torch.stack([tree(xp[t:t + 8]) for t in range(T)]) # (8, H)
# способ 2: те же веса как свёртки — все позиции за один проходh = xp.T # (C, 15)h = causal_conv(h, W1, b1, C, 1)h = causal_conv(h, W2, b2, H, 2)h = causal_conv(h, W3, b3, H, 4)conv = h.T[-T:] # последние 8 позиций — те же окна
print(torch.allclose(naive, conv, atol=1e-6)) # TrueМодель одна и та же, меняется только способ вычисления. Подробно о свёртках, расширении и рецептивном поле — в уроке 3.5.1.
10. torch.nn и как на самом деле выглядит работа
Заголовок раздела «10. torch.nn и как на самом деле выглядит работа»Подводя итоги, Карпатый делает несколько замечаний.
Мы переписали torch.nn. Всё, что строилось в серии, — это библиотека слоёв поверх torch.Tensor: модули,
вложенные друг в друга, контейнеры, параметры и буферы. Дальше Карпатый пользуется настоящим torch.nn.
Процесс разработки он описывает так:
- много времени уходит на документацию PyTorch: какие формы принимает слой, что именно он делает. Документацию Карпатый ругает: по его словам, она бывает неполной, неясной и даже неверной, так что приходится перепроверять;
- много «гимнастики» с формами: двумерный тензор или трёхмерный, NCL или NLC, где нужен
permute, гдеview; - слой сначала прототипируется в Jupyter, пока формы не сойдутся. Потом код переносится в репозиторий (у Карпатого рядом открыт VS Code), и эксперименты запускаются уже оттуда.
Что дальше в серии: настоящие свёрточные слои вместо дерева; остаточные и skip-связи и зачем они нужны; экспериментальная обвязка; рекуррентные сети (RNN, LSTM, GRU) и трансформеры. Следующая лекция — сразу GPT.
11. Задание: побейте 1.993
Заголовок раздела «11. Задание: побейте 1.993»В самом конце Карпатый предлагает соревнование: он почти не экспериментировал, и запас ещё есть. Идеи из видео:
- распределить каналы между слоями иначе;
- поменять размер эмбеддинга — возможно, он подобран неудачно;
- раздуть исходную сеть с одним скрытым слоем и проверить, не обгонит ли она «модную» иерархическую (Карпатый признаёт, что это было бы неловко, но исключать нельзя);
- прочитать статью WaveNet и реализовать её слои: гейтированную активацию, остаточные и skip-связи;
- подкрутить инициализацию и оптимизацию.
Где курс объясняет то же самое и что есть сверх курса
Заголовок раздела «Где курс объясняет то же самое и что есть сверх курса»| Тема лекции | Где в курсе | Что в лекции сверх курса |
|---|---|---|
| MLP-модель символов, train/val/test, генерация | 4.1.4 (на русских именах) | контекст 8 и иерархическое слияние |
nn.Module, nn.Sequential, параметры и буферы | 3.3.2 | сборка Embedding, FlattenConsecutive, Sequential с нуля |
| BatchNorm, режимы train/eval | 3.4.1 | баг осей на трёхмерном входе, соглашения NCL и NLC |
| Причинные и расширенные свёртки, рецептивное поле | 3.5.1 | свёртка, выведенная из MLP как «цикл по позициям» |
| Отладка: формы, журнал запусков | 3.3.3 | рабочий процесс Карпатого: Jupyter → репозиторий |
| Слабость фиксированного окна | 4.1.4, 4.1.5 | — |
Чего в лекции нет совсем: остаточных связей, Adam и нормального подбора гиперпараметров — всё это появится в следующих лекциях и в курсе (модуль 3.4, урок 4.6.3).
Что сделать руками
Заголовок раздела «Что сделать руками»- Скачайте ноутбук лекции и
names.txt, запустите все ячейки. Модель маленькая, CPU хватит. Можно взять русские имена из урока 4.1.2 — меняется только файл с данными. - После одного прямого прохода напечатайте формы всех слоёв:
for layer in model.layers: print(layer.__class__.__name__, tuple(layer.out.shape)). - Откатите исправление BatchNorm и посмотрите на
model.layers[3].running_mean.shape. Объясните, откуда берётся 1×4×68. - Замените свои слои на
nn.Embedding,nn.Linear,nn.BatchNorm1d. Где понадобитсяpermuteи почему? - Сравните плоскую и иерархическую сети при равном числе параметров на нескольких seed и ведите журнал, как у Карпатого. Значима ли разница 2.029 против 2.027?
- Реализуйте свёрточную версию через
nn.Conv1d(..., dilation=...)и считайте loss сразу на всех позициях имени, а не на одном окне. Сколько это экономит прямых проходов на эпоху? - Попробуйте побить 1.993 на валидации — и только в самом конце один раз проверьте результат на тесте.
Словарик
Заголовок раздела «Словарик»| EN | RU |
|---|---|
| hierarchical model | иерархическая модель |
| receptive field | рецептивное поле |
| dilated causal convolution | расширенная причинная свёртка |
container (Sequential) | контейнер |
buffer (running_mean, running_var) | буфер (скользящее среднее и дисперсия) |
| train / eval mode | режим обучения / режим оценки |
| broadcasting | broadcasting (растягивание форм) |
| batch dimension | ось батча, батчевая ось |
| CUDA kernel | ядро CUDA |
| gated activation | гейтированная активация |
| skip connection | skip-связь |
| experimental harness | экспериментальная обвязка |