{ "cells": [ { "cell_type": "markdown", "id": "88feef7d-68ef-47f3-9446-e7d3bab80db3", "metadata": {}, "source": [ "# Lab 1: Korean QnA (Question Answering) Training on AWS" ] }, { "cell_type": "markdown", "id": "8a562ce3-e032-489c-a5cc-03d9904e053f", "metadata": {}, "source": [ "\n", "## Introduction\n", "---\n", "\n", "본 모듈에서는 허깅페이스 트랜스포머(Hugging Face transformers) 라이브러리를 사용하여 한국어 질의응답(Korean QnA; Question Answering) 쌍을 훈련합니다. 질의응답은 본문에서 특정 질의에 대한 답변을 찾는 다운스트림 태스크입니다. 데이터는 본문(context)/질의(question) 쌍의 형태로 제공되며, 정답은 본문 내에 포함된 정답(answer) 문구의 시작과 끝을 색인화합니다. \n", "\n", "***[Note] SageMaker Studio Lab, SageMaker Studio, SageMaker 노트북 인스턴스, 또는 여러분의 로컬 머신에서 이 데모를 실행할 수 있습니다. SageMaker Studio Lab을 사용하는 경우 GPU를 활성화하세요.***\n", "\n", "***[주의] 본 데이터셋은 상업적인 목적으로 사용할 수 없습니다. 본 핸즈온은 연구/참고용으로만 활용하시고, 모델 훈련은 여러분만의 데이터셋을 직접 생성하셔야 합니다.***\n", "\n", "### References\n", "- Hugging Face Tutorial: https://huggingface.co/docs/transformers/tasks/question_answering\n", "- Fine-tuning with custom datasets: https://huggingface.co/transformers/v4.11.3/custom_datasets.html#question-answering-with-squad-2-0\n", "- KorQuAD 1.0: https://korquad.github.io/KorQuad%201.0/" ] }, { "cell_type": "markdown", "id": "fecc4a9e-0435-4356-8b4c-e34f7afdfe39", "metadata": {}, "source": [ "\n", "## 1. Setup Environments\n", "---\n", "\n", "### Import modules" ] }, { "cell_type": "code", "execution_count": 1, "id": "3a700dbe-e93a-4356-b6a1-f84a5081411b", "metadata": {}, "outputs": [], "source": [ "import os\n", "import sys\n", "import json\n", "import logging\n", "import argparse\n", "import torch\n", "from torch import nn\n", "import numpy as np\n", "import pandas as pd\n", "from tqdm import tqdm\n", "from sklearn.model_selection import train_test_split\n", "from transformers import (\n", " BertTokenizer, BertTokenizerFast, BertConfig, BertForTokenClassification, \n", " Trainer, TrainingArguments, set_seed\n", ")\n", "from transformers.trainer_utils import get_last_checkpoint\n", "\n", "logging.basicConfig(\n", " level=logging.INFO, \n", " format='[{%(filename)s:%(lineno)d} %(levelname)s - %(message)s',\n", " handlers=[\n", " logging.StreamHandler(sys.stdout)\n", " ]\n", ")\n", "logger = logging.getLogger(__name__)" ] }, { "cell_type": "markdown", "id": "ce03ca7f-41b6-4479-b4bc-965069f35b2d", "metadata": {}, "source": [ "### Argument parser" ] }, { "cell_type": "code", "execution_count": 2, "id": "dbe51e4c-095f-4c1e-9d76-745febcd35c2", "metadata": {}, "outputs": [], "source": [ "def parser_args(train_notebook=False):\n", " parser = argparse.ArgumentParser()\n", "\n", " # Default Setting\n", " parser.add_argument(\"--epochs\", type=int, default=3)\n", " parser.add_argument(\"--seed\", type=int, default=42)\n", " parser.add_argument(\"--train_batch_size\", type=int, default=32)\n", " parser.add_argument(\"--eval_batch_size\", type=int, default=64)\n", " parser.add_argument(\"--max_length\", type=int, default=384)\n", " parser.add_argument(\"--stride\", type=int, default=64)\n", " parser.add_argument(\"--warmup_steps\", type=int, default=100)\n", " parser.add_argument(\"--logging_steps\", type=int, default=100)\n", " parser.add_argument(\"--learning_rate\", type=str, default=5e-5)\n", " parser.add_argument(\"--disable_tqdm\", type=bool, default=False)\n", " parser.add_argument(\"--fp16\", type=bool, default=True)\n", " parser.add_argument(\"--tokenizer_id\", type=str, default='salti/bert-base-multilingual-cased-finetuned-squad')\n", " parser.add_argument(\"--model_id\", type=str, default='salti/bert-base-multilingual-cased-finetuned-squad')\n", " \n", " # SageMaker Container environment\n", " parser.add_argument(\"--output_data_dir\", type=str, default=os.environ[\"SM_OUTPUT_DATA_DIR\"])\n", " parser.add_argument(\"--model_dir\", type=str, default=os.environ[\"SM_MODEL_DIR\"])\n", " parser.add_argument(\"--n_gpus\", type=str, default=os.environ[\"SM_NUM_GPUS\"])\n", " parser.add_argument(\"--train_dir\", type=str, default=os.environ[\"SM_CHANNEL_TRAIN\"])\n", " parser.add_argument(\"--valid_dir\", type=str, default=os.environ[\"SM_CHANNEL_VALID\"])\n", " parser.add_argument('--chkpt_dir', type=str, default='/opt/ml/checkpoints') \n", "\n", " if train_notebook:\n", " args = parser.parse_args([])\n", " else:\n", " args = parser.parse_args()\n", " return args" ] }, { "cell_type": "code", "execution_count": 3, "id": "1b088282-0c75-4e8a-9e17-57949c5e03ba", "metadata": {}, "outputs": [], "source": [ "train_dir = 'qna_train'\n", "valid_dir = 'qna_valid'\n", "!rm -rf {train_dir} {valid_dir}\n", "os.makedirs(train_dir, exist_ok=True)\n", "os.makedirs(valid_dir, exist_ok=True) " ] }, { "cell_type": "markdown", "id": "76fc19c4-c663-420c-86f9-74d698582309", "metadata": {}, "source": [ "### Load Arguments\n", "\n", "주피터 노트북에서 곧바로 실행할 수 있도록 설정값들을 로드합니다. 물론 노트북 환경이 아닌 커맨드라인에서도 `cd scripts & python3 train.py` 커맨드로 훈련 스크립트를 실행할 수 있습니다." ] }, { "cell_type": "code", "execution_count": 4, "id": "51d77fcd-9a07-4343-b74f-917e18673657", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[{204499775.py:21} INFO - ***** Arguments *****\n", "[{204499775.py:22} INFO - epochs=3\n", "seed=42\n", "train_batch_size=32\n", "eval_batch_size=64\n", "max_length=384\n", "stride=64\n", "warmup_steps=100\n", "logging_steps=100\n", "learning_rate=5e-05\n", "disable_tqdm=False\n", "fp16=True\n", "tokenizer_id=salti/bert-base-multilingual-cased-finetuned-squad\n", "model_id=salti/bert-base-multilingual-cased-finetuned-squad\n", "output_data_dir=/home/ec2-user/SageMaker/sm-kornlp-usecases/question-answering/data\n", "model_dir=/home/ec2-user/SageMaker/sm-kornlp-usecases/question-answering/model\n", "n_gpus=4\n", "train_dir=/home/ec2-user/SageMaker/sm-kornlp-usecases/question-answering/qna_train\n", "valid_dir=/home/ec2-user/SageMaker/sm-kornlp-usecases/question-answering/qna_valid\n", "chkpt_dir=chkpt\n", "\n" ] } ], "source": [ "chkpt_dir = 'chkpt'\n", "model_dir = 'model'\n", "output_data_dir = 'data'\n", "num_gpus = torch.cuda.device_count()\n", "\n", "!rm -rf {chkpt_dir} {model_dir} {output_data_dir} \n", "\n", "if os.environ.get('SM_CURRENT_HOST') is None:\n", " is_sm_container = False\n", "\n", " #src_dir = '/'.join(os.getcwd().split('/')[:-1])\n", " src_dir = os.getcwd()\n", " os.environ['SM_MODEL_DIR'] = f'{src_dir}/{model_dir}'\n", " os.environ['SM_OUTPUT_DATA_DIR'] = f'{src_dir}/{output_data_dir}'\n", " os.environ['SM_NUM_GPUS'] = str(num_gpus)\n", " os.environ['SM_CHANNEL_TRAIN'] = f'{src_dir}/{train_dir}'\n", " os.environ['SM_CHANNEL_VALID'] = f'{src_dir}/{valid_dir}'\n", "\n", "args = parser_args(train_notebook=True) \n", "args.chkpt_dir = chkpt_dir\n", "logger.info(\"***** Arguments *****\")\n", "logger.info(''.join(f'{k}={v}\\n' for k, v in vars(args).items()))\n", "\n", "os.makedirs(args.chkpt_dir, exist_ok=True) \n", "os.makedirs(args.model_dir, exist_ok=True)\n", "os.makedirs(args.output_data_dir, exist_ok=True) " ] }, { "cell_type": "markdown", "id": "a4fcc91c-8c1f-4cae-a63a-791791600e00", "metadata": {}, "source": [ "
\n", "\n", "## 2. Preparation\n", "---\n", "\n", "### Dataset\n", "\n", "본 핸즈온에서 사용할 데이터셋은 KorQuAD (The Korean Question Answering Dataset) 1.0 입니다. 1,560개의 한국어 위키피디아 글들에 대해 66,181개의 질의응답 쌍(60,407개 훈련 데이터, 5,774개 검증 데이터)으로 구성된 데이터셋으로 SQuAD (Stanford Question Answering Dataset) 1.0과 동일한 포맷입니다.\n", "- KorQuad 1.0: https://korquad.github.io/KorQuad%201.0/" ] }, { "cell_type": "code", "execution_count": 5, "id": "cd57c85b-a8ec-4ade-bd9b-7137ca37ab4b", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "--2022-07-06 04:57:46-- https://korquad.github.io/dataset/KorQuAD_v1.0_train.json\n", "Resolving korquad.github.io (korquad.github.io)... 185.199.108.153, 185.199.110.153, 185.199.109.153, ...\n", "Connecting to korquad.github.io (korquad.github.io)|185.199.108.153|:443... connected.\n", "HTTP request sent, awaiting response... 200 OK\n", "Length: 38527475 (37M) [application/json]\n", "Saving to: ‘qna_train/KorQuAD_v1.0_train.json’\n", "\n", "100%[======================================>] 38,527,475 --.-K/s in 0.1s \n", "\n", "2022-07-06 04:57:46 (269 MB/s) - ‘qna_train/KorQuAD_v1.0_train.json’ saved [38527475/38527475]\n", "\n", "--2022-07-06 04:57:46-- https://korquad.github.io/dataset/KorQuAD_v1.0_dev.json\n", "Resolving korquad.github.io (korquad.github.io)... 185.199.111.153, 185.199.108.153, 185.199.110.153, ...\n", "Connecting to korquad.github.io (korquad.github.io)|185.199.111.153|:443... connected.\n", "HTTP request sent, awaiting response... 200 OK\n", "Length: 3881058 (3.7M) [application/json]\n", "Saving to: ‘qna_valid/KorQuAD_v1.0_dev.json’\n", "\n", "100%[======================================>] 3,881,058 --.-K/s in 0.02s \n", "\n", "2022-07-06 04:57:46 (219 MB/s) - ‘qna_valid/KorQuAD_v1.0_dev.json’ saved [3881058/3881058]\n", "\n" ] } ], "source": [ "!wget https://korquad.github.io/dataset/KorQuAD_v1.0_train.json -O {train_dir}/KorQuAD_v1.0_train.json\n", "!wget https://korquad.github.io/dataset/KorQuAD_v1.0_dev.json -O {valid_dir}/KorQuAD_v1.0_dev.json" ] }, { "cell_type": "markdown", "id": "00dd7ce1-72ee-40cf-8487-c487c39c33c6", "metadata": {}, "source": [ "
\n", "\n", "## 3. Construct Feature set\n", "---\n", "\n", "질의응답쌍에 대한 모델을 훈련하려면 토큰화된 본문/질문 쌍과 답변 범위를 알 수 있는 토큰 인덱스가 필요합니다. \n" ] }, { "cell_type": "markdown", "id": "346510d8-311b-4ca2-a876-d81d13249458", "metadata": {}, "source": [ "### Load raw data\n", "\n", "원본 데이터로부터 본문 및 질의응답 정보를 로드합니다." ] }, { "cell_type": "code", "execution_count": 6, "id": "733b628e-e9ef-490e-8ef1-7ccd31a0ae6c", "metadata": {}, "outputs": [], "source": [ "import json\n", "from pathlib import Path\n", "\n", "def read_squad(path):\n", " path = Path(path)\n", " with open(path, 'rb') as f:\n", " squad_dict = json.load(f)\n", "\n", " contexts = []\n", " questions = []\n", " answers = []\n", " for group in squad_dict['data']:\n", " for passage in group['paragraphs']:\n", " context = passage['context']\n", " for qa in passage['qas']:\n", " question = qa['question']\n", " for answer in qa['answers']:\n", " contexts.append(context)\n", " questions.append(question)\n", " answers.append(answer)\n", "\n", " return contexts, questions, answers\n", "\n", "train_contexts, train_questions, train_answers = read_squad(f'{train_dir}/KorQuAD_v1.0_train.json')\n", "val_contexts, val_questions, val_answers = read_squad(f'{valid_dir}/KorQuAD_v1.0_dev.json')" ] }, { "cell_type": "markdown", "id": "e0eb36a9-7c8d-4319-8e8b-7d8758e83bcd", "metadata": {}, "source": [ "### Add End index\n", "\n", "문장에서 답변이 끝나는 문자 위치를 계산합니다. 때로, 답변이 한두 글짜씩 차이가 나는 경우가 있으므로 이에 대한 예외처리를 추가합니다." ] }, { "cell_type": "code", "execution_count": 7, "id": "73a65ac2-6234-4cf4-9a77-550ceb94d04b", "metadata": {}, "outputs": [], "source": [ "def add_end_idx(answers, contexts):\n", " for answer, context in zip(answers, contexts):\n", " gold_text = answer['text']\n", " start_idx = answer['answer_start']\n", " end_idx = start_idx + len(gold_text)\n", "\n", " # sometimes squad answers are off by a character or two – fix this\n", " if context[start_idx:end_idx] == gold_text:\n", " answer['answer_end'] = end_idx\n", " elif context[start_idx-1:end_idx-1] == gold_text:\n", " answer['answer_start'] = start_idx - 1\n", " answer['answer_end'] = end_idx - 1 # When the gold label is off by one character\n", " elif context[start_idx-2:end_idx-2] == gold_text:\n", " answer['answer_start'] = start_idx - 2\n", " answer['answer_end'] = end_idx - 2 # When the gold label is off by two characters\n", " return answers\n", "\n", "train_answers = add_end_idx(train_answers, train_contexts)\n", "val_answers = add_end_idx(val_answers, val_contexts)" ] }, { "cell_type": "markdown", "id": "7ffaa50c-3f86-45ae-89a7-edb17b9e2459", "metadata": {}, "source": [ "### Tokenization \n", "\n", "연어 처리 모델을 훈련하려면, 토큰화(Tokenization)를 통해 말뭉치(corpus; 자연어 처리를 위한 대량의 텍스트 데이터)를 토큰 시퀀스로 나누는 과정이 필요합니다. BERT 이전의 자연어 처리 모델은 주로 도메인 전문가들이 직접 토큰화해놓은 토크아니저(Mecab, Kkma 등)들을 사용했지만, BERT를 훈련하기 위한 토크나이저는 도메인 지식 필요 없이 말뭉치에서 자주 등장하는 서브워드(subword)를 토큰화합니다. GPT 기반 모델은 BPE(Byte-pair Encoding)라는 통계적 기법을 사용하며, BERT 및 ELECTRA 기반 모델은 BPE와 유사한 Wordpiece를 토크나이저로 사용합니다." ] }, { "cell_type": "code", "execution_count": 8, "id": "26b2d33e-ff1f-42a5-bc0a-bb7117a3f9a7", "metadata": {}, "outputs": [], "source": [ "from transformers import AutoTokenizer, BertTokenizerFast\n", "#tokenizer = AutoTokenizer.from_pretrained(args.tokenizer_id)\n", "tokenizer = BertTokenizerFast.from_pretrained(args.tokenizer_id)\n", "\n", "train_encodings = tokenizer(\n", " train_contexts, \n", " train_questions, \n", " truncation=True, \n", " max_length=args.max_length,\n", " stride=args.stride, \n", " padding=\"max_length\"\n", ")\n", "val_encodings = tokenizer(\n", " val_contexts, \n", " val_questions, \n", " truncation=True, \n", " max_length=args.max_length,\n", " stride=args.stride, \n", " padding=\"max_length\"\n", ")" ] }, { "cell_type": "markdown", "id": "aea9ecd3-01fd-44be-be3b-27810b29f95c", "metadata": {}, "source": [ "### Covert to token positions for answers\n", "\n", "답변의 시작/끝 인덱스를 토큰 시작/끝 인덱스로 변환합니다. 원래의 문자열에서 토큰 인덱스를 반환하는 `char_to_token()` 메소드로 편하게 수행할 수 있습니다." ] }, { "cell_type": "code", "execution_count": 9, "id": "ee29c4b6-eba7-4440-b66c-9e560e0bc1a9", "metadata": {}, "outputs": [], "source": [ "def add_token_positions(encodings, answers, tokenizer):\n", " start_positions = []\n", " end_positions = []\n", " for i in range(len(answers)):\n", " start_positions.append(encodings.char_to_token(i, answers[i]['answer_start']))\n", " end_positions.append(encodings.char_to_token(i, answers[i]['answer_end'] - 1))\n", " # if None, the answer passage has been truncated\n", " if start_positions[-1] is None:\n", " start_positions[-1] = tokenizer.model_max_length\n", " if end_positions[-1] is None:\n", " end_positions[-1] = tokenizer.model_max_length\n", " \n", " # If the start and end positions are greater than max_length, both must be changed to max_length.\n", " if start_positions[-1] is None or start_positions[-1] > tokenizer.model_max_length:\n", " start_positions[-1] = tokenizer.model_max_length\n", " \n", " if end_positions[-1] is None or end_positions[-1] > tokenizer.model_max_length:\n", " end_positions[-1] = tokenizer.model_max_length\n", "\n", " encodings.update({'start_positions': start_positions, 'end_positions': end_positions}) \n", " return encodings\n", "\n", "train_encodings = add_token_positions(train_encodings, train_answers, tokenizer)\n", "val_encodings = add_token_positions(val_encodings, val_answers, tokenizer)" ] }, { "cell_type": "markdown", "id": "6e44a8ac-c722-4088-9c1c-cb05366a2167", "metadata": {}, "source": [ "### Custom Dataset\n", "\n", "훈련/검증 시에 사용할 커스텀 데이터셋을 생성하기 위한 클래스를 생성합니다. BERT 질의응답쌍 모델은 보통 아래의 입력값을 사용합니다.\n", "- `input_ids`: 문장이 인덱스(특정 vocab에 매핑하는 숫자값)로 구성된 토큰 시퀀스로 변환된 결괏값\n", "- `attention_mask` : 해당 토큰이 패딩 토큰인지, 아닌지를 마스킹\n", "- `token_type_ids`: 세그먼트 (두 문장 입력 시, 첫번째 문장인지 아닌지를 마스킹)\n", "- `start_positions`: 답변이 시작하는 토큰 위치\n", "- `end_positions`: 답변이 끝나는 토큰 위치" ] }, { "cell_type": "code", "execution_count": 10, "id": "dfca2c12-3b5b-483a-86c7-bbfe462eaf0c", "metadata": {}, "outputs": [], "source": [ "class SquadDataset(torch.utils.data.Dataset):\n", " def __init__(self, encodings):\n", " self.encodings = encodings\n", "\n", " def __getitem__(self, idx):\n", " return {key: torch.tensor(val[idx]) for key, val in self.encodings.items()}\n", "\n", " def __len__(self):\n", " return len(self.encodings.input_ids)\n", "\n", "train_dataset = SquadDataset(train_encodings)\n", "val_dataset = SquadDataset(val_encodings)" ] }, { "cell_type": "markdown", "id": "7fbbc4dc-a38b-4407-b411-2a2870d8561c", "metadata": {}, "source": [ "
\n", "\n", "## 4. Training\n", "---\n", "\n", "### Training Preparation\n", "\n", "본 핸즈온은 허깅페이스의 트랜스포머 라이브러리에 포함된 BertForQuestionAnswering 모델을 사용합니다." ] }, { "cell_type": "code", "execution_count": 11, "id": "f87dd1c8-0f3f-45dd-b5ab-4ca975778b16", "metadata": {}, "outputs": [], "source": [ "from transformers import (\n", " BertForQuestionAnswering,\n", " Trainer, TrainingArguments, set_seed\n", ")\n", "from transformers.trainer_utils import get_last_checkpoint\n", "\n", "model = BertForQuestionAnswering.from_pretrained(args.model_id)\n", "training_args = TrainingArguments(\n", " output_dir='chkpt',\n", " overwrite_output_dir=True if get_last_checkpoint('chkpt') is not None else False,\n", " num_train_epochs=args.epochs,\n", " per_device_train_batch_size=args.train_batch_size, \n", " per_device_eval_batch_size=args.eval_batch_size, \n", " warmup_steps=args.warmup_steps, \n", " weight_decay=0.01, \n", " logging_dir=\"logs\", \n", " logging_steps=args.logging_steps,\n", " learning_rate=float(args.learning_rate),\n", " save_total_limit=5,\n", " save_strategy=\"epoch\",\n", " fp16=args.fp16,\n", " gradient_accumulation_steps=4,\n", " #evaluation_strategy=\"steps\",\n", ")" ] }, { "cell_type": "markdown", "id": "c9aee250-b610-49bf-b15d-41bb54ff2c0e", "metadata": {}, "source": [ "훈련을 수행하기 위한 `Trainer` 클래스를 인스턴스화합니다." ] }, { "cell_type": "code", "execution_count": 12, "id": "3fd7b703-aff7-4d90-ae13-2474c64ff303", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "Using cuda_amp half precision backend\n" ] } ], "source": [ "trainer = Trainer(\n", " model=model, # the instantiated 🤗 Transformers model to be trained\n", " args=training_args, # training arguments, defined above\n", " train_dataset=train_dataset, # training dataset\n", " eval_dataset=val_dataset # evaluation dataset\n", ")" ] }, { "cell_type": "markdown", "id": "e83de449-0749-4e97-ae08-8e1612559a74", "metadata": {}, "source": [ "### Training\n", "훈련을 수행합니다. 딥러닝 기반 자연어 처리 모델 훈련에는 GPU가 필수이며, 본격적인 훈련을 위해서는 멀티 GPU 및 분산 훈련을 권장합니다. 만약 멀티 GPU가 장착되어 있다면 Trainer에서 총 배치 크기 = 배치 크기 x GPU 개수로 지정한 다음 데이터 병렬화를 자동으로 수행합니다." ] }, { "cell_type": "code", "execution_count": 13, "id": "1558ebc8-325c-4053-9f1c-a713abdfe337", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "/home/ec2-user/anaconda3/envs/pytorch_p38/lib/python3.8/site-packages/transformers/optimization.py:306: FutureWarning: This implementation of AdamW is deprecated and will be removed in a future version. Use the PyTorch implementation torch.optim.AdamW instead, or set `no_deprecation_warning=True` to disable this warning\n", " warnings.warn(\n", "***** Running training *****\n", " Num examples = 60407\n", " Num Epochs = 3\n", " Instantaneous batch size per device = 32\n", " Total train batch size (w. parallel, distributed & accumulation) = 512\n", " Gradient Accumulation steps = 4\n", " Total optimization steps = 354\n", "/home/ec2-user/anaconda3/envs/pytorch_p38/lib/python3.8/site-packages/torch/nn/parallel/_functions.py:68: UserWarning: Was asked to gather along dimension 0, but all input tensors were scalars; will instead unsqueeze and return a vector.\n", " warnings.warn('Was asked to gather along dimension 0, but all '\n" ] }, { "data": { "text/html": [ "\n", "
\n", " \n", " \n", " [354/354 17:44, Epoch 3/3]\n", "
\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
StepTraining Loss
1001.463100
2000.481400
3000.364300

" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stderr", "output_type": "stream", "text": [ "Saving model checkpoint to chkpt/checkpoint-118\n", "Configuration saved in chkpt/checkpoint-118/config.json\n", "Model weights saved in chkpt/checkpoint-118/pytorch_model.bin\n", "/home/ec2-user/anaconda3/envs/pytorch_p38/lib/python3.8/site-packages/torch/nn/parallel/_functions.py:68: UserWarning: Was asked to gather along dimension 0, but all input tensors were scalars; will instead unsqueeze and return a vector.\n", " warnings.warn('Was asked to gather along dimension 0, but all '\n", "Saving model checkpoint to chkpt/checkpoint-236\n", "Configuration saved in chkpt/checkpoint-236/config.json\n", "Model weights saved in chkpt/checkpoint-236/pytorch_model.bin\n", "/home/ec2-user/anaconda3/envs/pytorch_p38/lib/python3.8/site-packages/torch/nn/parallel/_functions.py:68: UserWarning: Was asked to gather along dimension 0, but all input tensors were scalars; will instead unsqueeze and return a vector.\n", " warnings.warn('Was asked to gather along dimension 0, but all '\n", "Saving model checkpoint to chkpt/checkpoint-354\n", "Configuration saved in chkpt/checkpoint-354/config.json\n", "Model weights saved in chkpt/checkpoint-354/pytorch_model.bin\n", "\n", "\n", "Training completed. Do not forget to share your model on huggingface.co/models =)\n", "\n", "\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ "CPU times: user 41min 50s, sys: 5min 59s, total: 47min 50s\n", "Wall time: 17min 54s\n" ] } ], "source": [ "%%time\n", "# train model\n", "if get_last_checkpoint(args.chkpt_dir) is not None:\n", " logger.info(\"***** Continue Training *****\")\n", " last_checkpoint = get_last_checkpoint(args.chkpt_dir)\n", " trainer.train(resume_from_checkpoint=last_checkpoint)\n", "else:\n", " trainer.train()" ] }, { "cell_type": "code", "execution_count": 14, "id": "64101d48-abd8-4c07-82eb-6b04666780c4", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "tokenizer config file saved in /home/ec2-user/SageMaker/sm-kornlp-usecases/question-answering/model/tokenizer_config.json\n", "Special tokens file saved in /home/ec2-user/SageMaker/sm-kornlp-usecases/question-answering/model/special_tokens_map.json\n", "Saving model checkpoint to /home/ec2-user/SageMaker/sm-kornlp-usecases/question-answering/model\n", "Configuration saved in /home/ec2-user/SageMaker/sm-kornlp-usecases/question-answering/model/config.json\n", "Model weights saved in /home/ec2-user/SageMaker/sm-kornlp-usecases/question-answering/model/pytorch_model.bin\n" ] } ], "source": [ "tokenizer.save_pretrained(args.model_dir) \n", "trainer.save_model(args.model_dir)" ] }, { "cell_type": "markdown", "id": "aacbc71d-bea9-48c2-bcf2-3b0895668ffc", "metadata": {}, "source": [ "
\n", "\n", "## 5. Evaluation\n", "---\n", "\n", "평가를 수행합니다. 질의응답 태스크의 평가는 EM(Exact Match)와 F1 score를 사용합니다.\n", "- EM: 예측한 정답의 토큰이 정확히 일치하면 1, 그렇지 않으면 0\n", "- F1: 예측한 정답과 실제 정답의 중첩 토큰으로 스코어 산출\n", "\n", "Reference: https://qa.fastforwardlabs.com/no%20answer/null%20threshold/bert/distilbert/exact%20match/f1/robust%20predictions/2020/06/09/Evaluating_BERT_on_SQuAD.html" ] }, { "cell_type": "code", "execution_count": 15, "id": "5ec00b58-ade4-4547-99fb-dc5cd5d2eb5f", "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "100%|██████████| 140/140 [00:01<00:00, 116.52it/s]\n", "100%|██████████| 5774/5774 [01:00<00:00, 96.18it/s]" ] }, { "name": "stdout", "output_type": "stream", "text": [ "[{3242478017.py:4} INFO - EM = 0.627641149982681\n", "[{3242478017.py:5} INFO - F1 = 0.6994331158010804\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "\n" ] } ], "source": [ "# compute metrics (EM and F1)\n", "from scripts.evaluate import get_metrics_korquadv1, get_prediction\n", "em, f1 = get_metrics_korquadv1(args.valid_dir, tokenizer, model)\n", "logger.info(f\"EM = {em}\")\n", "logger.info(f\"F1 = {f1}\")" ] }, { "cell_type": "markdown", "id": "526d05ac-5abd-4b56-b06b-9ea1780a05cf", "metadata": {}, "source": [ "### Example\n", "여러분만의 샘플 문장을 만들어서 자유롭게 추론을 수행해 보세요." ] }, { "cell_type": "code", "execution_count": 16, "id": "8de3787e-1aa7-4457-bffb-131ce58442e3", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'score': 0.9967060089111328, 'start': 29, 'end': 36, 'answer': '카카오게임즈가'}\n", "{'score': 0.8259254097938538, 'start': 263, 'end': 269, 'answer': '아마존 S3'}\n", "{'score': 0.879924476146698, 'start': 514, 'end': 521, 'answer': '아마존 오로라'}\n", "{'score': 0.9202089905738831, 'start': 626, 'end': 636, 'answer': '아마존 세이지메이커'}\n" ] } ], "source": [ "from transformers import pipeline\n", "nlp = pipeline(\"question-answering\", model=model, tokenizer=tokenizer, device=0)\n", "\n", "context = r\"\"\"\n", "아마존웹서비스(AWS)는 카카오 게임 전문 계열사 카카오게임즈가 자사 머신러닝(ML), 데이터베이스(DB) 및 데이터 분석 등 서비스를 통해 사용자 경험을 제고했다고 7일 밝혔다.\n", "AWS는 카카오게임즈가 AWS클라우드 역량을 활용해 게임 데이터 분석 솔루션을 실행하고, 대량의 게임 데이터와 설치 건수, 사용자 유지율과 같은 성과 지표를 분석하고 있다고 설명했다. \n", "현재 카카오게임즈는 폭증하는 데이터를 저장·분석하기 위한 방법으로 클라우드 오브젝트 스토리지 서비스 '아마존 S3(Amazon Simple Storage Service)' 기반 데이터 레이크(Data Lake)를 구축했다. 또 데이터 분석을 용이하게 해주는 대화형 쿼리 서비스 '아마존 아테나(Amazon Athena)'를 도입해 데이터 레이크로부터 게임 데이터를 통합하고, 게임 사용자 행동과 관련된 인사이트를 확보 중이다. \n", "이를 통해 카카오게임즈는 게임 봇을 탐지하고 제거하는 방식으로 사용자 경험을 제고했다. 또한 관계형 데이터베이스 서비스 '아마존 오로라(Amazon Aurora)'를 활용해 게임 내 구매와 같은 대규모 데이터베이스 거래를 처리하고 있다. 이밖에도 카카오게임즈는 ML 모델 구축, 교육 및 배포를 위한 완전 관리형 서비스 '아마존 세이지메이커(Amazon SageMaker)'를 활용할 예정이다.\n", "\"\"\"\n", "\n", "print(nlp(question=\"카카오 게임 전문 계열사는?\", context=context))\n", "print(nlp(question=\"AWS의 클라우드 오브젝트 스토리지 서비스는?\", context=context))\n", "print(nlp(question=\"AWS의 관계형 데이터베이스 서비스는?\", context=context))\n", "print(nlp(question=\"AWS의 ML 모델 완전 관리형 서비스는?\", context=context))" ] } ], "metadata": { "kernelspec": { "display_name": "conda_pytorch_p38", "language": "python", "name": "conda_pytorch_p38" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.12" } }, "nbformat": 4, "nbformat_minor": 5 }