* https://huggingface.co/blog/how-to-generate
위 사이트를 참고하여 작성한 내용입니다. :)
Open-ended Text Generation(개방형 텍스트 생성)이란 기계가 주어진 context에 이어질 문장을 생성해내는 것이다.
주어진 문장들을 토대로 기계가 스스로 이야기를 써내려갈 수 있게 하는 기술!
언젠가 기계가 작가 대신 글을 쓰는 날이 오지 않을까? 하고 생각해보았다면 그게 이 분야 이야기일 것이다.
(그치만 사람만이 불어넣을 수 있는 창의적인 표현력과 감성이 있어서 '대체'는 어려울 거라 생각한다 :) )
transformer 기반 언어 모델이 발전함에 따라 최근 이 Open-ended Text Generation 분야에도 큰 발전이 있었다.
(transformers 는 자연어처리 분야의 대표적인 파이썬 라이브러리이며, 수많은 언어 모델을 지원한다.)
언어 모델의 성능을 높이는 데 영향을 준 요소에는
- transformer architecture 개선
- 방대해진 학습 데이터의 양
이 있는데, 여기에 덧붙여 decoding 방법을 개선하는 것 또한 중요한 역할을 한다.
decoding 방법이라 하니 어려운 말 같지만, 간단히 말해 기계에서 문장을 생성해내는 방법을 다룬다고 생각하면 될 듯하다.
대표적인 decoding 방법에는 Greedy search, Beam search, Top-K sampling, Top-p sampling 이 있다.
1. Greedy Search
Greedy (탐욕스러운) 단어에 걸맞게, 현재단어 다음에 나올 단어 후보 가운데 '가장 확률이 높은' 것을 선택하는 방법.
그래프에서 보면 "The" 다음에 올 수 있는 단어들은 dog, nice, car 이고 각각의 확률은 0.4, 0.5, 0.1 이다.
(단어 후보군이나 확률을 계산하는 것은 기계 알고리즘 문제로, 이건 이제 또 다른 연구분야라고 보면 된다. 여기선 생략!)
이 때 확률이 가장 높은 nice(0.5) 를 다음 단어로 선택한다.
같은 방법으로 "The", "nice" 후에는 "woman" 을 선택한다.
이렇게 해서 nice(0.5) X woman(0.4) = 0.2 의 확률을 갖는 문장이 생성된다.
- 장점: 비교적 간단한 알고리즘
- 단점1: 동어 '반복' 현상이 빨리 나타난다.
ex) I enjoy walking with my cute dog, but I'm not sure if I'll ever be able to walk with my dog. I'm not sure if I'll ever be able to walk with my dog.
반복 문제는 기계 언어생성분야의 전반적인 문제다. 그치만 greedy/beam search 에서 좀 더 심하다고 함.
- 단점2: 현재 시점의 바로 다음 단어만을 고려하기 때문에, 그보다 더 뒤에 나올 아주 확률이 높은 단어가 있더라도 놓칠 수 있다.
그래프 상에서 "The" 다음에 "dog" 를 선택했다면 "has(0.9)" 라는 아주 높은 확률의 단어를 이어 붙였을 수도 있는데 말이다.
이 점을 개선한 방법이 다음에 나올 Beam Search 이다.
2. Beam Search
Greedy Search 에서 현재시점 바로 다음 단어 한 개만 고려하는 게 한계였다면, 현재시점 이후 여러 step 의 단어들을 '킵'해놓고 함께 고려하는 게 Beam Search 방식이다.
후보를 몇 개까지 킵해놓을 것인가? 를 결정하는 것은 num_beams 인자이다.
이는 실제 코드로 문장 생성할 때 들어가는 옵션 중 하나이다.
* num_beams: Beam Search 에 쓰이는 beam 의 개수.
num_beams=2 인 경우를 나타낸 그래프이다.
("The", "nice") 외에도 그 다음으로 확률이 높은 ("The", "dog") 또한 후보로 킵해둔다.
그 다음 단계에 ("The", "dog", "has") 확률을 계산하여 0.36, ("The", "nice", "woman") 확률을 계산하여 0.2 를 얻어낸다.
이렇게 ("The", "dog", "has") 가 더 높은 확률의 문장임을 알아낼 수 있다.
- 장점: 뒤쪽의 확률이 높은 단어를 놓치지 않을 수 있다.
- 단점1: 아무래도 연산 속도가 좀 걸린다. num_beams 개수가 커질 수록 더욱 그러하다.
- 단점2: Greedy Search 때와 마찬가지로 여전히 반복 문제가 있다.
이 반복 문제를 해결하기 위해 n-gram 을 활용, n-gram 이 반복되지 않도록 하는 방법이 있다.
(n-gram 은 연속된 단어 개수라고 보면 된다. ('I') 는 한 개니까 1-gram 또는 unigram, ('I', 'am') 는 두 개니까 2-gram 또는 bigram)
* no_repeat_ngram_size: 특정 n-gram 이 생성 문장 내에서 반복되지 않게 함
Beam Search 는 일단 문장들을 끝까지 다 생성한 후, 확률에 따라 문장을 선택한다.
최종적으로 생성된 문장들 가운데 상위 확률 몇 개의 문장을 선택할 것인지를 결정하는 게 num_return_sequences 이다.
* num_return_sequences: 생성 문장들 가운데 몇 개를 결과로 받을 것인지 선택
num_return_sequences <= num_beams 이어야 함!
beam search 로 생성된 문장은 사람이 만들어내는 문장과 비교했을 때 다소 뻔한 문장이 많다.
즉, 문장 내 단어의 선택과 표현에 있어서 다양성과 변이가 부족하다는 것.
사람은 예측하기 힘든 단어들을 종종 사용하는데 비해 기계는 한정된 학습데이터에 기반하여 문장을 생성하기 때문일 것이다.
이 간극을 조금이나마 좁혀보고자 언어 모델에 randomness(무작위성)을 부여하는 방법이 있는데
이제 설명할 Sampling 이 그것이다.
3. Sampling
조건부 확률 분포에 따라 다음에 올 단어를 랜덤하게 선택하는 방법.
do_sample 인자로 sampling 여부를 결정한다.
* do_sample: sampling 을 사용하여 문장 생성을 할 것인지 정함(True/False)
- 장점: 표현력이 기존보다 풍부해진다. 즉, 다양한 단어를 사용한 문장이 생성될 확률이 높다.
- 단점: 말이 안 되는, 어색한 표현이 나올 수도 있다.
단점을 개선하기 위해서 확률이 높은 단어는 더 높이고, 낮은 단어는 더 낮추는 식으로 확률분포를 조정하는 방식이 있다.
그리고 이 조정하는 정도를 결정하는 인자가 temperature 값이다.
* temperature: sampling 을 할 때 무작위성을 얼마나 조정할 것인지 결정 (값이 0에 가까울 수록 덜 랜덤해진다.)
4. Top-K Sampling
Sampling 방식의 일종. K개의 후보 단어를 먼저 필터링하고, 그 단어들 사이에서 확률분포가 일어난다.
K=6 으로 설정했을 때.
- 장점: 높은 확률 순 K개로 단어 pool 을 제한함에 따라 확률이 낮은(다소 어색한) 단어들을 걸러낼 수 있다.
(오른쪽 "not", "the", "small", "told")
- 단점1: 후보에 오를 만한 단어임에도 Top-K 개 안에 들지 못해서 걸러질 수도 있다.
(왼쪽 "people", "big", "house", "cat")
- 단점2: 확률이 낮은(다소 어색한) 단어임에도 걸러지지 않을 수도 있다.
(오른쪽 "down", "a")
이러한 Top-K Sampling 의 단점을 보완한 방식이 Top-p sampling 이다.
5. Top-p Sampling
Top-K Sampling 과 유사하나, Top-K 가 상위 K개의 단어를 선택한다면,Top-p sampling 에서는 누적 확률 p 를 기준점으로 두고 단어를 선택한다.
p=0.92 으로 설정했을 때.
누적 확률이 92% 를 넘지 않는 선에서만 단어들을 선택한다.
이렇게 되면 확률이 낮은(다소 어색한) 단어들 대부분을 거르기가 쉬워진다.
언뜻 봐서는 Top-p 가 Top-K 보다 훨씬 좋은 방법이 아닌가 싶지만 실제로는 Top-K 도 성능이 잘 나오는 걸로 알고 있다.
그리고 Top-K, Top-p 를 모두 사용하여 적절히 값을 튜닝하면 더 좋은 성능을 낼 수 있다.
코드를 통한 문장 생성
위에서 설명한 내용을 바탕으로 실제 문장을 생성하려 한다면 어떻게 할까?
huggingface document 에 있는 예시 코드를 가져와보았다.
# transformers 라이브러리에서 사용할 tokenizer 와 언어 모델을 import
from transformers import AutoTokenizer, AutoModelForCausalLM
tokenizer = AutoTokenizer.from_pretrained("distilgpt2")
model = AutoModelForCausalLM.from_pretrained("distilgpt2")
input_context = "The dog"
input_ids = tokenizer(input_context, return_tensors="pt").input_ids
outputs = model.generate(
input_ids,
do_sample=True,
max_length=50,
top_p=0.92,
top_k=0,
num_beams=5,
num_return_sequences=5
)
# num_return_sequences=5 이므로 outputs 에 5개의 문장이 있으나 첫 번째 문장만 출력해봄
print(tokenizer.decode(outputs[0], skip_special_tokens=True))
tokenizer 는 입력 문장을 토큰 단위로 쪼개는 역할을 하고,
model 은 쪼개진 입력 문장을 가지고 실제 문장생성을 한다.
model.generate 안에 들어가는 인자들이 위에서 설명한 decoding 옵션들임을 알 수 있다.
위와 같이 "The dog" 을 넣은 후 다음 결과를 얻었다.
# The dog was killed in a car accident at a police station on Tuesday evening.
상당히 정확한 문장이 생성되어서 놀랐다 @.@
여러 문장으로 테스트해보고,
decoding 옵션도 조절해보면서 가장 좋은 성능을 내는 옵션 구성을 찾아내는 것이 중요하다. :D
':: ai > nlp' 카테고리의 다른 글
NLP :: MecabTokenizer() 생성 시 MeCab Tagger 런타임 에러(RuntimeError) 해결 (0) | 2021.12.15 |
---|---|
NLP :: sentencepiece tokenizer 로 문장 토큰화하기 (0) | 2021.12.14 |
NLP :: 파이썬(python) 으로 OpenNMT 모델 통해 문장 번역하기(translate) (0) | 2021.12.10 |