Patrick's 데이터 세상

Huggingface generate 문장 생성 본문

Deep Learning/NLP 개발

Huggingface generate 문장 생성

patrick610 2023. 7. 21. 14:27
반응형
SMALL

 

 

 

 

huggingface의 transformer 라이브러리에서 model.generate는 모든 자동 회귀 auto-regressive 언어 모델에 적용 가능합니다.

이 generate 함수를 이용해서 문장 생성 하는데 보다 적은 노력으로 훌륭한 문장을 생성할 수 있습니다.

 

  • 문장A: GPT3는 문장을 생성한다

문장을 확률로 표현하고 싶을때, 위 문장의 확률은    과 같이 조건부 확률의 곱입니다.

 

 

auto-regressive

auto-regressive은 문장의 확률값을 구하듯이 이전의 단어들로 다음 단어의 확률을 예측하면서 방식을 말합니다.

현재 transformers에서 auto-regressive 언어 생성을 사용할 수 있는 모델은 GPT 계열 모델, XLNet, CTRL, TransfoXL, Bart, T5등이 있습니다.

 

자동 회귀 언어 생성 모델에서 단어 시퀀스의 확률 분포가 조건부 다음 단어 분포의 곱이라는 것을 전제로 합니다.

초기 컨텍스트가 입력되면 각 스텝마다 모든 확률을 곱합니다.

 

 

!pip3 install torch
!pip3 install transformers
import torch
from transformers import PreTrainedTokenizerFast, GPT2LMHeadModel
model = 'skt/kogpt2-base-v2'

tokenizer = PreTrainedTokenizerFast.from_pretrained(
    model,
    eos_token="</s>",
)
model = GPT2LMHeadModel.from_pretrained(
    model,
).to('cuda')
model.eval()

모델은 kogpt2로 실습합니다.

 

 

 

👉🏻 Greedy Search

언어 모델(Language Model)은 컨텍스트(단어 혹은 단어 시퀀스)를 입력받아 다음 단어가 나타날 확률을 출력으로 반환합니다.

모델의 출력 확률분포로부터 다음 토큰을 반복적으로 선택하는 과정이 문장 생성 태스크가 됩니다.

 

Greedy Search는 탐욕적 알고리즘으로 언어 모델에서 타임스텝 t에서 가장 높은 확률을 갖는 토큰을 다음 토큰으로 선택하는 전략입니다. 매순간 최선(best)를 선택해 탐색 범위를 줄이는 것이 핵심 아이디어입니다.

직관적이며 짧은 문장을 선택할 때는 괜찮은 전략입니다.

 

 

왼쪽 그림은 참고로 생성 후보를 추려보면 다음과 같습니다.

영어 예시인데 한글도 마찬가지 방식입니다.

∙ The dog and

∙ The dog runs

∙ The dog has 

∙ The nice woman

∙ The nice house

∙ The nice guy

∙ The car is

∙ The car drives

∙ The car turns

 

위 예시처럼 모든 컨텍스트를 입력하고 그 컨텍스트에 대한 단어 확률분포를 계산하는 것은 불가능에 가깝습니다.

'The'가 입력되었을 때 다음으로 나올 단어로 'nice'이 0.5로 가장 높고 'The nice'을 입력해 그 다음 단어 확률분포를 계산하면 다음으로 가장 높은 'woman'이 0.4로 'The nice woman'이 가장 높습니다.

 

input_ids = tokenizer.encode("안녕하세요?", return_tensors="pt").to('cuda')
with torch.no_grad():
    generated_ids = model.generate(
        input_ids,
        do_sample=False,
        min_length=10,
        max_length=50,
    )

핵심 인자 do_sample=False
max_length는 생성 최대 길이이며 이보다 길거나 짧아도 eos 등 스페셜 토큰이 나타나면 생성을 중단.
min_length는 생성 최소 길이이며 이보다 짧은 구간에서 스페셜 토큰이 등장해 생성이 중단될 경우 해당 토큰이 나올 확률을 0으로 수정하여 문장이 종료되지 않게 함.

 

print(tokenizer.decode([el.item() for el in generated_ids[0]]))

안녕하세요?"

"그럼, 그건 뭐예요?"

"그럼, 그건 뭐예요?"

"그럼, 그건 뭐예요?"

"그럼, 그건 뭐예요?"

 

 

👉🏻 Beam Search

순간의 최선이 전체의 최선이 되지 않을 수 있기 때문에 Greedy Search도 완벽한 대안은 아닙니다.

 

Beam Search Greedy Search에서 숨겨져 있는 높은 확률의 토큰을 놓칠 수 있다는 단점을 보완하기 위해 고안된 방법입니다.

각 타임스탭에서 가장 가능성 있는 빔 크기(num_beams)개의 크기 만큼 시퀀스를 유지하여 선택질를 계산 범위에 넣고, 최종적으로 가장 확률이 높은 가설을 선택하는 방법입니다.

 

 

빔 크기가 2일 때, 'The'를 입력할 때, 그 뒤에 nice(0.5), dog(0.4), car(0.1) 순으로 예측하면 빔 크기가 2이므로 'dog', 'nice'만 탐색 대상으로 남깁니다.

모델에 'The nice', 'The dog'을 각각 입력해 단어 시퀀스 확률을 계산합니다.

∙ The dog and : 0.4 X 0.05 = 0.02

∙ The dog runs : 0.4 X 0.05 = 0.02

∙ The dog has : 0.4 X 0.9 = 0.36

∙ The nice woman : 0.5 X 0.4 = 0.2

∙ The nice house : 0.5 X 0.3 = 0.18

∙ The nice guy : 0.5 X 0.3 = 0.15

 

빔 크기가 2일 때, 위 6가지 경우의 수에서 가장 확률이 높은 시퀀스 두 개만 남김.

∙ The dog has : 0.4 X 0.9 = 0.36

∙ The nice woman : 0.5 X 0.4 = 0.2

여기서 중단되면 조금 더 확률이 높은 'The dog has'가 최종 생성됩니다.

빔 서치는 빔 크기만큼의 경우의 수를 선택하기 때문에 그리디 서치에 비해 조금이라도 더 높은 확률을 내는 문장을 생성할 수 있는 가능성을 높입니다.

 

with torch.no_grad():
    generated_ids = model.generate(
        input_ids,
        do_sample=False,
        min_length=10,
        max_length=50,
        num_beams=3,
    )

do_sample=False
핵심 인자 num_beams=3로 빔 크기 설정.
빔 크기가 1 이면 매순간 최대 확률을 내는 단어만 선택하는 뜻이라 그리디 서치로 동작함.

print(tokenizer.decode([el.item() for el in generated_ids[0]]))

안녕하세요?"

"그렇지 않습니다."

"그렇지 않습니다."

"그렇지 않습니다."

"그렇지 않습니다."

"그렇지 않습니다."

"그렇지 않습니다."

"그

 

 

👉🏻  Reduce Repetiiton

위 결과들처럼 모델 출력이 반복되는 상황을 줄일 수 있는 방법입니다.

코드가 수행될 때, 토큰이 n-gram 단위로 반복될 경우 모델이 계산한 결과를 무시하고, 해당 n-gram의  등장 확률을 0으로 만들어 생성에서 배제하게 됩니다.

 

◉ no_repeat_ngram_size

with torch.no_grad():
    generated_ids = model.generate(
        input_ids,
        do_sample=False,
        min_length=10,
        max_length=50,
        no_repeat_ngram_size=3,
    )

핵심 인자는 no_repeat_ngram_size=3로 3개 이상의 토큰이 반복될 경우 해당 3-gram 등장 확률을 인위적으로 0으로 만듭니다.

print(tokenizer.decode([el.item() for el in generated_ids[0]]))

안녕하세요?"

"그럼, 그건 뭐예요?" 하고 나는 물었다.

"그건 뭐죠?" 나는 물었다.

나는 대답하지 않았다.

"그런데 왜 그걸 물어요? 그건 무슨 뜻이에요?

 

 repetition_penalty

with torch.no_grad():
    generated_ids = model.generate(
        input_ids,
        do_sample=False,
        min_length=10,
        max_length=50,
        repetition_penalty=1.0
    )

리피티션 패널티(repetition penalty) 방식으로 반복을 통제할 수도 있습니다.

repetition_penalty=1.0를 인자로 주어 패널티를 적용하는데, 값이 1.0이면 아무 패널티를 적용하지 않은 것과 같아 그리디 서치와 같은 결과가 나옵니다.

print(tokenizer.decode([el.item() for el in generated_ids[0]]))

안녕하세요?"

"그럼, 그건 뭐예요?"

"그럼, 그건 뭐예요?"

"그럼, 그건 뭐예요?"

"그럼, 그건 뭐예요?"

 

 

with torch.no_grad():
    generated_ids = model.generate(
        input_ids,
        do_sample=False,
        min_length=10,
        max_length=50,
        repetition_penalty=1.5,
    )

패널티 값을 점점 늘릴 때마다 반복이 줄어드는 경향이 있습니다.

print(tokenizer.decode([el.item() for el in generated_ids[0]]))

안녕하세요?"

"그럼, 그건 뭐예요, 아저씨. 저는 지금 이 순간에도 괜찮아요. 그리고 제가 할 수 있는 일은 아무것도 없어요.

이제 그만 돌아가고 싶어요.

제가 하는 일이 무엇

 

 

👉🏻  Top-k Sampling

Greedy Search, Beam Search 방식은 모델이 출력한 다음 토큰 확률분포를 점수로 활용한 것입니다.

탑k 샘플링(Top-k sampling)은 모델이 예측한 다음 토큰 확률분포에서 확률값이 가장 높은 k개의 토큰 가운데 하나를 다음 토큰으로 선택하는 기법입니다.

 

모델에 'The'라는 컨텍스트를 입력했을 때 모델은 다음 토큰으로 'dog' 0.4, 'nice' 0.5, 'car' 0.1이 나올 것이라 예측했는데 'nice'가 50%로 확률이 제일 크지만 'car'가 나올 확률은 10%로 여기서 확률적으로 선택합니다.

 

 

왼쪽 차트에서 'The'를 입력하고 k=6일 때, 선택된 다음 단어가 'car'이면 새로운 컨텍스트 'The car'를 모델에 입력해 다음 토큰 확률분포를 계산합니다.

그리고 그것을 내림차순 정렬한 것이 오른쪽 그림입니다. k가 6이므로 'drives'부터 'a'까지 다음 토큰 후보가 됩니다.

 

 

 

with torch.no_grad():
    generated_ids = model.generate(
        input_ids,
        do_sample=True,
        min_length=10,
        max_length=50,
        top_k=50,
    )

핵심 인자는 do_sample=True, top_k=50입니다.

50개의 토큰을 다음 후보로 설정한다는 의미이고 top_k는 1 이상의 정수를 입력해야 합니다.

top_k가 1이면 do_sample이 True여도 매 스탭마다 후보를 1개만 둔다는 의미이고 그리디 서치와 동일한 효과입니다.

 

print(tokenizer.decode([el.item() for el in generated_ids[0]]))

안녕하세요?"

"아니,요, 그렇습니다. 그러나 제가 더는 말씀드리고 싶지 않습니다."

그러자 웬일인지 그는 더더욱 고개를 흔들었다.

"이곳에 살 때는 어떤가요?"

"아니요,

 

 

👉🏻 Temperature Scaling

모델의 토큰 확률분포에 변형을 가해 문장을 다양하게 생성하는 기법입니다.

확률분포를 대소 관계의 역전없이 분포의 모양만을 바꾸는 것입니다.

 

'The' 다음 토큰 확률은 'nice' 0.5, 'dog' 0.4, 'car' 0.1이었으나 템퍼러쳐 스케일리을 적용하면 'nice' 0.75, 'dog' 0.23, 'car' 0.02가 됩니다. 원래 컸던 확률은 더 커지고, 작았던 확률은 더 작아져 모양이 더 sharp해집니다.

 

with torch.no_grad():
    generated_ids = model.generate(
        input_ids,
        do_sample=True,
        min_length=10,
        max_length=50,
        top_k=50,
        temperature=100000000.0,
    )

temperaturue=100000000.0를 인자로 사용하고 temperature를 1로 설정하면 확률분포를 어떠한 변형없이 사용한다는 의미입니다.

반대로 temperature를 1보다 큰 값으로 두면 확률분포가 평평해지면서 기존 top-k 샘플링에서 선택되기 어려웠던 토큰들이 다음 토큰으로 선택될 수 있습니다.

그만큼 다양한 문장 생성이 가능하지만 생성 문장의 품질이 나빠질 수 있습니다.

 

print(tokenizer.decode([el.item() for el in generated_ids[0]]))

안녕하세요?'

김진석이 차분히 대견스런 말실수도 쏟아진다.

서석구가 갑자기 정숙이와 악동질을 하려고 덤뱅이라는 표정을 하는데 갑자기 두근덜귀가 들리지 않은다.

순욱하고 귀까지 내려

 

 

👉🏻 Top-p Sampling

확률값이 높은 순서대로 내림차순 정렬을 한 뒤 누적 확률값이 p 이하인 단어들 가운데 하나를 다음 단어로 선택하는 기법입니다.

뉴클리어스 샘플링(necleus sampling)이라고도 불립니다.

top-k 샘플링은 상위 k개를 후보로 삼고, top-p 샘플링은 누적 확률값이 p 이하인 단어들을 후보로 삼는다는 것에 차이가 있습니다.

 

'The' 컨텍스트를 입력하고 p가 0.92로 설정될 때, 모델의 출력 다음 토큰 후보는 'nice'부터 'house'까지 9개이고 누적합은 0.94입니다.

'The car'를 입력하면 'drives'부터 'turns'까지 단어후보가 3개가 되고 누적합이 0.97이 됩니다.

 

 

 

탑p 샘플링은 누적 확률합으로 후보 단어를 취하기 때문에 누적 확률값은 일정하고 후보 단어 갯수는 해당 분포에 따라 달라집니다.

반대로 탑k 샘플링은 단어 갯수로 후보 단어를 취하기 때문에 후보 단어 갯수는 일정한 반면 누적 확률값은 해당 분포에 따라 달라집니다.

두 샘플링 모두 확률이 낮은 단어는 제외하기 때문에 문장을 품질을 높입니다.

 

with torch.no_grad():
    generated_ids = model.generate(
        input_ids,
        do_sample=True,
        min_length=10,
        max_length=50,
        top_p=0.92,
    )

핵심 인자는 do_sample=True, top_p=0.92입니다.

top_p를 1.0로 설정하면 확률값이 낮은 토큰 모두 고려하게 되어 모든 토큰을 고려한다는 의미입니다.

top_p가 0에 가까울수록 후보 단어 수가 줄어들고 그리디 서치와 비슷해집니다.

 

print(tokenizer.decode([el.item() for el in generated_ids[0]]))

안녕하세요?"

"그건 잘 모르겠어요. 내가 다 알아. 난 너 같은 인간이 아닌데."

"그런 거죠? 내 말 좀 들어볼게요."

"아직은 내 말 잘 들어보지 못한 건가?

 

 

👉🏻 종합 적용

with torch.no_grad():
    generated_ids = model.generate(
        input_ids,
        do_sample=True,
        min_length=10,
        max_length=50,
        repetition_penalty=1.5,
        no_repeat_ngram_size=3,
        temperature=0.9,
        top_k=50,
        top_p=0.92,
    )

do_sample=True : 컨텍스트가 동일한 경우 다른 문장을 나오게 하기 위함.

min_length=10, max_length=50 : 생성된 문장이 너무 짧거나 길지 않게 하기 위함.

repetition_penalty=1.5, no_repeat_ngram_size=3 : 반복되는 토큰 제외

temperature=0.9 : 확률분포를 sharp하게 만들고 확률값이 높은 토큰이 더 잘 나오도록 설정.

top_k=50, top_p=0.92 : 탑k 샘플링, 탑p 샘플링을 동시에 적용해 확률값이 낮은 후보 단어는 배제하도록 설정.

 

print(tokenizer.decode([el.item() for el in generated_ids[0]]))

안녕하세요?"라는 질문에 "안 돼. 너희는 뭘 하느냐"고 답해 화제가 됐다.

김현욱은 지난 8일 서울 강남구 삼성동 코엑스인터컨티넨탈 호텔에서 열린 '2011 한국전자전

 

 

👉🏻 Contrastive Search

Greedy Search, Beam Search와 같은 Deterministic 기법은 문장의 끝이 정해지지 않은 문장 생성 task인 open-ended text generation task에 사용될 때 반복되는 단어를 연속해서 생성하거나 너무 일반적인 문장을 결과로 낸다는 단점이 있고, Top-k, Top-p Sampling와 같은 Stochastic 기법은 Randomness로 인해 Prefix 텍스트와 일관적이지 않은 문장을 생성하는 문제가 있습니다.

 

이에 대한 대안으로  "A Contrastive Framework for Neural Text Generation" 논문을 통해 제안된 기법인 Contrastive Search는 Deterministic 기법과 Stochastic 기법의 장점을 모두 취하는 탐색 기법입니다.

 

t-1까지의 Prefix가 주어졌을 때 토큰 v가 등장할 확률에서 v의 Token Representation과 첫 번째 토큰부터 t-1 토큰까지 각각의 코사인 유사도를 계산해 가장 높은 유사도로 선정합니다.

언어 모델에 의해 높은 확률로 등장하면서 기존에 생성된 토큰들과도 유사도가 낮을수록 다음 토큰으로 생성될 확률이 높아집니다.

왼쪽 항은 Consistency를 맡고 오른쪽 항은 생성된 문장이 단조롭고 퀄리티가 낮아지는(degenerated) 현상을 방지하는 패널티로 적용됩니다.

논문에서도 Contrastive Search로 생성된 문장은 Repetition 문제로 토큰 간 유사도가 높은 'Greedy Search'보다 토큰 간 유사도가 크지 않은 모습을 보였습니다.

유사도 계산 과정의 추가로 레이턴시가 증가하지만 Repetition와 Randomness 문제를 해결하는 유의미한 기법이라고 볼 수 있습니다.

 

with torch.no_grad():
    generated_ids = model.generate(
        input_ids,
        do_sample=True,
        min_length=10,
        max_length=50,
        penalty_alpha=0.6,
        top_k=50
    )

Huggingface Transformers 4.24.0 버전에 구현되어 인자를 penalty_alpha=0.6로 사용할 수 있습니다.

 

print(tokenizer.decode([el.item() for el in generated_ids[0]]))

안녕하세요?’라고 말했다.

마지막엔 “지금 전화가 와서 그냥 답변하겠습니다. 제가 답변한 것도 아닌데 어떻게 그걸 들을 수 있겠습니까. 뭘 들을 수 있겠습니까. 제가 당신이 원하는 대로 하지 않거나 아웅

 

 

👉🏻 정리

Top-k, Top-p 샘플링으로 기존의 Greedy Search, Beam Search 보다 좀 더 유창한 텍스트를 생성해보았습니다.

하지만 경우에 따라서는 기존 방식들이 좀 더 텍스트가 더 잘 맞는 경우가 있고 Constrastive Search 방법이 잘 나올 경우가 있으므로 여러 인자값으로 실험해보며 자신의 연구에 맞는 다양한 디코딩 방법이 필요할 것입니다.

 

 


 

출처

https://huggingface.co/blog/how-to-generate

 

How to generate text: using different decoding methods for language generation with Transformers

How to generate text: using different decoding methods for language generation with Transformers Note: Edited on July 2023 with up-to-date references and examples. Introduction In recent years, there has been an increasing interest in open-ended language g

huggingface.co

https://ratsgo.github.io/nlpbook/docs/generation/inference1/

 

Inference (1)

pratical tips for Natural Language Processing

ratsgo.github.io

https://huggingface.co/blog/introducing-csearch?fbclid=IwAR2SB4IaQqhSIKRC7rUkavJt67-OY97VnaCZnWsmxvLYkYgX-sIq7LFYVKc 

 

Generating Human-level Text with Contrastive Search in Transformers 🤗

Generating Human-level Text with Contrastive Search in Transformers 🤗 1. Introduction: Natural language generation (i.e. text generation) is one of the core tasks in natural language processing (NLP). In this blog, we introduce the current state-of-the-

huggingface.co

 

반응형
LIST
Comments