개요
26~29편은 어떤 모델을 고를 것인가의 이야기였다. 이번 차시부터 질문이 바뀐다.
모델이 모르는 것을, 어떻게 알게 할까?
답은 두 갈래다. 하나는 모델을 다시 학습시키는 것 — 29편에서 본 파인튜닝과 LoRA가 여기 속한다. 다른 하나는 모델 밖에 지식을 두고, 필요할 때 꺼내서 같이 넣어주는 것이다. 이번 편의 주제는 두 번째다.
두 번째가 왜 필요한지는 한 문장으로 설명된다. 학습은 비싸고 느린데, 세상은 매일 바뀐다. 어제 발표된 사실을 오늘 답하게 하려고 매번 모델을 다시 학습시킬 수는 없다.
이 방식을 RAG(Retrieval-augmented Generation, 검색증강생성) 라 부른다. 20편에서 환각을 다루며 한 문단으로 지나갔던 그 이야기의 확장판이다. 다만 RAG를 제대로 이해하려면 검색(Retrieval) 부터 봐야 해서, 이번 글은 검색기 쪽에 무게를 실었다.
앞부분에는 밀린 숙제를 하나 붙인다. 16편에서 InstructGPT까지 정리하고 끝냈는데, 그 뒤에 나온 방식들이 이번 차시 앞머리에 딸려 나왔다.
교육 자료는 대외비라 슬라이드 이미지나 예제 데이터는 싣지 않는다.
1. 밀린 숙제 — 16편 이후에 나온 것들
16편에서 지시 학습(사람이 쓴 지시문-응답 쌍으로 파인튜닝)과 선호 학습(사람이 고른 더 나은 답을 학습)까지 정리했다. InstructGPT의 3단계 — 지시 학습 → 보상 모델 학습 → 강화 학습 — 가 결론이었다.
그런데 그게 끝이 아니었다. 이 3단계에는 구조적인 약점이 있고, 그 약점을 메우려는 시도가 계속 나왔다.
약점 ① 리워드 해킹
보상 모델은 사람의 선호를 흉내 낸 점수 매기는 기계다. 문제는 학습되는 모델이 사람을 만족시키는 게 아니라 그 기계를 만족시키는 쪽으로 최적화된다는 것이다.
강화학습 쪽에서는 오래된 문제다. 보트 경주 게임에서 점수를 최대화하라고 시켰더니, 결승선으로 안 가고 점수 아이템이 재생성되는 구석을 뱅뱅 도는 에이전트가 나온 사례가 유명하다. 목표는 “경주 우승”이었는데 보상은 “점수”였고, 에이전트는 정확히 보상만 챙겼다.
언어모델에서는 이게 이렇게 나타난다.
정답 여부와 관계없이 생산적이고 도움 되어 보이는 문장을 만들어낸다.
그럴듯한 어투, 적당한 길이, 자신감 있는 단정. 보상 모델이 좋아하는 특징들이다. 사실인지 아닌지는 보상 모델도 잘 모른다. 환각(Hallucination)이 여기서 자란다.
약점 ② 보상 모델 과최적화
더 곤란한 건 이게 어느 정도까지는 잘 작동한다는 점이다. 그래서 눈치채기 어렵다.
강화학습을 오래 돌릴수록 보상 모델이 매기는 점수는 계속 올라간다. 그런데 실제 사람이 평가한 선호도는 어느 지점에서 꺾여서 내려간다. 두 곡선이 중간에 갈라진다.
지표는 계속 좋아지는데 물건은 나빠지고 있다. 지표를 보고 있으면 절대 못 잡는다.
원인은 하나다. 측정하는 것과 원하는 것이 다르기 때문이다. 그런데 측정값이 계속 오르고 있으면 뭔가 잘못됐다고 의심할 이유가 없다. 그래서 발견이 늦는다.
대안 ① DPO — 강화학습을 빼버리자
DPO(Direct Preference Optimization, 직접 선호 최적화) 의 발상은 단순하다.
기존 RLHF는 [선호 데이터 → 보상 모델 학습 → 그 보상으로 강화학습] 이라는 3단이었다. 여기서 가운데를 없앤다. 선호 데이터로 언어모델의 확률 분포를 직접 조정한다.
- 사람이 고른 답이 나올 확률은 올린다
- 버려진 답이 나올 확률은 내린다
보상 모델도, PPO 같은 강화학습 알고리즘도 필요 없다. 계산량이 줄고 학습이 훨씬 안정적이다. 대신 사람이 만든 선호 데이터는 여전히 필요하다. 사람 손을 줄인 게 아니라 기계 부품을 줄인 것이다.
대안 ② RLVR — 채점 가능한 문제는 사람이 필요 없다
RLVR(Reinforcement Learning with Verifiable Reward, 검증 가능한 보상 기반 강화학습) 은 다른 쪽을 건드린다.
수학 문제나 코딩 문제를 생각해보자. 답이 맞았는지 사람 없이 자동으로 채점할 수 있다. 그러면 보상 모델이라는 근사치가 아예 필요 없다.
| 상황 | 보상 $r$ |
|---|---|
| 정답 | 1 |
| 오답 | 0 |
이렇게 끝이다. 보상이 근사치가 아니라 사실이니까 리워드 해킹이 원천적으로 어렵다. 대신 적용 범위가 좁다. “이 글을 감동적으로 써줘”는 자동 채점이 안 된다.
| 방식 | 보상을 어디서 | 강화학습 | 사람 손 | 약점 |
|---|---|---|---|---|
| RLHF | 사람 선호로 학습한 보상 모델 | 필요 (PPO 등) | 선호 데이터 수집 | 리워드 해킹, 과최적화 |
| DPO | 선호 데이터에서 직접 | 없음 | 선호 데이터 수집 | 선호 데이터 품질에 그대로 종속 |
| RLVR | 자동 채점기 | 필요 | 거의 없음 | 채점 가능한 문제에만 |
셋의 차이는 “사람의 선호를 무엇으로 대신할 것인가” 한 줄로 정리된다. RLHF는 보상 모델로, DPO는 데이터 그 자체로, RLVR은 채점기로 대신한다.
2. 검색증강 언어모델 — 파라미터 밖에 지식을 두는 것
여기서부터가 이번 차시의 본론이다.
거대 언어모델은 학습한 지식을 전부 파라미터 안에 넣어둔다. 이걸 파라미터 지식(Parametric Knowledge) 이라 부른다. 문제는 이 방식이 네 군데에서 무너진다는 것이다.
무너지는 네 지점
| 문제 | 무슨 뜻인가 |
|---|---|
| 긴 꼬리 지식 | 학습 데이터에 자주 나온 것만 잘 기억한다. 드문 사실은 어설프게 안다 |
| 지식 갱신 | 어제 바뀐 사실을 반영하려면 다시 학습해야 한다. 비싸고 느리다 |
| 출처 검증 | 왜 그렇게 답했는지 추적이 안 된다. 근거를 댈 수 없다 |
| 사내 보안 | 회사 내부 문서를 학습시키면 모델을 통해 새어 나갈 위험이 생긴다 |
특히 첫 번째가 흥미로웠다. 모델이 클수록 전반적인 정확도는 올라가는데, 드문 사실에 대한 정확도는 크기를 키워도 잘 안 오른다. 반대로 검색을 붙이면 드문 사실에서 효과가 크고, 이미 잘 아는 흔한 사실에서는 오히려 방해가 되기도 한다.
검색은 모델의 지식 공백을 메우는 도구지, 모델을 통째로 대체하는 도구가 아니다.
구성요소 네 개
검색증강 언어모델은 부품 네 개로 이루어진다.
| 부품 | 정체 | 헷갈리기 쉬운 점 |
|---|---|---|
| Datastore | 가공되지 않은 대규모 텍스트 뭉치 | 라벨링된 데이터셋도, 지식 그래프 같은 구조화 데이터도 아니다 |
| Query | 검색용 질의 | 사용자가 모델에 던진 질문과 같을 필요가 없다 |
| Index | 빨리 찾기 위해 미리 만들어둔 색인 | 검색 방식마다 인덱스 만드는 법이 다르다 |
| LM | 찾아온 문서를 읽고 답을 쓰는 언어모델 | 검색 결과를 그대로 뱉는 게 아니라 다시 쓴다 |
Query가 사용자 질문과 다를 수 있다는 게 처음엔 이상했는데, 생각해보면 당연하다. 사용자가 “아까 그거 어떻게 됐어?”라고 물으면 그 문장 그대로 검색해봐야 아무것도 안 나온다. 검색에 쓸 문장은 따로 만들어야 한다.
더 쉬운 비유로
조회(Lookup): 열쇠 번호를 알아야 사물함이 열린다 검색(Retrieval): “파란 가방 넣어둔 칸”이라고 말하면 비슷한 걸 찾아준다
Ducktopia에서 방 정보를 Redis에 넣고 꺼내는 함수를 만든 적이 있다. getRedisRoomInfo(key) — 키가 정확히 일치해야만 값이 나온다. 키가 한 글자만 달라도 결과는 없음이다. 그게 조회다.
검색은 다르다. 질문과 문서가 한 글자도 안 겹쳐도 의미가 가까우면 찾아와야 한다. 도서관 사서한테 “그, 고양이 나오는 소설 있잖아요”라고 말했을 때 책을 꺼내주는 것과 같다. 제목을 정확히 대지 않았는데도 된다.
이 차이가 이번 편의 전부라고 해도 된다. 정확히 일치하는 것을 찾느냐, 가까운 것을 찾느냐.
3. 희소 검색 — TF-IDF는 결국 무엇을 세는가
검색기(Retriever)는 크게 둘로 나뉜다. 어휘가 겹치는 걸 찾는 쪽과 의미가 가까운 걸 찾는 쪽이다. 전통적인 방식인 앞쪽부터 본다.
희소 검색기(Sparse Retriever) 는 단어가 실제로 겹치는지를 본다. 대표가 TF-IDF다. 이름이 그대로 설명이라 뜯어보면 쉽다.
TF — 이 문서 안에서 얼마나 자주 나오나
TF(Term Frequency, 단어 빈도). 어떤 단어가 한 문서에 많이 나오면, 그 문서는 그 단어에 관한 문서일 가능성이 높다. “패킷”이 20번 나오는 문서는 패킷 이야기를 하고 있을 것이다.
IDF — 그런데 아무 데나 나오는 단어면 소용없다
IDF(Inverse Document Frequency, 역문서 빈도). “그리고”, “이다”, “하는” 같은 단어는 모든 문서에 나온다. 많이 나온다고 해서 그 문서의 특징이 아니다.
그래서 전체 문서 중 몇 개에 등장하는지를 세서, 흔할수록 가중치를 깎는다.
$$ w_{x,y} = tf_{x,y} \times \log \left( \frac{N}{df_x} \right) $$
- $tf_{x,y}$ — 문서 $y$ 안에서 단어 $x$가 나온 횟수
- $df_x$ — 단어 $x$를 포함한 문서의 개수
- $N$ — 전체 문서 개수
핵심은 로그 안쪽이다. 단어가 모든 문서에 다 나오면 $df_x = N$ 이 되고, $\log(1) = 0$ 이라 가중치가 0이 된다. 아무 정보도 없는 단어로 취급된다. 반대로 드물게 나오는 단어일수록 분모가 작아져 가중치가 커진다.
TF-IDF는 “이 문서에는 자주 나오는데 다른 문서에는 잘 안 나오는 단어” 를 찾는 계산이다. 그게 곧 그 문서의 정체다.
장점 — 그런데 이게 아직도 쓰인다
| 장점 | 이유 |
|---|---|
| 단순함 | 구현과 이해가 쉽다. 계산이 뻔하다 |
| 효율성 | 역색인(Inverted Index) 구조로 대량 질의를 빠르게 처리한다 |
| 투명성 | 왜 이 문서가 나왔는지 설명된다. 겹친 단어를 짚어주면 끝이다 |
투명성은 생각보다 큰 장점이다. 검색 결과가 이상할 때 원인을 바로 볼 수 있다.
단점 — 어휘 불일치
치명적인 약점이 하나 있다. 같은 뜻인데 다른 단어면 못 찾는다.
게임 서버 문서로 예를 만들어보면 이렇다.
질의: “게임이 자꾸 튕겨요” 문서: “클라이언트 비정상 종료 시 로그 수집 절차”
사람이 보면 명백히 같은 이야기인데, 겹치는 단어가 하나도 없다. TF-IDF 점수는 0이다. “튕긴다”와 “비정상 종료”가 같은 말이라는 걸 알려면 단어의 의미를 알아야 하는데, TF-IDF는 단어를 그냥 문자열로만 센다.
이게 다음 절이 존재하는 이유다.
4. 밀집 검색 — 의미를 좌표로 바꾼다
밀집 검색기(Dense Retriever) 는 단어를 세는 대신, 문장을 숫자 벡터로 바꾼다. 이 벡터를 임베딩(Embedding) 이라 부른다.
27편에서 CLIP을 볼 때 나온 그 임베딩과 같은 개념이다. 그때는 이미지와 텍스트를 같은 공간에 놓았고, 여기서는 질의와 문서를 같은 공간에 놓는다. 목적은 똑같다.
의미가 비슷한 것들은 좌표상 가까이 모인다.
“게임이 튕긴다”와 “클라이언트 비정상 종료”는 겹치는 글자가 없어도 벡터로 바꾸면 가까운 자리에 앉는다. 그러면 거리를 재서 찾을 수 있다. 보통 코사인 유사도를 쓴다.
$$ \text{sim}(q, d) = \frac{\mathbf{q} \cdot \mathbf{d}}{\lVert \mathbf{q} \rVert \cdot \lVert \mathbf{d} \rVert} $$
두 벡터가 같은 방향을 보고 있으면 1에 가깝고, 무관하면 0에 가까워진다. 길이가 아니라 방향만 본다는 게 포인트다. 문서가 길든 짧든 주제가 같으면 방향이 비슷하다.
어떻게 학습시키나 — 대조 학습
임베딩 모델은 대조 학습(Contrastive Learning) 으로 만든다. 이것도 27편에서 CLIP이 쓴 그 방식이다.
- 질의와 관련 있는 문서(정답 문서)는 서로 가까워지도록
- 질의와 무관한 문서(오답 문서)는 서로 멀어지도록
이 두 힘으로 공간을 계속 밀고 당긴다. 학습이 끝나면 의미가 비슷한 것끼리 뭉쳐 있는 공간이 만들어진다.
인코더를 어떻게 쓸 것인가 — 두 방식
여기서 갈림길이 나온다. 질의와 문서를 따로 인코딩할 것인가, 같이 인코딩할 것인가.
바이인코더(Bi-encoder) 는 질의와 문서를 따로 벡터로 만든 뒤, 두 벡터의 유사도만 계산한다.
크로스인코더(Cross-encoder) 는 질의와 문서를 하나의 입력으로 붙여서 모델에 통째로 넣는다. 트랜스포머 안에서 질의의 모든 토큰과 문서의 모든 토큰이 서로를 본다. 그래서 훨씬 정교하다.
| 바이인코더 | 크로스인코더 | |
|---|---|---|
| 입력 방식 | 질의·문서를 따로 인코딩 | 질의·문서를 붙여서 한 번에 |
| 상호작용 | 마지막에 유사도 한 번 | 모든 토큰끼리 전부 |
| 정확도 | 낮다 | 높다 |
| 속도 | 빠르다 | 느리다 |
| 미리 계산 | 문서 벡터를 미리 만들어 둘 수 있다 | 질의가 와야 계산 시작 |
마지막 줄이 결정적이다. 바이인코더는 문서 쪽 벡터를 서비스 띄우기 전에 전부 계산해서 저장해둘 수 있다. 질의가 들어오면 질의만 벡터로 바꿔서 거리를 재면 끝이다.
크로스인코더는 그게 안 된다. 질의-문서 쌍마다 모델을 새로 돌려야 한다. 문서가 100만 건이면 100만 번이다. 현실적으로 불가능하다.
그래서 실제로는 둘 다 쓴다
정확한데 느리고, 빠른데 부정확하다. 답은 뻔하다. 순서대로 쓴다.
- 바이인코더로 넓게 거른다 — 100만 건에서 상위 100건 정도로 줄인다 (Retrieve)
- 크로스인코더로 정밀하게 다시 줄 세운다 — 100건을 정확도 순으로 재정렬한다 (Rerank)
구조 자체는 익숙한 모양이다. 넓은 그물로 후보를 확 줄이고, 남은 것만 비싼 계산을 돌린다. 서류 심사로 100명을 추린 뒤 그 100명만 면접을 보는 것과 같다. 전원 면접은 애초에 불가능하니까.
비싼 연산은 후보를 줄인 다음에 한다. 이건 검색만의 이야기가 아니다.
밀집 검색의 대가
물론 공짜가 아니다.
- 높은 연산 비용 — 모델 학습에도, 추론에도 자원이 많이 든다
- 낮은 투명성 — 왜 이 문서가 나왔는지 설명이 어렵다. TF-IDF의 장점을 정확히 잃는다
- 데이터 의존성 — 학습 데이터의 도메인을 벗어나면 성능이 떨어진다
그래서 실무에서는 희소 검색과 밀집 검색을 같이 쓰는 경우가 많다고 한다. 어느 한쪽이 이기는 싸움이 아니었다.
5. RAG 파이프라인 — 세 걸음
부품을 다 봤으니 조립한다. 실제 동작은 세 걸음이다.
- 질의 추출 — 사용자의 질문을 언어모델이 해석해서 검색에 쓸 문장을 만든다
- 문서 검색 — 검색기가 저장소에서 관련 문서를 찾아온다 (여기가 3~4절의 내용)
- 답변 생성 — 찾아온 문서를 질문과 함께 언어모델에 넣어 최종 답을 쓴다
3번에서 문서를 프롬프트에 같이 넣는다는 게 핵심이다. 모델을 다시 학습시키는 게 아니다. 그냥 읽을거리를 같이 건네주는 것이다.
시험 전에 머릿속에 다 외워 넣는 게 파인튜닝이라면, RAG는 오픈북 시험이다. 책을 못 찾으면 답도 못 쓴다.
그래서 RAG의 성질이 정해진다.
| 성질 | 이유 |
|---|---|
| 최신 정보 반영이 쉽다 | 저장소만 갱신하면 된다. 모델은 안 건드린다 |
| 근거를 댈 수 있다 | 어떤 문서를 보고 답했는지 남는다 |
| 모델 재학습이 필요 없다 | 학습 비용이 안 든다 |
| 검색 품질에 성능이 종속된다 | 못 찾아오면 못 답한다 |
마지막 줄이 다음 절의 주제다.
문서를 넣기 전에 적당한 크기로 잘라야 하는데(청킹, Chunking) 그건 실습 쪽 이야기라 다음 편에서 다룬다.
6. RAG가 깨지는 네 지점
검색을 붙였다고 사실 정확성이 보장되지는 않는다. 오히려 새로운 실패 방식이 생긴다.
① 컨텍스트 길이
찾아온 문서를 다 넣어야 하는데, 모델이 한 번에 읽을 수 있는 양은 정해져 있다. 최신 모델들이 컨텍스트 창을 10만 토큰 단위로 늘리고 있는 건 이 압박 때문이다.
다만 길이를 늘리는 건 돈과 속도를 쓰는 해결책이다. 길게 넣을수록 느리고 비싸진다. 그래서 잘 자르고 잘 고르는 게 여전히 중요하다.
② 검색 노이즈
검색이 틀린 문서를 섞어서 가져오는 경우다. 여기서 두 가지 능력이 필요해진다.
노이즈 견디기(Noise Robustness) — 관련 없는 문서가 섞여 들어와도 맞는 정보만 골라서 답하는 능력이다. 상위 5건 중 3건이 엉뚱해도 나머지 2건으로 답할 수 있어야 한다.
모르면 모른다고 하기(Negative Rejection) — 찾아온 문서에 답이 아예 없을 때 추측하지 않고 “문서에 없다”고 말하는 능력이다. 이게 더 어렵다. 언어모델은 기본적으로 뭐라도 쓰게 되어 있기 때문이다.
못 찾았을 때 침묵하는 것이, 그럴듯한 걸 지어내는 것보다 훨씬 어렵다.
③ 사전 지식과의 충돌
모델이 학습 때 외운 사실과, 지금 건네준 문서의 내용이 다를 때 벌어지는 일이다.
예를 들어 어떤 사람의 소속이 최근에 바뀌었다고 하자. 문서에는 바뀐 소속이 적혀 있는데, 모델은 예전 소속을 기억하고 있다. 이때 모델은 종종 자기가 외운 쪽을 답한다.
해결책은 두 방향이다.
- 접지(Grounding) 학습 강화 — “네가 아는 것은 무시하고 주어진 문서만 근거로 답하라”는 지시를 따르도록 학습시킨다
- 문서에 없으면 거절하도록 학습 — 근거가 컨텍스트에 없으면 답을 피하도록 만든다
두 번째가 특히 중요한데, 접지만 강하게 걸면 문서에 없는 내용까지 문서에서 읽은 척 하는 부작용이 생길 수 있어서다.
④ 복합 추론과 반사실 문서
정보 통합(Information Integration) — 답이 문서 한 건에 다 있지 않고 여러 문서에 나눠 흩어져 있는 경우다. A 문서에서 날짜를, B 문서에서 조건을 가져와 합쳐야 답이 된다. 검색은 성공했는데 조립에서 실패한다.
반사실 견고성(Counterfactual Robustness) — 찾아온 문서 자체에 틀린 사실이 적혀 있는 경우다. 인터넷 문서를 저장소로 쓰면 충분히 생긴다. 모델이 그 오류를 알아채고 지적할 수 있는지를 묻는 항목이다.
네 번째가 제일 어려워 보였다. 문서를 믿으라고 가르쳐놓고, 문서가 틀렸을 때는 의심하라고 요구하는 셈이다. 둘 다 잘하는 게 가능한지 아직 잘 모르겠다.
검색을 붙이면 환각이 사라지는 게 아니라, 환각의 원인이 모델에서 검색으로 옮겨간다.
정리
- 16편 이후의 후속 학습법 — 리워드 해킹(보상 모델만 만족시키는 최적화)과 과최적화(지표는 오르는데 실제 품질은 꺾임)가 RLHF의 약점
- DPO — 보상 모델과 강화학습을 없애고 선호 데이터로 확률을 직접 조정. 가볍고 안정적
- RLVR — 자동 채점 가능한 문제(수학·코딩)에서 정답 여부를 그대로 보상으로. 리워드 해킹이 원천 차단되지만 적용 범위가 좁다
- 파라미터 지식의 한계 — 긴 꼬리 지식, 지식 갱신, 출처 검증, 사내 보안. 이 넷이 RAG가 필요한 이유
- 부품 네 개 — Datastore(가공 안 된 원문 뭉치), Query(질문과 달라도 됨), Index, LM
- 희소 검색(TF-IDF) — 이 문서엔 자주, 다른 문서엔 드물게 나오는 단어를 찾는다. 단순·빠름·설명 가능. 단, 어휘가 다르면 못 찾는다
-
밀집 검색(임베딩) — 문장을 벡터로 바꿔 의미 거리로 찾는다. 대조 학습으로 만든다
- 바이인코더 — 따로 인코딩, 문서 벡터 사전 계산 가능, 빠르지만 덜 정확
- 크로스인코더 — 붙여서 인코딩, 모든 토큰이 상호작용, 정확하지만 미리 계산 불가
- 실무는 바이인코더로 거르고 크로스인코더로 재정렬하는 2단 구조
- RAG 파이프라인 — 질의 추출 → 문서 검색 → 답변 생성. 모델을 학습시키는 게 아니라 읽을거리를 같이 넣는다
- 깨지는 네 지점 — 컨텍스트 길이 / 검색 노이즈(견디기·거절하기) / 사전지식 충돌(접지 학습) / 복합 추론(정보 통합·반사실 견고성)
- 겹치는 글 — 지시 학습과 선호 학습은 16편, 환각과 RAG의 첫 등장은 20편, 임베딩과 대조 학습은 27편
참고 자료
- Dense Passage Retrieval for Open-Domain QA (EMNLP 2020)
- Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks (NeurIPS 2020)
- Direct Preference Optimization (NeurIPS 2023)
- Benchmarking Large Language Models in Retrieval-Augmented Generation (2023)
- Sentence-BERT — 바이인코더 구조의 출발점
한줄 평
- 검색이 결국 정확히 일치하는 걸 찾는 게 아니라 가까운 걸 찾는 일이라는 한 문장으로 정리되면서, Redis 키 조회밖에 안 해봤던 머릿속이 좀 넓어진 느낌이었다!