Blocking¶
base ¶
Abstract interface for blocking strategies.
BlockingStrategy ¶
Bases: ABC
Generates candidate entity pairs prior to full linking.
Blocking reduces the O(n*m) comparison space between two datasets down to a smaller set of plausible candidate pairs.
ranked
class-attribute
¶
ranked: bool = False
Whether candidate_pairs() returns each source entity's own
candidates best-first. Strategies with a graded notion of relevance
built on rank_top_matches
(LabelOverlap,
EmbeddingSimilarityBlocker) set this
True; consumers such as
sample_hard_negatives
check it to warn rather than silently mine the wrong candidates as
"hard" negatives.
candidate_pairs
abstractmethod
¶
candidate_pairs(
dataset1: list[Entity],
dataset2: list[Entity] | EntitySource,
) -> Iterable[tuple[Entity, Entity]]
Return candidate entity pairs to be scored by a linker.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dataset1
|
list[Entity]
|
Entities from the first dataset. |
required |
dataset2
|
list[Entity] | EntitySource
|
Entities from the second dataset, or an
EntitySource wrapping a
target too large to materialize as a |
required |
Returns:
| Type | Description |
|---|---|
Iterable[tuple[Entity, Entity]]
|
Candidate |
Iterable[tuple[Entity, Entity]]
|
graded notion of relevance, a given source entity's own |
Iterable[tuple[Entity, Entity]]
|
candidates should appear best-first (see |
Iterable[tuple[Entity, Entity]]
|
ranked) — consumers such as |
Iterable[tuple[Entity, Entity]]
|
|
Iterable[tuple[Entity, Entity]]
|
rely on this ordering. |
Source code in src/linkingtk/blocking/base.py
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | |
rank_top_matches ¶
rank_top_matches(
scores: dict[str, float],
entities_by_id: dict[str, Entity],
k: int,
*,
descending: bool,
keep: Callable[[float], bool] | None = None,
) -> list[Entity]
Rank scores (entity id -> score) and return the top k matching entities.
Shared by blocking strategies that score every candidate against a source entity and keep only the best few (LabelOverlap, EmbeddingSimilarityBlocker). Ties are broken by entity id for deterministic output.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scores
|
dict[str, float]
|
Candidate entity id -> score. |
required |
entities_by_id
|
dict[str, Entity]
|
Entity id -> |
required |
k
|
int
|
Maximum number of entities to return. |
required |
descending
|
bool
|
Whether higher scores are better (e.g. similarity) or lower scores are better (e.g. distance). |
required |
keep
|
Callable[[float], bool] | None
|
Optional predicate; candidates for which it returns
|
None
|
Returns:
| Type | Description |
|---|---|
list[Entity]
|
Up to |
Source code in src/linkingtk/blocking/base.py
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | |
exact ¶
Exact-label blocking strategy.
ExactMatch ¶
ExactMatch(top_k: int = 10)
Bases: BlockingStrategy
Blocks entities that share at least one identical label.
This is the default blocking strategy: two entities are considered
candidates if any of their labels are exactly equal (language tags are
ignored for comparison purposes). Also supports an
EntitySource for dataset2: it
queries dataset2.search(label) per label instead of enumerating the
whole target set, then keeps only the results that actually have a
matching label -- same semantics, no materialization required.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
top_k
|
int
|
Forwarded to |
10
|
Source code in src/linkingtk/blocking/exact.py
45 46 | |
label_overlap ¶
Approximate string matching blocking strategy.
Ported from Naisc's ApproximateStringMatching:
https://github.com/insight-centre/naisc/blob/master/naisc-core/src/main/java/org/insightcentre/uld/naisc/blocking/ApproximateStringMatching.java
Two interchangeable metrics are provided, matching the reference implementation:
"ngram": candidates are ranked by a frequency-weighted count of shared character n-grams (fast, index-based, no edit-distance computation)."levenshtein": candidates are ranked by normalized Levenshtein distance, using the PatriciaTrie for an approximate nearest-neighbor search that stays tractable on larger datasets.
Unlike the original, both metrics support an optional threshold to
drop weak candidates outright.
LabelOverlap ¶
LabelOverlap(
metric: Metric = "ngram",
ngram_size: int = 3,
max_matches: int = 5,
lowercase: bool = True,
threshold: float | None = None,
queue_max: int | None = None,
max_ngram_document_frequency: int = 1000,
)
Bases: BlockingStrategy
Blocks entities by approximate string similarity of their labels.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
metric
|
Metric
|
|
'ngram'
|
ngram_size
|
int
|
Character n-gram length. Only used by the |
3
|
max_matches
|
int
|
Maximum number of candidates to keep per source entity. |
5
|
lowercase
|
bool
|
Whether to lowercase labels before comparing. |
True
|
threshold
|
float | None
|
Optional cutoff used to drop weak candidates. For
|
None
|
queue_max
|
int | None
|
Maximum search-frontier size for the trie-based
search. Only used by the |
None
|
max_ngram_document_frequency
|
int
|
N-grams shared by more than this
many target entities are dropped as uninformative before
matching. Only used by the |
1000
|
Source code in src/linkingtk/blocking/label_overlap.py
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | |
trie ¶
A Patricia (radix) trie with approximate nearest-neighbor lookup.
Ported from Naisc's ApproximateStringMatching.PatriciaTrie:
https://github.com/insight-centre/naisc/blob/master/naisc-core/src/main/java/org/insightcentre/uld/naisc/blocking/ApproximateStringMatching.java
The trie stores strings compressed along shared prefixes so that a best-first search, guided by a lower bound on normalized Levenshtein distance, can find the nearest keys to a query without scoring every entry in the trie.
PatriciaTrie ¶
PatriciaTrie()
Bases: Generic[T]
Radix trie mapping strings to (possibly repeated) values.
Source code in src/linkingtk/blocking/trie.py
78 79 | |
insert ¶
insert(key: str, value: T) -> None
Associate value with key. Empty keys are ignored.
Source code in src/linkingtk/blocking/trie.py
81 82 83 84 | |
nearest ¶
nearest(
query: str, n: int, queue_max: int
) -> list[tuple[T, float]]
Find up to n values whose keys best match query.
Uses best-first search over the trie, pruned by a lower bound on normalized edit distance, so that only a bounded, promising portion of the trie is actually scored.
This is an approximate search, as in the original Naisc implementation: the lower bound assumes the shortest possible completion of each trie prefix, so a true match reached through a much longer key can occasionally be pruned before it is scored. For blocking (where the goal is a good candidate set, not a provably-exact top-n) this is an acceptable and deliberate speed/recall trade-off.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
query
|
str
|
The string to search for. |
required |
n
|
int
|
Maximum number of results to return. |
required |
queue_max
|
int
|
Maximum size of the search frontier; bounds worst-case search cost at some risk of missing a match. |
required |
Returns:
| Type | Description |
|---|---|
list[tuple[T, float]]
|
Up to |
list[tuple[T, float]]
|
sorted by ascending distance (closest match first). |
Source code in src/linkingtk/blocking/trie.py
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | |
edit_distance ¶
edit_distance(s: str, t: str) -> int
Levenshtein edit distance between two strings.
Source code in src/linkingtk/blocking/trie.py
22 23 24 25 26 27 28 29 30 31 32 33 | |
embedding ¶
Fuzzy blocking strategy based on TF-IDF vector similarity.
A non-neural, classical-ML alternative to LabelOverlap: entities are compared by cosine similarity of vectors from a pluggable scikit-learn-style text vectorizer (TF-IDF by default) rather than surface- string overlap, so paraphrased or reordered text (e.g. a description and a differently-worded gloss of the same sense) can still block together.
EmbeddingSimilarityBlocker ¶
EmbeddingSimilarityBlocker(
field: Field = "label",
top_k: int = 10,
threshold: float | None = None,
vectorizer: Vectorizer | None = None,
)
Bases: BlockingStrategy
Blocks entities by cosine similarity of vectorized text.
Fits vectorizer on dataset2 (treating it as the reference
corpus) and projects dataset1 into that same vector space, then for
each dataset1 entity retrieves the top_k closest dataset2
entities by cosine similarity via
sklearn.neighbors.NearestNeighbors.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
field
|
Field
|
Which text to compare entities on: |
'label'
|
top_k
|
int
|
Maximum number of candidates to keep per source entity. |
10
|
threshold
|
float | None
|
Optional minimum cosine similarity a candidate must
reach to be kept. If not set, candidates with a similarity of
exactly zero are still dropped, since |
None
|
vectorizer
|
Vectorizer | None
|
Any object exposing scikit-learn's
|
None
|
Source code in src/linkingtk/blocking/embedding.py
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | |
Vectorizer ¶
Bases: Protocol
Structural type for a fit_transform/transform text vectorizer (e.g. scikit-learn's).
negative_sampling ¶
Hard-negative sampling for training classical ML linkers.
Mines the most confusable non-matching candidates per source entity from
a BlockingStrategy, rather than sampling
negatives uniformly at random from the full candidate pool — a blocking
strategy already considered these plausible enough to surface, so they're
more informative training signal for a classifier
(fit)
than a random draw. Shaped as a plain function over Entity/
BlockingStrategy (no dependency on TrainingArguments or anything
Trainer-specific) so it's equally usable by
Trainer once that's implemented.
sample_hard_negatives ¶
sample_hard_negatives(
dataset1: list[Entity],
dataset2: list[Entity] | EntitySource,
ground_truth: list[tuple[str, str]],
blocking: BlockingStrategy,
top_k: int = 5,
) -> list[tuple[Entity, Entity]]
Sample the top-K most confusable non-matching candidates per source entity.
For each distinct source entity in ground_truth, takes that
entity's candidates from blocking.candidate_pairs(dataset1,
dataset2) in the best-first order documented by
candidate_pairs,
excluding any candidate that is itself a true match for that source
entity, up to top_k.
A source entity with fewer than top_k candidates (or none at all)
simply contributes fewer negatives — this is normal partial coverage,
not an error.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dataset1
|
list[Entity]
|
Source entities. |
required |
dataset2
|
list[Entity] | EntitySource
|
Target entities. |
required |
ground_truth
|
list[tuple[str, str]]
|
List of |
required |
blocking
|
BlockingStrategy
|
Strategy used to generate candidates to mine negatives from. |
required |
top_k
|
int
|
Maximum number of hard negatives to keep per source entity. |
5
|
Returns:
| Type | Description |
|---|---|
list[tuple[Entity, Entity]]
|
A flat list of |
list[tuple[Entity, Entity]]
|
fit's |
list[tuple[Entity, Entity]]
|
|
list[tuple[Entity, Entity]]
|
to |
list[tuple[Entity, Entity]]
|
runs once here and again inside |
list[tuple[Entity, Entity]]
|
intended toy/small-dataset scale, but worth knowing if |
list[tuple[Entity, Entity]]
|
is itself expensive. |
Source code in src/linkingtk/blocking/negative_sampling.py
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | |