세상의 변화에 대해 관심이 많은 이들의 Tech Blog search

Imbalanced Learning

|

1. Imbalanced Learning (불균형 학습) 개요

비정상 거래 탐지와 같은 케이스의 경우, 정상적인 거래 보다는 정상 범위에서 벗어난 것으로 판단되는 거래 기록의 비중이 현저하게 작을 것이다. 그런데 보통의 알고리즘으로 이러한 비정상 거래를 찾아내기 에는 이러한 데이터의 불균형이 중요한 이슈로 작용하는데, 본 글에서는 이러한 불균형 학습과 관련된 논의를 해보고자 한다.

알고리즘 자체로 Class 불균형을 해소하는 방법을 제외하면, Over-Sampling과 Under-Sampling 방법이 가장 대표적인 방법이라고 할 수 있다.

1.1. Over-Sampling

Over-Sampling은 부족한 데이터를 추가하는 방식으로 진행되며, 크게 3가지로 구분할 수 있다.

첫 번째 방법은 무작위 추출인데, 단순하게 랜덤하게 부족한 Class의 데이터를 복제하여 데이터셋에 추가하는 것이다.

두 번째 방법은 위와 달리 기존 데이터를 단순히 복사하는 것에 그치지 않고, 어떠한 방법론에 의해 합성된 데이터를 생성하는 것이다. 이후에 설명할 SMOTE 기법이 본 방법의 대표적인 예에 해당한다.

세 번째 방법은 어떤 특별한 기준에 의해 복제할 데이터를 정하고 이를 실행하는 것이다.

1.2. Under-Sampling

Over-Sampling과 반대로 Under-Sampling은 정상 데이터의 수를 줄여 데이터셋의 균형을 맞추는 것인데, 주의해서 사용하지 않으면 매우 중요한 정보를 잃을 수도 있기 때문에 확실한 근거를 바탕으로 사용해야 하는 방법이다.

Under-Sampling의 대표적인 예로는 RUS가 있고, 이는 단순히 Random Under Sampling을 뜻한다.


2. SMOTE 기법

SMOTE는 Synthetic Minority Oversampling TEchnique의 약자로, 2002년에 처음 등장하여 현재(2019.10)까지 8천 회가 넘는 인용 수를 보여주고 있는 Over-Sampling의 대표적인 알고리즘이다.

알고리즘의 원리 자체는 간단하다. Boostrap이나 KNN 모델 기법을 기반으로 하는데, 그 원리는 다음과 같다.

  • 소수(위 예시에선 비정상) 데이터 중 1개의 Sample을 선택한다. 이 Sample을 기준 Sample이라 명명한다.
  • 기준 Sample과 거리(유클리드 거리)가 가까운 k개의 Sample(KNN)을 찾는다. 이 k개의 Sample 중 랜덤하게 1개의 Sample을 선택한다. 이 Sample을 KNN Sample이라 명명한다.
  • 새로운 Synthetic Sample은 아래와 같이 계산한다. \(X_{new} = X_i + (X_k - X_i) * \delta\)

    $X_{new}$: Synthetic Sample
    $X_i$: 기준 Sample
    $X_k$: KNN Sample
    $\delta$: 0 ~ 1 사이에서 생성된 난수

본 과정을 일정 수 만큼 진행하면 아래 그림과 같이 새로운 합성 데이터가 생성됨을 알 수 있다.

간단한 예시를 보면,

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.datasets import make_classification
from imblearn.over_sampling import SMOTE
from sklearn.preprocessing import MinMaxScaler

x, y = make_classification(n_features=2, n_informative=2, n_samples=20, weights= [0.8, 0.2],
                           n_redundant=0, n_clusters_per_class=1, random_state=0)
scaler = MinMaxScaler(feature_range=(0, 1))
x = scaler.fit_transform(x)

# SMOTE 이전
df1 = pd.DataFrame(np.concatenate([x, y.reshape(-1, 1)], axis=1),
                  columns=['col1', 'col2', 'result'])
sns.relplot(x='col1', y='col2', hue='result', data=df1)
plt.show()

# SMOTE 이후
sm = SMOTE(ratio='auto', kind='regular', k_neighbors=3)
X, Y = sm.fit_sample(x, list(y))

df2 = pd.DataFrame(np.concatenate([X, Y.reshape(-1, 1)], axis=1),
                  columns=['col1', 'col2', 'result'])

sns.relplot(x='col1', y='col2', hue='result', data=df2)
plt.show()

다음 그림들에서 위는 SMOTE 이전의 데이터를, 아래는 SMOTE 이후의 데이터 분포를 보여준다.


3. 추가할 것

MSMOTE, Borderline SMOTE, Adasyn


Reference

참고 블로그
파이썬 머신러닝 완벽 가이드, 권철민, 위키북스

Comment  Read more

Contextual Bandit and Tree Heuristic

|

1. Contextual Bandit의 개념

Contextual Bandit 문제를 알기 위해선 Multi-Armed Bandit 문제의 개념에 대해 숙지하고 있어야 한다.
위 개념에 대해 알기를 원한다면 여기를 참고하기 바란다.

Multi-Armed Bandit 문제에서 Context 개념이 추가된 Contextual Bandit 문제는 대표적으로 추천 시스템에서 활용될 수 있다. 단 전통적인 추천 시스템을 구축할 때는 Ground Truth y 값, 즉 실제로 고객이 어떠한 상품을 좋아하는지에 대한 해답을 안고 시작하지만, Contextual Bandit과 관련된 상황에서는 그러한 이점이 주어지지 않는다.

그림을 통해 파악해보자.

첫 번째 그림은 전통적인 추천시스템에 관한 것이고, 두 번째 그림은 Contextual Bandit 문제와 관련된 것이다.

온라인 상황에서 우리가 고객에게 어떠한 상품을 제시하였을 때, 고객이 그 상품을 원하지 않는다면 우리는 새로운 시도를 통해 고객이 어떠한 상품을 좋아할지 파악하도록 노력해야 한다. 이것이 바로 Exploration이다.

만약 고객이 그 상품에 호의적인 반응을 보였다면, 이 또한 중요한 데이터로 적재되어 이후에 동일 혹은 유사한 고객에게 상품을 추천해 주는 데에 있어 이용될 것이다. 이 것이 Exploitation이다.

위 그림에 나와 있듯이, Contextual Bandit 문제 해결의 핵심은, Context(고객의 정보)를 활용하여 Exploitation과 Exploration의 균형을 찾아 어떤 Action을 취할 것인가에 대한 효과적인 학습을 진행하는 것이다.


2. Lin UCB

Lin UCB는 A contextual-bandit approach to personalized news article recommendation논문에 처음 소개된 알고리즘으로, Thompson Sampling과 더불어 Contextual Bandit 문제를 푸는 가장 대표적이고 기본적인 알고리즘으로 소개되어 있다.

이 알고리즘의 기본 개념은 아래와 같다.

Context Vector를 어떻게 구성할 것인가에 따라 Linear Disjoint Model과 Linear Hybrid Model로 구분된다. Hyperparameter인 Alpha가 커질 수록 Exploration에 더욱 가중치를 두게 되며, 결과는 이 Alpha에 다소 영향을 받는 편이다.

본 알고리즘은 이후 Tree Heuristic과의 비교를 위해 테스트 용으로 사용될 예정이다.


3. Tree Heuristic

3.1 Tree Boost

Tree Heuristic에 접근하기 위해서는 먼저 그 전신이라고 할 수 있는 Tree Boost 알고리즘에 대해 알아야 한다. 본 알고리즘은 A practical method for solving contextual bandit problems using decision trees 논문에서 소개되었다.

Tree Boost는 Thompson Sampling의 개념을 차용하여 설계된 알고리즘이다. 위의 Lin UCB가 Context와 Reward 사이의 관계를 선형적으로 정의하였다면, 본 알고리즘은 Tree 계열의 모델로써 이 관계를 정의한다.

Tree Boost의 작동 원리를 알아 보자. 한 고객의 정보가 입수되었다. 이 정보는 1개의 Context Vector라고 할 수 있다. 우리가 취할 수 있는 Action이 총 k개 있다고 가정하면, 각각의 Action과 연결된 Tree 모델에 방금 입수한 Context Vector를 투입하고 Reward가 1이 될 확률(Score)값을 얻는다. 가장 높은 값을 갖는 Action을 선택하여 고객에게 제시한다.

제시가 끝난 후에 고객의 반응(Reward가 1인지, 0인지)이 확인되었다면, 이를 제시하였던 Action의 Sub-data에 적재한다. 즉, 각 데이터(Design Matrix)는 제시한 Action의 Sub-data에 소속되는 것이다. 이 Sub-data들을 모두 모으면 전체 데이터가 구성된다.

Sub-data 내에서 부트스트랩을 여러 번 진행하고 그 중 하나를 선택하여 Tree 모델에 적합시키는데, 이 과정이 Exploration 과정에 해당하며, 선택된 데이터셋과 Tree 모델은 Thompson Sampling에서 사용되는 샘플 1개에 해당한다.

이후에 설명하겠지만, Tree Boost의 성능은 뛰어난 편이다. 그러나 이 모든 과정을 거치기에는 굉장히 많은 시간이 소요되며, 신속성이 중요한 평가 포인트라고 할 수 있는 Contextual Bandit 알고리즘들 사이에서 현실적으로 우위를 보이기는 어려운 것이 사실이다. 따라서 아래에 있는 Tree Heuristic이라는 알고리즘이 제시되었다고 볼 수 있다.

3.2 Tree Heuristic

Tree Boost와의 가장 큰 차이점은 바로, 한 Trial에 한 번만 적합을 진행하여 속도를 향상시켰다는 점이다. Tree Boost의 경우 각 Action 마다 부트스트랩 과정을 여러 번 시키고, 또 선택된 데이터에 Action 수 만큼 모델을 적합해야 했기 때문에 굉장히 오랜 시간이 소요되었는데 Tree Heuristic은 그러한 과정을 겪을 필요가 없는 것이다.

알고리즘의 실질적인 작동원리는 아래 그림과 코드를 보면 상세히 설명되어 있다.

"""
Tree Heuristic Implementation with Striatum Module
본 알고리즘은 Striatum Module의 가장 기본적인 class들을 활용하였음
"""

import time
import warnings
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

from striatum.bandit.bandit import BaseBandit
from striatum.storage import history, action, model

from sklearn.externals.joblib import Parallel, delayed
from sklearn.multiclass import _fit_binary, OneVsRestClassifier
from sklearn.preprocessing import LabelBinarizer
from sklearn.tree import DecisionTreeClassifier


# 터미널을 클린하게 해야 함
warnings.simplefilter(action='ignore', category=FutureWarning)
warnings.simplefilter(action='ignore', category=UserWarning)


class CustomOneVsRestClassifier(OneVsRestClassifier):
    """
    현재 scikit-learn의 OneVsRestClassifier class 의 경우,
    내부에 있는 Classifier 객체들이 독립적이지 않아 개별 접근이 불가능함
    따라서 개별 접근이 가능하게 (각 Action 별로 다른 모델이 필요하므로)
    본 클래스를 수정해주어야 함

    참조: https://www.oipapio.com/question-3339267
    """

    def __init__(self, estimators, n_jobs=1):
        super(CustomOneVsRestClassifier, self).__init__(estimators, n_jobs)
        self.estimators = estimators
        self.n_jobs = n_jobs

    def fit(self, X, y):
        self.label_binarizer_ = LabelBinarizer(sparse_output=True)
        Y = self.label_binarizer_.fit_transform(y)
        Y = Y.tocsc()
        self.classes_ = self.label_binarizer_.classes_
        columns = (col.toarray().ravel() for col in Y.T)

        # This is where we change the training method
        self.estimators_ = Parallel(n_jobs=self.n_jobs)(delayed(_fit_binary)(
            estimator, X, column, classes=[
                "not %s" % self.label_binarizer_.classes_[i],
                self.label_binarizer_.classes_[i]])
            for i, (column, estimator) in enumerate(zip(columns, self.estimators)))
        return self


class RecommendationCls(object):
    """
    우리가 추천한 Action 의 정보들을 저장할 클래스
    """
    def __init__(self, action, score, reward=None):
        self.action = action
        self.score = score
        self.reward = reward


class TreeHeuristic(BaseBandit):
    """
    Tree Heuristic Algorithm:
    Context 와 Reward 의 관계를 Tree Model 로서 정의내리고,
    Decision Tree 의 학습결과에 기반하여 Beta 분포 Sampling 을 진행하여 Action 을 선택하는 알고리즘임
    """

    def __init__(self,
                 history_storage,
                 model_storage,
                 action_storage,
                 n_actions,
                 ):
        super(TreeHeuristic, self).__init__(history_storage, model_storage, action_storage,
                                            recommendation_cls=RecommendationCls)

        # 1) history_storage 에는 매 trial 에서 진행되었던 기본적인 record 가 담겨 있음
        # 2) model_storage 는 Lin UCB 에서는 model parameter 가 저장되는 공간인데, 본 알고리즘에선 사실 쓰임새는 없음
        # 3) action_storage 에는 선택된 Action 의 ID 와 Score 가 저장됨

        # oracle: Action 수 만큼의 Decision Tree 를 담고 있음
        # n_actions: Action 수
        # n_features: Feature 수
        # D: Action 별로 적재한 데이터, 딕셔너리구조이며 value 자리에는 각 Action 에 맞는 np.array 가 적재됨
        # first_context = 첫 손님, 처음 Input 으로 주어지는 Context
        #               -> 얘를 저장하여 가짜 데이터를 만듦, build 메서드를 참고

        self.oracles = CustomOneVsRestClassifier([DecisionTreeClassifier() for i in range(n_actions)])
        self.n_actions = n_actions
        self.n_features = None
        self.D = None
        self.first_context = None

    def build(self, first_context, actions):
        """
        1) first_context 저장 및 n_features 저장
        2) Action objects 를 self._action_storage 에 저장함
        3) 가짜 데이터를 집어 넣어 D 를 만듦
        4) 초기 fitting 을 진행 함

        :param first_context: np.array (n_features, ) 첫 번째 context
        :param actions: list of action objects(Striatum 모듈 기본 class), action 의 종류를 담고 있음
        """
        self.first_context = first_context
        self.n_features = first_context.shape[0]

        self._action_storage.add(actions)

        # Add Fabricated Data
        # 적합을 진행하려고 하는데 만약 Label 이 오직 0만 존재한다거나 하는 상황이 오면
        # Classifier 를 그 데이터에 적합시키는 것은 불가능함
        # 가짜 데이터를 D 에 미리 적재함으로써 이 문제를 해결함 (논문 참조)
        # 데이터의 개수가 늘어날 수록 이 가짜 데이터의 영향력은 약화됨
        # D 에서 각 Action 에 맞는 np.array 의 마지막 열은 실제 Reward 값이며, 그 외의 열에는 Feature 값이 들어감
        x1, x2 = np.append(first_context, 0), np.append(first_context, 1)
        X = np.array([x1, x2])

        D = {action_id: X for action_id in self._action_storage.iterids()}

        oracles = self.oracles

        # 위에서 만든 가짜 데이터를 적합함
        for index, action_id in enumerate(list(self._action_storage.iterids())):
            oracle = oracles.estimators[index]
            oracle.fit(D[action_id][:, :-1], D[action_id][:, -1])

        self.D = D
        self.oracles = oracles

    def sample_from_beta(self, context):
        """
        :param context: np.array (n_features, ), 고객 1명의 context 임
        :return: history_id -- 저장 기록 index
                 recommendations -- 수행한 action 과 그 action 의 score 를 저장하는 class,
                                    위에서 만든 RecommendationCls class 의 Instance 임

        아래 loop 내의 코드는 Decision Tree 내부에 접근하는 과정을 다루고 있음
        접근 방법 참고:
        https://lovit.github.io/machine%20learning/2018/04/30/get_rules_from_trained_decision_tree/
        """
        oracles = self.oracles
        samples = []

        # Prediction 을 위해 reshaping 을 해줌
        context_vector = context.reshape(1, -1)

        for i, action_id in enumerate(list(self._action_storage.iterids())):
            oracle = oracles.estimators[i]

            # 각 DT 모델에 context 를 투입하여 당도한 leaf node 의 index 를 얻음
            leaf_index = oracle.apply(context_vector)[0]

            # 해당 leaf node 의 n0, n1 값을 얻음
            # n0: number of failure in the leaf node selected
            # n1: number of success in the leaf node selected
            n0 = oracle.tree_.value[leaf_index][0][0]
            n1 = oracle.tree_.value[leaf_index][0][1]

            # 이를 베타분포에 반영해주고, 여기서 sampling 을 진행함

            sample = np.random.beta(a=1 + n1, b=1 + n0, size=1)
            samples.append(sample)

        # Sample 값 중 가장 높은 값을 갖는 Action 을 선택함
        target = np.argmax(samples)
        recommendation_id = list(self._action_storage.iterids())[target]

        recommendations = self._recommendation_cls(
            action=self._action_storage.get(recommendation_id),
            score=np.max(samples)
        )

        history_id = self._history_storage.add_history(context, recommendations)

        return history_id, recommendations

    def update_D(self, action_id, context, reward):
        """
        추천한 Action 의 결과로 받은 Reward 와 Context 를 결합하여 데이터 딕셔너리 D 업데이트를 진행 함

        :param action_id: integer, D 에서 어떤 데이터를 업데이트할지 결정함
        :param context: np.array (n_samples, ), 고객 1명의 context 임
        :param reward: 실제 Reward -- 0 또는 1
        """
        D = self.D

        # new_data: context 와 reward 를 붙인 np.array
        new_data = np.append(context, reward).reshape(1, -1)

        # 해당 Action 의 데이터에 적재함
        D[action_id] = np.append(D[action_id], new_data, axis=0)

        self.D = D

    def update_tree(self, action_id):
        """
        해당 Action 에 소속된 Decision Tree 를 적합하여 업그레이드 함

        :param action_id: integer
        """
        D = self.D
        oracles = self.oracles

        action_index = list(self._action_storage.iterids()).index(action_id)
        oracle = oracles.estimators[action_index]
        oracle.fit(D[action_id][:, :-1], D[action_id][:, -1])

        self.oracles = oracles

    def reward(self, history_id, rewards):
        """
        self._history_storage.unrewarded_histories 에 있는,
        아직 Reward 를 받지 못한 기록들을 제거함

        :param history_id: Integer, sample_from_beta 메서드의 output
        :param rewards: Dictionary, {action_id : 0 or 1}
        """
        self._history_storage.add_reward(history_id, rewards)

    def add_action(self, actions):
        """
        새로운 Action 이 추가되었을 때,
        1) action_storage 를 업데이트하고
        2) D 에 새로운 가짜 데이터를 적재하며
        3) oracle 에 새로 추가된 Action 의 개수만큼 Decision Tree 를 추가하여
        4) 앞서 만든 가짜 데이터에 적합함

        :param actions: set of actions
        """

        oracles = self.oracles
        x = self.first_context
        D = self.D

        self._action_storage.add(actions)

        num_new_actions = len(actions)

        # 새롭게 정의된 Decision Tree 에 적합을 시작할 수 있게 기본 (가짜) 데이터셋을 넣어줌
        # 이어서 새롭게 Decision Tree 들을 추가된 Action 의 개수 만큼 만들어준 이후
        # 각 Action 에 매칭되는 Decision Tree 에 적합함
        x1, x2 = np.append(x, 0), np.append(x, 1)
        X = np.array([x1, x2])

        new_trees = [DecisionTreeClassifier() for j in range(num_new_actions)]

        for new_action_obj, new_tree in zip(actions, new_trees):
            # 여기서 new_action_obj 는 Striatum 패키지의 기본 class 로 짜여 있어
            # 그 class 의 attribute 인 id 를 불러와야 integer 인 action_id 를 쓸 수 있음
            new_action_id = new_action_obj.id
            D[new_action_id] = X
            new_tree.fit(D[new_action_id][:, :-1], D[new_action_id][:, -1])

            # 새로 적합한 Decision Tree 를 추가해 줌
            oracles.estimators.append(new_tree)

        self.oracles = oracles
        self.D = D

    def remove_action(self, action_id):
        """
        이제는 필요 없어진 Action을 제거한다.

        :param action_id: integer
        """
        D = self.D
        self._action_storage.remove(action_id)

        del D[action_id]

        self.D = D


# Preparation
def make_arm(arm_ids):
    """
    선택할 수 있는 Action 의 리스트를 받아
    Striatum 모듈의 Action Object 로 변환함

    이 작업을 거쳐야 위 Action Object 들을 Tree Heuristic 과 같은 Contextual Bandit class 의
    내부 Attribute 인 _action_storage 에 저장할 수 있음

    :param arm_ids: list,
    :return:
    """
    arms = []
    for arm_id in arm_ids:
        arm = action.Action(arm_id)
        arms.append(arm)
    return arms


# Training: Movie Lens Data
def train_movielens(max_iter=163683, batch_size=100):
    # 데이터 전처리 방법에 대해 알고자 한다면...
    # 참고: https://striatum.readthedocs.io/en/latest/auto_examples/index.html#general-examples

    streaming_batch = pd.read_csv('streaming_batch.csv', sep='\t', names=['user_id'], engine='c')
    user_feature = pd.read_csv('user_feature.csv', sep='\t', header=0, index_col=0, engine='c')
    arm_ids = list(pd.read_csv('actions.csv', sep='\t', header=0, engine='c')['movie_id'])
    reward_list = pd.read_csv('reward_list.csv', sep='\t', header=0, engine='c')

    streaming_batch = streaming_batch.iloc[0:max_iter]

    # 아래 n_actions 인자에서 처음 시점에서의 Action 의 개수를 정의 함
    th = TreeHeuristic(history.MemoryHistoryStorage(), model.MemoryModelStorage(),
                       action.MemoryActionStorage(), n_actions=50)
    actions = make_arm(arm_ids=arm_ids)

    reward_sum = 0
    y = []

    print("Starting Now...")
    start = time.time()

    for i in range(max_iter):
        context = np.array(user_feature[user_feature.index == streaming_batch.iloc[i, 0]])[0]

        if i == 0:
            th.build(first_context=context, actions=actions)

        history_id, recommendations = th.sample_from_beta(context=context)

        watched_list = reward_list[reward_list['user_id'] == streaming_batch.iloc[i, 0]]

        if recommendations.action.id not in list(watched_list['movie_id']):
            # 잘 못 맞췄으면 0점을 얻음
            th.reward(history_id, {recommendations.action.id: 0.0})
            th.update_D(context=context, action_id=recommendations.action.id, reward=0.0)

        else:
            # 잘 맞춨으면 1점을 얻음
            th.reward(history_id, {recommendations.action.id: 1.0})
            th.update_D(context=context, action_id=recommendations.action.id, reward=1.0)
            reward_sum += 1

        if i % batch_size == 0 and i != 0:
            for action_chosen in th._action_storage.iterids():
                th.update_tree(action_id=action_chosen)

        if i % 100 == 0:
            print("Step: {} -- Average Reward: {}".format(i, np.round(reward_sum / (i+1), 4)))

        y.append(reward_sum / (i + 1))

    print("Time: {}".format(time.time() - start))
    x = list(range(max_iter))
    plt.figure()
    plt.plot(x, y, c='r')
    plt.title("Cumulative Average Reward of \n Tree Heuristic: Movie Lens Data")
    plt.show()


# Training: Cover Type Data
def train_covtype(n_samples=581000, batch_size=3000):
    file = pd.read_csv("covtype.data", header=None)
    data = file.values
    np.random.shuffle(data)

    X, temp = data[:, 0:54], data[:, 54]
    Y = pd.get_dummies(temp).values

    actions = make_arm(list(range(7)))

    th = TreeHeuristic(history.MemoryHistoryStorage(), model.MemoryModelStorage(),
                       action.MemoryActionStorage(), n_actions=7)

    th.build(first_context=X[0], actions=actions)

    reward_sum = 0
    y = []

    print("Starting Now...")
    start = time.time()

    for i in range(n_samples):

        context = X[i]
        history_id, recommendations = th.sample_from_beta(context=context)

        # 실제 Reward 를 받고 이를 누적함
        actual_reward = Y[i, recommendations.action.id]
        reward_sum += actual_reward

        th.reward(history_id, {recommendations.action.id: actual_reward})

        # D는 매 trial 마다 업데이트해 주어야 함
        th.update_D(context=context, action_id=recommendations.action.id, reward=actual_reward)

        # batch size 만큼을 모아서 적합해줌
        if i % batch_size == 0 and i != 0:
            for action_chosen in th._action_storage.iterids():
                th.update_tree(action_id=action_chosen)

        # 로그는 100개 마다 찍음
        if i % 100 == 0:
            print("Step: {} -- Average Reward: {}".format(i, np.round(reward_sum / (i+1), 4)))

        y.append(reward_sum/(i+1))

    print("Time: {}".format(time.time() - start))
    x = list(range(n_samples))
    y[0] = 0
    plt.figure()
    plt.plot(x, y, c='r')
    plt.title("Cumulative Average Reward Flow of \n Tree Heuristic: Cover type Data")
    plt.show()

Test는 전통적으로 자주 애용되었던 Movielens 데이터와 Covtype 데이터로 진행할 수 있다. 아래 속도와 관련된 지표는 GPU가 없는 Laptop에 의한 것임을 밝혀둔다.

위 두 데이터의 경우, Tree Heuristic 알고리즘이 Lin UCB보다 우수한 성능을 보이는 것으로 확인되었다. 비록 Lin UCB보다는 속도 면에서 열위를 보이기는 하지만, Tree 구조에 기반한 모델이므로 해석에 있어 강점을 보일 수 있다는 점과 우수한 성능 때문에 충분히 기능할 수 있는 알고리즘으로 판단된다.

Test1: Covtype Data

알고리즘 10% Dataset

(58,100)
20% Dataset

(116,200)
50% Dataset

(290,500)
100% Dataset

(581,000)
비고
Lin UCB 0.7086

(23.66초)
0.7126

(49.39초)
0.7165

(137.19초)
0.7180

(5분 39초)
alpha=0.2
Tree Heuristic 0.7154

(100.65초)
0.7688

(6분 48초)
0.8261

(2463.70초)
0.8626

(2시간 37분)
3000 trial이

지날 때 마다 적합

Test2: Movielens Data

알고리즘 10% Dataset

(16,400)
20% Dataset

(32,700)
50% Dataset

(81,800)
100% Dataset

(163,600)
비고
Lin UCB 0.7521 0.7668 0.7746 0.7567

(6분 14초)
alpha=0.2
Tree Heuristic 0.7683 0.8017 0.8183 0.8346

(33분 16초)
100 trial이

지날 때 마다 적합

Reference

Lin UCB 논문 Tree Heuristic 논문

Comment  Read more

OpenAI GPT-2 - Language Models are Unsupervised Multitask Learners(GPT2 논문 설명)

|

이 글에서는 2019년 2월 Alec Radford 등이 발표한 OpenAI GPT-2: Language Models are Unsupervised Multitask Learners를 살펴보도록 한다.

코드와 논문은 여기에서 볼 수 있지만, 전체버전은 성능이 너무 강력하다는 이유로 공개되지 않았다.

중요한 부분만 적을 예정이므로 전체가 궁금하면 원 논문을 찾아 읽어보면 된다.


OpenAI GPT-2 - Language Models are Unsupervised Multitask Learners

논문 링크: OpenAI GPT-2 - Language Models are Unsupervised Multitask Learners

홈페이지: OpenAI

Tensorflow code: Official Code

초록(Abstract)

질답(QA), 기계번역, 독해, 요약과 같은 자연어처리 과제들은 대개 과제에 특화된 dataset과 지도학습을 통해 이루어졌다. 이 논문에서, 언어모델은 WebText라는 수백만 개의 웹페이지를 모은 새로운 dataset에서 학습될 때 어떤 명시적인 지도 없이 이러한 과제들을 학습하기 시작했음을 보인다. 문서와 질문이 있을 때 대답을 생성하는 언어모델은 CoQA dataset에서 55 F1 score를 달성하였고 이는 127k 이상의 학습데이터 사용 없이 4개 중 3개의 기준시스템을 능가한 것이다.
이 언어모델의 capacity는 zero-shot task transfer의 성공에 필수적이다. 이 논문에서 제시되는 가장 큰 모델 GPT-2는 15억 개의 Transformer parameter를 가지며 zero-shot 환경에서 8개 중 7개에서 state-of-the-art를 달성하였는데 이 큰 모델도 WebText에서 과소적합(underfitting) 현상을 보인다. 모델에서 나온 예는 이러한 개선점을 반영하며 일관성 있는 텍스트 단락을 포함한다. 이러한 발견은 자연적 설명으로부터 과제수행능력을 배우는 언어처리모델을 개발하는 촉망되는 방법을 시사한다.


1. 서론(Introduction)

기계학습 시스템은 큰 dataset과 고용량의 모델, 지도학습 등을 통해 빠르게 발전해왔다. 이러한 방법으로 개발된 모델들은 불안정하며 매우 좁은 범위의 문제에서만 뛰어난 능력을 발휘한다. 그래서 데이터를 수동 분류하는 과정 없이도 더 범용적인 모델을 개발할 필요가 있다.

현재 기계학습체계를 개발하는 주된 방법은 목표 과제에 맞는 dataset을 찾아서, 이를 학습/검증 단계로 나누어 학습 후 IID(independet and identically distributed)로 성능을 측정하는 방법이다. 이는 좁은 범위의 과제에서는 매우 효과적이나 범용적인 이해를 필요로 하는 독해나 다양한 이미지 분류시스템 등의 문제에서는 높은 성능을 내지 못했다.
많은 연구가 단일 영역의 dataset과 단일 과제에만 맞춘 학습에만 치중되었었다. 최근에야 넓은 범위의 dataset과 여러 과제에 대한 GLUE benchmark 등이 제안되기 시작했다.

다중작업 학습(Multitask learning)은 일반성능을 높이는 유망한 방법이지만 아직 초기 연구 단계이다. 최근에는 Learning and Evaluating General Linguistic Intelligence 등의 연구가 성능 향상을 이뤄냈지만, 최근의 기계학습 시스템은 일반화를 위해서는 수십만 개 정도의 학습 샘플을 필요로 하는데 이는 다중작업 학습을 위해서는 그 몇 배가 필요하다는 것을 뜻한다.

가장 성능이 높은 언어처리모델은 사전학습(pre-training)과 지도 세부학습(supervised fine-tuning)의 결합으로 만들어졌다. 이 접근법은 transfer과 더불어 긴 역사를 가졌다.

  • 단어벡터를 학습시킨 후 과제특화된(task-specific) 모델구조에 입력으로 넣고
  • 순환형 네트워크의 문맥표현이 전이되고
  • 최근에는 과제특화된 모델구조는 더 이상 중요하지 않으며 많은 self-attention block만으로 충분하다고 한다.

이러한 방법들은 여전히 지도학습을 필요로 한다. 만약 지도 데이터가 최소한으로 또는 전혀 필요하지 않다면, 일반상식 추론이나 감정분석과 같은 특정 과제들을 수행하는 데 큰 발전이 있을 것이다.

이 논문에서는, 위의 두 방향의 연구를 결합하여 전이학습의 더 일반적인 방법의 흐름을 잏는다. 언어모델이 어떤 parameter나 모델구조의 변화 없이도 zero-shot setting 하에서 downstream task를 수행할 수 있음을 보인다. 이 접근법은 언어모델이 zero-shot setting 하에서 넓은 범위의 과제를 수행할 수 있는 가능성을 보이며, 전도유망하고 경쟁력 있으며 과제에 따라서는 state-of-the-art를 달성하였다.


2. 접근법(Approach)

핵심은 언어모델링(language modeling)이다. 언어모델링은 보통 각 원소가 일련의 symbol $(s_1, s_2, …, s_n)$으로 구성된 예제 $(x_1, x_2, …, x_n)$에서 비지도분포 추정을 하는 것으로 정의된다. 언어는 자연적으로 연속된 순서를 가지므로 보통 조건부확률의 곱으로 이루어진 symbol에 따른 합동확률로 구해진다:

[p(x) = \prod_{i=1}^n p(s_n \vert s_1, …, s_{n-1})]

이 접근법은 어떤 조건부확률 $p(s_{n-k}, …, s_n \vert s_1, …, s_{n-k-1})$만큼이나 $p(x)$의 다루기 쉬운 샘플링을 가능하게 하였다. 최근에는 이러한 조건부확률을 매우 잘 계산하는 Transformer 등이 만들어졌다.

단일 과제 수행의 학습은 조건부분포 $p(output \vert input)$를 추정하는 확률 framework로 표현될 수 있다. 범용시스템은 여러 다른 과제들을 수행할 수 있어야 하기 때문에 같은 입력이라도 입력뿐 아니라 과제의 종류라는 조건이 들어가야 한다. 따라서 $p(output \vert input, task)$로 표현되어야 한다. 이는 다중학습과 메타학습 환경에서 다양하게 형식을 갖는다.
과제 조건을 다는 것은 모델구조 수준이나 MAML 최적화 framework에서 알고리즘 수준에서 구현되기도 한다. 그러나 McCann에서 나온 것과 같이, 언어는 과제/입력/출력 모두를 일련의 symbol로 명시하는 유연한 방법을 제공한다.

  • 예를 들어 번역학습은 (프랑스어로 번역, 영어 텍스트, 프랑스어 텍스트)로 표현된다(translate to french, english text, french text).
  • 독해는 (질문에 대답, 문서, 질문, 대답)이다(answer the question, document, question, answer).

McCann은 MQAN이라는 단일 모델로 학습하는 것이 가능했다고 설명하며 이 형식으로 많은 과제를 수행하였다고 한다.

언어모델링은 또한 어떤 symbol이 예측할 출력인지에 대한 명시적인 지도 없이도 McCann의 과제들을 학습할 수 있다. Sequence의 부분집합에 대해서만 평가하더라도 지도목적함수는 비지도목적함수와 같기 때문에, 전역최소값(global minimum)은 비지도학습에서와 지도학습에서 같은 값을 가진다. 즉 비지도목적함수에서 수렴하게 할 수 있다. 예비실험에서, 충분히 큰 언어모델은 이런 toy-ish 환경에서 다중작업 학습이 가능했으나 학습은 명시적 지도학습에서보다 훨씬 느렸다.

대화(dialog) 데이터는 꽤 괜찮은 학습 방법이지만, 상호작용이 필요없는 인터넷 사이트에 존재하는 방대한 양의 데이터가 더 낫다는 판단을 내렸다. 충분한 용량을 가지는 언어모델이라면 데이터 조달 방법과는 관련없이 더 나은 예측을 위한 수행을 시작할 것이라는 추측이 있다. 만약 지금 가능하다면, 아마 현실적으로 비지도 다중작업 학습이 될 것이다.

Examples

2.1. Training Dataset

많은 선행연구에서 사용된 dataset은 뉴스와 같이 한 영역에서만 가져온 데이터로 구성되어 있었다. 이 논문에서는 가능한 한 다양한 출처로부터 가져오려고 하였다.

이러한 점에서 촉망받는 것은 Common Crawl과 갈은 web scraping 자료인데, 이 중 많은 양이 이해할 수 없는(unintelligible, 또는 품질이 떨어지는) 데이터라 한다. 따라서 이 논문에서는 단순 크롤링이 아닌 고품질의 데이터를 얻는 다른 방법을 사용하기로 했다.

  • 사람에 의해 필터링된 글만을 사용하기로 하였다:
    • Reddit에서 3 karma 이상을 받은 글에 포함된 외부링크의 글을 가져왔다.
    • 결과적으로 45M개의 링크를 가져왔다.
  • Dataset 이름은 WebText라 하였다.
  • 텍스트 추출을 위해 DragnetNewspaper 내용추출기를 사용했다.
  • 2017년 12월 이후의 글과 위키피디아 글은 제거했으며, 중복제거 등을 거쳐 8M개의 문서, 40GB의 텍스트를 확보하였다.
    • 위키피디아는 다른 dataset에서 흔하고, 학습과 측정 단계에서의 데이터가 겹치는 문제로 인해 분석이 복잡해질 수 있어 제외했다.

2.2. Input Representation

범용언어모델은 어떤 문자열의 확률도 계산할 수 있어야 한다. 현재 대규모 언어모델은 소문자화, 토큰화, 모델링 가능한 문자열이 차지하는 공간을 제한하기 위한 사전외 token과 같은 전처리 과정을 거친다. Unicode 문자열을 UTF-8 형식으로 처리하는 것은 이를 우아하게 만족시키며 byte수준 언어모델은 One Billion Word Benchmark와 같은 대규모 dataset에서 단어수준 언어모델에 뒤떨어진다. WebText에서도 역시 그러한 성능차이를 확인하였다.

Byte Pair Encoding(BPE)는 글자(byte)와 단어의 적당한 중간 단위를 쓴다.

  • 이는 자주 나오는 symbol sequence의 단어수준 입력과 자주 나오지 않는 symbol sequence의 글자수준 입력을 적절히 보간(interpolate)한다.
  • 그 이름과는 달리 BPE 구현은 byte sequence가 아닌 Unicode code points에서 동작한다. 이러한 구현은 모든 Unicode 문자열을 모델링하기 위해 전체 Unicode symbol의 공간만큼을 필요로 한다.
  • multi-symbol token을 추가하기 전 13만 개의 token을 포함하는 기본사전을 필요로 하게 된다. 이는 보통의 3만 2천~6만 4천 token의 사전보다 엄청나게 큰 것이다.
    • 이와는 달리 byte수준의 BPE의 사전은 256개만의 token을 포함한다.
  • 그러나 BPE를 byte sequence에 직접 적용하는 것은 BPE가 token 사전을 구춘하기 위한 heuristic에 기반한 greedy frequency를 사용하기 때문에 최적이 아니게 된다.
  • BPE는 dog의 다양한 형태, dog.dog!dog? 등을 가진다.
    • 이는 한정적인 사전과 모델의 공간을 최적이 아니게 사용하게 된다.
    • 이를 피하기 위해 BPE가 어떤 byte sequence로부터도 문자 범주를 넘어 병합하는 것을 막았다.
  • 여러 vocab token의 최소부분만을 추가할 때 압축효율성을 크게 증가시키는 공간을 위한 예외를 두었다.

이러한 입력표현은 단어수준 언어모델의 경험적 이점과 문자수준 접근법의 일반성을 결합할 수 있게 한다. 이 논문의 접근법이 어떤 Unicode 문자열에든 확률을 부여할 수 있기 때문에, 이는 이 논문의 모델을 전처리, 토큰화, 사전크기 등과 관련없이 어떤 dataset에서도 평가할 수 있게 만든다.

2.3. Model

Transformer가 기본 구조이며, OpenAI Gpt-1의 구조를 대부분 따른다. 약간의 차이는 있는데,

  • Layer 정규화가 pre-activation residual network처럼 각 sub-block의 입력으로 옮겨졌다.
    • 추가 layer 정규화가 마지막 self-attention block 이후에 추가되었다.
  • 모델 깊이에 따른 residual path의 누적에 관한 부분의 초기화 방법이 변경되었다.
    • $N$이 residual layer의 수라 할 때, residual layer의 가중치에 $1 / \sqrt{N}$을 곱했다.
  • 사전은 50,257개로 확장되었다.
  • 문맥고려범위(context size)가 512~1024개의 token으로 늘어났으며 batch size도 512로 증가했다.

3. 실험(Experiments)

모델은 크기가 각각 다른 4개를 만들어 실험했다. 각 모델의 크기는 다음과 같다.

Parameters Layers d_{model}
117M 12 768
345M 24 1024
762M 36 1280
1542M 48 1600

가장 작은 모델은 크기가 OpenAI GPT-1와 같고, 두 번째는 BERT와 같다. 가장 큰 모델인 GPT-2는 10배 이상 크다.

각 모델의 learning rate는 WebText의 5%를 떼서 만든 held-out 샘플을 사용하여 수동 조정하였다. 모든 모델은 여전히 WebText에 과소적합(underfitted)되었으며 더 오래 학습시키면 더 높은 성능을 얻을 수 있을 것이다.

3.1. Language Modeling

GPT-2는 문자단위(byte level)로 되어 있어 손실이 있는 전처리나 토큰화 등이 필요하지 않으며, 어떤 언더 모델 benchmark에도 사용할 수 있다. 평가는 WebText 언어모델에 따른 dataset의 로그확률을 계산하는 방식으로 통일했다. WebText 언어모델은 일반 분포를 심하게 벗어난 것, 이를테면 대단히 규격화된 텍스트, 분리된 구두점이나 축약형, 섞인 문장에 대해 평가받으며, <UNK>는 WebText에 400억 byte 중 26번밖에 나타나지 않는다.

결과는 아래 표에 나와 있으며, 어떤 미세조정도 거치지 않고 zero-shot 환경에서 8개 중 7개에서 state-of-the-art를 달성하였다.

Results

3.2. Children’s Boot Test

품사(고유명사, 명사, 동사, 전치사)에 따른 언어 모델의 성능을 측정하기 위한 dataset이다. 원 논문에 소개된 내용을 따라 각 선택의 확률과 언어모델의 선택에 따른 문장의 나머지 부분에 대한 확률을 계산하고 가장 높은 확률의 선택지를 선택하게 했다. 결과는 89.1% $\to$ 93.3%가 되었다.

Results

3.3. LAMBADA

텍스트의 장거리 의존성(long-range dependencies)을 평가한다. 간단히 말하면 정확도는 19% $\to$ 52.66%, perplexity는 99.8 $\to$ 8.6으로 향상시켰다.

3.4. Winograd Schema Challenge

텍스트에 존재하는 중의성(또는 모호한 부분, ambiguities)을 해석하는 능력을 측정함으로써 일반상식 추론능력을 평가한다. GPT-2는 정확도를 7% 증가시켜 70.70%를 달성했다.

Results

3.5. Reading Comprehension

CoQA(The Conversation Question Answering dataset)을 7개의 분야에서 가져온 문서에서 질문자-답변자의 자연어 대화로 이루어진 dataset이다. CoQA 테스트는 독해능력과 대화에 기반한 모델의 답변능력을 평가한다. GPT-2는 55 F1 score를 달성해 4개 중 3개의 다른 모델을 능가했는데 이는 심지어 주어진 127k 이상의 수동 수집된 질답 쌍으로 학습시키지 않은 것이다. 지도가 포함된 state-of-the-art인 BERT는 89 F1 score에 근접하였다. 그러나 어떤 미세조정 없이 55점을 달성했다는 것은 상당히 고무적인 일이다.

3.6. Summarization

CNN과 Daily Mail dataset으로 요약 능력을 평가했다. 총합 6.4점의 향상을 보였다.

Results

3.7. Translation

번역에서는 예상외로 별로 좋은 결과를 내지 못했다고 한다.

3.8. Question Answering

답변한 문장이 ‘완전히 일치하는’ 평가 방법을 썼을 때 GPT-2는 약 4.1%의 정확도를 보여 일반적으로 5.3배의 정확도를 보인다. 그러나 GPT-2가 생성한 30개의 가장 ‘자신있는’ 답변은 아래 그림에 나와 있는데 이는 정보검색과 문서추출 질답을 병행한 열린분야 질답 시스템에 비하면 50%까지 낮은 성능을 보인다.

Results

4. 일반화 vs 암기(Generalization vs Memorization)

최근 연구에서 많이 사용되는 데이터셋 중에는 거의 동일한 이미지를 여럿 포함하는 것이 있는데, 예를 들면 CIFAR-10의 경우 3.3%의 이미지가 train / test 세트에서 겹친다. 이는 기계학습 시스템의 일반화 성능을 과대평가하게 만든다. 이는 성능 측정에 방해 요인이 되기 때문에 데이터셋이 이러한 요인이 없는지 확인하는 것은 중요하다.

그래서 WebText에 대해 8-gram 학습세트 토큰을 포함하는 Bloom 필터를 만들어 테스트해보았고, 결과는 다음과 같다.

Results

물론 WebText는 써도 괜찮다는 결론이 나왔다. 그런데 다른 데이터셋의 경우에는 생각보다 높은 겹침(overlap) 현상이 나타나는 것을 볼 수 있다.

비슷한 텍스트들이 성능에 어떤 영향을 미치는지 이해하고 정량화하는 것은 중요한 부분이다. 더 나은 데이터중복제거(de-duplication) 기술은 매우 중요하며, 중복제거에 기반한 n-gram overlap을 사용하는 것은 훌륭한 방법이며 이 논문에서는 이를 추천한다.

WebText LM의 성능을 결정하는 또 다른 잠재적 방법은 암기(기억, memorization)이 held-out set에 어떤 영향을 미치는지 살펴보는 것이다. train / test set에 대해 성능을 평가한 다음 그림은 그 거대한 GPT-2가 WebText에 대해 여전히 과소적합(underfitted)되었으며 암기에 의한 것이 아님을 보여준다.

Results

5. 관련 연구(Related Work)

이 논문의 많은 부분은 더 큰 데이터셋으로 학습된 더 큰 언어모델의 성능을 측정하는 데 쓰였다. 다른 많은 연구들도 이와 비슷하다(scaled RNN based LM 등).

iWeb Corpus와 같은 거대한 웹페이지의 텍스트 말뭉치를 필터링하고 구축하는 대안을 제시하거나, 언어문제를 위한 사전학습 방법, 벡터표현, seq2seq 등이 연구되었으며 최근 연구들은 언어모델의 사전학습이 잡담이나 대화 같은 어려운 생성문제에 맞춰 미세조정할 때 도움이 된다는 것을 밝혀내었다.


6. 토의(Discussion)

많은 연구들은 지도/비지도 사전학습 표현에 대한 학습, 이해, 비판적 평가에 관해 연구를 진행해 왔다. 이 논문은 비지도 학습이 아직 연구할 거리가 더 남아 있음을 밝혔다.

GPT-2의 zero-shot 학습 성능은 독해 등에서 좋은 성능을 보였으나 요약과 같은 문제에서는 기본적인 성능만을 보여주었다. 꽤 괜찮긴 해도 실제 사용하기엔 여전히 무리이다.

GPT-2의 성능이 많은 과제에서 괜찮긴 한데, 미세조정을 통한 그 한계가 얼마인지는 분명하지 않다. 그렇지만 이 논문의 저자들은 decaNLP나 GLUE와 갈은 벤치마크에서 미세조정(fine-tuning)할 것을 계획하고 있다고 한다.
또 GPT-2의 학습데이터와 그 크기가 BERT에서 말한 단방향 표현의 비효율성을 극복할 수 있을 만큼 충분한지도 확실치 않다고 한다.


7. 결론(Conclusion)

큰 크기의 언어모델이 충분히 크고 다양한 데이터셋에서 학습된다면 많은 분야와 데이터셋에서 괜찮은 성능을 보여준다. GPT-2의 zero-shot은 8개 중 7개의 주요 언어 과제에서 state-of-the-art를 달성하였다.
모델이 zero-shot으로 다양한 과제에서 잘 수행한다는 것은 대용량의 모델이 외부 지도 없이 충분히 크고 다양한 말뭉치로부터 학습하면 많은 문제를 잘 수행할 가능성을 최대화할 것을 제시한다.

Acknowledgements

언제나 있는 감사의 인사


Refenrences

논문 참조. 많은 레퍼런스가 있다.


Citation

@article{radford2019language,
  title={Language Models are Unsupervised Multitask Learners},
  author={Radford, Alec and Wu, Jeff and Child, Rewon and Luan, David and Amodei, Dario and Sutskever, Ilya},
  year={2019}
}

8. Appendix A: Samples

8.1. Model capacity

가장 작은 WebText LM과 GPT-2가 본 적 없는(unseen) WebTest 테스트 기사에 대해 생성한 문장들을 비교하여 나열한 것이 표 7~11에 있다. 256개의 단어(token)을 주고 다음 256를 생성하는 것이다. 논문에 따르면 잘 된 것을 골라 가져온 것은 아니라고 한다.
그 중 하나를 가져와 보았다. 기계가 생성한 문장치고 깔끔하다.

Generation Results

8.2. Text Memorization

게티즈버그 연설 유명한 인용문이나 연설 같은 문장이 주어질 때 GPT-2가 (그대로) ‘암기’하는 행동을 보이는 것이 관찰되었다.
이런 현상이 얼마나 일어나는지 보기 위해 test set 기사를 주고 GPT-2가 생성한 문장과 실제 문장의 완성한 것과의 overlap 비율을 비교해 보았다.
결과는 기준보다 그 비율이 낮다고 한다.

8.3. Diversity

표 12는 같은 문장을 주고 나머지를 생성하라 했을 때 얼마나 다양한 문장들을 생성하는지 본 것이다.

Generation Results

8.4. Robustness

표 13은 앞에서 언급 한 유니콘 뉴스 기사를 보여준다. 이 모델이 분포 문맥을 다룰 수는 있지만 이러한 샘플의 품질은 일반적으로 낮다.

Generation Results

Comment  Read more

BERT - Pre-training of Deep Bidirectional Transformers for Language Understanding(BERT 논문 설명)

|

이 글에서는 2018년 10월 Jacob Devlin 등이 발표한 BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding를 살펴보도록 한다.

어쩐지 ELMo를 매우 의식한 듯한 모델명이다.

코드와 사전학습(기학습)된 모델은 여기에서 볼 수 있다.

중요한 부분만 적을 예정이므로 전체가 궁금하면 원 논문을 찾아 읽어보면 된다.


BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding

논문 링크: BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding

Pytorch code: Github: dhlee347

초록(Abstract)

이 논문에서는 새로운 언어표현모델(language representation model)인 BERT(Bidirectional Encoder Representations from Transformers)를 소개한다. 최근의 언어표현모델과는 다르게 BERT는 모든 layer의 좌우 문맥 모두에서 깊은 양방향 표현(deep bidirectional representations)를 사전학습(pre-train)하도록 설계되었다. 결과적으로 사전학습된 BERT는 QA나 언어추론 등 대부분의 과제(task)에서, 해당 과제에 특화된 모듈을 추가하지 않고 딱 하나의 추가 출력 layer만 붙여도 state-of-the-art 결과를 얻을 수 있었다.

BERT는 개념적으로 간단하고 경험적으로 강력하다. GLUE benchmark에서 7.7% 상승한 80.5%, MultiNLI에서 4.6$ 상승한 86.7%, SQuAD v1.1에서 1.5 상승한 93.2(F1 score), SQuAD v2.0에서 5.1 상승한 83.1 F1 score를 기록하였다.


1. 서론(Introduction)

언어모델 사전학습은 자연어처리 과제들에서 효과적이다. 사전학습된 언어표현을 downstream task에 적용하는 데는 두 가지 전략이 있는데

  1. 사전학습된 표현을 특정과제에 특화된(task-specific) 모델구조에 추가하는 특성기반 접근법(feature-based approach), 예로 ELMo가 있다.
  2. 특정과제에 특화된 parameter를 최소로 추가하여, 간단히 사전학습된 모든 parameter를 미세조정(fine-tune)함으로써 downstream task에서 학습하는 방법, 예로 Generative Pre-trained Transformer(OpenAI GPT)가 있다.

이 두 접근법은 범용언어표현을 학습하기 위해 단방향 언어모델을 사용하기 때문에 사전학습기간 동안 같은 목적함수를 공유한다.
그러나 이러한 방법은, 특히 fine-tuning 접근법은 사전학습된 표현의 능력을 제한시킨다. 가장 큰 한계는 표준언어모델은 단방향이며 이것이 사전학습하는 동안 모델구조의 선택권을 제한한다. 예로 OpenAI GPT의 경우, 좌$\rightarrow$우 구조를 사용하였는데 이는 Transformer의 self-attention layer에서 모든 token이 이전 token에만 의존하게 만든다.

이 논문에서는 fine-tuning을 기반으로 한 BERT라는 접근법을 제안한다. 이 모델은 MLM(Masked Language Model) 사전학습을 사용하여 단방향성을 제거하였다. 이는

  • 입력의 일부 token을 무작위로 [mask] token으로 치환하고, 목적은 주변 문맥으로부터 마스크 처리된 단어를 유추하는 것이다.
  • 좌$\rightarrow$우 사전학습 언어모델과는 다르게 MLM objective는 좌/우 문맥을 결합하여 깊은 양방향(deep bidirectional) Transformer을 미리 학습시킬 수 있게 한다.
  • 추가로 NSP(Next Sentence Prediction) 과제를 사용하여 문자-쌍 표현을 미리 결합학습(jointly pre-train)할 수 있게 하였다.

그래서 이 논문의 기여한 바는

  • 언어표현을 위한 양방향 사전학습의 중요성을 보여주었다. 즉 deep bidirectional representation을 사전학습할 수 있다.
  • 사전학습된 표현은 특정과제에 특화된 구조를 만들기 위해 조정을 계속할 필요를 줄여준다는 것을 보였다.
  • 11개의 NLP 과제에서 state-of-the-art 결과를 얻어내었다.

2. 관련 연구(Related work)

범용언어표현의 사전학습 연구는 긴 역사가 있다. 간단히 살펴보자.

2.1. Unsupervised Feature-based Approaches

넓은 범위에서 사용가능한 단어의 표현을 학습시키는 것은 비신경망 모델과 신경망 모델 모두에서 많은 연구가 이러우졌다. 현대 NLP 체계에서 사전학습된 단어 embedding은 굉장한 성과를 거두었는데, 이 벡터를 학습시키기 위해서 좌$\rightarrow$우 언어모델 objective 또는 좌우 문맥으로부터 정확/부정확한 단어를 가려내는 방법 등이 사용되었다.
이러한 접근법은 문장/문단 embedding과 갈은 더 세부적인 부분으로 일반화되었다. 문장표현을 학습하기 위해서 다음 후보문장의 순위를 매기거나, 이전문장의 표현이 주어졌을 때 다음문장의 좌$\rightarrow$우 생성을 하거나, auto-encoder의 noise를 줄이는 방법 등이 사용되었다.

ELMo와 그 이전 모델들은 전통적인 단어 embedding을 다른 차원으로 일반화했다. 이들은 문맥에 민감한 특성들을 좌$\rightarrow$우 및 우$\rightarrow$좌 모델로부터 추출했다. 각 token의 문맥 표현은 좌$\rightarrow$우 및 우$\rightarrow$좌 표현의 결합으로 만들어진다.
이외 여러 모델이 있으나 전부 특정기반이며 또한 깊은 양방향 학습이 이루어지지 못했다.

2.2. Unsupervised Fine-tuning Approaches

이 방법은 미분류된(unlabeled) 문자로부터 사전학습된 단어 embedding을 얻는 것부터 시작한다.

더 최근에는, 문맥 token 표현을 생성하는 문장/문서 인코더가 미분류 문자로부터 사전학습되고 지도 downstream task에 맞춰 미세조정되었다. 이 접근법의 장점은 parameter의 수가 적다는 것이다.
OpenAI GPT는 GLUE benchmark에서 높은 성능을 기록핬다. 좌$\rightarrow$우 언어모델링과 auto-encoder objective가 이러한 모델에 사용되었다.

2.3. Transfer Learning from Supervised Data

언어추론이나 기계번역 등의 분야에서 지도가 있는 task에서 큰 dataset으로 효과적인 전이학습을 하려는 연구가 있어왔다. Computer vision 연구는 또한 ImageNet 등 사전학습된 큰 모델로부터의 전이학습이 중요함을 보였다.


3. BERT

이 framework에는 크게 두 가지 단계가 있다: pre-training(사전학습)과 fine-tuning(미세조정)이다.
pre-training 동안 모델은 다른 사전학습된 과제, 미분류된 데이터로 학습된다.
fine-tuning 동안 BERT 모델은 사전학습된 parameter로 초기화된 후, 모든 parameter가 downstream task로부터 분류된 데이터를 사용하여 미세조정된다. 각 downstream task는 그들이 같은 사전학습된 parameter로 초기화되었다 하더라도 독립된 fine-tuned 모델이다. 다음 Figure 1에 나오는 QA 예제로 설명이 이어질 것이다.

Architecture

BERT의 다른 모델과 구분되는 특징은 여러 다른 과제에 대해서도 통합된 모델구조를 갖는다는 것이다. 사전학습된 모델구조와 최종 downstream 구조에는 최소한의 차이만 존재한다.

Model Architecture

BERT의 모델구조는 Attention Is All You NeedTransformer를 기반으로 한 multi-layer bidirectional Transformer encoder이다. Transformer를 썼기 때문에 특별할 것이 없으며 그 구현은 원본과 거의 같기 때문에 자세한 설명은 생략한다. 여기에서 인코더 부분을 살펴보자.

Layer의 수를 $L$, 은닉층의 크기를 $H$, self-attention head의 수를 $A$라 한다.
BERT에는 두 가지 모델이 있는데

  • BERT_base: $L=12, H=768, A=12$. 전체 parameter 수: 110M
  • BERT_large: $L=24, H=1024, A=16$. 전체 parameter 수: 340M

BERT_base는 비교를 위해 OpenAI GPT와 같은 크기를 가지도록 만들었다. 그러나 BERT Transformer는 양뱡향 self-attention을 사용하고 GPT Transformer는 모든 token이 왼쪽 문맥만 참조하도록 제한된 self-attention을 사용한다.

Input/Output Representations

BERT가 다양한 downstream task를 처리할 수 있게 하기 위해, 입력표현은 단일 문장인지 문장들의 쌍(Q & A 등)인지 구분되어야 한다. 여기서 “문장”이란 실제 언어학적 문장이 아닌 인접한 문자들의 연속으로 본다. “Sequence”가 BERT의 입력 token sequence가 되는데, 이는 단일 문장이나 문장의 쌍이 될 수 있다.

이 논문에서는 3만 개의 단어 수를 갖는 WordPiece embedding을 사용한다. 모든 sequence의 첫 번째 token은 [CLS]라는 특별한 분류 token이다. 이 token과 연관된 최종 은닉상태는 분류문제에서 sequence 표현을 총합하는 것으로 사용된다. 문장의 쌍은 한 개의 문장으로 합쳐지는데 두 가지 방법으로 구분된다:

  1. [SEP]라는 특별한 token이 두 문장 사이에 들어간다.
  2. 문장들의 모든 token에 해당 토큰이 문장 A에 속하는지 B에 속하는지에 대한 정보를 담은 embedding이 추가된다.

위 그림에서처럼, 입력 embedding을 $E$라 하면, [CLS] token의 최종 은닉벡터 $C$와 $i$번째 입력 token에 대한 최종 은닉벡터 $T_i$는 $C \in \mathbb{R}^H, T_i \in \mathbb{R}^H$를 만족한다.

주어진 token에 대해 그 입력표현은 연관된 token, segment, position embedding의 합으로 구성된다. 이 구조는 Figure 2에서 볼 수 있다.

BERT input representation

3.1. Pre-training BERT

BERT를 사전학습시키기 위해 전통적인 좌$\rightarrow$우 또는 우$\rightarrow$좌 언어모델을 사용하지 않는다. 대신, 다음의 두 가지 비지도 task를 사용하여 학습시켜 놓는다.

Task #1: Masked LM

직관적으로, 깊은 양방향 모델은 좌$\rightarrow$우 모델 또는 얕은 양방향 모델보다 더 강력할 것이다. 그러나, 전통적인 언어모델은 단방향으로만 쉽게 학습가능한데, 양방향 조건은 각 단어가 간접적으로 ‘그 단어 자체’를 의미할 수 있으며, 모델은 자명하게 다층 문맥 안에서 목표 단어를 예측할 수 있기 때문이다.

양방향 모델을 학습시키기 위해 입력 token을 무작위로 masking한 다음, 문맥을 통해 해당 단어를 예측하게 한다. 문학에서 Cloze task라고도 하지만 이 과정을 MLM(masked LM)라 부르기로 한다.
이 경우, mask token과 연관된 최종 은닉벡터는 표준 LM처럼 단어집합 내 출력 softmax로 넘어간다. Denoising auto-encoder과는 다르게 전체 입력이 아닌 masked word만을 예측한다.

이것이 양방향 사전학습 모델을 얻을 수 있도록 해주지만, [mask] token은 fine-tuning 단계에 나타나지 않기 때문에 pre-training 단계와 fine-tuning 단계 간 mismatch가 생긴다는 단점이 있다. 이를 완화하기 위해, 어떤 token을 항상 [mask] token으로 바꿔버리지 않는다. 구체적으로는,

  • 학습데이터 생성자는, 전체 token 중 무작위로 15%를 선택한다.
  • 선정된 위치의 token은
    • 80%의 확률로 [mask] token으로 치환되고,
    • 10%의 확률로 무작위 token으로 치환되고,
    • 10%의 확률로 그대로 남는다.

그러면 $T_i$는 cross entropy loss로 원본 token을 예측한다. 이 과정의 변형은 부록 C.2에서 다룬다.

Task #2: Next Sentence Prediction(NSP)

QA(Question Answering)나 NLI(Natural Language Inference) 등의 많은 중요한 문제는 언어모델에는 직접적으로 포착되지 않는 두 문장 사이의 관계(relationship)를 이해하는 것에 기반한다. 문장 간 관계를 모델이 학습하도록, 아무 단일 언어 말뭉치에서 생성될 수 있는 이진화된 다음 문장 예측(binarized next sentence prediction)을 사전학습시켰다.
구체적으로, 학습 예제에서 문장 A와 B를 선택하는데,

  • 학습 데이터의 50%는 A와 B가 이어지는 문장이고(IsNext로 분류됨)
  • 학습 데이터의 50%는 B는 A와는 아무 관련 없는 무작위로 선택된 문장(NotNext로 분류됨)이다.

Figure 1에 나와 있듯이 $C$는 NSP(Next Sentence Prediction)을 위해 사용된다. 이렇게 간단함에도 이 task가 QA와 NLI에 굉장히 유용함을 Section 5.1에서 보일 것이다.
이 NSP task는 표현 학습에 긴밀히 연관되어 있지만, 이전 연구에서는 오직 문장 embedding만 downstream task로 이전(transfer)이 됐는데, BERT는 end-task 모델 parameter를 초기화하기 위해 모든 parameter를 이전시킨다.

Pre-training data

사전학습 과정은 언어모델 사전학습에서 이미 있던 것을 거의 따라간다. 사전학습 말뭉치로 BooksCorpus(800M 단어)와 English Wikipedia(2,500M 단어)를 사용했다. 위키피디아에 대해서는 문자 정보만을 추출했다.
긴 연속적 seqeunce를 추출하기 위해서는, 순서가 섞인 문장들의 집합인 Billion Word Benchmark같은 것보다는 문서단위 말뭉치를 쓰는 것이 매우 중요하다.

3.2. Fine-tuning BERT

Fine-tuning 단계는 Transformer의 self-attention mechanism이 적절한 입력과 출력은 교환해냄으로써, BERT가 많은 downstream task이 문자 또는 문자 쌍을 포함함에도 이들을 모델링할 수 있게 해주기 때문에 간단하다.
문자 쌍을 포함하는 문제에 대해 일반적인 패턴은 양방향 교차 attention을 적용하기 전 문자 쌍을 독립적으로 encoding하는 것이다.
BERT는 이 두 단계를 통합하기 위해 self-attention mechanism을 사용했다. 이는 두 문장 간 양방향 교차 attention을 효과적으로 포함하는 self-attention으로 결합한 문자 쌍을 encoding하는 것으로 이루어진다.

각 task마다, task-specific한 입출력을 BERT에 연결하고 모든 parameter를 end-to-end로 미세조정(fine-tune)했다.

입력 단계에서, 사전학습에서 나온 문장 A와 문장 B는

  1. ‘의역에서의 문장 쌍(sentence pairs in paraphrasing)’이나
  2. ‘함의에서 가장-전제 쌍(hypothesis-premise pairs in entailment)’이나
  3. ‘질답에서 질문-지문 쌍(question-passage pairs in question answering)’이나
  4. ‘문서분류나 sequence tagging에서의 퇴색된 문장-공집합 쌍(a degenerate text-∅ pair in text classification or sequence tagging)’

과 유사하다.

출력 단계에서,

  1. token 표현은, sequence tagging이나 QA처럼, token-level task을 위한 출력층으로 넘어가고,
  2. [CLS] 표현은, 함의나 감정분석처럼, 분류를 위한 출력층으로 넘어간다.

pre-training과 비교하여, fine-tuning은 상대적이로 연산량이 적다. 이 논문의 모든 결과는 같은 사전학습된 모델로부터 시작했을 때 단일 Cloud TPU에서 최대 한 시간, GPU에서 몇 시간 정도 안에 재현될 수 있다.
실험 결과는 Section 4에, 더 자세한 것은 부록 A.5를 보면 된다.


4. 실험(Experiments)

4.1. GLUE

GLUE benchmark는 다양한 자연어이해 문제들을 모아놓은 것이다.
GLUE에 대해 미세조정하기 위해, 입력 sequence를 Section 3에서 언급한 대로 변환하고, 첫 번째 token([CLS])와 연관된 최종 은닉벡터 $C \in \mathbb{R}^H$를 총합 표현으로 사용한다. fine-tuning 단계에서 새로 도입하는 유일한 parameter는 분류 layer weights $W \in \mathbb{R}^{K \times H}$($K$는 분류의 수)이다. 이제 $C$와 $W$의 표준 분류 loss log(softmax($CW^T$))를 계산한다.
모든 GLUE task에 대해 batch size 32, 3 epochs으로 실험한 결과는 다음과 같다.

GLUE Results
  • 각 task마다 Dev set에서 최적의 learning rate를 선택했다.
  • BERT_large는 작은 dataset에 대해 fine-tuning 학습이 불안정할 때가 있어서, 무작위 시작을 여러 번 하여 가장 좋은 것을 선택했다.

BERT_base만으로도 state-of-the-art 결과를 얻었으며, BERT_large는 그보다도 더 뛰어난 성능을 보여준다.

4.2. SQuAD v1.1

Stanford Question Answering Dataset은 10만여 개의 질답 쌍으로 구성되어 있다. 질문과 그에 대한 답을 포함하는 위키피디아 지문이 주어지면, 해당 지문에서 답이 되는 부분을 찾는 과제이다.

Figure 1에서 보인 것과 같이, QA task에서는 입력 질문(A)과 지문(B)을 하나의 결합된 sequence로 둔다. fine-tuning 중에 새로 추가하는 것은 시작벡터 $S \in \mathbb{R}^H$와 종료벡터 $E \in \mathbb{R}^H$ 뿐이다. 답이 지문의 $i$번째 span(단어뭉치)의 시작이 될 확률은 지문에서 다음 식으로 계산된다:

[P_i = \frac{e^{S \cdot T_i}}{\sum_f e^{S \cdot T_i}}]

유사한 식이 span의 끝에도 사용된다. 위치 $i \sim j$의 점수는 $S \cdot T_i + E \cdot T_j$로 정의되며, $j \ge i$이면서 최대 점수를 갖는 span이 예측 결과가 된다.

SQuAD Results

4.3. SQuAD v2.0

SQuAD v2.0은 (짧은) 답이 지문에 없는 경우를 포함시켜 더 확장한, 더 현실적인 task이다.

이를 위해 답이 없는 경우는 답이 되는 span의 시작과 끝이 [CLS]인 것으로 바꿔서 생각하는 것으로 해결한다. 이 때 점수는 다음과 같이 계산된다.

[s_{null}(\text{null span}) = S \cdot C + E \cdot C, \quad s_{\hat{i}, j}(\text{non-null span}) = \max_{j \ge i} S \cdot T_i + E \cdot T_j]

F1을 최대화하기 위해, non-null span이 답이 되려면 한계점 $\tau$에 대해 $ s_{\hat{i}, j} > s_{null} + \tau$이어야 한다.

4.4. SWAG

Situations With Adversarial Generations dataset은 113k개의 배경상식을 평가하는 문장 쌍으로 되어 있다. 이어지는 문장으로 4개 중 가장 그럴듯하게 이어지는 문장을 고르는 과제이다.

여기서도 첫 번째 문장(A)과 나머지 4개 선택지(B)로 묶어 진행하였고, 추가되는 parameter는 벡터 하나뿐인데, 이 벡터와 [CLS] token 표현 $C$와의 내적이 softmax로 정규화된 각 선택지의 점수를 나타낸다.

batch size 16, learning rate 2e^-5, 3 epochs로 진행한 결과는 다음과 같다. 역시 성능이 상당히 좋다.

SWAG Results

5. Ablation Studies

이 섹션에서는 특정 부분을 빼거나 교체해서 해당 부분의 역할을 알아보는 ablation 분석을 수행한다. 한국어로 번역하기 참 어려운 단어이다.

추가 연구는 부록 C에서 찾아볼 수 있다.

5.1. Effect of Pre-training Tasks

다음 두 가지 경우와 BERT_base를 비교한다: (No NSP), (LTR & No NSP).
MLM 대신 좌$\rightarrow$우(left-to-right, LTR) LM을 사용한 것으로, 이러한 제약은 pre-training뿐 아니라 fine-tuning에도 적용되었는데 두 단계 사이의 mismatch를 피하기 위해서다. 이는 같은 dataset, 입력표현, fine-tuning scheme을 사용하여 OpenAI GPT와도 직접비교가 가능하다.

Effect of Pre-training Tasks

NSP를 제거했을 때에도, MLM을 LTR로 바꿨을 때에도 성능이 크게 하락함을 볼 수 있다.

직관적으로, SQuAD에서는 token-level 은닉상태가 오른쪽 문맥에 대한 정보가 없기 때문에 이러한 결과가 명백하다.

5.2. Effect of Model Size

Layer 수, hidden units, attention head 등의 hyperparameter를 각각 바꿔보면서 최적의 모델을 찾아 보았다.

일반적으로 모델 크기가 커지면 성능도 향상되는데, 이 논문에서는 충분히 사전 학습되었다는 가정 하에 극단적으로 크기를 키우는 것 또한 아주 작은 규모의 과제에서도 큰 성능 향상이 있다는 것을 첫 번째로 보여주기도 했다.

Effect of Model Size

5.3. Feature-based Approach with BERT

BERT의 모든 결과는 간단한 분류 layer만 사전학습된 모델에 추가하는 fine-tuning 접근법을 사용했고, 모든 parameter는 downstream task에서 결합학습되었다.
그러나 이러한 특성기반 접근법에서, 고정된 특성이 사전학습된 모델로부터 추출될 때 특정 이점을 갖는다.

  1. 모든 task가 Transformer 인코더 구조로 쉽게 표현될 수 있는 것은 아니며, 따라서 특정과제에 특화된 모델구조가 추가될 필요가 있다.
  2. 학습데이터에 대한 한 번의 ‘비싼’ 사전 계산에 대한 연산량 관점에서의 이득이 있고 이 표현 위에서 연산량이 적은 모델에 대한 많은 실험을 진행할 수 있다.

BERT에 특성기반 접근법을 적용할 과제로는 CoNLL-2003 Named Entity Recognition(NER) task이 선정되었다. BERT에는 WordPiece 모델을 사용했고, 데이터에서 제공된 최대한의 문서 문맥을 포함시켰다.
fine-tuning 접근법을 피하기 위해, BERT의 어떤 parameter에 대해서도 fine-tuning 없이 하여 하나 또는 더 많은 layer에 대해 활성값을 추출한 특성기반 접근법을 사용하였다. 이러한 문맥 embedding은 분류 layer에 들어가기 전 무작위 초기화된 768차원 2-layer BiLSTM의 입력으로 사용되었다.

Feature-based Approach with BERT

BERT_large는 거의 state-of-the-art 성능을 가지며, 이는 BERT가 미세조정과 특성기반 접근법 모두에서 효율적임을 보여준다.


6. 결론(Conclusion)

최근 경험적 향상은 언어모델에서의 전이학습, 비지도 사전학습 등에 의해 이루어졌다. 특히, 이러한 결과들은 자원이 적은 task에서도 깊은 양방향 구조에서 이점을 얻도록 하였다. 이 논문의 가장 큰 기여는 같은 사전학습된 모델을 넓은 범위의 NLP task에 적용시킬 수 있도록 하는 깊은 양방향 구조를 일반화한 것이다.


Refenrences

논문 참조. 레퍼런스가 많다.


Appendix

A. Additional Details for BERT

부록 A.1은 MLM이 어떻게 masking을 하는지, NSP는 어떤지 예시와 함께 자세히 설명한다. 예시는 다음과 같다.

MLM & NSP

부록 A.2와 A.3은 각각 pre-training 단계와 fine-tuning 단계를 부가 설명한다.

부록 A.4는 다른 모델과의 구조 차이를 설명한다.

BERT, OpenAI GPT, ELMo

부록 A.5는 다른 task에 fine-tuning을 적용하는 방법을 설명한다. 그림으로 설명한 것은 다음과 같다.

Fine tuning on different tasks

B. Detailed Experimental Setup

부록 B.1은 GLUE benchmark에서 사용한 실험 세팅을 더 자세히 설명한다. 재현하고 싶다면 눈여겨보자.

C. Additional Ablation Studies

부록 C.1은 학습단계(Training Steps)의 수를 바꿔서 실험했다. 실험 결과는

  1. BERT는 엄청난 사전학습을 필요로 한다(128k words/batch * 1M steps).
  2. MLM 사전학습은 LTR보다 더 느리게 수렴하지만(최대 15%만이 치환되므로), 최종 정확도는 더 높다.

부록 C.2는 masking 과정을 변화시켰을 때의 실험이다.

Ablation Study for Masking

결국 처음 설명한 80%-10%-10% 비율이 가장 적절했다는 결론이다.


Comment  Read more

OpenAI GPT-1 - Improving Language Understanding by Generative Pre-Training(GPT1 논문 설명)

|

이 글에서는 2018년 6월 Alec Radford 등이 발표한 OpenAI GPT-1: Improving Language Understanding by Generative Pre-Training를 살펴보도록 한다.

코드와 논문은 여기에서 볼 수 있다.

중요한 부분만 적을 예정이므로 전체가 궁금하면 원 논문을 찾아 읽어보면 된다.


OpenAI GPT-1 - Improving Language Understanding by Generative Pre-Training

논문 링크: OpenAI GPT-1 - Improving Language Understanding by Generative Pre-Training

홈페이지: OpenAI

Tensorflow code: Official Code

초록(Abstract)

자연어이해는 원문함의, 질답, 의미 유사성 평가, 문서분류 등 넓은 범위의 과제로 이루어져 있다. 미분류 상태의 큰 말뭉치가 풍부함에도, 이러한 특정 과제의 학습을 위한 분류된 데이터는 부족하며, 모델이 적절히 수행하도록 만드는 것을 어렵게 한다.
이 논문에서는 이러한 과제들에서의 큰 성능 향상은, 언어모델을 다양한 미분류 말뭉치로 생성적 사전학습(generative pre-training)을 시킨 후 각 특정 과제에 맞춘 미세조정(fine-tuning) 과정을 거쳐 가능하다. 이전의 접근법과는 달리 모델구조는 최소한으로 변화시키면서 효과적인 전이(transfer)를 얻기 위한 미세조정 단계에서 과제에 맞는 입력표현(input representations)을 사용했다. 그리고 이 접근법이 다양한 과제에 대해 효과적임을 보일 것이다.

이 논문에서 제시하는 과제에 대한 별다른 지식이 없는(task-agnostic) 모델은 특정과제에 특화된 구조를 사용하는 모델의 성능을 뛰어넘는데 연구된 12개의 과제 중 9개에서는 state-of-the-art를 달성하였다. 예를 들어 상식추론(Cloze)에서는 8.9%, QA에서는 5.7%, 원문함의에서는 1.5% 상승하였다.


1. 서론(Introduction)

원본 그대로의 텍스트에서 효과적으로 학습하는 능력은 NLP에서 지도학습에 대한 의존성을 낮추는 데 있어 매우 중요하다. 대부분의 딥러닝 방법은 수동으로 분류된 방대한 양의 데이터를 필요로 하는데 이는 분류된 자원의 부족으로 인한 많은 범위로의 응용에 제약을 건다. 이러한 상황에서 미분류 데이터로부터 언어적 정보를 얻어낼 수 있는 모델은 힘들게 분류된 데이터를 만드는 것의 훌륭한 대안이 될 뿐만 아니라, 괜찮은 지도 방법이 있다 하더라도 비지도학습이 더 좋을 결과를 얻기도 한다. 사전학습된 단어 embedding이 그러한 예이다.

그러나 미분류 텍스트에서 단어 수준 정보 이상의 것을 얻는 것은 다음 두 가지 이유로 어렵다.

  1. 어떤 최적화 목적함수가 전이(transfer)에 유용한 텍스트 표현(representation)을 배우는 데 효과적인지 불분명하다. 최근 연구들은 언어모델링이나 기계번역, 담화 일관성(discourse coherence) 등 다양한 objective에서 각 방법이 다른 과제에서는 다른 방법을 능가하는 것을 보여 왔다.
  2. 학습된 표현을 다른 과제로 전이하는 가장 효과적인 방법에 대한 일치된 의견이 없다. 존재하는 방법들은 복잡한 학습전략이나 부가 학습 목적함수를 더하는 등 모델 구성에 과제에 특화된(task-specific) 변화를 주어야 한다.

이러한 불확실성은 언어처리에 대한 효과적인 준지도학습 접근법의 개발을 어렵게 한다.

이 논문에서는 비지도 사전학습(unsupervised pre-training)과 지도 미세조정(supervised fine-tuning)의 조합을 사용하여 언어이해 과제를 위한 준지도학습 접근법을 탐색한다. 목표는 약간의 조정만으로 넓은 범위의 과제에 전이 및 적용할 수 있는 범용 표현을 학습하는 것이다. 미분류 대량의 말뭉치와 수동으로 주석을 단(annotated) 학습예제를 갖는 여러 dataset에 대한 접근가능을 가정한다. 또한 학습은 두 단계를 거치게 된다.

  1. 신경망모델의 초기 parameter를 학습하기 위해 미분류 데이터에 대한 언어모델링 목적함수를 사용한다.
  2. 이 parameter를 연관된 지도 목적함수를 사용하여 목표 과제에 적용시킨다.

모델 구성은 기계번역, 문서생성, 구문분석 등에 상당한 성능을 보이는 Transformer를 사용한다. 이 모델은 RNN 등에 비해 장거리 의존성을 다루는 데 뛰어나 더 많은 구조화된 memory를 쓸 수 있게 한다. 전이 중에는 traversal-style 접근법에서 얻은 과제특화된 입력적응을 이용하며 입력은 한 개의, 일련의 ‘연속된 token’으로 주어진다. 이러한 적응방법은 사전학습된 모델의 구조를 바꾸는 것을 최소화한다.

이 접근법을 네 가지(자연어추론, 질답, 의미 유사성, 문서분류)에 대해 평가한다. 과제에 대한 지식이 없는 이 범용 모델은 12개 중 9개의 과제에서 state-of-the-art 결과를 보이며 선전했다.


2. 관련 연구(Related work)

Semi-supervised learning for NLP

이 연구는 자연어의 준지도학습에 넓게 걸쳐 있다. 이 체계는 sequence labeling이나 문서분류 등의 응용에 큰 관심을 불러일으켰다.

초기의 연구는 나중에 지도모델의 특징으로 사용될, 단어수준이나 구 수준의 통계를 계산하기 위해 미분류 데이터를 사용했다. 지난 몇 년간 연구자들은 미분류 말뭉치로부터 학습된 단어 embedding의 장점(다양한 과제에서의 성능 향상 가능성)을 발견했다. 그러나 이 접근법은 주로 단어 수준 표현을 학습할 뿐이다.

최근의 연구는 미분류 데이터로부터 단어수준 이상의 정보를 학습하려 하고 있다. 구 수준이나 문장 수준의 embedding은 미분류 데이터로부터 학습될 수 있으며 다양한 문제에서 텍스트를 적절한 벡터표현으로 변환할 수 있다.

Unsupervised pre-training

목표가 지도학습 목적함수를 수정하는 것이 아닌 좋은 초기화 지점을 찾는 것일 때, 비지도 사전학습은 준지도학습의 특별한 경우가 된다. 초기에는 이미지 분류나 회귀문제에 사용했다. 후속 연구는 사전학습이 정규화처럼 동작하며 DNN에서 더 좋은 일반화를 가능하게 함을 보였다. 최근에는 이미지분류, 음성인식, 다의어 명확화, 기계번역 등에 사용되고 있다.

GPT와 가장 유사한 연구는 신경망을 언어모델링 목적함수를 사용하여 사전학습시키고 지도 하에 목표 과제에 맞춰 미세조정하는 것을 포함한다. 그러나 어떤 언어적 정보를 포착하는 데 있어 LSTM의 사용은 이를 좁은 범위에 한정시킨다. 하지만 Transformer를 사용함으로써 넓은 범위에 걸친 언어적 구조와 정보를 학습할 수 있게 하였고 나아가 다양한 과제에 사용할 수 있게 되었다.
다른 접근법은 목표 과제에 맞춘 감독학습 중에 사전학습된 언어/기계번역 모델에서 얻은 은닉표현을 부가정보르 사용하였는데 이는 상당한 양의 parameter를 추가하는데, GPT는 그렇지 않다.

Auxiliary training objectives

보조 학습 목적함수를 추가하는 것은 준지도학습의 대안이다. A unified architecture for natural language processing deep neural networks with multitask learning이 여러 NLP task에 사용되었으며, 최근에는 Semi-supervised Multitask Learning for Sequence Labeling이 목표 과제에 보조 언어모델링 목적함수를 추가해 sequence labeling task에서 성능향상을 얻었다. GPT도 보조목적함수를 추가하지만, 비지도 사전학습이 이미 목표 과제에 대한 여러 언어적 정보를 학습했다는 것을 보일 것이다.


3. Framework

학습은 두 단계로 진행된다.

  1. 큰 말뭉치에서 대용량의 언어모델을 학습한다.
  2. 분류 데이터를 써서 특정 과제에 맞춰 모델을 미세조정한다.

3.1. Unsupervised pre-training

token의 비지도 말뭉치 $\mathcal{U} = {u_1, …, u_n}$이 주어질 때, 다음 우도를 최대화하도록 표준언어모델링 목적함수를 사용한다:

[L_1(\mathcal{U}) = \sum_i \log P(u_i \vert u_{i-k}, …, u_{i-1}; \Theta)]

$k$는 문맥고려범위(context window)이고 조건부확률 $P$는 parameter가 $\Theta$인 신경망을 사용하도록 설계된다. 이들은 확률적 경사하강법(SGD)에 의해 학습된다.

GPT는 언어모델로 Transformer의 변형인 multi-layer Transformer decoder를 사용한다. 이 모델은 입력 문맥 token에 multi-headed self-attention을 적용한 후, 목표 token에 대한 출력분포를 얻기 위해 position-wise feedforward layer를 적용한다:

[h_0 = UW_e + W_p \qquad \qquad \qquad \qquad \ \ \qquad]

[h_l = \text{transformer_block}(h_{l-1}) \ \ \forall l \in [1, n]]

[P(u) = \text{softmax}(h_n W_e^T) \qquad \qquad \qquad \quad \ \ \qquad]

$U = (u_{-k}, …, u_{-1})$는 token의 문맥벡터, $n$은 layer의 수, $W_e$는 token embedding 행렬, $W_p$는 위치 embedding 행렬이다.

(참고: 논문에는 위 식에서 $\forall l$이 $\forall i$로 오타가 나 있다)

3.2. Supervised fine-tuning

위 $L_1(\mathcal{U})$ 우도에 따라 모델을 학습시키고 나면, parameter를 목표 과제에 맞춰 미세조정한다. 분류된 dataset $\mathcal{C}$이 있고 각 구성요소가 일련의 입력 token $x^1, …, x^m$ 및 그 정답(label) $y$로 되어 있다고 하자. 입력은 최종 transformer block의 활성값 $h_l^m$을 얻기 위해 위의 사전학습된 모델에 전달하고 이 결과는 다시 $y$를 예측하기 위해 parameter $W_y$와 함께 선형 출력층으로 전달된다:

[P(y \vert x^1, …, x^m) = \text{softmax}(h_l^m W_y)]

이는 다음 우도를 최대화하도록 한다:

[L_2(\mathcal{C}) = \sum_{(x, y)} \log P(y \vert x^1, …, x^m)]

미세조정 단계에 언어모델을 보조 목적함수로 포함시키는 것은 다음 이유에서 학습을 돕는다.

  • 지도 모델의 일반화를 향상시키고
  • 수렴을 가속화한다.

이는 이전 연구들과 결을 같이한다.

구체적으로, weight $\lambda$에 대해 다음 목적함수를 최적화한다:

[L_3(\mathcal{C}) = L_2(\mathcal{C}) + \lambda \ast L_1(\mathcal{C})]

종합적으로, 미세조정 단계에서 추가된 parameter는 $W_y$과 구분자 token을 위한 embedding 뿐이다.

3.3. Task-specific input transformations

텍스트 분류와 같은 몇몀 과제에 대해서는 모델 미세조정을 위에서 언급한 방법으로 직접 할 수 있다. 그러나 질답과 원문함의와 같은 과제에서는 입력 형태가 문장의 2~3개 쌍인 등 많이 다르므로 이를 처리해주어야 한다. 그 방법은 아래 Figure 1에 나와 있는데, 질문/텍스트/선택지/가정/전제 등을 하나씩 따로 구분자(delimiter)로 구분하여 하나로 연결하는 방식을 쓴다. 구체적인 방법은 다음과 갈다.

Textual entailment

함의 문제에서는 전제 $p$와 가정 $h$를 구분자 $로 연결하였다.

Similarity

두 개의 텍스트 사이에 순서가 딱히 없으므로 텍스트 두 개를 다른 순서로 이어붙여 총 2개를 입력으로 쓴다. 이는 각각의 Transformer에 입력으로 들어간다.

Question Answering and Commonsense Reasoning

문맥 문서 $z$, 질문 $q$, 가능한 답변 ${a_k}$이라 하면, [z; q; $; a_k]로 연결하고 입력의 개수는 답변의 개수만큼 생성된다.

Architecture

4. 실험(Experiments)

4.1. Setup

Unsupervised pre-training

언어모델을 학습하기 위한 dataset으로 7천 개의 다양한 분야의 미출판 책에 대한 내용을 포함하는 BooksCorpus를 사용한다. 이는 특히 넓은 범위에 걸친 언어적 정보를 포함하기에 중요하다. 대안이 되는 dataset으로는 ELMo에서 사용된 1B Word Benchmark가 있다. 이는 크기는 비슷하지만 문장들이 서로 섞여 있어 장거리 의존정보가 파괴되어 있다.

사용하는 dataset 정보는 아래와 같다.

List of dataset

Model specifications

Transformer의 세부 세팅을 대부분 따르지만, Encoder-Decoder 중 Decoder만 사용한다. 이 decoder는 원본은 6번 반복되지만, GPT는 12번 반복한다.

Parameter Descrption
State dimension decoder: 768, inner state: 3072
Batch size 64 random sample $\times$ 512 token/sample
Schedule 100 epochs,
Optimizer Adam
Learning Rate 0~2000 step까지 2.5e-4까지 증가, 이후 cosine 함수를 따라 0으로 서서히 감소
warmup_steps 4000
Regularization L2($w=0.01$)
Activation GELU(Gaussian Error Linear Unit)

논문에는 안 나와있지만 모델의 크기는 parameter가 117M개이다.

Fine-tuning details

비지도 사전학습에서 사용한 hyperparameter를 그대로 사용했다. $p=0.1$의 dropout을 추가했다.
learning rate는 6.25e-5와 batch size는 32, 미세조정은 3 epoch 동안 진행되었으며 learning rate decay는 warmup을 포함해 각 학습당 0.2%였고, $\lambda=0.5$이다.

4.2. Supervised fine-tuning

자연어추론, 질답, 의미유사성, 문서분류에 대해 평가를 진행하였으며 이 중 일부는 GLUE benchmark에 포함되어 있다. 결과는 아래 Table 2, 3에 있다.

Natural Language Inference

Image caption(SNLI), 문서화된 음성, 대중소설, 정부 보고서(MNLI), 위키피디아 기사(QNLI), 과학시험(SciTail), 뉴스기사(RTE) 등의 다양한 데이터로 실험하였다. 각 0.6~5.8% 정도 성능이 향상되었다.

Results

Question answering and commonsense reasoning

중고등학교 시험에서 나온 영어지문과 관련 질문으로 구성된 RACE dataset으로 진행하였다. 또 Story Cloze에 대해서도 진행했는데 이는 무려 8.9%까지 높은 성능을 내며 결과를 종합했을 때 GPT가 넓은 범위에 걸친 문맥 정보도 잘 포착해냄을 알 수 있다.

Results

Semantic Similarity

QQP에 대해서는 BiLSTM + ELMo + Attention을 사용한 모델보다도 특히 성능이 향상되었다.

Results

Classification

두 개의 다른 텍스트 분류 과제에 대해서도 평가를 진행했다. CoLA(The Corpus of Linguistic Acceptability)는 어떤 문장이 문법적으로 옳은지를 전문가가 평가한 답변과, 학습된 모델에 대한 언어적 편향에 대한 테스트를 포함한다. SST-2(The Stanford Sentiment Treebank)는 표준 이진 분류 문제이다. CoLA에 대해서는 35.0 $\to$ 45.4점으로, SST-2에서는 68.9 $\to$ 72.8점으로 상승하였다.

종합하면, 12개의 task 중 9개에서 state-of-the-art를 달성하였다.


5. 분석(Analysis)

Impact of number of layers transferred

아래 Figure 2의 왼쪽은 layer의 수를 다르게 하면서 RACE와 MultiNLI에 대해 실험을 진행한 것인데, transferring embedding이 성능 향상을 가져오며, 각 transformer layer 당 9%까지 향상시킨다(on MultiNLI)는 내용이다. 이는 사전학습된 모델의 각각의 layer가 문제를 푸는 데 있어 유용한 기능을 포함한다는 것을 의미한다.

Impact of number of layers transferred

Zero-shot Behaviors

저자는 근본적인 generative model이 LM capability를 향상시키기 위해 많은 task를 수행하는 법을 배울 수 있고, LSTM과 비교해서 transformer의 attentional memory가 transfer에 도움이 된다고 가정하였다 Transformer를 통한 언어모델의 사전학습이 효과적인지에 대한 가정이 하나 있다. 기반 생성모델은 언어모델링 역량을 향상시키기 위해 평가에 포함된 여러 과제를 수행하는 것을 학습하였으며, Transformer의 attentional memory는 LSTM에 비해 전이를 더 원활하게 해 준다는 것이다. 지도 미세조정 없이 과제를 수행하기 위해 기반 생성모델을 사용하는 일련의 체험적 해결책(heuristic solutions)을 사용했다. 이 결과를 위 Figure 2의 오른쪽 부분에 시각화하였다.
이 체험적 해결책의 성능은 안정적이며 학습에 따라 꾸준히 증가하는 것으로 보아 생성적 사전학습은 과제와 관련된 넓은 범위의 기능성(functionality)의 학습을 뒷받침한다. 또한 LSTM은 zero-shot 성능에서 큰 편차를 보여 Transformer 구조의 귀납적 편향이 전이를 돕는다고 할 수 있다.

Ablation studies

세 가지 분석을 수행하였다.

  1. 미세조정 단계에서 보조 LM 목적함수는 NLI task와 QQP에 도움을 주는데, 큰 dataset에서는 이점이 있지만 작은 dataset에서는 그렇지 못함을 보여준다.
  2. Transformer을 같은 구조의 2048 unit의 LSTM로 대체하였는데 5.6점의 점수 하락이 있었다. 성능이 좋은 경우는 MRPC 뿐이었다.
  3. Transformer를 사전학습 없이 바로 지도학습을 하도록 해보았는데, 14.8%의 성능 하락이 있었다.
Ablation studies

6. 결론(Conclusion)

생성적 사전학습과 특정과제에 특화된 미세조정을 통해 학습된, 과제에 대해 별다른 지식이 없으며 자연어이해 능력이 뛰어난 단일 모델(framework)를 소개하였다. 넓은 범위에 걸친 언어적 정보를 포함하는 다양한 말뭉치에 대해 사전학습을 진행하여 중요한 일반지식과 질답, 의미유사성 평가, 함의 확인, 문서분류 등의 과제에서 성공적으로 전이되는 장거리 의존성을 처리하는 능력을 학습하여 12개 중 9개의 과제에 대해 state-of-the-art를 달성하였다. 특정 과제에 대한 성능을 높이는 비지도 사전학습은 기계학습연구의 중요한 목표가 되었다.
이 연구는 상당한 성능향상이 정말로 가능하며 어떤 모델(Transformers)과 dataset(장거리 의존성을 포함하는 텍스트)가 이 접근법에 가장 좋은지에 대한 조언을 제공한다. 이 연구가 자연어이해와 다른 분야에 대한 비지도학습에 대한 새로운 연구에 도움이 되기를 희망하며, 나아기 비지도학습이 언제 그리고 어떻게 작동하는지에 대한 우리의 이해를 증진시키기를 바란다.


Refenrences

논문 참조. 71개의 레퍼런스가 있다.

부록은 없다(yeah).


Comment  Read more