GPUを搭載したUbuntuで最強のDeep Leaning開発環境を作る

機械学習を行うためのマシンでnvidiaのGPU RTX-3060+Intel Core i5のマシンを購入しました。そこにUbuntu(Linux)をインストルしてTensorflowで機械学習をする環境を整えたのですが、右往左往して大変だったので、手順をブログにまとめました。5日ほど右往左往しながら環境構築した結果を思い出しながら書きましたので、間違いがあればご指摘いただけますようお願いいたします。

作成した開発環境はこのとおりです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136933/55ffb60c-a48a-f08e-4e03-1bad4890b9f2.png)

専用マシンなのでDockerなんていらないと思っていたのですが、Dockerを使用しないとGPUのロードに5分くらい時間がかかる問題を解消できず、仕方なくDockerを導入しました。

## 結果
M1チップ搭載のMacbook Airと比較しても以下の通り、早く動作することが確認できました。
これは、よくある手書き認識 ministを動かしたときの時間の比較になります。

| |M1 MBA | Ubuntu |
|--|--|--|
|Tensorflow |1分59秒 |1分22秒 |
| keras| 39秒 | 19秒 |

それでは作業していきます。

# 1. Linuxカーネルのダウンロードとインストール

[こちら](https://jp.ubuntu.com/download)のサイトからUbuntu Desktop 22.04LTSをダウンロードして導入しました。
https://jp.ubuntu.com/download

ダウンロードしたISOファイルをUSBメモリ等に書き込みをします。

ISOファイルの書き込みは、[Balena Etcher](https://www.balena.io/etcher/)や[Win32DiskImager](https://sourceforge.net/projects/win32diskimager/)を利用して書き込みを行うことで、USBメモリやSDカードをブートイメージにすることができます。

作成したUSBをPCに挿入してLinuxをインストールするPCに接続して起動してOSのインストールをしてください。

# 2. UbuntuのGUIを変える
UbuntuのGUIでも気にならない人は飛ばしてください。

UbuntuのGUIは個人的に好きではありません。私は以前 Lubuntuを使用していたのですがどうも安定しませんでした。今回は結果としてLXDEを導入することで安定動作することが確認できました。
インストールのコマンドはこちらです。
```bash:
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install lxde
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136933/2ad4bf07-d1bd-139d-7700-28dd9b86d5c1.png)
LXDEのGUI

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136933/854e594c-6817-d58e-88e5-0dd1fc3f7a0c.png)
UbuntuのGUI

# 3. nvidiaのドライバをインストールする
## 3-1. ドライバのインストール
こちらのリンクからnvidiaのドライバをダウンロードしてインストールをします。

https://www.nvidia.co.jp/Download/index.aspx?lang=jp

ダウンロードしたファイルを実行してドライバをインストールします。

```
sh NVIDIA-Linux-x86_64-515.48.07.run
```

## 3-2. nvidia dockerのインストール
[こちら](http://docs.nvidia.com)のサイトを参照してnvidia-docker2をインストールします
http://docs.nvidia.com

# 4. GPUに対応したTensorflowのDockerイメージファイルをインストール
利用するtensorflowのdockerイメージをダウンロード

```bash:
docker pull tensorflow/tensorflow:latest-gpu-jupyter
```

以下のコマンドを入力して動作確認をします。
--gpus allはGPUを全て使用するオプションになります。

```
docker run --gpus all -it tensorflow/tensorflow:latest-gpu-jupyter bash

________                               _______________                
___  __/__________________________________  ____/__  /________      __
__  /  _  _ \_  __ \_  ___/  __ \_  ___/_  /_   __  /_  __ \_ | /| / /
_  /   /  __/  / / /(__  )/ /_/ /  /   _  __/   _  / / /_/ /_ |/ |/ / 
/_/    \___//_/ /_//____/ \____//_/    /_/      /_/  \____/____/|__/


WARNING: You are running this container as root, which can cause new files in
mounted volumes to be created as the root user on your host machine.

To avoid this, run the container by specifying your user's userid:

$ docker run -u $(id -u):$(id -g) args...
```

続いて、nvidia-smiのコマンドを実行して、以下のような画面が表示されたら正しくnvidiaのドライバがインストールされています。

```
root@5373f491de28:/# nvidia-smi
Mon Jun 27 11:46:24 2022       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 510.73.05    Driver Version: 510.73.05    CUDA Version: 11.6     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|                               |                      |               MIG M. |
|===============================+======================+======================|
|   0  NVIDIA GeForce ...  Off  | 00000000:01:00.0  On |                  N/A |
|  0%   56C    P8    10W / 170W |     52MiB / 12288MiB |      0%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+
                                                                               
+-----------------------------------------------------------------------------+
| Processes:                                                                  |
|  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
|        ID   ID                                                   Usage      |
|=============================================================================|
+-----------------------------------------------------------------------------+
```

dockerを抜けるときは、exitを入力してください。そして、docker ps -aqで起動中のdockerのプロセスを表示することができ、docker rmでプロセスをKILLすることができます。

```
root@5373f491de28:/# exit
docker ps -aq
ca3814cc4195
docker rm ca3814cc4195
```

# 5. VS Codeのインストール
開発環境に利用するVisual Studio Code(以下、VS Code)をインストールします。まず、[こちら](http://code.visualstudio.com
)のサイトにアクセスしてLinux側にVS Codeをインストールします。
導入したOSはUbuntuなので、.debパッケージのダウンロードをします。
http://code.visualstudio.com

ダウンロードしたら以下のコマンドでインストールすることができます。

```
sudo install apt-get install ~/Downloads/code_1.68.1-1655263094_amd64.deb
```
インストールしたら、VS Codeの「拡張機能」で次の機能をインストールします。

- Docker
- Japanese Language Pack for Visual Studio Code
- Python
- Remote-Containers

# 6. Docker composeのインストールと設定
## 6-1. Docker composeのインストール
こちらのページを参照し、Docker composeをインストールします。

Docker Compose のインストール — Docker-docs-ja 19.03 ドキュメント
http://docs.docker.jp

## 6-2. Docker composeの設定
tensorflowの作業用フォルダを作成して、そのフォルダに.devcontainerのフォルダを作成して次の3つのファイルを作成します。

```
.devcontainer/
├── Dockerfile
├── devcontainer.json
└── docker-compose.yml
```
それぞれのファイルには以下の内容を記述します。

### Dockerfile
dockerとUbuntuのユーザID, Group IDを同じにして、ファイルのアクセスに支障が出ないようにしています。

また、matplotlib, pandas, scikit-leanを追加でインストールしています。他に必要なライブラリがあれば追加・変更を行ってください。

```:Dockerfile
FROM tensorflow/tensorflow:latest-gpu-jupyter

RUN apt-get update
# ユーザーを作成
ARG UID=1000
RUN useradd -m -u ${UID} user

# 作成したユーザーに切り替える
# このユーザーはRUN, CMD, ENTRYPOINT, docker run, exec の実行ユーザ。
USER ${UID}

# プロジェクトフォルダを/codeに追加する。ユーザー権限で扱えるようchownオプションを使う。
# ADDの実行権者はrootなのでオプションが必要。
ADD  --chown=user:user . /work
# 作成したフォルダに移動し、パッケージリストをインストールする。
#WORKDIR /work

RUN pip install matplotlib
RUN pip install scikit-learn
RUN pip install pandas
```

### devcontainer.json
workspaceFolderとして/tfを指定しています。これはtensorflowのサンプルが/tfに保存されているのと、jupyter notebookのパスが/tfになっているためです。

```json:devcontainer.json
{
	"name": "Existing Dockerfile",
	"context": "..",
	"dockerFile": "./Dockerfile",
	"workspaceFolder": "/tf",
	"settings": { 
		"terminal.integrated.shell.linux": "/bin/bash",
		"python.pythonPath": "/usr/local/bin/python",
		"python.linting.enabled": true,
		"python.linting.pylintEnabled": true,
		"python.linting.pylintPath": "/usr/local/bin/pylint"
	},
	"appPort": [ 9000 ],
	"remoteUser": "user",
	"extensions": [
		"ms-python.python"
		]
}
```

### docker-compose.yml
volumesのところで、.devcontainerのフォルダのひとつ上をdockerの/tf/workにマウントするように指定しています。

ulimitsのところでGPUのメモリの制限をなくしています。デフォルトだとメモリ不足によりアプリケーションが動かないことがあります。

deploy > resources > reservations > devices、およびenvironmentのところでGPUを全て使用するような設定をしています。

exportsとportsのところで、dockerのポートをubuntuの8888番のポートで使用できるようにしています。

```yaml:docker-compose.yml
version: '3'
services:
  tensorflow:
    build: .
    volumes:
      - ../:/tf/work
    working_dir: /tf
    ulimits:
      memlock: -1
      stack: -1
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              capabilities: [ gpu ]
    tty: true
    expose:
      - "8888"
    ports:
      - "127.0.0.1:8888:8888"
    environment:
      - NVIDIA_VISIBLE_DEVICES=all
      - NVIDIA_DRIVER_CAPABILITIES=all
```

これらのファイルを作成したら、VS Codeで作業用フォルダを開き、.devcontainer/docker-compose.ymlを左クリックして、Compose Upのメニューを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136933/ff74267b-6479-cde4-3ec7-6fa0dc2d7c0a.png)

画面左端のDockerのアイコンをクリックして、今作成したDocker composeのイメージにVS Codeをアタッチします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136933/cad53e3a-a9f9-9f34-e2b1-52be881820bd.png)


新しく開いたVS Codeのターミナル部分でnvidia-smiのコマンドを実行して、nvidiaのGPUが認識されていればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136933/5a4836d4-30a1-fc39-4c9d-0ab4d62e422b.png)


7. 別PCからリモートアクセスするための設定
以下の作業はMACまたはLinuxで使用できます。Windowsは開発に利用していないため設定方法は知りません。

7-1. SSHの鍵情報を作成する
ssh-keygenで鍵情報を作成します。
すでに鍵情報がある場合はそれを利用すれば良いので作成は不要です。

```
mkdir ~/.ssh
cd ~/.ssh
ssh-keygen
```
コマンドを実行すると、id_rsa.pub, id_rsaなどのファイルが作成されます。

7-2. SSHの鍵情報をUbuntuに登録する
作成した鍵情報をUbuntuにコピーします。
```bash:
ssh-copy-id ${ubuntuのユーザ名}@${UbuntuのIPアドレス}
ssh ${ubuntuのユーザ名}@${UbuntuのIPアドレス}のコマンドでパスワードがなくてもログインできることを確認します。
```
続いて、`~/.ssh/config`に以下の情報を追記します

```
Host Ubuntu
  HostName {UbuntuのIPアドレス}
  User {ubuntuのユーザ名}
  IdentityFile ~/.ssh/id_rsa
	LocalForward   8888 localhost:8888
  Port 22
```
LocalForwardはJupyter notebootのポートを別PCからアクセスできるようにするための設定です。

設定は以上になります。

# 8. 別PCから利用してみる
## 8-1. Pythonの実行環境

利用するPCのVS CodeにRemote - SSHの拡張機能をインストールすると、左端のメニューにリモートエクスプローラが表示されて、SSHターゲット先程登録したLinuxマシンが表示されます。フォルダのマークをクリックするとターゲットのPCのファイルを編集できるようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136933/9d97a8d0-038b-578c-3b59-264dcdba3ff9.png)


クリックすると別の画面が開くので、そこでもリモートエクスプローラのメニューを選択して、Containerに切り替え、作成したcontainerを選び、右クリックをして「attach in new window」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136933/937a965c-1158-9e1d-58ed-213686436f72.png)


新しく開いたVS Codeの画面で「フォルダーを開く」をクリックして、フォルダには「/tf」フォルダを選択すると、docker上の/tfフォルダを編集できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136933/2418ca1c-963a-a00e-f6e9-0e159b6e9da4.png)

tfフォルダにはworkフォルダが作成されていて、Ubuntuとファイル共有ができています。Dockerのファイルはイメージを消すとなくなりますので、workフォルダにファイルを保存するようにしましょう。

文字だけの出力であれば、このままPythonのコード( ~.py)であればここで実行して確認をすることができます。
matplotlibで画像を表示することなどはできませんので、そのようなときはJupyter notebookを使用したほうが便利です。

## 8-2. Jupyter notebookを利用する
作業用PCでブラウザを起動して、URLにlocalhost:8888を指定するとjupyter notebookが開きます。tokenが必要というメッセージが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136933/5862c28c-caa2-3f7a-5e01-53644da53beb.png)


dockerの内容を表示したVS Code上のターミナルで次のコマンドを入力してください。
```bash
jupyter notebook list
```
このようにtokenが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136933/0fd1de92-979c-c90a-95d1-0fe2d6d401f2.png)

token=4fa1c9b0c0dacd567c41d32c05fa3b7ceb46bbf1b98ecd1bの部分をURLに追記してブラウザのアドレスバーに入力すると、ブラウザから接続先のDockerのjupyter notebookにアクセスできます。

http://localhost:8888?token=4fa1c9b0c0dacd567c41d32c05fa3b7ceb46bbf1b98ecd1b`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136933/7126b2e2-3b53-f561-6e6f-55e9794d162c.png)


これで、普段作業しているPCから機械学習用PCにリモートアクセスして、Docker上のPythonやJupyter notebookにアクセスできるようになりました。

本ブログの内容をご活用して頂ければ幸いです。