オッサンはDesktopが好き

自作PCや機械学習、自転車のことを脈絡無く書きます

mnistでAutoEncoderを試してみる

傷検出がなかなか進展しない*1ので,アプローチを変えることにしました.
傷検出を行う為のアルゴリズムとしては,先日ReNomで試した矩形検出*2か,元々参考にしていた論文*3で採用されているAutoEncoderになると思います.
更にもうひとつ,Segmentationで傷領域を抽出するやり方もあるでしょう.
それぞれの特徴を,浅はか極まり無い知識でまとめてみます.

アルゴリズム メリット デメリット
矩形検出 複数のラベルをつけられる(傷パターン1,傷パターン2など) 抽出領域が矩形に限定される
AutoEncoder 各画素について傷情報を出力できる 傷のパターン分けが出来ない
Segmentation 傷領域をダイレクトに抽出できる 傷領域内の確率は一様になる

僕のやりたいことは,各画素についての傷確率を求めることなので,改めてAutoEncoderが相応しいと思いました.
そこで,アルゴリズム側から実相してみることにします.

AutoEncoder(自己符号化器)とは?

理解しにくい和名ですが,,,論文を意訳すると

  • エンコーダネットワークとデコーダネットワークから成る
  • エンコーダネットワークは,入力画像を多次元フィーチャー画像に変換する.この過程で,豊富な意味情報(Rich semantic information)を炙りだしたフィーチャーマップを取得する.
  • デコーダネットワークは,フィーチャーマップから各ピクセルのラベルを求める.出力画像は,元画像と同サイズにアップサンプリングされる.

要するに,エンコーダで特徴を抽出し,それを逆変換して位置情報を再構成するということだと思います.

ネットワーク構造はこんな感じです.

f:id:changlikesdesktop:20190430193823p:plain:w500

右側に進む上部の処理がエンコーダ,左に進む下部の処理がデコーダです.

実装

こちら*4を参考にしました.

AutoEncoderの特徴であるエンコーダとデコーダを,Convolution層とPool層を重ねて構成します.

def inference(x, input_size):
    x = tf.reshape(x, shape=[-1, IMG_SIZE, IMG_SIZE, 1])
    # Encoding
    with tf.variable_scope("conv_1"):
        conv_1 = conv2d(x, [3, 3, 1, 16], [16])
        pool_1 = max_pool(conv_1)
    with tf.variable_scope("conv_2"):
        conv_2 = conv2d(pool_1, [3, 3, 16, 8], [8])
        pool_2 = max_pool(conv_2)
    with tf.variable_scope("conv_3"):
        conv_3 = conv2d(pool_2, [3, 3, 8, 8], [8])
        pool_3 = max_pool(conv_3)
    
    # Decoding
    with tf.variable_scope("conv_4"):
        conv_4 = conv2dtranspose(pool_3, [3, 3, 8, 8], [8], [input_size, 7, 7, 8])
    with tf.variable_scope("conv_5"):
        conv_5 = conv2dtranspose(conv_4, [3, 3, 8, 8], [8], [input_size, 14, 14, 8])
    with tf.variable_scope("conv_6"):
        conv_6 = conv2dtranspose(conv_5, [3, 3, 16, 8], [16], [input_size, 28, 28, 16])
    with tf.variable_scope("conv_7"):
        conv_7 = conv2d_sigmoid(conv_6, [3, 3, 16, 1], [1])
    
    decoded = tf.reshape(conv_7, [-1, IMG_SIZE * IMG_SIZE])
    return decoded

特徴的なのが,損失関数の定義ですね.

def loss(x, decoded):
    cross_entropy = -1. *x *tf.log(decoded) - (1. - x) *tf.log(1. - decoded)
    loss = tf.reduce_mean(cross_entropy)
    return loss

これ,確かにラベル(0とか,1とかの文字情報)を銘記しないんですが,元画像が実質的なラベルとして働くんですね.
mnist画像の場合,文字のある場所が255付近(正規化すると1),文字のない黒い部分が0なので,ネットワークからの出力に元画像をかけることで,文字部分の出力のみが残って評価されることになります.
個人的には,「たまたま教師無しになっている」という印象を持ちます.
機械学習の意味合いからすると,文字部分を1,背景を0としたラベル画像を作るのがスッキリすると思いました.

結果はこんな感じです.

f:id:changlikesdesktop:20190503103111p:plain:w500

今回書いたソースはここ*5です.
次は,DAGM画像をこのアルゴリズムに当ててみます.