![]() |
![]() |
![]() |
![]() |
このチュートリアルでは、CSV データを tf.data.Dataset
にロードする手法の例を示します。
このチュートリアルで使われているデータはタイタニック号の乗客リストから取られたものです。乗客が生き残る可能性を、年齢、性別、チケットの等級、そして乗客が一人で旅行しているか否かといった特性から予測することを試みます。
設定
import functools
import numpy as np
import tensorflow as tf
TRAIN_DATA_URL = "https://storage.googleapis.com/tf-datasets/titanic/train.csv"
TEST_DATA_URL = "https://storage.googleapis.com/tf-datasets/titanic/eval.csv"
train_file_path = tf.keras.utils.get_file("train.csv", TRAIN_DATA_URL)
test_file_path = tf.keras.utils.get_file("eval.csv", TEST_DATA_URL)
Downloading data from https://storage.googleapis.com/tf-datasets/titanic/train.csv 32768/30874 [===============================] - 0s 0us/step Downloading data from https://storage.googleapis.com/tf-datasets/titanic/eval.csv 16384/13049 [=====================================] - 0s 0us/step
# numpy の値を読みやすくする
np.set_printoptions(precision=3, suppress=True)
データのロード
それではいつものように、扱っている CSV ファイルの先頭を見てみましょう。
head {train_file_path}
survived,sex,age,n_siblings_spouses,parch,fare,class,deck,embark_town,alone 0,male,22.0,1,0,7.25,Third,unknown,Southampton,n 1,female,38.0,1,0,71.2833,First,C,Cherbourg,n 1,female,26.0,0,0,7.925,Third,unknown,Southampton,y 1,female,35.0,1,0,53.1,First,C,Southampton,n 0,male,28.0,0,0,8.4583,Third,unknown,Queenstown,y 0,male,2.0,3,1,21.075,Third,unknown,Southampton,n 1,female,27.0,0,2,11.1333,Third,unknown,Southampton,n 1,female,14.0,1,0,30.0708,Second,unknown,Cherbourg,n 1,female,4.0,1,1,16.7,Third,G,Southampton,n
pandasでデータを読み込んでから Numpy の array を TensorFlow に渡すというのは可能です。もし、大規模なデータに対応するためにスケールアップしたり、データの読み込み処理を TensorFlow や tf.data に統合されたものにしたい場合、tf.data.experimental.make_csv_dataset
関数が利用できます。
The only column you need to identify explicitly is the one with the value that the model is intended to predict.
LABELS = [0, 1]
LABEL_COLUMN = 'survived'
コンストラクタの引数の値が揃ったので、ファイルから CSV データを読み込みデータセットを作ることにしましょう。
(完全なドキュメントは、tf.data.experimental.make_csv_dataset
を参照してください)
def get_dataset(file_path, **kwargs):
dataset = tf.data.experimental.make_csv_dataset(
file_path,
batch_size=5, # Artificially small to make examples easier to show.
label_name=LABEL_COLUMN,
na_value="?",
num_epochs=1,
ignore_errors=True,
**kwargs)
return dataset
raw_train_data = get_dataset(train_file_path)
raw_test_data = get_dataset(test_file_path)
def show_batch(dataset):
for batch, label in dataset.take(1):
for key, value in batch.items():
print("{:20s}: {}".format(key,value.numpy()))
データセットを構成する要素は、(複数のサンプル, 複数のラベル)の形のタプルとして表されるバッチです。サンプル中のデータは(行ベースのテンソルではなく)列ベースのテンソルとして構成され、それぞれはバッチサイズ(このケースでは5個)の要素が含まれます。
実際に見てみましょう。
show_batch(raw_train_data)
sex : [b'male' b'female' b'male' b'male' b'male'] age : [ 0.83 36. 7. 51. 28. ] n_siblings_spouses : [0 0 4 0 0] parch : [2 2 1 0 0] fare : [29. 71. 39.688 26.55 10.5 ] class : [b'Second' b'First' b'Third' b'First' b'Second'] deck : [b'unknown' b'B' b'unknown' b'E' b'unknown'] embark_town : [b'Southampton' b'Southampton' b'Southampton' b'Southampton' b'Southampton'] alone : [b'n' b'n' b'n' b'y' b'y']
上で見たように、この CSV の列には名前がついています。Dataset のコンストラクターはこれらの列名を自動的に抽出します。一行目に列名が記されていない CSV を扱う場合には、列名のリストを make_csv_dataset
関数の column_names
引数に渡してください。
CSV_COLUMNS = ['survived', 'sex', 'age', 'n_siblings_spouses', 'parch', 'fare', 'class', 'deck', 'embark_town', 'alone']
temp_dataset = get_dataset(train_file_path, column_names=CSV_COLUMNS)
show_batch(temp_dataset)
sex : [b'male' b'male' b'male' b'male' b'male'] age : [46. 40. 37. 20. 28.] n_siblings_spouses : [1 1 2 1 0] parch : [0 4 0 1 0] fare : [61.175 27.9 7.925 15.742 7.896] class : [b'First' b'Third' b'Third' b'Third' b'Third'] deck : [b'E' b'unknown' b'unknown' b'unknown' b'unknown'] embark_town : [b'Southampton' b'Southampton' b'Southampton' b'Cherbourg' b'Southampton'] alone : [b'n' b'n' b'n' b'n' b'y']
このサンプルでは利用できるすべての列を使います。もし、データセットから特定の列を除外したい場合には、利用したい列名のリストを作成し、コンストラクタのオプショナルな引数である select_columns
にそのリストを渡してください。
SELECT_COLUMNS = ['survived', 'age', 'n_siblings_spouses', 'class', 'deck', 'alone']
temp_dataset = get_dataset(train_file_path, select_columns=SELECT_COLUMNS)
show_batch(temp_dataset)
age : [28. 28. 23. 28. 28.] n_siblings_spouses : [0 0 2 1 2] class : [b'Third' b'First' b'Second' b'Third' b'Third'] deck : [b'unknown' b'unknown' b'unknown' b'unknown' b'unknown'] alone : [b'y' b'y' b'n' b'n' b'n']
データの前処理
CSV ファイルに含まれるデータには様々な型のものがありえます。データをモデルに入力する前に、典型的なケースでは、様々な型を含んだデータを固定長のベクトルに変換する必要があります。
TensorFlow には典型的なデータ変換を行う tf.feature_column
が組み込まれています。詳細はこのチュートリアルを参照してください。
また、任意のツール (たとえば、nltk や sklearn ) を使って前処理を行い、前処理した結果を TensorFlow にわたすこともできます。
モデルの内部で前処理を行う最大の利点は、モデルをエクスポートしたときにその中に前処理が含まれていることです。この方法を取れば、生データをそのままモデルに入力することができます。
連続データ
すでにデータが適切に数値型になっている場合、モデルに渡す前にそのデータをベクトル形式にできます。
SELECT_COLUMNS = ['survived', 'age', 'n_siblings_spouses', 'parch', 'fare']
DEFAULTS = [0, 0.0, 0.0, 0.0, 0.0]
temp_dataset = get_dataset(train_file_path,
select_columns=SELECT_COLUMNS,
column_defaults = DEFAULTS)
show_batch(temp_dataset)
age : [60. 24. 53. 28. 15.] n_siblings_spouses : [1. 0. 2. 0. 0.] parch : [1. 0. 0. 0. 0.] fare : [39. 49.504 51.479 7.896 7.225]
example_batch, labels_batch = next(iter(temp_dataset))
次のサンプルは、数値型の列をベクトルに変換するシンプルな関数の例です。
def pack(features, label):
return tf.stack(list(features.values()), axis=-1), label
この関数をデータセットのそれぞれの要素に適用します。
packed_dataset = temp_dataset.map(pack)
for features, labels in packed_dataset.take(1):
print(features.numpy())
print()
print(labels.numpy())
[[28. 0. 0. 56.496] [28. 1. 1. 15.246] [23. 0. 0. 15.046] [48. 1. 2. 65. ] [35. 1. 0. 83.475]] [0 1 0 1 1]
様々な型を含んだデータセットを利用する場合、シンプルな数値型のフィールドだけを分離したくなるかもしれません。tf.feature_column
はそのような処理を実現できますが、いくらかのオーバーヘッドを発生させるため、それが本当に必要になる場合以外ではできるだけ避けるべきでしょう。様々な型を含んだデータセットに話を戻しましょう。
show_batch(raw_train_data)
sex : [b'female' b'male' b'male' b'male' b'female'] age : [24. 28. 9. 23. 18.] n_siblings_spouses : [0 1 1 0 0] parch : [0 1 1 1 1] fare : [49.504 15.246 15.9 63.358 23. ] class : [b'First' b'Third' b'Third' b'First' b'Second'] deck : [b'C' b'unknown' b'unknown' b'D' b'unknown'] embark_town : [b'Cherbourg' b'Cherbourg' b'Southampton' b'Cherbourg' b'Southampton'] alone : [b'y' b'n' b'n' b'n' b'n']
example_batch, labels_batch = next(iter(temp_dataset))
数値型の特徴量を選んで、それらをベクトル化して単一の列に変換する、より一般的な前処理器を作成しましょう。
class PackNumericFeatures(object):
def __init__(self, names):
self.names = names
def __call__(self, features, labels):
numeric_features = [features.pop(name) for name in self.names]
numeric_features = [tf.cast(feat, tf.float32) for feat in numeric_features]
numeric_features = tf.stack(numeric_features, axis=-1)
features['numeric'] = numeric_features
return features, labels
NUMERIC_FEATURES = ['age','n_siblings_spouses','parch', 'fare']
packed_train_data = raw_train_data.map(
PackNumericFeatures(NUMERIC_FEATURES))
packed_test_data = raw_test_data.map(
PackNumericFeatures(NUMERIC_FEATURES))
show_batch(packed_train_data)
sex : [b'female' b'male' b'male' b'male' b'male'] class : [b'Third' b'Third' b'Third' b'Second' b'Second'] deck : [b'unknown' b'unknown' b'unknown' b'unknown' b'unknown'] embark_town : [b'Queenstown' b'Southampton' b'Southampton' b'Southampton' b'Southampton'] alone : [b'y' b'n' b'y' b'y' b'y'] numeric : [[28. 0. 0. 7.75 ] [22. 1. 0. 7.25 ] [28. 0. 0. 56.496] [36. 0. 0. 10.5 ] [16. 0. 0. 10.5 ]]
example_batch, labels_batch = next(iter(packed_train_data))
データの正規化
連続値をとるデータは常に正規化されるべきでしょう。
import pandas as pd
desc = pd.read_csv(train_file_path)[NUMERIC_FEATURES].describe()
desc
MEAN = np.array(desc.T['mean'])
STD = np.array(desc.T['std'])
def normalize_numeric_data(data, mean, std):
# Center the data
return (data-mean)/std
数値型の列を作成しましょう。tf.feature_columns.numeric_column
では normalizer_fn
引数で正則化のための関数を受け取ります。また、渡された関数はそれぞれのバッチに対して実行されます。
functools.partial
を用いて関数を部分適用し、normalize_numeric_data
の引数 MEAN
と STD
を固定しておきましょう。
# See what you just created.
normalizer = functools.partial(normalize_numeric_data, mean=MEAN, std=STD)
numeric_column = tf.feature_column.numeric_column('numeric', normalizer_fn=normalizer, shape=[len(NUMERIC_FEATURES)])
numeric_columns = [numeric_column]
numeric_column
NumericColumn(key='numeric', shape=(4,), default_value=None, dtype=tf.float32, normalizer_fn=functools.partial(<function normalize_numeric_data at 0x7eff403d9ae8>, mean=array([29.631, 0.545, 0.38 , 34.385]), std=array([12.512, 1.151, 0.793, 54.598])))
モデルの訓練時には、数値データのブロックを選び出して利用できるように、作成した特徴量の列を入力に含めましょう。
example_batch['numeric']
<tf.Tensor: shape=(5, 4), dtype=float32, numpy= array([[ 9. , 3. , 2. , 27.9 ], [32. , 0. , 0. , 7.925], [28. , 0. , 0. , 7.75 ], [ 1. , 1. , 2. , 20.575], [29. , 0. , 0. , 7.775]], dtype=float32)>
numeric_layer = tf.keras.layers.DenseFeatures(numeric_columns)
numeric_layer(example_batch).numpy()
array([[-1.649, 2.132, 2.043, -0.119], [ 0.189, -0.474, -0.479, -0.485], [-0.13 , -0.474, -0.479, -0.488], [-2.288, 0.395, 2.043, -0.253], [-0.05 , -0.474, -0.479, -0.487]], dtype=float32)
ここで用いられた、平均を用いた正規化を利用するためには、それぞれの列の平均値を事前に知っている必要があります。
カテゴリデータ
この CSV データ中のいくつかの列はカテゴリ列です。つまり、その中身は、限られた選択肢の中のひとつである必要があります。
tf.feature_column
API を用いて、それぞれのカテゴリ列のためのコレクションを tf.feature_column.indicator_column
で生成しましょう。
CATEGORIES = {
'sex': ['male', 'female'],
'class' : ['First', 'Second', 'Third'],
'deck' : ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'],
'embark_town' : ['Cherbourg', 'Southhampton', 'Queenstown'],
'alone' : ['y', 'n']
}
categorical_columns = []
for feature, vocab in CATEGORIES.items():
cat_col = tf.feature_column.categorical_column_with_vocabulary_list(
key=feature, vocabulary_list=vocab)
categorical_columns.append(tf.feature_column.indicator_column(cat_col))
# See what you just created.
categorical_columns
[IndicatorColumn(categorical_column=VocabularyListCategoricalColumn(key='sex', vocabulary_list=('male', 'female'), dtype=tf.string, default_value=-1, num_oov_buckets=0)), IndicatorColumn(categorical_column=VocabularyListCategoricalColumn(key='class', vocabulary_list=('First', 'Second', 'Third'), dtype=tf.string, default_value=-1, num_oov_buckets=0)), IndicatorColumn(categorical_column=VocabularyListCategoricalColumn(key='deck', vocabulary_list=('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'), dtype=tf.string, default_value=-1, num_oov_buckets=0)), IndicatorColumn(categorical_column=VocabularyListCategoricalColumn(key='embark_town', vocabulary_list=('Cherbourg', 'Southhampton', 'Queenstown'), dtype=tf.string, default_value=-1, num_oov_buckets=0)), IndicatorColumn(categorical_column=VocabularyListCategoricalColumn(key='alone', vocabulary_list=('y', 'n'), dtype=tf.string, default_value=-1, num_oov_buckets=0))]
categorical_layer = tf.keras.layers.DenseFeatures(categorical_columns)
print(categorical_layer(example_batch).numpy()[0])
[0. 1. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]
モデルを構築する際、これは入力レイヤーで行われるデータ処理の一部になります。
一体化した前処理レイヤー
2つの特徴量列のコレクションを結合し、tf.keras.layers.DenseFeatures
に入力して、両方のデータ型を読み込んで前処理する入力レイヤーを作成しましょう。
preprocessing_layer = tf.keras.layers.DenseFeatures(categorical_columns+numeric_columns)
print(preprocessing_layer(example_batch).numpy()[0])
[ 0. 1. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. -1.649 2.132 2.043 -0.119 0. 1. ]
モデルの構築
preprocessing_layer
から始まる tf.keras.Sequential
を構築しましょう。
model = tf.keras.Sequential([
preprocessing_layer,
tf.keras.layers.Dense(128, activation='relu'),
tf.keras.layers.Dense(128, activation='relu'),
tf.keras.layers.Dense(1),
])
model.compile(
loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
optimizer='adam',
metrics=['accuracy'])
訓練、評価、そして予測
これでモデルをインスタンス化し、訓練することができます。
train_data = packed_train_data.shuffle(500)
test_data = packed_test_data
model.fit(train_data, epochs=20)
Epoch 1/20 WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'collections.OrderedDict'> input: OrderedDict([('sex', <tf.Tensor 'ExpandDims_4:0' shape=(None, 1) dtype=string>), ('class', <tf.Tensor 'ExpandDims_1:0' shape=(None, 1) dtype=string>), ('deck', <tf.Tensor 'ExpandDims_2:0' shape=(None, 1) dtype=string>), ('embark_town', <tf.Tensor 'ExpandDims_3:0' shape=(None, 1) dtype=string>), ('alone', <tf.Tensor 'ExpandDims:0' shape=(None, 1) dtype=string>), ('numeric', <tf.Tensor 'IteratorGetNext:4' shape=(None, 4) dtype=float32>)]) Consider rewriting this model with the Functional API. WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'collections.OrderedDict'> input: OrderedDict([('sex', <tf.Tensor 'ExpandDims_4:0' shape=(None, 1) dtype=string>), ('class', <tf.Tensor 'ExpandDims_1:0' shape=(None, 1) dtype=string>), ('deck', <tf.Tensor 'ExpandDims_2:0' shape=(None, 1) dtype=string>), ('embark_town', <tf.Tensor 'ExpandDims_3:0' shape=(None, 1) dtype=string>), ('alone', <tf.Tensor 'ExpandDims:0' shape=(None, 1) dtype=string>), ('numeric', <tf.Tensor 'IteratorGetNext:4' shape=(None, 4) dtype=float32>)]) Consider rewriting this model with the Functional API. 126/126 [==============================] - 0s 3ms/step - loss: 0.5270 - accuracy: 0.7145 Epoch 2/20 126/126 [==============================] - 0s 3ms/step - loss: 0.4193 - accuracy: 0.8246 Epoch 3/20 126/126 [==============================] - 0s 3ms/step - loss: 0.4075 - accuracy: 0.8293 Epoch 4/20 126/126 [==============================] - 0s 3ms/step - loss: 0.3876 - accuracy: 0.8341 Epoch 5/20 126/126 [==============================] - 0s 3ms/step - loss: 0.3840 - accuracy: 0.8421 Epoch 6/20 126/126 [==============================] - 0s 3ms/step - loss: 0.3669 - accuracy: 0.8437 Epoch 7/20 126/126 [==============================] - 0s 3ms/step - loss: 0.3726 - accuracy: 0.8405 Epoch 8/20 126/126 [==============================] - 0s 3ms/step - loss: 0.3616 - accuracy: 0.8325 Epoch 9/20 126/126 [==============================] - 0s 3ms/step - loss: 0.3648 - accuracy: 0.8421 Epoch 10/20 126/126 [==============================] - 0s 3ms/step - loss: 0.3495 - accuracy: 0.8517 Epoch 11/20 126/126 [==============================] - 0s 3ms/step - loss: 0.3453 - accuracy: 0.8517 Epoch 12/20 126/126 [==============================] - 0s 3ms/step - loss: 0.3402 - accuracy: 0.8565 Epoch 13/20 126/126 [==============================] - 0s 3ms/step - loss: 0.3394 - accuracy: 0.8453 Epoch 14/20 126/126 [==============================] - 0s 3ms/step - loss: 0.3297 - accuracy: 0.8612 Epoch 15/20 126/126 [==============================] - 0s 3ms/step - loss: 0.3266 - accuracy: 0.8469 Epoch 16/20 126/126 [==============================] - 0s 3ms/step - loss: 0.3225 - accuracy: 0.8581 Epoch 17/20 126/126 [==============================] - 0s 3ms/step - loss: 0.3261 - accuracy: 0.8581 Epoch 18/20 126/126 [==============================] - 0s 3ms/step - loss: 0.3273 - accuracy: 0.8676 Epoch 19/20 126/126 [==============================] - 0s 3ms/step - loss: 0.3131 - accuracy: 0.8565 Epoch 20/20 126/126 [==============================] - 0s 3ms/step - loss: 0.3147 - accuracy: 0.8581 <tensorflow.python.keras.callbacks.History at 0x7eff40340438>
モデルの訓練が終わったら、test_data
データセットでの正解率をチェックできます。
test_loss, test_accuracy = model.evaluate(test_data)
print('\n\nTest Loss {}, Test Accuracy {}'.format(test_loss, test_accuracy))
WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'collections.OrderedDict'> input: OrderedDict([('sex', <tf.Tensor 'ExpandDims_4:0' shape=(None, 1) dtype=string>), ('class', <tf.Tensor 'ExpandDims_1:0' shape=(None, 1) dtype=string>), ('deck', <tf.Tensor 'ExpandDims_2:0' shape=(None, 1) dtype=string>), ('embark_town', <tf.Tensor 'ExpandDims_3:0' shape=(None, 1) dtype=string>), ('alone', <tf.Tensor 'ExpandDims:0' shape=(None, 1) dtype=string>), ('numeric', <tf.Tensor 'IteratorGetNext:4' shape=(None, 4) dtype=float32>)]) Consider rewriting this model with the Functional API. 53/53 [==============================] - 0s 3ms/step - loss: 0.4299 - accuracy: 0.8485 Test Loss 0.4298816919326782, Test Accuracy 0.8484848737716675
単一のバッチ、または、バッチからなるデータセットのラベルを推論する場合には、tf.keras.Model.predict
を使います。
predictions = model.predict(test_data)
# 結果のいくつかを表示
for prediction, survived in zip(predictions[:10], list(test_data)[0][1][:10]):
print("Predicted survival: {:.2%}".format(prediction[0]),
" | Actual outcome: ",
("SURVIVED" if bool(survived) else "DIED"))
WARNING:tensorflow:Layers in a Sequential model should only have a single input tensor, but we receive a <class 'collections.OrderedDict'> input: OrderedDict([('sex', <tf.Tensor 'ExpandDims_4:0' shape=(None, 1) dtype=string>), ('class', <tf.Tensor 'ExpandDims_1:0' shape=(None, 1) dtype=string>), ('deck', <tf.Tensor 'ExpandDims_2:0' shape=(None, 1) dtype=string>), ('embark_town', <tf.Tensor 'ExpandDims_3:0' shape=(None, 1) dtype=string>), ('alone', <tf.Tensor 'ExpandDims:0' shape=(None, 1) dtype=string>), ('numeric', <tf.Tensor 'IteratorGetNext:4' shape=(None, 4) dtype=float32>)]) Consider rewriting this model with the Functional API. Predicted survival: 215.91% | Actual outcome: DIED Predicted survival: -375.80% | Actual outcome: DIED Predicted survival: 63.23% | Actual outcome: SURVIVED Predicted survival: 15.39% | Actual outcome: SURVIVED Predicted survival: -233.62% | Actual outcome: DIED