ゲーミングPCで機械学習をして、CPU/GPUの性能の違いをColaboとも比較してみた話【Windowsの機械学習環境構築手順決定版。TF2.0対応】


# ひとことで言うと

格安ゲーミングPC(Windows)を購入して、
Anacondaの仮想環境でCPU/GPUを切り替えられるようにして、
Tensorflow-GPU(v2.0)のコードを動かして、
ColaboratoryのCPU/GPUも含めた４パターンで、
性能比較をしてみたよ。
性能比較結果とWindows版環境構築手順をまとめておくね。という記事。


* 機械学習にはGPUが有効だよ、ってよく聞く
* ゲーミングPCにはGPUがある、そして最近安い

⇒ **ゲーミングPCのGPUを機械学習に使ってみよう！**

ということで、Windows上でのGPU環境構築を実施したが、
ハマりどころが多く大変だった。

**欲しいゲーミングPCを「機械学習用だから！」と言って**
**買う言い訳になる決定版としての記事。**

~~GPUの性能の違いが、学習時間の決定的差になるということを・・・教えてやる！~~

# 機械学習GPU環境の５つの選択肢と、私見

## GPU有り自作PCにubuntu

* 機械学習ガチ勢にオススメ、高性能
* 高額になりがち（全て合わせると30万円くらい？）
* ~~普段使いのWindowsやMacと別に買うとさらに大きな出費~~
* 王道であるため、動かしやすさ的にも良い
* ※ゲーミングPCのOSから入れ替えるパターンもコレ

## Google Colaboratory

* 初心者のお試し～上級者まで幅広く対応
* 無料で、高性能GPUが使える
* セットアップの手間いらず。Driveとの連携も便利
* 一定時間ごとのリセットは気になる
* Windows/Mac/Chromebookでもどこからでも使える！

## NVIDIA Jetson Nano

* 中級者以上。または、初心者のお試しでも面白い
* 低価格（２万円くらい）だが性能もそれなり
* GPUメモリの上限で学習出来る上限が違う点は要注意
* 本体だけは安いが周辺機器も0から揃えると面倒
* IoT機器としての利用目的ならコレ一択
* （どちらかというと学習用よりPredict用な気がする）

## GPU版のクラウドインスタンス or サービス利用

* 従量課金なので初心者向けと思わせて実は違う
* 実行環境として使う場合、クラウド破産に注意
* 簡単な開発環境として使う場合、ロックオンされがち
* サービスにより様々

## ゲーミング(ノート)PC

* ダークホース的存在。通常はWindowsなのをどう見るか？
* 筐体の価格は、セット販売なので同性能での比較なら安い
* 10万円～30万円くらい？
* 機械学習以外の用途にも使うならばコレ一択
* ~~というか、ゲームにも使うなら100%コレしかない~~

# Why ゲーミングノートPCでやるの？？

~~個人的にデスクトップよりもノート派で、別目的のマシンを~~
~~機械学習へも転用するため、ということで答えが出ているが~~
PCを購入するタイミングで試してみたいという遊び心。
特に機械学習のガチ勢でもないので高額高性能なマシンは不要。

最安値クラスのゲーミングノートPC(10万円ほど)を購入して、
機械学習にも使ってみると、
どんな感じの使い勝手になるのか？を勉強がてら試した。

Colaboratoryが素晴らしいとはいえ、用途によっては
12時間で切れないローカル環境も有用であるとも考えられた。

# 本稿の記載目的

先駆者が多そうな気がしたが、
意外と大変だったためまとめておく。

機械学習　⇒　GPU
GPU 　　　⇒　ゲーミングPC
というイメージを持つ人は多いだろう。

最近はゲーミングPCも安くなってきていて、
機械学習用ではなく購入したけど、転用できないかなー、
と思っている人も結構いるのではないか？
または、これからの時代は機械学習が重要なので、PC買うときには、
GPUを付けておいた方が良いかも？とか思っている人も居るかもしれない。
実際、ちょっとGPUが付いたノートPC、も増えてきている。

しかし、本稿のようにこの三つを紐づけた情報はかなり少ないという印象。

機械学習の上級者は当然ubuntu上での話が多く、
一方で、ゲーミングPCを転用しようなどという
私を含めた不届きな初心者が、
Windows上で環境構築するのはハマりやすく大変なためだろう。

また、機械学習系の環境変化は激しく、
Tensorflow2系の情報は、1系よりもまだだいぶ少ない。

そこで、本稿のような
Windows × GPU × Tensorflow2.0 × Anaconda
の環境構築手順をまとめておいても良いと考えた。
また、Colaboratoryとの性能比較含めて、
他の環境でも転用できる情報やコードも多い。

予想以上に大変だったので同様にハマる人への一助になれば。
初心者の情報交換としての記事。

# 始める前から、重大な誤算有り

機械学習環境の構築は、
ubuntu上、Dockerが楽である、という評判と、
「WSL2」によってWindowsでもDockerが動かしやすい、
という話を聞いて、結構簡単に作れるかも？と誤解していた。

実は、**WSL2は2020年3月時点では、GPUデバイスに対応していない**。
**（★2020/06追記：WSL2のGPU対応は正式に追加されました。）**
**（Windows 10 Insider Preview Build 20150 以降のバージョンで利用可能）**
そのため、GPU利用環境はWindowsベースで構築する必要がある。

ただし、WSL2からWindowsで構築したPython環境を
呼び出し実行できるので、疑似的にWindows上で
ubuntuやDocker利用環境のように使うことは可能。
※本稿ではWSL2の話は記載しない

今回結局Windows上で素で構築することになって大変だった。

なお、もしこれからマシンを購入しようとしている場合、
NVIDIA製のGPUにしておくことは
情報量的な意味でほぼ必須なので注意。
NVIDIAの**ノートPC向け**の主要なGPUの性能は
良い方から順番に以下の感じ。
~~まずコレが初心者には超分かりにくい~~

 * GeForce RTX 2080
 * GeForce RTX 2070
 * GeForce GTX 1070
 * GeForce GTX 1660 Ti
 * GeForce RTX 2060
 * GeForce GTX 1080
 * GeForce GTX 1060
 * GeForce GTX 1650
 * GeForce GTX 1050 Ti
 * GeForce MX 350
 * GeForce GTX 1050
 * GeForce MX 250

**デスクトップPC向け**であれば以下の順。

 * Titan RTX 
 * GeForce RTX 2080 Ti
 * Titan V
 * GeForce RTX 2080 SUPER
 * Titan Xp
 * GeForce RTX 2080
 * GeForce GTX 1080 Ti
 * Titan X
 * GeForce RTX 2070 SUPER
 * GeForce RTX 2060 SUPER
 * GeForce GTX 1080
 * GeForce RTX 2070
 * GeForce GTX 1070 Ti
 * GeForce RTX 2060
 * GeForce GTX 980 Ti
 * GeForce GTX 1070

参考： https://pcfreebook.com/article/459993300.html

# 今回構築した環境のバージョン情報

* Windows10 Home
* GPU = GTX1650
* Anaconda Python = 3.7
* tensorflow-gpu = 2.0.0
* CUDA = 10.0
* cuDNN = v7.6.5(cudnn-10.0-windows10-x64-v7.6.5.32)

# 環境構築手順の全体像

* 各ライブラリのバージョン決定方法
* Anacondaの導入 / 仮想環境やJupyterの設定方法
* Python仮想環境の構築(CPU版)
* Tensorflow2.0のサンプルコードをCPU版で流す
* Python仮想環境の構築(GPU版)
* CUDA, cuDNN, ドライバのインストール
* Tensorflow2.0のサンプルコードをGPU版で流す
* （Errorが発生して大変だった話）
* ColaboratoryのCPU/GPU版も含めて、性能比較

# バージョンの決定方法（最重要）

tensorflow-gpu, CUDA, cuDNN,の
３つのバージョンを完璧に合わせる必要がある。
これを怠ると、例えば以下のような意味不明エラーの嵐に悩まされる。

```py:意味不明エラー例
UnknownError:  Failed to get convolution algorithm. This is probably because cuDNN failed to initialize, so try looking to see if a warning log message was printed above.
```

Tensorflowの公式サイトを確認する。
https://www.tensorflow.org/install/source_windows

2020/03当時、
Tensorflow2.1.0が最新であるものの、
テスト済みのビルド構成(Windows版)では、
2.0.0が最新であったため、2.0.0を採用。
Tensorflowのコードは、1系と2系で異なる点が多いため、
実行したいコードに合わせて、1系と2系は選ぶべし。

が、以下の記載を信じて痛い目にあった。

```sh:公式サイトの記載内容抜粋
バージョン	Python バージョン	コンパイラ	ビルドツール	cuDNN	CUDA
tensorflow_gpu-2.0.0	3.5～3.7	MSVC 2017	Bazel 0.26.1	7.4	10
```
正解は、CUDA10.0系に対するcuDNNのバージョンは7.6系。

7.4系だと上述の意味不明エラーが生じる。
そして、このエラーには複数の原因候補があり、
バージョン違い以外のケースでも多々発生するため、
原因の切り分けの難易度が高い。

参考： https://github.com/tensorflow/tensorflow/issues/24496
※さらに、Tensorflow公式サイトのコードを
　そのまま動かすだけでも上記issuesにある
　追記が必要という鬼畜っぷり。

# Anaconda Python3.7版のインストール

https://www.anaconda.com/distribution/#download-section

基本的にはデフォルト設定のままでインストールを進める。

# CondaのPython仮想環境の使い方

CPU版/GPU版を気軽に使い分けたい、
別バージョンを試したい、などの用途だけでなく、
環境構築時のリトライのしやすさなども含めて、
Pythonの仮想環境を用いて構築を進めると良い。

Anaconda Comand Prompt 上で下記のように操作する。

```sh:AnacondaComandPromptでの操作
# 以下のコマンドで仮想環境MyEnvNameを作る。
conda create -n MyEnvName python=3.7
# 以下のコマンドで仮想環境をアクティブにする。
conda activate MyEnvName
# ⇒コマンドラインの左側の「(base)」が、
#   「(MyEnvName)」に変わることが確認できる。

# 以下のコマンドで現在の仮想環境一覧が確認できる。
conda env list
# 以下のコマンドで対象の仮想環境を削除できる。
conda remove -n MyEnvName --all
# なお、Anaconda Navigator の左側のメニューからも見える。
```

上記以外のコマンドについては、下記が詳しい。
https://qiita.com/naz_/items/84634fbd134fbcd25296

# Jupyterから、対象の仮想環境に接続する方法

Anaconda Comand Prompt 上で疎通をとってから最後に、
Jupyterからその仮想環境を使えるように設定するほうが望ましい。
よって本手順は最後に実施した方が良い。
（が、普段仮想環境の作成とセットで実行する場合も
　多々あるため、仮想環境作成の直下に書いておく）

下記のコマンドで、Jupyter側から仮想環境が見えるようにしよう。

```sh:AnacondaComandPromptでの操作
# ipython(Jupyter)はbaseに入っているため、
# 下記のコマンドで一度baseに戻る。
conda activate base
# --nameで仮想環境名，--display-nameで表示名を設定する。
ipython kernel install --user --name=MyEnvName --display-name=MyEnvName
# ⇒ 下記のフォルダにJupyterの接続設定が作成される。(隠しフォルダを表示すること)
# C:\Users\[ユーザ名]\AppData\Roaming\jupyter\kernels
```

このままも一見Jupyter側から仮想環境に繋げられるのだが、
実は下記の設定ファイルに誤りがあるために、修正が必要。
python.exeの起動パスを、Anacondaの仮想環境側に変更する。

◇対象ファイル：
　`C:\Users\[ユーザ名]\AppData\Roaming\jupyter\kernels\MyEnvName\kernel.json`
◇修正前：
　`"C:\\Users\\[ユーザ名]\\anaconda3\\python.exe",`
◇修正後：
　`"C:\\Users\\[ユーザ名]\\Anaconda3\\envs\\MyEnvName\\python.exe", `

さらに、`kernel.json` に記載されている呼び出し引数の、
`ipykernel_launcher` を仮想環境側に追加する必要がある。
Anaconda Comand Promptで下記のように実行する。

```sh:AnacondaComandPromptでの操作
conda activate MyEnvName
pip install ipykernel
```

参考：https://qiita.com/howahowa/items/480607a06264426f24ed


この状態で Anaconda Navigator ⇒ JupyterNotebookを起動すると、
「New」や新しいノートブックを作成する際に、
「Python3」だけでなく、「MyEnvName」が選択肢として増えている。

または、既にPython3で作成済みのipynbについても、
Kernel ⇒ Change kernel を選ぶと、
MyEnvName の方にkernelを変更することが出来る。

もし、Jupyterからのkernel選択実行と、
Anaconda Comand Promptの仮想環境選択実行との
結果が異なってしまう場合は、
下記のスクリプトを実行して、使っているPythonや
ライブラリなどのパスを確認すると良いだろう。

```py:パス確認用のPythonスクリプト
import sys
print(sys.prefix)
print(sys.path)
```

また、Jupyterではブラウザを閉じただけでは
裏でプロセスはまだ残っているため、
「running」のタブから実行中プロセスを確認し、
一度全てをShutdownし、
Jupyter自身も終了させてから再起動すると、
きちんと設定が反映され、正しい動作となる場合も多い。

普段においても、GPUは「占有型」でデバイスを使うようなので、
他のGPU実行プロセスを残さないように、
適宜Shutdownしておいた方が良いだろう。


# Tensorflowのインストール（CPU版）

まずは、CPUのみを利用した
Tensorflow実行用仮想環境を構築し、疎通をとろう。

```sh:AnacondaComandPromptでの操作
conda create -n cpuenv python=3.7
conda activate cpuenv
pip install tensorflow==2.0.0
```

備考：pipとcondaは混ぜて使わなければ、
　Anacondaでもパッケージの管理は全てpipでも良い。
　（どちらでも大差はない、と思う）
　なお、Tensorflowの公式サイトではpip準拠である。
　Tensorflowの導入後、conda list, pip list で違いを見ると、
　conda のほうがtensorflow系の依存ライブラリを
　頑張ってインストールしている感があるが、
　特に手数が変わるわけではない。
　本稿では基本はpip側で実施するが、どこかをcondaで実施する場合、
　すべてをcondaで統一したほうが良い。


# Tensorflow2.0の上級者向けチュートリアルコードの実行(CPU版)

１ファイルだけで完結して実行できる一発実行コードを準備する。
Tensofrflow公式の 「エキスパートのための TensorFlow 2.0 入門」
のコードをつなぎ合わせて、時間計測用のコードを挿入し、
以下のように１ファイルにまとめて実行する。

参考：https://www.tensorflow.org/tutorials/quickstart/advanced

Jupyter Notebookの設定が済んでいる場合は、
下記のコードを直接１セルに貼り付けて実行、でもOK

```sh:AnacondaComandPromptでの操作
python tensorflow-tutorial-ex.py
# > ～～途中結果省略～～
# > 今回のCPU版での実行時間は下記の通り。
# > 実行時間:143.45523118972778[秒]
```

```py:tensorflow-tutorial-ex.py
#https://www.tensorflow.org/tutorials/quickstart/advanced
#Tensofrflow公式「エキスパートのための TensorFlow 2.0 入門」

from __future__ import absolute_import, division, print_function, unicode_literals
import tensorflow as tf
from tensorflow.keras.layers import Dense, Flatten, Conv2D
from tensorflow.keras import Model

## UnknownError:  Failed to get convolution algorithm. This is probably because cuDNN failed to initialize への対策
# gpu_devices = tf.config.experimental.list_physical_devices('GPU')
# for device in gpu_devices: tf.config.experimental.set_memory_growth(device, True)

#MNISTデータセットをロードして準備します。
mnist = tf.keras.datasets.mnist

#ダウンロード終了後からの実行時間計測のために追加します。
import time
start_time = time.time()

(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0

# Add a channels dimension
x_train = x_train[..., tf.newaxis]
x_test = x_test[..., tf.newaxis]

#データセットをシャッフルし、バッチ化するためにtf.dataを使います。
train_ds = tf.data.Dataset.from_tensor_slices(
    (x_train, y_train)).shuffle(10000).batch(32)
test_ds = tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(32)

#Kerasのmodel subclassing APIを使ってモデルを作りtf.kerasます。
class MyModel(Model):
  def __init__(self):
    super(MyModel, self).__init__()
    self.conv1 = Conv2D(32, 3, activation='relu')
    self.flatten = Flatten()
    self.d1 = Dense(128, activation='relu')
    self.d2 = Dense(10, activation='softmax')

  def call(self, x):
    x = self.conv1(x)
    x = self.flatten(x)
    x = self.d1(x)
    return self.d2(x)

# モデルのインスタンスを作成
model = MyModel()

#訓練のためにオプティマイザと損失関数を選びます。
loss_object = tf.keras.losses.SparseCategoricalCrossentropy()

optimizer = tf.keras.optimizers.Adam()

#モデルの損失と正解率を計測するためのメトリクスを選択します。これらのメトリクスはエポックごとに値を集計し、最終結果を出力します。

train_loss = tf.keras.metrics.Mean(name='train_loss')
train_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(name='train_accuracy')

test_loss = tf.keras.metrics.Mean(name='test_loss')
test_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(name='test_accuracy')

# tf.GradientTapeを使ってモデルを訓練する関数を定義します。
@tf.function
def train_step(images, labels):
  with tf.GradientTape() as tape:
    predictions = model(images)
    loss = loss_object(labels, predictions)
  gradients = tape.gradient(loss, model.trainable_variables)
  optimizer.apply_gradients(zip(gradients, model.trainable_variables))

  train_loss(loss)
  train_accuracy(labels, predictions)

# モデルをテストする関数を定義します。
@tf.function
def test_step(images, labels):
  predictions = model(images)
  t_loss = loss_object(labels, predictions)

  test_loss(t_loss)
  test_accuracy(labels, predictions)

EPOCHS = 5

for epoch in range(EPOCHS):
  for images, labels in train_ds:
    train_step(images, labels)

  for test_images, test_labels in test_ds:
    test_step(test_images, test_labels)

  template = 'Epoch {}, Loss: {}, Accuracy: {}, Test Loss: {}, Test Accuracy: {}'
  print (template.format(epoch+1,
                         train_loss.result(),
                         train_accuracy.result()*100,
                         test_loss.result(),
                         test_accuracy.result()*100))
  
  # 次のエポック用にメトリクスをリセット
  train_loss.reset_states()
  train_accuracy.reset_states()
  test_loss.reset_states()
  test_accuracy.reset_states()

# 計測した結果を出力
tat_time = time.time() - start_time
print ("実行時間:{0}".format(tat_time) + "[秒]")
```

# Tensorflowのインストール（GPU版）

インストールするライブラリ名に「-gpu」を付ける以外は、
全てCPU版と同様の手順である。

```sh:AnacondaComandPromptでの操作
conda create -n gpuenv python=3.7
conda activate gpuenv
pip install tensorflow-gpu==2.0.0
```

しかし、この状態のままで先述のコードを実行すると、
例えば以下のようなエラーがでる。

```sh:エラーログ
W tensorflow/stream_executor/platform/default/dso_loader.cc:55] Could not load dynamic library 'cudart64_100.dll'; dlerror: cudart64_100.dll not found
～～～中略～～～
tensorflow.python.framework.errors_impl.InternalError: cudaGetDevice() failed. Status: cudaGetErrorString symbol not found.
```

このようなdllが見つからない系のエラーが出る場合は、
以下のように、Anaconda Prompt からコマンドで、dllが存在するか、また、
そのpathが通っているかを確認することができる。
今回はまだ何も入れていないので、まずはCUDAやcuDNNをインストールする。

```sh:AnacondaComandPromptでの操作
where cudart64_100.dll
# > 情報: 与えられたパターンのファイルが見つかりませんでした。
```

引き続き、CUDA, cuDNN, Nvidia Driver を
インストールしてからまたコード実行に戻ってこよう。

新旧混在/玉石混合のインストール手順情報が多いなかで、
最も参考になるサイトは下記のサイト。
https://www.kkaneko.jp/tools/win/tensorflow2.html
環境構築系でエラーが生じた場合は、一度確認すると良い。

# CUDAのダウンロードとインストール

CUDAはNVIDIAが開発している、
GPUによる並列計算処理のための開発環境。

最新版はえてしてバージョンがあっていないので、
以下のアーカイブサイトから
自身のバージョンに合ったものを探す（今回は10.0）
https://developer.nvidia.com/cuda-toolkit-archive

自分のマシンのGPUの型番に応じたファイルであるため、
あらかじめGPUのデバイス名を確認しておこう。
今回の構築で使ったのは以下のファイル。
`cuda_10.0.130_411.31_win10.exe`

インストール時には、そのインストールオプションで、
「高速（推奨）」ではなく、「カスタム（詳細）」を選び、
「ドライバーコンポーネントの選択」から、
CUDA - Visual Studio Integration
のチェックを外しておくと少し幸せ。
（関連するVisualStudioが無いよ、みたいな
　警告を受けなくて済む）

VisualStudioが必要だというインストール手順も多いが、
重くてインストールが手間になるので、
モジュールのリコンパイルをしない場合は、
VisualStudio関連は必須ではないハズ。
最悪でもbuildtoolだけインストールすればいい。

# cuDNNのダウンロードとインストール

cuDNN はCUDA Deep Neural Network library の略。
ニューラルネットワーク計算を高速に行うためのライブラリー。

https://developer.nvidia.com/cudnn
ダウンロードのためには無料で簡単なユーザ登録が必要。
ユーザ登録後、再度上記のページを開き、
cuDNN Download に進む。

今回の環境で使ったのは下記のファイル
`cudnn-10.0-windows10-x64-v7.6.5.32.zip`

【重要】CUDAのバージョン(10.0)と、cudnnのバージョン(7.6)の
　掛け算のパターンでファイルが違うため、良く確認すること。
　Tensorflowのバージョンごとに、稼働確認がとれている組み合わせが違うため、
　使用するTensorflowのバージョン(2.0.0)に対応したものを選ぶ必要がある。
　https://www.tensorflow.org/install/source_windows
　上記公式サイトで確認出来るハズだが、
　今回は7.4⇒7.6に変更する必要があった模様。


ダウンロードしたファイルを解凍すると、
cudaフォルダの下に、以下が格納されている。

* `bin/cudnn64_7.dll`
* `include/cudnn.h`
* `lib/x64/cudnn.lib`
* `NVIDIA_SLA_cuDNN_Support.txt`

そのままのファイル構成のまま、以下のフォルダにコピーする。
`C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.0`

なお、後でどのバージョンを使っているのか分からなくなった場合は、
include/cudnn.h をテキストエディタで開いて57行目あたりに書いてある。

インストール終了後、一度Anaconda Prompotを再起動し、
下記のコマンドを実行し、cudaのバージョンを再確認する。

```sh:AnacondaComandPromptでの操作
(gpuenv) C:\Users\[ユーザ名]>nvcc -V
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2018 NVIDIA Corporation
Built on Sat_Aug_25_21:08:04_Central_Daylight_Time_2018
Cuda compilation tools, release 10.0, V10.0.130
```

先ほどは取得できなかったdllも認識されているハズ。

```sh:AnacondaComandPromptでの操作
(gpuenv) C:\Users\[ユーザ名]>where cudart64_100.dll
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.0\bin\cudart64_100.dll
```

もし、認識されていない場合、
システム環境変数の「path」を参照し、
下記のパスが設定されているかどうかを確認しよう。
`C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.0\bin`

# Nvidia Driver のアップデート（必要に応じて）

Nvidia Driverは、原則新しいバージョンが入っていれば問題ない。
CUDAに対応して、どのバージョンのドライバが必要かは、
下記の公式ページに一覧表がある。
https://docs.nvidia.com/cuda/cuda-toolkit-release-notes/index.html

大体は、PC購入時点の最新版のドライバが入っていると思うので、
ここは省略しても良い可能性が高いと思われる。

バージョンの確認方法は、
WindowsアプリケーションのNVIDIAコントロールパネルを開き、
ヘルプ⇒システム情報⇒コンポーネント
⇒NVCUDA.DLLを確認する。

製品名：NVIDIA CUDA 10.0.132 driver
ファイルのバージョン：25.21.14.1971
この右側の、41971の部分がDriverのバージョン。

または、nvidia-smiコマンドでも同様に確認できる。

```sh:AnacondaComandPromptでの操作
(gpuenv) C:\Users\[ユーザ名]>nvidia-smi
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 419.71       Driver Version: 419.71       CUDA Version: 10.0     |
|-------------------------------+----------------------+----------------------+
| GPU  Name            TCC/WDDM | Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GeForce GTX 1650   WDDM  | 00000000:01:00.0 Off |                  N/A |
| N/A   41C    P8     1W /  N/A |    134MiB /  4096MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+
```

GPUを使用中に上記のnvidia-smiコマンドをたたくことで、
その実行中プロセスやメモリ利用量等を確認できるため、
以降の手順中でも適宜たたいてみるのがオススメ。

# GPU版Tensorflowの疎通確認

Anaconda Prompt にて、GPU版仮想環境をactivateして、
以下のように順番にコマンドを実行する。

```sh:AnacondaComandPromptでの操作
conda activate gpuenv
python -c "import tensorflow as tf; print(tf.__version__)"
python -c "from tensorflow.python.client import device_lib; print(device_lib.list_local_devices())"
```

結果、tensorflowのバージョン2.0.0と、
以下のようなGPUデバイスの表示が出れば成功。
CPU版では、device_type: "CPU" のところまでしか出ない。
GPUが認識されていれば、その型番が表示されるハズ。

```sh:結果
～途中省略～
Created TensorFlow device (/device:GPU:0 with 2913 MB memory) -> physical GPU (device: 0, name: GeForce GTX 1650, pci bus id: 0000:01:00.0, compute capability: 7.5)
[name: "/device:CPU:0"
device_type: "CPU"
memory_limit: 268435456
locality {
}
incarnation: 4688799603900704883
, name: "/device:GPU:0"
device_type: "GPU"
memory_limit: 3055235892
locality {
  bus_id: 1
  links {
  }
}
incarnation: 15201502304566343823
physical_device_desc: "device: 0, name: GeForce GTX 1650, pci bus id: 0000:01:00.0, compute capability: 7.5"
]
```

また、もし失敗している場合でも、上記の実行ログ中で、
どのdllの呼び出しに失敗しているのか確認出来るハズ。

~~ここまでで、環境構築できたよお疲れ様、~~
~~と言っている手順情報が多々見受けられるのは、~~
~~良くない風潮だと思う。~~
~~下記のUnknownErroの件や、性能比較しないと~~
~~本当にGPUが適用されているか分かりにくいから。~~
~~あと、順調に環境構築進める手順だけでなく、~~
~~発生したエラーと解決方法や確認方法も書いて欲しい。~~

# チュートリアルコードの実行(GPU版)（※UnknownError対応）

早速、この環境でさきほどのCPU版の疎通用コードを実行する。
`python tensorflow-tutorial-ex.py`

GPU版の疎通が実施できていても、以下のようなエラーになる。

```sh:結果
～～途中省略～～
 W tensorflow/core/common_runtime/base_collective_executor.cc:216] BaseCollectiveExecutor::StartAbort Unknown: Failed to get convolution algorithm. This is probably because cuDNN failed to initialize, so try looking to see if a warning log message was printed above.
         [[{{node my_model/conv2d/Conv2D}}]]
Traceback (most recent call last):
  File "tensorflow-tutorial-ex.py", line 85, in <module>
    train_step(images, labels)
～～途中省略～～
 line 67, in quick_execute
    six.raise_from(core._status_to_exception(e.code, message), None)
  File "<string>", line 3, in raise_from
tensorflow.python.framework.errors_impl.UnknownError:  Failed to get convolution algorithm. This is probably because cuDNN failed to initialize, so try looking to see if a warning log message was printed above.
～～途中省略～～
Function call stack:
train_step
```

この、UnknownError がめちゃくちゃ厄介で、
複数の要因で似たエラーが発生しうる。
詳細は以下参照：
https://github.com/tensorflow/tensorflow/issues/24496

ざっくり言うと、以下のようなパターンがある模様。
* Tensofrow,CUDA,cuDNNのバージョン不整合
* インストール手順不正、DLL不足など
* 別スレッドでGPU使用プロセスが残っていた場合など、
　２回目の実行などでGPUメモリ不足（一回目は成功する）
* GPUメモリの割り当て方の指定方法の問題（※コード修正のみで対応可能）

> 余談：
> このエラー修正が最も時間がかかった。
> インストール手順も確認して何度か修正してやり直したが、
> 結果的にはバージョン不整合＆コードの問題の二重トラブル。
> どちらもTensorflowの公式サイトからの情報をもとに
> 実施した部分であったため、判明が遅れてしまった。
> Windows版の情報はあまりアテにしてはいけない。
> さらに、修正後もJupyterから実行すると再発しやすい。
> 「別スレッドでGPU使用プロセスが残っていた場合」が該当。
> 単純なRunning⇒shutdownのプロセスキルだけでは
> 何かが残っているのか、最悪PC再起動しないといけない。
> このこともあり、疎通完了まではJupyter以外で実施がオススメ。

コード修正のみで対応するためには、
import文の下に、以下のようなコードを追加する。
（tensorflow2系の場合。1系の場合は参照先URLをご確認）
※前出のコードには既にコメントアウト状態で貼ってあるので、
　コメントアウトを解除すればOK

```py:import文の下に追加するコード
# UnknownError:  Failed to get convolution algorithm. This is probably because cuDNN failed to initialize への対策
gpu_devices = tf.config.experimental.list_physical_devices('GPU')
for device in gpu_devices: tf.config.experimental.set_memory_growth(device, True)
```

Keras関連のサンプルコードを実行する場合も、
バックエンドでTensorflowを使用するため、
最初のほうで同様のコードを実行しておくと、
UnknownErrorが防止できるハズ。

UnknownError対策用のコードは、keras利用の場合は、
以下のようなコードになるかもしれない（未確認）

```py:keras版追加コード
# Allowing GPU memory growth
#config = tf.ConfigProto() #V1のコード
config = tf.compat.v1.ConfigProto()
config.gpu_options.allow_growth = True
#tf.keras.backend.set_session(tf.Session(config=config)) #V1のコード
tf.compat.v1.keras.backend.set_session(tf.compat.v1.Session(config=config));
```

実施した結果、今回は下記の時間で終了した。
実行時間:29.763328075408936[秒]

CPU版が、約143秒に対して、
GTX1650でのGPU版が、約30秒なので、
かなり早くなったように思える。

ただし、単純にこの割合で早くなるのではなく、
実行するコードの内容にもかなり依存する点は注意。

例えば、公式サイトの初心者向けクイックスタートのコードを使うと、
https://www.tensorflow.org/tutorials/quickstart/beginner

CPU版：約14秒
GPU版：約18秒
と逆転してしまう。（初期化等の時間を雑に扱っているせいもある）

実際に実行したコードは下記の通り。

```py:初心者向けクィックスタートコード＋実行時間ログ追加版
#https://www.tensorflow.org/tutorials/quickstart/beginner
from __future__ import absolute_import, division, print_function, unicode_literals
import tensorflow as tf

mnist = tf.keras.datasets.mnist

#ダウンロード終了後からの実行時間計測のために追加します。
import time
start_time = time.time()

(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0

model = tf.keras.models.Sequential([
  tf.keras.layers.Flatten(input_shape=(28, 28)),
  tf.keras.layers.Dense(128, activation='relu'),
  tf.keras.layers.Dropout(0.2),
  tf.keras.layers.Dense(10, activation='softmax')
])

model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

model.fit(x_train, y_train, epochs=5)

model.evaluate(x_test,  y_test, verbose=2)

# 計測した結果を出力
tat_time = time.time() - start_time
print ("実行時間:{0}".format(tat_time) + "[秒]")
```

こちらのコードでは、GPU版もCPU版と
全く同じコードで動作するので簡単な疎通にもオススメ。

ついでにもう一つ、比較用/疎通用に使えるコードを乗せておく。
fashion_mnistの分類問題の短めのコード。
これまでのサンプルと同様に、JupyterやColabのセルにコピペや、
.pyファイルにしてそのまま実行が可能で使いやすいと思う。

```py:fashion_mnist
#参考:https://github.com/tensorflow/tensorflow/issues/34888
import tensorflow as tf
#UnknownError回避用
gpu_devices = tf.config.experimental.list_physical_devices('GPU')
for device in gpu_devices: tf.config.experimental.set_memory_growth(device, True)

import numpy as np
print("Num GPUs Available: ", len(tf.config.experimental.list_physical_devices('GPU')))
from tensorflow import keras
fashion_mnist = keras.datasets.fashion_mnist
#ダウンロード終了後からの実行時間計測のために追加します。
import time
start_time = time.time()

(train_images, train_labels), (test_images, test_labels) = fashion_mnist.load_data()
train_images = train_images / 255.0
test_images = test_images / 255.0
train_images = np.expand_dims(train_images, axis=3)
test_images = np.expand_dims(test_images, axis=3)

model = tf.keras.Sequential()
model.add(tf.keras.layers.Conv2D(filters=64, kernel_size=2, padding='same', activation='relu', input_shape=(28,28,1)))
model.add(tf.keras.layers.MaxPooling2D(pool_size=2))
model.add(tf.keras.layers.Dropout(0.3))
model.add(tf.keras.layers.Flatten())
model.add(tf.keras.layers.Dense(256, activation='relu'))
model.add(tf.keras.layers.Dropout(0.5))
model.add(tf.keras.layers.Dense(10, activation='softmax'))
model.summary()
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])

model.fit(train_images, train_labels, batch_size=64, epochs=10, validation_data=(test_images, test_labels))

# 計測した結果を出力
tat_time = time.time() - start_time
print ("実行時間:{0}".format(tat_time) + "[秒]")
```


# Colaboratoryで実行した場合との比較結果

さてこのように、WindowsでGPUを使おうとすると
かなり面倒＆ハマりやすい手順が必要になってしまう。

ubuntuならばNVIDIA Dockerが最短として、
残念ながらWindowsのWSL1,2では
本記事執筆時点ではまだGPUイメージは使えないと聞くため、
「一般的なパッケージング済みのゲーミングPC(Windows)」
をGPU有り機械学習にも使おうとすると、
まだこの手順はしばらく現役だと思われる。

※WSLでDocker利用可能 ≠ NVIDIA Docker利用可能、
　は大きな誤算であった。

ColaboratoryにはこのGPU設定済み環境が、
無料で使える状態で用意してある。

**改めてColaboratoryの偉大さを感じる。**

ついでに、あくまで参考まで、
簡単な実行時間の比較計測を行ってみた。

## **性能比較結果一覧表**

| 環境 - CPU/GPU情報 | 初心者Tutorial | 上級者Tutorial | fashion_mnist |
| :--- | ---: | ---: | ---: |
| CPU(Local) i7-9750H 2.60GHz| 約１４秒 | 約１４３秒 | 約２８９秒 |
| GPU(Local) GTX1650| 約１９秒 | 約　２９秒 | 約　７７秒 |
| CPU(Colab) Xeon(R) 2.20GHz| 約２３秒 | 約２９１秒 | 約７６０秒 |
| GPU(Colab) Tesla P100| 約２４秒 | 約　１８秒 | 約　４６秒 |

評価に使ったどのコードも本稿に乗せており、
データセットのダウンロードも付いているので、
既に環境をお持ちの方は、お手元でも走らせてみると
さらに楽しめるかもしれない。

なお、Colaboratoryではtensorflowのバージョンは、
デフォルトでは「1.15.0」であり、
最初に下記の専用コマンドで「2.1.0」に
変更してから実行している。
ローカル側と多少ずれるが同じ２系だしそのまま動いた。
ただし、2020年3月27日～、デフォルトが2系になるとのことなので、
以降はこのコマンドは不要。（※逆に１系にしたい場合に使うことになる）

```py:Colaboratoryでのtensorflowバージョン変更用
%tensorflow_version 2.x
import tensorflow as tf
print(tf.__version__)
```

Colaboratoryでは、ランタイムに接続するたびに、
どのスペックのマシンが当たるかはムラがあるため、
今回引けたマシンの情報を書いておく。
CPU(Colab) ⇒ Intel(R) Xeon(R) CPU @ 2.20GHz ×2
GPU(Colab) ⇒ Tesla P100-PCIE-16GB  (最も良いヤツ？)
よって、実行時間の秒数は、あくまで参考値である。

詳細なマシンスペック情報は、以下のコマンドで確認が可能。

```py:Colaboratoryのスペック確認（下二つはGPU用）
!cat /proc/cpuinfo
!cat /proc/driver/nvidia/gpus/0000:00:04.0/information
!nvidia-smi
```

なお、私のゲーミングノートPCの情報は下記の通り。
CPU ＝ Intel(R)Core(TM)i7-9750H CPU 2.60GHz
GPU ＝ GeForce GTX 1650
ゲーミングノートPCとして、2020年時点で最安値エントリークラス。
10万円ほどで購入。GTX1050やMX250よりは良いので最低性能ではない。

> 余談：
> 最近では、特にゲーム用の虹色に光るようなヤツでなくても、
> MX250などの小さなGPUを積んでいたり、GPU搭載でありながら、
> かなりの薄型、普通のノートPCのようなビジネスも可のモデルが
> 増えてきており、今後普通のノートPCにおいても、
> ちょっとGPU積んでます、みたいなモデルが増えてくるかもしれない。
> それにしても、ちょっと前までは考えられないくらい、
> ゲーミングノートPCも安く＆薄くなったような気がする。

性能比較結果としてはやはり、(12時間/90分の問題を考慮しなければ)
Colaboratory(GPU)の利便性＆性能が際立っていた。
また、長時間利用したい場合でも、近い将来、
有償版の [Colab Pro](https://colab.research.google.com/signup) が日本でも使えるようになればさらに便利になりそう。

> 余談：
> Colab Pro は$9.99/月ほど払うと、
> より速いGPU/より長期間の使用/より多くのメモリ、で使えるらしい。
> 2020年3月時点では、アメリカ在住者向けにしか解放しておらず、
> 日本のクレジットカードでは登録出来ない模様。

# 感想/結論

ColaboratoryのGPUは良いものを使っている、
というのがどの程度のものなのか、
また、CPUとGPUでの実行時間の違いがどう出るのか、
実体験として理解することが出来た。

**やっぱりColaboratoryがGPU利用としては最強感。**

Windows環境（ゲーミングノートPC）でもGPUを使う方法と、
そのトラブルシュートをまとめることが出来た。
Anacondaの仮想環境の切り替え方法や、
Tensorflow2.0の一発時間計測実行コードも役立つかもしれない。

Macユーザも**七色に光る怪しいマシン**を買ってみてはいかがか？

**機械学習のためにゲーミングノートPCを買って、**
**お勉強したあとはいっぱいゲームをしよう！！**

以上。