Dockerを利用してオフラインで機械学習を行う環境を構築する

インターネット接続のないオフライン環境でPythonによる機械学習を行うためにはどうすればよいのでしょうか。今回は、その一例としてDockerによる環境構築法を紹介します。

開発環境

  1. オンライン環境:Windows 10 Pro 64bit
  2. オフライン環境:Ubuntu 16.04↓
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 16.04.5 LTS
Release: 16.04
Codename: xenial

まずはオンライン環境にDockerをインストールする

まずはDockerのサイトの手順に従ってDocker Desktop (Windows)をダウンロード・インストールします。


Dockerのサイトにも書かれていますが、いったんDockerをインストトールすると設定を変更しない限りOracleのVirtualBoxが使えなくなるので注意しましょう。ただし、VirtualBoxに保存されているイメージが消えるわけではないので安心してください。

Dockerが正しくインストールされたかテストする

ターミナルウィンドウを開きましょう。コマンドプロンプトあるいはWindows PowerShellです。Windows PowerShell ISEは使えないので注意してください。

docker --versionまたはdocker -vでDockerのバージョンを確認することができます。

> docker -v
Docker version 18.09.0, build 4d60db4

次にテスト用イメージをダウンロードしてコンテナを起動します。

イメージをダウンロードするときはdocker pull <イメージ名>で指定します。今回はテスト用のイメージとして利用されているhello-worldイメージをpullします。

> docker pull hello-world
d1725b59e92d: Pull complete
Digest: sha256:0add3ace90ecb4adbf7777e9aacf18357296e799f81cabc9fde470971e499788
Status: Downloaded newer image for hello-world:latest

pullしたイメージのリストはdocker imagesで確認することができます。

> docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-world latest 4ab4c602aa5e 3 months ago 1.84kB

 

REPOSITORYはイメージの名前、TAGはイメージのバージョンだと思ってください。docker pullする際にdocker pull <イメージ名>:<TAG>でTAGを指定することもできます。したいしない限りlatest、すなわちイメージの最新バージョンがpullされます。

次に、pullしたイメージを使ってコンテナ化します。docker run <イメージ名(REPOSITORY)>でコンテナ化できます。

> docker run hello-world

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/

For more examples and ideas, visit:
https://docs.docker.com/get-started/

といった具合に表示されれば成功です。hello-worldは上記の内容を表示するだけの機能を持ったイメージです。

起動しているコンテナのリストはdocker psで見ることができます。

> docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

hello-worldはrunすると先ほどの内容を表示して終了してしまうので、docker psで見ても表示さないことが分かります。

構築したすべてのコンテナの一覧はdocker ps -aで見ることができます。

> docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4f193fac9472 hello-world "/hello" 5 minutes ago Exited (0) 5 minutes ago zealous_turing

上の表示を見てみると、IMAGEはhello-worldですがNAMESがzealous_turingになっているのが分かります。コンテナのイメージ名はhello-worldなのですが、作成したコンテナには別の名前が付きます。それがNAMESです。このNAMESはrunする際にdocker run -name <コンテナ名> <イメージ名>で指定することができます。以下の例ではコンテナ名をtmpで指定してhello-worldをrunさせました。

> docker run --name tmp hello-world

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/

For more examples and ideas, visit:
https://docs.docker.com/get-started/

作成したコンテナを見てみましょう。

> docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
18145b90389c hello-world "/hello" 15 seconds ago Exited (0) 12 seconds ago tmp
4f193fac9472 hello-world "/hello" 15 minutes ago Exited (0) 15 minutes ago zealous_turing

最初に作成したコンテナが2番目で、コンテナ名を指定して作成したものが1番目です。NAMESがtmpで表示されていることが確認できます。

コマンドで何かわからないことがあればdocker --helpで調べましょう。 具体的にdocker runに関するコマンドで分からないことがあればdocker run --helpで調べることもできます。

次に、オフライン環境側にもDockerをインストールします。

オフライン環境にDockerをインストールする

インナーネット接続のない環境(今回はUbuntu)でDockerをどのようにインストールするかについてですが、『docker docs』に、Dockerのパッケージをダウンロードしてから展開する方法が載っています。

大まかな流れを言っておくと、オフライン環境のOSなどのスペックとマッチするようにcontainerd.ioとdocker-ce-cli、docker-ceの合計3つのパッケージをいったんオンライン環境でダウロードしたらオフライン側にscp転送し、インストールする感じです。インストール時の状況に応じて追加でパッケージをオンライン環境側でダウンロード、オフライン環境にscp転送してインストールしてください。

終わったら、hello-worldイメージでテストしておくと良いです。

ローカルで機械学習コンテナを作成する

コンテナ化のテストに成功したら、いよいよ機械学習用のコンテナ作成に入りましょう。

今回はUbuntu 16.04のイメージをpullしてrunしたあとに機械学習の環境構築を行います。あるいは、Dockerfileを自前で書いてイメージをビルドし、あらかじめ必要なパッケージがインストールされた状態からコンテナ化する方法もありますが、ここではUbuntuコンテナを起動してから環境を構築していきます。

Dockerfileを書きたい方は『Dockerを使ってみる – ももいろテクノロジー』に参考例が載っています。

まずはUbuntu 16.04のイメージをpullします。tagを指定しないとlatestでpullしてしまうので、きちんとバージョンを指定します。

> docker pull ubuntu:16.04
16.04: Pulling from library/ubuntu
7b8b6451c85f: Pull complete
ab4d1096d9ba: Pull complete
e6797d1788ac: Pull complete
e25c5c290bde: Pull complete
Digest: sha256:e547ecaba7d078800c358082088e6cc710c3affd1b975601792ec701c80cdd39
Status: Downloaded newer image for ubuntu:16.04

コンテナ化します。

docker run -it -d --name ML_python2 ubuntu:16.04
36463f883b2af49b28840bcc11d1fa8006e0840d3a9debd1d60a3cffaa420aef

このとき-itを指定することでbashを開き続けることができ、-dでコンテナをバックグランドでもrunし続けられるようにしています。–nameでコンテナ名をML_python2にしました。

docker psでコンテナの起動を確認します。

> docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
36463f883b2a ubuntu:16.04 "/bin/bash" 5 minutes ago Up 5 minutes ML_python2

大丈夫そうですね。

このままコンテナを停止せずにbashを開きましょう。docker execコマンドを使います。docker exec <NAMES> <COMMAND>です。このとき-itを指定してbashが開き続けられるようにします。

> docker exec -it ML_python2 /bin/bash
root@36463f883b2a:/#

もし-itを指定しないと、

> docker exec ML_python2 /bin/bash
>

となってしまいます。

コンテナの環境構築を行う

さきほどbashを開いた状態から引き続いて必要なアップデートやインストールを行いましょう。今回はpython2系のみインストールすることにします。

root@36463f883b2a:/# apt-get update && apt-get -y upgrade
root@36463f883b2a:/# apt-get -y install build-essential root@36463f883b2a:/# apt-get -y install git root@36463f883b2a:/# apt-get -y install python

あとは機械学習に必要な各種モジュールをインストールしましょう。pipがインストールされているか確認します。

root@36463f883b2a:/# pip --version
bash: pip: command not found

んー、インストールされていませんね。インストールしましょう。

root@36463f883b2a:/# apt-get install python-pip

もう一度確認します。

root@36463f883b2a:/# pip --version
pip 8.1.1 from /usr/lib/python2.7/dist-packages (python 2.7)

無事インストールできました。

You are using pip version 8.1.1, however version 18.1 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.

が表示されることがありますが、

root@36463f883b2a:/# pip install --upgrade pip

と、素直に実行すると、

root@36463f883b2a:/# pip install scikit-learn
(省略)
ImportError: cannot import name main

といった形でImportErrorとなってしまいますが、どうやらpipのversion9と10の間に大きな隔たりがあるようです。以下のコマンドで解決できます。

root@36463f883b2a:/usr/bin# hash -r
root@36463f883b2a:/usr/bin# pip --version
pip 18.1 from /usr/local/lib/python2.7/dist-packages/pip (python 2.7)

あとはnumpyやらscipyやらインストールしてください。

root@36463f883b2a:/# pip install numpy
root@36463f883b2a:/# pip install scipy
root@36463f883b2a:/# pip install scikit-learn
root@36463f883b2a:/# pip install matplotlib
root@36463f883b2a:/# pip install pandas
root@36463f883b2a:/# pip install h5py

pymvpa2モジュールをインストールしたい人はあらかじめ、

root@36463f883b2a:/usr/bin# apt-get install swig

でswigをインストールしてから実行しましょう。

root@36463f883b2a:/# pip install pymvpa2
root@36463f883b2a:/# pip install datetime
root@36463f883b2a:/# pip install nilearn
root@36463f883b2a:/# pip install chainer
root@36463f883b2a:/# pip install tensorflow
root@36463f883b2a:/# pip install pymc3
root@36463f883b2a:/usr/bin# pip install theano

必要な環境が構築できたら、コンテナを終了しましょう。

root@36463f883b2a:/usr/bin# exit
exit
> docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
36463f883b2a ubuntu:16.04 "/bin/bash" 3 hours ago Up 3 hours ML_python2
> docker stop ML_python2
ML_python2
> docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
>

作成したコンテナをイメージに変換する

コンテナをイメージに変換します。

> docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
36463f883b2a ubuntu:16.04 "/bin/bash" 3 hours ago Exited (0) 2 minutes ago ML_python2
18145b90389c hello-world "/hello" 3 hours ago Exited (0) 3 hours ago tmp
4f193fac9472 hello-world "/hello" 4 hours ago Exited (0) 4 hours ago zealous_turing
>

docker commit <CONTAINER ID> <イメージ名>でO.K.です。イメージ名は新しく自分でつけてください。今回はubuntu1604と名づけました。

> docker commit 36463f883b2a ubuntu1604
sha256:eb542727f40ff0935d48277876cc433e01d5848cd67679ff177664616e9d531d
>

イメージができたか確認します。

> docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu1604 latest eb542727f40f 3 minutes ago 1.56GB
ubuntu 16.04 a51debf7e1eb 4 weeks ago 116MB
miykael/nipype_tutorial latest 472830b32e1b 7 weeks ago 5.29GB
hello-world latest 4ab4c602aa5e 3 months ago 1.84kB
>

一番上のが今作ったやつですね。続いてイメージを.tarに丸めます。圧縮したときのファイル名も任意です。ここではREPOSITORY(イメージ名)と同じにしておきます。出来上がるまで待ちましょう。

> docker save ubuntu1604 -o ubuntu1604.tar
>

カレントディレクトリに保存されているはずです。

> ls
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2018/12/20 15:37 1588707328 ubuntu1604.tar
>

これをオフライン環境にscp転送しましょう。以降はオフライン環境側のbashです。

転送したイメージをオフライン環境で展開、実行する

Downloadというディレクトリを作ってそこに置きました。

~/Downloads$ ls
ubuntu1604.tar

.tarに丸めたイメージはdocker loadで展開します。

~/Downloads$ sudo docker load -i ubuntu1604.tar                                                                 
Loaded image: ubuntu1604:latest
~/Downloads$

イメージが無事にロードできたかを確認します。

$ sudo docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu1604 latest eb542727f40f 32 minutes ago 1.56GB
hello-world latest 4ab4c602aa5e 3 months ago 1.84kB
~/Downloads$

runします。このとき-vオプションを使ってコンテナの指定したディレクトリをホストの指定したディレクトリにマウントします。これによってファイル共有が実現します。-v <オフライン環境側のディレクトリの絶対パス>:<コンテナ側のマウントするディレクトリの絶対パス>でO.K.です。クライアント側のhomeにdocker_python2というディレクトリを作成しておきました。コンテナ側のディレクトリはhomeにしました。

$ sudo docker run -it -d -v /home/docker_python2:/home --name python2 -d ubuntu1604
3bb30df50b1f6fbbf59259af3dbab7347b2223835b513b7a3fcab9be969ccec5
$ sudo docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3bb30df50b1f ubuntu1604 "/bin/bash" 15 seconds ago Up 13 seconds python2
1ab5e5fc3927 hello-world "/hello" 7 days ago Exited (0) 7 days ago stupefied_turing
$

-dオプションを付けたのですが、runしてもコンテナが起動しませんでした。コンテナを起動する前に、マウントが無事にできたか確認したいので、ホストOS側の/home/docker_python2にhogeというディレクトリを作成しました。

~/docker_python2$ mkdir hoge
~/docker_python2$ ls
hoge
~/docker_python2$

コンテナを起動したいときはdocker start <NAMES>でO.K.です。NAMESはコンテナ名です。これでコンテナのbashが開きます。

$ sudo docker start  python2
python2
$ sudo docker exec -it python2 /bin/bash
root@3bb30df50b1f:/#

hogeファイルがあるかを確認します。

root@3bb30df50b1f:/# cd home
root@3bb30df50b1f:/home# ls
hoge
root@3bb30df50b1f:/home#

pythonも起動してみます。pythonを終了するときはexit()またはCtrl-Dです。bashから抜けるときはexitです。コンテナはdocker stop <NAMES(コンテナ名)>で停止します。

root@3bb30df50b1f:/home# python
Python 2.7.12 (default, Nov 12 2018, 14:36:49)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> import numpy as np
>>>
root@3bb30df50b1f:/home# exit
exit
~/docker_python2$
~/docker_python2$ sudo docker stop python2
python2
~/docker_python2$ sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
~/docker_python2$ sudo docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3bb30df50b1f ubuntu1604 "/bin/bash" 27 minutes ago Exited (0) 19 seconds ago python2
1ab5e5fc3927 hello-world "/hello" 7 days ago Exited (0) 7 days ago stupefied_turing
~/docker_python2$

あとは好きなようにpythonスクリプトを実行してください。

お疲れさまでした。