Tensorflowで犬猫画像分類する
最近Tensorflowを勉強していて、試しに定番の(?)犬猫の画像分類をしてみました。僕がやったことをまとめると
- CNN
- tf.kerasは使わない
- TFRecordにデータを保存してそこからデータを引っ張り出してくる
- もちろんBatch
こんな感じのことを書きます。なのでこの記事の位置づけは、画像解析手法を書くというよりかはTensorflowの使い方みたいな感じです。
使ったデータ
データの出処
今回使ったデータはKaggleからダウンロードしました。
www.kaggle.com
TFRecordにデータを保存
今回はデータを一度TFRecordにバイナリ形式で保存します。まずは各画像ファイルへのパスとラベルをリストの中に収納します。ラベルは猫が1、犬が0となっています。
import numpy as np import tensorflow as tf cat_dir = './training_set/cats/' dog_dir = './training_set/dogs/' image_paths = [] labels = [] for fname in os.listdir(cat_dir): if '.jpg' in fname: image_paths.append(cat_dir + fname) labels.append(1) for fname in os.listdir(dog_dir): if '.jpg' in fname: image_paths.append(dog_dir + fname) labels.append(0) # シャッフルする shuffle_ind = np.random.permutation(len(labels)) image_paths = np.array(image_paths)[shuffle_ind] labels = np.array(labels)[shuffle_ind]
リストの後ろから1000個のファイルをテストデータとして切り分けてそれぞれ別ファイルに保存します。
def _bytes_feature(value): return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value])) def _float_feature(value): return tf.train.Feature(float_list=tf.train.FloatList(value=[value])) def _int64_feature(value): return tf.train.Feature(int64_list=tf.train.Int64List(value=[value])) from PIL import Image # トレーニングデータの保存 with tf.python_io.TFRecordWriter('training_data.tfrecords') as writer: for fname, label in zip(image_paths[:-1000], labels[:-1000]): image = Image.open(fname) image_np = np.array(image) image_shape = image_np.shape image = open(fname, 'rb').read() feature = { 'height' : _int64_feature(image_shape[0]), 'width' : _int64_feature(image_shape[1]), 'channel' : _int64_feature(image_shape[2]), 'image_raw' : _bytes_feature(image), # 画像はバイトとして保存する 'label' : _int64_feature(label) } tf_example = tf.train.Example(features=tf.train.Features(feature=feature)) writer.write(tf_example.SerializeToString()) # テストデータの保存 with tf.python_io.TFRecordWriter('test_data.tfrecords') as writer: for fname, label in zip(image_paths[-1000:], labels[-1000:]): image = Image.open(fname) image_np = np.array(image) image_shape = image_np.shape image = open(fname, 'rb').read() feature = { 'height' : _int64_feature(image_shape[0]), 'width' : _int64_feature(image_shape[1]), 'channel' : _int64_feature(image_shape[2]), 'image_raw' : _bytes_feature(image), 'label' : _int64_feature(label) } tf_example = tf.train.Example(features=tf.train.Features(feature=feature)) writer.write(tf_example.SerializeToString())
これでデータの保存はできました。
CNNによるモデルの構築
次に、画像の分類に用いるモデルをCNNで構築します。
tf.reset_default_graph() X = tf.placeholder(tf.float32, shape=[None, 150, 150, 3]) y = tf.placeholder(tf.int32, shape=[None]) with tf.name_scope('layer1'): conv1 = tf.layers.conv2d(X, filters=32, kernel_size=4, strides=1, activation=tf.nn.relu, name='conv1') pool1 = tf.nn.max_pool(conv1, ksize=[1,2,2,1], strides=[1,2,2,1], padding='VALID', name='pool1') with tf.name_scope('layer2'): conv2 = tf.layers.conv2d(pool1, filters=64, kernel_size=3, strides=1, activation=tf.nn.relu, name='conv2') pool2 = tf.nn.max_pool(conv2, ksize=[1,2,2,1], strides=[1,2,2,1], padding='VALID', name='pool2') with tf.name_scope('layer3'): conv3 = tf.layers.conv2d(pool2, filters=128, kernel_size=3, strides=1, activation=tf.nn.relu, name='conv3') pool3 = tf.nn.max_pool(conv3, ksize=[1,2,2,1], strides=[1,2,2,1], padding='VALID', name='pool3') with tf.name_scope('dense'): flatten = tf.reshape(pool3, shape=[-1, 32768], name='flatten') dense1 = tf.layers.dense(flatten, 512, activation=tf.nn.relu, name='dense1') dense2 = tf.layers.dense(dense1, 2, activation=None, name='dense2') output = tf.nn.softmax(dense2, name='output') with tf.name_scope('train'): xentropy = tf.losses.sparse_softmax_cross_entropy(logits=dense2, labels=y) loss = tf.reduce_mean(xentropy) optimizer = tf.train.AdamOptimizer() training_op = optimizer.minimize(loss) with tf.name_scope('eval'): correct = tf.nn.in_top_k(dense2, y, 1) acc = tf.reduce_mean(tf.cast(correct, tf.float32)) with tf.name_scope('save'): train_acc = tf.summary.scalar('train_acc', acc) valid_acc = tf.summary.scalar('valid_acc', acc) file_writer = tf.summary.FileWriter('./log/190401/', tf.get_default_graph()) saver = tf.train.Saver()
入力画像の形状は(150, 150, 3)で、カラー画像です。3つの畳み込み層と3つのプーリング層を重ね、最後に全結合層で長さ2のベクトルを出力しています。出力ベクトルのそれぞれの要素は、画像が犬である確率と猫である確率をそれぞれ表しています。
訓練はAdamを用いて行います。そして訓練の途中結果とパラメータを保存するためにFileWriterとSaverを用意します。
訓練する
では実際に訓練をします。まず、TFRecordからデータを取り出すための準備をしておきます。
image_feature_description = { 'height' : tf.FixedLenFeature([], tf.int64), 'width' : tf.FixedLenFeature([], tf.int64), 'channel' : tf.FixedLenFeature([], tf.int64), 'image_raw' : tf.FixedLenFeature([], tf.string), 'label' : tf.FixedLenFeature([], tf.int64), } def _parse_fun(example_proto): feature = tf.parse_single_example(example_proto, image_feature_description) feature['image_raw'] = tf.image.decode_jpeg(feature['image_raw']) feature['image_raw'] = tf.cast(feature['image_raw'], tf.float32) / 255.0 #floatにキャストしてから255で割って正規化 feature['image_raw'] = tf.image.resize_images(feature['image_raw'], (150, 150)) #150x150にリサイズ feature['label'] = tf.cast(feature['label'], tf.int32) return feature
では実際に訓練します。
epochs = 31 batch_size = 500 with tf.Session() as sess: raw_image_dataset = tf.data.TFRecordDataset('training_data.tfrecords') test_dataset = tf.data.TFRecordDataset('test_data.tfrecords') parsed_image_dataset = raw_image_dataset.map(_parse_fun) test_dataset = test_dataset.map(_parse_fun).batch(100) batched_dataset = parsed_image_dataset.batch(batch_size) init = tf.global_variables_initializer() init.run() for epoch in range(epochs): iterator = batched_dataset.make_one_shot_iterator() test_iter = test_dataset.make_one_shot_iterator() while True: try: batched = iterator.get_next() batched_eval = sess.run(batched) X_batch = batched_eval['image_raw'] y_batch = batched_eval['label'] sess.run(training_op, feed_dict={X: X_batch, y: y_batch}) except tf.errors.OutOfRangeError: break if epoch % 5 == 0: print(f"finished epoch #{epoch}") test_data = test_iter.get_next() test_data_eval = sess.run(test_data) X_test = test_data_eval['image_raw'] y_test = test_data_eval['label'] train_acc_str = train_acc.eval(feed_dict={X: X_batch, y: y_batch}) valid_acc_str = valid_acc.eval(feed_dict={X: X_test, y: y_test}) file_writer.add_summary(train_acc_str, epoch) file_writer.add_summary(valid_acc_str, epoch) save_path = saver.save(sess, './log/190401/model_ckpt_{}.ckpt'.format(epoch)) file_writer.close()
訓練ではバッチサイズを500として30エポック訓練します。訓練では、イテレータを作っておいてバッチサイズずつデータを取り出して訓練します。イテレータで次のデータを取り出せなくなったらtf.error.OutOfRangeError
を送出するので、それを受け取ってwhileループを抜けます。エポック数が5の倍数のときに訓練データとテストデータでの精度とモデルの重みパラメータを保存します。
訓練結果をTensorboardで見てみる
訓練したら、その結果をTensorboardで見てみます。シェルから
tensorboard --logdir=./log/190401/
と入力すると、アドレスが出てくるので、そこにアクセスすると、下のような画面が出てきます。
左側が訓練データの精度で右側がテストデータの精度です。これを見ると訓練データでは精度がほとんど1になっているのに対してテストデータでは精度が0.68にしかなっておらず、過学習していることがわかります。これを解消するには、モデルのパラメータを減らす方法やドロップアウトなどの正則化をかける方法、そして画像の水増しなどの方法があります。これらの方法についてはまた別の場所で書きます。
scikit-learnとTensorFlowによる実践機械学習
- 作者: Aurélien Géron,下田倫大,長尾高弘
- 出版社/メーカー: オライリージャパン
- 発売日: 2018/04/26
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る