Commit 860e810053382435e79162618d4961b112bbce25
1 parent
6fa813b7
fix(lifelog/diarize): L1 reclusterAll 冻结人工命名 anchor 质心,止住雪球吞并
reclusterAll 原本对命中的簇一律滑动均值精修质心。问题:人工命名的 anchor 吸入相邻说话人的段后,质心会朝其漂移、再吸走更多 → 雪球式吞并(实测「秦子琛」 把「宜博」从 ~125 段啃到只剩 56)。 改为:命中人工命名 anchor 时只归属、**冻结质心不漂移**(autoIndexOf==null 判定); 自动簇仍滑动均值。anchor 始终保持用户标定方向,不被相邻说话人污染。 实测确认生效:冻结后「秦子琛」anchor 不再吞并,各簇内聚 0.76-0.82 干净分离。 注:此改动也暴露出历史漂移已把存量 anchor 的 embedding 平均坏了(连本该代表的 主说话人都匹配不上),需 L2 多 anchor 重注册给出干净 anchor。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Showing
1 changed file
with
15 additions
and
7 deletions
lifelog-recorder/app/src/main/kotlin/com/lemon/lifelog/asr/SpeakerClusterer.kt
| @@ -159,13 +159,21 @@ class SpeakerClusterer( | @@ -159,13 +159,21 @@ class SpeakerClusterer( | ||
| 159 | } | 159 | } |
| 160 | 160 | ||
| 161 | val assignment = if (best != null && bestSim >= matchThreshold) { | 161 | val assignment = if (best != null && bestSim >= matchThreshold) { |
| 162 | - val n = minOf(best.count, maxSamplesPerCentroid) | ||
| 163 | - val merged = FloatArray(emb.size) { i -> (best.centroid[i] * n + emb[i]) / (n + 1) } | ||
| 164 | - l2normalize(merged) | ||
| 165 | - best.centroid = merged | ||
| 166 | - best.count += 1 | ||
| 167 | - repo.speakers.updateEmbedding(best.id, merged.toLeBytes(), best.count) | ||
| 168 | - best.id to bestSim | 162 | + if (autoIndexOf(best.name) == null) { |
| 163 | + // 人工命名 anchor:只归属、**冻结质心不漂移**。否则 anchor 吸入相邻 | ||
| 164 | + // 说话人的段后质心会朝其漂移、再吸走更多 → 雪球式吞并(实测秦子琛 | ||
| 165 | + // 把宜博从 ~125 段啃到只剩 56)。冻结后 anchor 始终是用户标定的方向。 | ||
| 166 | + best.id to bestSim | ||
| 167 | + } else { | ||
| 168 | + // 自动簇:滑动均值精修质心(自适应、不被早期样本钉死)。 | ||
| 169 | + val n = minOf(best.count, maxSamplesPerCentroid) | ||
| 170 | + val merged = FloatArray(emb.size) { i -> (best.centroid[i] * n + emb[i]) / (n + 1) } | ||
| 171 | + l2normalize(merged) | ||
| 172 | + best.centroid = merged | ||
| 173 | + best.count += 1 | ||
| 174 | + repo.speakers.updateEmbedding(best.id, merged.toLeBytes(), best.count) | ||
| 175 | + best.id to bestSim | ||
| 176 | + } | ||
| 169 | } else if (seg.durationMs >= minNewClusterMs) { | 177 | } else if (seg.durationMs >= minNewClusterMs) { |
| 170 | val norm = emb.copyOf().also { l2normalize(it) } | 178 | val norm = emb.copyOf().also { l2normalize(it) } |
| 171 | seq += 1 | 179 | seq += 1 |