Przewidź następny merge.
Każdy wiersz jest osobnym dokumentem. Remis wygrywa najmniejsza numerycznie para ID. Zliczamy także nakładające się pary, ale łączymy je od lewej bez nakładania.
Najpierw przewidź merge. Potem napisz własny rdzeń i wytrenuj tokenizer, który poradzi sobie z nieznanymi polskimi tekstami.
Ta misja prowadzi od małego przykładu na kartce do tokenizera działającego na tekstach, których nie widział podczas treningu. Potrzebujesz podstaw Pythona: pętli, list, słowników i funkcji. Nie potrzebujesz GPU ani znajomości sieci neuronowych. Część w przeglądarce działa bez konta; kod uruchomisz na swoim komputerze, a klucz bety będzie potrzebny do indywidualnego wyzwania i oceny.
Nie zaczynaj od wysyłania plików. Najpierw naucz się przewidywać jeden krok algorytmu. Potem przenieś tę samą regułę do kodu. Na końcu zajmij się jakością danych i kompresją. Jeśli coś nie działa, wróć do najmniejszego przykładu, dla którego potrafisz samodzielnie wyliczyć wynik.
proof.json odpowiada na pytanie: „Czy mój rdzeń wykonuje dokładnie zdefiniowany algorytm?”. Powstaje z Twojej implementacji i osobistego challenge’u. tokenizer.json odpowiada na inne pytanie: „Czy umiem zbudować użyteczny tokenizer dla różnorodnych polskich tekstów?”. To finalny artefakt o słowniku 8192 tokenów. Do jego treningu wolno użyć biblioteki ze startera. Nie musisz rozbudowywać małego challenge’u do 8192 tokenów.
Wyjaśnienia i małe testy są jawne. Właściwy challenge i ukryta ewaluacja pozostają zadaniem do rozwiązania. Możesz korzystać z AI, ale sprawdzaj jego odpowiedzi: poprawny plik sam w sobie nie dowodzi, że rozumiesz algorytm.
Tokenizer zamienia tekst na sekwencję identyfikatorów tokenów. Token nie musi być słowem: może reprezentować bajt, fragment słowa, całe słowo albo fragment zawierający spację. Słownik opisuje, co oznaczają identyfikatory; zakodowany tekst to lista tych identyfikatorów w odpowiedniej kolejności.
W tej części zaczynamy od bajtów UTF-8. Bajt ma jedną z 256 wartości, od 0 do 255, więc początkowe ID są właśnie tymi wartościami. ASCII a zajmuje jeden bajt, ale polskie ż zajmuje dwa. Liczba widocznych liter, liczba bajtów i liczba tokenów to trzy różne rzeczy.
# Uruchom w Pythonie i porównaj wyniki:
list("a".encode("utf-8")) # [97]
list("ż".encode("utf-8")) # [197, 188]
len("żółć") # 4 znaki w tym przykładzie
len("żółć".encode("utf-8")) # 8 bajtów
Dlaczego nie zacząć od listy polskich liter? Bo wejściem może być też emoji, nazwa w innym języku albo kod. Pełny alfabet bajtowy pozwala reprezentować dowolny poprawny tekst UTF-8. Pojedynczy bajt wielobajtowego znaku nie musi być samodzielnie czytelny; dekodujemy dopiero złożoną sekwencję bajtów.
Jeśli często spotykamy obok siebie 97, 98, możemy nadać całej parze nowe ID 256. To skrót dla bajtów ab. Tekst abab będzie teraz reprezentowany jako [256, 256] zamiast [97, 98, 97, 98]. Tekst się nie zmienił. Skróciła się jego reprezentacja.
Nie. Round-trip oznacza dokładnie decode(encode(tekst)) == tekst. Zmiana wielkości liter traci informację. Tak samo usunięcie podwójnych spacji albo końcowego znaku nowej linii. „Podobnie wygląda” nie znaczy „jest identyczne”.
Weź cztery osobne dokumenty: abab, abab, baba, aaaa. Duplikat jest tu celowy: drugi dokument także uczestniczy w zliczaniu. Nie dodajemy par pomiędzy końcem jednego dokumentu a początkiem następnego.
abab dostaniesz [97, 98, 97, 98].abab zobaczysz ab, ba, ab. To trzy pary, a nie dwie.ab występuje 5 razy, ba 4 razy, a aa 3 razy.(97, 98). Przy remisie wybierasz numerycznie najmniejszą parę: najpierw porównujesz lewe ID, potem prawe.Przed: Po połączeniu (97, 98) → 256: [97, 98, 97, 98] [256, 256] [97, 98, 97, 98] [256, 256] [98, 97, 98, 97] [98, 256, 97] [97, 97, 97, 97] [97, 97, 97, 97]
Nowy słownik jest większy o jeden wpis, a korpus zajmuje mniej tokenów. To istota BPE: kolejne często występujące fragmenty otrzymują własne identyfikatory. Nowe tokeny również mogą być częścią kolejnych par.
(97, 97) ma częstość 3. Para (256, 256) ma częstość 2; pozostałe mają po 1. Nowe ID będzie wynosić 257. Dokument aaaa zmieni się w [257, 257].
Podczas zliczania sprawdzasz pozycje (0,1), (1,2), (2,3): trzy wystąpienia. Podczas zamiany łączysz pozycje (0,1), a następnie (2,3). Środkowa para nakłada się na już wykorzystaną pozycję, więc nie może być osobną zamianą. W śladzie zapisujesz częstość 3 sprzed merge’u, nie liczbę zamian 2.
Zero. Każdy dokument ma tylko jeden token. Sklejenie dokumentów utworzyłoby parę (97,98), której nie ma w wejściu. To częsty błąd implementacji.
97,98 i wybierz „Sprawdź i połącz”. Porównaj listy z przykładem powyżej.aaaa, kliknij „Uruchom eksperyment” i połącz 97,97. Sprawdź różnicę między częstością a liczbą zamian.ab i ac. Obie pary mają częstość 1. Przewidź zwycięzcę regułą remisu.żż. Zaczniesz od czterech bajtów. Sprawdź, czy potrafisz wskazać parę, która odtworzy jedno „ż”.To ćwiczenie służy zrozumieniu reguł, nie zdobyciu flagi. Rozwiązanie czterech krótkich dokumentów nie zastępuje pełnego challenge’u ani treningu tokenizera.
Każdy wiersz jest osobnym dokumentem. Remis wygrywa najmniejsza numerycznie para ID. Zliczamy także nakładające się pary, ale łączymy je od lewej bez nakładania.
Znak „ż” zajmuje dwa bajty UTF-8. Kliknij „Uruchom eksperyment” po zmianie tekstu. Rozgrzewka nie zalicza właściwej misji.
Licznik pojawi się po kliknięciu podpowiedzi.
Brak merge’ów.
To edytowalny Python, nie animacja gotowego wyniku. Wybierz przykład, przewidź rezultat i kliknij „Uruchom”. Nie potrzebujesz instalacji ani klucza. Silnik Pyodide pobieramy z CDN dopiero przy pierwszym uruchomieniu — może to potrwać kilkanaście sekund.
Gotowe do pierwszego eksperymentu.
Tutaj zobaczysz print(), wyniki testów i błędy Pythona.
Kod wykonuje się na Twoim urządzeniu w osobnym workerze. „Zatrzymaj” przerywa także nieskończoną pętlę. Limit uruchomienia: 30 s; wynik: 20 tys. znaków. Każde uruchomienie ma nową przestrzeń zmiennych. Zmiana przykładu przywraca jego kod. Edycje nie są zapisywane po odświeżeniu — skopiuj je przed wyjściem. Nie wklejaj kluczy ani niezaufanego kodu. Demo nie wysyła rozwiązania do gradera i nie przyznaje flagi.
Otwórz archiwum, zaloguj się swoim dostępem do nagrań, a następnie wczytaj wideo tutaj. Klucz gradera i dostęp do archiwum są osobne.
Nagranie wymaga dostępu do archiwum.
Powyżej jest pełne nagranie kohorty, nie zmontowane krótkie lekcje. Przyciski przenoszą do punktów w nagraniu. Nie musisz oglądać całości przed rozpoczęciem pracy.
ż. Uzasadnij, dlaczego iterowanie po znakach Pythona nie daje tego samego co iterowanie po bajtach UTF-8.slayer-m0-tokenizer, w którym leżą requirements.txt i bpe_core.py. Samo otwarcie ZIP-a nie wystarczy.python --version (na części komputerów: python3 --version). Zalecamy Python 3.12. W dalszych komendach używaj tego samego interpretera.# macOS / Linux, z katalogu startera: python3 -m venv .venv source .venv/bin/activate python -m pip install -r requirements.txt cp manifest.example.json manifest.json
py -3 -m venv .venv .venv\Scripts\python.exe -m pip install -r requirements.txt Copy-Item manifest.example.json manifest.json # W kolejnych poleceniach zamiast python używaj: .venv\Scripts\python.exe check_core.py
Nie musisz zmieniać polityki wykonywania skryptów, jeśli uruchamiasz interpreter bezpośrednio ze środowiska.
bpe_core.py — Twoje cztery funkcje. Na początku NotImplementedError oznacza oczekiwany brak implementacji, nie zepsutą instalację.check_core.py — małe testy dydaktyczne. Uruchamiaj je po każdej funkcji; nie potrzebują konta, challenge’u ani finalnego tokenizera.make_proof.py — gotowe opakowanie: odczytuje challenge, wywołuje Twój rdzeń i zapisuje dowód.train_tokenizer.py — uzupełniasz wybór plików i konfigurację finalnego tokenizera. Pozostała obsługa treningu i zapisu już jest.grade.py — jawny test całego rozwiązania; uruchomisz go dopiero, gdy masz wszystkie artefakty.manifest.json — opis autorstwa i danych. Wpisz rzeczywiste źródła; wartości z przykładu nie są dokumentacją Twojego treningu.Na tym etapie sukcesem jest uruchomienie python check_core.py. Początkowo zobaczysz wskazówki przy niezaimplementowanych funkcjach. To punkt startowy; nie musisz jeszcze nic wysyłać.
count_pairs — co występuje obok czego?Wejściem jest lista dokumentów, a każdy dokument to lista ID. Wynikiem jest licznik, którego kluczami są pary ID. Najpierw obsłuż jeden dokument, potem sumuj wyniki wielu dokumentów. Nie sklejaj ich. Pusty dokument i dokument długości jeden nie wnoszą żadnej pary.
Wejście: [[97, 97, 97, 97]]
Oczekiwane zliczenie: {(97, 97): 3}
Wejście: [[97], [98]]
Oczekiwane zliczenie: {}
select_pair — jednoznaczny wybórWybierz największą częstość. Jeśli kilku kandydatów ma ten sam wynik, wybierz najmniejszą parę liczbową. Nie polegaj na kolejności dodawania do słownika. Wartości 98 i 256 porównuj jako liczby, nie napisy. Gdy par już nie ma, pętla treningowa powinna to wykryć przed wywołaniem wyboru.
Częstości: {(98, 97): 2, (97, 99): 2, (97, 98): 2}
Wynik: (97, 98)
merge_pair — zamiana bez utraty informacjiZwróć nowe listy; nie zmieniaj wejścia. Przechodź od lewej. Dopasowanie pary zużywa dwa elementy wejścia i daje jeden element wyjścia. Brak dopasowania przenosi jeden element bez zmiany. Pamiętaj o ostatnim elemencie, który może nie mieć sąsiada.
Wejście: [[97, 97, 97]], para: (97, 97), nowe ID: 256 Wyjście: [[256, 97]] Nie: [[256, 256]] — taki wynik wykorzystałby środkowy bajt dwa razy.
train_bpe — połącz kroki w pętlęZamień każdy tekst na jego bajty UTF-8. W każdej iteracji policz pary, wybierz zwycięzcę, zapisz jego częstość przed zamianą, wykonaj merge i przejdź do następnej iteracji. Nowe ID to 256, 257, 258… Nie resetuj tego licznika pomiędzy dokumentami.
# Pierwszy wiersz śladu dla przykładu z lekcji:
{"left_id": 97, "right_id": 98, "result_id": 256, "frequency": 5}
Uruchom python check_core.py. Jeśli test nie przechodzi, porównaj wynik z przykładem ręcznym. Przy błędzie w późniejszym merge’u szukaj pierwszego rozbieżnego kroku — wszystkie kolejne mogą być tylko jego konsekwencją. Małe testy nie stanowią pełnego dowodu poprawności; dodaj własne przypadki.
Sprawdź brak modyfikacji wejścia, pusty korpus, dokument jednoelementowy, remis między ID 98 i 256, powtarzającą się parę złożoną z nowych tokenów i polskie znaki. Dla korpusu, w którym pary się wyczerpały, zakończ trening zamiast wymyślać kolejne merge’e. Serwerowy challenge wymaga pełnej zadanej liczby kroków.
Połącz konto kluczem w panelu poniżej i wybierz „Pobierz mój challenge”. Przenieś pobrany challenge.json do katalogu startera. Nie zmieniaj jego korpusu, ID ani sumy kontrolnej. Następnie uruchom:
python make_proof.py
Oczekiwany wynik dla tej bety to proof.json z 192 merge’ami. Challenge jest ważny 72 godziny; datę ważności widzisz przy pobraniu. Po wygaśnięciu pobierz nowe wyzwanie, wygeneruj nowy dowód i zaktualizuj ID w manifeście. Finalny tokenizer możesz rozwijać niezależnie od tego terminu.
Challenge sprawdza mechanikę algorytmu. Teraz chodzi o to, czego algorytm nauczy się z danych. Tokenizer wytrenowany wyłącznie na jednym rodzaju tekstu może dobrze kompresować ten rodzaj tekstu, a słabo radzić sobie z innymi domenami.
data/. Uwzględnij różne typy polskich tekstów: narrację, dialog, treści techniczne, liczby i kod. Zapisz źródło oraz licencję każdego wykorzystanego zbioru. Nie traktuj tekstu dostępnego w internecie automatycznie jako danych do dowolnego użytku.data/, żeby funkcja zbierająca pliki nie dołączyła jej do treningu. Usuń duplikaty między częściami. Walidacja ma pytać o nowe teksty, nie o zapamiętane przykłady.corpus_files. Zwróć stabilnie uporządkowaną listę rzeczywistych, niepustych plików tekstowych. Sprawdź ją przed długim treningiem.build_tokenizer. Potrzebujesz modelu BPE, zgodnego byte-level pre-tokenizera i dekodera oraz alfabetu pozwalającego reprezentować każdy bajt. Nie dodawaj stratnej normalizacji. W tej części biblioteka tokenizers jest dozwolona.python train_tokenizer.py. Powinien powstać samowystarczalny tokenizer.json z dokładnie 8192 wpisami słownika, łącznie z dodanymi tokenami. Zbyt małe lub zbyt jednorodne dane mogą nie wystarczyć do osiągnięcia tej liczby.Sprawdź zdanie z polskimi znakami, emoji, tabulator, kilka spacji i końcowy newline. Jeśli odkodowany tekst różni się od oryginału, nie poprawiaj jeszcze wyniku kompresji — najpierw napraw reprezentację. Usuwanie znaków nie jest dozwoloną metodą „ulepszania” wyniku.
from tokenizers import Tokenizer
t = Tokenizer.from_file("tokenizer.json")
text = "Łódź 2026\t🙂\n"
ids = t.encode(text, add_special_tokens=False).ids
restored = t.decode(ids, skip_special_tokens=False)
print(repr(text), repr(restored))
assert restored == text
Używamy znaków na token: liczba znaków podzielona przez liczbę tokenów. Jeśli 1200 znaków daje 400 tokenów, wynik wynosi 3,00. Jeśli kolejny tokenizer daje 300 tokenów dla tego samego tekstu, wynik wynosi 4,00 — lepiej. Nie myl tego z tokenami na znak: ta odwrotna miara powinna maleć.
Wynik globalny licz z sum znaków i sum tokenów, a nie jako zwykłą średnią wyników dokumentów. Sprawdzaj również każdą domenę osobno. Dobry wynik na długich tekstach literackich może ukryć słaby wynik na krótkich przykładach technicznych.
python grade.py tokenizer.json proof.json challenge.json
Jawny test wypisuje rozmiar słownika, wynik łączny i wyniki domen. Jego progi to 3,30 globalnie i 2,50 dla każdej domeny. Aktualne progi ukrytej oceny pobieramy z serwera w panelu kontraktu poniżej. To inne teksty: wyższy wynik na publicznej próbce nie gwarantuje wyniku na ukrytej.
Zapisz dla każdego wariantu: skład danych, konfigurację, wynik globalny, najsłabszą domenę i jedną hipotezę. Zmieniaj jedną istotną rzecz naraz, na przykład udział tekstów technicznych. Zachowuj wcześniejsze artefakty. Porównuj na tej samej własnej walidacji i nie dokładaj jej do treningu po nieudanym wyniku.
Nie jest to dowód generalizacji. Wynik na tych przykładach może wzrosnąć, ale przestają być niezależnym testem. Ukryty zbiór sprawdza inne teksty. Celem jest poprawa reprezentacji całego rodzaju danych, nie zapamiętanie pojedynczych zdań.
manifest.json wpisz handle swojego konta, ID z bieżącego challenge.json, prawdziwe źródła i licencje oraz krótki opis przygotowania danych. Usuń przykładowe wartości; zachowaj schemat i nazwę modułu.tokenizer.json, proof.json i manifest.json. challenge.json jest wejściem do pracy lokalnej — nie wybierasz go zamiast dowodu.Wróć do kroku 06. Sprawdź zgodność challenge’u, regułę remisów, zliczanie nakładających się par oraz granice dokumentów. Zapisuj listy po każdym merge’u i porównaj pierwszy rozbieżny krok z małym ręcznie policzonym przykładem. Więcej danych treningowych nie naprawi błędu w rdzeniu.
Wróć do konfiguracji z kroku 07. Sprawdź alfabet bajtowy, dekoder i normalizację. Wypisz repr wejścia oraz wyjścia, żeby zobaczyć niewidoczne spacje i nowe linie. Nie usuwaj problematycznego przykładu z testu.
Najpierw potwierdź poprawność techniczną. Potem porównaj domeny na własnej walidacji, przejrzyj tokenizację kilku nowych tekstów i zapisz hipotezę o brakujących danych. Zmiana miksu może poprawić jedną domenę kosztem innej — oceniaj cały raport.
To możliwy wynik eksperymentu, nie sprzeczność. Publiczna próbka jest mała i jawna; ukryta ma inne przykłady. Poszerz niezależną walidację i popraw generalizację. Beta nie jest jeszcze gwarancją jakości na każdym możliwym tekście.
Po odświeżeniu strony ponownie wpisz klucz — nie zapisujemy go trwale w przeglądarce. Jeśli challenge wygasł, pobierz nowy i przelicz dowód. Błąd infrastruktury nie jest diagnozą jakości tokenizera: zachowaj identyfikator próby i zgłoś go prowadzącemu, bez ujawniania klucza.
1. proof.json — pełne 192 merge’e z indywidualnego challenge’u.
2. tokenizer.json — dokładnie 8192 tokeny, bezstratny round-trip i zero UNK.
3. manifest.json — Twój handle, ID challenge’u oraz źródła danych.
Ładowanie aktualnych progów…
Kod uruchamiasz u siebie. Tutaj pobierasz challenge, wysyłasz pliki i otrzymujesz wynik. Sam publiczny PASS nie gwarantuje flagi.
python fetch_challenge.py python make_proof.py python train_tokenizer.py python grade.py
Klucz otrzymujesz od prowadzącego betę. Przechowujemy go wyłącznie w pamięci tej karty; po odświeżeniu połącz się ponownie.
Połącz konto, aby wysłać rozwiązanie.
Historia pojawi się po połączeniu z kontem.