diff --git a/docs/source/gettingstarted/overview.rst b/docs/source/gettingstarted/overview.rst
index 852bbee..0b71fb9 100644
--- a/docs/source/gettingstarted/overview.rst
+++ b/docs/source/gettingstarted/overview.rst
@@ -6,26 +6,25 @@ Introduction
.. raw:: html
-
-

-
+
+

+
OntoAligner is a modular, extensible, and efficient framework for ontology alignment that integrates classical heuristics, retrieval-based methods, and large language models (LLMs). It is designed to support a wide range of ontology alignment (OA) scenariosβfrom lightweight matching to advanced semantic reasoningβwith built-in support for evaluation and export.
.. tab:: π§© Parser
-
-
The ``Parser`` module serves as the entry point of OntoAligner, handling ontology ingestion and alignment data loading. Key components include:
- 1) ``OntologyParser`` that supports loading ontologies and extracts class/property names, IRIs, hierarchies, synonyms, annotations, and any relevant informations.
+ 1) ``OntologyParser`` that supports loading ontologies and extracts class/property names, IRIs, hierarchies, synonyms, annotations, and any relevant information.
2) ``AlignmentsParser`` for loading ground truth alignments used for evaluation. However, in cases where a specific dataset does not support references, this module may be ignored.
.. note::
π§ Checkout parsers at `Developer Guide > Parsers <../developerguide/parsers.html>`_
+
::
@@ -109,63 +108,69 @@ Usage
pip install -U OntoAligner
- See `installation `_ for further installation options.
+ .. note::
+
+ See `installation `_ for further installation options.
::
.. tab:: π Quickstart
- See the `Quickstart `_ for more quick information on how to use OntoAligner.
+ Working with OntoAligner is straightforward:
- ::
+ .. code-block:: python
+ from ontoaligner.ontology import MaterialInformationMatOntoOMDataset
+ from ontoaligner.utils import metrics, xmlify
+ from ontoaligner.aligner import MistralLLMBERTRetrieverRAG
+ from ontoaligner.encoder import ConceptParentRAGEncoder
+ from ontoaligner.postprocess import rag_hybrid_postprocessor
-Working with OntoAligner is straightforward:
+ # Step 1: Initialize the dataset object for MaterialInformation MatOnto dataset
+ task = MaterialInformationMatOntoOMDataset()
+ print("Test Task:", task)
-.. code-block:: python
+ # Step 2: Load source and target ontologies along with reference matchings
+ dataset = task.collect(
+ source_ontology_path="assets/MI-MatOnto/mi_ontology.xml",
+ target_ontology_path="assets/MI-MatOnto/matonto_ontology.xml",
+ reference_matching_path="assets/MI-MatOnto/matchings.xml"
+ )
- from ontoaligner.ontology import MaterialInformationMatOntoOMDataset
- from ontoaligner.utils import metrics, xmlify
- from ontoaligner.aligner import MistralLLMBERTRetrieverRAG
- from ontoaligner.encoder import ConceptParentRAGEncoder
- from ontoaligner.postprocess import rag_hybrid_postprocessor
+ # Step 3: Encode the source and target ontologies
+ encoder_model = ConceptParentRAGEncoder()
+ encoded_ontology = encoder_model(source=dataset['source'], target=dataset['target'])
- # Step 1: Initialize the dataset object for MaterialInformation MatOnto dataset
- task = MaterialInformationMatOntoOMDataset()
- print("Test Task:", task)
+ # Step 4: Define configuration for retriever and LLM
+ retriever_config = {"device": 'cuda', "top_k": 5,}
+ llm_config = {"device": "cuda", "max_length": 300, "max_new_tokens": 10, "batch_size": 15}
- # Step 2: Load source and target ontologies along with reference matchings
- dataset = task.collect(
- source_ontology_path="assets/MI-MatOnto/mi_ontology.xml",
- target_ontology_path="assets/MI-MatOnto/matonto_ontology.xml",
- reference_matching_path="assets/MI-MatOnto/matchings.xml"
- )
+ # Step 5: Initialize Generate predictions using RAG-based ontology matcher
+ model = MistralLLMBERTRetrieverRAG(retriever_config=retriever_config, llm_config=llm_config)
+ model.load(llm_path = "mistralai/Mistral-7B-v0.3", ir_path="all-MiniLM-L6-v2")
+ predicts = model.generate(input_data=encoded_ontology)
- # Step 3: Encode the source and target ontologies
- encoder_model = ConceptParentRAGEncoder()
- encoded_ontology = encoder_model(source=dataset['source'], target=dataset['target'])
+ # Step 6: Apply hybrid postprocessing
+ hybrid_matchings, hybrid_configs = rag_hybrid_postprocessor(predicts=predicts,
+ ir_score_threshold=0.1,
+ llm_confidence_th=0.8)
- # Step 4: Define configuration for retriever and LLM
- retriever_config = {"device": 'cuda', "top_k": 5,}
- llm_config = {"device": "cuda", "max_length": 300, "max_new_tokens": 10, "batch_size": 15}
+ evaluation = metrics.evaluation_report(predicts=hybrid_matchings, references=dataset['reference'])
+ print("Hybrid Matching Evaluation Report:", evaluation)
- # Step 5: Initialize Generate predictions using RAG-based ontology matcher
- model = MistralLLMBERTRetrieverRAG(retriever_config=retriever_config, llm_config=llm_config)
- model.load(llm_path = "mistralai/Mistral-7B-v0.3", ir_path="all-MiniLM-L6-v2")
- predicts = model.generate(input_data=encoded_ontology)
+ # Step 7: Convert matchings to XML format and save the XML representation
+ xml_str = xmlify.xml_alignment_generator(matchings=hybrid_matchings)
+ open("matchings.xml", "w", encoding="utf-8").write(xml_str)
- # Step 6: Apply hybrid postprocessing
- hybrid_matchings, hybrid_configs = rag_hybrid_postprocessor(predicts=predicts,
- ir_score_threshold=0.1,
- llm_confidence_th=0.8)
+ .. note::
+
+ See the `Quickstart `_ for more quick information on how to use OntoAligner.
+
+
+ ::
- evaluation = metrics.evaluation_report(predicts=hybrid_matchings, references=dataset['reference'])
- print("Hybrid Matching Evaluation Report:", evaluation)
- # Step 7: Convert matchings to XML format and save the XML representation
- xml_str = xmlify.xml_alignment_generator(matchings=hybrid_matchings)
- open("matchings.xml", "w", encoding="utf-8").write(xml_str)
What is Next?
----------------
diff --git a/ontoaligner/pipeline.py b/ontoaligner/pipeline.py
index 99f8f75..718f0bb 100644
--- a/ontoaligner/pipeline.py
+++ b/ontoaligner/pipeline.py
@@ -17,7 +17,7 @@
Ontology Alignment Pipeline. Various methods such as lightweight matching, retriever-based matching, LLM-based matching,
and RAG (Retriever-Augmented Generation) techniques has been applied.
-AlignerPipeline runs user-provided encoder, aligner and optional postprocessor components over a collected ontology matching dataset. Unlike
+AlignerPipeline runs user-provided encoder, aligner and optional postprocessor & reranker components over a collected ontology matching dataset. Unlike
OntoAlignerPipeline, it does not collect datasets, select methods, evaluate results, or save outputs.
"""
import json
@@ -53,7 +53,7 @@ class AlignerPipeline(BaseOMModel):
This class follows the standard OntoAligner flow for one aligner pipeline:
encode the ontology matching dataset, load the aligner if needed, generate predictions,
- and optionally apply a postprocessor.
+ and optionally apply a reranker & postprocessor.
"""
def __init__(
@@ -65,6 +65,10 @@ def __init__(
llm_dataset_class: Dataset = None,
batch_size: int = 1,
shuffle: bool = False,
+ reranker: BaseOMModel = None,
+ reranker_load_params: Dict = None,
+ reranker_encoder: BaseEncoder = None,
+ reranker_om_dataset: Dict = None,
postprocessor: Any = None,
postprocessor_params: Dict = None,
include_reference: bool = False,
@@ -81,6 +85,10 @@ def __init__(
llm_dataset_class (Dataset, optional): Dataset class used to wrap LLM inputs. Defaults to None.
batch_size (int, optional): Batch size used for LLM dataset generation. Defaults to 1.
shuffle (bool, optional): Whether to shuffle LLM dataset batches. Defaults to False.
+ reranker (BaseOMModel, optional): Optional reranking model used to reorder candidate predictions. Defaults to None.
+ reranker_load_params (Dict, optional): Parameters forwarded to the reranker load method. Defaults to None.
+ reranker_encoder (BaseEncoder, optional): Optional encoder used to prepare source and target ontologies text for reranking. Defaults to None.
+ reranker_om_dataset (Dict, optional): Optional ontology matching dataset used by the reranker encoder. Defaults to None.
postprocessor (Any, optional): Optional postprocessor applied to predictions. Defaults to None.
postprocessor_params (Dict, optional): Optional parameters forwarded to the postprocessor. Defaults to None.
include_reference (bool, optional): Whether to pass reference matchings to the encoder. Defaults to False.
@@ -94,6 +102,10 @@ def __init__(
self.llm_dataset_class = llm_dataset_class
self.batch_size = batch_size
self.shuffle = shuffle
+ self.reranker = reranker
+ self.reranker_load_params = reranker_load_params or {}
+ self.reranker_encoder = reranker_encoder
+ self.reranker_om_dataset = reranker_om_dataset
self.postprocessor = postprocessor
self.postprocessor_params = postprocessor_params or {}
self.include_reference = include_reference
@@ -157,6 +169,132 @@ def _load_aligner(self) -> None:
if hasattr(self.aligner, "load") and self.load_params:
self.aligner.load(**self.load_params)
+ def _load_reranker(self) -> None:
+ """
+ Loads the optional reranking model when reranker load parameters are provided.
+ """
+ if self.reranker is not None and hasattr(self.reranker, "load") and self.reranker_load_params:
+ self.reranker.load(**self.reranker_load_params)
+
+ def _is_grouped_candidate_output(self, predictions: List) -> bool:
+ """
+ Checks whether predictions are already in grouped candidate format.
+
+ Grouped candidate format:
+ [
+ {
+ "source": source_iri,
+ "target-cands": [...],
+ "score-cands": [...],
+ },
+ ...
+ ]
+ """
+ if not isinstance(predictions, list) or not predictions:
+ return False
+
+ first_prediction = predictions[0]
+
+ return isinstance(first_prediction, dict) and all(
+ key in first_prediction
+ for key in ["source", "target-cands", "score-cands"]
+ )
+
+ def _group_predictions(self, predictions: List) -> List:
+ """
+ Converts flat source-target-score predictions into grouped candidate format.
+
+ Parameters:
+ predictions (List): Flat alignment predictions generated by an aligner.
+
+ Returns:
+ List: Grouped candidate predictions that can be passed to a reranker.
+ """
+ grouped_predictions = {}
+
+ for prediction in predictions:
+ source = prediction["source"]
+ target = prediction["target"]
+ score = prediction.get("score", 1.0)
+
+ if source not in grouped_predictions:
+ grouped_predictions[source] = {
+ "source": source,
+ "target-cands": [],
+ "score-cands": [],
+ }
+
+ grouped_predictions[source]["target-cands"].append(target)
+ grouped_predictions[source]["score-cands"].append(float(score))
+
+ return list(grouped_predictions.values())
+
+ def _encode_reranker(self, om_dataset: Dict, encoded_data: List) -> List:
+ """
+ Encodes source and target ontologies for the optional reranker.
+
+ This is mostly used when the main pipeline encoder produces non-textual
+ representations, such as graph triples, while the reranker requires
+ source and target concept text.
+
+ Parameters:
+ om_dataset (Dict): The ontology matching dataset.
+ encoded_data (List): Encoded data produced by the main pipeline encoder.
+
+ Returns:
+ List: Source and target ontology representations used by the reranker.
+ """
+ if self.reranker_encoder is None:
+ return encoded_data
+
+ reranker_om_dataset = self.reranker_om_dataset or om_dataset
+
+ return self.reranker_encoder(
+ source=reranker_om_dataset["source"],
+ target=reranker_om_dataset["target"],
+ )
+
+ def _apply_reranker(
+ self,
+ predictions: List,
+ encoded_data: List,
+ om_dataset: Dict,
+ ) -> List:
+ """
+ Applies the optional reranker to generated predictions.
+
+ Parameters:
+ predictions (List): Predictions generated by the aligner.
+ encoded_data (List): Encoded data produced by the main pipeline encoder.
+ om_dataset (Dict): The ontology matching dataset.
+
+ Returns:
+ List: Reranked predictions.
+ """
+ if self.reranker is None:
+ return predictions
+
+ self._load_reranker()
+
+ if not self._is_grouped_candidate_output(predictions):
+ predictions = self._group_predictions(
+ predictions=predictions,
+ )
+
+ reranker_encoded_data = self._encode_reranker(
+ om_dataset=om_dataset,
+ encoded_data=encoded_data,
+ )
+
+ return self.reranker.generate(
+ input_data=[
+ reranker_encoded_data[0],
+ reranker_encoded_data[1],
+ predictions,
+ ]
+ )
+
+
def _generate_llm_predictions(self, llm_dataset: Dataset) -> List:
"""
Generates LLM predictions from an LLM dataset using batched prompts.
@@ -245,6 +383,13 @@ def generate(self, input_data: Dict = None) -> List:
else:
predictions = self.aligner.generate(input_data=encoded_data)
+ if self.reranker is not None:
+ predictions = self._apply_reranker(
+ predictions=predictions,
+ encoded_data=encoded_data,
+ om_dataset=om_dataset,
+ )
+
if self.postprocessor is not None:
predictions = self._apply_postprocessor(
predictions=predictions,
diff --git a/tutorial/04-nested-ensemble-aligners-in-ontoaligner.ipynb b/tutorial/04-nested-ensemble-aligners-in-ontoaligner.ipynb
new file mode 100644
index 0000000..c018e21
--- /dev/null
+++ b/tutorial/04-nested-ensemble-aligners-in-ontoaligner.ipynb
@@ -0,0 +1,1413 @@
+{
+ "cells": [
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "\n",
+ "\n",
+ "[](https://badge.fury.io/py/OntoAligner)\n",
+ "[](https://pepy.tech/projects/ontoaligner)\n",
+ "\n",
+ "[](https://github.com/pre-commit/pre-commit)\n",
+ "[](https://ontoaligner.readthedocs.io/)\n",
+ "[](MAINTANANCE.md)\n",
+ " [](https://doi.org/10.5281/zenodo.14533133)\n",
+ "\n",
+ "- **Documentation website**: [https://ontoaligner.readthedocs.io/index.html](https://ontoaligner.readthedocs.io/index.html)\n",
+ "- **Resource Paper**: [https://doi.org/10.1007/978-3-031-94578-6_10](https://doi.org/10.1007/978-3-031-94578-6_10)\n",
+ "\n",
+ "--------\n",
+ "\n",
+ "\n",
+ "# Nested Ensemble Aligners in OntoAligner"
+ ],
+ "id": "6c39714a95fee23e"
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "This notebook demonstrates how to build a nested ensemble alignment workflow in OntoAligner.\n",
+ "\n",
+ "Ontology alignment can benefit from more than one type of signal. Some aligners are good at retrieving broad candidate mappings, some are better at refining those candidates, and others use structural or language-model reasoning. Instead of choosing only one strategy, this notebook shows how these strategies can be grouped and combined.\n",
+ "\n",
+ "The workflow uses [AlignerPipeline](https://ontoaligner.readthedocs.io/developerguide/pipeline.html) as the standard unit for running each aligner. Related aligners are grouped with [EnsembleLearningAligner](https://ontoaligner.readthedocs.io/aligner/ensemble_learning.html), and those group-level ensembles are combined again into one final nested ensemble.\n",
+ "\n",
+ "The flow below shows how the individual `AlignerPipeline` objects are grouped into ensemble aligners and combined into the final nested ensemble:"
+ ],
+ "id": "668491ea5c2c82e1"
+ },
+ {
+ "cell_type": "markdown",
+ "id": "4ade03a1",
+ "metadata": {},
+ "source": [
+ "---\n",
+ "```text\n",
+ "Mouse-Human dataset\n",
+ " β\n",
+ " ββ llm_pipeline βββββββββ\n",
+ " ββ rag_pipeline βββββββββΌβ llm_ensemble βββββββββββββ\n",
+ " ββ fsrag_pipeline βββββββ β\n",
+ " β\n",
+ " ββ lightweight_pipeline ββ β\n",
+ " ββ tfidf_pipeline ββββββββΌβ retrieval_ensemble ββββββΌβ nested_ensemble\n",
+ " ββ sbert_pipeline ββββββββ β |\n",
+ " β |\n",
+ " ββ sbert_reranking_pipeline ββ β |\n",
+ " ββ tfidf_reranking_pipeline ββΌβ reranking_ensemble ββ |\n",
+ " ββ graph_reranking_pipeline ββ |\n",
+ " β\n",
+ " final_matchings\n",
+ " β\n",
+ " β\n",
+ " evaluation report\n",
+ " β\n",
+ " β\n",
+ " XML and JSON export\n",
+ "```"
+ ]
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "---\n",
+ "Contents of this tutorial:\n",
+ "\n",
+ "1. Setup and configuration\n",
+ "2. Dataset loading\n",
+ "3. Ensemble construction\n",
+ "4. Nested ensemble execution\n",
+ "5. Evaluation and export"
+ ],
+ "id": "3d19012b8cf9438f"
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "---\n",
+ "## 1οΈβ£. Setup and Configuration\n",
+ "\n",
+ "We begin by importing the OntoAligner modules used throughout the notebook and defining the runtime settings. These settings include ontology paths, model paths, and device configuration.\n",
+ "\n",
+ "This setup step keeps the rest of the notebook focused on the alignment workflow rather than repeated configuration."
+ ],
+ "id": "b70b695956feb313"
+ },
+ {
+ "cell_type": "markdown",
+ "id": "49f88dd7",
+ "metadata": {},
+ "source": [
+ "### Import Libraries\n",
+ "\n",
+ "OntoAligner provides separate modules for datasets, encoders, aligners, postprocessors, rerankers, ensembles, and evaluation. We import these components here so they can be used consistently across the different ensemble groups."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "f5864021",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-07-09T13:54:19.956041200Z",
+ "start_time": "2026-07-09T13:54:07.229614400Z"
+ }
+ },
+ "source": [
+ "# Import necessary libraries\n",
+ "import json\n",
+ "import torch\n",
+ "\n",
+ "from sklearn.linear_model import LogisticRegression\n",
+ "\n",
+ "# Import necessary modules from the 'ontoaligner' library\n",
+ "# The library provides tools for ontology alignment tasks, including dataset management,\n",
+ "# encoding, retrieval, reranking, evaluation, ensemble voting, and postprocessing.\n",
+ "from ontoaligner.ontology import MouseHumanOMDataset, GraphTripleOMDataset\n",
+ "from ontoaligner.utils import metrics, xmlify\n",
+ "from ontoaligner.encoder import (\n",
+ " ConceptParentLightweightEncoder,\n",
+ " ConceptLLMEncoder,\n",
+ " ConceptParentRAGEncoder,\n",
+ " ConceptParentFewShotEncoder,\n",
+ " GraphTripleEncoder,\n",
+ ")\n",
+ "from ontoaligner.aligner import (\n",
+ " SimpleFuzzySMLightweight,\n",
+ " TFIDFRetrieval,\n",
+ " SBERTRetrieval,\n",
+ " AutoModelDecoderLLM,\n",
+ " ConceptLLMDataset,\n",
+ " MistralLLMBERTRetrieverRAG,\n",
+ " MistralLLMBERTRetrieverFSRAG,\n",
+ " ConvEAligner,\n",
+ " CrossEncoderReranking,\n",
+ ")\n",
+ "from ontoaligner.postprocess import (\n",
+ " TFIDFLabelMapper,\n",
+ " llm_postprocessor,\n",
+ " rag_heuristic_postprocessor,\n",
+ " retriever_postprocessor,\n",
+ ")\n",
+ "from ontoaligner.aligner.ensemble import EnsembleLearningAligner\n",
+ "from ontoaligner.aligner.ensemble.voting import (\n",
+ " ReciprocalRankFusionVoting,\n",
+ " ScoreAverageVoting,\n",
+ ")\n",
+ "from ontoaligner import AlignerPipeline"
+ ],
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "C:\\Users\\AlluV\\Desktop\\1\\OntoAligner-dev-test\\.venv\\lib\\site-packages\\tqdm\\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
+ " from .autonotebook import tqdm as notebook_tqdm\n"
+ ]
+ }
+ ],
+ "execution_count": 2
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7561b274",
+ "metadata": {},
+ "source": [
+ "### Define Paths and Runtime Settings\n",
+ "\n",
+ "The ontology paths point to the source ontology, target ontology, and reference alignments of Mouse-Human anatomy dataset. The model paths define the retrieval, reranking, and LLM components used later in the notebook.\n",
+ "\n",
+ "The runtime device is selected once and reused across the aligners. The LLM path can be changed depending on the available hardware, but the detailed LLM and RAG settings are configured in the next step."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "28deb26b",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-07-09T13:54:22.179488200Z",
+ "start_time": "2026-07-09T13:54:22.139339500Z"
+ }
+ },
+ "source": [
+ "# Define paths for the ontology alignment task\n",
+ "source_ontology_path = \"../assets/mouse-human/source.xml\"\n",
+ "target_ontology_path = \"../assets/mouse-human/target.xml\"\n",
+ "reference_matching_path = \"../assets/mouse-human/reference.xml\"\n",
+ "\n",
+ "# Select the runtime device\n",
+ "# CUDA is used when available; otherwise, the notebook runs on CPU.\n",
+ "device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n",
+ "\n",
+ "# Define model paths\n",
+ "# The LLM is intentionally small for a runnable full-dataset example.\n",
+ "ir_model_path = \"all-MiniLM-L6-v2\"\n",
+ "cross_encoder_model_path = \"cross-encoder/ms-marco-MiniLM-L6-v2\"\n",
+ "llm_model_path = \"Qwen/Qwen2.5-0.5B-Instruct\"\n",
+ "\n",
+ "print(\"Device:\", device)\n",
+ "print(\"IR model:\", ir_model_path)\n",
+ "print(\"Reranker model:\", cross_encoder_model_path)\n",
+ "print(\"LLM model:\", llm_model_path)"
+ ],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Device: cpu\n",
+ "IR model: all-MiniLM-L6-v2\n",
+ "Reranker model: cross-encoder/ms-marco-MiniLM-L6-v2\n",
+ "LLM model: Qwen/Qwen2.5-0.5B-Instruct\n"
+ ]
+ }
+ ],
+ "execution_count": 3
+ },
+ {
+ "cell_type": "markdown",
+ "id": "e037a69d",
+ "metadata": {},
+ "source": [
+ "### Configure LLM and RAG Components\n",
+ "\n",
+ "The RAG-based aligners use both retrieval and language-model generation. In this step, we define the shared configuration values for the retriever, the LLM, and the label mapper used by the LLM-based outputs.\n",
+ "\n",
+ "These settings can be adjusted depending on the available hardware. For local runs, smaller LLMs and lower retrieval values keep the notebook easier to execute, while larger models or higher retrieval values can be used for fuller experiments."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "27dd2682",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-07-09T13:54:25.242786600Z",
+ "start_time": "2026-07-09T13:54:25.218866700Z"
+ }
+ },
+ "source": [
+ "# Define a label mapper for LLM outputs\n",
+ "# The mapper maps generated answer text into yes/no alignment labels.\n",
+ "mapper = TFIDFLabelMapper(\n",
+ " classifier=LogisticRegression(),\n",
+ " ngram_range=(1, 1),\n",
+ " label_dict={\n",
+ " \"yes\": [\"yes\", \"correct\", \"true\", \"same\", \"equivalent\", \"valid\"],\n",
+ " \"no\": [\"no\", \"incorrect\", \"false\", \"different\", \"not same\", \"invalid\"],\n",
+ " },\n",
+ ")\n",
+ "\n",
+ "# Define retrieval configuration for RAG aligners\n",
+ "retriever_config = {\n",
+ " \"device\": device,\n",
+ " \"top_k\": 5,\n",
+ " \"threshold\": 0.1,\n",
+ "}\n",
+ "\n",
+ "# Define LLM configuration for RAG aligners\n",
+ "llm_config = {\n",
+ " \"device\": device,\n",
+ " \"max_length\": 256,\n",
+ " \"max_new_tokens\": 10,\n",
+ " \"batch_size\": 1,\n",
+ " \"answer_set\": {\n",
+ " \"yes\": [\"yes\", \"correct\", \"true\", \"positive\", \"valid\"],\n",
+ " \"no\": [\"no\", \"incorrect\", \"false\", \"negative\", \"invalid\"],\n",
+ " },\n",
+ "}"
+ ],
+ "outputs": [],
+ "execution_count": 4
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "---\n",
+ "## 2οΈβ£. Dataset Loading\n",
+ "\n",
+ "Before running any aligner, we load the [ontology matching task](https://ontoaligner.readthedocs.io/developerguide/parsers.html). The dataset provides the source ontology, the target ontology, and the reference alignments used for evaluation.\n",
+ "\n",
+ "We also load a graph-based version of the dataset because the graph aligner uses ontology structure rather than only concept text."
+ ],
+ "id": "5c868ccb4deeac51"
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "### Load the Ontology Matching Dataset\n",
+ "\n",
+ "This step loads the standard ontology matching dataset. The resulting dataset is used by the retrieval, reranking, and RAG-based aligners."
+ ],
+ "id": "85d564618646590f"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-07-09T13:54:31.118125700Z",
+ "start_time": "2026-07-09T13:54:29.178632600Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "# Initialize the ontology alignment task\n",
+ "task = MouseHumanOMDataset()\n",
+ "print(\"Test Task:\", task)\n",
+ "\n",
+ "# Collect the ontology dataset\n",
+ "dataset = task.collect(\n",
+ " source_ontology_path=source_ontology_path,\n",
+ " target_ontology_path=target_ontology_path,\n",
+ " reference_matching_path=reference_matching_path,\n",
+ ")\n",
+ "\n",
+ "print(\"Dataset keys:\", dataset.keys())\n",
+ "print(\"Reference matchings:\", len(dataset[\"reference\"]))"
+ ],
+ "id": "90064d560f0752d2",
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Test Task: Track: anatomy, Source-Target sets: mouse-human\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "2744it [00:00, 8652.67it/s]\n",
+ "3304it [00:00, 5978.74it/s]\n",
+ "100%|ββββββββββ| 9102/9102 [00:00<00:00, 64871.94it/s]"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Dataset keys: dict_keys(['dataset-info', 'source', 'target', 'reference'])\n",
+ "Reference matchings: 1516\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n"
+ ]
+ }
+ ],
+ "execution_count": 5
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "### Load the Graph Dataset\n",
+ "\n",
+ "The graph dataset prepares the same ontology matching task for graph-based alignment. This allows the graph aligner to use structural information from the ontologies."
+ ],
+ "id": "ddaf56055d0ace62"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-07-09T13:54:39.574790900Z",
+ "start_time": "2026-07-09T13:54:35.706435800Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "# Initialize the graph ontology alignment task\n",
+ "# GraphTripleOMDataset prepares the ontology alignment task for graph-based aligners.\n",
+ "graph_task = GraphTripleOMDataset(ontology_name=\"mouse-human\")\n",
+ "print(\"Graph Task:\", graph_task)\n",
+ "\n",
+ "# Collect the graph dataset\n",
+ "graph_dataset = graph_task.collect(\n",
+ " source_ontology_path=source_ontology_path,\n",
+ " target_ontology_path=target_ontology_path,\n",
+ " reference_matching_path=reference_matching_path,\n",
+ ")\n",
+ "\n",
+ "print(\"Graph dataset keys:\", graph_dataset.keys())"
+ ],
+ "id": "44d8dfc5427f123e",
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Graph Task: Track: GraphTriple, Source-Target sets: mouse-human\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "100%|ββββββββββ| 9102/9102 [00:00<00:00, 62775.31it/s]"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Graph dataset keys: dict_keys(['dataset-info', 'source', 'target', 'reference'])\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n"
+ ]
+ }
+ ],
+ "execution_count": 6
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "---\n",
+ "## 3οΈβ£. Ensemble Construction\n",
+ "\n",
+ "In this section, we create the group-level [ensembles](https://ontoaligner.readthedocs.io/aligner/ensemble_learning.html). Each group focuses on a different alignment strategy.\n",
+ "\n",
+ "The retrieval ensemble captures lexical and semantic similarity. The reranking ensemble refines candidate mappings using a stronger scoring model. The LLM-based ensemble shows how language-model reasoning can be combined with retrieval.\n",
+ "\n",
+ "Each aligner is wrapped with [AlignerPipeline](https://ontoaligner.readthedocs.io/developerguide/pipeline.html), so all groups follow the same execution style. Each ensemble group also defines a [voting strategy](https://ontoaligner.readthedocs.io/aligner/ensemble_learning.html#voting-strategies), which controls how the predictions from its aligners are combined."
+ ],
+ "id": "c1365858e7be31f2"
+ },
+ {
+ "cell_type": "markdown",
+ "id": "aab8a4b4",
+ "metadata": {},
+ "source": [
+ "### Build the Retrieval Ensemble\n",
+ "\n",
+ "We first build the retrieval ensemble. [Retrieval aligners](https://ontoaligner.readthedocs.io/aligner/retriever.html#) are useful because they can quickly generate candidate mappings between source and target concepts.\n",
+ "\n",
+ "This group combines lightweight matching, TF-IDF retrieval, and SBERT retrieval. These aligners provide different lexical and semantic views of the same ontology matching task. Reciprocal rank fusion is used to combine retrieval aligners by rank."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "56a74daf",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-07-09T13:54:48.036962100Z",
+ "start_time": "2026-07-09T13:54:47.904588400Z"
+ }
+ },
+ "source": [
+ "# Define the lightweight fuzzy matching pipeline\n",
+ "lightweight_pipeline = AlignerPipeline(\n",
+ " encoder=ConceptParentLightweightEncoder(),\n",
+ " aligner=SimpleFuzzySMLightweight(fuzzy_sm_threshold=0.2),\n",
+ " om_dataset=dataset,\n",
+ ")\n",
+ "\n",
+ "# Define the TF-IDF retrieval pipeline\n",
+ "tfidf_pipeline = AlignerPipeline(\n",
+ " encoder=ConceptParentLightweightEncoder(),\n",
+ " aligner=TFIDFRetrieval(top_k=5),\n",
+ " om_dataset=dataset,\n",
+ " load_params={\"path\": None},\n",
+ ")\n",
+ "\n",
+ "# Define the SBERT retrieval pipeline\n",
+ "sbert_pipeline = AlignerPipeline(\n",
+ " encoder=ConceptParentLightweightEncoder(),\n",
+ " aligner=SBERTRetrieval(device=device, top_k=5),\n",
+ " om_dataset=dataset,\n",
+ " load_params={\"path\": ir_model_path},\n",
+ ")\n",
+ "\n",
+ "# Combine the retrieval aligners into one ensemble aligner\n",
+ "retrieval_ensemble = EnsembleLearningAligner(\n",
+ " aligners=[\n",
+ " (\"lightweight\", lightweight_pipeline, 1.0),\n",
+ " (\"tfidf\", tfidf_pipeline, 1.0),\n",
+ " (\"sbert\", sbert_pipeline, 1.0),\n",
+ " ],\n",
+ " voting=ReciprocalRankFusionVoting(k=60),\n",
+ ")"
+ ],
+ "outputs": [],
+ "execution_count": 7
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7689b43d",
+ "metadata": {},
+ "source": [
+ "### Build the Reranking Ensemble\n",
+ "\n",
+ "Next, we build the reranking ensemble. [Reranking](https://ontoaligner.readthedocs.io/aligner/retriever.html#reranking) starts with candidate mappings and then applies a stronger relevance model to reorder or filter those candidates.\n",
+ "\n",
+ "In this notebook, reranking is handled directly inside `AlignerPipeline`. This keeps the workflow consistent: the pipeline runs the encoder, aligner, optional reranker, and postprocessor in one place.\n",
+ "\n",
+ "The reranking group includes SBERT-based candidates, TF-IDF-based candidates, and graph-based candidates. Score averaging is used because the reranking scores are normalized."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "29527f6a",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-07-09T13:54:52.178533500Z",
+ "start_time": "2026-07-09T13:54:52.075793800Z"
+ }
+ },
+ "source": [
+ "# Define the SBERT reranking pipeline\n",
+ "# SBERT generates candidates, CrossEncoderReranking reranks them, and retriever_postprocessor flattens the output.\n",
+ "sbert_reranking_pipeline = AlignerPipeline(\n",
+ " encoder=ConceptParentLightweightEncoder(),\n",
+ " aligner=SBERTRetrieval(device=device, top_k=10),\n",
+ " reranker=CrossEncoderReranking(\n",
+ " device=device,\n",
+ " top_k=5,\n",
+ " normalize_score=\"sigmoid\",\n",
+ " ),\n",
+ " om_dataset=dataset,\n",
+ " load_params={\"path\": ir_model_path},\n",
+ " reranker_load_params={\"path\": cross_encoder_model_path},\n",
+ " postprocessor=retriever_postprocessor,\n",
+ " postprocessor_params={\"threshold\": 0.5},\n",
+ ")\n",
+ "\n",
+ "# Define the TF-IDF reranking pipeline\n",
+ "# TF-IDF provides lexical candidates before the same CrossEncoder reranking step.\n",
+ "tfidf_reranking_pipeline = AlignerPipeline(\n",
+ " encoder=ConceptParentLightweightEncoder(),\n",
+ " aligner=TFIDFRetrieval(top_k=10),\n",
+ " reranker=CrossEncoderReranking(\n",
+ " device=device,\n",
+ " top_k=5,\n",
+ " normalize_score=\"sigmoid\",\n",
+ " ),\n",
+ " om_dataset=dataset,\n",
+ " load_params={\"path\": None},\n",
+ " reranker_load_params={\"path\": cross_encoder_model_path},\n",
+ " postprocessor=retriever_postprocessor,\n",
+ " postprocessor_params={\"threshold\": 0.5},\n",
+ ")\n",
+ "\n",
+ "# Define the graph reranking pipeline\n",
+ "# The graph aligner is configured with retriever=True so it returns grouped candidates.\n",
+ "# The reranker_encoder prepares text representations for CrossEncoderReranking.\n",
+ "graph_reranking_pipeline = AlignerPipeline(\n",
+ " encoder=GraphTripleEncoder(),\n",
+ " aligner=ConvEAligner(\n",
+ " model=\"ConvE\",\n",
+ " device=device,\n",
+ " retriever=True,\n",
+ " top_k=10,\n",
+ " embedding_dim=32,\n",
+ " num_epochs=1,\n",
+ " train_batch_size=32,\n",
+ " eval_batch_size=32,\n",
+ " num_negs_per_pos=1,\n",
+ " random_seed=42,\n",
+ " ),\n",
+ " reranker=CrossEncoderReranking(\n",
+ " device=device,\n",
+ " top_k=5,\n",
+ " normalize_score=\"sigmoid\",\n",
+ " ),\n",
+ " reranker_encoder=ConceptParentLightweightEncoder(),\n",
+ " reranker_om_dataset=dataset,\n",
+ " om_dataset=graph_dataset,\n",
+ " reranker_load_params={\"path\": cross_encoder_model_path},\n",
+ " postprocessor=retriever_postprocessor,\n",
+ " postprocessor_params={\"threshold\": 0.5},\n",
+ ")\n",
+ "\n",
+ "# Combine the reranking aligners into one ensemble aligner\n",
+ "# All aligners use the same reranking model and sigmoid score normalization.\n",
+ "reranking_ensemble = EnsembleLearningAligner(\n",
+ " aligners=[\n",
+ " (\"sbert_reranking\", sbert_reranking_pipeline, 1.0),\n",
+ " (\"tfidf_reranking\", tfidf_reranking_pipeline, 1.0),\n",
+ " (\"graph_reranking\", graph_reranking_pipeline, 1.0),\n",
+ " ],\n",
+ " voting=ScoreAverageVoting(),\n",
+ ")"
+ ],
+ "outputs": [],
+ "execution_count": 8
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "### Build the LLM-Based Ensemble\n",
+ "\n",
+ "The LLM-based ensemble shows how language models can be used for ontology alignment.\n",
+ "\n",
+ "This group includes a direct [LLM aligner](https://ontoaligner.readthedocs.io/aligner/llm.html) with `AutoModelDecoderLLM`, along with [RAG](https://ontoaligner.readthedocs.io/aligner/rag.html) and [FS-RAG](https://ontoaligner.readthedocs.io/aligner/rag.html#fewshot-rag-aligner) aligners. The direct LLM aligner compares source and target concepts using generation, while RAG and FS-RAG first retrieve candidate targets and then use the LLM to support the alignment decision. Reciprocal rank fusion is used to combine LLM-based aligners by rank."
+ ],
+ "id": "caddc34fec971284"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-07-09T13:54:56.678691500Z",
+ "start_time": "2026-07-09T13:54:56.579371500Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "# Define the direct decoder LLM pipeline\n",
+ "# The pipeline uses ConceptLLMEncoder and ConceptLLMDataset to generate LLM prompts.\n",
+ "llm_pipeline = AlignerPipeline(\n",
+ " encoder=ConceptLLMEncoder(),\n",
+ " aligner=AutoModelDecoderLLM(\n",
+ " device=device,\n",
+ " max_length=256,\n",
+ " max_new_tokens=10,\n",
+ " batch_size=1,\n",
+ " ),\n",
+ " om_dataset=dataset,\n",
+ " llm_dataset_class=ConceptLLMDataset,\n",
+ " load_params={\"path\": llm_model_path},\n",
+ " postprocessor=llm_postprocessor,\n",
+ " postprocessor_params={\n",
+ " \"mapper\": mapper,\n",
+ " \"interested_class\": \"yes\",\n",
+ " },\n",
+ ")\n",
+ "\n",
+ "# Define the RAG pipeline\n",
+ "# RAG first retrieves candidate targets and then uses an LLM for answer generation.\n",
+ "rag_pipeline = AlignerPipeline(\n",
+ " encoder=ConceptParentRAGEncoder(),\n",
+ " aligner=MistralLLMBERTRetrieverRAG(\n",
+ " retriever_config=retriever_config,\n",
+ " llm_config=llm_config,\n",
+ " ),\n",
+ " om_dataset=dataset,\n",
+ " load_params={\n",
+ " \"llm_path\": llm_model_path,\n",
+ " \"ir_path\": ir_model_path,\n",
+ " },\n",
+ " postprocessor=rag_heuristic_postprocessor,\n",
+ " postprocessor_params={\n",
+ " \"topk_confidence_ratio\": 3,\n",
+ " \"topk_confidence_score\": 3,\n",
+ " },\n",
+ ")\n",
+ "\n",
+ "# Define the few-shot RAG pipeline\n",
+ "# Few-shot RAG adds reference-based examples during prompt construction.\n",
+ "fsrag_pipeline = AlignerPipeline(\n",
+ " encoder=ConceptParentFewShotEncoder(),\n",
+ " aligner=MistralLLMBERTRetrieverFSRAG(\n",
+ " positive_ratio=1.0,\n",
+ " n_shots=1,\n",
+ " retriever_config=retriever_config,\n",
+ " llm_config=llm_config,\n",
+ " ),\n",
+ " om_dataset=dataset,\n",
+ " load_params={\n",
+ " \"llm_path\": llm_model_path,\n",
+ " \"ir_path\": ir_model_path,\n",
+ " },\n",
+ " postprocessor=rag_heuristic_postprocessor,\n",
+ " postprocessor_params={\n",
+ " \"topk_confidence_ratio\": 3,\n",
+ " \"topk_confidence_score\": 3,\n",
+ " },\n",
+ " include_reference=True,\n",
+ ")\n",
+ "\n",
+ "# Combine the LLM aligners into one ensemble aligner\n",
+ "llm_ensemble = EnsembleLearningAligner(\n",
+ " aligners=[\n",
+ " (\"llm\", llm_pipeline, 1.0),\n",
+ " (\"rag\", rag_pipeline, 1.0),\n",
+ " (\"fsrag\", fsrag_pipeline, 1.0),\n",
+ " ],\n",
+ " voting=ReciprocalRankFusionVoting(k=60),\n",
+ ")"
+ ],
+ "id": "d294c56e486fe011",
+ "outputs": [],
+ "execution_count": 9
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "---\n",
+ "## 4οΈβ£. Nested Ensemble Execution\n",
+ "\n",
+ "After creating the group-level ensembles, we connect them into one final nested ensemble.\n",
+ "\n",
+ "At this stage, the retrieval, reranking, and LLM-based ensembles are treated as aligners inside the final [EnsembleLearningAligner](https://ontoaligner.readthedocs.io/aligner/ensemble_learning.html). When the final nested ensemble is executed, it runs each group-level ensemble, collects their predictions, and combines them into one ranked alignment output."
+ ],
+ "id": "32aa6c3a37f6e024"
+ },
+ {
+ "cell_type": "markdown",
+ "id": "fc730140",
+ "metadata": {},
+ "source": [
+ "### Build the Final Nested Ensemble\n",
+ "\n",
+ "This cell builds the final `EnsembleLearningAligner` using the group-level ensembles as inputs. When `generate()` is called, the nested ensemble runs the retrieval, reranking, and LLM-based groups through this final ensemble structure.\n",
+ "\n",
+ "Reciprocal rank fusion is used at the final level because the different groups may use different scoring semantics."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "1cd91ffb",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-07-08T15:41:50.581783600Z",
+ "start_time": "2026-07-08T15:31:52.613566200Z"
+ }
+ },
+ "source": [
+ "# Initialize the final nested ensemble aligner\n",
+ "# Each group-level ensemble behaves like an aligner because it exposes generate().\n",
+ "nested_ensemble = EnsembleLearningAligner(\n",
+ " aligners=[\n",
+ " (\"retrieval_ensemble\", retrieval_ensemble, 1.0),\n",
+ " (\"reranking_ensemble\", reranking_ensemble, 1.0),\n",
+ " ],\n",
+ " voting=ReciprocalRankFusionVoting(k=60),\n",
+ ")\n",
+ "\n",
+ "# Optional: Full nested ensemble with llm_ensemble\n",
+ "# Uncomment this version for Colab, GPU, or overnight execution.\n",
+ "# nested_ensemble = EnsembleLearningAligner(\n",
+ "# aligners=[\n",
+ "# (\"retrieval_ensemble\", retrieval_ensemble, 1.0),\n",
+ "# (\"reranking_ensemble\", reranking_ensemble, 1.0),\n",
+ "# (\"llm_ensemble\", llm_ensemble, 1.0),\n",
+ "# ],\n",
+ "# voting=ReciprocalRankFusionVoting(k=60),\n",
+ "# )\n",
+ "\n",
+ "# Generate final nested ensemble predictions\n",
+ "final_matchings = nested_ensemble.generate()\n",
+ "\n",
+ "# Print a small sample of predictions\n",
+ "print(\"Final nested ensemble matchings:\", len(final_matchings))\n",
+ "print(json.dumps(final_matchings[:20], indent=4, ensure_ascii=False))"
+ ],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "Running aligner: retrieval_ensemble\n",
+ "\n",
+ "Running aligner: lightweight\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "100%|ββββββββββ| 2737/2737 [00:01<00:00, 1404.64it/s]\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Finished aligner: lightweight\n",
+ "Predictions before flattening: 2737\n",
+ "Predictions after flattening: 2737\n",
+ "\n",
+ "Running aligner: tfidf\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "2737it [00:14, 192.37it/s]\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Finished aligner: tfidf\n",
+ "Predictions before flattening: 2737\n",
+ "Predictions after flattening: 13685\n",
+ "\n",
+ "Running aligner: sbert\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Batches: 100%|ββββββββββ| 207/207 [00:10<00:00, 19.13it/s]\n",
+ "Batches: 100%|ββββββββββ| 172/172 [00:07<00:00, 23.25it/s]\n",
+ "2737it [00:00, 12378.02it/s]\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Finished aligner: sbert\n",
+ "Predictions before flattening: 2737\n",
+ "Predictions after flattening: 13685\n",
+ "Finished aligner: retrieval_ensemble\n",
+ "Predictions before flattening: 21914\n",
+ "Predictions after flattening: 21914\n",
+ "\n",
+ "Running aligner: reranking_ensemble\n",
+ "\n",
+ "Running aligner: sbert_reranking\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "Batches: 100%|ββββββββββ| 207/207 [00:10<00:00, 19.15it/s]\n",
+ "Batches: 100%|ββββββββββ| 172/172 [00:08<00:00, 20.70it/s]\n",
+ "2737it [00:00, 9533.69it/s]\n",
+ "100%|ββββββββββ| 2737/2737 [02:40<00:00, 17.03it/s]\n",
+ "100%|ββββββββββ| 2737/2737 [00:00<00:00, 343193.13it/s]\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Finished aligner: sbert_reranking\n",
+ "Predictions before flattening: 5300\n",
+ "Predictions after flattening: 5300\n",
+ "\n",
+ "Running aligner: tfidf_reranking\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "2737it [00:09, 274.21it/s]\n",
+ "100%|ββββββββββ| 2737/2737 [02:22<00:00, 19.15it/s]\n",
+ "100%|ββββββββββ| 2737/2737 [00:00<00:00, 384789.50it/s]\n"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Finished aligner: tfidf_reranking\n",
+ "Predictions before flattening: 4872\n",
+ "Predictions after flattening: 4872\n",
+ "\n",
+ "Running aligner: graph_reranking\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "INFO:pykeen.triples.triples_factory:Creating inverse triples.\n",
+ "C:\\Users\\AlluV\\Desktop\\1\\OntoAligner-dev-test\\.venv\\lib\\site-packages\\torch\\utils\\data\\dataloader.py:666: UserWarning: 'pin_memory' argument is set as true but no accelerator is found, then device pinned memory won't be used.\n",
+ " warnings.warn(warn_msg)\n",
+ "Training epochs on cpu: 0%| | 0/1 [00:00, ?epoch/s]INFO:pykeen.triples.triples_factory:Creating inverse triples.\n",
+ "INFO:pykeen.training.training_loop:Dropping last (incomplete) batch each epoch (1/1105 (0.09%) batches).\n",
+ "\n",
+ "Training batches on cpu: 0%| | 0.00/1.10k [00:00, ?batch/s]\u001B[A\n",
+ "Training batches on cpu: 1%| | 7.00/1.10k [00:00<00:17, 63.8batch/s]\u001B[A\n",
+ "Training batches on cpu: 1%|β | 15.0/1.10k [00:00<00:15, 72.4batch/s]\u001B[A\n",
+ "Training batches on cpu: 2%|β | 23.0/1.10k [00:00<00:14, 74.8batch/s]\u001B[A\n",
+ "Training batches on cpu: 3%|β | 31.0/1.10k [00:00<00:14, 75.3batch/s]\u001B[A\n",
+ "Training batches on cpu: 4%|β | 40.0/1.10k [00:00<00:13, 79.9batch/s]\u001B[A\n",
+ "Training batches on cpu: 5%|β | 50.0/1.10k [00:00<00:12, 83.8batch/s]\u001B[A\n",
+ "Training batches on cpu: 5%|β | 59.0/1.10k [00:00<00:13, 80.4batch/s]\u001B[A\n",
+ "Training batches on cpu: 6%|β | 68.0/1.10k [00:00<00:13, 76.7batch/s]\u001B[A\n",
+ "Training batches on cpu: 7%|β | 76.0/1.10k [00:00<00:13, 74.6batch/s]\u001B[A\n",
+ "Training batches on cpu: 8%|β | 84.0/1.10k [00:01<00:13, 73.6batch/s]\u001B[A\n",
+ "Training batches on cpu: 8%|β | 92.0/1.10k [00:01<00:13, 73.0batch/s]\u001B[A\n",
+ "Training batches on cpu: 9%|β | 100/1.10k [00:01<00:15, 66.8batch/s] \u001B[A\n",
+ "Training batches on cpu: 10%|β | 107/1.10k [00:01<00:17, 56.7batch/s]\u001B[A\n",
+ "Training batches on cpu: 10%|β | 113/1.10k [00:01<00:18, 53.3batch/s]\u001B[A\n",
+ "Training batches on cpu: 11%|β | 120/1.10k [00:01<00:17, 55.6batch/s]\u001B[A\n",
+ "Training batches on cpu: 11%|ββ | 127/1.10k [00:01<00:16, 57.8batch/s]\u001B[A\n",
+ "Training batches on cpu: 12%|ββ | 136/1.10k [00:02<00:15, 64.3batch/s]\u001B[A\n",
+ "Training batches on cpu: 13%|ββ | 146/1.10k [00:02<00:13, 72.0batch/s]\u001B[A\n",
+ "Training batches on cpu: 14%|ββ | 155/1.10k [00:02<00:12, 76.7batch/s]\u001B[A\n",
+ "Training batches on cpu: 15%|ββ | 165/1.10k [00:02<00:11, 81.4batch/s]\u001B[A\n",
+ "Training batches on cpu: 16%|ββ | 174/1.10k [00:02<00:11, 82.9batch/s]\u001B[A\n",
+ "Training batches on cpu: 17%|ββ | 184/1.10k [00:02<00:10, 85.8batch/s]\u001B[A\n",
+ "Training batches on cpu: 17%|ββ | 193/1.10k [00:02<00:10, 86.0batch/s]\u001B[A\n",
+ "Training batches on cpu: 18%|ββ | 203/1.10k [00:02<00:10, 89.0batch/s]\u001B[A\n",
+ "Training batches on cpu: 19%|ββ | 212/1.10k [00:02<00:10, 88.7batch/s]\u001B[A\n",
+ "Training batches on cpu: 20%|ββ | 222/1.10k [00:02<00:09, 90.1batch/s]\u001B[A\n",
+ "Training batches on cpu: 21%|ββ | 232/1.10k [00:03<00:09, 91.1batch/s]\u001B[A\n",
+ "Training batches on cpu: 22%|βββ | 242/1.10k [00:03<00:09, 87.9batch/s]\u001B[A\n",
+ "Training batches on cpu: 23%|βββ | 252/1.10k [00:03<00:09, 89.9batch/s]\u001B[A\n",
+ "Training batches on cpu: 24%|βββ | 262/1.10k [00:03<00:09, 89.5batch/s]\u001B[A\n",
+ "Training batches on cpu: 25%|βββ | 272/1.10k [00:03<00:09, 89.9batch/s]\u001B[A\n",
+ "Training batches on cpu: 26%|βββ | 282/1.10k [00:03<00:08, 92.2batch/s]\u001B[A\n",
+ "Training batches on cpu: 26%|βββ | 292/1.10k [00:03<00:08, 93.1batch/s]\u001B[A\n",
+ "Training batches on cpu: 27%|βββ | 302/1.10k [00:03<00:08, 93.7batch/s]\u001B[A\n",
+ "Training batches on cpu: 28%|βββ | 312/1.10k [00:03<00:08, 91.8batch/s]\u001B[A\n",
+ "Training batches on cpu: 29%|βββ | 322/1.10k [00:04<00:08, 94.0batch/s]\u001B[A\n",
+ "Training batches on cpu: 30%|βββ | 332/1.10k [00:04<00:08, 93.3batch/s]\u001B[A\n",
+ "Training batches on cpu: 31%|βββ | 342/1.10k [00:04<00:08, 93.4batch/s]\u001B[A\n",
+ "Training batches on cpu: 32%|ββββ | 352/1.10k [00:04<00:08, 90.9batch/s]\u001B[A\n",
+ "Training batches on cpu: 33%|ββββ | 362/1.10k [00:04<00:08, 92.2batch/s]\u001B[A\n",
+ "Training batches on cpu: 34%|ββββ | 372/1.10k [00:04<00:07, 93.1batch/s]\u001B[A\n",
+ "Training batches on cpu: 35%|ββββ | 383/1.10k [00:04<00:07, 96.0batch/s]\u001B[A\n",
+ "Training batches on cpu: 36%|ββββ | 393/1.10k [00:04<00:07, 96.6batch/s]\u001B[A\n",
+ "Training batches on cpu: 36%|ββββ | 403/1.10k [00:04<00:07, 94.2batch/s]\u001B[A\n",
+ "Training batches on cpu: 37%|ββββ | 413/1.10k [00:05<00:07, 93.3batch/s]\u001B[A\n",
+ "Training batches on cpu: 38%|ββββ | 423/1.10k [00:05<00:07, 90.9batch/s]\u001B[A\n",
+ "Training batches on cpu: 39%|ββββ | 434/1.10k [00:05<00:07, 94.0batch/s]\u001B[A\n",
+ "Training batches on cpu: 40%|ββββ | 444/1.10k [00:05<00:07, 93.8batch/s]\u001B[A\n",
+ "Training batches on cpu: 41%|ββββ | 454/1.10k [00:05<00:07, 90.0batch/s]\u001B[A\n",
+ "Training batches on cpu: 42%|βββββ | 464/1.10k [00:05<00:07, 91.1batch/s]\u001B[A\n",
+ "Training batches on cpu: 43%|βββββ | 474/1.10k [00:05<00:06, 91.8batch/s]\u001B[A\n",
+ "Training batches on cpu: 44%|βββββ | 484/1.10k [00:05<00:06, 94.1batch/s]\u001B[A\n",
+ "Training batches on cpu: 45%|βββββ | 494/1.10k [00:05<00:06, 91.2batch/s]\u001B[A\n",
+ "Training batches on cpu: 46%|βββββ | 505/1.10k [00:06<00:06, 94.8batch/s]\u001B[A\n",
+ "Training batches on cpu: 47%|βββββ | 515/1.10k [00:06<00:06, 94.9batch/s]\u001B[A\n",
+ "Training batches on cpu: 48%|βββββ | 525/1.10k [00:06<00:06, 95.4batch/s]\u001B[A\n",
+ "Training batches on cpu: 48%|βββββ | 535/1.10k [00:06<00:05, 95.3batch/s]\u001B[A\n",
+ "Training batches on cpu: 49%|βββββ | 545/1.10k [00:06<00:06, 92.6batch/s]\u001B[A\n",
+ "Training batches on cpu: 50%|βββββ | 555/1.10k [00:06<00:05, 93.1batch/s]\u001B[A\n",
+ "Training batches on cpu: 51%|βββββ | 566/1.10k [00:06<00:05, 96.8batch/s]\u001B[A\n",
+ "Training batches on cpu: 52%|ββββββ | 576/1.10k [00:06<00:05, 95.0batch/s]\u001B[A\n",
+ "Training batches on cpu: 53%|ββββββ | 586/1.10k [00:06<00:05, 95.5batch/s]\u001B[A\n",
+ "Training batches on cpu: 54%|ββββββ | 596/1.10k [00:06<00:05, 92.8batch/s]\u001B[A\n",
+ "Training batches on cpu: 55%|ββββββ | 606/1.10k [00:07<00:05, 94.3batch/s]\u001B[A\n",
+ "Training batches on cpu: 56%|ββββββ | 617/1.10k [00:07<00:05, 97.1batch/s]\u001B[A\n",
+ "Training batches on cpu: 57%|ββββββ | 628/1.10k [00:07<00:04, 98.3batch/s]\u001B[A\n",
+ "Training batches on cpu: 58%|ββββββ | 638/1.10k [00:07<00:04, 98.2batch/s]\u001B[A\n",
+ "Training batches on cpu: 59%|ββββββ | 648/1.10k [00:07<00:04, 96.2batch/s]\u001B[A\n",
+ "Training batches on cpu: 60%|ββββββ | 658/1.10k [00:07<00:04, 95.9batch/s]\u001B[A\n",
+ "Training batches on cpu: 60%|ββββββ | 668/1.10k [00:07<00:04, 95.5batch/s]\u001B[A\n",
+ "Training batches on cpu: 61%|βββββββ | 678/1.10k [00:07<00:04, 96.2batch/s]\u001B[A\n",
+ "Training batches on cpu: 62%|βββββββ | 688/1.10k [00:07<00:04, 93.2batch/s]\u001B[A\n",
+ "Training batches on cpu: 63%|βββββββ | 698/1.10k [00:08<00:04, 93.9batch/s]\u001B[A\n",
+ "Training batches on cpu: 64%|βββββββ | 708/1.10k [00:08<00:04, 93.5batch/s]\u001B[A\n",
+ "Training batches on cpu: 65%|βββββββ | 719/1.10k [00:08<00:03, 96.9batch/s]\u001B[A\n",
+ "Training batches on cpu: 66%|βββββββ | 729/1.10k [00:08<00:03, 96.1batch/s]\u001B[A\n",
+ "Training batches on cpu: 67%|βββββββ | 739/1.10k [00:08<00:03, 94.2batch/s]\u001B[A\n",
+ "Training batches on cpu: 68%|βββββββ | 749/1.10k [00:08<00:03, 93.7batch/s]\u001B[A\n",
+ "Training batches on cpu: 69%|βββββββ | 760/1.10k [00:08<00:03, 96.4batch/s]\u001B[A\n",
+ "Training batches on cpu: 70%|βββββββ | 770/1.10k [00:08<00:03, 97.2batch/s]\u001B[A\n",
+ "Training batches on cpu: 71%|βββββββ | 781/1.10k [00:08<00:03, 99.2batch/s]\u001B[A\n",
+ "Training batches on cpu: 72%|ββββββββ | 791/1.10k [00:08<00:03, 98.4batch/s]\u001B[A\n",
+ "Training batches on cpu: 72%|ββββββββ | 801/1.10k [00:09<00:03, 97.5batch/s]\u001B[A\n",
+ "Training batches on cpu: 73%|ββββββββ | 811/1.10k [00:09<00:03, 96.6batch/s]\u001B[A\n",
+ "Training batches on cpu: 74%|ββββββββ | 821/1.10k [00:09<00:03, 94.5batch/s]\u001B[A\n",
+ "Training batches on cpu: 75%|ββββββββ | 831/1.10k [00:09<00:02, 93.6batch/s]\u001B[A\n",
+ "Training batches on cpu: 76%|ββββββββ | 841/1.10k [00:09<00:02, 92.8batch/s]\u001B[A\n",
+ "Training batches on cpu: 77%|ββββββββ | 851/1.10k [00:09<00:02, 92.2batch/s]\u001B[A\n",
+ "Training batches on cpu: 78%|ββββββββ | 861/1.10k [00:09<00:02, 93.2batch/s]\u001B[A\n",
+ "Training batches on cpu: 79%|ββββββββ | 871/1.10k [00:09<00:02, 92.2batch/s]\u001B[A\n",
+ "Training batches on cpu: 80%|ββββββββ | 881/1.10k [00:09<00:02, 92.2batch/s]\u001B[A\n",
+ "Training batches on cpu: 81%|ββββββββ | 891/1.10k [00:10<00:02, 93.6batch/s]\u001B[A\n",
+ "Training batches on cpu: 82%|βββββββββ | 902/1.10k [00:10<00:02, 95.8batch/s]\u001B[A\n",
+ "Training batches on cpu: 83%|βββββββββ | 912/1.10k [00:10<00:02, 95.3batch/s]\u001B[A\n",
+ "Training batches on cpu: 83%|βββββββββ | 922/1.10k [00:10<00:01, 95.5batch/s]\u001B[A\n",
+ "Training batches on cpu: 84%|βββββββββ | 932/1.10k [00:10<00:01, 93.0batch/s]\u001B[A\n",
+ "Training batches on cpu: 85%|βββββββββ | 942/1.10k [00:10<00:01, 91.4batch/s]\u001B[A\n",
+ "Training batches on cpu: 86%|βββββββββ | 952/1.10k [00:10<00:01, 91.7batch/s]\u001B[A\n",
+ "Training batches on cpu: 87%|βββββββββ | 962/1.10k [00:10<00:01, 94.1batch/s]\u001B[A\n",
+ "Training batches on cpu: 88%|βββββββββ | 972/1.10k [00:10<00:01, 93.1batch/s]\u001B[A\n",
+ "Training batches on cpu: 89%|βββββββββ | 982/1.10k [00:11<00:01, 93.8batch/s]\u001B[A\n",
+ "Training batches on cpu: 90%|βββββββββ | 992/1.10k [00:11<00:01, 93.2batch/s]\u001B[A\n",
+ "Training batches on cpu: 91%|βββββββββ | 1.00k/1.10k [00:11<00:01, 94.5batch/s]\u001B[A\n",
+ "Training batches on cpu: 92%|ββββββββββ| 1.01k/1.10k [00:11<00:00, 93.1batch/s]\u001B[A\n",
+ "Training batches on cpu: 92%|ββββββββββ| 1.02k/1.10k [00:11<00:00, 92.9batch/s]\u001B[A\n",
+ "Training batches on cpu: 93%|ββββββββββ| 1.03k/1.10k [00:11<00:00, 95.1batch/s]\u001B[A\n",
+ "Training batches on cpu: 94%|ββββββββββ| 1.04k/1.10k [00:11<00:00, 94.9batch/s]\u001B[A\n",
+ "Training batches on cpu: 95%|ββββββββββ| 1.05k/1.10k [00:11<00:00, 96.7batch/s]\u001B[A\n",
+ "Training batches on cpu: 96%|ββββββββββ| 1.06k/1.10k [00:11<00:00, 97.4batch/s]\u001B[A\n",
+ "Training batches on cpu: 97%|ββββββββββ| 1.07k/1.10k [00:12<00:00, 94.2batch/s]\u001B[A\n",
+ "Training batches on cpu: 98%|ββββββββββ| 1.08k/1.10k [00:12<00:00, 95.0batch/s]\u001B[A\n",
+ "Training batches on cpu: 99%|ββββββββββ| 1.09k/1.10k [00:12<00:00, 94.4batch/s]\u001B[A\n",
+ "Training batches on cpu: 100%|ββββββββββ| 1.10k/1.10k [00:12<00:00, 93.5batch/s]\u001B[A\n",
+ "Training epochs on cpu: 100%|ββββββββββ| 1/1 [00:12<00:00, 12.84s/epoch, loss=0.82, prev_loss=nan]\n",
+ "WARNING:pykeen.pipeline.api:Found evaluation_factory.create_inverse_triples=True which is ignored for evaluation factories. The model itself determines whether inverse relations are used in head prediction. Here, the model was created with training.create_inverse_triples=True\n",
+ "Evaluating on cpu: 0%| | 0.00/17.7k [00:00, ?triple/s]WARNING:torch_max_mem.api:Encountered tensors on device_types={'cpu'} while only ['cuda'] are considered safe for automatic memory utilization maximization. This may lead to undocumented crashes (but can be safe, too).\n",
+ "Evaluating on cpu: 100%|ββββββββββ| 17.7k/17.7k [00:24<00:00, 725triple/s] \n",
+ "INFO:pykeen.evaluation.evaluator:Evaluation took 24.63s seconds\n",
+ "100%|ββββββββββ| 2744/2744 [03:02<00:00, 15.06it/s]\n",
+ "100%|ββββββββββ| 2744/2744 [00:00<00:00, 392536.50it/s]"
+ ]
+ },
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Finished aligner: graph_reranking\n",
+ "Predictions before flattening: 911\n",
+ "Predictions after flattening: 911\n",
+ "Finished aligner: reranking_ensemble\n",
+ "Predictions before flattening: 5744\n",
+ "Predictions after flattening: 5744\n",
+ "Final nested ensemble matchings: 22620\n",
+ "[\n",
+ " {\n",
+ " \"source\": \"http://mouse.owl#MA_0001367\",\n",
+ " \"target\": \"http://human.owl#NCI_C52793\",\n",
+ " \"score\": 0.02900988017658188\n",
+ " },\n",
+ " {\n",
+ " \"source\": \"http://mouse.owl#MA_0000752\",\n",
+ " \"target\": \"http://human.owl#NCI_C49596\",\n",
+ " \"score\": 0.026190476190476188\n",
+ " },\n",
+ " {\n",
+ " \"source\": \"http://mouse.owl#MA_0000137\",\n",
+ " \"target\": \"http://human.owl#NCI_C12771\",\n",
+ " \"score\": 0.02574441687344913\n",
+ " },\n",
+ " {\n",
+ " \"source\": \"http://mouse.owl#MA_0001368\",\n",
+ " \"target\": \"http://human.owl#NCI_C52792\",\n",
+ " \"score\": 0.02548701298701299\n",
+ " },\n",
+ " {\n",
+ " \"source\": \"http://mouse.owl#MA_0001364\",\n",
+ " \"target\": \"http://human.owl#NCI_C52796\",\n",
+ " \"score\": 0.025481764612199396\n",
+ " },\n",
+ " {\n",
+ " \"source\": \"http://mouse.owl#MA_0001365\",\n",
+ " \"target\": \"http://human.owl#NCI_C52795\",\n",
+ " \"score\": 0.02402745995423341\n",
+ " },\n",
+ " {\n",
+ " \"source\": \"http://mouse.owl#MA_0001366\",\n",
+ " \"target\": \"http://human.owl#NCI_C52794\",\n",
+ " \"score\": 0.023064687168610817\n",
+ " },\n",
+ " {\n",
+ " \"source\": \"http://mouse.owl#MA_0002439\",\n",
+ " \"target\": \"http://human.owl#NCI_C13050\",\n",
+ " \"score\": 0.022737556561085975\n",
+ " },\n",
+ " {\n",
+ " \"source\": \"http://mouse.owl#MA_0002455\",\n",
+ " \"target\": \"http://human.owl#NCI_C12875\",\n",
+ " \"score\": 0.02188995215311005\n",
+ " },\n",
+ " {\n",
+ " \"source\": \"http://mouse.owl#MA_0002242\",\n",
+ " \"target\": \"http://human.owl#NCI_C52700\",\n",
+ " \"score\": 0.021183734298488396\n",
+ " },\n",
+ " {\n",
+ " \"source\": \"http://mouse.owl#MA_0001385\",\n",
+ " \"target\": \"http://human.owl#NCI_C52782\",\n",
+ " \"score\": 0.02111760883690708\n",
+ " },\n",
+ " {\n",
+ " \"source\": \"http://mouse.owl#MA_0001392\",\n",
+ " \"target\": \"http://human.owl#NCI_C52774\",\n",
+ " \"score\": 0.019298245614035085\n",
+ " },\n",
+ " {\n",
+ " \"source\": \"http://mouse.owl#MA_0000728\",\n",
+ " \"target\": \"http://human.owl#NCI_C32973\",\n",
+ " \"score\": 0.018863049095607237\n",
+ " },\n",
+ " {\n",
+ " \"source\": \"http://mouse.owl#MA_0002056\",\n",
+ " \"target\": \"http://human.owl#NCI_C52737\",\n",
+ " \"score\": 0.018595679012345677\n",
+ " },\n",
+ " {\n",
+ " \"source\": \"http://mouse.owl#MA_0002057\",\n",
+ " \"target\": \"http://human.owl#NCI_C52736\",\n",
+ " \"score\": 0.01855779427359491\n",
+ " },\n",
+ " {\n",
+ " \"source\": \"http://mouse.owl#MA_0002438\",\n",
+ " \"target\": \"http://human.owl#NCI_C12436\",\n",
+ " \"score\": 0.018509602525009362\n",
+ " },\n",
+ " {\n",
+ " \"source\": \"http://mouse.owl#MA_0001372\",\n",
+ " \"target\": \"http://human.owl#NCI_C52788\",\n",
+ " \"score\": 0.018439359651328473\n",
+ " },\n",
+ " {\n",
+ " \"source\": \"http://mouse.owl#MA_0001601\",\n",
+ " \"target\": \"http://human.owl#NCI_C49794\",\n",
+ " \"score\": 0.01834356794205637\n",
+ " },\n",
+ " {\n",
+ " \"source\": \"http://mouse.owl#MA_0001602\",\n",
+ " \"target\": \"http://human.owl#NCI_C49584\",\n",
+ " \"score\": 0.01823793490460157\n",
+ " },\n",
+ " {\n",
+ " \"source\": \"http://mouse.owl#MA_0000984\",\n",
+ " \"target\": \"http://human.owl#NCI_C49230\",\n",
+ " \"score\": 0.018189102564102565\n",
+ " }\n",
+ "]\n"
+ ]
+ },
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "\n"
+ ]
+ }
+ ],
+ "execution_count": 9
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "---\n",
+ "## 5οΈβ£. Evaluation and Export\n",
+ "\n",
+ "The final nested ensemble produces a ranked list of candidate alignments. We evaluate these predictions against the reference alignments to measure precision, recall, and F-score.\n",
+ "\n",
+ "The generated alignments are also saved in XML and JSON formats so they can be reused outside the notebook.\n"
+ ],
+ "id": "cb4586137e380b37"
+ },
+ {
+ "cell_type": "markdown",
+ "id": "beeeea8d",
+ "metadata": {},
+ "source": [
+ "### Evaluate the final nested ensemble\n",
+ "\n",
+ "This cell evaluates the final nested ensemble predictions against the Mouse-Human reference matchings."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "id": "c3fa2f99",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-07-08T15:41:50.666787100Z",
+ "start_time": "2026-07-08T15:41:50.583782500Z"
+ }
+ },
+ "source": [
+ "# Evaluate the final predictions\n",
+ "evaluation = metrics.evaluation_report(\n",
+ " predicts=final_matchings,\n",
+ " references=dataset[\"reference\"],\n",
+ ")\n",
+ "\n",
+ "# Print the evaluation report\n",
+ "print(\"\\nNested Ensemble Evaluation Report:\")\n",
+ "print(json.dumps(evaluation, indent=4))"
+ ],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "Nested Ensemble Evaluation Report:\n",
+ "{\n",
+ " \"intersection\": 1443,\n",
+ " \"precision\": 6.379310344827586,\n",
+ " \"recall\": 95.18469656992085,\n",
+ " \"f-score\": 11.957242293669209,\n",
+ " \"predictions-len\": 22620,\n",
+ " \"reference-len\": 1516\n",
+ "}\n"
+ ]
+ }
+ ],
+ "execution_count": 10
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "### Save the alignment outputs\n",
+ "\n",
+ "This cell exports the final nested ensemble predictions in XML and JSON format for later inspection or evaluation."
+ ],
+ "id": "417520081b2e6943"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-07-08T15:41:55.482945200Z",
+ "start_time": "2026-07-08T15:41:50.669792700Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "# Convert final matchings to XML alignment format\n",
+ "xml_str = xmlify.xml_alignment_generator(matchings=final_matchings)\n",
+ "\n",
+ "# Save the XML output\n",
+ "xml_output_file_path = \"nested_ensemble_alignments.xml\"\n",
+ "with open(xml_output_file_path, \"w\", encoding=\"utf-8\") as xml_file:\n",
+ " xml_file.write(xml_str)\n",
+ "\n",
+ "print(f\"Saved XML: {xml_output_file_path}\")\n",
+ "\n",
+ "# Save the JSON output\n",
+ "json_output_file_path = \"nested_ensemble_alignments.json\"\n",
+ "with open(json_output_file_path, \"w\", encoding=\"utf-8\") as json_file:\n",
+ " json.dump(final_matchings, json_file, indent=4, ensure_ascii=False)\n",
+ "\n",
+ "print(f\"Saved JSON: {json_output_file_path}\")\n",
+ "\n",
+ "# Save the evaluation output\n",
+ "evaluation_output_file_path = \"nested_ensemble_evaluation.json\"\n",
+ "with open(evaluation_output_file_path, \"w\", encoding=\"utf-8\") as json_file:\n",
+ " json.dump(evaluation, json_file, indent=4, ensure_ascii=False)\n",
+ "\n",
+ "print(f\"Saved Evaluation: {evaluation_output_file_path}\")\n"
+ ],
+ "id": "c220a8ca6982d6e6",
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Saved XML: nested_ensemble_alignments.xml\n",
+ "Saved JSON: nested_ensemble_alignments.json\n",
+ "Saved Evaluation: nested_ensemble_evaluation.json\n"
+ ]
+ }
+ ],
+ "execution_count": 11
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "### Optional Top-1 Selection\n",
+ "\n",
+ "The nested ensemble returns ranked candidate alignments. In some ontology matching settings, a stricter output is useful, where each source concept keeps only its highest-scoring target candidate.\n",
+ "\n",
+ "This optional step applies top-1 selection, evaluates the selected alignments, and saves the top-1 output in XML and JSON format."
+ ],
+ "id": "1b3f034b618a23d5"
+ },
+ {
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2026-07-08T15:41:55.925549200Z",
+ "start_time": "2026-07-08T15:41:55.484976700Z"
+ }
+ },
+ "cell_type": "code",
+ "source": [
+ "# Optional: Top-1 candidate selection, evaluation, and saving\n",
+ "# Keep only the highest-scoring target for each source concept.\n",
+ "\n",
+ "ranked_matchings = sorted(\n",
+ " final_matchings,\n",
+ " key=lambda prediction: prediction.get(\"score\", 0.0),\n",
+ " reverse=True,\n",
+ ")\n",
+ "\n",
+ "top1_matchings = []\n",
+ "seen_sources = set()\n",
+ "\n",
+ "for prediction in ranked_matchings:\n",
+ " if prediction[\"source\"] not in seen_sources:\n",
+ " top1_matchings.append(prediction)\n",
+ " seen_sources.add(prediction[\"source\"])\n",
+ "\n",
+ "top1_evaluation = metrics.evaluation_report(\n",
+ " predicts=top1_matchings,\n",
+ " references=dataset[\"reference\"],\n",
+ ")\n",
+ "\n",
+ "print(\"\\nTop-1 Nested Ensemble Evaluation Report:\")\n",
+ "print(json.dumps(top1_evaluation, indent=4))\n",
+ "print(\"Top-1 predictions:\", len(top1_matchings))\n",
+ "\n",
+ "# Save top-1 XML\n",
+ "with open(\"nested_ensemble_top1_alignments.xml\", \"w\", encoding=\"utf-8\") as xml_file:\n",
+ " xml_file.write(xmlify.xml_alignment_generator(matchings=top1_matchings))\n",
+ "\n",
+ "# Save top-1 JSON\n",
+ "with open(\"nested_ensemble_top1_alignments.json\", \"w\", encoding=\"utf-8\") as json_file:\n",
+ " json.dump(top1_matchings, json_file, indent=4, ensure_ascii=False)\n",
+ "\n",
+ "# Save top-1 evaluation\n",
+ "with open(\"nested_ensemble_top1_evaluation.json\", \"w\", encoding=\"utf-8\") as json_file:\n",
+ " json.dump(top1_evaluation, json_file, indent=4, ensure_ascii=False)\n",
+ "\n",
+ "print(\"Saved top-1 XML, JSON, and evaluation files.\")"
+ ],
+ "id": "43e2cd55ea57e7aa",
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\n",
+ "Top-1 Nested Ensemble Evaluation Report:\n",
+ "{\n",
+ " \"intersection\": 1148,\n",
+ " \"precision\": 41.94373401534527,\n",
+ " \"recall\": 75.72559366754618,\n",
+ " \"f-score\": 53.985422055019995,\n",
+ " \"predictions-len\": 2737,\n",
+ " \"reference-len\": 1516\n",
+ "}\n",
+ "Top-1 predictions: 2737\n",
+ "Saved top-1 XML, JSON, and evaluation files.\n"
+ ]
+ }
+ ],
+ "execution_count": 12
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "---\n",
+ "# β
Key Takeaways\n",
+ "\n",
+ "This notebook demonstrates how OntoAligner can combine multiple ontology alignment model families in a single nested ensemble workflow.\n",
+ "\n",
+ "The main idea is to treat each alignment strategy as an `AlignerPipeline`, then group related aligners with `EnsembleLearningAligner`. These group-level ensembles can then be combined again into a final nested ensemble.\n",
+ "\n",
+ "In this example, the workflow combines:\n",
+ "\n",
+ "- **Retrieval aligners**, which generate candidate mappings using lexical and semantic similarity.\n",
+ "- **Reranking aligners**, which first retrieve candidates and then refine them using a stronger reranking model.\n",
+ "- **Graph-based aligners**, which use ontology structure to generate candidate mappings.\n",
+ "- **LLM-based aligners**, which use retrieval and language-model reasoning.\n",
+ "\n",
+ "This structure makes the alignment process modular. Any OntoAligner aligner can be used through `AlignerPipeline` as long as it follows the standard pipeline interface. This allows different aligners, encoders, postprocessors, and optional rerankers to be composed in the same workflow.\n",
+ "\n",
+ "The nested ensemble shows that OntoAligner can support:\n",
+ "\n",
+ "```text\n",
+ "multiple aligners\n",
+ "β multiple model families\n",
+ "β multiple ensemble groups\n",
+ "β one final alignment output\n",
+ "```\n",
+ "\n",
+ "Overall, this notebook presents a flexible ensemble-based alignment design where different OntoAligner aligners can work together in one consistent pipeline and ensemble structure.\n",
+ "\n",
+ "For more information, visit the [OntoAligner Documentation](https://ontoaligner.readthedocs.io/)\n",
+ "\n",
+ "-----------------------------------------------------------\n",
+ "-----------------------------------------------------------\n",
+ "\n",
+ "π Acknowledgement\n",
+ "\n",
+ "OntoAligner is licensed under [](https://opensource.org/licenses/Apache-2.0)\n",
+ "\n",
+ "\n",
+ "```bibtex\n",
+ "@inproceedings{babaei2025ontoaligner,\n",
+ " title={OntoAligner: A Comprehensive Modular and Robust Python Toolkit for Ontology Alignment},\n",
+ " author={Babaei Giglou, Hamed and DβSouza, Jennifer and Karras, Oliver and Auer, S{\\\"o}ren},\n",
+ " booktitle={European Semantic Web Conference},\n",
+ " pages={174--191},\n",
+ " year={2025},\n",
+ " organization={Springer}\n",
+ "}\n",
+ "```"
+ ],
+ "id": "f750ce4bd5087edc"
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "name": "python",
+ "pygments_lexer": "ipython3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/tutorial/05-reusable-reranking-in-ontoaligner.ipynb b/tutorial/05-reusable-reranking-in-ontoaligner.ipynb
new file mode 100644
index 0000000..eeaebd7
--- /dev/null
+++ b/tutorial/05-reusable-reranking-in-ontoaligner.ipynb
@@ -0,0 +1,1033 @@
+{
+ "nbformat": 4,
+ "nbformat_minor": 0,
+ "metadata": {
+ "colab": {
+ "provenance": []
+ },
+ "kernelspec": {
+ "name": "python3",
+ "display_name": "Python 3"
+ },
+ "language_info": {
+ "name": "python"
+ }
+ },
+ "cells": [
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "\n",
+ "\n",
+ "[](https://badge.fury.io/py/OntoAligner)\n",
+ "[](https://pepy.tech/projects/ontoaligner)\n",
+ "\n",
+ "[](https://github.com/pre-commit/pre-commit)\n",
+ "[](https://ontoaligner.readthedocs.io/)\n",
+ "[](MAINTANANCE.md)\n",
+ " [](https://doi.org/10.5281/zenodo.14533133)\n",
+ "\n",
+ "- **Documentation website**: [https://ontoaligner.readthedocs.io/index.html](https://ontoaligner.readthedocs.io/index.html)\n",
+ "- **Resource Paper**: [https://doi.org/10.1007/978-3-031-94578-6_10](https://doi.org/10.1007/978-3-031-94578-6_10)\n",
+ "\n",
+ "\n",
+ "--------\n",
+ "\n",
+ "# Reusable Reranking in OntoAligner"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "This notebook demonstrates how [reranking](https://ontoaligner.readthedocs.io/aligner/retriever.html#reranking) can be used as a reusable candidate-refinement step in OntoAligner. Reranking is not tied to one specific aligner. It can be added after any component that produces multiple target candidates for a source concept.\n",
+ "\n",
+ "We start with the standard retrieval-based workflow, then show how the same idea can be applied to graph-based candidates, RAG retrieval outputs before LLM verification, and flat `source`β`target`β`score` predictions.\n",
+ "\n",
+ "Together, these examples show how reranking can be reused across different OntoAligner workflows when candidate alignments need to be refined before producing the final matchings."
+ ],
+ "metadata": {
+ "id": "87t9bxOy25t1"
+ }
+ },
+ {
+ "metadata": {},
+ "cell_type": "markdown",
+ "source": [
+ "---\n",
+ "Contents of this tutorial:\n",
+ "\n",
+ "1. Retrieval-based candidate reranking\n",
+ "2. Graph-based candidate reranking\n",
+ "3. Flat-output reranking\n",
+ "4. RAG IR-Output Reranking Before LLM Verification\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "---\n",
+ "## 1οΈβ£ Retrieval-Based Candidate Reranking\n",
+ "\n",
+ "We start with the most direct reranking workflow in OntoAligner: reranking candidates produced by a [retrieval aligner](https://ontoaligner.readthedocs.io/aligner/retriever.html#retrieval).\n",
+ "\n",
+ "A retrieval model first searches the target ontology and returns several possible target candidates for each source concept. In this example, `SBERTRetrieval` is used to generate those candidates.\n",
+ "\n",
+ "The retrieval output is already grouped by source concept, which is the format expected by the reranker:\n",
+ "\n",
+ "```python\n",
+ "{\n",
+ " \"source\": source_iri,\n",
+ " \"target-cands\": [target_iri_1, target_iri_2, ...],\n",
+ " \"score-cands\": [score_1, score_2, ...],\n",
+ "}\n",
+ "```\n",
+ "Because the candidates are already grouped, they can be passed directly to CrossEncoderReranking or CohereReranking. The reranker scores the candidate pairs again and returns the same grouped format, with the target candidates reordered.\n",
+ "\n",
+ "The flow below shows how retrieval candidates are generated, reranked, postprocessed, and converted into final matchings.\n",
+ "\n",
+ "\n",
+ "```text\n",
+ "Concept encoder\n",
+ " β\n",
+ "SBERTRetrieval\n",
+ " β\n",
+ "Grouped retrieval candidates\n",
+ " β\n",
+ "CrossEncoderReranking / CohereReranking\n",
+ " β\n",
+ "Reranked candidate groups\n",
+ " β\n",
+ "retriever_postprocessor\n",
+ " β\n",
+ "Final matchings\n",
+ "\n",
+ "```"
+ ],
+ "metadata": {
+ "id": "9HbtVq3H3CV8"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "# Import necessary libraries\n",
+ "import json\n",
+ "import os\n",
+ "import torch\n",
+ "\n",
+ "# Import necessary modules from the 'ontoaligner' library\n",
+ "# The library provides tools for ontology alignment tasks, including dataset management,\n",
+ "# encoding, retrieval, reranking, evaluation, and postprocessing.\n",
+ "from ontoaligner.encoder import ConceptParentLightweightEncoder\n",
+ "from ontoaligner.ontology import MaterialInformationMatOntoOMDataset\n",
+ "from ontoaligner.utils import metrics, xmlify\n",
+ "from ontoaligner.aligner import SBERTRetrieval, CohereReranking, CrossEncoderReranking\n",
+ "from ontoaligner.postprocess import retriever_postprocessor\n",
+ "\n",
+ "\n",
+ "# Step 1: Initialize the ontology matching task\n",
+ "# The task is created using the Material Information Ontology Dataset,\n",
+ "# which includes source and target ontologies and reference matchings for evaluation.\n",
+ "task = MaterialInformationMatOntoOMDataset()\n",
+ "\n",
+ "# Confirm the task initialization by printing its details\n",
+ "print(\"Test Task:\", task)\n",
+ "\n",
+ "# Step 2: Collect the ontology dataset\n",
+ "# The dataset includes paths to the source ontology, target ontology, and reference matching files.\n",
+ "dataset = task.collect(\n",
+ " source_ontology_path=\"assets/MI-MatOnto/mi_ontology.xml\",\n",
+ " target_ontology_path=\"assets/MI-MatOnto/matonto_ontology.xml\",\n",
+ " reference_matching_path=\"assets/MI-MatOnto/matchings.xml\",\n",
+ ")\n",
+ "\n",
+ "# Step 3: Select the runtime device\n",
+ "# The reranking example can use GPU if CUDA is available; otherwise, it runs on CPU.\n",
+ "device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n",
+ "\n",
+ "# Step 4: Initialize the encoder model\n",
+ "# The encoder prepares source and target ontology concept texts for retrieval and reranking.\n",
+ "encoder_model = ConceptParentLightweightEncoder()\n",
+ "\n",
+ "# Encode the source and target ontologies\n",
+ "# The output is used as input for the retrieval model.\n",
+ "source_onto, target_onto = encoder_model(source=dataset[\"source\"],target=dataset[\"target\"])\n",
+ "\n",
+ "# Step 5: Set up the retrieval model\n",
+ "# The retrieval model generates candidate target concepts for each source concept.\n",
+ "# Here, SBERTRetrieval is used with the all-MiniLM-L6-v2 sentence-transformer model.\n",
+ "retriever = SBERTRetrieval(device=device, top_k=10)\n",
+ "\n",
+ "# Load the SBERT retrieval model\n",
+ "retriever.load(path=\"all-MiniLM-L6-v2\")\n",
+ "\n",
+ "# Generate candidate alignments\n",
+ "# The retrieval model returns grouped candidates using source, target-cands, and score-cands.\n",
+ "retrieval_outputs = retriever.generate(input_data=[source_onto,target_onto])\n",
+ "\n",
+ "# Step 6: Set up the reranking model\n",
+ "# The reranker refines the candidates generated by the retrieval model.\n",
+ "# CrossEncoderReranking scores each source-target pair jointly and reranks the retrieved candidates.\n",
+ "reranker = CrossEncoderReranking(\n",
+ " device=device,\n",
+ " top_k=5,\n",
+ " normalize_score=\"sigmoid\",\n",
+ ")\n",
+ "\n",
+ "# Load the CrossEncoder reranking model\n",
+ "reranker.load(path=\"cross-encoder/ms-marco-MiniLM-L6-v2\")\n",
+ "\n",
+ "# To use Cohere reranking instead of CrossEncoderReranking, replace the block above with:\n",
+ "# reranker = CohereReranking(\n",
+ "# cohere_key=os.environ[\"COHERE_API_KEY\"],\n",
+ "# top_k=5,\n",
+ "# normalize_score=\"none\",\n",
+ "# )\n",
+ "# # Load the Cohere reranking model\n",
+ "# reranker.load(path=\"rerank-v3.5\")\n",
+ "\n",
+ "# Step 7: Rerank the retrieved candidates\n",
+ "# The reranker preserves the retrieval output format, so the existing retriever_postprocessor\n",
+ "# can still be used after reranking.\n",
+ "reranked_outputs = reranker.generate(input_data=[source_onto,target_onto,retrieval_outputs])\n",
+ "\n",
+ "# Step 8: Post-process the reranked outputs\n",
+ "# The retriever_postprocessor converts grouped candidates into flat source-target matchings.\n",
+ "# For CrossEncoderReranking with sigmoid normalization, threshold=0.5 can be used.\n",
+ "# For CohereReranking, threshold=0.0 keeps the reranked top-k candidates.\n",
+ "matchings = retriever_postprocessor(predicts=reranked_outputs,threshold=0.5)\n",
+ "\n",
+ "# Step 9: Evaluate the generated matchings\n",
+ "# The evaluation report compares predicted matchings against the reference alignments\n",
+ "# using metrics such as precision, recall, and F-score.\n",
+ "evaluation = metrics.evaluation_report(predicts=matchings,references=dataset[\"reference\"])\n",
+ "\n",
+ "# Print the evaluation report in a human-readable JSON format\n",
+ "print(\"Evaluation Report:\", json.dumps(evaluation, indent=4))\n",
+ "\n",
+ "# Step 10: Export matchings in XML format or JSON format\n",
+ "\n",
+ "# XML format\n",
+ "# Convert the generated matchings into an XML alignment file using the xmlify utility.\n",
+ "xml_str = xmlify.xml_alignment_generator(matchings=matchings)\n",
+ "\n",
+ "# Save the XML alignment to a file for further use or analysis\n",
+ "output_file_path = \"reranked_matchings.xml\"\n",
+ "with open(output_file_path, \"w\", encoding=\"utf-8\") as xml_file:\n",
+ " xml_file.write(xml_str)\n",
+ "\n",
+ "print(f\"Matchings in XML format have been successfully written to '{output_file_path}'.\")\n",
+ "\n",
+ "# JSON format\n",
+ "# Save the generated matchings in dictionary format for further analysis or debugging.\n",
+ "output_file_path = \"reranked_matchings.json\"\n",
+ "with open(output_file_path, \"w\", encoding=\"utf-8\") as json_file:\n",
+ " json.dump(matchings, json_file, indent=4, ensure_ascii=False)\n",
+ "\n",
+ "print(f\"Matchings in JSON format have been successfully written to '{output_file_path}'.\")"
+ ],
+ "metadata": {
+ "id": "NgA3m8gA28we"
+ },
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "---\n",
+ "## 2οΈβ£ Graph-Based Candidate Reranking\n",
+ "\n",
+ "This section shows how reranking can be used after a [graph-based KGE aligner](https://ontoaligner.readthedocs.io/aligner/kge.html#knowledge-graph-embedding).\n",
+ "\n",
+ "Unlike retrieval models, graph KGE aligners generate candidates from ontology triples and learned graph embeddings. In this example, `ConvEAligner` is used in retrieval mode by setting `retriever=True`. This makes the graph aligner return multiple target candidates for each source entity instead of only one final prediction.\n",
+ "\n",
+ "The reranker is then applied to these graph-generated candidates. Since `CrossEncoderReranking` compares text pairs, the source and target ontologies are also encoded into text format. The graph aligner provides the candidate IRIs, and the text encoder provides the source and target descriptions used for reranking.\n",
+ "\n",
+ "The workflow below shows how graph-based candidates are generated, reranked, and converted into final matchings.\n",
+ "\n",
+ "\n",
+ "```text\n",
+ "GraphTripleOMDataset\n",
+ " β\n",
+ "GraphTripleEncoder\n",
+ " β\n",
+ "ConvEAligner(retriever=True)\n",
+ " β\n",
+ "Grouped graph-generated candidates\n",
+ " β\n",
+ "CrossEncoderReranking\n",
+ " β\n",
+ "Reranked graph candidates\n",
+ " β\n",
+ "retriever_postprocessor\n",
+ " β\n",
+ "Final matchings\n",
+ "```"
+ ],
+ "metadata": {
+ "id": "WT_aDRLH3MNW"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "# Import necessary libraries\n",
+ "import json\n",
+ "import torch\n",
+ "\n",
+ "# Import necessary modules from the ontoaligner package\n",
+ "from ontoaligner.ontology import GraphTripleOMDataset, MaterialInformationMatOntoOMDataset\n",
+ "from ontoaligner.encoder import GraphTripleEncoder, ConceptParentLightweightEncoder\n",
+ "from ontoaligner.aligner import ConvEAligner, CrossEncoderReranking\n",
+ "from ontoaligner.postprocess import retriever_postprocessor\n",
+ "from ontoaligner.utils import metrics, xmlify\n",
+ "\n",
+ "\n",
+ "def print_matchings(title, matchings, limit=10):\n",
+ " \"\"\"\n",
+ " Prints a small sample of matchings in readable JSON format.\n",
+ " \"\"\"\n",
+ " print(f\"\\n{title}\")\n",
+ " print(f\"Total matchings: {len(matchings)}\")\n",
+ " print(json.dumps(matchings[:limit], indent=4, ensure_ascii=False))\n",
+ "\n",
+ "\n",
+ "# Step 1: Initialize graph ontology matching task\n",
+ "# GraphTripleOMDataset parses the ontology into triples for the KGE aligner.\n",
+ "graph_task = GraphTripleOMDataset(ontology_name=\"MI-MatOnto\")\n",
+ "print(\"Graph task:\", graph_task)\n",
+ "\n",
+ "# Step 2: Load source, target, and reference ontologies in graph triple format\n",
+ "graph_dataset = graph_task.collect(\n",
+ " source_ontology_path=\"assets/MI-MatOnto/mi_ontology.xml\",\n",
+ " target_ontology_path=\"assets/MI-MatOnto/matonto_ontology.xml\",\n",
+ " reference_matching_path=\"assets/MI-MatOnto/matchings.xml\",\n",
+ ")\n",
+ "\n",
+ "print(\"Graph dataset key-values:\", graph_dataset.keys())\n",
+ "print(\"Sample graph source item:\", graph_dataset[\"source\"][0])\n",
+ "\n",
+ "# Step 3: Encode the dataset into graph triple format\n",
+ "graph_encoder = GraphTripleEncoder()\n",
+ "encoded_graph_dataset = graph_encoder(**graph_dataset)\n",
+ "\n",
+ "# Step 4: Load the same ontology files in standard ontology format\n",
+ "# This is only used to create source and target text for the text-based reranker.\n",
+ "text_task = MaterialInformationMatOntoOMDataset()\n",
+ "\n",
+ "text_dataset = text_task.collect(\n",
+ " source_ontology_path=\"assets/MI-MatOnto/mi_ontology.xml\",\n",
+ " target_ontology_path=\"assets/MI-MatOnto/matonto_ontology.xml\",\n",
+ " reference_matching_path=\"assets/MI-MatOnto/matchings.xml\",\n",
+ ")\n",
+ "\n",
+ "# Step 5: Encode source and target concepts into text format for reranking\n",
+ "# The graph aligner works from triples, while CrossEncoderReranking compares text pairs.\n",
+ "text_encoder = ConceptParentLightweightEncoder()\n",
+ "\n",
+ "source_onto, target_onto = text_encoder(\n",
+ " source=text_dataset[\"source\"],\n",
+ " target=text_dataset[\"target\"],\n",
+ ")\n",
+ "\n",
+ "# Step 6: Select runtime device\n",
+ "device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n",
+ "\n",
+ "# Step 7: Define training parameters for the graph KGE aligner\n",
+ "kge_params = {\n",
+ " \"device\": \"cpu\",\n",
+ " \"embedding_dim\": 300,\n",
+ "\n",
+ " # A small number of epochs keeps the example lightweight.\n",
+ " # Increase this value for stronger graph embeddings.\n",
+ " \"num_epochs\": 3,\n",
+ "\n",
+ " \"train_batch_size\": 128,\n",
+ " \"eval_batch_size\": 64,\n",
+ " \"num_negs_per_pos\": 5,\n",
+ " \"random_seed\": 42,\n",
+ "\n",
+ " # Important for reranking:\n",
+ " # retriever=True returns multiple target candidates per source.\n",
+ " \"retriever\": True,\n",
+ " \"top_k\": 10,\n",
+ "}\n",
+ "\n",
+ "# Step 8: Initialize and run the graph KGE aligner\n",
+ "aligner = ConvEAligner(model=\"ConvE\", **kge_params)\n",
+ "\n",
+ "# Because retriever=True, the graph aligner returns grouped candidate outputs:\n",
+ "# {\n",
+ "# \"source\": source_iri,\n",
+ "# \"target-cands\": [...],\n",
+ "# \"score-cands\": [...],\n",
+ "# }\n",
+ "graph_candidates = aligner.generate(input_data=encoded_graph_dataset)\n",
+ "\n",
+ "# Step 9: Post-process and print graph candidates before reranking\n",
+ "graph_matchings = retriever_postprocessor(\n",
+ " predicts=graph_candidates,\n",
+ " threshold=0.5,\n",
+ ")\n",
+ "\n",
+ "print_matchings(\n",
+ " title=\"Graph KGE Matchings -- before reranking\",\n",
+ " matchings=graph_matchings,\n",
+ " limit=10,\n",
+ ")\n",
+ "\n",
+ "# Step 10: Initialize the reranking model\n",
+ "reranker = CrossEncoderReranking(\n",
+ " device=device,\n",
+ " top_k=5,\n",
+ " normalize_score=\"sigmoid\",\n",
+ ")\n",
+ "\n",
+ "reranker.load(path=\"cross-encoder/ms-marco-MiniLM-L6-v2\")\n",
+ "\n",
+ "# Step 11: Rerank graph-generated candidates\n",
+ "# The graph aligner provides candidate IRIs.\n",
+ "# source_onto and target_onto provide text for those IRIs.\n",
+ "reranked_graph_candidates = reranker.generate(\n",
+ " input_data=[\n",
+ " source_onto,\n",
+ " target_onto,\n",
+ " graph_candidates,\n",
+ " ]\n",
+ ")\n",
+ "\n",
+ "# Step 12: Post-process and print reranked graph candidates\n",
+ "reranked_graph_matchings = retriever_postprocessor(\n",
+ " predicts=reranked_graph_candidates,\n",
+ " threshold=0.3,\n",
+ ")\n",
+ "\n",
+ "print_matchings(\n",
+ " title=\"Graph KGE Matchings -- after reranking\",\n",
+ " matchings=reranked_graph_matchings,\n",
+ " limit=10,\n",
+ ")\n",
+ "\n",
+ "print(\"Number of graph matchings before reranking:\", len(graph_matchings))\n",
+ "print(\"Number of graph matchings after reranking:\", len(reranked_graph_matchings))\n",
+ "\n",
+ "# Step 13: Evaluate reranked graph candidates\n",
+ "evaluation = metrics.evaluation_report(\n",
+ " predicts=reranked_graph_matchings,\n",
+ " references=graph_dataset[\"reference\"],\n",
+ ")\n",
+ "\n",
+ "print(\"Graph KGE Reranking Evaluation Report:\")\n",
+ "print(json.dumps(evaluation, indent=4))\n",
+ "\n",
+ "# Step 14: Save reranked matchings in XML format\n",
+ "xml_str = xmlify.xml_alignment_generator(matchings=reranked_graph_matchings)\n",
+ "\n",
+ "with open(\"graph_reranked_matchings.xml\", \"w\", encoding=\"utf-8\") as xml_file:\n",
+ " xml_file.write(xml_str)\n",
+ "\n",
+ "print(\"Reranked graph matchings have been written to 'graph_reranked_matchings.xml'.\")"
+ ],
+ "metadata": {
+ "id": "shj89EQh2NTA"
+ },
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "---\n",
+ "## 3οΈβ£ Flat-Output Reranking\n",
+ "\n",
+ "Flat-output reranking applies to aligners that return flat `source`β`target`β`score` predictions.\n",
+ "\n",
+ "In this section, [OLaLA](https://ontoaligner.readthedocs.io/aligner/olala.html) is used to show the flat-output case. The same idea also applies to other aligners that produce flat predictions, such as ensemble outputs, final LLM/RAG outputs, or FLORA-style outputs.\n",
+ "\n",
+ "Before reranking, the flat predictions are grouped by source concept. After grouping, they follow the same candidate format used by retrieval-based reranking and can be passed to `CrossEncoderReranking`.\n",
+ "\n",
+ "This strategy is most useful when the flat output contains multiple target candidates for the same source concept. If an aligner already returns only one final target per source, such as a one-best or one-to-one matcher, there may be nothing meaningful left to rerank.\n",
+ "\n",
+ "The workflow below shows how flat alignment outputs are converted into grouped candidates and reranked.\n",
+ "\n",
+ "```text\n",
+ "Aligner with flat output\n",
+ " β\n",
+ "Flat source-target-score alignments\n",
+ " β\n",
+ "group_predictions_for_reranking\n",
+ " β\n",
+ "Grouped candidate format\n",
+ " β\n",
+ "CrossEncoderReranking\n",
+ " β\n",
+ "retriever_postprocessor\n",
+ " β\n",
+ "Final reranked matchings\n",
+ "```"
+ ],
+ "metadata": {
+ "id": "Fq3Dbi8-7iF5"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "# Run OLaLa flat-output reranking in OntoAligner\n",
+ "\n",
+ "import json\n",
+ "import torch\n",
+ "\n",
+ "from ontoaligner.ontology import OLaLaOMDataset\n",
+ "from ontoaligner.encoder import OLaLaEncoder\n",
+ "from ontoaligner.aligner import CrossEncoderReranking\n",
+ "from ontoaligner.aligner.olala import (\n",
+ " OLaLaSBERTRetrieval,\n",
+ " OLaLaLLMAligner,\n",
+ " OLaLaHighPrecisionMatcher,\n",
+ " OLaLaAligner,\n",
+ ")\n",
+ "from ontoaligner.aligner.olala.postprocessor import olala_postprocessor\n",
+ "from ontoaligner.postprocess import retriever_postprocessor\n",
+ "from ontoaligner.utils import metrics, xmlify\n",
+ "\n",
+ "\n",
+ "def print_matchings(title, matchings, limit=10):\n",
+ " \"\"\"\n",
+ " Prints a small sample of matchings in readable JSON format.\n",
+ " \"\"\"\n",
+ " print(f\"\\n{title}\")\n",
+ " print(f\"Total matchings: {len(matchings)}\")\n",
+ " print(json.dumps(matchings[:limit], indent=4, ensure_ascii=False))\n",
+ "\n",
+ "\n",
+ "def group_predictions_for_reranking(predictions):\n",
+ " \"\"\"\n",
+ " Converts flat source-target-score predictions into grouped candidate format.\n",
+ "\n",
+ " Input format:\n",
+ " [\n",
+ " {\"source\": source_iri, \"target\": target_iri, \"score\": score},\n",
+ " ...\n",
+ " ]\n",
+ "\n",
+ " Output format:\n",
+ " [\n",
+ " {\n",
+ " \"source\": source_iri,\n",
+ " \"target-cands\": [target_iri_1, target_iri_2, ...],\n",
+ " \"score-cands\": [score_1, score_2, ...],\n",
+ " },\n",
+ " ...\n",
+ " ]\n",
+ " \"\"\"\n",
+ " grouped_predictions = {}\n",
+ "\n",
+ " for prediction in predictions:\n",
+ " source = prediction[\"source\"]\n",
+ " target = prediction[\"target\"]\n",
+ " score = prediction.get(\"score\", 0.0)\n",
+ "\n",
+ " if source not in grouped_predictions:\n",
+ " grouped_predictions[source] = {\n",
+ " \"source\": source,\n",
+ " \"target-cands\": [],\n",
+ " \"score-cands\": [],\n",
+ " }\n",
+ "\n",
+ " grouped_predictions[source][\"target-cands\"].append(target)\n",
+ " grouped_predictions[source][\"score-cands\"].append(float(score))\n",
+ "\n",
+ " return list(grouped_predictions.values())\n",
+ "\n",
+ "\n",
+ "# Step 1: Load task and ontologies\n",
+ "task = OLaLaOMDataset()\n",
+ "print(\"Test Task:\", task)\n",
+ "\n",
+ "dataset = task.collect(\n",
+ " source_ontology_path=\"assets/MI-MatOnto/mi_ontology.xml\",\n",
+ " target_ontology_path=\"assets/MI-MatOnto/matonto_ontology.xml\",\n",
+ " reference_matching_path=\"assets/MI-MatOnto/matchings.xml\",\n",
+ ")\n",
+ "\n",
+ "# Step 2: Encode ontologies\n",
+ "encoder_model = OLaLaEncoder()\n",
+ "\n",
+ "encoded_ontology = encoder_model(\n",
+ " source=dataset[\"source\"],\n",
+ " target=dataset[\"target\"],\n",
+ ")\n",
+ "\n",
+ "source_onto = encoded_ontology[0]\n",
+ "target_onto = encoded_ontology[1]\n",
+ "\n",
+ "# Step 3: SBERT candidate generation\n",
+ "retriever = OLaLaSBERTRetrieval(\n",
+ " device=\"cuda\",\n",
+ " top_k=5,\n",
+ " both_directions=True,\n",
+ " topk_per_resource=True,\n",
+ ")\n",
+ "\n",
+ "# Step 4: LLM binary verification\n",
+ "llm_aligner = OLaLaLLMAligner(\n",
+ " device=\"cuda\",\n",
+ " max_new_tokens=10,\n",
+ " temperature=0.0,\n",
+ " truncation=True,\n",
+ " max_length=2048,\n",
+ " padding=True,\n",
+ " loading_arguments={\n",
+ " \"device_map\": \"auto\",\n",
+ " \"torch_dtype\": torch.float16,\n",
+ " },\n",
+ ")\n",
+ "\n",
+ "# Step 5: High-precision matcher\n",
+ "hp_aligner = OLaLaHighPrecisionMatcher(confidence=1.0)\n",
+ "\n",
+ "# Step 6: Initialize OLaLa aligner\n",
+ "olala = OLaLaAligner(\n",
+ " retriever=retriever,\n",
+ " llm_aligner=llm_aligner,\n",
+ " hp_aligner=hp_aligner,\n",
+ ")\n",
+ "\n",
+ "olala.load(\n",
+ " llm_path=\"upstage/Llama-2-70b-instruct-v2\",\n",
+ " retriever_path=\"multi-qa-mpnet-base-dot-v1\",\n",
+ ")\n",
+ "\n",
+ "# Step 7: Generate flat OLaLa alignments\n",
+ "# OLaLa returns flat source-target-score predictions with alignment_type.\n",
+ "alignments = olala.generate(input_data=encoded_ontology)\n",
+ "\n",
+ "print_matchings(\n",
+ " title=\"Flat OLaLa Alignments -- before reranking\",\n",
+ " matchings=alignments,\n",
+ " limit=10,\n",
+ ")\n",
+ "\n",
+ "# Optional: original OLaLa postprocessing before reranking\n",
+ "original_final_matchings = olala_postprocessor(\n",
+ " alignments,\n",
+ " encoded_ontology,\n",
+ " confidence_threshold=0.5,\n",
+ " strict_bad_hosts=False,\n",
+ ")\n",
+ "\n",
+ "print_matchings(\n",
+ " title=\"OLaLa Final Matchings -- before reranking\",\n",
+ " matchings=original_final_matchings,\n",
+ " limit=10,\n",
+ ")\n",
+ "\n",
+ "# Step 8: Convert flat OLaLa alignments into grouped candidate format\n",
+ "grouped_candidates = group_predictions_for_reranking(\n",
+ " predictions=alignments,\n",
+ ")\n",
+ "\n",
+ "print_matchings(\n",
+ " title=\"Grouped OLaLa Candidates -- ready for reranking\",\n",
+ " matchings=grouped_candidates,\n",
+ " limit=10,\n",
+ ")\n",
+ "\n",
+ "# Step 9: Initialize reranking model\n",
+ "device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n",
+ "\n",
+ "reranker = CrossEncoderReranking(\n",
+ " device=device,\n",
+ " top_k=5,\n",
+ " normalize_score=\"sigmoid\",\n",
+ ")\n",
+ "\n",
+ "reranker.load(path=\"cross-encoder/ms-marco-MiniLM-L6-v2\")\n",
+ "\n",
+ "# Step 10: Rerank grouped OLaLa candidates\n",
+ "reranked_outputs = reranker.generate(\n",
+ " input_data=[\n",
+ " source_onto,\n",
+ " target_onto,\n",
+ " grouped_candidates,\n",
+ " ]\n",
+ ")\n",
+ "\n",
+ "print_matchings(\n",
+ " title=\"Grouped OLaLa Candidates -- after reranking\",\n",
+ " matchings=reranked_outputs,\n",
+ " limit=10,\n",
+ ")\n",
+ "\n",
+ "# Step 11: Convert reranked grouped candidates into flat matchings\n",
+ "reranked_matchings = retriever_postprocessor(\n",
+ " predicts=reranked_outputs,\n",
+ " threshold=0.5,\n",
+ ")\n",
+ "\n",
+ "print_matchings(\n",
+ " title=\"Flat OLaLa Matchings -- after reranking\",\n",
+ " matchings=reranked_matchings,\n",
+ " limit=10,\n",
+ ")\n",
+ "\n",
+ "print(\"Number of flat OLaLa alignments before reranking:\", len(alignments))\n",
+ "print(\"Number of original OLaLa final matchings:\", len(original_final_matchings))\n",
+ "print(\"Number of grouped sources for reranking:\", len(grouped_candidates))\n",
+ "print(\"Number of reranked OLaLa matchings:\", len(reranked_matchings))\n",
+ "\n",
+ "# Step 12: Evaluate original OLaLa final matchings\n",
+ "original_evaluation = metrics.evaluation_report(\n",
+ " predicts=original_final_matchings,\n",
+ " references=dataset[\"reference\"],\n",
+ ")\n",
+ "\n",
+ "print(\"Original OLaLa Evaluation Report:\")\n",
+ "print(json.dumps(original_evaluation, indent=4))\n",
+ "\n",
+ "# Step 13: Evaluate reranked OLaLa matchings\n",
+ "reranked_evaluation = metrics.evaluation_report(\n",
+ " predicts=reranked_matchings,\n",
+ " references=dataset[\"reference\"],\n",
+ ")\n",
+ "\n",
+ "print(\"OLaLa Flat-output Reranking Evaluation Report:\")\n",
+ "print(json.dumps(reranked_evaluation, indent=4))\n",
+ "\n",
+ "# Step 14: XML export\n",
+ "xml_str = xmlify.xml_alignment_generator(matchings=reranked_matchings)\n",
+ "\n",
+ "output_file_path = \"olala_reranked_matchings.xml\"\n",
+ "with open(output_file_path, \"w\", encoding=\"utf-8\") as xml_file:\n",
+ " xml_file.write(xml_str)\n",
+ "\n",
+ "print(f\"Saved reranked OLaLa matchings to {output_file_path}\")"
+ ],
+ "metadata": {
+ "id": "pQ5E6AWMlIuw"
+ },
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "---\n",
+ "## 4οΈβ£ RAG IR-Output Reranking Before LLM Verification\n",
+ "\n",
+ "This section shows how reranking can be inserted inside a [RAG aligner](https://ontoaligner.readthedocs.io/aligner/rag.html).\n",
+ "\n",
+ "In a RAG workflow, the retriever first generates candidate source-target pairs. These candidates are then passed to an LLM for verification. Reranking can be added between these two steps, so the LLM receives a smaller and better-ranked set of candidates.\n",
+ "\n",
+ "The same pattern can be followed by other RAG-style aligners such as FewShotRAG, ICV, or any custom RAG pipeline where retrieval and LLM verification are separate steps.\n",
+ "\n",
+ "The workflow below shows how IR candidates are reranked before LLM verification.\n",
+ "\n",
+ "```text\n",
+ "ConceptParentRAGEncoder\n",
+ " β\n",
+ "RAG retriever\n",
+ " β\n",
+ "IR candidate groups\n",
+ " β\n",
+ "CrossEncoderReranking\n",
+ " β\n",
+ "Reranked IR candidates\n",
+ " β\n",
+ "LLM verification\n",
+ " β\n",
+ "RAG postprocessing\n",
+ " β\n",
+ "Final matchings\n",
+ "```"
+ ],
+ "metadata": {
+ "id": "clApryah3uFC"
+ }
+ },
+ {
+ "cell_type": "code",
+ "source": [
+ "# Import required libraries and modules\n",
+ "import json\n",
+ "import torch\n",
+ "\n",
+ "from ontoaligner.ontology import MaterialInformationMatOntoOMDataset\n",
+ "from ontoaligner.encoder import ConceptParentRAGEncoder\n",
+ "from ontoaligner.postprocess import (\n",
+ " retriever_postprocessor,\n",
+ " rag_hybrid_postprocessor,\n",
+ " rag_heuristic_postprocessor,\n",
+ ")\n",
+ "from ontoaligner.utils import metrics, xmlify\n",
+ "\n",
+ "# Direct imports are used to build the RAG pipeline explicitly.\n",
+ "from ontoaligner.aligner.rag.rag import RAG, AutoModelDecoderRAGLLMV2\n",
+ "from ontoaligner.aligner.retrieval.models import SBERTRetrieval\n",
+ "from ontoaligner.aligner.retrieval.reranking import CrossEncoderReranking\n",
+ "\n",
+ "\n",
+ "def print_matchings(title, matchings, limit=10):\n",
+ " \"\"\"\n",
+ " Prints a small sample of matchings in readable JSON format.\n",
+ " \"\"\"\n",
+ " print(f\"\\n{title}\")\n",
+ " print(f\"Total matchings: {len(matchings)}\")\n",
+ " print(json.dumps(matchings[:limit], indent=4, ensure_ascii=False))\n",
+ "\n",
+ "\n",
+ "# Step 1: Initialize the dataset object for MaterialInformation MatOnto dataset\n",
+ "task = MaterialInformationMatOntoOMDataset()\n",
+ "print(\"Test Task:\", task)\n",
+ "\n",
+ "# Step 2: Load source and target ontologies along with reference matchings\n",
+ "dataset = task.collect(\n",
+ " source_ontology_path=\"assets/MI-MatOnto/mi_ontology.xml\",\n",
+ " target_ontology_path=\"assets/MI-MatOnto/matonto_ontology.xml\",\n",
+ " reference_matching_path=\"assets/MI-MatOnto/matchings.xml\",\n",
+ ")\n",
+ "\n",
+ "# Step 3: Encode the source and target ontologies\n",
+ "# ConceptParentRAGEncoder prepares the input structure used by the RAG pipeline.\n",
+ "encoder_model = ConceptParentRAGEncoder()\n",
+ "encoded_ontology = encoder_model(\n",
+ " source=dataset[\"source\"],\n",
+ " target=dataset[\"target\"],\n",
+ ")\n",
+ "\n",
+ "# Step 4: Select runtime device\n",
+ "device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n",
+ "\n",
+ "# Step 5: Define configuration for the retriever and LLM\n",
+ "retriever_config = {\n",
+ " \"device\": device,\n",
+ " \"top_k\": 10,\n",
+ " \"threshold\": 0.1,\n",
+ "}\n",
+ "\n",
+ "llm_config = {\n",
+ " # CPU keeps the example easier to run.\n",
+ " # Change to \"cuda\" if your environment supports the selected LLM.\n",
+ " \"device\": \"cpu\",\n",
+ " \"max_length\": 300,\n",
+ " \"max_new_tokens\": 10,\n",
+ " \"huggingface_access_token\": \"\",\n",
+ " \"device_map\": \"balanced\",\n",
+ " \"batch_size\": 15,\n",
+ " \"answer_set\": {\n",
+ " \"yes\": [\"yes\", \"correct\", \"true\", \"positive\", \"valid\"],\n",
+ " \"no\": [\"no\", \"incorrect\", \"false\", \"negative\", \"invalid\"],\n",
+ " },\n",
+ "}\n",
+ "\n",
+ "# Step 6: Initialize the RAG-based ontology matcher\n",
+ "model = RAG(\n",
+ " retriever=SBERTRetrieval,\n",
+ " llm=AutoModelDecoderRAGLLMV2,\n",
+ " retriever_config=retriever_config,\n",
+ " llm_config=llm_config,\n",
+ ")\n",
+ "\n",
+ "# A small decoder model is used for a lightweight runnable example.\n",
+ "# For the original Mistral setup, replace \"distilgpt2\" with:\n",
+ "# \"mistralai/Mistral-7B-v0.3\"\n",
+ "model.load(\n",
+ " llm_path=\"distilgpt2\",\n",
+ " ir_path=\"all-MiniLM-L6-v2\",\n",
+ ")\n",
+ "\n",
+ "# Decoder-only models generate more correctly with left padding.\n",
+ "# This avoids the right-padding warning for lightweight models such as distilgpt2.\n",
+ "model.LLM.tokenizer.padding_side = \"left\"\n",
+ "\n",
+ "if model.LLM.tokenizer.pad_token is None:\n",
+ " model.LLM.tokenizer.pad_token = model.LLM.tokenizer.eos_token\n",
+ "\n",
+ "model.LLM.model.config.pad_token_id = model.LLM.tokenizer.pad_token_id\n",
+ "\n",
+ "# Step 7: Build the retriever input used inside the RAG model\n",
+ "# This gives source and target concept text needed by the reranker.\n",
+ "retrieval_input = encoded_ontology[\"retriever-encoder\"]()(\n",
+ " **encoded_ontology[\"task-args\"]\n",
+ ")\n",
+ "\n",
+ "source_onto = retrieval_input[0]\n",
+ "target_onto = retrieval_input[1]\n",
+ "\n",
+ "# Step 8: Generate IR candidates using the RAG retriever\n",
+ "# The IR output is grouped candidate format:\n",
+ "# {\n",
+ "# \"source\": source_iri,\n",
+ "# \"target-cands\": [...],\n",
+ "# \"score-cands\": [...],\n",
+ "# }\n",
+ "ir_outputs = model.Retrieval.generate(\n",
+ " input_data=retrieval_input\n",
+ ")\n",
+ "\n",
+ "# Step 9: Print general IR alignments before reranking\n",
+ "general_ir_matchings = retriever_postprocessor(\n",
+ " predicts=ir_outputs,\n",
+ " threshold=retriever_config[\"threshold\"],\n",
+ ")\n",
+ "\n",
+ "print_matchings(\n",
+ " title=\"General IR Matchings -- before reranking\",\n",
+ " matchings=general_ir_matchings,\n",
+ " limit=10,\n",
+ ")\n",
+ "\n",
+ "# Step 10: Initialize the reranking model\n",
+ "reranker = CrossEncoderReranking(\n",
+ " device=device,\n",
+ " top_k=5,\n",
+ " normalize_score=\"sigmoid\",\n",
+ ")\n",
+ "\n",
+ "reranker.load(path=\"cross-encoder/ms-marco-MiniLM-L6-v2\")\n",
+ "\n",
+ "# Step 11: Rerank the IR candidates before LLM verification\n",
+ "reranked_ir_outputs = reranker.generate(\n",
+ " input_data=[\n",
+ " source_onto,\n",
+ " target_onto,\n",
+ " ir_outputs,\n",
+ " ]\n",
+ ")\n",
+ "\n",
+ "# Step 12: Convert reranked grouped candidates into flat source-target pairs\n",
+ "# The LLM verification step expects retrieved source-target pairs.\n",
+ "reranked_ir_matchings = retriever_postprocessor(\n",
+ " predicts=reranked_ir_outputs,\n",
+ " threshold=0.5,\n",
+ ")\n",
+ "\n",
+ "print_matchings(\n",
+ " title=\"Reranked IR Matchings -- sent to LLM\",\n",
+ " matchings=reranked_ir_matchings,\n",
+ " limit=10,\n",
+ ")\n",
+ "\n",
+ "print(\"Number of original IR candidate groups:\", len(ir_outputs))\n",
+ "print(\"Number of general IR matchings:\", len(general_ir_matchings))\n",
+ "print(\"Number of reranked IR matchings sent to LLM:\", len(reranked_ir_matchings))\n",
+ "\n",
+ "# Step 13: Send reranked IR matchings to the LLM verification step\n",
+ "llm_predictions = model.llm_generate(\n",
+ " input_data=encoded_ontology,\n",
+ " ir_output=reranked_ir_matchings,\n",
+ ")\n",
+ "\n",
+ "print_matchings(\n",
+ " title=\"Final LLM Predictions -- after reranked IR\",\n",
+ " matchings=llm_predictions,\n",
+ " limit=10,\n",
+ ")\n",
+ "\n",
+ "# Step 14: Build RAG-style output with reranked IR candidates\n",
+ "# This keeps the output compatible with existing RAG postprocessors.\n",
+ "predicts = [\n",
+ " {\"ir-outputs\": reranked_ir_outputs},\n",
+ " {\"llm-output\": llm_predictions},\n",
+ "]\n",
+ "\n",
+ "# Step 15: Apply heuristic postprocessing\n",
+ "heuristic_matchings, heuristic_configs = rag_heuristic_postprocessor(\n",
+ " predicts=predicts,\n",
+ " topk_confidence_ratio=3,\n",
+ " topk_confidence_score=3,\n",
+ ")\n",
+ "\n",
+ "print_matchings(\n",
+ " title=\"Heuristic Final Matchings\",\n",
+ " matchings=heuristic_matchings,\n",
+ " limit=10,\n",
+ ")\n",
+ "\n",
+ "evaluation = metrics.evaluation_report(\n",
+ " predicts=heuristic_matchings,\n",
+ " references=dataset[\"reference\"],\n",
+ ")\n",
+ "\n",
+ "print(\"RAG with Reranked IR -- Heuristic Matching Evaluation Report:\")\n",
+ "print(json.dumps(evaluation, indent=4))\n",
+ "print(\"Heuristic Matching Obtained Configuration:\", heuristic_configs)\n",
+ "\n",
+ "# Step 16: Apply hybrid postprocessing\n",
+ "hybrid_matchings, hybrid_configs = rag_hybrid_postprocessor(\n",
+ " predicts=predicts,\n",
+ " ir_score_threshold=0.5,\n",
+ " llm_confidence_th=0.8,\n",
+ ")\n",
+ "\n",
+ "print_matchings(\n",
+ " title=\"Hybrid Final Matchings\",\n",
+ " matchings=hybrid_matchings,\n",
+ " limit=10,\n",
+ ")\n",
+ "\n",
+ "evaluation = metrics.evaluation_report(\n",
+ " predicts=hybrid_matchings,\n",
+ " references=dataset[\"reference\"],\n",
+ ")\n",
+ "\n",
+ "print(\"RAG with Reranked IR -- Hybrid Matching Evaluation Report:\")\n",
+ "print(json.dumps(evaluation, indent=4))\n",
+ "print(\"Hybrid Matching Obtained Configuration:\", hybrid_configs)\n",
+ "\n",
+ "# Step 17: Convert hybrid matchings to XML format\n",
+ "xml_str = xmlify.xml_alignment_generator(matchings=hybrid_matchings)\n",
+ "\n",
+ "output_file_path = \"rag_reranked_matchings.xml\"\n",
+ "with open(output_file_path, \"w\", encoding=\"utf-8\") as xml_file:\n",
+ " xml_file.write(xml_str)\n",
+ "\n",
+ "print(f\"RAG reranked matchings have been written to '{output_file_path}'.\")"
+ ],
+ "metadata": {
+ "id": "K148pwpY3uRf"
+ },
+ "execution_count": null,
+ "outputs": []
+ },
+ {
+ "cell_type": "markdown",
+ "source": [
+ "## β
Key Takeaways\n",
+ "\n",
+ "- Reranking is a reusable candidate-refinement step in OntoAligner.\n",
+ "- It can be applied after aligners that produce multiple target candidates for each source concept.\n",
+ "- Grouped outputs with `target-cands` and `score-cands` can be reranked directly.\n",
+ "- Flat outputs with `source`, `target`, and `score` can be grouped by source concept before reranking.\n",
+ "- The same reranking pattern can be used across retrieval, graph-based, RAG-style, ensemble, OLaLA, and custom aligner workflows.\n",
+ "\n",
+ "For more information, visit the [OntoAligner Documentation](https://ontoaligner.readthedocs.io/)\n",
+ "\n",
+ "-----------------------------------------------------------\n",
+ "-----------------------------------------------------------\n",
+ "\n",
+ "π Acknowledgement\n",
+ "\n",
+ "OntoAligner is licensed under [](https://opensource.org/licenses/Apache-2.0)\n",
+ "\n",
+ "\n",
+ "```bibtex\n",
+ "@inproceedings{babaei2025ontoaligner,\n",
+ " title={OntoAligner: A Comprehensive Modular and Robust Python Toolkit for Ontology Alignment},\n",
+ " author={Babaei Giglou, Hamed and DβSouza, Jennifer and Karras, Oliver and Auer, S{\\\"o}ren},\n",
+ " booktitle={European Semantic Web Conference},\n",
+ " pages={174--191},\n",
+ " year={2025},\n",
+ " organization={Springer}\n",
+ "}\n",
+ "```"
+ ],
+ "metadata": {
+ "id": "CEBpiGe4JzyW"
+ }
+ }
+ ]
+}
diff --git a/tutorial/README.md b/tutorial/README.md
index dcfecd2..f9a17a1 100644
--- a/tutorial/README.md
+++ b/tutorial/README.md
@@ -6,13 +6,13 @@ The tutorial is notebook-based and can be run directly in **Google Colab**  | **Quick Introduction to OntoAligner**: Overview of OntoAligner, basic concepts, and a simple end-to-end example | [](https://colab.research.google.com/drive/1sDO-vW1SwGrTi9nzrhD0vJeWVQHWPv-z?usp=sharing) |
-| 2 | [02-deep-dive-into-ontoaligner-modules-1.ipynb](02-deep-dive-into-ontoaligner-modules-1.ipynb) | **Deep Dive into OntoAligner Modules β Part 1**: Detailed explanation of core modules and internal components | [](https://drive.google.com/file/d/1lKQ8ChSROiG_KHG2zyTQ0fIQhQzg4cmP/view?usp=sharing) |
+| # | Notebook | Description | Open Notebook in Colab |
+|:-:|---------------------------------------------|-----------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| 1 | [01-quick-introduction-to-ontoaligner.ipynb](01-quick-introduction-to-ontoaligner.ipynb) | **Quick Introduction to OntoAligner**: Overview of OntoAligner, basic concepts, and a simple end-to-end example | [](https://colab.research.google.com/drive/1sDO-vW1SwGrTi9nzrhD0vJeWVQHWPv-z?usp=sharing) |
+| 2 | [02-deep-dive-into-ontoaligner-modules-1.ipynb](02-deep-dive-into-ontoaligner-modules-1.ipynb) | **Deep Dive into OntoAligner Modules β Part 1**: Detailed explanation of core modules and internal components | [](https://drive.google.com/file/d/1lKQ8ChSROiG_KHG2zyTQ0fIQhQzg4cmP/view?usp=sharing) |
| 3 | [03-deep-dive-into-ontoaligner-modules-2.ipynb](03-deep-dive-into-ontoaligner-modules-2.ipynb) | **Deep Dive into OntoAligner Modules β Part 2**: Detailed explanation of Retrieval, LLM, and RAG based Aligners| [](https://drive.google.com/file/d/1qx7JDn5WnAiSVUUoOBqWjYvMNjKsBfBN/view?usp=sharing) |
-
-
+| 4 | [04-nested-ensemble-aligners-in-ontoaligner.ipynb](04-nested-ensemble-aligners-in-ontoaligner.ipynb) | **Nested Ensemble Aligners in OntoAligner**: Building group-level ensembles and combining them into one nested ensemble workflow | [](https://colab.research.google.com/drive/1vKnKYx5ul1bAsKWcUAehRY2OGRex_k3J?usp=sharing) |
+| 5 | [05-reusable-reranking-in-ontoaligner.ipynb](05-reusable-reranking-in-ontoaligner.ipynb) | **Reusable Reranking in OntoAligner**: Applying reranking across retrieval, graph-based, flat-output, and RAG-style workflows | [](https://colab.research.google.com/drive/1KJ8wKjsT__TPovDAwhfFEUqOH1ecN5uW?usp=sharing) |
## π Getting Started