Merge branch 'cf-norm-cli-support' of https://github.com/khizirsiddiqui/mlpack into khizirsiddiqui-cf-norm-cli-support
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user