Naive-FAQ-Chatbot-1

간단한 FAQ 챗봇을 만들어보겠습니다.

이 챗봇은 간단한 형태로 챗봇을 처음 접하시는 분들을 위해 작성한 코드정도로 생각하시면 될듯합니다.

csv 파일은 질문과 그 질문이 속해 있는 카테고리의 집합입니다.
예를 들어서 질문의 내용이 “사용 중인 아이디 또는 이름을 변경하고 싶어요” 이라면 이것은 “회원” 카테고리에 등록된 질문이라고 인식하여 그 중에서 하나의 답변을 찾아 리턴하는 방법입니다.

본 테스트 데이터 셋에는 [“회원”,”교재”,”웹사이트”…] 총 6개의 카테고리가 있습니다.

즉, 어떠한 질문을 입력을 받고 입력 받은 데이터를 통해서 해당 질문이 어떤 카테고리에 속하는 질문인지 찾아 내는 분류(Classification)의 문제로 접근하면 됩니다.

일단 사용할 라이브러리를 import 합니다.
추가한 라이브러리를 보시면 아시겠지만 pytorch로 구현되어 있는 코드입니다.
나중에 Tensorflow나 keras로 작성된 코드도 정리해서 올려드리겠습니다.

코드의 구성은 단위 기능을 수행하는 몇개의 파일로 분리되어 있습니다.

  • train.py
  • trainer.py
  • model.py
  • dataloader.py
  • predict.py

아래의 파일은 train.py 입니다.

해당 파일의 기능은 데이터 준비, 모델 셋팅, 훈련,  모델 저장의 역할을 수행합니다.

import argparse
import numpy as np
from konlpy.tag import Okt

import torch
import torch.nn as nn
import torch.optim as optim

from model import FaqCategoryClassifier
from dataloader import DataLoader
from trainer import Trainer

아래 부분은 csv파일을 통해서 데이터를 읽어 오는 부분입니다.
데이터를 읽은 후에 x_train, y_train, labels로 정보를 리턴합니다.
해당 파일의 type은 numpy 형태로 들어오고 학습을 위해  데이터 타입을 변환해줍니다.
pytorch는 tensorflow와 달리 Define-by-Run 형태이기 때문에 코드를 이해하기가 편리합니다.

* 이미지를 포함한 간단한 설명이 있으니 참고하시면 이해하시기 좋을듯합니다.
https://medium.com/@zzemb6/define-and-run-vs-define-by-run-b527d127e13a

학습용 데이터는 적당히 섞어줍니다. 이때 각 feature 데이터와 label 데이터가 섞이지 않도록 반드시 주의해야 합니다. 그리고 마지막에 해당 데이터의  shape을 표시해보고 데이터가 잘 들어왔는지 확인해봅니다.

참고로 읽어온 데이터에서 훈련용 세트와 검증용 세트를 분리합니다. 본 실험에서는 7:3정도로 분리하여 사용합니다.

 ## Data Read
 dataloader = DataLoader('./data/faq.categories.extend.csv', okt)
 x_train, y_train, labels = dataloader.prepareDataset()

 x_train = torch.FloatTensor(x_train)
 y_train = torch.LongTensor(y_train)

 train_cnt = int(x_train.size(0) * 0.7)
 valid_cnt = x_train.size(0) - train_cnt

 indices = torch.randperm(x_train.size(0))
 x = torch.index_select(x_train, dim=0, index=indices).split([train_cnt, valid_cnt], dim=0) # x[0] x_train, x[1] x_train valid data
 y = torch.index_select(y_train, dim=0, index=indices).split([train_cnt, valid_cnt], dim=0) # y[0] y_train, y[1] y_train valid label

 print('Train', x[0].shape, x[1].shape)
 print('Valid', y[0].shape, y[1].shape)

데이터가 준비되었으면 이제 모델을 생성합니다.
모델은 입력(IPT)과 출력(Hidden), 그리고 최종 출력(OPT)의 형태로 나타낼 수 있습니다. 이때 최종 출력은 FAQ의 카테고리 즉 6개 중 하나인 one-hot의 형태로 출력합니다.
그리고 GD 알고리즘 중에 하나인 Adam을 사용하고 분류모델의 손실함수로 cross-entropy를 사용합니다.

## Model Setting
IPT = 196
H = 100
OPT = len(labels)
model = FaqCategoryClassifier(IPT, H, OPT)

optimizer = optim.Adam(model.parameters())
loss = nn.CrossEntropyLoss()

모델은 다음과 같이 생성합니다.
모델은 nn.Module을 상속 받아서 클래스 파일 형태로 작성합니다.
위의 모델은 간단한 형태의 정보입니다. 더 높은 학습 결과를 얻기 위해서는 모델의 레이어를 잘구성해야 합니다. 맨 마지막에 최종 출력의 shape을 넣어야 한다는 것을 기억하시 바랍니다. 만일 이 정보가 맞지 않을 경우 에러 코드를 표시합니다.

class FaqCategoryClassifier(nn.Module):
    def __init__(self, IPT, H, OPT):
        print('FaqCategoryClassifier Load!')
        super().__init__()

        self.layers = nn.Sequential(
            nn.Linear(IPT, H),
            nn.Linear(H, 50),
            nn.Linear(50, 20),
            nn.Linear(20, OPT)
        )
        

    def forward(self, x):
        return self.layers(x)

이제는 아래와 같은 방법으로 모델을 훈련시킵니다.
Trainer 클래스는 다음 편에서 내용을 설명해드리겠습니다.
일단 Trainer에서 활용하는 데이터는 입력값과 검증값 데이터들과 각각의 레이블 정보입니다.

## Trainer
trainer = Trainer(model, optimizer, loss)
trainer.train((x[0], y[0]), (x[1], y[1]), config)

훈련이 완료되면 해당 모델을 저장합니다.
이때 저장할 데이터는 모델 데이터 외에도 다양한 데이터를 함께 저장할 수 있습니다.
아래의 코드는 환경정보(config)와 레이블 정보를 같이 저장합니다.
이 외에도 필요한 정보가 있다면 같이 저장합니다.

## Save Model
torch.save({'model':trainer.model.state_dict(), 'config':config, 'labels':labels}, config.model_fn)

이렇게 train.py 파일에는 데이터 준비-모델 셋팅-훈련-모델 저장의 단계를 거치게됩니다.

다음 코드에서는 trainer.py가 어떻게 구성되어 있는지 보겠습니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 항목은 *(으)로 표시합니다