일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- NLP
- multi-head attention
- pytorch
- 취업
- BLEU Score
- bert
- huggingface
- Transformer
- Eliza
- scaled dot-product attention
- Chatbot
- BoostCamp
- 백준
- FSML
- boj
- KLUE
- BELU
- Dialogue System
- beam search
- Conversation System
- KLUE-RE
- Transformers
- ai-tech
- Relation Extraction
- fine-tuning
- MT-DNN
- layer normalization
- GPT-1
- Prompt Tuning with Rules for Text Classification
- text classification
- Today
- Total
dukim's blog
Eliza in Python - (2) 본문
import random
import re
여기선 POS(Part Of Speech) tagger를 불러와 활용한다. POS tagger는 입력된 단어들을 요소로 갖는 리스트를 입력받아(정확히는 입력된 문장을 tokenizing한 token들을 입력 받아) 이를 분석해 문장 내에서의 각 token의 문법적 역할(품사)을 출력한다.
아래 예제에서는 NLTK를 사용하여 POS tagger를 만든다. NLTK는 몇 가지 tagger를 내장하고 있긴하지만, Eliza에 적합한 것들은 없어서 직접 만들어볼 것이다.
여기선 the Brown Corpus라 하는 POS tagging이 비교적 잘 되어있는 데이터셋을 이용해 POS tagger를 훈련시킬 것이다. tag의 각 항목들은 여기에서 확인할 수 있다. 이 tag set에서 실질적으로 유용한 점은 아래 두 가지이다.
첫째, 이것은 주격 대명사(subject pronoun)와 목적격 대명사(object pronoun)를 구분한다(주격 명사를
PPSS
, 목적격 명사를PPO
tag로 구별한다). 따라서 단어you
가 함수translate
로 변환될 때 주격인I
로 변환되어야할지 목적격인me
로 변환되어야할지를 결정할 수 있다.둘쨰, 이것은 주어가 일반명사(common nouns)와 고유명사(proper nouns)를 구분한다(일반명사이면
NN
, 고유명사이면NP
tag로 구별한다). 따라서 문장의 첫번째로 오는 대문자 단어를 챗봇의 답변 문장의 중간에 포함시킬 때, 대문자를 유지할 것인지를 결정할 수 있다.
아래 코드는 tagger를 빌드하고 pickle로 저장하는 코드이다. ```python
import nltk
from pickle import dump
# Download the brown corpus
nltk.download('brown')
[nltk_data] Downloading package brown to /Users/dukim/nltk_data... [nltk_data] Package brown is already up-to-date!
True
# training & saving the POS tagger
unigram_tagger = nltk.UnigramTagger(nltk.corpus.brown.tagged_sents())
brill_tagger_trainer = nltk.tag.brill_trainer.BrillTaggerTrainer(unigram_tagger, nltk.tag.brill.brill24())
tagger = brill_tagger_trainer.train(nltk.corpus.brown.tagged_sents(), max_rules=1000)
outfile = open("bbt.pkl", "wb")
dump(tagger, outfile, -1)
outfile.close()
보다시피 대부분의 작업이 NLTK로 이뤄진다. 하지만 NLTK 내부에서 어떤 일이 일어나고 있는지 알 필요가 있다. 여기서 우리는 Brill Tagging이라 불리는 알고리즘을 사용한다. 이에 대해 NLTK 공식 문서에서는 Chapter 5의 Section 6에서 설명하고 있다. 이 알고리즘은 단순한 tagger인 Unigram Tagger
를 개선하여 만든 것으로, Unigram Tagger
는 맥락은 무시한채, 각 단어가 문장에서 가장 많이 맡게되는 품사로 태깅한다.
BrillTaggerTrainer
는 기존 Unigram Tagger
에서 발생하는 실수들을 수정하는 규칙들을 발견한다. 이러한 규칙들은 다양한 feature를 사용하는데, 예를 들어 주변 단어와, 주변 단어의 품사 등을 활용한다. 위 코드를 실행하면 이 training data를 사용하여 가능한 규칙들을 탐색하고, unigram tagger에서 제대로 처리하지 못 하는 것들을 처리할 수 있는 규칙들을 찾을 것이다. 그러나 POS tagger를 학습시키는데 시간이 오래 걸리기 때문에, 나중을 위하여 pickle 파일로 저장한다. 저장된 tagger는 NLTK에 대한 의존성이 없기 때문에 나중엔 NLTK를 불러오지 않고도 사용할 수 있다.
from pickle import load
infile = open("bbt.pkl", "rb")
tagger = load(infile)
infile.close()
pickle을 이용해 tagger
를 불러온 다음에는, 사용자의 발화(utterance)를 tokeninzing 하여 리스트 L
로 만들고, 함수 tagger.tag(L)
를 실행한다. 실행결과, (단어, 품사) 쌍을 원소로 갖는 리스트를 얻는다.
아래는 간단한 tokenizer 함수.
def tokenize(text) :
return [tok for tok in re.split(r"""([,.;:?"]?) # optionally, common punctuation
(['"]*) # optionally, closing quotation marks
(?:\s|\A|\Z) # necessarily: space or start or end
(["`']*) # snarf opening quotes on the next word
""",
text,
flags=re.VERBOSE)
if tok != '']
이제 기존 translate
함수가 처리하지 못했던 케이스를 개선할 수 있다. 기존의 reflection_of
dictionary는 아래와 같다.
untagged_reflection_of = {
"am" : "are",
"i" : "you",
"i'd" : "you would",
"i've" : "you have",
"i'll" : "you will",
"i'm" : "you are",
"my" : "your",
"me" : "you",
"you've": "I have",
"you'll": "I will",
"you're": "I am",
"your" : "my",
"yours" : "mine"}
기존 버전은 주격 대명사와 목적격 대명사를 잘 구분하지 못했지만, 이 버전은 앞에서 학습한 tagger를 이용하여 구분할 수 있다. 각 token을 tagging해 얻은 튜플을 통해 바꿀 단어를 대응시키는 방식이다.
tagged_reflection_of = {
("you", "PPSS") : "I",
("you", "PPO") : "me"
}
아래 코드는 개별 token을 translate 하는 코드이다. capitalization을 처리하기 위해서 NP
tag를 이용하였다.
def translate_token(x):
word, tag = x
wl = word.lower()
if (wl, tag) in tagged_reflection_of:
return (tagged_reflection_of[wl, tag], tag)
if wl in untagged_reflection_of:
return (untagged_reflection_of[wl], tag)
if tag.find("NP") < 0:
return (wl, tag)
return (word, tag)
한편, are
나 were
와 같은 동사를 처리하는 것은 좀 더 까다롭다. tagger가 2인칭 주어인 you
나 3인칭 복수 they
를 나타내지는 않기 때문이다. 그러나, 영어에서는 주어가 대개 동사에 꽤 가까이에 위치하고, you
와 같은 단어가 이어지는 단어에 따라 변형되는 일이 없다. 따라서 동사에 가장 가까운 명사구를 찾아서 동사의 주어가 우리가 목표로 삼고자 하는 대명사 중 하나인지 유추할 수 있다.
subject_tags = ["PPS", # he, she, it
"PPSS", # you, we, they
"PN", # everyone, someone
"NN", # dog, cat
"NNS", # dogs, cats
"NP", # Fred, Jane
"NPS" # Republicans, Democrats
]
def swap_ambiguous_verb(tagged_words, tagged_verb_form, target_subject_pronoun, replacement) :
for i, (w, t) in enumerate(tagged_words) :
if (w, t) == tagged_verb_form :
j = i - 1
# look earlier for the subject
while j >= 0 and tagged_words[j][1] not in subject_tags :
j = j - 1
# if subject is the target, swap verb forms
if j >= 0 and tagged_words[j][0].lower() == target_subject_pronoun :
tagged_words[i] = replacement
# didn't find a subject before the verb, so probably a question
if j < 0 :
j = i + 1
while j < len(tagged_words) and tagged_words[j][1] not in subject_tags :
j = j + 1
# if subject is the target, swap verb forms
if j < len(tagged_words) and tagged_words[j][0].lower() == target_subject_pronoun :
tagged_words[i] = replacement
이전 버전에서 동사의 주어를 잘못 처리하는 경우는 "are", "am", "were" 그리고 "was"의 네 가지 경우이다. 잘못 처리된 동사를 고치는 함수는 다음과 같다. 처리하기 전에 구두점을 제거한다.
def handle_specials(tagged_words) :
# don't keep punctuation at the end
while tagged_words[-1][1] == '.' :
tagged_words.pop()
# replace verb "be" to agree with swapped subjects
swap_ambiguous_verb(tagged_words, ("are", "BER"), "i", ("am", "BEM"))
swap_ambiguous_verb(tagged_words, ("am", "BEM"), "you", ("are", "BER"))
swap_ambiguous_verb(tagged_words, ("were", "BED"), "i", ("was", "BEDZ"))
swap_ambiguous_verb(tagged_words, ("was", "BEDZ"), "you", ("were", "BED"))
위 과정을 하나로 합쳐보자. 먼저 문장을 token으로 변환하고, 각 token에 POS tagging을 실시한다. 그 다음 tag를 이용해 translate를 수행하고, 잘못 변환된 동사를 처리한 뒤, 결과를 출력한다.
여기서 사용된 tagger는 다양한 유형의 구두점을 잘 잡아내기 때문에, 출력될 문장에서 어디에 공백을 넣을지 알 수 있다.
close_punc = ['.', ',', "''"]
def translate(this):
tokens = tokenize(this)
tagged_tokens = tagger.tag(tokens)
translation = [translate_token(tt) for tt in tagged_tokens]
handle_specials(translation)
if len(translation) > 0 :
with_spaces = [translation[0][0]]
for i in range(1, len(translation)) :
if translation[i-1][1] != '``' and translation[i][1] not in close_punc :
with_spaces.append(' ')
with_spaces.append(translation[i][0])
return ''.join(with_spaces)
정규표현식을 이용한 규칙은 이전과 동일하다.
rules = [(re.compile(x[0]), x[1]) for x in [
['How are you?',
[ "I'm fine, thank you."]],
["I need (.*)",
[ "Why do you need %1?",
"Would it really help you to get %1?",
"Are you sure you need %1?"]],
["Why don't you (.*)",
[ "Do you really think I don't %1?",
"Perhaps eventually I will %1.",
"Do you really want me to %1?"]],
["Why can't I (.*)",
[ "Do you think you should be able to %1?",
"If you could %1, what would you do?",
"I don't know -- why can't you %1?",
"Have you really tried?"]],
["I can't (.*)",
[ "How do you know you can't %1?",
"Perhaps you could %1 if you tried.",
"What would it take for you to %1?"]],
["I am (.*)",
[ "Did you come to me because you are %1?",
"How long have you been %1?",
"How do you feel about being %1?"]],
["I'm (.*)",
[ "How does being %1 make you feel?",
"Do you enjoy being %1?",
"Why do you tell me you're %1?",
"Why do you think you're %1?"]],
["Are you (.*)",
[ "Why does it matter whether I am %1?",
"Would you prefer it if I were not %1?",
"Perhaps you believe I am %1.",
"I may be %1 -- what do you think?"]],
["What (.*)",
[ "Why do you ask?",
"How would an answer to that help you?",
"What do you think?"]],
["How (.*)",
[ "How do you suppose?",
"Perhaps you can answer your own question.",
"What is it you're really asking?"]],
["Because (.*)",
[ "Is that the real reason?",
"What other reasons come to mind?",
"Does that reason apply to anything else?",
"If %1, what else must be true?"]],
["(.*) sorry (.*)",
[ "There are many times when no apology is needed.",
"What feelings do you have when you apologize?"]],
["Hello(.*)",
[ "Hello... I'm glad you could drop by today.",
"Hi there... how are you today?",
"Hello, how are you feeling today?"]],
["I think (.*)",
[ "Do you doubt %1?",
"Do you really think so?",
"But you're not sure %1?"]],
["(.*) friend(.*)",
[ "Tell me more about your friends.",
"When you think of a friend, what comes to mind?",
"Why don't you tell me about a childhood friend?"]],
["Yes",
[ "You seem quite sure.",
"OK, but can you elaborate a bit?"]],
["No",
[ "Why not?"]],
["(.*) computer(.*)",
[ "Are you really talking about me?",
"Does it seem strange to talk to a computer?",
"How do computers make you feel?",
"Do you feel threatened by computers?"]],
["Is it (.*)",
[ "Do you think it is %1?",
"Perhaps it's %1 -- what do you think?",
"If it were %1, what would you do?",
"It could well be that %1."]],
["It is (.*)",
[ "You seem very certain.",
"If I told you that it probably isn't %1, what would you feel?"]],
["Can you (.*)",
[ "What makes you think I can't %1?",
"If I could %1, then what?",
"Why do you ask if I can %1?"]],
["Can I (.*)",
[ "Perhaps you don't want to %1.",
"Do you want to be able to %1?",
"If you could %1, would you?"]],
["You are (.*)",
[ "Why do you think I am %1?",
"Does it please you to think that I'm %1?",
"Perhaps you would like me to be %1.",
"Perhaps you're really talking about yourself?"]],
["You're (.*)",
[ "Why do you say I am %1?",
"Why do you think I am %1?",
"Are we talking about you, or me?"]],
["I don't (.*)",
[ "Don't you really %1?",
"Why don't you %1?",
"Do you want to %1?"]],
["I feel (.*)",
[ "Good, tell me more about these feelings.",
"Do you often feel %1?",
"When do you usually feel %1?",
"When you feel %1, what do you do?"]],
["I have (.*)",
[ "Why do you tell me that you've %1?",
"Have you really %1?",
"Now that you have %1, what will you do next?"]],
["I would (.*)",
[ "Could you explain why you would %1?",
"Why would you %1?",
"Who else knows that you would %1?"]],
["Is there (.*)",
[ "Do you think there is %1?",
"It's likely that there is %1.",
"Would you like there to be %1?"]],
["My (.*)",
[ "I see, your %1.",
"Why do you say that your %1?",
"When your %1, how do you feel?"]],
["You (.*)",
[ "We should be discussing you, not me.",
"Why do you say that about me?",
"Why do you care whether I %1?"]],
["Why (.*)",
[ "Why don't you tell me the reason why %1?",
"Why do you think %1?" ]],
["I want (.*)",
[ "What would it mean to you if you got %1?",
"Why do you want %1?",
"What would you do if you got %1?",
"If you got %1, then what would you do?"]],
["(.*) mother(.*)",
[ "Tell me more about your mother.",
"What was your relationship with your mother like?",
"How do you feel about your mother?",
"How does this relate to your feelings today?",
"Good family relations are important."]],
["(.*) father(.*)",
[ "Tell me more about your father.",
"How did your father make you feel?",
"How do you feel about your father?",
"Does your relationship with your father relate to your feelings today?",
"Do you have trouble showing affection with your family?"]],
["(.*) child(.*)",
[ "Did you have close friends as a child?",
"What is your favorite childhood memory?",
"Do you remember any dreams or nightmares from childhood?",
"Did the other children sometimes tease you?",
"How do you think your childhood experiences relate to your feelings today?"]],
["(.*)\?",
[ "Why do you ask that?",
"Please consider whether you can answer your own question.",
"Perhaps the answer lies within yourself?",
"Why don't you tell me?"]],
["quit",
[ "Thank you for talking with me.",
"Good-bye.",
"Thank you, that will be $150. Have a good day!"]],
["(.*)",
[ "Please tell me more.",
"Let's change focus a bit... Tell me about your family.",
"Can you elaborate on that?",
"Why do you say that %1?",
"I see.",
"Very interesting.",
"So %1.",
"I see. And what does that tell you?",
"How does that make you feel?",
"How do you feel when you say that?"]]
]]
def respond(sentence):
# find a match among keys, last one is quaranteed to match.
for rule, value in rules:
match = rule.search(sentence)
if match is not None:
# found a match ... stuff with corresponding value
# chosen randomly from among the available options
resp = random.choice(value)
# we've got a response... stuff in reflected text where indicated
while '%' in resp:
pos = resp.find('%')
num = int(resp[pos+1:pos+2])
resp = resp.replace(resp[pos:pos+2], translate(match.group(num)))
return resp
만약 shell에서 작동하는 interactive version을 만들고자 한다면, 아래 코드를 추가하면 된다.
if __name__ == '__main__':
print("""
Therapist
---------
Talk to the program by typing in plain English, using normal upper-
and lower-case letters and punctuation. Enter "quit" when done.'""")
print('='*72)
print("Hello. How are you feeling today?")
s = ""
while s != "quit":
s = input(">")
while s and s[-1] in "!.":
s = s[:-1]
print(respond(s))
다음은 새로운 translation 방법을 적용하여 개선된 Eliza의 response이다.
respond("My mother hates me.")
'I see, your mother hates you.'
# previous version response: 'you will do anything me ask'
respond('I will do anything you ask')
'So you will do anything I ask.'
respond("I'm ``possibly,'' maybe crazy.")
"you are ``possibly,'' maybe crazy"
respond('the dogs were crazy')
'the dogs were crazy'
# previous version response: 'the dog were crazy'
respond('the dog was crazy')
'the dog was crazy'
# previous version response: 'Why do you say that your dog were crazy?'
respond("My dog was crazy.")
'Why do you say that your dog was crazy?'
respond("I was crazy")
'Why do you say that you were crazy?'
# previous version response: 'Why do you care whether I said fred were crazy?'
respond("You said Fred was crazy.")
'Why do you care whether I said Fred was crazy?'
respond("I asked you.")
'So you asked me.'
'NLP' 카테고리의 다른 글
Large-Scale LM에 대한 얕고 넓은 지식들 (Part 2) (0) | 2021.09.06 |
---|---|
Fine-tuning a model on the KLUE-STS (1) | 2021.08.11 |
Fine-tuning a model on the YNAT (0) | 2021.08.07 |
ALIGN: Scaling Up Visual and Vision-Language Representation Learning With Noisy Text Supervision (0) | 2021.06.07 |
Eliza in Python - (1) (0) | 2021.04.03 |