질문이있다? TensorFlow 포럼 방문 포럼 에서 커뮤니티와 연결

전처리 레이어 처리

TensorFlow.org에서 보기 Google Colab에서 실행 GitHub에서 소스 보기 노트북 다운로드

Keras 전처리 레이어

Keras 전처리 레이어 API를 사용하면 개발자가 Keras 네이티브 입력 처리 파이프라인을 빌드할 수 있습니다. 이러한 입력 처리 파이프라인은 Keras가 아닌 워크플로에서 독립적인 전처리 코드로 사용하고, Keras 모델과 직접 결합하고, Keras SavedModel의 일부로 내보낼 수 있습니다.

Keras 전처리 레이어를 사용하면 진정한 엔드 투 엔드 모델(원시 이미지 또는 원시 구조적 데이터를 입력으로 받아들이는 모델과 특성 정규화 또는 특성 값 인덱싱을 자체적으로 처리하는 모델)을 빌드하고 내보낼 수 있습니다.

사용 가능한 전처리 레이어

핵심 전처리 레이어

  • TextVectorization 레이어: 원시 문자열을 Embedding 레이어 또는 Dense 레이어에서 읽을 수 있는 인코딩된 표현으로 변환합니다.
  • Normalization 레이어: 입력 특성의 특성별 정규화를 수행합니다.

구조적 데이터 전처리 레이어

다음 레이어는 구조적 데이터 인코딩 및 특성 엔지니어링을 위한 것입니다.

  • CategoryEncoding 레이어: 정수 범주형 특성을 원-핫, 멀티-핫 또는 TF-IDF 밀도 표현으로 바꿉니다.
  • Hashing 레이어: "해싱 트릭(hashing trick)"이라고도 하는 범주형 특성 해싱을 수행합니다.
  • 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: 특성의 평균 및 표준 편차를 보유합니다.
  • StringLookupIntegerLookup: 입력 값과 출력 인덱스 간의 매핑을 보유합니다.
  • CategoryEncoding : 입력 값의 색인을 보유합니다.
  • Discretization : 값 버킷 경계에 대한 정보를 보유합니다.

결정적으로, 이러한 레이어는 훈련 불가능합니다. 이들의 상태는 훈련 중에 설정되지 않습니다. 훈련 전에 설정해야 합니다. 이 단계를 "적응(adaptation)"이라고 합니다.

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

adapt() 메소드는 Numpy 배열 또는 tf.data.Dataset 객체를 사용합니다. StringLookupTextVectorization 의 경우, 문자열의 목록을 전달할 수도 있습니다.

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 레이어를 인스턴스화하는 예입니다.

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)

이 옵션을 사용하면 나머지 모델 실행과 동시에 기기에서 전처리가 이루어지므로 GPU 가속의 이점이 있습니다. GPU에서 훈련하는 경우, Normalization 레이어와 모든 이미지 전처리 및 데이터 증대 레이어에 가장 적합한 옵션입니다.

옵션 2: tf.data.Dataset에 전처리 레이어를 적용하여 다음과 같이 전처리된 데이터의 배치를 생성하는 데이터세트를 얻습니다.

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

이 옵션을 사용하면 전처리가 CPU에서 비동기적으로 발생하고 모델로 이동하기 전에 버퍼링됩니다.

이 옵션은 TextVectorization 및 모든 구조적 데이터 전처리 레이어에 가장 적합한 옵션입니다. CPU에서 훈련하고 이미지 전처리 레이어를 사용하는 경우에도 좋은 옵션이 될 수 있습니다.

추론 시 모델 내에서 전처리를 수행할 때의 이점

옵션 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)
1563/1563 [==============================] - 3s 1ms/step - loss: 2.1873
<tensorflow.python.keras.callbacks.History at 0x7fd309ae57b8>

원-핫 인코딩을 통해 문자열 범주형 특성 인코딩하기

# 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_tokenStringLookupoov_token 생성자 인수를 사용하여 이를 구성할 수 있습니다.

처음부터 구조적 데이터 분류하기 예제에서 동작 중인 StringLookupCategoryEncoding 레이어를 볼 수 있습니다.

원-핫 인코딩을 통해 정수 범주형 특성 인코딩하기

# 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_valueIntegerLookupoov_value 생성자 인수를 사용하여 이를 구성할 수 있습니다.

처음부터 구조적 데이터 분류하기 예제에서 동작 중인 IntegerLookupCategoryEncoding 레이어를 볼 수 있습니다.

정수 범주형 특성에 해싱 트릭 적용하기

여러 다른 값(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']

처음부터 텍스트 분류 예에서 Embedding 모드와 결합된 TextVectorization 레이어가 동작하는 것을 볼 수 있습니다.

이러한 모델을 훈련할 때 최상의 성능을 위해 TextVectorization 레이어를 입력 파이프라인(위의 텍스트 분류 예제에서 수행한 작업)의 일부로 사용해야 합니다.

멀티-핫 인코딩을 사용하여 텍스트를 ngram의 밀집 행렬로 인코딩하기

다음은 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.3238977]], shape=(1, 1), dtype=float32)

TF-IDF 가중치를 사용하여 텍스트를 ngram의 밀집 행렬로 인코딩하기

다음은 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([[-2.7245688]], shape=(1, 1), dtype=float32)