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

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

Предварительная обработка Keras

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

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

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

Предварительная обработка текста

  • tf.keras.layers.TextVectorization : превращает необработанные строки в кодированное представление , которые могут быть прочитаны с помощью Embedding слоя или Dense слоя.

Предварительная обработка числовых признаков

  • tf.keras.layers.Normalization : выполняет особенность мудр Нормализация входных признаков.
  • tf.keras.layers.Discretization : переворачивает непрерывные числовые функции в числе категориальных признаков.

Предварительная обработка категориальных признаков

  • tf.keras.layers.CategoryEncoding : повороты целых категориальных функций в одной горячие, мульти-горячихи, или количество плотных представлений.
  • tf.keras.layers.Hashing : выполняет категоричен функция хеширования, также известный как «хеширования трюк».
  • tf.keras.layers.StringLookup : повороты строка категорично значение кодированного представления , которые могут быть прочитаны с помощью Embedding слоя или Dense слоя.
  • tf.keras.layers.IntegerLookup : витки целых категориальных значений в кодированное представление , которые могут быть прочитаны с помощью Embedding слоя или Dense слоя.

Предварительная обработка изображения

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

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

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

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

adapt() метод

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

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

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

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

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

data = np.array([[0.1, 0.2, 0.3], [0.8, 0.9, 1.0], [1.5, 1.6, 1.7],])
layer = layers.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

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

data = [
    "ξεῖν᾽, ἦ τοι μὲν ὄνειροι ἀμήχανοι ἀκριτόμυθοι",
    "γίγνοντ᾽, οὐδέ τι πάντα τελείεται ἀνθρώποισι.",
    "δοιαὶ γάρ τε πύλαι ἀμενηνῶν εἰσὶν ὀνείρων:",
    "αἱ μὲν γὰρ κεράεσσι τετεύχαται, αἱ δ᾽ ἐλέφαντι:",
    "τῶν οἳ μέν κ᾽ ἔλθωσι διὰ πριστοῦ ἐλέφαντος,",
    "οἵ ῥ᾽ ἐλεφαίρονται, ἔπε᾽ ἀκράαντα φέροντες:",
    "οἱ δὲ διὰ ξεστῶν κεράων ἔλθωσι θύραζε,",
    "οἵ ῥ᾽ ἔτυμα κραίνουσι, βροτῶν ὅτε κέν τις ἴδηται.",
]
layer = layers.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 слой предвычисленными лексики:

vocab = ["a", "b", "c", "d"]
data = tf.constant([["a", "c", "d"], ["d", "z", "b"]])
layer = layers.StringLookup(vocabulary=vocab)
vectorized_data = layer(data)
print(vectorized_data)
tf.Tensor(
[[1 3 4]
 [4 0 2]], 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)

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

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

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

С этой опцией ваша предварительная обработка будет происходить на ЦП асинхронно и будет буферизована перед переходом в модель. Кроме того, если вы звоните dataset.prefetch(tf.data.AUTOTUNE) на наборе данных, предварительная обработка будет происходить эффективно параллельно с обучением:

dataset = dataset.map(lambda x, y: (preprocessing_layer(x), y))
dataset = dataset.prefetch(tf.data.AUTOTUNE)
model.fit(dataset, ...)

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

При работе в ТПУ, вы всегда должны поместить предобработки слои в tf.data трубопроводе (за исключением Normalization и Rescaling , которые прекрасно работают на ТПУ и обычно используется как первый слой представляет собой изображение модели).

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

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

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

Если вы изначально положить Preprocessing слоев в вашем 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(
    [
        layers.RandomFlip("horizontal"),
        layers.RandomRotation(0.1),
        layers.RandomZoom(0.1),
    ]
)

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

# Create a tf.data pipeline of augmented images (and their labels)
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
train_dataset = train_dataset.batch(16).map(lambda x, y: (data_augmentation(x), y))


# Create a model and train it on the augmented image data
inputs = keras.Input(shape=input_shape)
x = layers.Rescaling(1.0 / 255)(inputs)  # Rescale inputs
outputs = keras.applications.ResNet50(  # Add the rest of the model
    weights=None, input_shape=input_shape, classes=classes
)(x)
model = keras.Model(inputs, outputs)
model.compile(optimizer="rmsprop", loss="sparse_categorical_crossentropy")
model.fit(train_dataset, steps_per_epoch=5)
Downloading data from https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz
170500096/170498071 [==============================] - 6s 0us/step
170508288/170498071 [==============================] - 6s 0us/step
5/5 [==============================] - 11s 44ms/step - loss: 8.8927
<keras.callbacks.History at 0x7f1c0c3f16d0>

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

Нормирование числовых признаков

# 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 = layers.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)
1563/1563 [==============================] - 3s 2ms/step - loss: 2.1304
<keras.callbacks.History at 0x7f1bc43f40d0>

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

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

# Use StringLookup to build an index of the feature values and encode output.
lookup = layers.StringLookup(output_mode="one_hot")
lookup.adapt(data)

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

Обратите внимание , что здесь индекс 0 зарезервирован для вне словаря значения (значения , которые не были замечены в процессе adapt() ).

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

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

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

# Use IntegerLookup to build an index of the feature values and encode output.
lookup = layers.IntegerLookup(output_mode="one_hot")
lookup.adapt(data)

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

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

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

Применение хэш-трюка к целочисленному категориальному признаку

Если у вас есть категориальный признак, который может принимать много разных значений (порядка 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 = layers.Hashing(num_bins=64, salt=1337)

# Use the CategoryEncoding layer to multi-hot encode the hashed values
encoder = layers.CategoryEncoding(num_tokens=64, output_mode="multi_hot")
encoded_data = encoder(hasher(data))
print(encoded_data.shape)
(10000, 64)

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

Это, как вы должны препроцессировать текст , который будет передан в Embedding слоя.

# Define some text data to adapt the layer
adapt_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",
    ]
)

# Create a TextVectorization layer
text_vectorizer = layers.TextVectorization(output_mode="int")
# Index the vocabulary via `adapt()`
text_vectorizer.adapt(adapt_data)

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

# Create a simple model
inputs = keras.Input(shape=(None,), dtype="int64")
x = layers.Embedding(input_dim=text_vectorizer.vocabulary_size(), output_dim=16)(inputs)
x = layers.GRU(8)(x)
outputs = layers.Dense(1)(x)
model = keras.Model(inputs, outputs)

# Create a labeled dataset (which includes unknown tokens)
train_dataset = tf.data.Dataset.from_tensor_slices(
    (["The Brain is deeper than the sea", "for if they are held Blue to Blue"], [1, 0])
)

# Preprocess the string inputs, turning them into int sequences
train_dataset = train_dataset.batch(2).map(lambda x, y: (text_vectorizer(x), y))
# Train the model on the int sequences
print("\nTraining model...")
model.compile(optimizer="rmsprop", loss="mse")
model.fit(train_dataset)

# For inference, you can export a model that accepts strings as input
inputs = keras.Input(shape=(1,), dtype="string")
x = text_vectorizer(inputs)
outputs = model(x)
end_to_end_model = keras.Model(inputs, outputs)

# Call the end-to-end model on test data (which includes unknown tokens)
print("\nCalling end-to-end model on test string...")
test_data = tf.constant(["The one the other will absorb"])
test_output = end_to_end_model(test_data)
print("Model output:", test_output)
Encoded text:
 [[ 2 19 14  1  9  2  1]]

Training model...
1/1 [==============================] - 3s 3s/step - loss: 0.4776

Calling end-to-end model on test string...
Model output: tf.Tensor([[0.04233753]], shape=(1, 1), dtype=float32)

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

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

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

Это, как вы должны препроцессировать текст , который будет принят в Dense слой.

# Define some text data to adapt the layer
adapt_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 "multi_hot" output_mode
# and ngrams=2 (index all bigrams)
text_vectorizer = layers.TextVectorization(output_mode="multi_hot", ngrams=2)
# Index the bigrams via `adapt()`
text_vectorizer.adapt(adapt_data)

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

# Create a simple model
inputs = keras.Input(shape=(text_vectorizer.vocabulary_size(),))
outputs = layers.Dense(1)(inputs)
model = keras.Model(inputs, outputs)

# Create a labeled dataset (which includes unknown tokens)
train_dataset = tf.data.Dataset.from_tensor_slices(
    (["The Brain is deeper than the sea", "for if they are held Blue to Blue"], [1, 0])
)

# Preprocess the string inputs, turning them into int sequences
train_dataset = train_dataset.batch(2).map(lambda x, y: (text_vectorizer(x), y))
# Train the model on the int sequences
print("\nTraining model...")
model.compile(optimizer="rmsprop", loss="mse")
model.fit(train_dataset)

# For inference, you can export a model that accepts strings as input
inputs = keras.Input(shape=(1,), dtype="string")
x = text_vectorizer(inputs)
outputs = model(x)
end_to_end_model = keras.Model(inputs, outputs)

# Call the end-to-end model on test data (which includes unknown tokens)
print("\nCalling end-to-end model on test string...")
test_data = tf.constant(["The one the other will absorb"])
test_output = end_to_end_model(test_data)
print("Model output:", test_output)
WARNING:tensorflow:5 out of the last 1567 calls to <function PreprocessingLayer.make_adapt_function.<locals>.adapt_step at 0x7f1b9c5c5290> triggered tf.function retracing. Tracing is expensive and the excessive number of tracings could be due to (1) creating @tf.function repeatedly in a loop, (2) passing tensors with different shapes, (3) passing Python objects instead of tensors. For (1), please define your @tf.function outside of the loop. For (2), @tf.function has experimental_relax_shapes=True option that relaxes argument shapes that can avoid unnecessary retracing. For (3), please refer to https://www.tensorflow.org/guide/function#controlling_retracing and https://www.tensorflow.org/api_docs/python/tf/function for  more details.
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.]]

Training model...
1/1 [==============================] - 0s 231ms/step - loss: 1.0046

Calling end-to-end model on test string...
Model output: tf.Tensor([[-0.54753447]], shape=(1, 1), dtype=float32)

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

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

# Define some text data to adapt the layer
adapt_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 = layers.TextVectorization(output_mode="tf-idf", ngrams=2)
# Index the bigrams and learn the TF-IDF weights via `adapt()`

with tf.device("CPU"):
    # A bug that prevents this from running on GPU for now.
    text_vectorizer.adapt(adapt_data)

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

# Create a simple model
inputs = keras.Input(shape=(text_vectorizer.vocabulary_size(),))
outputs = layers.Dense(1)(inputs)
model = keras.Model(inputs, outputs)

# Create a labeled dataset (which includes unknown tokens)
train_dataset = tf.data.Dataset.from_tensor_slices(
    (["The Brain is deeper than the sea", "for if they are held Blue to Blue"], [1, 0])
)

# Preprocess the string inputs, turning them into int sequences
train_dataset = train_dataset.batch(2).map(lambda x, y: (text_vectorizer(x), y))
# Train the model on the int sequences
print("\nTraining model...")
model.compile(optimizer="rmsprop", loss="mse")
model.fit(train_dataset)

# For inference, you can export a model that accepts strings as input
inputs = keras.Input(shape=(1,), dtype="string")
x = text_vectorizer(inputs)
outputs = model(x)
end_to_end_model = keras.Model(inputs, outputs)

# Call the end-to-end model on test data (which includes unknown tokens)
print("\nCalling end-to-end model on test string...")
test_data = tf.constant(["The one the other will absorb"])
test_output = end_to_end_model(test_data)
print("Model output:", test_output)
WARNING:tensorflow:6 out of the last 1568 calls to <function PreprocessingLayer.make_adapt_function.<locals>.adapt_step at 0x7f1b9f6eae60> triggered tf.function retracing. Tracing is expensive and the excessive number of tracings could be due to (1) creating @tf.function repeatedly in a loop, (2) passing tensors with different shapes, (3) passing Python objects instead of tensors. For (1), please define your @tf.function outside of the loop. For (2), @tf.function has experimental_relax_shapes=True option that relaxes argument shapes that can avoid unnecessary retracing. For (3), please refer to https://www.tensorflow.org/guide/function#controlling_retracing and https://www.tensorflow.org/api_docs/python/tf/function for  more details.
Encoded text:
 [[5.461647  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.       ]]

Training model...
1/1 [==============================] - 0s 239ms/step - loss: 4.4868

Calling end-to-end model on test string...
Model output: tf.Tensor([[0.25670475]], shape=(1, 1), dtype=float32)

Важные ошибки

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

Вы можете найти себе работать с очень большим словарным запасом в TextVectorization , в StringLookup слое, или IntegerLookup слое. Как правило, словарь размером более 500 МБ считается «очень большим».

В таком случае, для наилучшей производительности, вы должны избегать использования adapt() . Вместо этого заранее вычислите свой словарный запас (для этого вы можете использовать Apache Beam или TF Transform) и сохраните его в файле. Затем загрузите словарь в слой во время строительства, передавая путь файла в качестве vocabulary аргумента.

Использование подстановок слоев на ТП стручке или ParameterServerStrategy .

Существует нерешенный вопрос , который вызывает производительность деградирует при использовании TextVectorization , StringLookup или IntegerLookup слой во время тренировки на ТП стручке или на нескольких машинах через ParameterServerStrategy . Это планируется исправить в TensorFlow 2.7.