반응형

Knowledge Graph와 구조화된 도구를 사용해서 문제 해결하는 방법을 알아봐요.

텍스트 임베딩 모델, 다들 좋아하시죠? 구조화되지 않은 텍스트 인코딩에 아주 뛰어나서 의미상 유사한 콘텐츠를 더 쉽게 찾을 수 있게 해주니까요. 특히 문서나 다른 텍스트 리소스에서 관련 정보를 인코딩하고 검색하는 데 집중하는 요즘 대부분의 RAG 애플리케이션의 핵심이라는 점은 놀랍지 않아요. 하지만 RAG 애플리케이션에서 텍스트 임베딩 접근 방식이 부족하거나 잘못된 정보를 전달하는 경우를 생각해 볼 만한 명확한 예시들이 있답니다.

앞서 말씀드렸듯이 텍스트 임베딩은 구조화되지 않은 텍스트를 인코딩하는 데 탁월해요. 하지만 구조화된 정보, 예를 들어 , , 또는 같은 작업에는 그렇게 능숙하지 않죠. 간단한 질문을 하나 생각해 볼까요?

2024년에 개봉한 영화 중에서 가장 높은 평가를 받은 영화는 무엇인가요?

이 질문에 답하려면 먼저 출시 연도별로 필터링한 다음, 평점별로 정렬해야 해요. 텍스트 임베딩을 사용한 순진한 접근 방식이 어떻게 작동하는지 살펴보고, 이런 질문을 처리하는 방법을 보여드릴게요. 이번 블로그 포스팅에서는 구조화된 데이터 작업을 다룰 때 필터링, 정렬, 집계 등의 구조를 제공하는 다른 도구, 즉 Knowledge Graph를 사용해야 한다는 점을 강조할 거예요. 코드는 에서 확인하실 수 있습니다.

환경 설정

이번 블로그 포스팅에서는 Neo4j Sandbox의 추천 프로젝트를 사용할 거예요. 추천 프로젝트는 MovieLens 데이터 세트를 사용하는데, 여기에는 영화, 배우, 평점 등 다양한 정보가 담겨 있답니다.

다음 코드는 Neo4j 데이터베이스에 연결하기 위해 LangChain 래퍼를 인스턴스화해요.

os.environ["NEO4J_URI"] = "bolt://44.204.178.84:7687" os.environ["NEO4J_USERNAME"] = "neo4j" os.environ["NEO4J_PASSWORD"] = "minimums-triangle-saving" graph = Neo4jGraph(refresh_schema=False)

다음 코드에 전달할 OpenAI API 키도 필요해요.

os.environ["OPENAI_API_KEY"] = getpass.getpass("OpenAI API Key:")

데이터베이스에는 10,000개의 영화가 있지만, 아직 텍스트 임베딩이 저장되어 있지는 않아요. 모든 항목에 대한 임베딩 계산을 피하기 위해 최고 평점 영화 1,000개에 "Target"이라는 보조 라벨을 지정할 거예요.

graph.query("""
MATCH (m:Movie)
WHERE m.imdbRating IS NOT NULL
WITH m
ORDER BY m.imdbRating DESC
LIMIT 1000
SET m:Target
""")

텍스트 임베딩 계산 및 저장

어떤 내용을 임베딩할지 결정하는 건 중요한 고려 사항이에요. 연도별 필터링과 평점별 정렬을 보여드릴 예정이니, 임베딩 텍스트에서 해당 세부 정보를 제외하는 건 공정하지 않겠죠? 그래서 각 영화의 개봉 연도, 평점, 제목, 설명을 모두 캡처하기로 했어요.

다음은 임베딩할 텍스트의 예시입니다. 영화 "더 울프 오브 월 스트리트"의 경우:

plot: Based on the true story of Jordan Belfort, from his rise to a wealthy 
      stock-broker living the high life to his fall involving crime, corruption
      and the federal government.
title: Wolf of Wall Street, The
year: 2013
imdbRating: 8.2

이게 구조화된 데이터를 임베딩하는 데 좋은 접근 방식이 아니라고 생각할 수도 있지만, 저는 최고의 접근 방식을 모르기 때문에 딱히 반박하진 않을게요. 키-값 항목 대신 텍스트 등으로 변환해야 할 수도 있겠죠. 더 나은 방법이 있다면 알려주세요!

LangChain의 Neo4j Vector 객체에는 from_existing_graph라는, 인코딩해야 하는 텍스트 속성을 선택할 수 있는 편리한 방법이 있어요.

embedding = OpenAIEmbeddings(model="text-embedding-3-small")

neo4j_vector = Neo4jVector.from_existing_graph(
    embedding=embedding,
    index_name="movies",
    node_label="Target",
    text_node_properties=["plot", "title", "year", "imdbRating"],
    embedding_node_property="embedding",
)

이 예시에서는 임베딩 생성을 위해 OpenAI의 text-embedding-3-small 모델을 사용하고 있어요. from_existing_graph 메서드를 사용해서 Neo4jVector 객체를 초기화하죠. node_label 매개변수는 인코딩할 노드를 필터링하는데, 특히 "Target" 라벨이 붙은 노드만 선택해요. text_node_properties 매개변수는 포함될 노드 속성을 정의하는데, 여기에는 plot, title, year, 그리고 imdbRating이 포함돼요. 마지막으로, embedding_node_property는 생성된 임베딩이 저장될 속성을 정의하는데, 여기서는 embedding을 사용하고 있어요.

순진한 접근 방식

줄거리나 설명을 기반으로 영화를 찾는 것부터 시작해 볼까요?

pretty_print(
    neo4j_vector.similarity_search(
        "What is a movie where a little boy meets his hero?"
    )
)

결과는 다음과 같아요:

plot: A young boy befriends a giant robot from outer space that a paranoid government agent wants to destroy.
title: Iron Giant, The
year: 1999
imdbRating: 8.0

plot: After the death of a friend, a writer recounts a boyhood journey to find the body of a missing boy.
title: Stand by Me
year: 1986
imdbRating: 8.1

plot: A young, naive boy sets out alone on the road to find his wayward mother. Soon he finds an unlikely protector in a crotchety man and the two have a series of unexpected adventures along the way.
title: Kikujiro (Kikujirô no natsu)
year: 1999
imdbRating: 7.9

plot: While home sick in bed, a young boy's grandfather reads him a story called The Princess Bride.
title: Princess Bride, The
year: 1987
imdbRating: 8.1

결과는 전반적으로 꽤 괜찮은 것 같아요. 항상 어린 소년이 등장하지만, 그가 항상 영웅을 만나는지는 잘 모르겠네요. 그리고 데이터 세트에는 영화가 1,000개밖에 없어서 옵션이 다소 제한적이에요.

이제 몇 가지 기본적인 필터링이 필요한 쿼리를 시도해 볼게요.

pretty_print(
    neo4j_vector.similarity_search(
        "Which movies are from year 2016?"
    )
)

결과:

plot: Six short stories that explore the extremities of human behavior involving people in distress.
title: Wild Tales
year: 2014
imdbRating: 8.1

plot: A young man who survives a disaster at sea is hurtled into an epic journey of adventure and discovery. While cast away, he forms an unexpected connection with another survivor: a fearsome Bengal tiger.
title: Life of Pi
year: 2012
imdbRating: 8.0

plot: Based on the true story of Jordan Belfort, from his rise to a wealthy stock-broker living the high life to his fall involving crime, corruption and the federal government.
title: Wolf of Wall Street, The
year: 2013
imdbRating: 8.2

plot: After young Riley is uprooted from her Midwest life and moved to San Francisco, her emotions - Joy, Fear, Anger, Disgust and Sadness - conflict on how best to navigate a new city, house, and school.
title: Inside Out
year: 2015
imdbRating: 8.3

웃기게도 2016년 영화는 하나도 선택되지 않았네요. 어쩌면 인코딩을 위해 텍스트 준비를 더 꼼꼼히 하면 더 나은 결과를 얻을 수도 있을 거예요. 하지만 여기서는 문서나 메타데이터 속성을 기반으로 영화를 필터링해야 하는 간단한 구조화된 데이터 작업을 다루고 있기 때문에 텍스트 임베딩을 적용하는 건 적절하지 않아요. 메타데이터 필터링은 RAG 시스템의 정확성을 높이기 위해 자주 사용되는 기술이랍니다.

다음으로 시도할 쿼리에는 약간의 정렬이 필요할 거예요.

pretty_print(
    neo4j_vector.similarity_search("Which movie has the highest imdb score?")
)

결과:

plot: A silent film production company and cast make a difficult transition to sound.
title: Singin' in the Rain
year: 1952
imdbRating: 8.3

plot: A film about the greatest pre-Woodstock rock music festival.
title: Monterey Pop
year: 1968
imdbRating: 8.1

plot: This movie documents the Apollo missions perhaps the most definitively of any movie under two hours. Al Reinert watched all the footage shot during the missions--over 6,000,000 feet of it, ...
title: For All Mankind
year: 1989
imdbRating: 8.2

plot: An unscrupulous movie producer uses an actress, a director and a writer to achieve success.
title: Bad and the Beautiful, The
year: 1952
imdbRating: 7.9

IMDb 평점에 익숙하시다면 8.3점 이상의 영화가 많다는 걸 아실 거예요. 우리 데이터베이스에서 가장 높은 평가를 받은 타이틀은 사실 시리즈인 밴드 오브 브라더스인데, 무려 9.6점이라는 엄청난 점수를 받았답니다. 다시 말하지만, 결과를 정렬할 때 텍스트 임베딩 성능이 좋지 않다는 걸 알 수 있어요.

이번에는 일종의 집계가 필요한 질문도 한번 평가해 볼게요.

pretty_print(neo4j_vector.similarity_search("How many movies are there?"))

결과:

plot: Ten television drama films, each one based on one of the Ten Commandments.
title: Decalogue, The (Dekalog)
year: 1989
imdbRating: 9.2

plot: A documentary which challenges former Indonesian death-squad leaders to reenact their mass-killings in whichever cinematic genres they wish, including classic Hollywood crime scenarios and lavish musical numbers.
title: Act of Killing, The
year: 2012
imdbRating: 8.2

plot: A meek Hobbit and eight companions set out on a journey to destroy the One Ring and the Dark Lord Sauron.
title: Lord of the Rings: The Fellowship of the Ring, The
year: 2001
imdbRating: 8.8

plot: While Frodo and Sam edge closer to Mordor with the help of the shifty Gollum, the divided fellowship makes a stand against Sauron's new ally, Saruman, and his hordes of Isengard.
title: Lord of the Rings: The Two Towers, The
year: 2002
imdbRating: 8.7

결과가 4개의 무작위 영화를 반환하기 때문에 여기서 얻을 수 있는 정보는 딱히 없네요. 무작위로 선택된 4개의 영화에서 이 예시에 태깅하고 삽입한 영화가 총 1,000개라는 결론을 내리는 건 사실상 불가능하죠.

그렇다면 해결책은 뭘까요? 간단해요. 필터링, 정렬, 집계와 같은 구조화된 작업과 관련된 질문에는 구조화된 데이터를 사용하도록 설계된 도구가 필요하죠.

구조화된 데이터를 위한 도구

요즘 대부분의 사람들은 LLM이 제공된 질문과 스키마를 기반으로 데이터베이스와 상호 작용하기 위해 데이터베이스 쿼리를 생성하는 text2query 접근 방식을 떠올리는 것 같아요. Neo4j의 경우 text2cypher지만 SQL 데이터베이스용 text2sql도 있죠. 하지만 실제로는 안정적이지 않고 프로덕션 용도로 사용하기에 충분히 강력하지 않은 것 같아요.

몇 번의 예시, Fine-tuning과 같은 기술을 사용할 수도 있지만, 이 단계에서 높은 정확도를 달성하는 건 거의 불가능해요. text2query 접근 방식은 간단한 데이터베이스 스키마에 대한 간단한 질문에는 적합하지만, 프로덕션 환경의 현실과는 거리가 멀죠. 이 문제를 해결하기 위해 데이터베이스 쿼리 생성의 복잡성을 LLM에서 분리해서, 함수 입력을 기반으로 결정론적으로 데이터베이스 쿼리를 생성하는 코드 문제로 다루는 거예요. 유연성이 줄어드는 대신 견고성이 크게 향상된다는 장점이 있죠. 모든 것에 대답하려고 시도하다가 부정확하게 대답하는 것보다, RAG 애플리케이션의 범위를 좁혀서 해당 질문에 정확하게 대답하는 게 더 좋다고 생각해요.

함수 입력을 기반으로 데이터베이스 쿼리(이 경우 Cypher 문)를 생성하므로 LLM의 도구 기능을 활용할 수 있어요. 이 프로세스에서 LLM은 사용자 입력을 기반으로 관련 매개변수를 채우고, 함수는 필요한 정보 검색을 처리하는 거죠. 이 데모에서는 먼저 두 가지 도구(영화 개수를 계산하는 도구와 목록을 나열하는 도구)를 구현한 다음 LangGraph를 사용하여 LLM 에이전트를 만들 거예요.

영화 계산 도구

미리 정의된 필터를 기반으로 영화 개수를 계산하는 도구를 구현하는 것부터 시작해 볼게요. 먼저 이러한 필터가 무엇인지 정의하고, 이를 사용하는 시기와 방법을 LLM에 설명해야 해요.

class MovieCountInput(BaseModel):
    min_year: Optional[int] = Field(
        description="Minimum release year of the movies"
    )
    max_year: Optional[int] = Field(
        description="Maximum release year of the movies"
    )
    min_rating: Optional[float] = Field(description="Minimum imdb rating")
    grouping_key: Optional[str] = Field(
        description="The key to group by the aggregation", enum=["year"]
    )

LangChain은 함수 입력을 정의하는 여러 가지 방법을 제공하지만, 저는 Pydantic 접근 방식을 선호해요. 이 예에는 영화 결과를 세분화하는 데 사용할 수 있는 세 가지 필터(min_year, max_year 및 min_rating)가 있어요. 이러한 필터는 구조화된 데이터를 기반으로 하며 선택 사항이에요. 사용자는 필터 중 일부를 포함하거나, 모두 포함하거나, 포함하지 않도록 선택할 수 있죠. 또한 특정 속성별로 개수를 그룹화할지 여부를 함수에 알려주는 grouping_key 입력을 도입했어요. 이 경우 지원되는 유일한 그룹화는 enum 섹션에 정의된 대로 연도별이에요.

이제 실제 함수를 정의해 볼게요.

@tool("movie-count", args_schema=MovieCountInput)
def movie_count(
    min_year: Optional[int],
    max_year: Optional[int],
    min_rating: Optional[float],
    grouping_key: Optional[str],
) -> List[Dict]:
    """Calculate the count of movies based on particular filters"""

    filters = [
        ("t.year >= $min_year", min_year),
        ("t.year <= $max_year", max_year),
        ("t.imdbRating >= $min_rating", min_rating),
    ]

    # Create the parameters dynamically from function inputs
    params = {
        extract_param_name(condition): value
        for condition, value in filters
        if value is not None
    }
    where_clause = " AND ".join(
        [condition for condition, value in filters if value is not None]
    )

    cypher_statement = "MATCH (t:Target) "
    if where_clause:
        cypher_statement += f"WHERE {where_clause} "

    return_clause = (
        f"t.`{grouping_key}`, count(t) AS movie_count"
        if grouping_key
        else "count(t) AS movie_count"
    )

    cypher_statement += f"RETURN {return_clause}"

    print(cypher_statement)  # Debugging output
    return graph.query(cypher_statement, params=params)

movie_count 함수는 선택적 필터와 grouping key를 기반으로 영화 수를 계산하는 Cypher 쿼리를 생성해요. 인수로 제공되는 값을 사용해서 필터 목록을 정의하는 것으로 시작하죠. 필터는 값이 None이 아닌 조건만 포함하여 Cypher 문에 지정된 필터링 조건을 적용하는 WHERE 절을 동적으로 작성하는 데 사용돼요.

그런 다음 제공된 grouping_key를 기준으로 그룹화하거나, 단순히 총 영화 수를 계산해서 Cypher 쿼리의 RETURN 절이 구성돼요. 마지막으로 함수는 쿼리를 실행하고 결과를 반환하죠.

필요에 따라 더 많은 인수와 더 많은 관련 논리를 사용하여 함수를 확장할 수 있지만, LLM이 함수를 올바르고 정확하게 호출할 수 있도록 명확하게 유지하는 것이 중요해요.

영화 목록 도구

다시 한번, 함수의 인수를 정의하는 것부터 시작해야 해요.

class MovieListInput(BaseModel):
    sort_by: str = Field(
        description="How to sort movies, can be one of either latest, rating",
        enum=["latest", "rating"],
    )
    k: Optional[int] = Field(description="Number of movies to return")
    description: Optional[str] = Field(description="Description of the movies")
    min_year: Optional[int] = Field(
        description="Minimum release year of the movies"
    )
    max_year: Optional[int] = Field(
        description="Maximum release year of the movies"
    )
    min_rating: Optional[float] = Field(description="Minimum imdb rating")

영화 개수 함수와 동일한 세 개의 필터를 유지하지만, description 인수를 추가해요. 이 인수를 사용하면 Vector Embedding 유사성 검색을 사용하여 줄거리를 기반으로 영화를 검색하고 나열할 수 있죠. 구조화된 도구와 필터를 사용한다고 해서 텍스트 임베딩과 벡터 검색 방법을 통합할 수 없다는 의미는 아니에요. 대부분의 경우 모든 영화를 반환하고 싶지 않기 때문에 기본값과 함께 선택적 k 입력을 포함해요. 또한 목록을 작성하기 위해 가장 관련성이 높은 영화만 반환하도록 영화를 정렬하려고 하는데요. 이 경우 등급이나 출시 연도별로 정렬할 수 있어요.

함수를 구현해 볼까요?

@tool("movie-list", args_schema=MovieListInput)
def movie_list(
    sort_by: str = "rating",
    k : int = 4,
    description: Optional[str] = None,
    min_year: Optional[int] = None,
    max_year: Optional[int] = None,
    min_rating: Optional[float] = None,
) -> List[Dict]:
    """List movies based on particular filters"""

    # Handle vector-only search when no prefiltering is applied
    if description and not min_year and not max_year and not min_rating:
        return neo4j_vector.similarity_search(description, k=k)

    filters = [
        ("t.year >= $min_year", min_year),
        ("t.year <= $max_year", max_year),
        ("t.imdbRating >= $min_rating", min_rating),
    ]

    # Create parameters dynamically from function arguments
    params = {
        key.split("$")[1]: value for key, value in filters if value is not None
    }
    where_clause = " AND ".join(
        [condition for condition, value in filters if value is not None]
    )

    cypher_statement = "MATCH (t:Target) "
    if where_clause:
        cypher_statement += f"WHERE {where_clause} "

    # Add the return clause with sorting
    cypher_statement += " RETURN t.title AS title, t.year AS year, t.imdbRating AS rating ORDER BY "

    # Handle sorting logic based on description or other criteria
    if description:
        cypher_statement += (
            "vector.similarity.cosine(t.embedding, $embedding) DESC "
        )
        params["embedding"] = embedding.embed_query(description)
    elif sort_by == "rating":
        cypher_statement += "t.imdbRating DESC "
    else:  # sort by latest year
        cypher_statement += "t.year DESC "

    cypher_statement += " LIMIT toInteger($limit)"
    params["limit"] = k or 4

    print(cypher_statement)  # Debugging output
    data = graph.query(cypher_statement, params=params)
    return data

이 함수는 설명, 연도 범위, 최소 등급, 정렬 기본 설정 등 다양한 필터를 기반으로 영화 목록을 검색해줘요. 만약 다른 필터 없이 설명만 있다면, 벡터 인덱스 유사성 검색을 통해 관련 영화를 찾죠. 추가 필터가 있다면, 함수는 출시 연도나 IMDb 등급 같은 기준으로 영화를 매칭하는 Cypher 쿼리를 만들고, 이를 설명 기반 유사성과 결합해요. 결과는 유사성 점수, IMDb 등급, 또는 연도별로 정렬되고, k개의 영화로 제한된답니다.

LangGraph 에이전트로 모든 것을 하나로 묶기

간단하게 React LangGraph를 사용하는 에이전트를 구현해 볼까요?

에이전트는 LLM과 도구 단계로 구성되어 있어요. 에이전트와 상호 작용할 때, 먼저 LLM에 전화해서 도구를 사용해야 하는지 결정하죠. 그런 다음 루프를 실행하게 될 거예요.

  1. 에이전트가 액션(예: 도구 호출)을 취하라고 하면, 도구를 실행하고 결과를 에이전트에게 다시 전달해요.
  2. 에이전트가 도구 실행을 요청하지 않으면 완료되고, 사용자에게 응답하는 거죠.

코드 구현은 정말 간단해요. 먼저 도구를 LLM에 바인딩하고 보조 단계를 정의해볼게요.

llm = ChatOpenAI(model='gpt-4-turbo')

tools = [movie_count, movie_list]
llm_with_tools = llm.bind_tools(tools)

# System message
sys_msg = SystemMessage(content="You are a helpful assistant tasked with finding and explaining relevant information about movies.")

# Node
def assistant(state: MessagesState):
   return {"messages": [llm_with_tools.invoke([sys_msg] + state["messages"])]}

다음으로 LangGraph 흐름을 정의해볼까요?

# Graph
builder = StateGraph(MessagesState)

# Define nodes: these do the work
builder.add_node("assistant", assistant)
builder.add_node("tools", ToolNode(tools))

# Define edges: these determine how the control flow moves
builder.add_edge(START, "assistant")
builder.add_conditional_edges(
    "assistant",
    # If the latest message (result) from assistant is a tool call -> tools_condition routes to tools
    # If the latest message (result) from assistant is a not a tool call -> tools_condition routes to END
    tools_condition,
)
builder.add_edge("tools", "assistant")
react_graph = builder.compile()

LangGraph에서 두 개의 Node를 정의하고 이를 조건부 Edge와 연결했어요. 도구가 호출되면 흐름이 도구로 전달되고, 그렇지 않으면 결과가 사용자에게 다시 전송되는 구조에요.

이제 에이전트를 테스트해 볼게요.

messages = [
    HumanMessage(
        content="What are the some movies about a girl meeting her hero?"
    )
]
messages = react_graph.invoke({"messages": messages})
for m in messages["messages"]:
    m.pretty_print()

결과:

첫 번째 단계에서 에이전트는 적절한 설명 변수와 함께 영화 목록 도구를 사용하도록 선택했어요. kvalue 5를 선택한 이유는 확실하지 않지만, 해당 숫자를 선호하는 것 같아요. 이 도구는 줄거리를 기반으로 가장 관련성이 높은 상위 5개 영화를 반환하고, LLM은 마지막에 사용자를 위해 간단히 요약해 준답니다.

ChatGPT에게 k 값 5를 좋아하는 이유를 묻는다면 다음과 같은 응답을 받을 거예요.

다음으로 메타데이터 필터링이 필요한 좀 더 복잡한 질문을 해볼게요.

messages = [
    HumanMessage(
        content="What are the movies from the 90s about a girl meeting her hero?"
    )
]
messages = react_graph.invoke({"messages": messages})
for m in messages["messages"]:
    m.pretty_print()

결과:

이번에는 1990년대 영화만 필터링하기 위해 추가 인수가 사용되었어요. 이 예는 사전 필터링 방식을 사용한 메타데이터 필터링의 일반적인 예시인데요. 생성된 Cypher 문은 먼저 개봉 연도를 필터링해서 영화의 범위를 좁힌답니다. 다음 부분에서 Cypher 문은 텍스트 임베딩과 벡터 유사성 검색을 사용해서 어린 소녀가 자신의 영웅을 만나는 영화를 찾고 있어요.

다양한 조건에 따라 영화 수를 계산해 볼까요?

messages = [
    HumanMessage(
        content="How many movies are from the 90s have the rating higher than 9.1?"
    )
]
messages = react_graph.invoke({"messages": messages})
for m in messages["messages"]:
    m.pretty_print()

결과:

계산을 위한 전용 도구를 사용하면 복잡성이 LLM에서 도구로 이동하고, LLM은 관련 기능 매개변수를 채우는 일만 담당하게 돼요. 이러한 작업 분리는 시스템을 더욱 효율적이고 강력하게 만들고 LLM 입력의 복잡성을 줄여주죠.

에이전트는 여러 도구를 순차적으로 또는 병렬로 호출할 수 있으니까, 좀 더 복잡한 것으로 테스트해 볼까요?

messages = [
    HumanMessage(
        content="How many were movies released per year made after the highest rated movie?"
    )
]
messages = react_graph.invoke({"messages": messages})
for m in messages["messages"]:
    m.pretty_print()

결과:

앞서 언급한 대로 에이전트는 여러 도구를 호출해서 질문에 답하는 데 필요한 모든 정보를 수집할 수 있어요. 이 예에서는 최고 등급 영화가 개봉된 시기를 식별하기 위해 최고 등급 영화를 나열하는 것으로 시작하죠. 해당 데이터가 있으면 영화 개수 도구를 호출해서 질문에 정의된 그룹화 키를 사용하여 지정된 연도 이후 개봉된 영화 수를 수집합니다.

요약

텍스트 임베딩은 구조화되지 않은 데이터를 검색하는 데는 탁월하지만, , , 그리고 와 같은 구조화된 작업에서는 부족해요. 이러한 작업에는 이러한 작업을 처리하는 데 필요한 정확성과 유연성을 제공하는 구조화된 데이터용으로 설계된 도구가 필요하죠. 중요한 점은 시스템의 도구 세트를 확장하면 더 광범위한 사용자 쿼리를 처리할 수 있어 애플리케이션이 더욱 강력하고 다용도로 사용될 수 있다는 거예요. 구조화된 데이터 접근 방식과 구조화되지 않은 텍스트 검색 기술을 결합하면 보다 정확하고 관련성 높은 응답을 제공할 수 있으며, 궁극적으로 RAG 애플리케이션의 사용자 경험을 향상시킬 수 있어요.

언제나 그렇듯이 코드는 다음에서 사용할 수 있습니다. .

  • rag

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

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

반응형
반응형
  • GraphQL

지금은 강연을 준비하고 있어요. CityJS 아테네에서 프런트엔드 및 풀스택 개발자를 위한 GenAI(생성 AI)에 대해 이야기할 예정이거든요. GenAI가 GraphQL API 맥락에 어떻게 어울릴지 궁금했어요.

기본적으로 이걸 잘하는 회사 중 하나는 오픈 소스 벡터 데이터베이스인 Weaviate인데요. Weaviate 문서를 훑어보면서 생성 검색 문서에서 사용자 지정 프롬프트로 호출할 수 있는 생성 확인자를 언급한 걸 봤어요. 제가 알기로는 이 기능은 모든 컬렉션에서 사용할 수 있다니, 정말 좋은 소식이죠?

생성 확인자는 프롬프트를 받아서, 쿼리를 통해 검색된 값을 프롬프트에 전달하는 방식이에요. 이건 Retrieval-Augmented Generation(RAG)에 대한 유연한 접근 방식이라고 할 수 있죠.

제 머릿속에 바로 떠오른 생각은, 이걸 어떻게 가능하게 만들 수 있을까 하는 거였어요. @neo4j/graphql 프로젝트에서 말이죠!

GraphQL의 GenAI

물론 LLM 공급자의 엔드포인트에 프롬프트를 보내서 라이브러리 없이도 이 작업을 수행할 수 있지만, 저는 바로 Langchain.js를 사용하는 게 좋겠다고 생각했어요. 이렇게 하면 애플리케이션이 미래에도 계속 대응할 수 있고, 나중에 사전 구축된 도구와 에이전트에 API가 공개될 테니까요.

그러던 와중에 놀랍게도 Langchain이 GraphQL API를 도구로 지원한다는 걸 알게 됐어요! LLM을 통해 GraphQL 엔드포인트를 쿼리할 수 있게 된 거죠. 하지만 해당 GraphQL 엔드포인트 내부에서 LLM을 사용하는 예는 없었어요.

기본 @neo4j/graphql 프로젝트 생성

GraphAcademy의 Neo4j 및 GraphQL 과정 소개는 처음부터 시작하거나 복제 또는 포크할 수 있는 훌륭한 출발점이에요. 코드를 작성하기 위한 이 저장소의 기본 분기도 참고해보세요.

GraphQL 프로젝트 설정이 이미 있다면, LangChain.js 설치 단계로 바로 넘어가도 좋아요.

영화 추천 데이터 세트를 예시로 해서 간단한 예제를 만들어볼게요. Neo4j Sandbox에서 직접 영화 추천 데이터 세트를 실행하거나, GraphAcademy의 Neo4j 및 GraphQL 과정 소개를 참고하면 좋을 거예요.

새 폴더를 만들고 npm init 명령어를 실행해서 새 프로젝트를 시작해볼까요?

mkdir neo4j-graphql-genai && cd $_
npm init es6 --yes

neo4j/graphql과 Apollo 서버 dependencies를 설치하려면 다음 명령어를 실행하세요.

npm install @neo4j/graphql graphql neo4j-driver @apollo/server dotenv

다음으로 index.js 파일에서 데이터 세트의 MovieActor nodes를 설명하는 기본적인 type definitions를 정의할 거예요. 여기서는 너무 자세하게 다루진 않을게요. 간단한 정의만으로도 충분하답니다.

import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { Neo4jGraphQL } from "@neo4j/graphql";
import neo4j from "neo4j-driver";

const typeDefs = `#graphql
    type Movie {
        title: String!
        plot: String!
        actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN)
    }

    type Actor {
        name: String
        movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT)
    }
`;

Neo4j JavaScript driver instance도 지정해야 해요. Sandbox instance를 만들었다면, instance 세부 정보를 확장해서 연결 정보를 확인할 수 있을 거예요.

Example Neo4j Sandbox credentials
이 credentials는 더 이상 작동하지 않아요. instance가 사라진 지 오래됐거든요!

Bolt URL, 사용자 이름, 비밀번호를 복사해서 프로젝트 루트의 .env 파일에 붙여넣으세요.

NEO4J_URI="bolt+s://xxx.xxx.xxx.xxx:7687"
NEO4J_USERNAME="neo4j"
NEO4J_PASSWORD="three-word-password"

그 다음 dotenvconfig() 함수를 사용해서 이 credentials를 로드하고, 이걸 사용해서 새 driver instance를 만들 거예요.

import { config } from "dotenv"

// Load .env file
config()

// Create driver instance
const driver = neo4j.driver(
  process.env.NEO4J_URI,
  neo4j.auth.basic(
    process.env.NEO4J_USERNAME,
    process.env.NEO4J_PASSWORD
  )
);

Type definitions와 driver를 사용해서 새로운 Neo4jGraphQL instance를 생성할 수 있어요.

// Define schema
const neoSchema = new Neo4jGraphQL({
    typeDefs,
    driver,
});

// Get the schema
const schema = await neoSchema.getSchema()

이제 schema를 사용해서 새 ApolloServer instance를 만들 거예요. 해당 서버를 @apollo/server/standalone에서 가져온 startStandaloneServer 함수에 전달해서 4000번 포트에서 listen하는 새 서버를 만들 수 있죠.

// Create server
const server = new ApolloServer({
  schema,
});

// Listen
const { url } = await startStandaloneServer(server, {
  context: async ({ req }) => ({ req }),
  listen: { port: 4000 },
});

console.log(`🚀 Server ready at ${url}`);

node 명령어를 실행해서 서버를 시작해볼까요?

node index.js
query ExampleQuery($where: MovieWhere, $prompt: String!) { movies (where: $where) { title plot generate(prompt: $prompt) { text } } }

localhost:4000

을 로드하면 Apollo Server UI가 열리고, 여기서 type definitions에 따라 영화와 배우를 검색할 수 있어요.

자, 이제 재밌는 부분이에요!

LangChain.js 설치

이 예시에서는 OpenAI와 함께 Langchain을 사용하고 있지만, 80개 이상의 지원되는 LLM 중 하나를 사용할 수도 있어요. 글을 쓰는 시점에 원하는 걸 선택하면 돼요.

Langchain 및 OpenAI에 대한 종속성을 설치하려면 다음 명령을 실행하세요.

npm i --save langchain @langchain/openai

OpenAI와 함께 LangChain을 사용하려면 platform.openai.com에서 API 키를 생성해야 해요. 키가 있다면 .env에 추가하세요.

OPENAI_API_KEY=sk-...

체인 만들기

다음으로는 을 만들어볼게요. LangChain에서 체인은 목표를 달성하기 위해 거치는 일련의 작업들을 말해요.

PromptTemplate은 특정 항목에 대한 placeholder가 있는 재사용 가능한 prompt를 만드는 데 사용돼요. LLM이 따라야 하는 지침 역할을 하는 거죠.

LangChain이 어떻게 작동하는지, 특히 Neo4j에서 어떻게 작동하는지 배우고 싶다면, Python 중심의 Neo4j 및 LLM 기초 과정과 좀 더 발전된 TypeScript를 사용하여 Neo4j 지원 Chatbot 구축 강의를 참고해보세요.

영화 데이터 세트이므로 LLM에게 냉소적인 영화 리뷰를 작성하도록 지시하는 템플릿을 만들어 볼게요. prompt는 영화 제목과 줄거리를 가져와 query의 일부로 입력된 별점을 기반으로 리뷰를 작성해요.

const prompt = PromptTemplate.fromTemplate(`<br/>
  You are a sarcastic movie reviewer creating tongue-in-cheek<br/>
  reviews of movies.  Create a {stars} star movie review<br/>
  for {title}.<br/><br/>
  The plot of the movie is: {plot}.<br/><br/>
  Remember to use at least one pun or to include a dad joke.<br/>
`)<br/>
const llm = new ChatOpenAI({<br/>
  openAIApiKey: process.env.OPENAI_API_KEY,<br/>
})<br/><br/>
const chain = RunnableSequence.from([<br/>
  prompt,<br/>
  llm,<br/>
  new StringOutputParser()<br/>
])

체인에서 .invoke 메소드를 호출해서 이 체인을 테스트할 수 있어요.

const text = await chain.invoke({<br/>
  title: 'Toy Story',<br/>
  plot: "A cowboy doll is profoundly threatened and jealous when a new spaceman figure supplants him as top toy in a boy's room.",<br/>
  stars: 1<br/>
})

LLM은 끔찍한 1⭐️ 리뷰를 생성하네요…

음, 음, 음, 토이 스토리, 이 평범함의 걸작을 어디서부터 시작해야 할까요? 이 영화는 너무 믿기지 않아서 마치 불량품으로 가득 찬 장난감 상자에서 줄거리를 꺼낸 것과 같습니다. 우주인 장난감을 질투하는 카우보이 인형? 별을 향해 다가갔다가 은하계를 놓치는 것에 대해 이야기해 보세요.

GraphQL 리졸버에서 LLM 호출

GraphQL query에서 이 체인을 호출하려면 typeDefs를 수정해서 @customResolver 지시문으로 주석이 달린 Movie 유형에 새 필드를 추가해야 해요. 또한 이 필드에는 Int가 될 필수 인수인 star가 하나 필요해요.

type Movie  {<br/>
  title: String!<br/>
  plot: String!<br/>
  generateReview(stars: Int!): GeneratedResponse! @customResolver<br/>
}

생성된 응답 output은 자체 유형으로 정의되어야 해요. 이 경우 text property를 반환해서 나중에 프로세스에 추가 필드를 추가할 수 있는 가능성을 추가하는 거죠.

type GeneratedResponse {<br/>
  text: String!<br/>
}

다음으로 실행하려면 맞춤 리졸버 기능이 필요해요.

const generateReview = async (source, args) => {
  // <1> Define the prompt template
  const prompt = PromptTemplate.fromTemplate(`
    You are a sarcastic movie reviewer creating tongue-in-cheek
    reviews of movies.  Create a {stars} star movie review
    for {title}.

    The plot of the movie is: {plot}.

    Remember to use at least one pun or to include a dad joke.
  `)

  // <2> Create an LLM instance
  const llm = new ChatOpenAI({
    openAIApiKey: process.env.OPENAI_API_KEY,
  })

  // <3> Create a chain to invoke
  const chain = RunnableSequence.from([
    prompt,
    llm,
    new StringOutputParser()
  ])

  // <4> Invoke the chain with the source and args
  const text = await chain.invoke({ ...source, ...args })

  // Return an GeneratedResponse
  return { text }
}

이 함수는 두 가지 인수가 필요해요.

  1. source — `Query`에서 요청된 영화의 속성을 가지고 있어요.
  2. args — `generateReview` 리졸버를 호출하는 데 사용되는 인수인데, `{stars: number}`에 해당해야 해요.

마지막으로, `neoSchema`를 정의할 때 해당 리졸버를 `resolvers` 객체에 정의해야 해요. 각 키는 `type`에 응답하고, 후속 객체는 개별 `resolver`를 참조하죠.

// Define schema
const neoSchema = new Neo4jGraphQL({
    typeDefs,
    driver,
    resolvers: {
      Movie: {
        generateReview,
      },
    }
});

그게 전부예요! 모든 것이 계획대로 진행되었다면 이제 별점을 기준으로 리뷰를 생성할 수 있어요.

Query: query WriteActorIntroduction($where: ActorWhere, $prompt: String!) { actors (where: $where) { name actedInMovies { title plot } generate(prompt: $prompt) { text } } }.

일반 생성 리졸버

이 접근 방식은 애플리케이션에서 LLM이 사용되는 방식을 제한하려는 경우에 적합한데, 이게 바로 여러분이 원하시는 걸 수도 있죠.

하지만 이 게시물 시작 부분에서 언급한 예시처럼, 사용자가 자신만의 `prompt`를 보낼 수 있었어요. 사용자 정의 리졸버에 `$prompt` 인수를 추가해서 이걸 활성화하도록 코드를 수정할 수 있어요.

LLM 생성을 허용해야 하는 모든 `type`에 대한 인터페이스를 정의하는 것부터 시작할 수 있어요.

interface CanGenerate {
  generate(prompt: String!): GeneratedResponse!
}

이 인터페이스는 동일한 `GeneratedResponse` 출력을 사용해서 생성이 포함된 텍스트 필드를 반환해요.

다음으로, 생성 리졸버를 구현하기 위해 생성(이 경우 `Movie`)을 허용해야 하는 `type`을 수정해요. 필드에는 `@customResolver` 지시문으로 주석을 달아야 하죠.

type Movie implements CanGenerate {
  title: String!
  plot: String!
  generateReview(stars: Int!): GeneratedResponse! @customResolver
  generate(prompt: String!): GeneratedResponse! @customResolver
}

type Actor implements CanGenerate {
  name: String!
  born: Date
  actedInMovies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT)
  generate(prompt: String!): GeneratedResponse! @customResolver
}

사용자 지정 리졸버 함수는 `prompt` 값이 새 `PromptTemplate` 객체를 생성하는 데 사용되는 `args` 인수를 통과한다는 점을 제외하면 위와 유사해요.

const generate = async (source, args) => {
  // Create prompt from arguments
  const prompt = PromptTemplate.fromTemplate(args.prompt)

  const model = new ChatOpenAI({
    openAIApiKey: process.env.OPENAI_API_KEY,
    model: args.model || 'gpt-4'
  })

  const output = new StringOutputParser()

  const chain = RunnableSequence.from([
    prompt,
    model,
    output,
  ])

  // Stringify any objects
  const input = Object.fromEntries(
      Object.entries({...source, ...args}
    )
    .map(([ key, value ]) => [
      key,
      typeof value === 'object' ? JSON.stringify(value) : value
    ]))

  // Invoke the chain
  const res = await chain.invoke(input)

  return { text: res }
}

그런 다음 함수는 `Movie` 및 `Actor` `type`에 대한 맞춤 리졸버로 정의되어야 해요.

const resolvers = {
  Movie: {
    generateReview,
    generate,
  },
  Actor: {
    generate,
  },
};

// Define schema
const neoSchema = new Neo4jGraphQL({
    typeDefs,
    driver,
    resolvers,
});

잘 작동하는지 확인해 볼까요?

Query: query WriteActorIntroduction($where: ActorWhere, $prompt: String!) { actors (where: $where) { name actedInMovies { title plot } generate(prompt: $prompt) { text } } }
LLM은 영화 목록을 전달받은 후 사용자의 메시지를 기반으로 채팅 쇼 스타일의 소개를 생성합니다.

자동 상속

제가 하고 싶었지만 어려움을 겪었던 한 가지는 CanGenerate 인터페이스를 상속한 모든 타입을 감지하고 프로그래밍 방식으로 확인자를 적용하는 것이었어요. 하지만 graphql 라이브러리와 2시간 동안 씨름한 끝에 매핑 기능을 사용하기로 결정했죠.

// Assign resolver to many types
const withGenerateResolver = (types = [], existing = {}) =>
  Object.fromEntries(
    types.map(type => [
      type,
      { ...existing[type], generate }
    ])
  )

const resolvers = resolvers: withGenerateResolver(
  ['Movie', 'Actor'], 
  {
    Movie: { generateReview, }
  }
)

그래도 가능하다고 확신해요. 아이디어가 있으시면 언제든지 링크드인으로 문의해주세요.

결론

GraphQL은 데이터 검색에 대한 간단하고 유연한 접근 방식을 제공해서 맞춤형 요청이 가능하게 해줘요. LLM 생성을 지원하는 resolver를 추가하면 대규모로 동적 콘텐츠 생성 및 개인화가 가능해지죠.

좀 더 자세히 살펴보고 싶으시다면, 코드는 GitHub에서 확인할 수 있어요.

Knowledge Graph가 LLM이 환각을 피하는 데 어떻게 도움이 되는지 자세히 알아보려면 Neo4j GraphAcademy의 무료 LLM 과정을 확인해 보세요.

GraphAcademy에서 Neo4j 및 LLM 기초 과정을 수강하세요.


  • API
  • 생성 AI 솔루션
  • GraphQL
  • 랭체인JS
  • neo4j-graphql
  • Retrieval-Augmented Generation

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

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

반응형
반응형

GraphRAG 정확도를 높이기 위한 엔터티 중복 제거 및 사용자 지정 검색 방법

배경

Property Graph Index는 LlamaIndex에 정말 멋진 기능 추가이자, 기존의 Knowledge Graph 통합을 한 단계 업그레이드한 버전이라고 할 수 있어요. 이제 데이터 표현 방식이 조금 달라졌는데요. 이전에는 그래프가 트리플 형태로 표현되었다면, 이제는 label이 붙은 `Node`와 선택적으로 `Node` 속성을 가진 제대로 된 Property Graph 통합을 제공하죠.

Property Graph 모델의 예

각 `Node`에는 개인, 조직, 프로젝트 또는 부서와 같은 유형을 나타내는 label이 지정돼요. 그리고 `Node`와 `Relationship`은 이 예시에서 보이는 것처럼 생년월일, 프로젝트 시작 및 종료 날짜 등과 같은 관련 세부 정보를 `Node` 속성으로 저장할 수도 있답니다.

Property Graph Index는 모듈식으로 설계되었기 때문에, 하나 이상의 (맞춤형) Knowledge Graph 생성자와 검색기를 사용할 수 있어요. 덕분에 첫 번째 Knowledge Graph를 구축하거나 필요에 따라 구현을 맞춤 설정하는 데 아주 유용한 도구가 될 수 있죠.

Property Graph 워크플로

위 이미지는 그래프 생성자에 전달되는 문서부터 시작해서 LlamaIndex 내의 Property Graph 통합을 보여주고 있어요. 이러한 생성자는 구조화된 정보를 추출한 다음 Knowledge Graph에 저장하는 모듈식 구성 요소인데요. 다양한 데이터 소스 또는 추출 요구 사항에 맞춰 시스템의 유연성을 높여주는 다양한 모듈 또는 사용자 정의 모듈을 사용해서 그래프를 만들 수 있다는 점이 매력적이죠.

그다음 그래프 검색기는 Knowledge Graph에 접근해서 데이터를 검색해요. 이 단계 역시 모듈식이기 때문에 그래프 내에서 특정 유형의 데이터 또는 `Relationship`을 쿼리하도록 설계된 여러 검색기 또는 맞춤 솔루션을 사용할 수 있답니다.

LLM은 검색된 데이터를 사용해서 결과 또는 통찰력을 나타내는 답변을 생성해요. 이 흐름을 보면 전체 기능을 향상시키거나 특정 요구 사항에 맞게 조정하기 위해 각 구성 요소를 독립적으로 수정하거나 교체할 수 있는 적응성과 확장성이 뛰어난 시스템이라는 것을 알 수 있죠?

이번 블로그 포스팅에서는 다음 내용들을 알아볼 거예요.

  1. 스키마 기반 추출을 사용해서 Knowledge Graph 구성하기
  2. 텍스트 Vector Embedding과 단어 유사성 기술을 결합해서 엔터티 중복 제거 수행하기
  3. 사용자 정의 그래프 검색기 디자인하기
  4. 사용자 정의 검색기를 사용해서 질문 답변 흐름 구현하기

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

환경 설정

Neo4j를 기본 그래프 저장소로 사용할 건데요. 시작하는 가장 쉬운 방법은 무료 인스턴스를 사용하는 거예요. Neo4j AuraDB는 Neo4j 데이터베이스의 클라우드 인스턴스를 제공하거든요. 아니면 Neo4j Desktop 애플리케이션을 다운로드해서 로컬 데이터베이스 인스턴스를 설정할 수도 있어요:

from llama_index.graph_stores.neo4j import Neo4jPGStore

username="neo4j"
password="stump-inlet-student"
url="bolt://52.201.215.224:7687"

graph_store = Neo4jPGStore(
    username=username,
    password=password,
    url=url,
)

그리고 OpenAI API 키도 필요하겠죠?

import os

os.environ["OPENAI_API_KEY"] = "sk-"

데이터세트

저희는 Diffbot의 샘플 뉴스 기사 데이터세트를 사용할 건데요, 제가 에 올려서 더 쉽게 접근할 수 있도록 해놨어요.

데이터세트의 샘플 레코드

Property Graph Index는 문서와 함께 작동하므로 뉴스의 텍스트를 LlamaIndex 문서로 래핑해야 해요.

import pandas as pd
from llama_index.core import Document

news = pd.read_csv(
  "https://raw.githubusercontent.com/tomasonjo/blog-datasets/main/news_articles.csv")
documents = [Document(text=f"{row['title']}: {row['text']}") for i, row in news.iterrows()]

그래프 구성

LlamaIndex는 다양한 기능을 제공하는데요, 기본 그래프 생성자를 한번 살펴보세요. 이 예에서는 SchemaLLMPath 추출기를 사용해서 문서에서 추출하려는 그래프 구조의 스키마를 정의할 수 있어요.

스키마 기반 그래프 구조 추출

LLM이 추출할 `Node` 유형과 `Relationship`을 정의하는 것부터 시작해볼게요.

entities = Literal["PERSON", "LOCATION", "ORGANIZATION", "PRODUCT", "EVENT"]
relations = Literal[
    "SUPPLIER_OF",
    "COMPETITOR",
    "PARTNERSHIP",
    "ACQUISITION",
    "WORKS_AT",
    "SUBSIDIARY",
    "BOARD_MEMBER",
    "CEO",
    "PROVIDES",
    "HAS_EVENT",
    "IN_LOCATION",
]

보시다시피 우리는 사람과 조직을 중심으로 그래프 추출에 중점을 두고 있어요. 다음으로 각 `Node` 라벨과 연결된 `Relationship`을 지정할게요.

# define which entities can have which relations
validation_schema = {
    "Person": ["WORKS_AT", "BOARD_MEMBER", "CEO", "HAS_EVENT"],
    "Organization": [
        "SUPPLIER_OF",
        "COMPETITOR",
        "PARTNERSHIP",
        "ACQUISITION",
        "WORKS_AT",
        "SUBSIDIARY",
        "BOARD_MEMBER",
        "CEO",
        "PROVIDES",
        "HAS_EVENT",
        "IN_LOCATION",
    ],
    "Product": ["PROVIDES"],
    "Event": ["HAS_EVENT", "IN_LOCATION"],
    "Location": ["HAPPENED_AT", "IN_LOCATION"],
}

예를 들어, 개인은 다음과 같은 `Relationship`을 가질 수 있어요.

  • WORKS_AT
  • BOARD_MEMBER
  • CEO
  • HAS_EVENT

스키마는 약간 더 모호하고 LLM이 다양한 유형의 정보를 캡처할 수 있게 해주는 EVENT `Node` 레이블을 제외하고 매우 구체적이에요.

이제 그래프 스키마를 정의했으니 이를 그래프 스키마에 입력할 수 있어요. SchemaLLMPathExtractor를 사용해서 그래프를 구성해볼게요.

from llama_index.core import PropertyGraphIndex

kg_extractor = SchemaLLMPathExtractor(
    llm=llm,
    possible_entities=entities,
    possible_relations=relations,
    kg_validation_schema=validation_schema,
    # if false, allows for values outside of the schema
    # useful for using the schema as a suggestion
    strict=True,
)

NUMBER_OF_ARTICLES = 250

index = PropertyGraphIndex.from_documents(
    documents[:NUMBER_OF_ARTICLES],
    kg_extractors=[kg_extractor],
    llm=llm,
    embed_model=embed_model,
    property_graph_store=graph_store,
    show_progress=True,
)

이 코드는 250개의 뉴스 기사에서 그래프 정보를 추출하지만 원하는 대로 숫자를 조정할 수 있어요. 총 2,500개의 기사가 있답니다.

GPT-4o를 사용하면 250개의 기사를 추출하는 데 약 7분이 소요돼요. 하지만 병렬화를 통해 프로세스를 가속화할 수 있죠. num_workers 매개변수를 활용하면 돼요.

저장된 내용을 검사하기 위해 작은 하위 그래프를 시각화할 수 있어요.

텍스트 청크는 파란색이고 엔터티 `Node`는 다른 색이에요.

구성된 그래프에는 텍스트 청크(파란색)와 텍스트 및 `Vector Embedding`이 모두 포함돼요. 텍스트 청크에서 엔터티가 언급된 경우 텍스트 청크와 엔터티 사이에 MENTIONS `Relationship`이 있어요. 또한 엔터티는 다른 엔터티와 `Relationship`을 가질 수도 있답니다.

엔터티 중복 제거

엔터티 중복 제거, 즉 deduplication은 중요하지만 그래프 구성에서 종종 간과되는 단계에요. 쉽게 말해, 이는 단일 엔터티를 나타내는 여러 개의 Nodes를 찾아서, 더 나은 그래프 구조적 무결성을 위해 하나의 Node로 합치는 정리 작업이죠.

예를 들어, 구성된 그래프에서 병합할 수 있는 몇 가지 예시를 찾아봤어요.

잠재적 엔터티 중복

잠재적인 중복을 찾기 위해 텍스트 임베딩 유사성과 단어 거리의 조합을 사용하는데요. 먼저 그래프의 항목에 대한 Vector Index를 정의해야겠죠?

graph_store.structured_query("""
CREATE VECTOR INDEX entity IF NOT EXISTS
FOR (m:`__Entity__`)
ON m.embedding
OPTIONS {indexConfig: {
 `vector.dimensions`: 1536,
 `vector.similarity_function`: 'cosine'
}}
""")

다음 Cypher 쿼리는 중복 항목을 찾는 쿼리인데, 꽤 복잡해요. 저와 Michael Hunger, Eric Monk가 완성하는 데 몇 시간이 걸렸답니다.

 similarity_threshold = 0.9
word_edit_distance = 5
data = graph_store.structured_query("""
MATCH (e:__Entity__)
CALL {
  WITH e
  CALL db.index.vector.queryNodes('entity', 10, e.embedding)
  YIELD node, score
  WITH node, score
  WHERE score > toFLoat($cutoff)
      AND (toLower(node.name) CONTAINS toLower(e.name) OR toLower(e.name) CONTAINS toLower(node.name)
           OR apoc.text.distance(toLower(node.name), toLower(e.name)) < $distance)
      AND labels(e) = labels(node)
  WITH node, score
  ORDER BY node.name
  RETURN collect(node) AS nodes
}
WITH distinct nodes
WHERE size(nodes) > 1
WITH collect([n in nodes | n.name]) AS results
UNWIND range(0, size(results)-1, 1) as index
WITH results, index, results[index] as result
WITH apoc.coll.sort(reduce(acc = result, index2 IN range(0, size(results)-1, 1) |
        CASE WHEN index <> index2 AND
            size(apoc.coll.intersection(acc, results[index2])) > 0
            THEN apoc.coll.union(acc, results[index2])
            ELSE acc
        END
)) as combinedResult
WITH distinct(combinedResult) as combinedResult
// extra filtering
WITH collect(combinedResult) as allCombinedResults
UNWIND range(0, size(allCombinedResults)-1, 1) as combinedResultIndex
WITH allCombinedResults[combinedResultIndex] as combinedResult, combinedResultIndex, allCombinedResults
WHERE NOT any(x IN range(0,size(allCombinedResults)-1,1) 
    WHERE x <> combinedResultIndex
    AND apoc.coll.containsAll(allCombinedResults[x], combinedResult)
)
RETURN combinedResult  
""", param_map={'cutoff': similarity_threshold, 'distance': word_edit_distance})
for row in data:
    print(row)
 

너무 깊게 들어가진 않을게요. 텍스트 임베딩과 단어 거리의 조합을 사용해서 그래프에서 잠재적인 중복 항목을 찾는다는 것만 알아두세요. similarity_thresholdword_distance를 튜닝해서 너무 많은 오탐 없이 중복 항목을 최대한 많이 찾아내는 최적의 조합을 찾아야 해요. 안타깝게도 엔터티 명확성은 완벽한 해결책이 없는 어려운 문제랍니다. 이 접근 방식을 사용하면 꽤 괜찮은 결과를 얻을 수 있지만, 오탐지도 있다는 점 잊지 마세요.

['1963 AFL Draft', '1963 NFL Draft']
['June 14, 2023', 'June 15 2023']
['BTC Halving', 'BTC Halving 2016', 'BTC Halving 2020', 'BTC Halving 2024', 'Bitcoin Halving', 'Bitcoin Halving 2024']

다이얼을 조정하고 중복 Nodes를 병합하기 전에 수동으로 예외를 추가하는 건 여러분의 몫이에요.

커스텀 리트리버 구현

우리는 뉴스 데이터 세트를 기반으로 Knowledge Graph를 구축했는데요. 이제 리트리버 옵션을 한번 살펴볼까요? 현재는 기존 리트리버 4개를 사용할 수 있어요.

  • LLMSynonymRetriever는 쿼리를 받아서 키워드와 동의어를 생성해서 Nodes(그리고 해당 Nodes에 연결된 경로)를 검색하는 역할을 해요.
  • VectorContextRetriever는 Vector Embedding 유사성을 기준으로 node를 검색하고, 해당 node에 연결된 path를 가져와요.
  • TextToCypherRetriever는 Graph Database 스키마, query, 프롬프트 템플릿을 사용해서 Cypher query를 생성하고 실행하죠.
  • CypherTemplateRetriever는 LLM이 Cypher 구문을 생성할 수 있는 자유도를 주는 대신, Cypher 템플릿을 제공하고 LLM이 매개변수를 채우도록 해요.

사용자 정의 retriever를 구현하는 것도 간단한데요, 여기서 그걸 한번 해볼 거예요. 사용자 정의 retriever는 먼저 입력 query에서 entity를 식별한 다음, 식별된 각 entity에 대해 별도로 VectorContextRetriever를 실행해요.

먼저 entity 추출 모델을 정의하고, 다음과 같은 메시지를 표시해볼게요.

 from pydantic import BaseModel
from typing import Optional, List


class Entities(BaseModel):
    """List of named entities in the text such as names of people, organizations, concepts, and locations"""
    names: Optional[List[str]]


prompt_template_entities = """
Extract all named entities such as names of people, organizations, concepts, and locations
from the following text:
{text}
"""

이제 사용자 정의 retriever 구현을 진행해볼까요?

from typing import Any, Optional

from llama_index.core.embeddings import BaseEmbedding
from llama_index.core.retrievers import CustomPGRetriever, VectorContextRetriever
from llama_index.core.vector_stores.types import VectorStore
from llama_index.program.openai import OpenAIPydanticProgram


class MyCustomRetriever(CustomPGRetriever):
    """Custom retriever with entity detection."""
    def init(
        self,
        ## vector context retriever params
        embed_model: Optional[BaseEmbedding] = None,
        vector_store: Optional[VectorStore] = None,
        similarity_top_k: int = 4,
        path_depth: int = 1,
        include_text: bool = True,
        **kwargs: Any,
    ) -> None:
        """Uses any kwargs passed in from class constructor."""
        self.entity_extraction = OpenAIPydanticProgram.from_defaults(
            output_cls=Entities, prompt_template_str=prompt_template_entities
        )
        self.vector_retriever = VectorContextRetriever(
            self.graph_store,
            include_text=self.include_text,
            embed_model=embed_model,
            similarity_top_k=similarity_top_k,
            path_depth=path_depth,
        )

    def custom_retrieve(self, query_str: str) -> str:
        """Define custom retriever with entity detection.

        Could return `str`, `TextNode`, `NodeWithScore`, or a list of those.
        """
        entities = self.entity_extraction(text=query_str).names
        result_nodes = []
        if entities:
            print(f"Detected entities: {entities}")
            for entity in entities:
                result_nodes.extend(self.vector_retriever.retrieve(entity))
        else:
            result_nodes.extend(self.vector_retriever.retrieve(query_str))
        final_text = "nn".join(
            [n.get_content(metadata_mode="llm") for n in result_nodes]
        )
        return final_text

MyCustomRetriever 클래스에는 두 가지 method만 있어요. init method를 사용해서 retriever에서 사용할 함수나 클래스를 instance화할 수 있죠. 이 예에서는 Vector Embedding 컨텍스트 retriever와 함께 entity 감지 OpenAI 프로그램을 instance화해요.

custom_retrieve method는 검색 중에 호출돼요. 사용자 정의 retriever 구현에서는 먼저 텍스트에서 관련 entity를 식별하죠. Entity가 발견되면 각 entity에 대해 Vector Embedding 컨텍스트 retriever를 반복하고 실행해요. 반면에 entity가 식별되지 않으면 전체 입력을 Vector Embedding 컨텍스트 retriever에 전달하고요.

보시다시피, 기존 retriever를 통합하거나 처음부터 시작해서 사용 사례에 맞게 retriever를 쉽게 사용자 정의할 수 있어요. structured_query Graph Database의 method를 사용하면 된답니다.

질의응답 흐름

예제 질문에 답하기 위해 사용자 정의 검색기를 사용하여 마무리해 볼게요. 우리는 리트리버를 RetrieverQueryEngine로 만들 거예요:

from llama_index.core.query_engine import RetrieverQueryEngine

custom_sub_retriever = MyCustomRetriever(
    index.property_graph_store,
    include_text=True,
    vector_store=index.vector_store,
    embed_model=embed_model
)

query_engine = RetrieverQueryEngine.from_args(
    index.as_retriever(sub_retrievers=[custom_sub_retriever]), llm=llm
)

테스트해 볼까요?

response = query_engine.query(
    "What do you know about Maliek Collins or Darragh O’Brien?"
)
print(str(response))
# Detected entities: ['Maliek Collins', "Darragh O'Brien"]
# Maliek Collins is a defensive tackle who has played for the Dallas Cowboys, Las Vegas Raiders, and Houston Texans. Recently, he signed a two-year contract extension with the Houston Texans worth $23 million, including a $20 million guarantee. This new deal represents a raise from his previous contract, where he earned $17 million with $8.5 million guaranteed. Collins is expected to be a key piece in the Texans' defensive line and fit well into their 4-3 alignment.
# Darragh O’Brien is the Minister for Housing and has been involved in the State’s industrial relations process and the Government. He was recently involved in a debate in the Dáil regarding the pay and working conditions of retained firefighters, which led to a heated exchange and almost resulted in the suspension of the session. O’Brien expressed confidence that the dispute could be resolved and encouraged unions to re-engage with the industrial relations process.

요약

이번 포스팅에서는 GraphRAG 정확도를 높이기 위해 엔터티 중복 제거 구현 및 사용자 지정 검색 방법 설계에 중점을 두고 LlamaIndex 내에서 Property Graph Index를 사용자 지정하는 과정을 살펴봤어요. Property Graph Index를 사용하면 다양한 그래프 생성자와 검색기를 사용하여 필요에 맞게 구현을 맞춤화할 수 있는 모듈식의 유연한 접근 방식이 가능하죠. 첫 번째 Knowledge Graph를 구축하든 고유한 데이터 세트에 맞게 최적화하든, 이러한 사용자 정의 가능한 구성 요소는 강력한 툴킷을 제공해 줄 거예요. Knowledge Graph 프로젝트를 어떻게 향상시킬 수 있는지 알아보기 위해 Property Graph Index 통합을 테스트해 보시는 걸 추천드려요.

언제나 그렇듯이 코드는 다음에서 사용할 수 있습니다..


  • GraphRAG
  • LlamaIndex
  • Property Graph

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

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

반응형
반응형
  • Machine Learning

편집자 주: 이건 Hilary Mason님이 발표했던 프레젠테이션 내용을 옮긴 글이에요. GraphConnect New York, 2018년 10월에 있었죠.

프레젠테이션 요약

세상을 설명하기 위해 사용하는 비유는 기술을 구축하는 방식과 밀접하게 관련되어 있는데, 특히 인공지능과 Machine Learning 분야에서 그렇다는 점이 흥미로워요. 주변에서 개발된 기술 사례를 한번 살펴볼까요? 미국 인구 조사는 열차 차장이 승객 수를 세는 방식에서 아이디어를 얻었다고 해요. 이 간단한 기술이 결국 현대 컴퓨팅의 기초가 되었다니 놀랍죠?

오늘날 AI가 직면한 과제 중 하나는 새로운 기술 역량에 대한 전반적인 이해 부족과 그에 대한 두려움이에요. 매일 뉴스 헤드라인에 등장하는 걸 보면 알 수 있죠. 이 분야 전문가로서 우리가 해야 할 일은 사람들이 AI와 Machine Learning이 실제로 무엇인지 이해하도록 돕는 거라고 생각해요. 어디에서 왔는지, 그리고 이미 일상 생활에서 어떻게 성공적으로 사용되고 있는지 (예: Google 지도, 이미지에 텍스트 할당) 알려주는 거죠. 이 기술이 더 이상 놀랍지도, 두렵지도 않게 느껴질 정도로 우리 삶에 녹아들면 성공이라고 할 수 있을 거예요.

오늘날 AI 및 Machine Learning과 관련된 가장 큰 과제 중 하나는 기술이 우리의 역량을 따라잡을 수 있도록 생각하는 방식을 발전시키는 거라고 생각해요. 미래를 예측할 수 있는 방법론을 생각해내는 것도 중요하죠. 현재 사용 가능한 연구 자료, 경제적 변화(예: 어떤 유형의 기술이 "저렴"해지고 있는지), 기능이 상품화되고 있는지, 새로운 데이터가 제공되는지 등을 확인하기 위한 연구를 꾸준히 수행해야 해요.

이번 프레젠테이션에서는 Natural Language Generation, 실시간 스트림을 위한 확률적 방법, 이미지 분석 및 요약 등 현재 Machine Learning 기술에 대한 개요를 다뤘어요. 이러한 기술을 우리 손끝에 쥐고 있는 것만으로는 충분하지 않겠죠. 우리가 무엇을 개발할 수 있는지와 관계없이, 커뮤니티로서 이러한 새로운 도구를 둘러싼 윤리에 대해 끊임없이 고민하고 그에 따라 행동해야 할 거예요.

전체 발표: 인공지능과 Machine Learning의 현재와 미래

오늘 이야기할 내용은 Machine Learning과 인공지능을 기반으로 구축된 현재와 미래의 기술에 대한 거예요.

그런데 이건 은유에 대한 이야기이기도 해요. 이 프레젠테이션에서 딱 하나만 기억한다면, 문제 정의를 할 때 우리가 사용하는 은유, 그리고 우리가 만들어내는 세상이 우리가 내리는 아키텍처 결정에 어떤 영향을 주는지 생각해보면 좋을 것 같아요. 은유는 우리 알고리즘 설계를 이끌고, 우리가 해결하려는 문제에 대한 창의적인 솔루션을 만들어내죠. 그래서 저는 이 강연 내내 특정 디자인 결정으로 이어진 은유를 강조하고, 상황이 왜 그렇게 흘러갔는지에 대한 맥락을 설명하려고 해요.

은유는 우리가 세상을 바라보는 방식을 좌우하고, 새로운 종류의 솔루션을 만들고 새로운 기회를 열어주는 강력한 도구랍니다.

비유: 인구 조사와 현대 컴퓨팅의 탄생

여러분과 함께 이야기하고 싶은 첫 번째 비유는 바로 인구 조사에 관한 거예요. 아래 사진은 1880년대 기차를 타고 이동하는 사람들의 모습이에요.

제가 왜 이 사진을 보여드리는 걸까요? 바로 이 사진이 아래 시스템을 만들게 된 은유이기 때문이에요.

1880년대에는 기차를 타고 전국을 돌아다니면서 종이에 직접 정보를 기록하는 방식으로 미국 인구 조사를 진행했어요. 당시 기술로는 이 작업을 제대로 수행할 수 없었고, 효율적이지도 않았죠. 허먼 홀러리스라는 뉴욕의 기차 차장이 있었는데, 그는 티켓에 구멍을 뚫는 방식에서 영감을 받아 자동 계산 기계를 만들었고 특허를 받았어요. 이 펀치 카드 디자인은 1930년대에 나온 후기 디자인으로 이어졌고, 이는 오늘날 우리 모두가 컴퓨팅 환경에서 사용하는 아키텍처 은유로 직접 연결되죠. 정말 중요한 비유라고 할 수 있어요.

허먼 홀러리스는 원래 Tablating Machine Company라는 회사를 세웠고, 나중에 IBM으로 이름을 바꿨답니다. 제가 제일 좋아하는 농담이 하나 있는데요. 빈턴 서프라는 분이 "컴퓨터 과학자들이 Kentucky Fried Chicken이라는 이름을 붙였다면 우리는 그걸 Hot Dead Bird라고 불렀을 거다"라고 말한 적이 있어요.

오늘날의 AI 헤드라인

제가 요즘 즐겨보는 AI 관련 헤드라인 몇 가지를 소개해 드릴게요. 이 기사는 Kanye West처럼 랩을 하는 봇을 만든 사람에 대한 이야기예요. 저는 종종 Google 뉴스에 들어가서 "인공 지능"을 검색해서 무슨 일이 일어나고 있는지 확인하곤 해요. 기사의 절반은 "불멸과 우울" 같은 사고방식으로 가득 차 있고, 나머지 절반은 좀 더 긍정적인 태도를 보여주죠. "AI가 암을 치료한다"부터 "AI가 사회를 파괴할 것이다"까지 다양한 메시지가 있어요.

기사 "인공지능은 위험할까? 영국 FEARS 로봇의 절반 이상이 차지할 것"은 어제 Express에 실린 기사인데, 아직 갈 길이 멀다는 걸 보여주죠. 안타깝게도 대화가 제대로 진전되지 않고 있는 것 같아요.

그리고 어떤 기자는 챗봇과 아주 친한 친구가 되려고 하기도 했어요. 녹취록을 보면 그가 "내 이름이 뭐예요?"라고 물어보는데, 챗봇은 "hello, undetermined"라고 대답하죠. 물론 괴짜들은 그 이유를 알겠지만, 일반 사람들에게는 좀 이상하게 보일 수 있어요. 이건 우리가 사용하는 어휘가 기술에 대해 생각하는 방식을 어떻게 바꾸는지 보여주는 좋은 예시라고 생각해요.

우리는 AI와 Machine Learning을 그냥 컴퓨터 프로그램으로 이야기하는 것에서 벗어나, 마법 상자나 갑자기 나타나서 일자리를 빼앗고 Kanye West처럼 랩을 하고 이름을 잊어버리는 생물 같은 존재에 대해 이야기하기 시작했어요.

하지만 저는 이걸 좀 더 현실적으로 바라볼 수 있는 방법을 공유하고 싶어요. 저는 다양한 가능성에 대해 흥미를 느끼면서도, 결국에는 집에 가서 실제로 작동하는 무언가를 만들어야 한다는 점에서 끈질기게 실용적인 사람이랍니다.

결국, 솔루션을 결정하기 전에 우리가 무엇을 하고 싶은지 이야기하기 위한 강력한 은유가 필요하다는 뜻이죠. 그리고 저는 Machine Learning에서 질문을 던지는 게 정말 어려운 일이라는 걸 자주 느껴요. 답이 너무 뻔하거나 아예 불가능한 경우가 많거든요.

AI 기반 구축

이제는 일상적인 용어가 된 빅데이터에 대해 한번 생각해 볼게요. 10년 전에는 모든 데이터를 한 곳에서 확인하고, 그 데이터에 포함된 항목을 계산하는 기능에 초점을 맞춘 빅데이터 이야기를 나눴었죠. 단순해 보이지만, 당시로서는 정말 획기적인 일이었어요.

단순히 이전에는 없던 기능이라서 혁신적인 게 아니었어요. 이전에는 접근조차 할 수 없었고, 엄청나게 비쌌던 것이 저렴하고 접근 가능하게 바뀌었기 때문에 혁신적이었던 거죠. 덕분에 새로운 사용 사례, 새로운 애플리케이션, 그리고 기술에 대한 창의성을 통해 완전히 새로운 작업 영역이 열렸어요.

10년 전 빅데이터 시대가 그랬듯, 오늘날에도 똑같은 일이 벌어지고 있다고 생각해요. 바로 Graph Database죠. 일단 뭔가를 셀 수 있게 되면, 사업 목적으로 그걸 셀 수 있게 되는 거니까요.

분석을 수행할 수 있고, 계산하려는 항목의 종류를 분산시킬 수도 있고요. 또, 해당 기술을 더 많은 사람들의 손에 쥐여줄 수도 있어요. 이유가 있으니까 사물을 셀 수 있는 거겠죠? 그리고 사물을 영리하게 셀 수 있다면, 데이터 과학을 사용해서 사물을 모델링하고, 예측하고, 다양한 은유를 사용해서 데이터 표현을 구축해서 세상을 탐색할 수 있어요. 유용하고 마법처럼 보이는 시스템의 피드백 루프를 통해 사물을 영리하게 계산할 수 있다면, 그게 바로 Machine Learning과 AI인 거예요.

하지만 기술은 단순히 스택 위로 이동하고 있을 뿐이에요.

데이터 과학, 분석, 빅데이터 또는 기본적인 데이터 표현 없이는 Machine Learning을 수행하거나 AI를 만들 수 없어요.

이런 비유를 생각할 때, 저는 AI를 폄하하려는 게 아니에요. 대신, AI를 기능 스택을 위로 올린 결과인 이 기술의 새로운 레이블이라고 생각해주세요. 이전에는 비싸거나 손이 닿지 않았던 것들이 더 다양한 사람들에게 가능해지고, 손에 닿고, 유용해졌다는 거죠. 이것이 오늘날 Machine Learning과 AI에 대한 관심이 그토록 뜨거운 이유예요. 그리고 무엇이 진짜이고 무엇이 아닌지를 사람들이 이해하도록 돕는 건, 기술자인 우리에게 달려있다고 생각해요.

성공적인 AI는 어떤 모습일까요?

구글 지도

그렇다면, 우리가 AI를 잘 활용하면 어떤 모습일까요? 제가 가장 좋아하는 Machine Learning 애플리케이션 중 하나는 교통 상황이 켜진 Google 지도예요.

이 기술의 특별한 점은 정말 '지루하다'는 거예요.

앱은 목적지까지 가는 가장 좋은 경로를 알려주기 때문에, 우리는 그걸 전혀 생각할 필요가 없죠. 이 도구를 지원하는 기술에 대해 조금도 알 필요가 없어요. Android를 실행하는 모든 휴대폰은 Google로 데이터를 다시 전송하고, 과거 및 실시간 데이터를 사용해서 놀라운 예측을 수행해요. 셀룰러 타워 네트워크가 해당 정보를 장치로 스트리밍하기 위해 조정하고 있고, 시각화에 대해 생각할 필요조차 없어요. 우리 사회에서 자란 모든 사람은 녹색은 진행을 의미하고 빨간색은 중지를 의미한다는 것을 인식하도록 훈련받았기 때문이죠. 그냥 보고, 그게 뭘 의미하는지 이해하기만 하면 돼요.

우리는 그걸 Machine Learning 제품이라고 전혀 생각하지 않기 때문에, 성공적인 Machine Learning 제품이라고 할 수 있어요. 우리는 그냥 우리가 가고 있는 곳으로 이동하기 위해 그걸 사용한 다음 꺼버리죠. 이게 바로 성공의 모습이에요. 우리가 AI에 대한 관심을 멈추고, AI 애플리케이션이 우리를 위해 무엇을 할 수 있는지에 대해 흥미를 갖기 시작할 때가 온 거죠. 하지만 여기가 찾기 힘든 곳이기도 해요.

텍스트와 이미지 일치

저는 이 데이터 기반 세계에서 극단적인 사례가 된 간단한 개인적인 예를 하나 공유할게요. 2007년에 'Cuil'이라는 스타트업이 있었어요. 이 회사는 이미지를 웹페이지 결과와 결합해서 Google을 이기려고 했었죠. 당시 Google은 이런 일을 하지 않았기 때문에, 꽤 큰 이슈였어요.

아래는 제 이름으로 검색했을 때 나온 결과예요.

제 이름과 똑같은 철자를 가진 여배우가 있는데, 영화에서 '못생긴 노파' 역할을 맡았대요.

2006년에 데이터 과학 분야에서 최고의 인재를 보유한 Microsoft Bing은 Bing 검색 내에 유명인 시각화 도구를 출시했대요. 다시 말씀드리지만 저는 유명인이 아니에요. 저는 연예인과 이름을 공유하는 너드일 뿐이죠. 하지만 다른 Hilary의 약력을 검색했을 때 나온 내용은 다음과 같았어요.

다시 말하지만, 세계 최고의 인재들 중 일부도 적시에 올바른 데이터를 가져오는 데 어려움을 겪고 있다는 거죠. 이것이 바로 매우 흥미로운 이유이고, 새로운 은유, 데이터를 표현하고 생각하는 새로운 방식이 필요한 이유에요. 우리는 우리의 역량에 맞춰 기술을 생각하는 방식을 기다리고 있는 거예요.

이것이 바로 우리가 지금 이 대화를 나누고 은유, 세상에 대해 생각하는 방식, 구축하려는 아키텍처 간의 상호 작용에 대해 생각해야 하는 이유에요. 이 일을 잘 수행할 수 있는 가장 큰 기회가 바로 여기에 있는 거죠.

제가 가진 장점 중 하나는 흥미로운 Machine Learning 및 AI 애플리케이션을 연구하는 다양한 사람들과 함께 일할 수 있다는 것이에요.

모든 사람들은 멋진 일은 스타트업에서만 일어난다고 생각하지만, 기존 기업은 해당 기업을 장기간 운영하는 데 따른 부작용으로 많은 양의 데이터를 수집해 왔어요. 그들은 개발에 대한 이러한 새로운 은유를 받아들일 준비가 되어 있고 가장 큰 기회를 가질 창의적인 사람들을 보유하고 있죠. 이 사람들은 Fortune 100대 기업에 속할 수도 있고 정말 흥미로운 과제를 해결하는 연구자일 수도 있어요. 여러분에게는 큰 기회가 있는 거예요.

Machine Learning: 미래를 보는 방법

Machine Learning 분야에 종사한다면 예측이 어렵고 미래를 예측하는 것이 더욱 어렵다는 것을 알고 계실 거예요. 저는 응용 Machine Learning 연구 그룹이 미래를 보기 위해 거치는 과정을 검토할 거예요. 여러분도 우리의 방법론을 자신의 작업에 적용할 수 있기를 바라요.

At 패스트 포워드 랩, 현재 Cloudera의 일부가 된 곳인데, 실제 비즈니스 사용 사례를 위해 설계된 새로운 Machine Learning 애플리케이션에 대한 분기별 보고서를 작성하고 있어요. 우리 팀은 사람들이 현실 세계에서 기록하는 데이터로 가득 찬 30개의 논문을 읽고, 규모에 따라 무엇이 효과가 있을지 알아내기 위해 코드를 작성하죠.

우리의 목표는 고객이 이러한 기술을 활용할 수 있는 속도를 가속화하는 최고의 친구가 되는 것이에요. 그러기 위해서는 앞으로 무엇이 나올지 살펴보고 현재 생산 중인 것보다 6개월에서 2년 앞선 것을 목표로 삼아야 하죠.

우리의 비밀은 다음과 같아요. 커피를 마시고 아이디어를 가지세요. 심지어 나쁜 아이디어도 괜찮아요. 여기서 제가 지적하고 싶은 것은 아이디어가 많아야 하고, 이는 의도적으로 나쁜 아이디어를 가지려고 노력한다는 거예요.

어떤 회사를 방문했을 때 그들이 진행하고 있는 Machine Learning 프로젝트를 보여줬는데 다 좋은 아이디어였을 때 걱정이 많이 됐어요. 확실히 좋은 아이디어만 추구한다면 약간 위험할 수도 있지만 엄청난 잠재적 보상을 얻을 수 있는 많은 기회를 놓치게 되는 거죠.

따라서 많은 아이디어를 얻은 다음 검증하세요. 최대한 광범위하게 진행한 다음 해당 컬렉션이 있으면 정량적일 수 있는 강력한 기준에 따라 검증하는 거예요.

우리가 사용하는 기준은 다음과 같아요.

연구 활동

특정 Machine Learning 애플리케이션과 관련된 활발한 연구 활동을 찾아보세요. 사람들이 관련 논문을 출판하고 있나요? 한 학술 분야에 다른 분야로 이동할 수 있는 논문이 있나요? 우리 팀과 같은 팀의 장점 중 하나는 컴퓨터 과학자, 물리학자, 신경과학자, 전기 엔지니어 등 다양한 배경을 가진 사람들이 있다는 것이에요. 한 분야의 문제를 해결했지만 다른 사람에게 굳이 알리지 않는 사람들이 있죠. 하지만 한 방에서 이렇게 다양한 사고를 하게 되면 한 영역에서 다른 영역으로 이동할 수 있는 많은 창의성과 잠재적인 지식을 얻게 되는 거예요.

경제학의 변화

솔루션을 설계하는 데 시스템에 필요한 경제성에 변화가 있나요? 즉, GPU 또는 CPU 컴퓨터의 가격은 얼마인가요? 우리가 사용하는 거의 모든 시스템에 대해 이와 동일한 그래프를 그릴 수 있지만, 제가 찾은 매우 설득력 있는 특별한 예는 마이크로 SD 카드에요.

그 용량은 지난 10년 동안 엄청나게 증가했어요. 동일한 가격, 동일한 카드, 동일한 폼 팩터인데 말이죠. 지금은 손에 닿지 않는 물건이라도 1년 뒤에는 값이 싸질 수 있으니 버리지 마세요. 이걸 활용할 수 있는 서비스가 분명 있을 거예요.

상품화되는 기능

우리는 특히 오픈 소스 라이브러리에서 상품화되는 기능을 찾고 있어요. Hadoop 자체가 사람들이 어떻게 하는지 알고 있음에도 불구하고 하기가 매우 어렵고 비용이 많이 드는 일의 핵심 예시죠.

오픈 소스 프로젝트가 꽤 널리 채택되면 해당 인프라를 한 곳에 두고, 개수를 계산하고, 상당히 쉽게 답변을 얻을 수 있다는 것을 당연하게 여길 수 있어요. Word2vec은 Machine Learning 공간의 또 다른 훌륭한 예시고요. 단어 임베딩은 수학적으로 매우 복잡하죠. 자신만의 글을 쓰려면 상당한 시간과 에너지를 투자해야 해요. 하지만 이제는 다운로드해서 몇 시간 만에 실행할 수 있다니, 정말 놀랍죠?

상품화는 계속 움직이는 물결과 같고 Machine Learning 실행 능력에 매우 강력한 영향을 줘요.

새로운 데이터 이용 가능

마지막으로 찾아야 할 것은 내부적으로든 외부적으로든 새로운 데이터를 사용할 수 있게 되는 거예요. 새로운 제품이나 기능을 출시해서 생성되는 데이터일 수도 있고, 전 세계에서 수집하거나 구매할 수 있는 데이터일 수도 있죠. 하지만 데이터의 출처가 무엇이든 Machine Learning을 추구하려면 데이터를 사용할 수 있어야 해요.

제가 두 가지 이유로 데이터 과학에 관한 Wikipedia 페이지를 열었어요.

첫 번째는 Wikipedia가 스타트업이 운영하는 모든 MLP 애플리케이션의 더러운 비밀이라는 점이에요. 정보는 널리 이용 가능하고 상업적 목적으로 라이선스가 부여되기 때문이죠.

두 번째 이유는 오랫동안 Wikipedia 데이터 과학 페이지가 이 페이지의 생성을 직업으로서의 데이터 과학 발전의 주요 이정표로 인용했기 때문이에요. 그건 제가 본 것 중 가장 위키피디아에 관한 것이죠.

기준이 정해지면 일련의 아이디어를 검토하고 가능성이 있는 것과 그렇지 않은 것을 제쳐두세요.이러한 위험한 기능을 점진적으로 탐색할 수 있어요.

우리 그룹에서는 아이디어를 더 탐구해야 할지 여부를 결정하기 위해 인터넷 검색과 출판된 논문의 초록 읽기로 구성된 3시간의 조명 검토를 수행해요. 다음으로, 우리는 읽을 논문의 하위 집합을 선택하고 그것이 투자할 가치가 있는지 여부에 대한 개별적인 관점에 도달하죠. 그런 다음 이전 필터를 통과한 논문의 하위 집합을 가져와서 코드를 작성해요.

투자할 시간의 범위를 제한함으로써 이 점진적인 탐색을 통해 시간을 투자할 가치가 없을 수도 있는 위험한 아이디어를 고려할 수 있어요. 또한 작업하는 사람의 수에 관계없이 동일한 답변을 반복적으로 얻는 데 도움이 되고요.

이제 미래를 예측하는 것이 실제로 어렵다는 것을 보여주기 위해 아래에는 2000년의 삶을 예측하는 1900년경 프랑스의 엽서가 있어요.

중앙 엽서에는 컬렌더를 머리에 쓰고 교실에 있는 아이들이 지식을 뇌에 직접 펌핑하고 있어요. 오른쪽에는 불을 끄기 위해 날개를 달고 날아다니는 소방관들이 있네요. 그리고 이러한 문제 중 일부를 해결할 수 있는 기술이 있음에도 불구하고, 이러한 기술은 1900년대 사람들이 상상조차 할 수 없었던 방식으로 구현되었죠.

정확한 예측을 하는 건 정말 어려운 일이에요. 그래서 예측이 올바른 방향이라면 꽤 잘하고 있는 거라고 생각해요.

기술 심층 분석

이제 현재 존재하는 실제 Machine Learning 기술과 가까운 미래에 등장할 기술에 대해 자세히 살펴볼게요. 이러한 도구는 모두 위에 설명된 프로세스에서 나왔어요. 미래를 보는 방법을 알고 계신다면, 우리가 거기에서 무엇을 보는지 말씀드릴게요.

Natural Language Generation

우리 보고서 중 하나는 Natural Language Generation에 관한 것이에요. 뉴스에서 본 적이 있을 텐데요. 는 Associated Press에서 나온 기사인데, AP의 언론인들은 이제 자신의 기사를 쓰고 있다고 해요. 실제로 이러한 알고리즘을 감독하는 자동화 편집기가 있죠. 그는 이야기를 생성하는 소프트웨어 시스템을 감독하지만 사람은 감독하지 않아요.

우리는 부동산 광고를 생성하는 프로토타입을 만들었어요. 구매 또는 판매하려는 아파트의 특성을 입력하면 도구가 광고를 작성해주는 거죠. 하지만 존재하지 않는 것을 말하면 정말 이상해져요.

예를 들어, 도어맨이 있는 어퍼 이스트 지역의 침실 1개, 욕실 16개 아파트를 판매하려는 경우 "햇빛이 가득한 집에는 욕실이 많습니다."와 같은 결과가 반환될 거예요. 제 말은 로봇이 기사를 쓴다는 것이 아니라, 인간으로서 우리가 데이터와 상호 작용하는 방식을 보여준다는 거예요. 데이터 열이나 데이터 그래프에서 이 모든 언어를 상상할 수 있죠.

중간에 그래프가 있는 것을 상상할 수 있고, 모든 전문가가 그래프와 Excel을 읽을 수 있을 거라고 기대할 수 있겠죠. 하지만 대부분의 사람들은 해당 데이터에 대한 언어 기반 해석을 직관적으로 이해하는데, 이것이 바로 이 기술이 우리에게 제공하는 것이에요. 구조화된 데이터에서 데이터가 말하는 내용을 설명하는 몇 개의 문장 형태로 해당 데이터의 언어 기반 표현으로 이동할 수 있는 거죠. 진정한 힘은 언어의 힘을 통해 구조화된 데이터에 대한 이해를 훨씬 더 많은 청중에게 전달하는 데 있어요.

우리는 두 명의 고객에 대해 이것이 구현되는 것을 보았어요.

첫 번째는 이 기술을 사용하여 규정 준수 서류를 자동으로 생성하는 은행이에요. 그 뒤에는 정말 흥미로운 이야기가 숨어있죠. 두 번째는 유명인 의류의 구조화된 JSON 피드를 사용하여, 예를 들어 Kim Kardashian이 구매할 수 있는 특정 스웨터를 입고 있다는 텍스트를 생성하는 모바일 앱을 만드는 유명인 패션 잡지예요.

두 도구 모두 수학적으로는 동일하지만 형태는 매우 다르죠. 다시 한 번 은유가 여기에 있는 거예요.

실시간 스트림에 대한 확률적 방법

우리는 세상을 실시간으로 이해하는 데 필요한 새로운 아키텍처가 있다고 생각해요.

이를 엔지니어링 관점에서 생각하면, 예를 들어 데이터를 배치 환경에 보관하고 평균을 생성하는 비유에서 벗어나게 돼요. 매우 간단하지만 데이터 세트의 규모나 분산 정도에 따라 실제로 계산하는 데 꽤 오랜 시간이 걸릴 수 있죠.

하지만 컴퓨팅 장치가 내 썸네일 크기만 하고 메모리가 매우 제한되어 있고, 초당 수천 개의 이벤트가 들어오는 데이터 스트림이 있는 세상에 살고 있다면 어떻게 될까요? 해당 환경에서 평균을 어떻게 계산할까요?

여기서 대답은 저장소 샘플링이라는 알고리즘을 사용하는 것이에요. 필요한 컴퓨팅 양과 메모리 양이 제한되어 있고, 항상 확률적으로 업데이트되는 특정 양의 버킷이 있으며, 언제든지 평균을 구할 수 있죠. N개 버킷의 평균을 구하기 때문이에요. 이제 정답 대신 오류 막대가 있는 정답이 표시되는 거죠.

이것은 우리가 아직 완성하지 못한 Star Trek 트라이코더를 마침내 구축할 방식이라고 제가 상상하고 싶은 시스템 설계에 대한 생각을 비유한 것이에요. 대신, 다음은 하나의 작은 EC2 인스턴스를 사용하여 Reddit의 전체 댓글 모음에서 이를 실행하는 데모예요.

"샤워할 때 떠오르는 생각"과 "내가 솔로인 이유" 사이에 사용된 언어의 유사성이 보이는 것 같아요. 사람들은 인터넷 구석구석에서 비슷한 이야기를 나누고 있다는 거죠.

이것이 바로 우리가 아키텍처 디자인의 오래된 비유에 갇혀 있었다면 불가능했을 시스템을 구축할 수 있게 해준 그래프 비유랍니다. 커뮤니티로서 우리가 이제 막 활용하기 시작한 이러한 확률적 기술에는 엄청난 힘이 있는 것 같아요.

Deep Learning: 이미지 분석

물론, AI와 Machine Learning을 이야기하면서 Deep Learning을 빼놓을 순 없죠! Deep Learning은 뉴런을 가진 Neural Network의 진화된 형태라고 할 수 있어요.

이 뉴럴 네트워크들은 여러 개가 함께, 그리고 레이어 형태로 구성돼요. 여기서 "Deep"은 레이어의 수를 의미하는데, 사실 엄밀한 기술 용어는 아니랍니다. 왜냐하면 레이어가 단 하나인 Deep Network만 포함하는 Deep Learning 논문도 많이 봤거든요.

이 Neural Network는 1940년대와 50년대에 뇌가 작동한다고 생각했던 방식에서 영감을 받았어요. 실제로 최초의 Neural Network는 코넬항공연구소에서 조명이 켜지고 꺼지는 것을 인식할 수 있는 하드웨어 형태로 개발되었죠.

오늘날에는 이전에는 상상도 못 했던 리치 미디어 분석이 가능해졌어요. 예를 들어, '이 사람은 아일랜드 사진 찍는 것을 좋아합니다'라고 알려주는 Instagram 필터를 사용할 수 있는 데모도 있답니다.

Machine Learning 개발자이자 과학자로서, 우리는 이러한 기술의 한계를 보여줄 책임이 있기 때문에, 이 기술이 어떻게 잘못될 수 있는지 한번 살펴볼게요. 저는 치즈버거를 좋아해서 뉴욕의 치즈버거 맛집인 Bleecker Burger의 예를 들어보겠습니다.

꽤 잘 분류했네요. 왼쪽 상단에는 이것이 버거라는 것을 알려주고요. 맨 아래에 있는 것은 음식이라고 되어 있지만, 미트 로프일 수도 있고 핫도그일 수도 있대요. 우리는 완전히 확신할 수 없죠. 그리고 버거가 아닌 깃발이 유일하게 있고, 게도 있네요!

그럼 게는 뭘까요? 이걸 보면 이 Neural Network가 물이나 부두 근처에서 감자튀김처럼 보이는 것은 게라고 학습했다는 것을 알 수 있어요.

다시 한번 강조하지만, 이러한 기술과 은유의 힘을 이해하는 것, 그리고 때로는 약간 이상하게 변하는 것을 인지하는 건 우리 모두의 책임이에요. 우리 개개인이 기술이 이상하게 흘러가지 않도록 신경 써야 하는 거죠.

요약

단어 임베딩 및 문장 임베딩 같은 도구를 사용해서 이와 동일한 종류의 Deep Learning을 텍스트에 적용하면, 기사를 가져와서 동일한 정보를 담고 있는 문장을 추출하는 시스템 같은 걸 구축할 수 있어요.

이 작업은 모두 자동으로 수행되고, 모델이 훈련된 방식 덕분에 영어로 된 모든 기사에 적용할 수 있다는 장점이 있어요. 물론 다른 언어에서도 쉽게 개발할 수 있고요.

이런 종류의 도구는 단순히 텍스트 한 조각만 보는 게 아니라, 동일한 주제에 대한 50,000개의 문서로 구성된 콘텐츠 모음을 보고 다양한 관점을 추출하는 새로운 방법을 제시하죠. 클러스터링하고, 해당 코퍼스에서 관점을 찾고, 각 관점을 요약함으로써 가능한 일이에요. 이제는 꽤 쉽게 할 수 있는 일이 되었답니다.

마지막으로 알고리즘 해석 가능성과 관련된 내용이에요. 이 블랙박스 모델을 모두 조립했을 때 내부는 어떻게 생겼을까요? 그리고 왜 내부를 들여다봐야 할까요?

첫 번째 이유는 정부 규제 때문이에요. 규정 준수를 위해서는 시스템이 왜 그런 결정을 내렸는지 설명할 수 있어야 하는 경우가 많거든요. 금융이나 의료 분야에 종사하지 않아서 이 글을 그냥 넘기셨던 분들을 위해 두 번째 이유를 설명해 드릴게요. 때로는 이런 시스템이 정말 이상한 일을 할 때가 있는데, 여러분은 그 이유를 알고 싶어하잖아요. 이유를 알면 전반적으로 더 나은 시스템을 구축할 수 있게 되죠.

블랙박스 알고리즘 위에 얹어서 입력에 스며들게 한 다음, 출력과 분류가 어떻게 변하는지 살펴보는 알고리즘 세트가 있어요. 그 후에 블랙박스 모델에서 어떤 특징이 중요했는지 추론하는 거죠. 약간의 직관을 얻을 수 있겠죠?

예를 들어 통신 이탈 분석을 통해 고객이 이탈할 가능성뿐만 아니라, 이탈 이유와 취할 수 있는 조치까지 파악할 수 있어요.

작업을 수행하고, 해당 분류와 그에 따른 고객의 운명을 바꾸기 위해 변경되는 확률을 확인할 수 있다는 점도 매력적이죠.

이것이 바로 규정 준수 및 규제 사용 사례 외에도 매우 유용한 기술이라는 점이에요.

결론

제가 가장 좋아하는 그래프 기반의 Machine Learning 예시로 마무리할게요. 다음 기사는 길라드 로탄의 글에서 가져왔는데요, 그는 대통령 취임 후 Buzzfeed의 수석 데이터 과학자가 되었어요.

그는 소셜 미디어에서 이모티콘 사용에 대한 대규모 분석을 수행했고, 아래 그림처럼 취임식에 기뻐하는 사람들과 그렇지 않은 사람들로 나뉘어 표현했죠.

이 그래프 시각화에서 데이터들이 클러스터링되는 모습은 사람들의 이야기를 담고 있어요. 미국 국민들에게 특별하고 감정적인 순간을 분석하는 데 적합한 은유라고 할 수 있죠.

또 다른 프로젝트는 세계 최고의 회계 법인 중 하나가 세법 관련 자동화를 파악하도록 돕는 일이었어요. 미국의 세법은 새로운 법률과 기존 법률에 대한 사법적 해석의 변화를 통해 지역, 주 차원에서 매우 복잡하게 바뀌는데, 우리는 그 변화를 이해하고 싶었죠.

그래서 그들이 놓치는 부분이 없도록 워크플로우에서 CPA를 지원하는 Machine Learning 도구를 자동으로 구축하는 데 도움이 되는 모델을 만들 수 있었답니다.

우리는 또한 원자재 거래 방식에 대한 유사한 분석을 생성할 수 있었어요. 들어오는 뉴스는 여러분의 투자 포트폴리오에 영향을 미치고, 이러한 연결을 볼 수 있어야 하죠.

이것은 정확한 순간에 올바른 정보를 제공함으로써 인간 전문가의 업무 수행 능력을 더욱 향상시키는 도구를 구축하기 위해 해당 은유의 힘을 사용하는 예시랍니다.

이 일을 하려고 달려가는 분들을 위해 몇 가지 주의 사항으로 마무리할게요. 어렵다는 거예요. 우리는 아직 우리가 무엇을 하고 있는지 완전히 알지 못해요. 모범 사례들이 계속 등장하고 있죠. 그리고 기술을 사용하여 무언가를 만들고 있다면, 자신이 만든 것이 우리가 살고 있는 세상에 미치는 영향에 대해 깊이 생각해 보시길 바라요.

저는 “윤리와 데이터 과학”이라는 책을 DJ 및 Mike Loukides와 함께 공동 집필했어요. 이 책은 윤리 및 데이터 과학의 실천에 대해 이야기하고, 질문을 던지기는 하지만 모든 답을 제공하지는 않아요.

저는 이것이 데이터 과학 실무자들이 우리 시대에 대해 생각하고 있는 큰 질문이라고 생각해요. 이러한 기술을 구축할 때 이 점을 꼭 생각해 주세요.

제가 가장 좋아하는 비유는 특히 기술과 Machine Learning이 우리에게 초능력을 준다는 거예요. 꾸밈없는 인간으로서는 상상도 못 할 일들을 가능하게 해주니까요. 이 분야에서 일하는 건 정말 흥미로운 경험이죠.

이 백서를 다운로드하세요. 지속 가능한 경쟁 우위: 데이터 관계를 통해 비즈니스 가치 창출, 그리고 여러분의 회사가 Graph Database 기술을 통해 어떻게 경쟁력을 확보할 수 있는지 알아보세요.
  • AI 윤리
  • AI 기술
  • Machine Learning

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

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

반응형
반응형

편집자 주: 이 프레젠테이션은 David Bader가 GraphConnect New York 2018년 9월에서 발표한 내용이에요.

프레젠테이션 요약

David Bader는 당시 Georgia Tech의 컴퓨터 과학 및 공학부 의장이었어요. 예측 분석 프레젠테이션에서 그는 자신과 팀이 그래프를 사용해서 어떤 실제 문제와 데이터를 다루고 있는지 간략하게 소개했답니다.

그래프가 어디에 구현되었을까요? 소셜 네트워크, 교통 시스템, 폭풍 대피, 그리고 학술 연구 분야인 과학, 물리학, 화학, 천체 물리학 등 다양한 곳에서 활용되고 있어요. Bader는 특히 분석 및 감시에 그래프가 사용되는 점을 강조했는데요. 그래프가 어떻게 Twitter에서 처음 가치를 발견했는지, 2009년 H1N1 바이러스 대유행 당시 미국 의회 도서관에 전체 트윗 데이터 세트가 게시된 현대까지 이야기가 이어진답니다.

그래프 기술이 아무리 발전했더라도, 다른 시각으로 살펴보는 건 여전히 중요해요. 그래프를 통해 일반적으로는 얻을 수 없는 통찰력을 얻을 수도 있으니까요.

STING은 Georgia Tech의 시공간 상호작용 네트워크 및 그래프 그룹인데요. 현실 세계의 문제 해결을 위해 노력하고 있어요. 국가 안보 문제인 이벤트 보안(Macy's 추수감사절 퍼레이드나 DHS, FBI, NYPD의 지원을 받은 슈퍼볼 등)도 다루죠. STING 데이터베이스는 Neo4j 기반으로 구축되었고, 파트너의 다양한 데이터 세트, 공개 데이터 세트, 신문 기사, 심지어 손으로 쓴 메모까지 결합한답니다.

데이터 연결 예측 그래프 분석은 PageRank 및 Gephi와 협력하여 Neo4j에서 실행되는데, 분석가가 더 많은 데이터를 입력할수록 점점 발전해 나간대요. 그래프는 고정되어 있지 않고, 본질적으로 사용자와 함께 "학습"하는 거죠.

예측 그래프를 통해 Bader는 비슷한 비극을 먼저 겪지 않아도 재앙이 발생하기 전에 예측하는 것을 목표로 하고 있어요. 그래프의 미래는 과거의 행동 패턴을 예측하고, 동시에 새로운 위협을 탐지하는 것이죠.

전체 프레젠테이션

저는 데이비드 베이더이고, 조지아 공과대학 전산 과학 및 공학 의장직을 맡고 있어요. 이렇게 대규모 Knowledge Graph의 예측 분석과 저희가 진행 중인 몇 가지 작업을 를 통해 이야기하게 되어 정말 기쁩니다.

실제 문제와 데이터

조지아 공과대학은 정말 멋진 곳이에요. 저희는 Coda라는 새 건물을 짓고 있는데, 미국 남동부에서 데이터 과학 및 엔지니어링 전용 건물이라고 해요. 저희 학교는 함께 일하는 다른 회사들과 협력해서 그 건물의 핵심을 형성할 거예요.

제 연구 분야는 데이터와 관련된 실제 문제를 해결하려고 노력해 왔어요. 그 과정에서 건강 관리부터 대규모 소셜 네트워크, 폭풍우를 대피시키는 방법과 같은 교통 시스템에 이르기까지 가장 까다롭고 어려운 그래프 분석 문제를 해결하기 위해 노력했죠.

이건 정말 도전적인 응용 분야에요. 저희가 다루고자 하는 인간의 삶에 영향을 미치는 실제 문제가 정말 많거든요. 종종 단일 컴퓨터를 압도하는 데이터 세트가 있어서 밀리초 내에 답변을 얻어야 하는 경우도 있고요. 질문이 뭔지 생각하기도 전에 답을 얻고 싶을 때도 있어요.

몇 가지 샘플 쿼리를 예로 들어볼게요. 특정 커뮤니티의 핵심이 될 수 있는 인구 및 개인 내의 커뮤니티를 이해하려고 시도하는 거죠. 하지만 시간이 지남에 따라 해당 커뮤니티는 다른 커뮤니티의 핵심으로 이동할 수도 있어요. 새로운 정보가 들어오면 이러한 커뮤니티는 시간이 지남에 따라 어떻게 변할까요?

예를 들어, "고객을 잃고 있나요? 그들이 왜 떠나고 있나요?" 와 같은 질문들이요.

저희는 또한 이전에 볼 수 없었던 새로운 패턴을 식별하기 위해 많은 애플리케이션을 개발하고 있어요. 사이버 보안에서는 위협을 다루고 다양한 유형의 공격에 대한 원인을 제공하죠. 그 중 일부는 이전에 볼 수 없었던 것일 수도 있고요.

이를 위해서는 실시간으로 대규모 변화를 예측하고 영향을 미치는 것이 필요해요. 이것이 바로 저희가 하고 있는 연구의 특징이죠. 저는 종종 사람들과 그들의 관계에 대해 쉽게 이야기할 수 있는 소셜 네트워크의 관점에서 사물을 설명해요. 하지만 이 비유를 네트워크 트래픽, 비즈니스 인텔리전스, DNA 등과 관련된 다양한 문제에 적용하는 데 사용한다는 것을 알아주셨으면 좋겠어요.

그래프를 구현하는 위치

그래프는 학술 연구, 과학, 물리학, 화학, 천체물리학 등 제 업무 분야에 정말 널리 퍼져 있어요.

저희는 여기저기서 그래프를 보게 돼요. 그래프 분석가로서 제 임무는 이러한 실제 문제를 해결하고 그래프 추상화를 살펴본 다음, 이러한 문제를 해결하는 데 도움이 되는 알고리즘과 도구를 구축하는 데 도움을 주는 것이죠.

천체 물리학에서 퀘이사를 탐지한 예가 있어요. 시간이 지남에 따라 하늘에 대한 두 개의 디지털 이미지를 갖게 될 수 있는데, 퀘이사가 될 수 있는 변화를 찾고 있는 거죠. 저희는 그걸 매우 빠르게 감지하고 몇 분 안에 망원경을 움직여 천체 사건을 포착하려고 해요. 이 이벤트에서 이미지의 시간적 변화와 그래프 클러스터링 또는 해당 이미지와 신체 간의 일치와 같은 그래프 문제를 살펴볼 수 있어요.

약물 표적 단백질을 식별하려는 생물정보학 문제의 예도 있죠. Betweenness Centrality 또는 기타 Centrality Network 알고리즘을 사용하거나 해당 단백질을 클러스터링하여 중심적인 단백질을 찾는 단백질 상호 작용 네트워크를 가지고 있고요.

사회 정보학의 경우 소셜 네트워크에 있는 개인 간의 최단 경로 또는 조직을 통한 정보 흐름을 살펴볼 수 있어요.

오늘날 이러한 데이터 세트에 대한 법의학 분석에 대한 많은 작업이 수행되고 있어요. 하지만 앞으로는 이러한 데이터 스트림을 활용해서 즉각적으로 결정을 내릴 수 있게 될 거예요.

이것이 실제로 이러한 도전의 모습이에요. 데이터가 점점 더 커지면서 이질성이 커지고 있죠. 저희는 우리가 가지고 있는 데이터의 다양한 품질에 대해 확신을 갖지 못할 때도 있어요. 결국 저희는 비즈니스나 조직을 추진하는 더 나은 결정을 내리고 싶어하는 거고요.

저는 이러한 데이터 분석이 실제로 다음 시대의 회사를 정의할 거라고 생각해요. 조직 내에서, 그리고 다른 사람들을 대하는 방식에서 수익을 창출하고 효율성을 높일 수 있게 될 테니까요. 이 그래프 영역은 정말 세상을 변화시킬 거라고 믿어요.

분석 및 감시용 그래프

공개 이미지 두 개가 있네요. 위쪽 이미지는 9.11 테러 당시 납치범들을 나타내고 있어요. 만약 그 당시에 그래프 분석을 할 수 있었다면, Centrality 같은 알고리즘을 사용해서 납치범, 링 리더, 그리고 비행기의 다양한 셀들을 탐지할 수 있었을 거라는 주장이 있었죠.

아래쪽 이미지는 그래프 분석의 또 다른 예시인데요. 관찰된 정보를 바탕으로 여러분이 알고 있는 패턴이 의심스러운 경우에요. 함께 사는 두 사람이 하는 일을 관찰하는 거죠. 한 명은 트럭을 빌리고, 다른 한 명은 비료를 사요. 뭔가 안 좋은 일을 꾸미고 있을 수도 있겠죠. 여러분이 원하는 건 대규모 데이터 세트 내에서 해당 패턴과 비슷한 다양한 버전을 찾는 거예요. 이걸 하위 그래프 동형현상이라고 하고, 보통 그래프 일치 또는 모티프 찾기라고 부른답니다.

특히 데이터 세트가 점점 더 커지는 상황에서는 정말 어려운 알고리즘 문제라고 할 수 있어요.

Graphs and Twitter

저희는 공개된 트윗 전체를 미국 의회 도서관에 올린 최초의 기업이기도 해요. 10년 전쯤, 2009년 9월과 10월의 트윗을 가져와서 크레이 머신이라는 슈퍼컴퓨터에 넣었죠.

Cray라는 이름을 아는 분들도 계실 텐데요. 저희는 테라바이트급 정보를 연구하기 위해 Pacific Northwest National Lab과 협력했어요. 저희가 알아내려고 했던 건 트위터가 어떤 용도로든 쓸모가 있느냐는 거였어요. 트윗에서 좋은 점을 얻을 수 있을까? 당시에는 확신이 없었죠. 다들 "트윗에서 어떻게 가치를 얻지?"라고 궁금해했으니까요.

저희는 그 기간에 두 가지 이벤트를 겪었어요. 왼쪽에는 H1N1이 보이네요. 2009년에는 이 새로운 바이러스 변종인 H1N1이 전 세계적으로 두 자릿수 사망률을 기록할 거라는 우려가 있었어요. 정말 무서웠고, 사람들은 뭘 해야 할지 몰랐죠. 항공 여행에 대한 패닉도 있었고요. 사람들은 이걸로 엄청난 사망자가 발생할까 봐 두려워했어요.

저희는 모든 트윗을 가져와서 팔로워, 팔로잉, 그리고 콘텐츠의 일부 키워드를 사용해서 그래프를 만들기로 결정했어요. 그리고 해당 그래프에서, 이 이벤트와 관련해서 트위터에서 찾을 수 있는 가장 영향력 있는 핸들이 뭔지 확인하기 위해 Betweenness Centrality를 임시 측정 방법으로 사용했죠.

약 15개의 트위터 핸들을 뽑았는데, 그중 몇몇은 뻔했어요. 여기가 사람들이 바이러스에 대한 정보를 얻는 곳이었거든요.

보시다시피 CDC가 1위네요! 미국 질병 통제 예방 센터죠. 많은 사람들이 정보를 얻기 위해 이곳을 찾을 거라고 예상했을 거예요. 4위는 flu.gov이고, 이 중 두 개는 목록 뒷부분에 나오는 CDC에요. CNN, New York Times, Time Magazine 같은 상업 미디어도 보이는데, 이런 미디어도 사람들이 정보를 얻는 방식에 큰 영향을 미칠 거라고 예상할 수 있죠.

그래프의 힘은 여기서 끝나지 않아요. 속보 이벤트에서 누구의 말을 들어야 할지 어떻게 알 수 있을까요? 정보의 출처와 가장 영향력 있는 사람이 누구인지 어떻게 알 수 있을까요? 그리고 이 목록의 상위 항목 중 하나인 @Official_PAX가 3위에 올라와 있네요.

알고 보니 이곳은 페니 아케이드(Penny Arcade)인 Penny Exchange였어요. 주로 시애틀에서 게임을 하던 청소년 남성 게이머들이었는데, 미국에서 H1N1에 감염된 최초의 그룹 중 하나였죠. 그들은 증상에 대해 서로 트윗하기 시작했어요.

이게 바로 H1N1이 얼마나 치명적인지, 그리고 증상이 무엇인지 이해하는 주요 소스 중 하나였던 거예요. 미리 조정하는 것에 대해선 전혀 생각 못 했을 텐데 말이죠. 라디오 다이얼을 뉴스 소스에 맞추는 것처럼, 그래프의 힘이 바로 이런 거랍니다.

몇 년 전에 인도에서 강연을 하고 있었는데, 한 학생이 백스트리트 보이즈(Backstreet Boys)의 열렬한 팬이었나 봐요. "백스트리트 보이즈가 왜 저기 있죠?"라는 질문을 받았어요. 그래서 "글쎄요, 저도 모르겠네요. 가서 알아봐야겠어요."라고 답했죠.

데이터를 다시 살펴보고 몇 가지 분석을 해봤어요. 그래프가 정보를 제공하는 데 매우 효과적이지만, 데이터 모델을 살펴보고 항상 예외가 있다는 걸 이해해야 하는 경우였죠. 백스트리트 보이즈(Backstreet Boys)가 앨범을 발표했는데, 팬들이 새 앨범과 H1N1에 대해 함께 트윗을 올렸다는 사실이 밝혀졌어요. 정말 흥미로운 크로스오버 현상이었죠!

Double-checks and reality checks

데이터와 실제 문제가 있다면 그래프에서 유용한 정보를 얻을 수 있어요. 하지만 이런 데이터 문제를 다룰 때는 항상 자신이 가진 것을 확인하고 현실 점검을 해야 해요.

저희는 또한 미국 전력망의 탄력성을 분석하는 연구도 진행하고 있어요.

이 그래프는 전력망을 나타내고 있어요.

미국에는 주로 동부 전력망과 서부 전력망이 있죠. 저희는 Pacific Northwest National Labs와 협력해서 그래프 문제로 표현되는 전력망의 탄력성을 조사하고 있어요. 전력망의 단일 장애로 인해 전체 지역에 대규모 정전이 발생할 수 있다는 걸 알고 있거든요.

오른쪽은 저희가 인간 프로테옴을 관찰하면서 작업한 문제인데요. 저희는 Betweenness Centrality 알고리즘을 돌려서 가장 상호작용적이지 않은 단백질을 프로테옴에서 찾았어요. 마치 건초 더미 속의 바늘 같았지만, 어떤 의미에서는 중심성 측면에서 가장 잘 연결되어 있었죠. 가장 높은 중심성을 갖고 있는 것으로 확인된 단백질 중 하나가 유방암과 관련이 있다는 사실을 알아냈답니다.

그래프를 활용하면 일반적으로는 쉽게 얻을 수 없는 통찰력을 얻고, 실제 문제 해결에 도움을 받을 수 있어요.

STING

제 연구실에서는 25년간 STING 프로그램을 진행해 왔어요.

Georgia Tech의 마스코트는 노란 재킷인데요. STING은 Spatio-Temporal Interaction Networks and Graphs의 약자예요. 즉, 시간이 지남에 따라 변하는 그래프로 모델링할 수 있는 실제 문제를 연구하는 거죠. 여기서 Nodes와 Edges는 시공간적 위치, 속성, 타임스탬프 등 다양한 특징을 가질 수 있어요.

저희는 문제 해결을 목표로 하는 DARPA 프로그램을 진행했는데요. Nidal Hasan 소령은 육군 심리학자이자 Fort Hood 사수였어요. 그는 휴가를 받았고, 다른 사람들의 정신 건강을 돌보려고 노력하는 사람이었죠. 그런데 어느 날 갑자기 여러 사람을 총으로 쏴 죽이는 사건이 발생했어요.

시간이 지나면서 변화가 생기고, 심각한 사건이 발생하기 전에 아무런 잘못이 없는 개인이 있을 때, 시스템은 내부 위협을 어떻게 처리해야 할까요? 이런 종류의 문제가 저희가 해결하고자 하는 문제예요. 정보의 양이 엄청나게 많고, 실시간으로 문제를 해결하려면 새로운 알고리즘은 물론이고 새로운 컴퓨터까지 만들어야 할 때도 있죠.

저희는 새로운 컴퓨터 아키텍처 엔지니어링을 지원하고, Intel, Qualcomm, NVIDIA, IBM, Cray 같은 회사와 협력해서 새로운 컴퓨터를 만들었어요. 스마트폰에서 흔히 볼 수 있는 구성 요소들이 있는데, 이런 기술들이 조금씩 발전하고 있는 거죠. 언젠가는 모든 Neo4j 애플리케이션을 스마트폰에서 실행할 수 있게 되기를 바라요.

STING은 Neo4j를 사용하는 분석가들을 위한 솔루션으로 만들어졌어요. 그래프에 수조 개의 엔터티가 있고, 분석가들은 밀리초 단위의 응답 시간을 요구하는 상황까지 가는 거죠. 이 그래프는 엄청난 양의 정보를 쏟아내는 소방 호스와 같아요.

저희는 현재 Neo4j에서 실행할 수 있는 속성을 조사하는 초기 알고리즘을 연구했어요. 예를 들어, 연결된 구성 요소, 최단 경로, 커뮤니티 감지 같은 것들이죠.

그래프가 실시간으로 변할 때, 이런 변화를 어떻게 추적해야 할까요? 고객과 사용자에게 중요한 변경 사항을 어떻게 감지하고, 표시하고, 보고해야 할까요?

이제 국토 안보부를 위해 저희가 하고 있는 문제에 대해 좀 더 자세히 알아볼까요?

국가 안보 문제

분석가가 참고 자료, 각주, 그림이 포함된 여러 페이지 분량의 Natural Language 보고서를 작성하는 현재 진행 중인 작업이 있어요. 그리고 특별 행사를 보호하려는 혐의로 기소된 사건이죠.

예를 들어, 뉴욕시에는 메이시스 데이 퍼레이드가 있고, 전국 각지에서는 슈퍼볼 행사가 열리죠. 저희는 이런 유형의 행사가 안전하게 진행되기를 바라요. 현지 법 집행 기관이 무슨 일이 일어나고 있는지 인지하고, 이런 행사에 참석하는 동안 여러분이 안전하게 지낼 수 있도록 하기 위해 매우 중요한 작업들이 수행되고 있어요.

분석가들은 보고서와 액세스 권한이 있는 공개 데이터 세트 간의 관계를 수동으로 검색하고 재발견해요. 모든 정보는 일반적으로 공개되어 있고, 현지 법 집행 기관, 대중과 공유되고 사용될 수 있다는 점을 말씀드리고 싶어요. 이건 해결해야 할 문제이고, 그래프는 이런 지식을 서로 연결하는 자연스럽고 중요한 구조라고 생각해요.

저희 접근 방식은 국토 안보부가 Knowledge Graph를 구축하도록 돕는 것이었어요.

저희는 이전 보고서, 이벤트, 신문 기사, 다양한 유형의 공격을 수집해서 그래프로 연결하려고 노력했어요. DHS, FBI, NYPD 등과 협력했죠. 스프레드시트, 원시 데이터 세트, 손으로 쓴 메모, 보고서 등을 기반으로 그래프를 구축한 다음, 이걸 Neo4j 인스턴스에 통합했어요.

Knowledge Graph가 생성된 후에는 보고서와 이벤트 간의 관계를 살펴보고 유지 관리해요.

슈퍼볼을 조사하는 분석가는 작년 슈퍼볼이나 지난 10번의 슈퍼볼에서 어떤 위협이 있었는지 파악하기 위해 이전 보고서를 다시 찾아볼 필요가 없어요. 그런 유형의 정보를 연결할 수 있게 되는 거죠. 지역적 위협의 경우에도 마찬가지예요. 분석가는 Knowledge Graph를 사용해서 시간이 지남에 따라 유사한 유형의 이벤트를 찾는 것이 자연스러운 방식으로 정보를 하나로 묶을 수 있어요.

저희는 분석가들이 수행하는 작업에서 더 많은 운영 효율성을 갖도록 돕고 있어요. 그들의 시간은 매우 소중하고, 이 나라의 모든 사람에게 영향을 미치는 매우 중요한 문제를 해결하고 있기 때문에, 저희는 그래프를 통해 그들을 돕기 위해 할 수 있는 모든 일을 하려고 노력하고 있어요.

법의학 분석에서 벗어나고 싶은 마음도 있어요. 안 좋은 일이 생기면 다들 "아, 데이터에서 봤어"라고 말하잖아요. 데이터 내에서 추적해서 "그래, 이걸 잡았어야 했어, 저걸 잡았어야 했어"라고 말할 수 있게 하고 싶어요. 아직 일어나지 않은 사건을 예측 분석하는 것보다 이미 알려진 사건을 찾는 게 훨씬 쉽죠.

매번 정확해야 하고, 이전에 보지 못했던 것들에 대해 미래를 말할 수 있어야 해요. 여기에는 이전에 접촉한 적이 없는 사람들과 이전에 경험하지 못한 다양한 유형의 위협이 포함되죠. 이 부분이 이 분야를 정말 어렵게 만드는 이유에요. 우리는 이 분야에서 수년 동안 그래프를 사용해서 다양한 데이터 세트를 신속하게 연결하고, 그래프 알고리즘을 작성해서 이러한 질문에 최대한 빠르게 답할 수 있었어요.

국토안보부의 Knowledge Graph에 사용하는 데이터 소스는 실제로 세 가지 다른 데이터 세트에서 나와요.

첫 번째 데이터 세트는 파트너가 제공하는 보고서 모음이에요. 수십 개의 보고서가 있죠. 그리고 공개 데이터 세트를 사용하는데, 메릴랜드 대학교에는 START라는 글로벌 테러 데이터베이스를 활용하는 센터가 있어요. 여기는 다양한 이벤트에 대해 아주 잘 정의된 데이터베이스와 스키마를 가지고 있고, 데이터는 수십 년 전으로 거슬러 올라가요. 해당 데이터베이스에는 수만 개의 이벤트가 들어있죠.

또, 기본적으로 스프레드시트 형식의 목록도 있는데, 네브래스카 오마하 대학의 연구를 통해 알려진 국내 폭력 극단주의자 약 211명이 포함되어 있어요. 그들은 미국에서 자생하는 극단주의자들을 찾기 위해 신문 보도와 기타 정보를 검토했고, 여기에는 그들이 무엇을 했는지, 체포되었는지 여부, 어떤 유형의 공격을 받았는지, 살아 있는지 죽었는지, 그리고 그들에 대해 알려진 다른 정보가 포함되어 있죠.

우리는 이걸 구조화되지 않은 텍스트로 수집하기로 결정했어요.

이걸 `Node`가 있는 그래프로 바꿀 건데요. `Node`는 다양한 미디어, 이벤트, 그룹, 개인 및 위치가 될 수 있어요. 그리고 공격 유형과 위협 유형의 속성을 공격 유형으로 제공했어요. 예를 들어 폭발 장치, 차량 공격 등이 있죠.

그 다음 개인이나 그룹을 다른 유형의 공격과 연결하는 보고서가 있을 때마다 해당 그래프에 `Edge`를 배치했어요.

그렇게 해서 얻은 결과물은 다양한 조각들이었어요.

다음은 Wikipedia의 항목이에요.

2016년 11월 28일 오전 9시 52분(동부 표준시) 오하이오주 콜럼버스에 있는 오하이오 주립대학교 와츠 홀에서 테러리스트 차량 충돌 및 흉부 공격이 발생했습니다. 공격자는 소말리아 난민 압둘 라자크 알리 아르탄(Abdul Razak Ali Artan)이 최초 대응한 OSU 경찰관의 총에 맞아 사망했으며 13명이 부상으로 병원에 입원했다.

구조화되지 않은 텍스트를 가져와서 그래프 객체를 만들어요. 예를 들어, 다양한 유형의 `Node`를 나타내기 위해 색상이 다르게 지정되었죠. 공격자라는 사람이 있고 차량 공격과 첨단 무기라는 두 가지 공격 유형이 있어요. 위치는 오하이오 주립 대학이고요. 이 정보를 종합하기 위해 그래프를 작성하는 거예요.

위협 평가 보고서에서 우리는 처음에 114개의 `Node`와 163개의 `Edge`로 그래프를 만들었어요. 그래프는 그리 크지는 않지만 매우 귀중한 정보를 담고 있죠. `Node`의 다양한 색상은 위치, 개인, 그룹 등의 다양한 `Node` 유형을 나타내요.

우리는 이걸 출발점으로 삼아 글로벌 테러 데이터베이스를 구축했어요.

이 데이터베이스에는 약 120,000개의 이벤트가 각각 행으로 포함되어 있어요. 우리는 그 데이터베이스 내에서 국내 행사를 찾아보았고, 대략 70,000개 정도의 이벤트가 있었죠. 데이터베이스 `Schema`를 하나의 행이 이벤트를 나타내는 CSV 파일로 변환했어요. 해당 이벤트의 많은 기능이 해당 행의 셀로 존재하고요. Neo4j를 사용해서 시작점에 로드하고 Python 스크립트로 일부 사전 처리를 수행했죠.

이건 메릴랜드 대학의 START 센터에서 공개적으로 사용 가능한 데이터 세트에요. 그 다음 그래프 `Schema`와 수집을 살펴봤어요.

우리가 가지고 있는 다양한 데이터 세트를 어떻게 결합할까요? 우리는 원본 데이터베이스나 보고서에서 필드 매핑을 생각해냈어요. 우리는 Neo4j의 그래프 `Schema`가 무엇인지 살펴보고 결국 약 325,000개의 `Node`와 약 270만 개의 `Edge`가 있는 그래프를 구축했죠. 다시 한 번 말씀드리지만, 사람, 장소, 사물, 공격 등을 서로 연결하는 정보가 있으므로 그 `Edge`를 그래프에 추가했어요.

이를 통해 예시 `Query`에서 표현한 Neo4j의 아주 간단한 `Query`를 사용해서 분석가분들을 도울 수 있었어요.

다른 공격 유형이나 무기로 공격하는 개인이나 그룹과 관련된 모든 위치를 찾는 `Query`가 있을 수 있어요. 여기서는 해당 공격 유형 중 하나를 나타내는 X2라고 부르죠.

여기에 연관 `Query`를 수행하기 위한 `Query`가 있고, 결과가 여기 있어요. 다음은 Neo4j의 X2와 해당 유형의 공격과 관련된 주변 위치인데요. 이건 정말 간단한 `Query`이고, 저희는 이런 작업을 많이 했답니다.

모티브 찾기

분석가분들은 아마 이런 질문을 할 수도 있을 거예요. “X2와 X3 모두를 사용해서 공격하는 개인 또는 그룹과 관련된 모든 위치를 찾아보세요.” 이건 마치 날카로운 무기 공격과 차량 공격이 함께 있었던 위 Wikipedia 항목과 같은 경우죠. 여기 `Graph`에서 라벤더 색으로 표시된 개인을 찾아볼게요. 이들 중 일부는 두 가지 공격 유형을 모두 사용하는 데 연루된 사람들이에요. 이게 바로 모티프 찾기의 간단한 예시랍니다.

저희는 개인 간 최단 경로를 가지고 있어요.

두 개인 사이의 가장 가까운 연결 경로를 찾는 거죠. 여기서 `Query`는 왼쪽에 있고, 두 개인은 이 체인의 양 끝에 있어요. 저희는 해당 데이터에서 그 개인들 간의 연결을 찾으려고 하는 거랍니다.

예전에는 다양한 보고서를 살펴보고, 다양한 식별자를 확인하고, 여러 `Database`를 뒤져봐야 했고, 이런 연결을 놓치는 경우가 많았어요. 하지만 이 `Graph` 공간으로 넘어오니 감지되지 않았거나 과거에 검색하는 데 많은 시간이 걸렸던 연결을 쉽게 찾을 수 있게 되었죠.

데이터 연결

여기는 동일한 그룹에서 사용하는 두 미디어 사이에 엣지를 만드는 곳이에요. 여기 Neo4j와 이걸 수행할 수 있는 `Query`가 있답니다.

또한 `Graph`의 `Node`에 대해 PageRank 및 기타 유형의 중요도 순위를 실행했어요.

예를 들어, PageRank에서 점수가 가장 높은 개인을 찾는 거죠. 다음은 개인인 `Node`와 PageRank가 무엇인지에 대한 간단한 출력 결과에요.

이 `Graph`는 시각화를 위해 Gephi를 사용하고 있어요. 이건 미국 이벤트에서 실행되는 Louvain이라는 커뮤니티 감지 알고리즘이고, 색상은 서로 다른 커뮤니티를 나타낸답니다.

저는 중간에 `Node` 중요성을 가지고 있어요. 고유벡터 중심성 그리고 이러한 순위를 기반으로 더 영향력이 있거나 중요한 일부 행위자의 PageRank를 확인할 수 있죠. 고유벡터 중심성을 보고 그 중 일부의 그룹을 살펴보면 다음과 같아요.

첫 번째는 알려지지 않았지만 그룹을 추출하네요. 낙태 반대 극단주의자, 좌파 무장세력, 백인 극단주의자, 흑인 민족주의자, 동물 해방 전선. 이런 유형의 `Query`를 통해 이 데이터 세트 내에서 더 중심적이거나 더 영향력이 있는 일부 그룹을 파악할 수 있어요.

예측 `Graph` 분석은 실제로 데이터와 문제보다 앞서 나가고 있어요. 입력 `Graph`가 많으면 데이터를 부분적으로만 사용할 수 있으므로 데이터 세트를 스트리밍하거나 정보가 누락되기도 하죠. 더 많은 데이터를 갖고 싶지만, 가지고 있는 데이터를 바탕으로 빠르고 신속한 결정을 내려야 할 때도 있잖아요.

또한 이런 `Graph`는 시간이 지남에 따라 변하기도 해요. 고정된 `Graph`가 아니라는 거죠. 저희는 항상 무엇이 변화하고 있고 무슨 일이 일어나고 있는지 이해하려고 노력하고 있어요. 과거에는 중요하지 않았던 `Node`가 시간이 지남에 따라 매우 중요해질 수도 있고, 저희는 그걸 찾아낼 수 있기를 바라는 거랍니다.

예측 `Graph` 분석은 실제로 데이터가 채워지거나 본질적으로 변경될 때 분석에 어떤 일이 발생할지 예측하는 것을 목표로 하는 기술이에요.

저희는 실행 예시를 가지고 있어요. 페이지랭크
전이 학습이 포함된 훈련 모델을 사용해서 무작위 `Graph`를 훈련한 다음, 이걸 평가하거나 실제 `Graph`에서 실행할 수 있는 예측 PageRank가 있답니다.

이건 교육 유형을 수행하기 위해 거치는 프로세스를 강조해서 보여주는 거예요. 저희는 PageRank와 자연스럽게 연결되는 무작위 걷기를 수행했고, 이런 걷기를 사용해서 PageRank를 예측하는 `Neural Network` 시스템을 개발했답니다.

저희는 빠르고 신속하게 답변을 얻기 위해 이 모든 작업을 하고 있는 거에요.

저희는 세계에서 가장 큰 그래프에서도 이런 유형의 그래프 쿼리를 밀리초에서 초 단위로 실행할 수 있기를 바라요.

결론 및 감사의 말씀

결론을 맺으면서, 많은 분들께 감사의 말씀을 드리고 싶어요.

먼저 Jason Riedy, Anita Zakrzewska 박사님, Oded Green과 같은 연구 과학자분들께 감사드립니다. 그리고 이 그래프 분야에서 함께 작업해 온 많은 대학원생들에게도 감사 인사를 전하고 싶어요.

Neo4j와 함께하게 되어 정말 기쁘고, 저와 함께 Neo4j의 대규모 Knowledge Graph를 통한 예측 분석에 대해 배우는 데 관심을 가져주셔서 감사해요!

  • 페이지랭크

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

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

반응형
반응형
  • Cypher & GQL
  • Graph Data Science

Neo4j의 수석 과학자인 Jim Webber와 대화하는 건 언제나 즐겁죠! 그와 함께 코로나 시대의 어려움, Neo4j의 최근 제품 출시, APAC 및 그 이상의 그래프 생태계, 그리고 2023년 그래프 생태계에 대한 그의 예측 등 다양한 주제로 이야기를 나눌 기회가 있었어요. (힌트: 그래프는 절대 사라지지 않아요.)

그래프의 과거, 미래, 현재에 대한 그의 관점은 독자 여러분께 곧 어디에서나 볼 수 있게 될 이 기술에 대한 전문가의 시각을 제공해 줄 거예요. 우리 그라피스타들에게 다가올 멋진 한 해를 위해, 이 내용을 한번 살펴볼까요? 즐겁게 읽어주세요!

다니엘 응: 짐, 그거 예측이에요, 아니면 그냥 희망사항이에요?

짐 웨버: 그건 희망사항이죠, 다니엘. 보세요, 우리 모두 지난 몇 년간 정말 정신없는 시간을 보냈잖아요. 그걸 감안하면 2023년은 좀 차분한 해가 될 거라고 예측하는 건 꽤 대담한 걸지도 몰라요. 바라건대, 억지로 예측을 해야 한다면 2023년도 2022년만큼 정신없을 것 같아요.

다니엘 응: 아니면 그 이상일 수도 있겠네요.

짐 웨버: 아니면 그 이상이고요.

다니엘 응: 항공편 구하기도 2020년보다 더 쉬워지긴 힘들겠죠.

이번 코로나 시대를 되돌아보며

짐 웨버: 더 쉬워지진 않을 거예요. 결국 우리가 기술자로서 사람들에게 탄력적인 디지털 비즈니스를 구축할 수 있는 도구를 제공하는 게 중요한데, 2023년에는 비즈니스 환경이 점점 더 어려워지고 있다고 생각해요. 주요 경제국들이 불황에 빠져 있잖아요.

저는 지금 영국에 있는데, 상황이 정말 좋지 않아요. 어떤 사람들은 자해라고 할 수도 있지만, 아무튼 좋지 않은 상황이죠. 하지만 이런 역풍을 맞는 건 우리뿐만이 아니에요. 이게 중소기업, 중견기업, 대기업 모두에게 의미하는 건, 더욱 어려운 시기를 헤쳐나갈 수 있도록 탄력성과 효율성을 제공할 기술 백본을 찾고 있다는 거죠.

그래서 우리 모두는 지금까지 갇혀 있던 피난처에서 벗어날 기회를 찾기 위해 노력하고 있어요. 그러기 위해 점점 더 강력한 디지털 신경 시스템에 의존하게 될 거고, 저는 그런 강력한 디지털 신경 시스템을 갖춘 기업이 이 이상한 시대에서 더 빠르게 벗어날 거라고 생각해요.

그렇지 않고 아직도 수동으로 처리하는 사람들은 어려움을 겪을 거예요. 기회를 잡기가 힘들겠죠. 너무 일찍 뛰어들어서 위험을 감수하거나, 아니면 뒤처져서 중요한 기회를 놓치게 될 거예요. 그래서 2023년에는 유능한 디지털 비즈니스, 중요한 실제 업무를 수행하는 비즈니스의 성장을 기대하고 있어요. 이런 성장이 나머지 경제를 이전 모습과 비슷하게 회복시키는 데 도움이 될 거라고 생각해요.

다니엘 응: 네, 여기 APAC은 코로나19에 가장 먼저 직면했고, 또 가장 먼저 회복한 지역이라 축복받은 것 같아요. 저는 지금 싱가포르에 있는데, 디지털로의 전환이 정말 많이 일어나고 있어요. 강제적인 부분도 있고, 그냥 흐름을 타는 부분도 있죠. 하지만 짐의 말이 정말 맞아요. 이건 예측이 아니라, 오늘날 정부와 기업이 어떻게 변화하고 있는지 보여주는 현실이죠. 그리고 코로나 이후에는 모두가 "다음은 뭘까?"라고 생각하고 있어요.

짐 웨버: 맞아요, 호주와 뉴질랜드에도 계시죠? 아시아 전역에서 코로나19에 대한 대응은 정부마다 정말 달랐어요. 어떤 정부는 더 신중했고, 어떤 정부는 덜 위험을 회피했죠. 하지만 결국 거의 비슷한 출발선에 서게 된 것 같아요.

데이터에 구현된 기회를 포착할 준비가 된 지역, 국가, 주에서는 그렇지 않은 곳보다 더 빠르고, 더 좋고, 더 탄력적으로 일할 수 있을 거라고 생각해요. 중요한 건 이게 아시아 태평양 지역에만 국한된 일이 아니라는 거예요. 하지만 아시아 태평양 지역은 특히 성장이 기대되는 곳 같아요. 예를 들어 호주나 인도네시아 같은 아시아 태평양 지역의 경제는 항상 곧 성장해서 차세대 주역이 될 거라고 생각하잖아요.

제 생각에는 이 지역 전체가 가능성으로 가득 차 있고, 인프라를 활용해서 그 가능성을 현실로 만들 수 있을 때만 그럴 수 있을 거예요. 그렇기 때문에 데이터 제공자로서 Neo4j와 같은 회사가 해당 인프라의 핵심 블록이 되는 거죠. 내년에는 상황이 조금씩 정상으로 돌아가기 시작할 때, 사람들이 출발선에서 앞서 나갈 수 있도록 Graph 인프라를 통해 강력한 시스템을 구축하도록 도울 수 있기를 바라요. 물론 지금도 "조금씩 정상"이라는 말이 신중한 의견이라기보다는 터무니없는 추측처럼 들리긴 하지만요.

다니엘 응: 우리는 "New Normal"이라는 말을 많이 들었는데, 저는 사실 "No Normal"이라는 말에 더 끌려요.

짐 웨버: 그럴 수도 있겠네요. 2008년 이후로 '정상'이라는 건 사라졌으니까요. 앞으로 몇 년 동안 우리에게 "정상"이 뭘지는 아무도 모르죠. 지금은 패러다임 사이에 있는 것 같아요. 2008년에 적어도 금융화된 세계, 즉 서구에서는 바퀴가 빠져나갔고, 우리는 그걸 고치려고 오랫동안 애썼죠. 그러다가 민족주의 정치, 팬데믹 등으로 타격을 입었어요. 그리고 글로벌 금융 위기가 발생한 지 15년 후인 2023년의 세상은 완전히 달라 보인다고 생각해요. 과거로 돌아가서 2008년의 관점과 기술로 2023년을 고치려고 하는 건 정말 큰 실수일 거예요.

2022년 그래프 마일스톤

다니엘 응: 전적으로 동감해요. 그럼 거시경제적인 관점에서 좀 더 기술적인 관점으로 좁혀볼게요. 집중해주세요! 2022년에 대한 리뷰를 해볼 건데요. 짐, 2022년 그래프 기술의 주요 이정표는 무엇이었나요? 그리고 전 세계, 특히 APAC 지역의 기업과 정부에 어떤 영향을 미치고 있다고 생각하시나요?

짐 웨버: 네, 2022년은 정말 놀라운 한 해였던 것 같아요. Neo4j의 관점에서 보면, 올해는 우리가 경험한 것 중 가장 엔지니어링에 집중한 해였어요. 2022년에 우리는 200명의 엔지니어가 수년간 노력한 결과를 제품에 투자하고, 주고받았죠. 정말 놀랍지 않나요? 제가 12년, 13년 전에 Neo4j에 합류했을 때는 제품에 200년의 엔지니어링 시간을 투자할 수 있다는 건 상상도 못 할 일이었거든요.

우리가 10명도 안 되는 팀이었을 때는 모두가 엔지니어링을 한다고 해도 연간 최대 10년 정도의 엔지니어링 시간을 투자할 수 있었어요. 물론 그것도 쉽지 않았죠. 팀원 중 한 명이 너무 높은 위치에 있어서 더 이상 코딩을 할 수 없었으니까요.

하지만 보세요, 우리에게 일어난 일은 정말 다양하다고 생각해요. 우리는 Neo4j의 핵심 Graph Database의 매우 중요한 릴리스인 Neo4j 5를 출시했어요. 신뢰할 수 있는 디지털 백본이나 신경계를 원하는 기업들을 위한 거죠.

Neo4j 5는 Graph Database 기술에 있어서 큰 진전이에요. 스택의 모든 계층에서 전반적으로 효과적인 업데이트가 이루어졌죠. 쿼리 언어 스택에서 일부 쿼리는 이제 자율 클러스터라는 기능이 있는 클러스터링 스택을 통해 2~3배 더 빨라져요. 이에 대해서는 잠시 후에 다시 설명하겠지만, 이는 실제로 Neo4j 단일 목적 또는 다중 용도의 매우 큰 클러스터를 실행할 수 있다는 의미랍니다. 그리고 맨 아래에서도 그래프 데이터의 이동을 컴퓨터의 다양한 장치 간 바이트로 처리하는 데이터베이스의 핵심인 스토리지 엔진이 완전히 개선되어 더욱 빨라졌어요.

Neo4j 5와 자율 클러스터링, 게임 체인저!

짐 웨버: 핵심만 간단히 말하자면, 자율 클러스터링은 Neo4j의 큰 기능 중 하나이며 일반적인 기업 요구 사항을 해결한다고 생각해요. 제가 Neo4j에 있는 동안, 그리고 2022년에도 꽤 많이 본 것은 비즈니스, 특히 중견 기업과 대기업을 대상으로 더 많은 그래프를 작성하는 것이었어요.

몇 년 전 그들은 그래프에 발을 담그고 꽤 좋아했죠. 입소문이 퍼지면서 몇 가지 프로젝트가 비즈니스를 중심으로 유기적으로 나타났어요. 그런 다음 그들은 그것을 제도화했고요. 따라서 Neo4j는 선택한 Graph Database 또는 기본 Graph Database이지만, 많은 대기업의 경우 각 그래프를 별도의 클러스터로 실행하도록 만들었어요. 아키텍처 수준에서는 논리적으로 "글쎄, 괜찮아"라고 말할 수 있지만요.

그들이 정말로 원했던 것은 하나의 클러스터를 실행하고 그 클러스터에 각각 특정 부서나 비즈니스 요구 사항을 충족하는 많은 그래프를 갖는 것이었어요. 이것이 바로 자율 클러스터가 하는 일이죠. 이제 여러분은 "내 사업에는 내 그래프 워크로드를 실행할 서버가 10~100대 필요해요."라고 말할 수 있는 위치에 있는 거예요. 좋아요. 클라우드나 데이터 센터에서 이를 구현하죠. 이제 해당 서버에 대해 개별 사용자는 보안 권한이 허용되는 한 효과적으로 데이터베이스의 존재를 선언할 수 있어요.

그들은 "보세요, 저는 제품 카탈로그 담당자 중 한 명인데 데이터베이스가 필요해요. 그리고 3개의 중복성을 갖고 싶어요."라고 말하죠. 즉, 이 100개의 서버 주변에는 항상 내 데이터 복사본이 3개 있다는 뜻이에요. 예를 들어 영업 부서에 있는 Daniel은 "글쎄요, 판매 그래프가 필요하고 여기에는 좀 더 안전이 필요하고 이 그래프에서 좀 더 확장이 필요하기 때문에 5개의 중복성이 필요해요."라고 말할 수 있고요.

그것은 수백 개의 서버에 선언됩니다. 그리고 자율 클러스터링은 불변성, 즉 중복성 불변성이 항상 존재하도록 보장해요. 심지어 기계가 죽고 교체되는 경우에도 마찬가지죠. 또한 리소스에 대한 액세스를 놓고 다투지 않도록 데이터베이스를 이동시켜요.

따라서 예를 들어 매우 많은 쓰기 작업을 수행하는 두 개의 특정 데이터베이스가 있는 경우, 자율 클러스터는 이를 분리하여 상호적으로 가볍게 사용되는 서버에 있도록 하여 더 나은 처리량을 얻어요. 이것이 의미하는 바는, 특히 대규모 기업의 경우 단일 클러스터를 구현한 다음 직원들이 스스로 서비스를 제공할 수 있다는 것이죠. 그리고 대부분의 경우 클러스터는 자체적으로 관리해요. 즉, 사물을 이동하고, 나쁜 일이 발생하면 복구하는 등의 작업을 수행하죠. 제가 말했듯이 자율 클러스터는 그 기능에 대해 좋은 느낌을 줘요.

사용자 및 기업을 위한 Neo4j Operations Manager

짐 웨버: 게다가 우리는 운영에 중점을 둔 제품인 Neo4j Operations Manager도 출시했어요. 우리는 그것을 NOM이라고 부르는데, 맛있을 것 같네요. 실제로 해당 클러스터에 대한 인간의 운영 입력을 원하는 경우, Operations Manager는 이제 시스템에 대한 풍부한 대시보드를 제공해요.

우리는 운영 전문가가 시스템에 개입할 수 있도록 매우 의미 있는 방식으로 제공하기 위해 유용한 측정항목과 데이터를 수집하기 시작했어요. 더 많은 머신을 온라인으로 가져오고, 오래된 그래프를 삭제하고, 새로운 그래프를 생성하는 등의 작업을 수행하고 보안 로그를 확보하는 등 낮은 수준의 작업을 수행하고 모든 것이 괜찮은지 확인하죠. 그래프 친화적이고 그래프 친화적인 비즈니스에 종사하는 사람의 운영자로서 이제 여러분의 작업이 훨씬 더 즐거워졌다는 사실을 알게 될 거예요. 훨씬 적은 노력으로 더 많은 작업을 수행할 수 있을 것이고요.

실제로 최종 사용자는 훨씬 더 쉽게 그래프를 실현하고 그래프를 생성할 수 있게 돼요. 정말 행복한 일이겠죠? 귀중한 그래프가 많이 있다고 생각하고 Neo4j 클러스터링 소프트웨어 구축에 도움을 준 만큼 그 내용을 좋아하기 때문이에요. 매우 기술적이고 괴상하고 훌륭하지만 가치가 없어요. 그곳은 가치가 공개되는 곳이 아니죠. 비즈니스 문제를 해결하면 가치가 공개돼요. 따라서 운영 관련 작업과 모든 기술적인 괴상함을 통해 귀중한 비즈니스 문제를 해결하는 데 훨씬 더 가까워졌어요.

다니엘 응: 이제 많은 기술적인 부분에 NOM, NOM, NOM이 있으므로, 이것이 사용 사례나 산업에 가장 큰 영향을 미칠 수 있는 것은 무엇인가요? 비즈니스 관점에서 설명할 수 있다면 정말 좋을 것 같아요.

짐 웨버: 저는 그래프가 수평적 기술이라는 점을 항상 주장해왔고, 이것이 점점 더 분명해지고 있다고 생각해요. Neo4j에 합류하기 전 첫 번째 그래프 프로젝트인 그래프를 처음 시작했을 때, 기술에 대해 듣고 사업에 사용하고 있을 때 저는 통신 사업에 종사하고 있었는데 문제는 제품 추천이었어요. 그것은 정말 인간적으로 그래프로 모델링된 것으로 밝혀졌죠.

Neo4j에 합류하기 몇 년 전, 저는 사용자로서 제품 추천, 사기 탐지, 통신 및 데이터 네트워크의 단일 실패 지점 등에 관해 유사한 작업을 수행했어요. 그리고 수년 동안 Neo4j에서 지방 정부와 중앙 정부 수준 모두에서 의료 경로, 물류, 멋진 금융, 정부 관련 업무 등을 보았어요. 경찰, 정보 기관을 본 적이 있어요. 저는 그래프로 구축된 기차와 도시 대중교통 시스템을 본 적이 있습니다. 이는 매우 수평적인 플레이에요.

내가 현재 기회를 얻기 위해 어려운 데이터 문제를 해결하려는 기업이라면 해당 비즈니스에 참여하는 기술 담당자에게 다음과 같은 질문을 던질 거예요. "그렇지 않을 것이다 그래프를 선택하시나요?” 고충실도 모델이 필요한 시스템이 있고 많은 시스템에 고충실도 도메인 모델이 있는 경우, 그래프는 해당 데이터 모델을 캡처하고 충실도가 높고 복잡한 실제 데이터 모델을 시스템에 가져오는 가장 즐겁고 인도적이며 성능이 뛰어난 방법이에요.
모든 상호 연결성, 일부는 균일하고 일부는 균일하지 않고 일부는 밀도가 높으며 일부는 드물고 일부는 규칙적이며 일부는 불규칙한 모든 모델을 캡처해 보세요. 관계형 데이터베이스나 문서 데이터베이스 또는 열 데이터베이스를 사용하여 캡처해 보십시오.

저는 이 세 가지 범주 모두 훌륭한 데이터베이스라고 생각해요. 하지만 데이터가 딱 떨어지게 정형화되어 있지 않거나, 열 형태로 깔끔하게 정리되어 있지 않은 현실 세계의 모델을 캡처하려고 할 때, 이런 데이터베이스들은 연결성을 이해하지 못하기 때문에 어려움을 겪고 성능 저하가 발생하죠.

기술자로서 복잡한 도메인 모델을 가진 새로운 데이터 시스템을 구축해야 한다면, 2023년의 질문은 이거예요. 이 말을 듣는 모든 기술자들에게 도전하는 건데요. "왜 Graph가 첫 번째 선택이 되지 않아야 할까요?" 아마 과거에는 우리가 커뮤니티로서 Graph에 익숙해지는 과정이었기 때문에 이런 질문이 흔치 않았을 거예요. 저는 10년 넘게 Graph를 사용해 왔고, 항상 "왜 Graph를 선택하지 않겠어요?"라고 말해왔죠.

이제 운영 기반과 커뮤니티에 퍼져 있는 Graph에 대한 지식(이것 자체가 Graph죠!)을 고려하면, "이 프로젝트에서 Graph가 첫 번째 데이터베이스가 아닌 이유는 무엇인가요?"라고 자신 있게 물을 수 있어요. 많은 경우, 기술 전문가로서 프로젝트를 바라볼 때, 기존의 편견이나 선호하는 도구, 익숙한 도구 체인을 잠시 내려놓고 솔직하게 "사실 Graph가 내 요구 사항을 아주 잘 충족시킬 수 있겠네"라고 생각하게 될 거예요.

기업이 Graph 챌린지에 참여해야 하는 이유

짐 웨버: 물론, Daniel, 현실적인 부분도 있죠. 팀원 모두가 Graph를 잘 아는 건 아니고, 학습 곡선이 있을 거예요. 이 학습 곡선이 어떤 사람들에게는 맞지 않을 수도 있고, 예전처럼 문서나 관계형 데이터베이스로 돌아가려고 할 수도 있겠죠. 그리고 그런 기술을 사용해서 시스템이 어찌저찌 돌아가게 만들려고 더 열심히 노력해야 할 거예요.

하지만 도전을 받아들이는 사람들에게는 Graph 기술을 향상시키는 게 그렇게 큰 도전은 아니라고 생각해요. 수백만 명의 사람들이 겪는 학습 곡선을 극복하고 나면, 실제로 그렇게 어렵지 않다는 걸 알게 될 거예요. 극복하고 나면 정말 뿌듯할 거예요.

다니엘 응: 작년에 GraphSummit에서 저희를 도와주시면서 사람들이 "왜 Graph가 필요한지, Graph로 무엇을 할 수 있는지, 그리고 Graph를 어떻게 사용할 수 있는지"를 이해하도록 도와주셨죠. 올해 GraphSummit도 다시 열릴 예정이고, 여기서도 뵙기를 바라요.

APAC 지역을 살펴보면, Jim, 저는 이런 생각을 해요. 사람이 많을수록 데이터도 많아진다는 거죠. 왜냐하면 사람이 데이터를 생산하니까요. 실제로 세계 4대 인구 중 3개가 인도와 인도네시아를 포함한 APAC에 있어요. 그래서 "수집하고 활용해야 하는 데이터가 너무 많기 때문에 대규모 기술이 필요하다"고 말하는 중요한 동기가 되는 거죠.

2023년 Graph 예측

다니엘 응: 예측에 대해 좀 더 자세히 알아볼까요? 너무 욕심부리지 말고 한 10개 정도... 아니, 3부작 정도로 하는 게 좋을 것 같아요. Graph 세계가 2022년부터 2023년까지 우리가 물려받은 시장과 비교해서 비즈니스 규모를 확장하고 더 빠르게 운영하는 데 어떻게 도움이 될지에 대한 예측을 어떻게 보시는지 궁금해요.

짐 웨버: 그 부분에 대해 다시 생각해볼게요. 제가 아시아 태평양 지역에서 팀과 함께 시간을 보낼 수 있었던 건 정말 행운이었어요. 거기서 만난 사람들 중에는 Graph를 정말 좋아하는 사람들이 많았죠. 솔직히 제 일자리가 불안할 정도로 Graph에 능숙한 사람들이 이 지역 전체에 있다는 게 분명했어요. 정말 대단했죠.

그리고 스스로를 숙련자라고 생각하거나, Graph 작업을 조금 해본 초보자라고 생각하면서 기술을 향상시키거나 더 많은 것을 배우고 싶어하는 사람들이 많아요. 또, 완전히 새로운 사람들, Graph에 대해 들어본 적은 있고 Graph가 자신에게 어떤 도움이 될지 조금은 이해하는 호기심 많은 사람들도 있죠. 일반적으로 그들은 열린 마음으로 배우고 싶어해요.

제가 여러분과 함께 그곳에 있었을 때, 진정한 모션 빌딩의 느낌을 받았어요. 사람들은 Graph Database에 관심이 있거나, 데이터 과학 세계에서 온 비교적 새로운 커뮤니티이기 때문에 저희를 찾아왔어요. 그들은 Graph가 자신들에게 뭔가를 해줄 수 있다는 암시를 갖고 있었죠. 실제로 저희에게 와서는 "이것의 낮은 수준의 물린 완충 장치에 대해 말해주세요."라거나 "백업에 어떻게 액세스하나요?" 같은 질문을 하는 사람은 아무도 없었어요.

대부분의 사람들은 전반적으로 비즈니스 문제의 핵심에 집중하고 있었는데, 그게 정말 놀라웠어요. 지역 전체에 걸쳐 커뮤니티가 훌륭하게 혼합된 것처럼 느껴졌지만, 아마 세 가지 트렌드가 있을 거라고 믿게 되었어요. 제 머릿속에 있는 세 가지와 일치하기 때문에 3부작을 제안해주셔서 감사해요.

짐 웨버의 트렌드 3부작

짐 웨버: 세 가지가 정말 중요할 거예요. 첫 번째는 Graph Database예요. Neo4j의 핵심 사업이기 때문에 굳이 말할 필요도 없다고 생각해요. 사업을 시작했을 때, 우리는 어려운 비즈니스 문제를 해결할 수 있는 새로운 종류의 데이터베이스를 구축하는 데 집중했죠. Graph Database는 올해 더 많은 사람들이 선택하게 되면서 엄청나게 성장할 거라고 생각해요.

APAC 커뮤니티를 생각해보면, 초보자는 전문가가 되고, 새로운 사람들은 초보자와 숙련자가 되는 등 성장할 거예요. 그 피라미드의 기반은 커질 것이고, Graph Database는 그 모든 것을 뒷받침할 거예요. 이 기술을 채택하는 기업에게는 엄청난 가치가 있죠.

두 번째로 말씀드리고 싶은 것은 Graph 데이터 과학이에요. 이제 Graph 데이터 과학은 비록 작은 출발점에서 시작했지만, Graph Database보다 빠르게 성장할 거라고 생각해요. Graph 데이터 과학의 개념은 Graph의 토폴로지를 가져와서, 토폴로지가 없는 다른 데이터 모델에서는 할 수 없는 훨씬 더 풍부한 방식으로 데이터를 분석할 수 있다는 거예요.

Machine Learning 요소도 해당 버킷에 넣을 거예요. 일단 토폴로지가 있고, 예를 들어 예측 모델을 구축하기 위한 feature를 추출하려는 경우, Graph의 토폴로지 feature, PageRank 또는 중심성 점수 등을 더 나은 예측 모델을 만들기 위한 feature로 사용할 수도 있기 때문이죠.

그래서 그것들을 하나로 묶을게요. Graph 데이터 과학과 Machine Learning 움직임도 거대해질 거라고 생각해요. 사람들은 이미 많은 양의 데이터를 가지고 있어요. 이걸 통해 비즈니스 혜택을 얻을 수 있기 때문에 해당 데이터를 Graph로 보도록 권장할 거예요. 분류기 점수를 10% 향상시키는 것만으로도 엄청난 비즈니스 이점을 얻을 수 있어요. 이는 Graph feature를 다른 feature와 혼합해서 수행할 수 있는 작업이죠.

세 번째로 증가할 점은 여기 Neo4j에서 볼 수 있는 측정 항목을 통해 어느 정도 검증되었는데, 점점 더 많은 사람들이 먼저 클라우드로 이동하고 있다는 거예요. Neo4j의 역사 대부분에서 우리는 데이터베이스를 구축하고 논리적으로 CD ROM을 귀하에게 게시했으며 귀하는 이를 설치했어요. 설정을 더블클릭하세요.

다니엘 응: 컴퓨터 얘기는 꺼내지 마세요!

좋은 시절이었죠. 하지만 대규모 금융 기관이나 정부처럼 인프라를 갖추고 데이터 센터를 직접 운영하는 조직에게는 딱 맞는 방식이었을 거예요. 데이터 센터 소유를 원치 않거나, 그게 전략적으로 중요하지 않은 다른 많은 분들은 클라우드 환경에서 Graph Database를 사용하고 싶어 하셨죠.

이제 2023년! Neo4j는 드디어 해냈어요. AuraDB라는 시스템을 갖게 되었거든요. 이건 AWS, Azure, GCP, 이렇게 3대 클라우드에서 돌아가는 서비스형 Neo4j 데이터베이스예요. 그리고 Aura DS, 즉 Aura Data Science라는 서비스도 있죠. 이건 서비스형 그래프 데이터 과학이라고 할 수 있어요.

이런 기능들을 사용할 수 있게 되면서, 특히 그래프를 처음 접하는 분들이나 커뮤니티의 새로운 분들은 클라우드를 기본으로 사용하게 될 거라고 생각해요. 2023년은 클라우드가 우리에게 큰 힘이 될 것 같아요. Data Science도 마찬가지고요. 물론, 데이터베이스 자체가 계속 성장할 거라는 얘기도 빼놓을 수 없겠죠? 데이터베이스는 여전히 성장세니까요, 다니엘.

Graph Database의 성장과 지속성

지난 10년 동안 Graph Database는 현대적인 기준으로 최대 400%까지 성장했고, 그 성장세는 멈출 기미가 안 보여요. 통계에 관심 있는 분들은 이렇게 말할 수도 있겠죠. "짐, 2012년에 설치 기반이 10개였을 때는 400% 성장하기 쉬웠겠죠." 하지만 지금은 설치 기반이 수만 개가 넘고, 여전히 400% 성장하고 있어요. 오픈 소스나 Neo4j, 오픈 소스 방식으로 사용하는 분들까지 포함하면 훨씬 더 많겠죠. 이렇게 큰 설치 기반으로 매년 400%씩 성장한다는 건 더 이상 틈새 시장이나 작은 트렌드가 아니에요. 2023년 이후에도 데이터가 폭발적으로 증가하는 거대한 흐름이라는 거죠.

Neo4j는 사람들이 Graph Database를 더 쉽게 사용할 수 있도록 노력하고 있어요. AuraDB를 무료로 제공하고 있고, 실제로 바로 로그인해서 무료로 사용해 볼 수도 있고요.

맞아요. Neo4j Desktop 앱을 써서 노트북에 Neo4j를 다운로드하는 대신, 무료 클라우드 서비스를 쓰고 싶다면 AuraDB Free를 사용하면 돼요. 다니엘 덕분에 제가 Neo4j에서 일하는 걸 좋아하는 이유가 다시 떠올랐어요. 우리가 무료로 뭔가를 제공한다는 게 대단한 일은 아니지만, Neo4j에는 제가 항상 높이 평가하는 인간적인 면모가 있거든요.

예를 들어, "여기 무료 데이터베이스가 있고, 그걸 기반으로 작은 시스템을 만들 수 있어요." 같은 거죠. 작은 시스템을 돌릴 수 있고, Cypher Query Language도 사용할 수 있고요. 기술적인 얘기로 돌아가서 죄송하지만, Cypher Query Language는 사용하기 쉽도록 특별히 설계됐어요. 우리 모두 다른 쿼리 언어에 대한 경험이 있잖아요.

SQL도 다들 봤을 거고요. 사실 Neo4j는 최초의 최신 그래프 쿼리 언어인 Gremlin을 개발했지만, Cypher도 만들었어요. 왜냐하면 다른 언어들을 살펴보니 추론하기 어렵고, 읽기 어렵고, 디버그하기 어렵다는 걸 알게 됐거든요. 저희 엔지니어링 팀은 Microsoft VISIO를 정말 좋아했어요. 그래프, 원, 화살표 그림을 그리고 화살표 안에 작은 라벨을 쓰곤 했죠.

그러던 어느 날 엔지니어 한 명이 "우리가 다이어그램을 많이 공유하니까, 쿼리 언어로 그림을 그리고 그걸 사용해서 데이터베이스에 데이터를 저장하고 쿼리할 수 있다면 좋지 않을까?"라고 말했어요. 물론 다들 웃으면서 "시각적 언어는 안 될 거야"라고 했죠. 제 박사 학위가 시각 언어 분야였거든요.

저는 시각적 언어가 작동하도록 만들기 위해 정말 애썼어요. 하지만 몇몇 분들이 이 아이디어를 열심히 파고들어서 지금의 Cypher에서 볼 수 있는 멋진 ASCII를 만들어냈죠. 이건 최종 사용자, 심지어 기술 지식이 없는 최종 사용자와 그들의 데이터 사이의 격차가 크게 줄어든다는 걸 의미해요. 그래프와 Neo4j를 사용할 때 정말 좋고 따뜻하게 느껴지는 점은, 일반 사람들이 데이터에 더 쉽게 접근할 수 있도록 진입 장벽을 낮추기 위해 끊임없이 노력한다는 거예요.

Cypher 쿼리를 직접 작성하지 못하더라도 Bloom 시각화 도구를 사용하면 자연어 쿼리를 입력할 수 있고, Bloom이 내부적으로 Cypher로 바꿔줘요. 그러면 그래프가 추천 사항을 보여줄 수도 있고요. 일종의 그래프 기반 자동 완성 기능인데, 데이터를 다루는 정말 좋은 방법이기도 하죠.

사실 이번 주 초에 영국에서 한 고객사를 만났는데, 큰 통신사이자 미디어 회사였어요. 그분들이 저에게 "처음으로 상사에게 그래프를 보여줬는데, 데이터를 직접 볼 수 있게 되자 정말 놀라워하셨어요"라고 말씀하시더라고요. 사람과 데이터 사이의 장벽을 허무는 건 정말 멋진 일이에요.

딱 맞는 표현이 있죠. "말 한마디에 천 냥 빚을 갚는다"!

"천 마디 말보다 한 장의 그림이 낫다" 또는 좀 더 기술적으로 표현하면 "당신이 그리는 것이 당신이 저장하는 것이다"라고 할 수 있겠네요. 이게 바로 Neo4j 엔지니어링 팀이 중요하게 생각하는 부분이에요.

정말 멋지네요. 저희는 AuraDB 전문가도 보유하고 있어요. 특히 APAC 지역에 계신 분들에게는 정말 의미가 클 거예요. 왜냐하면 이 지역에는 SMB 기업이 많고, 대기업과는 완전히 다른 비즈니스 모델을 가지고 있거든요. 대기업의 경우에도 부서 차원에서 AuraDB 전문가 그래프를 시작점으로 삼아서 "일단 한번 시도해 보자. 아직 완전히 도입하고 싶지는 않아"라고 말할 수도 있고요. 그래서 저희는 사람들이 더 쉽게 사용할 수 있도록 만들고 있어요.

짐, 2023년은 그래프 기술이 더욱 풍성해지는 변곡점을 맞이하는 중요한 해가 될까요?

2023년: 그래프 기술의 중요한 해

변곡점을 향해 나아가는 동안에는 선형적으로 보이기 때문에 변곡점을 정확히 짚어내기가 어려워요. 미시적으로 보면 변곡점은 마치 그라데이션이 있는 선처럼 보이죠. 변곡점을 지나고 나서야 축소해서 볼 수 있고, 변곡점을 회고적으로 판단할 수 있게 돼요. 솔직히 말하면, 저도 잘 모르겠어요, 다니엘. 제가 확실히 아는 건 앞서 말씀드린 것처럼 그래프가 매년 400%씩 성장해 왔다는 거예요. 앞으로도 매년 400%씩 계속 성장할 수 있다면, 실리콘 밸리 사람들이 말하는 "하키 스틱 곡선"을 그리게 되겠죠. 2023년에 그런 일이 일어날지는 모르겠지만, 그래프가 다시 한번 엄청나게 성장할 거라고 확신해요.

올해 4배 성장을 예상한다면, 정말 거대한 시장인 거죠. 그래프를 접하고, 그 안에서 가치를 얻고, 사용하는 걸 즐기는 사람들이 정말 많아요. 어쩌면 데이터 기술에 대한 사랑이 다시 불붙을지도 모르겠어요. 음, 이게 바로 변곡점일까요? 아직은 확실히 말하기 어렵지만, 10년 뒤에 2023년을 다시 돌아보면 명확한 답을 얻을 수 있을 거예요. 그때는 2023년이 변곡점이었다는 게 분명해질 테니까요.

하지만 분명한 건 그래프는 2023년에 강세를 보일 거고, 앞으로도 계속 성장할 가능성이 높다는 거예요. 물론 저는 편향된 시각을 가지고 있고, 그래프를 정말 좋아하고 깊이 빠져있긴 하지만요. 지금 업계 분석가들이나 Gartner 같은 곳을 보면, 꽤나 보수적인 입장을 취하고 있다는 걸 알 수 있어요. 제 생각엔 그분들은 고객에게 섣불리 6개월 안에 사라질지도 모르는 틈새 기술을 추천하고 싶어하지 않는 것 같아요.

기술이 얼마나 오래 지속될지, 합법적인지, 수명이 얼마나 될지 확인하고 싶어하는 거죠. 그런데 이제는 모두가 그래프에 대해 이야기하고 있어요. 지난 몇 년 동안도 그랬지만, 이제는 2025년까지 Machine Learning의 50%가 그래프와 관련될 거라는 놀라운 예측도 나오고 있어요. 이런 놀라운 통계는 그래프가 전 세계적으로 얼마나 빠르게 성장하고 있는지를 보여주는 거죠. 특히 APAC 지역의 기술적인 저력을 생각하면, 그래프가 다른 지역보다 훨씬 빠르게 성장할 거라고 믿을 만한 이유가 충분하다고 생각해요.

다니엘 응: 감사합니다. 온라인 도박이나 온라인 전자 상거래를 생각해 보면, 사이버 보안이나 사기 탐지와 같은 주요 동인이 많다는 걸 알 수 있어요. APAC은 이런 분야에서 거대한 시장이죠. 그래서 저는 정말 긍정적으로 보고 있어요. APAC의 인력 규모와 여러 동인들을 고려하면, 변곡점 여부와 관계없이 우리는 계속 성장할 거고, 아주 잘 해낼 수 있을 거예요.

Jim, 마지막으로 요약하자면, Graph Database, 그래프 데이터 과학, 클라우드를 통한 전달 메커니즘에 대해 이야기해주셨는데요. 그래프를 발전시키는 여러 요소에 대해 말씀해주셨어요. 이번 웨비나의 최종 요약은 무엇일까요?

짐 웨버: 올해는 정말 기대돼요. APAC과 전 세계 커뮤니티는 정말 놀라워요. 사람들이 구축하고 있는 시스템들을 보면 솔직히 감탄하게 돼요. 사람들이 저에게 자신이 뭘 만들고 있는지 이야기해주는 게 제 직업의 가장 멋진 점이죠. 그리고 그들은 12년 동안이나 저를 놀라게 해왔어요. 2023년도 다르지 않을 거라고 생각해요. GraphSummit에 가면 사람들이 제 어깨를 툭 치면서 뭔가를 이야기해줄 거고, 그게 잠시 동안 저를 당황하게 만들겠죠. 그러다가 갑자기 '아하!' 하는 순간이 올 거예요.

제 생각에 2023년에는 사람들이 그래프를 사용해서 정말 놀라운 시스템을 구축할 거예요. 2022년이나 그 이전에는 불가능해 보이거나 상상조차 할 수 없었던 것들이요. 사람들은 그래프를 보면서 놀라운 아이디어를 떠올릴 거예요. 그리고 저는 이 지역 전체에서 이런 일들이 더 많이 일어날 거라고 생각해요.

앞서 호주나 인도네시아를 언급했는데요. 두 나라 모두에서 일이 본격적으로 시작되고 있다고 생각해요. 올바른 기술적 기반이 있고, 새로운 것에 대한 갈망이 있으며, 과거의 제약에 얽매일 필요가 없고, 앞으로 나아가는 데 도움이 될 문화가 있다고 말씀드렸죠.

저는 2023년에 그래프가 크게 성장할 거라고 생각해요. 만약 여러분의 비즈니스에서 아직 그래프를 활용하고 있지 않다면, 어딘가에 그래프가 존재하고 있고, 동료 중 일부가 그걸 다루고 있으며, 곧 여러분에게도 다가올 거라는 걸 기억하세요. 미리 준비하세요! Neo4j.com에 오셔서 GraphAcademy 강좌를 수강하고, 무료 eBook을 다운로드하고, 다른 사람들과 이야기를 나눠보세요. 여러분의 지역에 커뮤니티가 있는데, 이 커뮤니티가 여러분의 기술 향상을 도와줄 거예요. 그리고 저 같은 옛날 데이터 전문가들은 이제 컴퓨팅 측면, 즉 그래프 데이터 과학이 그래프 컴퓨팅이라는 점을 깨달아야 해요.

대학에서 컴퓨터 과학을 공부한 사람들은, 좋든 싫든 그래프 알고리즘 같은 것들에 대한 기억을 떠올리게 될 거예요. 비즈니스 의사 결정권자, 즉 IT 담당자에게 문제 해결을 요청하는 사람들은 더 이상 비즈니스 요구 사항을 충족하지 못하는 데이터 모델에 얽매일 필요가 없다는 걸 알아야 한다고 생각해요.

그래프는 비즈니스 요구 사항을 충족하는 유연한 데이터 모델이에요. 그리고 IT 직원이 "안 돼요"라고 말한다면, "글쎄요, 이걸 위해 그래프 기술 사용을 고려해본 적이 있나요? 그 영국인 말을 들어보니 그래프가 복잡한 도메인 모델도 처리할 만큼 충분히 유연하다고 하던데요."라고 말할 수 있어야 해요. 그리고 IT 담당자가 여전히 "안 돼요"라고 말한다면, "왜 이걸 시도해보지 않았죠? 이게 우리에게 특별한 도움이 될 수 있을 텐데요."라고 말할 타당한 이유가 있다고 생각해요.

자, 여러분! 파도가 몰려오고 있어요. 다니엘, 앞서 말씀드린 것처럼 이게 변곡점일까요? 글쎄요, 잘 모르겠어요. 그게 변곡점인지 아닌지는 몇 년 후에나 알 수 있겠지만, 확실한 건 그래프는 사라지지 않을 거라는 거예요. 계속 성장하고 있을 뿐이죠. 따라서 Deep Learning 기술 전문가와 분석가, 그리고 비즈니스 사용자 전반에 걸쳐 올해 그래프가 상당한 영향을 미칠 거라고 예상돼요.

Neo4j 기술을 한 단계 더 발전시키고 싶으신가요? 온라인 교육 강좌 중 하나를 수강하거나 인증을 받아보세요. GraphAcademy에서 레벨 업!
  • 2023년 예측
  • APAC
  • Graph Data Science

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

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

반응형
반응형

거시적 위험의 미래가 행위적이고 상호 연결된 이유

오전 3시 14분에 전화벨이 울렸다. 번화한 산살바도르에서 대규모 음료 유통 회사를 운영하는 로베르토에게 그 소리는 일반적으로 창고 경보가 울리거나 재고가 정체되는 두 가지 중 하나를 의미합니다.

"돈 로베르토." 반대편에서 목소리가 갈라졌다. 그의 물류 책임자였습니다. "항로가 수 마일에 걸쳐 정체되어 있습니다. 온두라스 쪽에서 시위가 벌어지고 있습니다. 청량음료 선적은 최소 48시간 동안 움직이지 않습니다."

예전 같으면 로베르토는 딱딱한 커피를 부어 추측을 시작했을 것이다. 그는 온두라스의 음료 제조업체에 전화하여 경로를 변경할 수 있는지 확인했습니다. 그는 공급업체의 생산도 중단될지 궁금해하면서 창고의 원당 수준을 확인하곤 했습니다. 그는 산살바도르의 선반이 비어 있지 않기를 기도했습니다.

하지만 오늘 아침 로베르토는 추측하지 못했습니다. 그는 노트북을 열었습니다.

파급력 매핑: 원당에서 허브까지

Roberto는 "트럭"만 보는 것이 아닙니다. 그는 본다지식 그래프. 그에게 국경에서의 지연은 정적인 사건이 아닙니다. 그것은 살아있는 웹의 노드입니다. 아래 영상은 단순한 지도가 아닙니다. 이는 관계가 기본 데이터 포인트이고 모든 국경 통과, 상업 허브 및 공급업체를 상호 연결된 노드로 처리하는 의미 계층입니다.

국경 통과(노란색), 상업 중심지(보라색)

이러한 종속성을 매핑함으로써 시스템은 El Amatillo(지정된)의 중단을 전체 네트워크에 즉시 전파하여 구조화되지 않은 뉴스, 거시 경제 지표 및 물류 로그를 중앙 아메리카 경제의 예측 가능하고 횡단 가능한 디지털 트윈으로 변환할 수 있습니다.

아래 그래프 모델을 살펴보세요. 이는 단순한 노드의 집합이 아니라 경제적 인과관계에 대한 청사진입니다.

네트워크의 가장자리에서 (황갈색 노드)는 주요 트리거, 즉 파급되는 감정과 이벤트 역할을 합니다., , , 또는 . 이 생태계에서 원자재는 필수 원재료로 사용됩니다. that 시장에 내놓다.(밝은 파란색 노드)는 특정에 고정되어 있으며 전체 시스템은: 제품이 물리적으로 a를 통과할 때 비즈니스와 공급업체 간에 가치가 교환되는 순간입니다.

경제적 피해의 청사진

해당 경계 노드가 빨간색으로 바뀌면 그래프는 재해 속도를 계산합니다. 트럭이 멈춰 있기 때문에 산살바도르에 있는 Roberto의 재고는 48시간 내에 "임계 최저"에 도달하게 됩니다. 그러나 그래프는 그에게 더 깊은 사실을 보여줍니다. 온두라스 제조업체도 동일한 병목 현상으로 인해 원당이 부족하다는 사실입니다. 내일 트럭이 이동하더라도 공급업체의 생산 라인은 곧 정지될 예정입니다.

Roberto의 창고를 축소하면 그래프에 훨씬 더 시스템적인 취약점이 드러납니다. 이것은 단지 한 사람의 재고 문제가 아닙니다. 이는 도시의 상업 활동에 구조적 위협이 됩니다.

모델에서는 종속성이 높은 노드 역할을 합니다. 그래프를 살펴보면 이 단일 상품이 생명선이라는 것을 알 수 있습니다.세 가지 완제품.이 세 가지 제품만으로도 힘이 나요수백 건의 개별 거래매일 지구 전역에서.

파급 효과는 다음에서 종료됩니다.7대 주요 사업문을 열어두기 위해 이러한 물품에 의존하는 산살바도르 허브 내에서요. 설탕 노드가 경계에서 "막혀" 있을 때 그래프에는 지연만 표시되는 것이 아닙니다. 동기화된 하락세를 계산합니다. 해당 7개 기업이 주요 제품을 잃으면 상업 허브는 경제 속도가 측정 가능한 수준으로 감소하여 잠재적으로 며칠 내에 최종 소비자에게 영향을 미치는 국지적인 노동 시간 단축 또는 가격 인상으로 이어질 수 있습니다. 그래프 렌즈를 통해 El Amatillo에 멈춰 있는 트럭 한 대가 수도 중심부에 있는 7개 소매점의 빈 진열대에 직접 연결되어 있습니다.

원당 의존도 높을 위험

에이전트 합성: 그래프 속 자율 지능

정적 지도에서 예측 엔진으로 이동하려면 단순한 데이터 이상의 것이 필요합니다. 이를 위해서는 각 경제 주체의 특정 맥락을 이해하는 행위자 프레임워크가 필요합니다. 모든 것을 해결하려는 단일 AI 대신 네트워크의 디지털 눈 역할을 하는 전문 도메인 에이전트 팀을 배포합니다.

각 에이전트는 특정 임무와 일련의 작업으로 준비되어 있습니다.들어오는 정보와 상호 작용하는 방법을 정의합니다.

  • 국경 횡단 요원:특정 대중교통 지점의 시민 불안, 기상 경보, 행정 파업에 대한 뉴스를 모니터링합니다.
  • 공급자 에이전트:기업 뉴스, 노동 분쟁 또는 제조 지연을 검색합니다. 모회사의 소식을 자회사 공급업체에 연결해 줍니다.
  • 상품 대리인:글로벌 시장과 환경 보고서를 시청하세요. 이 지역의 가뭄은 단순한 "날씨"가 아니라 생산에 필요한 원당의 미래 부족이라는 것을 알고 있습니다.
  • 상업 허브 에이전트:'라스트 마일'에 집중했습니다. 배전을 지연시킬 수 있는 도로 폐쇄나 전력망 장애 등 산살바도르의 지역 지방 뉴스를 모니터링합니다.

다음은 상품 에이전트의 샘플 코드입니다. 먼저 상품 노드와 관계가 있는 모든 뉴스 노드를 찾은 다음 그래프를 탐색하는 방법에 주목하세요.

commodity_risk_traversal_query = """
        MATCH (n:Noticia)-[:AFECTA_A]->(c:Commodity)
        WITH score, c, n
        MATCH (c)-[:INSUMO_PARA]->(p:Producto)<-[:CONTIENE]-(t:Transaccion)-[:ENTREGADA_A]-(e:Empresa)
        RETURN  
        n.texto AS news_content, 
        n.severidad as severity,
        n.titulo as headline,
        c.nombre as commodity,
        collect(p.descripcion) as ProductsImpacted,
        count(distinct e.id) AS businessesImpacted, 
        sum(distinct t.valorUSD) as dollarImpact,
        score AS SimilarityScore,
        elementId(n) AS Metadata_ID
"""

commodity_retriever = VectorCypherRetriever(
    driver,
    index_name="news_vector_index",
    embedder=embedder,
    retrieval_query=commodity_risk_traversal_query
)

이 순회 결과는 에이전트가 추론하고 답변을 제공하기 위한 컨텍스트로 사용됩니다. 다음은 OpenAI를 사용한 샘플 코드입니다.

response = embedding_client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {"role": "system", "content": prompts[risk_type]},
            {"role": "user", "content": f"Analyze these news alerts for risk:\n\n{news_context} Indicate dollar amount, product, and business impact"}
        ],
        temperature=0
    )

다음은 해당 컨텍스트를 시각적으로 표현한 것입니다.News노드(외부 트리거)에 링크됨(원시 입력). 해당 상품은 다음으로 매핑됩니다., 이는 하나 이상의 앵커 역할을 합니다., 최종적으로 종료되는 시간은 다음과 같습니다.상업 중심지 내에서.

상품 에이전트의 컨텍스트를 시각적으로 표현

궁극적으로 Roberto가 운송 파업이나 수확 실패와 같이 신호가 높은 특정 이벤트를 필터링하라는 메시지를 표시하면 단순히 헤드라인 목록만 표시되는 것이 아닙니다. 그는 점점가중 영향 분석. 에이전트는 그래프의 토폴로지를 이해하기 때문에 단순히 뉴스를 찾는 것이 아니라 행사에서 수익까지 구체적인 '의존 경로'를 식별하여 기술적으로 정확하고 전략적으로 건전한 답변을 제공합니다.

Based on the news alerts provided, here is an analysis of the potential risk and price volatility for the impacted commodities:

1. **Hard Red Winter Wheat (ZW)**
   - **Headline:** Geopolitical Tensions Impact ZW Pricing
   - **Severity:** High
   - **Dollar Impact:** $815,754.97
   - **Products Impacted:** Wheat Flour, Sweet Biscuits
   - **Business Impact:** No specific businesses mentioned as impacted.
   - **Analysis:** The uncertainty in international markets has led to speculative buying, which is destabilizing local costs. This could lead to increased price volatility for products derived from Hard Red Winter Wheat, such as wheat flour and sweet biscuits. The geopolitical tensions are likely to cause fluctuations in supply and demand, impacting prices.

2. **Corn (Maize) (ZC)**
   - **Headline:** Logistics Crisis at ZC Infrastructure
   - **Severity:** High
   - **Dollar Impact:** $1,065,036.03
   - **Products Impacted:** Carbonated Soft Drinks, Poultry Meat
   - **Business Impact:** No specific businesses mentioned as impacted.
   - **Analysis:** The breakdown in specialized transport systems has left cargo stranded, which could lead to supply chain disruptions. This is likely to cause short-term price increases and volatility for corn-based products, including carbonated soft drinks and poultry meat, as supply becomes constrained.

3. **Raw Sugar (SB)**
   - **Headline:** Transport Strike Targets SB Carriers
   - **Severity:** High
   - **Dollar Impact:** $1,543,928.18
   - **Products Impacted:** Carbonated Soft Drinks, Sweet Biscuits, Cane Sugar
   - **Business Impact:** No specific businesses mentioned as impacted.
   - **Analysis:** The strike by trucking unions demanding higher insurance coverage is likely to disrupt the transportation of raw sugar. This could lead to supply shortages and increased costs for products containing sugar, such as carbonated soft drinks, sweet biscuits, and cane sugar. The strike may result in significant price volatility until the issue is resolved.

Overall, the high severity of these events suggests a substantial risk of price volatility for the affected commodities and their derivative products. Businesses involved in the production or sale of these products should prepare for potential cost increases and supply chain disruptions.

언급된 뉴스를 요청할 때 차이점을 확인하세요.파업, 지연 또는 폐쇄

The news alerts indicate significant disruptions at various Aduana facilities, each with varying degrees of severity and impact on border crossings and transactions. Here's an analysis of the potential risks and impacts:

1. **Aduana La Hachadura Facility Explosion**
   - **Severity:** Critical
   - **Impact:** The indefinite shutdown of this key site due to an explosion will severely disrupt the handling and processing of goods. This will likely lead to delays in border crossings and transactions for the affected products.
   - **Dollar Amount:** $934,019.55
   - **Products Impacted:** Sweet Biscuits, Cane Sugar, Cement Clinkers, Portland Cement, Wheat Flour, Ethyl Alcohol (Ethanol), Poultry Meat
   - **Number of Businesses Impacted:** 7

2. **Aduana San Cristóbal Flooding**
   - **Severity:** High
   - **Impact:** The destruction of storage facilities due to flooding will cause significant supply chain disruptions and potential shortages in the region. This could lead to increased prices and delays in transactions.
   - **Dollar Amount:** $975,010.44
   - **Products Impacted:** Cane Sugar, Poultry Meat, Sweet Biscuits, Wheat Flour, Cement Clinkers, Portland Cement, Ethyl Alcohol (Ethanol)
   - **Number of Businesses Impacted:** 7

3. **Aduana Anguiatú Warehouse Fire**
   - **Severity:** Medium
   - **Impact:** The fire has destroyed a major distribution center, affecting the supply of several products. This will likely result in temporary shortages and increased costs for businesses relying on these supplies.
   - **Dollar Amount:** $1,140,795.04
   - **Products Impacted:** Portland Cement, Sweet Biscuits, Cement Clinkers, Poultry Meat, Ethyl Alcohol (Ethanol), Cane Sugar, Wheat Flour
   - **Number of Businesses Impacted:** 6

Overall, these events pose significant risks to border crossings and transactions, with potential delays, increased costs, and market instability. Businesses involved in the affected products should prepare for disruptions and consider alternative supply chain strategies.

border_risk_traversal_query = """
MATCH (n:Noticia)-[:AFECTA_A]->(c:Frontera)
WITH c, n
MATCH (c)<-[:ATRAVIEZA_FRONTERA]-(t:Transaccion)
MATCH (t)-[:CONTIENE]->(p:Producto)
MATCH (t)-[:ENTREGADA_A]->(e:Empresa)
RETURN n.texto AS news_content, 
        n.severidad as severity,
        n.titulo as headline,
        collect(distinct p.descripcion) as Products,
        count(distinct e) AS BusinessCount,
        sum(distinct t.valorUSD) as DollarAmount
"""

개별 창고를 넘어 진정한 혁신은숨겨진 위험 대리인,거시경제 감독을 위해 설계된 고위 감독자. Roberto가 탄산음료에 집중하는 동안 이 에이전트는 전체 그래프를 컨텍스트로 처리하여 인간 분석가가 발견할 수 없는 구조적 취약성을 검색합니다. 단지 파업을 기다리는 것이 아닙니다. 이는 폐쇄될 경우 산살바도르에서 모든 음료 거래의 40%가 중단되는 과테말라의 단일 다리와 같은 숨겨진 종속성을 식별합니다. 경제부 장관이나 중앙은행가에게 이는 궁극적인 정책 도구입니다. 이는 위험 완화를 두더지 잡기 게임에서 정확한 전략으로 전환하여 리더가 전체 지역 경제를 하나로 묶는 특정 노드(상품, 국경 통과, 공급업체)를 강화할 수 있도록 합니다. 위기 상황이 뉴스에 보도될 때쯤에는 정책 대응이 이미 시뮬레이션, 테스트 및 배포되었습니다.

다음은 섬유 산업의 위험을 찾기 위해 사용하는 마지막 예입니다.

hidden_risk_query = """
  MATCH (n:Noticia)-[:AFECTA_A]->(direct_entity)
  OPTIONAL MATCH (direct_entity)-[:INSUMO_PARA|PRODUCE|CONTIENE|UBICADA_EN*1..2]-(hidden_target)
  WHERE hidden_target <> direct_entity
  WITH n, direct_entity, hidden_target, labels(hidden_target)[0] as type
  RETURN 
    n.titulo AS NewsHeadline,
    direct_entity.nombre AS DirectImpact,
    collect(DISTINCT {
        entity: hidden_target.nombre, 
        type: type,
        risk_chain: labels(direct_entity)[0] + ' -> ' + type
    }) AS HiddenDependencies,
    count(DISTINCT hidden_target) AS TotalHiddenEntities
"""

The disruptions in the textile commodity sector are highlighted by 
the news of a speculative bubble bursting for Gildan Activewear. 
This incident directly impacts Gildan Activewear and is associated 
with hidden dependencies related to products, transactions, and commodities, 
specifically Upland Cotton. 

The situation may lead to broader implications for the textile industry, 
affecting supply chains and pricing.

기업 생존에서 국가 전략까지: 경제 정책을 위한 도구

전통적인 경제 정책은 종종 거대한 유조선 뒤에 있는 항적을 살펴보며 조종하는 것과 같습니다. GDP 하락이 보입니다.~ 후에분기가 종료됩니다. 인플레이션을 느끼시나요?~ 후에가격은 이미 급등했습니다. 지식 그래프는 거버넌스의 시간적 특성을 반응형에서 예측형으로 변경합니다.

  • 정밀 인프라:일반 도로 자금 대신, 그래프는 다운스트림 거래량이 가장 많은 특정 국경 통과 지점이나 상업 중심지가 "단일 실패 지점"인지 식별합니다. 정책은 최대 경제적 영향을 미치는 경로를 따릅니다.
  • 관세 영향 모델링:무역 협정이 체결되기 전에 그래프는 새로운 관세의 "폭발 반경"을 시뮬레이션할 수 있습니다. 이는 충격이 닥치기 전에 정부가 면제를 협상하거나 목표 구제책을 준비할 수 있도록 더 높은 투입 비용에 직면하게 될 국내 기업을 정확히 식별합니다.
  • 전쟁 및 지정학적 스트레스 테스트:글로벌 갈등을 나타내는 "뉴스 기사" 노드를 모델에 추가함으로써 그래프는 발트해 곡물 또는 중동 에너지의 중단이 현지 공급업체 및 상품을 통해 어떻게 폭포처럼 흘러가며 국가 식량 또는 에너지 안보에 숨겨진 취약성을 드러내는지 보여줍니다.


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

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

반응형
반응형

gram을 사용해서 그래프를 만들고ObservableHQ에서 공유하거나,d3-gram을 사용해서 어디서든 그래프를 SVG로 렌더링 해보세요.

Gram을 사용하면 (a)–>(b)<–(c) 처럼 쉽게 그래프를 만들 수 있어요. 그런데 이걸로 뭘 할 수 있을까요? 그래프는 시각적인 데이터 구조잖아요. 당연히 d3.js를 사용해서 시각화할 수 있겠죠!

Gram: 데이터 그래프 형식

다음과 같이 편리한 통합 기능도 있답니다: d3그램. ObservableHQ에서 한번 연습해 볼까요?

제가 설명하는 대로 따라해 보세요

먼저 Observable이 뭔지 짧은 동영상을 시청해 보세요.

https://www.youtube.com/watch?v=FXZrX3nzKdo&feature=youtu.be

그리고 를 하려면 ObservableHQ에 로그인해야 해요. 진짜로 해보세요!

Observable – 함께 데이터로 세상을 이해하세요 / Observable

가장 먼저 노트북을 만들어야겠죠?

  1. 오른쪽 상단에 있는 "새로 만들기" 버튼을 클릭하세요.
  2. '# Observable Graph by ABK' 처럼 멋진 제목을 붙여주세요.

다음으로, gram을 파싱하고 그래프를 렌더링하는 `graphOf` 함수를 가져올 거예요.

  1. '+' 버튼을 사용해서 새 블록을 만드세요.
  2. 다음 코드를 복사해서 붙여넣으세요.
  3. Shift-Return을 누르거나 실행 버튼을 눌러서 가져오기를 실행하세요.
import {graphOf} from “@akollegger/그래프 입력”

이제 그래프 시각화를 만드는 건 `graphOf` 함수를 호출하고 Cypher 쿼리 같은 gram 패턴을 전달하는 것만큼 쉬워요. 새 블록을 추가하고 다음을 시도해 보세요.

abc = graphOf("(a)-->(b)<--(c)");

깔끔하죠? `zoom` 수준과 `height`를 추가해서 시각화를 조금 더 조정할 수도 있어요.

abc = graphOf("(a)-->(b)<--(c)", {zoom:4, height: 60})

그래프 관찰하기

ABC가 뭘까요? `graphOf` 함수는 Observable View, 즉 현재 값을 가진 입력 요소에요. 슬라이더나 텍스트 입력 필드처럼 그래프 시각화를 사용할 수 있다는 뜻이죠.

`viewof`를 앞에 추가해서 `graphOf` 블록을 수정해 볼게요.

viewof abc = graphOf("(a)-->(b)<--(c)", {zoom:4, height: 60})

이번에는 `abc`만 포함하는 다른 블록을 추가해 보세요.

abc

해당 블록을 실행하면 결과가 객체라는 걸 알 수 있을 거예요. 이게 바로 입력 요소의 현재 값이죠. 여기에는 모든 `node`의 배열, `link` 배열, 그리고 현재 선택된 항목의 배열이 포함되어 있어요. Shift 키를 누른 채 그래프를 클릭하면 "선택된" 배열에 여러 `node`가 추가될 거예요.

Object {
 nodes: Array(3) [Object, Object, Object]
 links: Array(2) [Object, Object]
 selected: Array(1) [Object]
}

배열 요소는 원본 그래프 데이터와 함께 x, y 좌표와 같은 d3-js 속성을 혼합하는 객체에요. 그래프 데이터에 집중하기 위해서 다른 블록을 추가해 볼게요.

selectedNode = abc.selected.length == 0 ? {} : Object.getPrototypeOf(abc.selected[0])

훨씬 보기 좋죠? 이제 그래프 시각화에 영향을 주고 `selectedNode`에 표시되는 세부 정보를 그래프에 추가할 수 있어요.

이걸 한번 시도해 보세요:

viewof abc = graphOf("(a:Person {name:'ABK'})-->(b)<--(c)", 
  {zoom:4, height: 60})

이 모든 게 준비되면 graphOf()에 전달된 gram 문자열을 편집해서 여러분만의 그래프를 만들 수 있어요. 아니면 다음 링크에서 텍스트 필드를 추가할 수도 있고요. 양식 입력. 첨부된 파일이나 URL에서 로드하는 방법도 있답니다.

재미로 위키피디아 내용을 다시 만들어 봤는데, 한번 명명된 그래프 갤러리에서 확인해 보세요.

gram이 정말 유용하죠? 재미있게 사용해 보세요! 🙂

참고 자료:

d3그램 — d3로 렌더링을 구동하는 js 통합 라이브러리
ABK의 관찰 가능한 그래프 — 이 글에서 설명하는 전체 노트북
그래프의 그램 — gram과 그래프에 대한 노트 모음
@akollegger/그래프 입력 — 이 글에 사용된 유틸리티 기능을 제공하는 ObservableHQ 노트북

  • d3그램
  • ABK의 관찰 가능한 그래프
“ABK의 Observable Graph”는 Observable Notebook에서 Gram을 사용합니다.

  • d3gram
  • d3js
  • gram
  • ObservableHQ
  • 관찰 가능 항목

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

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

반응형
반응형
  • Cypher & GQL
Neo4j 2.0을 사용하면 그래프 내에서 Node 세트를 정의할 수 있습니다.
 
필립 래슬
제품 담당 수석 이사

업데이트: 이제 2.0.0-M02를 사용할 수 있습니다.
오늘 저희는 Neo4j 2.0.0-M01 마일스톤 릴리스를 출시합니다! Neo4j 2.0 시리즈는 앞으로 몇 달 안에 정식 버전(GA)으로 출시될 예정이에요. 이번 릴리스는 13년 전 Neo4j 출시 이후 처음으로 Property Graph 모델을 변경한다는 점에서 정말 의미가 깊어요. 구체적으로는 새로운 구성 요소인 Labels를 추가할 예정이랍니다.
이번 작업은 데이터 모델에 중요한 부분을 추가하는 첫 번째 단계이고, 여러분의 소중한 의견을 듣기 위해 코드를 먼저 공개하는 거예요. 이 마일스톤 릴리스는 일종의 라고 생각해주시면 좋을 것 같아요. 여러분이 이 새로운 기능을 어떻게 활용하고 싶으신지 정말 궁금하고, 여러분의 생각을 듣고 싶어요!
예를 들어, Joe라는 사람을 위한 Node를 만든다고 가정해 볼게요. Joe는 단순한 Node가 아니죠. 그는 '사람'이에요. 따라서 Joe의 Node를 "Person"으로 지정하고 싶을 텐데요. 이전에 Neo4j를 사용해본 적이 있다면, 아마 `type`이라는 Property를 추가하고 값을 "Person"으로 설정해서 이 작업을 처리했을 거예요.
VD1MdjPDD3ukaeZi4PC5IcGe83eSC88Pa-jztqi-lJaXu5wC1qPNW6goDhkpLJFSX4FPqLClV4V_3RSRuAvsBFQTD1bE4MJsr5sI-Pec8ibv6SetmEqDar1j
이 방법도 유용하긴 해요. 왜냐하면 이제 "가정용품" Node나 "지리적 위치" Node처럼 완전히 다른 그래프의 항목들과 Joe를 구별할 수 있으니까요. 맞아요, 이런 것들은 서로 다르게 다뤄져야 하죠.
이제 Joe에게 좌파, 우파, 아니면 온건 중도파 같은 정치적 성향을 부여하고 싶다고 가정해 볼게요. Property를 사용해서도 이 작업을 할 수 있지만, 특정 정당에 소속된 모든 사람을 쉽게 찾고 싶을 수도 있겠죠. Joe가 "중도파"라는 것을 알게 되면, 아래와 같이 정당을 Node로 분리한 다음 Joe를 그의 정당과 연결하기로 결정할 수도 있을 거예요.
VfMJKib3_FEio7ck-rT5dQqcaS94WEH7taynCAqavfEVHo7YiDs8-4wUSJZRCNF-mlQqfuRad3ZbIBcKCBH4-J-dDy44tsvQjV-tzQ37TvlffmgTGRyowRWE
이제 자연스럽게 그래프에서 하고 싶은 작업 중 하나는 "Person"의 고유 식별자를 기반으로 "Person" `Node`(다른 `Node`는 제외)를 자동으로 인덱싱하는 것이겠죠? (너무 단순화해서 이걸 "이름"이라고 해볼게요.) Cypher를 사용한다면 이게 꽤나 어려운 일이에요. 사실 Neo4j는 "사람"이 지리적 위치와 다르다는 것을 알지 못하기 때문에 아예 불가능할 수도 있어요. "이름"을 인덱싱하려고 하면 그래프의 모든 항목에 대해 인덱스를 생성하게 되어서 문제가 생길 수 있죠. 지리적 위치 이름은 사람 이름과 같지 않고, 도시가 사람과 같을 리도 없잖아요. "Middle-Wing" `Node`의 경우, 해당 `Node`가 그룹에 속하도록 지정하는 것이 유일한 목적인 많은 연결 때문에 그래프가 엄청 복잡해질 수 있어요.
그래서 우리는 이걸 더 잘할 수 있는 방법을 찾아봤어요. 이상적인 솔루션은 그래프를 더 쉽게 이해할 수 있도록 도와줄 뿐만 아니라, Cypher가 `Node`에 따라 `Node`에 위치할 수 있게 해줘서 (인덱싱도 가능하게 해서) Cypher를 훨씬 더 강력하게 만들어 줄 거예요.
그래서 2.0에는 `Node`를 그룹화하거나 분류하는 방법이 도입되었어요. 일단은 이 구조를 "Label"이라고 부르기로 했어요. "Label"이라는 용어는 일반적인 용도와 `Node`가 여러 개의 Label을 가질 수 있다는 점을 고려한 거예요. Label의 다양한 용도 중 하나는 (아마도 처음에는 가장 직관적인 용도일 텐데) 애플리케이션의 타입 시스템과 연결할 수 있는 그래프에 "hook"을 제공하는 것이에요. 이 기능 자체가 명시적으로 계층적이지 않기 때문에 (말 그대로 태그일 뿐이고, `Node`당 0개부터 여러 개까지 가질 수 있어요) Label이라고 부르는 거랍니다.
그래프는 데이터에 관계가 있기 때문에 그래프인 거죠. `Property Graph`에서 관계는 항상 두 `Node`가 어떻게 관련되어 있는지 설명하는 유형을 가지고 있어요. Label은 그 아이디어를 확장해서 전체 `Node` 집합이 어떻게 관련되어 있는지 설명하는 거예요. 이건 `Node`를 그룹화하는 메커니즘인 거죠. 어떻게 작동하냐고요? 아주 간단해요. 위의 예에서 "Type" 속성을 추가하고 Joe를 Party `Node`에 연결하는 대신, 두 개의 Label을 추가하는 거예요. 하나는 "Person"에 대한 Label이고, 다른 하나는 "Middle-Wing"에 대한 Label이죠.
6bxPbVbNrpsQHfTsvd_2SH9b3NsitMNJ_5cf33s-CE7F5xlQu4F6roUJy8ZVHWTT90VE2u3N_qyFkDa8rc3e-rbdB7kEzQbqxEq82ephOoUuqeTpIm5TwLoI
이건 정말 다양한 가능성을 열어주고, 여러분 머릿속에 많은 아이디어를 떠오르게 할 거예요. Label을 어떻게 사용하는지에 대한 생각을 제한하기보다는, 다양한 색상 세트를 사용하는 예를 한번 살펴볼까요?
느슨하게 연결된 임의의 영역이 있고, 그 안에서 최소한 빨간색, 녹색 또는 파란색이 될 수 있다는 것을 알고 있다고 가정해 볼게요. 각 Node에 "색상" 속성을 추가하거나, 이를 각 색상의 Value Node에 연결할 수도 있겠죠. 하지만 우리는 항상 이 그룹 내에서 작업하고 싶기 때문에, Label을 사용해서 세트의 멤버를 식별할 거예요.
먼저 빨간색을 만들어 볼게요.

Label이 있는 Node 생성

CREATE(thing:Red {uid: “TK-421”, make: 191860 })
RETURN thing;
방금 만든 것을 찾으려면, Red Node 내에서만 검색한 다음 Label을 반환하면 돼요.

Node에서 Label 찾기

MATCH(thing:Red)
WHERE thing.uid = “TK-421”
RETURN labels(thing);
왜 `labels`가 복수일까요? 그건 `Node`에는 여러 개의 `Label`이 있을 수 있기 때문이에요. "TK-421"도 파란색 세트에 속한다고 가정해 볼게요. 다음과 같이 파란색 `Label`을 추가하면 돼요.

`Node`에 `Label` 추가하기

MATCH(thing:Red)
WHERE thing.uid = “TK-421”
SET thing:Blue;
일부 덴마크인은 `Label`에 대해 긴장할 수도 있지만, 이걸 사용하면 많은 이점이 있어요. `Node` 집합에 `Label`을 적용하면 의도가 분명해지죠. 즉, "이러한 `Node`는 자주 액세스되며 하나의 그룹으로 간주됩니다." 라는 걸 나타내는 거예요. `Database` 자체는 이제 이 정보로 작업을 수행할 수 있으므로 의도를 명시함으로써 이점을 얻을 수 있는 거죠.
우선, Neo4j는 세트 내에서 `Node`를 찾을 때 성능을 향상시키는 `Index`를 생성할 수 있어요.(`Index` 생성을 위한 새로운 Cypher 구문에 유의하세요!):

빨간색 및 파란색 노드를 찾는 속도를 높이기 위해 Index 생성

CREATE INDEX ON :Red(uid);
CREATE INDEX ON :Blue(uid);

두 번째 Label이 지정된 Node 및 Relationship 만들기

CREATE(other_thing:Blue {uid: “TURK-182”, make: 181663})
WITH other_thing
MATCH(thing:Red)
WHERE thing.uid = “TK-421”
CREATE (thing)-[:HONORS]->(other_thing)
RETURN thing, other_thing;
훨씬 더 많은 즐거움이 있어요. 자세한 내용은 언제나 그렇듯이Neo4j 매뉴얼에서 확인해 보세요. 다시 말하지만, 이 간단한 변화가 엄청난 영향을 미칠 수 있다는 점! 가능성을 탐색하고 언어와 API를 조정하는 동안 Label을 사용해 보시길 추천드려요. Google 그룹에 피드백을 제공해서 여러분이 어떻게 사용하고 있는지 알려주세요. (다른 사람들이 여러분의 피드백을 보고 자신의 의견과 관찰로 응답할 수 있을 거예요.)

혹시라도...

Cypher에 새로운 기능이 추가되었어요. 바로 CASE 표현식인데요, 입력 값을 결과 값으로 매핑해주는 친구죠. 다른 일반적인 프로그래밍 언어에서도 비슷한 구조를 찾아볼 수 있을 거예요.
  • 간단하게 말하면, CASE는 속성을 직접 비교해서 첫 번째로 일치하는 WHEN 절의 결과 값을 선택하는 방식이에요.
MATCH (r:빨간색) RETURN CASE r.uid
    WHEN "TK-421" THEN "당신은 왜 자리에 없나요?"
   WHEN "TURK-182" THEN "한 사람의 작품"
   ELSE "…"
END
  • 좀 더 일반적인 형태에서는, 각각의 WHEN 절에서 임의의 조건자를 사용해서 결과를 선택할 수 있어요.
MATCH(r:빨간색) RETURN CASE
r.color > 180000이면 “붉은색”
r.color < 180000이면 "자주색"
ELSE "단순한 빨간색"
END
이 미리보기 마일스톤, 재밌게 즐기셨나요? 사용 Neo4j 구글 그룹에서 Neo4j 팀과 Neo4j 커뮤니티의 다른 구성원들에게 여러분의 생각을 알려주세요.이번 릴리스에는 셸을 포함해서 몇 가지 개선 사항이 더 있는데, 이건 앞으로 블로그에서 다룰 예정이에요. 그리고 물론, 다가오는 Neo4j 2.0 마일스톤에서는 더 많은 것들을 보게 될 거예요. 그 동안, 미리보기를 업그레이드했어요. 온라인 콘솔에서 새로운 기능을 테스트해 볼 수 있도록, 이제 레이블로 향상된 매트릭스 그래프가 제공된답니다.
마지막으로, 곧 프로덕션 환경에 적용할 계획이라면 1.9 버전을 적극적으로 개발하는 걸 추천드려요. 1.9 버전은 앞으로 몇 주 안에 GA될 예정이거든요 (이번 주에 RC를 찾아보세요!).
최신 2.0 마일스톤에는 단일 트랜잭션 내에서 여러 Cypher 문을 관리하기 위한 새로운 HTTP endpoint가 도입되었어요. 첫 번째 명세서 배치로 트랜잭션을 생성하면 되는데요. 추가 요청을 제출할 수 있고 트랜잭션을 커밋하거나 롤백할 수 있는 URL을 받게 될 거예요. 자세한 내용은 Neo4j 매뉴얼을 참조하세요.


Neo4j 팀 드림!

Graph Database에 대해 더 자세히 알고 싶으신가요? 아래를 클릭해서 O'Reilly의 무료 전자책을 다운로드하고, 지금 바로 여러분의 애플리케이션에 그래프 기술을 사용하는 방법을 알아보세요.

  • Cypher

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

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

반응형
반응형

Neo4j가 LangChain과의 통합을 발표한 이후로, Neo4j와 LLM을 사용해서 RAG(Retrieval-Augmented Generation)를 구축하는 다양한 사용 사례들이 나타났어요. 덕분에 최근 몇 달 동안 Knowledge Graph를 활용한 RAG에 대한 관심이 엄청나게 높아졌죠. Knowledge Graph 기반 RAG 시스템은 기존 RAG 방식보다 환각 현상(hallucination)을 더 잘 관리하는 경향이 있는 것 같아요. RAG 애플리케이션을 더욱 발전시키기 위해 에이전트 기반 시스템을 사용하는 경우도 늘고 있고요. 한 단계 더 나아가 LangChain 생태계에 LangGraph 프레임워크가 추가되면서 LLM 애플리케이션에 주기와 지속성을 더할 수 있게 되었답니다.

자, 그럼 LangChain과 LangGraph를 사용해서 Neo4j용 GraphRAG 워크플로우를 만드는 방법을 알아볼까요? 복잡한 작업 흐름을 만들기 위해 여러 단계에서 LLM을 활용하고, 동적인 프롬프트 쿼리 분해 기술도 사용할 거예요. 벡터 의미 검색과 그래프 QA 체인을 분리하기 위해 라우팅 기술도 활용할 거고요. LangGraph GraphState를 사용해서 이전 단계에서 얻은 컨텍스트로 프롬프트 템플릿을 풍부하게 만들어볼게요.

워크플로우의 대략적인 모습은 아래 이미지와 같아요.

자세한 내용을 살펴보기 전에 LangChain 기반 GraphRAG 워크플로우를 간단하게 요약해 볼까요?

출처: LangChain

일반적인 GraphRAG 애플리케이션은 LLM을 사용해서 Cypher 쿼리 언어를 생성하는 작업을 포함해요. 그런 다음 LangChain GraphCypherQAChain은 생성된 Cypher 쿼리를 Graph Database(예: Neo4j)에 제출해서 쿼리 결과를 검색하죠. 마지막으로 LLM은 초기 쿼리 및 그래프 응답을 기반으로 응답을 반환해요. 이 시점에서 응답은 기존 그래프 쿼리에만 기반하는 거죠. Neo4j 벡터 인덱싱 기능이 도입된 이후에는 Semantic Search도 수행할 수 있게 되었어요. Property Graph를 다룰 때 Semantic Search와 그래프 쿼리를 결합하거나 둘을 분리하는 것이 유용한 경우가 있답니다.

그래프 쿼리 예시

기사, 저자, 저널, 기관 등과 같은 Node가 있는 학술 저널의 Graph Database가 있다고 가정해 볼게요.

"가장 많이 인용된 상위 10개 기사 찾기"에 대한 일반적인 그래프 쿼리는 다음과 같아요.

MATCH(n:Article) 
WHERE n.citation_count > 50
RETURN n.title, n.citation_count

Semantic Search 예시

“기후 변화에 관한 기사 찾기”는 다음과 같죠.


하이브리드 쿼리

하이브리드 쿼리는 먼저 Semantic Search를 수행한 후, 의미 검색 결과를 이용해서 그래프 쿼리를 수행할 수 있어요. 이는 학술 그래프와 같은 Property Graph를 사용하려는 경우에 주로 유용하답니다. 대표적인 질문은 “기후변화에 관한 기사를 찾아 그 저자와 기관을 돌려달라”는 것이죠.

이 상황에서는 질문을 필요한 작업을 수행하는 원하는 수의 하위 쿼리로 구문 분석해야 해요. 이 경우 Vector Search는 그래프 쿼리의 컨텍스트로 작동하는 거죠. 따라서 우리는 그러한 맥락을 수용하는 복잡한 Prompt 템플릿을 디자인할 수 있어야 해요. (자세한 내용은 다음을 확인하세요. 고급 프롬프트.)

LangGraph 작업 흐름

현재 워크플로에는 두 개의 분기가 있어요 (아래 그림을 참고해주세요). 하나는 그래프 스키마를 사용하는 간단한 그래프 쿼리 검색 QA이고, 다른 하나는 벡터 유사성 검색을 사용하는 분기예요. 이 워크플로를 따라해 보실 수 있도록, 이 실험의 모든 코드가 포함된 GitHub 저장소를 만들었어요. My_LangGraph_Demo를 확인해보세요! 이 실험의 데이터 세트는 학술 메타데이터를 제공하는 OpenAlex에서 얻었답니다 (참조: OpenAlex 데이터에서 자세한 내용을 확인하세요). 그리고 Neo4j AuraDB 인스턴스도 필요해요.

일반적인 작업 흐름은 다음과 같이 설계되었어요.

def route_question(state: GraphState):
    print("---ROUTE QUESTION---")
    question = state["question"]
    source = question_router.invoke({"question": question})
    if source.datasource == "vector search":
        print("---ROUTE QUESTION TO VECTOR SEARCH---")
        return "decomposer"
    elif source.datasource == "graph query":
        print("---ROUTE QUESTION TO GRAPH QA---")
        return "prompt_template"

workflow = StateGraph(GraphState)

# Nodes for graph qa
workflow.add_node(PROMPT_TEMPLATE, prompt_template)
workflow.add_node(GRAPH_QA, graph_qa)

# Nodes for graph qa with vector search
workflow.add_node(DECOMPOSER, decomposer)
workflow.add_node(VECTOR_SEARCH, vector_search)
workflow.add_node(PROMPT_TEMPLATE_WITH_CONTEXT, prompt_template_with_context)
workflow.add_node(GRAPH_QA_WITH_CONTEXT, graph_qa_with_context)

# Set conditional entry point for vector search or graph qa
workflow.set_conditional_entry_point(
    route_question,
    {
        'decomposer': DECOMPOSER, # vector search
        'prompt_template': PROMPT_TEMPLATE # for graph qa
    },
)

# Edges for graph qa with vector search
workflow.add_edge(DECOMPOSER, VECTOR_SEARCH)
workflow.add_edge(VECTOR_SEARCH, PROMPT_TEMPLATE_WITH_CONTEXT)
workflow.add_edge(PROMPT_TEMPLATE_WITH_CONTEXT, GRAPH_QA_WITH_CONTEXT)
workflow.add_edge(GRAPH_QA_WITH_CONTEXT, END)

# Edges for graph qa
workflow.add_edge(PROMPT_TEMPLATE, GRAPH_QA)
workflow.add_edge(GRAPH_QA, END)

app = workflow.compile()

app.get_graph().draw_mermaid_png(output_file_path="graph.png")

이 코드는 아래와 같은 워크플로를 생성해줘요.

이 GraphRAG 흐름에서는 쿼리 흐름 경로를 결정할 수 있는 조건부 진입점으로 워크플로를 시작해요. 이 경우 __START__ node는 사용자 쿼리로 시작되죠. 쿼리에 따라 정보가 양쪽으로 흘러가는데요. 쿼리가 Vector Embedding을 조회해야 하는 경우 오른쪽으로 이동하고, 쿼리가 간단한 그래프 기반 쿼리인 경우 워크플로는 왼쪽 부분을 따라가요. 워크플로의 왼쪽 부분은 기본적으로 앞서 설명한 대로 LangChain을 사용하는 일반적인 그래프 쿼리 방식이에요. 유일한 차이점은 여기에서 LangGraph를 사용하고 있다는 점이죠.

위 워크플로의 오른쪽을 한번 살펴볼까요? DECOMPOSER node부터 시작하는데요. 이 node는 기본적으로 사용자 질문을 하위 쿼리로 분할해요. 예를 들어 "산화 스트레스에 관한 기사를 찾아보고, 가장 관련성이 높은 기사의 제목을 반환해줘"라고 묻는 사용자 질문이 있다고 가정해 볼게요.

하위 쿼리:

질문을 분해해야 하는 이유를 아시겠나요? 그래프 QA 체인은 전체 사용자 질문을 입력 쿼리로 전달할 때 어려움을 겪거든요. 분해는 GPT-3.5 Turbo 모델과 기본 Prompt Engineering 템플릿을 사용하는 query_analyzer 체인을 사용해서 간단하게 수행할 수 있어요.

class SubQuery(BaseModel): """Decompose a given question/query into sub-queries""" sub_query: str = Field( ..., description="A unique paraphrasing of the original questions.", ) system = """You are an expert at converting user questions into Neo4j Cypher queries. Perform query decomposition. Given a user question, break it down into two distinct subqueries that you need to answer in order to answer the original question. For the given input question, create a query for similarity search and create a query to perform neo4j graph query. Here is example: Question: Find the articles about the photosynthesis and return their titles. Answers: sub_query1 : Find articles related to photosynthesis. sub_query2 : Return titles of the articles """ prompt = ChatPromptTemplate.from_messages( [ ("system", system), ("human", "{question}"), ] ) llm_with_tools = llm.bind_tools([SubQuery]) parser = PydanticToolsParser(tools=[SubQuery]) query_analyzer = prompt | llm_with_tools | parser

오른쪽 분기의 또 다른 중요한 부분은 컨텍스트가 포함된 프롬프트 템플릿이에요. Property Graph에 대해 쿼리할 때 Cypher 생성 시 그래프 스키마를 사용하면 원하는 결과를 얻을 수 있죠. Vector Search로 컨텍스트를 생성하면 Vector Search가 제공하는 특정 Node에 Cypher 템플릿을 집중시켜서 더 정확한 결과를 얻을 수 있어요.

template = f"""
    Task:Generate Cypher statement to query a graph database.
    Instructions:
    Use only the provided relationship types and properties in the schema.
    Do not use any other relationship types or properties that are not provided.

    A context is provided from a vector search {context}

    Using the context, create cypher statements and use that to query with 
    the graph.
    """

컨텍스트가 포함된 프롬프트 템플릿

저장된 Vector Embedding에서 유사성 검색을 사용해서 컨텍스트를 생성해요. Semantic Search 컨텍스트나 Node 자체를 컨텍스트로 생성할 수 있죠. 예를 들어 여기서는 사용자 쿼리와 가장 유사한 기사를 나타내는 Node ID를 검색해요. 이러한 Node ID는 프롬프트 템플릿에 컨텍스트로 전달되고요.

컨텍스트가 캡처되면 프롬프트 템플릿이 올바른 Cypher 예제를 얻도록 하고 싶을 거예요. Cypher 예제가 늘어남에 따라 정적 프롬프트 예제가 관련성이 없어지면서 LLM이 어려움을 겪을 거라고 예상할 수 있어요. 그래서 우리는 *동적 프롬프트* 메커니즘을 사용해서 유사성을 기반으로 가장 관련성이 높은 Cypher 예제를 선택할 거예요. 사용자 쿼리를 기반으로 k-샘플을 선택하기 위해 Chroma 벡터 저장소를 바로 사용할 수 있죠. 따라서 최종 프롬프트 템플릿은 다음과 같아요.

context = state["article_ids"]
    
    prefix = f"""
    Task:Generate Cypher statement to query a graph database.
    Instructions:
    Use only the provided relationship types and properties in the schema.
    Do not use any other relationship types or properties that are not provided.
    ...
    ...    
    A context is provided from a vector search in a form of tuple ('a..', 'W..') 
    Use the second element of the tuple as a node id, e.g 'W..... 
    Here are the contexts: {context}

    Using node id from the context above, create cypher statements and use that to query with the graph.
    Examples: Here are a few examples of generated Cypher statements for some question examples:
    """

    FEW_SHOT_PROMPT = FewShotPromptTemplate(
        example_selector = example_selector,
        example_prompt = example_prompt,
        prefix=prefix,
        suffix="Question: {question}, nCypher Query: ",
        input_variables =["question", "query"],
    ) 
    return FEW_SHOT_PROMPT

동적으로 선택된 Cypher 예제는 *접미사* 인수를 통해 전달돼요. 마지막으로 그래프 QA 체인을 호출하는 Node에 템플릿을 전달하죠. 워크플로우의 왼쪽에서도 유사한 동적 프롬프트 템플릿을 사용했지만, 컨텍스트는 사용하지 않았어요.

일반적인 RAG 워크플로우와 달리 프롬프트 템플릿에 컨텍스트를 도입할 때 입력 변수를 생성하고 모델 체인(예: GraphCypherQAChain())을 호출할 때 변수를 전달해서 수행해요.

template = f"""
    Task:Generate Cypher statement to query a graph database.
    Instructions:
    Use only the provided relationship types and properties in the schema.
    Do not use any other relationship types or properties that are not provided.

    A context is provided from a vector search {context}

    Using the context, create cypher statements and use that to query with 
    the graph.
    """

    PROMPT = PromptTemplate(
        input_variables =["question", "context"],
        template = template,
    )

가끔 LangChain 체인을 통해 여러 변수를 전달하는 게 더 까다로워질 때가 있어요.

chain = (
    { 
     "question": RunnablePassthrough(),
     "context" : RetrievalQA.from_chain_type(),
    }
    | PROMPT
    | GraphCypherQAChain() # typically you have llm() here!
)

GraphCypherQAChain()에는 프롬프트 텍스트가 아니라 프롬프트 템플릿이 필요하기 때문에, 위 워크플로는 작동하지 않아요 (체인을 호출하면 프롬프트 템플릿 출력이 텍스트가 되거든요!). 이 때문에 제가 원하는 만큼 많은 컨텍스트를 전달하고 워크플로를 실행할 수 있는 LangGraph를 실험하게 되었답니다.

GraphQA 체인

컨텍스트가 포함된 프롬프트 템플릿 이후의 마지막 단계는 그래프 쿼리죠. 여기에서 일반적인 그래프 QA 체인을 사용해서 프롬프트를 Graph Database에 전달, 쿼리를 실행하고 LLM이 응답을 생성해요. 프롬프트 생성 후 워크플로 왼쪽에 있는 비슷한 경로를 확인해보세요. 또한 유사한 동적 프롬프트 접근 방식을 사용해서 양쪽에 프롬프트 템플릿을 생성합니다.

워크플로를 실행하기 전에 라우터 체인과 GraphState에 대해 몇 가지 생각해볼게요.

라우터 체인

언급했듯이 쿼리 흐름 경로를 결정할 수 있는 조건부 진입점으로 워크플로를 시작해요. 이는 간단한 프롬프트 템플릿과 LLM을 사용한 라우터 체인을 통해 달성되죠. Pydantic 모델은 이런 상황에서 유용하답니다.

class RouteQuery(BaseModel):
    """Route a user query to the most relevant datasource."""

    datasource: Literal["vector search", "graph query"] = Field(
        ...,
        description="Given a user question choose to route it to vectorstore or graphdb.",
    )
    
llm = ChatOpenAI(temperature=0)
structured_llm_router = llm.with_structured_output(RouteQuery)

system = """You are an expert at routing a user question to perform vector search or graph query. 
The vector store contains documents related article title, abstracts and topics. Here are three routing situations:
If the user question is about similarity search, perform vector search. The user query may include term like similar, related, relvant, identitical, closest etc to suggest vector search. For all else, use graph query.

Example questions of Vector Search Case: 
    Find articles about photosynthesis
    Find similar articles that is about oxidative stress
    
Example questions of Graph DB Query: 
    MATCH (n:Article) RETURN COUNT(n)
    MATCH (n:Article) RETURN n.title

Example questions of Graph QA Chain: 
    Find articles published in a specific year and return it's title, authors
    Find authors from the institutions who are located in a specific country, e.g Japan
"""

route_prompt = ChatPromptTemplate.from_messages(
    [
        ("system", system),
        ("human", "{question}")
    ]
)

question_router = route_prompt | structured_llm_router


def route_question(state: GraphState):
    print("---ROUTE QUESTION---")
    question = state["question"]
    source = question_router.invoke({"question": question})
    if source.datasource == "vector search":
        print("---ROUTE QUESTION TO VECTOR SEARCH---")
        return "decomposer"
    elif source.datasource == "graph query":
        print("---ROUTE QUESTION TO GRAPH QA---")
        return "prompt_template"

그래프 상태

LangGraph의 아름다운 측면 중 하나는 GraphState를 통한 정보의 흐름이에요. 노드가 모든 단계에서 액세스해야 할 수 있는 GraphState의 모든 잠재적 데이터를 정의해야 한답니다.

class GraphState(TypedDict):
    """
    Represents the state of our graph.

    Attributes:
        question: question
        documents: result of chain
        article_ids: list of article id from vector search
        prompt: prompt template object
        prompt_with_context: prompt template with context from vector search
        subqueries: decomposed queries
    """

    question: str
    documents: dict
    article_ids: List[str]
    prompt: object
    prompt_with_context: object
    subqueries: object

이 데이터에 접근하려면, `Node`나 함수를 정의할 때 `state`를 상속하기만 하면 돼요. 예를 들어:

def prompt_template_with_context(state: GraphState):

    question = state["question"]  # Access data through state
    queries = state["subqueries"] # Access data through state

    # Create a prompt template
    prompt_with_context = create_few_shot_prompt_with_context(state)
    
    return {"prompt_with_context": prompt_with_context, "question":question, "subqueries": queries}

이런 주요 주제에 대해 이야기했으니, 이제 Neo4j GraphRAG 앱을 실행해 볼까요?

그래프 품질 보증:

app.invoke({"question": "find top 5 cited articles and return their title"})

---ROUTE QUESTION---
---ROUTE QUESTION TO GRAPH QA---


> Entering new GraphCypherQAChain chain...
Generated Cypher:
MATCH (a:Article) WITH a ORDER BY a.citation_count DESC RETURN a.title LIMIT 5

> Finished chain.

# Examine the result
graph_qa_result['documents']

{'query': 'find top 5 cited articles and return their title',
 'result': [{'a.title': 'Humic Acids Isolated from Earthworm Compost Enhance Root Elongation, Lateral Root Emergence, and Plasma Membrane H+-ATPase Activity in Maize Roots'},
  {'a.title': 'Rapid Estimates of Relative Water Content'},
  {'a.title': 'ARAMEMNON, a Novel Database for Arabidopsis Integral Membrane Proteins'},
  {'a.title': 'Polyamines in plant physiology.'},
  {'a.title': 'Microarray Analysis of the Nitrate Response in Arabidopsis Roots and Shoots Reveals over 1,000 Rapidly Responding Genes and New Linkages to Glucose, Trehalose-6-Phosphate, Iron, and Sulfate Metabolism '}]}

벡터 검색을 통한 그래프 QA:

app.invoke({"question": "find articles about oxidative stress. Return the title of the most relevant article"})

---ROUTE QUESTION---
---ROUTE QUESTION TO VECTOR SEARCH---


> Entering new RetrievalQA chain...

> Finished chain.

# Examine the result
graph_qa_result['documents']

{'query': 'Return the title of the most relevant article.',
 'result': [{'a.title': 'Molecular Responses to Abscisic Acid and Stress Are Conserved between Moss and Cereals'}]}

# Examine output of GraphState
graph_qa_result.keys()
dict_keys(['question', 'documents', 'article_ids', 'prompt_with_context', 'subqueries'])

# Examine decomposer output
graph_qa_result['subqueries']

[SubQuery(sub_query='Find articles related to oxidative stress.'),
 SubQuery(sub_query='Return the title of the most relevant article.')]

보시다시피, 사용자 질문을 기반으로 질문을 올바른 분기로 성공적으로 라우팅하고 원하는 결과를 검색할 수 있었어요. 복잡성이 증가함에 따라 라우터 체인 자체에 대한 `Prompt`를 수정해야 하죠. 이와 같은 애플리케이션에서는 분해가 중요하지만, 쿼리 확장은 유용한 도구가 될 수 있는 LangChain의 또 다른 기능이에요. 특히 유사한 답변을 반환하기 위해 Cypher `Query`를 작성하는 여러 가지 방법이 있는 경우 더욱 그렇고요.

우리는 작업 흐름의 가장 중요한 부분을 다루었어요. 더 깊은 내용을 알아보고 싶다면 My_LangGraph_Demo 코드베이스를 한번 살펴보세요.

요약

이 워크플로는 여러 단계를 결합하므로, 여기서 모든 단계를 자세히 다루지는 않았어요. 하지만 LangChain을 사용하여 고급 GraphRAG 애플리케이션을 구축하는 데 어려움이 있었다는 점을 말씀드리고 싶어요. 이러한 어려움은 LangGraph를 사용하여 극복되었죠. 개인적으로 가장 아쉬웠던 점은 `Prompt` 템플릿에 필요한 만큼 많은 입력 변수를 도입하고, 해당 템플릿을 LangChain Expression Language를 통해 Graph QA 체인에 전달할 수 없다는 것이었어요.

처음에 LangGraph는 좀 복잡해 보였는데, 막상 사용해보니 훨씬 부드럽게 느껴지기 시작했어요. 앞으로는 에이전트를 워크플로에 통합하는 방법을 더 실험해볼 생각이에요. 혹시 좋은 아이디어가 있다면 언제든지 저에게 알려주세요! 여러분의 의견을 통해 더 많이 배우고 싶어요.

참고 자료:

이 글은 LangGraph의 다른 예제를 참고해서 작성되었어요.

  • Mistral 및 LangChain을 사용한 고급 RAG 제어 흐름: Corrective RAG, Self-RAG, Adaptive RAG
  • 메인의 langgaph-course/README.md · emarco177/langgaph-course
  • 메인의 요리책/third_party/langchain · mistralai/cookbook
  • LangChain 표현 언어를 사용한 동적 프롬프트
  • GraphRAG
  • LangChain
  • LangGraph

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

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

반응형

+ Recent posts