립시츠 함수를 다루는 이유는 아래 BatchNormalization 내용을 다룬 논문 때문입니다.

How Does Batch Normalization Help Optimization?

기존 BN 논문에서는 'BN은 Internal Covariance(내부 공변량 변화, 가끔 가다보면 내부 공선성이라고 표현하기도 하더라, 여기서는 더 많이 사용되는 내부 공변량 변화를 사용하겠습니다)을 해결하기 때문에 잘된다.'라고 주장합니다. 위 논문은 이에 반례를 제공하고, 한 단계 나아가 BN이 왜 잘되는지 수학적으로 증명하고 있으니 이를 조금씩 살펴보겠습니다.
(논문 소개와 립시츠 함수에 관한 글)

이번 글에서 소개할 논문

Internal Covariance

먼저 내부 공변량 변화를 쉽게 설명하자면, 우리가 흔히 사용하는 hidden layer는 당연하게도 input을 받아들이고 output을 내보냅니다. 또, 이렇게 도출된 output은 다음 단계에서 hidden layer의 input으로 활용되죠. 문제는 여기에 있습니다. 이전 층의 불규칙한 output으로 인해 다음 층의 input distribution이 급격히 변화하고, 이러한 문제가 최종적으로 학습에 큰 영향을 끼치게되어 좋지 않은 성능을 가져다 주는 문제입니다.

즉, 사공이 많으면 배가 산으로 가듯이 기존 입력값의 분포가 여러 가지 모양을 가지게 되니 해결하고자 하는 데이터의 분포를 학습할 수도 없고, 모델 입장에서는 혼란을 가지게 되어 우리에게 안좋은 성능을 가져다줍니다. 하지만 BN은 충분히 다양한 모델 구조에서도 자신의 성능을 증명했듯이, 이러한 문제를 해결할 수 있다고 합니다. BN은 다양한 장점을 가지고 있지만 그 중, 규제화(Regularizer) 기능을 이용하면, 이러한 문제가 해결되어 좋은 성능을 가지게 해준다고 합니다. 마치, hidden layer의 출력값에 표준화를 적용해준다는 것과 비슷합니다.

그림: BatchNormalization Paper, Algorithm1

신경망의 입력으로 사용할 데이터에 표준화를 적용하는 것과 그렇지 않은 것의 성능 차이는 여러 가지 데이터셋에서도 확인해 볼 수 있습니다. 이를 알면 위에서 말한 장점처럼 수학적으로 자세히 증명하지 않아도, BN이 좋은 성능을 이끌어내고 있다고 느낌적으로 생각해볼 수 있습니다.

 

Lipschitz Function

(위키 백과) 립시츠 함수는 두 점 사이의 거리를 일정 비 이상으로 증가시키지 않는 함수이다.
검색해보면 흔히 볼 수 있는 수식은 아래와 같다.

$$\left| f(x) - f(y)\over x - y\right| <= K$$

립시츠 조건이 성립하면 균등연속함수(역은 필수는 아님)이므로 해당 범위에서 미분이 가능하며, 미분 계수는 정해진 K 값을 넘을 수 없게 됩니다. 즉, 쉽게 해석해보면 립시츠 함수일때 기울기가 K를 넘지 않으므로 gradient exploding과 같은 문제를 예방할 수 있습니다. 이 점만 기억하면 논문의 주장을 충분히 이해할 수 있습니다.

 

BatchNormalization은 내부 공변량 변화랑 정말 관계가 있을까?

위에서 설명했듯이 BN은 내부 공변량 변화 문제를 해결한다고 했습니다. 이를 반증하기 위해 논문에서는 한 가지 실험을 수행합니다.

기존처럼 BN을 쓴 것과 여기에 Noise를 추가한 경우를 실험합니다. 노이즈를 추가하는 이유는 일부러 내부 공변량 변화를 발생시키기 위함입니다. 빨간색 분포를 보면 Layer를 거칠수록 파란색, 주황색 분포보다 더욱 불안정함을 알 수 있습니다. 하지만 성능을 확인해보면 내부 공변량 변화 존재 유무를 떠나서 거의 비슷하거나 또는 Noise를 추가한 모델이 더 좋은 성능을 보여주고 있다는 것을 볼 수 있습니다.

여기서 바로 BN이 주고 있는 성능 향상의 원인이 내부 공변량 변화에 크게 치우쳐져 있지 않음을 알 수 있습니다.

심지어 BN을 쓴다고 해서 내부 공변량 변화가 해결되지 않고, 완전히 심각한 문제를 일으키는 경우도 있습니다.

 

BatchNormalization은 그럼 무슨 역할을 하고 있을까?

이후에도 논문에서는 BN이 내부 공변량 변화와 약한 관계를 가지고 있음을 실험적으로 증명합니다. 그럼에도 불구하고 BN을 사용하면 성능 향상이 일어날 뿐더러 training 과정이 BN을 사용하지 않은 경우보다 더욱 stable함을 보여주고 있다는 것이죠.

왜 잘될까요? 논문에서는 BN을 optimization 관점으로 바라보아야 한다고 주장합니다. 일반적으로 신경망이 가지는 land-scape는 non-convex하고, unstable하기 때문에 loss function을 최적화하는데 문제점을 가지고 있습니다. 따라서 이 논문은 "BN이 내부 공변량 변화를 해결한다기 보다는 이러한 문제점을 해결해주고 있는 것이 아닐까?" 라고 생각합니다.

그럼 이 논문에서 BN을 통해 밝힌 것은 다음과 같습니다.

  1. optimization problem의 landscape를 더욱 smooth하게 만들어줍니다.
  2. Loss function이 립시츠해져서 loss의 gradient가 더욱 smooth해집니다. 이러한 결과는 향후 학습에서 gradient가 더욱 reliable해지고, predictive하게 만든다고 합니다.
  3. predictive가 어느정도 보완되기 때문에 gradient based optimization problem에서도 다음 스텝을 밟는데 있어 어느정도 위험을 배제할 수 있다.

2번 실험에 관한 내용은 다음 figure에서 확인할 수 있습니다.

 

자세한 수학적 증명은 논문을 통해 확인하시길 바라고, 그 중 하나를 보자면,,

아래 그림에서 오른쪽의 $\gamma^2 \over \sigma^2$가 립시츠 함수에서 상수 역할을 하기때문에 왼쪽 항의 Loss function에서 발생하는 gradient가 smooth 될 수 있다고 합니다.

 

Reference

www.slideshare.net/HoseongLee6/how-does-batch-normalization-help-optimization-paper-review

ko.wikipedia.org/wiki/립시츠_연속_함수

www.youtube.com/watch?v=hiN0IMM50FM&list=PLWKf9beHi3TgstcIn8K6dI_85_ppAxzB8&index=34

이 글은 keras 공식 문서의 'Few-Shot learning with Reptile' 글을 번역한 것입니다.


Introduction

Reptile 알고리즘은 Meta-Learning을 위해 open-AI가 개발한 알고리즘입니다. 특히, 이 알고리즘은 최소한의 training으로 새로운 task를 수행하기 위한 학습을 빠르게 진행하기 위해 고안되었습니다(Few-Shot learning). Few-shot learning의 목적은 소수의 데이터만 학습한 모델이 보지 못한 새로운 데이터를 접했을 때, 이를 분류하게 하는 것입니다. 예를 들어, 희귀병 진단이나 레이블 cost가 높은 경우에 유용한 방법이라고 할 수 있습니다. 즉, 해결해야 될 문제에서 데이터가 적을때 충분히 고려해볼만한 방법입니다.

알고리즘은 이전에 보지 못한 데이터의 mini-batch로 학습된 가중치와 고정된 meta-iteration을 통해 사전 학습된 모델 가중치 사이의 차이를 최적화하는 SGD와 함께 작동합니다. 즉, n번 만큼의 횟수에서 mini-batch로 학습하여 얻은 가중치와 모델 weight initialization 차이를 최소하하면서 optimal weight를 update하게 됩니다.
(동일 클래스 이미지에 계속해서 다른 레이블로 학습하는데 어떻게 정답을 맞추는지 정말 신기합니다. 이 부분을 알려면 더 깊게 공부해봐야 알듯...)

필요 라이브러리를 임포트합니다.

import matplotlib.pyplot as plt
import numpy as np
import random
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
import tensorflow_datasets as tfds

하이퍼파라미터 정의

learning_rate = 0.003 # 학습률
meta_step_size = 0.25 # 메타 스템 크기

inner_batch_size = 25 # 학습 배치 크기
eval_batch_size = 25 # 테스트 배치 크기

meta_iters = 2000 # 학습 총 횟수
eval_iters = 5 # 평가 데이터셋 반복 횟수
inner_iters = 4 # 학습 데이터셋 반복 횟수

eval_interval = 1 # 평가 기간 설정
train_shots = 20 # 학습에서 각 클래스마다 몇 개 샘플을 가져올지
shots = 5 # 평가에서 각 클래스마다 몇 개 샘플을 가져올지
classes = 5 # 몇 개 클래스를 사용할지

여기서는 5-way 5-shot이라고 표현할 수 있겠네요(N-way, k-shot)
--> 5개 클래스에서 5장 뽑아서 이를 학습에 사용하겠다는 의미

 

데이터 준비

Omniglot 데이터셋은 각 character별로 20개 샘플을 가지고 있는 50가지 알파벳(즉, 50개의 다른 언어)으로부터 얻어진 1,623개 데이터로 이루어져 있습니다(서로 다른 나라의 문자에서 특정 클래스들을 모아둔 데이터셋입니다). 여기서 20개 샘플은 Amazon's Mechanical Turk으로 그려졌습니다.

few-shot learning task를 수행하기 위해 k개 샘플은 무작위로 선택된 n개 클래스로부터 그려진 것을 사용합니다. n개 숫자값은 몇 가지 예에서 새 작업을 학습하는 모델 능력을 테스트하는 데 사용할 임시 레이블 집합을 만드는 데 사용됩니다. 예를 들어, 5개 클래스를 사용한다고 하면, 새로운 클래스 레이블은 0, 1, 2, 3, 4가 될 것입니다.

핵심은 모델이 계속 반복해서 class instance를 보게 되지만, 해당 instance에 대해서는 계속해서 서로 다른 레이블을 보게 됩니다. 이러한 과정에서 모델은 단순하게 클래스를 분류하는 방법이 아니라 해당 데이터를 특정 클래스로 구별하는 방법을 배워야 합니다.

Omniglot 데이터셋은 다양한 클래스에서 만족할만한 개수의 샘플을 가져다 사용할 수 있기 때문에 이번 task에 적합합니다.

class Dataset:
    # 이 클래스는 few-shot dataset을 만듭니다.
    # 새로운 레이블을 만듬과 동시에 Omniglot 데이터셋에서 샘플을 뽑습니다.
    def __init__(self, training):
        # omniglot data를 포함한 tfrecord file을 다운로드하고 dataset으로 변환합니다.
        split = "train" if training else "test"
        ds = tfds.load("omniglot", split=split, as_supervised=True, shuffle_files=False)
        # dataset을 순환하면서 이미지와 클래스를 data dictionary에 담을겁니다.
        self.data = {}

        def extraction(image, label):
            # RGB에서 grayscale로 변환하고, resize를 수행합니다.
            image = tf.image.convert_image_dtype(image, tf.float32)
            image = tf.image.rgb_to_grayscale(image)
            image = tf.image.resize(image, [28, 28])
            return image, label

        for image, label in ds.map(extraction):
            image = image.numpy()
            label = str(label.numpy())
            # 레이블이 존재하지 않으면 data dictionary에 넣고,
            if label not in self.data:
                self.data[label] = []
            # 존재하면 해당 레이블에 붙여줍니다.
            self.data[label].append(image)
            # 레이블을 저장합니다.
            self.labels = list(self.data.keys())

    def get_mini_dataset(self, batch_size, repetitions, shots, num_classes, split=False):
        # num_classes * shots 수 만큼 이미지와 레이블을 가져올 것입니다.
        temp_labels = np.zeros(shape=(num_classes * shots))
        temp_images = np.zeros(shape=(num_classes * shots, 28, 28, 1))
        if split:
            test_labels = np.zeros(shape=(num_classes))
            test_images = np.zeros(shape=(num_classes, 28, 28, 1))
          
        # 전체 레이블 셋에서 랜덤하게 몇 개의 레이블만 가져옵니다.
        # self.labels는 omniglot에서 가져온 데이터의 전체 레이블을 담고 있습니다.
        # 전체 레이블에서 num_classes만큼 label을 랜덤하게 뽑습니다.
        label_subset = random.choices(self.labels, k=num_classes)
        for class_idx, _ in enumerate(label_subset):
            # few-shot learning mini-batch를 위한 현재 레이블값으로 enumerate index를 사용합니다.
            # temp_label에서는 shots 수만큼 class_idx를 가지게 됩니다.[0, 0, 0, 0, 0]
            # 해당되는 이미지의 레이블은 계속해서 변하는 점을 참고
            temp_labels[class_idx * shots : (class_idx + 1) * shots] = class_idx

            # 테스트를 위한 데이터셋을 만든다면, 선택된 sample과 label을 넣어줍니다.
            # 테스트 과정에서 temp_label과 test_label은 동일해야 합니다.
            if split:
                test_labels[class_idx] = class_idx
                images_to_split = random.choices(self.data[label_subset[class_idx]], 
                                                 k=shots + 1)
                test_images[class_idx] = images_to_split[-1]
                temp_images[class_idx * shots : (class_idx + 1) * shots] = images_to_split[:-1]
            else:
                # label_subset[class_idx]에 해당하는 이미지를 shots만큼 temp_images에 담습니다.
                # 이로써 temp_images는 label_subset[class_idx] 레이블에 관한 이미지를 가지고 있지만,
                # temp_labels는 이에 상관없이 class_idx 클래스를 가집니다.
                temp_images[
                    class_idx * shots : (class_idx + 1) * shots
                ] = random.choices(self.data[label_subset[class_idx]], k=shots)

        # TensorFlow Dataset을 만듭니다.
        dataset = tf.data.Dataset.from_tensor_slices(
            (temp_images.astype(np.float32), temp_labels.astype(np.int32))
        )
        dataset = dataset.shuffle(100).batch(batch_size).repeat(repetitions)

        if split:
            return dataset, test_images, test_labels
            
        return dataset


import urllib3

urllib3.disable_warnings()  # Disable SSL warnings that may happen during download.
train_dataset = Dataset(training=True)
test_dataset = Dataset(training=False)

 

모델 구성

def conv_bn(x):
    x = layers.Conv2D(filters=64, kernel_size=3, strides=2, padding="same")(x)
    x = layers.BatchNormalization()(x)
    return layers.ReLU()(x)


inputs = layers.Input(shape=(28, 28, 1))
x = conv_bn(inputs)
x = conv_bn(x)
x = conv_bn(x)
x = conv_bn(x)
x = layers.Flatten()(x)
outputs = layers.Dense(classes, activation="softmax")(x)

model = keras.Model(inputs=inputs, outputs=outputs)
model.compile()
optimizer = keras.optimizers.SGD(learning_rate=learning_rate)

 

모델 학습

training = []
testing = []
for meta_iter in range(meta_iters):
    frac_done = meta_iter / meta_iters
    cur_meta_step_size = (1 - frac_done) * meta_step_size
    # 현재 모델 weights를 저장합니다.
    old_vars = model.get_weights()
    # 전체 데이터셋에서 샘플을 가져옵니다.
    mini_dataset = train_dataset.get_mini_dataset(
        inner_batch_size, inner_iters, train_shots, classes
    )

    for images, labels in mini_dataset:
        with tf.GradientTape() as tape:
            preds = model(images)
            loss = keras.losses.sparse_categorical_crossentropy(labels, preds)

        grads = tape.gradient(loss, model.trainable_weights)
        optimizer.apply_gradients(zip(grads, model.trainable_weights))

    # 위의 loss로 업데이트된 weight를 가져옵니다.
    new_vars = model.get_weights()
    # meta step with SGD
    for var in range(len(new_vars)):
        new_vars[var] = old_vars[var] + (
            (new_vars[var] - old_vars[var]) * cur_meta_step_size
        )

    # meta-learning 단계를 수행했다면, 모델 가중치를 다시 업데이트해줍니다.
    model.set_weights(new_vars)

    # Evaluation loop
    if meta_iter % eval_interval == 0:
        accuracies = []
        for dataset in (train_dataset, test_dataset):
            # 전체 데이터셋에서 mini dataset을 가져옵니다.
            train_set, test_images, test_labels = dataset.get_mini_dataset(
                eval_batch_size, eval_iters, shots, classes, split=True
            )
            old_vars = model.get_weights()
            # Train on the samples and get the resulting accuracies.
            for images, labels in train_set:
                with tf.GradientTape() as tape:
                    preds = model(images)
                    loss = keras.losses.sparse_categorical_crossentropy(labels, preds)

                grads = tape.gradient(loss, model.trainable_weights)
                optimizer.apply_gradients(zip(grads, model.trainable_weights))
            
            test_preds = model.predict(test_images)
            test_preds = tf.argmax(test_preds).numpy()
            num_correct = (test_preds == test_labels).sum()

            # 평가 acc를 얻었다면 다시 weight를 초기화해야합니다.
            model.set_weights(old_vars)
            accuracies.append(num_correct / classes)

        training.append(accuracies[0])
        testing.append(accuracies[1])

        if meta_iter % 100 == 0:
            print(
                "batch %d: train=%f test=%f" % (meta_iter, accuracies[0], accuracies[1])
            )

 

결과 시각화

# First, some preprocessing to smooth the training and testing arrays for display.
window_length = 100
train_s = np.r_[
    training[window_length - 1 : 0 : -1], training, training[-1:-window_length:-1]
]
test_s = np.r_[
    testing[window_length - 1 : 0 : -1], testing, testing[-1:-window_length:-1]
]
w = np.hamming(window_length)
train_y = np.convolve(w / w.sum(), train_s, mode="valid")
test_y = np.convolve(w / w.sum(), test_s, mode="valid")

# 학습 acc를 그립니다.
x = np.arange(0, len(test_y), 1)
plt.plot(x, test_y, x, train_y)
plt.legend(["test", "train"])
plt.grid()

train_set, test_images, test_labels = dataset.get_mini_dataset(
    eval_batch_size, eval_iters, shots, classes, split=True
)

for images, labels in train_set:
    with tf.GradientTape() as tape:
        preds = model(images)
        loss = keras.losses.sparse_categorical_crossentropy(labels, preds)

    grads = tape.gradient(loss, model.trainable_weights)
    optimizer.apply_gradients(zip(grads, model.trainable_weights))

test_preds = model.predict(test_images)
test_preds = tf.argmax(test_preds).numpy()

_, axarr = plt.subplots(nrows=1, ncols=5, figsize=(20, 20))

for i, ax in zip(range(5), axarr):
    temp_image = np.stack((test_images[i, :, :, 0],) * 3, axis=2)
    temp_image *= 255
    temp_image = np.clip(temp_image, 0, 255).astype("uint8")
    ax.set_title(
        "Label : {}, Prediction : {}".format(int(test_labels[i]), test_preds[i])
    )
    ax.imshow(temp_image, cmap="gray")
    ax.xaxis.set_visible(False)
    ax.yaxis.set_visible(False)
plt.show()

 

Reference

Few-shot learning은 카카오 기술 블로그에도 설명되어 있습니다. >> 클릭

talkingaboutme.tistory.com/entry/DL-Meta-Learning-Learning-to-Learn-Fast

www.borealisai.com/en/blog/tutorial-2-few-shot-learning-and-meta-learning-i/

'# Machine Learning > TensorFlow doc 정리' 카테고리의 다른 글

tf.data tutorial 번역 (5)  (0) 2020.02.28
tf.data tutorial 번역 (4)  (1) 2020.02.27
tf.data tutorial 번역 (3)  (0) 2020.02.26
tf.data tutorial 번역 (2)  (0) 2020.02.18
tf.data tutorial 번역 (1)  (0) 2020.02.14

다양한 문제를 해결하고, 만들어내고 있는 GPT-3가 연일 화제가 되면서 인공지능 관련 모든 분야에서 굉장히 뜨거운 반응을 보이고 있습니다. NLP뿐만 아니라 Computer Vision task도 해결할 수 있음을 보였는데, 이러한 결과가 매우 매력적일 수밖에 없습니다. 물론 사용된 파라미터 수, 학습 시간 등을 고려해보면 개인적으로 어떻게 활용할 수 있을까에 대한 좌절감도 동시에 다가옵니다.

트렌드를 빠른 속도로 파악하고 제시하고 있는 Andrew Ng님도 Transformer, BERT, GPT-3가 매우 대단한 결과를 보이고 있음을 이야기하고, NLP 분야의 수요가 지속적으로 늘어날 것을 이야기할 정도입니다.

BERT에 관심이 있는, NLP에 관심이 있다면 반드시 밑의 세 가지 논문을 요약으로라도 읽어봐야 합니다.

이 중에서도 이번 글에서는 BERT를 직접 공부하면서 도움이 될만한 몇 가지 사항을 정리하였습니다. 이전에 연관 논문을 읽고 BERT라는 주제를 공부하면 매우 도움이 되겠지만, 순서가 뒤바뀌어도 문제 될게 없습니다.


BERT

Transformer architecture을 중점적으로 사용한 BERT는 Bidirectional Encoder Representations from Transformers을 의미합니다. 바로 BERT에서 살펴볼 주요한 사항을 살펴보겠습니다.

들어가기 전에, BERT는 text classification, answering 등을 해결할 수 있는 모델이지만, 이보다는 Language Representation을 해결하기 위해 고안된 구조라는 것을 알아주세요.
(즉, 단어, 문장, 언어를 어떻게 표현할까에 관한 고민이고, 이를 잘 표현할 수 있다면 다양한 task를 해결할 수 있습니다.)

 

BERT는 양방향성을 포함하여 문맥을 더욱 자연스럽게 파악할 수 있습니다.

Bidirectional은 직역하는 것과 동일하게 BERT 모델이 양방향성 특성을 가지고 이를 적용할 수 있다는 것을 의미합니다.
이를 사용하기 이전의 대부분 모델은 문장이 존재하면 왼쪽에서 오른쪽으로 진행하여 문맥(context)를 파악하는 방법을 지녔는데요. 이는 전체 문장을 파악하는 데 있어서 당연히 제한될 수 밖에 없는 한계점입니다.

쉽게 생각해보면, '나는 하늘이 예쁘다고 생각한다'라는 문장을 이해할 때, 단순히 '하늘'이라는 명사를 정해놓고 '예쁘다'라는 표현을 사용하지는 않습니다. '예쁘다'를 표현하고 싶어서 '하늘'이라는 명사를 선택했을 수도 있습니다. 즉, 앞에서 뒤를 볼수도 있지만, 뒤에서 앞을 보는 경우도 충분히 이해할 수 있어야 전체 맥락을 완전히 파악할 수 있다는 것이죠.

단방향성, 양방향성

이 예가 아니더라도 동의어를 파악하기 위해선 앞뒤 단어의 관계성을 파악해야 한다는 점을 이해할 수 있습니다.

-> 긍정적인 의미의 잘했다와 부정적인 의미의 잘했다.
ex1) A: "나 오늘 주식 올랐어!", B: "진짜 잘했다"
ex2) A: "나 오늘 주식으로 망했어", B: "진짜 잘~했다"

결론적으로는 기존 단방향성 모델은 성능 향상, 문맥 파악에 한계점이 존재했었고, 이를 해결하기 위해 양방향성을 띄는 모델을 제안하는 방향으로 진행된 것입니다. (RNN과 Bidirectional RNN의 차이점을 살펴도 같은 문제를 해결하기 위한 것을 알 수 있습니다)

 

BERT는 pre-training이 가능한 모델입니다.

이전에 존재하던 NLP 모델은 pre-training이 어려웠기 때문에 특정 task가 존재할 경우 처음부터 학습시켜야 하는 단점이 존재했습니다. 각종 Image task를 해결하는 데 있어서도 pre-trianing이 매우 큰 역할을 하고 있다는 점을 보면, NLP에서도 여러 task에 transfer하기 위해서는 이를 활용할 수 있는 모델이 매우 시급했습니다.

이에 대한 연구가 매우 활발히 진행되었고, 대표적으로 ELMO, XLNet, BERT, GPT 등 모델이 만들어지게 되었죠.

엄청난 수의 Wikipedia와 BooksCorpus 단어를 학습한 BERT를 다양한 task에 적용하고, fine-tuning해서 사용할 수 있는 것은 매우 큰 장점으로 우리에게 다가올 수 밖에 없습니다.

 

BERT는 주로 어떤 문제에 적용할 수 있을까요?

대표적으로 해결할 수 있는 몇 가지 문제를 알려드리겠습니다. 하지만 여기서 설명되지 않은 다양한 분야가 매우 다양하게 존재하다는 것을 잊어버리면 안됩니다. 아래 문제들은 대부분 NLP task에서 볼 수 있는 대표적인 문제들입니다.

  1. Question and Answering
    - 주어진 질문에 적합하게 대답해야하는 매우 대표적인 문제입니다.
    - KoSQuAD, Visual QA etc.
  2. Machine Translation
    - 구글 번역기, 네이버 파파고입니다.
  3. 문장 주제 찾기 또는 분류하기
    - 역시나 기존 NLP에서도 해결할 수 있는 문제는 당연히 해결할 수 있습니다.
  4. 사람처럼 대화하기
    - 이와 같은 주제에선 매우 강력함을 보여줍니다.
  5. 이외에도 직접 정의한 다양한 문제에도 적용 가능합니다. 물론 꼭 NLP task일 필요는 없습니다.

 

어떻게 학습되었는지 알아보자

기본적으로 BERT는 'Attention is all you need" 논문에서 볼 수 있는 Transformer 구조를 중점적으로 사용한 구조입니다. 특히
self-attiotion layer를 여러 개 사용하여 문장에 포함되어 있는 token 사이의 의미 관계를 잘 추출할 수 있습니다.

BERT는 transformer 구조를 사용하면서도 encoder 부분만 사용(아래 그림에서 왼쪽 부분)하여 학습을 진행했는데요. 기존 모델은 대부분 encoder-decoder으로 이루어져 있으며, GPT 또한, decoder 부분을 사용하여 text generation 문제를 해결하는 모델입니다. Transformer 구조 역시, input에서 text의 표현을 학습하고, decoder에서 우리가 원하는 task의 결과물을 만드는 방식으로 학습이 진행됩니다.

Attention mechanism - Attention Paper

BERT는 decoder를 사용하지 않고, 두 가지 대표적인 학습 방법으로 encoder를 학습시킨 후에 특정 task의 fine-tuning을 활용하여 결과물을 얻는 방법으로 사용됩니다.

 

어떻게 학습되었는지 알아보자 - input Representation

역시나 어떤 주제이던 데이터 처리에 관한 이야기는 빠질 수가 없습니다. BERT는 학습을 위해 기존 transformer의 input 구조를 사용하면서도 추가로 변형하여 사용합니다. Tokenization은 WorldPiece 방법을 사용하고 있습니다.

Input Representation - BERT paper

위 그림처럼 세 가지 임베딩(Token, Segment, Position)을 사용해서 문장을 표현합니다.

먼저 Token Embedding에서는 두 가지 특수 토큰(CLS, SEP)을 사용하여 문장을 구별하게 되는데요. Special Classification token(CLS)은 모든 문장의 가장 첫 번째(문장의 시작) 토큰으로 삽입됩니다. 이 토큰은 Classification task에서는 사용되지만, 그렇지 않을 경우엔 무시됩니다. 
또, Special Separator token(SEP)을 사용하여 첫 번째 문장과 두 번째 문장을 구별합니다. 여기에 segment Embedding을 더해서 앞뒤 문장을 더욱 쉽게 구별할 수 있도록 도와줍니다. 이 토큰은 각 문장의 끝에 삽입됩니다.

Position Embedding은 transformer 구조에서도 사용된 방법으로 그림고 같이 각 토큰의 위치를 알려주는 임베딩입니다.
최종적으로 세 가지 임베딩을 더한 임베딩을 input으로 사용하게 됩니다.

 

어떻게 학습되었는지 알아보자 - Pre-training

 BERT는 문장 표현을 학습하기 위해 두 가지 unsupervised 방법을 사용합니다.

  1. Masked Language Model
  2. Next Sentence Model

 

Masked Language Model (MLM)

문장에서 단어 중의 일부를 [Mask] 토큰으로 바꾼 뒤, 가려진 단어를 예측하도록 학습합니다. 이 과정에서 BERT는 문맥을 파악하는 능력을 기르게 됩니다.

ex) 나는 하늘이 예쁘다고 생각한다 -> 나는 하늘이 [Mask] 생각한다.
ex) 나는 하늘이 예쁘다고 생각한다 -> 나는 하늘이 흐리다고 생각한다.
ex) 나는 하늘이 예쁘다고 생각한다 -> 나는 하늘이 예쁘다고 생각한다.

추가적으로 더욱 다양한 표현을 학습할 수 있도록 80%는 [Mask] 토큰으로 바꾸어 학습하지만, 나머지 10%는 token을 random word로 바꾸고, 마지막 10%는 원본 word 그대로를 사용하게 됩니다.

Next Sentence Prediction (NSP)

다음 문장이 올바른 문장인지 맞추는 문제입니다. 이 문제를 통해 두 문장 사이의 관계를 학습하게 됩니다. 

문장 A와 B를 이어 붙이는데, B는 50% 확률로 관련 있는 문장(IsNext label) 또는 관련 없는 문장(NotNext label)을 사용합니다.
QA(Question Answering)나 NLI(Natural Language Inference) task의 성능 향상에 영향을 끼친다고 합니다.

 

이런 방식으로 학습된 BERT를 fine-tuning할 때는 (Classification task라면)Image task에서의 fine-tuning과 비슷하게 class label 개수만큼의 output을 가지는 Dense Layer를 붙여서 사용합니다.

 

Reference

pydicom을 쓸때 나타나는 에러 같음.

다음 코드를 실행하면 해결됨

conda install -c conda-forge gdcm

 


import sys
from collections import deque

# input = sys.stdin.readline

M, N, H = map(int, input().split())

floors = []

for _ in range(H):
    floor = []
    for _ in range(N):
        floor.append(list(map(int, input().split())))
        
    floors.append(floor)
    
queue = deque()

for i in range(H):
    for j in range(N):
        for k in range(M):
            if(floors[i][j][k] == 1):
                queue.append([i, j, k])

# 이전 문제랑 다른 점은 높이가 추가됨
dx = [1, -1, 0, 0, 0, 0]
dy = [0, 0, 1, -1, 0, 0]
dh = [0, 0, 0, 0, 1, -1]

while queue:
    height, row, col = queue.popleft()
    
    for k in range(6):
        _height = height + dh[k]
        _row = row + dy[k]
        _col = col + dx[k]
        
        if 0 <= _height < H and 0 <= _row < N and 0 <= _col < M and floors[_height][_row][_col] == 0:
            queue.append([_height, _row, _col])
            floors[_height][_row][_col] = floors[height][row][col] + 1
            
check_tot = False
result = -2

for i in floors:
    for j in i:
        for k in j:
            if(k == 0):
                check_tot = True
            result = max(result, k)
            
if check_tot:
    print(-1)
elif(result == -1):
    print(0)
else:
    print(result - 1)

'# 코딩 문제 관련 > 파이썬' 카테고리의 다른 글

[HackerRank-python] Apple and Orange  (0) 2022.06.29
[HackerRank-python] Grading Students  (0) 2022.06.29
백준 7576번(python)  (0) 2020.08.02
백준 2606번(python)  (0) 2020.08.01
백준 2805번(python)  (0) 2020.07.27