みらいテックラボ

音声・画像認識や機械学習など, 週末プログラマである管理人が興味のある技術の紹介や実際にトライしてみた様子などメモしていく.

ペットボトルを認識してみよう! (3)

これは, 昨年の10月~12月に開催された「チームで学ぼう! TensorFlow(機械学習)実践編第2期」において, 私の参加した「チーム仲鶴後吉(仮)」の成果を数回に分けて紹介するものである.

関連記事
ペットボトルを認識してみよう! (1)
ペットボトルを認識してみよう! (2)
ペットボトルを認識してみよう! (3)
ペットボトルを認識してみよう! (4)
ペットボトルを認識してみよう! (5)

前回までは画像データの収集や学習データの準備について紹介したが, 今回は「システムとモデル」, 「学習と評価」について紹介する.

4. STEP1システムとモデル
4.1 STEP1システム
今回は機械学習ツールの勉強ということもあるので, TensorFlowでチュートリアルDeep MNIST for Experts[1]を参考に, CNNを使って認識を行うことにした.

[コード]

# -*- coding: utf-8 -*-

import os
import sys
import struct
import random
import numpy as np
import tensorflow as tf
import pet_data40x80 as pdat
import pet_info as pinfo
import pet_result as pres

IMAGE_WIDTH  = 32
IMAGE_HEIGHT = 64
IMAGE_DEPTH  = 3

BOTTLE_CATEGORY_SIZE = 11

ITELATION_NUM = 20000
MINI_BATCH_SIZE = 100
CANDIDATE_MAX = 5
LOG_DIR = './log'

CONV1_FILTER_NUM = 64 
CONV2_FILTER_NUM = 128 
FULL_CONNECT_NUM = 256

def options(argv):
    # init
    mode = 'LT'
    model = '';
    # parse
    i = 0
    while i < len(argv):
        if argv[i] == '-mdl':
            model = argv[i+1]
            i+=2

        elif argv[i] == '-mode':
            if argv[i+1] == 'T' or argv[i+1] == 'L' or argv[i+1] == 'LT':
                mode = argv[i+1]
                i+=2
            else:
                return false, mode, model
        else:
            return False, mode, model
    return True, mode, model

# model
def get_modelname(model):
    if model == 'auto':
        if tf.train.get_checkpoint_state('./'):
            ckpt = tf.train.get_checkpoint_state('./')
            last_model = ckpt.model_checkpoint_path
            return last_model
    else:
        return model

# 重み&バイアス初期化関数
def weight_variable(shape):
    initial = tf.truncated_normal(shape, stddev=0.1)
    return tf.Variable(initial)
 
def bias_variable(shape):
    initial = tf.constant(0.1, shape=shape)
    return tf.Variable(initial)

# Convolution & Pooling
def conv2d(x, W):
    return tf.nn.conv2d(x, W, strides=[1,1,1,1], padding='SAME')

def max_pool_3x3(x):
    return tf.nn.max_pool(x, ksize=[1,3,3,1], 
                          strides=[1,2,2,1], padding='SAME')

def main(mode, model):
    # 特徴ベクトルx[][Height][Width][Depth] 
    x = tf.placeholder(tf.float32, shape=[None, IMAGE_HEIGHT, IMAGE_WIDTH, IMAGE_DEPTH])
    # 目標
    y_ = tf.placeholder(tf.float32, shape=[None, BOTTLE_CATEGORY_SIZE])

    # 1層畳み込み層
    W_conv1 = weight_variable([5,5,3,CONV1_FILTER_NUM])
    b_conv1 = bias_variable([CONV1_FILTER_NUM])
    h_conv1 = tf.nn.relu(conv2d(x, W_conv1) + b_conv1)

    # 15層畳み込み層
    W_conv15 = weight_variable([3,3,CONV1_FILTER_NUM,CONV1_FILTER_NUM])
    b_conv15 = bias_variable([CONV1_FILTER_NUM])
    h_conv15 = tf.nn.relu(conv2d(h_conv1, W_conv15) + b_conv15)
    h_pool1 = max_pool_3x3(h_conv15)

    # 2層畳み込み層
    W_conv2 = weight_variable([3,3,CONV1_FILTER_NUM,CONV2_FILTER_NUM])
    b_conv2 = bias_variable([CONV2_FILTER_NUM])

    h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
    h_pool2 = max_pool_3x3(h_conv2)

    # 密に結合された層
    W_fc1 = weight_variable([8*16*CONV2_FILTER_NUM, FULL_CONNECT_NUM])
    b_fc1 = bias_variable([FULL_CONNECT_NUM])

    h_pool2_flat = tf.reshape(h_pool2, [-1, 8*16*CONV2_FILTER_NUM])
    h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)

    # ドロップアウト
    keep_prob = tf.placeholder(tf.float32)
    h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

    # 読み出し層
    W_fc2 = weight_variable([FULL_CONNECT_NUM, BOTTLE_CATEGORY_SIZE])
    b_fc2 = bias_variable([BOTTLE_CATEGORY_SIZE])

    y_conv = tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)

    # Train
    cross_entropy = -tf.reduce_sum(y_ * tf.log(tf.clip_by_value(y_conv, 1e-10, 1.0)))
    ce_summary = tf.scalar_summary('cross_entropy', cross_entropy)

    train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)

    correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_, 1))
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
    acc_summary = tf.scalar_summary('accuracy', accuracy)

    # Saver
    saver = tf.train.Saver()

    # セッション
    sess = tf.Session()
    # ログ出力準備
    merged = tf.merge_all_summaries()
    summ_writer = tf.train.SummaryWriter(LOG_DIR, sess.graph_def)
    # 初期化
    sess.run(tf.initialize_all_variables())

    # 学習パラメータの復元
    if model != '':
        if model == 'auto':
            if tf.train.get_checkpoint_state('./'):
                ckpt = tf.train.get_checkpoint_state('./')
                last_model = ckpt.model_checkpoint_path
                saver.restore(sess, last_model)
        elif os.path.isfile(model):
            saver.restore(sess, model)

    if mode == 'L' or mode == 'LT' or model == '':
        print('--- Start Learning ---')
        train = pdat.PETBottle('L')
        for i in range(ITELATION_NUM):
            # ミニバッチのデータ
            batch_xs, label1 = train.getRandomImages(MINI_BATCH_SIZE)
            batch_ys = np.zeros((MINI_BATCH_SIZE, BOTTLE_CATEGORY_SIZE))
            for j in range(MINI_BATCH_SIZE):
                batch_ys[j][label1[j]] = 1

            # small batch
            if i%100 == 0:
                summary, train_acc = sess.run([merged, accuracy], 
                    feed_dict={x: batch_xs, y_: batch_ys, keep_prob: 1.0})
                summ_writer.add_summary(summary, i)
                print('step %d, training accuracy %g' % (i, train_acc))
                sys.stdout.flush()
            sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys, keep_prob: 0.5})
            if (i + 1) % 1000 == 0 and model != '':
                saver.save(sess, get_modelname(model), global_step=i+1)

    # 評価
    if mode == 'T' or mode == 'LT':
        test = pdat.PETBottle('T')
        print('--- Start Evaluating ---')
        num2 = test.getDatNum()
        test_res = pres.Accuracy(CANDIDATE_MAX)
        for i in range(num2):
            img, code = test.getImage(i)
            label2 = np.zeros((1,BOTTLE_CATEGORY_SIZE))
            label2[0][code] = 1
            cand = sess.run(y_conv, feed_dict={x: [img], y_: label2, keep_prob: 1.0})
            test_res.setResult(i, cand, code)
        test_res.calcAccuracy(num2)
        test_res.dispConfusionMatrix()

    # セッションクローズ
    sess.close()

if __name__ == '__main__':
    ret, mode, model = options(sys.argv[1:])
    if ret == False:
        sys.exit('Usage: %s <-mode L/T/LT> <-mdl model file>' %sys.argv[0])
    main(mode, model)

4.2 モデル
モデル構造は以下の通り.
 1. 畳み込み層[5x5]
 2. 畳み込み層[3x3]
 3. プーリング層[2x2]
 4. 畳み込み層[3x3]
 5. プーリング層[2x2]
 6. 全結合層
 7. Softmax Regression層

f:id:moonlight-aska:20170416112114p:plain:w600

5. 学習と評価
5.1 学習
学習条件と学習の経過は, 以下の通り.

学習条件:
 ・カテゴリ数:11
 ・入力画像:32x64dot
 ・学習データ:32,472(=369*88)
 ・学習回数:20,000回(ミニバッチ100サンプル)

学習の経過:
 f:id:moonlight-aska:20170416113648p:plain:w300
 
5.2 評価
カテゴリ識別率と結果分析は, 以下の通り.

識別条件:
 ・カテゴリ数:11
 ・評価データ:84

識別率:
  TOP: 1 : 66.67 % ( 56 / 84 )
     2 : 77.38 % ( 65 / 84 )
     3 : 83.33 % ( 70 / 84 )

当初, 識別率75%程度は達成できるかと考えていたが, 66.7%と思ったほど識別率はよくなかった.
そこで, コンフュージョンマトリックスを作成してみると, スポート飲料, 果汁飲料の識別率が悪いことが分かる.

f:id:moonlight-aska:20170416115907p:plain

誤識別した画像を少し見たが, 画像だけからでは誤っても仕方ないかと思えるものもあった.
また, 各カテゴリの学習データ数も偏りがあり, そのあたりも誤識別の原因の可能性がある.

STEP1のカテゴリ認識については, 以上で終わり.
次回は, STEP2について紹介する.

---
[1] Deep MNIST for Experts | TensorFlow