Public Access
feat: add local Resemblyzer speaker recognition
PR and Push Build/Test / build-and-test (push) Successful in 12m37s
PR and Push Build/Test / build-and-test (push) Successful in 12m37s
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
using MeetingAssistant;
|
||||
using MeetingAssistant.Speakers;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
||||
namespace MeetingAssistant.Tests;
|
||||
|
||||
public sealed class ResemblyzerVoiceClusterMatcherTests
|
||||
{
|
||||
[Fact]
|
||||
public void CoherentSimilarClusterSelectsUnambiguousIdentity()
|
||||
{
|
||||
var matcher = CreateMatcher();
|
||||
var query = Enumerable.Range(0, 5)
|
||||
.Select(index => UnitVector(0, secondaryDimension: index + 2, secondaryValue: 0.05f))
|
||||
.ToList();
|
||||
var candidates = new[]
|
||||
{
|
||||
new ResemblyzerVoiceVectorCandidate(42, [UnitVector(0), UnitVector(0, 10, 0.02f)]),
|
||||
new ResemblyzerVoiceVectorCandidate(77, [UnitVector(1), UnitVector(1, 11, 0.02f)])
|
||||
};
|
||||
|
||||
var result = matcher.Match(query, candidates);
|
||||
|
||||
Assert.True(result.Accepted);
|
||||
Assert.Equal(42, result.IdentityId);
|
||||
Assert.True(result.Cohesion >= 0.99);
|
||||
Assert.True(result.BestSimilarity >= 0.99);
|
||||
Assert.True(result.RunnerUpSimilarity < 0.1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FourVectorsDoNotTriggerAutomaticMatching()
|
||||
{
|
||||
var result = CreateMatcher().Match(
|
||||
Enumerable.Repeat(UnitVector(0), 4).ToList(),
|
||||
[new ResemblyzerVoiceVectorCandidate(42, [UnitVector(0)])]);
|
||||
|
||||
Assert.False(result.Accepted);
|
||||
Assert.Contains("4/5", result.Reason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IncoherentClusterIsRejectedBeforeIdentityScoring()
|
||||
{
|
||||
var result = CreateMatcher().Match(
|
||||
Enumerable.Range(0, 5).Select(index => UnitVector(index)).ToList(),
|
||||
[new ResemblyzerVoiceVectorCandidate(42, [UnitVector(0)])]);
|
||||
|
||||
Assert.False(result.Accepted);
|
||||
Assert.Contains("cohesion", result.Reason);
|
||||
Assert.Null(result.BestSimilarity);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SimilarityBelowThresholdIsRejected()
|
||||
{
|
||||
var result = CreateMatcher().Match(
|
||||
Enumerable.Repeat(UnitVector(0), 5).ToList(),
|
||||
[new ResemblyzerVoiceVectorCandidate(42, [UnitVector(1)])]);
|
||||
|
||||
Assert.False(result.Accepted);
|
||||
Assert.Contains("best similarity", result.Reason);
|
||||
Assert.Equal(0, result.BestSimilarity);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SimilarCandidatesWithinMarginAreRejected()
|
||||
{
|
||||
var result = CreateMatcher().Match(
|
||||
Enumerable.Repeat(UnitVector(0), 5).ToList(),
|
||||
[
|
||||
new ResemblyzerVoiceVectorCandidate(42, [UnitVector(0)]),
|
||||
new ResemblyzerVoiceVectorCandidate(77, [UnitVector(0, 1, 0.01f)])
|
||||
]);
|
||||
|
||||
Assert.False(result.Accepted);
|
||||
Assert.Contains("margin", result.Reason);
|
||||
Assert.NotNull(result.RunnerUpSimilarity);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidStoredCandidateDoesNotAbortScoringOtherIdentities()
|
||||
{
|
||||
var result = CreateMatcher().Match(
|
||||
Enumerable.Repeat(UnitVector(0), 5).ToList(),
|
||||
[
|
||||
new ResemblyzerVoiceVectorCandidate(13, [new float[256]]),
|
||||
new ResemblyzerVoiceVectorCandidate(42, [UnitVector(0)])
|
||||
]);
|
||||
|
||||
Assert.True(result.Accepted);
|
||||
Assert.Equal(42, result.IdentityId);
|
||||
}
|
||||
|
||||
private static ResemblyzerVoiceClusterMatcher CreateMatcher()
|
||||
{
|
||||
return new ResemblyzerVoiceClusterMatcher(
|
||||
new ResemblyzerSpeakerRecognitionOptions
|
||||
{
|
||||
RequiredVectorsPerSpeaker = 5,
|
||||
MinimumClusterCohesion = 0.75,
|
||||
MinimumIdentitySimilarity = 0.75,
|
||||
MinimumSimilarityMargin = 0.05
|
||||
},
|
||||
NullLogger<ResemblyzerVoiceClusterMatcher>.Instance);
|
||||
}
|
||||
|
||||
private static float[] UnitVector(
|
||||
int primaryDimension,
|
||||
int? secondaryDimension = null,
|
||||
float secondaryValue = 0)
|
||||
{
|
||||
var vector = new float[256];
|
||||
vector[primaryDimension] = 1;
|
||||
if (secondaryDimension is { } dimension)
|
||||
{
|
||||
vector[dimension] = secondaryValue;
|
||||
}
|
||||
|
||||
return vector;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user