Link

학습 마친 모델을 실전 투입하기

학습을 마친 질의 응답 모델을 인퍼런스하는 과정을 실습합니다. 인퍼런스란 학습을 마친 모델로 실제 과제를 수행하는 행위 혹은 그 과정을 가리킵니다. 다시 말해 모델을 질의 응답이라는 실전에 투입하는 것입니다.

Table of contents

  1. 이번 실습의 목표
  2. 1단계 코랩 노트북 초기화하기
  3. 2단계 환경 설정하기
  4. 3단계 토크나이저 및 모델 불러오기
  5. 4단계 모델 출력값 만들고 후처리하기
  6. 5단계 웹 서비스 시작하기

이번 실습의 목표

이번 실습에서는 학습을 마친 질의 응답 모델을 가지고 웹 서비스(web service)를 만들어보려고 합니다. 대강의 개념도는 그림1과 같습니다. 지문과 질문을 받아 답변하는 웹 서비스인데요. 지문과 질문을 각각 토큰화한 뒤 모델 입력값으로 만들고 이를 모델에 태워 지문에서 정답이 어떤 위치에 나타나는지 확률값을 계산하게 만듭니다. 이후 약간의 후처리 과정을 거쳐 응답하게 만드는 방식입니다.

그림1 web service의 역할


1단계 코랩 노트북 초기화하기

이 튜토리얼에서 사용하는 코드를 모두 정리해 구글 코랩(colab) 노트북으로 만들어 두었습니다. 아래 링크를 클릭해 코랩 환경에서도 수행할 수 있습니다. 코랩 노트북 사용과 관한 자세한 내용은 1-4장 개발환경 설정 챕터를 참고하세요.

  • Open In Colab

위 노트북은 읽기 권한만 부여돼 있기 때문에 실행하거나 노트북 내용을 고칠 수가 없을 겁니다. 노트북을 복사해 내 것으로 만들면 이 문제를 해결할 수 있습니다.

위 링크를 클릭한 후 구글 아이디로 로그인한 뒤 메뉴 탭 하단의 드라이브로 복사를 클릭하면 코랩 노트북이 자신의 드라이브에 복사됩니다. 이 다음부터는 해당 노트북을 자유롭게 수정, 실행할 수 있게 됩니다. 별도의 설정을 하지 않았다면 해당 노트북은 내 드라이브/Colab Notebooks 폴더에 담깁니다.

한편 이 튜토리얼에서는 하드웨어 가속기가 따로 필요 없습니다. 그림2와 같이 코랩 화면의 메뉴 탭에서 런타임 > 런타임 유형 변경을 클릭합니다. 이후 그림3의 화면에서 None을 선택합니다.

그림2 하드웨어 가속기 설정 (1)

그림3 하드웨어 가속기 설정 (2)


2단계 환경 설정하기

코드1을 실행해 의존성 있는 패키지를 우선 설치합니다. 코랩 환경에서는 명령어 맨 앞에 느낌표(!)를 붙이면 파이썬이 아닌, 배쉬 명령을 수행할 수 있습니다.

코드1 의존성 패키지 설치

!pip install ratsnlp

이전 장에서 학습한 모델의 체크포인트는 구글 드라이브에 저장해 두었으므로 코드2를 실행해 코랩 노트북과 자신 구글 드라이브를 연동합니다.

코드2 구글드라이브 연동

from google.colab import drive
drive.mount('/gdrive', force_remount=True)

코드3을 실행하면 각종 설정을 할 수 있습니다.

코드3 인퍼런스 설정

from ratsnlp.nlpbook.qa import QADeployArguments
args = QADeployArguments(
    pretrained_model_name="beomi/kcbert-base",
    downstream_model_dir="/gdrive/My Drive/nlpbook/checkpoint-qa",
    max_seq_length=128,
    max_query_length=32,
)

각 인자(argument)의 역할과 내용은 다음과 같습니다.

  • pretrained_model_name : 이전 장에서 파인튜닝한 모델이 사용한 프리트레인 마친 언어모델 이름(단 해당 모델은 허깅페이스 라이브러리에 등록되어 있어야 합니다)
  • downstream_model_dir : 이전 장에서 파인튜닝한 모델의 체크포인트 저장 위치.
  • max_seq_length : 토큰 기준 입력 문장 최대 길이(지문, 질문 모두 포함).
  • max_query_length : 토큰 기준 질문 최대 길이.

3단계 토크나이저 및 모델 불러오기

코드4를 실행하면 토크나이저를 초기화할 수 있습니다.

코드4 토크나이저 로드

from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained(
    args.pretrained_model_name,
    do_lower_case=False,
)

코드5를 실행하면 이전 장에서 파인튜닝한 모델의 체크포인트를 읽어들입니다.

코드5 체크포인트 로드

import torch
fine_tuned_model_ckpt = torch.load(
    args.downstream_model_checkpoint_path,
    map_location=torch.device("cpu")
)

코드6을 수행하면 이전 장에서 파인튜닝한 모델이 사용한 프리트레인 마친 언어모델의 설정 값들을 읽어들일 수 있습니다. 이어 코드7을 실행하면 해당 설정값대로 BERT 모델을 초기화합니다.

코드6 BERT 설정 로드

from transformers import BertConfig
pretrained_model_config = BertConfig.from_pretrained(
    args.pretrained_model_name,
)

코드7 BERT 모델 초기화

from transformers import BertForQuestionAnswering
model = BertForQuestionAnswering(pretrained_model_config)

코드8을 수행하면 코드6에서 초기화한 BERT 모델에 코드5의 체크포인트를 읽어들이게 됩니다. 이어 코드9를 실행하면 모델이 평가 모드로 전환됩니다.

코드8 체크포인트 읽기

model.load_state_dict({k.replace("model.", ""): v for k, v in fine_tuned_model_ckpt['state_dict'].items()})

코드9 eval mode

model.eval()

4단계 모델 출력값 만들고 후처리하기

코드10은 인퍼런스 과정을 정의한 함수입니다. 질문(question)과 지문(context)을 입력받아 토큰화를 수행한 뒤 input_ids, attention_mask, token_type_ids를 만듭니다. 이들 입력값을 파이토치 텐서(tensor) 자료형으로 변환한 뒤 모델에 입력합니다. 모델 출력값은 소프트맥스 함수 적용 이전의 로짓(logit) 형태입니다.

마지막으로 모델 출력을 약간 후처리하여 정답 시작과 관련한 로짓(start_logits)의 최댓값에 해당하는 인덱스부터, 정답 끝과 관련한 로짓(end_logits)의 최댓값이 위치하는 인덱스까지에 해당하는 토큰을 이어붙여 pred_text으로 만듭니다. 로짓에 소프트맥스(softmax)를 취하더라도 최댓값은 바뀌지 않기 때문에 소프트맥스 적용은 생략했습니다.

코드10 inference

def inference_fn(question, context):
    if question and context:
        truncated_query = tokenizer.encode(
            question,
            add_special_tokens=False,
            truncation=True,
            max_length=args.max_query_length
       )
        inputs = tokenizer.encode_plus(
            text=truncated_query,
            text_pair=context,
            truncation="only_second",
            padding="max_length",
            max_length=args.max_seq_length,
            return_token_type_ids=True,
        )
        with torch.no_grad():
            outputs = model(**{k: torch.tensor([v]) for k, v in inputs.items()})
            start_pred = outputs.start_logits.argmax(dim=-1).item()
            end_pred = outputs.end_logits.argmax(dim=-1).item()
            pred_text = tokenizer.decode(inputs['input_ids'][start_pred:end_pred+1])
    else:
        pred_text = ""
    return {
        'question': question,
        'context': context,
        'answer': pred_text,
    }

5단계 웹 서비스 시작하기

코드10에서 정의한 인퍼런스 함수(inference_fn)을 가지고 코드11을 실행하면 웹 서비스를 띄울 수 있습니다. 파이썬 플라스크(flask)를 활용한 앱입니다.

코드11 웹 서비스

from ratsnlp.nlpbook.qa import get_web_service_app
app = get_web_service_app(inference_fn)
app.run()

코드11을 실행하면 그림2처럼 뜨는데요. 웹 브라우저로 http://17b289803156.ngrok.io에 접속한 뒤 각각의 질문, 지문을 입력하면 그림3, 그림4, 그림5와 같은 화면을 만날 수 있습니다. 단 실행할 때마다 이 주소가 변동하니 실제 접속할 때는 직접 코드10을 실행해 당시 출력된 주소로 접근해야 합니다.

그림2 colab에서 띄운 예시

그림3 colab에서 띄운 예시

그림4 colab에서 띄운 예시

그림5 colab에서 띄운 예시