2022年8月1日 追記)本記事は
nbdev v1
の手順であり、nbdev v2
の手順とは異なります。
nbdev2版はこちら
https://qiita.com/lilacs/items/19c9c572a0740c5fc548
はじめに
nbdevはライブラリ作成の多くを自動化して、少人数での大規模プロジェクト開発をサポートしてくれる。しかし、その分環境構築が難しい。そこで、Dockerイメージを自作して、環境構築手順をまとめた。
また、nbdev独自の書き方が分からなかったので、書き方も自分なりにまとめた。
nbdevとは
- Jupyter NotebookでPythonライブラリを開発し、すべてのコード、テスト、ドキュメントを1か所にまとめることができるライブラリです。
- 文芸的プログラミング環境
- コードスニペットとマクロを使用して、人間の言語でプログラムロジックを書き出すこと
- コードと一緒にプログラムの背後にある考えを明示的に記述するので、設計上の決定が明白になり、高品質のプログラムを作成できる。
nbdevの機能
- Jupyterノートブックからドキュメントを自動的に生成
- Googleカスタム検索可能
- バッククォートで囲んだキーワードは適切なドキュメントページに自動的にハイパーリンクされる
- pypiおよびcondaパッケージの公開を自動化
- ノートブックとソースコード間の双方向同期
- IDEを使用してコードナビゲーションやクイック編集ができる
- ノートブックで直接テストを作成
- テストグループを定義できる
- 長時間実行されるテストを常に実行する必要がないようにするため
- テストグループを定義できる
- 継続的インテグレーション(CI)でテストを自動的に実行
- GitHub Actionsを使用
- ドキュメントホスティング
- GitHubページを使用してドキュメントを無料で簡単にホストできる
環境構築について
動作確認時の環境
- Windows10 Pro
- WSL2 + Ubuntu (Nvidia Docker インストール済み)
セットアップするもの
- GitHub プロジェクト
- GitHub Pages も有効にする
- Dockerイメージで対応するもの
- Pythonランタイムとパッケージ(Jupyter/nbdev)
- nbdevコマンド(
nbdev_build_lib
など) - jekyll (静的サイトジェネレータ、ドキュメント作成のため)
- プロジェクトに設定するもの
- git hooks
- pip用インストールスクリプト(setup.py)
🐳nbdev用Dockerイメージ
- 自作したイメージ
-
lilacs2039/fastai-nbdev
(GPU版 fastai・nbdevインストール済み)とfastai/jekyll
の2つのイメージを、docker compose up
で利用する。 - GitHub https://github.com/lilacs2039/docker_fastai-nbdev
- DockerHub https://hub.docker.com/repository/docker/lilacs2039/fastai-nbdev
-
公式イメージもあるが、エラーが出た(2022年6月27日時点)のと、自分用のDockerイメージが欲しかったので自作した。
- 公式イメージ
- DockerHub fastai/codespaces Tags | Docker Hub
- GitHub fastai/docker-containers: Docker images for fastai
- fastaiプロジェクトで使うDockerファイルをまとめて管理しているリポジトリ
環境構築手順
- 参考
- 公式手順(簡易版):A Minimal Example | nbdev
- 公式手順(詳細版):nbdev tutorial | nbdev
プロジェクト作成
GitHubにテンプレートから新しいプロジェクトを作成
以下のリンクから作成可能。
Create a New Repository from fastai/nbdev_template
nbdevのテンプレート(fastai/nbdev_template - GitHub)を自分のリポジトリに複製して、プロジェクトを開始できる。
GitHub Pagesの有効化
Settings > Pages > Source > masterブランチを選択(masterをPagesのソースブランチにする場合)
リポジトリをローカルに複製
git clone (作成したプロジェクトのURL)
settings.iniを編集
ライブラリをパッケージ化するのに必要な情報を設定する。
主な設定内容
# lib_name = your_project_name
# repo_name = name of github repo
# user = your_github_username
# description = A description of your project
# keywords = some keywords
# author = Your Name
# author_email = email@example.com
# copyright = Your Name or Company Name
# branch = The default branch of your GitHub repo (usually either master or main)
fastai/fastaiリポジトリのように、Jupyter Notebookをnbsフォルダにまとめる場合は、以下の項目を設定すること
nbs_path = nbs
以下の項目も入力しておくこと。
一見なくても大丈夫そうだが、setup.pyの実行時にAssertionエラーになってしまうため。
参考: nbdev_template/setup.py のcfg_keysとexpectedより。
version
description
keywords
author
author_email
lib_name
user
branch
license
status
min_python
audience
language
実行環境の構築(Dockerで)
参考 https://github.com/lilacs2039/docker_fastai-nbdev
🐳Dockerイメージ取得・実行
プロジェクトルートで以下のコマンドを実行する。
(Windowsの場合は、wsl環境で実行のこと)
# 本リポジトリの`docker-compose.yml`を取得
wget https://raw.githubusercontent.com/lilacs2039/docker_fastai-nbdev/main/docker-compose.yml
# 環境立ち上げ
docker compose up
※「pytorch/pytorch:1.11.0-cuda11.3-cudnn8-runtime」イメージを使うため、5GB以上のイメージサイズになるので注意。
ローカルサーバへアクセス
- Jupyter Lab : http://localhost:8888
- Jekyll : http://localhost:4000/(lib_name)//
プロジェクトにGitフックのインストール
JupyterLab(http://localhost:8888)のTerminalから、以下のコマンドを実行する
nbdev_install_git_hooks
ファイル.git/hooks/post-merge
が追加される。
※実行エラーになっても、.git/hooks/post-merge
ファイルはちゃんと作成されているかも。
開発手順
Notebook編集・ドキュメントのプレビュー
1.ドキュメントに編集内容を反映
nbdev_build_docs
2.ドキュメントのプレビューをリロード
プレビュー:http://localhost:4000/(lib_name)//
※プレビューがうまくいかないときは、必要に応じてsudo make docs_serve
を実行のこと。
- 古い内容がドキュメントに表示される場合、jekyllサーバを再起動すると直ることがある。
- Ctrl+Cでサーバをいったん終了してから、本コマンドで再起動する。
コミット
1.コミット準備
# モジュールのエクスポート(Notebookから実行)
nbdev_build_lib
# ドキュメントの出力
nbdev_build_docs
# テストの実施
nbdev_test_nbs
# Notebookをきれいに
nbdev_clean_nbs
- 備考
-
nbdev_test_nbs
- すべてのノートブックのテストを並行実行できる
-
nbdev_clean_nbs
- 実行番号などは除去される。アウトプットは残る。
- Notebookの競合を避けるため。
-
nbdev_test_nbs
2.コミットを実行する
リリース
-
GitHubへプッシュ
- GitHub Pagesにドキュメントがホストされる
- CIで自動実行されるもの
- テスト
- Pythonモジュール
- ドキュメント
- CIの注意点:以下の項目が設定されていないと、setup.pyの実行時にAssertionエラーが出てしまう。settings.iniに記述が必要。
4.version description keywords author author_email lib_name user branch license status min_python audience language
-
PyPIへアップロード
4. ここまでの作業で、プロジェクト用に完全にpypiに準拠したインストーラーをすでに作成済み。
5. PyPIアップロードの準備
5. PyPIにアカウント登録し(まだの場合)、ログイン情報を含むファイルを作成する。
6. 「~/.pypirc」に以下の内容を記述する。
1. [pypi]
2. username = your_pypi_username
3. password = your_pypi_password
6. PyPIへのアップロード
7. プロジェクトルートで以下のコマンドを入力
4. make pypi
5. (ホントは「make release」コマンドによるPyPI/Conda両方へのアップロードが推奨)
📖nbdevの記述方法
概要
🪐JupyterNotebookだけを編集しながら、アウトプットの「🐞テスト・🐍ライブラリ・📃ドキュメント」を意識して記述すること。
- 🐞テスト
- 書き方:Notebookにassert文を記述
- Notebookの正常な実行をもってテストとする。
- 🐍ライブラリ(*.py)
- 書き方
- モジュール分割:Notebookの先頭に
# default_exp (モジュール名)
を記述 - 関数・クラス定義:
#export
をつけたセルが出力される。
- モジュール分割:Notebookの先頭に
- 書き方
- 📃ドキュメント
- 書き方:Notebookのほぼ全ての内容がドキュメントに出力される。(
#hide
で抑制) - 見出しを付けて適切な論理構造でNotebookを記述すれば、そのまま読みやすいドキュメントになる。
- 書き方:Notebookのほぼ全ての内容がドキュメントに出力される。(
ライブラリ・ドキュメントへの出力制御
セルのキーワードによって、出力するか・しないかを切り替えることができる。
🐍ライブラリ | 📃ドキュメント | |
---|---|---|
(キーワードなし) | ❌しない | ✅する |
#export | ✅する | ✅する(クラス・関数定義のみ?) |
#hide | ❌しない | ❌しない |
#export #hide | ✅する | ❌しない |
記述例
nbdevで、Notebookに記述した内容がどのようにドキュメントに変換されるか比較した。
🪐Notebook | 📃ドキュメント | |
---|---|---|
(参考URL) | https://github.com/fastai/deck_of_cards/blob/master/00_card.ipynb | https://fastai.github.io/deck_of_cards/card.html |
モジュール名の指示 |
モジュール名:deck_of_cards.card 出力ファイル名:deck_of_cards/card.py ※ |
- |
見出し1 | ||
目次 (自動生成) | - | |
import****文 (出力しない) | - | |
クラス定義 **(コード) ** | ||
説明 (マークダウン) | ||
実行例 (コード) | ||
assert****文によるテスト (コード) | ||
(中略**)** | (中略) | (中略) |
show_docs (ドキュメントに関数定義を表示) |
※本例のライブラリ名はdeck_of_cards
入力・出力ファイルの対応
以下のような対応で、入力ファイル(Jupyter Notebook)に対して出力ファイル(ライブラリ・ドキュメント・README.md)が生成される。
🪐Notebook | 🐍ライブラリ | 📃ドキュメント | その他 |
---|---|---|---|
index.ipynb | - | docs/index.html(ドキュメントのトップページ) | README.md |
*.ipynb | *.py | docs/*.html(ドキュメント) | - |
便利機能
nbdev_clean_nbs
重要でないメタデータを除去して、Notebookをコンフリクトしにくくする。
Outputなどは変わらない。
全部 | Notebook****指定 |
---|---|
nbdev_clean_nbs | nbdev_clean_nbs --fname (Notebook名) |
Before | After |
---|---|
... "execution_count": 5, ... |
... "execution_count": null, ... |
所感
- nbdevは、少人数で大規模プロジェクトを開発できることを目標にしている。
- 反面、小さいパッケージを作りたい時は、機能が多すぎで余分な作業が多いかもしれない。(PyPIへの登録やGitHub Pagesでのドキュメントなど)
- 以下の整合性を同時に取りながらノートブックを記述するのが難しく慣れが必要。
- Notebook:ソースコード(*.ipynb)の可読性は問題ないか
- ライブラリ:モジュール(*.py)に必要なコードをexportしているか
- ドキュメント:順番に読んだとき意味の通る内容になるか
- テスト:コード(*.ipynb)の実行は成功するか
- これをするには出力制御構文(#export, #hide, show_doc(...))の挙動を正しく理解する必要がある
- 成果物を同時に作るのが難しい分、成果物の良くない点はソースコード(*.ipynb)の作成中に気づくので、すぐ修正できる。結果、PDCAが速く回り、生産性が向上する。