« AI特集のInterface 12月号を買いました! | トップページ | Windows 10でスタートメニューも通知もタスクマネージャーも”管理者権限で実行”も開かなくなった・・・時にやったこと »

2017年11月10日 (金)

TensorFlowで畳み込みニューラルネットワークの認識を可視化する

ディープラーニングというのは非常に精度が高くて素晴らしい技術なんですが、学習結果がブラックボックス化されていて何が行われているのかわからない、という声もあるのは事実。

ある意味どうしようもないんですけど、せめてちょっとくらいは可視化できないものか?

以下の記事を参考に、トライしてみました。

畳み込み層とプーリング層を画像表示してみるてすと - Qiita

ここで紹介するコードは、以下の記事で作ったコードで学習させたものを使っています。

”けものフレンズ”動画顔検出器をディープラーニング+OpenCV物体検出で作る(転移学習&メモリ不足対策): EeePCの軌跡

ただ、教師データは「けもフレ」ではなくて、以下の記事で書いた「お城」を使ってます。

TensorFlowで”日本のお城”を識別させてみた: EeePCの軌跡

では、まずコードを紹介しておきます。

◆cnn_png_56.py

#!/usr/bin/env python

import glob
import os
import sys
import numpy as np
import tensorflow as tf
import cv2
import math
from PIL import Image

path=os.getcwd()+'/analysis/'
file_list=os.listdir(path)

i = 0
label_name = []

flags = tf.app.flags
FLAGS = flags.FLAGS
flags.DEFINE_string('label','label.txt','File name of label')

f = open(FLAGS.label,'r')
for line in f:
  line = line.rstrip()
  l = line.rstrip()
  label_name.append(l)
  i = i + 1

NUM_CLASSES = i
IMAGE_SIZE = 56
IMAGE_PIXELS = IMAGE_SIZE*IMAGE_SIZE*3

test_image = []
test_filenm = []

for file in file_list:
    test_filenm.append(file)

    img = cv2.imread('./analysis/' + file )
    img = cv2.resize(img, (IMAGE_SIZE, IMAGE_SIZE))
    test_image.append(img.flatten().astype(np.float32)/255.0)

test_image = np.asarray(test_image)

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)

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

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

def save_image(file_name, image_ndarray, cols=8):
    # 画像数, 幅, 高さ
    count, w, h = image_ndarray.shape
    # 縦に画像を配置する数
    rows = int((count - 1) / cols) + 1
    # 復数の画像を大きな画像に配置し直す
    canvas = Image.new("RGB", (w * cols + (cols - 1), h * rows + (rows - 1)), (0x80, 0x80, 0x80))
    for i, image in enumerate(image_ndarray):
        # 横の配置座標
        x_i = int(i % cols)
        x = int(x_i * w + x_i * 1)
        # 縦の配置座標
        y_i = int(i / cols)
        y = int(y_i * h + y_i * 1)
        out_image = Image.fromarray(np.uint8(image))
        canvas.paste(out_image, (x, y))
    canvas.save('images/' + file_name, "PNG")

def channels_to_images(channels):
    count = channels.shape[2]
    images = []
    for i in range(count):
        image = []
        for line in channels:
            out_line = [pix[i] for pix in line]
            image.append(out_line)
        images.append(image)
    return np.array(images) * 255

images_placeholder = tf.placeholder("float", shape=(None, IMAGE_PIXELS))
labels_placeholder = tf.placeholder("float", shape=(None, NUM_CLASSES))
keep_prob = tf.placeholder("float")

x_image = tf.reshape(images_placeholder, [-1, 56, 56, 3])

with tf.name_scope('A_conv1') as scope:
    W_conv1 = weight_variable([3, 3, 3, 32])
    b_conv1 = bias_variable([32])
    h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)

with tf.name_scope('A_pool1') as scope:
    h_pool1 = max_pool_2x2(h_conv1)

with tf.name_scope('A_conv2') as scope:
    W_conv2 = weight_variable([3, 3, 32, 64])
    b_conv2 = bias_variable([64])
    h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)

with tf.name_scope('A_pool2') as scope:
    h_pool2 = max_pool_2x2(h_conv2)

with tf.name_scope('A_conv3') as scope:
    W_conv3 = weight_variable([3, 3, 64, 128])
    b_conv3 = bias_variable([128])
    h_conv3 = tf.nn.relu(conv2d(h_pool2, W_conv3) + b_conv3)

with tf.name_scope('A_pool3') as scope:
    h_pool3 = max_pool_2x2(h_conv3)

with tf.name_scope('fc1') as scope:
    W_fc1 = weight_variable([7*7*128, 1024])
    b_fc1 = bias_variable([1024])
    h_pool3_flat = tf.reshape(h_pool3, [-1, 7*7*128])
    h_fc1 = tf.nn.relu(tf.matmul(h_pool3_flat, W_fc1) + b_fc1)
    h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

with tf.name_scope('fc2') as scope:
    W_fc2 = weight_variable([1024, NUM_CLASSES])
    b_fc2 = bias_variable([NUM_CLASSES])

with tf.name_scope('softmax') as scope:
    y_conv=tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)

with tf.Session() as sess:

    saver = tf.train.Saver()
    sess.run(tf.global_variables_initializer())
    saver.restore(sess, "./model.ckpt")

    feed_dict = {images_placeholder : test_image, keep_prob: 1.0}
    tag = "test"

    h_conv1_result = h_conv1.eval(feed_dict=feed_dict)
    for i, result in enumerate(h_conv1_result):
        images = channels_to_images(result)
        save_image("1_%s_h_conv1_%02d.png" % (tag, i), images)

    # プーリング1層
    h_pool1_result = h_pool1.eval(feed_dict=feed_dict)
    for i, result in enumerate(h_pool1_result):
        images = channels_to_images(result)
        save_image("2_%s_h_pool1_%02d.png" % (tag, i), images)

    h_conv2_result = h_conv2.eval(feed_dict=feed_dict)
    for i, result in enumerate(h_conv2_result):
        images = channels_to_images(result)
        save_image("3_%s_h_conv2_%02d.png" % (tag, i), images)

    # プーリング2層
    h_pool2_result = h_pool2.eval(feed_dict=feed_dict)
    for i, result in enumerate(h_pool2_result):
        images = channels_to_images(result)
        save_image("3_%s_h_pool1_%02d.png" % (tag, i), images)

    h_conv3_result = h_conv3.eval(feed_dict=feed_dict)
    for i, result in enumerate(h_conv3_result):
        images = channels_to_images(result)
        save_image("4_%s_h_conv2_%02d.png" % (tag, i), images)

    # プーリング3層
    h_pool3_result = h_pool3.eval(feed_dict=feed_dict)
    for i, result in enumerate(h_pool3_result):
        images = channels_to_images(result)
        save_image("5_%s_h_pool3_%02d.png" % (tag, i), images)

実行方法は単純に、

> python cnn_png_56.py

です。

このコードと同じ階層に”analysis”というフォルダを作り、その中の画像を読み込んで可視化を行ってくれます。この辺りの動作は過去の”cnn_app_56.py”と同じ。

ただし、上記コード中の”saver.restore(sess, "./model.ckpt")”の中の”model.ckpt”は、各自の学習済データのファイル名に合わせて変えてください。

これを実行した結果ですが、7種類のお城を判別する学習済データ(56×56版)を読み込み、以下の画像ファイルを読み込ませました。

5_oosaka03

大坂城ですね。このお城を学習済みデータに通すとどうなるのか?

このコードはConv層+Pool層が3つに結合層2つ、Softmaxが一つという階層になってます。

このうち、最初のConv層を通ってきた画像がこちら。

1_test_h_conv1_17

・・・わかりにくいですけど、なんとなくお城って感じの形が見えます。

Conv層の後の画像とは、要するに3×3のフィルターを通した後の画像が出てきているようです。

ここで浮き出てくるものが、この画像のどの部分の特徴をとらえようとしてるのか・・・という解釈でいいみたいです。

ここが真っ黒だと何も見ていないのと同じことになってしまうみたいですが、一応真っ黒という部分はありませんね(限りなく黒はたくさんありますけど)。

これが最後の3つ目のPool層を通るとどうなるのか?

5_test_h_pool3_17

・・・ますますなんだかわかりませんね。

この層では7×7の画像×128個になってます。なんとなくお城っぽい形をマクロにとらえているようですが・・・

可視化したものの、どう解釈すればいいのかわかりづらいところです。

0_okazaki01

さて、この岡崎城の画像は誤認識されやすい画像です。

これを通してみると、どうなるんでしょうか?

1_test_h_conv1_00

1層目のConv層ですが、なんというかノイズのような画像が多いですね。あまりお城には見えません。

もしかして、岡崎城の認識率が悪いのは、そのせいかもしれません。

わかったような、わからないような結果ですが、会社ではこのコードを改良して使っています。どのあたりの特徴をとらえて判別しているかが大体わかるため、学習の進度を知るために一応参考で使っています。

もうちょっと見やすくする方法があるといいのですが、今のところこれがましな方のようです。

ディープラーニングを可視化したいと思っている方は、参考になさってください。

初めてのTensorFlow 数式なしのディープラーニング

« AI特集のInterface 12月号を買いました! | トップページ | Windows 10でスタートメニューも通知もタスクマネージャーも”管理者権限で実行”も開かなくなった・・・時にやったこと »

数値解析系」カテゴリの記事

コメント

コメントを書く

(ウェブ上には掲載しません)

トラックバック


この記事へのトラックバック一覧です: TensorFlowで畳み込みニューラルネットワークの認識を可視化する:

« AI特集のInterface 12月号を買いました! | トップページ | Windows 10でスタートメニューも通知もタスクマネージャーも”管理者権限で実行”も開かなくなった・・・時にやったこと »

当ブログ内検索

スポンサード リンク

ブログ村

無料ブログはココログ