Processingで画像の切り抜きプログラムを作った

お久しぶりです。

自作のデータセットで画像認識を行うにあたって、MNISTのように 28x28 サイズで特徴を切り抜かないといけない問題にあたってしまう。

というわけでProcessingで画像の切り抜き、リサイズして保存するプログラム作った。
Processingにしたのは元の画像をみながら切り抜きたいだけ

コードはここ

f:id:Owatank:20170714134440p:plain

あまり例外処理とか入れてないので(苦手)プログラムを実行する前に、LOAD_DATA_PATHSAVE_DATA_PATHを設定しておかないとエラー出ます。

最初に作った時は右下にボタンを作ってそれを押して一枚ごとにファイル選択や保存する設定にしてたんですが、その動作がだるすぎたのでキー操作とファイル選択をリスト表示して選択する形にした。結構快適。

スクレイピングしたことないからあとは元の画像データをいかに時間かけずに集めてくるかが問題。右クリ保存は時間がかかってしまう。。。。。

物体検出であるRCNNをまずは目指してはいるけども、分類において画像データがたくさんないといいモデルは作れないからやっぱりデータ集めは大事。

畳み込み層について その2

前回の続き

前回をまとめると

  • 画像は基本3次元の形状で、形状には大切な空間的情報が含まれている。
  • 入力の形状を意識したニューラルネットワークの構築には、「畳み込み層」と「プーリング層」を用いる。
  • 畳み込み層で行われる畳み込み演算の処理に使われるフィルターが重みパラメータに相当する。

次はプーリング層。プーリングは縦・横方向の空間を小さくする演算。
f:id:Owatank:20170707153411p:plain
プーリングには最大値を取るMaxプーリング、平均値を取るAveプーリングがある。
上の画像ではMaxプーリングを行なっている。地味に色んなサイト見てもMaxプーリングしか見たことがない。。。。。
またこの場合 プーリングのウィンドウサイズは 2x2 であり、2x2 の領域に対して最大値となる要素を入力の値から取り出している。またストライド値は 2 である。一般的にはプーリングのウィンドウサイズとストライドの値は同じ値に設定するらしい。

プーリング層の特徴として

  • 学習するパラメータを持たない(フィルターなどを持たない。最大値を取る処理などを行うだけ)
  • チャンネル数は変化しない(入力と出力のチャンネル数は変化しないということ)
  • 微小な位置変化に対して頑健(入力データの小さなズレに対してプーリングは同じような値を返す。)

3つめに関しては、プーリングの処理で見る領域内で入力の値がシャッフル(ズレが起こる)されても、最大値は変わらないので出力はシャッフルする前と後で同じになる、という感じだろうか。

畳み込みとプーリングの処理でよく見かける「パディング」と「ストライドについて

パディング

パディングは、畳み込みの処理を行う前に、入力データの周囲に固定の値(0など)を埋めること。
f:id:Owatank:20170707155614p:plain
画像では、幅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) になる。
ストライドの設定値を大きくすると、出力サイズは小さくなる。逆にパディングの幅の値を大きくすると、出力サイズは大きくなる。

入力とフィルターサイズ、ストライドとパディングの値による出力サイズの関係式として
f:id:Owatank:20170707161104p:plain
上が成り立つ。入力サイズを(H,W)、フィルターサイズを(FH,FW)、出力サイズを(OH,OW)、パディングの値を P、ストライドの値を Sとしている。

フィルターサイズなどの値の設定としては、上の式が必ず割り切れるようにしないといけない。

どうでもいいけどこの式自作のデータセットで画像認識を行う際必要だった。

Tensorflowで道路標識の画像認識をしてみる

勉強ばっかりだと頭に残らないので、実際に自分でデータセット作りtensorflowを使って画像認識をしてみる。
データは道路標識の「制限速度」と「止まれ」の2クラス分類にする。
画像検索のAPIは貧乏で使えないので手作業でデータは各100枚ほど集めた。
80枚を訓練に、20枚をテストデータに使った。

まずは全結合のみのネットワークで。コードはここ
学習ステップが2500ほどで
loss: 0.000067, acc: 1.000000, [Test] acc : 1.000000
こんな感じの損失関数の値が出たけど、精度が1.0なのが怖い。
新しいデータでの出力で正解が何個かでたのでいい感じのパラメータにはなってるはず、多分。

次は畳み込み層ありのネットワークで。コードはここ
ステップ毎の損失関数の値から、学習が停滞している?感じがする。精度も上がったり下がったり不安定
層が浅かったり、フィルターサイズや重みのノード数とかハイパーパラメータの設定など改善できることはたくさんありそう。
精度が上がったり下がったりするのはミニバッチ学習をしているからかな?
まだまだ勉強するべきことは多い。。

畳み込み層について その1

ディープラーニングを用いた画像認識の記事で「畳み込みネットワーク(CNN)」という単語をよく見かける。最近CNNを勉強してたのでメモ。
CNNでは「畳み込み層(Conv)」「プーリング層(Pool)」の2つの層がニューラルネットワークに登場する。

全結合のニューラルネットワークでは、データの形状を無視してしまう問題が実はある。形状に意味のあるデータに関してはそれを生かすことができない学習モデルを作成することになってしまう。
例えば入力データが画像の場合を考える。画像は基本、縦・横・チャンネル の3次元の形状を持っている。これを全結合層に渡すとき、3次元の形状を1次元の形状に直して渡す必要がある。(平らに直すとも考えられる)
入力画像が「チャンネル・縦・横」の順番で(1,28,28)の形状の時、一列に並べた 1x28x28 = 784個のデータに直して渡す。

tensorflowの公式ページにあるMNISTのチュートリアル(Beginner)の方でも、入力層が 1x28x28のx = tf.placeholder(tf.float32, [None, 784])と定義されている。

画像の形状には、大切な空間的情報が含まれていることが大半。例えば空間的に近いピクセルの値は似ていたり、RGBの各チャンネルの間には密接な関係性があったりなど。全結合層では形状を無視してしまう(一列に直してしまう)のでこれらの情報を生かすことができない。

畳み込み層は形状を維持する(入力データを3次元のデータとして受け取る)ニューラルネットワークの構築が可能となる。形状の情報を生かした画像認識などの学習モデルの作成ができる。

畳み込み層では「畳み込み演算」という処理を行う。画像処理では「フィルター演算」として有名らしい。全くわからん。。
f:id:Owatank:20170703181706p:plain
画像が荒っぽいが、フィルターの行列をカラフルな色で囲った入力の枠の行列と演算を行う。フィルターの行列を一定の間隔でスライドさせながら演算(適用)していく感じ。フィルターの各要素と入力の対応する要素の値同士を乗算し、和を求める。和の値を出力の対応する場所へ当てはめる。

CNNでは、フィルターのパラメータ、すなわち行列の値ニューラルネットワークでいう重みに相当する。

もちろんバイアスも存在して、 f:id:Owatank:20170703182556p:plain
フィルター適用後の行列の各要素に対してバイアスの値を加算する。

とりあえずここまで。

tf.layers.conv2dでCNNを構築する

有名なゼロから作るDeepLearningの魚の本ありますよね。自分もお世話になってます。
その本の8章に f:id:Owatank:20170701172825p:plain
こんな感じのCNNの説明がありましたよね。コードも公開してあります。
個人的にtensorflowで実装して見たいと思い、このCNNを構築してみた。
コードはここ
ネットワークの特徴として

  • 畳み込みのフィルターは3x3
  • Adamによる最適化
  • 活性化関数はReLU
  • 全結合の後にDropoutレイヤを使用
  • 重みの初期値には「Heの初期値」を使用

の他に荷重減衰を追加してみた。
実装においてtf.nn.conv2dではエラーが出てしまい困った。
最初の第1層目を以下のように定義すると

h_conv1_1 = tf.nn.relu(tf.nn.conv2d(input=input_layer, 
                                    filter=W_conv1_1, 
                                    strides=[1, 1, 1, 1], 
                                    padding='SAME')+ b_conv1_1)
h_conv1_2 = tf.nn.relu(tf.nn.conv2d(input=h_conv1_1, 
                                    filter=W_conv1_2, 
                                    strides=[1, 1, 1, 1], 
                                    padding='SAME')+ b_conv1_2)

ValueError: Dimensions must be equal, but are 3 and 1 for 'Conv2D_1' (op: 'Conv2D') with input shapes: [?,28,28,1], [5,5,1,32].
こんな感じのエラーが出力される。非常に困った。
良い解決方法が思いつかなかったので、tf.layers.conv2dで畳み込み層を定義することにした。

トレーニングでは、メモリ2GBでしょっぱいのでバッチサイズは10で回した。
バッチサイズを50にして学習ステップも1万ほど回したらもっといい精度出るのかな?

畳み込みやDropoutとについてはまたメモする
tf.nn.conv2dtf.layers.conv2dの違いは何だろう?引数が増えたくらいにしかリファレンス読んだところ感じなかった・・・

損失関数について その2

損失関数について その1 - 時給600円

これの続きです。その1では

  • 学習とは訓練データから最適な重みパラメータの値を自動で獲得する
  • 重みパラメータを修正、更新のために損失関数が存在する

というところまでメモった。

損失関数がどのような指標かというと、重みパラメータを用いた出力の答えと、正解の答えがどれだけ一致しているかの指標と自分は認識している。
例えば、3つのデータに対して重みパラメータAを用いた学習モデルの方の答えが2つ正解を出したとすると、1つ間違えている。
逆に重みパラメータBを用いた時、3つ正解を出したとすると、間違いは0。
この時、損失関数の値3つのうち全てに正解を出した重みパラメータBの方がAより小さい(間違いが少ないから)。

このように損失関数は間違いが少ないほど、値としては小さくなるようになっている。だから損失関数の値を小さくすることは重みパラメータが最適な値を取っているということに繋がる。だから損失関数の値が最小値を取るように重みパラメータを修正していく。

損失関数にはいくつか種類がある。

2乗和誤差

f:id:Owatank:20170628170816p:plain
yk はニューラルネットワークの出力で、tk は教師データを表す。k はデータの次元数。
出力と教師データ(正解データ)の各要素 k の差の2乗を計算して総和を求めて 2 で割っている。

交差エントロピー

f:id:Owatank:20170628171427p:plain
logは底がe(自然対数)
y = logx を考える。x = 1 の時、 y = 0になる。 xが0に近づくにつれてyの値は小さくなる。
上の画像の式に戻ると、出力 yk の値が大きければ大きいほど、E は 0 に近づくということになる。
マイナスがついているのは yk が 0に近づく時負の値が出るからそれを正に直すため?

もし訓練データが N個ある時はその N個の損失関数の和を指標とするので、こんな感じになる。
f:id:Owatank:20170628172803p:plain
y_nk では N個あるうちの n番目のデータのk次元目の値を意味する。
最後にN個で割っているので「平均の損失関数の値」を出していることになる。


どうやって損失関数が最小になるようなパラメータを見つけるのか。
重みパラメータCがあるとして、その値を増やせば今より損失関数の値が最小になるのか?というのは直感的にはわからない。。。
これに関しては関数の値を最も減らす方向を示してくれる 勾配 を用いる。微分。ただし勾配の指す方向の先が関数の最小値なのかは保証できないっぽい。
勾配を用いて最小値を探す方法を 勾配降下法 と呼ぶらしい。

物理の授業で勾配とかわかんねえ、、、何に使うん、、、と思って問題を解いていたが、ディープラーニングでのパラメータの修正に使うことができるなんて正直たまげた

損失関数について その1

今回は損失関数についてメモる。
機械学習ディープラーニングとかの言葉をたくさん聞くようになってきたけど、何を学習しているのか?
勉強して調べた結果、今の所学習とは訓練データから最適な重みパラメータの値を自動で獲得するという認識に落ち着いた。


f:id:Owatank:20170623121117p:plain
重みパラメータの例としてミックスジュースをあげる。ミックスジュースAには20%がりんご、30%がみかん、50%がいちごだとする。
A~Cのミックスジュース(使ってる果物は同じと考える、割合が違う)の教師データからどれがAかを判定する学習モデルを考える。
このとき、人はAがどの割合でミックスジュースが構成されているのか「分かっている」ので学習モデルに割合を教えてあげれば教師データなんてなくてもすぐに判別するモデルが作れる。
でも教えないときはどうか?そのとき学習モデルはA~Cの教師データから「学習」をして20%がりんご、30%がみかん、50%がいちごの割合はおそらくAのミックスジュースだと判定するようになる。
これは教師データから一定のパターン、特徴を見つけ出していると考えることができる。
この一定のパターン、特徴こそがニューラルネットワークにおける重みパラメータに相当する、はず。

決定した重みパラメータの値が間違っている可能性もある。
というのも上の例で1000個の教師データがあるとき、100個のデータから重みパラメータを見つける時と、500個のデータから見つけるときでは重みパラメータは違うかもしれない。(前者では1:2:7の割合をAとする重みパラメータ、後者は2:4:4の割合をAとする重みパラメータとして決定してしまうかもしれない)
だから学習を繰り返しながら重みパラメータを修正、更新する機能を持っていなくてはいけない。
修正、更新するために損失関数という指標が存在する。

長くなりそうなのでこのへんで