Посмотреть на TensorFlow.org | Запустить в Google Colab | Посмотреть исходный код на GitHub | Скачать блокнот |
Обучение модели обычно сопровождается некоторой предварительной обработкой признаков, особенно при работе со структурированными данными. При обучении tf.estimator.Estimator
в TF1 предварительная обработка этой функции обычно выполняется с помощью API tf.feature_column
. В 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.01798586, -0.2808677 , 0.27639154, 0.06081508], [ 0.05771849, 0.02464074, 0.20080602, 0.50164527], [-0.9208247 , -0.40816694, -0.49132794, 0.9203153 ]], 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.04838837, -0.04014301, 0.02001903, -0.01150769], [-0.04580117, -0.04319514, 0.03725603, -0.00572466], [-0.0401094 , 0.00997342, 0.00111955, 0.00132702]], dtype=float32)>
Суммирование взвешенных категорийных данных
В некоторых случаях вам нужно иметь дело с категориальными данными, где каждое появление категории имеет соответствующий вес. В столбцах функций это обрабатывается с помощью tf.feature_column.weighted_categorical_column
. В сочетании с indicator_column
это приводит к суммированию весов по категориям.
ids = tf.constant([[5, 11, 5, 17, 17]])
weights = tf.constant([[0.5, 1.5, 0.7, 1.8, 0.2]])
categorical_col = tf1.feature_column.categorical_column_with_identity(
'ids', num_buckets=20)
weighted_categorical_col = tf1.feature_column.weighted_categorical_column(
categorical_col, 'weights')
indicator_col = tf1.feature_column.indicator_column(weighted_categorical_col)
call_feature_columns(indicator_col, {'ids': ids, 'weights': weights})
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.7/site-packages/tensorflow/python/feature_column/feature_column_v2.py:4203: sparse_merge (from tensorflow.python.ops.sparse_ops) is deprecated and will be removed in a future version. Instructions for updating: No similar op available at this time. <tf.Tensor: shape=(1, 20), dtype=float32, numpy= array([[0. , 0. , 0. , 0. , 0. , 1.2, 0. , 0. , 0. , 0. , 0. , 1.5, 0. , 0. , 0. , 0. , 0. , 2. , 0. , 0. ]], dtype=float32)>
В Keras это можно сделать, передав вход count_weights
в tf.keras.layers.CategoryEncoding
с output_mode='count'
.
ids = tf.constant([[5, 11, 5, 17, 17]])
weights = tf.constant([[0.5, 1.5, 0.7, 1.8, 0.2]])
# Using sparse output is more efficient when `num_tokens` is large.
count_layer = tf.keras.layers.CategoryEncoding(
num_tokens=20, output_mode='count', sparse=True)
tf.sparse.to_dense(count_layer(ids, count_weights=weights))
<tf.Tensor: shape=(1, 20), dtype=float32, numpy= array([[0. , 0. , 0. , 0. , 0. , 1.2, 0. , 0. , 0. , 0. , 0. , 1.5, 0. , 0. , 0. , 0. , 0. , 2. , 0. , 0. ]], dtype=float32)>
Встраивание взвешенных категорийных данных
В качестве альтернативы вы можете захотеть встроить взвешенные категориальные входные данные. В столбцах функций embedding_column
содержит аргумент combiner
. Если какой-либо образец содержит несколько записей для категории, они будут объединены в соответствии с настройкой аргумента (по умолчанию 'mean'
).
ids = tf.constant([[5, 11, 5, 17, 17]])
weights = tf.constant([[0.5, 1.5, 0.7, 1.8, 0.2]])
categorical_col = tf1.feature_column.categorical_column_with_identity(
'ids', num_buckets=20)
weighted_categorical_col = tf1.feature_column.weighted_categorical_column(
categorical_col, 'weights')
embedding_col = tf1.feature_column.embedding_column(
weighted_categorical_col, 4, combiner='mean')
call_feature_columns(embedding_col, {'ids': ids, 'weights': weights})
<tf.Tensor: shape=(1, 4), dtype=float32, numpy= array([[ 0.02666993, 0.289671 , 0.18065728, -0.21045178]], dtype=float32)>
В combiner
нет опции объединения для tf.keras.layers.Embedding
, но вы можете добиться того же эффекта с помощью tf.keras.layers.Dense
. Вышеприведенный столбец embedding_column
просто линейно комбинирует векторы встраивания в соответствии с весом категории. Хотя поначалу это не очевидно, это в точности эквивалентно представлению ваших категориальных входных данных в виде разреженного весового вектора размера (num_tokens)
и умножению их на Dense
ядро формы (embedding_size, num_tokens)
.
ids = tf.constant([[5, 11, 5, 17, 17]])
weights = tf.constant([[0.5, 1.5, 0.7, 1.8, 0.2]])
# For `combiner='mean'`, normalize your weights to sum to 1. Removing this line
# would be eqivalent to an `embedding_column` with `combiner='sum'`.
weights = weights / tf.reduce_sum(weights, axis=-1, keepdims=True)
count_layer = tf.keras.layers.CategoryEncoding(
num_tokens=20, output_mode='count', sparse=True)
embedding_layer = tf.keras.layers.Dense(4, use_bias=False)
embedding_layer(count_layer(ids, count_weights=weights))
<tf.Tensor: shape=(1, 4), dtype=float32, numpy= array([[-0.03897291, -0.27131438, 0.09332469, 0.04333957]], 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/tmp8lwbuor2 INFO:tensorflow:Using config: {'_model_dir': '/tmp/tmp8lwbuor2', '_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/tmp8lwbuor2/model.ckpt. INFO:tensorflow:Calling checkpoint listeners after saving checkpoint 0... INFO:tensorflow:loss = 0.54634213, step = 0 INFO:tensorflow:Calling checkpoint listeners before saving checkpoint 3... INFO:tensorflow:Saving checkpoints for 3 into /tmp/tmp8lwbuor2/model.ckpt. INFO:tensorflow:Calling checkpoint listeners after saving checkpoint 3... INFO:tensorflow:Loss for final step: 0.7308526. <tensorflow_estimator.python.estimator.canned.dnn.DNNClassifier at 0x7f90685d53d0>
Столбцы функций также будут использоваться для преобразования входных данных при выполнении логического вывода в модели.
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/tmp8lwbuor2/model.ckpt-3 INFO:tensorflow:Running local_init_op. INFO:tensorflow:Done running local_init_op. {'logits': array([0.5172372], dtype=float32), 'logistic': array([0.6265015], dtype=float32), 'probabilities': array([0.37349847, 0.6265015 ], dtype=float32), 'class_ids': array([1]), 'classes': array([b'1'], 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.7248 <keras.callbacks.History at 0x7f9041a294d0>
Наконец, во время вывода может быть полезно объединить эти отдельные этапы в единую модель, которая обрабатывает необработанные входные данные признаков.
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([[0.936637]], 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-10-27 01:23:25.649967: 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([[0.936637]], dtype=float32)
Таблица эквивалентности столбца признаков
Для справки, вот примерное соответствие между столбцами объектов и слоями предварительной обработки:
* output_mode
может быть передан в layers.CategoryEncoding
, layers.StringLookup
layers.TextVectorization
layers.IntegerLookup
† layers.TextVectorization
может напрямую обрабатывать ввод текста произвольной формы (например, целые предложения или абзацы). Это не является однозначной заменой обработки категориальных последовательностей в TF1, но может предложить удобную замену для специальной предварительной обработки текста.
Следующие шаги
- Для получения дополнительной информации о слоях предварительной обработки keras см . руководство по слоям предварительной обработки .
- Более подробный пример применения слоев предварительной обработки к структурированным данным см. в руководстве по структурированным данным .