Trao đổi về dự án
Trí Tuệ Nhân Tạo

Căn chỉnh hai kế hoạch phân loại bằng transport tối ưu không cân bằng

Khi di chuyển GED, một số mục đổi tên, tách, gộp hoặc không có tương đương. Hướng dẫn này tạo một ma trận có thể giải thích với nhiều liên kết, chi phí, khối lượng và cảnh báo, rồi xuất tệp để rà soát.

Parcours pédagogique : comprendre le cas, construire, mesurer puis vérifier.

Trong một đợt di chuyển GED, một số mục đổi tên, tách ra, gộp lại hoặc không có mục tương đương. Ép ghép một-một tạo các tương ứng gây hiểu lầm và đôi khi chuyển tài liệu sang một hạng mục không phù hợp.

Một ma trận có thể giải thích đề xuất nhiều liên kết với chi phí, khối lượng và cảnh báo, rồi xuất một tệp để rà soát. Không skos:exactMatch nào và không việc di chuyển tài liệu nào được tạo tự động.

Tải dự án hoàn chỉnh, bản sửa đổi r001

Hành trình của hướng dẫn, từ trường hợp cụ thể đến bước kiểm chứng.

Một kho dữ liệu có nhiều tương ứng

Hai mươi bốn cặp kế hoạch, mỗi cặp có 8 khái niệm nguồn và 10 khái niệm đích, tức 192 và 240 khái niệm. Mười hai miền dùng để hiệu chỉnh, mười hai miền khác dùng để kiểm thử. Miền và phần vỏ văn bản khác nhau, nhưng nguyên mẫu ngữ nghĩa của quan hệ và từ đồng nghĩa được chia sẻ: đây không phải thí nghiệm hoàn toàn ngoài phân phối. Lựa chọn này làm cơ chế dễ đọc; hiệu năng không chứng minh khả năng khái quát hóa nghiệp vụ.

Mỗi cặp có 64 ghi chú tải gốc. Hai cách phân loại của chúng được mô phỏng độc lập và tạo a và b theo số đếm/64; đây không phải chú giải tương ứng. Mọi khái niệm trong luồng chính đều có khối lượng dương. Gold chứa các quan hệ một-đến-nhiều, nhiều-đến-một và một mục không tương đương. Nó nằm trong tệp riêng, không bao giờ nằm trong chi phí.

Các broader chỉ nằm trong một kế hoạch; mã từ chối phần tử trùng, cha vắng mặt và chu trình. Bài thực hành này dùng quy ước cục bộ không chu trình; nó không tuyên bố xác định mọi ràng buộc chung của SKOS. Không xuất nào khai báo skos:exactMatch và không di chuyển nào được áp dụng.

E5: ý nghĩa và ngữ cảnh

E5-small cố định tại 614241f622f53c4eeff9890bdc4f31cfecc418b3 mã hóa nhãn+định nghĩa và một chuỗi ngữ cảnh thứ hai gồm các tổ tiên. Tiền tố query: đối xứng cho nguồn và đích. Pooling có mặt nạ, gồm token đặc biệt thật, rồi chuẩn hóa L2 trong 384 chiều; mã từ chối quá 512 token và kiểm tra mọi tệp và tensor đã tải.

Một root dùng cùng câu ngữ cảnh root rõ ràng, nên root/root có ngữ cảnh giống nhau. Chi phí là Cij=1−cos(ei,ej)+0,25(1−cos(ai,aj)). Hệ số 0,25 được cố định. Cosine tính bằng vector FP32 được cộng dồn ở FP64 rồi chặn trong [-1,1] để làm tròn. Chi phí không biết gold.

Ba phương pháp, ba cách diễn giải

Top 1 cosine lấy đích gần nhất về văn bản, với ngưỡng chọn khi hiệu chỉnh. Nó bỏ qua khối lượng nên không đổi trong bài tập về khối lượng. Transport cân bằng áp đặt các biên a và b: nó có thể chuyển tải sang một đích kém phù hợp về ngữ nghĩa để thỏa các ràng buộc này.

Transport không cân bằng tối thiểu hóa somme(ΠC)+epsilon somme(Π(logΠ−1))+rho_a KL(Π1||a)+rho_b KL(Πᵀ1||b). KL tổng quát là somme(p log(p/q)−p+q), với số không được xử lý rõ ràng. Các phạt biên cho phép thiếu và thừa; một biên có thể vượt tải ban đầu. Không đại lượng nào trong số này là xác suất tương ứng.

Hạt nhân log là −C/epsilon. Ta luân phiên logu=fa(loga−logsumexp(logK+logv)) và logv=fb(logb−logsumexp(logKᵀ+logu)), với fa=rho_a/(rho_a+epsilon). Rho vô hạn cho fa=1 trong trường hợp cân bằng. Việc ổn định trừ giá trị lớn nhất trong logsumexp. Khối lượng bằng không bị loại khỏi bài toán con rồi chèn lại dưới dạng hàng/cột rỗng. Tổng hoàn toàn bằng không, dữ liệu không hữu hạn hay tham số không hợp lệ đều bị từ chối; không hội tụ là lỗi dễ đọc.

Hội tụ yêu cầu biến thiên lớn nhất của các thế log không quá 1e-11, dưới 20000 vòng lặp, và các biên cân bằng ở 1e-8. Trường hợp nhỏ 2×2 được so với POT 0.9.7 ở FP64, với reg_type='entropy' rõ ràng và dung sai plan 1e-8. Mặc định kl dùng một quy ước regularization khác. Hai plan được đánh giá bằng cùng công thức objective của chúng tôi; total_cost do POT báo cáo không bị đồng nhất mù quáng với quy ước hằng số này.

Hiệu chỉnh trước khi kiểm thử

Epsilon chạy qua 0,03 và 0,1; rho qua 0,1, 1 và 10. Ngưỡng liên kết dùng Πij/ai, ngưỡng rà soát dùng somme_jΠij/ai. Các tỷ lệ này có thể vượt 1. Chúng không được chuẩn hóa thành xác suất cũng không được gọi là độ tin cậy. Tính tương thích business_scope lọc các đề xuất tài chính không tương thích thành EXCLUDED; chính solver dùng chi phí hữu hạn và quy tắc này không phải ràng buộc transport ẩn.

Các lưới và thứ tự của chúng được cố định. Trên 12 cặp hiệu chỉnh, mỗi phương pháp chọn micro-F1 của nhiều liên kết, rồi chọn phần tử lưới đầu tiên nếu bằng nhau. 12 cặp kiểm thử không chọn ngưỡng, epsilon hay rho nào. Các plan kiểm thử có thể tính không cần gold; chỉ số của chúng chỉ được tính sau hiệu chỉnh.

Các đề xuất được so với tập gold của liên kết: TP/FP/FN, precision, recall, F1, độ bao phủ khái niệm nguồn, liên kết sai trên mục không tương đương, REVIEW và EXCLUDED. Đầu ra chỉ ra biên, thiếu, thừa, objective và residual của solver. Thời gian solver CPU tách biệt với mã hóa GPU. Điểm tốt hơn trên các taxonomy nhỏ này không biện minh cho một đợt di chuyển tự động.

Bài tập với embedding cố định

Cặp kiểm thử 12 được chọn trước kết quả. Nhân khối lượng của Visites atelier lên mười rồi chuẩn hóa lại a, không đổi b, embedding hay tham số đã chọn. Tính lại bản cân bằng và không cân bằng. Top 1 vẫn dựa vào văn bản; các plan có thể đổi. Đọc các giá trị quan sát mà không đòi hỏi cải thiện, rồi so sánh các định nghĩa trước mọi quyết định di chuyển của con người. Đầu ra chính vẫn được bảo vệ bằng hash.

Bước 01 — Tải các kế hoạch dưới dạng khái niệm có định danh

SKOS, nhãn và phân cấp

Lỗi: Các khái niệm bị gộp chỉ theo nhãn của chúng

Khắc phục: Giữ URI và kế hoạch gốc; từ chối chu trình ở mức tối thiểu.

Tệp src/ged_migration_taxonomies/data.py

import hashlib, json, random
from .config import save_json

DOMAINS = [
    "water pumping",
    "railway switches",
    "solar panels",
    "industrial cooling",
    "harbour cranes",
    "food conveyors",
    "hospital ventilation",
    "urban elevators",
    "hydraulic presses",
    "optical sensors",
    "textile looms",
    "district heating",
    "wind turbines",
    "laboratory freezers",
    "mining drills",
    "airport lighting",
    "marine generators",
    "paper rollers",
    "waste sorting",
    "telecom antennas",
    "irrigation gates",
    "battery storage",
    "ceramic furnaces",
    "robotic welders",
]
SOURCE = [
    (
        "Visites atelier",
        "Scheduled routine and exceptional site visits for equipment maintenance.",
    ),
    ("Inspections", "Safety inspections and conformity observations."),
    (
        "Étalonnage",
        "Calibration of measurement instruments against reference standards.",
    ),
    ("Remises en état", "Repair of defective components and restoration of service."),
    ("Commandes neuves", "Purchase orders for new equipment from external suppliers."),
    ("Renouvellements", "Replacement equipment procurement from external suppliers."),
    (
        "Ancien registre papier",
        "Retired paper visitor sign-in book; no maintenance content.",
    ),
    (
        "Comptes rendus",
        "Technical reports covering completed jobs and incident investigations.",
    ),
]
TARGET = [
    ("Tournées programmées", "Routine scheduled equipment maintenance visits."),
    ("Déplacements urgents", "Exceptional unscheduled equipment maintenance visits."),
    ("Contrôles sécurité", "Safety inspection records and conformity observations."),
    ("Réglage métrologique", "Instrument calibration against reference standards."),
    ("Réparations", "Restoration of defective components to working service."),
    (
        "Approvisionnements",
        "Purchase and replacement equipment procurement from suppliers.",
    ),
    ("Rapports de travaux", "Technical reports about completed maintenance work."),
    ("Analyses incidents", "Technical investigation reports concerning incidents."),
    ("Archives financières", "Historical financial ledger and payment records."),
    ("Innovation", "Proposals for new engineering research projects."),
]
LINKS = [[0, 1], [2], [3], [4], [5], [5], [], [6, 7]]


def validate_broader(concepts):
    ids = {c["id"] for c in concepts}
    if len(ids) != len(concepts):
        raise ValueError("Duplicate concept ID")
    byid = {c["id"]: c for c in concepts}
    for c in concepts:
        seen = {c["id"]}
        parent = c["broader"]
        while parent is not None:
            if parent not in byid or parent in seen:
                raise ValueError("Invalid or cyclic broader relation")
            seen.add(parent)
            parent = byid[parent]["broader"]


def ancestors(concept, concepts):
    validate_broader(concepts)
    byid = {c["id"]: c for c in concepts}
    values = []
    parent = concept["broader"]
    while parent is not None:
        c = byid[parent]
        values.append(c["label"] + ". " + c["definition"])
        parent = c["broader"]
    return (
        " Ancestor: ".join(values)
        if values
        else "Root concept of this independent classification scheme."
    )


def prepare(config):
    pairs = []
    gold = []
    pieces = []
    templates = {}
    for family, domain in enumerate(DOMAINS):
        split = "calibration" if family < 12 else "test"
        template = (
            f"Independent {domain} vocabulary revision {family+1}."
            if split == "calibration"
            else f"The {domain} documentation office defines scope {family+1} below."
        )
        templates[str(family)] = {
            "id": f"template-{family:02d}",
            "split": split,
            "text": template,
        }
        source = []
        target = []
        for side, specs, parents in [
            ("s", SOURCE, [None, 0, 0, 0, None, 4, None, 0]),
            ("t", TARGET, [None, 0, 0, 2, 0, None, 0, 6, None, None]),
        ]:
            values = []
            for i, (label, definition) in enumerate(specs):
                values.append(
                    {
                        "id": f"P{family:02d}-{side}{i}",
                        "label": label,
                        "definition": f"{template} {definition} Domain: {domain}.",
                        "broader": (
                            None
                            if parents[i] is None
                            else f"P{family:02d}-{side}{parents[i]}"
                        ),
                        "business_scope": (
                            "financial" if side == "t" and i == 8 else "technical"
                        ),
                        "active": True,
                    }
                )
            validate_broader(values)
            if side == "s":
                source = values
            else:
                target = values
        rng = random.Random(17000 + family)
        src = list(range(8)) + [rng.randrange(8) for _ in range(56)]
        tgt = list(range(10)) + [rng.randrange(10) for _ in range(54)]
        rng.shuffle(src)
        rng.shuffle(tgt)
        for j, (a, b) in enumerate(zip(src, tgt)):
            body = f"Inventory note {family:02d}-{j:02d} about {domain}. Independently recorded source and destination filing loads, not an alignment annotation."
            pieces.append(
                {
                    "piece_id": f"piece-{family:02d}-{j:02d}",
                    "pair": family,
                    "text": body,
                    "text_sha256": hashlib.sha256(body.encode()).hexdigest(),
                    "source_bucket": source[a]["id"],
                    "target_bucket": target[b]["id"],
                }
            )
        a = [src.count(i) / 64 for i in range(8)]
        b = [tgt.count(i) / 64 for i in range(10)]
        pair = {
            "pair": family,
            "split": split,
            "domain": domain,
            "template_id": f"template-{family:02d}",
            "source": source,
            "target": target,
            "a": a,
            "b": b,
            "mass_source": "64 independently simulated filing-load notes; not gold link counts",
        }
        pairs.append(pair)
        gold.append(
            {
                "pair": family,
                "split": split,
                "links": [
                    [source[i]["id"], target[j]["id"]]
                    for i, links in enumerate(LINKS)
                    for j in links
                ],
                "without_equivalent": [source[6]["id"]],
            }
        )
    save_json("data/generated/input/pages.json", pairs)
    save_json("data/generated/input/pieces.json", pieces)
    save_json("data/generated/input/templates.json", templates)
    save_json("data/generated/labels/gold.json", gold)
    save_json(
        "data/generated/labels/validation.json",
        [g for g in gold if g["split"] == "calibration"],
    )
    save_json(
        "data/generated/manifest.json",
        {
            "pairs": 24,
            "source_concepts": 192,
            "target_concepts": 240,
            "pieces": 1536,
            "calibration_pairs": 12,
            "test_pairs": 12,
            "model_features_have_gold": False,
            "shared_semantic_relation_pattern": True,
            "domain_and_template_disjoint": True,
            "limits": "Concept relation pattern and synonym prototypes shared across pairs; distinct domains/templates do not make the task fully out-of-distribution.",
        },
    )

Bước 02 — Xây dựng tương ứng văn bản đầu tiên

Embedding và chi phí đối xứng

Lỗi: Các tương ứng gold phục vụ ngữ cảnh tổ tiên

Khắc phục: Xây dựng ngữ cảnh chỉ từ broader, không truy cập mapping gold.

Tệp src/ged_migration_taxonomies/features.py

import numpy as np
from .encoder import Encoder
from .data import ancestors
from .config import read_json, save_json


def baseline(config):
    pairs = read_json("data/generated/input/pages.json")
    encoder = Encoder(config)
    rows = []
    for pair in pairs:
        concepts = pair["source"] + pair["target"]
        texts = [c["label"] + ". " + c["definition"] for c in concepts]
        contexts = [
            ancestors(c, pair["source"] if i < 8 else pair["target"])
            for i, c in enumerate(concepts)
        ]
        values = encoder.encode_queries(texts).astype(np.float64)
        context = encoder.encode_queries(contexts).astype(np.float64)
        cosine = np.clip(values[:8] @ values[8:].T, -1, 1)
        ancestor_cosine = np.clip(context[:8] @ context[8:].T, -1, 1)
        cost = 1 - cosine + config["ancestor_weight"] * (1 - ancestor_cosine)
        rows.append(
            {
                "pair": pair["pair"],
                "concept_ids": [c["id"] for c in concepts],
                "texts": texts,
                "contexts": contexts,
                "embeddings": values.tolist(),
                "ancestor_embeddings": context.tolist(),
                "cosine": cosine.tolist(),
                "ancestor_cosine": ancestor_cosine.tolist(),
                "cost": cost.tolist(),
                "top1": [int(np.argmax(row)) for row in cosine],
            }
        )
    save_json(
        "outputs/features.json",
        {
            "rows": rows,
            "encoder": encoder.identity,
            "encoding_calls": encoder.calls,
            "labels_visible": False,
            "ancestor_weight": config["ancestor_weight"],
        },
    )

Bước 03 — Suy ra các hệ số co giãn của transport

Entropy, KL của biên và log-space

Lỗi: So sánh reg_type='kl' với phép tính entropy của bài thực hành

Khắc phục: Truyền reg_type='entropy' vào tham chiếu và giải thích khác biệt quy ước này.

Tệp src/ged_migration_taxonomies/transport.py

"""Entropy-regularized transport in float64 log space; explicit mass semantics."""

import math, time, warnings
import numpy as np


def logsumexp(values, axis):
    maximum = np.max(values, axis=axis, keepdims=True)
    return (
        maximum + np.log(np.exp(values - maximum).sum(axis=axis, keepdims=True))
    ).squeeze(axis)


def generalized_kl(p, q):
    p = np.asarray(p, dtype=np.float64)
    q = np.asarray(q, dtype=np.float64)
    if (
        np.any(p < 0)
        or np.any(q < 0)
        or not np.isfinite(p).all()
        or not np.isfinite(q).all()
    ):
        raise ValueError("Invalid KL masses")
    if np.any((q == 0) & (p > 0)):
        return math.inf
    active = p > 0
    return float(
        np.sum(p[active] * np.log(p[active] / q[active])) - np.sum(p) + np.sum(q)
    )


def objective(plan, cost, a, b, epsilon, rho_a, rho_b):
    positive = plan > 0
    entropy = float(np.sum(plan[positive] * (np.log(plan[positive]) - 1)))
    linear = float(np.sum(plan * cost))
    left = generalized_kl(plan.sum(1), a)
    right = generalized_kl(plan.sum(0), b)
    value = linear + epsilon * entropy
    if math.isfinite(rho_a):
        value += rho_a * left
    if math.isfinite(rho_b):
        value += rho_b * right
    return {
        "linear": linear,
        "negative_entropy": entropy,
        "row_KL": left,
        "column_KL": right,
        "regularized": value,
        "convention": "P*(logP-1); generalized KL includes -p+q",
    }


def sinkhorn_unbalanced(
    a, b, cost, epsilon, rho_a, rho_b=None, tolerance=1e-11, max_iterations=20000
):
    started = time.perf_counter()
    a = np.asarray(a, dtype=np.float64)
    b = np.asarray(b, dtype=np.float64)
    cost = np.asarray(cost, dtype=np.float64)
    rho_b = rho_a if rho_b is None else rho_b
    if a.ndim != 1 or b.ndim != 1 or cost.shape != (len(a), len(b)):
        raise ValueError("Mass/cost dimensions")
    if (
        not np.isfinite(a).all()
        or not np.isfinite(b).all()
        or not np.isfinite(cost).all()
        or np.any(a < 0)
        or np.any(b < 0)
        or a.sum() <= 0
        or b.sum() <= 0
    ):
        raise ValueError("Finite nonnegative masses with positive total required")
    if (
        not math.isfinite(epsilon)
        or epsilon <= 0
        or any(math.isnan(r) or r <= 0 for r in [rho_a, rho_b])
        or tolerance <= 0
        or max_iterations < 1
    ):
        raise ValueError("Invalid regularization/convergence configuration")
    balanced = math.isinf(rho_a) and math.isinf(rho_b)
    if balanced and abs(a.sum() - b.sum()) > 1e-12:
        raise ValueError("Balanced totals differ")
    active_a = a > 0
    active_b = b > 0
    aa = a[active_a]
    bb = b[active_b]
    C = cost[np.ix_(active_a, active_b)]
    kernel = -C / epsilon
    lu = np.zeros(len(aa))
    lv = np.zeros(len(bb))
    fa = 1.0 if math.isinf(rho_a) else rho_a / (rho_a + epsilon)
    fb = 1.0 if math.isinf(rho_b) else rho_b / (rho_b + epsilon)
    residual = math.inf
    for iteration in range(1, max_iterations + 1):
        new_u = fa * (np.log(aa) - logsumexp(kernel + lv[None, :], axis=1))
        new_v = fb * (np.log(bb) - logsumexp(kernel + new_u[:, None], axis=0))
        residual = max(
            float(np.max(np.abs(new_u - lu))), float(np.max(np.abs(new_v - lv)))
        )
        lu, lv = new_u, new_v
        if not np.isfinite(lu).all() or not np.isfinite(lv).all():
            raise FloatingPointError("Non-finite Sinkhorn dual iterate")
        if residual <= tolerance:
            break
    else:
        raise RuntimeError(
            f"Sinkhorn did not converge after {max_iterations} iterations; residual={residual}"
        )
    small = np.exp(lu[:, None] + kernel + lv[None, :])
    plan = np.zeros_like(cost)
    plan[np.ix_(active_a, active_b)] = small
    if not np.isfinite(plan).all():
        raise FloatingPointError("Non-finite transport plan")
    row = plan.sum(1)
    col = plan.sum(0)
    if balanced and max(np.max(np.abs(row - a)), np.max(np.abs(col - b))) > 1e-8:
        raise RuntimeError("Balanced marginal residual too large")
    return {
        "plan": plan.tolist(),
        "row_mass": row.tolist(),
        "column_mass": col.tolist(),
        "row_deficit": np.maximum(a - row, 0).tolist(),
        "row_excess": np.maximum(row - a, 0).tolist(),
        "column_deficit": np.maximum(b - col, 0).tolist(),
        "column_excess": np.maximum(col - b, 0).tolist(),
        "objective": objective(plan, cost, a, b, epsilon, rho_a, rho_b),
        "iterations": iteration,
        "residual": residual,
        "seconds": time.perf_counter() - started,
        "active_rows": np.flatnonzero(active_a).tolist(),
        "active_columns": np.flatnonzero(active_b).tolist(),
        "balanced": balanced,
        "epsilon": epsilon,
        "rho_a": None if math.isinf(rho_a) else rho_a,
        "rho_b": None if math.isinf(rho_b) else rho_b,
    }


def compare_pot(a, b, cost, epsilon, rho_a, rho_b=None, tolerance=1e-8):
    import ot

    rho_b = rho_a if rho_b is None else rho_b
    a = np.asarray(a, dtype=np.float64)
    b = np.asarray(b, dtype=np.float64)
    cost = np.asarray(cost, dtype=np.float64)
    ours = sinkhorn_unbalanced(a, b, cost, epsilon, rho_a, rho_b)
    ia = a > 0
    ib = b > 0
    with warnings.catch_warnings(record=True) as caught:
        warnings.simplefilter("always")
        small, info = ot.unbalanced.sinkhorn_unbalanced(
            a[ia],
            b[ib],
            cost[np.ix_(ia, ib)],
            epsilon,
            (rho_a, rho_b),
            reg_type="entropy",
            numItermax=20000,
            stopThr=1e-13,
            log=True,
        )
    reference = np.zeros_like(cost)
    reference[np.ix_(ia, ib)] = small
    error = float(np.max(np.abs(reference - np.asarray(ours["plan"]))))
    if error > tolerance:
        raise AssertionError(f"POT plan discrepancy {error} > {tolerance}")
    return {
        "ours": ours,
        "reference_plan": reference.tolist(),
        "max_plan_error": error,
        "tolerance": tolerance,
        "reg_type": "entropy",
        "pot_version": ot.__version__,
        "pot_reported_total_cost": float(info["total_cost"]),
        "reference_objective_recomputed": objective(
            reference, cost, a, b, epsilon, rho_a, rho_b
        ),
        "warnings": [str(w.message) for w in caught],
    }

Bước 04 — Giải các plan có gộp và tách

Nới lỏng biên và rà soát

Lỗi: Mọi hàng được chuẩn hóa thành 1 rồi gọi là độ tin cậy

Khắc phục: Giữ khối lượng thô, biên, chi phí và quy tắc rà soát đã hiệu chỉnh.

Tệp src/ged_migration_taxonomies/run.py

import itertools, math
import numpy as np
from .config import read_json, save_json, sha
from .transport import sinkhorn_unbalanced, compare_pot
from .proposals import propose, metrics


def inspect_mechanism(config):
    a = [0.7, 0.3]
    b = [0.4, 0.6]
    cost = [[0.1, 0.9], [0.8, 0.2]]
    reference = compare_pot(a, b, cost, 0.1, 1.0, 0.7)
    permutation = sinkhorn_unbalanced(
        a[::-1], b[::-1], np.asarray(cost)[::-1, ::-1], 0.1, 1.0, 0.7
    )
    if (
        np.max(
            np.abs(
                np.asarray(reference["ours"]["plan"])
                - np.asarray(permutation["plan"])[::-1, ::-1]
            )
        )
        > 1e-10
    ):
        raise AssertionError("Permutation failure")
    zero = sinkhorn_unbalanced([1.0, 0.0], [0.4, 0.6], cost, 0.1, 1.0)
    save_json(
        "outputs/mechanism.json",
        {
            "a": a,
            "b": b,
            "cost": cost,
            "reference": reference,
            "permutation": permutation,
            "zero_mass": zero,
            "status": "PASS",
        },
    )


def compute_all(config):
    pairs = read_json("data/generated/input/pages.json")
    features = {r["pair"]: r for r in read_json("outputs/features.json")["rows"]}
    solutions = []
    for pair in pairs:
        for eps in config["epsilon_grid"]:
            solutions.append(
                {
                    "pair": pair["pair"],
                    "variant": "balanced",
                    "epsilon": eps,
                    "rho": None,
                    "solution": sinkhorn_unbalanced(
                        pair["a"],
                        pair["b"],
                        features[pair["pair"]]["cost"],
                        eps,
                        math.inf,
                    ),
                }
            )
            for rho in config["rho_grid"]:
                solutions.append(
                    {
                        "pair": pair["pair"],
                        "variant": "unbalanced",
                        "epsilon": eps,
                        "rho": rho,
                        "solution": sinkhorn_unbalanced(
                            pair["a"],
                            pair["b"],
                            features[pair["pair"]]["cost"],
                            eps,
                            rho,
                            tolerance=config["solver_tolerance"],
                            max_iterations=config["max_iterations"],
                        ),
                    }
                )
    save_json("outputs/plans.json", solutions)


def calibrate(config):
    pairs = read_json("data/generated/input/pages.json")
    features = {r["pair"]: r for r in read_json("outputs/features.json")["rows"]}
    plans = read_json("outputs/plans.json")
    gold = {g["pair"]: g for g in read_json("data/generated/labels/validation.json")}
    if set(gold) != set(range(12)):
        raise ValueError("Calibration gold only required")
    lookup = {
        (r["pair"], r["variant"], r["epsilon"], r["rho"]): r["solution"] for r in plans
    }
    choices = {}
    tables = {}
    for variant in ["top1", "balanced", "unbalanced"]:
        options = (
            [(None, None, None, None, t) for t in config["cosine_threshold_grid"]]
            if variant == "top1"
            else [
                (e, r, l, m, None)
                for e in config["epsilon_grid"]
                for r in ([None] if variant == "balanced" else config["rho_grid"])
                for l in config["link_mass_ratio_grid"]
                for m in config["minimum_row_mass_ratio_grid"]
            ]
        )
        rows = []
        for eps, rho, link, row, cos in options:
            total = {"tp": 0, "fp": 0, "fn": 0}
            for pair in pairs[:12]:
                solution = (
                    None
                    if variant == "top1"
                    else lookup[pair["pair"], variant, eps, rho]
                )
                result = metrics(
                    propose(
                        pair, features[pair["pair"]], variant, solution, link, row, cos
                    ),
                    gold[pair["pair"]],
                )
                for k in total:
                    total[k] += result[k]
            score = (
                2 * total["tp"] / (2 * total["tp"] + total["fp"] + total["fn"])
                if 2 * total["tp"] + total["fp"] + total["fn"]
                else 0.0
            )
            rows.append(
                {
                    "epsilon": eps,
                    "rho": rho,
                    "link_ratio": link,
                    "row_ratio": row,
                    "cosine_threshold": cos,
                    "counts": total,
                    "micro_F1": score,
                }
            )
        # Stable predefined grid order breaks ties, independent of test labels.
        winner = max(range(len(rows)), key=lambda i: (rows[i]["micro_F1"], -i))
        choices[variant] = dict(rows[winner], grid_index=winner)
        tables[variant] = rows
    save_json(
        "outputs/calibration.json",
        {
            "pairs": list(range(12)),
            "choices": choices,
            "grid": tables,
            "tie": "first in predefined grid",
            "test_gold_visible": False,
        },
    )


def evaluate(config):
    pairs = read_json("data/generated/input/pages.json")
    features = {r["pair"]: r for r in read_json("outputs/features.json")["rows"]}
    plans = read_json("outputs/plans.json")
    gold = {g["pair"]: g for g in read_json("data/generated/labels/gold.json")}
    selected = read_json("outputs/calibration.json")["choices"]
    lookup = {
        (r["pair"], r["variant"], r["epsilon"], r["rho"]): r["solution"] for r in plans
    }
    rows = []
    for pair in pairs:
        for variant, choice in selected.items():
            solution = (
                None
                if variant == "top1"
                else lookup[pair["pair"], variant, choice["epsilon"], choice["rho"]]
            )
            proposals = propose(
                pair,
                features[pair["pair"]],
                variant,
                solution,
                choice["link_ratio"],
                choice["row_ratio"],
                choice["cosine_threshold"],
            )
            rows.append(
                {
                    "pair": pair["pair"],
                    "split": pair["split"],
                    "variant": variant,
                    "proposals": proposals,
                    "metrics": metrics(proposals, gold[pair["pair"]]),
                    "choice": choice,
                }
            )
    save_json(
        "outputs/evaluation.json",
        {
            "rows": rows,
            "populations": {"calibration_pairs": 12, "test_pairs": 12},
            "applied": False,
            "semantics": "proposals only; transported mass is not probability",
        },
    )


def exercise(config):
    protected = {
        p: sha(p)
        for p in [
            "outputs/features.json",
            "outputs/calibration.json",
            "outputs/evaluation.json",
        ]
    }
    pair = read_json("data/generated/input/pages.json")[12]
    original = list(pair["a"])
    pair["a"][0] *= 10
    total = sum(pair["a"])
    pair["a"] = [v / total for v in pair["a"]]
    feature = read_json("outputs/features.json")["rows"][12]
    gold = read_json("data/generated/labels/gold.json")[12]
    choices = read_json("outputs/calibration.json")["choices"]
    rows = []
    for variant, c in choices.items():
        solution = (
            None
            if variant == "top1"
            else sinkhorn_unbalanced(
                pair["a"],
                pair["b"],
                feature["cost"],
                c["epsilon"],
                math.inf if variant == "balanced" else c["rho"],
            )
        )
        proposals = propose(
            pair,
            feature,
            variant,
            solution,
            c["link_ratio"],
            c["row_ratio"],
            c["cosine_threshold"],
        )
        rows.append(
            {
                "variant": variant,
                "solution": solution,
                "proposals": proposals,
                "metrics": metrics(proposals, gold),
            }
        )
    save_json(
        "outputs/exercise.json",
        {
            "pair": 12,
            "source_label": pair["source"][0]["label"],
            "original_a": original,
            "changed_a": pair["a"],
            "b": pair["b"],
            "multiplier": 10,
            "rows": rows,
            "protected_sha256": protected,
            "main_unchanged": all(sha(p) == v for p, v in protected.items()),
            "applied": False,
        },
    )

Bước 05 — Đánh giá mapping, không chỉ chi phí của nó

Gold nhiều-đến-nhiều

Lỗi: Một objective thấp bị diễn giải là ontology tốt

Khắc phục: Đối chiếu với gold và phân tích lỗi định nghĩa cùng văn bản của chúng.

Tệp src/ged_migration_taxonomies/run.py

import itertools, math
import numpy as np
from .config import read_json, save_json, sha
from .transport import sinkhorn_unbalanced, compare_pot
from .proposals import propose, metrics


def inspect_mechanism(config):
    a = [0.7, 0.3]
    b = [0.4, 0.6]
    cost = [[0.1, 0.9], [0.8, 0.2]]
    reference = compare_pot(a, b, cost, 0.1, 1.0, 0.7)
    permutation = sinkhorn_unbalanced(
        a[::-1], b[::-1], np.asarray(cost)[::-1, ::-1], 0.1, 1.0, 0.7
    )
    if (
        np.max(
            np.abs(
                np.asarray(reference["ours"]["plan"])
                - np.asarray(permutation["plan"])[::-1, ::-1]
            )
        )
        > 1e-10
    ):
        raise AssertionError("Permutation failure")
    zero = sinkhorn_unbalanced([1.0, 0.0], [0.4, 0.6], cost, 0.1, 1.0)
    save_json(
        "outputs/mechanism.json",
        {
            "a": a,
            "b": b,
            "cost": cost,
            "reference": reference,
            "permutation": permutation,
            "zero_mass": zero,
            "status": "PASS",
        },
    )


def compute_all(config):
    pairs = read_json("data/generated/input/pages.json")
    features = {r["pair"]: r for r in read_json("outputs/features.json")["rows"]}
    solutions = []
    for pair in pairs:
        for eps in config["epsilon_grid"]:
            solutions.append(
                {
                    "pair": pair["pair"],
                    "variant": "balanced",
                    "epsilon": eps,
                    "rho": None,
                    "solution": sinkhorn_unbalanced(
                        pair["a"],
                        pair["b"],
                        features[pair["pair"]]["cost"],
                        eps,
                        math.inf,
                    ),
                }
            )
            for rho in config["rho_grid"]:
                solutions.append(
                    {
                        "pair": pair["pair"],
                        "variant": "unbalanced",
                        "epsilon": eps,
                        "rho": rho,
                        "solution": sinkhorn_unbalanced(
                            pair["a"],
                            pair["b"],
                            features[pair["pair"]]["cost"],
                            eps,
                            rho,
                            tolerance=config["solver_tolerance"],
                            max_iterations=config["max_iterations"],
                        ),
                    }
                )
    save_json("outputs/plans.json", solutions)


def calibrate(config):
    pairs = read_json("data/generated/input/pages.json")
    features = {r["pair"]: r for r in read_json("outputs/features.json")["rows"]}
    plans = read_json("outputs/plans.json")
    gold = {g["pair"]: g for g in read_json("data/generated/labels/validation.json")}
    if set(gold) != set(range(12)):
        raise ValueError("Calibration gold only required")
    lookup = {
        (r["pair"], r["variant"], r["epsilon"], r["rho"]): r["solution"] for r in plans
    }
    choices = {}
    tables = {}
    for variant in ["top1", "balanced", "unbalanced"]:
        options = (
            [(None, None, None, None, t) for t in config["cosine_threshold_grid"]]
            if variant == "top1"
            else [
                (e, r, l, m, None)
                for e in config["epsilon_grid"]
                for r in ([None] if variant == "balanced" else config["rho_grid"])
                for l in config["link_mass_ratio_grid"]
                for m in config["minimum_row_mass_ratio_grid"]
            ]
        )
        rows = []
        for eps, rho, link, row, cos in options:
            total = {"tp": 0, "fp": 0, "fn": 0}
            for pair in pairs[:12]:
                solution = (
                    None
                    if variant == "top1"
                    else lookup[pair["pair"], variant, eps, rho]
                )
                result = metrics(
                    propose(
                        pair, features[pair["pair"]], variant, solution, link, row, cos
                    ),
                    gold[pair["pair"]],
                )
                for k in total:
                    total[k] += result[k]
            score = (
                2 * total["tp"] / (2 * total["tp"] + total["fp"] + total["fn"])
                if 2 * total["tp"] + total["fp"] + total["fn"]
                else 0.0
            )
            rows.append(
                {
                    "epsilon": eps,
                    "rho": rho,
                    "link_ratio": link,
                    "row_ratio": row,
                    "cosine_threshold": cos,
                    "counts": total,
                    "micro_F1": score,
                }
            )
        # Stable predefined grid order breaks ties, independent of test labels.
        winner = max(range(len(rows)), key=lambda i: (rows[i]["micro_F1"], -i))
        choices[variant] = dict(rows[winner], grid_index=winner)
        tables[variant] = rows
    save_json(
        "outputs/calibration.json",
        {
            "pairs": list(range(12)),
            "choices": choices,
            "grid": tables,
            "tie": "first in predefined grid",
            "test_gold_visible": False,
        },
    )


def evaluate(config):
    pairs = read_json("data/generated/input/pages.json")
    features = {r["pair"]: r for r in read_json("outputs/features.json")["rows"]}
    plans = read_json("outputs/plans.json")
    gold = {g["pair"]: g for g in read_json("data/generated/labels/gold.json")}
    selected = read_json("outputs/calibration.json")["choices"]
    lookup = {
        (r["pair"], r["variant"], r["epsilon"], r["rho"]): r["solution"] for r in plans
    }
    rows = []
    for pair in pairs:
        for variant, choice in selected.items():
            solution = (
                None
                if variant == "top1"
                else lookup[pair["pair"], variant, choice["epsilon"], choice["rho"]]
            )
            proposals = propose(
                pair,
                features[pair["pair"]],
                variant,
                solution,
                choice["link_ratio"],
                choice["row_ratio"],
                choice["cosine_threshold"],
            )
            rows.append(
                {
                    "pair": pair["pair"],
                    "split": pair["split"],
                    "variant": variant,
                    "proposals": proposals,
                    "metrics": metrics(proposals, gold[pair["pair"]]),
                    "choice": choice,
                }
            )
    save_json(
        "outputs/evaluation.json",
        {
            "rows": rows,
            "populations": {"calibration_pairs": 12, "test_pairs": 12},
            "applied": False,
            "semantics": "proposals only; transported mass is not probability",
        },
    )


def exercise(config):
    protected = {
        p: sha(p)
        for p in [
            "outputs/features.json",
            "outputs/calibration.json",
            "outputs/evaluation.json",
        ]
    }
    pair = read_json("data/generated/input/pages.json")[12]
    original = list(pair["a"])
    pair["a"][0] *= 10
    total = sum(pair["a"])
    pair["a"] = [v / total for v in pair["a"]]
    feature = read_json("outputs/features.json")["rows"][12]
    gold = read_json("data/generated/labels/gold.json")[12]
    choices = read_json("outputs/calibration.json")["choices"]
    rows = []
    for variant, c in choices.items():
        solution = (
            None
            if variant == "top1"
            else sinkhorn_unbalanced(
                pair["a"],
                pair["b"],
                feature["cost"],
                c["epsilon"],
                math.inf if variant == "balanced" else c["rho"],
            )
        )
        proposals = propose(
            pair,
            feature,
            variant,
            solution,
            c["link_ratio"],
            c["row_ratio"],
            c["cosine_threshold"],
        )
        rows.append(
            {
                "variant": variant,
                "solution": solution,
                "proposals": proposals,
                "metrics": metrics(proposals, gold),
            }
        )
    save_json(
        "outputs/exercise.json",
        {
            "pair": 12,
            "source_label": pair["source"][0]["label"],
            "original_a": original,
            "changed_a": pair["a"],
            "b": pair["b"],
            "multiplier": 10,
            "rows": rows,
            "protected_sha256": protected,
            "main_unchanged": all(sha(p) == v for p, v in protected.items()),
            "applied": False,
        },
    )

Bước 06 — Thay đổi trọng số của một mục chi phối

Độ nhạy với các phân phối

Lỗi: Solver được giới thiệu như đọc được ý định nghiệp vụ

Khắc phục: Chỉ ra các giả định về tải và đưa các thay đổi vào tệp rà soát.

Tệp src/ged_migration_taxonomies/run.py

import itertools, math
import numpy as np
from .config import read_json, save_json, sha
from .transport import sinkhorn_unbalanced, compare_pot
from .proposals import propose, metrics


def inspect_mechanism(config):
    a = [0.7, 0.3]
    b = [0.4, 0.6]
    cost = [[0.1, 0.9], [0.8, 0.2]]
    reference = compare_pot(a, b, cost, 0.1, 1.0, 0.7)
    permutation = sinkhorn_unbalanced(
        a[::-1], b[::-1], np.asarray(cost)[::-1, ::-1], 0.1, 1.0, 0.7
    )
    if (
        np.max(
            np.abs(
                np.asarray(reference["ours"]["plan"])
                - np.asarray(permutation["plan"])[::-1, ::-1]
            )
        )
        > 1e-10
    ):
        raise AssertionError("Permutation failure")
    zero = sinkhorn_unbalanced([1.0, 0.0], [0.4, 0.6], cost, 0.1, 1.0)
    save_json(
        "outputs/mechanism.json",
        {
            "a": a,
            "b": b,
            "cost": cost,
            "reference": reference,
            "permutation": permutation,
            "zero_mass": zero,
            "status": "PASS",
        },
    )


def compute_all(config):
    pairs = read_json("data/generated/input/pages.json")
    features = {r["pair"]: r for r in read_json("outputs/features.json")["rows"]}
    solutions = []
    for pair in pairs:
        for eps in config["epsilon_grid"]:
            solutions.append(
                {
                    "pair": pair["pair"],
                    "variant": "balanced",
                    "epsilon": eps,
                    "rho": None,
                    "solution": sinkhorn_unbalanced(
                        pair["a"],
                        pair["b"],
                        features[pair["pair"]]["cost"],
                        eps,
                        math.inf,
                    ),
                }
            )
            for rho in config["rho_grid"]:
                solutions.append(
                    {
                        "pair": pair["pair"],
                        "variant": "unbalanced",
                        "epsilon": eps,
                        "rho": rho,
                        "solution": sinkhorn_unbalanced(
                            pair["a"],
                            pair["b"],
                            features[pair["pair"]]["cost"],
                            eps,
                            rho,
                            tolerance=config["solver_tolerance"],
                            max_iterations=config["max_iterations"],
                        ),
                    }
                )
    save_json("outputs/plans.json", solutions)


def calibrate(config):
    pairs = read_json("data/generated/input/pages.json")
    features = {r["pair"]: r for r in read_json("outputs/features.json")["rows"]}
    plans = read_json("outputs/plans.json")
    gold = {g["pair"]: g for g in read_json("data/generated/labels/validation.json")}
    if set(gold) != set(range(12)):
        raise ValueError("Calibration gold only required")
    lookup = {
        (r["pair"], r["variant"], r["epsilon"], r["rho"]): r["solution"] for r in plans
    }
    choices = {}
    tables = {}
    for variant in ["top1", "balanced", "unbalanced"]:
        options = (
            [(None, None, None, None, t) for t in config["cosine_threshold_grid"]]
            if variant == "top1"
            else [
                (e, r, l, m, None)
                for e in config["epsilon_grid"]
                for r in ([None] if variant == "balanced" else config["rho_grid"])
                for l in config["link_mass_ratio_grid"]
                for m in config["minimum_row_mass_ratio_grid"]
            ]
        )
        rows = []
        for eps, rho, link, row, cos in options:
            total = {"tp": 0, "fp": 0, "fn": 0}
            for pair in pairs[:12]:
                solution = (
                    None
                    if variant == "top1"
                    else lookup[pair["pair"], variant, eps, rho]
                )
                result = metrics(
                    propose(
                        pair, features[pair["pair"]], variant, solution, link, row, cos
                    ),
                    gold[pair["pair"]],
                )
                for k in total:
                    total[k] += result[k]
            score = (
                2 * total["tp"] / (2 * total["tp"] + total["fp"] + total["fn"])
                if 2 * total["tp"] + total["fp"] + total["fn"]
                else 0.0
            )
            rows.append(
                {
                    "epsilon": eps,
                    "rho": rho,
                    "link_ratio": link,
                    "row_ratio": row,
                    "cosine_threshold": cos,
                    "counts": total,
                    "micro_F1": score,
                }
            )
        # Stable predefined grid order breaks ties, independent of test labels.
        winner = max(range(len(rows)), key=lambda i: (rows[i]["micro_F1"], -i))
        choices[variant] = dict(rows[winner], grid_index=winner)
        tables[variant] = rows
    save_json(
        "outputs/calibration.json",
        {
            "pairs": list(range(12)),
            "choices": choices,
            "grid": tables,
            "tie": "first in predefined grid",
            "test_gold_visible": False,
        },
    )


def evaluate(config):
    pairs = read_json("data/generated/input/pages.json")
    features = {r["pair"]: r for r in read_json("outputs/features.json")["rows"]}
    plans = read_json("outputs/plans.json")
    gold = {g["pair"]: g for g in read_json("data/generated/labels/gold.json")}
    selected = read_json("outputs/calibration.json")["choices"]
    lookup = {
        (r["pair"], r["variant"], r["epsilon"], r["rho"]): r["solution"] for r in plans
    }
    rows = []
    for pair in pairs:
        for variant, choice in selected.items():
            solution = (
                None
                if variant == "top1"
                else lookup[pair["pair"], variant, choice["epsilon"], choice["rho"]]
            )
            proposals = propose(
                pair,
                features[pair["pair"]],
                variant,
                solution,
                choice["link_ratio"],
                choice["row_ratio"],
                choice["cosine_threshold"],
            )
            rows.append(
                {
                    "pair": pair["pair"],
                    "split": pair["split"],
                    "variant": variant,
                    "proposals": proposals,
                    "metrics": metrics(proposals, gold[pair["pair"]]),
                    "choice": choice,
                }
            )
    save_json(
        "outputs/evaluation.json",
        {
            "rows": rows,
            "populations": {"calibration_pairs": 12, "test_pairs": 12},
            "applied": False,
            "semantics": "proposals only; transported mass is not probability",
        },
    )


def exercise(config):
    protected = {
        p: sha(p)
        for p in [
            "outputs/features.json",
            "outputs/calibration.json",
            "outputs/evaluation.json",
        ]
    }
    pair = read_json("data/generated/input/pages.json")[12]
    original = list(pair["a"])
    pair["a"][0] *= 10
    total = sum(pair["a"])
    pair["a"] = [v / total for v in pair["a"]]
    feature = read_json("outputs/features.json")["rows"][12]
    gold = read_json("data/generated/labels/gold.json")[12]
    choices = read_json("outputs/calibration.json")["choices"]
    rows = []
    for variant, c in choices.items():
        solution = (
            None
            if variant == "top1"
            else sinkhorn_unbalanced(
                pair["a"],
                pair["b"],
                feature["cost"],
                c["epsilon"],
                math.inf if variant == "balanced" else c["rho"],
            )
        )
        proposals = propose(
            pair,
            feature,
            variant,
            solution,
            c["link_ratio"],
            c["row_ratio"],
            c["cosine_threshold"],
        )
        rows.append(
            {
                "variant": variant,
                "solution": solution,
                "proposals": proposals,
                "metrics": metrics(proposals, gold),
            }
        )
    save_json(
        "outputs/exercise.json",
        {
            "pair": 12,
            "source_label": pair["source"][0]["label"],
            "original_a": original,
            "changed_a": pair["a"],
            "b": pair["b"],
            "multiplier": 10,
            "rows": rows,
            "protected_sha256": protected,
            "main_unchanged": all(sha(p) == v for p, v in protected.items()),
            "applied": False,
        },
    )

Nguồn

Chizat và cộng sự, Scaling Algorithms for Unbalanced Transport Problems mô tả họ thuật toán co giãn. POT 0.9.7 ghi lại solver tham chiếu và lựa chọn regularization của nó. SKOS Reference phân biệt quan hệ tổ chức và mapping; các đề xuất của bài thực hành không khẳng định chúng. E5 cố định.

Diễn giải quá trình phát triển

Trên 12 cặp kiểm thử, F1 trung bình theo cặp là 0,875 cho Top 1, 0,591636 cho transport cân bằng và 0,742633 cho transport không cân bằng. Tổng TP/FP/FN lần lượt là 84/0/24, 49/9/59 và 74/18/34. Hai transport lần lượt tạo 2 và 1 liên kết sai trên các khái niệm không tương đương; Top 1 tạo 0. Cả 192 plan đều hội tụ và kiểm tra POT 2×2 có sai số tối đa 2,4871e-12. Solver được kiểm tra, nhưng không có cải thiện độ liên quan so với baseline. Với kho dữ liệu này, Top 1 là lựa chọn được chất lượng đo được ủng hộ; nhu cầu phân phối khối lượng toàn cục cần một đánh giá nghiệp vụ mới.

Bài tập nhân tải của nguồn 0 lên mười rồi chuẩn hóa lại a; chi phí, biên đích và các ngưỡng giữ cố định. Biến thiên của khối lượng transport không thể hiện biến thiên xác suất liên kết. Hai heatmap có thang màu riêng: hãy so sánh giá trị trên các thanh, không chỉ màu sắc.

Quan sát trong quá trình phát triển

Các giá trị được tính lại từ đầu ra thô của lượt chạy này. Chúng không phải ngưỡng cải thiện; lượt chạy từ ZIP sẽ tính lại chúng trên cùng các tập dữ liệu.

{
  "status": "PASS",
  "pairs": 24,
  "calibration_pairs": 12,
  "test_pairs": 12,
  "source_concepts": 192,
  "target_concepts": 240,
  "pieces": 1536,
  "plans": 192,
  "calibration_choices": {
    "top1": {
      "epsilon": null,
      "rho": null,
      "link_ratio": null,
      "row_ratio": null,
      "cosine_threshold": 0.95,
      "counts": {
        "tp": 84,
        "fp": 0,
        "fn": 24
      },
      "micro_F1": 0.875,
      "grid_index": 19
    },
    "balanced": {
      "epsilon": 0.03,
      "rho": null,
      "link_ratio": 0.3,
      "row_ratio": 0.1,
      "cosine_threshold": null,
      "counts": {
        "tp": 66,
        "fp": 3,
        "fn": 42
      },
      "micro_F1": 0.7457627118644068,
      "grid_index": 4
    },
    "unbalanced": {
      "epsilon": 0.03,
      "rho": 0.1,
      "link_ratio": 0.3,
      "row_ratio": 0.1,
      "cosine_threshold": null,
      "counts": {
        "tp": 83,
        "fp": 4,
        "fn": 25
      },
      "micro_F1": 0.8512820512820513,
      "grid_index": 4
    }
  },
  "test_metrics": [
    {
      "pair": 12,
      "variant": "top1",
      "true_links": 9,
      "proposed_links": 7,
      "tp": 7,
      "fp": 0,
      "fn": 2,
      "precision": 1.0,
      "recall": 0.7777777777777778,
      "F1": 0.875,
      "covered_source_concepts": 7,
      "source_concepts": 8,
      "false_links_without_equivalent": 0,
      "without_equivalent_concepts": 1,
      "review_rows": 1,
      "excluded_rows": 0,
      "applied": false,
      "skos_exactMatch_assertions": 0
    },
    {
      "pair": 12,
      "variant": "balanced",
      "true_links": 9,
      "proposed_links": 4,
      "tp": 4,
      "fp": 0,
      "fn": 5,
      "precision": 1.0,
      "recall": 0.4444444444444444,
      "F1": 0.6153846153846154,
      "covered_source_concepts": 4,
      "source_concepts": 8,
      "false_links_without_equivalent": 0,
      "without_equivalent_concepts": 1,
      "review_rows": 3,
      "excluded_rows": 1,
      "applied": false,
      "skos_exactMatch_assertions": 0
    },
    {
      "pair": 12,
      "variant": "unbalanced",
      "true_links": 9,
      "proposed_links": 6,
      "tp": 6,
      "fp": 0,
      "fn": 3,
      "precision": 1.0,
      "recall": 0.6666666666666666,
      "F1": 0.8,
      "covered_source_concepts": 6,
      "source_concepts": 8,
      "false_links_without_equivalent": 0,
      "without_equivalent_concepts": 1,
      "review_rows": 1,
      "excluded_rows": 1,
      "applied": false,
      "skos_exactMatch_assertions": 0
    },
    {
      "pair": 13,
      "variant": "top1",
      "true_links": 9,
      "proposed_links": 7,
      "tp": 7,
      "fp": 0,
      "fn": 2,
      "precision": 1.0,
      "recall": 0.7777777777777778,
      "F1": 0.875,
      "covered_source_concepts": 7,
      "source_concepts": 8,
      "false_links_without_equivalent": 0,
      "without_equivalent_concepts": 1,
      "review_rows": 1,
      "excluded_rows": 0,
      "applied": false,
      "skos_exactMatch_assertions": 0
    },
    {
      "pair": 13,
      "variant": "balanced",
      "true_links": 9,
      "proposed_links": 4,
      "tp": 4,
      "fp": 0,
      "fn": 5,
      "precision": 1.0,
      "recall": 0.4444444444444444,
      "F1": 0.6153846153846154,
      "covered_source_concepts": 4,
      "source_concepts": 8,
      "false_links_without_equivalent": 0,
      "without_equivalent_concepts": 1,
      "review_rows": 3,
      "excluded_rows": 1,
      "applied": false,
      "skos_exactMatch_assertions": 0
    },
    {
      "pair": 13,
      "variant": "unbalanced",
      "true_links": 9,
      "proposed_links": 8,
      "tp": 7,
      "fp": 1,
      "fn": 2,
      "precision": 0.875,
      "recall": 0.7777777777777778,
      "F1": 0.8235294117647058,
      "covered_source_concepts": 6,
      "source_concepts": 8,
      "false_links_without_equivalent": 0,
      "without_equivalent_concepts": 1,
      "review_rows": 1,
      "excluded_rows": 1,
      "applied": false,
      "skos_exactMatch_assertions": 0
    },
    {
      "pair": 14,
      "variant": "top1",
      "true_links": 9,
      "proposed_links": 7,
      "tp": 7,
      "fp": 0,
      "fn": 2,
      "precision": 1.0,
      "recall": 0.7777777777777778,
      "F1": 0.875,
      "covered_source_concepts": 7,
      "source_concepts": 8,
      "false_links_without_equivalent": 0,
      "without_equivalent_concepts": 1,
      "review_rows": 1,
      "excluded_rows": 0,
      "applied": false,
      "skos_exactMatch_assertions": 0
    },
    {
      "pair": 14,
      "variant": "balanced",
      "true_links": 9,
      "proposed_links": 5,
      "tp": 5,
      "fp": 0,
      "fn": 4,
      "precision": 1.0,
      "recall": 0.5555555555555556,
      "F1": 0.7142857142857143,
      "covered_source_concepts": 5,
      "source_concepts": 8,
      "false_links_without_equivalent": 0,
      "without_equivalent_concepts": 1,
      "review_rows": 2,
      "excluded_rows": 1,
      "applied": false,
      "skos_exactMatch_assertions": 0
    },
    {
      "pair": 14,
      "variant": "unbalanced",
      "true_links": 9,
      "proposed_links": 9,
      "tp": 7,
      "fp": 2,
      "fn": 2,
      "precision": 0.7777777777777778,
      "recall": 0.7777777777777778,
      "F1": 0.7777777777777778,
      "covered_source_concepts": 6,
      "source_concepts": 8,
      "false_links_without_equivalent": 0,
      "without_equivalent_concepts": 1,
      "review_rows": 1,
      "excluded_rows": 1,
      "applied": false,
      "skos_exactMatch_assertions": 0
    },
    {
      "pair": 15,
      "variant": "top1",
      "true_links": 9,
      "proposed_links": 7,
      "tp": 7,
      "fp": 0,
      "fn": 2,
      "precision": 1.0,
      "recall": 0.7777777777777778,
      "F1": 0.875,
      "covered_source_concepts": 7,
      "source_concepts": 8,
      "false_links_without_equivalent": 0,
      "without_equivalent_concepts": 1,
      "review_rows": 1,
      "excluded_rows": 0,
      "applied": false,
      "skos_exactMatch_assertions": 0
    },
    {
      "pair": 15,
      "variant": "balanced",
      "true_links": 9,
      "proposed_links": 6,
      "tp": 3,
      "fp": 3,
      "fn": 6,
      "precision": 0.5,
      "recall": 0.3333333333333333,
      "F1": 0.4,
      "covered_source_concepts": 6,
      "source_concepts": 8,
      "false_links_without_equivalent": 1,
      "without_equivalent_concepts": 1,
      "review_rows": 2,
      "excluded_rows": 1,
      "applied": false,
      "skos_exactMatch_assertions": 0
    },
    {
      "pair": 15,
      "variant": "unbalanced",
      "true_links": 9,
      "proposed_links": 11,
      "tp": 6,
      "fp": 5,
      "fn": 3,
      "precision": 0.5454545454545454,
      "recall": 0.6666666666666666,
      "F1": 0.6,
      "covered_source_concepts": 8,
      "source_concepts": 8,
      "false_links_without_equivalent": 1,
      "without_equivalent_concepts": 1,
      "review_rows": 0,
      "excluded_rows": 1,
      "applied": false,
      "skos_exactMatch_assertions": 0
    },
    {
      "pair": 16,
      "variant": "top1",
      "true_links": 9,
      "proposed_links": 7,
      "tp": 7,
      "fp": 0,
      "fn": 2,
      "precision": 1.0,
      "recall": 0.7777777777777778,
      "F1": 0.875,
      "covered_source_concepts": 7,
      "source_concepts": 8,
      "false_links_without_equivalent": 0,
      "without_equivalent_concepts": 1,
      "review_rows": 1,
      "excluded_rows": 0,
      "applied": false,
      "skos_exactMatch_assertions": 0
    },
    {
      "pair": 16,
      "variant": "balanced",
      "true_links": 9,
      "proposed_links": 4,
      "tp": 4,
      "fp": 0,
      "fn": 5,
      "precision": 1.0,
      "recall": 0.4444444444444444,
      "F1": 0.6153846153846154,
      "covered_source_concepts": 4,
      "source_concepts": 8,
      "false_links_without_equivalent": 0,
      "without_equivalent_concepts": 1,
      "review_rows": 4,
      "excluded_rows": 0,
      "applied": false,
      "skos_exactMatch_assertions": 0
    },
    {
      "pair": 16,
      "variant": "unbalanced",
      "true_links": 9,
      "proposed_links": 6,
      "tp": 6,
      "fp": 0,
      "fn": 3,
      "precision": 1.0,
      "recall": 0.6666666666666666,
      "F1": 0.8,
      "covered_source_concepts": 6,
      "source_concepts": 8,
      "false_links_without_equivalent": 0,
      "without_equivalent_concepts": 1,
      "review_rows": 1,
      "excluded_rows": 1,
      "applied": false,
      "skos_exactMatch_assertions": 0
    },
    {
      "pair": 17,
      "variant": "top1",
      "true_links": 9,
      "proposed_links": 7,
      "tp": 7,
      "fp": 0,
      "fn": 2,
      "precision": 1.0,
      "recall": 0.7777777777777778,
      "F1": 0.875,
      "covered_source_concepts": 7,
      "source_concepts": 8,
      "false_links_without_equivalent": 0,
      "without_equivalent_concepts": 1,
      "review_rows": 1,
      "excluded_rows": 0,
      "applied": false,
      "skos_exactMatch_assertions": 0
    },
    {
      "pair": 17,
      "variant": "balanced",
      "true_links": 9,
      "proposed_links": 5,
      "tp": 5,
      "fp": 0,
      "fn": 4,
      "precision": 1.0,
      "recall": 0.5555555555555556,
      "F1": 0.7142857142857143,
      "covered_source_concepts": 5,
      "source_concepts": 8,
      "false_links_without_equivalent": 0,
      "without_equivalent_concepts": 1,
      "review_rows": 2,
      "excluded_rows": 1,
      "applied": false,
      "skos_exactMatch_assertions": 0
    },
    {
      "pair": 17,
      "variant": "unbalanced",
      "true_links": 9,
      "proposed_links": 7,
      "tp": 7,
      "fp": 0,
      "fn": 2,
      "precision": 1.0,
      "recall": 0.7777777777777778,
      "F1": 0.875,
      "covered_source_concepts": 6,
      "source_concepts": 8,
      "false_links_without_equivalent": 0,
      "without_equivalent_concepts": 1,
      "review_rows": 1,
      "excluded_rows": 1,
      "applied": false,
      "skos_exactMatch_assertions": 0
    },
    {
      "pair": 18,
      "variant": "top1",
      "true_links": 9,
      "proposed_links": 7,
      "tp": 7,
      "fp": 0,
      "fn": 2,
      "precision": 1.0,
      "recall": 0.7777777777777778,
      "F1": 0.875,
      "covered_source_concepts": 7,
      "source_concepts": 8,
      "false_links_without_equivalent": 0,
      "without_equivalent_concepts": 1,
      "review_rows": 1,
      "excluded_rows": 0,
      "applied": false,
      "skos_exactMatch_assertions": 0
    },
    {
      "pair": 18,
      "variant": "balanced",
      "true_links": 9,
      "proposed_links": 5,
      "tp": 4,
      "fp": 1,
      "fn": 5,
      "precision": 0.8,
      "recall": 0.4444444444444444,
      "F1": 0.5714285714285714,
      "covered_source_concepts": 5,
      "source_concepts": 8,
      "false_links_without_equivalent": 0,
      "without_equivalent_concepts": 1,
      "review_rows": 3,
      "excluded_rows": 0,
      "applied": false,
      "skos_exactMatch_assertions": 0
    },
    {
      "pair": 18,
      "variant": "unbalanced",
      "true_links": 9,
      "proposed_links": 8,
      "tp": 6,
      "fp": 2,
      "fn": 3,
      "precision": 0.75,
      "recall": 0.6666666666666666,
      "F1": 0.7058823529411765,
      "covered_source_concepts": 7,
      "source_concepts": 8,
      "false_links_without_equivalent": 0,
      "without_equivalent_concepts": 1,
      "review_rows": 1,
      "excluded_rows": 0,
      "applied": false,
      "skos_exactMatch_assertions": 0
    },
    {
      "pair": 19,
      "variant": "top1",
      "true_links": 9,
      "proposed_links": 7,
      "tp": 7,
      "fp": 0,
      "fn": 2,
      "precision": 1.0,
      "recall": 0.7777777777777778,
      "F1": 0.875,
      "covered_source_concepts": 7,
      "source_concepts": 8,
      "false_links_without_equivalent": 0,
      "without_equivalent_concepts": 1,
      "review_rows": 1,
      "excluded_rows": 0,
      "applied": false,
      "skos_exactMatch_assertions": 0
    },
    {
      "pair": 19,
      "variant": "balanced",
      "true_links": 9,
      "proposed_links": 5,
      "tp": 5,
      "fp": 0,
      "fn": 4,
      "precision": 1.0,
      "recall": 0.5555555555555556,
      "F1": 0.7142857142857143,
      "covered_source_concepts": 5,
      "source_concepts": 8,
      "false_links_without_equivalent": 0,
      "without_equivalent_concepts": 1,
      "review_rows": 2,
      "excluded_rows": 1,
      "applied": false,
      "skos_exactMatch_assertions": 0
    },
    {
      "pair": 19,
      "variant": "unbalanced",
      "true_links": 9,
      "proposed_links": 8,
      "tp": 7,
      "fp": 1,
      "fn": 2,
      "precision": 0.875,
      "recall": 0.7777777777777778,
      "F1": 0.8235294117647058,
      "covered_source_concepts": 6,
      "source_concepts": 8,
      "false_links_without_equivalent": 0,
      "without_equivalent_concepts": 1,
      "review_rows": 1,
      "excluded_rows": 1,
      "applied": false,
      "skos_exactMatch_assertions": 0
    },
    {
      "pair": 20,
      "variant": "top1",
      "true_links": 9,
      "proposed_links": 7,
      "tp": 7,
      "fp": 0,
      "fn": 2,
      "precision": 1.0,
      "recall": 0.7777777777777778,
      "F1": 0.875,
      "covered_source_concepts": 7,
      "source_concepts": 8,
      "false_links_without_equivalent": 0,
      "without_equivalent_concepts": 1,
      "review_rows": 1,
      "excluded_rows": 0,
      "applied": false,
      "skos_exactMatch_assertions": 0
    },
    {
      "pair": 20,
      "variant": "balanced",
      "true_links": 9,
      "proposed_links": 4,
      "tp": 4,
      "fp": 0,
      "fn": 5,
      "precision": 1.0,
      "recall": 0.4444444444444444,
      "F1": 0.6153846153846154,
      "covered_source_concepts": 4,
      "source_concepts": 8,
      "false_links_without_equivalent": 0,
      "without_equivalent_concepts": 1,
      "review_rows": 3,
      "excluded_rows": 1,
      "applied": false,
      "skos_exactMatch_assertions": 0
    },
    {
      "pair": 20,
      "variant": "unbalanced",
      "true_links": 9,
      "proposed_links": 6,
      "tp": 6,
      "fp": 0,
      "fn": 3,
      "precision": 1.0,
      "recall": 0.6666666666666666,
      "F1": 0.8,
      "covered_source_concepts": 5,
      "source_concepts": 8,
      "false_links_without_equivalent": 0,
      "without_equivalent_concepts": 1,
      "review_rows": 2,
      "excluded_rows": 1,
      "applied": false,
      "skos_exactMatch_assertions": 0
    },
    {
      "pair": 21,
      "variant": "top1",
      "true_links": 9,
      "proposed_links": 7,
      "tp": 7,
      "fp": 0,
      "fn": 2,
      "precision": 1.0,
      "recall": 0.7777777777777778,
      "F1": 0.875,
      "covered_source_concepts": 7,
      "source_concepts": 8,
      "false_links_without_equivalent": 0,
      "without_equivalent_concepts": 1,
      "review_rows": 1,
      "excluded_rows": 0,
      "applied": false,
      "skos_exactMatch_assertions": 0
    },
    {
      "pair": 21,
      "variant": "balanced",
      "true_links": 9,
      "proposed_links": 6,
      "tp": 5,
      "fp": 1,
      "fn": 4,
      "precision": 0.8333333333333334,
      "recall": 0.5555555555555556,
      "F1": 0.6666666666666666,
      "covered_source_concepts": 5,
      "source_concepts": 8,
      "false_links_without_equivalent": 0,
      "without_equivalent_concepts": 1,
      "review_rows": 3,
      "excluded_rows": 0,
      "applied": false,
      "skos_exactMatch_assertions": 0
    },
    {
      "pair": 21,
      "variant": "unbalanced",
      "true_links": 9,
      "proposed_links": 8,
      "tp": 6,
      "fp": 2,
      "fn": 3,
      "precision": 0.75,
      "recall": 0.6666666666666666,
      "F1": 0.7058823529411765,
      "covered_source_concepts": 6,
      "source_concepts": 8,
      "false_links_without_equivalent": 0,
      "without_equivalent_concepts": 1,
      "review_rows": 1,
      "excluded_rows": 1,
      "applied": false,
      "skos_exactMatch_assertions": 0
    },
    {
      "pair": 22,
      "variant": "top1",
      "true_links": 9,
      "proposed_links": 7,
      "tp": 7,
      "fp": 0,
      "fn": 2,
      "precision": 1.0,
      "recall": 0.7777777777777778,
      "F1": 0.875,
      "covered_source_concepts": 7,
      "source_concepts": 8,
      "false_links_without_equivalent": 0,
      "without_equivalent_concepts": 1,
      "review_rows": 1,
      "excluded_rows": 0,
      "applied": false,
      "skos_exactMatch_assertions": 0
    },
    {
      "pair": 22,
      "variant": "balanced",
      "true_links": 9,
      "proposed_links": 5,
      "tp": 3,
      "fp": 2,
      "fn": 6,
      "precision": 0.6,
      "recall": 0.3333333333333333,
      "F1": 0.42857142857142855,
      "covered_source_concepts": 5,
      "source_concepts": 8,
      "false_links_without_equivalent": 1,
      "without_equivalent_concepts": 1,
      "review_rows": 3,
      "excluded_rows": 1,
      "applied": false,
      "skos_exactMatch_assertions": 0
    },
    {
      "pair": 22,
      "variant": "unbalanced",
      "true_links": 9,
      "proposed_links": 9,
      "tp": 6,
      "fp": 3,
      "fn": 3,
      "precision": 0.6666666666666666,
      "recall": 0.6666666666666666,
      "F1": 0.6666666666666666,
      "covered_source_concepts": 6,
      "source_concepts": 8,
      "false_links_without_equivalent": 0,
      "without_equivalent_concepts": 1,
      "review_rows": 1,
      "excluded_rows": 1,
      "applied": false,
      "skos_exactMatch_assertions": 0
    },
    {
      "pair": 23,
      "variant": "top1",
      "true_links": 9,
      "proposed_links": 7,
      "tp": 7,
      "fp": 0,
      "fn": 2,
      "precision": 1.0,
      "recall": 0.7777777777777778,
      "F1": 0.875,
      "covered_source_concepts": 7,
      "source_concepts": 8,
      "false_links_without_equivalent": 0,
      "without_equivalent_concepts": 1,
      "review_rows": 1,
      "excluded_rows": 0,
      "applied": false,
      "skos_exactMatch_assertions": 0
    },
    {
      "pair": 23,
      "variant": "balanced",
      "true_links": 9,
      "proposed_links": 5,
      "tp": 3,
      "fp": 2,
      "fn": 6,
      "precision": 0.6,
      "recall": 0.3333333333333333,
      "F1": 0.42857142857142855,
      "covered_source_concepts": 4,
      "source_concepts": 8,
      "false_links_without_equivalent": 0,
      "without_equivalent_concepts": 1,
      "review_rows": 3,
      "excluded_rows": 1,
      "applied": false,
      "skos_exactMatch_assertions": 0
    },
    {
      "pair": 23,
      "variant": "unbalanced",
      "true_links": 9,
      "proposed_links": 6,
      "tp": 4,
      "fp": 2,
      "fn": 5,
      "precision": 0.6666666666666666,
      "recall": 0.4444444444444444,
      "F1": 0.5333333333333333,
      "covered_source_concepts": 5,
      "source_concepts": 8,
      "false_links_without_equivalent": 0,
      "without_equivalent_concepts": 1,
      "review_rows": 2,
      "excluded_rows": 1,
      "applied": false,
      "skos_exactMatch_assertions": 0
    }
  ],
  "POT_max_error": 2.4871216197652757e-12,
  "proposals_applied": false
}

Các kết quả quan sát được, phạm vi của chúng và thí nghiệm được đề xuất.

Chia sẻ bài viết