Stimulator

機械学習とか好きな技術話とかエンジニア的な話とかを書く

dlibのSimple_Object_detectorを用いたPythonでの物体検出器の学習

- はじめに -

これはこの記事の続きで、dlibを使って物体検出をしようというものである。

まあ正確には、dlibには「顔検出器の学習」ってのは無くて「物体検出器の学習」の機能を使って、顔検出器の再学習がしたいという記事です。

dlibを使う際の参考になればよいです。


- dlibのObjectDetectorについて -

dlibに物体検出の学習が入ったのは2014年の時。
内部にはHoG+SVMを使っていて、OpenCVで学習する場合に比べて、遥かに少ない学習データで、かなりの精度を出す事ができる。

リリース時の本家記事 : dlib C++ Library: Dlib 18.6 released: Make your own object detector!

本記事では、Pythonのdlib apiを使って、物体検出器の学習を行っていく。

Python用のドキュメント : Classes — dlib documentation

dlib.simple_object_detectorを使う。
一応、こちらに公式の学習サンプルがある。

http://dlib.net/train_object_detector.py.html

大体の事は書いてあるけど、パラメータ等が全部書いてある訳ではないので、日本語訳してごにょごにょしたものをリポジトリに置いておいたので見て頂ければ。

github.com



- 学習形式とサンプル -

ディレクトリ内の画像と矩形情報が入ったテキストファイルを元に学習するスクリプトは以下。

#! /usr/bin/python
# -*- coding: utf-8 -*-
u"""rect.txtと画像データを用いてdlibを追加学習するスクリプト."""

import dlib
import os
from skimage import io

input_folder = "./test/"
rect_file = "./true_rect.txt"
output_svm = "detector.svm"

def get_rect(rect_file):
    u"""矩形ファイルを読み込みリスト化."""
    rect_list = []
    for line in open(rect_file, 'r'):
        rect_list.append(line)
    return rect_list


def make_train_data(rect_list):
    u"""矩形リストから学習用データを生成する."""
    boxes = []
    images = []
    for i, x in enumerate(rect_list):

        # 改行と空白を除去してリスト化
        x = x.replace('\n', '')
        x = x.replace('\r', '')
        one_data = x.split(' ')
        # 矩形の数k
        k = len(one_data) / 4

        # 矩形をdlib.rectangle形式でリスト化
        img_rect = []
        for j in range(k):
            left = int(one_data[j*4])
            top = int(one_data[j*4+1])
            right = int(one_data[j*4+2])
            bottom = int(one_data[j*4+3])
            img_rect.append(dlib.rectangle(left, top, right, bottom))

        # boxesに矩形リストをtupleにして追加
        # imagesにファイル情報を追加
        f_path = input_folder + one_data[k*4] + '.jpg'
        if os.path.exists(f_path):
            boxes.append(tuple(img_rect))
            images.append(io.imread(f_path))

    return boxes, images


def training(boxes, images):
    u"""学習するマン."""
    # simple_object_detectorの訓練用オプションを取ってくる
    options = dlib.simple_object_detector_training_options()
    # 左右対照に学習データを増やすならtrueで訓練(メモリを使う)
    options.add_left_right_image_flips = True
    # SVMを使ってるのでC値を設定する必要がある
    options.C = 5
    # スレッド数指定
    options.num_threads = 16
    # 学習途中の出力をするかどうか
    options.be_verbose = True
    # 学習許容範囲
    options.epsilon = 0.001
    # サンプルを増やす最大数(大きすぎるとメモリを使う)
    options.upsample_limit = 8
    # 矩形検出の最小窓サイズ(80*80=6400となる)
    options.detection_window_size = 6400

    # 学習してsvmファイルを保存
    print('train...')
    detector = dlib.train_simple_object_detector(images, boxes, options)
    detector.save(output_svm)


if __name__ == '__main__':
    rect_list = get_rect(rect_file)
    boxes, images = make_train_data(rect_list)
    training(boxes, images)


simple_object_detector_training内部でデータの増量を行っており、optionのupsample_limitとadd_left_right_image_flipsで調整できる。
データの増量では、基本的なData Augmentationが行われているため、学習用のデータは最小で良い。

実際、公式のサンプルコードでは、22枚のサンプル画像と矩形情報を学習用データセットとして、高い精度の顔検出器を作っている。

あまり画像を入れるとMemoryErrorの原因となる。
大体こんな感じで止まったら、Optionのパラメータ調整しなおすか、画像を減らすか、メモリを増やす必要がある。

Traceback (most recent call last):
  File "detector.py", line 104, in <module>
    boxes, images = make_train_data(rect_list)
  File "detector.py", line 70, in make_train_data
    images.append(io.imread(f_path))
  File "C:\Python27\lib\site-packages\skimage\io\_io.py", line 61, in imread
    img = call_plugin('imread', fname, plugin=plugin, **plugin_args)
  File "C:\Python27\lib\site-packages\skimage\io\manage_plugins.py", line 211, in call_plugin
    return func(*args, **kwargs)
  File "C:\Python27\lib\site-packages\skimage\io\_plugins\pil_plugin.py", line 37, in imread
    return pil_to_ndarray(im, dtype=dtype, img_num=img_num)
  File "C:\Python27\lib\site-packages\skimage\io\_plugins\pil_plugin.py", line 111, in pil_to_ndarray
    frame = np.array(frame, dtype=dtype)
MemoryError

dlibの公式Q&Aで「MemoryErrorって出るんだけど…」という質問に対して、作者が「Buy Memory!」と応えているくらいなので仕方ない。

感覚としては、32Gメモリ積んだマシンでも、100*100サイズの画像1000枚を、add_left_right_image_flips=true、upsample_limit=4とかで学習させたら落ちる。
CPUもフルに使うので最悪PCフリーズが有り得る。
学習データを減らすのが手っ取り早いが、対応できる環境が少なくなる。
マシンかパラメータでなんとかこうとかするのが良い。
(こういう点から、dlibの物体検出器学習クラスは背景や周りの環境が固定な場合超強いって感じする。)

64Gメモリ、16コアのCPUでも100*100の画像2000枚くらいが限界っぽい。
それ以上はパラメータ調整云々でもなんともならなかった。


学習用の矩形情報と画像情報はPythonコードで言うと以下のような形式で入力する。
boxes[n]とimages[n]が共通の情報となれば良い。

boxes_img1 = ([dlib.rectangle(left=329, top=78, right=437, bottom=186),
               dlib.rectangle(left=224, top=95, right=314, bottom=185),
               dlib.rectangle(left=125, top=65, right=214, bottom=155)])
boxes_img2 = ([dlib.rectangle(left=154, top=46, right=228, bottom=121),
               dlib.rectangle(left=266, top=280, right=328, bottom=342)])
boxes = [boxes_img1, boxes_img2]
images = [io.imread(dir_path + '/xxxxxx.jpg'),
          io.imread(dir_path + '/yyyyyy.jpg')]


学習に使うrect.txtは

x1 y1 x2 y2 file_name
x1 y1 x2 y2 file_name2

のような空白CSVっぽくなってる前提。
矩形が複数ある場合の1行は

x1 y1 x2 y2 x3 y3 x4 y4 file_name

といった形式で保存してあるものをパースしている。

いつかxmlにもする。
学習データ作って、xmlで学習させてる人は居たのでリンク貼っとく。



- 学習結果のsvmを使う -

前回の記事のdetector.runする部分を修正する。

- detector = dlib.get_frontal_face_detector()
+ detector = dlib.simple_object_detector("detector.svm")

- dets, scores, idx = detector.run(img_rgb, 0)
+ dets = detector(img_rgb, 0)

自前で学習した学習器はスコアや第二候補を返さないっぽい。



- テスト -

前回の記事Google Cloud Vision APIの記事で出したデータを元に学習させる。
例によって河村友歌ちゃんの顔画像でテストする。

f:id:vaaaaaanquish:20160902172432j:plain

はい、かわいい。


いつも顔検出ばかりやっていては仕方ないので、それっぽく猫の画像を学習させ適応する。
学習データは手動で矩形を出して、たった20枚作っただけ。

f:id:vaaaaaanquish:20160902172729j:plain

はい、かわいい。


- 考察 -

dlibの物体検出は内部でサンプリングもしてくれるので、正データとなる画像と矩形だけ集めれば良いし、精度も良いので結構良い。
パラメータも少なく物体検出できる方だと思う。
メモリとCPUはバカ食いするけど愛嬌がある。

v19.01現在で、追加学習のようなクラスはないため、既存のfrontal_faceのsvmファイルをsaveしてさらに学習とかはできない。
あと、メモリ少ないから学習データ小分けにして食わせようとかもできないのでつらい。
SVMなので仕方ない感じではあるが。

文中にも書いたけど、固定的な環境(監視カメラとか背景が固定とか)だと、手軽にかなり高い精度を実現できる。
それ以外ならOpenCVとかの検出器と組み合わせるか、CNNに突っ込んだ方が吉。


- おわりに -

dlib、Deep Learningとか強化学習とか新しい手法をガンガン積んでいってるし、期待したい。

まだまだかゆい所に手が届かないので「コントリビュータにさせてくれよ!」と思ったけどGithubリポジトリなかったのでつらい。

まあでも、やり取りするより1からオレオレで書いた方が早いなと思った。

ほんとそれなわかるアカデミア。



もろもろのコードはGithubリポジトリに入れといたんでよしなに。
github.com



- 追記 -

--09/04--


がんばります。

dlibを用いた顔検出器と物体検出器とその学習

- はじめに -

dlibのSimple_Object_Detectorクラスを使った物体検出用カスケードの学習をする記事。

dlibは機械学習ライブラリとして2006年から始まったプロジェクトで、基本的なSVMや線形アルゴリズム、Bayesian Network等に加えて、機械学習関係で用いるような画像処理ツールやグラフツールが付属している。

https://dlib.net

この素敵な図を見たことある人も居ると思う
https://dlib.net/ml_guide.svg


dlibは画像からの物体検出として顔検出を代表としたオブジェクト検出用のクラスが用意されている。
中身はHoG+SVMとシンプルな構成だが、矩形情報と正例画像を与えるだけで、学習用サンプルを生成し、HoGフィルターのパラメータ調整や学習を行ってくれるAPIがある。
C++Pythonをサポートしており、以下からはUbuntu 14.04デフォルトのPython 2.7とdlib v19.0を用いる。


- dlibのインストール -

Ubuntu環境へのインストールならこの記事が分かりやすいらしい。

一応コマンドだけ貼っておくので上から実行してけばUbuntu 14.04ならワンパン。

sudo apt-get update
sudo apt-get upgrade

sudo apt-get install python-setuptools
sudo apt-get install python-pip

sudo pip install numpy
sudo apt-get install liblapack-dev libatlas-base-dev gfortran g++
sudo pip install scipy
sudo pip install matplotlib

sudo wget http://downloads.sourceforge.net/project/opencvlibrary/opencv-unix/3.0.0/opencv-3.0.0.zip
sudo apt-get install unzip
sudo unzip opencv-3.0.0.zip
sudo rm opencv-3.0.0.zip

sudo apt-get install build-essential libgtk2.0-dev libjpeg-dev libtiff5-dev libjasper-dev libopenexr-dev cmake python-dev python-numpy python-tk libtbb-dev libeigen3-dev yasm libfaac-dev libopencore-amrnb-dev libopencore-amrwb-dev libtheora-dev libvorbis-dev libxvidcore-dev libx264-dev libqt4-dev libqt4-opengl-dev sphinx-common texlive-latex-extra libv4l-dev libdc1394-22-dev libavcodec-dev libavformat-dev libswscale-dev default-jdk ant libvtk5-qt4-dev

sudo apt-get install cmake
cd opencv-3.0.0
sudo cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local -D WITH_TBB=ON -D BUILD_NEW_PYTHON_SUPPORT=ON -D WITH_V4L=ON -D WITH_FFMPEG=OFF -D BUILD_opencv_python2=ON .
// ここでの16はコア数。自分のコア数はnprocコマンド辺りで確認
sudo make -j16
sudo make install

cd ..
sudo cp opencv-3.0.0/lib/cv2.so /usr/local/lib/python2.7/site-packages/

//-----------
下記シンボリックリンクを貼らないとopencvをimportする時エラーが出る
libdc1394 error: Failed to initialize libdc1394
http://stackoverflow.com/questions/29274638/opencv-libdc1394-error-failed-to-initialize-libdc1394
カメラとかのハードウェア用ドライバが邪魔するせいらしく、dlibやopencv使用自体に問題はない。
環境次第で再起動毎にシンボリックリンク貼り直す必要がある。具体的な解決策はまだないっぽい。
//-----------

sudo ln /dev/null /dev/raw1394

//-----------
python
import cv2 
でチェック
//-----------

sudo apt-get install git

sudo pip install cython
sudo pip install scikit-image

sudo apt-get install python-dev python-numpy
sudo apt-get install libboost-dev
sudo apt-get install libboost-python-dev
sudo apt-get install libboost-system-dev

sudo pip install dlib

//-----------
python
import dlib 
でチェック
//-----------


多分これでdlibのインストールは出来ると思う。Permission deniedとかはよしなに。


- 最もシンプルな顔検出機能を使う -

dlibは標準で顔検出用にfrontal_face_detector()を提供している。
こののブログの他記事でも述べているが、OpenCVの標準の顔検出カスケードよりは遥かに性能が良いと思う。

シンプルに顔検出器を使ってテストしたい場合は下のようなスクリプトで簡単にできる。

#! /usr/bin/python
# -*- coding: utf-8 -*-
u"""dlibによる顔画像検出."""
import cv2
import dlib

# 画像ファイルパスを指定
sample_img_path = 'sample.jpg'

def facedetector_dlib(img, image_path):
    try:
        detector = dlib.get_frontal_face_detector()
        # RGB変換 (opencv形式からskimage形式に変換)
        img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        # frontal_face_detectorクラスは矩形, スコア, サブ検出器の結果を返す
        dets, scores, idx = detector.run(img_rgb, 0)
        # 矩形の色
        color = (0, 0, 255)
        s = ''
        if len(dets) > 0:
            # 顔画像ありと判断された場合
            for i, rect in enumerate(dets):
                # detsが矩形, scoreはスコア、idxはサブ検出器の結果(0.0がメインで数が大きい程弱い)
                # print rect, scores[i], idx[i]
                cv2.rectangle(img, (rect.left(), rect.top()), (rect.right(), rect.bottom()), color, thickness=10)
                s += (str(rect.left()) + ' ' + str(rect.top()) + ' ' + str(rect.right()) + ' ' + str(rect.bottom()) + ' ')
            s += image_path
        # 矩形が書き込まれた画像とs = 'x1 y1 x2 y2 x1 y1 x2 y2 file_name'
        # 顔が無ければ s='' が返る
        return img, s
    except:
        # メモリエラーの時など
        return img, ""

if __name__ == '__main__':
    img = cv2.imread(sample_img_path)
    img, s = facedetector_dlib(img, sample_img_path)
    cv2.imwrite('output_' + sample_img_path, img)
    f = open('./rect.txt', 'w')
    f.write(s)
    f.close()

引数には画像とupsample_numの値を与えてやる。upsample_numは多分selective searchで見る枚数か回転拡大縮小で見る枚数を増やしているんだと思う。精度は向上するが、その分探索時間とメモリをめっちゃ使う。
返り値としては顔の矩形位置座標と各スコア、サブ検出器の結果が返ってくる。スコアは割と信用できるので使い勝手は良い。

Google APIの時と同じく例によって、画像をまとめてdlibの顔検出に投げるスクリプトを下記Gitリポジトリにまとめておいた。
いつかGoogleの方とまとめるかも。

https://github.com/vaaaaanquish/dlib_detection_python_script


- 一応検証 -

例のごとく適当にテスト
入力画像は最近コミケでのコスプレが可愛かった河村友歌ちゃん。
なんかもう顔検出系のデモをやる時は毎回お世話になってるナイスフリー素材。

入力
f:id:vaaaaaanquish:20160815033016j:plain

出力
f:id:vaaaaaanquish:20160815033035j:plain


はいかわいい。


- なんかおわりに -

なんかコード貼ったら長くなったので、Simple_Object_Detectorの学習に関する内容は次の記事に書いときます。

なんかごめん。


追記:書きました。
vaaaaaanquish.hatenablog.com

PythonでGoogle Cloud Visionを使った顔検出

- はじめに -

前回の記事でGoogle Cloud VisionAPIキーを発行しました。

そのAPIキーを使って、Pythonを使った顔検出(Face Detector)をやります。

顔認識じゃないです。顔検出です。


- Google APIのFACE_DETECTIONになげるやつ -

以下Pythonコードです。
変数を変更します。
api_keyに発行したAPIキーを入れます。
とりあえずこのスクリプトでSample.jpgから顔検出が出来ると思います。

#! /usr/bin/python
# -*- coding: utf-8 -*-
'''Gooogle Cloud Vision APIに画像を投げるやつ.'''

import base64
import cv2
from requests import Request, Session
import json

# ここにAPIキーを入れる
api_key = ''
# このスクリプトを単体で実行する場合はここにファイルパスを指定
sample_img_path = 'sample.jpg'
# 検出する顔の数の最大数 (多いほどレスポンスが返ってくるのが遅い)
max_results = 8
# DISCOVERY_URLは現時点(2016/08)でこれしかないのでこのまま
DISCOVERY_URL = 'https://vision.googleapis.com/v1/images:annotate?key='


# cv画像と画像ファイルへのPathと検出最大数が引数
def facedetector_gcv(img, image_path, max_results):
    # 通信不良等を考慮してTry...expectしておく
    try:
        # base64convert用に読み込む
        image = open(image_path, 'rb').read()

        # 顔を検出するやつのResponse作成
        str_headers = {'Content-Type': 'application/json'}
        batch_request = {'requests': [{'image': {'content': base64.b64encode(image)}, 'features': [{'type': 'FACE_DETECTION', 'maxResults': max_results, }]}]}

        # セッション作ってリクエストSend
        obj_session = Session()
        obj_request = Request("POST", DISCOVERY_URL + api_key, data=json.dumps(batch_request), headers=str_headers)
        obj_prepped = obj_session.prepare_request(obj_request)
        obj_response = obj_session.send(obj_prepped, verify=True, timeout=180)

        # Responseからjsonを抽出
        response_json = json.loads(obj_response.text)
        # 返り値用
        s = ''
        # 'faceAnnotations'があれば顔あり
        if 'faceAnnotations' in response_json['responses'][0]:
            faces = response_json['responses'][0]['faceAnnotations']

            # 画像情報
            s += image_path + ' '
            # OpenCVで矩形を書き込み
            for face in faces:
                # 0と2が両端の番地
                x = face['fdBoundingPoly']['vertices'][0]['x']
                y = face['fdBoundingPoly']['vertices'][0]['y']
                x2 = face['fdBoundingPoly']['vertices'][2]['x']
                y2 = face['fdBoundingPoly']['vertices'][2]['y']
                cv2.rectangle(img, (x, y), (x2, y2), (0, 0, 255), thickness=10)
                # 矩形情報を保存
                s += (str(x) + ' ' + str(y) + ' ' + str(x2) + ' ' + str(y2) + ' ')

        # 矩形が書き込まれた画像とs = 'file_name x1 y1 x2 y2'
        # 顔が無ければsは空
        return img, s

    except:
        return img, ""


if __name__ == '__main__':
    # 画像読み込み
    img = cv2.imread(sample_img_path)

    # Goog;e API
    img, s = facedetector_gcv(img, sample_img_path, max_results)

    # 画像出力
    cv2.imwrite('output_' + sample_img_path, img)

    # 矩形情報出力
    f = open('./rect.txt', 'w')
    f.write(s)
    f.close()

PEP8ガン無視で横に長くてスマン。


- テスト -

フリー画像で顔があって可愛いと言えば河村友歌ちゃんかなと思い用意しました。

これを入力にします。
f:id:vaaaaaanquish:20160808145629j:plain


こうなります。
f:id:vaaaaaanquish:20160808145702j:plain


複数の顔画像が入った写真もテストしてみます。
こんな感じでmax_resultsで指定した数までは顔検出できます。
f:id:vaaaaaanquish:20160808151204j:plain


- レスポンス -

レスポンスで返ってくるのは以下の通りです。

{
"boundingPoly": 顔の矩形位置(4頂点のx,y座標)

"fdBoundingPoly": 耳なども含めた顔の矩形位置(4頂点のx,y座標)

"landmarks": 以下のパーツの位置、三次元座標軸(x,y,z)
[
    "UNKNOWN_LANDMARK" : 謎
    "LEFT_EYE" : 左目
    "RIGHT_EYE" : 右目
    "LEFT_OF_LEFT_EYEBROW" : 左眉の左端
    "RIGHT_OF_LEFT_EYEBROW" : 左眉の右端
    "LEFT_OF_RIGHT_EYEBROW" : 右眉の左端
    "RIGHT_OF_RIGHT_EYEBROW" : 右眉の右端
    "MIDPOINT_BETWEEN_EYES" : 両目の中心
    "NOSE_TIP" : 鼻の頂点
    "UPPER_LIP" : 上唇
    "LOWER_LIP" : 下唇
    "MOUTH_LEFT" : 口全体の左端
    "MOUTH_RIGHT" : 口全体の右端
    "MOUTH_CENTER" : 口中央
    "NOSE_BOTTOM_RIGHT" : 鼻の下右側
    "NOSE_BOTTOM_LEFT" : 鼻の下左側
    "NOSE_BOTTOM_CENTER" : 鼻の下中央
    "LEFT_EYE_TOP_BOUNDARY" : 左目中央上境界
    "LEFT_EYE_RIGHT_CORNER" : 左目右ライン
    "LEFT_EYE_BOTTOM_BOUNDARY" : 左目中央下境界
    "LEFT_EYE_LEFT_CORNER" : 左目左ライン
    "LEFT_EYE_PUPIL" : 左目瞳
    "RIGHT_EYE_TOP_BOUNDARY" : 右目中央上境界
    "RIGHT_EYE_RIGHT_CORNER" : 右目右ライン
    "RIGHT_EYE_BOTTOM_BOUNDARY" : 右目中央下境界
    "RIGHT_EYE_LEFT_CORNER" : 右目左ライン
    "RIGHT_EYE_PUPIL" : 右目瞳
    "LEFT_EYEBROW_UPPER_MIDPOINT" : 左眉中間点座標
    "RIGHT_EYEBROW_UPPER_MIDPOINT" : 右眉中間点座標
    "LEFT_EAR_TRAGION" : 右耳
    "RIGHT_EAR_TRAGION" : 左耳
    "FOREHEAD_GLABELLA" : おでこ
    "CHIN_GNATHION" : アゴ、下顎下縁正中点
    "CHIN_LEFT_GONION" : アゴの左側
    "CHIN_RIGHT_GONION" : アゴの右側
]

"rollAngle" : 画像の回転角度
"panAngle" : 顔の左右回転角度,
"tiltAngle" : 顔の左右回転角度,
"detectionConfidence" : 顔検出信頼度
"landmarkingConfidence" : landmarksの信頼度

以下感情推定や状態推定
UNKNOWN	判定不能。
VERY_UNLIKELY, UNLIKELY, POSSIBLE, LIKELY, VERY_LIKELYで返ってくる。

"joyLikelihood" : 楽しそう
"sorrowLikelihood" : 悲しそう
"angerLikelihood" : 怒ってそう
"surpriseLikelihood" : 驚いてそう
"underExposedLikelihood" : 肌を露出してそう
"blurredLikelihood" : ぼやけてそう
"headwearLikelihood" : 帽子とか付けてそう
}

返ってきすぎ。
感情推定や状態推定はちょっと精度悪くてあんまり信用できないです。
ただ、横顔や顔が途切れてても結構返ってくるのですごい精度です。
後Landmarkが返ってきたりするのも学習用データ作りが捗りますね。


- 精度に関する知見 -

OpenCVとdlibで顔検出をおこなった場合の知見を以前まとめました。

OpenCVは無駄な矩形を多く出力して、recallを上げてくるイメージです。
OpenCVでは、入力に顔画像の最小サイズを指定できますが、結局そのパラメータを調整すると顔がそのサイズ以下になった時に検出できないのでう~んという感じです。
ただ、追加の学習が非常に簡単に行えるので、精度向上を測るのはやりやすいですね。
Anime_Faceを代表に、色んな状況での顔検出に最適化する事ができます。

対してdlibは、デフォで顔検出に関してとても精度が高いです。
ただ、中身にHoGSVMを使っている事もあり、顔っぽい文字やロゴを検出してしまうのがネックです。
また、オブジェクト検出用の学習も出来るには出来るんですが、学習用の関数がまだまだ使いにくい事もネックですね。

対して、Google Cloud Visionは追加での学習こそ出来ませんが、ものすごい精度で検出してきます。
しかも無駄な矩形をほとんど返さないのが魅力的です。
デフォルトの検出器を適当にprecisionで言うなら OpenCVが0.3、dlibが0.6、Googleは0.98という感じです。
しかも状態情報もくれるのでサイコー。


- 料金体系 -

最後に料金体系はこんな感じです。

- ~1000枚 ~100万枚 ~500万枚 ~2000万枚
1000枚毎に Free $2.50 $1.50 $0.6
限度枚数投げると Free $2497.5 $8496.0 $16895.4

限度枚数投げるとというのは、例えば100万枚投げた場合、最初の1000枚はFreeの価格が適応されるので無料。その後の999000枚は$2.5/1000枚かかるので総額が

F + 999000 * 2.5 / 1000 = $2497.5

という感じです。その後も同様に計算して

F + $2497.5 + (3999000 * 1.50 / 1000) = $2497.5 + $5998.5 = $8496.0
F + $2497.5 + (3999000 * 1.50 / 1000) + (13999000 * 0.6 /1000)
                   = $8496.0 + $8399.4 = $16895.4

日本円にすると2000万枚投げたら大体180万円くらいですね。

ただ、今は初期登録で60日間使える$300を貰えるので、無料で$2.5/1000枚とFree分合わせて12万と1000枚投げられます。
12万枚かなりの精度で色んな情報が返ってくると思うと、課金まで行かなくても全然使えますね。

後、これ以上投げる場合の金額は相談してねって感じみたいです。

Googleの公式の金額のページはここ。相談先もここ。
https://cloud.google.com/vision/docs/pricing

他にもラベル検出(LABEL_DETECTION)、文字検出+OCR(TEXT_DETECTION)、 有害コンテンツ検出(SAFE_SEARCH_DETECTION)、各物体のランドマーク検出(LANDMARK_DETECTION)、ロゴマーク検出(LOGO_DETECTION)、色解析(IMAGE_PROPERTIES)が使えて、それぞれ大体同じ金額になると思います。
何故かラベル出すやつだけ高いです。企業にお金でも払ってるんでしょうか。


- おわりに -

人工知能だので画像認識系の機械学習技術が話題という事もあり、機械学習APIがポツポツ出てきてますが、精度や安定性はやっぱりGoogleには勝てないですね。
中身はDeep Learning、R-CNN辺りでしょうか。
1レスポンスで返ってくる辺り、感情推定なんかも同じネットワーク内でやってそうですね。
すごいぞGoogle。流石だGoogle
「とりあえず顔検出用の学習データが欲しい」という時に無料枠でとりあえず使えますね。
そのデータを使いながら、環境に対して最適化していけば良いと思います。
あと、他のAPIも使う記事を気が向けば書きます

一応フォルダ内の画像を全部Google APIに投げて「顔画像あり」「顔画像なし」に分けて矩形情報を保存するスクリプトを書きました。
上部の画像単体を投げるスクリプトも一緒にGitリポジトリに入れてますので以下参照。

github.com

Google Cloud Visionを登録しよう

- はじめに -

Google Cloud Visionがすごい。とにかくすごい。
クレジットカードが無いと利用出来ないのはちょっとネックだけど。
でも今なら$300分を無料で使えるし、機械学習データ作る用にでも是非。


- APIキー発行まで -

Google Cloud PlatformのコンソールにGoogleアカウントでログインする。

https://console.cloud.google.com/home/dashboard

初回にクレジットカード情報を登録しろと通知が来た場合は、素直に登録する。

先述の通りクレジットカードを登録して課金を有効にしないとGoogle Cloud Visionは使えない。無料枠はあれど、課金を有効にしないとダメ。
なので実質クレカ必須。

通知が出なかった場合は、課金を有効にするため以下をクリック。
f:id:vaaaaaanquish:20160808124313p:plain

Google Cloud Platform の無料試用、利用規約に同意して続行。
個人情報とクレジットカード情報を入力して続行。
f:id:vaaaaaanquish:20160808124904p:plain
割りと丁寧にこんな通知が出る。


Google Cloud Platformはプロジェクト単位で管理ができる。
最初に新しいプロジェクトを作成する。
上部ヘッダー、左上から「プロジェクト」>「プロジェクトの作成」をクリック。
f:id:vaaaaaanquish:20160808125407p:plain


名前を入力しろとダイアログが出るので、自分の管理しやすい適当な名前を入力して「作成」。
右上の通知マークがくるくる回っている間は作成中なので待つ。
大体3分くらいで作成される。カップ麺と同じ。
f:id:vaaaaaanquish:20160808125730p:plain


できたら今度は左上のプロジェクトが変わる。もしくはクリックして変える。
基本はここにプロジェクト名が出てるので、自分の作成した設定なんか違うな~と思ったらまずプロジェクト名を見る。
f:id:vaaaaaanquish:20160808125933p:plain


プロジェクト名を確認しつつ、「APIを利用する」をクリック。
f:id:vaaaaaanquish:20160808134024p:plain


API Managerなるページに飛ばされるので、「+ APIを有効にする」をクリック。
f:id:vaaaaaanquish:20160808134302p:plain


色んなAPIの名前が出てくるけど、Google Cloud Visionは多分見当たらないと思うので検索ボックスに「Google Cloud Vision」と入力する。出てきたらGoogle Cloud Visionをクリック。
f:id:vaaaaaanquish:20160808134724p:plain


こんな感じの画面に飛べば正解。
まずはじめに「▶有効にする」をクリック。
f:id:vaaaaaanquish:20160808140632p:plain


こうなると思うので「認証情報に進む」
左側の認証情報からでも設定できるっちゃできる。
f:id:vaaaaaanquish:20160808135413p:plain


次の記事でPythonからAPIキーを叩いて画像認識をしたいと思うので、とりあえずブラウザキーを発行する。
「ウェブブラウザ(JavaScript)」こ表記はなかなか不親切だがこれでOK。
他の形式で欲しい人は他で。
f:id:vaaaaaanquish:20160808135849p:plain


次にAPIにつける名前を適当に考えて入力する。
HTTPリファラは、個人で使う分には必要なし。会社で使うならそのドメインを入力した方が良さ。
APIキーを作成する」。
f:id:vaaaaaanquish:20160808140104p:plain


そしたらもうAPIキーが出てくるのでおしまい。「完了」
f:id:vaaaaaanquish:20160808140241p:plain


最後に出るAPIキーをコピペしておいても良いし、画面左の認証情報からいつでも参照できる。


- おわりに -

次はPythonを使ってAPIを叩く。
いざ画像処理へ。

次の記事では、Pythonによる顔検出(Face Detection)とその知見と料金体系辺りを書いています。

OpenCVとdlibとOpenFaceでの顔検出と知見まとめ

- はじめに -

色々あって顔検出をする機会があった。世の中、顔認識(Face Recognition,Facial Recognition)と顔検出(face detection)がごっちゃになってるじゃねえかと思いつつ、とにかく画像から人の顔を高精度で出したいんじゃという話。

先に結論を言うと、OpenCVよりはdlibの方がやっぱり精度良くて、OpenFaceも使って動かしたんだけどそんな変わらないし、でもまあ先はあるよって話。
OpenFaceで顔検出と言っても、実際にはdlib(もしくはOpenCV)の顔検出とTorch7のCNNを接続するフレームワークのようなもので何でこれが話題になった時期があったのかという感じがある。

環境はUbuntu 14.04を想定。

- インストールして動かすかれこれ -

まずは必要な物をインストールする。

厳しい環境に身を置いているので、最初WindowsOpenCVとdlibを動かした。Windowsでもインストーラとpipが使えれば出来るので奇跡的に出来た。時代は変わった。
OpenFaceはLinux, Unix環境しか対応してないとの事だったので、仕方なく仮想環境を作りインストールした。

環境は Ubuntu 16.04 LTS
必要なものは以下の通り

- python (Ubuntu デフォルトで可)
- python pip
- numpy
- scipy

- torch7 (機械学習, Deep Learningライブラリ)

- opencv
 - 必要な周辺ライブラリ沢山
 - cmake

- dlib
 - boost
 - boost-python
 - scikit-image

- openface

多い。OpenFaceの公式Setupには「Docker用意したから使ってくれよな」とあったけど、「"tested in Ubuntu 14.04 and OSX 10.10"」とも書いてあるし、OpenCVは先月3.x系が出てるしでダメそうだったので全部手動で入れた。インストール前にこの辺を読んで遺産感があるのに気付くべきだった。


- インストール作業 -

pythonUbuntuデフォルトで充分。一応バージョンを確認するけど大抵2.7系だと思う。

python -V

一応定番apt-getのアップデートをしておく

sudo apt-get update
sudo apt-get upgrade

パッケージ管理はやっぱりeasy_installとpip

sudo apt-get install python-setuptools
sudo apt-get install python-pip

pipを入れたらまずはこの三種の神器

pip install numpy
sudo apt-get install liblapack-dev libatlas-base-dev gfortran g++
pip install scipy
pip install matplotlib

先にOpenCVから導入。思い思いのディレクトリで。

wget http://downloads.sourceforge.net/project/opencvlibrary/opencv-unix/3.0.0/opencv-3.0.0.zip
sudo apt-get install unzip
unzip opencv-3.0.0.zip

必要な周辺ライブラリを全載せ

sudo apt-get install build-essential libgtk2.0-dev libjpeg-dev libtiff5-dev libjasper-dev libopenexr-dev cmake python-dev python-numpy python-tk libtbb-dev libeigen3-dev yasm libfaac-dev libopencore-amrnb-dev libopencore-amrwb-dev libtheora-dev libvorbis-dev libxvidcore-dev libx264-dev libqt4-dev libqt4-opengl-dev sphinx-common texlive-latex-extra libv4l-dev libdc1394-22-dev libavcodec-dev libavformat-dev libswscale-dev default-jdk ant libvtk5-qt4-dev

libtiffはUbuntu 14.04辺りからlibtiff5推奨になったらしいlibtiff4-devはエラーが出た。

cmakeを準備しつつコンパイル

sudo apt-get install cmake
cd opencv-3.0.0
cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local -D WITH_TBB=ON -D BUILD_NEW_PYTHON_SUPPORT=ON -D WITH_V4L=ON -D WITH_FFMPEG=OFF -D BUILD_opencv_python2=ON .
make -j1
sudo make install

make -jの後は自分のコア数を入れる。
Linuxだと一番簡単なコマンドは多分 nproc と打つ事。
今回は仮想環境なので1。

python用の設定はopencvのlib内にあるcv2.soというファイルをsite-packages以下に入れるだけ。GUIでやってもOK。

cp ~/ path to opencv /opencv-3.0.0/lib/cv2.so /usr/local/lib/python2.7/site-packages/

Linux系だとcv2用にリンクを貼っておく必要がある。
でないと" libdc1394 error: Failed to initialize libdc1394 "といったErrorが出る、

sudo ln /dev/null /dev/raw1394

python起動してimportできるか確認しておく

python
>>> import cv2
>>> cv2.__version__
'3.0.0'
>>> exit()

次はdlib。
dlibは前提としてgitが要るので先にそちらを準備しておく。

sudo apt-get install git

加えて前提として必要なskimageとライブラリ

sudo pip install cython
sudo pip install scikit-image

確かdlibはpipで入れれば下記作業不要だけど、結局OpenFaceを入れる前にTorch7が必要で、その前提としてboostが必要みたいな感じで入れた気がする(曖昧)。
多分pipで入れても" Could NOT find Boost "なるエラーが出ると思うのでやったら吉。

sudo apt-get install python-dev python-numpy
sudo apt-get install libboost-dev
sudo apt-get install libboost-python-dev
sudo apt-get install libboost-system-dev

boost.numpyのインストールは他に作業が必要だけど今回は無用
一応やりたい人向け参考URL(http://ttlg.hateblo.jp/entry/2015/12/17/124747

多分これでdlibは入る(Windowsもboostインストーラとpipが動くのでここまではできる)

sudo pip install dlib

python起動してimportできるか確認しておく

python
>>> import dlib
>>> dlib.__version__
'18.17.100'
>>> exit()

次にTorch7を入れる。

git clone https://github.com/torch/distro.git ~/torch --recursive
cd ~/torch
sudo dpkg --configure -a
bash install-deps
./install.sh

インストールの文字が最後に
Do you want to automatically prepend the Torch install location to PATH and LD_LIBRARY_PATH
と聞いてくるので yes と入力するると .bashrcにexportが追記される。
一応 source ~/.bashrc するかbashを再起動しておく。

witch thでpathが出て来ればOK。出てこない場合はPathを設定する。
~/.bashrcを開いて

export PATH=~/torch/bin:$PATH;
export LD_LIBRARY_PATH=~/torch/lib:$LD_LIBRARY_PATH;

を追記する。

Torchにはluaのパッケージが幾つか用意されていて、今回のサンプルを動かすのに必要な物があるのでインストールしておく。

luarocks install nn
luarocks install dpnn
luacocks install optim
luarocks install csvigo

lualocks installでインストール推奨とされているのは以下があるけど、ディープラーニングのトレーニングやCUDAを使う場合のみなのでインストールしてない。
必要そうだと判断したらインストール
cutorch and cunn (only with CUDA)
fblualib (only for training a DNN)
tds (only for training a DNN)
torchx (only for training a DNN)
optnet (optional, only for training a DNN)

上のやつを全乗せしたい時

for NAME in dpnn nn optim optnet csvigo cutorch cunn fblualib torchx tds; do luarocks install $NAME; done

Torch7で使うluarocksだと、無難に以下をインストールしておくと良い。

luarocks install image
luarocks install nngraph

やっとOpenFace

git clone https://github.com/cmusatyalab/openface ~/openface --recursive
cd ~/openface
sudo python setup.py install

無事通れば終わり


- サンプルを動かす -

サンプルとして提示されてるのは以下のcompare.py
https://github.com/cmusatyalab/openface/blob/master/demos/compare.py

まず前提としてdlibの顔検出用のlandmarks.datファイルを用意する
自前で用意しなくても、modelsディレクトリにスクリプトが入っているので基本はそれで。

cd ~/openface/models
./get-models.sh

~/openface/の中に好きなsample.jpgとsample2.jpgを突っ込んで

python demos/compare.py ./sample.jpg ./sample2.jpg --verbose

って感じで入力して実行。--verboseでは動作詳細を見れる。

サンプルは「顔の場所を検出してcropして学習済みネットワークで特徴量化しそのベクトルのL2距離を比較する」というもの。
本家は「"OpenCV or dlib"」と言っているが、この推奨サンプルではcv2の画像読み込みでロードして顔検出精度が高いdlibで顔検出しているので「dlibもOpenCVも結局どっちも要るじゃねえか!」となる。

なんかそれっぽい数値が出たら終わり
正直もっと良いサンプルあるやろと思う。


- 感想 -

OpenFaceだけど、Deep Netなら他の便利なフレームワークがある印象が強い。dlibやOpenCVと簡易的に接続できると言っても、最近は他が簡易的になったし。
APIOpenFace API Documentation — OpenFace API Docs 0.1.1 documentation)を見てもそんな便利な機能があるわけではない。

Deep LearningライブラリとdlibやOpenCVが個別で扱える事のメリットの方が大きいかなと思った次第。

Torch7で顔の分類や回帰を行っている。その学習済ネットワークがある、または入手できる。といった状況以外ではあまり使わなそう。
海外だとTorch7結構流行ってるのかなと思ったところで終わり。正直chainerやkerasが良いと思うよ正直。

記事を書くなら精度検証しろよとも思うけど、やっぱ所感でdlibが良い程度しか言う事がない。
dlibはHoG+SVMを使っているらしく、OpenCVはHaar-Like+Adaboostを使った物体検出系のカスケードらしい。
どちらもデフォルトだとSliding Windowやってるみたいで、dlibの方はSelective Searchとかも使えるけど顔検出だと微妙なのではと思う。
dlibの方が精度が良いのは目視した感じ確かだけど、Window走査の時間設定のパラメータ(upsample_num_times)を最小の0以外にすると処理時間が長く、2以上だと+メモリが必要だった。適当な仮想環境では落ちる程度に重かった。あと2以上にしてもそんな大胆に精度改善が見込める訳ではない。
OpenCVの方が遥かに軽いし、OpenCVでググってると「ここに乗ってるAdaboostを使った検出器はクソ早いし最高なんだ!Viola & Johnsが作ったスゲーやつなんだ!」と書いてあったりする。OpenCVはデフォルトのカスケードファイルがいくつかあって、顔だけじゃなく、目や耳用があったりする(全て実験してる人の記事:OpenCV 使用可能なCascadeClassifierの種類と効果 - Symfoware)。その辺は有効活用できそう。追加での学習もできるっぽい。

顔認識本当にやるならOpenCVとdlibを組み合わせてある程度データを作り、ちゃんと特徴量と学習器作ったりDeep Netにぶち込むのが良いんじゃないかと思った。

もう少しdlib使ってみたら日本語の記事増やしたいので書くかも。後はGoogle Cloud APIの顔認識も気になってはいる。

あと、「顔検出」とかでググるとオタクがとりあえずアニメ画像認識やってみたって感じのブログが山のように出てくるので日本語でググるよりは英語を乗り越えて公式のドキュメント理解に努めたほうが良いかも。

Active Object Localization with Deep Reinforcementを作っている

- はじめに -


この記事はDeep Learning Advent Calendar 2015の24日目の記事です.


Deep Q-Network(DQN)がNIPSで発表されてから*1はや2年.

DQNは, 深層強化学習として一分野を確立し, 機械学習分野自体の活発さ, Deep Learningの話題性も相まって, 怒涛の勢いで新しい研究成果が発表されています.*2

この記事では, DQNを物体認識タスクに応用したActive Object Localization with Deep Reinforcement Learning[ PDF ]について解説, 実装を行っていこうと思います.*3

(12/24現在まだ思うように実装が出来てませんすいません)

 

- 背景 -

そもそも物体認識タスクとは, 画像から特定の物体を検出するタスクの事を指します.

例えば, 私が昨年kazoo04 Advent Calendarで行ったものがそれですね.
この時の記事では, 画像の中からkazoo04という特定の物体(人物)を検出しています.

この記事で私は, kazoo04かどうか分類する学習器にかける前に, 様々な大きさの窓をスライドし, その枠内を入力としています(sliding window, Exhaustive Search).
この手法は, かなり以前から物体認識タスクで多く用いられていましたが, 元の画像サイズや窓を移動させる幅によって, かなりの計算時間がかかってしまう問題がありました.

またConvolutional Neural Networks(CNN)という画像認識に強いDeep Learningの手法が流行し, 学習器の性能が格段に向上しました.
過去のブログの記事にもしていますが, それまでSHIFTやHOGのような特徴量抽出を挟む事で実現していた認識処理を学習器1つで行えるようになりました.

こうのように機械学習による画像認識の精度が高まって行く中で, Exhaustive Searchで計算時間を使っていてはリアルタイム認識なんかは無理だよねという流れが出てきました. また, sliding windowのスケールの違いによって入力も違うため, 誤認識が発生するという問題もありました.

そこで, Exhaustive Searchのスケールによる誤認識を減らすための画像処理手法や計算量を減らす手法*4, 物体検出に対して効率的な手法*5が出てきたり, CNN以外でもsliding windowの欠点を補うようなRandom Forest的手法*6が出てきたりしました.


そして, 次の大きな成果としてR-CNNというモデルが現れました. R-CNN*7はGirshickらが2014年に提案した手法で, 先に物体が入る窓を推定*8し, その窓(window, bounding box)を入力としています.

CNN部分では, その入力を分類する学習に加えて, そのbox自体を矩形回帰するように学習させる事で, 物体の場所検出とその分類を同時に行う事ができるようになります.
分類と回帰を同じ学習器でも行うという点でも, CNN, ニューラルネットワークの強みを活かした手法です.

R-CNNは当初, ネットワークの大きさ等から認識に時間がかかるという問題がありましたが, Fast R-CNN*9のような改良手法が提案され, 今回用いてるPascal VOC(http://host.robots.ox.ac.uk/pascal/VOC/)というデータセットにおいてもかなりの結果を出しています.

近年では上記のようなCNNで抽出した特徴量をRNNにつなげることで, センテンス表現の学習を行う手法(Image Captioning*10 )等も発表されています.


今回のDQNを用いた手法は, 今までの趣向とは少し異なっており, トップダウンな探索によって物体の位置を検出するアルゴリズムになっています.
f:id:vaaaaaanquish:20151225005241p:plain

最初に画像の大きな領域を入力とし, 動的にその入力領域を変化させていきます. 動的な探索において強化学習(Q-learning)な技術が使われています.
また, マルコフ決定過程(MDP)に基づいた動的探索ステップを複数回行う事によって, その回数だけ複数の物体を検出する事も可能にしています.

 

- Q-learningな部分 -

一般的なQ学習の要領を用います. 以下に行動と報酬を示します.

  • 行動

行動は8つのActionと終端条件(trigger)に分かれています.
Actionは, 入力範囲の上下左右の移動と拡大縮小です.
f:id:vaaaaaanquish:20151225000420p:plain

また, 全てのActionは以下の2式によって制御することができます.

{
\alpha_{w}=\alpha * (x_{2}-x_{1})
}
{
\alpha_{h}=\alpha * (y_{2}-y_{1})
}

{\alpha}は幅を制御するパラメータで, 論文中では{\alpha=0.2}として固定の値を用いています.
{\alpha}が大きければ探索が雑になり, 小さければ時間がかかるという事が感覚的にもわかると思います.

また, Triggerは1つの探索終了を示します.
inhibition-of-return(IoR)*11を参考に, 探索が終了した時点で, box内に十字のマークを挿入します. これは, 次の探索で同じ領域がゴールになることを防ぐためです.
f:id:vaaaaaanquish:20151225000813p:plain
報酬の定義を基に複数回繰り返す事で, 複数の物体を認識する事を可能にしています.

  • 報酬

報酬関数にはIoU(Intersection-over-Union)を用います. IoUは, boxである{b}(box)に対して, 目的となる領域{g}(ground truth box)がどれだけ含まれているかとなります.

{IoU(b,g)=area(b \cap g)/area(b \cup g)}

IoUを用いて, 状態{s}において行動{a}を行って状態{s^{'}}に遷移する時の報酬関数{R}は, 以下のように定義されます.

{R_{a}(s,s^{'})=sign(IoU(b^{'},g)-IoU(b,g)) }

ある状態のIoUから次の状態へのIoUの差ですね.
またこの値は正負がbinaryで制御されます.

{\\
R_{\omega}(s,s^{'}) = \cases{
\eta & if IoU(b,g)\(\leq \tau\) \cr
\\-\eta & otherwise \cr
}
}

しきい値{\tau}を超えていれば{+\eta}, 無ければ{\\-\eta}の報酬という形です.
論文中では{\eta=3.0, \tau=0.6}に設定されています. {\eta, \tau}は経験則によるものが大きく, {\tau}はデカすぎるとなかなか達成できないので0.6という感じみたいです.

  • 学習手法

学習では, パラメータを初期化した後, 目標となる{g}に対して率直に+-で進むよう行動を選択していきます.複数目的があった場合は, 内1つがランダムに選択されます.

しかし, すべて貪欲に動いていれは汎化性能が上がらないため, 全てのtraining画像に対して学習を終えた後, ε-greedy法を用いた学習を行い探索します.

論文中では, ε-greedyなTrainingを15epoch分回しますが, 最初の5epochで{ε}を1~0.1に線形に下がるよう設定しているようです.

また, boxのスタート地点は4隅から, 全体の75%のサイズで始めます.

 

- CNNな部分 -

CNNのネットワーク構成は, 以下のようになっています.
f:id:vaaaaaanquish:20151225001924p:plain

実際にQ-learningを適応しているのは後ろ3層のみです.
これについては論文中でも言及されており, 前層のpre-trainingによって学習収束速度が向上すること, 全体を学習するにはさらに大きなデータセットが必要と考えられる事などから, 今後の研究課題であるとされています.
ちなみにpre-training層は分類器としてVOCデータセット学習したCNNの特徴抽出部分を用いています.

入力は224*224に正規化された画像のベクトル.
出力はActionとTriggerを含む9ユニットです.
出力で強化学習におけるQtableを再現するイメージです.

NNの部分は誤差逆伝搬法(back propagation)による最適化, Dropoutによる正則化を用いています.

また, 過去10Actionをbinary形式で保存したaction historyと呼ばれるユニットをQ-Network以前の層に挿入しています.
これにより短期的に良い行動を学習する事が可能となり, 精度にして3%前後の向上が見られるようです.


Deep Q-Networkの学習機構については, 日本語であれば次の記事が分かりやすいかと思います.
DQNの生い立ち + Deep Q-NetworkをChainerで書いた - Qiita

 

- 実装 -

こんな感じなので少しまって

*1:Playing Atari with Deep Reinforcement Learning - http://arxiv.org/pdf/1312.5602.pdf

*2:自分もここ1ヶ月くらいで本腰入れて調査した程度なので詳しくはないです

*3:この記事で用いてる画像は論文中から引用したものです

*4:http://www.kyb.mpg.de/fileadmin/user_upload/files/publications/pdfs/pdf5070.pdf

*5:http://www.cv-foundation.org/openaccess/content_cvpr_2015/papers/Gonzalez-Garcia_An_Active_Search_2015_CVPR_paper.pdf

*6:http://www.habe-lab.org/habe/pdf/2011SSII_SIHF.pdf

*7:http://arxiv.org/abs/1311.2524

*8:bjectness. どちらかというとコンピュータビジョンな技術が多い. Selective Search(最初のR-CNN), BING等様々な手法がある

*9:http://arxiv.org/abs/1504.08083

*10:Deep Visual-Semantic Alignments for Generating Image Descriptions, http://cs.stanford.edu/people/karpathy/deepimagesent/

*11:http://www.cnbc.cmu.edu/~tai/readings/tom/itti_attention.pdf

DQN-chainerリポジトリを動かすだけ

- はじめに -

こんにちは. @vaaaaanquishです.
この記事はChainer Advent Calender 2015 11日目の記事です.

今回は滑り込みで, 特に何か新しい事をやった訳でもないですが,
一応記事にする事に意味があると思うので投稿しておきます.

今回はただこれを動かすだけです.

完全に備忘録みたいな感じです. ご了承下さい.
多分誰でもできると思います.


- Deep Q-Networkとは -

Ugo-Nama氏の記事は非常に分かりやすく, Deep Q-Networkに至るまでの研究遷移も書かれており, かなり参考になると思います. ここでは一応の概略のみ記述しておきます.

Deep Q-Networkは強化学習(Reinforcement Learning)の一種であるQ学習(Q-learning)に対して, ニューラルネットワーク(Neural Networks)を多層化したDeep Learningを適応したものです.

まずQ学習とは, 「ある状態 sに対する最も良い行動 aの組み合わせ」となるテーブル関数 Q^{*}(s, a)を求める手法です.  Q^{*}(s, a)を作成するため, 様々な状態のエピソードを試行し,  Q(s, a)を更新します.
 Q(s, a) Q^{*}(s, a)に近付ける際は, 以下の様な更新式を用います.

 {\displaystyle
Q(s, a) ← Q(s, a) + \alpha (r + \gamma \  \max_{a^{'}} Q^{'}(s, a)- Q(s, a))
}

 Q^{*}(s,a)は直接的には得られないので, テーブル関数を用いた Q^{'}(s, a)を用います.
 Q^{'}(s, a)は状態sにおいて行動 aをした次のステップでのQ値です.
また,  \gammaは強化信号の割引率,  \alphaは学習を収束させるための学習率となるパラーメタです.

次にDeep Learningに代表されるニューラルネットワークのような手法では, 最適化の際に勾配法を用いるのが一般的です. 教師データを入力し, パラメータ \thetaを持つ学習器の出力と教師の誤差 L_{\theta}を用いて, 以下のように更新していきます.


\theta ← \theta - \alpha \nabla_{\theta} L_{\theta}

誤差 Lをパラメータ \thetaについて微分し, 足し合わせる事で更新する一般的な式です.
ここで誤差 L_{\theta}に対して強化学習における Q(s, a)を適応した場合, 以下のようになります.


\theta ← \theta + \alpha ( r + \gamma \  \max_{a^{'}} Q^{'}(s, a)- Q(s, a) ) \nabla_{\theta} Q_{\theta}(s,a)

前述した通り強化学習では Q^{*}(s,a)を事前に得る事はできないため, 教師信号として Q^{'}(s, a)を使っている形です. ニューラルネットワーク等におけるback propagationの各層の更新式と見比べたりすると分かりやすいかもしれません.

Deep Learningには様々な手法がありますが, 関数近似手法として上記のように強化学習の枠組みに適応する事で, 複雑で高次元な問題設定に対応する事が可能になりつつあります.


- DQN-chainer実行環境をつくる -

私の環境

  • OS : Ubuntu Server 14.04 LTS (GUI欲しいのでXが入っています.)
  • GPU : NVIDIA Tesla K40 (ラボのマシンです.)
  • Driver : CUDA 6.5 (訳あって6.5なだけで7.0以降でも大丈夫だと思います.)
  • Python : 2.7.6

既にインストールされているものもありますが, できる限り書いていきます.

python, numpy, scipy

言わずと知れた言語と数値計算ライブラリです.
私はPythonUbuntuデフォルトのものを用いています. バージョンは2.7.6です.
numpy, scipyは今後の事を考えるとpipでインストールする方が良いかと思います.
私の環境は研究室の他のメンバーが既にapt-getで入れてますがそれでも大丈夫です.

sudo apt-get install python2.7
sudo apt-get install python-numpy
sudo apt-get install python-scipy

 

・chainer

Deep Learning等をサポートする機械学習ライブラリです. 言わずもがなです. いつも使っています.

入ってない人はpipを導入後, インストールします.

sudo -y apt-get install python-pip
pip install chainer

私は入ってるのでアップデートのオプション"-U"をつけて

pip install -U chainer 

でアップデートしておきます.

よくGPUを利用するためにchainer-cuda-depsを別途インストールするような記載がありますが,
chainer 1.5版以降にすれば別途インストールの必要ないようです.

(chainerを最新版にし, import chainer出来るかどうかテストしておくと良いです.
またCUDAにパスが通り, GPUを操作できるか確認しておきます.
今回動かすリポジトリGPUを用いているため, 少し書き換えないとSegmentation Fault等になります.)

・RL-Glue

強化学習で言う所のエージェントと環境を接続するパッケージです.
RL-Glue - Journal of Machine Learning Research
(http://www.jmlr.org/papers/volume10/tanner09a/tanner09a.pdf)

"強化学習の接着剤"との名前の通り, 実験設定, エージェントの行動, 問題環境の3つを用意する事で, それらを接続してくれるものです.
JavaC/C++をサポートしています.
初めて知ったんですが, 強化学習のベンチマークを行う際には多いらしく, めっちゃ便利そうです.

以下のページからダウンロードします.
RL-Glue Core Project - RL-Glue
https://sites.google.com/a/rl-community.org/rl-glue/Home/rl-glue

最初「.debがあるな」と思い, Ubuntuソフトウェアセンター経由で入れましたが, ソフトウェアセンターからだと実行時に上手く動作しなかったので, ファイルを落として来て自前でコンパイルしました.
(後述のALE実行時にALEが認識できず終了してしまう)
上記ページにあるRL-Glue 3.04 (.tar.gz)を用いました.

解凍したらRL-Glue/docs/内にTechnicalManual.pdfがあるのでそれに従います.
RL-Glueのディレクトリ内で

./configure
make
sudo make install

上手くいかなかった場合のアンインストールはManualを参照して下さい.

・PL-Glue Python codec

先ほどのRL-GlueをPythonで扱えるようにするものです.

以下のページからダウンロードします.
RL-Glue Python codec
https://sites.google.com/a/rl-community.org/rl-glue/Home/Extensions/python-codec

解凍するとディレクトリ直下にPythonCodec.pdfというマニュアルがあるので参考にします.

cd /path/to/codecs/Python
python setup.py install

 

・Aecade-Learning-Environment(ALE)

今回は強化学習における"環境"としてATARIのROMを動かします.
ALEは, エミュレータを基盤にし, ROMファイルをRL-Glueと接続するためのライブラリです.

このページからインストールしてもいいですが, RL-Glueの最新版との互換性から最新版を入れた方が良いと思います.
http://www.arcadelearningenvironment.org/

記事内ではALE v0.4.4を用いていますが, 私はv0.5.0を用いました.
gitリポジトリがあるのでcloneします.

git clone https://github.com/mgbellemare/Arcade-Learning-Environment.git

(※2015年に0.5.1が出たらしく上記コマンドだとそっちが入るみたいです。バージョン絡みでコアダンプするといった報告も受けてるので、そういう時はダウンロードページから0.5.0をzipでダウンロードして解凍すると上手く動く場合があるかもしれません。)

こちらは/doc/manual/内にmanual.pdfがあるので参考にします.

ALEをインストールする前に, 今回ALEでROMを動かすためにディスプレイ表示, 音声出力を行いたいのでSDL(Simple DirectMedia Layer)を用います. SDLをインストールしておきます. mixerは必要ないかもしれません.

sudo apt-get install libsdl1.2-dev
sudo apt-get install libsdl-image1.2-dev
sudo apt-get install libsdl-mixer1.2-dev

次にALE直下にあるMakefileを編集しておきます.
元からあるMakefilemacのものである場合があり, 編集用にmakefile.macmakefile.unixが用意されています.
元のMakefileは削除し, makefile.unixを編集します.
USE_SDL, USE_RLGLUEの2つの項目を以下のように編集し,Makefileとして保存しておきます.

USE_SDL := 1
USE_RLGLUE := 1

編集したらcmakeの後makeします.
cmakeが入ってない場合は

sudo apt-get install cmake

ALEディレクトリで以下を実行します.
make の -j の後の数値はコア数です.
自分のコア数はnprocコマンド辺りでよしなに確認します.

sudo cmake -DBUILD_EXAMPLES=ON
sudo make -j 4

ここで最新版を入れてない, 設定を行ってないと, 実行時に

RL-Glue interface unavailable. Please recompile with RL-Glue support.

のようなエラーが出ると思います. 多分バージョンよりMakefileの方を見なおした方が良いと思います.
SDLとRL-Glueを使う設定をしたか確認すると良いです.

DQN-chainer

ATARIのPong!に対してDQNを適応したリポジトリ, DQN-chainerを使用させて頂きます.
リポジトリ内にはRL-GlueからALEを使う問題設計ファイルとなるexperiment_ale.pyに加えて, DQNのnature実装, nips実装が入っています.
詳しくはリポジトリ内のreadme.txtを読むと良いと思います.
Agentは, 元論文にもあるようにConvolutional Neural Network(CNN)モデルを使っています.

git clone https://github.com/ugo-nama-kun/DQN-chainer.git

 

ATARI ROMs

ATARIのROMファイルを用意します.
私はここからダウンロードしました.
Atari 2600 VCS ROM Collection
http://www.atarimania.com/rom_collection_archive_atari_2600_roms.html

/ROMs/内に各ROMファイルがありますが, Pong!は,
「Video Olympics – Pong Sports (Paddle) (1977) (Atari, Joe Decuir – Sears) (CX2621 – 99806, 6-99806, 49-75104) ~.bin」
みたいになってるので, Pong.bin等に適宜変更しておくと良いかと思います.


- 実行 -

リポジトリ内のreadme.txtにもありますが4つのターミナルを用意します.
それぞれ以下を実行します.

rl_glue
python dqn_agent_nature.py
python experiment_ale.py
./[path to ale] -game_controller rlglue -use_starting_actions true -random_seed time -display_screen true -frame_skip 4 [path_to_roms/pong.bin]

真ん中2つは/DQN-chainer内です.
[path to ale]と[path_to_roms/pong.bin]はALEのディレクトリとROMのあるディレクトリに適宜変更します.
多分ALEのディレクトリの中(ALEのMakefileがある場所)にale実行ファイルがあると思うのでそこめがけてPath書きます.
romへのpathも指定するのでALEディレクトリ外からやった方が吉.

これで多分動くと思います.
画面が出て来て学習が始まると良いです.


- 動画 -

ちょっと12月が想像以上に忙しかったのでTwitter動画で勘弁して下さい.
以下はnature実装を130000stepくらい経過した後の動画です.

かなり勝ちます.


- おわりに -

以上です. 多分あとはchainer実装の部分をイジれば他のアルゴリズムやROMで遊べるようになると思います.

本当はその拡張の部分をメインに書くつもりでしたが, 内定者研修とかいう意味不明な文化に生活を圧迫され, とりあえず動かすまでという記事になった次第です.

ただ, Ugo-Nama氏のDQNの記事もコードも美しいのでかなり理解が進みました.
Advent Calendarをケツカッチンになんとか押し進める事が出来て良かったです.
ありがとうございました.

Deep Learning Advent Calendar 2015の方では, DQNを用いた物体認識タスクについてとそのchainer実装なんかの話を(できれば)したいと思ってますので, そちらの方もよろしくお願いします.