[커뮤니티 콘텐츠로서 본 게시물은 특정 작성자의 견해와 의견을 반영한 것으로, Neo4j의 공식적인 입장이 반드시 반영되는 것은 아닙니다.]
프로젝트 관리자에게 물어보면 새 프로젝트를 시작하기 전에 배운 내용을 검토하는 게 얼마나 중요한지 알려줄 거예요.
교훈을 얻은 데이터베이스는 프로젝트 팀이 프로젝트 성공 가능성을 높이는 데 도움이 되는 귀중한 정보로 가득 차 있죠. 그런데 왜 대부분의 교훈을 얻은 데이터베이스가 프로젝트 팀에서 잘 사용되지 않을까요?
제 경험에 따르면 검색하기가 너무 어렵고, 결과 집합을 검토하는 데 몇 시간이나 걸리거든요.
최근에 한 프로젝트 엔지니어가 팀이 관심을 갖고 있는 22개의 핵심 용어 목록을 사용해서 배운 교훈을 검색할 수 있는지 물어봤어요. 현재 키워드 검색 엔진에서는 각 용어를 개별적으로 입력하고 링크를 선택한 다음 검토를 위해 문서를 저장해야 했죠. (게다가 데이터베이스만 검색할 수 있는 방법은 없었고, 이 쿼리는 2천만 개에 가까운 URL 전체를 검색하게 되는 상황이었어요.) 이런 방법으로는 불가능하죠.
저는 우리 검색팀에게 제공된 용어를 사용해서 레슨 데이터베이스에 대해서만 특수 쿼리를 실행할 것인지 물어봤어요. 그랬더니 해당 용어가 포함된 각 문서에 대한 링크가 포함된 스프레드시트를 줬는데, 목록에는 1100개가 넘는 문서가 들어있었어요. 엔지니어는 그를 위해 하던 일을 멈췄죠.
저는 더 나은 방법이 있어야 한다고 생각하기 시작했어요.
특히 더 쉬운 시각화 메커니즘을 통해 서로 다른 것처럼 보이는 문서를 연결하는 사용자를 돕기 위해 주제 모델링을 실험하고 있었거든요. 여러 페이지의 링크 목록보다는 훨씬 나을 거라고 생각했어요.
그래서 도구 상자를 모았어요. R/RStudio는 주제 모델링 및 데이터 탐색을 위해, 는 주제를 모델링하고 시각화하기 위해, 그리고 Linkurious는 사용자가 검색하고 시각화할 수 있는 웹 프런트 엔드 Graph Database를 위해서요.
주제 모델 구축
모든 코드와 데이터는 제 GitHub 저장소에서 찾을 수 있어요. 이번 글에서는 과 Cypher 코드가 Neo4j와 함께 어떻게 작동하는지에 초점을 맞출 거예요. 다음 기사에서는 주제 모델링 코드를 자세히 설명해 드릴게요.
이 데모를 위해 저는 2,000개가 넘는 레슨을 공개 NASA 엔지니어링 네트워크 교훈 데이터베이스에서 가져왔어요. 데이터는 상당히 깨끗했고 몇 가지 유용한 메타데이터가 포함되어 있었죠.
주제 모델을 생성하는 단계는 다음과 같아요.
- 말뭉치를 생성해요.
- 모델에 사용할 문서 용어 행렬을 만들어요.
- 모델에 대한 최적의 주제 수를 결정해요. (토픽 모델링에서는 k개의 토픽을 알아야 하죠. 저는 현재 조화 평균 접근법을 사용하고 있는데, 이 데이터의 경우 35개의 토픽이 최적으로 결정되었어요.)
- 모델을 실행해요.
문서는 둘 이상의 주제에 속할 수 있기 때문에 모델에서 세타(문서별 확률)를 추출하고 각 문서에 가장 진단적인 주제를 할당했어요. 그런 다음 순위 순으로 각 주제에 대한 상위 30개의 용어를 추출하고 상위 3개 용어를 기반으로 각 주제에 대한 레이블을 생성하고 각 문서와 관련된 카테고리 메타데이터를 기반으로 주제를 상호 연관시켰어요. 이 모든 정보는 제 그래프 모델을 개발하는 데 도움이 될 거예요.
여기서는 RNeo4j 및 visNetwork 패키지를 사용해서 R에서 Graph Database를 시각화하는 방법을 보여드리고 싶었어요. (자세한 내용은 Nicole White의 RNeo4j 및 visNetwork를 사용한 그래프 시각화를 참고하세요.)
이미 데이터베이스를 만들었다고 가정하고, 제가 실행한 첫 번째 쿼리에서는 "오염"이라는 용어와 해당 용어가 포함된 주제가 반환되었어요.
간선 가중치는 주제 내 용어의 순위에 따라 결정돼요. 간선이 두꺼울수록 주제에서 순위가 높은 용어라는 뜻이죠. 다음 `query`는 주제 27에 대한 모든 교훈을 반환했어요. 마지막으로 다음을 사용해서 R에서 모델을 시각화했죠. LDAvis 패키지.
각 주제에 대한 상위 30개의 용어와 해당 용어가 다른 주제에 어떻게 분포되어 있는지 확인할 수 있었어요. 저에게는 도움이 되었지만, 최종 사용자에게는 필요한 게 아니었죠. 그래서 모든 수업 정보를 CSV 파일로 저장해서 Neo4j로 가져왔답니다.
그림 1: R의 용어 `query`
그림 2: R의 주제 `query`
그림 3: LDAvis를 사용한 주제 모델의 시각화
Graph Database 구축
만약 여러분이 Graph Database와 Neo4j 작업을 막 시작했다면, Nicole White의 블로그를 읽어보세요. 다음 섹션은 그녀의 웹 세미나를 기반으로 해요. 실제 세계에서 CSV 로드 사용.
8트랙 시대에 태어난 저는 먼저 화이트보드에 그래프를 모델링해 보기로 했어요. 제안된 `node`들의 다양한 연결을 그래프로 보여주는 대략적인 개요를 그렸죠. 정말 간단해요. 하지만 이게 모델에 계속 집중하는 데 도움이 된다는 걸 알았어요.
그림 4: 교훈을 얻은 데이터베이스 그래프 모델
`node`를 생성하기 위해 Cypher에서 LOAD CSV를 사용하여 위에서 생성한 데이터를 가져왔어요.
코드의 첫 번째 섹션은 강의 `node`에 대한 고유 제약 조건을 생성해서 강의 ID 중복을 방지해요. CSV 파일을 읽으면 레슨 `node`가 생성되고 속성이 설정되죠.
속성은 각 강의와 관련된 메타데이터에서 추출되었어요. Nicole이 웨비나에서 제안한 대로 날짜를 세 부분으로 나누었고요. 나중에 연도 속성을 사용하여 간선에 가중치를 할당할 거예요. 레슨이 최신일수록 간선이 더 커지겠죠. 각 강의 `node`에는 초록, 강의 자체(사용 가능한 경우), 강의가 시작된 부서, 안전 문제인지 여부 및 NEN 웹 사이트의 강의 링크에 대한 속성도 포함된답니다.
// Nodes created for Lessons, Submitter, Center and Topic
// Relations created
// Uniqueness constraints.
CREATE CONSTRAINT ON (l:Lesson) ASSERT l.name IS UNIQUE;
// Load.
USING PERIODIC COMMIT
LOAD CSV WITH HEADERS
FROM 'file:e:/Users/David/Dropbox/doctopics/data/llis.csv' AS line
WITH line, SPLIT(line.LessonDate, '-') AS date
CREATE (lesson:Lesson { name: TOINT(line.`LessonId`) } )
SET lesson.year = TOINT(date[0]),
lesson.month = TOINT(date[1]),
lesson.day = TOINT(date[2]),
lesson.title = (line.Title),
lesson.abstract = (line.Abstract),
lesson.lesson = (line.Lesson),
lesson.org = (line.MissionDirectorate),
lesson.safety = (line.SafetyIssue),
lesson.url = (line.url)
많은 Node들이 CSV 파일의 여러 강의와 연결되어 있어요. MERGE 명령은 Node가 없으면 Node를 생성하고, Node가 있으면 일치시키는 역할을 해요.
// Merges multiple entries of node in CSV file
MERGE (submitter:Submitter { name: UPPER(line.Submitter1) })
MERGE (center:Center { name: UPPER(line.Organization) })
MERGE (topic:Topic { name: TOINT(line.Topic) })
MERGE (category:Category { name: UPPER(line.Category) })
Node가 완성되면 Node 간의 관계를 만들었어요. 수업은 Topic에 포함되어 있고, Submitter가 작성했으며, NASA Center에서 발생했고, 특정 Category에 속하게 되는 거죠.
CREATE (topic)-[:Contains]->(lesson)
CREATE (submitter)-[:Wrote]->(lesson)
CREATE (lesson)-[:OccurredAt]->(center)
CREATE (lesson)-[:InCategory]->(category)
;
두 개의 학습 Node와 그 관계를 보여주는 완성된 결과는 다음과 같아요.
그림 5: 학습 Node 및 관계
R 코드에서는 각 Topic에 대해 가장 대표적인 Category를 계산해서 CSV 파일에 저장했고, 이제 이를 Graph Database에 로드할 거예요. Node는 이미 이전에 생성되었기 때문에 MATCH 함수를 사용해서 Node를 얻은 다음, Topic과 Category의 관계를 만들죠.
// Topic, category.
// Adds the category node and creates a relations to the topic
// Load.
USING PERIODIC COMMIT
LOAD CSV WITH HEADERS
FROM 'file:e:/Users/David/Dropbox/doctopics/data/topicCategory.csv' AS line
MATCH (topic:Topic { name: TOINT(line.Topic) })
MATCH (category:Category { name: UPPER(line.Category) })
CREATE (topic)-[:AssociatedTo]->(category)
;
그림 6: Category와 관련된 Topic
Topic을 연관시킨 다음 해당 관계를 Node에 추가할 수 있어요. 저는 방향이 지정되지 않은 관계를 사용하고 있었기 때문에 Edge를 생성하려면 MERGE를 사용해야 한다는 것을 알게 되었어요.
그런 다음 나중에 이 속성을 사용해서 Database에 Query해서 제가 관심 있는 강의와 연결되어 있는 강의가 포함될 수 있는 다른 Topic을 찾을 수 있죠.
// Topic, Correlation.
// Adds a relation to each topic using their correlation as a property of the relationship
// Load.
USING PERIODIC COMMIT
LOAD CSV WITH HEADERS
FROM 'file:e:/Users/David/Dropbox/doctopics/data/topicCorr.csv' AS line
MATCH (topic:Topic), (topic2:Topic)
WHERE topic.name = TOINT(line.Topic) AND topic2.name = TOINT(line.ToTopic)
MERGE (topic)-[c:CorrelatedTo {corr : TOFLOAT(line.Correlation)}]-(topic2)
// MERGE is used because it is non directed relationship
;
다음 Cypher 코드는 각 주제의 상위 30개 용어에 대한 `Node`를 생성하고, 용어 `Node`와 주제 `Node` 사이의 경계를 생성하고, `rank` 관계 속성을 주제의 용어 순위로 설정하고, 마지막으로 레이블이 R 코드에서 생성한 주제 `Node`에 레이블 속성을 설정합니다.
// Topic, Terms.
// Creates term nodes and relationship to topic by the rank the
// term is in the topic. A rank of 1 means that the term is the most
// frequent in that topic
// Load.
USING PERIODIC COMMIT
LOAD CSV WITH HEADERS
FROM 'file:e:/Users/David/Dropbox/doctopics/data/topicTerms.csv' AS line
MATCH (topic:Topic { name: TOINT(line.Topic) })
MERGE (term:Term { name: UPPER(line.Terms) })
CREATE (term)-[r:RankIn {rank : TOINT(line.Rank)}]->(topic)
;
// Topic, Labels.
// Creates label property for each topic by using the top 3 ranked terms in the topic.
// A rank of 1 means that term is the most frequent in that topic
// Load.
USING PERIODIC COMMIT
LOAD CSV WITH HEADERS
FROM 'file:e:/Users/David/Dropbox/doctopics/data/topicLabels.csv' AS line
MATCH (topic:Topic { name: TOINT(line.Topic) })
SET topic.label = line.Label
;
Neo4j의 Graph Database에서 정보를 추출하는 방법을 보여주기 위해 몇 가지 샘플 `Query`와 그 결과를 준비했어요. `Graph`와 행 버전 모두 아래에 표시되어 있습니다.
이 `Query`는 주제 27의 교훈을 반환합니다.
MATCH (n:Topic)-[r:Contains]->(m:Lesson)
WHERE n.name = 27
RETURN n, m,
n.name AS from,
m.id AS to,
m.title AS title,
(m.year) AS value,
"In" AS label
그림 7: 주제 27의 교훈, `Graph` 보기
그림 8: 주제 27의 교훈, 행 보기
주제 27과 상관관계가 0.02보다 큰 주제:
MATCH (n:Topic)-[r:CorrelatedTo]->(m:Topic)
WHERE n.name = 27 AND (r.corr > 0.02)
RETURN n, m,
n.name AS from,
m.id AS to,
m.title AS title,
(m.year) AS value,
"In" AS label
그림 9: 주제 27과 상관관계가 0.02보다 큰 주제
상관관계가 0.40보다 큰 주제들이에요. 각 주제에 대한 모든 상관관계도 표시하고 있는데, 아직 작업 중이랍니다.
MATCH (n:Topic)-[r:CorrelatedTo]->(m:Topic)
WHERE r.corr > 0.40
RETURN n, m,
n.name AS from,
m.name AS to,
(r.corr) AS value
그림 10: 상관관계가 0.40보다 큰 주제
주제 27과 상관된 모든 주제를 보여주고 있어요. 엣지 레이블은 숫자 상관 값이죠.
MATCH (n:Topic)-[r:CorrelatedTo]->(m:Topic)
WHERE n.name = 27
RETURN n, m,
n.name AS from,
m.name AS to,
(r.corr) AS value
그림 11: 주제 27과 관련된 모든 주제
사용자 시각화
이제 모든 데이터가 Graph Database에 있지만, 최종 사용자가 자신의 기준에 따라 강의를 더 쉽게 검색하고 연결할 수 있도록 해야 해요.
Neo4j 배우기에서 여러 시각화 옵션이 언급되었는데요. 저는 여러 가지 애플리케이션을 평가했고 이 데모를 위해 Linkurious를 선택했어요. Linkurious는 사용자가 그래프 데이터를 검색하고 시각화할 수 있는 웹 기반 인터페이스랍니다.
Linkurious는 Neo4j 데이터베이스에 연결하도록 설계되었고, 시작하기 전에 Neo4j 데이터베이스가 실행되고 있어야 해요. 애플리케이션을 열면 다음 대시보드가 표시될 거예요.
그림 12: Linkurious 대시보드
Linkurious에는 Elastic Search 인스턴스가 내장되어 있어서, 원한다면 모든 Node와 Edge를 검색할 수 있어요. 아래 예를 참조하세요.
그림 13: Linkurious Elastic Search
첫 번째 시각화를 만들 때 정말 큰 이점을 얻을 수 있어요. 예를 들어, "연료", "물", "밸브" 또는 "고장"이라는 용어가 포함된 강의를 찾고 있다고 가정해 볼게요. 그러면 해당 단어가 포함된 강의가 있는 Lesson Node, 두 용어에 대한 Term Node, 그리고 레이블로 세 용어가 포함된 Topic Node가 표시될 거예요.
레이블은 각 주제에 대한 상위 3개 용어를 사용해서 만들어졌어요. 따라서 주제 2에 포함된 강의와 연료 밸브 및/또는 물 밸브 문제와 관련된 강의에서 해당 용어가 자주 등장한다고 생각해도 괜찮겠죠?
그림 14: 데이터베이스 검색
그림 15: 주제 2 Node
저는 주어진 주제를 탐구하고 관계를 밝혀낼 수 있어요. 주제가 다른 4개의 Node와 관련되어 있으니까, 화면에 표시하고 싶은 Node를 선택할 수 있는 옵션이 제공되는 거죠.
강의 Node를 선택하면 이 주제에 포함된 모든 강의를 표시할 수 있어요. 시각화를 조정해서 색상과 크기를 추가할 수도 있고요. 이 경우 각 Node 유형은 서로 다른 색상을 가지고, Node 크기는 강의가 작성된 연도에 따라 결정돼요. 최신 강의일수록 Node의 크기가 커지는 거죠.
각 Node의 속성이 왼쪽에 표시돼요. 아래 이미지에서 강조 표시된 Node에 대한 정보를 볼 수 있고, 사용자가 강의가 저장된 사이트를 방문하고 싶다면 속성에서 URL을 클릭하면 돼요.
그림 16: 주제 내 개별 강의
제 데이터를 계속 탐색할 수 있어요. 강조 표시된 Lesson Node를 클릭하면 Lesson의 작성자, Lesson이 발생한 센터, 그리고 연관된 카테고리를 알려주는 Edge가 표시된답니다.
그림 17: 강의 Node 탐색
주제 Node로 돌아가서 다시 클릭하면 관련 카테고리가 표시돼요. 그런 다음 카테고리 Node를 탐색해서 위험 관리, 에너지, 전력 또는 지상 지원 시스템과 관련된 강의를 볼 수 있죠. 이걸 통해 내가 찾고 있는 것과 밀접하게 일치하는 다른 강의를 찾을 수 있어요.
키워드 검색 목록에서는 이러한 연결을 볼 수 없다는 점!
그림 18: 카테고리 Node 추가하기
그림 19: 카테고리 Node 탐색
저는 Neo4j, R/RStudio 및 Linkurious를 통해 현재 검색 엔진이 할 수 없는 방식으로 데이터를 탐색하고 시각화할 수 있다고 생각해요. 아직 진행 중인 작업이지만, 이러한 방식으로 Graph Database를 사용하면 사용자에게 보다 효과적인 검색 경험을 제공해서 답변을 찾는 시간을 줄이고 올바른 방향으로 프로젝트를 시작할 수 있다고 믿습니다.
이러한 도구의 조합을 통해 분석가는 대규모 문서 저장소에 대한 탁월한 분석 및 시각적 표현을 생성할 수 있어요.
궁금한 점이나 제안 사항이 있다면 트위터로 편하게 연락 주세요!
- 교훈을 얻은 데이터베이스
- 링크리어스
- nasa
- r/rstudio
- RNeo4j
- rstudio
- Visualization
에이치시스템즈의 LogTree는 Neo4j 기반 GraphRAG 플랫폼으로, 데이터를 자동으로 지식그래프화하고 자연어 질의로 즉시 답을 제공합니다.
'GraphRAG' 카테고리의 다른 글
| Neo4j GraphRAG 검색기를 MCP 서버로 구현하기 (0) | 2026.05.26 |
|---|---|
| AWS와 Neo4j, LLM 환각 해결 및 GenAI 진화를 위해 손을 잡다 (0) | 2026.05.26 |
| 그래프의 시각적 단순성: Fractal 5 팀과의 5분 인터뷰 (Neo4j, GraphRAG, Machine Learning, API) (1) | 2026.05.25 |
| Neo4j에 Microsoft GraphRAG 통합하기 (0) | 2026.05.25 |
| 지식 그래프로 뉴스 속 의미 찾기: Neo4j와 GraphRAG 활용 (0) | 2026.05.25 |
