雑記: Windows10上AnacondaへのGPU版TensorFlow導入

GPU環境がほしいとかあると思います。ハードのことはよくわからないですが、さすがにGPU環境を手に入れるためにはそれに対応したGPUというものが必要そうな気がします。ちなみにGPUが何なのかはよくわかりません。画面を描画するための並列処理をする演算装置らしいですが、画面を描画するための情報ってそんなに後処理が必要な状態になっているんでしょうか。何もわかりません。

今回やったことは以下です。先に書いておくと大事なのは正しいバージョンの CUDA と cuDNN をインストールすることです。そこガバガバだと駄目です。というかちゃんとそう書いている先達がいらっしゃいました。
WindowsにTensorflowを入れてみる(成功編)

それっぽいGPUが入ったマシンを手に入れます。

手元のマシンにGPUを組み込むことができればGPUを調達すればいいと思うんですが、手元のノート2台はどちらも結構古いので今回はGPUが入っているノートを新調しました。ノートなのは自分には据え置き機を据え置いて作業できる場所がないので致し方ないです。OS は Windows 10 で、NVIDIA GeForce GTX 1060 というGPUが入っているらしく、それっぽさが感じられます。なおこれがどういうGPUなのかはよくわかりません。あとこういうGPUが入ったマシンがほしいときは自作するかBTOショップで購入するらしいです。インターネットに書いてありました。今回はBTOショップで購入しました。

  • それでよくわからないんですが、こういう単体GPUが入っているマシンにおいて単体GPUはいつどのように使われているんでしょうか。基本的に画面描画には単体GPUから使うんでしょうか。機械学習のために単体GPUを使いたい人は「画面描画を頑張っていただかなくてもよいです」とかならないんでしょうか。→ 設定から自分で細かく設定できるんですね。
    Windows10 - グラフィックの設定(アプリごとに使用するGPUを選択) - PC設定のカルマ
    ただグラフィックの設定という項目が見当たらないんですが、単体GPUを画面描画に使用するためのドライバをインストールしていないからかもしれません。あと普通にタスク マネージャーのパフォーマンス タブにGPUが表示されるんですね。みるとGPU 0=内臓GPUIntel HD Graphics 630)は使用されていましたがGPU 1=単体GPUNVIDIA GeForce GTX 1060)は使用率0%でした。
GPU版 TensorFlow を入れる前に用意しておくものを確認します。

どのように環境構築するか何も考えていなかったんですが、よくわからないので Anaconda を入れてそこに TensorFlow を入れることにします。TensorFlow 公式を参照します。
https://www.tensorflow.org/install/install_windows
これを参照すると、GPU対応の TensorFlow を入れるときは以下を入れておくこととありますが、それぞれ何なのかよくわからないので確認しておきます。

CUDA Toolkit 9.0 及び NVIDIA Driver

  • CUDAとはどうもGPU向けのコンパイラであるようです。ライブラリも含まれているようです。通常のCPU向けのコンパイラでは駄目なのかはよくわからないですが、GPUとCPUは構造が違うので、GPUが処理しやすいような命令にした方がよさそうな気はします。NVIDIA Driver というのは、なんかプリンタで印刷するときにもプリンタを動かすドライバがいるのでGPUを動かすドライバが要るんだと思います。
    CUDA - Wikipedia

cuDNN v7.0

CUDA Compute Capability 3.5 以上の GPU (TensorFlow をソースからビルドする場合は 3.0 以上)

  • 最後に GPU です。GPU が要るのは当たり前です。ただし、CUDA Compute Capability なるものが 3.5 以上ある GPU でなければならないとのことです。下のページで確認すると、GeForce GTX 1060 の CUDA Compute Capability は 6.1 なので要求を満たしています。CUDA Compute Capability が何なのかはさっぱりわかりませんが、GPU にもなんか女子力みたいなものがあるんだろうと思っておきます。
    CUDA GPUs | NVIDIA Developer
CUDA Toolkit 9.0 及び NVIDIA Driver をインストールします。

TensorFlow 公式からリンクがある以下のドキュメントの自分に必要な部分を日本語にしたにすぎません。
Installation Guide Windows :: CUDA Toolkit Documentation

  • そもそもGPUの存在を確認します。Windowsマークを右クリックしてデバイス マネージャーを起動、ディスプレイ アダプターを展開して、NVIDIA GeForce GTX 1060 が含まれていることを確認します。
  • Microsoft Visual Studio が必要なのでドキュメントに記載のあるバージョンをインストールします。以下のリンクから Visual Studio Community 2017 をインストールしました。インストール時にワークロードのうち Desktop development with C++ を選択しました。必要な Visual C++コンパイラが含まれていればいいと思うんですがどこまでインストールするのが最小限なのかわかりません。
    ダウンロード | IDE、Code、Team Foundation Server | Visual Studio
  • ドキュメント内のリンクから、CUDA Toolkit 9.0 のインストーラをダウンロードします。リンク先には最新版(この記事を書いている時点では 9.2)のダウンロードボタンがありますが、要求のバージョンはこれではないので、ちゃんと Legacy Releases から 9.0 を探してダウンロードします
    CUDA Toolkit 9.2 Download | NVIDIA Developer
    適切なインストーラをダウンロードしたら画面にしたがってインストールします。再起動の指示があれば再起動します。
    • ここで不用意な人(自分)は最新版 CUDA 9.2 をインストールしてしまったかもしれません。その場合は改めて CUDA 9.0 をインストールすればいいのですが、何も考えずに CUDA 9.0 をインストールすると、The installed version of Nsight Visual Studio Edition is newer than the one to be installed などというメッセージが出て失敗します。これはメッセージの続きの指示通りこれをアンインストールすればよく、コントロールパネルのプログラムのアンインストールから NVIDIA Nsight Visual Studio をアンインストールした上で CUDA 9.0 をインストールし直せば成功します(手元の環境では)。
  • ドキュメントには適切な Driver Model を使うこととあるんですがよくわかりません。Driver 自体は上の手順で一緒にインストールされたと思います。
  • コマンドプロンプトを起動し、
    C:\Users\Cookie>nvcc -V
    と入力してインストールされた CUDA Toolkit のバージョンが 9.0 であることを確認します。
cuDNN v7.0 をインストールします。

TensorFlow 公式からリンクがある以下からインストールすればいいんですが NVIDIA アカウントが必要なので作成します。色々なアンケートに答えるとようやく zip をダウンロードできます。ここでも最新版をダウンロードするのではなく、Archived cuDNN Releases から CUDA 9.0 向けの cuDNN 7.0 系を選択します。ダウンロードした zip は展開しておきます。
NVIDIA cuDNN | NVIDIA Developer
以下の 4.3. Installing cuDNN on Windows にしたがい、ファイルを所定の場所にコピーします。
cuDNN Installation Guide :: Deep Learning SDK Documentation

cuDNN を置いた場所に環境変数を通します(しかし、手元では既に通っていました)。

  • コマンドプロンプトを起動し、
    C:\Users\Cookie>control sysdm.cpl
    と入力すると「システムのプロパティ」ウィンドウが出てくるので「詳細設定」タブの「環境変数」をクリックします。システム環境変数に CUDA_PATH がなければ「新規」から変数名 CUDA_PATH に変数値 C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v9.0 をセットします。既にあればいいです。
Anaconda をインストールし、GPU版 TensorFlow をインストールします。

ここからは TensorFlow 公式の Installing with Anaconda の通りです。
https://www.tensorflow.org/install/install_windows

  • 以下のサイトから Anaconda 5.2 For Windows Installer の Python 3.6 version をダウンロードします。
    Downloads - Anaconda
    基本的に Next をクリックすればインストールできるはずです。
  • Anaconda Prompt を起動し、tensorflow という名前の環境をつくります。別の名前でもいいんですが。今回は Python3.6 なので引数もそうします。
    (base) C:\Users\Cookie>conda create -n tensorflow pip python=3.6
  • そのままつくった環境に入り、GPU版 TensorFlow をインストールします。
    (base) C:\Users\Cookie>activate tensorflow
    (tensorflow) C:\Users\Cookie>pip install --ignore-installed --upgrade tensorflow-gpu
  • インストールできたか検証します。過去に TensorFlow をインストールしたことがあることがある人にはいつものです。sess = tf.Session() を入力した後でぶおおんとなるので早くしろといった気持ちになると思います(手元では)。画面に GeForce GTX 1060 と表示されているので GPU を使っていそうです。
    (tensorflow) C:\Users\Cookie>python
    >>> import tensorflow as tf
    >>> hello = tf.constant('Hello, TensorFlow!')
    >>> sess = tf.Session()
    >>> print(sess.run(hello))
  • GPU とひもづいているかには以下のような確認方法もあるようで、これで確認してもOKでした。
    TensorFlowからGPUが認識できているかを2行コードで確認する - 動かざることバグの如し
Keras もインストールして画像分類タスクで動作確認します(GPU版 vs CPU版)。
  • 先の環境に Keras もインストールします。
    (tensorflow) C:\Users\Cookie>pip install keras
  • 試しにCPU版環境もつくってみます。別の名前にします。こちらの環境で GPU とのひもづけを確認すると当然ひもづいていません。
    (tensorflow) C:\Users\Cookie>deactivate
    (base) C:\Users\Cookie>conda create -n tensorflow-cpu pip python=3.6
    (base) C:\Users\Cookie>activate tensorflow-cpu
    (tensorflow-cpu) C:> pip install --ignore-installed --upgrade tensorflow
    (tensorflow-cpu) C:> pip install keras
  • 動作確認用のタスクを準備します。以下の記事で Augmentation 付きの画像分類をしたことがあったのでこれをもっとたくさんの画像でやってみます。ネットワーク構造その他は適当です。このタスクが GPU と CPU の比較に適しているかは知らないです。
    Keras で少ない画像データから学習したい - クッキーの日記
    • 以下のページから花の画像のデータを拾ってきます(Dataset images)。解凍すると jpg\ 以下に 1360枚の jpg が入っていますが、ファイル名順に80枚ずつ17種類の花の画像になっているので、以下のように振り分けておきます。それぞれの花は Iris とか Pansy とかきちんと名前がありますが、面倒なのでフォルダ名は flower0~flower16 とします。なお自動で振り分けるスクリプトは下の方にあります(Python でやることなのかは知りません)。
      Visual Geometry Group Home Page
      • data/train/flower0/image_0001.jpg(各花の画像80枚のうち60枚は訓練用)
      • data/validation/flower0/image_0061.jpg(各花の画像80枚のうち20枚は訓練用)
    • この花の画像を17クラス分類する畳込みニューラルネットワークを学習します。スクリプトは下の方にあります。PIL というモジュールがないと怒られたら以下の記事を参照して対処します(普通に conda install PIL しようとすると Python を 2.7 系にしようとしてくるので止めてください)。
      Kerasに「PILが無い」と怒られた場合の対策
    • GPU環境(上)とCPU環境(下)でそれぞれ実行した結果は以下です。エポック数は20です。
      Elapsed Time: 296.8[sec]
      Accuracy (train): 95.4%
      Accuracy (test): 54.7%
      Elapsed Time: 1116.6[sec]
      Accuracy (train): 93.2%
      Accuracy (test): 50.6%
      乱数固定できていないので正解率はぶれぶれですがとりあえずGPU版の方がだいぶ早かったです。
動作確認に使用したスクリプトは以下です。

画像データのフォルダ振り分け
jpg フォルダがある場所でこれを実行します。data/train フォルダと data/validation フォルダだけ作ってから実行しましたが作っておかなくても os.makedirs が作ってくれる気がします。なお、jpg フォルダの下から画像ファイルを移動してしまうので注意してください。

import os
import glob
import shutil
imgs = glob.glob('jpg\\*.jpg')
for img in imgs:
    img_num = int(img.replace('jpg\\image_', '').replace('.jpg', '')) - 1
    flower_num = int(img_num / 80)
    is_train = ((img_num - flower_num * 80) < 60)
    dir_name = 'data\\' + ('train\\' if is_train else 'validation\\') + 'flower' + str(flower_num) + '\\'
    if not os.path.exists(dir_name):
        os.makedirs(dir_name)
    shutil.move(img, dir_name + img.replace('jpg\\', ''))

畳込みニューラルネットワークによるクラス分類
昔の記事を2クラス分類から17クラス分類にして微調整したつもりなんですが、突貫で改造したので何か間違っていたらご連絡ください。

# -*- coding: utf-8 -*-
from keras.preprocessing.image import ImageDataGenerator
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D
from keras.layers import Activation, Dropout, Flatten, Dense
from keras import backend as K
import time
#import pandas as pd

if __name__ == '__main__':
  img_width, img_height = 256, 256 # 訓練時の画像サイズ
  
  train_data_dir = 'data/train'
  validation_data_dir = 'data/validation'
  nb_train_samples = 60 * 17
  nb_validation_samples = 20 * 17
  epochs = 20
  batch_size = 32
  
  if K.image_data_format() == 'channels_first':
    input_shape = (3, img_width, img_height)
  else:
    input_shape = (img_width, img_height, 3)
  
  # モデル構築
  model = Sequential()
  model.add(Conv2D(32, (3, 3), activation='relu', input_shape=input_shape))
  model.add(MaxPooling2D(pool_size=(2, 2)))
  model.add(Conv2D(32, (3, 3), activation='relu'))
  model.add(MaxPooling2D(pool_size=(2, 2)))
  model.add(Conv2D(64, (3, 3), activation='relu'))
  model.add(MaxPooling2D(pool_size=(2, 2)))
  model.add(Flatten())
  model.add(Dense(128, activation='relu'))
  model.add(Dropout(0.5))
  model.add(Dense(17, activation='softmax'))
  
  model.compile(loss='categorical_crossentropy', optimizer='rmsprop', metrics=['accuracy'])

  # 訓練データのプレ処理の設定: RGB値のスケーリングに加え、シア変形や拡大縮小によって訓練増強
  train_datagen = ImageDataGenerator(rescale=1. / 255, shear_range=0.2, zoom_range=0.2, horizontal_flip=True)
  # テストデータのプレ処理の設定: こちらはRGB値のスケーリングのみ
  test_datagen = ImageDataGenerator(rescale=1. / 255)
  
  # 訓練データをディレクトリ内のデータから随時作成するジェネレータ
  train_generator = train_datagen.flow_from_directory(train_data_dir,
    target_size=(img_width, img_height), batch_size=batch_size, class_mode='categorical', shuffle=True)
  # テストデータ作成をディレクトリ内のデータから随時作成するジェネレータ
  validation_generator = test_datagen.flow_from_directory(validation_data_dir,
    target_size=(img_width, img_height), batch_size=batch_size, class_mode='categorical', shuffle=True)
  
  # 訓練
  time_start = time.time()
  model.fit_generator(train_generator, steps_per_epoch=nb_train_samples//batch_size,
    epochs=epochs, validation_data=validation_generator, validation_steps=nb_validation_samples//batch_size)
  time_end = time.time()

  # 結果確認(のときは増強しない)
  train_generator = test_datagen.flow_from_directory(train_data_dir,
    target_size=(img_width, img_height), batch_size=batch_size, class_mode='categorical', shuffle=False)
  validation_generator = test_datagen.flow_from_directory(validation_data_dir,
    target_size=(img_width, img_height), batch_size=batch_size, class_mode='categorical', shuffle=False)

  print ('Elapsed Time: %.1f[sec]' % (time_end - time_start))
  result = model.evaluate_generator(train_generator)
  print('Accuracy (train): %.1f%%' % (result[1]*100))
  result = model.evaluate_generator(validation_generator)
  print('Accuracy (test): %.1f%%' % (result[1]*100))