Гра у волейбол з AI-агентами для прогнозування обороту ресторану
Хакатон з підвищення обороту ресторану, створений за допомогою AI-агентів із людським керуванням: аудит даних, перевірка з урахуванням особливостей, інженерія ознак, об’єднання моделей та відставання від результатів лідерборду.
Уявіть собі хакатон, у якому сама модель є лише половиною роботи. Інша половина полягає у тому, щоб зайняти кілька штучних інтелектуальних агентів роботою над конкуруючими ідеями, не дозволяючи жодному з них приймати рішення, яке належить людині. У цій статті описується одна з таких спроб – хакатон з прогнозування обороту в ресторанах, організований компанією Great Learning у співпраці з UT Austin та її бізнес-школою McCombs. У ній розглядається ефективний процес роботи з машинним навчанням, сувора перевірка даних та ансамбль моделей, який, за словами самого проекту, добре показав себе у змаганні.
Шар оркестрації складався з фреймворку під назвою DontPanic. Його процеси виконання завдань слідують логіці сутички у волейболі: агент отримує завдання, планує наступний крок, передає роботу спеціалізованим елементам та нарешті передає результат, проте м’яч завжди повертається до людини, яка приймає рішення. Агенти прискорюють виконання, але не мають права на прийняття рішень.
1. Завдання
Учасники мусили спрогнозувати річний оборот ресторанів по всій Індії на основі 3,493 позначених записів для навчання та 500 безпозначених записів для тестування. Існувало приблизно 32 фактори, які впливали на прогнозування: місцезнаходження, кухня, формат ресторану, зручності, популярність у соціальних мережах та оцінки якості обслуговування клієнтів. Під „оборотом“ тут маються на увазі щорічні продажі, а не втрата персоналу. Метрикою був RMSE – він квадрує похибки перед усередненням, тож кілька значних помилок можуть суттєво вплинути на результат.
Метою не було створення найскладнішої моделі. Йшлося про систему прогнозування, яку можна було б обґрунтувати: перевірити дані, зрозуміти економіку сфери, створити сигнали, які мають сенс з бізнесової точки зору, підтвердити ефективність кандидатських моделей за допомогою стабільного крос-підтвердження, відкинути ті покращення, які не давали стабільних результатів, та нарешті відокремити справжню здатність моделі до узагальнення від налаштувань, специфічних для конкретного змагання.
2. Інструменти
Усе знаходилось у VS Code у вигляді модульного Python-пакету під директорією src/, замість купи непов’язаних експериментів у форматі ноутбуків. Цей вибір виявився корисним, оскільки очищення даних, створення функцій, їх перевірка та аналіз відбувалися через один і той самий повторно використовуваний код. Таким чином функція не могла поводитися по-різному під час навчання та під час генерації кінцевого результату.
3. Оркестрація агентів
DontPanic був механізмом, який організовував роботу проекту. Повна автономія ніколи не була метою. Метою було дозволити кільком агентам швидко працювати, водночас чітко визначивши структуру прийняття рішень: хто досліджує, хто створює, хто кидає виклик, який результат є авторитетним та коли роботу потрібно повернути людині для підтвердження чи відхилення.
Це має значення, тому що експериментування з штучним інтелектом пропонує один за одним правдоподібні наступні кроки. Агент, який діє самостійно, буде продовжувати оптимізувати той сигнал, який бачив останнім часом, а людина може втратити контроль над тим, чи змінилися гіпотеза, протокол підтвердження чи критерії оцінки. Уявіть DontPanic як правила, за якими проходив експеримент: агенти мали свободу продовжувати дії, але не могли переписувати правила без того, щоб хтось це помітив.
Кожен період роботи агента описувався маніфестом. Один з прикладів — для роботи Claude CLI — вказує команди та файли, які потрібні, як перевіряється готовність, які частини належать фреймворку, що має робити оператор та які дії заборонені:
{
"schema_version": "1.0.0",
"id": "agent-claude-cli",
"title": "Claude CLI agent runtime",
"kind": "agent_cli",
"category": "agent-cli",
"summary": "External agent CLI used by DontPanic as an implementer, auditor, or goal-auditor runtime.",
"setup_required": true,
"setup_doc": "docs/GETTING_STARTED.md#setup-tracks--pick-yours",
"default_in_profiles": ["core"],
"use_cases": ["U1", "U3", "U4", "U5", "U6", "U7"],
"requires": {
"commands": ["claude"],
"env": [],
"files": ["~/.dontpanic/config.json"],
"services": ["Claude account / Claude Code or CLI runtime"],
"auth": ["Claude CLI authenticated by operator"],
"config": [
"roles.implementer",
"roles.auditor",
"roles.goal_auditor"
]
},
"verify": {
"doctor_profiles": ["core"],
"probes": ["cli:claude", "agent_role_config"],
"readiness": "Ready when the claude command is present and the selected DontPanic role points at claude."
},
"owner_boundary": {
"dontpanic_core": [
"agent registry entry",
"dispatch envelope",
"audit/signoff capture",
"quota and breaker handling",
"role configuration"
],
"adapter": [
"subprocess executor for claude",
"stream/output parsing",
"timeout and envelope normalization"
],
"operator": [
"Claude CLI installation",
"account authentication",
"model/quota management",
"local CLI updates"
]
},
"mutation_boundary": {
"writes_dontpanic_state": true,
"external_mutation": "agent_subprocess_execution",
"allowed_path": "DontPanic supervisor invokes claude through the governed executor and captures artifacts through audit/signoff writers.",
"forbidden": [
"Bypassing supervisor gates",
"Untracked agent writes outside declared plan scope",
"Treating Claude account configuration as DontPanic-owned state"
]
},
"notes": [
"Claude is an external runtime, not bundled by DontPanic.",
"Core profile may also support other agent CLIs; this manifest records the Claude-specific path."
],
"setup_steps": [
{
"id": "install_claude_cli",
"what": "Install the Claude CLI so DontPanic can spawn agent runs through the governed executor.",
"automatable": true,
"command_template": "npm install -g @anthropic-ai/claude-cli",
"verify_probe": "cli:claude",
"human_required_reason": null
},
{
"id": "authenticate_claude",
"what": "Authenticate the Claude CLI against the operator's Claude account or API key.",
"automatable": false,
"command_template": "claude auth login --token <YOUR_CLAUDE_API_TOKEN>",
"verify_probe": "agent_role_config",
"human_required_reason": "Operator must approve scopes for their Claude account and treat the API token as a secret; DontPanic does not provision Claude credentials on the operator's behalf."
}
]
}
3.1 Ралі у п’яти кроках
- Надання: ще до початку будь-якого експерименту людина сформулювала бізнес-запитання, обмеження, метрики, критерії прийняття та поточного „чемпіона“.
- Отримання: агент виявлення перевірив дані, виокремив аномалії та запропонував гіпотези, які можна було перевірити, замість того щоб одразу переписувати модель.
- Встановлення: після схвалення гіпотези агент реалізації записав її у вигляді модульного коду, детерміністичних артефактів та визначення експерименту, який можна було повторно запустити.
- Перевірка: агент-рецензент перевіряв на наявність витоків інформації, проблем у крос-підтвердженні, нестабільних значень та незрозумілого походження даних, а також запитував, чи зберігається очевидна перевага після порівняння з „замороженим чемпіоном“.
Справжньою перевагою був контрольований паралелізм, а не проста швидкість програмування. Різні агенти могли зосереджуватися на різних способах виникнення проблем, тоді як людина забезпечувала дотримання початкової мети продукту, його бізнес-цілеспрямованості та можливості відповідальності. Це нагадувало роботу невеликої команди над продуктом, а не сесію з чат-ботом.
3.2 Ефективна реалізація ML за принципами Lean
Робочий процес агентів також було прив’язано до ефективного життєвого циклу продукту з метою здешевлення експериментів на етапі розробки, ускладнення підробки даних на етапі тестування та запобігання випадковим змінам у продакшен-середовищі. Розділення на DEV/UAT/PROD ставало все ціннішим у міру збільшення кількості експериментів. Новий агент міг приєднатися, не покладаючись на пам’ять про розмову, оскільки авторитетні дані зберігалися у вигляді файлів, маніфестів, прогнозів поза структурою даних, реєстру лідерів та записаних рішень щодо просування. Зразок, який варто зберегти: нехай агенти забезпечують швидкість, а нехай артефакти та контрольні точки забезпечують достовірність.
4. Перевірка даних перед тим, як їм вірити
Перед моделюванням файли навчання та тестування були проаналізовані за допомогою алгоритму SHA-256 та перевірені стовпець за стовпцем. Це не була бюрократія – кілька особливостей могли змінити простір ознак під час виконання прогнозувань та завдати шкоди результатам, не створюючи жодних помітних помилок.
- Набір даних для навчання містив 3 493 рядки та 34 стовпці, тоді як набір для тестування – 500 рядків та 33 стовпці, оскільки в ньому не було метричних значень.
- У стовпцях
AmbienceтаRestaurant Tierу наборах даних для навчання та тестування були різні типи даних, тому для того, щоб моделі отримували ідентичні ознаки, знадобилась пряма конвертація даних. - У наборі даних для навчання один стовпець мав назву
Endorsed By, а у наборі для тестування –Endoresed By. Без відповідного мапування процес обробки даних міг призвести до того, що певна ознака у тестових даних взагалі б не з’явилась.
Номер реєстрації виконував функцію ідентифікатора, а не бізнес-прогнозника (Pearson приблизно -0,017, Spearman приблизно -0,022). Його не включали до моделей, а зберігали лише для поєднання рядків наприкінці.5. Дослідження даних
5.1 Сильно відхилене у правий бік цільове значення
Річний оборот мав довгий правий хвіст. 90-й перцентиль становив близько ₹48 млн, 95-й — близько ₹57 млн, 99-й — близько ₹100 млн, а максимум досягав ₹400 млн; коефіцієнт викривлення становив близько 6,65. Після застосування трансформації log1p розподіл став майже симетричним, і було порівняно первинні та скориговані значення, а також параметри моделей Yeo-Johnson; пізніше була протестована версія з урахуванням площі за квадратний фут. З точки зору бізнесу, більшість ресторанів належать до досить вузького діапазону доходів, тоді як невелика група дуже великих закладів спричиняє значні фінансові помилки, коли їх не враховують.
Цей хвіст був незвичним не лише з візуальної точки зору. Він значно впливав на розмір збитків. Аналіз помилок показав, що невелика кількість ресторанів з найгіршими прогнозами становила значну частину загальної квадратичної помилки, тому діагностика хвоста стала ключовою частиною оцінки.
5.2 Для відсутніх даних потрібно більше ніж одне правило
Відсутні значення не розглядалися як єдине явище. Деякі відображали звичайні проблеми з якістю даних. Інші відображали структуру бізнесу – наприклад, порожній рейтинг розваг через відсутність таких послуг у закладі. Єдине правило імпутації для обох випадків знищило б корисну інформацію.
Звичайна відсутність даних, така як популярність у соціальних мережах, загальний рейтинг, атмосфера та категорія, оброблялась за допомогою імпутації на основі навчальних даних та чітких позначок відсутності. Це дозволяє моделі розрізняти оцінене значення від ситуації, коли нічого не було спостережено. Структурна відсутність даних, що стосується живої музики, комедійних виступів, вигідних пропозицій та прямих спортивних передач, відображалась через прапорці наявності, такі як has_live_music та has_comedy. Порожній рейтинг може означати відсутність послуги, що є іншим станом бізнесу порівняно з невідомим рейтингом.
ORDINARY_MISSINGNESS_FLAGS = {
"facebook_popularity_quotient": "facebook_missing",
"instagram_popularity_quotient": "instagram_missing",
"overall_restaurant_rating": "overall_rating_missing",
"ambience": "ambience_missing",
"restaurant_tier": "tier_missing",
}
STRUCTURAL_AVAILABILITY_FLAGS = {
"live_music_rating": "has_live_music",
"comedy_gigs_rating": "has_comedy",
"value_deals_rating": "has_value_deals",
"live_sports_rating": "has_live_sports",
}
def add_missingness_features(df: pd.DataFrame) -> pd.DataFrame:
"""Add ordinary-null and structural-availability indicators before imputation."""
required_columns = set(ORDINARY_MISSINGNESS_FLAGS) | set(
STRUCTURAL_AVAILABILITY_FLAGS
)
missing_columns = sorted(required_columns - set(df.columns))
if missing_columns:
raise KeyError(f"Missing required columns for missingness features: {missing_columns}")
df = df.copy()
for column, flag in ORDINARY_MISSINGNESS_FLAGS.items():
df[flag] = df[column].isna().astype("int8")
for column, flag in STRUCTURAL_AVAILABILITY_FLAGS.items():
df[flag] = df[column].notna().astype("int8")
return df
5.3 Категорійний безлад: 297 варіантів написання міст
У стовпці «Місто» було 297 первинних варіантів написання для значно меншої кількості реальних ринків. Різниця у великості та пробілах, помилки та аліаси були поширені, а деякі записи складалися з кількох міст, що позначалося у вигляді складених рядків. Для очищення даних використовувалося поєднання алгоритмів фазового збігу за допомогою rapidfuzz, добре структурованого словника аліасів та чіткого аналізу складених записів. Прапорці рідкісних міст та показники їх частоти залишилися, оскільки розмір ринку та наявність даних можуть впливати на оцінку обороту. Крок зі збігом лише пропонує кандидатів для перевірки людиною та ніколи не змінює значень самостійно:
def generate_city_candidate_matches(
city: pd.Series,
minimum_similarity: float = 80.0,
) -> pd.DataFrame:
"""Return nearest fuzzy matches for review; this never changes city values."""
normalized = normalize_city_values(city, aliases={})
counts = normalized.value_counts()
names = [name for name in counts.index if name != "unknown"]
rows = []
for raw_city in names:
candidates = [candidate for candidate in names if candidate != raw_city]
match = process.extractOne(raw_city, candidates, scorer=fuzz.ratio)
if match is not None and match[1] >= minimum_similarity:
rows.append(
{
"Raw City": raw_city,
"Candidate": match[0],
"Similarity": float(match[1]),
"Count": int(counts[raw_city]),
}
)
return pd.DataFrame(
rows,
columns=["Raw City", "Candidate", "Similarity", "Count"],
).sort_values(["Similarity", "Count", "Raw City"], ascending=[False, False, True])
5.4 Пари змінних
Тип ресторану та рівень міста обидва значущим чином впливають на оборот. Формат закладу визначає середню вартість замовлення, обсяг роботи та модель управління, тоді як рівень міста є приблизним показником щільності попиту, купівельної спроможності та розміру ринку. Окремі оцінки були корисними, але містили багато шуму. Їжа, гігієна, співвідношення ціни та якості, обслуговування та атмосфера не є незалежними факторами. Вони взаємодіють із форматом, віком закладу, ринком, соціальною видимістю та позиціонуванням, що стимулює як нелінійне навчання, так і ретельно контрольовані механізми взаємодії.
5.5 Ширша картина
Кореляції Пірсона та Спірмана показували різні результати. Кілька факторів мали значно сильнішу монотонну, ніж лінійну, залежність від рівня зміни персоналу, тож корисні сигнали існували навіть у випадках, коли пряма лінія погано підходила для опису зв’язку. Були додані перевірки парного підтримку, щоб екстремальні кореляції, засновані на дуже малій кількості перекриваючихся спостережень, не були прийняті за стабільну структуру.
5.6 Характеристики, засновані на часі
Дати відкриття розділялися на календарні компоненти та характеристики віку. Ресторани, які існували довше, зазвичай мали вищий дохід, але це розглядалося як асоціація, а не причинно-наслідковий зв’язок, оскільки вік може свідчити про впізнаваність бренду, наявність закладів, постійний попит чи накопичений масштаб. Фіксована дата відліку забезпечувала відтворюваність цих характеристик.
6. Проблема дисбалансу у завданні регресії
У регресії немає дисбалансу класів у сенсі класифікації, але у цій метриці існував аналог: верхня частина розподілу була рідкою та призводила до великих втрат. За показником RMSE кілька великих за модулем залишків можуть домінувати над загальною сумою квадратів помилок. Під час діагностики найгірші спостереження становили непропорційно велику частку, тож модель могла виглядати нормальною для більшості ресторанів, але все одно показувати погані результати через пропуск кількох об’єктів із високими доходами.
Тому для перевірки використовувалась стратифікована 5-кратна крос-перевірка за метрикою. Неперервну метрику розділили на 10 квантильних інтервалів лише для призначення груп, тоді як моделі все одно тренувалися та оцінювалися за первинним обсягом доходів. Остаточні перевірки проводилися з використанням незалежних налаштувань (17, 42 та 101). Це зменшило ймовірність того, що кандидат стане чемпіоном лише через те, що один вдалий розподіл потрапив екстремальні значення до легших груп.
7. Інженерія ознак
Функції були розділені на безстанові трансформації та статистику налаштувань. Безстанова логіка працювала однаково як на навчальних, так і на тестових даних. Усе, що налаштовувалося — чи то медіани, карти частот або кодування — вивчалося лише на основі навчальних даних, версіонувалося та повторно використовувалося під час інференсу. Це забезпечувало можливість аудиту процесу та захищало від непомітних змін між навчальними та тестовими даними, спричинених попередньою обробкою.
- Прапорці відсутності та наявності, як описано вище.
- Календарні характеристики з дати початку (рік, місяць, квартал, день тижня), а також вік у місяцях та роках та категорія „ера початку“ у чотирьох групах.
- Кількість кухонь, отримана шляхом аналізу списку кухонь, розділених комами.
- Ієрархія міст від сирого міста до очищеного міста, потім до основного міста та далі до штату, з прапорцями для складних записів, регіонів та псевдонімів, а також індикаторами частоти та рідкісних міст за трьома пороговими значеннями.
Основний фіксований процес обробки даних використовував 71 прогнозуючий фактор. Додаткові створені сім’ї факторів тестувалися за допомогою методу абляції замість автоматичного включення, а кожен фактор отримував своє місце завдяки покращенню точності прогнозування, а не через інтуїтивну привабливість.
8. Використання зовнішніх даних-джерел
Окрема дослідницька група поєднала очищені дані про міста та штати з публічними географічними та економічними даними для порівняння. Питання полягало у тому, чи надають місцева ринкова потужність, рівень урбанізації, інтенсивність туризму та чисельність населення додаткову інформацію окрім власних характеристик ресторану.
- Для кожного міста: чисельність населення, кількість та розмір домогосподарств, логарифм чисельності населення, координати, а також позначки «так/ні» щодо наявності мегаполісів, столичного регіону, прибережних міст, столиць штатів та міст з населенням понад мільйон осіб.
- На рівні штату: чисельність населення, кількість та частка міського населення, щільність, ВВП на душу населення, ВВП та інші економічні показники, а також кількість відвідувачів з-поміж вітчизняних та іноземних туристів, з розрахунковим показником туризму на душу населення.
Результат був навмисно консервативним. Кілька зовнішніх ознак показували сигнал у окремих тестах, але сімейство не покращило кінцеву модель за умов повного протоколу заміни та підвищення якості, тому воно так і не потрапило до категорії найкращих моделей. Це корисний урок для бізнес-моделювання: змінна може здаватися правдоподібною та корисною для прогнозування окремо, проте її значення значно зменшується при наявності сильніших, взаємопов’язаних сигналів.
9. Моделі та чому
Дані поєднували в собі неперервні оцінки, розріджені та структурні прогалини, а також категорії з високою кардинальністю, тому кілька родин моделей конкурували як гіпотези, замість того щоб припускати, що один алгоритм буде домінувати. Ridge та ElasticNet виявилися несподівано конкурентоспроможними порівняно з CatBoost. Це не було поразкою для методів boosting. Це відображало те, наскільки багато структури потрапило у створені характеристики та наскільки корисною є регуляризація, коли існує багато взаємопов’язаних прогнозуючих факторів. Моделі на основі дерев все ще мали значення, оскільки вони фіксували нелінійні взаємодії, які лінійні моделі не можуть прямо виразити.
10. Налаштування
Під час розробки кожна група отримала власне дослідження в Optuna, оцінюване за значенням RMSE поза фолдом на основі багаторазових розділень даних із стратифікацією за цільовою змінною. Вибір кандидатів та їх підвищення у пріоритети здійснювалося за допомогою внутрішньої перевірки, а не таблиці лідерів; це має важливе значення, оскільки пізню фазу оптимізації для змагань не слід плутати із процесом узагальнення моделі.
Найефективніша комбінація включала CatBoost, Ridge, ElasticNet та модифіковану версію Ridge зі стабілізацією дисперсії у вигляді квадратного кореня (WR01) у співвідношенні приблизно 30%, 10%, 6% та 54%. Її багаторазове значення RMSE поза фолдом становило близько ₹20,10 млн. Висока частка WR01 свідчила про те, що трансформована лінійна модель не була найкращою самостійною моделлю, але її унікальна геометрія похибок робила її дуже корисною в рамках ансамблю.
11. Що допомогло, а що — ні
Кожен експеримент фіксувався разом із сім’єю його характеристик, конфігурацією моделі, схемою підтвердження, прогнозами OOF та рішенням щодо використання. Прогрес досягався шляхом рішучого відкидання слабких ідей так само, як і активного застосування сильних.
- Спочатку була встановлена базова лінія. Середні та медіанні показники прогнозування визначили мінімальне значення RMSE на рівні приблизно 21,6–21,7 млн, що полегшило виявлення моделей, які були лише складними за структурою.
- Регуляризація мала велике значення. Методи Ridge та ElasticNet значно перевершували звичайний OLS, оскільки створений простір містив корелювані сигнали та взаємодії.
- CatBoost надавав можливості для обробки нелінійних та категоріальних даних, проте не очікувалося, що налаштування допоможуть. Один перспективний результат від CatBoost покращив розділення даних для оптимізації приблизно на 258 тис., але не пройшов перевірку на незалежних даних, тому був відкинутий через переобучення під час вибору.
12. Оцінки для різних чемпіонів
У проекті було створено два типи чемпіонів: модель без доповнень, обрана шляхом багаторазової перекритої валідації, та чемпіон змагань, налаштований під умови оцінювання. Третій контрольний пункт S08 демонструє, який ефект має навмисно незначна корекція, ще до більш складних етапів змагань. Ці оцінки відповідають на різні запитання та не є взаємозамінними показниками узагальнювання.
У рамках змагань модель LB05 зменшила публічний показник RMSE на ₹9,195,461.72 порівняно з S08, що становить приблизно 76.1%. Це результат топ-листу, а не доказ того, що модель, придатна до використання, раптово починає узагальнювати дані з показником RMSE ₂.89 млн. Ось скрипт, який використовує заморожені компоненти та створює першу змішану модель:
from __future__ import annotations
from datetime import datetime, timezone
from pathlib import Path
import time
import json
import joblib
import numpy as np
import pandas as pd
from src import config, data, models, submission
from src.pipeline import FeaturePipeline
from main import _prepare_catboost_matrices, _prepare_model_matrices
S01_ID = "S01"
S01_WEIGHTS = {"catboost": 0.50, "ridge": 0.45, "elasticnet": 0.05}
def run(output_dir: Path | None = None) -> tuple[Path, Path]:
output_dir = output_dir or config.SUBMISSIONS_DIR
output_dir.mkdir(parents=True, exist_ok=True)
manifest_path = config.EXPERIMENTS_DIR / "frozen_v1_manifest.json"
if not manifest_path.exists():
raise FileNotFoundError(f"Frozen V1 manifest is required: {manifest_path}")
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
if manifest.get("feature_pipeline") != "processed_v1" or manifest.get("predictor_count") != 71:
raise ValueError("Frozen V1 manifest does not describe the processed_v1 71-predictor contract")
train_raw = data.load_train()
test_raw = data.load_test()
pipeline = FeaturePipeline().fit(train_raw)
train = pipeline.transform(train_raw)
test = pipeline.transform(test_raw)
target = config.INTERNAL_TARGET
X_train = train.drop(columns=[target, config.INTERNAL_ID_COL], errors="ignore")
X_test = test.drop(columns=[config.INTERNAL_ID_COL], errors="ignore")
y = train[target].astype(float).to_numpy()
X_numeric, X_test_numeric = _prepare_model_matrices(X_train, X_test)
X_cat, X_test_cat = _prepare_catboost_matrices(X_train, X_test)
predictions = {}
timings = {}
start = time.perf_counter()
catboost = models.get_catboost(**{
key: value for key, value in manifest["catboost_config"].items()
if key != "native_categorical"
})
cat_features = [
index for index, dtype in enumerate(X_cat.dtypes)
if str(dtype) in {"object", "string", "category"}
]
catboost.fit(X_cat, y, cat_features=cat_features, verbose=False)
predictions["catboost"] = catboost.predict(X_test_cat)
timings["catboost_seconds"] = time.perf_counter() - start
start = time.perf_counter()
ridge = models.get_ridge(**manifest["ridge_config"])
ridge.fit(X_numeric, y)
predictions["ridge"] = ridge.predict(X_test_numeric)
timings["ridge_seconds"] = time.perf_counter() - start
start = time.perf_counter()
elasticnet = models.get_elasticnet(**manifest["elasticnet_config"])
elasticnet.fit(X_numeric, y)
predictions["elasticnet"] = elasticnet.predict(X_test_numeric)
timings["elasticnet_seconds"] = time.perf_counter() - start
weights = manifest["ensemble_weights"]
blend_prediction = sum(weights[name] * predictions[name] for name in weights)
submission_df = submission.build_submission(test_raw, blend_prediction)
submission_path = output_dir / "S01_frozen_v1_blend.csv"
submission.write_submission(submission_df, submission_path, expected_rows=len(test_raw))
component_paths = {}
for name, prediction in predictions.items():
component_paths[name] = output_dir / f"S01_{name}_predictions.csv"
pd.DataFrame({config.ID_COL: test_raw[config.ID_COL].to_numpy(), config.TARGET: prediction}).to_csv(
component_paths[name], index=False
)
metadata = {
"submission_id": S01_ID,
"model": "Frozen V1 Blend",
"feature_version": "frozen production",
"oof_rmse": 20097389.205713525,
"catboost_weight": S01_WEIGHTS["catboost"],
"ridge_weight": S01_WEIGHTS["ridge"],
"elasticnet_weight": S01_WEIGHTS["elasticnet"],
"public_leaderboard_rmse": None,
"leaderboard_rank": None,
"timestamp_utc": datetime.now(timezone.utc).isoformat(timespec="seconds"),
"submission_file": str(submission_path),
"component_files": {name: str(path) for name, path in component_paths.items()},
"fit_method": "fit_each_frozen_component_on_all_3493_processed_v1_training_rows",
"catboost_production_iterations_rule": "fixed_manifest_value_no_early_stopping",
"manifest_path": str(manifest_path),
**timings,
}
metadata_path = output_dir / "S01_metadata.json"
metadata_path.write_text(json.dumps(metadata, indent=2), encoding="utf-8")
return submission_path, metadata_path
if __name__ == "__main__":
for path in run():
print(path)
13. Результати у змаганнях проти здатності до узагальнення
Як тільки чиста модель та протокол її перевірки були фіксовані, окремий етап оптимізації для змагань покращив прогнозування для конкретного незалученого до навчання набору даних для оцінки. Механізми цього процесу не входять у межі цього опису. З методологічної точки зору важливо те, що цей етап використовував інформацію з середовища змагань, а не нові позначені дані для навчання.
Тож кінцеве значення RMSE у топ-листі, яке становить приблизно 2,893 млн, слід розглядати як специфічне для змагань та потенційно як ознаку надприлягання моделі до цього набору даних для оцінки. Об’єктивною оцінкою того, як би модель впоралася з новими ресторанами, залишається результат повторного CV у розмірі близько 20,10 млн. Розділення цих двох чисел є необхідним як для наукового, так і для бізнес-аналізу.
У реальній ситуації впровадження прогнозування шар, оптимізований під конкуренцію, був би вилучений, якщо тільки не буде доступного еквівалентного сигналу калібрування у майбутньому. Система, призначена до впровадження, використовувала б зафіксований потік ознак та ансамбль моделей, стежила б за залишками та змінами розподілу, а також перенавчалася б при надходженні реальних даних про оборот.
14. Припущення
- Припускалося, що дані для навчання та тестування походять з достатньо схожих бізнес-процесів, щоб крос-перевірка мала сенс. Жодна формальна корекція через зміну коваріатів не використовувалася, окрім перевірок схеми та розподілу.
- Номер реєстрації був суто ідентифікатором та не включався до чистої моделі, оскільки не мав обґрунтованого зв’язку з оборотом.
- Припускалося, що відсутні оцінки зручностей містили структурну інформацію про відсутність послуги, що стало підставою для проектування флага доступності.
15. Рекомендації
- Розглядайте якість даних як продуктивність моделі. Невідповідності схем, фрагментовані назви міст та неузгоджені категоріальні дані можуть знищити корисну інформацію ще до того, як алгоритм її побачить.
- Розділяйте дані, відсутні через невідомість, та дані, відсутні через відсутність можливостей для їх збору. Останнє часто є перевагою, а не проблемою.
- У разі зміщених цілей доходу відстежуйте значення RMSE для крайніх значень, концентрацію квадратів похибок, а також загальну суму. Кілька великих клієнтів можуть визначити, чи є прогноз фінансово корисним.
- Використовуйте чіткі критерії для просування: пошук кандидатів, підтвердження на незалежних даних та заміна алгоритмів. Це запобігає тому, щоб прибуток, отриманий на одній з груп даних, випадково став найкращим.
- Розглядайте штучний інтелект-агента як модель функціонування, а не як ліцензію на автономне просування. Дозвольте агентам пропонувати рішення, створювати та критикувати їх, тоді як важливі рішення щодо моделей та продуктів мають прийматися людьми.
- Зберігайте записи інформації не в вікні чату, а в іншому місці. Набори даних, описи, прогнози OOF, журнали експериментів, „заморожені“ найкращі рішення та рішення щодо їх просування мають зберігатися, щоб новий агент міг спиратися на ці дані, а не на попередні повідомлення.
- Під час експериментування з машинним навчанням необхідно розділяти середовища DEV, UAT та PROD. Свобода творчості дозволена у середовищі DEV, тоді як реплікація та критичний аналіз відбуваються у середовищі UAT; середовище PROD має бути детермінованим, зафіксованим та здатним до скасування змін.
16. Уроки
Основний урок полягає не в тому, що якийсь один алгоритм переміг. Систематичне формулювання проблеми, аудит даних, перевірка з урахуванням межових випадків, інженерія ознак, різноманітні залишки та контрольований робочий процес створили кращу систему прогнозування, ніж сама лише складність моделі. За допомогою DontPanic агенти працювали як команда, що прискорювало процес відкриття та реалізації, водночас чіткі людські контролі запобігали тому, щоб швидкість замінила розсудливість.
Чемпіон фінального змагання досяг значення RMSE близько 2,893 млн рупій після етапу оптимізації, адаптованого під середовище оцінки. Цей результат визначив переможця змагання, проте його наводять окремо, оскільки він може бути наслідком надпідгонки моделі, і його не слід сприймати як очікувану похибку для майбутніх ресторанів.
Ширший урок є практичним: спочатку проводьте перевірку моделі, перш ніж її налаштовувати; зберігайте результати невдалих експериментів; враховуйте досягнення лише після їх підтвердження; ніколи не плутайте модель, яка досягає найкращих результатів у тестах, з тією, якій можна довіряти у реальних умовах. А якщо беруть участь кілька штучних інтелектуальних агентів, нехай вони «грають у волейбол»: дозвольте їм активно працювати, але переконайтеся, що важливі рішення все одно приймаються людиною перед тим, як буде зарахований очко.