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とする重みパラメータとして決定してしまうかもしれない)
だから学習を繰り返しながら重みパラメータを修正、更新する機能を持っていなくてはいけない。
修正、更新するために損失関数という指標が存在する。

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

活性化関数について

活性化関数は入力の値と重み、バイアスから計算をして得られた値を出力信号に変換する関数のことを指す。
ニューラルネットワークは分類問題と回帰問題の2つがあるが、問題によって出力層の活性化関数を変更する必要がある。
回帰問題には恒等関数(何も手を加えず出力する)、分類問題にはソフトマックス関数を用いるのが一般的とか
以下に関数の例をメモ

ステップ関数

これは x <= 0 の時出力の値を 0にし、 x > 0 の時は 1 にする関数。
非線形関数

シグモイド関数

f:id:Owatank:20170621171737p:plain
この関数に何か値を入力すると [0,1) の間の値を返す。
h(x) を シグモイド関数とすると、 h(1.0) = 0.731… , h(2.0) = 0.880… とかになる。
非線形関数
ステップ関数との共通点として、入力の値が大きいほど返ってくる値は 1 に近づき、 小さいほど値は 0 に近づくという点がある。

ReLU関数

ReLU関数は上記二つと違って、入力の値が 0より大きければその入力をそのまま出力し、
0以下ならば 0 を出力する関数。
個人的によく中間層で使われているのを見る。

ソフトマックス関数

f:id:Owatank:20170621170946p:plain
出力層がn個あるとき、k番目の出力 y_k を求める計算式を表している。
分子は入力信号の指数関数、分母は全ての入力信号の和から構成されている。
ソフトマックス関数の出力は 0 から 1.0 の間の実数で、総和は 1 となる。
この 総和が 1 となる性質がとても重要で、出力を 確率として見ることができる。
例えば3クラス分類の学習モデルを考えるとして、活性化関数にソフトマックスを適用した時、出力が [ 0.0182, 0.2451, 0.7365 ] だったとすると、
0番目の値を 0.018(1.8%), 1番目を 0.2451(24.5%), 2番目を 0.7365(73.3%)と解釈できる。
この結果から、ある入力は 2番目のクラスに分類できると判断することができる。
ある問題に対して確率的な対応ができるようになるのはとても大切なことらしい。