케라스는 강력하면서도 사용하기 쉬운 전처리 층을 제공함
케라스의 전처리 층 사용 시,
대용량 데이터셋에서 텐서플로 모델을 훈련할 때는 자체 데이터 로드 및 전처리 API인 tf.data를 사용하는 것이 좋음
tf.data API가 읽을 수 있는 데이터
데이터셋이 준비되면 변환 메서드를 호출하여 여러 종류의 변환을 수행할 수 있음

경사 하강법은 훈련 세트에 있는 샘플이 독립적이고 동일한 분포일 때 최고의 성능을 보임
→ shuffle() 을 사용하여 샘플을 섞어서 구현
랜덤 시드를 부여하여 셔플링되는 순서를 프로그램 실행때마다 동일하게 만들 수 있음
메모리 용량보다 큰 대규모 데이터셋은 간단한 셔플링 버퍼 방식으로는 충분하지 않음
→ 원본 데이터 자체를 섞음으로써 해결할 수 있음. 셔플링 효과가 크게 향상.
원본 데이터가 섞여 있더라도 epoch마다 한 번 더 섞어주면, epoch마다 동일한 순서가 반복되어 모델에 편향이 추가되는 것을 방지할 수 있음.
파일 여러 개를 랜덤으로 선택하고 파일에서 동시에 읽은 레코드를 돌아가면서 반환한 뒤, shuffle()을 사용해 셔플링 버퍼를 추가하면 효과를 극대화 할 수 있음.
이를 위해서는 여러 파일에서 한 줄씩 번갈아 읽는 기능을 구현해야 함.
train_filepaths # 훈련 파일 경로를 담은 리스트
filepath_dataset = tf.data.Dataset.list_files(train_filepaths, seed=42)
n_readers=5
dataset = filepath_dataset.interleaver(
# 각 파일의 첫 번째 줄은 열 이름이므로 skip(1)
lambda filepath: tf.data.TextLineDataset(filepath).skip(1),
cycle_length=n_readers)
interleave()
※ 데이터셋으로는 '캘리포니아 주택 데이터셋'을 활용함
주택 데이터셋에는 바이트 문자열이 담긴 텐서가 있으므로, 문자열을 파싱하고 데이터 스케일을 조정하는 등의 전처리 작업이 필요함
X_mean, X_std = [...] # 훈련세트의 각 특성의 평균과 표준편차
n_inputs = 8
def parse_csv_line(line):
defs = [0.] * n_inputs + [tf.constant([], dtype=tf.float32)]
fields = tf.io.decode_csv(line, record_defaults=defs)
return tf.stack(fields[:-1]), tf.stack(fields[-1:])
def preprocess(line):
x, y = parse_csv_line(line)
return (x - X_mean) / X_std, y
지금까지 작성한 함수들을 캘리포니아 주택 데이터셋을 효율적으로 적재하고 전처리, 셔플링, 배치를 적용한 데이터셋을 만들어 반환하는 함수 하나로 작성
def csv_reader_dataset(filepaths, n_readers=5, n_read_threads=None,
n_parse_threads=5, shuffle_buffer_size=10_000, seed=42,
batch_size=32):
dataset = tf.data.Dataset.list_files(filepaths, seed=seed)
dataset = dataset.interleave(
lambda filepath: tf.data.TextLineDataset(filepath).skip(1)
cycle_length=n_readers, num_parallel_calls=n_read_threads)
dataset = dataset.map(preprocess, num_parellel_calls=n_parse_threads)
dataset = dataset.shuffle(shuffle_buffer_size, seed=seed)
return dataset.batch(batch_size).prefetch(1)

csv_reader_dataset() 함수의 마지막에 prefetch(1)을 호출하면 데이터셋은 항상 하나의 배치가 미리 준비되도록함
csv_reader_dataset() 함수로 훈련 세트, 검증 세트, 테스트 세트를 위한 데이터셋을 생성하고, 케라스 모델을 만들어 이 데이터셋으로 훈련할 수 있음
신경망에 사용할 데이터를 준비하기 위해 전처리 하는 방법에는 여러가지가 있음
Normalization 층에서는 입력 특성을 표준화할 수 있음
층을 생성할 때 각 특성의 평균과 분산을 지정하거나 / adapt() 메서드에 훈련 세트를 전달하여 특성의 평균과 분산을 계산함
모델에 Normalization 층 을 추가하면,
+) 모델이 정규화를 알아서 처리하고, 전처리 불일치 위험(훈련과 제품에 사용하는 전처리 코드를 별도로 유지하다가 제대로 업데이트가 되지 않는 문제)을 완전히 제거함
-) 전처리가 훈련하는 동안 즉시 적용되기 때문에 epoch마다 매번 수행하게 되어 훈련 속도가 느려짐
모델과 Normalization 층을 독립적으로 사용하면,
+) 전처리를 한 번만 수행하여 훈련 속도가 빨라짐
-) 모델을 제품에 배포했을 때 입력을 전처리하지 못함
→ Normalization 층과 훈련된 모델을 포함하는 새로운 모델을 만들고, 최종 모델을 제품에 배포
Discretization 층에서는 값 범위를 범주로 매핑하여 수치 특성을 범주형 특성으로 변환
다중모드(multimodal) 분포를 가진 특성이나 타깃과의 관계가 매우 비선형적인 특성에 유용함
범주 식별자는 원-핫 인코딩을 사용해 인코딩하고, 신경망으로 주입해야 함
범주의 개수가 적다면 원-핫 인코딩은 좋은 옵션. CategoryEncoding은 이를 제공함
동시에 한 개 이상의 범주형 특성을 인코딩하면 CategoryEncoding 클래스는 기본적으로 멀티-핫 인코딩 수행. 입력 특성에 있는 범주에 해당하는 위치마다 출력 텐서의 값이 1이 됨.
멀티-핫 인코딩과 카운트 인코딩은 범주를 활성화한 특성이 어떤 것인지 알 수 없기 때문에 정보에 손실이 있음
→ 특성마다 별도로 원-핫 인코딩을 한 다음 출력을 합치면, 범주 식별자가 겹치지 않도록 조정할 수 있음
멀티-핫 인코딩 / 특성별 원-핫 인코딩이 더 나을지는 미리 알기 어려움. 두 방식을 모두 테스트하여 비교해보아야 함.
StringLookup 층에서는 텍스트로 된 범주형 특성을 다룸
StringLookup 층을 만들 때 output_mode="one_hot"으로 지정하면 정수대신 원-핫 벡터 출력
훈련 세트가 매우 크면 랜덤하게 일부를 추출하여 adapt() 에 전달하는 것이 편리
이 경우, adapt() 는 등장 빈도가 낮은 범주를 모두 0으로 매핑하기 때문에 모델이 구별할 수 X
→ num_oov_indices를 1보다 큰 정수로 지정하여 이런 위험을 줄일 수 있음
= OOV(out-of-vocabulary) 버킷 개수를 지정하는 것. 알 수 없는 범주는 해시 함수를 사용해 OOV 버킷 중 하나로 랜덤하게 매핑
cities = ["Auckland", "Paris", "Paris", "San Francisco"]
str_lookup_layer = tf.keras.layers.StringLookup(num_oov_indices=5)
str_lookup_layer.adapt(cities)
str_lookup_layer([["Paris"], ["Auckland"], ["Foo"], ["Bar"], ["Baz"]])
# <tf.Tensor: shape(4, 1), dtype=int64, numpy=array([[5], [7], [4], [3], [4]])>
해싱 충돌의 위험을 줄이는 유일한 방법은 OOV 버킷 수를 늘리는 것
해싱 트릭 : 범주를 랜덤하게 버킷에 매핑하는 아이디어
케라스에는 해싱 트릭을 위한 전용 Hasing 층이 존재함
여기에서도 해싱 충돌이 발생하기 때문에 일반적으로는 StringLookup 층을 사용

임베딩 행렬은
케라스는 임베딩 행렬을 감싼 Embedding 층을 제공
범주 ID를 임베딩으로 변환하기 위해 Embedding 층이 범주에 해당하는 행을 찾아 반환함
지금까지 설명한 층들을 사용하여 일반적인 수치 특성과 함께 범주형 텍스트 특성을 처리하고, 각 범주를 위한 임베딩을 학습하는 케라스 모델 예제
X_train_num, X_train_cat, y_train = [...] # 훈련세트 로드
X_valid_num, X_valid_cat, y_valid = [...] # 검증세트 로드
num_input = tf.keras.layers.Input(shape=[8], name="num")
cat_input = tf.keras.layers.Input(shape=[], dtype=tf.string, name="cat")
cat_embeddings = lookup_and_embed(cat_input)
encoded_inputs = tf.keras.layers.concatenate([num_input, cat_embeddings])
outputs = tf.keras.layers.Dense(1)(encoded_inputs)
model = tf.keras.models.Model(inputs=[num_input, cat_input], outputs=[outputs])
model.compile(loss="mse", optimizer="sgd")
history = model.fit((X_train_num, X_train_cat), y_train, epochs=5,
validation_data=((X_valid_num, X_valid_cat), y_valid))
자연어 처리 작업을 수행할 때 직접 단어 임베딩을 훈련하는 것보다 사전 훈련된 임베딩을 재사용하는 것이 좋은 경우가 많음
비슷한 말은 임베딩이 매우 비슷하고, France / Spain / Italy 같이 관련(국가명)된 단어들은 함께 군집을 형성
단어 임베딩은 어떤 의미를 가진 축을 따라 임베딩 공간 안에서 조직됨
단어의 인베딩 벡터를 더하고 뺄 수 있음
예시 1 ) King - Man + Woman 계산
예시 2 ) Madrid - Spain + France 계산
하지만, 단어 임베딩은 크게 편향되는 경우가 있음
TextVectorization층에서는 기본적인 텍스트 전처리를 할 수 있음
층을 생성할 때 vocabulary 매개변수로 어휘 사전을 전달하거나, adapt() 메서드를 사용해 훈련 데이터로부터 어휘 사전을 학습할 수 있음
train_data = ["To be", "!(to be)", "That's the question", "Be, be, be."]
text_vec_layer = tf.keras.layers.TextVectorization()
text_vec_layer.adapt(train_data)
text_vec_layer(["Be good!", "Question: be or be?"])
<tf.Tensor: shape=(2, 4), dtype=int64, numpy=
# array([[2, 1, 0, 0],
# [6, 2, 1, 2]])>
대소문자와 구두점을 유지하거나, 문장 분할을 막으려면 옵션을 설정할 수 있음
to, the와 같은 단어는 너무 자주 등장해서 중요성이 떨어지는 반면, basketball과 같이 드물게 나타나는 단어는 많은 정보를 가짐 → output_mode="tf_idf"로 설정
텐서플로 텍스트 라이브러리를 사용하면 텍스트를 단어보다 작은 토큰(token)으로 분할할 수 있는 부분 단어 토크나이저(subword tokenizer)를 제공
→ TextVectorization 보다 높은 기능 수행
텐서플로 허브 라이브러리를 사용하면 텍스트, 이미지, 오디오 등을 위해 사전 훈련된 모델의 구성요소를 쉽게 재사용할 수 있음. 이러한 모델의 구성 요소를 모듈이라고 함.
문장 인코더
케라스 전처리 API에는 3개의 이미지 전처리 층이 포함
데이터 증식 : 인공적으로 훈련 세트의 크기를 증가시켜 변형된 이미지가 증식되지 않은 실제 이미지처럼 보이는 한 성능을 향상시킴
import tensorflow_datasets as tfds
dataset = tfds.load(name="mnist")
mnist_train, mnist_test = dataset["train"], dataset["test"]
for batch in mnist_train.shuffle(10_000, seed=42).batch(32).prefetch(1):
images = batch["image"]
labels = batch["label"]
# [...] images와 labels로 필요한 작업 수행
train_set, valid_set, test_set = tfds.load(
name="mnist",
split=["train[:90%]", "train[90%:]", "test"],
as_supervised=True
)
train_set = train_set.shuffle(buffer_size=10_000, seed=42).batch(32).prefetch(1)
valid_set = valid_set.batch(32).cache()
test_set = test_set.batch(32)cache()
tf.random.set_seed(42)
model = tf.keras.Sequential([
tf.keras.layers.Flatten(input_shape=(28, 28)),
tf.keras.layers.Dense(10, activation="softmax")
])
model.compile(loss="sparse_categorical_crossentropy", optimizer="nadam",
metrics=["accuracy"])
history = model.fit(train_set, validation_data=valid_set, epochs=5)
test_loss, test_accuracy = model.evaluate(test_set)