空飛ぶ気まぐれ雑記帳

主に趣味とかプログラミングについて扱います。

DLIB with CUDAでCNNを使ってみた in Windows

はじめに

下記の記事を書いてて、うぉぉぉぉってなってそのままDLIBのコンパイルをやりなおしました。

elda27.hatenablog.com

DLIBのコンパイル

DLIBのCUDAを有効にするためには、CUDA 8.0(RC版じゃないですよ)とcuDNN 5.0以上が必要です。また、Windowsも64bit環境でなければならないので注意してください。
まず、cuDNNは下記のURLからダウンロードしてください。バージョンは5.0でも5.1でもどっちでも構いません。

cuDNN
developer.nvidia.com

このcuDNNを適当な所に解凍し、さらに以下のCMakeを実行します。

cmake -DCOMPILER_CAN_DO_CPP_11=ON -DCMAKE_PREFIX_PATH=<cuDNNのルートディクレトリ> <dlibのルートディレクトリ>

ただし、このままだとコンフィギャレーションエラーが出るので、以下の様に"dlib/cmake_utils/test_for_cudnn/find_cudnn.txt"を以下のように書き換えます(githubリポジトリにある最新版のdlibを使う場合、このバグは修正済みなので飛ばしてOK)。

message(STATUS "Looking for cuDNN install...")
# Look for cudnn, we will look in the same place as other CUDA
# libraries and also a few other places as well.
find_path(cudnn_include cudnn.h
    HINTS ${CUDA_INCLUDE_DIRS} ENV CUDNN_INCLUDE_DIR  ENV CUDNN_HOME
    PATHS /usr/local ENV CPATH
    PATH_SUFFIXES include
    )
get_filename_component(cudnn_hint_path "${CUDA_CUBLAS_LIBRARIES}" PATH)
find_library(cudnn cudnn
    HINTS ${cudnn_hint_path} ENV CUDNN_LIBRARY_DIR  ENV CUDNN_HOME 
    PATHS /usr/local /usr/local/cuda ENV LD_LIBRARY_PATH
    PATH_SUFFIXES lib64 lib x64
    )
mark_as_advanced(cudnn cudnn_include)

これでコンパイルすれば、無事CMakeも通ると思います。

サンプルコード

ソースコードの内容は前と代わりませんがCUDAを使用する場合はcmakeオプションに"-DUSE_CUDA"オプションを追加してください。

github.com

結果

推定精度は前回と同じなんだけど、実行時間(学習時間)が爆速になりました。
以前は学習に2日程かかっていたものが20sで終わるようになりました(GTX 970で実行)
まあ、DLIBではそもそもCPUにおける並列化すらしていないようなので当たり前といえば当たり前ですが。

CUDA 8.0がいつの間にかリリースされててVisual Studio 2015 Update 3でも使えるようになっていた話

CUDA8.0 RCがリリースされたのが4月頃だったと思うが、それから半年。CUDA8.0がいつの間にかリリースされていた。
ダウンロードは下記から。

developer.nvidia.com

さて、このCUDA 8.0の何がスゴイかと言うとCuda8.0 RC では未対応だったVisual Studio 2015 Update 2以降に対応しているという点だ。
これで、DLIBをコンパイルできれば、Windows環境でGPGPUを使ったDNNがまともに使えるかもしれない。

とりあえず、サンプルをコンパイルして動作を確認するところまでは出来たので、今後DLIBのコンパイルをやっていきたい。

追記

下記によると、CUDA8.0は9/28にリリースされたらしい。
知らなかった。

gihyo.jp

GLEWをVisual Studio 2015とCMakeでビルドするときの注意点

GLEWをCMakeでVisual Studio 2015向けのビルドファイルを生成してDebugビルドするとリンクエラーがでる。
実際にリンクに失敗する原因となったシンボル名について検索するとGLEWのgithubのissueが見つかった。

cmake: Cannot build glew32d.dll with Visual Studio 2015 · Issue #99 · nigels-com/glew · GitHub

曰く、Visual Studio 2015以降では/RTC1オプションをオンにした状態だとリンクエラーが出るそうで、無効(Default)にすることを推奨しているそうです。
ちなみに/RTCオプションとはランタイムエラーチェックに関するオプションだそうで、変数のメモリ配置においてReleaseビルドを想定したDebugを行えるようにするオプションだそうです。
/RTC (ランタイム エラー チェック)

DLIBでCNNを使ってみた in Windows

はじめに

今回は前回コンパイルしなおしたDLIBを使ってWindows環境のC++(これ重要)でCNNを使おうぜって話。
ほぼサンプル通りだけど、コメントは日本語化しましたので解説していきます。
ソースコードは全て以下に全部上げてるんでよしなに。
READMEはいずれ更新します。

github.com

なお、私は趣味で機械学習に触れているので、間違えていることや厳密性を欠いていることがままあります。
もし、間違えているところがありましたら、コメント等いただけると幸いです。

What is CNN

CNNとは深層学習の一種でConvolutional Nueral Networkのこと。
中間層でフィルタ処理(畳み込み処理)をするからそう呼ばれているそうです。
(厳密には、フィルタ処理というわけではないそうですが、忘れたんで適当にググれば山ほど情報は出てきます)

今回取り扱うのはLeNetと呼ばれるもので全7層(入力層除く)から成るCNNとなっています。

解説

以下は TestDNN/TestDNN.cpp の中身となっています。


Test CNN using dlib

[1] 学習データの読み込み

今回使うデータはMNISTの手書き文字となっています。
データは以下からダウンロードした4つのファイルを"./Train"以下に保存してください。
MNIST handwritten digit database, Yann LeCun, Corinna Cortes and Chris Burges

[2] CNNの定義

これがおそらく一番の難所。
実際にCNNを定義するんだけれども如何せん読みづらい。
dlibにおいてDNNの構造を定義する時は最も内側にあるテンプレート引数が入力層で,loss_multiclass_logのすぐ内側にあるテンプレート引数が出力層にあたる。
ちなみに以下の場合、dlib::input> というのが入力層で dlib::fc<10, ...>が出力層になる。
ここで、dlib::input<>が入力のためのインターフェースで、デフォルトでは dlib::array2d とdlib::matrix型に対応している。

  // [2] CNN の定義

  // CNN の定義
  // A<B<C>> となっている場合 Cが入力で,Bが中間層,Aが出力層
  // なので,出力はfcにするのがいいと思う.
  // fc<10, ...>:Fully connected layerでノード数は10
  // relu:活性化関数の名前.詳しくはReLUで調べてください
  // con<16,5,5,1,1,SUBNET> で5✕5のフィルタサイズを1✕1のstrideで畳み込みするノードが16個ある
  // max_pool<2, 2, 2, 2, SUBNET> 2✕2のウインドウサイズで2✕2のstrideでプーリングを行う.
  // relu<fc<84, ...>> この場合活性化関数がReLUで84ノードからなる層を定義している.
  // max_pool<2,2,2,2,relu<con<16,5,5,1,1,SUBNET>>> これでconvolutionした結果をReLU関数で活性化してそれをMax poolingする
  // input<array2d<uchar>> cv_image<uchar>を入力に取る.現在cv::Matを入力に取れるように試行錯誤中
  using net_type = dlib::loss_multiclass_log<
    dlib::fc<10,
    dlib::relu<dlib::fc<84,
    dlib::relu<dlib::fc<120,
    dlib::max_pool<2, 2, 2, 2, dlib::relu<dlib::con<16, 5, 5, 1, 1,
    dlib::max_pool<2, 2, 2, 2, dlib::relu<dlib::con<6, 5, 5, 1, 1,
    dlib::input<dlib::array2d<uchar>>
    >>>>>>>>>>>>;

  // 上のCNN場合
  // -FC-> : Fully connectedな接続
  // -> : 重みを共有した接続
  // 入力画像->[6ノードの畳み込み層]->プーリング層->[16ノードの畳み込み層]->プーリング層-FC-> ...
  //          [120ノードの普通のNN]-FC>[84ノードの普通のNN]-FC>出力(10次元ベクトルで各次元に各数字の確率が保存される)

ここで,サンプルに登場している各クラスについてはそれぞれ以下の通りになっている。

  • loss_multiclass_log:おそらく損失関数。
  • fc: Fully Connected layer のこと。つまり、接続する層同士で全てのノードが接続されている状態。
  • relu:活性化関数の一種で、ReLU関数のこと。制御系だとランプ関数と呼ばれるもので、x=\mbox{max}(0, x)で表される。
  • max_pool:Max pooling による重みのリサンプリング
  • con:畳み込み層
  • input:DNNの入力のTraits。dlibではデフォルトでarray2dとmatrix型につちえ実装がなされている。

上記の用語についてわからない用語があれば以下のQitaのエントリーが詳しいと思う。

qiita.com

[3] 学習器の設定

これはなんてことないですね。ただの学習器の設定です。
今回は学習に全ての画像を読み込んでいるので何も考えずにtrainメソッドを呼べばOKです.

[4] 学習結果の保存

DLIBのサンプル曰く、保存する前に一度clearメソッドを呼べとのことです。詳しい理由は書いていなかったのですが、これはポイントなので外さないようにとのことでした。

実行結果

以下が実行結果
かなり高い識別率がでていることがわかる。

正答数 誤答数 総数
学習済みデータ 59985 15 60000
非学習済みデータ 9914 86 10000

おわりに

今回学習に2日近くかかっているので、やっぱりCUDAは欲しいなと思う。
ただ、現状Windows環境でCUDAを使うことが難しいのが本当に辛い。

C++でDNNが使えないなんてあるわけないよ in Windows

一ヶ月くらいクソ忙しかったせいで久々の更新。
書くネタは山ほどあるんだけど、またその内、連続で書きます。

はじめに

Windows環境のC++(これ重要)でDNN(Deep Nueral Network)を使おうぜって話。
DNNといえば最近流行りのものなんだけど、如何せんWindows環境。特にC++となるとグッと道が狭まる傾向にある。
正直嫌がらせでも受けてるんじゃないんですかねって言うくらいに。

実際、有名な所でGoogleのTensol Flowがあるけれども、あれもMacLinuxの64bitしか対応していない。
それで色々とC++で使えるDNNののライブラリを調べてみたわけです。
個人的な要望として、GPGPUによる高速化が成されているものがベストかなと思っています。

Tensor Flow

Googleが出した商用フリーの機械学習ライブラリで、なんかすごいらしい。
曰く、状態遷移図で表現できるあらゆる問題を解けるとか凄い話を聞いたことがある。
私は現在Windows以外のまともな環境が無いので使ったことはないけれども、学習の経過をビジュアライズまで自動でやってくれて、その結果をブラウザで確認できるのがすごく魅力的。
ただし、前述の通りWindowsでは使えないのです。

OpenCV

おそらく最も有名無い画像処理/コンピュータビジョンのライブラリ。
なのだけれども、いくら探してもOpenCVだけでDNNを使うという話が出てこない。
より正確に言うとC++OpenCVだけを使ってDNNを使うという話が出てこない。
実際に「OpenCV DeepNeuralNetwork」で検索しても上から10個が全てCaffeのモデルを読み込んでみた、という内容のものばかりだし、OpenCVのサンプル一覧を見てもそれらしいものが見あたらなかった。

DLIB

以前記事をかいたけれども、機械学習ライブラリでその中にDNNも含まれており、もちろんGPGPUを用いた高速化も可能。
なのだけれども、残念なことにWindows環境だとリンクエラーがでる。
FAQ曰く、Visual Studioの場合C++11に完全に対応しているバージョンがないので未対応とのこと。
さらに、こいつ。厄介なことに全てのパラメータがテンプレートで解決されるため、動的にパラメータを変えてどうのこうのっていうのはやり難い。
まあ、最近はAutoEncoderかなにかで事前学習して層数とかを決めるらしいからそれもあまり問題にならないのかもしれないけれども。

Tiny DNN(https://github.com/tiny-dnn/tiny-dnn)

非常にスマートなライブラリで、Windowsに対応している素敵ちゃん。
必要なライブラリとしてOpenCVを要求していることは入力インターフェースにOpenCVを使っていると思われる。
特徴としては、DLIBと違ってパラメータを動的に決定することが可能。
ただし、CPUでの学習しか対応していないので、その辺りはネックに成るかもしれない。

えっ、無くね?って話

Tiny DNNが一番良いのだけれども、GPGPUによる高速化がないと学習が終わる気しない。
Tiny DNNのトップページ曰く、

98.8% accuracy on MNIST in 13 minutes training (@Core i7-3520M)

とのことなので、6000サンプル10クラスよりも多いクラスに分けたり、サンプル数を増やしたりとしていると爆発的に計算時間が掛かりそう、というか掛かるのでやっぱりGPGPUが良い。

DLIBさんのDNNってなぜに動かないのん?

FAQに書かれている内容は以下の通りだが、どうにも納得できない。

The deep learning toolkit in dlib requires a C++11 compiler. Unfortunately, as of July 2016, no versions of Visual Studio fully support C++11, so not all the deep learning code will compile. However, all the other modules in dlib can be used in Visual Studio without any trouble.

確かに、Visual Studio 2015は結構C++11の中で対応していない機能がある。
ただ、それもUpdate 2でかなり解消されている。

それで、実際にソースコードを読み漁ってみてもどこにもそれらしいコードは見られなかった。
というわけで無理やりコンパイルしてしまえという結論に至りました。

DLIB with DNN in Windows

DLIBをDNNが使える状態でVisual Studioにてコンパイルするためにはcmakeをするときに以下のオプションを足せばOKです。

cmake -DCOMPILER_CAN_DO_CPP_11 <dlibのルートディレクトリ>

これで後は普通にコンパイルするだけです。
実際にライブラリをリンクしてコンパイルしても問題なく動作することを確認しています。

ただし、未だnvccでコンパイルが通るかどうか確認していません。
というのもCUDA8.0 RC ですらVisual Studio 2015 Update 1までしか対応していなくて、現在、私が持っている環境はUpdate2とUpdate3なので、両方共CUDAをONにしてdlibのコンパイルを出来ていません。
なので、上記の話は全てCPUで使う場合にのみ有効です。

CUDAについては、おいおい動作確認をしていきます。
それとサンプルコードも実はすでに書いたのですが、それはまた後日別記事であげますので悪しからず。

追記

今朝方、上記の方法にてDLIBをVisual Studio 2015 Update2でコンパイルしてみるとコンパイルに失敗したので、おそらくCUDAを使うことは難しそうです。

追記2

私がこの記事を上げてから、ほぼ同時か、翌日くらいにgithubにissueが上がっていたので追記。
どうやらdlib::repeatを使ってDNNのノードを定義すると、テンプレートの展開で無限ループにハマるらしくVS2015 Update3ではまだDNNを使えないようにしているらしい。

追記3

実際にDLIBを使ってDNNを使ってみたサンプル&解説記事を書きました。
elda27.hatenablog.com

GPGPUって色々あるけど結局どれがいいの

はじめに

元ネタはこれ。本文の引用も特に注意書きがなければここから引用してます。
CUDAとOpenCLどっちがいいの? - Qiita

オチは変わらず、

そもそも単純に比べんな。ナイフとノコギリがどっちがいいかなんて一概には言えないだろう?

ただ、自分なりに色々調べたり使ってみて、それのまとめ。

CUDA

元ネタにかかれている通り、GPGPUっていうとCUDAが最強って説が多いし、実際使ってみるとめちゃくちゃ早い……らしい。
それに、オフラインコンパイルができるのがかなり素敵。
ただ、最新のCUDA8.0RC版ですら、Visual Studio 2015のUpdate 1までしか対応してないので、常に最新の環境を使いたい人や何も考えずにVisual Studioをアップデートしてきた人にとってはかなり辛い(2016年8月18日現在Update 3まで出ています)。
まあ、常に最新の環境を使いたい人はclang使えよって思うから一概には言い難いけど。

それはさておき、私は何も考えずにVisual Studioをアップデートしてきた勢なんでCUDAは全く使っておりません(他所の環境で遊ばせて貰ったことはありますが…)。

OpenCL

OpenなGPGPUというか並列化ライブラリ代表で、コンピュータビジョンやってる人的にはOpenCVのラッパーが結構便利で利用しやすいのが売りかな。
それと、GPUが載っていない環境でもOpenCLの場合、CPUでも同一のソースコードで並列化が実現できるので、その点結構便利かもしれない。
ただ、如何せん規格が微妙という説がある。
以下は個人的に使ってみた感想というか不満。

・オフラインコンパイルができないせいで実行時に毎回コンパイルする必要がある。
・元ネタにも書いているけどnVidiaのやる気の無さがパない。具体的にはGTX 970のドライババージョン350.81(だったかな)でOpenCL1.2のそれも半端にしか対応してない。これがどれ位酷いかと言うと、2005年頃にノートパソコン向けで一線級を誇っていたAthron X2 QL-60ですらOpenCL1.2に対応しているので、推して知るべし。
・元ネタを忘れたんだけど、初期の頃、中間言語がないため、実装をベンダーに丸投げしたせいで結構昔からあるくせにあまり浸透しなかったらしい。さらに、ベンダーによって実装レベルがマチマチ&nVidiaさん見たいなやる気無し勢が発生する原因にもなっているらしい。

OpenGL Compute Shader

Vertex ShaderやFragment Shaderは使ったことあるけど、これは使ったことない。でも、書き方は一緒なので多分使えるんだと思う。
ただ、OpenGLもShaderのオフラインコンパイルができないので実行時に毎回コンパイルする必要があるから、OpenCL使える人はわざわざこれ使う意味はないと思う。

DirectX Direct Compute

こいつもOpenGL Compute Shaderと一緒。ただ、こいつはオフラインコンパイルができるからOpenGL Computer Shaderよりも遥かにまし。

C++ AMP

もう最高。配列に突っ込んで計算するだけでハイ爆速っていうのが最高。
ただし、以下のとおり、Direct Computeが動いているのでDirectX 11の機能レベル11.0以上が必要。

Microsoftがやってるので当然Windowsで動きます(裏ではDirectComputeが動いているっぽいです)

さらに、倍精度演算を行うためにはWDDM1.4以上に対応している必要があったはず(うろ覚え)。

なお、私のメインの開発PCはDirectX 11対応のくせに機能レベル10.0とかいう謎仕様なので、使えません。

HIP

CUDAとほぼ同じ構文でポータブルな感じにしてくれるもの。 HIP自体はフロントエンドコンパイラみたいなもので、バックエンドにnvccを使えばCUDAに、先述のHCCを使えばGCN-ISAかHSAILになります。どちらも、オーバーヘッドはありません。もし更に最適化したかったら変換後のコードを見て最適化が可能です。

らしいのだけれども、Windowsに対する嫌がらせなのかWindows環境だとCMakeが通らない。
どうやら、CMake内でPerlスクリプトを使ってバージョン情報やらを取得してるんだけど、それの中でエラーが起きて上手くバージョン情報やらを取得できてないっぽい。
それに、私のコンパイル方法が悪いのかと思ってggってもHIPっていう名前のせいで尻についてしか出てこず、碌な情報がて手に入らないのがかなり辛い。

謳っていること自体はかなり私好みなだけに本当に辛い。

Etc...

使ったことも使う予定もないので知らん。
元ネタでも書かれているとおり調べても情報が出てこないか、微妙だなという話が非常に多い。

おわりに

私の開発PCはAMDなのでできたらHIPを使いたかったのだけれども、どうにも動かないので結局OpenCLを使い続けてます。
どなたか、HIPの情報まとめてくれないかなー

OpenGLでズームを実装する方法

OpenGLでズームを実装する方法について。
最初、カメラの中心と距離をいじってやろうと思ってたんだけど、どうも上手くいかなかったから、ちょっと調べたら出てきたました。
元のネタはここの8.040、How do I implement a zoom operation?
https://www.opengl.org/archives/resources/faq/technical/viewing.htm

最も手っ取り早い方法としてModelView行列の対角要素をN倍する方法があるけど、それはスケールが大きく/小さくなりすぎると、Projection行列のNearかFarの影響で描画したいものが正確に表示されないという問題がある。そこで、ModelView行列ではなく、Projection行列を弄ってやればハッピーじゃないかという話です。

例えば、glFrustum、ここではもう少しモダンOpenGLということでglm::frustumですが、それを使ってズームを実装すると以下のようになります。

glm::frustum(left / scale, right / scale,
    bottom / scale, top / scale,
    z_near, z_far);

ただ、これだけだと常に画面中心にしかズームができないので、さらにx,y座標のどこにズームするかを指定できるように以下のように変更してやります。

glm::frustum((left + center_x) / scale, (right + center_x) / scale,
    (bottom + center_y) / scale, (top + center_y) / scale,
    z_near, z_far);

ただ、このコード、正確に動くかは検証していません。
なにせ、私の場合near平面のサイズが既知の条件で扱っているので、以下のようになっているかです。

glm::frustum(
  -0.5f * (this->WindowWidth  + this->Center.x) / this->Scale,
   0.5f * (this->WindowWidth  - this->Center.x) / this->Scale,
  -0.5f * (this->WindowHeight + this->Center.y) / this->Scale,
   0.5f * (this->WindowHeight - this->Center.y) / this->Scale,
  z_near, z_far
);

微妙にthisとかチラチラしてますが基本的な考え方は同じです。