In [ ]:
#@title Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
Note: これらのドキュメントは私たちTensorFlowコミュニティが翻訳したものです。コミュニティによる 翻訳はベストエフォートであるため、この翻訳が正確であることや英語の公式ドキュメントの 最新の状態を反映したものであることを保証することはできません。 この翻訳の品質を向上させるためのご意見をお持ちの方は、GitHubリポジトリtensorflow/docsにプルリクエストをお送りください。 コミュニティによる翻訳やレビューに参加していただける方は、 docs-ja@tensorflow.org メーリングリストにご連絡ください。
データの読み込みを効率的にするには、データをシリアライズし、連続的に読み込めるファイルのセット(各ファイルは100-200MB)に保存することが有効です。データをネットワーク経由で流そうとする場合には、特にそうです。また、データの前処理をキャッシュする際にも役立ちます。
TFRecord形式は、バイナリレコードの系列を保存するための単純な形式です。
プロトコルバッファ は、構造化データを効率的にシリアライズする、プラットフォームや言語に依存しないライブラリです。
プロトコルメッセージは.proto
という拡張子のファイルで定義されます。メッセージ型を識別する最も簡単な方法です。
tf.Example
メッセージ(あるいはプロトコルバッファ)は、{"string": value}
形式のマッピングを表現する柔軟なメッセージ型です。これは、TensorFlow用に設計され、TFXのような上位レベルのAPIで共通に使用されています。
このノートブックでは、tf.Example
メッセージの作成、パースと使用法をデモし、その後、tf.Example
メッセージをパースして、.tfrecord
に書き出し、その後読み取る方法を示します。
注:こうした構造は有用ですが必ずそうしなければならなというものではありません。tf.data
を使っていて、それでもなおデータの読み込みが訓練のボトルネックである場合でなければ、既存のコードをTFRecordsを使用するために変更する必要はありません。データセットの性能改善のヒントは、 Data Input Pipeline Performanceを参照ください。
In [ ]:
import tensorflow.compat.v1 as tf
import numpy as np
import IPython.display as display
基本的にはtf.Example
は{"string": tf.train.Feature}
というマッピングです。
tf.train.Feature
メッセージ型は次の3つの型のうち1つをとることができます(.proto fileを参照)。一般的なデータ型の多くは、これらの型のいずれかに強制的に変換することができます。
tf.train.BytesList
(次の型のデータを扱うことが可能)string
byte
tf.train.FloatList
(次の型のデータを扱うことが可能)float
(float32
)double
(float64
) tf.train.Int64List
(次の型のデータを扱うことが可能)bool
enum
int32
uint32
int64
uint64
通常のTensorFlowの型をtf.Example
互換の tf.train.Feature
に変換するには、次のショートカット関数を使うことができます。
どの関数も、1個のスカラー値を入力とし、上記の3つのlist
型のうちの一つを含むtf.train.Feature
を返します。
In [ ]:
# 下記の関数を使うと値を tf.Exampleと互換性の有る型に変換できる
def _bytes_feature(value):
"""string / byte 型から byte_listを返す"""
return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value]))
def _float_feature(value):
"""float / double 型から float_listを返す"""
return tf.train.Feature(float_list=tf.train.FloatList(value=[value]))
def _int64_feature(value):
"""bool / enum / int / uint 型から Int64_listを返す"""
return tf.train.Feature(int64_list=tf.train.Int64List(value=[value]))
注:単純化のため、このサンプルではスカラー値の入力のみを扱っています。スカラー値ではない特徴を扱う最も簡単な方法は、tf.serialize_tensor
を使ってテンソルをバイナリ文字列に変換する方法です。TensorFlowでは文字列はスカラー値として扱います。バイナリ文字列をテンソルに戻すには、tf.parse_tensor
を使用します。
上記の関数の使用例を下記に示します。入力が様々な型であるのに対して、出力が標準化されていることに注目してください。入力が、強制変換できない型であった場合、例外が発生します。(例:_int64_feature(1.0)
はエラーとなります。1.0
が浮動小数点数であるためで、代わりに_float_feature
関数を使用すべきです)
In [ ]:
print(_bytes_feature(b'test_string'))
print(_bytes_feature(u'test_bytes'.encode('utf-8')))
print(_float_feature(np.exp(1)))
print(_int64_feature(True))
print(_int64_feature(1))
メッセージはすべて.SerializeToString
を使ってバイナリ文字列にシリアライズすることができます。
In [ ]:
feature = _float_feature(np.exp(1))
feature.SerializeToString()
既存のデータからtf.Example
を作成したいとします。実際には、データセットの出処はどこでも良いのですが、1件の観測記録からtf.Example
メッセージを作る手順は同じです。
観測記録それぞれにおいて、各値は上記の関数を使って3種類の互換性のある型のうち1つだけを含むtf.train.Feature
に変換する必要があります。
次に、特徴の名前を表す文字列と、#1で作ったエンコード済みの特徴量を対応させたマップ(ディクショナリ)を作成します。
#2で作成したマップをFeaturesメッセージに変換します。
このノートブックでは、NumPyを使ってデータセットを作成します。
このデータセットには4つの特徴量があります。
False
または True
を表す論理値。出現確率は等しいものとします。[0,5)
の範囲から一様にサンプリングした整数値。サンプルは上記の分布から独立して同じ様に分布した10,000件の観測記録からなるものとします。
In [ ]:
# データセットに含まれる観測結果の件数
n_observations = int(1e4)
# ブール特徴量 FalseまたはTrueとしてエンコードされている
feature0 = np.random.choice([False, True], n_observations)
# 整数特徴量 0以上 5未満の乱数
feature1 = np.random.randint(0, 5, n_observations)
# バイト文字列特徴量
strings = np.array([b'cat', b'dog', b'chicken', b'horse', b'goat'])
feature2 = strings[feature1]
# 浮動小数点数特徴量 標準正規分布から発生
feature3 = np.random.randn(n_observations)
これらの特徴量は、_bytes_feature
, _float_feature
, _int64_feature
のいずれかを使ってtf.Example
互換の型に強制変換されます。その後、エンコード済みの特徴量からtf.Example
メッセージを作成できます。
In [ ]:
def serialize_example(feature0, feature1, feature2, feature3):
"""
Creates a tf.Example message ready to be written to a file.
ファイル出力可能なtf.Exampleメッセージを作成する
"""
# 特徴量名とtf.Example互換データ型との対応ディクショナリを作成
feature = {
'feature0': _int64_feature(feature0),
'feature1': _int64_feature(feature1),
'feature2': _bytes_feature(feature2),
'feature3': _float_feature(feature3),
}
# tf.train.Exampleを用いて特徴メッセージを作成
example_proto = tf.train.Example(features=tf.train.Features(feature=feature))
return example_proto.SerializeToString()
例えば、データセットに[False, 4, bytes('goat'), 0.9876]
という1つの観測記録があるとします。create_message()
を使うとこの観測記録からtf.Example
メッセージを作成し印字できます。上記のように、観測記録一つ一つがFeatures
メッセージとして書かれています。tf.Example
メッセージは、このFeatures
メッセージを包むラッパーに過ぎないことに注意してください。
In [ ]:
# データセットからの観測記録の例
example_observation = []
serialized_example = serialize_example(False, 4, b'goat', 0.9876)
serialized_example
メッセージをデコードするには、tf.train.Example.FromString
メソッドを使用します。
In [ ]:
example_proto = tf.train.Example.FromString(serialized_example)
example_proto
TFRecordファイルにはレコードのシーケンスが含まれます。このファイルはシーケンシャル読み取りのみが可能です。
それぞれのレコードには、データを格納するためのバイト文字列とデータ長、そして整合性チェックのためのCRC32C(Castagnoli多項式を使った32ビットのCRC)ハッシュ値が含まれます。
各レコードのフォーマットは下記の通りです。
uint64 長さ
uint32 長さのマスク済みcrc32ハッシュ値
byte data[長さ]
uint32 データのマスク済みcrc32ハッシュ値
複数のレコードが結合されてファイルを構成します。CRCについてはここに說明があります。CRCのマスクは下記のとおりです。
masked_crc = ((crc >> 15) | (crc << 17)) + 0xa282ead8ul
注:TFRecordファイルを作るのに、tf.Example
を使わなければならないということはありません。tf.Example
は、ディクショナリをバイト文字列にシリアライズする方法の1つです。エンコードされた画像データや、(tf.io.serialize_tensor
を使ってシリアライズされ、tf.io.parse_tensor
で読み込まれる)シリアライズされたテンソルもあります。その他のオプションについては、tf.io
モジュールを参照してください。
tf.data
モジュールには、TensorFlowでデータを読み書きするツールが含まれます。
In [ ]:
tf.data.Dataset.from_tensor_slices(feature1)
配列のタプルに適用すると、タプルのデータセットが返されます。
In [ ]:
features_dataset = tf.data.Dataset.from_tensor_slices((feature0, feature1, feature2, feature3))
features_dataset
In [ ]:
# データセットから1つのサンプルだけを取り出すには`take(1)` を使います。
for f0,f1,f2,f3 in features_dataset.take(1):
print(f0)
print(f1)
print(f2)
print(f3)
Dataset
のそれぞれの要素に関数を適用するには、tf.data.Dataset.map
メソッドを使用します。
マップされる関数はTensorFlowのグラフモードで動作する必要があります。関数はtf.Tensors
を処理し、返す必要があります。create_example
のような非テンソル関数は、互換性のためtf.py_func
でラップすることができます。
tf.py_func
を使用する際には、シェイプと型は取得できないため、指定する必要があります。
In [ ]:
def tf_serialize_example(f0,f1,f2,f3):
tf_string = tf.py_func(
serialize_example,
(f0,f1,f2,f3), # pass these args to the above function.
tf.string) # the return type is `tf.string`.
return tf.reshape(tf_string, ()) # The result is a scalar
この関数をデータセットのそれぞれの要素に適用します。
In [ ]:
serialized_features_dataset = features_dataset.map(tf_serialize_example)
serialized_features_dataset
TFRecordファイルに書き出します。
In [ ]:
filename = 'test.tfrecord'
writer = tf.data.experimental.TFRecordWriter(filename)
writer.write(serialized_features_dataset)
tf.data.TFRecordDataset
クラスを使ってTFRecordファイルを読み込むこともできます。
tf.data
を使ってTFRecordファイルを取り扱う際の詳細については、こちらを参照ください。
TFRecordDataset
を使うことは、入力データを標準化し、パフォーマンスを最適化するのに有用です。
In [ ]:
filenames = [filename]
raw_dataset = tf.data.TFRecordDataset(filenames)
raw_dataset
この時点で、データセットにはシリアライズされたtf.train.Example
メッセージが含まれています。データセットをイテレートすると、スカラーの文字列テンソルが返ってきます。
.take
メソッドを使って最初の10レコードだけを表示します。
注:tf.data.Dataset
をイテレートできるのは、Eager Executionが有効になっている場合のみです。
In [ ]:
for raw_record in raw_dataset.take(10):
print(repr(raw_record))
これらのテンソルは下記の関数でパースできます。
注:ここでは、feature_description
が必要です。データセットはグラフ実行を使用するため、この記述を使ってシェイプと型を構築するのです。
In [ ]:
# 特徴の記述
feature_description = {
'feature0': tf.FixedLenFeature([], tf.int64, default_value=0),
'feature1': tf.FixedLenFeature([], tf.int64, default_value=0),
'feature2': tf.FixedLenFeature([], tf.string, default_value=''),
'feature3': tf.FixedLenFeature([], tf.float32, default_value=0.0),
}
def _parse_function(example_proto):
# 上記の記述を使って入力のtf.Exampleを処理
return tf.parse_single_example(example_proto, feature_description)
あるいは、tf.parse example
を使ってバッチ全体を一度にパースします。
tf.data.Dataset.map
メソッドを使って、データセットの各アイテムにこの関数を適用します。
In [ ]:
parsed_dataset = raw_dataset.map(_parse_function)
parsed_dataset
Eager Execution を使ってデータセット中の観測記録を表示します。このデータセットには10,000件の観測記録がありますが、最初の10個だけ表示します。
データは特徴量のディクショナリの形で表示されます。それぞれの項目はtf.Tensor
であり、このテンソルのnumpy
要素は特徴量を表します。
In [ ]:
for parsed_record in parsed_dataset.take(10):
print(repr(raw_record))
ここでは、tf.parse_example
がtf.Example
のフィールドを通常のテンソルに展開しています。
tf.python_io
モジュールには、TFRecordファイルの読み書きのための純粋なPython関数も含まれています。
次にこの10,000件の観測記録をtest.tfrecords
ファイルに出力します。観測記録はそれぞれtf.Example
メッセージに変換され、ファイルに出力されます。その後、test.tfrecords
ファイルが作成されたことを確認することができます。
In [ ]:
# `tf.Example`観測記録をファイルに出力
with tf.python_io.TFRecordWriter(filename) as writer:
for i in range(n_observations):
example = serialize_example(feature0[i], feature1[i], feature2[i], feature3[i])
writer.write(example)
In [ ]:
!ls
モデルに入力にするため、このデータを読み込みたいとしましょう。
次の例では、データをそのまま、tf.Example
メッセージとしてインポートします。これは、ファイルが期待されるデータを含んでいるかを確認するのに役に立ちます。これは、また、入力データがTFRecordとして保存されているが、この例のようにNumPyデータ(またはそれ以外のデータ型)として入力したい場合に有用です。このコーディング例では値そのものを読み取れるからです。
入力ファイルの中のTFRecordをイテレートして、tf.Example
メッセージを取り出し、その中の値を読み取って保存できます。
In [ ]:
record_iterator = tf.python_io.tf_record_iterator(path=filename)
for string_record in record_iterator:
example = tf.train.Example()
example.ParseFromString(string_record)
print(example)
# Exit after 1 iteration as this is purely demonstrative.
# 純粋にデモであるため、イテレーションの1回目で終了
break
(上記で作成したtf.Example
型の)example
オブジェクトの特徴量は(他のプロトコルバッファメッセージと同様に)ゲッターを使ってアクセス可能です。example.features
はrepeated feature
メッセージを返し、feature
メッセージをを取得すると(Pythonのディクショナリとして保存された)特徴量の名前と特徴量の値のマッピングが得られます。
In [ ]:
print(dict(example.features.feature))
このディクショナリから、指定した値をディクショナリとして得ることができます。
In [ ]:
print(example.features.feature['feature3'])
次に、ゲッターを使って値にアクセスできます。
In [ ]:
print(example.features.feature['feature3'].float_list.value)
In [ ]:
cat_in_snow = tf.keras.utils.get_file('320px-Felis_catus-cat_on_snow.jpg', 'https://upload.wikimedia.org/wikipedia/commons/thumb/b/b6/Felis_catus-cat_on_snow.jpg/320px-Felis_catus-cat_on_snow.jpg')
williamsburg_bridge = tf.keras.utils.get_file('194px-New_East_River_Bridge_from_Brooklyn_det.4a09796u.jpg','https://upload.wikimedia.org/wikipedia/commons/thumb/f/fe/New_East_River_Bridge_from_Brooklyn_det.4a09796u.jpg/194px-New_East_River_Bridge_from_Brooklyn_det.4a09796u.jpg')
In [ ]:
display.display(display.Image(filename=cat_in_snow))
display.display(display.HTML('Image cc-by: <a "href=https://commons.wikimedia.org/wiki/File:Felis_catus-cat_on_snow.jpg">Von.grzanka</a>'))
In [ ]:
display.display(display.Image(filename=williamsburg_bridge))
display.display(display.HTML('<a "href=https://commons.wikimedia.org/wiki/File:New_East_River_Bridge_from_Brooklyn_det.4a09796u.jpg">source</a>'))
上記で行ったように、この特徴量をtf.Example
と互換のデータ型にエンコードできます。この場合には、生画像のバイト文字列を特徴として保存するだけではなく、縦、横のサイズにチャネル数、更に画像を保存する際に猫の画像と橋の画像を区別するためのlabel
特徴量を付け加えます。猫の画像には0
を、橋の画像には1
を使うことにしましょう。
In [ ]:
image_labels = {
cat_in_snow : 0,
williamsburg_bridge : 1,
}
In [ ]:
# 猫の画像を使った例
image_string = open(cat_in_snow, 'rb').read()
label = image_labels[cat_in_snow]
# 関連する特徴量のディクショナリを作成
def image_example(image_string, label):
image_shape = tf.image.decode_jpeg(image_string).shape
feature = {
'height': _int64_feature(image_shape[0]),
'width': _int64_feature(image_shape[1]),
'depth': _int64_feature(image_shape[2]),
'label': _int64_feature(label),
'image_raw': _bytes_feature(image_string),
}
return tf.train.Example(features=tf.train.Features(feature=feature))
for line in str(image_example(image_string, label)).split('\n')[:15]:
print(line)
print('...')
ご覧のように、すべての特徴量がtf.Example
メッセージに保存されました。上記のコードを関数化し、このサンプルメッセージをimages.tfrecords
ファイルに書き込みます。
In [ ]:
# 生の画像をimages.tfrecordsファイルに書き出す
# まず、2つの画像をtf.Exampleメッセージに変換し、
# 次に.tfrecordsファイルに書き出す
with tf.python_io.TFRecordWriter('images.tfrecords') as writer:
for filename, label in image_labels.items():
image_string = open(filename, 'rb').read()
tf_example = image_example(image_string, label)
writer.write(tf_example.SerializeToString())
In [ ]:
!ls
In [ ]:
raw_image_dataset = tf.data.TFRecordDataset('images.tfrecords')
# 特徴量を記述するディクショナリを作成
image_feature_description = {
'height': tf.FixedLenFeature([], tf.int64),
'width': tf.FixedLenFeature([], tf.int64),
'depth': tf.FixedLenFeature([], tf.int64),
'label': tf.FixedLenFeature([], tf.int64),
'image_raw': tf.FixedLenFeature([], tf.string),
}
def _parse_image_function(example_proto):
# 入力のtf.Exampleのプロトコルバッファを上記のディクショナリを使って解釈
return tf.parse_single_example(example_proto, image_feature_description)
parsed_image_dataset = raw_image_dataset.map(_parse_image_function)
parsed_image_dataset
TFRecordファイルから画像を復元しましょう。
In [ ]:
for image_features in parsed_image_dataset:
image_raw = image_features['image_raw'].numpy()
display.display(display.Image(data=image_raw))