← 전체 프로젝트

프로젝트 / 05

한국어 검색 기반 질의응답(Korean NLP RAG)

한국어 질문의 근거를 검색하고 원문에서 답을 추출하는 로컬 질의응답 시스템입니다. 검색 모델과 읽기 문맥을 설계하고, 같은 질문에서 구성별 정확도와 답변 보류 및 처리 비용을 비교했습니다.

개발 기간2026년 6월 (4주간)
PyTorch한국어 NLPRAGFAISSBM25GitHub소스와 문서 ZIP

사용 기술

프로젝트 개요

한국어 질문에 맞는 근거를 찾아 원문에서 답변 구간을 추출하고, 출처와 함께 반환하는 로컬 질의응답 시스템입니다. 사용자 컴퓨터에서 실행하며 한국어 검색과 재정렬, 답 추출을 연결합니다. 검색용 학습과 읽기 문맥의 설계가 답변 품질과 처리 비용에 미치는 영향을 비교했습니다.

단어의 일치를 찾는 검색과, 문장의 의미를 숫자 배열로 바꿔 유사성을 비교하는 벡터 검색을 결합합니다. 검색하기 좋게 나눈 본문 조각인 청크를 후보로 찾고, 질문과 본문을 함께 읽는 재정렬 모델로 순위를 조정합니다. 답 추출 모델은 검색된 근거의 주변 문맥을 읽으며, 답변 조건을 충족하지 못하면 응답을 보류합니다. 검색한 자료를 답변에 활용하는 RAG의 흐름을 따르되, 이 프로젝트는 정해진 문서에서 답변 구간을 선택하는 추출형 질의응답으로 구현했습니다.

핵심 결과

정답 일치율은 답변이 정답과 일치한 질문의 비율이고, 상위 5개 검색 재현율은 검색 결과 다섯 개 안에 정답 전체 구간을 담은 청크가 있는 질문의 비율입니다. 따라서 근거를 잘 찾아도 답을 잘못 추출하거나 보류하면 정답 일치율은 낮을 수 있습니다. 원문에 답이 없도록 만든 ’합성 무응답 질문’에서는 답을 만들어 내지 않고 보류하는지도 별도로 확인했습니다.

정답 일치율
73.25%
답이 있는 동일 501문항에서 62.28% → 73.25%
상위 5개 검색 재현율
96.61%
정답 전체 구간을 찾은 질문 459문항 → 484문항
정답 순증가
+55문항
새 정답 82개, 기존 정답을 놓친 사례 27개
평균 GPU 처리 시간
209.53ms
105.34ms에서 약 1.99배 증가, 모델 적재 제외

비교 구성과 최종 구성을 같은 질문에 대응시켜 평가했습니다. 두 구성은 같은 시스템에서 검색 모델, 청크와 읽기 문맥의 선택을 달리한 실험 조건입니다. 최종 구성은 답변 품질과 검색 성능이 높았지만 처리 시간과 무응답 질문에 잘못 답한 사례도 늘었습니다. 이 변화까지 함께 기록하고 최종 결과를 본 뒤 설정을 다시 조정하지 않았습니다.

그래프의 문자 F1은 답변과 정답에서 겹치는 문자를 기준으로, 필요한 내용을 얼마나 포함했는지와 불필요한 내용을 얼마나 덧붙였는지를 함께 반영합니다. 답이 완전히 일치해야 인정하는 정답 일치율과 달리 부분적으로 맞은 답도 반영하며, 높을수록 정답 문자열에 가깝습니다.

같은 501문항에서 정답 일치율 62.28%에서 73.25%, 문자 F1 70.55%에서 79.94%, 상위 5개 검색 재현율 91.62%에서 96.61%로 변화한 비교
답이 있는 질문 501개를 두 모델에 동일하게 입력한 결과입니다. 검색 재현율은 정답 구간 전체를 포함한 청크가 상위 다섯 개에 있는지 측정합니다.
평균 GPU 처리 시간은 105.34ms에서 209.53ms, 별도 합성 무응답 80문항의 잘못된 답변은 1건에서 4건으로 증가
속도는 전체 581문항을 같은 RTX 3060 Laptop GPU에서 측정한 평균입니다. 무응답 질문 80개는 별도의 합성 평가로, 실제 사용자 질문의 오류율과 구분합니다.
전체 성능 수치와 측정 조건

아래의 답변 제공률은 정답 여부와 관계없이 답을 반환한 비율입니다.

측정 항목 비교 구성 최종 구성
정답 일치율, 답이 있는 501문항 62.28% (312/501) 73.25% (367/501)
한국어 문자 F1, 동일 501문항 70.55% 79.94%
상위 5개 검색 재현율, 동일 501문항 91.62% (459/501) 96.61% (484/501)
답변 제공률, 동일 501문항 86.03% (431/501) 87.82% (440/501)
별도 합성 무응답 80문항의 보류 79/80 (98.75%) 76/80 (95.00%)
동일 합성 80문항의 잘못된 답변 1/80 4/80
전체 581문항 평균 GPU 처리 시간 105.34ms 209.53ms

정답 일치율은 10.98%p, 문자 F1은 9.39%p 높아졌습니다. %p는 두 백분율을 뺀 차이를 나타내는 퍼센트포인트입니다. 평가 문항의 구성이 달라질 때 차이가 얼마나 흔들리는지 살펴보기 위해, 문단과 중복 질문으로 연결된 102개 그룹을 유지한 채 2,000회 다시 뽑아 계산했습니다. 이 재표집에서 얻은 설명적 95% 구간은 정답 일치율 변화 +6.94–+15.26%p, 문자 F1 변화 +5.35–+13.37%p입니다. 기사 간 의존성, 공개 모델의 학습 자료 노출과 개발 과정의 적응적 선택을 모두 반영한 일반적인 통계적 유의성 주장은 아닙니다.

Windows 11과 RTX 3060 Laptop GPU에서 검색부터 답 추출까지 측정했으며 모델 적재 시간은 제외했습니다. 비교 구성과 최종 구성의 지연 시간 중앙값은 103.38ms와 206.98ms, 95백분위 값은 125.02ms와 247.23ms입니다. 중앙값은 측정값을 순서대로 놓았을 때 가운데 값이며, 95백분위는 약 95%의 요청이 그 시간 안에 끝났다는 뜻입니다. 모델 적재에는 각각 15.55초와 19.10초가 걸렸습니다. 모든 장치의 속도나 서버 처리량을 의미하지 않습니다.

문제와 접근 방법

문장 유사도 학습에서 좋은 표현을 얻더라도, 질문으로 긴 본문을 검색할 때 같은 성능을 얻는 것은 아닙니다. 질문과 본문에 맞는 학습 예제와 오답 후보를 구성하고, 검색 순위의 개선이 실제 답변에도 이어지는지 확인했습니다.

검색한 청크에 정답이 있어도 읽기 문맥이 짧으면 필요한 정보를 놓칠 수 있습니다. 반대로 문맥을 늘리면 질문과 관계없는 내용을 답으로 선택할 가능성도 생깁니다. 따라서 검색 모델, 청크, 읽기 문맥과 답변 보류 기준을 나눠 비교하면서 전체 시스템의 품질과 실행 비용을 함께 평가했습니다.

시스템 구조

BM25는 단어의 일치를 중심으로 찾는 검색 방식이고, E5는 질문과 본문의 의미를 벡터로 표현하는 모델입니다. 두 검색기의 결과는 순위를 결합하는 RRF를 거쳐 재정렬 모델로 전달합니다. 아래의 토큰은 모델이 글을 읽는 단위로, 단어나 단어의 일부가 될 수 있어 글자 수와는 다릅니다.

시스템 처리 흐름
  1. 질문 입력

    한국어 질문을 검색용 표현으로 변환

  2. 근거 검색

    BM25와 E5에서 각각 상위 50개 확보

  3. 후보 재정렬

    RRF로 20개 결합 후 질문과 본문을 함께 평가

  4. 원문 문맥 복원

    상위 5개 근거 중심으로 512토큰 구성

  5. 답 추출과 보류

    KoELECTRA로 답 구간을 선택하고 인용 및 기준 검사

구성 요소 역할
multilingual E5와 FAISS 질문과 본문을 구별하는 접두어를 붙이고, 유효 토큰의 표현을 평균 내 길이를 정규화한 벡터를 FAISS 검색 도구로 비교
BM25와 RRF 단어 일치 검색과 벡터 검색의 후보 순위를 결합
Cross-Encoder 질문과 본문을 함께 읽어 후보 순위를 조정
청크와 읽기 문맥 원문 위치를 보존한 192토큰 청크, 32토큰 겹침과 512토큰 문맥
KoELECTRA QA Reader 원문의 답 시작 및 끝 위치를 선택
답변 정책 답이 없다는 점수와의 차이, 임계값, 원문 인용과 명시된 연도를 검사

검색 대상 문서 모음인 지식베이스는 한국어 질의응답 자료 KorQuAD의 검증 자료에서 얻은 고유 문단 960개와 청크 2,264개입니다. 청크의 순서, 식별자, 원문 위치와 파일의 변경 여부를 확인하는 해시를 검사합니다. 답 추출 모델에 전달할 문맥을 구성할 때 정답 위치를 사용하지 않으며, 학습된 모델 가중치를 한 번 메모리에 적재한 뒤 반복 질문에 재사용합니다.

실험과 검증

공식 학습 자료에서 모델을 학습하는 3,002문항과 학습 상태를 확인하는 내부 검증 206문항을 구성했습니다. 질문과 정답 본문에 검색 순위가 높은 오답 후보를 추가하되, 같은 문단과 정답을 포함한 본문 및 검증 대상은 제외했습니다. 이렇게 정답처럼 보이는 오답도 함께 보여 주어 구별하도록 했습니다. 직접 학습한 검색 모델의 내부 검색 재현율은 전체 학습 자료를 1회 학습한 뒤 96.12%에서 100%로 올랐지만, 두 번째 반복에서는 99.03%로 내려가 첫 번째 모델을 유지했습니다.

서비스 구성은 설정을 선택하는 개발 질문 304개와 합성 무응답 80개에서만 선택했습니다. 검색과 청크 구성 여섯 개, 읽기 문맥 구성 네 개를 비교했습니다. 최종 구성은 E5 검색과 원문 문맥 복원을 사용하며 비교 구성과 같은 재정렬 가중치를 사용합니다. 직접 학습한 검색 모델은 비교와 재현을 위한 자료로 남겼습니다. 이처럼 학습에 쓴 자료, 구성을 고른 자료와 최종 성능을 측정한 자료의 역할을 구분했습니다.

중단한 실험과 평가 자료 분리

재정렬 모델은 동일한 비교 모델에서 일반 오답 후보와 검색 상위 오답 후보를 사용한 추가 학습을 비교했습니다. 두 방식 모두 비교 모델의 100% 내부 지표를 넘지 못해 각각 한 번의 학습 후 중단했습니다. 지표가 포화됐고 해당 가중치의 내부 검증 노출 이력도 완전히 확인되지 않아, 오답 후보 구성 방식의 우열을 입증한 결과로 해석하지 않습니다.

답을 반환할지 보류할지 가르는 기준 점수인 임계값의 조합 12개는 공동 품질 기준을 모두 통과하지 못했습니다. 이 실패를 확인한 뒤 별도로 선언한 더 엄격한 아홉 개 조합을 같은 개발 자료에서 비교했고, 비교 구성의 품질과 답변 제공률 및 같은 GPU의 무응답 조건을 유지한 여섯 개 중 하나를 선택했습니다. 비교 예산을 확대한 사실과 실패 기록을 보존했습니다.

최종 501문항은 구성 선택에 사용하지 않은 공개 KorQuAD 기반 프로젝트 평가 자료입니다. 이미 노출된 평가 문항과 개발 기록에서 확인 가능한 학습 문단은 제외하고 문단 및 정규화 질문 그룹을 분리했지만, 개발과 최종 자료의 기사 19개는 겹칩니다. 공식 비공개 시험 자료가 아니며 공개 사전 학습 모델의 과거 자료 노출도 배제하지 못합니다. 검색 모델, 청크와 입력 길이가 함께 바뀌었으므로 성능 차이를 직접 학습 하나의 효과로 설명하지 않습니다.

Windows와 Linux의 분리된 환경에서 구현 검사 112개 중 각각 109개를 통과했고, 과거 선택 자료가 필요한 세 개는 생략했습니다. 최종 실행 두 개의 581개 질문 식별자와 고정 해시, 저장된 예측을 별도로 대조하고 지표를 재계산했습니다. 이 수치 감사와 새로운 모델 추론은 구분합니다.

실행 환경과 공개 다운로드 확인 범위

동일한 컴퓨터의 Windows와 Linux CPU 환경에서 개발 질문 두 개를 각각 두 번 실행한 총 여덟 번의 답변, 판단과 인용이 일치했습니다. 별도 Windows CPU 환경의 합성 최종 80문항 전체 실행에서도 GPU와 같은 네 질문에서 잘못 답했고 응답 또는 보류 판단의 불일치는 없었습니다. 답이 있는 501문항 전체의 CPU 성능 평가나 외부 제3자의 독립 검토를 뜻하지 않습니다.

Python이 없는 Windows와 Linux에서 프로젝트 내부에 Python 3.12와 CPU 의존성을 자동 준비하는 과정도 확인했습니다. 모델과 인덱스는 공개 명세의 크기와 해시를 검사하며, 공식 KorQuAD 다운로드에서 원문과 청크를 다시 구성해 동일한 파일 해시를 확인합니다. 검증 환경 분리에는 Docker를 사용했고 일반 실행에는 필요하지 않습니다.

공개 소스 ZIP을 새 폴더에 내려받아 기존 캐시와 인증정보 없이 기본 CPU 설치를 수행했습니다. 모델 세 개와 공식 자료 준비에 약 3분 43초가 걸렸고, 반복 질문 두 세션에서 판단 일치와 오프라인 재사용을 확인했습니다. 예시 질문은 보류됐으므로 설치와 실행 검증으로 기록합니다. GitHub 자동 검증은 계정의 Actions 비활성화로 실행되지 않았으며 공개 다운로드와 로컬 검사는 별도로 확인했습니다.

한계와 해석

최종 구성은 답이 있는 질문 61개를 보류하고 73개에 정확히 일치하지 않은 답을 반환했습니다. 정답 구간을 포함한 청크가 상위 다섯 개에 있어도 보류 51건과 오답 68건이 남았습니다. 원문에서 답변 문자열을 찾았다는 사실만으로 질문의 대상과 관계에 맞는 답이라고 보장할 수는 없습니다.

합성 무응답 질문의 잘못된 답변은 1건에서 4건으로 증가했습니다. 비교 구성에서 발생한 가상 관계 오답 한 건을 바로잡았지만, 가상 개체의 정체를 묻는 질문에서 새 오답 네 건이 발생했습니다. 개발 단계의 무응답 조건이 최종 자료에서도 유지된 것은 아니며, 이 결과를 보고 임계값을 다시 조정하지 않았습니다.

답변 기준 점수는 정답 확률로 보정한 값이 아닙니다. 합성 자료의 오류율을 실제 질문 전반으로 일반화하지 않으며, 최신 웹 정보와 임의 문서 업로드 및 대화 이력은 구현 범위에 포함하지 않습니다. 이 프로젝트는 근거 검색과 답 추출, 평가 경계 및 재현 가능한 제공을 보여 주는 사례입니다.

실행 방법

소스 ZIP을 내려받아 압축을 풀면 Windows에서는 launch-rag.bat으로 실행할 수 있습니다. 최초 실행에는 Python과 의존성 및 모델을 내려받을 인터넷이 필요하며, 준비한 뒤에는 Docker와 API 키, GPU 없이 로컬에서 질문을 반복할 수 있습니다.

.\launch-rag.bat --doctor
.\launch-rag.bat --question "직지심체요절은 언제 인쇄되었나요?" --json

Linux에서는 bash launch-rag.sh에 같은 인자를 전달합니다. 단일 질문과 일괄 처리, JSON 출력 및 검색 근거만 확인하는 옵션을 제공합니다. 전체 평가 명령과 오류 해결 방법은 한국어 실행 안내와 소스 ZIP의 같은 문서에서 확인할 수 있습니다.

코드와 자료

실행과 최종 성능 검증은 저장소 또는 소스 ZIP의 안내를 기준으로 진행합니다. 연결한 개발 기록에는 한국어 모델 학습과 검색 구현의 근거를 보존했습니다. 학습 재현용 ZIP은 비교용 문장 유사도 검색 모델과 추가 학습한 첫 번째 후보, 학습 설정과 출처 기록을 담습니다. 최종 서비스는 E5와 별도로 준비하는 가중치를 사용하므로 일반 실행에 이 ZIP은 필요하지 않습니다.

GitHub 문서는 영어를 기본으로 제공하며 한국어 설명도 함께 제공합니다. 공개 소스에는 학습, 선택, 평가 및 실행 코드와 최종 예측, 실패 기록을 포함하고 개인 작업 기록은 제외했습니다. GitHub 연결이 어려우면 소스 ZIP의 README.ko.md, docs와 reports 폴더에서 같은 자료를 확인할 수 있습니다.

소스와 문서 ZIP 다운로드GitHub에서 소스 코드 보기