- 딥러닝의 원리에 대해서 학습
- 수리적인 부분이 많이 나온다. 코딩은 거의 없다.
1. 언어모델
언어모델과 조건부확률
- 단어 뭉치에 확률을 부여하는 모델
- 조건부확률을 기반으로 한다.
- 조건부확률이란? 클릭
왜 조건부확률인가?
기본구조 : f(단어) → 확률
그런데, 단어가 2개인 상황이라면?
ex. 단어1; “난폭” 과 단어2; “운전” 가 동시에 나타날 확률 = 조건부확률로 표현
“난폭”이 나타날 확률
“난폭”이 나타났을 때, “운전”이 나타날 확률
“난폭”과 “운전”이 함께 나올 확률
만약 단어가 n개 이상인 상황이라면?
언어모델의 종류 1
- 순방향: 사람이 이해하는 방향으로 전개
- 역방향: 사람이 이해하는 것과 반대 방향으로 전개
넓은 의미에서의 언어 모델
요즘은 언어모델을 P(w|Context)로 정의하기도 한다.
즉, Context(특정 맥락)이 나올 때 단어(w)가 나타날 조건부확률이라고 보는 것이다.
언어모델의 종류 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 : 피드포워드 뉴럴넷
-
압축과정 3 : 잔차 연결 및 레이어 정규화
-
이 과정을 블록이라고 하며,
인코더는 이러한 블록을 수십번 쌓아 이뤄진다.
-
-
디코더는 압축된 소스 스퀀스와 토큰을 모두 고려
-
디코더에서도 압축이 이뤄짐.
- 압축과정 1 : 마스크 멀티헤드 어텐션
- 압축과정 2 : 멀티헤드 어텐션
- 압축과정 3 : 피드포워드 뉴럴넷
- 압축과정 4 : 잔차 연결 및 레이어 정규화
- 역시 이 과정을 반복한다.
-
최종적인 출력 형태는 확률벡터.
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_{많더라}
--------------- 이 과정을 모든 쿼리들에 적용한다.------------------
그리고 키벡터와 벨류벡터를 서로 가중합 해준다.
나머지 단어들도 이 과정을 반복한다. 한 시퀸스의 계산을 블록이라고 한다.
모든 시퀸스의 계산이 끝나면 다음 블록으로 넘긴다.
타 방식과의 비교
vs 합성곱 신경망 (CNN)
-
합성곱 신경망 : 합성곱 필터라는 장치로 시퀸스의 지역적 특징을 잡아내는 모델
- 합성곱 신경망에 관한 설명
- 합성곱이란? 클릭
그러나, 합성곱 신경망은 합성곱 필터를 넘어서는 문맥을 읽기 힘듬.
[합성곱 신경망; 필터 크기가 3인 경우]
(어제 카페 갔었어) 거기 사람 많더라
어제 (카페 갔었어 거기) 사람 많더라
어제 카페 (갔었어 거기 사람) 많더라
어제 카페 갔었어 (거기 사람 많더라)
vs 순환 신경망 (RNN)
-
순환 신경망:
-
순환 신경망에 관한 설명
brunch.co.kr/@linecard/324
[순환 신경망] 어제 -> 카페 -> 갔었어 -> 거기 -> 사람 -> 많더라- 문제점 : 시퀀스 길이가 길어질수록 정보 압축에 문제가 발생.
-
vs 어텐션
-
순환신경망의 단점을 보완하기 위해 만들어진게 어텐션
-
타깃 시퀀스를 생성할 때 소스 시퀀스 전체에서 어떤 요소에 주목해야 할 지 알려줌
-
디코더 쪽 RNN에 어텐션을 추가하는 방식.
[어텐션] 소스 시퀀스 : n개 타킷 시퀀스 : 1개 어제 카페 갔었어 거기 사람예시 문장 -> 어제 카페 갔었어 거기 사람 많더라 <셀프 어텐션> ---------------------------------------------------------------------------------------- 어제 - 카페, 어제 - 갔었어, 어제 - 거기, 어제 - 사람, ................ 카페 - 갔었어, 카페 - 거기, 카페 - 사람, ..... 갔었어 - 거기, ........ ........ -------------------------------------------------------------------------- <어텐션> 어제 - 카페 갔었어 - 카페 사람 - 카페 ... - ...
어텐션은 n개 → 1개 모델이라면, 셀프 어텐션은 n개 → n개 모델이다.
이제 원리에 대해서 좀 더 자세히 알아보자.
1. 모델은 어떻게 문장 속 단어 하나하나를 쿼리, 키, 벨류로 만드는가?
입력 벡터를 X라고 했을 때,
쿼리로 만들어주는 행렬(W_{Q})을 곱하면 쿼리가 되고
키로 만들어주는 행렬(W_{K})을 곱하면 키가 되며,
밸류로 만들어주는 행렬(W_{V})을 곱하면 밸류가 된다.
입력벡터의 구조
X = [[1, 0, 1, 0],
[0, 2, 0, 2],
[1, 1, 1, 1]]
# X[1] = 1st 단어
단어 임베딩 차원 수: 4개
단어 갯수 : 3개
- 입력벡터 자체가 하나의 문장이라고 할 수 있다.
- 임베딩이란? 자연어를 벡터로 바꾼 모습
- 사람 손으로는 2개씩 쿼리와 키를 묶을 수 있지만, 소프트웨어는 그게 안되기 때문에 임베딩을 한다.
쿼리, 키, 벨류 만들기
Q = [[1, 0, 2],
[2, 2, 2],
[2, 1, 3]]
Q[1]: 1st 단어의 쿼리
K = [[0, 1, 1],
[4, 4, 0],
[2, 3, 1]]
K[1]: 1st 단어의 키
V = [[1, 2, 3],
[2, 8, 0],
[2, 6, 3]]
V[1]: 1st 단어의 밸류
2. 키의 값, 즉 확률들은 어떻게 계산되었는가?
우선 다음과 같은 행렬 a를 구한다. (이중에서 QK^T 를 어텐션 스코어라고 한다.)
그리고 이를 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{행}} ) }
이를 다시 벨류벡터와 곱한다.
X = [[1.8639, 6.3194, 1.7042] \\ 첫 번째 단어의 셀프 어텐션 출력값
[1.9991, 7.8141, 0.2735] \\ 두 번째 단어의 셀프 어텐션 출력값
[1.9926, 7.4796, 0.7359]. \\ 세 번째 단어의 셀프 어텐션 출력값
공식을 정리하자면 다음과 같다.
여기서 알 수 있는 것은 결과적으로 최종 행렬의 크기는 (단어의 갯수) * (밸류 행렬의 차원 수)이다.
- 최종 셀프 어텐션의 결과행렬에 영향을 끼치는 요소를 찾아보자.
- \sqrt{dim(K)} 는 각 원소값를 루트 값으로 나누는 역할. 행렬 단위 계산이 아니다.
- softmax()는 실수인 각 원소값을 0~1 사이 소수로 바꾸는 역할. 역시 행렬 단위 계산이 아님.
- 결국 Q \cdot K^t \cdot V 가 결과행렬의 크기에 영향을 준다.
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에 대한 확률이 높아진다.
이 과정을 반복한다.
코드로 셀프 어텐션 만들기
# 정의
# 입력시퀸스 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]
])
# 쿼리, 키, 밸류 구하기
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.]])
"""
# 어텐션 스코어 구하기
a_score = torch.matmul(querys, keys.T)
a_score
"""
tensor([[ 2., 4., 4.],
[ 4., 16., 12.],
[ 4., 12., 10.]])
"""
# 소프트맥스 함수값 구하기
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]])
"""
# 가중합
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
수리적인 이해
멀티 헤드 어텐션의 출력: 행렬.
- 입력층 만들기
- 각 벡터들(\mathbf{X}_{i})을 피드포워드 뉴럴 네트워크에 입력한다.
- 은닉층 만들기
- 각 벡터들에 가중치를 곱하고 바이아스 b를 더해줌
- 구조가 1차 회귀식과 유사해보인다.
- 활성함수 RELU (f(x) = max(0, x)) 를 씌워줌
- 각 벡터들에 가중치를 곱하고 바이아스 b를 더해줌
-
출력층 만들기
- 각 벡터들에 가중치를 곱하고 바이아스 b를 더해줌
\sum_{i}\; \mathbf{h}_{i}\;\mathbf{X}_{i} + b_{2} = \mathbf{Y}
코드로 보기
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
print(h_preact, "\n", h, '\n', y)
"""
tensor([ 9, 2, -6])
tensor([9, 2, 0])
tensor([-8, 12])
"""
4-3. 핵심기술 3 : 잔차 연결
- 블록이나 레이어 계산을 건너뛰는 경로를 하나 두는 것
- 좀 더 다양한 관점에서 계산이 가능
- 레이어의 수가 줄어들기 때문에 속도가 빨라짐.
- 미분을 했을 때 0이 되는 것을 막기 위해 x를 추가.
4-4. 핵심기술 4: 레이어 정규화
- 표준정규분포 만드는 이유와 비슷하다.
- 계산 속도가 빨라짐.
- 감마는 가중치. 베타 역시 가중치
- 입실론은 분모가 0이 되는 것을 방지하는 장치
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>
)
"""
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으로 바꿔버린다.
import torch
m2 = torch.nn.Dropout(p = 0.2)
input = torch.randn(1, 10)
output = m2(input)
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]])
"""
아담 옵티마이저
- 오차를 최소화하기 : 딥러닝 모델 학습의 목표!
- 따라서 오차(=손실)을 최소화 하는 방향, 그라디언트를 찾아내야 한다.
- 이러한 과정을 최적화라고 한다.
- 수학에서의 ‘비선형계획’을 이용한다 : 클릭
- 오차를 구하려면, 입력 > 출력을 구해야 한다. 이를 순전파라고 한다.
- 구했다면, 오차를 최소화하는 최초 그라디언트를 구할 수 있다.
- 반대로, 각 파라미터별 그라디언트를 미분의 연쇄법칙으로 찾을 수도 있다.
- 이를 역전파라고 한다.
아담 옵티마이저는 그라디언트를 찾을 때 쓰는 도구라고 보면 된다.
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 모델에 문장을 입력해서 이를 벡터로 변환하는 실습
- 코랩노트북 초기화
!pip install ratsnlp
- 토크나이저 선언
from transformers import BertTokenizer
tok = BertTokenizer.from_pretrained("beomi/kcbert-base", do_lower_case = False)
- 모델 초기화
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, )
결과
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
}
"""
- 입력값 만들기
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]])
}
"""
- BERT로 단어/문장 수준 벡터 구하기
# 피처를 토치 텐서로 변환
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 만들기
# 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>)
"""