분석하고싶은코코

NLP - BERT 실전 활용(2) 본문

머신러닝&딥러닝/NLP

NLP - BERT 실전 활용(2)

코코로코코 2023. 10. 3. 00:58
반응형

지난번 활용에 이어서 뒷내용이었던 기계독해(Q&A)와 간단 챗봇 구현 두 가지에 대해서 진행해보겠습니다.

 

1) 기계독해(Q&A)

 

1-0) 기계독해(Q&A)란?

기계독해에 대해서 먼저 어떻게 처리되는지 기본적인 흐름에 대해서 이해해보고 직접 구현하는 순서로 진행해보겠습니다. 기계독해는 수능에 나오는 문제랑 같다고 생각하시면 됩니다. 본문이 있고 질문이 있고 답이 있습니다. 당연히 답은 본문 안에 있겠죠? 이 일을 컴퓨터가 학습을 통해 스스로 한다고 생각하시면 됩니다.

 

이 기계독해는 ' TFAutoModelForQuestionAnswering' 모듈을 통해서 훈련된 모델을 부착 해주면 가능합니다. 그런데 모델만 설정해준다고 해서 절대 내가 원하는 본문, 질문을 넣는다고 제대로 된 답을 줄 수 없습니다. 왜냐하면 불러온 Pre-trained모델은 내가 원하는 목적에 훈련된 모델이 아니기 때문이죠. 그렇기 때문에 나의 목적에 맞는 파인튜닝과정이 필수적입니다. 여기서는 기본적인 한글 버트모델을 불러와서 KorQuad데이터를 사용해 파인튜닝을 진행한 후 모델에게 예측시켜보는 작업을 진행해보겠습니다.

 

추가적으로 알아두면 좋을 것이 ' TFAutoModelForQuestionAnswering' 모델 클래스는 최종적으로 두 가지 값을 반환합니다. 왜 두 가지냐하면 컴퓨터는 우리와 같지 않습니다. 예를들어 질문에 대한 정답이 '자연어처리'였는데 우리는 이 단어에 대해서 하나로 이해하지만 컴퓨터는 그렇게 이해하지 못합니다. 그래서 기계독해의 모델에서는 정답이라고 생각되는 단어의 시작점과 끝점 두 가지의 값에 대한 예측을 진행하고 두 가지 값의 위치를 결과로 보내줍니다. 이를 우리가 토크나이저를 통해서 우리가 바로 확인할 수 있는 형태로 변형해주는 과정입니다.

 

 

1-1) 기계독해 구현

우선 파인튜닝을 위한 KorQuda데이터를 가져옵니다. 데이터가 어떤 형태로 되어있는지는 직접 확인하는 작업을 진행해주시면 됩니다. 하나의 json파일로 되어 있기에 이를 본문, 질문, 답으로 분류하는 함수입니다.
import os
import json
import numpy as np
from tqdm import tqdm
from pathlib import Path
from transformers import BertTokenizerFast
import tensorflow as tf

!wget https://korquad.github.io/dataset/KorQuAD_v1.0_train.json -O KorQuAD_v1.0_train.json
!wget https://korquad.github.io/dataset/KorQuAD_v1.0_dev.json -O KorQuAD_v1.0_dev.json


# 한국어 질의응답 데이터 KorQuad 사용 - 데이터 가져오기
def read_squad(path):
  path = Path(path)
  with open(path, 'rb') as f:
      squad_dict = json.load(f)
  contexts = []
  questions = []
  answers = []
  for group in squad_dict['data']:
    for passage in group['paragraphs']: 
      context = passage['context'] 
      for qa in passage['qas']:
        question = qa['question'] 
        for answer in qa['answers']: 
          contexts.append(context)
          questions.append(question)
          answers.append(answer)
  return contexts, questions, answers

train_contexts, train_questions, train_answers = read_squad('KorQuAD_v1.0_train.json')
val_contexts, val_questions, val_answers = read_squad('KorQuAD_v1.0_dev.json')

train_contexts[0] train_questions[0], train_answers[0]
# context
1839년 바그너는 괴테의 파우스트을 처음 읽고 그 내용에 마음이 끌려 이를 소재로 해서 하나의 교향곡을 쓰려는 뜻을 갖는다. 이 시기 바그너는 1838년에 빛 독촉으로 산전수전을 다 걲은 상황이라 좌절과 실망에 가득했으며 메피스토펠레스를 만나는 파우스트의 심경에 공감했다고 한다. 또한 파리에서 아브네크의 지휘로 파리 음악원 관현악단이 연주하는 베토벤의 교향곡 9번을 듣고 깊은 감명을 받았는데, 이것이 이듬해 1월에 파우스트의 서곡으로 쓰여진 이 작품에 조금이라도 영향을 끼쳤으리라는 것은 의심할 여지가 없다. 여기의 라단조 조성의 경우에도 그의 전기에 적혀 있는 것처럼 단순한 정신적 피로나 실의가 반영된 것이 아니라 베토벤의 합창교향곡 조성의 영향을 받은 것을 볼 수 있다. 그렇게 교향곡 작곡을 1839년부터 40년에 걸쳐 파리에서 착수했으나 1악장을 쓴 뒤에 중단했다. 또한 작품의 완성과 동시에 그는 이 서곡(1악장)을 파리 음악원의 연주회에서 연주할 파트보까지 준비하였으나, 실제로는 이루어지지는 않았다. 결국 초연은 4년 반이 지난 후에 드레스덴에서 연주되었고 재연도 이루어졌지만, 이후에 그대로 방치되고 말았다. 그 사이에 그는 리엔치와 방황하는 네덜란드인을 완성하고 탄호이저에도 착수하는 등 분주한 시간을 보냈는데, 그런 바쁜 생활이 이 곡을 잊게 한 것이 아닌가 하는 의견도 있다.

# Q
바그너는 괴테의 파우스트를 읽고 무엇을 쓰고자 했는가?

# A
{'text': '교향곡', 'answer_start': 54}

 

정답에 대한 데이터를 확인해보니 시작점에 대한 인덱스 정보는있는데 끝점에 대한 인덱스 정보가 없으므로 추가해줍시다.

def add_end_idx(answers, contexts):
  for answer, context in zip(answers, contexts):

    answer['text'] = answer['text'].rstrip()

    gold_text = answer['text']
    start_idx = answer['answer_start']
    end_idx = start_idx + len(gold_text)

    assert context[start_idx:end_idx] == gold_text, "end_index 계산에 에러가 있습니다."
    answer['answer_end'] = end_idx
    
add_end_idx(train_answers, train_contexts)
add_end_idx(val_answers, val_contexts)

 

이렇게 추가적으로 훈련 시켜줄 데이터를 분류해두었으니 토크나이저를 불러와서 입력에 맞는 변환을 진행합시다!! 이전에 하던 토크나이징 처럼 하면 좋겠지만 앞서 본문에 대한 내용을 확인해보면 알겠지만 생각보다 텍스트가 깁니다. 그런데 우리가 사용할 한국어BERT 모델인 'klue/bert-base/'의 토크나이저는 최대 길이가 512로 정해져있습니다. 그래서 이를 통해서 토크나이징을 진행하게 되면 당연히 본문의 내용이 사라지는 상황이 발생할 것입니다. 그렇게 토크나이징을 하게 되면 본문의 정수 인코딩에는 정답에 대한 토큰이 사라지는 데이터가 발생하게 됩니다. 이를 그냥 방치하고 모델에게 훈련 시키게 된다면 모델은 본문에 있지도 않은 정답을 예측해야하는 상황에 놓여저버리는 것이죠. 그래서 토크나이징의 결과에서 정답에 대한 포지션을 본문에서 찾았는데 없는 문항들은 삭제하고 훈련을 시켜주는것이죠. 그러기 위해서 토크나이징하면서 결과를 확인하고 삭제할 문항에 대한 인덱스를 찾아줍니다. 그 이후에 찾은 인덱스 번호를 통해서 삭제리스트에 있는 인덱스 문항은 제외하고 훈련, 테스트 데이터 셋을 만들어주는 것이 아래의 과정입니다.

 

토크나이저를 통해 인코딩하는 과정은 본문 + 질문을 묶어서 훈련값으로 입력합니다. 그렇다면 인코딩은 

[CLS] 'context 토큰' [SEP] 'questions 토큰' .... [SEP]  [PAD] ....

이렇게 될것입니다. 물론 이 형태는512를 넘지 않는 데이터일 경우이겠죠?

tokenizer = BertTokenizerFast.from_pretrained('klue/bert-base')
train_encodings = tokenizer(train_contexts, train_questions, truncation=True,padding=True)
val_encodings = tokenizer(val_contexts, val_questions, truncation=True, padding=True)

def add_token_positions(encodings, answers):
  start_positions = []
  end_positions = []
  deleting_list = []
  for i in tqdm(range(len(answers))):
  
    start_positions.append(encodings.char_to_token(i, answers[i]['answer_start']))
    end_positions.append(encodings.char_to_token(i, answers[i]['answer_end'] - 1))

    if start_positions[-1] is None :
      start_positions[-1] = tokenizer.model_max_length
      deleting_list.append(i)
  
    if end_positions[-1] is None :
      end_positions[-1] = tokenizer.model_max_length 
      if i not in deleting_list:
        deleting_list.append(i)
      
    encodings.update({'start_positions': start_positions, 'end_positions':end_positions})

  return deleting_list
  
  
deleting_list_for_train = add_token_positions(train_encodings, train_answers)
deleting_list_for_test = add_token_positions(val_encodings, val_answers)
 
 
def delete_samples(encodings, deleting_list):
  input_ids = np.delete(np.array(encodings['input_ids']), deleting_list, axis=0)
  attention_masks = np.delete(np.array(encodings['attention_mask']), deleting_list, axis=0)
  start_positions = np.delete(np.array(encodings['start_positions']), deleting_list, axis=0)
  end_positions = np.delete(np.array(encodings['end_positions']), deleting_list, axis=0)

  X_data = [input_ids, attention_masks]
  y_data = [start_positions, end_positions]

  return X_data, y_data
  
X_train, y_train = delete_samples(train_encodings, deleting_list_for_train)
X_test, y_test = delete_samples(val_encodings, deleting_list_for_test)

 

이제 훈련과 검증을 위한 데이터의 형태까지 잘 만들었으니 모델 클래스를 불러와서 사용하면 되겠습니다. 우선 TPU 상황에서 훈련을 진행시켜줄 것이기 때문에 작동을 위한 코드를 작성하였습니다. 그리고 우리는 2개의 결과값을 받기 때문에 마지막 층에서 2개의 값을 받습니다. 두 개의 값을 반환해주는데 하나만 생각해보면 512개의 확률값이 있고 여기서 우리는 가장 높은 인덱스 정보만 사용할 것입니다. 이는 생각해보면 다중클래스 분류 문제와 같습니다. 그래서 손실함수는 크로스엔트로피를 사용할 건데 이번에는 활성화함수를 지정해주지 않았기 때문에 모델 컴파일시 ' SparseCategoricalCrossentropy' 손실함수를 사용합니다. 그 이외에는 별도로 특별한 작업은 없습니다.

#TPU작동을 위한 코드
resolver = tf.distribute.cluster_resolver.TPUClusterResolver(tpu='grpc://' + os.environ['COLAB_TPU_ADDR'])
tf.config.experimental_connect_to_cluster(resolver)
tf.tpu.experimental.initialize_tpu_system(resolver)
strategy = tf.distribute.experimental.TPUStrategy(resolver)

from transformers import TFBertModel

class TFBertForQuestionAnswering(tf.keras.Model):
  def __init__(self, model_name):
    super(TFBertForQuestionAnswering, self).__init__()
    self.bert = TFBertModel.from_pretrained(model_name, from_pt=True)
    self.qa_outputs = tf.keras.layers.Dense(2, kernel_initializer=tf.keras.initializers.TruncatedNormal(0.02), name='qa_outputs')
    self.softmax = tf.keras.layers.Activation(tf.keras.activations.softmax)

  def call(self, inputs):
    input_ids, attention_mask = inputs
    outputs = self.bert(input_ids, attention_mask=attention_mask)

    sequence_output = outputs[0]
  
    logits = self.qa_outputs(sequence_output)
  
    start_logits, end_logits = tf.split(logits, 2, axis=-1)
  
    start_logits = tf.squeeze(start_logits, axis=-1)
    end_logits = tf.squeeze(end_logits, axis=-1)

    start_probs = self.softmax(start_logits)
    end_probs = self.softmax(end_logits)
  
    return start_probs, end_probs
    
with strategy.scope():
  model = TFBertForQuestionAnswering("klue/bert-base")
  optimizer = tf.keras.optimizers.Adam(learning_rate=5e-5)
  loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False)
  model.compile(optimizer=optimizer, loss=[loss, loss])
  
history = model.fit(X_train, y_train, epochs=3, verbose=1, batch_size=16)

def predict_test_data_by_idx(idx):
  context = tokenizer.decode(X_test[0][idx]).split('[SEP] ')[0]
  question = tokenizer.decode(X_test[0][idx]).split('[SEP] ')[1]
  print(' 본 문 :', context)
  print(' 질 문 :', question)
  answer_encoded = X_test[0][idx][y_test[0][idx]:y_test[1][idx]+1]
  print(' 정 답 :',tokenizer.decode(answer_encoded))

  output = model([tf.constant(X_test[0][idx])[None, :], tf.constant(X_test[1][idx])[None, :]])

  start = tf.math.argmax(tf.squeeze(output[0]))
  end = tf.math.argmax(tf.squeeze(output[1]))+1
  answer_encoded = X_test[0][idx][start:end]
  print('예 측 :',tokenizer.decode(answer_encoded))
  print('----------------------------------------')
  
  
for i in range(0, 100, 10):
  predict_test_data_by_idx(i)
본 문 : [CLS] 1989년 2월 15일 여의도 농민 폭력 시위를 주도한 혐의 ( 폭력행위등처벌에관한법률위반 ) 으로 지명수배되었다. 1989년 3월 12일 서울지방검찰청 공안부는 임종석의 사전구속영장을 발부받았다. 같은 해 6월 30일 평양축전에 임수경을 대표로 파견하여 국가보안법위반 혐의가 추가되었다. 경찰은 12월 18일 ~ 20일 사이 서울 경희대학교에서 임종석이 성명 발표를 추진하고 있다는 첩보를 입수했고, 12월 18일 오전 7시 40분 경 가스총과 전자봉으로 무장한 특공조 및 대공과 직원 12명 등 22명의 사복 경찰을 승용차 8대에 나누어 경희대학교에 투입했다. 1989년 12월 18일 오전 8시 15분 경 서울청량리경찰서는 호위 학생 5명과 함께 경희대학교 학생회관 건물 계단을 내려오는 임종석을 발견, 검거해 구속을 집행했다. 임종석은 청량리경찰서에서 약 1시간 동안 조사를 받은 뒤 오전 9시 50분 경 서울 장안동의 서울지방경찰청 공안분실로 인계되었다. 
 질 문 : 임종석이 여의도 농민 폭력 시위를 주도한 혐의로 지명수배 된 날은? 
 정 답 : 1989년 2월 15일
예 측 : 1989년 2월 15일
----------------------------------------
 본 문 : [CLS] " 내각과 장관들이 소외되고 대통령비서실의 권한이 너무 크다 ", " 행보가 비서 본연의 역할을 벗어난다 " 는 의견이 제기되었다. 대표적인 예가 10차 개헌안 발표이다. 원로 헌법학자인 허영 경희대 석좌교수는 정부의 헌법개정안 준비 과정에 대해 " 청와대 비서실이 아닌 국무회의 중심으로 이뤄졌어야 했다 " 고 지적했다.'국무회의의 심의를 거쳐야 한다'( 제89조 ) 는 헌법 규정에 충실하지 않았다는 것이다. 그러면서 " 법무부 장관을 제쳐놓고 민정수석이 개정안을 설명하는 게 이해가 안 된다 " 고 지적했다. 민정수석은 국회의원에 대해 책임지는 법무부 장관도 아니고, 국민에 대해 책임지는 사람도 아니기 때문에 정당성이 없고, 단지 대통령의 신임이 있을 뿐이라는 것이다. 또한 국무총리 선출 방식에 대한 기자의 질문에 " 문 대통령도 취임 전에 국무총리에게 실질적 권한을 주겠다고 했지만 그러지 못하고 있다. 대통령비서실장만도 못한 권한을 행사하고 있다. " 고 답변했다. 
 질 문 : 법무부 장관을 제쳐놓고 민정수석이 개정안을 설명하는 게 이해가 안 된다고 지적한 경희대 석좌교수 이름은? 
 정 답 : 허영
예 측 : 허영
----------------------------------------
 본 문 : [CLS] 노터데임 대학교에서 2년간 합리적으로 심각한 공부를 한 후 헤이그는 1944년 미국 육군사관학교로 임명을 획득하여 자신의 어린 시절을 군사 경력의 야망으로 알아챘다. 그 경력은 헤이그의 학문적 경연이 암시하려고 한것보다 더욱 극적이었으며 그는 1947년 310의 동기병에서 217번째 사관으로서 졸업하였다. 22세의 소위로 헤이그는 처음에 캔자스 주 포트라일리에서 정통 제병 연합부대로, 그러고나서 켄터키 주 포트녹스에 있는 기갑 훈련소로 갔다. 그후에 그는 제1 기병 사단으로 선임되고 그러고나서 일본에서 점령군의 임무와 기력이 없는 훈련을 하였다. 그는 1950년 5월 한번 자신의 사령관 알론조 폭스 장군의 딸 퍼트리샤 앤토이넷 폭스와 결혼하여 슬하 3명의 자식을 두었다. 
 질 문 : 알렉산더 헤이그가 미국 육군사관학교로 임명받은 해는 언제인가? 
 정 답 : 1944년
예 측 : 1944년
----------------------------------------
 본 문 : [CLS] 헤이그는 닉슨 대통령이 그를 사성 장군과 육군 부참모로 진급시킬 때 집중 광선과 논쟁으로 들어갔다. 헤이그를 군사의 최상으로 밀어넣은 닉슨의 행동은 대통령의 남자들을 다양한 연방 대리법에서 권한의 직우들로 놓은 노력과 함께 일치였다. 하지만 그는 곧 백악관으로 돌아가 1973년부터 1974년까지 대통령 특별 보좌관을 지냈다. 워터게이트 사건이 일어난지 한달 후, 헤이그는 포위된 닉슨 대통령을 위한 치명적 역할을 하였다. 그일은 8월 닉슨의 사임과 제럴드 포드의 대통령으로 계승으로 이끈 협상들에서 헤이그가 수단이었던 우연이 아니었다. 곧 후에 헤이그는 미국 유럽 연합군 최고사령부의 최고 사령관으로 임명되었다. 그는 나토에서 다음 5년을 보내고 1979년 군에서 퇴역하여 미국 기술 주식 회사의 우두머리가 되었다. 
 질 문 : 헤이그가 군에서 퇴역한 년도는 몇년도입니까? 
 정 답 : 1979년
예 측 : 1979년
----------------------------------------
 본 문 : [CLS] 노아는 하나님의 명령에 따라 배를 만들고 가족과 정결한 짐승 암수 일곱 마리씩, 부정한 짐승 암수 한 마리씩 ( 혹은 두 마리씩 ; 사본에 따라 다름 ), 그리고 새 암수 일곱 마리씩을 싣고 밀어닥친 홍수를 피하였다. 모든 사람들이 타락한 생활에 빠져 있어 하나님이 홍수로 심판하려 할 때 홀로 바르게 살던 노아는 하나님의 특별한 계시로 홍수가 올 것을 미리 알게 된다. 그는 길이 300 규빗, 너비 50 규빗, 높이 30 규빗 ( 고대의 1규빗은 팔꿈치에서 가운데 손가락끝까지의 길이로 약 45 ~ 46cm를 가리킴 ), 상 · 중 · 하 3층으로 된 방주를 만들어 8명의 가족과, 한 쌍씩의 여러 동물을 데리고 이 방주에 탄다. 대홍수를 만나 모든 생물 ( 물고기 제외 ) 이 전멸하고 말았지만, 이 방주에 탔던 노아의 가족과 동물들은 살아 남았다고 한다. 〈 창세기 〉 6장 14 ~ 16절에 보면 길이 300규빗 ( 약 135m ), 폭 50 규빗 ( 약 22. 5m ), 높이 30 규빗 ( 약 13. 5m ) 인 이 배는 지붕과 문을 달고 배 안은 3층으로 만들어져 있었다. 선체 ( [UNK] 體 ) 는 고페르나무 ( 잣나무 ) 로 되고 안쪽에는 역청 ( 아스팔트와 비슷한 성분 ) 을 칠하여 굳혔다고 기록하고 있다. 
 질 문 : 하나님의 명령에 배를 만들고 가족과 짐승들을 배에 태워 홍수를 피한 사람은 누구인가? 
 정 답 : 노아
예 측 : 노아
----------------------------------------
 본 문 : [CLS] 역사학과 과학이 발달하지 않았던 과거 전통 신학계에서는 근본주의적 시각을 받아들여 노아의 방주를 역사적 사실로 기술하려 했으며, 이러한 관점은 아직도 과학과 역사학에 어두운 보수적 근본주의계열의 개신교에서만 받아들여지고 있다. 하지만 역사학과 과학의 발달로 인해, 노아의 방주의 실존에 대한 의문이 제기가 되고, 세계적 홍수가 존재할 수 없음이 밝혀짐에 따라 현대 신학계에서는 비록 노아의 홍수가 과학적으로 실존하지는 않았지만 그 자체의 의미는 신학적으로 매우 중요하며, 이에 대한 해석은 다양하게 이루어지고 있으며, 대부분의 기독교 ( 가톨릭, 개신교를 포함한 대부분 ) 에서는 노아의 방주는 상징적 의미로 받아들여진다. 그러므로 과학과는 상관없이 신학적으로 노아의 방주 자체의 의미는 중요하게 해석된다고 한다 
 질 문 : 노아의 방주의 실존에 대한 의문이 제기되고 세계적 홍수가 없었다는 것이 밝혀지게된 이유는? 
 정 답 : 역사학과 과학의 발달
예 측 : 역사학과 과학의 발달
----------------------------------------
 본 문 : [CLS] 역사학과 과학의 발달이 [UNK] 고대사회에서는, 성경이 단순한 교리적인 부분 뿐 아니라 역사책으로서의 권위도 높았기에 노아의 방주를 역사적인 존재로서 다루고 있었다. 이는 제칠일안식교에서 비롯된 의사과학의 한 종류인 유사지질학인 홍수지질학과 같은 것에 영향을 주었으며, 과거 신학에서는 이러한 근본주의적 해석을 받아들여 역사와 사회적인 모든 부분에 있어 성경을 교과서로 채택할 것을 촉구했다. 이러한 홍수지질학을 주장했던 유사지질학자들은 성경에 나오는 노아의 홍수가 어딘가에 그 흔적이 남아 있을것이라고 주장하며 노아의 방주를 찾기 위한 노력을 했다고 주장한다. 이들은 같은 메소포타미아 지방의 신화인 이슬람교 경전이나 길가메쉬 서사시등의 신화를 들어서 이를 근거라고 주장하기도 했다. 그러나 이러한 전통적 근본주의적 시각은 과거에는 상당히 힘을 얻었으나, 역사학과 과학의 발달에 따라 힘을 잃게 되었고, 홍수지질학은 유사과학으로서 남게 되었다. 현대에는 뒤의 실존논란에서 다루는 것처럼 이러한 근본주의적 해석은 비과학적인 해석으로 여기는 것이 일반적이지만, 남침례교로 대표되는 극보수주의계열 기독교에서는 아직도 이것이 받아들여지고 있다. 
 질 문 : 현대에 노아의 방주에 대학 근본주의적 해석은 어떻게 여겨지는가? 
 정 답 : 비과학적인 해석
예 측 : 비과학적인 해석
----------------------------------------
 본 문 : [CLS] 일반적으로 터키의 아라랏 산의 경우, 실제 성경 속에 등장하는 아라랏 산은 지금 아라랏이라 불리는 하나의 산이 아니라 당시 아라랏이라고 불리던 광대한 지역의 산들을 모두 가리키는 표현이라는 주장도 나와 있으며, 또한 목재로 만들어진 방주가 현재까지 남아있을 수는 없다는 비판도 받고 있다. 예를 들어, 1955년 프랑스의 탐험가인 Fernand Navarra가 발견한 목재 파편의 경우, 스페인의 임업 연구소에서 목재의 특성을 토대로 5000년 전의 것이라고 밝히긴 했으나 그 신빙성에 문제점이 있었고 후에 방사성 동위원소 측정법 등의 첨단 과학의 도움을 받은 5개 연구소에서 모두 기원 이후의 시기로 연대를 측정했다. 2009년 뿐 아니라 거의 수년에 한번씩 어디선가 노아의 방주를 발견했다는 주장들이 제시되었지만, 심지어 같은 창조과학을 주장하는 사람들에게조차 비판받을 정도였다. 노아의 방주가 다른 여러 지방에서 발견되었다는 주장이 있으나 너무나 다양한 지방 ( 중국, 터키, 인도 등 ) 에 걸쳐있고, 그 주장도 각각 제각각이므로 신빙성이 없다. 예를 들자면, 중국 BTV에서는 2012년에 중국에서 노아의 방주가 발견되었다는 보도를 하였는데, 이것은 창조과학회에서 주장하는 장소와는 전혀 다른곳이기도 하며, 화석화가 진행되지 않은 나무의 존재등으로 가짜임이 밝혀졌다. 때때로 일부 " 학자 " 라 칭하는 사람들이 이를 찾기 위해 노력한다고 주장하지만, 이는 학계에서 유사지질학으로 평가되고 있다. 
 질 문 : 2012년 중국 BTV에서 노아의 방주가 발견되었다고 보도한 나라는? 
 정 답 : 중국
예 측 : 인도
----------------------------------------
 본 문 : [CLS] 창조과학회에서는 또한 노아의 방주가 안정적인 구조였다고 주장하지만, 이와는 달리 노아의 방주는 항해가 불가능한 설계에 가깝다. 실제로 창조과학에서 주장하는 방주의 크기와 철제 부품을 사용하지 않은 목재 선박 중에서 가장 큰 수준의 선박들을 비교하면 배수량이 두배 이상 차이난다. 그리고 목재 선박은 강도 상의 문제 때문에 통상 길이 100m, 배수량 2000톤 정도가 한계로 여겨져 왔다. 창조과학회에서는 노아의 방주의 안정성을 실험하기 위한 연구가 있다고 주장하기도 하나, 그 자체의 불합리성에 대한 비판을 받고 있으며, 관련 주요 연구자는 지질학 석사학위, 생물학 학사학위를 가진 초등학교 교사로서, 주류 학계의 학회나 저널 등에 발표한 적이 없으며 또한 정당한 피어 리뷰에 의해 검증받지 않았다. 
 질 문 : 노아의 방주 안정성을 실험하기 위한 연구가 있다고 주장하는 단체는? 
 정 답 : 창조과학회
예 측 : 창조과학회
----------------------------------------
 본 문 : [CLS] 1868년 게이오 4년 4월 11일 에도 성 무혈 개성을 한 이후 신정부 군에게 양도가 약속되어 있었다. 그러나 해군 부총재, 에노모토 다케아키가 기상 불량 등을 이유로 이를 연기한 후에 결국 인도를 거부했다. 도쿠가와 요시노부를 슨푸 번에 이송할 때의 태운 함선으로 사용한 후, 8월 19일 자정 ( 20일 ) 에는 마쓰오카 바키치를 함장으로 카이요마루, 가이텐마루, 신소쿠마루, 간린마루 등과 함께 막부 해군이 정박하고 있던 시나가와 해역을 탈출했다. 그 때 태풍에 휘말려 침몰직전이 되었지만, 1개월만에 에노모토 해군과 합류하였다. 에조치에 건너가 하코다테 전쟁에서는 에노모토 ( 하코다테 정부 ) 해군의 주력함이 되었다. 영국이 기증했을 때 엠퍼러 ( Emperor, 기증 당시 일본의 수장은 황제가 아니라 쇼군으로 인식되고 있었기 때문에 장군을 지칭 ) 로 명명하고 있음에서 알 수 있듯이, 쇼군용 유람 요트로 기증되었다고 생각되지만, 세상이 그것을 허락하지 않았다. 아이러니하게도, 군함에 통합되어 실제로 쇼군이 첫 좌승한 것이 대정봉환 이후 슨푸 번에 이송되었을 때였다. 
 질 문 : 에노모토 해군인 반류마루가 주력함이 되었던 전쟁은? 
 정 답 : 하코다테 전쟁
예 측 : 하코다테 전쟁
----------------------------------------

 

 

2) 간단 챗봇 구현

 

2-0) 챗봇(Chat-bot)의 종류

챗봇을 구현하는 방법에는 대표적으로 두 가지 방법이 있습니다.

  • 생성 대화 모델
  • DB 기반 모델

생성 대화 모델은 seq2seq 모델을 생각하시면 됩니다. 질문에 대한 텍스트가 들어오고 기계는 이를 학습한 내용을 바탕으로 답변을 생성하는 것입니다. 그런데 이 모델은 'Maximum Likelihood'로 학습이 되기 때문에 평이한 답변들이 자주 나오게 됩니다. 그래서 이를 해결하기 위한 다양한 시도들이 있었고 지금은 완벽하지 않지만 어느정도 흐름이 이어지는 느낌은 나는 답변을 해주는 딥러닝 모델들이 나오고 있죠. 대표적인게 GPT-3입니다. GPT-3가 큰 이슈를 거치면서 많은 사람들이 사용했습니다. 그런데 이 GPT는 애초에 대화를 위해 만들어진 모델이 아니기 때문에 자연스러운 대화가 된다는 느낌이 들지 않습니다. 대표적으로 끝말잇기를 해보면 마음대로 답변하는 경우가 굉장히 많습니다. 이러한 대화형 봇에 가장 좋은 모델은 구글의 미나(Meena)라고 합니다.(논문 : https://arxiv.org/abs/2001.09977) 논문에서는 대화의 품질을 측정하기 위한 점수로 SSA라는 수치를 소개하는데 이는 봇이 만들어낸 답변이 얼마나 말이되는지에 대해서 사람이 채점한 방식인데 실제 사람의 답변은 86점 미나는 79점을 받을 정도였다고 합니다.

 

DB 기반 모델은 주위에서 쉽게 접할 수 있는 '이루다'라는 챗봇입니다. 이루다와 관련된 기사를 보셨던 분이라면 DB 기반 모델이라는 것에 대해서 바로 이해가 가능하실 것 같습니다. 이루다는 거대한 카톡 대화 데이터를 활용해 DB를 구축하여 답변 해주는 챗봇입니다. 대용량 DB에서 한 번에 고르는게 아니라 질문이 들어오면 DB에서 그럴듯한 답변을 추출하고 이후에 보다 정확한 답변이 될 수 있는 답변을 선정하여 답변하는 챗봇입니다. 그래서 기계랑 이야기하는 것보다 사람과 이야기하는 느낌이 많이납니다. 그럴 수 밖에 없는게 실제로 사람이 답변한 텍스트로 답변을 해주고 있는 것이기 때문입니다. 이런 챗봇을 구현하기 위해서는 엄청난 대화 텍스트 데이터가 필요하고 또, 부적절한 답변을 주지 않게 하기 위한 데이터의 필터링 작업까지 해줘야하는 수고로움이 존재한다는 것이 단점입니다. (이러한 단점 중 하나인 필터링이 제대로 이뤄지지 않아서 주소, 계좌번호와 같은 개인정보를 노출한 기사가 났던 것입니다.)

 

 

2-1) 챗봇 구현

저는 생성 대화 모델에 대한 작업은 진행하지 않고 DB 기반 모델의 챗봇을 구현해보도록 하겠습니다.

setence-transformer를 통해서 간단하게 구현해보겠습니다. 불러온 모델은 다국어 Bert base 모델로 SNLI 데이터로 학습후 STS-B 데이터로 학습하고 문장 표현을 위한 평균 풀링을 사용한 모델입니다.

 

이 모델을 통해서 인코딩을 진행하고 이 데이터를 바탕으로 질문과 DB에 있는 답변 리스트간 코사인 유사도를 계산하여 가장 높은 답변을 출력해주는 방식입니다. 11,823 이번에 사용한 데이터의 수로 적은 데이터로 챗봇을 구현했기 때문에 살짝 이상한 답변이 올때도 있고 뚱딴지 같은 답변을 하는 것을 결과로 확인했습니다.

 

앞서 이야기했듯 DB에 얼마나 많은 데이터가 있고 그 중에서 최고의 답변을 어떻게 선정하는지에 따라 챗봇의 성능이 사용자로 하여금 실제로 대화하고 있는지 결정할 수 있습니다.

import numpy as np
import pandas as pd
from numpy import dot
from numpy.linalg import norm
import urllib.request
from sentence_transformers import SentenceTransformer

urllib.request.urlretrieve("https://raw.githubusercontent.com/songys/Chatbot_data/master/ChatbotData.csv", filename="ChatBotData.csv")
train_data = pd.read_csv('ChatBotData.csv')
train_data.head()

model = SentenceTransformer('sentence-transformers/xlm-r-100langs-bert-base-nli-stsb-mean-tokens')

train_data['embedding'] = train_data.apply(lambda row: model.encode(row.Q),axis = 1)

def cos_sim(A, B):
   return dot(A, B)/(norm(A)*norm(B))
   
def return_answer(question):
  embedding = model.encode(question)
  train_data['score'] = train_data.apply(lambda x: cos_sim(x['embedding'],embedding), axis=1)
  return train_data.loc[train_data['score'].idxmax()]['A']
 
 
 print(return_answer('오늘 날씨가 좋아'))
 print(return_answer('나 기분 별로야'))
좋은 사람이 찾아오려나봐요.
스스로 좋다고 못 느끼는게 제일 어려운 것 같아요.

 

반응형