Мастер обучения ИИ: от новичка до создания рабочих моделей

Курс предназначен для быстрого погружения в мир машинного обучения и нейросетей с фокусом на решение практических бизнес-задач. Вы пройдете путь от простых No-Code инструментов до самостоятельного обучения и развертывания современных архитектур, таких как Transformer и YOLO.

Что такое обучение моделей: от правил к примерам

Что такое обучение моделей: от правил к примерам

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

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

Вы попадаете в бесконечную гонку, где вам нужно вручную описывать каждое возможное изменение в мире. А теперь представьте, что вам нужно написать программу, которая отличает фотографию кота от фотографии собаки. Как описать кота правилами? «Если есть усы и треугольные уши»? У собак они тоже бывают. Описать форму глаз в пикселях? При малейшем повороте головы пиксели изменятся, и правило сломается.

Именно здесь традиционное программирование заходит в тупик. И именно для решения таких задач было придумано машинное обучение.

Смена парадигмы: как компьютеры научились учиться

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

Портрет Артура Самуэля

Самуэль понял нечто фундаментальное и ввел термин «машинное обучение» (Machine Learning). Суть этого открытия — в радикальной смене того, как мы ставим задачу компьютеру.

В традиционном программировании мы даем компьютеру Данные (входящее письмо) и Правила (наш код со списком спам-слов). Компьютер применяет правила к данным и выдает Ответ (спам это или нет).

В машинном обучении мы меняем элементы местами. Мы даем компьютеру Данные (тысячи писем) и заранее известные Ответы (мы сами помечаем, какие из них спам, а какие — нормальные). Компьютер анализирует их и сам выводит Правила.

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

Что такое «Модель» и как она учится?

В мире искусственного интеллекта постоянно звучит слово «модель». Модель ChatGPT, модель для распознавания лиц, модель для прогноза продаж. Что это физически?

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

Процесс ее создания можно сравнить с подготовкой студента к экзамену:

  1. Датасет (учебник и билеты прошлых лет): Вы собираете исторические данные. Например, 10 000 обращений клиентов в техподдержку, где операторы уже проставили категории: «Оплата», «Технический сбой», «Возврат».
  2. Обучение (зубрежка и поиск логики): Специальный алгоритм анализирует эти тексты. Он замечает: «Ага, если в тексте есть слова карта, списали, деньги, то в 98% случаев это категория Оплата». Алгоритм настраивает свои внутренние параметры, чтобы минимизировать ошибки.
  3. Готовая модель (студент на экзамене): Обучение закончено. Теперь у вас есть «умный черный ящик». Вы подаете в него совершенно новый текст от клиента, который модель никогда раньше не видела, и она предсказывает категорию, опираясь на выученный опыт.

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

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

От теории к вашей рабочей задаче

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

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

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

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

Обучение классификатора картинок в Teachable Machine

Обучение классификатора картинок в Teachable Machine

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

На самом деле, свою первую нейросеть вы можете обучить прямо сейчас, не написав ни строчки кода и не покидая браузер. Для этого мы используем инструмент от Google — Teachable Machine. Это «песочница», которая наглядно демонстрирует весь цикл создания ИИ: от сбора примеров до проверки готовой модели.

Формулируем задачу: что мы хотим автоматизировать?

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

В терминах машинного обучения нам нужно определить классы.

Класс — это категория или ярлык (тот самый «ответ»), который модель должна научиться присваивать новым данным.

В нашем эксперименте будет три класса:

  1. Документ (лист бумаги, договор, счет).
  2. Канцелярия (ручка, степлер, маркер).
  3. Пустой стол (ничего нет, система находится в ожидании).

Шаг 1: Сбор датасета через веб-камеру

В традиционном программировании мы бы сейчас пытались описать правила: «если в кадре есть белый прямоугольник с соотношением сторон 210×297210 \times 297 мм — это документ». В машинном обучении мы просто показываем примеры.

В Teachable Machine мы создаем три категории (класса) и для каждой записываем серию снимков с веб-камеры. Это и есть наш датасет. Чтобы модель получилась качественной, нужно собрать хотя бы по 100–200 изображений для каждого класса.

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

Подход к сбору данных Как это выглядит Результат для модели
Идеально стерильный (ошибка) Вы кладете ручку строго по центру стола. Освещение не меняется. Рука убрана из кадра. Все 200 фото почти идентичны. Модель запомнит конкретную ручку в конкретной точке. Стоит сдвинуть ее на сантиметр — ИИ перестанет ее узнавать.
Реалистичный (правильно) Вы крутите ручку в кадре. Кладете ее в разные углы. Перекрываете часть ручки пальцами. Меняете наклон камеры. Модель выделяет общие признаки «канцелярии» (форма, текстура), игнорируя фон, угол наклона и наличие вашей руки.

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

Шаг 2: Процесс обучения и понятие эпохи

Когда данные собраны, мы нажимаем кнопку «Обучить модель» (Train Model). В этот момент браузер начинает анализировать пиксели на фотографиях, пытаясь найти математические закономерности, отличающие «Документ» от «Канцелярии».

Во время этого процесса вы заметите счетчик, который быстро бежит вперед: 1,2,3501, 2, 3 \dots 50. Это эпохи.

Эпоха (Epoch) — это один полный проход обучающего алгоритма по всему вашему датасету.

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

Шаг 3: Проверка боем (Инференс)

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

В машинном обучении процесс применения готовой модели к новым данным называется инференсом.

Инференс (Inference) — это этап работы обученной модели, когда она делает предсказания на основе новых, неизвестных ей ранее входных данных.

Вы подносите к камере новый документ (не тот, на котором обучали). Модель анализирует кадр и выдает результат в виде процентов, например:

  • Документ: 98%98\%
  • Канцелярия: 1%1\%
  • Пустой стол: 1%1\%

Эти проценты — уверенность модели (Confidence). Модель редко бывает уверена на 100%100\%. Она сообщает: «Исходя из тех примеров, что я видела, вероятность того, что это документ, составляет 98%98\%». В реальных рабочих задачах мы сами задаем порог срабатывания: например, система отправляет документ в базу, только если уверенность модели >90%> 90\%.

Ловушка фона: чему на самом деле учится ИИ?

Во время тестирования в Teachable Machine вы можете столкнуться с парадоксом. Вы показываете камере пустую руку, а модель с уверенностью 95%95\% кричит: «Это документ!». Почему?

Вспомните, как вы собирали данные. Если каждый раз, фотографируя документ, вы держали его левой рукой, а фотографируя канцелярию — клали ее на стол и убирали руки, модель нашла самое простое правило. Она решила, что «Документ» = «Наличие левой руки в кадре». Сама бумага ей была не интересна.

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

Именно поэтому сбор и балансировка данных — это 80%80\% работы инженера по ИИ.

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

Знакомство с Google Colab: запускаем готовый код

Знакомство с Google Colab: запускаем готовый код

В Teachable Machine мы в несколько кликов обучили модель распознавать объекты через веб-камеру. Это отличная демонстрация того, как данные превращаются в правила. Но что, если ваша рабочая задача — проанализировать тональность 10 000 отзывов клиентов, сгенерировать текст или найти дефекты на рентгеновских снимках? Браузерной песочницы уже недостаточно. Нам нужны профессиональные инструменты, но здесь новичков поджидает главная ловушка программирования — настройка рабочего окружения.

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

Чтобы обойти эту стену, дата-саентисты и разработчики ИИ используют облачные среды.

Google Colab — Google Docs для программистов

Google Colab (Colaboratory) — это бесплатный облачный сервис от Google, который позволяет писать и выполнять код прямо в браузере.

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

Colab основан на формате Jupyter Notebook (интерактивный блокнот). В отличие от классических программ, где код пишется сплошным полотном, блокнот разделен на независимые блоки — ячейки.

Анатомия блокнота: текст и код

В Colab есть два основных типа ячеек:

  1. Текстовые ячейки — здесь пишут пояснения, заголовки, вставляют картинки и ссылки. Прямо как в обычном текстовом редакторе.
  2. Ячейки с кодом — здесь живет язык программирования Python. Слева от такой ячейки есть кнопка Play (▶). Нажав на нее, вы отправляете команду на сервер Google, он ее выполняет и печатает ответ прямо под ячейкой.

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

Главное правило: важен порядок запуска, а не расположения

В классическом программировании компьютер читает код строго сверху вниз. В Colab вы можете запускать ячейки в любом порядке. Сервер (среда выполнения) запоминает всё, что вы уже запустили.

Представьте три ячейки с кодом:

  • Ячейка А: x = 10
  • Ячейка Б: x = 5
  • Ячейка В: Покажи x

Если вы нажмете Play в порядке А → В, компьютер покажет 10. Если в порядке А → Б → В, компьютер покажет 5. Если вы перезагрузите страницу и сразу запустите В, компьютер выдаст ошибку, потому что он еще не знает, что такое x.

Сервер хранит данные в оперативной памяти до тех пор, пока вы не закроете вкладку или не нажмете «Перезапустить среду выполнения».

Зачем ИИ нужна видеокарта (GPU)

Если вы откроете меню «Среда выполнения» в Colab, вы найдете пункт «Сменить среду выполнения», где можно выбрать аппаратный ускоритель: CPU (центральный процессор) или GPU (графический процессор / видеокарта). Это важнейший выбор в машинном обучении.

Почему модели ИИ обучают на видеокартах, которые изначально создавались для видеоигр?

Обучение нейросети — это, по сути, миллионы простейших математических операций (сложение и умножение таблиц с числами).

  • CPU (Центральный процессор) — это гениальный профессор математики. Он может решать невероятно сложные задачи, но делает это последовательно, одну за другой. У него всего 4–16 ядер (помощников).
  • GPU (Графический процессор) — это армия из 10 000 школьников. Каждый из них знает только таблицу умножения, но они могут решать 10 000 простых примеров одновременно.

Когда мы обрабатываем картинку размером 1024×10241024 \times 1024 пикселей, нам нужно одновременно умножить более миллиона чисел. Профессор (CPU) будет делать это долго. Армия школьников (GPU) справится за доли секунды. В Colab Google бесплатно предоставляет вам доступ к мощным GPU, аренда которых обычно стоит немалых денег.

Характеристика Локальный компьютер (без мощной GPU) Google Colab
Установка Сложная настройка, конфликты версий Не требуется, всё готово в браузере
Мощность Ограничена вашим "железом" Доступ к серверным процессорам и GPU
Доступность Привязка к одному рабочему месту Доступ с любого устройства через интернет
Совместная работа Нужно пересылать файлы Можно поделиться ссылкой, как в Google Docs

Запускаем первый код

Давайте посмотрим, как выглядит работа в Colab на практике. Наша задача — использовать уже обученную кем-то модель для анализа тональности текста (определить, позитивный отзыв или негативный).

Вам пока не нужно понимать синтаксис языка Python. Обратите внимание на сам процесс: мы берем готовую модель из библиотеки и просим ее сделать предсказание (инференс).

В ячейке с кодом мы пишем:

# Загружаем готовую библиотеку для работы с ИИ
from transformers import pipeline

# Скачиваем обученную модель для анализа тональности
анализатор = pipeline("sentiment-analysis")

# Даем модели новые данные
результат = анализатор("I absolutely love this new feature! It saves me so much time.")

# Выводим результат на экран
print(результат)

Мы нажимаем кнопку Play. Что происходит в этот момент?

  1. Сервер Google читает первую строку и подключает нужные инструменты.
  2. Он скачивает в свою оперативную память готовую модель (веса и правила, которые она усвоила на этапе обучения).
  3. Модель анализирует наш текст.
  4. Под ячейкой мгновенно появляется результат:

[{'label': 'POSITIVE', 'score': 0.998}]

Модель с уверенностью (confidence) 99.8%99.8\% определила, что отзыв позитивный. Мы только что решили реальную бизнес-задачу: автоматизировали чтение отзыва. Если бы у нас была таблица с тысячами таких текстов, мы могли бы прогнать их через этот анализатор за пару минут.

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

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

Тонкая настройка готовой текстовой модели через No-Code платформы

Тонкая настройка готовой текстовой модели через No-Code платформы

Представьте, что вы руководите IT-отделом в компании, разрабатывающей сложное программное обеспечение. Клиент присылает письмо: «Эндпоинт биллинга отдаёт ошибку 502 при попытке синхронизации с легаси-базой». Если вы передадите этот текст стандартной языковой модели и попросите определить категорию обращения, она, скорее всего, выдаст что-то вроде «Проблема с сайтом» или «Негативный отзыв». Но для вашего бизнеса это совершенно бесполезно. Вам нужно, чтобы система автоматически направила этот тикет в отдел «Backend DevOps».

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

От студента к специалисту: концепция Fine-tuning

Создание современной текстовой модели с нуля требует огромных вычислительных кластеров и миллионов долларов. Алгоритм читает терабайты текста из интернета, чтобы просто выучить язык: понять грамматику, логику, факты о мире и научиться связно отвечать на вопросы. Результат этого процесса называется базовой моделью (Base Model).

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

Эта «стажировка» для искусственного интеллекта называется дообучением или тонкой настройкой (Fine-tuning).

Математически процесс можно описать простой концепцией:

Wnew=Wbase+ΔWW_{new} = W_{base} + \Delta W

Где WbaseW_{base} — это миллиарды параметров (знаний) базовой модели, а ΔW\Delta W — это небольшие, точечные изменения, которые модель получает, изучая ваши специфические примеры. Например, WbaseW_{base} обеспечивает общее понимание русского языка, а ΔW\Delta W добавляет знание того, что термин «эндпоинт» в вашей компании относится к отделу «Backend DevOps». Итоговая модель WnewW_{new} сохраняет весь интеллект базовой, но теперь её ответы смещены в сторону вашей конкретной задачи.

Данные: как показать модели, чего мы хотим

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

Вместо папок с картинками, текст обычно собирают в структурированные файлы, где каждая строка — это независимый пример идеального взаимодействия. Самый популярный формат для этого — JSONL (JSON Lines). В нем каждый пример содержит то, что модель получит на вход (Prompt), и то, что она должна выдать на выходе (Completion).

Пример структуры данных для классификации тикетов:

{"prompt": "Текст: Не могу зайти в аккаунт, пишет неверный пароль. Категория:", "completion": " Доступ и авторизация"}
{"prompt": "Текст: Эндпоинт биллинга отдаёт ошибку 502. Категория:", "completion": " Backend DevOps"}

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

No-Code платформы: обучение без кода

Чтобы провести тонкую настройку, больше не нужно разворачивать сложные среды программирования. Существуют No-Code платформы (например, интерфейсы OpenAI, Hugging Face AutoTrain и другие), которые берут всю математику и настройку видеокарт (GPU) на себя.

Процесс выглядит как работа с обычным веб-сервисом:

  1. Загрузка датасета: Вы загружаете ваш файл с примерами (CSV или JSONL) через браузер. Платформа проверяет его на ошибки форматирования.
  2. Выбор базовой модели: Вы указываете, какую «заготовку» хотите обучить (например, легкую и быструю модель или более тяжелую и умную).
  3. Настройка гиперпараметров: Вы задаете количество эпох — сколько раз алгоритм должен полностью перечитать ваш файл с примерами. Обычно для текста достаточно от 3 до 5 эпох.
  4. Запуск: Платформа выделяет серверные мощности, проводит вычисления и через 15–30 минут выдает вам готовую, персонализированную модель.

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

Промпт или Дообучение?

Управление поведением модели с помощью текстовых инструкций (промптов) и тонкая настройка — это два разных инструмента. Важно понимать, когда использовать каждый из них.

Критерий Промптинг (Инструкции в тексте) Тонкая настройка (Fine-tuning)
Суть Подробное описание правил в запросе: «Ты оператор. Если видишь слово X, пиши Y...» Показ сотен примеров без длинных объяснений правил.
Сложность задачи Отлично для простых задач, где правила легко описать словами. Идеально для задач, где правила описать сложно, но легко показать примеры (интонация, сложный сленг).
Скорость внедрения Мгновенно. Изменил текст — изменился результат. Требует времени на сбор данных и запуск процесса обучения.
Затраты при работе Высокие. Приходится каждый раз отправлять модели огромную инструкцию вместе с текстом. Низкие. Модель уже «впитала» правила, достаточно отправить только сам текст тикета.

Тонкая настройка блестяще справляется с задачей форматирования и стиля. Если вам нужно, чтобы ИИ всегда отвечал строгим JSON-кодом для интеграции с другой программой, или чтобы он писал тексты в специфическом, сухом юридическом стиле вашей корпорации — дообучение на примерах сработает гораздо надежнее, чем уговоры в промпте.

За пределы песочницы

No-Code платформы позволяют быстро проверить гипотезу. Вы можете вручную собрать 100 примеров в Excel, сохранить их в нужном формате, загрузить на сайт и получить рабочую модель, которая идеально сортирует технические письма.

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

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

Основы Python для анализа данных за один вечер

Основы Python для анализа данных за один вечер

Представьте, что перед вами выгрузка из CRM-системы: 10 000 обращений клиентов в техподдержку. Чтобы обучить текстовую модель классифицировать эти тикеты, их нужно перевести в формат JSONL, где каждая строка — это пара из запроса и идеального ответа. Если на форматирование одного примера вручную уходит 3 минуты, то для датасета из 10 000 строк общее время составит 500 часов непрерывного копирования и вставки.

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

Переменные и базовые типы данных

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

Данные бывают разных типов, и от типа зависит, какие операции с ними можно проводить. Четыре фундаментальных типа:

  1. Строки (str) — любой текст, заключенный в кавычки.
  2. Целые числа (int) — числа без дробной части.
  3. Вещественные числа (float) — числа с плавающей точкой (дробные).
  4. Логический тип (bool) — принимает только два значения: True (истина) или False (ложь).
# Примеры базовых типов данных
ticket_text = "Ошибка 502 при загрузке страницы"  # str
user_age = 28                                     # int
confidence_score = 0.95                           # float
is_resolved = False                               # bool

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

Структуры данных: списки и словари

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

Структура Описание Синтаксис Пример использования
Список (List) Упорядоченная последовательность элементов. Элементы можно добавлять, удалять и получать по их порядковому номеру (индексу). Квадратные скобки [] Хранение всех текстов обращений за день: ['баг', 'вопрос', 'жалоба']
Словарь (Dict) Набор пар «ключ-значение». Данные извлекаются не по номеру, а по уникальному имени (ключу). Фигурные скобки {} Хранение всех параметров одного тикета: {'id': 104, 'status': 'open'}

Словарь в Python концептуально идентичен объекту JSON. Когда мы готовим данные для тонкой настройки (Fine-tuning) языковой модели, мы фактически создаем списки, состоящие из словарей.

# Список из двух словарей (мини-датасет)
raw_dataset = [
    {"prompt": "Не могу войти в аккаунт", "category": "auth"},
    {"prompt": "Как поменять аватарку?", "category": "settings"}
]

# Получение категории первого элемента (индексация начинается с нуля)
first_category = raw_dataset[0]["category"]

Управление потоком: условия и циклы

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

Условные операторы (if/else) позволяют выполнять разный код в зависимости от ситуации. Например, если вероятность предсказания модели p0.9p \geq 0.9, мы можем автоматически закрыть тикет, а если p<0.9p < 0.9 — отправить его человеку.

Циклы (for) заставляют программу проходить по каждому элементу в списке и выполнять с ним заданные действия. Это главный инструмент для массовой обработки данных.

# Обработка списка тикетов
for ticket in raw_dataset:
    if ticket["category"] == "auth":
        print("Отправить в отдел безопасности")
    else:
        print("Отправить в общую поддержку")

В этом примере цикл берет первый словарь из списка raw_dataset, помещает его в переменную ticket, проверяет условие if, выполняет печать, а затем автоматически переходит ко второму элементу.

Функции: упаковка логики

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

# Определение функции
def format_for_finetuning(user_text, bot_answer):
    # Создаем словарь нужной структуры
    formatted_data = {
        "prompt": user_text,
        "completion": bot_answer
    }
    return formatted_data

# Использование функции
result = format_for_finetuning("Где кнопка оплаты?", "В правом верхнем углу.")

Кульминация: автоматизируем сборку JSONL

Теперь объединим переменные, словари, списки, циклы и условия, чтобы решить задачу из начала статьи. У нас есть «сырые» данные из базы, и нам нужно сгенерировать структуру для обучения модели, отбросив пустые обращения.

import json

# 1. Сырые данные (имитация выгрузки из базы)
raw_tickets = [
    {"user_msg": "Сайт тормозит", "admin_reply": "Проверяем сервер."},
    {"user_msg": "", "admin_reply": "Уточните проблему."}, # Пустой запрос
    {"user_msg": "Спасибо, помогло!", "admin_reply": "Рады помочь!"}
]

# 2. Пустой список для готовых данных
training_data = []

# 3. Цикл обработки
for row in raw_tickets:
    # 4. Условие: отсекаем пустые сообщения
    if row["user_msg"] != "":
        # Формируем правильный словарь
        valid_pair = {
            "prompt": row["user_msg"],
            "completion": row["admin_reply"]
        }
        # Добавляем в итоговый список
        training_data.append(valid_pair)

# 5. Вывод результата (в реальности здесь было бы сохранение в файл)
for item in training_data:
    print(json.dumps(item, ensure_ascii=False))

Результат выполнения этого кода — идеально отформатированные строки JSONL: {"prompt": "Сайт тормозит", "completion": "Проверяем сервер."} {"prompt": "Спасибо, помогло!", "completion": "Рады помочь!"}

Мы написали скрипт, который за доли секунды делает то, на что ушли бы сотни часов ручного труда. Базовый Python дает полный контроль над тем, как информация передается алгоритмам. Однако в реальных задачах данные редко приходят в виде аккуратных списков — обычно это громоздкие таблицы Excel или CSV с пропущенными значениями и ошибками. Для работы с такими массивами встроенных инструментов Python уже недостаточно, и в дело вступают специализированные библиотеки аналитики.

Работа с таблицами: библиотеки Pandas и NumPy

Работа с таблицами: библиотеки Pandas и NumPy

Представьте, что вы выгрузили из корпоративной CRM-системы историю обращений клиентов за последние пять лет. Это CSV-файл на 3 миллиона строк. Если вы попытаетесь открыть его в обычном Excel, программа не сможет загрузить его целиком — лимит листа составляет 1 048 576 строк, и большая часть данных просто обрежется. Если вы напишете скрипт на чистом Python и используете цикл for, чтобы проверить каждую строку, обработка займет долгие минуты. В мире машинного обучения датасеты такого размера — это самый минимум, а время исследователя стоит дорого.

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

NumPy: математический двигатель под капотом

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

Библиотека NumPy (Numerical Python) решает эту проблему. Она написана на быстром языке C и вводит концепцию массивов (arrays). Главная суперсила NumPy — векторизация. Это способность применять одну математическую операцию сразу ко всему массиву данных без использования медленных циклов Python.

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

Рассмотрим простую задачу: у нас есть массив цен, и мы хотим применить к ним налог, вычислив итоговую стоимость по формуле:

Ptotal=Pbase×1.2P_{total} = P_{base} \times 1.2

Где PtotalP_{total} — итоговая цена с налогом, PbaseP_{base} — базовая цена товара, а 1.21.2 — множитель, добавляющий 20% налога.

Вот как это выглядит в коде:

import numpy as np

# Создаем массив NumPy из обычного списка
prices = np.array([100, 250, 300, 150, 500])

# Векторизованная операция: умножаем ВЕСЬ массив сразу
total_prices = prices * 1.2

print(total_prices)
# Результат: [120. 300. 360. 180. 600.]

На массиве из пяти чисел разница в скорости незаметна. Но если в массиве будет 10 миллионов цен, NumPy выполнит эту операцию в десятки раз быстрее, чем стандартный цикл Python. NumPy работает с числами идеально, но реальные бизнес-данные — это не только числа. Это тексты, даты, имена и статусы. Здесь на сцену выходит Pandas.

Pandas: табличный процессор для ИИ

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

В Pandas есть две главные сущности:

  1. Series — одномерный массив, по сути, одна колонка в таблице.
  2. DataFrame — двумерная таблица, состоящая из строк и колонок (набор объектов Series).

Сравним концепции в таблице:

Инструмент Структура Тип данных Аналог из жизни
Python List Список Любые, вперемешку Корзина с покупками
NumPy Array Массив Строго один тип (обычно числа) Математический вектор
Pandas DataFrame Таблица Разные типы в разных колонках Лист Microsoft Excel

Загрузка и осмотр данных

Чаще всего данные для обучения ИИ хранятся в формате CSV (Comma-Separated Values) — текстовом файле, где колонки разделены запятыми. Pandas позволяет превратить такой файл в DataFrame одной строкой кода.

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

import pandas as pd

# В реальности мы бы написали: df = pd.read_csv('tickets.csv')
# Для примера создадим DataFrame вручную из словаря:
data = {
    'Ticket_ID': [101, 102, 103, 104],
    'Category': ['Billing', 'Tech', 'Tech', 'Billing'],
    'User_Text': ['Where is my refund?', 'App crashes on startup', 'Blank screen', 'Double charged'],
    'Response_Time_Hours': [2.5, 12.0, None, 1.0]
}

df = pd.DataFrame(data)

Чтобы не выводить на экран миллион строк, у DataFrame есть метод head(), который показывает только первые несколько записей:

print(df.head(2))

Результат будет выглядеть как аккуратная таблица:

Ticket_ID Category User_Text Response_Time_Hours
101 Billing Where is my refund? 2.5
102 Tech App crashes on startup 12.0

Фильтрация данных

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

# Создаем новый DataFrame, оставляя только строки, где категория равна 'Tech'
tech_tickets = df[df['Category'] == 'Tech']

Грязные данные: знакомство с NaN

В идеальном мире датасеты полны и структурированы. В реальности, если вы посмотрите на колонку Response_Time_Hours в нашем примере, у тикета 103 значение отсутствует.

В Pandas пустые ячейки обозначаются специальным маркером NaN (Not a Number). Алгоритмы машинного обучения не умеют работать с пустотой — если вы передадите NaN в математическую модель, она выдаст ошибку. Подготовка данных всегда включает обработку таких пропусков.

У нас есть два базовых пути:

  1. Удалить строки с пропусками (метод dropna()). Подходит, если пустых строк мало (например, 1% от датасета) и их потеря не исказит общую картину.
  2. Заполнить пропуски (метод fillna()). Подходит, если данных жалко. Заполнять можно нулями, средним значением по колонке или специальным словом (например, «Unknown»).
# Вариант 1: Удаляем строку с пустым временем ответа
clean_df = df.dropna()

# Вариант 2: Заполняем пустоту средним временем ответа (векторизованная операция!)
average_time = df['Response_Time_Hours'].mean()
df['Response_Time_Hours'] = df['Response_Time_Hours'].fillna(average_time)

Объединяем всё в единый конвейер

Теперь мы можем связать полученные знания в единый процесс. Мы берем сырые данные, используем Pandas для их фильтрации, обрабатываем пропуски и применяем векторизованные операции NumPy (которые работают внутри Pandas) для создания новых признаков.

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

# 1. Заполняем пропуски во времени ответа средним значением
df['Response_Time_Hours'] = df['Response_Time_Hours'].fillna(df['Response_Time_Hours'].mean())

# 2. Создаем новую колонку с помощью векторизованной операции
# Умножаем всю колонку сразу, без циклов for
df['Penalty_Score'] = df['Response_Time_Hours'] * 10

# 3. Отфильтруем только те тикеты, где штраф больше 50
problem_tickets = df[df['Penalty_Score'] > 50]

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

Инструменты готовы. У нас есть среда (Colab), язык (Python) и механизмы для работы с массивами информации (Pandas и NumPy). Но алгоритму мало просто загрузить таблицу. Ему нужны правильные, размеченные примеры. В следующем шаге мы разберем, откуда брать данные для обучения и как правильно ставить им «оценки», чтобы модель поняла, чего мы от неё хотим.

Сбор и разметка данных для обучения

Сбор и разметка данных для обучения

В индустрии искусственного интеллекта есть негласное правило: алгоритмы дают лишь 20% успеха, остальные 80% зависят от данных. Вы можете арендовать самые мощные серверы и написать идеальный код на Python, но если вы загрузите в модель мусор, на выходе вы получите мусор. Этот принцип так и называется — Garbage In, Garbage Out (GIGO).

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

Откуда берутся данные

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

  1. Внутренние системы бизнеса (Исторические данные) Это самый ценный актив. CRM-системы, логи серверов, история транзакций, записи звонков в техподдержку. Эти данные уникальны и отражают реальные бизнес-процессы. Если цель — предсказать отток клиентов вашего сервиса, никакие данные из интернета не заменят историю поведения именно ваших пользователей.
  2. Открытые датасеты (Open Source) Платформы вроде Kaggle, Hugging Face Datasets или Google Dataset Search хранят миллионы готовых наборов данных. Они идеально подходят для старта, проверки гипотез или обучения базовых моделей. Например, чтобы научить ИИ распознавать лица, не нужно фотографировать тысячи людей — можно взять готовый открытый датасет.
  3. Сбор извне (Парсинг и API) Если внутренних данных нет, а открытые не подходят, информацию собирают с помощью скриптов. Это может быть автоматическое скачивание цен конкурентов с их сайтов или сбор публичных постов из социальных сетей через API для анализа трендов.

Анатомия датасета: Признаки и Целевая переменная

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

В терминах машинного обучения эти вводные данные называются признаками (Features). В математических формулах и коде матрицу признаков традиционно обозначают заглавной латинской буквой XX.

Однако для обучения модели одних признаков недостаточно. Алгоритму нужно знать, к какому результату привели эти признаки. Была ли транзакция мошеннической? Колонка с правильным ответом, который модель должна научиться предсказывать, называется целевой переменной (Target). Её принято обозначать строчной буквой yy.

Процесс создания колонки yy, то есть присвоения правильных ответов сырым данным XX, называется разметкой данных. Без разметки алгоритм видит просто набор цифр и текста. Разметка превращает сырую информацию в учебник, по которому ИИ будет калибровать свои внутренние веса.

Кто ставит «оценки»: Подходы к разметке

Если целевая переменная не формируется автоматически (как, например, факт возврата кредита — клиент либо вернул деньги, либо нет), ответы приходится создавать искусственно. Существует несколько подходов к этому процессу.

Метод Суть Плюсы Минусы
Внутренние эксперты Штатные специалисты (врачи, юристы, инженеры) вручную просматривают данные и ставят метки. Максимальная точность. Незаменимо для сложных предметных областей (например, разметка рентгеновских снимков). Очень дорого и медленно. Экспертов мало, их время стоит дорого.
Краудсорсинг Задача дробится на микро-задания и раздается тысячам людей в интернете через специальные платформы (например, Яндекс Задания, Toloka). Дешево и невероятно быстро. Можно разметить сотни тысяч картинок за пару дней. Низкое качество. Исполнители часто ошибаются или хитрят. Требуется сложная система перекрытия (одну задачу дают 3-5 разным людям).
AI-разметка Использование огромной, дорогой модели (например, GPT-4) для разметки данных, на которых затем будет учиться маленькая, быстрая и дешевая локальная модель. Идеальный баланс скорости и стоимости. Модель-ученик может унаследовать галлюцинации и ошибки модели-учителя.

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

Проблема субъективности и инструкции

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

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

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

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

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

Очистка данных и борьба с пропусками

Очистка данных и борьба с пропусками

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

Алгоритмы машинного обучения буквально понимают то, что им дают. Они не обладают человеческим здравым смыслом, чтобы догадаться, что «10.05.2023» и «10 мая 23» — это одно и то же, а пустая ячейка в графе «Жалобы» означает их отсутствие, а не ошибку системы. Считается, что специалисты по ИИ целыми днями проектируют сложные нейросети, но на практике львиная доля их времени уходит на очистку данных.

Анатомия пропусков и стратегия импутации

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

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

Масштаб проблемы Стратегия Риски
Пропусков менее 5% в строках Удаление строк с пропусками Минимальные, если пропуски распределены случайно.
Пропусков более 70% в колонке Удаление всего столбца Можно потерять потенциально полезный признак.
Отсутствие данных несет смысл Заполнение константой (например, 0 или Нет) Искажение распределения, если логика выбрана неверно.
Пропусков 10–20% в числовой колонке Импутация статистикой (среднее или медиана) Снижение разнообразия данных (много одинаковых значений).

Среднее против Медианы (и проблема выбросов)

Когда мы решаем заполнить пропуски в числовой колонке (например, возраст или зарплата), первая мысль — посчитать среднее арифметическое и вставить его во все пустые ячейки.

Формула среднего арифметического выглядит так:

μ=xin\mu = \frac{\sum x_i}{n}

Здесь μ\mu — искомое среднее значение, знак \sum требует сложить все имеющиеся значения xix_i, а nn — это общее количество этих значений.

Допустим, у нас есть зарплаты четырех рядовых сотрудников: 50, 60, 65 и 70 тысяч рублей. μ=50+60+65+704=61.25\mu = \frac{50 + 60 + 65 + 70}{4} = 61.25 тысяч рублей.

Это отлично работает, пока в данные не закрадывается выброс (outlier) — аномальное значение, которое радикально отличается от общей массы наблюдений.

Представьте, что в наш отдел добавили зарплату генерального директора (CEO) — 1000 тысяч рублей. Считаем новое среднее: μ=50+60+65+70+10005=249\mu = \frac{50 + 60 + 65 + 70 + 1000}{5} = 249 тысяч рублей.

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

В таких случаях на помощь приходит медиана. Это число, которое находится ровно посередине, если отсортировать все значения выборки по возрастанию. Выстроим наши пять зарплат по порядку: 50, 60, 65, 70, 1000. Медиана равна 65. Она полностью проигнорировала аномальный миллион CEO и показала реальную картину типичного дохода в отделе.

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

Что делать с самими выбросами?

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

  1. Ошибки ввода или сбои: кто-то случайно добавил лишний ноль при вводе площади квартиры, или датчик температуры на секунду выдал +500 градусов.
  2. Естественные аномалии: мошенническая банковская транзакция на огромную сумму или покупка элитного пентхауса на фоне обычных квартир.

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

Дубликаты и рассинхронизация форматов

Помимо пропусков и выбросов, сырые данные часто страдают от «грязи» в форматировании.

Для компьютера строки Москва, москва (с невидимым пробелом в конце) и Мск — это три абсолютно разных значения. Если не привести их к единому виду (например, перевести все буквы в нижний регистр и удалить лишние пробелы), модель решит, что это три разных фактора. Она разделит свой «опыт» между ними, вместо того чтобы объединить знания о столице воедино.

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

Единый конвейер очистки (Пайплайн)

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

  1. Удаление дубликатов: Сначала избавляемся от полностью идентичных строк, чтобы они не искажали статистику на следующих шагах.
  2. Стандартизация форматов: Приводим текстовые данные к единому виду. Заменяем в колонке «Наличие балкона» разнобой из «Да/Нет», «Есть/Нету», «1/0» на строгие числа 1 и 0.
  3. Обработка выбросов: Находим в данных квартиру с площадью 9000 кв.м. Понимаем, что это ошибка ввода, и либо удаляем строку, либо (если уверены) исправляем на 90.
  4. Удаление безнадежных пропусков: Если целевая переменная (цена квартиры) неизвестна — смело удаляем строку. Мы не можем учить модель на примерах без правильного ответа.
  5. Импутация оставшихся пропусков: В колонке «Год постройки» не хватает 15% значений. Так как в данных есть исторические здания XVIII века (выбросы), заполняем пустые ячейки медианным годом постройки.

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

Разделение данных на обучающую, валидационную и тестовую выборки

Разделение данных на обучающую, валидационную и тестовую выборки

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

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

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

Базовое разделение: Обучение и Тест

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

  1. Обучающая выборка (Train set) — обычно составляет 70–80% всех данных. Это учебники и классная работа. Модель изучает эти данные, ищет в них скрытые правила и настраивает свои внутренние параметры.
  2. Тестовая выборка (Test set) — оставшиеся 20–30%. Это финальный государственный экзамен. Эти данные прячутся в «сейф» до самого конца проекта. Модель не видит их в процессе обучения.

Когда обучение закончено, мы достаем тестовую выборку, показываем модели только признаки (вводные данные) и просим сделать предсказания. Затем сравниваем предсказания модели с реальными ответами, скрытыми в тестовой выборке.

Разница между точностью на обучающей и тестовой выборках — главный индикатор здоровья ИИ. Если модель предсказывает данные из обучения с точностью 98%, а на тестовых данных выдает лишь 60% — произошло переобучение (overfitting). Модель просто выучила данные наизусть.

Зачем нужна третья выборка?

Кажется, что схемы 80/20 вполне достаточно. Но на практике процесс создания ИИ никогда не бывает линейным.

Допустим, мы обучили модель предсказывать отток клиентов банка. Проверили на тестовой выборке — точность 75%. Нас это не устраивает. Мы меняем настройки алгоритма, добавляем новые признаки, обучаем заново. Снова проверяем на тестовой выборке — 78%. Снова меняем настройки — 82%.

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

Чтобы разорвать этот порочный круг, датасет делят на три части. Стандартная пропорция: 70% / 15% / 15% (или 80/10/10 для очень больших массивов данных).

Тип выборки Доля данных Аналогия из жизни Назначение в машинном обучении
Обучающая (Train) 70% Классная и домашняя работа Алгоритм напрямую учится на этих данных, находя базовые закономерности.
Валидационная (Validation) 15% Пробные экзамены (можно пересдавать) Используется разработчиком для сравнения разных версий модели, подбора настроек и исправления ошибок до финала.
Тестовая (Test) 15% Финальный экзамен (одна попытка) Используется строго один раз в самом конце. Показывает реальную эффективность модели для бизнеса.

Как правильно делить данные на практике

В Python для разделения таблиц чаще всего используется функция train_test_split из библиотеки scikit-learn. Она автоматически перемешивает строки и разбивает уже подготовленные матрицу признаков (XX) и вектор правильных ответов (yy) на обучающую и тестовую части.

from sklearn.model_selection import train_test_split

# 1. Сначала отделяем 15% на финальный тест
X_temp, X_test, y_temp, y_test = train_test_split(X, y, test_size=0.15, random_state=42)

# 2. Оставшиеся 85% (temp) делим на обучение и валидацию
# Чтобы получить 15% от исходных 100%, берем ~17.6% от оставшихся 85%
X_train, X_val, y_train, y_val = train_test_split(X_temp, y_temp, test_size=0.176, random_state=42)

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

Смертельный грех дата-саентиста: Утечка данных

Разделение данных кажется простой технической задачей, но именно здесь новички совершают ошибку, которая обходится бизнесу в миллионы. Эта ошибка называется утечкой данных (Data Leakage) — ситуацией, когда информация из тестовой выборки или из будущего нелегально просачивается в обучающую выборку.

Рассмотрим классический пример утечки при работе со временем.

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

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

Например:

  • Обучающая выборка: Январь — Сентябрь
  • Валидационная выборка: Октябрь
  • Тестовая выборка: Ноябрь — Декабрь

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

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

Линейная регрессия: предсказываем числовые значения

Линейная регрессия: предсказываем числовые значения

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

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

Регрессия против классификации

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

Характеристика Классификация Регрессия
Главный вопрос «К какой категории это относится?» «Сколько?»
Целевая переменная (yy) Дискретная (ярлыки, классы) Непрерывная (любое число)
Примеры задач Болен/Здоров, Спам/Не спам Цена квартиры, Температура, Возраст
Результат модели Вероятность принадлежности к классу Конкретное числовое значение

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

Анатомия прямой линии

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

Линейная регрессия пытается описать эту тенденцию знакомым со школы уравнением прямой:

y=wx+by = wx + b

Давайте расшифруем каждый элемент этой формулы максимально просто:

  • yy — это то, что мы хотим предсказать (целевая переменная, например, выручка).
  • xx — это наш признак (вводные данные, например, площадь магазина).
  • ww (от английского weight) — это вес признака. Он показывает угол наклона линии. Говоря бизнес-языком: сколько рублей выручки приносит каждый дополнительный квадратный метр площади.
  • bb (от английского bias) — это смещение. Точка, где линия пересекает вертикальную ось. Физический смысл: базовая выручка, которую магазин получил бы даже при площади, равной нулю (например, за счет онлайн-заказов с самовывозом).

Практический пример: Допустим, модель обучилась и подобрала параметры: w=500,b=10000w = 500, b = 10000. Уравнение приняло вид: Выручка = 500 × Площадь + 10000. Если вы открываете новый магазин площадью x=100x = 100 кв.м, модель предскажет выручку: 500 × 100 + 10000 = 60 000.

Как машина «учится»: поиск идеальных параметров

В традиционном программировании вы бы сами задали значения для ww и bb. В машинном обучении алгоритм находит их сам. Как? Во многих моделях ИИ это происходит через метод постепенного приближения (градиентный спуск):

  1. Модель берет случайные числа для веса и смещения.
  2. Делает предсказания для всех магазинов из обучающей выборки.
  3. Сравнивает свои предсказания с реальной выручкой из датасета.
  4. Немного корректирует параметры, чтобы в следующий раз ошибиться меньше.

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

Чтобы алгоритм понимал, насколько сильно он ошибается, используется функция потерь. Для линейной регрессии стандартом является Среднеквадратичная ошибка (Mean Squared Error, или MSE):

MSE=1n(yy^)2MSE = \frac{1}{n} \sum (y - \hat{y})^2

Разберем элементы формулы:

  • nn — количество примеров в датасете (сколько у нас магазинов).
  • yy — реальное значение из данных (фактическая выручка).
  • y^\hat{y} — предсказание нашей модели.
  • Символ \sum означает, что мы суммируем ошибки по всем магазинам.

Почему мы возводим ошибку в квадрат? Во-первых, чтобы отрицательные ошибки (модель предсказала на 5000 меньше) не компенсировали положительные (модель предсказала на 5000 больше). Во-вторых, квадрат жестко «штрафует» модель за сильные промахи: ошибка в 10 рублей даст штраф 100, а ошибка в 100 рублей даст огромный штраф 10 000. Цель обучения — найти такие ww и bb, при которых значение MSE будет минимальным.

Множественная линейная регрессия

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

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

y=w1x1+w2x2++wnxn+by = w_1x_1 + w_2x_2 + \dots + w_nx_n + b

У каждого признака (x1,x2x_1, x_2 и так далее) появляется свой собственный вес (w1,w2w_1, w_2). Если признак сильно влияет на результат (например, проходимость улицы), модель в процессе обучения присвоит ему большой вес. Если признак оказался бесполезным (например, цвет стен в подсобке), алгоритм математически «выключит» его, присвоив вес, близкий к нулю.

Практический пример: Выручка = 500 × Площадь + 2000 × Трафик_у_метро − 1500 × Количество_конкурентов_рядом + 10000. Обратите внимание на отрицательный вес (1500-1500): алгоритм понял, что каждый новый конкурент поблизости математически уменьшает итоговое предсказание выручки.

Собираем всё вместе: код

В прошлой главе мы научились делить очищенные данные на обучающую (X_train, y_train) и тестовую (X_test, y_test) выборки. Благодаря библиотеке scikit-learn в Python, вся сложная математика по поиску минимального MSE скрыта под капотом.

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

from sklearn.linear_model import LinearRegression

# 1. Создаем "пустую" модель
model = LinearRegression()

# 2. Запускаем процесс обучения (поиск идеальных весов на обучающей выборке)
model.fit(X_train, y_train)

# 3. Делаем предсказания на новых данных (тестовой выборке)
predictions = model.predict(X_test)

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

Линейная регрессия — отличный базовый инструмент. Она работает быстро и легко интерпретируется (вы всегда можете посмотреть на веса и сказать бизнесу: «каждый метр до метро снижает выручку на 10 рублей»). Однако она способна улавливать только прямые, линейные зависимости.

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

Логистическая регрессия: решаем задачу бинарной классификации

Логистическая регрессия: решаем задачу бинарной классификации

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

Кликнет ли пользователь по рекламе? Является ли эта транзакция мошеннической? Уволится ли сотрудник в ближайшие полгода?

Когда возможных ответов всего два (обычно их кодируют как 1 и 0), мы сталкиваемся с задачей бинарной классификации. И здесь привычная прямая линия начинает давать сбои.

Почему линейная регрессия не подходит для классификации?

Представьте, что мы предсказываем, купит ли клиент премиум-подписку (класс 1) или нет (класс 0), основываясь на времени, проведенном на сайте.

Если мы применим обычную линейную регрессию, алгоритм построит прямую линию. Для клиента, который провел на сайте 5 минут, формула может выдать значение 0.2. Для клиента с 20 минутами — 0.8. Пока всё выглядит логично.

Но что если лояльный пользователь просидел на сайте 5 часов? Линейное уравнение не ограничено рамками от 0 до 1. Оно может выдать результат 4.5 или даже 150. А если человек только зашел и сразу вышел, результат может стать отрицательным: -1.2.

Как интерпретировать предсказание «клиент купит подписку с вероятностью 450%» или «-120%»? Никак. Нам нужен механизм, который возьмет любой ответ линейного уравнения — от минус бесконечности до плюс бесконечности — и аккуратно сожмет его в понятный диапазон от 0 до 1 (от 0% до 100% вероятности).

Сигмоида: математика превращения чисел в вероятности

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

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

Чтобы сжать zz в диапазон от 0 до 1, логистическая регрессия пропускает его через специальную математическую функцию — сигмоиду.

σ(z)=11+ez\sigma(z) = \frac{1}{1 + e^{-z}}

Разберем элементы этой формулы:

  • zz — сырая сумма признаков с их весами (например, w1×x+bw_1 \times x + b, где xx — время на сайте).
  • ee — число Эйлера, математическая константа, примерно равная 2.71.
  • σ(z)\sigma(z) — итоговая вероятность от 0 до 1.

Как это работает на практике: Если сырой результат zz равен 0, то e0=1e^0 = 1. Формула превращается в 11+1=0.5\frac{1}{1 + 1} = 0.5. Модель сомневается ровно на 50%. Если zz очень большое положительное число (например, 10), то e10e^{-10} стремится к нулю. Формула дает 11+0=1\frac{1}{1 + 0} = 1. Модель на 100% уверена в классе 1. Если zz большое отрицательное число (например, -10), знаменатель становится огромным, и вся дробь стремится к 0.

Сигмоида работает как идеальный фильтр: она плавно переводит любые числа в понятные нам вероятности.

От вероятности к решению: порог отсечения

Получить вероятность 0.83 — это полдела. Бизнесу нужно конкретное действие: звонить клиенту или нет? Блокировать транзакцию или пропустить?

Для принятия окончательного решения вводится порог отсечения (Threshold). По умолчанию он равен 0.5:

  • Если вероятность 0.5\geq 0.5 — предсказываем класс 1.
  • Если вероятность <0.5< 0.5 — предсказываем класс 0.

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

Если вы делаете спам-фильтр для важной рабочей почты, ложное срабатывание (отправка важного письма от босса в спам) обходится очень дорого. Вы можете поднять порог до 0.9. Тогда ИИ отправит письмо в спам, только если уверен на 90%.

Если же вы предсказываете поломку дорогостоящего станка на заводе, лучше перестраховаться. Вы можете снизить порог до 0.2. Пусть инженер лишний раз проверит исправный станок (вероятность поломки 20%), чем завод встанет из-за пропущенной аварии.

Реализация на Python в scikit-learn

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

from sklearn.linear_model import LogisticRegression

# Создаем и обучаем модель на наших данных (X - признаки, y - ответы 0 или 1)
model = LogisticRegression()
model.fit(X_train, y_train)

# Метод 1: Получаем жесткие классы (0 или 1) по порогу 0.5
predictions = model.predict(X_test)
print(predictions)
# Результат: [0, 1, 1, 0, 0]

# Метод 2: Получаем точные вероятности
probabilities = model.predict_proba(X_test)
print(probabilities)
# Результат:
# [[0.85, 0.15],  <- 85% за класс 0, 15% за класс 1
#  [0.10, 0.90],  <- 10% за класс 0, 90% за класс 1
#  ...]

Метод predict_proba (от слова probability — вероятность) особенно полезен, когда вам нужно отсортировать клиентов. Например, у колл-центра есть ресурс сделать только 100 звонков в день. Выдав вероятности покупки для всей базы, вы можете отсортировать список по убыванию и позвонить тем 100 клиентам, чья вероятность ближе всего к единице.

Линейная граница принятия решений

Важно понимать одно фундаментальное ограничение логистической регрессии. Хоть она и предсказывает вероятности, внутри нее всё еще работает линейное уравнение. Это значит, что алгоритм пытается провести прямую линию (или плоскую гиперплоскость, если признаков много), которая отделит класс 1 от класса 0.

Если ваши данные можно разделить прямой линией — например, все, кто провел на сайте больше 10 минут и посмотрел больше 3 страниц, покупают подписку, — логистическая регрессия сработает идеально. Она быстрая, надежная и отлично интерпретируется (мы всегда можем посмотреть на веса ww и понять, какой признак сильнее всего влияет на вероятность).

Но в реальном мире данные редко бывают такими послушными. Что если клиенты покупают подписку в двух случаях: либо они зашли на 2 минуты и сразу купили то, что искали, либо изучали сайт 3 часа? А те, кто провел среднее время — просто читали бесплатные статьи и ушли. Прямой линией такую логику не отделить. Для таких задач нам понадобятся алгоритмы, способные находить сложные, нелинейные зависимости.

Деревья решений и случайный лес (Random Forest)

Деревья решений и случайный лес (Random Forest)

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

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

Дерево решений: алгоритм, играющий в «Да/Нет»

Дерево решений (Decision Tree) работает в точности как человек, пытающийся отгадать предмет, задавая вопросы, на которые можно ответить только «да» или «нет».

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

Структура дерева состоит из трех элементов:

  1. Корень (Root) — исходный датасет со всеми сотрудниками.
  2. Узлы (Nodes) — точки, где задается вопрос (например, «Зарплата <50000< 50 000?»). Данные разделяются на две ветви.
  3. Листья (Leaves) — конечные точки, где алгоритм принимает окончательное решение (присваивает класс 0 или 1).

Как дерево выбирает вопросы? Математика чистоты

Алгоритму нужно выбрать, какой признак проверять первым и какое числовое значение использовать для порога (например, почему именно 50 000, а не 40 000?).

Цель алгоритма на каждом шаге — разделить данные так, чтобы в получившихся ветвях оказались объекты преимущественно одного класса. Для оценки качества разделения используется Примесь Джини (Gini Impurity). Это метрика хаоса в узле.

Формула для бинарной классификации:

Gini=1(p02+p12)Gini = 1 - (p_0^2 + p_1^2)

Где p0p_0 — доля объектов нулевого класса в узле, а p1p_1 — доля объектов первого класса.

Пример из практики: Допустим, в узле оказалось 100 сотрудников.

  • Если 50 уволились и 50 остались, хаос максимален. p0=0.5p_0 = 0.5, p1=0.5p_1 = 0.5. Считаем: Gini=1(0.52+0.52)=1(0.25+0.25)=0.5Gini = 1 - (0.5^2 + 0.5^2) = 1 - (0.25 + 0.25) = 0.5. Это худший вариант.
  • Если в узел попали 100 человек, и все они уволились (идеальная чистота), то p0=0p_0 = 0, p1=1p_1 = 1. Считаем: Gini=1(02+12)=11=0Gini = 1 - (0^2 + 1^2) = 1 - 1 = 0. Узел абсолютно чист, хаоса нет.

При обучении алгоритм перебирает все признаки и все возможные пороги, подсчитывая Примесь Джини. Он выбирает тот вопрос, который сильнее всего снижает общий хаос (Gini стремится к нулю).

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

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

Дерево может создать такое правило: «Если возраст >32.5> 32.5, и зарплата <112400< 112 400, и количество больничных =3= 3, и имя начинается на "А" \rightarrow уволится».

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

Можно ограничить глубину дерева (например, параметром max_depth = 5), но тогда мы рискуем упустить сложные связи. Индустрия нашла более элегантное решение.

Случайный лес (Random Forest): мудрость толпы

Если один эксперт (дерево) часто ошибается или делает слишком поспешные выводы из своего узкого опыта, давайте соберем консилиум из 100 экспертов.

Случайный лес (Random Forest) — это алгоритм, который создает множество независимых деревьев решений, а итоговый ответ определяется голосованием. Этот подход называется ансамблевым обучением (Ensemble Learning).

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

  1. Случайные данные (Bootstrapping): Каждое дерево получает не весь датасет, а случайную выборку из него. Причем объекты выбираются с возвращением: один и тот же сотрудник может попасть к одному дереву трижды, а к другому — ни разу.
  2. Случайные признаки: В каждом узле, когда дерево ищет лучший вопрос для разделения, ему разрешено смотреть не на все колонки таблицы, а только на случайную их часть. Например, одному дереву на старте запретили смотреть на зарплату, и оно вынуждено искать закономерности в должности и возрасте.

Как лес принимает решение (Инференс)

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

  • 80 деревьев сказали: «Уволится» (Класс 1).
  • 20 деревьев сказали: «Останется» (Класс 0).

Лес усредняет результат. Вероятность увольнения составит 80%. Порог отсечения мы можем настроить сами, как делали это ранее.

Характеристика Логистическая регрессия Случайный лес
Тип границ Линейные (прямая линия) Нелинейные (ступенчатые)
Подготовка данных Требует строгой очистки и масштабирования Не боится выбросов и разных масштабов
Интерпретируемость Высокая (понятен вес каждого признака) Низкая (черный ящик из сотен деревьев)
Склонность к переобучению Низкая Средняя (лес компенсирует ошибки одиночных деревьев)

Случайный лес стал золотым стандартом для работы с табличными данными (Excel, базы данных). Он «прощает» отсутствие нормализации (ему всё равно, измеряется зарплата в десятках тысяч, а возраст в десятках — он просто ищет порог) и отлично находит неочевидные комбинации признаков без сложной математической настройки.

Метрики качества моделей: Accuracy, Precision, Recall и F1-score

Метрики качества моделей: Accuracy, Precision, Recall и F1-score

Представьте, что вы создали ИИ для контроля качества на заводе по производству микрочипов. Из 1000 выпущенных чипов 990 идеальны, а 10 имеют скрытый дефект. Вы запускаете свою модель, и она гордо рапортует: «Точность моих предсказаний — 99%!» Руководство в восторге, модель внедряют на конвейер.

Но через месяц завод получает миллионные штрафы от клиентов за бракованные партии. Что пошло не так?

Если заглянуть в код «идеальной» модели, окажется, что она вообще не смотрела на чипы. Она просто всегда выдавала ответ: «Дефектов нет». Поскольку 990 чипов из 1000 действительно были нормальными, алгоритм угадал в 99% случаев. Но те 10 бракованных чипов, ради которых всё затевалось, беспрепятственно уехали к заказчикам.

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

Иллюзия Accuracy и дисбаланс классов

Метрика, которой так гордилась наша бракованная модель, называется Accuracy (доля правильных ответов).

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

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

Такая ситуация называется дисбалансом классов. В задаче с сильным дисбалансом метрика Accuracy становится бесполезной: модель может просто всегда предсказывать самый частый класс (например, «нормальная транзакция») и получать Accuracy выше 99%, полностью проваливая свою реальную бизнес-задачу.

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

Матрица ошибок (Confusion Matrix)

Любое предсказание бинарного классификатора можно отнести к одной из четырёх категорий. Для удобства договоримся, что событие, которое мы ищем (дефект, мошенничество, спам), мы будем называть Положительным классом (Positive), а норму — Отрицательным (Negative).

Давайте вернемся на наш завод. Допустим, мы обучили новую, более умную модель, и прогнали через неё 1000 чипов (из которых 50 — реальный брак, а 950 — норма). Сведём результаты в таблицу, которая называется матрицей ошибок:

Модель сказала: БРАК (Positive) Модель сказала: НОРМА (Negative)
На самом деле: БРАК TP (True Positive). Истинно положительный. Модель нашла дефект. Итог: 40 чипов FN (False Negative). Ложноотрицательный. Модель пропустила брак. Итог: 10 чипов
На самом деле: НОРМА FP (False Positive). Ложноположительный. Ложная тревога. Итог: 20 чипов TN (True Negative). Истинно отрицательный. Модель подтвердила норму. Итог: 930 чипов

Вместо одной цифры Accuracy мы получили четыре независимых сценария:

  • TPTP (40) — наша победа, дефект найден.
  • TNTN (930) — рутинная правильная работа, нормальный чип пропущен дальше.
  • FPFP (20) — ложная тревога. Мы выбросили 20 хороших чипов, потеряв немного денег на производстве.
  • FNFN (10) — катастрофа. 10 бракованных чипов уехали к клиенту, завод получит штраф.

Именно из этих четырёх кирпичиков строятся две самые важные метрики в машинном обучении.

Precision (Точность): цена ложной тревоги

Precision отвечает на вопрос: «Из всех объектов, которые модель назвала положительными, сколько действительно таковыми являются?»

Формула вычисляет долю истинных срабатываний среди всех срабатываний сигнализации:

Precision=TPTP+FPPrecision = \frac{TP}{TP + FP}

В нашем примере: 40/(40+20)0.6740 / (40 + 20) \approx 0.67 (или 67%). Это означает, что когда наша модель кричит «БРАК!», в 33% случаев она ошибается (поднимает ложную тревогу).

Когда важен Precision? Когда цена ложного срабатывания (FP) очень высока. Например, при блокировке банковской карты за подозрительную операцию. Если алгоритм будет блокировать карты слишком часто по ошибке, клиенты взбесятся и уйдут в другой банк. Здесь мы хотим быть абсолютно уверены в вердикте, прежде чем действовать.

Recall (Полнота): цена упущенной выгоды

Recall отвечает на другой вопрос: «Из всех реально положительных объектов в данных, какую долю смогла найти наша модель?»

Формула вычисляет долю найденного брака от всего существующего брака:

Recall=TPTP+FNRecall = \frac{TP}{TP + FN}

В нашем примере: 40/(40+10)=0.8040 / (40 + 10) = 0.80 (или 80%). Это значит, что наша модель отлавливает 80% всего брака, но 20% дефектов всё равно ускользают от её внимания.

Когда важен Recall? Когда цена пропуска (FN) критична. В медицинской диагностике рака лучше отправить 100 здоровых людей на дополнительное обследование (снизить Precision), чем пропустить одного больного пациента (максимизировать Recall). На нашем заводе микрочипов Recall тоже важнее, так как штраф от клиента за пропущенный брак (FN) перевешивает стоимость выброшенного хорошего чипа (FP).

Балансир метрик и порог отсечения

Precision и Recall — это качели. Невозможно одновременно выкрутить обе метрики на 100% в сложных реальных задачах.

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

  1. Снижаем порог до 0.1. Модель становится параноиком. Она бракует чипы при малейшем подозрении. Мы находим абсолютно все дефекты (FN=0FN = 0, Recall = 100%), но при этом выбрасываем половину хороших деталей (FPFP взлетает до небес, Precision падает до 5%).
  2. Повышаем порог до 0.9. Модель становится гипер-осторожной. Она бракует чип, только если уверена наверняка. Ложных тревог больше нет (FP=0FP = 0, Precision = 100%), но мы начинаем пропускать неочевидные дефекты (FNFN растет, Recall падает).

Выбор между Precision и Recall — это всегда бизнес-решение, а не математическая задача.

F1-score: объединяем метрики

Часто бизнесу не нужны крайности. Нам нужна одна цифра, которая покажет, насколько модель хороша в целом, учитывая и ложные тревоги, и пропуски. Для этого используют F1-score — среднее гармоническое между Precision и Recall.

Формула выглядит так:

F1=2PrecisionRecallPrecision+RecallF_1 = 2 \cdot \frac{Precision \cdot Recall}{Precision + Recall}

Почему используется сложное среднее гармоническое, а не обычное среднее арифметическое?

Представьте «параноидальную» модель из примера выше. Её Recall = 1.0 (100%), а Precision = 0.05 (5%). Если мы посчитаем обычное среднее арифметическое: (1.0+0.05)/2=0.525(1.0 + 0.05) / 2 = 0.525. Получается, модель хороша на 52.5%? Нет, она ужасна, потому что выбрасывает половину завода в мусор!

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

F1=20.051.00.05+1.0=20.051.050.095F_1 = 2 \cdot \frac{0.05 \cdot 1.0}{0.05 + 1.0} = 2 \cdot \frac{0.05}{1.05} \approx 0.095

F1-score равен 9.5%. Эта цифра честно отражает, что модель никуда не годится. F1-score будет высоким только в том случае, если и Precision, и Recall показывают хорошие результаты одновременно.

Итоговая шпаргалка

Метрика Что показывает Когда использовать
Accuracy Доля всех правильных ответов Только если классы в данных сбалансированы (50/50).
Precision Доля правды в тревогах модели Когда ложная тревога стоит очень дорого (блокировка пользователей, спам-фильтр).
Recall Доля найденных целевых объектов Когда пропуск события — катастрофа (медицина, дефекты, утечки газа).
F1-score Баланс между Precision и Recall Когда классы не сбалансированы и нам важны обе ошибки (FP и FN).

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

Обучение первой ML-модели на реальном датасете

Обучение первой ML-модели на реальном датасете

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

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

Бизнес-задача: предсказание оттока клиентов

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

У нас есть исторические данные о клиентах (датасет). Посмотрим на фрагмент:

CustomerID ContractType InternetService MonthlyCharges Churn
7590-VHVEG Month-to-month DSL 29.85 0
5575-GNVDE One year DSL 56.95 0
3668-QPYBK Month-to-month Fiber optic 53.85 1
7795-CFOCW One year No 42.30 0
9237-HQITU Month-to-month Fiber optic 70.70 1

Здесь Churn (отток) — это наша целевая переменная (yy). Значение 1 означает, что клиент ушел, 0 — остался. Остальные колонки — это признаки (XX), на основе которых модель будет делать предсказания.

Шаг 1. Перевод текста в числа (One-Hot Encoding)

Модели машинного обучения — это математические уравнения (как в логистической регрессии) или алгоритмы сравнения чисел (как в деревьях решений). Они не понимают слов «Fiber optic» или «Month-to-month». Нам нужно перевести текстовые категории в числа.

Самый популярный метод для этого — One-Hot Encoding (OHE). Идея проста: мы берем колонку с текстом и создаем из нее несколько новых колонок, где стоят только 0 и 1 (Да/Нет).

Например, колонка InternetService содержит три варианта: DSL, Fiber optic, No. После OHE она превратится в три новые колонки:

InternetService_DSL InternetService_Fiber optic InternetService_No
1 0 0
0 1 0
0 0 1

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

import pandas as pd

# Загружаем данные
df = pd.read_csv('telecom_churn.csv')

# Удаляем бесполезный признак
df = df.drop('CustomerID', axis=1)

# Применяем One-Hot Encoding ко всем текстовым колонкам
df_encoded = pd.get_dummies(df)

Инсайт: Часто при кодировании удаляют одну из колонок (передавая параметр drop_first=True), чтобы избежать математической избыточности. Но алгоритмы на основе деревьев (как наш Случайный лес) этого не требуют. Оставив все колонки, нам будет проще интерпретировать важность каждого отдельного признака в дальнейшем.

Шаг 2. Разделение на X и y, и создание выборок

Теперь разделим таблицу на матрицу признаков (XX) и вектор правильных ответов (yy), а затем разобьем их на обучающую и тестовую выборки, чтобы честно проверить знания модели.

from sklearn.model_selection import train_test_split

# Отделяем целевую переменную от признаков
X = df_encoded.drop('Churn', axis=1)
y = df_encoded['Churn']

# Разбиваем данные (80% на обучение, 20% на тест)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

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

Шаг 3. Обучение модели (Случайный лес)

Для этой задачи мы выберем алгоритм Random Forest (Случайный лес). Почему он? Во-первых, он отлично справляется с нелинейными зависимостями (например, когда высокая абонентская плата ведет к оттоку только при отсутствии оптоволокна). Во-вторых, деревья решений не требуют приведения всех чисел к одному масштабу (им неважно, что возраст измеряется десятками, а зарплата — сотнями тысяч).

from sklearn.ensemble import RandomForestClassifier

# Создаем "пустую" модель случайного леса из 100 деревьев
model = RandomForestClassifier(n_estimators=100, random_state=42)

# Обучаем модель на тренировочных данных
model.fit(X_train, y_train)

Метод .fit() — это момент магии. Именно здесь 100 деревьев решений анализируют обучающую выборку, строят узлы, считают примесь Джини и находят оптимальные правила для отделения уходящих клиентов от лояльных.

Шаг 4. Предсказание и оценка качества

Модель обучена. Теперь покажем ей тестовую выборку (XtestX_{test}), которую она никогда не видела, и попросим предсказать, кто из абонентов уйдет. Затем сравним ее предсказания с реальными ответами (ytesty_{test}).

from sklearn.metrics import classification_report

# Делаем предсказания на тестовой выборке
predictions = model.predict(X_test)

# Выводим отчет по метрикам
print(classification_report(y_test, predictions))

Результат работы функции classification_report выглядит так:

              precision    recall  f1-score   support

           0       0.84      0.90      0.87      1036
           1       0.66      0.53      0.59       373

    accuracy                           0.80      1409

Как читать этот отчет бизнесу?

Вспомним проблему дисбаланса классов. Клиентов, которые остаются (класс 0), всегда больше, чем тех, кто уходит (класс 1). В нашем тесте 1036 лояльных и 373 уходящих. Общая accuracy (точность) равна 80%. Звучит отлично, но это иллюзия. Нас интересует только класс 1 (отток).

Смотрим на метрики для класса 1:

  • Recall = 0.53 (53%). Из всех реально уходящих клиентов модель нашла только чуть больше половины. Остальные 47% уйдут незамеченными.
  • Precision = 0.66 (66%). Из всех, на кого модель указала «он уйдет», 66% действительно собирались уйти, а 34% — это ложная тревога.

Бизнес-решение: Если скидка для удержания стоит дешево (например, бесплатный месяц подписки), нам выгоднее охватить как можно больше рискующих клиентов. В этом случае мы можем программно снизить порог отсечения (threshold), чтобы увеличить Recall, пожертвовав Precision. Мы раздадим больше скидок лояльным клиентам, но спасем больше тех, кто реально хотел уйти.

Шаг 5. Важность признаков (Feature Importance)

Одно из главных преимуществ алгоритма Random Forest перед сложными нейросетями — его интерпретируемость. Мы можем заглянуть «под капот» и узнать, на какие именно колонки модель опиралась при принятии решений. Это называется Важность признаков (Feature Importance).

В Scikit-Learn это доступно через атрибут .feature_importances_:

# Получаем важность признаков
importances = model.feature_importances_

# Для наглядности объединяем их с названиями колонок
feature_names = X.columns
forest_importances = pd.Series(importances, index=feature_names)

# Сортируем по убыванию
print(forest_importances.sort_values(ascending=False).head(3))

Вывод:

MonthlyCharges              0.32
ContractType_Month-to-month 0.28
InternetService_Fiber optic 0.15

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

  1. Главный драйвер оттока — размер ежемесячного платежа (MonthlyCharges).
  2. На втором месте — тип контракта. Люди с помесячной оплатой (Month-to-month) уходят катастрофически чаще, чем те, кто заключил годовой контракт.
  3. Наличие оптоволокна (Fiber optic) также сильно влияет на отток (возможно, у конкурентов оно дешевле или стабильнее).

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

Итог классического машинного обучения

Вы только что прошли полный путь Data Scientist'а. Вы взяли сырые данные, перевели их в математический вид (OHE), разделили на выборки, обучили ансамбль деревьев, оценили его работу в бизнес-метриках и извлекли инсайты о поведении клиентов.

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

Как устроен искусственный нейрон и многослойный перцептрон

Как устроен искусственный нейрон и многослойный перцептрон

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

Но что, если ваша рабочая задача — проанализировать скан документа, фотографию бракованной детали или аудиозапись звонка? Даже крошечная черно-белая картинка размером 100×100 пикселей — это 10 000 колонок с числами (яркостью каждого пикселя). Классические алгоритмы захлебнутся в таком объеме разрозненной информации, потому что отдельный пиксель сам по себе не несет смысла. Смысл рождается только в их комбинации.

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

Анатомия искусственного нейрона

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

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

  1. Сбор данных и умножение на веса. Нейрон получает входные данные (признаки). Каждому входу назначен свой вес — число, определяющее важность этого признака.
  2. Сложение и добавление смещения. Нейрон суммирует все взвешенные входы и добавляет смещение (базовую поправку).
  3. Активация. Полученная сумма пропускается через математическую функцию, которая решает, какой сигнал передать дальше.

Математически шаг сложения выглядит так:

z=(x1w1)+(x2w2)+bz = (x_1 \cdot w_1) + (x_2 \cdot w_2) + b

Где:

  • zz — итоговая сумма внутри нейрона.
  • x1,x2x_1, x_2 — входные данные (например, яркость первого и второго пикселя).
  • w1,w2w_1, w_2 — веса, показывающие важность каждого входа.
  • bb — смещение (bias), которое сдвигает результат, помогая нейрону срабатывать раньше или позже.

Практический пример: Представьте нейрон, который решает, выдать ли кредит. На вход подаются два числа: x1x_1 (доход клиента) и x2x_2 (кредитный рейтинг). Нейрон знает, что рейтинг важнее, поэтому вес w2w_2 будет большим (например, 5.0), а вес дохода w1w_1 — поменьше (например, 1.5). Если итоговая сумма zz получается большой, нейрон склоняется к выдаче кредита.

Но сумма zz может быть любым числом: от -1000 до +5000. Чтобы превратить это число в понятный ответ (например, вероятность от 0 до 1), сумма zz передается в функцию активации. В логистической регрессии мы для этого использовали функцию сигмоиды.

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

Ограничения одного нейрона

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

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

Представьте более сложную политику банка: «Мы выдаем кредит молодым специалистам с высоким доходом ИЛИ пенсионерам с низким доходом (у них есть стабильная пенсия и недвижимость). Но мы НЕ выдаем кредит молодым с низким доходом И пенсионерам с высоким доходом (это подозрительно)».

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

Многослойный перцептрон (MLP)

Чтобы решать сложные задачи, нейроны объединяют в сеть. Самая базовая архитектура такой сети называется Многослойный перцептрон (Multilayer Perceptron, MLP).

В MLP нейроны организованы в колонки, которые называются слоями.

  1. Входной слой (Input Layer) — просто принимает сырые данные (например, пиксели картинки) и передает их дальше. Здесь нет вычислений.
  2. Скрытые слои (Hidden Layers) — один или несколько слоев нейронов, расположенных между входом и выходом. Именно здесь происходит магия поиска закономерностей.
  3. Выходной слой (Output Layer) — выдает финальный результат (например, вероятность того, что на фото изображен брак).

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

Как скрытые слои строят абстракции

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

Рассмотрим классическую задачу: распознавание рукописной цифры «8» на черно-белой картинке 28×28 пикселей. На входной слой подаются 784 числа (яркость каждого пикселя). Что происходит дальше?

Слой Что получает на вход Что ищут нейроны слоя
Скрытый слой 1 784 сырых пикселя Простые геометрические элементы: короткие прямые линии, изгибы, углы в разных частях картинки.
Скрытый слой 2 Наличие линий и изгибов от 1-го слоя Комбинации линий: замкнутые круги, пересечения линий крест-накрест.
Выходной слой Наличие кругов и пересечений от 2-го слоя Финальный вывод: «Вижу круг сверху и круг снизу — с вероятностью 95% это цифра 8».

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

Прямое распространение (Forward Propagation)

Процесс, который мы только что описали — когда данные поступают на вход, проходят через скрытые слои и превращаются в предсказание на выходе — называется прямым распространением (Forward Propagation).

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

Но возникает главный вопрос: откуда нейроны в первом скрытом слое знают, какие веса нужно использовать, чтобы распознать именно изгиб линии, а не просто серый фон? Как сеть понимает, как правильно настроить миллионы параметров, чтобы в итоге уверенно отличать цифру «8» от цифры «3»?

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

Функции активации и обратное распространение ошибки

Функции активации и обратное распространение ошибки

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

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

Зачем нужны функции активации: проблема плоского мира

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

Любая последовательность линейных математических операций (умножение и сложение) может быть сведена к одной единственной операции. Если у вас есть сеть из 100 слоев, но в ней нет нелинейности, математически она схлопывается в эквивалент одного слоя. Такая сеть никогда не сможет решить нелинейную задачу — она останется обычной линейной регрессией, просто с очень сложным и долгим вычислением одних и тех же коэффициентов.

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

Главные функции активации

В зависимости от того, где находится нейрон (в скрытых слоях или на самом выходе), используются разные функции.

Функция Суть и формула Где применяется и почему
Sigmoid Сжимает любое число в диапазон от 0 до 1.<br>σ(x)=11+ex\sigma(x) = \frac{1}{1 + e^{-x}}<br><br>Здесь xx — входящий сигнал, а ee — математическая константа (около 2,71). Например, при x=0x = 0, e0=1e^0 = 1, и результат 11+1=0.5\frac{1}{1+1} = 0.5. Одна из первых популярных функций для обучения сетей. Сегодня используется в основном на выходном слое для задач бинарной классификации (выдает вероятность от 0 до 1).
ReLU Пропускает положительные числа без изменений, а отрицательные превращает в ноль.<br>f(x)=max(0,x)f(x) = \max(0, x) Стандарт де-факто для скрытых слоев. Работает невероятно быстро (простое сравнение с нулем) и решает проблему затухания градиента.
Softmax Превращает набор чисел в вероятности, сумма которых всегда равна 1. Используется на выходном слое для многоклассовой классификации.

ReLU (Rectified Linear Unit) — это рубильник. В формуле xx — это сумма сигналов, пришедших в нейрон, а max\max выбирает наибольшее значение. Если сумма сигналов отрицательная (например, x=5x = -5), max(0,5)\max(0, -5) выдает 00 (нейрон «молчит»). Если сумма положительная (например, x=12x = 12), max(0,12)\max(0, 12) выдает 1212 (нейрон передает сигнал дальше). Эта простейшая нелинейная «ступенька» позволяет современным сетям обучаться.

Проблема затухания градиента

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

Если нейрон выдает значение на этом плоском участке, любое небольшое изменение его весов почти не меняет результат. Сеть становится «слепой» к ошибкам — сигнал об ошибке просто затухает, проходя через такие плоские участки от слоя к слою. Это называется затуханием градиента (Vanishing Gradient). ReLU лишена этого недостатка для положительных чисел: ее график идет вверх под углом 45 градусов бесконечно, поэтому сигнал проходит четко и без искажений.

Обратное распространение ошибки (Backpropagation)

Теперь у нас есть сеть с правильными функциями активации. Она делает предсказание (прямое распространение), и мы вычисляем ошибку. Возвращаемся к нашему дирижеру: как найти виноватых?

Алгоритм, который решает эту задачу, называется обратным распространением ошибки (Backpropagation, или просто Backprop). Это сердце всего глубокого обучения.

Представьте автомобильный завод. С конвейера сходит машина, и инспектор качества (Функция потерь) видит, что правая дверь не закрывается.

  1. Инспектор идет к начальнику цеха сборки (Выходной слой) и штрафует его на 1000 рублей.
  2. Начальник цеха понимает, что дверь ставил робот-манипулятор №4 (Скрытый слой 2). Он передает 800 рублей штрафа ему, а 200 оставляет себе за недосмотр.
  3. Инженер, обслуживающий робота №4, видит, что проблема в бракованных петлях. Он передает 700 рублей штрафа поставщику петель (Скрытый слой 1).

Backprop работает точно так же, используя правило дифференцирования сложной функции (Chain Rule) из высшей математики. Ошибка вычисляется на выходе, а затем шаг за шагом передается назад, от последнего слоя к первому. Каждый нейрон получает свою «долю вины» пропорционально тому, насколько сильно его сигнал повлиял на финальную ошибку.

Что такое градиент?

Доля вины каждого конкретного веса выражается математическим термином — градиент.

Градиент показывает направление и крутизну подъема ошибки. Для каждого отдельного веса в сети градиент отвечает на один простой вопрос: "Если я сейчас увеличу этот вес на микроскопическую долю, итоговая ошибка вырастет или упадет?"

  • Если градиент положительный (например, +2.5+2.5), значит, увеличение веса ведет к росту ошибки. Логичный вывод: чтобы сеть стала умнее, этот вес нужно уменьшить.
  • Если градиент отрицательный (например, 1.2-1.2), значит, увеличение веса снижает ошибку. Вывод: этот вес нужно увеличить.
  • Если градиент равен нулю, значит, вес находится в идеальном балансе (на дне впадины ошибок), и трогать его не нужно.

Полный цикл обучения нейросети

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

  1. Прямое распространение (Forward Pass). Берем пример из данных (например, фотографию кота). Пиксели проходят через слои нейронов. Происходят миллионы умножений на случайные веса, результаты фильтруются через ReLU. На выходе функция Softmax выдает вероятности: «Собака — 80%, Кот — 20%».
  2. Вычисление потерь (Loss Calculation). Сравниваем предсказание с реальностью. Правильный ответ: Кот — 100%. Сеть сильно ошиблась. Функция потерь вычисляет математический размер этой ошибки.
  3. Обратное распространение (Backward Pass / Backprop). Сигнал об ошибке идет в обратную сторону. Алгоритм вычисляет градиенты для каждого веса: кто и насколько виноват в том, что сеть назвала кота собакой.
  4. Обновление весов (Weight Update). Получив градиенты, сеть корректирует свои параметры. Каждый вес делает крошечный шаг в сторону, противоположную ошибке.

После обновления весов сеть стала капельку умнее. Если мы снова покажем ей ту же фотографию кота, она выдаст уже не 20%, а, скажем, 22% уверенности.

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

Обучение нейросети на PyTorch: шаг за шагом

Обучение нейросети на PyTorch: шаг за шагом

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

Если бы нам пришлось писать всю эту математику с нуля на чистом Python, вычисление градиентов для сети из миллионов параметров заняло бы тысячи строк сложнейшего кода. К счастью, нам это не нужно. В индустрии стандартом де-факто для исследований и разработки ИИ стал PyTorch — фреймворк, который берет всю математику на себя. Наша задача — лишь спроектировать архитектуру и запустить конвейер.

В этой статье мы переведем теоретический цикл обучения в рабочий код.

Тензоры: кровеносная система PyTorch

Ранее мы работали с таблицами Pandas и массивами NumPy. В PyTorch главным контейнером для данных является тензор (Tensor).

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

Характеристика Массив NumPy (np.array) Тензор PyTorch (torch.tensor)
Среда выполнения Только процессор (CPU) CPU и видеокарты (GPU)
Градиенты Не вычисляет Автоматически отслеживает историю операций для Backpropagation
Назначение Анализ данных, классический ML Глубокое обучение (Deep Learning)

Главная суперсила тензора в PyTorch — механизм autograd (автоматическое дифференцирование). Когда тензор проходит через слои нейросети, PyTorch невидимо для нас записывает каждое математическое действие в специальный граф. Именно благодаря этой записи фреймворк может мгновенно вычислить градиенты при движении в обратную сторону.

Строим архитектуру: класс nn.Module

В PyTorch любая нейросеть создается по строгому шаблону. Мы создаем класс, который наследует базовый функционал от nn.Module.

Даже если вы глубоко не изучали объектно-ориентированное программирование, воспринимайте этот код как обязательную форму документа. В ней всегда есть два главных раздела: __init__ (что у нас есть) и forward (как мы это используем).

Напишем нейросеть для задачи бинарной классификации: мы хотим предсказать, купит ли пользователь премиум-подписку (1) или нет (0), на основе двух признаков: времени на сайте и количества кликов.

import torch
import torch.nn as nn

class ConversionNet(nn.Module):
    def __init__(self):
        # Обязательная строка для инициализации базового класса
        super().__init__()

        # Определяем слои (наши "детали конструктора")
        # Входной слой: 2 признака -> Скрытый слой: 4 нейрона
        self.hidden_layer = nn.Linear(in_features=2, out_features=4)

        # Выходной слой: 4 нейрона -> 1 ответ (вероятность)
        self.output_layer = nn.Linear(in_features=4, out_features=1)

    def forward(self, x):
        # Описываем путь данных (x) через слои
        x = self.hidden_layer(x)       # Данные проходят через скрытый слой
        x = torch.relu(x)              # Применяем функцию активации ReLU

        x = self.output_layer(x)       # Данные проходят через выходной слой
        x = torch.sigmoid(x)           # Сжимаем ответ в вероятность от 0 до 1

        return x

# Создаем конкретный экземпляр нашей модели
model = ConversionNet()

Обратите внимание: мы используем nn.Linear. Это стандартный полносвязный слой (многослойный перцептрон), где каждый нейрон текущего слоя связан с каждым нейроном предыдущего. PyTorch сам создаст веса и смещения нужного размера и заполнит их случайными числами.

Подготовка к обучению: Loss и Optimizer

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

  1. Функция потерь (Loss Function) — вычисляет ошибку. Для нашей задачи бинарной классификации мы используем BCELoss (Binary Cross Entropy).
  2. Оптимизатор (Optimizer) — алгоритм, который берет вычисленные градиенты и обновляет веса. Самый базовый вариант — SGD (Stochastic Gradient Descent).

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

import torch.optim as optim

# Оценщик: бинарная кросс-энтропия
criterion = nn.BCELoss()

# Механик: передаем ему параметры модели (веса) и скорость обучения (lr)
optimizer = optim.SGD(model.parameters(), lr=0.1)

Священный Грааль PyTorch: Цикл обучения

Мы подошли к кульминации. Цикл обучения (Training Loop) в PyTorch — это то, что вы будете писать сотни раз. Он состоит из пяти обязательных шагов, которые повторяются каждую эпоху.

Давайте создадим игрушечный датасет из 4 пользователей и запустим обучение на 100 эпох.

# 1. Готовим данные (Тензоры)
# Признаки: [Время на сайте (нормализованное), Количество кликов]
X_train = torch.tensor([
    [0.1, 0.2], # Мало был, мало кликал -> не купит
    [0.8, 0.9], # Долго был, много кликал -> купит
    [0.3, 0.4], # Не купит
    [0.9, 0.7]  # Купит
])

# Правильные ответы (Target)
y_train = torch.tensor([
    [0.0],
    [1.0],
    [0.0],
    [1.0]
])

# 2. Запускаем цикл обучения
epochs = 100

for epoch in range(epochs):
    # ШАГ 1: Обнуление градиентов
    optimizer.zero_grad()

    # ШАГ 2: Прямой проход (Forward Pass)
    predictions = model(X_train)

    # ШАГ 3: Вычисление ошибки (Loss)
    loss = criterion(predictions, y_train)

    # ШАГ 4: Обратное распространение (Backward Pass)
    loss.backward()

    # ШАГ 5: Обновление весов
    optimizer.step()

    # Печатаем прогресс каждые 20 эпох
    if (epoch + 1) % 20 == 0:
        print(f"Эпоха {epoch+1}/100 | Ошибка: {loss.item():.4f}")

Разбор 5 шагов под микроскопом

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

  1. optimizer.zero_grad() PyTorch устроен так, что при каждом вызове .backward() он прибавляет новые градиенты к старым, а не заменяет их. Если их не очищать в начале каждого шага, градиенты с прошлых эпох накопятся, и модель сойдет с ума. Это самая частая ошибка новичков.
  2. predictions = model(X_train) Мы передаем данные в модель. Под капотом PyTorch вызывает метод forward, который мы написали ранее. Данные проходят через слои и функции активации.
  3. loss = criterion(predictions, y_train) Сравниваем предсказания модели с реальными ответами. Результат — одно число (скаляр), отражающее общую боль модели.
  4. loss.backward() Магия autograd в действии. PyTorch берет значение ошибки и прогоняет его назад по графу вычислений, вычисляя градиент (долю вины) для каждого веса в слоях hidden_layer и output_layer.
  5. optimizer.step() Оптимизатор берет вычисленные градиенты и делает шаг: немного изменяет значения весов в ту сторону, где ошибка станет меньше.

Запустив этот код, вы увидите, как значение ошибки (Loss) стабильно падает с каждой эпохой. Модель нашла закономерность в данных.

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

Функции потерь и оптимизаторы (SGD, Adam)

Функции потерь и оптимизаторы (SGD, Adam)

В предыдущей главе мы собрали работающий конвейер обучения на PyTorch. Мы вызывали методы loss.backward() для вычисления градиентов и optimizer.step() для обновления весов. В коде это выглядит как магия: пара строк, и нейросеть сама понимает, как ей стать умнее.

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

Первую задачу решают функции потерь, вторую — оптимизаторы.

Компас нейросети: Функции потерь

Функция потерь (Loss Function) — это математическая формула, которая сравнивает предсказание модели с правильным ответом и выдает одно число: величину «штрафа». Чем хуже предсказание, тем выше штраф. Цель обучения — свести это число к минимуму.

Выбор функции потерь зависит исключительно от типа задачи.

1. Регрессия: Среднеквадратичная ошибка (MSE)

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

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

2. Бинарная классификация: Бинарная кросс-энтропия (BCE)

Если задача подразумевает ответ «да/нет» (вероятность от 0 до 1), MSE работает плохо. Для таких задач используют Binary Cross-Entropy (BCE).

Формула выглядит так:

L=(ylog(p)+(1y)log(1p))L = - \left( y \log(p) + (1 - y) \log(1 - p) \right)

Где yy — это правильный ответ (0 или 1), а pp — предсказание модели (вероятность от 0 до 1).

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

  • Если правильный ответ y=1y = 1 (например, письмо — это спам): Вторая часть формулы умножается на (11)(1 - 1), то есть на ноль, и исчезает. Остается только log(p)- \log(p). Если модель предсказала вероятность p=0.99p = 0.99, то штраф будет близок к нулю. Если модель уверена, что это не спам (p=0.01p = 0.01), логарифм выдаст огромное отрицательное число, а минус перед скобкой сделает штраф огромным положительным.
  • Если правильный ответ y=0y = 0 (нормальное письмо): Первая часть умножается на ноль. Остается log(1p)- \log(1 - p). Логика та же: предсказал p=0.99p = 0.99 для нормального письма — получи колоссальный штраф.

3. Многоклассовая классификация: Кросс-энтропия (Cross-Entropy)

Когда классов больше двух (например, маршрутизация тикетов в техподдержке на 5 разных отделов), используется обобщенная версия — Cross-Entropy Loss. Она работает в паре с функцией активации Softmax, которая распределяет 100% вероятности между всеми возможными классами. Кросс-энтропия смотрит только на ту вероятность, которую модель присвоила правильному классу, и штрафует сеть, если эта вероятность далека от 100%.

Навигатор: Как сделать шаг к минимуму

После того как функция потерь вычислила штраф, алгоритм обратного распространения ошибки (Backpropagation) находит градиенты. Градиент показывает, в какую сторону нужно изменить каждый вес в нейросети, чтобы штраф увеличился. Значит, чтобы штраф уменьшился, нам нужно сделать шаг в противоположном направлении.

Базовая формула обновления любого веса выглядит так:

wnew=woldαLw_{new} = w_{old} - \alpha \cdot \nabla L

Здесь woldw_{old} — текущий вес нейрона, L\nabla L — градиент (направление роста ошибки), а α\alpha (альфа) — это скорость обучения (Learning Rate).

Скорость обучения — это размер шага. Если α\alpha слишком мала, нейросеть будет обучаться мучительно долго. Если α\alpha слишком велика, сеть может «перепрыгнуть» оптимальное значение и начать выдавать случайные ответы, так как веса будут бесконтрольно метаться из стороны в сторону.

Оптимизатор SGD: Стохастический градиентный спуск

Самый простой способ обновлять веса — алгоритм SGD (Stochastic Gradient Descent).

В классическом алгоритме «стохастический» (случайный) означает обновление после каждого отдельного примера. Но на практике под SGD сегодня почти всегда имеют в виду мини-батч спуск (Mini-batch SGD): мы вычисляем ошибку и делаем шаг не после всего набора данных (это заняло бы слишком много оперативной памяти) и не после одного примера, а после просмотра небольшой случайной порции данных — батча (batch). Например, по 32 или 64 примера за раз.

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

Проблема SGD: Так как каждый батч содержит лишь часть данных, направление склона постоянно немного меняется. Человек начинает петлять. Кроме того, если он попадет в длинный пологий овраг (где дно почти плоское, а стены крутые), SGD заставит его прыгать со стены на стену, очень медленно продвигаясь вперед по дну оврага.

Решение: SGD с инерцией (Momentum)

Чтобы решить проблему метаний, инженеры добавили в SGD физическую концепцию инерции (Momentum).

Теперь наш слепой путественник превращается в тяжелый металлический шар, катящийся с горы. Если шар несколько шагов подряд катился в одном направлении, он набирает скорость. Мелкие кочки (шум от случайных батчей) уже не могут сбить его с пути.

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

Эволюция: Оптимизатор Adam

Хотя SGD с инерцией работает хорошо, у него есть один фундаментальный недостаток: размер шага (Learning Rate, α\alpha) одинаков для всех весов в нейросети.

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

Эту задачу решает оптимизатор Adam (Adaptive Moment Estimation). На сегодняшний день это золотой стандарт индустрии.

Adam объединяет в себе две мощные идеи:

  1. Инерция (Momentum): Он накапливает историю предыдущих направлений, чтобы сглаживать путь (как тяжелый шар).
  2. Адаптивная скорость обучения: Он индивидуально подстраивает размер шага α\alpha для каждого отдельного веса. Если градиент какого-то веса постоянно скачет, Adam уменьшает для него размер шага. Если градиент стабилен и мал, Adam увеличивает шаг, чтобы ускорить обучение.

Что выбрать на практике?

Критерий SGD (без инерции) SGD + Momentum Adam
Скорость сходимости Очень медленно Средне Очень быстро
Настройка Learning Rate Требует ручного подбора Требует ручного подбора Прощает неидеальные настройки
Когда использовать? Почти никогда в чистом виде В научных статьях для выжимания долей процента точности В 95% рабочих задач

Для старта любого нового проекта всегда используйте оптимизатор Adam. В сообществе AI-разработчиков даже есть негласное правило: если не знаешь, какую скорость обучения поставить для Adam, ставь α=0.001\alpha = 0.001 или α=3104\alpha = 3 \cdot 10^{-4}. В большинстве случаев этого будет достаточно, чтобы модель начала уверенно сходиться к правильным ответам.

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

Переобучение модели и методы регуляризации (Dropout, Batch Normalization)

Переобучение модели и методы регуляризации (Dropout, Batch Normalization)

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

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

Анатомия зубрежки: коадаптация нейронов

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

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

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

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

Например, при оценке стоимости недвижимости один из нейронов вместо анализа площади может настроить свои веса так: «Если этаж равен 3, а в описании есть опечатка в слове "балкон", значит, это квартира номер 42 из обучающей базы, и её цена ровно 15 миллионов».

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

Dropout: выключаем нейроны случайным образом

Самый популярный и элегантный метод борьбы с коадаптацией называется Dropout (исключение).

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

Представьте IT-отдел, в котором есть один гениальный Senior-разработчик. Остальная команда расслабилась и просто передает все сложные задачи ему (коадаптация). Отдел работает идеально, пока этот разработчик на месте. Но если он заболеет, работа встанет. Что делает Dropout? Он играет роль строгого директора, который каждый день случайным образом отправляет в отгул половину сотрудников. Оставшимся приходится брать ответственность на себя, изучать весь проект и не полагаться на коллег. В итоге каждый сотрудник становится самостоятельным специалистом.

Как это работает математически

При добавлении слоя Dropout мы задаем гиперпараметр pp — вероятность отключения нейрона. Обычно pp устанавливают в диапазоне от 0.20.2 до 0.50.5.

Если p=0.5p = 0.5, то при каждом новом проходе данных (Forward Pass) каждый нейрон бросает монетку. Выпал орел — нейрон передает свой сигнал дальше. Выпала решка — его выход умножается на ноль, и в текущем шаге обновления весов он не участвует.

Важный нюанс: режим работы (Train vs Eval) Dropout нужен только для тренировки. Когда мы отправляем модель в продакшен делать реальные предсказания (инференс), нам нужны все нейроны. Но если включить их все сразу, суммарный сигнал стал бы в два раза сильнее (ведь при обучении с p=0.5p = 0.5 работала только половина). Фреймворки вроде PyTorch используют «инвертированный Dropout» (Inverted Dropout). Они компенсируют разницу еще на этапе обучения, пропорционально увеличивая сигналы оставшихся активных нейронов (например, умножают на 2 при p=0.5p = 0.5). Поэтому в режиме оценки (инференса) нейроны просто не отключаются, а их сигналы передаются дальше без каких-либо изменений.

Batch Normalization: стабилизируем конвейер

Dropout решает проблему зубрежки, но у глубоких сетей есть еще одна беда — нестабильность самого процесса обучения.

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

Чтобы стабилизировать конвейер, применяют Batch Normalization (пакетную нормализацию).

Механика нормализации

Идея в том, чтобы принудительно приводить выходы слоя к единому стандарту прямо внутри батча (порции данных), прежде чем передать их функции активации.

Формула нормализации выглядит так:

xnorm=xμσ2+ϵx_{norm} = \frac{x - \mu}{\sqrt{\sigma^2 + \epsilon}}

Разберем её элементы на практике:

  • xx — исходный сигнал от нейрона.
  • μ\mu — среднее значение сигналов в текущем батче. Мы вычитаем его, центрируя данные вокруг нуля.
  • σ2\sigma^2 — дисперсия (разброс) сигналов батча. Мы делим на её корень (стандартное отклонение), делая разброс стандартным (со средним 0 и дисперсией 1).
  • ϵ\epsilon — крошечное число (например, 0.000010.00001), которое добавляется, чтобы случайно не поделить на ноль, если дисперсия равна нулю.

Например, если нейрон выдал сигнал x=10x = 10, среднее по батчу μ=6\mu = 6, а дисперсия σ2=4\sigma^2 = 4 (без учета ϵ\epsilon), нормализованный сигнал составит 1064=2\frac{10 - 6}{\sqrt{4}} = 2.

Восстановление свободы (γ\gamma и β\beta)

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

Поэтому после стандартизации Batch Normalization добавляет два обучаемых параметра:

  • γ\gamma (гамма) — позволяет сети самой растянуть или сжать данные.
  • β\beta (бета) — позволяет сдвинуть данные вправо или влево.

Сеть сама в процессе градиентного спуска решает, какими должны быть γ\gamma и β\beta. Batch Norm не заставляет данные всегда быть со средним 0 и дисперсией 1, он просто делает их изменения предсказуемыми и плавными.

Сравнение и совместное использование

Оба метода стали стандартом в архитектуре современных нейросетей. Их часто используют вместе, но у них разные цели.

Характеристика Dropout Batch Normalization
Главная цель Борьба с переобучением (зубрежкой) Ускорение и стабилизация обучения
Механизм Случайное обнуление сигналов Стандартизация масштаба сигналов
Дополнительные веса Нет параметров для обучения Есть обучаемые параметры (γ\gamma, β\beta)
Поведение при инференсе Отключается (работают все нейроны) Использует накопленную статистику, а не текущий батч

В PyTorch стандартный блок скрытого слоя с регуляризацией обычно собирается в таком порядке:

  1. Линейный слой (nn.Linear) — вычисляет суммы.
  2. Пакетная нормализация (nn.BatchNorm1d) — стабилизирует масштаб.
  3. Функция активации (nn.ReLU) — добавляет нелинейность.
  4. Исключение (nn.Dropout) — предотвращает коадаптацию перед следующим слоем.

Благодаря Dropout и Batch Normalization мы можем строить сети из десятков и сотен слоев, не боясь, что обучение застрянет или модель просто выучит обучающую выборку наизусть. Теперь наш фундамент для работы с глубоким обучением полностью готов. Впереди — переход от чисел к человеческому языку и работа с текстами.

Как компьютер понимает слова: токенизация и эмбеддинги

Как компьютер понимает слова: токенизация и эмбеддинги

Нейросети, которые мы строили до сих пор, принимали на вход понятные числа: площадь квартиры, возраст клиента, количество кликов на сайте. Но что произойдет, если мы попытаемся передать в PyTorch текст жалобы клиента: «Ужасный сервис, верните деньги!»? Модель выдаст ошибку. Математические операции (умножение на веса, сложение со смещением) работают только с тензорами.

Чтобы научить ИИ анализировать документы, отвечать на вопросы или писать код, нам нужен надежный мост между человеческим языком и математикой. Этот мост состоит из двух пролетов: токенизации (нарезки текста на кусочки) и эмбеддингов (превращения кусочков в координаты смыслового пространства).

Шаг 1. Токенизация: как правильно нарезать текст

Первая мысль — давайте присвоим каждому слову в языке свой уникальный номер. «Ужасный» = 1, «сервис» = 2, и так далее.

Этот подход на уровне целых слов (Word-level) быстро разбивается о реальность. В русском языке слова склоняются, спрягаются и образуют новые формы. «Сервис», «сервиса», «сервисом», «суперсервис» — для модели это будут абсолютно разные идентификаторы. Словарь разрастется до миллионов записей, а если клиент опечатается («сервисс»), модель получит неизвестное число и сломается.

Вторая крайность — разбить текст на отдельные буквы (Character-level). Словарь станет крошечным (около 100 символов), опечатки не страшны. Но пропадает смысл. Буква «с» сама по себе не несет информации, нейросети придется тратить огромные вычислительные ресурсы просто на то, чтобы заново научиться складывать буквы в слова.

Индустрия пришла к золотой середине — сабворд-токенизации (Subword tokenization), из которых самый популярный алгоритм называется BPE (Byte Pair Encoding).

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

Рассмотрим слово «автокатастрофа». Алгоритм может разбить его на три токена:

  1. авто (очень частая приставка)
  2. катастроф (корень)
  3. а (окончание)

Каждому токену в словаре модели заранее присвоен уникальный ID (целое число). Например:

  • авто → 4591
  • катастроф → 18204
  • а → 32

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

Шаг 2. Ловушка One-Hot Encoding

Итак, у нас есть последовательность ID. Допустим, токен «кошка» имеет ID 405, а «собака» — ID 812. Можно ли подать эти числа прямо в нейронную сеть?

Нет. Если мы передадим числа 405 и 812, алгоритм обратного распространения ошибки решит, что «собака» ровно в два раза «больше» или «важнее» кошки, ведь 812 примерно равно 405 × 2. Но ID — это просто порядковые номера в словаре, в них нет математической иерархии.

В главе про табличные данные мы решали эту проблему с помощью One-Hot Encoding (OHE) — превращали категорию в набор нулей и одной единицы. Если наш словарь состоит из 50 000 токенов, то слово «кошка» превратится в вектор длиной 50 000, где на 405-й позиции стоит 1, а остальные 49 999 позиций — нули.

Для текста этот подход катастрофически неэффективен по двум причинам:

  1. Вычислительный коллапс: матрица из миллионов нулей сожжет всю оперативную память.
  2. Отсутствие смысла: в OHE векторы любых двух слов ортогональны (не пересекаются). Для модели слова «кошка» и «собака» будут так же далеки друг от друга, как «кошка» и «синхрофазотрон». Модель не понимает, что это родственные понятия.

Нам нужен способ сжать эти 50 000 нулей во что-то компактное, что при этом будет отражать смысл слова.

Шаг 3. Эмбеддинги: координаты смысла

Эмбеддинг (Embedding) — это плотный вектор фиксированной длины (обычно от нескольких сотен до нескольких тысяч чисел, например 300 в Word2Vec или 12288 в GPT-3), который представляет слово как точку в многомерном пространстве.

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

  1. Одушевленность (от 0 до 1)
  2. Королевский статус (от 0 до 1)
  3. Женственность (от 0 до 1)

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

Слово Одушевленность Королевский статус Женственность Вектор
Король 0.99 0.95 0.05 [0.99, 0.95, 0.05]
Королева 0.99 0.95 0.98 [0.99, 0.95, 0.98]
Мужчина 0.99 0.02 0.05 [0.99, 0.02, 0.05]
Женщина 0.99 0.02 0.98 [0.99, 0.02, 0.98]
Яблоко 0.01 0.00 0.00 [0.01, 0.00, 0.00]

Что дает нам такое представление? Во-первых, слова с похожим смыслом («Король» и «Мужчина») имеют близкие координаты. Во-вторых, с этими векторами можно совершать осмысленные математические операции!

Самый знаменитый пример в истории обработки естественного языка (NLP) — это векторная арифметика. Если мы возьмем вектор слова «Король», вычтем из него вектор «Мужчина» (уберем мужской пол) и прибавим вектор «Женщина» (добавим женский пол), то мы получим координаты, которые почти идеально совпадут с вектором слова «Королева».

В виде формулы это выглядит так:

vкорольvмужчина+vженщинаvкоролева\mathbf{v}_{\text{король}} - \mathbf{v}_{\text{мужчина}} + \mathbf{v}_{\text{женщина}} \approx \mathbf{v}_{\text{королева}}

Где:

  • vкороль\mathbf{v}_{\text{король}} — вектор, описывающий смысл слова «король» (содержит признаки монархии и мужского пола).
  • vмужчина\mathbf{v}_{\text{мужчина}} — вектор слова «мужчина» (вычитая его, мы удаляем из значения признак мужского пола, оставляя чистую «монархию»).
  • vженщина\mathbf{v}_{\text{женщина}} — вектор слова «женщина» (прибавляя его, мы добавляем к «монархии» женский пол).
  • \approx — знак приближенного равенства (результат не совпадет с вектором «королева» до тысячных долей, но будет к нему ближе всего в многомерном пространстве).
  • vкоролева\mathbf{v}_{\text{королева}} — итоговый вектор слова «королева».

Практический пример: Представьте, что мы решаем аналогичную задачу со странами и столицами. Если мы возьмем вектор vПариж\mathbf{v}_{\text{Париж}}, вычтем из него vФранция\mathbf{v}_{\text{Франция}} (убрав французский контекст, но оставив концепцию «столицы») и прибавим vИталия\mathbf{v}_{\text{Италия}}, то полученные координаты укажут прямо на вектор vРим\mathbf{v}_{\text{Рим}}.

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

В реальных нейросетях оси не имеют понятных человеку названий вроде «Одушевленность». Это просто 300 или 768 абстрактных математических признаков, которые модель вывела сама.

Откуда берутся эти координаты?

Возникает главный вопрос: кто заполняет эти векторы правильными числами? Как модель узнает, что у «короля» высокий статус?

В PyTorch для этого существует специальный слой — nn.Embedding. По сути, это просто огромная таблица (матрица весов). Количество строк в ней равно размеру словаря (например, 50 000 токенов), а количество колонок — размеру вектора (например, 300).

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

Но затем начинается магия обучения, которую мы разбирали в предыдущих главах:

  1. Вы подаете текст (набор ID токенов) в слой nn.Embedding.
  2. Слой достает случайные векторы по этим ID и передает их дальше по сети (Forward Pass).
  3. Сеть делает предсказание (например, пытается угадать тональность отзыва).
  4. Функция потерь вычисляет ошибку.
  5. Алгоритм обратного распространения (Backpropagation) вычисляет градиенты не только для скрытых слоев, но и для самой таблицы эмбеддингов.
  6. Оптимизатор (например, Adam) сдвигает координаты слов в таблице так, чтобы в следующий раз ошибка была меньше.

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

Итог: полный путь текста

Теперь мы видим полную картину того, как сырой текст превращается в данные, готовые для нейросети:

  1. Текст: «Плохой сервис»
  2. Токенизация (BPE): ['Плох', 'ой', ' сервис']
  3. Словарь (ID): [1542, 89, 4021]
  4. Слой nn.Embedding: Вытаскивает векторы из строк 1542, 89 и 4021.
  5. Тензор (Математика): Набор плотных векторов (например, матрица 3 × 300) отправляется в скрытые слои нейросети.

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

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

Рекуррентные нейросети и их ограничения

Рекуррентные нейросети и их ограничения

Представьте, что вы анализируете отзывы клиентов. У вас есть две фразы: «Клиент не счастлив, он разочарован» и «Клиент не разочарован, он счастлив». Набор слов (и их векторных представлений — эмбеддингов) в обоих случаях абсолютно одинаковый. Если мы просто сложим эти векторы и передадим в стандартный многослойный перцептрон (MLP), модель выдаст один и тот же результат для обеих фраз. Стандартная нейросеть страдает тяжелой формой амнезии: она видит слова как перемешанные кубики в мешке и совершенно не понимает концепцию времени и порядка.

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

Архитектура с памятью: как работает RNN

Рекуррентная нейронная сеть (Recurrent Neural Network, RNN) решает проблему порядка слов элегантным способом. Вместо того чтобы принимать весь текст целиком, она обрабатывает его по одному токену за шаг.

Главное нововведение RNN — это скрытое состояние (hidden state). Это вектор, который служит «краткосрочной памятью» сети. На каждом шаге нейросеть делает две вещи одновременно: смотрит на новое слово и читает записку, которую сама же написала себе на предыдущем шаге.

Математически шаг внутри RNN выглядит так:

ht=tanh(Whhht1+Wxhxt+b)h_t = \tanh(W_{hh} h_{t-1} + W_{xh} x_t + b)

Разберем элементы этой формулы:

  • hth_t — новое скрытое состояние (память после прочтения текущего слова).
  • xtx_t — эмбеддинг текущего слова на шаге tt.
  • ht1h_{t-1} — предыдущее скрытое состояние (контекст всех прошлых слов).
  • WhhW_{hh} и WxhW_{xh} — матрицы весов, которые модель настраивает в процессе обучения. Они определяют, насколько важна прошлая память по сравнению с новым словом.
  • tanh\tanh (гиперболический тангенс) — функция активации, которая сжимает итоговые значения в диапазон от -1 до 1, не давая числам в памяти бесконечно расти при чтении длинных текстов.

Развертка во времени на практике

Посмотрим, как RNN анализирует фразу «Кредит одобрен».

  1. Шаг 1 (t=1t=1): На вход поступает эмбеддинг слова «Кредит» (x1x_1). Предыдущей памяти еще нет, поэтому h0h_0 состоит из нулей. Нейросеть вычисляет h1h_1 — вектор, который теперь содержит математический смысл концепции «что-то связанное с финансами».
  2. Шаг 2 (t=2t=2): На вход поступает слово «одобрен» (x2x_2). Сеть берет его и объединяет с памятью h1h_1. Вычисляется новое состояние h2h_2. Теперь этот вектор содержит смысл «финансовая операция с положительным исходом».
  3. Финал: Вектор h2h_2 передается в обычный линейный слой классификации, который выдает предсказание: тональность позитивная.

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

Почему базовые RNN проиграли: два фатальных ограничения

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

Ограничение 1: Катастрофическое забывание

Вспомните проблему затухания градиента (Vanishing Gradient), которую мы разбирали при изучении многослойного перцептрона. В RNN эта проблема возводится в абсолют.

Чтобы обучить RNN, используется алгоритм BPTT (Backpropagation Through Time — обратное распространение ошибки сквозь время). Ошибка от финального предсказания должна «протечь» назад через все шаги времени, чтобы обновить веса для самых первых слов.

На каждом шаге назад градиент умножается на матрицу весов WhhW_{hh}. Если веса в этой матрице при умножении уменьшают сигнал (строго говоря, если её собственные значения меньше единицы), градиент быстро стремится к нулю.

Представьте задачу предсказания следующего слова для текста: «Я вырос во Франции, там я ходил в школу, ел круассаны, играл с друзьями во дворе, а теперь я свободно говорю по-___»

Чтобы правильно предсказать слово «французски», сети нужно вспомнить слово «Франции», которое было 20 шагов назад. Но из-за затухания градиента сигнал об ошибке просто не доходит до начала предложения. Базовая RNN обладает памятью золотой рыбки: она отлично помнит последние 3-5 слов, но полностью теряет контекст начала абзаца.

Ограничение 2: Бутылочное горлышко последовательности

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

Архитектура Обработка данных Использование GPU
MLP / CNN Все входы обрабатываются одновременно. Максимальная загрузка, высокая скорость.
RNN Шаг tt невозможно вычислить, пока не завершен шаг t1t-1. Простаивание мощностей, крайне медленное обучение.

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

Попытка ремонта: LSTM и GRU

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

Вместо одной простой формулы с tanh\tanh, ячейка LSTM содержит систему внутренних «вентилей» (gates) — математических фильтров на базе функции Sigmoid, которые выдают значения от 0 до 1.

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

LSTM создала внутри сети «скоростную магистраль» для градиентов, позволив им протекать сквозь сотни шагов без затухания. Модель смогла запоминать контекст на дистанции целых абзацев. Позже появилась архитектура GRU (Gated Recurrent Unit) — упрощенная и более быстрая версия LSTM, работающая по схожему принципу.

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

LSTM вылечила RNN от амнезии, но не смогла избавить её от медлительности. Тексты всё ещё приходилось обрабатывать строго слово за словом.

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

Архитектура Transformer: революция в обработке текста

Архитектура Transformer: революция в обработке текста

В 2017 году исследователи из Google опубликовали научную статью с провокационным названием «Attention Is All You Need» (Внимание — это всё, что вам нужно). В ней не было ни одной рекуррентной сети (RNN) или ячейки долгой памяти (LSTM). Авторы предложили полностью отказаться от последовательного чтения текста слово за словом, которое вечно тормозило обучение моделей на видеокартах.

Вместо этого они создали архитектуру Transformer — алгоритм, который способен смотреть на весь текст целиком, мгновенно понимая, как каждое слово связано со всеми остальными. Именно этот прорыв сделал возможным появление ChatGPT, Claude и других современных языковых моделей.

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

Отказ от очередей: параллельная обработка

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

Трансформер решает эту проблему радикально: он загружает в нейросеть все эмбеддинги слов предложения одновременно.

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

Механизм Self-Attention: кто на кого смотрит?

В человеческом языке смысл слова часто зависит от окружения. Возьмем слово «замок».

  • В предложении «Старый замок скрипнул в двери» речь идет о механизме.
  • В предложении «Старый замок возвышался на горе» речь идет о здании.

Трансформер решает эту задачу с помощью механизма Self-Attention (внутреннее внимание). Этот механизм позволяет каждому слову в предложении «посмотреть» на все остальные слова и решить, какие из них важны для его собственного понимания.

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

  1. Query (Запрос, QQ) — что я ищу? Это вектор, с помощью которого слово «задает вопрос» остальному тексту. Например, слово «замок» спрашивает: «Есть ли вокруг слова, указывающие на дверь или на архитектуру?».
  2. Key (Ключ, KK) — что я собой представляю? Это вектор-ярлык слова. Слово «двери» держит табличку: «Я связано с проемами, деревом и механизмами».
  3. Value (Значение, VV) — мое фактическое смысловое содержание. Если Запрос и Ключ совпали, именно вектор Значения будет использован для обогащения смысла того слова, которое задавало вопрос.

Математика внимания

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

Attention(Q,K,V)=softmax(QKTdk)V\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{Q K^T}{\sqrt{d_k}}\right) V

Разберем ее элементы на практическом примере:

  • QKTQ K^T (умножение матрицы Запросов на транспонированную матрицу Ключей): алгоритм берет Запрос слова «замок» и математически сравнивает его (через скалярное произведение) с Ключами всех остальных слов в предложении. Чем больше векторы похожи, тем выше итоговое число (оценка совпадения).
  • dk\sqrt{d_k}: это просто масштабирующий коэффициент (корень из размера вектора). Он нужен, чтобы при умножении больших векторов числа не улетали в бесконечность и не ломали обучение.
  • softmax\text{softmax}: знакомая нам функция активации. Она превращает сырые оценки совпадений в проценты (от 0 до 1), сумма которых равна 100%. Например, слово «замок» может на 80% обратить внимание на «двери», на 15% на «скрипнул» и на 5% на самого себя.
  • VV (умножение на Значение): мы берем векторы Значений всех слов и умножаем их на полученные проценты внимания.

В итоге слово «замок» впитывает в себя 80% смысла слова «двери». Его вектор меняется: теперь это не просто абстрактный «замок», а «замок-в-контексте-двери».

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

Multi-Head Attention: взгляд с разных сторон

Связи между словами бывают разными. В предложении «Маша быстро съела зеленое яблоко» слово «съела» связано с «Маша» (кто совершил действие?) и с «яблоко» (что съели?). Если у нас будет только один механизм внимания, он может сфокусироваться только на одной связи, упустив другую.

Поэтому авторы Трансформера ввели Multi-Head Attention (многоголовое внимание).

Вместо того чтобы создавать один набор векторов Запросов, Ключей и Значений, модель создает их несколько (например, 8 или 12 «голов»). Каждая голова обучается обращать внимание на свои специфические паттерны:

  • Первая голова может следить за грамматикой (связь подлежащего и сказуемого).
  • Вторая голова — за прилагательными и существительными («зеленое» -> «яблоко»).
  • Третья голова — за эмоциональной окраской текста.

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

Positional Encoding: возвращаем порядок слов

Если мы подаем все слова в нейросеть одновременно, возникает критическая уязвимость. Для механизма Self-Attention фразы «Кот съел мышь» и «Мышь съела кота» абсолютно идентичны — это просто один и тот же набор слов, которые обмениваются смыслами. Свойство порядка, которое было естественным для рекуррентных сетей, исчезло.

Чтобы вернуть понимание порядка, используется Positional Encoding (позиционное кодирование).

До того как отправить эмбеддинги слов в механизмы внимания, алгоритм прибавляет к каждому эмбеддингу специальный вектор позиции. Этот вектор содержит информацию о порядковом номере слова (1-е, 2-е, 3-е и т.д.).

Создаются эти векторы с помощью математических функций синуса и косинуса разных частот. Это позволяет нейросети не просто заучить абсолютные номера («это слово номер 5»), но и понимать относительные расстояния («это слово находится через три позиции после того»).

В результате на вход Трансформера поступает комбинированный вектор:

Вектор на входе = Эмбеддинг слова + Вектор позиции

Теперь слова «Кот» на первом месте и «Кот» на третьем месте будут иметь разные числовые представления.

Архитектура: Энкодер и Декодер

Оригинальный Трансформер состоит из двух больших блоков:

  1. Энкодер (Кодировщик): Его задача — прочитать входной текст, применить Self-Attention и создать максимально насыщенные контекстом векторы для каждого слова. Он видит весь текст сразу и идеально подходит для задач анализа (например, классификации текстов или извлечения фактов).
  2. Декодер (Декодировщик): Его задача — генерировать новый текст шаг за шагом. Он использует контекст, собранный Энкодером, но при генерации следующего слова ему запрещено «заглядывать в будущее» (смотреть на слова, которые он еще не написал). Для этого применяется маскирование (Masked Self-Attention).

Интересно, что со временем индустрия ИИ разделила эти блоки:

  • Модели семейства BERT от Google используют только Энкодер. Они великолепно понимают текст, но не предназначены для написания стихов или эссе.
  • Модели семейства GPT от OpenAI используют только Декодер. Они заточены под предсказание следующего слова и генерацию длинных связных текстов.

Архитектура Transformer решила главную дилемму машинного обучения: она объединила способность понимать глубокий контекст (которую пытались достичь RNN) со скоростью параллельных вычислений на GPU. Это позволило обучать модели на терабайтах текстов, что и привело к появлению современных Больших Языковых Моделей (LLM).

Использование предобученных моделей из Hugging Face

Использование предобученных моделей из Hugging Face

Обучение современной архитектуры Transformer с нуля — это задача, требующая колоссальных ресурсов. Чтобы создать языковую модель уровня GPT-3, корпорации арендуют тысячи видеокарт на несколько месяцев и тратят миллионы долларов только на счета за электричество и вычислительные мощности. Возникает закономерный вопрос: как обычным разработчикам и бизнесу использовать эти технологии для своих рабочих задач, если таких бюджетов нет?

Ответ кроется в концепции предобученных моделей (pre-trained models). Нам не нужно учить нейросеть понимать человеческий язык с чистого листа. Мы можем взять готовую модель, которую уже обучила крупная корпорация или исследовательский институт, скачать её веса и использовать для своих целей. Эту революцию доступности в мире ИИ совершила платформа Hugging Face.

Hugging Face: GitHub для нейросетей

Hugging Face — это одновременно и компания, и крупнейшее open-source сообщество в сфере машинного обучения. Экосистема строится вокруг двух главных компонентов:

  1. Hugging Face Hub — это веб-сайт, работающий по принципу каталога (подобно GitHub, но для нейросетей). Здесь исследователи со всего мира выкладывают обученные модели, датасеты и готовые приложения. На Hub можно найти более двух миллионов моделей: от тех, что переводят текст с суахили на английский, до тех, что определяют тональность финансовых новостей.
  2. Библиотека transformers — это Python-библиотека, которая позволяет скачивать модели с Hub и запускать их на вашем компьютере или сервере буквально в несколько строк кода.

Вам больше не нужно вручную прописывать слои Трансформера, матрицы внимания и функции активации. Библиотека берёт всю инженерную сложность на себя.

Самый быстрый старт: абстракция pipeline

Самый простой способ заставить модель работать — использовать класс pipeline из библиотеки transformers. Это высокоуровневая обёртка, которая скрывает все промежуточные этапы обработки данных.

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

from transformers import pipeline

# Создаем пайплайн для анализа тональности
classifier = pipeline("sentiment-analysis")

# Передаем текст
result = classifier("I absolutely love the new design of your website!")
print(result)

Вывод программы будет выглядеть так: [{'label': 'POSITIVE', 'score': 0.9998}].

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

Что происходит под капотом?

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

Шаг 1: Токенизация (AutoTokenizer)

Нейросети не понимают текст, они понимают только тензоры (многомерные массивы чисел). Поэтому сначала текст попадает в токенизатор.

В библиотеке transformers есть универсальный класс AutoTokenizer. Его главная задача — разбить текст на токены (сабворды) и заменить их на числовые ID из словаря, с которым обучалась конкретная модель.

from transformers import AutoTokenizer

model_name = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(model_name)

text = "The product is okay, but delivery was late."
# return_tensors="pt" означает, что мы хотим получить тензоры PyTorch
inputs = tokenizer(text, return_tensors="pt")

Шаг 2: Прямой проход через модель (AutoModel)

Полученные тензоры передаются в саму нейросеть. Для задач классификации текста используется класс AutoModelForSequenceClassification. Он загружает архитектуру Трансформера (Энкодер) и уже обученные веса.

Модель выполняет прямой проход (forward pass) и выдает сырые предсказания, которые в машинном обучении называются логитами (logits). Логиты — это числа от -\infty до ++\infty, показывающие «уверенность» модели в каждом из классов до применения функции активации.

from transformers import AutoModelForSequenceClassification

model = AutoModelForSequenceClassification.from_pretrained(model_name)
outputs = model(**inputs)

print(outputs.logits)
# Вывод: tensor([[ 1.25, -1.05]])

Шаг 3: Постобработка (Softmax)

Сырые логиты бесполезны для бизнеса. Чтобы превратить их в понятные вероятности от 00 до 11, мы применяем функцию активации Softmax (о которой говорили при изучении многоклассовой классификации).

import torch

# Применяем Softmax к логитам
probabilities = torch.nn.functional.softmax(outputs.logits, dim=-1)
print(probabilities)
# Вывод: tensor([[0.9089, 0.0911]])

Теперь мы видим, что модель на 90.9%90.9\% уверена в принадлежности текста к первому классу (например, негативному отзыву из-за опоздавшей доставки) и на 9.1%9.1\% — ко второму. pipeline делал все эти три шага за нас.

Практический кейс: маршрутизация русскоязычных отзывов

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

Модель по умолчанию в pipeline работает с английским языком. Нам нужно найти на Hugging Face Hub модель, которая понимает русский язык и обучена на задачу анализа тональности (Sentiment Analysis). Отличным кандидатом будет модель blanchefort/rubert-base-cased-sentiment, обученная на базе русскоязычного BERT.

Мы можем передать имя этой конкретной модели прямо в pipeline:

from transformers import pipeline

# Указываем конкретную модель с Hub
analyzer = pipeline(
    "sentiment-analysis",
    model="blanchefort/rubert-base-cased-sentiment"
)

reviews = [
    "Номер чистый, завтраки вкусные. Вернемся еще!",
    "Ужасный сервис. Ждали заселения два часа, кондиционер сломан.",
    "Обычная гостиница на одну ночь, ничего особенного."
]

for review in reviews:
    result = analyzer(review)[0]
    print(f"Отзыв: {review[:20]}... | Статус: {result['label']} | Уверенность: {result['score']:.2f}")

    if result['label'] == 'NEGATIVE':
        print("-> [АЛАРМ] Отправка уведомления менеджеру!\n")

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

Однако предобученные модели не всесильны. Что если в нашем отеле есть специфический сленг? Например, клиент пишет: «Цены в баре просто космос». Для общей модели слово «космос» может иметь позитивный окрас (великолепно), но в контексте цен отеля это явный негатив (слишком дорого). Универсальная модель с Hugging Face Hub не знает специфики вашего бизнеса и начнет ошибаться на таких узкоспециализированных данных.

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

Тонкая настройка (Fine-tuning) LLM под свою задачу

Тонкая настройка (Fine-tuning) LLM под свою задачу

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

Мы не можем переучивать гигантскую нейросеть с нуля — на это ушли бы месяцы работы суперкомпьютеров и миллионы долларов. Нам нужно взять готовую, умную модель и быстро «доучить» ее правилам нашей компании. Этот процесс называется тонкой настройкой (Fine-tuning).

От энциклопедии к корпоративному справочнику

Чтобы понять место тонкой настройки, давайте разделим обучение современных больших языковых моделей (LLM) на два этапа:

  1. Pre-training (Предобучение): Модель читает весь интернет. Она усваивает грамматику, факты, логику, учится связывать слова (через механизм Attention, который мы разбирали ранее). На выходе получается «выпускник университета» — он знает много, но ничего не знает о вашей конкретной работе.
  2. Fine-tuning (Тонкая настройка): Мы берем этого выпускника и даем ему корпоративный регламент. Мы показываем ему сотни или тысячи примеров того, как именно он должен отвечать на наши специфические вопросы.

Тонкая настройка меняет веса нейросети, но делает это прицельно, опираясь на уже существующий мощный фундамент (эмбеддинги и слои Трансформера).

Проблема полного дообучения (Full Fine-tuning)

Исторически тонкая настройка подразумевала обновление всех весов модели. Если мы берем популярную модель Llama 3 с 8 миллиардами параметров (весов) и начинаем пропускать через нее наши данные, алгоритм обратного распространения ошибки (Backpropagation) будет пытаться обновить каждый из этих 8 миллиардов весов.

Для этого потребуется огромный объем видеопамяти (VRAM), недоступный обычным рабочим компьютерам или стандартным серверам.

Решение пришло в виде концепции PEFT (Parameter-Efficient Fine-Tuning) — эффективной по параметрам тонкой настройки. И самым популярным методом PEFT сегодня является LoRA.

Как работает LoRA (Low-Rank Adaptation)

Представьте, что базовая модель — это огромная, тяжелая энциклопедия, напечатанная в типографии. Вы нашли в ней неточности для вашей предметной области. Перепечатывать всю энциклопедию (Full Fine-tuning) — безумие.

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

Механизм LoRA делает именно это:

  1. Заморозка базы: Все миллиарды оригинальных весов предобученной модели блокируются. Алгоритм больше не может их изменять.
  2. Добавление адаптера: К слоям модели (обычно к матрицам механизма Attention — Query и Value) сбоку прикрепляются крошечные дополнительные нейросети — адаптеры.
  3. Обучение: В процессе обучения обновляются веса только в этих маленьких адаптерах.

Математически это выглядит так: оригинальный вес нейрона WW остается константой, а к нему прибавляется изменение ΔW\Delta W, которое вычисляется через адаптер. Итоговый сигнал равен W+ΔWW + \Delta W.

Характеристика Full Fine-Tuning PEFT (LoRA)
Обновляемые веса 100% (миллиарды) ~0.1% - 1% (миллионы)
Требования к железу Кластер из нескольких мощных GPU Одна потребительская видеокарта
Результат Новая тяжелая модель Крошечный файл-адаптер (несколько мегабайт)
Риск забывания Высокий (модель может забыть базовые знания) Низкий (базовые веса заморожены)

На практике файл адаптера LoRA может весить всего 50-100 МБ. Вы можете обучить один адаптер для работы с юридическими договорами, второй — для генерации Python-кода, и переключать их на лету, используя одну и ту же тяжелую базовую модель.

Подготовка данных: Instruction Tuning

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

Для решения рабочих задач используется формат Instruction Tuning (Обучение на инструкциях). Мы должны собрать датасет, где каждый пример состоит из трех частей:

  1. System Prompt (Системный промпт): Глобальное правило поведения.
  2. User (Запрос пользователя): Входные данные.
  3. Assistant (Ответ ассистента): Идеальный ответ, который мы хотим получить.

Пример из датасета для маршрутизации обращений в клинике:

System: Ты — медицинский диспетчер. Твоя задача — определить отделение по жалобе пациента и выдать ответ строго в формате JSON с ключами "department" и "urgency". User: Третий день тянет поясницу, отдает в правую ногу. Температуры нет. Assistant: {"department": "neurology", "urgency": "low"}

Модель читает весь этот блок текста целиком. Функция потерь (Cross-Entropy) настраивается так, чтобы штрафовать модель только за ошибки в генерации части Assistant. То есть мы не учим модель генерировать вопросы пользователя, мы учим ее правильно на них реагировать.

Полный цикл Fine-tuning на практике

Давайте соберем воедино то, как выглядит процесс тонкой настройки LLM с использованием библиотеки transformers и метода LoRA для задачи из примера выше.

  1. Загрузка базовой модели в сжатом виде: Чтобы 8-миллиардная модель поместилась в память одной видеокарты, ее веса часто сжимают (квантуют). Этот подход в связке с LoRA называется QLoRA.
  2. Инициализация LoRA: Мы указываем конфигурацию — к каким именно слоям Трансформера прикрепить наши «стикеры» и какого размера они будут.
  3. Прямой проход (Forward Pass): Текст из нашего датасета (инструкция + жалоба пациента) проходит через замороженные веса базы и через инициализированные случайным образом веса адаптера.
  4. Вычисление ошибки (Loss): Модель пытается предсказать ответ. Допустим, она выдает текст «Вам нужно к терапевту», а в нашем датасете ожидается JSON {"department": "neurology"}. Вычисляется штраф.
  5. Обратное распространение (Backward Pass): Сигнал об ошибке идет от конца к началу. Но, в отличие от классического обучения, градиенты обновляют веса только внутри маленького адаптера LoRA. Базовая модель остается нетронутой.
  6. Сохранение: После нескольких эпох (проходов по всему датасету) мы сохраняем только веса адаптера.

В момент применения модели (инференса) в рабочей среде, система загружает тяжелую базовую модель, накладывает поверх нее ваш легковесный LoRA-адаптер, и они работают как единый механизм. Модель сохраняет свои глубокие знания о языке, но теперь безупречно выдает нужный вам JSON для медицинского ПО.

Сверточные нейросети (CNN) для анализа изображений

Сверточные нейросети (CNN) для анализа изображений

Если вы возьмете современную цветную фотографию размером 1000 на 1000 пикселей, внутри компьютера она будет представлена как массив из трех миллионов чисел (по миллиону на красный, зеленый и синий каналы). Попробуем передать эти данные в стандартный многослойный перцептрон (MLP), который мы разбирали ранее. Чтобы соединить каждое из 3 миллионов значений всего с одной тысячей нейронов первого скрытого слоя, модели потребуется 3 миллиарда весов. И это только для первого слоя.

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

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

Принцип скользящего окна и операция свертки

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

Именно так работает сверточная нейронная сеть (Convolutional Neural Network, CNN). Ее базовый элемент — не отдельный нейрон, привязанный к конкретному пикселю, а небольшой шаблон, который называется ядром (kernel) или фильтром.

Размер фильтра обычно невелик: 3×33 \times 3 или 5×55 \times 5 пикселей. Фильтр прикладывается к верхнему левому углу изображения. Происходит математическая операция свертки: значения пикселей изображения умножаются на соответствующие веса фильтра, а результаты складываются в одно итоговое число.

S=(p1×w1)+(p2×w2)++(p9×w9)S = (p_1 \times w_1) + (p_2 \times w_2) + \dots + (p_9 \times w_9)

Где SS — итоговая сумма для данного участка, pp — значения пикселей картинки, а ww — обучаемые веса нашего фильтра.

После вычисления фильтр сдвигается на один пиксель вправо, и операция повторяется. Когда ряд заканчивается, фильтр спускается ниже. Пройдя по всему изображению, фильтр создает новую матрицу чисел — карту признаков (Feature Map).

Что именно ищет фильтр?

Веса внутри фильтра (w1w9w_1 \dots w_9) — это не случайные числа. В процессе обучения через обратное распространение ошибки сеть настраивает их так, чтобы фильтр реагировал на определенные визуальные паттерны.

Допустим, веса в левом столбце фильтра 3×33 \times 3 положительные, а в правом — отрицательные. Когда такой фильтр наткнется на вертикальную границу (где слева светлые пиксели, а справа темные), математическая сумма умножений выдаст большое число. Фильтр «вспыхнет», сигнализируя: «Я нашел вертикальную линию!». Если приложить этот же фильтр к однотонному небу, сумма будет близка к нулю.

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

Экономия через общие веса

Ключевая магия свертки заключается в концепции общих весов (Parameter Sharing).

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

В CNN один и тот же фильтр (с одними и теми же 9 весами) сканирует всю картинку. Если фильтр научился находить глаз, он найдет его в любой точке изображения. Это дает модели свойство трансляционной инвариантности (независимости от сдвига) и колоссально сокращает количество параметров. Вместо миллиардов весов, слою из 64 фильтров размером 3×33 \times 3 требуется всего около 576 параметров.

Max Pooling: сжатие и обобщение

После применения свертки и функции активации (обычно ReLU, чтобы отсечь отрицательные значения), в дело вступает второй важнейший механизм CNN — субдискретизация (Pooling). Самый популярный ее вид — Max Pooling.

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

Окно Max Pooling (обычно размером 2×22 \times 2) проходит по карте признаков и из каждых четырех соседних чисел оставляет только одно — максимальное.

Преимущества Max Pooling Описание механизма
Снижение нагрузки Уменьшает размер карт признаков ровно в 2 раза по ширине и высоте, отсекая 75% данных.
Пространственная устойчивость Если объект на фото сдвинется на пару пикселей, максимальное значение в окне 2×22 \times 2 останется тем же. Сеть не сломается от легкой тряски камеры.
Расширение обзора Поскольку картинка сжимается, фильтры в следующих слоях начинают охватывать бóльшую площадь исходного изображения.

Архитектура: от пикселей к смыслам

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

  1. Свертка (Conv) — ищет паттерны.
  2. Активация (ReLU) — добавляет нелинейность.
  3. Max Pooling — сжимает данные и выделяет главное.

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

Финал: принятие решения

Сверточные слои — это идеальные экстракторы признаков. Но они не умеют принимать финальные решения (например, классифицировать картинку на 10 категорий).

Поэтому в самом конце архитектуры CNN происходит операция Flatten (выравнивание). Трехмерная стопка сжатых, высокоуровневых карт признаков просто вытягивается в один длинный одномерный вектор.

Этот вектор подается на вход классическому многослойному перцептрону (Fully Connected слоям), который мы изучали ранее. MLP смотрит на собранные признаки («вижу два пушистых уха, один кожаный нос, усов нет») и выносит вердикт через функцию Softmax: вероятность того, что это собака — 98%.

Мы объединили способность сверток понимать пространственную структуру с умением классических сетей делать логические выводы на основе набора фактов. Эта архитектура стала стандартом де-факто в компьютерном зрении. Однако обучать глубокие CNN с нуля на миллионах картинок — задача, требующая огромных серверных мощностей. На практике инженеры редко делают это сами, предпочитая брать готовые, уже натренированные фундаменты.

Классификация изображений на предобученной сети ResNet

Классификация изображений на предобученной сети ResNet

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

Сеть из 56 слоев работала значительно хуже, чем сеть из 20 слоев. И проблема была не в переобучении — модель ошибалась даже на тех данных, на которых тренировалась. Этот феномен назвали проблемой деградации.

Проблема деградации и гениальность ResNet

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

Решение, предложенное в архитектуре ResNet (Residual Network — остаточная нейронная сеть), оказалось поразительно изящным. Разработчики добавили «обходные пути», или skip connections (проброс связей).

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

Математически это выглядит так:

H(x)=F(x)+xH(x) = F(x) + x

Здесь xx — это входные данные (например, карта признаков от предыдущего слоя), F(x)F(x) — это изменения, которые вносят текущие сверточные слои (поиск новых паттернов), а H(x)H(x) — итоговый выход блока.

Если в процессе обучения нейросеть понимает, что текущий слой не приносит пользы и только портит сигнал, она просто обнуляет веса внутри F(x)F(x). Тогда формула превращается в H(x)=0+xH(x) = 0 + x. Сигнал xx проходит дальше в первозданном виде. Это позволило обучать сети глубиной в 50, 101 и даже 152 слоя, не боясь деградации.

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

ImageNet: почему вам не нужно учить ResNet с нуля

Обучение глубокой сети вроде ResNet-50 (где 50 — количество слоев) с нуля требует миллионов изображений, недель работы мощных видеокарт и огромных затрат на электричество. К счастью, для решения большинства рабочих задач это не требуется.

В компьютерном зрении стандартом де-факто стало использование моделей, предварительно обученных на ImageNet — гигантском наборе данных, содержащем более 1.2 миллиона фотографий, разбитых на 1000 классов (от пород собак до видов транспорта).

Сеть, обученная на ImageNet, уже сформировала отличный набор универсальных фильтров. Ее первые слои прекрасно реагируют на перепады света и тени, средние — на шерсть, металл или пластик, а глубокие — на глаза, колеса или лепестки. Нам остается лишь взять эти готовые «знания» и направить их на решение нашей специфической задачи. Этот подход называется трансферным обучением (Transfer Learning).

Анатомия трансферного обучения в компьютерном зрении

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

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

  1. Сверточная база (Feature Extractor): Все сверточные слои и слои пулинга. Эта часть извлекает признаки.
  2. Голова классификации (Classifier Head): Финальный полносвязный слой, который превращает признаки в вероятности классов.

У оригинального ResNet голова настроена на 1000 классов ImageNet. Для нашей задачи контроля плат мы делаем следующее:

Шаг Действие Физический смысл
1 Загрузка базы Берем предобученный ResNet.
2 Заморозка весов Отключаем обновление весов в сверточной базе (requires_grad = False). Мы запрещаем алгоритму менять фильтры, которые уже умеют искать линии и текстуры.
3 Замена головы Отрезаем старый полносвязный слой на 1000 выходов и ставим новый, чистый слой на 2 выхода (Норма и Дефект).
4 Обучение Запускаем цикл обучения на наших 500 фотографиях плат. Ошибка распространяется только на новую «голову».

В PyTorch замена головы выглядит как одна простая строчка кода. Например, для ResNet-50 на вход финального слоя поступает вектор из 2048 признаков, поэтому мы пишем: model.fc = nn.Linear(2048, 2).

Как это работает на практике

Когда фотография печатной платы попадает в такую сеть, она проходит через замороженные сверточные слои ResNet. Эти слои работают как идеальный сканер: они не учатся, а просто применяют свои знания, чтобы выдать плотный вектор признаков (например, «здесь есть блестящий металл», «здесь прерывается прямая линия»).

Затем этот вектор попадает в нашу новую, необученную полносвязную «голову». Именно она в процессе обучения на наших 500 картинках понимает: «Ага, если сканер говорит, что прямая линия прерывается, а рядом есть неровная текстура — значит, это класс "Дефект"».

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

Мы успешно адаптировали мощную архитектуру под узкую задачу. Но что делать, если для обучения удалось собрать не 500, а всего 50 фотографий дефектных деталей? Даже трансферное обучение может спасовать перед такой нехваткой данных. В следующем шаге мы разберем, как искусственно размножить имеющиеся изображения, чтобы модель не выучила их наизусть.

Аугментация данных: как увеличить датасет без сбора новых картинок

Аугментация данных: как увеличить датасет без сбора новых картинок

Представьте, что вы успешно заменили финальный слой в архитектуре ResNet, чтобы обучить модель находить ржавчину на трубах. Вы запускаете обучение, и метрики на тренировочной выборке быстро достигают идеальных 100%100\%. Но когда вы загружаете новые фотографии с завода, модель ошибается в половине случаев. Проблема банальна: у вас было всего 50 фотографий дефектов. Нейросеть просто вызубрила их наизусть — вплоть до расположения теней и угла наклона трубы на конкретном снимке.

Сбор и разметка новых данных — это всегда долго и дорого. Но что, если мы можем заставить нейросеть думать, что у нас не 50, а 5000 уникальных фотографий, вообще не выходя из-за компьютера?

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

Зачем нейросети инвариантность

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

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

Важно понимать: мы не создаем тысячи новых файлов и не сохраняем их на жесткий диск. Аугментация происходит «на лету» (on-the-fly) в оперативной памяти. В каждую новую эпоху обучения (когда модель заново просматривает весь датасет) алгоритм применяет к картинкам случайные преобразования. Таким образом, нейросеть буквально никогда не видит одну и ту же картинку дважды.

Основные виды преобразований

Все базовые методы аугментации можно разделить на две большие группы: пространственные и цветовые.

1. Пространственные (геометрические) искажения

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

  • Случайное отражение (Random Flip). Картинка зеркально отражается по горизонтали или вертикали с определенной вероятностью (обычно p=0.5p = 0.5).
  • Случайный поворот (Random Rotation). Изображение поворачивается на случайный угол в заданном диапазоне, например от 15-15^\circ до +15+15^\circ.
  • Случайная обрезка и масштабирование (Random Resized Crop). Алгоритм вырезает случайный прямоугольный фрагмент картинки и растягивает его до нужного размера. Это заставляет сеть узнавать объект по его частям (например, определять собаку только по лапам или хвосту), а не опираться на весь силуэт целиком.

2. Цветовые и оптические искажения

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

  • Изменение яркости и контрастности (Color Jitter). Имитирует разное время суток или разные настройки камеры.
  • Добавление шума (Gaussian Noise). Наложение случайной «ряби» на пиксели. Помогает модели не обращать внимания на артефакты сжатия или плохое качество сенсора камеры.
  • Размытие (Blur). Имитация расфокуса объектива.
Тип аугментации Пример из реальной задачи Что предотвращает
Горизонтальное отражение Распознавание пешеходов с камер умного города. Запоминание того, что люди идут только слева направо.
Случайный поворот Анализ спутниковых снимков лесов. Привязку к конкретному направлению севера на фото.
Изменение яркости Камеры беспилотного авто на рассвете и в полдень. Падение точности при изменении освещенности.

Золотое правило аугментации

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

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

Рассмотрим классические ошибки слепого применения аугментаций:

  1. Распознавание рукописных цифр. Если вы примените поворот на 180180^\circ к цифре «6», она визуально превратится в «9». Но в данных она по-прежнему будет подписана как «6». Модель получит противоречивый сигнал и качество обучения упадет.
  2. Распознавание дорожных знаков. Применение горизонтального отражения к знаку «Поворот налево запрещен» превратит его в знак «Поворот направо запрещен». Опять же, метка останется старой, и модель запутается.
  3. Медицинская диагностика. Сильное изменение цветового баланса (Color Jitter) на снимках МРТ или гистологии может привести к тому, что здоровая ткань визуально станет неотличима от некроза или опухоли.

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

Продвинутые методы: заставляем модель смотреть шире

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

Один из самых популярных методов — Cutout (или Random Erasing). Суть алгоритма в том, что на случайную область картинки накладывается черный (или заполненный случайным шумом) квадрат.

Зачем портить данные? Представьте, что вы учите модель распознавать лица, и на большинстве фото люди носят очки. Нейросеть может пойти по пути наименьшего сопротивления и научиться искать только оправу очков, игнорируя форму носа, глаз и губ. Если мы с помощью Cutout будем случайным образом «закрывать» черным квадратом глаза на некоторых фото, мы заставим нейросеть искать запасные признаки. Модель будет вынуждена обращать внимание на подбородок, прическу и форму ушей, чтобы сделать правильное предсказание. Это делает финальную модель гораздо более надежной (робастной) в реальных условиях.

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

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

Детекция объектов на примере архитектуры YOLO

Детекция объектов на примере архитектуры YOLO

Представьте, что вы разрабатываете систему автопилота для автомобиля. Камера передает кадр в нейросеть архитектуры ResNet, и та с уверенностью 99% выдает результат: «Пешеход». Отличная работа классификатора, но есть проблема — машина не знает, где именно находится пешеход, какого он размера и сколько пешеходов в кадре. Автопилоту не нужно просто знать факт наличия объекта, ему нужны точные координаты, чтобы вовремя нажать на тормоз.

Классификация изображений отвечает на вопрос «Что на картинке?». Детекция объектов решает задачу сложнее: «Где это находится и что это?».

От скользящего окна к единому взгляду

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

Революция произошла с появлением архитектуры YOLO (You Only Look Once — «Ты смотришь только один раз»).

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

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

Bounding Box: как нейросеть описывает границы

Чтобы указать, где находится объект, YOLO использует Bounding Box (ограничивающую рамку). Это прямоугольник, который максимально плотно облегает найденный объект.

Каждый Bounding Box описывается пятью числами:

  1. xx — координата центра рамки по горизонтали.
  2. yy — координата центра рамки по вертикали.
  3. ww — ширина рамки (width).
  4. hh — высота рамки (height).
  5. cc — уверенность модели (confidence score), что внутри этой рамки вообще есть какой-то осмысленный объект, а не просто фон.

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

Сетка YOLO: разделяй и властвуй

Как заставить одну нейросеть предсказывать координаты сразу нескольких объектов за один проход? YOLO решает это с помощью невидимой сетки.

Изображение условно делится на сетку, например, 7×77 \times 7 ячеек (всего 49 квадратов). Главное правило YOLO звучит так: если центр объекта попадает в определенную ячейку сетки, именно эта ячейка несет ответственность за его детекцию.

Представьте кадр с камеры дрона, который считает овец на ферме.

  • Изображение разбивается на сетку.
  • Каждая ячейка анализирует свой участок и пытается предсказать несколько Bounding Boxes.
  • Если овца стоит на границе ячеек, алгоритм смотрит, где находится её геометрический центр. Та ячейка, в которой оказался центр овцы, обязана выдать параметры x,y,w,hx, y, w, h и класс «Овца».
  • Ячейки, в которых центров объектов нет, предсказывают низкий параметр уверенности cc (близкий к нулю), и их предсказания просто игнорируются.
Характеристика Классификация (например, ResNet) Детекция (YOLO)
Вход Изображение Изображение
Выход Один вектор вероятностей классов Матрица с координатами рамок и классами
Цель Понять суть картинки в целом Найти все объекты и очертить их границы
Решаемая бизнес-задача Отбраковка фото по содержанию Подсчет товаров на полке, трекинг людей

Проблема дубликатов и метрика IoU

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

Чтобы алгоритм мог понять, говорят ли две рамки об одном и том же объекте, используется метрика IoU (Intersection over Union — пересечение поверх объединения).

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

IoU=Площадь пересеченияПлощадь объединения\text{IoU} = \frac{\text{Площадь пересечения}}{\text{Площадь объединения}}

  • Площадь пересечения — это зона, где две рамки наложились друг на друга.
  • Площадь объединения — это общая площадь, которую занимают обе рамки вместе.

Если две рамки идеально совпадают, их пересечение равно их объединению, и IoU=1.0\text{IoU} = 1.0. Если они вообще не соприкасаются, IoU=0.0\text{IoU} = 0.0. В индустрии принято считать, что если IoU0.5\text{IoU} \geq 0.5, то рамки указывают на один и тот же объект.

Non-Maximum Suppression (NMS): генеральная уборка

Имея метрику IoU, алгоритм YOLO применяет финальный фильтр, который называется Non-Maximum Suppression (NMS) — подавление немаксимумов. Это логический процесс, который оставляет только одну, самую точную рамку для каждого объекта.

Как работает NMS на практике:

  1. Алгоритм отбрасывает все рамки, где уверенность cc ниже заданного порога (например, удаляет всё, в чём сеть уверена меньше чем на 40%).
  2. Берет рамку с самой высокой уверенностью (например, 98%) и объявляет её «эталоном».
  3. Сравнивает этот эталон со всеми остальными рамками того же класса с помощью IoU.
  4. Если у какой-то рамки IoU\text{IoU} с эталоном больше 0.5 (то есть она сильно накладывается на эталон), алгоритм безжалостно её удаляет, считая дубликатом.
  5. Процесс повторяется для следующей рамки с наивысшей оставшейся уверенностью, пока не будут проверены все предсказания.

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

Архитектура YOLO объединила мощь сверточных признаков (о которых мы говорили ранее) с элегантной геометрической логикой сетки и фильтрацией NMS. Именно это сочетание позволило перенести искусственный интеллект из лабораторий на улицы: в камеры контроля скорости, системы безопасности на стройках и умные кассы самообслуживания.

Сохранение обученной модели и экспорт в формат ONNX

Сохранение обученной модели и экспорт в формат ONNX

Вы потратили часы на сбор данных, выбрали архитектуру, запустили обучение и, наконец, получили отличные метрики. Модель безошибочно находит бракованные детали на фотографиях с конвейера прямо в вашем Jupyter Notebook. Заказчик доволен и просит: «Отлично, загружай это в камеры на заводе, там стоит софт на C++». И тут возникает проблема: камеры не понимают язык Python, а библиотека PyTorch со всеми зависимостями весит несколько гигабайт. Как перенести ваш результат из тепличной среды разработки в суровую реальность продакшена?

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

Как PyTorch сохраняет модели: начинка без формы

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

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

import torch

# Сохраняем только веса (словарь состояний)
torch.save(model.state_dict(), 'model_weights.pth')

Файл model_weights.pth получается компактным, но у него есть критический недостаток. Чтобы загрузить эту модель обратно и использовать её, вам необходим исходный Python-код, описывающий архитектуру сети (тот самый класс, наследуемый от nn.Module).

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

# 1. Обязательно нужен код архитектуры
model = MyResNetArchitecture()

# 2. Только теперь загружаем веса в каркас (weights_only=True — стандарт безопасности)
model.load_state_dict(torch.load('model_weights.pth', weights_only=True))

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

Проблема продакшена: Python слишком тяжел

Среда, в которой модель обучается, и среда, в которой она применяется (совершает инференс), кардинально отличаются.

Характеристика Среда обучения (Jupyter, Colab) Среда применения (Продакшен)
Язык Python C++, Java, Swift, JavaScript, Go
Библиотеки PyTorch (занимает гигабайты памяти) Жёсткие ограничения по памяти
Оборудование Мощные видеокарты (GPU) Процессоры серверов, смартфоны, IoT-устройства
Приоритет Удобство экспериментов и отладки Максимальная скорость ответа и стабильность

Нам нужен способ отвязать обученную нейросеть от языка Python и библиотеки PyTorch, превратив её в самостоятельный, независимый файл.

ONNX: «PDF» для нейросетей

Решением этой проблемы стал формат ONNX (Open Neural Network Exchange).

Представьте, что вы написали красивый отчет в Microsoft Word. Если вы отправите файл .docx коллеге, у которого нет Word, или шрифты на его компьютере отличаются, документ «поедет» или вообще не откроется. Чтобы этого избежать, вы экспортируете отчет в формат .pdf. PDF выглядит абсолютно одинаково на любом устройстве и не требует установки тяжелого редактора — достаточно легкой программы для чтения.

ONNX — это PDF в мире машинного обучения. Это открытый стандарт, который сохраняет внутри одного файла и веса модели, и её вычислительный граф (последовательность всех математических операций).

Модель в формате .onnx больше не зависит от PyTorch. Её можно передать разработчикам, и они запустят её на сервере с C++, в браузере на JavaScript или в приложении на Android.

Как происходит экспорт: механизм трассировки

Чтобы сохранить вычислительный граф (архитектуру), PyTorch должен понять, как именно данные проходят через вашу сеть от входа к выходу. Поскольку Python — динамический язык, PyTorch использует подход, называемый трассировкой (tracing).

Трассировка работает гениально просто: вы берете обученную модель и пропускаете через неё фиктивный входной сигнал (dummy input). Это тензор, заполненный случайными числами, но имеющий строго ту же форму (размерность), которую модель ожидает в реальности.

PyTorch следит за этим фиктивным сигналом, как за GPS-маячком, и скрупулезно записывает каждый шаг: «Ага, здесь тензор умножился на матрицу, затем прошел через функцию активации ReLU, затем сжался через Max Pooling». Записанный маршрут и становится вычислительным графом, который упаковывается в ONNX-файл вместе с весами.

Вот как выглядит экспорт модели компьютерного зрения, которая ожидает на вход одну цветную картинку размером 1×3×224×2241 \times 3 \times 224 \times 224 (1 изображение, 3 цветовых канала, высота 224, ширина 224):

import torch

# 1. Переводим модель в режим инференса (отключаем Dropout и т.д.)
model.eval()

# 2. Создаем фиктивный вход нужной размерности
dummy_input = torch.randn(1, 3, 224, 224)

# 3. Экспортируем модель в формат ONNX
torch.onnx.export(
    model,                  # Ваша обученная модель
    dummy_input,            # Тот самый GPS-маячок
    "production_model.onnx",# Имя итогового файла
    export_params=True,     # Сохраняем веса вместе с графом
    input_names=['input'],  # Называем входной узел для удобства
    output_names=['output'] # Называем выходной узел
)

В результате на диске появляется файл production_model.onnx. Исходный Python-код архитектуры вам больше не нужен.

ONNX Runtime: запуск модели без PyTorch

Вы получили независимый файл, но как его запустить? Для чтения «PDF-документа» нужен PDF-ридер. В нашем случае это ONNX Runtime.

ONNX Runtime — это высокопроизводительный движок от Microsoft, созданный специально для запуска (инференса) моделей. Его главные преимущества:

  • Легковесность: он весит всего несколько десятков мегабайт (в отличие от гигабайтов PyTorch).
  • Кроссплатформенность: существуют версии ONNX Runtime для Python, C++, C#, Java и даже WebAssembly для работы прямо в браузере.
  • Оптимизация: движок автоматически анализирует граф операций и ускоряет вычисления под конкретное железо (процессор Intel, видеокарту NVIDIA или мобильный чип).

Теперь разработчику на сервере или в мобильном приложении достаточно подключить крошечную библиотеку ONNX Runtime, загрузить ваш файл production_model.onnx и передать в него данные. Модель стала полноценным, отчуждаемым программным продуктом.

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

Создание простого веб-интерфейса для модели на Streamlit

Создание простого веб-интерфейса для модели на Streamlit

У вас есть обученная нейросеть. В прошлой главе вы успешно экспортировали её в универсальный формат ONNX. Теперь модель отвязана от тяжеловесного фреймворка PyTorch и готова к работе. Но если вы отправите файл .onnx менеджеру, клиенту или врачу, они не будут знать, что с ним делать. Файл нельзя «открыть» и загрузить в него картинку.

Модели нужен пользовательский интерфейс (UI) — кнопка загрузки данных, экран ожидания и красивый вывод результата. Исторически это означало, что дата-саентисту нужно было либо просить помощи у команды frontend-разработчиков, либо самому с нуля учить HTML, CSS и JavaScript.

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

Парадигма Streamlit: интерфейс на чистом Python

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

Главная особенность Streamlit заключается в его модели выполнения. Она радикально отличается от классической веб-разработки.

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

В Streamlit скрипт выполняется сверху вниз при каждом взаимодействии.

Как только пользователь меняет значение ползунка, вводит текст или загружает файл, Streamlit перезапускает весь ваш Python-файл с первой до последней строки. Это делает код невероятно простым и линейным.

Характеристика Классический Web (React, Vue) Streamlit
Языки JavaScript/TypeScript, HTML, CSS Только Python
Логика работы Асинхронная, на основе событий (Event-driven) Линейная, сверху вниз (Top-to-bottom)
Обновление экрана Точечное (меняется только нужный блок) Перерисовка всего приложения при любом действии
Порог входа Высокий (нужно знать стек веб-технологий) Низкий (достаточно базового Python)

Базовые строительные блоки (Виджеты)

Streamlit перехватывает команды Python и на лету превращает их в красивые элементы интерфейса (виджеты).

Вывод текста и данных выглядит так:

import streamlit as st

# Заголовки и текст
st.title("Детектор дефектов на платах")
st.write("Загрузите снимок печатной платы, и ИИ проверит её на брак.")

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

# Ползунок (возвращает число)
threshold = st.slider("Порог уверенности модели", min_value=0.0, max_value=1.0, value=0.8)

# Загрузчик файлов (возвращает объект файла в памяти)
uploaded_file = st.file_uploader("Выберите изображение", type=["jpg", "png"])

Проблема перезапуска и кэширование моделей

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

Представьте, что ваша модель весит 500 МБ. Загрузка весов в оперативную память с помощью ONNX Runtime занимает 3 секунды. Если пользователь загрузит картинку, скрипт перезапустится, и модель будет загружаться заново. Если он сдвинет ползунок — скрипт снова перезапустится, и мы опять потеряем 3 секунды. Приложение станет невыносимо медленным.

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

Для этого используется декоратор @st.cache_resource. Декоратор — это специальная метка, которая ставится перед функцией и меняет её поведение.

import onnxruntime as ort

@st.cache_resource
def load_model():
    # Эта функция выполнится только один раз при первом запуске приложения
    session = ort.InferenceSession("model.onnx")
    return session

# При перезапусках скрипта сессия берется из кэша мгновенно
model_session = load_model()

Использование @st.cache_resource обязательно для любых глобальных объектов, таких как веса нейросетей, подключения к базам данных или сессии ONNX. Без этого ваше приложение не выдержит даже одного активного пользователя.

Собираем всё вместе: веб-приложение для инференса

Давайте объединим знания из предыдущей главы (ONNX) и текущей (Streamlit). Построим интерфейс для модели контроля качества печатных плат (Норма / Дефект).

Сценарий работы приложения:

  1. Загрузить ONNX-модель в кэш.
  2. Показать пользователю заголовок и кнопку загрузки фото.
  3. Если фото загружено — отобразить его на экране.
  4. Подготовить изображение (изменить размер до 224x224, превратить в массив чисел).
  5. Передать массив в модель и получить логиты.
  6. Вывести результат на экран крупным шрифтом.

Вот как выглядит полный код такого приложения (обычно его сохраняют в файл app.py):

import streamlit as st
import onnxruntime as ort
import numpy as np
from PIL import Image

# 1. Кэшируем загрузку модели
@st.cache_resource
def load_model():
    return ort.InferenceSession("pcb_defect_model.onnx")

session = load_model()

# 2. Интерфейс
st.title("Контроль качества печатных плат")
st.write("Загрузите фото платы для проверки.")

uploaded_file = st.file_uploader("Загрузить фото...", type=["jpg", "png", "jpeg"])

# 3. Логика обработки
if uploaded_file is not None:
    # Открываем изображение и показываем его пользователю
    image = Image.open(uploaded_file)
    st.image(image, caption="Загруженное изображение", use_container_width=True)

    # 4. Предобработка (подгоняем под формат, который ждет модель)
    # Изменяем размер до 224x224
    img_resized = image.resize((224, 224))
    # Превращаем в массив и нормализуем (пиксели от 0 до 1)
    img_array = np.array(img_resized).astype(np.float32) / 255.0
    # Меняем порядок осей для PyTorch/ONNX формата: (Каналы, Высота, Ширина)
    img_array = np.transpose(img_array, (2, 0, 1))
    # Добавляем размерность батча: (1, 3, 224, 224)
    input_tensor = np.expand_dims(img_array, axis=0)

    # 5. Инференс через ONNX
    input_name = session.get_inputs()[0].name
    outputs = session.run(None, {input_name: input_tensor})

    # Получаем сырые предсказания (логиты)
    logits = outputs[0][0]

    # Определяем класс (0 - Норма, 1 - Дефект)
    predicted_class = np.argmax(logits)

    # 6. Вывод результата
    st.divider() # Визуальная линия-разделитель
    if predicted_class == 0:
        st.success("✅ Плата в норме. Дефектов не обнаружено.")
    else:
        st.error("❌ ВНИМАНИЕ: Обнаружен дефект!")

Чтобы запустить это приложение, в терминале достаточно написать одну команду: streamlit run app.py

Streamlit локально поднимет веб-сервер и автоматически откроет новую вкладку в вашем браузере. Теперь у модели есть полноценный интерфейс, с которым может взаимодействовать любой человек.

Ограничения подхода

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

Однако это инструмент для взаимодействия «человек — машина».

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

Роботу, мобильному приложению или CRM-системе не нужны красивые кнопки и ползунки. Им нужен стандартизированный канал связи для обмена чистыми данными. Для решения таких задач модель необходимо обернуть в программный интерфейс (API).

Развертывание модели в виде API с помощью FastAPI

Развертывание модели в виде API с помощью FastAPI

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

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

Что такое API и на каком языке говорят машины

API (Application Programming Interface) — это контракт, по которому одна программа может попросить другую выполнить работу. Если представить, что ваша модель — это повар на кухне, то API — это официант. Клиентская программа (например, мобильное приложение) передает официанту заказ, тот относит его на кухню, дожидается блюда (предсказания) и возвращает его клиенту.

Универсальный язык, на котором общаются современные веб-сервисы — это JSON (JavaScript Object Notation). Он выглядит как обычный текстовый словарь с ключами и значениями.

Запрос к модели (Request) может выглядеть так:

{
  "age": 34,
  "balance": 12500.50,
  "is_active": true
}

А ответ от модели (Response) — так:

{
  "churn_probability": 0.12,
  "status": "loyal"
}

Чтобы создать такого «официанта» для нашей модели на Python, индустрия использует фреймворк FastAPI. Он работает асинхронно (может обрабатывать тысячи запросов одновременно) и автоматически проверяет правильность входящих данных.

Шаг 1: Контракт данных с Pydantic

Прежде чем передавать данные в модель, мы должны убедиться, что они корректны. Если CRM-система по ошибке пришлет возраст клиента в виде текста "тридцать", математика внутри ONNX-графа сломается.

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

from pydantic import BaseModel

# Описание входящего запроса
class CustomerData(BaseModel):
    age: int
    balance: float
    is_active: bool

# Описание ответа
class PredictionResult(BaseModel):
    churn_probability: float
    status: str

Если кто-то отправит в API строку вместо числа, FastAPI даже не станет будить модель — он мгновенно вернет клиенту ошибку с указанием, в каком конкретно поле допущена опечатка.

Шаг 2: Создание приложения и загрузка модели

Создадим файл main.py. Нам нужно инициализировать приложение и загрузить нашу ONNX-модель в память.

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

from fastapi import FastAPI
import onnxruntime as ort
import numpy as np

app = FastAPI(title="Модель оттока клиентов")

# Загружаем веса в память при старте
session = ort.InferenceSession("churn_model.onnx")
input_name = session.get_inputs()[0].name

Шаг 3: Эндпоинт для предсказаний

Эндпоинт — это конкретный URL-адрес (точка входа), к которому обращаются другие программы.

В вебе существуют разные типы запросов. Когда вы открываете страницу в браузере, это запрос GET (дай мне информацию). Но когда мы отправляем данные для обработки, используется метод POST.

Создадим эндпоинт /predict, который будет принимать данные клиента, превращать их в массив NumPy, прогонять через модель и возвращать результат.

@app.post("/predict", response_model=PredictionResult)
def predict_churn(data: CustomerData):
    # 1. Превращаем булево значение в число (True -> 1.0, False -> 0.0)
    active_flag = 1.0 if data.is_active else 0.0

    # 2. Собираем признаки в вектор нужной формы (1 строка, 3 колонки)
    input_vector = np.array([[data.age, data.balance, active_flag]], dtype=np.float32)

    # 3. Делаем инференс через ONNX
    outputs = session.run(None, {input_name: input_vector})

    # 4. Достаем вероятность из сырого ответа (например, 0.85)
    probability = float(outputs[0][0][0])

    # 5. Формируем бизнес-логику
    client_status = "churn_risk" if probability > 0.5 else "loyal"

    # 6. Возвращаем ответ строго по контракту Pydantic
    return PredictionResult(
        churn_probability=probability,
        status=client_status
    )

Декоратор @app.post("/predict") связывает функцию Python с сетевым адресом. Аргумент data: CustomerData говорит фреймворку: «жди JSON, который совпадает с нашим классом, и автоматически преврати его в объект Python».

Запуск сервера и автоматическая документация

Сам по себе код FastAPI — это просто набор правил. Чтобы он начал слушать сетевые запросы, нужен веб-сервер. В экосистеме Python для этого используется uvicorn.

Запуск происходит из командной строки: uvicorn main:app --host 0.0.0.0 --port 8000

Теперь наш сервис работает по адресу http://localhost:8000. Любая программа в сети может отправить POST-запрос на /predict и получить предсказание.

Но как разработчикам других систем (например, создателям мобильного приложения) узнать, какие данные ожидает ваша модель? В классической разработке пришлось бы писать длинную документацию вручную. FastAPI генерирует её сам. Если открыть в браузере адрес http://localhost:8000/docs, вы увидите Swagger UI — интерактивную веб-страницу. На ней визуально представлены все эндпоинты, форматы JSON, типы данных. Более того, прямо на этой странице есть кнопка «Try it out», позволяющая отправить тестовый запрос в модель без написания единой строчки кода.

Обернув модель в API, мы сделали её независимым микросервисом. Ей больше не важно, кто к ней обращается — скрипт на Python, сервер на Java или умные часы. Главное, чтобы запрос пришел в правильном JSON-формате.

Мониторинг работы модели и обновление данных в продакшене

Мониторинг работы модели и обновление данных в продакшене

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

Код API не менялся. Версии библиотек заморожены. Сервер не падал. Что сломалось?

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

В этой финальной главе мы разберем, как вовремя заметить деградацию модели и как безопасно обновить ее в рабочей среде (продакшене).

Почему модели «протухают»: два типа смещений

В машинном обучении процесс деградации предсказательной способности из-за изменений во внешнем мире называется смещением (Drift). Выделяют два основных вида.

Тип проблемы Суть Пример из жизни
Data Drift (Смещение данных) Изменились сами входные данные. Модель никогда не видела таких примеров при обучении. Вы обучили сверточную сеть распознавать дефекты на платах при ярком свете. На заводе перегорела половина ламп, и на API стали приходить темные снимки.
Concept Drift (Смещение концепции) Данные остались теми же, но изменился их смысл или правила игры в реальном мире. В 2020 году доход в 100 000 рублей означал высокую покупательную способность (класс 1). Из-за инфляции в 2026 году та же сумма означает среднюю способность (класс 0).

Главный парадокс продакшена: модель может выдавать технически идеальный ответ (JSON формируется без ошибок), который при этом абсолютно неверен по смыслу.

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

Самая большая сложность мониторинга заключается в задержке обратной связи. Когда API выдает предсказание (например, вероятность оттока клиента), мы не можем сразу посчитать метрики вроде Accuracy или F1-score. Почему? Потому что мы узнаем, ушел клиент или нет, только в конце месяца.

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

1. Мониторинг в реальном времени (прокси-метрики)

Поскольку истинных ответов у нас пока нет, мы следим за поведением самой модели и входящим потоком данных.

  • Распределение входных данных: Мы логируем все входящие JSON-запросы и сравниваем их статистику с обучающей выборкой. Если средний возраст клиента при обучении был 35 лет, а сегодня API массово получает запросы с возрастом 18 лет — это сигнал Data Drift.
  • Распределение предсказаний: Если исторически ваша модель предсказывала статус «Уйдет» в 15% случаев, а сегодня этот показатель подскочил до 60%, это повод бить тревогу.
  • Уверенность модели (Confidence Score): Мы анализируем сырые вероятности после слоя Softmax. Здоровая модель обычно уверена в своих ответах (вероятности вроде 0.95 или 0.05). Если модель начинает массово выдавать вероятности в районе 0.51 или 0.49, она «сомневается». Это значит, что новые данные ложатся на границу классов.

2. Замыкание петли обратной связи (Feedback Loop)

Чтобы посчитать реальное качество, нам нужно собрать истинные метки (Ground Truth) и сопоставить их с теми предсказаниями, которые API выдавал в прошлом.

Существует два пути сбора разметки в продакшене:

  1. Явная обратная связь: Пользователь сам говорит, права ли нейросеть. Например, кнопка «Пожаловаться на спам» в почте или интерфейс для оператора колл-центра, где он может исправить неверно заполненную моделью карточку.
  2. Неявная обратная связь: Мы отслеживаем бизнес-события. Если рекомендательная система посоветовала товар, а пользователь его купил — предсказание было верным.

Как только мы накопили достаточное количество реальных ответов, мы вычисляем метрики. Если, например, Accuracy<0.85\text{Accuracy} < 0.85 (падает ниже допустимого бизнес-порога), запускается процесс переобучения.

Безопасное обновление: как выкатить новую модель

Допустим, вы собрали новые данные за последние три месяца, добавили их к старому датасету, заново запустили процесс тонкой настройки (Fine-tuning) и экспортировали новый файл model_v2.onnx.

Как заменить старую модель на новую? Просто перезаписать файл на сервере — худшая идея. Если новая модель содержит скрытый баг, вы сломаете бизнес-процесс для всех пользователей. В инженерии машинного обучения (MLOps) используют безопасные стратегии развертывания.

Теневой режим (Shadow Mode)

Это самый надежный способ проверить новую модель. Обе модели — старая (v1) и новая (v2) — работают на сервере одновременно. Когда от клиента приходит запрос, API отправляет данные в обе модели. Однако пользователю возвращается только ответ от старой проверенной v1. Ответ от v2 просто тихо записывается в базу данных.

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

Канареечный релиз (Canary Release)

Если теневой режим слишком дорог (например, инференс большой языковой модели требует много GPU-ресурсов), используют «канареечный» подход.

Вы направляете 90% пользовательского трафика на старую модель, а 10% случайных запросов — на новую. Если метрики на этих 10% не проседают, ошибки не сыплются, а пользователи не жалуются, вы постепенно увеличиваете долю: 25%, 50%, 100%. Старая модель отключается.

Финал: цикл замыкается

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

Теперь вы знаете главное: создание ИИ — это не линейный процесс от точки А до точки Б. Это бесконечный цикл. Данные меняются → модель деградирует → мониторинг бьет тревогу → вы собираете новые данные → дообучаете модель → выкатываете ее в теневом режиме → и всё начинается заново.

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