미래내일일경험 - 빅리더(23.06~23.12)/교육

[스터디챌린지] ICT융합대학 스터디 챌린지 5주차(7/29 ~ 8/04)

NINE1ll 2023. 8. 4. 18:30

저번주 자연어처리는 따로 글로 정리하도록 하고... 생각보다 난이도가 어려워서 위키독스와 책을 계속 보느라고 내 언어로 정리를 못했다..

# 텍스트를 컴퓨터가 이해하고, 효율적으로 처리하게 하기 위해서는 컴퓨터가 이해 할 수 있도록 텍스트를 적절하게 숫자로 변환하는 과정이 워드 임베딩이라는 방법을 사용한다. 

# 원-핫 인코딩을 통해서 나온 원-핫 벡터는 단어가 있는 index만 1로 표현하고 나머지를 1로 표현하는 희소 벡터 형태를 가진다. 하지만  matrix의 크기가 단어의 개수를 따라가기 때문에,  단어의 개수가 늘어날 수록 차원이 엄청 나게 늘어나는 문제가 있고, 이에 따라 공간적 낭비가 엄청나게 심하게 일어납니다. 그리고 무엇보다 그냥 인덱스만 나타내는 것이기 때문에 단어의 의미를 표현하지 못합니다. (이는 동음이의어를 처리하지 못한다는 문제를 야기합니다)

# 보통 이런 원-핫 벡터와 같은 희소 벡터에서 나오는 공간을 너무 많이 차지하는 문제점을 밀집 표현으로 바꾸면서 해결이 어느 정도 가능합니다. 당연하게도 차원을 줄이는 방법이기 때문에 어느 정도 정보의 손실은 생기지만, 그래도 공간이 너무 커서 컴퓨터가 처리하기 어려워하는 matrix를 처리할 수 있는 아주 좋은 방법입니다.

# 단어를 밀집 벡터 형식으로 표현하는 방식을 워드 임베딩이라고 합니다. 그리고 밀집 벡터를 임베딩 과정을 통해 나온 결과라고 하여 임베딩 벡터라고 이야기합니다.

종류는 정말 다양합니다. Word2Vec이 대표적인 예시라고 할 수 있습니다.


ChatGPT 1일차 (수업에서 한 예제가 다 위키독스에 있다니.. 교수님 어떤 싸움을 해오신 겁니까..)

서브워드 토그나이저(Subword Tokenizer)

out of word, 그니까 우리가 모델에게 학습시켜준 단어를 벗어나는 경우 문제가 생기는데 이를 해결하기 위해 고안한 방법이다.

일단 단어를 쪼갠 다음, 쪼갠 단어와 쪼갠 단어를 합친 단어를 내 단어주머니에 저장하는 것이다.

birthplace = birth + place 이런식으로 쪼개고 알파벳도 들어가는 식으로 이야기하셨다.

1. BPE  (Byte Pair Encoding)

거의 졸면서 들었던 부분인데, 요약하자면 빈도가 제일 높은 단어? 알파벳 글자 "쌍"을 하나로 합치는 것이다.

aaabdaaabac

=> aa를 Z로 치환하구 (빈도가 제일 높은 단어 쌍)

ZabdZabac

=> 그 다음 요 글자를 보면 ab가 많이 등장하니까 ab를 Y로 치환하고

ZYdZYc

=> 끝인 것같았지만, ZY를 X로 치환하면

XdXc 같이 변경되고 더 이상 BPE를 만들수 없어서 종료된다.

조금 더 명확하게 표현하면, (한국어 단어는 애매해보여서)

chracters나 unicode 단위로 나눠서 vocabulary를 만들고 가장 많이 등장하는 chracters나 unicode를 뭉쳐서 새로 하나의 단위를 만들어 내면 된다.

# 일반 단어와 빈도수
low : 5, lower : 2, newest : 6, widest : 3
# Vocabulary
low, lower, newest, widest

'''BPE를 사용한 경우'''
# dictionary
l o w : 5,  l o w e r : 2,  n e w e s t : 6,  w i d e s t : 3
# vocabulary
l, o, w, e, r, n, s, t, i, d
# 딕셔너리를 참고로 하였을 때 빈도수가 9로 가장 높은 (e, s)의 쌍을 es로 통합합니다.
# vocabulary update!
l, o, w, e, r, n, s, t, i, d, es
# 이런 식으로 쭉 진행해서
# vocabulary update!
l, o, w, e, r, n, s, t, i, d, es, est, lo, low, ne, new, newest, wi, wid, widest
결국 이런 vocabulary가 완성된다.

기존의 RNN, LSTM은 들어가는 단어의 토큰이 나오는 단어의 토큰과 같다. 

LSTM 구조를 생각하면 그렇게 만들어질 수 밖에 없다고 생각이 든다.. (내가 이해를 잘 했겠지?)

근데 번역기를 생각해보자 

한국어로 너는 바보다. 이런 문장을 번역기에 입력해보면 You are a fool.라는 문장을 출력한다. 그러면 번역기는 LSTM의 구조를 사용하면 이상한 문장이 결과로 출력될 것이다.

그래서 조금 더 섬세한 방법인

시퀀스-투-시퀀스(Sequence-to-Sequence, seq2seq)가 등장했다.

보통 seq2seq는 챗봇, 번역기, 내용요약, STT(Speech to Text)에서 쓰인다.

구조를 간단하게 살펴보자. 

간단하게 보면 인코더와 디코더로 구성되어 있다. 

인코더는 모든 단어를 순차적으로 입력 받은 뒤에 마지막에 모든 단어 정보를 압축해서 하나의 "Context vector"로 만든다.

여기서 압축을 하면 당연하게도 정보 손실이 발생하지만, 나름 효율적인 방법이다.

그런데 RNN, LSTM의 문제점인 입력과 출력의 토큰의 개수가 같아야한다는 단점을 해결하는 방법인 seq2seq의 임코더와 디코더의 내부를 뜯어보면 

짜잔!!

LSTM들로 이뤄져있다.

그러면 여기서 다시 의문이 생긴다. 아니 # of input == # of output이라 번역기에서 사용을 못한다고 했는데?

왜 여기서 LSTM 너가 나와?

그러면 저 문제를 해결했다는 뜻인데? 해결방법을 살펴보자


seq2seq는 길이가 다른 문제를 해결하기 위해 "시작"과 "끝"을 나타내는 문자들이 따로 넣어준다.

이거를 여기서는 Start of sentence <SOS>, End of sentence <EOS>라고 명명했다. 

아 참고로 eos는 모델을 사용할 때도 필수로 나와야하는데, sos는 들어가도 되고 안들어가도 된다.

학습에서는 당연하게도 sos와 eos가 모두 필수다.

seq2seq는 훈련 과정과 테스트 과정(또는 실제 번역기를 사람이 쓸 때)의 작동 방식이 다른데,  훈련 과정에서는 디코더에게 인코더가 보낸 컨텍스트 벡터와 실제 정답인 상황인 <sos> je suis étudiant를 입력 받았을 때, je suis étudiant <eos>가 나와야 된다고 정답을 알려주면서 훈련한다. 당연하게 정답을 알려주는 상황이 훈련과정인 것이다. 이게 교사 강요라는 단어로 설명된다.

그니까 모델이 예측값을 다르게 도출해도 그냥 답을 집어 넣어버리는 것이다. 이게 "교사 강요"로 설명된다.

 반면 테스트 과정에서는 컨텍스트 벡터와 <sos>만을 입력으로 받은 후에 다음에 올 단어를 예측하고, 그 단어를 다음 시점의 RNN 셀의 입력으로 넣는 행위를 반복한다.


당연하게도 출력은 softmax다. softmax가 뭐냐고? 그냥 간단하게 설명해서 활성화 함수 중에서 결과 값을 확률 값으로 바꿔주는 함수다. 이는 나중에 다른 글에서 자세하게 다루겠다.

그래서 softmax를 거친 출력값 중 가장 높은 값을 가지는 단어를 출력값으로 가져간다. 


 

일단 이것도 당연하게도 문제가 있다.

1. 위에서 설명했던 차원 압축으로 인한 정보소실 문제

2. RNN의 고질적인 문제인 기울기 소실 문제

이 두 문제가 합쳐져 결국 seq2seq도 문장이 길어지면 번역 품질이 떨어지는 현상이 나타났다.

이 떨어진 정확도를 보정해주기 위해서 어텐션을 고안해서 만들어냈다고 한다. 


어텐션 메커니즘

이게 이제 Transfomer에 기반이 되는 메커니즘이다. 

seq2seq의 단점을 보완하면서 나온 메커니즘인데, 

디코더에서 출력 단어를 예측하는 매 시점마다 인코더에서 전체 입력문장을 다시 한번 참고한다는 것이다. 

모두 참고하기는 하는데, 해당 시점에서 예측해야할 단어와 연관있는 입력단어를 조금 더 attention해서(가중치를 줘서) 보게 된다는 것이다.

Attention(Q, K, V) = Attention Value

주어진 쿼리에 대해서 모든 키의 유사도를 각각 구해서 유사도를 키와 매핑되어있는 각각의 value에 반영해줍니다. 그리고 유사도가 반영된 value값을 모두 더해서 리턴합니다. 

Q = Query : t 시점의 디코더 셀에서의 은닉 상태
K = Keys : 모든 시점의 인코더 셀의 은닉 상태들
V = Values : 모든 시점의 인코더 셀의 은닉 상태들

그냥 간단하게 다르게 말하면 순서 정도로 생각하면 좋을 것 같은데..

은닉상태?

은닉 상태(hidden state)는 순환 신경망(Recurrent Neural Network, RNN)과 같은 시퀀스 모델에서 사용되는 중간 단계의 정보를 나타냅니다. 시퀀스 모델은 입력 데이터의 순서를 고려하여 처리하는 모델로, 문장, 시계열 데이터 등과 같이 순서가 있는 데이터를 다룰 때 주로 사용됩니다.

RNN은 시퀀스 데이터를 처리하는데 사용되는 대표적인 모델로, 시간의 흐름에 따라 정보가 순환하는 구조를 가지고 있습니다. 이때, 각 시간 단계(time step)에서 입력 데이터와 이전 시간 단계의 은닉 상태를 바탕으로 새로운 은닉 상태를 계산합니다.

은닉 상태는 모델이 입력 데이터를 이해하는데 사용되는 중간 표현(representation)으로서, 입력 데이터에 대한 정보가 주로 담겨져 있습니다. 모델이 훈련되면서 입력 데이터의 패턴과 의미를 파악하여 은닉 상태를 갱신하고, 이를 기반으로 다음 시간 단계에서 예측이나 출력을 수행합니다.

일반적으로 RNN의 은닉 상태는 시퀀스의 각 시간 단계마다 존재하며, 이를 표현할 때는 주로 벡터 형태로 표현됩니다. RNN의 각 시간 단계에서의 은닉 상태는 다음 시간 단계로 전달되어 시퀀스 데이터를 순차적으로 처리하는데 도움을 줍니다.

자연어 처리에서는 문장의 의미를 파악하거나 시퀀스 데이터의 패턴을 학습하는데 활용되는 등 중요한 역할을 수행합니다..

조금 다양하게 있는데 순서를 먹여준다고 생각하면 좋을 듯.


트렌스포머

예 그 맞습니다. GPT의 T를 담당하고 있는 그 친구 입니다.

이 친구도 seq2seq의 단점을 개선했지만 인코더 - 디코더 구조를 유지하고 있습니다.

인코더 - 디코더 구조를 따르면서 Attention으로만 구현한 모델입니다. 

RNN을 사용을 안했어요! 오! 엥 그러면 위치 정보를 어떻게 가져가는 거지..?

왜냐면 RNN은 단어 위치에 따라 단어를 순차적으로 입력받아 처리하거든요..

근데 RNN보다 훨씬 좋은 성능을 보여줬다고 합니다. 구성이 어떻게 되었길래 궁금하네요


트렌스포머는 Attention만으로 인코더와 디코더를 만든 친구입니다. 

앞에서는 볼 수 없는 여러 개의 인코더들과 디코더들입니다.  

일단 단어 순서를 어떻게 처리하는지부터 봐야할 것 같습니다

RNN을 사용하지 않아 알 수 없는 순서를 임베딩 벡터에 위치 정보들을 더하여 모델의 입력으로 사용합니다. 

이를 포지서녈 인코딩(Positional encoding)이라고 합니다. 포지서녈 인코딩을 할 때, 사인과 코사인 함수를 사용합니다.

이 과정을 자세히 설명하려면 논문을 뜯어봐야합니다. 그래서 일단 생략하겠습니다. 


어텐션을 살펴보면

같다는 표시는 벡터의 값이 동일 하다는 것이 아닌, 벡터의 출처가 서로 같다는 것을 의미합니다.

각각 인코더, 디코더로 갈리겠네요.

인코더의 셀프 어텐션 : Query = Key = Value 
디코더의 마스크드 셀프 어텐션 : Query = Key = Value
디코더의 인코더-디코더 어텐션 : Query : 디코더 벡터 / Key = Value : 인코더 벡터

이런식으로 3가지가 있습니다.