#@title Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Explanation: Copyright 2019 The TensorFlow Authors.
End of explanation
"""


!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__

"""
Explanation: Keras 전처리 레이어를 사용한 구조적 데이터 분류
<table class="tfo-notebook-buttons" align="left">
  <td><a target="_blank" href="https://www.tensorflow.org/tutorials/structured_data/preprocessing_layers"><img src="https://www.tensorflow.org/images/tf_logo_32px.png">TensorFlow.org에서 보기</a></td>
  <td><a target="_blank" href="https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ko/tutorials/structured_data/preprocessing_layers.ipynb"><img src="https://www.tensorflow.org/images/colab_logo_32px.png">Google Colab에서 실행</a></td>
  <td><a target="_blank" href="https://github.com/tensorflow/docs-l10n/blob/master/site/ko/tutorials/structured_data/preprocessing_layers.ipynb"><img src="https://www.tensorflow.org/images/GitHub-Mark-32px.png">GitHub에서 소스 보기</a></td>
  <td><a href="https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/ko/tutorials/structured_data/preprocessing_layers.ipynb"><img src="https://www.tensorflow.org/images/download_logo_32px.png">노트북 다운로드</a></td>
</table>

이 튜토리얼에서는 구조적 데이터(예: 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 및 기타 라이브러리 가져오기
End of explanation
"""


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)

dataframe.head()

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


# 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'])

"""
Explanation: 목표 변수 만들기
Kaggle 대회에서의 작업은 애완 동물이 입양되는 속도를 예측하는 것입니다(예: 첫 주, 첫 달, 첫 3개월 등). 튜토리얼을 위해 단순화해 봅시다. 여기에서는 입양 속도를 이진 분류 문제로 변환하고 단순히 애완 동물이 입양되었는지 여부를 예측합니다.
레이블 열을 수정한 후, 0은 애완 동물이 입양되지 않았음을 나타내고 1은 입양되었음을 나타냅니다.
End of explanation
"""


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')

"""
Explanation: 데이터 프레임을 훈련, 검증 및 테스트로 분할하기
다운로드한 데이터세트는 단일 CSV 파일입니다. 이를 훈련, 검증 및 테스트 세트로 분할합니다.
End of explanation
"""


# 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

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


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 )

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


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)

"""
Explanation: 데이터세트가 데이터 프레임의 행에서 열 값에 매핑되는 열 이름의 사전(데이터 프레임에서)을 반환하는 것을 볼 수 있습니다.
전처리 레이어의 사용을 시연합니다.
Keras 전처리 레이어 API를 사용하면 Keras 네이티브 입력 처리 파이프라인을 빌드할 수 있습니다. 3개의 전처리 레이어를 사용하여 특성 전처리 코드를 보여줍니다.

Normalization - 데이터의 특성별 정규화입니다.
CategoryEncoding 카테고리 인코딩 레이어입니다.
StringLookup - 어휘의 문자열을 정수 인덱스로 매핑합니다.
IntegerLookup - 어휘의 정수를 정수 인덱스로 매핑합니다.

여기에서 사용 가능한 전처리 레이어의 목록을 찾을 수 있습니다.
숫자 열
각 숫자 특성에 대해 Normalization() 레이어를 사용하여 각 특성의 평균이 0이고 표준 편차가 1인지 확인합니다.
get_normalization_layer 함수는 특성별 정규화를 숫자 특성에 적용하는 레이어를 반환합니다.
End of explanation
"""


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)

"""
Explanation: 참고: 숫자 특성(수백 개 이상)이 많은 경우, 먼저 숫자 특성을 연결하고 단일 normalization 레이어를 사용하는 것이 더 효율적입니다.
범주 열
이 데이터세트에서 Type은 문자열(예: 'Dog'또는 'Cat')으로 표시됩니다. 모델에 직접 문자열을 공급할 수 없습니다. 전처리 레이어는 문자열을 원-핫 벡터로 처리합니다.
get_category_encoding_layer 함수는 어휘의 값을 정수 인덱스로 매핑하고 특성을 원-핫 인코딩하는 레이어를 반환합니다.
End of explanation
"""


type_col = train_features['Age']
category_encoding_layer = get_category_encoding_layer('Age', train_ds,
                                                      'int64', 5)
category_encoding_layer(type_col)

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


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)


"""
Explanation: 사용할 열 선택하기
여러 유형의 전처리 레이어를 사용하는 방법을 살펴보았습니다. 이제 레이어를 모델을 훈련하는 데 사용합니다. Keras 함수형 API를 사용하여 모델을 빌드합니다. Keras 함수형 API는 tf.keras.Sequential API보다 더 유연한 모델을 생성하는 방법입니다.
이 튜토리얼의 목표는 전처리 레이어를 처리하는 데 필요한 전체 코드(예: 메커니즘)를 보여주는 것입니다. 모델을 훈련하기 위해 몇 개의 열이 임의로 선택되었습니다.
요점: 목표가 정확한 모델을 빌드하는 것이라면 자신의 더 큰 데이터세트를 시도하고 포함할 가장 의미 있는 특성과 표현 방법에 대해 신중하게 고려하세요.
이전에는 입력 파이프라인을 보여주기 위해 작은 배치 크기를 사용했습니다. 이제 더 큰 배치 크기로 새 입력 파이프라인을 생성해 보겠습니다.
End of explanation
"""


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"])

"""
Explanation: 모델 생성, 컴파일 및 훈련하기
이제 엔드 투 엔드 모델을 만들 수 있습니다.
End of explanation
"""


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


"""
Explanation: 연결 그래프를 시각화해 보겠습니다.
End of explanation
"""


model.fit(train_ds, epochs=10, validation_data=val_ds)

loss, accuracy = model.evaluate(test_ds)
print("Accuracy", accuracy)

"""
Explanation: 모델 훈련하기
End of explanation
"""


model.save('my_pet_classifier')
reloaded_model = tf.keras.models.load_model('my_pet_classifier')

"""
Explanation: 새로운 데이터로 추론하기
요점: 전처리 코드가 모델 자체에 포함되어 있기 때문에 여러분이 개발한 모델은 이제 CSV 파일에서 행을 직접 분류할 수 있습니다.
이제 Keras 모델을 저장하고 다시 로드할 수 있습니다. TensorFlow 모델에 대한 자세한 내용은 여기에서 튜토리어를 따르세요.
End of explanation
"""


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)
)

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

배치 차원을 갖도록 스칼라를 목록으로 래핑합니다(모델은 단일 샘플이 아닌 데이터 배치만 처리함).
각 특성에 대해 convert_to_tensor를 호출합니다.
End of explanation
"""


