Add SampleInitialization and different type of initialization.

This commit is contained in:
Ryan Curtin
2016-04-12 14:43:52 +00:00
parent cb3c489bee
commit a38608bea2
4 changed files with 138 additions and 21 deletions
+1
View File
@@ -25,6 +25,7 @@ set(SOURCES
random_partition.hpp
refined_start.hpp
refined_start_impl.hpp
sample_initialization.hpp
)
# Add directory name to sources.
+6 -5
View File
@@ -10,7 +10,7 @@
#include <mlpack/core.hpp>
#include <mlpack/core/metrics/lmetric.hpp>
#include "random_partition.hpp"
#include "sample_initialization.hpp"
#include "max_variance_new_cluster.hpp"
#include "naive_kmeans.hpp"
@@ -47,8 +47,9 @@ namespace kmeans /** K-Means clustering. */ {
* @tparam MetricType The distance metric to use for this KMeans; see
* metric::LMetric for an example.
* @tparam InitialPartitionPolicy Initial partitioning policy; must implement a
* default constructor and 'void Cluster(const arma::mat&, const size_t,
* arma::Row<size_t>&)'.
* default constructor and either 'void Cluster(const arma::mat&, const
* size_t, arma::Row<size_t>&)' or 'void Cluster(const arma::mat&, const
* size_t, arma::mat&)'.
* @tparam EmptyClusterPolicy Policy for what to do on an empty cluster; must
* implement a default constructor and 'void EmptyCluster(const arma::mat&
* data, const size_t emptyCluster, const arma::mat& oldCentroids,
@@ -56,11 +57,11 @@ namespace kmeans /** K-Means clustering. */ {
* const size_t iteration)'.
* @tparam LloydStepType Implementation of single Lloyd step to use.
*
* @see RandomPartition, RefinedStart, AllowEmptyClusters,
* @see RandomPartition, SampleInitialization, RefinedStart, AllowEmptyClusters,
* MaxVarianceNewCluster, NaiveKMeans, ElkanKMeans
*/
template<typename MetricType = metric::EuclideanDistance,
typename InitialPartitionPolicy = RandomPartition,
typename InitialPartitionPolicy = SampleInitialization,
typename EmptyClusterPolicy = MaxVarianceNewCluster,
template<class, class> class LloydStepType = NaiveKMeans,
typename MatType = arma::mat>
+82 -16
View File
@@ -8,10 +8,71 @@
#include "kmeans.hpp"
#include <mlpack/core/metrics/lmetric.hpp>
#include <mlpack/core/util/sfinae_utility.hpp>
namespace mlpack {
namespace kmeans {
/**
* This gives us a GivesCentroids object that we can use to tell whether or not
* an InitialPartitionPolicy returns centroids or point assignments.
*/
HAS_MEM_FUNC(Cluster, GivesCentroidsCheck);
/**
* 'value' is true if the InitialPartitionPolicy class has a member
* Cluster(const arma::mat& data, const size_t clusters, arma::mat& centroids).
*/
template<typename InitialPartitionPolicy>
struct GivesCentroids
{
static const bool value =
// Non-static version.
GivesCentroidsCheck<InitialPartitionPolicy,
void(InitialPartitionPolicy::*)(const arma::mat&,
const size_t,
arma::mat&)>::value ||
// Static version.
GivesCentroidsCheck<InitialPartitionPolicy,
void(*)(const arma::mat&, const size_t, arma::mat&)>::value;
};
//! Call the initial partition policy, if it returns assignments. This returns
//! 'true' to indicate that assignments were given.
template<typename MatType,
typename InitialPartitionPolicy>
bool GetInitialAssignmentsOrCentroids(
InitialPartitionPolicy& ipp,
const MatType& data,
const size_t clusters,
arma::Row<size_t>& assignments,
arma::mat& /* centroids */,
const typename boost::disable_if_c<
GivesCentroids<InitialPartitionPolicy>::value == true>::type* = 0)
{
ipp.Cluster(data, clusters, assignments);
return true;
}
//! Call the initial partition policy, if it returns centroids. This returns
//! 'false' to indicate that assignments were not given.
template<typename MatType,
typename InitialPartitionPolicy>
bool GetInitialAssignmentsOrCentroids(
InitialPartitionPolicy& ipp,
const MatType& data,
const size_t clusters,
arma::Row<size_t>& /* assignments */,
arma::mat& centroids,
const typename boost::enable_if_c<
GivesCentroids<InitialPartitionPolicy>::value == true>::type* = 0)
{
ipp.Cluster(data, clusters, centroids);
return false;
}
/**
* Construct the K-Means object.
*/
@@ -110,25 +171,30 @@ Cluster(const MatType& data,
// the initial centroids.
if (!initialGuess)
{
// The partitioner gives assignments, so we need to calculate centroids from
// those assignments. This is probably not the most efficient way to do
// this, so maybe refactoring should be considered in the future.
// The GetInitialAssignmentsOrCentroids() function will call the appropriate
// function in the InitialPartitionPolicy to return either assignments or
// centroids. We prefer centroids, but if assignments are returned, then we
// have to calculate the initial centroids for the first iteration.
arma::Row<size_t> assignments;
partitioner.Cluster(data, clusters, assignments);
// Calculate initial centroids.
arma::Row<size_t> counts;
counts.zeros(clusters);
centroids.zeros(data.n_rows, clusters);
for (size_t i = 0; i < data.n_cols; ++i)
bool gotAssignments = GetInitialAssignmentsOrCentroids(partitioner, data,
clusters, assignments, centroids);
if (gotAssignments)
{
centroids.col(assignments[i]) += arma::vec(data.col(i));
counts[assignments[i]]++;
}
// The partitioner gives assignments, so we need to calculate centroids
// from those assignments.
arma::Row<size_t> counts;
counts.zeros(clusters);
centroids.zeros(data.n_rows, clusters);
for (size_t i = 0; i < data.n_cols; ++i)
{
centroids.col(assignments[i]) += arma::vec(data.col(i));
counts[assignments[i]]++;
}
for (size_t i = 0; i < clusters; ++i)
if (counts[i] != 0)
centroids.col(i) /= counts[i];
for (size_t i = 0; i < clusters; ++i)
if (counts[i] != 0)
centroids.col(i) /= counts[i];
}
}
// Counts of points in each cluster.
@@ -0,0 +1,49 @@
/**
* @file sample_initialization.hpp
* @author Ryan Curtin
*
* In order to construct initial centroids, randomly sample points from the
* dataset. This tends to give better results than the RandomPartition
* strategy.
*/
#ifndef __MLPACK_METHODS_KMEANS_SAMPLE_INITIALIZATION_HPP
#define __MLPACK_METHODS_KMEANS_SAMPLE_INITIALIZATION_HPP
#include <mlpack/core.hpp>
namespace mlpack {
namespace kmeans {
class SampleInitialization
{
public:
//! Empty constructor, required by the InitialPartitionPolicy type definition.
SampleInitialization() { }
/**
* Initialize the centroids matrix by randomly sampling points from the data
* matrix.
*
* @param data Dataset.
* @param clusters Number of clusters.
* @param centroids Matrix to put initial centroids into.
*/
template<typename MatType>
inline static void Cluster(const MatType& data,
const size_t clusters,
arma::mat& centroids)
{
centroids.set_size(data.n_rows, clusters);
for (size_t i = 0; i < clusters; ++i)
{
// Randomly sample a point.
const size_t index = math::RandInt(0, data.n_cols);
centroids.col(i) = data.col(index);
}
}
};
} // namespace kmeans
} // namespace mlpack
#endif