Julia - Jupyter notebook と Julia のソースコードの相互変換方法

Jupyter notebook って便利ですよね。私も数式の入ったメモを入れたい場合や、コードとプロット結果を一緒にしたいときにはよく利用しています*1

ただ一つ問題があって git との相性が恐ろしいほどに悪いことで有名ですよね。たった1行コードを追加しただけでも notebook の内部はそれ以上に変化してしまうので、git で管理しようにもどこに変更が入ったのか全然わかりません。

折衷案として notebook をソースコードに変換してそれを git で管理すると言う方法があります。

notebook のままだと1行追加するだけでも jupyter を起動せねばならず私としては億劫なのですが*2ソースコードなら編集も気軽にすることができます。しかし、nbconvert では notebook --> ソースコードの変換はできても、ソースコード --> notebook の変換ができないことが悩みの種でした。

そんなところに彗星のごとく現れた救世主が今回紹介する Literate.jl です。これを使うことで Julia のソースコードから notebook を生成することができます。README を読んだ時はいまいちイメージできなかったのですが、実際に notebook が作成されると感動しました!

github.com

以下、$ で始まるコードブロックは bash での実行を、julia> で始まるコードブロックは Julia の REPL での実行を表します。

目次

環境

julia> versioninfo()
Julia Version 1.0.0
Commit 5d4eaca0c9 (2018-08-08 20:58 UTC)
Platform Info:
  OS: Linux (x86_64-pc-linux-gnu)
  CPU: Intel(R) Xeon(R) CPU           E5645  @ 2.40GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-6.0.0 (ORCJIT, westmere)
Environment:
  JULIA_SHELL = /usr/bin/zsh

インストール

Literate.jl をインストール

julia> using Pkg; Pkg.add("Literate")

notebook からソースコードを生成する時に jupyter_contrib_nbextensions も必要らしいので入れます。

$ conda install -c conda-forge jupyter_contrib_nbextensions

github.com

使い方

Julia のソースコードを notebook に変換

まずはサンプルとして以下の内容を example.jl として作ってください。

# # Hello World
# こんにちは

x = 1
println("Hello World!")

ソースコード上でコメントになっているところが markdown cell になり、コードの部分が code cell になります。

Julia を起動し、以下のコマンドを実行してみましょう。

julia> using Literate

julia> Literate.notebook("example.jl", "."; documenter=false, credit=false, execute=false)

すると、example.ipynb という notebook がカレントディレクトリにできるはずです。 中身を見てみましょう!

f:id:goropikarikun:20180902223710p:plain

どうですか!ちゃんと notebook が生成されましたよ!!!

先程は executefalse にしましたが、ここを true にすると実行までされた notebook が生成されます。

julia> Literate.notebook("example.jl", "."; documenter=false, credit=false, execute=true)

f:id:goropikarikun:20180902223851p:plain

#- を入れると cell を区切ることが出来ます。

example.jl

# # Hello World
# こんにちは

x = 1
#-
println("Hello World!")

f:id:goropikarikun:20180902223919p:plain

Julia のソースコードmarkdown に変換

LIterate 使うと Julia のソースコードから markdown の生成も出来ます。

Literate.markdown("example.jl", "."; documenter=false, credit=false)

f:id:goropikarikun:20180902223911p:plain

notebook を Julia のソースコードに変換

心を新たにし、example.jl はまだ無いとします。
notebook から Julia のソースコードへの変換は "Download as" で Julia を選べばOK♪...では無いんです。残念。。。

f:id:goropikarikun:20180902233358p:plain

実際ダウンロードしてみればわかりますが、下記のように markdown の内容が消失します。

x = 1

println("Hello World!")

Python をダウンロードする時はちゃんと markdown も残ってるんですけど、Julia の場合だと消失します。 IJulia にあった issue によると nbconvert の問題だそうです。

github.com

うまいことテンプレートを作ると良いらしいのですが、ドキュメントを読んでもよくわからなかったので、上のリンク中で Carreau さんが提示されているテンプレートを使わせていただきました。
Customizing nbconvert — nbconvert 5.3.1 documentation

julia.tplを新規作成

{%- extends 'script.tpl' -%}

{% block markdowncell scoped %}
{{ cell.source | comment_lines(prefix='# ') }}
{% endblock markdowncell %}

notebook を Julia のソースコードに変換

$ jupyter nbconvert --to script 'example.ipynb' --template=julia.tpl

すると example.jl というファイルが出来ます。中身はこんな感じ。

# # Hello World
# こんにちは

x = 1

println("Hello World!")

変換されたこの書式はまさに Literate.jl で notebook に変換できる書式そのものではありませんか!!!

問題点

notebook --> ソースコードソースコード --> notebook と相互変換が出来るようになりましたが、完全に可逆というわけではありません。

ソースコード --> notebook の変換の時 #- を入れると cell を分割することが出来ましたが、notebook --> ソースコード では分割を表すような記号が入らないので notebook --> ソースコード --> notebook と変換をすると cell の分割が消えます。テンプレートで回避できそうな気がしますが、私は Jupyter の構造に詳しくないので解決策はわかりません。

おわりに

Literate で変換するソースコードを git で管理すれば、間接的に notebook を git で管理していることになるので大分 notebook のバージョン管理がやりやすくなるのではと思います。

Python だと gitnb というのを使うと notebook を git で管理しやすいようです。
GitHub - brookisme/gitnb: git tracking for python notebooks

*1:パッケージ作るときはもっぱら Atom 使っています

*2:エディタで直接編集することもできますが、編集箇所を探すのが大変