Есть вопрос? Присоединяйтесь к сообществу на форуме TensorFlow. Посетите форум.

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

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

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

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

С помощью слоев предварительной обработки Keras вы можете создавать и экспортировать действительно сквозные модели: модели, которые принимают необработанные изображения или необработанные структурированные данные в качестве входных данных; модели, которые самостоятельно обрабатывают нормализацию признаков или индексацию значений признаков.

Доступные слои предварительной обработки

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

  • Слой TextVectorization : превращает необработанные строки в закодированное представление, которое может быть прочитано слоем Embedding или Dense слоем.
  • Слой Normalization : выполняет поэлементную нормализацию входных функций.

Уровни предварительной обработки структурированных данных

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

  • CategoryEncoding : превращает целочисленные категориальные функции в одноразовые, многоразовые или плотные представления TF-IDF.
  • Слой Hashing : выполняет хеширование категориальных функций, также известное как «трюк хеширования».
  • Слой Discretization : превращает непрерывные числовые признаки в целочисленные категориальные признаки.
  • Слой StringLookup : превращает строковые категориальные значения в целочисленные индексы.
  • Слой IntegerLookup : превращает целочисленные категориальные значения в целочисленные индексы.
  • Слой CategoryCrossing : объединяет категориальные объекты в объекты совместного появления. Например, если у вас есть значения признаков «a» и «b», он может предоставить комбинированный признак «a и b присутствуют одновременно».

Слои предварительной обработки изображений

Эти слои предназначены для стандартизации входных данных модели изображения.

  • Resizing слоя: изменяет размер пакета изображений до целевого размера.
  • Слой изменения Rescaling : изменяет масштаб и смещает значения пакета изображения (например, переход от входов в диапазоне [0, 255] к входам в диапазоне [0, 1] .
  • Слой CenterCrop : возвращает центр кадрирования пакета изображений.

Слои увеличения данных изображения

Эти слои применяют случайные преобразования увеличения к пакету изображений. Они активны только во время тренировок.

  • Слой RandomCrop
  • Слой RandomFlip
  • Слой RandomTranslation
  • Слой RandomRotation
  • Слой RandomZoom
  • Слой RandomHeight
  • Слой RandomWidth

Метод adapt()

Некоторые уровни предварительной обработки имеют внутреннее состояние, которое должно быть вычислено на основе выборки обучающих данных. Список уровней предварительной обработки с отслеживанием состояния:

  • TextVectorization : содержит сопоставление между строковыми токенами и целочисленными индексами
  • Normalization : содержит среднее и стандартное отклонение характеристик.
  • StringLookup и IntegerLookup : удерживают сопоставление между входными значениями и выходными индексами.
  • CategoryEncoding : содержит индекс входных значений.
  • Discretization : содержит информацию о границах сегмента значений.

Что особенно важно, эти слои не поддаются обучению . Их состояние не устанавливается во время обучения; он должен быть установлен перед тренировкой , шаг, называемый «адаптация».

Вы устанавливаете состояние уровня предварительной обработки, открывая его обучающим данным с помощью метода adapt() :

import numpy as np
import tensorflow as tf
from tensorflow.keras.layers.experimental import preprocessing

data = np.array([[0.1, 0.2, 0.3], [0.8, 0.9, 1.0], [1.5, 1.6, 1.7],])
layer = preprocessing.Normalization()
layer.adapt(data)
normalized_data = layer(data)

print("Features mean: %.2f" % (normalized_data.numpy().mean()))
print("Features std: %.2f" % (normalized_data.numpy().std()))
Features mean: 0.00
Features std: 1.00

Метод adap adapt() принимает либо массив Numpy, либо объектtf.data.Dataset . В случае StringLookup и TextVectorization вы также можете передать список строк:

data = [
    "ξεῖν᾽, ἦ τοι μὲν ὄνειροι ἀμήχανοι ἀκριτόμυθοι",
    "γίγνοντ᾽, οὐδέ τι πάντα τελείεται ἀνθρώποισι.",
    "δοιαὶ γάρ τε πύλαι ἀμενηνῶν εἰσὶν ὀνείρων:",
    "αἱ μὲν γὰρ κεράεσσι τετεύχαται, αἱ δ᾽ ἐλέφαντι:",
    "τῶν οἳ μέν κ᾽ ἔλθωσι διὰ πριστοῦ ἐλέφαντος,",
    "οἵ ῥ᾽ ἐλεφαίρονται, ἔπε᾽ ἀκράαντα φέροντες:",
    "οἱ δὲ διὰ ξεστῶν κεράων ἔλθωσι θύραζε,",
    "οἵ ῥ᾽ ἔτυμα κραίνουσι, βροτῶν ὅτε κέν τις ἴδηται.",
]
layer = preprocessing.TextVectorization()
layer.adapt(data)
vectorized_text = layer(data)
print(vectorized_text)
tf.Tensor(
[[37 12 25  5  9 20 21  0  0]
 [51 34 27 33 29 18  0  0  0]
 [49 52 30 31 19 46 10  0  0]
 [ 7  5 50 43 28  7 47 17  0]
 [24 35 39 40  3  6 32 16  0]
 [ 4  2 15 14 22 23  0  0  0]
 [36 48  6 38 42  3 45  0  0]
 [ 4  2 13 41 53  8 44 26 11]], shape=(8, 9), dtype=int64)

Кроме того, адаптируемые слои всегда предоставляют возможность напрямую установить состояние с помощью аргументов конструктора или назначения веса. Если предполагаемые значения состояния известны во время создания уровня или вычисляются вне вызова adapt() , их можно установить, не полагаясь на внутренние вычисления уровня. Например, если внешние файлы словаря для TextVectorization , StringLookup или IntegerLookup уже существуют, их можно загрузить непосредственно в таблицы поиска, передав путь к файлу словаря в аргументах конструктора слоя.

Вот пример, в котором мы StringLookup слой StringLookup с предварительно StringLookup словарем:

vocab = ["a", "b", "c", "d"]
data = tf.constant([["a", "c", "d"], ["d", "z", "b"]])
layer = preprocessing.StringLookup(vocabulary=vocab)
vectorized_data = layer(data)
print(vectorized_data)
tf.Tensor(
[[2 4 5]
 [5 1 3]], shape=(2, 3), dtype=int64)

Предварительная обработка данных перед моделью или внутри модели

Есть два способа использовать слои предварительной обработки:

Вариант 1. Сделайте их частью модели, например:

inputs = keras.Input(shape=input_shape)
x = preprocessing_layer(inputs)
outputs = rest_of_the_model(x)
model = keras.Model(inputs, outputs)

С этой опцией предварительная обработка будет происходить на устройстве синхронно с остальной частью выполнения модели, а это означает, что она выиграет от ускорения графического процессора. Если вы тренируетесь на графическом процессоре, это лучший вариант для уровня Normalization , а также для всех уровней предварительной обработки изображений и увеличения данных.

Вариант 2: примените его к вашемуtf.data.Dataset , чтобы получить набор данных, который дает пакеты предварительно обработанных данных, например:

dataset = dataset.map(
  lambda x, y: (preprocessing_layer(x), y))

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

Это лучший вариант для TextVectorization и всех уровней предварительной обработки структурированных данных. Это также может быть хорошим вариантом, если вы тренируетесь на ЦП и используете слои предварительной обработки изображений.

Преимущества предварительной обработки внутри модели во время вывода

Даже если вы выберете вариант 2, вы можете позже экспортировать сквозную модель только для вывода, которая будет включать слои предварительной обработки. Ключевым преимуществом этого является то, что это делает вашу модель портативной и помогает уменьшить перекос при обучении / обслуживании .

Когда вся предварительная обработка данных является частью модели, другие люди могут загружать и использовать вашу модель, не зная, как ожидается кодирование и нормализация каждой функции. Ваша модель вывода сможет обрабатывать необработанные изображения или необработанные структурированные данные и не требует, чтобы пользователи модели знали детали, например, схему токенизации, используемую для текста, схему индексации, используемую для категориальных функций, и значения пикселей изображения. нормализованы до [-1, +1] или до [0, 1] и т. д. Это особенно важно, если вы экспортируете свою модель в другую среду выполнения, такую ​​как TensorFlow.js: вам не придется заново реализовывать предварительную обработку конвейер в JavaScript.

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

inputs = keras.Input(shape=input_shape)
x = preprocessing_layer(inputs)
outputs = training_model(x)
inference_model = keras.Model(inputs, outputs)

Быстрые рецепты

Увеличение данных изображения (на устройстве)

Обратите внимание, что слои дополнения данных изображения активны только во время обучения (аналогично слою Dropout ).

from tensorflow import keras
from tensorflow.keras import layers

# Create a data augmentation stage with horizontal flipping, rotations, zooms
data_augmentation = keras.Sequential(
    [
        preprocessing.RandomFlip("horizontal"),
        preprocessing.RandomRotation(0.1),
        preprocessing.RandomZoom(0.1),
    ]
)

# Create a model that includes the augmentation stage
input_shape = (32, 32, 3)
classes = 10
inputs = keras.Input(shape=input_shape)
# Augment images
x = data_augmentation(inputs)
# Rescale image values to [0, 1]
x = preprocessing.Rescaling(1.0 / 255)(x)
# Add the rest of the model
outputs = keras.applications.ResNet50(
    weights=None, input_shape=input_shape, classes=classes
)(x)
model = keras.Model(inputs, outputs)

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

Нормализация числовых характеристик

# Load some data
(x_train, y_train), _ = keras.datasets.cifar10.load_data()
x_train = x_train.reshape((len(x_train), -1))
input_shape = x_train.shape[1:]
classes = 10

# Create a Normalization layer and set its internal state using the training data
normalizer = preprocessing.Normalization()
normalizer.adapt(x_train)

# Create a model that include the normalization layer
inputs = keras.Input(shape=input_shape)
x = normalizer(inputs)
outputs = layers.Dense(classes, activation="softmax")(x)
model = keras.Model(inputs, outputs)

# Train the model
model.compile(optimizer="adam", loss="sparse_categorical_crossentropy")
model.fit(x_train, y_train)
Downloading data from https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz
170500096/170498071 [==============================] - 11s 0us/step
1563/1563 [==============================] - 3s 1ms/step - loss: 2.1776
<tensorflow.python.keras.callbacks.History at 0x7f58c5f44208>

Кодирование категориальных функций строки с помощью однократного кодирования

# Define some toy data
data = tf.constant(["a", "b", "c", "b", "c", "a"])

# Use StringLookup to build an index of the feature values
indexer = preprocessing.StringLookup()
indexer.adapt(data)

# Use CategoryEncoding to encode the integer indices to a one-hot vector
encoder = preprocessing.CategoryEncoding(output_mode="binary")
encoder.adapt(indexer(data))

# Convert new test data (which includes unknown feature values)
test_data = tf.constant(["a", "b", "c", "d", "e", ""])
encoded_data = encoder(indexer(test_data))
print(encoded_data)
tf.Tensor(
[[0. 0. 0. 0. 1.]
 [0. 0. 0. 1. 0.]
 [0. 0. 1. 0. 0.]
 [0. 1. 0. 0. 0.]
 [0. 1. 0. 0. 0.]
 [1. 0. 0. 0. 0.]], shape=(6, 5), dtype=float32)

Обратите внимание, что индекс 0 зарезервирован для отсутствующих значений (которые вы должны указать как пустая строка "" ), а индекс 1 зарезервирован для значений вне словаря (значений, которые не были обнаружены во время adapt() ). Вы можете настроить это, используя аргументы конструктора mask_token и oov_token StringLookup .

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

Кодирование целочисленных категориальных функций с помощью однократного кодирования

# Define some toy data
data = tf.constant([10, 20, 20, 10, 30, 0])

# Use IntegerLookup to build an index of the feature values
indexer = preprocessing.IntegerLookup()
indexer.adapt(data)

# Use CategoryEncoding to encode the integer indices to a one-hot vector
encoder = preprocessing.CategoryEncoding(output_mode="binary")
encoder.adapt(indexer(data))

# Convert new test data (which includes unknown feature values)
test_data = tf.constant([10, 10, 20, 50, 60, 0])
encoded_data = encoder(indexer(test_data))
print(encoded_data)
tf.Tensor(
[[0. 0. 0. 1. 0.]
 [0. 0. 0. 1. 0.]
 [0. 0. 1. 0. 0.]
 [0. 1. 0. 0. 0.]
 [0. 1. 0. 0. 0.]
 [1. 0. 0. 0. 0.]], shape=(6, 5), dtype=float32)

Обратите внимание, что индекс 0 зарезервирован для пропущенных значений (которые вы должны указать как значение 0), а индекс 1 зарезервирован для значений вне словаря (значений, которые не были обнаружены во время adapt() ). Вы можете настроить это, используя аргументы конструктора mask_value и oov_value IntegerLookup .

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

Применение трюка хеширования к целочисленной категориальной функции

Если у вас есть категориальная функция, которая может принимать много разных значений (порядка 10e3 или выше), где каждое значение появляется в данных только несколько раз, индексирование и однократное кодирование значений функций становится непрактичным и неэффективным. Вместо этого может быть хорошей идеей применить «трюк с хешированием»: хешировать значения в вектор фиксированного размера. Это позволяет управлять размером пространства функций и устраняет необходимость в явном индексировании.

# Sample data: 10,000 random integers with values between 0 and 100,000
data = np.random.randint(0, 100000, size=(10000, 1))

# Use the Hashing layer to hash the values to the range [0, 64]
hasher = preprocessing.Hashing(num_bins=64, salt=1337)

# Use the CategoryEncoding layer to one-hot encode the hashed values
encoder = preprocessing.CategoryEncoding(max_tokens=64, output_mode="binary")
encoded_data = encoder(hasher(data))
print(encoded_data.shape)
(10000, 64)

Кодирование текста как последовательности индексов токенов

Вот как вы должны предварительно обработать текст для передачи на слой Embedding .

# Define some text data to adapt the layer
data = tf.constant(
    [
        "The Brain is wider than the Sky",
        "For put them side by side",
        "The one the other will contain",
        "With ease and You beside",
    ]
)
# Instantiate TextVectorization with "int" output_mode
text_vectorizer = preprocessing.TextVectorization(output_mode="int")
# Index the vocabulary via `adapt()`
text_vectorizer.adapt(data)

# You can retrieve the vocabulary we indexed via get_vocabulary()
vocab = text_vectorizer.get_vocabulary()
print("Vocabulary:", vocab)

# Create an Embedding + LSTM model
inputs = keras.Input(shape=(1,), dtype="string")
x = text_vectorizer(inputs)
x = layers.Embedding(input_dim=len(vocab), output_dim=64)(x)
outputs = layers.LSTM(1)(x)
model = keras.Model(inputs, outputs)

# Call the model on test data (which includes unknown tokens)
test_data = tf.constant(["The Brain is deeper than the sea"])
test_output = model(test_data)
Vocabulary: ['', '[UNK]', 'the', 'side', 'you', 'with', 'will', 'wider', 'them', 'than', 'sky', 'put', 'other', 'one', 'is', 'for', 'ease', 'contain', 'by', 'brain', 'beside', 'and']

Вы можете увидеть слой TextVectorization в действии в сочетании с режимом Embedding в примере классификации текста с нуля .

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

Кодирование текста как плотной матрицы нграмм с многократным горячим кодированием

Вот как вы должны предварительно обработать текст для передачи на Dense слой.

# Define some text data to adapt the layer
data = tf.constant(
    [
        "The Brain is wider than the Sky",
        "For put them side by side",
        "The one the other will contain",
        "With ease and You beside",
    ]
)
# Instantiate TextVectorization with "binary" output_mode (multi-hot)
# and ngrams=2 (index all bigrams)
text_vectorizer = preprocessing.TextVectorization(output_mode="binary", ngrams=2)
# Index the bigrams via `adapt()`
text_vectorizer.adapt(data)

print(
    "Encoded text:\n",
    text_vectorizer(["The Brain is deeper than the sea"]).numpy(),
    "\n",
)

# Create a Dense model
inputs = keras.Input(shape=(1,), dtype="string")
x = text_vectorizer(inputs)
outputs = layers.Dense(1)(x)
model = keras.Model(inputs, outputs)

# Call the model on test data (which includes unknown tokens)
test_data = tf.constant(["The Brain is deeper than the sea"])
test_output = model(test_data)

print("Model output:", test_output)
Encoded text:
 [[1. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 1. 1. 0. 0. 0. 0. 0.

  0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 1. 1. 0. 0. 0.]] 

Model output: tf.Tensor([[0.6381588]], shape=(1, 1), dtype=float32)

Кодирование текста как плотной матрицы нграмм с взвешиванием TF-IDF

Это альтернативный способ предварительной обработки текста перед его передачей на Dense слой.

# Define some text data to adapt the layer
data = tf.constant(
    [
        "The Brain is wider than the Sky",
        "For put them side by side",
        "The one the other will contain",
        "With ease and You beside",
    ]
)
# Instantiate TextVectorization with "tf-idf" output_mode
# (multi-hot with TF-IDF weighting) and ngrams=2 (index all bigrams)
text_vectorizer = preprocessing.TextVectorization(output_mode="tf-idf", ngrams=2)
# Index the bigrams and learn the TF-IDF weights via `adapt()`
text_vectorizer.adapt(data)

print(
    "Encoded text:\n",
    text_vectorizer(["The Brain is deeper than the sea"]).numpy(),
    "\n",
)

# Create a Dense model
inputs = keras.Input(shape=(1,), dtype="string")
x = text_vectorizer(inputs)
outputs = layers.Dense(1)(x)
model = keras.Model(inputs, outputs)

# Call the model on test data (which includes unknown tokens)
test_data = tf.constant(["The Brain is deeper than the sea"])
test_output = model(test_data)
print("Model output:", test_output)
Encoded text:
 [[8.04719   1.6945957 0.        0.        0.        0.        0.

  0.        0.        0.        0.        0.        0.        0.
  0.        0.        1.0986123 1.0986123 1.0986123 0.        0.
  0.        0.        0.        0.        0.        0.        0.
  1.0986123 0.        0.        0.        0.        0.        0.
  0.        1.0986123 1.0986123 0.        0.        0.       ]] 

Model output: tf.Tensor([[-1.2379041]], shape=(1, 1), dtype=float32)