Skip to content

Commit 8541d09

Browse files
feat: optionally return per-speaker embedding centroids (#2967)
* feat(spk): optionally return per-speaker embedding centroids Add a return_spk_center option so AutoModel.generate surfaces the per-speaker centroid embeddings (mean of clustered chunk embeddings) that diarization already computes in postprocess() but currently discards. Lets downstream speaker voiceprint / identity reuse them without re-embedding. Backward compatible: default off; postprocess return shape is unchanged unless return_spk_center=True. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(spk): address review on spk_embedding_center - pass np.ndarray (not torch.Tensor) to postprocess to match its type hint - update postprocess return hint to Union[list, tuple] - compute spk_embs lazily, only when return_spk_center=True Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent cd38586 commit 8541d09

2 files changed

Lines changed: 26 additions & 10 deletions

File tree

funasr/auto/auto_model.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -818,7 +818,18 @@ def inference_with_vad(self, input, input_len=None, **cfg):
818818
spk_embedding.cpu(), oracle_num=kwargs.get("preset_spk_num", None)
819819
)
820820
# del result['spk_embedding']
821-
sv_output = postprocess(all_segments, None, labels, spk_embedding.cpu())
821+
# postprocess expects np.ndarray embeddings (per its type hint).
822+
spk_embedding_np = spk_embedding.detach().cpu().numpy()
823+
if kwargs.get("return_spk_center", False):
824+
sv_output, spk_center = postprocess(
825+
all_segments, None, labels, spk_embedding_np, return_spk_center=True
826+
)
827+
# Per-speaker ERes2NetV2 centroids, indexed by the `spk` id in
828+
# sentence_info. Kept on the result for downstream voiceprint use
829+
# (the per-chunk spk_embedding below is still deleted to keep output small).
830+
result["spk_embedding_center"] = spk_center
831+
else:
832+
sv_output = postprocess(all_segments, None, labels, spk_embedding_np)
822833
if self.spk_mode == "punc_segment" and "timestamp" not in result and "timestamps" not in result:
823834
logging.warning("No timestamps in ASR result (e.g. SenseVoice), falling back to vad_segment mode for speaker diarization.")
824835
self.spk_mode = "vad_segment"

funasr/models/campplus/utils.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,12 @@ def extract_feature(audio):
138138

139139

140140
def postprocess(
141-
segments: list, vad_segments: list, labels: np.ndarray, embeddings: np.ndarray
142-
) -> list:
141+
segments: list,
142+
vad_segments: list,
143+
labels: np.ndarray,
144+
embeddings: np.ndarray,
145+
return_spk_center: bool = False,
146+
) -> Union[list, tuple]:
143147
"""Postprocess.
144148
145149
Args:
@@ -156,13 +160,6 @@ def postprocess(
156160
# merge the same speakers chronologically
157161
distribute_res = merge_seque(distribute_res)
158162

159-
# accquire speaker center
160-
spk_embs = []
161-
for i in range(labels.max() + 1):
162-
spk_emb = embeddings[labels == i].mean(0)
163-
spk_embs.append(spk_emb)
164-
spk_embs = np.stack(spk_embs)
165-
166163
def is_overlapped(t1, t2):
167164
"""Is overlapped.
168165
@@ -184,6 +181,14 @@ def is_overlapped(t1, t2):
184181
# smooth the result
185182
distribute_res = smooth(distribute_res)
186183

184+
if return_spk_center:
185+
# spk_embs[i] is the centroid (mean of clustered chunk embeddings) for
186+
# corrected speaker label i, aligned with the `spk` ids in sentence_info.
187+
# Computed lazily: only when the caller requests speaker centers.
188+
spk_embs = np.stack(
189+
[embeddings[labels == i].mean(0) for i in range(labels.max() + 1)]
190+
)
191+
return distribute_res, spk_embs
187192
return distribute_res
188193

189194

0 commit comments

Comments
 (0)