Add SampleInitialization and different type of initialization.
This commit is contained in:
@@ -25,6 +25,7 @@ set(SOURCES
|
||||
random_partition.hpp
|
||||
refined_start.hpp
|
||||
refined_start_impl.hpp
|
||||
sample_initialization.hpp
|
||||
)
|
||||
|
||||
# Add directory name to sources.
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user