OpenRestyとTorch7でREST APIを作る
前回の続きですが、Torchのインストール先を/usr/localに変更した都合上、Torchのインストールからやり直しています。 kakakazuma.hatenablog.com
Torchをインストール
今回はOpenRestyからTorchが見える必要があるので、ユーザーディレクトリ配下ではなく/usr/local配下にインストールします。
$ curl -s https://raw.githubusercontent.com/torch/ezinstall/master/install-deps | bash $ git clone https://github.com/torch/distro.git ~/torch --recursive $ cd ~/torch $ vi install.sh PREFIX="/usr/local" #Torchのインストール先を/usr/localに変更 $ sudo ./install.sh $ source ~/.bashrc $ th #Torchのコンソールが立ち上がる事を確認 $ cd /usr/local/lib $ sudo ln -s libluajit.so libluajit-5.1.so #OpenRestyから読み込む時用のLuaJitシンボリックリンク作成
OpenRestyをインストール
通常ではOpenRestyのインストール時にLuaJitもインストールされるのですが、今回はTorchのインストール時に入れたLuaJitを使うように指定してインストールします。
$ wget http://openresty.org/download/ngx_openresty-1.7.10.1.tar.gz $ tar xzvf ngx_openresty-1.7.10.1.tar.gz $ cd ngx_openresty-1.7.10.1 $ sudo apt-get install libreadline-dev libncurses5-dev libpcre3-dev libssl-dev perl make build-essential
ngx_openresty-1.7.10.1/configureを書き換え、コンパイル時にTorchの方のLuaJitを参照するようにします。かなり強引なやり方なので、もう少し綺麗なやり方があると思います。
#env LUAJIT_LIB => $lib;が記載されている2箇所の直後に以下を記述 print "export LUAJIT_LIB='/usr/local/lib'\n"; print "export LUAJIT_INC='/usr/local/include'\n"; $ENV{'LUAJIT_LIB'} = '/usr/local/lib'; $ENV{'LUAJIT_INC'} = '/usr/local/include';
$ ./configure --with-luajit=/usr/local $ make $ sudo make install $ ldd /usr/local/openresty/nginx/sbin/nginx|grep -i lua #LuaJitのパスが/usr/local/lib/libluajit.soになっていることを確認
/etc/init.d/nginxにhttps://gist.github.com/vdel26/8805927を配置
$ chmod a+x /etc/init.d/nginx $ sudo service nginx start
http://localhost/にアクセスできる事を確認
テストAPI作成
CaffeのImage Net学習済みモデルを使って、画像URLを渡すとImage Netでの分類結果を返してくれるAPIを作ってみます。
CUDA及びloadcaffeのインストール
CaffeのモデルをTorchで使うために、CUDAとloadcaffeパッケージをインストールします。
この辺は前回の記事を参照してください。
Vagrant上のUbuntuでTorch7のCPU開発環境を作る - サナギわさわさ.json
サンプルプログラムの配置
https://github.com/torch/tutorials/tree/master/7_imagenet_classificationのTutoroalをAPI化してみます。
以下の3つのファイルを配置し、sudo service nginx restart
を行います。
/usr/local/openresty/nginx/lua/init.lua
rocks = require "luarocks.loader" torch = require "torch" path = require 'paths' cjson = require 'cjson' require 'loadcaffe' require 'image' -- Loads the mapping from net outputs to human readable labels function load_synset() local file = io.open '/usr/local/openresty/nginx/lua/synset_words.txt' local list = {} while true do local line = file:read() if not line then break end table.insert(list, string.sub(line,11)) end return list end -- Setting up networks and downloading stuff if needed local synset_name = '/usr/local/openresty/nginx/lua/synset_words.txt' local proto_name = '/usr/local/openresty/nginx/lua/deploy.prototxt' local model_name = '/usr/local/openresty/nginx/lua/nin_imagenet.caffemodel' local img_mean_name = '/usr/local/openresty/nginx/lua/ilsvrc_2012_mean.t7' local synset_url = 'https://raw.githubusercontent.com/torch/tutorials/master/7_imagenet_classification/synset_words.txt' local prototxt_url = 'http://git.io/vIdRW' local model_url = 'https://www.dropbox.com/s/0cidxafrb2wuwxw/nin_imagenet.caffemodel' local img_mean_url = 'https://www.dropbox.com/s/p33rheie3xjx6eu/ilsvrc_2012_mean.t7' if not paths.filep(synset_name) then os.execute('wget '..synset_url..' -O '..synset_name) end if not paths.filep(proto_name) then os.execute('wget '..prototxt_url..' -O '..proto_name) end if not paths.filep(model_name) then os.execute('wget '..model_url..' -O '..model_name) end if not paths.filep(img_mean_name) then os.execute('wget '..img_mean_url..' -O '..img_mean_name) end -- Using network in network http://openreview.net/document/9b05a3bb-3a5e-49cb-91f7-0f482af65aea net = loadcaffe.load(proto_name, model_name) net.modules[#net.modules] = nil -- remove the top softmax -- as we want to classify, let's disable dropouts by enabling evaluation mode net:evaluate() img_mean = torch.load(img_mean_name).img_mean:transpose(3,1) synset_words = load_synset()
/usr/local/openresty/nginx/lua/classify.lua
-- Imagenet classification with Torch7 demo -- Converts an image from RGB to BGR format and subtracts mean function preprocess(im, img_mean) -- rescale the image local im3 = image.scale(im,224,224,'bilinear')*255 -- RGB2BGR local im4 = im3:clone() im4[{1,{},{}}] = im3[{3,{},{}}] im4[{3,{},{}}] = im3[{1,{},{}}] -- subtract imagenet mean return im4 - image.scale(img_mean, 224, 224, 'bilinear') end local image_url = 'http://upload.wikimedia.org/wikipedia/commons/e/e9/Goldfish3.jpg' local basepath = '/usr/local/openresty/nginx/lua/tmpimage/' local args = ngx.req.get_uri_args() for key, val in pairs(args) do if type(val) == "table" then val = table.concat(val, ", ") else end if key == "url" then image_url = val end end if image_url ~= "" then local tmp_image_name = basepath..ngx.now().."."..paths.extname(image_url) os.execute('wget '..image_url..' -O '..tmp_image_name) print '==> Loading image and imagenet mean' local im = image.load(tmp_image_name) print '==> Preprocessing' -- Have to resize and convert from RGB to BGR and subtract mean local I = preprocess(im, img_mean) -- Propagate through the network and sort outputs in decreasing order and show 5 best classes local score,classes = net:forward(I):view(-1):float():sort(true) local resArray = {} for i=1,5 do local resjson = {} resjson["class"] = synset_words[classes[i]] resjson["score"] = score[i] resArray[i] = resjson --ngx.say('predicted class '..tostring(i)..': ', synset_words[classes[i]]..' score : '..score[i]) end ngx.say(cjson.encode(resArray)) end
/usr/local/openresty/nginx/conf/nginx.conf
http { init_by_lua_file 'lua/init.lua'; server { location /classify { default_type 'text/plain'; content_by_lua_file "lua/classify.lua"; }
nginx再起動後、http://localhost/classify?url=任意の画像urlにアクセスし、 分類結果が以下のように帰って来ればOKです。うどんの画像をやってみたら以下のようになりました。
[ {"class":"soup bowl","score":45.646766662598}, {"class":"consomme","score":38.72269821167}, {"class":"caldron, cauldron","score":37.106609344482}, {"class":"hot pot, hotpot","score":36.95849609375}, {"class":"mixing bowl","score":36.407737731934} ]
以上です。Image Netの分類は少し使いにくいので、次回はファインチューニングしたいです。