vocal_helper.exemplar module
Regroupement incrémental sans centroïde : des exemplaires, et le meilleur point.
Pourquoi abandonner le centroïde
IncrementalClusterer tient un centroïde par
locuteur, c’est-à-dire une moyenne mobile de ses empreintes. Une moyenne est un
point, et un point ne peut pas représenter un étalement. Or c’est l’étalement
qui pose problème sur du matériel réel : la même voix embarquée près du micro et
loin du micro, de face et de profil, dans le silence et sous le brouhaha, occupe
une région — parfois deux régions franchement séparées. Le centroïde se pose
alors entre les deux modes, là où le locuteur ne se trouve jamais, et il est à
la fois trop loin de ses propres observations et trop près de celles du voisin.
La mesure dit exactement cela. Sur AMI, avec le seuil qui minimise le taux
d’erreur, le regroupement à centroïdes laisse vingt étiquettes pour quatre
locuteurs, et aucun réglage de seuil ne le corrige — quatre paramétrages ont
été essayés et réfutés (voir le commentaire en tête de
separating_threshold()). Pendant ce temps,
l’espace d’empreintes lui-même est sain : la variation d’une même voix tourne
autour de 0,12 quand la distance entre voix différentes tourne autour de 0,83,
soit un facteur sept. Ce n’est donc pas l’empreinte qui limite, c’est sa
réduction à un point.
Ce que fait ce module
Chaque locuteur n’est plus un point mais un ensemble borné d’exemplaires, chacun gardé avec sa confiance. La ressemblance à un locuteur est la distance à son meilleur point, pas à sa moyenne — un locuteur bimodal est alors reconnu par celui de ses deux modes qui correspond, au lieu d’être manqué par une moyenne qui n’en est aucun.
Quatre choses y gagnent par rapport au schéma à centroïdes :
Pas de moyenne. Une voix à deux régimes est représentée par les deux, pas par leur milieu.
La fusion globale devient définissable. Comparer deux ensembles d’exemplaires a un sens ; comparer deux moyennes mobiles n’en avait pas, car une moyenne a déjà perdu de quoi savoir si deux groupes se recouvrent. C’est ce qui permet de rattraper la sur-segmentation après coup, sans rien recalculer — le seul levier que le diagnostic laissait ouvert.
La confiance remplace la moyenne mobile aveugle. Un centroïde était mis à jour au même taux par une empreinte tirée de huit secondes de parole propre et par une tirée de quatre dixièmes. Ici la durée propre est la confiance, elle décide de l’admission et elle survit à l’éviction.
Le chaînage est réglable. Le meilleur point pur est un lien simple, donc sensible à un exemplaire aberrant qui ferait pont entre deux locuteurs.
neighbourspermet de demander le deuxième ou le troisième plus proche au lieu du premier, ce qui coupe le pont sans perdre la multimodalité.
Ce qui est conservé du schéma précédent, parce que c’est ce qui marchait : la contrainte d’injectivité par algorithme hongrois, qui interdit à deux voix simultanées de recevoir la même étiquette ; le plancher de confiance avant d’écrire quoi que ce soit ; et la création explicite d’un locuteur au-delà d’un seuil, qui est ce qui fait deviner le compte.
- class vocal_helper.exemplar.Exemplar(vector, confidence)[source]
Bases:
objectUne observation gardée, et ce qu’elle vaut.
- vector
L’empreinte, normalisée L2.
- Type:
NDArray[np.float32]
- confidence
Les secondes de parole propre dont elle a été tirée. C’est la seule mesure de qualité disponible sans vérité terrain, et elle est juste : une empreinte calculée sur huit secondes où le locuteur est seul vaut mieux qu’une calculée sur quatre dixièmes de seconde.
- Type:
- class vocal_helper.exemplar.ExemplarClusterer(policy=<factory>)[source]
Bases:
objectRattache les locuteurs locaux d’une fenêtre, sans jamais moyenner.
L’interface est celle de
IncrementalClusterer—assignprend des empreintes et des durées propres, rend des étiquettes — donc les deux sont interchangeables dansIncrementalDiarStage, et comparables à extraction identique.Examples
Deux voix orthogonales ouvrent le flux, chacune reçoit son étiquette, et la correspondance tient quand elles se présentent dans l’autre sens.
>>> import numpy as np >>> e0 = np.array([1.0, 0.0, 0.0], dtype=np.float32) >>> e1 = np.array([0.0, 1.0, 0.0], dtype=np.float32) >>> c = ExemplarClusterer() >>> c.assign(np.stack([e0, e1]), [5.0, 5.0]) ['S0', 'S1'] >>> c.assign(np.stack([e1, e0]), [5.0, 5.0]) ['S1', 'S0']
Voici ce qu’un centroïde ne peut pas faire. La même voix revient dans un autre régime, assez loin pour être gardée comme second exemplaire mais assez près pour être reconnue :
>>> other = np.array([0.8, 0.6, 0.0], dtype=np.float32) >>> c.assign(other[None, :], [5.0]) ['S0'] >>> c.exemplar_counts [2, 1]
Ce second régime est désormais reconnu exactement, parce que la mesure est la distance au meilleur point et non à une moyenne qui serait tombée entre les deux :
>>> float(round(c.distances(other[None, :])[0, 0], 6)) 0.0
Un quasi-doublon ne prend pas de place, il renforce la confiance :
>>> c.assign((other + 0.01 * e0)[None, :], [5.0]) ['S0'] >>> c.exemplar_counts [2, 1]
Et la fusion globale, que l’absence de moyenne rend définissable. Deux voix quasi identiques vues en même temps sont forcément séparées par la contrainte d’injectivité — c’est le mécanisme même de la sur-segmentation :
>>> d = ExemplarClusterer() >>> near = np.array([1.0, 0.1, 0.0], dtype=np.float32) >>> d.assign(np.stack([e0, near]), [5.0, 5.0]) ['S0', 'S1'] >>> d.merge() {'S1': 'S0'} >>> d.speaker_count 1
- Parameters:
policy (ExemplarPolicy)
- assign(embeddings, clean_durations)[source]
Étiqueter les locuteurs locaux d’une fenêtre.
- Parameters:
- Returns:
kétiquettes, dans l’ordre des lignes reçues.- Return type:
- Raises:
ValueError – Si les deux entrées n’ont pas la même longueur, ou si les empreintes ne sont pas une matrice.
- distances(embeddings)[source]
Distance de chaque empreinte au meilleur point de chaque locuteur.
Avec
neighbours > 1, c’est le k-ième plus proche et non le plus proche : un seul exemplaire aberrant ne suffit alors plus à rapprocher deux locuteurs.- Parameters:
embeddings (NDArray[np.float32]) –
(k, d), pas nécessairement normalisées.- Returns:
(k, m)de distances cosinus,métant le nombre de locuteurs.- Return type:
NDArray[np.float32]
- merge()[source]
Réunir les locuteurs que la mémoire révèle identiques.
C’est ce que l’absence de moyenne rend possible. Deux ensembles d’exemplaires se comparent — on regarde si leurs observations se recouvrent — là où deux moyennes mobiles avaient déjà perdu de quoi le savoir. La passe est exacte, ne recalcule aucune empreinte, et répare après coup la sur-segmentation que la décision locale ne pouvait pas éviter : la contrainte d’injectivité doit séparer deux locuteurs locaux simultanés, même quand la segmentation s’est trompée et qu’il n’y en avait qu’un.
- Returns:
{ancienne étiquette: étiquette survivante}pour les locuteurs absorbés. Vide si rien ne fusionne. À l’appelant de réécrire ses propres sorties avec cette table.- Return type:
- policy: ExemplarPolicy
- class vocal_helper.exemplar.ExemplarPolicy(rho_update_s=0.4, delta_new=0.6, reference='meilleur', neighbours=1, max_exemplars=12, diversity_margin=0.1, max_speakers=None, merge=True, merge_threshold=None)[source]
Bases:
objectLes réglages du regroupement par exemplaires.
- Parameters:
- rho_update_s
Confiance minimale, en secondes de parole propre, pour qu’une observation soit gardée. En dessous, elle est quand même étiquetée — on ne perd personne — mais elle n’entre pas dans la mémoire du locuteur.
- Type:
- delta_new
Distance au meilleur point au-delà de laquelle on crée un locuteur plutôt que de forcer une jointure. Reste à 0,6 : trois réunions d’AMI donnent 0,750 / 0,600 / 0,600 comme optimum, et aucune normalisation n’a réduit cette dispersion.
- Type:
- reference
Comment mesurer la ressemblance à un locuteur, sans jamais moyenner.
"meilleur"Distance au
neighbours-ième exemplaire le plus proche. Garde la multimodalité, mais c’est un minimum sur un ensemble, donc un estimateur optimiste : il diminue mécaniquement quand l’ensemble grandit, si bien qu’un seul exemplaire bruité suffit à capturer un locuteur. Mesuré sur AMI : taux d’erreur 0,529 contre 0,299 pour le centroïde, et dérive 0,859. C’est ce biais queneighbourscorrige partiellement, 0,529 puis 0,378 puis 0,363 pour k=1,2,3."confiance"Distance à l’unique exemplaire de meilleure confiance. Un point observé, donc aucune moyenne, et pas de biais optimiste non plus puisque le nombre d’exemplaires n’entre pas dans la mesure.
"médoïde"Distance au médoïde — l’exemplaire le plus central de l’ensemble, celui de plus forte similarité totale aux autres. Également un point observé, et robuste à un intrus isolé, qui ne peut pas devenir le plus central.
- Type:
- neighbours
Combien de voisins regarder, pour
reference="meilleur"seulement. 1 est le meilleur point pur, donc un lien simple, sensible à un exemplaire aberrant faisant pont entre deux locuteurs. 2 ou 3 coupent le pont tout en gardant la multimodalité.- Type:
- max_exemplars
Taille de la mémoire par locuteur. C’est ce qui garde le coût borné en direct : la distance à un locuteur coûte au plus
max_exemplarsproduits scalaires.- Type:
- diversity_margin
En dessous de cette distance à un exemplaire déjà gardé, une observation est un quasi-doublon : elle renforce la confiance de l’existant au lieu de prendre une place. C’est ce qui empêche la mémoire de se remplir de dix copies du même instant.
- Type:
- max_speakers
Plafond dur du nombre de locuteurs.
Nonelaisse le compte libre, c’est-à-dire deviné.- Type:
int, optional
- merge
Faire ou non la reprise globale.
Truepar défaut, parce que c’est l’apport principal de la représentation par exemplaires. Le mettre àFalsesert à isoler les deux effets dans une mesure : l’absence de moyenne d’une part, la reprise globale de l’autre.- Type:
- merge_threshold
Distance entre deux ensembles d’exemplaires en dessous de laquelle
ExemplarClusterer.merge()les réunit.Nonereprenddelta_new, ce qui est cohérent : deux locuteurs plus proches que le seuil de création n’auraient jamais dû être deux.- Type:
float, optional