ontolearn.lp_generator.complexity_lp_generator
Goal-directed, complexity-controlled learning-problem generation.
ontolearn.lp_generator.LPGen/KB2Data synthesize learning problems bottom-up: they
refine owl:Thing with ExpressRefinement and keep whatever non-redundant concepts fall
out, sorted by length only as a dedup tie-break. There is no way to ask for “a learning problem
whose target uses exactly a nested existential restriction and an object intersection, depth <= 3”.
This module instead samples a target OWLClassExpression top-down from an explicit DL-construct
grammar (ComplexityProfile): the caller names which constructors are allowed
(intersection, union, existential/universal restriction, cardinality restrictions, has-value,
negation, inverse roles) and bounds on length/depth, and the generator recursively builds concepts
that respect that budget. Each candidate is turned into a learning problem via reasoner-based
retrieval (kb.individuals_set).
Candidates are additionally ranked by an explicit difficulty score (DifficultyWeights)
combining four signals: normalized target length/depth, how far the best “cheap” one-step
hypothesis (an atomic class, its negation, or a bare exists r.Top) falls short of solving the
LP outright, and the density of near-miss negatives – individuals that satisfy the target with
exactly one top-level conjunct dropped (e.g. the right class but the wrong relation), which is
exactly where a greedy top-down learner is most likely to overgeneralize. generate()
oversamples candidates and keeps the hardest num_lps of them, and biases the negative sample
towards near-misses via hard_negative_ratio, so the returned problems are pointed at the
learner’s actual failure modes rather than generic random negatives.
The grammar and probes are built purely from the knowledge base’s own signature
(kb.ontology.classes_in_signature(), kb.get_object_properties()), so the same
ComplexityProfile curriculum runs unmodified across knowledge graphs with very different
shapes – e.g. Family, Mutagenesis, Carcinogenesis, Biopax, Lymphography. Relation-based constructs
(∃/∀/cardinality/has-value) are silently dropped from the grammar on a KB that exposes no object
properties (e.g. Lymphography) rather than raising, so a curriculum degrades gracefully instead of
crashing on such KBs.
A ComplexityProfile curriculum (e.g. atomic -> conjunctive -> nested existential ->
cardinality -> full ALCQ) can then be run through GoalDirectedLPGenerator.generate_benchmark()
to build a graded benchmark suite, e.g. to measure how a learner’s F1 degrades as target-concept
complexity grows – see examples/benchmark_complexity_lp_generator_with_tdl.py.
Classes
DL constructors a |
|
Weights combining four difficulty signals into one score in (roughly) |
|
A named point (or curriculum step) in the space of "how complex should the target be". |
|
A goal-directed learning problem: a sampled target concept plus its (E+, E-). |
|
Samples target concepts from an explicit DL-construct grammar and turns them into LPs. |
Functions
|
Serialize a benchmark (as returned by |
Module Contents
- class ontolearn.lp_generator.complexity_lp_generator.DLConstruct(*args, **kwds)[source]
Bases:
enum.EnumDL constructors a
ComplexityProfilecan allow/forbid.- ATOMIC = 'atomic'
- NEGATION = 'negation'
- INTERSECTION = 'intersection'
- UNION = 'union'
- EXISTENTIAL = 'existential'
- UNIVERSAL = 'universal'
- MIN_CARDINALITY = 'min_cardinality'
- MAX_CARDINALITY = 'max_cardinality'
- EXACT_CARDINALITY = 'exact_cardinality'
- HAS_VALUE = 'has_value'
- class ontolearn.lp_generator.complexity_lp_generator.DifficultyWeights[source]
Weights combining four difficulty signals into one score in (roughly)
[0, 1].- Parameters:
length – weight on
length / profile.max_length(longer target -> harder).depth – weight on
depth / profile.max_depth(deeper nesting -> harder).baseline_hardness – weight on
1 - baseline_f1– how far the best cheap one-step hypothesis falls short of solving the LP outright.near_miss – weight on the fraction of positives that have a matching near-miss negative (an individual satisfying the target with exactly one top-level conjunct dropped) – these are the negatives a greedy learner is most likely to accidentally cover.
- length: float = 0.25
- depth: float = 0.15
- baseline_hardness: float = 0.4
- near_miss: float = 0.2
- class ontolearn.lp_generator.complexity_lp_generator.ComplexityProfile[source]
A named point (or curriculum step) in the space of “how complex should the target be”.
- Parameters:
name – identifier used to tag generated problems and group benchmark results.
allowed_constructs – DL constructors
GoalDirectedLPGenerator.generate()may use when sampling a target concept. Relation-based constructs (∃/∀/cardinality/has-value) are silently unavailable on a knowledge base exposing no object properties.min_length/max_length – bounds on
concept_lenof the (NNF) target.min_depth/max_depth – bounds on restriction-nesting depth (see
GoalDirectedLPGenerator.concept_depth()); a bare named class has depth 0.max_arity – maximum number of operands sampled under one intersection/union.
card_limit – cardinality restrictions sample
nuniformly from[1, card_limit].use_inverse_roles – if True, restrictions may use
r-in place ofr.min_pos/min_neg – an LP is discarded unless the target has at least this many positive and negative instances in the knowledge base.
max_examples_per_side – positives/negatives are subsampled down to this cap.
max_baseline_f1 – an LP is discarded if the best of a pool of “cheap” one-step hypotheses (an atomic class, its negation, or a bare
exists r.Top) already reaches this F1 – i.e. it is trivially solvable without the constructs being tested.min_difficulty – an LP is discarded if its
DifficultyWeights-weighted score falls below this bar.0.0(default) disables this filter.oversample_factor –
GoalDirectedLPGenerator.generate()collects up tonum_lps * oversample_factorpassing candidates (subject tomax_attempts) before ranking by difficulty and keeping the hardestnum_lps– i.e. it actively searches for hard problems instead of returning the first ones found.hard_negative_ratio – fraction of the sampled negatives drawn from the near-miss pool (when one exists) rather than arbitrary non-instances.
difficulty_weights – see
DifficultyWeights.
- name: str
- allowed_constructs: FrozenSet[DLConstruct]
- min_length: int = 1
- max_length: int = 10
- min_depth: int = 0
- max_depth: int = 3
- max_arity: int = 3
- card_limit: int = 5
- use_inverse_roles: bool = False
- min_pos: int = 3
- min_neg: int = 3
- max_examples_per_side: int = 200
- max_baseline_f1: float = 0.9
- min_difficulty: float = 0.0
- oversample_factor: int = 4
- hard_negative_ratio: float = 0.5
- difficulty_weights: DifficultyWeights
- class ontolearn.lp_generator.complexity_lp_generator.GeneratedLearningProblem[source]
A goal-directed learning problem: a sampled target concept plus its (E+, E-).
- profile_name: str
- target_concept: owlapy.class_expression.OWLClassExpression
- dl: str
- pos: Individuals
- neg: Individuals
- length: int
- depth: int
- construct_histogram: Dict[str, int]
- baseline_f1: float
- difficulty: float
- num_near_miss_negatives: int
- to_lp() PosNegLPStandard[source]
Build the
PosNegLPStandardconsumed by Ontolearn learners (e.g. TDL).
- class ontolearn.lp_generator.complexity_lp_generator.GoalDirectedLPGenerator(knowledge_base: KnowledgeBase, seed: int | None = None, probe_cap: int = 300)[source]
Samples target concepts from an explicit DL-construct grammar and turns them into LPs.
Usage:
kb = KnowledgeBase(path="KGs/Family/family-benchmark_rich_background.owl") gen = GoalDirectedLPGenerator(kb, seed=1) profile = ComplexityProfile( name="nested-existential", allowed_constructs=frozenset({DLConstruct.EXISTENTIAL, DLConstruct.INTERSECTION}), max_length=8, max_depth=3, ) lps = gen.generate(profile, num_lps=10) model = TDL(kb).fit(lps[0].to_lp())
- kb
- rng
- all_individuals: Individuals
- atomic_classes: List[owlapy.class_expression.OWLClass]
- object_properties: List[owlapy.owl_property.OWLObjectProperty]
- baseline_f1(pos: Individuals, neg: Individuals) float[source]
Best F1 any single “cheap” one-step hypothesis reaches on (pos, neg).
- near_miss_negatives(target: owlapy.class_expression.OWLClassExpression, pos: Individuals) Individuals[source]
Individuals satisfying target with exactly one top-level conjunct dropped, minus pos.
E.g. for
C = Compound ⊓ (∃ hasChild.Father)these are individuals that are aCompoundbut whose child (if any) isn’t aFather, or that have aFatherchild but aren’t aCompound– the negatives one ablated conjunct away from being positive, which is exactly what trips up a greedy top-down learner.
- static difficulty_score(profile: ComplexityProfile, length: int, depth: int, base_f1: float, pos: Individuals, near_miss: Individuals) float[source]
Combine length/depth/baseline-hardness/near-miss-density into one score in
[0, 1].
- static concept_depth(ce: owlapy.class_expression.OWLClassExpression) int[source]
Restriction-nesting depth: a named class or
has valueatom has depth 0.
- sample_target(profile: ComplexityProfile) Tuple[owlapy.class_expression.OWLClassExpression, collections.Counter][source]
Sample one target concept (in NNF) respecting profile, plus its construct histogram.
- generate(profile: ComplexityProfile, num_lps: int, max_attempts: int = 3000) List[GeneratedLearningProblem][source]
Sample the num_lps hardest distinct, non-trivial learning problems matching profile: up to
num_lps * profile.oversample_factorpassing candidates are collected (subject to max_attempts sampling attempts total), ranked bydifficulty_score()and the top num_lps are kept.
- generate_benchmark(profiles: Sequence[ComplexityProfile], num_lps_per_profile: int = 5, max_attempts_per_profile: int = 3000) Dict[str, List[GeneratedLearningProblem]][source]
Run
generate()for each profile in a curriculum; returns {profile.name: […]}.
- ontolearn.lp_generator.complexity_lp_generator.save_benchmark(benchmark: Dict[str, List[GeneratedLearningProblem]], path: str) None[source]
Serialize a benchmark (as returned by
GoalDirectedLPGenerator.generate_benchmark()) to JSON: {dl_string: {“profile”, “positive_examples”, “negative_examples”, “length”, “depth”, “construct_histogram”, “baseline_f1”, “difficulty”, “num_near_miss_negatives”}}, individuals kept as full IRIs.