【この記事だけで完結】kaggle公式imageを使ってGCP+DockerでGPU環境構築

# はじめに
この記事はほぼ自分のメモ用です。

結論としてはDocker+GCPを使ってGPUが動くkaggle環境（kaggle notebookと同じ）を作ります。

突然ですが

###最近のkaggleのコンペ、データがめっちゃくちゃ多くてメモリが足りなかったり、cudf必須で無限のGPU時間が必要だったり、notebookコンペで訳わからんsubmission errorが出たり、しんどくないですか？？？？？？

そのしんどさを少しでも軽減できればなと思いDocker+GCPでの環境構築をはじめました。

この記事の嬉しさは以下かなと思います。
**・この記事だけで全て完結する
・どんなにメモリが必要なコンペでも可変的に対応することができる
・gpu時間が無制限（お金はかかる）
・kaggle notebookと全く同じ環境を作れるのでsubmission errorの原因が少なくなる**

またこの記事は実行場所が
・ローカル
・GCEインスタンス
・コンテナ
 と目まぐるしく入れ替わります。

コードブロックの左上にどこで実行すればいいか記載しているので注意してください。

## ちなみに
既存のQiitaの記事をうまく3~4個くらい合わせれば同じことができるんで車輪の再発明と言われればそうなんですが、一つの記事で構築できた方が楽かなと思いこの記事を書いてます。（というか自分が複数の記事を参照しながらやっててめんどくさかった）

完全に自分用の備忘録です。

 データセットの扱い方とか、もっといい方法があればコメントで教えてください。

# 目次

| 目次     | 
|:-----------------|
| [GCEインスタンスを建てる](#GCEインスタンスを建てる)  |   
| [CloudSDKを使ってローカルから接続](#CloudSDKを使ってローカルから接続)  |     
| [cudaのverを合わせる](#cudaのverを合わせる)         | 
| [GCEインスタンス上でDockerを利用しkaggle環境を構築](#GCEインスタンス上でDockerを利用しkaggle環境を構築)       | 
| [分析データをコンテナ内に展開しコンテナに入る](#分析データをコンテナ内に展開しコンテナに入る)     | 
| [コンテナ内で動作確認](#コンテナ内で動作確認)     | 
| [再開方法](#再開方法)     | 
| [おまけ（自動補完）](#おまけ（自動補完）)     | 


#GCEインスタンスを建てる

まずGCPのアカウントを作ってください。以下のリンクから飛べます。

`https://cloud.google.com/gcp/`

そうしたら新しくプロジェクトを作成し、左上のメニューからMarketplaceを選択します。

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540234/0aa4228b-7f1e-6c5b-83cc-daafe0c71c87.png">

ここで適当に「deep」などと検索し、以下を選択します。

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540234/3d280ff8-7176-b86e-1527-7f67322a96ba.png">

GCEからもっとカスタマイズしてインスタンスを建てる方が良いのかもしれませんか、コマンドやDockerを使ったりするための環境構築がめんどくさいので「deep Learning VM」を使用します。

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540234/e14e5ac3-5608-e244-bffa-c0d9b85a4d21.png">

運用開始をクリックすると以下のような設定画面が現れます。

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540234/922c4c75-bcab-4104-2e54-196866f84661.png">

自分が好きなように設定しましょう。なお、自分は以上のように構築しました。
後からCUDAを上書きするのでFrameWorkはなんでも大丈夫です。（ここが綺麗じゃないので気になる人はGCEから適切にインスタンスを建てた方がいいと思いますが、自分はめんどくさかったのでこの方法を使っています。）

**ここで2点注意があります**

**1.SSDのサイズを100GB以上にしましょう**
kaggle公式imageがかなり大きいので、１００GBより小さいとビルドに失敗するケースがあります。（友人に試してもらったところ80GBで失敗しました）

**2.CuDFを使用したい方はGPUの選択に気をつけてください。**
以下は`https://github.com/rapidsai/cudf`の画像ですがCuDFを動かす場合、「Pascal architectureかより良いもの(>= P100)」を使用する必要があります。（k80とかだと動きません）
なのでP100を選択するのが無難です。

各GPUと対応しているregionについては下記のサイトで確認することができます。
"https://cloud.google.com/compute/docs/gpus"


<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540234/fef0fb14-14e1-95fb-bb70-8e6161e6a022.png">

なお、最初はGPUの割り当て上限が0になっているので、警告から割り当てページにとんで上限を編集してください。

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540234/96c0cdf0-b6af-41e7-47f4-b84ab22902b7.png">

申請を行うとメールが届き、その後10分くらいで上限が変更されます。

以上の設定が終わったらデプロイをクリックしましよう。
これでGCEインスタンスを建てることができました！


#CloudSDKを使ってローカルから接続
次にcloud SDKを使って先ほど建てたGCEインスタンスに接続します。以下のサイトを参照にすれば問題なく可能ですが、一応mac版だけ手順を示します。
(どう考えても公式の説明の方がわかりやすく正しいので、スキップ推奨です完全に自分用に書いています。)

`https://cloud.google.com/sdk/docs/install`

まず上記のサイトに行き、macOS 64bit版をダウンロードしましょう。（なお上記のページにも書いてありますが、Pythonが必要です。ない場合は各自インストールしてください）

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540234/bddc9fb5-b2a7-34ce-76cf-2222f0a5ec4f.png">n

```:ローカル
./google-cloud-sdk/install.sh
```
を実行し、パスを通して、

```:ローカル
./google-cloud-sdk/bin/gcloud init
```
を実行し、指示に従いGoogleアカウントやプロジェクトへの紐付けを行います。（プロジェクトの変更や、googleアカウントの変更の時もこのコマンドを利用します。）

そうするとgcloudコマンドが使えるようになっているので、以下のようにコマンドをターミナルで実行し、ssh通信を行ってください。

```:ローカル
gcloud compute ssh [インスタンス名] --zone [ゾーン名]
```

ちなみに自分の場合は以下のようなコマンドを実行することになります。
<img width="900" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540234/5cf75a53-65e0-80a0-0071-82a6587537ac.png">


```:ローカル
gcloud compute ssh sample-vm --zone us-west1-b
```

これでGCEインスタンスに接続することができました。

なお、初回接続時にはnvidiaのドライバを入れるかどうか聞かれますが、次の節で入れるので「no」で大丈夫です。（一応「yes」verも試してみましたが、綺麗に上書きされるようで動作しました。）

次はDocekrを利用し、kaggle環境をコンテナ内で再現していきます。


#cudaのverを合わせる
今回は公式の以下のレポジトリを利用してコンテナを作成していきます。
`https://github.com/Kaggle/docker-python`
ここでgpu.Dockerfileを見てみましょう。
<img width="900" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540234/6be7533e-7695-d1d8-cd53-07d446a52e0f.png">
**そう！cuda10.2なんです！**
本当はGCEインスタンスを建てる時cuda10.2対応のFrameworkを使用するのが楽なんですが、なんと現在10.2だけ対応していません...
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540234/e4504d76-9f08-7b78-a88e-a1f719b83112.png">

なのでhost側（GCEインスタンス）にもcuda10.2をいれる必要があります。
「cudaを自分でいれる」と聞いて苦虫を潰したような顔になっているかもしれませんが、とても簡単です。（自分も最初ver合わせないといけないことに気づいた時絶望しました）
二行のコマンドを実行するだけです。以下のリンクからnvidiaの公式サイトに入り、
`https://developer.nvidia.com/cuda-toolkit-archive`
対応しているverをクリックしましょう。（今回だと10.2）
<img width="1200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540234/317af599-f3e2-c811-522a-39fd63248371.png">
そうすると以下のような画面が出てくるので、
`linux→x86_46→Ubuntu→18.04→runfile[local]`
の順で選択します。

そうするとBase Installerコマンドが出てくるのでこれをコピーして実行するだけです。

<img width="1200" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540234/467615f2-0a70-a157-d918-7edd31403615.png">

現在GCEインスタンスの中にgcloudコマンドで入っていると思うのでそのままコピーして実行します。

```　:GCEインスタンス内
$ wget https://developer.download.nvidia.com/compute/cuda/10.2/Prod/local_installers/cuda_10.2.89_440.33.01_linux.run
$ sudo sh cuda_10.2.89_440.33.01_linux.run
```

なお、下記のような表示が出てきますが、`accept→一番下まで行ってSelect→Yes`で大丈夫です。（`$ sudo sh cuda_10.2.89_440.33.01_linux.run
`は少し時間がかかりますが心配しないでください）

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540234/a76b88d7-9764-7758-705d-ac2e2640a7f1.png">
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540234/a625a383-477c-6de3-f105-388134ff53dd.png">
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540234/a6cf548d-4cd2-dc1b-0634-4729232a67ec.png">

インストールが終わったら以下のコマンドを実行して、GPUが認識されているか、指定したverのcudaが入っているか確認しましょう。

```:GCEインスタンス内
$ export LD_LIBRARY_PATH=/usr/local/cuda/lib64
$ nvidia-smi
```

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540234/c84fbe31-d3f2-0fab-1c07-29f282f38044.png">
自分の場合、cuda10.2が入っており、P100が認識されているため上手く行ったようです。

#GCEインスタンス上でDockerを利用しkaggle環境を構築
次にGCEインスタンス上にDockerを利用し、kaggle環境を構築します。
と言っても簡単で、以下のコマンドを実行し終わりです。
なお、ビルドにかなりの時間がかかることに注意してください。（30~60分程度）


```:GCEインスタンス内
$ git clone https://github.com/Kaggle/docker-python.git
$ cd docker-python

$ ./build --gpu
$ docker images
```


**なお、GCEインスタンスの起動中はお金がかかるので、ビルドしてるのを忘れて寝たりしなしでください！**
`docker images`で以下のように表示されていたら成功です。
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540234/fdd79855-e826-c244-a58a-60d6df4a7321.png">



#分析データをコンテナ内に展開しコンテナに入る

次に分析データをコンテナ内に展開し、コンテナに入ります。
手順としては以下の手順で行います。

`ローカルのデータをGCEインスタンスにコピーする→コンテナを作成し、GCEインスタンスのデータをマウント`

kaggle apiを使っても良いですが、ローカルのデータを分析したり、外部データを使用する時などに必要になってくるのでこの手順を踏みます。(逆にデータの大きさが100GBとかの場合はkaggle apiからデータを入れた方がいいです。)

まずサンプルとして以下のような適当なdatasetをダウンロードします。
`https://www.kaggle.com/c/titanic/data`

適当なディレクトリを作成し、そこにダウンロードしたデータを入れましょう。（今回はsample_datasetとします）
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540234/c4f4615b-b2f6-2a63-f14b-2bc7801ccd1a.png">

次にGCEインスタンスの中にデータを入れるためのディレクトリを作成します。今回はデモなので必要最低限で行います。

```:GCEインスタンス内
$ mkdir kaggle
$ cd kaggle
$ mkdir dataset
```


次に別のタブを開き以下でファイルを移動させます。

```:ローカル
$ gcloud compute scp --recurse ローカルのファイルパス GCEインスタンス名:コピー先パス --zone "ゾーン名"
```
今回の場合だと（自分の場合）

```:ローカル
$ gcloud compute scp --recurse ~/Desktop/sample_dataset sample-vm:~/kaggle/dataset --zone=us-west1-b
```

そうするとこのようにGCEインスタンス上にデータセットがコピーされています。
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540234/53aa6c02-d210-5f8e-75b1-6f5934ee3b51.png">

次にDockerのコンテナを起動させて、コンテナの中に入りましょう。

```:GCEインスタンス内
$ docker run -itd  --runtime=nvidia  -p 8888:8888   -v ~/kaggle/dataset/sample_dataset:/home/kaggle   --name kaggle-gpu   -h host   kaggle/python-gpu-build /bin/bash
$ docker container start kaggle-gpu
$ docker container attach kaggle-gpu
```

ちなみにここで重要なのは` -v ~/kaggle/dataset/sample_dataset:/home/kaggle`の部分で、`~/kaggle/dataset/sample_dataset`がマウント元、`/home/kaggle`がマウント先になります。
ファイル編集などはマウントされているディレクトリで行うようにしましょう。

コンテナ内に入ったらパスを通してgpuが使えるかどうか確認しましょう。


```:コンテナ内
$ export LD_LIBRARY_PATH=/usr/local/cuda/lib64
$ nvidia-smi
```
自分の環境では以下のように表示され、cudaのverが10.2で、GPUがP100と表示されていることから上手く使えていることがわかります。
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540234/fc7e6b8f-7899-4eb3-3496-ffb35fa4ff6a.png">

次にコンテナ内からjupyter notebookを起動しましよう。

```:コンテナ内
$ cd home
$ jupyter notebook --port 8888 --ip=0.0.0.0 --allow-root
```
次に別タブを開き、以下のコマンドで `http://localhost:28888`　からコンテナ内で起動させたノートブックに接続できるようにします。

```:ローカル
$ gcloud compute ssh インスタンス名 --zone ゾーン名 -- -N -f -L 28888:localhost:8888
```
なお、自分の場合だと以下のようになります。

```:ローカル
$ gcloud compute ssh sample-vm --zone us-west1-b -- -N -f -L 28888:localhost:8888
```

ブラウザから`http://localhost:28888`にアクセスし以下のような画面が表示されれば成功です
お疲れまでした！
<img width="900" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540234/eb1adacc-0d06-6d90-8b50-632c7a108082.png">

なお、windows環境だと-fがいらず、で接続可能です。

```:ローカル(windows)
$ gcloud compute ssh sample-vm --zone us-west1-b -- -N -L 28888:localhost:8888
```

#コンテナ内で動作確認


それでは最後にGPUが動くかどうか試してみましょう。
以下のようにP100が使われています。
<img width="900" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540234/91b04cd9-0f5c-5802-6773-ad4817cc8d25.png">

また,以下のようにCuDFが使えていることがわかります。
<img width="900" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540234/e22f0283-c953-5c9d-5a72-77aa83fe6431.png">

これでDocker+GCPを使ってGPUが動くkaggle環境（kaggle notebookと同じ）が完成しました！
**なお、GCEインスタンスの起動中はお金がかかるので、こまめに止めることを気をつけてください！**
お疲れ様でした！

また、適当にnotebookを作成してみてください。
GCEインスタンスを止めた後でも反映されたかを試すために利用します。
自分は`sample_dataset`以下に`sample.ipynp`を作成しました。
<img width="900" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540234/1456132c-f81b-8676-0482-f42a1ba13d5d.png">



#再開方法

最後に一度停止させたGCEインスタンスに入り、コンテナを起動する方法を紹介します。
まずGCPのプロジェクトからGCEインスタンスを再起動させます。
<img width="900" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540234/0081aab5-47dc-1bad-c736-c9133faefbb4.png">

次にインスタンスの再起動に成功したら以下のコマンドでローカルからインスタンス内に入ります。

```:ローカル
$ cloud compute ssh sample-vm --zone us-west1-b
```
以下のコマンドでコンテナの状態を確認しましょう。

```:GCEインスタンス内
$ docker ps -a
```
以下のように表示されていればOKです。
<img width="900" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540234/c544cde1-1486-7790-831e-49a818082725.png">

以下のコマンドでコンテナをrestartし、コンテナ内に入りましょう。

```:GCEインスタンス内
$ docker container restart kaggle-gpu
$ docker container attach kaggle-gpu
```
その後コンテナ内でhomeディレクトリに移動し、jupyter notebookを起動します。

```:コンテナ内
$ cd home
$ jupyter notebook --port 8888 --ip=0.0.0.0 --allow-root
```

さらに別タブを開いて以下のコマンドで`http://localhost:28888`で開けるようにします。

```:ローカル
$ gcloud compute ssh インスタンス名 --zone ゾーン名 -- -N -f -L 28888:localhost:8888
```
なお、自分の場合だと以下のようになります。

```:ローカル
$ gcloud compute ssh sample-vm --zone us-west1-b -- -N -f -L 28888:localhost:8888
```

`http://localhost:28888`にアクセスするとjupyter notebookにアクセスすることができます。


GCEインスタンス停止前に作成したnotebookがあることを確認してください。（マウントしているのでGCEインスタンスからでも確認することができます）

以上で再開方法は終わりです。
ちなみに作業を終えるときは`ctrl+c`でnotebookを終了し

```:コンテナ
$ exit
```
で抜けることができます。（再開時はやはり`restart`&`attach`です）

**何度もいいますが。お金がかかるのでこまめにGCEインスタンスは停止してください**

#おまけ（自動補完）

おまけにデフォルトの設定のままでは非常に使いにくいのでjupyter notebookに自動補完機能をつける方法を説明します。
コンテナの中に入り以下のコマンドを実行します。

```:コンテナ
$ pip install jupyter-contrib-nbextensions
$ pip install jupyter-nbextensions-configurator

$ jupyter contrib nbextension install
$ jupyter nbextensions_configurator enable
```

次にkaggle notebookを開き、タブのところに「」という項目が増えているので`Nbextensions`から`Hinterland`を有効にします。
<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540234/02174cd7-da2e-c517-1238-2e04226b68b4.png">
これで自動補完が可能になります。
<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540234/7aa347d2-9320-8f67-1d2f-6820df87e88e.png">

以上で備忘録は終わりです！
動かないところがあったらコメントで教えてください！
よろしくお願いします！

#参考文献

[GCPとDockerでKaggle用計算環境構築](https://qiita.com/lain21/items/a33a39d465cd08b662f1)

[GCPとkaggle公式イメージ(docker)でGPU環境の構築](https://qiita.com/taiga518/items/b69de2e00936e48578b0)

[Jupyter Notebookでコードの自動補完をする](https://www.soudegesu.com/post/python/jupyter-autocomplete/)