오늘 오전에는 Korg X2/X3 매뉴얼을 좀 더 편하게 찾아보기 위한 작은 프로그램을 만들었다(원본 글). 가지고 있는 매뉴얼 PDF는 오래된 스캔 자료라서 텍스트를 직접 검색할 수 없다. 우선 OCR을 거쳐 각 페이지의 글자를 추출하고, 어느 매뉴얼의 몇 번째 PDF 페이지에서 나온 글인지 알 수 있도록 JSON 파일로 정리하였다. 여기에 Ollama에서 로컬로 실행하는 qwen3:1.7b를 이용하여 한국어 질문을 검색에 적합한 영어 표현으로 바꾸고, 관련성이 높은 매뉴얼 페이지를 찾아주는 기능을 붙였다. 검색된 페이지는 원래 PDF의 모습 그대로 화면에서 볼 수 있게 하였다.
처음에는 검색된 내용을 다시 언어모델에 넘겨 질문에 대한 답까지 만들어 보기도 했다. 그런데 여기에서 작은 문제가 발견되었다. 검색된 원문에는 없는 용어나 수치를 언어모델이 그럴듯하게 만들어 답에 섞는 일이 있었다. 오래된 신시사이저의 사용법을 알아보려고 매뉴얼을 찾는 것인데, 없는 기능을 AI가 만들어내면 곤란하다. 그래서 AI가 답을 대신 써 주게 하는 것보다 관련된 원문 페이지를 잘 찾아서 내가 직접 읽는 것이 이 용도에는 더 안전하겠다는 생각이 들었다.
그렇게 해서 오전 작업의 결과물은 거창한 AI 챗봇이라기보다는 일종의 지능형 매뉴얼 검색기에 가까워졌다. 한국어로 질문하면 관련성이 높은 페이지 몇 개를 제시하고, 그중 하나를 선택하면 실제 매뉴얼 페이지를 보여준다. Basic Guide와 Reference Guide의 목차를 이용해서 직접 찾아 들어갈 수도 있다. 인터넷 연결도 필요 없고 API 사용료도 들지 않는다. 드럼 키트의 편집 위치나 snare drum의 pitch 조정처럼 구체적인 질문에는 꽤 쓸 만한 페이지를 찾아 주었다. 여기까지만 해도 처음 원했던 것은 거의 이루어진 셈이었다.
그런데 오후가 되자 욕심이 생겼다. 예를 들어 “왼손으로는 베이스, 오른손으로는 피아노를 연주하려면 어떻게 하나?”와 같은 질문을 생각해 보았다. 사람이라면 이것이 건반을 두 영역으로 나누는 split 설정에 관한 질문이라는 것을 쉽게 알아듣는다. Korg X2의 구조를 조금 아는 사람이라면 Combination에서 각 Timbre의 Key Window를 설정하는 부분을 찾아야 한다는 것도 짐작할 수 있다. 그러나 작은 언어모델과 단순한 검색의 조합은 반드시 그렇게 생각하지 않았다.
여기에서 오후의 삽질이 시작되었다. 요즘 문서 기반 AI를 이야기하면 빠지지 않고 등장하는 것이 RAG(Retrieval-Augmented Generation), 즉 검색 증강 생성이다. 오전에 만든 프로그램도 넓게 보면 검색한 원문을 언어모델과 연결한다는 점에서 아주 단순한 RAG라고 할 수 있다. 그렇다면 조금 더 제대로 된 RAG 방식을 적용하면 검색 성능이 좋아지지 않을까 하는 생각이 들었다.
우선 페이지 전체를 검색 단위로 삼는 것이 문제일 수 있다고 생각하여 매뉴얼의 OCR 텍스트를 작은 덩어리, 즉 chunk로 나누었다. Basic Guide와 Reference Guide를 합쳐 모두 609개의 chunk가 만들어졌다. 결과를 직접 살펴보니 나누어진 모양 자체는 제법 괜찮았다. Split과 관련된 Key Window 설명도 적절한 덩어리로 잡혔고, snare drum의 Tune이나 tempo 설정에 관한 설명도 잘 나뉘어 있었다.
다음에는 각 chunk의 의미를 숫자로 표현하는 embedding을 만들었다. Ollama에서 nomic-embed-text 모델을 사용하였다. 609개의 chunk를 모두 embedding하는 데 내 노트북에서 약 249초가 걸렸다. 질문도 같은 방법으로 벡터로 만들면 단어가 정확하게 일치하지 않더라도 의미가 비슷한 설명을 찾을 수 있을 것이라고 기대하였다.
여기에 CrossEncoder라는 것도 동원하였다. embedding 검색으로 우선 후보를 뽑고, 질문과 각각의 후보를 다시 함께 비교하여 관련성을 좀 더 정밀하게 평가하는 reranking 방식이다. 문서 검색에서는 이미 널리 사용되는 전형적인 2단계 구조라고 한다. 처음에는 609개 chunk를 전부 CrossEncoder로 비교해 보았는데 너무 느렸고, 그래서 embedding으로 상위 20개 후보를 먼저 고른 뒤 그 후보만 다시 순위를 매기는 방식으로 바꾸었다. 어느새 오래된 신시사이저 매뉴얼을 좀 편하게 읽겠다는 일이 작은 정보검색 실험으로 변하고 있었다.
결과는 흥미로웠다. Snare pitch나 tempo처럼 질문과 매뉴얼의 용어가 비교적 잘 대응하는 경우에는 아주 잘 작동하였다. 정답에 해당하는 chunk가 embedding 검색에서 처음부터 1위에 들어왔고 reranking을 거친 뒤에도 1위를 유지하였다. 하지만 정작 개선하고 싶었던 split 질문은 그렇지 않았다. 관련된 Key Window chunk 하나가 embedding 검색 후보 16위에 겨우 들어왔고 reranking 뒤에는 5위가 되었다. 더 직접적으로 관련된 다른 chunk는 상위 20개 후보에도 들어오지 못했다. 엉뚱한 effect 관련 설명이 최종 1위를 차지하는 경우도 있었다.
검색 한 번에는 3초 남짓이 걸렸다. 오전에 만든 단순한 검색보다 구조는 훨씬 복잡해졌지만 결과가 그만큼 좋아졌다고 하기는 어려웠다. “왼손은 베이스, 오른손은 피아노”라는 일상적인 표현과 “Combination mode의 Timbre별 Key Window”라는 Korg X2의 전문 용어 사이에는 상당한 의미의 간격이 있다. 문서를 잘게 나누고 벡터를 만들고 순위를 다시 매긴다고 해서 그 간격이 저절로 사라지는 것은 아니었다.
그러다 문득 내가 왜 이 일을 하고 있는지를 다시 생각하게 되었다. 내가 필요한 것은 Korg X2에 관한 질문이라면 무엇이든 대답하는 인공지능이 아니다. 오래된 매뉴얼 두 권에서 내가 읽어야 할 페이지를 빨리 찾아주는 도구가 필요했을 뿐이다. 그렇게 생각하고 나니 오전에 만든 단순한 프로그램이 다시 달리 보였다.
질문을 입력하면 관련성이 높은 페이지 세 개 정도가 나온다. 그중 하나를 선택하여 원본 매뉴얼을 읽고, 설명이 앞뒤로 이어진다면 화살표를 눌러 한 페이지씩 넘겨 보면 된다. 결국 오후 늦게 프로그램에 추가한 가장 유용한 기능은 복잡한 RAG 기법이 아니라 PDF 페이지를 앞뒤로 넘기는 좌우 화살표였다. PDF를 작게 만드는 별도의 버튼도 처음에는 만들어 보았지만 그것조차 필요 없었다. 브라우저 창의 폭을 조절하면 PDF도 선명한 상태로 따라서 줄어든다. 몇 시간 동안 embedding, chunking, reranking을 돌아다닌 뒤 도착한 곳은 결국 책에서 관련 페이지를 찾아 앞뒤로 넘겨 읽는 방식이었다.
오늘 오후의 경험에서 얻은 교훈은 RAG나 embedding의 원리를 조금 더 알게 되었다는 것만은 아니다. 오히려 이미 잘 알려진 문제를 너무 처음부터 다시 풀려고 했다는 생각이 들었다. 문서 검색에는 오랫동안 사용되어 온 방법이 있고, embedding 검색과 reranking 역시 각각 어디에서 유용하고 어떤 한계를 갖는지 많은 사람이 이미 경험하였다. 그런데 AI가 코드를 척척 만들어 주니 “이것이 잘 안 되면 이것도 붙여 보자”는 식으로 하나씩 직접 시험하게 되었고, 어느 순간 남들이 이미 겪은 시행착오를 작은 Korg 매뉴얼 두 권을 가지고 다시 재현하고 있었다.
생각해 보면 AI를 이용한 프로그래밍에서 이것은 꽤 조심해야 할 점이다. 예전에는 새로운 방법을 시험하려면 코드를 작성하는 비용이 컸기 때문에 시작하기 전에 방법을 충분히 조사할 수밖에 없었다. 지금은 AI에게 부탁하면 몇 분 안에 다음 실험을 위한 코드가 나온다. 구현 비용이 낮아진 덕분에 오히려 충분히 조사하지 않고 여러 방법을 차례로 시험해 보는 일이 쉬워졌다. 코드를 빨리 만들 수 있다는 것과 문제를 빨리 해결한다는 것은 같은 뜻이 아니다.
다음에는 순서를 조금 바꾸어야겠다. 먼저 내가 풀려는 문제가 이미 어떤 이름으로 불리는지 알아보고, 그 문제에 널리 쓰이는 가장 단순한 방법이 무엇인지 찾아보는 것이 먼저다. 그것을 구현하여 실제로 써 본 뒤 부족한 점이 확인되었을 때에만 다음 단계를 추가하면 된다. AI 시대에는 코드를 만드는 비용이 크게 낮아졌다. 그래서 역설적으로 무엇을 더 만들 것인가보다 무엇을 만들지 않을 것인가를 판단하는 일이 더 중요해졌는지도 모르겠다.
609개의 chunk를 embedding하느라 노트북이 249초 동안 열심히 일한 것은 아마 그것을 배우기 위한 수업료였을 것이다. 다행히 API 사용료는 들지 않았다. 전기료는 조금 들었겠지만.
댓글 없음:
댓글 쓰기