JupyterのDockerイメージまとめ

More than 1 year has passed since last update.

以下の記事のアップデート版です。Jupyter が5系にバージョンアップしているのと、イメージが整理されています。

Jupyter は Python のツールである IPython から出発しましたが、データ分析の過程を Web 上で共有できますので、R でも使われるようになり、最近ではHadoop基盤をバックエンドとして Spark との統合も進んでいます。

Numpy, pandas に代表されるように、データ分析に必要なライブラリは依存関係が複雑化してインストールに苦労することもありますが、Docker を使うことで実行環境のポータビリティを向上できます。また、実行環境をコンテナごとに分離できますので、Hadoop上でDockerを動かす仕組み (Docker Container Executor - Apache Hadoop 2.7.4) も検討されています(実用段階にはちょっと早いと思いますが)。

用途に応じて軽量なイメージを使い分ける方向性になると考えられますので、Jupyterで整理されているイメージをまとめておきます。

なお、Jupyter サーバーは単一ユーザーでの利用を想定しています。このため、パスワードは一種類のみの設定となります。複数人で利用するには JupyterHub を使ってください。


Dockerイメージの一覧

Jupyter の Docker イメージは以下のように分割して管理されています。GitHubのリポジトリは jupyter/docker-stacks ひとつですが、Docker Hub には複数のイメージがあります。

イメージ名
説明

base-notebook
Jupyter Notebook 5.0.x を使えます。科学計算ライブラリは含まれません。

minimal-notebook

base-notebook に pandoc と texlive などのドキュメント変換ツールが追加されています。

scipy-notebook
pandas や scikit-learn など、Python でのデータ分析ライブラリが含まれます。

datascience-notebook

scipy-notebook に R と Julia が追加されています。R の plyr などは conda で管理されています。

tensorflow-notebook

scipy-notebook に tensorflow が追加されています。GPUのサポートはありません。

pyspark-notebook

scipy-notebook に Spark 2.2.0 と Hadoop 2.7 が追加されています。Mesos 1.2 のクライアントも含まれます。

all-spark-notebook

pyspark-notebook に R と Toree および Spylon が追加されています。

r-notebook

minimal-notebook に R が追加されています。plyr などは conda で管理されています。Python も Julia も使わない場合には datascience-notebook よりも軽量に用意できます。

それぞれのイメージの関係性は GitHub リポジトリの Visual Overview の図 (internal/inherit-diagram.png) が分かりやすくなっています。

起動設定は base-notebook のシェルスクリプトで管理されていますので、オプションなどは "Base Jupyter Notebook Stack" の README を読むのが良いでしょう。

[off topic]

Toree や Spylon を使うと、Spark を使うノートブックを Scala で記述できます。しかし、プロジェクトのステータスとしてはアルファからベータという段階ですので、利用するときはどのバージョンを使っているかに注意する必要があります。

Jupyter にこだわらず、Spark を使うノートブックを構成したい場合には Apache Zeppelin も良い選択肢になるでしょう。


base-notebook を使う

base-notebook を使ってみます。実際にこのイメージで運用することはないと思いますが、約600MBのイメージですので、手早く起動してみる用途にはちょうど良いでしょう。

まずは pull コマンドでイメージをダウンロードします。

$ docker pull jupyter/base-notebook

$ docker images jupyter/base-notebook
REPOSITORY TAG IMAGE ID CREATED SIZE
jupyter/base-notebook latest 749eef0adf19 4 days ago 599MB

デフォルトの8888番ポートでコンテナを起動します。正常に起動するとコンソールに認証トークンが表示されますので、その URL をコピーしてブラウザでアクセスします。トークンを付けないで Web UI にアクセスし、入力ボックスに token= 以降の値を入力しても構いません。

$ docker run -it --rm -p 8888:8888 jupyter/base-notebook

(省略)

Copy/paste this URL into your browser when you connect for the first time,
to login with a token:
http://localhost:8888/?token=1898d7309d85940ac77acc59b15b4f9c572c96afdfc0e198

次に、バックグラウンドで実行するには、オプションを変えて実行します。

この場合、コマンド実行後の標準出力には何も表示されません。ノートブックにアクセスするには認証トークンが必要になりますので、 logs コマンドを使って起動時のログを確認します。引数には、コンテナ起動時に設定して名前を指定します。

$ docker run -d --name basenb -p 8888:8888 jupyter/base-notebook

$ docker logs basenb

ログの表示だと後から読みにくいという問題があります。 jupyter コマンドで、起動しているサーバーの一覧を返すこともできますので、 exec コマンドでコンテナにアクセスして jupyter notebook list を実行すると分かりやすい出力を得られます。

$ docker exec basenb jupyter notebook list

Currently running servers:
http://localhost:8888/?token=1898d7309d85940ac77acc59b15b4f9c572c96afdfc0e198 :: /home/jovyan


認証トークンとパスワード

Jupyter ノートブックサーバーのセキュリティについては以下のドキュメントに記載されています。

認証トークンを使う方法と、パスワードを使う方法の2種類があります。認証トークンは、HTTP の Authorization ヘッダーで与える方法と、URL の token パラメータで与える方法があります。

デフォルト設定では認証トークンを使う方法が有効化されており、URL の token パラメータで与える方法が起動時のメッセージに表示されていると言えます。

認証トークンをデフォルトで有効化する挙動は、Jupyter 4.3 から導入されています。

しかし、ドキュメントを整備し切れないままでのリリースだったようで、いくつかのオプションを組み合わせた場合の誤解も多々見受けられます。

ローカルマシンで起動時に自動的にブラウザを開くようにしている場合は、一時トークン付きのURLにアクセスし、アクセスできたという情報をクッキーに保存するのでシームレスに使えます。しかし、Docker から起動したりリモートサーバーで起動する場合はブラウザの自動起動を無効にしていますので、認証関係に一手間を要することになっています。

認証トークンとパスワードは起動時のオプションで指定できます。


  • --NotebookApp.token: デフォルト自動生成ではなく、固定の文字列を指定します。設定した値は起動時のログには出力されません。


  • --NotebookApp.password: URLおよびヘッダーの認証トークンを使わずパスワード認証とします。パスワード文字列は notebook.auth.passwd 関数でハッシュ化したものを使います。


原理的には両方に空白を指定すると認証機能を無効化できますが、Webアプリケーションの他のレイヤーでアクセスを制限していない限り、この方法は 強く非推奨 とされています。

[off topic]

設定オプションの処理は notebook/notebookapp.py で実装されていますので、オプション同士の関係などはソースコードを読むのが正確です。実装には Traitlets を使っていますので、リファレンスを開きながら読むと良いでしょう。

notebook.auth.passwd 関数は import して引数無しで呼び出すだけです。

指定したいパスワードを入力して、確認用に再入力してください。ハッシュ化した結果を出力します。

notebook_auth_passwd-on-Try Jupyter.png

設定ファイルとして保存するには、 jupyter notebook passwordpython -m notebook.auth password を実行します。前者の方が良いでしょう。

実行して設定したいパスワードを2回入力すると、 .jupyter/jupyter_notebook_config.json に設定ファイルが保存されます。ファイルにしておくと、Dockerコンテナからホストマシンのファイルをマウントすることで管理できます。なお、設定ファイルは Python で管理することも可能です。

$ docker exec -it basenb jupyter notebook password

$ docker exec -it basenb cat .jupyter/jupyter_notebook_config.json
{
"NotebookApp": {
"password": "sha1:2165e2ddd92d:8131245514d60dd9eb91433af30bf1ccbbc36962"
}
}

ここでは、コンテナを再起動させて設定ファイルの内容を反映させます。ブラウザを再読み込みすると、パスワードを入力するテキストボックスが表示されますので、上記で設定したパスワードでログインできることを確認しましょう。

$ docker stop basenb

$ docker start basenb

また、認証トークンが存在しなくなったため、ログにはトークン付きURLが出力されず、 jupyter notebook list コマンドでもトークンは表示されません。

これにより、デフォルトの認証トークンから、パスワードに認証に切り替えることができました。


dockerコマンドからの起動オプションによる設定

起動する度にログを確認したりファイルをマウントすることが適さない環境もあります。そのような場合は起動オプションで認証トークンかパスワードハッシュを与えます。

コンテナを起動するときに start-notebook.sh を使い、上述のオプションを指定します。

認証トークンを与える場合:

$ docker run -d --name basenb-token -p 8080:8888 jupyter/base-notebook start-notebook.sh --NotebookApp.token=foobarbaz

パスワードハッシュを与える場合:

$ docker run -d --name basenb-passwd -p 8088:8888 jupyter/base-notebook start-notebook.sh --NotebookApp.password=sha1:2165e2ddd92d:8131245514d60dd9eb91433af30bf1ccbbc36962

認証トークンとパスワードハッシュの両方を与える場合は両方のオプションを付けて実行します。

基本的にはパスワードで管理するのが良いと思いますが、クラウド環境で動かしてURLだけで簡単に共有したい場合は認証トークン方式も良いでしょう。ただし、アクセス元のIPアドレス制限などはユーザー環境のセキュリティポリシーに準じましょう。

その他、SSLの設定などは以下のドキュメントを参照してください。


scipy-notebook を使う

起動に関することを整理しましたので、先ほどとは異なる Docker イメージを使ってグラフを描画してみます。

scipy-notebook のイメージをダウンロードして容量を確認します。

$ docker pull jupyter/scipy-notebook

$ docker images jupyter/scipy-notebook
REPOSITORY TAG IMAGE ID CREATED SIZE
jupyter/scipy-notebook latest 092599e85093 5 days ago 3.91GB

バックグラウンドで起動してトークンを確認します。

$ docker run -d --name scipynb -p 8888:8888 jupyter/scipy-notebook

$ docker exec scipynb jupyter notebook list

Webブラウザでノートブックサーバーにアクセスし、Python 3 のノートブックを新規に作成します。

まずは下記のように定型のモジュール読み込みを実行します。


Pythonモジュールの読み込み

import pandas as pd

import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
%matplotlib inline

次に、三角関数の正弦と余弦のデータを生成します。


sin/cosの定義

import math

cycle = math.pi * 2
x = np.linspace(-1 * cycle, cycle, 100)
y1 = np.sin(x)
y2 = np.cos(x)

matplotlibのAPIで描画してみます。2つのデータ系列を指定し、X軸とY軸に名称を付けてグラフにもタイトルを付けます。日本語を使いたい場合はコンテナ内にフォントファイルを配置してください。 1


matplotlibのAPIで描画

sns.set_style('whitegrid')

plt.plot(x, y1, color='red', linewidth=2, label='sin')
plt.plot(x, y2, color='blue', linewidth=1, label='cos')
plt.xlabel('x axis')
plt.ylabel('y axis')
plt.title('sin/cos curve')
plt.legend()
plt.show()

Kobito.RrClEH.png

同じことをpandasのDataFrameのAPIで描画します。グラフのプロット自体は簡単ですが、軸の設定などは戻り値の matplotlib.AxesSubplot を使います。なお、上記のグラフとの違いを分かりやすくするために Seaborn でスタイルを暗めの罫線付きに変更しています。


DataFrameのAPIで描画

df = pd.DataFrame({'sin': y1, 'cos': y2}, index=x)

sns.set_style('darkgrid')
ax = df.plot(title='sin/cos curve')
ax.set_xlabel('x axis')
ax.set_ylabel('y axis')

Kobito.6I3ItZ.png

続いて、Bokeh を使って株価チャートを描画してみます。株価データは pandas-datareader を使って Yahoo! から取得します。 pandas-datareaderpandas.io パッケージから独立したものです。

モジュールをインストールするには pip を使います。ノートブックからコマンドを実行するには先頭に "!" マークを付けます。


pipを使ってモジュールをインストール

!pip install -y pandas-datareader


ex1.png

日付を指定してデータを取得します。


APIでデータを取得

import datetime

import pandas_datareader.data as web

start = datetime.date(2014, 4, 1)
end = datetime.date.today()

stocks = web.DataReader("^N225", 'yahoo', start, end)
stocks.head(5)


ex2.png

ノートブックで Bokeh を有効にします。


Bokehを有効化

import bokeh.plotting as bplt

bplt.output_notebook()

ex3.png

グラフを描画します。Bokeh は JavaScript を使ってブラウザでレンダリングしますので、コンテナ側に日本語フォントを置かなくても日本語を利用できます。


Bokehでグラフを描画

p = bplt.figure(title='日経平均', x_axis_type='datetime', plot_width=640, plot_height=320)

p.segment(stocks.index, stocks.Open, stocks.index, stocks.Close, color='black')
bplt.show(p)

ex4.png


pyspark-notebook を使う

PySpark のイメージも使ってみます。稼働中の Spark クラスタに接続するには追加設定が必要となりますが、API の簡単な動作確認などであればスタンドアローンの動作でも十分でしょう。

pyspark-notebook のイメージをダウンロードして容量を確認します。

$ docker pull jupyter/pyspark-notebook

$ docker images jupyter/pyspark-notebook
REPOSITORY TAG IMAGE ID CREATED SIZE
jupyter/pyspark-notebook latest 26c919b64b68 2 days ago 4.46GB

バックグラウンドで起動してトークンを確認します。

$ docker run -d --name pysparknb -p 8888:8888 jupyter/pyspark-notebook

$ docker exec pysparknb jupyter notebook list

Webブラウザでノートブックサーバーにアクセスし、Python 3 のノートブックを新規に作成します。

公式ドキュメントに沿って上から順に実行してみます。なお、ソースコードをクローンしているわけではありませんので、データは個別にダウンロードして使います。


Sparkセッションの構築

from pyspark.sql import SparkSession

spark = SparkSession \
.builder \
.appName("Python Spark SQL basic example") \
.config("spark.some.config.option", "some-value") \
.getOrCreate()


wget コマンドを使ってファイルをダウンロードします。


データのダウンロード

!wget https://raw.githubusercontent.com/apache/spark/master/examples/src/main/resources/people.json


DataFrame API を使ってデータを読み込みます。データとスキーマを表示し、カラム指定でデータを選択できることも確認します。


データの読み込み

df = spark.read.json("people.json")

df.show()
df.printSchema()
df.select("name").show()

SQLが使えることも確認します。簡単な集計などはこの方が進めやすいでしょう。


SQLの利用

df.createOrReplaceTempView("people")

sqlDF = spark.sql("SELECT * FROM people")
sqlDF.show()


tensorflow-notebook を使う

TensorFlow のイメージも使ってみます。TensorFlow Core の API を試すには GPU 無しでも十分でしょう。

tensorflow-notebook のイメージをダウンロードして容量を確認します。

$ docker pull jupyter/tensorflow-notebook

$ docker images jupyter/tensorflow-notebook
REPOSITORY TAG IMAGE ID CREATED SIZE
jupyter/tensorflow-notebook latest 374b8bc43218 3 days ago 4.5GB

バックグラウンドで起動してトークンを確認します。

$ docker run -d --name tfnb -p 8888:8888 jupyter/tensorflow-notebook

$ docker exec tfnb jupyter notebook list

Webブラウザでノートブックサーバーにアクセスし、Python 3 のノートブックを新規に作成します。

公式ドキュメントに沿って API の動作を確認します。

まずはモジュールをインポートして定数ノードを定義します。

import tensorflow as tf

node1 = tf.constant(3.0, dtype=tf.float32)
node2 = tf.constant(4.0)
node1, node2

セッションを定義してノードを評価します。セッションの run() メソッドにノードをリストにまとめて与えます。

sess = tf.Session()

sess.run([node1, node2])

ふたつの定数ノードに加算操作を適用するノードを定義し、ノードを評価します。

node3 = tf.add(node1, node2)

sess.run(node3)

入力を定数ノードではなく、評価時に与えられるようにします。TensorFlow では placeholder と呼ばれるデータ型を使います。なお、 a + btf.add(a, b) のショートカットとなっています。

a = tf.placeholder(tf.float32)

b = tf.placeholder(tf.float32)
adder_node = a + b

セッションの run() メソッドを呼び出すときに feed_dict 引数に対して値を指定します。

sess.run(adder_node, {a: 3, b: 4.5})

引数をリストで与えると、それぞれの要素に加算操作が適用されます。定数もベクトルも同じように扱えるのは便利ではないでしょうか。

sess.run(adder_node, {a: [1, 3], b: [2, 4]})

ノードを接続してセッションで評価する流れは、他の操作に対しても同様です。ただし、小数の演算では端数処理が想定とは異なることがあるかもしれませんので、簡単な計算で動作を確認する場合は注意しましょう。

add_and_triple = adder_node * 3.

sess.run(add_and_triple, {a: [1, 2], b: [3.4, 5.6]})

ここまでをノートブックにまとめると以下のようになります。

tf.png

公式ドキュメントではここから機械学習の扱い方について進んでいきます。変数を定義して線形モデルを定義したり、訓練データを与えてモデルを構築して実行結果を評価する方法が記載されています。

また、 tensorflow.examples.tutorials.mnist モジュールで MNIST の画像認識を試すこともできますので、チュートリアルを順番に読み進めてみると良いでしょう。


まとめ

Jupyter の Docker イメージが整理されましたので、 base-notebook, scipy-notebook, pyspark-notebook, tensorflow-notebook を使ってみました。

データサイエンスで利用できるツールセットは増えてきており、ひとつひとつの管理は大変かもしれませんが、Docker イメージを使うことで初期導入の煩雑さを解消できます。大規模にデプロイしたりパフォーマンスチューニングも懸念として挙げられますが、AWS や GCP などのクラウド環境ではコンテナの利用も簡易化されていますので、ポータビリティの高い再現環境を手軽に作れるようになっています。

用途に応じて公開イメージを使い分けたり、自分で拡張したりと、選択肢の幅が増えているのは良いことだと言えるでしょう。





  1. グラフ軸の日本語設定方法はこちらの記事を参照してください。matplotlib と Seaborn の軸の日本語設定 - Qiita