Merge branch 'cf-norm-cli-support' of https://github.com/khizirsiddiqui/mlpack into khizirsiddiqui-cf-norm-cli-support

This commit is contained in:
Ryan Curtin
2020-01-26 22:00:23 -05:00
4 changed files with 247 additions and 36 deletions
+16 -1
View File
@@ -100,6 +100,15 @@ PROGRAM_INFO("Collaborative Filtering",
" - 'average' -- Average Interpolation Algorithm\n"
" - 'regression' -- Regression Interpolation Algorithm\n"
" - 'similarity' -- Similarity Interpolation Algorithm\n"
"\n\n"
"The following ranking normalization algorithms can be specified via" +
" the " + PRINT_PARAM_STRING("normalization") + " parameter:"
"\n"
" - 'none' -- No Normalization\n"
" - 'item_mean' -- Item Mean Normalization\n"
" - 'overall_mean' -- Overall Mean Normalization\n"
" - 'user_mean' -- User Mean Normalization\n"
" - 'z_score' -- Z-Score Normalization\n"
"\n"
"A trained model may be saved to with the " +
PRINT_PARAM_STRING("output_model") + " output parameter."
@@ -136,6 +145,8 @@ PROGRAM_INFO("Collaborative Filtering",
PARAM_MATRIX_IN("training", "Input dataset to perform CF on.", "t");
PARAM_STRING_IN("algorithm", "Algorithm used for matrix factorization.", "a",
"NMF");
PARAM_STRING_IN("normalization", "Normalization performed on the ratings.", "z",
"none");
PARAM_INT_IN("neighborhood", "Size of the neighborhood of similar users to "
"consider for each query user.", "n", 5);
PARAM_INT_IN("rank", "Rank of decomposed matrices (if 0, a heuristic is used to"
@@ -372,8 +383,12 @@ void PerformAction(arma::mat& dataset,
{
const size_t neighborhood = (size_t) CLI::GetParam<int>("neighborhood");
CFModel* c = new CFModel();
const string normalizationType = CLI::GetParam<string>("normalization");
c->template Train<DecompositionPolicy>(dataset, neighborhood, rank,
maxIterations, minResidue, CLI::HasParam("iteration_only_termination"));
maxIterations, minResidue, CLI::HasParam("iteration_only_termination"),
normalizationType);
PerformAction(c);
}
+71 -21
View File
@@ -1,6 +1,7 @@
/**
* @file cf_model.hpp
* @author Wenhao Huang
* @author Khizir Siddiqui
*
* A serializable CF model, used by the main program.
*
@@ -24,6 +25,12 @@
#include <mlpack/methods/cf/decomposition_policies/bias_svd_method.hpp>
#include <mlpack/methods/cf/decomposition_policies/svdplusplus_method.hpp>
#include <mlpack/methods/cf/normalization/no_normalization.hpp>
#include <mlpack/methods/cf/normalization/overall_mean_normalization.hpp>
#include <mlpack/methods/cf/normalization/user_mean_normalization.hpp>
#include <mlpack/methods/cf/normalization/item_mean_normalization.hpp>
#include <mlpack/methods/cf/normalization/z_score_normalization.hpp>
namespace mlpack {
namespace cf {
@@ -35,8 +42,9 @@ class DeleteVisitor : public boost::static_visitor<void>
{
public:
//! Delete CFType object.
template<typename DecompositionPolicy>
void operator()(CFType<DecompositionPolicy>* c) const;
template <typename DecompositionPolicy,
typename NormalizationType = NoNormalization>
void operator()(CFType<DecompositionPolicy, NormalizationType>* c) const;
};
/**
@@ -46,8 +54,9 @@ class GetValueVisitor : public boost::static_visitor<void*>
{
public:
//! Return stored pointer as void* type.
template<typename DecompositionPolicy>
void* operator()(CFType<DecompositionPolicy>* c) const;
template <typename DecompositionPolicy,
typename NormalizationType = NoNormalization>
void* operator()(CFType<DecompositionPolicy, NormalizationType>* c) const;
};
/**
@@ -66,8 +75,9 @@ class PredictVisitor : public boost::static_visitor<void>
public:
//! Predict ratings for each user-item combination.
template<typename DecompositionPolicy>
void operator()(CFType<DecompositionPolicy>* c) const;
template <typename DecompositionPolicy,
typename NormalizationType = NoNormalization>
void operator()(CFType<DecompositionPolicy, NormalizationType>* c) const;
//! Visitor constructor.
PredictVisitor(const arma::Mat<size_t>& combinations,
@@ -100,8 +110,9 @@ class RecommendationVisitor : public boost::static_visitor<void>
const bool usersGiven);
//! Generates the given number of recommendations.
template<typename DecompositionPolicy>
void operator()(CFType<DecompositionPolicy>* c) const;
template <typename DecompositionPolicy,
typename NormalizationType = NoNormalization>
void operator()(CFType<DecompositionPolicy, NormalizationType>* c) const;
};
/**
@@ -112,17 +123,54 @@ class CFModel
private:
/**
* cf holds an instance of the CFType class for the current
* decompositionPolicy. It is initialized every time Train() is executed.
* We access to the contained value through the visitor classes defined above.
* decompositionPolicy and normalizationType. It is initialized every time
* Train() is executed. We access to the contained value through the visitor
* classes defined above.
*/
boost::variant<CFType<NMFPolicy>*,
CFType<BatchSVDPolicy>*,
CFType<RandomizedSVDPolicy>*,
CFType<RegSVDPolicy>*,
CFType<SVDCompletePolicy>*,
CFType<SVDIncompletePolicy>*,
CFType<BiasSVDPolicy>*,
CFType<SVDPlusPlusPolicy>*> cf;
boost::variant<CFType<NMFPolicy, NoNormalization>*,
CFType<BatchSVDPolicy, NoNormalization>*,
CFType<RandomizedSVDPolicy, NoNormalization>*,
CFType<RegSVDPolicy, NoNormalization>*,
CFType<SVDCompletePolicy, NoNormalization>*,
CFType<SVDIncompletePolicy, NoNormalization>*,
CFType<BiasSVDPolicy, NoNormalization>*,
CFType<SVDPlusPlusPolicy, NoNormalization>*,
CFType<NMFPolicy, ItemMeanNormalization>*,
CFType<BatchSVDPolicy, ItemMeanNormalization>*,
CFType<RandomizedSVDPolicy, ItemMeanNormalization>*,
CFType<RegSVDPolicy, ItemMeanNormalization>*,
CFType<SVDCompletePolicy, ItemMeanNormalization>*,
CFType<SVDIncompletePolicy, ItemMeanNormalization>*,
CFType<BiasSVDPolicy, ItemMeanNormalization>*,
CFType<SVDPlusPlusPolicy, ItemMeanNormalization>*,
CFType<NMFPolicy, UserMeanNormalization>*,
CFType<BatchSVDPolicy, UserMeanNormalization>*,
CFType<RandomizedSVDPolicy, UserMeanNormalization>*,
CFType<RegSVDPolicy, UserMeanNormalization>*,
CFType<SVDCompletePolicy, UserMeanNormalization>*,
CFType<SVDIncompletePolicy, UserMeanNormalization>*,
CFType<BiasSVDPolicy, UserMeanNormalization>*,
CFType<SVDPlusPlusPolicy, UserMeanNormalization>*,
CFType<NMFPolicy, OverallMeanNormalization>*,
CFType<BatchSVDPolicy, OverallMeanNormalization>*,
CFType<RandomizedSVDPolicy, OverallMeanNormalization>*,
CFType<RegSVDPolicy, OverallMeanNormalization>*,
CFType<SVDCompletePolicy, OverallMeanNormalization>*,
CFType<SVDIncompletePolicy, OverallMeanNormalization>*,
CFType<BiasSVDPolicy, OverallMeanNormalization>*,
CFType<SVDPlusPlusPolicy, OverallMeanNormalization>*,
CFType<NMFPolicy, ZScoreNormalization>*,
CFType<BatchSVDPolicy, ZScoreNormalization>*,
CFType<RandomizedSVDPolicy, ZScoreNormalization>*,
CFType<RegSVDPolicy, ZScoreNormalization>*,
CFType<SVDCompletePolicy, ZScoreNormalization>*,
CFType<SVDIncompletePolicy, ZScoreNormalization>*,
CFType<BiasSVDPolicy, ZScoreNormalization>*,
CFType<SVDPlusPlusPolicy, ZScoreNormalization>*> cf;
public:
//! Create an empty CF model.
@@ -132,8 +180,9 @@ class CFModel
~CFModel();
//! Get the pointer to CFType<> object.
template<typename DecompositionPolicy>
const CFType<DecompositionPolicy>* CFPtr() const;
template <typename DecompositionPolicy,
typename NormalizationType = NoNormalization>
const CFType<DecompositionPolicy, NormalizationType>* CFPtr() const;
//! Train the model.
template<typename DecompositionPolicy,
@@ -143,7 +192,8 @@ class CFModel
const size_t rank,
const size_t maxIterations,
const double minResidue,
const bool mit);
const bool mit,
const std::string& normalizationType = "none");
//! Make predictions.
template <typename NeighborSearchPolicy,
+62 -14
View File
@@ -16,17 +16,27 @@
#include <boost/serialization/variant.hpp>
#include <mlpack/methods/cf/normalization/no_normalization.hpp>
#include <mlpack/methods/cf/normalization/overall_mean_normalization.hpp>
#include <mlpack/methods/cf/normalization/user_mean_normalization.hpp>
#include <mlpack/methods/cf/normalization/item_mean_normalization.hpp>
#include <mlpack/methods/cf/normalization/z_score_normalization.hpp>
using namespace mlpack::cf;
template<typename DecompositionPolicy>
void DeleteVisitor::operator()(CFType<DecompositionPolicy>* c) const
template <typename DecompositionPolicy,
typename NormalizationType>
void DeleteVisitor::
operator()(CFType<DecompositionPolicy, NormalizationType>* c) const
{
if (c)
delete c;
}
template<typename DecompositionPolicy>
void* GetValueVisitor::operator()(CFType<DecompositionPolicy>* c) const
template <typename DecompositionPolicy,
typename NormalizationType>
void* GetValueVisitor::
operator()(CFType<DecompositionPolicy, NormalizationType>* c) const
{
if (!c)
throw std::runtime_error("no cf model initialized");
@@ -45,9 +55,10 @@ PredictVisitor<NeighborSearchPolicy, InterpolationPolicy>::PredictVisitor(
template <typename NeighborSearchPolicy,
typename InterpolationPolicy>
template<typename DecompositionPolicy>
template <typename DecompositionPolicy,
typename NormalizationType>
void PredictVisitor<NeighborSearchPolicy, InterpolationPolicy>
::operator()(CFType<DecompositionPolicy>* c) const
::operator()(CFType<DecompositionPolicy, NormalizationType>* c) const
{
if (!c)
{
@@ -75,9 +86,10 @@ RecommendationVisitor<NeighborSearchPolicy, InterpolationPolicy>
template <typename NeighborSearchPolicy,
typename InterpolationPolicy>
template<typename DecompositionPolicy>
template <typename DecompositionPolicy,
typename NormalizationType>
void RecommendationVisitor<NeighborSearchPolicy, InterpolationPolicy>
::operator()(CFType<DecompositionPolicy>* c) const
::operator()(CFType<DecompositionPolicy, NormalizationType>* c) const
{
if (!c)
{
@@ -105,15 +117,50 @@ void CFModel::Train(const MatType& data,
const size_t rank,
const size_t maxIterations,
const double minResidue,
const bool mit)
const bool mit,
const std::string& normalization)
{
// Delete the current CFType object, if there is one.
boost::apply_visitor(DeleteVisitor(), cf);
// Instantiate a new CFType object.
DecompositionPolicy decomposition;
cf = new CFType<DecompositionPolicy>(data, decomposition,
numUsersForSimilarity, rank, maxIterations, minResidue, mit);
if (normalization == "overall_mean")
{
cf = new CFType<DecompositionPolicy, OverallMeanNormalization>(data,
decomposition, numUsersForSimilarity, rank, maxIterations, minResidue,
mit);
}
else if (normalization == "item_mean")
{
cf = new CFType<DecompositionPolicy, ItemMeanNormalization>(data,
decomposition, numUsersForSimilarity, rank, maxIterations, minResidue,
mit);
}
else if (normalization == "user_mean")
{
cf = new CFType<DecompositionPolicy, UserMeanNormalization>(data,
decomposition, numUsersForSimilarity, rank, maxIterations, minResidue,
mit);
}
else if (normalization == "z_score")
{
cf = new CFType<DecompositionPolicy, ZScoreNormalization>(data,
decomposition, numUsersForSimilarity, rank, maxIterations, minResidue,
mit);
}
else if (normalization == "none")
{
cf = new CFType<DecompositionPolicy, NoNormalization>(data,
decomposition, numUsersForSimilarity, rank, maxIterations, minResidue,
mit);
}
else
{
throw std::runtime_error("Unsupported normalization algorithm."
" It should be one of none, overall_mean, "
"item_mean, user_mean or z_score");
}
}
//! Make predictions.
@@ -151,11 +198,12 @@ void CFModel::GetRecommendations(const size_t numRecs,
boost::apply_visitor(recommendation, cf);
}
template<typename DecompositionPolicy>
const CFType<DecompositionPolicy>* CFModel::CFPtr() const
template <typename DecompositionPolicy,
typename NormalizationType>
const CFType<DecompositionPolicy, NormalizationType>* CFModel::CFPtr() const
{
void* pointer = boost::apply_visitor(GetValueVisitor(), cf);
return (CFType<DecompositionPolicy>*) pointer;
return (CFType<DecompositionPolicy, NormalizationType>*) pointer;
}
template<typename Archive>
+98
View File
@@ -616,4 +616,102 @@ BOOST_AUTO_TEST_CASE(CFNeighborSearchTest)
BOOST_REQUIRE(arma::any(arma::vectorise(output1 != output3)));
}
/**
* Ensure normalization algorithm is one of { "none", "z_score",
* "item_mean", "user_mean" }.
*/
BOOST_AUTO_TEST_CASE(CFNormalizationBoundTest)
{
mat dataset;
data::Load("GroupLensSmall.csv", dataset);
const int querySize = 7;
Mat<size_t> query = arma::linspace<Mat<size_t>>(0, querySize - 1, querySize);
SetInputParam("neighbor_search", std::string("cosine"));
SetInputParam("algorithm", std::string("NMF"));
// Normalization algorithm should be valid.
SetInputParam("normalization", std::string("invalid_normalization"));
SetInputParam("training", std::move(dataset));
SetInputParam("query", query);
Log::Fatal.ignoreInput = true;
BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error);
Log::Fatal.ignoreInput = false;
}
/**
* Ensure that using normalization techniques make difference.
*/
BOOST_AUTO_TEST_CASE(CFNormalizationTest)
{
mat dataset;
data::Load("GroupLensSmall.csv", dataset);
const int querySize = 7;
Mat<size_t> query = arma::linspace<Mat<size_t>>(0, querySize - 1, querySize);
// Query with different normalization techniques.
ResetSettings();
SetInputParam("training", dataset);
SetInputParam("max_iterations", int(10));
SetInputParam("query", query);
SetInputParam("algorithm", std::string("NMF"));
// Using without Normalization.
SetInputParam("normalization", std::string("none"));
SetInputParam("recommendations", 5);
mlpackMain();
const arma::Mat<size_t> output1 = CLI::GetParam<arma::Mat<size_t>>("output");
BOOST_REQUIRE_EQUAL(output1.n_rows, 5);
BOOST_REQUIRE_EQUAL(output1.n_cols, 7);
// Query with different normalization techniques.
ResetSettings();
SetInputParam("training", dataset);
SetInputParam("max_iterations", int(10));
SetInputParam("query", query);
SetInputParam("algorithm", std::string("NMF"));
// Using Item Mean normalization.
SetInputParam("normalization", std::string("item_mean"));
SetInputParam("recommendations", 5);
mlpackMain();
const arma::Mat<size_t> output2 = CLI::GetParam<arma::Mat<size_t>>("output");
BOOST_REQUIRE_EQUAL(output2.n_rows, 5);
BOOST_REQUIRE_EQUAL(output2.n_cols, 7);
// Query with different normalization techniques.
ResetSettings();
SetInputParam("training", dataset);
SetInputParam("max_iterations", int(10));
SetInputParam("query", query);
SetInputParam("algorithm", std::string("NMF"));
// Using Z-Score normalization.
SetInputParam("normalization", std::string("z_score"));
SetInputParam("recommendations", 5);
mlpackMain();
const arma::Mat<size_t> output3 = CLI::GetParam<arma::Mat<size_t>>("output");
BOOST_REQUIRE_EQUAL(output3.n_rows, 5);
BOOST_REQUIRE_EQUAL(output3.n_cols, 7);
// The resulting matrices should be different.
BOOST_REQUIRE(arma::any(arma::vectorise(output1 != output2)));
BOOST_REQUIRE(arma::any(arma::vectorise(output1 != output3)));
}
BOOST_AUTO_TEST_SUITE_END();