ディープラーニングのできる機械学習フレームワークTorchをWindowsに入れようとしてみたメモ.
基本的にGitHubのWikiのWindowsのUsing VisualStudio manuallyを踏襲すればいいが,(2017.3.21時点では)いくつか引っかかる点がある.
以下,前提環境.
- Windows10
- CMake
- Git
- Visual Studio 2015 Professional
-
OpenBLAS
- バイナリ版はLAPACK付くのでFortranコンパイラが無いと不可.
- (2017.3.31追記)wikiにblas周りのインストラクションが追加されている模様.lapackとblasのバイナリ版が紹介されている.
※更新が早いので,この情報は古い可能性が高いです.
Torchをインストール
- luajit-rocksをインストール
- どこかにgit cloneする
mkdir C:\libs && cd C:\libs && git clone https://github.com/torch/luajit-rocks.git
- どこかにgit cloneする
- cmakeする
- cmake-guiでok
-
64bitじゃなくて無印(32bit)にしておく64bitでも行けるかもしれないけど,公式が64bit試してないって言ってるので怪しいので試してないです できるかもしれないけど,的なことは言ってる- 64bitいけます.
- ビルドディレクトリは適当に 一つbuildを掘る感じでいいのでは(C:/libs/luajit-rocks/build)
- CMAKE_INSTALL_PREFIXはgitのワーキングディレクトリ(C:/libs/luajit-rocks)でいいかも.ProgramFilesはやめておいた方が…
- ビルドする
- Releaseビルドにするのを忘れない.
-
環境変数を設定する
- だいたいwikiのとおりでいい.
- (2017.3.21追記)torchのビルド中にtorchcwrap.luaが見つからないエラーを吐いていたので,カレントディレクトリも探すようにさせるためLUA_PATHに追加.
PATH=C:\libs\luajit-rocks;%PATH% LUA_DEV=C:\libs\luajit-rocks LUA_PATH=C:\libs\luajit-rocks\?;C:\libs\luajit-rocks\?.lua;C:\libs\luajit-rocks\lua\?;C:\libs\luajit-rocks\lua\?.lua;C:\libs\luajit-rocks\lua\?\init.lua;?.lua LUA_CPATH=C:\libs\luajit-rocks\?.DLL;C:\libs\luajit-rocks\LIB\?.DLL;?.DLL
Torchをgithubからダウンロード
mkdir C:\libs\Torch && cd C:\libs\Torch && git clone https://github.com/torch/torch7.git
-
LuaRocks用のCMakeのラッパーを作成
- LuaRocksはnmakeをビルダーに使うが,cmakeはMakefileを作る設定になっておらず,VisualStudioのソリューションを作る設定になっている.そこを書き換える
- C:/libs/luajit-rocks/cmake.cmd
if %1 == -E ( cmake.exe %* ) else ( cmake.exe -G "NMake Makefiles" -DCMAKE_LINK_FLAGS:implib=libluajit.lib -DLUALIB=libluajit %* )
-
TorchのLuaRocksの設定ファイル(rockspec)をダウンロード
C:\libs\Torchでluarocks download torch
- ちなみにluajit-rocksを使わず本家のLuaRocksを使った場合,torchが見つからない.デフォルトのリポジトリには登録されていないのだ.torchの登録されている,別の検索リポジトリを追加する必要がある.やり方は暇な時にやる.あるいはおとなしくluajit-rocksを使う.
-
ダウンロードした設定ファイルをちょっと変更.BLASの設定回り.
- && $(MAKE)の前に以下を追加.
-DBLAS_LIBRARIES=(openblasのライブラリのパス)/libopenblas.lib -DBLAS_INFO=open
- OpenBLASじゃない場合はBLAS_INFOに他のキーワードを入れる.備考にて.
- && $(MAKE)の前に以下を追加.
-
VisualStudioの実行環境を整える
- 各Visual Studioのバージョンに対応したvcvarsallを使えばたぶんいける
call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat"
-
CMakeのラッパーにパスを通す
- コマンドプロンプト上でパスを通す.
PATH=C:\libs\luajit-rocks;%PATH%
- パスは必ずluarocks makeの直前にやること.PATHの先頭に置くのがコツ.cmake.exeが代わりに呼ばれてしまうのを防ぐ必要があるため.
- コマンドプロンプト上でパスを通す.
-
1回make
cd C:\libs\Torch\torch7 luarocks make ..\torch-scm-1.rockspec
- 途中でエラーが出るかもしれない.高速化に使用できるフレームワークのチェック時に,非対応時にエラーを吐くことがある.その都度中止を押してスルーすればよい.
- (2017.3.31追記) Unknown CMake command "check_function_exists" と出ることがある.CMakeのバージョンとlib/TH/cmake/FindLAPACK.cmakeの書き方の問題?C:\libs\Torch\torch7\lib\TH\cmake\FindLAPACK.cmakeの28行目
include(CheckFortranFunctionExists)
の下に,include(CheckFunctionExists)
を追加.
-
(エラーが出た場合)C:\libs\Torch\torch7\build\CMakeCache.txtをいじる
- CMAKE_C_FLAGSとCMAKE_CXX_FLAGSの末尾に/arch:AVXを追加
- CMAKE中にAVX2まわりがSuccessだったら代わりに/arch:AVX2を追加すればよい?なったことが無いからわからない
- CMakeCache.txt中のCXX_AVX_FOUNDでも有効かどうか確認できる
- 本家は対応しなさそうだなー
-
(エラーが出た場合)もう一回make
luarocks make ..\torch-scm-1.rockspec
- これでビルドが成功すればLuaRocksに登録される.
-
テストを起動してみる
luajit -ltorch -e "torch.test()"
- LAPACKを入れていない場合,LAPACKまわりでERRORを検出する模様.LAPACKまわりの機能を使うものがあれば,LAPACK入りビルドが必要になる.使わないなら気にする必要はない.
その他モジュールをインストール
luaffi
wikiどおり.
nn
基本はwikiどおり.
nnでコンパイルエラーする場合,
nn\lib\thnn\generic/FeatureLPPooling.c(213): error C3015: OpenMP 'for' ステートメントの初期化には、正しくない形式が含まれています
などと出る場合,該当ファイルのOpenMPにかかわる部分を修正する必要がある(参考記事).
OpenMPの部分だけ,以下のように変更する.
- for文の中で変数定義しない
- ループに使うインデックス変数は符号付整数型にする
すなわち,以下のようにする.
void
THNN_(FeatureLPPooling_updateOutput)(
THNNState *state,
THTensor *input,
THTensor *output,
accreal power,
int width,
int stride,
bool batchMode) {
int inputDim = THTensor_(nDimension)(input);
int batch;
int opt1;
int opt2;
int outputFeature;
int _i;
if (batchMode) {
THArgCheck(inputDim >= 2 && inputDim <= 4, 2,
"input must be 2-4 dimensions for batch mode");
} else {
THArgCheck(inputDim >= 1 && inputDim <= 3, 2,
"input must be 1-3 dimensions for non-batch mode");
}
FeatureLPPoolingSizes inputDesc =
THNN_(FeatureLPPooling_upcastCPU)(input, batchMode);
// Make sure the feature dimension is properly sized
THArgCheck(inputDesc.size[1] >= width, 3,
"input: feature dimension must be >= width");
// Make sure that width and stride are within range
THArgCheck(width >= 2 && width <= 16, 5,
"width must be between 2 - 16");
THArgCheck(stride >= 1 && stride <= 4, 6,
"stride must be between 1 - 4");
// Resize output
THNN_(FeatureLPPooling_resizeForOutputCPU)(
output, input, batchMode, width, stride);
FeatureLPPoolingSizes outputDesc =
THNN_(FeatureLPPooling_upcastCPU)(output, batchMode);
real* inputP = THTensor_(data)(input);
real* outputP = THTensor_(data)(output);
#pragma omp parallel for
for (batch = 0; batch < inputDesc.size[0]; ++batch) {
#pragma omp parallel for
for (opt1 = 0; opt1 < inputDesc.size[2]; ++opt1) {
#pragma omp parallel for
for (opt2 = 0; opt2 < inputDesc.size[3]; ++opt2) {
#pragma omp parallel for
for (outputFeature = 0;
outputFeature < outputDesc.size[1]; ++outputFeature) {
accreal v = (accreal) 0;
#pragma omp parallel for
for (_i = 0; _i < width; ++_i) {
size_t inputFeature = outputFeature * stride + _i;
if (inputFeature >= inputDesc.size[1]) {
break;
}
v +=
pow(inputP[flpGetOffset(&inputDesc,
batch,
inputFeature,
opt1,
opt2)], power);
}
outputP[flpGetOffset(&outputDesc, batch, outputFeature, opt1, opt2)] =
pow(v, (accreal) 1 / power);
}
}
}
}
}
void
THNN_(FeatureLPPooling_updateGradInput)(
THNNState *state,
THTensor* gradOutput,
THTensor* input,
THTensor* output,
THTensor* gradInput,
accreal power,
int width,
int stride,
bool batchMode) {
int inputDim = THTensor_(nDimension)(input);
int batch;
int opt1;
int opt2;
int outputFeature;
int _i;
if (batchMode) {
THArgCheck(inputDim >= 2 && inputDim <= 4, 3,
"input must be 2-4 dimensions for batch mode");
} else {
THArgCheck(inputDim >= 1 && inputDim <= 3, 3,
"input must be 1-3 dimensions for non-batch mode");
}
FeatureLPPoolingSizes inputDesc =
THNN_(FeatureLPPooling_upcastCPU)(input, batchMode);
FeatureLPPoolingSizes gradOutputDesc =
THNN_(FeatureLPPooling_upcastCPU)(gradOutput, batchMode);
FeatureLPPoolingSizes outputDesc =
THNN_(FeatureLPPooling_upcastCPU)(output, batchMode);
// Make sure the feature dimension is properly sized
THArgCheck(inputDesc.size[1] >= width, 3,
"input: feature dimension must be >= width");
// Make sure that width and stride are within range
THArgCheck(width >= 2 && width <= 16, 7,
"width must be between 2 - 16");
THArgCheck(stride >= 1 && stride <= 4, 8,
"stride must be between 1 - 4");
for (int i = 0; i < 4; ++i) {
THAssertMsg(outputDesc.size[i] == gradOutputDesc.size[i],
"output and gradOutput sizes do not match");
}
// Make sure that the input sizes produce the output sizes
THArgCheck(flpOutputSize(inputDesc.size[1], width, stride) ==
outputDesc.size[1], 3,
"input and output sizes do not match with respect to "
"width and stride");
// Resize `gradInput` based on `input`
THNN_(FeatureLPPooling_resizeCPU)(gradInput, input);
// Zero gradInput for accumulation
THTensor_(zero)(gradInput);
FeatureLPPoolingSizes gradInputDesc =
THNN_(FeatureLPPooling_upcastCPU)(gradInput, batchMode);
real* gradOutputP = THTensor_(data)(gradOutput);
real* gradInputP = THTensor_(data)(gradInput);
real* outputP = THTensor_(data)(output);
real* inputP = THTensor_(data)(input);
#pragma omp parallel for
for (batch = 0; batch < inputDesc.size[0]; ++batch) {
#pragma omp parallel for
for (opt1 = 0; opt1 < inputDesc.size[2]; ++opt1) {
#pragma omp parallel for
for (opt2 = 0; opt2 < inputDesc.size[3]; ++opt2) {
#pragma omp parallel for
for (outputFeature = 0;
outputFeature < outputDesc.size[1]; ++outputFeature) {
// Load output (f(x_is)). It is possible that this is zero, in
// which case we'll ignore this point.
real outputV =
outputP[
flpGetOffset(&outputDesc, batch, outputFeature, opt1, opt2)];
if (outputV == (real) 0) {
continue;
}
#pragma omp parallel for
for (_i = 0; _i < width; ++_i) {
size_t inputFeature = outputFeature * stride + _i;
THAssert(inputFeature < inputDesc.size[1]);
real gradOutputV =
gradOutputP[
flpGetOffset(&gradOutputDesc, batch, outputFeature, opt1, opt2)];
real inputV =
inputP[
flpGetOffset(&inputDesc, batch, inputFeature, opt1, opt2)];
// Calculate grad * (x_i / f(x_is))^(p - 1)
real v = gradOutputV * pow(inputV / outputV, power - (accreal) 1);
gradInputP[
flpGetOffset(&gradInputDesc, batch, inputFeature, opt1, opt2)]
+= v;
}
}
}
}
}
}
image
zlib, ligpng, libjpegが必要.現在のGnuWin32付属のzlib, libpng, libjpegを使おうとするとエラーする.自分でソースを拾ってビルドする必要がある.libjpegはlibjpeg-turboを使うのが楽で確実.DLLにはパスを通すこと.
- いろいろ準備
- zlib, libjpeg, libpngをビルド
- libjpeg-turboを使うのであれば,すべてCMake.staticでいい.
- sharedを使うならdllパスを通す.
- vcpkgを使うと楽.
- zlib, libjpeg, libpngをビルド
-
ビルド環境を整える
cd libs/Torch luarocks download image git clone https://github.io/torch/image.git cd image luarocks make ..\(ダウンロードしたrockspecファイル)
-
build/CMakeCache.txtから,ZLIB,PNG,JPEGのINCLUDE_DIR,LIBRARY_RELEASEの設定の編集をする
- 設定の編集だけならcmake-guiを使うと楽かも.
もう一度
luarocks make ..\image-scm1.0.rockspec
-
luajit -limage -e "image.test()"
- Compressまわりでエラーするかも.解決方法はわからないが,とりあえずそこらへんを触らない使い方をすれば使えると思う
- jpegの方はjpeg8対応ビルドにするとOK.
trepl
要readline.GnuWinのreadlineか,WinEditLineが必要.
※ (2017.9.17) 最近はtreplパッケージの中でreadlineを用意しているようだが,luajit-rocksのビルド時のものと別のものになってしまうと,luajit-rocksがエラーで起動できなくなってつらい.やってしまったら,trepl,luajit-rocksの中のreadline.dllを削除して,rockspecの中からreadline関係をコメントアウトしてreadline.dllを生成しないようにすることで,上で用意したreadline.dllを使用してもらうようにする.
-
GnuWinのreadlineの場合
- 32bitならgnuwin32,64bitならこれを64bitでビルド
-
成果物をrockspecのincdirs, libdirs, librariesに書き込んで,definesの行を消して,メイク.こんなかんじ
windows = { modules = { ['readline'] = { sources = {'readline.c'}, libraries = {'readline'}, -- defines = {"WinEditLine"}, incdirs = {"C:\\gnuwin64\\include"}, libdirs = {"C:\\gnuwin64\\lib"}, -- libraries = {'edit_static', 'user32'} libraries = {'readline'} } } }
成果物のreadline.dllを,luajit-rocksのreadline.dllと置き換える.
- WinEditLineの場合
- ここからダウンロード&cmakeでビルド
- 成果物をrockspecのincdirs, libdirsに書き込んでメイク.
- 成果物のreadline.dllを,luajit-rocksのreadline.dllと置き換える.
lua-cjson
- cd C:\libs\Torch && luarocks download --rockspec lua-cjson
- ダウンロードしたrockspecの中の,sourceのurlから,ソースをダウンロード,展開
- ここではC:\libs\Torch\lua-cjsonにしておく
-
lua_cjson.cの77行目あたりの空いているところに以下を追加
#if defined(_WIN32) && !defined(strncasecmp) #define strncasecmp(x,y,z) _strnicmp(x,y,z) #endif
cd lua-cjson && luarocks make ..\(ダウンロードしたrockspecファイル)
threads
- dlfcn-winをインストール
git clone https://github.com/dlfcn-win32/dlfcn-win32.git
- visual-studio/12にあるプロジェクトを使って,dlをビルド
- dl.dll, dl.lib, dlfcn.hを適当な場所に置く
- ここではC:\libs\dlfcn-winに置くことにする
git clone https://github.com/torch/threads.git
- threads/rocks/threads-scm-1.rockspecに書いてあるコメントを参考に,makeを実行
luarocks make rocks\threads-scm-1.rockspec WIN_DLFCN_INCDIR="C:\libs\dlfcn-win" WIN_DLFCN_LIBDIR="C:\libs\dlfcn-win"
cutorch, cunn
nmake対応にするため,rockspecの中の,$(MAKE)の後ろの-jオプションを消す.
残念ながらnmakeには並列コンパイル機能がついていない.clのコンパイラオプションに/MPを仕込む必要がある.
cutorch (2017.9.17追加)
- lib/THC/THCReduce.cuhでエラーすることがある.
THC_getGridFromTiles(THCCeilDiv(outElements, (long)block.x), grid);
をTHC_getGridFromTiles(THCCeilDiv((long)outElements, (long)block.x), grid);
に変更し,型を統一する. - lib/THC/generic/THCTensor.cでエラーすることがある.
VisualStudio的には配列のサイズ指定に変数を使用するのはご法度なので,ちゃんとmallocを使うようにする.すなわち,long *op_sizes[count];
,long op_dims[count];
をlong **op_sizes = malloc(sizeof(long *)*count);
,long *op_dims=malloc(sizeof(long)*count);
にして,関数の末尾にfree(op_sizes);
,free(op_dims);
をつける.
cunn (2017.9.17追加)
cunn\lib\THCUNN\LogSigmoid.cu が以下のエラーすることがある.
more than one instance of overloaded function "fmaxType" matches the argument list
複数あるconst T max = fmaxType(0.f, - *input);
を探し出し,const T max = fmaxType((T)0, - *input);
に変更する.
cudnn (2017.9.17追加)
cuDNN本家は先にインストールしてある必要がある.
luarocks download --rockspec cudnn
git clone git://github.com/soumith/cudnn.torch.git
cd cudnn.torch
その後,..\cudnn-scm-1.rockspecを編集し,build_commandの&& $(MAKE)
の前でcuDNNをいれた場所を指定する.例えば,-DCUDNN_LIBRARY="C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v8.0/lib/x64/cudnn.lib"
.
なお,本家cuDNNのバージョンに合わせて,gitリポジトリのブランチを変更してやる必要がある.cuDNN v6をインストールした場合は,git checkout R6
をやる.対応しているかどうかはgit branch
で確認.
その後,luarocks make ..\cudnn-scm-1.rockspec
でインストール.
graph
lua上でのgraph自体のインストールは問題ないが,64bitの場合,64bit対応済みGraphVizにする必要がある.
GraphViz公式は64bit対応する気がないようなので,サードパーティーの64bitパッチをあてる. https://github.com/mahkoCosmo/GraphViz_x64
そして,環境変数PATHにGraphViz/binへのパスを通す.
ただし,上パッチのzlibの参照がおかしい(zlibのバージョンが違う?)ので,zlibを自分で用意してGraphViz/binにコピーしてあげる必要がある.
他のモジュール
(2017.9.17更新)
素直にインストールできないものが他にもいろいろありそう.素直にできるものもあると思うけど.
知ってる限り素直にいったもの一覧:
cwrap, dok, graphicsmagick, luafilesystem, optim, paths, penlight, sundown, sys, xlua
ダメだったもの一覧:
async, cuda-convnet2(ccn2)
BLASまわり (2017.9.17更新)
LAPACK,BLASによりテンソル計算回りを高速計算することができる.
BLASのみならOpenBLASを使うのが高速だが,LAPACKも生かしたい場合はVisualSutdioオンリーはあきらめたほうが良い.
LAPACKを使いたい場合は,LAPACK for Windowsを使うことになるが,ビルドにはMinGWあるいはVisualStudio + Intel Fortran Compiler(有料)が必要になる.
LAPACKに付属するのはRefBLASだが,RefBLASはOpenBLASに比べてかなり遅い.
できればOpenBLASを使いたいが,OpenBLAS対応のLAPACKバイナリは提供されていない.
最も簡単なインストールをする場合は,以下の手順がよい.
- MSYS2+MinGWをインストール
筆者はchoco install msys2
でインストールしている.dllのある場所へのパスが通っているのを確認する. - LAPACK for Windowsの,Prebuilt dynamic libraries using Mingwの,RefBLASのdll,lib,LAPACKのdll,libをダウンロードし,適当な場所に置く.パスを通す.
- torchのインストールの時に,rockspecで指定するLAPACK,BLASに,上記を置いた場所を指定.
- インストール後,torch.test()を試して,すべての機能が使えることを確認する.
GPU対応
インストール
CUDA,cudnnがインストールされていることが前提.
cutorch, cunn, cudnnをインストール.
ビルドに時間がかかるので注意.
GPU版のチェックポイントをCPU版に変換
GPUで諸々作った場合,実際に使うときはCPUで使うことも多いと思うので,CPU版に変換する方法.
GPU入ったマシンでやる必要があります.
require 'torch'
require 'nn'
require 'nngraph'
require 'cutorch'
require 'cudnn'
torch.setdefaulttensortype('torch.FloatTensor')
checkpoint=torch.load('filename.t7')
checkpoint:float() --CudaTensor to FloatTensor
cudnn.convert(checkpoint, nn) --CudaLayer to nnLayer
torch.save('filename_cpu.t7',checkpoint)
備考
- Lua: プログラミング言語およびインタプリタ実行環境.chocolateyにのってる.
- LuaJit: Luaより高速な実行環境.かなりはやくなるらしい.chocolateyにのってない.
- LuaRocks: Luaのパッケージ管理ツール.パッケージインストールには,だいたいCMakeとC++コンパイラが必要.あとwindowsで使うにはちょっとしたカスタマイズが必要.
- luajit-rocks: ↑3つが全部乗ってる.しかもtorch仕様で,torch対応のluarocksリポジトリがデフォルトで設定される.LuaJITを使うか,Luaを使うか,更にどのバージョンを使うかをCMake時に選べるので,基本的にこれ1本でいいっぽい.ちなみにLuaJITにtorch印がつく.
- Using MSVC automaticallyはインストール失敗してしまった.たぶんBLASまわり?
なんかへん
- 反映されてない気がする
キャッシュの影響かもしれない.CMakeCache.txtを消してまたやり直してみるとか,buildディレクトリごと消してやりなおしてみるとかするといいかもしれない. - require('torch')でエラーする
- DLLへのパス通しがうまくいってないかもしれないです.BLASなど.PATHを見直してみるといいかもしれない.
- Dependency Walkerを使ってlibtorch.dllのトレースをしてみると原因がわかるかもしれない.だいたいFortranのせい.Dependency Walkerでの,windowsシステム系DLLのロードされないエラーは過検出らしい.
- torch.loadでtable index is nilとかいわれる
- バイナリ形式でシリアライズされているデータはマシン依存.
- torch.loadの第二引数に'b32', 'b64', 'ascii'のどれかを入れてみる.
- ファイルサイズが気にならないならば,配布版はasciiでシリアライズすべきだと思います.
- (私的TODO)
- luajit-rocks以下のsharedの場所が意図しない位置にできる?
- おそい
調査中… - mt.exe : general error c101008d: Failed to write the updated manifest to the resource of fileとかでる
- ウイルスセキュリティのせいなことが多いらしい.(参考)
- Error: Failed move: could not delete
- おそらく同上.
- 大きなバッチサイズをforwardすると落ちる
- 調査中…