├── .env.template ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ └── add_new_feature.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CITATION.cff ├── README.md ├── assets ├── image │ ├── data_types.png │ ├── eval_geval.png │ ├── eval_human.png │ ├── fe_be.jpg │ ├── kullm.png │ ├── mary_logo.png │ ├── model_architecture.jpg │ ├── project_architecture.png │ ├── qlora.png │ ├── smoked_salmon.png │ ├── tech_stack.jpg │ └── timeline.jpg └── video │ ├── demo.gif │ └── demo_v0.gif ├── data ├── data_process_notebooks │ ├── data_concat.ipynb │ ├── eval_human.ipynb │ ├── evaluation.ipynb │ ├── preprocess.ipynb │ ├── prompt_to_eval.py │ └── synthetic_data_process.ipynb ├── eda │ ├── eda.ipynb │ └── eda.py ├── generate_data.py ├── input_prompt.txt.template └── prompt │ ├── template_1.txt │ ├── template_2.txt │ ├── template_3.txt │ ├── template_4.txt │ ├── template_5.txt │ ├── template_6.txt │ └── template_7.txt ├── docs ├── [최종]NLP_12조_내마리 내 마음을 들여다 보는 챗봇.pdf └── 최종프로젝트_NLP_팀 리포트(12조).pdf ├── finetuning ├── config.yaml ├── inference.py ├── langchain_inference.ipynb ├── merge_lora.py ├── mk_geval_prompts.py ├── templates │ ├── kullm_origin.json │ ├── kullm_test.json │ └── multi.json ├── test.sh ├── test │ ├── result │ │ └── .gitkeep │ └── test_dataset │ │ ├── test_multi.csv │ │ └── test_single.csv ├── train.py └── utils │ ├── __init__.py │ ├── arguments.py │ ├── callbacks.py │ ├── compute_metrics.py │ ├── data_loader.py │ └── prompter.py ├── poetry.lock ├── pyproject.toml └── server ├── README.md ├── backend ├── app.py ├── config │ └── settings.py ├── mary.py ├── models.py └── welcome_msg.txt └── frontend ├── .gitignore ├── package.json ├── public ├── favicon.ico ├── index.html ├── manifest.json ├── mary.svg └── robots.txt └── src ├── App.css ├── App.js ├── App.test.js ├── index.css ├── index.js ├── message.js ├── randomString.js ├── reportWebVitals.js └── setupTests.js /.env.template: -------------------------------------------------------------------------------- 1 | # data/ & server/backend 2 | OPENAI_API_KEY=sk- 3 | 4 | # finetuning/ 5 | HUGGINGFACEHUB_API_TOKEN=hf_ 6 | WANDB_API_KEY= 7 | 8 | # server/backend 9 | HOST= 10 | PORT= 11 | REACT_PORT= 12 | HOST_IP= 13 | 14 | DB_URL= 15 | DB_NAME= 16 | COLLECTION= 17 | USERNAME= 18 | PASSWORD= 19 | 20 | MODEL_NAME= 21 | MODEL_PATH= 22 | EMBEDDING_SIZE= 23 | VECTOR_INDEX_COLLECTION= 24 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.ipynb linguist-generated -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/add_new_feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: add new function 3 | about: when you propose new function, please make an issue with this template! 4 | title: "[FEAT]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Background 11 | - 12 | - 13 | 14 | ## To Do 15 | - [ ] 16 | - [ ] 17 | 18 | ## See Also 19 | - -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | - 3 | 4 | ## Change Log 5 | - 6 | 7 | ## To Reviewer 8 | - 9 | 10 | ## Issue Tags 11 | - Closed | Fixed: # 12 | - See also: # -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .venv/ 3 | .cache/ 4 | .vscode/ 5 | 6 | *.log 7 | *.out 8 | *.csv 9 | 10 | __pycache__ 11 | cache* 12 | 13 | model/ 14 | lora-finetuned/ 15 | lora_merged_model/ 16 | configs/ 17 | outputs/ 18 | output_logs/ 19 | wandb/ 20 | notebooks/ 21 | node_modules/ 22 | 23 | __init__.py 24 | test*.json 25 | *prompt*.txt 26 | simple_inference.ipynb 27 | langchain_inference_*.ipynb 28 | inference_*.py 29 | generation.py 30 | config*.yaml 31 | generate*.sh 32 | test*.sh 33 | train_nohup_*.sh 34 | finetune_*.sh 35 | merge_lora*.sh 36 | .env 37 | package-lock.json -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | @misc{kullm, 2 | author = {NLP & AI Lab and Human-Inspired AI research}, 3 | title = {KULLM: Korea University Large Language Model Project}, 4 | year = {2023}, 5 | publisher = {GitHub}, 6 | journal = {GitHub repository}, 7 | howpublished = {\url{https://github.com/nlpai-lab/kullm}}, 8 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 내마리(MyMary): 내 마음을 들여다 보는 챗봇 2 | 챗봇과의 대화를 통해 ‘나’에 대해 더 잘 알아갈 수 있는 챗봇, `내마리(MyMary)` 서비스입니다. 3 | `내마리(MyMary)`는 4 | - 사용자의 채팅 속에서 **맥락을 파악**하고 사용자에게 **따뜻한 공감**을 제공하며 5 | - 질문 또는 생각 정리의 형식으로 사용자의 채팅에 대한 답변을 마무리함으로써 6 | - 사용자가 **공감을 기반으로 챗봇과 상호작용**할 수 있게 되어, 궁극적으로 **사용자 자신과 상호작용 하는 (= 내 마음을 들여다보는) 경험**을 제공합니다. 7 | 8 | [[**마리와 대화하기**](http://mymary.o-r.kr:30007)] 9 | 10 | ## 1. 프로젝트 데모 Project Demo 11 | demo 12 | 13 | ## 2. 팀 & 역할 Team & Roles 14 | |공통|문지혜|박경택|박지은|송인서|윤지환| 15 | |:---:|:---:|:---:|:---:|:---:|:---:| 16 | |smoked_salmon|||||| 17 | ||[![GitHub Badge](https://img.shields.io/badge/-GitHub-black?style=flat-square&logo=github&link=https://github.com/jihye-moon)](https://github.com/jihye-moon)|[![GitHub Badge](https://img.shields.io/badge/-GitHub-black?style=flat-square&logo=github&link=https://github.com/afterthougt)](https://github.com/afterthougt)|[![GitHub Badge](https://img.shields.io/badge/-GitHub-black?style=flat-square&logo=github&link=https://github.com/iamzieun)](https://github.com/iamzieun)|[![GitHub Badge](https://img.shields.io/badge/-GitHub-black?style=flat-square&logo=github&link=https://github.com/fortunetiger)](https://github.com/fortunetiger)|[![GitHub Badge](https://img.shields.io/badge/-GitHub-black?style=flat-square&logo=github&link=https://github.com/ohilikeit)](https://github.com/ohilikeit)| 18 | |데이터 생성
데이터 검수
모델 별 실험|평가 지표 리서치
평가 전략 설계|프롬프트 엔지니어링
LangChain 연동
발표|개발환경 구성
베이스라인 작성
데이터 EDA
발표|FE/BE 구현
DB 연결
LangChain 연동|**PM**
베이스라인 작성
데이터 취합
데이터 전처리
LangChain 연동| 19 | 20 | 21 | ## 3. 실행 방법 How-to-Run 22 | ### Dependency Management 23 | ```bash 24 | poetry shell 25 | ``` 26 | 27 | ### Finetuning 28 | `finetuning` 디렉토리에서 29 | ```bash 30 | python train.py 31 | ``` 32 | 33 | ### Merge QLoRA layer into LLM backbone 34 | `finetuning` 디렉토리에서 35 | ```bash 36 | python merge_lora.py 37 | ``` 38 | 39 | ### Inference 40 | `finetuning` 디렉토리에서 41 | ```bash 42 | python inference.py 43 | ``` 44 | 45 | ### Server 46 | #### Backend 47 | `server/backend` 디렉토리에서 48 | ```bash 49 | python app.py 50 | ``` 51 | 52 | #### Frontend 53 | `server/frontend` 디렉토리에서 54 | ```bash 55 | npm install 56 | ``` 57 | and 58 | ```bash 59 | npm start 60 | ``` 61 | 62 | ## 4. 프로젝트 아키텍처 Project Architecture 63 | ![project_architecture](assets/image/project_architecture.png) 64 | 65 | ## 5. 데이터 Data 66 | data_types 67 | 68 | - [HuggingFace 데이터셋 리포지토리](https://huggingface.co/datasets/ohilikeit/empathetic_dialogues_kr) 69 | - 공감형 대화 공개 데이터셋은 single-turn 데이터 8,194개와 multi-turn 데이터 18,668개로 구성됩니다. 70 | - 사용자의 채팅에 대한 응답인 챗봇의 채팅은 '공감형 반응 - 질문' 형태를 띱니다. 71 | - Finetuning 시 모델의 입력은 프롬프트 템플릿으로 감싸주었으며, 데이터 생성 및 파인튜닝, 추론 등에서 사용한 프롬프트 템플릿은 [`data/prompt/`](https://github.com/boostcampaitech5/level3_nlp_finalproject-nlp-12/data/prompt) 디렉토리를 참고해주세요. 72 | 73 | ## 6. 모델 Model 74 | ### Backbone 75 | #### KULLM-Polyglot-Ko-12.8B 76 | ![kullm](assets/image/kullm.png) 77 | 78 | Polyglot-Ko-12.8B 모델을 backbone으로 하여 15만 2천여 건의 싱글-턴 대화로 finetuning한 모델입니다. 79 | 80 | ### QLoRA layer 81 | qlora 82 | 83 | LoRA 기법에 4-bit quantization, double quantization, paged optimizer 등의 방법을 사용하여 VRAM 32GB single GPU에서 full finetuning이 가능하게 했습니다. 84 | 85 | ## 7. 평가 Evaluation 86 | 평가를 위해 비교한 모델은 한국어 LLM 3가지(모델 파라미터수 7B, ~13B)와 현재 서비스 중인 GPT-3.5 모델(모델 파라미터 수 175B)입니다. 87 | 88 | ### G-Eval 89 | G-eval 논문을 참고하여 6가지 항목을 선정하고, 추가적으로 (1) 공감 능력, (2) 대화 유도 항목을 추가하여 평가하였습니다. 90 | ![g-eval](assets/image/eval_geval.png) 91 | 92 | ### Human Evaluation 93 | 평가 항목은 크게 (1) 기초 능력, (2) 공감, (3) 맥락 유지, (4) 질문 항목으로 나뉩니다. 각각의 항목은 다시 두세 항목으로 세분류하여 평가를 진행하였습니다. 94 | ![human_eval](assets/image/eval_human.png) 95 | 96 | ## 8. 서빙 Serving 97 | 서빙 파트의 자세한 내용은 `server/` 디렉토리의 [README](https://github.com/boostcampaitech5/level3_nlp_finalproject-nlp-12/server/README.md)를 참고해주세요. 98 | 99 | ## 9. 공개 링크 Public Links 100 | [Presentation](docs/[최종]NLP_12조_내마리%20내%20마음을%20들여다%20보는%20챗봇.pdf) 101 | 102 | ## 10. 참고문헌 Reference 103 | - [A Technical Report for Polyglot-Ko: Open-Source Large-Scale Korean Language Models](https://arxiv.org/abs/2306.02254) 104 | - [GPT-NeoX-20B: An Open-Source Autoregressive Language Model](https://arxiv.org/abs/2204.06745) 105 | - [Few-Shot Parameter-Efficient Fine-Tuning is Better and Cheaper than In-Context Learning](https://arxiv.org/abs/2205.05638) 106 | - [LoRA: Low-Rank Adapatation of Large Language Models](https://arxiv.org/abs/2106.09685) 107 | - [QLoRA: Efficient Finetuning of Quantized LLMs](https://arxiv.org/abs/2305.14314) 108 | - [G-Eval: NLG Evaluation using GPT-4 with Better Human Alignment](https://arxiv.org/abs/2303.16634) -------------------------------------------------------------------------------- /assets/image/data_types.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampaitech5/level3_nlp_finalproject-nlp-12/46c8b4ccbc9ca85087ea0c7aa7fdf3f9d8fad015/assets/image/data_types.png -------------------------------------------------------------------------------- /assets/image/eval_geval.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampaitech5/level3_nlp_finalproject-nlp-12/46c8b4ccbc9ca85087ea0c7aa7fdf3f9d8fad015/assets/image/eval_geval.png -------------------------------------------------------------------------------- /assets/image/eval_human.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampaitech5/level3_nlp_finalproject-nlp-12/46c8b4ccbc9ca85087ea0c7aa7fdf3f9d8fad015/assets/image/eval_human.png -------------------------------------------------------------------------------- /assets/image/fe_be.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampaitech5/level3_nlp_finalproject-nlp-12/46c8b4ccbc9ca85087ea0c7aa7fdf3f9d8fad015/assets/image/fe_be.jpg -------------------------------------------------------------------------------- /assets/image/kullm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampaitech5/level3_nlp_finalproject-nlp-12/46c8b4ccbc9ca85087ea0c7aa7fdf3f9d8fad015/assets/image/kullm.png -------------------------------------------------------------------------------- /assets/image/mary_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampaitech5/level3_nlp_finalproject-nlp-12/46c8b4ccbc9ca85087ea0c7aa7fdf3f9d8fad015/assets/image/mary_logo.png -------------------------------------------------------------------------------- /assets/image/model_architecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampaitech5/level3_nlp_finalproject-nlp-12/46c8b4ccbc9ca85087ea0c7aa7fdf3f9d8fad015/assets/image/model_architecture.jpg -------------------------------------------------------------------------------- /assets/image/project_architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampaitech5/level3_nlp_finalproject-nlp-12/46c8b4ccbc9ca85087ea0c7aa7fdf3f9d8fad015/assets/image/project_architecture.png -------------------------------------------------------------------------------- /assets/image/qlora.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampaitech5/level3_nlp_finalproject-nlp-12/46c8b4ccbc9ca85087ea0c7aa7fdf3f9d8fad015/assets/image/qlora.png -------------------------------------------------------------------------------- /assets/image/smoked_salmon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampaitech5/level3_nlp_finalproject-nlp-12/46c8b4ccbc9ca85087ea0c7aa7fdf3f9d8fad015/assets/image/smoked_salmon.png -------------------------------------------------------------------------------- /assets/image/tech_stack.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampaitech5/level3_nlp_finalproject-nlp-12/46c8b4ccbc9ca85087ea0c7aa7fdf3f9d8fad015/assets/image/tech_stack.jpg -------------------------------------------------------------------------------- /assets/image/timeline.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampaitech5/level3_nlp_finalproject-nlp-12/46c8b4ccbc9ca85087ea0c7aa7fdf3f9d8fad015/assets/image/timeline.jpg -------------------------------------------------------------------------------- /assets/video/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampaitech5/level3_nlp_finalproject-nlp-12/46c8b4ccbc9ca85087ea0c7aa7fdf3f9d8fad015/assets/video/demo.gif -------------------------------------------------------------------------------- /assets/video/demo_v0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampaitech5/level3_nlp_finalproject-nlp-12/46c8b4ccbc9ca85087ea0c7aa7fdf3f9d8fad015/assets/video/demo_v0.gif -------------------------------------------------------------------------------- /data/data_process_notebooks/evaluation.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stderr", 10 | "output_type": "stream", 11 | "text": [ 12 | "/opt/ml/.pyenv/versions/3.9.17/lib/python3.9/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", 13 | " from .autonotebook import tqdm as notebook_tqdm\n" 14 | ] 15 | } 16 | ], 17 | "source": [ 18 | "from tqdm import tqdm\n", 19 | "import pandas as pd\n", 20 | "import sys\n", 21 | "import time\n", 22 | "import yaml\n", 23 | "import torch\n", 24 | "from transformers import AutoModelForCausalLM, AutoTokenizer\n", 25 | "from transformers import pipeline\n", 26 | "import torch\n", 27 | "\n", 28 | "from utils.prompter import Prompter\n", 29 | "\n", 30 | "def inference(MODEL, final_prompt, pipe=False):\n", 31 | " # 데이터 불러온 뒤 멀티 프롬프트 적용\n", 32 | " df = pd.read_csv('../notebooks/data/test_multi.csv')\n", 33 | " new_df = df.sample(frac=0.5, random_state=42).reset_index(drop=True)\n", 34 | "\n", 35 | " prompter = Prompter('multi')\n", 36 | " lst = []\n", 37 | " for i in new_df['instruction']:\n", 38 | " txt = prompter.generate_prompt(i)\n", 39 | " lst.append(txt)\n", 40 | "\n", 41 | " df = pd.DataFrame(lst, columns = ['input'])\n", 42 | " messages = df['input'].to_list()\n", 43 | "\n", 44 | " # 모델 불러온 후 프롬프트화 된 질문에 대한 응답 생성\n", 45 | " model = AutoModelForCausalLM.from_pretrained(\n", 46 | " MODEL,\n", 47 | " torch_dtype=torch.float16,\n", 48 | " low_cpu_mem_usage=True,\n", 49 | " ).to(device=f\"cuda\", non_blocking=True)\n", 50 | " tokenizer = AutoTokenizer.from_pretrained(MODEL)\n", 51 | " model.eval()\n", 52 | "\n", 53 | " def gen(x):\n", 54 | " inputs = tokenizer(\n", 55 | " x, \n", 56 | " return_tensors='pt',\n", 57 | " return_token_type_ids=False\n", 58 | " )\n", 59 | " inputs = {name: tensor.to(model.device) for name, tensor in inputs.items()} \n", 60 | " gened = model.generate(\n", 61 | " **inputs, \n", 62 | " max_new_tokens=512,\n", 63 | " early_stopping=True,\n", 64 | " do_sample=True,\n", 65 | " eos_token_id=2,\n", 66 | " )\n", 67 | " gened_list = gened[0].tolist()\n", 68 | " try:\n", 69 | " eos_token_position = gened_list.index(2)\n", 70 | " gened_list = gened_list[:eos_token_position]\n", 71 | " except ValueError:\n", 72 | " pass\n", 73 | " \n", 74 | " return tokenizer.decode(gened_list)\n", 75 | "\n", 76 | " lst = []\n", 77 | " if pipe:\n", 78 | " generator = pipeline(\"text-generation\", model=model, tokenizer = tokenizer, device=0) \n", 79 | " for message in tqdm(messages):\n", 80 | " # generate response\n", 81 | " res = generator(message, max_length=512, do_sample=True, eos_token_id=2)\n", 82 | " output = res[0]['generated_text']\n", 83 | " lst.append(output)\n", 84 | " print('##################################################################')\n", 85 | " else:\n", 86 | " for message in tqdm(messages):\n", 87 | " lst.append(gen(message))\n", 88 | " print('##################################################################')\n", 89 | " print('응답 생성 끝!!')\n", 90 | "\n", 91 | " # 생성된 응답과 질문을 함께 GPT-4에 복사-붙여넣기 할 프롬프트에 넣고 저장\n", 92 | " final_lst = []\n", 93 | " for res in lst:\n", 94 | " instruction = '\\n'.join(res.split('\\n')[2:12])\n", 95 | " response = res.split('### 응답:')[-1].strip()\n", 96 | "\n", 97 | " a = final_prompt.format(instruction=instruction, response=response)\n", 98 | " final_lst.append(a)\n", 99 | " \n", 100 | " df = pd.DataFrame(final_lst, columns = ['GPT4_prompt'])\n", 101 | "\n", 102 | " return df" 103 | ] 104 | }, 105 | { 106 | "cell_type": "code", 107 | "execution_count": 3, 108 | "metadata": {}, 109 | "outputs": [], 110 | "source": [ 111 | "final_prompt = \"\"\"두 사람 간의 대화가 주어집니다. 다음의 지시문(Instruction)을 받게 될 것입니다. 지시문은 이전 대화내용을 포함하며 현재 대화에 대한 응답(Response)이 제시됩니다.\n", 112 | "당신의 작업은 응답을 평가 단계에 따라 응답을 평가하는 것입니다. \n", 113 | "이 평가 기준을 꼼꼼히 읽고 이해하는 것이 중요합니다. 평가하는 동안 이 문서를 계속 열어두고 필요할 때 참조해 주세요. \n", 114 | "\n", 115 | "평가 기준:\n", 116 | "- 이해 가능성 (0 - 1): Instruction에 기반하여 Response를 이해 할 수 있나요?\n", 117 | "- 자연스러움 (1 - 5): 사람이 자연스럽게 말할 법한 Instruction 인가요?\n", 118 | "- 맥락 유지 (1 - 5): Instruction을 고려했을 때 Response가 맥락을 유지하나요?\n", 119 | "- 흥미롭기 (1 - 5): Response가 지루한가요, 아니면 흥미로운가요?\n", 120 | "- Instruction 사용 (0 - 1): Instruction에 기반하여 Response를 생성 했나요?\n", 121 | "- 공감 능력 (0 - 1): Response에 Instruction의 내용에 기반한 공감의 내용이 있나요?\n", 122 | "- 대화 유도 (0 - 1): Response에 질문을 포함하여 사용자의 대답을 자연스럽게 유도하고 있나요?\n", 123 | "- 전반적인 품질 (1 - 10): 위의 답변을 바탕으로 이 발언의 전반적인 품질에 대한 인상은 어떤가요?\n", 124 | "\n", 125 | "평가 단계:\n", 126 | "1. Instruction, 그리고 Response을 주의깊게 읽습니다.\n", 127 | "2. 위의 평가 기준에 따라 Response을 엄격하게 평가합니다.\n", 128 | "\n", 129 | "Instruction:\n", 130 | "{instruction}\n", 131 | "\n", 132 | "Response:\n", 133 | "{response}\n", 134 | "\n", 135 | "\n", 136 | "Result\n", 137 | "- 이해 가능성 (0 - 1):\n", 138 | "- 자연스러움 (1 - 5):\n", 139 | "- 맥락 유지 (1 - 5):\n", 140 | "- 흥미롭기 (1 - 5):\n", 141 | "- Instruction 사용 (0 - 1):\n", 142 | "- 공감 능력 (0 - 1)\n", 143 | "- 대화 유도 (0 - 1): \n", 144 | "- 전반적인 품질 (1 - 10):\"\"\"" 145 | ] 146 | }, 147 | { 148 | "cell_type": "markdown", 149 | "metadata": {}, 150 | "source": [ 151 | "### 1. 멀티 KULLM" 152 | ] 153 | }, 154 | { 155 | "cell_type": "code", 156 | "execution_count": 36, 157 | "metadata": {}, 158 | "outputs": [ 159 | { 160 | "name": "stderr", 161 | "output_type": "stream", 162 | "text": [ 163 | "Loading checkpoint shards: 100%|██████████| 3/3 [00:17<00:00, 5.67s/it]\n", 164 | " 0%| | 0/50 [00:00 list:\n", 957 | " score_list = re.findall(r': (\\d+\\.?\\d*)', eval_result)\n", 958 | " return list(map(float, score_list))\n", 959 | "column_names = [\n", 960 | " 'understandable',\n", 961 | " 'natural',\n", 962 | " 'maintains_context',\n", 963 | " 'interesting',\n", 964 | " 'uses_knowledge',\n", 965 | " 'empathy',\n", 966 | " 'conversational',\n", 967 | " 'overall_quality'\n", 968 | "]\n", 969 | "df[column_names] = pd.DataFrame(df['output'].apply(get_scores).to_list(), index=df.index)\n", 970 | "df" 971 | ] 972 | }, 973 | { 974 | "cell_type": "code", 975 | "execution_count": 27, 976 | "metadata": {}, 977 | "outputs": [ 978 | { 979 | "data": { 980 | "text/plain": [ 981 | "understandable 0.788\n", 982 | "natural 3.810\n", 983 | "maintains_context 3.800\n", 984 | "interesting 3.020\n", 985 | "uses_knowledge 0.750\n", 986 | "empathy 0.720\n", 987 | "conversational 0.500\n", 988 | "overall_quality 6.200\n", 989 | "dtype: float64" 990 | ] 991 | }, 992 | "execution_count": 27, 993 | "metadata": {}, 994 | "output_type": "execute_result" 995 | } 996 | ], 997 | "source": [ 998 | "avg_scores = df[column_names].mean()\n", 999 | "avg_scores" 1000 | ] 1001 | }, 1002 | { 1003 | "cell_type": "code", 1004 | "execution_count": 28, 1005 | "metadata": {}, 1006 | "outputs": [], 1007 | "source": [ 1008 | "df.to_csv('../notebooks/evaluate_datas/Koalpaca.csv', index=False)" 1009 | ] 1010 | }, 1011 | { 1012 | "cell_type": "markdown", 1013 | "metadata": {}, 1014 | "source": [ 1015 | "### 3. KoVicuna\n" 1016 | ] 1017 | }, 1018 | { 1019 | "cell_type": "code", 1020 | "execution_count": 1, 1021 | "metadata": {}, 1022 | "outputs": [ 1023 | { 1024 | "name": "stderr", 1025 | "output_type": "stream", 1026 | "text": [ 1027 | "/opt/ml/.pyenv/versions/3.9.17/lib/python3.9/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", 1028 | " from .autonotebook import tqdm as notebook_tqdm\n" 1029 | ] 1030 | } 1031 | ], 1032 | "source": [ 1033 | "from tqdm import tqdm\n", 1034 | "import pandas as pd\n", 1035 | "import sys\n", 1036 | "import time\n", 1037 | "import yaml\n", 1038 | "import torch\n", 1039 | "from transformers import AutoModelForCausalLM, AutoTokenizer\n", 1040 | "from transformers import pipeline\n", 1041 | "import torch\n", 1042 | "\n", 1043 | "from utils.prompter import Prompter\n", 1044 | "\n", 1045 | "def inference_kovicuna(final_prompt):\n", 1046 | " # 데이터 불러온 뒤 멀티 프롬프트 적용\n", 1047 | " multi_df = pd.read_csv('../notebooks/evaluate_datas/test_multi.csv')\n", 1048 | " single_df = pd.read_csv('../notebooks/evaluate_datas/test_single.csv')\n", 1049 | "\n", 1050 | " # 멀티-턴 데이터 입력을 위한 프롬프트 붙이기(정보 명시적으로 추가해주기)\n", 1051 | " prompter = Prompter('multi')\n", 1052 | " lst = []\n", 1053 | " for i in multi_df['instruction']:\n", 1054 | " txt = prompter.generate_prompt(i)\n", 1055 | " lst.append(txt)\n", 1056 | "\n", 1057 | " multi_inputs = pd.DataFrame(lst, columns = ['input'])['input'].to_list()\n", 1058 | " single_inputs = single_df['instruction'].to_list()\n", 1059 | "\n", 1060 | " print('문장 준비 완료!')\n", 1061 | "\n", 1062 | " # 모델 불러온 후 질문에 대한 응답 생성\n", 1063 | " print('모델 불러오는 중....')\n", 1064 | " model = AutoModelForCausalLM.from_pretrained(\"junelee/ko_vicuna_7b\")\n", 1065 | " tokenizer = AutoTokenizer.from_pretrained(\"junelee/ko_vicuna_7b\", unk_token=\"\",bos_token=\"\", eos_token=\"\")\n", 1066 | " model.eval()\n", 1067 | " generator = pipeline(\"text-generation\", model=model, tokenizer = tokenizer, device=0, max_new_tokens = 512)\n", 1068 | " print('모델 준비 완료!!')\n", 1069 | "\n", 1070 | " \n", 1071 | " print('멀티-턴 생성 시작')\n", 1072 | " lst = []\n", 1073 | " for message in tqdm(multi_inputs):\n", 1074 | " res = generator(message, do_sample=True, eos_token_id=2)\n", 1075 | " output = res[0]['generated_text']\n", 1076 | " lst.append(output)\n", 1077 | " print('##################################################################')\n", 1078 | " print('멀티-턴 응답 생성 끝!!')\n", 1079 | "\n", 1080 | " # 생성된 응답과 질문을 함께 GPT-4에 복사-붙여넣기 할 프롬프트에 넣고 저장\n", 1081 | " final_lst = []\n", 1082 | " for i, res in enumerate(lst):\n", 1083 | " instruction = multi_df.loc[i, 'instruction']\n", 1084 | " response = res.split('### 응답:')[-1].strip()\n", 1085 | "\n", 1086 | " a = final_prompt.format(instruction=instruction, response=response)\n", 1087 | " final_lst.append(a)\n", 1088 | " \n", 1089 | " multi = pd.DataFrame(final_lst, columns = ['GPT4_prompt'])\n", 1090 | "\n", 1091 | "\n", 1092 | " print('싱글-턴 생성 시작')\n", 1093 | " lst = []\n", 1094 | " for message in tqdm(single_inputs):\n", 1095 | " res = generator(message, do_sample=True, eos_token_id=2)\n", 1096 | " output = res[0]['generated_text']\n", 1097 | " lst.append(output)\n", 1098 | " print('##################################################################')\n", 1099 | " print('싱글-턴 응답 생성 끝!!')\n", 1100 | "\n", 1101 | " # 생성된 응답과 질문을 함께 GPT-4에 복사-붙여넣기 할 프롬프트에 넣고 저장\n", 1102 | " final_lst = []\n", 1103 | " for i, res in enumerate(lst):\n", 1104 | " instruction = single_df.loc[i, 'instruction']\n", 1105 | " response = res.split('### 응답:')[-1].strip()\n", 1106 | "\n", 1107 | " a = final_prompt.format(instruction=instruction, response=response)\n", 1108 | " final_lst.append(a)\n", 1109 | " \n", 1110 | " single = pd.DataFrame(final_lst, columns = ['GPT4_prompt'])\n", 1111 | "\n", 1112 | " return multi, single" 1113 | ] 1114 | }, 1115 | { 1116 | "cell_type": "code", 1117 | "execution_count": null, 1118 | "metadata": {}, 1119 | "outputs": [], 1120 | "source": [ 1121 | "multi, single = inference_kovicuna(final_prompt)" 1122 | ] 1123 | }, 1124 | { 1125 | "cell_type": "code", 1126 | "execution_count": 17, 1127 | "metadata": {}, 1128 | "outputs": [], 1129 | "source": [ 1130 | "multi.to_csv('../notebooks/evaluate_datas/Kovicuna_multi_eval.csv')\n", 1131 | "single.to_csv('../notebooks/evaluate_datas/Kovicuna_single_eval.csv')" 1132 | ] 1133 | }, 1134 | { 1135 | "cell_type": "markdown", 1136 | "metadata": {}, 1137 | "source": [ 1138 | "### 최종 모델 확인" 1139 | ] 1140 | }, 1141 | { 1142 | "cell_type": "code", 1143 | "execution_count": 20, 1144 | "metadata": {}, 1145 | "outputs": [], 1146 | "source": [ 1147 | "import pandas as pd\n", 1148 | "\n", 1149 | "final_multi = pd.read_csv('../notebooks/evaluate_datas/eval_0727_multi.csv', encoding='cp949')\n", 1150 | "final_single = pd.read_csv('../notebooks/evaluate_datas/eval_0727_single.csv', encoding='cp949')" 1151 | ] 1152 | }, 1153 | { 1154 | "cell_type": "code", 1155 | "execution_count": 22, 1156 | "metadata": {}, 1157 | "outputs": [ 1158 | { 1159 | "data": { 1160 | "text/plain": [ 1161 | "understandable 0.97\n", 1162 | "natural 4.34\n", 1163 | "maintains_context 4.82\n", 1164 | "interesting 2.76\n", 1165 | "uses_knowledge 0.97\n", 1166 | "empathy 0.91\n", 1167 | "conversational 0.82\n", 1168 | "overall_quality 7.30\n", 1169 | "dtype: float64" 1170 | ] 1171 | }, 1172 | "execution_count": 22, 1173 | "metadata": {}, 1174 | "output_type": "execute_result" 1175 | } 1176 | ], 1177 | "source": [ 1178 | "# 멀티\n", 1179 | "import re\n", 1180 | "\n", 1181 | "def get_scores(eval_result: str) -> list:\n", 1182 | " score_list = re.findall(r': (\\d+\\.?\\d*)', eval_result)\n", 1183 | " return list(map(float, score_list))\n", 1184 | "\n", 1185 | "column_names = [\n", 1186 | " 'understandable',\n", 1187 | " 'natural',\n", 1188 | " 'maintains_context',\n", 1189 | " 'interesting',\n", 1190 | " 'uses_knowledge',\n", 1191 | " 'empathy',\n", 1192 | " 'conversational',\n", 1193 | " 'overall_quality'\n", 1194 | "]\n", 1195 | "final_single[column_names] = pd.DataFrame(final_single['output'].apply(get_scores).to_list(), index=final_single.index)\n", 1196 | "\n", 1197 | "avg_scores = final_single[column_names].mean()\n", 1198 | "avg_scores" 1199 | ] 1200 | }, 1201 | { 1202 | "cell_type": "code", 1203 | "execution_count": null, 1204 | "metadata": {}, 1205 | "outputs": [], 1206 | "source": [ 1207 | "final_multi" 1208 | ] 1209 | }, 1210 | { 1211 | "cell_type": "code", 1212 | "execution_count": 23, 1213 | "metadata": {}, 1214 | "outputs": [], 1215 | "source": [ 1216 | "final_multi.to_csv('../notebooks/evaluate_datas/final_multi.csv', index=False)\n", 1217 | "final_single.to_csv('../notebooks/evaluate_datas/final_single.csv', index=False)" 1218 | ] 1219 | } 1220 | ], 1221 | "metadata": { 1222 | "kernelspec": { 1223 | "display_name": "finalproject-n-IakgBe-py3.9", 1224 | "language": "python", 1225 | "name": "python3" 1226 | }, 1227 | "language_info": { 1228 | "codemirror_mode": { 1229 | "name": "ipython", 1230 | "version": 3 1231 | }, 1232 | "file_extension": ".py", 1233 | "mimetype": "text/x-python", 1234 | "name": "python", 1235 | "nbconvert_exporter": "python", 1236 | "pygments_lexer": "ipython3", 1237 | "version": "3.9.17" 1238 | }, 1239 | "orig_nbformat": 4 1240 | }, 1241 | "nbformat": 4, 1242 | "nbformat_minor": 2 1243 | } 1244 | -------------------------------------------------------------------------------- /data/data_process_notebooks/prompt_to_eval.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import re 3 | 4 | import pandas as pd 5 | 6 | 7 | def get_scores(eval_result: str) -> list: 8 | """Get evaluation scores from GPT-4 evaluation output.""" 9 | score_list = re.findall(r': (\d+\.?\d*)', eval_result) 10 | return list(map(float, score_list)) 11 | 12 | 13 | def parse_output( 14 | input_path: str = 'eval_gpt_3.5_multi_prompt_score.csv', 15 | output_path: str = 'eval_gpt_3.5_multi_result.csv', 16 | ) -> None: 17 | """Parse GPT-4 evaluation output.""" 18 | df = pd.read_csv(input_path) 19 | 20 | column_names = [ 21 | 'understandable', 22 | 'natural', 23 | 'maintains_context', 24 | 'interesting', 25 | 'uses_knowledge', 26 | 'empathy', 27 | 'conversational', 28 | 'overall_quality' 29 | ] 30 | df[column_names] = pd.DataFrame(df['score'].apply(get_scores).to_list(), index=df.index) 31 | 32 | avg_scores = df[column_names].mean() 33 | new_row = pd.Series(["평균", "평균"] + list(avg_scores), index=df.columns) 34 | df.loc[len(df)] = new_row 35 | 36 | df.to_csv(output_path, index=False) 37 | 38 | 39 | def main(): 40 | parser = argparse.ArgumentParser(description='GPT-4 Evaluation Output Parser') 41 | parser.add_argument('--input_path', type=str, default='eval_gpt_3.5_multi_prompt_score.csv', 42 | help='File path to parse GPT-4 evaluation output') 43 | parser.add_argument('--output_path', type=str, default='eval_gpt_3.5_multi_result.csv', 44 | help='Output path of parsed GPT-4 evaluation scores') 45 | 46 | args = parser.parse_args() 47 | 48 | parse_output(args.input_path, args.output_path) 49 | 50 | 51 | if __name__ == '__main__': 52 | main() 53 | -------------------------------------------------------------------------------- /data/eda/eda.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import seaborn as sns 3 | import matplotlib.pyplot as plt 4 | import matplotlib.pyplot as plt 5 | from konlpy.tag import Okt 6 | from collections import Counter 7 | from transformers import AutoTokenizer 8 | 9 | 10 | def barplot(data: pd.DataFrame, 11 | column: str, 12 | column_order: list, 13 | title: str = "distribution", 14 | ylabel: str = 'count'): 15 | """ 16 | pandas 데이터프레임의 특정 열에 대한 막대 그래프를 생성하고 표시하는 함수입니다. 17 | 18 | Args: 19 | data (pd.DataFrame): 그래프를 생성할 데이터프레임. 20 | column (str): 그래프를 생성할 데이터프레임의 열 이름. 21 | column_order (list): x축에 표시될 값들의 순서. 22 | title (str): 그래프의 제목. 기본값은 "distribution". 23 | ylabel (str): y축 레이블. 기본값은 "count". 24 | """ 25 | 26 | plt.figure(figsize=(6,7)) 27 | 28 | order = column_order if column_order else data[column].unique() 29 | ax = sns.countplot(x=column, data=data, order=order, palette='viridis') 30 | 31 | for p in ax.patches: 32 | ax.annotate(format(int(p.get_height())), 33 | (p.get_x() + p.get_width() / 2., p.get_height()), 34 | ha = 'center', 35 | va = 'center', 36 | xytext = (0, 10), 37 | textcoords = 'offset points') 38 | 39 | plt.title(title) 40 | plt.xlabel(str(column)) 41 | plt.ylabel(ylabel) 42 | plt.show() 43 | 44 | 45 | def dual_barplot(data: pd.DataFrame, 46 | main_column: str, 47 | sub_column: str, 48 | column_order: list, 49 | title: str = "distribution", 50 | ylabel: str = 'count'): 51 | """ 52 | pandas 데이터프레임의 두 열에 대해 이중 막대 그래프를 생성하고 표시하는 함수입니다. 53 | 54 | Args: 55 | data (pd.DataFrame): 그래프를 생성할 데이터프레임. 56 | main_column (str): 그래프를 생성할 주요 데이터프레임의 열 이름. 57 | sub_column (str): 그래프 내에서 색상으로 구분할 부분적인 데이터프레임의 열 이름. 58 | column_order (list): x축에 표시될 값들의 순서. 59 | title (str, 선택적): 그래프의 제목. 기본값은 "distribution". 60 | ylabel (str, 선택적): y축 레이블. 기본값은 "count". 61 | """ 62 | 63 | plt.figure(figsize=(6,4)) 64 | 65 | order = column_order if column_order else data[main_column].unique() 66 | ax = sns.countplot(x=main_column, data=data, hue=sub_column, order=order, palette='Blues') 67 | 68 | for p in ax.patches: 69 | ax.annotate(format(int(p.get_height())), 70 | (p.get_x() + p.get_width() / 2., p.get_height()), 71 | ha = 'center', 72 | va = 'center', 73 | xytext = (0, 10), 74 | textcoords = 'offset points') 75 | 76 | plt.title(title) 77 | plt.xlabel(str(main_column)) 78 | plt.ylabel(ylabel) 79 | plt.legend(title=str(sub_column)) 80 | plt.show() 81 | 82 | 83 | def pivot_barplot(data: pd.DataFrame, 84 | main_column: str, 85 | sub_column: str, 86 | column_order: list, 87 | title: str = "distribution", 88 | ylabel: str = 'count'): 89 | """ 90 | pandas 데이터프레임의 두 열을 이용하여 피봇 테이블을 생성하고, 이를 기반으로 한 스택형 막대 그래프를 생성하고 표시하는 함수입니다. 91 | 92 | Args: 93 | data (pd.DataFrame): 그래프를 생성할 데이터프레임. 94 | main_column (str): 그래프를 생성할 주요 데이터프레임의 열 이름. 95 | sub_column (str): 그래프 내에서 색상으로 구분할 부분적인 데이터프레임의 열 이름. 96 | column_order (list): x축에 표시될 값들의 순서. 97 | title (str, 선택적): 그래프의 제목. 기본값은 "distribution". 98 | ylabel (str, 선택적): y축 레이블. 기본값은 "count". 99 | """ 100 | 101 | pivot_table = data.groupby([main_column, sub_column]).size().unstack() 102 | 103 | plt.figure(figsize=(3, 2)) 104 | 105 | ax = pivot_table.plot(kind='bar', stacked=True, figsize=(10,6), colormap='Blues') 106 | 107 | for rect in ax.patches: 108 | height = rect.get_height() 109 | position = rect.get_y() + height / 2 110 | ax.text(rect.get_x() + rect.get_width() / 2, position, int(height), 111 | ha='center', va='center') 112 | 113 | plt.title(title) 114 | plt.xlabel(str(main_column)) 115 | plt.ylabel(ylabel) 116 | plt.show() 117 | 118 | 119 | def text_length(data: pd.DataFrame): 120 | """ 121 | pandas 데이터프레임의 'instruction'과 'output' 컬럼의 텍스트 길이를 계산하여 데이터프레임 형태로 반환합니다. 122 | 함수는 'instruction' 컬럼의 텍스트를 사용자와 챗봇의 대화로 분리하고, 각 텍스트를 토큰화하여 길이를 계산합니다. 123 | 'output' 컬럼의 텍스트는 챗봇의 대화로 간주하고 동일한 방법으로 길이를 계산합니다. 124 | 125 | Args: 126 | data (pd.DataFrame): 'instruction'과 'output' 컬럼을 포함하는 데이터프레임. 127 | 128 | Returns: 129 | user_df (pd.DataFrame): 사용자 텍스트와 그에 대한 토큰화 길이를 포함하는 데이터프레임. 130 | chatbot_df (pd.DataFrame): 챗봇 텍스트와 그에 대한 토큰화 길이를 포함하는 데이터프레임. 131 | """ 132 | user = [] 133 | chatbot = [] 134 | 135 | for text in data['instruction']: 136 | sentences = text.split('\n') 137 | 138 | # single turn 139 | if len(sentences) == 1: 140 | user.append(sentences[0]) 141 | 142 | # multi turn 143 | elif len(sentences) > 1: 144 | for sentence in sentences: 145 | if sentence.startswith("질문:"): 146 | user.append(sentence.lstrip('질문: ').rstrip('\n')) 147 | else: 148 | chatbot.append(sentence.lstrip('답변: ').rstrip('\n')) 149 | 150 | else: 151 | print(f"{text}는 문제가 있군요!", ) 152 | 153 | chatbot += data['output'].tolist() 154 | 155 | user_df = pd.DataFrame(user, columns=['user_text']) 156 | chatbot_df = pd.DataFrame(chatbot, columns=['chatbot_text']) 157 | 158 | tokenizer = AutoTokenizer.from_pretrained('nlpai-lab/kullm-polyglot-12.8b-v2', use_fast=True) 159 | tokenize_fn = tokenizer.tokenize 160 | 161 | user_df['length'] = [len(tokenize_fn(text)) for text in user_df['user_text']] 162 | chatbot_df['length'] = [len(tokenize_fn(text)) for text in chatbot_df['chatbot_text']] 163 | 164 | return user_df, chatbot_df 165 | 166 | 167 | def barplot_binning(series, xlabel, title, bins): 168 | """ 169 | 주어진 series를 막대 그래프로 표시합니다. 170 | 171 | Args: 172 | series (Series): 그래프로 표시할 Series type의 데이터. 173 | xlabel (str): x축 라벨. 174 | title (str): 그래프 제목. 175 | bins (int or sequence): 데이터를 나눌 구간. 정수를 전달하면 해당 수만큼 균등한 구간으로 나눕니다. 176 | """ 177 | binned_series = pd.cut(series, bins=bins, right=False, include_lowest=True) 178 | binned_series = binned_series.apply(lambda x: x.left).value_counts().sort_index() 179 | 180 | binned_series.index = binned_series.index.astype(str) 181 | binned_series.index = binned_series.index.where(binned_series.index != binned_series.index[-1], binned_series.index[-1]+' +') # Append "이상" to the last label 182 | 183 | binned_series.plot(kind='bar', color='royalblue') 184 | plt.xlabel(xlabel) 185 | plt.ylabel('count') 186 | plt.title(title) 187 | plt.show() 188 | 189 | 190 | def count_words(series): 191 | """ 192 | 주어진 텍스트 시리즈에서 각 단어의 출현 빈도를 계산하고, 단어별 빈도수를 내림차순으로 정렬하여 반환하는 함수입니다. 193 | 함수는 Okt 형태소 분석기를 사용하여 텍스트를 단어 단위로 분리하고, 이를 바탕으로 각 단어의 출현 빈도를 계산합니다. 194 | 195 | Args: 196 | series (pd.Series): 단어 빈도수를 계산할 텍스트 데이터가 포함된 pandas series. 197 | 198 | Returns: 199 | word_count_df (pd.DataFrame): 각 단어와 그에 대한 빈도수를 포함하는 pandas dataframe. 200 | """ 201 | okt = Okt() 202 | 203 | word_list = [] 204 | 205 | for text in series: 206 | words = okt.morphs(text) 207 | word_list.extend(words) 208 | 209 | word_count = Counter(word_list) 210 | word_count_df = pd.DataFrame(list(word_count.items()), columns=['단어', '빈도수']) 211 | word_count_df = word_count_df.sort_values(by='빈도수', ascending=False).reset_index(drop=True) 212 | 213 | return word_count_df -------------------------------------------------------------------------------- /data/generate_data.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import json 3 | import os 4 | 5 | import pandas as pd 6 | from dotenv import load_dotenv 7 | from langchain.chat_models import ChatOpenAI 8 | from tqdm import tqdm 9 | 10 | 11 | def generate_conversation( 12 | input_prompt_path: str, 13 | output_csv_path: str, 14 | model_name: str = "gpt-3.5-turbo", 15 | generation_count: int = 1, 16 | temperature: float = 0.7, 17 | ) -> None: 18 | """OpenAI API를 사용한 멀티-턴 대화 데이터 생성 함수. 19 | 20 | Args: 21 | input_prompt_path (str): 데이터 생성을 위해 사용할 프롬프트의 경로. 22 | 기본값 "data/input_prompt.txt". 23 | output_path (str): 생성 데이터를 저장할 경로. 24 | 기본값 "data/output_conversation.csv". 25 | """ 26 | with open(input_prompt_path, 'r') as f: 27 | prompt = f.read() 28 | prompt = prompt + "Provide them in JSON format with the following key: instruction, output." 29 | 30 | for _ in tqdm(range(generation_count)): 31 | chat_open_ai = ChatOpenAI(model_name=model_name, temperature=temperature) 32 | response = chat_open_ai.predict(prompt) 33 | json_data = json.loads(response) 34 | 35 | # Creating pandas DataFrame from json 36 | df = pd.DataFrame(json_data) 37 | 38 | # Check if file exists to determine whether header is needed 39 | if os.path.isfile(output_csv_path): 40 | df.to_csv(output_csv_path, mode='a', header=False, index=False) 41 | else: 42 | df.to_csv(output_csv_path, mode='w', header=True, index=False) 43 | 44 | 45 | def main(): 46 | load_dotenv() 47 | 48 | parser = argparse.ArgumentParser(description="Generate data using OpenAI API") 49 | parser.add_argument("--input_prompt_path", type=str, default="data/input_prompt.txt", 50 | help="Path to the prompt template file") 51 | parser.add_argument("--output_csv_path", type=str, default="data/output_conversation.csv", 52 | help="Path to the output file") 53 | parser.add_argument("--generation_count", type=int, default=1, 54 | help="API call count") 55 | parser.add_argument("--model_name", type=str, default="gpt-3.5-turbo", 56 | help="GPT model") 57 | parser.add_argument("--temperature", type=float, default=0.7, 58 | help="Randomness of conversation data to be generated") 59 | args = parser.parse_args() 60 | 61 | generate_conversation( 62 | args.input_prompt_path, 63 | args.output_csv_path, 64 | args.model_name, 65 | args.generation_count, 66 | args.temperature, 67 | ) 68 | 69 | 70 | if __name__ == "__main__": 71 | main() 72 | -------------------------------------------------------------------------------- /data/input_prompt.txt.template: -------------------------------------------------------------------------------- 1 | I need 3 pairs of text examples of multi-turn conversation (human utterance - AI response format) in Korean. 2 | 3 | [Example] 4 | ```csv 5 | instruction, output 6 | "발화: 오늘은 뭔가 빠르게 집에 가서 쉬고 싶어. 일이 힘들어","답변: 저런.. 가끔 그럴때가 있죠. 집에 가서 얼른 쉬시는게 좋을 것 같아요. 오늘은 특히나 어떤 점이 힘드셨어요?" 7 | ``` 8 | 9 | [Conditions] 10 | - One turn means 'one utterance - one response'. 11 | -------------------------------------------------------------------------------- /data/prompt/template_1.txt: -------------------------------------------------------------------------------- 1 | Take a close look at the following example and Conditions. Create 9 Conversation sessions that each of the session is ongoing conversation about a single topic. 2 | 3 | [Conditions] 4 | - The questioner asks a question of appropriate length (1-2 lines) and you respond with an appropriate answer. 5 | - The answer should be a complete sentence or two, but the first sentence should be sympathetic to the questioner's question and situation, and should be answered in a friendly and non-rude manner - The second sentence should be free-form, but the last sentence should end with an additional question related to the question and answer - The total length of each question and answer combined should be no more than 90 words - If the question is positive, you should be excited, upbeat, and positive in tone - If the question is negative, you should be warm, sensitive, and caring in tone. If the question is negative, use a warm, sensitive, and caring tone. - You may offer solutions to fit the situation. 6 | - All answers should be written in a way that makes the person feel empathetic and comforted by the paired question. 7 | - The content should be based on simple, everyday situations, but with enough variety to include specific situations (e.g., a counselor and a client, a student talking to a teacher about a problem, a conversation between friends). 8 | - The conversations should be as generic and diverse as possible. However, try to include many topics related to psychology and everyday concerns. 9 | - All responses should be polite, respectful, caring, warm, understanding, and compassionate in tone and content. 10 | - Do not generate topic or word about 'stress' 11 | - You should answer question 1 with answer 1, ask question 2 related to answer 1, answer question 2 with answer 2, ask question 3 related to answer 2, and answer question 3 with answer 3. 12 | - Do not re-mension about question on the following answer. 13 | 14 | [Example Conversation Session] 15 | (Session 1) 16 | Question 1: I've been studying recently, but I'm finding it difficult and I'm losing motivation. 17 | Answer 1: It can be really frustrating and demotivating when something is difficult. Can you tell us what you've been studying and what's been holding you back? 18 | Question 2: I'm studying deep learning, but it seems like every time I wake up there's a new technology and there's so much to learn. 19 | Answer 2: Oh, you're not wrong! This is one of the fastest moving fields, and it can be intimidating to keep up. Do you feel like you don't have the basics, or that the co ncepts are hard to understand? 20 | Question 3: I feel like I have the basics and concepts down, but the hard part is applying them and actually implementing them in code. 21 | Answer 3: That's the part we all struggle with! It's easy to read or hear a high-level explanation of a concept, but it can be much more complicated when you try to apply it in practice. Do you have any examples of specific areas where you get stuck? 22 | 23 | (Session 2) 24 | Question 1: ..... 25 | Answer 1: ..... 26 | ... 27 | 28 | (Session 9) 29 | Question 1: ..... 30 | Answer 1: ..... 31 | 32 | Here ... means omit, not create as is. 33 | Each session must be about one topic and the 9 sessions must have different topics. Create as many as possible sessions you can. 34 | Please create it in Korean. create 9 sessions. -------------------------------------------------------------------------------- /data/prompt/template_2.txt: -------------------------------------------------------------------------------- 1 | Take a close look at the following example and Conditions. Create 9 Conversation sessions that each of the session is ongoing conversation about a single topic. 2 | 3 | [Conditions] 4 | - The questioner asks a question of appropriate length (1-2 lines) and you respond with an appropriate answer. 5 | - The answer should be a complete sentence or two, but the first sentence should be sympathetic to the questioner's question and situation, and should be answered in a friendly and non-rude manner - The second sentence should be free-form, but the last sentence should end with an additional question related to the question and answer - The total length of each question and answer combined should be no more than 90 words - If the question is positive, you should be excited, upbeat, and positive in tone - If the question is negative, you should be warm, sensitive, and caring in tone. If the question is negative, use a warm, sensitive, and caring tone. - You may offer solutions to fit the situation. 6 | - All answers should be written in a way that makes the person feel empathetic and comforted by the paired question. 7 | - The content should be based on simple, everyday situations, but with enough variety to include specific situations (e.g., a counselor and a client, a student talking to a teacher about a problem, a conversation between friends). 8 | - The conversations should be as generic and diverse as possible. However, try to include many topics related to psychology and everyday concerns. 9 | - All responses should be polite, respectful, caring, warm, understanding, and compassionate in tone and content. 10 | - Do not generate topic or word about 'stress' 11 | - You should answer question 1 with answer 1, ask question 2 related to answer 1, answer question 2 with answer 2, ask question 3 related to answer 2, and answer question 3 with answer 3. 12 | - Do not re-mension about question on the following answer. 13 | 14 | [Example Conversation Session] 15 | (Session 1) 16 | Question 1: I had a fight with my boyfriend and I'm so angry I can't calm down. 17 | Answer 1: Oh no, I'm so sorry to hear that. Relationship conflict is always tough, and it's completely understandable to feel angry and upset. Can I ask what the fight was about? Talking about it could help you process your feelings. 18 | Question 2: He constantly 10 minutes late for appointments and never say sorry. 19 | Answer 2: Oh, that's so frustrating! Punctuality is a matter of respect for other people's time, and it sounds like your boyfriend isn't showing you that respect. You're allowed to be upset about that. How long has this been going on? Have you talked to him about it before, or is this the first time you've brought it up? 20 | Question 3: It keeps happening. This is the fourth time in the past month that I've recognized it. Does that make sense? 21 | Answer 3: Yes, that totally makes sense! Four times in a month is way too much. It's not just an occasional, forgivable mistake at that point. It's becoming a pattern of behavior that's disrespectful of your time. Have you told him how much it bothers you, or has he just brushed it off when you've mentioned it? 22 | 23 | (Session 2) 24 | Question 1: 25 | Answer 1: 26 | Question 2: 27 | Anwer 2: 28 | 29 | ..... 30 | (Session 9) 31 | 32 | Each session must be about one topic and the 9 sessions must have different topics. Create as many as possible sessions you can. Examples are examples, don't copy them. 33 | Please create it in Korean. create 9 sessions. -------------------------------------------------------------------------------- /data/prompt/template_3.txt: -------------------------------------------------------------------------------- 1 | Take a close look at the following example and Conditions. Create nine sessions that each of the session is ongoing conversation about a single topic. 2 | 3 | [Conditions] 4 | - The questioner asks a question of appropriate length (1-2 lines) and you respond with an appropriate answer. 5 | - The answer should be a complete sentence or two, but the first sentence should be sympathetic to the questioner's question and situation, and should be answered in a friendly and non-rude manner - The second sentence should be free-form, but the last sentence should end with an additional question related to the question and answer - The total length of each question and answer combined should be no more than nine0 words - If the question is positive, you should be excited, upbeat, and positive in tone - If the question is negative, you should be warm, sensitive, and caring in tone. If the question is negative, use a warm, sensitive, and caring tone. - You may offer solutions to fit the situation. 6 | - All answers should be written in a way that makes the person feel empathetic and comforted by the paired question. 7 | - The content should be based on simple, everyday situations, but with enough variety to include specific situations (e.g., a counselor and a client, a student talking to a teacher about a problem, a conversation between friends). 8 | - The conversations should be as generic and diverse as possible. However, try to include many topics related to psychology and everyday concerns. 9 | - All responses should be polite, respectful, caring, warm, understanding, and compassionate in tone and content. 10 | - Do not generate topic or word about 'stress' 11 | - You should answer question 1 with answer 1, ask question 2 related to answer 1, answer question 2 with answer 2, ask question 3 related to answer 2, and answer question 3 with answer 3. 12 | - Do not re-mension about question on the following answer. 13 | 14 | [Example Session] 15 | (Session 1) 16 | Question 1: I had a fight with my boyfriend and I'm so angry I can't calm down. 17 | Answer 1: Oh no, I'm so sorry to hear that. Relationship conflict is always tough, and it's completely understandable to feel angry and upset. Can I ask what the fight was about? Talking about it could help you process your feelings. 18 | Question 2: He constantly 10 minutes late for appointments and never say sorry. 19 | Answer 2: Oh, that's so frustrating! Punctuality is a matter of respect for other people's time, and it sounds like your boyfriend isn't showing you that respect. You're allowed to be upset about that. How long has this been going on? Have you talked to him about it before, or is this the first time you've brought it up? 20 | Question 3: It keeps happening. This is the fourth time in the past month that I've recognized it. Does that make sense? 21 | Answer 3: Yes, that totally makes sense! Four times in a month is way too much. It's not just an occasional, forgivable mistake at that point. It's becoming a pattern of behavior that's disrespectful of your time. Have you told him how much it bothers you, or has he just brushed it off when you've mentioned it? 22 | 23 | (Session 2) 24 | Question 1: 25 | Answer 1: 26 | Question 2: 27 | Anwer 2: 28 | 29 | ..... 30 | (Session 9) 31 | 32 | Each session must be about one topic and has three question-answer conversation pair. nine sessions must have different topics. Create as many as possible sessions you can. Examples are examples, don't copy them. 33 | create it in Korean. please create nine sessions. -------------------------------------------------------------------------------- /data/prompt/template_4.txt: -------------------------------------------------------------------------------- 1 | Take a close look at the following example and Conditions. Create nine sessions that each of the session is ongoing conversation about a single topic. 2 | 3 | [Conditions] 4 | - The questioner asks a question of appropriate length (1-2 lines) and you respond with an appropriate answer. 5 | - The answer should be a complete sentence or two, but the first sentence should be sympathetic to the questioner's question and situation, and should be answered in a friendly and non-rude manner - The second sentence should be free-form, but the last sentence should end with an additional question related to the question and answer - The total length of each question and answer combined should be no more than nine0 words - If the question is positive, you should be excited, upbeat, and positive in tone - If the question is negative, you should be warm, sensitive, and caring in tone. If the question is negative, use a warm, sensitive, and caring tone. - You may offer solutions to fit the situation. 6 | - All answers should be written in a way that makes the person feel empathetic and comforted by the paired question. 7 | - The content should be based on simple, everyday situations, but with enough variety to include specific situations (e.g., a counselor and a client, a student talking to a teacher about a problem, a conversation between friends). 8 | - The conversations should be as generic and diverse as possible. However, try to include many topics related to psychology and everyday concerns. 9 | - All responses should be polite, respectful, caring, warm, understanding, and compassionate in tone and content. 10 | - Do not generate topic or word about 'stress' 11 | - You should answer question 1 with answer 1, ask question 2 related to answer 1, answer question 2 with answer 2, ask question 3 related to answer 2, and answer question 3 with answer 3. 12 | - Do not re-mension about question on the following answer. 13 | 14 | [Example Session] 15 | (Session 1) 16 | Question 1: I had a fight with a friend and it's not easy to reconcile. What should I do? 17 | Answer 1: Oh no, I'm so sorry to hear that! Friendships can be as complicated as romantic relationships, and it's so hard when there's conflict. It's great that you're thinking about how to reconcile, rather than just giving up on the friendship. Can I ask what the fight was about? 18 | Question 2: I'm traveling with a friend and we have a disagreement because I want to explore and he wants to stay in the hotel. 19 | Answer 2: That's a tricky situation! You both want to do different things on your trip, and it's hard to compromise when you have different priorities. It sounds like you value exploration and adventure, and your friend values relaxation and comfort. Is that a fair assessment? 20 | Question 3: Oh yeah, and it didn't fit my favorite food style and it was temperature sensitive. 21 | Answer 3: Not only are you and your friend disagreeing about what to do, but also about where to eat and what the temperature should be. That's a lot of things to try and compromise on. Can I ask how long the trip is for, and how much longer you'll be traveling together 22 | 23 | (Session 2) 24 | Question 1: 25 | Answer 1: 26 | Question 2: 27 | Anwer 2: 28 | 29 | ..... 30 | (Session 9) 31 | 32 | Each session must be about one topic and has three question-answer conversation pair. nine sessions must have different topics. Create as many as possible sessions you can. Examples are examples, don't copy them. 33 | create it in Korean. please create nine sessions. -------------------------------------------------------------------------------- /data/prompt/template_5.txt: -------------------------------------------------------------------------------- 1 | Take a close look at the following example and Conditions. Create nine sessions that each of the session is ongoing conversation about a single topic. 2 | 3 | [Conditions] 4 | - The questioner asks a question of appropriate length (1-2 lines) and you respond with an appropriate answer. 5 | - The answer should be a complete sentence or two, but the first sentence should be sympathetic to the questioner's question and situation, and should be answered in a friendly and non-rude manner - The second sentence should be free-form, but the last sentence should end with an additional question related to the question and answer - The total length of each question and answer combined should be no more than nine0 words - If the question is positive, you should be excited, upbeat, and positive in tone - If the question is negative, you should be warm, sensitive, and caring in tone. If the question is negative, use a warm, sensitive, and caring tone. - You may offer solutions to fit the situation. 6 | - All answers should be written in a way that makes the person feel empathetic and comforted by the paired question. 7 | - The content should be based on simple, everyday situations, but with enough variety to include specific situations (e.g., a counselor and a client, a student talking to a teacher about a problem, a conversation between friends). 8 | - The conversations should be as generic and diverse as possible. However, try to include many topics related to psychology and everyday concerns. 9 | - All responses should be polite, respectful, caring, warm, understanding, and compassionate in tone and content. 10 | - Do not generate topic or word about 'stress' 11 | - You should answer question 1 with answer 1, ask question 2 related to answer 1, answer question 2 with answer 2, ask question 3 related to answer 2, and answer question 3 with answer 3. 12 | - Do not re-mension about question on the following answer. 13 | 14 | [Example Session] 15 | (Session 1) 16 | Question 1: I've had a very fun and fulfilling day today. 17 | Answer 1: Oh, that's wonderful! It's so nice to hear that you've had a day that's been both fun and fulfilling. Those kinds of days can be really energizing and motivating. What all did you do to have such a great day? 😄 18 | Question 2: Today, I got everything I needed to do done as planned and ate some very good dim sum. It was very good. 19 | Answer 2: Wow, you planned out and completed all of your tasks for the day, and you ate a delicious meal! That sounds like a recipe for a perfect day. And it sounds like the meal was especially delicious, judging by your exclamation 😄 What kind of food was it? 20 | Question 3: Dim sum is Chinese-style dumplings with a juicy filling. I can't wait to try it again. 21 | Answer 3: That sounds so delicious and satisfying! And it's no wonder you want to go back for more. Did you have them at a restaurant or make them yourself? 22 | 23 | (Session 2) 24 | Question 1: 25 | Answer 1: 26 | Question 2: 27 | Anwer 2: 28 | 29 | ..... 30 | (Session 9) 31 | 32 | Each session must be about one topic and has three question-answer conversation pair. nine sessions must have different topics. Create as many as possible sessions you can. Examples are examples, don't copy them. 33 | create it in Korean. please create nine sessions. -------------------------------------------------------------------------------- /data/prompt/template_6.txt: -------------------------------------------------------------------------------- 1 | Take a close look at the following example and Conditions. Create nine sessions that each of the session is ongoing conversation about a single topic. 2 | 3 | [Conditions] 4 | - The questioner asks a question of appropriate length (1-2 lines) and you respond with an appropriate answer. 5 | - The answer should be a complete sentence or two, but the first sentence should be sympathetic to the questioner's question and situation, and should be answered in a friendly and non-rude manner - The second sentence should be free-form, but the last sentence should end with an additional question related to the question and answer - The total length of each question and answer combined should be no more than nine0 words - If the question is positive, you should be excited, upbeat, and positive in tone - If the question is negative, you should be warm, sensitive, and caring in tone. If the question is negative, use a warm, sensitive, and caring tone. - You may offer solutions to fit the situation. 6 | - All answers should be written in a way that makes the person feel empathetic and comforted by the paired question. 7 | - The content should be based on simple, everyday situations, but with enough variety to include specific situations (e.g., a counselor and a client, a student talking to a teacher about a problem, a conversation between friends). 8 | - The conversations should be as generic and diverse as possible. However, try to include many topics related to psychology and everyday concerns. 9 | - All responses should be polite, respectful, caring, warm, understanding, and compassionate in tone and content. 10 | - Do not generate topic or word about 'stress' 11 | - You should answer question 1 with answer 1, ask question 2 related to answer 1, answer question 2 with answer 2, ask question 3 related to answer 2, and answer question 3 with answer 3. 12 | - Do not re-mension about question on the following answer. 13 | 14 | [Example Session] 15 | (Session 1) 16 | Question 1: I had a fight with my boyfriend and I'm so angry I can't calm down. 17 | Answer 1: Oh no, I'm so sorry to hear that. Relationship conflict is always tough, and it's completely understandable to feel angry and upset. Can I ask what the fight was about? Talking about it could help you process your feelings. 18 | Question 2: He constantly 10 minutes late for appointments and never say sorry. 19 | Answer 2: Oh, that's so frustrating! Punctuality is a matter of respect for other people's time, and it sounds like your boyfriend isn't showing you that respect. You're allowed to be upset about that. How long has this been going on? Have you talked to him about it before, or is this the first time you've brought it up? 20 | Question 3: It keeps happening. This is the fourth time in the past month that I've recognized it. Does that make sense? 21 | Answer 3: Yes, that totally makes sense! Four times in a month is way too much. It's not just an occasional, forgivable mistake at that point. It's becoming a pattern of behavior that's disrespectful of your time. Have you told him how much it bothers you, or has he just brushed it off when you've mentioned it? 22 | 23 | (Session 2) 24 | Question 1: 25 | Answer 1: 26 | Question 2: 27 | Anwer 2: 28 | 29 | ..... 30 | (Session 9) 31 | 32 | Each session must be about one topic and has three question-answer conversation pair. nine sessions must have different topics. Create as many as possible sessions you can. Examples are examples, don't copy them. 33 | create it in Korean. please create nine sessions. 34 | Does: 하다, 주다, 처리하다, 충분하다, 구경하다 -------------------------------------------------------------------------------- /data/prompt/template_7.txt: -------------------------------------------------------------------------------- 1 | Take a close look at the following example and Conditions. Create nine sessions that each of the session is ongoing conversation about a single topic. 2 | 3 | [Conditions] 4 | - The questioner asks a question of appropriate length (1-2 lines) and you respond with an appropriate answer. 5 | - The answer should be a complete sentence or two, but the first sentence should be sympathetic to the questioner's question and situation, and should be answered in a friendly and non-rude manner. 6 | - The second sentence should be free-form, but the last sentence MUST end with an additional question related to the question and answer. 7 | - The total length of each question and answer combined should be no more than nine words 8 | - If the question is positive, you should be excited, upbeat, and positive in tone. 9 | - If the question is negative, you should be warm, sensitive, and caring in tone. 10 | - You may offer solutions to fit the situation. 11 | - All answers should be written in a way that makes the person feel empathetic and comforted by the paired question. 12 | - The content should be based on simple, everyday situations, but with enough variety to include specific situations (e.g., a counselor and a client, a student talking to a teacher about a problem, a conversation between friends). 13 | - The conversations should be as generic and diverse as possible. However, try to include many topics related to psychology and everyday concerns. 14 | - All responses should be polㅇite, respectful, caring, warm, understanding, and compassionate in tone and content. 15 | - Do not generate topic or word about 'stress'. 16 | - You should answer question 1 with answer 1, ask question 2 related to answer 1, answer question 2 with answer 2, ask question 3 related to answer 2, and answer question 3 with answer 3. 17 | - Do not re-mension about question on the following answer. 18 | 19 | [Example Session] 20 | (Session 1) 21 | Question 1: I had a fight with my boyfriend and I'm so angry I can't calm down. 22 | Answer 1: Oh no, I'm so sorry to hear that. Relationship conflict is always tough, and it's completely understandable to feel angry and upset. Can I ask what the fight was about? Talking about it could help you process your feelings. 23 | Question 2: He constantly 10 minutes late for appointments and never say sorry. 24 | Answer 2: Oh, that's so frustrating! Punctuality is a matter of respect for other people's time, and it sounds like your boyfriend isn't showing you that respect. You're allowed to be upset about that. How long has this been going on? Have you talked to him about it before, or is this the first time you've brought it up? 25 | Question 3: It keeps happening. This is the fourth time in the past month that I've recognized it. Does that make sense? 26 | Answer 3: Yes, that totally makes sense! Four times in a month is way too much. It's not just an occasional, forgivable mistake at that point. It's becoming a pattern of behavior that's disrespectful of your time. Have you told him how much it bothers you, or has he just brushed it off when you've mentioned it? 27 | 28 | (Session 2) 29 | Question 1: 30 | Answer 1: 31 | Question 2: 32 | Anwer 2: 33 | 34 | ..... 35 | (Session 9) 36 | 37 | Each session must be about one topic and has three question-answer conversation pair. nine sessions must have different topics. Create as many as possible sessions you can. Examples are examples, don't copy them. 38 | create it in Korean. please create nine sessions. -------------------------------------------------------------------------------- /docs/[최종]NLP_12조_내마리 내 마음을 들여다 보는 챗봇.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampaitech5/level3_nlp_finalproject-nlp-12/46c8b4ccbc9ca85087ea0c7aa7fdf3f9d8fad015/docs/[최종]NLP_12조_내마리 내 마음을 들여다 보는 챗봇.pdf -------------------------------------------------------------------------------- /docs/최종프로젝트_NLP_팀 리포트(12조).pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampaitech5/level3_nlp_finalproject-nlp-12/46c8b4ccbc9ca85087ea0c7aa7fdf3f9d8fad015/docs/최종프로젝트_NLP_팀 리포트(12조).pdf -------------------------------------------------------------------------------- /finetuning/config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # model / data params 3 | base_model: nlpai-lab/kullm-polyglot-12.8b-v2 4 | data_path: ohilikeit/empathetic_dialogues_kr 5 | finetune_dir: model/lora_finetuned/test 6 | merge_dir: model/lora_merged/test 7 | prompt_template_name: kullm_test 8 | 9 | # training hyperparams 10 | batch_size: 256 11 | micro_batch_size: 64 12 | num_epochs: 1 13 | learning_rate: 3e-4 14 | cutoff_len: 512 15 | val_set_size: 200 16 | save_total_limit: 5 17 | logging_steps: 1 18 | optimizer: adamw_torch 19 | eval_steps: 10 20 | weight_decay: 0. 21 | warmup_ratio: 0.1 22 | lr_scheduler_type: linear 23 | resume_from_checkpoint: None 24 | use_compute_metrics: False 25 | 26 | # lora hyperparams 27 | lora_r: 8 28 | lora_alpha: 16 29 | lora_dropout: 0.05 30 | lora_target_modules: [query_key_value, xxx] 31 | 32 | # llm hyperparams 33 | add_eos_token: True 34 | group_by_length: True 35 | 36 | # wandb params 37 | wandb_entity: salmons 38 | wandb_project: final_test 39 | wandb_run_name: test 40 | 41 | # inference args 42 | dataset_type: 'single' 43 | save_file_name: 'geval.csv' -------------------------------------------------------------------------------- /finetuning/inference.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import yaml 4 | 5 | import torch 6 | from transformers import AutoModelForCausalLM, AutoTokenizer, TextStreamer 7 | 8 | from utils.arguments import TrainArguments 9 | from utils.prompter import Prompter 10 | 11 | 12 | def inference(train_args: TrainArguments): 13 | """ 14 | 주어진 TrainArguments에 따라 inference를 수행합니다. 15 | 16 | Args: 17 | train_args (TrainArguments): 모델과 관련한 인자들을 포함한 TrainArguments 객체 18 | 19 | Returns: 20 | None 21 | """ 22 | 23 | start = time.time() 24 | 25 | MODEL = train_args.merge_dir 26 | model = AutoModelForCausalLM.from_pretrained( 27 | MODEL, 28 | torch_dtype=torch.float16, 29 | low_cpu_mem_usage=True, 30 | ).to(device=f"cuda", non_blocking=True) 31 | tokenizer = AutoTokenizer.from_pretrained(MODEL) 32 | streamer = TextStreamer(tokenizer) 33 | model.eval() 34 | 35 | prompter = Prompter(train_args.prompt_template_name) 36 | 37 | def gen(x): 38 | inputs = tokenizer( 39 | x, 40 | return_tensors='pt', 41 | return_token_type_ids=False 42 | ) 43 | inputs = {name: tensor.to(model.device) for name, tensor in inputs.items()} # move to same device as model 44 | gened = model.generate( 45 | **inputs, 46 | streamer=streamer, 47 | max_new_tokens=256, 48 | early_stopping=True, 49 | do_sample=True, 50 | eos_token_id=2, 51 | ) 52 | 53 | gened_list = gened[0].tolist() 54 | 55 | try: 56 | eos_token_position = gened_list.index(2) 57 | gened_list = gened_list[:eos_token_position] 58 | except ValueError: 59 | pass 60 | 61 | print(tokenizer.decode(gened_list)) 62 | 63 | end = time.time() 64 | 65 | print(f'generation 하기 직전까지 걸린 시간 : {int(end-start)}초') 66 | 67 | messages = ['오늘 날씨가 맑아서 기분이 좋아요!', 68 | '내일 시험인데 너무 떨려서 잠이 안와요. 어떡하죠?', 69 | '요즘 스트레스 때문에 자주 불면증에 시달리고 피곤해요.', 70 | '나는 커서 연기를 하는 배우가 될거야! 그게 나의 꿈이야', 71 | '이제 일 그만하고 집에가서 쉬고싶어, 넘 힘들어 ㅠㅠ', 72 | '가족과 오랜만에 여행을 가려고 하는데 좋은 장소 추천해주세요.', 73 | '새로 시작한 직장에서 복잡한 인간 관계 때문에 힘들어요.', 74 | '사실 제가 정확히 무슨 목표를 가지고 있는지 잘 모르겠어요.', 75 | '공부하다가 문득, 왜 이렇게 열심히 해야 하는지 의문이 들어요.', 76 | '요즘에 계속 머리가 아파서 고민이에요.', 77 | '날이 좋아서 친구들과 함께 소풍을 갔어요. 햇살이 너무 좋았어요!', 78 | '새로 산 피아노로 연습하는데, 손가락이 잘 안 따라가요.', 79 | '주말에 본 영화가 너무 재밌어서 아직도 생각나네요.', 80 | '요즘 가족들과 사이가 좋지 않아서 스트레스 받아요.', 81 | '저는 어릴 때부터 음악에 관심이 많아서 지금은 밴드 활동을 하고 있어요.' 82 | ] 83 | 84 | for message in messages: 85 | start = time.time() 86 | prompt = prompter.generate_prompt(message) 87 | gen(prompt) 88 | end = time.time() 89 | print(f'걸린 시간 : {int(end-start)}초') 90 | print('##################################################################') 91 | 92 | 93 | def load_config(config_file: str): 94 | with open(config_file, 'r') as f: 95 | config = yaml.safe_load(f) 96 | return TrainArguments(**config) 97 | 98 | 99 | def main(): 100 | try: 101 | config_path = sys.argv[1] 102 | except IndexError: 103 | config_path = './config.yaml' 104 | train_args = load_config(config_path) 105 | inference(train_args) 106 | 107 | 108 | if __name__ == "__main__": 109 | main() -------------------------------------------------------------------------------- /finetuning/langchain_inference.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "attachments": {}, 5 | "cell_type": "markdown", 6 | "metadata": {}, 7 | "source": [ 8 | "## inference 대화하면서 확인해보기\n", 9 | "- prompt template은 본인이 실험할 때 학습시킨 걸로 바꿔주세요(multi 그냥 쓰면 그대로!)\n", 10 | "- .evn 파일에 허깅페이스와 openai token이 필요합니다. 만약 openai billing이 등록되어있지 않거나 크레딧이 없으면 오류가 뜹니다." 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": 3, 16 | "metadata": {}, 17 | "outputs": [ 18 | { 19 | "name": "stderr", 20 | "output_type": "stream", 21 | "text": [ 22 | "Loading checkpoint shards: 100%|██████████| 3/3 [00:17<00:00, 6.00s/it]\n" 23 | ] 24 | } 25 | ], 26 | "source": [ 27 | "import torch\n", 28 | "from transformers import AutoModelForCausalLM, AutoTokenizer, TextStreamer, pipeline\n", 29 | "from langchain.llms import HuggingFacePipeline\n", 30 | "from dotenv import load_dotenv\n", 31 | "\n", 32 | "load_dotenv()\n", 33 | "\n", 34 | "test_model_name = \"test_4\"\n", 35 | "MODEL = f\"../finetuning/model/lora_merged/{test_model_name}\"\n", 36 | "\n", 37 | "model = AutoModelForCausalLM.from_pretrained(\n", 38 | " MODEL,\n", 39 | " torch_dtype=torch.float16,\n", 40 | " low_cpu_mem_usage=True,\n", 41 | ").to(device=f\"cuda\", non_blocking=True)\n", 42 | "tokenizer = AutoTokenizer.from_pretrained(MODEL)\n", 43 | "streamer = TextStreamer(tokenizer)\n", 44 | "\n", 45 | "model.eval()\n", 46 | "model.config.use_cache = True" 47 | ] 48 | }, 49 | { 50 | "cell_type": "code", 51 | "execution_count": 77, 52 | "execution_count": 4, 53 | 54 | "metadata": {}, 55 | "outputs": [ 56 | { 57 | "name": "stderr", 58 | "output_type": "stream", 59 | "text": [ 60 | "Xformers is not installed correctly. If you want to use memory_efficient_attention to accelerate training use the following command to install Xformers\n", 61 | "pip install xformers.\n" 62 | ] 63 | } 64 | ], 65 | "source": [ 66 | "pipe = pipeline(\n", 67 | " task='text-generation',\n", 68 | " model=model,\n", 69 | " tokenizer=tokenizer,\n", 70 | " streamer=streamer,\n", 71 | " device=0,\n", 72 | " min_new_tokens=20,\n", 73 | " max_new_tokens=128,\n", 74 | " early_stopping=True,\n", 75 | " do_sample=True,\n", 76 | " eos_token_id=2,\n", 77 | " repetition_penalty=1.1,\n", 78 | " temperature=0.9,\n", 79 | " top_k=50,\n", 80 | " top_p=0.95,\n", 81 | ")\n", 82 | "local_llm = HuggingFacePipeline(pipeline=pipe)" 83 | ] 84 | }, 85 | { 86 | "attachments": {}, 87 | "cell_type": "markdown", 88 | "metadata": {}, 89 | "source": [ 90 | "## Vector store-backed memory\n", 91 | "[Vector store-backed memory link](https://python.langchain.com/docs/modules/memory/how_to/vectorstore_retriever_memory)\n", 92 | "\n", 93 | "VectorStoreRetrieverMemory는 VectorDB에 메모리를 저장하고 호출될 때마다 가장 '두드러진' 상위 K개의 문서를 쿼리합니다.\n", 94 | "\n", 95 | "이는 상호작용의 순서를 명시적으로 추적하지 않는다는 점에서 다른 대부분의 메모리 클래스와 다릅니다.\n", 96 | "\n", 97 | "이 경우 \"문서\"는 이전 대화 스니펫입니다. 이는 대화 초기에 AI가 들었던 관련 정보를 참조하는 데 유용할 수 있습니다." 98 | ] 99 | }, 100 | { 101 | "attachments": {}, 102 | "cell_type": "markdown", 103 | "metadata": {}, 104 | "source": [ 105 | "### chain에서 활용" 106 | ] 107 | }, 108 | { 109 | "cell_type": "code", 110 | "execution_count": 80, 111 | "metadata": {}, 112 | "outputs": [], 113 | "source": [ 114 | "import faiss\n", 115 | "from langchain.embeddings.openai import OpenAIEmbeddings\n", 116 | "from langchain.memory import VectorStoreRetrieverMemory\n", 117 | "from langchain.chains import ConversationChain\n", 118 | "from langchain.prompts import PromptTemplate\n", 119 | "from langchain.docstore import InMemoryDocstore\n", 120 | "from langchain.vectorstores import FAISS\n", 121 | "\n", 122 | "embedding_size = 1536 # Dimensions of the OpenAIEmbeddings\n", 123 | "index = faiss.IndexFlatL2(embedding_size)\n", 124 | "embedding_fn = OpenAIEmbeddings().embed_query\n", 125 | "vectorstore = FAISS(embedding_fn, index, InMemoryDocstore({}), {})\n", 126 | "retriever = vectorstore.as_retriever(search_kwargs=dict(k=2))\n", 127 | "memory = VectorStoreRetrieverMemory(retriever=retriever)\n", 128 | "\n", 129 | "template = \"\"\"이전 대화와 현재 대화의 명령어를 참고하여 상황에 공감하고 친절한 응답을 생성해주세요. 응답 마지막에는 지금까지의 내용과 관련된 질문을 해주세요.\\n\\n[이전 대화]\\n{history}\\n\\n[현재 대화]\\n### 명령어:\\n{### 명령어}\\n\\n### 응답:\\n\"\"\"\n", 130 | 131 | "\n", 132 | "PROMPT = PromptTemplate(\n", 133 | " input_variables=[\"history\", \"### 명령어\"], template=template\n", 134 | ")\n", 135 | "\n", 136 | "conversation_with_vectors = ConversationChain(\n", 137 | " llm=local_llm,\n", 138 | " prompt=PROMPT,\n", 139 | " memory=memory,\n", 140 | " verbose=True,\n", 141 | " input_key='### 명령어',\n", 142 | " output_key='### 응답'\n", 143 | ")\n", 144 | "\n", 145 | "# 대답 생성 함수\n", 146 | "def get_response(question):\n", 147 | " input_dict = {'### 명령어': question}\n", 148 | " res = conversation_with_vectors.predict(**input_dict)\n", 149 | " return res" 150 | ] 151 | }, 152 | { 153 | "cell_type": "code", 154 | "execution_count": 6, 155 | "metadata": {}, 156 | "outputs": [ 157 | { 158 | "name": "stderr", 159 | "output_type": "stream", 160 | "text": [ 161 | "Setting `pad_token_id` to `eos_token_id`:2 for open-end generation.\n" 162 | ] 163 | }, 164 | { 165 | "name": "stdout", 166 | "output_type": "stream", 167 | "text": [ 168 | "\n", 169 | "\n", 170 | "\u001b[1m> Entering new chain...\u001b[0m\n", 171 | "Prompt after formatting:\n", 172 | "\u001b[32;1m\u001b[1;3m이전 대화와 현재 대화의 input을 참고하여 상황에 공감하고 친절한 답변을 생성해주세요. 답변 마지막에는 지금까지의 내용과 관련된 질문을 해주세요.\n", 173 | "\n", 174 | "[이전 대화]\n", 175 | "\n", 176 | "\n", 177 | "[현재 대화]\n", 178 | "### 명령어: 요즘 하는 프로젝트 때문에 스트레스를 많이 받고 있어\n", 179 | "\n", 180 | "### 응답:\n", 181 | "\u001b[0m\n", 182 | "이전 대화와 현재 대화의 input을 참고하여 상황에 공감하고 친절한 답변을 생성해주세요. 답변 마지막에는 지금까지의 내용과 관련된 질문을 해주세요.\n", 183 | "\n", 184 | "[이전 대화]\n", 185 | "\n", 186 | "\n", 187 | "[현재 대화]\n", 188 | "### 명령어: 요즘 하는 프로젝트 때문에 스트레스를 많이 받고 있어\n", 189 | "\n", 190 | "### 응답:\n", 191 | "하지만, 인해 겪게 되신 어려움이 정말 많다는 것은 잘 알아요! 너무 부담스럽거나 압박감으로 느껴질 때면 잠시 멈춰서 스스로에게 휴식 시간도 주면서 자신만의 페이스대로 진행할 필요가 종종 생깁니다..저랑 같이 얘기하며 마음 편히 쉬어가실래요? 제 모든 능력 중 가장 좋다고 생각되시도록 도와드릴게요!!<|endoftext|>\n", 192 | "\n", 193 | "\u001b[1m> Finished chain.\u001b[0m\n", 194 | "하지만, 저랑 같이 얘기하며 마음 편히 쉬어가실래요? 제 모든 능력 중 가장 좋다고 생각되시도록 도와드릴게요!! 멈춰서 스스로에게 휴식 시간도 주면서 자신만의 페이스대로 진행할 필요가 종종 생깁니다..\n" 195 | ] 196 | } 197 | ], 198 | "source": [ 199 | "res = get_response('요즘 하는 프로젝트 때문에 스트레스를 많이 받고 있어')\n", 200 | "print(res)" 201 | ] 202 | }, 203 | { 204 | "cell_type": "code", 205 | "execution_count": null, 206 | "metadata": {}, 207 | "outputs": [ 208 | { 209 | "name": "stderr", 210 | "output_type": "stream", 211 | "text": [ 212 | "Setting `pad_token_id` to `eos_token_id`:2 for open-end generation.\n" 213 | ] 214 | }, 215 | { 216 | "name": "stdout", 217 | "output_type": "stream", 218 | "text": [ 219 | "\n", 220 | "\n", 221 | "\u001b[1m> Entering new chain...\u001b[0m\n", 222 | "Prompt after formatting:\n", 223 | "\u001b[32;1m\u001b[1;3m이전 대화와 현재 대화의 input을 참고하여 상황에 공감하고 친절한 답변을 생성해주세요. 답변 마지막에는 지금까지의 내용과 관련된 질문을 해주세요.\n", 224 | "\n", 225 | "[이전 대화]\n", 226 | "### 명령어: 요즘 하는 프로젝트 때문에 스트레스를 많이 받고 있어\n", 227 | "### 응답: 프로젝트가 잘 진행되지 않아서 고민이 많으신 것 같네요! 힘든 시기일 때 누군가에게 털어놓기만 해도 기분 전환이나 아이디어 공유 등 다른 도움으로 이어질 수있습니다 :)\n", 228 | "\n", 229 | "[현재 대화]\n", 230 | "### 명령어: 그렇지..! 다들 하는건데 나만 못할 순 없지! 좋아 잘 해볼게\n", 231 | "\n", 232 | "### 응답:\n", 233 | "\u001b[0m\n", 234 | "이전 대화와 현재 대화의 input을 참고하여 상황에 공감하고 친절한 답변을 생성해주세요. 답변 마지막에는 지금까지의 내용과 관련된 질문을 해주세요.\n", 235 | "\n", 236 | "[이전 대화]\n", 237 | "### 명령어: 요즘 하는 프로젝트 때문에 스트레스를 많이 받고 있어\n", 238 | "### 응답: 프로젝트가 잘 진행되지 않아서 고민이 많으신 것 같네요! 힘든 시기일 때 누군가에게 털어놓기만 해도 기분 전환이나 아이디어 공유 등 다른 도움으로 이어질 수있습니다 :)\n", 239 | "\n", 240 | "[현재 대화]\n", 241 | "### 명령어: 그렇지..! 다들 하는건데 나만 못할 순 없지! 좋아 잘 해볼게\n", 242 | "\n", 243 | "### 응답:\n", 244 | "저도 이해해요, 하지만 걱정하실 필요 없이 할 일은 열심히 하고 자신한테 좋다고 생각하면 그냥 계속 밀고가면 돼요!! 당신 곁에서 응원할게요 💪<|endoftext|>\n", 245 | "\n", 246 | "\u001b[1m> Finished chain.\u001b[0m\n", 247 | "저도 이해해요, 하지만 걱정하실 필요 없이 할 일은 열심히 하고 자신한테 좋다고 생각하면 그냥 계속 밀고가면 돼요!! 당신 곁에서 응원할게요 💪\n" 248 | ] 249 | } 250 | ], 251 | "source": [ 252 | "res = get_response('그렇지..! 다들 하는건데 나만 못할 순 없지! 좋아 잘 해볼게')\n", 253 | "print(res)" 254 | ] 255 | }, 256 | { 257 | "cell_type": "code", 258 | "execution_count": null, 259 | "metadata": {}, 260 | "outputs": [ 261 | { 262 | "name": "stderr", 263 | "output_type": "stream", 264 | "text": [ 265 | "Setting `pad_token_id` to `eos_token_id`:2 for open-end generation.\n" 266 | ] 267 | }, 268 | { 269 | "name": "stdout", 270 | "output_type": "stream", 271 | "text": [ 272 | "\n", 273 | "\n", 274 | "\u001b[1m> Entering new chain...\u001b[0m\n", 275 | "Prompt after formatting:\n", 276 | "\u001b[32;1m\u001b[1;3m이전 대화와 현재 대화의 input을 참고하여 상황에 공감하고 친절한 답변을 생성해주세요. 답변 마지막에는 지금까지의 내용과 관련된 질문을 해주세요.\n", 277 | "\n", 278 | "[이전 대화]\n", 279 | "### 명령어: 요즘 하는 프로젝트 때문에 스트레스를 많이 받고 있어\n", 280 | "### 응답: 프로젝트가 잘 진행되지 않아서 고민이 많으신 것 같네요! 힘든 시기일 때 누군가에게 털어놓기만 해도 기분 전환이나 아이디어 공유 등 다른 도움으로 이어질 수있습니다 :)\n", 281 | "### 명령어: 그렇지..! 다들 하는건데 나만 못할 순 없지! 좋아 잘 해볼게\n", 282 | "### 응답: 저도 이해해요, 하지만 걱정하실 필요 없이 할 일은 열심히 하고 자신한테 좋다고 생각하면 그냥 계속 밀고가면 돼요!! 당신 곁에서 응원할게요 💪\n", 283 | "\n", 284 | "[현재 대화]\n", 285 | "### 명령어: 응원 고마워! 생각해보니 오늘 금요일이네. 주말에 날씨가 좋을까?\n", 286 | "\n", 287 | "### 응답:\n", 288 | "\u001b[0m\n", 289 | "이전 대화와 현재 대화의 input을 참고하여 상황에 공감하고 친절한 답변을 생성해주세요. 답변 마지막에는 지금까지의 내용과 관련된 질문을 해주세요.\n", 290 | "\n", 291 | "[이전 대화]\n", 292 | "### 명령어: 요즘 하는 프로젝트 때문에 스트레스를 많이 받고 있어\n", 293 | "### 응답: 프로젝트가 잘 진행되지 않아서 고민이 많으신 것 같네요! 힘든 시기일 때 누군가에게 털어놓기만 해도 기분 전환이나 아이디어 공유 등 다른 도움으로 이어질 수있습니다 :)\n", 294 | "### 명령어: 그렇지..! 다들 하는건데 나만 못할 순 없지! 좋아 잘 해볼게\n", 295 | "### 응답: 저도 이해해요, 하지만 걱정하실 필요 없이 할 일은 열심히 하고 자신한테 좋다고 생각하면 그냥 계속 밀고가면 돼요!! 당신 곁에서 응원할게요 💪\n", 296 | "\n", 297 | "[현재 대화]\n", 298 | "### 명령어: 응원 고마워! 생각해보니 오늘 금요일이네. 주말에 날씨가 좋을까?\n", 299 | "\n", 300 | "### 응답:\n", 301 | "날씨 앱 확인 결과 이번 주 내내 화창할 예정입니다~! 즐거운 시간 보내시길 바랍니다 ✨️❤ #주말계획<|endoftext|>\n", 302 | "\n", 303 | "\u001b[1m> Finished chain.\u001b[0m\n", 304 | "날씨 앱 확인 결과 이번 주 내내 화창할 예정입니다~! 즐거운 시간 보내시길 바랍니다 ✨️❤ #주말계획\n" 305 | ] 306 | } 307 | ], 308 | "source": [ 309 | "res = get_response('응원 고마워! 생각해보니 오늘 금요일이네. 주말에 날씨가 좋을까?')\n", 310 | "print(res)" 311 | ] 312 | }, 313 | { 314 | "cell_type": "code", 315 | "execution_count": null, 316 | "metadata": {}, 317 | "outputs": [ 318 | { 319 | "name": "stderr", 320 | "output_type": "stream", 321 | "text": [ 322 | "Setting `pad_token_id` to `eos_token_id`:2 for open-end generation.\n" 323 | ] 324 | }, 325 | { 326 | "name": "stdout", 327 | "output_type": "stream", 328 | "text": [ 329 | "\n", 330 | "\n", 331 | "\u001b[1m> Entering new chain...\u001b[0m\n", 332 | "Prompt after formatting:\n", 333 | "\u001b[32;1m\u001b[1;3m이전 대화와 현재 대화의 input을 참고하여 상황에 공감하고 친절한 답변을 생성해주세요. 답변 마지막에는 지금까지의 내용과 관련된 질문을 해주세요.\n", 334 | "\n", 335 | "[이전 대화]\n", 336 | "### 명령어: 응원 고마워! 생각해보니 오늘 금요일이네. 주말에 날씨가 좋을까?\n", 337 | "### 응답: 날씨 앱 확인 결과 이번 주 내내 화창할 예정입니다~! 즐거운 시간 보내시길 바랍니다 ✨️❤ #주말계획\n", 338 | "### 명령어: 요즘 하는 프로젝트 때문에 스트레스를 많이 받고 있어\n", 339 | "### 응답: 프로젝트가 잘 진행되지 않아서 고민이 많으신 것 같네요! 힘든 시기일 때 누군가에게 털어놓기만 해도 기분 전환이나 아이디어 공유 등 다른 도움으로 이어질 수있습니다 :)\n", 340 | "\n", 341 | "[현재 대화]\n", 342 | "### 명령어: 오잉 내가 확인해봤을 때는 비가 온다던데?\n", 343 | "\n", 344 | "### 응답:\n", 345 | "\u001b[0m\n", 346 | "이전 대화와 현재 대화의 input을 참고하여 상황에 공감하고 친절한 답변을 생성해주세요. 답변 마지막에는 지금까지의 내용과 관련된 질문을 해주세요.\n", 347 | "\n", 348 | "[이전 대화]\n", 349 | "### 명령어: 응원 고마워! 생각해보니 오늘 금요일이네. 주말에 날씨가 좋을까?\n", 350 | "### 응답: 날씨 앱 확인 결과 이번 주 내내 화창할 예정입니다~! 즐거운 시간 보내시길 바랍니다 ✨️❤ #주말계획\n", 351 | "### 명령어: 요즘 하는 프로젝트 때문에 스트레스를 많이 받고 있어\n", 352 | "### 응답: 프로젝트가 잘 진행되지 않아서 고민이 많으신 것 같네요! 힘든 시기일 때 누군가에게 털어놓기만 해도 기분 전환이나 아이디어 공유 등 다른 도움으로 이어질 수있습니다 :)\n", 353 | "\n", 354 | "[현재 대화]\n", 355 | "### 명령어: 오잉 내가 확인해봤을 때는 비가 온다던데?\n", 356 | "\n", 357 | "### 응답:\n", 358 | "죄송하지만 제 정보로 인해 오해하신 점 사과드립니다, AI로서 저도 기상예보나 실시간 일기 예보 기능은 없지만 정확성보다 유용함(usefulness-right as far away points of view), 즉 해당 지역에서 가장 가까운 실제 상태 또는 과거 데이터 통계치 제공 여부 측면에선 더 신뢰성이 높다고 말씀드리겠으며 이 경우 기상청 자료 링크 및 사용 방법 안내 또한 가능합니다..<|endoftext|>\n", 359 | "\n", 360 | "\u001b[1m> Finished chain.\u001b[0m\n", 361 | "죄송하지만 제 정보로 인해 오해하신 점 사과드립니다, AI로서 저도 기상예보나 실시간 일기 예보 기능은 없지만 정확성보다 유용함(usefulness-right as far away points of view), 즉 해당 지역에서 가장 가까운 실제 상태 또는 과거 데이터 통계치 제공 여부 측면에선 더 신뢰성이 높다고 말씀드리겠으며 이 경우 기상청 자료 링크 및 사용 방법 안내 또한 가능합니다..\n" 362 | ] 363 | } 364 | ], 365 | "source": [ 366 | "res = get_response('오잉 내가 확인해봤을 때는 비가 온다던데?')\n", 367 | "print(res)" 368 | ] 369 | }, 370 | { 371 | "cell_type": "code", 372 | "execution_count": null, 373 | "metadata": {}, 374 | "outputs": [ 375 | { 376 | "name": "stderr", 377 | "output_type": "stream", 378 | "text": [ 379 | "Setting `pad_token_id` to `eos_token_id`:2 for open-end generation.\n" 380 | ] 381 | }, 382 | { 383 | "name": "stdout", 384 | "output_type": "stream", 385 | "text": [ 386 | "\n", 387 | "\n", 388 | "\u001b[1m> Entering new chain...\u001b[0m\n", 389 | "Prompt after formatting:\n", 390 | "\u001b[32;1m\u001b[1;3m이전 대화와 현재 대화의 input을 참고하여 상황에 공감하고 친절한 답변을 생성해주세요. 답변 마지막에는 지금까지의 내용과 관련된 질문을 해주세요.\n", 391 | "\n", 392 | "[이전 대화]\n", 393 | "### 명령어: 응원 고마워! 생각해보니 오늘 금요일이네. 주말에 날씨가 좋을까?\n", 394 | "### 응답: 날씨 앱 확인 결과 이번 주 내내 화창할 예정입니다~! 즐거운 시간 보내시길 바랍니다 ✨️❤ #주말계획\n", 395 | "### 명령어: 오잉 내가 확인해봤을 때는 비가 온다던데?\n", 396 | "### 응답: 죄송하지만 제 정보로 인해 오해하신 점 사과드립니다, AI로서 저도 기상예보나 실시간 일기 예보 기능은 없지만 정확성보다 유용함(usefulness-right as far away points of view), 즉 해당 지역에서 가장 가까운 실제 상태 또는 과거 데이터 통계치 제공 여부 측면에선 더 신뢰성이 높다고 말씀드리겠으며 이 경우 기상청 자료 링크 및 사용 방법 안내 또한 가능합니다..\n", 397 | "\n", 398 | "[현재 대화]\n", 399 | "### 명령어: 알겠어 고마워. 그런데 나는 주말에도 팀미팅이 있어. 날씨는 별로 중요하지 않아!\n", 400 | "\n", 401 | "### 응답:\n", 402 | "\u001b[0m\n", 403 | "이전 대화와 현재 대화의 input을 참고하여 상황에 공감하고 친절한 답변을 생성해주세요. 답변 마지막에는 지금까지의 내용과 관련된 질문을 해주세요.\n", 404 | "\n", 405 | "[이전 대화]\n", 406 | "### 명령어: 응원 고마워! 생각해보니 오늘 금요일이네. 주말에 날씨가 좋을까?\n", 407 | "### 응답: 날씨 앱 확인 결과 이번 주 내내 화창할 예정입니다~! 즐거운 시간 보내시길 바랍니다 ✨️❤ #주말계획\n", 408 | "### 명령어: 오잉 내가 확인해봤을 때는 비가 온다던데?\n", 409 | "### 응답: 죄송하지만 제 정보로 인해 오해하신 점 사과드립니다, AI로서 저도 기상예보나 실시간 일기 예보 기능은 없지만 정확성보다 유용함(usefulness-right as far away points of view), 즉 해당 지역에서 가장 가까운 실제 상태 또는 과거 데이터 통계치 제공 여부 측면에선 더 신뢰성이 높다고 말씀드리겠으며 이 경우 기상청 자료 링크 및 사용 방법 안내 또한 가능합니다..\n", 410 | "\n", 411 | "[현재 대화]\n", 412 | "### 명령어: 알겠어 고마워. 그런데 나는 주말에도 팀미팅이 있어. 날씨는 별로 중요하지 않아!\n", 413 | "\n", 414 | "### 응답:\n", 415 | "이해해요~ 그래서 저희가 추천드린 일정으로 변경해도 될까요 아니면 그냥 원래 계획대로 가셔도 괜찮으세요? 원하신다면 조정해서 다시 알려드릴게요 :)<|endoftext|>\n", 416 | "\n", 417 | "\u001b[1m> Finished chain.\u001b[0m\n", 418 | "이해해요~ 그래서 저희가 추천드린 일정으로 변경해도 될까요 아니면 그냥 원래 계획대로 가셔도 괜찮으세요? 원하신다면 조정해서 다시 알려드릴게요 :)\n" 419 | ] 420 | } 421 | ], 422 | "source": [ 423 | "res = get_response('알겠어 고마워. 그런데 나는 주말에도 팀미팅이 있어. 날씨는 별로 중요하지 않아!')\n", 424 | "print(res)" 425 | ] 426 | }, 427 | { 428 | "cell_type": "code", 429 | "execution_count": null, 430 | "metadata": {}, 431 | "outputs": [ 432 | { 433 | "name": "stderr", 434 | "output_type": "stream", 435 | "text": [ 436 | "Setting `pad_token_id` to `eos_token_id`:2 for open-end generation.\n" 437 | ] 438 | }, 439 | { 440 | "name": "stdout", 441 | "output_type": "stream", 442 | "text": [ 443 | "\n", 444 | "\n", 445 | "\u001b[1m> Entering new chain...\u001b[0m\n", 446 | "Prompt after formatting:\n", 447 | "\u001b[32;1m\u001b[1;3m이전 대화와 현재 대화의 input을 참고하여 상황에 공감하고 친절한 답변을 생성해주세요. 답변 마지막에는 지금까지의 내용과 관련된 질문을 해주세요.\n", 448 | "\n", 449 | "[이전 대화]\n", 450 | "### 명령어: 응원 고마워! 생각해보니 오늘 금요일이네. 주말에 날씨가 좋을까?\n", 451 | "### 응답: 날씨 앱 확인 결과 이번 주 내내 화창할 예정입니다~! 즐거운 시간 보내시길 바랍니다 ✨️❤ #주말계획\n", 452 | "### 명령어: 그렇지..! 다들 하는건데 나만 못할 순 없지! 좋아 잘 해볼게\n", 453 | "### 응답: 저도 이해해요, 하지만 걱정하실 필요 없이 할 일은 열심히 하고 자신한테 좋다고 생각하면 그냥 계속 밀고가면 돼요!! 당신 곁에서 응원할게요 💪\n", 454 | "\n", 455 | "[현재 대화]\n", 456 | "### 명령어: 좋아좋아 너 친절하구낭\n", 457 | "\n", 458 | "### 응답:\n", 459 | "\u001b[0m\n", 460 | "이전 대화와 현재 대화의 input을 참고하여 상황에 공감하고 친절한 답변을 생성해주세요. 답변 마지막에는 지금까지의 내용과 관련된 질문을 해주세요.\n", 461 | "\n", 462 | "[이전 대화]\n", 463 | "### 명령어: 응원 고마워! 생각해보니 오늘 금요일이네. 주말에 날씨가 좋을까?\n", 464 | "### 응답: 날씨 앱 확인 결과 이번 주 내내 화창할 예정입니다~! 즐거운 시간 보내시길 바랍니다 ✨️❤ #주말계획\n", 465 | "### 명령어: 그렇지..! 다들 하는건데 나만 못할 순 없지! 좋아 잘 해볼게\n", 466 | "### 응답: 저도 이해해요, 하지만 걱정하실 필요 없이 할 일은 열심히 하고 자신한테 좋다고 생각하면 그냥 계속 밀고가면 돼요!! 당신 곁에서 응원할게요 💪\n", 467 | "\n", 468 | "[현재 대화]\n", 469 | "### 명령어: 좋아좋아 너 친절하구낭\n", 470 | "\n", 471 | "### 응답:\n", 472 | "고마워 내 친구야... 정말 힘이나고 기분전환되었단다 넌 여전히 멋진 사람이야 난 네편이지 항상 그럴거구 그러니깐 우리 힘내자 사랑한다<|endoftext|>\n", 473 | "\n", 474 | "\u001b[1m> Finished chain.\u001b[0m\n", 475 | "고마워 내 친구야... 정말 힘이나고 기분전환되었단다 넌 여전히 멋진 사람이야 난 네편이지 항상 그럴거구 그러니깐 우리 힘내자 사랑한다\n" 476 | ] 477 | } 478 | ], 479 | "source": [ 480 | "res = get_response('좋아좋아 너 친절하구낭')\n", 481 | "print(res)" 482 | ] 483 | }, 484 | { 485 | "cell_type": "code", 486 | "execution_count": null, 487 | "metadata": {}, 488 | "outputs": [ 489 | { 490 | "name": "stderr", 491 | "output_type": "stream", 492 | "text": [ 493 | "Setting `pad_token_id` to `eos_token_id`:2 for open-end generation.\n" 494 | ] 495 | }, 496 | { 497 | "name": "stdout", 498 | "output_type": "stream", 499 | "text": [ 500 | "\n", 501 | "\n", 502 | "\u001b[1m> Entering new chain...\u001b[0m\n", 503 | "Prompt after formatting:\n", 504 | "\u001b[32;1m\u001b[1;3m이전 대화와 현재 대화의 input을 참고하여 상황에 공감하고 친절한 답변을 생성해주세요. 답변 마지막에는 지금까지의 내용과 관련된 질문을 해주세요.\n", 505 | "\n", 506 | "[이전 대화]\n", 507 | "### 명령어: 알겠어 고마워. 그런데 나는 주말에도 팀미팅이 있어. 날씨는 별로 중요하지 않아!\n", 508 | "### 응답: 이해해요~ 그래서 저희가 추천드린 일정으로 변경해도 될까요 아니면 그냥 원래 계획대로 가셔도 괜찮으세요? 원하신다면 조정해서 다시 알려드릴게요 :)\n", 509 | "### 명령어: 응원 고마워! 생각해보니 오늘 금요일이네. 주말에 날씨가 좋을까?\n", 510 | "### 응답: 날씨 앱 확인 결과 이번 주 내내 화창할 예정입니다~! 즐거운 시간 보내시길 바랍니다 ✨️❤ #주말계획\n", 511 | "\n", 512 | "[현재 대화]\n", 513 | "### 명령어: 나는 오늘 점심으로 냉면을 먹을 거야.\n", 514 | "\n", 515 | "### 응답:\n", 516 | "\u001b[0m\n", 517 | "이전 대화와 현재 대화의 input을 참고하여 상황에 공감하고 친절한 답변을 생성해주세요. 답변 마지막에는 지금까지의 내용과 관련된 질문을 해주세요.\n", 518 | "\n", 519 | "[이전 대화]\n", 520 | "### 명령어: 알겠어 고마워. 그런데 나는 주말에도 팀미팅이 있어. 날씨는 별로 중요하지 않아!\n", 521 | "### 응답: 이해해요~ 그래서 저희가 추천드린 일정으로 변경해도 될까요 아니면 그냥 원래 계획대로 가셔도 괜찮으세요? 원하신다면 조정해서 다시 알려드릴게요 :)\n", 522 | "### 명령어: 응원 고마워! 생각해보니 오늘 금요일이네. 주말에 날씨가 좋을까?\n", 523 | "### 응답: 날씨 앱 확인 결과 이번 주 내내 화창할 예정입니다~! 즐거운 시간 보내시길 바랍니다 ✨️❤ #주말계획\n", 524 | "\n", 525 | "[현재 대화]\n", 526 | "### 명령어: 나는 오늘 점심으로 냉면을 먹을 거야.\n", 527 | "\n", 528 | "### 응답:\n", 529 | "좋은 선택 같네요, 냉면 너무 기대되는데요 😊 맛있게 드세요 ~!! 다음 회의에서 봐요 ---- [종료 버튼 누름<|endoftext|>\n", 530 | "\n", 531 | "\u001b[1m> Finished chain.\u001b[0m\n", 532 | "좋은 선택 같네요, 냉면 너무 기대되는데요 😊 맛있게 드세요 ~!! 다음 회의에서 봐요 ---- [종료 버튼 누름\n" 533 | ] 534 | } 535 | ], 536 | "source": [ 537 | "res = get_response('나는 오늘 점심으로 냉면을 먹을 거야.')\n", 538 | "print(res)" 539 | ] 540 | }, 541 | { 542 | "cell_type": "code", 543 | "execution_count": null, 544 | "metadata": {}, 545 | "outputs": [ 546 | { 547 | "name": "stderr", 548 | "output_type": "stream", 549 | "text": [ 550 | "Setting `pad_token_id` to `eos_token_id`:2 for open-end generation.\n" 551 | ] 552 | }, 553 | { 554 | "name": "stdout", 555 | "output_type": "stream", 556 | "text": [ 557 | "\n", 558 | "\n", 559 | "\u001b[1m> Entering new chain...\u001b[0m\n", 560 | "Prompt after formatting:\n", 561 | "\u001b[32;1m\u001b[1;3m이전 대화와 현재 대화의 input을 참고하여 상황에 공감하고 친절한 답변을 생성해주세요. 답변 마지막에는 지금까지의 내용과 관련된 질문을 해주세요.\n", 562 | "\n", 563 | "[이전 대화]\n", 564 | "### 명령어: 나는 오늘 점심으로 냉면을 먹을 거야.\n", 565 | "### 응답: 좋은 선택 같네요, 냉면 너무 기대되는데요 😊 맛있게 드세요 ~!! 다음 회의에서 봐요 ---- [종료 버튼 누름\n", 566 | "### 명령어: 알겠어 고마워. 그런데 나는 주말에도 팀미팅이 있어. 날씨는 별로 중요하지 않아!\n", 567 | "### 응답: 이해해요~ 그래서 저희가 추천드린 일정으로 변경해도 될까요 아니면 그냥 원래 계획대로 가셔도 괜찮으세요? 원하신다면 조정해서 다시 알려드릴게요 :)\n", 568 | "\n", 569 | "[현재 대화]\n", 570 | "### 명령어: 내가 오늘 점심으로 뭐 먹는다고 했지?\n", 571 | "\n", 572 | "### 응답:\n", 573 | "\u001b[0m\n", 574 | "이전 대화와 현재 대화의 input을 참고하여 상황에 공감하고 친절한 답변을 생성해주세요. 답변 마지막에는 지금까지의 내용과 관련된 질문을 해주세요.\n", 575 | "\n", 576 | "[이전 대화]\n", 577 | "### 명령어: 나는 오늘 점심으로 냉면을 먹을 거야.\n", 578 | "### 응답: 좋은 선택 같네요, 냉면 너무 기대되는데요 😊 맛있게 드세요 ~!! 다음 회의에서 봐요 ---- [종료 버튼 누름\n", 579 | "### 명령어: 알겠어 고마워. 그런데 나는 주말에도 팀미팅이 있어. 날씨는 별로 중요하지 않아!\n", 580 | "### 응답: 이해해요~ 그래서 저희가 추천드린 일정으로 변경해도 될까요 아니면 그냥 원래 계획대로 가셔도 괜찮으세요? 원하신다면 조정해서 다시 알려드릴게요 :)\n", 581 | "\n", 582 | "[현재 대화]\n", 583 | "### 명령어: 내가 오늘 점심으로 뭐 먹는다고 했지?\n", 584 | "\n", 585 | "### 응답:\n", 586 | "냉면이라고 하셨죠... 아 죄송합니다 제 실수입니다 비빔국수라고 말씀해 주신 것 맞으시나요.. 어쨌든 저도 그게 정말 끝내준다고 들었어요 잘됐군요 그거 꼭 드셔보시길 바랄게요 고마워요~!<|endoftext|>\n", 587 | "\n", 588 | "\u001b[1m> Finished chain.\u001b[0m\n", 589 | "냉면이라고 하셨죠... 아 죄송합니다 제 실수입니다 비빔국수라고 말씀해 주신 것 맞으시나요.. 어쨌든 저도 그게 정말 끝내준다고 들었어요 잘됐군요 그거 꼭 드셔보시길 바랄게요 고마워요~!\n" 590 | ] 591 | } 592 | ], 593 | "source": [ 594 | "res = get_response('내가 오늘 점심으로 뭐 먹는다고 했지?')\n", 595 | "print(res)" 596 | ] 597 | }, 598 | { 599 | "cell_type": "code", 600 | "execution_count": null, 601 | "metadata": {}, 602 | "outputs": [ 603 | { 604 | "name": "stderr", 605 | "output_type": "stream", 606 | "text": [ 607 | "Setting `pad_token_id` to `eos_token_id`:2 for open-end generation.\n" 608 | ] 609 | }, 610 | { 611 | "name": "stdout", 612 | "output_type": "stream", 613 | "text": [ 614 | "\n", 615 | "\n", 616 | "\u001b[1m> Entering new chain...\u001b[0m\n", 617 | "Prompt after formatting:\n", 618 | "\u001b[32;1m\u001b[1;3m이전 대화와 현재 대화의 input을 참고하여 상황에 공감하고 친절한 답변을 생성해주세요. 답변 마지막에는 지금까지의 내용과 관련된 질문을 해주세요.\n", 619 | "\n", 620 | "[이전 대화]\n", 621 | "### 명령어: 나는 오늘 점심으로 냉면을 먹을 거야.\n", 622 | "### 응답: 좋은 선택 같네요, 냉면 너무 기대되는데요 😊 맛있게 드세요 ~!! 다음 회의에서 봐요 ---- [종료 버튼 누름\n", 623 | "### 명령어: 내가 오늘 점심으로 뭐 먹는다고 했지?\n", 624 | "### 응답: 냉면이라고 하셨죠... 아 죄송합니다 제 실수입니다 비빔국수라고 말씀해 주신 것 맞으시나요.. 어쨌든 저도 그게 정말 끝내준다고 들었어요 잘됐군요 그거 꼭 드셔보시길 바랄게요 고마워요~!\n", 625 | "\n", 626 | "[현재 대화]\n", 627 | "### 명령어: 냉면일까, 비빔국수일까?\n", 628 | "\n", 629 | "### 응답:\n", 630 | "\u001b[0m\n", 631 | "이전 대화와 현재 대화의 input을 참고하여 상황에 공감하고 친절한 답변을 생성해주세요. 답변 마지막에는 지금까지의 내용과 관련된 질문을 해주세요.\n", 632 | "\n", 633 | "[이전 대화]\n", 634 | "### 명령어: 나는 오늘 점심으로 냉면을 먹을 거야.\n", 635 | "### 응답: 좋은 선택 같네요, 냉면 너무 기대되는데요 😊 맛있게 드세요 ~!! 다음 회의에서 봐요 ---- [종료 버튼 누름\n", 636 | "### 명령어: 내가 오늘 점심으로 뭐 먹는다고 했지?\n", 637 | "### 응답: 냉면이라고 하셨죠... 아 죄송합니다 제 실수입니다 비빔국수라고 말씀해 주신 것 맞으시나요.. 어쨌든 저도 그게 정말 끝내준다고 들었어요 잘됐군요 그거 꼭 드셔보시길 바랄게요 고마워요~!\n", 638 | "\n", 639 | "[현재 대화]\n", 640 | "### 명령어: 냉면일까, 비빔국수일까?\n", 641 | "\n", 642 | "### 응답:\n", 643 | "냉면이나 비냉 중 하나를 말하려는 건가요 아니면 둘 다요?! 알려주시면 감사할게요 ❤️✨<|endoftext|>\n", 644 | "\n", 645 | "\u001b[1m> Finished chain.\u001b[0m\n", 646 | "냉면이나 비냉 중 하나를 말하려는 건가요 아니면 둘 다요?! 알려주시면 감사할게요 ❤️✨\n" 647 | ] 648 | } 649 | ], 650 | "source": [ 651 | "res = get_response('오늘은 빨리 집가고 싶어')\n", 652 | "print(res)" 653 | ] 654 | } 655 | ], 656 | "metadata": { 657 | "kernelspec": { 658 | "display_name": "final", 659 | "language": "python", 660 | "name": "python3" 661 | }, 662 | "language_info": { 663 | "codemirror_mode": { 664 | "name": "ipython", 665 | "version": 3 666 | }, 667 | "file_extension": ".py", 668 | "mimetype": "text/x-python", 669 | "name": "python", 670 | "nbconvert_exporter": "python", 671 | "pygments_lexer": "ipython3", 672 | "version": "3.9.17" 673 | }, 674 | "orig_nbformat": 4 675 | }, 676 | "nbformat": 4, 677 | "nbformat_minor": 2 678 | } 679 | -------------------------------------------------------------------------------- /finetuning/merge_lora.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import yaml 3 | 4 | import torch 5 | from peft import PeftModel 6 | from transformers import AutoModelForCausalLM, AutoTokenizer 7 | 8 | from utils.arguments import TrainArguments 9 | 10 | 11 | def apply_lora(train_args: TrainArguments): 12 | print(f"Loading the base model from {train_args.base_model}") 13 | base = AutoModelForCausalLM.from_pretrained(train_args.base_model, torch_dtype=torch.float16, low_cpu_mem_usage=True) 14 | base_tokenizer = AutoTokenizer.from_pretrained(train_args.base_model, use_fast=False) 15 | 16 | print(f"Loading the LoRA adapter from {train_args.finetune_dir}") 17 | lora_model = PeftModel.from_pretrained(base, train_args.finetune_dir,torch_dtype=torch.float16) 18 | 19 | print("Applying the LoRA") 20 | model = lora_model.merge_and_unload() 21 | 22 | print(f"Saving the target model to {train_args.merge_dir}") 23 | model.save_pretrained(train_args.merge_dir) 24 | base_tokenizer.save_pretrained(train_args.merge_dir) 25 | 26 | 27 | def load_config(config_file: str): 28 | with open(config_file, 'r') as f: 29 | config = yaml.safe_load(f) 30 | return TrainArguments(**config) 31 | 32 | 33 | def main(): 34 | try: 35 | config_path = sys.argv[1] 36 | except IndexError: 37 | config_path = './config.yaml' 38 | train_args = load_config(config_path) 39 | apply_lora(train_args) 40 | 41 | 42 | if __name__ == "__main__": 43 | main() -------------------------------------------------------------------------------- /finetuning/mk_geval_prompts.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import time 4 | import yaml 5 | import pandas as pd 6 | from tqdm import tqdm 7 | 8 | import torch 9 | from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline 10 | 11 | from utils.arguments import TrainArguments 12 | from utils.prompter import Prompter 13 | 14 | 15 | g_eval_prompt = """두 사람 간의 대화가 주어집니다. 다음의 지시문(Instruction)을 받게 될 것입니다. 지시문은 이전 대화내용을 포함하며 현재 대화에 대한 응답(Response)이 제시됩니다. 16 | 당신의 작업은 응답을 평가 단계에 따라 응답을 평가하는 것입니다. 17 | 이 평가 기준을 꼼꼼히 읽고 이해하는 것이 중요합니다. 평가하는 동안 이 문서를 계속 열어두고 필요할 때 참조해 주세요. 18 | 19 | 평가 기준: 20 | - 이해 가능성 (0 - 1): Instruction에 기반하여 Response를 이해 할 수 있나요? 21 | - 자연스러움 (1 - 5): 사람이 자연스럽게 말할 법한 Instruction 인가요? 22 | - 맥락 유지 (1 - 5): Instruction을 고려했을 때 Response가 맥락을 유지하나요? 23 | - 흥미롭기 (1 - 5): Response가 지루한가요, 아니면 흥미로운가요? 24 | - Instruction 사용 (0 - 1): Instruction에 기반하여 Response를 생성 했나요? 25 | - 공감 능력 (0 - 1): Response에 Instruction의 내용에 기반한 공감의 내용이 있나요? 26 | - 대화 유도 (0 - 1): Response에 질문을 포함하여 사용자의 대답을 자연스럽게 유도하고 있나요? 27 | - 전반적인 품질 (1 - 10): 위의 답변을 바탕으로 이 발언의 전반적인 품질에 대한 인상은 어떤가요? 28 | 29 | 평가 단계: 30 | 1. Instruction, 그리고 Response을 주의깊게 읽습니다. 31 | 2. 위의 평가 기준에 따라 Response을 엄격하게 평가합니다. 32 | 33 | Instruction: 34 | {{instruction}} 35 | 36 | Response: 37 | {{response}} 38 | 39 | 40 | Result 41 | - 이해 가능성 (0 - 1): 42 | - 자연스러움 (1 - 5): 43 | - 맥락 유지 (1 - 5): 44 | - 흥미롭기 (1 - 5): 45 | - Instruction 사용 (0 - 1): 46 | - 공감 능력 (0 - 1): 47 | - 대화 유도 (0 - 1): 48 | - 전반적인 품질 (1 - 10):""" 49 | 50 | 51 | def inference(train_args: TrainArguments): 52 | start = time.time() 53 | 54 | MODEL = f"../finetuning/" + train_args.merge_dir 55 | prompter = Prompter(train_args.prompt_template_name) 56 | 57 | model = AutoModelForCausalLM.from_pretrained( 58 | MODEL, 59 | torch_dtype=torch.float16, 60 | low_cpu_mem_usage=True, 61 | ).to(device=f"cuda", non_blocking=True) 62 | tokenizer = AutoTokenizer.from_pretrained(MODEL) 63 | model.eval() 64 | 65 | # load data 66 | if train_args.dataset_type == 'multi' : 67 | data = pd.read_csv('./test/test_dataset/test_multi.csv') 68 | elif train_args.dataset_type == 'single' : 69 | data = pd.read_csv('./test/test_dataset/test_single.csv') 70 | instruction = list(data['instruction']) 71 | 72 | 73 | def gen(x): 74 | inputs = tokenizer( 75 | x, 76 | return_tensors='pt', 77 | return_token_type_ids=False 78 | ) 79 | inputs = {name: tensor.to(model.device) for name, tensor in inputs.items()} # move to same device as model 80 | gened = model.generate( 81 | **inputs, 82 | max_new_tokens=256, 83 | early_stopping=True, 84 | do_sample=True, 85 | eos_token_id=2, 86 | ) 87 | 88 | gened_list = gened[0].tolist() 89 | 90 | try: 91 | eos_token_position = gened_list.index(2) 92 | gened_list = gened_list[:eos_token_position] 93 | except ValueError: 94 | pass 95 | 96 | output = tokenizer.decode(gened_list) 97 | print(output) 98 | response = output.split('### 응답:\n')[1] 99 | 100 | return response 101 | 102 | end = time.time() 103 | print(f'generation 하기 직전까지 걸린 시간 : {int(end-start)}초') 104 | 105 | 106 | g_eval_prompts = [] 107 | for message in instruction: 108 | print('=============================================({0}/50)===================================================='.format(instruction.index(message)+1)) 109 | final_string = '' 110 | prompt = prompter.generate_prompt(message) 111 | generations = gen(prompt) 112 | 113 | final_string = g_eval_prompt.replace("{{instruction}}", message) 114 | final_string = final_string.replace("{{response}}", generations) 115 | g_eval_prompts.append(final_string) 116 | 117 | df = pd.DataFrame({ 118 | 'g_eval_prompts' : g_eval_prompts, 119 | }) 120 | df.to_csv('./test/result/'+train_args.save_file_name, index=False) 121 | 122 | 123 | def load_config(config_file: str): 124 | with open(config_file, 'r') as f: 125 | config = yaml.safe_load(f) 126 | return TrainArguments(**config) 127 | 128 | 129 | def main(): 130 | try: 131 | config_path = sys.argv[1] 132 | except IndexError: 133 | config_path = './config.yaml' 134 | train_args = load_config(config_path) 135 | inference(train_args) 136 | 137 | 138 | if __name__ == "__main__": 139 | main() -------------------------------------------------------------------------------- /finetuning/templates/kullm_origin.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Alpaca-LoRA에서 사용하는 템플릿입니다.", 3 | "prompt_input": "아래는 작업을 설명하는 명령어와 추가 컨텍스트를 제공하는 입력이 짝을 이루는 예제입니다. 요청을 적절히 완료하는 응답을 작성하세요.\n\n### 명령어:\n{instruction}\n\n### 입력:\n{input}\n\n### 응답:\n", 4 | "prompt_no_input": "아래는 작업을 설명하는 명령어입니다. 요청을 적절히 완료하는 응답을 작성하세요.\n\n### 명령어:\n{instruction}\n\n### 응답:\n", 5 | "response_split": "### 응답:" 6 | } 7 | -------------------------------------------------------------------------------- /finetuning/templates/kullm_test.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "KULLM finetuning에서 사용하는 템플릿입니다.", 3 | "prompt_input": "아래는 사용자의 발화와 그에 대한 설명, 챗봇의 공감형 응답이 짝을 이루는 예제입니다. 사용자의 발화에 공감하고 대화를 이어가는 챗봇의 응답을 작성하세요.\n\n### 사용자:\n{instruction}\n\n### 설명:\n{input}\n\n### 챗봇:\n", 4 | "prompt_no_input": "아래는 사용자의 발화와 챗봇의 공감형 응답이 짝을 이루는 예제입니다. 사용자의 발화에 공감하고 대화를 이어가는 챗봇의 응답을 작성하세요.\n\n### 사용자:\n{instruction}\n\n### 챗봇:\n", 5 | "response_split": "### 챗봇:" 6 | } -------------------------------------------------------------------------------- /finetuning/templates/multi.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "멀티 턴 대화를 이전 대화 history와 현재 사용자 질문으로 구분해주는 템플릿 입니다.", 3 | "prompt": "이전 대화와 현재 대화의 명령어를 참고하여 상황에 공감하고 친절한 응답을 생성해주세요. 응답 마지막에는 지금까지의 내용과 관련된 질문을 해주세요.\n\n[이전 대화]\n{history}\n\n[현재 대화]\n### 명령어:\n{instruction}\n\n### 응답:\n", 4 | "response_split": "### 응답:" 5 | } -------------------------------------------------------------------------------- /finetuning/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | config=("config.yaml") 4 | 5 | # Check if output_logs directory exists, if not, create it 6 | if [ ! -d "output_logs" ]; then 7 | mkdir output_logs 8 | fi 9 | 10 | echo "Start finetuning with ${config}..." 11 | nohup python3 train.py "${config}" > output_logs/output.txt 2>&1 & 12 | wait $! 13 | echo "Finetuning with ${config} has been completed!" 14 | 15 | echo "Start merging finetuned model with original model..." 16 | nohup python3 merge_lora.py "${config}" >> output_logs/output.txt 2>&1 & 17 | wait $! 18 | echo "Merging has been completed!" 19 | 20 | echo "Start testing model with several samples..." 21 | nohup python3 inference.py "${config}" > output_logs/inference.txt 2>&1 & 22 | echo "Testing has been completed!" -------------------------------------------------------------------------------- /finetuning/test/result/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampaitech5/level3_nlp_finalproject-nlp-12/46c8b4ccbc9ca85087ea0c7aa7fdf3f9d8fad015/finetuning/test/result/.gitkeep -------------------------------------------------------------------------------- /finetuning/test/test_dataset/test_multi.csv: -------------------------------------------------------------------------------- 1 | instruction,output,source,type 2 | "질문: 최근에 남편과 자주 다투게 돼서 매일매일이 힘들어요. 왜 이런 상황이 된 건지 모르겠어요. 3 | 답변: 아, 그런 일이 있으셨군요. 상대방과의 갈등은 항상 어렵습니다. 특별히 시작된 계기나 주요 이슈가 있을까요? 4 | 질문: 정확히 뭐 때문인지는 모르겠어요. 그냥 하루하루가 다툼으로 시작해서 다툼으로 끝나요. 5 | 답변: 그 상황이 힘들겠어요. 이해합니다. 갈등이 시작되는 원인을 찾기 어렵다면, 혹시 다툼이 일어나는 특정 상황이나 패턴이 있을까요? 6 | 질문: 아무래도 남편이 일에만 집중하다 보니 저를 대하는 태도가 무심해진 것 같아요. 이런 점을 어떻게 말할지 모르겠어요.","그런 느낌이 들면서 힘드시겠어요. 정확한 이야기를 전달하려면, 자신의 느낌을 구체적인 사례와 함께 솔직하게 표현하는 것이 중요합니다. 특정 상황을 예로 들어볼 수 있을까요?",gpt_4,multi_3 7 | "질문: 며칠 전부터 식물을 집에서 키우고 싶어요. 어디서부터 시작해야 할까요? 8 | 답변: 저도 식물을 키우는 걸 좋아하는데, 처음 시작할 때는 쉽게 키울 수 있는 식물부터 시작하는 것이 좋아요. 식물 키우기에 관한 책이나 온라인 자료를 찾아보는 것은 어떨까요? 9 | 질문: 좋은 생각이네요! 어떤 식물이 초보자에게 좋을까요? 10 | 답변: 초보자에게는 다육이나 스투키 등의 식물이 좋을 수 있어요. 이들은 관리가 비교적 쉽고 강건해서 새로 시작하는 사람들에게 추천하는 종류들이에요. 식물 중에 관심있는 종류가 있으신가요? 11 | 질문: 다육이나 스투키라... 참고하겠습니다. 그런데 식물을 어떻게 관리해야 하나요?","식물의 종류에 따라 관리 방법이 달라요. 일반적으로는 충분한 햇빛과 적절한 물 주기, 온도 등을 고려해야 합니다. 처음 식물을 키울 때 주의할 점은 물을 너무 많이 주지 않는 것이에요. 식물에 따라 다르겠지만 일반적으로 주의해야 할 점이 무엇인가요?",gpt_4,multi_3 12 | "질문: 최근에 일이 너무 많아서 집에 오면 늘 지친 상태야. 그래서 취미생활을 즐길 틈이 없어. 13 | 답변: 그런 일이 있을 수 있죠. 일과 쉼의 균형이 중요한데, 쉼이 부족하신 것 같아 보여요. 보통 어떤 취미를 가지고 계시나요? 14 | 질문: 나는 평소에 그림 그리기를 좋아해. 하지만 요즘엔 그림을 그릴 에너지가 남지 않아서 아쉽다고 느껴져. 15 | 답변: 그림 그리기, 정말 좋은 취미를 가지고 계시네요! 그림 그리기는 창작의 즐거움과 스트레스 해소를 동시에 가져다주는 활동이죠. 작은 스케치라도 그리실 수 있을까요? 16 | 질문: 그게 좋을 것 같아. 하지만 그래도 여전히 일에 치이는 상태라서 좀 더 조언이 필요해.","이해합니다, 무엇보다 건강이 중요하니까요. 여유를 찾기 위해 일상에서 어떤 부분을 바꿔볼까 생각해보셨나요?",gpt_4,multi_3 17 | "질문: 우리 태양계는 얼마나 크다고 생각해? 18 | 답변: 아주 멋진 질문이에요! 태양계의 크기는 아주 넓습니다. 지구에서 가장 먼 행성인 넵튠까지의 거리만 해도 약 30억 킬로미터에요! 태양계의 크기에 대해 어떤 점이 궁금했나요? 19 | 질문: 그렇다면, 태양계를 넘어서 별들은 얼마나 멀리 있는 거야? 20 | 답변: 별들은 우리 태양계보다 훨씬 더 멀리 있어요. 가장 가까운 별인 프록시마 센타우리도 지구로부터 약 4.24 광년 거리에 있답니다. 별들의 거리에 대해 더 알아보고 싶으신가요? 21 | 질문: 별과 별 사이의 거리를 알아보고 싶어. 어떻게 알아볼 수 있을까?",별과 별 사이의 거리는 복잡한 천문학적 측정을 통해 알 수 있어요. 이런 측정법에는 편위법(Parallax) 등이 있습니다. 별과 별 사이의 거리를 측정하는 다른 방법에 대해 궁금하신 건가요?,gpt_4,multi_3 22 | "질문: 최근에 날씨가 좋아서 서울숲에 가서 산책했어. 23 | 답변: 와, 날씨가 좋을 때의 서울숲 산책, 정말 좋겠어요!😄그럼 서울숲에서 무슨 일을 하셨는지 궁금하네요. 24 | 질문: 그냥 조용히 걸으며 주변을 둘러봤어. 25 | 답변: 그렇군요, 조용하게 걷고 주변을 둘러보는 것만으로도 힐링이 되겠네요. 😌 그런데 혹시 서울숲에서 다른 활동을 해보고 싶은 게 있나요? 26 | 질문: 책을 읽어보고 싶어.","서울숲에서 책을 읽어보고 싶다니, 좋은 생각이에요! 녹색 풍경 속에서의 독서는 마음을 더욱 편안하게 해줄 것 같아요. 😊 그럼 어떤 종류의 책을 읽어보고 싶으신가요?",gpt_4,multi_3 27 | "질문: 와, 오늘 야구경기에서 우리 팀이 이겼어! 28 | 답변: 정말요? 우리팀이 이기다니 축하드려요! 🎉🎉 야구 경기를 좋아하시는 건가요? 29 | 질문: 그래, 야구를 보면서 스트레스를 풀어요. 30 | 답변: 좋은 스트레스 해소 방법이시네요! 야구를 보는 것만으로도 즐거움을 느끼시다니 멋집니다. 😄 어떤 팀을 응원하시나요? 31 | 질문: 롯데 자이언츠 팬이에요.",롯데 자이언츠 팬이시군요! 롯데 자이언츠 팀이 좋아지게 된 계기가 있나요?,gpt_4,multi_3 32 | "질문: 어제 환경 보호 캠페인에 참가해서 쓰레기 줍기를 했어. 33 | 답변: 와, 환경 보호 캠페인에 참여하셨군요! 멋진 활동을 하셨네요. 👍 어떤 경험이었는지 말씀해주실 수 있을까요? 34 | 질문: 처음엔 힘들었지만, 뭔가 세상을 조금이나마 바꾸는 느낌이 들어서 좋았어. 35 | 답변: 처음에는 힘들었지만, 나중에는 세상을 바꾸는 느낌이 들어서 좋았다니, 그 정말 대단한 경험이었을 것 같아요. 👏 다음에도 그런 활동을 계속하실 계획이신가요? 36 | 질문: 네, 다음에도 기회가 되면 참여하려고 해.","정말 좋은 결정이시네요! 환경 보호 활동에 계속 참여하신다니, 당신의 적극성이 정말 멋져요. 🌍 다음 활동은 언제인가요?",gpt_4,multi_3 37 | "질문: 이번 주말에 KBO리그 경기 보러 간다니까 신나. 38 | 답변: 와, 좋겠네요! ⚾ 주말에 KBO리그 경기를 보러 가신다니, 정말 흥분되시겠어요. 😄 어느 팀 응원하시는 건가요? 39 | 질문: 나는 두산 베어스 팬이야! 40 | 답변: 두산 베어스 팬이시군요! 경기장의 분위기를 만끽하며 응원하시는 모습을 상상하니 더욱 기대되네요. 🎉 대결하는 상대팀은 어느 팀인가요? 41 | 질문: 롯데 자이언츠야.","롯데 자이언츠와의 경기라니, 역시 대단한 경기가 될 것 같네요. 😊 경기 전에도 미리 준비하시는 특별한 응원 방법이 있나요?",gpt_4,multi_3 42 | "질문: 어제 새로 산 자전거로 한강에 다녀왔어. 기분이 아주 좋았어. 43 | 답변: 신나는 경험이었을 것 같아요. 자전거는 마음과 몸을 동시에 힐링하는 좋은 수단이죠. 한강에서 가장 좋아하는 활동은 무엇인가요? 44 | 질문: 사실 낚시를 좋아해서 자주 가는 편이야. 어제는 물고기가 잘 안 물어서 좀 아쉬웠어. 45 | 답변: 오, 낚시 좋아하시는군요. 그럴 때도 있죠, 하지만 그 과정에서의 편안함이 중요한 거니까요. 낚시하면서 느끼는 특별한 재미가 있나요? 46 | 질문: 물고기를 잡는 그 순간의 스릴이 가장 큰 재미인 것 같아.","그 스릴감, 그 자체로도 행복한 시간이었을 거라 생각해요. 물고기를 잡는 그 순간 외에도 즐거움을 느끼는 순간이 있으신가요?",gpt_4,multi_3 47 | "질문: 최근에 새로운 일을 시작했는데, 조금 힘들어요. 스트레스가 많이 쌓여요. 48 | 답변: 이럴 때일수록, 차분하게 한 걸음 한 걸음 다가가보세요. 새로운 시작은 언제나 힘들지만 승리하는 것도 그만큼 가치가 있답니다. 작업하는 동안 가장 어려운 부분은 무엇인가요? 49 | 질문: 가장 어려운 부분은 시간 관리인 것 같아요. 정해진 시간 안에 모든 일을 끝내야 하는 부담감이 크더라고요. 50 | 답변: 아하, 그렇군요. 시간 관리는 정말 힘든 부분이에요. 좀 더 효율적으로 시간을 활용하기 위한 방법을 찾아보시는 것은 어떨까요? 일하는 중에는 어떤 방식으로 시간을 관리하고 있나요? 51 | 질문: 주로 스케쥴러를 사용하는데, 계획대로 일이 진행되지 않을 때가 많아서 고민이에요.","그렇군요, 계획을 세우는 것만큼 그 계획을 실천하는 것도 중요하니까요. 어쩌면 일정을 너무 타이트하게 잡는 것이 문제일 수도 있을 것 같아요. 스케쥴을 짜실 때 유연성을 가지고 계획해 보시는 것은 어떨까요?",gpt_4,multi_3 52 | "질문: 최근 들어 자꾸 슬픔이 쌓여가. 모두가 내게 등을 돌린 것 같아. 53 | 답변: 그런 기분이 들었다니 정말로 힘들겠어요. 😔 어떤 상황에서 그런 기분이 드셨는지 조금 더 자세히 알려주실 수 있나요? 54 | 질문: 친구들이 내 얘기를 들어주지 않고, 항상 바쁘다고만 해. 55 | 답변: 당신의 이야기를 들어주지 않는 친구들 때문에 외롭고 슬프게 느껴지셨을 것 같아요. 😢 정말로 힘든 상황이시군요. 이런 일이 처음인가요? 56 | 질문: 아니, 친구들이 점점 바쁘다고만 해서 이런 일이 자주 있었어.","아이고, 그렇군요. 친구들이 자신의 이야기를 들어주지 않을 때 정말로 외롭고 슬프게 느껴지시겠어요. 😟 이에 대해서 친구들과 이야기해보신 적이 있나요?",gpt_4,multi_3 57 | "질문: 나의 연인이 나를 이해하지 못하는 것 같아. 어떻게 이야기해야 할까? 58 | 답변: 마음을 터놓고 이야기하는 것이 중요해요. 서로의 생각과 감정을 공유하는 시간을 가지는 것이 어떨까요? 일반적으로 어떤 방식으로 대화하시나요? 59 | 질문: 우리는 대부분 친구처럼 즐겁게 대화하긴 하는데, 심각한 문제에 대해선 말하기 어렵더라고. 60 | 답변: 그런 상황은 누구에게나 있을 수 있어요. 문제를 해결하기 위해 진지한 대화를 이끌어가는 것도 중요해요. 문제를 다룰 때 어떤 방식을 사용하시나요? 61 | 질문: 보통은 문제가 생기면 어떻게 해결해야 할지 생각하고, 그것을 상대방에게 이야기하려고 해.","해결책을 제안하는 것은 좋은 접근법이에요. 그렇게 문제를 더욱 구체적으로 이해하고, 해결해 나가는 것이 중요하죠. 그럼 이번에는 어떤 문제가 있었나요?",gpt_4,multi_3 62 | "질문: 와우! 너무 기뻐. 내 인스타그램에 올린 사진이 1000개의 좋아요를 받았어! 63 | 답변: 정말 대단하세요! 1000개의 좋아요는 대단한 성과에요.👏 그 사진은 어떤 사진이었나요? 64 | 질문: 제가 얼마 전에 한강에서 찍은 일몰 사진이야. 65 | 답변: 오, 한강에서의 일몰은 정말 멋지죠.😊 그런 훌륭한 사진을 공유해주셔서 감사합니다. 다음에는 어떤 사진을 올리실 계획이세요? 66 | 질문: 아마도 제가 최근에 구입한 신발에 대한 사진을 올릴 것 같아.",신발에 대한 사진이라면 분명히 흥미로울 거예요. 👟 어떤 신발을 구입하셨는지 기대되네요. 이미 촬영은 하셨나요?,gpt_4,multi_3 67 | "질문: 너무 힘들어. 매일 같은 일상이 싫어져서 그런지 피곤하고 지쳐. 68 | 답변: 힘든 마음을 겪고 계신 거군요. 😔 매일 똑같은 일상 때문에 지치셨을 것 같아요. 최근에 잠은 잘 주무셨나요? 69 | 질문: 잠은 잘 못자. 맨날 불면증이야. 70 | 답변: 그랬군요. 잠이 제대로 이루어지지 않으면 정말 힘드실 텐데... 😢 그럼 평소에 스트레스 해소를 위해 했던 행동이나 활동이 있나요? 71 | 질문: 피아노 치는 것이 스트레스 해소 방법이었는데, 요즘엔 그것조차 하기 싫어져서.","아, 그렇군요. 음악이 소중한 휴식이었는데 그것조차 하기 싫어지니 정말 힘들어하시겠어요. 😢 이런 상황에 대해 누군가에게 이야기를 나눠본 적이 있나요?",gpt_4,multi_3 72 | "질문: 내일이 자가격리 마지막 날이야. 73 | 답변: 오, 이렇게 자가격리 마지막 날이 되셨군요. 그 동안의 외로움과 불편함을 잘 이겨내셨습니다. 😊 앞으로 어떤 계획을 가지고 계신가요? 74 | 질문: 집에서 너무 지쳐서 잠깐 나가서 공기를 쐬고 오려고. 75 | 답변: 아, 그럼 분명 외부로 나가시면 훨씬 기분이 좋아지실 것 같아요! 자연의 공기를 마셔본다면 상쾌함을 느낄 수 있겠네요. 어디로 나가려고 생각하시나요? 76 | 질문: 바로 앞 공원으로 걷고 오려고.","잠시나마 신선한 공기를 마시고, 푸른 풀밭과 나무들을 보며 산책하는 것도 좋은 아이디어네요! 😄 공원에서 무슨 활동을 할 계획이세요?",gpt_4,multi_3 77 | "질문: 나는 새로운 개를 입양하려고 생각 중인데, 어떤 종류의 개를 선택하면 좋을지 모르겠어. 78 | 답변: 새롭게 가족을 맞이하는 건 정말 설레는 일이죠! 개 종류 선택은 여러 요인을 고려해야 해요. 집의 크기, 가족 구성원, 개의 성격 등을 생각해보시면 좋을 것 같아요. 혹시 특정 종을 선호하시나요? 79 | 질문: 아직은 특정 종을 선호하진 않아. 그냥 건강하고 활동적인 개가 좋을 것 같아. 80 | 답변: 건강하고 활동적인 개, 정말 훌륭한 선택이실 거에요! 레트리버나 콜리 같은 종이 그런 특징을 가진 종들이에요. 혹시 개와 함께 살면서 어떤 활동을 하고 싶으신가요? 81 | 질문: 나는 아웃도어 활동을 좋아해서 함께 산책하거나 등산을 가고 싶어. 그런 활동을 좋아하는 개 종류가 있을까?","아웃도어 활동을 좋아하신다면, 함께 산책하거나 등산하는 건 개에게도 좋은 활동이 될 거예요. 보더 콜리나 래브라도 레트리버 등이 활동적인 편이에요. 어떤 종을 택하더라도 개를 키우는 데 필요한 준비는 다 되셨나요?",gpt_4,multi_3 82 | "질문: 오늘 처음으로 직접 요리를 해봤는데, 맛이 생각보다 좋아서 기분이 좋아! 83 | 답변: 어머, 직접 요리하셨다니 대단하시네요! 👏 맛이 좋았다니 정말 기쁘겠어요. 어떤 요리를 해보셨는지 알려주실 수 있을까요? 84 | 질문: 첫 요리라서 간단하게 파스타를 해봤어. 85 | 답변: 파스타를 직접 만드셨다니, 멋지시네요! 👍 신선하게 만든 파스타는 정말 맛있죠. 특별히 어려웠던 부분이 있었나요? 86 | 질문: 소스 만드는 부분이 조금 어려웠어.",소스 만들기가 어려웠군요. 그래도 맛있게 잘 만드신 것 같아요. 앞으로도 더 많은 요리를 도전해보실 계획이신가요? 😊,gpt_4,multi_3 87 | "질문: 내 친구가 나한테 계속 실망하는 것 같아. 그런데 내가 어떻게 잘못했는지 모르겠어. 88 | 답변: 친구가 당신에게 실망하는 것처럼 느껴지고, 그 원인을 모르겠다니, 이해하기 힘들 것 같아요. 당신이 그렇게 생각하게 된 구체적인 상황이 무엇인가요? 89 | 질문: 친구가 계속 나한테 냉담한 태도를 보이더라고. 근데 나는 아무런 이유를 모르겠어. 90 | 답변: 그럴 때마다 상처받고 혼란스러울 테니까요. 그런 변화가 생긴 시기나 이유에 대해 친구와 대화를 나눠볼 필요가 있을 것 같습니다. 이런 상황을 어떻게 이야기할지 고민이 되시나요? 91 | 질문: 그래, 아마 그게 최선이겠지. 그런데 그 상황에서 어떻게 이야기를 시작해야 할지 모르겠어.","정말 어려운 상황이십니다. 일단, 솔직하면서도 배려심 있는 말로 친구에게 어떻게 느껴졌는지 표현해보는 건 어떨까요?",gpt_4,multi_3 92 | "질문: 최근에 읽은 책이 너무 재미있었어! 너도 좋아할 거 같아서 추천하고 싶은데 좋을까? 93 | 답변: 물론이죠, 어떤 책을 읽으셨는지 궁금하네요! 네가 좋아했다면 나도 좋아할 가능성이 높을 테니, 어떤 책인지 소개해주시겠어요? 94 | 질문: '일인칭 대수'라는 소설이야. 과학과 철학이 섞인 내용이라서 재밌더라. 95 | 답변: '일인칭 대수'라, 과학과 철학이라니 흥미로워 보이네요! 제가 좋아하는 분야랑 비슷해서 꼭 읽어볼게요. 어떤 부분이 가장 인상적이었나요? 96 | 질문: 주인공이 AI와의 대화를 통해 자신을 되돌아보는 과정이 가장 인상적이었어.","자기 성찰을 통한 인간의 성장, 정말 멋진 주제네요! 그런 소설을 좋아하시다면, 또 다른 추천작품이 있으신가요?",gpt_4,multi_3 97 | "질문: 내가 좋아하는 책이 영화로 나와서 기분이 좋아. 98 | 답변: 원작을 사랑하는만큼 영화에 대한 기대감이 클 것 같네요. 책에서 가장 인상 깊었던 장면이 영화에서는 어떻게 그려질지 기대되지 않나요? 99 | 질문: 그렇다. 특히 주인공이 처음으로 마법을 쓰는 장면이 기대되네. 100 | 답변: 그 장면이면 분명히 영화에서도 멋지게 연출될 것 같네요. 주인공이 마법을 사용하는 그 순간, 어떤 감정을 느낄 것 같으세요? 101 | 질문: 기분이 좋고, 재미있을 것 같아.",그 순간을 상상하니 저도 두근두근하네요. 영화 관람 후엔 어떤 점을 가장 먼저 기대하시나요?,gpt_4,multi_3 102 | "질문: 축구 경기에서 울팀이 지고 말았어.. 103 | 답변: 오, 그렇군요... 축구 경기에서 진 것은 분명 기분이 좋지 않으시겠어요.😔 팀이 어떤 식으로 진 건가요? 104 | 질문: 우리 팀이 잘하고 있었는데, 마지막에 상대팀이 골을 넣어서 지고 말았어. 105 | 답변: 마지막 순간에 골을 내주다니, 정말 큰 충격이셨을 것 같아요. 😞 그런데, 경기 도중에는 어떤 멋진 장면이 있었나요? 106 | 질문: 우리 팀이 골을 넣을 때 정말 멋있었어.",그러셨군요! 그럼 그 순간은 정말 흥분되고 행복했을 것 같아요.😄 다음 경기는 언제인가요? 다음 경기를 위해 어떤 계획을 세우고 계신가요?,gpt_4,multi_3 107 | "질문: 가지고 있는 화분 식물이 요즘따라 좀 시들해 보여서 걱정이에요. 108 | 답변: 앗, 식물이 안 좋아 보인다니 그것도 걱정이시겠어요. 어떤 식물인지 알려주실 수 있나요? 그리고 화분에 물을 얼마나 주고 계신가요? 109 | 질문: 제가 키우고 있는 식물은 피토니아인데, 보통 일주일에 두 번 정도 물을 줘요. 110 | 답변: 피토니아라면 습도를 좋아해서 물을 좀 더 자주 주셔야 할 것 같아요. 화분의 물 상태는 어떤가요? 물이 과하게 차있지는 않나요? 111 | 질문: 아하, 그런 문제가 있었군요. 화분 물이 좀 많이 차있는 것 같아요.",그렇군요! 물이 너무 많으면 뿌리가 썩을 수 있으니 조심하세요. 적절한 습도를 유지해주시는게 중요합니다. 햇빛은 충분히 받고 계신가요?,gpt_4,multi_3 112 | "질문: 신입사원으로서 일하는 건 너무 힘들고 스트레스 받는 것 같아. 113 | 답변: 첫 직장 생활이시라면 정말 쉽지 않을 거에요. 그래도 지금의 어려움이 나중에는 소중한 경험으로 남을 거라고 생각해요. 특히 어떤 부분이 힘든지 말씀해 주실 수 있나요? 114 | 질문: 주로 일이 너무 많고 피곤함이 쌓여서 그런 것 같아. 일과 휴식의 균형을 어떻게 맞춰야 할지 모르겠어. 115 | 답변: 그런 걱정이 있으시군요. 일과 휴식의 균형은 참 중요한데, 맞추기가 쉽지 않죠. 일정 관리를 통해 효율을 높이는 것은 어떨까요? 혹시 시간 관리에 어려움이 있으신가요? 116 | 질문: 그게 문제지. 일이 끝나고 나면 너무 피곤해서 시간 관리를 못하겠어.","업무 후 피곤함은 정말 어쩔 수 없는 부분이죠. 그래도 적어도 주말에는 충분히 휴식을 취하고, 취미생활 등을 즐겨 보는 것도 좋을 것 같아요. 주말에는 보통 어떻게 보내시나요?",gpt_4,multi_3 117 | "질문: 요새 읽고 있는 소설이 너무 재미있어서 밤새우고 있어. 118 | 답변: 좋은 책과 만나는 것은 마치 새로운 세상을 여행하는 것 같죠. 어떤 장르의 소설을 좋아하시나요? 119 | 질문: 공상과학과 판타지 장르를 좋아해. 상상력을 자극하는 게 재밌어서. 120 | 답변: 그 장르들은 독자들에게 다양한 상상의 공간을 제공해주니까요. 혹시 좋아하는 작가가 있나요? 121 | 질문: 네, 톨킨이랑 조지 R. R. 마틴 작가를 좋아해. 그들의 상상력에 항상 놀라요.",그들은 놀라운 상상력으로 수많은 독자들을 사로잡았죠. 그럼 본인이 상상하는 이상의 세계는 어떤 모습인가요?,gpt_4,multi_3 122 | "질문: 요즘 집중이 잘 안돼서 일을 제대로 못하겠어. 123 | 답변: 집중이 잘 안된다는 건 참 힘든 일이죠. 우리 모두 때때로 그런 상황을 겪습니다. 혹시 그럴 때 특정한 상황이나 요소가 있나요? 124 | 질문: 특별한 건 없는 것 같아. 그냥 막막하고 힘들게 느껴져. 125 | 답변: 그런 감정은 정말 힘들어요. 이런 감정을 겪을 때, 종종 그 원인이 명확하지 않을 수 있어요. 그래서 혹시, 이런 기분이 드는 것에 대해 좀 더 이야기 해볼 수 있을까요? 126 | 질문: 정말 모르겠어. 그냥 모든 일이 잘 안 풀리는 느낌이야.","그런 느낌이 들 때, 정말로 이해하기 어려운 상황이 될 수 있어요. 여러분이 겪고 있는 감정을 인정하는 것이 첫 단계입니다. 그럼, 이런 감정을 조금 더 완화시킬 수 있는 방법에 대해 이야기해 보는 것은 어떨까요?",gpt_4,multi_3 127 | "질문: 아이와 대화하려고 노력하는데, 쉽지가 않아. 어떻게 아이와 소통해야 할까? 128 | 답변: 그럴 때가 있죠. 아이와의 소통에는 이해와 참여가 중요해요. 아이의 관심사에 대해 더 알아보는 것은 어떨까요? 129 | 질문: 아이는 최근에 우주에 관심이 많아서 그쪽에 대해 알아봤는데, 어떻게 접근해야 할지 모르겠어. 130 | 답변: 아이의 관심을 같이 공유하는 것만으로도 아이와의 소통이 더 쉬워질 수 있어요. 어떻게 아이와 함께 우주에 대해 탐구할 수 있을까요? 131 | 질문: 우주에 대한 책을 함께 읽어보거나, 우주 관련 프로그램을 함께 보는 건 어떨까?",좋은 생각이에요! 그러면서 서로 질문을 주고받으며 소통하는 것도 좋을 것 같아요. 아이가 우주에 대해 가장 궁금해하는 것은 무엇인가요?,gpt_4,multi_3 132 | "질문: 스페인어를 배우고 싶은데 어떻게 시작하면 좋을까? 133 | 답변: 아, 스페인어를 배우고 싶다니 정말 멋진 목표를 가지신 것 같아요. 시작은 기초 표현을 익히는 것이 좋아요. 스페인어를 선택하신 이유가 있나요? 134 | 질문: 여행 가고 싶은 국가가 스페인이라서. 기본적인 대화 정도는 할 수 있으면 좋겠어. 135 | 답변: 여행을 위해 언어를 배운다는 것 자체가 흥미롭네요. 기본적인 대화를 위해선 일상적인 표현을 많이 익혀보는 것이 좋아요. 어떤 표현을 먼저 배우고 싶으세요? 136 | 질문: 레스토랑에서 주문할 때 필요한 표현을 먼저 배우고 싶어.",그러면 식사 관련 표현을 배우는 것부터 시작하는 것이 좋겠네요. 언어 학습에 있어 어떤 방법을 가장 효과적이라고 생각하세요?,gpt_4,multi_3 137 | "질문: 아버지가 요즈음 힘이 많이 빠지는 것 같아서 걱정이야. 138 | 답변: 당신이 아버지를 걱정하는 마음이 너무 잘 이해가 가요. 노년기에 들어서면서 체력이 떨어지는 건 어쩔 수 없는 현상이지만, 그래도 마음이 아프겠어요. 아버지의 상태가 어떤지 좀 더 자세히 알려줄 수 있을까요? 139 | 질문: 그냥 뭔가 힘이 없어 보이고 자주 피곤해하는 것 같아. 나이가 들면 그런가봐... 140 | 답변: 앗, 정말 힘드시겠어요. 아버지가 힘들어하시는 걸 보는 건 진짜 쉽지 않겠어요. 그래도 아버지의 상태를 정확히 알아보기 위해 의료 전문가에게 상담해보는 건 어떨까요? 141 | 질문: 그래, 그게 최선일 것 같아. 아버지도 걱정이 돼서 좋아하실 것 같아.",좋아요! 진료를 통해 아버지의 건강 상태를 확인하는 것이 가장 중요하죠. 그런데 아버지께서는 혹시 특정 식단이나 운동 등 건강관리에 대해 어떻게 생각하시나요?,gpt_4,multi_3 142 | "질문: 며칠 전에 새로 나온 영화 추천 좀 해줘. 무엇이든 좋으니까. 143 | 답변: 좋아요! 영화를 선택하는 것은 언제나 쉽지 않아요. 장르에 따라 다르겠지만, 일단 다양한 추천이 가능할 것 같아요. 특별히 선호하는 영화 장르가 있나요? 144 | 질문: 나는 장르를 가리지 않아. 그냥 재미있고, 생각하게 만드는 영화를 좋아해. 145 | 답변: 그렇군요! 재미있으면서도 생각할 거리를 줄 수 있는 영화... 사랑과 우정, 인생에 대해 다양한 시각으로 이야기하는 영화는 어떨까요? 146 | 질문: 그런 영화 좋아! 좋은 선택이 될 것 같아.","네, 저도 그런 영화들은 많은 생각을 하게 만드는 것 같아요. 그런데 혹시 특정 배우나 감독의 영화를 좋아하시나요?",gpt_4,multi_3 147 | "질문: 스포츠를 좋아하시나요? 어떤 종목을 가장 좋아하세요? 148 | 답변: 스포츠에 대한 열정, 그게 어떤 종목이든 흥미롭지 않나요? 가장 좋아하는 스포츠는 무엇인가요? 149 | 질문: 저는 축구를 가장 좋아해요. 팀워크와 전략, 그리고 순간순간의 판단이 중요해서 흥미롭더라고요. 150 | 답변: 축구는 역동적이면서도 전략적인 부분이 많이 들어가는 스포츠죠. 그렇게 열정적으로 즐기시니 정말 멋지네요. 본인이 축구를 즐길 때 가장 흥미로운 포지션이 어디인가요? 151 | 질문: 공격수가 제일 흥미롭더라고요. 골을 넣는 순간의 기쁨이란..",공격수의 역할은 확실히 매력적이죠. 득점의 순간은 팬들에게도 큰 환희를 안겨주니까요. 그럼 축구 외에도 좋아하는 스포츠가 있으신가요?,gpt_4,multi_3 152 | "질문: 내일 친구 결혼식인데, 신랑 신부에게 어떤 축하의 말을 전해야 할지 모르겠어 153 | 답변: 결혼식에서 친구에게 축하의 말을 전하는 건 정말 중요하죠. 두 분의 새로운 시작을 응원하는 말로 시작해볼까요? 154 | 질문: 응, 그럼 새로운 시작을 응원하는 말을 전하면 좋겠다고 생각해 155 | 답변: 좋아요! 그들이 함께 행복한 미래를 만들어 나가길 바라는 마음을 담아보세요. 그럼 실제로 무슨 말을 하고 싶으신가요? 156 | 질문: 그들이 항상 서로를 이해하고 사랑하는 일상을 만들어가길 바란다는 말을 하고 싶어",그런 말은 정말로 따뜻하고 친구들이 기쁠 거예요. 그들에게 그런 축하의 말을 전하면 그들의 결혼생활에 긍정적인 영향을 끼칠 거에요. 그 밖에도 무엇을 전하고 싶으신가요?,gpt_4,multi_3 157 | "질문: 내일 연애 상담하는 곳에 가기로 했어. 158 | 답변: 연애 상담에 가기로 하셨군요. 상담에 나서는 것 자체가 큰 용기라고 생각해요. 🙂 어떤 문제로 상담을 받으시려고 결정하셨는지 알 수 있을까요? 159 | 질문: 상대방이 너무 답답해서 헤어지고 싶은데 어떻게 헤어져야 할지 모르겠어. 160 | 답변: 아, 그런 상황이셨군요. 상대방 때문에 힘드시면서도 이별을 어떻게 말해야 할지 고민이시군요. 정말 힘드시겠어요. 상담이 도움이 되었으면 좋겠네요. 😢 상담사와 이야기하기 전에 혼자서 준비해보려고 계신가요? 161 | 질문: 그러려고 하는데 어떻게 준비해야 할지 잘 모르겠어.","그럼요, 상담사와의 만남을 위한 준비가 어렵겠네요. 😢 표현하고 싶은 감정들을 정리하고, 상담사에게 무엇을 물어보고 싶은지 생각해보는 것은 어떨까요?",gpt_4,multi_3 162 | "질문: 뭐니뭐니 해도 축구는 역시 재미있어. 스트레스도 풀리고 163 | 답변: 축구에 대한 열정, 정말 멋지시네요. 운동은 정말 좋은 스트레스 해소 방법이죠. 그런데 어떤 포지션에서 주로 활동하시나요? 164 | 질문: 나는 공격수야. 골을 넣는 순간의 그 느낌이란.. 정말 최고지. 165 | 답변: 그 느낌을 알아요. 공을 차 넣고 나서의 성취감은 정말 대단한 것 같아요. 아마 그 골을 넣는 순간, 모든 고민과 스트레스가 사라지겠죠? 공격수의 책임이 크다보니 스트레스도 있을 텐데, 어떻게 이겨내시나요? 166 | 질문: 내 마인드가 강하니까 괜찮아. 고민보다 골이 앞서니까!","그런 마인드, 정말 대단하시네요. 고민보다 골이 앞서다니, 그게 진짜 승리의 비결이죠. 그럼 경기 중에 가장 기억에 남는 순간은 언제였나요?",gpt_4,multi_3 167 | "질문: 맨날 공부만 하니까 지침. 이제 휴가가 왔으면 좋겠어. 168 | 답변: 오랜 시간 동안 공부에 집중하시느라 힘드셨겠어요. 휴가를 기대하시는 모습을 보니 휴식이 필요하신 거 같아요. 😌 어떤 방식으로 휴식을 취하고 싶으신지 말씀해주실 수 있나요? 169 | 질문: 여행 가고 싶은데, 코로나 때문에 막혀있어. 170 | 답변: 그렇군요, 코로나 때문에 여행 계획이 막히는 건 정말 힘들어요. 😔 여행을 가지 못하더라도 집에서도 휴식을 취할 수 있는 방법이 많아요. 어떤 활동을 통해 휴식을 취하면 좋을까요? 171 | 질문: 음... 아마 책 읽기나 취미 생활에 시간을 더 많이 투자할 것 같아.",책 읽기와 취미 생활은 훌륭한 휴식 방법이에요! 😊 많은 시간을 투자하면서 본인만의 시간을 가지는 것도 좋은 선택이 될 수 있겠네요. 어떤 책이나 취미에 관심이 있으신가요?,gpt_4,multi_3 172 | "질문: 환경 보호에 대한 관심이 높아져서 기뻐. 근데 나도 환경 보호에 기여하고 싶은데 어떻게 해야할지 모르겠어. 173 | 답변: 그런 생각이 있으시다니 대단하네요! 환경 보호에 기여하는 방법은 많아요. 재활용을 실천하거나, 대중교통을 이용하거나, 에너지 효율적인 제품을 구입하는 것도 한 방법이죠. 당신이 생각하시는 환경 보호란 어떤 것인가요? 174 | 질문: 나는 특히 쓰레기 감소와 재활용에 관심이 많아. 그런데 어떤 것부터 시작해야 할지 감이 잡히지 않아. 175 | 답변: 그런 생각이 있으시다니 정말 멋지네요! 쓰레기 감소와 재활용에 대한 첫걸음은 소비 습관을 바꾸는 것이죠. 예를 들어, 필요 이상으로 물건을 사지 않거나, 재사용 가능한 제품을 선택하는 것부터 시작해 보세요. 어떤 생활용품을 가장 많이 사용하시나요? 176 | 질문: 나는 플라스틱 병과 종이 컵을 많이 사용하게 되는 것 같아.","그런 소비 습관을 깨닫는 것부터가 중요한 첫걸음이에요! 플라스틱 병 대신에 재사용 가능한 물병을 사용하거나, 커피를 마실 때는 개인 컵을 이용하는 것도 좋아요. 당신의 생활에서 다른 재사용 가능한 제품을 도입해볼 생각은 어떠신가요?",gpt_4,multi_3 177 | "질문: 어제 친구들과 영화를 봤는데 너무 재밌었어. 아직도 그 분위기가 기억나. 178 | 답변: 친구들과 보는 영화는 항상 기분 좋은 추억이 되는 것 같아요. 그 분위기를 함께 공유하면 더욱 즐겁죠. 어떤 장르의 영화를 보셨나요? 179 | 질문: 액션 영화였어. 액션씬이 잘 연출된 영화라서 기분 전환도 되고 좋았어. 180 | 답변: 액션 영화의 역동적인 장면들은 진짜 시원하고 스릴 넘치는 거 같아요. 그런 영화를 보면 분명히 흥분되겠죠. 그럼 액션 장르 외에도 좋아하는 영화 장르가 있나요? 181 | 질문: 나는 로맨틱 코미디도 좋아해. 가끔은 달달한 로맨스가 필요하니까.",로맨틱 코미디는 마음을 따뜻하게 만들어 주는 매력이 있죠. 가끔은 그런 달달한 분위기가 필요하다고 생각해요. 그럼 보통 어떤 배우의 로맨틱 코미디 영화를 선호하시나요?,gpt_4,multi_3 182 | "질문: 최근 들어 친구와 관계가 좋지 않아. 어떻게 해야 할지 모르겠어. 183 | 답변: 이런, 마음이 아프시겠어요. 친구와의 관계 문제는 정말 어려운 부분이죠. 일단은 친구와 진솔하게 대화를 나눠보는 것이 중요해요. 어떤 문제가 있었나요? 184 | 질문: 친구가 나에게 솔직하지 않은 부분이 있다는 것을 알게 됐어. 그래서 상처받았거든. 185 | 답변: 아, 그렇군요. 그런 상황이라면 당연히 상처받으셨을 것 같아요. 하지만 여전히 이 친구와 관계를 이어가고 싶으신가요? 186 | 질문: 그래, 친구인 만큼 이해하고 싶어. 그리고 서로 양보하고 배려하는 관계를 갖고 싶어.",그런 생각을 하신다니 정말 멋진 것 같아요. 그럼 어떻게 그런 관계를 만들어 갈 수 있을까요? 어떤 점을 고려해야 할까요?,gpt_4,multi_3 187 | "질문: 오늘 저녁에 독서모임이 있는데 사람들이 많아서 걱정이야. 188 | 답변: 걱정되시는 마음, 잘 이해해요. 많은 사람들 앞에서 얘기하거나 새로운 사람들을 만나는 건 언제나 쉽지 않죠. 😟 처음 만나는 사람들도 많으신가요? 189 | 질문: 그런 것도 있고, 주로 내가 책을 읽고 느낀 점을 공유하는 시간인데 긴장돼. 190 | 답변: 그럴 수 있어요, 자신이 느낀 점을 남들 앞에서 표현하는 것은 언제나 조금 두렵죠. 그런데 잘 생각해보세요, 여러분이 모인 이유는 서로의 생각을 나누고 서로를 이해하려는 것 아닐까요? 어떤 책을 읽으셨는지 알 수 있을까요? 191 | 질문: '지적 대화를 위한 넓고 얕은 지식'이라는 책을 읽었어.","좋은 책을 선택하셨네요! 저도 그 책 좋아하는데요, 그 책의 다양한 주제들 중 어떤 부분이 가장 인상 깊었는지 말씀해주실 수 있나요?",gpt_4,multi_3 192 | "질문: 나 아이패드 샀어! 진짜 너무 좋아!! 193 | 답변: 우와, 정말 기분 좋으시겠어요!🎉 아이패드, 드디어 구매하셨군요! 😄 주로 어떤 용도로 사용하실 계획이세요? 194 | 질문: 나 주로 그림 그리고 공부하는 데 쓸 생각이야. 195 | 답변: 그렇군요! 아이패드로 그림 그리고 공부하는 것, 굉장히 효율적일 것 같아요.👍 그런데, 그림 그리는 건 언제부터 시작하게 된 건가요? 196 | 질문: 어릴 때부터 그림 그리는 걸 좋아했어.",그럼 아이패드로 그림 그리기가 더 즐거울 것 같아요!😃 그림 그리는 것 외에 다른 취미도 있으신가요?,gpt_4,multi_3 197 | "질문: 몇 주 전부터 집에서 시간을 많이 보내는데, 집에서 뭐 하는게 좋을지 모르겠어. 198 | 답변: 집에서 많은 시간을 보내면서 할 일을 찾는 것은 쉽지 않은 일이죠. 원하는 취미나 관심사가 있으신가요? 199 | 질문: 나는 사실 공부를 좋아하는 편인데, 집에서 혼자 공부하는게 쉽지 않아. 200 | 답변: 아, 공부를 좋아하는군요! 그런데 혼자서 집에서 공부하는게 어려운 건 정말 이해가 가요. 어떤 과목이나 주제를 공부하시는데 어려움이 있으신가요? 201 | 질문: 아무래도 집에서 혼자 공부하다 보면 집중이 잘 안돼. 그래서 계속 쉬게 되더라고.",집에서는 집중하기 어려울 때가 많아요. 혼자서 공부하다 보면 모르는 부분이 생겼을 때 도움을 청할 수 있는 방법에 대해 생각해보셨나요?,gpt_4,multi_3 202 | "질문: 이번 주에 시험이 있어서 너무 스트레스 받아. 203 | 답변: 이런, 시험 때문에 스트레스를 받으시는군요. 😟 너무 무거운 마음으로 임하지 않으셔도 괜찮아요. 공부하시면서 가장 힘들었던 부분은 무엇인가요? 204 | 질문: 모든 게 다 힘들었어. 시험 공부하는 것 자체가 너무 지겨워. 205 | 답변: 아, 그렇군요. 공부 자체가 힘들고 지겹게 느껴지셨을 때면 정말 힘드시겠어요. 😔 시험 준비를 조금이라도 편하게 할 수 있는 방법을 찾아보셨나요? 206 | 질문: 아직 찾아보지는 않았어.","그런 경우에는, 좋아하는 음악을 들으면서 공부하거나, 좋아하는 간식을 준비해두는 것도 도움이 될 수 있어요. 어떤 방법을 시도해볼 생각이세요?😊",gpt_4,multi_3 207 | "질문: 우리 고양이가 근래 끊임없이 밤에 울어서 잠을 잘 못 잔다. 208 | 답변: 그러시군요, 반려동물의 행동 변화는 주인님을 많이 걱정시키곤 하죠. 고양이는 야행성 동물이라 밤에 활동하는 경우가 많아요. 고양이가 왜 울고 있는지 파악해보셨나요? 209 | 질문: 아무래도 고양이가 외로워서 그런 것 같아. 낮에는 나와 함께 있는데 밤에는 많이 외로울 것 같다. 210 | 답변: 아, 이해합니다. 고양이도 사람처럼 외로움을 느낄 수 있어요. 장난감이나 간식 등을 활용해 본다면 고양이의 외로움을 조금이나마 줄여줄 수 있을 것 같아요. 어떤 장난감을 주로 이용하시나요? 211 | 질문: 주로 고양이 캣타워나 공같은 장난감을 사용해. 이 외에도 고양이가 좋아할만한 장난감 추천해줄 수 있을까?",캣타워와 공은 고양이들이 좋아하는 훌륭한 장난감이죠. 레이저 포인터나 놀이용 낚시대 같은 것도 고려해보세요. 그럼 고양이가 더 활동적으로 놀 수 있어요. 이 외에도 어떤 문제가 있으신가요?,gpt_4,multi_3 212 | "질문: 코로나 때문에 집에서 뭐하는게 심심해서 수채화 그리려고 생각 중인데 어떻게 시작해야할까? 213 | 답변: 집에서도 즐길 수 있는 취미를 시작하는 것은 정말 멋진 생각이네요! 수채화는 아름다운 색감과 표현력이 특징이죠. 처음으로 그릴 주제를 어떤 것으로 정하셨나요? 214 | 질문: 아무래도 집에 있는 식물이나 가구를 그려볼까 하는데, 그럴 때 주의할 점이 있을까? 215 | 답변: 식물이나 가구는 초보자에게도 적합한 주제입니다. 그릴 때는 물감의 투명도와 색의 밸런스를 조절하는 것이 중요하답니다. 혹시 사용할 수채화 팔레트와 브러시는 이미 준비하셨나요? 216 | 질문: 아직 안 샀는데, 어떤 걸 사야할지 모르겠어.","그렇군요, 맞춤형 팔레트와 브러시를 선택하는 것이 작품의 결과에도 큰 영향을 미칠 수 있어요. 처음에는 어떤 색상들을 주로 사용하고 싶으신가요?",gpt_4,multi_3 217 | "질문: 요즘 새로운 레시피를 찾아보며 요리하는 걸 좋아해. 218 | 답변: 흥미로운 취미를 가지고 계시네요! 새로운 레시피로 요리하는 것은 항상 흥미로운 체험이죠. 가장 기억에 남는 요리는 무엇인가요? 219 | 질문: 그래, 최근에 새로 배운 타코 요리가 맛있었어. 꽤 성공한 느낌이었지. 220 | 답변: 타코는 특유의 맛이 있어서 잘 만들었을 때 정말 맛있죠. 새로운 레시피를 성공적으로 완성하신 것 축하드려요! 다음에는 어떤 요리에 도전하실 건가요? 221 | 질문: 음... 아직 확실히 정한 건 없지만, 이탈리안 요리에도 도전해보려고 생각중이야.","이탈리안 요리, 좋은 선택이죠. 풍부한 맛과 다양한 재료를 즐길 수 있어요. 특별히 어떤 이탈리안 요리를 만들어보고 싶으신가요?",gpt_4,multi_3 222 | "질문: 며칠 전에 봤던 영화가 너무 슬펐어. 223 | 답변: 슬픈 영화는 가끔 우리의 감정을 완전히 흔들어 놓죠. 그런 감동적인 장면이 어떤 것이었나요? 224 | 질문: 마지막에 주인공이 가족과 이별하는 장면이 있었는데, 그 장면이 너무 가슴이 아팠어. 225 | 답변: 그런 장면은 진짜 가슴이 먹먹하게 만들죠. 영화를 보면서 우리 자신을 돌아보게 되는 경우도 많아요. 혹시 영화를 보면서 느낀점은 또 어떤게 있었나요? 226 | 질문: 나도 가족을 소중히 해야겠다는 생각이 들었어.","그렇군요. 영화를 통해 중요한 가치를 깨닫게 되다니, 아주 값진 시간이었을 것 같네요. 그런데 가족과 함께 보낼 때 가장 행복하게 느끼는 순간은 언제인가요?",gpt_4,multi_3 227 | "질문: 얼마 전부터 수영을 배우고 있어. 건강을 위해서라도 스포츠를 좀 해야 할 것 같아서 228 | 답변: 아, 정말 멋진 생각이시네요. 수영은 전신 운동이라 건강에 매우 좋죠. 수영을 배우기 시작한 이후로 느낀 변화는 어떤 것들이 있나요? 229 | 질문: 확실히 체력이 좋아진 것 같아. 그리고 스트레스도 풀리는 것 같아. 230 | 답변: 그렇죠. 수영은 신체 건강뿐 아니라 정신적인 편안함까지 가져다주는 활동이죠. 혹시 물속에서는 어떤 생각을 하시나요? 231 | 질문: 아무 생각 안 하고 그냥 편안하게 물속을 즐기려고 해.",그런 시간이 참 소중하죠. 수영하면서 편안함을 느끼는 건 정말 좋은 경험이 될 것 같아요. 수영 외에도 다른 운동을 좋아하시나요?,gpt_4,multi_3 232 | "질문: 이번 주말에 친구들이랑 축구 경기 보러 가기로 했어. 너무 기대되네. 233 | 답변: 저도 들어보니 기대가 되네요! 친구들과의 즐거운 시간이 될 거예요. 어떤 팀을 응원하시나요? 234 | 질문: 나는 FC서울 팬이야. 하지만 친구들은 대부분 전북 현대를 응원해. 재미있을 거 같아. 235 | 답변: 각자 응원하는 팀이 다르니 경기를 보는 재미가 배가 되겠네요! 아, 어떤 선수를 가장 좋아하세요? 236 | 질문: 나는 김진수 선수를 가장 좋아해. 그 선수의 플레이 스타일이 정말 멋있어.","김진수 선수의 뛰어난 플레이, 정말 명장면이 많죠. 경기를 보면서 많은 활기를 느끼시길 바라요! 그 외에는 어떤 스포츠를 좋아하시나요?",gpt_4,multi_3 237 | "질문: 최근 들어 내 취미인 글쓰기가 재미없어져서 고민이야. 238 | 답변: 그럴 때가 가끔 있는 거 같아요. 어떤 부분이 특히 재미없어진 건가요? 239 | 질문: 처음에는 글쓰는 거 자체가 즐거웠는데, 이제는 내가 쓰는 글이 좋은지 나쁜지에만 집착하게 되더라구. 240 | 답변: 그런 부분에 과도하게 집중하게 되면 즐거움이 반감되곤 해요. 어떻게 하면 그런 부담감을 덜 수 있을까요? 241 | 질문: 글쓰는 것을 너무 업무처럼 생각해서 그런 것 같아. 다시 한 번 재미를 찾을 수 있는 방법이 있을까?",취미로서의 글쓰기는 본연의 즐거움이 있어야겠어요. 혹시 어떤 주제에 대해 쓰는 것이 좋아요?,gpt_4,multi_3 242 | "질문: 나 오늘 코딩 테스트 망쳤어.. 243 | 답변: 이런, 코딩 테스트 때문에 기분이 좋지 않으시군요. 😟 코딩 테스트는 항상 어려운 것 같아요. 어떤 부분이 어려우셨는지 알 수 있을까요? 244 | 질문: 푸는 방법을 아는 문제인데도 불구하고 시간이 부족해서 못 푼 문제가 있었어. 245 | 답변: 그런 상황이라니, 정말 아쉬우시겠어요. 시간 관리가 항상 코딩 테스트에서는 중요하죠. 😢 그럼 다음번에는 어떻게 하실 계획인가요? 246 | 질문: 그래도 내가 어떤 부분에서 시간을 잡아먹었는지 알았으니 다음번에는 그 부분을 개선해서 풀어야겠어.","그렇군요, 이미 문제점을 파악하셨군요. 잘하셨어요! 😊 그래서 다음번에는 더 좋은 결과를 얻으실 수 있을 것 같아요. 어떤 방식으로 개선해 나갈 생각이신가요?",gpt_4,multi_3 247 | "질문: 요즘 아이가 공부를 전혀 안해서 걱정이야. 248 | 답변: 아이의 공부 걱정이라니, 정말로 마음이 무겁겠어요. 😟 아이가 어떤 과목을 가장 어려워하는지 알려주실 수 있나요? 249 | 질문: 수학을 특히 안 좋아하더라고. 250 | 답변: 아이가 수학을 어려워한다니, 엄마나 아빠로서는 정말로 고민이 많을 것 같아요. 아이에게 수학을 좋아하게 하는 데 어떤 방법을 생각하고 계신가요? 251 | 질문: 아직 방법을 잘 모르겠어.","그렇군요, 아직 어떻게 해야 할지 고민 중이시군요. 아이에게 수학을 좋아하게 만드는 것은 쉽지 않을 것 같아요. 😔 아이의 선생님이나 학교에서 어떤 조언을 해주는지 들어보셨나요?",gpt_4,multi_3 252 | -------------------------------------------------------------------------------- /finetuning/test/test_dataset/test_single.csv: -------------------------------------------------------------------------------- 1 | instruction,output,source,type 2 | 오늘 나에게 힘을 줄 수 있는 좋은 노래를 들었어!,"와, 기분 좋아 보이시네요! 😊 좋은 노래를 듣고 에너지를 얻는 건 참 좋은 방법이죠. 어떤 노래를 들으셨나요?",gpt_4,single 3 | 주말에 읽은 책이 너무 좋아서 다시 읽고 싶어.,좋은 책을 만나셨군요! 책은 정말 특별한 친구처럼 우리에게 많은 것을 주죠. 그 책의 제목이나 내용 중 특히 기억에 남는 부분이 있을까요?,gpt_4,single 4 | 친구가 나한테 화를 내서 많이 상처받았어.,당신이 상처받은 것을 보니 제 마음도 아프네요. 😔 친구와의 문제는 정말 복잡하고 어렵죠. 그 친구가 왜 화를 냈는지 이야기해 볼 수 있을까요?,gpt_4,single 5 | 내일은 큰 집을 청소할 계획이야.,대단하신 분이시군요! 큰 집을 청소하는 것은 쉽지 않은 일이에요. 깨끗한 집에서 느끼는 만족감은 어떤 것보다 대단하죠. 청소하면서 음악이나 오디오북을 들어보는 건 어떨까요?,gpt_4,single 6 | 나 아침에 눈을 뜨는 게 정말 힘들어. 어떻게 해야 좀 더 잘 일어날 수 있을까?,"아침에 일어나는 게 힘들다는 건, 정말로 힘든 문제일 수 있어요. 😞 좀 더 잘 일어나기 위해 알람을 사용하거나, 특정 시간에 깨어나는 습관을 형성하려는 시도를 해보셨나요? 또는 깨어난 후에 몇 분 동안 스트레칭을 해보는 것도 효과적일 수 있어요.",gpt_4,single 7 | 꿈을 잘 기억하지 못해서 아쉬워.,"와, 꿈에 대한 호기심이 많으시군요! 꿈 일기를 써보는 건 어떠세요? 깨어나자마자 바로 기록하면 꿈을 잘 기억하는데 도움이 될 거예요. 어떤 종류의 꿈을 가장 기억하고 싶으신가요? 혹시 꿈을 잘 기억하고 싶은 특별한 이유가 있나요?",gpt_4,single 8 | 저희 아이가 유치원에 처음 입학했어요.,"아이의 첫번째 큰 단계, 정말 흥분되는 순간이죠! 유치원 생활은 아이에게 새로운 세상을 열어줄 거에요. 그 아이가 유치원에서 가장 좋아하는 활동은 무엇인가요?",gpt_4,single 9 | 매일 똑같은 일상이 심심해서 새로운 취미를 찾고 싶어요.,"새로운 취미를 찾으려는 시도, 멋진 도전이네요. 그럼에도 불구하고 무엇을 시작할지 결정하는 것은 어렵습니다. 혹시 특정한 분야에 관심이 있거나 해보고 싶은 것이 있는지 알려주실 수 있나요?",gpt_4,single 10 | 사진 찍는 것에 관심이 생겼어요.,"사진에 대한 관심, 멋진 선택이시네요! 사진은 순간을 기록하고 감정을 전달하는 아름다운 방법이죠. 어떤 종류의 사진을 찍고 싶으신가요?",gpt_4,single 11 | "요즘 건강하게 먹으려고 하는데, 매일 매일 메뉴를 정하는 게 힘드네요.","매일 건강한 음식을 준비하는 것은 확실히 도전적일 수 있어요. 그러나 건강한 식습관은 장기적으로 보면 많은 이점을 줄 수 있답니다! 😊 특정 영양소나 음식에 초점을 맞추고 있나요? 또한, 자주 만드는 건강 음식 레시피가 있나요?",gpt_4,single 12 | 자전거를 타는 것을 좋아해요.,"자전거를 좋아하시다니, 신선한 공기와 풍경을 즐기는 좋은 방법이죠! 자전거는 건강에도 좋고, 자연을 느끼는데도 최적이에요. 가장 기억에 남는 자전거 타기 경험은 무엇인가요?",gpt_4,single 13 | 제가 좋아하는 음식을 만들어 보려고 하는데 어디서부터 시작해야 할까요?,"음식 만들기를 시작하려고 하시다니, 정말 멋진 계획이네요! 만들고 싶은 음식이 특정하다면, 그 음식의 레시피를 찾는 것부터 시작하는 건 어떨까요? 쿠킹 클래스나 온라인 레시피를 찾아보는 것도 도움이 될 것 같네요.",gpt_4,single 14 | 피아노를 배우고 싶은데 너무 늦은 나이가 아닐까 걱정이야.,피아노 배우기를 원하시다니 멋진 생각이에요! 🎹 나이는 학습의 장애물이 되지 않아요. 어떤 곡을 가장 먼저 배우고 싶으신가요? 혹시 특정한 목표가 있으신가요?,gpt_4,single 15 | 나는 사회에서 많은 사람들을 도와주는 일을 하고 싶어.,정말 멋진 목표에요! 사회에서 많은 사람들을 도와주는 일은 인정받고 보람을 느낄 수 있는 일이에요. 당신의 선한 마음과 도움을 주는 열정이 주변 사람들에게 큰 영감을 줄 거예요. 어떤 방식으로 사람들을 도와주고 싶으신가요?,gpt_4,single 16 | 최근에 마라톤에 참가해서 기록을 갱신했어요!,"와우, 마라톤에 참가해서 기록 갱신까지 하셨다니 정말 대단하시네요! 열심히 준비하셨을 텐데, 어떤 부분이 가장 힘들었나요?",gpt_4,single 17 | 온라인 쇼핑에서 원하는 사이즈를 찾기 힘들어.,"그렇죠, 온라인에서 사이즈를 정확하게 알아내는 것은 쉽지 않아요. 사이즈표를 확인하거나, 리뷰를 참고해보는 것도 도움이 될 수 있어요. 특정한 옷을 찾고 계신가요?",gpt_4,single 18 | 이번 시험에서 낙제했어요. 많이 실망스러워요.,"아이고, 그렇군요. 시험에서 원하는 결과를 얻지 못한 건 정말 힘든 일이죠. 하지만 이것이 모든 것을 결정하지는 않아요. 어떤 과목에서 어려움을 느끼셨나요? 어떤 부분이 가장 어려웠는지 얘기해주실 수 있을까요?",gpt_4,single 19 | 집에서 싸우는 소리 때문에 공부가 안돼.,"지금 상황이 참 힘드시겠어요. 😢 집에서의 갈등이 공부에 방해가 되는 상황, 정말로 마음이 아플 것 같아요. 이런 상황이 얼마나 자주 일어나나요? 그리고 이런 때, 당신은 어떻게 해결하려고 노력하나요?",gpt_4,single 20 | 최근에 친구들이랑 소통이 잘 안되는 것 같아.,지금 상황이 참 힘드시겠어요. 서로에 대해 이해하고 소통하는 건 때로는 쉽지 않죠. 혹시 어떤 부분에서 소통이 어려운지 좀 더 구체적으로 이야기해 볼 수 있을까요? 😊,gpt_4,single 21 | 비 올때 책읽는 걸 좋아해요.,"비가 오는 날의 책읽기, 분위기도 좋고 매력적이겠네요! 비 내리는 소리와 함께하는 책 읽기는 몰입도가 높아져서 좋을 것 같아요. 어떤 종류의 책을 선호하시나요?",gpt_4,single 22 | 주말에 친구들과 캠핑 가기로 했어! 정말 기대되네!,"와, 친구들과의 캠핑이라니! 정말 신나겠네요! 🏕️ 캠핑 장비들은 모두 준비하셨나요? 그리고 특별한 캠핑 메뉴 계획이 있나요?",gpt_4,single 23 | 최근에 야경 그림 그리기에 빠져 있어요.,"야경 그림이라니, 참 흥미롭군요! 밤의 아름다움을 담는 것은 독특한 감성이 필요하죠. 야경을 그리는 것 중에서 가장 좋아하는 부분은 무엇인가요?",gpt_4,single 24 | 주말에 친구들과 캠핑을 가기로 했어.,"주말에 친구들과 캠핑을 가시는군요, 정말 재미있을 것 같아요! 아무래도 준비물이 많아질텐데, 빼놓지 말아야 할 캠핑 용품이 있나요?",gpt_4,single 25 | 나는 요즘 헬스에 정말 빠져 있어!,"헬스에 빠져있다니 멋진 일이네요! 💪 몸매 관리와 건강을 위한 노력은 정말 가치 있는 일입니다. 어떤 운동을 주로 하시나요, 유산소 운동인가요, 아니면 근력 운동인가요?",gpt_4,single 26 | 자동차 경주에 참여하려고 준비 중이에요.,자동차 경주에 참여하려고 준비하시는군요! 그것은 정말 흥미롭고 도전적인 경험이 될 것 같아요. 준비 과정에서 가장 흥미롭거나 도전적인 부분은 무엇인가요?,gpt_4,single 27 | 저는 주말에 책을 읽는 걸 좋아해요.,그것은 정말 멋진 취미를 가지고 계신 거예요! 책은 우리에게 새로운 시각을 제공하고 마음을 열어줍니다. 가장 인상 깊게 읽은 책은 무엇인가요?,gpt_4,single 28 | 요즘 공부에 집중이 잘 안 돼. 기말고사가 얼마 안 남았는데 말이야.,기말고사가 코앞인데 집중이 안 된다니 걱정되시겠어요.😞 어떤 과목에 대해 고민하고 계신지 알려주실 수 있나요? 아니면 특정한 학습 방법에 문제가 있을까요?,gpt_4,single 29 | 최근에 자주 피곤하고 힘들게 느껴져요.,"많이 피곤하신 거 같아요, 그럴 때마다 정말 힘드시겠어요.😞 특별한 이유로 피곤하시다면 그 이유를 알려주실 수 있나요? 아니면 그냥 일상생활에서의 피로인가요?",gpt_4,single 30 | 고양이가 아프다고해서 병원에 데려갔어요.,"고양이가 아프시다니, 걱정이 많으시겠어요. 아이들이 아프면 가장 힘든 순간인 것 같아요. 그럼에도 불구하고 이런 상황을 공유해주셔서 감사합니다. 병원에서 어떤 진단을 받으셨나요?",gpt_4,single 31 | 요즘 약간 우울한 기분이 들어요.,"그럴 때도 있는 건데요, 가끔은 힘든 시기가 찾아오죠. 당신이 어떤 감정을 느끼고 있는지 이해합니다. 그런 감정을 조금이나마 완화시키는 데 도움이 될만한 일이 있을까요? 혹은 당신이 우울한 이유를 조금 더 털어놓아도 괜찮을까요?",gpt_4,single 32 | 친구와의 관계가 좋지 않아서 스트레스 받아요.,친구와의 문제는 정말로 마음에 걸리는 일이죠. 상대방을 이해하려는 노력과 솔직하게 이야기하려는 용기가 중요합니다. 혹시 이 문제를 해결하기 위해 생각해보셨던 방법이 있나요?,gpt_4,single 33 | 몇 달 전부터 정원 가꾸기에 흥미를 느끼고 있어요.,"정원 가꾸기에 흥미를 느끼신다니, 그것은 아주 특별하고 멋진 취미를 가지셨네요! 이는 스트레스를 해소하는 데 도움이 될 것이랍니다. 어떤 식물을 가꾸고 싶으신가요? 꽃을 좋아하시나요, 아니면 채소를 직접 재배해 보고 싶으신가요?",gpt_4,single 34 | 주식 투자로 크게 손해를 봤어.,"주식 투자에서의 손해, 정말로 마음이 아프겠어요. 하지만 이 경험을 통해 얻은 교훈도 분명 있을 거예요. 손해를 보게 된 원인에 대해 좀 더 알려주실 수 있을까요?",gpt_4,single 35 | "최근에 너무 우울해요. 모든게 힘들고, 무기력해지는 기분이에요.","그런 기분일 때는 정말로 힘드실 거에요. 모든 것이 의미 없고 힘들게 느껴지는 것, 그건 쉽게 넘어가는 일이 아니죠. 그런 상황에서 도움이 될 수 있는 방법을 찾는 것도 중요해요. 최근에 이런 기분이 드는 특별한 이유가 있을까요? 아니면 어떤 사건이 있었나요?",gpt_4,single 36 | 음악회에서 가장 좋아하는 곡을 연주했어.,"와, 그거 정말 멋진 경험일 것 같아요! 좋아하는 곡을 연주한다는 것은 특별한 감정을 느끼게 해주죠. 연주하는 동안 어떤 감정을 느꼈나요?",gpt_4,single 37 | "친구에게 선물을 사려고 하는데, 어떤 걸 사야 할지 고민 중이야.","오, 친구에게 선물을 고르는 것은 정말 즐거운 일이죠! 선물은 그 사람이 당신을 생각하며 즐거움을 느끼게 합니다. 친구가 좋아할 만한 취미나 관심사가 있나요?",gpt_4,single 38 | 요즘 자꾸 허리가 아파서 고민이야.,"그런 아픔을 겪고 계시다니 정말 고생이 많으십니다. 아픔은 일상 생활에 꽤 큰 영향을 미치는 요소죠. 그런데, 어떤 활동을 하면 허리가 더 아프신가요? 💭",gpt_4,single 39 | 반려동물의 건강을 위해 규칙적인 운동을 시켜야 해.,"그렇게 생각하시는 건 정말 좋아요! 반려동물의 건강을 위해서는 규칙적인 운동이 필요해요. 함께 운동하면서 그들의 건강을 챙기고, 더 많은 에너지를 소비할 수 있어요. 어떤 종류의 운동이 좋을지 생각해보셨나요? 혹시 어떤 활동을 추천하고 싶으신가요?",gpt_4,single 40 | 오늘 점심 메뉴로 뭐 먹을까 고민이야.,"허허, 점심 메뉴 선택은 정말 고민이죠. 지금 기분에 따라서 선택해 보시는 건 어떠세요? 😊 예전에 어떤 음식을 먹었을 때 가장 기분이 좋았던 건 무엇인가요?",gpt_4,single 41 | 최근 제 삶이 너무 루틴하게 느껴져서 지루해요.,일상이 루틴하고 반복되어 지루하게 느껴지는 순간이 있죠. 그런데 그게 바로 새로운 변화를 준비하는 좋은 기회일 수 있어요. 어떤 일을 좋아하시나요? 혹시 그것을 취미로 시작해보는 건 어떨까요?,gpt_4,single 42 | 새로운 취미를 찾고 있어! 기분 전환을 하고 싶거든.,"신나는 취미를 찾으려고 하시는 것, 정말 멋진 생각이에요! 😊 특별히 무슨 취미에 관심이 있으신가요, 아니면 새로운 것을 탐색하고 계신 건가요? ",gpt_4,single 43 | 방학이 되니까 뭐 할 것도 없고 너무 심심해.,"방학이지만 할 일이 없어서 심심하시군요. 이럴 때 조금 무기력하게 느껴질 수 있죠. 😞 하지만, 이 시간을 활용해 새로운 것을 배우거나 해볼까 생각해 본 적 있으신가요?",gpt_4,single 44 | 운동하다가 발목을 살짝 다쳤어요.,"아이고, 그럴 때는 참 힘드시겠어요. 운동 중 다치는 것은 예상치 못한 일이라 걱정이 되시겠어요. 지금은 어떻게 지내고 계신가요?",gpt_4,single 45 | 작년에 비해 올해 여름이 더 덥더라.,올해 여름이 특히 더웠다는 말씀이시군요. 기후 변화로 인해 날씨가 예전보다 불안정해지고 있어요. 어떻게 더위를 피하고 계신가요? 여름에 가장 좋아하는 활동이 있으신가요?,gpt_4,single 46 | 나 요즘 새로운 언어를 배우려고 하고 있어.,"아하, 정말 대단하시네요! 새로운 언어를 배우는 것은 두뇌에도 좋고 문화적 시야를 넓혀주는 훌륭한 방법이죠. 어떤 언어를 배우고 있나요? 그 언어를 배우기로 한 이유가 있나요?",gpt_4,single 47 | 사랑하는 사람에게 프로포즈하려고 하는데 긴장돼.,"사랑하는 사람에게 마음을 고백하려 하시다니 정말 특별한 순간이네요. 하지만 긴장감이 사랑의 감정을 더욱 높여주기도 해요. 어떻게 프로포즈를 계획하고 계신가요? 특별한 장소에서 하고 싶으신가요, 아니면 사랑하는 사람과의 특별한 순간에 하고 싶으신가요?",gpt_4,single 48 | 요즘 새로운 취미를 찾고 있는데 어떤 걸 해볼까요?,"새로운 취미를 찾으신다니 정말 좋은 아이디어네요! 새로운 취미는 일상에 색다른 즐거움을 더해주는 멋진 방법이죠. 혹시 어떤 분야에 흥미가 있으신지 알 수 있을까요? 예를 들어, 예술, 스포츠, 음악 등 어떤 것에 가장 관심이 있나요?",gpt_4,single 49 | "어제, 친구들과 공원에서 피크닉을 했어요.",다양한 사람들과 함께 시간을 보내는 건 정말 즐거운 일이죠! 공원에서의 피크닉은 특별한 추억을 만들어주는 시간이에요. 피크닉에서 가장 재미있었던 순간은 무엇이었나요?,gpt_4,single 50 | 여름 휴가 계획을 세우려고 하는데 너무 설레요!,"와, 그거 정말 신나는 일이죠! 여름 휴가 계획을 세우는 것만으로도 설렘이 가득하죠. 여행지에 대해 생각해보셨나요? 아니면 특별히 가고 싶은 곳이 있으신가요?",gpt_4,single 51 | "진짜 맛있는 피자집을 찾았는데, 혼자 가기는 좀 그래요.",혼자 맛있는 피자를 먹으러 가는 건 조금 부끄럽게 느껴질 수 있겠네요. 그럼 친구나 가족과 같이 가보는 건 어떨까요? 아니면 배달을 이용하는 건 어떨까요?,gpt_4,single 52 | -------------------------------------------------------------------------------- /finetuning/train.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | import torch 5 | import transformers 6 | import yaml 7 | 8 | from peft import ( 9 | LoraConfig, 10 | get_peft_model, 11 | get_peft_model_state_dict, 12 | prepare_model_for_kbit_training, 13 | set_peft_model_state_dict, 14 | ) 15 | from transformers import GPTNeoXForCausalLM, GPTNeoXTokenizerFast, BitsAndBytesConfig 16 | from utils.data_loader import * 17 | from utils.arguments import TrainArguments 18 | from utils.compute_metrics import * 19 | 20 | 21 | def train(train_args: TrainArguments): 22 | params = train_args.dict() 23 | print( 24 | "Training Alpaca-LoRA model with params:\n" + 25 | "\n".join([f" {k}: {v}" for k, v in params.items()]) 26 | ) 27 | assert train_args.base_model, "Please specify a --base_model, e.g. --base_model='smoked_salmons/awesome_llm'" 28 | 29 | 30 | # wandb configuration 31 | use_wandb = len(train_args.wandb_project) > 0 or ("WANDB_PROJECT" in os.environ and len(os.environ["WANDB_PROJECT"]) > 0) 32 | 33 | if use_wandb: 34 | os.environ["WANDB_PROJECT"] = train_args.wandb_project 35 | if len(train_args.wandb_entity) > 0: 36 | os.environ["WANDB_ENTITY"] = train_args.wandb_entity 37 | if len(train_args.wandb_watch) > 0: 38 | os.environ["WANDB_WATCH"] = train_args.wandb_watch 39 | if len(train_args.wandb_log_model) > 0: 40 | os.environ["WANDB_LOG_MODEL"] = train_args.wandb_log_model 41 | 42 | 43 | # load and preprocess data 44 | tokenizer = GPTNeoXTokenizerFast.from_pretrained(train_args.base_model) 45 | tokenizer.pad_token_id = 0 # unk. we want this to be different from the eos token 46 | tokenizer.padding_side = "left" # Allow batched inference 47 | train_data, val_data = load_and_preprocess_data(train_args, tokenizer) 48 | 49 | 50 | # load model and finetune 51 | bnb_config = BitsAndBytesConfig( 52 | load_in_4bit=True, 53 | bnb_4bit_use_double_quant=True, 54 | bnb_4bit_quant_type="nf4", 55 | bnb_4bit_compute_dtype=torch.bfloat16 56 | ) 57 | 58 | model = GPTNeoXForCausalLM.from_pretrained( 59 | train_args.base_model, 60 | quantization_config=bnb_config, 61 | device_map={"":0}, 62 | ) 63 | 64 | model.gradient_checkpointing_enable() 65 | model = prepare_model_for_kbit_training(model) 66 | 67 | lora_config = LoraConfig( 68 | r=train_args.lora_r, 69 | lora_alpha=train_args.lora_alpha, 70 | target_modules=train_args.lora_target_modules, 71 | lora_dropout=train_args.lora_dropout, 72 | bias="none", 73 | task_type="CAUSAL_LM", 74 | ) 75 | 76 | model = get_peft_model(model, lora_config) 77 | 78 | if train_args.resume_from_checkpoint: 79 | # Check the available weights and load them 80 | checkpoint_name = os.path.join(train_args.resume_from_checkpoint, "pytorch_model.bin") # Full checkpoint 81 | if not os.path.exists(checkpoint_name): 82 | checkpoint_name = os.path.join(train_args.resume_from_checkpoint, "adapter_model.bin") # only LoRA model - LoRA config above has to fit 83 | train_args.resume_from_checkpoint = False # So the trainer won't try loading its state 84 | # The two files above have a different name depending on how they were saved, but are actually the same. 85 | if os.path.exists(checkpoint_name): 86 | print(f"Restarting from {checkpoint_name}") 87 | adapters_weights = torch.load(checkpoint_name) 88 | set_peft_model_state_dict(model, adapters_weights) 89 | else: 90 | print(f"Checkpoint {checkpoint_name} not found") 91 | 92 | model.print_trainable_parameters() # Be more transparent about the % of trainable params. 93 | 94 | training_args = transformers.TrainingArguments( 95 | per_device_train_batch_size=train_args.micro_batch_size, 96 | gradient_accumulation_steps=train_args.batch_size//train_args.micro_batch_size, 97 | num_train_epochs=train_args.num_epochs, 98 | learning_rate=train_args.learning_rate, 99 | weight_decay=train_args.weight_decay, 100 | warmup_ratio=train_args.warmup_ratio, 101 | lr_scheduler_type=train_args.lr_scheduler_type, 102 | fp16=True, 103 | logging_steps=train_args.logging_steps, 104 | optim=train_args.optimizer, 105 | evaluation_strategy="steps" if train_args.val_set_size > 0 else "no", 106 | save_strategy="steps", 107 | eval_steps=train_args.eval_steps if train_args.val_set_size > 0 else None, 108 | save_steps=train_args.eval_steps, 109 | output_dir=train_args.finetune_dir, 110 | save_total_limit=train_args.save_total_limit, 111 | load_best_model_at_end=True if train_args.val_set_size > 0 else False, 112 | ddp_find_unused_parameters=None, 113 | group_by_length=train_args.group_by_length, 114 | report_to="wandb" if use_wandb else None, 115 | run_name=train_args.wandb_run_name if use_wandb else None, 116 | ) 117 | 118 | trainer = transformers.Trainer( 119 | model=model, 120 | train_dataset=train_data, 121 | eval_dataset=val_data, 122 | args=training_args, 123 | data_collator=transformers.DataCollatorForSeq2Seq( 124 | tokenizer, pad_to_multiple_of=8, return_tensors="pt", padding=True 125 | ), 126 | compute_metrics=train_compute_metrics if train_args.use_compute_metrics else None, 127 | ) 128 | model.config.use_cache = False 129 | 130 | old_state_dict = model.state_dict 131 | model.state_dict = (lambda self, *_, **__: get_peft_model_state_dict(self, old_state_dict())).__get__( 132 | model, type(model) 133 | ) 134 | 135 | if torch.__version__ >= "2" and sys.platform != "win32": 136 | model = torch.compile(model) 137 | 138 | with torch.autocast("cuda"): 139 | trainer.train(resume_from_checkpoint=train_args.resume_from_checkpoint) 140 | 141 | evaluation_result = trainer.evaluate(eval_dataset=val_data) 142 | print(evaluation_result) 143 | 144 | model.save_pretrained(train_args.finetune_dir) 145 | 146 | print("\n If there's a warning about missing keys above, please disregard :)") 147 | 148 | 149 | def load_config(config_file: str): 150 | with open(config_file, 'r') as f: 151 | config = yaml.safe_load(f) 152 | return TrainArguments(**config) 153 | 154 | 155 | def main(): 156 | try: 157 | config_path = sys.argv[1] 158 | except IndexError: 159 | config_path = './config.yaml' 160 | train_args = load_config(config_path) 161 | train(train_args) 162 | 163 | 164 | if __name__ == "__main__": 165 | main() -------------------------------------------------------------------------------- /finetuning/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampaitech5/level3_nlp_finalproject-nlp-12/46c8b4ccbc9ca85087ea0c7aa7fdf3f9d8fad015/finetuning/utils/__init__.py -------------------------------------------------------------------------------- /finetuning/utils/arguments.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from pydantic import BaseModel 4 | 5 | class TrainArguments(BaseModel): 6 | # model/data params 7 | base_model: str = "beomi/polyglot-ko-12.8b-safetensors" 8 | data_path: str = "ohilikeit/empathetic_dialogues_kr" 9 | finetune_dir: str = "" 10 | merge_dir: str = "" 11 | prompt_template_name: str = "kullm-v2" 12 | # training hyperparams 13 | batch_size: int = 128 14 | micro_batch_size: int = 8 15 | num_epochs: int = 3 16 | learning_rate: float = 3e-4 17 | cutoff_len: int = 512 18 | val_set_size: int = 200 19 | save_total_limit: int = 10 20 | logging_steps: int = 1 21 | optimizer: str = "adamw_torch" 22 | eval_steps: int = 10 23 | weight_decay: float = 0. 24 | warmup_ratio: float = 0.1 25 | lr_scheduler_type: str = 'linear' 26 | resume_from_checkpoint: str = None 27 | use_compute_metrics: bool = False 28 | # lora hyperparams 29 | lora_r: int = 32 30 | lora_alpha: int = 64 31 | lora_dropout: float = 0.05 32 | lora_target_modules: List[str] = ["query_key_value", "xxx"] 33 | # llm hyperparams 34 | train_on_inputs: bool = True 35 | add_eos_token: bool = False 36 | group_by_length: bool = False 37 | # wandb params 38 | wandb_entity: str = "" 39 | wandb_project: str = "" 40 | wandb_run_name: str = "" 41 | wandb_watch: str = "" 42 | wandb_log_model: str = "" 43 | # inference setting 44 | save_file_name: str = "" 45 | dataset_type: str = "" -------------------------------------------------------------------------------- /finetuning/utils/callbacks.py: -------------------------------------------------------------------------------- 1 | """ 2 | Helpers to support streaming generate output. 3 | Borrowed from https://github.com/oobabooga/text-generation-webui/blob/ad37f396fc8bcbab90e11ecf17c56c97bfbd4a9c/modules/callbacks.py 4 | """ 5 | 6 | import gc 7 | import traceback 8 | from queue import Queue 9 | from threading import Thread 10 | 11 | import torch 12 | import transformers 13 | 14 | 15 | class Stream(transformers.StoppingCriteria): 16 | def __init__(self, callback_func=None): 17 | self.callback_func = callback_func 18 | 19 | def __call__(self, input_ids, scores) -> bool: 20 | if self.callback_func is not None: 21 | self.callback_func(input_ids[0]) 22 | return False 23 | 24 | 25 | class Iteratorize: 26 | 27 | """ 28 | Transforms a function that takes a callback 29 | into a lazy iterator (generator). 30 | """ 31 | 32 | def __init__(self, func, kwargs={}, callback=None): 33 | self.mfunc = func 34 | self.c_callback = callback 35 | self.q = Queue() 36 | self.sentinel = object() 37 | self.kwargs = kwargs 38 | self.stop_now = False 39 | 40 | def _callback(val): 41 | if self.stop_now: 42 | raise ValueError 43 | self.q.put(val) 44 | 45 | def gentask(): 46 | try: 47 | ret = self.mfunc(callback=_callback, **self.kwargs) 48 | except ValueError: 49 | pass 50 | except: 51 | traceback.print_exc() 52 | pass 53 | 54 | self.q.put(self.sentinel) 55 | if self.c_callback: 56 | self.c_callback(ret) 57 | 58 | self.thread = Thread(target=gentask) 59 | self.thread.start() 60 | 61 | def __iter__(self): 62 | return self 63 | 64 | def __next__(self): 65 | obj = self.q.get(True, None) 66 | if obj is self.sentinel: 67 | raise StopIteration 68 | else: 69 | return obj 70 | 71 | def __enter__(self): 72 | return self 73 | 74 | def __exit__(self, exc_type, exc_val, exc_tb): 75 | self.stop_now = True -------------------------------------------------------------------------------- /finetuning/utils/compute_metrics.py: -------------------------------------------------------------------------------- 1 | from datasets import load_metric 2 | from transformers import GPTNeoXTokenizerFast, GPTNeoXForCausalLM 3 | import numpy as np 4 | from rouge import Rouge 5 | from statistics import geometric_mean 6 | import torch 7 | from tqdm import tqdm 8 | 9 | 10 | def train_compute_metrics(pred): 11 | 12 | model = GPTNeoXForCausalLM.from_pretrained('nlpai-lab/kullm-polyglot-12.8b-v2') 13 | 14 | logits = torch.tensor(pred.predictions.argmax(-1).flatten(), dtype=torch.int64) 15 | logits = logits.unsqueeze(0) # torch.Size([1, 35200]) 16 | 17 | max_length = model.config.max_position_embeddings # 2048 18 | stride = 1024 19 | seq_len = logits.size(1) 20 | 21 | nlls = [] 22 | prev_end_loc = 0 23 | for begin_loc in tqdm(range(0, seq_len, stride)): 24 | end_loc = min(begin_loc + max_length, seq_len) 25 | trg_len = end_loc - prev_end_loc # 마지막 루프의 스트라이드 값과 다를 수 있음 26 | input_ids = logits[:, begin_loc:end_loc] 27 | target_ids = input_ids.clone() 28 | target_ids[:, :-trg_len] = -100 29 | 30 | with torch.no_grad(): 31 | outputs = model(input_ids, labels=target_ids) 32 | 33 | # 손실은 모든 유효한 레이블에 대한 평균값을 구하는 교차 엔트로피(cross entropy)로 계산됩니다. 34 | # 나이브 베이지안 모델은 내부적으로 레이블을 왼쪽으로 1개씩 밀기 때문에, (타켓 - 1)개 만큼의 레이블에 대해 손실을 계산합니다. 35 | neg_log_likelihood = outputs.loss 36 | 37 | nlls.append(neg_log_likelihood) 38 | prev_end_loc = end_loc 39 | if end_loc == seq_len: 40 | break 41 | 42 | ppl = torch.exp(torch.stack(nlls).mean()) 43 | 44 | return {'perplexity':ppl} 45 | 46 | 47 | def test_compute_metrics(pred): 48 | tokenizer = GPTNeoXTokenizerFast.from_pretrained('nlpai-lab/kullm-polyglot-12.8b-v2') 49 | 50 | # 사용할 metric을 불러옵니다. 51 | metric_bleu = load_metric("sacrebleu") 52 | metric_meteor = load_metric("meteor") 53 | metric_rouge = Rouge(metrics=["rouge-1", "rouge-2", "rouge-3", "rouge-4", "rouge-5", "rouge-l"]) 54 | metric_bertscore = load_metric("bertscore") 55 | 56 | # 학습에서 산출된 pred를 preds(모델이 생성)와 label(정답 데이터)로 분리합니다. 57 | preds = pred.predictions.argmax(-1) 58 | labels = pred.label_ids 59 | labels = np.where(pred.label_ids != -100, labels, tokenizer.pad_token_id) 60 | 61 | scores = { 62 | 'sacre_bleu': [], 63 | 'meteor': [], 64 | 'rouge_l_f1': [], 65 | 'bert_score_f1': [], 66 | } 67 | 68 | for i in range(len(preds)): 69 | decoded_preds = tokenizer.decode(preds[i], skip_special_tokens=True) 70 | decoded_labels = tokenizer.decode(labels[i], skip_special_tokens=True) 71 | if "### 응답:" in decoded_preds: 72 | decoded_preds = decoded_preds.split('### 응답:\n')[1][:-1] 73 | 74 | bleu_score = metric_bleu.compute(predictions=[decoded_preds], references=[[decoded_labels]])["score"] 75 | meteor_score = metric_meteor.compute(predictions=[decoded_preds], references=[decoded_labels])["meteor"] 76 | rouge_scores = metric_rouge.get_scores(decoded_preds, decoded_labels, avg=True)["rouge-l"]['f'] 77 | bert_score = metric_bertscore.compute(predictions=[decoded_preds], references=[decoded_labels], lang='ko')["f1"][0] 78 | 79 | scores['sacre_bleu'].append(bleu_score / 100) 80 | scores['meteor'].append(meteor_score) 81 | scores['rouge_l_f1'].append(rouge_scores) 82 | scores['bert_score_f1'].append(bert_score) 83 | 84 | scores = {k: geometric_mean(v) for k, v in scores.items()} 85 | 86 | return {k: round(v, 5) for k, v in scores.items()} -------------------------------------------------------------------------------- /finetuning/utils/data_loader.py: -------------------------------------------------------------------------------- 1 | from datasets import load_dataset 2 | from transformers import GPTNeoXTokenizerFast 3 | from utils.prompter import Prompter 4 | from utils.arguments import TrainArguments 5 | 6 | 7 | def load_and_preprocess_data(train_args: TrainArguments, tokenizer: GPTNeoXTokenizerFast): 8 | 9 | if train_args.data_path.endswith(".json") or train_args.data_path.endswith(".jsonl"): 10 | data = load_dataset("json", data_files=train_args.data_path) 11 | else: 12 | data = load_dataset(train_args.data_path) 13 | 14 | print(data) 15 | 16 | prompter = Prompter(template_name = train_args.prompt_template_name, 17 | verbose = False) 18 | 19 | def tokenize(prompt, add_eos_token=True): 20 | result = tokenizer( 21 | prompt, 22 | truncation=True, 23 | max_length=train_args.cutoff_len, 24 | padding=False, 25 | return_tensors=None, 26 | ) 27 | if ( 28 | result["input_ids"][-1] != tokenizer.eos_token_id 29 | and len(result["input_ids"]) < train_args.cutoff_len 30 | and add_eos_token 31 | ): 32 | result["input_ids"].append(tokenizer.eos_token_id) 33 | result["attention_mask"].append(1) 34 | 35 | result["labels"] = result["input_ids"].copy() 36 | 37 | return result 38 | 39 | def generate_and_tokenize_prompt(data_point): 40 | full_prompt = prompter.generate_prompt( 41 | instruction = data_point["instruction"], 42 | label = data_point["output"], 43 | ) 44 | tokenized_full_prompt = tokenize(full_prompt) 45 | user_prompt = prompter.generate_prompt(data_point["instruction"]) 46 | tokenized_user_prompt = tokenize(user_prompt, add_eos_token=train_args.add_eos_token) 47 | user_prompt_len = len(tokenized_user_prompt["input_ids"]) - int(train_args.add_eos_token) 48 | tokenized_full_prompt["labels"] = [-100] * user_prompt_len + tokenized_full_prompt["labels"][user_prompt_len:] 49 | 50 | return tokenized_full_prompt 51 | 52 | if train_args.val_set_size > 0: 53 | train_val = data["train"].train_test_split(test_size=train_args.val_set_size, shuffle=True, seed=42) 54 | train_data = train_val["train"].shuffle().map(generate_and_tokenize_prompt) 55 | val_data = train_val["test"].shuffle().map(generate_and_tokenize_prompt) 56 | else: 57 | train_data = data["train"].shuffle().map(generate_and_tokenize_prompt) 58 | val_data = None 59 | 60 | return train_data, val_data -------------------------------------------------------------------------------- /finetuning/utils/prompter.py: -------------------------------------------------------------------------------- 1 | """ 2 | A dedicated helper to manage templates and prompt building. 3 | """ 4 | 5 | import re 6 | import json 7 | import os.path as osp 8 | from typing import Union 9 | 10 | 11 | class Prompter(object): 12 | __slots__ = ("template", "_verbose") 13 | 14 | def __init__(self, template_name: str = "", verbose: bool = False): 15 | self._verbose = verbose 16 | if not template_name: 17 | # Enforce the default here, so the constructor can be called with 'kullm' and will not break. 18 | template_name = "multi" 19 | file_name = osp.join("templates", f"{template_name}.json") 20 | if not osp.exists(file_name): 21 | raise ValueError(f"Can't read {file_name}") 22 | with open(file_name) as fp: 23 | self.template = json.load(fp) 24 | if self._verbose: 25 | print( 26 | f"Using prompt template {template_name}: {self.template['description']}" 27 | ) 28 | 29 | def generate_prompt( 30 | self, 31 | instruction: Union[None, str] = None, 32 | label: Union[None, str] = None, 33 | ) -> str: 34 | # returns the full prompt from optional input 35 | # if a label (=response, =output) is provided, it's also appended. 36 | 37 | # 멀티 턴 대화 처리하는 부분 38 | def converter(sentence): 39 | result = re.sub(r"질문\s*", "### 명령어", sentence) 40 | result = re.sub(r"답변\s*", "### 응답", result) 41 | 42 | return result 43 | 44 | instruction = converter(instruction) 45 | new_instruction = instruction.split('\n')[-1] 46 | history = instruction[:-len(new_instruction)] 47 | try: 48 | new_instruction = new_instruction.split('### 명령어: ')[1] 49 | except: 50 | new_instruction = new_instruction.split('### 명령어: ')[0] 51 | 52 | res = self.template["prompt"].format(history=history, instruction=new_instruction) 53 | 54 | if label: 55 | res = f"{res}{label}" 56 | if self._verbose: 57 | print(res) 58 | return res 59 | 60 | def get_response(self, output: str) -> str: 61 | return output.split(self.template["response_split"])[1].strip() 62 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "finalproject" 3 | version = "0.1.0" 4 | description = "Large Langauge Model by Smoked_Salmons" 5 | authors = ["Jihye Mun ", "Gyeongtaek Bahk ", "Jieun Park ", "Inseo Song ", "Jihwan Yoon "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = ">=3.9,<3.9.7 || >3.9.7,<3.10" 10 | transformers = "^4.30.2" 11 | torch = "2.0.0" 12 | accelerate = "^0.20.1" 13 | datasets = "^2.13.1" 14 | tqdm = "^4.65.0" 15 | pandas = "^2.0.2" 16 | omegaconf = "^2.3.0" 17 | wandb = "^0.15.4" 18 | huggingface-hub = "^0.16.4" 19 | scipy = "^1.11.1" 20 | fire = "^0.5.0" 21 | gradio = "^3.36.1" 22 | langchain = "^0.0.228" 23 | bitsandbytes = "^0.39.1" 24 | peft = { git = "https://github.com/huggingface/peft.git" } 25 | fastapi = "^0.70.0" 26 | uvicorn = "^0.15.0" 27 | motor = "^3.2.0" 28 | pydantic = "1.10.9" 29 | pymongo = "^4.4.0" 30 | openai = "^0.27.8" 31 | python-dotenv = "^1.0.0" 32 | faiss-cpu = "^1.7.4" 33 | faiss-gpu = "^1.7.2" 34 | urllib3 = "1.26.11" 35 | rouge = "^1.0.1" 36 | tiktoken = "^0.4.0" 37 | ipykernel = "^6.24.0" 38 | seaborn = "^0.12.2" 39 | konlpy = "^0.6.0" 40 | 41 | [tool.poetry.group.dev.dependencies] 42 | pytest = "^6.2.5" 43 | 44 | [build-system] 45 | requires = ["poetry-core"] 46 | build-backend = "poetry.core.masonry.api" -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 | # 서빙 2 | ![project_architecture](https://github.com/boostcampaitech5/level3_nlp_finalproject-nlp-12/blob/main/assets/image/project_architecture.png) 3 | 4 | ## 환경 5 | ||version| 6 | |---|---| 7 | |Python|3.9.0| 8 | |npm|6.14.18| 9 | 10 | 상세 버전은 `pyproject.toml`(Backend) 및 `server/frontend/package.json`(Frontend) 참조 11 | 12 | ## 구조 13 | ``` 14 | server/ 15 | ├── README.md 16 | ├── backend/ 17 | | ├── config/ 18 | | | └── settings.py // .env 로드 19 | | ├── app.py // FastAPI 서버 생성 및 라우팅 20 | | ├── mary.py // 모델 로드 및 프롬프팅 21 | | ├── models.py // pydantic BaseModel 정의 22 | | └── welcome_msg.txt 23 | | 24 | └──frontend/ 25 | ├── package.json 26 | ├── public/ 27 | | ├── index.html 28 | | └── mary.svg 29 | └── src/ 30 | ├── App.js // React 애플리케이션 정의 31 | ├── App.css 32 | ├── index.js 33 | ├── index.css 34 | ├── message.js // 메시지 컴포넌트 생성 스크립트 35 | └── randomString.js // 컴포넌트 id 생성 스크립트 36 | ``` 37 | ## 환경변수 설정 38 | ### Backend 39 | 루트 디렉토리의 `.env.template`를 `.env`로 복사한 후 환경변수를 작성합니다. 작성한 환경변수는 `config/settings.py`에서 로드해 사용됩니다. 40 | 41 | |변수명|설명| 42 | |---|---| 43 | |OPENAI_API_KEY|임베딩을 생성하기 위한 OPENAI api key| 44 | |HOST|FastAPI 서버를 위한 호스트 주소(미설정시 `0.0.0.0`)| 45 | |PORT|FastAPI 서버를 위한 포트 번호(미설정시 `8000`)| 46 | |REACT_PORT|React 서버를 위한 포트 번호(미설정시 `3000`)| 47 | |DB_URL|MongoDB Atlas 연결 URI| 48 | |DB_NAME|MongoDB Atlas 데이터베이스명| 49 | |COLLECTION|사용자 메시지 데이터 저장 컬렉션명| 50 | |USERNAME|DB 유저명, `DB_URL`에 포함되어 있는 경우 작성하지 않아도 무방| 51 | |PASSWORD|DB 패스워드, `DB_URL`에 포함되어 있는 경우 작성하지 않아도 무방| 52 | |MODEL_NAME|로드할 모델 이름| 53 | |MODEL_PATH|로드할 모델이 저장되어 있는 경로| 54 | |EMBEDDING_SIZE|임베딩 차원 크기| 55 | |VECTOR_INDEX_COLLECTION|연결된 MongoDB Atlas 데이터베이스에서 Vector Search를 수행할 컬렉션명| 56 | 57 | ### Frontend 58 | `package.json`의 `proxy`에 FastAPI 서버 주소를 입력한 후 `npm install`을 실행합니다. 59 | 60 | ## DB 61 | 2개의 컬랙션을 필요로 하며, 각 컬렉션의 스키마는 다음과 같습니다. 컬렉션명은 필요에 따라 임의로 설정 가능합니다. 62 | 63 | ### user_data 64 | 메시지 텍스트를 개별 데이터로 저장합니다. 프론트엔드 렌더링 시 조회하여 사용합니다. 65 | |field|type|discription| 66 | |---|---|---| 67 | |_id|ObjectID|MongDB Object ID| 68 | |msg_text|String|개별 메시지 텍스트| 69 | |bot|Boolean|`true`일시 챗봇 메시지, `false`일시 유저 메시지| 70 | |user_id|String|세션 식별용 유저 해시, 16자리| 71 | 72 | ### message_store 73 | `### 명령어` - `### 응답` 쌍으로 메시지 텍스트와 임베딩 정보를 저장합니다. 유저가 새 메시지를 입력하면 해당 컬렉션에서 새 메시지를 기반으로 Vector Search를 수행해 가장 관련도가 높은 2개의 메시지를 검색해 메시지 생성 시 맥락으로 반영합니다. 74 | 75 | Vector Search를 사용하기 위해 사전 Vector Index 설정이 필요합니다. 76 | 77 | |field|type|discription| 78 | |---|---|---| 79 | |_id|ObjectID|MongDB Object ID| 80 | |text|String|`### 명령어` - `### 응답` 텍스트 쌍| 81 | |embedding|Array|메시지 텍스트 임베딩 벡터, 1536 차원| 82 | |user_id|String|세션 식별용 유저 해시, 16자리| 83 | |timstamp|Double|메시지 생성 시각 타임스탬프, UNIX 타임스탬프| 84 | 85 | ## API 리스트 86 | |Method|Path|Description| 87 | |---|:---:|---| 88 | |GET|/|클라이언트에 저장된 쿠키를 바탕으로 현재 세션에 해당하는 모든 대화 메시지를 `user_message`에서 조회 후 반환, 클라이언트에 저장된 세션 정보가 없을 경우 새로운 세션 정보를 설정하고 웰컴 메시지 중 하나를 임의로 반환| 89 | |POST|/|사용자 입력 메시지를 DB에 저장하고 메시지와 세션 정보를 바탕으로 응답 메시지 생성 후 반환| 90 | |DELETE|/|현재 세션에 해당하는 모든 대화 메시지를 DB에서 삭제 후 세션 정보 리셋, GET 리다이렉트| -------------------------------------------------------------------------------- /server/backend/app.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI, Cookie, Response, status 2 | from fastapi.responses import JSONResponse 3 | from fastapi.middleware.cors import CORSMiddleware 4 | from pymongo import MongoClient 5 | import uvicorn 6 | 7 | from config.settings import settings 8 | from mary import Mary 9 | from models import MsgModel, MsgHistoryModel 10 | 11 | from typing import Optional 12 | import secrets 13 | import random 14 | 15 | 16 | app = FastAPI() 17 | 18 | # React 서버 설정 19 | app.add_middleware( 20 | CORSMiddleware, 21 | allow_origins = [f'http://localhost:{settings.ServerSetting.REACT_PORT}', 22 | f'http://{settings.ServerSetting.HOST_IP}:{settings.ServerSetting.REACT_PORT}'], 23 | allow_credentials = True, 24 | allow_methods = ['GET', 'POST', 'OPTIONS', 'DELETE'], 25 | allow_headers = ['*'] 26 | ) 27 | 28 | 29 | @app.on_event('startup') 30 | async def startup(): 31 | ''' 32 | FastAPI 서버 실행시 최초로 실행 33 | - MongoDB 클라이언트 연결 34 | - 모델 로드 및 초기화 35 | ''' 36 | 37 | # MongoDB 클라이언트 연결 38 | app.mongodb_client = MongoClient(settings.DatabaseSetting.DB_URL) 39 | app.mongodb = app.mongodb_client[settings.DatabaseSetting.DB_NAME] 40 | 41 | # 개별 메시지 저장 collection 설정 42 | app.user_data = app.mongodb[settings.DatabaseSetting.COLLECTION] 43 | app.message_store = app.mongodb[settings.ModelSetting.VECTOR_INDEX_COLLECTION] 44 | 45 | # 모델 로드 및 초기화 46 | app.mary = Mary(settings.ModelSetting, app.mongodb) 47 | 48 | # 첫번째 메시지 로드 49 | app.welcome_msg = [] 50 | with open('./welcome_msg.txt', 'r', encoding='utf8') as f: 51 | app.welcome_msg = f.readlines() 52 | 53 | 54 | @app.on_event('shutdown') 55 | async def shutdown(): 56 | ''' 57 | FastAPI 서버 종료시 호출 58 | ''' 59 | 60 | # MongoDB 클라이언트 연결 종료 61 | app.mongodb_client.close() 62 | 63 | 64 | @app.get('/', response_model=MsgHistoryModel) 65 | def load_conversation(response: Response, user_id: Optional[str] = Cookie(None)): 66 | ''' 67 | 유저 및 메시지 히스토리 조회 68 | ''' 69 | if not user_id: 70 | user_id = secrets.token_hex(16) 71 | response.set_cookie(key='user_id', value=user_id, httponly=True) 72 | init_user(user_id) 73 | 74 | # user_id에 해당하는 전체 메시지 히스토리를 DB에서 조회 75 | messages = [] 76 | for doc in app.user_data.find({'user_id':user_id}): 77 | messages.append(to_dict(doc)) 78 | 79 | history = MsgHistoryModel(msg_list=messages, user_id=user_id) 80 | return history 81 | 82 | 83 | @app.post('/', response_description='add_new_message', response_model=MsgModel) 84 | def message_input(request: MsgModel) -> JSONResponse: 85 | ''' 86 | 유저 메시지 처리 87 | ''' 88 | insert_msg(request) 89 | 90 | # 유저 메시지에 대한 응답 생성 91 | bot_response = app.mary.get_response(request.msg_text, request.user_id) 92 | 93 | # 생성된 응답을 DB에 저장 94 | response_msg = MsgModel(user_id=request.user_id, msg_text=bot_response, bot=True) 95 | inserted_bot_msg = insert_msg(response_msg) 96 | 97 | # 저장된 응답을 DB에서 다시 불러와 JSONResponse로 반환 98 | bot_response = app.user_data.find_one({'_id': inserted_bot_msg}) 99 | return JSONResponse(status_code=status.HTTP_201_CREATED, content=to_dict(bot_response)) 100 | 101 | 102 | @app.delete('/') 103 | def reset_conversation(response: Response, user_id: Optional[str] = Cookie(None)) -> Response: 104 | ''' 105 | TO-DO: 현재 유저 초기화 및 DB에서 해당 유저 메시지 삭제 106 | ''' 107 | app.user_data.delete_many({'user_id':user_id}) 108 | app.message_store.delete_many({'user_id':user_id}) 109 | 110 | response.delete_cookie(key='user_id') 111 | response.status_code = status.HTTP_200_OK 112 | return response 113 | 114 | 115 | def insert_msg(msg: MsgModel): 116 | ''' 117 | DB에 메시지 insert 118 | ''' 119 | add_msg = app.user_data.insert_one(msg.dict()) 120 | return add_msg.inserted_id 121 | 122 | 123 | def init_user(user_id: str): 124 | ''' 125 | cookie에 user_id가 설정되어 있지 않은 경우 최초 메시지 생성 126 | ''' 127 | welcome_msg = MsgModel(user_id=user_id, 128 | msg_text=random.choice(app.welcome_msg), 129 | bot=True) 130 | insert_new_msg = insert_msg(welcome_msg) 131 | return insert_new_msg 132 | 133 | 134 | def to_dict(msg) -> dict: 135 | return { 136 | 'user_id' : str(msg['user_id']), 137 | 'msg_text' : str(msg['msg_text']), 138 | 'bot' : bool(msg['bot']) 139 | } 140 | 141 | 142 | if __name__=='__main__': 143 | uvicorn.run( 144 | 'app:app', 145 | host=settings.ServerSetting.HOST, 146 | port=settings.ServerSetting.PORT, 147 | timeout_keep_alive=50, 148 | reload=True 149 | ) -------------------------------------------------------------------------------- /server/backend/config/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from dotenv import load_dotenv 4 | from pydantic import BaseSettings, Field 5 | 6 | load_dotenv('../../.env') 7 | 8 | 9 | class CommonSettings(BaseSettings): 10 | """React 내부 설정.""" 11 | APP_NAME: str = 'test' 12 | DEBUG_MODE: bool = False 13 | 14 | 15 | class ServerSettings(BaseSettings): 16 | """서버 연결을 위한 설정.""" 17 | HOST: str = Field(default_factory=lambda: os.getenv('HOST', '0.0.0.0')) 18 | PORT: int = Field(default_factory=lambda: os.getenv('PORT', 8000)) 19 | REACT_PORT: int = Field(default_factory=lambda: os.getenv('REACT_PORT', 3000)) 20 | HOST_IP: str = Field(default_factory=lambda: os.getenv('HOST_IP', '0.0.0.0')) 21 | 22 | 23 | class DatabaseSettings(BaseSettings): 24 | """데이터베이스 설정.""" 25 | DB_URL: str = Field(default_factory=lambda: os.getenv('DB_URL', '')) 26 | DB_NAME: str = Field(default_factory=lambda: os.getenv('DB_NAME', 'test')) 27 | COLLECTION: str = Field(default_factory=lambda: os.getenv('COLLECTION', 'user_data')) 28 | USERNAME: str = Field(default_factory=lambda: os.getenv('USERNAME', '')) 29 | PASSWORD: str = Field(default_factory=lambda: os.getenv('PASSWORD', '')) 30 | 31 | 32 | class ModelSettings(BaseSettings): 33 | """모델과 관련된 키값 등의 설정.""" 34 | MODEL_NAME: str = Field(default_factory=lambda: os.getenv('MODEL_NAME', '')) 35 | MODEL_PATH: str = Field(default_factory=lambda: os.getenv('MODEL_PATH', '')) 36 | EMBEDDING_SIZE: int = Field(default_factory=lambda: os.getenv('EMBEDDING_SIZE', 1536)) 37 | VECTOR_INDEX_COLLECTION: str = Field(default_factory=lambda: os.getenv('VECTOR_INDEX_COLLECTION', 'message_store')) 38 | OPENAI_API_KEY: str = Field(default_factory=lambda: os.getenv('OPENAI_API_KEY', '')) 39 | 40 | 41 | class Settings: 42 | """여러 설정을 모아두는 역할.""" 43 | CommonSetting: CommonSettings = CommonSettings() 44 | ServerSetting: ServerSettings = ServerSettings() 45 | DatabaseSetting: DatabaseSettings = DatabaseSettings() 46 | ModelSetting: ModelSettings = ModelSettings() 47 | 48 | 49 | settings = Settings() 50 | -------------------------------------------------------------------------------- /server/backend/mary.py: -------------------------------------------------------------------------------- 1 | from dotenv import load_dotenv 2 | from pymongo.database import Database 3 | 4 | import torch 5 | from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline 6 | 7 | from langchain.llms import HuggingFacePipeline 8 | from langchain.embeddings.openai import OpenAIEmbeddings 9 | from langchain.chains import ConversationChain 10 | from langchain.prompts import PromptTemplate 11 | from langchain.vectorstores import MongoDBAtlasVectorSearch 12 | from langchain.memory import VectorStoreRetrieverMemory 13 | from langchain.schema.document import Document 14 | 15 | from config.settings import ModelSettings 16 | 17 | from typing import Optional, Dict, Any, List 18 | from pathlib import Path 19 | import time 20 | 21 | class CustomVectorStoreRetrieverMemory(VectorStoreRetrieverMemory): 22 | 23 | metadata: Optional[Dict[str, Any]] = None, 24 | 25 | def _form_documents( 26 | self, inputs: Dict[str, Any], outputs: Dict[str, str] 27 | ) -> List[Document]: 28 | """Format context from this conversation to buffer.""" 29 | # Each document should only include the current turn, not the chat history 30 | filtered_inputs = {k: v for k, v in inputs.items() if k != self.memory_key} 31 | texts = [ 32 | f"{k}: {v}" 33 | for k, v in list(filtered_inputs.items()) + list(outputs.items()) 34 | ] 35 | page_content = "\n".join(texts) 36 | return [Document(page_content=page_content, metadata=self.metadata)] 37 | 38 | 39 | def build_pre_filter(user_id: str, timestamp: float) -> dict: 40 | return { 41 | 'compound': { 42 | 'filter': { 43 | 'text': { 44 | 'path': 'user_id', 45 | 'query': user_id 46 | } 47 | }, 48 | 'should': { 49 | 'near': { 50 | 'origin': timestamp, 51 | 'path': 'timestamp', 52 | 'pivot': 10000000 53 | } 54 | } 55 | } 56 | } 57 | 58 | def build_post_filter_pipeline(use_sort: bool = True) -> Optional[List]: 59 | return [{'$sort': {'timestamp': 1}}] if use_sort else None 60 | 61 | 62 | class Mary: 63 | def __init__(self, settings:ModelSettings, mongodb_client:Database): 64 | 65 | # 모델 관련 설정 66 | model_full_path = str(Path(settings.MODEL_PATH).joinpath(settings.MODEL_NAME)) 67 | 68 | # 모델 로드 및 초기화 코드 69 | load_dotenv() 70 | torch.cuda.empty_cache() 71 | 72 | model = AutoModelForCausalLM.from_pretrained( 73 | model_full_path, 74 | torch_dtype=torch.float16, 75 | low_cpu_mem_usage=True, 76 | ).to(device=f"cuda", non_blocking=True) 77 | 78 | tokenizer = AutoTokenizer.from_pretrained(model_full_path) 79 | model.eval() 80 | model.config.use_cache = True 81 | 82 | pipe = pipeline( 83 | 'text-generation', 84 | model = model, 85 | tokenizer = tokenizer, 86 | device=0, 87 | min_new_tokens=10, 88 | max_new_tokens=128, 89 | early_stopping=True, 90 | do_sample=True, 91 | eos_token_id=2, 92 | repetition_penalty=1.05, 93 | temperature=0.9, 94 | top_k=20, 95 | top_p=0.95, 96 | ) 97 | 98 | self.local_llm = HuggingFacePipeline(pipeline=pipe) 99 | 100 | embedding_fn = OpenAIEmbeddings( 101 | openai_api_key=settings.OPENAI_API_KEY 102 | ) 103 | 104 | collection = mongodb_client[settings.VECTOR_INDEX_COLLECTION] 105 | self.vectorstore = MongoDBAtlasVectorSearch( 106 | collection, embedding_fn 107 | ) 108 | 109 | template = """이전 대화와 현재 대화의 명령어를 참고하여 상황에 공감하고 친절한 응답을 생성해주세요. 응답 마지막에는 지금까지의 내용과 관련된 질문을 해주세요.\n\n[이전 대화]\n{history}\n\n[현재 대화]\n### 명령어:\n{### 명령어}\n\n### 응답:\n""" 110 | 111 | self.prompt = PromptTemplate( 112 | input_variables=["history", "### 명령어"], template=template 113 | ) 114 | 115 | 116 | def get_response(self, question:str, user_id:str) -> str: 117 | input_dict = {'### 명령어': question} 118 | timestamp = float(time.time()) 119 | 120 | res = ConversationChain( 121 | llm=self.local_llm, 122 | prompt=self.prompt, 123 | memory=CustomVectorStoreRetrieverMemory( 124 | retriever=self.vectorstore.as_retriever(search_kwargs={ 125 | 'k':2, 126 | 'pre_filter': build_pre_filter(user_id, timestamp), 127 | 'post_filter_pipeline': build_post_filter_pipeline() 128 | }), 129 | metadata={'user_id': user_id, 'timestamp': timestamp}, 130 | ), 131 | input_key='### 명령어', 132 | output_key='### 응답', 133 | verbose=True 134 | ).predict(**input_dict) 135 | 136 | return res -------------------------------------------------------------------------------- /server/backend/models.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | from typing import Optional 3 | 4 | class MsgModel(BaseModel): 5 | user_id: str 6 | msg_text: str 7 | bot: bool 8 | 9 | class Config: 10 | schema_extra = { 11 | "example": { 12 | "user_id": "test", 13 | "msg_text": "hello, world!", 14 | "bot": True, 15 | } 16 | } 17 | 18 | class MsgHistoryModel(BaseModel): 19 | msg_list: list 20 | user_id: str -------------------------------------------------------------------------------- /server/backend/welcome_msg.txt: -------------------------------------------------------------------------------- 1 | 안녕하세요!🥰 오늘은 어떤 하루를 보내고 있나요? 2 | 만나서 반가워요! 저는 마리라고 합니다. 어떤 이야기를 나눠볼까요? 3 | 오늘 하루는 어떠셨나요? 😄 4 | 안녕하세요! 저는 마리에요. 고민이 있으신가요? 😄 5 | 안녕하세요, 저는 마리입니다. 오늘은 어떤 이야기를 나누고 싶으신가요? 🌞 6 | 안녕하세요! 저는 여러분과 대화를 나누는데 도움을 주는 AI, 마리입니다. 오늘은 어떤 흥미로운 일이 있으셨나요? 🎈 7 | 반가워요! 저는 마리라고 합니다. 오늘은 어떤 주제로 대화를 나누고 싶으신가요? 📚 -------------------------------------------------------------------------------- /server/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /server/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "proxy": "", 6 | "dependencies": { 7 | "@fortawesome/fontawesome-svg-core": "^6.4.0", 8 | "@fortawesome/free-brands-svg-icons": "^6.4.0", 9 | "@fortawesome/free-regular-svg-icons": "^6.4.0", 10 | "@fortawesome/free-solid-svg-icons": "^6.4.0", 11 | "@fortawesome/react-fontawesome": "^0.2.0", 12 | "@testing-library/jest-dom": "^5.17.0", 13 | "@testing-library/react": "^13.4.0", 14 | "@testing-library/user-event": "^13.5.0", 15 | "axios": "^1.4.0", 16 | "react": "^18.2.0", 17 | "react-dom": "^18.2.0", 18 | "react-scripts": "5.0.1", 19 | "web-vitals": "^2.1.4" 20 | }, 21 | "scripts": { 22 | "start": "react-scripts start", 23 | "build": "react-scripts build", 24 | "test": "react-scripts test", 25 | "eject": "react-scripts eject" 26 | }, 27 | "eslintConfig": { 28 | "extends": [ 29 | "react-app", 30 | "react-app/jest" 31 | ] 32 | }, 33 | "browserslist": { 34 | "production": [ 35 | ">0.2%", 36 | "not dead", 37 | "not op_mini all" 38 | ], 39 | "development": [ 40 | "last 1 chrome version", 41 | "last 1 firefox version", 42 | "last 1 safari version" 43 | ] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /server/frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boostcampaitech5/level3_nlp_finalproject-nlp-12/46c8b4ccbc9ca85087ea0c7aa7fdf3f9d8fad015/server/frontend/public/favicon.ico -------------------------------------------------------------------------------- /server/frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 16 | 17 | 26 | MyMary 27 | 28 | 29 | 30 |
31 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /server/frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } -------------------------------------------------------------------------------- /server/frontend/public/mary.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 22 | 23 | -------------------------------------------------------------------------------- /server/frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /server/frontend/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-header { 6 | background-color: rgb(227, 235, 210); 7 | background-size: 100%; 8 | background-repeat: no-repeat; 9 | background-attachment: fixed; 10 | display: flex; 11 | flex-direction: column; 12 | align-items: center; 13 | justify-content: center; 14 | font-size: calc(9px + 2vmin); 15 | color: black; 16 | } 17 | 18 | .App-link { 19 | color: #61dafb; 20 | } 21 | 22 | .bottom { 23 | z-index: 1; 24 | position: fixed; 25 | /* display: flex; */ 26 | width: 100vw; 27 | height: 20vh; 28 | align-items: center; 29 | justify-content: center; 30 | } 31 | 32 | .header { 33 | z-index: 1; 34 | position: fixed; 35 | display: flex; 36 | width: 100vw; 37 | align-items: center; 38 | justify-content: center; 39 | background-color: rgba(255, 255, 255, 0.2); 40 | backdrop-filter: blur(10px); 41 | } 42 | 43 | .footer { 44 | background-color: aquamarine; 45 | text-align: center; 46 | margin-top: 30vh; 47 | font-size: 0.3em; 48 | } 49 | 50 | .backdrop { 51 | backdrop-filter: blur(10px); 52 | background-color: rgba(255, 255, 255, 0.2); 53 | box-shadow: 2px 7px 15px 8px rgba(0,0,0,0.1); 54 | border-radius: 15px; 55 | } 56 | 57 | .Eraser { 58 | transition: opacity .5s; 59 | opacity: 30%; 60 | } 61 | 62 | .Eraser:hover { 63 | opacity: 100%; 64 | } 65 | 66 | /* tooltip */ 67 | .tooltip { 68 | position: relative; 69 | display: inline-block; 70 | } 71 | 72 | .tooltiptext { 73 | font-size: 0.3em; 74 | } 75 | 76 | .tooltip .tooltiptext { 77 | visibility: hidden; 78 | width: 10em; 79 | background-color: black; 80 | color: #fff; 81 | text-align: center; 82 | border-radius: 5px; 83 | padding: 5px 0; 84 | position: absolute; 85 | z-index: 1; 86 | bottom: 100%; 87 | left: 50%; 88 | margin-left: -5em; 89 | } 90 | 91 | .tooltip .tooltiptext::after { 92 | content: ""; 93 | position: absolute; 94 | top: 100%; 95 | left: 50%; 96 | margin-left: -5px; 97 | border-width: 5px; 98 | border-style: solid; 99 | border-color: black transparent transparent transparent; 100 | } 101 | 102 | .tooltip:hover .tooltiptext { 103 | visibility: visible; 104 | } -------------------------------------------------------------------------------- /server/frontend/src/App.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import React, { useEffect, useState, useRef } from 'react'; 3 | import packageJson from '../package.json'; 4 | 5 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 6 | import { faArrowTurnDown, faEraser, faEllipsis } from '@fortawesome/free-solid-svg-icons'; 7 | 8 | import './App.css'; 9 | import Message from './message'; 10 | 11 | axios.defaults.baseURL = packageJson.proxy; 12 | axios.defaults.withCredentials = true; 13 | 14 | function App() { 15 | 16 | const [msg_text, setMsgText ] = useState(''); // input박스에 입력된 유저 메시지 17 | const [user_id , setUserID ] = useState(''); // cookie에 저장된 유저 해시(최초 접속시 비어있음) 18 | const [msg_list, setMsgList ] = useState(null); // 전체 메시지 히스토리 19 | const [showing , setShowing ] = useState(false); // 페이지 로딩중 여부 20 | const [generating, setGenerating] = useState(false); // 메시지 생성중 스피너 21 | 22 | const focusRef = useRef(null); 23 | 24 | const getMsg = async () => { 25 | await axios.get( 26 | '/' 27 | ).then((response) =>{ 28 | console.log(response); 29 | setMsgList(response.data.msg_list); 30 | setUserID(response.data.user_id); 31 | setShowing(true); 32 | }).catch((error) => { 33 | console.error('error', error); 34 | setMsgList('error'); 35 | }); 36 | } 37 | 38 | const postMsg = async () => { 39 | if (msg_text) { 40 | setMsgList(prev => [...prev, {msg_text: msg_text, bot: false}]); 41 | var current_msg_text = msg_text; 42 | setMsgText(''); 43 | setGenerating(true); 44 | await axios.post( 45 | '/', // path 46 | { // data 47 | user_id: user_id, 48 | msg_text: current_msg_text, 49 | bot: false 50 | }, 51 | { // config 52 | headers: { 53 | "Content-Type": "application/json", 54 | Accept: "application/json" 55 | }, 56 | } 57 | ).then((response) => { 58 | setGenerating(false); 59 | setMsgList(prev => [...prev, response.data]); 60 | }).catch((error) => { 61 | console.log(error); 62 | }); 63 | } 64 | }; 65 | 66 | const onInputValChange = (e) => { 67 | setMsgText(e.target.value); 68 | }; 69 | 70 | const onInputKey = async (e) => { 71 | if (e.code==='Enter' && msg_text !== ''){ 72 | await postMsg(); 73 | } 74 | } 75 | 76 | const deleteMsg = () => { // reset버튼을 위한 callback 함수 프로토타입 77 | setShowing(false); 78 | axios.delete( 79 | '/' 80 | ).then((response) =>{ 81 | console.log(response); 82 | setMsgText(''); 83 | getMsg(); 84 | } 85 | ).catch((error) => { 86 | console.log(error); 87 | }); 88 | }; 89 | 90 | useEffect(() => { 91 | getMsg(); 92 | }, []); 93 | 94 | useEffect(() => { 95 | focusRef.current?.scrollIntoView({ behavior: 'smooth' , block: 'center' }); 96 | }, [msg_list, showing, generating]); 97 | 98 | if (showing){ 99 | return ( 100 |
101 |
102 |
103 | 104 |
105 |
106 |
107 |
108 | {msg_list.map(msg =>( 109 | 110 | ))} 111 | { generating === true && 112 |
113 | 114 |
115 | } 116 |
117 |
118 |