ІнфраструктураSenior

ML-пайплайни

ML-пайплайн — це автоматизований шлях від сирих даних до моделі, яку можна розгорнути. Натренована модель — це артефакт збірки, а не код. Вона постає з чотирьох входів — даних, тренувального коду, конфігурації та середовища, — і лише один із них лежить у вашому репозиторії. Саме тому клонування репозиторію й запуск скрипта дає вам іншу модель. Пайплайн існує, щоб усі чотири були записані, повторювані й доступні для перевірки. На цій сторінці — стадії та чому вони окремі, чому дані ламаються тихо, а код гучно, що таке skew, який робить чудову офлайн-модель марною онлайн, і коли ноутбука справді достатньо.

Модель — це
Артефакт, а не код
Входів фіксувати
Чотири, а не один
Ламається тихо
Завжди дані

Модель — це артефакт збірки, а не код

Під інженерією програмного забезпечення лежить зручне припущення: репозиторій — це істина, і кожен, хто його викачає й зібʼє, отримає ту саму програму. Машинне навчання це припущення тихо ламає. У вашому репозиторії лежить тренувальний код, але модель постала з цього коду плюс саме тих рядків даних, які він побачив, плюс гіперпараметрів, які хтось передав у командному рядку, плюс версій бібліотек і драйверів на машині, що це запустила. Змініть будь-що з цих чотирьох — і отримаєте іншу модель: зазвичай трохи іншу, іноді дуже.

Натренована модель — це артефакт збірки, а не код, і вона постає з чотирьох входів: саме тих рядків даних, що були використані, коміту тренувального коду, конфігурації з гіперпараметрами та середовища з версіями бібліотек і драйверів. Лише другий із них живе у вашому репозиторії — тому клонування репозиторію й запуск скрипта тренування не відтворює модель. Пайплайн існує саме для того, щоб усі чотири були записані проти одного запуску, а артефакт, який він видає, ніс хеш вмісту й свої оцінки. Реєстр стоїть після артефакта, а не поруч із ним, бо він є передачею у сервінг: тримає версію, походження назад до тих чотирьох входів і запис про те, яка версія де розгорнута.

Це не теоретичне занепокоєння, і воно приходить як цілком конкретний поганий день. Модель у проді починає поводитися дивно. Ви хочете порівняти її з попередньою, тож шукаєте, як зібрали попередню, — і знаходите ноутбук, коміт чотиримісячної давності й таблицю, у яку відтоді писали щодня. Перезібрати стару модель неможливо, тож і сказати, що змінилося, неможливо. Саме цей день і відвертає пайплайн, і це дешевша причина його побудувати, ніж будь-який аргумент про автоматизацію.

Стадії — і чому вони окремі

Пайплайн — це орієнтований граф кроків, де кожен крок читає названі входи й пише названі виходи. Розділення тут не заради охайності. Воно дозволяє кешувати крок замість перераховувати, дозволяє збою назвати себе й дозволяє порівнювати два запуски крок за кроком. Шість стадій покривають майже кожен тренувальний пайплайн у проді, і кожна існує тому, що щось ламається, коли її зливають із сусідньою.

  • 1 · Забір

    Забрати сирі дані й заморозити їх як адресований зріз — партиція, версія таблиці, маніфест хешів файлів. «Учорашній запит» — це не вхід; ідентифікатор зрізу — вхід.

  • 2 · Валідація

    Перевірити зріз проти очікуваної схеми й очікуваних розподілів, перш ніж на нього щось витрачати. Цю стадію пропускають — і саме вона зловила б інцидент.

  • 3 · Ознаки

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

  • 4 · Тренування

    Дорогий крок — і найменш цікавий. Він читає файл конфігурації, а не прапорці командного рядка, і пише артефакт плюс метадані запуску, що називають його чотири входи.

  • 5 · Оцінка

    Оцінити кандидата на відкладеній вибірці І проти моделі, що зараз у проді. Число, з яким нема чого порівняти, ще ніколи не спинило поганого релізу.

  • 6 · Реєстрація

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

Дві речі про цей граф варто сказати прямо. Перше: оркестратор — найменш важливий вибір на цій сторінці. Airflow, Kubeflow, Dagster, Metaflow, Prefect або Makefile за вашим наявним CI виражають той самий граф, і жоден із них не полагодить неадресований вхід даних. Друге: кроки мають бути звичайним кодом, що виконується на вашому ноутбуці з малим зрізом. Пайплайн, який можна запустити лише пушем у кластер, — це пайплайн, який ніхто не ітеруватиме, і ціна цього приходить у вигляді повернення людей до ноутбука.

Код ламається гучно, дані — тихо

Коли ламається код, щось кидає виняток і збірка стає червоною. Коли ламаються дані, все успішно. Команда вище перейменувала колонку — і ваше зʼєднання дає порожні значення; поле валюти перейшло з копійок на гривні — і ознака тихо ділиться на сто; зміна в логуванні перестала слати подію — і категорія зникла з тренувальної вибірки. У кожному з цих випадків пайплайн зелений, модель тренується, метрики трохи рухаються, і ніхто не дивиться — бо ніщо не впало.

Ліки — зробити збої даних гучними, тобто записати очікування й дозволити пайплайну відмовлятися. Перевірки, які варто мати, нудні й нечисленні:

  • Схема: колонки існують, із тими типами й допустимістю порожніх значень, які ви очікуєте. Дешево — і ловить перейменування.
  • Обсяг: кількість рядків у діапазоні недавніх запусків. Зріз на 3% від учорашнього означає, що впала задача вище за течією, а не що попит обвалився.
  • Діапазони й категорії: числові поля в межах правдоподібного, категорійні — з відомої множини. Ловить зміну одиниць і нове значення переліку.
  • Пропуски: частка порожніх значень на колонку проти попередніх запусків, бо колонка, що тихо стає на 90% порожньою, — класичний тихий злам.
  • Дрейф розподілу проти тренувального зрізу — спершу як звіт, а не як заборона: жорсткий барʼєр тут спрацює на «чорну пʼятницю» і буде вимкнений до березня.
  • Витік: жодної ознаки, порахованої з інформації, якої немає на момент передбачення. Найдорожча для пізнього виявлення й найважча для автоматизації.

Skew між тренуванням і сервінгом — і навіщо сховище ознак

Ось найпоширеніша причина, чому модель із хорошими офлайн-оцінками погано працює онлайн, — і це взагалі не задача моделювання. Тренувальні ознаки рахуються в одному місці, у пакетній задачі, на Python, по всій таблиці. Сервінгові ознаки рахуються в іншому місці, на кожен запит, часто іншою мовою, по одному рядку. Дві реалізації однієї ідеї розходяться: інше правило округлення, інше значення за замовчуванням для пропуску, вікно, що означає останні 30 днів в одній і останні 30 календарних днів в іншій. Модель тоді бачить під час сервінгу входи, яких ніколи не бачила під час тренування, і деградує так, що жодне перетренування цього не полагодить.

У чому різницяБік тренуванняБік сервінгу
Форма роботиОдна пакетна задача на мільйони рядків, хвилини — нормально.Один рядок на запит, у межах кількох мілісекунд.
Момент часуМає реконструювати, що було відомо тоді, — зʼєднання на момент часу.Читає те, що відомо зараз, — це простий випадок.
Хто це пишеЗазвичай дата-саєнтист, у тренувальному репозиторії.Зазвичай бекенд-інженер, у сервісі. Ось де тріщина.
Як це ламаєтьсяНіяк — офлайн-оцінка лишається чудовою.Тихо — передбаченнями, гіршими за обіцяне тестовою вибіркою.

Сховище ознак — це відповідь саме на це й ні на що інше, і це варто сказати, бо продають його як значно більше. Його справжня робота: ознака визначена один раз і подається двома способами — офлайн-сховище, що вміє відповісти «яким було це значення станом на ту мітку часу» для побудови тренувальних вибірок, і онлайн-сховище, що відповідає «яке це значення зараз» за одноцифрові мілісекунди. Купувати продукт необовʼязково; дисципліна — обовʼязкова. Спільна бібліотека, яку викликають обидва боки, з одним тестом, що стверджує однакове значення офлайн і онлайн для тієї самої сутності й часу, дає більшість користі за частку експлуатаційної ваги.

Реєстр — і чому зареєструвати не означає розгорнути

Остання стадія пише до реєстру моделей, і корисно думати про нього як про репозиторій артефактів, що випадково знає про моделі. Він зберігає бінарник під версією разом із метриками, які той набрав, чотирма входами, що його породили, і записом про те, яке середовище зараз запускає яку версію. Саме це останнє поле робить відкат зміною конфігурації, а не задачею перетренування.

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

  • Версіонуйте артефакт і ніколи не перезаписуйте. model-latest.pkl у бакеті — це не реєстр, і через нього кожен інцидент лишається без відповіді.
  • Зберігайте оцінку поруч з артефактом, включно з тим, на якому наборі її отримано. Метрика без свого набору — не метрика.
  • Записуйте походження ідентифікаторами, а не текстом: ідентифікатор зрізу, sha коміту, хеш конфігурації, дайджест образу.
  • Робіть підвищення явним переходом із назвою, щоб «яка модель зараз жива» було запитом, а не археологією.

Перетренування — це розклад, а не аварія

Моделі псуються, бо світ рухається, а не тому, що ваги гниють. Ціни змінюються, конкурент запускається, приходить новий сегмент клієнтів — і залежність, яку модель вивчила пів року тому, більше не є залежністю в даних. Зріла відповідь — запланований запуск перетренування, результат якого оцінюють проти чинної моделі й підвищують, лише якщо він виграє. Це перетворює деградацію з інциденту на рутину, що здебільшого дає результат «без змін». Незріла відповідь — перетреновувати, коли хтось помітить, що щось не так; тоді пайплайн вправляється рідко й через це падає саме тоді, коли потрібен.

Як часто — це питання бізнесу з технічним тестом. Візьміть модель із проду, натренуйте її на даних, що закінчуються місяць тому, і оцініть обидві на останньому місяці. Якщо свіжа модель виразно виграє, ваші дані рухаються швидко й місяць — це нижня межа; якщо дві нерозрізненні, ви перетреновуєте частіше, ніж це виправдано світом, і платите за це обчисленнями й часом на ревʼю. Зробіть цей вимір один раз — і він закриє суперечку, яка інакше триває рік.

Антипатерни, які варто назвати

  • Ноутбук, що став продакшеном: клітинки виконані в порядку, який ніхто не записав, проти таблиці, яка відтоді змінилася.
  • Дані, адресовані як «таблиця», а не як зріз: той самий код за задумом дає щодня іншу модель.
  • Платформа, збудована до появи другої моделі. Одній моделі не потрібен оркестратор — потрібні скрипт і зафіксоване середовище.
  • Код ознак, продубльований між тренуванням і сервінгом, без жодного тесту, що стверджує їхню згоду.
  • Кандидат, підвищений тому, що його оцінка побила минулотижневу, — і жодного разу не оцінений проти моделі, яка справді працює.
  • Перетренування, запущене самим лише сигналом дрейфу: сезонний тиждень переписує модель, з якою все було гаразд.

Коли це застосовувати

Застосовуйте, коли

  • Модель, чиї передбачення доходять до справжніх користувачів, де питання «яка версія це дала і з яких даних» зрештою поставить хтось інший, не ви.
  • Дані, що змінюються за розкладом: тренування стає повторюваною задачею, а не одноразовим дослідженням.
  • Більше ніж одна людина торкається тренувального коду: незаписаний крок — це крок, який знає лише хтось один.
  • Будь-який контекст, де регулятор, аудитор чи клієнт може спитати, як було вироблено рішення щодо нього.

Уникайте, коли

  • Разовий аналіз, чий результат — слайд, а не сервіс: зафіксуйте середовище, збережіть ноутбук і йдіть далі.
  • Одна модель, яку двічі на рік перетреновує одна людина: оркестратор додає систему для підтримки й нічого не прибирає.
  • Продукти цілком на сторонньому API моделі, де немає тренувального запуску, який відтворювати, — дивіться натомість LLMOps.
  • Як заміну рішенню про власність над даними: пайплайн над джерелом, яке ніхто не підтримує, автоматизує проблему, а не розвʼязує її.

Було корисно?

Поділіться з тим, хто працює над тією ж задачею.