월요일에 시작해서 목요일까지 우다다다 달려왔던 IR 대회. 짧고 굵게 끝나고 짧은 시간 탓에 블로그에 제대로 정리할 시간이 없었다. 어떤 시도들을 했고, 어떤 아이디어를 가지고 진행했는지 흐름을 되짚어보려고 한다.
1. 대회 소개
- "질문"이 들어오면, 질문과 연관된 "적절한 문서"를 찾고, 그 문서를 참조해서 적절한 답변을 생성한다.
- 대회에서는 답변을 확인하지 않고, 답변을 위해 참조한 문서 3개(top k)를 뽑아서, 이 3개 문서가 잘 추출됐는지로 평가한다.
- 임베딩 생성 모델, 검색 엔진, LLM을 활용할 수 있다.
- 학습 데이터로 주어지는 문서와 쿼리 모두 jsonl 형태.
2. 베이스라인 익히기
- 베이스라인을 보면 한국어 임베딩 모델로 "snunlp/KR-SBERT-V40K-klueNLI-augSTS"를 사용하고, 엘라스틱서치에 문서들을 인덱스를 생성한 뒤에 sparse_retrieve, dense_retrieve를 이용해서 검색한다.
- 검색이 이뤄지는 과정: eval 파일의 라인을 한 줄씩 읽어서 llm에 보낸다. llm한테 처음에 보낼 때에는 persona_function_calling을 써서 과학과 관련된 질문인지 아닌지를 먼저 판단하게 하고, 과학과 관련된 질문인 경우에는 tools에 지정한대로 검색을 위한 쿼리를 작성한다. standalone_query에 작성해서 응답값에 넣어주고, 이 standalone_query를 sparse / dense 함수에 넣어 관련 문서를 검색해준다. 검색결과로 문서를 뽑은 뒤에 이것과 함께 다시 persona_qa로 llm에게 질문을 던진다. (주어진 레퍼런스를 활용해서 답변을 생성하라고). 만약 검색이 필요하지 않은 경우라면 그냥 대답해준다. 답변한 것들을 모아 output으로 저장해준다.
정리하면, 쿼리 -> LLM 보내 -> 키워드 뽑아(standalone_query) -> 키워드로 엘라스틱서치에서 문서 검색 -> 문서 내용 가져와서 LLM에 참조 -> 답변 생성
3. 초기 아이디어
- 베이스라인을 파악한 뒤에 시도해본 것은 먼저 프롬프트를 바꿔보는 것이였다. 가장 빠르게 시도해볼 수 있는 것이였고.
persona_function_calling을 과학 관련 질문과 아닌 것을 좀 더 명확하게, 잘 구분할 수 있도록 바꾸어 보았다. 그리고 tools에서도 standalone_query를 작성하는 법에 대해서 좀 더 구체적으로 만들어 보았다. tools는 영어로 되어 있었는데 한글로 바꿔보기도 했다.
자잘자잘하게 올렸던 기록들. 처음에 프롬프트를 바꿨던 게 유의미한 성능 향상이 있었다.
- 키워드로 엘라스틱서치에서 문서 검색 부분 바꾸기 :
베이스라인에서는 sparse_retrieve만 사용해서 검색 결과를 추출하고 있었다. 그래서 이거 대신에 dense_retrieve를 사용해봤더니 결과가 더 낮았다. dense 리트리브는 벡터 유사도를 이용한 검색 방법이고 sparse는 단어의 출현 여부를 고려해서 검색하는 방법이다. 두 개를 합치는 방식을 쓰면 어떨까 싶어서 hybrid retrieve를 만들었다.
hybrid retrieve는 먼저 sparse로 후보군을 추출하고(sparse가 성능이 더 좋게 나왔으니까 이걸로 일단 후보가 될 수 있는 문서 10개를 뽑아오면 더 유사한 문서들이 10개가 뽑힐 거라고 생각했다.) 그 다음에 dense로 추출된 문서들의 dense 유사도를 계산해서 sparse 점수 0.4와 dense 점수 0.6을 넣어 총 점수를 매겼다. 그리고 점수 순서로 재정렬해서 상위 3개 문서만 선택하는 방법이다. 이렇게 hybrid를 써보니까 0.45라는 애매한 점수가 나왔는데, 베이스라인인 sparse에서 얻은 0.42와 dense 0.37 보다는 어쨌든 높았다. 그래서 hybrid를 쓰는 건 유지하되, dense의 성능이 높아지면 hybrid 성능도 더 높아질 수 있겠다고 생각했다.
----> dnese 성능을 높여보자 = 어떻게? 임베딩 모델을 더 좋은 걸 쓰면 문서와 쿼리 사이의 유사도를 더 정확하게 파악할 수 있다. 임베딩 모델을 바꿔서 dense 성능을 근본적으로 높여보자!
* 여기서 더 해보면 좋았을 것 : sparse와 dense의 비율을 좀 더 실험적으로 최적화해볼껄. 처음에 뽑는 문서양도 10개가 아닌 늘리거나 줄여볼껄. dense 검색에서 유사도를 계산할 때 다른 방식을 써봤어도 좋았을 것 같다. (관련글)
4. 임베딩 모델 바꾸기
임베딩 모델은 텍스트를 고정된 크기의 숫자 벡터로 변환해주는 모델이다. 여러 가지 임베딩 모델을 사용해봤는데, 일단 베이스라인에 주어졌던 snunlp/KR-SBERT-V40K-klueNLI-augSTS부터 시작해서, 5개 정도 써본 것 같다.
BM-K/KoSimCSE-bert -> 0.56
OpenAI text-embedding-3-small -> 0.64
Solar embedding -> 0.8 (다른 분은 같은 솔라 임베딩 써서 0.89 찍음)
klue/bert-base -> 0.39,
BAAI/bge-large-en-v1.5 -> 0.37
오픈ai, solar가 성능이 좋았고(solar가 압도적으로 좋았음..) 나머지는 문서 뽑는 게 시원찮았다.
처음에 solar 임베딩이 엘라스틱서치에 안들어가서(솔라 임베딩은 차원수가 4096이고, 엘라스틱서치에는 2048 이하만 들어간다고) 차원축소나 반으로 잘라서 사용하면 손실되는 정보가 있어서 제대로 결과가 안나올 것 같아 패스했다. 그런데 멘토링 시간에 멘토님이 솔라 임베딩을 강력 추천(?) 해주시길래 차원축소를 시도해서 넣어봤는데 아무리 해도 안되서.. 결국 앞에 2048을 넣는 걸로 잘라서 사용.. 그랬더니 결과가 또 이상하게 나오는거다?! 결국 솔라 임베딩에 성공한 팀원에서 임베딩한 문서와 쿼리 파일을 pkl 형태로 받아서 그걸로 사용했다.
5. 쿼리 3개씩 날려보기
gpt를 사용해서 원본 질의를 3개의 비슷한 질문으로 생성하게 했다. (동의어/유사어를 활용한 변형, 상위개념/하위개념을 포함한 변형, 인과관계 키워드를 추가한 변형) 이렇게 3개의 쿼리를 추가로 만들어서 하이브리드 리트리버에 추가한다. 하이브리드 리트리브는 각 질의에 따라 10개씩 문서를 뽑고, 그렇게 뽑힌 문서들 중에서 점수가 가장 높은 상위 3개 문서를 추출하는 방식이다.
점수가 이렇게 해서 조금씩이나마 쭉쭉 오를 때 기분이 참 좋았다...
6. 시도는 했지만 결과가 좋지 않았던 방법들
- 오픈ai 임베딩으로 임배딩한 문서들을 10개 뽑고 그걸로 LLM에게 질문을 보고 가장 적절한 문서 3개를 뽑아달라고 한 것 -> 이거 점수가 생각보다 안좋게 나왔는데, 과학관련 질문인지 아닌지를 너무 빡세게 판별시켜서 그런 것 같다; 애초에 topk가 나온 문서가 많지 않았다... 이걸 좀 더 높일 수는 있었는데 LLM 쓰는 건 치트키같은 거고, AI 엔지니어링 관점에서 다른 시도들을 많이 해보는 게 좋겠다는 멘토님의 의견이 있어서 더이상 진행하지 않았다.
- sparse, dense 순서 바꾸는 방법:
내가 쓰던 hybrid 함수가 sparse로 먼저 10개 뽑고 dense가 들어가서 점수를 분배하는 방식이였는데, 이 방식에서 순서를 바꿔봤다.
처음부터 dense로 문서를 10개 뽑고, sparse 검색으로 키워드 매칭이 좋은 문서 ID를 뽑는다. dense 검색으로 뽑힌 문서 중에서 sparse에서도 좋은 점수를 받은 문서만 남기고 나머지 제거, 필터링된 문서 중에서는 dense 스코어가 가장 높은 3개만 뽑는 방식이다. 그런데 sparse로 너무 많이 걸러져서 3개가 다 안뽑히는 경우들이 생겼다. 그래서 10개가 아니라 20개씩 뽑게 했고, sparse의 threshold도 0.5, 0.7 높여가면서 진행해봤다. 그런데 dense만 했을 때 0.4727, dense -> sparse로 해도 0.4727.. 성능도 거지같은데다가 심지어 dense만 한 것에 비해 아무런 변화가 없었다. 이건 dense에게 전폭적(?)인 지원을 한 방식인데 다시 하이브리드로 복귀하게된 계기가 됨..
6. 이후에 더 하려고 했지만 못한 것
솔라 임베딩으로 나온 점수가 높고, 오픈ai 임베딩도 점수가 나름 높으니 두 임베딩으로 계산해서 뽑은 top k 개 문서들을 10개씩 취합해서, 거기서 겹치는 것들을 뽑고 키워드 겹치는 걸로 리랭킹하는 방식을 생각해봤다. 하지만 놀랍게도(?) 10개 + 10개 = 20개의 문서들 중에서 겹치는 문서가 생각보다 많지 않았고, 심지어 다수의 쿼리에서 겹치는 문서가 하나도 안나온 것들이 있었다. 이 방식을 좀더 파보는 것도 재밌을 것(?) 같은데.. 이걸 연구하던 중 대회 시간이 마무리됐다.
신기한 점!
MAP의 최고 숫자는 0.8947로 똑같고 MRR도 두 팀이 0.8955로 똑같다. 발표가 굉장히 흥미로울 것 같다. 다들 솔라 임베딩을 쓴건지..?
'공부방 > Upstage AI Lab 4기' 카테고리의 다른 글
데이터셋 분할 방법 (0) | 2024.12.23 |
---|---|
패스트캠퍼스 Upstage AI Lab 부트캠프 4기, IR 경진대회(feat. RAG) (0) | 2024.12.23 |
[IR] 벡터 유사도를 계산하는 다양한 방법들 (0) | 2024.12.19 |
[IR] 클로드로 프로젝트 관련 질문 싹다 몰아넣기 (0) | 2024.12.19 |
[IR] 지금까지 했던 것, 그리고 하고 있는 것들 (0) | 2024.12.18 |