Обучение нейросетей с нуля на чистом Си без единой внешней библиотеки — задача, от которой у большинства разработчиков сдадут нервы, но Харш Бхатт доказал её выполнимость на примере классической «Змейки». Создав собственный движок автодифференцирования, эффективный арена-аллокатор памяти и алгоритм REINFORCE прямо в procedural-парадигме, он наглядно показал, как устроены ML-фреймворки изнутри. Это хардкорное погружение в инженерию искусственного интеллекта без магии высокоуровневых абстракций.
🛠️ Фундамент RL-библиотеки на Си: архитектура и управление памятью 0:00
Обзор проекта: RL-библиотека на Си с нуля 0:00
В современном программировании создание нейросетей обычно ассоциируется с высокоуровневыми фреймворками, такими как PyTorch или TensorFlow, однако Харш Бхатт (Harsh Bhatt) предлагает радикально иной подход. Цель данного курса — построить полноценную библиотеку для обучения с подкреплением (Reinforcement Learning) на чистом языке Си, не используя сторонние ML-библиотеки . Основной фокус направлен на реализацию алгоритма градиента политики REINFORCE . Чтобы доказать работоспособность системы, автор планирует не просто написать математическое ядро, но и реализовать с нуля окружение классической игры «Змейка», где агент будет учиться навигации и поиску пищи в режиме реального времени .
Харш Бхатт (Harsh Bhatt) подчёркивает, что для понимания курса желательно иметь базовое представление о цикле взаимодействия агента со средой: как совершаются действия, как среда возвращает награды и каким образом происходит обучение на основе обратной связи . Проект разделен на несколько фундаментальных блоков: создание движка автоматического дифференцирования (autograd), разработку модели нейросети, проектирование игрового окружения и реализацию самого алгоритма обучения . Вдохновением для проекта послужили работы других разработчиков в области низкоуровневых ML-библиотек, что подталкивает к исследованию того, как именно нейронные сети функционируют «под капотом» на уровне указателей и структур данных .
Проектирование движка автоградиентных вычислений 2:27
Сердцем любой современной библиотеки глубокого обучения является механизм автоматического дифференцирования. Харш Бхатт (Harsh Bhatt) начинает проектирование с этого фундаментального элемента, так как обучение нейросети требует прохождения вычислительного графа в двух направлениях: прямом (forward pass) для получения предсказания и обратном (backward pass) для вычисления градиентов . Этот процесс позволяет автоматически определять, как изменение каждого веса влияет на итоговую ошибку, что и называется автодифференцированием .
Основной единицей данных в этой системе выступает структура var (переменная). Она спроектирована как узел графа и содержит следующие элементы:
- Уникальный индекс (ID) для идентификации в модели .
- Массив входных данных (inputs), так как одна переменная может быть результатом операции над другими . Например, если $C = A + B$, то $C$ является переменной, чьими входами являются $A$ и $B$ .
- Флаги управления (flags), определяющие свойства переменной .
- Две матрицы: одна для хранения текущего значения (value), другая для градиента (gradient) .
Для описания операций вводится структура VarType, которая хранит указатели на функции прямого и обратного прохода, имя операции и параметры её формы (shape) . Харш Бхатт (Harsh Bhatt) уточняет, что такие операции, как ReLU или Softmax, требуют только одного входа, в то время как сложение или умножение матриц — минимум двух . Это разделение на типы переменных позволяет движку гибко выстраивать цепочки вычислений любой сложности .
Управление памятью и структура мем-арены 10:00
Одной из самых сложных задач при написании ML-библиотеки на Си является эффективное выделение памяти под тысячи узлов графа и тензоров. Чтобы избежать фрагментации и накладных расходов от стандартного malloc, Харш Бхатт (Harsh Bhatt) использует концепцию «арены памяти» (Memory Arena), подключая заголовочный файл arena.h . Арена представляет собой заранее выделенный большой буфер памяти, из которого последовательно нарезаются блоки для нужд приложения .
Для управления глобальным состоянием системы создается структура Model, которая интегрируется с ареной. В этой структуре хранятся:
- Текущее количество созданных переменных (
number_of_vars) . - Значения преимуществ (advantages), необходимые для алгоритмов RL .
- Указатель на саму арену для аллокации новых узлов.
При создании новой матрицы внутри графа используется функция push_array, которая просто сдвигает указатель внутри арены на нужный размер (количество строк умноженное на количество столбцов) . Это гарантирует, что все данные графа лежат в памяти компактно. При инициализации переменной через create_var автор также вводит систему флагов, например requires_grad . Если флаг установлен, система автоматически выделяет память под матрицу градиентов; если нет — экономит ресурс, создавая только матрицу значений . Каждая новая переменная автоматически регистрируется в состоянии модели, инкрементируя общий счетчик, что позволяет легко отслеживать размер вычислительного графа в реальном времени . В завершение Харш Бхатт (Harsh Bhatt) подготавливает структуру самого графа, которая будет содержать массив указателей на все задействованные переменные .
🏗️ Проектирование узлов и механизмы формирования графа 25:01
На данном этапе Харш Бхатт (Harsh Bhatt) переходит от базовых структур данных к архитектуре вычислительного графа, который станет «сердцем» нейросетевого движка. Главная задача здесь — обеспечить связность операций и автоматизировать проверку математической корректности вычислений еще на этапе сборки модели .
Создание узлов и логика вычислительных операций 25:01
Основой графа является узел (node), который Харш Бхатт определяет как структуру, инкапсулирующую переменную и её связи . В отличие от простых массивов данных, узел в автоградиентном движке должен «знать», из каких входных компонентов он был получен. Функция create_node принимает ссылки на две родительские переменные — A и B . Это позволяет движку восстанавливать цепочку вычислений в обратном направлении.
Внутри реализации узла Харш Бхатт выделяет несколько критических компонентов:
- Валидация размерностей (Shape Check): Для каждой операции (будь то сложение или умножение матриц) вызывается функция проверки формы . Она возвращает булево значение: если формы векторов или матриц несовместимы, создание узла прерывается и возвращается
null. - Автоматическое вычисление выходной формы: Разработчику не нужно вручную указывать размер результирующей матрицы. Вместо этого в функцию проверки передаются указатели на переменные
rows(строки) иcolumns(столбцы), которые обновляются внутри логики самой операции . - Наследование градиентных флагов: Харш Бхатт внедряет изящное решение для управления памятью и вычислениями через флаг
requires_grad. Если хотя бы одна из входных переменных (A или B) требует вычисления градиента, результирующий узел автоматически получает этот статус . Это критично для эффективной работы — если цепочка вычислений не ведет к обучаемым параметрам, алгоритм не будет тратить ресурсы на хранение производных для этих узлов .
Ранее в разговоре автор касался структуры мем-арены, и здесь она используется для выделения памяти под новые переменные в рамках модели . Каждый узел также получает свой тип (v_type), определяющий, какая именно математическая операция будет выполняться при прямом проходе .
Алгоритм обхода графа в обратном порядке 35:57
Когда узлы созданы и связаны, возникает следующая проблема: в каком порядке их нужно выполнять? Харш Бхатт объясняет, что для нейронной сети порядок вычислений при прямом проходе должен быть строго последовательным, от входа к выходу, а при обратном — наоборот . Для автоматизации этого процесса он реализует алгоритм поиска в глубину (DFS) с использованием стека .
Процесс построения графа начинается с финального узла — например, функции активации Softmax на выходе сети . Отталкиваясь от этого «выходного провода» (outwire), алгоритм начинает рекурсивно искать все зависимости, которые привели к получению данного результата .
Харш Бхатт детально разбирает проблему дублирования зависимостей, демонстрируя это на слайдах . В сложных графах одна и та же переменная может использоваться в разных ветвях вычислений (например, переменная B может быть входом и для узла X, и для узла Y) . Если просто добавлять все найденные узлы в стек, переменная B окажется там дважды, что приведет к ошибкам при расчете градиентов и избыточным операциям .
Для решения этой проблемы автор вводит следующие механизмы:
- Массив посещенных узлов (visited): Каждому узлу присваивается индекс, и алгоритм помечает его как «посещенный», чтобы не заходить в одну и ту же ветку дважды .
- Динамическое переупорядочивание стека: Если алгоритм обнаруживает, что узел уже находится в стеке, но встречается снова как зависимость другого пути, происходит «сдвиг» . Харш Бхатт реализует логику перемещения элементов: старая позиция узла удаляется, а сам узел переносится в правильное место, чтобы гарантировать топологический порядок выполнения .
- Итеративный цикл: Вместо классической рекурсии, которая может привести к переполнению стека в глубоких сетях на Си, Харш использует цикл
while stack_size > 0.
Результатом работы этого алгоритма становится массив узлов, выстроенный в идеальном порядке для выполнения вычислений . Харш Бхатт подчеркивает, что такая архитектура позволяет строить графы любой сложности, сохраняя при этом контроль над памятью и эффективностью, что особенно важно при разработке на языке Си с нуля .
🧠 Реализация операторов: от функций активации до матричных вычислений 50:04
После того как фундамент вычислительного графа заложен, Харш Бхатт (Harsh Bhatt) переходит к «сердцу» библиотеки — реализации конкретных математических операций, которые будут выполняться в узлах этого графа . На этом этапе фокус смещается с абстрактных структур на прикладную математику нейронных сетей, начиная от базовых функций активации и заканчивая сложными алгоритмами матричного умножения с поддержкой транспонирования .
Математика активаций: Softmax, ReLU и проверка размерностей 51:09
Реализация нейросетевых операторов начинается с определения типов переменных (v_type) и написания вспомогательных функций для валидации данных . Харш Бхатт подчёркивает, что перед выполнением любой операции необходимо убедиться в совместимости тензоров . Для этого внедряется функция var_shape, которая проверяет, совпадают ли строки и столбцы у операндов . Если формы корректны, функция обновляет метаданные узла, фиксируя правильные размерности для выходного тензора .
Особое внимание уделяется функции Softmax, которая критически важна для модели Actor (в рамках алгоритма REINFORCE, выбранного автором из-за его относительной простоты по сравнению с PPO) . При кодировании Softmax в прямом проходе (forward) Харш использует стандартный подход с обеспечением численной стабильности . Алгоритм включает в себя:
- Поиск максимального значения во входном векторе для предотвращения переполнения при экспонировании .
- Вычисление экспонент разностей (Zi - max) .
- Нормализацию полученных значений путём деления на их общую сумму .
Параллельно реализуется функция ReLU . Её логика в Си-коде предельно проста: итерация по массиву данных и обнуление всех отрицательных значений, в то время как положительные остаются без изменений . Это базовый кирпичик нелинейности, который позволяет сети обучаться сложным зависимостям . Ранее в разговоре автор уже упоминал, что все эти операции вписываются в общую структуру узлов графа, где для каждого типа прописаны указатели на функции прямого и обратного прохода .
Поэлементные операции и градиенты 1:03:41
Помимо функций активации, нейросети требуют выполнения множества арифметических действий над тензорами. Харш Бхатт реализует поэлементное сложение (add) и вычитание (sub) . Код этих операций строится по схожему шаблону: вычисляется общий размер данных (количество столбцов, помноженное на количество строк), после чего запускается цикл, выполняющий операцию для каждого индекса .
При реализации градиентов (обратного прохода) для этих операций логика остаётся прямолинейной: для сложения градиент просто передаётся дальше, так как производная суммы по каждому слагаемому равна единице . Эти низкоуровневые манипуляции с памятью напрямую взаимодействуют с системой управления ресурсами, созданной в начале проекта, что позволяет избегать утечек при постоянном создании временных тензоров во время обучения .
Матричное умножение и магия транспонирования 1:04:29
Самой сложной и важной частью этого этапа является реализация матричного умножения (MatMul) . В отличие от простых поэлементных функций, MatMul требует трёх вложенных циклов и строгой проверки совместимости (число столбцов первой матрицы должно быть равно числу строк второй) .
Харш Бхатт объясняет, что в библиотеке используется «строчный порядок» (row-major order) хранения данных в памяти . Это означает, что двумерная матрица разворачивается в одномерный массив, и для доступа к элементу используется формула: index = row * num_columns + column .
Для эффективного обучения нейросети (Backpropagation) недостаточно просто уметь умножать матрицы A и B. Часто требуется умножать транспонированные матрицы (например, при расчёте градиентов по весам или по входу) . Вместо того чтобы физически копировать и переставлять данные в памяти (что затратно), Харш реализует логику «виртуального» транспонирования через изменение формул индексации внутри алгоритма умножения :
- mat_n: Стандартное умножение (No Transpose) .
- mat_nt: Умножение A на транспонированную B .
- mat_tn: Умножение транспонированной A на B .
- mat_tt: Оба операнда транспонированы .
Такой подход позволяет динамически изменять порядок обхода памяти. Например, если матрица A транспонирована, то вместо итерации по строкам алгоритм начинает интерпретировать её структуру так, будто строки стали столбцами, меняя переменные i и k в формуле смещения . В завершение Харш объединяет все эти случаи в одну универсальную функцию mat, которая выбирает нужный алгоритм индексации в зависимости от флагов, переданных узлом вычислительного графа .
🎮 Проектирование игрового окружения и векторизация состояний 1:15:04
Прежде чем переходить к архитектуре нейронной сети, Харш Бхатт завершает работу над вычислительным бэкендом, реализуя функции для градиентов REINFORCE . Он подчёркивает, что для этого алгоритма градиент рассчитывается как отношение вознаграждения к вероятности действия с отрицательным знаком . Завершив математическую подготовку, включая вычисление градиента Softmax через скалярное произведение данных , автор переходит к созданию полигона для обучения агента — классической игры «Змейка» .
Логика и структура окружения «Змейка» 1:23:47
Харш Бхатт решает создать среду «Змейки» (Snake Environment) как простую, но эффективную задачу для демонстрации возможностей RL-библиотеки на Си . Проектирование начинается с определения перечислений для действий (actions): влево (0), вправо (1), вверх (2) и вниз (3) . В основе окружения лежит структура snake_env, которая хранит в себе полное состояние игрового мира: положение тела змейки, координаты еды, текущий счёт и количество пройденных шагов .
Ключевым аспектом механики является POV (Point of View) — точка обзора или текущее направление движения змейки . В отличие от классических аркад, где змейка может мгновенно изменить направление по нажатию клавиши, в этой реализации модель управляет направлением движения (POV), которое сохраняется до следующего шага . Если модель решает не предпринимать никаких действий (none), змейка продолжает двигаться в текущем направлении POV .
Для управления игровым полем автор использует сетку, размеры которой определяются через строки (rows) и столбцы (columns) . Сетка чаще всего проектируется квадратной, где положение каждой точки (например, головы змейки или еды) задаётся координатами X и Y . Начальное состояние инициализируется в левом нижнем углу (0, 0) с направлением движения вправо . Также реализована функция сброса среды (reset_env), которая возвращает змейку в исходную точку и генерирует новое случайное положение еды с помощью встроенного генератора псевдослучайных чисел .
Сбор опыта и буфер воспроизведения (Replay Buffer) 1:26:13
Одной из фундаментальных особенностей обучения с подкреплением является отсутствие заранее подготовленных данных — агент создаёт их сам в процессе взаимодействия со средой . Харш Бхатт вводит понятие «роллаутов» (rollouts) — процесса сбора коллекций данных в ходе игры . Для хранения этого опыта создаётся структура replay_buffer, которая аккумулирует траектории движения .
Каждая траектория включает в себя:
- Последовательность состояний, через которые прошла змейка .
- Действия, предпринятые в каждом состоянии .
- Полученные награды и возвраты (returns) .
- Флаг завершения игры (
done), сигнализирующий о столкновении или победе .
Размер буфера в данном проекте определён в 1024 записи, а максимальная длина эпизода ограничена 100–200 шагами . Это предотвращает бесконечные циклы, если змейка начнёт двигаться по кругу, не достигая цели . Автор поясняет, что после завершения эпизода (или заполнения буфера) накопленный опыт используется для корректировки весов модели, чтобы «исправить» действия агента в будущем . Ранее в разговоре они кратко касались структуры мем-арены, и здесь Харш также использует арену для эффективного выделения памяти под игровое окружение .
Векторизация состояния и система наград 1:36:16
Чтобы нейронная сеть могла «видеть» игровое поле, Харш Бхатт реализует механизм векторизации — преобразования структуры игры во входной вектор (embeddings/one-hot vector) . Поскольку модель не может напрямую обрабатывать координаты объектов, мир змейки представляется в виде плоского массива (flat vector) . Для поля размером 6x6 (36 ячеек) создаётся вектор, где позиции головы змейки и еды помечаются значением 1.0, а пустые клетки — 0.0 .
Процесс перевода 2D-координат (x, y) в индекс плоского массива выполняется по формуле: index = x + (y * columns) . Таким образом, если голова змейки находится в определённой ячейке, соответствующий индекс в векторе состояния получает сигнал . В этот же вектор добавляется информация о текущем POV, что позволяет модели учитывать инерцию движения .
Система вознаграждений в окружении построена на простых правилах :
- Положительная награда: начисляется при поедании еды, что увеличивает общий счёт (
score) . - Штраф (отрицательная награда): применяется, если змейка врезается в стену или собственное тело .
- Стимул к эффективности: каждое действие, не приводящее к смерти, может иметь небольшой штраф или нейтральное значение, чтобы поощрять поиск кратчайшего пути к еде .
Этот векторный вход становится основой для обучения: модель анализирует паттерны расположения еды и стен, чтобы предсказать действие, максимизирующее суммарную награду за эпизод .
🏗️ Построение пайплайна обучения и сбор опыта (Rollouts) 1:44:07
После того как Харш Бхатт (Harsh Bhatt) завершил работу над логикой вознаграждений в окружении «Змейка», где агент получает штрафы за столкновения и бонусы за еду , он переходит к критически важному этапу — созданию пайплайна обучения. В Reinforcement Learning (RL) этот процесс представляет собой циклическую последовательность: сбор траекторий (rollouts), расчет возвратов, сохранение опыта в буфер и последующее обновление весов модели .
Сердцем этого процесса является функция train, которая управляет жизненным циклом агента на протяжении множества эпох. Харш Бхатт (Harsh Bhatt) подчеркивает, что структура обучения в Си, несмотря на отсутствие высокоуровневых абстракций вроде тех, что есть в PyTorch, строится на тех же принципах . Основные этапы пайплайна включают:
- Цикл по эпохам: Глобальный итератор, определяющий общую продолжительность обучения .
- Сбор роллаутов (Rollouts): Внутри каждой эпохи агент взаимодействует с окружением в течение фиксированного количества шагов или до завершения эпизода .
- Использование буфера траекторий: Для хранения данных о текущем опыте создается структура
trajectory, которая выступает в роли временного буфера . В ней фиксируются состояния, действия и полученные награды. - Управление гиперпараметрами: На этом этапе инициализируются такие значения, как длина эпизода (
episode length) и коэффициент дисконтирования гамма (gamma), необходимые для будущих расчетов .
Особое внимание Харш уделяет организации взаимодействия модели с графом вычислений. Для каждого шага в роллауте необходимо построить вычислительный граф, выполнить прямой проход для получения вероятностей действий и сохранить состояние графа для последующего обратного прохода . Ранее в разговоре автор уже упоминал структуру мем-арены, и здесь она становится незаменимой, позволяя быстро выделять и очищать память для временных узлов графа во время каждой итерации сбора опыта .
Чтобы эффективно собирать опыт, Харш реализует цикл, ограниченный длиной эпизода . На каждом шаге агент анализирует вектор состояния, полученный из окружения, и выбирает действие . Все эти переходы должны быть атомарно записаны в буфер, чтобы алгоритм обучения мог корректно соотнести действия агента с итоговым результатом.
🧠 Инициализация архитектуры Actor-Critic и весовых слоев 1:59:22
Для управления поведением «Змейки» Харш Бхатт (Harsh Bhatt) приступает к проектированию архитектуры Actor-Critic модели. Несмотря на название, в данном фрагменте основной упор делается на инициализацию слоев, которые будут предсказывать вероятности действий (Actor) . Реализация на чистом Си требует ручного создания каждого тензора весов и смещений, а также определения их связей в графе.
Архитектура нейросети, предложенная автором, имеет следующие параметры:
- Входной слой: 76 нейронов . Эта цифра не случайна: 36 ячеек отведены под сетку игрового поля (grid size), еще 36 — под координаты еды, и 4 нейрона кодируют направление взгляда (POV) агента .
- Скрытые слои: Два полносвязных слоя по 128 нейронов в каждом для обеспечения достаточной емкости модели .
- Выходной слой: Слой, соответствующий количеству возможных действий (в данном случае оптимизировано до 4-5 вариантов) .
Процесс инициализации в функции create_actor_critic_model начинается с выделения памяти через мем-арену для структур весов (weights) и смещений (bias) . Харш Бхатт (Harsh Bhatt) использует функцию для заполнения параметров случайными значениями (random initialization), что критически важно для предотвращения симметрии нейронов при старте обучения . Для каждого тензора параметров устанавливается флаг requires_grad, указывающий движку автограда, что для этих узлов необходимо вычислять градиенты .
Связывание слоев происходит через создание узлов матричного умножения (v_mat_mul) и сложения (v_add) . Автор буквально «прошивает» архитектуру:
- Результат умножения входа на первые веса (
weight0) суммируется со смещением (bias0) . - Полученное значение проходит через функцию активации ReLU для внесения нелинейности и нормализации промежуточных вычислений .
- Финальный слой завершается операцией Softmax, которая превращает сырые значения (логиты) в распределение вероятностей действий .
Харш отмечает, что такая «ручная» сборка в стиле PyTorch, но на низком уровне Си, позволяет наглядно увидеть, как формируются узлы графа и как данные текут от входного вектора к финальному решению агента . В завершение инициализации подготавливаются переменные для функции потерь (Reinforce loss) и преимуществ (advantages), которые будут заполнены данными уже в процессе обучения .
🏁 Финализация обучения: от расчета возвратов до первых успехов REINFORCE 2:05:22
На финальном этапе разработки RL-библиотеки Харш Бхатт (Harsh Bhatt) переходит к самой ответственной части — превращению собранного опыта в обновленные веса нейросети. После того как процесс сбора данных (rollouts) завершен , необходимо математически интерпретировать полученные награды, чтобы агент мог понять, какие действия привели к успеху, а какие — к гибели «Змейки».
Расчет дисконтированных возвратов и преимуществ 2:07:41
Процесс обучения начинается с вычисления «возвратов» (returns) для каждого шага внутри траектории. Харш Бхатт (Harsh Bhatt) объясняет, что просто суммировать награды недостаточно — необходимо использовать коэффициент дисконтирования ($\gamma$, gamma) . Это позволяет алгоритму отдавать приоритет немедленным наградам перед отдаленными и математически стабилизирует сходимость.
Алгоритм расчета реализован через обратный проход по траектории (от конца эпизода к началу) . Основные шаги выглядят следующим образом:
- Итерация начинается с последнего шага $t$ и идет к нулевому .
- Текущий возврат $G_t$ вычисляется как сумма награды на данном шаге и дисконтированного возврата следующего шага: $R_t + \gamma \cdot G_{t+1}$ .
- Параллельно накапливается сумма всех возвратов и их квадратов для расчета среднего значения (baseline) и стандартного отклонения .
Во время отладки автор замечает, что на первых итерациях змейка постоянно получает отрицательные награды . Это ожидаемо: необученная модель совершает хаотичные движения и быстро врезается в стены или собственное тело . Чтобы алгоритм REINFORCE работал эффективнее, вычисляется «преимущество» (advantage) — разница между реальным полученным возвратом и средним значением (baseline) . Харш Бхатт (Harsh Bhatt) подчеркивает, что использование среднего значения в качестве базовой линии помогает снизить высокую дисперсию, характерную для градиентных методов политики .
Оптимизация весов и градиентный спуск 2:10:11
Когда преимущества для каждого шага рассчитаны, наступает фаза непосредственного обучения. Первым делом необходимо очистить градиенты во всем вычислительном графе, чтобы накопленные значения с предыдущих итераций не искажали текущий шаг оптимизации . Хотя ранее в курсе обсуждалось проектирование движка автодифференцирования, именно здесь его работа становится критической.
Для обновления весов Харш Бхатт (Harsh Bhatt) выполняет цикл по всему буферу опыта (rollout buffer) . На каждой итерации:
- Состояние из траектории подается на вход сети .
- Выполняется прямой проход (forward pass) через граф для получения предсказаний .
- Рассчитывается градиент функции потерь, масштабированный на величину «преимущества» для конкретного выбранного действия .
- Запускается обратный проход (backward pass) по графу стоимости (cost graph) .
Завершается цикл шагом оптимизатора. В этой минималистичной реализации на Си автор применяет классический градиентный спуск . Веса обновляются путем вычитания градиента, умноженного на коэффициент скорости обучения (learning rate), который дополнительно масштабируется на количество образцов в батче (sample count) для стабилизации обучения . Харш Бхатт (Harsh Bhatt) отмечает, что он намеренно избегал сложных проверок безопасности и дополнительных абстракций, чтобы сохранить код лаконичным и уместить всю логику обучения в 400–500 строк .
Итоги тренировки алгоритма REINFORCE 2:16:45
После настройки всех компонентов и запуска цикла обучения автор демонстрирует результаты работы агента. Несмотря на то что REINFORCE считается довольно нестабильным алгоритмом из-за высокого стандартного отклонения градиентов , спустя 20–30 минут тренировки «Змейка» начинает демонстрировать осмысленное поведение.
В логах обучения отчетливо виден прогресс: если изначально возвраты были глубоко отрицательными, то по мере накопления опыта они переходят в положительную зону . Это означает, что агент научился не только избегать немедленной гибели, но и находить еду на игровом поле . Харш Бхатт (Harsh Bhatt) подчеркивает, что даже такая простая реализация policy gradient способна достигать хороших результатов, если дать ей достаточно времени на исследование среды .
Автор признает, что в процессе написания кода «в прямом эфире» он мог допустить мелкие ошибки, но общая структура библиотеки остается работоспособной и пригодной для дальнейшего расширения . Главная цель курса — показать, что Reinforcement Learning на низком уровне (без использования PyTorch или TensorFlow) вполне достижим .
Завершение курса и планы на будущее 2:17:37
Подводя итоги, Харш Бхатт (Harsh Bhatt) отмечает, что формат одного видео накладывает определенные ограничения. Изначально он планировал реализовать в рамках этого курса и более продвинутые алгоритмы, такие как PPO (Proximal Policy Optimization) . Однако из-за огромного хронометража (более двух часов только на базовую структуру) было решено вынести более сложные методы в следующие видео.
Курс завершается призывом к зрителям экспериментировать с созданной библиотекой и улучшать её . В будущих материалах автор обещает рассмотреть:
- Реализацию версий алгоритма PPO.
- Оптимизацию производительности вычислительного графа.
- Применение библиотеки к другим классическим задачам обучения с подкреплением.
Проект доказал свою жизнеспособность, превратившись из набора структур данных на Си в полноценный инструмент, способный обучить нейронную сеть играть в «Змейку» с нуля.