Математический фундамент, Machine Learning, Deep Learning и современные трансформерные модели

Курс последовательно ведёт от математической базы и реализации алгоритмов с нуля к классическому ML, глубокому обучению, трансформерам, LLM и инфраструктуре обучения больших моделей. Программа ориентирована на профессиональный уровень: чтение статей, реализацию моделей на NumPy и PyTorch, fine-tuning, исследовательскую практику и production-ready подходы.

1. Математический фундамент: линейная алгебра для ML

Математический фундамент: линейная алгебра для ML

Почему одна и та же фотография может быть представлена как массив чисел, сжата почти без заметной потери качества и одновременно стать входом для нейросети, которая распознаёт на ней кошку? Ответ не в «магии ИИ», а в том, что почти весь современный Machine Learning опирается на язык линейной алгебры. Если вы видите в модели веса, эмбеддинги, признаки, батчи, проекции или attention-матрицы, то на самом деле вы видите разные формы работы с векторами и матрицами.

Вы наверняка уже сталкивались с таблицами чисел в Excel или с координатами точки на плоскости. Линейная алгебра делает следующий шаг: она учит воспринимать такие наборы чисел как объекты, над которыми можно выполнять содержательные преобразования. Именно поэтому она нужна в ML не как формальная математика «для галочки», а как рабочий инструмент. Когда модель умножает входной вектор на матрицу весов, она не просто считает числа — она меняет представление данных так, чтобы важные закономерности стали видимыми.

Векторы как носители признаков

Вектор — это упорядоченный набор чисел, который удобно интерпретировать как один объект. В ML это почти всегда набор признаков одного наблюдения. Если у вас есть клиент интернет-магазина, его можно представить вектором: возраст, число покупок, средний чек, время с последнего визита. Это не просто четыре числа рядом, а компактная запись состояния объекта.

Важно понимать, зачем это нужно. Как только данные представлены векторами, мы можем вычислять между ними расстояния, сравнивать направления, искать похожие объекты и строить модели, которые работают сразу с целыми группами признаков. Нейросеть не «понимает» текст или изображение напрямую — она получает числовой вектор и далее действует уже в пространстве этих представлений.

Микропример: слово в модели можно представить как вектор из сотен чисел. Два слова вроде «король» и «королева» оказываются не рядом по буквам, а рядом по смыслу, потому что их векторы близки в пространстве признаков.

Когда мы собираем несколько объектов вместе, появляется матрица — прямоугольная таблица чисел, где строки часто соответствуют объектам, а столбцы — признакам. Если в датасете 10 000 клиентов и у каждого 50 признаков, перед вами матрица размера 10 000 на 50. Это базовая форма данных почти для всех алгоритмов классического ML.

Но матрица в ML — не только способ хранения. Это ещё и способ преобразования. Если умножить вектор признаков на матрицу весов, мы получим новый вектор — другое представление тех же данных. Именно так работает линейный слой в нейросети: он берёт вход, смешивает признаки с разными коэффициентами и создаёт новую систему координат, в которой следующему слою проще найти закономерность.

Линейные преобразования как язык моделей

Вы наверняка замечали, что одну и ту же карту города можно растянуть по ширине или повернуть — объекты на ней изменят положение, но сохранят структуру связей. Линейное преобразование делает нечто похожее с пространством данных: растягивает, сжимает, отражает или поворачивает его по определённым правилам.

Формально матрица задаёт преобразование одного вектора в другой. Но полезнее держать в голове образ: матрица — это машина, которая берёт точки из одного пространства и переносит их в другое. В ML эта «машина» может усиливать важные признаки и подавлять шумные. Например, если один столбец данных почти не связан с целевой переменной, модель со временем присвоит ему малый вес.

Микропример: если у вас два признака — площадь квартиры и число комнат, то модель может преобразовать их в новые признаки вроде «общая вместимость жилья» и «плотность планировки». Эти новые признаки напрямую в данных не лежат, но матричное преобразование может их создать.

!Как матрица меняет пространство признаков

Проблема в том, что одно и то же преобразование можно понимать поверхностно — как набор арифметических операций, — а можно глубоко: как изменение геометрии данных. Для ML второй взгляд важнее. Если после преобразования точки двух классов легче разделить прямой, значит матрица помогла модели сделать данные более «удобными» для классификации.

Здесь появляется важное понятие базиса — набора направлений, через которые мы описываем пространство. В обычной двумерной плоскости это оси и . Но в признаковом пространстве базис можно менять. И иногда именно смена базиса делает задачу простой. Многие методы снижения размерности и факторизации по сути ищут такой базис, в котором структура данных проявляется яснее.

Микропример: если точки на плоскости вытянуты по диагонали, оси «вправо-вверх» и «вправо-вниз» опишут их лучше, чем стандартные горизонтальная и вертикальная оси. Данные те же, а картина понятнее.

Скалярное произведение, длина и сходство

Вам уже знакома идея «похожести» объектов. В ML её часто выражают через скалярное произведение — операцию, которая показывает, насколько два вектора направлены в одну сторону. Если говорить просто, это способ измерить согласованность двух наборов признаков.

Зачем это знать? Потому что огромное число моделей сравнивает объекты именно так. Поисковые системы, рекомендательные модели, эмбеддинги слов и документов, attention-механизм в трансформерах — все они так или иначе используют скалярные произведения или их нормализованные варианты.

Если два вектора направлены похоже, их скалярное произведение велико. Если они почти ортогональны, оно близко к нулю. Ортогональность здесь означает независимость направлений: один вектор почти ничего не говорит о другом. Для ML это ценно, потому что независимые признаки уменьшают дублирование информации.

Микропример: если один признак — рост человека в сантиметрах, а второй — тот же рост в метрах, они почти полностью дублируют друг друга. А вот рост и любимый жанр музыки могут быть почти независимыми.

Из скалярного произведения вырастают понятия длины вектора и угла между векторами. Длина важна, когда модель чувствительна к масштабу признаков. Именно поэтому нормализация данных так часто улучшает обучение: она не даёт одному признаку «перекричать» остальные только из-за больших чисел.

Собственные значения и собственные векторы

Представьте лист бумаги с нарисованной сеткой. Большинство преобразований повернёт линии, растянет их, смешает направления. Но иногда существуют особые направления, которые после преобразования не меняют своей оси — только растягиваются или сжимаются. Это и есть собственные векторы, а коэффициенты растяжения — собственные значения.

На первый взгляд это слишком абстрактно, но в ML это один из самых полезных сюжетов линейной алгебры. Собственные векторы показывают естественные направления структуры данных или преобразования. Собственные значения говорят, насколько эти направления важны. Если значение велико, вдоль этого направления происходит сильное изменение. Если мало — направление второстепенно.

Микропример: представьте облако точек в форме сигары. Его главный «длинный» наклон — это фактически направление наибольшего разброса. Оно очень похоже на главный собственный вектор матрицы ковариации.

!Собственные направления и растяжение пространства

Это особенно важно для анализа матриц ковариации, графов, динамических систем и методов снижения размерности. Например, в PCA главные компоненты находятся именно через собственные векторы ковариационной матрицы данных. То есть мы ищем направления, вдоль которых данные меняются сильнее всего.

На практике полезно помнить не определение, а смысловую интерпретацию:

  • собственный вектор — устойчивое направление преобразования;
  • собственное значение — сила воздействия вдоль этого направления;
  • набор крупнейших собственных значений часто указывает, сколько в данных действительно важной структуры.
  • Но здесь есть ловушка. Наличие большого собственного значения не всегда означает «полезный признак» в прикладном смысле. Иногда это просто направление шума с крупным масштабом. Поэтому линейная алгебра почти всегда работает вместе со статистическим смыслом данных, а не отдельно от него.

    SVD как универсальный инструмент разложения

    Если собственные значения — это особый случай для квадратных матриц, то сингулярное разложение или SVD работает гораздо шире. Любую матрицу данных можно представить как последовательность трёх шагов: поворот, растяжение, ещё один поворот. Это делает SVD одним из самых универсальных инструментов в ML.

    Интуитивно SVD отвечает на вопрос: какие независимые направления вариации скрыты в данных, и насколько каждое из них важно? Матрица раскладывается на три части, где одна задаёт новые направления во входном пространстве, другая — силы растяжения, третья — направления в выходном пространстве.

    Микропример: если у вас есть матрица «пользователь × фильм», SVD может выявить скрытые факторы вроде «любовь к боевикам», «интерес к авторскому кино», «предпочтение семейных фильмов», даже если эти категории не были явно заданы.

    !SVD как поворот, растяжение и новый поворот

    Зачем это нужно practically:

  • для сжатия данных — можно оставить только самые сильные сингулярные значения;
  • для удаления шума — слабые компоненты часто соответствуют случайным колебаниям;
  • для рекомендательных систем — скрытые факторы вкуса естественно возникают из разложения;
  • для понижения размерности — можно перейти к компактному представлению объектов.
  • На уровне интуиции сингулярные значения похожи на «уровни значимости» направлений в данных. Чем больше значение, тем больше информации теряется, если этот компонент выбросить. Поэтому SVD часто используют как способ ответить на вопрос: «Сколько измерений нам реально нужно?»

    Но здесь возникает практическая тонкость. Линейная алгебра говорит, какие направления сильны по структуре матрицы, но не гарантирует, что эти направления оптимальны для конкретной целевой задачи. Для сжатия изображения это может быть отлично, а для классификации редкого класса — не всегда.

    PCA как способ увидеть структуру в данных

    Вы наверняка видели рассеяние точек, вытянутое не вдоль осей, а под углом. Если смотреть на такие данные в неудобной системе координат, структура скрыта. Метод главных компонент, или PCA, ищет новые оси так, чтобы первая объясняла максимум разброса данных, вторая — следующий максимум, и так далее.

    Проще говоря, PCA ищет наиболее информативные направления. Это полезно, когда признаков много, а часть из них избыточна или шумна. Метод не использует целевую переменную — он смотрит только на внутреннюю геометрию данных.

    Микропример: если у вас 300 признаков текста после векторизации, часть из них может описывать почти одни и те же различия между документами. PCA позволяет сжать пространство, сохранив основную форму облака точек.

    Главная практическая выгода PCA:

    | Что делает PCA | Что это значит в жизни ML | |---|---| | Снижает размерность | Меньше памяти и быстрее обучение | | Убирает коррелированные направления | Меньше дублирования признаков | | Помогает визуализировать данные | Можно увидеть кластеры в 2D или 3D | | Иногда подавляет шум | Модель становится устойчивее |

    Важно не перепутать: PCA сохраняет направления с максимальным разбросом, а не обязательно с максимальной полезностью для предсказания. Если редкий, но важный сигнал имеет малую дисперсию, PCA может его ослабить. Это частый источник ошибок у начинающих.

    Разобранный пример: от матрицы признаков к понижению размерности

    Представим задачу: у нас есть датасет клиентов банка с 20 признаками — доход, возраст, число транзакций, доля онлайн-покупок, средний баланс, число просрочек и так далее. Мы хотим предсказывать, откликнется ли клиент на маркетинговое предложение, но сначала замечаем, что признаки частично дублируют друг друга.

    Шаг 1. Мы собираем данные в матрицу объектов и признаков. Почему так: модели удобнее работать, когда весь датасет имеет единую форму, а операции можно применять сразу ко всем строкам.

    Шаг 2. Центрируем признаки, вычитая среднее по каждому столбцу. Почему так: PCA ищет направления разброса относительно центра данных. Если этого не сделать, средние значения будут искажать геометрию.

    Шаг 3. Строим ковариационную матрицу. Почему так: она показывает, какие признаки меняются вместе. Если два признака часто растут и падают одновременно, ковариация велика.

    Шаг 4. Находим собственные векторы и собственные значения этой матрицы. Почему так: собственные векторы задают новые оси, а собственные значения говорят, сколько разброса объясняет каждая ось.

    Шаг 5. Сортируем компоненты по убыванию значимости и оставляем первые несколько. Почему так: слабые компоненты часто несут мало новой информации по сравнению с уже выбранными.

    Шаг 6. Проецируем исходные данные на новые оси. Почему так: теперь каждый клиент описывается не 20 исходными признаками, а, например, 5 главными компонентами.

    Предположим, что первые 5 компонент объясняют 92% общей вариации. Это означает, что мы сократили размерность в четыре раза, почти не потеряв структуру данных. Модель логистической регрессии после этого обучается быстрее, а мультиколлинеарность снижается.

    Микропример: это похоже на упаковку большого чемодана в компактный вакуумный пакет. Форма меняется, объём уменьшается, но содержимое в основном сохраняется.

    Но здесь скрыта важная тонкость. После PCA признаки становятся менее интерпретируемыми. Вместо «возраст» или «средний чек» вы получаете линейные комбинации признаков. Для соревнования на Kaggle это часто приемлемо, а для банковской модели с требованиями объяснимости — уже не всегда.

    Где линейная алгебра встречается в современных моделях

    Когда вы дойдёте до глубоких нейросетей, линейная алгебра станет буквально повсюду. Каждый линейный слой — это матричное умножение. Каждый эмбеддинг — это вектор в обучаемом пространстве. Каждый attention score — результат скалярного произведения или его модификации. Даже нормализация и инициализация весов зависят от размерностей матриц.

    В трансформерах это особенно заметно. Последовательность токенов превращается в матрицу эмбеддингов, затем умножается на матрицы весов для получения запросов, ключей и значений. Потом снова возникают матричные операции, проекции, суммирования и разложения по подпространствам. Без уверенного понимания линейной алгебры всё это выглядит как набор формул; с пониманием — как единая геометрическая история о преобразовании представлений.

    Есть и ещё одна ловушка: многие разработчики умеют использовать библиотечные функции, но не чувствуют размерности объектов. А именно ошибки размерностей чаще всего ломают реализацию. Если вы не понимаете, почему матрица размера batch × features умножается именно на features × hidden, вы будете постоянно исправлять баги вслепую.

    Частые заблуждения и практические ориентиры

    Часто думают, что линейная алгебра в ML нужна только для «математиков» и чтения статей. На самом деле она нужна для трёх куда более приземлённых вещей:

  • понимать, что делает модель с данными;
  • реализовывать алгоритмы без слепого копирования;
  • замечать, когда результаты выглядят подозрительно из-за плохой геометрии признаков.
  • Ещё одно заблуждение: если библиотека всё считает сама, можно не разбираться в разложениях и собственных значениях. Но на практике именно эти идеи помогают выбрать число компонент, интерпретировать эмбеддинги, понять нестабильность обучения и принять архитектурные решения.

    Если из этой главы запомнить только три вещи — это:

  • Матрица в ML — не просто хранилище чисел, а преобразование пространства признаков.
  • Собственные векторы, SVD и PCA нужны не ради красивой теории, а чтобы находить главную структуру, сжимать данные и убирать избыточность.
  • Уверенное понимание размерностей, проекций и геометрии данных напрямую переносится в нейросети, attention и реализацию моделей с нуля.
  • 10. Сверточные сети: CNN, ResNet, EfficientNet для компьютерного зрения

    Сверточные сети: CNN, ResNet, EfficientNet для компьютерного зрения

    Как модель вообще понимает, что на изображении есть край, текстура, глаз или колесо, если на входе у неё всего лишь таблица чисел по пикселям? Наивный подход — расплющить изображение в длинный вектор и отправить в обычную полносвязную сеть — быстро оказывается неэффективным. Он теряет локальную структуру и требует слишком много параметров. Сверточные нейронные сети, или CNN, появились именно как ответ на этот вызов.

    Вы наверняка уже замечали, что человек распознаёт изображение не одним взглядом на все пиксели сразу. Мы сначала улавливаем локальные элементы: контуры, углы, пятна, текстуры. Потом складываем их в более сложные части объекта. CNN делает нечто похожее: начинает с локальных фильтров и постепенно строит всё более абстрактные признаки.

    Свёртка: локальные фильтры вместо полного смешивания пикселей

    Свёртка — это операция, при которой небольшой фильтр проходит по изображению и вычисляет локальный отклик в каждом положении. В отличие от полносвязного слоя, здесь один и тот же набор весов используется многократно на разных участках. Это даёт два огромных преимущества: меньше параметров и учёт пространственной структуры.

    Зачем это нужно? Потому что край или угол важен сам по себе, где бы он ни находился в изображении. Нет смысла учить отдельный детектор горизонтальной линии для верхнего левого угла и ещё один — для нижнего правого. Один фильтр может искать такой паттерн везде.

    Микропример: если фильтр реагирует на вертикальные границы, он даст сильный отклик и на границу двери, и на край книги, и на контур столба. Местоположение изменится, а сама локальная структура останется узнаваемой.

    !Посмотрите, как фильтр проходит по изображению и строит карту признака

    В ML это выражается в понятиях parameter sharing и local connectivity. Фильтр небольшой, но переиспользуется по всему изображению, а значит сеть становится намного эффективнее по сравнению с MLP на тех же данных.

    Feature maps и каналы

    Результат работы свёртки — это feature map, то есть карта откликов фильтра. Если фильтров много, получается несколько каналов. На ранних слоях одни фильтры могут реагировать на края, другие — на диагонали, третьи — на текстурные переходы. На более глубоких слоях каналы начинают кодировать уже более сложные конструкции.

    Микропример: в модели для распознавания животных ранние feature maps могут выделять шероховатую текстуру или округлые контуры, а более поздние — форму уха, глаза или лапы.

    Это важный сдвиг в понимании CNN: внутри сети изображение не просто «сжимается», а переводится в серию всё более содержательных представлений. Именно поэтому визуализация feature maps так полезна для интуиции.

    Receptive field: сколько изображения видит нейрон

    Вы наверняка замечали, что смысл детали зависит от контекста. Маленький чёрный круг может быть и глазом, и колесом, и точкой на фоне — смотря что вокруг. В CNN этот контекст задаётся через receptive field, то есть область входного изображения, которая влияет на конкретный элемент глубокой карты признаков.

    На первом слое receptive field маленький: нейрон видит лишь локальное окно. Но с каждым следующим слоем, особенно при pooling или stride, область обзора растёт. В результате более поздние слои могут учитывать уже крупные части изображения и даже почти всю сцену.

    Микропример: ранний слой видит фрагмент кошачьего уха как набор локальных краёв. Более глубокий слой, имея больший receptive field, уже способен распознать ухо как часть головы, а не как абстрактный треугольник.

    Это объясняет, почему глубина сети важна не только для числа параметров, но и для масштаба контекста, который доступен модели.

    Pooling и понижение пространственного разрешения

    Чтобы сеть постепенно переходила от локальных деталей к более абстрактным признакам, часто нужно уменьшать пространственное разрешение. Классический инструмент для этого — pooling, например max pooling. Он агрегирует информацию в небольшом окне и делает представление компактнее.

    Зачем это нужно? Во-первых, уменьшается вычислительная стоимость. Во-вторых, сеть получает некоторую устойчивость к небольшим сдвигам и локальным вариациям.

    Микропример: если край объекта сдвинулся на пару пикселей, max pooling часто всё равно сохранит сильный сигнал в соответствующей области. Это полезно, потому что реальные изображения редко идеально выровнены.

    Сегодня pooling частично вытесняется свёртками со stride, но сама идея многоуровневого уменьшения пространственного масштаба остаётся фундаментальной.

    Как CNN строит иерархию признаков

    Полезно видеть CNN как пирамиду представлений:

  • нижние слои: края, углы, простые текстуры;
  • средние: части объектов, повторяющиеся паттерны;
  • верхние: целостные семантические структуры.
  • Микропример: для распознавания автомобиля сеть может пройти путь от «вертикальная линия» и «дуга» к «окно», «колесо», «фара», а затем к общему представлению машины.

    Это объясняет, почему CNN так хорошо работают в зрении: они не пытаются сразу учить понятие «кошка» из сырых пикселей. Они собирают это понятие постепенно, из всё более богатых строительных блоков.

    ResNet: почему глубокие сети было трудно обучать

    С ростом глубины возникла проблема: просто добавлять всё больше слоёв не всегда помогало. Иногда более глубокая сеть обучалась хуже, чем менее глубокая. Причина не только в переобучении, но и в том, что оптимизация очень глубоких сетей становится сложной. Градиенты проходят через длинную цепочку преобразований, и полезный сигнал может деградировать.

    ResNet решил это через residual connections — пропускные связи, которые позволяют слою учить не полное новое преобразование, а поправку к уже имеющемуся сигналу. Это оказалось намного легче для оптимизации.

    Микропример: если у вас уже есть хороший черновик текста, удобнее вносить правки поверх него, чем переписывать всё заново с нуля. Residual block делает нечто похожее: учит поправку к входу.

    !Residual block добавляет shortcut и облегчает обучение глубины

    Это был один из ключевых архитектурных прорывов deep learning. После него очень глубокие сети стали реально обучаемыми, а residual-идея распространилась далеко за пределы компьютерного зрения.

    Почему residual connections работают

    Интуитивно residual-связь создаёт короткий путь для сигнала и градиента. Если блок сам по себе пока не научился полезной трансформации, сеть всё равно может хотя бы пропустить вход почти без искажений. Это делает обучение устойчивее.

    Кроме того, residual-формулировка снижает «стоимость ошибки» при добавлении слоёв. Новый блок не обязан с первого дня стать идеальным преобразователем. Ему достаточно научиться полезному дополнению к уже существующему представлению.

    Микропример: если команда работает над продуктом по итерациям, новая версия не обязана быть полностью переписанной. Достаточно внести полезное улучшение поверх старой, уже работающей версии.

    EfficientNet: масштабирование не наугад

    Когда CNN стали зрелыми, возник новый вопрос: как правильно увеличивать модель? Делать её глубже? Шире? Кормить изображениями большего разрешения? Раньше это часто решали эвристически. EfficientNet предложил идею compound scaling — согласованного масштабирования сразу по нескольким измерениям.

    Смысл в том, что увеличение только глубины или только ширины не всегда оптимально. Есть баланс между количеством слоёв, числом каналов и разрешением входа. EfficientNet ищет более рациональный способ наращивать модель в рамках фиксированного вычислительного бюджета.

    !EfficientNet масштабирует глубину, ширину и разрешение согласованно

    Микропример: если вы строите команду проекта, бессмысленно бесконечно добавлять только менеджеров или только исполнителей. Эффективнее расти сбалансированно. EfficientNet делает похожий шаг для архитектуры CNN.

    Практически это важно не только для research, но и для продакшна, где качество нужно увязать с latency и ограничениями железа.

    Разобранный пример: CNN для CIFAR-10

    Представим задачу классификации изображений CIFAR-10. Вход — цветные картинки 32×32, классы — самолёт, автомобиль, птица и так далее.

    Шаг 1. Строим первый сверточный блок: свёртка, активация, возможно BatchNorm. Почему так: нужно научиться выделять самые базовые локальные признаки.

    Шаг 2. Добавляем второй блок и уменьшаем пространственное разрешение. Почему так: сеть должна переходить от пиксельных деталей к более устойчивым паттернам и уменьшать вычисления.

    Шаг 3. После нескольких блоков получаем тензор признаков и агрегируем его перед классификатором. Почему так: финальный линейный слой должен работать уже не с сырыми пикселями, а с содержательными признаками.

    Шаг 4. Обучаем модель и наблюдаем, что ранние слои реагируют на простые структуры, а более поздние — на целостные части объектов. Почему так: CNN действительно формирует иерархию признаков, а не просто запоминает пиксели.

    Шаг 5. Пробуем углубить сеть и замечаем, что без residual-связей обучение становится менее устойчивым. Почему так: глубина сама по себе не бесплатна для оптимизации.

    Этот пример особенно полезен, потому что показывает: CNN — это не один «магический слой», а дисциплина организации многоуровневого пространственного представления.

    Практический кейс: когда ResNet выигрывает у простой CNN

    Возьмём задачу классификации медицинских изображений, где нужно различать норму и патологию на сравнительно небольшом датасете. Простая CNN может быстро переобучаться и при этом плохо использовать глубину. ResNet-архитектура, особенно в режиме transfer learning, часто даёт заметно более устойчивый результат.

    Почему? Потому что residual-блоки облегчают обучение глубоких представлений, а предварительное обучение на больших датасетах даёт хорошие базовые визуальные фильтры. Даже если предметная область отличается, ранние признаки вроде краёв, текстур и локальных контрастов остаются полезными.

    Это хороший пример профессионального мышления: не всегда нужно изобретать архитектуру с нуля. Часто правильнее взять зрелый архитектурный шаблон и адаптировать его под задачу.

    Частые ошибки и ловушки

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

    Вторая — думать, что чем глубже сеть, тем лучше автоматически. Без правильной архитектуры, residual-связей, нормализации и режима обучения глубина может только ухудшить оптимизацию.

    Третья — игнорировать вычислительные ограничения. В реальном проекте лучшая модель — не та, что даёт максимум accuracy на одной GPU-ферме, а та, что укладывается в latency, память и бюджет.

    Если из этой главы запомнить только три вещи — это:

  • Свёртка делает CNN эффективной, потому что использует локальность и разделение параметров.
  • Receptive field объясняет, как сеть переходит от деталей к контексту и объектам.
  • ResNet и EfficientNet важны не как набор модных названий, а как ответы на два практических вопроса: как обучать глубину и как масштабировать модель рационально.
  • 11. Рекуррентные сети: RNN, LSTM, GRU и работа с последовательностями

    Рекуррентные сети: RNN, LSTM, GRU и работа с последовательностями

    Почему модель, которая отлично работает на отдельных объектах, начинает путаться, когда данные идут как последовательность: слово за словом, день за днём, тик за тиком? Потому что в последовательностях важен не только сам текущий элемент, но и его контекст. Одно и то же слово может означать разное в разных предложениях, а один и тот же ценовой скачок на рынке зависит от того, что происходило раньше. Рекуррентные нейронные сети, или RNN, были первым большим ответом deep learning на эту проблему.

    Вы наверняка уже знаете из жизни, что смысл часто раскрывается только во времени. Если услышать одно слово «банк», невозможно понять, речь о финансах или о берегу реки. Следующее слово снимает неоднозначность. Рекуррентные модели устроены вокруг этой идеи: они переносят часть информации из прошлого шага в следующий.

    Последовательность как особый тип данных

    В табличных задачах объект обычно можно описать как фиксированный вектор признаков. В последовательностях так не получается. Здесь важны порядок, длина, временная зависимость и накопление контекста. Это касается текста, речи, логов, временных рядов, биологических последовательностей, кликов пользователя.

    Зачем это выделять отдельно? Потому что если просто усреднить элементы последовательности или рассматривать каждый шаг независимо, мы теряем структуру. А именно структура и есть главный носитель смысла.

    Микропример: в предложениях «собака укусила человека» и «человек укусил собаку» набор слов почти одинаковый, но порядок меняет смысл радикально. Последовательная модель должна это чувствовать.

    RNN: перенос состояния через время

    Рекуррентная нейронная сеть обрабатывает последовательность шаг за шагом. На каждом шаге она получает текущий вход и скрытое состояние из прошлого шага. Затем вычисляет новое состояние, которое несёт сжатую информацию о прошлом контексте.

    Это и есть центральная идея RNN: вместо хранения всей истории в явном виде сеть поддерживает компактную внутреннюю память, которая обновляется при каждом новом элементе последовательности.

    Микропример: при чтении предложения вы не удерживаете все предыдущие слова как независимый список. Скорее, в голове есть текущее понимание фразы, которое обновляется по мере чтения. Скрытое состояние играет похожую роль.

    !Нажимайте шаги и смотрите, как скрытое состояние переносится через время

    С точки зрения параметров RNN элегантна: одна и та же ячейка с одними и теми же весами применяется на каждом временном шаге. Это похоже на свёртку во времени: структура повторяется, а данные на входе меняются.

    Почему простая RNN быстро сталкивается с проблемами

    На бумаге идея выглядит мощно. На практике простые RNN плохо запоминают дальний контекст. Главная причина — проблемы с градиентами при обратном распространении через время. Если зависимость отдалена на много шагов, градиент может либо затухать, либо взрываться.

    Микропример: если в длинном предложении подлежащее было в самом начале, а глагол согласуется с ним только в конце, модели нужно сохранить нужную информацию через десятки токенов. Простая RNN часто теряет её по дороге.

    Это особенно болезненно в задачах языка, длинных временных рядов и любых последовательностях, где значимая зависимость может проявиться далеко не сразу.

    Backpropagation Through Time

    Вы уже знаете backpropagation для обычных сетей. В рекуррентных сетях он разворачивается по временной оси и превращается в Backpropagation Through Time, или BPTT. Сеть как бы раскладывают на цепочку копий одной и той же ячейки по шагам последовательности, а затем проводят ошибку назад через всю эту цепочку.

    Зачем это понимать? Потому что именно здесь видно, откуда берутся проблемы долгосрочной памяти. Градиент проходит через многократные повторения сходных преобразований, и его норма может быстро деградировать.

    Микропример: если вы передаёте сообщение по длинной цепочке людей, каждое звено немного искажает сигнал. Через 50 шагов исходный смысл может исчезнуть. BPTT показывает аналогичный риск в математической форме.

    Практически это ведёт к техникам вроде gradient clipping, а также к появлению более совершенных рекуррентных ячеек.

    LSTM: память с управляемыми воротами

    LSTM, или Long Short-Term Memory, был создан именно как ответ на проблему долгосрочных зависимостей. Внутри ячейки появляется отдельный путь для памяти и набор ворот — механизмов, которые решают, что сохранить, что забыть и что добавить.

    На интуитивном уровне LSTM говорит: не вся новая информация одинаково важна, и не всё прошлое нужно нести дальше. Значит, модель должна уметь управлять памятью, а не просто механически обновлять состояние.

    !Ячейка LSTM разделяет память и управление через ворота

    Микропример: читая длинный текст, вы не запоминаете каждое слово одинаково. Одни детали сразу отбрасываются, другие временно удерживаются, а ключевые факты остаются надолго. LSTM формализует такую селекцию.

    Структурно в LSTM есть:

  • forget gate — решает, что стереть из памяти;
  • input gate — решает, что добавить;
  • output gate — управляет тем, что показать наружу.
  • Это не просто усложнение ради усложнения. Именно такая организация делает путь для важной информации более устойчивым во времени.

    GRU: более компактный компромисс

    GRU, или Gated Recurrent Unit, — более компактная альтернатива LSTM. Он тоже использует gating-механику, но с меньшим числом внутренних компонентов. Это уменьшает число параметров и иногда упрощает обучение.

    Почему GRU оказался популярен? Потому что во многих задачах он даёт качество, близкое к LSTM, при меньшей вычислительной сложности. Это особенно удобно, когда важны скорость и компактность модели.

    Микропример: если LSTM — это сложный органайзер с несколькими отделениями и правилами доступа, то GRU — более лёгкая версия, где часть функций объединена, но основная логика контроля памяти сохраняется.

    !RNN, LSTM и GRU по-разному управляют памятью последовательности

    Практически выбор между LSTM и GRU часто эмпирический. На одних данных LSTM лучше использует долгий контекст, на других GRU оказывается достаточно хорошим и быстрее обучается.

    Как рекуррентные сети читают текст

    В задаче обработки текста последовательностью обычно служат токены. На каждом шаге модель получает вектор текущего токена и обновляет скрытое состояние. Это состояние можно использовать для:

  • классификации всей последовательности;
  • предсказания следующего токена;
  • маркировки каждого шага;
  • генерации ответа по входной последовательности.
  • Микропример: в задаче анализа тональности можно читать весь отзыв по словам, а потом по последнему скрытому состоянию предсказать общий класс. В задаче language modeling, наоборот, после каждого шага предсказывается следующее слово.

    Этот шаблон сделал RNN фундаментом ранней эпохи NLP до доминирования трансформеров.

    Временные ряды: где рекуррентные сети особенно естественны

    Хотя в массовом сознании RNN часто связывают с текстом, они очень естественны и для временных рядов. Здесь на каждом шаге есть новое наблюдение во времени, а задача — прогноз, фильтрация, детекция аномалий или классификация режима.

    Микропример: если вы предсказываете потребление электроэнергии по часам, значение в текущий момент зависит не только от последнего часа, но и от суточного ритма, рабочего дня, погодного контекста и недавнего тренда. Рекуррентная модель может постепенно накапливать такую информацию.

    Но и здесь есть нюансы. Временные ряды часто требуют не только памяти, но и явной сезонности, внимания к пропускам, внешних признаков и строгой валидации по времени. RNN — не волшебная палочка, а инструмент внутри более широкой постановки.

    Разобранный пример: классификация текста с LSTM

    Представим задачу определения тональности отзывов. Есть последовательности токенов разной длины, и нужно предсказать положительный или отрицательный класс.

    Шаг 1. Преобразуем слова в эмбеддинги. Почему так: LSTM работает с числовыми векторами, а эмбеддинг даёт плотное представление токена.

    Шаг 2. Подаём эмбеддинги по одному шагу во времени в LSTM. Почему так: модель должна обновлять контекст по мере чтения текста.

    Шаг 3. На каждом шаге обновляются скрытое состояние и память. Почему так: это механизм накопления и фильтрации контекста.

    Шаг 4. После последнего токена берём итоговое представление последовательности. Почему так: оно должно содержать наиболее важную информацию для общей тональности.

    Шаг 5. Передаём это представление в классификатор. Почему так: последний слой превращает внутренний контекст сети в вероятность класса.

    Шаг 6. Обучаем модель через BPTT с градиентным спуском. Почему так: веса всех временных шагов общие, и ошибка должна корректировать единый набор параметров.

    Здесь особенно важно правильно работать с padding и масками, иначе модель начнёт учиться на «пустых» токенах вместо реального содержания.

    Разобранный пример: прогноз временного ряда с GRU

    Теперь задача из практики: прогноз трафика на сайт по часам. Есть ряд за год, а также внешние признаки — день недели, праздники, маркетинговые кампании.

    Шаг 1. Формируем окна последовательностей, например по 168 часов. Почему так: неделя истории часто содержит достаточно контекста для прогноза следующего интервала.

    Шаг 2. Нормализуем значения и добавляем календарные признаки. Почему так: сама по себе модель памяти недостаточна без стабильного масштаба и внешнего контекста.

    Шаг 3. Подаём последовательности в GRU. Почему так: компактная gating-структура хорошо подходит для относительно длинных рядов без чрезмерной вычислительной цены.

    Шаг 4. Используем последнее скрытое состояние для прогноза следующего часа или следующего окна. Почему так: оно агрегирует историю окна в компактной форме.

    Шаг 5. Оцениваем модель только на будущих периодах. Почему так: случайное перемешивание временного ряда разрушает реальную задачу прогнозирования.

    Этот пример показывает, что рекуррентные сети полезны не только для языка, но и как общее средство работы с данными, где время — часть структуры.

    Частые ограничения и почему трансформеры их вытеснили

    У RNN, LSTM и GRU есть естественный предел: обработка идёт последовательно. Нельзя полноценно распараллелить все шаги по времени так же легко, как в трансформере. Это делает обучение длинных последовательностей медленным.

    Кроме того, даже улучшенные gating-модели не всегда хорошо удерживают очень дальние зависимости. Если важный сигнал был сотни шагов назад, рекуррентный путь может оказаться слишком длинным.

    Именно поэтому трансформеры позже стали доминировать в NLP. Но это не делает RNN бесполезными. Они по-прежнему важны как фундамент последовательного моделирования и как практичный инструмент в ряде временных и компактных задач.

    Если из этой главы запомнить только три вещи — это:

  • RNN важна идеей переноса скрытого состояния через время, а не только конкретной формулой ячейки.
  • LSTM и GRU решают проблему долгосрочной памяти через управляемые ворота.
  • Для последовательностей нужно мыслить не объектами по отдельности, а контекстом, порядком и передачей информации по временной оси.
  • 12. Трансформеры с нуля: Attention, Self-Attention, Multi-Head Attention, Positional Encoding

    Трансформеры с нуля: Attention, Self-Attention, Multi-Head Attention, Positional Encoding

    Почему слово в начале длинного предложения может внезапно оказаться решающим для слова в самом конце, и модель при этом не обязана последовательно «пронести» его через сотни шагов памяти, как в RNN? Именно этот вопрос привёл deep learning к архитектуре, которая изменила NLP, а затем и почти весь ИИ. Сердце этой архитектуры — attention.

    Вы уже видели рекуррентные сети, где контекст переносится через скрытое состояние. У attention другая философия: вместо того чтобы сжимать всё прошлое в один вектор, модель может напрямую посмотреть на любые релевантные элементы последовательности. Это не просто инженерный трюк, а смена парадигмы. Контекст теперь не передаётся только по цепочке шагов, а выбирается динамически под текущую задачу.

    Attention: когда не все части входа одинаково важны

    В жизни мы постоянно используем избирательное внимание. Читая длинное предложение, вы не храните все слова с одинаковой важностью. Если хотите понять местоимение «он», ваш взгляд мысленно возвращается к возможному субъекту. Attention формализует эту идею: при обработке одного элемента модель вычисляет, на какие другие элементы стоит смотреть сильнее, а на какие слабее.

    Зачем это нужно? Потому что в последовательностях значимая информация может находиться далеко, и фиксированная локальная память оказывается недостаточной. Attention создаёт механизм динамического доступа к контексту.

    Микропример: в вопросно-ответной системе при ответе на вопрос «когда родился Эйнштейн?» модель должна сильнее учитывать часть контекста, где упомянута дата рождения, а не случайные соседние слова.

    Query, Key, Value: три роли в одном механизме

    Чтобы attention не оставался туманной метафорой, его удобно понимать через три сущности: query, key и value. Текущий элемент формирует запрос — query. Все элементы последовательности имеют ключи — keys, по которым измеряется релевантность. И у каждого элемента есть значение — value, то есть содержательное представление, которое будет агрегировано.

    Проще говоря:

  • query спрашивает: «что мне сейчас нужно?»;
  • key отвечает: «что я могу предложить?»;
  • value содержит саму информацию, которую можно взять.
  • Микропример: если вы ищете в книге главы про обучение, query — это ваш поисковый запрос, key — названия и сигналы разделов, а value — реальное содержимое найденных страниц.

    !Схема Query, Key, Value показывает, как attention выбирает релевантный контекст

    Это один из самых важных conceptual jumps во всём курсе. Attention — не просто матрица весов, а адресный механизм доступа к информации.

    Scaled dot-product attention

    После того как есть query и keys, нужно измерить их совместимость. Для этого обычно берут скалярные произведения. Чем выше сходство между query и key, тем сильнее соответствующий элемент влияет на результат. Затем эти оценки нормализуются через softmax, чтобы получились веса внимания, и по ним усредняются values.

    Почему используется масштабирование? Когда размерность векторов велика, скалярные произведения могут становиться слишком большими по модулю, а softmax — чрезмерно резким. Деление на корень из размерности стабилизирует вычисления.

    Микропример: если вы оцениваете кандидатов по слишком грубой шкале, один небольшой случайный перевес может полностью доминировать выбор. Масштабирование делает сравнение менее нервным и более устойчивым.

    Практически это связывает attention с уже знакомыми темами:

  • линейная алгебра — потому что всё выражается через матричные проекции и произведения;
  • численная устойчивость — из-за softmax;
  • вероятностная интерпретация — веса внимания суммируются в единицу.
  • Self-attention: последовательность смотрит на саму себя

    Self-attention — это случай, когда query, key и value строятся из одной и той же последовательности. Каждый токен может посмотреть на все остальные токены и решить, кто из них полезен для обновления его представления.

    Это радикально отличается от RNN. В рекуррентной сети связь между далёкими токенами идёт через длинную цепь шагов. В self-attention два токена могут взаимодействовать напрямую за один слой.

    Микропример: в предложении «Книга, которую студент взял в библиотеке, оказалась редкой» слово «оказалась» связано с «книга», хотя между ними много токенов. Self-attention позволяет модели быстро восстановить эту связь.

    !Меняйте токен и наблюдайте, как self-attention перераспределяет веса по всей последовательности

    Именно это свойство сделало трансформеры такими сильными для языка, где зависимость может быть как локальной, так и очень далёкой.

    Матрицы в attention: почему это так удобно для GPU

    Одна из причин успеха трансформеров не только в качестве, но и в вычислительной форме. Attention можно выразить как несколько крупных матричных операций, которые хорошо параллелятся на GPU. Это резко контрастирует с последовательной природой RNN.

    На практике последовательность превращается в матрицу эмбеддингов. Затем через три линейных преобразования получаются матрицы Q, K и V. После этого вычисляется матрица попарных сходств между токенами, нормализуется и используется для смешивания значений.

    Микропример: вместо того чтобы читать предложение строго слово за словом и обновлять одно состояние, трансформер во многом обрабатывает все токены блока сразу как набор взаимосвязанных объектов.

    Это важный инженерный момент: хорошая архитектура в deep learning — это не только выразительность, но и совместимость с массово-параллельными вычислениями.

    Multi-head attention: несколько взглядов на один контекст

    Но одного механизма внимания может быть мало. Один «взгляд» может улавливать синтаксическую зависимость, другой — семантическое сходство, третий — локальное соседство. Поэтому в трансформере используется multi-head attention: несколько независимых голов внимания, каждая со своими проекциями Q, K и V.

    Зачем это нужно? Чтобы модель могла одновременно извлекать разные типы отношений. После этого результаты голов объединяются и снова смешиваются линейным слоем.

    Микропример: читая предложение, вы можете одновременно отслеживать, кто является подлежащим, где объект действия и какие слова тематически связаны. Одна голова внимания редко охватывает всё это одинаково хорошо.

    !Multi-head attention смотрит на последовательность несколькими независимыми способами

    Практически multi-head attention — это один из тех механизмов, где выразительность растёт не за счёт гигантской глубины, а за счёт параллельных подпространств представления.

    Positional encoding: как трансформер узнаёт порядок

    Здесь возникает важная проблема. Сам по себе self-attention почти не знает, в каком порядке идут токены. Если просто подать набор эмбеддингов, модель увидит содержание, но не последовательность. Для языка это критично: перестановка слов меняет смысл.

    Чтобы передать информацию о порядке, вводят positional encoding — способ добавить позиции токенов к их эмбеддингам. В классическом трансформере использовались синусоидальные кодировки, но существуют и обучаемые позиционные векторы.

    Микропример: слова «кот укусил собаку» и «собаку укусил кот» содержат одинаковые токены, но в разном порядке. Без позиционной информации self-attention не смог бы надёжно различить такие случаи.

    Полезно понимать, что positional encoding — не косметическая деталь, а фундаментальный способ совместить permutation-friendly механику внимания с реальной природой последовательного языка.

    Маскирование: как заставить модель не смотреть в будущее

    Если трансформер используется для autoregressive генерации, возникает ещё один нюанс. При предсказании следующего токена модель не должна видеть будущие токены. Для этого применяют causal mask — матрицу, которая запрещает attention к позициям справа.

    Микропример: если студент пишет диктант, ему нельзя подсматривать в следующее слово в правильном тексте. Causal mask делает такое «подглядывание» невозможным для модели.

    Этот механизм особенно важен для GPT-подобных моделей. Без него обучение выглядело бы успешным, но было бы нечестным: модель училась бы на информации, которой не будет при реальной генерации.

    Разобранный пример: self-attention на коротком предложении

    Возьмём короткую последовательность: «Старый пёс нашёл мяч». Пусть модель обновляет представление слова «нашёл».

    Шаг 1. Слово «нашёл» формирует query. Почему так: это текущая позиция, для которой мы хотим собрать контекст.

    Шаг 2. Все слова предложения предоставляют свои keys и values. Почему так: любой токен потенциально может быть полезным источником информации.

    Шаг 3. Считаем сходство query слова «нашёл» с keys всех токенов. Почему так: нужно понять, на кого модель должна смотреть сильнее.

    Шаг 4. После softmax получаем веса внимания. Почему так: теперь есть нормализованное распределение важности по токенам.

    Шаг 5. Строим новую репрезентацию слова «нашёл» как взвешенную сумму values. Почему так: обновлённый вектор теперь содержит не только локальную информацию, но и контекст предложения.

    Если веса внимания особенно велики на словах «пёс» и «мяч», это означает, что модель при обработке глагола активно учитывает субъекта и объект действия. Именно так attention начинает захватывать структуру предложения.

    Разобранный пример: реализация attention с нуля в PyTorch

    Теперь инженерный взгляд. Допустим, у нас есть батч последовательностей с формой batch × seq_len × d_model.

    Шаг 1. Пропускаем вход через три линейных слоя и получаем Q, K, V. Почему так: разные проекции позволяют одной и той же последовательности играть три разные роли.

    Шаг 2. Разбиваем представление на несколько голов. Почему так: multi-head attention требует параллельных подпространств, а значит и специальной перестройки формы тензора.

    Шаг 3. Считаем attention scores как матричное произведение Q и транспонированного K. Почему так: это даёт попарные оценки совместимости токенов.

    Шаг 4. Масштабируем scores и применяем mask при необходимости. Почему так: масштаб stabilizes softmax, а mask реализует ограничения архитектуры.

    Шаг 5. Применяем softmax и умножаем на V. Почему так: получаем агрегированный контекст для каждой позиции.

    Шаг 6. Склеиваем головы обратно и делаем финальную линейную проекцию. Почему так: модель объединяет разные взгляды внимания в единое представление.

    Это уже почти половина трансформерного блока. Именно поэтому understanding attention на уровне тензоров так важно перед GPT и LLM.

    Частые ошибки в понимании attention

    Первая ошибка — думать, что attention weight равен объяснению модели. Вес внимания часто даёт полезную интерпретацию, но не исчерпывает всех причин предсказания. Поздние слои, residual-связи и MLP-блоки тоже сильно влияют на результат.

    Вторая — считать, что self-attention сам по себе «понимает» порядок. Без positional encoding это неверно.

    Третья — путать число голов с «числом независимых смыслов». Heads действительно могут специализироваться, но их поведение не всегда так чисто интерпретируемо, как кажется на схемах.

    Если из этой главы запомнить только три вещи — это:

  • Attention — это механизм адресного выбора релевантного контекста, а не просто ещё одна матричная операция.
  • Self-attention позволяет токенам взаимодействовать напрямую, что радикально меняет работу с дальними зависимостями.
  • Multi-head attention и positional encoding нужны не как украшения, а как способы одновременно моделировать разные отношения и сохранять порядок в последовательности.
  • 13. Transformer-архитектура: Encoder, Decoder, обучение GPT-2 mini на PyTorch

    Transformer-архитектура: Encoder, Decoder, обучение GPT-2 mini на PyTorch

    Как из механизма self-attention вырастает полноценная языковая модель, которая умеет продолжать текст токен за токеном? После знакомства с attention легко недооценить следующий шаг и решить, что трансформер — это просто «несколько attention-слоёв подряд». На практике архитектура трансформера — это тщательно организованная система блоков, нормализаций, residual-связей, масок и обучающих целей. Именно эта система и привела к появлению моделей семейства GPT.

    Вы уже понимаете, как токены могут смотреть друг на друга через self-attention. Теперь нужно увидеть, как из этой локальной механики строится архитектурный стек. А затем — как этот стек обучается на задаче предсказания следующего токена. Именно здесь происходит переход от красивой математической идеи к рабочей модели уровня GPT-2 mini.

    Encoder и Decoder: две архитектурные роли

    Классический трансформер из статьи Attention Is All You Need состоит из двух частей: encoder и decoder. Encoder обрабатывает входную последовательность и строит её контекстуализированные представления. Decoder использует эти представления и одновременно генерирует выходную последовательность, видя только уже сгенерированные токены.

    Зачем нужно это различие? Потому что разные задачи требуют разной структуры потока информации. В машинном переводе, например, есть отдельный входной текст и отдельный выходной. Encoder читает исходное предложение, а decoder шаг за шагом генерирует перевод.

    Микропример: если переводить фразу с английского на русский, encoder создаёт осмысленное представление всего английского предложения, а decoder, генерируя русские слова, опирается и на это представление, и на уже выданную часть перевода.

    Но GPT-подобные модели используют не полную encoder-decoder схему, а decoder-only архитектуру. Это упрощение оказалось удивительно мощным для language modeling и генерации текста.

    !Encoder-decoder и decoder-only решают похожие задачи по разной схеме

    Почему GPT — это decoder-only

    Вы наверняка замечали, что многие языковые задачи можно свести к продолжению текста. Вопрос-ответ, суммаризация, программирование, диалог — всё можно представить как префикс, после которого модель должна сгенерировать правдоподобное продолжение. Именно поэтому GPT-архитектура делает ставку на decoder-only.

    В такой модели каждый токен видит только токены слева благодаря causal mask. Это идеально согласуется с задачей предсказания следующего токена. Модель учится: по всему уже увиденному контексту предскажи наиболее вероятный следующий символ словаря.

    Микропример: если дан префикс «Париж — столица», модель должна с высокой вероятностью выдать токен «Франции». Она не знает будущего текста, только прошлое.

    Эта постановка одновременно проста и универсальна. Из неё вырастают и предобучение LLM, и многие downstream-возможности через prompting.

    Что внутри transformer block

    Один transformer block в decoder-only модели обычно содержит:

  • masked multi-head self-attention;
  • residual connection;
  • layer normalization;
  • position-wise MLP;
  • ещё одну residual connection.
  • Идея блока в том, что attention смешивает информацию между токенами, а MLP перерабатывает представление каждого токена по отдельности. Эти два механизма чередуются: сначала контекстный обмен, затем локальная нелинейная переработка.

    Микропример: это похоже на работу команды. Сначала участники обмениваются информацией на совещании, а потом каждый дорабатывает свою часть работы индивидуально. Attention — это обмен, MLP — индивидуальная переработка.

    !Блок GPT-2 mini: masked attention, residual, LayerNorm и MLP

    Важно не путать: attention сам по себе не заменяет всё остальное. MLP-блоки и residual-структура не менее важны для выразительности и устойчивости обучения.

    LayerNorm и residual в GPT

    В трансформерах особенно важны LayerNorm и residual-связи. Residual помогают сигналу и градиенту проходить через глубокий стек блоков. LayerNorm стабилизирует статистику признаков внутри токена. Вместе они делают обучение глубокой архитектуры гораздо более надёжным.

    GPT-2 использует вариант pre-norm, когда нормализация применяется перед основным подблоком. Это оказалось полезно для устойчивости оптимизации, особенно при увеличении глубины.

    Микропример: если у вас длинная цепочка обсуждений в проекте, residual-связь — это возможность сохранить исходное сообщение рядом с каждой новой интерпретацией, а нормализация — способ не дать одному особенно шумному участнику доминировать над всей коммуникацией.

    Токены, эмбеддинги и позиции

    На вход GPT получает не слова как таковые, а токены. Каждый токен превращается в обучаемый token embedding. К нему добавляется позиционная информация. Получившаяся матрица формы sequence length × hidden size проходит через стек transformer blocks.

    Важно понимать, что эмбеддинг — не словарь определений, а обучаемое пространство признаков. Значение токена определяется не только его собственной embedding-строкой, но и тем, как это представление меняется под действием контекста через слои.

    Микропример: токен «банк» в отрыве от контекста может быть нейтральным. Но после нескольких attention-слоёв его представление в фразе про кредиты и в фразе про берег реки станет разным.

    Это одна из причин, почему контекстуальные модели так сильно превзошли статические эмбеддинги.

    Обучающая цель: next-token prediction

    Сердце GPT — autoregressive objective, то есть предсказание следующего токена по предыдущим. На каждом положении последовательности модель выдаёт распределение вероятностей по словарю, а затем сравнивает его с реальным следующим токеном.

    Зачем это настолько важно? Потому что эта цель очень дешева по данным: практически любой текст можно превратить в обучающий сигнал без ручной разметки. Достаточно сдвинуть последовательность на один токен.

    Микропример: из строки «Машинное обучение полезно» можно сделать множество пар «контекст → следующий токен»: «Машинное» → «обучение», «Машинное обучение» → «полезно» и так далее.

    Такой objective позволяет обучать модели на гигантских текстовых массивах и извлекать из них языковую, фактологическую и частично логическую структуру без отдельной аннотации.

    Teacher forcing и реальная генерация

    Во время обучения GPT обычно использует teacher forcing: на вход на каждом шаге подаются истинные предыдущие токены из датасета, а не собственные предсказания модели. Это стабилизирует обучение и позволяет эффективно считать loss на всей последовательности параллельно.

    Но во время генерации всё иначе. После начального prompt модель предсказывает следующий токен, добавляет его к контексту, затем снова запускает себя на расширенной последовательности. Так возникает autoregressive generation.

    !Нажимайте «следующий токен» и наблюдайте autoregressive генерацию шаг за шагом

    Микропример: во время обучения студенту каждый раз показывают правильное предыдущее слово. На экзамене ему приходится опираться уже на собственные ответы. Именно поэтому между train и inference всегда есть важный operational gap.

    Разобранный пример: архитектура GPT-2 mini

    Представим, что мы хотим собрать компактную модель уровня GPT-2 mini на PyTorch. Пусть это будет учебная версия: несколько decoder blocks, умеренный hidden size, ограниченный словарь и контекстное окно, которое можно обучить на одной или нескольких GPU.

    Шаг 1. Выбираем токенизатор и создаём словарь. Почему так: архитектура начинается не со слоя, а с дискретного представления текста.

    Шаг 2. Реализуем token embeddings и positional embeddings. Почему так: модель должна знать и содержание токена, и его положение в последовательности.

    Шаг 3. Собираем decoder block: LayerNorm, masked self-attention, residual, LayerNorm, MLP, residual. Почему так: это повторяющийся строительный блок всего GPT.

    Шаг 4. Стэкуем несколько блоков, добавляем финальную нормализацию и линейный head на словарь. Почему так: после глубокого контекстуального преобразования нужно получить логиты для next-token prediction.

    Шаг 5. Строим causal mask и проверяем формы тензоров на каждом этапе. Почему так: ошибка в маске или shape почти гарантированно ломает корректность модели.

    Шаг 6. Обучаем модель на language modeling objective с cross-entropy loss. Почему так: это стандартная autoregressive постановка GPT.

    На этом этапе уже можно получить рабочую мини-модель, которая учится продолжать текст на простом корпусе.

    Разобранный пример: обучающий цикл на PyTorch

    Теперь посмотрим на инженерную сторону. У нас есть датасет токенизированных последовательностей фиксированной длины.

    Шаг 1. Формируем батч входов и целевых токенов со сдвигом на один. Почему так: для каждого положения модель должна предсказывать следующий токен.

    Шаг 2. Передаём входной батч в модель и получаем логиты формы batch × seq_len × vocab_size. Почему так: модель выдаёт распределение по словарю для каждой позиции.

    Шаг 3. Перестраиваем логиты и target так, чтобы удобно посчитать cross-entropy. Почему так: loss-функция обычно ожидает плоский список предсказаний и соответствующих истинных классов.

    Шаг 4. Считаем loss, делаем backward, шаг оптимизатора и обновление learning rate scheduler при необходимости. Почему так: это базовый цикл обучения любого GPT-подобного трансформера.

    Шаг 5. Периодически оцениваем validation loss и запускаем короткую генерацию по prompt. Почему так: loss важен, но качественная генерация даёт ранний сигнал о реальном поведении модели.

    Шаг 6. Сохраняем checkpoint и следим за стабильностью градиентов. Почему так: обучение трансформера чувствительно к оптимизатору, lr и численной дисциплине.

    Именно на этом уровне проект перестаёт быть «демкой» и превращается в исследовательский или инженерный pipeline.

    Практические нюансы GPT-2 mini

    Даже маленькая GPT-подобная модель быстро сталкивается с реальными компромиссами:

  • больше контекстное окно — выше стоимость внимания;
  • больше словарь — тяжелее выходной слой;
  • глубже модель — сложнее оптимизация;
  • меньше batch size — шумнее обучение.
  • Микропример: если вы увеличили context length с 256 до 1024 токенов, attention-квадратичность резко повышает вычислительную цену. Иногда это более критично, чем увеличение числа слоёв.

    Ещё одна тонкость — weight tying, то есть совместное использование матрицы входных эмбеддингов и выходного слоя. Это уменьшает число параметров и часто улучшает качество компактных языковых моделей.

    Частые ошибки при реализации

    Первая ошибка — неверная causal mask. Если модель видит будущие токены, loss может быть подозрительно низким, но генерация окажется нечестной и бесполезной.

    Вторая — путаница с формами тензоров при multi-head attention. Ошибки в reshape и transpose здесь очень часты.

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

    Если из этой главы запомнить только три вещи — это:

  • GPT — это не «attention сам по себе», а стек decoder-блоков с causal mask, residual-связями, нормализацией и MLP.
  • Autoregressive обучение превращает почти любой текст в обучающий сигнал через задачу предсказания следующего токена.
  • Реализация GPT-2 mini с нуля на PyTorch — это лучший мост между теорией трансформера и пониманием того, как реально обучаются современные языковые модели.
  • 14. Современные LLM: tokenization, fine-tuning, LoRA, RLHF, DPO и reward modelling

    Современные LLM: tokenization, fine-tuning, LoRA, RLHF, DPO и reward modelling

    Почему одна и та же языковая модель может быть почти бесполезной в сыром виде, а после правильной донастройки — писать код, отвечать в нужном стиле, следовать инструкциям и вести диалог безопаснее? Потому что современные LLM — это не только предобучение на гигантском корпусе. Их реальная сила рождается из цепочки решений: как разбить текст на токены, как адаптировать модель под задачу, как сделать это вычислительно посильным и как согласовать поведение модели с человеческими предпочтениями.

    Вы уже знаете, как работает GPT-подобная архитектура и autoregressive обучение. Теперь важно понять вторую половину современной практики: что происходит после базового pretraining. Именно здесь появляются tokenization, fine-tuning, LoRA, reward modelling, RLHF и DPO — набор идей, без которых разговор о современных LLM будет неполным.

    Tokenization: почему модель не видит текст так, как его видит человек

    Когда человек читает слово, он обычно воспринимает его как целостную языковую единицу. Модель так не умеет. Сначала текст нужно разбить на токены — дискретные элементы словаря. Это и делает tokenization.

    Зачем это важно глубже, чем кажется? Потому что токенизация определяет почти всё: длину последовательностей, размер словаря, стоимость внимания, способность модели работать с редкими словами, кодом, числами и морфологически богатыми языками.

    Микропример: слово «непредсказуемость» можно представить как один токен, несколько субслов или даже почти посимвольно. От этого изменится и длина входа, и статистика обучения, и то, как модель обобщает на незнакомые слова.

    Современные LLM обычно используют субсловные методы вроде BPE, WordPiece или SentencePiece. Они строят словарь не из готовых слов, а из часто встречающихся частей. Это компромисс между слишком крупными и слишком мелкими единицами.

    BPE и логика слияний

    Byte Pair Encoding, или BPE, начинает с маленьких единиц и постепенно сливает самые частые пары в новые токены. В результате словарь растёт вокруг статистически полезных фрагментов языка.

    Почему это практично? Потому что модель получает:

  • компактный словарь;
  • возможность разбирать редкие слова на знакомые части;
  • разумную длину последовательности.
  • Микропример: если в корпусе часто встречается суффикс вроде -ing или подстрока tion, BPE может сделать их отдельными полезными токенами. Тогда новые слова с такими частями не будут полностью чужими модели.

    !Посмотрите, как BPE постепенно сливает частые пары символов в субслова

    Но здесь есть тонкие trade-offs. Большой словарь уменьшает длину последовательности, но увеличивает embedding-таблицу и выходной softmax. Малый словарь экономит параметры, но делает последовательности длиннее и дороже по attention.

    Fine-tuning: адаптация общей модели под конкретную задачу

    Предобученная LLM знает много о языке и мире, но это знание ещё не обязательно соответствует вашей задаче. Fine-tuning — это дообучение модели на более узком распределении данных или на конкретном формате поведения.

    Зачем это нужно? Потому что универсальное продолжение текста и, например, аккуратный юридический суммаризатор — не одно и то же. Fine-tuning меняет веса модели так, чтобы она лучше соответствовала нужной domain-specific или instruction-specific постановке.

    Микропример: базовая модель может знать Python и SQL, но после fine-tuning на корпоративных логах и внутренних инструкциях станет лучше помогать именно вашей инженерной команде.

    Практически fine-tuning бывает разным:

  • domain adaptation — адаптация к корпусу определённой области;
  • instruction tuning — обучение следованию инструкциям;
  • task-specific tuning — под конкретную задачу классификации, QA, суммаризации и так далее.
  • Очень важно понимать, что fine-tuning может как улучшить поведение, так и испортить его, если данные узкие, шумные или смещённые.

    Почему полный fine-tuning дорог

    Если модель имеет миллиарды параметров, полное обновление всех весов требует огромной памяти, времени и инфраструктуры. Это особенно тяжело, когда нужно адаптировать одну и ту же базовую модель под десятки задач или клиентов.

    Микропример: если у вас модель на 7 млрд параметров и вы хотите отдельную версию под поддержку, аналитику и кодогенерацию, хранить и обучать полные копии крайне дорого. Нужен более лёгкий способ адаптации.

    Именно поэтому появились parameter-efficient fine-tuning подходы: не менять всю модель, а добавлять небольшие обучаемые компоненты.

    LoRA: низкоранговые обновления вместо полного переписывания весов

    LoRA — один из самых популярных способов параметроэффективной адаптации. Идея в том, чтобы не обучать целиком большие матрицы весов, а представлять обновление как произведение двух маленьких матриц низкого ранга. Базовые веса остаются замороженными, а обучаются только компактные добавки.

    Зачем это работает? Потому что для адаптации модели под многие задачи не всегда нужно полностью менять огромную матрицу. Часто достаточно низкоразмерного направления коррекции.

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

    !LoRA добавляет к большой матрице компактное низкоранговое обновление

    Преимущества LoRA:

  • намного меньше обучаемых параметров;
  • ниже требования к памяти;
  • удобно хранить и переключать адаптации;
  • можно быстро экспериментировать с разными доменами.
  • Но есть и ограничения. Не каждая задача одинаково хорошо решается низкоранговой адаптацией, а выбор rank, целевых слоёв и режима интеграции влияет на результат.

    Adapters и prompt tuning

    LoRA — не единственный путь. Adapter layers добавляют в сеть маленькие обучаемые блоки, обычно внутри трансформерных слоёв. Базовая модель остаётся почти неизменной, а адаптация происходит через эти вставки.

    Prompt tuning и его варианты делают ещё более лёгкий шаг: обучают не веса модели, а специальные векторы-префиксы или виртуальные токены, которые направляют поведение модели.

    Микропример: это похоже на то, как опытный сотрудник может выполнять разные стили работы в зависимости от очень короткого брифа. Не нужно менять всю его квалификацию, достаточно правильно «настроить» контекст.

    Практически выбор между LoRA, adapters и prompt tuning зависит от бюджета, числа задач, нужной степени контроля и качества.

    Reward modeling: как превратить человеческие предпочтения в обучающий сигнал

    Когда модель уже умеет продолжать текст, возникает новый вопрос: как сделать так, чтобы она отвечала не просто правдоподобно, а полезно, безопасно, вежливо, по инструкции? Для этого нужен не просто следующий токен, а более высокий критерий качества. Здесь появляется reward model.

    Reward modelling — это обучение отдельной модели, которая получает ответ LLM и оценивает его качество с точки зрения человеческих предпочтений. Обычно для этого собирают пары или ранжирования ответов, где люди выбирают лучший вариант.

    Микропример: на один и тот же вопрос базовая модель может дать короткий, резкий или уклончивый ответ. Аннотатор выбирает более helpful и harmless вариант. Reward model учится предсказывать такое предпочтение.

    !RLHF и DPO по-разному используют предпочтения человека для настройки модели

    Это мощная идея: человеческое суждение превращается в приближённую функцию качества, с которой дальше можно оптимизировать поведение модели.

    RLHF: выравнивание через обучение с подкреплением

    RLHF, или Reinforcement Learning from Human Feedback, строится в несколько этапов:

  • предобучение языковой модели;
  • instruction tuning или supervised fine-tuning на примерах хороших ответов;
  • обучение reward model на человеческих предпочтениях;
  • оптимизация policy модели с помощью RL, обычно в духе PPO, чтобы повышать reward.
  • Почему здесь нужно RL? Потому что reward определяется на уровне целого ответа, а не как локальная правильность каждого следующего токена. RL позволяет оптимизировать поведение модели относительно этой внешней оценки.

    Микропример: если пользователь спрашивает о сложной теме, ответ «не знаю» может быть безопасным, но не helpful. Слишком уверенная галлюцинация — helpful по тону, но вредна по содержанию. RLHF пытается сдвинуть модель к лучшему балансу.

    Но RLHF сложен. Он вычислительно дорог, чувствителен к стабильности обучения и может приводить к reward hacking, когда модель начинает эксплуатировать слабости reward model, а не реально вести себя лучше.

    DPO: более прямой путь через предпочтения

    На этом фоне DPO, или Direct Preference Optimization, стал важной альтернативой. Вместо явного обучения reward model и отдельного RL-этапа DPO напрямую использует пары предпочтений для оптимизации policy.

    Интуитивно DPO говорит: если у нас есть два ответа на один prompt и известно, какой предпочтительнее, можно напрямую подвинуть модель к более вероятному выбору лучшего ответа и меньшей вероятности худшего, с учётом reference model.

    Микропример: если аннотаторы выбрали ответ А вместо ответа Б, DPO напрямую использует эту информацию для корректировки модели, не вводя отдельный сложный RL-контур.

    Почему это важно? Потому что DPO часто оказывается проще, стабильнее и дешевле RLHF-пайплайна, особенно в исследовательских и прикладных сценариях, где хочется быстро и контролируемо работать с preference data.

    Разобранный пример: instruction tuning с LoRA

    Представим, что у нас есть open-weight LLM на 7 млрд параметров, и мы хотим адаптировать её под русскоязычного корпоративного ассистента. У нас есть 200 000 пар «инструкция → хороший ответ», но нет ресурсов на полный fine-tuning.

    Шаг 1. Выбираем токенизатор и проверяем покрытие русскоязычных и доменных токенов. Почему так: если модель плохо сегментирует важные термины, качество адаптации будет ограничено уже на входе.

    Шаг 2. Загружаем базовую модель и замораживаем основные веса. Почему так: мы хотим дешёвую адаптацию без полного переписывания параметров.

    Шаг 3. Вставляем LoRA в attention-проекции, например в матрицы query и value. Почему так: именно эти части часто дают сильный рычаг адаптации при умеренной цене.

    Шаг 4. Обучаем только LoRA-параметры на instruction dataset. Почему так: это снижает память и позволяет использовать более доступное железо.

    Шаг 5. Валидируем модель не только по loss, но и по реальным prompt-наборам. Почему так: инструкционные модели важнее оценивать по поведению, чем по одному числу next-token loss.

    Шаг 6. Сохраняем adapters отдельно. Почему так: базовая модель остаётся общей, а разные адаптации можно подключать по задаче.

    Это типичный современный workflow для прикладной настройки LLM.

    Разобранный пример: предпочтения и DPO

    Теперь представим, что после instruction tuning модель отвечает полезно, но всё ещё иногда слишком многословна и плохо следует стилевым требованиям. Мы собираем preference dataset: на один prompt есть два ответа, один лучше другого.

    Шаг 1. Формируем пары chosen/rejected. Почему так: DPO работает именно на относительных предпочтениях, а не на абсолютных числах качества.

    Шаг 2. Выбираем reference model, обычно исходную до текущего шага. Почему так: DPO балансирует движение policy относительно эталонной модели, чтобы не уйти слишком далеко.

    Шаг 3. Оптимизируем модель так, чтобы выбранные ответы становились вероятнее отвергнутых. Почему так: это прямой перенос человеческого предпочтения в обновление параметров.

    Шаг 4. Проверяем качество на ручных evaluation prompts. Почему так: preference tuning легко может улучшить стиль и ухудшить фактическую точность, если смотреть только на train-objective.

    Этот пример важен, потому что показывает: современная настройка LLM всё чаще движется от «учить по правильным ответам» к «учить по сравнительным предпочтениям поведения».

    Частые ошибки и иллюзии

    Первая ошибка — считать tokenization чисто технической деталью. На деле она влияет на throughput, стоимость инференса, multilingual-качество и способность работать с кодом и числами.

    Вторая — думать, что LoRA всегда полностью заменяет полный fine-tuning. Во многих случаях да, но не всегда. Для сильных доменных сдвигов или очень тонких задач полный fine-tuning всё ещё может выигрывать.

    Третья — верить, что RLHF или DPO «делают модель безопасной раз и навсегда». Эти методы лишь оптимизируют модель относительно доступного набора предпочтений и выбранного пайплайна. Они уменьшают часть проблем, но не устраняют их окончательно.

    Если из этой главы запомнить только три вещи — это:

  • Tokenization задаёт фундамент представления текста и влияет на всё — от длины контекста до стоимости модели.
  • LoRA, adapters и другие parameter-efficient методы сделали fine-tuning больших моделей практичным.
  • RLHF, reward modelling и DPO — это способы учить LLM не только правдоподобно продолжать текст, но и вести себя ближе к человеческим предпочтениям.
  • 15. Масштабирование и исследования: distributed training, scaling laws, mixed precision, разбор arXiv-статей

    Масштабирование и исследования: distributed training, scaling laws, mixed precision, разбор arXiv-статей

    Почему одна модель на 7 млрд параметров можно обучить в относительно доступной инфраструктуре, а другая на 70 млрд требует уже совсем другого уровня инженерии, несмотря на то что «архитектура вроде бы та же»? В этот момент deep learning перестаёт быть только историей про формулы и слои. Он становится историей про память GPU, пропускную способность сети, устойчивость оптимизации, режимы чисел и цену каждого дополнительного токена. Именно здесь начинается настоящий разговор о масштабировании.

    Вы уже прошли путь от линейной алгебры и градиентного спуска до трансформеров и LLM. Последний профессиональный шаг — понять, как всё это живёт в масштабе исследований и production. То есть не просто как реализовать модель, а как обучать её эффективно, как оценивать рост качества относительно вычислительного бюджета и как читать современные статьи не как рекламу результата, а как инженерный и научный документ.

    Почему масштабирование — это отдельная дисциплина

    На малых моделях многие проблемы скрыты. Батч помещается в память, один GPU справляется, fp32 работает без драматических затрат, а полная копия модели на каждом устройстве выглядит естественно. Но как только вы увеличиваете размер модели, длину контекста и объём данных, начинают доминировать совсем другие ограничения:

  • память под веса, градиенты и состояния оптимизатора;
  • стоимость передачи данных между устройствами;
  • пропускная способность интерконнекта;
  • нестабильность обучения на длинных сериях шагов;
  • стоимость оценки и выбора чекпоинтов.
  • Микропример: если даже веса модели занимают 28 ГБ в fp32, то с градиентами и состояниями Adam фактическая потребность уже кратно больше. На одной карте такая модель не помещается не потому, что «GPU слабая», а потому что оптимизатору и backward нужно гораздо больше памяти, чем кажется по числу параметров.

    Именно поэтому масштабирование — это не «запустить тот же код на большем железе», а отдельный слой инженерных и научных решений.

    Distributed training: когда одна GPU уже не справляется

    Distributed training — это обучение модели с использованием нескольких устройств. Но важно сразу различать, что именно распределяется: данные, параметры модели, вычисления по слоям, состояния оптимизатора или всё сразу.

    Самый базовый вариант — data parallelism. Каждое устройство хранит полную копию модели, но получает свою часть батча. После прямого и обратного прохода градиенты синхронизируются, и все копии модели обновляются одинаково.

    Зачем это работает? Потому что суммарный градиент по большому батчу можно собрать из градиентов по его частям. Это делает data parallelism естественным продолжением mini-batch обучения.

    Микропример: если четыре исследователя независимо оценивают разные части одного датасета и потом усредняют выводы, получается аналог распределённого батча с синхронизацией градиентов.

    Но у этого подхода есть предел: каждая карта всё равно должна хранить всю модель. Для очень больших LLM это быстро становится невозможным.

    DDP: промышленный стандарт data parallelism

    В экосистеме PyTorch стандартной реализацией считается Distributed Data Parallel, или DDP. Каждый процесс работает на своей GPU, получает свой шард батча и синхронизирует градиенты через all-reduce.

    Почему DDP важен понимать не поверхностно? Потому что на практике производительность зависит не только от числа GPU, но и от того, насколько эффективно перекрываются вычисления и коммуникация. Если синхронизация занимает слишком большую долю шага, масштабирование перестаёт быть линейным.

    Микропример: если на четырёх GPU обучение идёт не в четыре раза быстрее, это не обязательно плохой код модели. Возможно, bottleneck сидит в сети между устройствами или в маленьком размере батча на карту.

    В реальных системах DDP хорошо работает до тех пор, пока модель помещается на одну карту и коммуникационные накладные расходы остаются приемлемыми.

    Model parallelism: когда модель делят по устройствам

    Если модель не помещается целиком на одно устройство, нужен model parallelism — распределение параметров по нескольким GPU. Здесь уже не батч делится на части, а сама сеть.

    Есть разные варианты:

  • tensor parallelism — деление больших матриц и вычислений внутри слоя;
  • pipeline parallelism — распределение последовательных блоков модели по устройствам;
  • гибридные схемы, сочетающие несколько уровней параллелизма.
  • Микропример: если огромную библиотеку нельзя поставить в одной комнате, её распределяют по нескольким залам. Но тогда возникает новая проблема: как быстро передавать читателя между залами. В model parallelism аналогом становится передача активаций между устройствами.

    Это сильный подход, но он усложняет всё: код, отладку, профилирование, требования к интерконнекту и логику запуска.

    ZeRO: экономия памяти без полного отказа от data parallelism

    Здесь появляется одна из самых влиятельных идей современного масштабирования — ZeRO, или Zero Redundancy Optimizer. Его цель — убрать избыточное дублирование состояний обучения между устройствами.

    В обычном data parallelism каждая карта хранит:

  • полные веса модели;
  • полные градиенты;
  • полные состояния оптимизатора.
  • ZeRO распределяет эти сущности между устройствами поэтапно. В разных стадиях шардируются состояния оптимизатора, затем градиенты, затем и сами параметры.

    !DDP, model parallelism и ZeRO по-разному распределяют память и вычисления

    Микропример: если каждый филиал компании хранит полную копию всех архивов, память тратится чудовищно неэффективно. ZeRO — это переход к распределённому хранению с нужной сборкой данных только в моменты, когда они реально требуются.

    Практически ZeRO сделал возможным обучение и fine-tuning моделей, которые иначе не помещались бы даже на многокарточные узлы без избыточных затрат памяти.

    Mixed precision: почему не всё нужно считать в fp32

    Когда модель и батч растут, особенно дорогой становится память и пропускная способность. Один из главных способов экономии — mixed precision, то есть использование более компактных форматов вроде fp16 или bf16 там, где это допустимо.

    Почему это помогает:

  • уменьшается объём памяти под тензоры;
  • ускоряются вычисления на специализированных блоках GPU;
  • растёт эффективный throughput.
  • Но здесь есть опасность: более короткий формат хуже выражает очень маленькие и очень большие числа. Это может привести к потере точности, underflow, overflow и нестабильным градиентам.

    Микропример: если записывать бухгалтерию с недостаточным числом знаков, мелкие, но важные суммы могут округляться до нуля. В mixed precision похожая проблема возникает для маленьких градиентов.

    !Mixed precision сочетает быстрые низкоточные вычисления и аккуратное управление численной стабильностью

    Именно поэтому mixed precision обычно не равен «всё в half precision». Это продуманная смесь форматов с учётом чувствительных частей пайплайна.

    FP16, BF16 и loss scaling

    Практически чаще всего обсуждают два формата: fp16 и bf16.

  • fp16 даёт экономию памяти, но имеет узкий динамический диапазон, поэтому часто требует loss scaling.
  • bf16 хранит меньше мантиссы, но сохраняет более широкий диапазон степеней, а значит часто оказывается устойчивее для обучения больших моделей.
  • Loss scaling — это приём, при котором loss временно умножают на коэффициент, чтобы маленькие градиенты не обнулялись в fp16. После backward градиенты масштаб обратно компенсируется.

    Микропример: если сигнал слишком тихий для записи на чувствительном, но ограниченном приборе, его сначала усиливают, а потом корректно калибруют назад. Loss scaling делает примерно это для градиентов.

    В современных практиках bf16 часто предпочтителен там, где оборудование его хорошо поддерживает, потому что он даёт меньше боли с численной стабильностью.

    Gradient checkpointing: обмен вычислений на память

    Ещё один важный инструмент — gradient checkpointing. Обычно для backward нужно хранить промежуточные активации всех слоёв. Это дорого по памяти. Checkpointing говорит: часть активаций не будем хранить, а пересчитаем заново во время backward.

    Зачем это нужно? Потому что память часто становится главным ограничением раньше, чем чистая вычислительная мощность. Если дополнительные вычисления дешевле, чем нехватка памяти, checkpointing выигрывает.

    Микропример: вместо того чтобы хранить все промежуточные расчёты большого отчёта, можно сохранить только ключевые вехи и при необходимости пересчитать детали. Это медленнее, но экономит место.

    Для длинного контекста в трансформерах checkpointing часто оказывается не optional trick, а обязательной частью запуска.

    Scaling laws: как качество связано с размером модели, данными и compute

    До сих пор мы обсуждали, как запускать большие модели. Но есть не менее важный исследовательский вопрос: во что именно стоит вкладывать compute? Увеличивать параметры? Данных? Число шагов? Контекст? Ответом стали scaling laws — эмпирические законы, связывающие loss или downstream-качество с масштабом модели, данных и вычислений.

    Главная идея здесь проста и очень мощна: качество модели улучшается закономерно, а не хаотично, при росте масштаба. Но этот рост подчиняется trade-off. Если модель слишком большая для объёма данных, она недообучена. Если данных слишком много для маленькой модели, часть вычислений тратится неэффективно.

    Микропример: если вы наняли огромную исследовательскую команду, но дали ей слишком маленький корпус задач, люди простаивают. Если задач много, а команда слишком мала, потенциал данных не реализуется. Нужен баланс ресурсов.

    !Меняйте бюджет compute и смотрите, как меняется оптимальный баланс модели и данных

    Это мышление сильно повлияло на практику pretraining. Теперь вопрос уже не просто «сделаем модель больше», а «сделаем ли мы её compute-optimal».

    Chinchilla и compute-optimal training

    Особенно влиятельной стала линия работ, показавшая, что многие крупные модели были переоценены по числу параметров и недообучены по объёму данных. Работа про Chinchilla популяризовала идею compute-optimal training: при фиксированном бюджете часто выгоднее иметь несколько меньшую модель, но обучить её на большем числе токенов.

    Почему это так важно? Потому что для исследований и продукта compute — это жёсткий бюджет. Неверный баланс между моделью и данными может означать месяцы дорогого обучения с неоптимальным результатом.

    Микропример: лучше ли купить ещё больше серверов или прокормить имеющуюся архитектуру лучшим и более длинным корпусом? Scaling laws переводят такой вопрос из интуиции в количественную рамку.

    Evaluation: почему loss недостаточно

    Когда модель стала большой и дорогой, особенно опасно судить о ней по одному числу. Evaluation современных LLM обычно включает и автоматические benchmark-метрики, и более содержательные тесты поведения.

    Для языковых моделей важны такие типы оценки:

  • perplexity или validation loss на близких распределениях;
  • task-бенчмарки вроде MMLU, HumanEval, GSM8K и других;
  • domain-specific evals;
  • human evaluation и preference-based сравнения;
  • safety и robustness тесты.
  • Микропример: модель может иметь хороший language modeling loss, но при этом проваливаться на кодогенерации или на многошаговом рассуждении. Один глобальный показатель здесь вводит в заблуждение.

    Особенно важно, что benchmark — это не абсолютная истина, а инструмент с собственными смещениями, утечками и насыщением.

    Как читать arXiv-статью профессионально

    На этом этапе курса чтение статей становится отдельным навыком. Хорошая исследовательская статья — это не просто место, где опубликован результат. Это инженерный документ, в котором нужно уметь выделять:

  • постановку проблемы;
  • основную гипотезу;
  • реальный вклад по сравнению с предыдущими работами;
  • вычислительный бюджет;
  • датасеты и протокол оценки;
  • ablation studies;
  • ограничения и open questions.
  • Микропример: если статья заявляет «новую SOTA-архитектуру», но не даёт честного сравнения по compute и данным, такой результат может оказаться не архитектурным прорывом, а просто следствием большего бюджета.

    Очень полезно читать статьи в следующем порядке:

  • abstract и introduction — что за claim;
  • figure/architecture overview — где основная идея;
  • experimental setup — на чём и как обучали;
  • main results tables — где реальный прирост;
  • ablations — что действительно работает;
  • appendix — где часто спрятаны критические детали.
  • Разобранный пример: как анализировать работу о новой LLM

    Представим, что вы читаете новую статью о сильной open-weight LLM.

    Шаг 1. Смотрите на размер модели, число токенов и training compute. Почему так: без этого нельзя понять, справедлив ли прирост качества.

    Шаг 2. Выясняете, чем отличается архитектура или training recipe. Почему так: нужно отделить реальное новшество от масштабирования по ресурсам.

    Шаг 3. Ищете данные о tokenizer, context length, optimizer, precision, parallelism strategy. Почему так: в больших моделях успех часто рождается в совокупности «некрасивых» инженерных решений.

    Шаг 4. Сравниваете eval not only on headline benchmark, but across tasks. Почему так: узкий выигрыш на одном бенчмарке может не переноситься в реальные use cases.

    Шаг 5. Читаете ограничения и ablations. Почему так: именно там видно, что авторы сами считают хрупким местом своей системы.

    Это превращает чтение статьи из пассивного восхищения в профессиональный разбор.

    Практический ориентир для senior-уровня

    Если ваша цель — senior или исследовательский уровень, мышление должно измениться. Недостаточно знать, что такое DDP или LoRA. Нужно уметь связать математику, архитектуру и инфраструктуру в одну систему решений.

    На практике это означает уметь ответить на вопросы:

  • где bottleneck — в памяти, compute или коммуникации;
  • когда выгоднее LoRA, а когда полный fine-tuning;
  • когда использовать bf16, checkpointing и ZeRO;
  • как оценить, не переобучаете ли вы большую модель на слишком малом корпусе;
  • как понять, что claimed improvement в статье не артефакт setup-а.
  • Это и есть переход от «умею запускать» к «умею проектировать и критически анализировать».

    Если из этой главы запомнить только три вещи — это:

  • Масштабирование LLM упирается не только в архитектуру, но и в память, коммуникацию, precision и организацию обучения.
  • Scaling laws и идеи вроде Chinchilla учат распределять compute между параметрами и данными осмысленно, а не интуитивно.
  • Профессиональное чтение arXiv-статей — это умение разбирать claim, compute, eval и ограничения, а не просто смотреть на лучший результат в таблице.
  • 2. Математический фундамент: матанализ и оптимизация

    Математический фундамент: матанализ и оптимизация

    Почему нейросеть с миллионами параметров вообще способна учиться, а не блуждать хаотично по пространству чисел? На первый взгляд кажется невероятным, что из случайно инициализированных весов можно прийти к модели, которая различает объекты на изображении или продолжает текст. Но весь этот процесс держится на двух вещах: математическом анализе и оптимизации. Первый даёт язык изменения функций, второй — стратегию, как использовать это изменение, чтобы уменьшать ошибку.

    Вы уже наверняка встречали идею наклона: если дорога идёт вниз, мяч покатится в сторону уменьшения высоты. В ML почти то же самое происходит с функцией ошибки. Модель делает предсказание, получает штраф за ошибку и затем меняет параметры так, чтобы этот штраф стал меньше. Разница лишь в том, что вместо одной дороги у нас пространство из тысяч, миллионов или миллиардов измерений.

    Производная как чувствительность функции

    Производная показывает, как быстро меняется функция при малом изменении аргумента. В повседневном смысле это мера чувствительности. Если функция потерь сильно растёт даже при маленьком изменении параметра, производная велика. Если почти не меняется — мала.

    Зачем это нужно в ML? Потому что обучение модели — это не перебор всех параметров, а поиск направления, в котором ошибка уменьшается быстрее всего. Производная говорит, какие параметры сейчас влияют на ошибку сильно, а какие почти нет.

    Микропример: если вы подкручиваете ручку громкости и звук резко меняется, чувствительность высокая. Если вращаете, а эффект едва заметен, чувствительность низкая. Производная формализует именно такую интуицию.

    Но в моделях параметров обычно много. Поэтому вместо одной производной возникает градиент — вектор из частных производных по всем параметрам. Он показывает, как изменяется функция по каждому направлению отдельно. Если параметров 1000, градиент — это вектор длины 1000, где каждый элемент отвечает за свой параметр.

    Частная производная — это производная функции по одному аргументу при фиксированных остальных. Для ML это критично: мы хотим понять вклад каждого веса в общую ошибку, не путая его с вкладом других весов.

    Микропример: если вы готовите суп, солёность зависит и от соли, и от воды, и от времени варки. Частная производная по соли отвечает на узкий вопрос: что будет с вкусом, если изменить только соль, а всё остальное оставить тем же.

    Градиент как направление самого быстрого роста

    Есть полезная геометрическая интуиция: градиент указывает направление самого быстрого увеличения функции. Значит, если мы хотим уменьшать функцию потерь, нужно двигаться в противоположную сторону — против градиента. Отсюда и возникает идея градиентного спуска.

    Это не просто красивый образ. Он практичен. Если градиент большой по модулю, модель сейчас находится в области сильного изменения ошибки. Если маленький, обучение может замедлиться: либо модель близка к хорошему решению, либо попала в плоскую область поверхности потерь.

    Микропример: на крутом склоне достаточно сделать маленький шаг, чтобы заметно опуститься ниже. На почти ровном плато можно долго идти и почти не терять высоту.

    Для нейросетей это значит, что величина градиента напрямую влияет на стабильность обучения. Слишком большие градиенты могут вызывать скачки и расходимость, слишком маленькие — медленное обучение или исчезновение сигнала, особенно в глубоких сетях.

    Цепное правило как сердце backpropagation

    Вы наверняка замечали, что в сложной системе один эффект возникает через цепочку промежуточных шагов. Например, поздний автобус может опоздать из-за пробки, пробка — из-за аварии, а авария — из-за дождя. Цепное правило в анализе делает похожую вещь: оно позволяет вычислить, как итоговый результат зависит от раннего элемента через последовательность промежуточных функций.

    В нейросетях это фундаментально. Выход модели зависит от последнего слоя, тот — от предпоследнего, тот — от предыдущего, и так до входа. Чтобы понять, как изменить ранний вес, надо передать информацию об ошибке назад через всю цепочку преобразований. Именно это и делает backpropagation.

    !Цепное правило как поток зависимости через вычислительный граф

    Если функция составная, мы не считаем производную «с нуля» каждый раз. Мы разбиваем вычисление на части и перемножаем локальные чувствительности. Это резко упрощает обучение сложных моделей.

    Микропример: если итоговая цена зависит от скидки, а скидка зависит от статуса клиента, то влияние статуса на цену проходит через скидку. Цепное правило формализует такую передачу влияния.

    Практически это означает, что каждая операция в модели должна уметь ответить на два вопроса: как посчитать прямой проход и как передать градиент назад. В ручной реализации нейросетей это один из самых важных навыков.

    Функция потерь: что именно оптимизирует модель

    Модель учится не «вообще хорошо», а согласно конкретной функции потерь. Это числовая мера того, насколько плохи текущие предсказания. Если предсказание идеально, потери малы. Если сильно ошибочно — велики.

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

    Микропример: если вы прогнозируете цену квартиры и ошиблись на 5000 руб., это одна история. Если уверенно назвали кошку собакой в медицинской задаче по снимкам — последствия уже иные. Функция потерь задаёт, что именно считается плохой ошибкой.

    Здесь возникает важный мостик к оптимизации: производные и градиенты считаются не сами по себе, а именно для функции потерь по параметрам модели. То есть мы не просто изучаем поведение функции — мы строим механизм обучения.

    Градиентный спуск и learning rate

    Градиентный спуск — это итеративный способ уменьшать функцию потерь. На каждом шаге мы вычисляем градиент и двигаем параметры в сторону, противоположную ему. Размер этого шага задаёт learning rate, или скорость обучения.

    Если learning rate слишком велик, модель может перепрыгивать через хорошее решение и колебаться. Если слишком мал — обучение станет мучительно медленным. Поэтому настройка шага обучения — одна из самых важных практических задач.

    !Как шаг обучения меняет траекторию градиентного спуска

    Микропример: спускаться с горы ночью опасно и с гигантскими шагами, и с микроскопическими. В первом случае легко сорваться, во втором — можно не дойти до утра. Нужен адекватный темп.

    Есть несколько основных вариантов градиентного спуска:

  • batch gradient descent — использует весь датасет на каждом шаге;
  • stochastic gradient descent — обновляет параметры по одному примеру;
  • mini-batch gradient descent — использует небольшие батчи и потому стал стандартом в deep learning.
  • Mini-batch особенно важен: он даёт компромисс между стабильностью и скоростью. Градиент получается немного шумным, но этот шум часто даже полезен — помогает выходить из плохих локальных областей.

    Выпуклость: когда оптимизация предсказуема

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

    Для классического ML это очень удобно. Например, линейная регрессия с квадратичной потерей и логистическая регрессия без сложных нелинейностей часто имеют хорошо изучаемые свойства. А вот нейросети обычно приводят к невыпуклой оптимизации, где поверхность потерь содержит сложный рельеф.

    !Выпуклая и невыпуклая поверхность потерь

    Микропример: выпуклая задача — как гладкая миска: куда ни катись, всё равно попадёшь на дно. Невыпуклая — как горный ландшафт с впадинами, седлами и плато.

    Важно не драматизировать. Хотя нейросетевые задачи невыпуклы, современные методы всё равно успешно находят хорошие решения. Но это требует более тонкой инициализации, нормализации, выбора оптимизатора и контроля градиентов.

    Ограничения и множители Лагранжа

    В реальных задачах мы часто оптимизируем не «что угодно как угодно», а при условиях. Например, сумма вероятностей должна быть равна 1, бюджет не должен превышать лимит, веса должны удовлетворять норме. Это уже оптимизация с ограничениями.

    Множители Лагранжа — это способ встроить ограничение в саму задачу оптимизации. Идея в том, чтобы не решать две задачи отдельно — минимизацию и проверку условий, — а объединить их в одну конструкцию.

    Зачем это нужно в ML? Такие идеи встречаются в SVM, в вероятностных моделях, в задачах максимизации правдоподобия при нормировках, в dual-формулировках и в регуляризованных постановках.

    Микропример: если нужно максимизировать прибыль магазина, но склад ограничен 100 единицами товара, невозможно оптимизировать прибыль в отрыве от вместимости. Ограничение меняет само решение.

    На интуитивном уровне множитель Лагранжа показывает «цену» ограничения: насколько изменился бы оптимум, если бы условие слегка ослабили. Это очень полезный взгляд для понимания dual-задач в ML.

    Разобранный пример: обучение линейной модели градиентным спуском

    Возьмём простую регрессионную задачу: предсказать стоимость аренды квартиры по площади. Пусть модель имеет вид: предсказание равно вес, умноженный на площадь, плюс смещение. У нас есть 1000 наблюдений, и мы хотим подобрать параметры так, чтобы ошибка была минимальной.

    Шаг 1. Определяем функцию потерь как среднеквадратичную ошибку. Почему так: она особенно сильно штрафует крупные промахи и удобна для дифференцирования.

    Шаг 2. Инициализируем вес и смещение случайными малыми числами. Почему так: если начать с нулей, в простой регрессии это допустимо, но для общего навыка полезно привыкать к явной инициализации.

    Шаг 3. Считаем предсказания модели для всех квартир. Почему так: чтобы сравнить их с истинными ценами и измерить текущую ошибку.

    Шаг 4. Вычисляем градиенты потерь по весу и смещению. Почему так: нам нужно понять, в какую сторону менять каждый параметр и насколько сильно он влияет на ошибку.

    Шаг 5. Обновляем параметры: новое значение равно старому минус learning rate, умноженный на градиент. Почему так: это и есть движение против направления самого быстрого роста ошибки.

    Шаг 6. Повторяем процесс много раз. Почему так: один шаг обычно лишь слегка улучшает параметры; обучение — это последовательность маленьких корректировок.

    Представим, что в начале вес равен 0.1, а реальные данные требуют примерно 2.5. Первые градиенты будут большими, потому что модель систематически недооценивает цену. По мере приближения к хорошему значению ошибка и градиенты уменьшатся.

    Микропример: это похоже на настройку антенны. Сначала вы крутите ручку заметно и быстро находите направление улучшения сигнала, а потом делаете совсем маленькие движения, чтобы поймать максимум качества.

    Но на практике здесь возникают три частые проблемы:

  • признаки не масштабированы, и из-за этого одна координата доминирует над другой;
  • learning rate выбран неудачно;
  • поверхность потерь вытянута, и движение идёт «зигзагом».
  • Именно поэтому нормализация признаков и более продвинутые оптимизаторы вроде Momentum или Adam оказываются такими полезными.

    Частые заблуждения и реальные нюансы

    Часто думают, что если градиент равен нулю, значит найден минимум. На самом деле это может быть и максимум, и седловая точка, и плоская область. В нейросетях седловые точки особенно важны: из-за высокой размерности они встречаются часто и могут тормозить обучение.

    Ещё одно заблуждение: чем меньше learning rate, тем «безопаснее» и потому лучше. Безопаснее — иногда да, но слишком маленький шаг может практически остановить прогресс. В реальных проектах learning rate — это не косметическая настройка, а один из главных рычагов качества.

    Есть и более тонкий момент. В классической математике часто ищут точное решение. В ML почти всегда достаточно хорошего приближённого решения, которое обобщается на новые данные. И это меняет критерий успеха: идеальный минимум training loss не всегда означает лучшую модель.

    Если из этой главы запомнить только три вещи — это:

  • Градиент — это не абстрактный набор производных, а практическая карта чувствительности модели по всем параметрам.
  • Цепное правило делает возможным backpropagation, а значит и обучение глубоких сетей.
  • Оптимизация в ML — это не только формула обновления параметров, но и работа с рельефом функции потерь, шагом обучения, ограничениями и устойчивостью вычислений.
  • 3. Математический фундамент: теория вероятностей и статистика

    Математический фундамент: теория вероятностей и статистика

    Почему модель может показывать 99% точности на обучающей выборке и проваливаться на новых данных уже на следующий день? Если смотреть только на код, это выглядит как каприз алгоритма. Но если смотреть через теорию вероятностей и статистику, становится видно: модель никогда не учится на «всей реальности», она учится лишь на выборке из неё. А выборка почти всегда неполна, шумна и случайна.

    Вы наверняка замечали это в жизни без всякой математики. Если спросить мнение о фильме у двух друзей, можно получить один вывод; если у двух тысяч зрителей — совсем другой. Разница не только в количестве, но и в случайности наблюдений. Именно поэтому ML опирается не просто на числа, а на вероятностное мышление: данные рассматриваются как проявление скрытого случайного процесса.

    Случайная величина и распределение

    Случайная величина — это числовой результат случайного процесса. В ML это может быть почти что угодно: число кликов пользователя, длина сессии, класс изображения, время до отказа устройства. Главное здесь не слово «случайная», а то, что одно и то же явление при повторении может давать разные результаты.

    Но одной случайной величины мало. Нужна картина того, как часто встречаются разные значения. Эту картину задаёт распределение вероятностей. Оно отвечает на вопрос: какие значения возможны, какие типичны, а какие редки.

    Микропример: если измерять рост взрослых людей, почти все значения будут сосредоточены в некотором диапазоне, а экстремально низкие или высокие встретятся редко. Распределение описывает именно такую форму частот.

    В ML распределение важно сразу в нескольких смыслах:

  • данные считаются выборкой из некоторого распределения;
  • модель часто пытается предсказать параметры распределения или вероятность класса;
  • качество обобщения зависит от того, насколько train-данные похожи по распределению на test-данные.
  • Это подводит к важной практической мысли: многие проблемы моделей — это не «плохой алгоритм», а сдвиг распределения. Если пользователи продукта меняются, данные следующего месяца могут уже не подчиняться тем закономерностям, на которых модель училась.

    Математическое ожидание и дисперсия

    Когда говорят о среднем значении, чаще всего имеют в виду математическое ожидание — усреднённый результат случайной величины при большом числе наблюдений. Это не обязательно то, что реально встретится в данных как конкретное наблюдение, но это хороший ориентир центра распределения.

    Зачем это нужно? Потому что многие алгоритмы в ML оптимизируют именно ожидаемую ошибку, а не ошибку на одном примере. Мы почти всегда хотим, чтобы модель была хороша «в среднем» по новым данным.

    Микропример: средняя температура за месяц может быть 18 градусов, хотя ни в один конкретный день ровно 18 не было. Но как характеристика периода это полезное число.

    Одного центра недостаточно. Нужно понимать, насколько сильно данные разбросаны вокруг него. Это описывает дисперсия — мера вариативности. Если дисперсия мала, значения сгруппированы близко к среднему. Если велика, наблюдения сильно колеблются.

    Для ML дисперсия важна хотя бы по двум причинам. Во-первых, она помогает оценивать нестабильность оценок и шума в данных. Во-вторых, в связке с bias-variance tradeoff она объясняет, почему слишком сложная модель может быть чувствительна к случайным колебаниям выборки.

    Микропример: два продавца могут иметь одинаковую среднюю выручку в 100 000 руб. в неделю. Но если у первого результаты стабильно от 95 000 до 105 000, а у второго скачут от 20 000 до 180 000, управленческие выводы будут разными. Среднее одно, разброс разный.

    Ковариация и зависимость признаков

    Вы уже знаете из линейной алгебры, что направления в данных могут быть связаны. В статистике эта связь часто измеряется через ковариацию — показатель того, как две случайные величины изменяются вместе. Если одна обычно растёт, когда растёт другая, ковариация положительная. Если одна растёт, а другая падает — отрицательная. Если выраженной линейной связи нет, она близка к нулю.

    Зачем это нужно в ML? Потому что данные редко состоят из полностью независимых признаков. Цена дома связана с площадью, доход клиента — с количеством покупок, длина текста — с числом токенов. Ковариация помогает понять, где признаки дублируют друг друга, а где несут новую информацию.

    Микропример: в датасете автомобилей масса машины и расход топлива часто будут статистически связаны. Если игнорировать это, можно переоценить вклад отдельных признаков в модель.

    Практически ковариация лежит в основе:

  • анализа коррелированных признаков;
  • PCA и других методов снижения размерности;
  • вероятностных моделей с многомерными распределениями;
  • понимания структуры шума в данных.
  • Но здесь есть важная ловушка: ковариация отражает линейную совместную изменчивость, а не причинность. Два признака могут двигаться вместе не потому, что один вызывает другой, а потому что оба зависят от третьего фактора.

    Основные распределения, которые реально встречаются в ML

    Теория вероятностей быстро становится полезной, когда вы знаете несколько ключевых распределений не как формулы, а как рабочие модели мира.

    Нормальное распределение важно, потому что многие суммарные эффекты и ошибки измерений стремятся к нему. Оно часто используется как приближение для шумов, начальной инициализации весов, остаточных ошибок в регрессии.

    Бернуллиевское распределение описывает один бинарный исход: успех или неуспех. Это база для бинарной классификации, кликов, конверсий, ответов «да/нет».

    Биномиальное распределение описывает число успехов в серии независимых испытаний. Оно возникает, когда вы считаете количество положительных событий на фиксированном числе примеров.

    Категориальное распределение нужно для многоклассовой классификации и языковых моделей: на каждом шаге модель выбирает один токен из словаря с определёнными вероятностями.

    Микропример: если LLM предсказывает следующее слово, она фактически задаёт категориальное распределение по словарю из тысяч или десятков тысяч токенов. Сам токен — это один конкретный исход из этого распределения.

    Оценивание: выборка, параметр и статистика

    В жизни мы почти никогда не знаем истинное распределение полностью. У нас есть только выборка — набор наблюдений. А значит, параметры вроде среднего, дисперсии или вероятности класса нужно оценивать по данным. Получающиеся численные оценки называются статистиками.

    Это фундаментально для ML. Когда вы считаете среднее и стандартное отклонение для нормализации признаков, вы оцениваете параметры по train-выборке. Когда измеряете accuracy на validation, вы строите статистическую оценку качества модели, а не абсолютную истину.

    Микропример: если вы подбросили монету 10 раз и получили 8 орлов, это не доказывает, что вероятность орла равна 0.8. Это лишь оценка по малой выборке, и она может быть сильно шумной.

    !Как размер выборки влияет на устойчивость статистических оценок

    Отсюда вырастает важная интуиция: маленькие выборки дают нестабильные оценки, большие — более устойчивые. Поэтому многие впечатляющие результаты на узких датасетах потом не воспроизводятся на реальных данных.

    Байесовский подход: обновление знаний при поступлении данных

    Обычный статистический взгляд часто спрашивает: «Какой параметр лучше всего объясняет наблюдаемые данные?» Байесовский подход добавляет ещё один слой: «Что мы думали о параметре до данных, и как должны изменить это мнение после наблюдений?»

    Здесь появляются три ключевые части:

  • априорное распределение — наше исходное знание или допущение до наблюдений;
  • правдоподобие — насколько вероятны данные при разных значениях параметра;
  • апостериорное распределение — обновлённое знание после учёта данных.
  • !Байесовское обновление: prior, likelihood, posterior

    Почему это важно для ML? Потому что байесовское мышление учит работать с неопределённостью явно, а не прятать её. Это полезно для small-data задач, медицинских систем, активного обучения, калибровки уверенности модели и A/B-тестирования.

    Микропример: если вы знаете, что обычно конверсия лендинга находится около 2%, то после первых пяти посещений и одного заказа не стоит сразу верить в 20% как в новую истину. Байесовский подход «смешивает» новые данные с прежним знанием.

    Практически это означает: модель может не просто давать точечный ответ, а выражать степень уверенности. В условиях риска это часто важнее самого предсказания.

    Статистическое обобщение и переобучение

    Главный статистический вопрос в ML звучит так: насколько результат на наблюдаемых данных переносится на новые? Это и есть проблема обобщения. Модель может идеально запомнить train-выборку, но это не гарантирует качества за её пределами.

    Здесь полезно различать шум данных и структуру сигнала. Если модель слишком сложна, она начинает подстраиваться не только под закономерности, но и под случайные флуктуации выборки. Это называется переобучением.

    Микропример: если студент запомнил ответы на конкретные билеты слово в слово, это помогает только до тех пор, пока вопросы не поменялись. Понимание темы — это уже обобщение, а не запоминание.

    Статистически это связано с компромиссом между смещением и разбросом модели. Простая модель может систематически ошибаться, но быть устойчивой. Сложная — быть гибкой, но нестабильной к случайности train-выборки.

    Разобранный пример: оценка вероятности конверсии

    Представим продуктовую задачу: на новый лендинг пришло 200 пользователей, из них 14 сделали покупку. Нужно оценить вероятность конверсии и решить, запускать ли страницу на весь трафик.

    Шаг 1. Формулируем случайную величину: покупка или непокупка одним пользователем. Почему так: каждый визит даёт бинарный исход, значит естественной моделью будет схема Бернулли.

    Шаг 2. Считаем выборочную оценку вероятности: 14 делим на 200 и получаем 7%. Почему так: это простая точечная оценка наблюдаемой доли успехов.

    Шаг 3. Задаём вопрос о надёжности оценки. Почему так: 7% на 200 пользователях и 7% на 200 000 пользователях — это не одинаковая уверенность.

    Шаг 4. Вводим априорное знание: например, прошлые похожие лендинги давали 3–5% конверсии. Почему так: единичный всплеск на маленькой выборке не должен мгновенно переписывать весь опыт.

    Шаг 5. Обновляем оценку байесовски и получаем апостериорное распределение вероятности. Почему так: вместо одной цифры у нас теперь есть диапазон правдоподобных значений и степень уверенности.

    Шаг 6. Принимаем решение не только по среднему, но и по неопределённости. Почему так: если вероятность того, что реальная конверсия ниже текущего контрольного варианта, всё ещё велика, ранний rollout рискован.

    Этот пример кажется простым, но он иллюстрирует профессиональный переход от «посчитать долю» к «принять решение под неопределённостью». Именно здесь статистика превращается в инструмент продукта и исследований.

    Частые ошибки в вероятностном мышлении

    Одна из самых частых ошибок — трактовать вероятность как гарантию. Если модель говорит 0.9, это не значит «точно будет так». Это значит: при корректной калибровке в похожих ситуациях такой исход должен происходить примерно в 90% случаев.

    Вторая ошибка — путать отсутствие наблюдений с отсутствием вероятности. Редкое событие может не встретиться в маленькой выборке и всё же быть важным, особенно в fraud detection, медицине или безопасности.

    Третья ошибка — считать train/test split чисто инженерной формальностью. На самом деле это статистический механизм оценки того, насколько модель обобщает за пределами увиденных данных.

    Если из этой главы запомнить только три вещи — это:

  • Данные в ML — это выборка из распределения, а не сама реальность целиком.
  • Математическое ожидание, дисперсия и ковариация помогают отделять устойчивую структуру от случайных колебаний.
  • Байесовский подход ценен тем, что позволяет обновлять знание и работать не только с ответом, но и с неопределённостью ответа.
  • 4. NumPy и SciPy: реализация математических концепций в коде

    NumPy и SciPy: реализация математических концепций в коде

    Почему два куска кода, которые математически делают одно и то же, на практике могут отличаться по скорости в сотни раз и по устойчивости настолько, что один обучает модель, а другой выдаёт nan уже на третьей эпохе? Когда вы переходите от теории ML к реальной реализации, оказывается, что математика живёт не в вакууме, а внутри массивов, форматов памяти и численных алгоритмов. Именно здесь на сцену выходят NumPy и SciPy.

    Если до этого матрицы и градиенты были для вас объектами на бумаге, то теперь они становятся конкретными структурами в памяти. И это важный шаг. Хороший ML-инженер отличается от человека, который просто вызывает готовые функции, не только знанием формул, но и пониманием, как эти формулы воплощаются в коде. Ошибки размерностей, неудачные циклы, нестабильные вычисления и неправильный выбор библиотечной операции ломают проекты чаще, чем неочевидные теоретические тонкости.

    Массивы как базовая единица вычислений

    NumPy-массив — это компактная структура данных для хранения чисел одного типа в регулярной многомерной форме. В ML он играет роль базового контейнера для векторов, матриц, тензоров небольшого ранга, батчей объектов и промежуточных результатов вычислений.

    Зачем это важно знать глубже, чем «это как список, но быстрее»? Потому что массив — это не просто способ записать числа, а модель вычисления. Его форма, порядок хранения, тип данных и операции над ним определяют и производительность, и корректность реализации.

    Микропример: список Python из миллиона чисел можно сложить поэлементно в цикле, но это будет заметно медленнее, чем одна векторизованная операция над массивом NumPy, потому что во втором случае работа уходит в низкоуровневый оптимизированный код.

    Для ML особенно важны три свойства массивов:

  • shape — форма, то есть размер по каждой оси;
  • dtype — тип данных, например float32 или float64;
  • axis semantics — смысл осей: где батч, где признаки, где каналы, где временной шаг.
  • Новички часто воспринимают shape как техническую деталь, но на деле это грамматика всей модели. Если вы не чувствуете форму объекта, вы не понимаете, какую именно математику сейчас реализуете.

    Broadcasting: как NumPy «достраивает» размеры

    Вы наверняка замечали в быту, что одно и то же правило можно применить сразу ко всей группе. Например, если вы хотите прибавить 10 руб. ко всем ценам в таблице, не нужно переписывать каждую ячейку по отдельности. В NumPy похожую роль играет broadcasting — механизм, который позволяет операциям над массивами разных, но совместимых форм выполняться автоматически.

    Это кажется мелочью, но для ML broadcasting экономит огромное количество кода. Вы можете прибавить вектор смещений ко всей матрице батча, нормализовать данные по столбцам или масштабировать каналы изображения без циклов.

    !Как broadcasting расширяет размеры по осям

    Микропример: если у вас есть матрица из 1000 объектов и 20 признаков, а также вектор из 20 средних значений, NumPy может автоматически вычесть среднее из каждого столбца, не создавая вручную 1000 копий этого вектора в коде.

    Но broadcasting опасен тем, что ошибки могут выглядеть «правдоподобно». Иногда операция проходит без исключения, но не потому, что вы правильно задали оси, а потому, что NumPy смог совместить формы не тем способом, который вы имели в виду. Поэтому хорошая привычка — явно проверять shape до и после операции.

    Векторизация: почему циклы проигрывают

    Векторизация — это стиль программирования, в котором операции применяются сразу к целым массивам, а не к отдельным элементам в Python-циклах. В ML это не косметика, а базовая техника. Она даёт и скорость, и более чистую связь между кодом и математической записью.

    Если у вас есть формула линейной регрессии, матричное умножение в NumPy выражает её почти буквально. Если же вы пишете тройные циклы, математическая структура задачи скрывается за служебными деталями.

    Микропример: вычислить предсказания для 10 000 объектов можно либо в цикле по строкам, либо одной операцией умножения матрицы на вектор весов. Второй способ не только быстрее, но и меньше подвержен ошибкам индексации.

    Векторизация особенно важна для:

  • прямого прохода линейных моделей и нейросетей;
  • вычисления градиентов по батчу;
  • реализации функций потерь;
  • предобработки данных и нормализации.
  • Но здесь есть тонкость. Не всякая векторизация полезна автоматически. Иногда ради «одной строки» программист создаёт гигантские временные массивы и тратит лишнюю память. Поэтому профессиональный стиль — это не максимальная компактность, а баланс между ясностью, скоростью и расходом памяти.

    Линейная алгебра в NumPy

    Когда вы используете NumPy по-настоящему для ML, большая часть серьёзной математики проходит через подмодуль linalg и базовые операции над массивами. Скалярные произведения, матричное умножение, нормы, решение систем, разложения — всё это имеет прямые реализации.

    Ключевая идея здесь в том, что код должен сохранять математический смысл. Если на бумаге вы умножаете матрицу объектов на вектор весов, в коде вы должны видеть почти то же самое. Это резко снижает когнитивную нагрузку и делает отладку осмысленной.

    Микропример: если предсказание модели записывается как произведение матрицы признаков на вектор параметров плюс смещение, то векторизованный код отражает ровно эту структуру, а не набор пошаговых циклов.

    Практически полезно различать несколько типов операций:

    | Тип операции | Что делает в ML | |---|---| | Поэлементные | активации, маски, нормализация | | Суммирования по осям | усреднение loss по батчу, подсчёт статистик | | Матричные умножения | линейные слои, регрессия, attention-проекции | | Разложения | PCA, SVD, решение устойчивых задач |

    Здесь критичен ещё один момент: не пытаться там, где возможно, инвертировать матрицу вручную ради красивой формулы. В численных вычислениях прямое обращение матрицы часто хуже, чем решение системы уравнений специализированной процедурой. Это как открывать консервную банку молотком: иногда сработает, но есть инструмент лучше.

    Зачем нужен SciPy поверх NumPy

    SciPy — это набор более специализированных численных алгоритмов поверх экосистемы NumPy. Если NumPy даёт базовые массивы и основные операции, то SciPy приносит зрелые решения для оптимизации, линейной алгебры, статистики, работы с разреженными матрицами, интерполяции и многого другого.

    Для ML он особенно полезен в двух режимах. Первый — когда вы реализуете математику с нуля и хотите опираться на надёжные численные методы. Второй — когда вы исследуете алгоритм или прототип, ещё не переходя на крупный фреймворк вроде PyTorch.

    !Где заканчивается NumPy и начинается SciPy

    Микропример: NumPy легко посчитает матричное произведение, но если вы хотите решать задачу оптимизации с ограничениями или работать с разреженной матрицей графа на миллионы узлов, SciPy предлагает инструменты гораздо ближе к профессиональной практике.

    Особенно полезны такие разделы SciPy:

  • scipy.linalg — расширенная линейная алгебра;
  • scipy.optimize — минимизация функций, constrained optimization, root finding;
  • scipy.sparse — разреженные матрицы;
  • scipy.stats — распределения и статистические процедуры.
  • Важный навык здесь — не только знать, что библиотека «умеет», но и понимать, когда использовать её вместо ручной реализации. Ручной код нужен для понимания и обучения, но production-устойчивость обычно требует проверенных численных процедур.

    Численная устойчивость: математика на компьютере не идеальна

    На бумаге числа ведут себя идеально. На компьютере — нет. Представление чисел ограничено, округления накапливаются, очень большие и очень маленькие значения ведут себя несимметрично. Это и есть мир численной устойчивости.

    В ML это критично. Вычисление логарифма от почти нуля, экспоненты от большого числа, деление на очень маленькую норму, накопление сумм с разным масштабом — всё это может породить переполнение, потерю точности или nan.

    Микропример: если в softmax подать очень большие логиты, экспоненты могут стать настолько огромными, что вычисление переполнится. Формула математически корректна, но численно реализация ломается.

    !Как масштаб значений влияет на численную устойчивость

    Поэтому профессиональная реализация почти всегда содержит устойчивые версии стандартных операций:

  • softmax вычисляют после вычитания максимума из логитов;
  • логарифмы вероятностей считают в связке с устойчивыми функциями;
  • деление сопровождают маленькой константой epsilon;
  • тип данных выбирают с учётом баланса точности и памяти.
  • Это один из моментов, где «знаю формулу» и «умею реализовать» расходятся особенно сильно.

    Разобранный пример: линейная регрессия на NumPy без циклов

    Возьмём датасет с 50 000 наблюдений и 30 признаками. Нужно реализовать линейную регрессию с MSE и градиентным спуском только на NumPy.

    Шаг 1. Представляем данные как матрицу формы объекты × признаки и целевой вектор. Почему так: это сразу подготавливает код к векторизованным операциям и согласует его с линейной алгеброй.

    Шаг 2. Инициализируем вектор весов длины 30 и скалярный bias. Почему так: параметры модели должны совпадать по размерностям с данными.

    Шаг 3. Считаем предсказания одной операцией матричного умножения плюс broadcasting смещения. Почему так: предсказание для всего батча можно получить сразу, без цикла по объектам.

    Шаг 4. Вычисляем вектор ошибок как разность между предсказаниями и истинными значениями. Почему так: все следующие расчёты градиентов удобно строить именно от этого массива ошибок.

    Шаг 5. Получаем градиент по весам через транспонированную матрицу признаков и вектор ошибок. Почему так: это компактная векторизованная форма суммы вкладов всех объектов.

    Шаг 6. Обновляем параметры и повторяем цикл обучения. Почему так: это стандартный итеративный процесс оптимизации.

    Здесь особенно важно заметить, что ни один шаг не требует Python-цикла по наблюдениям. Вся тяжёлая работа переносится в низкоуровневые реализации NumPy.

    Микропример: если ваш код на 50 000 строках работает секунды вместо минут, это часто не из-за более мощного компьютера, а из-за правильного выбора векторизованных операций.

    Практический кейс: логарифмы, softmax и ловушка переполнения

    Рассмотрим уже более нейросетевую задачу. Пусть у модели есть логиты для 50 000 токенов словаря, и некоторые значения достигают 120. Если вы напрямую считаете экспоненты, получите астрономические числа, часть которых не помещается в стандартный формат.

    Правильный трюк: перед softmax вычесть максимум логитов из каждого набора. Почему это работает: softmax зависит не от абсолютного уровня логитов, а от их относительных различий. Сдвиг всех значений на одну и ту же константу не меняет результат, но резко улучшает численное поведение.

    Это типичный пример профессионального мышления в ML: не просто «знать формулу», а понимать её инварианты и использовать их для устойчивой реализации.

    Частые ошибки при работе с NumPy и SciPy

    Самая частая ошибка — писать Python-циклы там, где нужна векторизация. Вторая — не следить за размерностями и надеяться, что broadcasting «сам всё сделает правильно». Третья — игнорировать типы данных: случайный переход с float64 на float32 или наоборот может повлиять и на память, и на стабильность.

    Ещё одна тонкость — избыточное копирование массивов. Некоторые операции возвращают представления на те же данные, другие создают полную копию. На малых примерах это незаметно, на больших батчах начинает стоить гигабайтов памяти.

    Если из этой главы запомнить только три вещи — это:

  • NumPy нужен не просто для удобства, а как способ выразить математику ML в быстрой и ясной форме.
  • Broadcasting и векторизация дают выигрыш только тогда, когда вы контролируете формы массивов и смысл осей.
  • SciPy и приёмы численной устойчивости превращают учебные формулы в реализацию, которая выдерживает реальные данные и реальные масштабы.
  • 5. Классический ML: линейная и логистическая регрессия с нуля на NumPy

    Классический ML: линейная и логистическая регрессия с нуля на NumPy

    Как одна и та же математическая идея может предсказывать цену квартиры, вероятность оттока клиента и шанс клика по баннеру? На первый взгляд это разные задачи. Но если посмотреть глубже, во всех трёх случаях модель пытается связать признаки объекта с числовым ответом. Именно поэтому линейная регрессия и логистическая регрессия — не устаревшие учебные игрушки, а базовые конструкции, из которых вырастает значительная часть практического ML.

    Вы наверняка уже видели на графике прямую, которая «примерно проходит через точки». Это почти и есть линейная регрессия: мы ищем такую зависимость между признаками и целевой переменной, которая лучше всего объясняет данные. А логистическая регрессия делает близкую вещь для классификации: вместо прямого численного ответа она оценивает вероятность класса.

    Линейная регрессия как модель числовой зависимости

    Линейная регрессия предсказывает числовую величину как линейную комбинацию признаков. Простыми словами, модель считает, что каждый признак вносит свой вклад в итоговый ответ, и эти вклады складываются.

    Почему это важно? Потому что даже если реальный мир сложнее линейной зависимости, линейная модель даёт прозрачную базу: она интерпретируема, быстра, часто surprisingly сильна на табличных данных и служит отправной точкой для более сложных методов.

    Микропример: если мы предсказываем аренду квартиры по площади, этажу и расстоянию до метро, модель может дать каждому фактору свой вес. Площадь обычно поднимает цену, удалённость от метро — снижает, этаж может влиять слабее и неоднозначнее.

    Ключевая интуиция здесь в том, что веса модели — это коэффициенты важности признаков в текущем масштабе данных. Но не стоит сразу трактовать их как «чистую причинность». Вес отражает статистическую связь в конкретной постановке задачи и зависит от нормализации, коррелированных признаков и качества выборки.

    От одного признака к многомерной модели

    Когда в учебниках рисуют прямую на плоскости, всё выглядит слишком просто. Но реальная сила линейной регрессии раскрывается в многомерном случае. Там уже нет одной видимой прямой — есть гиперплоскость в пространстве признаков. С математической точки зрения идея та же, просто признаков больше.

    Это особенно удобно для реализации на NumPy, потому что предсказание всей модели естественно записывается как матричное умножение матрицы объектов на вектор весов плюс смещение. Так теория прямо переводится в код.

    Микропример: если у вас 100 000 клиентов и 40 признаков, линейная регрессия не строит 40 отдельных правил. Она одновременно учитывает все признаки и их совместный вклад в итоговый прогноз, например вероятность ожидаемой выручки от клиента в следующем месяце.

    !Линейная и логистическая регрессия решают разные типы задач

    Практически это даёт два важных эффекта:

  • модель легко масштабируется на большое число объектов;
  • код остаётся компактным и векторизуемым.
  • Функция потерь в линейной регрессии

    Чтобы обучить модель, нужно измерить, насколько её предсказания плохи. Для линейной регрессии стандартом становится среднеквадратичная ошибка. Она берёт разницу между предсказанием и истинным значением, возводит её в квадрат и усредняет.

    Почему именно квадрат? Потому что большие ошибки штрафуются сильнее маленьких. Это делает модель чувствительной к крупным промахам, что во многих задачах полезно.

    Микропример: ошибиться в оценке квартиры на 50 000 руб. неприятно, но ошибиться на 2 млн руб. — несравнимо хуже. Квадратичная потеря отражает такую разницу жёстче, чем простое среднее абсолютных ошибок.

    В контексте NumPy линейная регрессия хороша тем, что и предсказание, и loss, и градиенты можно написать почти в одну строку каждая. Это отличный первый алгоритм для полной ручной реализации.

    Градиенты и обучение с нуля

    Как модель понимает, в какую сторону менять веса? Через градиенты функции потерь по параметрам. Если вес слишком велик и из-за этого предсказания систематически завышены, градиент «подскажет» уменьшить его. Если вклад признака недостаточен — увеличить.

    На практике обучение с нуля выглядит так:

  • Инициализируем веса и bias.
  • Считаем предсказания на всём батче.
  • Вычисляем ошибку.
  • Считаем градиенты по весам и bias.
  • Делаем шаг градиентного спуска.
  • Повторяем много раз.
  • Здесь теория встречается с инженерной дисциплиной. Если размеры массивов не согласованы или признаки не масштабированы, даже правильная формула может вести себя плохо.

    Микропример: если один признак измеряется в годах, а другой в рублях и его значения в тысячу раз больше, шаги оптимизации по соответствующим направлениям будут сильно различаться. Отсюда вырастают медленное обучение и зигзагообразная траектория.

    Логистическая регрессия: когда нужен класс, а не число

    Теперь переходим к классификации. Логистическая регрессия называется регрессией исторически, но по сути это базовая модель вероятности класса. Она берёт линейную комбинацию признаков и пропускает её через сигмоиду — функцию, которая переводит любое число в диапазон от 0 до 1.

    Зачем это нужно? Потому что в бинарной классификации нам нужен не просто скор, а вероятность принадлежности объекту к положительному классу. Например, вероятность спама, вероятность дефолта, вероятность покупки.

    !Сигмоида превращает линейный скор в вероятность

    Микропример: если скор модели для клиента равен очень большому положительному числу, сигмоида даст вероятность близкую к 1. Если очень отрицательному — близкую к 0. В районе нуля модель не уверена и выдаёт вероятность около 0.5.

    Ключевая сила логистической регрессии в том, что она остаётся линейной по признакам на уровне разделяющей поверхности, но даёт вероятностную интерпретацию. Поэтому это отличный baseline почти для любой бинарной табличной задачи.

    Почему для классификации нельзя использовать MSE

    Новички часто спрашивают: если логистическая регрессия тоже выдаёт число, почему бы не обучать её на той же среднеквадратичной ошибке? Формально можно, но это плохая идея. Для вероятностной бинарной классификации естественной функцией потерь является логарифмическая потеря, или binary cross-entropy.

    Она устроена так, что очень уверенные, но неправильные предсказания наказываются особенно сильно. Это хорошо согласуется с задачей вероятностной классификации.

    Микропример: если модель сказала «0.99, что это не мошенничество», а на деле это fraud, ошибка должна быть большой, потому что модель ошиблась не просто по знаку, а с высокой уверенностью.

    Именно здесь логистическая регрессия начинает выглядеть как первый мостик к нейросетям. Линейный слой плюс нелинейность плюс специальная loss-функция — это уже знакомая нам архитектурная схема, только в очень простом виде.

    Разобранный пример: линейная регрессия на NumPy

    Представим задачу прогнозирования ежемесячных продаж магазина по трём признакам: числу рекламных показов, среднему чеку и числу активных клиентов. У нас 20 000 наблюдений.

    Шаг 1. Собираем матрицу признаков формы 20 000 на 3 и целевой вектор продаж. Почему так: матрица позволяет одним умножением получить предсказания для всех наблюдений.

    Шаг 2. Нормализуем признаки. Почему так: градиентный спуск будет сходиться заметно стабильнее, если масштабы признаков сопоставимы.

    Шаг 3. Инициализируем веса малыми случайными числами, bias нулём. Почему так: даже для простой модели это делает реализацию ближе к общей практике обучения.

    Шаг 4. На каждой итерации считаем y_pred = X @ w + b. Почему так: это прямой векторизованный эквивалент математической модели.

    Шаг 5. Вычисляем MSE и её градиенты. Почему так: именно градиенты говорят, как параметры влияют на среднюю ошибку по батчу.

    Шаг 6. Обновляем параметры. Почему так: повторяющийся маленький шаг постепенно приближает модель к лучшему решению.

    Допустим, после 3000 итераций loss стабилизировалась, а коэффициент при активных клиентах оказался намного выше, чем при рекламных показах. Это не значит, что реклама не важна вообще. Это значит, что в текущих данных именно число активных клиентов сильнее связано с целевым прогнозом при прочих равных.

    Разобранный пример: логистическая регрессия для churn prediction

    Теперь возьмём задачу бинарной классификации: предсказать отток клиента в подписочном сервисе. Признаки: число логинов за месяц, время с последней активности, число обращений в поддержку, тариф, наличие просрочки оплаты.

    Шаг 1. Кодируем признаки и масштабируем числовые столбцы. Почему так: логистическая регрессия чувствительна к масштабу, а категориальные признаки нужно перевести в числа.

    Шаг 2. Вычисляем линейный скор для каждого клиента. Почему так: это агрегированный сигнал риска до перевода в вероятность.

    Шаг 3. Пропускаем скор через сигмоиду и получаем вероятность оттока. Почему так: теперь результат можно интерпретировать как шанс события.

    Шаг 4. Считаем binary cross-entropy. Почему так: она естественно измеряет качество вероятностного предсказания для бинарного класса.

    Шаг 5. Находим градиенты и обновляем параметры. Почему так: обучение идёт тем же общим принципом, что и в регрессии, но с другой связкой «выход + loss».

    Шаг 6. Превращаем вероятности в классы через порог, например 0.5, либо подбираем порог под бизнес-задачу. Почему так: реальная стоимость ошибок первого и второго рода часто различается.

    Микропример: если отток дорогого клиента критичен, бизнес может предпочесть более низкий порог и чаще сигнализировать «риск», даже ценой роста ложных срабатываний.

    Регуляризация: когда простота спасает от переобучения

    Даже линейные модели умеют переобучаться, особенно если признаков много, они шумные или сильно коррелированы. Здесь помогает регуляризация — добавление штрафа за слишком большие веса.

    L2-регуляризация мягко тянет веса к меньшим значениям, делая модель устойчивее. L1-регуляризация может занулять часть коэффициентов и тем самым выполнять неявный отбор признаков.

    !Как регуляризация меняет решение модели

    Микропример: если модель строится по сотням маркетинговых признаков, часть из них почти случайна. Без регуляризации веса могут «подстроиться» под шум. Со штрафом модель охотнее выбирает более простое решение.

    Здесь важно помнить: регуляризация почти всегда улучшает обобщение ценой небольшого ухудшения fit на train-данных. Это не баг, а ожидаемый компромисс.

    Практический кейс: baseline, который часто недооценивают

    В реальных проектах линейная и логистическая регрессия часто служат baseline-моделями. И не формально, а по-настоящему полезно. Они быстро обучаются, дают интерпретацию коэффициентов, хорошо работают на многих табличных данных и помогают понять, есть ли в данных вообще сигнал.

    На соревнованиях Kaggle сильные участники редко начинают со сложной архитектуры. Сначала они строят понятный baseline, чтобы проверить пайплайн, утечки, качество фичей и метрики. Если даже логистическая регрессия даёт неожиданно высокий score, это может означать, что данные уже очень информативны. Если низкий — возможно, проблема не в модели, а в признаках.

    Частые заблуждения

    Первое заблуждение: линейная регрессия «слишком простая, чтобы быть полезной». На самом деле простые модели часто выигрывают на малых и средних данных за счёт устойчивости и интерпретируемости.

    Второе: коэффициенты логистической регрессии можно читать как прямой рост вероятности. Не совсем. Они линейны в пространстве логитов, а не вероятностей. Вероятностный эффект зависит от текущей точки на сигмоиде.

    Третье: если логистическая регрессия линейна, она бесполезна для сложных границ. Не обязательно. С хорошими признаками даже линейный классификатор может работать очень сильно. Часто решает не столько сложность модели, сколько качество представления данных.

    Если из этой главы запомнить только три вещи — это:

  • Линейная регрессия учит главному шаблону ML: предсказание, функция потерь, градиент, обновление параметров.
  • Логистическая регрессия — это базовая вероятностная модель бинарной классификации, а не просто «линейка с порогом».
  • Регуляризация, нормализация признаков и корректная loss-функция часто влияют на качество не меньше, чем сама формула модели.
  • 6. Классический ML: деревья, Random Forest, Gradient Boosting и XGBoost

    Классический ML: деревья, Random Forest, Gradient Boosting и XGBoost

    Почему модель может выдавать решение в духе «если доход клиента меньше этого порога, а просрочка была больше двух раз, то риск высокий», и при этом оставаться одной из самых сильных на табличных данных? Потому что не все хорошие модели думают как формула с весами. Деревья решений и их ансамбли работают иначе: они режут пространство признаков на области и принимают локальные правила.

    Вы наверняка уже принимаете похожие решения в жизни. Например: если на улице идёт дождь и температура ниже 5 градусов — взять тёплую куртку; если дождя нет, но сильный ветер — всё равно взять. Это уже дерево решений в бытовой форме. ML лишь учится строить такие правила автоматически по данным, а затем комбинировать множество деревьев в более сильные модели.

    Дерево решений как серия вопросов

    Дерево решений — это модель, которая последовательно задаёт вопросы о признаках и в зависимости от ответов отправляет объект в разные ветви. В конце пути объект попадает в лист, где и формируется предсказание: класс, вероятность или численное значение.

    Почему это важно? Потому что дерево естественно работает с нелинейными зависимостями и взаимодействиями признаков. Линейной модели сложно выразить правило «если высокий доход полезен только при низкой долговой нагрузке», а дерево делает это органично.

    Микропример: в задаче кредитного скоринга дерево может сначала спросить, была ли просрочка за последние 90 дней. Если да — риск резко повышается. Если нет — модель может перейти к анализу дохода и стажа работы. То есть важность признака зависит от контекста, а не задаётся одним глобальным коэффициентом.

    !Как дерево решений последовательно делит пространство признаков

    Главная интуиция здесь такая: дерево не строит одну гладкую формулу на всё пространство, а делит мир на куски и на каждом куске действует по локальному правилу.

    Как дерево выбирает разбиения

    Вы наверняка замечали, что хороший вопрос быстро отделяет важные случаи от неважных. Например, врачу на первичном приёме полезнее сначала спросить о высокой температуре, чем о любимой еде пациента. В деревьях решений тот же принцип реализуется через жадный выбор разбиения.

    На каждом узле дерево ищет такой признак и такой порог, которые лучше всего разделяют данные. Для классификации это обычно измеряется через снижение неоднородности узла, например по критерию Gini impurity или entropy. Для регрессии — через уменьшение разброса целевой переменной.

    Микропример: если после разбиения по признаку «число просрочек» в левую ветку почти целиком попадают хорошие клиенты, а в правую — проблемные, это сильное разбиение. Если же обе ветки по-прежнему смешанные, пользы мало.

    Важно понимать, что дерево выбирает локально лучший шаг, а не глобально идеальную структуру целиком. Именно поэтому его рост называют жадным. Это быстро и практично, но создаёт риск переобучения: если дать дереву расти слишком глубоко, оно начнёт подгоняться под шум.

    Смещение, разброс и слабость одного дерева

    Одиночное дерево удобно интерпретировать, но оно нестабильно. Небольшое изменение данных может привести к другой структуре дерева. Это означает высокий variance: модель сильно зависит от конкретной выборки.

    Микропример: если в датасете на границе разбиения случайно добавились несколько необычных примеров, корневой узел дерева может выбрать другой порог, и вся структура ниже перестроится. Получится уже другая логика решений.

    Эта нестабильность — не просто недостаток. Она даёт возможность строить сильные ансамбли. Если у нас есть много «капризных», но полезных базовых моделей, можно усреднить их поведение и получить более устойчивый итог.

    Random Forest: сила усреднения

    Random Forest — это ансамбль многих деревьев, каждое из которых обучается на случайной подвыборке объектов и случайном подмножестве признаков. Идея выглядит почти бытовой: если один эксперт легко ошибается, спросите сто экспертов, которые смотрят на задачу немного по-разному, и усредните их ответы.

    Почему это работает? Потому что усреднение снижает variance. Каждое дерево шумит по-своему, но коллективное решение оказывается стабильнее. Случайность в выборе объектов и признаков дополнительно уменьшает корреляцию между деревьями, а это делает усреднение ещё полезнее.

    Микропример: представьте оценку стоимости квартиры. Один агент может переоценить вид из окна, другой — близость метро, третий — ремонт. Средняя оценка группы обычно надёжнее, чем мнение одного специалиста.

    Практически Random Forest хорош тем, что:

  • мало требует предобработки;
  • устойчив к нелинейностям и взаимодействиям;
  • даёт полезные меры важности признаков;
  • часто силён на табличных задачах «из коробки».
  • Но есть и ограничения. Лес хуже интерпретируем, чем одно дерево, может быть тяжёлым по памяти и не всегда оптимален на очень больших sparse-задачах или там, где критична дифференцируемость.

    Bagging против Boosting

    На этом месте полезно сделать паузу и активировать знакомую интуицию. Если группа людей решает задачу, можно действовать по-разному. Первый путь — дать всем одинаковую задачу независимо и усреднить ответы. Второй — попросить каждого следующего смотреть именно на ошибки предыдущего. В ML эти две стратегии соответствуют bagging и boosting.

    В bagging модели учатся параллельно и независимо. Random Forest — главный пример. В boosting модели учатся последовательно: каждая новая исправляет промахи уже построенного ансамбля.

    !Bagging и boosting усиливают модели разными способами

    Это различие критично. Bagging в первую очередь снижает variance, а boosting часто снижает bias, последовательно приближая ансамбль к сложной зависимости.

    Микропример: bagging — как собирать среднюю оценку у группы студентов. Boosting — как давать следующему студенту специально те задачи, на которых предыдущий ошибся.

    Gradient Boosting: ансамбль, который исправляет ошибки

    Gradient Boosting строит модели последовательно. Первая делает грубое предсказание. Вторая обучается на ошибках первой. Третья — на ошибках уже обновлённого ансамбля, и так далее. На каждом шаге мы не начинаем с нуля, а улучшаем уже имеющееся решение.

    Почему это называется gradient? Потому что в общей формулировке каждый новый базовый алгоритм приближает направление, в котором нужно уменьшать функцию потерь. Это перенос идеи градиентного спуска в пространство функций, а не отдельных числовых параметров.

    Микропример: если первая модель недооценивает дорогие квартиры, следующая может специализироваться именно на этой систематической ошибке. Потом ещё одна поправит остатки после второй. В итоге ансамбль становится намного точнее, чем любое одно дерево.

    !Как boosting последовательно исправляет остаточные ошибки

    На практике базовыми моделями в boosting чаще всего служат неглубокие деревья. Они достаточно просты, чтобы не переобучаться мгновенно, но достаточно гибки, чтобы ловить полезные локальные структуры.

    XGBoost: инженерная эволюция boosting

    Когда говорят о сильных моделях для табличных данных, почти неизбежно всплывает XGBoost. Это не новая математическая идея с нуля, а очень сильная инженерная и алгоритмическая реализация boosting-подхода.

    Почему он стал таким популярным:

  • использует регуляризацию структуры деревьев и весов листьев;
  • эффективно работает с пропусками;
  • поддерживает важные трюки ускорения и параллелизации;
  • устойчив и силён на реальных соревнованиях и продакшн-задачах.
  • Микропример: на Kaggle XGBoost годами был одной из первых моделей, к которым обращались на табличных датасетах, потому что при умеренном тюнинге он часто давал очень сильный baseline или даже near-SOTA результат.

    Полезно понимать, что XGBoost усиливает не только точность, но и требования к аккуратности настройки. Слишком глубокие деревья, слишком много итераций, неудачный learning rate — и модель легко начнёт переобучаться.

    Разобранный пример: дерево и лес для кредитного скоринга

    Представим банк, который хочет предсказывать вероятность дефолта. Есть признаки: доход, долговая нагрузка, стаж работы, число просрочек, регион, тип занятости.

    Шаг 1. Обучаем одиночное дерево ограниченной глубины. Почему так: сначала полезно получить интерпретируемый baseline и увидеть логику первых разбиений.

    Шаг 2. Анализируем корневые и верхние узлы. Почему так: именно они задают крупные бизнес-правила и часто уже дают полезный смысл, например что недавние просрочки сильнее дохода.

    Шаг 3. Замечаем нестабильность качества по разным разбиениям train/validation. Почему так: одно дерево чувствительно к случайности выборки.

    Шаг 4. Переходим к Random Forest из 300 деревьев с ограничением по глубине и случайным подмножеством признаков в узлах. Почему так: это снижает variance и делает предсказания устойчивее.

    Шаг 5. Сравниваем качество и видим прирост ROC-AUC, а также меньший разброс метрики между фолдами. Почему так: ансамбль усредняет случайные колебания отдельных деревьев.

    Шаг 6. Изучаем важности признаков и проверяем, не возникает ли утечки. Почему так: очень высокая важность подозрительного признака может означать data leakage, а не реальную закономерность.

    Этот пример показывает типичный рабочий путь: от интерпретируемой модели к более сильному ансамблю с контролем устойчивости.

    Разобранный пример: boosting в задаче прогноза спроса

    Теперь более практичный кейс. Ритейлер хочет прогнозировать спрос на товары по магазинам. Данные включают цену, скидку, день недели, сезонность, остатки, конкурирующие акции, погоду и историю продаж.

    Шаг 1. Строим простую baseline-модель и замечаем, что ошибки особенно велики на акционных товарах. Почему так: baseline редко ловит сложные взаимодействия между скидкой, типом товара и сезонностью.

    Шаг 2. Обучаем Gradient Boosting на неглубоких деревьях. Почему так: такие деревья хорошо подхватывают локальные нелинейности, не теряя устойчивости сразу.

    Шаг 3. Наблюдаем, что новые итерации постепенно уменьшают остаточные ошибки, особенно на трудных сегментах. Почему так: boosting концентрируется на том, что предыдущий ансамбль объяснил плохо.

    Шаг 4. Переходим к XGBoost и добавляем регуляризацию, контроль глубины, subsampling и early stopping. Почему так: это позволяет сильнее контролировать переобучение и эффективнее использовать данные.

    Шаг 5. Оцениваем модель на отложенном временном периоде, а не на случайном split. Почему так: для временных рядов случайное перемешивание может дать нереалистично оптимистичную оценку.

    Здесь особенно видно, что сила ансамблей деревьев раскрывается не в абстракции, а в данных с неоднородной структурой, пропусками, нелинейными эффектами и смешанными типами признаков.

    Частые ловушки и ошибки

    Первая ошибка — думать, что дерево «само всё поймёт» без грамотной валидации. На табличных данных утечки признаков особенно коварны, и ансамбль может выглядеть блестяще просто потому, что увидел подсказку из будущего.

    Вторая — слепо доверять feature importance. Эти метрики полезны, но не равны причинной интерпретации и иногда смещены в пользу признаков с большим числом уникальных значений.

    Третья — считать boosting автоматически лучше леса. На практике выбор зависит от данных, объёма тюнинга, требований к скорости и устойчивости. Random Forest часто проще и надёжнее как стартовая модель, а boosting выигрывает при более точной настройке.

    Если из этой главы запомнить только три вещи — это:

  • Дерево решений сильно тем, что моделирует локальные правила и взаимодействия признаков, но само по себе нестабильно.
  • Random Forest использует bagging, чтобы усреднением уменьшить variance и сделать деревья надёжнее.
  • Gradient Boosting и XGBoost строят ансамбль последовательно, исправляя ошибки предыдущих моделей, и именно поэтому так сильны на табличных данных.
  • 7. Классический ML: SVM, K-Means, DBSCAN, кросс-валидация и регуляризация

    Классический ML: SVM, K-Means, DBSCAN, кросс-валидация и регуляризация

    Почему два облака точек, которые выглядят почти одинаково, одна модель разделяет уверенно, а другая — катастрофически плохо? Потому что в ML важно не только «нарисовать границу», но и понять, какой запас устойчивости у этой границы, какая структура есть в данных без разметки и насколько честно мы оцениваем качество. Именно на этом пересечении находятся SVM, K-Means, DBSCAN, кросс-валидация и регуляризация.

    Вы наверняка замечали, что хороший разделитель в жизни — это не линия, проходящая впритык к двум группам людей, а линия с запасом, чтобы никто случайно не оказался по другую сторону. В SVM эта интуиция формализуется как максимизация зазора. А в кластеризации, наоборот, задача часто состоит не в разделении уже известных классов, а в поиске скрытой структуры, когда правильных ответов заранее нет.

    SVM: классификация через максимальный зазор

    Support Vector Machine, или SVM, — это алгоритм классификации, который ищет разделяющую гиперплоскость с максимальным margin, то есть запасом до ближайших точек обоих классов. Идея мощная именно своей геометрической простотой: если границу провести с большим зазором, она чаще лучше обобщается на новые данные.

    Почему это важно? Потому что не каждая разделяющая линия одинаково хороша. Можно провести прямую, которая правильно разделяет текущие точки, но проходит почти вплотную к некоторым из них. Тогда малый шум в новых данных легко вызовет ошибку. SVM предпочитает более «осторожную» границу.

    Микропример: представьте две полосы припаркованных машин и линию, по которой должен проехать робот. Если траектория идёт в сантиметре от зеркал, малейшее отклонение опасно. Если между рядами есть запас, маршрут устойчивее. Margin — это и есть такой запас в признаковом пространстве.

    !SVM ищет не любую границу, а максимальный зазор между классами

    Особую роль играют support vectors — точки, которые лежат ближе всего к границе и фактически определяют её положение. Интуитивно это «критические случаи». Далёкие точки менее важны: даже если убрать часть из них, граница может почти не измениться.

    Мягкий зазор и параметр C

    В идеальном мире классы линейно разделимы. В реальных данных почти всегда есть шум, выбросы и наложение классов. Поэтому практический SVM использует soft margin — допускает некоторые нарушения разделения, но штрафует за них. Силу этого штрафа контролирует параметр C.

    Если C очень велик, модель старается классифицировать training points как можно точнее и может построить слишком жёсткую границу. Если C меньше, допускается больше ошибок на обучении, но граница становится мягче и устойчивее.

    Микропример: если в данных о клиентах есть несколько аномальных наблюдений с ошибочной разметкой, слишком жёсткий SVM подстроится под них и ухудшит качество на нормальных новых клиентах.

    Здесь хорошо видно, что регуляризация — не абстракция из учебника, а геометрический компромисс между точным fit и устойчивостью.

    Ядра: когда линейная граница недостаточна

    Вы наверняка видели задачи, где классы расположены кольцом, спиралью или сложной дугой. Линейной границей их не разделить. SVM решает это через kernel trick — способ вычислять скалярные произведения в более богатом пространстве признаков, не переходя туда явно.

    Идея в том, что в исходном пространстве задача может быть нелинейной, а в более высоком — уже линейной. Ядро позволяет работать так, словно мы выполнили это преобразование, но без явного построения новых признаков.

    Микропример: точки внутри круга и снаружи круга на плоскости не разделяются прямой. Но если добавить признак, связанный с расстоянием от центра, разделение становится возможным.

    На практике ядровые SVM сильны на средних по размеру задачах, но плохо масштабируются на очень большие датасеты. Это один из примеров, когда математическая красота сталкивается с вычислительной ценой.

    K-Means: кластеризация через центры

    Теперь перейдём к задаче без разметки. K-Means — это алгоритм, который разбивает объекты на кластеров так, чтобы каждый объект был ближе к своему центру, чем к другим центрам. По сути он ищет компактные группы в пространстве признаков.

    Почему это полезно? Потому что в реальной аналитике и ML часто нужно сначала понять структуру данных: сегменты клиентов, типы документов, режимы работы оборудования. Классов заранее нет, и кластеризация становится способом увидеть естественные группы.

    Микропример: интернет-магазин может не знать готовые сегменты аудитории, но K-Means способен выделить группы вроде «редкие дорогие покупки», «частые недорогие покупки», «постоянные клиенты со средним чеком».

    K-Means прост, но важен именно как базовый шаблон итеративной оптимизации. Он повторяет два шага:

  • назначает каждый объект ближайшему центру;
  • пересчитывает центры как среднее своих кластеров.
  • Эти шаги чередуются, пока разбиение не перестанет заметно меняться.

    Почему K-Means не так прост, как кажется

    У алгоритма есть несколько скрытых предположений. Он любит примерно сферические кластеры, чувствителен к масштабу признаков и требует заранее задать число кластеров. Кроме того, результат зависит от начальной инициализации центров.

    Микропример: если один признак — годовой доход, а другой — число покупок в месяц, и первый имеет намного больший масштаб, кластеры будут формироваться в основном по доходу, даже если поведение по покупкам важнее бизнесу.

    Именно поэтому K-Means почти всегда требует стандартизации признаков и нескольких перезапусков с разными инициализациями. Иначе вы рискуете принять артефакт старта за «естественную сегментацию».

    !Меняйте число кластеров и наблюдайте, как K-Means перестраивает сегментацию

    DBSCAN: плотность вместо центров

    Если K-Means хорошо работает на компактных шарообразных кластерах, то DBSCAN смотрит на задачу иначе. Он ищет области высокой плотности точек и расширяет кластеры через соседства. Это делает его особенно полезным для кластеров сложной формы и для выделения шума.

    В DBSCAN есть три типа точек:

  • core points — точки, вокруг которых достаточно соседей;
  • border points — точки на краю плотной области;
  • noise points — точки, не принадлежащие плотным областям.
  • Микропример: если точки изображают две извилистые «луны», K-Means скорее разрежет их неестественно по прямой. DBSCAN, наоборот, может восстановить обе изогнутые формы как отдельные кластеры.

    !DBSCAN различает плотные ядра, границы и шум

    Это огромное преимущество в задачах пространственного анализа, аномалий, геоданных и вообще там, где форма кластеров не обязана быть круглой.

    Но и у DBSCAN есть ограничения. Он чувствителен к выбору параметров радиуса соседства и минимального числа точек. А если плотности кластеров сильно различаются, подобрать единый набор параметров становится трудно.

    Кросс-валидация: честная оценка модели

    Когда вы настраиваете SVM, выбираете число кластеров или силу регуляризации, возникает вопрос: как понять, что модель действительно хороша, а не просто удачно подогналась под один split? Для этого нужна кросс-валидация.

    Идея проста: мы несколько раз делим данные на обучающие и валидационные части, обучаем модель на одних фолдах и проверяем на других. Так качество оценивается не по одной случайной разбивке, а более устойчиво.

    Микропример: если модель показала 0.91 accuracy на одном split, это может быть везение. Если на пяти фолдах она стабильно даёт значения около 0.90–0.92, доверие к результату выше.

    Особенно важно, что кросс-валидация помогает сравнивать гиперпараметры:

  • значение C в SVM;
  • число кластеров или качество инициализации;
  • силу L1/L2-регуляризации;
  • выбор ядра, глубины модели и других настроек.
  • Но здесь есть тонкость: для временных рядов и некоторых структурированных данных обычная случайная кросс-валидация неверна. Она может дать утечку будущего в прошлое.

    Регуляризация как контроль сложности

    Хотя регуляризация часто ассоциируется с линейными моделями, общий принцип шире: мы ограничиваем свободу модели, чтобы улучшить обобщение. В SVM это выражается через компромисс margin и штрафа за ошибки. В других моделях — через ограничения на веса, глубину, число признаков или структуру.

    Микропример: если модель в задаче классификации идеально запомнила train и начала реагировать на случайные колебания, это похоже на студента, который выучил шумные детали конспекта, но не понял тему. Регуляризация возвращает фокус к устойчивым закономерностям.

    Полезно думать о регуляризации не как о «добавке к loss», а как о явном ответе на вопрос: какую сложность модели мы считаем допустимой при данном объёме данных?

    Разобранный пример: SVM для обнаружения спама

    Представим задачу: классифицировать письма как спам или не спам по TF-IDF-признакам текста. Пространство признаков высокоразмерное и разреженное.

    Шаг 1. Преобразуем письма в векторы признаков. Почему так: SVM работает в числовом пространстве, а текст нужно представить геометрически.

    Шаг 2. Начинаем с линейного SVM. Почему так: в высокоразмерных текстовых задачах линейная граница часто уже оказывается достаточно сильной и намного дешевле ядрового варианта.

    Шаг 3. Подбираем параметр C через кросс-валидацию. Почему так: слишком большое значение может переобучиться под редкие паттерны слов, слишком маленькое — сделать границу чрезмерно грубой.

    Шаг 4. Смотрим не только на accuracy, но и на precision/recall. Почему так: стоимость ложноположительных и ложноотрицательных ошибок в спаме различается.

    Шаг 5. Проверяем стабильность результата на нескольких фолдах. Почему так: высокая метрика на одном split недостаточна для доверия.

    Этот пример показывает, почему SVM долго оставался сильным стандартом для текстовой классификации до эпохи больших трансформеров.

    Разобранный пример: сегментация клиентов через K-Means и DBSCAN

    Теперь задача без разметки. Есть данные по 200 000 клиентам: средний чек, частота покупок, доля возвратов, глубина просмотра, число категорий, время между заказами.

    Шаг 1. Масштабируем признаки. Почему так: K-Means и distance-based методы чувствительны к масштабу координат.

    Шаг 2. Запускаем K-Means для нескольких значений и оцениваем инерцию и бизнес-интерпретацию сегментов. Почему так: формально можно подобрать по метрике, но без бизнес-смысла сегментация бесполезна.

    Шаг 3. Замечаем, что часть клиентов образует вытянутую редкую группу с аномальным поведением. Почему так: K-Means склонен прятать такие случаи внутрь ближайшего центра, хотя бизнесу они могут быть интересны как отдельные аномалии.

    Шаг 4. Запускаем DBSCAN на подмножестве признаков поведения. Почему так: density-based подход лучше выделяет плотные группы и выбрасывает шум.

    Шаг 5. Сравниваем результаты и понимаем, что методы отвечают на разные вопросы. Почему так: K-Means сегментирует всю массу клиентов, а DBSCAN помогает найти естественные плотные режимы и выбросы.

    Это важный профессиональный вывод: не существует «лучшей кластеризации вообще». Есть постановка задачи, свойства данных и полезность результата для принятия решений.

    Частые ошибки

    Первая ошибка — выбирать число кластеров исключительно по одной численной метрике. Кластеры нужны не ради геометрии как таковой, а ради смысла и действия.

    Вторая — использовать случайную кросс-валидацию там, где есть зависимость по времени, пользователю или объекту. Это создаёт утечку и завышенные оценки.

    Третья — считать, что регуляризация только «мешает модели учиться». На деле именно она часто спасает модель от красивого провала на новых данных.

    Если из этой главы запомнить только три вещи — это:

  • SVM ценен не просто разделяющей линией, а идеей максимального зазора и контроля сложности через параметр C.
  • K-Means и DBSCAN ищут кластеры по разной логике: через центры и через плотность, поэтому подходят к разным типам структуры.
  • Кросс-валидация и регуляризация — это не второстепенные техники, а механизмы, которые делают оценку качества и выбор модели честными.
  • 8. Deep Learning с нуля: перцептрон, MLP и backpropagation вручную

    Deep Learning с нуля: перцептрон, MLP и backpropagation вручную

    Как из набора чисел получить систему, которая сначала едва угадывает, а потом начинает различать классы, приближать сложные функции и извлекать полезные представления? Если смотреть на нейросеть как на чёрный ящик, это кажется магией. Но если разобрать её на части, окажется, что в основе лежат очень конкретные механизмы: линейное преобразование, нелинейность и последовательная передача ошибок назад. Именно с этого начинается Deep Learning.

    Вы уже видели линейную и логистическую регрессию. Нейросеть делает следующий шаг: вместо одной линейной комбинации признаков она строит цепочку преобразований. Это позволяет моделировать куда более сложные зависимости. Но вместе с гибкостью приходит новый вопрос: как обучать все эти слои сразу? Ответ даёт backpropagation — алгоритм, без которого современное глубокое обучение было бы практически невозможным.

    Перцептрон: простейший нейрон как пороговое решение

    Перцептрон — это исторически одна из первых моделей искусственного нейрона. Он берёт входные признаки, умножает их на веса, складывает, добавляет смещение и затем применяет правило решения. В ранней версии это было жёсткое пороговое правило: если сумма выше порога — один класс, иначе другой.

    Зачем это важно, если сегодня используют гораздо более сложные сети? Потому что перцептрон показывает базовую идею нейросети: каждый вход влияет на выход не напрямую, а через обучаемый вес. Эти веса определяют, какие признаки важнее, а какие можно почти игнорировать.

    Микропример: если модель распознаёт, является ли письмо спамом, то наличие слова «скидка» может получить положительный вес, а наличие имени знакомого отправителя — отрицательный. Нейрон суммирует эти сигналы и принимает решение.

    Но у одиночного перцептрона есть жёсткое ограничение: он умеет строить только линейную границу. Это значит, что задачи с по-настоящему сложной геометрией ему недоступны.

    Почему одного слоя недостаточно

    Вы наверняка замечали, что многие решения в жизни принимаются не одним правилом, а несколькими последовательными фильтрами. Например, при найме сначала смотрят на базовую квалификацию, затем на опыт, затем на соответствие команде. Одно условие редко бывает достаточным.

    В нейросетях похожую роль играет многослойный перцептрон, или MLP. Это сеть из нескольких слоёв нейронов, где каждый следующий слой работает не с исходными признаками, а с представлением, созданным предыдущим слоем. Именно так сеть может постепенно извлекать всё более абстрактные признаки.

    !От одиночного перцептрона к многослойной сети

    Микропример: в задаче распознавания рукописных цифр первый слой может реагировать на локальные комбинации пикселей, следующий — на простые штрихи и контуры, а более поздний — на целостный образ цифры. Даже если MLP не так хорош для изображений, сама логика иерархии представлений здесь отлично видна.

    Ключевой момент: если между линейными слоями нет нелинейности, сколько слоёв ни складывай, всё сведётся к одному линейному преобразованию. Поэтому глубина начинает иметь смысл только вместе с нелинейными функциями активации.

    Forward pass: как сеть получает предсказание

    Forward pass — это прямой проход данных через сеть от входа к выходу. На каждом слое происходит одно и то же базовое действие: линейное преобразование плюс активация. С точки зрения кода это последовательность матричных операций, с точки зрения смысла — поэтапное изменение представления данных.

    Зачем это понимать глубоко? Потому что нейросеть — это не «одна большая формула», а композиция маленьких функций. Если вы понимаете каждый шаг прямого прохода, вам проще и отлаживать модель, и выводить градиенты вручную.

    Микропример: в задаче классификации цветов по измерениям лепестков первый слой может преобразовать четыре входных признака в восемь скрытых сигналов, второй — собрать из них ещё более удобные признаки, а последний — превратить всё это в вероятности классов.

    Практически forward pass — это место, где особенно важны формы массивов. Ошибка в размерности одного слоя ломает всю сеть. Поэтому ручная реализация MLP на NumPy — отличный способ научиться «чувствовать» тензоры до перехода в PyTorch.

    Нелинейность как источник выразительности

    Ранее мы говорили, что без нелинейности сеть остаётся эквивалентной одной линейной модели. Значит, именно функции активации делают нейросеть выразительной. В этой статье нам достаточно базовой интуиции: активация меняет сигнал после линейного слоя так, чтобы сеть могла описывать более сложные зависимости.

    Исторически использовали сигмоиду и гиперболический тангенс, но уже на раннем этапе обучения с нуля полезно понять, почему жёсткие насыщаемые функции создают проблемы. Когда активация почти не меняется на больших участках входа, градиенты через неё могут становиться очень малыми.

    Микропример: если ручка регулировки звука застряла почти в крайнем положении и почти не реагирует на движение, вы можете долго крутить её без заметного эффекта. У насыщаемых активаций похожая проблема для обучения.

    В MLP это проявляется как медленное обучение или исчезающие градиенты, особенно если сеть углубляется.

    Backpropagation: как ошибка проходит назад

    Вот главный вопрос: если на выходе сети ошибка одна, как понять, какой именно вес в первом скрытом слое виноват и насколько? Backpropagation отвечает именно на это. Он передаёт информацию об ошибке от выхода к входу, используя цепное правило.

    Смысл здесь не в том, что градиенты «магически появляются», а в том, что сложная сеть разбивается на последовательность простых операций. Для каждой операции можно вычислить локальный вклад в производную, а затем перемножить эти вклады в нужном порядке.

    !Вычислительный граф нейросети и обратное распространение ошибки

    Это важный концептуальный сдвиг: вместо попытки дифференцировать всю сеть целиком вы дифференцируете её по частям. Именно так обучение становится масштабируемым.

    Микропример: если итоговый счёт в магазине зависит от цены товара, скидки и налога, то изменение цены влияет на финальную сумму через всю цепочку. Чтобы понять общий эффект, нужно пройти путь зависимости целиком. Backpropagation делает это систематически для сети из тысяч операций.

    Где именно рождаются градиенты

    Для ручной реализации полезно не просто знать слово backpropagation, а видеть структуру вычислений. Пусть последний слой выдаёт logits, затем применяется loss. Тогда обучение идёт так:

  • на выходе считается производная loss по выходу сети;
  • эта ошибка переводится в градиенты по весам последнего слоя;
  • затем ошибка передаётся в предыдущий слой;
  • повторяется тот же шаблон до самого начала сети.
  • !Пошаговый backpropagation через слои сети

    Микропример: если сеть ошиблась на объекте и вы хотите исправить ранний вес, нельзя менять его «на глаз». Нужно понять, как он повлиял на следующий слой, тот — на следующий, и так до итоговой ошибки.

    На уровне матриц это означает, что каждый слой должен хранить свои входы и промежуточные активации из forward pass, потому что без них backward корректно не посчитать.

    Разобранный пример: MLP для XOR

    Исторически задача XOR стала символом ограничения одиночного перцептрона. Четыре точки на плоскости расположены так, что линейной прямой их не разделить. Но маленький MLP с одним скрытым слоем может справиться.

    Шаг 1. Берём вход размерности 2 и скрытый слой, например из 4 нейронов. Почему так: одного скрытого слоя уже достаточно, чтобы создать нелинейное представление, в котором задача станет решаемой.

    Шаг 2. Инициализируем веса случайно. Почему так: если задать одинаковые веса всем нейронам, они будут учиться одинаково, и сеть потеряет смысл многослойности.

    Шаг 3. Делаем forward pass: вход, линейное преобразование, активация, второй линейный слой, выход. Почему так: сеть должна получить текущий прогноз по всем четырём точкам XOR.

    Шаг 4. Считаем loss между предсказаниями и истинными метками. Почему так: без числовой меры ошибки мы не знаем, насколько нужно менять параметры.

    Шаг 5. Проводим backward pass, получая градиенты по каждому весу и смещению. Почему так: это единственный систематический способ узнать вклад каждого параметра в ошибку.

    Шаг 6. Обновляем параметры и повторяем много эпох. Почему так: за один шаг задача почти никогда не решается; сеть постепенно перестраивает внутреннее представление.

    Со временем скрытый слой начинает кодировать комбинации признаков так, что финальный линейный слой уже легко отделяет нужный класс. Именно в этом и состоит сила многослойности.

    Разобранный пример: классификация ирисов на NumPy

    Теперь более практичная задача: классификация цветков Iris по четырём признакам. Возьмём MLP с одним скрытым слоем.

    Шаг 1. Делим данные на train и validation, стандартизируем признаки. Почему так: нейросети чувствительны к масштабу входов, а без validation нельзя судить об обобщении.

    Шаг 2. Кодируем метки классов и выбираем архитектуру 4 → 16 → 3. Почему так: 4 входа соответствуют признакам, 16 скрытых нейронов дают модели достаточную гибкость, 3 выхода — числу классов.

    Шаг 3. Реализуем все части вручную: linear forward, activation forward, softmax, loss, backward для каждого блока. Почему так: только так начинает по-настоящему проясняться механика нейросети.

    Шаг 4. Обучаем сеть mini-batch градиентным спуском. Почему так: батчи уменьшают шум градиентов по сравнению с SGD по одному объекту и ближе к реальной практике.

    Шаг 5. Следим за train loss и validation accuracy. Почему так: снижение train loss само по себе ещё не означает хорошее обобщение.

    Обычно даже такая простая сеть быстро достигает хорошего качества на Iris, и это отличный тренировочный полигон перед более серьёзными архитектурами.

    Практические ловушки ручной реализации

    Первая ловушка — ошибки размерностей. Неверная транспозиция или несовпадение форм в backward быстро приводят к непонятным багам. На бумаге всё красиво, а в коде одна пропущенная ось ломает вычисления.

    Вторая — неправильное усреднение по батчу. Если в одном месте вы усреднили loss, а в другом забыли так же нормировать градиенты, шаги оптимизации станут зависеть от размера батча.

    Третья — численная нестабильность в softmax и логарифмах. Даже маленькая сеть может выдавать nan, если реализовать loss наивно.

    Микропример: если logits в конце обучения становятся очень большими, экспоненты без стабилизации могут переполниться. Модель «сломалась» не потому, что идея неверна, а потому что реализация недостаточно аккуратна.

    Почему ручная реализация так важна перед PyTorch

    Может возникнуть вопрос: если всё это уже умеет PyTorch, зачем вообще писать MLP вручную? Потому что без этого фреймворк легко превращается в магическую коробку. А когда что-то идёт не так — exploding gradients, плохая сходимость, странные формы тензоров, — помочь сможет только понимание механики.

    Ручная реализация даёт три профессиональных преимущества:

  • вы понимаете, что именно вычисляет слой;
  • вы видите, как устроен backward;
  • вы легче читаете статьи и нестандартные архитектуры, потому что умеете мысленно разбирать их на блоки.
  • Если из этой главы запомнить только три вещи — это:

  • Перцептрон важен не своей мощностью, а тем, что вводит базовую схему нейрона: веса, сумма, активация.
  • MLP становится выразительным только благодаря сочетанию линейных слоёв и нелинейностей.
  • Backpropagation — это практический механизм передачи ошибки назад через вычислительный граф, а не абстрактная формула из учебника.
  • 9. Deep Learning: активации, инициализация, BatchNorm, Dropout, основы PyTorch

    Deep Learning: активации, инициализация, BatchNorm, Dropout, основы PyTorch

    Почему две нейросети с одинаковой архитектурой могут вести себя совершенно по-разному: одна быстро учится, другая застывает на месте, третья начинает выдавать нестабильные градиенты? Очень часто дело не в «большой идее» модели, а в, казалось бы, второстепенных деталях: какой выбрана функция активации, как инициализированы веса, используется ли Batch Normalization, есть ли Dropout, и насколько правильно вы организовали код в PyTorch.

    Вы уже разобрали MLP и backpropagation вручную. Теперь наступает важный переход от «сеть вообще умеет учиться» к «сеть учится стабильно и эффективно». Это почти как разница между автомобилем, который в принципе едет, и автомобилем, на котором можно безопасно ехать быстро, долго и в разных условиях.

    Функции активации: где сеть перестаёт быть линейной

    Если линейные слои только смешивают признаки, то функция активации придаёт сети способность моделировать нелинейные зависимости. Именно она решает, как преобразовать сумму сигналов перед передачей дальше.

    Исторически широко использовали сигмоиду и tanh. Они дают гладкий ограниченный выход и удобны для теоретических рассуждений. Но в глубоких сетях быстро выяснилось, что они страдают насыщением: на больших по модулю входах градиенты становятся малыми, и обучение тормозится.

    Микропример: если усилитель звука уже почти дошёл до предела, дальнейший поворот ручки почти ничего не меняет. Градиент в такой зоне маленький, а значит и обучение почти не чувствует, куда двигаться.

    На практике революцию сделала ReLU — простая активация, которая пропускает положительные значения и обнуляет отрицательные. Её сила не в изысканности, а в том, что она помогает градиентам проходить через глубокие сети заметно лучше, чем сигмоида.

    Но и у ReLU есть недостаток: если нейрон стабильно получает отрицательный вход, он может «умереть» и перестать обновляться. Поэтому появились варианты вроде Leaky ReLU, ELU, GELU.

    Микропример: в трансформерах часто используют GELU, потому что она даёт более мягкое нелинейное поведение и хорошо сочетается с современными архитектурными практиками.

    Почему инициализация весов решает больше, чем кажется

    Вы наверняка помните, что если все веса инициализировать одинаково, нейроны будут учиться одинаково и симметрия не сломается. Но на этом история не заканчивается. Даже случайная инициализация может быть плохой, если масштаб весов выбран неудачно.

    Если веса слишком велики, активации и градиенты могут взрываться. Если слишком малы — сигнал будет затухать по мере прохождения через слои. Значит, инициализация должна не просто «ломать симметрию», а поддерживать разумный масштаб сигналов.

    Микропример: представьте цепочку людей, передающих шёпотом сообщение. Если каждый добавляет слишком много усиления, к концу получится крик и искажение. Если каждый чуть заглушает звук, к концу сообщение исчезнет. Хорошая инициализация держит сигнал в рабочем диапазоне.

    Для этого используются схемы вроде Xavier initialization и He initialization. Их смысл в том, чтобы учитывать число входов и выходов слоя и подбирать масштаб случайных весов так, чтобы вариация сигнала не разрушалась слишком быстро.

    !Сравните, как разные активации и масштабы инициализации меняют сигнал по слоям

    Практически это одна из тех деталей, которые особенно заметны при переходе от учебных MLP к более глубоким сетям.

    Batch Normalization: стабилизация внутренних представлений

    Когда сеть обучается, распределения активаций внутри неё меняются. Из-за этого каждому слою приходится постоянно адаптироваться к слегка другой статистике входа. Batch Normalization, или BatchNorm, частично решает эту проблему: она нормализует активации внутри батча и затем позволяет слою обучить удобный масштаб и сдвиг.

    Важно понимать это не как «магическую нормировку», а как механизм управления динамикой обучения. BatchNorm часто делает оптимизацию стабильнее, позволяет использовать большие learning rate и служит слабой формой регуляризации.

    !Где BatchNorm вставляется в вычислительный поток слоя

    Микропример: если сотрудники каждый день получают отчёт в новом формате, им трудно работать быстро. Если формат стабилизирован, адаптация проще. BatchNorm делает нечто похожее для слоёв сети.

    Но есть нюансы. BatchNorm зависит от статистики батча, а значит плохо себя чувствует при очень маленьких батчах. Кроме того, в режиме inference используются накопленные running statistics, и новички часто путают train/eval режимы, получая странные результаты.

    Dropout: защита от чрезмерной коадаптации

    Dropout — это регуляризационный приём, при котором на этапе обучения часть нейронов случайно отключается. Сеть не может рассчитывать на один и тот же набор путей прохождения сигнала и вынуждена строить более устойчивые представления.

    Почему это работает? Потому что нейроны меньше переобучаются на совместное существование конкретных соседей. Сеть учится распределять информацию более избыточно и устойчиво.

    Микропример: если в проектной команде периодически недоступен один из ключевых участников, остальные вынуждены понимать задачу шире, а не слепо зависеть от одного человека. Dropout создаёт похожее давление на сеть.

    !Dropout случайно выключает часть нейронов во время обучения

    На практике Dropout полезен не везде одинаково. В старых fully connected сетях он часто был очень эффективен. В современных архитектурах с BatchNorm и residual-связями его роль стала более контекстной. В трансформерах Dropout всё ещё широко используется, но уже как часть более широкой системы регуляризации.

    Как все эти техники связаны друг с другом

    Очень важно не изучать активации, инициализацию, BatchNorm и Dropout как разрозненный список приёмов. Это единая история о прохождении сигнала и градиента через сеть.

  • активация определяет форму нелинейного преобразования;
  • инициализация задаёт стартовый масштаб сигналов;
  • BatchNorm стабилизирует статистику активаций во время обучения;
  • Dropout мешает сети слишком рано подстроиться под частные паттерны train-данных.
  • Микропример: если сеть — это фабрика, активация определяет тип обработки детали, инициализация — качество стартовой настройки станков, BatchNorm — контроль стабильности сырья, Dropout — стресс-тест на отказ отдельных узлов.

    Этот взгляд особенно полезен, когда вы видите плохую сходимость. Проблема может быть не в «недостатке данных» или «неудачной архитектуре вообще», а в том, что один из этих механизмов нарушает поток сигналов.

    Основы PyTorch: от ручной математики к автоматическому дифференцированию

    После ручной реализации сети наступает момент, когда нужно перейти к более мощному инструменту. PyTorch — это фреймворк, который даёт тензоры, автоматическое дифференцирование и удобную модульную сборку моделей. Но его сила проявляется только тогда, когда вы понимаете, что именно он автоматизирует.

    Вы уже знаете, что forward pass — это композиция операций, а backward — применение цепного правила. В PyTorch это реализовано через autograd: фреймворк строит вычислительный граф и сам считает градиенты, если операции дифференцируемы.

    Микропример: если вы пишете слой как матричное умножение, активацию и loss, PyTorch запоминает нужные промежуточные связи и затем по команде backward() вычисляет градиенты по всем параметрам.

    Ключевые строительные блоки здесь:

  • Tensor — многомерный массив с возможностью отслеживания градиентов;
  • nn.Module — базовый класс для слоёв и моделей;
  • optim — оптимизаторы;
  • DataLoader — потоковая подача данных батчами.
  • Важно не путать «понимаю PyTorch» с «могу написать 20 строк tutorial-кода». Настоящее владение начинается, когда вы можете осмысленно проектировать forward, контролировать train/eval режимы, понимать shapes и отлаживать нестандартное поведение.

    Разобранный пример: MLP в PyTorch с BatchNorm и Dropout

    Представим задачу классификации табличных данных по 100 признакам. Мы строим сеть: входной слой, два скрытых слоя, выход.

    Шаг 1. Определяем nn.Module с последовательностью Linear -> BatchNorm -> ReLU -> Dropout. Почему так: такая композиция даёт стабильность активаций, нелинейность и регуляризацию.

    Шаг 2. Инициализируем слои разумно, например используя стандартные инициализации PyTorch, близкие к He для ReLU-подобных активаций. Почему так: хороший старт уменьшает риск плохой динамики в первых эпохах.

    Шаг 3. В forward последовательно прогоняем тензор через блоки. Почему так: структура кода должна максимально соответствовать логике модели.

    Шаг 4. На каждой итерации обучения переводим модель в режим train(). Почему так: BatchNorm и Dropout ведут себя по-разному в train и eval режимах.

    Шаг 5. Считаем loss, вызываем backward(), делаем шаг оптимизатора и очищаем градиенты. Почему так: это базовый цикл обучения в PyTorch.

    Шаг 6. Для валидации переключаемся в eval() и отключаем градиенты. Почему так: иначе BatchNorm и Dropout продолжат вести себя как на обучении, а вычисления будут лишне дорогими.

    Это, казалось бы, технические детали, но именно на них построена корректность большинства реальных учебных циклов.

    Практический кейс: когда BatchNorm мешает, а не помогает

    Есть соблазн думать, что BatchNorm всегда улучшает обучение. Но на практике всё сложнее. Если батчи очень маленькие, статистики становятся шумными. В задачах сегментации, детекции или на огромных изображениях batch size может быть 2–4, и BatchNorm начинает работать нестабильно.

    В таких случаях используют альтернативы вроде LayerNorm, GroupNorm или заморозку статистик. Это важный профессиональный урок: нет универсального «ускорителя обучения», который одинаково полезен в любой архитектуре и при любом режиме вычислений.

    Частые ошибки новичков в PyTorch

    Первая ошибка — забывать model.eval() на валидации и inference. Из-за этого Dropout продолжает случайно выключать нейроны, а BatchNorm обновляет статистики, и качество «плавает».

    Вторая — не вызывать optimizer.zero_grad(). Тогда градиенты накапливаются между шагами, и обновления становятся неправильными.

    Третья — не понимать формы тензоров. Особенно это болезненно при переходе к CNN, RNN и трансформерам, где порядок осей начинает играть критическую роль.

    Если из этой главы запомнить только три вещи — это:

  • Функция активации и инициализация вместе определяют, будет ли сигнал проходить через сеть устойчиво.
  • BatchNorm и Dropout решают разные задачи: стабилизация динамики и регуляризация.
  • PyTorch ценен не тем, что скрывает математику, а тем, что автоматизирует её, сохраняя необходимость понимать вычислительный граф, режимы модели и формы тензоров.