Strona główna / Artykuły

Gra w siatkówkę z agentami AI w celu przewidywania obrotu w restauracjach

Hackathon dotyczący obrotu w restauracjach, oparty na agentach AI sterowanych przez ludzi: audyt danych, walidacja uwzględniająca specyfikę przypadków, inżynieria cech, metoda ensembling oraz różnica w wynikach w porównaniu z liderami rankingu.

Wydawca: REACTAPP.TOP18 min czytania4112 słów

Źródła: **How ‘Playing Volleyball’ with AI Agents Helped Me Predict Restaurant Turnover —**and Win a Hackathon

Załóżmy hackathon, w którym sam model stanowi zaledwie połowę pracy. Druga połowa polega na utrzymywaniu kilku agentów sztucznej inteligencji zajętych opracowywaniem konkurencyjnych pomysłów, bez możliwości podjęcia przez żadnego z nich decyzji należącej do człowieka. Ten tekst opisuje jeden z takich projektów – hackathon dotyczący prognozowania obrotu w restauracjach, zorganizowany przez Great Learning we współpracy z UT Austin i jego szkołą biznesu McCombs. Omawia on efektywny proces pracy z maszynowym uczeniem, ścisłą weryfikację oraz zespół modeli, który według samych organizatorów doskonale poradził sobie w konkursie.

Szczególną rolę odgrywała warstwa orkiestracji oparta na frameworku o nazwie DontPanic. Jej procesy pracy odpowiadają logice wymiany piłki w siatkówce: agent otrzymuje zadanie, planuje kolejny krok, przekazuje pracę odpowiednim specjalistom i w końcu przekazuje wynik, przy czym piłka zawsze wraca do osoby podejmującej decyzję. Agenci przyspieszają wykonywanie zadań, ale nie podejmują ostatecznych decyzji.

1. Zadanie

Uczestnicy musieli przewidzieć roczny obrót restauracji w całych Indiach na podstawie 3 493 oznaczonych rekordów treningowych oraz 500 nieoznaczonych rekordów testowych. Istniało około 32 surowych czynników predykcyjnych dotyczących lokalizacji, kuchni, formatu restauracji, udogodnień, popularności w mediach społecznościowych oraz ocen doświadczenia klienta. Pod pojęciem „obrót” rozumie się tutaj roczne przychody, a nie rotację personelu. Miarą była wartość RMSE, która kwadratuje błędy przed ich średniowaniem, więc kilka bardzo dużych błędów może znacząco obniżyć wynik.

Celem nie było stworzenie najbardziej skomplikowanego modelu. Chodziło o system prognozowania, który można było uzasadnić: przeprowadzić audyt danych, zrozumieć aspekty ekonomiczne celu, opracować sygnały mające sens biznesowy, zweryfikować kandydatów za pomocą stabilnej walidacji krzyżowej, odrzucić ulepszenia, które nie dały się powtórzyć, oraz w końcu oddzielić prawdziwą zdolność generalizacji modelu od dostosowań specyficznych dla konkretnej rywalizacji.

2. Narzędzia

Wszystko znajdowało się w VS Code jako modułowy pakiet Pythona w katalogu src/, zamiast być zbiorem niepowiązanych eksperymentów w notatkach. Ta decyzja okazała się skuteczna, ponieważ czyszczenie danych, budowa funkcji, walidacja oraz wyciąganie wniosków odbywały się za pomocą tego samego, wielokrotnie używalnego kodu. Dzięki temu funkcja nie mogła zachowywać się inaczej podczas treningu i inaczej po stworzeniu ostatecznego wyniku.

3. Orkiestracja agentów

DontPanic stanowiło narzędzie służące do zarządzania projektem. Pełna autonomia nigdy nie była celem. Chodziło o to, aby kilka agentów mogło działać szybko, jednocześnie określając strukturę decyzyjną: kto bada, kto buduje, kto kwestionuje decyzje, który element jest autorytatywny oraz kiedy praca musi wrócić do człowieka w celu podjęcia decyzji o kontynuacji lub przerwaniu.

To ma znaczenie, ponieważ eksperymenty z sztuczną inteligencją przedstawiają kolejne prawdopodobne kroki dalsze. Agent działający samodzielnie będzie nadal optymalizował ten sygnał, który widział ostatnio, a człowiek może stracić poczucie tego, czy hipoteza, protokół walidacji czy kryterium promocji uległy zmianie. Można traktować DontPanic jako zbiór zasad, według których przebiegał eksperyment: agenci mogli swobodnie kontynuować działanie, ale nigdy nie mogli przepisać zasad bez tego, by ktoś to zauważył.

Każdy okres działania agenta był opisany w pliku manifestu. Przykład dla środowiska Claude CLI przedstawia komendy i pliki, których wymaga, sposób weryfikacji gotowości, które elementy należą do frameworka, co musi zrobić operator oraz jakie działania są zabronione:

{
  "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 Rally w pięciu krokach

  1. Servowanie: przed rozpoczęciem jakiegokolwiek eksperymentu człowiek sformułował pytanie biznesowe, ograniczenia, metrykę, kryteria przyjęcia oraz obecnego lidera.
  2. Odbieranie: agent odkrywczy przeanalizował dostępne dowody, zaznaczył anomalie i zaproponował hipotezy poddawalne testom zamiast od razu przepisywać model.
  3. Ustawianie: po zatwierdzeniu hipotezy agent implementacyjny zapisał ją w postaci kodu modularnego, artefaktów deterministycznych oraz definicji eksperymentu, który można było ponownie przeprowadzić.
  4. Wyrzut: agent recenzent sprawdzał możliwość wycieku informacji, błędy w walidacji krzyżowej, niestabilne wartości początkowe oraz niejasne pochodzenie danych i pytał, czy uzyskany efekt przetrwa porównanie z obecnym liderem.
  • Dystrybucja decyzji do człowieka: promowanie, odrzucanie, rozszerzanie lub zmiana strategii spoczywały w rękach osoby. Agenci mogli proponować rozwiązania, ale ostateczna decyzja należała do człowieka.
  • Prawdziwą zaletą była kontrolowana równoległość działań, a nie sama szybkość kodowania. Różni agenci mogli skupić się na różnych sposobach występowania błędów, podczas gdy ludzki element systemu chronił intencje produktu, sens biznesowy oraz odpowiedzialność. Taki model przypominał bardziej mały zespół produkcyjny niż jedną sesję z chatbotem.

    3.2 Lean ML delivery

    Przepływ pracy agentów został również dostosowany do zwinnego cyklu życia produktu, w celu uproszczenia eksperymentów w fazie DEV, utrudnienia sfałszowania dowodów w fazie UAT oraz uniemożliwienia przypadkowych zmian w stanie produkcji. Podział na DEV/UAT/PROD nabierał coraz większej wartości w miarę wzrostu liczby eksperymentów. Nowy agent mógł dołączyć bez konieczności korzystania z pamięci rozmów, ponieważ autorytatywne dane przechowywano w postaci plików, manifestów, przewidywań typu out-of-fold (OOF), rejestru najlepszych rozwiązań oraz zapisanych decyzji dotyczących promocji. Warto zachować ten wzorzec: niech agenci zapewniają szybkość, a artefakty i mechanizmy kontrolne – prawdę.

    4. Audytowanie danych przed wierzeniem im

    Zanim rozpoczęto modelowanie, pliki treningowe i testowe zostały skanowane za pomocą SHA-256 oraz sprawdzone kolumna po kolumnie. To nie była biurokracja – kilka nietypowych cech mogło zmienić przestrzeń cech podczas wykonywania predykcji i zaszkodzić wynikom, nie powodując żadnego widocznego błędu.

    • Zbiór treningowy zawierał 3 493 wiersze i 34 kolumny, natomiast zbiór testowy miał 500 wierszy i 33 kolumny, ponieważ nie zawierał pola celowego.
    • Kolumny Ambience i Restaurant Tier miały różne typy danych w zbiorze treningowym i testowym, dlatego konieczna była wyraźna konwersja danych, aby modele otrzymywały identyczne cechy.
    • Jedna kolumna była zapisana jako Endorsed By w zbiorze treningowym, a Endoresed By w zbiorze testowym. Bez odpowiedniego mapowania proces mogłby wygenerować w zbiorze testowym cechę, która w ogóle nie istniałaby.
  • Nie było żadnych duplikatów wierszy ani wektorów predykcyjnych, więc dane były czyste na poziomie wierszy.
  • Numer rejestracyjny pełnił rolę identyfikatora, a nie predyktora biznesowego (Pearson około -0,017, Spearman około -0,022). Nie został włączony do modeli i zachowano go jedynie w celu dopasowania wierszy na końcu.
  • 5. Badanie danych

    5.1 Silnie prawoskośny cel

    Roczny obrót miał długi prawy ogon. 90. percentyl wynosił około 48 milionów rupii, 95. około 57 milionów rupii, 99. około 100 milionów rupii, a maksimum osiągało 400 milionów rupii; surowa odchylenie asymetrii wynosiła około 6,65. Transformacja log1p sprawiła, że rozkład stał się niemal symetryczny, i porównano surowe wartości, wartości po transformacji log1p oraz metody Yeo-Johnson; później przetestowano również wariant oparty na powierzchni w stopach kwadratowych. Z punktu widzenia biznesu większość restauracji mieści się w dość wąskim przedziale przychodów, podczas gdy niewielka grupa bardzo dużych lokali powoduje znaczne błędy finansowe, gdy ich nie uwzględni się w analizach.

    Ogon rozkładu nie był tylko widocznie nietypowy – dominował w przypadku strat. Analiza błędów pokazała, że niewielka grupa restauracji o najgorszych prognozach odpowiadała za dużą część całkowitego błędu kwadratowego, dlatego diagnostyka ogona stała się kluczową częścią oceny.

    5.2 Brakujące dane wymagają więcej niż jednej zasady

    Brakujące wartości nie były traktowane jako jeden zjawisko. Niektóre odzwierciedlały zwykłe braki w jakości danych. Inne odzwierciedlały strukturę biznesową – na przykład pusta ocena rozrywki wynikała z faktu, że miejsce nie oferuje żadnej rozrywki. Jedna zasada imputacji dla obu przypadków zniszczyłaby przydatne informacje.

    Zwykły brak danych, taki jak popularność w mediach społecznościowych, ogólna ocena, atmosfera i kategoria, był obsługiwany poprzez imputację na podstawie danych treningowych w połączeniu z wyraźnymi wskaźnikami braku danych. Dzięki temu model może odróżnić oszacowaną wartość od sytuacji, gdy nic nie zostało zaobserwowane. Strukturalny brak danych, dotyczący muzyki na żywo, występów komediowych, okazji cenowych i transmisji sportowych na żywo, był reprezentowany przez flagi dostępności, takie jak has_live_music i has_comedy. Pusta ocena może oznaczać, że usługa nie istnieje, co stanowi inny stan biznesowy niż nieznana ocena.

    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 Bałagan kategorialny: 297 wariantów pisowni miast

    Kolumna „Miasto” zawierała 297 surowych wariantów pisowni dla znacznie mniejszej liczby rzeczywistych rynków. Różniła się wielkość liter i ilość spacji, pojawiały się błędy pisarskie oraz alternatywne nazwy, a niektóre wpisy były złożonymi ciągami znaków oznaczającymi kilka miast. Proces oczyszczania polegał na połączeniu analizy podobieństwa za pomocą narzędzia rapidfuzz, wyselekcjonowanego słownika aliasów oraz świadomego analizowania złożonych wpisów. Flagi dotyczące rzadkich miast oraz wskaźniki częstotliwości występowania miast pozostały bez zmian, ponieważ zarówno rozmiar rynku, jak i dostępne dane mogą wpływać na szacunki obrotu. Krok dopasowywania służy jedynie do proponowania kandydatów do sprawdzenia przez człowieka i nigdy sam nie zmienia wartości:

    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 Pary zmiennych

    Rodzaj restauracji oraz poziom miasta w znaczący sposób wpływały na obroty. Format lokalu determinuje średnią wartość zamówienia, prędkość obsługi oraz model działania, natomiast poziom miasta stanowi przybliżony wskaźnik gęstości popytu, siły nabywczej i skali rynku. Poszczególne oceny były przydatne, ale zawierały wiele szumów. Jakość jedzenia, higiena, stosunek jakości do ceny, obsługa oraz atmosfera nie są niezależnymi czynnikami wpływającymi na wyniki. Współdziałają one z formatem lokalu, wiekiem klienteli, rynkiem, widocznością społeczną oraz pozycjonowaniem, co skłoniło zarówno badaczy zajmujących się nauką nieliniową, jak i osoby kontrolujące zjawiska interakcji, do dalszych badań.

    5.5 Szerszy kontekst

    Korelacje Pearsona i Spearmana opowiadały różne historie. Kilka czynników predykcyjnych wykazywało znacznie silniejszy związek monotoniczny niż liniowy z rotacją pracowników, więc istniał użyteczny sygnał nawet wtedy, gdy prosta linia słabo pasowała do danych. Dodano sprawdzenia wsparcia pomiędzy parametrami, aby ekstremalne korelacje oparte na bardzo niewielkiej liczbie pokrywających się obserwacji nie zostały błędnie uznane za solidną strukturę.

    5.6 Cechy oparte na czasie

    Data otwarcia została podzielona na komponenty kalendarzowe oraz cechy związane z wiekiem. Starsze restauracje miały tendencję do osiągania wyższych przychodów, ale traktowano to jako związek, a nie twierdzenie przyczynowe, ponieważ wiek może być wskaźnikiem rozpoznawalności marki, lokalizacji, którym udało się przetrwać, popytu na powtórne wizyty lub zgromadzonej skali działalności. Ustalona data odniesienia zapewniała powtarzalność tych cech.

    6. Problem nierównowagi w zadaniu regresji

    Regresja nie charakteryzuje się brakiem równowagi klas w sensie klasyfikacji, ale w tym przypadku istniał jej odpowiednik: górna grupa wartości była rzadka i kosztowna. Przy metryce RMSE kilka dużych resztek może dominować nad całkowitą wartością błędu kwadratowego. W analizach diagnostycznych najgorsze obserwacje stanowiły niewspółmierną część danych, więc model mógł wyglądać dobrze dla większości restauracji, a mimo to osiągać słabe wyniki po pominięciu kilku restauracji o wysokich przychodach.

    Dlatego do walidacji zastosowano stratyfikowaną 5-krotną walidację krzyżową według celu. Ciągły cel został podzielony na 10 przedziałów kwantylowych wyłącznie w celu przydzielenia grup do walidacji, podczas gdy modele nadal były trenowane i oceniane na podstawie surowych przychodów. Ostateczne sprawdzenia promocji powtarzano przy użyciu niezależnych wartości losowych (17, 42 i 101). Dzięki temu zmniejszono szanse, że dany kandydat zostanie mistrzem tylko dlatego, że szczęśliwy podział przypadkowo umieścił wartości skrajne w łatwiejszych grupach.

    7. Inżynieria cech

    Cechy zostały podzielone na transformacje bezstanowe i statystyki dostosowane. Logika bezstanowa działała identycznie zarówno na danych treningowych, jak i testowych. Wszystko, co zostało dostosowane – czy to mediane, mapy częstości czy kodowania – było uczone wyłącznie na podstawie danych treningowych, wersjonowane i ponownie wykorzystywane podczas inferencji. Dzięki temu proces był sprawdzalny i chronił przed subtelnymi odchyleniami między danymi treningowymi a testowymi spowodowanymi przetwarzaniem wstępnym.

    • Flagi braku i dostępności, jak opisano powyżej.
    • Cechy kalendarzowe dotyczące daty rozpoczęcia (rok, miesiąc, kwartał, dzień tygodnia), a także wiek w miesiącach i latach oraz kategoria „era rozpoczęcia” podzielona na cztery grupy.
    • Liczba gatunków kulinarnych, uzyskana poprzez analizę listy gatunków oddzielonych przecinkami.
    • Hierarchia miast od wersji surowej po wersję oczyszczoną, a następnie do miasta głównego i stanu, z flagami dla wpisów złożonych, regionów i aliasów, wraz z wskaźnikami częstości i rzadkich miast przy trzech progu.
  • Interakcje kategoryczne, które uwzględniają kombinacje o innej charakterystyce ekonomicznej niż ich składniki: temat z typem restauracji, temat z lokalizacją, napoje alkoholowe z ich typem oraz niektóre połączenia rozrywki i formatu.
  • Interakcje liczbowe dla ustandaryzowanej rodziny liniowej: poziom restauracji z poziomem miasta, widoczność społeczna z poziomem, doświadczenie klienta z widocznością społeczną oraz wiek z popularnością. Pozwoliły one przekształcić pewne nieliniowe związki biznesowe w formę, którą mógł wykorzystać Ridge.
  • Kodowania częstotliwościowe i porządkowe dla każdej kolumny kategorycznej, trenowane na danych treningowych i ponownie używane w testach.
  • Główny pipeline używał 71 predyktorów. Dodatkowe rodziny predykcyjne były testowane poprzez ablację zamiast być automatycznie zachowywane, a dany cechownik uzyskał swoje miejsce dzięki wzrostowi skuteczności weryfikacji, a nie intuicyjnemu uznaniu.

    8. Wykorzystanie zewnętrznych danych referencyjnych

    Odrębna grupa badawcza połączyła oczyszczone dane dotyczące miast i stanów z publicznymi danymi referencyjnymi geograficznymi i ekonomicznymi. Pytanie brzmiało, czy zdolność lokalnego rynku, stopień urbanizacji, intensywność turystyki oraz wielkość populacji dostarczają dodatkowych informacji poza własnymi cechami restauracji.

    • Dla każdego miasta: liczba ludności, liczba i wielkość gospodarstw domowych, logarytm liczby ludności, współrzędne, a także oznaczenia typu „tak/nie” dla metropolii, stolicy krajowej, miast nadmorskich, stolic stanów oraz miast liczących ponad milion mieszkańców.

    Wynik został celowo przyjęty w sposób konserwatywny. Kilka cech zewnętrznych wykazywało sygnały w pojedynczych testach, ale ta grupa nie poprawiła ostatecznego zestawu modeli zgodnie z pełnym protokołem substytucji i promocji, dlatego nigdy nie trafiła do grona najlepszych modeli. To przydatna lekcja z zakresu modelowania biznesowego: zmienna może być wiarygodna i predyktywna sama w sobie, ale ma niewielki wpływ, gdy obecne są silniejsze, skorelowane sygnały.

    9. Modele i dlaczego

    Dane zawierały zarówno ciągłe oceny, rzadkie i strukturalne luki, jak i kategorie o dużej kardynalności, więc kilka rodzin modeli konkurowało jako hipotezy, zamiast zakładać, że jeden algorytm będzie dominujący. Ridge i ElasticNet okazały się nieoczekiwanie konkurencyjne z CatBoost. To nie było porażką metod boostingu – pokazało to, jak dużo struktury pojawiło się w stworzonych cechach oraz jak przydatna jest regularizacja, gdy współistnieje wiele powiązanych predyktorów. Modele drzewiaste wciąż były istotne, ponieważ potrafiły odzwierciedlić nieliniowe interakcje, których rodzina modeli liniowych nie może bezpośrednio wyrazić.

    10. Dostosowywanie

    W trakcie rozwoju każda grupa otrzymała własne badanie w Optuna, oceniane za pomocą wartości RMSE z metody out-of-fold na podstawie powtarzanych partycji stratyfikowanych według celu. Wybór kandydatów i ich promocja opierały się na walidacji wewnętrznej, a nie na liście liderów – różnica ta jest istotna, ponieważ późniejsza faza optymalizacji pod kątem konkurencji nie powinna być mylona z procesem uogólniania.

    Najskuteczniejsza czysta kombinacja obejmowała CatBoost, Ridge, ElasticNet oraz wariant Ridge z uspokojoną wariancją w postaci pierwiastka kwadratowego (WR01) w proporcjach około 30%, 10%, 6% i 54%. Jej powtarzana wartość RMSE z metody out-of-fold wynosiła około 20,10 miliona rupii. Duży udział WR01 był wymowny: przekształcony model liniowy nie był najlepszym samodzielnym modelem, ale jego inna geometria błędu sprawiła, że stał się bardzo cenny w ramach zespołu modeli.

    11. Co pomogło, a co nie

    Każdy eksperyment był rejestrowany wraz z informacjami o rodzinie cech, konfiguracji modelu, projekcie walidacji, przewidywaniach poza zakresem danych oraz decyzji o wyborze najlepszego modelu. Postępy osiągano poprzez stanowcze odrzucanie słabych pomysłów, jednocześnie promując te silne.

    • Najpierw ustalono bazę porównawczą. Średnie i medianowe wartości predyktorów określiły dolną granicę RMSE na poziomie około 21,6 do 21,7 miliona, co ułatwiło wykrycie modeli, które były jedynie skomplikowane.
    • Regularizacja miała kluczowe znaczenie. Metody Ridge i ElasticNet przewyższyły zwykłe OLS o dużą przewagę, ponieważ stworzony przestrzeń danych zawierała skorelowane sygnały i interakcje.
    • CatBoost zapewniał moc w obszarze zmiennych nieliniowych i kategorycznych, jednak nie zakładano, że dostosowanie parametrów pomoże. Jeden obiecujący wynik uzyskany przy użyciu CatBoost poprawił efekty optymalizacji o około 258 tysięcy, lecz nie przetrwał weryfikacji na niezależnych danych i został odrzucony z powodu nadmiernego dopasowania.
  • Inżynieria cech zależała od modelu. Kodowania celu, interakcje, częstotliwości kategorii oraz cechy związane z czasem trwania były spośród najbardziej przydatnych grup, natomiast kilka intuicyjnych agregatów przynosiło niewielki efekt, gdy istniały silniejsze predykatory.
  • Zewnętrzne uzupełnienia dotyczące miasta i stanu okazały się przydatnym negatywnym efektem: niektóre testy składowe się poprawiły, zastąpienie modeli zbiorowych pogorszyło wyniki, a ta grupa nie znalazła się w gronie najlepszych rozwiązań.
  • Zbieranie modeli w zespoły przyniosło najbardziej niezawodną poprawę, ponieważ CatBoost oraz znormalizowane warianty liniowe popełniały błędy w inny sposób. Celem było uzyskanie najniższego zweryfikowanego błędu kwadratowego wśród modeli komplementarnych, a nie maksymalna złożoność.
  • Klasyfikatory obsługujące rzadkie przypadki oraz modele korygujące reszty wykazały obiecujący potencjał diagnostyczny, lecz nie przyniosły wystarczającej poprawy RMSE, aby uzasadnić ich wykorzystanie. Przydatność do diagnozowania oraz przydatność do prognozowania traktowano jako odrębne kwestie.
  • 12. Wyniki dla różnych mistrzów

    W ramach projektu stworzono dwa rodzaje mistrzów: model czysty wybrany poprzez wielokrotne walidacje krzyżowe oraz mistrz z konkursu dostosowany do środowiska oceny. Trzeci punkt kontrolny, S08, pokazuje, jaki efekt ma celowe zmniejszenie błędów, przed bardziej złożonymi etapami specyficznymi dla konkursu. Te wyniki odpowiadają na różne pytania i nie stanowią wzajemnie zamienialnych miar uogólnialności.

    W przypadku konkursu, rozwiązanie LB05 zmniejszyło publiczny RMSE o 9 195 461,72 ₹ w porównaniu z S08, co stanowi około 76,1%. To wynik z listy liderów, a nie dowód na to, że model gotowy do użycia nagle osiąga wartość RMSE na poziomie 2,89 mln ₹. Oto skrypt, który dopasowuje zamarznięte komponenty i zapisuje pierwszą połączoną wersję rozwiązania:

    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. Wyniki w konkursie a generalizacja

    Gdy model oczyszczony wraz z protokołem walidacji zostały ustalone, oddzielna faza optymalizacji pod kątem konkursu poprawiła przewidywania dla konkretnego zestawu oceny. Szczegóły tej mechaniki wykraczają poza zakres tego tekstu. Z metodologicznego punktu widzenia istotne jest to, że faza ta korzystała z informacji pochodzących z środowiska konkursu, a nie z nowo oznaczonych danych treningowych.

    Dlatego końcowe wartości RMSE na liście liderów, wynoszące około 2,893 miliona, należy interpretować jako specyficzne dla danego konkursu i potencjalnie wynikające z nadfitowania modelu do tego zestawu oceny. Sprawiedliwą oceną tego, jak model poradziłby sobie w przypadku nowych restauracji, pozostaje wynik z powtarzanego testu walidacyjnego, wynoszący około 20,10 miliona. Rozróżnianie tych dwóch wartości jest kluczowe zarówno dla interpretacji naukowej, jak i biznesowej.

    W rzeczywistym zastosowaniu prognozowania warstwa optymalizowana pod kątem konkurencji zostałaby usunięta, chyba że dostępny byłby odpowiedni sygnał kalibracyjny. System do wdrożenia wykorzystywałby zamrożony przepływ cech i zbiór modeli, monitorowałby reszty oraz zmiany w rozkładzie danych i ponownie szkoliłby modele po pojawieniu się rzeczywistych danych o obrotach.

    14. Założenia

    • Założono, że dane treningowe i testowe pochodzą z wystarczająco podobnych procesów biznesowych, aby kwalifikować się do walidacji krzyżowej. Nie zastosowano żadnych formalnych korekt spowodowanych zmianami w zmiennych, poza sprawdzeniami schematu i rozkładu danych.
    • Numer rejestracyjny stanowił wyłącznie identyfikator i nie został włączony do ostatecznego modelu, ponieważ nie istniał między nim a obrotami żaden uzasadniony związek.
    • Założono, że brakujące oceny udogodnień zawierają informacje strukturalne w przypadku braku danej usługi, co wpłynęło na projekt flagi dostępności.
  • Wszystkie użyte statystyki wstępnego przetwarzania musiały pochodzić z danych treningowych i być ponownie wykorzystywane bez zmian podczas inferencji – to zasada, której przestrzega sam pipeline, więc nie zależało to od pamięci analityka.
  • Dane rynkowe zewnętrzne były uznawane za dopuszczalne tylko wtedy, gdy poprawiały wyniki powtarzanych procesów walidacji oraz efektywność modeli typu ensemble. Sama wiarygodność nie wystarczała.
  • 15. Zalecenia

    • Traktuj jakość danych jak wydajność modelu. Niezgodności schematów, fragmentaryczne nazwy miast oraz niespójne dane kategoryczne mogą zniszczyć istotny sygnał, zanim algorytm go dostrzeże.
    • Rozróżnij „brakujące z powodu nieznajomości” od „brakujące, ponieważ dana cecha nie istnieje”. To drugie często stanowi zaletę, a nie problem.
    • W przypadku zniekształconych celów przychodowych należy śledzić wartość RMSE na końcu zestawienia, stopień koncentracji błędów kwadratowych oraz ogólną wartość. Kilka dużych kont może zadecydować o tym, czy prognoza ma znaczenie finansowe.
    • Należy stosować wyraźne zasady promocji: identyfikację kandydatów, weryfikację na niezależnych próbkach oraz zastępowanie modeli. Dzięki temu zysk uzyskany w jednej grupie nie zostanie przypadkowo uznany za najlepszy.
    • Należy traktować sztuczną inteligencję jako model operacyjny, a nie licencję na autonomiczną promocję. Niech agenci proponują, tworzą i kwestionują rozwiązania, podczas gdy decyzje dotyczące modeli i produktów o dużym wpływie pozostają w rękach ludzi.
    • Zapisy prawdziwych danych należy przechowywać gdzie indziej niż w oknie czatu. Zbiory danych, pliki manifestu, prognozy OOF, rejestr eksperymentów, „zamrożone” najlepsze rozwiązania oraz decyzje o ich promocji powinny być zachowywane, aby nowy agent mógł czerpać informacje z tych danych, a nie z wcześniejszych wiadomości.
    • Podczas eksperymentowania z sztuczną inteligencją należy oddzielić środowiska DEV, UAT i PROD. Pełna swoboda twórcza jest dozwolona w środowisku DEV, natomiast replikacja i krytyczna ocena odbywają się w UAT, a środowisko PROD powinno być deterministyczne, zamrożone i możliwe do odwrócenia.

    16. Lekcje

    Główną lekcją nie jest to, że jeden algorytm wygrał. Ustrukturyzowane formułowanie problemu, audyt danych, walidacja uwzględniająca rzadkie przypadki, inżynieria cech, zróżnicowane reszty oraz kontrolowany proces pracy agentów dały lepszy system prognozowania niż sama złożoność modelu. Dzięki DontPanic agenty pracowały jako zespół, przyspieszając odkrywanie i wdrażanie rozwiązań, podczas gdy wyraźne kontrolery ludzkie zapobiegały temu, by szybkość zastąpiła osąd.

    Finalny mistrz konkursu osiągnął wartość RMSE na poziomie około 2,893 miliona rupii po etapie optymalizacji dostosowanym do środowiska oceny. Ten wynik rozstrzygnął konkurs, jednak jest podawany oddzielnie, ponieważ może wskazywać na nadprzystosowanie modelu i nie powinien być traktowany jako oczekiwany błąd w przypadku przyszłych restauracji.

    Bardziej ogólna lekcja jest praktyczna: przeprowadź weryfikację przed jakąkolwiek optymalizacją, zachowaj informacje o nieudanych eksperymentach, uwzględniaj postępy tylko po ich powtórzeniu i nigdy nie myl modelu, który wygrywa z bazą referencyjną, z modelem, któremu moglibyśmy zaufać w warunkach produkcyjnych. A jeśli bierze udział kilka agentów AI, spraw, by „grali w siatkówkę” – pozwól im aktywnie pracować, ale upewnij się, że ważne decyzje trafiają do człowieka przed zaliczeniem punktu.

    Literatura pokrewna