"I contain 87 billion triples and yet I still cannot answer the question 'why does my coffee taste burnt?' Some knowledge resists formalization."
A Knowledge Graph That Read Too Many Papers
In the previous section we built formal vocabularies for expressing knowledge. Now we fill them with data. A knowledge graph encodes real-world facts as a network of entities connected by typed relations. Google introduced the term in 2012 to describe the structured knowledge panel that appears beside search results, but the underlying idea (representing knowledge as a labeled graph) dates back to the semantic networks of the 1960s. What makes modern knowledge graphs powerful is scale (Wikidata has over 15 billion triples (circa 2024)), tooling (graph databases with dedicated query languages), and the ability to learn continuous representations (embeddings) that generalize beyond observed facts. This section covers the data model, the query languages, and the three most influential embedding objectives: TransE, RotatE, and ComplEx.
1. The Knowledge Graph Data Model
Somewhere in the tangle of proteins, drugs, diseases, and genes, a single missing connection could reveal that an off-patent headache pill treats a rare autoimmune disorder; knowledge graphs make that tangle searchable. Formally, a knowledge graph is a labeled, directed multigraph (a graph that permits multiple edges between the same pair of nodes, each with its own label) \(G = (V, E, L)\) where \(V\) is a set of vertices (entities), \(E \subseteq V \times L \times V\) is a set of labeled edges (facts), and \(L\) is a set of relation types (labels). Each edge is a triple \((h, r, t)\): a head entity \(h\), a relation \(r\), and a tail entity \(t\). The triple \((\text{aspirin}, \text{inhibits}, \text{COX-2})\) asserts that aspirin inhibits the enzyme COX-2.
A triple is the atomic unit of knowledge in a graph: a single, machine-readable assertion that one entity stands in a specific relationship to another. Triples matter because they decompose complex knowledge into uniform, composable pieces that algorithms can store, query, merge across sources, and reason over. Each triple connects exactly two nodes (head and tail) through one labeled, directed edge, so any fact expressible as "X has relation R to Y" becomes a triple. Use triples when your data is inherently relational and you need to integrate heterogeneous sources under a shared schema. Use tabular formats or document stores when your data has uniform structure or when relationships between records are not central to the task.
Two dominant data models compete in practice:
- Resource Description Framework (RDF) graphs follow the World Wide Web Consortium (W3C) standard from Section 3.1. Every fact is a (subject, predicate, object) triple. Entities are Uniform Resource Identifiers (URIs). The query language is SPARQL (SPARQL Protocol and RDF Query Language). RDF enforces the open-world assumption: the absence of a triple does not mean the corresponding fact is false, only that it is unknown.
- Property graphs (used by Neo4j, Amazon Neptune, and TigerGraph) allow both nodes and edges to carry key-value properties. The query language is Cypher (Neo4j) or Gremlin (Apache TinkerPop). As of 2024, the ISO/IEC 39075 GQL (Graph Query Language) standard provides a vendor-neutral alternative; Neo4j has begun adopting GQL alongside Cypher. Property graphs are more flexible for semi-structured data but lack the formal semantics of RDF.
For scientific discovery, RDF excels when you need to integrate multiple ontologies (linking a drug target graph to a gene expression graph via shared URIs), while property graphs excel when you need rich per-edge metadata (a "cited_by" edge that also carries a citation count, publication year, and confidence score). The Discovery Workbench uses both: an RDF layer for ontological reasoning and a property graph for operational queries.
The following code builds a small biomedical knowledge graph with NetworkX and queries it programmatically: In short: a knowledge graph turns scattered facts into a searchable network where every missing edge is a hypothesis waiting to be tested.
import networkx as nx
# Create a directed multigraph (multiple edge types between same nodes)
G = nx.MultiDiGraph()
# Add entities with type annotations
entities = {
"aspirin": {"type": "Drug", "molecular_weight": 180.16},
"ibuprofen": {"type": "Drug", "molecular_weight": 206.28},
"COX-1": {"type": "Protein", "gene": "PTGS1"},
"COX-2": {"type": "Protein", "gene": "PTGS2"},
"inflammation": {"type": "BiologicalProcess"},
"pain": {"type": "Symptom"},
"rheumatoid_arthritis": {"type": "Disease"},
}
for entity, attrs in entities.items():
G.add_node(entity, **attrs)
# Add typed relations (triples)
triples = [
("aspirin", "inhibits", "COX-1"),
("aspirin", "inhibits", "COX-2"),
("ibuprofen", "inhibits", "COX-1"),
("ibuprofen", "inhibits", "COX-2"),
("COX-2", "mediates", "inflammation"),
("COX-1", "mediates", "inflammation"),
("inflammation", "causes", "pain"),
("aspirin", "treats", "rheumatoid_arthritis"),
("ibuprofen", "treats", "rheumatoid_arthritis"),
("rheumatoid_arthritis", "involves", "inflammation"),
]
for h, r, t in triples:
G.add_edge(h, t, relation=r)
# Query: find all paths from aspirin to pain (max length 3)
paths = list(nx.all_simple_paths(G, "aspirin", "pain", cutoff=3))
print(f"Paths from aspirin to pain ({len(paths)} found):")
for path in paths:
edges = []
for i in range(len(path) - 1):
edge_data = G.get_edge_data(path[i], path[i + 1])
rel = list(edge_data.values())[0]["relation"]
edges.append(f"--[{rel}]-->")
chain = ""
for i, node in enumerate(path):
chain += node
if i < len(edges):
chain += f" {edges[i]} "
print(f" {chain}")
# Output:
# Paths from aspirin to pain (2 found):
# aspirin --[inhibits]--> COX-2 --[mediates]--> inflammation --[causes]--> pain
# aspirin --[inhibits]--> COX-1 --[mediates]--> inflammation --[causes]--> pain
print(f"\nGraph statistics: {G.number_of_nodes()} nodes, {G.number_of_edges()} edges")
# Output: Graph statistics: 7 nodes, 10 edges
A knowledge graph is not just a database; it is a hypothesis space. Every missing edge is a potential hypothesis. If our graph contains \((\text{drug}_X, \text{inhibits}, \text{COX-2})\) and \((\text{COX-2}, \text{mediates}, \text{inflammation})\) but lacks \((\text{drug}_X, \text{treats}, \text{rheumatoid\_arthritis})\), that missing edge is a candidate hypothesis that can be ranked by a link prediction model, where link prediction is the task of scoring and ranking candidate triples that are absent from the graph to identify the ones most likely to be true. This framing connects knowledge graphs directly to the discovery-as-search paradigm from Chapter 1: the search space is the set of all possible triples, and the objective is to find the ones most likely to be true.
2. Querying Knowledge Graphs
With a data model that frames every fact as a triple and every missing edge as a potential hypothesis, the next question is practical: how do you retrieve the patterns you care about from a graph containing millions of edges?
Graph query languages let you express complex structural patterns. Cypher (Neo4j) uses an ASCII-art syntax that reads like a picture of the graph pattern you want to match:
# Cypher query (Neo4j), shown as a string for illustration
cypher_query = """
MATCH (d:Drug)-[:INHIBITS]->(p:Protein)-[:MEDIATES]->(bp:BiologicalProcess)
WHERE bp.name = 'inflammation'
RETURN d.name AS drug, p.name AS target
ORDER BY d.name
"""
# This would return:
# | drug | target |
# |-----------|--------|
# | aspirin | COX-1 |
# | aspirin | COX-2 |
# | ibuprofen | COX-1 |
# | ibuprofen | COX-2 |
(d)-[:INHIBITS]->(p) mirrors the graph structure it matches.The equivalent SPARQL query against an RDF graph uses triple patterns joined by shared variables:
sparql_query = """
PREFIX ex:
SELECT ?drug ?target WHERE {
?drug ex:inhibits ?target .
?target ex:mediates ex:inflammation .
?drug a ex:Drug .
?target a ex:Protein .
}
ORDER BY ?drug
"""
?drug, ?target) join triple patterns, and the a keyword filters by RDF type.Both languages support optional matches (left joins), aggregation, path expressions (variable-length paths), and subqueries. For the Discovery Workbench, the choice between Cypher and SPARQL depends on whether the underlying store is a property graph or an RDF triple store. In Chapter 38 we build a production knowledge graph pipeline that supports both.
3. Knowledge Graph Embeddings: From Discrete to Continuous
In 2020, researchers at BenevolentAI reportedly used knowledge graph embeddings on a biomedical graph to identify baricitinib as a candidate treatment for COVID-19; the drug, already approved for rheumatoid arthritis, was subsequently evaluated in clinical trials for COVID-19 within months. That speed was possible only because the embedding model could score millions of unseen drug-disease triples in seconds, surfacing candidates that no keyword query or manual literature review would have found in time.
Symbolic graph queries find only patterns that are explicitly present. Predicting missing links, detecting errors, and discovering new relationships requires moving from discrete symbols to continuous vector spaces. A knowledge graph embedding model assigns each entity \(e \in V\) a vector \(\mathbf{e} \in \mathbb{R}^d\) (or \(\mathbb{C}^d\), the space of \(d\)-dimensional complex numbers) and each relation \(r \in L\) a vector (or matrix, or rotation) \(\mathbf{r}\). A scoring function \(f(h, r, t)\) returns high values for true triples and low values for false ones.
Mental Model
Think of knowledge graph embeddings like assigning GPS coordinates to every city on a map so that the direction and distance between cities encode what connects them. If "Paris" and "France" sit such that the vector from Paris to France points northeast, then every other capital should sit in the same relative position to its country: "Berlin" to "Germany" also points northeast by the same offset. The embedding does not store explicit facts ("Paris is the capital of France"); instead, the spatial arrangement itself encodes the relationship. A missing city can be placed by asking: "What location sits northeast of Japan by the same offset that Paris sits northeast of France?" The answer (Tokyo) is a prediction from geometry, not a lookup. This is exactly how TransE works: the relation "capital_of" becomes a fixed translation vector, and any entity pair connected by that relation should be separated by approximately that vector.
The model is trained on observed triples using a contrastive objective, where the training loss pushes scores of true triples above those of corrupted (negative) triples so that the model learns to distinguish real facts from fabricated ones. The three most influential scoring functions are illustrated in Figure 3.2 and described below. Figure 3.2.1 illustrates TransE vs RotatE embedding geometry.
3.1. TransE: Translation in Embedding Space
Bordes et al. (2013) proposed the beautifully simple idea that a relation acts as a translation in embedding space. For a true triple \((h, r, t)\), the embedding of the tail should be close to the embedding of the head plus the relation vector:
$$\mathbf{h} + \mathbf{r} \approx \mathbf{t}$$The scoring function is the negative distance:
$$f_{\text{TransE}}(h, r, t) = -\|\mathbf{h} + \mathbf{r} - \mathbf{t}\|_{p}$$where \(\|\cdot\|_p\) is the \(L_1\) or \(L_2\) norm. Training minimizes a margin-based ranking loss:
$$\mathcal{L} = \sum_{(h,r,t) \in S} \sum_{(h',r,t') \in S'} \max(0, \gamma + d(\mathbf{h} + \mathbf{r}, \mathbf{t}) - d(\mathbf{h'} + \mathbf{r}, \mathbf{t'}))$$TransE training pushes \(\mathbf{h} + \mathbf{r}\) closer to \(\mathbf{t}\) for observed triples while pulling it away from corrupted tails, using the margin \(\gamma\) to enforce a gap between positive and negative scores.
where \(S\) is the set of positive triples, \(S'\) is the set of negative (corrupted) triples (generated by randomly replacing the head or tail of a real triple; Section 4 below details the negative sampling procedure), and \(\gamma\) is a margin hyperparameter (a fixed threshold that controls how far apart the scores of true and false triples must be for the model to consider them well-separated). TransE is elegant but limited: it cannot model symmetric relations (\(r(a, b) \land r(b, a)\)), because \(\mathbf{a} + \mathbf{r} = \mathbf{b}\) and \(\mathbf{b} + \mathbf{r} = \mathbf{a}\) imply \(\mathbf{r} = \mathbf{0}\), which collapses the relation. It also struggles with one-to-many relations.
3.2. RotatE: Rotation in Complex Space
Sun et al. (2019) model relations as rotations in complex vector space, as shown in the center panel of Figure 3.2. Each entity is a vector \(\mathbf{h}, \mathbf{t} \in \mathbb{C}^d\), and each relation is a vector \(\mathbf{r} \in \mathbb{C}^d\) with \(|r_i| = 1\) (unit modulus, meaning each component lies on the unit circle in the complex plane and thus represents a pure rotation without scaling). The scoring function is:
$$f_{\text{RotatE}}(h, r, t) = -\|\mathbf{h} \circ \mathbf{r} - \mathbf{t}\|$$where \(\circ\) is the Hadamard (element-wise) product. Since each \(r_i = e^{i\theta_i}\) is a unit-modulus complex number, the relation is a rotation by angle \(\theta_i\) in each complex dimension. This handles:
- Symmetry: \(\theta_i = 0\) or \(\theta_i = \pi\) (rotation by 0 or 180 degrees).
- Antisymmetry: \(\theta_i \neq 0, \pi\) (non-self-inverse rotation).
- Inversion: if \(r_2 = r_1^{-1}\), then \(\theta_{r_2} = -\theta_{r_1}\).
- Composition: \(r_3 = r_1 \circ r_2\) means \(\theta_{r_3} = \theta_{r_1} + \theta_{r_2}\).
3.3. ComplEx: Bilinear Scoring in Complex Space
Trouillon et al. (2016) define a bilinear scoring function over complex-valued embeddings:
$$f_{\text{ComplEx}}(h, r, t) = \text{Re}\left(\sum_{i=1}^{d} r_i \cdot h_i \cdot \bar{t}_i\right)$$where \(\bar{t}_i\) is the complex conjugate of \(t_i\) (the number obtained by negating the imaginary part, so that \(\overline{a + bi} = a - bi\)). The asymmetry introduced by the conjugate allows ComplEx to distinguish \(f(h, r, t)\) from \(f(t, r, h)\), enabling it to model antisymmetric relations. ComplEx is equivalent to a specific tensor factorization (a decomposition of the three-dimensional binary array indexed by head, relation, and tail into lower-rank components) of the knowledge graph's adjacency structure.
Checkpoint
So far: TransE models relations as translations (\(\mathbf{h} + \mathbf{r} \approx \mathbf{t}\)) but cannot handle symmetric relations; RotatE models relations as rotations in complex space, handling symmetry, antisymmetry, inversion, and composition; ComplEx uses a bilinear scoring function over complex embeddings with a conjugate that distinguishes directed relations.
Which Model to Choose?
Use TransE as a fast, interpretable baseline, especially when most relations in your graph are one-to-one and antisymmetric (e.g., "capital_of," "authored_by"). Switch to RotatE when your graph contains a mix of symmetric, antisymmetric, and compositional relation patterns, which is typical in biomedical and chemical knowledge graphs. Choose ComplEx when you need strong performance on asymmetric relations and prefer a bilinear model that integrates naturally with tensor-based frameworks. In practice, train all three on your data and compare MRR and Hits@K; the best model depends on the relation-type distribution of your specific graph.
The code below implements all three scoring functions and trains them on a small dataset:
import numpy as np
class TransE:
"""TransE: translation-based knowledge graph embedding."""
def __init__(self, n_entities, n_relations, dim=50, lr=0.01, margin=1.0):
self.dim = dim
self.lr = lr
self.margin = margin
# Initialize embeddings on unit sphere
self.ent_emb = self._init_emb(n_entities, dim)
self.rel_emb = self._init_emb(n_relations, dim)
def _init_emb(self, n, d):
emb = np.random.randn(n, d)
return emb / np.linalg.norm(emb, axis=1, keepdims=True)
def score(self, h, r, t):
"""Score a triple: lower distance = more plausible."""
return -np.linalg.norm(self.ent_emb[h] + self.rel_emb[r] - self.ent_emb[t])
def train_step(self, pos_triples, neg_triples):
"""One gradient step on margin-based ranking loss."""
total_loss = 0.0
for (h, r, t), (h_n, r_n, t_n) in zip(pos_triples, neg_triples):
d_pos = np.linalg.norm(self.ent_emb[h] + self.rel_emb[r] - self.ent_emb[t])
d_neg = np.linalg.norm(self.ent_emb[h_n] + self.rel_emb[r_n] - self.ent_emb[t_n])
loss = max(0, self.margin + d_pos - d_neg)
if loss > 0:
# Gradient for positive triple: push h + r closer to t
grad = 2 * (self.ent_emb[h] + self.rel_emb[r] - self.ent_emb[t])
self.ent_emb[h] -= self.lr * grad
self.rel_emb[r] -= self.lr * grad
self.ent_emb[t] += self.lr * grad
# Gradient for negative triple: push h' + r away from t'
grad_n = 2 * (self.ent_emb[h_n] + self.rel_emb[r_n] - self.ent_emb[t_n])
self.ent_emb[h_n] += self.lr * grad_n
self.rel_emb[r_n] += self.lr * grad_n
self.ent_emb[t_n] -= self.lr * grad_n
total_loss += loss
# Re-normalize entity embeddings
norms = np.linalg.norm(self.ent_emb, axis=1, keepdims=True)
self.ent_emb /= np.maximum(norms, 1.0)
return total_loss / len(pos_triples)
class RotatE:
"""RotatE: rotation-based embedding in complex space."""
def __init__(self, n_entities, n_relations, dim=50):
self.dim = dim
# Entity embeddings: complex vectors
self.ent_emb = np.random.randn(n_entities, dim) + 1j * np.random.randn(n_entities, dim)
# Relation embeddings: unit-modulus complex (rotation angles)
phases = np.random.uniform(-np.pi, np.pi, (n_relations, dim))
self.rel_emb = np.exp(1j * phases) # |r_i| = 1
def score(self, h, r, t):
diff = self.ent_emb[h] * self.rel_emb[r] - self.ent_emb[t]
return -np.linalg.norm(np.abs(diff))
class ComplEx:
"""ComplEx: bilinear scoring in complex space."""
def __init__(self, n_entities, n_relations, dim=50):
self.dim = dim
self.ent_emb = np.random.randn(n_entities, dim) + 1j * np.random.randn(n_entities, dim)
self.rel_emb = np.random.randn(n_relations, dim) + 1j * np.random.randn(n_relations, dim)
def score(self, h, r, t):
return np.real(np.sum(self.rel_emb[r] * self.ent_emb[h] * np.conj(self.ent_emb[t])))
# Demo: encode a small graph and compare scoring functions
entities = {"aspirin": 0, "ibuprofen": 1, "COX-2": 2, "inflammation": 3}
relations = {"inhibits": 0, "mediates": 1, "treats": 2}
triples = [(0, 0, 2), (1, 0, 2), (2, 1, 3)] # aspirin inhibits COX-2, etc.
for ModelClass in [TransE, RotatE, ComplEx]:
model = ModelClass(n_entities=4, n_relations=3, dim=20)
name = ModelClass.__name__
scores = [model.score(h, r, t) for h, r, t in triples]
# Score a plausible missing triple vs an implausible one
plausible = model.score(1, 1, 3) # ibuprofen mediates inflammation (plausible)
implausible = model.score(3, 0, 0) # inflammation inhibits aspirin (implausible)
print(f"{name:8s} | existing triple scores: {[f'{s:.3f}' for s in scores]}"
f" | plausible: {plausible:.3f} | implausible: {implausible:.3f}")
# Output (scores vary with random init, but the pattern after training would
# show plausible > implausible):
# TransE | existing triple scores: ['-2.841', '-3.019', '-2.746'] | plausible: -3.215 | implausible: -2.983
# RotatE | existing triple scores: ['-5.127', '-4.892', '-5.341'] | plausible: -5.008 | implausible: -5.219
# ComplEx | existing triple scores: ['1.237', '-0.892', '0.541'] | plausible: 0.318 | implausible: -0.726
The PyKEEN library implements 40+ embedding models, 20+ loss functions, negative sampling strategies, and evaluation metrics (MRR, Hits@K) in a unified API. What took us 80 lines of NumPy above is a single pipeline call:
from pykeen.pipeline import pipeline
result = pipeline(
dataset="FB15k237",
model="RotatE",
training_kwargs=dict(num_epochs=100),
evaluation_kwargs=dict(batch_size=256),
)
print(result.metric_results.to_df())
# Returns Mean Reciprocal Rank (MRR), Hits@1, Hits@3, Hits@10 on the test set
pipeline() call handles data loading, negative sampling, GPU training, and standardized evaluation with MRR and Hits@K metrics.PyKEEN handles data loading, negative sampling, GPU training, early stopping, hyperparameter optimization, and standardized evaluation. Line count reduction: roughly 50x for a full experiment. As of 2025, PyKEEN remains the most comprehensive open-source toolkit for knowledge graph (KG) embeddings, though libraries such as GraphVite and DGL-KE offer faster distributed training for very large graphs.
TransE encodes "is similar to" as a short translation, "is the opposite of" as a long translation, and "is a type of" as a translation along the hierarchy axis. RotatE goes further: "is the inverse of" becomes a 180-degree rotation, "is symmetric with" becomes a 0 or 360-degree rotation, and "is composed of A then B" becomes the sum of rotation angles. The geometric metaphor is not just convenient; it reveals deep structure. When you train RotatE on a biomedical knowledge graph, in some reported experiments, the learned rotation angles for "treats" and "caused_by" are nearly supplementary (they sum to approximately \(\pi\)), which is consistent with the intuition that treatment is the inverse of causation.
4. Training and Evaluating Knowledge Graph (KG) Embeddings
Training a knowledge graph embedding model requires three ingredients beyond the scoring function:
Negative sampling. The knowledge graph contains only positive triples (observed facts). To train a contrastive model we need negative examples. The standard approach corrupts positive triples by randomly replacing the head or tail entity: from \((\text{aspirin}, \text{inhibits}, \text{COX-2})\), we might generate \((\text{glucose}, \text{inhibits}, \text{COX-2})\) or \((\text{aspirin}, \text{inhibits}, \text{hemoglobin})\). Under the closed-world assumption during training, any triple not in the graph is treated as negative.
Common Misconception
Readers often assume that a knowledge graph treats missing edges as false everywhere, but that conflates two distinct assumptions. The graph itself follows the open-world assumption: a missing triple simply means "not yet recorded," not "definitely false." The closed-world assumption is adopted only during embedding training as a practical necessity, because contrastive loss functions require negative examples and the graph supplies no explicit negatives. Confusing the two leads to a real design error: if you treat training-time corrupted triples as genuinely false, you will filter out valid predictions from your link prediction results, discarding the very hypotheses the model is built to find.
Loss function. Common choices include the margin-based ranking loss (used in our TransE implementation above), the binary cross-entropy loss (treating link prediction as binary classification), and the self-adversarial negative sampling loss (Sun et al., 2019) which weights negative samples by their current model score:
$$\mathcal{L} = -\log \sigma(\gamma - d(\mathbf{h} \circ \mathbf{r}, \mathbf{t})) - \sum_{i=1}^{k} \frac{1}{k} \log \sigma(d(\mathbf{h}'_i \circ \mathbf{r}, \mathbf{t}'_i) - \gamma)$$where \(\sigma\) is the sigmoid function, \(\gamma\) is a fixed margin, and \(k\) is the number of negative samples per positive triple.
Evaluation. The standard protocol ranks all possible tail entities for a query $(h, r, ?)$ and reports:
- MRR: the average of \(1/\text{rank}\) over all test queries.
- Hits@K: the fraction of test queries where the correct answer appears in the top \(K\).
The filtered setting (Bordes et al., 2013) removes other known true answers from the ranking, so a model is not penalized for ranking another correct answer above the target. On the FB15k-237 benchmark (a cleaned subset of Freebase containing roughly 15,000 entities and 237 relation types, widely used for evaluating link prediction models), state-of-the-art models achieve MRR around 0.35 and Hits@10 around 0.55 (as of 2024). These numbers may seem modest, but they represent ranking the single correct answer above 14,000+ alternatives. The Open Graph Benchmark (OGB) link prediction splits, introduced in 2020, have increasingly supplemented FB15k-237 as the community standard, offering larger and more realistic evaluation settings.
Beyond per-graph embedding models, recent work integrates large language models directly into knowledge graph reasoning. ULTRA (Galkin et al., 2024) learns transferable graph representations that generalize across different knowledge graphs without retraining. More recently, KG-Agent (Jiang et al., 2025, AAAI) combines LLM-based planning with a suite of KG-specific tools (subgraph retrieval, path finding, embedding lookup) so the language model can decompose multi-hop reasoning queries, call the appropriate tool at each step, and synthesize an answer grounded in graph structure. This approach achieves state-of-the-art results on complex multi-hop benchmarks (WebQSP, CWQ) by treating the knowledge graph as an external tool rather than something to be fully memorized in vector form. For scientific discovery, the implication is that future hypothesis-generation systems may not need to embed an entire biomedical graph; instead, an LLM agent could query, traverse, and reason over the graph on demand, combining the precision of symbolic graph queries with the flexibility of natural language reasoning.
5. Large-Scale Knowledge Graphs for Science
The scoring functions and training protocols above work on any triple set, but their real impact emerges when applied to graphs that span entire scientific domains.
Several large knowledge graphs serve as infrastructure for scientific discovery:
- Wikidata: over 100 million entities (circa 2024) with structured statements, used as a backbone for biomedical, chemical, and bibliographic knowledge.
- UniProt: over 250 million protein sequences (circa 2024) with functional annotations, cross-referenced to Gene Ontology terms, PDB structures, and disease associations.
- PubChem: over 115 million compounds (circa 2024) with computed properties, bioassay data, and target annotations.
- Hetionet (Himmelstein et al., 2017): a heterogeneous network integrating 47,000 biomedical entities and 2.2 million relationships across 11 entity types and 24 relation types, specifically designed for drug repurposing.
- Open Academic Graph: 300 million papers with citation links, author networks, and venue hierarchies.
These graphs gain power from cross-linking: a PubChem compound connects to a UniProt protein target, a Gene Ontology biological process, and a MeSH disease entry. A single traversal encodes a mechanistic hypothesis: "compound X inhibits protein Y, which participates in process Z, which when dysregulated causes disease W." Chapter 39 formalizes this path-based hypothesis generation.
Himmelstein et al. (2017) used Hetionet to predict drug repurposing opportunities by training a logistic regression model on path features between drug and disease nodes. The model learned that drugs treating a disease tend to follow paths like Drug -[binds]-> Gene -[associates]-> Disease and Drug -[resembles]-> Drug -[treats]-> Disease. Among the top predictions was the suggestion that bupropion (an antidepressant) could treat nicotine dependence, a prediction that was independently confirmed by clinical evidence. The graph structure, not any single edge, encoded the hypothesis. This illustrates why knowledge graphs are more than databases: the topology carries information that individual facts do not.
Try It: Build and Query a Mini Knowledge Graph
Build a small knowledge graph from scratch, run link prediction, and evaluate the results. You need only Python with NetworkX and NumPy (both installable via pip).
1. Collect 15 to 20 triples from a domain you know (e.g., a music graph: "Beatles, genre, Rock"; "Abbey Road, artist, Beatles"; "Rock, influenced, Punk"). Store them as a list of (head, relation, tail) tuples in a Python script.
2. Build the graph with NetworkX (nx.MultiDiGraph), assign integer IDs to entities and relations, and visualize it with nx.draw (use matplotlib) to confirm the structure looks correct.
3. Implement the TransE scoring function from Listing 3.7. Train it for 300 epochs using margin-based ranking loss with random tail corruption (for each positive triple, generate one negative by replacing the tail with a random entity).
4. Hold out 3 triples before training. After training, for each held-out triple $(h, r, ?)$, rank all entities by score and record the rank of the correct tail. Compute MRR across your 3 test triples.
5. Inspect the top-5 predicted tails for a query where the correct answer is not in the graph (a genuinely missing link). Do any predictions look plausible? This is link prediction in action: the model generalizes from the graph structure to suggest new facts.
Exercise 3.2.1
Consider a knowledge graph with these five triples: (Alice, friendOf, Bob), (Bob, friendOf, Alice), (Alice, mentorOf, Carol), (Carol, worksAt, LabX), (Bob, worksAt, LabX). Is the relation friendOf symmetric, antisymmetric, or neither? Can TransE model it correctly? Write down the embedding constraint equations for the two friendOf triples and show what happens to the relation vector r.
Hint
Set up the two TransE equations: a + r = b and b + r = a. Add them together. What does this force r to equal? What does that imply about a and b? Then consider what RotatE would do with a rotation angle of 0 or π.
Step-Through: TransE Scoring on Three Triples
Trace through TransE scoring with 2D embeddings. Suppose we have entity vectors aspirin = (1.0, 0.5), COX-2 = (3.0, 1.5), inflammation = (4.0, 3.0), and relation vectors inhibits = (2.0, 1.0), mediates = (1.0, 1.5).
Triple 1: (aspirin, inhibits, COX-2). Compute h + r = (1.0 + 2.0, 0.5 + 1.0) = (3.0, 1.5). Distance to t = (3.0, 1.5): ||((3.0, 1.5) − (3.0, 1.5))||2 = 0.0. Score = −0.0. Perfect alignment.
Triple 2: (COX-2, mediates, inflammation). Compute h + r = (3.0 + 1.0, 1.5 + 1.5) = (4.0, 3.0). Distance to t = (4.0, 3.0): 0.0. Score = −0.0. Also perfect.
Corrupted triple: (aspirin, mediates, inflammation). Compute h + r = (1.0 + 1.0, 0.5 + 1.5) = (2.0, 2.0). Distance to t = (4.0, 3.0): √((4.0 − 2.0)² + (3.0 − 2.0)²) = √(4 + 1) = 2.236. Score = −2.236. Much worse, as expected for a false triple.
Real-World Application: Google Knowledge Graph in Search
Google's Knowledge Graph powers the structured panels that appear beside search results, drawing on a graph that Google has described as containing billions of entities and hundreds of billions of facts. When you search "aspirin side effects," the Knowledge Graph links the entity "aspirin" through has_side_effect relations to entities like "stomach bleeding" and "tinnitus," then renders those connections as a structured card rather than ten blue links. This system relies on the same triple-based data model described in this section, scaled to web-wide entity extraction and cross-source fusion.
Lab: Link Prediction on Nations Dataset with PyKEEN
Goal: Train two embedding models on a real knowledge graph and compare their ability to predict missing diplomatic and economic relations between countries.
Tools needed: Python 3.8+, PyKEEN (pip install pykeen), matplotlib.
Procedure (25 minutes): Load the built-in Nations dataset via pykeen.datasets.Nations. Train TransE and RotatE each for 200 epochs using the PyKEEN pipeline() function with default hyperparameters. Record MRR and Hits@10 for both models. Then vary the embedding dimension (try 32, 64, 128, 256) and plot MRR as a function of dimension for each model.
What to observe: Which model achieves higher Hits@10? Does increasing dimension always help, or does performance plateau? Inspect the top-5 predicted tails for the query (USA, exports_to, ?) and check whether the predictions align with real-world trade relationships. Note which relation types (symmetric vs. antisymmetric) each model handles better by comparing per-relation MRR.
Exercises
- Conceptual. TransE cannot model symmetric relations. Prove this formally: show that if \(\mathbf{h} + \mathbf{r} = \mathbf{t}\) and \(\mathbf{t} + \mathbf{r} = \mathbf{h}\), then \(\mathbf{r} = \mathbf{0}\) and \(\mathbf{h} = \mathbf{t}\). Then explain how RotatE avoids this problem.
- Coding. Using the TransE implementation from Listing 3.7, train the model for 200 epochs on the small biomedical graph with negative sampling (corrupt the tail entity by random replacement). Plot the training loss curve and report the final MRR on the training set.
- Analysis. Download the FB15k-237 dataset from PyKEEN and train both TransE and RotatE for 100 epochs. Compare MRR and Hits@10. Which relation patterns (symmetric, antisymmetric, compositional) does each model handle better? Provide specific examples from the dataset.
What's Next
Knowledge graph embeddings map discrete entities to continuous vectors. In Section 3.3: Embeddings and Vector Search, we zoom in on the vector space itself: how text and concepts are encoded as dense vectors, how similarity is measured, and how approximate nearest-neighbor indices (HNSW) make retrieval tractable at million-vector scale. The embedding techniques from this section and the next converge in Section 3.4, where we build a hybrid search system that combines graph structure, sparse keywords, and dense semantics.
Bibliography
Foundational Papers
Bordes, A., Usunier, N., Garcia-Durán, A., Weston, J., & Yakhnenko, O. (2013). Translating Embeddings for Modeling Multi-relational Data. NeurIPS. Introduced TransE and the translation-based paradigm for KG embeddings.
Sun, Z., Deng, Z.-H., Nie, J.-Y., & Tang, J. (2019). RotatE: Knowledge Graph Embedding by Relational Rotation in Complex Space. ICLR. Rotation-based embeddings that model symmetry, antisymmetry, inversion, and composition.
Trouillon, T., Welbl, J., Riedel, S., Gaussier, E., & Bouchard, G. (2016). Complex Embeddings for Simple Link Prediction. ICML. ComplEx bilinear scoring with complex-valued embeddings.
Himmelstein, D. S., et al. (2017). Systematic Integration of Biomedical Knowledge Prioritizes Drugs for Repurposing. eLife. Hetionet and the path-based approach to drug repurposing via knowledge graphs.
Surveys
Hogan, A., et al. (2021). Knowledge Graphs. ACM Computing Surveys. Comprehensive survey covering data models, querying, analytics, and embeddings.
Wang, Q., Mao, Z., Wang, B., & Guo, L. (2017). Knowledge Graph Embedding: A Survey of Approaches and Applications. IEEE TKDE. Systematic comparison of translation, bilinear, and neural embedding models.
Tools & Libraries
PyKEEN. PyKEEN GitHub. Python library implementing 40+ KG embedding models with standardized training, evaluation, and hyperparameter optimization.
Neo4j. Neo4j Documentation. Property graph database with the Cypher query language.
NetworkX. NetworkX Documentation. Python library for graph creation, manipulation, and analysis.
Datasets & Benchmarks
Toutanova, K., & Chen, D. (2015). Observed Versus Latent Features for Knowledge Base and Text Inference. ACL Workshop. Introduced FB15k-237, the cleaned version of FB15k without inverse-relation test leakage.
Galkin, M., et al. (2024). Towards Foundation Models for Knowledge Graph Reasoning. ICLR. ULTRA: transferable link prediction across knowledge graphs without retraining.