MRC Base_line 코드 이해(1)

SeongGyun Hong·2024년 10월 13일

0. Preview

현재 파일구조는 이하와 같다.

.
|-- code
|   |-- README.md
|   |-- arguments.py
|   |-- assets
|   |   |-- dataset.png
|   |   |-- mrc.png
|   |   `-- odqa.png
|   |-- inference.py
|   |-- notebooks
|   |   `-- data_EDA.ipynb
|   |-- requirements.txt
|   |-- retrieval.py
|   |-- train.py
|   |-- trainer_qa.py
|   `-- utils_qa.py
`-- data
    |-- test_dataset
    |   |-- dataset_dict.json
    |   `-- validation
    |       |-- dataset.arrow
    |       |-- dataset_info.json
    |       `-- state.json
    |-- train_dataset
    |   |-- dataset_dict.json
    |   |-- train
    |   |   |-- dataset.arrow
    |   |   |-- dataset_info.json
    |   |   `-- state.json
    |   `-- validation
    |       |-- dataset.arrow
    |       |-- dataset_info.json
    |       `-- state.json
    `-- wikipedia_documents.json
  

위 구조에서 실제 입력하는 CLI 명령어는 다음 세가지로 제공된 바

  • 학습의 경우
    python train.py --output_dir ./models/train_dataset --do_train
  • 평가의 경우
    python train.py --output_dir ./outputs/train_dataset --model_name_or_path ./models/train_dataset/ --do_eval
  • 실제 inference하는 경우
    python inference.py --output_dir ./outputs/test_dataset/ --dataset_name ../data/test_dataset/ --model_name_or_path ./models/train_dataset/ --do_predict

총 두 지점을 시작점으로(train.py, inference.py) 아래에서는 파일구조 및 코드를 이해하고 팀원이 미리 개편한 구조를 토대로 좀더 효율성이 높은 구조를 찾아가보도록 하겠다.

1. Module화된 객체들에 대한 이해

1.1 arguments.py

기본적인 설정값들이 저장된 파일이다. 특별한 역할을 하는 함수는 들어있지 않다.

class ModelArguments

인자설명기본값
model_name_or_path학습시킬 모델의 이름 또는 경로"klue/bert-base"
config_name모델 config의 이름 또는 경로None
tokenizer_name사용할 토크나이저의 이름 또는 경로None

class DataTrainingArguments

인자설명기본값
dataset_name사용할 데이터셋의 이름"../data/train_dataset"
overwrite_cache캐시된 데이터셋 덮어쓰기 여부False
preprocessing_num_workers전처리에 사용할 프로세스 수None
max_seq_length최대 입력 시퀀스 길이384
pad_to_max_length모든 샘플을 max_seq_length에 맞춰 패딩False
doc_stride문서 청크 간 겹치는 정도128
max_answer_length생성 가능한 답변의 최대 길이30
eval_retrievalSparse embedding을 사용하는 Passage Retrieval를 사용할지 여부True
num_clustersFaiss 인덱싱용 클러스터 수64
top_k_retrieval검색할 상위 패시지 수10
use_faissFaiss 사용 여부False

1.2 inference.py

CLI 명령어
python inference.py --output_dir ./outputs/test_dataset/ --dataset_name ../data/test_dataset/ --model_name_or_path ./models/train_dataset/ --do_predict
의 대상이 되는 파일로 이하를 통해 구체적으로 살핀다.

1.2.1 def main()

inference.py가 직접 실행될때


if __name__ == "__main__":
    main()

위 코드를 통해 main() 함수가 호출되며 실행된다.
구체적으로 main()은 이하와 같은 역할을 한다.

  1. 먼저 Parser를 받아온다. 앞서 언급한 ModelArguments, DataTrainingArguments에서 받은 설정 값과 HuggingFacetransformers라이브러리를 통해 받아오는 TrainingArguments의 설정값들을 받아온다.

  2. 이후 각각의 설정값들에 대하여 model_args, data_args, training_args로 저장하고
    training_args에 있는 do_trainTrue로 설정해준다.

  3. 다음으로 datasets라는 변수에 DataTrainingArguments로부터 비롯된 사용할 데이터셋의 이름을 저장하고

  4. 난수를 고정한 다음 config, tokenizer, model이라는 변수들에 각각의 변수 이름에 걸맞게 ModelArguments에서 가져온 값들을 실행시킨 값을 저장해둔다.

  5. 그리고 만약 DataTrainingArguments에서 eval_retrieval 값이 True라면 (Sparse Embedding을 사용하는 Passage retrieval를 활용하지 여부)
    run_sparse_retrieval()함수를 실행한다.

  6. 또한 만약 do_eval값이 True이거나 do_predict값이 True인 경우, run_mrc()함수를 실행한다.


1.2.2 def run_sparse_retrieval()

주어진 데이터셋에 대해 스파스 검색을 수행하는 역할을 하는 함수이며, 주요 기능은 다음과 같다.

  1. 검색기 초기화:

    • retriever 변수에 SparseRetrieval 클래스 초기화
    • 스파스 임베딩 생성
  2. 검색 수행:

    • data_args.use_faiss 값에 따라 FAISS 인덱스 사용 여부 결정
    • FAISS 사용 시:
      • num_clusterstop_k_retrieval 값으로 검색 설정
      • retrieve_faiss() 메서드로 검색 수행
    • FAISS 미사용 시:
      • retrieve() 메서드로 검색 수행
    • 검색 결과는 df 변수에 데이터프레임 형태로 저장
  3. 데이터셋 구성:

    • training_args.do_predict 또는 training_args.do_eval 값에 따라 다른 특성(Features) 정의
    • 예측 모드: ID, 질문, 컨텍스트로 구성
    • 평가 모드: ID, 질문, 컨텍스트, 답변으로 구성

데이터셋의 구성은 Hugging Face의 datasets 라이브러리에서 제공하는 Features를 사용하고 있다. Features는 데이터셋의 내부 구조를 정의하는 데 사용되며, 구체적으로는 이하와 같다.

Features의 역할:
데이터셋의 각 열(column)의 이름과 데이터 타입을 지정한여, 데이터의 구조와 형식을 명확히 정의해 일관성 있는 처리를 가능하게 한다.

구성 방식:
Featuresdict[column_name, column_type] 형태로 구성된다.

각 열의 이름을 key로, 해당 열의 데이터 타입을 value로 가진다.

데이터 타입 예시:

Value: 기본적인 데이터 타입(string, int32 등)을 지정할 때 사용한다.
Sequence: 리스트 형태의 데이터를 표현할 때 사용한다.
ClassLabel: 분류 문제에서 레이블을 정의할 때 사용한다.

코드에서의 활용:
training_args.do_predictTrue일 경우, 예측용 데이터셋 구조를 정의하고
training_args.do_evalTrue일 경우, 평가용 데이터셋 구조를 정의한다.

  1. 결과 반환:
    • 검색 결과를 바탕으로 datasets 변수에 새로운 DatasetDict 생성하여 반환

결론적으로, 이 run_sparse_retrieval 함수는 효율적인 정보 검색을 통해 모델의 성능을 향상시키는 데 중요한 역할을 한다.
예를 들어, "대한민국의 수도는?"이라는 질문에 대해 관련성 높은 문단들을 검색하고, 이를 새로운 데이터셋으로 구성하여 질문 답변 모델의 입력으로 사용할 수 있게 한다.


1.2.3 run_mrc()

Machine Reading Comprehension (MRC) 모델을 실행하고 평가하는 핵심 기능을 수행한다. 상세한 단계는 다음과 같다.

  1. 데이터셋 구조 분석:

    • datasets["validation"]의 컬럼 이름을 확인
    • 질문, 컨텍스트, 답변 컬럼 자동 식별
      예: "question", "context", "answers" 컬럼 식별
  2. 토크나이저 설정:

    • tokenizer.padding_side를 확인하여 패딩 방식 결정
      예: 오른쪽 패딩 → (질문 | 컨텍스트), 왼쪽 패딩 → (컨텍스트 | 질문)
  3. 오류 확인 및 최대 시퀀스 길이 설정:

    • check_no_error 함수로 데이터와 설정 오류 확인
    • max_seq_length 설정
  4. 검증 데이터 전처리 함수 정의:

    • prepare_validation_features 함수 정의
    • 예제 토큰화, 오버플로우 토큰 처리, 오프셋 매핑 생성
      예: "대한민국의 수도는 어디인가요?" 질문과 관련 컨텍스트 처리
  5. 검증 데이터셋 변환:

    • eval_dataset.map() 메서드로 전체 데이터셋에 전처리 적용
    • num_proc 인자로 병렬 처리 설정
  6. 데이터 콜레이터 설정:

    • DataCollatorWithPadding 초기화로 동적 패딩 수행
    • FP16 학습 시 패딩을 8의 배수로 설정 가능

1. FP16 (Half Precision)

  • 16비트 부동 소수점 형식
    • 1비트: 부호
    • 5비트: 지수
    • 10비트: 가수

2. FP16의 장점

  • 메모리 사용량 감소: FP32 대비 50% 절감
  • 학습 및 추론 시간 단축: 메모리 대역폭 사용 감소로 인한 속도 향상
  • 확장성 개선: 더 큰 모델 또는 더 큰 미니배치 학습 가능

3. DataCollatorWithPadding과 FP16

  • 동적 패딩 수행
  • FP16 학습 시 패딩을 8의 배수로 설정
    DataCollatorWithPadding(tokenizer, pad_to_multiple_of=8 if training_args.fp16 else None)

4. 8의 배수로 패딩하는 이유
NVIDIA GPU의 Tensor Core 최적화
8의 배수로 정렬된 데이터 → Tensor Core에서 효율적 처리

5. 주의사항
정밀도 낮음 → 일부 모델에서 정확도 손실 가능
해결책: 혼합 정밀도 학습(Mixed Precision Training) 고려

  1. 후처리 함수 정의:

    • post_processing_function 정의로 모델 원시 출력 처리
    • 예측된 시작/끝 로짓을 실제 텍스트 답변으로 변환
      예: 로짓 값 → "서울" 답변 텍스트 추출
  2. 평가 메트릭 설정:

    • SQuAD 메트릭 로드 및 compute_metrics 함수 정의
    • 정확도, F1 점수 등 계산
  3. QA 트레이너 초기화:

    • QuestionAnsweringTrainer로 모델, 데이터셋, 전처리 및 후처리 함수 설정
  4. 평가 또는 예측 실행:

    • do_predict 모드:
      • trainer.predict() 호출로 예측 수행
      • 결과를 predictions.json 파일로 저장
    • do_eval 모드:
      • trainer.evaluate() 호출로 모델 평가
      • 계산된 메트릭(EM 스코어, F1 스코어 등)을 로그로 저장

결론적으로, run_mrc()는 MRC 모델의 전체 평가 및 예측 파이프라인을 관리하며, 복잡한 데이터 처리 과정을 자동화하여 효율적인 모델 평가를 가능하게 한다.


1.3 retrieval.py

문서 검색(retrieval) 기능을 구현한 핵심 모듈이다.

1.3.1 timer()

이 코드는 Python의 컨텍스트 매니저를 사용하여 실행 시간을 측정해주는 유틸리티 함수이다.

  1. @contextmanager 데코레이터:

    • 이 데코레이터는 함수를 컨텍스트 매니저로 변환하고
    • 'with' 문과 함께 사용할 수 있게 해준다.
  2. timer 함수:

    • 매개변수 name은 측정하려는 작업의 이름을 받는다.
  3. 함수 내부:

    • t0 = time.time(): 현재 시간을 기록하고
    • yield: 컨텍스트 매니저의 본문 실행을 일시 중단하고 제어를 'with' 블록 내부로 넘긴 후
    • 'with' 블록이 종료되면 다시 이 지점으로 돌아와서
    • 마지막 줄에서 경과 시간을 계산하고 출력한다.

사용 예:

with timer("데이터 로딩"):
    load_data()

"데이터 로딩" 작업의 실행 시간을 측정하고 출력해준다. 최적화에 용이하다.

1.3.2 SparseRetrieval 클래스의 init메서드

  1. 매개변수:

    • tokenize_fn: 텍스트 토큰화 함수
    • data_path: 데이터가 저장된 경로 (기본값: "../data/")
    • context_path: 문서(Passage) 파일의 이름 (기본값: "wikipedia_documents.json")
  2. 데이터 로딩:

    • os.path.join(data_path, context_path)로 파일 경로를 생성하고
    • JSON 파일을 열어 wiki 변수에 로드
  3. 컨텍스트 처리:

    • self.contexts에 유니크한 문서 텍스트를 저장하고
    • dict.fromkeys를 사용하여 중복을 제거하면서 순서를 유지한다.
    • 유니크한 컨텍스트의 수를 출력
  4. ID 생성:

    • self.ids에 컨텍스트 수만큼의 연속된 정수 ID를 생성한다.
  5. TF-IDF 벡터라이저 초기화:

    • sklearnTfidfVectorizer를 사용
    • tokenizer: 입력받은 tokenize_fn을 사용한다.
    • ngram_range=(1, 2): 단일 단어와 두 단어의 조합을 모두 사용한다 (바이그램 유니그램)
    • max_features=50000: 최대 50,000개의 특성(단어)을 사용한다.
  6. 추가 속성 초기화:

    • self.p_embedding: 문서 임베딩을 저장할 속성 (초기값: None)
    • self.indexer: Faiss 인덱서를 저장할 속성 (초기값: None)

이 초기화를 통해서 문서 검색을 위한 기본 구조를 설정하고, TF-IDF를 사용하여 문서를 벡터화한 후에 이를 기반으로 유사도 검색을 수행할 수 있도록 준비함.

1.3.3 get_sparse_embedding() 메서드

get_sparse_embedding 메서드의 경우 문서 임베딩을 생성하고 관리하는 기능을 수행한다.

  1. 파일 경로 설정:

    • pickle_name: 임베딩을 저장할 파일 이름 ("sparse_embedding.bin")
    • tfidfv_name: TF-IDF 벡터라이저를 저장할 파일 이름 ("tfidv.bin")
    • emd_pathtfidfv_path: 각각의 전체 파일 경로
  2. 기존 파일 확인:

    • 임베딩과 TF-IDF 벡터라이저 파일이 이미 존재하는지 확인
  3. 기존 파일이 있는 경우:

    • 저장된 pickle 파일에서 임베딩(self.p_embedding)과 TF-IDF 벡터라이저(self.tfidfv)를 로드
    • "Embedding pickle load." 메시지를 출력

pickle?
pickle은 영어 단어 pickle(절인 음식)에서 유래한 것으로 데이터를 마치 절여쓰는 것 처럼 저장하고 나중에 다시 불러와 사용할 수 있도록 하는 의미이다.

pickle 모듈의 경우 데이터를 직렬화하고 역직렬화하는 방법을 제공하는데, 이를 통해서 복잡한 데이터구조를 파일에 저장하고 쉽게 불러들일 수 있다.

pickle 모듈은 Python 표준 라이브러리의 일부로 제공되고 있다.

직렬화
객체를 바이트 스트림으로 변환해서 저장하는 것.

역직렬화
바이트 스트림을 다시 원래 객체로 변환하는 것.

바이트 스트림
데이터를 연속된 byte로 표현하는 것.이를 통해 데이터를 파일에 저장하거나 네트워크를 통해 전송할 수 있는데, 이렇게 바이트스트림하게 저장하게 되면 다음의 이점이 있음.

  1. 호환성 : 어느 환경에서도 동일 데이터 복구 가능
  2. 효율성 : 이진 데이터 취급이라 빠르게 읽고 쓸 수 있음
  3. 유연성 : 리스트, 딕셔너리, 사용자 정의 객체 등도 바이트 스트림으로 저장하고 불러들일 수 있음
  4. 보안성 : 직렬화되어 전송될 때 텍스트 형식보다 보안성이 높음
  5. 네트워크 전송 : 바이트 스트림형태의 전송은 전송 속도 등의 효율성을 크게 향상시킴. 패킷 단위로 데이터를 분할하여 전송하고 쉽게 재조립이 가능하기 떄문.
  1. 기존 파일이 없는 경우:
    • "Build passage embedding" 메시지를 출력
    • self.tfidfv.fit_transform(self.contexts)를 사용하여 문서 임베딩을 생성
    • 생성된 임베딩의 shape를 출력
    • 임베딩과 TF-IDF 벡터라이저를 각각의 pickle 파일로 저장.
    • "Embedding pickle saved." 메시지를 출력

결론적으로, 이 메서드는 효율성을 위해 한 번 생성된 임베딩을 재사용할 수 있도록 설계되었으며, 이를 통해 대규모 문서 집합에 대한 임베딩 생성 시간을 크게 절약할 수 있게 되었다.
또한, TF-IDF 벡터라이저도 함께 저장함으로써, 새로운 쿼리에 대해 일관된 벡터화를 보장한다.

1.3.4 build_faiss() 메서드

build_faiss 메서드는 Faiss 라이브러리를 사용하여 고차원 벡터에 대한 효율적인 유사도 검색 인덱스를 구축한다.

  1. 인덱스 파일 확인:

    • indexer_name: 클러스터 수를 포함한 인덱스 파일 이름 생성
    • indexer_path: 전체 파일 경로 생성
    • 기존 인덱스 파일 존재 여부 확인
  2. 기존 인덱스 파일이 있는 경우:

    • faiss.read_index()로 저장된 인덱스 로드
    • "Load Saved Faiss Indexer." 메시지 출력
  3. 새로운 인덱스 구축 (기존 파일이 없는 경우):

    • self.p_embedding을 float32 타입의 NumPy 배열로 변환
    • 임베딩 차원(emb_dim) 저장.
  4. Faiss 인덱서 초기화:

    • faiss.IndexFlatL2: L2 거리를 사용하는 기본 양자화기 생성
    • faiss.IndexIVFScalarQuantizer:
      • Inverted File 인덱스와 스칼라 양자화를 결합한 인덱서 생성
      • num_clusters개의 클러스터 사용

실제 양자화 과정은 IndexIVFScalarQuantizer를 사용하여 수행되는데, 이 과정에서 각 벡터는 가장 가까운 클러스터에 할당되고, 그 클러스터의 ID로 표현된다.
양자화라고 하여서 0과 1의 직접적인 이진 양자화는 아니지만, 고차원 벡터를 더 작은 공간(클러스터 ID)으로 매핑하는 형태의 양자화를 진행한다.
위와 같은 작업을 통해 데이터의 크기를 줄이고 검색 속도를 향상시킬 수 있다.

  1. 인덱스 학습 및 데이터 추가:
    • self.indexer.train(): 인덱스 학습
    • self.indexer.add(): 실제 데이터 추가

IndexIVFScalarQuantizer의 학습 및 데이터 추가 과정

  1. 인덱스 학습 (self.indexer.train()):
    • 클러스터링: 입력 데이터를 nlist개의 클러스터로 나누고
    • 중심점 계산: 각 클러스터의 중심점(centroid)을 계산한다.
    • 스칼라 양자화 파라미터 학습: 각 차원별로 최소값과 최대값을 계산하여 양자화 범위를 결정한다.
  1. 데이터 추가 (self.indexer.add()):
    • 벡터 할당: 각 입력 벡터를 가장 가까운 클러스터에 할당한다.
    • 잔차 계산: 벡터와 할당된 클러스터 중심점 간의 차이(잔차)를 계산한다.
    • 스칼라 양자화: 잔차를 계산된 양자화 파라미터를 사용하여 양자화하고
    • 저장: 클러스터 ID와 양자화된 잔차를 저장한다.

이 과정을 통해 IndexIVFScalarQuantizer는 데이터의 분포를 학습하고, 효율적인 검색을 위한 구조를 만든다.
직접적인 이진 양자화는 수행하지 않지만, 스칼라 양자화를 통해 데이터를 압축하여 저장 공간을 절약하고 검색 속도를 향상시킨다.

  1. 인덱스 저장:
    • faiss.write_index(): 생성된 인덱스를 파일로 저장
    • "Faiss Indexer Saved." 메시지 출력

결론적으로, 이 메서드는 대규모 데이터셋에 대한 효율적인 유사도 검색을 가능하게 한다.
Faiss 인덱스를 사용함으로써 검색 속도를 크게 향상시킬 수 있으며, 한 번 구축한 인덱스를 재사용함으로써 계산 비용을 절감할 수도 있다.


1.3.5 retrieve() 메서드

문자열 쿼리 또는 Dataset 객체를 입력으로 받고, 상위 k개의 관련 문서를 반환하는 메서드로
retrieve 메서드는 주어진 쿼리에 대해 관련성 높은 문서를 검색해준다.
(전제조건 : get_sparse_embedding() 메서드가 먼저 수행되었을 것)

  1. 입력 처리:

    • 단일 문자열 쿼리 또는 Dataset 객체를 입력으로 받는다.
    • topk 매개변수로 반환할 상위 문서 수를 지정
  2. 단일 쿼리 처리 (문자열 입력):

    • get_relevant_doc 메서드를 호출하여 관련 문서를 검색한 후에
    • 검색 결과를 출력하고, 점수와 문서 내용을 튜플로 반환한다.
  3. 다중 쿼리 처리 (Dataset 입력):

    • get_relevant_doc_bulk 메서드로 대량 검색을 수행한다.
    • 각 쿼리에 대해 관련 문서를 검색하고 결과를 처리한다.
    • 검색 결과를 pandas DataFrame으로 구성한다.
  4. 결과 포맷팅:

    • 단일 쿼리: (점수 리스트, 문서 내용 리스트) 형태의 튜플 반환
    • 다중 쿼리: 쿼리, ID, 검색된 문맥, 원본 문맥(있는 경우), 답변(있는 경우)을 포함한 DataFrame 반환
  5. 성능 측정:

    • timer 컨텍스트 매니저를 사용하여 검색 시간을 측정한다.
  6. 유연성:

    • 검증 데이터의 경우 원본 문맥과 답변도 함께 반환한다.
    • 테스트 데이터의 경우 검색된 문맥만 반환한다.

결론적으로, 이 메서드는 다양한 입력 형식을 처리하고, 단일 쿼리와 대량 쿼리 모두에 대응할 수 있는 유연한 검색 기능을 제공한다.

1.3.6 get_relevant_doc() 메서드

get_relevant_doc 메서드는 주어진 쿼리에 대해 가장 관련성 높은 문서를 검색하는 기능을 수행한다.

  1. 입력 처리:

    • query (str): 검색할 질문 문자열
    • k (int, 기본값 1): 반환할 상위 문서의 개수
  2. 쿼리 벡터화:

    • self.tfidfv.transform을 사용하여 쿼리를 TF-IDF 벡터로 변환한다.
    • 이 과정의 실행 시간을 측정 (timer활용)
  3. 유효성 검사:

    • 쿼리 벡터의 합이 0이 아닌지 확인
    • 벡터의 합이 0이면 쿼리에 알려진 단어가 없다는 의미이므로 오류를 발생시킨다.
  4. 유사도 계산:

    • 쿼리 벡터와 문서 임베딩의 내적을 계산하여 유사도를 구한다.
    • 이 과정의 실행 시간 또한 timer로 측정한다.
  5. 결과 형식 변환:

    • 결과가 NumPy 배열이 아니면 배열로 변환한다.
  6. 결과 정렬 및 상위 k개 선택:

    • 유사도 점수를 내림차순으로 정렬한다.
    • 상위 k개의 문서 점수와 인덱스를 선택한다.
  7. 결과 반환:

    • 선택된 문서들의 유사도 점수 리스트와 인덱스 리스트를 반환한다.

결론적으로, 이 메서드는 효율적인 벡터 연산을 통해 빠른 검색을 수행하며, 타이머를 사용하여 각 단계의 성능을 모니터링할 수 있다.


1.3.7 get_relevant_doc_bulk() 메서드

get_relevant_doc_bulk 메서드는 여러 개의 쿼리에 대해 동시에 관련 문서를 검색하는 기능을 수행한다. 일종의 bulk버전 !

  1. 입력 처리:

    • queries (List): 여러 개의 쿼리 문자열 리스트
    • k (int, 기본값 1): 각 쿼리당 반환할 상위 문서의 개수
  2. 쿼리 벡터화:

    • self.tfidfv.transform을 사용하여 모든 쿼리를 TF-IDF 벡터로 변환.
  3. 유효성 검사:

    • 쿼리 벡터의 합이 0이 아닌지 확인
    • 벡터의 합이 0이면 알려진 단어가 없다는 의미이므로 오류를 발생시킨다.
  4. 유사도 계산:

    • 쿼리 벡터와 문서 임베딩의 내적을 계산하여 유사도를 구한다.
  5. 결과 처리:

    • 각 쿼리에 대해 유사도 점수를 내림차순으로 정렬
    • 상위 k개의 문서 점수와 인덱스를 선택
  6. 결과 반환:

    • 모든 쿼리에 대한 상위 k개 문서의 점수 리스트와 인덱스 리스트를 반환

대량의 쿼리를 효율적으로 처리할 수 있도록 설계되었으며, 벡터 연산을 통해 빠른 검색을 수행한다.
앞선 코드의 bulk 버전이라고 보면 된다.


1.3.8 retrieve_faiss() 메서드

retrieve_faiss 메서드는 Faiss 인덱서를 사용하여 효율적인 문서 검색을 수행한다.

  1. 입력 유효성 검사:

    • Faiss 인덱서(self.indexer)가 초기화되었는지 확인한다.
    • 초기화되지 않았다면 오류 메시지를 출력. build_faiss()를 먼저 실행해야 한다.
  2. 입력 처리:

    • 입력이 문자열인지 Dataset 객체인지 확인
  3. 단일 쿼리 처리 (문자열 입력의 경우):

    • get_relevant_doc_faiss 메서드를 호출하여 관련 문서를 검색
    • 검색 쿼리를 출력
    • 상위 k개의 검색 결과에 대해 점수와 문서 내용을 출력
    • 결과를 (점수 리스트, 문서 내용 리스트) 형태의 튜플로 반환
  4. 다중 쿼리 처리 (Dataset 입력의 경우):

    • Dataset에서 "question" 필드를 추출하여 쿼리 리스트를 생성
    • timer 컨텍스트 매니저를 사용하여 Faiss 검색 시간을 측정
    • get_relevant_doc_bulk_faiss 메서드를 호출하여 대량 검색을 수행
    • 각 쿼리에 대해 결과를 처리
      • 질문, ID, 검색된 문맥을 포함하는 임시 딕셔너리를 생성하고
      • 원본 데이터에 "context"와 "answers" 필드가 있으면 이를 포함함.
    • 처리된 결과를 pandas DataFrame으로 변환하여 반환
  5. 성능 최적화:

    • Faiss를 사용하여 고차원 벡터에 대한 효율적인 유사도 검색을 수행하고
    • 대량 쿼리 처리 시 벡터화된 연산을 사용하여 성능을 향상시킨다.
  6. 유연성:

    • 단일 쿼리와 대량 쿼리 모두를 처리할 수 있음.
    • 검증 데이터의 경우 원본 문맥과 답변도 함께 반환
  7. 진행 상황 표시:

    • tqdm을 사용하여 대량 쿼리 처리 시 진행 상황을 표시함.

1.3.9 get_relevant_doc_faiss() 메서드

get_relevant_doc_faiss 메서드는 Faiss 인덱서를 사용하여 단일 쿼리에 대한 관련 문서를 검색하는 기능을 수행한다.

  1. 입력 처리:

    • query (str): 검색할 질문 문자열
    • k (int, 기본값 1): 반환할 상위 문서의 개수
  2. 쿼리 벡터화:

    • self.tfidfv.transform([query])를 사용하여 쿼리를 TF-IDF 벡터로 변환
  3. 유효성 검사:

    • 쿼리 벡터의 합이 0이 아닌지 확인
    • 벡터의 합이 0이면 쿼리에 알려진 단어가 없다는 의미이므로 오류를 발생시킴.
  4. 벡터 형식 변환:

    • query_vec.toarray().astype(np.float32)를 사용하여 벡터를 NumPy 배열로 변환하고 float32 타입으로 변경
  5. Faiss 검색 수행:

    • timer 컨텍스트 매니저를 사용하여 검색 시간을 측정
    • self.indexer.search(q_emb, k)를 호출하여 Faiss 인덱서로 검색을 수행
    • D: 상위 k개 문서와의 거리(유사도)
    • I: 상위 k개 문서의 인덱스
  6. 결과 반환:

    • 거리(D)와 인덱스(I)를 리스트로 변환하여 반환

결론적으로 이 메서드는 Faiss를 사용하여 고차원 벡터에 대한 효율적인 유사도 검색을 수행하며, 대규모 데이터셋에서도 빠른 검색이 가능하다. 일반적인 get_relevant_doc 메서드와 비교하여 더 빠른 검색 속도를 제공한다.

1.3.10 get_relevant_doc_bulk_faiss() 메서드

get_relevant_doc_bulk_faiss 메서드는 Faiss 인덱서를 사용하여 여러 쿼리에 대해 동시에 관련 문서를 검색하는 기능을 수행한다.

  1. 입력 처리:

    • queries (List): 여러 개의 쿼리 문자열 리스트
    • k (int, 기본값 1): 각 쿼리당 반환할 상위 문서의 개수
  2. 쿼리 벡터화:

    • self.tfidfv.transform(queries)를 사용하여 모든 쿼리를 TF-IDF 벡터로 변환
  3. 유효성 검사:

    • 쿼리 벡터의 합이 0이 아닌지 확인
    • 벡터의 합이 0이면 알려진 단어가 없다는 의미이므로 오류를 발생시킴.
  4. 벡터 형식 변환:

    • query_vecs.toarray().astype(np.float32)를 사용하여 벡터를 NumPy 배열로 변환하고 float32 타입으로 변경
  5. Faiss 검색 수행:

    • self.indexer.search(q_embs, k)를 호출하여 Faiss 인덱서로 검색을 수행한다.
    • D: 각 쿼리에 대한 상위 k개 문서와의 거리(유사도) 행렬
    • I: 각 쿼리에 대한 상위 k개 문서의 인덱스 행렬
  6. 결과 반환:

    • 거리(D)와 인덱스(I)를 리스트로 변환하여 반환

결론적으로, 이 메서드는 Faiss를 사용하여 대량의 쿼리에 대해 효율적인 유사도 검색을 수행한다.
일반적인 get_relevant_doc_bulk 메서드와 비교하여 더 빠른 검색 속도를 제공하며, 특히 대규모 데이터셋에서 성능 차이가 두드러진다.


retrieve_faiss() 메서드와 retrieve() 메서드의 주요 차이점

retrieve_faiss() 메서드와 retrieve() 메서드는 다음 과 같은 점에서 다르다.

  1. 검색 알고리즘:
    • retrieve_faiss(): Faiss 라이브러리를 사용한 고효율 벡터 검색
    • retrieve(): TF-IDF 기반의 일반적인 스파스 벡터 검색
  1. 사전 요구사항:
    • retrieve_faiss(): build_faiss() 메서드로 Faiss 인덱스를 먼저 구축해야 함
    • retrieve(): TF-IDF 벡터화만 필요하며 추가 인덱스 구축 불필요
  1. 내부 호출 메서드:
    • retrieve_faiss(): get_relevant_doc_faiss() 또는 get_relevant_doc_bulk_faiss() 사용
    • retrieve(): get_relevant_doc() 또는 get_relevant_doc_bulk() 사용
  1. 성능:
    • retrieve_faiss(): 대규모 데이터셋에서 더 빠른 검색 속도 제공
    • retrieve(): 상대적으로 느린 전수 검색 수행
  1. 메모리 효율성:
    • retrieve_faiss(): Faiss 인덱스를 사용하여 메모리 효율적인 검색 가능
    • retrieve(): 전체 임베딩을 메모리에 로드하여 사용
  1. 확장성:
    • retrieve_faiss(): 대규모 데이터셋에 더 적합
    • retrieve(): 소규모 데이터셋에 적합

두 메서드는 기본적인 인터페이스와 결과 형식은 유사하지만, 내부 검색 메커니즘과 성능 특성에서 차이가 있다.
특히 Faiss 버전은 대규모 데이터셋에서 더 효율적인 검색을 제공한다.


1.3.11 inference.py가 직접실행 됐을 때

  1. CLI 명령어로 인자 파싱:

    • argparse를 사용하여 다양한 매개변수를 받고 (dataset_name, model_name_or_path, data_path, context_path, use_faiss).
  2. 데이터셋 로드 및 전처리:

    • load_from_disk로 데이터셋을 로드
    • train과 validation 데이터셋을 합쳐 전체 데이터셋을 생성
  3. 토크나이저 초기화:

    • AutoTokenizer를 사용하여 지정된 모델의 토크나이저를 로드
  4. SparseRetrieval 객체 생성:

    • 토크나이저, 데이터 경로, 컨텍스트 경로를 인자로 전달
  5. 검색 테스트:

    • 단일 쿼리 예시를 정의
    • args.use_faiss 값에 따라 Faiss 사용 여부를 결정
  6. Faiss 사용 시:

    • 단일 쿼리에 대해 retrieve_faiss 메서드를 테스트
    • 전체 데이터셋에 대해 retrieve_faiss를 실행하고 정확도를 계산
  7. Faiss 미사용 시:

    • 전체 데이터셋에 대해 retrieve 메서드를 실행하고 정확도를 계산
    • 단일 쿼리에 대해 retrieve 메서드를 테스트
  8. 성능 측정:

    • timer 컨텍스트 매니저를 사용하여 각 검색 작업의 실행 시간을 측정한다.

1.4 train.py

QA 모델을 훈련하고 평가하기 위한 파일이다.

1.4.1 main()

  1. 인자 파싱:

    • HfArgumentParser를 사용하여 ModelArguments, DataTrainingArguments, TrainingArguments를 파싱
    • 파싱된 인자들은 각각 model_args, data_args, training_args에 저장
  2. 모델 및 데이터 정보 출력:

    • 모델의 출처와 데이터셋의 이름을 출력
  3. 로깅 설정:

    • logging.basicConfig를 사용하여 로그 포맷, 날짜 형식, 핸들러를 설정
    • 로그는 표준 출력(sys.stdout)으로 전송됨.
  4. 훈련/평가 파라미터 로깅:

    • training_args의 내용을 로그로 기록
  5. 난수 고정:

    • set_seed 함수를 사용하여 재현 가능성을 확보
  6. 데이터셋 로드:

    • load_from_disk 함수를 사용하여 디스크에서 데이터셋을 로드
    • 이 함수는 Hugging Face의 Dataset 객체로 데이터를 변환
  7. 모델 설정:

    • AutoConfig, AutoTokenizer, AutoModelForQuestionAnswering을 사용하여 사전 훈련된 모델, 토크나이저, 설정을 로드한다.
    • 모델과 토크나이저는 model_args에 지정된 경로에서 로드됨.
  8. 객체 타입 출력:

    • 주요 객체들(training_args, model_args, datasets, tokenizer, model)의 타입을 출력
  9. MRC(기계 독해) 실행:

    • training_args.do_train 또는 training_args.do_eval이 True인 경우, run_mrc 함수를 호출
    • run_mrc 함수에 data_args, training_args, model_args, datasets, tokenizer, model을 인자로 전달

이 코드는 질문 답변 모델의 훈련 또는 평가를 위한 초기 설정을 수행한다.
다만, 필요한 모든 컴포넌트를 준비해주기는 하는데, 실제 훈련이나 평가는 run_mrc 함수 내에서 이루어짐.


1.4.2 run_mrc()

  1. 데이터셋 컬럼 설정:

    • 훈련 모드인지 평가 모드인지에 따라 적절한 데이터셋의 컬럼 이름을 가져옴.
    • training_args.do_train이 True면 훈련 데이터셋의 컬럼 이름을, 아니면 검증 데이터셋의 컬럼 이름을 사용
  2. 질문, 컨텍스트, 답변 컬럼 이름 설정:

    • 데이터셋의 구조에 따라 적절한 컬럼 이름을 설정
    • 기본값으로 "question", "context", "answers"를 사용하지만, 데이터셋에 이 이름들이 없으면 순서대로 첫 번째, 두 번째, 세 번째 컬럼을 사용
  3. 패딩 방향 설정:

    • 토크나이저의 패딩 방향에 따라 pad_on_right 변수를 설정
    • 이는 입력 시퀀스의 구성 방식을 결정
      (질문|컨텍스트 또는 컨텍스트|질문).
  4. 오류 확인 및 최대 시퀀스 길이 설정:

    • check_no_error 함수를 호출하여 데이터셋과 설정에 오류가 없는지 확인
    • 이 함수는 마지막 체크포인트와 최대 시퀀스 길이를 반환
    • 이 정보는 이후 데이터 전처리와 모델 훈련에 사용됨
  5. train_args.do_train 인 경우

    • train_dataset을 datasets["train"]으로 받고
    • map()메서드를 이용하여 train_dataset에 prepare_train_features 적용
  6. train_args.do_eval 인 경우

    • eval_dataset을 datasets["validation"]으로 설정하고
    • map() 메서드를 이용하여 eval_dataset에 prepare_validation_features 적용
  7. data_collator로 DataCollatorwithpadding 클래스 초기화

  8. metric 변수 선언해주고

  9. QuestionAnsweringTrainer를 사용하여 모델 훈련을 위한 트레이너를 설정한 후

  10. 이미 정의된 compute_metrics post_processing_function 함수를 사용하여 훈련과 평가 옵션에 따라 실행한다.


1.4.3 prepare_train_features()

  1. 토큰화 및 인코딩:

    • 질문과 컨텍스트를 토큰화
    • pad_on_right 변수에 따라 질문과 컨텍스트의 순서를 결정
    • max_length, stride, return_overflowing_tokens, return_offsets_mapping 등의 옵션을 사용하여 토큰화를 수행
  2. 오버플로우 처리:

    • sample_mapping을 통해 긴 컨텍스트가 여러 부분으로 나뉘었을 때 원본 예제와의 매핑을 유지하고

      sample_mapping 예시
      [0, 0, 1, 1, 2, 2, 3, 3, 4, 4]

  3. 오프셋 매핑:

    • 각 토큰의 시작과 끝 위치를 원본 텍스트에 매핑
  4. 답변 위치 레이블링:

    • start_positionsend_positions 리스트를 초기화
  5. 각 예제에 대한 처리:

    • input ID와 시퀀스 ID를 가져온다.
    • CLS 토큰의 인덱스를 찾고
    • 샘플 인덱스를 통해 원본 답변 정보를 가져온다.
  6. 답변 없는 경우 처리:

    • 답변이 없는 경우, CLS 토큰 인덱스를 시작과 끝 위치로 설정
  7. 답변 있는 경우 처리:

    • 답변의 시작과 끝 문자 인덱스를 찾고
    • 컨텍스트의 시작 토큰 인덱스를 찾음.
    • 컨텍스트의 끝 토큰 인덱스를 찾음.
  8. 답변 위치 조정:

    • 답변이 주어진 span 내에 있는지 확인하고
    • span을 벗어난 경우, CLS 토큰 인덱스를 사용함.
    • span 내에 있는 경우, 정확한 시작과 끝 토큰 인덱스를 찾음.
  9. 결과 반환:

    • 처리된 예제들을 포함한 딕셔너리를 반환

1.4.4 prepare_validation_features()

prepare_validation_features 함수는 검증 데이터셋을 전처리하는 역할을 한다.

  1. 토큰화 및 인코딩:

    • 질문과 컨텍스트를 토큰화
    • pad_on_right 변수에 따라 질문과 컨텍스트의 순서를 결정하고
    • max_length, stride, return_overflowing_tokens, return_offsets_mapping 등의 옵션을 사용하여 토큰화를 수행
  2. 오버플로우 처리:

    • sample_mapping을 통해 긴 컨텍스트가 여러 부분으로 나뉘었을 때 원본 예제와의 매핑을 유지
  3. 예제 ID 설정:

    • tokenized_examples["example_id"] 리스트를 초기화
  4. 각 토큰화된 예제에 대한 처리:

    • 시퀀스 ID를 설정하여 각 토큰이 컨텍스트인지 질문인지 구분
    • context_index를 설정하여 컨텍스트 부분을 식별
    • 예제 ID를 추가하여 원본 데이터와의 연결을 유지
  5. 오프셋 매핑 조정:

    • 컨텍스트에 해당하는 토큰의 오프셋 매핑만 유지하고, 나머지는 None으로 설정
    • 이는 나중에 예측 결과를 원본 컨텍스트와 매칭시킬 때 유용
  6. 결과 반환:

    • 처리된 예제들을 포함한 딕셔너리를 반환

이 함수는 prepare_train_features()와 유사하지만, 평가를 위한 몇 가지 차이가 있음.
1. 답변 위치를 레이블링하지 않음 (평가 시에는 모델의 예측을 사용하므로).
2. 예제 ID를 유지하여 예측 결과를 원본 데이터와 연결할 수 있게 함.
3. 오프셋 매핑을 조정하여 컨텍스트 부분만 식별할 수 있게 한다.


1.4.5 post_processing_function()

post_processing_function은 모델의 예측 결과를 후처리하는 함수이다.

  1. 예측 후처리:

    • postprocess_qa_predictions 함수를 호출하여 모델의 원시 예측(start logits와 end logits)을 원본 컨텍스트의 실제 텍스트 답변으로 변환
    • 이 과정에서 최대 답변 길이(max_answer_length)를 고려
  2. 예측 결과 포맷팅:

    • 후처리된 예측을 {"id": 예제ID, "prediction_text": 예측된답변} 형식의 딕셔너리 리스트로 변환한다.
  3. 모드별 처리:

    • 예측 모드(do_predict):
      • 포맷팅된 예측 결과를 그대로 반환
    • 평가 모드(do_eval):
      • 검증 데이터셋에서 정답을 추출하여 참조(reference) 데이터를 생성
      • EvalPrediction 객체를 생성하여 예측과 정답을 함께 반환
  4. 평가를 위한 데이터 준비:

    • 평가 모드에서는 모델의 예측과 실제 정답을 비교할 수 있는 형태로 데이터를 구성한다.
    • 이는 후속 평가 메트릭 계산에 사용됨.

이 함수는 모델의 raw 출력을 의미 있는 텍스트 답변으로 변환하고, 평가를 위한 데이터 구조를 준비하는 역할을 함.
특히, 질문 답변 태스크에서 모델 성능을 정확히 평가하기 위해 필수적인 단계임.


1.4.6 compute_metrics()

compute_metrics 함수는 모델의 예측 성능을 평가한다.

  1. 입력 파라미터:

    • p: EvalPrediction 타입의 객체를 받음.
      이 객체는 모델의 예측값과 실제 레이블을 포함함.
  2. 메트릭 계산:

    • metric.compute() 메서드를 호출하여 성능 지표를 계산
    • metric은 이전에 load_metric("squad")로 로드된 SQuAD(Stanford Question Answering Dataset) 평가 메트릭
  3. 인자:

    • predictions: 모델이 예측한 답변들이고, p.predictions에서 가져옴
    • references: 실제 정답들이고,p.label_ids에서 가져옴.
  4. 반환값:

    • SQuAD 메트릭에 따른 성능 지표를 딕셔너리 형태로 반환
    • 일반적으로 Exact Match(EM)와 F1 점수가 포함됨.
  5. 사용 목적:

    • 이 함수는 주로 Trainer 클래스에 의해 자동으로 호출되어 평가 단계에서 모델의 성능을 측정함.
    • 훈련 중 또는 훈련 후 모델의 성능을 모니터링하고 보고해줌.

1.5 trainer_qa.py

  1. 클래스 정의:

    • QuestionAnsweringTrainer는 Hugging Face의 Trainer 클래스를 상속받아 질문 답변 태스크에 특화된 기능을 제공
  2. 초기화 메소드 (__init__):

    • 부모 클래스의 초기화 메소드를 호출
    • eval_examplespost_process_function을 추가로 받아 저장하며, 이는 평가시 사용됨.
  3. evaluate 메소드:

    • 모델 평가를 위한 메소드
    • 평가 데이터셋과 데이터로더를 설정하고
    • prediction_loop를 사용하여 모델의 예측을 수행함.
    • 임시로 compute_metrics를 비활성화하고, 예측 후 다시 활성화
    • 데이터셋 형식을 원래대로 복원하고
    • post_process_function이 있으면 예측 결과를 후처리까지 해줌.
    • 그리고 compute_metrics가 있으면 평가 지표를 계산하고 로깅
    • TPU 사용 시 디버그 메트릭을 출력하고
    • 콜백 핸들러를 통해 평가 결과를 처리함.

      콜백 핸들러?
      self.callback_handler.on_evaluate():
      이는 평가 과정이 완료된 후 호출되는 콜백 메소드로,
      callback_handler는 여러 콜백들을 관리하는 객체임.

      1. 인자들:
        self.args: 훈련 인자들을 포함(예: 학습률, 배치 크기 등).
        self.state: 현재 훈련 상태(예: 현재 에폭, 스텝 수 등).
        self.control: 훈련 루프의 제어 흐름을 관리
        metrics: 방금 완료된 평가의 결과 메트릭스
      2. 목적:
        이 메소드는 평가 결과를 처리하고, 필요한 경우 훈련 과정을 조정할 수 있게 함.
        예를 들어, 조기 종료(early stopping)나 학습률 조정 등을 구현할 수 있음.
      3. 반환값:
        on_evaluate 메소드는 새로운 control 객체를 반환
        이 control 객체는 훈련 루프의 다음 단계를 결정하는 데 사용됨.
      4. 사용 예:
        평가 결과에 따라 훈련을 계속할지, 중단할지, 또는 다른 조치를 취할지 결정할 수 있음.
        특정 메트릭이 개선되지 않으면 훈련을 중단하거나, 학습률을 조정하는 등의 로직을 구현할 수 있음.
  1. predict 메소드:
    • 테스트 데이터에 대한 예측을 수행
      (evaluate 메소드와 유사한 구조를 가짐)
    • 테스트 데이터로더를 생성하고 prediction_loop를 실행
    • 예측 결과를 후처리하여 반환

주요 특징:

  • post_process_function을 사용하여 모델의 raw 출력을 처리
    이는 QA 태스크에서 중요한 단계로, 모델의 출력을 실제 텍스트 답변으로 변환
  • compute_metrics 함수를 유연하게 처리하여, 사용자 정의 평가 지표를 쉽게 적용할 수 있게 함.
  • TPU 지원을 위한 코드가 포함되어 있어, 대규모 학습에 적합함.
  • datasets 라이브러리와의 호환성을 유지하여, 다양한 데이터셋 형식을 지원

1.6 utils_qa.py

postprocess_qa_predictions 함수는 모델의 예측 결과를 후처리 한다.
QA모델이 생성한 원시 예측값(start logits와 end logits)을 가져와 실제 텍스트 답변으로 변환하고, 여러 가지 제약 조건과 휴리스틱을 적용하여 최종 답변의 품질을 향상시켜준다.

  1. 입력 데이터 준비

    • 원본 예제와 전처리된 특징들을 매핑
    • 예측 결과를 저장할 데이터 구조를 초기화
  2. 예제별 처리
    각 예제에 대해 다음 과정을 수행

    • 후보 답변 생성

      • 모든 가능한 start/end 인덱스 조합을 고려
      • 유효하지 않은 답변(길이 제한 초과 등)을 필터링
    • 답변 점수 계산

      • start logit과 end logit을 합산하여 각 후보 답변의 점수를 계산
    • 최상의 답변 선택

      • 점수에 따라 후보 답변을 정렬
      • 상위 n개(n_best_size)의 답변만 유지
  3. 텍스트 추출

    • 선택된 start/end 인덱스를 사용하여 원본 컨텍스트에서 실제 텍스트를 추출
  4. 확률 계산

    • 소프트맥스 함수를 사용하여 각 답변의 확률을 계산
  5. 최종 답변 선택

    • 일반적으로 가장 높은 점수의 답변을 선택하고
    • 'no answer' 가능성이 있는 경우(version_2_with_negative), 임계값을 사용하여 null 답변과 비교함.
  6. 결과 저장

    • 최종 예측, n-best 예측, 그리고 필요한 경우 null 스코어를 JSON 파일로 저장함.

위 코드는 다음의 장점이 있음.

  • 유연성: 다양한 매개변수(n_best_size, max_answer_length 등)를 통해 후처리 과정을 조정하고
  • 'No Answer' 처리: SQuAD 2.0과 같은 데이터셋에서 중요한 'no answer' 가능성을 처리해줌.
  • 컨텍스트 고려: 토큰의 최대 컨텍스트를 고려하여 더 정확한 답변을 선택

즉 원시 모델 출력을 사람이 읽을 수 있는 답변으로 변환하는 복잡한 과정을 처리해주고
다양한 휴리스틱과 후처리 단계를 통해 MRC 시스템의 전반적인 성능을 향상시켜줌.

휴리스틱?
복잡한 문제를 빠르게 해결하기 위해 사용하는 간단한 방법이나 경험적 법칙

가용성 휴리스틱:
쉽게 떠오르는 정보를 기반으로 판단

대표성 휴리스틱:
과거 경험을 바탕으로 새로운 상황의 확률을 추정

기준점과 조정 휴리스틱:
알고 있는 수치를 기준으로 삼아 판단을 조정

profile
헤매는 만큼 자기 땅이다.

2개의 댓글

comment-user-thumbnail
2024년 10월 14일

감사합니다. 잘 읽었어요!

1개의 답글