Tworzenie zadań na SprawdzAI
Na naszej platformie umożliwiamy wiele sposobów współpracy. Niezależnie od tego, czy chcesz poszerzyć naszą bazę o nowe zadanie stworzone przez Ciebie lub znalezione w innych źródłach, czy chcesz zorganizować własny konkurs lub dołożyć swoją cegiełkę do tych już zaplanowanych, czy może wykorzystać nasz system do własnego rozwoju lub prowadzonego przez Ciebie kółka - z chęcią Ci pomożemy!
Jeśli przymierzasz się do stworzenia własnego zadania, lub nie masz jeszcze pomysłu, ale po prostu chcesz zapoznać się z systemem, to wystarczy, że napiszesz do jednego z organizatorów koła AI na MIKO (np. do afedafed), na oficjalne konto Discord SprawdzAI, lub e-mail sprawdzai@mikomath.org. Otrzymasz wtedy dostęp do edycji własnych zadań.
Dalsza część tej strony pokaże Ci, jak poprawnie edytować zadania (zakładając, że masz już link do swojego pierwszego zadania). Zapozanie się z całym tym dokumentem może zająć kilkanaście minut, ale i tak w porównaniu do czasu który zajmuje stworzenie całego zadania (napisanie treści, przygotowanie zbiorów itp.) to wcale nie jest tak dużo. Zalecamy dokładne przeczytanie go przed rozpoczęciem tworzenia zadania - gdy już zapoznasz się dobrze z systemem, po przygotowaniu treści i zbiorów danych, integracja z SprawdzAI często zajmie Ci zaledwie kilka minut!
Korzystanie z CLI
Przy tworzeniu zadań bardzo pomocny będzie dla Ciebie stworzony przez nas command line interface napisany w Pythonie. Pozwoli Ci on na łatwe utrzymywanie lokalnej kopii zadania. Możesz wtedy edytować wszystkie pliki lokalnie a następnie jedną komendą zsynchronizować je z serwerem. Posiadanie i korzystanie z CLI jest opcjonalne, ale cały ten tutorial będzie głównie przeznaczony edycji przez CLI. Te same operacje można zawsze wykonać z poziomu przeglądarki w intuicyjny sposób, więc nie będziemy się tu w nie zgłębiać. Możesz go pobrać za pomocą komendy:
pip install --upgrade sprawdzai-cli
Zweryfikuj instalację:
sai
Następnie zaloguj się na swoje konto:
sai login
Możesz szybko sprawdzić informacje o swoim koncie przy użyciu:
sai me
Opis każdej komendy znajdziesz w:
sai [COMMAND] --help
sai
pozwala również na korzystanie z wielu kont na raz (jeśli np. chcesz mieć osobne konto do edycji zadań). W tym celu możesz użyć komendy sai backend
i flagi --backend
(więcej informacji: sai login --help
). Aby wylogować się z konta (lub wszystkich kont jeśli masz zalogowałeś(-aś) się na kilka), użyj sai logout --all
.Następnie możesz już łatwo utworzyć lokalną wersję zadania:
sai pull [id | slug | url]
Na przykład
sai pull https://sprawdzai.org/zadanie/slug-zadania/...
lub sai pull slug-zadania
. Po wynokaniu tej komendy, otrzymasz komunikat gdzie został stworzony folder z zadaniem. Standardowo jest to folder o takiej samej nazwie jak zadnie, chyba że została użyta flaga --location
. Wejdź do folderu z zadaniem:cd folder-z-zadaniem
Możesz teraz dowolnie edytować zadanie lokalnie a następnie synchronizować je z serwerem za pomocą:
sai sync
sai
obsługuje również zdalne zmiany oraz konflikty. Innymi słowy, możesz jednocześnie edytować zadanie lokalnie jak i z poziomu przeglądarki. Po wykonaniu synchronizacji sai sync
, wszystkie zmiany które zostały wykonane z zewnątrz zostaną automatycznie przeniesione do kopii lokalnej, a wszystkie zmiany które zostały wykonane lokalnie zostaną przeniesione na serwer. Jeśli wystąpią konflikty, czyli to samo pole zostało jednocześnie zmienione zdalnie jak i lokalnie, otrzymasz informację o konfilkcie i możliwość rozwiązania go w dowolny sposób.Wstęp
- Uruchamiamy notatnik, który generuje plik cloudpickle.
- Uruchamiana jest checkerka (checker.py):
- Najpierw ładuje ona wszystkie labelki (odpowiedzi) testowe do RAM.
- Usuwa ona wszelkie pliki z odpowiedziami i zostawia tylko te dane, które wejdą na wejście funkcji cloudpickle. Gdyby nie to, plik cloudpickle mógłby po prostu odczytać wszystkie odpowiedzi.
- Uruchamia plik cloudpickle na osobnym procesie (bezpośrednie uruchomienie wewnątrz checker.pysprawiłoby, że trywialnie dałoby się odczytać odpowiedzi lub przypisać sobie dowolny wynik).
- Na tym osobnym procesie zachodzi zapisanie wyjścia funkcji do pliku/plików.
- Checkerka liczy wynik na podstawie załadowanych do zmiennych odpowiedzi i zapisanych predykcji.
Aby efektywnie tworzyć bezpieczne i jakościowe zadania, musisz najpierw zrozumieć jak działa system oraz czemu został stworzony w taki a nie inny sposób. W SprawdzAI musieliśmy pogodzić ze sobą kilka cech jakie powinien mieć dobry system oceny. Zadania powinny być bezpieczne, tzn. niezależnie od woli edytora oraz użytkowników, żaden inny użytkownik ani system nie powinien ucierpieć. Jednocześnie nie powinna być możliwa zmiana swojego wyniku na inny poprzez hackowanie systemu, a w szczególności nie powinna też być możliwa ekstrakcja danych ze zbioru testowego. Wreszcie, chcielibyśmy też przekazywać użytkownikom możliwie wiele informacji na temat ich zgłoszenia, aby pomóc im naprawić błędy w swoim rozwiązaniu.
Dlatego standardowo dzielimy proces sprawdzania na dwie fazy. W pierwszej, czyli uruchomieniu notatnika, ma on dostęp wyłącznie do danych treningowych (tych, do których użytkownicy najczęściej i tak mają bezpośredni dostęp), więc możemy udostępniać całe wyjście zwrócone podczas tej fazy (STDOUT oraz STDERR uruchomienia notatnika). Na jej koniec notatnik generuje plik cloudpickle, który zostanie uruchomiony w czasie ewaluacji. Ma on wtedy dostęp do danych testowych, choć nie powinien oczywiście mieć dostępu do etykiet/odpowiedzi. Ale nawet same dane, gdyby zostały wyekstraktowane, stanowiłyby istotne ułatwienie rozwiązywania (np. ktoś mógłby je manualnie sklasyfikować). Dlatego też z tej fazy najczęściej udostępniamy już minimalne logi, i dlatego taki podział w ogóle istnieje - dzięki temu możemy dawać pełne logi dopóki możemy, a ograniczać je dopiero, gdy dane testowe jakkolwiek mogą wpływać na algorytm. W skrócie standardowy proces sprawdzenia zadania wygląda następująco:
Oczywiście to jest tylko najbardziej standardowa struktura - SprawdzAI pozwala na wiele innych styli zadań. Jednym z nich mogą być zadania interaktywne, tzn. takie gdzie chcielibyśmy często uruchamiać kod który będzie stale wpływać na proces sprawdzania (przykładem może być "szum w etykietach danych" z II edycji OAI). Tego rodzaju zadania są tak różnorodne i nietypowe, że zostały pominięte w tej dokumentacji. Jeśli chcesz zrobić takie zadanie, skontaktuj się z administracją o pomoc.
Co istotne, można też pójść w drugą stronę, czyli odpuścić część z założeń które postawiliśmy sobie na początku tej sekcji na rzecz prostszego tworzenia zadań. Można na przykład bezpośrednio przekazać do uruchomienia notatnika dane walidacyjne/testowe, który wygeneruje plik z predykcjami. Wtedy checkerka może pominąć większość z wcześniejszych kroków (tzn. nie musi nic uruchamiać na osobnym procesie ani usuwać wszystkich plików z odpowiedziami), a Ty pisząc checkerkę od razu przechodzisz do ostatniego podpunktu, czyli po prostu policzenia metryki w zadaniu. Wiąże się to oczywiście z ogromnym ryzykiem ekstrakcji danych, bo ktoś może wtedy po prostu je wypisać na STDOUT czy STDERR. Jeśli zależy Ci na prostocie stworzenia zadania a niekoniecznie na tym, czy ktoś będzie w stanie oszukać system, to możesz rozważyć tą opcję. Alternatywnie, możesz też wtedy po prostu zmniejszyć limit znaków STDOUT i STDERR (patrz: następna sekcja) - wtedy zachowujesz brak możliwości ekstrakcji danych testowych, ale kosztem innego założenia z początku, czyli aby użytkownicy otrzymywali możliwie wiele logów do debuggowania.
Zazwyczaj zachęcamy jednak do pisania zadań według ogólnej formy plików cloudpickle (o ile to możliwe). Choć ten wstęp był dość długi, to po zrozumieniu wszystkiego, tak naprawdę tworzenie zadań uruchamiających osobny proces jest marginalnie trudniejsze i dłuższe, ponieważ postaraliśmy się, aby pisanie zadań było jak najszybsze i ułatwiliśmy to również poprzez zapewnienie biblioteki w której jest już napisane niemalże wszystko, czego możesz potrzebować. Rozumiejąc już lepiej system, możemy wreszcie przejść do faktycznej edycji zadań!
Edycja metadanych
- title- tytuł zadania. Na ogół powinien zawierać maksymalnie 4 słowa.
- short_description- opis zadania. Może zawierać znaki nowej linii, ale nie jest to zalecane i nie będzie widoczne na liście zadań.
- tags_ids/ tagi - zalecamy edycję z przeglądarki.
- slug/ niestandardowy link - link pod którym będzie zadanie. Może się składać wyłącznie z małych liter alfabetu łacińskiego, cyfr oraz znaków-.
- difficulty/ trudność - trudność zadania w zakresie 1,0 - 7,0. Jeśli nie chcesz w zadaniu podawać trudności lub nie została ona ustalona, pozostaw to pole puste (lub ustawnullwmetadata.json).
- notebook_limits,checker_limits/ tabelka w zakładce zgłoszeń - ograniczenia w trakcie fazy uruchomienianotebookoraz ewaluacjichecker. Dane wyświetlane w zielonym nagłówku są limitami dla notatnika.
- time/ limit czasu - w sekundach.
- ramivram/ limit RAM i VRAM - wmetadata.jsonwartość"ram": 1048576oznacza 1MB, ale można też wpisać np."ram": "1m"lub"ram": "1024k". W przypadku formy z uruchamianiem pliku cloupickle, warto uruchomieniu checkerki dać więcej pamięci, tzn. o tyle więcej, ile zajmuje sama checkerka (o ile zajmuje cokolwiek) - np. trzymanie danych w RAM albo ewentualne obliczenia wykonywane przez nią na GPU. Po prostu dla wygody użytkowników powinno być tak, że z ich perspektywy, mają tyle RAM ile było podane w zadaniu, czyli limit dla checkerki powinien być sumą tego oraz pamięci którą tak czy siak wykorzystuje checkerka.
- cpus/ % kor CPU - dodatkowy limit na sumaryczną ilość zużycia kor CPU w zadaniu, na przykład 150% ("cpus": 150w CLI) doda ograniczenie na ilość zużycia CPU równego 150% mocy pojedynczej kory. O ile nie ma nietypowych potrzeb na dodatkowe ograniczenie szybkości CPU, sugerujemy ustawić na 1000%, co spowoduje wykorzystanie całej dostępnej mocy CPU (przeważnie będą to i tak tylko jedna lub cztery kory - 1000% jest tylko dodatkowym limitem który nie zostanie wtedy wykorzystany).
- cpu_only/ Tylko CPU - włączenie tej opcji spowoduje zablokowanie dostępu do GPU.
- allow_internet/ Dostęp do internetu - bez konkretnej i dokładnie ustalonej wcześniej potrzeby, prosimy nigdy nie dawać dostępu do internetu, ponieważ może to prowadzić do nadużyć systemu.
- compute/ rodzaj mocy obliczeniowej - decyduje, na której maszynie obliczeniowej będzie sprawdzane zadanie. Informacje o tej fladze powinieneś otrzymać wraz z linkiem do zadania.
- submission_files/ pliki zgłoszeniowe - pliki, które należy przesłać przy wysyłaniu zgłoszenia, oraz ich maksymalny rozmiar. Na CLI można używać tych samych skrótów do ilości pamięci, np."max_file_size": "100k".
- valid,test/ druga tabelka w zakładce zgłoszeń - ograniczenia w przypadku zgłoszeń wysyłanych na zbiór walidacyjny lub testowy.
- daily_submissions/ Dzienny limit zgłoszeń - ustaw na 0, aby całkowicie wyłączyć możliwość wysyłania zgłoszeń na dany zbiór. Na stronie będzie to wtedy poprawnie wyświetlane - utracisz możliwość przesyłania ponownie oraz wysyłania na dany zbiór jeśli jego limit wyniesie 0. Jeśli oba limity wyniosą 0, to zakładka zgłoszeń oraz przycisk do wysyłania zgłoszeń znikną. Jako edytor zadania omijasz jednak ten limit - wystarczy, że wejdziesz w tryb edycji, a zakładka pojawi się ponownie i będziesz mógł wysyłać na oba zbiory niezależnie od ustawionych tu limitów. Ustaw na"daily_submissions": nullaby wyłączyć limit dzienny (zgłoszenia bez limitu).
- notes_char_limit,stdout_char_limit,stderr_char_limit/ Limit znaków notatek, STDOUT, STDERR - limit ilości znaków na kolejne pola widoczne w detalach zgłoszenia (notatkami nazywamy to co zwróci checkerka - to pole na górze, nad STDOUT). Prosimy nie przekraczać zdroworozsądkowych wartości rzędu 20000, aby nie zawalić nam miejsca na dysku nadmiernymi ilościami logów. Standardowe wartości ~300 na notatki i 10000 na STDOUT oraz STDERR są przeważnie zupełnie wystarczające aby przekazać wszystkie istotne informacje i nie przyciąć żadnych błędów.
- display_ranking/ ranking widoczny - czy ranking ma być widoczny dla wszystkich użytkowników. Po wyłączniu będzie wciąż dostępny dla Ciebie i administratorów po wejściu w tryb edycji.
- score_mode/ typ liczenia rankingu - jest to niedokończona jeszcze opcja, prosimy zawsze ustawiać ją na "Metryka" ("score_mode": "raw"), bo inaczej zaczną się dziać dziwne rzeczy!
- baseline_score- dotyczny innych ustawień liczenia rankingu. Przy ustawieniu "Metryka", wartość ta niczego nie zmienia, więc nie należy się nią na razie w ogóle przejmować (podobnie jak poprzednią) dopóki te opcje nie zostaną zaimplementowane.
Aby edytować zadanie na przeglądarce, wciśnij przycisk "Edytuj zadanie" znajdujący się nad przyciskiem do wysyłania zgłoszeń (przycisk ten pojawi się, gdy będziesz posiadać uprawnienia do edycji zadania). Wszystkie pola znajdują się w odpowiadających im miejscach - w zielonym nagłówku, oraz zakładkach treści, zgłoszeń i rankingu. Do edycji lokalnej przeznaczony jest plik
metadata.json
. Edycja tytułu, opisu, tagów czy niestandardowego linku będzie prostsza na stronie, ponieważ od razu widać zmiany wizualne. Edycję większości pozostałych pól (zwłaszcza plików oraz zbiorów danych) lepiej wykonywać lokalnie przez sai
. Dlatego przeważnie podczas edycji zadania najlepiej mieć jednocześnie otwarte środowisko programistyczne (np. Visual Studio Code z otwartym terminalem) oraz stronę, co pozwoli wprowadzać zmiany tam gdzie wygodniej i od razu widzieć zmiany wizualne na stronie.
Obsługa strony i dodatkowe uprawnienia
Jako edytor zadania, masz dostęp do wielu fantastycznych rzeczy. Będąc na stronie, dopóki nie wejdziesz w tryb edycji, widzisz zadanie w dokładnie ten sam sposób, jak będą je widzieć wszyscy inni. Po wejściu w tryb edycji będziesz miał dostęp do zakładki zgłoszeń i rankingu nawet jeśli zostały wyłączone. Omijasz też wszelkie limity na zgłoszenia - zarówno te dzienne jak i globalny limit jednego zgłoszenia na raz. Kolejki na SprawdzAI raczej nie występują, ale gdyby wystąpiła wyjątkowa sytuacja w której zacznie się pojawiać trochę zgłoszeń, prosimy nie nadużywać obchodzenia limitów żeby ktoś nie czekał godzinę na skończenie kilkunastu Twoich zgłoszeń. Jeśli kolejka jest i tak pusta, to nikt nie zabrania, żeby wysyłać dużo zgłoszeń do testowania. Jako edytor otrzymujesz również dostęp do wszystkich informacji potrzebnych do debuggowania checkerek - znajdziesz je w każdym zgłoszeniu do tego zadania, na dole detali zgłoszenia. Poza tym, masz też możliwość wykonywania rejudge zgłoszeń oraz możesz widzieć zgłoszenia wszystkich osób w tym zadaniu.
Obsługiwanie zbiorów danych
Każdy zbiór danych na SprawdzAI to po prostu jeden plik, najczęściej
.zip
. Wszystkie zbiory danych znajdziesz w zakładce Pliki, która znajduje się w rozwijanym pasku po kliknięciu Twojej nazwy użytkownika w prawym górnym rogu strony (aby mieć dostęp do tej zakładki, musisz być edytorem jakiegoś zadania). Ciągłe zippowanie folderów, wysyłanie ich do zakładki i managowanie przypisań po stronie zadania byłoby bardzo żmudne, więc na szczęście zbiory danych zostały również zintegrowane z CLI. Są cztery możliwe fazy sprawdzania (określane w CLI jako "stages") - możesz dać osobne zbiory na zgłoszenia wysyłane na zbiór walidacyjny i testowy, oraz osobne dla uruchomienia notatnika (faza "uruchamianie") i osobne dla uruchomienia checkerki (faza "ewaluacji"), co daje 4 możliwości na wszystkie pary.Najważniejszą komendą jest
, która pokaże Ci, jakie zbiory danych są przypisane do każdej fazy. Stamtąd uzyskasz też linki oraz id do swoich zbiorów. Uważaj, aby te linki nie wyciekły, ponieważ posiadanie takowych pozwoliłoby np. na bezpośrednie pobranie zbioru testowego. Na początku lista zbiorów powinna być pusta.
sai ds
Możesz stworzyć nowy dataset wykonując:
Zalecamy nie używania spacji i w ogóle innych znaków niż cyfry i litery w nazwie zbioru danych, bo utrudni to dalszą pracę. Jeśli teraz jeszcze raz uruchomisz
Na przykład
, która dołączy zbiór do danej fazy, np.
dołączy zbiór do zgłoszeń testowych (zarówno do fazy uruchomienia jak i ewaluacji).
sai ds create [nazwa]
sai ds
, to zobaczysz, że nowy zbiór został stworzony i jest automatycznie dołączony do wszystkich faz. To oznacza, że będzie widoczny zarówno podczas uruchomienia notatnika jak i dla checkerki (oraz uruchamianych plików cloudpickle), zarówno dla zgłoszeń walidacyjnych jak i testowych. Możesz odłączyć zbiór od jakiejś fazy za pomocą:
sai ds unlink [nazwa] --notebook/--checker (-n/-c) --valid/--test (-v/-t)
sai ds unlink [nazwa] -n -v
odłączy zbiór z fazy uruchamiania notatnika dla zgłoszeń walidacyjnych, a np. sai ds unlink [nazwa] -c
odłączy z fazy checkerki zarówno dla zgłoszeń walidacyjnych jak i testowych. Podobnie działa komenda
sai ds link [nazwa] --notebook/--checker (-n/-c) --valid/--test (-v/-t)
sai ds link [nazwa] --test
Możesz dowolnie edytować zawartość każdego folderu ze zbiorem danych, a potem wykonać:
co automatycznie spakuje go do .zip oraz wyśle na serwer. Co istotne, pliki będą pojawiać się bezpośrednio w folderze notatnika lub checkerki, tzn. jeśli stworzysz plik
Jeśli chcesz pobrać jakiś zbiór danych z powrotem z serwera (automatycznie zostanie rozpakowany jeśli był zip-em), uruchom:
Możesz też użyć
, aby pobrać wszystkie zbiory danych, które zostały podłączone do zadania, a nie masz ich pobranych lokalnie.
sai ds send [nazwa]
folder-z-zadaniem/nazwa-zbioru-danych/foo.bar
, to będzie on na sprawdzarce dostępny bezpośrednio jako ./foo.bar
(a nie na przykład ./nazwa-zbioru-danych/foo.bar
). Czasem zamiast tworzyć zbiór danych który się składa z całej struktury plików i folderów wolisz, aby zbiór danych był po prostu jednym plikiem, czyli zamiast spakowywać pojedynczy plik do zipa a potem musieć go rozpakowywać, chciałoby się po prostu bezpośrednio wysłać ten jeden plik. Wtedy możesz użyć tego polecenia, które wypisze błąd, jeśli wewnątrz folderu ze zbiorem danych nie ma dokładnie jednego pliku:
sai ds send [nazwa] --no-zip (-n)
sai ds pull [nazwa]
sai ds pull --missing (-m)
Na SprawdzAI mamy też kilka globalnych datasetów które mogą się przydawać w wielu zadaniach i nie chcieliśmy ich duplikować za każdym razem. Wszystkie je znajdziesz w zakładce plików. Nie masz uprawnień do ich edycji, ale możesz je dołączać do swojego zadania oraz pobierać. Aby dołączyć taki zewnętrzny zbiór danych, użyj po prostu:
Na przykład
dołaczy plik z resnet18 do wszystkich czterech faz.
sai ds link [id] --notebook/--checker (-n/-c) --valid/--test (-v/-t)
sai ds link 28d12307-3880-4bdc-bbf6-68fefb3a434d
Uwaga. Sai wykorzystuje nazwy folderów aby wiedzieć, który folder to który dataset. Jeśli zmienisz nazwę folderu, to sai przestanie wiedzieć, jakim datasetem ten folder jest. Zamiast tego, jeśli chcesz zmienić nazwę zbioru danych (i tym samym folderu), użyj:
sai ds rename [nazwa] [nowa nazwa]
Przykładowymi poleceniami tworzącymi standardowy układ zbiorów mogłoby być:
sai ds create train
sai ds create valid --checker --valid
sai ds create test --checker --test
statement.ipynb
- To jest plik z treścią zadania. Tak jak wszystkie inne pliki, możesz go dowolnie edytować a następnie wywołać
-
Pierwsza komórka powinna być typu markdown i zawierać dokładnie:
# <div align="center">Tytuł</div> ---
-
Jeśli chcesz zachować informacje o autorze zadania, należy w kolejnej komórce umieścić:
<h4> Autor zadania: ... </h4>
- Nie pisz całej treści zadania w jednej komórce - znacznie bardziej wygodne dla Ciebie i użytkowników będzie (w miarę możliwości) rozdzielenie różnych sekcji treści na osobne komórki.
-
Obrazki powinny być umieszczane jako tagi HTML, na przykład:
Zachęcamy, aby nie dawać obrazków z internetu (bo ich linki mogą wygasnąć), tylko w zakładce pliki w sekcji "obrazki" dodać obrazek i skopiować jego link.<img width="500" src="https://sprawdzai.org/static/files/image/..." alt="Opcjonalny opis obrazka" />
- Stosować ogólne zaimki, np. zrobiłeś(-aś), bo są stosowane na większości strony.
- Na większości strony zapisujemy również zwroty w 2. osobie (np. "Ci", "Twój") wielką literą, ale pozostawiamy decyzję na temat używania ich jako osobiste uznanie edytora.
sai sync
aby łatwo przenieść zmiany na serwer. Nie mamy tutaj wiele do dodania poza tym, że w celu zachowania spójności systemu, powinieneś stosować się do standardowego formatowania na SprawdzAI, czyli:
baseline
- Standardowo, przy używaniu
sai
, plik do pobrania (ten, który można pobrać przyciskiem, ale jednocześnie też ten, który będzie otwierać się w Google Colab) będzie tym samym plikiem co statement.ipynb
. Czasem jednak chciałoby się dać co innego do treści i co innego do pobrania. Z poziomu strony wychodzi to samo z siebie, bo trzeba osobno przesłać plik baseline
oraz statement.ipynb
. Na sai
musisz po prostu stworzyć plik o nazwie baseline
(z dowolnym rozszerzeniem lub bez, np. baseline.ipynb
) i dowolnie go edytować, a jeśli chcesz, aby baseline
był tym samym plikiem co statement.ipynb
, po prostu usuń go, a CLI automatycznie przerzuci się na korzystanie z statement jako baseline. Jeśli chcesz, aby pojawiała się opcja otwierania w Google Colab, to rozszerzeniem baseline musi być .ipynb (lub ten plik może zostać usunięty - wtedy automatycznie będzie brane statement.ipynb
, czyli rozszerzeniem siłą rzeczy będzie .ipynb). Po każdej z tych zmian oczywiście musisz uruchomić sai sync
aby zmiany weszły w życie.image
- Podobnie jak w przypadku
baseline
, możesz stworzyć plik image
(z dowolnym rozszerzeniem) aby dodać obrazek do zadania wyświetlany w liście zadań. Jeśli usuniesz ten plik, to obrazek zniknie. Jednak każde zadanie powinno mieć swój obrazek. W sytuacji ostatecznej, możesz dodać generyczny obrazek, np. jakieś logo.requirements.txt
Standardowy plik z bibliotekami dla
pip
. Zostanie pobrany za pomocą uv komendą uv pip install -r requirements.txt
, ale w praktyce działa to tak samo, jak zwykły pip
. W informacjach do debuggowania w każdym zgłoszeniu (do których masz dostęp jako edytor tego zadania) znajdziesz ewentualne błędy i logi, które powstały przy intsalowaniu paczek. Zalecamy dodawanie wersji do paczek, aby uniknąć zdziwienia w przyszłości.Uwaga. Pakiety
torch
, torchvision
, torchaudio
oraz triton
są instalowane osobno. Nie możesz dopisywać do nich wersji ani innych takich rzeczy. Dla tych czterech pakietów musi być napisane konkretnie np. torch
(w jednej linii requirements.txt) i nic więcej! Nie możesz napisać np. torch=...
!initiator.py
- Edytować submission.pyprze uruchomieniem.
- Uruchamiać go w inny sposób, np. import submissionlub przezexec()- wtedy można robić zadania w stylu 1. i 3. edycji OAI, gdzie ma się tylko stworzyć zmienne, a nie zapisywać je do cloudpickle (wtedy po odczytaniu lub zaimportowaniu ich, możesz je samemu zapisać z poziomuinitiator.py).
- Uruchamiając w inny sposób możesz też zmieniać zachowanie w różny sposób, np. zablokować dostęp do jakichś bibliotek.
Ten plik zostanie uruchomiony w fazie uruchomienia notatnika. Tak naprawdę nie zostaje uruchomiony notatnik, tylko właśnie
Czyli usuwa on siebie (żeby nie zostawiać śmieci w folderze notatnika), a następnie po prostu uruchamia zgłoszenie. W 90% przypadków chcesz ten plik tak zostawić.
initiator.py
. Jego standardowa zawartość to po prostu:
import os
import subprocess
os.remove('initiator.py')
subprocess.run(['python', '-u', 'submission.py'])
To po co on jest? Możesz za jego pomocą np. przygotować środowisko przed uruchomieniem notatnika. Na przykład, żeby korzystać z globalnych datasetów ResNet'ów poprzez PyTorch, musisz najpierw je umieścić w folderze
Jeśli chcesz korzystać z ResNet'ów i innych globalnych zbiorów danych, koniecznie zapoznaj się z ich spisem (musisz być edytorem jakiegoś zadania, aby mieć do niego dostęp).
.cache
. Plik ten będzie wyglądać wtedy na przykład tak:
import os
import subprocess
import shutil
os.makedirs('/home/runner/.cache/torch/hub/checkpoints', exist_ok=True)
shutil.move('./resnet18-f37072fd.pth', '/home/runner/.cache/torch/hub/checkpoints/resnet18-f37072fd.pth')
os.remove('initiator.py')
subprocess.run(['python', '-u', 'submission.py'])
Można też na przykład:
Checkerka
- IS_VALID-Truedla zgłoszeń wysłanych na zbiór walidacyjny, aFalsedla zgłoszeń testowych.
- NOTEBOOK_EXECUTION_TIME- Ilość czasu (w sekundach), który trwało uruchomienie notatnika. Przydatne to podnoszenia błędów przekroczenia czasu w przypadku, gdy chcemy ograniczyć sumaryczną długość uruchomienia pickle oraz notatnika.
- NOTEBOOK_TIME_LIMIT- Limit czasu dla notatnika, dokładnie ten sam który podałeś w metadanych, jest tu czysto dla wygody.
- CHECKER_TIME_LIMIT- Podobnie jak poprzednie, ale limit dla checkerki.
- check_file_exists(filepath: str) -> bool- Funkcja, która dla ścieżki będącej stringiem zwróci, czy istnieje taki plik (tzn. czy istnieje taka ścieżka, oraz czy jest rzeczywiście plikiem a nie folderem).
- linear_grade(score: float, min_score: float, max_score: float, use_better: bool = False) -> int- Standardowa funkcja do oceniania w liniowej skali.scoreto metryka którą osiągnął w tym zgłoszeniu użytkownik,min_scoreto wartość metryki za którą przyznawane jest 0 punktów, amax_scoreto wartość metryki która daje 100 punktów (przy czym oczywiście wszystko będzie działać poprawnie nawet jeślimax_scorebędzie mniejsze niżmin_score). Zwróci wynik zgodnie z skalą liniową [min_score,max_score] -> [0pkt, 100pkt].use_betterzrobi poprawniejsze, ale rzadziej stosowane zaokrąglanie.
- debug()- Działa dokładnie tak samo jak wbudowana funkcjaprint(), ale wypisze na STDERR. Może się przydać do wyraźniejszego oddzielenia niektórych logów. Wszystko co wypisze checkerka poprzezprint()lubdebug()będzie widoczne wyłącznie dla administracji oraz edytorów zadania, użytkownicy będą widzieć tylko notatkę którą im zostawiłeś.
- format_time(t: float) -> str- Pomocnicza funkcja która zamieni ilość sekundtna ładną reprezentację czasu, np.format_time(3667.4)zwróci1:01:07, aformat_time(-69)zwróci-1:09. Stosowana np. przy wyświetlaniu ilości czasu które zajęło uruchamianie jakichś rzeczy.
- print_tree(path: str = '.')- Pięknie wyświetli drzewko plików znajdujących się w danym folderze, domyślnie w aktualnym folderze czyli w folderze checkerki/sandbox. **Uwaga. ** Ta funkcja służy do debuggowania kodu i sprawdzenia, czy np. w folderze nie pojawiły się jakieś pliki których nie powinno tam być (więcej informacji w sekcji o uruchamianiu pickle). Zalecamy usunięcie jej przed ostatecznym opublikowaniem zadania, bo jej uruchomienie może zajmować niepotrzebny czas jeśli np. notatnik wygenerował bardzo dużo małych plików.
- run_pickle- Więcej informacji w sekcji o uruchamianiu pickle.
- Importuje on funkcję checkz checkerki (dlatego też błędy które pojawią się poza funkcjącheck()będą uznawane za błędy systemu - przechwytywane jest dopiero uruchomieniecheck()).
- Wypisuje zawartości pomocniczych flag (dzięki temu są widoczne w logach każdego zgłoszenia).
- Usuwa śmieci oraz wszystkie inne pliki, które mogłyby służyć do hackowania systemu podczas uruchamiania pickle (więcej informacji w następnej sekcji).
- Uruchamia funkcję check()i zapisuje wynik lub ewentualne błędy.
Wreszcie przechodzimy do najciekawszej części, i na szczęście wcale nie tak trudnej. Omówienie pisania checkerek podzielimy na dwie części. Pierwsza omówi cały system rozważając przypadek, w którym nie uruchamiamy żadnego kodu wewnąrz checkerki. Są to systuacje, gdy odpowiedzi wysyła się bezpośrednio w zgłoszeniu i pomija się fazę uruchamiania notatnika (np. Wyślij swój wynik lub Operacja "odwrót"), lub gdy notatnik jest uruchamiany, ale dostaje (jako zbiór danych dla fazy uruchamiania) dane testowe, i generuje plik z odpowiedziami zamiast pliku cloudpickle. Szczegóły, dlaczego to podejście nie jest preferowane, zostały już wyjaśnione w sekcji "wstęp". Tak czy siak umiejętność ogólnego sprawdzenia odpowiedzi jest niezbędna. Druga część będzie już tylko dodatkiem tłumaczącym, jak bezpiecznie uruchamiać pliki cloudpickle.
Przede wszystkim, checkerka w nowym zadaniu zawsze będzie miała następujące importy:
from checklib import IS_VALID, NOTEBOOK_EXECUTION_TIME, NOTEBOOK_TIME_LIMIT, CHECKER_TIME_LIMIT
from checklib import check_file_exists, linear_grade, safe_score, debug, format_time, print_tree, run_pickle
from checklib import SubmissionError, TimeError
checklib.py
to pomocnicza biblioteka zawierająca liczne flagi oraz funkcje pomagające pisać checkerki. Odpowiadająca jej wersja pojawi się automatycznie w folderze z zadaniem. Nie należy tego pliku edytować - jest on tylko dla odniesienia żeby w razie czego zobaczyć, jak dokładnie wyglądają te dostępne funkcje.Twoim zadaniem jest zaimplementowanie funkcji
Checkerka może zwrócić dowolną ilość punktów, w szczególności liczbę zmiennoprzecinkową lub poza zakresem 0-100 (it's not a bug, it's a feature). Najczęściej jednak chcesz zwrócić konkretnie liczbę całkowitą z zakresu 0-100. W takim przypadku, aby uniknąć głupich błędów, przygotowaliśmy kontekst
check()
. Funkcja ta powinna zwrócić liczbę będącą ilością punktów za zadanie, lub tuple liczby oraz stringu, gdzie liczba będzie ilością punktów a string będzie notatkami ze zgłoszenia (więcej informacji w notes_char_limit
edycji metadanych):
def check() -> float | tuple[float, str]:
punkty = ...
notatki = 'Ta wiadomość będzie widoczna dla użytkowników'
return punkty, notatki
checklib.safe_score
. Zapewni on, że punkty będą liczbą całkowitą, i jeśli ilość punktów przekroczy 100, to zostanie zclampowana do 100, i podobnie nigdy nie będzie mniejsza niż 0.
@safe_score
def check() -> float | tuple[float, str]:
return punkty, notatki
Możesz również (tylko wewnątrz funkcji
Lub bez żadnej dodatkowej wiadomości:
Podobnie można wysyłać błąd przekroczenia limitu czasu
check()
!) wysyłać błędy przekroczenia czasu oraz błędy zgłoszenia, po prostu podnosząc checklib.SubmissionError
lub checklib.TimeError
, np.:
def check() -> float | tuple[float, str]:
raise SubmissionError('Ta wiadomość będzie widoczna dla użytkowników')
def check() -> float | tuple[float, str]:
raise SubmissionError()
raise TimeError(opcjonalna wiadomość)
.Z poziomu checkerki dostępne są w tym samym folderze wszystkie pliki zgłoszeniowe oraz wszystkie zbiory danych. Dla przykładu, tak wygląda checkerka do zadania Wyślij swój wynik:
def check():
with open('wynik.txt', 'r') as f:
res = f.read()
try:
wynik = int(res)
except Exception:
raise SubmissionError('Wysłany plik wynik.txt nie jest liczbą całkowitą')
if wynik < 0 or wynik > 100:
raise SubmissionError('Wynik musi być z zakresu od 0 do 100')
return wynik
Cały folder z uruchamiania notatnika jest dostępny w folderze
./nb
dla checkerki. Czyli jeśli notatnik stworzy plik ./foo.bar
, to checkerka może go odczytać w ./nb/foo.bar
Warto też omówić pozostałe funkcje i flagi
checklib.py
:
Poza tym, w zasadzie nie ma w temacie dużo do dodania. Absolutnie wszystkie możliwe błędy powinny być handlowane i zgłaszane je jako
SubmissionError
. Jeśli checkerka wypisze jakikolwiek inny błąd, co doprowadzi do tego, że nie dojdzie do zapisania pliku z wynikiem, to pojawi się komunikat Błąd systemu
. Błędów systemu nie powinno być na produkcji. Regularnie sprawdzamy czy pojawiły się jakieś nowe błędy systemu, bo każdy taki jest informacją, że checkerka została źle napisana. Błędy systemu muszą być natychmiast rozwiązywane a później rejudgowane. W produkcji nie powinno być żadnego błędu systemu. Oczywiście mogą się takowe pojawić w czasie tworzenia zadania i jest to całkowicie normalne, ale ostateczna checkerka powinna dobrze handlować wszystkie błędy i jasno przekazywać użytkownikowi, co jest nie tak. Resztę logiki implementacji checkerki zostawiamy Tobie, bo jest to w tym momencie wyłącznie kwestia zaimplementowania idei danego zadania.Wspomnijmy jeszcze, jak dokładnie uruchamiana jest checkerka. Podobnie jak notatnik był uruchamiany przez
W skrócie:
initiator.py
, checkerka jest uruchamiana przez zawsze ten sam plik checkrun.py
. Ma on dokładnie taką zawartość:
from checklib import IS_VALID, NOTEBOOK_EXECUTION_TIME, NOTEBOOK_TIME_LIMIT, CHECKER_TIME_LIMIT
from checklib import write_ok, write_time_error, write_submission_error
from checklib import SubmissionError, TimeError
from checker import check
import os
import shutil
print('This message is automatically printed by checkrun.py before running check():')
print(f'IS_VALID: {IS_VALID}')
print(f'NOTEBOOK_EXECUTION_TIME: {NOTEBOOK_EXECUTION_TIME}')
print(f'NOTEBOOK_TIME_LIMIT: {NOTEBOOK_TIME_LIMIT}')
print(f'CHECKER_TIME_LIMIT: {CHECKER_TIME_LIMIT}')
print('checkrun.py will first remove: checker.py, checklib.py, checkrun.py and __pycache__')
os.remove('checker.py')
os.remove('checklib.py')
os.remove('checkrun.py')
shutil.rmtree('__pycache__')
print('checkrun.py will now run check()...')
try:
result = check()
if isinstance(result, tuple):
score, notes = result
write_ok(score, notes)
else:
write_ok(result)
except SubmissionError as e:
write_submission_error(str(e))
except TimeError as e:
write_time_error(str(e))
Uruchamianie cloudpickle
- Załadować pliki z odpowiedziami i je usunąć.
- Sprawdzić, czy plik cloudpickle i wszelkie inne pliki które oczekujesz faktycznie zostały stworzone przez notatnik.
- Uruchomić pickle na osobnym procesie gdzie jego predykcje zostaną zapisane do pliku, oraz przekazywać ewentualne błędy w odpowiedniej formie użytkownikowi.
- Wreszcie sprawdzić te pliki dokładnie tak samo jak w poprzedniej sekcji.
- Uruchamia plik subprocess_script.py- po prostupython subprocess_script.py
- Wypisuje kod wyjścia, STDOUT i STDERR subprocess_script.py.
- Sprawdza czy powstał plik error.txt. Jeśli tak, to podniesieSubmissionErrorz zawartością tego pliku, a w przeciwnym razie zakończy działanie i zwróci ilość czasu, którą zajęło wykonanie tego skryptu.
Jeśli dokładnie przeczytałeś i zrozumiałeś poprzednie sekcje, to uruchamianie cloudpickle jest już tylko prostym krokiem pośrednim. Przypominając informacje ze wstępu - plik cloudpickle musimy uruchamiać na osobnym procesie, bo w przeciwnym razie mógłby trywialnie robić takie rzeczy jak np. wpisanie sobie milion punktów lub powodować bardzo nieprzewidywalne błędy systemu. To też oznacza, że wszystkie pliki z odpowiedziami muszą zostać na początku załadowane do zmiennych, a następnie usunięte przed uruchomieniem pickle, bo w przeciwnym razie mógłby on je po prostu odyczytać. W skrócie musisz więc po prostu zrealizować te proste punkty:
Pierwsze dwa punkty są dość oczywiste (ewentualne wątpliwości powinien rozwiać przykład który podamy za chwilę), więc pozwolimy sobie od razu przejść do trzeciego. Pickle uruchamiamy za pomocą funkcji
run_pickle()
. Jeśli zajrzysz do wnętrza checklib.py
to zauważysz, że funkcja ta robi trzy rzeczy:
Widzisz więc, że sprowadza się to do napisania
subprocess_script.py
który załaduje plik cloudpickle, załaduje dane testowe, uruchomi go na tych danych i zapisze wynik (oraz zapisze plik error.txt
gdyby pojawił się błąd). Zawartość pliku error.txt
może być całkowicie dowolna, dlatego aby ubezpieczyć się przed wyciekiem danych, należy stosować rozsądnie mały notes_char_limit
.Plik
Nie jest on więc szczególnie skomplikowany - robi dokładnie to, co powiedzieliśmy, czyli uruchamia funkcję cloudpickle na danych testowych i zapisuje wyniki, jednocześnie handlując wszystkie możliwe błędy. W tym przypadku zapisywana jest tylko nazwa błędu oraz jego zawartość. Można też rozważyć inne opcje (będą one zabierać więcej znaków z notatek, więc należy je stosować tylko gdy z jakiegoś powodu zwiększyło się limit znaków notatek) - np. wypisywanie też linii błędu:
lub całego traceback:
subprocess_script.py
w zadaniu Prosta klasyfikacja wygląda następująco:
try:
import cloudpickle
import numpy as np
with open('nb/predict.pkl', 'rb') as f:
try:
predict = cloudpickle.load(f)
except Exception as e:
raise Exception('Nie udało się załadować pliku cloudpickle (plik został uszkodzony?) ' + repr(e))
try:
predicted = predict(np.load('snails_test.npy'))
except Exception as e:
raise Exception('Wystąpił błąd podczas uruchamiania pliku cloudpickle: ' + repr(e))
try:
if hasattr(predicted, 'cpu'):
predicted = predicted.cpu()
np.save('predictions.npy', predicted)
except Exception as e:
raise Exception('Nie udało się zapisać odpowiedzi do pliku (czy na pewno zwracasz numpy array lub tensora?) ' + repr(e))
except Exception as e:
with open('error.txt', 'w', encoding='UTF-8') as f:
f.write(str(e) if type(e).__name__ == 'Exception' else repr(e))
raise # raise so full logs will be visible on stderr
try:
predicted = pickle_function()
except Exception as e:
import traceback
tb = traceback.extract_tb(e.__traceback__)[-1]
raise Exception('Wystąpił błąd podczas uruchamiania pliku cloudpickle: ' + repr(e) + ('' if tb.line is None) else '
' + str(tb.line))
try:
predicted = pickle_function()
except Exception as e:
import traceback
raise Exception(traceback.format_exc())
Następnie wewnątrz checkerki należy się po prostu stosować wymienionych wcześniej czterech podpunktów czyli sprawdzenia poprawności wygenerowanych plików, załadowania i usunięcia plików z odpowiedziami, uruchomienia pickle a następnie sprawdzenia wygenerowanych plików. Oto przykładowa checkerka z zadania Prosta klasyfikacja, możesz ją wykorzystać jako punkt wyjścia do swojej:
from checklib import IS_VALID, NOTEBOOK_EXECUTION_TIME, NOTEBOOK_TIME_LIMIT, CHECKER_TIME_LIMIT
from checklib import check_file_exists, linear_grade, safe_score, debug, format_time, print_tree, run_pickle
from checklib import SubmissionError, TimeError
import numpy as np
import os
@safe_score
def check() -> float | tuple[float, str]:
pickle_file = 'predict.pkl'
# Load all test labels (files you don't want pickle function to access) into RAM and REMOVE THOSE FILES!
labels = np.load('labels_test.npy')
os.remove('labels_test.npy')
print_tree() # This prints all files, useful for checking if there are only files that the subprocess will need
# Check if all files that the notebook should generate are actually here
if not check_file_exists(f'nb/{pickle_file}'):
raise SubmissionError(f'Nie znaleziono pliku {pickle_file}')
# Run pickle by calling subprocess_script.py
# If it creates error.txt file, it will be raised as SubmissionError()
# If running pickle exceeds timeout, it will raise TimeError()
# You can handle these yourself to give additional error messages like in this example
total_execution_time = NOTEBOOK_EXECUTION_TIME # This will be the 'Sumaryczny czas uruchomienia'
total_time_limit = NOTEBOOK_TIME_LIMIT # This will be the time limit for total_execution_time
total_execution_time += run_pickle(timeout = total_time_limit - total_execution_time)
# Now just check the generated files.
# Do not assume anything about the files, handle every exception!
# If you don't run any pickles inside checker, just start from this section and remove all sections above.
try:
preds = np.load('predictions.npy')
except Exception as e:
print(e)
raise SubmissionError('Funkcja predict() nie zwróciła numpy array lub tensora')
if preds.shape != labels.shape:
raise SubmissionError(f'Oczekiwano, że wyjście z funkcji pliku {pickle_file} będzie miało kształt {labels.shape}, ale otrzymano {preds.shape}')
acc = (preds == labels).astype(float).mean()
return linear_grade(acc, 0.5, 0.95, True), f'Dokładność klasyfikacji: {acc * 100 :.1f}%, Sumaryczny czas uruchomienia: {format_time(total_execution_time)}'
Funkcja
run_pickle()
automatycznie wypisze błędy SubmissionError
oraz TimeError
gdy takie wystąpią. Jeśli chcesz dodać do nich dodatkowe informacje, możesz je osobno handlować:
try:
total_execution_time += run_pickle(timeout = total_time_limit - total_execution_time)
except SubmissionError as e:
raise SubmissionError('Możesz tu dodać dodatkową wiadomość: ' + str(e))
except TimeError as e:
raise TimeError('Wiadomość gdy wystąpi przekroczenie czasu')
Zakończenie
- Na zakończenie przedstawimy jeszcze spis rzeczy, które warto sprawdzić, zanim opublikujesz zadanie:
- Pamiętaj, że nie możesz niczego zakładać o plikach które wygeneruje subprocess_script.py. Ktoś mógłby złośliwie zapisać te pliki a następnie wykonaćsys.exit(0), przez co mógłby do tych plików wsadzić absolutnie cokolwiek. Jeśli uważasz, że jakiś błąd, jeśli wystąpi, to na pewno jest czystą złośliwością użytkownika, to możesz nawet podnieśćSubmissionErrorbez żadnej wiadomości, po prostu upewnij się, że nie ma nieoczekiwanych błędów systemu.
- Nie pozbawiaj się też wszystkich błędów systemu, np. nie dawaj całej funkcji check()w ogromny try...catch który zawsze podniesieSubmissionError. Błędy systemu pozwalają na łatwe monitorowanie, czy poszło nie tak coś, do czego nie powinno dojść. Możesz więc nie handlować błędów, o których jesteś pewien, że nie powinny wystąpić. Wtedy, gdyby okazało się, że nie masz racji, zostaniesz jasno i szybko poinformowany o tym, bo pojawi się właśnie błąd systemu.
- Jeśli robisz zadanie w którym uruchamiasz pickle w checkerce, to bardzo mocno zalecamy, aby wszystkie zbiory treningowe, które są podlinkowane do uruchomienia notatnika, były też podlinkowane do checkerki - na wypadek, gdyby ktoś chciał je odczytać dopiero wewnątrz pickla.
Bonus
- Tą sekcję poświęcimy na kilka trików, które pomoże Ci szybciej i przyjemniej pracować nad zadaniami, choć absolutnie nic z tego nie jest obowiązkowe. Możesz pominąć tę sekcję jeśli już zmęczyłeś się czytaniem całego tego tutorialu.
- Sai umożliwia też wysyłanie zgłoszeń, co jest bardzo wygodnym ułatwieniem, aby nie musieć się przełączać na przeglądarkę i przeklikiwać się przez wysyłanie odpowiedniego zgłoszenia. Wysyłanie zgłoszeń odbywa się za pomocą sai sub PATHS..., gdzie PATHS... to ścieżki do plików które chcesz wysłać, w kolejności zgodnej zsubmission_filesz metadanych. Np. jeśli w zadaniu ustawiłeś tylko jeden plik zgłoszeniowy, to musisz podać dokładnie jedną ścieżkę. Ścieżki mogą być zapisane skrótami (więc informacji wsai sub --help). Na przykład możesz stworzyć folder./submissionsi wysyłać z niego zgłoszeniasai sub s/[pierwsza litera pliku](lub pierwsze kilka liter, jeśli kilka plików zaczyna się na tę samą literę). Zgłoszenie zostanie wysłane na domyślny zbiór danych, czyli walidacyjny jeśli dzienny limit zgłoszeń walidacyjnych nie jest równy 0, a w przeciwnym razie na testowy. Możesz też dodać flagi
sai sub PATHS... --valid/--test (-v/-t)
--all (-a)
aby automatycznie wysłać zgłoszenie na oba zbiory na raz. Możesz też dodać flagę --open (-o)
, aby otworzyć zgłoszenie w przeglądarce (może nie działać na niektórych platformach).