День сообщества ML - 9 ноября! Присоединяйтесь к нам для обновления от TensorFlow, JAX, и многое другое Подробнее

Перенос feature_columns на слои предварительной обработки Keras TF2

Посмотреть на TensorFlow.org Запускаем в Google Colab Посмотреть исходный код на GitHub Скачать блокнот

Обучение модели обычно сопровождается некоторой предварительной обработкой функций, особенно при работе со структурированными данными. При обучении tf.estimator.Estimator в TF1, эта функция предварительной обработки, как правило , делается с tf.feature_column API. В TF2, это предварительная обработка может быть сделана непосредственно с Keras слоями, называемая предобработкой слоев.

В этом руководстве по миграции вы выполните некоторые преобразования общих функций, используя как столбцы функций, так и уровни предварительной обработки, а затем обучите полную модель с обоими API.

Во-первых, начнем с нескольких необходимых операций импорта,

import tensorflow as tf
import tensorflow.compat.v1 as tf1
import math

и добавьте утилиту для вызова колонки функций для демонстрации:

def call_feature_columns(feature_columns, inputs):
  # This is a convenient way to call a `feature_column` outside of an estimator
  # to display its output.
  feature_layer = tf1.keras.layers.DenseFeatures(feature_columns)
  return feature_layer(inputs)

Обработка ввода

Чтобы использовать столбцы функций с оценщиком, входные данные модели всегда должны быть словарем тензоров:

input_dict = {
  'foo': tf.constant([1]),
  'bar': tf.constant([0]),
  'baz': tf.constant([-1])
}

Каждый столбец функций должен быть создан с ключом для индексации исходных данных. Выходные данные всех столбцов характеристик объединяются и используются моделью оценки.

columns = [
  tf1.feature_column.numeric_column('foo'),
  tf1.feature_column.numeric_column('bar'),
  tf1.feature_column.numeric_column('baz'),
]
call_feature_columns(columns, input_dict)
<tf.Tensor: shape=(1, 3), dtype=float32, numpy=array([[ 0., -1.,  1.]], dtype=float32)>

В Keras ввод модели намного более гибкий. tf.keras.Model может обрабатывать один вход тензорного, список тензорных функций, или словарь тензорных функций. Вы можете обрабатывать словарный ввод пропускания словаря tf.keras.Input по созданию модели. Входные данные не будут объединяться автоматически, что позволяет использовать их гораздо более гибко. Они могут быть объединены с tf.keras.layers.Concatenate .

inputs = {
  'foo': tf.keras.Input(shape=()),
  'bar': tf.keras.Input(shape=()),
  'baz': tf.keras.Input(shape=()),
}
# Inputs are typically transformed by preprocessing layers before concatenation.
outputs = tf.keras.layers.Concatenate()(inputs.values())
model = tf.keras.Model(inputs=inputs, outputs=outputs)
model(input_dict)
<tf.Tensor: shape=(3,), dtype=float32, numpy=array([ 1.,  0., -1.], dtype=float32)>

Целочисленные идентификаторы с горячим кодированием

Общей функцией преобразования является быстрое кодирование целочисленных входных данных известного диапазона. Вот пример использования столбцов функций:

categorical_col = tf1.feature_column.categorical_column_with_identity(
    'type', num_buckets=3)
indicator_col = tf1.feature_column.indicator_column(categorical_col)
call_feature_columns(indicator_col, {'type': [0, 1, 2]})
<tf.Tensor: shape=(3, 3), dtype=float32, numpy=
array([[1., 0., 0.],
       [0., 1., 0.],
       [0., 0., 1.]], dtype=float32)>

Использование Keras предварительной обработки слоев, эти столбцы могут быть заменены одним tf.keras.layers.CategoryEncoding слоем с output_mode набором для 'one_hot' :

one_hot_layer = tf.keras.layers.CategoryEncoding(
    num_tokens=3, output_mode='one_hot')
one_hot_layer([0, 1, 2])
<tf.Tensor: shape=(3, 3), dtype=float32, numpy=
array([[1., 0., 0.],
       [0., 1., 0.],
       [0., 0., 1.]], dtype=float32)>

Нормализация числовых функций

При работе с непрерывной плавающей точкой особенности с функцией столбцов, необходимо использовать tf.feature_column.numeric_column . В случае, когда ввод уже нормализован, преобразование его в Keras тривиально. Вы можете просто использовать tf.keras.Input непосредственно в вашей модели, как показано на рисунке выше.

numeric_column также может быть использован для нормализуют ввода:

def normalize(x):
  mean, variance = (2.0, 1.0)
  return (x - mean) / math.sqrt(variance)
numeric_col = tf1.feature_column.numeric_column('col', normalizer_fn=normalize)
call_feature_columns(numeric_col, {'col': tf.constant([[0.], [1.], [2.]])})
<tf.Tensor: shape=(3, 1), dtype=float32, numpy=
array([[-2.],
       [-1.],
       [ 0.]], dtype=float32)>

В отличие от этого , с Keras, эта нормализация может быть сделано с tf.keras.layers.Normalization .

normalization_layer = tf.keras.layers.Normalization(mean=2.0, variance=1.0)
normalization_layer(tf.constant([[0.], [1.], [2.]]))
<tf.Tensor: shape=(3, 1), dtype=float32, numpy=
array([[-2.],
       [-1.],
       [ 0.]], dtype=float32)>

Бакетизация и быстрое кодирование числовых функций

Другое распространенное преобразование непрерывных входных данных с плавающей запятой - разделение на целые числа фиксированного диапазона.

В художественных колонках, это может быть достигнуто с помощью tf.feature_column.bucketized_column :

numeric_col = tf1.feature_column.numeric_column('col')
bucketized_col = tf1.feature_column.bucketized_column(numeric_col, [1, 4, 5])
call_feature_columns(bucketized_col, {'col': tf.constant([1., 2., 3., 4., 5.])})
<tf.Tensor: shape=(5, 4), dtype=float32, numpy=
array([[0., 1., 0., 0.],
       [0., 1., 0., 0.],
       [0., 1., 0., 0.],
       [0., 0., 1., 0.],
       [0., 0., 0., 1.]], dtype=float32)>

В Keras, это может быть заменено tf.keras.layers.Discretization :

discretization_layer = tf.keras.layers.Discretization(bin_boundaries=[1, 4, 5])
one_hot_layer = tf.keras.layers.CategoryEncoding(
    num_tokens=4, output_mode='one_hot')
one_hot_layer(discretization_layer([1., 2., 3., 4., 5.]))
<tf.Tensor: shape=(5, 4), dtype=float32, numpy=
array([[0., 1., 0., 0.],
       [0., 1., 0., 0.],
       [0., 1., 0., 0.],
       [0., 0., 1., 0.],
       [0., 0., 0., 1.]], dtype=float32)>

Быстрое кодирование строковых данных со словарем

Обработка строковых функций часто требует поиска в словаре для перевода строк в индексы. Вот пример использования столбцов функций для поиска строк и последующего быстрого кодирования индексов:

vocab_col = tf1.feature_column.categorical_column_with_vocabulary_list(
    'sizes',
    vocabulary_list=['small', 'medium', 'large'],
    num_oov_buckets=0)
indicator_col = tf1.feature_column.indicator_column(vocab_col)
call_feature_columns(indicator_col, {'sizes': ['small', 'medium', 'large']})
<tf.Tensor: shape=(3, 3), dtype=float32, numpy=
array([[1., 0., 0.],
       [0., 1., 0.],
       [0., 0., 1.]], dtype=float32)>

Используя Keras предварительную обработку слоев, использовать tf.keras.layers.StringLookup слой с output_mode набором для 'one_hot' :

string_lookup_layer = tf.keras.layers.StringLookup(
    vocabulary=['small', 'medium', 'large'],
    num_oov_indices=0,
    output_mode='one_hot')
string_lookup_layer(['small', 'medium', 'large'])
<tf.Tensor: shape=(3, 3), dtype=float32, numpy=
array([[1., 0., 0.],
       [0., 1., 0.],
       [0., 0., 1.]], dtype=float32)>

Встраивание строковых данных в словарь

Для больших словарей часто требуется вложение для хорошей производительности. Вот пример встраивания строковой функции с использованием столбцов функций:

vocab_col = tf1.feature_column.categorical_column_with_vocabulary_list(
    'col',
    vocabulary_list=['small', 'medium', 'large'],
    num_oov_buckets=0)
embedding_col = tf1.feature_column.embedding_column(vocab_col, 4)
call_feature_columns(embedding_col, {'col': ['small', 'medium', 'large']})
<tf.Tensor: shape=(3, 4), dtype=float32, numpy=
array([[ 0.70173466,  0.28964448,  0.1353878 , -0.48838466],
       [-0.38824508, -0.42320415,  0.10066552, -0.3342148 ],
       [ 0.14083381,  0.04141187,  0.0923849 , -0.26665878]],
      dtype=float32)>

Используя Keras предварительной обработку слоев, это может быть достигнуто путем объединения tf.keras.layers.StringLookup слоя и tf.keras.layers.Embedding слоя. Выход по умолчанию для StringLookup будет целое число индексов , которые можно подавать непосредственно в вложения.

string_lookup_layer = tf.keras.layers.StringLookup(
    vocabulary=['small', 'medium', 'large'], num_oov_indices=0)
embedding = tf.keras.layers.Embedding(3, 4)
embedding(string_lookup_layer(['small', 'medium', 'large']))
<tf.Tensor: shape=(3, 4), dtype=float32, numpy=
array([[-0.01173455, -0.03834573, -0.02355952, -0.01048126],
       [-0.03316599,  0.02532525,  0.02044696,  0.0385625 ],
       [-0.03027378,  0.04027149,  0.03658042,  0.04446581]],
      dtype=float32)>

Пример полного обучения

Чтобы показать полный рабочий процесс обучения, сначала подготовьте данные с тремя функциями разных типов:

features = {
    'type': [0, 1, 1],
    'size': ['small', 'small', 'medium'],
    'weight': [2.7, 1.8, 1.6],
}
labels = [1, 1, 0]
predict_features = {'type': [0], 'size': ['foo'], 'weight': [-0.7]}

Определите некоторые общие константы для рабочих процессов TF1 и TF2:

vocab = ['small', 'medium', 'large']
one_hot_dims = 3
embedding_dims = 4
weight_mean = 2.0
weight_variance = 1.0

С функциональными столбцами

Столбцы функций должны быть переданы в виде списка оценщику при создании и будут вызываться неявно во время обучения.

categorical_col = tf1.feature_column.categorical_column_with_identity(
    'type', num_buckets=one_hot_dims)
# Convert index to one-hot; e.g. [2] -> [0,0,1].
indicator_col = tf1.feature_column.indicator_column(categorical_col)

# Convert strings to indices; e.g. ['small'] -> [1].
vocab_col = tf1.feature_column.categorical_column_with_vocabulary_list(
    'size', vocabulary_list=vocab, num_oov_buckets=1)
# Embed the indices.
embedding_col = tf1.feature_column.embedding_column(vocab_col, embedding_dims)

normalizer_fn = lambda x: (x - weight_mean) / math.sqrt(weight_variance)
# Normalize the numeric inputs; e.g. [2.0] -> [0.0].
numeric_col = tf1.feature_column.numeric_column(
    'weight', normalizer_fn=normalizer_fn)

estimator = tf1.estimator.DNNClassifier(
    feature_columns=[indicator_col, embedding_col, numeric_col],
    hidden_units=[1])

def _input_fn():
  return tf1.data.Dataset.from_tensor_slices((features, labels)).batch(1)

estimator.train(_input_fn)
INFO:tensorflow:Using default config.
WARNING:tensorflow:Using temporary folder as model directory: /tmp/tmpp_a4ltrr
INFO:tensorflow:Using config: {'_model_dir': '/tmp/tmpp_a4ltrr', '_tf_random_seed': None, '_save_summary_steps': 100, '_save_checkpoints_steps': None, '_save_checkpoints_secs': 600, '_session_config': allow_soft_placement: true
graph_options {
  rewrite_options {
    meta_optimizer_iterations: ONE
  }
}
, '_keep_checkpoint_max': 5, '_keep_checkpoint_every_n_hours': 10000, '_log_step_count_steps': 100, '_train_distribute': None, '_device_fn': None, '_protocol': None, '_eval_distribute': None, '_experimental_distribute': None, '_experimental_max_worker_delay_secs': None, '_session_creation_timeout_secs': 7200, '_checkpoint_save_graph_def': True, '_service': None, '_cluster_spec': ClusterSpec({}), '_task_type': 'worker', '_task_id': 0, '_global_id_in_cluster': 0, '_master': '', '_evaluation_master': '', '_is_chief': True, '_num_ps_replicas': 0, '_num_worker_replicas': 1}
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/training/training_util.py:236: Variable.initialized_value (from tensorflow.python.ops.variables) is deprecated and will be removed in a future version.
Instructions for updating:
Use Variable.read_value. Variables in 2.X are initialized automatically both in eager and graph (inside tf.defun) contexts.
INFO:tensorflow:Calling model_fn.
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/training/adagrad.py:77: calling Constant.__init__ (from tensorflow.python.ops.init_ops) with dtype is deprecated and will be removed in a future version.
Instructions for updating:
Call initializer instance with the dtype argument instead of passing it to the constructor
INFO:tensorflow:Done calling model_fn.
INFO:tensorflow:Create CheckpointSaverHook.
INFO:tensorflow:Graph was finalized.
INFO:tensorflow:Running local_init_op.
INFO:tensorflow:Done running local_init_op.
INFO:tensorflow:Calling checkpoint listeners before saving checkpoint 0...
INFO:tensorflow:Saving checkpoints for 0 into /tmp/tmpp_a4ltrr/model.ckpt.
INFO:tensorflow:Calling checkpoint listeners after saving checkpoint 0...
INFO:tensorflow:loss = 0.779853, step = 0
INFO:tensorflow:Calling checkpoint listeners before saving checkpoint 3...
INFO:tensorflow:Saving checkpoints for 3 into /tmp/tmpp_a4ltrr/model.ckpt.
INFO:tensorflow:Calling checkpoint listeners after saving checkpoint 3...
INFO:tensorflow:Loss for final step: 0.6699668.
<tensorflow_estimator.python.estimator.canned.dnn.DNNClassifier at 0x7fb17062b250>

Столбцы функций также будут использоваться для преобразования входных данных при выполнении вывода в модели.

def _predict_fn():
  return tf1.data.Dataset.from_tensor_slices(predict_features).batch(1)

next(estimator.predict(_predict_fn))
INFO:tensorflow:Calling model_fn.
INFO:tensorflow:Done calling model_fn.
INFO:tensorflow:Graph was finalized.
INFO:tensorflow:Restoring parameters from /tmp/tmpp_a4ltrr/model.ckpt-3
INFO:tensorflow:Running local_init_op.
INFO:tensorflow:Done running local_init_op.
{'logits': array([-1.5316477], dtype=float32),
 'logistic': array([0.17775273], dtype=float32),
 'probabilities': array([0.82224727, 0.17775275], dtype=float32),
 'class_ids': array([0]),
 'classes': array([b'0'], dtype=object),
 'all_class_ids': array([0, 1], dtype=int32),
 'all_classes': array([b'0', b'1'], dtype=object)}

Со слоями предварительной обработки Keras

Слои предварительной обработки Keras более гибки в том, где они могут быть вызваны. Слой может быть нанесен непосредственно на тензоры, используется внутри tf.data входного трубопровода или встроен непосредственно в модель обучаемого Keras.

В этом примере, вы будете применять предварительную обработку слоев внутри tf.data входного трубопровода. Чтобы сделать это, вы можете определить отдельный tf.keras.Model для предварительной обработки ваших входных функций. Эту модель нельзя обучить, но она представляет собой удобный способ группировки слоев предварительной обработки.

inputs = {
  'type': tf.keras.Input(shape=(), dtype='int64'),
  'size': tf.keras.Input(shape=(), dtype='string'),
  'weight': tf.keras.Input(shape=(), dtype='float32'),
}
# Convert index to one-hot; e.g. [2] -> [0,0,1].
type_output = tf.keras.layers.CategoryEncoding(
      one_hot_dims, output_mode='one_hot')(inputs['type'])
# Convert size strings to indices; e.g. ['small'] -> [1].
size_output = tf.keras.layers.StringLookup(vocabulary=vocab)(inputs['size'])
# Normalize the numeric inputs; e.g. [2.0] -> [0.0].
weight_output = tf.keras.layers.Normalization(
      axis=None, mean=weight_mean, variance=weight_variance)(inputs['weight'])
outputs = {
  'type': type_output,
  'size': size_output,
  'weight': weight_output,
}
preprocessing_model = tf.keras.Model(inputs, outputs)

Теперь вы можете применить эту модель внутри вызова tf.data.Dataset.map . Пожалуйста , обратите внимание , что функция передается map будет автоматически преобразована в tf.function , и обычные предостережения для написания tf.function код не применяются (без побочных эффектов).

# Apply the preprocessing in tf.data.Dataset.map.
dataset = tf.data.Dataset.from_tensor_slices((features, labels)).batch(1)
dataset = dataset.map(lambda x, y: (preprocessing_model(x), y),
                      num_parallel_calls=tf.data.AUTOTUNE)
# Display a preprocessed input sample.
next(dataset.take(1).as_numpy_iterator())
({'type': array([[1., 0., 0.]], dtype=float32),
  'size': array([1]),
  'weight': array([0.70000005], dtype=float32)},
 array([1], dtype=int32))

Далее, вы можете определить отдельную Model , содержащую обучаемые слои. Обратите внимание, как входные данные для этой модели теперь отражают предварительно обработанные типы и формы элементов.

inputs = {
  'type': tf.keras.Input(shape=(one_hot_dims,), dtype='float32'),
  'size': tf.keras.Input(shape=(), dtype='int64'),
  'weight': tf.keras.Input(shape=(), dtype='float32'),
}
# Since the embedding is trainable, it needs to be part of the training model.
embedding = tf.keras.layers.Embedding(len(vocab), embedding_dims)
outputs = tf.keras.layers.Concatenate()([
  inputs['type'],
  embedding(inputs['size']),
  tf.expand_dims(inputs['weight'], -1),
])
outputs = tf.keras.layers.Dense(1)(outputs)
training_model = tf.keras.Model(inputs, outputs)

Теперь вы можете обучить training_model с tf.keras.Model.fit .

# Train on the preprocessed data.
training_model.compile(
    loss=tf.keras.losses.BinaryCrossentropy(from_logits=True))
training_model.fit(dataset)
3/3 [==============================] - 0s 3ms/step - loss: 0.6582
<keras.callbacks.History at 0x7faceece5f10>

Наконец, во время вывода может быть полезно объединить эти отдельные этапы в единую модель, которая обрабатывает исходные входные данные функций.

inputs = preprocessing_model.input
outpus = training_model(preprocessing_model(inputs))
inference_model = tf.keras.Model(inputs, outpus)

predict_dataset = tf.data.Dataset.from_tensor_slices(predict_features).batch(1)
inference_model.predict(predict_dataset)
array([[1.3051766]], dtype=float32)

Эта модель состоит может быть сохранена в качестве SavedModel для последующего использования.

inference_model.save('model')
restored_model = tf.keras.models.load_model('model')
restored_model.predict(predict_dataset)
WARNING:tensorflow:Compiled the loaded model, but the compiled metrics have yet to be built. `model.compile_metrics` will be empty until you train or evaluate the model.
2021-09-22 20:25:10.799683: W tensorflow/python/util/util.cc:348] Sets are not currently considered sequences, but this may change in the future, so consider avoiding using them.
INFO:tensorflow:Assets written to: model/assets
WARNING:tensorflow:No training configuration found in save file, so the model was *not* compiled. Compile it manually.
array([[1.3051766]], dtype=float32)

Таблица эквивалентности столбцов функций

Для справки, вот примерное соответствие между столбцами функций и слоями предварительной обработки:

Столбец функций Слой Кераса
feature_column.bucketized_column layers.Discretization
feature_column.categorical_column_with_hash_bucket layers.Hashing
feature_column.categorical_column_with_identity layers.CategoryEncoding
feature_column.categorical_column_with_vocabulary_file layers.StringLookup или layers.IntegerLookup
feature_column.categorical_column_with_vocabulary_list layers.StringLookup или layers.IntegerLookup
feature_column.crossed_column Не реализована.
feature_column.embedding_column layers.Embedding
feature_column.indicator_column output_mode='one_hot' или output_mode='multi_hot' *
feature_column.numeric_column layers.Normalization
feature_column.sequence_categorical_column_with_hash_bucket layers.Hashing
feature_column.sequence_categorical_column_with_identity layers.CategoryEncoding
feature_column.sequence_categorical_column_with_vocabulary_file layers.StringLookup , layers.IntegerLookup или layer.TextVectorization
feature_column.sequence_categorical_column_with_vocabulary_list layers.StringLookup , layers.IntegerLookup или layer.TextVectorization
feature_column.sequence_numeric_column layers.Normalization
feature_column.weighted_categorical_column layers.CategoryEncoding

* output_mode могут быть переданы в layers.CategoryEncoding , layers.StringLookup , layers.IntegerLookup и layers.TextVectorization .

layers.TextVectorization может обрабатывать произвольной формы ввода текста непосредственно (например , целые предложения или абзацы). Это не однозначная замена категориальной обработки последовательностей в TF1, но может предложить удобную замену специальной предварительной обработки текста.

Следующие шаги