研究

インスタ度判定Twitterアカウント@insta_san

今年はAI班で三田祭に向けてインスタっぽい画像を判定するアプリを作成しました。その名もインスタさんです。

インスタさんに画像を渡すと・・・

Screenshot from 2017-11-23 00-52-32

このように画像のインスタっぽさと、インスタっぽい部分が表示されます!
(三田祭期間中のみの運用予定なので、記事を閲覧しているときには動かないかもしれません、ごめんなさい!)

AIは@sesenosannko、Twitterの入出力は@mt_caretが作成しました。

しくみ

インスタさんのアイデアはとても簡単です。Instagramからたくさんの画像といいね数を集めてきて、いいね数が多い画像をインスタっぽい画像として学習させます。

ここで考えなくてはいけないことは、「いいね数が多いという基準」と「学習モデル」です

  • データセット

画像からインスタっぽさを学習するのは難しい課題なので、今回は単純に「インスタっぽい」画像と「インスタっぽくない」画像の二つに分類することにしました。単純にいいね数が多い画像と言ってもユーザーによってフォロワー数が違いますし、投稿した時点でのフォロワー数がわからないので、いいね数をそのまま使うことは難しいでしょう。

調査の結果、多くの人のアカウントでは投稿するたびにいいね数が徐々に増えていく傾向にあることが分かりました。しかし、人によって増加のタイミングや速度は様々で何かの関数で近似するのは難しそうだったので、単純に前後20投稿の平均いいね数よりもいいね数が多いか少ないかによって「インスタっぽい」画像と「インスタっぽくない」画像を分けることにしました

  • 学習モデル

近年は画像の分類にはCNNというディープラーニングのモデルが使われることが多いです。インスタさんもCNNを用いて学習しています。

今回は画像数が少ないこともあり、大量の画像で分類を学習した結果を活用して学習を行なっています。このような手法は転移学習と言われています。使用したモデルの詳しい説明は以下のサイトを参照してください。

https://www.tensorflow.org/tutorials/image_retraining

画像がインスタっぽいかという問題は人間でもはっきりと分けられないので、あまり良い学習ができることは期待できません。特に今回は学習セットの分け方も非常に単純で信頼性の低いものとなってしまっています。しかし、転移学習によって画像から意味のある情報を取り出して学習を行なっているため、何かを学習しているということは見て取れます。

LIME

インスタっぽい部分を出力するときに使用しているのはLocal Interpretable Model-Agnostic Explanations(LIME)という手法です。

簡単にいうと、入力画像の一部を隠した画像をたくさん作り、インスタ度が高かったときに隠されていなかった部分のインスタ度が高いと考える手法です。詳しくは以下のサイトを参照してください。

https://homes.cs.washington.edu/~marcotcr/blog/lime/

StackTraceを実装する話

* この記事では、MSVCのinlineアセンブリ構文を使う。
** C++はあまり詳しくないのと、深夜テンションで思いついた実装なので、もっと速い・簡単なやり方があればぜひ教えてほしい。

Unity3Dをやっている人は分かるが、簡単に実例を説明しよう。例えば、描画関数の中でしか使えない関数がある。

void OnDraw () {
    Game::DrawCube(...);
}

ここでGame::DrawCubeはOnDrawおよびOnDrawで呼ばれた関数からの実行出なければいけない。では、エラーチェックとしてOnDrawから呼ばれたかを確認したい。

じゃあ、これをどう実装すればいいのかという話。もちろんStackUnwindとかのライブラリ使ってもいいけど、そもそもデバッグではなくて実際のリリースでも使いたいのと、関数の名前より関数のIDを見るのと、個人的に人の実装を使いたくない癖(ここ重要)がある。

  • StackFrameについて

さて、C++(別にC++じゃなくても)のDisassemblyに着目する。関数が呼ばれたときにstackをみると

address   value
------------------------
EBP-4     return address
EBP       old EBP
EBP+4     funcvar1
EBP+8     funcvar2
...

になっている。これを関数のStackFrameと言う。

では、EBPはこの関数のヘッドを指しているが、その値がなんと呼ばれた関数のヘッドのアドレスが入っている。つまり、

mov EBP [EBP]

をやれば、StackTraceをすることができる(EBPは破壊されるのでこのまま使わないが)。

  • アドレスに着目

関数というのは、あくまで下のようにStackにある変数を操ったりする命令の集合である。例えば、以下ではDrawCubeを呼ぶOnDraw関数の例である。

        OnDraw:
0x123     instruction 1
0x125     instruction 2
...
0x200     call Game::DrawCube (0x502)
0x204     ret

(0x502はGame::DrawCubeのラベルが存在する位置である。例えばOnDrawだと0x123となる)

ここで、Game::DrawCubeが呼ばれた時のStackFrameを見ると、return addressが0x204となる。つまり、この関数が終わったらEIP(instruction pointer)がどこを指せばいいかを教えている。

では、この情報をどう使えばいいかとなるが、まず「呼ばれた関数」の定義を改める。「この関数はあの関数であるか」というのは、厳密に言うと「この関数のreturn addressは一緒なのか」となる。なぜかというと関数の名前より、システムが描画したいときにしか呼ばない「一ヵ所」からの実行だからである。

  • 実装

まず、OnDraw関数を呼ぶところは一定なので、下のようにあらかじめ登録することができる。なお、ラベルはアプリで共通されるため、__COUNTER__などでユニークなラベルにする必要がある。

int funcLoc;
__asm{
    uniquelabel1:
    mov EAX, offset uniquelabel1
    mov funcLoc, EAX
}

なお、このコードは関数を呼んだ直後に入れる。

次に、DrawCubeの中で、親(の親の親の…)のreturn addressがfuncLocであるかを確認すればいい。

偽コード
for EBP=*EBP
while *(EBP+4) != funcLoc ;

実際のコード

__asm{
    mov EBX, EBP
  loop:
    mov EBX [EBX]
    mov EAX, [EBX-4]
    cmp EAX, funcLoc
    jne loop
}
  • Access Violation防止

上のループは、funcLocが見つからないと永遠にループしてAccessViolationエラーが起こる。これを防止するにはどこかで止める必要がある。まず、mainスレッドからの関数と考えれば、
1. assert (std::this_thread::get_id() == mainThreadId);
2. asmのなかに

cmp EAX, mainThreadFuncLoc
je end

ただし、mainThreadFuncLocはmain()を呼んだ__CRT_Startupなんちゃらの場所である。

  • まとめ

アセンブリをいじることで、関数の親をトレースすることができた。この方法を使って、この関数を呼んだ親を区別することも可能となる。なお、デバッガでRTCが入ると、関数が実行された後にEBPの確認instructionが入るので、アドレス登録が動かなくなる場合がある。解決方法考え中(とりあえずオフにした)。もちろんリリースでは影響されない。

Winapiでプロセス間のリアルタイム映像転送

どうも、チョコです。久しぶりの進捗がありました。

今回は、OpenGLの2つのアプリ間で通信をしないといけないことになりました。ただし、リアルタイムの生画像転送なので、100mbpsくらいの速度が最低ないと困る。

ポートとかパイプあけて通信すればよいではという考えもあったが、アプリAがアプリBの好きなデータを取れるようにしたい(そういうプロジェクトだから)。なので、いちいちデータの名前送ってデータをもらうとすごく面倒だし、なにより遅い。

そこで、WinAPIのReadProcessMemoryです。要は、アプリAがアプリBに親権限を持っていれば、なんでもしていいよとのこと。よさ。
WriteProcessMemory(HANDLE processHandle, void* source, void* destination, ulong numbytes, ulong* numbytesread)

*sourceはアプリBでのポインタ値で、destinationはそのポインタの指しているデータをアプリAにコピーする場所。destinationを先に初期化しないといけない(それはそう/1時間消耗した。)。
ん、読みたいところのポインタがないとできないね(それはそう)。ので、まずはポインタ集というStructを作ります。
struct PointerList {
uint hasDataLoc, pixelsLoc, pixelCountLoc, displayWLoc, displayHLoc, okLoc;
} pointers;

*win32なので、ポインタは32ビットunsigned intに変換できます。
なので、最初にこのStructを読み取れば、すべてのポインタがわかるね。わーい。

実際にやったものを見せよう。

わーい。(ただこれを見せたかった)

では。

今年もオリジナルCDアルバム出します

こんにちは、GMAです。

KCS音楽班では10月29日(日)の音系・メディアミックス同人即売会M3に向けて準備を頑張っています。

というわけで(何かしらトラブルが起きない限りは)今年もオリジナルCDアルバムの新作を出します。
タイトルは「Flowers in the mirror」です。

Flowers in the mirror ジャケット(v1.1)

↑ジャケットはこちら!

今回のCDの収録曲は7曲。価格は300円予定。ボーカロイドRanaとIAによるのオリジナル曲です。
曲調は前作に比べるとだいぶRockに寄っている感じ。
販売当日は会場でトラック1~6を試聴可能です。M3にお越しの際は是非、聴いてみてくださいね!


~トラックリスト~
作詞作曲:GMA(track1-7)
ボーカル:Rana(track1-3)IA(track4-6)

1 ランドスケープ (3:32)
2 リリィ (4:32)
3 君を継ぐ (5:21)
4 Another Mirror (5:00)
5 Timeless World (3:43)
6 Egoistic Gears (4:25)
7 Bonus Track (Instrument)

※収録曲等は全て予定です。


CUDAのSurfaceを使ってみる

CUDAのTextureはreadonlyです. (唐突)
「なんでwriteできないんだ!HLSLにはRWTexture2Dがあるのに!」と思うかもしれませんが, Textureの特色はグローバルメモリからデータをフェッチしてくる際に利用されるキャッシュにあるため, そもそも書き込みではその恩恵を受けることができず, よって書き込みはできる必要がないと言えます. (書き込みの際には普通のメモリを使う).
SurfaceはCC2.0以上でしか利用できませんが, Textureと異なり書き込みも行うことができます. じゃあSurfaceには書き込みにもうま味があるんか?というと, 特にそういった記述はProgramming Guide中で見つけられませんでした. TextureとSurfaceは同列に語られているようなので, 単に書き込みの対象にも指定できるようになっただけなのかもしれません.
処理が一段だけであれば単純にTextureを使えばよいのですが, 処理が何段階もあり, 二つのTextureの間を行き来するようにして処理をしていく場合には少し不便なので(ほんまか?)Surfaceを利用してみました.

今回の知見ですが,

  • CUDAはバージョンによって結構仕様が変わっているっぽいのでちゃんと自分が使用しているバージョンの Programming Guide を読まないといけない(それはそう)
  • Textureの場合はバージョンの違いに加えて, Low-Level APIとHigh-Level APIの2種類のAPIが存在するため, 一方でうまくいかない場合は他方を試してみると良い

といったところでしょうか.

以下にSurfaceのサンプルコードを示します. cudaMallocPitchで確保した普通のバッファに入れてある, Webカメラから取得した色情報をSurfaceに移して(この時uchar->floatの変換とグレースケール化を行う), また戻すだけです. surfRef2は今後使う用で今は使っていないです. ガウス窓も今後使う用で今は使っていないです.

作業中に見た「イリヤの空、UFOの夏」がよかったです.
以上.

GolangでもOpenCVしたい!!!

人間の三大欲求の一つに「GolangでOpenCVをやりたい」というものがあります.
満たす必要があったので満たしました.

今回の環境はWindowsだったので, 基本的にはこちらで紹介されている通りにすればできました.
WindowsでgolangからOpenCVを呼び出してみた – ねずみとりんご

またWindowsでのGolang環境としてはVisual Studio Codeを使うといい感じです.
VSCodeでGoの開発環境を作成する方法まとめ

今回一点嵌ったのは, OpenCV3.0をGolangで使うことができないという点でした.
以前入れたOpenCVが3.0.0だったのでこれを流用しようと思ったのですが, gocはコンパイラとしてgccを利用するのに対し, OpenCV3.0以降ではgccでヘッダ周りをコンパイルできないため, GolangでOpenCVを用いるには適当に古いバージョンを持ってくる必要があります. 参考にしたサイトではOpenCV2.4.9を使用していたためこれを真似たところ, うまくいきました.
Opencv3 compilation issue with C API #6585

またこれ以外にも, C++のライブラリを使用する際には注意が必要です.
cgoでGolangとC++ライブラリをリンクするとき、何が起きているのか

以下はサンプルコードとなります. コードはこちらを参考にGoに移植しました.
カメラ画像を表示(C言語)

以上.

CUDA + OpenCVでWebカメラから取得した画像を変換

とりあえず簡単にグレースケールを作成.
実行すると画像の下の端が切れて悲しい. 理由は分からない.
実装した感想ですが, 「textureをつかえ」という感じなので, cudaBindTextureToArrayを使ってテクスチャを使っていきたい.
とりあえずミニマムで実装するとこんな感じになるんじゃないでしょうか.

参考 : http://fareastprogramming.hatenadiary.jp/entry/2016/11/10/181234
あとはCUDA落とした時に入ってるCUDA SamplesのsimpleTextureなども参考になるかと思います.

Visual StudioでOpenCVを使うメモ

開発環境をUnityからCUDAに移すときにWebカメラから取得した画像が欲しかったのでOpenCVと連携する必要があり, 長年忌避していたOpenCVを導入することになったのでメモ.
Visual Studioはこの辺の設定が難しいので苦手.
ちなみに試してみたのは昨日だけど記憶力が残念なので忘れている点がありそうなので注意.
今回落としたのはOpenCV 3.3.0, OSはWindows10

  1. このへんからソースを落としてきていい感じのディレクトリに移す(曖昧)
  2. [Project]->[Property]->[VC++ Directories]->[Include Directories]に”opencv\build\include”へのパスを追加する.
  3. [Project]->[Property]->[VC++ Directories]->[Library Directories]に”opencv\build\x64\vc14\lib”へのパスを追加する.
  4. ここまでやってビルドしてもリンカ周りで怒られる. かなしい.

  5. [Project]->[Property]->[Linker]->[General]->[Additional Library Directories]に”opencv\build\x64\vc14\lib”へのパスを追加する.
  6. [Project]->[Property]->[Linker]->[Input]->[Additional Dependencies]に”opencv\build\x64\vc14\lib”内の.libファイルの名前を追加.
    (今回はopencv_world330.lib, opencv_world330d.libの二つでした. )
    2017/11/17追記:この両方を追加するのは誤りでした. デバッグ時にはopencv_world330d.libを, リリース時にはopencv_world330.libを指定してください.
  7. ここまでやるとビルドは通るが.dllがないとキレられる. 厳しい世の中.

  8. “opencv\build\x64\vc14\bin”に先ほど追加した.libに対応する.dll(拡張子を除いた名前が同じ)があるのでこれを”C:\Windows\System32”, “C:\Windows\SysWOW64″にコピーする.
  9. 私の場合はこれで動きました.

上の作業は多分必要十分でなく, 冗長になっていると思います. 必要ない手順がどれなのかはわからない.
わかる人には自明だし, わからない人にはわからない. よってこの記事の情報量は0.
以上.

参考 : https://qiita.com/imura/items/d5fadbbcf1830019adce

REINFORCE Algorithm でジャンプアクションを学習させてみた

こんにちは. TRSasasusu です.今回は前回に引き続き強化学習の話です.

スクリーンショット 2017-08-07 4.17.50

前回のQ学習は行動価値関数を更新していくことで学習を進めましたが,今回の REINFORCE Algorithm は直接方策を更新していくことで学習を進める方策勾配に基づくアルゴリズムの一種です.

方策勾配に基づく強化学習は状態空間や行動空間が連続であっても取り扱うことができます.ですが,なんとなく連続な状態空間における離散的な行動空間を持つ問題をやってみました.具体的には,ジャンプで穴を飛び越えることを学習していきます.ジャンプするのが早すぎてもタイミングが合わず落ちますし,ジャンプしなくてももちろん落ちます.行動はジャンプするかしないかで,横方向への移動に関しては自動で右へ一定の早さで進ませます.穴との距離と方策の確率モデルのパラメータの関係式をソフトマックス関数に入れ,これを計算して勾配を求めてパラメータを更新しました.


$$ \nabla_{\theta}J(\theta) = \sum^M_{m=1}\sum^T_{t=1}(R^m_t – \overline{b})\nabla_{\theta}\log{\pi_{\theta}}(a^m_t|s^m_t) $$
$$ \theta_{t+1} = \theta_t + \eta\nabla_{\theta}J(\theta) $$

また,報酬については,ジャンプしたら減点(疲れるから),落ちたら大きく減点,穴を越えたら大きく加点という形にしました.

結果は以下のようになりました.

実際にプログラムを動かしてみると,パラメータとしては初期値に関わらずジャンプアクションができそうな値になるのですが,うまくいかない場合も散見されました.REINFORCE Algorithm よりも工夫された手法もあるのでそちらに変更するのも良さそうです.

ところで, REINFORCE Algorithm ってすごく調べづらいのですが… そもそも強化学習は Reinforcement Learning なのでこちらばかり出てきますね.

あと,未だに上に挙げた式でベースラインを引いて良い理由がわからないので,誰か教えてください.

迷路にQ学習を使ってみた

お久しぶりです.ユーザ名を今回から Twitter に合わせて TRSasasusu にしました.

スクリーンショット 2017-08-03 4.06.12

記事にできるような活動があまりできなかったため,気がついたら前回の何か作った系の投稿から7ヶ月も経ってしまいました.今回の投稿は去年UMU氏が投稿したものを大いに参考にしています.(というか,ほぼ同じ.違う点は迷路が大きくなったことくらい)

Q学習は


$$ Q(S_t, A_t) = Q(S_t, A_t) + \alpha(R_{t+1} + \gamma \max_{a’ \in A(s’)} Q(S_{t+1}, a’) – Q(S_t, A_t)) $$

に従って行動価値関数を更新します.そもそも行動価値関数とは,といったことはこちらもUMU氏がまとめてくださっています.ありがとうございます.

方策決定には ε を固定した ε-greedy 法を用いています.これにより局所解を抜け出せるようにします.パラメータについては,


$$ \varepsilon = 0.1 $$
$$ 学習率\ \alpha = 0.1 $$
$$ 割引率\ \gamma = 0.9 $$

としています.

やっぱり途中で戻ったりするようになってしまいますね…何ででしょうね.

あと,Pygame 便利ですね.

今回のスクリプト(q_maze2.py, moyf/ml/rl/q.py)