Edited at
JupyterDay 12

Jupyter Notebookの内部構造

More than 1 year has passed since last update.

Jupyter Advent Calendar 2017 12日目です。

Jupyter Notebookの内部構造を解説します。


Jupyter Notebookを輸出する

Jupyter は、科学技術文書を作成する環境として大変便利ですね。Download as のメニューから、Notebook以外の形式でも保存できます。現時点で保存可能な形式は、HTML、markdown、PDF、LaTeX、reST (restructured text)、使用中言語ソース (下では Julia) です。

スクリーンショット 2017-12-10 11.43.20.png

Jupyterから直接保存できない形式で使う場合には、Notebookファイルから直接変換するのがよいかもしれません。Notebook形式 (.ipynb) は JSONファイルですから、読み書きするのも簡単です。 


Notebookの内部構造

Notebook形式(.ipynb)の内部構造は、The Notebook file format に説明がありますが、構文規則のみです。実務家としては、実際の Notebook の中身をみるのが分かりやすいでしょう。

以下のような、Juliaプログラムの Notebookの中身を見てみます。gist に貼っておきます → https://gist.github.com/anonymous/8d51c367d5cee2828f3da30390033010

https://gyazo.com/6b88dbbadac14fe8d9d9eb61ce2b43e4

using PyPlot

ts=linspace(0,2pi)
plot( ts .* cos.(ts), ts .* sin.(ts))

NotebookのJSONの中身は、gist のraw形式で表示できます。そのまま貼り付けるのも芸がないので、Rubyを使ってYAMLで表示してみます。参考 → YAML <-> JSON 変換 (Rubyで実装)

require "JSON"

json=JSON.parse( File.read("Untitled10.ipynb"));
require "YAML"
YAML.dump(json)
---
cells:
- cell_type: markdown
metadata: {}
source:
- "# アルキメデスの渦"
- cell_type: markdown
metadata: {}
source:
- "$$r = \\theta\\quad\\text{極座標}$$"
- cell_type: code
execution_count: 1
metadata: {}
outputs: []
source:
- using PyPlot
- cell_type: code
execution_count: 2
metadata: {}
outputs:
- data:
text/plain:
- 0.0:0.1282282715750936:6.283185307179586
execution_count: 2
metadata: {}
output_type: execute_result
source:
- ts=linspace(0,2pi)
- cell_type: code
execution_count: 3
metadata: {}
outputs:
- data:
image/png:
iVBORw0KGgoAAAANSUhE ..(途中省略).. 64AAAAASUVORK5CYII=
text/plain:
- PyPlot.Figure(PyObject <matplotlib.figure.Figure object at 0x11e764eb8>)
metadata: {}
output_type: display_data
- data:
text/plain:
- '1-element Array{PyCall.PyObject,1}:

'
- " PyObject <matplotlib.lines.Line2D object at 0x1248e2eb8>"
execution_count: 3
metadata: {}
output_type: execute_result
source:
- plot( ts .* cos.(ts), ts .* sin.(ts))
- cell_type: code
execution_count:
metadata:
{}
outputs: []
source: []
metadata:
kernelspec:
display_name:
Julia 0.6.1
language: julia
name: julia-0.6
language_info:
file_extension:
".jl"
mimetype: application/julia
name: julia
version: 0.6.1
nbformat: 4
nbformat_minor: 2

以下のような内部構造が読み取れます。


  1. 最上位の要素は metadata, cells, nbformat, nbformat_minor です。前2つは配列で、最後の二つは整数です。


  2. metadataには、Jupyterのカーネルと、言語の情報が入ります。


  3. cellsには、Notebookの各セルが入ります。


    1. 各セルで重要な要素は、cell_type, metadata, sources です。


    2. cell_typeは、セルの型です。 markdownセルでは markdown、codeセルではcodeという値が入ります。 Raw NBConvert セル(=表示の際に変換されたくないデータを入れる)では rawという値が入ります。



      1. sourceには、入力文字列の各行が配列で入ります

      2. codeセルには、execution_countoutputの要素があります。



        1. execution_count は、入力セルの通し番号です。 In[n] の n に相当する整数またはnullが入ります。


        2. outputには、出力結果が配列で入ります。


          1. 出力結果の重要な要素は output_typedataです。


          2. outout_typeは、出力の形式です。


          3. data には、データ本体が入ります。
            以上です。









本例では、画像を一つ含んでいます。 画像に対応する出力セルは output_type=display_data という値となり、dataにはimage/pngの行に引き続き、BASE64形式で png画像が格納されていました。

ちなみに昨年のAdvent Calendarでは、Mayavi という3DオブジェクトをNotebookに埋め込む試みを紹介しました。 → [Python, Julia] Jupyter で 3D 表示 - Mayavi ライブラリ

Mayaviオブジェクトに対応する出力セルは、output_type = execute_result となり、dataにはtext/html に続き、xml形式のデータが含まれていました。


Scrapboxに輸出したい

Nota Inc. が提供するScrapboxは、複数人でリアルタイムに編集できるWikiです。各ページ中のタグやリンクを使うと、関連する複数のページが整理されて表示されます、Markdownと比べて記法が簡単です。 → Scrapboxヘルプ: 記法

https://gyazo.com/092e74b612f8d212b24dcfc4b80a6f8b

私は、Jupyter NotebookをScrapboxに輸出したくなりました。始めは手作業で、以下のようにすれば、scrapboxのページと親和性が高いことが分かりました。

- markdownセルは、Scrapboxの地のテキストに入れる。

- codeセルは、source(= プログラム)と出力テキストを、別々のコードブロックに格納する。


JupyterToScrapboxの紹介

Notebook形式からScrapbox形式への変換を支援するツールJupyterToScrapboxを紹介します。 Rubyで書かれていて gemでインストールできます。 bundler を使っていますが、その辺の環境設定は割愛します。

gem install jupyter_to_scrapbox

簡単な使い方は、以下の通りです。 Scrapboxで輸入できる形式のJSONファイルが scrapbox.json に格納されます。

bundle exec jupyter_to_scrapbox convert notebook1.ipynb notebook2.ipynb > scrapbox.json

さらに、Notebook内の画像を取り込む機能もつけました。Scrapboxの画像は、Nota Inc. の画像キャプチャサービス Gyazoへの各リンクになっています。Gyazo APIへのuser access tokenを環境変数GYAZO_TOKENに指定しておき、--imageオプションをつけて jupyter_to_scrapboxを起動します。

export GYAZO_TOKEN="(gyazo user access token)"

bundle exec jupyter_to_scrapbox convert Untitled10.ipynb --image > scrapbox.json

冒頭の Notebookから生成したScrapbox形式JSONを、YAMLで表示しましょう。

- title: '2017-12-10 01:15:04 +0900'

lines:
- '2017-12-10 01:15:04 +0900'
- "Untitled10.ipynb"
- ''
- "[*** アルキメデスの渦]"
- "[$ r = \\theta\\quad\\text{極座標} ]"
- code:source.jl
- "\t# In[1]"
- "\tusing PyPlot"
- ''
- code:source.jl
- "\t# In[2]"
- "\tts=linspace(0,2pi)"
- ''
- code:output.txt
- "\t0.0:0.1282282715750936:6.283185307179586"
- ''
- code:source.jl
- "\t# In[3]"
- "\tplot( ts .* cos.(ts), ts .* sin.(ts))"
- ''
- code:output.txt
- "\timage/png"
- "[https://i.gyazo.com/f720e453a77bd42bfe666fb5f8a2d914.png]"
- ''
- code:output.txt
- "\tPyPlot.Figure(PyObject <matplotlib.figure.Figure object at 0x11e764eb8>)"
- ''
- code:output.txt
- "\t1-element Array{PyCall.PyObject,1}:"
- "\t PyObject <matplotlib.lines.Line2D object at 0x1248e2eb8>"
- ''
- code:source.jl
- "\t# In[]"
- ''

codeブロックは、code:(ファイル名) で始まり、空行''で終わる範囲です。

プログラム列はsource.jlブロックに、出力テキストはoutput.txtブロックに格納することにしました。

これをScrapboxに取り込むと、以下のようになります。画像も含めて取り込まれています。

https://scrapbox.io/JuliaExamples/2017-12-10_01:15:04_+0900

https://gyazo.com/f6336483e04cc3d97097b731c4599c59

なお、上のURLに source.jlを付与すると、プログラム列のみが取り出せます。

https://scrapbox.io/api/code/JuliaExamples/2017-12-10_01:15:04_+0900/source.jl


source.jl

# In[1]

using PyPlot
# In[2]
ts=linspace(0,2pi)
# In[3]
plot( ts .* cos.(ts), ts .* sin.(ts))
# In[]


終わりに

Jupyter Notebookの内部構造と、Scrapboxへの変換ツールを紹介しました。 たくさんのNotebookをScrapboxに輸出して実例集を作ってみたいと考えています。詳細は Julia Advent Calendarとして書きます。