TensorFlowは詳解なチュートリアルがありますが、データの入出力に関してはちょっとわかりづらいように思います。試行錯誤の結果、ようやく思うような動きをさせることができたのでこちらに記録します。
想定するデータ
2種類のカテゴリの画像を分類する問題を考えてみます。訓練用の画像を個別のjpegファイルとして、カテゴリごとにサブディレクトリで分けて保存してあるとしましょう。
+--dir1
| +-- file1.jpg
| +-- file2.jpg
:
+--dir2
+-- file01.jpg
+-- file02.jpg
:
画像ファイル名とカテゴリのリストをCSVで記録したものを用意します。カテゴリは0から始まる数値で表現するものとします。
dir1/file1.jpg,0
dir1/file2.jpg,0
dir2/file01.jpg,1
dir2/file02.jpg,1
(以下略)
コード
# -*- coding: utf-8 -*-
import tensorflow as tf
csv_name = 'path/to/filelist.csv'
fname_queue = tf.train.string_input_producer([csv_name])
reader = tf.TextLineReader()
key, val = reader.read(fname_queue)
fname, label = tf.decode_csv(val, [["aa"], [1]])
jpeg_r = tf.read_file(fname)
image = tf.image.decode_jpeg(jpeg_r, channels=3)
sess = tf.Session()
init = tf.initialize_all_variables()
sess.run(init)
tf.train.start_queue_runners(sess)
x = sess.run(image)
print(x)
CSVの読み込み
TensorFlowでファイルの読み込みを行うクラスについては、https://www.tensorflow.org/versions/master/api_docs/python/io_ops.html#readers で解説されています。
CSVを読み込むためには、1行単位でデータを読み取るTextLineReaderと、読み取った内容をCSVとしてデコードするdecode_csvを組み合わせます。
TextLineReader.read
TextLineReader.readで実際にデータを読み込むことができますが、直接ファイル名を与える手段は用意されておらず、ファイル名のリストを保持するQueueを与える必要があります。
Queueのインスタンスをtf.train.string_input_producerで生成します。今回の対象となるファイルは1つだけなので、引数にファイル名1つのみを持つリストを与えます。
このインスタンスをTextLineReader.readに与えます。Queueが処理されるごとに、keyとvalueに値が更新されてゆきます。keyにはファイル名の何行目かという文字列が、valueには行のデータそのものが入ります。
decode_csv
valueの内容をパースするために、decode_csvを用います。今回の場合は先頭からデータが入っており(見出し行がない)、デリミタはカンマなのでデフォルトでそのままパースできます。
1列目を文字列、2列目を数値として解釈するために、record_defaults引数[["aa"], [1]]を与えています。ここはデータ型そのものを指定するのではなく、代表値をいれるようです。
対話的な確認
対話実行モードでここまでの動きを確認してみます。
$ python
Python 2.7.9 (default, Mar 1 2015, 12:57:24)
[GCC 4.9.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import tensorflow as tf
>>> queue = tf.train.string_input_producer(["path/to/filelistlist.csv"])
>>> reader = tf.TextLineReader()
>>> key, value = reader.read(queue)
>>> fname, label = tf.decode_csv(value, [["aa"], [1]])
>>> sess = tf.InteractiveSession()
I tensorflow/core/common_runtime/local_device.cc:40] Local device intra op parallelism threads: 4
I tensorflow/core/common_runtime/direct_session.cc:58] Direct session inter op parallelism threads: 4
>>> tf.train.start_queue_runners(sess)
[<Thread(Thread-1, started daemon 140431553558272)>]
>>> key.eval()
'path/to/filelist.csv:1'
>>> value.eval()
'dir1/file2.jpg,0'
>>> fname.eval()
'dir2/file01.jpg'
>>> label.eval()
1
対話的に内容を見るため、InteractiveSessionを開始しています。また、Queueの処理を行うためにセッション内のQueueRunnerを起動する必要があります(tf.train.start_queue_runners)。
その上でkey.eval()を実行すると、最初の行を読み込んだ時の内容を表示できます。同様にvalue.eval()を実行すると2行目の内容が表示されます。これはkey.eval()を実行した時点でグラフ上のすべての処理が進んでしまったため、処理の対象が次のQueueに移ってしまっているからです。同様に、fname, labelの内容も確認できます。
jpegファイルの読み込み
単独のファイルを読み込むには、read_fileを使います。その内容をimage.decode_jpegに与えることで、画素の情報を3次元行列(横、縦、色面)として表現したものが得られます。
$ python tf-load.py
[[[246 237 194]
[249 240 197]
[251 242 199]
...,
[174 181 200]
[197 204 223]
[228 235 254]]
(中略)
[[190 183 128]
[192 185 130]
[194 187 132]
...,
[165 142 88]
[161 138 84]
[157 134 80]]]
例示したコードでは、channels=3を指定しています。これがないと、Tensorオブジェクトの時点でのshapeの色面数が不定(None、元画像がカラーかグレースケールかに依存する)になります。後の処理をするときに、不定のままだと困ることがある(たとえばbrightness処理ができない等)ので、用途に応じて明示しています。
Queueの仕組みについて
具体的な説明はHOW TO の中のhttps://www.tensorflow.org/versions/master/how_tos/threading_and_queues/index.html#threading-and-queues に書かれています。アニメーションgifを見ればなんとなく理解できると思います。
TensorFlowの持つI/O処理が全てこの仕組みの上に成り立っているので、学び始めのうちはちょっとオーバースペック気味で把握するのが難しいと感じたので、記録としてこの記事を書いてみました。
インタラクティブセッションとevalの組み合わせを、チュートリアルのコードの途中に挟んでみるとより理解しやすいと思います。
待望のデバッガ登場 (2017/2/23追記)
今の段階ではexperimentalではありますが、デバッガ(tfdbg)が実装されました。
1.0でも使えるようです。
$ python -m tensorflow.python.debug.examples.debug_mnist --debug
実行時のVariableの中身をみたりできて、理解をより進めやすくなりそうです。