出たとこデータサイエンス

アラサーでデータサイエンティストになったエンジニアが、覚えたことを書きなぐるためのブログ

TensorFlowでCNN実装(TensorBoardでの可視化有り)

初めてCNNを実装したので備忘録として。

目標

※ CNNの理論的詳細については定番のConvolutional Neural Networkをゼロから理解するを御覧ください。

方針

  • ネットワークはおおまかに畳み込み層1、畳み込み層2、全結合層、出力層に分かれる
    • 畳み込み層では畳込みとプーリングを行い、画像から特徴量を抽出
    • 全結合層では、畳み込み層で生成された特徴量の次元を落とす
    • 出力層で、10の数値のいずれかに分類
  • TensorBoardに下記を表示する
    • 各層の重みのヒストグラム
    • 学習途中のaccuracyとlossの推移
    • ネットワークのグラフ構造

コード

import tensorflow as tf
import tensorflow.examples.tutorials.mnist as mnist

# MNISTを保存
mnist_data = mnist.input_data.read_data_sets("../data/mnist/", one_hot=True)

def train_cnn():
    
    # TensorBoardに載せるsummaryのリスト
    summaries = []

    # 入力
    with tf.name_scope("data"):

        # 入力画像
        x = tf.placeholder(tf.float32,
                           [None, 784],
                           name="x")

        # 正解ラベル
        y = tf.placeholder(tf.float32,
                           [None, 10],
                           name="y")

    # 畳み込み層1
    with tf.name_scope("conv_1"):

        # 畳み込み可能なshapeに変換
        img = tf.reshape(x,
                         [-1, 28, 28, 1])

        # 畳み込みフィルタ
        f_1 = tf.Variable(tf.truncated_normal([5, 5, 1, 32],
                                              stddev=0.1),
                          name="f_1")

        # ログにf_1のヒストグラムを出力
        summaries.append(tf.summary.histogram("f_1", f_1))

        # 畳み込み演算
        conv_1 = tf.nn.conv2d(img,
                              f_1,
                              strides=[1, 1, 1, 1],
                              padding="SAME",
                              name="conv_1")

        # バイアス
        b_1 = tf.Variable(tf.constant(0.1,
                                      shape=[32]))

        # 活性化関数
        h_conv_1 = tf.nn.relu(conv_1 + b_1)

        # プーリング演算
        h_pool_1 = tf.nn.max_pool(h_conv_1,
                                  ksize=[1, 2, 2, 1],
                                  strides=[1, 2, 2, 1],
                                  padding="SAME")

    # 畳み込み層2
    with tf.name_scope("conv_2"):

        # 畳み込みフィルタ
        f_2 = tf.Variable(tf.truncated_normal([5, 5, 32, 64],
                                              stddev=0.1),
                          name="f_2")

        # ログにf_2のヒストグラムを出力
        summaries.append(tf.summary.histogram("f_2", f_2))

        # 畳み込み演算
        conv_2 = tf.nn.conv2d(h_pool_1,
                              f_2,
                              strides=[1, 1, 1, 1],
                              padding="SAME",
                              name="conv_2")

        # バイアス
        b_2 = tf.Variable(tf.constant(0.1,
                                      shape=[64]))

        # 活性化関数
        h_conv_2 = tf.nn.relu(conv_2 + b_2)

        # プーリング演算
        h_pool_2 = tf.nn.max_pool(h_conv_2,
                                  ksize=[1, 2, 2, 1],
                                  strides=[1, 2, 2, 1],
                                  padding="SAME")

    # 全結合層
    with tf.name_scope("fc"):

        # フラットな形に変換
        h_pool_2_flat = tf.reshape(h_pool_2,
                                   [-1, 7*7*64])

        # 全結合層の重み
        w_fc = tf.Variable(tf.truncated_normal([7*7*64, 1024],
                                               stddev=0.1),
                           name="w_fc")

        # ログにw_fcのヒストグラムを出力
        summaries.append(tf.summary.histogram("w_fc", w_fc))

        # バイアス
        b_fc = tf.Variable(tf.constant(0.1,
                                       shape=[1024]))

        # 活性化関数
        h_fc = tf.nn.relu(tf.matmul(h_pool_2_flat,
                                    w_fc) + b_fc)

    # 出力層
    with tf.name_scope("out"):

        # 全結合層の重み
        w_out = tf.Variable(tf.truncated_normal([1024, 10],
                                                stddev=0.1))

        # ログにw_outのヒストグラムを出力
        summaries.append(tf.summary.histogram("w_out", w_out))

        # バイアス
        b_out = tf.Variable(tf.constant(0.1,
                                        shape=[10]))

        # 活性化関数
        out = tf.nn.softmax(tf.matmul(h_fc,
                                   w_out) + b_out)

    # 誤差
    with tf.name_scope("loss"):

        # 誤差
        loss = tf.reduce_mean(-tf.reduce_sum(y * tf.log(out + 1e-5),
                                             axis=[1]))

        # 誤差をログに出力
        summaries.append(tf.summary.scalar("loss", loss))

    # 訓練
    with tf.name_scope("train"):
        train_step = tf.train.GradientDescentOptimizer(0.01).minimize(loss)

    # 評価
    with tf.name_scope("accuracy"):

        # 正解率
        correct = tf.equal(tf.argmax(out, 1),
                           tf.argmax(y, 1))
        accuracy = tf.reduce_mean(tf.cast(correct,
                                          tf.float32))

        # 正解率をログに出力
        summaries.append(tf.summary.scalar("accuracy", accuracy))

    # 初期化
    init = tf.global_variables_initializer()

    # 実行
    with tf.Session() as sess:

        # ログをひとまとめにする設定
        summary_op = tf.summary.merge(summaries)

        # ログの保管場所
        summary_writer = tf.summary.FileWriter("../logs",
                                               sess.graph)

        # 初期化
        sess.run(init)

        # テストデータ
        test_images, test_labels = mnist_data.test.images, mnist_data.test.labels

        # 学習
        for i in range(1000):

            # 訓練データ
            train_images, train_labels = mnist_data.train.next_batch(50)

            # ミニバッチ学習
            sess.run(train_step,
                     feed_dict={x: train_images,
                                y: train_labels})

            # 定期的にログ書き出し
            step = i + 1
            if step % 100 == 0:

                pass

                # ログのテキスト取得
                summary_text, acc_val = sess.run([summary_op, accuracy],
                                                 feed_dict={x: test_images,
                                                           y: test_labels})

                # ログに書き出し
                summary_writer.add_summary(summary_text,
                                           step)

                print("step: {} acc: {: .3}".format(step,
                                                    acc_val))
                
train_cnn()

出力

tensorboard --logdir logs/mnistを実行し、ブラウザからlocalhost:6006にアクセスすると、可視化結果が見られる。

Accuracy

f:id:mizuwan:20180630161752p:plain

Loss

f:id:mizuwan:20180630161801p:plain

出力層の活性化関数を間違えてSoftmaxではなくReluにしてしまっていたため、最初全く精度が出ずに焦った。。。