물론 등수가 전부는 아니지만 엄청나게 반등하니 정말 놀랐고, 지금까지 프로젝트를 위해 했던 시도들이 보상받는 기분이라… 정말 좋았다… 도파민 MAX였다.
프로젝트 소개
ODQA(Open-Domain Question Answering)는 쉽게 말하면 “아무거나 물어봐도 잘 대답해주기”다.
이 프로젝트를 잘 해결할 수 있는 방법은 다음과 같다.
질문에 어울리는 단서(ex. 문서, 문장 등)을 찾는다.
단서를 바탕으로 질문에 대한 답을 찾는다.
보통 전자를 Retriever, 후자를 Reader라고 한다.
점수는 EM(Exact Match), 즉 질문에 대한 예측이 실제 정답과 정확히 일치해야 좋은 점수를 받을 수 있다.
참고용으로 F1도 주어진다. 질문에 대한 예측이 실제 정답과 정확히 일치하지 않고 겹치는 경우, 얼마나 겹치는지에 대한 대략적인 정보를 얻을 수 있다.
KLUE-MRC로 학습된 모든 기학습 가중치를 사용할 수 없으며, 학습용 데이터셋으로 사용할 수 없다.
아마 프로젝트에서 주어진 데이터셋이 KLUE-MRC의 일부라서 그런 것 같다.
프로젝트 진행 과정
24/09/30 ~ 24/10/25
개천절(10/03)부터 한글날(10/09)까지 새로고침 데이(일종의 휴일)이 있었다.
그래서 사실상 4주가 아니라 3주 프로젝트였다.
09/30 ~ 10/02
강의, 어떻게 볼 것인가?
원래 강의를 보면서 프로젝트를 하는 것도 고려를 했는데 그렇게 하지 않았다.
과제랑 강의에서 다루는 내용이 프로젝트 내용과 겹치는 게 많아서 감을 잡고 프로젝트를 시작하는 게 더 좋아 보였다.
근데 강의랑 과제가 참 쉽지 않았다… ㄹㅇ 어려워서 하루에 많아도 2개씩 밖에 못봤던 것 같다.
베이스라인 코드 정리 (N트)
그리고 베이스라인 코드 정리를 미리미리 해두려고 했는데, 코드도 상당히 어려워서 이 작업도 꽤 애먹었다. 결국엔 어떻게 정리를 할지 정리가 되지 않아서 그대로 냅두고 말았다.
10/03 ~ 10/09
강의를 새로 고침
원래 새로고침 데이 들어가기 전에 모두 강의와 과제를 다 보는 것이 목표였는데 난이도 이슈랑 억까 이슈가 겹쳐서 결국 새로고침 데이에도 강의와 과제를 보게 되었다…
팀 아이즈원 첫 공식 정모
그리고 이 기간을 이용해서 처음으로 팀 아이즈원의 정모를 갖게 되었다.
생각보다 키가 크신 팀원 분이 한 분 계셔서 당황했지만, 그럭저럭 보드게임도 하며 즐거운 시간을 보낼 수 있었다.
베이스라인 코드 정리_최종
또 베이스라인 코드 정리를 한번 나름대로 해서 새로운 베이스라인 코드를 다시 만들어보았다.
근데 이게 아무리 생각해도 원래 코드가 너무 정갈해서 내가 건들 때마다 오히려 더 복잡해지는 기분이 들었다.
앞으로 또 베이스라인 코드 정리할 거면 주석 같은 걸 많이 달아야겠다.
10/10 ~ 10/11
베이스라인 코드 이해
새로고침 데이가 끝나고 나서는, 아직 강의를 다 못 보신 캠퍼 + 베이스라인 코드를 이해하지 못하는 캠퍼 분이 계셔서 아쉽지만 바로 프로젝트를 시작하지는 못하였다.
그러다가 다른 캠퍼분들의 베이스라인 코드 이해를 도울 수 있도록 파워포인트로 간단하게 만들고 발표를 하기도 했는데, 파워포인트를 다 완성하지 못한 상태라 좀 아쉬웠다.
10/12 ~ 10/13
(토이)프로젝트 리신
주말동안 대회 홈페이지의 리더보드를 가려주는 익스텐션을 만들어서 생생정보팁에 공유했다.
근데 나중에 알았는데 리더보드만 가리는 게 아니라 내가 제출한 파일의 값도 안 보이더라… 나중에 수정해야지해야지 하다가 결국 못했다.
10/14 ~ 10/17
Streamlit 서버 추가
일단 훈련용 데이터셋이 json이나 csv처럼 인간이 쉽게 볼 수 있는 형식의 파일이 아니라 pyarrow라는 이진 파일이더라.
그래서 데이터 EDA를 하려면 일단 노트북 파일이든 뭐든 켜서 데이터를 불러오고 하나씩 봐야 했는데, 이 과정이 약간 병목이라고 생각했다.
그것때문에 streamlit 서버를 이용해보고자 했다. 어차피 GPU 서버도 서버니까 streamlit 서버를 띄어 두면 모든 팀원이 각자 로컬 환경에서 브라우저를 이용하여 볼 수 있지 않을까 하는 논의가 프로젝트 시작하기 전부터 있기도 했고, 마침 좋겠다고 생각했다.
아쉬운 점: 근데 만든 것까지는 좋았는데 이게 실제로 실험을 위한 가설의 밑바탕이 되었을까라는 의문에는 아직 물음표가 남는 것 같다. 데이터 보면서 뭔가 생각한 건 많은데 이를 활용한 실험이라든지 그런 거는 이번에도 많이 못한 것 같아서 아쉽다.
애초에 이번 프로젝트에서 데이터 증강이나 전처리 쪽은 좀 부실했던 것 같기도 하다. 뭔가 그런 것 보다도 모델 아키텍처를 구현하는 쪽에 시간을 더 많이 투자한 것 같다.
그래서 만들어 놓고도 (적어도 나는) 많은 활용을 하지 못했고, 그렇다 보니까 좀 의미있는 결과로 이어져서 최종 솔루션에 도움이 되지 못한 것 같아 아쉽다. 이게 어느 순간부터는 그냥 자기 만족이 되었던 것 같다.
NLP 오프라인 팀미팅데이
10월 17일에 네이버 스퀘어 역삼 스튜디오에서 또 한번 오프라인으로 팀원들을 만나는 시간을 가질 수 있었다.
수원에서 역삼까지 가려면 1호선 → 4호선 → 2호선을 타야하는데 4호선에 진짜 사람 엄청 많더라.
그 여파로 인해 몸도 마음도 더 빨리 지쳐버렸고 결국 팀미팅데이에는 거의 하루 종일 놀았다 해도 과장이 아니지 않을까 느꼈다.
확실히 놀라고 팀원을 만났을 때랑 코어타임을 같이 하려고 만났을 때랑은 좀 다른 느낌이었던 것 같다.
일단 당장 코드에 생기는 의문을 바로바로 물어보고 답할 수 있는 환경이 좋았다.
또 다른 팀원이 뭘 하고 있는지 볼 수 있으니까 막히는 것 같으면 옆에서 적극적으로 도움을 줄 수 있어서 좋았다.
근데 그만큼 또 잘 떠들기도 해서 약간 일에 집중못할 수도 있는 것 같은 느낌인 게 마이너스 요소이긴 한데, 역시 양날의 검인 것 같다.
난 다른 팀원과도 이야기할 수 있는 시간이 있을 줄 알았는데, 딱히 그런 건 없었고 그냥 대회의실 같은 데에서 팀마다 각개적투하는 느낌이라 아쉽기도 하고 좋기도 했다.
10/18 ~ 10/21
Cross Encoder 구현
Dense Retrieval가 왜인지 몰라도 성능이 너무 안나왔다.
이때쯤 나도 streamlit 서버로 대시보드를 만드는 것에 약간 회의를 느끼고 있어서 거기에 가세하기로 했다.
내가 구현한 건 Cross Encoder였다.
쿼리에 대한 임베딩이랑 문서(Context)에 대한 임베딩을 각각 구하고, 내적으로 유사도를 구하는 것을 Dense Embedding이라고 한다.
Cross Encoder는 그런 식으로 하지 않고, 쿼리와 문서를 함께 묶어 모델에 넘겨주어 유사도 점수를 구한다.
성능은 나쁘지 않았지만, 기본적으로 한 쿼리와 전체 문서에 대해 유사도 점수를 구할 수 있는 Dense Embedding 방식과 달리 Cross Encoder는 그런 식으로 하면 너무 오래걸리기 때문에 미리 후보를 골라 추려야 한다.
후보는 BM25라는 Sparse Embedding 방식으로 구했다.
단점 1: 그러다 보니까 결국 후보를 뽑을 때 정답 문서를 못 뽑으면 Cross Encoder는 정답 문서를 절대 못 찾으므로, 후보를 뽑는 방법론의 고점을 못 넘긴다.
단점 2: 너무 느리다. Sparse Embedding으로 retrieval하면 거의 30초 안에는 다 끝나는 것 같던데 Dense Embedding은 600개 질문에 대한 쿼리 구하는데 3~5분 정도 걸린다. 실제 서빙 환경에서는 좀 치명적이지 않을까.
그래도 일단 성능이 BM25를 그냥 쓰는 것보다, reranker를 달아주는 게 더 좋아서 최종적으로는 채택했다.
10/22 ~ 10/24
아맞다 Reader
남은 시간 3일. 지금까지 생각해보니 Reader를 싸늘하게 방치해두었다.
사실 백 모 캠퍼님께서 Generation-based MRC를 열심히 도전하고 계셨는데 그건 성능이 안 나와서 아쉬웠다.
추후 멘토링에서 물어본 바로는 백 모 캠퍼님께서 구현을 딱히 잘못한 게 없으셨고, 시도해볼 수 있는 건 다 했는데도 성능이 안 나온 것 같아 내가 다 서운하더라.
그래도 여기서 만들어 놓은 LoRA를 나중에 활용할 수 있었으니 그것 자체만 놓고 봐도 나쁘지 않았던 것 같다.
3일이라는 시간이 길다면 길고 짧다면 짧지만, 우리는 좀 짧다고 판단했던 것 같다.
그래서 데이터 전처리나 클리닝과 같이 EDA를 거쳐야 하는 방법들 대신에, 단순히 데이터셋만 추가로 불러와서 concat만 해주면 되는 데이터 증강을 먼저 시도해보기로 했다. 그리고 나서는 모델을 바꿔가면서 퍼포먼스를 측정해보는 걸로.
아쉬운 점 1: 돌이켜 생각해보면 그냥 되든 안되든 데이터 전처리와 클리닝을 EDA를 통해 시도해보는 편이 좋았을 것 같다. 프로젝트 마감 기한이 급하니 대충 쉬운 걸로 성능 올리자는 마인드 자체가 나쁜 건 아니지만, 너무 리더보드 등수에 목맨 것이 아닌가 회고한다. 성장이 목표였다면 만들어 놓은 대시보드를 이용해서 좀 더 논리적으로 판단하면서 모델을 개선하고자 하는게 좋지 않았을까.
아쉬운 점 2: 사실 데이터 증강을 선택한 이유 중 하나에 코드가 너무 개판이었던 게 한 몫하지 않았나 생각하기도 한다. 데이터 전처리 기능 하나 추가하려면 또 끔찍한 코드를 들여다 보고 폭탄 해체하듯 했어야 했는데 이게 피로해서 자연스럽게 멀리 했던 것 같기도 하고… 모듈화의 중요성을 다시금 깨우쳤다.
모델 개선했던 점
timpal0l/mdeberta-v3-base-squad2라는 모델을 써봤는데 validation 점수는 잘 나오는데 public 점수가 잘 안나왔다. 근데 또 F1은 잘나오고?
그래서 의아하며 제출한 파일을 확인해보니 이게 다국어 모델이라 그런지 몰라도 토크나이저가 한글의 조사를 잘 이해하지 못한 것 같더라.
“(인물)가”처럼 “인물”로 끝내야 할 것을 자꾸 조사가 붙어서 나오고 있었다.
이걸 손으로 직접 뗄까 하다가 너무 리더보드 등수에 미친 사람 같아서, 홍 모 캠퍼님의 의견을 적극 수용하여 Mecab()이라는 라이브러리로 조사만 끝에 있으면 떼주는 작업을 했다.
오. 근데 하니까 public 점수가 바로 오르길래 좋았다.
앙상블
마지막날에 이젠 할 수 있는 게 실험밖에 없는 상황에서 GPU만 바쁘고 난 별로 안 바쁜 상황이었다.
그래서 뭐라도 할 겸, 기왕 등수 경쟁하고 있으니까 앙상블 코드를 구현해봤다.
nbest_predictions.json이라는 파일을 이용해서 모델이 예측한 각 span의 점수(확률)을 취합하여 soft voting하는 식으로 작성했다.
이번에는 validation에 대한 예측들도 다 잘 관리를 해놔서 저번보다는 그래도 근거있는 앙상블을 진행할 수 있지 않았나 생각한다.
그래서 validation으로 먼저 성능을 뽑고, 그 성능이 잘 나오면 test로도 앙상블을 진행하여 제출하였다.
가장 성적이 괜찮게 나왔던 방법으로는 가중치를 각자의 validation score에 비례하여 주는 방법이 있었다.
역시 앙상블님께서는 우리를 저버리시 않으셨고 꾸준한 퍼포먼스 향상을 안겨주셨다.
참고: 앙상블이 좋은 이유가 어떤 모델이 잘 못 푸는 문제는 다른 모델이 잘 푸는 경우가 있어서 상호 보완적으로 되기 때문. 따라서 전체 그냥 점수만 놓고 보기보다 어떤 문제를 잘 풀고 못 푸는지에 대한 경향성이 더 중요하다.
validation 점수는 70%씩 나왔던 모델이 public에서는 별로 안 나오길래(64%), 그래도 private에서 오를 거라고 살짝 기대해보긴 했는데, 5등까지 달성할 수 있어서 정말 좋았다.
그리고 최종 제출한 모델이 private에서도 점수가 가장 높은 모델들이었어서 이것도 스스로 대견하다고 생각한다.