Загрузите данные в формате CSV

Смотрите на TensorFlow.org Запустите в Google Colab Изучайте код на GitHub Скачайте ноутбук

Этот учебник приводит пример того как выгрузить данные в формате 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 в TensorFlow. Если Вам нужно масштабироваться до большого количества файлов или нужен загрузчик который совместим с TensorFlow и tf.data то используйте функцию tf.data.experimental.make_csv_dataset:

Единственная колонка которую нужно указать явно - это та значение которой вы собиратесь предсказывать с помощью модели.

LABEL_COLUMN = 'survived'
LABELS = [0, 1]

Сейчас прочитайте данные 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, # Значение искусственно занижено для удобства восприятия.
      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()))

Каждый элемент в датасете это пакет представленный в виде кортежа (много примеров, много меток). Данные из примеров организованы в тензоры столбцы (а не тензоры строки), каждый с таким количеством элементов каков размер пакета (12 в этом случае).

Будет лучше увидеть это вам самим.

show_batch(raw_train_data)
sex                 : [b'male' b'male' b'male' b'female' b'female']
age                 : [24.  7. 19. 29. 11.]
n_siblings_spouses  : [0 4 0 0 4]
parch               : [0 1 0 4 2]
fare                : [ 7.496 39.688  7.896 21.075 31.275]
class               : [b'Third' b'Third' b'Third' b'Third' b'Third']
deck                : [b'unknown' b'unknown' b'unknown' b'unknown' b'unknown']
embark_town         : [b'Southampton' b'Southampton' b'Southampton' b'Southampton'
 b'Southampton']
alone               : [b'y' b'n' b'y' b'n' b'n']

Как вы видите столбцы в CVS с именами. Конструктор датасета использует эти имена автоматически. Если файл с которым вы работаете не содержит имен столбцов в первой строке передайте их списком строк в аргумент column_names функции make_csv_dataset.

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'female' b'male' b'male' b'female']
age                 : [16. 38. 28. 28.  4.]
n_siblings_spouses  : [0 1 0 1 0]
parch               : [0 0 0 2 2]
fare                : [26.    71.283  7.796 23.45  22.025]
class               : [b'Second' b'First' b'Third' b'Third' b'Third']
deck                : [b'unknown' b'C' b'unknown' b'unknown' b'unknown']
embark_town         : [b'Southampton' b'Cherbourg' b'Southampton' b'Southampton' b'Southampton']
alone               : [b'y' b'n' b'y' b'n' b'n']

Этот пример будет использовать все возможные столбцы. Если не нужны некоторые столбцы в датасете, создайте список только из тех колонок, которые вы планируете использовать и передайте его в (опциональный) аргумент 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                 : [40. 31. 35. 42. 24.]
n_siblings_spouses  : [1 0 0 0 1]
class               : [b'First' b'First' b'Third' b'Second' b'Second']
deck                : [b'E' b'A' b'unknown' b'unknown' b'unknown']
alone               : [b'n' b'y' b'y' b'y' 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                 : [31. 25. 22. 35. 28.]
n_siblings_spouses  : [1. 0. 0. 0. 0.]
parch               : [0. 0. 0. 0. 0.]
fare                : [52.     7.742  7.229  8.05   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.     1.     0.    15.5  ]
 [61.     0.     0.     6.238]
 [14.     1.     0.    11.242]
 [28.     0.     0.     8.663]
 [21.     0.     0.     7.925]]

[1 0 1 0 0]

Если у вас смешанные типы данных, то вам может захотеться выделить эти простые числовые поля. API tf.feature_column может с этим справиться, но это повлечет за собой накладные расходы, и это стоит делать только если действительно необходимо. Вернитесь к смешанному датасету:

show_batch(raw_train_data)
sex                 : [b'male' b'male' b'male' b'male' b'male']
age                 : [32. 59. 43. 11. 47.]
n_siblings_spouses  : [0 0 1 5 0]
parch               : [0 0 1 2 0]
fare                : [ 8.05 13.5  26.25 46.9   7.25]
class               : [b'Third' b'Second' b'Second' b'Third' b'Third']
deck                : [b'E' b'unknown' b'unknown' b'unknown' b'unknown']
embark_town         : [b'Southampton' b'Southampton' b'Southampton' b'Southampton'
 b'Southampton']
alone               : [b'y' b'y' b'n' b'n' b'y']

example_batch, labels_batch = next(iter(temp_dataset)) 

Так что выберите более общий препроцессор который выбирает список числовых свойств и упаковывает их в одну колонку:

class PackNumericFeatures(object):
  def __init__(self, names):
    self.names = names

  def __call__(self, features, labels):
    numeric_freatures = [features.pop(name) for name in self.names]
    numeric_features = [tf.cast(feat, tf.float32) for feat in numeric_freatures]
    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'male' b'male' b'male' b'male']
class               : [b'Third' b'Second' b'Third' b'First' b'Third']
deck                : [b'unknown' b'unknown' b'unknown' b'D' b'unknown']
embark_town         : [b'Cherbourg' b'Southampton' b'Southampton' b'Cherbourg' b'Southampton']
alone               : [b'y' b'n' b'y' b'n' b'n']
numeric             : [[28.     0.     0.     7.225]
 [25.     1.     0.    26.   ]
 [55.5    0.     0.     8.05 ]
 [23.     0.     1.    63.358]
 [34.     1.     1.    14.4  ]]

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):
  # Центрируем данные
  return (data-mean)/std

Сейчас создайте числовой столбец. В API tf.feature_columns.numeric_column можно использовать аргумент normalizer_fn который выполнится на каждом пакете.

Добавьте MEAN и STD к normalizer fn с помощью functools.partial.

# 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 0x7f95a42c4730>, 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([[22.   ,  1.   ,  0.   ,  7.25 ],
       [22.   ,  0.   ,  0.   ,  7.896],
       [33.   ,  0.   ,  2.   , 26.   ],
       [28.   ,  1.   ,  0.   , 15.5  ],
       [29.   ,  0.   ,  4.   , 21.075]], dtype=float32)>
numeric_layer = tf.keras.layers.DenseFeatures(numeric_columns)
numeric_layer(example_batch).numpy()
array([[-0.61 ,  0.395, -0.479, -0.497],
       [-0.61 , -0.474, -0.479, -0.485],
       [ 0.269, -0.474,  2.043, -0.154],
       [-0.13 ,  0.395, -0.479, -0.346],
       [-0.05 , -0.474,  4.565, -0.244]], 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))
# Посмотрите что вы только что создали.
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. 1. 0.]

Позже, когда вы создадите модель это станет частью обработки входных данных.

Комбинированный слой предобработки

Добавьте две коллекции столбцов признаков и передайте их в 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.    -0.61   0.395
 -0.479 -0.497  1.     0.   ]

Постройте модель

Постройте tf.keras.Sequential начиная с препроцессингового слоя preprocessing_layer.

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, activation='sigmoid'),
])

model.compile(
    loss='binary_crossentropy',
    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.5255 - accuracy: 0.7448
Epoch 2/20
126/126 [==============================] - 0s 3ms/step - loss: 0.4333 - accuracy: 0.8102
Epoch 3/20
126/126 [==============================] - 0s 3ms/step - loss: 0.4046 - accuracy: 0.8198
Epoch 4/20
126/126 [==============================] - 0s 3ms/step - loss: 0.4002 - accuracy: 0.8373
Epoch 5/20
126/126 [==============================] - 0s 3ms/step - loss: 0.3846 - accuracy: 0.8357
Epoch 6/20
126/126 [==============================] - 0s 3ms/step - loss: 0.3720 - accuracy: 0.8357
Epoch 7/20
126/126 [==============================] - 0s 3ms/step - loss: 0.3741 - accuracy: 0.8501
Epoch 8/20
126/126 [==============================] - 0s 3ms/step - loss: 0.3607 - accuracy: 0.8533
Epoch 9/20
126/126 [==============================] - 0s 3ms/step - loss: 0.3622 - accuracy: 0.8437
Epoch 10/20
126/126 [==============================] - 0s 3ms/step - loss: 0.3469 - accuracy: 0.8596
Epoch 11/20
126/126 [==============================] - 0s 3ms/step - loss: 0.3493 - accuracy: 0.8533
Epoch 12/20
126/126 [==============================] - 0s 3ms/step - loss: 0.3461 - accuracy: 0.8533
Epoch 13/20
126/126 [==============================] - 0s 3ms/step - loss: 0.3413 - accuracy: 0.8549
Epoch 14/20
126/126 [==============================] - 0s 3ms/step - loss: 0.3327 - accuracy: 0.8581
Epoch 15/20
126/126 [==============================] - 0s 3ms/step - loss: 0.3266 - accuracy: 0.8596
Epoch 16/20
126/126 [==============================] - 0s 3ms/step - loss: 0.3262 - accuracy: 0.8612
Epoch 17/20
126/126 [==============================] - 0s 3ms/step - loss: 0.3279 - accuracy: 0.8676
Epoch 18/20
126/126 [==============================] - 0s 3ms/step - loss: 0.3174 - accuracy: 0.8596
Epoch 19/20
126/126 [==============================] - 0s 3ms/step - loss: 0.3148 - accuracy: 0.8628
Epoch 20/20
126/126 [==============================] - 0s 3ms/step - loss: 0.3133 - accuracy: 0.8692

<tensorflow.python.keras.callbacks.History at 0x7f95a43166a0>

После того как модель обучена вы можете проверить ее точность на множестве 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 2ms/step - loss: 0.4265 - accuracy: 0.8598


Test Loss 0.42648592591285706, Test Accuracy 0.8598484992980957

Используйте 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: 8.78%  | Actual outcome:  SURVIVED
Predicted survival: 3.95%  | Actual outcome:  DIED
Predicted survival: 2.15%  | Actual outcome:  DIED
Predicted survival: 9.72%  | Actual outcome:  DIED
Predicted survival: 27.35%  | Actual outcome:  DIED