Dzień Społeczności ML jest 9 listopada! Dołącz do nas na aktualizacje z TensorFlow Jax i więcej Dowiedz się więcej

Migracja feature_columns do warstw Keras Preprocessing TF2

Zobacz na TensorFlow.org Uruchom w Google Colab Wyświetl źródło na GitHub Pobierz notatnik

Szkolenie modelu zwykle wiąże się z pewnym wstępnym przetwarzaniem funkcji, szczególnie w przypadku danych strukturalnych. Podczas szkolenia tf.estimator.Estimator w TF1, funkcja ta przerób jest zwykle wykonywane z tf.feature_column API. W TF2, to przerób może odbywać się bezpośrednio z warstwami Keras, zwany przerób warstw.

W tym przewodniku migracji wykonasz niektóre typowe przekształcenia funkcji przy użyciu zarówno kolumn funkcji, jak i warstw przetwarzania wstępnego, a następnie przeszkolisz kompletny model z obydwoma interfejsami API.

Najpierw zacznij od kilku niezbędnych importów,

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

i dodaj narzędzie do wywoływania kolumny funkcji do demonstracji:

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)

Obsługa wprowadzania

Aby używać kolumn funkcji z estymatorem, dane wejściowe modelu powinny być zawsze słownikiem tensorów:

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

Każda kolumna funkcji musi być utworzona z kluczem do indeksowania danych źródłowych. Dane wyjściowe wszystkich kolumn funkcji są łączone i używane przez model estymatora.

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)>

W Keras wprowadzanie modelu jest znacznie bardziej elastyczne. tf.keras.Model może obsługiwać jedno wejście tensora listę cech tensorowych lub słownika tensorowych funkcji. Można obsługiwać wejście słownika przekazując słownika tf.keras.Input na stworzeniu modelu. Dane wejściowe nie będą łączone automatycznie, co pozwala na ich znacznie bardziej elastyczne wykorzystanie. Mogą one być łączone z 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)>

Kodowanie jednorazowych identyfikatorów całkowitych

Wspólną cechą transformacji jest kodowanie danych wejściowych liczb całkowitych o znanym zakresie z jednym gorącym kodem. Oto przykład z użyciem kolumn funkcji:

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)>

Korzystanie Keras przerób warstw, kolumny te mogą być zastąpione jednym tf.keras.layers.CategoryEncoding warstwy z output_mode zestawu do '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)>

Normalizowanie cech numerycznych

Podczas obchodzenia się ciągłą, zmiennoprzecinkowe wyposażony z kolumnami fabularnych, trzeba użyć tf.feature_column.numeric_column . W przypadku, gdy dane wejściowe są już znormalizowane, konwersja do Keras jest banalna. Można po prostu użyć tf.keras.Input bezpośrednio do modelu, jak pokazano powyżej.

numeric_column może być również używany do wejścia normalizować:

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)>

W przeciwieństwie do tego, ze Keras ta normalizacja można zrobić z 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)>

Funkcje numeryczne dzielenia na wiadro i kodowania na gorąco

Inną powszechną transformacją ciągłych, zmiennoprzecinkowych danych wejściowych jest przeliczanie na liczby całkowite o ustalonym zakresie.

W kolumnach fabularnych, można to osiągnąć z 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)>

W Keras, może być zastąpiony przez 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)>

Kodowanie jednorazowych danych ciągu ze słownictwem

Obsługa funkcji ciągów często wymaga wyszukiwania słownictwa w celu przetłumaczenia ciągów na indeksy. Oto przykład użycia kolumn funkcji do wyszukiwania ciągów, a następnie kodowania indeksów w trybie hot-hot:

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)>

Korzystanie Keras przerób warstw użyć tf.keras.layers.StringLookup warstwę z output_mode ustawionym na '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)>

Osadzanie danych ciągów ze słownictwem

W przypadku większych słowników często potrzebne jest osadzanie, aby uzyskać dobre wyniki. Oto przykład osadzania funkcji ciągu za pomocą kolumn funkcji:

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)>

Korzystanie Keras przerób warstwy, może to być osiągnięte przez połączenie tf.keras.layers.StringLookup warstwę oraz tf.keras.layers.Embedding warstwy. Domyślny wyjściowe dla StringLookup będzie całkowita wskaźników, które mogą być podawane bezpośrednio do osadzania.

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)>

Kompletny przykład szkolenia

Aby pokazać pełny przepływ pracy szkoleniowej, najpierw przygotuj dane z trzema funkcjami różnych typów:

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]}

Zdefiniuj kilka typowych stałych dla przepływów pracy TF1 i TF2:

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

Z kolumnami funkcji

Kolumny funkcji muszą być przekazywane jako lista do estymatora podczas tworzenia i będą wywoływane niejawnie podczas uczenia.

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>

Kolumny funkcji będą również używane do przekształcania danych wejściowych podczas wnioskowania na modelu.

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)}

Z warstwami wstępnego przetwarzania Keras

Warstwy przetwarzania wstępnego Keras są bardziej elastyczne, jeśli chodzi o ich wywoływanie. Warstwa może być nakładana bezpośrednio na tensorów stosowane wewnątrz tf.data potoku wejściowego lub wbudowane bezpośrednio w nadającym się do szkolenia modelu Keras.

W tym przykładzie, można zastosować przerób warstw wewnątrz tf.data rurociągu wejściowym. Aby to zrobić, można zdefiniować oddzielne tf.keras.Model do Preprocesuj swoje funkcje wejściowe. Tego modelu nie można trenować, ale jest to wygodny sposób grupowania warstw przetwarzania wstępnego.

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)

Teraz można zastosować ten model wewnątrz wywołania tf.data.Dataset.map . Należy pamiętać, że funkcja przekazywane do map zostaną automatycznie przekształcone w tf.function i zwykłe ostrzeżenia na piśmie tf.function kod zastosowania (bez skutków ubocznych).

# 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))

Następnie można zdefiniować oddzielny Model zawierający wyszkolić warstw. Zwróć uwagę, jak dane wejściowe do tego modelu odzwierciedlają teraz wstępnie przetworzone typy funkcji i kształty.

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)

Teraz możesz trenować training_model z 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>

Wreszcie, w czasie wnioskowania, przydatne może być połączenie tych oddzielnych etapów w jeden model, który obsługuje surowe dane wejściowe funkcji.

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)

Ten składa się model może być zapisany jako SavedModel do późniejszego wykorzystania.

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)

Tabela równoważności kolumny funkcji

W celach informacyjnych poniżej przedstawiono przybliżoną zależność między kolumnami funkcji a warstwami przetwarzania wstępnego:

Kolumna funkcji Warstwa Keras
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 lub layers.IntegerLookup
feature_column.categorical_column_with_vocabulary_list layers.StringLookup lub layers.IntegerLookup
feature_column.crossed_column Nie zaimplementowano.
feature_column.embedding_column layers.Embedding
feature_column.indicator_column output_mode='one_hot' lub 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 lub layer.TextVectorization
feature_column.sequence_categorical_column_with_vocabulary_list layers.StringLookup , layers.IntegerLookup lub layer.TextVectorization
feature_column.sequence_numeric_column layers.Normalization
feature_column.weighted_categorical_column layers.CategoryEncoding

* output_mode mogą być przekazywane do layers.CategoryEncoding , layers.StringLookup , layers.IntegerLookup i layers.TextVectorization .

layers.TextVectorization może obsługiwać nieregularnym kształcie wprowadzania tekstu bezpośrednio (np całych zdań lub akapitów). Nie jest to jeden do jednego zamiennika obsługi sekwencji kategorialnych w TF1, ale może zaoferować wygodne zastąpienie wstępnego przetwarzania tekstu ad-hoc.

Następne kroki