Audio similarity engine: optimized release and artists matching
This commit is contained in:
@@ -38,30 +38,49 @@ namespace lms::math
|
|||||||
using value_type = typename VectorType::value_type;
|
using value_type = typename VectorType::value_type;
|
||||||
using size_type = std::size_t;
|
using size_type = std::size_t;
|
||||||
|
|
||||||
// Add a single vector, returning its index
|
// provided value must be accessible until finalize() is called
|
||||||
size_type add(const VectorType& value)
|
void add(const VectorType& value)
|
||||||
{
|
{
|
||||||
_vectors.push_back(value);
|
_pointers.push_back(&value);
|
||||||
return _vectors.size() - 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute the medoid: the vector with minimum sum of squared distances to all others
|
// Returns a pointer to the medoid among the added vectors.
|
||||||
// Returns the index of the medoid in the added vectors
|
// The pointer remains valid as long as the original vectors are alive.
|
||||||
size_type findMedoidIndex() const
|
const VectorType* finalize() const
|
||||||
{
|
{
|
||||||
assert(!empty());
|
assert(!empty());
|
||||||
|
return _pointers[findMedoidIndex()];
|
||||||
|
}
|
||||||
|
|
||||||
|
bool empty() const
|
||||||
|
{
|
||||||
|
return _pointers.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_type count() const
|
||||||
|
{
|
||||||
|
return _pointers.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear()
|
||||||
|
{
|
||||||
|
_pointers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
size_type findMedoidIndex() const
|
||||||
|
{
|
||||||
size_type medoidIndex{};
|
size_type medoidIndex{};
|
||||||
value_type minTotalDistance{ std::numeric_limits<value_type>::max() };
|
value_type minTotalDistance{ std::numeric_limits<value_type>::max() };
|
||||||
|
|
||||||
for (size_type i{}; i < _vectors.size(); ++i)
|
for (size_type i{}; i < _pointers.size(); ++i)
|
||||||
{
|
{
|
||||||
value_type totalDistance{};
|
value_type totalDistance{};
|
||||||
const SquaredEuclideanDistance distFunc{ _vectors[i] };
|
const SquaredEuclideanDistance distFunc{ *_pointers[i] };
|
||||||
for (size_type j{}; j < _vectors.size(); ++j)
|
for (size_type j{}; j < _pointers.size(); ++j)
|
||||||
{
|
{
|
||||||
if (i != j)
|
if (i != j)
|
||||||
totalDistance += distFunc(_vectors[j]);
|
totalDistance += distFunc(*_pointers[j]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (totalDistance < minTotalDistance)
|
if (totalDistance < minTotalDistance)
|
||||||
@@ -74,37 +93,7 @@ namespace lms::math
|
|||||||
return medoidIndex;
|
return medoidIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute the medoid vector itself
|
std::vector<const VectorType*> _pointers;
|
||||||
VectorType finalize() const
|
|
||||||
{
|
|
||||||
return _vectors[findMedoidIndex()];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get a specific vector by index
|
|
||||||
const VectorType& getVector(size_type index) const
|
|
||||||
{
|
|
||||||
assert(index < _vectors.size());
|
|
||||||
return _vectors[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query methods
|
|
||||||
bool empty() const
|
|
||||||
{
|
|
||||||
return _vectors.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
size_type count() const
|
|
||||||
{
|
|
||||||
return _vectors.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
void clear()
|
|
||||||
{
|
|
||||||
_vectors.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::vector<VectorType> _vectors;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename VectorType>
|
template<typename VectorType>
|
||||||
@@ -113,15 +102,6 @@ namespace lms::math
|
|||||||
MedoidCalculator<VectorType> calculator;
|
MedoidCalculator<VectorType> calculator;
|
||||||
for (const auto& value : values)
|
for (const auto& value : values)
|
||||||
calculator.add(value);
|
calculator.add(value);
|
||||||
return calculator.finalize();
|
return *calculator.finalize();
|
||||||
}
|
|
||||||
|
|
||||||
template<typename VectorType>
|
|
||||||
VectorType computeNormalizedMedoid(std::span<const VectorType> values)
|
|
||||||
{
|
|
||||||
MedoidCalculator<VectorType> calculator;
|
|
||||||
for (const auto& value : values)
|
|
||||||
calculator.add(value);
|
|
||||||
return calculator.finalizeNormalized();
|
|
||||||
}
|
}
|
||||||
} // namespace lms::math
|
} // namespace lms::math
|
||||||
|
|||||||
@@ -40,12 +40,7 @@ namespace lms::math::medoidCalculatorTests
|
|||||||
|
|
||||||
EXPECT_FALSE(calculator.empty());
|
EXPECT_FALSE(calculator.empty());
|
||||||
EXPECT_EQ(calculator.count(), 1U);
|
EXPECT_EQ(calculator.count(), 1U);
|
||||||
EXPECT_EQ(calculator.findMedoidIndex(), 0U);
|
EXPECT_EQ(calculator.finalize(), &vec);
|
||||||
|
|
||||||
const Vector<3, float> result = calculator.finalize();
|
|
||||||
EXPECT_EQ(result[0], 1.0F);
|
|
||||||
EXPECT_EQ(result[1], 2.0F);
|
|
||||||
EXPECT_EQ(result[2], 3.0F);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(MedoidCalculator, twoVectors)
|
TEST(MedoidCalculator, twoVectors)
|
||||||
@@ -58,9 +53,9 @@ namespace lms::math::medoidCalculatorTests
|
|||||||
calculator.add(v2);
|
calculator.add(v2);
|
||||||
|
|
||||||
EXPECT_EQ(calculator.count(), 2U);
|
EXPECT_EQ(calculator.count(), 2U);
|
||||||
// Both have equal distance to the other, but first one is returned
|
// Both have equal distance to the other; result must be one of the two
|
||||||
const std::size_t medoidIndex = calculator.findMedoidIndex();
|
const Vector<2, float>* medoid = calculator.finalize();
|
||||||
EXPECT_TRUE(medoidIndex == 0 || medoidIndex == 1);
|
EXPECT_TRUE(medoid == &v1 || medoid == &v2);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(MedoidCalculator, threeDifferentVectors)
|
TEST(MedoidCalculator, threeDifferentVectors)
|
||||||
@@ -68,16 +63,14 @@ namespace lms::math::medoidCalculatorTests
|
|||||||
MedoidCalculator<Vector<2, float>> calculator;
|
MedoidCalculator<Vector<2, float>> calculator;
|
||||||
// Three points: (0,0), (1,0), (10,0)
|
// Three points: (0,0), (1,0), (10,0)
|
||||||
// Medoid should be (1,0) as it's closest to the others
|
// Medoid should be (1,0) as it's closest to the others
|
||||||
calculator.add(Vector<2, float>{ 0.0F, 0.0F });
|
const Vector<2, float> v0{ 0.0F, 0.0F };
|
||||||
calculator.add(Vector<2, float>{ 1.0F, 0.0F });
|
const Vector<2, float> v1{ 1.0F, 0.0F };
|
||||||
calculator.add(Vector<2, float>{ 10.0F, 0.0F });
|
const Vector<2, float> v2{ 10.0F, 0.0F };
|
||||||
|
calculator.add(v0);
|
||||||
|
calculator.add(v1);
|
||||||
|
calculator.add(v2);
|
||||||
|
|
||||||
const std::size_t medoidIndex = calculator.findMedoidIndex();
|
EXPECT_EQ(calculator.finalize(), &v1);
|
||||||
EXPECT_EQ(medoidIndex, 1U); // The middle point (1,0) is the medoid
|
|
||||||
|
|
||||||
const Vector<2, float> result = calculator.finalize();
|
|
||||||
EXPECT_FLOAT_EQ(result[0], 1.0F);
|
|
||||||
EXPECT_FLOAT_EQ(result[1], 0.0F);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(MedoidCalculator, computeMedoidSpan)
|
TEST(MedoidCalculator, computeMedoidSpan)
|
||||||
@@ -94,25 +87,11 @@ namespace lms::math::medoidCalculatorTests
|
|||||||
EXPECT_FLOAT_EQ(result[1], 0.0F);
|
EXPECT_FLOAT_EQ(result[1], 0.0F);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(MedoidCalculator, getVector)
|
|
||||||
{
|
|
||||||
MedoidCalculator<Vector<2, float>> calculator;
|
|
||||||
calculator.add(Vector<2, float>{ 1.0F, 2.0F });
|
|
||||||
calculator.add(Vector<2, float>{ 3.0F, 4.0F });
|
|
||||||
|
|
||||||
const Vector<2, float>& v0 = calculator.getVector(0U);
|
|
||||||
const Vector<2, float>& v1 = calculator.getVector(1U);
|
|
||||||
|
|
||||||
EXPECT_FLOAT_EQ(v0[0], 1.0F);
|
|
||||||
EXPECT_FLOAT_EQ(v0[1], 2.0F);
|
|
||||||
EXPECT_FLOAT_EQ(v1[0], 3.0F);
|
|
||||||
EXPECT_FLOAT_EQ(v1[1], 4.0F);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(MedoidCalculator, clear)
|
TEST(MedoidCalculator, clear)
|
||||||
{
|
{
|
||||||
MedoidCalculator<Vector<2, float>> calculator;
|
MedoidCalculator<Vector<2, float>> calculator;
|
||||||
calculator.add(Vector<2, float>{ 1.0F, 2.0F });
|
const Vector<2, float> v{ 1.0F, 2.0F };
|
||||||
|
calculator.add(v);
|
||||||
EXPECT_EQ(calculator.count(), 1);
|
EXPECT_EQ(calculator.count(), 1);
|
||||||
calculator.clear();
|
calculator.clear();
|
||||||
EXPECT_EQ(calculator.count(), 0);
|
EXPECT_EQ(calculator.count(), 0);
|
||||||
|
|||||||
@@ -86,7 +86,9 @@ namespace lms::recommendation
|
|||||||
std::vector<ReducedVector> _vectors;
|
std::vector<ReducedVector> _vectors;
|
||||||
std::unordered_map<db::TrackId, const ReducedVector*> _trackVectors;
|
std::unordered_map<db::TrackId, const ReducedVector*> _trackVectors;
|
||||||
std::unordered_map<db::ReleaseId, std::vector<std::reference_wrapper<const ReducedVector>>> _releaseVectors;
|
std::unordered_map<db::ReleaseId, std::vector<std::reference_wrapper<const ReducedVector>>> _releaseVectors;
|
||||||
|
std::unordered_map<db::ReleaseId, const ReducedVector*> _releaseMedoids;
|
||||||
std::unordered_map<db::ArtistId, std::vector<std::reference_wrapper<const ReducedVector>>> _artistVectors;
|
std::unordered_map<db::ArtistId, std::vector<std::reference_wrapper<const ReducedVector>>> _artistVectors;
|
||||||
|
std::unordered_map<db::ArtistId, const ReducedVector*> _artistMedoids;
|
||||||
TrackMetadataMap _trackMetadata;
|
TrackMetadataMap _trackMetadata;
|
||||||
|
|
||||||
FloatType _trackDistanceThreshold{};
|
FloatType _trackDistanceThreshold{};
|
||||||
|
|||||||
+88
-33
@@ -182,7 +182,7 @@ namespace lms::recommendation
|
|||||||
if (medoidCalculator.empty())
|
if (medoidCalculator.empty())
|
||||||
return res;
|
return res;
|
||||||
|
|
||||||
const ReducedVector queryVector{ medoidCalculator.finalize() };
|
const ReducedVector& queryVector{ *medoidCalculator.finalize() };
|
||||||
const math::NormalizedCosineDistance distFunc{ queryVector };
|
const math::NormalizedCosineDistance distFunc{ queryVector };
|
||||||
|
|
||||||
using Distance = float;
|
using Distance = float;
|
||||||
@@ -356,9 +356,7 @@ namespace lms::recommendation
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<AudioVectorProvider Provider, std::size_t ReducedDimCount>
|
template<AudioVectorProvider Provider, std::size_t ReducedDimCount>
|
||||||
ReleaseResults AudioSimilarityEngine<Provider, ReducedDimCount>::findSimilarReleases(
|
ReleaseResults AudioSimilarityEngine<Provider, ReducedDimCount>::findSimilarReleases(db::ReleaseId releaseId, std::size_t maxCount) const
|
||||||
db::ReleaseId releaseId,
|
|
||||||
std::size_t maxCount) const
|
|
||||||
{
|
{
|
||||||
LMS_SCOPED_TRACE_DETAILED("AudioSimilarityEngine", "Find similar releases");
|
LMS_SCOPED_TRACE_DETAILED("AudioSimilarityEngine", "Find similar releases");
|
||||||
|
|
||||||
@@ -373,26 +371,37 @@ namespace lms::recommendation
|
|||||||
const auto& queryReleaseFeatures{ itQueryRelease->second };
|
const auto& queryReleaseFeatures{ itQueryRelease->second };
|
||||||
|
|
||||||
using Distance = float;
|
using Distance = float;
|
||||||
std::vector<std::pair<db::ReleaseId, Distance>> rankedReleases;
|
|
||||||
rankedReleases.reserve(_releaseVectors.size());
|
|
||||||
|
|
||||||
using CosineDistance = math::NormalizedCosineDistance<ReducedVector::getSize(), FloatType>;
|
using CosineDistance = math::NormalizedCosineDistance<ReducedVector::getSize(), FloatType>;
|
||||||
|
|
||||||
for (const auto& [candidateId, candidateReleaseVectors] : _releaseVectors)
|
// Stage 1: fast medoid scan to get top candidates
|
||||||
{
|
constexpr std::size_t preFilterMultiplier{ 10 };
|
||||||
if (candidateId == releaseId || candidateReleaseVectors.empty())
|
constexpr std::size_t preFilterMinCount{ 50 };
|
||||||
continue;
|
const std::size_t preFilterCount{ std::min(_releaseMedoids.size() - 1, std::max(preFilterMinCount, maxCount * preFilterMultiplier)) };
|
||||||
|
|
||||||
const FloatType distance{ math::symmetricalChamferDistance<CosineDistance>(
|
const math::NormalizedCosineDistance queryMedoidDist{ *_releaseMedoids.at(releaseId) };
|
||||||
queryReleaseFeatures,
|
std::vector<std::pair<db::ReleaseId, FloatType>> medoidCandidates;
|
||||||
candidateReleaseVectors) };
|
medoidCandidates.reserve(_releaseMedoids.size());
|
||||||
|
for (const auto& [candidateId, medoid] : _releaseMedoids)
|
||||||
|
{
|
||||||
|
if (candidateId != releaseId)
|
||||||
|
medoidCandidates.emplace_back(candidateId, queryMedoidDist(*medoid));
|
||||||
|
}
|
||||||
|
std::nth_element(medoidCandidates.begin(), std::next(medoidCandidates.begin(), preFilterCount), medoidCandidates.end(), [](const auto& a, const auto& b) { return a.second < b.second; });
|
||||||
|
medoidCandidates.resize(preFilterCount);
|
||||||
|
|
||||||
|
// Stage 2: Chamfer re-rank on top candidates
|
||||||
|
std::vector<std::pair<db::ReleaseId, Distance>> rankedReleases;
|
||||||
|
rankedReleases.reserve(preFilterCount);
|
||||||
|
for (const auto& [candidateId, _] : medoidCandidates)
|
||||||
|
{
|
||||||
|
const FloatType distance{ math::symmetricalChamferDistance<CosineDistance>(queryReleaseFeatures, _releaseVectors.at(candidateId)) };
|
||||||
|
|
||||||
if (distance <= _releaseDistanceThreshold)
|
if (distance <= _releaseDistanceThreshold)
|
||||||
rankedReleases.emplace_back(candidateId, distance);
|
rankedReleases.emplace_back(candidateId, distance);
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::size_t resultCount{ std::min(maxCount, rankedReleases.size()) };
|
const std::size_t resultCount{ std::min(maxCount, rankedReleases.size()) };
|
||||||
std::partial_sort(std::begin(rankedReleases), std::next(std::begin(rankedReleases), resultCount), std::end(rankedReleases), [](const auto& lhs, const auto& rhs) {
|
std::partial_sort(std::begin(rankedReleases), std::next(std::begin(rankedReleases), static_cast<std::ptrdiff_t>(resultCount)), std::end(rankedReleases), [](const auto& lhs, const auto& rhs) {
|
||||||
return lhs.second < rhs.second;
|
return lhs.second < rhs.second;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -422,19 +431,30 @@ namespace lms::recommendation
|
|||||||
const auto& queryArtistFeatures{ itQueryArtist->second };
|
const auto& queryArtistFeatures{ itQueryArtist->second };
|
||||||
|
|
||||||
using Distance = float;
|
using Distance = float;
|
||||||
std::vector<std::pair<db::ArtistId, Distance>> rankedArtists;
|
|
||||||
rankedArtists.reserve(_artistVectors.size());
|
|
||||||
|
|
||||||
using CosineDistance = math::NormalizedCosineDistance<ReducedVector::getSize(), typename ReducedVector::value_type>;
|
using CosineDistance = math::NormalizedCosineDistance<ReducedVector::getSize(), typename ReducedVector::value_type>;
|
||||||
|
|
||||||
for (const auto& [candidateId, candidateArtistFeatures] : _artistVectors)
|
// Stage 1: fast medoid scan to get top-K candidates
|
||||||
{
|
constexpr std::size_t preFilterMultiplier{ 10 };
|
||||||
if (candidateId == artistId || candidateArtistFeatures.empty())
|
constexpr std::size_t preFilterMinCount{ 50 };
|
||||||
continue;
|
const std::size_t preFilterCount{ std::min(_artistMedoids.size() - 1, std::max(preFilterMinCount, maxCount * preFilterMultiplier)) };
|
||||||
|
|
||||||
const FloatType distance{ math::symmetricalChamferDistance<CosineDistance>(
|
const math::NormalizedCosineDistance queryMedoidDist{ *_artistMedoids.at(artistId) };
|
||||||
queryArtistFeatures,
|
std::vector<std::pair<db::ArtistId, FloatType>> medoidCandidates;
|
||||||
candidateArtistFeatures) };
|
medoidCandidates.reserve(_artistMedoids.size());
|
||||||
|
for (const auto& [candidateId, medoid] : _artistMedoids)
|
||||||
|
{
|
||||||
|
if (candidateId != artistId)
|
||||||
|
medoidCandidates.emplace_back(candidateId, queryMedoidDist(*medoid));
|
||||||
|
}
|
||||||
|
std::nth_element(medoidCandidates.begin(), std::next(medoidCandidates.begin(), preFilterCount), medoidCandidates.end(), [](const auto& a, const auto& b) { return a.second < b.second; });
|
||||||
|
medoidCandidates.resize(preFilterCount);
|
||||||
|
|
||||||
|
// Stage 2: Chamfer re-rank on top-K candidates
|
||||||
|
std::vector<std::pair<db::ArtistId, Distance>> rankedArtists;
|
||||||
|
rankedArtists.reserve(preFilterCount);
|
||||||
|
for (const auto& [candidateId, _] : medoidCandidates)
|
||||||
|
{
|
||||||
|
const FloatType distance{ math::symmetricalChamferDistance<CosineDistance>(queryArtistFeatures, _artistVectors.at(candidateId)) };
|
||||||
|
|
||||||
if (distance <= _artistDistanceThreshold)
|
if (distance <= _artistDistanceThreshold)
|
||||||
rankedArtists.emplace_back(candidateId, distance);
|
rankedArtists.emplace_back(candidateId, distance);
|
||||||
@@ -578,7 +598,9 @@ namespace lms::recommendation
|
|||||||
_vectors.clear();
|
_vectors.clear();
|
||||||
_vectors.reserve(_trackCount); // must keep pointers valid
|
_vectors.reserve(_trackCount); // must keep pointers valid
|
||||||
_releaseVectors.clear();
|
_releaseVectors.clear();
|
||||||
|
_releaseMedoids.clear();
|
||||||
_artistVectors.clear();
|
_artistVectors.clear();
|
||||||
|
_artistMedoids.clear();
|
||||||
_trackMetadata.clear();
|
_trackMetadata.clear();
|
||||||
|
|
||||||
Provider::visitVectors(session, [&](db::TrackId trackId, const SourceVector& sourceVector) {
|
Provider::visitVectors(session, [&](db::TrackId trackId, const SourceVector& sourceVector) {
|
||||||
@@ -652,6 +674,23 @@ namespace lms::recommendation
|
|||||||
_artistVectors.try_emplace(artist->getId(), std::move(artistTrackVectors));
|
_artistVectors.try_emplace(artist->getId(), std::move(artistTrackVectors));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
math::MedoidCalculator<ReducedVector> calc;
|
||||||
|
for (const auto& [id, vecs] : _releaseVectors)
|
||||||
|
{
|
||||||
|
calc.clear();
|
||||||
|
for (const auto& v : vecs)
|
||||||
|
calc.add(v.get());
|
||||||
|
_releaseMedoids.try_emplace(id, calc.finalize());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& [id, vecs] : _artistVectors)
|
||||||
|
{
|
||||||
|
calc.clear();
|
||||||
|
for (const auto& v : vecs)
|
||||||
|
calc.add(v.get());
|
||||||
|
_artistMedoids.try_emplace(id, calc.finalize());
|
||||||
|
}
|
||||||
|
|
||||||
// Sort artistIds in each TrackMetadata entry for set-intersection in SameArtistConstraint
|
// Sort artistIds in each TrackMetadata entry for set-intersection in SameArtistConstraint
|
||||||
for (auto& [trackId, metadata] : _trackMetadata)
|
for (auto& [trackId, metadata] : _trackMetadata)
|
||||||
std::sort(metadata.artistIds.begin(), metadata.artistIds.end());
|
std::sort(metadata.artistIds.begin(), metadata.artistIds.end());
|
||||||
@@ -714,6 +753,7 @@ namespace lms::recommendation
|
|||||||
LMS_SCOPED_TRACE_DETAILED("AudioSimilarityEngine", "ComputeReleaseDistanceThreshold");
|
LMS_SCOPED_TRACE_DETAILED("AudioSimilarityEngine", "ComputeReleaseDistanceThreshold");
|
||||||
|
|
||||||
constexpr std::size_t maxSampleCount{ 200 };
|
constexpr std::size_t maxSampleCount{ 200 };
|
||||||
|
constexpr std::size_t maxCandidateCount{ 1'000 };
|
||||||
constexpr float stdDevMultiplier{ 2.F };
|
constexpr float stdDevMultiplier{ 2.F };
|
||||||
using CosineDistance = math::NormalizedCosineDistance<ReducedVector::getSize(), FloatType>;
|
using CosineDistance = math::NormalizedCosineDistance<ReducedVector::getSize(), FloatType>;
|
||||||
|
|
||||||
@@ -722,11 +762,18 @@ namespace lms::recommendation
|
|||||||
for (const auto& [id, vecs] : _releaseVectors)
|
for (const auto& [id, vecs] : _releaseVectors)
|
||||||
allProfiles.push_back(&vecs);
|
allProfiles.push_back(&vecs);
|
||||||
|
|
||||||
const std::size_t sampleCount{ std::min(allProfiles.size(), maxSampleCount) };
|
// move maxCandidateCount random elements to the front
|
||||||
LOG(INFO, "computing release distance threshold using " << sampleCount << " samples...");
|
const std::size_t candidateCount{ std::min(allProfiles.size(), maxCandidateCount) };
|
||||||
|
|
||||||
std::minstd_rand randomEngine{ 42 };
|
std::minstd_rand randomEngine{ 42 };
|
||||||
core::random::shuffleContainer(randomEngine, allProfiles);
|
for (std::size_t i{}; i < candidateCount; ++i)
|
||||||
|
{
|
||||||
|
std::uniform_int_distribution<std::size_t> dist{ i, allProfiles.size() - 1 };
|
||||||
|
std::swap(allProfiles[i], allProfiles[dist(randomEngine)]);
|
||||||
|
}
|
||||||
|
allProfiles.resize(candidateCount);
|
||||||
|
|
||||||
|
const std::size_t sampleCount{ std::min(candidateCount, maxSampleCount) };
|
||||||
|
LOG(INFO, "computing release distance threshold using " << sampleCount << " samples on " << candidateCount << " candidates...");
|
||||||
|
|
||||||
math::StatsAccumulator<FloatType> stats;
|
math::StatsAccumulator<FloatType> stats;
|
||||||
for (std::size_t i{}; i < sampleCount; ++i)
|
for (std::size_t i{}; i < sampleCount; ++i)
|
||||||
@@ -759,6 +806,7 @@ namespace lms::recommendation
|
|||||||
LMS_SCOPED_TRACE_DETAILED("AudioSimilarityEngine", "ComputeArtistDistanceThreshold");
|
LMS_SCOPED_TRACE_DETAILED("AudioSimilarityEngine", "ComputeArtistDistanceThreshold");
|
||||||
|
|
||||||
constexpr std::size_t maxSampleCount{ 200 };
|
constexpr std::size_t maxSampleCount{ 200 };
|
||||||
|
constexpr std::size_t maxCandidateCount{ 1'000 };
|
||||||
constexpr float stdDevMultiplier{ 2.F };
|
constexpr float stdDevMultiplier{ 2.F };
|
||||||
using CosineDistance = math::NormalizedCosineDistance<ReducedVector::getSize(), FloatType>;
|
using CosineDistance = math::NormalizedCosineDistance<ReducedVector::getSize(), FloatType>;
|
||||||
|
|
||||||
@@ -767,11 +815,18 @@ namespace lms::recommendation
|
|||||||
for (const auto& [id, vecs] : _artistVectors)
|
for (const auto& [id, vecs] : _artistVectors)
|
||||||
allProfiles.push_back(&vecs);
|
allProfiles.push_back(&vecs);
|
||||||
|
|
||||||
const std::size_t sampleCount{ std::min(allProfiles.size(), maxSampleCount) };
|
// move maxCandidateCount random elements to the front
|
||||||
LOG(INFO, "computing artist distance threshold using " << sampleCount << " samples...");
|
const std::size_t candidateCount{ std::min(allProfiles.size(), maxCandidateCount) };
|
||||||
|
|
||||||
std::minstd_rand randomEngine{ 42 };
|
std::minstd_rand randomEngine{ 42 };
|
||||||
core::random::shuffleContainer(randomEngine, allProfiles);
|
for (std::size_t i{}; i < candidateCount; ++i)
|
||||||
|
{
|
||||||
|
std::uniform_int_distribution<std::size_t> dist{ i, allProfiles.size() - 1 };
|
||||||
|
std::swap(allProfiles[i], allProfiles[dist(randomEngine)]);
|
||||||
|
}
|
||||||
|
allProfiles.resize(candidateCount);
|
||||||
|
|
||||||
|
const std::size_t sampleCount{ std::min(candidateCount, maxSampleCount) };
|
||||||
|
LOG(INFO, "computing artist distance threshold using " << sampleCount << " samples on " << candidateCount << " candidates...");
|
||||||
|
|
||||||
math::StatsAccumulator<FloatType> stats;
|
math::StatsAccumulator<FloatType> stats;
|
||||||
for (std::size_t i{}; i < sampleCount; ++i)
|
for (std::size_t i{}; i < sampleCount; ++i)
|
||||||
|
|||||||
Reference in New Issue
Block a user