Ćwiczenie 10: Od notatnika do skryptu¶
Po co to ćwiczenie?¶
Przez dziewięć ćwiczeń pracowałeś wyłącznie w notatniku. I bardzo dobrze - notatnik jest znakomitym narzędziem do eksploracji: uruchamiasz kawałek kodu, oglądasz wynik, poprawiasz, uruchamiasz jeszcze raz. Do zaglądania w dane i szukania pomysłu nic lepszego nie wymyślono.
Problem zaczyna się wtedy, gdy notatnik przestaje być miejscem szukania rozwiązania, a staje się miejscem, gdzie rozwiązanie mieszka. Wtedy jego zalety zamieniają się w wady:
- komórki można uruchamiać w dowolnej kolejności - i prawie zawsze ktoś to robi,
- zmienna może istnieć w pamięci, chociaż komórka, która ją tworzyła, została skasowana - to ukryty stan (ang. hidden state),
- Git widzi notatnik jako jeden wielki plik JSON razem z wynikami - różnice między wersjami są nieczytelne,
- „u mnie zadziałało" nie oznacza, że zadziała u kogokolwiek innego, bo nikt nie wie, w jakiej kolejności komórki były uruchamiane.
Efekt końcowy jest zawsze ten sam: model, którego nie da się odtworzyć. Ktoś pyta „skąd wzięła się ta liczba w raporcie?", a Ty nie potrafisz odpowiedzieć.
Dlaczego to ćwiczenie jest dziesiąte, a nie pierwsze. Gdyby zacząć kurs od pisania skryptów i argumentów wiersza poleceń, brzmiałoby to jak zbędna biurokracja. Dopiero po dziewięciu ćwiczeniach - po zgubionych wynikach,
NameErrorpo uruchomieniu komórek w złej kolejności i modelach, których nie dało się powtórzyć - widać, na co to jest lekarstwo. Ból musi być pierwszy, lekarstwo drugie.
Czego się nauczysz¶
- Co notatnik robi dobrze, a czego nie powinien robić nigdy.
- Czym jest ukryty stan i jak go wykryć jednym kliknięciem.
- Jak przenieść wytrenowany model do pliku
.pyz funkcjąmain(). - Jak sterować skryptem z wiersza poleceń przez
argparse, zamiast edytować kod. - Jak zapisać model na dysk (
joblib) i wczytać go w zupełnie osobnym procesie. - Czym jest odtwarzalność (ang. reproducibility) i jak sprawdzić, czy faktycznie ją masz.
Zanim zaczniesz: uruchamiaj komórki po kolei (Shift+Enter). Notatnik zakłada, że jesteś w katalogu
cwiczenia-ml/.
1. Notatnik kontra skrypt - do czego który¶
To nie jest konkurs, w którym jedno narzędzie wygrywa. To podział obowiązków.
Notatnik .ipynb |
Skrypt .py |
|
|---|---|---|
| Do czego służy | eksploracja, wykresy, nauka, prezentacja wyników | kod, który ma działać powtarzalnie |
| Kolejność wykonania | dowolna - decyduje użytkownik | zawsze od góry do dołu |
| Stan zmiennych | żyje w jądrze (ang. kernel) między uruchomieniami | świeży przy każdym uruchomieniu procesu |
| Parametry | zmieniasz, edytując kod | podajesz z wiersza poleceń |
| Różnice w Git | JSON z wynikami - nieczytelne | zwykły tekst - czytelne linia po linii |
| Uruchomienie automatyczne (np. co noc) | trudne i kruche | naturalne |
| Testy jednostkowe | praktycznie niewykonalne | standard (ćwiczenie 11) |
Zdanie, które warto zapamiętać: notatnik jest brudnopisem, skrypt jest czystopisem. Brudnopis jest potrzebny - ale nikt nie oddaje brudnopisu jako pracy końcowej.
2. Ukryty stan - zobacz go na własne oczy¶
Uruchom poniższą komórkę. Potem uruchom ją jeszcze raz. I jeszcze raz.
licznik = locals().get('licznik', 0) + 1
print("Ta komórka była uruchomiona razy:", licznik)
Wynik zależy od tego, ile razy kliknąłeś, a nie od treści kodu. To jest ukryty stan w najczystszej postaci: identyczny notatnik u dwóch osób daje dwie różne liczby.
W prawdziwym kodzie ML wygląda to groźniej. Klasyczny scenariusz:
# komórka A (uruchomiona rano)
X = dane.drop(columns=['PatientID', 'Diabetic'])
# komórka B (uruchomiona po południu, po eksperymentach)
X = X.drop(columns=['Age']) # sprawdzam model bez wieku
# komórka C (uruchomiona wieczorem - "ostateczny model")
model.fit(X, y)
Po restarcie jądra komórka C nauczy model na innych danych niż wieczorem, bo komórka B już nie zadziała w tej samej kolejności. Wynik w raporcie przestaje pasować do kodu w pliku.
Test, który to wykrywa - i zajmuje 10 sekund¶
W JupyterLab: Kernel → Restart Kernel and Run All Cells...
To jedyny uczciwy sposób sprawdzenia, czy notatnik działa od początku do końca. Zasada na resztę życia zawodowego:
Notatnik, który nie przechodzi „Restart and Run All", jest zepsuty - nawet jeśli wszystkie komórki mają zielone wyniki.
Zrób to teraz z tym notatnikiem. Zwróć uwagę, że licznik powyżej wrócił do 1.
3. Punkt wyjścia: model w notatniku¶
Zaczynamy od kodu, który znasz z ćwiczenia 01. Nic w nim nowego - to jest właśnie ten „brudnopis", który zaraz przeniesiemy do skryptu.
# sys.executable to sciezka do TEGO interpretera Pythona, ktory
# obsluguje notatnik. Uzywamy jej zamiast golego 'python', bo na
# PATH moze byc inna instalacja Pythona - bez zainstalowanych
# bibliotek kursu. Wtedy skrypt cicho by padal.
import sys
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score, recall_score
ZIARNO = 42
dane = pd.read_csv('dane/diabetes.csv')
X = dane.drop(columns=['PatientID', 'Diabetic'])
y = dane['Diabetic']
X_ucz, X_test, y_ucz, y_test = train_test_split(
X, y, test_size=0.2, stratify=y, random_state=ZIARNO
)
model = DecisionTreeClassifier(max_depth=5, random_state=ZIARNO)
model.fit(X_ucz, y_ucz)
przewidywania = model.predict(X_test)
print(f"Skuteczność: {accuracy_score(y_test, przewidywania):.4f}")
print(f"Czułość: {recall_score(y_test, przewidywania):.4f}")
Kod działa. I ma trzy wady, których w notatniku nie widać:
- Żeby zmienić
max_depth, trzeba edytować kod. Po dziesięciu eksperymentach nie wiadomo, która wartość dała który wynik. - Wynik istnieje tylko na ekranie. Zamknij notatnik - i tyle go widzieli.
- Model istnieje tylko w pamięci. Restart jądra kasuje godziny liczenia.
Wszystkie trzy naprawimy jednym ruchem: przenosząc kod do skryptu.
4. Skrypt - budowa¶
Zanim napiszemy plik, ustalmy jego szkielet. Dobry skrypt ML ma cztery części:
| Część | Po co |
|---|---|
| funkcje robocze | każda robi jedną rzecz i ma jasne wejście i wyjście |
parsuj_argumenty() |
wszystkie parametry z wiersza poleceń, w jednym miejscu |
main() |
scenariusz: wczytaj → podziel → trenuj → oceń → zapisz |
if __name__ == "__main__": |
uruchom main() tylko wtedy, gdy plik jest uruchamiany, a nie importowany |
Ostatni punkt jest ważniejszy, niż wygląda. Dzięki niemu ten sam plik można uruchomić z terminala i zaimportować z innego kodu (na przykład z testów - patrz ćwiczenie 11), bez przypadkowego trenowania modelu przy imporcie.
Dlaczego argparse, a nie zmienne na górze pliku¶
Bo parametry zapisane w kodzie mają brzydki nawyk: zmieniasz je, uruchamiasz, zmieniasz z powrotem - i po tygodniu nie wiesz, z jakimi wartościami powstał model leżący na dysku. Parametr podany w wierszu poleceń zostaje w historii terminala, w logu, w pliku wyników. Jest dowodem.
from pathlib import Path
# %%writefile nie tworzy katalogow - musimy je zalozyc sami
Path('src').mkdir(exist_ok=True)
Path('wyniki').mkdir(exist_ok=True)
print("Katalogi src/ i wyniki/ gotowe.")
Teraz piszemy plik. Używamy do tego magicznego polecenia %%writefile, które zapisuje całą zawartość komórki do pliku (i nic nie uruchamia).
Uwaga dydaktyczna.
%%writefilejest tu użyte tylko po to, żeby cały materiał zmieścił się w jednym notatniku. W prawdziwym projekcie plik.pypo prostu leży w repozytorium, otwierasz go w edytorze i trzymasz pod kontrolą Gita. Nikt nie generuje kodu źródłowego z notatnika.
%%writefile src/trenuj.py
"""Trening klasyfikatora cukrzycy - wersja skryptowa.
Uruchomienie:
python src/trenuj.py --max-depth 5
"""
import argparse
import json
from pathlib import Path
import joblib
import pandas as pd
from sklearn.metrics import accuracy_score, f1_score, recall_score
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
KOLUMNA_ID = "PatientID"
KOLUMNA_ETYKIETY = "Diabetic"
def wczytaj_dane(sciezka):
"""Wczytuje plik CSV i zwraca ramke danych."""
ramka = pd.read_csv(sciezka)
print(f"Wczytano {len(ramka)} wierszy z {sciezka}")
return ramka
def przygotuj_cechy(ramka):
"""Dzieli ramke na cechy X i etykiete y. Identyfikator NIE jest cecha."""
X = ramka.drop(columns=[KOLUMNA_ID, KOLUMNA_ETYKIETY])
y = ramka[KOLUMNA_ETYKIETY]
return X, y
def trenuj_model(X_ucz, y_ucz, max_depth, ziarno):
"""Trenuje drzewo decyzyjne i zwraca wytrenowany model."""
model = DecisionTreeClassifier(max_depth=max_depth, random_state=ziarno)
model.fit(X_ucz, y_ucz)
return model
def ocen_model(model, X_test, y_test):
"""Zwraca slownik metryk - slownik, bo latwo go zapisac do JSON-a."""
przewidywania = model.predict(X_test)
return {
"skutecznosc": float(accuracy_score(y_test, przewidywania)),
"czulosc": float(recall_score(y_test, przewidywania)),
"f1": float(f1_score(y_test, przewidywania)),
}
def parsuj_argumenty():
"""Wszystkie parametry skryptu w jednym miejscu."""
parser = argparse.ArgumentParser(description="Trening modelu cukrzycy")
parser.add_argument("--dane", default="dane/diabetes.csv",
help="sciezka do pliku CSV z danymi")
parser.add_argument("--max-depth", type=int, default=5,
help="maksymalna glebokosc drzewa")
parser.add_argument("--ziarno", type=int, default=42,
help="ziarno losowosci (ang. random seed)")
parser.add_argument("--wyjscie", default="wyniki",
help="katalog na model i metryki")
return parser.parse_args()
def main():
args = parsuj_argumenty()
ramka = wczytaj_dane(args.dane)
X, y = przygotuj_cechy(ramka)
X_ucz, X_test, y_ucz, y_test = train_test_split(
X, y, test_size=0.2, stratify=y, random_state=args.ziarno
)
model = trenuj_model(X_ucz, y_ucz, args.max_depth, args.ziarno)
metryki = ocen_model(model, X_test, y_test)
# Zapisujemy razem z parametrami - inaczej metryki sa bezuzyteczne
metryki["parametry"] = vars(args)
metryki["liczba_cech"] = int(X.shape[1])
metryki["liczba_wierszy"] = int(len(ramka))
katalog = Path(args.wyjscie)
katalog.mkdir(parents=True, exist_ok=True)
sciezka_modelu = katalog / f"model_d{args.max_depth}.joblib"
sciezka_metryk = katalog / f"metryki_d{args.max_depth}.json"
joblib.dump(model, sciezka_modelu)
with open(sciezka_metryk, "w", encoding="utf-8") as plik:
json.dump(metryki, plik, indent=2, ensure_ascii=False)
print(f"Skutecznosc: {metryki['skutecznosc']:.4f}")
print(f"Czulosc: {metryki['czulosc']:.4f}")
print(f"Model -> {sciezka_modelu}")
print(f"Metryki -> {sciezka_metryk}")
if __name__ == "__main__":
main()
5. Uruchomienie skryptu¶
Wykrzyknik na początku linii w notatniku oznacza: „uruchom to w powłoce systemowej, a nie w Pythonie". python src/trenuj.py startuje nowy proces - z pustą pamięcią, bez żadnych zmiennych z tego notatnika. To jest dokładnie ta izolacja, o którą nam chodzi.
!{sys.executable} src/trenuj.py --max-depth 5
Zwróć uwagę, że skryptowi nie przeszkadza, w jakiej kolejności uruchamiałeś komórki powyżej. On nie wie, że ten notatnik istnieje.
Zmiana parametru nie wymaga teraz dotykania kodu:
!{sys.executable} src/trenuj.py --max-depth 2
!{sys.executable} src/trenuj.py --max-depth 12
A argparse daje w prezencie pomoc - za darmo, z samych definicji argumentów:
!{sys.executable} src/trenuj.py --help
6. Odtwarzalność - sprawdźmy ją naprawdę¶
Odtwarzalność (ang. reproducibility) oznacza: ten sam skrypt, uruchomiony z tymi samymi argumentami na tych samych danych, daje ten sam wynik. Nie „podobny". Ten sam.
Brzmi trywialnie, a jest jedną z najczęściej łamanych własności w projektach ML. Wystarczy jeden pominięty random_state, żeby to przestało działać.
Sprawdźmy to twardo: uruchomimy skrypt dwa razy i porównamy zapisane metryki co do bitu.
import json
!{sys.executable} src/trenuj.py --max-depth 5 --wyjscie wyniki/bieg_A
!{sys.executable} src/trenuj.py --max-depth 5 --wyjscie wyniki/bieg_B
with open('wyniki/bieg_A/metryki_d5.json', encoding='utf-8') as f:
bieg_A = json.load(f)
with open('wyniki/bieg_B/metryki_d5.json', encoding='utf-8') as f:
bieg_B = json.load(f)
print("Bieg A, skuteczność:", bieg_A['skutecznosc'])
print("Bieg B, skuteczność:", bieg_B['skutecznosc'])
print()
print("Identyczne co do bitu?", bieg_A['skutecznosc'] == bieg_B['skutecznosc'])
Trzy rzeczy sprawiły, że to działa - i wszystkie trzy trzeba robić świadomie:
| Element | Co gwarantuje |
|---|---|
random_state w train_test_split |
ten sam podział na zbiór uczący i testowy |
random_state w modelu |
te same decyzje algorytmu tam, gdzie ma element losowy |
| te same dane wejściowe | oczywiste, a i tak najczęstsza przyczyna rozjazdu |
Czego to jeszcze nie gwarantuje: innej wersji scikit-learn, innej wersji Pythona, innego systemu operacyjnego. Dlatego w poważnych projektach do wyników dopisuje się wersje bibliotek, a środowisko zamraża w pliku (requirements.txt, environment.yml) albo w obrazie kontenera. Zajmiesz się tym w zadaniu 5.
7. Model na dysku: zapis i wczytanie w osobnym procesie¶
Wytrenowany model to obiekt Pythona żyjący w pamięci. joblib.dump zamienia go w plik; joblib.load odtwarza z pliku. Dzięki temu trening i używanie modelu to dwie różne rzeczy, wykonywane w różnym czasie, a nawet na różnych maszynach.
To jest fundament wdrożenia (ang. deployment): trenujesz raz, a przewidujesz tysiące razy, nie powtarzając treningu.
%%writefile src/przewiduj.py
"""Wczytuje zapisany model i przewiduje dla pierwszych pacjentow z pliku.
Uruchomienie:
python src/przewiduj.py --model wyniki/model_d5.joblib --ile 5
"""
import argparse
import joblib
import pandas as pd
KOLUMNA_ID = "PatientID"
KOLUMNA_ETYKIETY = "Diabetic"
def parsuj_argumenty():
parser = argparse.ArgumentParser(description="Predykcja z zapisanego modelu")
parser.add_argument("--model", default="wyniki/model_d5.joblib")
parser.add_argument("--dane", default="dane/diabetes.csv")
parser.add_argument("--ile", type=int, default=5,
help="ilu pierwszych pacjentow pokazac")
return parser.parse_args()
def main():
args = parsuj_argumenty()
model = joblib.load(args.model)
print(f"Wczytano model: {type(model).__name__} z pliku {args.model}")
ramka = pd.read_csv(args.dane).head(args.ile)
X = ramka.drop(columns=[KOLUMNA_ID, KOLUMNA_ETYKIETY])
przewidywania = model.predict(X)
prawdopodobienstwa = model.predict_proba(X)[:, 1]
print()
print(f"{'PatientID':>12} {'przewidziano':>13} {'p(cukrzyca)':>12} {'prawda':>8}")
for identyfikator, pred, prob, prawda in zip(
ramka[KOLUMNA_ID], przewidywania, prawdopodobienstwa, ramka[KOLUMNA_ETYKIETY]
):
print(f"{identyfikator:>12} {pred:>13} {prob:>12.3f} {prawda:>8}")
if __name__ == "__main__":
main()
!{sys.executable} src/przewiduj.py --model wyniki/model_d5.joblib --ile 8
Ten proces nigdy nie widział danych uczących i nie wykonał ani jednej operacji treningu. Dostał plik i policzył predykcje. Tak wygląda używanie modelu w produkcji.
Dwie pułapki
joblib. Po pierwsze: plik.joblibzawiera obiekt scikit-learn, więc wczytanie go w środowisku z inną wersją biblioteki może się nie udać albo - gorzej - udać się z ostrzeżeniem i innym zachowaniem. Po drugie: wczytanie pliku.jobliburuchamia kod, więc nigdy nie wczytuj modeli z niezaufanego źródła, tak samo jak nie uruchamiasz przypadkowych plików.exe.
Zadania¶
Wszystko, czego potrzebujesz, pojawiło się w przykładzie powyżej. Skrypty twórz przez %%writefile (ta linia musi być pierwszą linią komórki), a uruchamiaj przez !python ....
Zadanie 1: Porównanie głębokości bez edytowania kodu¶
Uruchom src/trenuj.py dla --max-depth równego 2, 4, 6, 8 i 12 (każdy bieg do osobnego katalogu przez --wyjscie, albo skorzystaj z tego, że nazwa pliku metryk już zawiera głębokość).
Następnie wczytaj zapisane pliki JSON w Pythonie, zbuduj z nich pandas.DataFrame i wypisz tabelę: głębokość, skuteczność, czułość, F1.
Zwróć uwagę na to, co właśnie zrobiłeś: wyniki eksperymentów nie są już „na ekranie" - leżą na dysku i da się je wczytać kiedykolwiek.
# TWÓJ KOD TUTAJ
# Podpowiedź: pętla po głębokościach z !python nie zadziała wprost.
# Możesz uruchomić pięć osobnych linii z ! albo użyć subprocess.run([...]).
Zadanie 2: Dowód odtwarzalności¶
Uruchom src/trenuj.py dwa razy z identycznymi argumentami, ale do dwóch różnych katalogów wyjściowych.
Wczytaj oba pliki JSON i sprawdź programowo (==, a nie „na oko"), czy metryki są identyczne. Wypisz czytelny komunikat: „ODTWARZALNE" albo „NIEODTWARZALNE".
Dodatkowo porównaj predykcje obu zapisanych modeli na tych samych danych - czy tablice są identyczne co do elementu? (Przyda się numpy.array_equal.)
# TWÓJ KOD TUTAJ
Zadanie 3: Zepsuj odtwarzalność celowo¶
Napisz plik src/trenuj_losowo.py - kopię src/trenuj.py, w której usuwasz random_state z modelu i z train_test_split.
Uruchom go trzy razy z tymi samymi argumentami i porównaj skuteczność.
Potem odpowiedz sobie na pytanie: gdyby ten skrypt był Twoim projektem zaliczeniowym, a prowadzący uruchomił go u siebie - czy dostałby liczbę, którą wpisałeś do sprawozdania?
# TWÓJ KOD TUTAJ
# Podpowiedź: nie musisz przepisywać całego pliku ręcznie -
# skopiuj zawartość komórki z %%writefile i usuń random_state w dwóch miejscach.
Zadanie 4: Nowy argument wiersza poleceń¶
Rozbuduj skrypt (zapisz jako src/trenuj2.py) o dwa nowe argumenty:
--test-size(typfloat, domyślnie0.2) - jaka część danych idzie na test,--modelzchoices=['drzewo', 'las']-DecisionTreeClassifieralboRandomForestClassifier.
Zadbaj o to, żeby oba nowe parametry trafiły do zapisanego pliku JSON (vars(args) robi to samo). Uruchom skrypt dla obu modeli i porównaj wyniki.
Zastanów się przy okazji: dlaczego choices=[...] jest lepsze niż zwykły tekst, który sprawdzasz if-em w środku?
# TWÓJ KOD TUTAJ
Zadanie 5: Metadane odtwarzalności¶
Model bez informacji o tym, jak powstał, jest zagadką. Rozbuduj zapisywany słownik metryk (plik src/trenuj3.py) o sekcję "srodowisko" zawierającą:
- wersję Pythona (
sys.version), - wersję scikit-learn (
sklearn.__version__) i pandas (pd.__version__), - znacznik czasu uruchomienia (
datetime.now().isoformat()), - sumę kontrolną pliku z danymi - skrót SHA-256 (podpowiedź:
hashlib.sha256(Path(sciezka).read_bytes()).hexdigest()).
Wypisz powstały JSON. Zastanów się, który z tych elementów wykryłby podmianę pliku z danymi, o której nikt Cię nie poinformował.
# TWÓJ KOD TUTAJ
Zadanie 6: Zapis i wczytanie w dwóch osobnych procesach¶
- Uruchom trening z
--max-depth 8i zapisem do kataloguwyniki/model8. - Zrestartuj jądro notatnika (Kernel → Restart Kernel). Wszystkie zmienne znikają.
- W nowej komórce wczytaj model przez
joblib.loadi policz jego skuteczność na zbiorze testowym - odtwarzając podział tym samymrandom_state=42.
Porównaj otrzymaną skuteczność z tą zapisaną w pliku JSON. Jeśli się różnią, coś w łańcuchu odtwarzalności jest złamane - znajdź co.
# TWÓJ KOD TUTAJ
Zadanie 7 (trudniejsze): Skrypt, który sam wybiera najlepszy model¶
Napisz src/porownaj.py, który:
- przyjmuje argument
--max-depthznargs='+', czyli listę wartości (np.--max-depth 2 4 6 8 12), - trenuje model dla każdej z nich,
- wybiera najlepszy według metryki wskazanej argumentem
--metryka(choices=['skutecznosc', 'czulosc', 'f1'], domyślnief1), - zapisuje tylko najlepszy model do pliku, ale wszystkie wyniki do jednego JSON-a z polami
"ranking"(lista) i"najlepszy"(słownik), - wypisuje na ekran czytelną tabelę i informację, która głębokość wygrała.
Punkt do przemyślenia na koniec: skrypt wybiera najlepszą głębokość, patrząc na zbiór testowy. W ćwiczeniu 06 ustaliliśmy, że to nie jest uczciwe. Dopisz w docstringu skryptu jedno zdanie o tym, co należałoby zrobić zamiast tego.
# TWÓJ KOD TUTAJ
Pytania do przemyślenia¶
Na te pytania odpowiadasz słowami, nie kodem.
- Notatnik ma 40 komórek i wszystkie pokazują wyniki. Dlaczego to nie jest dowód, że kod działa?
- Dlaczego parametr podany jako
--max-depth 5jest lepszy od zmiennejMAX_DEPTH = 5na górze pliku? Wymień co najmniej dwa powody. - Po co komu
if __name__ == "__main__":? Co konkretnie zepsułoby się bez tej linii, gdyby ktoś zaimportował Twój skrypt? - Ustawiłeś
random_statewszędzie, gdzie się dało, a kolega i tak dostał inny wynik. Wymień trzy możliwe przyczyny. - Dlaczego metryki warto zapisywać do pliku razem z parametrami, a nie osobno?
- Kiedy notatnik jest lepszym wyborem niż skrypt? Podaj konkretną sytuację - bo takie istnieją.
- Model
.joblibma 300 MB. Czy powinien trafić do repozytorium Gita razem z kodem? Co przemawia za, a co przeciw?
Chcesz wiedzieć więcej¶
- Dokumentacja
argparse- zwróć uwagę nanargs,choicesitype. - Trwałe przechowywanie modeli w scikit-learn - ostrzeżenia o bezpieczeństwie i zgodności wersji warto przeczytać w całości.
- Dokumentacja
joblib- dlaczegojoblibbywa lepszy odpickledla dużych tablic NumPy. subprocess.run- uruchamianie skryptu z poziomu Pythona, przydatne w pętli po parametrach.
W ostatnim ćwiczeniu (11 - Jakość kodu w ML) pójdziemy o krok dalej: skoro kod leży już w pliku .py, da się go przetestować. Zobaczysz, co właściwie da się testować w kodzie uczenia maszynowego - bo na pewno nie skuteczność modelu.