열렬히.뛰기

3장 : 언어모델과 그 원리

머신러닝 & 딥러닝 > 자연어처리 (1) : BERT와 GPT > 3장 : 언어모델과 그 원리

  • 딥러닝의 원리에 대해서 학습
  • 수리적인 부분이 많이 나온다. 코딩은 거의 없다.

1. 언어모델

언어모델과 조건부확률

  • 단어 뭉치에 확률을 부여하는 모델
  • 조건부확률을 기반으로 한다.
    • 조건부확률이란? 클릭

왜 조건부확률인가?

기본구조 : f(단어) → 확률

그런데, 단어가 2개인 상황이라면?

ex. 단어1; “난폭” 과 단어2; “운전” 가 동시에 나타날 확률 = 조건부확률로 표현

“난폭”이 나타날 확률

P(\text{난폭})

“난폭”이 나타났을 때, “운전”이 나타날 확률

\frac{P(\text{난폭}) \cap P(\text{운전})} {P(\text{난폭})} = P(\text{운전}|\text{난폭})

“난폭”과 “운전”이 함께 나올 확률

P(\text{난폭}, \text{운전}) = P(\text{난폭})P(\text{운전}|\text{난폭})
P(a, b) = P(a) \times P(b|a)

만약 단어가 n개 이상인 상황이라면?

P(a, b, c) = P(a) \times P(b|a) \times P(c|b,a)
P(w_{1}, \cdots, w_{n}) = \displaystyle\prod_{i=1}^{n} P(w_{i}| w_{1}, \cdots, w_{i-1})

언어모델의 종류 1

  1. 순방향: 사람이 이해하는 방향으로 전개
  2. 역방향: 사람이 이해하는 것과 반대 방향으로 전개

넓은 의미에서의 언어 모델

요즘은 언어모델을 P(w|Context)로 정의하기도 한다.

즉, Context(특정 맥락)이 나올 때 단어(w)가 나타날 조건부확률이라고 보는 것이다.

언어모델의 종류 2

  • 두 모델은 모두 양방향성을 가지고 있다.
  1. 마스크 언어모델
    • 빈칸을 만들어 놓고 다른 단어에 따라 시의적절한 단어가 무엇인지 보는 모델
  2. 스킵-그랩 모델
    • 빈칸을 만들어 놓고 특정 범위 내 앞뒤 단어에 따라 시의적절한 단어가 무엇인지 보는 모델

언어모델로 할 수 있는 것.

  • 번역, 문법 교정, 문장 생성
  • 데이터 제작 : 수작업 없이도 데이터를 생성할 수 있음. GPT가 탄생한 이유
  • 트랜스퍼 러닝 : 적은 량의 데이터로도 성능을 올릴 수 있음.

2. 트랜스포머

2017년 구글이 제안한 모델로, sequence-to-sequence 과제를 수행한다.

sequence-to-sequence 이란?

  • 특정 속성을 지닌 시퀸스를 다른 시퀸스로 변환하는 작업
  • 여기서의 sequence : 나열. 즉 문장이라고 생각하면 됨.
  • f(소스) → 타깃의 구조. 소스와 타깃의 길이는 달라도 된다.
ex. 
어제, 카페, 갔었어, 거기, 사람, 많더라. : 한국어 (소스 언어)
										
										▽

I, went to the cafe, there were many people there : 영어 (타깃 언어)

3. 전체적인 원리

인코더와 디코더

  • 인코더: 소스 시퀸스를 압축하는 과정
  • 디코더: 타깃 시퀸스를 생성하는 과정
  1. 인코더에 소스 시퀸스를 입력.

    디코더에도 토큰 입력.

    • 토근의 종류에는
  2. 소스 시퀸스가 압축됨.

    • 압축과정 1 : 멀티헤드 어텐션

    • 압축과정 2 : 피드포워드 뉴럴넷

    • 압축과정 3 : 잔차 연결 및 레이어 정규화

    • 이 과정을 블록이라고 하며,

      인코더는 이러한 블록을 수십번 쌓아 이뤄진다.

  3. 디코더는 압축된 소스 스퀀스토큰을 모두 고려

  4. 디코더에서도 압축이 이뤄짐.

    • 압축과정 1 : 마스크 멀티헤드 어텐션
    • 압축과정 2 : 멀티헤드 어텐션
    • 압축과정 3 : 피드포워드 뉴럴넷
    • 압축과정 4 : 잔차 연결 및 레이어 정규화
    • 역시 이 과정을 반복한다.
  5. 최종적인 출력 형태는 확률벡터.

ex. 사람들이 원하는 답이 “i” 인 경우

입력  [...., 나는, ....]

------------------------------------------
출력	[cafe,  I,  to,  the,  went,  were]
1st = [0.01, 0.1, 0.09, 0.08, ...]
2nd = [0.01, 0.2, ....]
...
nth = [0.01, 0.9, ....]


------------------------------------------
모델이 반복되면서 "I"로 갈 확률을 높인다.

4. 핵심기술

  • 압축에 쓰이는 기술들이다.
  • 수학적인 원리가 사용된다

<기술>

셀프 어텐션

피드포워드 뉴럴 네트워크

잔차 연결

<수학>

행렬연산

신경망 연산

정규화

4-1. 핵심기술 1 : 셀프 어텐션

  • 어텐션 = 시퀸스 입력에 수행하는 기계학습 방법의 일종
    • ‘셀프’라는 말이 들어간 것처럼 자기 자신에게 수행하는 어텐션 기법이라고 할 수 있다.
  • 셀프 어텐션은 쿼리, 키, 벨류가 서로 영향을 주고 받는 구조.
    • 행렬 곱셈을 사용한다. 행렬이 뭔지 모르면 클릭!

원리 : 간단하게

예시 문장 -> 어제 카페 갔었어 거기 사람 많더라


단어들을 2개씩 묶어보자. 앞을 쿼리, 뒤를 키라고 한다.
문장 속 단어가 6개면 -> 쿼리 6개, 키 6개가 만들어진다.

----------------------------------------------------------------------------------------
어제 - 카페, 어제 - 갔었어, 어제 - 거기, 어제 - 사람, ................
카페 - 갔었어, 카페 - 거기, 카페 - 사람, .....
갔었어 - 거기, ........
........

이를 쿼리가 “카페”인 경우에 한해 다시 정리해보면 다음과 같다.

"쿼리 - 키   :   확률" 구조로 정리한 표
확률이 계산된 방법은 밑에서 설명. (모종의 방식으로 나왔다고 이해)
-------------------------------------------------------------------


카페 - 어제   : 0.1 = key_{어제}
카페 - 카페   : 0.1 = key_{카페}
카페 - 갔었어 : 0.2 = key_{갔었어}
카페 - 거기   : 0.4 = key_{거기}
카페 - 사람   : 0.1 = key_{사람}
카페 - 많더라 : 0.1 = key_{많더라}

--------------- 이 과정을 모든 쿼리들에 적용한다.------------------

그리고 키벡터와 벨류벡터를 서로 가중합 해준다.

\begin{align*} Z_{\text{카페}} &= key_{\text{어제}} \cdot value_{\text{어제}} + key_{\text{카페}} \cdot value_{\text{카페}} + key_{\text{갔었어}} \cdot value_{\text{갔었어}} + \cdots \end{align*}

나머지 단어들도 이 과정을 반복한다. 한 시퀸스의 계산을 블록이라고 한다.

모든 시퀸스의 계산이 끝나면 다음 블록으로 넘긴다.

타 방식과의 비교

vs 합성곱 신경망 (CNN)
  • 합성곱 신경망 : 합성곱 필터라는 장치로 시퀸스의 지역적 특징을 잡아내는 모델

    • 합성곱 신경망에 관한 설명
    wikidocs.net/64066

그러나, 합성곱 신경망은 합성곱 필터를 넘어서는 문맥을 읽기 힘듬.

[합성곱 신경망; 필터 크기가 3인 경우]
(어제 카페 갔었어) 거기 사람 많더라
어제 (카페 갔었어 거기) 사람 많더라
어제 카페 (갔었어 거기 사람) 많더라
어제 카페 갔었어 (거기 사람 많더라)

vs 순환 신경망 (RNN)
  • 순환 신경망:

    [순환 신경망]
    어제 -> 카페 -> 갔었어 -> 거기 -> 사람 -> 많더라
    
    • 문제점 : 시퀀스 길이가 길어질수록 정보 압축에 문제가 발생.
vs 어텐션
  • 순환신경망의 단점을 보완하기 위해 만들어진게 어텐션

  • 타깃 시퀀스를 생성할 때 소스 시퀀스 전체에서 어떤 요소에 주목해야 할 지 알려줌

  • 디코더 쪽 RNN에 어텐션을 추가하는 방식.

    [어텐션]
    소스 시퀀스 : n개    타킷 시퀀스 : 1개
    어제                 카페
    갔었어
    거기
    사람										
    
    
    예시 문장 -> 어제 카페 갔었어 거기 사람 많더라
    
    <셀프 어텐션>
    ----------------------------------------------------------------------------------------
    어제 - 카페, 어제 - 갔었어, 어제 - 거기, 어제 - 사람, ................
    카페 - 갔었어, 카페 - 거기, 카페 - 사람, .....
    갔었어 - 거기, ........
    ........
    --------------------------------------------------------------------------
    <어텐션>
    어제 - 카페
    갔었어 - 카페
    사람 - 카페
    ... - ...
    

어텐션은 n개 → 1개 모델이라면, 셀프 어텐션은 n개 → n개 모델이다.


이제 원리에 대해서 좀 더 자세히 알아보자.

1. 모델은 어떻게 문장 속 단어 하나하나를 쿼리, 키, 벨류로 만드는가?

입력 벡터를 X라고 했을 때,

쿼리로 만들어주는 행렬(W_{Q})을 곱하면 쿼리가 되고

키로 만들어주는 행렬(W_{K})을 곱하면 키가 되며,

밸류로 만들어주는 행렬(W_{V})을 곱하면 밸류가 된다.

\begin{align*} Query &= X \times W_{Q} \\ Key &= X \times W_{K} \\ Value &= X \times W_{V} \end{align*}

입력벡터의 구조

python
X = [[1, 0, 1, 0],
		 [0, 2, 0, 2],
		 [1, 1, 1, 1]]
			
# X[1] = 1st 단어

단어 임베딩 차원 수: 4개

단어 갯수 : 3개

  • 입력벡터 자체가 하나의 문장이라고 할 수 있다.
  • 임베딩이란? 자연어를 벡터로 바꾼 모습
    • 사람 손으로는 2개씩 쿼리와 키를 묶을 수 있지만, 소프트웨어는 그게 안되기 때문에 임베딩을 한다.

쿼리, 키, 벨류 만들기

\begin{bmatrix} 1 & 0 & 1 & 0 \\ 0 & 2 & 0 & 2 \\ 1 & 1 & 1 & 1 \end{bmatrix} \begin{bmatrix} 1 & 0 & 1 \\ 1 & 0 & 0 \\ 0 & 0 & 1 \\ 0 & 1 & 1 \end{bmatrix} = \begin{bmatrix} 1 & 0 & 2 \\ 2 & 2 & 2 \\ 2 & 1 & 3 \end{bmatrix} = Q
Q = [[1, 0, 2],
		 [2, 2, 2],
		 [2, 1, 3]]

Q[1]: 1st 단어의 쿼리
\begin{bmatrix} 1 & 0 & 1 & 0 \\ 0 & 2 & 0 & 2 \\ 1 & 1 & 1 & 1 \end{bmatrix} \begin{bmatrix} 0 & 0 & 1 \\ 1 & 1 & 0 \\ 0 & 1 & 0 \\ 1 & 1 & 0 \end{bmatrix} = \begin{bmatrix} 0 & 1 & 1 \\ 4 & 4 & 0 \\ 2 & 3 & 1 \end{bmatrix} = K
python
K = [[0, 1, 1],
		 [4, 4, 0],
		 [2, 3, 1]]

K[1]: 1st 단어의 키
\begin{bmatrix} 1 & 0 & 1 & 0 \\ 0 & 2 & 0 & 2 \\ 1 & 1 & 1 & 1 \end{bmatrix} \begin{bmatrix} 0 & 2 & 0 \\ 0 & 3 & 0 \\ 1 & 0 & 3 \\ 1 & 1 & 0 \end{bmatrix} = \begin{bmatrix} 1 & 2 & 3 \\ 2 & 8 & 0 \\ 2 & 6 & 3 \end{bmatrix} = V
python
V = [[1, 2, 3],
		 [2, 8, 0],
		 [2, 6, 3]]
			
V[1]: 1st 단어의 밸류

2. 키의 값, 즉 확률들은 어떻게 계산되었는가?

우선 다음과 같은 행렬 a를 구한다. (이중에서 QK^T 어텐션 스코어라고 한다.)

\mathbf{a} = \frac{QK^{T}}{\sqrt {dim(K)}}
\mathbf{a} = \dfrac{1}{\sqrt3} \begin{bmatrix} 1 & 0 & 2 \\ 2 & 2 & 2 \\ 2 & 1 & 3 \end{bmatrix} \begin{bmatrix} 0 & 4 & 2 \\ 1 & 4 & 3 \\ 1 & 0 & 1 \end{bmatrix} = \begin{bmatrix} \frac{2}{\sqrt3} & \frac{4}{\sqrt3} & \frac{4}{\sqrt3} \\[10pt] \frac{4}{\sqrt3} & \frac{12}{\sqrt3} & \frac{16}{\sqrt3} \\[10pt] \frac{4}{\sqrt3} & \frac{12}{\sqrt3} & \frac{10}{\sqrt3} \\ \end{bmatrix}

그리고 이를 softmax() 라는 함수에 집어넣는다.

  • softmax 함수란? 벡터 → 벡터인 함수.

    softmax(\mathbf{a}_{i}) = \dfrac{exp(\mathbf{a}_{i})} {\displaystyle\sum_{j} exp(\mathbf{a}_{j})}
    \mathbf{a}\;(i, j) = \dfrac { e^{\text{행렬}\;a\text{의}\;i\text{번째}\;\text{행},\;j\text{번째}\;\text{열}} } { sum( e^{\text{행렬}\;a\text{의}\;i\text{번째}\;\text{행}} ) }
softmax \left( \begin{bmatrix} \frac{2}{\sqrt3} & \frac{4}{\sqrt3} & \frac{4}{\sqrt3} \\[10pt] \frac{4}{\sqrt3} & \frac{12}{\sqrt3} & \frac{16}{\sqrt3} \\[10pt] \frac{4}{\sqrt3} & \frac{12}{\sqrt3} & \frac{10}{\sqrt3} \\ \end{bmatrix} \right) = \begin{bmatrix} 0.13613 & 0.43194 & 0.43194 \\[5pt] 0.00089 & 0.90884 & 0.09027 \\[5pt] 0.00744 & 0.75471 & 0.23785 \\ \end{bmatrix}

이를 다시 벨류벡터와 곱한다.

softmax(a) * V
\begin{bmatrix} 0.13613 & 0.43194 & 0.43194 \\[5pt] 0.00089 & 0.90884 & 0.09027 \\[5pt] 0.00744 & 0.75471 & 0.23785 \\ \end{bmatrix} \begin{bmatrix} 1 & 2 & 3 \\ 2 & 8 & 0 \\ 2 & 6 & 3 \end{bmatrix} = \begin{bmatrix} 1.8639 & 6.3194 & 1.7042 \\ 1.9991 & 7.8141 & 0.2735 \\ 1.9926 & 7.4796 & 0.7359 \end{bmatrix}
X = [[1.8639, 6.3194, 1.7042]       \\ 첫 번째 단어의 셀프 어텐션 출력값
		 [1.9991, 7.8141, 0.2735]       \\ 두 번째 단어의 셀프 어텐션 출력값
		 [1.9926, 7.4796, 0.7359].      \\ 세 번째 단어의 셀프 어텐션 출력값

공식을 정리하자면 다음과 같다.

Attention(Q, K, V) \;=\; softmax \bigg(\frac{QK^{T}} {\sqrt {dim(K)}}\bigg) * V

여기서 알 수 있는 것은 결과적으로 최종 행렬의 크기 (단어의 갯수) * (밸류 행렬의 차원 수)이다.

  • 최종 셀프 어텐션의 결과행렬에 영향을 끼치는 요소를 찾아보자.
    • \sqrt{dim(K)} 는 각 원소값를 루트 값으로 나누는 역할. 행렬 단위 계산이 아니다.
    • softmax()는 실수인 각 원소값을 0~1 사이 소수로 바꾸는 역할. 역시 행렬 단위 계산이 아님.
  • 결국 Q \cdot K^t \cdot V 가 결과행렬의 크기에 영향을 준다.
\begin{align*} Q \cdot K^t \cdot V &= \mathbf{X} * (\text{변환행렬}\;q) * \mathbf{X}^{t} * (\text{변환행렬}\;k)^{t} * \mathbf{X} * (\text{변환행렬}\;v) \\[10pt] dim(Atten(\mathbf{X})) &= \mathbf{X}_{\text{행}\;\text{갯수}} * V_{\text{열}\;\text{갯수}}\\[10pt] &= (\text{단어}\;\text{갯수}) * (\text{벨류행렬의}\;\text{차원}) \end{align*}

3. 멀티 헤드 어텐션

  • 셀프 어텐션을 여러번 수행한 것을 ‘멀티 헤드 어텐션’ 이라고 함.

    • \mathbf{W}^{0} : 일종의 변환행렬이라고 생각하면 됨. 업데이트를 위해 존재.
    Z_{1} = Attention(Q_{z_{1}}, K_{z_{1}}, V_{z_{1}}) \;=\; softmax \bigg(\frac{Q_{z_{1}}{K_{z_{1}}}^{T}} {\sqrt {dim(K_{z_{1}})}}\bigg) * V_{z_{1}}
    \begin{bmatrix} Z_{1} & Z_{2} & \cdots \end{bmatrix} \mathbf{W}^{0} = Z
    \begin{align*} Z &= \{(\text{단어}\;\text{수}) * (\text{밸류수의}\;\text{합})\} * \{(\text{밸류}\;\text{수의}\;\text{합}) * (\text{차원}\;\text{수}) \} \\[8pt] &= \{(\text{단어}\;\text{수}) * (\text{차원}\;\text{수}) \} \end{align*}

4. 인코더 & 디코더에서의 셀프 어텐션 (1)

인코더에서의 셀프 어텐션은 다음과 같이 이뤄진다.

입력: 어제, 카페, 갔었어, 거기, 사람, 많더라
----------------------------------------------

어제 - 카페 : 0.1 // 어제 - 갔었어 : 0.1 // .....
카페 - 어제 : 0.1 // 카페 - .......
.........

-------------------------------------------------
모든 단어 쌍에 대해서 관계가 나온다.

디코더에서의 첫 번째 멀티 헤드 어텐션은 다음과 같이 이뤄진다.

→ 마스크 멀티 헤드 어텐션라고도 한다.

입력: <s> I, went, to, the, cafe, yesterday
----------------------------------------------

cafe - <s> : 0.1 // cafe - I : 0.1 // .....
went - to : 0.1 // went - .......
.........

-------------------------------------------------
모든 단어 쌍에 대해서 관계가 나온다.

5. 인코더 & 디코더에서의 셀프 어텐션 (2)

디코더에서의 두번째 멀티 헤드 어텐션는 다음과 같이 이뤄진다.

이때, 쿼리와 키는 다음과 같다.

입력1 - 쿼리 : 어제, 카페, 갔었어, 거기, 사람, 많더라
입력2 - 키   : <s>, I, went, to, the, cafe, yesterday

그런데 여기서 약간의 트릭을 사용한다.

다음이 I라는 것은 누구나 다 아는 상식.

따라서 다음 키를 모두 가린다.

쿼리: 어제 카페 갔었어 거기 사람 많더라
키  : <s>  <s>   <s>   <s>  <s>   <s>
확률: 0.xx 0.xx 0.xx   0.x  0.x 0.xxx
--------------------------------------------------
이 정보를 가지고 <s> 다음 무엇이 올지 추론한다.

그 다음은 I 다음 키를 모조리 가린다.

쿼리: 어제 카페 갔었어 거기 사람 많더라 어제 카페 갔었어 거기 사람 많..
키  : <s>  <s>   <s>   <s>  <s>   <s>  I    I    I      I    I   I
확률: 0.xx 0.xx 0.xx   0.x  0.x 0.xxx
--------------------------------------------------
이 정보를 가지고 I 다음 무엇이 올지 추론한다.
하다보면 I에 대한 확률이 높아진다.

이 과정을 반복한다.

코드로 셀프 어텐션 만들기

python
# 정의
# 입력시퀸스 x, 변환행렬 w_query, w_key, w_value 입력

import torch
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]
])

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

w_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]	
])
python
# 쿼리, 키, 밸류 구하기
keys = torch.matmul(x, w_key)
querys = torch.matmul(x, w_query)
values = torch.matmul(x, w_value)
print(keys, "\n", querys, "\n", values, "\n")

"""
tensor([[0., 1., 1.],
        [4., 4., 0.],
        [2., 3., 1.]]) 
 tensor([[1., 0., 2.],
        [2., 2., 2.],
        [2., 1., 3.]]) 
 tensor([[1., 2., 3.],
        [2., 8., 0.],
        [2., 6., 3.]])
"""
python
# 어텐션 스코어 구하기
a_score = torch.matmul(querys, keys.T)
a_score

"""
tensor([[ 2.,  4.,  4.],
        [ 4., 16., 12.],
        [ 4., 12., 10.]])
"""
python
# 소프트맥스 함수값 구하기
import numpy as np
from torch.nn.functional import softmax
key_dim_sqrt = np.sqrt(keys.shape[-1])   
# root(dim(K)) 구하는 과정
# shape[-1] : 니가 알아서 차원을 처리하라는 뜻.

print(key_dim_sqrt)
"""
1.7320508075688772
"""

a_prob = softmax(a_scores / key_dim_sqrt, dim=1)
a_prob

"""
tensor([[1.3613e-01, 4.3194e-01, 4.3194e-01],
        [8.9045e-04, 9.0884e-01, 9.0267e-02],
        [7.4449e-03, 7.5471e-01, 2.3785e-01]])

"""
python
# 가중합
weighted = torch.matmul(a_prob, values)
weighted

"""
tensor([[1.8639, 6.3194, 1.7042],
        [1.9991, 7.8141, 0.2735],
        [1.9926, 7.4796, 0.7359]])
"""

4-2. 핵심기술 2 : 피드포워드 뉴럴 네트워크

신경망(neural net)의 한 종류.

신경망의 구조

입력층  -- 곱셈 & RELU -->  은닉층       --- 곱셈 --->       출력층
  X1                         h1                               y1
  X2                         h2                               y2
  X3                         h3

수리적인 이해

멀티 헤드 어텐션의 출력: 행렬.

  1. 입력층 만들기
    • 각 벡터들(\mathbf{X}_{i})을 피드포워드 뉴럴 네트워크에 입력한다.
  2. 은닉층 만들기
    • 각 벡터들에 가중치를 곱하고 바이아스 b를 더해줌
      • 구조가 1차 회귀식과 유사해보인다.
    • 활성함수 RELU (f(x) = max(0, x)) 를 씌워줌
f\Big( \sum_{i}\; \mathbf{W}_{i}\;\mathbf{X}_{i} + b_{1} \Big) = max\Big(0, \sum_{i}\; \mathbf{W}_{i}\;\mathbf{X}_{i} + b_{1} \Big) = \mathbf{h}
  1. 출력층 만들기

    • 각 벡터들에 가중치를 곱하고 바이아스 b를 더해줌
    \sum_{i}\; \mathbf{h}_{i}\;\mathbf{X}_{i} + b_{2} = \mathbf{Y}

코드로 보기

python
import torch
x = torch.tensor([2, 1])
w1 = torch.tensor([[3, 2, -4], [2, -3, 1]])
b1 = 1
w2 = torch.tensor([[-1, 1], [1, 2], [3, 1]])
b2 = -1

# 은닉층
h_preact = torch.matmul(x, w1) + b1
h = torch.nn.functional.relu(h_precat)

# 출력층
y = torch.matmul(h, w2) + b2
python
print(h_preact, "\n", h, '\n', y)

"""
tensor([ 9,  2, -6]) 
tensor([9, 2, 0]) 
tensor([-8, 12])
"""

4-3. 핵심기술 3 : 잔차 연결

  • 블록이나 레이어 계산을 건너뛰는 경로를 하나 두는 것
    • 좀 더 다양한 관점에서 계산이 가능
    • 레이어의 수가 줄어들기 때문에 속도가 빨라짐.
    • 미분을 했을 때 0이 되는 것을 막기 위해 x를 추가.
\mathbb{F}(\mathbf{x})\; +\; \mathbf{x}

4-4. 핵심기술 4: 레이어 정규화

  • 표준정규분포 만드는 이유와 비슷하다.
    • 계산 속도가 빨라짐.
    • 감마는 가중치. 베타 역시 가중치
    • 입실론은 분모가 0이 되는 것을 방지하는 장치
\mathbf{y} = \frac {\mathbf{x} - \mathbb{E}(\mathbf{x})} {\sqrt{var(\mathbf{x})+\epsilon}} \;\gamma + \beta
python
import torch

input = torch.tensor([[1.0, 2.0, 3.0], [1.0, 1.0, 1.0]])
m = torch.nn.LayerNorm(input.shape[-1])

output = m(input)
output

"""
tensor([[-1.2247,  0.0000,  1.2247],
        [ 0.0000,  0.0000,  0.0000]],
  		 grad_fn=<NativeLayerNormBackward0>
			)
"""
python
m.weight
"""
Parameter containing:
tensor([1., 1., 1.], requires_grad=True)
"""

m.bias
"""
Parameter containing:
tensor([0., 0., 0.], requires_grad=True)
"""

4-5. 핵심기술 5: 모델학습 기법

  • 신경망을 좀 더 좋게 만들어주는 기술들이다.
  • 드롭아웃과 아담 옵티마이저가 있다.

드롭아웃

  • 과적합을 방지하기 위해 쓰는 기술
    • 과적합이란? 클릭
  • 뉴런의 일부를 0으로 바꿔버린다.
python
import torch
m2 = torch.nn.Dropout(p = 0.2)
input = torch.randn(1, 10)
output = m2(input)
python
input
"""
tensor([[ 3.3975,  0.6354,  0.2405, -0.6356, -1.5659, -1.4878,  0.8919, -0.5834,
         -0.3233,  0.6220]])
"""

output
"""
tensor([[ 4.2469,  0.7942,  0.3007, -0.7945, -1.9573, -0.0000,  1.1149, -0.7292,
         -0.4042,  0.7775]])
"""

아담 옵티마이저

  • 오차를 최소화하기 : 딥러닝 모델 학습의 목표!
  • 따라서 오차(=손실)을 최소화 하는 방향, 그라디언트를 찾아내야 한다.
    • 이러한 과정을 최적화라고 한다.
    • 수학에서의 ‘비선형계획’을 이용한다 : 클릭
  • 오차를 구하려면, 입력 > 출력을 구해야 한다. 이를 순전파라고 한다.
    • 구했다면, 오차를 최소화하는 최초 그라디언트를 구할 수 있다.
  • 반대로, 각 파라미터별 그라디언트를 미분의 연쇄법칙으로 찾을 수도 있다.
    • 이를 역전파라고 한다.

아담 옵티마이저는 그라디언트를 찾을 때 쓰는 도구라고 보면 된다.

python
from torch.optim import Adam
optimizer = Adam(model.parameters(), lr = model.learning_rate)

5. BERT와 GPT 모델

  • 트랜스포머 아키텍처를 기반으로 하는 모델이다.

GPT vs BERT

GPT

  • 언어 모델이다.
  • 이전 단어가 주어질 때 다음 단어를 맞추는 과정에서 프리트레인함.
  • 일방향 언어모델이다.
  • 문장 생성에 강점을 지닌다.
  • 트랜스포터 아키텍처에서 디코더만을 사용함.

BERT

  • 마스크 언어 모델이다.
  • 문장 중간에 빈 칸을 만들고 해당 빈칸을 맞추는 과정에서 프리트레인함.
  • 양방향적 언어모델임.
  • 문장 의미 추출에 강점을 지님.
  • 트렌스포머 아키텍처에서 인코더만을 사용함.

GPT의 구조

ex. 어제, 카페, 갔었어, 거기, 사람, 많더라
- 카페를 맞춰야 하는 상황.

=================================================================================
어제   --  어제 --> 카페
=================================================================================

- 정답 단어 '카페'를 맞힐 때 '어제'라는 단어만 참고 가능.
- 다른 단어를 전부 마스킹함.

- '어제'에 대응하는 마지막 출력 결과에 선형변환 & 소프트 맥스 참고로
  요소값 각각이 확률이고 어휘 수만큼의 차원을 가진 벡터가 되도록 함.

“갔었어”를 맞추기 위해 ‘어제’와 ‘카페’ 참고

=================================================================================
어제   --  어제 
카페   --  카페 --> 갔었어
=================================================================================

“거기”를 맞추기 위해 ‘어제’, ‘카페’, ‘갔었어’ 참고

=================================================================================
어제   --  어제 
카페   --  카페 
갔었어 --  갔었어 -> 거기  
=================================================================================
  • GPT는 버전업 되면서 점점 더
    • 크기가 커지고 있음.
    • 성능은 유지하되 계산량을 줄이려는 시도가 계속 됨.

BERT의 구조

=================================================================================
어제    --  어제 
카페    --  카페 
갔었어  --  갔었어 
masked  --  masked  --> 거기
....    --  .....  
=================================================================================
가려진 부분 : masked
  • 가려진 부분 앞, 뒤 단어를 모두 참고 할 수 있음.

6. 단어/문장의 벡터화

파인튜닝

  • 파인튜닝이란? 프리트레인을 마친 BERT와 그 위 작은 모듈을 포함한 전체 모델을

    문서 분류들 등의 다운 스트림 데이터로 업데이트하는 과정

  • BERT의 출력 결과 가운데 어떤 걸 사용하느냐에 따라 크게 두 가지로 나눌 수 있음.

1. 문장 벡터 활용 : 문서 분류 등

CLS 이 영화 재미 없네요 SEP  ----> 프리트레인 ---> <CLS> 벡터 ---> 최종출력

최종출력 ---> 작은 모듈 적용 ; 긍정/부정/중립 중 하나로 선택하게 만들기.

2. 단어 벡터 활용 : 개체명 인식 등

CLS 그 제품 삼성 건가요 SEP ----> 단어 벡터 중 '삼성' 뽑음. 

---> '삼성'을 '기관'으로 분류.

문장을 벡터로 바꾸기

  • BERT 모델에 문장을 입력해서 이를 벡터로 변환하는 실습
  1. 코랩노트북 초기화
python
!pip install ratsnlp
  1. 토크나이저 선언
python
from transformers import BertTokenizer
tok = BertTokenizer.from_pretrained("beomi/kcbert-base", do_lower_case = False)
  1. 모델 초기화
python
from transformers import BertConfig, BertModel
pretrained_model_config = BertConfig.from_pretrained("beomi/kcbert-base")

model = BertModel.from_pretrained("beomi/kcbert-base", config=pretrained_model_config, )
결과
python
pretrained_model_config

""" 결과
BertConfig {
  "_name_or_path": "beomi/kcbert-base",
  "architectures": [
    "BertForMaskedLM"
  ],
  "attention_probs_dropout_prob": 0.1,
  "classifier_dropout": null,
  "directionality": "bidi",
  "gradient_checkpointing": false,
  "hidden_act": "gelu",
  "hidden_dropout_prob": 0.1,
  "hidden_size": 768,
  "initializer_range": 0.02,
  "intermediate_size": 3072,
  "layer_norm_eps": 1e-12,
  "max_position_embeddings": 300,
  "model_type": "bert",
  "num_attention_heads": 12,
  "num_hidden_layers": 12,
  "pad_token_id": 0,
  "pooler_fc_size": 768,
  "pooler_num_attention_heads": 12,
  "pooler_num_fc_layers": 3,
  "pooler_size_per_head": 128,
  "pooler_type": "first_token_transform",
  "position_embedding_type": "absolute",
  "transformers_version": "4.10.0",
  "type_vocab_size": 2,
  "use_cache": true,
  "vocab_size": 30000
}
"""
  1. 입력값 만들기
python
sentences = ["안녕하세요", "하이!"]
features = tok(
		sentences,
		max_length = 10,
		padding = "max_length",
		truncation = True
)

features
"""
{'attention_mask': 
		tensor([[1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
	         [1, 1, 1, 1, 0, 0, 0, 0, 0, 0]]),

 'input_ids': 
		tensor
				([[    2, 19017,  8482,     3,     0,     0,     0,     0,     0,     0],
         [    2, 15830,     5,     3,     0,     0,     0,     0,     0,     0]]),
 
'token_type_ids':
	  tensor([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
	         [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
}
"""
  1. BERT로 단어/문장 수준 벡터 구하기
python
# 피처를 토치 텐서로 변환
features = {k: torch.tensor(v) for k, v in features.items()}

# 임베딩 계산하기
outputs = model(**features)

outputs.last_hidden_state
"""
tensor([[[-0.6969, -0.8248,  1.7512,  ..., -0.3732,  0.7399,  1.1907],
         [-1.4803, -0.4398,  0.9444,  ..., -0.7405, -0.0211,  1.3064],
         [-1.4299, -0.5033, -0.2069,  ...,  0.1285, -0.2611,  1.6057],
         ...,
         [-1.4406,  0.3431,  1.4043,  ..., -0.0565,  0.8450, -0.2170],
         [-1.3625, -0.2404,  1.1757,  ...,  0.8876, -0.1054,  0.0734],
         [-1.4244,  0.1518,  1.2920,  ...,  0.0245,  0.7572,  0.0080]],

        [[ 0.9371, -1.4749,  1.7351,  ..., -0.3426,  0.8050,  0.4031],
         [ 1.6095, -1.7269,  2.7936,  ...,  0.3100, -0.4787, -1.2491],
         [ 0.4861, -0.4569,  0.5712,  ..., -0.1769,  1.1253, -0.2756],
         ...,
         [ 1.2362, -0.6181,  2.0906,  ...,  1.3677,  0.8132, -0.2742],
         [ 0.5409, -0.9652,  1.6237,  ...,  1.2395,  0.9185,  0.1782],
         [ 1.9001, -0.5859,  3.0156,  ...,  1.4967,  0.1924, -0.4448]]],
       grad_fn=<NativeLayerNormBackward0>)
"""

pooler_output 만들기

  • 마지막 레이어 CLS 벡터(\mathbf{h})tanh를 취함.
    • 하이퍼볼릭탄젠트(tanh)란? 클릭
  • 이 과정으로 자연어를 실수 형태의 벡터로 만들 수 있음 = 임베딩
python
# pooler_output
outputs.pooler_output

"""
tensor([[-0.1594,  0.0547,  0.1101,  ...,  0.2684,  0.1596, -0.9828],
        [-0.9221,  0.2969, -0.0110,  ...,  0.4291,  0.0311, -0.9955]],
       grad_fn=<TanhBackward0>)
"""
읽기 전용 아카이브 · 작성 중인 글은 공개하지 않습니다