# Copyright 2018 The TensorFlow Hub Authors. All Rights Reserved.
#
# 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
#
#     http://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 2018 The TensorFlow Hub Authors.
Licensed under the Apache License, Version 2.0 (the "License");
End of explanation
"""


# Install TF-Hub.
!pip install seaborn

"""
Explanation: TF-Hub로 간단한 텍스트 분류자를 빌드하는 방법

참고: 이 튜토리얼에서는 지원 중단된 TensorFlow 1 기능을 사용합니다. 이 작업에 대한 최신 접근 방식은 TensorFlow 2 버전을 참조하세요.

<table class="tfo-notebook-buttons" align="left">
  <td><a target="_blank" href="https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ko/hub/tutorials/text_classification_with_tf_hub.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/hub/tutorials/text_classification_with_tf_hub.ipynb"><img src="https://www.tensorflow.org/images/GitHub-Mark-32px.png">GitHub 소스 보기</a></td>
  <td><a href="https://tfhub.dev/google/nnlm-en-dim128/1"><img src="https://www.tensorflow.org/images/hub_logo_32px.png">TF Hub 모델보기</a></td>
</table>

TF-Hub는 재사용 가능한 리소스, 특히 사전 훈련된 모듈 형태로 머신러닝 전문 지식을 공유하는 플랫폼입니다. 이 튜토리얼은 크게 두 부분으로 구성되어 있습니다.
소개: TF-Hub로 텍스트 분류자 훈련하기
TF-Hub 텍스트 임베딩 모듈을 사용하여 합리적인 기준 정확성으로 간단한 감상 분류자를 훈련합니다. 그런 다음, 예측을 분석하여 모델이 합리적인지 확인하고 정확성을 높이기 위한 개선점을 제안합니다.
고급: 전이 학습 분석
이 섹션에서는 다양한 TF-Hub 모듈을 사용하여 추정기(estimator)의 정확성에 미치는 영향을 비교하고 전이 학습의 장점과 함정을 보여줍니다.
선택적 전제 조건

Tensorflow 사전 제작된 추정기 프레임워크에 대한 기본적 이해
Pandas 라이브러리에 익숙

설정
End of explanation
"""


from absl import logging

import tensorflow as tf
import tensorflow_hub as hub
import matplotlib.pyplot as plt
import numpy as np
import os
import pandas as pd
import re
import seaborn as sns

"""
Explanation: Tensorflow 설치에 대한 자세한 정보는 https://www.tensorflow.org/install/에서 찾을 수 있습니다.
End of explanation
"""


# Load all files from a directory in a DataFrame.
def load_directory_data(directory):
  data = {}
  data["sentence"] = []
  data["sentiment"] = []
  for file_path in os.listdir(directory):
    with tf.io.gfile.GFile(os.path.join(directory, file_path), "r") as f:
      data["sentence"].append(f.read())
      data["sentiment"].append(re.match("\d+_(\d+)\.txt", file_path).group(1))
  return pd.DataFrame.from_dict(data)

# Merge positive and negative examples, add a polarity column and shuffle.
def load_dataset(directory):
  pos_df = load_directory_data(os.path.join(directory, "pos"))
  neg_df = load_directory_data(os.path.join(directory, "neg"))
  pos_df["polarity"] = 1
  neg_df["polarity"] = 0
  return pd.concat([pos_df, neg_df]).sample(frac=1).reset_index(drop=True)

# Download and process the dataset files.
def download_and_load_datasets(force_download=False):
  dataset = tf.keras.utils.get_file(
      fname="aclImdb.tar.gz", 
      origin="http://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz", 
      extract=True)
  
  train_df = load_dataset(os.path.join(os.path.dirname(dataset), 
                                       "aclImdb", "train"))
  test_df = load_dataset(os.path.join(os.path.dirname(dataset), 
                                      "aclImdb", "test"))
  
  return train_df, test_df

# Reduce logging output.
logging.set_verbosity(logging.ERROR)

train_df, test_df = download_and_load_datasets()
train_df.head()

"""
Explanation: 시작하기
데이터
Large Movie Review Dataset v1.0 과제(Mass 등, 2011)의 해결을 시도할 것입니다. 이 데이터세트는 긍정성을 기준으로 1부터 10까지 분류된 IMDB 영화 리뷰로 구성됩니다. 리뷰를 부정적 또는 긍정적으로 분류하는 것이 과제입니다.
End of explanation
"""


# Training input on the whole training set with no limit on training epochs.
train_input_fn = tf.compat.v1.estimator.inputs.pandas_input_fn(
    train_df, train_df["polarity"], num_epochs=None, shuffle=True)

# Prediction on the whole training set.
predict_train_input_fn = tf.compat.v1.estimator.inputs.pandas_input_fn(
    train_df, train_df["polarity"], shuffle=False)
# Prediction on the test set.
predict_test_input_fn = tf.compat.v1.estimator.inputs.pandas_input_fn(
    test_df, test_df["polarity"], shuffle=False)

"""
Explanation: 모델
입력 함수
Estimator 프레임워크는 Pandas 데이터프레임을 래핑하는 입력 함수를 제공합니다.
End of explanation
"""


embedded_text_feature_column = hub.text_embedding_column(
    key="sentence", 
    module_spec="https://tfhub.dev/google/nnlm-en-dim128/1")

"""
Explanation: 특성 열
TF-Hub는 주어진 텍스트 특성에 모듈을 적용하고 모듈의 출력을 추가적으로 전달하는 특성 열을 제공합니다. 이 튜토리얼에서는 nnlm-en-dim128 모듈을 사용합니다. 이 튜토리얼의 목적에 비추어 가장 중요한 사실은 다음과 같습니다.

모듈은 1-D 문자열 텐서의 문장 배치를 입력으로 사용합니다.
모듈은 문장의 전처리(예: 구두점 제거 및 공백 분할)를 담당합니다.
모듈은 모든 입력에서 동작합니다(예: nnlm-en-dim128은 어휘에 없는 단어를 ~20.000개 버킷으로 해시 처리함).
End of explanation
"""


estimator = tf.estimator.DNNClassifier(
    hidden_units=[500, 100],
    feature_columns=[embedded_text_feature_column],
    n_classes=2,
    optimizer=tf.keras.optimizers.Adagrad(lr=0.003))

"""
Explanation: 추정기
분류를 위해 DNN 분류자를 사용할 수 있습니다(튜토리얼 마지막에 있는 레이블 함수의 다른 모델링에 대한 추가 설명 참고).
End of explanation
"""


# Training for 5,000 steps means 640,000 training examples with the default
# batch size. This is roughly equivalent to 25 epochs since the training dataset
# contains 25,000 examples.
estimator.train(input_fn=train_input_fn, steps=5000);

"""
Explanation: 훈련
적절한 스텝 수만큼 추정기를 훈련합니다.
End of explanation
"""


train_eval_result = estimator.evaluate(input_fn=predict_train_input_fn)
test_eval_result = estimator.evaluate(input_fn=predict_test_input_fn)

print("Training set accuracy: {accuracy}".format(**train_eval_result))
print("Test set accuracy: {accuracy}".format(**test_eval_result))

"""
Explanation: 예측
훈련 및 테스트 세트 모두에 대한 예측을 실행합니다.
End of explanation
"""


def get_predictions(estimator, input_fn):
  return [x["class_ids"][0] for x in estimator.predict(input_fn=input_fn)]

LABELS = [
    "negative", "positive"
]

# Create a confusion matrix on training data.
cm = tf.math.confusion_matrix(train_df["polarity"], 
                              get_predictions(estimator, predict_train_input_fn))

# Normalize the confusion matrix so that each row sums to 1.
cm = tf.cast(cm, dtype=tf.float32)
cm = cm / tf.math.reduce_sum(cm, axis=1)[:, np.newaxis]

sns.heatmap(cm, annot=True, xticklabels=LABELS, yticklabels=LABELS);
plt.xlabel("Predicted");
plt.ylabel("True");

"""
Explanation: 혼동 행렬
오분류 분포를 이해하기 위해 혼동 행렬을 시각적으로 확인할 수 있습니다.
End of explanation
"""


def train_and_evaluate_with_module(hub_module, train_module=False):
  embedded_text_feature_column = hub.text_embedding_column(
      key="sentence", module_spec=hub_module, trainable=train_module)

  estimator = tf.estimator.DNNClassifier(
      hidden_units=[500, 100],
      feature_columns=[embedded_text_feature_column],
      n_classes=2,
      optimizer=tf.keras.optimizers.Adagrad(learning_rate=0.003))

  estimator.train(input_fn=train_input_fn, steps=1000)

  train_eval_result = estimator.evaluate(input_fn=predict_train_input_fn)
  test_eval_result = estimator.evaluate(input_fn=predict_test_input_fn)

  training_set_accuracy = train_eval_result["accuracy"]
  test_set_accuracy = test_eval_result["accuracy"]

  return {
      "Training accuracy": training_set_accuracy,
      "Test accuracy": test_set_accuracy
  }


results = {}
results["nnlm-en-dim128"] = train_and_evaluate_with_module(
    "https://tfhub.dev/google/nnlm-en-dim128/1")
results["nnlm-en-dim128-with-module-training"] = train_and_evaluate_with_module(
    "https://tfhub.dev/google/nnlm-en-dim128/1", True)
results["random-nnlm-en-dim128"] = train_and_evaluate_with_module(
    "https://tfhub.dev/google/random-nnlm-en-dim128/1")
results["random-nnlm-en-dim128-with-module-training"] = train_and_evaluate_with_module(
    "https://tfhub.dev/google/random-nnlm-en-dim128/1", True)

"""
Explanation: 추가 개선

감상 회귀: 분류자를 사용하여 각 예를 극성 클래스에 할당했습니다. 그러나 우리에게는 이용할 수 있는 또 다른 범주 특성인 감상이 있습니다. 여기서 클래스는 실제로 척도를 나타내며 기본 값(긍정적/부정적)은 연속 범위로 잘 매핑될 수 있습니다. 분류(DNN Classifier) 대신 회귀(DNN Regressor)를 계산하여 이 속성을 사용할 수 있습니다.
더 큰 모듈: 이 튜토리얼에서는 메모리 사용을 제한하기 위해 작은 모듈을 사용했습니다. 더 큰 어휘와 더 큰 임베딩 공간을 가진 모듈의 경우에는 정확성과 관련해 추가적으로 고려할 사항이 있을 수 있습니다.
매개변수 조정: 특히 다른 모듈을 사용하는 경우에 학습률 또는 스텝 수와 같은 메타 매개변수를 조정하여 정확성을 향상할 수 있습니다. 테스트 세트로 잘 일반화하지 않고 훈련 데이터를 예측하는 방법을 배우는 모델을 설정하는 것이 매우 쉽기 때문에 검증 세트는 합리적인 결과를 얻으려는 경우에 매우 중요합니다.
더 복잡한 모델: 여기서는 각 개별 단어를 포함한 다음 평균과 결합하여 문장 임베딩을 계산하는 모듈을 사용했습니다. 문장의 본질을 더 잘 포착하기 위해 순차 모듈(예: Universal Sentence Encoder 모듈)을 사용할 수도 있습니다. 또는 두 개 이상의 TF-Hub 모듈을 조화롭게 사용할 수도 있습니다.
정규화: 과대적합을 방지하기 위해 일종의 정규화를 수행하는 옵티마이저(예: Proximal Adagrad Optimizer)를 사용할 수 있습니다.

고급: 전이 학습 분석
전이 학습을 사용하면 훈련 리소스를 절약하고 작은 데이터세트에서 훈련하는 경우에도 우수한 모델 일반화를 달성할 수 있습니다. 여기서는 두 가지 다른 TF-Hub 모듈로 훈련하는 예를 설명합니다.

nnlm-en-dim128 - 사전 훈련된 텍스트 임베딩 모듈
random-nnlm-en-dim128 - nnlm-KO-dim128과 같은 어휘와 네트워크를 가진 텍스트 임베딩 모듈이지만 가중치는 무작위로 초기화되고 실제 데이터에서 훈련되지 않습니다.

그리고 두 가지 모드로 훈련합니다.

분류자만 훈련(예: 모듈 동결)
모듈과 함께 분류자 훈련

다양한 모듈을 사용할 때 정확성에 어떤 영향을 미치는지 알아보기 위해 몇 가지 훈련과 평가를 실행해 보겠습니다.
End of explanation
"""


pd.DataFrame.from_dict(results, orient="index")

"""
Explanation: 결과를 살펴보겠습니다.
End of explanation
"""


estimator.evaluate(input_fn=predict_test_input_fn)["accuracy_baseline"]

"""
Explanation: 이미 일부 패턴을 볼 수 있지만 먼저 테스트 세트의 기준 정확성(가장 대표적인 클래스의 레이블만 출력하여 얻을 수 있는 하한)을 수립해야 합니다.
End of explanation
"""


