UltipaDocs
Products
Solutions
Resources
Company
Start Free Trial
UltipaDocs
Start Free Trial
  • Introduction
  • Node Classification
  • Link Prediction (ML)
  1. Docs
  2. /
  3. Machine Learning

Link Prediction (ML)

A link-prediction pipeline learns to predict whether an edge should exist between two nodes, for recommendations, knowledge-graph completion, or forecasting future connections.

NOTE

This is the trained-model approach. For the classic topology-based scores (Adamic–Adar, Common Neighbors, etc.) that need no training, see the Link Prediction algorithms.

NOTE

For the full parameters of every procedure on this page, see Procedures.

How It Differs from Node Classification

Link prediction classifies a pair of nodes (does an edge belong between them?), but the features you add are per-node — each endpoint has its own feature vector. A combiner is the rule that turns the two endpoint vectors u and v into the single vector that describes the pair. Combiners are set on the pipeline (create_pipeline).

If each node vector has d numbers, each combiner produces:

CombinerPair vector from endpoint vectors u, vWidth
hadamard (default)element-wise product u * vd
l1element-wise |u - v| (absolute difference)d
l2element-wise (u - v)^2 (squared difference)d
averageelement-wise (u + v) / 2d
concatu followed by v2d

combiners is a list, so you can pick more than one. When you do, each combiner's output vector is computed separately and the results are concatenated end-to-end into one longer pair vector — letting the model see the endpoints' relationship from several angles at once. With d = 64:

Text
combiners: ['hadamard']          -> pair vector = 64 numbers
combiners: ['hadamard', 'l1']    -> pair vector = 64 + 64 = 128 numbers
combiners: ['concat', 'l2']      -> pair vector = 128 + 64 = 192 numbers

The default is ['hadamard'] (a single combiner). Unknown names fall back to hadamard.

Training examples come from existing edges (positive examples); non-connected node pairs are sampled as negatives (negativeSamplingRatio per positive, default 1.0).

Quick Start

This uses the same social graph as Node Classification (:User nodes connected by :FOLLOWS edges). Here we train on the existing :FOLLOWS edges to predict the ones that are missing: "who should follow whom".

1. Build the graph

The same graph as Node Classification.

2. Define and train the pipeline

GQL
-- Pipeline: predict FOLLOWS edges. Combiners + edge type are set here.
CALL ml.create_pipeline('reco', {
  task: 'LINK_PREDICTION',
  targetEdgeType: 'FOLLOWS',     -- edge type to predict (optional; empty = any edge)
  combiners: ['hadamard']        -- how endpoint vectors are combined (default ['hadamard'])
})

-- Node features: combined per candidate pair at train time
CALL ml.add_feature('reco', {property: 'posts'})
CALL ml.add_feature('reco', {algo: 'fastrp', params: {dimensions: 64}, output: 'emb'})

-- Train/test split (small graph: hold out a quarter)
CALL ml.configure_split('reco', {testFraction: 0.25, randomSeed: 42})

-- Train and persist the model
CALL ml.train_lp('reco', {model: 'reco_model', metric: 'AUC'})
YIELD model, numFeatures, positives, trainSize, testSize, auc, accuracy

3. Predict

predict_lp scores candidate non-adjacent, 2-hop pairs (users who share a followee but don't yet follow each other) and returns the highest-scoring ones — the follows most likely to be missing.

GQL
CALL ml.predict_lp({model: 'reco_model', mode: 'stream', topK: 20})
YIELD node1, node2, probability

Example output from one run. The model surfaces a single strong candidate — bob–erin — with every other pair scoring near zero:

node1node2probability
boberin0.9918289894490157
graceerin0.007025801623288817
bobfrank0.003275945559245846
gracefrank0.0018845099481372219
alicefrank0.0007833275393326102
davecarol0.0007032104498193619
aliceheidi0.00036563839891730316
carolerin0.0000334034796401832
daveerin0.00003340262468713315
bobgrace0.0000002689436385535279
carolheidi0.00000013956603792936783
daveheidi0.00000013956443039479093

Only non-adjacent, 2-hop pairs appear — users who share a neighbor but don't yet follow each other. probability ranks them, so you'd act on the top few (here, just bob–erin).

NOTE

Don't read too much into these numbers. This demo graph is tiny with only a handful of positive edges, so the held-out metrics aren't statistically meaningful and the probabilities are extreme (one near 1, the rest near 0). FastRP is also stochastic, so the exact pairs and values vary from run to run. On real, larger datasets expect more spread in the probabilities and treat the reported metrics as genuinely informative.

Creating the Pipeline

GQL
CALL ml.create_pipeline('<name>', {
  task: 'LINK_PREDICTION',          -- required for link prediction
  targetEdgeType: '<EdgeType>',     -- edge type that defines positive examples (optional; empty = any edge)
  targetLabel: '<Label>',           -- optional: restrict candidate nodes to this label
  combiners: ['hadamard'],          -- hadamard | l1 | l2 | average | concat (list, concatenated)
  negativeSamplingRatio: 1.0,       -- non-edge samples per positive edge
  orReplace: false
}) YIELD name, status

Adding Features

Features are node properties / algorithm outputs; they are combined per candidate pair at train and predict time. Add them exactly as for node classification:

GQL
CALL ml.add_feature('reco', {algo: 'fastrp', params: {dimensions: 64}, output: 'emb'}) YIELD name, status
CALL ml.add_feature('reco', {property: 'age'}) YIELD name, status

Training

GQL
CALL ml.train_lp('<pipeline>', {
  model: '<model name>',             -- required
  modelType: 'logistic_regression', -- logistic_regression (default) | random_forest
  metric: 'AUC',                    -- AUC (default) | F1_WEIGHTED | ACCURACY
  folds: 0                          -- >= 2 enables k-fold cross-validation (reports mean AUC)
  -- logistic_regression params: learningRate, maxEpochs, penalty
  -- random_forest params:       numTrees, maxDepth, minSamplesLeaf, maxFeatures
}) 
YIELD model, modelType, numFeatures, positives, trainSize, testSize,
      auc, accuracy, f1Weighted, cvScore, primaryMetric, primaryScore

Training collects positive edges, samples negative (non-edge) pairs, combines each pair's endpoint features with the pipeline's combiners, standardizes on the train split, fits the classifier, and reports ranking quality (auc) plus classification metrics on the held-out edges. positives is the number of positive edges used.

Random forest with 5-fold cross-validation:

GQL
CALL ml.train_lp('reco', {model: 'reco_rf', modelType: 'random_forest', numTrees: 100, folds: 5})
YIELD model, auc, cvScore

Predicting

ml.predict_lp scores candidate non-adjacent, 2-hop node pairs (nodes that share a neighbor but aren't yet connected) and returns the highest-scoring ones — the most likely missing links across the graph.

GQL
-- Stream the top candidate links
CALL ml.predict_lp({model: 'reco_model', mode: 'stream', topK: 20})
YIELD node1, node2, probability

-- Return materialized rows
CALL ml.predict_lp({model: 'reco_model', topK: 20}) YIELD node1, node2, probability
ParamDefaultDescription
model(required)Trained link-prediction model name.
topK10Number of top-scoring candidate links to return.
maxCandidates200000Safety cap on the number of candidate pairs scored.

predict_lp returns results only (run/stream); unlike predict_nc it has no write-back mode.

Catalog

GQL
CALL ml.list_pipelines() YIELD name, task, targetLabel, numFeatures, testFraction
CALL ml.list_models() YIELD name, pipeline, modelType, numFeatures, auc, accuracy, trainSize, testSize

CALL ml.drop_pipeline('reco') YIELD name, status
CALL ml.drop_model('reco_model') YIELD name, status