diff --git a/include/flucoma/algorithms/public/NNDSVD.hpp b/include/flucoma/algorithms/public/NNDSVD.hpp index 5385c941..be0c0e03 100644 --- a/include/flucoma/algorithms/public/NNDSVD.hpp +++ b/include/flucoma/algorithms/public/NNDSVD.hpp @@ -11,6 +11,7 @@ under the European Union’s Horizon 2020 research and innovation programme #pragma once #include "../util/AlgorithmUtils.hpp" +#include "../util/EigenRandom.hpp" #include "../util/FluidEigenMappings.hpp" #include "../../data/FluidIndex.hpp" #include "../../data/TensorTypes.hpp" @@ -28,7 +29,7 @@ class NNDSVD index process(RealMatrixView X, RealMatrixView W, RealMatrixView H, index minRank = 0, index maxRank = 200, double amount = 0.8, - index method = 0) // 0 - NMF-SVD, 1 NNDSVDar, 2 NNDSVDa 3 NNDSVD + index method = 0, index seed = -1) // 0 - NMF-SVD, 1 NNDSVDar, 2 NNDSVDa 3 NNDSVD { using namespace _impl; using namespace Eigen; @@ -101,14 +102,16 @@ class NNDSVD WT.col(j) = u; // avoid scaling for NMF with normalized W HT.row(j) = lbd * v; } - WT = WT.array().max(epsilon); - HT = HT.array().max(epsilon); + + double mean = XT.mean(); if (method == 1) { auto Wrand = - MatrixXd::Random(WT.rows(), WT.cols()).array().abs() / 100.0; + EigenRandom(WT.rows(), WT.cols(), RandomSeed{seed}, + Range{epsilon, mean * 0.001}); auto Hrand = - MatrixXd::Random(HT.rows(), HT.cols()).array().abs() / 100.0; + EigenRandom(HT.rows(), HT.cols(), RandomSeed{seed}, + Range{epsilon, mean * 0.001}); WT = (WT.array() < epsilon).select(Wrand, WT); HT = (HT.array() < epsilon).select(Hrand, HT); } diff --git a/include/flucoma/clients/nrt/NMFSeedClient.hpp b/include/flucoma/clients/nrt/NMFSeedClient.hpp index 36be7abe..d1c72a63 100644 --- a/include/flucoma/clients/nrt/NMFSeedClient.hpp +++ b/include/flucoma/clients/nrt/NMFSeedClient.hpp @@ -32,6 +32,7 @@ enum NMFSeedParamIndex { kMaxRank, kCoverage, kMethod, + kRandomSeed, kFFT }; @@ -46,6 +47,7 @@ constexpr auto NMFSeedParams = FloatParam("coverage", "Coverage", 0.5, Min(0), Max(1)), EnumParam("method", "Initialization Method", 0, "NMF-SVD", "NNDSVDar", "NNDSVDa", "NNDSVD"), + LongParam("seed", "Random Seed", -1), FFTParam("fftSettings", "FFT Settings", 1024, -1, -1)); class NMFSeedClient : public FluidBaseClient, public OfflineIn, public OfflineOut @@ -100,9 +102,9 @@ class NMFSeedClient : public FluidBaseClient, public OfflineIn, public OfflineOu auto nndsvd = algorithm::NNDSVD(); - index rank = nndsvd.process(magnitude, outputFilters, outputEnvelopes, - get(), get(), - get(), get()); + index rank = nndsvd.process( + magnitude, outputFilters, outputEnvelopes, get(), + get(), get(), get(), get()); auto filters = BufferAdaptor::Access{get().get()}; Result resizeResult = diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 80c3d9e0..2ad0f441 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -129,6 +129,7 @@ target_link_libraries(TestEnvelopeGate PRIVATE TestSignals) target_link_libraries(TestTransientSlice PRIVATE TestSignals) add_test_executable(TestEigenRandom algorithms/util/TestEigenRandom.cpp) +add_test_executable(TestNNDSVD algorithms/public/TestNNDSVD.cpp) add_test_executable(TestRTPGHI algorithms/util/TestRTPGHI.cpp) include(CTest) @@ -158,6 +159,7 @@ catch_discover_tests(TestBufferedProcess WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" catch_discover_tests(TestMLP WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") catch_discover_tests(TestKMeans WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) catch_discover_tests(TestEigenRandom WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +catch_discover_tests(TestNNDSVD WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) catch_discover_tests(TestNMF WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) catch_discover_tests(TestRTPGHI WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") catch_discover_tests(TestUMAP WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") diff --git a/tests/algorithms/public/TestNNDSVD.cpp b/tests/algorithms/public/TestNNDSVD.cpp new file mode 100644 index 00000000..90d63c52 --- /dev/null +++ b/tests/algorithms/public/TestNNDSVD.cpp @@ -0,0 +1,30 @@ +#define CATCH_CONFIG_MAIN +#include +#include +#include +#include + +TEST_CASE("NNDSVD Mode 1 is repeatable with manually set random seed"){ + +using Tensor = fluid::FluidTensor; +using fluid::algorithm::NNDSVD; + +// To test the effect of randomness in NNDSVD mode 1, there must be 0s in the input +Tensor input = {{0,0,0},{0,0,0},{0,0,0}}; + +std::vector Ws(3, Tensor(3,3)); +std::vector Hs(3, Tensor(3,3)); + +NNDSVD algo; + +algo.process(input,Ws[0],Hs[0],2,2,0.8,1, 42); +algo.process(input,Ws[1],Hs[1],2,2,0.8,1, 42); +algo.process(input,Ws[2],Hs[2],2,2,0.8,1, 4672); + +using Catch::Matchers::RangeEquals; + +REQUIRE_THAT(Ws[1],RangeEquals(Ws[0])); +REQUIRE_THAT(Ws[1],!RangeEquals(Ws[2])); +REQUIRE_THAT(Hs[1],RangeEquals(Hs[0])); +REQUIRE_THAT(Hs[1],!RangeEquals(Hs[2])); +} \ No newline at end of file