본문 바로가기
Cloud SIEM 제작/Graph DB(Neo4j)

[Neo4j] Graph Data Science-머신러닝(Machine Learning)

by LIZ0904 2023. 1. 6.
반응형

GDS의 머신러닝이란?

Neo4j에서 그래프를 생성하고 Python, Apache Spark 등과 같은 머신 러닝 학습을 수행하기 위해 다른 환경으로 내보낼 수 있어야 한다. 이러한 외부 프레임 워크는 사용자가 정의할 수 있는 유연성을 갖고 있다. GDS의 머신러닝은 그래프 머신러닝 학습 패턴, 파이프라인 객체 및 관리, 노드 분류, 링크 예측 등을 할 수 있다.

 

- Neo4j에서 그래프 기반 머신러닝 도구를 사용해야 하는 이유

1. 복잡한 모델 디자인 관리
2. 강력한 데이터베이스 결합을 통한 빠른 프로덕션 경로
3. 개발 및 테스팅

 

GDS의 ML 파이프라인 및 모델

GDS는 end-to-end ML 워크플로를 위한 관리형 파이프라인을 제공한다. 데이터 선택, 기능 엔지니어링, 데이터 분할, 하이퍼 파라미터 구성 및 학습 단계가 파이프라인 개체 내에서 함께 결합되어 종단 간 단계를 추적한다.

 

지원되는 ML 파이프 라인의 유형은 두가지이다.

노드 분류 파이프라인 노드 에 대한 감독 바이너리 및 다중 클래스 분류
링크 예측 파이프라인 관계 또는 "링크"가 노드 쌍 사이에 존재해야 하는지 여부에 대한 감독 예측

GDS에서는 한번에 여러 파이프라인과 모델 개체를 가질 수 있다. 이때 파이프라인과 모델 모두 그래프 카탈로그와 같이 유사하게 이름을 사용해 관리할 수 있는 카탈로그가 있다.

 

전처리 및 기타 리소스

https://neo4j.com/docs/graph-data-science/current/machine-learning/pre-processing/

 

Pre-processing - Neo4j Graph Data Science

In most machine learning scenarios, several pre-processing steps are applied to produce data that is amenable to machine learning algorithms. This is also true for graph data. The goal of pre-processing is to provide good features for the learning algorith

neo4j.com

위 링크에서 전처리에 관련한 추가 가이드를 확인할 수 있다.

 

https://neo4j.com/docs/graph-data-science/current/machine-learning/training-methods/

 

Training methods - Neo4j Graph Data Science

This section describes supervised machine learning methods for training pipelines in the Neo4j Graph Data Science library.

neo4j.com

위 링크에서 학습 방법 및 하이퍼 파라미터를 구성하는데 관련한 추가 가이드를 확인할 수 있다.

 

노드 분류 패턴(노드 분류 파이프라인)

노드 분류

위 그림에서 훈련단계인 1~6단계는 파이프라인에 의해 자동으로 실행된다. 

1. 그래프를 만들고 파이프라인을 구성한다.

2. train 명령으로 파이프라인을 실행한다.

3. predict 명령을 사용해 그래프를 예측한다. 그 다음 필요에 따라 그래프 쓰기 작업을 사용해 예측 결과를 데이터베이스에 다시 쓸 수 있다.

 

노드 분류로 영화 장르 예측 예제

- 문제 설정

그래프의 어떤 영화가 "Comedy"가 있는 영화 Genre로 분류할지 예측하는 모델을 훈련한다.

MATCH (m:Movie)-[:IN_GENRE]->(g)
WITH m , collect(g.name) AS genres
SET m.cls = toInteger('Comedy' IN genres)
RETURN count(m), m.cls;

영화에서 cls를 하당해 Comedy 장르이면 1, 그렇지 않으면 0을 할당해준다.

 

위 명령의 결과를 보면, Comedy 장르인건 3315개, Comedy 장르가 아닌 영화는 5810개 결과가 출력된다.

 

MATCH(m:Movie)
WHERE m.year >= 2010
    AND m.runtime IS NOT NULL
    AND m.imdbRating IS NOT NULL
SET m:TrainMovie
RETURN count(m)

Movie에는 일부 누락된 속성 값(NULL인 것)들이 있다. 이 시나리오에서는 해당 값을 없애고 싶기 때문에, WHERE 절에서 NOT NULL 인 애들만 가져와 TrainMovie라는 레이블로 따로 지정해준다. 또한 2010년 이후에 개봉된 영화만 넣도록 필터링해준다. 이때 NULL 값이 아닌 값들은 runtime과 imdbRating이다.

 

CALL gds.graph.project('proj',
    {
        Actor:{},
        TrainMovie:{ properties: ['cls', 'imdbRating', 'runtime']}
    },
    {
        ACTED_IN:{},
        HAD_ACTOR:{type:'ACTED_IN', orientation:'REVERSE'}
    }
);

CALL gds.beta.collapsePath.mutate('proj',
  {
    pathTemplates: [['HAD_ACTOR', 'ACTED_IN']],
    allowSelfLoops: false,
    mutateRelationshipType: 'SHARES_ACTOR_WITH'
  }
) YIELD relationshipsWritten;

그 다음 TrainMovie 노드 레이블을 사용해 그래프를 만들어준다. Actor 노드와 TrainMovie 노드를 가져오고 TrainMovie의 프로퍼티는 cls, imdbRaing, runtime만 가져온다. 그리고 관계는 ACTED_IN 관계를 가져오고, HAD_ACTOR 관계는 ACTED_IN 관계를 반대방향으로 만들어서 넣어둔 관계이다.

 

그 다음 빠른 데모를 위해 파이프라인 내에서 그래프를 더 쉽게 처리할 수 있는 collapsePath 프로젝션을 사용한다.

 

파이프라인 구성

구성 단계는 다음과 같다.

1. 파이프라인 생성
2. 노드 속성 추가
3. 기능으로 노드 속성 선택
4. 노드 분할 구성
5. 모델 후보 추가

 

1. 파이프라인 생성

CALL gds.beta.pipeline.nodeClassification.create('pipe')

위 명령을 실행해 파이프라인을 생성한다.

 

2. 노드 속성 추가

노드 분류 파이프라인은 프로젝션에서 노드 속성을 생성하는 mutate 모드에서 한개 이상의 GDS 알고리즘을 실행할 수 있다.

CALL gds.beta.pipeline.nodeClassification.addNodeProperty('pipe', 'fastRP', {
  embeddingDimension: 32,
  randomSeed: 7474,
  mutateProperty:'embedding'
})
YIELD name, nodePropertySteps;

먼저 위 코드를 통해 그래프에서 Movie 노드의 지역성을 캡슐화 할 FastRP 임베딩을 생성한다.

 

CALL gds.beta.pipeline.nodeClassification.addNodeProperty('pipe', 'degree', {
  mutateProperty:'degree'
})
YIELD name, nodePropertySteps;

다음으로 Actor를 공유하는 다른 영화의 수를 측정하는 정도 중심성(Degree)를 추가한다.

 

CALL gds.beta.pipeline.nodeClassification.addNodeProperty('pipe', 'alpha.scaleProperties', {
  nodeProperties: ['runtime'],
    scaler: 'Log',
  mutateProperty:'logRuntime'
})
YIELD name, nodePropertySteps;

마지막으로 다른 속성에 비해 상대적으로 크기가 큰 이와 같은 값에 대한 모범 사례인 런타임 속성의 크기를 조정해준다.

 

3. 기능으로 노드 속성 선택

CALL gds.beta.pipeline.nodeClassification.selectFeatures(
    'pipe',
    ['imdbRating', 'logRuntime', 'embedding', 'degree'])
YIELD name, featureProperties;

속성이 구성되면 모델의 기능으로 사용할 노드 속성의 하위 집합을 구성할 수 있다.

 

4. 노드 분할 구성(데이터 분할)

CALL gds.beta.pipeline.nodeClassification.configureSplit('pipe', {
 testFraction: 0.2,
  validationFolds: 5
})
YIELD splitConfig;

테스트 노드와 학습 노드 간에 무작위로 분할하는 방법을 결정하는 testFraction을 구성한다. 파이프라인은 교차 유효성 검사 전략을 사용하기 때문에 여기에서 원하는 유효성 검사 수를 설정할 수 있다.

 

5. 모델 후보 추가

파이프라인은 서로 다른 학습 방법과 하이퍼 파라미터 구성으로 여러 모델을 실행할 수 있다. 훈련이 완료되면 가장 성능이 좋은 모델이 선택된다.

 

CALL gds.beta.pipeline.nodeClassification.addLogisticRegression('pipe', {penalty: 0.0})
YIELD parameterSpace;

CALL gds.beta.pipeline.nodeClassification.addLogisticRegression('pipe', {penalty: 0.1})
YIELD parameterSpace;

CALL gds.beta.pipeline.nodeClassification.addLogisticRegression('pipe', {penalty: 1.0})
YIELD parameterSpace;

다른 패널티 하이퍼파라미터와 함께 몇가지 다른 로지스틱 회귀를 추가해주었다. 

 

파이프라인 교육

파이프라인을 훈련하기 위한 과정을 아래와 같다,.

1. 노드 및 관계 필터 적용
2. 파이프라인 구성 단계 실행
3. 모든 후보 모델에 대한 교차 검증 훈련
4. 매개변수에 따라 최상의 후보를 선택
5. 전체 훈련 세트에서 선택된 모델을 재훈련하고, 테스트 세트에서 최종 평가를 수행
6. 모델 카탈로그에 최종 선택 모델을 등록
CALL gds.beta.pipeline.nodeClassification.train('proj', {
  pipeline: 'pipe',
  targetNodeLabels: ['TrainMovie'],
  modelName: 'nc-pipeline-model',
  targetProperty: 'cls',
  randomSeed: 7474,
  metrics: ['ACCURACY']
}) YIELD modelInfo
RETURN
  modelInfo.bestParameters AS winningModel,
  modelInfo.metrics.ACCURACY.train.avg AS avgTrainScore,
  modelInfo.metrics.ACCURACY.outerTrain AS outerTrainScore,
  modelInfo.metrics.ACCURACY.test AS testScore;

위 과정에 대한 쿼리는 위와 같다.

 

파이프라인으로 예측

CALL gds.beta.pipeline.nodeClassification.predict.write(
  graphName: String,
  configuration: Map
)
YIELD
  preProcessingMillis: Integer,
  computeMillis: Integer,
  postProcessingMillis: Integer,
  writeMillis: Integer,
  nodePropertiesWritten: Integer,
  configuration: Map

학습된 모델로 예측하고 그래프에 다시 쓰는 작업은 위와 같은 쿼리를 통해 실행된다.

이 작업은 stream과 mutate 를 지원한다.

 

링크 예측

파이프라인 구성 및 실행과 결과 모델링 개체로 예측하는 방법을 공부해보자! GDS는 0-1 표시기, 0은 링크 없음, 1은 링크인 이진 분류기를 제공한다. 이러한 유형의 링크 예측은 소셜 네트워크 및 엔터티 해결 문제와 같이 단일 레이블의 노드 간 한 유형의 관계를 예측하는 무방향 그래프에서 짱 잘 작동한다.

 

 

위 그림은 다양한 단계를 통해 그래프에서 최종적으로 모델을 등록하고 데이터에 대한 예측을 수행하는 GDS의 링크 예측 패턴을 보여준다. 노드 분류와 마찬가지로 훈련단계는 파이프라인에 의해 자동으로 실행된다. 구성 및 하이퍼파라미터를 잘 짜기만 하면 된다.

 

링크 예측으로 Actor 관계 예측하기(예제)

- 문제 설정

//최근 개봉된 영화와 수익을 기준으로 Movie 노드를 세팅한다. (RecenBigMovie)
MATCH (m:Movie)
WHERE m.year >= 1990 AND m.revenue >= 1000000
SET m:RecentBigMovie;

//관계를 반대방향으로 지정한 기본 프로젝션을 생성한다.
CALL gds.graph.project('proj',
  ['Actor','RecentBigMovie'],
  {
  	ACTED_IN:{type:'ACTED_IN'},
    HAS_ACTOR:{type:'ACTED_IN', orientation: 'REVERSE'}
  }
);

//관계 집계에 대한 축소된 경로 유틸리티를 만든다. (가중치 속성 X)
CALL gds.beta.collapsePath.mutate('proj',{
    pathTemplates: [['ACTED_IN', 'HAS_ACTOR']],
    allowSelfLoops: false,
    mutateRelationshipType: 'ACTED_WITH'
});

//그래프로 돌아가 관계를 다시 작성한다.
CALL gds.graph.writeRelationship('proj', 'ACTED_WITH');

//중복 제거 수행
MATCH (a1:Actor)-[s:ACTED_WITH]->(a2)
WHERE id(a1) < id(a2)
DELETE s;

//남아있던 extra 레이블을 삭제한다.
MATCH (m:RecentBigMovie) REMOVE m:RecentBigMovie;

//그래프 프로젝션 수행
CALL gds.graph.drop('proj');
CALL gds.graph.project('proj', 'Actor', {ACTED_WITH:{orientation: 'UNDIRECTED'}});

어떤 배우가 같은 영화에 출연할 가능성이 가장 높은지를 예측하기 위해 ACTED_WITH 링크를 만들어 모델을 훈련시킨다. 이와 동일한 방법론은 다양한 소셜 네트워크 추천 문제에 사용할 수 있다. 예를 들어, 배우들이 같은 연기를 하고 서로 친구인 사용자가 있다는 모델을 사용해 친구추천을 해줄 수 있다. 또 사기 탐지 등에서 서로 알고 있거나 상호작용하는 용의자와 피해자 커뮤니티가 있는 경우 링크 예측을 사용해 그래프에서는 보이지 않은 링크 예측을 수행할 수 있다. 

 

- 파이프라인 구성

구성단계는 아래와 같다.

1. 파이프라인 생성
2. 노드 속성 추가
3. 링크 기능 추가
4. 관계 분할 구성
5. 모델 후보 추가

 

1. 파이프라인 생성

CALL gds.beta.pipeline.linkPrediction.create('pipe');

위 명령을 통해 파이프라인을 생성한다. 위 코드를 통해 파이프라인 카탈로그에 파이프라인이 저장된다. 

 

2. 노드 속성 추가

CALL gds.beta.pipeline.linkPrediction.addNodeProperty('pipe', 'fastRP', {
    mutateProperty: 'embedding',
    embeddingDimension: 128,
    randomSeed: 7474
}) YIELD nodePropertySteps;

CALL gds.beta.pipeline.linkPrediction.addNodeProperty('pipe', 'degree', {
    mutateProperty: 'degree'
}) YIELD nodePropertySteps;

위 코드를 통해 두 배우가 네트워크에서 서로 가까이 있으면 같은 영화에서 같은 역할을 할 가능성이 더 높다는 전제를 두어 fastRP 노드 임베딩을 사용한다.

 

3. 링크 기능 추가

이 단계에서는 노드 쌍에서 속성을 가져오고 링크 예측 모델에 대한 기능을 계산하는 대칭함수를 구성한다.

이 문제에 대해 우리는 FastRP 임베딩에 코사인 거리와 L2를 사용하는데, 이는 유사성/거리의 좋은 측정이며 두 노드 사이의 총 크기의 좋은 측정인 정도 중심성에 대한 hadamard가 있다.

//L2
CALL gds.beta.pipeline.linkPrediction.addFeature('pipe', 'l2', {
  nodeProperties: ['embedding']
}) YIELD featureSteps;

//cosine
CALL gds.beta.pipeline.linkPrediction.addFeature('pipe', 'cosine', {
  nodeProperties: ['embedding']
}) YIELD featureSteps;

//정도 중심성(노드 사이의 총 크기)
CALL gds.beta.pipeline.linkPrediction.addFeature('pipe', 'hadamard', {
  nodeProperties: ['degree']
}) YIELD featureSteps;

 

4. 관계 분할 구성

이제 교차 검증에 사용되는 훈련/테스트/기능 세트의 비율, 네거티브 샘플링 비율 및 검증 개수를 설정하는 관계 분할을 구성해야 한다.

CALL gds.beta.pipeline.linkPrediction.configureSplit('pipe', {
    testFraction: 0.2,
    trainFraction: 0.5,
    negativeSamplingRatio: 2.0
}) YIELD splitConfig;

위 쿼리에서는 관계는 20% 테스트, 학습은 40%, 기능은 40%로 입력해 분할한다. 또한 네거티브 샘플링 비율은 2.0을 사용해서 추정한다. 링크 예측의 맥락에서 네거티브 샘플은 링크가 없는 노드 쌍을 의미한다. 이들은 관계 분할 단계에서 무작위로 샘플링된다. 

 

5. 모델 후보 추가

CALL gds.beta.pipeline.linkPrediction.addLogisticRegression('pipe', {
    penalty: 0.001,
    patience: 2
}) YIELD parameterSpace;

CALL gds.beta.pipeline.linkPrediction.addLogisticRegression('pipe', {
    penalty: 1.0,
    patience: 2
}) YIELD parameterSpace;

파이프라인은 서로 다른 학습 방법과 하이퍼파라미터 구성으로 여러 모델을 실행할 수 있으며, 훈련 단계가 완료된 후 가장 성능이 좋은 모델이 선택된다. 위 쿼리를 통해 다른 페널티 하이퍼파라미터와 함께 몇가지 다른 로지스틱 회귀를 추가했다. 

 

파이프라인 교육

파이프 라인 교육 절차는 다음과 같다.

1. 노드 및 관계 필터 적용
2. 파이프라인 구성 단계 실행
3. 모든 후보 모델에 대한 교차 검증으로 훈련
4. AUCPR이라고 하는 평균 정밀도 재현율에 따라 최상의 후보 선택
5. 전체 교육 세트에서 선택된 모델을 재교육하고, AUCPR을 사용해 테스트에 대한 최종 평가를 수행
6. 모델 카탈로그에 최종 선택 모델 등록
CALL gds.beta.pipeline.linkPrediction.train('proj', {
    pipeline: 'pipe',
    modelName: 'lp-pipeline-model',
    targetRelationshipType: 'ACTED_WITH',
    randomSeed: 7474 //usually a good idea to set a random seed for reproducibility.
}) YIELD modelInfo
RETURN
modelInfo.bestParameters AS winningModel,
modelInfo.metrics.AUCPR.train.avg AS avgTrainScore,
modelInfo.metrics.AUCPR.outerTrain AS outerTrainScore,
modelInfo.metrics.AUCPR.test AS testScore

그와 관련한 예시 코드는 위와 같다.

 

모델을 사용한 예측

파이프라인이 훈련되면 이를 사용하여 그래프의 새 링크를 예측할 수 있다. 동일한 스키마를 가진 데이터에 파이프라인을 다시 적용할 수도 있다. 

 

CALL gds.beta.pipeline.linkPrediction.predict.stream('proj', {
  modelName: 'lp-pipeline-model',
  sampleRate:0.1,
  topK:1,
  randomSeed: 7474,
  concurrency: 1
})
 YIELD node1, node2, probability
 RETURN gds.util.asNode(node1).name AS actor1, gds.util.asNode(node2).name AS actor2, probability
 ORDER BY probability DESC, actor1

위 쿼리는 그래프 프로젝션에서 예측된 링크를 저장하는 mutate 실행 모드를 수행하는 쿼리이다. 데이터베이스에 다시 쓰려면 mutate 명령 다음에 gds.graph.writeRelationship 명령을 사용할 수도 있다.

 

반응형

댓글