[파이썬]Transformer로 오피스 챗봇 만들기 - 코드

Seolini·2021년 12월 15일
6

Chatbot

목록 보기
5/5
post-thumbnail

👉이전 글👈에서는 Transformer로 오피스 챗봇을 만들기 전, Transformer에 대해 공부한 내용을 요약해서 정리해보았다. 이번 글에서는 데이터를 다운로드 받는 것부터 챗봇 모델을 구현하는 것까지 코드를 살펴보며 글을 적으려고 한다.
(사실 이미 티스토리에 프로젝트식으로 적어 놓았지만 블로그 특성상 코드 복붙이 불가능해서 다시 정리할겸 여기 벨로그에다가 다시 적는데, 복붙&카피가 아닌 동일인이 적었기 때문에 오해의 소지가 없길!)

한편, 참고한 코드가 트랜스포머의 기능을 하나하나 구현한 코드인데, 이 부분에 대해선 너무 길어서 따로 설명하지 않고, 수정한 코드에 위주로 설명을 적으려고 한다. 전체 코드는 깃허브에 업로드 해놓았다.



Project Description

Transformer를 이용한 이번 프로젝트는 일상 대화와 오피스 대화 데이터를 Transformer 모델로 학습시켜, 질문에 대한 적절한 답변을 하는 챗봇을 만드는 것이다. 코드는 여기를 참고했고, 데이터 적용하는 코드 부분을 변형했고, 전체적으로 트랜스포머를 구현하는 코드는 거의 같다.

학습 데이터는 참고한 코드에서 사용한 Chatbot data와 추가로 학습할 한국어대화데이터셋(오피스데이터)을 사용하였다. Chatbot 데이터의 경우 깃헙에서 불러오는 코드를 사용할 것이라 다운로드 받을 필요는없고, 한국어대화데이터셋의 경우 AIHUB에 '개방데이터-인식기술(언어지능)-한국어대화데이터셋'에서 로그인 후 다운로드 받으면 된다.

코드는 파이썬으로 Colab에서 작성하였고, GPU를 사용하였다. 따라서 코드를 참고할 사람은 GPU로 설정해주어야 할 것이다.

그 외 환경

  • batch size = 64
  • buffer size = 20000
  • epochs = 50

🌟코드 START!(창을 넓게하면 목차가 보여요!)🌟

1.환경설정 & 데이터 준비

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import re
import urllib.request
import time
import tensorflow_datasets as tfds
import tensorflow as tf
urllib.request.urlretrieve("https://raw.githubusercontent.com/songys/Chatbot_data/master/ChatbotData%20.csv", filename="ChatBotData.csv")
data = pd.read_csv('ChatBotData.csv')
data = data[0:5290]

먼저 환경변수 셋팅을 해주고, 학습시킬 데이터를 드라이브에 준비한다. 위 코드를 통해 Chatbot 데이터 깃헙에서 불러와주었다.

한편 해당 데이터셋에서 일상 관련 데이터가 5290행 까지라서 data[0:5290]으로 불러와줬다. 일상 데이터를 상위 10개를 출력하면 아래와 같이 출력된다(data.head(10))

그 다음으로는 오피스대화 데이터를 드라이브에 저장한 뒤 계정을 연동하여 불러와 같은 형식으로 전처리를 해준다.

#구글드라이브 연동
from google.colab import drive
drive.mount('/content/drive')
f = open(r'/content/drive/MyDrive/파이썬공부/챗봇/대화데이터_일상_오피스.txt',"r")
lines = f.readlines()
Q = []
A = []

for i in range(len(lines)) :
    if i%2 == 0 :
        Q.append(lines[i][2:-1])
        A.append(lines[i+1][2:-1])
import pandas as pd
df = pd.DataFrame()

df['Q'] = Q
df['A'] = A
df['label'] = 1

위 코드를 입력하여 전처리를 해주고 df를 출력해보면 아래 오른쪽 이미지와 같이 출력될 것이다.

데이터를 이제와서(?) 살펴보면 동일한 질문에 대해 여러 개의 답변으로 이루어진 것을 살펴볼 수 있다.

이제 위 두 데이터를 concat() 함수를 이용하여 합쳐 하나의 데이터프레임으로 나타내주도록 한다.

train_data = pd.concat([data, df],ignore_index=True)

train_data = train_data.sample(frac=1).reset_index(drop=True)

위 코드에서 마지막 줄 코드는 데이터를 랜덤으로 섞어주는 코드이다. len(train_data)를 입력한 뒤 출력하면 6615가 출력될 것이다.(데이터 갯수)


2.단어 집합 생성

한편, 문장 그대로 학습 모델에 넣으면 모델이 인식을 할 수 없기 때문에 단어 집합을 만들어 주어야 한다. 그리고 정수 인코딩과 패딩을 해주는 작업을 해주어야 한다.

#특수기호 띄어쓰기
questions = []
for sentence in train_data['Q']:
    sentence = re.sub(r"([?.!,])", r" \1 ", sentence)
    sentence = sentence.strip()
    questions.append(sentence)
    
    answers = []
for sentence in train_data['A']:
    sentence = re.sub(r"([?.!,])", r" \1 ", sentence)
    sentence = sentence.strip()
    answers.append(sentence)
# 서브워드텍스트인코더를 사용하여 질문, 답변 데이터로부터 단어 집합(Vocabulary) 생성
tokenizer = tfds.deprecated.text.SubwordTextEncoder.build_from_corpus(
    questions + answers, target_vocab_size=2**13)
    
# 시작 토큰과 종료 토큰에 대한 정수 부여
START_TOKEN, END_TOKEN = [tokenizer.vocab_size], [tokenizer.vocab_size + 1]

# 시작 토큰과 종료 토큰을 고려하여 단어 집합의 크기를 + 2
VOCAB_SIZE = tokenizer.vocab_size + 2    
#정수 인코딩과 패딩
# 서브워드텍스트인코더 토크나이저의 .encode()를 사용하여 텍스트 시퀀스를 정수 시퀀스로 변환.
print('임의의 질문 샘플을 정수 인코딩 : {}'.format(tokenizer.encode(questions[20])))
#출력 : 임의의 질문 샘플을 정수 인코딩 : [8656, 331]

# 최대 길이를 40으로 정의
MAX_LENGTH = 40

# 토큰화 / 정수 인코딩 / 시작 토큰과 종료 토큰 추가 / 패딩
def tokenize_and_filter(inputs, outputs):
  tokenized_inputs, tokenized_outputs = [], []

  for (sentence1, sentence2) in zip(inputs, outputs):
    # encode(토큰화 + 정수 인코딩), 시작 토큰과 종료 토큰 추가
    sentence1 = START_TOKEN + tokenizer.encode(sentence1) + END_TOKEN
    sentence2 = START_TOKEN + tokenizer.encode(sentence2) + END_TOKEN

    tokenized_inputs.append(sentence1)
    tokenized_outputs.append(sentence2)

  # 패딩
  tokenized_inputs = tf.keras.preprocessing.sequence.pad_sequences(
      tokenized_inputs, maxlen=MAX_LENGTH, padding='post')
  tokenized_outputs = tf.keras.preprocessing.sequence.pad_sequences(
      tokenized_outputs, maxlen=MAX_LENGTH, padding='post')

  return tokenized_inputs, tokenized_outputs
questions, answers = tokenize_and_filter(questions, answers)

여기까지 코드를 입력하고 실행한 뒤, 샘플로 print(questions[0]), print(answers[0])을 해보면 아래와 같이 정수 인코딩과 패딩이 된 결과가 출력된다.

[10023 1324 18 5 569 1 10024 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0]

[10023 73 6 125 772 1 10024 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0]

마지막으로 인코더와 디코더의 입력 데이터가 되도록 배치 크기로 데이터를 묶어준다.

BATCH_SIZE = 64
BUFFER_SIZE = 20000

dataset = tf.data.Dataset.from_tensor_slices((
    {
        'inputs': questions,
        'dec_inputs': answers[:, :-1] # 디코더의 입력 / 마지막 패딩 토큰 제거
    },
    {
        'outputs': answers[:, 1:]  # 맨 처음 토큰이 제거 = 시작 토큰 제거
    },
))

dataset = dataset.cache()
dataset = dataset.shuffle(BUFFER_SIZE)
dataset = dataset.batch(BATCH_SIZE)
dataset = dataset.prefetch(tf.data.experimental.AUTOTUNE)

3.트랜스포머 모델 만들기

위에서도 말했지만 트랜스포머를 일일이 구현하는 코드를 참고했다. 코드의 각 자세한 설명은 이 글을 참조하길!

def transformer(vocab_size, num_layers, dff,
                d_model, num_heads, dropout,
                name="transformer"):

  # 인코더의 입력
  inputs = tf.keras.Input(shape=(None,), name="inputs")

  # 디코더의 입력
  dec_inputs = tf.keras.Input(shape=(None,), name="dec_inputs")

  # 인코더의 패딩 마스크
  enc_padding_mask = tf.keras.layers.Lambda(
      create_padding_mask, output_shape=(1, 1, None),
      name='enc_padding_mask')(inputs)

  # 디코더의 룩어헤드 마스크(첫번째 서브층)
  look_ahead_mask = tf.keras.layers.Lambda(
      create_look_ahead_mask, output_shape=(1, None, None),
      name='look_ahead_mask')(dec_inputs)

  # 디코더의 패딩 마스크(두번째 서브층)
  dec_padding_mask = tf.keras.layers.Lambda(
      create_padding_mask, output_shape=(1, 1, None),
      name='dec_padding_mask')(inputs)

  # 인코더의 출력은 enc_outputs. 디코더로 전달된다.
  enc_outputs = encoder(vocab_size=vocab_size, num_layers=num_layers, dff=dff,
      d_model=d_model, num_heads=num_heads, dropout=dropout,
  )(inputs=[inputs, enc_padding_mask]) # 인코더의 입력은 입력 문장과 패딩 마스크

  # 디코더의 출력은 dec_outputs. 출력층으로 전달된다.
  dec_outputs = decoder(vocab_size=vocab_size, num_layers=num_layers, dff=dff,
      d_model=d_model, num_heads=num_heads, dropout=dropout,
  )(inputs=[dec_inputs, enc_outputs, look_ahead_mask, dec_padding_mask])

  # 다음 단어 예측을 위한 출력층
  outputs = tf.keras.layers.Dense(units=vocab_size, name="outputs")(dec_outputs)

  return tf.keras.Model(inputs=[inputs, dec_inputs], outputs=outputs, name=name)
class PositionalEncoding(tf.keras.layers.Layer):
  def __init__(self, position, d_model):
    super(PositionalEncoding, self).__init__()
    self.pos_encoding = self.positional_encoding(position, d_model)

  def get_angles(self, position, i, d_model):
    angles = 1 / tf.pow(10000, (2 * (i // 2)) / tf.cast(d_model, tf.float32))
    return position * angles

  def positional_encoding(self, position, d_model):
    angle_rads = self.get_angles(
        position=tf.range(position, dtype=tf.float32)[:, tf.newaxis],
        i=tf.range(d_model, dtype=tf.float32)[tf.newaxis, :],
        d_model=d_model)

    # 배열의 짝수 인덱스(2i)에는 사인 함수 적용
    sines = tf.math.sin(angle_rads[:, 0::2])

    # 배열의 홀수 인덱스(2i+1)에는 코사인 함수 적용
    cosines = tf.math.cos(angle_rads[:, 1::2])

    angle_rads = np.zeros(angle_rads.shape)
    angle_rads[:, 0::2] = sines
    angle_rads[:, 1::2] = cosines
    pos_encoding = tf.constant(angle_rads)
    pos_encoding = pos_encoding[tf.newaxis, ...]

    print(pos_encoding.shape)
    return tf.cast(pos_encoding, tf.float32)

  def call(self, inputs):
    return inputs + self.pos_encoding[:, :tf.shape(inputs)[1], :]
def create_padding_mask(x):
  mask = tf.cast(tf.math.equal(x, 0), tf.float32)
  # (batch_size, 1, 1, key의 문장 길이)
  return mask[:, tf.newaxis, tf.newaxis, :]

# 디코더의 첫번째 서브층(sublayer)에서 미래 토큰을 Mask하는 함수
def create_look_ahead_mask(x):
  seq_len = tf.shape(x)[1]
  look_ahead_mask = 1 - tf.linalg.band_part(tf.ones((seq_len, seq_len)), -1, 0)
  padding_mask = create_padding_mask(x) # 패딩 마스크도 포함
  return tf.maximum(look_ahead_mask, padding_mask)

#encoder
def encoder(vocab_size, num_layers, dff,
            d_model, num_heads, dropout,
            name="encoder"):
  inputs = tf.keras.Input(shape=(None,), name="inputs")

  # 인코더는 패딩 마스크 사용
  padding_mask = tf.keras.Input(shape=(1, 1, None), name="padding_mask")

  # 포지셔널 인코딩 + 드롭아웃
  embeddings = tf.keras.layers.Embedding(vocab_size, d_model)(inputs)
  embeddings *= tf.math.sqrt(tf.cast(d_model, tf.float32))
  embeddings = PositionalEncoding(vocab_size, d_model)(embeddings)
  outputs = tf.keras.layers.Dropout(rate=dropout)(embeddings)

  # 인코더를 num_layers개 쌓기
  for i in range(num_layers):
    outputs = encoder_layer(dff=dff, d_model=d_model, num_heads=num_heads,
        dropout=dropout, name="encoder_layer_{}".format(i),
    )([outputs, padding_mask])

  return tf.keras.Model(
      inputs=[inputs, padding_mask], outputs=outputs, name=name)
def encoder_layer(dff, d_model, num_heads, dropout, name="encoder_layer"):
  inputs = tf.keras.Input(shape=(None, d_model), name="inputs")

  # 인코더는 패딩 마스크 사용
  padding_mask = tf.keras.Input(shape=(1, 1, None), name="padding_mask")

  # 멀티-헤드 어텐션 (첫번째 서브층 / 셀프 어텐션)
  attention = MultiHeadAttention(
      d_model, num_heads, name="attention")({
          'query': inputs, 'key': inputs, 'value': inputs, # Q = K = V
          'mask': padding_mask # 패딩 마스크 사용
      })

  # 드롭아웃 + 잔차 연결과 층 정규화
  attention = tf.keras.layers.Dropout(rate=dropout)(attention)
  attention = tf.keras.layers.LayerNormalization(
      epsilon=1e-6)(inputs + attention)

  # 포지션 와이즈 피드 포워드 신경망 (두번째 서브층)
  outputs = tf.keras.layers.Dense(units=dff, activation='relu')(attention)
  outputs = tf.keras.layers.Dense(units=d_model)(outputs)

  # 드롭아웃 + 잔차 연결과 층 정규화
  outputs = tf.keras.layers.Dropout(rate=dropout)(outputs)
  outputs = tf.keras.layers.LayerNormalization(
      epsilon=1e-6)(attention + outputs)

  return tf.keras.Model(
      inputs=[inputs, padding_mask], outputs=outputs, name=name)
class MultiHeadAttention(tf.keras.layers.Layer):

  def __init__(self, d_model, num_heads, name="multi_head_attention"):
    super(MultiHeadAttention, self).__init__(name=name)
    self.num_heads = num_heads
    self.d_model = d_model

    assert d_model % self.num_heads == 0

    # d_model을 num_heads로 나눈 값.
    # 논문 기준 : 64
    self.depth = d_model // self.num_heads

    # WQ, WK, WV에 해당하는 밀집층 정의
    self.query_dense = tf.keras.layers.Dense(units=d_model)
    self.key_dense = tf.keras.layers.Dense(units=d_model)
    self.value_dense = tf.keras.layers.Dense(units=d_model)

    # WO에 해당하는 밀집층 정의
    self.dense = tf.keras.layers.Dense(units=d_model)

  # num_heads 개수만큼 q, k, v를 split하는 함수
  def split_heads(self, inputs, batch_size):
    inputs = tf.reshape(
        inputs, shape=(batch_size, -1, self.num_heads, self.depth))
    return tf.transpose(inputs, perm=[0, 2, 1, 3])

  def call(self, inputs):
    query, key, value, mask = inputs['query'], inputs['key'], inputs[
        'value'], inputs['mask']
    batch_size = tf.shape(query)[0]

    query = self.query_dense(query)
    key = self.key_dense(key)
    value = self.value_dense(value)

    # 2. 헤드 나누기
    # q : (batch_size, num_heads, query의 문장 길이, d_model/num_heads)
    # k : (batch_size, num_heads, key의 문장 길이, d_model/num_heads)
    # v : (batch_size, num_heads, value의 문장 길이, d_model/num_heads)
    query = self.split_heads(query, batch_size)
    key = self.split_heads(key, batch_size)
    value = self.split_heads(value, batch_size)

    # 3. 스케일드 닷 프로덕트 어텐션. 앞서 구현한 함수 사용.
    # (batch_size, num_heads, query의 문장 길이, d_model/num_heads)
    scaled_attention, _ = scaled_dot_product_attention(query, key, value, mask)
    # (batch_size, query의 문장 길이, num_heads, d_model/num_heads)
    scaled_attention = tf.transpose(scaled_attention, perm=[0, 2, 1, 3])

    # 4. 헤드 연결(concatenate)하기
    # (batch_size, query의 문장 길이, d_model)
    concat_attention = tf.reshape(scaled_attention,
                                  (batch_size, -1, self.d_model))

    # 5. WO에 해당하는 밀집층 지나기
    # (batch_size, query의 문장 길이, d_model)
    outputs = self.dense(concat_attention)

    return outputs
def scaled_dot_product_attention(query, key, value, mask):
  # query 크기 : (batch_size, num_heads, query의 문장 길이, d_model/num_heads)
  # key 크기 : (batch_size, num_heads, key의 문장 길이, d_model/num_heads)
  # value 크기 : (batch_size, num_heads, value의 문장 길이, d_model/num_heads)
  # padding_mask : (batch_size, 1, 1, key의 문장 길이)

  # Q와 K의 곱. 어텐션 스코어 행렬.
  matmul_qk = tf.matmul(query, key, transpose_b=True)

  # 스케일링
  # dk의 루트값으로 나눠준다.
  depth = tf.cast(tf.shape(key)[-1], tf.float32)
  logits = matmul_qk / tf.math.sqrt(depth)

  # 마스킹. 어텐션 스코어 행렬의 마스킹 할 위치에 매우 작은 음수값을 넣는다.
  # 매우 작은 값이므로 소프트맥스 함수를 지나면 행렬의 해당 위치의 값은 0이 된다.
  if mask is not None:
    logits += (mask * -1e9)

  # 소프트맥스 함수는 마지막 차원인 key의 문장 길이 방향으로 수행된다.
  # attention weight : (batch_size, num_heads, query의 문장 길이, key의 문장 길이)
  attention_weights = tf.nn.softmax(logits, axis=-1)

  # output : (batch_size, num_heads, query의 문장 길이, d_model/num_heads)
  output = tf.matmul(attention_weights, value)

  return output, attention_weights
def decoder(vocab_size, num_layers, dff,
            d_model, num_heads, dropout,
            name='decoder'):
  inputs = tf.keras.Input(shape=(None,), name='inputs')
  enc_outputs = tf.keras.Input(shape=(None, d_model), name='encoder_outputs')

  # 디코더는 룩어헤드 마스크(첫번째 서브층)와 패딩 마스크(두번째 서브층) 둘 다 사용.
  look_ahead_mask = tf.keras.Input(
      shape=(1, None, None), name='look_ahead_mask')
  padding_mask = tf.keras.Input(shape=(1, 1, None), name='padding_mask')

  # 포지셔널 인코딩 + 드롭아웃
  embeddings = tf.keras.layers.Embedding(vocab_size, d_model)(inputs)
  embeddings *= tf.math.sqrt(tf.cast(d_model, tf.float32))
  embeddings = PositionalEncoding(vocab_size, d_model)(embeddings)
  outputs = tf.keras.layers.Dropout(rate=dropout)(embeddings)

  # 디코더를 num_layers개 쌓기
  for i in range(num_layers):
    outputs = decoder_layer(dff=dff, d_model=d_model, num_heads=num_heads,
        dropout=dropout, name='decoder_layer_{}'.format(i),
    )(inputs=[outputs, enc_outputs, look_ahead_mask, padding_mask])

  return tf.keras.Model(
      inputs=[inputs, enc_outputs, look_ahead_mask, padding_mask],
      outputs=outputs,
      name=name)
def decoder_layer(dff, d_model, num_heads, dropout, name="decoder_layer"):
  inputs = tf.keras.Input(shape=(None, d_model), name="inputs")
  enc_outputs = tf.keras.Input(shape=(None, d_model), name="encoder_outputs")

  # 룩어헤드 마스크(첫번째 서브층)
  look_ahead_mask = tf.keras.Input(
      shape=(1, None, None), name="look_ahead_mask")

  # 패딩 마스크(두번째 서브층)
  padding_mask = tf.keras.Input(shape=(1, 1, None), name='padding_mask')

  # 멀티-헤드 어텐션 (첫번째 서브층 / 마스크드 셀프 어텐션)
  attention1 = MultiHeadAttention(
      d_model, num_heads, name="attention_1")(inputs={
          'query': inputs, 'key': inputs, 'value': inputs, # Q = K = V
          'mask': look_ahead_mask # 룩어헤드 마스크
      })

  # 잔차 연결과 층 정규화
  attention1 = tf.keras.layers.LayerNormalization(
      epsilon=1e-6)(attention1 + inputs)

  # 멀티-헤드 어텐션 (두번째 서브층 / 디코더-인코더 어텐션)
  attention2 = MultiHeadAttention(
      d_model, num_heads, name="attention_2")(inputs={
          'query': attention1, 'key': enc_outputs, 'value': enc_outputs, # Q != K = V
          'mask': padding_mask # 패딩 마스크
      })

  # 드롭아웃 + 잔차 연결과 층 정규화
  attention2 = tf.keras.layers.Dropout(rate=dropout)(attention2)
  attention2 = tf.keras.layers.LayerNormalization(
      epsilon=1e-6)(attention2 + attention1)

  # 포지션 와이즈 피드 포워드 신경망 (세번째 서브층)
  outputs = tf.keras.layers.Dense(units=dff, activation='relu')(attention2)
  outputs = tf.keras.layers.Dense(units=d_model)(outputs)

  # 드롭아웃 + 잔차 연결과 층 정규화
  outputs = tf.keras.layers.Dropout(rate=dropout)(outputs)
  outputs = tf.keras.layers.LayerNormalization(
      epsilon=1e-6)(outputs + attention2)

  return tf.keras.Model(
      inputs=[inputs, enc_outputs, look_ahead_mask, padding_mask],
      outputs=outputs,
      name=name)
tf.keras.backend.clear_session()

# Hyper-parameters
D_MODEL = 256
NUM_LAYERS = 2
NUM_HEADS = 8
DFF = 512
DROPOUT = 0.1

model = transformer(
    vocab_size=VOCAB_SIZE,
    num_layers=NUM_LAYERS,
    dff=DFF,
    d_model=D_MODEL,
    num_heads=NUM_HEADS,
    dropout=DROPOUT)
#출력 : (1, 10025, 256) (1, 10025, 256)
class CustomSchedule(tf.keras.optimizers.schedules.LearningRateSchedule):

  def __init__(self, d_model, warmup_steps=4000):
    super(CustomSchedule, self).__init__()
    self.d_model = d_model
    self.d_model = tf.cast(self.d_model, tf.float32)
    self.warmup_steps = warmup_steps

  def __call__(self, step):
    arg1 = tf.math.rsqrt(step)
    arg2 = step * (self.warmup_steps**-1.5)

    return tf.math.rsqrt(self.d_model) * tf.math.minimum(arg1, arg2)

def loss_function(y_true, y_pred):
  y_true = tf.reshape(y_true, shape=(-1, MAX_LENGTH - 1))

  loss = tf.keras.losses.SparseCategoricalCrossentropy(
      from_logits=True, reduction='none')(y_true, y_pred)

  mask = tf.cast(tf.not_equal(y_true, 0), tf.float32)
  loss = tf.multiply(loss, mask)

  return tf.reduce_mean(loss)
learning_rate = CustomSchedule(D_MODEL)

optimizer = tf.keras.optimizers.Adam(
    learning_rate, beta_1=0.9, beta_2=0.98, epsilon=1e-9)

def accuracy(y_true, y_pred):
  # 레이블의 크기는 (batch_size, MAX_LENGTH - 1)
  y_true = tf.reshape(y_true, shape=(-1, MAX_LENGTH - 1))
  return tf.keras.metrics.sparse_categorical_accuracy(y_true, y_pred)

model.compile(optimizer=optimizer, loss=loss_function, metrics=[accuracy])

여기까지 코드 실행을 해주었다면 앞서 준비한 데이터셋으로 지정한 epochs 만큼 모델을 학습시킨다.

EPOCHS = 50
model.fit(dataset, epochs=EPOCHS)

위 코드를 통해 50번 학습을 시켰고, 수행 시간은 10분 미만이었다. 50번 째 학습했을 때의 loss 값은 0.0145로 측정되었다.


4.챗봇 평가하기

학습을 시켰다면 학습시킨 챗봇에 새로운 문장을 넣어서 평가를 해보고자 했다. 새로운 문장 역시 인코더 입력 형식으로 변형시켜야 하기 때문에 아래의 코드를 실행해준다.

def evaluate(sentence):
  sentence = preprocess_sentence(sentence)

  sentence = tf.expand_dims(
      START_TOKEN + tokenizer.encode(sentence) + END_TOKEN, axis=0)

  output = tf.expand_dims(START_TOKEN, 0)

  # 디코더의 예측 시작
  for i in range(MAX_LENGTH):
    predictions = model(inputs=[sentence, output], training=False)

    # 현재(마지막) 시점의 예측 단어를 받아온다.
    predictions = predictions[:, -1:, :]
    predicted_id = tf.cast(tf.argmax(predictions, axis=-1), tf.int32)

    # 만약 마지막 시점의 예측 단어가 종료 토큰이라면 예측을 중단
    if tf.equal(predicted_id, END_TOKEN[0]):
      break

    # 마지막 시점의 예측 단어를 출력에 연결한다.
    # 이는 for문을 통해서 디코더의 입력으로 사용될 예정이다.
    output = tf.concat([output, predicted_id], axis=-1)

  return tf.squeeze(output, axis=0)
def predict(sentence):
  prediction = evaluate(sentence)

  predicted_sentence = tokenizer.decode(
      [i for i in prediction if i < tokenizer.vocab_size])

  print('Input: {}'.format(sentence))
  print('Output: {}'.format(predicted_sentence))

  return predicted_sentence
  
  
  def preprocess_sentence(sentence):
  sentence = re.sub(r"([?.!,])", r" \1 ", sentence)
  sentence = sentence.strip()
  return sentence

이제 predict() 함수에 문장을 입력하면 해당 문장에 대한 결과가 출력된다. 아래는 새로운 문장을 넣었을 때 출력된 결과들이다.

output = predict("굿모닝")

'굿모닝'과 같은 데이터는 학습데이터에 있는 데이터지만 학습 데이터에 '야근'이 포함되는 문장이 없는데도 적절한 답변이 출력되었음을 살펴볼 수 있다. 이를 통해 학습한 챗봇 모델이 입력한 문장에 대해 상당히 적절한 답변을 하는 것을 확인할 수 있다.



# Project Result Transformer를 구현하는 코드를 입력하고, 데이터를 적절히 활용하여 질의에 대해 적당한 답변을 하는 오피스 챗봇을 만들어 보았다. 약 6600개 밖에 안되는 데이터로 학습시켰지만 성능이 상당히 좋았다. 특히 오피스 대화 데이터는 전체 데이터의 5분의 1밖에 되지 않지만, 일상 데이터와 같이 학습을 시킨 결과 학습데이터에 없던 문장을 입력해도 적절한 답변을 출력하는 것을 확인할 수 있었다. 따라서 좀 더 구체적으로 특정 분야에 특화된 데이터셋이 많다면 아주 성능이 좋은 챗봇이 만들어지지 않을까 생각된다.

추가로, 일반적인 대화 말고도 실제 회사에서 상용화할 수 있는 AI비서 챗봇을 만들기 위해선 현재 할 일, 남은 퇴근시간, 주요 업무 스케쥴 등의 가변적인 데이터를 함께 사용한다면 더욱 더 쓸모가 있는 챗봇이 되지 않을까 싶다.



<이전 글 보기>
[파이썬]일상/연애 주제의 한국어 대화 'BERT'로 이진 분류 모델 만들기 - 이론편
[파이썬]일상/연애 주제의 한국어 대화 'BERT'로 이진 분류 모델 만들기 - 코드
[파이썬]KoBERT로 다중 분류 모델 만들기 - 코드
[파이썬]TextRank(텍스트랭크)란.. 간단하게 사용해보기

profile
코드 문의는 언제나 환영입니다 :D 메일 답변이 빨라요! 댓글은 안봐요ㅠ💗

2개의 댓글

comment-user-thumbnail
2023년 2월 24일

Good Jobs!!!
https://www.abellarora.com/sanghavi-escorts-service.html
https://www.abellarora.com/wadgaon-sheri-escorts-service.html
https://www.abellarora.com/junnar-escorts-service.html
https://www.abellarora.com/vastrapur-escort-service.html
https://www.abellarora.com/maninagar-escort-service.html
https://www.abellarora.com/palanpur-call-girls.html
https://www.abellarora.com/gondal-call-girls.html
https://www.abellarora.com/veraval-call-girls.html
https://www.abellarora.com/kalol-call-girls.html
https://www.abellarora.com/deesa-call-girls.html
https://www.abellarora.com/jetpur-call-girls.html
https://www.abellarora.com/himmatnagar-call-girls.html
https://www.abellarora.com/godhra-call-girls.html
https://www.abellarora.com/ankleshwar-call-girls.html
https://www.abellarora.com/kutch-call-girls.html
https://www.abellarora.com/somnath-call-girls.html
https://www.abellarora.com/visnagar-call-girls.html
https://www.abellarora.com/udhana-call-girls.html
https://www.abellarora.com/gandhidham-call-girls.html
https://www.abellarora.com/bhuj-call-girls.html
https://www.abellarora.com/valsad-call-girls.html
https://www.abellarora.com/cg-road-call-girls.html
https://www.abellarora.com/sg-highway-call-girls.html
https://www.abellarora.com/prahlad-nagar-call-girls.html
https://www.abellarora.com/anand-escorts-service.html
https://www.abellarora.com/chhota-udaipur-escorts-service.html
https://www.abellarora.com/dahod-escorts-service.html
https://www.abellarora.com/kheda-escorts-service.html
https://www.abellarora.com/mahisagar-escorts-service.html
https://www.abellarora.com/panchmahal-escorts-service.html
https://www.abellarora.com/aravalli-escorts-service.html
https://www.abellarora.com/banaskantha-escorts-service.html
https://www.abellarora.com/mehsana-escorts-service.html
https://www.abellarora.com/patan-escorts-service.html
https://www.abellarora.com/sabarkantha-escorts-service.html
https://www.abellarora.com/saurashtra-kutch-escorts-service.html
https://www.abellarora.com/botad-escorts-service.html
https://www.abellarora.com/devbhoomi-dwarka-escorts-service.html
https://www.abellarora.com/gir-somnath-escorts-service.html
https://www.abellarora.com/porbandar-escorts-service.html
https://www.abellarora.com/surendranagar-escorts-service.html
https://www.abellarora.com/kachchh-escorts-service.html
https://www.abellarora.com/bharuch-escorts-service.html
https://www.abellarora.com/dang-escorts-service.html
https://www.abellarora.com/narmada-escorts-service.html
https://www.abellarora.com/navsari-escorts-service.html
https://www.abellarora.com/tapi-escorts-service.html
https://www.abellarora.com/vapi-escorts-service.html
https://www.abellarora.com/silvassa-escorts-service.html
https://www.abellarora.com/diu-escorts-service.html
https://www.abellarora.com/sola-over-bridge-escort-service.html
https://www.abellarora.com/thaltej-chokdi-escort-service.html
https://www.abellarora.com/ashram-road-escort-service.html
https://www.abellarora.com/satellite-escort-service.html
https://www.abellarora.com/naroda-escort-service.html
https://www.abellarora.com/daman-escorts.html
https://www.abellarora.com/rajkot-escorts.html
https://www.abellarora.com/kolar-escorts-service.html
https://www.abellarora.com/chitradurga-escorts-service.html
https://www.abellarora.com/udupi-escorts-service.html
https://www.abellarora.com/rourkela-escorts-service.html
https://www.abellarora.com/anantapur-escorts.html
https://www.abellarora.com/vizianagaram-escorts.html
https://www.abellarora.com/eluru-escorts.html
https://www.abellarora.com/nellore-escorts.html
https://www.abellarora.com/kadapa-escorts.html
https://www.abellarora.com/narasaraopet-escorts.html
https://www.abellarora.com/port-blair-escorts.html
https://www.abellarora.com/amravati-escorts-service.html
https://www.abellarora.com/tenali-escorts-service.html
https://www.abellarora.com/nandyal-escorts-service.html
https://www.abellarora.com/srikakulam-escorts-service.html
https://www.abellarora.com/proddatur-escorts-service.html
https://www.abellarora.com/adoni-escorts-service.html
https://www.abellarora.com/hindupur-escorts-service.html
https://www.abellarora.com/tadipatri-escorts-service.html
https://www.abellarora.com/tadepalligudem-escorts-service.html
https://www.abellarora.com/guntakal-escorts-service.html
https://www.abellarora.com/madanapalle-escorts-service.html
https://www.abellarora.com/chilakaluripet-escorts-service.html
https://www.abellarora.com/chittoor-escorts-service.html
https://www.abellarora.com/dharmavaram-escorts-service.html
https://www.abellarora.com/palakollu-escorts-service.html
https://www.abellarora.com/srisailam-escorts-service.html
https://www.abellarora.com/bheemunipatnam-escorts-service.html
https://www.abellarora.com/anakapalle-escorts-service.html
https://www.abellarora.com/amalapuram-escorts-service.html
https://www.abellarora.com/mangalagiri-escorts-service.html
https://www.abellarora.com/lepakshi-escorts-service.html
https://www.abellarora.com/srikalahasti-escorts-service.html
https://www.abellarora.com/puttaparthi-escorts-service.html
https://www.abellarora.com/horsley-hills-escorts-service.html
https://www.abellarora.com/thiruvananthapuram-escorts-service.html
https://www.abellarora.com/kozhikode-escorts-service.html
https://www.abellarora.com/thrissur-escorts-service.html
https://www.abellarora.com/kollam-escorts-service.html
https://www.abellarora.com/kannur-escorts-service.html
https://www.abellarora.com/alappuzha-escorts-service.html
https://www.abellarora.com/kottayam-escorts-service.html
https://www.abellarora.com/palakkad-escorts-service.html

답글 달기
comment-user-thumbnail
2023년 8월 4일

안녕하세요 해당 모델을 사용하던 도중 다음과 같은 오류가 났습니다.

--> 340 optimizer = tf.keras.optimizers.Adam(
341 learning_rate, beta_1=0.9, beta_2=0.98, epsilon=1e-9)

InvalidArgumentError: Value for attr 'T' of int64 is not in the list of allowed values: bfloat16, half, float, double, complex64, complex128
; NodeDef: {{node Rsqrt}}; Op<name=Rsqrt; signature=x:T -> y:T; attr=T:type,allowed=[DT_BFLOAT16, DT_HALF, DT_FLOAT, DT_DOUBLE, DT_COMPLEX64, DT_COMPLEX128]> [Op:Rsqrt] name:

혹시 해결방법을 알 수 있을까요?

답글 달기