Keras 전처리 레이어를 사용한 구조적 데이터 분류

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

이 튜토리얼에서는 구조적 데이터(예: CSV의 표 형식 데이터)를 분류하는 방법을 보여줍니다. Keras를 사용하여 모델을 정의하고, 전처리 레이어를 CSV의 열에서 모델 훈련에 사용되는 특성으로 매핑하는 브리지로 사용합니다. 이 튜토리얼에는 다음을 위한 전체 코드가 포함되어 있습니다.

  • Pandas를 사용하여 CSV 파일을 로드합니다.
  • tf.data를 사용하여 행을 일괄 처리하고 셔플하는 입력 파이프라인을 빌드합니다.
  • Keras 전처리 레이어를 사용하여 CSV의 열에서 모델을 훈련하는 데 사용되는 특성으로 매핑합니다.
  • Keras를 사용하여 모델을 빌드, 훈련 및 평가합니다.

참고: 이 튜토리얼은 특성 열의 구조적 데이터 분류하기와 유사합니다. 이 버전은 tf.feature_column 대신 새로운 실험용 Keras 전처리 레이어를 사용합니다. Keras Preprocessing Layer는 더 직관적이며 배포를 단순화하기 위해 모델 내에 쉽게 포함될 수 있습니다.

데이터세트

PetFinder 데이터세트의 단순화된 버전을 사용합니다. CSV에는 수천 개의 행이 있습니다. 각 행은 애완 동물을 설명하고 각 열은 속성을 설명합니다. 이 정보를 사용하여 애완 동물의 입양 여부를 예측합니다.

다음은 이 데이터세트에 대한 설명입니다. 숫자 열과 범주 열이 모두 있습니다. 이 튜토리얼에서 사용하지 않는 자유 텍스트 열이 있습니다.

설명 특성 유형 데이터 형식
유형 동물의 종류(개, 고양이) 범주형 문자열
나이 애완 동물의 나이 수치 정수
품종 1 애완 동물의 기본 품종 범주형 문자열
색상 1 애완 동물의 색상 1 범주형 문자열
색상 2 애완 동물의 색상 2 범주형 문자열
MaturitySize 성장한 크기 범주형 문자열
FurLength 모피 길이 범주형 문자열
예방 접종 애완 동물이 예방 접종을 받았습니다 범주형 문자열
불임 시술 애완 동물이 불임 시술을 받았습니다 범주형 문자열
건강 건강 상태 범주형 문자열
회비 입양비 수치 정수
설명 이 애완 동물에 대한 프로필 작성 텍스트 문자열
PhotoAmt 이 애완 동물의 업로드된 총 사진 수치 정수
AdoptionSpeed 입양 속도 분류 정수

TensorFlow 및 기타 라이브러리 가져오기

pip install -q sklearn
import numpy as np
import pandas as pd
import tensorflow as tf

from sklearn.model_selection import train_test_split
from tensorflow.keras import layers
from tensorflow.keras.layers.experimental import preprocessing
tf.__version__
'2.6.0'

Pandas를 사용하여 데이터 프레임 만들기

Pandas는 구조적 데이터를 로드하고 처리하는 데 유용한 여러 유틸리티가 포함된 Python 라이브러리입니다. Pandas를 사용하여 URL에서 데이터세트를 다운로드하고 데이터 프레임에 로드합니다.

import pathlib

dataset_url = 'http://storage.googleapis.com/download.tensorflow.org/data/petfinder-mini.zip'
csv_file = 'datasets/petfinder-mini/petfinder-mini.csv'

tf.keras.utils.get_file('petfinder_mini.zip', dataset_url,
                        extract=True, cache_dir='.')
dataframe = pd.read_csv(csv_file)
Downloading data from http://storage.googleapis.com/download.tensorflow.org/data/petfinder-mini.zip
1671168/1668792 [==============================] - 0s 0us/step
1679360/1668792 [==============================] - 0s 0us/step
dataframe.head()

목표 변수 만들기

Kaggle 대회에서의 작업은 애완 동물이 입양되는 속도를 예측하는 것입니다(예: 첫 주, 첫 달, 첫 3개월 등). 튜토리얼을 위해 단순화해 봅시다. 여기에서는 입양 속도를 이진 분류 문제로 변환하고 단순히 애완 동물이 입양되었는지 여부를 예측합니다.

레이블 열을 수정한 후, 0은 애완 동물이 입양되지 않았음을 나타내고 1은 입양되었음을 나타냅니다.

# In the original dataset "4" indicates the pet was not adopted.
dataframe['target'] = np.where(dataframe['AdoptionSpeed']==4, 0, 1)

# Drop un-used columns.
dataframe = dataframe.drop(columns=['AdoptionSpeed', 'Description'])

데이터 프레임을 훈련, 검증 및 테스트로 분할하기

다운로드한 데이터세트는 단일 CSV 파일입니다. 이를 훈련, 검증 및 테스트 세트로 분할합니다.

train, test = train_test_split(dataframe, test_size=0.2)
train, val = train_test_split(train, test_size=0.2)
print(len(train), 'train examples')
print(len(val), 'validation examples')
print(len(test), 'test examples')
7383 train examples
1846 validation examples
2308 test examples

tf.data를 사용하여 입력 파이프라인 만들기

다음으로 데이터를 셔플하고 일괄 처리하기 위해 tf.data로 데이터 프레임을 래핑합니다. 매우 큰 CSV 파일(메모리에 적합하지 않을 정도로 큰 파일)을 사용하는 경우, tf.data를 사용하여 디스크에서 직접 읽을 수 있습니다. 이 튜토리얼에서는 다루지 않습니다.

# A utility method to create a tf.data dataset from a Pandas Dataframe
def df_to_dataset(dataframe, shuffle=True, batch_size=32):
  dataframe = dataframe.copy()
  labels = dataframe.pop('target')
  ds = tf.data.Dataset.from_tensor_slices((dict(dataframe), labels))
  if shuffle:
    ds = ds.shuffle(buffer_size=len(dataframe))
  ds = ds.batch(batch_size)
  ds = ds.prefetch(batch_size)
  return ds

이제 입력 파이프라인을 생성했으므로 반환되는 데이터의 형식을 확인하기 위해 호출해 보겠습니다. 출력을 읽기 쉽게 유지하기 위해 작은 배치 크기를 사용했습니다.

batch_size = 5
train_ds = df_to_dataset(train, batch_size=batch_size)
[(train_features, label_batch)] = train_ds.take(1)
print('Every feature:', list(train_features.keys()))
print('A batch of ages:', train_features['Age'])
print('A batch of targets:', label_batch )
Every feature: ['Type', 'Age', 'Breed1', 'Gender', 'Color1', 'Color2', 'MaturitySize', 'FurLength', 'Vaccinated', 'Sterilized', 'Health', 'Fee', 'PhotoAmt']
A batch of ages: tf.Tensor([ 1 24  3 96  1], shape=(5,), dtype=int64)
A batch of targets: tf.Tensor([1 0 1 1 1], shape=(5,), dtype=int64)

데이터세트가 데이터 프레임의 행에서 열 값에 매핑되는 열 이름의 사전(데이터 프레임에서)을 반환하는 것을 볼 수 있습니다.

전처리 레이어의 사용을 시연합니다.

Keras 전처리 레이어 API를 사용하면 Keras 네이티브 입력 처리 파이프라인을 빌드할 수 있습니다. 3개의 전처리 레이어를 사용하여 특성 전처리 코드를 보여줍니다.

여기에서 사용 가능한 전처리 레이어의 목록을 찾을 수 있습니다.

숫자 열

각 숫자 특성에 대해 Normalization() 레이어를 사용하여 각 특성의 평균이 0이고 표준 편차가 1인지 확인합니다.

get_normalization_layer 함수는 특성별 정규화를 숫자 특성에 적용하는 레이어를 반환합니다.

def get_normalization_layer(name, dataset):
  # Create a Normalization layer for our feature.
  normalizer = preprocessing.Normalization(axis=None)

  # Prepare a Dataset that only yields our feature.
  feature_ds = dataset.map(lambda x, y: x[name])

  # Learn the statistics of the data.
  normalizer.adapt(feature_ds)

  return normalizer
photo_count_col = train_features['PhotoAmt']
layer = get_normalization_layer('PhotoAmt', train_ds)
layer(photo_count_col)
<tf.Tensor: shape=(5,), dtype=float32, numpy=
array([-0.19294643,  0.12948996,  0.12948996,  0.45192632, -0.19294643],
      dtype=float32)>

참고: 숫자 특성(수백 개 이상)이 많은 경우, 먼저 숫자 특성을 연결하고 단일 normalization 레이어를 사용하는 것이 더 효율적입니다.

범주 열

이 데이터세트에서 Type은 문자열(예: 'Dog'또는 'Cat')으로 표시됩니다. 모델에 직접 문자열을 공급할 수 없습니다. 전처리 레이어는 문자열을 원-핫 벡터로 처리합니다.

get_category_encoding_layer 함수는 어휘의 값을 정수 인덱스로 매핑하고 특성을 원-핫 인코딩하는 레이어를 반환합니다.

def get_category_encoding_layer(name, dataset, dtype, max_tokens=None):
  # Create a StringLookup layer which will turn strings into integer indices
  if dtype == 'string':
    index = preprocessing.StringLookup(max_tokens=max_tokens)
  else:
    index = preprocessing.IntegerLookup(max_tokens=max_tokens)

  # Prepare a Dataset that only yields our feature
  feature_ds = dataset.map(lambda x, y: x[name])

  # Learn the set of possible values and assign them a fixed integer index.
  index.adapt(feature_ds)

  # Create a Discretization for our integer indices.
  encoder = preprocessing.CategoryEncoding(num_tokens=index.vocabulary_size())

  # Apply one-hot encoding to our indices. The lambda function captures the
  # layer so we can use them, or include them in the functional model later.
  return lambda feature: encoder(index(feature))
type_col = train_features['Type']
layer = get_category_encoding_layer('Type', train_ds, 'string')
layer(type_col)
<tf.Tensor: shape=(3,), dtype=float32, numpy=array([0., 1., 1.], dtype=float32)>

종종 모델에 숫자를 직접 입력하지 않고 대신 해당 입력의 원-핫 인코딩을 사용합니다. 애완 동물의 나이를 나타내는 원시 데이터를 고려합니다.

type_col = train_features['Age']
category_encoding_layer = get_category_encoding_layer('Age', train_ds,
                                                      'int64', 5)
category_encoding_layer(type_col)
<tf.Tensor: shape=(5,), dtype=float32, numpy=array([1., 0., 1., 1., 0.], dtype=float32)>

사용할 열 선택하기

여러 유형의 전처리 레이어를 사용하는 방법을 살펴보았습니다. 이제 레이어를 모델을 훈련하는 데 사용합니다. Keras 함수형 API를 사용하여 모델을 빌드합니다. Keras 함수형 API는 tf.keras.Sequential API보다 더 유연한 모델을 생성하는 방법입니다.

이 튜토리얼의 목표는 전처리 레이어를 처리하는 데 필요한 전체 코드(예: 메커니즘)를 보여주는 것입니다. 모델을 훈련하기 위해 몇 개의 열이 임의로 선택되었습니다.

요점: 목표가 정확한 모델을 빌드하는 것이라면 자신의 더 큰 데이터세트를 시도하고 포함할 가장 의미 있는 특성과 표현 방법에 대해 신중하게 고려하세요.

이전에는 입력 파이프라인을 보여주기 위해 작은 배치 크기를 사용했습니다. 이제 더 큰 배치 크기로 새 입력 파이프라인을 생성해 보겠습니다.

batch_size = 256
train_ds = df_to_dataset(train, batch_size=batch_size)
val_ds = df_to_dataset(val, shuffle=False, batch_size=batch_size)
test_ds = df_to_dataset(test, shuffle=False, batch_size=batch_size)
all_inputs = []
encoded_features = []

# Numeric features.
for header in ['PhotoAmt', 'Fee']:
  numeric_col = tf.keras.Input(shape=(1,), name=header)
  normalization_layer = get_normalization_layer(header, train_ds)
  encoded_numeric_col = normalization_layer(numeric_col)
  all_inputs.append(numeric_col)
  encoded_features.append(encoded_numeric_col)
# Categorical features encoded as integers.
age_col = tf.keras.Input(shape=(1,), name='Age', dtype='int64')
encoding_layer = get_category_encoding_layer('Age', train_ds, dtype='int64',
                                             max_tokens=5)
encoded_age_col = encoding_layer(age_col)
all_inputs.append(age_col)
encoded_features.append(encoded_age_col)
# Categorical features encoded as string.
categorical_cols = ['Type', 'Color1', 'Color2', 'Gender', 'MaturitySize',
                    'FurLength', 'Vaccinated', 'Sterilized', 'Health', 'Breed1']
for header in categorical_cols:
  categorical_col = tf.keras.Input(shape=(1,), name=header, dtype='string')
  encoding_layer = get_category_encoding_layer(header, train_ds, dtype='string',
                                               max_tokens=5)
  encoded_categorical_col = encoding_layer(categorical_col)
  all_inputs.append(categorical_col)
  encoded_features.append(encoded_categorical_col)

모델 생성, 컴파일 및 훈련하기

이제 엔드 투 엔드 모델을 만들 수 있습니다.

all_features = tf.keras.layers.concatenate(encoded_features)
x = tf.keras.layers.Dense(32, activation="relu")(all_features)
x = tf.keras.layers.Dropout(0.5)(x)
output = tf.keras.layers.Dense(1)(x)
model = tf.keras.Model(all_inputs, output)
model.compile(optimizer='adam',
              loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
              metrics=["accuracy"])

연결 그래프를 시각화해 보겠습니다.

# rankdir='LR' is used to make the graph horizontal.
tf.keras.utils.plot_model(model, show_shapes=True, rankdir="LR")

png

모델 훈련하기

model.fit(train_ds, epochs=10, validation_data=val_ds)
Epoch 1/10
29/29 [==============================] - 2s 24ms/step - loss: 0.7024 - accuracy: 0.4139 - val_loss: 0.6032 - val_accuracy: 0.6067
Epoch 2/10
29/29 [==============================] - 0s 9ms/step - loss: 0.5997 - accuracy: 0.6312 - val_loss: 0.5725 - val_accuracy: 0.7102
Epoch 3/10
29/29 [==============================] - 0s 9ms/step - loss: 0.5770 - accuracy: 0.6672 - val_loss: 0.5574 - val_accuracy: 0.7226
Epoch 4/10
29/29 [==============================] - 0s 8ms/step - loss: 0.5634 - accuracy: 0.6854 - val_loss: 0.5477 - val_accuracy: 0.7335
Epoch 5/10
29/29 [==============================] - 0s 8ms/step - loss: 0.5504 - accuracy: 0.6958 - val_loss: 0.5409 - val_accuracy: 0.7416
Epoch 6/10
29/29 [==============================] - 0s 8ms/step - loss: 0.5469 - accuracy: 0.7023 - val_loss: 0.5366 - val_accuracy: 0.7400
Epoch 7/10
29/29 [==============================] - 0s 8ms/step - loss: 0.5382 - accuracy: 0.7089 - val_loss: 0.5326 - val_accuracy: 0.7373
Epoch 8/10
29/29 [==============================] - 0s 8ms/step - loss: 0.5367 - accuracy: 0.7176 - val_loss: 0.5291 - val_accuracy: 0.7400
Epoch 9/10
29/29 [==============================] - 0s 8ms/step - loss: 0.5357 - accuracy: 0.7176 - val_loss: 0.5267 - val_accuracy: 0.7373
Epoch 10/10
29/29 [==============================] - 0s 8ms/step - loss: 0.5306 - accuracy: 0.7158 - val_loss: 0.5246 - val_accuracy: 0.7394
<keras.callbacks.History at 0x7fb2557d2690>
loss, accuracy = model.evaluate(test_ds)
print("Accuracy", accuracy)
10/10 [==============================] - 0s 5ms/step - loss: 0.5150 - accuracy: 0.7487
Accuracy 0.7487002015113831

새로운 데이터로 추론하기

요점: 전처리 코드가 모델 자체에 포함되어 있기 때문에 여러분이 개발한 모델은 이제 CSV 파일에서 행을 직접 분류할 수 있습니다.

이제 Keras 모델을 저장하고 다시 로드할 수 있습니다. TensorFlow 모델에 대한 자세한 내용은 여기에서 튜토리어를 따르세요.

model.save('my_pet_classifier')
reloaded_model = tf.keras.models.load_model('my_pet_classifier')
2021-08-25 21:03:32.035869: 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.
WARNING:absl:Function `_wrapped_model` contains input name(s) PhotoAmt, Fee, Age, Type, Color1, Color2, Gender, MaturitySize, FurLength, Vaccinated, Sterilized, Health, Breed1 with unsupported characters which will be renamed to photoamt, fee, age, type, color1, color2, gender, maturitysize, furlength, vaccinated, sterilized, health, breed1 in the SavedModel.
INFO:tensorflow:Assets written to: my_pet_classifier/assets
INFO:tensorflow:Assets written to: my_pet_classifier/assets

새 샘플에 대한 예측값을 얻으려면, model.predict()를 호출하면 됩니다. 다음 두 가지만 수행해야 합니다.

  1. 배치 차원을 갖도록 스칼라를 목록으로 래핑합니다(모델은 단일 샘플이 아닌 데이터 배치만 처리함).
  2. 각 특성에 대해 convert_to_tensor를 호출합니다.
sample = {
    'Type': 'Cat',
    'Age': 3,
    'Breed1': 'Tabby',
    'Gender': 'Male',
    'Color1': 'Black',
    'Color2': 'White',
    'MaturitySize': 'Small',
    'FurLength': 'Short',
    'Vaccinated': 'No',
    'Sterilized': 'No',
    'Health': 'Healthy',
    'Fee': 100,
    'PhotoAmt': 2,
}

input_dict = {name: tf.convert_to_tensor([value]) for name, value in sample.items()}
predictions = reloaded_model.predict(input_dict)
prob = tf.nn.sigmoid(predictions[0])

print(
    "This particular pet had a %.1f percent probability "
    "of getting adopted." % (100 * prob)
)
This particular pet had a 75.1 percent probability of getting adopted.

요점: 일반적으로 더 크고 복잡한 데이터세트를 사용한 딥 러닝을 통해 최상의 결과를 얻을 수 있습니다. 작은 데이터세트로 작업할 때는 의사 결정 트리 또는 랜덤 포레스트를 강력한 기준으로 사용하는 것이 좋습니다. 이 튜토리얼의 목표는 구조적 데이터를 처리하는 메커니즘을 보여주기 위한 것이므로 향후 자체 데이터세트를 처리할 때 시작점으로 사용할 수 있는 코드를 살펴보았습니다.

다음 단계

구조적 데이터의 분류에 대해 자세히 알아보는 가장 좋은 방법은 직접 시도해 보는 것입니다. 처리할 데이터세트를 찾고 위와 유사한 코드를 사용하여 분류하도록 모델을 훈련할 수 있습니다. 정확성을 높이려면 모델에 포함할 특성과 표현 방법을 신중하게 고려하세요.