boostcampaitech7 / level2-mrc-nlp-14

level2-mrc-nlp-14 created by GitHub Classroom
1 stars 1 forks source link

[New Feature] 주어진 context에서 답변을 찾을 수 없는 경우 `<no_answer>` 제공 기능 #11

Closed jagaldol closed 3 weeks ago

jagaldol commented 1 month ago

기능 요약

제공된 context 내에서 답변이 포함되어 있지 않은 경우, 답변할 수 없다고 판단할 수도 있게 학습하면 좋을 것 같습니다.

고안 배경

image

강의 8강에서 no answer bias를 다루는 부분을 보고 해당 기능에 대한 필요성을 생각하였습니다.

또한, 코드를 살펴보았을 때 해당 기능이 일부 구현되어 있기에 약간의 수정으로 충분히 적용할 수 있다고 생각하였습니다.

기능 상세 플로우

기본적으로 util.pypostprocess_qa_predictions 함수에서 version_2_with_negative를 True로 주면 null prediction을 고려하게 됩니다.

if score_diff > null_score_diff_threshold:
    all_predictions[example["id"]] = ""
else:
    all_predictions[example["id"]] = best_non_null_pred["text"]

위와 같은 코드로 threshold 이상일 때 빈 문자열을 리턴하게 구현이 되어 있습니다.

또한, min_null_prediction은 아래와 같으며, start와 end의 둘다 index가 0일 경우의 확률로 하드코딩되어 score가 계산됩니다.

min_null_prediction = {
    "offsets": (0, 0),
    "score": feature_null_score,
    "start_logit": start_logits[0],
    "end_logit": end_logits[0],
}

text_data_loader.pyprepare_train_features 함수를 보면 정답이 없는 경우 cls_index로 넣는데 현재 기본 모델인 bert의 경우 전부 0이 들어 갑니다. (즉, 여기서는 하드코딩되어 있지 않지만 위와 동일하게 0으로 들어갑니다)

cls_index = input_ids.index(self.tokenizer.cls_token_id)  # cls index

# sequence id를 설정합니다 (to know what is the context and what is the question).
sequence_ids = tokenized_examples.sequence_ids(i)

# 하나의 example이 여러개의 span을 가질 수 있습니다.
sample_index = sample_mapping[i]
answers = examples[self.answer_column_name][sample_index]

# answer가 없을 경우 cls_index를 answer로 설정합니다(== example에서 정답이 없는 경우 존재할 수 있음).
if len(answers["answer_start"]) == 0:
    tokenized_examples["start_positions"].append(cls_index)
    tokenized_examples["end_positions"].append(cls_index)

예상 결과

retrieval가 실패했을 때, 잘못된 문서를 가지고 억지 정답을 찾아 이상한 대답을 하는 경우를 줄일 수 있을 것으로 예상됩니다.

기타 사항

현재 문서의 길이가 긴 경우 stride를 가진채 여러개로 쪼개집니다. 이때 정답을 포함하고 있지 않은 경우, answer의 start와 end를 0으로 설정하게 됩니다.

그리고 postprocessing 과정 중에 같은 id의 데이터를 취합하여 가장 높은 확률의 정답을 선택하기 때문에, 단순히 위와 같이 변경해버리면 문제가 생길 수 있습니다.

즉, 현재 구현에서 version_2_with_negative = True를 하게 되면, 위 두 경우의 각 확률을 단순히 비교하고 더 큰 값을 선택해버립니다.

이 부분에 대한 유의를 하고 해결방안을 고민하여 조심스럽게 접근할 필요가 있습니다.

jagaldol commented 1 month ago

이후 확인 결과 util.postprocess_qa_predictions 는 단순히 평가를 위해 logits 값을 바탕으로 최종 예측된 단어를 만드는 함수라는 걸 알았습니다.

학습 과정에서는 이미 정답이 없는 경우 <cls> 토큰 index로 간주하여 no_answer에 대한 학습을 하고 있습니다.

따라서, 1차적으로 단순히 no_answer에 대한 학습 데이터 증강만 해도 될 것 같습니다.


이 외에 version_2_with_negative = True 를 할 경우, 토크나이징 과정에서 한 문서가 여러개로 쪼개졌을 때, 한 split에서 정답을 제대로 예측했음에도 불구하고 다른 splitno_answer score가 더 커 no_answer로 최종 예측되는 문제는 잔존합니다.

이 문제에 대한 해결법으로 한 문서에서 출발한 여러 split data에 대해 모두가 no_answer로 예측한 경우만 최종 예측으로 no_answer를 리턴하는 방법이 존재합니다.

이렇게 no_answer에 대한 예측을 무시하지 않고 유지할 때의 장점이 존재합니다.

jagaldol commented 3 weeks ago

데이터 증강은 우선순위에서 밀려나 안하게 되었습니다.