Running code is more complicated than displaying code.
可再現性 Reproducibility
數聚點與約維安計畫是以學習 SQL、Python、R 語言程式設計與資料分析為主題的電子報,所以在絕大多數的文章中都有為數不少的程式碼,就如同我們在引言所云:執行程式碼比顯示程式碼還要來得複雜,特別在很多的情境中,程式碼能否執行還會依賴「模組」與「資料」,這也是為什麼在程式設計與資料分析的應用場景中,軟體工程師與資料科學家非常強調可再現研究(Reproducible research)的根本原因,也就是說在給定相同軟體版本、模組版本與資料之下,必須能夠確保程式碼可以順利執行並且得到接近的執行結果。簡而言之,我希望可以讓讀者避免碰到像是:「為什麼我照著電子報的範例程式碼打卻出現了錯誤?」或者「為什麼我複製貼上電子報中的程式碼執行卻沒有得到相同的結果?」
解決關於程式設計與資料分析的可重現性問題會是一個持續不斷演進優化的過程,具體來說,目前我的解決方式是透過三個服務:
repo2docker
repo2docker 是一個能夠依據 Git 儲存庫中的設定檔建立 Docker 映像檔的軟體,我們可以基於 Jupyter 提供的幾個 Docker 映像檔在 GitHub 建立數個設定妥善的儲存庫,像是:
在教學與示範的場景中,我很常要使用到的模組會是 RISE 與 nbgitpuller,就可以透過編寫 Dockerfile 在前述的幾個 Docker 映像檔中加入 RISE 與 nbgitpuller,以 jupyter/datascience-notebook 的映像檔為例:
FROM jupyter/datascience-notebook:<DOCKER-IMAGE-TAG>
RUN pip install --no-cache-dir RISE nbgitpuller
mybinder
mybinder.org 提供免費的聯邦 BinderHub(Federated BinderHubs)同時使用人數上限為 600 人,只要輸入公開的 Git 儲存庫網址,就可以即時啟動一個能夠分享給多人的雲端計算環境,將本來只能靜態展現的 Jupyter 筆記本轉換成為能夠在瀏覽器上執行、編寫的內容,BinderHub 會搜尋儲存庫的環境設置檔案,例如 runtime.txt、requirements.txt、environment.yml 或 install.R 等,然後根據這些檔案的內容建立 Docker 映像檔,然後啟動一個 Jupyter 伺服器,運行狀況可以透過這個網址即時監控:https://mybinder.readthedocs.io/en/latest/about/status.html
mybinder 預設使用 gke.mybinder.org 作為入口,會視四個 BinderHub 的使用人數來分配主機要開啟在哪一個 BinderHub 上面:
gke.mybinder.org (200 個同時使用人數)
ovh.mybinder.org (80 個同時使用人數)
gesis.mybinder.org (120 個同時使用人數)
turing.mybinder.org (200 個同時使用人數)
在 BinderHub 的資源上,gesis 會給予 8G 記憶體,其他三個則是 2G 記憶體,值得注意的是,由於 mybinder 預設使用 gke 作為入口,當 gke 無法運作的時候,欲透過 mybinder 開啟的儲存庫都會失敗,即便 ovh、gesis 與 turing 還是正常運作的,碰到這樣狀況時候,就必須直接指派特定的 BinderHub 才能順利運行。
nbgitpuller
nbgitpuller 可以在使用者不需要接觸 Git 的前提下,運用點擊一個超連結,在家目錄取得一個我們想傳送給的 Git 儲存庫內容,並且透過自動合併的設定,確保使用者可以接收到儲存庫最新的內容,這個模組主要與 JupyterHub、BinderHub 一起使用,能讓管理員(一般來說是老師)分派資料、檔案與程式碼給使用者(一般來說是學生)。nbgitpuller 除了能夠分派 Git 儲存庫給使用者,還具備優化 BinderHub 啟動速度的優點,一般單獨使用 repo2docker 的情況會因為儲存庫的內容更新,而需要重新建立 Docker 映像檔,但很多時候更新的內容並不是與 Docker 映像檔相關,像是 README.md 或者 Jupyter Notebook 等的更動,這些更動若造成映像檔的重新建置,其實相當地浪費時間。因此,更好作法是讓環境相關的檔案(包含 runtime.txt、requirements.txt、environment.yml 或 install.R 等)獨立為一個儲存庫,讓內容相關的檔案作為另一個儲存庫,透過 nbgitpuller 在環境儲存庫啟動之後,再將內容儲存庫複製到家目錄之中。
實作
接著我們來實作一個能夠執行電子報中 Python 與 R 語言程式碼的環境,首先在 GitHub 建立一個儲存庫,並於其中新增兩個沒有副檔名的文字檔案:
Dockerfile
postBuild
在 Dockerfile 中指定要引用 jupyter/datascience-notebook 並安裝 RISE 與 nbgitpuller。
FROM jupyter/datascience-notebook:64c4cd717ab1
RUN pip install --no-cache --upgrade pip && \
pip install --no-cache RISE nbgitpuller
在 postBuild 中啟動 nbgitpuller 功能。
jupyter serverextension enable --py nbgitpuller --sys-prefix
接著在瀏覽器啟動這個環境:https://mybinder.org/v2/gh/jovyans/environment-jovyans-datascience-notebook/HEAD
我們可以順利地在瀏覽器啟動一個 JupyterLab,在這個環境中,我們可以新增 Python、R 語言或 Julia 為核心的 Jupyter Notebook,執行 Python、R 語言或 Julia 的程式碼。
我們也能夠透過 nbgitpuller 在環境成功啟動之後,同步 Git 儲存庫,像是我建立了一個有前述三個程式語言「哈囉世界」的儲存庫,藉此來測試能否順利執行,由於 nbgitpuller 的網址比較複雜,文件中提供了生成模板供我們使用:https://jupyterhub.github.io/nbgitpuller/link
點選 nbgitpuller 連結確認三個程式語言「哈囉世界」的儲存庫啟動後有順利同步、程式碼也能執行,就完成了第一個實作!
接著我們來進行第二個實作,一個能夠執行電子報中 SQL 的環境,首先在 GitHub 建立一個儲存庫,並於其中新增文字檔案:
environment.yml
postBuild
在 environment.yml 中指定要安裝 xeus-sql 並安裝 RISE 與 nbgitpuller。
name: xeus
channels:
- conda-forge
dependencies:
- xeus-sql
- soci-sqlite
- xeus-cling
- xtensor
- xtensor-blas
- pip
- pip:
- rise
- nbgitpuller
在 postBuild 中啟動 nbgitpuller 功能。
jupyter serverextension enable --py nbgitpuller --sys-prefix
接著在瀏覽器啟動這個環境:https://mybinder.org/v2/gh/jovyans/environment-jovyans-xeus/HEAD
我們可以順利地在瀏覽器啟動一個 JupyterLab,在這個環境中,我們可以新增 Python、Xeus-SQL 或 C++ 為核心的 Jupyter Notebook,執行Python、SQL 或 C++ 的程式碼。
在暸解如何透過 repo2docker、mybinder.org 與 nbgitpuller 重現電子報中的程式碼之後我們來到了尾聲,希望您也和我一樣期待下一封電子報。
對於這封電子報有什麼想法呢?喜歡😻、留言🙋♂️或者分享🙌