Pozwól, że pokażę Ci dzisiaj jedno narzędzie, które ułatwi Ci życie. Wystarczy 1 minuta, żeby uniknąć konieczności patrzenia na obrazki zapisane w base64 w plikach JSON (aż mi się słabo zrobiło…)
Zaczniemy gładko, od samego porównywania notebooków, zwiewnie przejdziemy do wersji GUI, gdzie możemy porównywać pliki w przeglądarce, by na końcu odpalić ostateczną petardę – merge’owanie notebooków.
Zapraszam!
# TL;DR na dole posta 😉
Co nas czeka?
Roman ma dość – w czym problem?
W firmie Romana używają GITa. Z resztą, każdy szanujący się zespół używa jakiegoś systemu kontroli wersji, no bo chyba nie przenosi kodu na pendrive, co nie? (Pozdrawiam Krzysiu!)
No i jak wiadomo w GITcie mamy branche, a jak kilka osób pracuje nad projektem, to branche trzeba łączyć, tj. meregować. A Roman jest biegły w używaniu GITa niczym langusta.
U Romana w projekcie mają sobie kodzik pythonowy w bardzo ładnej strukturze utworzonej przez Data Science Cookie Cutter. Jak wiadomo, projekt Data Science więc jest tam taki folder notebooks/
, a w nim nic innego jak pewne Jupyter Notebooki.
Jak pamiętamy z poprzedniego posta, Jupyter Notebooki są zapisane w formacie JSON. Mamy więc pewne pliki tekstowe, wydaje się więc, że repo GIT to dobre miejsce na tego typu dokumenta.
Roman pyknął poprawki w swoim pliku pythonowym. Standardowa procedura po poprawce. Commit. Push. Pull Request.
Ale po commit przypomniał sobie, że w sumie tego swojego brancha to dawno zrobił, wiec domerguje zmiany z brancha developa, żeby pull (merge dla gitlabowców) request ładnie siadł.
Konflikty w Jupyter Notebookach
Ktoś zrobił zmiany w Jupyter Notebooku, który Roman akurat ciut dopieścił. Przy próbie łączenia gałęzi repozytorium pojawiły się konflikty.
Roman odpalił git diff. Oczom jego ukazał się las… wróć, tekst jak na obrazku poniżej.
Nie jest dobrze. W zasadzie nie za bardzo wiadomo, o co chodzi. Jak więc wybrać, które zmiany powinny zostać zachowane, a które nie?
Zobaczmy może do jakiegoś IDE albo jakiegoś klienta GIT jak Sublime Merge
Trochę lepiej, ale i tak bez szału. Grzebanie w JSON przy merge’owaniu notebooków nie jest najprzyjemniejsze…
No i co teraz? Czy Roman jest bez szans w starciu z brutalnym Jsonem, zmutowanym przez Gita?
Nbdime na ratunek!
Nbdime to narzędzie do porównywania i merge’owania zmian w Jupyter Notebookach.
https://nbdime.readthedocs.io/en/latest/
Roman od razu przystąpił do instalacji narzędzia, która, jak to w Pythonie, jest mocno standardowa:
pip install nbdime
I co teraz? Co dalej może zrobić Roman z narzędziem, które zainstalował?
Porównywanie notebooków
Pierwszym, co Roman wziął na tapet jest porównywanie notebooków. I tutaj mamy dwie możliwości.
- Z poziomu terminala
Jeżeli nie mamy np. dostępu do środowiska graficznego (bo jesteśmy po SSH podpięci do zdalnego serwera), to możemy skorzystać z porównania wyświetlonego w terminalu. Służy do tego polecenie
nbdiff notebook_1.ipynb notebook_2.ipynb
Dostajemy wtedy na wyjściu coś podobnego do:
Przyznasz, że jest lepiej? Wiemy, że dwie komórki zostały dodane, że jedna zmodyfikowana, a jeden output został usunięty. Tak, to samo można wyczytać z JSONa, ale chyba z tekstu powyżej jest to łatwiejsze, prawda?
Ale to nie koniec, bo nbdiff
nie powiedział ostatniego słowa. Przejdźmy do…
- GUI w przeglądarce
Jeżeli mamy środowisko graficzne (bo jesteśmy na naszym komputerze albo zdalnej maszynie podłączonej po RDP) to możemy skorzystać z GUI. Uruchomi nam się strona w przeglądarce, gdzie będziemy mogli wygodnie porównać dwa notebooki.
I tutaj oczy Romana zaświeciły jasno jak reflektory w Passacie. Wydał komendę jak poniżej:
nbdiff-web [<commit> [<commit>]] [<path>]
i po chwili w przeglądarce uruchomiło się notebooków porównanie, jakie mu się nawet nie śniło!
Czy to nie jest piękne?
– No dobrze, mogę sobie porównać dwa notebooki. A co z moimi konfliktami w gicie? – słusznie zauważył Roman.
– Czy Nbdime mi pomoże?
Mergowanie notebooków
Nbdime to skrót od “Jupyter Notebooks diffing and merging. Jak możemy się domyślić, oprócz oglądania zmian (diff), możemy je też łączyć (merge)!
Nbdime daje nam możliwość merge’owania notebooków, czyli łączenia zmian, które zostały wykonane w tym samym pliku przez dwie różne* osoby.
I tutaj jest to, co Romany lubią najbardziej. Tutaj Romana oczy się zaszkliły ze wzruszenia. Koniec łączenia zmian w JSONach albo kombinowania z odpalonymi równocześnie notebookami. Koniec męczarni, koniec jęków żalu. Czysta ekscytacja prostotą łączenia dwóch notebooków w jeden. Niczym na ślubnym kobiercu dwoje ludzi od teraz są jedno, tak dwa notebooki nie do końca pasujące do siebie, są teraz jednym…
– Show me the Kodzik! – Krzyknął kolega, któremu Roman chciał się pochawlić. W terminalu wydał polecenie ponieżej i mlasnął “ENTER”.
nbmerge-web base.ipynb local.ipynb remote.ipynb --out merged.ipynb
I oczom ich ukazał się ekran, niczym z żurnala:
Roman kilkoma sprawnymi kliknięciami połączył zmiany, które występowały w notebooku. Były to m.in.
- Zmiany w Metadanych, jak np. tagi danej komórki (TAK, te zmiany też możemy mergować! Czy to nie piękne?)
- Zmiany, które wystąpiły w danej komórce w jednym z notebooków:
- Zmiany, które wystąpiły w danej komórce w obu notebookach:
- Usunięte komórki:
- Dodane komórki:
nbmerge
może używać różnych strategii do wstępnego łączenia zmian. Poniżej przeklejam parametry, które możesz mu zapodać, żeby rozruszać mergowaną imprezę:
➜ nbmerge-web (base)
usage: nbmerge-web [-h] [--version] [--config]
[--log-level {DEBUG,INFO,WARN,ERROR,CRITICAL}] [-s] [-o]
[-a] [-m] [-d]
[--merge-strategy {inline,use-base,use-local,use-remote}]
[--input-strategy {inline,use-base,use-local,use-remote}]
[--output-strategy {inline,use-base,use-local,use-remote,remove,clear-all}]
[--no-ignore-transients] [-p PORT] [-b BROWSER] [--persist]
[--ip IP] [-w WORKDIRECTORY] [--base-url BASE_URL]
[--show-unchanged] [--out OUT]
base local remote
Integracja z Git
I tutaj jest ten moment, który dla Romana był wybawieniem. Nbdime integruje się z Gitem. I to jest ten ficzer, na który wszyscy czekali.
Diff
Nbdime możemy aktywować per repozytorium, w którym pracujemy, albo globalnie, dla wszystkich repozytoriów i obsługi wszystkich diffów
Roman się w tańcu nie rozdrabnia, więc aktywował globalnie. YOLO.
nbdime config-git --enable --global
Gdyby nie był takim cwaniakiem to mógłby pominąć parametr —global
i wtedy aktywowałby tylko dla repozytorium, w którym aktualnie się znajdował.
Merge
Tutaj sytuacja jest trochę bardziej skomplikowana. Jeżeli ustawimy nbmerge
jako domyślny mergetool w git, to będzie on obsługiwał wszystkie pliki. Tego raczej nie chcemy.
Najwygodniej tutaj skorzystać, w razie konfliktów, z komendy poniżej. W ten sposób rozwiążemy konflikty przy użyciu nbdime w tym konkretnym notebooku, a w plikach z kodem w naszym podstawowym narzędziu (VSCode, Pycharm, VIM, whateva).
git mergetool --tool=nbdime biutiful_notebook.ipynb
Warto zajrzeć do dokumentacji: https://nbdime.readthedocs.io/en/latest/vcs.html#git-integration
Znajdziesz tam szczegółowo opisane jak zarejestrować nbdime jako mergetool globalnie, selektywnie, itd.
P.S. Podobno działa też z Mercurialem, ale czy ktoś tego jeszcze używa?
Plugin do Jupytera
Żeby tego wszystkiego było mało, Roman odkrył, że nbdime ma plugin do Jupyter Laba! Nie musiał już nawet sięgać do konsoli (choć bardzo to lubił). Mógł podglądać zmiany w notebookach bezpośrednio z IDE Jupyter Notebooków!
Tutaj znajdziesz link do rozszerzenia i szczegóły instalacji: https://nbdime.readthedocs.io/en/latest/extensions.html
W gruncie rzeczy wystarczy wydać tę komendę:
nbdime extensions --enable [--sys-prefix/--user/--system]
Smaczek na koniec – nbshow
W życiu bywa różnie, czasem jedyne co mamy dostępne to terminal. Połączyliśmy się do zdalnej maszyny po SSH. Chcemy podejrzeć jakiś notebook. Co teraz? Znowy oglądanie mało mówiących JSONów?
Tutaj pomocny będzie nbshow
, który jest
Przykładowy output nbshow
może wyglądać tak:
➜ nbshow prep_data/image_data_guide/03c_pytorch_preprocessing.ipynb (base)
notebook format: 4.4
metadata (known keys):
kernelspec:
display_name: conda_pytorch_latest_p36
language: python
name: conda_pytorch_latest_p36
[...]
code cell 25:
execution_count: 10
source:
sample = iter(sample)
sample_augmented = iter(sample_augmented)
markdown cell 26:
source:
Re-rull the cell below to sample another image
code cell 27:
execution_count: 11
source:
fig, ax = plt.subplots(1, 2, figsize=(10,5))
image = next(iter(sample))[0]
image_augmented = next(iter(sample_augmented))[0]
ax[0].imshow(image.permute(1, 2, 0))
ax[0].axis('off')
ax[0].set_title(f'Before - {tuple(image.shape)}')
ax[1].imshow(image_augmented.permute(1, 2, 0))
ax[1].axis('off')
ax[1].set_title(f'After - {tuple(image_augmented.shape)}');
plt.tight_layout()
outputs:
output 0:
output_type: display_data
data:
image/png: iVBORw0K...<snip base64, md5=3ea6c670233dfdcd...>
text/plain: <Figure size 720x360 with 2 Axes>
metadata (unknown keys):
needs_background: light
Nie jest to może tak przyjazne, jak widok w przeglądarce, ale w podbramkowej sytuacji na pewno lepsze, niż JSON 😉
Linki
Jeżeli masz jakieś pytania albo uwagi, daj znać! Czy to w komentarzu, czy mailowo, czy przez formularz kontaktowy 🙂
TL:DR;
$ pip install nbdime
$ nbdiff notebook_1.ipynb notebook_2.ipynb
$ nbdiff-web [<commit> [<commit>]] [<path>]
$ nbmerge-web base.ipynb local.ipynb remote.ipynb --out merged.ipynb
$ nbdime config-git --enable --global
$ git mergetool --tool=nbdime biutiful_notebook.ipynb