深層学習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
の実装についてを記事にする。
Processingで画像の切り抜きプログラムを作った
お久しぶりです。
自作のデータセットで画像認識を行うにあたって、MNISTのように 28x28 サイズで特徴を切り抜かないといけない問題にあたってしまう。
というわけでProcessingで画像の切り抜き、リサイズして保存するプログラム作った。
Processingにしたのは元の画像をみながら切り抜きたいだけ
コードはここ
あまり例外処理とか入れてないので(苦手)プログラムを実行する前に、LOAD_DATA_PATH
とSAVE_DATA_PATH
を設定しておかないとエラー出ます。
最初に作った時は右下にボタンを作ってそれを押して一枚ごとにファイル選択や保存する設定にしてたんですが、その動作がだるすぎたのでキー操作とファイル選択をリスト表示して選択する形にした。結構快適。
スクレイピングしたことないからあとは元の画像データをいかに時間かけずに集めてくるかが問題。右クリ保存は時間がかかってしまう。。。。。
物体検出であるRCNNをまずは目指してはいるけども、分類において画像データがたくさんないといいモデルは作れないからやっぱりデータ集めは大事。
畳み込み層について その2
前回の続き
前回をまとめると
- 画像は基本3次元の形状で、形状には大切な空間的情報が含まれている。
- 入力の形状を意識したニューラルネットワークの構築には、「畳み込み層」と「プーリング層」を用いる。
- 畳み込み層で行われる畳み込み演算の処理に使われるフィルターが重みパラメータに相当する。
次はプーリング層。プーリングは縦・横方向の空間を小さくする演算。
プーリングには最大値を取るMaxプーリング、平均値を取るAveプーリングがある。
上の画像ではMaxプーリングを行なっている。地味に色んなサイト見てもMaxプーリングしか見たことがない。。。。。
またこの場合 プーリングのウィンドウサイズは 2x2 であり、2x2 の領域に対して最大値となる要素を入力の値から取り出している。またストライド値は 2 である。一般的にはプーリングのウィンドウサイズとストライドの値は同じ値に設定するらしい。
プーリング層の特徴として
- 学習するパラメータを持たない(フィルターなどを持たない。最大値を取る処理などを行うだけ)
- チャンネル数は変化しない(入力と出力のチャンネル数は変化しないということ)
- 微小な位置変化に対して頑健(入力データの小さなズレに対してプーリングは同じような値を返す。)
3つめに関しては、プーリングの処理で見る領域内で入力の値がシャッフル(ズレが起こる)されても、最大値は変わらないので出力はシャッフルする前と後で同じになる、という感じだろうか。
畳み込みとプーリングの処理でよく見かける「パディング」と「ストライド」について
パディング
パディングは、畳み込みの処理を行う前に、入力データの周囲に固定の値(0など)を埋めること。
画像では、幅1のパディングを適用している。幅1のパディングとは入力の周囲を幅1ピクセルの 0 で埋めることを言う。
入力は (4,4) から幅1のパディングで (6,6) になってるのがわかる。
パディングの処理を行う理由としては、出力サイズの調整に使われる。と言うのも
input -> Conv1_1 -> Conv1_2 -> Conv1_3 -> ... Conv1_n -> MaxPool1
上のような第一層の畳み込みを考えるとき、パディングを行わないと、出力サイズが1回の畳み込みを終えるごとに小さくなっていくのでConv1_nに到達する前に出力サイズが 1 になることもありえてしまう。出力サイズが 1 になると言うことはそれ以上畳み込み演算を行えなくなってしまうということなのでこれを回避するためにパディングという処理を行う必要が出てくる。
ストライド
ストライドはフィルターの適用する位置の間隔(ずらす間隔)の値のことをさす。
畳み込みにおいて入力サイズが (7,7) のデータに対し、ストライドが 2 のとき出力サイズは (3,3) になる。
ストライドの設定値を大きくすると、出力サイズは小さくなる。逆にパディングの幅の値を大きくすると、出力サイズは大きくなる。
入力とフィルターサイズ、ストライドとパディングの値による出力サイズの関係式として
上が成り立つ。入力サイズを(H,W)、フィルターサイズを(FH,FW)、出力サイズを(OH,OW)、パディングの値を P、ストライドの値を Sとしている。
フィルターサイズなどの値の設定としては、上の式が必ず割り切れるようにしないといけない。
どうでもいいけどこの式自作のデータセットで画像認識を行う際必要だった。