<h1 align="center">
BoostCamp AI Tech - [NLP] ODQA
</h1>
###### tags: `MRC` `ODQA`
<p align="center">
<a href="https://boostcamp.connect.or.kr/program_ai.html">
<img src="https://img.shields.io/badge/BoostCamp-P--Stage-bronze?logo=Naver&logoColor=white"/></a>  
</a>
<a href="https://github.com/KLUE-benchmark/KLUE">
<img src="https://img.shields.io/badge/Dataset-KLUE--MRC-critical?logo=GitHub&logoColor=white"/></a>  
</a>
<a href="https://huggingface.co/klue/roberta-large">
<img src="https://img.shields.io/badge/KLUE-roberta--large-yellow"/></a>  
</a>
<a href="https://stackoverflow.com/questions/52229059/em-score-in-squad-challenge">
<img src="https://img.shields.io/badge/Score (EM)-64.170-bronze"/></a>  
</a>
<a href="https://stackoverflow.com/questions/52229059/em-score-in-squad-challenge">
<img src="https://img.shields.io/badge/Score (F1 score)-74.690
-bronze"/></a>  
</a>
</p>
## Table of Contents
1. [Project Overview](#1.-Project-Overview)
2. [Usage](#2.-Usage)
- [Train](#Train)
- [Eval](#Evaluation)
- [Inference](#Inference)
- [Inference through Elastic Search](#Inference-through-Elastic-Search)
3. [Experiment](#3.-Experiment)
4. [Result](#4.-Result)
5. [Things to know](#5.-Things-to-know)
6. [Contributors](#6.-Contributors)
<br/>
## 1. Project Overview
#### Goals
- Retriever Task와 Reader Task를 구성하고 통합하여, 질문을 던졌을 때 답변을 해주는 ODQA 시스템 개발
- Retriever
- 방대한 Open Domain Dataset에서 질의에 알맞은 지문을 찾아오는 Task
- Machine Reading Comprehension(MRC)
- 지문이 주어진 상황에서 질의에 대해 응답하는 기계 독해 Task
- Open-Domain Question Answering(ODQA)
- Retriever 와 MRC Task를 결합한 시스템
#### Dataset
아래는 제공하는 데이터셋의 분포를 보여준다.

데이터셋은 편의성을 위해 Huggingface 에서 제공하는 datasets를 이용하여 pyarrow 형식의 데이터로 구성
#### Evaluation Metrics
- EM(Exact Match)
- 모델의 예측과, 실제 답이 정확하게 일치할 때만 점수가 주어진다.
- 답이 하나가 아닌 경우는 하나라도 일치하면 정답으로 간주한다.

- F1 score

#### Directory Structure
```python
.
mrc-level2-nlp-06
|-- arguments.py -> 학습 관련 설정
|-- dense_retrieval.py -> retriever 모델 학습
|-- inference.py
|-- elastic_inference.py
|-- install
| `-- install_requirements.sh
|-- retrieval.py
|-- train.py -> reader 모델 학습
|-- trainer_qa.py
|-- utils_qa.py
|-- utils
| |-- init_wandb.py
| |-- postprocess.py
| |-- preprocess.py
| |-- trainer_qa.py
| |-- utils_dpr.py
| `-- utils_qa.py
`-- wandb_arguments.py -> wandb 설정
```
data에 대한 argument 는 `arguments.py` 의 `DataTrainingArguments` 에서 확인 가능하다.
<br/>
## 2. Usage
### Install Requirements
```
bash install_requirements.sh
```
### Train
- Custom Hyperparameter : `arguments.py` 참고
- Using roberta Model : tokenizer 사용시 아래 함수의 옵션을 수정
- tokenizer의 return_token_type_ids=False로 설정
```
# Retriever 학습 예시 # 내부에서 변경
python dense_retriver.py
```
```
# Reader 학습 예시 (train_dataset 사용)
python train.py --output_dir ./models/train_dataset --do_train
```
### Evaluation
- MRC 모델의 평가 : `--do_eval`사용
- 위의 예시에 `--do_eval` 을 추가로 입력해서 훈련 및 평가를 동시에 진행할 수도 있다.
```
# mrc 모델 평가 (train_dataset 사용)
python train.py --output_dir ./outputs/train_dataset --model_name_or_path ./models/train_dataset/ --do_eval
```
### Inference
retrieval 과 mrc 모델의 학습이 완료되면 `inference.py` 를 이용해 odqa 를 진행한다.
* 학습한 모델의 test_dataset에 대한 결과를 제출하기 위해선 추론(`--do_predict`)만 진행한다.
* 학습한 모델이 train_dataset 대해서 ODQA 성능이 어떻게 나오는지 알고 싶다면 평가(--do_eval)를 진행한다.
```
# ODQA 실행 (test_dataset 사용)
# wandb 가 로그인 되어있다면 자동으로 결과가 wandb 에 저장되고 아니면 단순히 출력된다.
python inference.py --output_dir ./outputs/test_dataset/ --dataset_name ../data/test_dataset/ --model_name_or_path ./models/train_dataset/ --do_predict
```
- DataTrainingArguments
```
dpr:bool - 학습된 dpr encoder와 bm25를 정한 비율로 결합해서 inference
dpr_negative:bool - dense_retriver.py에 있는 코드를 통해 학습된 모델로 inference
```
### Inference through Elastic Search
* 학습한 모델의 test_dataset에 대한 결과를 제출하기 위해선 추론(`--do_predict`)만 진행하면 됩니다. 위와 과정은 동일하고 실행 파일만 변경하면 됩니다. (elastic search를 통한 retrieval 구성이 포함되어 있는 파일입니다.)
* elastic search 사용을 위해 현재 버전으로 저장되어 있는 폴더 내에 user_dic을 만들고 안에 stopwords.txt를 넣어야 한다.
```
# ODQA 실행 (test_dataset 사용)
python elastic_inference.py --output_dir ./outputs/test_dataset/ --dataset_name ../data/test_dataset/ --model_name_or_path ./models/train_dataset/ --do_predict
```
- Data Training Arguments
```
elastic_score: bool = field(
default = True,
metadata={"help": "whether to score to top_k of each query"}
)
# elastic search로 context만 뽑아내고 싶다면 False
# score도 함께 뽑아내고 싶다면 True
```
### How to submit
`inference.py` 파일을 위 예시처럼 `--do_predict` 으로 실행하면 `--output_dir` 위치에 `predictions.json` 이라는 파일이 생성되고 해당 파일을 제출한다.
<br/>
## 3. Experiment
#### Data Preprocessing
- 동일한 문장의 데이터가 42개, 동일하면서도 라벨이 달랐던 데이터가 5개 존재했고 둘 중 올바른 라벨로 수정하였다.
- `preprocess_wiki_documents(contexts)`
- 들어온 contexts 중에 한글이 포함되지 않거나, 한글이 포함되었지만 code로 구성된 context, 그리고 특정 글자를 포함하는 contexts를 제거하였고, 남은 contexts 에서 특정 문자, 또는 특정 문자를 포함하는 문장을 제거하였다.
- `use_wiki_preprocessing=True` 인 경우 해당 함수를 사용할 수 있다.
#### Retriver
- DPR retriever
- TF-IDF , BM25 기반 Negative Sample: wiki-context와 mrc-context를 내적하는 방법으로 접근했다.
- In-Batch: query와 positive sentence로 이루어진 미니 배치 내부에서 다른 example들 사이에서 내적을 통한 유사도에서 가장 높은 유사도에 해당하는 example으로 prediction한다.
- 단일 DPR을 활용하기 보단 BM25의 점수를 선형결합해서 단일 모델 중 가증 큰 성능을 보였다.
- BM25 단일모델 EM : 55 -> BM25+DPR EM : 61.240
- BM25 retriever
- Okapi bm25 github의 라이브러리를 상속해서 구현했다.
- bm25, bm25L, bm25Plus, 다양한 토크나이저 실험 중 성능이 가장 좋은 bm25와 klue/bert-base 토크나이저를 사용하여 비교했다.
- retrieval accuracy: top 1: 0.5875, top 10: 0.9041, top 20: 0.9208
- Elasticsearch retriever
- Okapi bm25의 성능이 task에서 높은 것을 파악하고 빠르게 동작시키기 위해 사용했다.
- python 내에서 돌아가도록 Elastic search의 index setting을 구성했다.
- stopwords filter와 pororo 라이브러리 기반의 tokenizing을 통해 context를 분석했다.
#### Reader
- KLUE/Roberta-large
- 다른 기학습 가중치를 활용해보았으나 KLUE 데이터셋을 기반으로 전이 학습된 모델이 평균적으로 더 높은 성능을 보였다.
<br/>
## 4. Result
#### Retriver 결과

#### MRC 결과(train)
- roberta-large, bert-base, xlm-roberta-large

#### ODQA 결과(LB 점수)
- Ensemble
- 최고성능 : Hard Voting

- Elastic : Hard Voting

- 성능이 향상된 방법
- 단일 모델 최고성능 : Elastic Search

- Preprocess


- BM25-Plus topk20

- BM25 topk20

- Baseline
- topk20 TF-IDF

- topk10 TF-IDF

<br/>
## 5. Things to know
1. `train.py`에서 sparse embedding 을 훈련하고 저장하는 과정은 시간이 오래 걸리지 않아 따로 argument 의 default 가 True로 설정되어 있다.
실행 후 sparse_embedding.bin 과 tfidfv.bin 이 저장이 된다.
**만약 sparse retrieval 관련 코드를 수정 시 존재하는 파일이 load되지 않도록 두 파일을 지우고 다시 실행한다**
2. 모델의 경우 `--overwrite_cache` 를 추가하지 않으면 같은 폴더에 저장되지 않는다.
3. ./outputs/ 폴더 또한 `--overwrite_output_dir` 을 추가하지 않으면 같은 폴더에 저장되지 않는다.
<br/>
## 6. Contributors
나요한_T2073 : https://github.com/nudago
백재형_T2102 : https://github.com/BaekTree
송민재_T2116 : https://github.com/Jjackson-dev
이호영_T2177 : https://github.com/hylee-250
정찬미_T2207 : https://github.com/ChanMiJung
한진_T2237 : https://github.com/wlsl8135
홍석진_T2243 : https://github.com/HongCu