Jak mergować Jupyter notebooki w GIT i nie osiwieć? Proste narzędzie!

uczymymaszyny.pl 3 lat temu

Kto Notebooki w GITcie merge’ował, ten się w cyrku nie śmieje…

Taki Michał, 2014

Pozwól, iż 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

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, iż 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, iż 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.

  1. 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, iż jest lepiej? Wiemy, iż dwie komórki zostały dodane, iż 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…

  1. 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ę choćby 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 dzisiaj 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. o ile 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ł, iż nbdime ma plugin do Jupyter Laba! Nie musiał już choćby 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
    Idź do oryginalnego materiału