간단한 답변 랭킹 모델 만들기
Introduction
Chapter 5. 문장 임베딩 만들기 강의의 간단한 답변 랭킹 모델 만들기 실습 강의입니다.
Transformers에 공유된 KoBERT-Transformers를 활용하여, 문장 수준 임베딩 간 유사도 계산을 활용하여 간단한 답변 랭킹 모델 (챗봇)을 구현합니다.
1. 한국어 일상 대화 데이터셋 수집 및 전처리
오늘 실습에서는 일상 대화 챗봇 구현에 많이 사용되는 Chatbot_data를 사용하겠습니다.
로드한 데이터셋은 이후 답변 랭킹 모델 추론에 사용됩니다.
출처: https://github.com/songys/Chatbot_data
import urllib.request
import pandas as pd
#다음의 링크에서 챗봇 데이터를 로드할 수 있습니다.
urllib.request.urlretrieve("https://raw.githubusercontent.com/songys/Chatbot_data/master/ChatbotData.csv", filename="ChatBotData.csv")
train_dataset = pd.read_csv('ChatBotData.csv')
print(f"전체 데이터셋 개수: {len(train_dataset)}") # 11823
train_dataset
전처리가 이미 되어있는 데이터 형식이다.
2. 데이터 전처리
2.1. 데이터셋 내 결측값 확인
train_dataset.replace("", float("NaN"), inplace=True)
print(train_dataset.isnull().values.any()) # False
2.2. 데이터셋 내 중복 제거
# Question: 열을 기준으로 중복제거
train_dataset = train_dataset.drop_duplicates(['Q']).reset_index(drop=True)
print(f"필터링된 데이터셋 총 개수 : {len(train_dataset)}") # 11662
train_dataset
# Answer: 열을 기준으로 중복제거
train_dataset = train_dataset.drop_duplicates(['A']).reset_index(drop=True)
print(f"필터링된 데이터셋 총 개수 : {len(train_dataset)}") # 7731
train_dataset
2.3. 데이터 분포 확인
import matplotlib.pyplot as plt
question_list = list(train_dataset['Q'])
answer_list = list(train_dataset['A'])
print('질문의 최대 길이 :',max(len(question) for question in question_list))# 56
print('질문의 평균 길이 :',sum(map(len, question_list))/len(question_list)) # 13.673263486721
plt.hist([len(question) for question in question_list], bins=50)
plt.xlabel('length of samples')
plt.ylabel('number of samples')
plt.show()
분포에서 크게 문제가 없는 형태이다.
print('답변의 최대 길이 :',max(len(answer) for answer in answer_list)) #76
print('답변의 평균 길이 :',sum(map(len, answer_list))/len(answer_list))#15.611563833915406
plt.hist([len(answer) for answer in answer_list], bins=50)
plt.xlabel('length of samples')
plt.ylabel('number of samples')
plt.show()
질문과 답변을 나눠서 쓰기 때문에 따로 길이에 따른 정규화를 진행하지 않았다.
2.3. 답변 후보 목록 구성
import random
print(f"question 개수: {len(question_list)}") # 7731
print(f"answer 개수: {len(answer_list)}") # 7731
response_candidates = random.sample(answer_list, 100)
response_candidates[:10]
['달콤한 커피 한 잔 마셔보세요.',
'조금만 더 기다려주세요.',
'조급하게 생각하지 말아요.',
'짝사랑인지 생각해보세요.',
'늦지 않았어요.',
'저는 자장면이요.',
'페북 염탐하지 마요.',
'좀 더 알아보고 하세요.',
'만들어서 먹는 기쁨이죠.',
'자신의 건강만큼 중요한건 업습니다.']
답변후보를 적게하여 답변을 빠르게 주게 만들었다.
현업에서는 리트리버와 랭커를 활용하여 이렇게 줄이지 않아도 빠르게 할 수 있다.
3. KoBERT-Transformers 불러오기
오늘 실습에는 Transformers의 multi-lingual BERT(다양한 언어를 학습)를 사용하는 대신,
KoBERT-Transformers 모델을 사용합니다!
KoBERT 모델은 SKTBrain에서 공개한 한국어 데이터로 사전학습한 BERT 모델로, google의 multi-lingual BERT 성능의 한계를 극복하기 위해 공개되었습니다.
KoBERT-Transformers 모델은 KoBERT를 Huggingface.co 기반으로 사용할 수 있게 Wrapping 작업을 진행한 모델입니다.
KoBERT : https://github.com/SKTBrain/KoBERT
KoBERT-Transformers: https://github.com/monologg/KoBERT-Transformers
!pip3 install kobert-transformers
import torch
from kobert_transformers import get_kobert_model, get_distilkobert_model
model = get_kobert_model()# 한국어로 사전 학습된 모델을 바로 받을 수 있다.
model.eval() # 학습 안하고 평가하겠다.
input_ids = torch.LongTensor([[31, 51, 99], [15, 5, 0]])
attention_mask = torch.LongTensor([[1, 1, 1], [1, 1, 0]])
token_type_ids = torch.LongTensor([[0, 0, 1], [0, 1, 0]])
output = model(input_ids, attention_mask, token_type_ids)
output
output[0]
print(f"sequence_output is {sequence_output}") # last_hidden_state
print(f"pooled_output is {pooled_output}") # pooler_output
from kobert_transformers import get_tokenizer
tokenizer = get_tokenizer()
# 문장을 토큰화(분절) 한다.
tokenizer.tokenize("[CLS] 한국어 모델을 공유합니다. [SEP]") #['[CLS]', '▁한국', '어', '▁모델', '을', '▁공유', '합니다', '.', '[SEP]']
# 내가 입력들의 어떤 인덱스로 들어가는지 알 수 있다.
tokenizer.convert_tokens_to_ids(['[CLS]', '▁한국', '어', '▁모델', '을', '▁공유', '합니다', '.', '[SEP]']) #[2, 4958, 6855, 2046, 7088, 1050, 7843, 54, 3]
4. KoBERT 기반 답변 랭킹 모델 구현
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
유사도가 가장 높은 답변을 출력해줄 것이다.
4.1. 답변 랭킹 파이프라인
def get_cls_token(sentence):
model.eval()
tokenized_sent = tokenizer( # 문장을 토근화하여 사용할 수 있게 해준다.
sentence,
return_tensors="pt",
truncation=True,
add_special_tokens=True,
max_length=128
)
input_ids = tokenized_sent['input_ids']
attention_mask = tokenized_sent['attention_mask']
token_type_ids = tokenized_sent['token_type_ids']
with torch.no_grad(): # 학습 안할것이다.
output = model(input_ids, attention_mask, token_type_ids)
cls_output = output[1]
cls_token = cls_output.detach().cpu().numpy() # 여기선 CPU의 속도가 더 빠르다.
return cls_token
def predict(query, candidates):
candidates_cls = []
for cand in candidates: # string 타입이다.
cand_cls = get_cls_token(cand)
candidates_cls.append(cand_cls)
candidates_cls = np.array(candidates_cls).squeeze(axis=1) # 행렬계산 할 수 있도록
queury_cls = get_cls_token(query) # 쿼리와의 유사도도 구할 것이다.
similarity_list = cosine_similarity(queury_cls, candidates_cls)
target_idx = np.argmax(similarity_list)
return candidates[target_idx]
4.2. get_cls_token
테스트 코드
query = '너 요즘 바빠?'
query_cls_hidden = get_cls_token(query)
print(query_cls_hidden)
print(query_cls_hidden.shape)
768차원의 백터였으므로 1,768의 벡터가 나오게 된다.
4.3. predict
테스트 코드
sample_query = '너 요즘 바빠?'
sample_candidates = ['아니 별로 안바빠','바쁘면 바보','사자와 호랑이가 싸우면 누가 이길까', "내일은 과연 해가 뜰까"]
predicted_answer = predict(query, sample_candidates) #[[0.9004743 0.8332883 0.7963901 0.65769005]]
print(f"predicted_answer = {predicted_answer}") #predicted_answer = 아니 별로 안바빠
유사도는 잘 나오긴 하는데 완전히 관계가 없어 보이는 것도 조금은 높게 나오는 것이 보인다.
5. 답변 랭킹 모델 평가
user_query = '너 요즘 바빠?'
predicted_answer = predict(query, response_candidates)
print(f"predicted_answer = {predicted_answer}") # 후폭풍이 지나갔길 바랄게요
# 빠른 출력을 위해 100개를 넣었더니 괜찮은 답변이 나오지 않았다.
response_candidates = random.sample(answer_list, 100)
user_query = '나 요즘 너무 힘들어'
predicted_answer = predict(query, response_candidates)
print(f"predicted_answer = {predicted_answer}") #predicted_answer = 마음 단단히 잡길 바랄게요.
end = 1
while end == 1 :
sentence = input("하고싶은 말을 입력해주세요 : ")
if len(sentence) == 0 :
break
predicted_answer = predict(sentence, response_candidates)
print(predicted_answer)
print("\n")
predicted_answer = 마음 단단히 잡길 바랄게요.
하고싶은 말을 입력해주세요 : 너 지금 뭐해?
그만 듣고 싶다고 이야기해보세요.
하고싶은 말을 입력해주세요 : 그만 듣고 싶어
같이 먹어요!
하고싶은 말을 입력해주세요 : 뭐 먹고 싶은데?
나를 사랑하고 상대를 사랑하는 여유를 갖게 되길 바라요.
하고싶은 말을 입력해주세요 : 나도 사랑해!
당신을 위해 좋은 추억만 간직하세요.
하고싶은 말을 입력해주세요 :
'인공지능 > 자연어 처리' 카테고리의 다른 글
자연어 처리 모델 학습 - 전이 학습(Transfer Learning)이란 (0) | 2024.03.29 |
---|---|
자연어 처리 모델 학습 - Pre-training이란 (1) | 2024.03.29 |
자연어 처리 문장 embedding 만들기 - BERT (0) | 2024.03.28 |
자연어 처리 문장 embedding 만들기 - GPT (0) | 2024.03.27 |
자연어 처리 문장 embedding 만들기 - Transformer (0) | 2024.03.24 |