반응형

GenAI(생성 AI) 애플리케이션을 구축하기 위한 언어, 프레임워크, 도구 옵션이 정말 많죠? 이제 막 시작하는 단계라면 이런 결정을 내리고 모든 것을 통합하는 방법을 알아내는 게 부담스러울 수 있어요.

우리 팀은 몇 가지 핵심 기술이 포함된 스타터 키트 프로젝트를 제공해서 이 프로세스를 단순화하려고 사전 패키지된 솔루션을 개발해 왔어요. 그 중 하나가 바로 오늘 포스팅 주제인 Java에서 Spring AI를 사용해서 GenAI 애플리케이션을 구축하는 방법이에요.

Spring AI란 무엇인가요?

Spring AI는 GenAI 애플리케이션을 구축하기 위한 프레임워크예요. LLM(Large Language Model) 및 RAG(Retrieval-Augmented Generation)와 같은 GenAI 모델 및 아키텍처 작업을 위한 도구와 유틸리티를 제공하죠. Java에는 GenAI를 위한 다른 옵션도 있지만 Spring AI는 다양한 커뮤니티에 걸친 소수의 Neo4j 초기 통합 중 하나랍니다.

이 프로젝트에는 무엇이 포함되어 있나요?

모든 코드와 배경 정보는 spring-ai-starter-kit GitHub 저장소에 있어요. 여기서 일부 코드를 살펴볼 거지만, 언제든지 저장소를 다시 참조할 수 있어요.

Neo4j를 사용해서 구조화된 데이터(예: entity 및 relationship)는 물론 관련 Vector Embedding이 포함된 구조화되지 않은 텍스트 데이터를 저장해요. 벡터에 대한 유사성 검색을 실행한 다음, 검색 `query`를 실행해서 추가 관련 entity를 가져올 수 있죠. 오늘은 OpenAI 모델을 선택할게요. 하지만 다른 모델로 교체할 수도 있어요. Spring AI 지원 LLM를 참고하세요.

프로젝트의 pom.xml 파일을 살펴보면 이 프로젝트에 포함된 네 가지 종속성을 확인할 수 있어요.

  • Spring Web(REST API 생성용)
  • OpenAI(또는 Mistral AI, Ollama 등과 같은 기타 LLM 모델)
  • Neo4j 벡터 데이터베이스(벡터 저장 및 `query`용)
  • Spring Data Neo4j(Spring 애플리케이션에서 Neo4j 작업용)

 

우리가 사용하는 데이터 세트는 EDGAR에서 제공한 SEC 서류예요. 여기에는 회사 및 개인 서류 제출을 위한 문서 모음이 포함되어 있죠. 문서는 일반 텍스트로 되어 있고, 다양한 회사에 대한 재무 정보가 포함되어 있어요. 우리 팀은 이 데이터를 회사, 재무 형태 및 관리자에 대한 정보가 포함된 Knowledge Graph로 정리했어요. 여기에는 다음이 포함돼요.

  1. 양식 10-K — 상장 회사가 SEC에 제출해야 하는 연례 보고서. 회사의 재무 성과에 대한 포괄적인 요약을 제공해요.
  2. 양식 13 — 1억 달러 이상의 자산을 관리하는 기관 관리자가 제출함

스타터 키트 애플리케이션은 기본적으로 공개 데이터베이스에 미리 로드된 데이터 세트 버전을 사용해요. Neo4j Aura 클라우드 데이터베이스 덕분이죠. 덕분에 데이터 가져오기 및 형식 지정으로 인한 번거로움이 사라진답니다. 또는 처음부터 자신만의 Knowledge Graph를 로드하거나, sec-edgar-notebooks 저장소에서 데이터 세트에 대해 자세히 알아볼 수도 있어요.

배경 정보를 살펴보고 코드를 확인해 볼까요?

 

프로젝트 저장소를 복제한 다음, 즐겨 사용하는 IDE에서 열어보세요. pom.xml 파일을 보면 이전의 네 가지 종속성과 함께 마일스톤 저장소가 포함되어 있음을 알 수 있을 거예요. Spring AI는 아직 일반 출시 버전이 아니기 때문에 마일스톤 저장소가 포함되어 있답니다.

OpenAI의 LLM을 사용하려면 OpenAI에 가입해서 API 키를 요청해야 해요. 해당 데이터베이스(직접 생성한 경우 데이터베이스 포함)가 있다면 application.properties 파일에서 구성을 설정할 수 있어요. 다음은 그 예시입니다.

spring.ai.openai.api-key=<YOUR API KEY HERE>
spring.neo4j.uri=neo4j+s://9fcf58c6.databases.neo4j.io
spring.neo4j.authentication.username=public
spring.neo4j.authentication.password=read_only
spring.data.neo4j.database=neo4j

데이터베이스 자격 증명은 미리 로드된 기본 인스턴스에 대한 것이에요. 따라서 자체 데이터베이스를 사용하는 경우 .uri, .username 및 .password 속성을 해당 속성과 일치하도록 업데이트해야 합니다.

애플리케이션의 코드를 한번 검토해 볼까요?

Vector Embedding 및 벡터 저장소 설정

SpringAiApplication 클래스에서는 OpenAI 클라이언트용 Spring Bean 두 개와 애플리케이션에서 필요할 때마다 필요한 구성 요소에 액세스할 수 있는 Neo4j 벡터 저장소를 설정해야 해요.

@Bean
public EmbeddingClient embeddingClient() {
  return new OpenAiEmbeddingClient(new OpenAiApi(System.getenv("SPRING_AI_OPENAI_API_KEY")));
}

@Bean
public Neo4jVectorStore vectorStore(Driver driver, EmbeddingClient embeddingClient) {
  return new Neo4jVectorStore(driver, embeddingClient,
      Neo4jVectorStore.Neo4jVectorStoreConfig.builder()
          .withIndexName("form_10k_chunks")
          .withLabel("Chunk")
          .withEmbeddingProperty("textEmbedding")
          .build());
}

EmbeddingClient 빈은 OpenAI API용 클라이언트를 생성하고 속성 파일에서 API 키를 전달해요. 그런 다음 Neo4jVectorStore 빈은 Neo4j를 임베딩(벡터)용 저장소로 구성하죠. 이는 데이터베이스 자격 증명과 내장 클라이언트에서 자동 구성된 Driver를 가져와요. 벡터 Index 이름(Spring AI 기본값은 spring-ai-document-index), 임베딩을 저장할 Node의 Label(기본값은 Document 엔터티 검색), 임베딩을 포함하는 Property 이름을 지정하려면 벡터 저장소 구성을 사용자 정의해야 해요.

애플리케이션 모델

다음으로 애플리케이션 엔터티를 데이터베이스 모델에 매핑하는 몇 가지 표준 도메인 클래스가 있어요. Document Chunk Node를 나타내는 Chunk 클래스와 데이터베이스의 해당 엔터티를 나타내는 Form, Company 및 Manager에 대한 클래스도 있죠. Chunk 엔터티에는 유사성 검색에 사용할 임베딩(벡터)이 연결되어 있답니다.

이러한 엔터티는 표준 Spring Data Neo4j 코드이므로 여기에 코드를 표시하지는 않을게요. 하지만 각 클래스의 전체 코드는 프로젝트의 GitHub 저장소에서 확인할 수 있어요.

프로젝트의 저장소 인터페이스는 애플리케이션이 데이터베이스와 상호 작용할 수 있도록 도와줘요. 유사한 Chunk에 대한 관련 엔터티(Form, Company, Manager)를 찾는 정의된 Query 방법이 하나 있답니다.

다음으로 컨트롤러 클래스는 사용자 요청을 처리하고 응답을 생성하기 위해 모든 부분이 함께 모이는 곳이에요. 이 클래스에는 사용자로부터 질문을 받고 Neo4jVectorStore를 호출하여 가장 유사한 문서를 계산하고 반환하는 로직이 포함되어 있죠. 그런 다음 유사한 Chunk를 Neo4j Query에 전달해서 연결된 엔터티를 검색하고 LLM 프롬프트에 추가 컨텍스트를 제공할 수 있어요. 제공된 모든 정보를 사용해서 더욱 정확하게 답변하는 거죠.

제어 장치

컨트롤러 클래스는 유사성 검색을 실행하고, LLM을 호출하고, 데이터베이스를 Query하기 위해 Neo4jVectorStore 빈, OpenAiChatClient 빈 및 ChunkRepository 인터페이스를 삽입해야 해요.

@RestController
@RequestMapping("/api")
public class ChunkController {
  private final OpenAiChatClient client;
  private final Neo4jVectorStore vectorStore;
  private final ChunkRepository repo;

  @GetMapping("/chat")
  String getGeneratedResponse(@RequestParam String question) {
      List<Document> results = vectorStore.similaritySearch(SearchRequest.query(question));
      List<Chunk> docList = repo.getRelatedEntitiesForSimilarChunks(results.stream()
            .map(Document::getId)
            .collect(Collectors.toList()));

      var template = new PromptTemplate("""
        You are a helpful question-answering agent. Your task is to analyze
        and synthesize information from the top result from a similarity search
        and relevant data from a graph database.
        Given the user's query: {question}, provide a meaningful and efficient answer based
        on the insights derived from the following data:

        {graph_result}
        """,
        Map.of("question", question,
            "graph_result", docList.stream().map(chunk -> chunk.toString()).collect(Collectors.joining("n"))));
      System.out.println(" - - - PROMPT - - -");
      System.out.println(template.render());

      return client.call(template.create().getContents());
  }
}

마지막 부분은 사용자가 /chat 엔드포인트에 GET 요청을 보낼 때 호출되는 메서드를 정의하는 부분이에요. 이 메서드는 질문을 query 파라미터로 받아서, vectorStoresimilaritySearch() 메서드에 전달해서 유사한 문서 덩어리(chunks)를 찾죠.

Spring AI는 일반적인 Document 타입을 예상하기 때문에, 유사한 Chunk 노드가 Document 엔티티에 매핑돼요. Neo4jVectorStore 클래스에는 Document를 사용자 정의 레코드로 변환하는 메서드와, 반대로 레코드를 Document로 변환하는 메서드가 포함되어 있답니다.

컨트롤러 메서드로 다시 돌아와서, 이제 유사한 문서 청크가 있지만, 텍스트 청크만으로는 유용한 답변을 제공하기에 충분한 정보가 없을 수도 있어요. 이제 Neo4j에서 query를 실행해서 해당 청크와 관련된 양식, 회사, 관리자를 검색해야 해요. 바로 이 부분이 애플리케이션의 Retrieval-Augmented Generation (RAG) 부분이에요.

유사성 검색이 유사한 문서 청크를 반환한 후, 저장소의 getRelatedEntitiesForSimilarChunks() 메서드를 호출해서 (유사한 문서 ID 목록을 전달해서) 해당 청크에 대한 관련 항목을 찾아요.

다음 코드 블록은 관련 엔터티가 포함된 사용자의 질문과 그래프 결과를 함께 Large Language Model (LLM)으로 보낼 텍스트가 포함된 프롬프트 템플릿이에요. 마지막으로 템플릿의 create() 메서드를 호출해서 LLM에서 응답을 생성하고 응답 문자열에 대한 content 키를 반환하죠.

한번 시험해 볼까요!

애플리케이션 실행

스타터 키트 애플리케이션을 실행하려면 터미널에서 ./mvnw spring-boot:run 명령을 사용하면 돼요. 애플리케이션이 실행되면 EDGAR 데이터에 대한 질문을 query 파라미터로 사용해서 /api/chat 엔드포인트에 GET 요청을 보낼 수 있어요. 몇 가지 예시를 보여드릴게요.

curl "http://localhost:8080/api/chat?question=How%20many%20forms%20are%20there%3F"
curl "http://localhost:8080/api/chat?question=Which%20companies%20are%20in%20healthcare%3F"
curl "http://localhost:8080/api/chat?question=Which%20managers%20own%20stock%20in%20more%20than%20one%20company%3F"

SEC 관련 질문을 자유롭게 던져보거나, 애플리케이션이나 코드를 조정해서 어떻게 반응하는지 확인해보세요. 콘솔 출력을 확인해서 애플리케이션과 LLM 간에 데이터가 잘 주고받는지 확인할 수도 있답니다.

마무리

이번 포스팅에서는 Java로 GenAI 애플리케이션 구축을 시작하는 데 도움이 되는 Spring AI Neo4j 스타터 키트를 살펴봤어요. Spring AI를 사용해서 이미 잘 구축된 Spring 생태계의 풍부함을 확장해서 JVM 언어(이번 포스팅에서는 Java)로 GenAI 앱을 작성할 수 있었죠.

Spring AI는 다양한 LLM 모델과 Vector Store를 지원하지만, 저희는 OpenAI 모델과 Neo4j 데이터베이스를 선택했어요. Neo4j는 구조화되지 않은 텍스트 데이터 및 Vector Embedding과 함께 관계 및 표준 구조화된 데이터를 저장하는 기능을 제공하거든요. OpenAI 모델을 사용해서 데이터베이스의 유사성 검색 결과 및 관련 엔터티를 기반으로 사용자 질문에 대한 응답을 생성했답니다.

이 포스팅이 GenAI와 그 이상을 시작하는 데 도움이 되었기를 바라요! 즐거운 코딩 되세요!

자원

  • 코드(GitHub 저장소):Spring AI 스타터 키트
  • 무료 온라인 강좌:GraphAcademy를 ​​통해 Neo4j 및 LLM에 대해 알아보세요
  • 선적 서류 비치:스프링 AI
  • 웹페이지:Spring AI 프로젝트


  • java
  • rag
  • spring

에이치시스템즈LogTree는 Neo4j 기반 GraphRAG 플랫폼으로, 데이터를 자동으로 지식그래프화하고 자연어 질의로 즉시 답을 제공합니다.

👉 에이치시스템즈 홈페이지

반응형
반응형

Neo4j로 노래와 가사 카탈로그를 만들고, 내장된 Gen AI 기능으로 내용 기반 노래 찾기!

여러분, 노래 제목이나 누가 불렀는지 기억은 안 나는데, 대략적인 내용만 어렴풋이 기억나는 노래를 찾고 싶었던 적 없으신가요? 딱 4줄의 코드로 이 문제를 해결하는 도구를 만들어 볼 거예요.

제 아들 Noah Bergman(13세)의 작품

인터넷은 금방 지나갈 과대 광고일 뿐이에요. 장기적으로 봤을 때, 사람들이 인터넷 서핑에 그렇게 많은 시간을 쏟고 싶어 하진 않을 거라고 생각해요.

이 말은 1996년에 스웨덴 통신부 장관이었던 이네스 우스만(Ines Uusmann)이 했던 말인데요. 그녀의 말처럼 인터넷이 과대 광고였을 수도 있죠. 하지만 인터넷은 이미 20년 넘게 우리 곁에 있었고, 이제는 절대 사라지지 않을 존재에 대한 과대 광고였던 것 같아요. 인터넷 없는 현대 생활은 상상하기 힘들 정도니까요. 사실 90년대 초반에 과대광고를 불러일으킨 건 바로 월드 와이드 웹(World Wide Web)의 등장 때문이었죠.

마찬가지로 많은 사람들이 AI를 과대 광고라고 부르기도 해요. 물론 그럴 수도 있지만, 인터넷의 과대 광고와 굉장히 비슷하다고 생각해요. AI는 반세기가 넘는 역사를 가지고 있고, 오늘날의 인터넷처럼 우리 삶에 필수적인 존재로 계속 남아있을 거라고 믿어요. 인터넷에 WWW가 있다면, AI에는 LLM(Large Language Model)과 GenAI 같은 존재가 있는 거죠.

만약 제 예측이 맞다면, 특히 소프트웨어 개발자라면 ChatGPT와 대화하는 것뿐만 아니라, 이 기술을 직접 사용하는 방법을 배우면서 새로운 기술을 배우고 받아들이는 게 중요할 거예요. 그리고 그렇게 하기 위해 꼭 수학자일 필요도 없어요! AI를 쉽게 사용할 수 있도록 도와주는 훌륭한 도구들이 정말 많거든요. 그중 하나가 바로 Neo4j랍니다.

Neo4j와 AI

Vector Search가 뭔지 너무 깊게 들어가진 않을게요. 간단하게 설명하자면, 벡터는 화살표처럼 길이와 방향을 가진 것이라고 생각하면 돼요. 2차원에서는 아래 그림처럼 x와 y 두 숫자로 벡터를 표현할 수 있죠.

2차원 벡터

Vector Embedding은 LLM이 자연어 텍스트(또는 이미지나 음악)의 내용을 나타내는 단일 벡터를 생성하도록 하는 건데요. 예를 들어, 이 블로그 글 전체를 하나의 벡터로 표현하는 거죠. 그런데 위 그림처럼 2차원 벡터가 아니라, 1000차원이 넘는 벡터(OpenAI의 경우 1536차원)를 사용해요. 이렇게 텍스트를 숫자로 표현한 다음, 이걸 이용해서 GenAI와 Neo4j에 대한 다른 블로그 글(제가 쓴 글이 몇 개 있어요 😉)처럼 비슷한 텍스트를 검색할 수 있는 거예요. 이때 텍스트에 사용된 단어의 유사성이 아니라, 텍스트의 실제 의미(LLM이 이해하도록 설계된)를 기준으로 검색하는 거죠.

Neo4j를 사용하면 Vector Embedding을 Node의 Property로 저장한 다음, 특정 임베딩과 유사한 Node를 검색할 수 있어요. 검색 속도를 높이기 위해 이런 벡터들을 Indexing할 수도 있고요. 바로 이 기능을 이용해서 가사 검색기를 만들 거예요.

가사 검색기가 왜 필요할까요?

그런데 왜 이런 도구를 만들고 싶을까요? ChatGPT도 이 기능을 꽤 잘하거든요. 예를 들어, 제가 ChatGPT에게 이렇게 물어봤어요.

메탈리카 노래 중에 전쟁에서 심하게 다쳐서 감각을 잃은 남자에 대한 노래가 뭐였지?

그러자 ChatGPT는 이렇게 대답했어요.

당신이 언급한 메탈리카의 노래는 'One'입니다. "One"은 1988년 앨범 "...And Justice for All"의 트랙입니다. 이 곡은 제1차 세계대전 당시 부상을 입어 시각, 청각, 언어 등 모든 감각을 잃은 군인의 이야기를 담은 달튼 트럼보(Dalton Trumbo)의 소설 'Johnny Got His Gun'에서 영감을 받았다. 메탈리카는 이 노래에서 전쟁의 공포와 고통, 그리고 그러한 비극적인 상황에서 일어날 수 있는 개인의 정체성 상실을 전달한다.

음, 우리가 직접 이런 도구를 만들고 싶은 이유는 몇 가지가 있어요.

  • ChatGPT는 Language Model이 훈련될 때 존재했던 정보만 알고 있어요. 그래서 만약 메탈리카의 최신 앨범 '72 Seasons'에 수록된 곡이었다면 찾지 못했을 거예요.
  • ChatGPT는 훌륭하지만, 완전히 신뢰할 수는 없어요. 때로는 솔직한 거짓말을 하기도 하거든요.
  • Spotify 같은 음악 서비스에 이 기능을 넣는다면, 모든 노래가 아니라 라이브러리에 있는 노래만 검색하면 되겠죠.
  • 그리고 Spotify 같은 도구에 이런 기능을 통합하려면, 채팅 기능보다는 좀 더 명확하게 정의된 API를 통합하고 싶을 거예요.

우리가 만들 도구

시작하기 전에 OpenAI 계정을 만들어야 해요. Neo4j는 임베딩을 수행하기 위해 OpenAI, VertexAI, AWS Bedrock을 지원하지만, 이번에는 OpenAI를 사용할 거예요. OpenAI 계정은 유료 계정이라서 비용이 발생할 수 있지만, 제가 이 글을 쓰면서 테스트하는 데 사용한 비용은 2센트도 안 됐어요. OpenAI 계정에서 API Key를 생성해야 하고, 이 키는 아래 쿼리에서 $apiKey로 사용될 거예요.

GenAI 지원은 Neo4j 5.x 버전에서 점진적으로 이루어졌는데요. 여기서 사용할 가장 최신 기능인 임베딩 절차는 Neo4j 5.15에서 출시되었기 때문에, 이 버전을 사용할 거예요.

Neo4j 자체 인스턴스를 사용하고 있다면, neo4j-genai-plugin-5.XX.0.jar 파일을 복사해서 plugins 폴더에 넣어준 다음 Neo4j를 재시작해야 해요. AuraDB를 사용하고 있다면, 기본적으로 이 기능들을 바로 사용할 수 있답니다.

이 글의 내용을 테스트해보고 싶다면 AuraDB Free 인스턴스를 설정하는 것도 좋은 방법이에요. 여기서 무료 계정을 만들 수 있어요: 그리고 인스턴스를 만들어서 직접 테스트해볼 수 있죠. OpenAI 계정도 필요하다는 점, 잊지 마세요!

이번 글에서는 특정 기능을 구현하는 애플리케이션을 만들지는 않을 거예요. 대신 Neo4j Browser에서 Cypher 쿼리를 작성해볼 건데요, 실제 서비스를 개발한다면 당연히 이 쿼리들을 애플리케이션 코드에 통합해야겠죠?

세상 모든 노래에 대해 다 해보면 좋겠지만, 아쉽게도 그런 데이터를 구할 수가 없어서... 대신 St. Anger 앨범까지의 Metallica 노래 가사 라이브러리를 찾았어요. HTML 형식이었지만, 가사를 파싱해서 아티스트 (여기서는 Metallica 하나뿐!), 앨범, 노래로 이루어진 그래프를 만들었답니다. 노래 가사는 각 `Song` **node**의 문자열 **property**로 저장했어요.

아티스트는 빨간색, 앨범은 파란색, 노래는 녹색

직접 테스트해보고 싶다면, 아래 코드를 실행해서 똑같은 데이터셋을 가져올 수 있어요. 물론, 아래 `import` 구문에서 사용하는 것과 동일한 **스키마**를 따른다면, 좋아하는 아티스트와 노래를 사용해도 괜찮아요!

LOAD CSV WITH HEADERS FROM 'https://drive.google.com/uc?export=download&id=1uD3h7xYxr9EoZ0Ggoh99JtQXa3AxtxyU' AS line
CREATE (song:Song {name: line.Song, lyrics: line.Lyrics})
MERGE (album:Album {name: line.Album})
MERGE (artist:Artist {name: line.Artist})
MERGE (song)-[:IS_ON]->(album)
MERGE (album)-[:PERFORMED_BY]->(artist)

자, 이제 **database**가 준비됐으니, 제일 먼저 할 일은 **vector**에 사용할 **property**에 대한 **vector index**를 만드는 거예요. 여기서는 `embedding`이라는 **property**를 사용할 거고, `Song` **node**에 저장할 거예요. `1536`은 **vector**의 차원인데 (OpenAI가 사용하는 차원과 같아요!), 마지막 **property**인 `'cosine'`은 사용할 유사도 알고리즘이에요. 일반적으로 `'cosine'`이 가장 많이 쓰인답니다.

CREATE VECTOR INDEX song_embeddings IF NOT EXISTS
FOR (s:Song) ON (s.embedding)
OPTIONS {
  indexConfig: {
    `vector.dimensions`: 1536,
    `vector.similarity_function`: 'cosine'
  }
}

다음으로는 모든 노래 가사에 대한 **Vector Embedding**을 추가해야 해요. 간단한 `MATCH` **query**를 사용해서 노래를 찾은 다음, 가사 **property** 문자열을 **embedding** 함수에 전달하면 돼요.

MATCH (song:Song)
WITH song, genai.vector.encode(song.lyrics, "OpenAI", {token: $apiKey}) AS vector
CALL db.create.setNodeVectorProperty(song, "embedding", vector)

`db.create.setNodeVectorProperty()` **procedure**는 `SET song.embedding = 임베딩`과 똑같은 역할을 하지만, **vector**를 더 효율적인 형식으로 저장해준다는 점이 다르답니다.

이 **query**는 **database**에 있는 모든 노래에 대해 OpenAI **API**를 한 번씩 호출하기 때문에, 실행하는 데 시간이 좀 걸릴 거예요. 위 함수 대신 **batch procedure**를 사용하는 방법도 있는데, 이 경우에는 (여기서는) 단 한 번의 **API** 호출만 발생해요.

MATCH (song:Song)
WITH collect(song.lyrics) AS lyrics, collect(song) AS songs
CALL genai.vector.encodeBatch(lyrics, "OpenAI", {token: $apiKey}) YIELD index, resource, vector
CALL db.create.setNodeVectorProperty(songs[index], "embedding", vector)

여기서 주의할 점은 한 **batch**에 포함할 수 있는 항목 수에 제한이 있다는 거예요 (OpenAI의 경우 2048개). 따라서 **database**에 Metallica 말고 다른 아티스트의 노래도 있다면, 예를 들어 아티스트별로 또는 앨범별로 나눠서 처리해야 할 거예요.

자, 이제 모든 설정이 끝났으니 가사 **search**를 수행할 준비가 됐어요. 검색할 문구는 `phrase`라는 **parameter**로 설정하고, `apiKey`는 여전히 **parameter**로 유지할 거예요 (아래에는 표시하지 않았지만요).

:params 
{
  phrase: "A song about a guy who is so badly wounded in war so he no longer has any senses",
  apiKey: "*****"
}

**search**를 하려면, 먼저 똑같은 함수를 호출해서 검색 문구에 대한 **Vector Embedding**을 생성해야 해요. 그런 다음, 전용 **procedure**를 사용해서 **database**에서 가장 유사한 **embedding**을 가진 노래를 찾아주는 **vector search**를 수행하면 된답니다.

WITH genai.vector.encode($phrase, "OpenAI", {token: $apiKey}) AS embedding
CALL db.index.vector.queryNodes('song_embeddings', 1, embedding) YIELD node AS song, score
RETURN song.name

이것이 우리에게 DISPOSABLE HEROES라는 답을 주네요. 글쎄요, 그건 우리가 기대했던 건 아니었어요. 우리는 ONE을 생각하고 있었거든요. DISPOSABLE HEROES 역시 고통에 대한 전쟁 노래이기 때문에 정답이긴 하지만, 우리가 원하는 건 아니죠. 이건 정확한 과학이 아니니까요. 그래서 일반적으로 최고의 일치 항목 수를 요청하는 게 가장 좋아요. 올바른 것은 일반적으로 상위 3~5개 결과에 있거든요. queryNodes의 두 번째 매개변수는 우리가 원하는 결과이니까, 이를 3으로 늘리면 돼요.

WITH genai.vector.encode($phrase, "OpenAI", {token: $apiKey}) AS embedding
CALL db.index.vector.queryNodes('song_embeddings', 3, embedding) YIELD node AS song, score
RETURN song.name

이제 다시 실행해볼게요.

DISPOSABLE HEROES
ONE
ONE

됐네요! 두 번째 일치로 올바른 것을 얻었어요. 그리고 세 번째 경기는…? 왜 우리는 그것을 두 번 얻을까요? 음, 두 개 이상의 앨범에 포함되어 있기 때문에 우리가 모델링한 대로 데이터베이스에 복제되는 거죠.

이것이 우리 솔루션을 뒷받침하는 Knowledge Graph를 갖는 것이 매우 좋은 점이에요. 가사를 기반으로 노래를 찾은 다음 그래프를 탐색하여 다른 모든 것을 찾을 수 있거든요.

WITH genai.vector.encode($phrase, "OpenAI", {token: $apiKey}) AS embedding
CALL db.index.vector.queryNodes('song_embeddings', 3, embedding) YIELD node AS song, score
MATCH (song)-[:IS_ON]->(album:Album)-[:PERFORMED_BY]->(artist:Artist)
RETURN song.name AS Song, album.name AS Album, artist.name AS Artist

이제 결과는 이렇습니다.

Song                Album                  Artist
DISPOSABLE HEROES   MASTER OF PUPPETS      Metallica
ONE                 S&M                    Metallica
ONE                 And Justice For All..  Metallica

이제 제대로 작동하는지 확인하기 위해 한 번 더 테스트하면 돼요. 이번에는 다음을 검색해볼게요.

악몽을 꾸는 소년의 노래

결과는 다음과 같아요.

Song                           Album                  Artist
Enter Sandman                  Black Album            Metallica
Enter Sandman                  S&M                    Metallica
The thing that should not be   MASTER OF PUPPETS      Metallica

Enter Sandman은 제가 의도한 것이므로 성공이라고 해야겠죠?

됐어요. 네 줄의 코드로 지능형 노래 시놉시스 찾기를 완료했어요!

참고 자료

Vector Search Index에 대한 문서는 다음과 같아요.

벡터 검색 인덱스 – Cypher 매뉴얼

여기에서 내장 함수/절차에 대한 문서를 찾을 수 있어요.

GenAI 통합 – Cypher 매뉴얼


  • Vector Embedding 검색
  • GenAI 도구

에이치시스템즈LogTree는 Neo4j 기반 GraphRAG 플랫폼으로, 데이터를 자동으로 지식그래프화하고 자연어 질의로 즉시 답을 제공합니다.

👉 에이치시스템즈 홈페이지

반응형
반응형

확장 법칙은 정체될 수 있지만 GenAI는 줄어들지 않았어요. 엔지니어들은 어디에서나 무엇이 작동하고 어떻게 유용하게 만들 수 있는지 알아내느라 바빴죠. GraphRAG, 즉 Knowledge Graph와 Retrieval-Augmented Generation (RAG)이 결합된 기술은 다양한 기술로 발전해 왔고, 연구 논문과 소프트웨어 통합도 활발하게 이루어지고 있어요.

이 점을 염두에 두고 두 번째 GenAI 그래프 모임을 준비했고, 그래프와 LLM의 교차점에서 일하는 뛰어난 분들을 초대했어요. 목표는 GraphRAG 주변에서 일어나는 많은 일에 대한 메모를 파악하고 비교하는 것이었답니다.

회고전: 5월부터 지금까지

An incomplete timeline of GraphRAG since the first GenAI Graph Gathering in May

5월 첫 번째 GenAI 그래프 모임 이후 GraphRAG의 타임라인이에요 (완전하진 않지만요!).

처음에 GenAI 그래프 모임에서 GenAI 애플리케이션이 세 가지 주요 버킷의 소스 데이터를 사용하는 것을 관찰하면서 검색을 위해 Knowledge Graph를 사용하는 방법을 탐색했었죠.

  1. 텍스트 파일 또는 PDF의 구조화되지 않은 데이터
  2. 기존 데이터베이스의 구조화된 데이터
  3. 두 가지가 결합된 혼합 데이터

여름 동안 개발자와 기업의 관심이 엄청나게 증가했어요. 프로젝트는 "PDF로 채팅" 또는 "CSV로 채팅"과 같이 구조화되지 않았거나 구조화된 데이터로 시작하는 경향이 있었죠. 하지만 첨단 기술이 앞으로 나아갈 길을 제시했음에도 불구하고 많은 사람들이 파일럿 단계에서 정체되었어요.

개념 증명 이상의 일을 할 수 있었던 사람들의 공통점은 무엇이었을까요? 저희 경험에 따르면 비정형 데이터와 정형 데이터 사이에 더 강력한 연결이 있어서 혼합 데이터의 최적 지점에 도달하는 것 같았어요.

Among GenAI proof-of-concept efforts, 71% stall when only using unstructured data. The third that gets past PoC either started with or incorporated more structured business data.

GenAI 개념 증명 노력 중 71%는 구조화되지 않은 데이터만 사용할 때 멈춘다고 해요. PoC를 통과한 3분의 1은 더 구조화된 비즈니스 데이터로 시작하거나 통합했대요.

학문적 이론을 실제 실무로 전환하는 건 정말 어려운 일이죠. 그래서 저희는 몇 가지 일을 하기로 결정했어요:

  • 패턴 카탈로그를 큐레이팅해서 연구 논문에서 정보를 추출하는 거예요.
  • 검증된 접근 방식을 와 라이브러리로 구현하는 거죠.
  • 동료들과 만나서 도움이 될 만한 다른 게 뭐가 있는지 알아보는 거예요.

Gathering 행사에서 저희는 Knowledge Graph 구성, GraphRAG 기술, 그리고 실제 경험에 중점을 두고 그룹 토론을 진행했어요.

GraphRAG 시작하기

여러분 모두 Vector Embedding이 뭔지 이미 알고 있고, RAG 약어가 뭔지도 설명할 수 있는 AI 엔지니어라고 가정해 볼게요. 그럼 가장 좋은 시작 방법은 뭘까요?

공유 노트 내용을 한번 살펴볼까요:

  • 기술 전반에 걸친 병렬 비교
  • 연결된 데이터와 결과 프롬프트를 포함해서 답변을 생성하는 데 사용된 전체 컨텍스트를 공개하는 것
  • 그래프 시각화는 데이터와 스키마에 대한 "아하!" 모먼트에 정말 중요해요.
  • Knowledge Graph 구성, 자동 마법과 손으로 직접 선별하는 것 사이의 균형을 맞추는 데 도움을 주는 것
  • 세 가지 방향: 1) 모든 소스에 대한 일반적인 도구 기반 워크플로, 2) 잘 알려진 심층적인 예시로서 구체적인 "Northwind" Knowledge Graph, 3) 다양한 예시를 위한 독립 실행형 노트북

 

시작에 대한 대화를 확장한 또 다른 세션에서는 개발자 경험이라는 더 넓은 주제를 고려했어요.

공유 노트에서:

  • 대부분의 사람들은 비정형 데이터로 시작하는 반면, GraphRAG는 정형 또는 혼합 데이터로 시작한다는 점
  • 일반적인 RAG는 도메인에 구애받지 않지만, GraphRAG는 도메인에 따라 달라진다는 점
  • "고급 RAG"는 GraphRAG의 첫 번째 단계로 이해될 수 있으며, 도메인에 대해 상대적으로 독립적이라는 점
  • 템플릿으로 해결할 수 있는 "콜드 스타트" 또는 "빈 캔버스" 문제가 있다는 점
  • 올바른 GraphRAG 접근 방식은 도메인별로 다르다는 점. 지침과 예시가 필요하다는 점
  • 광범위한 일반 비즈니스 문제를 다루는 "7개의 그래프"(자세한 내용은 추후 제공)
  • 고전적인 객체 지향 디자인 패턴과 같은 공통 참조로 사용되는 GraphRAG 패턴 카탈로그

Knowledge Graph 엔지니어링

Knowledge Graph는 요약이 포함된 덩어리진 텍스트만큼 단순할 수도 있고, 전체 기업에 대한 통합 뷰만큼 포괄적일 수도 있는 전체 정보 아키텍처에요. 데이터 엔지니어링과 마찬가지로 Knowledge Graph에도 데이터 준비, 변환, 모델링 및 평가가 모두 필요하죠.

그룹은 Knowledge Graph의 혼합을 고려했어요.

  • CSV 또는 JSON과 같은 구조화된 데이터에서 매핑된 도메인 그래프
  • 구조화된 데이터에서 매핑된 긴 형식의 텍스트가 포함된 도메인 그래프
  • 제품 카탈로그나 매뉴얼과 같은 잘 알려진 문서 컬렉션에서 파생된 알려진 구조의 어휘 그래프
  • 알려진 용어에 따라 명명된 개체 인식(NER)을 사용하여 검색된 구조가 포함된 어휘 그래프
  • NER과 구조화된 데이터를 결합하여 알려진 구조와 발견된 구조를 모두 포함하는 어휘 그래프
  • 개방형 NER를 사용하여 완전히 발견된 구조를 갖는 어휘 그래프

온톨로지: 계획은 무엇인가요?

온톨로지는 단순히 해당 속성과 이들 간의 관계를 보여주는 주제 영역의 개념 및 범주 집합이에요. 원한다면 이걸 그래프 스키마라고 불러도 괜찮아요.

공유 노트에서:

  • 스키마는 상호 운용성, 설명 가능성, 접지에 도움이 된다는 점
  • 엔터티 추출 및 비정형 데이터와 정형 데이터 간의 정렬에 유용하다는 점
  • 완전하고 형식적인 온톨로지는 부담스러울 수 있다는 점. GraphRAG에 대한 더 간단한 형식이 있을까요?
  • 비정형 데이터와 일치하도록 카탈로그의 기존 스키마를 자동으로 선택할 수 있다는 점
  • 엄격하게 정확하고 사용하기 쉬운 균형을 맞추는 게 중요하다는 점
  • 스키마는 상호 운용성, 설명 가능성, 접지에 도움이 된다는 점

 

정보 검색을 위해 그래프를 사용하는 다양한 기술을 탐구하는 연구가 정말 활발하게 진행되고 있어요.

공유 노트 내용을 한번 살펴볼까요:

  • 연구에는 상황별 검색, 쿼리 중심 요약, 텍스트-암호화, 계층형 메모리, 그래프 기반 재순위 지정, 하이브리드 index, GNN 등이 포함돼요.
  • GNN과 그래프 데이터 과학은 정확성을 강화하고 개선하는 데 도움을 줄 수 있어요.
  • 그래프는 소스 정보, 메모리, 보안 제약 조건, 정보 검색을 위한 안내 경로를 나타낼 수 있어요.
  • 가장 큰 과제는 "올바른" 일이 사용 사례에 따라 다르다는 점이에요.

 

GenAI 그래프 수집은 조직 간 협업을 위한 특별한 기회였어요. 당장의 목표는 P2P 연결이었지만, 장기적으로는 각 참여자가 자신의 길에서 성공하고 궁극적으로 모든 사람이 GraphRAG의 혜택을 받는 것이죠.

RAG의 'R'에 그래프를 사용하는 GraphRAG는 정말 다양한 접근 방식과 기술로 계속 발전하고 있어요. 다행히 모든 것을 다 알 필요도 없고, 한 번에 모든 걸 다 할 필요도 없다는 거죠. 그래프는 보기 좋게 구성되어 있잖아요. 그래프로 생각하는 방식, 즉 멘탈 모델에는 준비된 범위까지 확장되는 몇 가지 개념이 있어요. ML 모델부터 애플리케이션 워크플로, 디스크의 데이터 스토리지까지 그래프가 될 수 있죠. 아니면 단순히 텍스트 덩어리들을 서로 연결하고, 문서들을 연결하는 것만큼 간단할 수도 있고요.

최소 실행 가능한 그래프로 시작해보세요. 데이터를 더 추가하고, 풍부하게 만들고, 연결하고, 계속 반복하는 거예요!

  • 그래프RAG

에이치시스템즈LogTree는 Neo4j 기반 GraphRAG 플랫폼으로, 데이터를 자동으로 지식그래프화하고 자연어 질의로 즉시 답을 제공합니다.

👉 에이치시스템즈 홈페이지

반응형
반응형
  • Cypher & GQL

편집자 주: 이 강연은윌 리옹 at 클라우드 개발자를 위한 Neo4j의 Connections 이벤트 그래프.

풀스택 개발자의 관점에서 Neo4j가 현대 클라우드 생태계에 어떻게 적응하는지 공유할게요. 저는 Neo4j의 개발자 관계 팀에서 일하고 있고, Neo4j와 다양한 기술을 통합하고 고객이 이미 이 작업을 수행하는 방법을 살펴보는 데 많은 시간을 보내고 있어요.

Neo4j 그래프 플랫폼

Full Stack Graph in the Cloud

데이터베이스로서 Neo4j는 우리 애플리케이션의 아키텍처 핵심에 딱 맞는 것 같아요. 우리가 생각해야 할 것은 Neo4j가 다른 기술과 어떻게 작동하는지인데, 이는 우리가 달성하려는 목표에 따라 달라지죠. 위 다이어그램에서 오른쪽의 데이터 과학 및 분석 사용 사례부터 왼쪽의 더 많은 트랜잭션 운영 애플리케이션 구축에 이르는 스펙트럼을 볼 수 있어요. 이 블로그를 통해 저는 스펙트럼의 왼쪽 측면에 더 집중하고, 예를 들어 클라이언트와 데이터베이스 사이에 있는 API 계층을 구축하여 Neo4j로 운영 워크로드를 처리하는 방법을 공유할 거예요.

랍스터 그래프 애플리케이션

Lobsters는 Hacker News와 비슷한 뉴스 애그리게이터 사이트인데, 댓글을 보면 좀 더 친근한 느낌이 들 수도 있어요. 기술 커뮤니티에서 무슨 일이 일어나고 있는지 살펴보기 좋은 사이트죠. 그래서 Lobsters 사이트의 그래프 버전을 만들어보면 재미있을 것 같아서, "Lobsters Graph"라는 이름으로 만들어봤어요. 기본적으로 Lobsters에서 데이터를 가져오는 그래프 시각화 애플리케이션이에요. 여전히 인기 있는 기사와 관련 주제를 볼 수 있지만, 정보를 좀 더 시각적인 방식으로 볼 수 있다는 게 장점이죠. 기사의 태그와 주제를 기반으로 내가 관심 있는 내용을 더 빠르게 확인할 수 있고요. Lobsters Graph가 어떻게 생겼는지 간단하게 보여주는 데모 영상이에요.

애플리케이션 아키텍처

데모에서 보셨듯이, 아주 간단한 그래프 시각화 애플리케이션이에요. 이제 이 애플리케이션을 어떻게 구축했는지, 좀 더 구체적으로 아키텍처가 어떤 모습인지 자세히 살펴볼게요. 풀 스택 개발자로서 우리가 다루는 문제들을 보여주는 몇 가지 흥미로운 부분들이 있거든요. Lobsters의 JSON endpoint에서 데이터를 가져와 Neo4j에 로드하는 방법, 그리고 Neo4j에 데이터가 있으면 해당 데이터를 애플리케이션 계층에 노출하는 방법을 공유할게요.

Neo4j로 데이터를 가져오는 방법

가장 먼저 Neo4j AuraDB 인스턴스를 실행해야 해요. 저는 AuraDB 무료 인스턴스를 사용했는데, 이런 취미 프로젝트를 시작하기에 딱 좋은 방법이죠. 다행히 Lobsters는 데이터를 몇 가지 JSON 피드에 게시하고 있어요. 예를 들어, hottest.JSON endpoint는 순위가 가장 높은 기사를 기준으로 정렬된 리더보드 역할을 해요. 최신 endpoint도 있어서 Lobsters에 게시된 최신 기사를 추적할 수도 있고요.

그래프 데이터 모델링

다음 단계는 반복적인 과정인 그래프 데이터 모델링이에요. 제 소스 데이터는 JSON이고요. 이걸 그래프로 표현하기 위해, 먼저 Node가 될 엔터티를 식별하는 것부터 시작해요. 이 경우에는 Node는 기사, 기사를 게시한 사용자, 그리고 기사와 연결되는 태그가 되겠죠.

그 다음에는 이러한 개체들이 어떻게 연결되어 있는지 생각해요. 이게 바로 Relationship이 되는 거죠. 마지막으로, 제가 가지고 있는 Property들을 살펴보죠. 예를 들어 기사에는 ID, URL, 관련 점수가 있어요. 이러한 Property들은 Node나 Relationship에 저장되는 속성이 되는 거예요. 이 반복적인 프로세스의 마지막 단계는 애플리케이션과 데이터 모델의 비즈니스 요구 사항을 살펴보고, 비즈니스 요구 사항에 대한 질문에 답하는 이 그래프를 통한 순회가 있는지 확인하는 거예요.

이 경우에는 매우 간단해요. 상위권 기사도 보고 싶고, 태그도 보고 싶어요. 그리고 동일한 태그가 있는 다른 기사를 볼 수 있기를 원하죠. 따라서 기사 Node를 ID나 점수별로 정렬하고, 해당 태그 Relationship을 순회해야 한다고 생각하게 돼요. 이 데이터 모델링 프로세스를 진행하면서 다음과 같은 훌륭한 도구를 사용할 수 있어요. Arrows는 다이어그램을 그리는 데 유용하죠. Arrows에서 다이어그램을 그린 후에는 이미지로 내보낼 수도 있고, 데이터 모델을 설명하는 JSON을 내보낼 수도 있어요. JSON 데이터를 버전 제어로 관리하고 프로젝트에 참여하는 다른 사람들과 모델을 공유할 수 있다는 점이 정말 좋죠.

Cypher에서 JSON 데이터 작업

이제 데이터 모델과 JSON 데이터가 준비되었어요. 여기서부터는 Cypher를 사용해서 해당 JSON 데이터를 가져와 Neo4j에 로드할 거예요. 다행히 Neo4j에는 APOC Load JSON procedure가 있답니다.

이건 APOC 절차 라이브러리의 일부인데요, APOC를 표준 라이브러리처럼 생각하면 돼요. 몇몇 프로시저와 기능으로 Cypher를 확장해주고, JSON 데이터 작업을 가능하게 해주거든요. apoc.load.json과 함께라면, JSON 엔드포인트로 가서 Cypher를 사용해 데이터를 다시 가져올 수 있어요. 들어오는 JSON 객체(여기선 객체 배열)를 반환하는 것뿐이지만, Cypher를 사용해서 그래프에서 만들고 싶은 데이터 모델을 설명하고 Cypher로 풀어낼 수 있죠. 각 기사에 대해 Cypher를 써서 기사 `Node`, 사용자 `Node`, 태그, 그리고 `Relationship`을 그래프에 생성하는 거예요.

이렇게 Cypher 구문 하나로 데이터를 로드했어요. 이제 Neo4j Browser나 Bloom을 사용해서 쿼리할 수 있죠. 랍스터 데이터를 특정 시점에 표현하는 데는 괜찮지만, 인터넷 검색을 할 때 매일 아침 사이트에 가서 그날의 블로그를 읽고 싶을 수도 있잖아요? 그럼 데이터는 계속 새로고침되어야 하고, 이 스크립트를 제 컴퓨터에서만 실행할 수는 없겠죠. 클라우드에 지속적으로 실행되면서 랍스터 데이터를 가져오는 무언가가 필요할 거예요.

Github 작업 – 플랫 데이터 크론 작업

바로 이럴 때 GitHub Actions가 등장하는 거죠! GitHub 이벤트에 응답하는 맥락에서 GitHub Actions를 많이 보셨을 텐데요. 예를 들어 코드를 체크인하거나 GitHub에 Pull Request가 들어오면 GitHub Actions를 사용해서 테스트를 실행하거나 배포를 트리거할 수 있어요. GitHub Actions를 cron 작업처럼 사용해서 매시간, 매일 실행할 수도 있고요.

GitHub 팀에서 플랫 데이터라는 GitHub Action을 출시했는데, 정말 깔끔하더라고요. 이걸 사용하면 JSON이나 CSV 같은 플랫 파일 엔드포인트로 가는 GitHub 작업을 만들거나, 주기적으로 데이터베이스 `Query`를 실행한 다음 해당 데이터를 GitHub에 커밋할 수 있어요. 데이터에 대한 기록도 남길 수 있고, 데이터를 데이터베이스에 로드하는 등의 후처리 단계도 정의할 수 있죠.

위에서 보시는 것처럼 GitHub 리포지토리에서는 이 플랫 데이터 GitHub 작업을 사용해서 Lobsters JSON 엔드포인트로 이동하고, 최신 .json 파일을 가져와 GitHub에 체크인하는 YAML 파일을 생성해요. 이 데이터는 자주 바뀌는 데이터가 아니라서 cron에서 매시간 실행되도록 설정해뒀어요.

Github 작업: 데이터 로드를 위한 평면 그래프

해당 데이터를 Neo4j Aura에 로드하기 위한 후처리 작업으로, 저는 평면 그래프라는 간단한 GitHub Action을 만들었어요. 이걸 사용하면 JSON 데이터로 작업하는 방법(여기서는 Cypher를 사용하겠죠?)을 정의할 수 있답니다.

전체 YAML 파일은 다음과 같아요.

YAML

먼저 Flat Data GitHub Action을 사용해서 최신 .json 파일을 가져와요. 그리고 Flat Graph GitHub Action을 사용하면 되죠. GitHub에는 비밀로 체크인된 Neo4j Aura 인스턴스에 대한 자격 증명이 있답니다. 다음은 JSON 배열을 반복하는 방법을 알려주는 Cypher 쿼리에요. apoc.load.json을 사용해서 이 쿼리와 매우 유사하게 만들 수 있지만, 이번에는 해당 JSON을 Cypher 파라미터로 전달할 거예요. 이렇게 하면 데이터가 Neo4j Aura에서 매 시간마다 계속 업데이트될 거예요.

Neo4j Bloom을 사용한 시각적 탐색

데이터를 시각적으로 탐색하는 방법 중 하나는 Neo4j Bloom을 사용하는 거예요.

Bloom

Bloom은 Neo4j에 데이터만 있다면 그래프 UI를 만들 수 있는 아주 훌륭한 노코드 솔루션이에요. 하지만 이번 경우에는 Bloom이 딱 맞는 선택은 아니에요. 좀 더 간단한 걸 원하고, Bloom의 모든 기능을 다 보여주고 싶지도 않거든요. 웹 애플리케이션을 만들고 싶어요.

Neo4j GraphQL 라이브러리를 사용하여 API 계층 구축

데이터베이스를 프론트엔드 애플리케이션에 바로 노출하는 대신, 클라이언트와 데이터베이스 사이에 API 계층을 두고 싶어요. 여기에는 몇 가지 커스텀 로직이 들어가고, 보통 인증도 여기서 처리하죠. 이 프로젝트에서는 다양한 도구를 사용해서 이 문제를 해결할 수 있는 여러 방법이 있는데, 이번에는 Neo4j GraphQL 라이브러리를 사용해서 GraphQL API를 만들어볼게요.

Neo4j GraphQL 라이브러리는 JavaScript GraphQL API를 만들기 위한 JS 라이브러리가 아니에요. 다시 말하지만, 클라이언트와 데이터베이스 사이에 있는 이 레이어의 경우 GraphQL을 사용해서 데이터베이스에 직접 쿼리하지 않아요. 그럼에도 불구하고 Neo4j GraphQL 라이브러리는 시작할 때 여러 가지 장점을 제공해준답니다. 혹시 GraphQL에 익숙하지 않다면, 이건 기본적으로 API 쿼리 언어이자 API 요청을 처리하기 위한 런타임이라고 생각하면 돼요.

GraphQL을 사용하면 작업 중인 데이터 형태를 정확하게 정의하고 API에서 사용할 수 있는 엄격한 유형 시스템을 갖게 돼요. 클라이언트에서 해당 데이터를 쿼리하는 방법을 정의하는 거죠. 여기서 그래프 부분이 중요한데, 이러한 유형들이 어떻게 연결되는지를 설명해주거든요. GraphQL의 그래프는 다른 유형을 참조하는 유형의 관계 필드에 대한 아이디어를 가지고 있어요.

쿼리할 때 클라이언트는 GraphQL 유형 정의에 설명된 이 데이터 그래프를 자유롭게 탐색해서 클라이언트가 관심 있는 데이터, 또는 클라이언트가 렌더링해야 하는 데이터(예: 애플리케이션의 뷰)를 가져올 수 있어요. 다시 가져오려는 데이터를 지정하는 클라이언트와 결합된 이 유형 시스템은 우리가 돌아오는 데이터의 모양과 유형을 정확히 알고 있다는 걸 의미하고, 이는 클라이언트를 위해 유형이 안전한 데이터로 작업할 수 있는 정말 좋은 방법이에요.

언급했듯이 Neo4j GraphQL 라이브러리는 JS 그래픽 API를 구축하기 위한 JavaScript 라이브러리예요. API 레이어를 구축할 때 작성해야 하는 상용구 코드를 줄이는 데 정말 도움이 될 거예요. 새로운 GraphQL 라이브러리를 사용해서 GraphQL 유형 정의를 정의하고 이를 라이브러리에 전달하면, GraphQL API를 생성하는 데 필요한 많은 작업을 대신 처리해줘요. 따라서 Neo4j GraphQL 라이브러리의 주요 목표 중 하나는 GraphQL 최초 개발 아이디어를 지원하는 것이죠.



이건 GraphQL API뿐만 아니라 데이터베이스에 대한 데이터 모델을 정의하는 GraphQL 유형 정의가 있다는 걸 의미해요. GraphQL 정의는 실제로 데이터베이스의 데이터 모델을 구동할 수 있답니다. API에서 이는 Neo4j와 GraphQL 레이어로 작업할 때 두 개의 별도 스키마를 유지할 필요가 없다는 걸 의미하죠.

앞서 언급한 도구인 Arrows의 좋은 점 중 하나는 그래프 다이어그램을 만들 때 내보내기 옵션 중 하나가 GraphQL 유형 정의라는 거예요. 이러한 유형 정의는 Neo4j GraphQL 라이브러리에서 작동하고요. 그래프 모델을 시각적으로 정의하면 Arrows에서 직접 GraphQL 유형 정의를 내보낼 수 있어요.

GraphQL CRUD API 자동 생성

유형 정의가 있으면 이를 Neo4j GraphQL 라이브러리에 넘겨주세요. 그러면 모든 CRUD 작업(Create, Read, Update, Delete)이 생성될 거예요. 기본적으로 유형 정의에 정의된 모든 유형에 대한 작업이죠.

이건 순서 지정, 페이지 매김, 복잡한 필터링을 추가하고 데이터베이스가 지원하는 공간 및 시간 기본 유형에 대한 지원을 추가해서 Node 유형을 검색하기 위한 API의 진입점이에요.

GraphQL에서 Cypher 생성

쿼리 시 임의의 GraphQL 요청은 Cypher로 변환돼요. 개발자 입장에서 데이터 계층에서 데이터를 가져오는 방법을 설명하기 위해 많은 상용구 코드를 작성할 필요가 없다는 걸 의미하죠. 이는 개발자 생산성에 좋지만, 여기에는 또 다른 엄청난 성능 이점이 있어요. 단일 데이터베이스 쿼리를 생성하고 있고, 이는 데이터베이스에 대한 단일 왕복 여행이며 데이터베이스는 해당 쿼리를 최적화할 수 있거든요.

일반적으로 GraphQL을 사용하면 여러 데이터베이스 요청이 발생하죠. 이걸 캐싱하고 일괄 처리하는 방법을 고민해야 하지만, Neo4j GraphQL 라이브러리를 사용하면 기본적으로 생성되는 단일 데이터베이스 `쿼리`를 사용해서 루트에서 전체 요청을 자동으로 해결해줘요.

Cypher로 GraphQL 확장하기

이제 Neo4j 그래프 라이브러리를 사용해서 타입 정의 없이 무료로 생성되는 기본 CRUD 기능을 넘어서 볼 거예요. 정의하고 싶은 사용자 정의 로직이 있다면, Cypher GraphQL `스키마` 지시문을 사용할 수 있어요.

`스키마` 지시문은 서버 측에서 발생하는 사용자 정의 작업이 있다는 걸 나타내는 GraphQL에 내장된 확장 메커니즘이에요. Neo4j GraphQL 라이브러리에는 꽤 많은 지시문이 있는데요. 개발자가 지시문을 통해 API를 구성하는 주요 방법이죠. Cypher 지시문은 Neo4j GraphQL 라이브러리에서 제가 제일 좋아하는 기능이자, 아마도 가장 강력한 기능일 거예요. 이걸 사용하면 Cypher `쿼리`를 작성하고, Cypher `쿼리`에 매핑되는 타입 정의의 필드에 주석을 달 수 있어요.

즉, Cypher에서 사용자 지정 복잡한 로직을 작성하고 `스키마`에 주석을 달기만 하면 해당 기능을 추가할 수 있다는 거죠. 드라이버 연결 생성에 대해 걱정할 필요도 없어요. GraphQL 요청에서 생성된 단일 생성 데이터베이스 `쿼리`와 계속 작동하니까요. 하위 `쿼리`로 연결되므로 주소 지정, 데이터 계층으로 여러 번 왕복하지 않고 데이터베이스가 `쿼리`를 최적화하도록 하는 것의 성능 이점을 계속 활용할 수 있어요.

프론트엔드에서 그래프로 작업하는 방법: Next.js

이제 GraphQL API가 있고 타입 정의가 정의되었고, Neo4j GraphQL 라이브러리를 사용하고 있고, 클라이언트에서 GraphQL 요청을 작성해서 애플리케이션에서 렌더링하려는 데이터를 정확하게 가져올 수 있어요. 다음 단계는 프론트 엔드에서 그래프 데이터와 GraphQL을 사용해서 작업하는 방법에 대해 생각하는 거예요.

저는 Next.js를 사용하는 걸 좋아해요. React를 기반으로 구축된 프레임워크죠. SEO에 매우 유용한 서버 측 렌더링이나 파일 기반 라우팅과 같은 일부 기능을 추가하는 메타 프레임워크라고도 해요. 새로운 JavaScript 파일이나 구성 요소를 생성하고 성능을 위해 이미지 압축 및 코드 분할을 처리하기 위한 경로를 자동으로 확보할 수 있어요.

특히 우리와 관련된 기능은 서버 측 API 경로인데요. Next.js를 사용하면 그래프 시각화가 될 프런트엔드 React 코드를 만들 수 있어요. GraphQL 엔드포인트를 API 경로로 구축하고 배포할 수도 있고요. 이는 서버리스 기능으로 배포되죠. 이제 하나의 프레임워크를 사용하는 하나의 코드베이스에 프런트엔드와 백엔드 코드가 모두 있는 거예요.

아폴로 클라이언트

React 애플리케이션으로서 우리는 일반적으로 아폴로 클라이언트 GraphQL 데이터로 작업해요.

여기서는 Apollo Client 인스턴스를 생성한 다음 이를 React 컴포넌트 계층 구조에 주입해요. 애플리케이션의 모든 React 컴포넌트는 React의 훅에 액세스할 수 있어서 GraphQL `쿼리`를 작성할 수 있죠. 그러면 Apollo Client가 데이터 가져오기를 처리하는데요. 동일한 데이터를 요청하는 경우 Apollo Client는 API 계층으로 이동하는 대신 로컬에 캐시할 수 있어요. Apollo Client는 또한 이 데이터를 React 컴포넌트의 상태 데이터로 관리한답니다.

우리가 해야 할 일은 애플리케이션을 처음 로드할 때 가져오려는 모든 데이터를 정의하는 GraphQL `쿼리`를 작성하는 거예요. 이 경우에는 점수별로 정렬된 기사가 될 텐데, 현재 시점에서 가장 높은 기사를 제공하겠죠. 또한 사용자 상호 작용을 처리해서 해당 주제 중 하나를 클릭하면 API로 돌아가 해당 특정 태그에 연결된 더 많은 기사를 가져올 수 있기를 원해요.

React Force Graph를 사용한 그래프 데이터 시각화

이제 프론트 엔드에 데이터가 있고 그래프 시각화를 구축하려고 해요.

대부분의 그래프 시각화의 중요한 특징은 강제 지향 레이아웃이라는 아이디어인데요. 이는 시각화의 `노드`가 서로 밀어내는 반발 자성을 가지며 `관계`가 스프링 역할을 하여 `노드`를 끌어당기는 물리 시뮬레이션이 브라우저에서 진행되고 있음을 의미해요.

이 물리 시뮬레이션을 실행하고 시각화가 안정화되면 특정 `노드`가 다른 `노드`에 연결되는 위치와 그래프에서 `노드` 그룹이 존재하는 위치를 해석할 수 있는 클러스터와 커뮤니티가 형성되는 위치를 확인할 수 있죠. 기본적으로 거의 모든 `노드` 차트 시각화에서 강제 방향 레이아웃에 대한 아이디어를 볼 수 있어요. 물론 사용할 수 있는 다른 레이아웃 알고리즘이 있지만 Neo4j Bloom 및 Neo4j Browser에서 볼 수 있듯이 강제 지정이 매우 일반적이랍니다.

우리는 React를 사용하고 있으므로 다음과 같은 React 라이브러리를 사용해 볼게요. React Force Graph. 이는 Force Graph라는 라이브러리 위에 구축되었어요. 3D 그래프 구현을 구축할 수 있는 3D 버전도 있는데, 이는 본질적으로 React Force Graph에 대한 React 바인딩이에요.

이 경우 GraphQL `쿼리`는 JSON JavaScript의 객체로 다시 제공되므로 이를 React Force Graph 라이브러리에 쉽게 전달할 수 있어요. `노드` 대신 텍스트를 시각화하고 싶기 때문에 약간의 구성을 수행해야 하지만 그래프 시각화를 구축하기 위해 몇 가지 사소한 구성 외에 수행해야 할 작업은 많지 않아요.

이제 프론트 엔드에 그래프 시각화가 있고 Neo4j에서 데이터를 가져오는 GraphQL API를 통해 데이터를 가져오고 있어요. 이 데이터는 GitHub 작업을 사용하여 지속적으로 업데이트되고 있답니다.

Vercel을 사용한 배포

이제 프런트엔드 React 앱과 GraphQL을 모두 사용하여 애플리케이션을 배포할 차례인데요. 바로 Vercel을 사용해 볼 거예요. Vercel은 개발자에게 최적화된 배포 플랫폼이죠. Vercel에서는 Lobsters Graph용 GitHub 저장소를 연결할 수 있어요.

제 GraphQL API가 Vercel에 의해 서버리스 기능으로 배포될 예정이고 Neo4j Aura 인스턴스에 연결할 수 있어야 하기 때문에 환경 변수와 Neo4j Aura 인스턴스에 대한 연결 자격 증명을 입력하여 약간의 구성을 수행해야 해요.

GitHub 저장소를 처음 연결할 때 Vercel은 애플리케이션을 빌드한 다음 정적 React 코드를 Vercel CDN에 배포한답니다.

기본적으로 두 개의 도메인이 제공되는데요. 위에 보이는 것처럼 사용자 지정 도메인을 추가해서 특정 배포 미리보기 URL을 얻을 수도 있어요. Pull Request가 있거나 GitHub에 새로운 코드를 Push할 때마다 Vercel은 이 GitHub 저장소에 업데이트가 있다는 알림을 받게 돼요.

Vercel은 앱을 빌드하고 특정 미리보기 URL을 만들어 팀원들이나 클라이언트와 공유할 수 있게 해주죠. 이 미리보기 빌드를 사용하면 라이브 애플리케이션에 배포된 것과 똑같이 애플리케이션과 상호 작용할 수 있지만, 프로덕션 도메인으로 꼭 승격할 필요는 없어요. 팀에서 확인하고 검토한 후에 해당 Pull Request를 병합할 준비가 되면 앱의 프로덕션 URL로 승격되는 거죠. Vercel은 미리보기 빌드 아이디어를 관리하고 팀과 공유할 미리보기 URL을 제공하는 데 정말 유용한 것 같아요.

이제 앱이 활성화되었어요! 최신 Lobsters 뉴스와 함께 그래프 시각화를 볼 수 있을 뿐만 아니라, /API/GraphQL에서 GraphQL API에도 접근할 수 있어요. React 앱은 CDN에 배포되었고, GraphQL API는 Vercel에 의해 서버리스 기능으로 배포되었답니다. Next.js의 API 경로 기능을 활용하기만 하면 돼요.

애플리케이션 구축 방법

방금 구축한 애플리케이션의 아키텍처를 다시 한번 살펴볼까요? GitHub Actions를 사용해서 백그라운드에서 크론 작업을 설정해서 Lobsters JSON 엔드포인트에서 지속적으로 데이터를 가져왔어요. 그리고 Cypher를 사용해서 해당 데이터를 Neo4j Aura 인스턴스에 로드하고 GraphQL API를 구축했죠.

Neo4j GraphQL 라이브러리를 사용해서 애플리케이션 데이터를 프론트엔드 애플리케이션에 노출했어요. 프론트엔드 애플리케이션은 그래프 시각화를 생성하기 위해 반응형 그래프 컴포넌트를 사용하는 React 앱이고요. 관련된 모든 코드는 GitHub에서 확인할 수 있어요. . 더 자세한 내용이 궁금하신 분들을 위해 3부작 블로그 튜토리얼 시리즈도 준비했어요. 오늘 이야기한 다양한 단계를 좀 더 자세히 살펴볼 수 있도록 말이죠. 이 링크도 참고해주세요!

이 애플리케이션을 Neo4j 라이브 스트림에서 만들었는데, 여기서는 Neo4j의 많은 사람들과 커뮤니티의 다른 분들이 애플리케이션을 구축하고, 데이터로 작업하고, 그래프 데이터 과학을 수행하는 방법을 보여준답니다. 라이브 코딩을 좋아하신다면 라이브 스트림이 딱일 거예요. 질문도 하고, 관심 있는 부분에 대해 더 깊이 파고들 수도 있으니까요. 녹화된 영상은 Neo4j 유튜브 채널이나 Neo4j 트위치 채널에서 찾아볼 수 있어요.

리소스

흥미로운 자료 몇 가지를 더 소개해 드릴게요. 먼저 Neo4j AuraDB 무료 버전이 있어요. Neo4j Aura는 무료 등급을 제공해서 "무료"라고 부르는데요, 취미 프로젝트나 개발에 사용할 수 있도록 아주 쉽게 시작할 수 있는 Neo4j AuraDB 인스턴스를 만들 수 있답니다.
GitHub Actions, Vercel, 그리고 Neo4j AuraDB 모두 무료 버전을 제공한다는 점도 기억해두면 좋을 것 같아요. 신용카드 정보 없이도 이 앱을 구축하는 데 필요한 모든 서비스를 무료로 이용할 수 있다는 거죠. 물론, 실제 서비스를 배포하고 확장할 준비가 되면 각 서비스의 유료 플랜을 이용할 수도 있고요.

더 자세한 정보가 필요하신 분들을 위해 Neo4j GraphQL 라이브러리 랜딩 페이지를 방문해 보세요. 다양한 기능에 대한 정보와 문서, 예제 링크를 찾아볼 수 있을 거예요. Neo4j의 GraphAcademy에는 Neo4j GraphQL 라이브러리를 사용해서 GraphQL API를 구축하는 방법을 자세히 알려주는 교육 과정도 준비되어 있답니다.
Neo4j GraphQL 팀은 유럽에서 소프트웨어 엔지니어를 채용하고 있다고 해요. 채용 공고는 에서 확인하거나, GraphQL@neo4j.com으로 직접 문의하셔도 돼요. 궁금한 점이 있다면 언제든지 연락 주세요!
이 튜토리얼이 여러분께 도움이 되었기를 바라며, 유용한 자료들을 잘 활용하시길 응원할게요!

  • Bloom
  • Cypher
  • 풀스택 GraphQL
  • GraphQL
  • GraphQL API
  • Neo4j GraphQL 라이브러리
  • Vercel

에이치시스템즈LogTree는 Neo4j 기반 GraphRAG 플랫폼으로, 데이터를 자동으로 지식그래프화하고 자연어 질의로 즉시 답을 제공합니다.

👉 에이치시스템즈 홈페이지

반응형
반응형

무료 Graph Database 인스턴스를 만들어 보세요!Neo4j AuraDB

Retrieval-Augmented Generation(RAG)은 외부 지식으로 LLM을 향상시키는 강력한 기술로 떠올랐지만, 기존의 벡터 기반 RAG 접근 방식은 복잡하고 상호 연결된 정보를 처리할 때 한계를 드러내고 있어요. 단순한 Semantic Search는 엔터티 간의 미묘한 관계를 제대로 포착하지 못하고, 다중 홉 추론에 어려움을 겪으면서 여러 문서에 흩어진 중요한 맥락을 놓칠 수 있죠.

이러한 과제를 해결하기 위한 다양한 접근 방식이 있지만, 특히 유망한 해결책 중 하나는 데이터를 구조화해서 더 정교한 검색 및 추론 기능을 활용하는 거예요. 구조화되지 않은 문서를 구조화된 지식 표현으로 바꾸면 단순한 유사성 비교를 훨씬 뛰어넘는 복잡한 그래프 탐색, 관계 Query, 상황별 추론을 수행할 수 있게 되죠.

이럴 때 필요한 게 바로 LlamaCloud 같은 도구예요. LlamaCloud는 원시 문서를 구조화된 데이터로 변환하는 강력한 구문 분석 및 추출 기능을 제공하거든요. Neo4j는 Knowledge Graph 표현을 위한 든든한 기반이 되어주고요. 덕분에 존재하는 정보뿐만 아니라 모든 정보가 어떻게 연결되는지 이해할 수 있는 GraphRAG 아키텍처를 만들 수 있는 거죠.

법률 분야는 RAG에 구조화된 데이터 접근 방식을 적용했을 때 가장 강력한 효과를 볼 수 있는 사례 중 하나인데요. 정보 검색의 정확성과 정밀성이 현실 세계에 중요한 영향을 미치기 때문이에요. 법률 문서는 사건, 법령, 규정, 판례 간의 복잡한 참조 웹으로 연결되어 있어서 기존의 벡터 검색으로는 효과적으로 파악하기 어려워요. 법적 정보의 계층적인 특성과 개체, 조항, 법적 개념 간의 관계를 이해하는 것이 정말 중요하기 때문에 구조화된 Knowledge Graph는 검색 정확도를 높이는 데 특히 유용하답니다.

이 잠재력을 보여주기 위해 아래에 전체 파이프라인을 나타내는 법률 문서 처리의 포괄적인 예를 한번 살펴볼까요?

법률 문서에서 정보를 추출하고 이를 그래프로 표현하기 위한 처리 파이프라인

파이프라인 프로세스:

  • LlamaParse를 사용해서 PDF 문서를 구문 분석하고 읽을 수 있는 텍스트를 추출해요.
  • LLM을 사용해서 계약 유형을 분류하고 상황 인식 처리가 가능하게 만들어요.
  • LlamaExtract를 활용해서 분류에 따라 각 특정 계약 범주에 맞는 다양한 관련 속성 세트를 추출해요.
  • 구조화된 모든 정보를 Neo4j Knowledge Graph에 저장해서 법률 문서 내의 콘텐츠와 복잡한 관계를 모두 캡처하는 풍부하고 Query 가능한 표현을 만들어요.

코드는 LlamaIndex 요리책에서 확인할 수 있어요.

환경설정

이 코드를 실행하기 전에 LlamaCloud와 OpenAI에서 API 키를 설정해야 해요. Neo4j의 경우 가장 간단한 방법은 무료 Aura Database 인스턴스를 사용하는 거랍니다.

LlamaParse를 사용한 OCR

이 튜토리얼에서는 샘플 상업 계약을 분석할 건데요, 계약 이해 Atticus 데이터세트(CUAD)를 사용했어요.

이제 LlamaParse를 사용해서 계약 문서를 구문 분석하고 텍스트 콘텐츠를 추출하는 단계를 알아볼게요.

# Initialize parser with specified mode ```html
# Initialize parser with specified mode
parser = LlamaParse(
   api_key=llama_api_key,
   parse_mode="parse_page_without_llm"
)
pdf_path = "CybergyHoldingsInc_Affliate Agreement.pdf"
results = await parser.aparse(pdf_path)

코드는 LlamaParse 인스턴스를 생성하고 샘플 계약 PDF를 처리하는 내용이에요.

문서 분류

계약에서 관련 정보를 추출하기 전에 어떤 유형의 계약인지 알아야 해요. 계약 유형에 따라 조항 구조와 법적 정보가 다르기 때문에, 적절한 추출 스키마를 동적으로 선택하려면 계약 유형이 꼭 필요하죠.

openai_client = AsyncOpenAI(api_key=openai_api_key)

classification_prompt = """You are a legal document classification assistant.
Your task is to identify the most likely contract type based on the content of the first 10 pages of a contract.
Instructions:
Read the contract excerpt below.
Review the list of possible contract types.
Choose the single most appropriate contract type from the list.
Justify your classification briefly, based only on the information in the excerpt.

Contract Excerpt:
{contract_text}
Possible Contract Types:
{contract_type_list}

Output Format:
<Reason>brief_justification</Reason>
<ContractType>chosen_type_from_list</ContractType>
"""

async def classify_contract(contract_text: str, contract_types: list[str]) -> dict:
   prompt = classification_prompt.format(
       contract_text=file_content,
       contract_type_list=contract_types
   )
   history = [{"role": "user", "content": prompt}]
   response = await openai_client.responses.create(
       input=history,
       model="gpt-4o-mini",
       store=False,
   )
   return extract_reason_and_contract_type(response.output[0].content[0].text)

이 코드는 OpenAI 클라이언트를 설정하고, LLM에게 계약 분류 방법을 알려주는 프롬프트 템플릿과 실제 분류 프로세스를 처리하는 기능으로 구성된 분류 시스템을 만들어요.

이제 파싱된 계약 데이터를 사용해서 분류 프로세스를 실행해볼까요?

contract_types = ["Affiliate_Agreements", "Co_Branding", "Development"]
# Take only the first 10 pages for contract classification as input
file_content = " ".join([el.text for el in results.pages[:10]])

classification = await classify_contract(file_content, contract_types)

LlamaExtract를 사용한 추출

LlamaExtract는 AI 기반 스키마 기반 추출을 사용해서 비정형 문서를 정형 데이터로 변환해주는 클라우드 서비스예요.

스키마 정의

여기서는 두 가지 Pydantic 모델을 정의할 건데요. Location은 국가, 주, 주소에 대한 선택적 필드를 사용해서 구조화된 주소 정보를 캡처하고, Party는 필수 이름과 선택적 위치 세부 정보가 포함된 계약 당사자를 나타내요. 필드 설명은 각 필드에서 찾아야 할 정보를 LLM에 정확하게 알려줘서 추출 프로세스를 안내하는 데 도움이 된답니다.

class Location(BaseModel):
   """Location information with structured address components."""
  
   country: Optional[str] = Field(None, description="Country")
   state: Optional[str] = Field(None, description="State or province")
   address: Optional[str] = Field(None, description="Street address or city")


class Party(BaseModel):
   """Party information with name and location."""
  
   name: str = Field(description="Party name")
   location: Optional[Location] = Field(None, description="Party location details")

여러 계약 유형이 있기 때문에 각 유형에 대한 특정 추출 스키마를 정의하고, 분류 결과에 따라 적절한 스키마를 동적으로 선택하는 매핑 시스템을 만들어야 한다는 점을 기억하세요!

class BaseContract(BaseModel):
   """Base contract class with common fields."""
   parties: Optional[List[Party]] = Field(None, description="All contracting parties")
   agreement_date: Optional[str] = Field(None, description="Contract signing date. Use YYYY-MM-DD")
   effective_date: Optional[str] = Field(None, description="When contract becomes effective. Use YYYY-MM-DD")
   expiration_date: Optional[str] = Field(None, description="Contract expiration date. Use YYYY-MM-DD")
   governing_law: Optional[str] = Field(None, description="Governing jurisdiction")
   termination_for_convenience: Optional[bool] = Field(None, description="Can terminate without cause")
   anti_assignment: Optional[bool] = Field(None, description="Restricts assignment to third parties")
   cap_on_liability: Optional[str] = Field(None, description="Liability limit amount")

class AffiliateAgreement(BaseContract):
   """Affiliate Agreement extraction."""
   exclusivity: Optional[str] = Field(None, description="Exclusive territory or market rights")
   non_compete: Optional[str] = Field(None, description="Non-compete restrictions")
   revenue_profit_sharing: Optional[str] = Field(None, description="Commission or revenue split")
   minimum_commitment: Optional[str] = Field(None, description="Minimum sales targets")

class CoBrandingAgreement(BaseContract):
   """Co-Branding Agreement extraction."""
   exclusivity: Optional[str] = Field(None, description="Exclusive co-branding rights")
   ip_ownership_assignment: Optional[str] = Field(None, description="IP ownership allocation")
   license_grant: Optional[str] = Field(None, description="Brand/trademark licenses")
   revenue_profit_sharing: Optional[str] = Field(None, description="Revenue sharing terms")

mapping = {
   "Affiliate_Agreements": AffiliateAgreement,
   "Co_Branding": CoBrandingAgreement,
}

이 스키마 디자인에는 당사자 이름, 날짜, 부울 플래그 같은 구조화된 추출 필드뿐만 아니라 독점 조건, 수익 공유 계약, IP 소유권 세부 정보와 같은 복잡한 조항에 대한 요약과 비슷한 필드도 포함되어 있어요.

이제 구문 분석된 계약 텍스트와 정의된 스키마를 사용해서 구조화된 정보를 추출할 수 있어요. LlamaExtract는 전체 문서 콘텐츠를 분석하고 우리가 정의한 특정 필드를 가져오는 거죠.

extractor = LlamaExtract(api_key=llama_api_key)

agent = extractor.create_agent(
   name=f"extraction_workflow_import_{uuid.uuid4()}",
   data_schema=mapping[classification['contract_type']],
   config=ExtractConfig(
       extraction_mode=ExtractMode.BALANCED,
   ),
)

result = await agent.aextract(
   files=SourceText(
       text_content=" ".join([el.text for el in results.pages]),
       filename=pdf_path
   ),
)

Neo4j Knowledge Graph

마지막 단계는 추출된 구조화된 정보를 가져와 계약 엔터티 간의 관계를 나타내는 Knowledge Graph를 구축하는 거예요. Neo4j에서 계약 데이터를 Nodes와 Relationships로 구성하는 방법을 지정하는 그래프 모델을 정의해야 하죠.

법적 그래프 모델

그래프 모델은 세 가지 주요 Node 유형으로 구성돼요.

  • Node는 날짜, 조건, 법적 조항을 포함한 핵심 계약 정보를 저장해요.
  • Node는 이름으로 계약 법인을 나타내죠.
  • Node는 주소 구성 요소로 지리 정보를 캡처하고요.

이제 정의된 그래프 모델에 따라 추출된 계약 데이터를 Neo4j로 가져올 거예요.

import_query = """WITH $contract AS contract MERGE (c:Contract {path: $path}) SET c += apoc.map.clean(contract, ["parties", "agreement_date", "effective_date", "expiration_date"], []) // Cast to date SET c.agreement_date = date(contract.agreement_date), c.effective_date = date(contract.effective_date), c.expiration_date = date(contract.expiration_date) // Create parties with their locations WITH c, contract UNWIND coalesce(contract.parties, []) AS party MERGE (p:Party {name: party.name}) MERGE (c)-[:HAS_PARTY]->(p) // Create location nodes and link to parties WITH p, party WHERE party.location IS NOT NULL MERGE (p)-[:HAS_LOCATION]->(l:Location) SET l += party.location """ response = await neo4j_driver.execute_query(import_query, contract=result.data, path=pdf_path) response.summary.counters

데이터를 가져온 후 Neo4j 그래프는 다음 이미지와 같을 거예요.

추출된 정보를 그래프로 표현

모든 것을 하나의 워크플로로 통합

마지막으로 이 모든 로직을 하나의 실행 가능한 에이전트 워크플로로 결합할 수 있어요. 단일 PDF를 승인하고 매번 Neo4j 그래프에 새 항목을 추가해서 워크플로가 실행되도록 만들어보죠.

추출 작업흐름

이 워크플로를 생성하는 데 필요한 모든 코드는 해당 노트북에서 찾을 수 있어요. 코드 블록이 너무 길어서 이 게시물이 복잡해지지 않도록 코드는 그대로 두었어요.

워크플로는 단일 명령으로 모든 문서를 처리할 수 있도록 단순하게 설계되었답니다.

knowledge_graph_builder = KnowledgeGraphBuilder(
    parser=parser,
    affiliate_extract_agent=affiliage_extraction_agent,
    branding_extract_agent=cobranding_extraction_agent,
    classification_prompt=classification_prompt,
    timeout=None,
    verbose=True,
)

response = await knowledge_graph_builder.run(
    pdf_path="CybergyHoldingsInc_Affliate Agreement.pdf"
)

요약

기존의 Retrieval-Augmented Generation (RAG) 시스템은 문서 청크를 통한 Semantic Search에 의존하는데, 이 때문에 엔터티 간의 중요한 컨텍스트와 관계가 손실되는 경우가 많아요. 계약 데이터를 Knowledge Graph로 구성함으로써 계약, 당사자 및 위치가 어떻게 상호 연결되는지 이해하는 더욱 지능적인 검색 시스템을 만들 수 있죠. 구조화되지 않은 텍스트 조각을 검색하는 대신, Large Language Model (LLM)은 이제 정확한 엔터티 관계를 활용해서 "Cybergy Holdings와 계약한 모든 당사자의 위치는 어디인가?" 또는 "뉴욕에 있는 회사와 관련된 모든 제휴 계약을 보여줘."와 같은 복잡한 Query에 답할 수 있게 돼요.

코드는 LlamaIndex 요리책에서 확인할 수 있어요.

자원

  • 개발자 가이드: Knowledge Graph 구축 방법
  • Retrieval-Augmented Generation (RAG)이란 무엇인가?
  • Knowledge Graph란 무엇인가?
  • LlamaIndex 워크플로를 사용하여 Knowledge Graph 에이전트 구축

  • GraphRAG
  • LlamaIndex
  • LlamaParse

에이치시스템즈LogTree는 Neo4j 기반 GraphRAG 플랫폼으로, 데이터를 자동으로 지식그래프화하고 자연어 질의로 즉시 답을 제공합니다.

👉 에이치시스템즈 홈페이지

반응형
반응형
  • Graph Data Science
  • Machine Learning

그래프 기술은 데이터의 그래프 표현을 저장, 관리 및 `Query`하는 것을 의미해요. 여기서 `Node`는 정점이 되고, `Relationship`은 가장자리로 변환되죠. 세분화된 관계 분석을 통해 그래프 분석을 활용하면 알고리즘과 `Query`를 통해 이상 징후를 찾아낼 수 있어요.

주요 장점은 Graph Database가 `Relationship`에 의해 자연스럽게 `Index`된다는 점이에요. 그래프 기술을 사용하면 사용자는 사전에 많은 모델링을 하지 않고도 데이터를 포함할 수 있죠. 이 모든 것이 그래프 기술을 사기꾼의 속임수와 속도를 따라잡는 데 매우 유용하게 만들어줘요.

그래프를 통한 이상 탐지

사기 탐지에서 일반적으로 분석은 이산형 데이터 분석과 연결된 데이터 분석이라는 두 가지 방식으로 분류돼요.

개별 데이터 분석에서는 개별 활동, 사용자, 계정을 평가하지만, 연결된 데이터 분석에서는 통합된 행동과 관계가 중요해요. 사기 감지에도 도움이 되죠. 그래프 모델링 및 분석은 특히 연결된 데이터 분석에 유용한 것으로 입증되었어요.

연결된 데이터와 그래프 기술 덕분에 여러분은 다음과 같은 것들을 할 수 있어요:

    • 조직에 대한 정보를 병합하고 연관시켜요.
    • 연결된 그래프처럼 결과를 보여줘요.
    • 소셜 네트워크와 링크를 적용해서 인사이트를 발견할 수 있어요.

이제 기업이 연결된 데이터 분석을 사용해서 실제 비즈니스 문제를 해결하는 방법을 한번 살펴볼까요?

소셜 미디어 네트워크에서 봇 계정 찾기

소셜 미디어는 오늘날 세상에 점점 더 많은 영향을 미치고 있고, 마케팅 담당자들은 이 플랫폼의 최신 트렌드를 파악하느라 정신이 없죠.

예를 들어, 특정 제품을 판매하는 경우 해당 제품 카테고리에서 무엇이 인기 있는지 알고 싶을 거예요. 마케팅 담당자는 자사 제품이 경쟁사와 비교해서 어떤지, 자사 제품에 대한 대중의 인식, 사람들이 소셜 미디어에서 해당 제품에 대해 말하는 내용 등을 알고 싶어하죠.

이런 종류의 정보는 정말 중요해요. 하지만 데이터가 부정확하거나 회사의 경쟁업체가 봇에 돈을 쏟아부어서 정보가 왜곡되면 소용이 없겠죠. 가짜 봇은 인기에 관해서 왜곡된 정보를 제공하거든요. 일부 대상 계정의 인기를 높이기 위해 이런 봇들은 해당 계정을 리트윗해요.

이러한 계정의 진위 여부를 정확하게 알아내기 위해, 특정 사용자는 리트윗 횟수를 edge 가중치로 사용해서 계정의 그래픽 표현을 묘사해서, 이 계정이 이웃 계정을 리트윗한 횟수를 정확하게 결정할 수 있어요.

여기서 마케터들은 인기 있는 계정이 부자연스럽게 인기 있는 계정과 다른 특징을 보인다는 것을 알 수 있을 거예요.

이러한 계정을 분석하는 동안 부자연스러운 편차가 있는 일부 계정을 볼 수 있을 텐데요. 다음과 같은 방법으로 그래프와 관계를 사용해서 더 많은 봇을 찾을 수 있어요.

    • 리트윗 수가 많은 계정 식별
    • 다른 계정이 리트윗하는 방식 결정
    • 이 봇에서만 리트윗되는 계정 찾기

순환 결제

순환 자금 이체는 대부분의 금융 범죄에서 추적할 수 있는 일반적인 패턴 중 하나에요. 이 패턴에서는 자신에게 돈을 보내고 이를 '일반 계좌' 간의 합법적인 자금 이체로 위장하죠. 하지만 이 계정은 사실 가짜 계정이에요.

일반적으로 그들은 도난당한 신원(이메일 주소 및 유사한 정보)에서 생성된 특정 세부 정보를 공유해요. 이러한 종류의 정보는 그래프 분석을 해당 정보를 찾는 완벽한 도구로 만들어주죠.

이러한 종류의 Use Case에서는 주소, 비밀번호, 이메일 주소 등과 같은 특정 세부 정보를 공유하는 엔터티 간의 전환 그래프를 묘사해서 그래픽 표현을 만들 수 있어요. 사용자가 이 정보를 사용해서 그래프를 만든 후에 남은 것은 기본적인 `Query`를 작성하고 이를 실행해서 비슷한 종류의 세부 정보를 가진 계정을 가진 사람들을 검색하는 것이에요.

금융 사기 및 자금 세탁

자금 세탁에서는 불법 자금을 실제 합법적인 돈과 섞어서 자산으로 전환해요. 이러한 유형의 탈세 계획은 브로커와 허위 재판매업체에 의존하며, 이들은 세금 환급을 신청하여 지불을 회피할 수 있도록 하죠.

`Graph Database`를 사용하면 사용자가 분류, `Machine Learning`, 패턴 인식 및 통계 분석을 이러한 모델에 적용해서 대용량 데이터 세트에 대해 대규모로 보다 효과적인 검사를 수행할 수 있는 관계 모델이 제공돼요.

사례 상관관계를 살펴보자

규정에 따라 의심스러운 거래가 이루어지면 조사관의 정밀 조사를 받게 돼요. 이 경우의 주요 목표는 사전에 알려진 연결에 따라 모든 의심스러운 활동을 그룹화하는 것이 아니라 모든 개별 활동을 개별적으로 조사하는 것이죠.

이러한 상관관계를 확인하려면 그래프 기반 기술을 사용해야 해요. 사용자는 일반 그래프 시스템의 도움뿐만 아니라 `Query`(경로 찾기)와 일치하는 필터 및 패턴이 있는 연결된 구성 요소 그래프 알고리즘을 사용해서 이 흐름을 구현할 수 있어요.

회사가 이 방법을 사용하면 맞춤형 사례 상관 엔진을 구축할 필요가 없어요. 대신 유연성이 향상된 그래프 기술을 사용할 수 있죠.

결론

의심할 여지 없이 오늘날 사기꾼은 점점 더 발전하고 있어요. 하지만, 기술도 이 경쟁에서 뒤처지지 않아요. `Graph Database`와 같은 기술은 데이터에서 진실을 찾는 좋은 방법이죠. 전 세계적으로 이 기술은 점차 관련성을 얻고 있으며 모든 비즈니스에 중요한 도구에요.

이 백서를 통해 기업 사기 탐지 활동을 위해 `Graph Database`의 강력한 기능을 활용하는 방법을 알아보세요. 사기 탐지: `Graph Database`와의 연결 검색.
  • 데이터 모델링
  • Machine Learning

에이치시스템즈LogTree는 Neo4j 기반 GraphRAG 플랫폼으로, 데이터를 자동으로 지식그래프화하고 자연어 질의로 즉시 답을 제공합니다.

👉 에이치시스템즈 홈페이지

반응형
반응형

Neo4j가 Strong Performer로 인정받았다는 기쁜 소식을 전해드려요! Forrester Wave: Vector Databases, Q3 2024 보고서에서 말이죠.

"우리는 데이터베이스 업계에서 이렇게 뜨거운 열기를 느껴본 적이 없어요!" Forrester 보고서에 따르면, Neo4j처럼 통합된 벡터 기능을 갖춘 데이터베이스는 "GenAI 애플리케이션에 안정적이고 풍부한 데이터를 제공"하는 데 중요한 역할을 한다고 해요. 보고서는 GenAI 모델에서 더 통찰력 있고 상황에 맞는 응답을 가능하게 해준다고 밝혔어요.

GenAI 애플리케이션 성능을 끌어올리기 위한 Neo4j의 특별한 접근 방식은 바로 GraphRAG인데요. Knowledge Graph와 RAG (Retrieval-Augmented Generation)를 결합한 방식이죠. GraphRAG를 사용하면 업계의 판도를 뒤흔들 만한 엔터프라이즈급 GenAI 애플리케이션을 빠르게 개발할 수 있어요.

벡터 전용 RAG의 한계를 넘고 GenAI 도입을 가속화해요

GraphRAG를 알아보기 전에 먼저 벡터 전용 RAG에 대해 알아야겠죠? Vector Database는 많은 GenAI 애플리케이션의 핵심 역할을 하며, RAG 과정에서 중요한 외부 데이터를 제공해요. 특히 텍스트, 오디오, 비디오와 같은 복잡한 데이터의 수치 표현인 Vector Embedding을 저장하고 처리하죠. 이러한 임베딩은 Semantic Search를 가능하게 해서 GenAI 앱이 비교적 안정적인 응답을 제공하도록 도와줘요.

하지만 Semantic Search만으로는 엔터프라이즈급 GenAI 앱에 필요한 정확도와 상황별 관련성을 제공하기 어려워요. GraphRAG는 Neo4j Graph Database와 Knowledge Graph에 벡터 검색을 도입해서 이 문제를 해결해요. 이 조합은 다음과 같은 장점을 제공하죠:

  1. 향상된 상황 및 정확성: GraphRAG는 단순히 Vector Embedding을 비교하는 게 아니에요. 데이터 세트 내의 복잡한 Relationships를 파악해서 더 풍부한 Context를 제공하므로, 더 정확하고 관련성 높은 결과를 얻을 수 있죠.
  2. 복잡한 데이터 Relationships에 대한 이해: Graph Database는 복잡한 데이터 Relationships를 모델링하고 분석하는 데 탁월해서 GenAI 앱이 복잡한 데이터 환경을 탐색하고 이해하는 데 도움을 줘요.
  3. 향상된 패턴 인식: 그래프와 벡터 기술을 결합하면 GenAI 시스템이 데이터의 패턴을 인식하는 능력이 크게 향상돼서 더 깊은 통찰력과 더 정확한 예측이 가능해져요.

GenAI 데이터 및 성능 개선을 통해 잠재적 사용 사례를 확장해요

GenAI가 계속 발전하면서 이러한 시스템에 제공되는 데이터의 품질과 Context가 점점 더 중요해지고 있는데, GraphRAG는 이러한 문제를 정면으로 해결해줘요. 벡터 검색과 Graph Database의 정확한 도메인별 데이터와 상황별 이해를 결합함으로써 Neo4j는 차세대 AI 인프라의 핵심 플레이어로 자리매김하고 있어요.

GraphRAG로 강화된 GenAI는 정말 다양한 분야에 적용될 수 있어요. 더 스마트한 추천 엔진과 챗봇부터 의료 및 금융 분야의 더 나은 사기 탐지와 더 정확한 지식 검색까지! GraphRAG는 속도와 거버넌스의 균형을 맞춰주면서 다양한 새로운 사용 사례를 열어줄 수 있죠. 보고서에서도 "Neo4j는 RAG 애플리케이션, 고급 AI 애플리케이션 및 향상된 추천 엔진을 지원하기 위해 Knowledge Graph와 벡터 검색 기능을 결합하려는 고객에게 적합합니다"라고 강조하고 있어요.

GenAI의 미래를 위한 제품 비전 및 로드맵을 구축해요

Forrester Wave 평가는 Neo4j의 제품 비전과 로드맵의 가치를 인정해줬어요. 저희는 현재 Knowledge Graph를 통해 GraphRAG 기능을 강화하고, 자동화를 개선하고, 벡터 스토리지를 최적화하고, 성능과 확장성을 강화하고 있답니다.

GenAI 분야가 엄청난 속도로 계속 발전함에 따라 GraphRAG와 같은 혁신은 데이터 인프라가 AI 기능을 어떻게 발전시킬 수 있는지를 보여주는 좋은 예시 같아요. Neo4j는 정형, 반정형 및 비정형 데이터 전반에 걸쳐 빠른 Relationship 분석을 지원함으로써 더욱 지능적이고 상황을 인식하는 AI 시스템을 위한 길을 열고 있어요.

Neo4j 및 GenAI에 대해 자세히 알아보기
  • GraphRAG

에이치시스템즈LogTree는 Neo4j 기반 GraphRAG 플랫폼으로, 데이터를 자동으로 지식그래프화하고 자연어 질의로 즉시 답을 제공합니다.

👉 에이치시스템즈 홈페이지

반응형
반응형

많은 조직에서 챗봇이나 가상 비서처럼 질문에 답하거나 창의적인 텍스트를 생성하는 비즈니스 사용 사례를 위해 LLM을 고려하고 있어요. 하지만 보통 지식 단절, 환각, 사용자 맞춤화 부족 같은 제한 사항에 직면하게 되죠.

이런 과제를 해결하는 방법으로 Fine-tuningRetrieval-Augmented Generation(RAG) 두 가지 접근 방식이 있어요. LLM Fine-tuning은 LLM 성능을 최적화하려고 질문-답변 쌍을 제공하는 지도 학습 단계를 포함해요. 반대로, Retrieval-Augmented Generation(RAG)은 LLM이 외부 정보에 접근하기 위한 자연어 인터페이스 역할을 하는 방식이에요. 다시 말해, 답변을 만들 때 내부 지식에만 의존하지 않는다는 뜻이죠.

이번 글에서는 LLM의 한계를 억제하기 위해 Fine-tuning과 RAG를 사용할 때 장단점을 살펴볼게요. 그리고 이미 Knowledge Graph를 사용해서 데이터를 정리하고 관리하는 조직에 각 접근 방식이 어떤 의미를 가지는지도 함께 고려해 볼 거예요.

LLM의 한계

Midjourney's idea of a knowledge graph chatbot.
Knowledge Graph 챗봇에 대한 Midjourney의 아이디어.

2022년 11월 ChatGPT가 출시된 이후로, 많은 사람들이 창의적인 콘텐츠를 만들거나 텍스트 요약 및 번역 같은 일반적인 Natural Language Processing 작업을 해결하기 위해 LLM을 애플리케이션에 통합하는 걸 고려하기 시작했어요.

하지만 비즈니스 사용 사례에 LLM을 바로 적용하면 심각한 문제가 생길 수 있어요. 가장 중요한 문제들을 한번 살펴볼까요?

지식 차단 (Knowledge Cutoff)

지식 차단 (Knowledge Cutoff)은 LLM이 학습 후에 발생한 사건은 알지 못한다는 뜻이에요. 예를 들어 2024년에 일어난 일에 대해 ChatGPT에 물어보면 다음과 같은 답변을 받을 거예요.

ChatGPT의 지식 마감일입니다. 작성자별 이미지.

훈련 데이터 세트에 없는 이벤트에 대해 LLM에 문의하는 경우에도 똑같은 문제가 발생해요. 지식 마감일은 공개적으로 이용 가능한 모든 정보와 관련 있지만, LLM은 지식 마감일 이전에도 이용 가능했을 개인 정보나 기밀 정보에 대한 지식을 갖고 있지 않죠.

예를 들어, 많은 회사에는 공개적으로 공유하지 않는 기밀 정보가 있지만 해당 정보에 대한 질문에 답변할 수 있는 맞춤형 LLM에 관심이 있을 수 있어요. 반면, LLM이 알고 있는 공개적으로 이용 가능한 정보는 이미 오래된 정보일 수도 있고요.

환각

LLM이 만들어내는 모든 것을 맹목적으로 믿을 수는 없어요.

LLM은 실제처럼 들리는 출력을 생성하도록 훈련되었지만 항상 정확하지는 않을 수 있거든요. 어떤 유효하지 않은 정보는 다른 정보보다 발견하기가 더 어려운데, 특히 누락된 데이터의 경우 LLM은 교육에서 기본 사실이 부족하다는 점을 인정하는 대신 설득력 있게 들리는 (하지만 잘못된) 답변을 만들어낼 가능성이 높아요.

예를 들어, 연구나 법원 인용문을 확인하는 것이 더 쉬울 수 있겠죠. 2023년에 한 변호사가 곤경에 처했는데, ChatGPT가 생성한 법원 인용문을 맹목적으로 믿었기 때문이에요. 그 이후로 사람들은 ChatGPT 및 Gemini와 같은 공개 LLM이 생성하는 정보에 대해 더욱 경계하게 되었지만 여전히 위험하답니다.

LLM은 또한 Wikidata ID를 포함해서 모든 종류의 식별 번호에 대해 주장적이면서도 잘못된 정보를 계속해서 생성하고 있어요.

ChatGPT의 환각. 작성자별 이미지.

위 스크린샷에서 ChatGPT는 단호한 응답을 제공했지만 정확하지 않아요. ChatGPT가 러시아 가수에게 포인트를 준 위키데이터 ID랍니다.

기타 문제

LLM에는 다른 문제들도 있어요.

  • Bias – LLM은 훈련 데이터로부터 편향을 물려받아 차별적이거나 불공평한 결과를 초래할 수 있어요.
  • – 악의적인 사용자는 LLM을 조작해서 유해하거나 오해의 소지가 있는 콘텐츠를 생성하는 프롬프트를 만들 수 있죠.
  • – LLM은 상식적인 추론이나 상황의 맥락에 대한 이해가 필요한 작업으로 어려움을 겪는 경우가 많아요.
  • – LLM 출력 이면의 추론이나 소스를 이해하기 어려울 수 있으며, 이로 인해서 출력에 대한 신뢰도가 낮아지고 디버깅 노력이 복잡해져요.

간결함을 위해 이에 대해 자세히 다루지는 않을게요. 대신, 이러한 과제를 극복하기 위한 첫 번째 옵션인 감독된 Fine-tuning을 살펴볼게요.

LLM의 감독된 Fine-tuning

Fine-tuning은 LLM을 위한 목표 교육 접근 방식이에요. 이는 LLM의 성과를 최적화하기 위해 추가 질문-답변 쌍을 제공하는 지도 교육 단계를 의미하죠. LLM은 이러한 데이터 샘플을 반복적으로 처리함으로써 이해도를 높이고 특정 영역에서 질문에 답하거나 텍스트를 생성하는 능력을 향상시키는 거예요.

Fine-tuning을 통해 LLM의 기존 기능을 사용하면서 고유한 요구 사항에 맞게 조정할 수 있어요. Fine-tuning과 도메인별 사전 학습을 결합하면 도메인별 LLM이 금융과 같은 분야의 전문적인 작업을 더욱 정확하게 수행할 수 있답니다.

메모: LLM Fine-tuning의 주요 사용 사례는 내부 지식을 업데이트하고 확장하는 것, 그리고 특정 작업(예: 텍스트 요약 또는 자연어를 데이터베이스 Query로 번역)에 대한 모델을 Fine-tuning하는 것이에요. 개인화된 예술 콘텐츠 생성, 콘텐츠 필터링, 추천을 위한 사용자 선호도 및 행동 패턴 분석 등 창의적인 목적을 위한 모델을 준비할 수도 있죠. 이 글에서는 LLM의 내부 지식을 업데이트하고 확장하기 위해 Fine-tuning 기술을 사용하는 첫 번째 사용 사례에 대해 알아볼 거예요.

Fine-tuning은 어디에 적합할까요?

LLM 교육 방법을 자세히 설명하는 건 이 블로그 게시물의 범위를 벗어나요. 궁금하시다면 Andrej Karpathy의 비디오를 시청하고 LLM 교육의 다양한 단계를 알아보세요.

LLM을 만드는 첫 번째 단계는 기본 LLM을 개발하기 위한 모델 사전 훈련을 포함해요. 기본 LLM은 일반적으로 수십억, 심지어 수조 개의 토큰에 달하는 거대한 텍스트 모음을 사용하여 사전 훈련되죠. 교육 비용은 수십만 달러, 심지어 수백만 달러에 달할 수 있는데, 이는 대부분의 조직이 피하고 싶어하는 비용이에요.

최근에 Microsoft의 논문(2024년 6월 게시)에서는 Gradient checkpoint 및 Low-Rank Adaptation과 같은 기술이 Fine-tuning 프로세스 중에 메모리 병목 현상을 줄일 수 있음을 보여주었어요. 이렇게 하면 Fine-tuning 시간과 비용 효율성이 높아지지만, 지금까지 비용 절감이 입증된 실제 사례는 없어요.

Supervised fine-tuning flow. Image by Tomaž Bratanič. Icons from [Flaticon](https://www.flaticon.com/).
Supervised Fine-tuning 흐름. 이미지: Tomaž Bratanič. 아이콘: Flaticon.

따라서 가장 일반적인 첫 번째 단계는 사용 사례에 맞게 사전 훈련된 기본 LLM을 선택하는 거예요. 이 선택을 할 때 다음과 같은 몇 가지 사항을 고려해야 해요.

  • The 매개변수의 수 LLM에서는 데이터의 복잡한 패턴을 학습하고 표현하는 능력을 결정해요. 모델이 클수록 학습 및 추론을 위해 더 많은 계산 능력과 메모리가 필요하죠. 또한 훈련 시간이 더 길어질 수도 있어요. 이러한 모델을 Fine-tuning하려면 리소스 집약적일 수 있으므로 GPU 및 TPU와 같은 강력한 하드웨어가 필요해요.
  • The 기본 LLM의 Fine-tuning 모델을 사용하는 방법을 나타내요. 일부 라이센스에는 상업적 사용, 배포 또는 수정이 제한될 수 있어요. 오픈 소스 모델은 Fine-tuning 및 사용자 정의에 더 많은 유연성을 제공하는 반면, 독점 모델은 더 나은 성능이나 특정 기능을 제공하지만 더 제한적인 라이센스를 제공할 수 있죠. 법적 문제를 피하려면 라이센스 조건을 준수해야 해요.
  • Bias 기본 모델의 문제는 적절하게 해결되지 않으면 Fine-tuning 중에 증폭될 수 있으며, 이는 Fine-tuning 모델에 의해 불공평하거나 차별적인 동작으로 이어질 수 있어요. Bias를 식별하고 완화하려면 데이터 밸런싱 기술, Bias 감지 도구, 공정성 인식 교육 알고리즘을 사용하는 등 추가 데이터 전처리 및 평가 단계가 필요해요. 초기 교육 데이터 세트에서 알려진 Bias가 가장 적은 기본 LLM을 선택하려고 노력해야겠죠.
  • 모델이 제대로 제어되지 않으면 독성이 있거나 유해한 콘텐츠가 생성될 수 있다는 사실을 의미해요. 독성을 최소화하고 긍정적인 사용자 경험을 유지하며 피해를 방지하려면 훈련 데이터를 필터링하고 안전 레이어와 지속적인 모니터링을 구현하려고 노력해야 해요. 가능하다면 알려진 독성이 가장 적고 Fine-tuning 중에 독성을 감지하고 완화하는 메커니즘을 포함하는 기본 LLM도 선택하세요.

기본 LLM을 선택한 후 다음 단계는 이를 Fine-tuning하는 거예요.

훈련 데이터 세트 준비

Fine-tuning의 첫 번째 단계는 기본 LLM을 Fine-tuning하는 데 사용할 데이터 세트를 준비하는 것이에요.

사전 훈련에 비해 Fine-tuning은 다음과 같은 기술로 인해 계산 비용 측면에서 상대적으로 저렴해요. LoRA and QLoRA. 하지만 훈련 데이터 세트를 구성하는 것은 훈련 자체보다 더 복잡하고 비용이 많이 들 수 있어요.

전담 애노테이터 팀을 확보할 여유가 없는 일부 조직은 실제로 메타화하여 LLM을 사용하여 LLM을 Fine-tuning하기 위한 교육 데이터 세트를 구성해요. 예를 들어 Stanford의 Alpaca 훈련 데이터 세트는 OpenAI의 LLM을 사용하여 생성되었어요. 52,000개의 교육 지침을 제작하는 데 드는 비용은 약 USD $500로 비교적 저렴하죠. Vicuna 모델은 ShareGPT에 게시된 ChatGPT 대화 사용자 사용으로 Fine-tuning되었어요. 이 모델을 훈련하는 데 드는 비용은 USD $140이에요.

H2O는 또한 다음과 같은 프로젝트를 개발했어요. WizardLM LLM을 Fine-tuning하기 위해 문서를 질문-답변 쌍으로 변환하는 프로젝트죠.

하지만 내부 지식을 확장하기 위해 LLM을 Fine-tuning하는 데 도움이 될 수 있는 좋은 질문-답변 쌍을 준비하기 위해 Knowledge Graph를 사용하는 것과 관련된 구현을 아직 본 적이 없어요. 이는 회사별 데이터를 저장하기 위해 Knowledge Graph에 의존하는 조직에 우려되는 사항이죠. 데이터 저장소에서 훈련 데이터 세트를 생성하는 것은 문제가 될 가능성이 높아요. 이를 수행할 수 있는 방법은 커뮤니티 보고서 요약 단계인 Microsoft의 그래프 RAG 제안이지만, 그 아이디어는 아직 테스트되지 않았어요.

Fine-tuning의 한계

LLM의 주요 문제는 아직 밝혀지지 않은 부분이 많다는 점이에요. 예를 들어, 똑같은 질문에 대해 두 가지 다른 답변을 내놓을 수도 있고, LLM이 내부 지식 저장소에서 어떻게든 이 둘을 결합할 수도 있을까요?

만약 여러분이 Knowledge Graph의 데이터를 사용해서 LLM을 학습시키려고 한다면, Knowledge Graph의 정보 중 일부는 관계를 살펴볼 때만 의미가 있다는 걸 고려해야 해요. 이런 관계를 학습 데이터 세트에 통합하려면 관련 `쿼리`를 미리 정의해야 할까요? 아니면 더 일반적인 방법이 있을까요? 아니면 주제-술어-객체 표현을 나타내는 `노드`-`관계`-`노드` 패턴을 사용해서 관련 쌍을 생성할 수도 있을까요?

이런 고민은 잠시 접어두고, Knowledge Graph에 저장된 정보를 기반으로 질문-답변 쌍으로 이루어진 훈련 데이터 세트를 어떻게든 만들었다고 가정해 볼게요. 결과적으로 LLM은 업데이트된 지식을 갖게 되겠죠. 하지만 모델을 Fine-tuning해도 지식 차단 문제는 해결되지 않아요. 그저 지식 차단을 나중으로 미루는 것뿐이죠.

게다가 Fine-tuning 방식으로 환각 현상을 줄일 수는 있지만, 완전히 없앨 수는 없어요. LLM은 답변을 제공할 때 출처를 인용할 수 없거든요. 그래서 답변이 사전 훈련 데이터에서 나온 건지, Fine-tuning 데이터 세트에서 나온 건지, 아니면 LLM이 그냥 만들어낸 건지 알 수가 없어요. LLM을 사용해서 Fine-tuning 데이터 세트를 생성하는 경우, 가짜 출처가 포함될 가능성도 항상 존재하죠.

또한 Fine-tuning된 모델은 질문하는 사람에 따라 자동으로 다른 응답을 제공할 수 없어요. 마찬가지로, 접근 제한이라는 개념도 없죠. 즉, LLM과 상호 작용하는 사람은 누구나 LLM의 모든 정보에 접근할 수 있다는 뜻이에요.

Fine-tuning을 사용해야 하는 경우

Fine-tuning의 한계를 염두에 두고, 천천히 변경되거나 업데이트되는 데이터에 대해서만 Fine-tuning 기술을 통해 LLM의 내부 지식을 업데이트하는 걸 추천해요.

예를 들어, 특정 시간 종속적인 정보가 필요하지 않다면 Fine-tuning된 모델을 사용해서 역사적인 관광 명소에 대한 정보를 제공할 수 있어요. 하지만 응답에 특별한 시간 종속적인(실시간) 정보나 개인화된 프로모션을 포함하려는 순간 문제가 발생할 수 있죠.

마찬가지로, Fine-tuning된 모델은 회사가 지난주에 얼마나 많은 신규 고객을 확보했는지 물어볼 수 있는 분석 워크플로우에는 적합하지 않아요.

Fine-tuning은 앱이 고도로 전문화된 작업이나 도메인에서 뛰어난 성능을 요구할 때 가장 유용해요. 특정 데이터 세트에 대해 모델을 훈련함으로써 특정 관심 영역의 뉘앙스와 세부 사항에 더 잘 맞도록 응답을 커스터마이징할 수 있죠. 이 프로세스는 모델의 본질적인 이해 및 생성 기능을 향상시켜 전문적인 `쿼리`를 더 효과적으로 처리하고, 더 정확하고 상황에 맞는 결과를 만들어낼 수 있게 해줘요.

Retrieval-Augmented Generation

검색 증강 LLM 애플리케이션의 기본적인 아이디어는 답변을 생성하기 위해 내부 LLM 지식에만 의존하지 않는다는 거예요.

RAG는 LLM을 사용해서 자연어로 데이터베이스 `쿼리`를 구성하고, 외부에서 제공된 정보를 기반으로 답변을 구성하거나, 검색용 플러그인이나 에이전트를 사용하는 등의 작업을 해결해요. LLM의 내부 지식을 사용하는 대신, RAG를 사용하면 LLM을 회사 정보나 개인 정보에 대한 자연어 인터페이스로 활용할 수 있죠.

Retrieval-Augmented Generation의 작동 방식

Retrieval-augmented generation. Image by Tomaž Bratanič. Icons from [Flaticon](https://www.flaticon.com/).
Retrieval-Augmented Generation. 이미지: Tomaž Bratanič. 아이콘플랫아이콘.

RAG는 LLM에 적절하게 표현된 질문과 데이터 저장소의 관련 문서 세트를 제공하는 도구예요. 그런 다음 데이터 소스의 정보를 기반으로 답변을 생성하죠. 이는 답변을 생성하기 위해 LLM의 내부 지식에 의존하지 않는다는 걸 의미해요. 대신 LLM은 여러분이 전달한 문서에서 관련 정보를 추출하고 요약하는 데에만 사용돼요.

에이전트 및 도구 호출 지원의 도입은 RAG 성장의 촉매제 역할을 했어요.

에이전트는 LLM을 데이터베이스 및 API와 같은 외부 지식 소스에 연결하는 중개자 역할을 해요. 결과적으로 LLM은 방대한 실시간 데이터 풀을 활용해서 지식 부족을 극복하고 최신 정보에 액세스할 수 있죠. 상담원은 사실 확인 서비스와 확인 도구를 LLM 워크플로에 통합할 수 있으며, 이를 통해 사실 오류의 위험을 완화하여 LLM 결과의 신뢰성을 높일 수 있어요.

기본 도구 호출 기능은 이를 한 단계 더 발전시켜요. LLM은 복잡한 계산이나 데이터 분석과 같은 특정 작업을 위해 외부 도구를 직접 호출할 수 있으므로 더 넓은 범위의 문제를 처리하고 전반적인 기능을 확장할 수 있죠. 예를 들어, ChatGPT 도구에서 웹 응답을 받고 작성 또는 분석에 도움을 주기 위해 파일을 업로드하는 것은 LLM 애플리케이션에 대한 RAG 접근 방식으로 생각할 수 있어요. ChatGPT 인터페이스는 인터넷에 액세스할 수 있으므로 LLM은 최신 정보에 액세스하고 이를 사용해서 최종 답변을 구성할 수 있답니다.

브라우징 플러그인이 포함된 ChatGPT. 작성자별 이미지.

이 예시에서 ChatGPT는 2024년 NBA MVP를 수상한 사람이 누구인지 대답할 수 있었어요. 하지만 ChatGPT의 지식 마감 날짜는 2022년 1월이라 내부 지식으로는 누가 2024 NBA MVP를 수상했는지 알 수 없었죠. 대신 인터넷을 통해 외부 정보에 접근했는데, Bing 기능으로 탐색 기능을 사용해서 최신 정보로 질문에 답변할 수 있었어요. 이런 기능은 OpenAI 플랫폼 내부에 통합된 증강 메커니즘을 제공하는 거죠.

Retrieval-Augmented Generation 구현

시스템에 RAG를 구현하는 방법은 여러 가지가 있어요.

LangChain은 LLM을 사용해서 애플리케이션을 개발하고 배포하기 위한 라이브러리인데, LLM을 RAG 기술과 통합하기 위한 도구를 제공하고 있어요. LangChain 라이브러리를 사용하면 LLM이 실시간 정보에 액세스할 수 있도록 해줄 수 있어요. 예를 들어 Google 검색, Vector Database, 또는 Knowledge Graph 같은 것들이죠.

예를 들어 LangChain은 Cypher Search Chain을 추가했는데, 자연어 질문을 [Cypher]() 문을 사용해서 Neo4j와 같은 Graph Database에서 정보를 검색하고 제공된 정보를 기반으로 최종 답변을 구성해줘요. Cypher Search Chain을 사용하면 LLM은 최종 답변을 구성하는 것뿐만 아니라 자연어 질문을 Cypher 쿼리로 번역하는 데에도 사용되는 거죠.

Cypher search in LangChain. Image by Tomaž Bratanič.
LangChain의 Cypher 검색. 이미지: Tomaž Bratanič.

검색이 강화된 LLM 워크플로를 위한 또 다른 인기 있는 라이브러리는 LlamaIndex에요. 이전에는 GPT Index로 알려져 있었죠. LlamaIndex는 LLM의 성능을 향상시키는 포괄적인 데이터 프레임워크인데, 개인 또는 사용자 정의 데이터를 사용할 수 있도록 지원해줘요.

LlamaIndex는 API, PDF, 문서부터 SQL 또는 Graph Database까지 모든 것을 포함해서 다양한 데이터 소스와 형식을 쉽게 수집해서 기존 데이터를 LLM에 통합할 수 있는 데이터 커넥터를 제공해요. 또한 Index와 그래프를 사용해서 수집된 데이터를 구조화하는 효율적인 메커니즘을 제공해서 데이터가 LLM과 함께 사용하기에 적합하게 정렬되도록 해준답니다. 고급 검색 및 쿼리 인터페이스를 통해 사용자는 LLM 프롬프트를 입력하고 컨텍스트 검색 및 지식 강화 출력을 다시 받을 수 있어요.

LlamaIndex는 생성 및 연결을 지원하는데, Property Graph Index는 비정형 데이터에서 풍부한 Knowledge Graph를 구축한 다음 이를 유연한 방식으로 쿼리해서 정보 내의 통찰력과 관계를 찾아내죠. Knowledge Graph 생성을 건너뛰고 Neo4j와 같은 Graph Database 도구에 이미 있는 것을 LlamaIndex 프로젝트에서 직접 사용할 수도 있어요.

Neo4j의 기본 Property Graph 모델 덕분에, Neo4j Graph Database에 구축된 Knowledge Graph는 트랜잭션 데이터, 조직 데이터, 그리고 Vector Embedding을 하나의 데이터베이스에 결합해서 전체 애플리케이션 설계를 단순화할 수 있어요. LlamaIndex의 Property Graph Index와 함께 사용하면 그래프 내에서 특정 유형의 정보를 쿼리하는 사용자 정의 솔루션을 만들 수 있죠.

Retrieval-Augmented Generation (RAG)의 장점과 단점

Fine-tuning과 비교했을 때, RAG 방식은 몇 가지 분명한 장점이 있어요.

  • 답변에 정보 출처를 인용할 수 있어서 정보의 유효성을 검사하고, 필요에 따라 기본 정보를 변경하거나 업데이트할 수 있죠.
  • LLM이 질문에 답하려고 내부 지식에 더 이상 의존하지 않기 때문에, 환각 현상이 발생할 가능성이 줄어들어요. 대신 관련 문서에 제공된 정보를 사용하거든요.
  • 문제를 LLM 유지 관리에서 데이터베이스 유지 관리, Query 및 컨텍스트 구성과 관련된 문제로 바꾸기 때문에, LLM이 사용하는 기본 정보를 변경, 업데이트, 유지 관리하는 게 더 쉬워요.
  • 답변은 사용자의 상황이나 액세스 권한에 따라 개인화될 수 있다는 점도 좋죠.

하지만 RAG의 제한 사항도 잊지 마세요!

  • 답변의 품질은 스마트 검색 도구 성능에 달려있어요. 스마트 검색 도구가 데이터 저장소에서 충분히 관련성 높은 문서를 찾아 LLM에 제공할 수 있어야 하죠.
  • 애플리케이션은 데이터베이스든 다른 데이터 저장소든 특정 Knowledge Graph에 접근할 수 있어야 해요.
  • Large Language Model의 내부 지식을 완전히 무시하면 답변할 수 있는 질문 수가 제한될 수 있어요.
  • 때로는 LLM이 지침을 따르지 않아서 컨텍스트가 무시되거나, 컨텍스트에서 관련 답변 데이터를 찾지 못하면 환각 현상이 계속 발생할 수도 있답니다.

LLM을 위한 Fine-tuning과 RAG 비교

두 접근 방식의 주요 차이점은 아래 표에 잘 설명되어 있어요.

비교 포인트 Fine-tuning RAG
데이터 요구 사항 대량의 작업별 레이블이 지정된 데이터 사전 훈련된 LLM, 별도의 정보 검색 시스템, 생성을 위해 잠재적으로 일부 레이블이 지정된 데이터
  특정 작업에 내부 LLM 지식을 적용합니다. 외부 지식 소스를 통합하여 최신 정보에 액세스할 수 있습니다.
  해당 영역의 작업에 대해 더 정확하며 영역 외부에 대한 환각이 발생하기 쉽습니다. 환각에 덜 취약합니다. 정확성은 검색된 데이터 및 LLM 처리에 따라 달라집니다.
  다양한 작업/도메인에 대한 재교육 필요 검색 시스템 데이터 소스를 수정하여 새로운 도메인에 더 쉽게 적응하거나 지식을 업데이트할 수 있습니다.
투명성과 설명 가능성 답변 뒤에 추론을 설명하기가 어렵습니다. 더 나은 투명성을 위해 출처 인용을 허용합니다.
개발 및 배포 데이터 준비 및 Fine-tuning 기술에 대한 전문 지식이 필요합니다. LLM과 함께 검색 시스템을 구축하거나 통합해야 합니다.

Fine-tuning은 명확하고 잘 정의된 도메인이 있는 작업, 그리고 특정 제품에 대한 감정 분석이나 데이터베이스 Query 생성과 같이 대량의 레이블이 지정된 데이터에 대한 액세스에 아주 적합해요. 기본 LLM의 컨텍스트를 새 데이터로 업데이트하고, 외부 데이터 소스에 대한 의존성을 완전히 제거할 수 있죠.

하지만 RAG가 더 정확하다는 연구 결과도 있어요 (이 연구와 이 연구를 참고해보세요!). 최신 정보에 대한 액세스가 필요하거나, 질문 답변 시스템 및 고객 서비스 챗봇처럼 기존 Knowledge Graph와의 통합이 필요한 작업에 더 적합하다는 의미겠죠?

Graph Database가 있는 RAG (예: )는 여러 가지 장점을 제공해요. 상호 연결된 엔터티가 있는 풍부한 Knowledge Graph에 접근하면 더 포괄적인 답변을 얻을 수 있고, 새로운 영역과 진화하는 지식에 대한 적응성도 높아지죠. RAG가 답변을 생성하는 데 사용하는 관계와 데이터 포인트에 대한 통찰력도 얻을 수 있고요. 또한 정적 훈련 데이터에 의존하는 Fine-tuning LLM과는 달리, Fine-tuning에서 부족한 설명 가능성과 최신 정보에 대한 액세스를 제공한다는 점도 매력적이에요.

특히 잘 정의된 영역에서 고도로 전문화된 지식이 필요할 때는 Fine-tuning을 사용해서 RAG를 보완할 수 있어요. 하지만 대부분의 애플리케이션에서 RAG는 더 비용 효율적이고 시간 효율적인 접근 방식을 제공하죠. 훈련 데이터 세트를 준비하고 지도 학습 단계를 거치는 대신, 지식 저장소가 포함된 사전 훈련된 LLM을 바로 사용할 수 있거든요. 게다가 RAG는 Fine-tuning과 동일한 수준의 기술 전문 지식이 필요하지 않아요.

사실 대부분의 회사가 틈새 기술을 위해 맞춤형 LLM을 만들 필요는 없어요. RAG를 통해 사전 훈련된 LLM과 도메인별 데이터를 결합하면 기업이 강력한 언어 모델 내에서 자체 데이터를 사용하기에 충분하기 때문에 매력적인 선택지가 될 수 있죠.

많은 팀이 Graph Database를 사용해서 RAG를 효과적으로 구현하는 방법을 연구하고 있어요. "GraphRAG"라는 용어도 점점 많이 쓰이고 있죠. Microsoft나 애틀랜타 에모리대학교 팀처럼 RAG 시스템의 결과를 개선하기 위해 RAG와 함께 그래프를 가장 잘 사용하는 방법에 대한 아이디어를 제시하는 곳도 있답니다. 이 조합에 대해 더 자세히 알고 싶다면 Neo4j의 GraphRAG 선언문을 한번 살펴보세요. 자세하게 분석되어 있을 거예요.

Neo4j 및 LLM 시작하기

LangChain을 사용해서 Neo4j Knowledge Graph와 Generative AI 모델을 통합하는 방법에 대한 무료 GraphAcademy 과정이 준비되어 있어요.

  • ChatGPT
  • Fine-tuning
  • RAG

에이치시스템즈LogTree는 Neo4j 기반 GraphRAG 플랫폼으로, 데이터를 자동으로 지식그래프화하고 자연어 질의로 즉시 답을 제공합니다.

👉 에이치시스템즈 홈페이지

반응형
반응형

데이터는 정말 많이 가지고 있는데, Databricks에서 테이블 정리도 하고, 잘 정의된 스키마에 확장 가능한 컴퓨팅까지 갖췄어요. 그런데도 뭔가 부족한 느낌이 들 수 있어요.

어려운 건 데이터 자체가 아니에요. 데이터들이 실제로 어떻게 연결되는지 파악하는 게 핵심이죠. 더 나은 추천, 공동 사기 적발, 고객 간의 영향 관계 등 실제 결과를 만들어내는 패턴은 단순히 행과 열만 봐서는 찾기 힘들거든요.

Neo4j Aura Graph Analytics를 사용하면 이미 가지고 있는 데이터 위에 그래프 알고리즘을 실행할 수 있어서, 추천, 사기, 고객 행동과 관련된 패턴을 훨씬 쉽게 찾아내고 활용할 수 있어요. 새로운 인프라를 구축하거나 파이프라인을 재구성할 필요 없이, 수십억 개의 Node와 Relationship까지 확장할 수 있다니 정말 매력적이죠?

그래프 분석에 적합한 비즈니스 문제는 무엇일까요?

유사성 알고리즘(Similarity Algorithms)은 "무엇이 비슷할까?"라는 질문에 답을 줘요. 소매, 금융 서비스, 미디어 회사에서는 이걸 활용해서 제품 추천을 강화하고, 중복 고객을 식별하고, 타겟팅을 위해 유사한 행동을 하는 사용자를 찾고 있어요.

커뮤니티 감지 알고리즘(Community Detection Algorithms)은 엔터티가 연결되는 방식에 따라 자연스러운 그룹을 찾아내요. 팀에서는 이걸 활용해서 구매 패턴과 같은 공유 행동을 기준으로 고객을 분류하고, 긴밀하게 연결된 그룹으로 활동하는 사기 조직을 감지하기도 해요.

중심성 알고리즘(Centrality Algorithms)은 네트워크에서 눈에 띄는 요소를 강조해 줘요. 제조, 금융 서비스, 공급망 팀은 이걸 사용해서 병목 현상을 식별하고, 중요한 공급업체를 찾아내고, 결과에 가장 큰 영향을 미치는 고객이나 거래처를 찾고 있죠.

임베딩 알고리즘(Embedding Algorithms)은 그래프 데이터를 Machine Learning 모델이 사용할 수 있는 숫자 Feature로 변환해 줘요. 데이터 과학 및 ML 팀은 이걸 사용해서 엔터티가 연결되는 방식을 캡처해서 추천, 예측, 이상 탐지와 같은 작업을 개선하고 있어요.

어떻게 작동할까요?

이 강력한 그래프 알고리즘을 실행하기 위해 데이터를 옮기거나 Graph Database를 설정할 필요가 없다는 점이 매력적이에요.

오른쪽 그림을 한번 살펴볼까요? 이 프로세스는 Python 데이터 프레임으로 시작해요. 대부분의 경우 Node(고객이나 제품처럼 관심 있는 항목)에 대한 데이터프레임을 하나 만들고, Relationship(구매나 상호 작용처럼 항목들이 연결되는 방식)에 대한 데이터프레임을 또 하나 만들죠.

다음으로 점선 프레임으로 표시된 서버리스 세션을 시작하는데, 이건 일시적이라 실행되는 동안에만 요금이 부과돼요. 그리고 해당 세션에서 그래프 투영(Graph Projection)을 만들어요. 그래프 투영은 메모리 내 그래프(in-memory graph)인데, 메모리에 저장되고 네트워크로 구조화된 데이터 버전을 의미해서 알고리즘이 효율적으로 실행될 수 있는 거죠.

그래프가 투영되면 알고리즘을 실행해요. 오른쪽 다이어그램에서는 연결을 기반으로 Node가 얼마나 유사한지 캡처하는 각 Node의 숫자 표현인 임베딩(Embedding)을 생성했네요.

결과는 Databricks에 데이터 프레임 형태로 다시 나타나요. 여기서 Machine Learning 워크플로우에 사용하거나 Databricks의 Delta 테이블에 다시 쓸 수 있어서 보고, 분석, 운영 용도로 활용할 수 있어요.

이제 Databricks 데이터를 가져와 그래프로 모델링하고 알고리즘을 실행하는 방법을 살펴봤으니, 이걸 여러분의 문제에 적용해 볼 차례에요! 다음 튜토리얼에서 직접 데이터를 가지고 실습해 보세요. 단계별 튜토리얼Databricks의 Aura Graph Analytics용.

  • Graph Algorithms
  • Graph Data Science

에이치시스템즈LogTree는 Neo4j 기반 GraphRAG 플랫폼으로, 데이터를 자동으로 지식그래프화하고 자연어 질의로 즉시 답을 제공합니다.

👉 에이치시스템즈 홈페이지

반응형
반응형

최근 몇 년 동안 Natural Language Processing(NLP)은 정말 빠르게 발전하고 있어요. 그중에서도 중요한 부분은 임베딩인데요, 임베딩은 단어나 구절의 의미와 다른 단어들과의 관계를 숫자로 표현한 것이라고 할 수 있어요.
임베딩은 문서 분류, 기계 번역, 감정 분석, 개체명 인식 등 다양한 NLP 작업에 활용될 수 있죠. 게다가 GPT-3 같은 사전 훈련된 Large Language Model(LLM) 덕분에 여러 언어 작업과 분야에서 전이 학습을 하는 데 임베딩이 더욱 중요해졌어요. 임베딩이 NLP의 발전에 큰 영향을 주고 있다는 거, 정말 흥미롭죠?

한편, 그래프와 그래프 Neural Network의 발전으로 이미지 인식, 신약 개발, 추천 시스템 등 여러 분야에서 성능이 향상되었어요.
특히 그래프 Neural Network는 데이터 포인트 간의 관계가 Machine Learning 작업의 정확도를 높이는 데 중요한 역할을 하는 그래프 구조 데이터의 표현을 학습하는 데 엄청난 가능성을 보여주고 있어요.

이번 블로그에서는 그래프 Neural Network의 기능을 활용해서 데이터 포인트 간의 관계를 캡처하고 인코딩해서 문서 분류 정확도를 높이는 방법을 알아볼 거예요. 특히, Medium 기사의 태그를 예측하기 위해 두 가지 모델을 훈련해 볼 건데요.

이전 블로그 게시물의 태그 예시. 작성자가 직접 만들었어요.

대부분의 Medium 기사에는 작성자가 더 쉽게 검색하고 검색 성능을 높이기 위해 관련 태그를 달아놓죠. 이러한 태그는 기사 분류라고도 생각할 수 있어요. 위 이미지처럼 각 기사에는 최대 5개까지 태그 또는 카테고리가 할당될 수 있어요. 그래서 두 가지 분류 모델을 훈련해서 다중 라벨 분류를 할 건데요, 각 기사에 하나 이상의 태그를 할당하는 방식이에요.

Medium 기사 태그의 다중 라벨 분류. 작성자가 직접 만들었어요.

첫 번째 분류 모델은 기사 제목과 부제의 OpenAI 최신 임베딩(text-embedding-ada-002)을 입력 기능으로 사용할 거예요. 이 모델은 GraphSAGE라는 그래프 Neural Network 알고리즘을 사용해서 개선하려는 기준 정확도를 제공하죠. 흥미롭게도 이 예시에서는 단어 임베딩이 GraphSAGE에 대한 입력으로 사용될 수 있어요.
훈련하는 동안 GraphSAGE 알고리즘은 이러한 단어 임베딩을 활용해서 인접한 Node의 정보를 반복적으로 집계하고, 문서 분류와 같은 Machine Learning 작업의 정확성을 높일 수 있는 강력한 Node 수준 표현을 생성해요.

간단히 말해서, 이번 블로그에서는 데이터 포인트 간의 관계를 고려해서 단어 임베딩을 개선하기 위해 그래프 Neural Network를 사용하는 방법을 살펴볼 거예요.
데이터 포인트 간의 관계가 중요하고 예측 가능하다면 그래프 Neural Network는 텍스트 데이터의 더 의미 있고 정확한 표현을 학습할 수 있고, 결과적으로 Machine Learning 모델의 정확도를 높일 수 있어요.

Medium 데이터 세트

Kaggle에는 몇 가지 Medium 기사 데이터 세트가 있지만, 기사 간의 관계를 포함하는 데이터 세트는 없어요. 어떤 유형의 관계가 태그 예측에 도움이 될까요? Medium에는 사용자가 콘텐츠를 북마크하거나 읽고 싶을 때 목록을 만들어서 관리하는 기능이 있어요.

Medium에서 사용자가 만든 기사 목록. 작성자가 직접 만들었어요.

이 이미지는 사용자가 주제에 따라 4개의 기사 목록을 만든 예시를 보여주고 있어요. 예를 들어, 대부분의 기사는 목록에 있고, 다른 기사는 , , 그리고 목록에 추가되었죠. 두 기사가 동일한 목록에 있다면 공통 목록이 없는 경우보다 더 유사하다는 아이디어예요. Medium 목록은 유사한 기사를 찾고 추천하는 데 도움이 되는 기사 간의 관계라고 생각할 수 있어요.

물론 예외도 있어요. 일부 사용자는 온갖 종류의 기사가 포함된 엄청나게 큰 읽기 목록을 만들기도 하거든요.

2551개의 기사가 포함된 읽기 목록의 예시. 작성자가 직접 만들었어요.

재미있게도 이렇게 많은 기사가 포함된 목록의 대부분은 제목이 똑같아요. 바로 이죠. Medium의 기본 설정인 것 같아요.

아쉽게도 Medium 기사와 사용자가 만든 목록에 대한 정보가 포함된 공개 데이터 세트는 없어요.
그래서 저는 데이터를 분석하면서 시간을 좀 보내야 했어요. 4,000명의 사용자 목록에서 55,000개의 Medium 기사에 대한 정보를 가져왔답니다.

Neo4j 환경 준비

그래프 구성 및 GraphSAGE 훈련은 Neo4j에서 실행될 거예요. 저는 Cypher라는 멋진 그래프 쿼리 언어와 50개 이상의 그래프 알고리즘이 포함된 Graph Data Science(GDS) 플러그인을 제공하는 Neo4j를 정말 좋아해요. 덕분에 그래프를 생성하고 분석하기 위해 여러 도구를 사용할 필요가 없거든요.

Medium 데이터 세트의 그래프 스키마는 다음과 같아요:

Graph 스키마. 작성자가 만든 이미지입니다.

스키마는 중간 기사를 중심으로 진행돼요. 우리는 url, , 그리고 date 기사의 정보를 가지고 있죠. 또한 기사 제목과 부제를 기반으로 text-embedding-ada-002 모델을 사용하여 OpenAI의 임베딩을 계산하고 openai임베딩 속성으로 저장했어요. 기사를 작성한 사람, 해당 기사가 속한 사용자 목록 및 태그도 알고 있고요.

중간 규모 데이터 세트를 Neo4j 데이터베이스로 가져올 수 있는 두 가지 옵션을 준비했어요. 다음 Jupyter Notebook을 실행하고 Python에서 데이터 세트를 가져올 수 있어요. 이 옵션은 Neo4j 샌드박스 환경(빈 Graph Data Science 프로젝트 사용)에서도 작동한답니다.

마스터의 블로그/Import.ipynb · tomasonjo/blogs

다른 옵션은 제가 준비한 Neo4j 데이터베이스 덤프를 복원하는 거예요.

중간 덤프-v55.dump

덤프는 Neo4j 버전 5.5.0으로 생성되었으므로 해당 버전 이상을 사용해야 해요. 데이터베이스 덤프를 복원하는 가장 쉬운 방법은 Neo4j 데스크탑 환경을 사용하는 것이랍니다.
또한 다음을 설치해야 해요: APOC and GDS 라이브러리는 Neo4j Desktop 환경을 사용하는 경우 필요해요.

데이터베이스 가져오기가 완료된 후 Neo4j 브라우저에서 다음 Cypher 쿼리를 실행하여 가져오기가 성공했는지 확인할 수 있어요.

MATCH p=(n:Author)-[:WROTE]->(d)-[:IN_LIST]->(), p1=(d)-[:HAS_TAG]->()
WHERE n.name = "Tomaz Bratanic"
RETURN p,p1 LIMIT 25

결과에는 목록 및 태그와 함께 제가 작성한 기사 몇 개가 포함될 거예요.

중간 그래프의 작은 하위 집합입니다. 작성자가 만든 이미지입니다.

이제 이 블로그 게시물의 실제적인 부분을 살펴볼 시간이에요. 모든 분석 코드는 Jupyter Notebook으로 제공된답니다.

블로그/마스터에서 GraphSAGE.ipynb를 사용한 분류 · tomasonjo/blogs

탐색적 분석

우리는 Graph Data Science Python Client를 사용해서 Neo4j 및 Graph Data Science 플러그인과 인터페이스할 거예요. 이는 순수 Python 코드를 사용하여 그래프 알고리즘을 실행할 수 있게 해주는 Neo4j 생태계에 대한 탁월한 추가 기능이죠. 제 소개 블로그 게시물에서 자세한 내용을 확인해보세요.

먼저 매체 기사당 태그 분포를 평가해볼게요.

dist_df = gds.run_cypher("""
MATCH (a:Article)
RETURN count{(a)-[:HAS_TAG]->()} AS count
""")

sns.displot(dist_df['count'], height=6, aspect=1.5)
기사당 태그 배포. 작성자가 만든 이미지입니다.

기사의 약 50%에는 태그가 없네요.
여기에는 두 가지 이유가 있어요. 작성자가 아무 것도 사용하지 않았거나 사용자 정의 HTML 구조를 가진 매체 출판물과 같은 다양한 이유로 스크랩 프로세스에서 해당 항목을 검색하지 못했을 수도 있죠. 그러나 태그가 있는 기사가 여전히 25,000개가 넘으므로 기사 태그의 다중 레이블 분류 모델을 훈련하고 평가할 수 있으므로 큰 문제는 아니에요. 대부분의 작성자는 기사당 5개의 태그를 사용하기로 선택하는데, 이는 Medium 플랫폼에서 허용하는 상한이기도 하답니다.

다음으로 사용자 목록에 포함되지 않은 기사가 있는지 평가해볼게요.

gds.run_cypher(
    """
MATCH (a:Article)
RETURN exists {(a)-[:IN_LIST]-()} AS in_list,
       count(*) AS count
ORDER BY count DESC
"""
)

결과는 모든 기사가 하나 이상의 목록에 속해 있음을 보여주네요. 격리된 Node(연결되지 않은 Node)를 식별하는 것은 모든 그래프 분석 워크플로에서 중요한 부분이에요. Node 임베딩을 계산하는 동안 이에 특별한 주의를 기울여야 하기 때문이죠. 다행히 이 데이터세트에는 격리된 Node가 없으므로 걱정할 필요가 없어요.

탐색적 분석의 마지막 부분에서는 가장 자주 사용되는 태그를 살펴볼게요. 여기서는 최소 100개 이상의 기사에 존재하는 태그의 단어 구름을 구성할 거예요.

tags = gds.run_cypher( """ MATCH (t:Tag) WITH t, count {(t)<--()} AS size WHERE size > 100 RETURN t.name AS tag, size ORDER BY size DESC """ ) d = {} for i, row in tags.iterrows(): d[row["tag"]] = row["size"] wordcloud = WordCloud( background_color="white", colormap="tab20c", min_font_size=1 ).generate_from_frequencies(d) plt.figure() plt.imshow(wordcloud) plt.axis("off") plt.show()
가장 자주 사용되는 태그의 Word Cloud에요.

가장 자주 사용되는 태그는 Data Science, 인공 지능, 프로그래밍, 그리고 Machine Learning이네요.

다중 라벨 분류

앞서 언급했듯이, 우리는 매체 기사의 태그를 예측하기 위해 다중 레이블 분류 모델을 훈련할 거예요. 이를 위해 scikit-multilearn 라이브러리를 사용할 건데요, 이 라이브러리가 데이터 분할 및 모델 훈련에 도움이 되거든요.

scikit-multilearn 라이브러리를 사용한 데이터세트 분할은 무작위 시드 파라미터를 제공하지 않아서 데이터세트 분할이 결정적이지 않다는 점을 확인했어요. OpenAI의 단어 임베딩으로 훈련된 기본 모델과 GraphSAGE 임베딩을 기반으로 한 모델을 제대로 비교하려면, 두 모델 버전 모두 동일한 훈련 및 테스트 예제를 사용하도록 단일 데이터 세트 분할을 수행해야 해요. 그렇지 않으면 데이터세트 분할에 따라서 모델의 정확도에 약간의 차이가 있을 수 있거든요.

단어 임베딩은 이미 그래프에 저장되어 있으니, 분류 모델을 훈련하기 전에 GraphSAGE 알고리즘을 사용해서 Node Embedding을 계산하기만 하면 돼요.

GraphSAGE

GraphSAGE는 컨볼루션 그래프 Neural Network 알고리즘이에요. 이 알고리즘의 핵심 아이디어는 Node의 로컬 이웃에서 특징 정보를 샘플링하고 집계해서 Node Embedding을 생성하는 함수를 학습하는 것이죠. GraphSAGE 알고리즘은 Node Embedding을 유도할 수 있는 함수를 학습하므로, 학습 단계에서 관찰되지 않은 새로운 Node의 Embedding을 유도하는 데에도 사용할 수 있어요. 이걸 귀납적 학습이라고 부른답니다.

GraphSAGE에서 지역 탐색 및 정보 공유. [1]

GraphSAGE 알고리즘의 훈련 과정과 수학에 대해 더 자세히 알고 싶다면, GraphSAGE에 대한 직관적인 설명 (Rıza Özçelik의 블로그 게시물) 또는 공식 GraphSAGE 사이트를 살펴보는 걸 추천드려요.

Node Similarity 알고리즘을 사용한 단일 부분 투영

GraphSAGE는 여러 유형의 Node가 있는 그래프를 지원하는데, 각 Node 유형에는 이를 나타내는 다양한 기능이 있어요. 저희 예시에서는 ArticleList Node가 있죠. 하지만 저는 작업 흐름을 단순화하기 위해 단일 부분 투영을 수행하기로 결정했어요.

Article의 단일 투영. Article이 List를 공유하면 Article 간에 Relationship이 있어요.

단일 부분 투영은 그래프 분석에서 자주 사용되는 단계인데요. 아이디어는 이분 그래프(두 개의 Node 유형이 있는 그래프)를 가져와서 단일 부분 그래프(단 하나의 Node 유형만 있는 그래프)를 출력하는 거예요. 이 특정 예시에서는 두 Article이 동일한 List의 일부인 경우 두 Article 간의 Relationship을 생성할 수 있어요. 또한 공유 List의 수 또는 Jaccard 계수와 같은 정규화된 값을 Relationship Property로 저장할 수도 있죠.

단일 부분 투영은 그래프 분석의 일반적인 단계이므로, Neo4j Graph Data Science 라이브러리는 Node Similarity 알고리즘을 제공해서 우리를 도와줄 수 있어요.

먼저, 메모리 내 그래프를 투영해야 해요. 우리는 ArticleList Node와 함께 IN_LIST Relationship을 사용할 거예요. 추가적으로, openaiEmbedding Node Property도 포함할 거고요.

G, metadata = gds.graph.project(
    "articles", 
    ["Article", "List"],
    "IN_LIST", 
    nodeProperties=["openaiEmbedding"]
)

이제 Node Similarity 알고리즘을 사용해서 단일 부분 투영을 수행할 수 있어요. 한 가지 주의할 점은 topK 파라미터가 10이라는 건데요. 이는 각 Node가 가장 유사한 10개의 Node에만 연결된다는 의미예요. 하지만 이 예시에서는 사용자 List의 모든 Article 간에 Relationship을 생성하려고 하니까, 상대적으로 높은 값을 사용해야겠죠. topK 파라미터에 말이에요.

gds.nodeSimilarity.mutate(
    G, topK=2000, mutateProperty="score", mutateRelationshipType="SIMILAR"
)

우리는 mutate 알고리즘 모드를 사용해서 결과를 메모리 내 투영 그래프에 다시 저장할 거예요. SIMILAR Relationship은 하나 이상의 사용자 List를 공유하는 모든 Article 쌍 간에 생성될 거예요.

GraphSAGE 모델 훈련

GraphSAGE 알고리즘

GraphSAGE 알고리즘은 귀납적이에요. 즉, 이전 훈련 중에 보지 못했던 `Node`에 대한 임베딩을 생성하는 데 사용할 수 있다는 거죠. 이런 귀납적 특성 덕분에 그래프의 일부에서만 GraphSAGE 모델을 훈련한 다음, 모든 `Node`에 대한 임베딩을 생성할 수 있어요.

그래프의 일부에 대해서만 GraphSAGE 모델을 훈련하면 시간과 컴퓨팅 성능을 절약할 수 있어서 큰 그래프를 처리할 때 유용해요. 그래프가 그렇게 크진 않지만, 이 예제를 통해 그래프의 훈련 하위 집합을 효율적으로 샘플링하는 방법을 보여드릴게요.

재시작 샘플링을 통한 랜덤 워크

재시작 샘플링을 통한 랜덤 워크 아이디어는 정말 간단해요. 알고리즘은 미리 정의된 시작 `Node` 집합에서 무작위로 이동하죠. 워크의 각 단계에서 현재 랜덤 워크가 중지되고 시작 `Node` 집합에서 새로운 랜덤 워크가 시작될 확률이 있어요. 사용자는 시작 `Node`를 정의할 수 있는데, 시작 `Node`가 정의되지 않은 경우 알고리즘은 이를 무작위로 균일하게 선택해요.

시작 `Node`를 수동으로 선택하는 예제를 보여드리는 게 흥미로울 것 같아서, 기사 그래프가 어떻게 연결되어 있는지 평가하기 위해 약하게 연결된 구성 요소 알고리즘을 실행하는 것부터 시작해볼게요. 약하게 연결된 구성 요소는 관계 방향을 무시할 경우, 집합의 모든 `Node` 사이에 경로가 존재하는 그래프 내의 `Node` 집합이에요.

약하게 연결된 구성 요소는 다른 구성 요소의 `Node`가 도달할 수 없는 섬으로 간주될 수 있어요. 알고리즘은 연결된 `Node` 집합을 식별하지만, 그 출력은 전체 그래프의 연결이 얼마나 끊어졌는지 평가하는 데 도움이 될 수 있죠.

wcc = gds.wcc.stream(G)
wcc_grouped = (
    wcc.groupby("componentId")
    .size()
    .to_frame("componentSize")
    .reset_index()
    .sort_values("componentSize", ascending=False)
    .reset_index()
)
print(wcc_grouped)

그래프에는 총 604개의 연결된 구성 요소가 있어요. 가장 큰 구성 요소는 모든 `Node`의 98%를 포함하고, 다른 구성 요소는 더 작으며 많은 구성 요소가 2개의 `Node`만 포함하고 있죠. 구성 요소에 `Node`가 두 개만 있다면, 기사가 두 개만 있는 중간 규모 사용자 목록이 있고 해당 두 기사는 다른 목록의 일부가 아니라는 의미예요.

우리는 Weakly Connected Component 알고리즘을 실행해서 큰 연결된 구성 요소에 속하는 `Node`를 식별하고, 따라서 샘플링 알고리즘의 시작 `Node`로 사용할 수 있어요. 예를 들어, 이웃이 하나만 있는 `Node`를 사용한 경우 샘플링 알고리즘은 그래프를 효율적으로 서브샘플링하기 위해 더 긴 탐색을 수행할 수 없겠죠.

다행히 샘플링 알고리즘은 랜덤 워크가 새 `Node`를 방문하지 않는 경우 시작 `Node` 세트를 자동으로 확장하도록 구현되어 있어요. 하지만 모든 `Node`의 98%를 차지하는 가장 큰 연결된 구성 요소의 시작 `Node`를 사용했기 때문에 알고리즘은 시작 `Node` 세트를 자동으로 확장할 필요가 없어요.

largest_component = wcc_grouped["componentId"][0]
start_node = wcc[wcc["componentId"] == largest_component]["nodeId"][0]

trainG, metadata = gds.alpha.graph.sample.rwr(
    "trainGraph",
    G,
    samplingRatio=0.20,
    startNodes=[int(start_node)],
    nodeLabels=["Article"],
    relationshipTypes=["SIMILAR"],
)

샘플링 비율 매개변수는 샘플링할 원본 그래프의 `Node` 비율을 정의해요. 예를 들어 샘플링 비율로 0.20 값을 사용하면 샘플링된 하위 그래프는 원래 그래프 크기의 20%가 되죠. 추가적으로 우리는 랜덤 워크가 오직 방문만 할 수 있도록 정의해야 해요. `Node`를 통해 `Relationship`을 이용해서 Node Label and Relationship Type 매개변수.

GraphSAGE 훈련

마지막으로 샘플링된 하위 그래프에서 GraphSAGE 모델을 훈련할 수 있어요.

gds.beta.graphSage.train(
    trainG,
    modelName="articleModel",
    embeddingDimension=256,
    sampleSizes=[10, 10],
    searchDepth=15,
    epochs=20,
    learningRate=0.0001,
    activationFunction="RELU",
    aggregator="MEAN",
    featureProperties=["openaiEmbedding"],
    batchSize=10,
)

GraphSAGE 알고리즘은 openaiEmbedding `Node` 속성을 입력 기능으로 사용해요. GraphSAGE 임베딩의 차원은 256(벡터 크기)이죠. 이 블로그에서 하이퍼 파라미터 최적화를 시도해 보는 동안 학습률과 활성화 함수가 가장 영향력 있는 매개변수라는 것을 알게 되었어요.

임베딩 생성

GraphSAGE 모델이 훈련된 후에는 이를 사용해서 모든 `Node`에 대한 `Node` 임베딩을 계산할 수 있어요. 원래의 더 큰 투영 그래프의 `Node`를 고려하고 `Relationship`을 사용해서요.

gds.beta.graphSage.write(
    G,
    modelName="articleModel",
    nodeLabels=["Article"],
    writeProperty="graphSAGE",
    relationshipTypes=["SIMILAR"],
)

이번에는 GraphSAGE 임베딩을 데이터베이스의 `Node` 속성으로 저장하는 모드에요.

분류 모델

OpenAI 및 GraphSAGE 임베딩을 모두 준비했어요. 이제 남은 건 모델을 훈련하고 성능을 비교하는 것뿐이죠.

먼저 예측하려는 기사 태그에 라벨을 지정할게요. 저는 임의로 최소 100개 이상의 기사에 존재하는 태그만 포함하기로 결정했어요. 대상 태그에는 보조 태그가 지정됩니다. 상표.

gds.run_cypher(
    """
MATCH (t:Tag)
WHERE count{(t)<--()} > 100
SET t:Target
RETURN count(*) AS count
"""
)

예측하려는 태그 161개에 라벨을 붙였어요. 위 단어 클라우드 시각화는 동일한 161개의 태그를 빈도에 따라 시각화한 것이라는 점, 기억하시죠?

scikit-multilearn 라이브러리를 사용할 거니까, Neo4j에서 관련 정보를 내보내야 해요.

data = gds.run_cypher(
    """
MATCH (a:Article)-[:HAS_TAG]->(tag:Target)
RETURN a.url AS article,
        a.openaiEmbedding AS openai,
        a.graphSAGE AS graphSAGE,
        collect(tag.name) AS tags
"""
)

다음으로, 특정 기사에 대한 태그의 존재를 나타내는 이진 행렬을 만들어야 해요. 기본적으로 기사당 태그를 one-hot encoding하는 거라고 생각하면 돼요. 이걸 위해 MultiLabelBinarizer 절차를 활용할 수 있어요.

mlb = MultiLabelBinarizer()
tags_mlb = mlb.fit_transform(data["tags"])
data["target"] = list(tags_mlb)

scikit-multilearn 라이브러리는 다중 레이블 예측 작업을 위한 향상된 데이터 세트 분할을 제공해요. 하지만 무작위 시드 매개변수를 사용하는 결정론적 접근 방식은 허용되지 않으니까, Word Embedding과 GraphSAGE Embedding 모두에 대해 데이터 세트 분할을 한 번만 수행한 다음, 그에 따라 두 모델을 학습시킬 거예요.

다음 함수는 다중 레이블 분류 모델에 대한 입력 기능으로 별도로 사용해야 하는 데이터 프레임과 열을 가져와 가중치가 적용된 매크로와 가중치가 적용된 정밀도를 인쇄하는 동안 가장 성능이 좋은 모델을 반환해요. 여기서는 LabelPowerset 접근 방식을 사용한 다중 라벨 분류를 진행할 거예요.

def train_and_evaluate(df, input_columns):
    max_weighted_precision = 0
    best_input = ""
    # Single split data
    X = data[input_columns].values
    y = np.array(data["target"].to_list())
    x_train_all, y_train, x_test_all, y_test = iterative_train_test_split(
        X, y, test_size=0.2
    )
    # Train a model for each input option
    for i, input_column in enumerate(input_columns):
        print(f"Training a model based on {input_column} column")
        x_train = np.array([x[i] for x in x_train_all])
        x_test = np.array([x[i] for x in x_test_all])

        # train
        classifier = LabelPowerset(LogisticRegression())
        classifier.fit(x_train, y_train)
        # predict
        predictions = classifier.predict(x_test)
        print("Test accuracy is {}".format(accuracy_score(y_test, predictions)))
        print(
            "Macro Precision: {:.2f}".format(
                get_macro_precision(mlb.classes_, y_test, predictions)
            )
        )
        weighted_precision = get_weighted_precision(mlb.classes_, y_test, predictions)
        print("Weighted Precision: {:.2f}".format(weighted_precision))
        if weighted_precision > max_weighted_precision:
            max_weighted_precision = weighted_precision
            best_classifier = classifier
            best_input = input_column

    return best_classifier, best_input

자, 이제 모든 준비가 끝났으니 Word Embedding과 graphSAGE Embedding을 기반으로 모델을 훈련하고 성능을 비교해 볼까요?

추신. Google Colab을 사용하는 경우, openai Embedding을 사용하면 OOM 문제가 발생할 수 있다는 점 참고해주세요!

classifier, best_input = train_and_evaluate(data, ["openai", "graphSAGE"])

결과는 다음과 같아요.

Training a model based on openai column
Test accuracy is 0.055443548387096774
Macro Precision: 0.20
Weighted Precision: 0.36
Training a model based on graphSAGE column
Test accuracy is 0.05584677419354839
Macro Precision: 0.30
Weighted Precision: 0.41

제목과 부제의 임베딩이 해당 태그에 대한 정보를 제공하긴 하지만, 효율적이지 않을 수 있어요. 왜냐하면 콘텐츠를 정확하게 설명하기보다는 관심을 끄는 클릭베이트 스타일의 제목을 우선시하기 때문일 수 있죠. 또한 작성자마다 다양한 레이블을 사용해서 동일한 콘텐츠에 태그를 지정하는 것에 대한 선호도가 다를 수도 있고요. 이런 어려움에도 불구하고, 우리 모델은 161개의 레이블을 예측하는데, 그 중 다수에는 예시가 거의 없는데도 허용 가능한 결과를 얻을 수 있다는 점이 놀라워요. 정확성을 더 높이기 위해 전체 기사 텍스트를 삽입하고 성능을 평가해볼 수도 있겠죠.

흥미롭게도 GraphSAGE 임베딩을 사용하면 기사 간의 관계를 고려해서 분류 정확도가 향상돼요. 우리 모델의 매크로 정밀도는 10% 포인트나 향상되고, 가중 정밀도는 5% 향상되죠. 이런 결과는 GraphSAGE 임베딩이 자주 사용되지 않는 태그를 더 효과적으로 식별하는 데 도움이 된다는 것을 보여줘요. 표준 단어 임베딩 모델과 달리, 그래프 Neural Network를 사용하면 데이터 포인트 간의 추가 관계를 인코딩할 수 있어서 다운스트림 Machine Learning 모델 성능이 향상되는 거죠. 게다가 성능을 높이면서 차원 축소를 1536에서 256으로 수행했는데, 이건 정말 큰 성과라고 할 수 있어요.

테스트 예측

우리 데이터베이스에는 태그가 없는 기사가 거의 50%나 돼요. 여러 모델에서 모델을 테스트하고 결과를 직접 평가해볼 수 있어요.

example = gds.run_cypher(
    """
MATCH (a:Article)
WHERE NOT EXISTS {(a)-[:HAS_TAG]->()}
RETURN a.title AS title,
       a.openaiEmbedding AS openai,
       a.graphSAGE AS graphSAGE
LIMIT 15
"""
)

tags_predicted = classifier.predict(np.array(example[best_input].to_list()))
example["tags"] = [list(mlb.inverse_transform(x)[0]) for x in tags_predicted]
example[["title", "tags"]]

흥미롭게도 모델은 대부분의 실제 기사에 5개의 태그가 있는 반면, 기사당 하나 또는 두 개의 레이블만 할당해요. 아마도 이게 정밀도 점수 값의 원인 중 하나일 거예요. 그 외에는 이 작은 샘플로 판단했을 때 결과가 꽤 유망해 보이죠?

요약

word2vec과 같은 전통적인 단어 임베딩 모델은 단어의 동시 발생 통계를 인코딩하는 데 중점을 둬요. 하지만 데이터 포인트 간에 찾을 수 있는 다른 관계는 완전히 무시되죠. 예를 들어, 유사한 기사를 다양한 읽기 목록에 배치해서 사용자에게 주석을 달도록 하는 경우가 있어요. 다행히 그래프 Neural Network는 단어 임베딩을 기반으로 구축하고 데이터 포인트 간의 관계에서 파생된 추가 정보를 인코딩할 수 있어서 전통적인 단어 임베딩과 그래프 임베딩 사이에 다리를 제공해준답니다. 따라서 그래프 Neural Network는 처음부터 시작할 필요 없이 최첨단 단어 또는 문서 임베딩을 향상시키는 데 사용할 수 있어요.

참고 자료

[1] 해밀턴, 윌, Zhitao Ying, Jure Leskovec. “대형 그래프에서의 유도적 표현 학습.” 신경 정보 처리 시스템의 발전. 2017.


  • 그래프 Neural Network
  • GraphSAGE
  • 다중 라벨 분류기

에이치시스템즈LogTree는 Neo4j 기반 GraphRAG 플랫폼으로, 데이터를 자동으로 지식그래프화하고 자연어 질의로 즉시 답을 제공합니다.

👉 에이치시스템즈 홈페이지

반응형

+ Recent posts