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 Overview -
+
+ OntoAligner Overview +
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": [ + "![logo](https://raw.githubusercontent.com/sciknoworg/OntoAligner/main/images/logo-with-background.png)\n", + "\n", + "[![PyPI version](https://badge.fury.io/py/OntoAligner.svg)](https://badge.fury.io/py/OntoAligner)\n", + "[![PyPI Downloads](https://static.pepy.tech/badge/ontoaligner)](https://pepy.tech/projects/ontoaligner)\n", + "![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)\n", + "[![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit)](https://github.com/pre-commit/pre-commit)\n", + "[![Documentation Status](https://readthedocs.org/projects/ontoaligner/badge/?version=main)](https://ontoaligner.readthedocs.io/)\n", + "[![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](MAINTANANCE.md)\n", + " [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.14533133.svg)](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