物体検出の実装を目指す-SPPnetについて
RCNNに続き、次はSPPnetの理解、実装を目指す。
今回も頑張って論文を読んだ。気のせいかRCNNの論文より読みやすかった。アジアの人が書いた論文だからかな。
まずRCNNの問題点として以下が挙げられる。
RCNNはSelectiveSearchなどで得た候補領域の1つ1つをクロッピング、ワーピングの処理を施してCNNの入力に渡すが、クロッピング、ワーピングの処理は望ましくない幾何学的な歪みをもたらす可能性があること。
SelectiveSearchで得る約2000件ほどの候補領域1つ1つをCNNにかけるのでとても時間がかかること。
2つめにおいては、認識に関係ないような候補領域までCNNにかけていたら時間が無駄になってしまう。
SPPnetはこれを改善し、なんと畳み込みの処理を画像全体から一度だけ行うことで計算時間の短縮ができると書いてあった。しゅごい。。。。
特に最大の特徴はSPP(Spatial Pyramid Pooling)と呼ばれるプーリングを実装することで、入力の画像サイズに関わらず固定長の出力を得ることができるので入力が固定ではなく可変サイズに変更できること。
RCNNの論文を読んだ自分からすれば胸が熱くなる内容しか書いてない。素晴らしい。。。。
実装としてはあるCNNの最後のConv層に、SPP層を追加するだけっぽい。
上の方法がRCNNでの手順で、下の方法がSPPnetでの手順。
SPP層で何を行うかというと以下のような処理をする。
畳み込み層からの特徴マップを それぞれ 16x16, 4x4, 1x1 のウィンドウでmaxプーリングを行う。(ウィンドウサイズの設定は条件がある。)
行った結果をreshapeして固定長の出力にして全結合層へ渡すといった形かな。
実装があっているか確信はないけど、SPP層らしきものを作り結果を試してみた。
学習率1e-5,ミニバッチ30枚でのAlexnetで試す。SPP層なしの方では
ミニバッチのせいもあるけど損失値が上がったり下がったり不安定。でもちゃんと学習している。
ありの方では
0~20stepからかなり小さい損失の値を出している。でも200stepくらいではなしの方と値はどっこいといった感じだろうか。ありの方が順調に学習している感じがする。実装が間違っていなければ
このSPP層による物体検出のプログラムを作りたいがいくつかまだ疑問が残っていて、
畳み込みの入力は画像一枚からだが、何を学習したCNNを選べばいいのか
SPP層の操作を行った出力か、それとも元の入力画像をSelectiveSearchにかけるのか。(SPP層の出力で強く発火した元の入力画像の候補領域を全結合層に渡すのか)
プログラムの構築も難しそうだが、まだ理解できていない部分が多い。道のりは長そう。
上記の画像はSPPnetの論文から引用。
論文のリンク
深層学習5章の白色化とデノイジング自己符号化器を試してみた
お久しぶりです。
SPPnetの論文をよみつつ深層学習の青い本も読み進めてます。
青い本の5章における白色化とデノイジング自己符号化器が気になったので作って見ることにした。
白色化というのは機械学習における訓練データに偏りがあると学習の妨げになる場合があるので学習前に訓練データに何らかの処理を施し、偏り(相関でいいのかな)を除去する処理のこと。
訓練データの成分間の相関は共分散行列で与えられるっぽい
この共分散行列の(p,q)成分は、訓練データのサンプルXの(p,q)成分がどの程度同じように変化するか、相関があるかを示す。
ある確率変数 X1,X2 に対しての共分散行列を以下のように定めることにする。
ρ11,ρ22 はそれぞれ X1の分散、X2の分散を、ρ12 は X1とX2 の共分散を表す。
つまり対角成分に分散が、非対角成分には今日分散が並ぶ行列を共分散行列と呼ぶんだな。
確率変数が互いに独立(無相関)ならば、共分散の値は 0 になるという性質から、白色化はこの共分散行列の非対角成分を 0 にする、つまり対角行列に線形変換する操作を行っている、で合っているかな?。
手順を本通り行い、乱数のデータセットを作り試してみた。
結果はここ
元の訓練データ(正規化済み)の分布が
こんな感じになる
線形変換後の共分散行列が対角行列になっているかの確認が取れなかった。
おまけでデノイジング自己符号化器を作った。こっちはTensorflowで実装した。
デノイジング自己符号化器は訓練データをノイズありのもので学習し、出力でノイズなしの結果が得られることを期待する感じ。
訓練データはMNISTのデータを使った。
学習をガウス分布に従うノイズありのみのデータで行ったため、最後のソルトペッパーノイズでの出力はあまりよろしくなかった。
これを実装している時に、配列を a = b
のような形でコピーして、 a
の値を変える操作を行うと参照元 b
の値も変化するということに気づいた。
ポインタとかそこらへんの問題かな。a = b.copy()
のように copy を使うと値だけを得られるということを知った。気をつける。
深層学習5章の自己符号化器をTensorflowで作ってみる
間がかなり空いてしまった。
イルカの深層学習5章にて自己符号化器なるものの説明があった。これは入力層よりノードの少ない中間層で符号化し、入力と同じノード数の出力層にて復号化を行う方法でいいのだろうか。入力のノード数より少ないノード数に落とした時、どれだけ入力と近い結果が得られるかが大事なのかな?
読んでいて「復号できんの!すげえ!!!!!!!!」とワクワクしたのと、誤差関数などが自分でも実装できそうだったので、Tensorflowで構築してみることにした。
本通り、損失関数には二乗誤差を、出力の活性化関数には恒等関数を使用した。
MNISTのデータセットを用いたコードはここ。
学習を続けることに、徐々に復号の結果が入力に近くなっていくのがわかった。
次に 64x64 の道路標識の「止まれ」でやってみようと思った。
最初はRGBでやっていたが
禍々しいものが生まれた。何を学習したのだろうか。
損失の値もいい値にはならなかったので、諦めてグレースケールの方でやってみることにした。
コードはここ。
こっちはノード数を 50x50 にした。 32x32 の半分のノード数では上手くいかなかった。
最後の結果からわかるように訓練データの結果は
そこそこ形がわかるような出力になってくれたが、テストデータの結果は
モザイクのような結果になってしまった。
訓練データが少なくて過適合を起こしているのかもしれない。
結果を観察するのは楽しかった。
深層学習4章の誤差逆伝播について
深層学習という青いイルカさんが表紙の本がありまして最近読んでいる。
重み上限など自分の知らないことが載っていてまだまだ勉強不足と感じる。
4章の誤差逆伝播について、二乗誤差の第n層の重みの更新を考えるとき
こんな式が載っていた。右辺の第二項の微分を計算したいが、出力層から第n層までを展開、微分しなくてはいけなくなり計算量が大きくなる問題があるということが書いてあった。そしてその問題を解決するために出てきたのが誤差逆伝播法。
読み進めていった結果
(;^ω^)
こんな感じの顔になった。連鎖律からどういう微分をするのかはわかった気はするがスッキリしなかった。こっちも微分の計算大変じゃね?という感想。
この資料を参考に実際計算をしてみる。
ネットワークとしては中間層が1つの以下を考える。
まずは順伝播、h1,z1,h2,z2,p1,p2を求める。
中間層。活性化関数としてシグモイド関数を使うことにする。
出力層。活性化関数は恒等関数。
次は逆伝播。重みの更新を試みる。まずは誤差関数の値を計算する
次に出力層に近い w5 ~ w8 の重みの勾配を求める。
w5はネットワークの図から、p1を通過するのがわかる。連鎖律から以下のように式で表せるはず。
右辺の第1項は
第2項は
右辺が計算できたので、勾配を求めてそのまま重み w5 を更新する。
元の w5 の値より若干増加した。同様に w6 を更新する。右辺の第1項は共通、計算してあるので
w7,w8については、この2つは p2 を通過しているので dE/dp2 を計算する。
w7,w8を更新する
w7とw8はw5,w6に比べてかなり減少してしまった。あってるのか不安になってきた。
最後に入力と中間の間にある w1~w4の重みを更新する。
w1 は z1とh1 を通過しているので
右辺の第1項から計算していく。z1はp1とp2に値を伝播しているので以下のような連鎖律で表せるはず。
dE1/dp1とdE2/dp2はw5~w8の重みの更新をする際に計算したのでやるべき計算は dp1/dz1 と dp2/dz1 のみ
右辺第2項と第3項を計算する。
こっちは比較的楽。右辺が計算できたので勾配を求め、w1の重みを更新する。
w2も同様に
w3はz2,h2を通過してきているので、
右辺第1項を計算する
右辺第2項と第3項も計算する
右辺が全て計算できたので w3 の重みを更新する。
w4も同様に
これで w1 ~ w8 全ての重みが更新できた。
誤差逆伝搬における入力と中間の間の w1 ~ w4 の重みに更新については出力層から微分の値を受け取ることで、局所的な計算のみで勾配を計算できる認識でいいのかな?
計算が苦手な自分でも求められたから楽にはなってるだろう。多層になればなおさらな気がした。
Tensorflowで作った学習モデルをandroidで利用する
物体検出を勉強中です。Tensorflowにはネットワークのグラフ情報やパラメータなどを保存するcheckpoint
というものがありますよね。
checkpoint
ファイルをロードすれば一旦学習をストップして、再度学習を続行することもできて非常に便利。
それとは別でcheckpoint
ファイルからProtocolBuffer
と呼ばれるグラフ情報とパラメータが格納されたファイルを作成できるらしく、それを使ってAndroid上で動かしてみた。
まずはProtocolBuffer
ファイルの作成から。コードはいつも通りここにあげた。
出力結果でネットワークに使用したConv2d
などのパラメータが出てきているのがわかります。
利用としてはtf.Session
での初期化をProtocolBuffer
ファイルに入っているグラフ情報で初期化して使うという感じかな?
モルガンさんのこの記事を参照した。
ProtocolBuffer
ファイルの作成もできたので、Android上で読み込んで使ってみる。
アプリの設計としては
- OpenCVのライブラリを導入して、カメラのフレームを入力にする。
- カメラのフレームを取得して、その画像でOpenCVライブラリにあるテンプレートマッチングを行う。
- マッチングしたところを切り抜きリサイズして
ProtocolBuffer
ファイルを読み込み、学習にかける
こんな感じ こちらのコードはここ
Androidアプリの開発はそんな経験はなくThreadやListenerなどはあまりわかってないので、頭悪いコードになってしまった。
ProtocolBuffer
ファイルが400MBとデカすぎるのでSDカードから読み込むことに。それでもかなり重いが。。
結果としては以下のようになった
欠点として、端末の電池の減りが早くなるのと、端末自体が激アツになってしまう。
作るにおいて躓いたのは、OpenCVの導入だった。
android用のTensorflowの導入は簡単だったけど、OpenCVを手順通りにやるとLinkerErrorが出てしまい困った。
原因としては、OpenCVの導入の手順では「sdk > native > libs」の libs を 自プロジェクトの jniLibs に置くと書いてある。
libsの中身としては、armeabi
やx86
とかAndroid端末のCPU?の名前のディレクトリが何個かある。
自分のプロジェクトにはTensorflowを利用するために、「jniLibs -> armeabi-v7a -> libtensorflow_inference.so」ファイルが置いてある。
jniLibsに armeabi-v7a
以外のx86
などのディレクトリを置いてしまうと、Tensorflowのライブラリを読み込むときに、libtensorflow_inference.soをx86
とかから参照してしまいエラーが出るということだった。OpenCVを利用するためには libsのarmeabi-v7a
だけをコピーして置けば解決した。
RCNNも作ってみたかったけどjavaでのSelectiveSearchのライブラリが見つからなかったので諦めた。
ProtocolBuffer
ファイルの利用例が作れたのでよしとする。
Tensorflowを用いて物体検出の実装を目指す-RCNN
前回に続いて今回はRCNNです。
まずは論文を頑張って読みました。個人的にまとめたスライドを上げておきます。
英語が苦手なので翻訳に頼ってしまった。。BB回帰のところの実装がわからなかったので探したがなかったので今回は無しで実装することに。
RCNNでは要約すると3つのモジュールが必要になるらしい。
一つ目の提案領域の生成には論文通りSelectiveSearch
を使うことにした
SelectiveSearchを実装できるほどアルゴリズムに強いわけでもコーディングスキルがあるわけでもないのでアルパカさんのライブラリを利用することに。大変便利だ・・・
二つ目のCNNにはAlexNetと呼ばれるCNNを使用した。コードはこちら
流石に層が深すぎて学習に時間がとてもかかった。いいインスタンスを借りれる機会があって良かった。
AlexNetの論文を読んでいないので、実装はクラスキャットさんの記事やネットワークのイメージ図を見たりして構築した。
実装しててどうしてもわからなかったのはtf.nn.local_response_normalization
の部分だった。今度ゆっくり調べたい。
三つ目のSVMは、この論文でSVMの存在を知ったレベルで知識がないのでこの部分は二つ目のCNNに識別の部分も任せることにしてみた
モジュールの説明はここまでで、結果はこちらにおいておきます。
結果はこんな感じになった
CNNのモデルが悪いのか変なところも囲んでしまっていてイマイチ。。。
論文ではNon-Maximum-Suppressionと呼ばれるアルゴリズムを導入してるよと書いてあり、このアルゴリズムによって各クラスの四角の重複をいい感じに抑制してくれる。アルゴリズムの内容についてはこちらのサイトを参考に
まだ勉強不足のせいかBoundingBox回帰の実装ができなかった。訓練データにground-truth boxを付与してないからそれもあるのだが。
BoundingBox回帰について詳しい説明などがあるサイトあれば教えて欲しいです。
次はSPP-net
?とやらを調べつつFast-RCNN
を目指す。
Tensorflowを用いて物体検出の実装を目指す-テンプレートマッチング
最近アルバイトのこともあって物体検出のことについて調べていました。
有名なところではRCNN
に続きFast RCNN
、Faster RCNN
、YOLO
、最近だとSSD
というのが熱い感じ?
Faster RCNN
やSSD
はすでにTensorflow版のがGithubで公開されてますが、自分で実装して見たいなと思った。
最終的にはSSD
の実装を目指したいけどRCNN
からYOLO
まで芋づる式的に論文を読まないと実装は難しいとのこと。
まずはRCNN
から実装を目指す。
道路標識を認識する学習モデルを作っていたので、画像から道路標識を検出する物体検出器を作る。
RCNN
はできたけど、その前にOpenCV
にはテンプレートマッチングなるものが用意されているらしいのでそれを使って道路標識っぽいものを探してその部分を切り取って画像認識にかける。という物体検出もどきを作ったのでそれを記事にする。
コードはここ
少し前の記事で道路標識のモデルを作った。
この道路標識のモデルは「止まれ」と「速度制限(10~80まで)」の2クラスだったが、今思えばかなりアホなことをしている。
出力層の活性化でSoftmaxを使用しているということは出力が確率になる。予測結果として出力の0.5以上の値を採用するとして、新しい入力として猫の画像を渡したら少なくとも出力結果は[0.5... ,0.5...]
になるはず。これはいけない
猫の画像で「止まれ」と「制限速度」のそれっぽさが0.5以上になるってかなりおかしいはず。そもそも2クラス分類でSoftmax使うのがおかしいのは?
なので物体検出で使う画像認識のモデルは、「止まれ」、「速度制限10」、「速度制限20」、「速度制限30」の4クラス分類の学習モデルにしました。
次はRCNN
の実装についてを記事にする。