tf.data を使って CSV をロードする

コレクションでコンテンツを整理 必要に応じて、コンテンツの保存と分類を行います。

View on TensorFlow.org Run in Google Colab View source on GitHub Download notebook

このチュートリアルでは、CSV データを tf.data.Dataset にロードする手法の例を示します。

このチュートリアルで使われているデータはタイタニック号の乗客リストから取られたものです。乗客が生き残る可能性を、年齢、性別、チケットの等級、そして乗客が一人で旅行しているか否かといった特性から予測することを試みます。

設定

import functools

import numpy as np
import tensorflow as tf
2022-08-09 02:33:21.583352: E tensorflow/stream_executor/cuda/cuda_blas.cc:2981] Unable to register cuBLAS factory: Attempting to register factory for plugin cuBLAS when one has already been registered
2022-08-09 02:33:22.165320: W tensorflow/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libnvinfer.so.7'; dlerror: libnvrtc.so.11.1: cannot open shared object file: No such file or directory
2022-08-09 02:33:22.165613: W tensorflow/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libnvinfer_plugin.so.7'; dlerror: libnvrtc.so.11.1: cannot open shared object file: No such file or directory
2022-08-09 02:33:22.165627: W tensorflow/compiler/tf2tensorrt/utils/py_utils.cc:38] TF-TRT Warning: Cannot dlopen some TensorRT libraries. If you would like to use Nvidia GPU with TensorRT, please make sure the missing libraries mentioned above are installed properly.
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
30874/30874 [==============================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tf-datasets/titanic/eval.csv
13049/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'male' b'male' b'male' b'male']
age                 : [35. 22. 33. 28. 28.]
n_siblings_spouses  : [0 1 0 0 2]
parch               : [0 0 0 0 0]
fare                : [26.288  7.25   5.     9.5   21.679]
class               : [b'First' b'Third' b'First' b'Third' b'Third']
deck                : [b'E' b'unknown' b'B' b'unknown' b'unknown']
embark_town         : [b'Southampton' b'Southampton' b'Southampton' b'Southampton' b'Cherbourg']
alone               : [b'y' b'n' b'y' b'y' b'n']

上で見たように、この 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'female' b'female']
age                 : [30. 28. 28. 23. 22.]
n_siblings_spouses  : [0 0 0 0 0]
parch               : [0 1 0 0 0]
fare                : [ 7.225 33.    35.5    7.55  10.517]
class               : [b'Third' b'Second' b'First' b'Third' b'Third']
deck                : [b'unknown' b'unknown' b'C' b'unknown' b'unknown']
embark_town         : [b'Cherbourg' b'Southampton' b'Southampton' b'Southampton' b'Southampton']
alone               : [b'y' b'n' b'y' b'y' 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                 : [18. 40. 18. 31. 45.]
n_siblings_spouses  : [0 0 0 1 0]
class               : [b'Second' b'First' b'Third' b'Third' b'Third']
deck                : [b'unknown' b'unknown' b'unknown' b'unknown' b'unknown']
alone               : [b'n' b'y' b'y' b'n' b'y']

データの前処理

CSV ファイルに含まれるデータには様々な型のものがありえます。データをモデルに入力する前に、典型的なケースでは、様々な型を含んだデータを固定長のベクトルに変換する必要があります。

TensorFlow には典型的なデータ変換を行う tf.feature_column が組み込まれています。詳細はこのチュートリアルを参照してください。

また、任意のツール (たとえば、nltksklearn ) を使って前処理を行い、前処理した結果を 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                 : [21. 39. 28. 36. 46.]
n_siblings_spouses  : [0. 1. 0. 0. 1.]
parch               : [0. 1. 0. 0. 0.]
fare                : [ 8.433 83.158  7.896  7.496 61.175]
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.     1.     0.    89.104]
 [28.     0.     0.     7.75 ]
 [ 0.83   0.     2.    29.   ]
 [22.     0.     0.    10.517]
 [17.     1.     0.    57.   ]]

[1 1 1 0 1]

様々な型を含んだデータセットを利用する場合、シンプルな数値型のフィールドだけを分離したくなるかもしれません。tf.feature_column はそのような処理を実現できますが、いくらかのオーバーヘッドを発生させるため、それが本当に必要になる場合以外ではできるだけ避けるべきでしょう。様々な型を含んだデータセットに話を戻しましょう。

show_batch(raw_train_data)
sex                 : [b'male' b'male' b'female' b'male' b'female']
age                 : [40. 16. 14. 34. 28.]
n_siblings_spouses  : [0 0 1 1 0]
parch               : [0 0 0 0 2]
fare                : [ 7.896 26.    30.071 26.    15.246]
class               : [b'Third' b'Second' b'Second' b'Second' b'Third']
deck                : [b'unknown' b'unknown' b'unknown' b'unknown' b'unknown']
embark_town         : [b'Southampton' b'Southampton' b'Cherbourg' b'Southampton' b'Cherbourg']
alone               : [b'y' b'y' 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'male' b'female' b'male' b'male' b'male']
class               : [b'Third' b'First' b'Third' b'Second' b'Third']
deck                : [b'unknown' b'C' b'unknown' b'unknown' b'unknown']
embark_town         : [b'Queenstown' b'Cherbourg' b'Southampton' b'Southampton' b'Southampton']
alone               : [b'y' b'n' b'y' b'n' b'y']
numeric             : [[28.     0.     0.     7.733]
 [38.     1.     0.    71.283]
 [51.     0.     0.     8.05 ]
 [43.     1.     1.    26.25 ]
 [28.     0.     0.     9.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 の引数 MEANSTD を固定しておきましょう。

# 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 0x7f5a4e80c9d0>, 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([[51.   ,  0.   ,  1.   , 61.379],
       [28.   ,  0.   ,  0.   , 30.696],
       [45.   ,  0.   ,  0.   ,  8.05 ],
       [28.   ,  0.   ,  0.   , 12.35 ],
       [19.   ,  0.   ,  0.   ,  8.158]], dtype=float32)>
numeric_layer = tf.keras.layers.DenseFeatures(numeric_columns)
numeric_layer(example_batch).numpy()
array([[ 1.708, -0.474,  0.782,  0.494],
       [-0.13 , -0.474, -0.479, -0.068],
       [ 1.228, -0.474, -0.479, -0.482],
       [-0.13 , -0.474, -0.479, -0.404],
       [-0.85 , -0.474, -0.479, -0.48 ]], 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. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 1. 0.]

モデルを構築する際、これは入力レイヤーで行われるデータ処理の一部になります。

一体化した前処理レイヤー

2つの特徴量列のコレクションを結合し、tf.keras.layers.DenseFeatures に入力して、両方のデータ型を読み込んで前処理する入力レイヤーを作成しましょう。

preprocessing_layer = tf.keras.layers.DenseFeatures(categorical_columns+numeric_columns)
print(preprocessing_layer(example_batch).numpy()[0])
[ 0.     1.     1.     0.     0.     0.     0.     0.     0.     0.

  0.     0.     0.     0.     0.     1.     0.     0.     1.708 -0.474
  0.782  0.494  1.     0.   ]

モデルの構築

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. Received: inputs=OrderedDict([('sex', <tf.Tensor 'IteratorGetNext:5' shape=(None,) dtype=string>), ('class', <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=string>), ('deck', <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>), ('embark_town', <tf.Tensor 'IteratorGetNext:3' shape=(None,) dtype=string>), ('alone', <tf.Tensor 'IteratorGetNext:0' shape=(None,) 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. Received: inputs=OrderedDict([('sex', <tf.Tensor 'IteratorGetNext:5' shape=(None,) dtype=string>), ('class', <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=string>), ('deck', <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>), ('embark_town', <tf.Tensor 'IteratorGetNext:3' shape=(None,) dtype=string>), ('alone', <tf.Tensor 'IteratorGetNext:0' shape=(None,) dtype=string>), ('numeric', <tf.Tensor 'IteratorGetNext:4' shape=(None, 4) dtype=float32>)]). Consider rewriting this model with the Functional API.
126/126 [==============================] - 2s 6ms/step - loss: 0.4839 - accuracy: 0.7560
Epoch 2/20
126/126 [==============================] - 1s 4ms/step - loss: 0.4117 - accuracy: 0.8246
Epoch 3/20
126/126 [==============================] - 1s 4ms/step - loss: 0.3957 - accuracy: 0.8262
Epoch 4/20
126/126 [==============================] - 1s 4ms/step - loss: 0.3799 - accuracy: 0.8437
Epoch 5/20
126/126 [==============================] - 1s 4ms/step - loss: 0.3790 - accuracy: 0.8421
Epoch 6/20
126/126 [==============================] - 1s 4ms/step - loss: 0.3683 - accuracy: 0.8469
Epoch 7/20
126/126 [==============================] - 1s 4ms/step - loss: 0.3637 - accuracy: 0.8437
Epoch 8/20
126/126 [==============================] - 1s 4ms/step - loss: 0.3550 - accuracy: 0.8453
Epoch 9/20
126/126 [==============================] - 1s 4ms/step - loss: 0.3590 - accuracy: 0.8469
Epoch 10/20
126/126 [==============================] - 1s 4ms/step - loss: 0.3456 - accuracy: 0.8517
Epoch 11/20
126/126 [==============================] - 1s 4ms/step - loss: 0.3393 - accuracy: 0.8453
Epoch 12/20
126/126 [==============================] - 1s 4ms/step - loss: 0.3338 - accuracy: 0.8469
Epoch 13/20
126/126 [==============================] - 1s 4ms/step - loss: 0.3301 - accuracy: 0.8517
Epoch 14/20
126/126 [==============================] - 1s 4ms/step - loss: 0.3300 - accuracy: 0.8517
Epoch 15/20
126/126 [==============================] - 1s 4ms/step - loss: 0.3247 - accuracy: 0.8581
Epoch 16/20
126/126 [==============================] - 1s 4ms/step - loss: 0.3223 - accuracy: 0.8501
Epoch 17/20
126/126 [==============================] - 1s 4ms/step - loss: 0.3198 - accuracy: 0.8581
Epoch 18/20
126/126 [==============================] - 1s 4ms/step - loss: 0.3129 - accuracy: 0.8660
Epoch 19/20
126/126 [==============================] - 1s 4ms/step - loss: 0.3115 - accuracy: 0.8517
Epoch 20/20
126/126 [==============================] - 1s 4ms/step - loss: 0.3119 - accuracy: 0.8612
<keras.callbacks.History at 0x7f5b041666d0>

モデルの訓練が終わったら、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. Received: inputs=OrderedDict([('sex', <tf.Tensor 'IteratorGetNext:5' shape=(None,) dtype=string>), ('class', <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=string>), ('deck', <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>), ('embark_town', <tf.Tensor 'IteratorGetNext:3' shape=(None,) dtype=string>), ('alone', <tf.Tensor 'IteratorGetNext:0' shape=(None,) dtype=string>), ('numeric', <tf.Tensor 'IteratorGetNext:4' shape=(None, 4) dtype=float32>)]). Consider rewriting this model with the Functional API.
53/53 [==============================] - 1s 4ms/step - loss: 0.4491 - accuracy: 0.8447


Test Loss 0.4491174817085266, Test Accuracy 0.8446969985961914

単一のバッチ、または、バッチからなるデータセットのラベルを推論する場合には、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. Received: inputs=OrderedDict([('sex', <tf.Tensor 'IteratorGetNext:5' shape=(None,) dtype=string>), ('class', <tf.Tensor 'IteratorGetNext:1' shape=(None,) dtype=string>), ('deck', <tf.Tensor 'IteratorGetNext:2' shape=(None,) dtype=string>), ('embark_town', <tf.Tensor 'IteratorGetNext:3' shape=(None,) dtype=string>), ('alone', <tf.Tensor 'IteratorGetNext:0' shape=(None,) 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
Predicted survival: -223.60%  | Actual outcome:  DIED
Predicted survival: -211.48%  | Actual outcome:  SURVIVED
Predicted survival: -216.92%  | Actual outcome:  SURVIVED
Predicted survival: -163.19%  | Actual outcome:  SURVIVED
Predicted survival: -233.28%  | Actual outcome:  DIED