# Using Neo4j for Heterogeneous nodes link prediction

**URL:** <https://community.neo4j.com/t/using-neo4j-for-heterogeneous-nodes-link-prediction/67069>\
**Category:** Graph Data Science / Graph Analytics\
**Tags:** cypher, migrated\
**Created:** [March 28, 2024, 6:00am UTC](https://community.neo4j.com/t/using-neo4j-for-heterogeneous-nodes-link-prediction/67069 "2024-03-28T06:00:44Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![alanwtmec](https://sea1.discourse-cdn.com/flex021/user_avatar/community.neo4j.com/alanwtmec/32/31233_2.png) [@alanwtmec](https://community.neo4j.com/u/alanwtmec)\
**Post date:** [March 28, 2024, 6:00am UTC](https://community.neo4j.com/t/using-neo4j-for-heterogeneous-nodes-link-prediction/67069/1 "2024-03-28T06:00:44Z")

</div>

Dear neo4j developer

I have a question, I want to use Neo4j for Heterogeneous nodes link prediction.  
There are 2 types of node labels, and I need to predict the connections between these 2 types of nodes.  
Do you have any actual code examples for this?"

I tested using the example from [Training the pipeline - Neo4j Graph Data Science](https://neo4j.com/docs/graph-data-science/current/machine-learning/linkprediction-pipelines/training/).  
Then I used my own data (code as follows:), and made many changes (e.g. sourceNodeLabel: 'Person', targetNodeLabel: 'Product', targetRelationshipType: 'BUY').  
However, the results seemed unreasonable, with all probabilities being 0.4999.

Could you please advise how I should modify the code? Where did I go wrong? Are there any other examples I can refer to? Thank you.

code: (below)

```auto
CREATE
  (alice:Person {name: 'Alice', age: 38, gender:1}),
  (michael:Person {name: 'Michael', age: 67, gender:0}),
  (karin:Person {name: 'Karin', age: 30, gender:1}),
  (chris:Person {name: 'Chris', age: 52, gender:0}),
  (will:Person {name: 'Will', age: 18, gender:0}),
  (mark:Person {name: 'Mark', age: 20, gender:0}),
 
  (milk:Product {name: 'milk', price:80}),
  (apple:Product {name: 'apple', price:25}),
  (orange:Product {name: 'orange', price:35}),
  (piapple:Product {name: 'piapple', price:65}),
  (watermelon:Product {name: ' watermelon', price:45}),
  (cantaloupe:Product {name: 'cantaloupe', price:55}),
  (notebook:Product {name: 'notebook', price:25000}),
  (tv:Product {name: 'TV', price:19999}),
  (aircon:Product {name: 'aircondition', price:29000}),
  (shoe_air:Product {name: 'shoe_air', price:5000}),
  (runshoes:Product {name: 'runshoes', price:1999}),
  (sportshoes:Product {name: 'sportshoes', price:3900}),
  (goodshoes:Product {name: 'goodshoes', price:7900}),
  (underwear:Product {name: 'Underwear', price:390}),
  (pants:Product {name: 'Pants', price:499}),
  (jen:Product {name: 'Jen', price:600}),

  (fruit:fruit {name: 'fruit'}),
  (ele:ele {name: 'ele'}),
  (shoe:shoe {name: 'shoe'}),
  (clothe:clothe {name: 'clothe'}),
  
  (milk)-[:BELONG]->(fruit),
  (apple)-[:BELONG]->(fruit),
  (orange)-[:BELONG]->(fruit),
  (piapple)-[:BELONG]->(fruit),
  (watermelon)-[:BELONG]->(fruit),
  (cantaloupe)-[:BELONG]->(fruit),
  (notebook)-[:BELONG]->(ele),
  (tv)-[:BELONG]->(ele),
  (aircon)-[:BELONG]->(ele),
  (shoe_air)-[:BELONG]->(shoe),
  (runshoes)-[:BELONG]->(shoe),
  (sportshoes)-[:BELONG]->(shoe),
  (goodshoes)-[:BELONG]->(shoe),
  (underwear)-[:BELONG]->(clothe),
  (pants)-[:BELONG]->(clothe),
  (jen)-[:BELONG]->(clothe),    

  (alice)-[:BUY]->(milk),
  (alice)-[:BUY]->(apple),
  (alice)-[:BUY]->(orange),
  (alice)-[:BUY]->(piapple), 
  (michael)-[:BUY]->(apple),
  (michael)-[:BUY]->(orange),
  (michael)-[:BUY]->(notebook),
  (michael)-[:BUY]->(shoe_air),
  (karin)-[:BUY]->(milk),
  (karin)-[:BUY]->(pants),
  (karin)-[:BUY]->(jen),
  (karin)-[:BUY]->(underwear),
  (chris)-[:BUY]->(sportshoes),
  (chris)-[:BUY]->(runshoes),
  (chris)-[:BUY]->(goodshoes),  
  (will)-[:BUY]->(aircon),
  (will)-[:BUY]->(tv),
  (will)-[:BUY]->(notebook),
  (mark)-[:BUY]->(watermelon),
  (mark)-[:BUY]->(cantaloupe),
  
  (alice)-[:FRIEND]->(michael),
  (michael)-[:FRIEND]->(will),
  (michael)-[:FRIEND]->(mark),
  (alice)-[:FRIEND]->(karin),
  (will)-[:FRIEND]->(mark),
  (mark)-[:FRIEND]->(chris),
  (chris)-[:FRIEND]->(alice),
  (will)-[:FRIEND]->(karin)

CALL gds.graph.project(
  'fullGraph',
  {
    Person: 
    {
      properties: 
        {
        age: {defaultValue: 1},
        price: {defaultValue: 1}
        }    
    },
    Product: 
    {
      properties: 
        {
        age: {defaultValue: 1},
        price: {defaultValue: 1}
        }
    }
  },
  {
    BUY: {
      orientation: 'UNDIRECTED'
    },
    FRIEND: {},
    BELONG: {}
  }
)

CALL gds.beta.pipeline.linkPrediction.create('pipe-with-context')

CALL gds.beta.pipeline.linkPrediction.addNodeProperty('pipe-with-context', 'fastRP', {
  mutateProperty: 'embedding',
  embeddingDimension: 256,
  randomSeed: 42,
  contextRelationshipTypes: ['FRIEND', 'BELONG']
})

CALL gds.beta.pipeline.linkPrediction.addFeature('pipe-with-context', 'hadamard', {
  nodeProperties: ['embedding', 'age', 'price']
})

CALL gds.beta.pipeline.linkPrediction.configureSplit('pipe-with-context', {
  testFraction: 0.25,
  trainFraction: 0.6,
  validationFolds: 3
})

CALL gds.alpha.pipeline.linkPrediction.addMLP('pipe-with-context',
{hiddenLayerSizes: [4, 2], penalty: 1, patience: 2})

CALL gds.beta.pipeline.linkPrediction.train('fullGraph', {
  pipeline: 'pipe-with-context',
  modelName: 'lp-pipeline-model-filtered',
  metrics: ['AUCPR', 'OUT_OF_BAG_ERROR'],
  sourceNodeLabel: 'Person',
  targetNodeLabel: 'Product',
  targetRelationshipType: 'BUY',
  randomSeed: 12
}) YIELD modelInfo, modelSelectionStats
RETURN
  modelInfo.bestParameters AS winningModel,
  modelInfo.metrics.AUCPR.train.avg AS avgTrainScore,
  modelInfo.metrics.AUCPR.outerTrain AS outerTrainScore,
  modelInfo.metrics.AUCPR.test AS testScore,
  [cand IN modelSelectionStats.modelCandidates | cand.metrics.AUCPR.validation.avg] AS validationScores
  
CALL gds.beta.pipeline.linkPrediction.predict.stream('fullGraph', {
  modelName: 'lp-pipeline-model-filtered',
  topN: 50,
  threshold: 0
})
 YIELD node1, node2, probability
 RETURN gds.util.asNode(node1).name AS Person, gds.util.asNode(node2).name AS Product, probability
 ORDER BY Product

```

Result : (below)

```auto
╒═══════════╤══════════════╤══════════════════╕
│Person │Product │probability │
╞═══════════╪══════════════╪══════════════════╡
│"Alice" │" watermelon" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Alice" │"cantaloupe" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Alice" │"TV" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Alice" │"shoe_air" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Alice" │"sportshoes" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Alice" │"runshoes" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Alice" │"aircondition"│0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Alice" │"notebook" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Chris" │"milk" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Chris" │"cantaloupe" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Chris" │"TV" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Chris" │"shoe_air" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Chris" │"aircondition"│0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Chris" │"notebook" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Chris" │" watermelon" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Chris" │"piapple" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Chris" │"orange" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Chris" │"apple" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Karin" │"piapple" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Karin" │"cantaloupe" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Karin" │"TV" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Karin" │"shoe_air" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Karin" │"sportshoes" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Karin" │"runshoes" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Karin" │"aircondition"│0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Karin" │"notebook" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Karin" │" watermelon" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Karin" │"orange" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Karin" │"apple" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Mark" │"milk" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Mark" │"orange" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Mark" │"TV" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Mark" │"shoe_air" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Mark" │"runshoes" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Mark" │"sportshoes" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Mark" │"aircondition"│0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Mark" │"notebook" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Mark" │"piapple" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Mark" │"apple" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Pants" │"Alice" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Pants" │"Michael" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Pants" │"Will" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Pants" │"Mark" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Pants" │"Chris" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Underwear"│"Alice" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Underwear"│"Michael" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Underwear"│"Mark" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Underwear"│"Will" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Underwear"│"Chris" │0.4999999999999998│
├───────────┼──────────────┼──────────────────┤
│"Will" │"milk" │0.4999999999999998│
└───────────┴──────────────┴──────────────────┘

```

Neo4j Desktop-1.5.7  
Version 5.18.0`Preformatted text`

---

<div class="post-metadata">

**Author:** ![adam\_schill-col](https://sea1.discourse-cdn.com/flex021/user_avatar/community.neo4j.com/adam_schill-col/32/19938_2.png) [@adam\_schill-col](https://community.neo4j.com/u/adam_schill-col)\
**Post date:** [April 2, 2024, 9:14am UTC](https://community.neo4j.com/t/using-neo4j-for-heterogeneous-nodes-link-prediction/67069/2 "2024-04-02T09:14:09Z")

</div>

Hi @alanwtmec,

The output does indeed look a bit fishy. Unfortunately there are no other examples I'm aware of that does this.

What is the output of the call to `gds.beta.pipeline.linkPrediction.train`? Do the scores seem resonable at that point in the pipeline?

It looks like you're leaving out some node labels from your projection: `fruit`, `ele`, `shoe`, `clothe`. Should these perhaps share a node label instead? That should then also be included in the projection?

Apart from that the code looks ok to me. But it's very noisy to train a model on such a small graph, so hard to predict what you're going to get. To see if you can get interesting scores, here are some things to experiment with:

- Use a different training method, like logistic regression or random forest
- Try different parameters for the MLP
- Try using fewer features for the pipeline, maybe only 'embedding'
- Experiment with different default values for `age` and `price`

Hope this is helpful,  
Adam

---

<div class="post-metadata">

**Author:** ![alanwtmec](https://sea1.discourse-cdn.com/flex021/user_avatar/community.neo4j.com/alanwtmec/32/31233_2.png) [@alanwtmec](https://community.neo4j.com/u/alanwtmec)\
**Post date:** [April 3, 2024, 2:36am UTC](https://community.neo4j.com/t/using-neo4j-for-heterogeneous-nodes-link-prediction/67069/3 "2024-04-03T02:36:50Z")

</div>

Hi, Adam  
Thank you for your reply.  
Below is output of gds.beta.pipeline.linkPrediction.train.

 ![image](https://us1.discourse-cdn.com/flex021/uploads/neo4jcommunity/original/3X/9/5/9515520558f11125e8729d146b68c00bfe4d5f9e.png)

I have tried another dataset( the [Hetionet](https://het.io/) dataset), but the answer is still all 0.49999.  
Is there something I haven't noticed?  
Thank you!

---

<div class="post-metadata">

**Author:** ![adam\_schill-col](https://sea1.discourse-cdn.com/flex021/user_avatar/community.neo4j.com/adam_schill-col/32/19938_2.png) [@adam\_schill-col](https://community.neo4j.com/u/adam_schill-col)\
**Post date:** [April 3, 2024, 7:42am UTC](https://community.neo4j.com/t/using-neo4j-for-heterogeneous-nodes-link-prediction/67069/4 "2024-04-03T07:42:15Z")

</div>

Hi again @alanwtmec,

So the train output looks reasonable, that's good.

It's concerning that you get strange scores also on the other dataset.

> Is there something I haven't noticed?

I cannot see anything more than what I mentioned in my last post. Did you try any of the things I suggested as experiments? I wonder if there's something fishy going on with the MLP implementation.

Thanks,  
Adam
