728x90

왜 할루시네이션이 발생하는지 알려면 언제 발생하는지도 알아야한다.

What is Hallucination?

사실이나 맥락적 논리(맥락에 안맞는 말)에서 벗어나는 LLM의 Output.

할루시네이션은 사소한 불일치부터 완전히 조작되거나 모순된 Output이 될 수 있다.

오늘은 4가지 종류의 할루시네이션에 대해 살펴보자.

1. 문장 모순 (Sentence Contradiction)

LLM이 이전 문장과 모순되는 문장을 생성하는 경우.

“오늘 날씨가 참 맑네요. 근데 오늘 날씨가 너무 안좋아요”

2. 프롬프트 모순 (Prompt Contradiction)

생성된 문장이 그것을 생성하는데 사용된 Prompt와 모순되는 경우

음식점에 대해 좋은 리뷰를 작성해달라고 요청했을때 : “음식이 다 식어있고 맛이 너무 없어요”

3. 사실적 모순 / 오류 (Factual Contradiction / Error)

생성된 문장이 사실과 다른 내용을 포함한 경우

“이순신 대통령은 대한민국 1대 대통령입니다”

4. 무관한 오류 (Non-sensical)

LLM이 실제로는 존재하지 않는 무언가를 집어넣거나 질문과 전혀 관련 없는 내용을 이야기 할때.

“파리는 프랑스의 수도에요. 그리고 파리는 유명한 가수의 이름이에요”

Why Hallucinate?

LLM의 결과 도출방식이 사실상 블랙박스(안을 볼수가 없음 / 봐도 이해 못함)기 때문에 정확히 어떤 이유에서 할루시네이션이 발생했다고 말하기는 어렵지만 몇 가지 일반적인 원인이 있다.

1. 데이터의 품질(Data Quality)

LLM은 노이즈, 오류, 편견, 불일치가 포함된 데이터로 학습하는 경우 예를 들어, 위키피디아나 레딧을 사용했기 때문이다. 레딧이나 위키피디아의 내용을 100% 신뢰할 수는 없다.

데이터가 100% 정확하더라도 콘텐츠 생성에 필요한 모든 주제 / 도메인을 다루지 않을 수 있다.

따라서, 데이터의 정확성 / 관련성을 검증하지 못한채 데이터를 일반화하는 경우가 발생하고 때로는 이러한 방식이 잘못되는 경우가 발생한다.

물론, LLM의 추론(Reasoning) 능력이 향상됨에 따라 환각이 감소하는 경향이 있다.

2. 생성 방식 (Generation Method)

LLM은 텍스트를 생성하기 위해 다양한 방법과 목표를 사용한다. 예를 들어, Beam Search, Sampling, Maximum Likelihood Estimation, Reinforcement Learning)

이러한 방법과 목표는 유창함과 다양성, 일관성과 창의성, 정확성과 참신함 사이의 상충관계(Trade-Off)를 초래할 수 있다.

가령, Beam Search는 높은 확률이지만 포괄적인(Generic) 단어를 낮은 확률이지만 구체적인(Specific)보다 선호한다.

3. 입력 맥락 (Input Context)

여기서 Context란 Input Prompt로 모델에 제공되는 정보를 의미한다.

Context는 모델이 관련성 있고 정확한 출력을 생성하도록 안내하는데 도움이 될 수 있지만,

명확하지 않거나 일관성이 없거나 모순되면 모델을 혼동하거나 오해하게 할 수 있다.

예를 들어, LLM 챗봇에 “고양이가 영어를 할 수 있나요?” 라고 물으면 “아니요, 좀 앉아서 쉬실래요?”라는 답변을 받을것이다. 하지만 만약 내가 가필드 만화 시리즈에 대해 이야기 하고 있다는 Context를 포함했다면 LLM은 “네, 고양이는 영어를 할 수 있고 저 고양이는 라자냐를 두번째로 달라고 할거같아요~” 라는 답변을 받을 것 이다.

Context는 중요하고 학술 에세이나 창의적인 글쓰기 연습에 필요한 텍스트를 찾고 있다고 말하지 않으면 해당 텍스트가 해당 맥락 내에서 응답할것이라 기대할 수 없다.

How can we MITIGATE Hallucination

1. 명확하고 구체적인 프롬프트 제공 (Clear & Specific Prompt)

입력 프롬프트가 더 정확하고 상세할수록 LLM이 관련성이 높고 (가장 중요한) 정확한 출력을 생성할 가능성이 높아진다.

예를 들어, “2차 세계 대전에 무슨 일이 일어났나요?” 라는 질문 대신에

“갈등의 주요 원인에 연루된 주요 국가를 포함하여 제 2차 세계 대전의 주요 사건을 요약할 수 있습니까?” 라고 말할 수 있다.

2. 적극적인 완화 전략 (Active Mitigation)

LLM이 문장을 생성할때 관여하는 hyper parameter를 조정하는 방식이다.

이에 대한 좋은 예는 출력의 Randomness(임의성)을 제어할 수 있는 Temperature 매개변수이다.

Temperature가 낮을수록 더 보수적이고 집중된 답변을 생성하고 Temperature가 높을수록 더 다양하고 창의적인 반응이 생성된다.

다만, Temperature가 높을수록 환각이 일어날 확률이 높아진다.

3. 멀티샷 프롬프트 (Multi-shot Prompt)

하나의 Prompt만 제공하는 Single-shot Prompt와 달리 멀티샷은 LLM에 원하는 출력 형식이나 컨텍스트에 대한 여러 예를 제공해서 모델의 답변을 개선한다.

여러 가지 예를 통해 LLM을 제시해서 패턴이나 context를 보다 효과적으로 인식하는데 도움이 되고 이는

특정 출력 형식이 필요한 작업에 특히 유용하다.

예를 들어, 특정 스타일로 코드를 작성하고, 시를 쓰고, 질문에 특정한 형태로 답을 하는 경우이다.

Reference

Why Large Language Models Hallucinate

'인공지능 > LLM' 카테고리의 다른 글

AGI로 가는 길에 LLM의 한계와 강화학습의 한계  (3) 2024.11.02
할루시네이션 발생 원인  (0) 2024.11.02
LLM의 Risk  (0) 2024.11.02
Knowledge Distillation - LLM  (0) 2024.03.17
RAG란?  (0) 2024.03.17
728x90

LLM의 한계

Scaling is not enough

현재 LLM의 규모만 더 키우면 결국에 인간과 비슷한 수준의 AI가 탄생할 것이라는 주장을 단 한 순간도 믿어본 적이 없다 얀 르쿤

1. 현재 LLM은 토큰화가 필수

  • 토큰화는 discrete하고 low-dimensional인 텍스트 데이터에는 잘 적용된다
  • 하지만 continuous 하고 high-dimensional 시그널인 비디오에는 접목되지 않는다.

2. LLM 구조상 한정적인 추론만 가능

  • Abstract latent variables를 포함하지 않는 모델의 특성상→ 사람처럼 고맥락의 행동을 할 수 없다고 주장(과거에 대한 기억이 없으니까)(2) 그리고 그 바탕으로 optimal course of action을 찾아 수행할 여지가 없음
  • (1) perception에 대한 다양하고 다채로운 해석을
  • → short-term memory에 해당하는 부분이 없어서 한정적인 정보 만으로 다음 상황을 예측해야함

강화 학습의 한계

Reward is not enough

강화학습 방식을 사용하려면 모델에게 간단한 작업만 학습시킨다 해도 방대한 데이터가 필요하다. 나는 그런 방식은 성공 가능성이 없다고 생각한다. 얀 르쿤

1. 스칼라 리워드로는 한정적인 정보만 제공되고, 이는 ‘과다한’ 실패로 이어진다

숫자로 정의되는 리워드들은 한정적이다 → 세상은 숫자만으로는 표현 불가능한데 명확히 판단되지 않는 상황이 너무 많다

  • Intrinsic cost module이 환경 그 자체가 되고, 그 말은 unknown function 이라는 말이다.
  • Model-free RL은 인간/동물 대비 대단히 비효율적이다 → 모든 경우의 수를 다 경험해야한다
  • Model-based는 그나마 낫지만 World Model을 훈련하기에는 reward로는 부족하다

결론

결국 AGI를 만들기 위해서는 뇌 구조도 인간과 유사해야하고 훈련 방식도 인간과 유사해야한다.

“위와 같은 얀 르쿤의 아이디어가 딥러닝 커뮤니티에서 나온 것은 처음이다” ”LLM은 실제로 중요한 부분은 세계에 대한 내부 모델과 기억력이 부족하다” Santa Fe 연구소, 멜라니 미셸

반대 입장

“르쿤의 제안에서 언어가 완전히 배제된 것은 이상하다; 우리는 LLM이 매우 효과적이며 수많은 인간의 지식을 활용한다는 사실을 알고 있다” ”인간이 무언가를 배울 때 직접적인 경험이 꼭 필요한 것은 아니다, 우리는 뜨거운 냄비를 직접 만지지 않더라도 그렇게 하지 말라는 말만 듣고 행동을 고칠 수 있으며 언어가 없다면 얀이 제안하는 이 세계모델을 어떻게 업데이트할 수 있겠는가? 구글 브레인, 나타샤 자크

Reference

[AI인사이트] AI계의 사대천왕 얀 르쿤! 딥러닝 외길만 40년을 걸어온 그가 이제 와서 지금의 연구가 잘못 됐다는데.. 과연 그가 제안하는 AGI(일반인공지능)로 가는 길은?

AGI로 가는 길에 LLM의 한계와 강화학습의 한계

'인공지능 > LLM' 카테고리의 다른 글

Why LLMs Hallucinate?  (1) 2024.11.02
할루시네이션 발생 원인  (0) 2024.11.02
LLM의 Risk  (0) 2024.11.02
Knowledge Distillation - LLM  (0) 2024.03.17
RAG란?  (0) 2024.03.17
728x90

원인

LLM이 다음 토큰이나 단어를 생성할 때 마다 그 단어가 합리적인 정답에서 벗어난 단어가 될 가능성이 있다.

그리고 이 오류가 각 단어마다 따로 발생할 수 있다고 가정하면 긴 문장을 만들수록 오류가 발생할 가능성이 커진다

즉, 토큰을 생성할때마다 올바른 정답의 집합에 머무를 확률이 기하급수적으로 낮아진다

실수할 가능성이 조금이라도 있으면 오류가 점점 더 커질 수 있다. 그리고 그 실수는 기하급수적으로 증가하며 오류가 쌓인다.

따라서, 문장이 길어질수록 엉뚱한 답(non-sensical)이 나올 확률도 기하급수적으로 높아진다.

질문

만약 훈련 데이터가 대부분 올바르면 자연스럽게 올바른 답변을 하지 않을까?

답변

그렇지 않다. “차원의 저주”라고 할 수 있다. 즉, 데이터의 차원이 증가할수록, 즉 feature(변수)의 수가 많아질수록 데이터 분석이나 모델 학습이 어려워지는 현상

이를 해결하려면 시스템이 다양한 질문에 답할 수 있도록 조정해야한다.

사람들이 자주 하는 질문은 비슷한 경우가 많다. 따라서, 수집된 데이터로 80%의 질문에는 답할 수 있다.

그리고 시스템을 finetune해서 모든 질문에 대해서 좋은 답변을 생성하도록 한다.

그런데 훈련중에 다루지 않는 막대한 양의 Prompt가 존재한다. 그리고 그 집합은 매우 크다.

가능한 모든 프롬프트 중에서 훈련에 사용된 프롬프트는 아주 적다.

전체 프롬프트 중에서 매우 적은 일부만 훈련에 사용된다.

따라서, 시스템은 사전 훈련되거나 FineTuning 된 프롬프트에서는 잘 작동한다.

하지만, 훈련되지 않은 막대한 양의 질문의 집합이 존재한다.

시스템이 아무리 훈련이 잘돼도 훈련되지 않은 프롬프트나 그런 형식의 프롬프트를 찾으면 시스템이 잘못된 답변을 생성할 수 있다. 그러면 엉뚱한 답변을 생성한다.

질문

인터넷에 없는 완전 새로운 prompt를 만드는게 가능한가? 쉬운가?

답변

프롬프트에 무작위로 생성된 문자나 기호를 넣는 방식이 있다. 이 방식을 사용하면 시스템은 혼란을 겪어 평소와 전혀 다른 방식으로 답변하게 된다.

이런 식으로 프롬프트에 변형을 주면 시스템이 원래 의도한 답변이 아닌 완전 다른 반응을 보이게 된다.

이는 시스템을 제약에서 벗어나게하는 방식으로 시스템의 한계를 명확하게 보여준다.

질문

문법적으로 올바른 문장을 사용해도 시스템을 무너뜨릴 수 있는가?

답변

그렇다. 영어로 문장을 작성한 뒤에 일부 단어를 동일한 의미를 가진 다른 언어로 치환하면 답변이 갑자기 바뀐다.

문제는 긴 문장이다. 사람들의 질문은 매우 긴 문장이고 대부분의 사람들이 물어보는 질문중 80% 정도만 시스템을 학습시킬 수 있다.

하지만, 긴 문장이 너무 많아서 모든 조건에 맞게 시스템을 학습시킬 수 없다.

결국 시스템은 일종의 Look Up Table이 될 수 밖에 없다.

→ 특정 입력 값에 대해 미리 계산된 결과 값을 저장해두는 데이터 테이블

하지만, 이는 추론하고 계획할 수 있는 시스템이 아니다.

원인

잘못된 학습 데이터

AR 방식의 토큰 생성

해결 방법

학습되지 않은 프롬프트에 대해서도 신뢰성 있는 대답 → 일반화 능력 필요 or 추론 능력 필요

그저 답을 생성하는 것 이상의 과정이 필요

'인공지능 > LLM' 카테고리의 다른 글

Why LLMs Hallucinate?  (1) 2024.11.02
AGI로 가는 길에 LLM의 한계와 강화학습의 한계  (3) 2024.11.02
LLM의 Risk  (0) 2024.11.02
Knowledge Distillation - LLM  (0) 2024.03.17
RAG란?  (0) 2024.03.17
728x90

LLM의 Risk는 총 4가지가 있다. Hallucination, Bias, Consent, Security.

오늘은 이러한 위험과 그 위험을 완화할 수 있는 전략을 알아보자

Risk 1 : Hallucination

LLM은 구문적(syntactic)으로 올바른 다음 단어를 예측한다.

즉, 사람의 질문을 이해해서 답을 생성하는 것이 아니다.

따라서, 듣기에는 그럴듯해보이지만 거짓말을 늘어놓는 경우가 발생한다.

이러한 잘못된 답변은 통계적 오류다.

예를 들어, “시 A, B, C를 쓴 사람이 누구야?” 라는 질문이 들어왔고 데이터 소스에 작품 A는 작가 X가, 작품 B는 작가 X가, 작품 C는 작가 Z가 작성했다라는 정보가 있다 하자.

이 경우 훈련 데이터에 상충되는 source가 있고 잘못된 답변을 생성하는 경우가 발생할 수 있다.

심지어 훈련 데이터에 불일치가 없더라도 통계적 오류가 발생할 수 있다.

이게 다 LLM이 의미를 이해하지 못하기 때문이다.

Strategy 1 : Explainability

Inline explainability를 제공할수도 있고 지식 그래프를 통해서 실제 데이터와 데이터 계보 및 출처를 제공하는 시스템을 LLM과 연동할 수 있다.

LLM이 방금 생성한 답변의 이유, 데이터의 출처 등..

LLM은 지식 그래프에서 제공한 답변에 대한 변형을 제공할 수 있다.

Risk 2 : Bias

시인에 대한 질문을 했을때 백인 | 남성 | 서유럽 시인만 나열되는 경우가 발생할 수 있다.

즉, 데이터가 편향되어있는 경우이다.

만약 조금 더 일반적인 경우를 원한다면 프롬프트에 “여성과 비서구 유럽인이 포함된 시인 목록을 알려줘”라고 해야 할 수 있다.

Strategy 2 : Culture and Audits

AI는 우리의 편견을 반영하는 훌륭한 거울이라 LLM에 문화를 다양하게 반영하려면 AI를 작업하는 팀도 다양한 배경과 분야에 걸친 인력이 필요하다.

AI 모델을 감사해서 이질적인 결과가 나오면 조직 문화를 바로잡을필요(?)가 있다.

사전 모델 배포와 사후 모델 배포를 감사한다.

Risk 3 : Consent

Curating 하는 데이터가 대표적인 데이터인지? 동의를 받고 수집되었는지? 저작권 문제는 없는지? 등을 살펴봐야한다.

위의 내용들은 찾기 쉬운 자료로 관리해야한다.

자주 LLM의 학습 데이터의 출처를 모르는 경우가 발생한다.

Strategy 3 : Accountability

AI 거버넌스 프로세스 확립, 기존 법률 및 규정 준수 확인, 사람들이 피드백을 통합할 수 있는 방법 제공해야 한다.

Risk 4 : Security

LLM은 모든 종류의 악의적인 작업에 사용 가능하다. 예를 들어 개인정보 유출, 피싱 범죄, 스팸, 사기 지원 등이 있다.

탈옥 : 해커들이 AI 모델로 원래 프로그램을 변경하거나 인종차별을 지지하고 사람들에게 불법적인 일을 하도록 제안하는 경우가 발생한다.

Indirect Prompt Injection : 제 3자가 웹사이트를 변경해서 숨겨진 데이터를 추가해서 AI의 동작을 변경하는 것이다.

따라서, AI에 의존하는 자동화는 사용자가 모르는 사이에 악의적인 지침을 보낼 수 있다.

Strategy 4 : Education

우리가 사용하는 기술의 강점과 약점을 알아야한다.

AI의 책임있는 큐레이션, 위험, 환경비용 및 기회 등에 대한 교육이 필요하다.

오늘날 일부 기술 회사는 LLM의 교육 데이터가 악의적으로 변조되지 않았다고 믿지만 사실은 Website를 사서 오염된 데이터로 채울 수 있고 충분한 예제로 데이터셋을 오염시킨다면 LLM의 동작과 출력에 영향을 미칠 수 있다.

Reference

Risks of Large Language Models (LLM)

'인공지능 > LLM' 카테고리의 다른 글

AGI로 가는 길에 LLM의 한계와 강화학습의 한계  (3) 2024.11.02
할루시네이션 발생 원인  (0) 2024.11.02
Knowledge Distillation - LLM  (0) 2024.03.17
RAG란?  (0) 2024.03.17
챗봇 구현 실습 (4) - 챗봇 구현  (0) 2024.03.17
728x90

RAG

Abstract

  • 대규모 사전학습 모델들은 정보를 매개변수에 저장한다고 알려져 있고 downstream task에 fine-tuning 시키면 성능이 좋음.
  • 하지만 pretrain된 LLM만으로는 아직 지식에 접근하고 정밀하게 조작하는 것은 한계가 분명
  • 또한, LLM의 지식의 출처를 파악하는것, 정보를 업데이트하는 것은 연구가 더 필요
  • non-parametric memory를 명시하기 위한, 구분 가능한(미분 가능한인가?) 접근 메커니즘을 가진 사전학습된
    모델들은 추출 작업에 대해서만 연구됨.
  • 사전 학습된 parametric memory(언어 모델) 및 non-parametric memory(벡터 db와 같은 Knowledge source)를 결합하여
    언어 생성을 위한 일반 목적의 미세 조정 방법을 탐구
  • 본 연구의 RAG 모델은 parametric memory는 pre-trained seq2seq model이고
    non-parametric memory는 사전학습된 neural retriever로 접근되는 Wikipedia의 dense vector index이다.
  • 본 논문에서는 두가지 RAG 형식을 비교. 하나는 생성된 시퀀스들에 대해서 동일한 검색된 passage를 조건으로 사용하고, 다른 하나는 토큰마다 다른 passage를 사용한다.

요약

  • RAG Model = Parametric Memory + Non-Parametric Memory
    • Parametric Memory : 기존의 pretrained LLM
    • Non-Parametric Memory : Retriever(검색기) + Dense Vector Index(Vector Store)

1. Introduction

  • 사전학습된 언어모델들은 매개변수화된 내재적 지식 베이스가 있어서 따로 외부 메모리에 접근이 필요 없음
  • 하지만, 이런 방식의 단점이 몇가지 있는데
    • 메모리를 쉽게 확장하거나 확인할 수 없고
    • 모델의 예측에 대한 이유나 근거를 직관적으로 이해하기 힘들고
    • hallucination 문제가 발생 가능
  • Hybrid 모델은 Parametric memory를 non-parametric memory와 합쳐서 이러한 문제 일부를 해결
    • 지식이 직접적으로 확장 및 확인 가능
    • 접근된 지식이 해석 및 확인가능
  • REALM과 ORQA 모델은 유망한 결과를 도출했지만 open-domain extractive question answering에 대해서만 연구됨

Figure 1

  • 논문의 저자들은 pretrained seq2seq transformer (생성기)와 dense vector index (저장소),
    pretrained neural retriever (검색기)를 합쳐서 end-to-end로 학습된 확률적 모델을 생성.

모델 작동 방식

1. Retriever(Dense Passage Retriever)는 input을 조건으로 문서를 검색합니다.
2. Seq2Seq 모델(BART)가 input과 검색된 문서를 조건으로 출력을 생성합니다.
  • 이 과정에서 top-K approximation 두 가지 중 하나를 사용해서 문서를 선택합니다.
    • per-output basis : 모든 토큰에 대해서 동일한 문서가 관련이 있다
    • per-token basis : 서로 다른 토큰에 대해서는 서로 다른 문서가 관련이 있다.
  • Non-parametric Memory 즉, 검색기나 벡터 스토어같은 외부 데이터베이스를 활용해서 시스템을 개선하는 기존 연구들이 많았음
    • memory network
    • stack-augmented networks
    • memory layers
  • 반면에 저자들은 광범위한 지식에 대해서 pre-train, pre-load된 non-parametric memory 뿐만 아니라 parametric memory를 사용
    • 사전학습된 접근 메커니즘을 사용해서 추가적인 학습 없이 지식에 접근하는 능력이 있음
  • 본 연구는 knowledge-intensive tasks에 대한 생성 작업에 특장점이 있음
    • knowledge-intensive tasks란 인간이 외부 지식 소스에 접근 없이 합리적으로 수행하기 힘든 작업

2. Methods

  • RAG 모델은 input 시퀀스 x로 텍스트 문서 z를 검색하고 z를 추가적인 컨텍스트로 사용해서 target 시퀀스 y를 생성할때 사용합니다.
  • Figure 1에서 확인 가능한 것처럼 RAG 모델은 2가지 구성요소를 활용합니다.
    1. 검색기 pη(zx) p_{\eta}(z|x)
      • 검색기는 매개변수 η \eta 를 사용
      • qeury x가 포함된 텍스트 구절에 대한 분포(top-K개의 문서)를 반환
    2. 생성기 pθ(yix,z,y1:i1) p_{\theta}(y_i | x,z,y_{1:i-1})
      • 생성기는 매개변수 θ \theta 사용
      • 기존 i-1 token들 y1:i1 y_{1:i-1} , 원본 입력 x, 검색한 passage z를 컨텍스트로 사용해서 현재 토큰을 생성한다.
  • 생성된 텍스트의 분포를 생성하기 위해서 서로 다른 방법으로 문서들을 marginalize하는 2가지 모델을 제시한다.

2가지 모델

1.  RAG-Sequence : 동일한 문서로 각각의 target token을 예측한다.
2.  RAG-Token : 서로 다른 문서를 기반으로 각각의 target token을 예측한다.

2.1 Models

RAG-Sequence Model

  • 완전한 sequence를 생성하기 위해서 하나의 문서를 사용한다.
  • 검색한 문서를 top-K approximation을 통해서 seq2seq 확률 p(yx) p(y|x) 를 구하기 위한 하나의 잠재 변수로 취급한다.
  • top-K 문서는 retriever 사용하여 검색되며, generator는 각 문서로 출력 시퀀스 확률을 생성하고 이후에 marginalize한다.

수식에서 등장한 각각에 대해서 살펴보면

  • pRAGSequence(yx)p_{RAG-Sequence}(y|x) : 입력 x에 대해서 모델이 출력 y를 생성할 확률
  • topk(p(x))top-k(p(\centerdot|x)) : 입력 x에 대해 검색된 상위 k개의 문서의 집합
    • x와 관련이 높은 문서들로 구성
  • ztopk(p(x))\sum_{z\in top-k(p( \centerdot |x))} : 상위 k개의 검색된 문서 z 각각에 대해서 합산을 수행함을 의미
  • pη(zx)p_{\eta}(z|x) : 검색된 문서 z가 입력 x에 대해서 선택될 확률
  • pθ(yx,z)p_\theta(y|x,z) : x,z에 대해 모델이 출력 y를 생성할 확률

그리고 이 과정을 조금 더 자세하게 설명해면

  1. 입력 x로 상위 K개의 문서 z를 검색
  2. 각 문서 z에 대해 pη(zx)p_{\eta}(z|x)를 계산해서 문서가 선택될 확률 계산
  3. 각 문서 z에 대해 pθ(yx,z)p_\theta(y|x,z)를 계산해서 문서를 기반으로 출력 y가 생성될 확률 계산
  4. 2,3번 과정을 통해 구한 확률을 곱해서 해당 문서가 선택되고 출력 y가 생성될 전체 확률을 구한다.
  5. top-K개 문서에 대해서 확률들을 모두 합해서 최종 pRAGSequence(yx)p_{RAG-Sequence}(y|x)를 구한다.

그리고 맨 오른쪽의 식은 p(yx,z)p(y|x,z) 를 구하는 식을 자세히 풀어쓴 것으로 i번째 토큰을 생성할때 x, z, 그리고 1 ~ i-1 까지의 토큰이 사용됨을 보여주는 수식입니다.

RAG-Token Model

  • 각각의 target token에 대해서 서로 다른 문서를 선택할 수 있고 그에 따라 marginalize 할 수 있다.
  • 이 방식을 사용하면 generator가 문장을 생성할때 몇가지 문서에서 내용을 선택할 수 있다.
  • 우선 top-K 문서가 검색기로 검색되고 각각의 문서에 대해서 생성기가 다음 output token에 대한 분포를 생성하고 marginalize 한 후
    다음 출력 토큰에 대해 이 과정을 반복한다.

RAG-Sequence와의 차이점은 토큰을 예측할때마다 다른 검색 문서를 사용할 수 있다는 것이다.

따라서 파이가 밖으로 나와서 토큰마다 검색을 한다는 것을 확인할 수 있다.

마지막으로, 논문의 저자들은 target class를 길이가 1인 target sequence로 간주해서 RAG를 시퀀스 분류 태스크에 사용할 수 있다고 주장함.

2.2 Retriever: DPR

  • Retriever 구성요소 pη(zx)p_{\eta}(z|x)는 DPR 기반이고 DPR은 bi-encoder 아키텍쳐 기반

  • d(z)d(z) : Document Encoder(BERT BASE)로 생성된 문서의 dense representation(문서를 나타내는 벡터 = 문서 정보를 벡터로 바꾼것)
  • q(x)q(x) : Query Encoder(BERT BASE)로 생성된 query representation(query를 나타내는 벡터 = query 정보를 벡터로 바꾼것)
  • topk(p(x))top-k(p(\centerdot|x)) : pη(zx)p_{\eta}(z|x) 값이 가장 큰 문서 k개의 리스트인데, MIPS(Maximum Inner Product Search) 방식으로 검색한다. 즉, 내적의 값이 큰 문서들을 찾는 것.
  • DPR의 사전학습된 bi-encoder를 사용해서 retriever를 초기화하고 문서 인덱스(문서 저장소)를 생성
  • Retriever는 TriviaQA의 답을 포함한 문서를 검색하도록 훈련됨
  • 논문의 저자들은 document index를 non-parametric memory라고 함

2.3 Generator : BART

  • Generator 구성요소 pθ(yx,z,y1:i1)p_\theta(y|x,z,y_{1:i-1})는 encoder-decoder 구조를 가진 어떤 모델이든 상관 없음
  • 논문의 저자들은 BART-large 모델을 사용(400M 매개변수 가진 seq2seq 트랜스포머 모델)
  • Input x와 검색된 문서 내용 z로 답변을 생성하기 위해서 두 값을 단순히 연결함
  • 논문의 저자들은 BART generator를 parametric memory라고 함

2.4 Training

  • 논문의 저자들은 어떤 문서가 검색되어야 하는지에 대한 직접적인 감독 없이 Retriever와 Generator 구성 요소를 동시에 학습시킴
  • Fine-tuning 훈련 corpus input/output pair(xj,yjx_j,y_j), Adam을 사용해서 목적함수를 Loss=jlogp(yjxj)Loss = \sum_j - log p(y_j|x_j)로 놓고 학습함

붉은 선은 log 함수, 파란 선은 -log 함수

  • 즉, negative marginal log-likelihood를 최소화함으로써 학습한다는 의미는 확률은 0~1 사이의 값을 가지고 -log 함수를 최소화 해야하므로 p(yjxj)p(y_j|x_j)가 1에 가까워야한다. 이 말은 곧 xjx_j가 입력되었을때 출력값이 yjy_j일 확률을 최대화해야 한다는 의미
  • 논문의 저자들은 훈련 과정에서 문서 인코더 BERTdBERT_d를 계속 update하는게 비용만 크고 성능 향상에 크게 도움이 되지 않는다 판단해서 BERTqBERT_q와 BART generator만 fine-tuning 함

2.5 Decoding

테스트시에 RAG-SequenceRAG-Token방식은 서로 다른 방식으로 argmaxyp(yx)\arg\max_y p(y|x)를 근사해야 함

RAG-Token

  • RAG-Token 모델을 표준이라고 생각해도 좋음
  • Decode 하기 위해서 pθ(yix,y1:i1)p\prime_\theta(y_i|x,y_{1:i-1})를 standard beam decoder에 plug함
    pθ(yix,y1:i1)=ztop-k(p(x))pη(zx)pθ(yix,z,y1:i1)p\prime_{\theta}(y_i \mid x, y_{1:i-1}) = \sum_{z \in \text{top-k}(p(\cdot \mid x))}p_{\eta}(z \mid x)p_{\theta}(y_i \mid x, z, y_{1:i-1})
  • pθ(yix,y1:i1)p\prime_{\theta}(y_i \mid x, y_{1:i-1}) 를 다음 토큰을 생성하는 확률 계산에 삽입하여 사용

RAG-Sequence

  • RAG-Sequence의 경우 우도 p(yx)p(y|x)는 일반적인 토큰 당 우도로 분할되지 않아서 단일 beam search로는 풀수가 없음
  • 각 문서 z에 대해서 beam search를 수행하고 각 후보 문장(hypothesis)를 pθ(yix,y1:i1)p_{\theta}(y_i \mid x, y_{1:i-1})로 scoring
    • 위 과정으로 hypothesis의 집합 Y를 구함
    • 일부는 모든 문서의 beam에서 등장하지 않았을수도 있음
  • 특정 hypothesis y의 확률을 구하기 위해서 해당 hypothesis y가 beam에 나타나지 않는 각 문서 z에 대해서 추가적인 forward pass를 실행하고 generator 확률 pη(zx)p_\eta(z|x)를 곱한 후 marginal 확률을 계산하기 위해 모든 beam(beam search 과정에서 선택된 상위 k개의 후보 문장 즉, hypothesis)에 걸쳐 확률을 합산한다.
  • 위 과정을 Thorough Decoding이라 한다
  • 더 긴 출력 시퀀스의 경우 |Y|가 커져서 많은 forward pass가 필요
  • 효율적인 decoding을 위해 pθ(yx,zi)0p_\theta (y|x,z_i) \approx 0이라 가정해서 y가 beam search 중 xxziz_i에서 생성되지 않은 경우 추가적인 forward pass가 필요 없도록 할 수 있다.
  • 이 과정을 "Fast Decoding"이라 한다

RAG-Sequence 추가 설명

보통 시퀀스(문장) 생성 모델에서는 출력 시퀀스 y를 생성할 때 각 토큰 yiy_i의 확률을 독립적으로 계산한다. 예를 들어서 p(yx)=p(y1x)p(y2x,y1)p(y3x,y1,y2)p(y|x) = p(y_1|x) \centerdot p(y_2|x,y_1) \centerdot p(y_3|x,y_1,y_2) 처럼 각 토큰별로 확률을 계산하고 곱해서 전체 시퀀스 확률을 구한다.

근데 RAG-Sequence 모델에서는 하나의 문서 z를 선택해서 전체 시퀀스를 생성한다. 이때 전체 시퀀스 y의 확률 p(yx)p(y|x)는 선택된 문서 z에 대한 확률 pθ(yx,z)p_\theta(y|x,z)와 문서 z가 선택될 확률 pη(zx)p_\eta(z|x)로 계산된다. 하지만 이 확률이 각 토큰별 확률로 나뉘지 않기 때문에 일반적인 방법으로 처리가 안된다.

일반적인 시퀀스 생성 모델에서는 single beam search를 사용해서 각 토큰을 생성하면서 최적의 문장을 찾을 수 있는데 RAG-Sequence는 전체 시퀀스의 확률을 계산하기 위해서 각 문서에 대해서 별도로 beam search를 계산해야한다.

3. Experiments

  • 논문의 저자들은 다양한 knowledge intensive task로 RAG를 실험함
    • knowledge-intensive tasks란 인간이 외부 지식 소스에 접근 없이 합리적으로 수행하기 힘든 작업
  • non-parametric knowledge source로 Wikipedia dump 사용
    • 100 단어씩 chunk로 만들어서 벡터화
  • 각 문서를 document encoder로 임베딩한 후 FAISS로 MIPS index 생성함

3.1 Open-domain Question Answering

  • 현실에서 중요하고 knowledge-intensive task에서 흔히 사용되는 testbed
  • 질문과 답변을 input-output text 쌍(x,y)로 취급하고 RAG를 훈련
  • RAG의 parametric ability와 non-parametric ability를 각각 비교함
  • RAG의 non-parametric한 성능을 비교하기 위해서 extractive QA paradigm들과 비교함
    • extractive QA paradigm의 답변은 검색된 문서에서 추출한 내용 즉, non-parametric 지식에만 기반한 답변
  • RAG의 parametric 성능을 비교하기 위해서 RAG 모델의 retrieval 기능을 사용하지 않고 closed book QA 접근 방식과 비교함
    • 4가지 open-domain QA dataset으로 비교
      • Natural Questions(NQ)
      • TriviaQA(TQA)
      • WebQuestions(WQ)
      • CuratedTrec(CT)
  • DPR 논문에서는 작은 데이터셋을 처리할 때 더 큰 데이터셋으로 사전 학습된 모델을 사용하는 방법을 제안했는데 WQ와 CT는 소규모 데이터셋이라 이 방식으로 훈련함
    • 즉, NQ 데이터셋으로 사전 학습된 RAG 모델의 가중치를 사용하여 CT와 WQ 데이터셋에 대한 학습을 시작한다는 의미
  • 정확도는 Exact Match(EM) score로 측정. TQA의 경우 T5와 비교하기 위해서 TQA Wiki test set에서도 평가함

3.2 Abstractive Question Answering

  • RAG 모델들은 simple extractive QA와 다르게 단순한 답변 말고도 추상적인 내용에 대해서도 문장을 생성 가능
  • MSMARCO NLG task v2.1 환경에서 RAG의 NLG 성능을 테스트
    • 질문들, 질문에 대해서 검색해서 얻은 10개의 gold passage, 검색한 gold passage로 생성한 답변으로 구성됨
    • 논문의 저자들은 제공된 passage는 사용하지 않고 질문과 답변만 사용함
    • MSMARCO에는 gold passage 없이는 답변하기 어려운 질문들이 포함돼서 gold passage가 없으면 성능이 낮음
    • 또한, Wikipedia 단독으로는 답변할 수 없는 질문들이 존재
    • 위와 같은 경우에 RAG의 parametric 지식 즉, 언어 모델의 지식으로 합리적인 답변을 생성할 수 있음

3.3 Jeopardy Question Generation

  • QA 외의 RAG의 성능을 평가하기 위해서 open-domain question generation을 실험
    • 짧고 단순한 질문을 포함하는 표준 open-domain QA task 외에 jeopardy question을 생성하는 task를 도입
    • Jeopardy question이란 엔티티에 대한 정보로 엔티티를 맞힐 수 있는 질문
      • 가령 "1986년에 멕시코가 이 국제 스포츠 대회를 2번 개최한 최초의 국가가 되었습니다" 라는 질문이 Jeopardy Question이고 이 질문의 답은 "World Cup"이다.
    • Jeopardy Question은 정교하고 사실적인 문장으로 이루어져있기 때문에 답변 엔터티를 조건으로 Jeopardy 질문을 생성하는 건어려운 knowledge intensive generation task를 구성
  • 비교를 하기 위해 BART 모델을 훈련함
  • SearchQA의 일부를 사용해서 훈련 및 검증
  • Q-BLEU-1로 평가 -> BLEU보다 엔티티 매칭에 대한 가중치가 더 높고 표준 메트릭보다 사람의 판단과 높은 상관 관계를 가짐
  • Generation factuality와 specificity에 대해서 인간 평가가 수행됨
    • Factuality : 신뢰할 수 있는 외부 자료에 근거한 답변인지
    • Specificity : input과 output 사이에 높은 상호 의존이 있는지
  • 인간 평가자는 답과 두개의 생성된 질문(BART와 RAG)을 비교해서 4가지 옵션중에 하나를 택한다
    • A가 낫다
    • B가 낫다
    • 둘다 좋다
    • 둘다 별로

3.4 Fact Verification

  • FEVER는 자연어 주장이 Wikipedia에 의해서 지지되거나 반박되는지 또는 결정하기 충분한 정보가 없는지를 분류
    • 이 task는 wikipedia에서 증거를 검색하고 증거를 추론해서 주장의 참,거짓 또는 wikipedia 만으로 판단할 수 없는지 분류
    • 검색 문제와 추론이 수반된 task
    • 이를 통해 RAG의 classification 성능 실험 가능
  • FEVER 클래스 레이블(지지, 반박, 증거 불충분)을 하나의 output token에 맵핑시켜서 claim-class 쌍으로 직접 훈련함
  • Thorne과 Vlachos에서 연구된 표준 3방향 분류 작업(지지/반박/증거 불충분)과 2방향 분류(지원/반박) 작업의 두 가지 변형을 탐구

4. Results

4.1 Open-domain Question Answering

  • 위의 Table 1은 RAG와 SOTA 모델들의 성능을 비교한 표이다.
  • RAG 모델의 성능이 가장 높은걸 확인할 수 있는데 이는 RAG가 closed-book 즉, parametric memory의 능력과 retrieval 기반의 open-book 즉, non-parametric memory의 성능을 합친 유연성 덕분
  • REALM, T5+SSM과 다르게 비용이 비싼 “salient span masking” pre-training 없이도 RAG는 성능이 좋음
  • RAG는 BERT 기반 "cross encoder"를 사용해서 extractive reader와 함께 문서를 re-rank 하는
    DPR QA 시스템에 비해 좋음
    -> RAG는 re-ranker나 extractive reader 없이도 SOTA 성능 뽑음 => 이 부분은 논의가 필요
  • 답변을 추출할 수 있어도 생성을 하는게 유리한 경우가 존재
    • 답에 대한 단서가 있지만 답이 그대로 포함되어 있지 않은 문서도 정답 생성에 기여할 수 있음
      • 이는 기존의 extractive 접근 방식으로는 불가능
  • RAG 모델의 경우 검색 문서에 정답이 없어도 정답을 생성함
    • NQ 데이터셋에서 위와 같은 경우에도 11.8%의 정확도를 달성함
    • extractive 모델들은 0% 성능(당연하게)

4.2 Abstractive Question Answering

  • Table 2에서 보이듯이 RAG sequence 모델이 BART 모델보다 성능 좋음
    • MSMARCO 데이터셋에서 Rouge-L, Bleu 지표에서 성능이 높음
  • SOTA 모델에 비해서 성능이 낮은 건 사실이지만 주목할 점은 SOTA 모델은 gold passage가 제공되었고 RAG 모델은 그렇지 않다는 점
    1. SOTA 모델들은 reference answer를 생성하기 위한 정보가 포함된 gold passage가 있었고
    2. 대부분의 질문은 gold passage 없이는 답변할 수 없고
    3. 일부 질문들은 Wikipedia 정보만으로 답변할 수 없었다는 점
  • 질적으로 RAG 모델이 BART보다 hallucination이 덜하고 사실적으로 정확한 텍스트럴 더 자주 생성함
  • RAG의 생성이 BART 보다 더 다양함

4.3 Jeopardy Question Generation

  • Table 2에서 보이듯이 RAG-Sequence 모델이 BART보다 성능이 좋음
  • Table 4에 의하면 인간 평가자 평가의 결과를 확인할 수 있는데 평가자들은 RAG를 더 사실적이고 specific하다 라고 평가한 걸 확인할 수 있다.
  • 저자들은 Jeopardy question에 대해서 RAG-Token 방식이 더 나은 성능을 보인 이유로 Jeopardy question은 2가지 서로 다른 정보로 이루어져있는데 RAG-Token은 여러 문서의 정보를 토대로 답변을 생성하므로 더 좋은 성능을 보였다고 판단함

  • 예를 들어 Figure 2에서 "Sun"을 생성할때 Doc2가 진한 것을 확인할 수 있는데 Doc2에는 "The Sun Also Rises"라는 내용이 포함되어 있고 "A Farewell to Arms"를 생성할때는 동일한 내용이 포함된 Doc1의 posterior가 높은 것을 확인할 수 있다.
    • 여기서 posterior는 검색된 문서가 총 5개라고 하면 특정 토큰(예를 들어 Sun이나 A)가 입력값으로 들어왔을때
      문서 z1~z5가 선택될 확률
  • 각 책의 첫 토큰 생성후 문서의 posterior가 flatten 되는데 이 말은 generator가 문서에 의존해서 문장을 생성하지 않을 수 있다는 의미 = 책 제목 완성하는데 parametric knowledge면 충분
    • 이 주장을 뒷받침하기 위해서 BART로 "The Sun"을 시작으로하는 문장을 완성시키자 "The Sun Also Rises" is a novel by this author of "The Sun Also Rises" 라는 문장을 생성함
    • 이는 BART의 parameter에 "The Sun Also Rises"라는 정보가 저장됨을 의미
  • 이 예시를 통해서 parametric memory와 non-parametric memory가 함께 문장을 생성함을 확인함

4.4 Fact Verification

  • Table 2의 FEVER의 결과를 보면 3-way classification(FVR3)의 경우 SOTA 모델에 비해서 4.3% 낮다
    • 하지만, SOTA 모델들의 경우 복잡한 파이프라인을 거치고 retrieval supervision을 포함한 반면
      RAG는 해당 과정이 없었다는 점에서 의미가 있다
  • 2-way classification(FVR2)의 경우 SOTA 모델에 비해 2.7% 낮다
    • SOTA 모델(RoBERTa)은 주요 근거가 제공된 상태에서 주장의 참 거짓을 판별한 반면 RAG는 스스로 주장을 기반으로 근거를 검색함
  • 추가적으로 RAG로 검색한 문서가 FEVER의 gold evidence(주요 근거)와 일치하는지 분석함
    • Top-K 검색 문서와 gold evidence의 제목의 overlap을 계산
    • 검색된 상위 문서들의 경우 71%의 사례에서 gold article로 부터 생성된 문서였고,
      90%의 사례에서 상위 10개 문서에 gold article이 있었다

6. Discussion

본 연구에서는 parametric memory와 non-parametric memory를 결합한 hybrid generation model을 제시함.

RAG가 QA task에서 SOTA 성능을 달성했고 인간 평가자가 BART에 비해서 RAG를 더 구체적이고 사실적이라고 평가함을 확인함. 향후 연구로는 2가지 구성 요소를 함께 처음부터 pre-train하는 방법을 제안함. 이 연구는 parametric and non-parametric memory가 상호작용하면서 더 좋은 성능을 내는 것을 증명함으로써 새로운 연구 방향을 제시함

6.1 Broader Impact

RAG는 실제 사실 정보를 기반으로 생성하기 때문에 hallucination이 적고 해석가능성이 크며 제어가 가능하다.

따라서, 의료 같은 다양한 분야에 활용할 수 있다.

물론, 단점도 존재하는데 외부 지식 소스가 완벽할 수 없고 편향에서 자유롭지 못하다는 것이다.

뉴스나 소셜미디어에 가짜정보를 생산할 수 있고 다른 사람을 사칭하거나 spam/phishing 컨텐츠의 생산을 자동화할 수 있다.


논문을 이해하기 위한 추가 개념

Marginalization?

사건 A와 사건 B가 동시에 발생할 확률을 Joint Probability 또는 결합확률이라 하고 P(A,B)P(A,B) 또는 P(AB) P(A \cap B) 라고 하기도 한다.

결합확률에서 어떤 변수 하나에 대한 확률을 계산하는 방법이다. 주목하지 않는 변수를 Marginalization 또는 주변화한다고 한다.

XX에 주목한다면 X X 가 아닌 변수들을 주변화하여 P(X)P(X)를 구한다.

이를 다변수 확률분포로 일반화해서 n개의 변수중에서 k번째 확률만 구할때 나머지 변수들을 주변화한다.

이산적인 경우

연속확률분포

이렇게 다른 변수들을 주변화해서 얻은 확률 P(X)P(X)를 주변확률이라 한다.

조건부 확률(conditional probability) P(YX)P(Y|X)는 X일때 Y일 확률이다.

그리고 확률의 곱셈정리 P(YX)=P(X,Y)P(X)P(Y|X) = \frac{P(X,Y)}{P(X)}에 의해서 joint probability는 conditional probability와 marginal
probability의 곱이라는 것을 확인할 수 있다.

요약

Marginalization은 어떤 변수에 대해서 주변 확률을 계산하는 과정

예를 들어, p(yx)p(y|x)를 계산할때 문서 z를 잠재 변수로 간주하고, 모든 가능한 문서에 대해서 p(y,zx)p(y,z|x)를 합산해서 p(yx)p(y|x)를 계산

즉, p(yx)=zp(y,zx)=/sumzp(yx,z)p(zx)p(y|x) = \sum_z p(y,z|x) = /sum_z p(y|x,z)p(z|x)

Sparse Representation vs Dense Representation

Sparse Representation

  • 단어를 sparse vector로 표현하는 기법으로 one-hot encoding의 결과라고 생각하면 된다.
    • 예를 들어, apple, banana, carrot이 있을때 apple = [1,0,0], banana = [0,1,0], carrot = [0,0,1] 로 표현하는 것
  • Sparse vector의 경우 대부분 0으로 표현하므로 공간적으로 낭비가 심하다.
  • 또한, 단어간의 관계를 표현하는데 한계가 존재한다.

Dense Representation

  • Sparse vector를 더 작은 차원의 vector 즉, Dense vector로 표현하는 방법
  • 고차원의 연속 벡터로 표현되며, 벡터의 모든 요소가 실수 값을 가지는 것을 의미
  • 예를 들어, apple = [0.1,0.4,0.7], banana = [0.1,0.6,0.3], carrot=[0.1,0.1,0.1]로 표현하는 방식

검색 기법(MIPS, MCSS)

  • MIPS는 Maximum Inner Product Search의 약자로 MIPS의 목적함수는 argmaxqTviviV\underset{v_i \in V}{\arg\max_q^Tv_i} 로 내적의 값이 큰 문서를 검색
    • 벡터의 크기에 영향을 받음
    • 벡터의 크기가 중요한 경우 MIPS를 사용
  • MCSS는 Maximum Cosine Similarity Search의 약자로 MCSS의 목적함수는 argmaxqTviqviviV\underset{v_i \in V}{\arg\max\frac{q^Tv_i}{||q|| ||v_i||}} 이고 코사인 유사도가 큰 문서를 검색
    • 벡터의 크기에 영향을 받지 않음

Cross Encoder vs Bi-Encoder

Cross Encoder란?

  • BERT를 이용해서 문장 간 유사도를 구할때 두 문장을 동시에 트랜스포머 네트워크에 전달하고 두 문장 사이의 유사성을 의미하는 0~1 사이의 출력값을 생성.
  • 장점 : 문장이 변형되지 않은 상태에서 비교해서 정보 손실이 없음
  • 단점 : 연산량이 너무 많다. n개의 문장을 비교하는 경우 nC2 즉, /fracn(n1)2/frac{n(n-1)}{2} 번의 계산이 필요하다.

BiEncoder란?

  • 두 문장을 비교하기 위해서 임베딩된 문장을 생성하는데 두 문장을 독립적으로 트랜스포머 네트워크에 전달해서 서로 다른 임베딩을 생성
  • 이렇게 생성한 문장 임베딩을 코사인 유사도로 비교
  • 장점 : 빠르다
  • 단점 : 임베딩 생성시 정보 손실이 발생해서 Cross-Encoder보다는 성능이 낮다

Decoding 방법론

Greedy Decoding

  • test time에는 다음 단어를 생성하는 과정을 반복적으로 수행해서 문장을 생성하는데 매 time step마다 가장 높은 확률을 가지는 단어를 선택해서 decoding을 진행하는 방법
  • 문장 전체를 보고 판단하지 않고 매 순간마다 가장 그럴듯한 단어를 선택
  • Greedy Decoding은 한번 단어를 잘못 선택하면 잘못 선택한 상태에서 다음을 또 예측하므로 점점 잘못됨
  • 이를 개선하기위해 등장한게 Exhaustive Search Decoding

Exhaustive Search Decoding

  • 지금 시점에서 단어 예측시 뒤에 올 단어들의 joint probability가 최대가 되게 하는 단어를 선택

  • 예를 들어, P(y1x)P(y_1|x) 계산 시에 해당 시점에서 최대 확률은 아니더라도 P(y2x)...P(yty1,...yt1,x)P(y_2|x) ...P(y_{t}|y_1,...y_{t-1},x)를 고려해서 확률이 가장 큰 단어를 선택
  • Vocabulary 사이즈가 V이고 time step이 t일때 VtV^t번 계산해야하기에 너무 비효율적
  • 이를 해결하기위해 등장한게 Beam Search Decoding

Beam Search Decoding

  • 매 time step에 k개의 경우의 수만 고려해서 다음 단어를 예측하는 방법
  • time step 마다 누적 확률을 구해서 상위 k개를 남기고 제거
  • k를 beam size라 함
  • Exhaustive Search Decoding처럼 모든 경우의 수를 고려해서 global optimal solution은 아니지만 효율적

Greedy Decoding의 경우 eos 토큰이 나오면 문장 생성이 끝나지만 beam search decoding의 경우 time step마다 k(beam size)개의 서로 다른 후보 문장이 생성되기에 eos 토큰이 생성되는 시점이 서로 다를 수 있다.

따라서, eos가 생성되어서 먼저 종료된 문장은 저장하고 나머지에서 eos가 나올때까지 beam search를 진행한다.

보통 사전에 설정한 최대 time step에 도달하거나 k개의 후보 문장이 토큰을 가지면 멈춘다.

Beam Search Decoding이 완료되면 joint probability가 최대인 후보 문장을 선정한다.

이때, 후보 문장의 단어 개수가 서로 달라서 문제가 발생하는데 문장마다 문장을 구성하는 단어의 개수가 다르면 단어가 많은 문장이 score가 낮게 책정돼 불리하므로 joint probability를 단어의 개수로 나누어 정규화한다.

-> 각 단어마다 log를 취한 확률값은 음수니까 단어가 많으면 더 많이 작아진다

Likelihood & Probability

  • Probability는 주어진 확률 분포가 고정된 상태에서, 관측되는 사건이 변화될 때, 확률을 표현하는 단어
  • Likelihood는 관측된 사건이 고정된 상태에서, 확률 분포가 변화될 때(=확률 분포를 모를 때 = 가정할 때), 확률을 표현하는 단어

Extractive Question Answering

  • 구글에서 어떤 내용을 검색할때 관련 페이지(사이트) 뿐만 아니라 우리가 질문한 내용에 대한 답변이 하이라이트 된 걸 확인할 수 있다.

  • 이처럼 관련된 문서에서 사용자의 질문에 해당되는 내용(텍스트 조각)을 추출하는 작업

MSMARCO

  • Question Answering과 Passage re-ranking 데이터셋 존재
  • 쿼리에 대해 10개의 문서가 주어졌을 때 가장 관련있는 문서를 선택하여 답변을 찾음

Exact Match란?

  • EM 점수(Exact Match Score)는 질문 응답(QA) 시스템의 성능을 평가하는 지표 중 하나
  • EM 점수는 모델이 예측한 답변이 정답과 정확히 일치하는지 여부를 측정
    • "정확히 일치"는 공백, 대소문자, 구두점 등을 포함하여 모든 문자가 동일해야 함을 의미
    • 평균 계산: 모든 질문에 대해 정확히 일치하는 답변의 비율을 계산하여 전체 데이터셋에 대한 EM 점수 계산
    • EM Score = Number of Exact Matches / Total number of questions
  • 가령 "What is the capital of France?"란 질문에 대해서 정답이 paris일때 모델의 답변이 paris면 EM Score = 1.0이고 paris. 이라 답하면 0.0이 된다.

데이터셋

Natural Questions

  • 자연어 질문, 위키피디아 페이지, 긴 답변, 짧은 답변, 스텟등이 포함된 데이터셋

TriviaQA

  • 자연어 질문, 정답, 근거가 포함된 데이터셋
  • 질문당 평균 6개의 근거 문서가 포함됨
  • 사람이 직접 주석을 다는 대신, 독립적으로 수집된 문서에서 질문과 관련된 답변을 자동으로 추출하여 학습 데이터로 사용함

WebQuestions

  • 근거 URL, 질문, 답변이 포함된 데이터셋

CuratedTrec

https://arxiv.org/pdf/1704.00051

  •  

Reference

주변확률(marginal probability)와 확률의 주변화(marginalization)
Marginalization
sparse, dense representation
MIPS & MCSS
MIPS & MCSS
Cross Encoder & Bi-Encoder
Cross Encoder & Bi-Encoder
Decoding
Decoding
Likelihood & Probability
Extractive QA
MSMARCO NLG task v2
Exact Match
Natural Questions
Trivia QA
WebQuestions
Trec

728x90

How to route between sub-chains

라우팅을 사용하면 이전 단계의 결과가 다음 단계를 정의하는 "non-deterministic(비결정적) chain"을 구현 할 수 있습니다.
라우팅은 state(상태)를 정의하고 상태에 대한 정보를 모델 호출 시에 context로 사용할 수 있여 모델과의 상호작용에 대한 구조와 일관성을 제공할 수 있습니다.

라우팅을 사용하는 두가지 방법이 있는데

  1. 조건부로 RunnableLambda에서 runnable을 반환
  2. Runnable Branch 사용(과거 방식)

본 실습에서는 두가지 방법을 모두 사용합니다.

두 단계로 구성되는데 첫 단계에서는 입력된 질문을 Langchain, Anthropic 혹은 Other로 분류하고,
두번째 단계에서는 상응하는 Prompt Chain으로 라우팅 하는 것입니다.

실습

우선 입력된 질문을 Langchain, Anthropic, Other로 분류하는 chain을 생성합니다.

from langchain_anthropic import ChatAnthropic
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import PromptTemplate

chain = (
    PromptTemplate.from_template(
        """Given the user question below, classify it as either being about `LangChain`, `Anthropic`, or `Other`.

Do not respond with more than one word.

<question>
{question}
</question>

Classification:"""
    )
    | ChatOpenAI(model_name = 'gpt-3.5-turbo')
    | StrOutputParser()
)

chain.invoke({"question": "how do I call LangChain?"})

결과

langchain

chain의 역할은 Prompt를 통해서 사용자의 질문이 어떤 것에 관련된 질문인지 파악하는 것 입니다

Create SubChain

이제 3가지 subchain을 생성할 차례입니다.

langchain_chain = PromptTemplate.from_template(
    """You are an expert in langchain. \
Always answer questions starting with "As Harrison Chase told me". \
Respond to the following question:

Question: {question}
Answer:
"""
) | ChatOpenAI(model="gpt-3.5-turbo")

anthropic_chain = PromptTemplate.from_template(
    """You are an expert in anthropic. \
Always answer questions starting with "As Dario Amodei told me". \
Respond to the following question:

Question: {question}
Answer:"""
) | ChatOpenAI(model="gpt-3.5-turbo")

general_chain = PromptTemplate.from_template(
    """Respond to the following question:

Question: {question}
Answer:"""
) | ChatOpenAI(model="gpt-3.5-turbo")

각각의 체인 별로 prompt에 대해서 변화를 주었습니다.

1. Custom Function 사용(추천)

서로 다른 output에 대해서 사용자설정 함수를 라우팅할 수 있다.

def route(info):
    if "anthropic" in info['topic'].lower():
        return anthropic_chain
    elif "langchain" in info['topic'].lower():
        return langchain_chain
    else:
        return general_chain

route 함수의 역할은 info 딕셔너리에서 topic key로 값을 찾고 소문자로 만들었을때

그 정보가 anthropic 이면 anthropic_chain을 반환하고 langchain이면 langchain_chain을 반환합니다.

from langchain_core.runnables import RunnableLambda

full_chain = {'topic' : chain, 'question' : lambda x : x['question']} | RunnableLambda(route)

chain의 결과로 question이 들어오면 해당 question을 chain의 input으로 넣고 그 output을 "topic"의 value 값으로 설정한다.

이후 {'topic' : chain의 결과, 'question' : 사용자의 질문} 을 route 함수의 매개변수로 전달해서 route 함수를 실행한다.

full_chain.invoke({"question": "how do I use Anthropic?"})

결과

AIMessage(content='As Dario Amodei told me, you can use Anthropic by applying the principles of anthropic reasoning to understand the role of observers in the universe and how our existence shapes the laws of physics. This can help in exploring questions related to the nature of consciousness, the fine-tuning of the universe, and the implications of our presence in the cosmos.', response_metadata={'token_usage': {'completion_tokens': 72, 'prompt_tokens': 47, 'total_tokens': 119}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-912c72d0-ab79-4943-a13c-4fb1c533370f-0', usage_metadata={'input_tokens': 47, 'output_tokens': 72, 'total_tokens': 119})
full_chain.invoke({"question": "how do I use LangChain?"})

결과

AIMessage(content='As Dario Amodei told me, you can use Anthropic by applying the principles of anthropic reasoning to understand the role of observers in the universe and how our existence shapes the laws of physics. This can help in exploring questions related to the nature of consciousness, the fine-tuning of the universe, and the implications of our presence in the cosmos.', response_metadata={'token_usage': {'completion_tokens': 72, 'prompt_tokens': 47, 'total_tokens': 119}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-912c72d0-ab79-4943-a13c-4fb1c533370f-0', usage_metadata={'input_tokens': 47, 'output_tokens': 72, 'total_tokens': 119})
full_chain.invoke({"question": "how do I use LangChain?"})

결과

AIMessage(content='4', response_metadata={'token_usage': {'completion_tokens': 1, 'prompt_tokens': 24, 'total_tokens': 25}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-f1c1ae37-a7cc-4a17-bb0a-68e4d09d4687-0', usage_metadata={'input_tokens': 24, 'output_tokens': 1, 'total_tokens': 25})

2. Using a RunnableBranch

Runnable Branch는 input에 따라 조건과 runnable을 정의할 수 있는 특별한 종류의 runnable

이는 위에서 설명한 custom function으로 가능한 일들이라 custom function을 사용하는 것을 권장

RunnableBranch는 (condition, runnable) 쌍의 목록과 기본 실행 가능한 작업으로 초기화됨.

입력과 함께 각 조건을 전달하여 어떤 branch를 선택할지 결정

첫 번째로 True를 반환하는 조건을 선택하여 해당 조건과 연결된 runnable을 입력과 함께 실행

제공된 조건 중 어떤 것도 일치하지 않으면 기본 runnable을 실행

from langchain_core.runnables import RunnableBranch

branch = RunnableBranch(
    ( lambda x : "anthropic" in x["topic"].lower(), anthropic_chain),
    ( lambda x : "langchain" in x["topic"].lower(), langchain_chain),
    general_chain,
)
full_chain = {"topic": chain, "question": lambda x : x['question']} | branch
full_chain.invoke({"question": "how do I use Anthropic?"})

결과

AIMessage(content='As Dario Amodei told me, you can use Anthropic by applying its principles and theories to study the relationship between humans and their environments, as well as the impact of human activities on the world around us. This can involve research, analysis, and critical thinking to better understand our place in the world and how we can work towards a more sustainable and harmonious existence.', response_metadata={'token_usage': {'completion_tokens': 76, 'prompt_tokens': 47, 'total_tokens': 123}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-9c9a4c74-9b79-4cb7-824b-6d3c830fd69a-0', usage_metadata={'input_tokens': 47, 'output_tokens': 76, 'total_tokens': 123})
  1. full_chain에서는 chain으로 topic의 값을 생성한 후 branch에 "topic"과 "question을 key로 하는 딕셔너리 객체를 branch에 전달
  2. branch에서는 topic을 키로 검색해서 해당하는 chain을 실행한다.
full_chain.invoke({"question": "how do I use LangChain?"})

결과

AIMessage(content='As Harrison Chase told me, to use LangChain, you need to first create an account and log in. Then, you can input text or upload documents to be analyzed for linguistic patterns and insights. You can also explore the various features and tools available on the platform to enhance your language analysis capabilities.', response_metadata={'token_usage': {'completion_tokens': 60, 'prompt_tokens': 44, 'total_tokens': 104}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-335488fb-1ead-4e2c-811b-aea768653bb2-0', usage_metadata={'input_tokens': 44, 'output_tokens': 60, 'total_tokens': 104})
full_chain.invoke({"question": "whats 2 + 2"})

결과

AIMessage(content='4', response_metadata={'token_usage': {'completion_tokens': 1, 'prompt_tokens': 24, 'total_tokens': 25}, 'model_name': 'gpt-3.5-turbo', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-3f4c3f6e-92d2-45d9-8b49-6359bcb9e26b-0', usage_metadata={'input_tokens': 24, 'output_tokens': 1, 'total_tokens': 25})

3. Routing by Semantic Similarity

Embedding을 사용해서 사용자의 query를 가장 관련성 높은 prompt에 라우팅할 수 있다.

from langchain_community.utils.math import cosine_similarity
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnableLambda, RunnablePassthrough
from langchain_openai import OpenAIEmbeddings

physics_template = """You are a very smart physics professor. \
You are great at answering questions about physics in a concise and easy to understand manner. \
When you don't know the answer to a question you admit that you don't know.

Here is a question:
{query}"""

math_template = """You are a very good mathematician. You are great at answering math questions. \
You are so good because you are able to break down hard problems into their component parts, \
answer the component parts, and then put them together to answer the broader question.

Here is a question:
{query}"""

embeddings = OpenAIEmbeddings()
prompt_templates = [physics_template, math_template]
prompt_embeddings = embeddings.embed_documents(prompt_templates)

우선 physics_templatemath_template을 작성하고 Embedding Model 객체를 생성한다.

prompt_templates라는 리스트를 만들고 prompt template을 저장합니다.

이후 Embedding Model로 Prompt Template 들의 임베딩을 생성합니다

def prompt_router(input):
    query_embedding = embeddings.embed_query(input["query"])
    similarity = cosine_similarity([query_embedding],prompt_embeddings)[0]
    most_similar = prompt_templates[similarity.argmax()]
    print("Using MATH" if most_similar == math_template else "Using PHYSICS")
    return PromptTemplate.from_template(most_similar)

chain = (
    {"query" : RunnablePassthrough()}
    | RunnableLambda(prompt_router)
    | ChatOpenAI(model = "gpt-3.5-turbo")
    | StrOutputParser()
)
  1. 우선 prompt_router 함수는 사용자의 질문이 담긴 딕셔너리 객체를 입력으로 받고 해당 질문을 임베딩한다.
  2. 질문 임베딩과 template 임베딩을 비교해서 가장 비슷한 template을 PromptTemplate 객체로 만들어서 반환한다.
  3. 이후 RunnableLambdaprompt_router 함수를 넣어서 chain을 구성한다.

따라서, query를 넣어서 invoke하면 질문과 관련이 높은 Prompt Template을 사용하여 자동으로 답변을 생성한다.

print(chain.invoke("What's a black hole"))

결과

Using PHYSICS
A black hole is a region in space where the gravitational pull is so strong that nothing, not even light, can escape from it. It is formed when a massive star collapses in on itself and the remaining mass is compressed into a very small space. The center of a black hole is called a singularity, where the laws of physics as we know them break down. Black holes can vary in size, with supermassive black holes found at the centers of galaxies and smaller stellar black holes formed from the remnants of massive stars.
print(chain.invoke("What's a path integral"))

결과

Using MATH
A path integral is a concept in mathematics and physics, specifically in the field of quantum mechanics. It involves integrating along all possible paths that a particle could take from one point to another, taking into account the probability amplitudes associated with each possible path. This approach allows for the calculation of quantum mechanical quantities such as the probability of a particle transitioning from one state to another. The mathematics behind path integrals can be quite complex, but they provide a powerful tool for understanding the behavior of particles at the quantum level.

 

'인공지능 > RAG' 카테고리의 다른 글

LangChain - Dynamic Chain  (1) 2024.06.04
LangChain - Runnable  (0) 2024.06.04
LangChain - RunnablePassTrough  (0) 2024.06.04
LangChain (11) 오픈소스 LLM으로 RAG 구현  (0) 2024.06.04
LangChain (10) Gemini로 RAG 구현  (0) 2024.06.04
728x90

How to create a dynamic (self-constructing) chain

  • chain의 일부를 입력값에 따라 runtime에 할당하고 싶을때 사용하는 기법이다.
  • RunnableLambda의 속성을 활용해서 Dynamic chain을 구성 할 수 있다.

실습

from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model = "gpt-3.5-turbo")
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import Runnable, RunnablePassthrough, chain

contextualize_instructions = """
Convert the latest user question into a standalone question given the chat history. 
Don't answer the question, return the question and nothing else (no descriptive text).
"""

contextualize_prompt = ChatPromptTemplate.from_messages(
    [
        ("system", contextualize_instructions),
        ("placeholder", "{chat_history}"),
        ("human","{question}"),
    ]
)

contextualize_question = contextualize_prompt | llm | StrOutputParser()

위에서 contextualize_instructions를 통해서 채팅 기록을 기반으로 새로운 질문을 생성하는 chain을 구성한다.

qa_instructions = (
    """Answer the user question given the following context:\n\n{context}."""
)

qa_prompt = ChatPromptTemplate.from_messages(
    [('system',qa_instructions),('human','{question}')]    
)

Chat Prompt Template을 구성한다.

@chain
def contextualize_if_needed(input_ : dict) -> Runnable:
    if input_.get("chat_history"):
        #이 과정을 통해서 실제 출력값을 생성하는건 아니고 또 다른 Runnable을 반환한다.
        return contextualize_question
    else:
        return RunnablePassthrough()

@chain
def fake_retriever(input_ : dict) -> str:
    return "egypt's population in 2024 is about 111 million"

full_chain = (
    RunnablePassthrough.assign(qusetion = contextualize_if_needed).assign(
        context=fake_retriever
    )
    | qa_prompt
    | llm
    | StrOutputParser()
)
  • 만약에 RunnablePassThrough를 통해서 입력된 dict() 객체에 "chat_history"라는 key가 있다면 contextualize_question이라는 Runnable을 반환한다.
    • 반환된 Runnable은 full chain이 실행되면 스스로 실행된다.
  • 만약에 없다면 RunnablePassThrough를 반환한다.

여기서 Runnable이란 invoked, batched, streamed, transformed and composed 될 수 있는 일의 단위이다.

  • invoke/ainvoke: 하나의 input을 output으로 변경시켜주는 메서드
  • batch/abatch: 여러 개의 input을 output으로 변경시켜주는 메서드
  • stream/astream: 결과물을 stream 해준다.
  • astream_log: 출력 및 입력에서 선택된 중간 결과를 Stream 해준다.

Runnable을 구성하는 주요 primitive(구성 요소)는 RunnableSequence and RunnableParallel이다.

  • RunnableSequence는 한 Runnable의 결과물을 다음 과정의 input으로 사용함으로써 Runnable들을 순차적으로 invoke한다.
    • | 연산자를 사용하거나 Runnable의 list를 통해서 구성한다.
  • RunnableParallel은 동일한 input을 여러 Runnable들에 전달함으로써 Runnable들을 동시에 invoke한다.
    • dict 타입을 활용해서 구성한다.

출처 : 링크

full_chain.invoke(
    {
        "question": "what about egypt",
        "chat_history": [
            ("human", "what's the population of indonesia"),
            ("ai", "about 276 million"),
        ],
    }
)

결과

"Egypt's population in 2024 is about 111 million."
for chunk in contextualize_if_needed.stream(
    {
        "question" : "what about egypt",
        "chat_history": [
            ("human", "what's the population of indonesia"),
            ("ai", "about 276 million"),
        ],
    }
):
    print(chunk)

결과

What
 is
 the
 population
 of
 Egypt
?

'인공지능 > RAG' 카테고리의 다른 글

LangChain - Routing Chain  (1) 2024.06.04
LangChain - Runnable  (0) 2024.06.04
LangChain - RunnablePassTrough  (0) 2024.06.04
LangChain (11) 오픈소스 LLM으로 RAG 구현  (0) 2024.06.04
LangChain (10) Gemini로 RAG 구현  (0) 2024.06.04
728x90

Runnable

Runnable이란 invoked, batched, streamed, transformed and composed 될 수 있는 일의 단위이다.

  • invoke/ainvoke: 하나의 input을 output으로 변경시켜주는 메서드
  • batch/abatch: 여러 개의 input을 output으로 변경시켜주는 메서드
  • stream/astream: 결과물을 stream 해준다.
  • astream_log: 출력 및 입력에서 선택된 중간 결과를 Stream 해준다.

Runnable을 구성하는 주요 primitive(구성 요소)는 RunnableSequence and RunnableParallel이다.

  • RunnableSequence는 한 Runnable의 결과물을 다음 과정의 input으로 사용함으로써 Runnable들을 순차적으로 invoke한다.
    • | 연산자를 사용하거나 Runnable의 list를 통해서 구성한다.
  • RunnableParallel은 동일한 input을 여러 Runnable들에 전달함으로써 Runnable들을 동시에 invoke한다.
    • dict 타입을 활용해서 구성한다.

실습

1. Runnable Sequence

from langchain_core.runnables import RunnableLambda

# | 연산자를 통해 RunnableSequence를 구성하였다
sequence = RunnableLambda(lambda x : x + 1) | RunnableLambda(lambda x : x * 2)
sequence.invoke(1)

1이 들어가서 RunnableLambda에 의해 1+1 연산이 먼저 이루어지고

그 결과인 2가 두번째 RunnableLambda의 입력값으로 들어가서 2 * 2 연산이 이루어진다.

최종 결과는 4

sequence.batch([1,2,3])

앞선 과정이 병렬적으로 동시에 일어난다.

2. Runnable Parallel

# A sequence that contains a RunnableParallel constructed using a dict literal
sequence = RunnableLambda(lambda x: x + 1) | {
    'mul_2': RunnableLambda(lambda x: x * 2),
    'mul_5': RunnableLambda(lambda x: x * 5)
}
sequence.invoke(1)

위 코드는 우선 첫번째 RunnableLambda가 수행된 이후 2가지 RunnableLambda가 병렬적으로 수행된다.

이런 특성을 활용하면 서로 다른 2가지 Retriever를 동시에 적용하여 문서를 검색할 수 있다.

가령 lexical search(키워드 기반)와 semantic search(유사도 기반)를 따로 수행하고 검색한 문서를 기반으로 ReRanking을 수행해서 LLM의 최종 답변을 얻을 수 있다.

3. Runnable Method 사용하기

모든 Runnable은 동작을 수정(실패시 재시도, configurable하게 만들기)하는 데 사용할 수 있는 추가 메서드가 있다.

from langchain_core.runnables import RunnableLambda
import random

def add_one(x: int) -> int:
    return x + 1

def buggy_double(y: int) -> int:
    # 70%의 확률로 fail하는 buggy code
    if random.random() > 0.3 :
        print("This code failed, will probably be retried")
        raise ValueError("Triggerd Buggy Code!")
    return y * 2

sequence = (
    RunnableLambda(add_one) |
    RunnableLambda(buggy_double).with_retry(    # 실패시 재시도하는 메서드
        stop_after_attempt=10,
        wait_exponential_jitter=False
    )
)

print(sequence.input_schema.schema())
print(sequence.output_schema.schema())
print(sequence.invoke(2))

위 코드를 살펴보면 add_one함수는 1을 더해주고 buggy_double 함수는 random 값을 구해서 0.3 이상인 경우
ValueError를 raise하는 코드이다.

RunnableSequence 객체는 input에 대해 add_one 함수를 적용시키고 그 결과를 다시 input으로

buggy_double 함수를 실행하는데 with_retry 메서드를 실행하는데 이 함수는 stop_after_attempt

통해서 몇 번 retry할지 설정할 수 있고 재시도 대기 시간에 wait_exponential_jitter를 통해 jitter를 사용 할 지 설정 할 수 있다.

 

Attributes

InputType The type of input this runnable accepts specified as a type annotation.
OutputType The type of output this runnable produces specified as a type annotation.
config_specs List configurable fields for this runnable.
input_schema The type of input this runnable accepts specified as a pydantic model.
name The name of the runnable.
output_schema The type of output this runnable produces specified as a pydantic model.

Methods

__init__()
abatch(inputs[, config, return_exceptions]) Default implementation runs ainvoke in parallel using asyncio.gather.
abatch_as_completed() Run ainvoke in parallel on a list of inputs, yielding results as they complete.
ainvoke(input[, config]) Default implementation of ainvoke, calls invoke from a thread.
assign(**kwargs) Assigns new fields to the dict output of this runnable.
astream(input[, config]) Default implementation of astream, which calls ainvoke.
astream_events(input[, config, ...]) [Beta] Generate a stream of events.
astream_log() Stream all output from a runnable, as reported to the callback system.
atransform(input[, config]) Default implementation of atransform, which buffers input and calls astream.
batch(inputs[, config, return_exceptions]) Default implementation runs invoke in parallel using a thread pool executor.
batch_as_completed() Run invoke in parallel on a list of inputs, yielding results as they complete.
bind(**kwargs) Bind arguments to a Runnable, returning a new Runnable.
config_schema(*[, include]) The type of config this runnable accepts specified as a pydantic model.
get_graph([config]) Return a graph representation of this runnable.
get_input_schema([config]) Get a pydantic model that can be used to validate input to the runnable.
get_name([suffix, name]) Get the name of the runnable.
get_output_schema([config]) Get a pydantic model that can be used to validate output to the runnable.
get_prompts([config])
invoke(input[, config]) Transform a single input into an output.
map() Return a new Runnable that maps a list of inputs to a list of outputs, by calling invoke() with each input.
pick(keys) Pick keys from the dict output of this runnable.
pipe(*others[, name]) Compose this Runnable with Runnable-like objects to make a RunnableSequence.
stream(input[, config]) Default implementation of stream, which calls invoke.
transform(input[, config]) Default implementation of transform, which buffers input and then calls stream.
with_config([config]) Bind config to a Runnable, returning a new Runnable.
with_fallbacks(fallbacks, *[, ...]) Add fallbacks to a runnable, returning a new Runnable.
with_listeners(*[, on_start, on_end, on_error]) Bind lifecycle listeners to a Runnable, returning a new Runnable.
with_retry(*[, retry_if_exception_type, ...]) Create a new Runnable that retries the original runnable on exceptions.
with_types(*[, input_type, output_type]) Bind input and output types to a Runnable, returning a new Runnable.

 

 

출처 : 링크

'인공지능 > RAG' 카테고리의 다른 글

LangChain - Routing Chain  (1) 2024.06.04
LangChain - Dynamic Chain  (1) 2024.06.04
LangChain - RunnablePassTrough  (0) 2024.06.04
LangChain (11) 오픈소스 LLM으로 RAG 구현  (0) 2024.06.04
LangChain (10) Gemini로 RAG 구현  (0) 2024.06.04
728x90

RunnablePassThrough

chain을 여러 단계로 구성할때 전 단계의 출력값을 바로 다음 단계의 입력값으로 사용해야하는 경우가 있는데

이런 경우에 RunnablePassThrough를 사용하면 됨. 일반적으로 RunnableParallel이랑 같이 사용됨

1. API key 설정

%pip install -qU langchain langchain-openai

import os
from getpass import getpass

os.environ["OPENAI_API_KEY"] = getpass()

2. RunnablePassTrough 구조 살피기

from langchain_core.runnables import RunnableParallel, RunnablePassthrough

runnable = RunnableParallel(
    passed = RunnablePassthrough(),
    modified = lambda x : x["num"] + 1,
)

runnable.invoke({"num": 1})
  • 위 코드에서는 RunnablePassThrough()를 통해서 값을 전달받고 passed 변수에 저장함.
  • 이후 modified에 수정된 값을 저장함.
  • modified에는 람다식으로 앞서 passed 된 값에 1을 더해줌.
    • lambda x : 여기서 x는 입력값
    • x["num"] + 1 : x는 딕셔너리 값이므로 num이라는 key를 가진 value에 1을 더해줌

3. Retrieval Example

from langchain_community.vectorstores import FAISS
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import ChatOpenAI, OpenAIEmbeddings

vectorstore = FAISS.from_texts(
    ["harrison worked at kensho"], embedding=OpenAIEmbeddings()
)
retriever = vectorstore.as_retriever()
template = """Answer the question based only on the following context:
{context}

Question: {question}
"""
prompt = ChatPromptTemplate.from_template(template)
model = ChatOpenAI()

retrieval_chain = (
    {"context" : retriever, "question": RunnablePassthrough()}
    | prompt
    | model
    | StrOutputParser()
)

retrieval_chain.invoke("where did harrison work?")
  • Retrival_chain은 {"context", "question"}, prompt, model, StrOutputParser()로 구성된다.
  • "context"는 vector store에서 검색을 해서 가져오는 문자열
  • "question"은 RunnablePassThrough 즉,invoke() 안의 문자열이다.
  • 이후 "context"와 "question"이 prompt에 formatting 된다.
  • 이후 이 prompt로 model에 질의를 하고
  • StrOutputParser()를 통해서 결과를 문자열로 출력한다.

StrOutputParser()가 없는 경우 아래와 같다.

retrieval_chain = (
    {"context" : retriever, "question": RunnablePassthrough()}
    | prompt
    | model
)
retrieval_chain.invoke("where did harrison work?")

'인공지능 > RAG' 카테고리의 다른 글

LangChain - Dynamic Chain  (1) 2024.06.04
LangChain - Runnable  (0) 2024.06.04
LangChain (11) 오픈소스 LLM으로 RAG 구현  (0) 2024.06.04
LangChain (10) Gemini로 RAG 구현  (0) 2024.06.04
What is Elastic Search?  (0) 2024.06.03
728x90

오픈소스 LLM의 필요성

  • 비용 : API 호출시마다 비용 발생
  • 보안 : 기업 정보 유출 우려
  • 안정성 : API를 관리하는 회사에서 문제가 생기면 서비스 제공이 불가

한국의 오픈소스 LLM

업스테이지와 NIA가 주관하여 한국어 오픈소스 LLM 리더보드 운영중. 이 리더보드를 통해서 최신 오픈소스 LLM들의 성능 비교하고 사용 가능

오픈 소스 LLM의 양자화(Quantization)

컴퓨팅 리소스 부담을 줄이기 위해 양자화를 통해 경량화한 모델을 많이 활용

https://medium.com/@florian_algo/model-quantization-1-basic-concepts-860547ec6aa9

모델 양자화를 통해 가중치 정확도(소수점)을 줄여서 계산량과 저장해야하는 정보를 줄인다.

실습

1단계 - LLM 양자화에 필요한 패키지 설치

  • bitsandbytes: Bitsandbytes는 CUDA 사용자 정의 함수, 특히 8비트 최적화 프로그램, 행렬 곱셈(LLM.int8()) 및 양자화 함수에 대한 경량 래퍼
  • PEFT(Parameter-Efficient Fine-Tuning): 모델의 모든 매개변수를 미세 조정하지 않고도 사전 훈련된 PLM(언어 모델)을 다양한 다운스트림 애플리케이션에 효율적으로 적용 가능
  • accelerate: PyTorch 모델을 더 쉽게 여러 컴퓨터나 GPU에서 사용할 수 있게 해주는 도구
#양자화에 필요한 패키지 설치
!pip install -q -U bitsandbytes
!pip install -q -U git+https://github.com/huggingface/transformers.git
!pip install -q -U git+https://github.com/huggingface/peft.git
!pip install -q -U git+https://github.com/huggingface/accelerate.git

2단계 - 트랜스포머에서 BitsandBytesConfig를 통해 양자화 매개변수 정의하기

  • load_in_4bit=True: 모델을 4비트 정밀도로 변환하고 로드하도록 지정
  • bnb_4bit_use_double_quant=True: 메모리 효율을 높이기 위해 중첩 양자화를 사용하여 추론 및 학습
  • bnd_4bit_quant_type="nf4": 4비트 통합에는 2가지 양자화 유형인 FP4와 NF4가 제공됨. NF4 dtype은 Normal Float 4를 나타내며 QLoRA 백서에 소개되어 있습니다. 기본적으로 FP4 양자화 사용
  • bnb_4bit_compute_dype=torch.bfloat16: 계산 중 사용할 dtype을 변경하는 데 사용되는 계산 dtype. 기본적으로 계산 dtype은 float32로 설정되어 있지만 계산 속도를 높이기 위해 bf16으로 설정 가능
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig

bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_use_double_quant=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16
)

3단계 - 경량화 모델 로드하기

이제 모델 ID를 지정한 다음 이전에 정의한 양자화 구성으로 로드합니다.

model_id = "kyujinpy/Ko-PlatYi-6B"

tokenizer=AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(model_id,quantization_config=bnb_config,device_map="auto")
  • AutoTokenizer.from_pretrained : model_id 에서 사용된 토크나이저를 자동으로 가져온다.
  • AutoModelForCausalLM.from_pretrained : model_id 에서 사용된 언어모델을 자동으로 가져온다.
    • quantization_config : 경량화 기법 어떻게 할건지 정의하는 부분. 우리는 위에서 정의한 BitsAndBytes config를 전달한다.
    • device_map : auto로해서 어떤 gpu에서 돌아갈지 자동으로 설정해준다.
print(model)
  • LLama 기반 모델
  • (embed_tokens): Embedding(78464, 4096, padding_idx=0) 부분을 살펴보면 78464 토큰이 들어오면 4096차원으로 임베딩 시킨다는 뜻이다.
  • 모델을 print를 주는 이유는 양자화 잘 되었는지 확인하기 위해서이다.
    • Attention 부분에 Linear4bit으로 돼있으면 4bit로 양자화가 잘 된거다.

4단계 - 잘 실행되는지 확인

device = "cuda:0"

messages = [
    {"role":"user","content":"은행의 기준 금리에 대해서 설명해줘"}
]

encodes = tokenizer.apply_chat_template(messages, return_tensors="pt")

model_inputs = encodes.to(device)

generated_ids = model.generate(model_inputs,max_new_tokens=1000,do_sample=True)
decoded = tokenizer.batch_decode(generated_ids)
print(decoded[0])
  • device를 cuda 첫번째 걸로 정의(gpu로 설정한다고 생각하자)
  • tokenizer의 apply_chat_template을 통해 messages를 토크나이저에 넣고 인코딩한다.
  • return_tensors = 'pt'로 설정해서 pytorch tensor 형식으로 메시지를 변환해준다.
  • 인코딩한 값을 device(gpu)에 넣는다.
  • 모델의 generate 함수를 통해서 model_input을 넣어서 결과를 출력받는다
    • 최대 토큰 설정
    • sampling은 뭐지?
  • 토크나이저의 batch_decode를 통해서 LLM이 생성한 임베딩을 자연어로 변환해준다.

RAG 시스템 구현

!pip -q install langchain pypdf chromadb sentence-transformers faiss-cpu
from langchain.llms import HuggingFacePipeline
from langchain.prompts import PromptTemplate
from langchain.embeddings.huggingface import HuggingFaceEmbeddings
from transformers import pipeline
from langchain.chains import LLMChain

text_generation_pipeline = pipeline(
    model = model,
    tokenizer=tokenizer,
    task="text-generation",
    temperature=0.2,
    return_full_text = True,
    max_new_tokens=300,
)

prompt_template = """
### [INST]
Instruction: Answer the question based on your bank economy knowledge.
Here is context to help:

{context}

### QUESTION:
{question}

[/INST]
 """

koplatyi_llm = HuggingFacePipeline(pipeline=text_generation_pipeline)

#Create Prompt from Prompt Template
prompt = PromptTemplate(
    input_variables=["context","question"],
    template=prompt_template,
)

#create llm chain
llm_chain = LLMChain(llm=koplatyi_llm,prompt=prompt)
  • HuggingFacePipelineLLMChain은 API를 통해서 답변을 얻으면 필요 없는 라이브러리
  • 모델에 질문을 하고 답변을 받는 일련의 과정을 pipeline을 통해서 수행
  • HuggingFacePipeline을 사용하는 이유는 langchain과 결합할때 허깅페이스 파이프라인과 결합하는게 더 편해서이다.
  • LLMChain을 통해서 어떤 prompt template으로 어떤 llm을 돌리냐만 정의해주면 chain이 완성된다.
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores import FAISS
from langchain.document_loaders import PyPDFLoader
from langchain.schema.runnable import RunnablePassthrough
loader = PyPDFLoader("/content/drive/MyDrive/코딩/LangChain 실습(모두의AI)/data/[이슈리포트 2022-2호] 혁신성장 정책금융 동향.pdf")
pages = loader.load_and_split()

text_splitter = RecursiveCharacterTextSplitter(chunk_size = 500, chunk_overlap=50)
texts = text_splitter.split_documents(pages)

from langchain.embeddings import HuggingFaceEmbeddings

model_name = "jhgan/ko-sbert-nli"
encode_kwargs={"normalize_embeddings":True}
hf = HuggingFaceEmbeddings(
    model_name = model_name,
    encode_kwargs=encode_kwargs
)

db = FAISS.from_documents(texts,hf)
retriever = db.as_retriever(
    search_type = "similarity",
    search_kwargs={'k':3}
)
rag_chain = (
    {"context":retriever,"question":RunnablePassthrough()}
    | llm_chain
)

rag_chain : rag_chain에 들어오는 값을 question으로 삼아서 해당 질문을 토대로 retriever를 통해서 context를 넣을 내용을 가져온다.

그리고 llm_chain으로 들어오는 질문과 context를 넣어서 답변을 생성한다.

import warnings
warnings.filterwarnings('ignore')
result = rag_chain.invoke("혁신성장 정책 금융에서 인공지능이 중요한가?")

for i in result['context']:
    print(f"주어진 근거: {i.page_content} / 출처: {i.metadata['source']} - {i.metadata['page']} \n\n")

print(f"\n답변:{result['text']}")

Reference

모두의 AI[https://youtu.be/04jCXo5kzZE?si=W6eZI0TYRcc28REw]

'인공지능 > RAG' 카테고리의 다른 글

LangChain - Runnable  (0) 2024.06.04
LangChain - RunnablePassTrough  (0) 2024.06.04
LangChain (10) Gemini로 RAG 구현  (0) 2024.06.04
What is Elastic Search?  (0) 2024.06.03
Langchain - Ensemble Retriever  (1) 2024.05.31

+ Recent posts