GraphRAG with Document
비정형 데이터인 문서를 Graph로 표현하고, 이를 RAG 에 활용할 때 고려해야할 요소들에 대해 이야기합니다.
Oct 10, 2024
오늘은 비정형 데이터를 정제해 GraphRAG 활용하는 과정 그리고 그 과정에서 수반되는 세 가지 요소인 체계 구성 방식, Document Hierarchy , Multi-Agetn Design에 대해 알아보겠습니다.
GraphRAG 리마인드 그리고 메타 검색 방식과 대조해보기지식 그래프 구축을 위해 정보체계 온톨로지를 만드는 방식Graph Document HierarchyIntra, 문서 간의 관계Inter, 문서 내부 요소들간의 관계Content Hierarchy, 문서가 이루어진 요소들 중 표 이미지에서 관계를 표현해본다면?코드 설명이미지 추출과 저장주석(설명 문단) 연결Lexical 그리고 Text, 기존 RAG 에서 활용하는 청킹 방식과 GraphRAG에서 Text를 활용한 방식은 무엇이 다를까요?끝으로,본 글은 Open UP(오픈소스 소프트웨어 통합지원센터)로부터 지원받아 작성하였습니다.레퍼런스
GraphRAG 리마인드 그리고 메타 검색 방식과 대조해보기
기존 RAG 와 다르게 ‘연관성’을 데이터에 직접 명시할 수 있다는 점이 GraphRAG의 장점이라 이전 게시물에서 말씀드렸습니다. 지식 베이스를 구축할 때, 분명 연관성이 있는 데이터들일텐데 이를 어떻게 기입하느냐 고민하실때 아마 대다수 분들께서는 ‘메타’ 를 입력하고 관리해주시는 형태로 접근하실 거라 생각해요.
하지만, 그래프는 그 연관성을 ‘엣지 혹은 링크’라는 관점으로 이어주고 이를 LLM에게 context로 준다는 점에서 다른 접근이라고 할 수 있겠습니다. 각 방식마다 모두 장단점이 있을거라 생각되요. 각 방식마다의 장단점을 테이블로 정리해보면 다음과 같아요.
특징 | 메타 스토어 | 그래프 데이터 스토어 |
정의 | 메타데이터(데이터에 대한 데이터)를 구조화하여 저장 | 엔티티를 노드로, 관계를 엣지로 표현하는 그래프 기반의 구조 |
저장 효율성 | 구조화된 메타데이터에 대해 효율적 | 유연성이 높지만, 단순한 쿼리에 대해서는 덜 효율적일 수 있음 |
복잡성 | 더 간단하고 구현이 용이 | 대규모 그래프의 경우 더 복잡해질 수 있음 |
확장성 | 간단하고 평면적인 데이터에 대해 잘 확장됨 | 복잡하고 상호 연결된 데이터에 대해 더 잘 확장됨 |
쿼리 유연성 | 사전에 정의된 메타데이터에 한정 | 매우 유연하며, 복잡한 관계 기반 쿼리가 가능 |
사용 사례 | 구조화된 평면 데이터에 최적화 | 상호 연결되거나 관계 기반 또는 계층적 데이터에 적합 |
RAG와의 통합 | 단순한 검색에 대해 더 쉽게 통합 | 복잡한 지식 기반 검색에서 더 나은 관련성 발견 가능 |
검색 성능 | 기본적이고 구조화된 검색에 대해 더 빠름 | 더 느릴 수 있지만, 관계 기반 검색에서 뛰어남 |
유지보수 | 단순성 덕분에 유지보수가 쉬움 | 데이터 규모가 커질수록 더 많은 유지보수 필요 |
학습 곡선 | 개발자에게 더 낮은 학습 곡선 | 그래프 이론 개념 때문에 더 가파른 학습 곡선 |
메타 스토어를 통해 주로 RAG하는 방식은 Self-querying 이지 않을까 싶어요. 기본 RAG와 동일하게, 지식 베이스를 구축하지만, 각각의 지식베이스에 담긴 정보들의 메타들을 입력하고 이를 Retrieval 단계에서 가져오는거죠. 기존 vector seacrh의 vector similarity 에 메타라는 정보를 추가해서 search 하기에 한 층 더 정확도가 높아진다 라는 장점이 있습니다. 사전에 메타가 구축되어 있거나 구축할 의지가 있다면 굉장히 좋은 방식이긴 하지만, 지식베이스내 대량의 정보들이 있을 경우 이 모든 정보들을 하나하나 살펴보며 메타를 분류하고 입력하는건 오히려 배보다 배꼽이 커질 수 있기에 비용 측면에서 고려해보아야합니다. Langchain 에서 이 부분을 이야기 했는데요. 자세한 내용은 How to do "self-querying" retrieval 링크에서 살펴 볼 수 있습니다 .
지식 그래프 구축을 위해 정보체계 온톨로지를 만드는 방식
위 메타 검색 방식에도 메타 구축에 비용이 필요하다면, 그래프도 마찬가지로 비용이 듭니다. 바로 온톨로지 , 데이터 내 지식 체계를 구축해야한다는거죠. 단계는 다음과 같습니다.
1. 용어집 구축 (Build glossary of terms)
온톨로지를 설계하기 위한 첫 번째 단계는 도메인에 속하는 핵심 용어들을 정의하는 것입니다. 이는 지식 그래프에서 중요한 개념과 키워드를 명확히 하고, 나중에 개념을 정의하고 관계를 구축하는 데 도움을 줍니다. 각 용어는 명확한 의미를 가져야 하며, 중복되는 의미나 불분명한 정의는 피해야 합니다.
2. 개념 분류 체계 구축 (Build concept taxonomies)
다음으로, 용어를 분류 체계에 맞게 정리합니다. 이는 각 용어를 계층 구조로 분류하여 상위 개념과 하위 개념 간의 관계를 나타내는 작업입니다. 예를 들어, "동물"이라는 상위 개념이 있고, 그 아래에 "포유류", "조류"와 같은 하위 개념들이 위치할 수 있습니다. 이렇게 개념 간의 계층 구조를 명확히 하면 지식 그래프에서 개념 간의 포함 관계를 쉽게 파악할 수 있습니다.
3. 임시 이항 관계도 구축 (Build ad hoc binary relation diagrams)
개념들 간의 관계를 이해하기 위해 이항 관계(두 개의 개념 간의 관계)를 정의하는 단계입니다. 이 단계에서는 개념과 개념 사이의 관계를 시각적으로 표현하는 간단한 다이어그램을 작성합니다. 예를 들어, "고양이"와 "동물" 사이에는 "is a type of"라는 관계가 있을 수 있습니다. 이렇게 관계를 정의하면 개념들 간의 연관성을 명확히 이해할 수 있습니다.
4. 개념 사전 구축 (Build concept dictionary)
앞서 정의한 개념과 관계를 바탕으로, 각각의 개념을 명확히 설명하는 개념 사전을 만듭니다. 이 사전은 개념의 의미와 역할을 정확히 기술하며, 후속 작업에서 이 개념들을 참조하거나 확장하는 데 사용됩니다. 개념 사전은 용어집과 달리 더 구체적인 설명을 포함합니다.
5. 임시 이항 관계 설명 (Describe ad hoc binary relations)
이항 관계를 설명하는 단계입니다. 앞서 구축한 이항 관계 다이어그램에 대한 상세한 설명을 추가하여, 각 관계가 무엇을 의미하는지 명확히 기술합니다. 예를 들어, "고양이"와 "동물" 간의 "is a type of" 관계는 고양이가 동물의 하위 개념임을 의미하며, 이 관계의 의미를 설명하는 것입니다.
6. 인스턴스 속성 설명 (Describe instance attributes)
개념에 대한 특정 인스턴스(실제 예시나 개체)들이 가진 속성을 정의하는 단계입니다. 예를 들어, "고양이"라는 개념의 특정 인스턴스(실제 고양이)는 "이름", "나이", "색깔"과 같은 속성을 가질 수 있습니다. 이러한 속성을 설명하는 것은 개체를 더 구체적으로 표현하고 이해하는 데 도움이 됩니다.
7. 클래스 속성 설명 (Describe class attributes)
개념 자체(즉, 클래스)가 가진 속성을 정의합니다. 이는 인스턴스 속성과는 달리, 개념 전체에 공통적으로 적용되는 속성을 의미합니다. 예를 들어, "고양이"라는 클래스는 "동물"이라는 상위 개념에 속하며, "포유류"라는 속성을 가질 수 있습니다. 이 단계에서는 클래스의 특성을 정의하고 설명합니다.
8. 온톨로지 고정 용어 정의 (Describe constants)
온톨로지에서 자주 사용되는 용어들을 정의하고 설명합니다. 이전 단계인 개념 사전 구축과 개념 분류 체계에서 변하지 않은 값들을 정의하는 단계입니다. 예를 들어 "기온", "압력"과 같은 개념에서 0도나 1기압과 같은 고정된 값을 의미할 수 있습니다. 이러한 값들은 온톨로지 내에서 일정한 기준을 설정하는 데 중요합니다.
9. 공식 공리 설명 (Describe formal axioms)
공리는 온톨로지 내에서 항상 참인 규칙을 의미합니다. 예를 들어, "모든 포유류는 동물이다"와 같은 공리는 항상 참으로 간주되는 규칙입니다. 이 단계에서는 온톨로지 내에서 적용할 수 있는 공식적인 규칙과 법칙을 설정하고 설명합니다.
10. 규칙 설명 (Describe rules)
공리와는 별개로, 온톨로지 내에서 적용되는 특정 규칙을 설명하는 단계입니다. 이러한 규칙은 상황에 따라 참일 수 있는 규칙으로, 특정 조건이 충족될 때만 적용될 수 있습니다. 예를 들어, "만약 동물이 포유류라면, 그 동물은 젖을 먹여야 한다"라는 규칙이 있을 수 있습니다.
11. 인스턴스 설명 (Describe instances)
마지막으로, 온톨로지 내에서 구체적인 인스턴스를 설명하는 단계입니다. 이는 실제 예시나 사례를 통해 개념을 더 명확히 이해하도록 돕는 역할을 합니다. 예를 들어, "고양이"라는 개념의 인스턴스로 특정 고양이를 설명할 수 있습니다. 이 단계는 온톨로지의 개념을 구체화하고, 실생활에 적용할 수 있는 예시를 제공합니다.
위 단계를 거치게 된다면, 비로소 GraphRAG 의 재료가 될 정보 체계가 완성됩니다. 정보간 관계 그리고 그 관계가 맥락에 의해 참일지 거짓일지도 결정해야할 기준을 설계해야 하기때문에, 기존 그래프 모델링 하는 방식과는 다른 관점으로 접근해야합니다. 동일하게 데이터 간 관계를 설정하지만, 도메인 지식을 어떻게 체계화하고 담는지에 대한 철학이 조금 다르기 때문입니다.
위에 적어둔 내용이 다소 추상적이다보니, 구체적으로 어떤 도메인에서 어떻게 온톨로지를 설계하는지 궁금하실 분들을 위해 두 가지 사례를 가져왔습니다. 검색 분야에서 온톨로지 구축사례, 제조 공정에서 온톨로지 구축 사례입니다. 구체적인 부분이 궁금하신 분들은 아래 참고 문헌을 참조하시면 좋을거라 생각되네요.
해외 온톨로지 구축 사례
Shi, Xiaolin, et al. "Knowledge graph-based assembly resource knowledge reuse towards complex product assembly process." Sustainability 14.23 (2022): 15541.
국내 온톨로지 구축 사례
김현호 이주현 전희선 NAVER SEARCH. , “Stella NAVER 통합 Knowledge Graph.”, DEVIEW 2021.
Graph Document Hierarchy
이번 섹션을 진행하기에 앞서, 다양성, 중복성 그리고 추천에 대해 한 번 이야기 해보겠습니다. 다양성과 중복성 관계에 대해 이야기해보겠습니다. 저희가 검색창에 상품을 검색할 때, 상품과 유사한 상품들이 중복해서 등장할때와 상품과 연관 깊은 다양한 타 상품들이 등장할 때 어느 결과에 만족하실까요? 정답은 그 때의 목적 그리고 기분 등 여러 요소들이 개입하여 만족도가 높고 낮아질 수 있다 라고 할 수 있습니다. 하지만, 기본적인 검색 목적은 비슷하겠죠. “상품 구매를 위한 정보 취득“ 이라는 것을요. 이때, 너무 많은 중복성과 너무 다양한 정보들은 오히려 유저에게 혼란을 줄 뿐입니다. 그럼 이를 어떻게 잘 조절할 수 있을까요?
중복성을 줄이고, Submodular 가 The good 라는 위 게시물을 확인할 수 있을거에요. 해석해보자면, 중복도를 최소한으로 하고 그 중복도 내에서 유저가 관심이 있을만한 Set coverage 를 넓히는게 중요한 과제라고 할 수 있습니다. 여기에서, Submodular 라는 관점에 대해 익숙치 않은 분들을 위해 챗 선생님에게 물어본 결과는 다음과 같아요.
A submodular concept in the context of personalized recommendations refers to a mathematical property of certain types of functions that are often used in optimization problems. Specifically, a function is called submodular if it has a diminishing returns property, meaning that adding an item to a smaller set provides more benefit than adding the same item to a larger set.In personalized recommendations, submodular functions are used to model how to select a diverse and relevant set of recommendations. Here's a simplified explanation:
- Diminishing Returns: If you recommend a movie or product to a user, the first few items in a set will add significant value because they meet the user's interest. However, as you keep adding more items that are similar, the additional value decreases. This property of diminishing returns helps ensure that a recommendation system suggests a diverse set of items, not just items similar to what the user has already interacted with.
For example, if you are building a recommendation list, you want to suggest items that cover different aspects of a user’s interest to maximize engagement. A submodular function could help optimize this by balancing diversity with relevance.
그럼 저희는 결국 유저의 선호에 맞게 적절한 집합을 추출하고 이 집합을 LLM 의 context로 부여하여 Answer을 하는게 핵심이라 할 수 있습니다. 여기에서 적절한 집합 추출시, 활용되는 관점이 결국 Graph가 되겠구요. 문서가 가지고 있는 여러 계층의 정보 그리고 정보간 관계를 명시적으로 표현하고 저장 및 관리할 때 우리가 언급할 Graph를 사용하게 됩니다.
위에 있는 Figure 에 그래프 관점인 ‘명시적’인 관점을 넣어준다면, 다음과 같이 변할 거에요. Edge로 해당 문서에서 단어가 얼마만큼의 중요도를 가지고, 그 중요도에 대한 근거를요. 예를들어, 문서 검색 엔진으로 유명한 BM25 bag-of-words 를 비유해서 이야기해보자면 기존에는 위 그림에서 Edge 두께로만 그 단어의 Frequency 를 중요도를 표현했었을거에요. 하지만, 그래프를 추가한다면 위에서 Frequency + 알파를 제공할 수 있어요. 여기에서 알파는 LLM이 이해할 수 있는 서술어가 되겠구요.
결국 그래프의 데이터간 관계를 통해 해당 데이터에서 추출된 정보가 얼마만큼 중요한지를 정량적, 정성적인지를 LLM에게 명시적으로 전달할 수 있다는 점에서 Graph가 유용하고, GraphRAG가 RAG 산업에서 각광받고 있는 이유라고 할 수 있어요. 그럼 이 Graph를 product 에서 사용하기 위해 어떤식으로 그래프를 표현하면 좋을까요? 제가 생각하기에 최적의 그래프 스키마는 다음과 같아요. 지식베이스, 문서 , 표 이미지 텍스트 , 임베딩 주석 문단 문장 단어 , 요약 키워드 이렇게요.
보시다시피 총 5단계의 위계로 구성된 그래프 스키마라고 할 수 있어요. 각 단계마다의 역할은 모두 다른데, 크게 1,2단계 위계는 문서 간 관계를 위한 Intra 분석 역할 , 3,4,5 단계는 문서 내부 데이터 간 관계를 위한 Inter 분석 역할이라 할 수 있어요.
Intra, 문서 간의 관계
유저가 검색했을때 만족할만한 답변을 제공할만한 문서들은 굉장히 수가 많고 다양합니다. 이 중, 유저 질의와 관련이 있고 정말 중요하다 싶은 문서를 기반으로 context를 구축해서 질의에 활용하는게 RAG의 핵심이라 할 수 있겠습니다. 하지만, 이 문서간에 유사하다 라는 기준을 가지고 특정 문서 기반 유사한 문서들만 유저에게 제공한다면, 과연 좋은 결과를 이룰 수 있을까요?
위에서 이야기한 내용과 유사합니다. 유사한 문서라 할지라도, 각 문서에 포함되어 있는 concept 들이 다르기에 이를 유저 질의와 어떤 관련이 있는지를 확인해보아야 합니다. 이를 위해선 결국 문서내 여러 요소들을 잘게 쪼개고 이 잘개 쪼개진 요소들 간 관련성을 파악한 뒤 중요한 concept이라 생각되는 요소들을 추출해 앞서 언급한 유저의 질의와 관련이 있는지를 기준으로 중요도를 판별해야합니다.
Inter, 문서 내부 요소들간의 관계
문서 내 여러 요소들이 있기 마련입니다. 대체적으로 표 , 이미지, 텍스트 세 요소들로 문서가 이루어져있습니다. 크게 텍스트 혹은 표 이미지 형태 두 가지로 나눌 수 있겠습니다. 텍스트로 전달하기 어려운 데이터나 내용을 효과적으로 전달하기 위한 표 이미지 그리고 이 요소들을 서두나 마지막에 소개와 요약하는 내용이 담긴 앞 뒤 문단 과 해당 문단에 담긴 키워드 그리고 일반적인 텍스트 문단 문장 단어 이렇게요.
여기에서 대다수 분들은 텍스트 ,문단, 문장, 단어 계층은 익숙하실거라 생각합니다. 흔히 사용하는 RAG 에서 청크 단위로 분절해주고 이를 Retrieval 해오는 과정과 별반 차이가 없기 때문이죠. 하지만, 표 이미지를 어떻게 그리고 왜 그래프로 표현할까?라는 의문이 들 수 있습니다.
Content Hierarchy, 문서가 이루어진 요소들 중 표 이미지에서 관계를 표현해본다면?
위에 언급한 바와 같이 표와 이미지를 그래프로 어떻게 왜 표현하는지에 대해 알아보겠습니다. 그림과 표 예시를 들어 설명해볼게요.
그림 예시
위 그림 예시은 연령별 가입자 현황과 안심차단 서비스 가입경로에 대한 조사 결과를 파이 차트로 나타냈습니다. 결과에 대한 분포를 표현하기에 유용한 파이 차트를 활용했습니다. 과연 분포만 보여주기 위해 위 그림을 활용했을까요? 아래 글을 이어서 확인해볼게요. 인터넷전문은행을 통한 비대면 신청 개시라는 제목과 함께 “비대면 금융거래에 익숙한 2030대 청년층 등의 가입이 한층 용이” 라는 텍스트를 확인할 수 있습니다. 또한 “비대면 안심차단 신청 채널을 단계적으로 확대하여 서비스 접근성을 제고할 계획” 라는 것도 확인할 수 있습니다.
글 작성자가 모두 볼드체로 기록될만큼 강조하고 싶은 부분이라 할 수 있습니다. 텍스트 그리고 그림 각각 하나의 요소라도 분절이 된다면, 정보가 잘 전달이 될까요? 만일 그림만을 기록해두었다면, 가입자 현황 과 가입 경로만의 분포만을 저희는 인지했을 겁니다. 반대로, 글만 기록되어 있었다면 20대 30대 청년층들이 현재 얼마만큼 가입을 했는지에 대한 정보 그리고 가입경로에 대한 현재 수치를 알기 어렵기에 맥락이 끊겨 정보를 이해하는데 어려움을 겪게 됩니다.
이처럼, 텍스트와 그림은 서로 상호 보완적인 관계를 가진 정보라고 할 수 있습니다. 이를, 그래프가 아닌 기존 RAG로 표현하게 된다면 단순 청크로만 나타내어 ‘관련성’이 있음을 표현하기 어려워지게 됩니다. 맥락 유실을 방지하기 위해 물론 서두에 언급한 메타스토어에 attribute로 입력할 수 있겠지만, 그 메타마다 locality 를 반영하여 앞 뒤 맥락을 매칭지어주어야 하기때문에, 결국 그래프와 별반 차이가 없다 라고도 볼 수 있습니다.
표 예시
이번엔 표를 살펴보겠습니다. 표는 RDBMS 를 주로 활용하는 저희들에게 그림 대비 좀 더 익숙한 요소이지 않을까 싶습니다. 표에서 핵심은 바로 행과 열이라 할 수 있습니다. 열(column)에는 행(raw)에 어떤 요소들이 담길지 체계 혹은 메타를 설계해놓고 행에 차곡차곡 데이터를 담습니다. 때문에, 저희는 자연스레 표를 봄으로써 정보들을 체계적으로 확인하고 분석할 수 있는거죠.
표와 아래 글간의 관계를 살펴본다면, 위 표에서는 신규지정 그리고 지정내용 변경된 업체와 서비스 명을 표현했습니다. 그리고 아래 글에서는 “BMW파이낸셜서비스코리아(주) 외 11개사 등 혁신금융서비스를 신규 지정하여 ‘망분리 규제’ 예외를 허용” 하였다 에 볼드체로 강조하여 위 내용에 덧붙여 어떤 현상이 이루어졌음을 이야기하고 읽는 저희로 하여금 전달하려고 합니다. 추가로, (주)두나무 , (주)서울거래에 발생한 내용들에 변경이 필요함등을 이야기합니다.
이처럼, 표와 텍스트는 정보 체계 보여주고 이 정보 체계내에서 무슨 변동이 있는가를 추가로 글로 요약 혹은 설명해주기에 그림과 유사하게 상호 보완하는 관계를 가진다 라고 할 수 있습니다. 그림과 마찬가지로 이 요소들이 과연 기존 RAG 청크 분리에서 ‘잘’ 적용이 될까요?
한 층 더 나아가 생각해본다면, 문서들간 ‘유사’하다 라는 관계를 표현할 Intra 관점에서 기존 RAG 처럼 지식베이스를 구축한다면 과연 이런 요소들이 반영되어 문서간 유사한 관계를 메타화하고 지식베이스를 구축할 수 있을까요? 위 콘셉들을 코드로 표현해보면 어떻게 될까 라는 궁금증이 있으실 분들이 계실거라 생각되어, 간단한 코드도 첨부해봅니다. 여기에선 추출만 설명했지만, 다음 글에서는 추출된 요소들을 그래프 형태로 잇는 방식을 다루는 코드와 함께 이야기하겠습니다.
import fitz # PyMuPDF import pandas as pd from bs4 import BeautifulSoup import re import os def convert_html_table_to_markdown(html_table_text: str) -> str: # BeautifulSoup을 사용하여 HTML 파싱 soup = BeautifulSoup(html_table_text, 'html.parser') # 테이블 찾기 table = soup.find('table') # Pandas DataFrame으로 변환 df = pd.read_html(str(table))[0] # DataFrame을 Markdown으로 변환 markdown_table = df.to_markdown(index=False) return markdown_table def is_table(text: str) -> bool: # 간단한 규칙으로 표를 구분 (예: 여러 개의 연속된 공백이나 줄바꿈이 표 형식일 가능성이 큼) if re.search(r'\n\s*\n', text): return True return False def get_nearest_paragraph(text: str, current_position: int, window_size: int = 1000) -> str: """ 주어진 위치에서 앞뒤 문단을 추출하는 함수 :param text: 전체 텍스트 :param current_position: 현재 텍스트 위치 :param window_size: 앞뒤로 몇 글자 정도 추출할지 결정 :return: 주석에 해당하는 텍스트 (앞, 뒤 텍스트) """ start = max(0, current_position - window_size) end = min(len(text), current_position + window_size) # 앞뒤로 텍스트를 제한된 범위만큼 추출 return text[start:current_position], text[current_position:end] def extract_pdf_content(pdf_path: str, image_output_dir: str, window_size: int = 1000): # PDF 문서 열기 doc = fitz.open(pdf_path) documents = [] for page_num in range(len(doc)): page = doc.load_page(page_num) text = page.get_text("text") html = page.get_text("html") # 이미지가 포함되어 있는지 체크 image_list = page.get_images(full=True) # 이미지가 있으면 이미지 처리 if image_list: for img_index, img in enumerate(image_list): xref = img[0] base_image = doc.extract_image(xref) image_bytes = base_image["image"] image_ext = base_image["ext"] # 이미지 저장 image_filename = os.path.join(image_output_dir, f"page{page_num + 1}_img{img_index + 1}.{image_ext}") with open(image_filename, "wb") as image_file: image_file.write(image_bytes) # 이미지 주변의 텍스트(주석)를 추출 img_position = page.search_for(base_image['image'])[0] if base_image else 0 before_text, after_text = get_nearest_paragraph(text, img_position, window_size) # 이미지 메타데이터와 주석 정보 기록 documents.append({ "page_content": image_filename, "metadata": { "source": pdf_path, "page_type": "image", "page_num": page_num + 1, "image_index": img_index + 1, "before_text": before_text, # 이미지 앞 주석 "after_text": after_text # 이미지 뒤 주석 } }) # 텍스트와 표 구분 if is_table(text): # 표로 인식한 경우 HTML 형태로 저장 table_position = text.find('<table>') before_text, after_text = get_nearest_paragraph(text, table_position, window_size) documents.append({ "page_content": html, "metadata": { "source": pdf_path, "page_type": "table", "page_num": page_num + 1, "before_text": before_text, # 표 앞 주석 "after_text": after_text # 표 뒤 주석 } }) else: # 텍스트로 인식한 경우 documents.append({ "page_content": text, "metadata": { "source": pdf_path, "page_type": "text", "page_num": page_num + 1 } }) return documents def process_documents(documents): markdown_documents = [] for doc in documents: if doc["metadata"].get("page_type") == 'table': try: # HTML로부터 표를 마크다운으로 변환 doc["page_content"] = convert_html_table_to_markdown(doc["page_content"]) markdown_documents.append(doc) except Exception as e: print(f"Error processing table on page {doc['metadata']['page_num']}: {e}") markdown_documents.append(doc) else: # 텍스트 및 이미지는 그대로 추가 markdown_documents.append(doc) return markdown_documents # 사용 예시 pdf_path = 'your_pdf_file.pdf' # 처리할 PDF 파일 경로 image_output_dir = 'output_images' # 이미지가 저장될 폴더 window_size = 1000 # 주석을 추출할 앞뒤 텍스트의 길이 설정 # 이미지 저장 폴더가 없으면 생성 if not os.path.exists(image_output_dir): os.makedirs(image_output_dir) # PDF에서 텍스트, 표, 이미지 및 주석 추출 documents = extract_pdf_content(pdf_path, image_output_dir, window_size) markdown_documents = process_documents(documents) print(f"Total documents processed: {len(markdown_documents)}")
코드 설명
convert_html_table_to_markdown
함수: HTML 형식의 표를 마크다운 형식으로 변환하는 기존 함수입니다.
is_table
함수: 텍스트가 표로 인식되는지를 간단히 판단하는 함수입니다.
get_nearest_paragraph
함수: 주어진 위치에서 앞뒤로 일정 범위의 텍스트를 추출하여 이미지나 표 앞뒤의 주석(설명)을 찾아냅니다.window_size
는 추출할 텍스트의 길이를 결정하며, 필요에 따라 조정할 수 있습니다.
extract_pdf_content
함수: 각 페이지에서 텍스트, 표, 이미지 등을 구분하여 추출하며, 이미지나 표의 앞뒤에 있는 설명 문단도 함께 추출합니다.- 이미지: 이미지가 있을 경우 파일로 저장하며, 이미지 주변의 설명(앞뒤 텍스트)을 추출하여 함께 저장합니다.
- 표: 표가 있을 경우 HTML로 변환하고, 표 주변의 설명 문단을 함께 기록합니다.
process_documents
함수: 각 문서를 처리하여 표는 마크다운으로 변환하고, 이미지 및 텍스트는 그대로 저장합니다.
이미지 추출과 저장
PDF에서 추출된 이미지는
output_images
폴더에 저장됩니다. 이 폴더는 스크립트 실행 시 자동으로 생성되며, 각 이미지 파일은 pageN_imgM
형식으로 저장됩니다. 예를 들어, page2_img1.png
는 PDF의 2번째 페이지에서 추출된 첫 번째 이미지입니다.주석(설명 문단) 연결
이미지나 표 앞뒤에 있는 주석은
metadata
에 "before_text"
와 "after_text"
로 함께 저장됩니다. 이를 통해 나중에 이미지나 표를 설명하는 텍스트와 함께 사용할 수 있습니다.Lexical 그리고 Text, 기존 RAG 에서 활용하는 청킹 방식과 GraphRAG에서 Text를 활용한 방식은 무엇이 다를까요?
텍스트 콘텐츠는 문단, 문장 그리고 단어 크게 3가지 카테고리로 이루어진다 라고 할 수 있습니다. 기존 RAG에서는 Dense Retrieval 혹은 Sparse Retrieval 에 따라 텍스트 정제를 다르게 하고 Retrieval 또한 다르게 하겠지만, 위 처럼 세세하게 위계를 구성한다 라고 볼 수는 없습니다. 그렇다면, 그래프는 왜 굳이 비용을 더 소모하면서까지 단락 > 문장 > 단어 와 같이 층계를 나누고 이어줄까요? 그 이유와 장점은 다음과 같습니다.
1.맥락적 검색:
텍스트를 계층적으로 (단락 > 문장 > 단어) 구성하고 이 노드들을 연결함으로써, 단순히 개별 문장을 검색하는 것뿐만 아니라 필요한 경우 주변 맥락(예: 전체 단락)을 쉽게 검색할 수 있습니다. 이는 RAG (Retrieval-Augmented Generation)에서 매우 유용하며, 관련 정보를 더 큰 단위로 검색하여 생성된 응답의 정확성을 높일 수 있습니다.
2.의존성 관리:
그래프 구조는 본질적으로 관계와 의존성을 모델링합니다. 문장을 해당 단락과 연결하고 그 문장 내 단어들을 연결함으로써 그래프는 의존성 체인을 추적하는 방법을 제공합니다. 이는 텍스트를 생성할 때 일관성을 유지하고 검색된 내용이 더 넓은 맥락과 일치하도록 하는 데 도움이 됩니다.
3.효율적인 정보 조직:
그래프는 문서 내 복잡한 관계를 유연하고 확장 가능한 방식으로 표현할 수 있습니다. 각 문장이나 단락을 독립적인 단위로 취급하는 대신, 그래프 구조를 통해 의미적 또는 구문적 관계를 기반으로 서로 연결할 수 있습니다. 이를 통해 검색이 맥락에 맞고 더 정확하게 이루어질 수 있습니다.
4.강화된 의미적 연결:
그래프는 구조적 연결 외에도 공동 참조, 주제의 연속성, 문장 간 논리적 연결 등과 같은 관계를 나타낼 수 있습니다. 이를 통해 RAG 모델이 더 풍부한 맥락 인식 검색을 수행할 수 있으며, 이는 포괄적이고 맥락적으로 정확한 답변을 제공하는 데 매우 중요합니다.
5.긴 맥락 관리 용이:
긴 문서에서 섹션 간의 흐름과 일관성을 이해하는 것은 중요합니다. 그래프를 사용하면 문장과 단락 사이에서 주제와 정보가 어떻게 흐르는지 추적할 수 있어, 일관성 있고 맥락적으로 정확한 정보를 검색하고 조합하는 것이 더 쉬워집니다.
위 5가지 장점을 통해 왜 텍스트를 층계로 표현하고 관리 그리고 Context로 활용하면 좋을지 알아보았습니다. 좀 더 깊게 살펴볼까요? 위에 그림에서 특이한 부분이 있을겁니다. “문단” 노드에만 하위 노드 “임베딩” 노드가 존재한다는걸요. 나머지 “문장” , “단어” 또한 할 수 있을텐데, 왜 그러지 않았을까요? 바로 텍스트 임베딩이 효율적이다 라고 생각하는 범주를 문단으로 한정지었기 때문인데요.다시말해서, 문단 하위 계층인 문장 단어 노드는 구조적인 특성을 활용하는 것이 더 효율적이라고 판단했기 때문입니다.
왜 저는 그렇게 생각을 했을까요? Neo4j 에서 작성한 글 중 일부를 발췌해서 이야기해볼게요.
Structured or Semantic Search?Structured Search typically requires certain schema to be defined; e.g., column, row, and fields in a relational database, or node, relationship, and property in a graph database. The biggest benefit of having a predefined schema, which means you need to know what fields or columns are available and their data types, is the precision and powerful features of manipulating data through a query language like SQL for RDBMS and Cypher for graph database.Meanwhile, Semantic Search aims to understand the intent and contextual meaning of search phrases to produce highly relevant results. Instead of focusing solely on matching keywords, it seeks to understand the context in which keywords are used. For instance, the word “apple” could refer to the fruit or the tech company, and a semantic search system would attempt to discern which meaning was intended based on additional context.Semantic Search often uses NLP techniques to understand context, synonyms, user intent, and more. Many semantic search systems use knowledge graphs to understand the relationships between different entities and concepts.Because of the nature of the stored information and the capabilities offered, structured search excels in environments where data is well-organized, and the user knows precisely what they are querying for. Semantic search shines in more complex, natural language environments where user intent and context play a significant role in retrieving the right information.
위에 이야기한 바와같이 Semantic Search 는 Matching 에 우선순위를 두는 것보다, Contextual 에 우선순위를 두어 검색 결과를 도출한다 라는걸 알 수 있습니다. 그럼 여기에서 과연 Keyword matching 은 간과해도 되는걸까요? 정보는 모두 소중하기에 간과하게 아닌, 하나하나 적확하게 활용하는게 중요하다 라고 할 수 있습니다. 때문에, 이를 적확하게 활용해보자 라는 관점에서 저는 문단의 임베딩을 Semantic Search에 활용하고 문장 그리고 단어를 Matching 관점에서 활용하는 방안을 택했고, 그래프 모델링에 그렇게 표현을 한거죠. 결국 임베딩이 유의미할 부분과 그렇지 않을 부분을 그래프에 표현했다 라고 생각해주시면 됩니다.
Going Meta #22: RAG with Knowledge Graphs , https://github.com/jbarrasa/goingmeta/blob/main/session22/Going Meta 22 RAG with Knowledge Graphs.pdf
위 그림에서 Neo4j가 이야기한 Context Augmentation 도 이와 유사한 맥락입니다. 그래프 구조내에 텍스트 임베딩이 유의한 노드를 기준으로 추가 탐색을 함으로써 맥락을 확장한다는거죠. 다시말해서, Embedding 관점에선 놓칠 Context에 그래프 구조를 첨가함으로써 정확도를 향상한다는겁니다.
자세한 내용은 이전에 마키나락스에서 주관한 MLOps 세미나 자료에 담아두었습니다.링크 를 통해 접근 및 파일 다운로드가 가능하니, 디테일한 내용이 궁금하신 분들은 한 번 살펴보시고 질문 주시기 바랍니다.
끝으로,
이번 포스팅에서는 GraphRAG 중 문서 계층 구조를 활용한 방안에 대해 다뤘습니다. GraphRAG는 그래프와 메타데이터를 활용해 지식베이스를 구성하고, 이를 기반으로 검색 및 정보를 제공하는 기술입니다. 먼저, 그래프 관점과 메타데이터 관점에서 접근했을 때의 장단점을 살펴보고, 이 방안을 효과적으로 적용하기 위해 필요한 메타데이터와 그래프 구조에 대해 언급했습니다. 그래프 관점에서는 문서의 요소들을 노드와 엣지로 표현해 관계를 시각화할 수 있는 장점이 있지만, 구현이 복잡해질 수 있다는 단점이 있습니다. 반면에 메타데이터 관점에서는 문서나 요소에 대한 속성 정보를 담아 유연하게 활용할 수 있지만, 연결성 표현이 제한적일 수 있습니다.
또한, 지식베이스 내부 문서들 간의 관계를 다루는 Intra 관점과 문서 내부 요소들 간의 관계를 다루는 Inter 관점에 대해 알아보았습니다. Intra 관점은 지식베이스를 구성하는 다양한 문서들이 서로 어떤 관계를 가지는지, 예를 들어 상위 문서와 하위 문서의 연결성 또는 참조 링크 등을 그래프로 표현하는 방식입니다. 반면, Inter 관점은 각 문서 내부의 요소들—예를 들어 표, 이미지, 문단, 키워드 등—이 어떻게 연결되어 있는지를 나타내며, 이를 통해 문서 내 정보의 흐름과 맥락을 시각화할 수 있습니다.
추가로, 표, 이미지, 텍스트와 같은 요소들을 그래프로 연결했을 때 얻게 되는 이점에 대해서도 논의했습니다. 이를 통해 문서 내 시각적 자료와 텍스트 간의 관계를 더 명확하게 파악할 수 있으며, 정보 검색 시 관련 요소들을 빠르게 탐색하고 연관된 데이터를 제시할 수 있는 장점이 있습니다.
이번 글에서는 지식베이스를 그래프 형태로 구축할 때 고려해야 할 점들을 다뤘다면, 다음 글에서는 GraphRAG를 운용할 때 그래프 구성 요소를 효과적으로 활용할 수 있는 Graph Supervisor Agent를 설계하는 방법에 대해 알아보겠습니다. 이 Agent는 그래프 기반 데이터의 다양한 구성 요소를 적절하게 활용하여 RAG(Retrieval-Augmented Generation) 모델이 효율적으로 작동할 수 있도록 지원하는 역할을 하게 됩니다.
본 글은 Open UP(오픈소스 소프트웨어 통합지원센터)로부터 지원받아 작성하였습니다.
레퍼런스
금융위원회, [보도자료] 여신거래 안심차단 시행(8.23.) 이후, 89,817명의 금융소비자가 이용하고 있습니다.https://www.fsc.go.kr/no010101/83151?srchCtgry=&curPage=&srchKey=&srchText=&srchBeginDt=&srchEndDt=
금융위원회, [보도자료] 클라우드 활용 소프트웨어 서비스(SaaS) 이용을 허용하는 혁신금융서비스 14건 지정, https://www.fsc.go.kr/no010101/83148?srchCtgry=&curPage=&srchKey=&srchText=&srchBeginDt=&srchEndDt=
Shi, Xiaolin, et al. "Knowledge graph-based assembly resource knowledge reuse towards complex product assembly process." Sustainability 14.23 (2022): 15541.
Share article