68 lines
2.4 KiB
Python
68 lines
2.4 KiB
Python
"""Ähnlichkeit für Merge-KANDIDATEN — entscheidet nie selbst (das tut das Panel).
|
||
sentence-transformers wenn vorhanden, sonst lexikalischer Jaccard-Fallback.
|
||
Verglichen werden DEFINITIONEN, nie Titel (Lektion: Titel-Embeddings rauschen)."""
|
||
|
||
import logging
|
||
import re
|
||
|
||
log = logging.getLogger("creator2.embedding")
|
||
|
||
_modell = None
|
||
_geladen = False
|
||
|
||
|
||
def _lade_modell():
|
||
global _modell, _geladen
|
||
if _geladen:
|
||
return _modell
|
||
_geladen = True
|
||
try:
|
||
from sentence_transformers import SentenceTransformer
|
||
_modell = SentenceTransformer("sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
|
||
log.info("Embedding-Modell geladen")
|
||
except Exception:
|
||
log.warning("kein Embedding-Modell — lexikalischer Jaccard-Fallback"
|
||
" (findet Paraphrasen-Dubletten deutlich schlechter);"
|
||
" Fix: pip install sentence-transformers")
|
||
_modell = None
|
||
return _modell
|
||
|
||
|
||
_WORT = re.compile(r"[a-zäöüß0-9]+")
|
||
|
||
|
||
def _tokens(text: str) -> set[str]:
|
||
return set(_WORT.findall(text.lower()))
|
||
|
||
|
||
def _jaccard(a: str, b: str) -> float:
|
||
ta, tb = _tokens(a), _tokens(b)
|
||
if not ta or not tb:
|
||
return 0.0
|
||
return len(ta & tb) / len(ta | tb)
|
||
|
||
|
||
def aehnlichkeit_matrix(texte: list[str]) -> list[list[float]]:
|
||
"""Paarweise Ähnlichkeit [0..1]. Symmetrisch, Diagonale 1."""
|
||
modell = _lade_modell()
|
||
n = len(texte)
|
||
if modell is not None:
|
||
import numpy as np
|
||
emb = modell.encode(texte, normalize_embeddings=True)
|
||
return (emb @ emb.T).tolist()
|
||
return [[1.0 if i == j else _jaccard(texte[i], texte[j]) for j in range(n)] for i in range(n)]
|
||
|
||
|
||
def kandidaten_paare(texte: list[str], floor: float,
|
||
fallback_floor: float | None = None) -> list[tuple[int, int, float]]:
|
||
"""Alle Index-Paare mit Ähnlichkeit ≥ Schwelle, absteigend sortiert.
|
||
Im Jaccard-Fallback gilt fallback_floor: echte Paraphrasen messen dort nur
|
||
~0.3–0.5 — die Kosinus-Schwelle ließe sie alle durchrutschen (aak Lauf 8:
|
||
13 Panel-Calls bei 26 überlebenden Dubletten-Gruppen)."""
|
||
m = aehnlichkeit_matrix(texte)
|
||
if _modell is None and fallback_floor is not None:
|
||
floor = fallback_floor
|
||
paare = [(i, j, m[i][j]) for i in range(len(texte)) for j in range(i + 1, len(texte))
|
||
if m[i][j] >= floor]
|
||
return sorted(paare, key=lambda p: -p[2])
|