Graph Learning2016intermediate12 min read

node2vec: Scalable Feature Learning for Networks

node2vec: تعلُّم سمات قابلة للتوسُّع على الشبكات

Grover, A. · Leskovec, J. — KDD

The problem

Predicting properties of nodes (e.g. protein functions) or edges (e.g. missing friendships) in a network requires converting each node into a feature vector. Before node2vec, engineers hand-crafted these features — slow, brittle, and task-specific. DeepWalk automated this by running uniform random walks and feeding them to , but uniform walks explore blindly: they cannot distinguish between staying local to capture community structure () and roaming widely to capture structural roles (). A single rigid walk strategy cannot serve both needs.

The contribution

node2vec introduces a biased 2nd-order governed by two parameters — return parameter p and in-out parameter q — that smoothly interpolate between BFS-like local exploration (capturing structural equivalence) and DFS-like deep exploration (capturing homophily). The walks are fed to a model with to produce low-dimensional node embeddings. A key insight is that by learning p and q from a small amount of labeled data, node2vec can adapt to whatever mix of community and role structure each network exhibits. Edge embeddings are derived by composing node vectors with binary operators like the .

The impact

node2vec showed that a single flexible walk strategy can outperform both DeepWalk's uniform walks and LINE's rigid BFS sampling, achieving up to 26.7% improvement on and 12.6% on . It became one of the most cited graph methods, bridging the gap between shallow walk-based approaches and the later explosion of Graph Neural Networks like GraphSAGE. Its ideas — controllable neighborhood sampling and composable edge features — became foundational design principles for modern .

Think of a network as a city. DeepWalk explores it like a tourist who flips a coin at every intersection — equally likely to go anywhere, no preference for familiar streets or unexplored alleys.

node2vec replaces the coin with a smart compass that has two dials. The first dial (p) controls the pull to retrace your steps — turn it up and the explorer pushes outward; turn it down and they hover near home. The second dial (q) controls the pull toward uncharted territory — turn it up and the explorer sticks to the local block; turn it down and they venture far into new neighborhoods.

By adjusting these two dials, the same explorer can map tight-knit cul-de-sacs or discover that two distant hubs play the same role in the city's traffic.

The problem: rigid walks miss structure

Networks encode relationships: friendships in social graphs, interactions in protein networks, citations between papers. Two fundamental questions drive most network tasks: which nodes belong to the same community? (homophily) and which nodes play the same structural role? (structural equivalence). A hub in one community looks like a hub in another — even if they share no neighbors.

DeepWalk (2014) made a breakthrough by treating random walks on a graph as "sentences" and nodes as "words", then feeding them to the Skip-gram model from Word2Vec. But its walks are uniform — at every step, the walker picks a neighbor at random with equal probability. This is like exploring a city with no map and no memory: you cannot steer toward local detail or global structure.

LINE (2015) tried a different fix: it explicitly samples 1-hop and 2-hop neighbors in separate passes, essentially hard-coding a BFS strategy. This is rigid in the opposite direction — it never ventures beyond two hops.

The core limitation is that no single fixed walk strategy works across all networks and tasks. Some networks need local exploration; others need deep exploration; most need a mix of both.

Open in Lab
Click a node and compare BFS (stays local, captures structural roles) vs DFS (goes deep, captures communities). Neither alone tells the full story.
The demo wakes as you arrive…

Two kinds of similarity: homophily vs structural equivalence

Consider a social network. Homophily says: people who are friends tend to be similar — they share interests, belong to the same club, appear in the same cluster. A good embedding should place connected, community-sharing nodes close together. BFS-like local exploration captures this because it samples the tight neighborhood around each node.

Structural equivalence says: two people can play the same role without ever meeting. Two bridge nodes connecting different communities should get similar embeddings even though they are far apart in the graph. DFS-like deep exploration captures this by sampling nodes at varying distances, revealing the macro-shape of each node's position in the network.

Most real networks exhibit both. In a protein-protein interaction network, some proteins assist their neighbors (homophily) while others serve complementary functions (structural equivalence). A rigid walk cannot serve both — but a tunable walk can.

Open in Lab
Toggle between homophily and structural equivalence views. Same network, different coloring — because different walk strategies reveal different kinds of similarity.
The demo wakes as you arrive…

The solution: a biased random walk with two dials

node2vec's key idea is a 2nd-order random walk — at each step, the walker considers not just where it is but where it came from. This memory of one step back enables the two control parameters:

Imagine the walker just moved from node t to node v. Now it must choose the next node x from v's neighbors. The depends on the distance between t (the previous node) and x (the candidate next node):

  • If x = t (go back): weight is 1/p. High p → discourage backtracking, push outward. Low p → encourage staying local.
  • If x is also a neighbor of t (stay at the same distance): weight is 1.
  • If x is not a neighbor of t (move further away): weight is 1/q. High q → discourage moving outward, stay local like BFS. Low q → encourage exploring outward like DFS.

That's the entire mechanism. Two parameters, three cases, and infinite flexibility between BFS and DFS.

αpq(t,x)={1pif dtx=01if dtx=11qif dtx=2\alpha_{pq}(t, x) = \begin{cases} \frac{1}{p} & \text{if } d_{tx} = 0 \\ 1 & \text{if } d_{tx} = 1 \\ \frac{1}{q} & \text{if } d_{tx} = 2 \end{cases}
Search bias α — the walk's compass — d_tx is the shortest-path distance between the previous node t and candidate x. The unnormalized transition probability is π_vx = α_pq(t,x) · w_vx. Only three cases exist because x must be a neighbor of v.

Think of p as a rubber band tying the walker to their previous position: low p makes the band tight (the walker bounces back), high p lets it stretch (the walker moves on). Think of q as a fence around the local neighborhood: high q makes the fence tall (the walker stays inside), low q takes the fence down (the walker roams freely).

Open in Lab
Drag the p and q sliders and watch the walk change character. Low p + low q = deep exploration; high p + high q = tight local sampling.
The demo wakes as you arrive…

The objective: maximize neighborhood likelihood

Once we have sampled walks, node2vec treats them exactly like sentences in Word2Vec. For each node u, we want its embedding f(u) to predict the nodes that appeared in its walk-sampled neighborhood N_S(u). The objective maximizes the log-probability of observing these neighbors, conditioned on the source node's feature vector.

Two assumptions make this tractable. First, conditional independence: the probability of seeing each neighbor is independent of the others given f(u). Second, symmetry: the probability of node n_i being a neighbor of u depends on the f(n_i) · f(u), passed through a . This is exactly the Skip-gram architecture.

max⁡f∑u∈V[−log⁡Zu+∑ni∈NS(u)f(ni)⋅f(u)]\max_{f} \sum_{u \in V} \left[ -\log Z_u + \sum_{n_i \in N_S(u)} f(n_i) \cdot f(u) \right]
Skip-gram objective adapted for graphs — Z_u is the per-node partition function (approximated by negative sampling in practice). f(u) is the d-dimensional embedding of node u. The walks define N_S(u) — the biased neighborhood.

The full pipeline: from graph to embeddings

node2vec works in three sequential phases, each trivially parallelizable:

Phase 1 — Preprocessing. Compute the biased transition probabilities π_vx for every edge, based on p, q, and the graph structure. This is done once upfront using tables so that each step of the walk later takes O(1) time.

Phase 2 — Walk simulation. From every node u, launch r random walks of length l. At each step, sample the next node in O(1) using the precomputed alias tables. This generates r · |V| walk sequences.

Phase 3 — SGD optimization. Feed the walks to the Skip-gram model with negative sampling, using size k and embedding dimension d. Optimize with for one epoch.

The result: a |V| × d matrix where row u is the d-dimensional embedding of node u.

Open in Lab
Step through the three phases of node2vec — preprocessing, walk simulation, optimization.
The demo wakes as you arrive…

Tuning p and q: the exploration-exploitation trade-off

The brilliance of node2vec lies in how p and q create a continuous spectrum between two extremes:

  • p = 1, q = 1 → Uniform random walk — exactly DeepWalk. No bias at all.
  • High p, high q → BFS-like. The walker avoids backtracking (high p) and avoids moving outward (high q), so it oscillates among the immediate neighbors. This captures structural equivalence — nodes with similar local connectivity patterns get similar embeddings.
  • Low p, low q → DFS-like. The walker is willing to backtrack (low p) and eager to explore outward (low q), producing long-range walks that capture homophily — nodes in the same community get similar embeddings.

In practice, p and q are learned via grid search on a small fraction (as little as 10%) of labeled data, making node2vec semi-supervised.

Open in Lab
Drag p and q on the 2D grid and see how the embedding clusters change. Top-left = homophily-focused; bottom-right = structural-equivalence-focused.
The demo wakes as you arrive…

node2vec learns embeddings for individual nodes, but many tasks — especially link prediction — involve pairs of nodes. How do we get an embedding for an edge (u, v)?

The answer is simple: combine f(u) and f(v) using a binary operator. node2vec evaluates four options: element-wise average, Hadamard (element-wise) product, weighted L1 distance, and weighted L2 distance. Among these, the Hadamard product — multiplying the two vectors element by element — consistently gives the best and most stable results.

This compositionality is a major practical advantage: you learn node embeddings once, then construct edge features on the fly for any pair of nodes, even pairs with no existing edge.

g(u,v)=f(u)⊙f(v)where [f(u)⊙f(v)]i=fi(u)⋅fi(v)g(u, v) = f(u) \odot f(v) \quad \text{where } [f(u) \odot f(v)]_i = f_i(u) \cdot f_i(v)
Hadamard product — the best edge operator — Each dimension of the edge embedding is the product of the corresponding node dimensions. This captures interaction patterns: dimensions where both nodes are active contribute most.

The idea in code

node2vec biased random walk — the core mechanismpython

Simplified to show the idea — not the real implementation.

import numpy as np
from collections import defaultdict

def compute_transition_probs(G, prev, curr, p, q):
    """Compute biased transition probabilities from curr, given prev."""
    neighbors = list(G[curr])
    probs = []
    for x in neighbors:
        if x == prev:          # backtrack: distance 0
            probs.append(1.0 / p)
        elif x in G[prev]:     # neighbor of prev too: distance 1
            probs.append(1.0)
        else:                  # moving further away: distance 2
            probs.append(1.0 / q)
    probs = np.array(probs)
    return probs / probs.sum()  # normalize

def node2vec_walk(G, start, length, p, q):
    """Simulate one biased random walk of given length."""
    walk = [start]
    if length == 1:
        return walk
    # First step: uniform among neighbors
    first = np.random.choice(list(G[start]))
    walk.append(first)
    for _ in range(length - 2):
        curr = walk[-1]
        prev = walk[-2]
        probs = compute_transition_probs(G, prev, curr, p, q)
        nxt = np.random.choice(list(G[curr]), p=probs)
        walk.append(nxt)
    return walk

# After generating walks, feed them to Word2Vec's Skip-gram
# with negative sampling — exactly like DeepWalk, but with
# smarter walks. The p,q parameters are the only difference.

Scalability: millions of nodes in hours

node2vec scales linearly with the number of nodes. On Erdos-Renyi graphs with an average degree of 10, it embeds one million nodes in under four hours. Three design choices enable this:

  • Alias sampling for O(1) next-node selection during walks.
  • Walk reuse: a walk of length l starting from u also generates neighborhoods for every intermediate node in the walk, amortizing the cost.
  • Asynchronous SGD with negative sampling for the optimization phase, avoiding the expensive softmax .

All three phases — preprocessing, walk simulation, optimization — are parallelizable, and in practice each runs on its own thread pool.

Results: flexibility wins

node2vec was evaluated on multi-label classification (BlogCatalog, PPI, Wikipedia) and link prediction (Facebook, PPI, arXiv). The key findings:

  • On BlogCatalog, node2vec with p=0.25, q=0.25 achieved a 22.3% Macro-F1 improvement over DeepWalk and over 229% over LINE.
  • On Wikipedia, node2vec gained 21.8% over DeepWalk by learning that the word co-occurrence network benefits from a mix of deep and local exploration (p=4, q=0.5).
  • On PPI, the best setting (p=4, q=1) was nearly identical to DeepWalk's uniform walk, showing that when uniform walks happen to be optimal, node2vec gracefully reduces to them.
  • For link prediction, node2vec with Hadamard operators achieved up to 12.6% improvement over the best heuristic baseline (Adamic-Adar) on the arXiv collaboration network.

The algorithm also proved robust to missing and noisy edges, with Macro-F1 declining only linearly as edges were removed.

The bigger picture: from handcrafted features to GNNs

  1. 2013

    DeepWalk

    Perozzi et al. applied Word2Vec's Skip-gram to uniform random walks on graphs. First scalable graph embedding method, but walks have no steering mechanism.

  2. 2015

    LINE

    Tang et al. learned embeddings by sampling 1-hop and 2-hop neighborhoods separately. Fast but rigidly BFS — cannot explore beyond two hops.

  3. 2016

    node2vec

    Grover & Leskovec introduced biased walks with p,q parameters, smoothly interpolating between BFS and DFS. Outperformed DeepWalk and LINE across tasks and domains.

  4. 2017

    GraphSAGE

    Hamilton, Ying & Leskovec moved beyond walk-based embeddings to learnable neighborhood aggregation — the first inductive GNN that can embed unseen nodes.

  5. 2017

    GCN (Kipf & Welling)

    Graph Convolutional Networks applied spectral convolutions on graphs with a simple layer-wise propagation rule, opening the era of end-to-end GNNs.

  6. 2020

    GNNs go mainstream

    GNNs powered drug discovery, recommendation systems, and fraud detection at scale. Many still use node2vec-style walks for pretraining or as baselines.

node2vec sits at a pivotal point in graph representation learning: it showed that the sampling strategy matters as much as the embedding model itself. This insight — that how you see a node's context determines what you learn about it — carried forward directly into GraphSAGE's neighborhood aggregation and the broader revolution.

CitationGrover, Leskovec. node2vec: Scalable Feature Learning for Networks. KDD, 2016.

Terms in this paper