이 포스팅은 『밑바닥부터 시작하는 딥러닝』을 기반으로 작성되었습니다. 간단한 이론이지만 누군가에게 설명할 수 있는가에 대해 생각한 결과, 올바르게 설명하지 못한다고 판단되어 이를 쇄신하고자 하는 마음으로 작성합니다. 

 

1. 신경망의 구조 (Structure of Neural Network)

신경망(neural network)의 구조는 아래와 같이 심플하게 입력 레이어(Input layer), 은닉 레이어(hidden layer), 출력 레이어(output layer)로 총 3개의 레이어(layer)로 구성된다. 

신경망 구조 (Structure of Neural Network)

위 신경망의 구조는 레이어가 3개이므로 3층 레이어라고 표현하지만, 정확히는 입력 레이어와 은닉 레이어 사이, 은닉 레이어와 출력 레이어 사이에 있는 가중치를 기준으로 하기 때문에 2층 레이어라 표현하는 것이 일반적이다. 입력 레이어와 출력 레이어는 하나씩 존재한다. 하지만 은닉 레이어를 여러 개 쌓음으로써 신경망의 층을 깊게(크게) 만들 수 있게 된다.

 

2. 활성화 함수 (Activation Function)

위 그림에서 보는 신경망은 퍼셉트론(Perceptron)과 어떻게 다를까? 본질적으로 신경망과 퍼셉트론은 구조적으로 동일한 형태이다. 하지만 핵심 차이점은 활성화 함수의 차이이다. 그렇다면 활성화 함수란 무엇일까? 아래 그림을 확인해보자.

위 그림에서 활성화 함수는 $h()$이다. $h()$의 역할은 입력 신호(x1, x2)와 가중치 (w1, w2)를 곱한 것과 편항(b)의 합인 $a$를 기준으로 활성화 할지(1) 활성화 하지 않을지(0) 여부를 결정하는 함수이다.

다시 말해 간단하게 퍼셉트론을 구성했던 수식은 1)과 같다. 이 때 활성화 함수는 입력 신호와 가중치의 곱 그리고 편향의 합을 입력으로 하는 수식 2)가 된다. 수식 2)의 활성화 함수 $h()$를 통해 신호의 합이 활성화(1) 할지 활성화 하지 않을지(0) 표현하는 함수이다.

 

$1)\ y = \begin{cases} 0\ (b + x_1w_1 +x_2w_2\leq\theta \\ 1\ (b+x_1w_2+x_2w_2\gt\theta)\end{cases}$

 

$2)\ y = h(b + w_1x_1+w_2x_2)$

 

$3)\ h(x) = \begin{cases} 0\ (x\leq0) \\ 1\ (x\gt0)\end{cases}$

 

퍼셉트론은 위와 같이 활성화 함수로 계단형 함수를 사용했다. 계단형 함수란 값이 연속적(continuous)이지 않고 이산적(discrete)인 특징을 갖는 함수를 말한다. 예컨데 계단 함수를 코드로 구현한 것과 그래프로 나타낸 것은 다음과 같다.

 

2.1 계단 함수 구현과 그래프 (Step Function & Graph)

import numpy as np
import matplotlib.pyplot as plt

def step_function(x):
	return np.array(x > 0, dtype=np.int)

x = np.arange(-5.0, 5.0, 0.1)
y = step_function(x)
plt.plot(x, y)
plt.ylim(-0.1, 1.1)
plt.show()

간단히 다시 말해 계단 함수는 x가 0보다 크면 활성화(1) 작으면 비활성화(0)를 하는 것이다. 계단 함수는 x=0을 기준으로 하여 비활성화(0) 활성화(1)가 뚜렷하게 나뉜다.

계단 함수 (step function) 그래프

퍼셉트론은 이러한 계단 함수를 사용하는 데, 이러한 계단 함수의 단점은 비연속적/이산적이기 때문에 “매끄러움”을 갖지 못한다는 것에 있다. 따라서 이 퍼셉트론을 신경망으로 동작해줄 수 있게 하기 위해서는 비연속적인 활성화 함수가 아닌 연속적인 활성화 함수를 사용해야 한다. 즉 다른 말로 비선형 활성화 함수를 사용해야 하는 것이다. 대표적인 비선형 활성화 함수에는 시그모이드(Sigmoid) 함수가 있다.

 

2.2 시그모이드 함수 구현과 그래프 (Sigmoid Function & Graph)

시그모이드(Sigmoid) 함수란 S자 형태의 띠는 함수를 의미한다. 신경망(뉴럴넷)에 사용되는 대표적인 비선형 활성화 함수로 간단하게 코드로 구현한 것과 함수의 그래프는 다음과 같다.

import numpy as np
import matplotlib.pyplot as plt

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

x = np.arange(-5.0, 5.0, 0.1)
y = sigmoid(x)
plt.plot(x, y)
plt.ylim(-0.1, 1.1)
plt.show()

간단하게 코드를 설명하면 시그모이드 함수는 내부적으로 자연상수 $e(=2.7182...)$를 사용한다. 자연상수를 기반으로한 시그모이드 함수는 계단 함수와 마찬가지로 동일한 입력 신호 $x$를 입력으로 준 뒤 그래프로 나타내면 아래와 같다.

시그모이드 함수 (sigmoid function) 그래프

계단 함수와 비교해 연속적이고 매끄러운 형태를 갖는 다는 것을 확인할 수 있다. 앞서 설명했듯 퍼셉트론과 신경망의 핵심 차이는 활성화 함수라고 했다. 퍼셉트론의 경우 이산적인 정수를 출력(0, 1)하는 반면 신경망의 경우 연속적인 실수를 출력(0.1, 0.2, 0.3, ...) 한다. 그렇다면 왜 매끈한 형태를 갖는 비선형 함수가 신경망과 퍼셉트론의 차이를 결정짓는 것일까?

 

그 이유는 이산적인 특징을 갖는 선형 함수를 사용하게 된다면 층을 깊게 하는 것에 의미가 없기 때문이다. 예를 들면 선형 함수 $h(x) = cx$가 있다고 가정할 경우 3층으로 쌓으면 $y(x) = h(h(h(x)))$가 된다. 하지만 이 계산은 $y(x) = ax$와 동일한 식이다. 단순히 $a=c^3$이라고 치환하면 끝인 것이다. 즉, 은닉층 없는 네트워크로 표현이 가능하게 된다. 이와 같은 예시처럼 선형 함수를 이용하면 여러 층으로 구성하는 신경망의 이점을 살릴 수 없다. 따라서 층을 쌓는 이점을 얻기 위해서는 활성화 함수를 반드시 비선형 함수를 사용해야 한다.

 

그렇다면 또 다른 비선형 함수들의 종류와 특징들은 어떤 것들이 있을까? 추가적으로 더 알아보자.

 

2.3 ReLU 함수 구현과 그래프 (ReLU Function & Graph)

시그모이드 함수는 신경망에서 자주 사용되었으나 최근에는 시그모이드 대신 ReLU를 주로 사용한다. ReLU란 입력 값이 0보다 작으면 0을 출력하고 0보다 크면 입력 값을 그대로 출력하는 함수이다. 코드로 구현한 것과 ReLU 함수를 그래프로 나타낸 것은 아래와 같다.

import numpy as np
import matplotlib.pyplot as plt

def ReLU(x):
    return np.maximum(0, x)

x = np.arange(-6, 6, 1)
y = ReLU(x)
plt.plot(x, y)
plt.ylim(-1, 6)
plt.show()

렐루 함수 (ReLU function) 그래프

다시 말해 입력 값이 0보다 작으면 0으로 출력하고 입력 값이 0보다 크다면 입력 값을 그대로 출력한다. 하지만 이런 ReLU 함수의 한계점은 한 번 활성화 값이 0인 출력값을 다음 레이어의 입력으로 전달하게 되면 이후 뉴런들의 출력 값이 모두 0이 되는 현상이 발생하는 것이다. 이를 dying ReLU라 하며 이러한 한계점을 개선하기 위해 음수 값을 일부 표현할 수 있도록 개선한 활성화 함수들이 사용된다. 그 종류로는 LeakyReLU, ELU, PReLU, ThresholdReLU, Softplus, Swish 등이 있다.

 

2.4 항등 함수 및 소프트맥스 함수의 구현과 그래프

출력층의 활성화 함수는 풀고자 하는 문제에 적합한 활성화 함수를 사용해야 한다. 예를 들면 분류 문제와 회귀 문제가 있다. 분류 문제는 크게 이중 분류와 다중 분류로 나뉜다. 이중 분류를 한다면 시그모이드 함수를 사용하는 것이 적합하고, 다중 분류를 사용한다면 소프트맥스 함수를 사용하는 것이 적합하다. 그리고 회귀 문제의 경우는 항등 함수를 사용하는 것이 적합하다. 그렇다면 소프트맥스 함수와 항등 함수는 무엇일까?

 

먼저 항등 함수의 경우 ReLU와 비슷한 맥락으로 입력 값 자기 자신을 출력하는 함수를 의미한다. ReLU와 다른점이 있다면 음수 입력도 그대로 출력하는 것이다. 코드로 표현하면 아래와 같다.

def identity_function(x)
	return x

소프트맥스 함수는 다중 분류에 사용된다 했다. 소프트맥스 함수를 한 마디로 표현하면 분류해야 할 출력 개수에 대해 각각의 확률을 출력하는 함수이다. 예를 들어 이 사진이 강아지, 고양이, 원숭이인지 분류해야할 다중 분류에는 어떤 한 사진이 입력 신호로 들어왔을 때 최종 출력으로 강아지일 확률 0.A%, 고양이일 확률 0.B%, 원숭이일 확률 0.C%로 표현하는 것이다. 이 때 소프트맥스 함수의 특징은 A+B+C = 1이 되는 것이다. 즉, 모든 출력 확률의 합은 1이 된다. 그렇다면 이런 소프트맥스 함수는 어떤 형태를 갖고 있을까? 그 형태는 다음과 같다.

 

$y_k = {exp(a_k)\over \sum_{i=1}^nexp(a_i)}$

 

여기서 $n$은 출력층의 뉴런 수(=분류해야할 크기), $exp$는 지수함수 $e^x$, $y_k$는 $k$번째 출력을 의미한다. 분모는 모든 입력 신호의 지수 함수 값의 합을 의미하고, 분자는 입력 신호의 지수 함수 값을 의미한다. 이를 코드로 구현하면 다음과 같다.

import numpy as np

a = np.array([0.3, 2.9, 4.0])

def softmax(x):
    exp_a = np.sum(a)
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a
    return y

 

2.4.1 소프트맥스 함수 한계점과 개선

하지만 위와 같은 소프트맥스 함수에는 한가지 큰 단점이 있다. 바로 오버플로우 문제이다. 지수 함수의 특성상 과도하게 큰 수치의 값을 출력할 수 있게 되어 컴퓨터가 이를 올바르게 표현하지 못할 수 있다. 따라서 컴퓨터로 하여금 이러한 오버플로우 문제를 해결하기 위해서 소프트맥스 함수를 개선하여 구현하는 것이 일반적이다. 핵심 방법은 입력 신호 중 최대값을 빼는 것이다. 코드로 구현하면 다음과 같다.

import numpy as np

a = np.array([0.3, 2.0, 4.0])

def sofmtax(x):
    maximum = np.max(a)
    exp_a = np.exp(a-c)
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a
    return y

이를 수식적으로 설명하면 다음과 같다.

 

$y_k = {exp(a_k)\over \sum_{i=1}^nexp(a_i)}$

 

$= {Cexp(a_k) \over C\sum_{i=1}^nexp(a_i)}$

 

$= {exp(a_k + logC) \over \sum_{i=1}^nexp(a_i+logC)}$

 

$= {exp(a_k+C')\over \sum_{i=1}^nexp(a_i+C')}$

 

즉 C라고 하는 임의의 정수를 곱해준 다음, $log$ 함수로 표현하여 지수 함수 내부로 옮겨준 뒤 마지막으로 $logC$를  $C'$로 치환해주는 식이다. 이러한 방식을 통해 오버플로우 문제를 개선한다. 이렇게 임의의 정수를 초기에 곱해주고 마지막으로 $C'$로 치환해도 함수 상 문제가 없는 것은 분모 분자 모두 동일한 임의의 정수 $C$를 곱해준 것이기 때문에 결론적으로 동일한 계산이 된다. 다른 말로 표현하면 소프트맥스 함수에서 내부적으로 사용되는 지수 함수인 $exp(x)$는 단조증가함수이기 때문에 가능한 것이다. 여기서 단조증가 함수란 $a\leq b$일 때 항상 $f(a)\leq g(b)$가 되는 함수를 의미한다.

 

Reference

[1] https://phobyjun.github.io/2019/09/12/밑바닥부터-시작하는-딥러닝-신경망.html

[2] https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=cjswo9207&logNo=221059314707 

[3] https://yeomko.tistory.com/39

이 포스팅은 『밑바닥부터 시작하는 딥러닝』을 기반으로 작성되었습니다. 간단한 이론이지만 누군가에게 설명할 수 있는가에 대해 생각한 결과, 올바르게 설명하지 못한다고 판단되어 이를 쇄신하고자 하는 마음으로 작성합니다.

 

1. 퍼셉트론(Perceptron)

퍼셉트론이란 여러 개의 입력 신호를 받아 하나의 출력 신호를 만들어내는 알고리즘이다. 1957년에 고안된 것으로 딥러닝의 기원이된 개념이다. 딥러닝을 이해하기 위한 가장 기초가 되는 개념이다. 그림으로 표현하면 아래와 같다.

퍼셉트론 구조

$x_1, x_2$는 퍼셉트론이라는 신경망 알고리즘의 입력 신호에 해당한다. $w_1, w_2$는 가중치(weight)로, 입력된 신호가 얼마나 중요한지를 표현하기 위한 매개변수이다. 가중치가 클수록 해당 신호가 그만큼 중요한 것이다. 가중치 $w$는 일종의 전류의 저항과 같이 흐름을 제어하는 요소로써 중요한 입력 신호는 더 큰 신호로 만들어주고 덜 중요한 신호는 비교적 작은 신호로 만들어 주는 역할을 한다. 딥러닝에서 학습을 한다는 것은 입력된 신호를 잘 나타내기 위해 $w_1, w_2$와 같은 가중치를 업데이트 하는 과정이라 할 수 있다. 입력 신호와 출력 신호에 해당하는 둥그런 원들을 뉴런 또는 노드라고 부른다. 입력 신호와 가중치의 곱셈합이 $y$가 되는데 수식으로 나타내면 아래와 같다.

 

$y = \begin{cases}0\ (x_1w_1+x_2w_2\leq \theta) \\ 1\ (x_1w_1+x_2w_2\gt \theta) \end{cases}$

 

여기서 $\theta$는 threshold를 의미하는 것으로 사용자가 설정한 임계값을 뜻한다. 입력신호와 가중치의 합이 $\theta$를 넘지 못하면 0을, 넘는다면 1을 출력하는 방식으로 퍼셉트론은 동작한다.

 

2. 편향

사실 위의 퍼셉트론 개념에는 편향이라는 개념이 포함되는데, 다시 수식으로 나타내면 다음과 같다.

$y = \begin{cases}0\ (b+ x_1w_1+x_2w_2\leq 0) \\ 1\ (b+ x_1w_1+x_2w_2\gt 0) \end{cases}$

 

$\theta \rightarrow -b$로 치환된 것에 불과하다. 여기서 $b$는 bias를 의미하는 것으로 입력 신호가 얼마나 쉽게 활성화(결과를 1로 출력)하는지를 조정하는 매개변수이다.

예를 들어 $b=-0.5$라면 입력신호와 매개변수의 합 $(x_1w_1+x_2w_2)$의 값이 0.5가 넘어야만 활성화 할 수 있는 것이다.

 

3. 논리게이트와 퍼셉트론의 한계

퍼셉트론을 통해 컴퓨터의 가장 기본이 되는 논리 게이트를 구현할 수 있다. 논리 게이트 종류는 AND, OR, XOR, NOR, NAND, NOT이 있지만 퍼셉트론을 통해서는 XOR을 구현할 수 없다. XOR이라는 논리게이트를 구현하지 못하는 것은 큰 의미를 갖는다. 논리 게이트는 컴퓨터에 있어 가장 근간이 되는 것이다. 컴퓨터에서 일어나는 연산은 모든 논리 게이트의 합이라 할 수 있다. XOR을 구현할 수 없다는 것은 기본이 되는 연산 중 하나를 못하게 되는 것으로, 복합 논리 연산을 하지 못한다는 것이다. 이 XOR 연산을 하지 못한다는 것이 퍼셉트론의 한계이다.

 

하지만 정확히는 하나의 층으로 이루어진 싱글레이어 퍼셉트론(single-layer perceptron)으로는 구현할 수 없다는 것이 한계이다. 이를 해결하는 방법이 퍼셉트론을 중첩해서 쌓는 멀티레이어 퍼셉트론(multi-layer perceptron)이다. 퍼셉트론의 아름다움은 여러 개의 층을 쌓는 멀티레이어 퍼셉트론에 있는 것이다.

예를 들면 위 퍼셉트론 기본 구조 그림에서 $x_1, x_2$은 0층이고 $y$는 1층으로 이루어진 싱글레이어 퍼셉트론이라 할 수 있다. 여기서 $x_1, x_2$와 $y$사이에 층을 하나 더 쌓게 되면 XOR 연산을 표현할 수 있게 된다. 이렇게 층이 하나씩 더 쌓일 때 마다 복잡한 회로를 만들 수 있게 되면서 다양한 논리 연산이 가능해진다.

NAVER CLOVA 이기창님의 『BERT와 GPT로 배우는 자연어 처리』 책 내용 요약과, 알고 있는 내용을 기반으로 각색하여 작성되었습니다. 이 책은 전반적으로 누구나 이해하기 쉬운 비유와 그림을 사용하고 핵심만 이야기 해주기 때문에 정보의 홍수로부터 자유로울 수 있다는 것이 장점인 것 같습니다. 쉬운 실습도 함께 포함되어 있어 자연어처리에 입문하는 사람이 보면 큰 맥락을 파악하기에 좋은 책이 되는 것 같습니다.

 

1. Transformer: 최근 자연어처리의 핵심 모델

최근 자연어처리를 이해하는 데 있어서 알아야 할 핵심 한 가지가 있다면 Transformer이다. 이 책에서 설명하는 BERT 모델과 GPT 모델은 모두 Transformer를 기반으로 하기 때문이다. BERT와 GPT 모델이 자연어처리 트렌드에 있어 의의가 있는 것은 크게 3가지 이유가 있다. 첫 번째는 Transformer를 통해 대규모 언어 모델 학습을 할 수 있게 되었고, 두 번째는 대규모 언어 모델 학습을 통해 전이 학습(Transfer Learning)이 가능해졌다는 것이며, 세 번째는 대규모 언어 모델 학습과 전이 학습을 통해 기존 존재하던 모델들의 성능을 압도했기 때문이다. 다른 말로 표현하면 자연어처리 패러다임의 변화(paradigm shift)를 가져온 것이다. Transformer에 대해 자세히 알기 전에 먼저 전이 학습이란 무엇인가를 살펴보자.

 

2. 전이 학습 (Transfer Learning)

전이 학습이란 쉽게 말해 축구, 배구, 농구, 탁구 등의 여러 운동을 넓게 미리 가르쳐 일종의 운동신경을 만들어두면 배우지 않았던 특정 종목인 피구, 야구도 쉽게 배울 수 있는 것을 의미한다. 물리학, 화학, 지구과학을 미리 배워두면 배웠던 지식들로 하여금 생명과학이라는 분야에도 "전이"를 통해 쉽게 배울 수 있는 것과 같다. 정리하자면 특정 태스크를 학습한 모델을 다른 태스크 수행에 재사용하는 기법이라 할 수 있다. 이 전이 학습의 장점으로는 모델학습 속도 향상이 가능하고, 새로운 태스크를 더 잘 수행할 수 있다는 점이다. 이러한 전이 학습을 활용하는 대표적인 모델의 예시가 BERT와 GPT인 것이다. 전이 학습 방법은 크게 두 가지로 나뉜다. 업스트림 태스크(upstream task)와 다운스트림 태스크(downstream task)다.

 

2.1 업스트림 태스크 (upstream task)

업스트림 태스크는 쉽게 말해 일단 넓게 배우는 것이다. 넓게 배우기 위해 대규모 말뭉치(corpus)를 학습한다. 학습하기 위한 대표적인 방법으로 크게 2가지로 다음 단어(문장) 맞히기 (Next Sentence Prediction, NSP)와 빈칸 맞히기 (Masked Language Model, MLM)가 있다. NSP는 일종의 문장간의 연관성을 학습하여 어떤 단어 뒤에 나올 다음 단어는 무엇인가 맞히는 것이며, 빈칸 맞히기는 단어에 빈칸을 둔 뒤 이를 맞히는 것이다. 이러한 방법을 통해 업스트림 태스크를 수행하는 것을 pre-training이라고 한다. 대표적으로 BERT 모델은 위 두 가지 방식 모두를 사용하여 업스트림 태스크(=pre-training)를 수행하고, GPT는 NSP 방법만을 통해 업스트림 태스크를 수행한다. 

 

NSP에 대해 조금더 구체적으로 이야기하면, "티끌 모아 __"일 때, "티끌 모아"는 문맥이 되고, "__"는 맞춰야할 다음 단어가 된다. 정답이 "태산"일 경우 태산에 대한 확률 값을 높이고 이외의 모든 단어는 확률을 낮추는 방향으로 모델을 업데이트 하는 것이다.

 

MLM 또한 예를 들어 "티끌 __ 태산"일 때, 티끌과 태산은 문맥이 되며 "__"은 맞혀야 할 빈칸 단어가 된다. 정답이 "모아"일 경우 "모아"에 대한 확률이 높아지며 이외의 단어는 확률이 낮아지는 방향으로 모델이 업데이트 된다.

 

이러한 업스트림 태스크의 장점은 self-supervised 학습이 가능하다는 것이다. 다른 말로 손수 라벨을 붙인 데이터가 필요한 지도 학습과 달리 수작업 라벨링 작업 없이 학습 데이터 생성이 가능하다는 것이다. 즉 데이터 내에서 정답을 생성하고 이를 바탕으로 학습하는 방법을 의미한다.

 

2.2 다운스트림 태스크 (downstream task)

다운스트림 태스크는 쉽게 말해 깊게 배우는 것을 의미한다. 업스트림 태스크를 통해 넓게 배운 다음 깊게 배우는 것이다. 다른 말로 downstream task는 실제로 구체적으로 풀고자하는 문제를 수행하는 것을 의미한다. 여러가지 task를 생성할 수 있지만 대표적인 예시는 아래와 같다.

- 문장 분류 (SC): → 문장에 대한 긍정, 부정, 중립에 대한 확률 값을 반환하는 태스크이다.

- 자연어 추론 (NLI) → 문장에 대한 참, 거짓, 중립 확률 값 반환하는 태스크이다.

- 개체명 인식 (NER) → 기관명, 인명, 지명 등 개체명 범주 확률값 반환하는 태스크이다.

- 질의 응답 (QA) → 질문이 주어질 때 답변에 대한 확률값을 반환하는 태스크이다 (각 단어가 정답의 시작일 확률값 + 끝일 확률값 반환

- 문장 생성 (SG) → 문장을 입력 받고 어휘 전체에 대한 확률 값 반환하는 태스크이다.

 

다운스트림 태스크에는 크게 3가지 방법이 존재한다. 파인 튜닝(fine-tuning)프롬프트 튜닝(prompt-tuning)인컨텍스트 러닝(in-context learning)이다. 

 

2.2.1 파인튜닝 (fine-tuning)

다운스트림 태스크 데이터 전체를 사용하는 것으로, 모델 전체를 업데이트 하는 것이 특징이다. 단점으로는 언어 모델이 크면 클수록 모델 전체 업데이트에 필요한 계산 비용이 발생한다. 이러한 단점으로 인해 프롬프트 튜닝과 인컨텍스트 러닝이 주목을 받는다.

 

2.2.2 프롬프트 튜닝(prompt tuning)

다운스트림 태스크 데이터 전체를 사용해서 모델을 일부 업데이트 하는 방법을 의미한다. 

 

2.2.3 인컨텍스트 러닝(in-context learning)

다운스트림 태스크 데이터 일부만 사용하는 방법으로 모델을 업데이트하지 않고 다운스트림 태스크를 수행하는 방법이다. 인컨텍스트 러닝은 크게 3가지 방식으로 나뉜다. 제로샷 러닝(zero-shot learning), 원샷 러닝(one-shot learning), 퓨샷 러닝(few-shot learning)이다.

 

- 제로샷 러닝: 다운스트림 태스크 데이터를 전혀 사용하지 않고 모델이 바로 다운스트림 태스크를 수행하는 것이다.

- 원샷 러닝: 다운스트림 태스크 데이터를 1건만 사용하는 것을 의미한다. 모델이 1건의 데이터가 어떻게 수행되는지 참고한 뒤 다운스트림 태스크를 수행한다.

- 퓨샷 러닝: 다운스트림 태스크 데이터를 몇 건만 사용하는 것을 의미한다. 모델은 몇 건의 데이터가 어떻게 수행되는지 참고한 뒤 다운스트림 태스크를 수행한다.

 

전이 학습에 대한 큰 범주의 내용은 위와 같다. 다음은 이제 Transformer란 무엇이고 BERT와 GPT에 어떻게 영향을 주었는지 알아보자.

 

3. 트랜스포머 (Transformer)

트랜스포머는 2017년 구글(Google)에서 제안한 시퀀스 투 시퀀스(sequence to sequence) 모델로 자연어처리를 위해 만들어졌고 이후에는 컴퓨터 비전에도 사용되고 있다(Vision Transformer). 앞서 말했듯 BERT와 GPT 또한 Transformer를 기반으로 만들어졌다. 트랜스포머 모델은 크게 인코더와 디코더로 구성되는데 BERT는 트랜스포머의 인코더, GPT는 트랜스포머의 디코더를 사용하여 만들어진 것이 특징이다. BERT는 Bidirectional Encoder Representation from Transformer의 약자이며 GPT는 Generative Pre-trained Transformer의 약자이다. 이름에서 모두 Transformer가 사용된 것을 알 수 있다. 트랜스포머에 대해 구체적으로 알아보기 위해 간단히 배경, 활용, 특징, 구조에 대해 차례대로 알아보자.

 

3.1 트랜스포머 모델 탄생 배경

트랜스포머 모델이 나오게 된 배경은 기존 자연어 처리 모델의 단점 때문이다. 트랜스포머 모델은 시퀀스 투 시퀀스 모델이라 했다. 이 시퀀스 투 시퀀스 모델을 처리하는 기존의 모델은 RNN 계열 모델이었다. 대표적으로 LSTM이 있었는데 이 방식의 단점은 크게 2가지 였다. long-term dependency 문제와 비병렬성 문제였다. long-term dependency 문제란 문장의 길이가 길고 단어 사이의 간격이 클수록 모델이 "잊게" 되어 단어 간의 관계 파악이 어려워지는 문제이며, 비병렬성의 경우 문장을 처리하는데 있어 LSTM 모델이 순차적으로 처리하기 때문에 시간 복잡도가 높다는 단점이다. 이를 극복하는 모델이 바로 트랜스포머 모델이다. 병렬처리를 통해 속도가 빠르면서도, 긴 문장 또한 관계 파악을 분명하게 처리할 수 있는 것이다.

 

3.2 트랜스포머 모델 활용 및 특징

이러한 트랜스포머 모델이 활용된 부분은 자연어처리 태스크 중 기계번역에 가장 처음으로 사용되었다. 예를 들면 소스 언어 (source language)인 한국어를 타겟 언어(target language)인 영어로 번역하는 것이다. 인코더를 통해 소스 언어의 시퀀스를 압축하며, 디코더를 통해 타겟 언어의 시퀀스를 생성하는 것이다.

소스 언어: 어제, 카페, 갔었어, 거기, 사람, 많더라

타겟 언어: I, went, to, the, cafe, there, were, many, people, there

 

여기서 특징은 소스 언어의 길이(시퀀스)와 타겟 언어의 길이(시퀀스)가 달라도 해당 태스크를 수행할 수 있다는 것이다. 트랜스포머의 특징 중 하나는 임의의 시퀀스나 속성이 다른 시퀀스 변환 작업 또한 가능하다. 예를 들면 필리핀 앞바다 한 달치 기온 데이터를 기반으로 1주일간 하루 단위 태풍 발생 예측이 가능하다. 즉, 기온 시퀀스로 태풍 발생 여부 시퀀스를 예측할 수 있는 것이다. 

 

트랜스포머의 최종 출력값 즉, 디코더의 출력은 타겟 언어의 어휘 수 만큼의 차원으로 구성된 벡터이다. 학습은 인코더와 디코더에 입력이 주어질 때 정답에 해당하는 단어의 확률값을 높이는 방식으로 수행된다. "어제 카페 갔었어 거기 사람 많더라"라는 입력이 들어갈 경우 출력으로는 "I went to cafe there were a lot of people"이 나오는데, 이 때 가장 처음 번역되어야 할 "I"에 대한 확률값이 높아지고 나머지 went, to, cafe, there, were, a, lot, of, people는 확률값이 낮아지고 "I"다음에 나올 단어는 "went"일 때 "went"에 대한 확률값이 높아지고 나머지 단어는 확률이 낮아지는 것이다.

 

트랜스포머의 활용에 있어 특징 중 하나는 학습 도중의 디코더 입력과 학습 후 인퍼런스 때의 디코더의 입력이 다르다는 것이다. 학습 과정에는 디코더의 입력에 맞혀야 할 단어가 went라면 이전의 정답 타겟 시퀀스인 "<s> I"를 입력한다. 반면 인퍼런스 과정에는 현재 디코더 입력에 직전 디코딩 결과를 사용한다. 

 

3.3 트랜스포머 모델 구조

트랜스포머 모델은 앞서 언급한대로 아래와 같이 크게 인코더와 디코더로 구성되어 있다. 

왼쪽 회색 박스에 해당하는 영역이 인코더이며 오른쪽에 회색박스에 해당하는 영역이 디코더이다. 실제 트랜스포머를 사용할 때는 인코더를 N개 디코더를 N개 만큼 쌓아 사용한다. 인코더와 디코더에는 공통적인 요소는 멀티 헤드 어텐션(Multi-Head Attention), 피드포워드 뉴럴넷(Feedforward Neural Network), 잔차 연결 & 레이어 정규화(Residual-Connection & Layer Normalization)이다. 그 중 트랜스포머 모델의 가장 핵심이 되는 것은 멀티 헤드 어텐션이다. 따라서 먼저 멀티 헤드 어텐션에 대해 알아보자.

 

 

3.3.1 셀프 어텐션(self attention)

트랜스포머가 다른 sequence to sequence 모델에 비해 경쟁력을 가질 수 있는 원천은 셀프 어텐션(self attention)에 있다. 정확히는 멀티 헤드 어텐션인데 이 멀티 헤드 어텐션이란 단순히 셀프 어텐션을 여러 개(head)를 붙인 것에 불과하다. 먼저 어텐션(attention)이란 단어 시퀀스 요소 중 중요한 요소에만 집중해 특정 태스크의 성능을 올리는 기법을 뜻한다. 앞서 언급한대로 셀프 어텐션은 RNN의 단점인 long-term dependency 문제와 비병렬성 문제를 극복하는 방식으로 동작한다. 또한 어텐션은 CNN의 단점 또한 극복하는 방법이기도 하다. CNN의 단점은 컨볼루션 필터의 크기를 넘어서는 문맥을 읽어내기 어렵다는 것이다. 때문에 어텐션은 RNN과 CNN의 핵심 단점인 전체 문맥을 고려할 수 없다는 것을 극복하는 방법이다. 개별 단어와 전체 입력 시퀀스를 대상으로 어텐션 계산을 진행하는 방식으로 동작한다. 모든 경우의 수를 고려하기 때문에 지역적 문맥만 고려할 수 있는 CNN보다 강하며, 시퀀스의 길이가 길어도 RNN처럼 정보를 잊거나 (기울기 소실로 인해) 정보가 왜곡되지 않는다. 셀프 어텐션은 이 어텐션 개념을 자신에게 수행하는 것이다. 가령 "I love you"라는 시퀀스가 있을 경우 아래와 같이 모든 경우의 수에서 어텐션 스코어를 계산해 특정 시퀀스 요소(I, love, you)가 어떤 것과 가장 연관성이 높은지 판단한다.

I → I

I → love

I → you

love → I

love → love

love → you

you → I

you → love

you → you

 

3.3.1.1 셀프 어텐션 동작 원리

셀프 어텐션의 어텐션 스코어를 계산하기 위해 필요한 것은 Query, Key, Value이다. 다른 말로 셀프 어텐션이란 Query, Key, Value의 세 요소 사이의 문맥적 관계성을 추출하는 과정이라할 수 있다. Query, Key, Value로부터 문맥적 관계성을 추출하는 절차는 아래와 같다.

 

(1) Query, Key, Value 생성 하기

먼저 Q, K, V 행렬을 생성해야 한다. 이는 입력 벡터 시퀀스인 $X$를, 랜덤 초기화된 행렬 $W_Q$, $W_K$, $W_V$과 곱해서 Q, K, V 행렬을 생성한다. 수식으로 나타내면 아래와 같다. 

$Q = X \times W_Q$

$K = X \times W_K$

$V = X \times W_V$

이후 $W_Q, W_K, W_V$ 세 행렬은 태스크를 잘 수행하는 방향으로 학습 과정에서 업데이트 된다.

 

(2) 셀프 어텐션 출력값 계산

(1)의 과정을 통해 Q, K, V 행렬을 계산했다면 셀프 어텐션을 계산할 수 있게 된다. 과정을 단일 수식으로 나타내면 다음과 같다.

 

$Attention(Q, K,V) = softmax$ $({QK^T\over \sqrt{d_K}})V$

 

이를 풀어서 이야기하면 $Q \times K^T$한 뒤, 모든 요소 값을 $K$ 차원 수의 제곱근으로 나누고(if 차원 = 3, $d_K=3$), 이 행렬을 행 단위로 소프트맥스를 취해 스코어 행렬로 만들어 주는 것이다. 그리고 이 스코어 행렬에 V를 행렬곱하면 셀프 어텐션 계산이 된다.

 

이러한 수식을 가지게 되는 이유는 다음과 같다.

1. $QK^T$라는 내적을 통해 시퀀스 요소 간의 유사도를 구할 수 있다. 예를 들어 $Q$에는 I, love, you라는 시퀀스가 올 경우 3개의 행과 전체 어휘 차원수의 열을 가지게 되고, $K^T$는 I, love, you라는 시퀀스가 3차원 열을 만들고 전체 어휘 차원수 만큼이 행이 되는 것이다. 결과적으로 $QK^T$는 I, love, you라는 시퀀스 간의 유사도, 정확히는 벡터의 유사도를 구할 수 있는 것이다. 

 

2. $\sqrt{d_K}$로 나누어줌으로써 안정적인 경사값(gradient)를 계산할 수 있게 된다. 참고로 이러한 셀프 어텐션에서 $QK^T$의 내적을 계산한 다음 $\sqrt{d_K}$로 나누는 것을 스케일 닷 프로덕트 어텐션(scaled dot product attention)이라고도 부른다.

 

3. softmax 함수를 통해 정규화를 한다. 이를 통해 모든 요소는 0~1 사이의 값을 갖게 된다. 참고로 softmax 함수를 수식으로 나타내면 $softmax (x_i)= {exp(x_i) \over \sum_jexp(x_j)}$이다.

 

4. 가중치 행렬 $V$를 행렬곱함으로써 단어 벡터를 계산할 수 있게 된다. 

 

3.3.1.2 셀프 어텐션 동작 원리 실습

이러한 위의 셀프 어텐션을 구하는 과정을 파이토치로 계산하는 과정은 다음과 같다. 

import torch
import numpy as np
from torch.nn.functional import softmax

# 1. define input vector sequence: 3 x 4 = (number of words what entered) * (word embedding dimension)
x = torch.tensor([
    [1.0, 0.0, 1.0, 0.0],
    [0.0, 2.0, 0.0, 2.0],
    [1.0, 1.0, 1.0, 1.0],
])

# 2. define weighted query, weighted key, weigted value
w_query = torch.tensor([
    [1.0, 0.0, 1.0],
    [1.0, 0.0, 0.0],
    [0.0, 0.0, 1.0],
    [0.0, 1.0, 1.0]
])

w_key = torch.tensor([
    [0.0, 0.0, 1.0],
    [1.0, 1.0, 0.0],
    [0.0, 1.0, 0.0],
    [1.0, 1.0, 0.0]
])

v_value = torch.tensor([
    [0.0, 2.0, 0.0],
    [0.0, 3.0, 0.0],
    [1.0, 0.0, 3.0],
    [1.0, 1.0, 0.0]
])

# 3. create query, key, value
queries = torch.matmul(x, w_query)
keys = torch.matmul(x, w_key)
values = torch.matmul(x, v_value)

# 4. create attension score
attn_scores = torch.matmul(queries, keys.T)

# 5. apply softmax function
key_dim_sqrt = np.sqrt(keys.shape[-1])
attn_probs = softmax(attn_scores / key_dim_sqrt)

# 6. weighted sum with values
weighted_values = torch.matmul(attn_probs, values)
print (weighted_values)

 

3.3.1.3 멀티 헤드 어텐션

멀티 헤드 어텐션이란 앞서 언급한대로 셀프 어텐션을 동시에 여러 번(multi-head) 수행하는 것을 의미한다. 여러 헤드가 독자적으로 셀프어텐션을 계산하는 것이다. 비유를 하자면 같은 문서(입력)를 두고 여러 명(헤드)이 함께 읽는 것이다. 여러 번의 셀프 어텐션을 수행하여 결과값을 더함으로써 일종의 앙상블 효과를 낸다. 치우치지 않고 정확성이 높은 결과를 도출할 수 있는 것이다. 

 

개별 헤드의 셀프 어텐션 수행 결과 = $\times$ $W^O$

$W^O$의 크기 = 셀프 어텐션 수행 결과 행렬 열 수 $\times$ 목표 차원 수

최종 수행 결과 → 입력 단어 수 $\times$ 목표 차원 수

 

여기까지 트랜스포머의 구성요소 중 멀티 헤드 어텐션에 대해 알아보았다. 다음으로는 피드포워드 뉴럴넷, 잔차 연결, 레이어 정규화, 드롭 아웃에 대해 알아보자.

 

3.3.2 피드포워드 뉴럴넷(feedforward neural network)

피드포워드 뉴럴넷의 구성요소는 DNN의 형태로 Input layer, hidden layer, output layer로 3가지로 구성되어 있으며, 피드포워드 뉴럴넷의 학습대상은 weight와 bias이다. 흔히 알고 있는 가장 기본적인 뉴럴넷 구조이다.

 

 

3.3.3 잔차 연결(residual connection)

잔차 연결이란 블록 또는 레이어 계산을 건너뛰는 경로를 하나 두는 것을 의미한다. 책의 그림상 3개의 잔차 연결을 두면 8개의 새로운 경로가 생기는 것을 확인할 수 있었다. 이러한 잔차 연결의 역할은 모델이 다양한 관점에서 블록이나 레이어 계산을 수행 가능하다. 딥러닝 모델은 레이어가 많아질수록 학습이 어려워진다. 그이유는 모델 업데이트를 위한 gradient가 전달되는 경로가 길어지기 때문이다. 하지만, 잔차 연결을 통해 모델 중간에 블록을 건너뛰는 경로를 설정함으로써 학습을 쉽게하는 효과가 있다.

 

 

3.3.4 레이어 정규화(layer normalization)

미니 배치 인스턴스 별로 평균 ($\mathbb{E}[x])$을 빼주고 표준편차($\sqrt{\mathbb{V}[x]}$로 나눠 정규화를 수행하는 기법이다. 이를 통해 학습이 안정되고 속도가 빨라지는 효과를 얻을 수 있다. 레이어 정규화에 사용되는 수식 요소는 엡실론($\epsilon$), 감마($\\gamma$), 베타($\beta$)이다. 감마, 베타는 학습 과정에서 업데이트 되는 가중치이며 엡실론은 분모가 0이 되는 것을 방지하기 위해 사용된다. 보통 $1e^{-5}$로 설정한다. 딥러닝 프레임워크 중 하나인 파이토치에서 레이어 정규화를 위한 LayerNorm 객체는 감마와 베타를 각각 1과 0으로 초기화 하며 이후 업데이트 과정을 거친다.

 

3.3.5 드롭아웃(dropout)

드롭아웃은 트랜스포머에서 추가적인 요소로 더 나은 일반화를 위해 사용된다. 드롭아웃은 과적합(overfit) 방지 기법으로 모델이 표현력이 좋아서 외워버리는 것을 방지한다. 드롭아웃을 적용할 때 딥러닝 프레임워크 중 하나인 파이토치의 특징은 torch.nn.Dropout을 사용할 때 안정적인 학습을 위해 각 요소 값에 1/(1-p)를 곱하는 역할을 수행한다. 예를 들면 드롭아웃 비율을 p=0.2로 설정할 경우 1/(1-p)에 의해 드롭아웃 적용후 살아남은 요소값에 각각 1.25를 곱하게 된다. 드롭아웃은 일반적으로 0.1을 사용한다. 또 드롭아웃은 학습 과정에만 적용하고 인퍼런스에는 적용하지 않는 것이 특징이다.

 

 

4. 토큰화

토큰화란 문장을 작은 단위로 분리하는 것을 의미한다. BERT와 GPT와 같은 자연어처리 모델에 입력을 위해서 우선적으로 토큰화 절차를 필요로 한다. 토큰화 방법은 크게 3가지가 존재한다. 단어 단위, 문자 단위, 서브워드 단위이다..

 

단어 단위 토큰화 (word-level)

단어(어절) 단위로 토큰화를 수행하는 것을 의미한다. 예시로는 "어제 카페 갔었어"라는 문장은 "어제", "카페", "갔었어"로 분리 되는 식이다. 이 방식의 단점은 가능한한 많은 단어에 대해 고려해야 하기 때문에 어휘 집합(lexical set)의 크기가 커질 수 있고, 커지면 모델 학습이 어려워지게 된다.

 

문자 단위 토큰화 (character level)

문자 단위로 토큰화를 수행하는 것을 의미한다. 문자 단위란 ㄱ, ㄴ, ㄷ, ㄹ / a, b, c, d를 의미한다. 참고로 한글로 표현 가능한 글자는 총 11,172개이다. 문자 단위 토큰화 방식의 장점은 해당 언어의 모든 언어를 포함할 수 있기 때문에 미등록된 토큰(Out Of Vocabulary, OOV) 문제로부터 자유롭다. 반면 단점으로는 각 문자 토큰은 의미있는 단위가 어렵다는 것이다. 예를 들면 "제"의 어와 "어미"의 어의 구분이 사라지게 된다. 또한 토큰 시퀀스 길이가 길어지기 때문에 학습 성능이 저하된다는 단점도 존재한다. 예시로는 "어제 카페 갔었어" → 어, 제, 카, 페, 갔, 었, 어로 변환되어 토큰 시퀀스가 길어지는 것이다.

 

서브워드 단위 토큰화 (sub-word level)

단어 단위 토큰화와 문자 단위 토큰화의 중간 형태로 둘의 장점만 취한 것이다. 특징은 어휘 집합의 크기가 지나치게 크지도 않고, 미등록 토큰 문제를 해결하며, 토큰 시퀀스가 너무 길어지지 않게 하는 특징을 가진다. 대표적인 구현 예시가 BPE (Byte Pair Encoding)이다.

 

4.1 Byte Pair Encoding (BPE)

BPE는 1994년에 처음제안된 정보 압축 알고리즘이다. 하지만 근래에는 자연어처리의 토큰화 기법으로 사용된다.(BPE는 기계 번역분야에서 가장 먼저 쓰임) 토큰화를 진행 할 때 빈도수 높은 문자열을 병합해 데이터를 압축한다. 압축 알고리즘의 동작 절차 예시는는 다음과 같다.

 

1. aaabdaaabac가 있을 때 빈도수가 가장 높은 aaa를 Z로 치환해 ZabdZabac로 치환한다.

2. 다음 빈도수가 높은 ab를 Y로 치환해 ZYdZYac로 만든다.

3. ZY를 다시 X로 치환하여 XdXac로 치환한다. 

 

기존의 어휘수는 (a, b, c, d) 였으나 압축 어휘수는 (a, b, c, d, Z, Y, X)로 늘었고, 압축 전 글자수는 11글자에서 압축 후 글자수는 5글자가 되었다. 이러한 BPE 알고리즘의 장점은 사전 크기 증가를 적당히 억제하면서도 정보를 효율적으로 압축 가능하며, 분석 대상 언어에 대한 지식이 필요하지 않다는 것이다. 이러한 BPE 알고리즘의 대표적인 활용 예시는 GPT 모델에서 사용된다. GPT 모델은 BPE를 통해 토큰화를 수행한다. 반면 BERT는 wordpiece로 토큰화를 수행한다.

 

4.2 BPE 어휘 집합 구축

BPE 어휘 집합은 구축은 한 마디로 요약하면 고빈도 bi-gram 쌍을 병합하는 방식으로 구축한다. 구체적으로는 먼저, pre-tokenize를 통해 corpus의 모든 문장을 공백기준으로 나눈다. 이후 사용자가 정의한 크기(어휘 집합)이 될 때 까지 가장 높은 빈도수대로 추가한다. 높은 빈도수란 n-gram을 기준으로 하며, 일반적으로 성능과 계산량의 trade-off 관계로 인해 5-gram 아래를 사용하는 것이 좋다고 알려져 있다. 

 

4.3 WordPiece (워드피스)

워드피스 알고리즘 또한 토큰화를 수행하는 방법 중 하나로 자주 등장한 문자열을 토큰으로 인식한다는 점에서 BPE와 본질적으로 유사성을 가진다. 차이점으로는 병합 기준에 있다. 워드피스는 BPE 처럼 단순 빈도수 기반이 아니라, 우도(likelihood)를 가장 높이는 쌍을 병합하는 것이다. 한 마디로 워드피스는 우도를 가장 높이는 글자 쌍을 병합한다.

 

4.4 어휘 집합 구축하기

BPE & wordpiece 기반 토크나이저 만들기

 

Q. 왜 wordpiece에는 special char인 [PAD]를 사용하지 않는가? 정확히는 BPE는 special char [PAD]를 추가해주는데 wordpiece tokenizer에는 그런 과정이 없는가?

A. ?

 

Q. 왜 위 둘의 결과물은 유니코드로 되어 있어 보기 힘들까?

A. 학습 데이터가 한글이고, 한글은 3개의 유니코드 바이트로 표현됨. GPT 모델은 바이트 기준 BPE를 적용하기 때문임.

 

indexing: 토큰 → 인덱스

  • 예시: “별루 였다..” → 4957, 451, 363, 263, 0, 0, 0, ...

input_ids: vocab.txt 또는 vocab.json에 순서대로 부여된 번호로 나타내는 토큰 인덱스 시퀀스

attention_mask: 일반 토큰(1), 패딩 토큰(0) 구분하는 역할

token_type_ids: BERT 모델은 기본적으로 문장 2개 이상을 입력받기에 문장 번호를 이것으로 구분

 

 

5. Pretrained Language Model (PLM)

언어 모델이란 단어 시퀀스에 확률을 부여하는 모델이다. 수식은 $P(w_1, w_2, w_3, ..., w_n)$로 표현 가능하다. 넓은 의미에서의 언어 모델은 맥락이 전제된 상태서 특정 단어(w)가 나타날 조건부 확률이다. 수식으로는 $P(w|context)$로 표현 한다. 예시는 $P(운전|난폭) = {P(난폭, 운전) \over P(난폭)}$이다. 만약 잘 학습된 한국어 모델이 있을 경우 $P(무모|운전)$ 보다 $P(난폭|운전)$이 확률이 더 높을 것이다. 

 

단어가 3개 동시에 등장 확률은 $P(w_1, w_2,w_3) = P(w_1) * P(w_2|w_1) * P(w_3|w_1,w_2)$이 된다. 

언어 모델을 조건부 확률로 다시 쓰면, $P(w_1, w_2,w_3, w_4, \dots, w_n) = \prod_{i=1}^nP(w_i|w_1,w_2,w_3,\dots,w_{i-1})$이다.

 

언어 모델 종류에는 크게 순방향과 역방향이 있다. 순방향의 예시는 "어제 → 카페 → 갔었어 → 거기 → 사람 → 많더라" 순으로 순차적으로 예측하는 것을 의미한다. 대표적인 예시로는 GPT와 ELMo가 이 방식으로 pretrain을 수행한다. 역방향의 예시는 "많더라 → 사람 → 거기 → 갔어어 → 카페 → 어제"로 역순으로 예측하는 것을 의미한다. 대표적으로 ELMo가 이 방식으로 pretrain을 수행한다. 참고로 ELMo는 순방향, 역방향을 모두 활용하는 기법이다.

 

5.1 마스크 언어 모델(Masked Language Model)

마스크 언어 모델이란 학습 대상 문장에 빈칸을 만들고 빈칸에 올 적절한 단어를 분류하는 과정으로 학습하는 것이다. 대표적으로 BERT가 이 방식을 이용하여 업스트림 태스크를 수행한다. 마스크 언어 모델의 장점은 문장 전체의 맥락을 참고 가능하다는 것이다. 때문에 마스크 언어 모델에 양방향 성질이 있다고 말한다.

 

5.2 스킵-그램 모델(skip-gram model)

단어 앞뒤 특정 범위(window size)를 지정 후, 범위 내에 어떤 단어 올지 분류하는 과정으로 학습하는 것이다. 예시로는 window size = 2라 가정할 경우, 아래와 같이 center word 앞 뒤로 2개씩 단어(context word)가 있고 context word를 기반으로 center word를 예측(분류)하는 방식이다.

  1. 어제 카페 갔었어 거기 사람 많더라
  2. 어제 카페 갔었어 거기 사람 많더라

 

 

 

6. 자연어 처리 태스크 종류

6.1 문서 분류 태스크

문서 분류 모델은 (이 책에서 사용하는) 입력 문장을 토큰화한 뒤 [CLS]와 [SEP]를 토큰 시퀀스 앞뒤에 붙인다. 이후 BERT 모델에 입력하고 문장 수준 벡터인 pooler_output을 뽑는다. 이 벡터에 추가 모듈을 덧붙여 모델 전체의 출력이 긍정 확률, 부정 확률 형태로 만든다.

  • pooler_output 벡터 뒤 붙는 추가 모듈은 pooler_output 벡터에 드롭아웃 적용 일부를 768차원중 일부를 0으로 변경한다.
  • 만약 분류 대상이 2가지라면 가중치 행렬 크기는 768 * 2가 됨.

여기서 pooler_output이란 무엇일까? pooler_output은 메서드의 하나로 last_hidden_state라는 메서드와 연관이 있다. pooler_output과 last_hidden_state는 모두 BERT 모델에서 단어와 문장을 벡터로 변환한 것이다. 구체적으로 pooler_output은 BERT 모델에서의 최종 출력값인 벡터 시퀀스이다. 예를 들어 768차원으로 임베딩된다 가정하고, 입력 문장이 [”안녕하세요”, “저의”, "이름은“, "로이입니다”]일 경우 pooler_output은 4 x 768의 shape을 가지게 된다. 즉 아래와 같은 형태로 특정 단어에 대한 확률값을 벡터를 가진 요소이다. (예시를 위해 확률값은 임의 설정함)

 

안녕하세요 = [0.01, 0.04, 0.09, ... , 0.02]

저의         = [0.02, 0.03, 0.04, ... , 0.01]

이름은      = [0.04, 0.07, 0.09, ... , 0.09]

로이입니다 = [0.03, 0.06, 0.01, ... , 0.1]

 

이러한 pooler_output을 다르게 말해 문장 수준 임베딩이라 할 수 있다. 다시 말해 4개의 문장(단어)이 768차원의 벡터로 바뀐 것이다. 1개의 벡터 (1x768)은 하나의 문장 전체를 표현한다. pooler_output 메서드를 통해 이 결과값을 확인할 수 있다.

 

반면 last_hidden_state는 단어 수준 임베딩을 의미하는 것으로 예를 들어 4x10x768의 3차원 형태로 표현될 수 있다. 이는 4개의 문장이 길이 10을 가지고 있고, 768차원 형태라는 것이다.

 

 

 

 

6.1.1 문서 분류 모델 학습하기

Korpora 라이브러리를 통해 데이터를 내려받을 수 있다. 문서 분류 모델 학습을 위해 NSMC 데이터셋을 다운로드 받는다. 이후 데이터로더를 통해 학습 데이터를 배치 단위로 모델에 공급한다. 배치 단위로 공급할 때 데이터셋 내에 있는 인스턴스를 배치 크기 만큼으로 뽑는다. 데이터셋이 100개가 있다고 가정할 경우 인스턴스는 개별 요소 1개가 되며 배치크기가 10이라면 인스턴스 10개가 모여 1개의 배치를 이루게 된다.

  •  

6.1.2 인퍼런스 실습

 

토크나이저 초기화

from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained(
	args.pretrained_model_name, 
	do_lower_case=False,
)

체크포인트 로드

import torch
fine_tuned_model_ckpt = torch.load(
	args.downstream_model_checkpoint_fpath,
	map_location=torch.device("cpu"),
)

BERT 설정 로드

from transformers import BertConfig
pt_model_config = BertConfig.from_pretrained(
	args.pretrained_model_name,num_labels=fine_tuned_model_ckpt["stte_dict"]["model.classifier.bas"].shape.numel(),
)

BERT 모델 초기화

from transformers import BertForSequenceClassification
model = BertForSequenceClassificiation(pt_model_config)

체크포인트 주입

model.load_state_dict({k.replace(”model.”, “”): v for k, v in fine_tuned_model_cpkt[’state_dict’].items()})

평가 모드 전환

model.eval()

 

 

6.2 문장 쌍 분류 태스크

문장 2개가 주어졌을 때 문장 사이 관계가 어떤 범주일지 분류하는 것을 의미한다. 두 문장 관계는 참(entailment), 거짓(contradiction), 중립 또는 판단 불가(neutral)로 가려낼 수 있다. 예를 들어 "나 출근했어 + 난 백수야"는 거짓이 된다. 반면 "나 출근했어 + 난 개발자다"면 중립이 되는 식이다. 문장 쌍 분류 태스크를 위해 업스테이지가 공개한 KLUE-NLI 데이터셋을 활용하여 실습을 진행하였다.

 

모델 구조

문장 쌍 분류 모델은 전제와 가설 두 문장을 사용한다. 따라서 "[CLS] + 전제 + [SEP] + 가설 + [SEP]"의 형태로 이어 붙인다. 이후 토큰화 후 모델에 입력한 뒤, 문장 수준 벡터(pooler_output)을 추출한다. pooler_output에는 전제와 가설의 의미가 응축되어 있음. 여기에 추가 모듈을 붙여 모델 전체 출력이 아래의 형태가 되도록 한다.

  • 전제에 대해 가설이 참일 확률
  • 전제에 대해 가설이 거짓일 확률
  • 전제에 대해 가설이 중립일 확률

 

 

6.3 개체명 인식 태스크

범주 수가 m개이고 입력 토큰이 n개일 때 문서 분류, 문장 쌍 분류 모델은 모델 출력은 m차원의 확률 벡터 1개이다. 시퀀스 레이블링은 m차원 확률벡터가 n개 만들어진다.

 

방법론 입력 출력 대표 과제

문서 분류 문장 1개 한 문서가 속하는 범주에 대한 확률 감성 분석
문장 쌍 분류 문장 2개 두 문서를 아우르는 범주에 대한 확률 자연어 추론
시퀀스 레이블링 문장 1개 토큰 각각의 범주 확률 개체명 인식

 

 

6.4 질의응답 태스크

질의응답 태스크의 유형은 다양하지만 본 책에서는 지문(context)에서 답을 찾는 것으로함 즉, open-book qa이다.

모델의 입력은 질문과 지문(Question, Context)가 되며, 모델 출력은 입력한 각 토큰이 [정답 시작일 확률, 정답 끝일 확률]이 된다. 질의응답 모델은 출력 확률을 적당한 후처리 과정을 통해 사람이 이해 가능한 형태로 가공한다. 실습을 위해 LG CNS가 공개한 KorQuAD 1.0 데이터를 활용하였다. 모델 구조는 토큰화 후, "[CLS] 질문 [SEP] 지문 [CLS]"의 형태로 입력된다.

 

 

6.5 문장 생성 태스크

문장 생성은 컨텍스트(context)가 주어졌을 때 다음 단어로 어떤 단어가 오는 게 적절한지 분류하는 것이다. 모델 입력은 컨텍스트가 되며 모델 출력은 다음 토큰이 등장할 확률이 된다. 수식으로 일반화하여 나타내면 $P(w|context)$이다. 조금 구체적으로는 $P(w|안녕)$일 경우 "안녕" 다음에 올 단어의 확률이 출력된다. 이런 문장 생성 태스크는 문서 분류, 문서 쌍 분류, 개체명 인식, 질의응답 태스크와 특성이 다르다. 가장 큰 차이는 모델 구조이다.

 

모델 구조 GPT BERT
pretrain task 다음 단어 맞히기 빈칸 맞히기
fine tuining 다음 단어 맞히기 각 다운스트림 태스크

 

GPT는 BERT와 달리 pretrain task와 fine tuning 자체가 다음 단어를 맞히는 것으로, 문장 생성 Task에 더 적합한 모델이라 할 수 있다. 

 

6.5.1 토크나이저

  • eos_token은 문장 마지막에 붙는 스페셜 토큰으로 SK Telecom이 모델을 pretrain할 때 지정했으므로 같은 방식으로 사용
  • from transformers import PreTrainedTokenizerFast tokenier = PreTrainedTokenizerFast.from_pretrained( args.pretrained_model_name, eos_token=”</s>”, )

 

6.5.2 문장 생성 전략 수립

문장 생성을 위해서는 문장 생성 전략 수립이 제일 중요하다. 이를 위해, 문장 생성을 위한 단어를 탐색하는 테크닉 종류 두 가지가 있다. 크게 빔서치그리드 서치로 나뉜다. 두 방법 모두 최고 확률을 내는 단어 시퀀스를 찾는 방법이다. 차이점이 있다면 빔서치의 계산량이 그리디 서치의 계산량보다 많다는 것이다. 그 이유는 그리디 서치는 최고 확률을 내는 한 가지 경우의 수를 내지만, 빔서치의 경우 빔 크기만큼의 경우의 수를 낼 수 있기 때문이다. 빔서치는 그리디 서치보다 조금 더 높은 확률을 내는 문장생성이 가능하다는 장점이 있다. 이런 빔서치와 그리드 서치에 도움을 주는 내부 파라미터가 4개 있다. repetition_penalty, top-k sampling, top-p sampling, temperature scaling이다. 

 

repetition_penalty

반복되는 표현을 줄여주는 역할을 한다. default 값은 1.0이다. 만약 이 값을 적용하지 않는다면 그리디 서치와 동일한 효과를 낸다.

 

top-k sampling

모델이 예측한 토큰 확률 분포에서 확률값이 가장 높은 k개 토큰 가운데 하나를 다음 토큰으로 선택하는 기법이다. top_k를 1로 설정할 경우 그리디 서치와 동일한 효과를 낸다.

 

top-p sampling

확률값이 높은 순서대로 내림차순 정렬 후 누적 확률값이 p 이상인 최소 개수의 토큰 집합 가운데 하나를 다음 토큰으로 선택하는 기법이다. 뉴클리어스 샘플링이라고도 한다. 특징은 0에 가까울수록 후보 토큰이 줄어 그리디 서치와 비슷해지며, 1이 되면 모든 확률값을 고려하기 때문에 이론상 어휘 전체를 고려하게 된다.

 

템퍼러처 스케일링(temperature scaling)

토큰 확률 분포의 모양을 변경하는데 이는 모델의 출력 로짓의 모든 요소값을 temperature로 나누는 방식이기 때문이다. 예를 들어 로짓: [-1.0 2.0 3.0], temperature: 2라면 템퍼러처 스케일링 후 로짓은 [-0.5 1.0 1.5]가 된다. 이 템퍼러처 스케일링의 특징은 0에 가까울수록 확률분포모양이 원래보다 뾰족해진다. 또 1보다 큰 값을 설정할 시 확률분포가 평평(uniform) 해진다. 핵심은 이 값이 1보다 적으면 상대적으로 정확한 문장, 1보다 크면 상대적으로 다양한 문장이 생성되는 특징을 가진다. 템퍼러처 스케일링은 top-k 샘플링과 top-p 샘플링과 함께 적용해야 의미가 있다고 한다.

 

 

 

 

7. BERT와 GPT 비교와 모델 크기를 줄이는 기법

BERT는 의미 추출에 강점을, GPT는 문장 생성에 강점을 지닌다. BERT는 트랜스포머의 인코더를 사용했고 GPT는 디코더만을 사용했다. 최근 자연어처리 트렌드는 모델 크기를 키우는 것으로 크기를 키움으로써 언어 모델의 품질이 향상되고 다운스트림 태스크의 성능도 좋아지게 하는 것이다. 하지만 모델의 크기가 너무 커지면 계산 복잡도가 높아지는데 이러한 계산량 또는 모델 크기를 줄이려는 여러 시도가 있는데 대표적으로 4가지가 있다.

디스틸레이션 (distillation)퀀타이제이션 (quantization)프루닝 (pruning), 파라미터 공유 (weight sharing)

 

 

8. 학습 파이프라인

자연어 처리 모델을 학습시키기 위한 일련의 파이프라인은 다음과 같다.

1. 하이퍼파라미터 설정 (learning rate, batch size, epochs, ...)

2. 학습 데이터셋 준비

3. pretrain된 모델 준비

4. tokenizer 준비

5. 데이터 로더 준비

6. 태스크 정의 

7. 모델 학습

의 과정으로 이루어진다.

 

여기서 데이터 로더란 데이터를 모델의 입력에 필요한 형태인 배치(batch)로 만들어주는 역할을 한다. 배치를 만들기 위해 전체 데이터셋 가운데 일부 샘플(instance)를 추출해 배치로 구성한다. 배치 구성을 위한 인스턴스 추출방식에는 랜덤 샘플링과 시퀀셜 샘플링 방식이 있다. 랜덤 샘플링 방식은 주로 train 데이터셋을 구성할 때 사용되고 시퀀셜 샘플링은 valid, test 데이터셋을 구성할 때 사용한다. 배치란 인스턴스의 합이다. 여기서 인스턴스란 문장과 라벨을 갖는 하나의 가장 작은 요소이다. 여기서 인스턴스란 문장과 라벨을 갖는 하나의 가장 작은 요소이며, 인스턴스가 모여서 배치를 이루게 된다. 데이터 로더에는 컬레이트라는 과정이 있다. 컬레이트는 모델의 최종 입력으로 만들어주는 과정이다. 태스크 정의에는 주로 요즘은 파이토치 라이트닝을 사용한다. 그 이유는 딥러닝 모델 학습시 반복적인 내용을 대신 수행해줘 사용자가 모델 구축에만 신경쓸 수 있도록 돕기 때문이다.

 

 

 

9. 자연어 처리 관련 양질 국내 강의

 

 

 

10. 기타 내용

극성 레이블 (polarity label)

  • 예시: 부정 = 0, 긍정 = 1

model.eval()

  • 학습때만 필요한 기능들을 꺼서 평가 모드로 동작하도록 함

tokenizer.encode()

  • 입력 문장 토큰화 후 정수 인덱싱하는 역할 수행
  • 인자로 return_tensors=’pt’를 주게되면 인덱싱 결과를 파이토치 텐서 자료형으로 변환

언어 모델 유용성

  • NSP, MLM: 데이터 제작 비용 절감
  • Transfer Learning

언어 모델 최종 또는 중간 출력값 = 임베딩(Embedding) or 리프레젠테이션(representation)

한 줄 핵심 요약

Word2Vec의 내부 구조로 CBOW와 Skip-gram 방식이 있고 이 두 개의 아키텍처가 기존 NNLM 모델보다 뛰어남.

 

1. Word2Vec 모델의 배경

Word2Vec이라 불리는 이 논문의 핵심은 Word2Vec의 구조에 있다. 우선 Word2Vec은 단어를 분산 표상(distributed representation)하는 방법이다. 분산 표상이란 고차원 공간상에 단어를 continous하게 벡터화시키는 것이다. Word2Vec이 나오기 전인 2003년 가장 초기에는 NNLM(Nueral Network Language Model) 모델이라고 하여 단어를 컴퓨터로 하여금 이해 시키기 위해 내부적으로 one-hot encoding 방법을 사용했다. 하지만 이 one-hot encoding 방법의 sparse matrix라는 것이다. 예를 들면 one-hot encoding은 강아지, 고양이, 호랑이가 있을 때 강아지를 [1, 0, 0]으로, 고양이를 [0, 1, 0]으로 호랑이를 [0, 0, 1]로 표현하는 방식이다.

 

하지만 단점은 표현하고자 하는 단어의 수가 늘어날 수록, 단어를 표현하는 matrix의 크기가 커지지만 matrix 상에 실제 단어를 나타내는 위치 이외에는 전부 0으로 표현된다. 때문에 고차원 공간을 간헐적(?)으로 사용하므로 비효율적이다. 다른 말로 표현하면, one-hot encoding을 사용할 경우 단어를 discrete하게 학습시키는 것이다. 또한 sparse matrix의 단점은 단어간 유사도를 계산할 수 없다는 것이다. 예를 들면 big과 bigger는 의미상 유사성을 띠지만 one-hot encoding은 각각의 단어를 모두 일종의 독립된 개인으로 간주하기 때문에 유사성을 표현할 수 없다. 이러한 one-hot encoding 방식의 한계를 극복하기 위해 나온 것이 분산 표상 방법이다. 분산 표상 방식의 특징은 one-hot encoding과 달리 단어 벡터를 continuous하게 표현가능하다. 이 분산 표상 방식을 제안한 모델이 Word2Vec이다. 

 

 

2. Word2Vec의 구조

앞서 언급한대로 Word2Vec 모델의 핵심은 그 구조에 있다. Word2Vec의 동작에는 내부적으로 2가지 방식이 있다. 첫 번째는 CBOW (continous Bag-of-Words)Skip-gram 방식이다. 아키텍처는 아래 그림과 같다. 

먼저 CBOW 방법은 주변 단어를 의미하는 context에 기반해 중심 단어(centric word)를 예측하는 방식이며, Skip-gram 방식은 중심 단어에 기반해 주변 단어를 예측하는 방식이다. CBOW와 Skip-gram 방식 모두 간단한 구조인 3가지 레이어로 구성되어 있다. Input layer, projection layer, output layer이다. 참고로 Input layer에는 one-hot encoding된 값이 들어가는데 그 이유는 word2vec 모델이 기본 뼈대를 NNLM 모델로 취했기 때문이다.

 

2.1 CBOW

CBOW 방법을 통해 중심 단어를 예측하기 위해서는 주변 단어(앞뒤)의 개수를 결정해야 하는데 이를 window size를 통해 결정 가능하다. 예를 들어 window size가 2일 경우 앞뒤로 2개씩 하여 총 4개의 단어를 CBOW 모델의 입력으로 사용하는 것이다. 다음은 『딥러닝을 이용한 자연어 처리 입문』에서 가져온 CBOW의 동작 원리이다.

window size가 2인 경우 중심 단어를 예측 하기 위해 주변 앞뒤 단어를 2개씩 Input layer에 넣어주게 된다. 만약 중심 단어 앞에 window size만큼의 단어가 없을 경우 가능한 만큼만 입력으로 넣게 된다. 만약 "sat"을 예측하고 싶다면 아래와 같다. 

"sat"에 대한 one-hot encoding된 값인 one-hot vector를 output layer에 label로 두며, Input layer에는 window size 2에 의해 앞 뒤로 두 개의 단어를 one-hot encoding시킨 값을 Input layer에 입력으로 넣어주게 된다. Projection layer의 역할은 lookup table 연산을 담당한다. lookup table이란 주어진 연산에 대해 미리 계산된 결과들의 집합을 의미하는 행렬이다. 쉽게 말해 사전이라 표현할 수 있다. CBOW에서의 lookup table의 구체적인 연산 과정은 아래와 같다.

Projection layer는 M차원을 가진다. M은 하이퍼파라미터로서 임의로 설정될 수 있다. 기존의 one-hot vector의 차원인 7에서 Input layer를 거치게 되면 M이 된다. 위의 예시에서는 "The fat cat sat on the mat"의 단어의 개수인 7개를 차원으로 두고 W라하는 랜덤 초기화된 가중치 행렬과 계산하게 되면 M차원이 된다. W는 차원의 크기를 나타내는 V와 Projection layer의 크기를 나타내는 M의 곱으로 표현된다. 이 때 W를 살펴보면, Input layer에서 입력된 2번째 index에 1이라는 값을 가지는 one-hot vector와 가중치 행렬 W의 곱은 사실상 W 행렬의 2번째 행을 그대로 읽어 오는 것과 동일하다. 이 가중치 행렬 W를 lookup table이라 한다.

 

word2vec에 의해 단어가 학습되면 W가 업데이트 되는데 W의 각 행 벡터는 M차원, 위의 예시로는 5차원 임베딩 벡터로 표현된다. 예를 들면 2번째 index의 값인 cat이라는 단어는 5차원 임베딩 벡터로 [2.1, 1.8, 1.5, 1.7, 2.7]이라는 값을 갖게 된다. W'는 M차원 벡터에서 다시 one-hot vector가 가지고 있던 기존의 차원으로 바뀌는 과정에서 업데이트 된다. 일종의 decoding을 진행하며 가중치 행렬 W'를 학습시킨다 볼 수 있다. 구체적인 과정은 아래와 같다.

 

만약 중심 단어 sat을 예측하고자 하고, window size가 2일 경우 주변 단어를 총 2N개를 input layer에 입력해준다. 이후 가중치 행렬 W에 의해 생성된 결과 벡터들은 Projection layer에서 벡터들의 평균값을 구하게 된다. 이후 구해진 평균 벡터와 가중치 행렬 W'와 곱하여 기존의 one-hot vector와 같은 차원의 값이 도출된다. 여기서 output layer에 label인 "sat"을 예측하기 위해서 내부적으로 아래와 같이 softmax 함수를 사용한다.  

 

softmax 함수를 사용하여 one-hot vector들의 각 원소를 0~1사이의 확률값으로 표현한다. 확률값 중 가장 높은 것이 중심 단어일 가능성이 가장 높은 것이다. 여기까지의 과정을 요약하면 CBOW는 주변단어로 중심 단어를 잘 예측하기 위해 W와 W'를 업데이트해 나가는 방법이라 할 수 있다.

 

이외의 CBOW의 특징은 기존의 BOW와 달리 continuous distributed representation을 사용한다. 또한 Input layer와 projection layer 사이의 가중치 행렬은 NNLM과 같은 방식으로 모든 단어 위치에 대해 공유된다. 또 순서가 projection에 영향을 미치지 않기 때문에 Bag-of-words라 한다.

 

 

2.2 Skip-gram

Skip-gram 방식은 CBOW 방식과 매우 유사하다. 다만 크게 2가지 차이점이 있다. CBOW는 주변단어로 중심단어를 예측했다면 Skip-gram 방식은 아래 그림과 같이 중심단어로 주변단어를 예측하는 것이 첫 번째이다. 

두 번째는 Skip-gram의 Input layer의 입력 값이 중심단어 하나이기에 projection layer에서 벡터의 평균을 구하지 않는다는 것이다. Skip-gram의 전체 과정을 도식화 하면 아래와 같다.

Input layer에 중심 단어를 입력하며, 벡터 평균 계산이 없는 projection layer를 거쳐 주변단어의 label이 위치한 output layer로 학습이 이루어진다. Skip-gram이 CBOW보다 성능이 좋다고 알려져 있다 한다. 

 

 

3. 모델 성능 및 결론

기존 모델은 RNNLM과 NNLM을 뛰어넘는 성능을 보임

여러 NNLM 변형과 CBOW, Skip-gram의 성능 비교 결과 저자들이 내세운 아키텍처의 성능이 전반적으로 높은 것을 확인 가능

 

 

논문에는 연구 배경이나 연구 목표들의 여러 내용이 있었지만 핵심만 요약하자면 Word2Vec의 구조인 CBOW와 Skip-gram 방식의 메커니즘이 그 핵심이며 또한 그 결과 CBOW와 Skip-gram 방식이 기존 NNLM 모델보다 뛰어나다는 것이다.

 

이외의 장점은

1. 벡터 산술 연산이 가능하다. ex: King - Man + Woman = Queen

2. Very simple한 모델 아키텍처로 고퀄리티로 단어 벡터를 학습 가능하다.

3. 기존 모델들 대비 낮은 계산 복잡도를 가진다.

4. continous representation of word를 계산하기 위해 기존엔 LSA, LDA를 사용했으나 word2vec은 LSA를 뛰어 넘었고 LDA는 이제 계산 비용이 높은 알고리즘이 되었다.

 

 

4. 기타 추가로 알게된 부분

  • word2vec은 feedforward NNLM의 한계인 context length 문제를 해결하기 위해 고안됨.
  • RNNLM에서의 벡터는 Context Vector 또는 Thought Vector라 부름
  • projection layer: 기존 입력층이 이산 표상인데 비해 입력층에서 넘어오게 되면 연속 표상으로 바뀜.
  • word2vec은 은닉층이 1개인 shallow NN임
  • word2vec은 일반적 은닉층과 달리 활성화 함수가 존재X, lookup table 연산을 담당하는 projection layer가 있음
  • DistBelief → 병렬 실행 가능이 핵심
    • 모델을 여러개로 복제해서 병렬로 실행하고, 중앙집중화 서버를 통해 gradient 업데이트를 동기화가능하게 함.
    • DistBelief 프레임워크에서는 일반적으로 100개 이상의 모델 복제본을 사용함.
  • paraphrase detection (의역 예측 = 같은지 다른지)
    • 집가서 밥 먹었다.
    • 귀가 후 식사 했다.

 

Reference

[1] https://wikidocs.net/22660

[2] https://jiho-ml.com/weekly-nlp-28/

[3] https://sonsnotation.blogspot.com/2020/11/11-attention-transformer-models.html

 

* 구현 코드만 확인하고 싶은 경우 맨 아래의 깃허브 링크를 참조.

 

1. Competetion 개요

3년전 캐글에 올라온 주제인 Real and Fake Face Detection이라는 competition이 있다. 소셜 네트워크를 사용할 때 가짜 신분을 사용하는 사람이 있고 이를 탐지하여 WWW을 더 나은 곳으로 만들자는 것이 이 competition의 목적이다.

 

Competetion의 핵심 task는 크게 아래 2가지를 탐지하는 것이다. 

 

1. 데이터셋 내에서 변조된 얼굴 이미지변조되지 않은 얼굴 이미지분류하는 것

 

2. 만약 얼굴 이미지가 변조되었다면 변조된 영역분류하는 것

 

변조된 영역은 아래와 같이 네 부분으로 나뉜다. 왼쪽 눈, 오른쪽 눈, 코, 입이다. 

 

 

2. 데이터셋 구성 및 특징

데이터셋의 경우 포토샵 전문가가 변조/합성한 얼굴 이미지와 변조/합성하지 않은 얼굴 이미지로 구성되어 있다. 이렇게 포토샵으로 만들어진 데이터셋의 경우 GAN 기반의 데이터셋과 비교가될 수 있다. 이 데이터셋을 만든 배경은 GAN으로만은 충분하지 않다는 것이다. GAN은 Fake 이미지를 생성하기 쉬울 뿐더러 포토샵 전문가가 만드는 것과 GAN이 만드는 메커니즘 자체가 다르기 때문에 GAN이 학습한 패턴은 무의미할 수 있다는 것이다.

 

데이터셋 폴더 구성은 아래와 같다. 

 

 

데이터셋은 크게 두 개로 훈련 데이터셋(real_and_fake_face_detection)과 테스트 데이터셋(real_and_fake_face)으로 구성되어 있고, 이는 다시 각각 합성 이미지(training_fake)와 원본 이미지(training_real)로 구성되어 있다. 특히 합성 이미지의 경우 난이도가 크게 3가지로 easy, mid, hard로 나뉜 것이 특징이며 아래와 같이 mid 난이도만 되어도 시각적으로 합성 여부 탐지가 어려운 것이 특징이다.

 

mid_389_1101.jpg

 

라벨링된 이미지를 확인하던 도중 아래와 같이 라벨링이 잘못되어 있어 학습에 사용하기 어려운 이미지를 확인하였고, 따라서 아래의 해당 이미지는 학습에서 제외하였다. 

 

라벨링 오류 데이터셋 (easy_116_111.jpg)

 

따라서 본 Task를 수행하기 위해 아래와 같이 총 4,080장의 데이터셋을 이용하였다.

구분 폴더명 장수 중합 총합
train training_fake 959장 2,040장 4,080장
training_real 1,081장
test training_fake 959장 2,040장
training_real 1,081장

 

캐글에서 배포하는 이 데이터셋에서 아쉬운 점은 크게 두 가지가 있는데 첫 번째는 폴더 구성이 그다지 직관적이지 않다는 것이며, 두 번째는 training set과 test set이 동일한 데이터셋으로 구성되었다는 것이다. 그러다 보니 결과적으로 높은 성능이 나오기 쉬운 구조다. 하지만 본 데이터셋을 통해 캐글에 처음 입문하는 케이스로는 해볼만 하다고 판단된다. 

 

 

 

 

3. 관련 연구

관련 연구를 찾기 위해 SOTA (State Of the Art) 연구를 모아둔 Paperswithcode라는 사이트에서 fake face detection과 관련된 자료를 찾던 도중 아래와 같은 연구를 확인할 수 있었다.

논문을 리뷰해본 결과 위 연구에서 사용하는 데이터셋이 GAN으로 만들어진 것이 아니라 본 competition과 동일하게 포토샵 기반의 데이터셋을 이용하는 것을 확인하였다. 

 

 

위 연구에서 실험을 위해 OpenImage와 Flickr에서 데이터셋을 마련하였으며 크게 변조되지 않은 이미지와 변조된 이미지로 구성하여 훈련, 검증, 테스트 셋으로 나누었다. 변조된 이미지의 변조 방법의 경우 Random FAL(일종의 변조 자동화 포토샵 스크립트)과 Pro Artist가 변조하는 방법을 사용하였다. 

 

저자는 실험을 위해 크게 두 가지 모델을 만들었다. 이는 얼굴이 합성되었는지를 여부를 식별하는 글로벌 분류 모델과 합성된 위치를 예측하는 로컬 분류 모델이다. 따라서 위 연구를 기반으로 본 competetion에서도 합성 탐지 여부 모델과 합성 부위 예측 모델을 만들었다.

 

저자들은 실험을 위해 ImageNet 데이터셋으로부터 Pretrained된 DRN(Dialted Residual Network)의 변형인 DRN-C-26 모델을 사용하였다. 이를 통해 얻은 실험 결과는 다음과 같다.

 

 

저자들은 모델에 대해 스크립트를 통해 랜덤으로 변조한 Random FAL 방식과 프로 아티스트가 변조한 Professional Artist 방식에 대해 모델의 성능을 측정하였다. 성능 측정 결과, 고해상도의 인풋이 들어가면 더 높은 성능을 보이는 것을 확인하였다. 또한 augmentation이 없이 학습된 것이 더 뛰어난 것을 보였으나 augmentation을 적용한 것이 반대로 조금 더 모델이 robust한 결과를 보인다 하였다.

 

4. 모델 구현

4.1 원본/합성 판별 모델

위 관련 연구의 실험에 기반하여 이미지의 원본/합성 판별을 위한 모델과 합성된 위치를 예측하는 모델로 크게 두 개를 구현하였다. Pretrained Model을 사용하지 않고 학습을 위해 두 모델 모두 이미지 처리에 효율적이라 판단되는 Convolution Neural Network를 사용하였다.

 

4.1.1 레이어 구성

원본/합성 판별 모델 (syn_detect_model)

모델에 입력되는 이미지의 사이즈는 원본의 경우 600x600이나 원본 그대로 모델 학습시켜본 결과 시간이 오래 걸린다는 단점이 있어 300x300으로 축소하여 모델의 Input으로 지정하였다. 이후 Convolution 2D, Max Pooling, Batch Normalization, flatten, Dense 레이어로 모델을 구성하였고 출력 부분에는 sigmoid 함수를 통해 이진 분류가 가능하도록 하였다.

 

4.1.2 모델 학습 결과

 

학습 초반에는 위와 같이 loss 값이 0.9013, accuracy 0.5162로 시작하는 것을 확인할 수 있었고, 학습 종료 시점에는 아래와 같이 loss 값이 0.0479, accuracy가 0.9931임을 확인할 수 있었다. 또한 추가적인 다른 metric을 통해서도 학습이 전반적으로 잘 이루어지는 것을 확인할 수 있었다.

 

4.2 합성 위치 예측 모델

4.2.1 레이어 구성

합성 위치 예측 모델 (loc_detect_model)

합성 위치 예측 모델 또한 원본/합성 탐지 모델과 거의 동일한 구조를 가지고 있다. 차이점은 학습 과정에서 빠른 과적합이 이루어지는 것을 확인하였다. 따라서 Dropout 레이어를 추가하였고 출력 부분에는 라벨을 15개로 지정하여 softmax 함수를 통해 출력되도록 하였다. 라벨에 대한 구체적인 내용은 실험 부분에 기술하였다.

 

4.2.2 모델 학습 결과

학습 종료 시점에 아래와 같이 loss 값이 0.0586, accuracy가 0.9875임을 확인할 수 있었고 추가적인 다른 metric을 통해서 학습이 전반적으로 잘 이루어져 있음을 확인할 수 있었다.

 

 

5. 실험

5.1 원본/합성 판별 모델

5.1.1 이미지 어그멘테이션 (Image Augmentation)

어그멘테이션을 진행하지 않고 원본/합성 탐지 모델을 학습한 결과 충분한 성능이 도출되지 않았다. 때문에 어그멘테이션 작업을 필요로 했고 아래와 같은 옵션을 사용하여 어그멘테이션을 진행하였다.

 

구분 옵션 비고
훈련 데이터셋 horizontal_flip True 수평 플립
vertical_flip False 수직 플립
shear_range 0.2 기울기 비율
zoom_range 0.2 확대 비율
rescale 1/255. 이미지 스케일링
테스트 데이터셋 rescale 1/255. 이미지 스케일링
validation_split 0.2 훈련/평가 분할 비율

 

모델의 Robustness를 위해 수평 플립을 True로 설정하였고 기울기와 확대 비율에 대해 주로 사용되는 값인 0.2를 설정하였다. 또한 훈련 데이터셋과 테스트 데이터셋에 대해 각각 이미지 스케일링을 적용하였고 테스트 데이터셋에 대해서는 0.2 비율로 평가와 테스트 데이터셋으로 나누었다. 어그멘테이션 옵션을 적용한 결과는 다음과 같다.

 

 

Training 데이터셋 2,040장 Validation 데이터셋 1,633장 Test 데이터셋 407장에 대해 어그멘테이션을 진행하였다.

 

5.1.2 모델 레이어 구성

 

6개의 Convolution 2D 레이어와 3개의 Maxpooling 2D 레이어, 그리고 3개의 Batch Normalization 레이어를 구성하고 출력부분에는 활성화 함수로 sigmoid를 사용하여 합성 여부를 탐지할 수 있도록 만들었다. Dropout의 경우 모델이 과적합되지 않아, 별도로 적용하지 않았다. 모델의 옵티마이저는 RMSProp을 사용하였고 learning rate 값으로 0.00001을 사용하였다.

 

모델 구성의 순서에서 CS231n 강의 기준으로 Conv2D → BatchNorm → MaxPool2D 순으로 쌓는 것이 적합하다 하였으나 Conv2D → MaxPool2D → BatchNorm 형태로 구성하는 것이 더 학습 성능이 좋은 것을 실험적으로 확인하였다.

 

 

5.1.3 모델 학습 결과

100회의 에폭을 통해 학습한 결과는 아래와 같다.

학습 과정

Early Stop을 적용하여 58번째 에폭에서 종료되었고, 점진적으로 loss가 줄고, accuracy가 증가하는 양상을 확인할 수 있었다. 부가적으로 precision, recall, auc에 대한 평가 metric을 추가하여 확인할 수 있도록 하였다. 학습된 모델에 대해 validation set으로 평가한 결과 아래와 같다.

 

 

결과 값으로 Loss값 5.6%, Accuracy값 98.7%, Precision 99.4%, Recall 98.0%, AUC 99.9%을 확인할 수 있다.

 

또한 별도의 인퍼런스 모듈을 생성하여 아래와 같이 확인하였다.

Inference 과정

 

최종적으로 합성 여부 탐지를 위해 Test 데이터셋의 모든 fake 이미지를 넣은 결과 위와 같이 대부분의 이미지가 올바르게 Inference되는 것을 확인할 수 있고, 일부 Real로 오분류 되는 것 또한 확인할 수 있다. Inference를 통해 최종적인 F1 Score의 경우 98.8%의 성능을 보임을 확인하였다.

 

F1 Score

 

5.1.4 손실함수 및 정확도 그래프 시각화

 

모델 학습의 초반부에 변화가 심한 양상을 보이기도 하나 점차 변화 폭이 좁아지면서 안정적으로 학습되는 것을 확인할 수 있다. Loss는 약 0.9에서 시작하여 0.05까지 줄어들며, Accuracy는 약 0.55부터 시작하여 0.99에 근접하게 학습되는 것을 확인할 수 있다.

 

5.2 합성 위치 예측 모델

5.2.1 데이터 프레임 생성

합성 여부와 달리 합성 위치에 대한 예측 모델을 만들기 위해서는 별도의 formal한 구조로 만들어 두어야 학습에 용이할 것이라 판단되어 아래와 같이 판다스(Pandas) 라이브러리를 이용하여 데이터프레임을 생성하였다.

Dataframe 생성

파일 이름의 합성 부위를 나타내는 4자리의 숫자만 추출하여 각각의 필드로 나타내었다. 특이한점은 label이라는 컬럼을 만들었는데 이는 기존 left_eye, right_eye, nose, mouth와 같이 별개의 컬럼을 라벨로 지정하여 학습을 시도한 결과, 방법론을 잘못 적용한 것인지 학습 결과가 좋지 않았다. 마지막 레이어에 sigmoid 또는 softmax로 Unit을 4로 설정하여도 학습 결과가 미미한 것을 확인하였다. 전반적으로 모델 설계 방식에 대한 방법론 조사를 시도했으나 크게 찾지 못하였다. 하지만 조사 도중 일부 예시에서 위와 같이 개개 별로 나누는 것이 아닌 하나로 통합하여 라벨로 두는 방법을 찾아 있는 것을 확인하였고, 이를 적용하였더니 학습 결과가 개선되는 것을 확인할 수 있었다.

 

5.2.2 이미지 어그멘테이션 (Image Augmentation)

합성 여부 탐지 모델과 달리 변조 부위 탐지 모델의 경우 합성된 이미지만 사용해야 했기에 충분한 학습을 위해 이미지 어그멘테이션 작업을 진행하였다. 다음은 이미지 어그멘테이션을 위해 사용한 옵션의 종류와 값을 나타내었다.

 

구분 옵션 비고
학습 데이터셋 horizontal_flip False 수평 플립
vertical_flip False 수직 플립
shear_range 0.2 기울기 비율
zoom_range 0.2 확대 비율
테스트 데이터셋 validation_split 0.2 훈련/평가 분할 비율

 

왼쪽 눈과 오른쪽 눈의 예측 위치가 바뀌어선 되지 않기에 수평 플립을 False로 주었고, 적은 데이터셋이기에 학습 데이터셋의 일부를 수직 플립하는 옵션 또한 False로 적용하였다. 나머지 옵션인 기울기, 확대, 분할 비율의 경우 일반적으로 0.2를 설정한다고 판단하여 값을 설정하였다.

 

Training Set의 경우 총 959개를 사용하였고, Validation Set과 Test Set은 0.2 비율로 각각 768개, 191개로 나누어 어그멘테이션을 진행하였다.

 

5.2.3 모델 레이어 구성 (Model Layer Structure)

 

합성 여부 탐지 모델과 동일하게 레이어를 구성하였으며 달라진 부분은 출력 부분에 합성의 경우의 수인 15가지에 대해 softmax 함수를 적용한 것이다. 15가지는 합성 부위 별로 나올 수 있는 경우의 수인 $2^4$에서 합성되지 않은 0000에 대한 경우를 제외한 것이다. 또한 Dropout 레이어를 추가하여 학습이 과적합 되지 않도록 하였다. Dropout을 0.1로 모두 적용하여 진행한 결과 적용하지 않은 것과 비슷하게 대부분의 평가 메트릭에서 99%대를 확인할 수 있었고, Dropout을 0.2로 적용한 결과 조금 더 느슨하게 학습되는 것을 확인할 수 있었다. 다음은 모델학습 결과이다.

 

5.2.4 모델 학습 결과

모델 학습 결과 (epoch: 61 종료)

 

61번째 에폭에서 Early stop이 적용되어 종료되었다. 점진적으로 loss가 줄고, accuracy가 증가하는 양상을 확인할 수 있었고, 부가적으로 precision, recall, auc에 대한 평가 metric을 추가하여 확인할 수 있도록 하였다. (그림의 가시성을 위해 val_precision, val_recall, val_auc 정보를 담지 않았다) 모델에 대해 Validation Set을 사용하여 평가한 결과 다음과 같다.

 

모델 Evaluation 결과

모델 성능 평가 결과 값으로 Loss 값 16%, Accuracy 값 96.9%, Precision 99.0%, Recall 94.0%, AUC 99.9%를 확인할 수 있다. 다음으로 학습한 합성 부위 예측 탐지 모델에 대해 합성 이미지 전체를 입력으로 넣어 추론을 진행하는 과정을 아래와 같이 진행하였다.

 

Inference 진행 과정

0 ~ 15사이의 값을 0001, 0010, 0011 ~ 1111 값으로 딕셔너리로 아래와 같이 매핑 해두었다.

 

 

실제 합성 이미지가 들어 갔을 때 실제 값과 예측 값을 출력하였으며 만약 다를 경우 Fail이라는 문자열을 출력하도록 하여 구분하도록 하였다. 올바르게 합성 부위를 탐지한 결과의 경우 부위를 합성 부위를 True/False로 표현하여 확인할 수 있도록 하였다. 전체 959장의 합성 이미지에서 Inference를 통해 F1 Score를 계산한 결과는 다음과 같다.

 

 

Inference 진행 결과, F1 Score 97.0%로 학습이 매우 잘 이루어졌음을 확인할 수 있다.

 

5.2.5 손실함수 및 정확도 그래프 시각화

 

합성 여부 탐지 모델과 달리 초반 변화폭이 큰 양상이 없으며 전반적으로 loss 값이 줄어들고 accuracy가 증가하는 추세를 확인할 수 있다. 이와 같은 추세와 F1 Score를 통해 모델 학습 잘 이루어졌음을 알 수 있다.

 

코드: https://github.com/roytravel/fake-face-detection/

 

[1] 『Detecting Photoshopped Faces by Scripting Photoshop』 - ICCV 2019

선형대수학을 볼 때마다 느끼지만 대수적으로만 기술되어 있다보니 늘 추상적으로 느껴지고 직관적이지가 않았습니다. 이번에 국문과 공대생이라는 블로그를 운영하시는 분의 글을 보다가 명불허전이라는 분의 선형대수 강의를 수강하고보니 선형대수 이해를 이렇게 잘 알려주는 사람이 있구나 하면서 감탄했습니다. 아무래도 선형대수에서 흔히 배우는 방식인 대수적인 측면이외에도 기하적인 측면의 설명과 더불어 시각화 자료가 높은 이해도의 핵심을 차지 한 것 같습니다. 

 

늘 선형대수를 보며 그래서 대체 물리와 어떻게 연결해 설명할 수 있는 것인지 궁금했습니다. 중고등학교때 함수나 미적분을 왜 배우는지 몰랐지만 지나고나니 물리학에서 파동을 기술하거나 다른 물리량을 알아내는 데에 쓰인다는 것을 알게 되었고, 수학으로 물리학을 말할 수 있다는 사실이 흥미로워지면서 더 관심을 갖게 됐습니다. 마찬가지로 선형대수도 물리학적으로 가지는 의미를 이해하면 더 이해가 높아질 것 같아서 그런 강의가 없을까 했는데 3Blue1Brown라는 유튜버의 강의를 접하고나서 많이 해소가 된 것 같습니다.

 

링크는 https://www.youtube.com/playlist?list=PLZHQObOWTQDPD3MizzM2xVFitgF8hE_ab 입니다. 총 16강으로 구성되어 있구요 각 강의는 10분대 내외로 짧게 이루어집니다. 서두가 길어지면서 홍보글 같긴하네요. 아무튼 강추합니다. 

 

첫 강의에서부터 선형대수를 바라볼 수 있는 3가지 측면을 언급하면서 시작합니다. 물리학의 관점, 컴퓨터 과학의 관점, 수학의 관점이 있다고 이야기합니다. 선형대수에서 자주 쓰이는 벡터는 물리학의 관점에서 화살표와 동일하며, 컴퓨터과학에서는 숫자 리스트, 수학의 관점에서는 모든 것이라 이야기합니다. 관점으로는 간단히 여기까지만 이야기하고 이후로는 선형대수의 개념에 대해 설명을 진행합니다. 저는 물리 이외에도 (당연하지만) 컴퓨터과학과 수학의 관점에서 바라볼 수 있다고 언급한 것에서 넘어가서 계속 보게 된 것 같습니다.

 

기초 벡터 연산

선형대수학에서는 크게 두 가지 기본적인 연산으로 이루어집니다. 벡터 합스칼라곱입니다. 벡터는 단순합니다. 화살표라고 생각해도 됩니다. 또는 화살표의 좌표를 나타내는 것이라 생각하면 됩니다. 이후 강의를 통해 스칼라의 의미를 처음으로 제대로 이해하게 됐습니다. 

 

 

스칼라는 단순히 숫자로만 이해하고 있었으나 위와 같이 하나의 벡터(화살표)의 길이를 늘리거나 줄이거나 방향을 바꾸는 것을 Scaling이라 하는데, 위의 그림과 같이 2, 1/3, -1.8과 같이 벡터 스케일링에 사용되는 숫자들을 스칼라(scalar)라고 한다고 합니다. (스칼라의 영어발음이 스케일라였습니다.) 즉 선형대수에서 스칼라는 벡터 스케일링을 목적으로 사용됩니다.

 

벡터에 스칼라를 곱하는 것은 숫자 리스트(행렬)라는 개념에서 리스트의 각 원소에 숫자를 곱하는 것과 같습니다. 스칼라를 이용해 벡터를 스케일링할 수 있고 이렇게 스케일링된 두 벡터끼리 더하는 것을 선형결합(linear combination)이라고 합니다. 

 

기저

기저 또한 처음으로 시원하게 강의를 듣게 되어 이해할 수 있었습니다. 기저를 설명하기 위해서는 먼저 2차원 x, y 좌표계상의 특별한 두 가지 벡터가 있다고 합니다. 바로 x축에 있는 단위 벡터(unit vector)인 $\hat{i}$와 y축에 있는 단위 벡터인 $\hat{j}$입니다. 이 둘을 좌표계의 기저(basis)라고 합니다. 

 

 

그렇다고하면 이 기저는 어떤 의미를 가지는가?라고 했을 때 핵심은 벡터의 변환을 구할 수 있다는 것입니다. 조금 더 설명하면 어떤 벡터 $(x, y)$가 있을 때 어떤 행렬을 거쳐서 공간상에 $(x', y')$로 매핑된다면 그 기저는 어떤 행렬이 되는 것입니다. 즉 Input vector $(x, y)$는 기저만 알고 있다면 바로 output vector $(x', y')$를 도출해낼 수 있는 것과 같습니다. 조금 더 자세히 말해 벡터가 행렬로부터 변환이 된다면 벡터를 이루는 기저 또한 변환이 되는데 결과적으로 변환된 기저벡터를 알고 있다면 어떤 벡터 $(x, y)$가 와도 변환을 바로 시킬 수 있다는 것입니다. (보충 설명은 앞으로 작성될 선형변환 포스팅에서 참고 바랍니다)

 

이 기저라고 하는 것은 암묵적으로 원점을 기준으로 하는 것으로 사용하고 있습니다. 때문에 움직이는 것은 두 벡터만 움직입니다. 두 벡터를 가지고 어디에 활용할까요? 아래와 같이 2차원 공간상의 모든 좌표를 표현할 수 있게 됩니다. 무한하고 평평한 2차원 평면을 만들 수 있다는 것입니다. 

 

 

이렇게 두 벡터를 통해 표현할 수 있는 공간을 Span이라고 합니다. 정확히 Span의 사전적 정의는 주어진 두 벡터 쌍의 조합으로 나타낼 수 있는 output vector의 집합입니다. 2차원 벡터쌍의 span은 대부분 2차원 공간 전체가 됩니다. 

 

하지만 이러한 span이 특정 선 위로 제한이 되는 경우도 있습니다. 바로 두 벡터 중 하나가 다른 벡터와 겹치게 되는 경우 입니다.

 

 

이럴 때는 Span이 2차원 공간이 아니라 단순히 직선 하나로 볼 수 있습니다. 이렇게 하나의 벡터가 다른 벡터에 겹치게 되어 Span이 한 차원 확장되지 못하는 경우를 선형 종속(Linear Dependent)이라고 합니다. 다시 말해 벡터가 하나만 있었다면 1차원 선만 표현하지만 2개라면 2차원을 표현해야합니다. 하지만 2개의 벡터가 있음에도 불구하고 1차원 밖에 표현하지 못하는 것을 의미합니다. 

 

반대로 하나의 벡터를 추가하여 기존 Span이외에 다른 차원을 추가해주는 것이 가능하다면 선형 독립(Linear Independent)라고 합니다. 이는 3차원에서도 마찬가지입니다.

 

 

세 개의 벡터가 있을 때 스팬은 모든 가능한 선형결합의 결과집합입니다. 즉, 세 개의 벡터로 모든 3차원 공간을 다 만들 수 있는 것과 동일합니다. 하지만 2차원과 마찬가지로 예외가 존재하는데, 만약 세 번째 벡터가 두 개의 벡터가 만드는 스팬(평면)에 놓여있다면 세 번째 벡터를 추가해도 스팬이 바뀌지 않습니다. 이를 선형종속이라 하였습니다.

우분투에 아나콘다 환경을 설치하는 법에 대해 간단 정리하려 합니다. 윈도우에는 자주 설치하였으나 점점 머신러닝/딥러닝 환경을 우분투에 구축할 필요성을 느껴 기록용으로 작성합니다.

 

Anaconda를 설치하기 위한 과정은 크게 5가지 밖에 없어 간단합니다. (셸스크립트 다운, 실행, 설치경로설정, 환경변수 설정/적용, conda 자동 실행 off)

 

1. 설치 셸 스크립트 다운로드

wget https://repo.anaconda.com/archive/Anaconda3-2021.05-Linux-x86_64.sh

wget을 통해 아나콘다를 설치할 수 있는 셸 스크립트를 가져와줍니다. 실행할 경우 아래와 같이 다운로드 되는 것을 확인할 수 있습니다.

 

만약 더 최신버전으로 받고 싶을 경우 아나콘다 홈페이지 https://www.anaconda.com/products/individual에 들어가줍니다. 이후 맨 아래로 스크롤을 내리면 우분투 설치가 보입니다. 

 

마우스 오른쪽을 눌러주고 URL을 복사해서 wget에 입력해주시면 됩니다.

 

2. 설치 셸 스크립트 실행

sh Anaconda3-2021.05-Linux-x86_64.sh

이후 아래와 같이 설치 진행 화면이 보이면 Enter를 입력하고 Space를 계속 눌러 줍니다.

 

 

3. 아나콘다 설치 경로 설정

다음은 설치 경로를 지정하는 것인데 Default로 사용자 계정 바로 아래에 설치하는 것이니 Enter를 쳐줍니다. 필요시 원하는 경로를 입력해주시면 됩니다.

 

 

4. 환경변수 등록 및 적용

이후 환경변수에 등록할 것을 묻습니다. yes로 입력해줍니다.

 

 

yes를 입력해주면 환경변수가 등록되었습니다. 이제 등록과는 별개로 적용을 해주어야 합니다.

source ~/.bashrc 명령을 통해 적용해줍니다.

 

 

5. conda 자동실행 off

conda config --set auto_activate_base false

단순히 환경변수 적용만하고 나면 앞으로 자동으로 conda의 base라는 이름의 환경이 켜집니다. 따라서 우리가 원할때만 활성화 할 수 있도록 자동 활성화를 꺼줍니다.

 

 

아나콘다 설치가 완료되었습니다.

 

 

Reference

[1] https://thinmug.tistory.com/51

자연어처리 프로젝트 진행 도중 우분투에 딥러닝 학습 환경을 만들 필요성이 생겼고 설치 과정을 정리하여 이후에 참고하고자합니다. 먼저 우분투에 딥러닝 환경 설치하는 방법에 대해서는 https://webnautes.tistory.com/1428에서 참조하였습니다. 참조한 블로그의 포스팅과 차이점은 핵심만 추려 간소화한 것입니다.

 

딥러닝 환경을 구성하기 위해서는 크게 3가지가 필요하다.

 

1. NVIDIA 드라이버

2. CUDA

3. cuDNN

 

이를 위해 아래의 명령어를 순차적으로 실행하면 된다.

 

우분투 최신 환경 유지

sudo apt-get update

sudo apt-get upgrade

 

현재 사용중인 그래픽카드에 설치할 수 있는 드라이버 목록 확인

ubuntu-drivers devices

 

* 만약 ubuntu: command not found 에러가 발생한다면 아래와 같이 관련 패키지 설치를 우선하면 된다.

sudo apt-get install -y ubuntu-drivers-common

 

 

결과로 확인된 driver 중 하나를 설치하며, 가급적 recommended라 표시된 것을 설치한다. 설치 이후 적용을 위해서 재부팅이 필요함.

 

만약 명령어의 결과값이 위 처럼 나오지 않고 출력값이 없을 경우 그래픽카드가 올바르게 연결되어 있지 않은 것이므로 연결을 잘 확인해야 함.

 

NVIDIA 드라이버 설치

sudo apt-get install nvidia-driver-470

nvidia-smi

 

Driver Version으로부터 설치한 드라이버 버전인 470을 확인할 수 있음

 

https://developer.nvidia.com/cuda-toolkit-archive에 접속해서 CUDA Toolkit 11.4.2를 클릭. 이후 아래와 같이 Linux, x64_64, Ubuntu, 20.04, deb(local)를 선택. (각자 환경에 적합한 것으로 선택)

 

선택 이후 아래의 명령을 확인할 수 있고 터미널에 아래의 명령어를 입력

 

wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/cuda-ubuntu2004.pin
sudo mv cuda-ubuntu2004.pin /etc/apt/preferences.d/cuda-repository-pin-600
wget https://developer.download.nvidia.com/compute/cuda/11.4.2/local_installers/cuda-repo-ubuntu2004-11-4-local_11.4.2-470.57.02-1_amd64.deb
sudo dpkg -i cuda-repo-ubuntu2004-11-4-local_11.4.2-470.57.02-1_amd64.deb
sudo apt-key add /var/cuda-repo-ubuntu2004-11-4-local/7fa2af80.pub
sudo apt-get update
sudo apt-get -y install cuda

(낮은 버전에서는 위 그림보다 더 간소화된 설치 command를 제공한다. 그 때 설치 에러가 발생한다면 sudo apt-get install gcc를 통해서 gcc를 설치하면 된다)

 

CUDA Toolkit 관련 설정을 환경 변수에 추가 후 적용

sudo sh -c "echo 'export PATH=$PATH:/usr/local/cuda-11.4/bin' >> /etc/profile"

sudo sh -c "echo 'export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/cuda-11.4/lib64' >> /etc/profile"

sudo sh -c "echo 'export CUDADIR=/usr/local/cuda-11.4' >> /etc/profile"

source /etc/profile

 

버전이 다르다면 위 명령어에서 11.4 말고 설치한 버전만 바꾸어 입력하면 된다.

 

cuDNN 다운로드

https://developer.nvidia.com/cudnn 에 접속하여 Download cuDNN 클릭 후 로그인. 이후 Download cuDNN v8.2.4 ~ for CUDA 11.4를 선택하여 다운로드

 

cuDNN 파일 압축해제 및 파일 복사

cd Downloads

tar -xvzf cudnn-11.4-linux-x64-v8.2.4.15.tgz

sudo cp cuda/include/cudnn* /usr/local/cuda/include

sudo cp cuda/lib64/libcudnn* /usr/local/cuda/lib64

sudo chmod a+r /usr/local/cuda/include/cudnn.h /usr/local/cuda/lib64/libcudnn*

 

 

cuDNN 파일 심볼릭 링크 설정

sudo ln -sf /usr/local/cuda-11.4/targets/x86_64-linux/lib/libcudnn_adv_train.so.8.1.0 /usr/local/cuda-11.4/targets/x86_64-linux/lib/libcudnn_adv_train.so.8

sudo ln -sf /usr/local/cuda-11.4/targets/x86_64-linux/lib/libcudnn_ops_infer.so.8.1.0  /usr/local/cuda-11.4/targets/x86_64-linux/lib/libcudnn_ops_infer.so.8

sudo ln -sf /usr/local/cuda-11.4/targets/x86_64-linux/lib/libcudnn_cnn_train.so.8.1.0  /usr/local/cuda-11.4/targets/x86_64-linux/lib/libcudnn_cnn_train.so.8

sudo ln -sf /usr/local/cuda-11.4/targets/x86_64-linux/lib/libcudnn_adv_infer.so.8.1.0  /usr/local/cuda-11.4/targets/x86_64-linux/lib/libcudnn_adv_infer.so.8

sudo ln -sf /usr/local/cuda-11.4/targets/x86_64-linux/lib/libcudnn_ops_train.so.8.1.0  /usr/local/cuda-11.4/targets/x86_64-linux/lib/libcudnn_ops_train.so.8

sudo ln -sf /usr/local/cuda-11.4/targets/x86_64-linux/lib/libcudnn_cnn_infer.so.8.1.0 /usr/local/cuda-11.4/targets/x86_64-linux/lib/libcudnn_cnn_infer.so.8

sudo ln -sf /usr/local/cuda-11.4/targets/x86_64-linux/lib/libcudnn.so.8.1.0  /usr/local/cuda-11.4/targets/x86_64-linux/lib/libcudnn.so.8

 

마찬가지로 버전이 다르다면 11.4를 설치한 버전으로 바꾸면 된다.

 

새로 추가된 라이브러리를 시스템에서 찾을 수 있도록 함

sudo ldconfig

 

 

루트 디렉터리로 이동

cd

 

 

링크 설정이 정확하게 되었는지 확인

ldconfig -N -v \$(sed 's/:/ /' <<< $LD_LIBRARY_PATH) 2>/dev/null | grep libcudnn

위와 같이 8.2.4가 보여야 함.

 

여기까지하면 딥러닝 환경 설치에 필요한 NVIDIA 드라이버, CUDA Toolkit, cuDNN 설치를 완료한 것임.

 

실제로 GPU가 학습에 사용될 수 있는지 여부를 확인하기 위해서는 추가적으로 텐서플로우를 설치.

 

pip3 install tensorflow

이후 위의 tf.config.list_physical_devices("GPU")를 통해 successful이 보인다면 정상적으로 학습에 사용할 수 있음. 끝.

 

 

설치 자체는 위 명령어들만 입력해주면 간단하게 설치되어서 문제 없었음. 하지만 도중에 그래픽 카드 낮은 성능인거 확인하고 다른 그래픽카드로 갈아끼우는데 애먹어서 힘들었음. 그래픽 카드 장착 해제하기 위해서 멈치 찾고 누르는게 어찌나 어렵던지... 갈아끼우고 나서 ubuntu-drivers devices 명령어 입력 했을 때 결과가 출력되지 않아서 당황도 했음. PCI 선을 꼽지 않아서 그래픽 카드에 연결이 되지 않았던 것임. 그래서 연결하려고 PCI 선 하나 꼽았다가 비프음 4번 나는거 듣고 파워 부족이라는거 보고 PCI 선 다 넣어주고 켰더니 이번엔 비프음 5번 또 발생함. 그래픽카드와 모니터가 연결안되어서 발생하는 문제였음. 그래서 올드한 VGA 케이블에서 HDMI로 바꾸니까 됐음. 연구실 사람들 도움 받아서 하긴했는데 멈치가 망가짐 아닌 망가지긴 했음. 무튼 다음에는 삽질을 좀 덜할 수 있을 것으로 보임

 

 

Reference

[1] https://webnautes.tistory.com/1428

멱급수 (Power Series)

멱급수 이전에, 먼저 급수란 규칙이 있는 수열의 합을 의미한다.

예컨데 등차수열의 합이나 등비수열의 합도 급수이다. 등비수열의 합을 나타내는 식은 아래와 같은데, 

 

$S_n = a + ar + ar^2 + ar^3 + \dots + ar^{n-1}$

 

위와 같은 일반적인 급수는 각 항에 단순한 숫자가 들어가는 것이 특징이다. 하지만 멱급수의 경우 같은 형태를 띠지만 숫자가 아닌 $x$와 같은 문자로 표현되는 것이 특징이다. 가령 수식으로 나타내면 아래와 같다.

 

$f(x) = a_0 + a_1x + a_2x^2 + a_3x^3 + \dots = \sum_{n=0}^\infty a_nx^n$

 

이러한 멱급수는 언제 사용될까? 멱급수는 물리학이나 수학에서 처음부터 정확한 해를 찾기 어려울 때 근사치를 통해 점점 근사한 값을 찾아갈 때 사용한다. 즉, 찾고자 하는 함수나 해가 어떤 형태나 값인지 잘 모를 경우 멱급수로 가정하고 문제를 풀게 되면 근사 함수나 근사 해를 구할 수 있는 것이 특징이다. 이 때 정확한 함수나 정확한 해를 구할 수 없는 것은 실사용의 편의를 위해 고차원이 되는 항은 제외하고 2차항 또는 3, 4차항만 구한 뒤, 이를 구하고자 했던 함수 또는 해라고 판단한다. 실제로 멱급수는 양자역학에서 원자핵 주위를 도는 전자와 같이 미시세계를 다룰 때 2차항 정도까지만 고려해도 계산값과 측정값이 근사하기 때문에 유용하게 쓰인다고 한다. 

 

테일러 급수 (Taylor Series)

테일러 급수란 여러 번 미분가능한 함수 $f(x)$에 대해 $x=a$에서 그 $f(x)$에 접하는 멱급수로 표현하는 방법이다. 테일러 급수를 사용하는 방법은 무한개의 항을 가진 멱급수를 통해 어떤 함수를 표현하는 것이다. 하지만 실 사용에서는 편의를 위해 몇 개의 항만 사용해 근사의 형태로 활용한다.

 

테일러 급수를 활용한 근사 함수 추정

 

일반화한 수식으로 나타내면 다음과 같다.

$f(x) = \sum_{n=0}^\infty {f^{(n)}(a) \over n!} {(x-a)}^n$

 

테일러 급수의 특징은 $x$가 $a$ 근처에서만 성립하는 것이다. 만약 $x$가 $a$로부터 멀어지면 멀어질수록 큰 오차를 갖게 된다. 또한 테일러 급수를 활용해서 무한히 미분가능한 f(x)를 우리가 찾고자 하는 함수에 무한히 근사한다고 해도 같아지지 않을 수 있다. 예컨데 아래와 같이 항의 개수(N)가 많아질수록 오히려 근사 함수에 수렴하는 것이 아니라 어느 특정 구간에서 오차가 크게 생기는 것을 확인할 수 있다.

 

 

우리는 찾고자 하는 함수에 근사하기 위해서는 테일러 급수 f(x)에서 $x=a$인 $f(a)$가 필요하다. 

 

(1) $f(x) = a_0 + a_1(x-a) + a_2(x-a)^2 + a_3(x-a)^3 + a_4(x-a)^4 + \dots$

의 수식이 있을 때 (0)과 같이 일반화한 수식으로 나타내기 위해서는 미분한 식에 $x=a$를 대입하면 된다. 과정은 아래와 같다.

 

(1)의 식을 한 번 미분하게 되면 아래와 같은 함수를 얻을 수 있다. 

$f'(x) = a_1 + 2a_2(x-a) + 3a_3(x-a)^2 + 4a_4(x-a)^3 + \dots$

위의 식에 $x=a$를 대입하게 되면 모두 사라지고 하나의 항만 남아 $f'(a) = a_1$이 된다.

 

(1)의 식에 두 번 미분하게 되면 아래의 식이 된다

$f''(x) = 2a_2 + 2 * 3a(x-a) + 3*4a_4(x-a)^2+\dots$

위의 식에 $x=a$를 대입하게 되면 모두 사라지고 하나의 항만 남아 $f''(a) = 2a_2$이 된다. 즉, $a_2 = {1\over2} f''(a)$이다

 

(1)의 식에 세 번 미분하게 되면 아래의 식이 된다.

$f'''(x) = 2 * 3a_3 + 2*3*4a_4(x-a)+\dots$

위의 식에 $x=a$를 대입하게 되면 모두 사라지고 하나의 항만 남아 $f'''(a) = 2*3a_3$이 된다. 즉, $a_3 = {1\over{2*3}} f'''(a)$이다.

 

위와 같은 절차를 정리해서 테일러 급수를 표현하면 아래와 같다.

$f(x) = f(a) + {1\over 1!}f'_{(a)}(x-a) + {1\over 2!}f''_{(a)}(x-a)^2 + {1\over 3!}f'''_{(a)}(x-a)^3 + \dots + {1\over n!}f^n_{(a)}(x-a)^n$

 

결론을 정리하면 어떤 함수에 근사하는 함수를 찾기 위해 어떤 함수를 테일러 급수의 형태로 나타낸 다음, 미분을 통해서 찾고자 하는 함수에 점점 근사해 나갈 때 사용된다. 

 

 

매클로린 급수 (Maclaurin's Series)

매클로린 급수는 테일러 급수에 포함되는 개념이라 볼 수 있다. 테일러 급수의 특정한 경우에 사용되는 급수이다. 매클로린 급수는 $a=0$일 때를 의미하는 원점 근처에서 함수를 급수 전개 한것을 의미한다. 테일러 급수에서 사용되는 $x-a$를 $x$로만 바꾸면 되기에 더 간단한 형태이다. 

 

$f(x) = \sum_{n=0}^\infty {f^{(n)} \over n!} {x}^n$

 

Reference

[1] http://www.aistudy.com/math/optimization.htm

[2] https://nacture.kr/70

[3] https://crush-on-study.tistory.com/93

 

한 줄 핵심 요약: 렌즈에서 반드시 왜곡이 생기며 영상처리를 위해선 왜곡을 보정해줄 수 있는 카메라 캘리브레이션(camera calibration) 과정을 거쳐야 한다.

 

1. 핀홀 카메라 모델


영상처리 분야에서 영상에 대한 모든 기하학적 해석은 핀홀 카메라 모델을 바탕으로 이루어진다. 핀홀이란 종이에 핀으로 뚫은 구멍처럼 매우 작은 구멍을 뜻한다. 핀홀 카메라는 바늘구멍 사진기라고도 한다. 렌즈를 사용하지 않고 작은 구멍을 통해 빛을 받아 들여 촬영하는 사진기로, 간단하게 제작이 가능해 교육용으로 쓰이기도 한다. 핀홀 카메라 모델은 물체에 반사된 빛이 우리 수정체를 거쳐 망막에 도달하는 구조를 가장 잘나타난 모델이라 할 수 있다. 하지만 매우 이상적인 모델이기 때문에 실제로는 렌즈의 특성에 따른 영상 왜곡 문제도 함께 고려해야 한다.

핀홀 카메라 예시

 

1.1 핀홀 카메라와 일반 카메라

1.1.1 핀홀 카메라

핀홀 카메라 모델의 단점은 빛이 아주 작은 구멍을 거쳐 오기 때문에 빛을 모아서 영상을 만들기에는 많은 시간이 걸린다는 점이다. 반대로 장점은 정확한 영상을 얻을 수 있다.

 

1.1.2 일반 카메라

핀홀 카메라는 빛을 모으는데 오랜 시간이 걸리기 때문에 핀홀 카메라를 사용하지 않고, 일반적인 카메라를 사용한다. 일반 카메라의 장점은 핀홀대신 렌즈를 사용하기 때문에 빛을 잘 모아서 영상을 빠르게 만들 수 있다는 장점이 있다. 하지만 단점은 렌즈를 사용하기 때문에 왜곡이 발생해 영상이 손상되다는 점이다. 따라서 일반 카메라에서 렌즈의 왜곡을 줄여주는 작업이 필요로 하게 된다.

 

1.2 핀홀 카메라 구조

간단한 구조이다. 일반 카메라와 비교해서 핀홀 평면은 카메라의 렌즈라고 생각하면 되고, X는 실제 물체라고 생각하면 된다. 영상평면은 물체가 핀홀 평면를 통해 들어온 빛이 맺혀 영상이 만들어지는 곳이라고 생각하면 된다. 물체에서 반사된 빛 중 하나의 광선 핀홀을 통과하게 된다. 핀홀을 통과한 광선은 영상평면에 투영되어 영상이 맺히게 된다. 이 때 영상평면에 맺히는 물체의 크기는 핀홀카메라의 초점거리에 의해 결정된다.

이를 수식으로 나타내면 위와 같이 $ -x = f({X \over Z})$ 가 된다.

 

 

  • f: 핀홀 카메라 초점거리
  • z: 카메라와 물체 사이 거리
  • X: 실제 물체 크기
  • x: 영상내의 물체 크기

 

1.3 핀홀 카메라 모델의 특징 및 한계점

핀홀 카메라 모델은 영상과 관련해서 3차원 기하 모델을 얻을 수 있다. 하지만 적은 양의 빛(광선)만 핀홀을 통과하기 때문에 영상을 구성하기 위해서는 충분한 빛을 받아들일 수 있는 시간이 많이 필요하다. 빠르게 영상을 생성하기 위해서는 넓은 영역으로부터 빛을 모아야 하고, 빛이 투영점에 수렴하도록 굴절시켜야 한다. 이런 작업을 수행하기 위해 일반적으로 렌즈를 사용하게 된다. 즉, 렌즈는 한 번에 많은 빛을 모아줌으로써 빠른 영상 획득을 가능하게 한다. 

 

1.4 렌즈의 특징 및 한계점

왜곡 없는 렌즈는 없다. 렌즈 왜곡은 제조 과정에서 발생한다. 수학적으로 이상적인 포물선 렌즈보다 구면 렌즈를 만드는 것이 더 쉽기 때문이며, 렌즈와 영상 센서를 완벽하게 정렬하는 것도 쉽지 않기 때문이다. 렌즈의 왜곡률이 적을 수록 비싼 렌즈(카메라)가 된다. 하지만 이러한 왜곡을 오히려 이용한 어안렌즈라는 것을 사용하기도 한다.

어안렌즈 예시

 

이러한 렌즈에는 왜곡이 생긴다는 특성 때문에 카메라로 찍은 것과 실제 세상은 다르게 보인다. 때문에 영상처리에 있어서는 이러한 왜곡을 없애줄 수 있는 카메라 캘리브레이션 과정을 필수적으로 필요로 하게 된다. 카메라 캘리브레이션 관련해서는 이후 포스팅에서 진행될 예정이다.

 

 

Reference

[1] https://carstart.tistory.com/179?category=209767

+ Recent posts