위와 같은 방법으로 kFold를 해줄 수 있습니다. 기존의 Stratifed는 여기서는 의미가 없어서 사용하지 않습니다. 또, idx로 나눈 train과 valid를 다시 DatasetDict로 해서 datasets로 선언해주는 이유는 기존 run_mrc에서 datasets['validation'] 처럼 사용하기 때문에 기존의 사용하는 방식을 그대로 유지해주기 위함입니다.
이는, 실제로 구현하기가 좀 골치아픈 부분이 많았는데, KFold 방식을 추가하기 위해서는 기존 코드를 많이 고쳐야 했습니다. 단순히 if문으로 kfold 사용여부에 따라 분기를 나눌수도 있겠지만, 코드의 재사용성과 가독성이 너무나 떨어질까 이를 고칠까 처음에 고민했습니다. 그치만, 코드가 너무 달라져도 그 나름대로 기존의 이해를 해친다고 생각이 들었습니다.(그만큼 get_data를 거의 없앨 정도로 고쳐야 했고 이는 inference에서도 사용하다 보니, 굉장히 많은 개선 작업이 필요했음) 그래서 최대한 기존의 코드를 유지하면서 조금만 수정하고 분기로 흐를 수 있도록 했습니다.
좀 더 자세하게는, k fold는 반복작업이 수행되는 것이기 때문에, 불필요한 반복작업(ex 똑같은 설정을 매 반복문마다 불온다던지)들만 수정하고 그 외의 기능은 최대한 유지하려고 했습니다.
달라진 내용은 run_mrc 함수 안에 있던 get_data를 main 함수로 꺼내왔습니다. 기존의 코드 실행 순서는 완전히 동일합니다. 다만, run_mrc안에서 get_data가 먼저 실행 순으로 실행되던 것이 get_data실행 후 run_mrc 실행으로만 변경된 것입니다. 즉, 코드의 위치가 변경되었습니다. 자세한 내용은 아래에서 코드적으로 더 설명합니다.
K FOLD를 사용할 때의 방식입니다. get_data 함수를 재사용할 수 없어서 해당 함수에서 사용하던 기능을 모두 그대로 불러왔습니다. 재사용할 수 없는 가장 큰 이유는 get_data의 코드 중에서 5번 반복실행되어야 하는 코드와 1번만 실행되는 코드가 같이 위치해있기 때문입니다.
엄밀히 보면, 5번 실행되도 코드 수행 상 큰 문제는 없을 수도 있습니다. 다만, 추후에 개선을 위한 코드 수정에 도움이 될 수 있도록 했습니다.
코드를 보면 대략의 흐름도가 이해될 것입니다.
inference
여기서 가장 중요하게 생각한 부분은, 최대한의 수정 없이 재사용할 수 있는 부분을 고민하는 것입니다.
기존의 K-Fold 방식은 inference 메커니즘을 변경시켜서 5개의 결과를 평균내는 방법(=soft vote)을 택했습니다. 이는 꽤 확실하지만 기존의 코드를 수정해야 하는 단점이 있습니다. "수정하면 되는 것이 아닌가" 라고 말할 수 있겠습니다만, postprocess_qa_predictions 함수를 전반적으로 수정해야 한다는 점, fold를 이용할 때와 하지 않을 때를 둘 다 가능하게 해야한다는 점이 꽤 쉽지 않은 난이도를 형성했습니다.
따라서, 목표를 코드를 재사용해서 cross하는 방법으로 세웠습니다. 바로, 기존의 nbest_prediction을 이용하는 것입니다.
best_prediction은 단 하나의 예측만 가지고 있어서 soft vote할 수 없습니다.
게다가, best_prediction에는 제일 중요한 logit 값이 없습니다. 결국 hard vote밖에 할 수 없다는 이유.
다만, 여기에도 문제가 조금 있습니다.
동일한 text가 여러 개 뽑힐 수 있다는 점. 따라서, nbest_prediction을 이용하면 결국 k <= n 인 kbest_prediction으로밖에 결과를 내지 못한다는 점입니다. (운이 안좋으면 아예 kbest_prediction을 못얻을 수도 있습니다)
다만, 이 부분은 우리의 목표가 best 1을 원한다는 점에서 k=1인 경우만 생각하기로 간주했으며 각각의 fold내의 동일한 text에 대해서는 확률이 더 높은 text의 확률을 사용했습니다.
또한, 재사용성이라는 점의 초점을 맞추어, inference를 5번(fold 개수) 해야합니다. 물론 이는 train에서 자동으로 진행되게 설정했으며, 마지막에 combine.py를 실행해서 최종 결과를 얻을 수 있도록 했습니다.
✨ 결과
k = 1
fold 적용 전 : 22.920
fold 적용 후 : 27.500
차이 : 4.580
k = 30
fold 적용 전 : 34.170
fold 적용 후 : 37.080
차이 : 2.910
왜 인지는 모르겠으나, k = 30일 때의 신곤님의 결과(31.250)보다도 2.920이 더 높았고 희락님의 결과(32.080)보다도 2.090이 더 높았습니다. 2~3점의 차이가 난 이유는 확실히는 모르겠습니다. seed도 고정되어있고 dataset도 동일한데 말이죠.
저의 경우는 5번을 시도해보았지만 매번 동일한 결과 그래프를 얻었습니다.
추측할 수 있는 경우는 다음과 같습니다.
inialization이 다르게 되었다. (seed가 같아도 다르게 되나?? 라는 의문은 있음)
train과 inference시에 사용한 모델이 다르다? (두분이 실수했다?? 예?)
tfidfv.bin과 sparse_embedding.bin의 값이 다르다. (실제로, 저도 이 파일을 지우고 새로 만들었더니 성능이 올랐습니다.)
결론
k fold를 사용하는 것이 무조건 유리한 것으로 보입니다.
개선점
현재는 kfold에서 retrieve를 비효율적으로 사용합니다. 쉽게 예를 들겠습니다. 학생 A, B, C, D, E 총 5명이 있습니다. 도서관에서 책을 빌리는데는 5원이들고 도서관에 책을 반납하는데도 5원이 듭니다. 책을 보는데 1인당 총 10원의 비용이 듭니다. 따라서 전체 인원이 책을 보는데는 50원의 비용이 소모됩니다. 근데 여기서, A가 책을 빌리고 A, B, C, D, E가 이 책을 나눠서 본다음에 E가 반납을 하면 총 10원의 비용이 소모됩니다.
위 예시와 같이, 각 fold마다 retrieve를 독립적으로 하는것이 아니라 retrieve를 하면 이것을 각 fold가 나누어서 처리할 수 있도록 개선해야 될 것입니다.
✨ 계획
[x] 1. 주어진 데이터셋 합치고 새로운 공간 찾기
[x] 2. get_data에서 데이터를 split하고 k fold가 작동하도록 구현
✨ 수행
1. 주어진 데이터셋 합치고 새로운 공간 찾기
라이브러리 선언
데이터셋 로드
데이터셋은 다음과 같은 특징을 가집니다.
k=5 일 때의 데이터셋 길이
데이터셋 통합
2. get_data에서 데이터를 split할 수 있도록 하기
Kfold 간단히 알아보기
Kfold 수행 (참고링크)
DatasetDict
로 해서 datasets로 선언해주는 이유는 기존run_mrc
에서datasets['validation']
처럼 사용하기 때문에 기존의 사용하는 방식을 그대로 유지해주기 위함입니다.run_mrc
함수 안에 있던get_data
를main
함수로 꺼내왔습니다. 기존의 코드 실행 순서는 완전히 동일합니다. 다만,run_mrc안에서 get_data가 먼저 실행
순으로 실행되던 것이get_data실행 후 run_mrc 실행
으로만 변경된 것입니다. 즉, 코드의 위치가 변경되었습니다. 자세한 내용은 아래에서 코드적으로 더 설명합니다.실제 코드
get_data
에서 위 부분을run_mrc
로 옮겨주었습니다.get_data
를 수행한 뒤run_mrc
를 수행하며, 이 때 인자가 조금 늘어난 모습입니다.get_data
함수를 재사용할 수 없어서 해당 함수에서 사용하던 기능을 모두 그대로 불러왔습니다. 재사용할 수 없는 가장 큰 이유는get_data
의 코드 중에서 5번 반복실행되어야 하는 코드와 1번만 실행되는 코드가 같이 위치해있기 때문입니다.inference
여기서 가장 중요하게 생각한 부분은, 최대한의 수정 없이 재사용할 수 있는 부분을 고민하는 것입니다.
postprocess_qa_predictions
함수를 전반적으로 수정해야 한다는 점, fold를 이용할 때와 하지 않을 때를 둘 다 가능하게 해야한다는 점이 꽤 쉽지 않은 난이도를 형성했습니다.따라서, 목표를 코드를 재사용해서 cross하는 방법으로 세웠습니다. 바로, 기존의
nbest_prediction
을 이용하는 것입니다.best_prediction
은 단 하나의 예측만 가지고 있어서 soft vote할 수 없습니다.best_prediction
에는 제일 중요한 logit 값이 없습니다. 결국 hard vote밖에 할 수 없다는 이유.다만, 여기에도 문제가 조금 있습니다.![image](https://user-images.githubusercontent.com/45033215/138614025-fe7236dc-2bef-4290-af92-31619d9585fa.png)
nbest_prediction
을 이용하면 결국 k <= n 인kbest_prediction
으로밖에 결과를 내지 못한다는 점입니다. (운이 안좋으면 아예 kbest_prediction을 못얻을 수도 있습니다)또한, 재사용성이라는 점의 초점을 맞추어, inference를 5번(fold 개수) 해야합니다. 물론 이는 train에서 자동으로 진행되게 설정했으며, 마지막에
combine.py
를 실행해서 최종 결과를 얻을 수 있도록 했습니다.✨ 결과
k = 1
k = 30
추측할 수 있는 경우는 다음과 같습니다.
결론
k fold를 사용하는 것이 무조건 유리한 것으로 보입니다.
개선점
현재는 kfold에서 retrieve를 비효율적으로 사용합니다. 쉽게 예를 들겠습니다. 학생 A, B, C, D, E 총 5명이 있습니다. 도서관에서 책을 빌리는데는 5원이들고 도서관에 책을 반납하는데도 5원이 듭니다. 책을 보는데 1인당 총 10원의 비용이 듭니다. 따라서 전체 인원이 책을 보는데는 50원의 비용이 소모됩니다. 근데 여기서, A가 책을 빌리고 A, B, C, D, E가 이 책을 나눠서 본다음에 E가 반납을 하면 총 10원의 비용이 소모됩니다. 위 예시와 같이, 각 fold마다 retrieve를 독립적으로 하는것이 아니라 retrieve를 하면 이것을 각 fold가 나누어서 처리할 수 있도록 개선해야 될 것입니다.