1462 lines
42 KiB
C++
1462 lines
42 KiB
C++
#include "thor/thor.h"
|
|
#include "fastlib/fastlib.h"
|
|
|
|
#include "base/deprecated.h"
|
|
|
|
#include <string.h>
|
|
#include <sys/time.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
/*
|
|
|
|
As two-variable functions:
|
|
|
|
\rho(i, k) = \sum_{j != i, j != k} max(0, S(j,k) - \alpha(j,k))
|
|
|
|
\alpha(i, k) = min(0, \max_{j != k} S(j,j) + \alpha(j,j) + \rho(i,j))
|
|
|
|
As one-variable rho:
|
|
|
|
\rho(k) = \sum_{j != k} max(0, S(j,k) - \alpha(j,k))
|
|
|
|
\sum max(0, S(j,k) - \alpha(j,k)) - max(0, S(k,k) - alpha(k,k))
|
|
|
|
\alpha(i, k) = min(0, \max_{j != k} S(j,j) + \alpha(j,j)
|
|
+ \rho(i) - max(0, S(i, j) - \alpha(i, j)))
|
|
|
|
\alpha(i) = min(0, \max^2{j}
|
|
S(j,j) + \alpha(j,j) + \rho(j) - max(0, S(i, j) - \alpha(i, j)))
|
|
except when i = j in which case we don't need to do the second part
|
|
|
|
S(j,j) + \alpha(j,j) + \rho(j) - S(i, j) + min(S(i,j), \alpha(i, j))
|
|
*/
|
|
|
|
#define SIMILARITY_MAX 0
|
|
|
|
struct AffinityCommon {
|
|
struct Param {
|
|
public:
|
|
/** Self-pereference. */
|
|
double pref;
|
|
/** The damping factor. */
|
|
double lambda;
|
|
/** The proportion of points to prime as exemplars. */
|
|
double prime;
|
|
/** The dimensionality. */
|
|
index_t dim;
|
|
|
|
OT_DEF(Param) {
|
|
OT_MY_OBJECT(pref);
|
|
OT_MY_OBJECT(lambda);
|
|
OT_MY_OBJECT(prime);
|
|
OT_MY_OBJECT(dim);
|
|
}
|
|
|
|
public:
|
|
void Init(datanode *module) {
|
|
pref = fx_param_double_req(module, "pref");
|
|
lambda = fx_param_double(module, "lambda", 0.6);
|
|
prime = fx_param_double(module, "prime", 0.002);
|
|
}
|
|
};
|
|
|
|
/** The bounding type. Required by THOR. */
|
|
typedef DHrectBound<2> Bound;
|
|
|
|
// struct MomentInfo {
|
|
// public:
|
|
// Vector mass;
|
|
// double sumsq;
|
|
// index_t count;
|
|
//
|
|
// OT_DEF_BASIC(MomentInfo) {
|
|
// OT_MY_OBJECT(mass);
|
|
// OT_MY_OBJECT(sumsq);
|
|
// OT_MY_OBJECT(count);
|
|
// }
|
|
//
|
|
// public:
|
|
// void Init(int dim) {
|
|
// mass.Init(dim);
|
|
// Reset();
|
|
// }
|
|
//
|
|
// void Reset() {
|
|
// mass.SetZero();
|
|
// sumsq = 0;
|
|
// count = 0;
|
|
// }
|
|
//
|
|
// void Add(index_t count_in, const Vector& mass_in, double sumsq_in) {
|
|
// if (unlikely(count_in != 0)) {
|
|
// la::AddTo(mass_in, &mass);
|
|
// sumsq += sumsq_in;
|
|
// count += count_in;
|
|
// }
|
|
// }
|
|
//
|
|
// void Add(const MomentInfo& other) {
|
|
// Add(other.count, other.mass, other.sumsq);
|
|
// }
|
|
//
|
|
// void Add(const Vector& v) {
|
|
// Add(1, v, la::Dot(v, v));
|
|
// }
|
|
//
|
|
// double ComputeDistanceSqSum(const Vector& q) const {
|
|
// double quadratic_term =
|
|
// + count * la::Dot(q, q)
|
|
// - 2.0 * la::Dot(q, mass)
|
|
// + sumsq;
|
|
// return quadratic_term;
|
|
// }
|
|
//
|
|
// double ComputeDistanceSqSum(double distance_squared,
|
|
// double center_dot_center) const {
|
|
// //q*q - 2qr + rsumsq
|
|
// //q*q - 2qr + r*r - r*r
|
|
// double quadratic_term =
|
|
// (distance_squared - center_dot_center) * count
|
|
// + sumsq;
|
|
//
|
|
// return quadratic_term;
|
|
// }
|
|
//
|
|
// DRange ComputeDistanceSqSumRange(
|
|
// const Bound& query_bound) const {
|
|
// DRange distsq_sum_bound;
|
|
// Vector center;
|
|
// double inv_count = 1.0 / count;
|
|
// double center_dot_center = la::Dot(mass, mass) * inv_count * inv_count;
|
|
//
|
|
// DEBUG_ASSERT(count != 0);
|
|
//
|
|
// center.Copy(mass);
|
|
// for (index_t i = center.length(); i--;) {
|
|
// center[i] *= inv_count;
|
|
// }
|
|
//
|
|
// distsq_sum_bound.lo = ComputeDistanceSqSum(
|
|
// query_bound.MaxDistanceSq(center), center_dot_center);
|
|
// distsq_sum_bound.hi = ComputeDistanceSqSum(
|
|
// query_bound.MinDistanceSq(center), center_dot_center);
|
|
//
|
|
// return distsq_sum_bound;
|
|
// }
|
|
//
|
|
// bool is_empty() const {
|
|
// return likely(count == 0);
|
|
// }
|
|
// };
|
|
|
|
/**
|
|
* Alpha corresponds to "maximum availability" with the != k condition.
|
|
*/
|
|
struct Alpha {
|
|
public:
|
|
double max2;
|
|
double max1_sim;
|
|
double max1;
|
|
index_t max1_index;
|
|
|
|
OT_DEF(Alpha) {
|
|
OT_MY_OBJECT(max2);
|
|
OT_MY_OBJECT(max1_sim);
|
|
OT_MY_OBJECT(max1);
|
|
OT_MY_OBJECT(max1_index);
|
|
}
|
|
|
|
public:
|
|
double get(index_t i) const {
|
|
if (unlikely(i == max1_index)) {
|
|
return max2;
|
|
} else {
|
|
return max1;
|
|
}
|
|
}
|
|
};
|
|
|
|
struct CombinedInfo {
|
|
/** Maximum availability of the point. */
|
|
Alpha alpha;
|
|
/** Sum of responsibilities. */
|
|
double rho;
|
|
|
|
OT_DEF(CombinedInfo) {
|
|
OT_MY_OBJECT(alpha);
|
|
OT_MY_OBJECT(rho);
|
|
}
|
|
};
|
|
|
|
class Point {
|
|
private:
|
|
Vector vec_;
|
|
CombinedInfo info_;
|
|
|
|
OT_DEF(Point) {
|
|
OT_MY_OBJECT(vec_);
|
|
OT_MY_OBJECT(info_);
|
|
}
|
|
|
|
public:
|
|
void Init(const Param& param, const DatasetInfo& schema) {
|
|
vec_.Init(schema.n_features());
|
|
info_.alpha.max1 = 0;
|
|
info_.alpha.max2 = 0;
|
|
info_.alpha.max1_index = -1;
|
|
info_.alpha.max1_sim = 0;
|
|
info_.rho = param.pref;
|
|
}
|
|
|
|
void Set(const Param& param, index_t index, const Vector& data) {
|
|
vec_.CopyValues(data);
|
|
// Randomly prime points to be exemplars.
|
|
if (math::Random(0.0, 1.0) < param.prime) {
|
|
info_.rho = -param.pref / 2;
|
|
}
|
|
}
|
|
|
|
const Vector& vec() const { return vec_; }
|
|
Vector& vec() { return vec_; }
|
|
|
|
const CombinedInfo& info() const { return info_; }
|
|
CombinedInfo& info() { return info_; }
|
|
};
|
|
|
|
struct CombinedStat {
|
|
public:
|
|
DRange alpha;
|
|
DRange rho;
|
|
|
|
OT_DEF(CombinedStat) {
|
|
OT_MY_OBJECT(alpha);
|
|
OT_MY_OBJECT(rho);
|
|
}
|
|
|
|
public:
|
|
void Init(const Param& param) {
|
|
Reset(param);
|
|
}
|
|
void Reset(const Param& param) {
|
|
alpha.InitEmptySet();
|
|
rho.InitEmptySet();
|
|
}
|
|
void Accumulate(const Param& param, const Point& point) {
|
|
alpha |= DRange(point.info().alpha.max2, point.info().alpha.max1);
|
|
rho |= point.info().rho;
|
|
}
|
|
void Accumulate(const Param& param,
|
|
const CombinedStat& stat, const Bound& bound, index_t n) {
|
|
alpha |= stat.alpha;
|
|
rho |= stat.rho;
|
|
}
|
|
void Postprocess(const Param& param, const Bound& bound, index_t n) {}
|
|
};
|
|
|
|
typedef ThorNode<Bound, CombinedStat> Node;
|
|
|
|
struct Helpers {
|
|
static double Similarity(double distsq) {
|
|
return -distsq;
|
|
}
|
|
static double Similarity(const Vector& a, const Vector& b) {
|
|
//uint32 anum = (mem::PointerAbsoluteAddress(a.ptr()) * 315187727);
|
|
//uint32 bnum = (mem::PointerAbsoluteAddress(b.ptr()) * 210787727);
|
|
//uint32 val = ((anum - bnum) >> 16) & 0xfff;
|
|
//double noise = (1.0e-5 / 4096) * val;
|
|
return Similarity(la::DistanceSqEuclidean(a, b));
|
|
}
|
|
static double Similarity(
|
|
const Param& param,
|
|
const Vector& q, index_t q_index,
|
|
const Vector& r, index_t r_index) {
|
|
if (unlikely(q_index == r_index)) {
|
|
return param.pref;
|
|
} else {
|
|
return Similarity(q, r);
|
|
}
|
|
}
|
|
static double SimilarityHi(
|
|
const Param& param,
|
|
const Node& a, const Node& b) {
|
|
double distsq = a.bound().MinDistanceSq(b.bound());
|
|
double hi = Similarity(distsq);
|
|
if (a.begin() < b.end() && b.begin() < a.end()
|
|
&& param.pref > hi) {
|
|
hi = param.pref;
|
|
}
|
|
return hi;
|
|
}
|
|
static double SimilarityLo(
|
|
const Param& param,
|
|
const Node& a, const Node& b) {
|
|
double distsq = a.bound().MaxDistanceSq(b.bound());
|
|
double lo = Similarity(distsq);
|
|
if (a.begin() < b.end() && b.begin() < a.end()
|
|
&& param.pref < lo) {
|
|
lo = param.pref;
|
|
}
|
|
return lo;
|
|
}
|
|
static double SimilarityHi(
|
|
const Param& param,
|
|
const Vector& a, index_t a_index, const Node& b) {
|
|
double distsq = b.bound().MinDistanceSq(a);
|
|
double hi = Similarity(distsq);
|
|
if (a_index < b.end() && a_index >= b.begin()
|
|
&& param.pref > hi) {
|
|
hi = param.pref;
|
|
}
|
|
return hi;
|
|
}
|
|
static double SimilarityLo(
|
|
const Param& param,
|
|
const Vector& a, index_t a_index, const Node& b) {
|
|
double distsq = b.bound().MaxDistanceSq(a);
|
|
double lo = Similarity(distsq);
|
|
if (a_index < b.end() && a_index >= b.begin()
|
|
&& param.pref < lo) {
|
|
lo = param.pref;
|
|
}
|
|
return lo;
|
|
}
|
|
};
|
|
};
|
|
|
|
class AffinityAlpha {
|
|
public:
|
|
typedef AffinityCommon::Point QPoint;
|
|
typedef AffinityCommon::Point RPoint;
|
|
|
|
typedef AffinityCommon::Alpha Alpha;
|
|
|
|
typedef AffinityCommon::Param Param;
|
|
|
|
typedef AffinityCommon::Node QNode;
|
|
typedef AffinityCommon::Node RNode;
|
|
|
|
typedef BlankGlobalResult GlobalResult;
|
|
|
|
typedef BlankQPostponed QPostponed;
|
|
|
|
struct Delta {
|
|
public:
|
|
DRange alpha;
|
|
|
|
OT_DEF(Delta) {
|
|
OT_MY_OBJECT(alpha);
|
|
}
|
|
|
|
public:
|
|
void Init(const Param& param) {
|
|
}
|
|
};
|
|
|
|
struct QResult {
|
|
public:
|
|
Alpha alpha;
|
|
|
|
OT_DEF(QResult) {
|
|
OT_MY_OBJECT(alpha);
|
|
}
|
|
|
|
public:
|
|
void Init(const Param& param) {
|
|
Reset();
|
|
}
|
|
void Seed(const Param& param, const QPoint& q_point) {}
|
|
void Reset() {
|
|
alpha.max1 = -DBL_MAX;
|
|
alpha.max2 = -DBL_MAX;
|
|
alpha.max1_index = -1;
|
|
alpha.max1_sim = -DBL_MAX;
|
|
}
|
|
void Postprocess(const Param& param,
|
|
const QPoint& q, index_t q_index, const RNode& r_root) {}
|
|
void ApplyPostponed(const Param& param,
|
|
const QPostponed& postponed, const QPoint& q, index_t q_i) {}
|
|
};
|
|
|
|
struct QSummaryResult {
|
|
public:
|
|
DRange alpha;
|
|
|
|
OT_DEF(QSummaryResult) {
|
|
OT_MY_OBJECT(alpha);
|
|
}
|
|
|
|
public:
|
|
void Init(const Param& param) {
|
|
alpha.Init(-DBL_MAX, -DBL_MAX);
|
|
}
|
|
void Seed(const Param& param, const QNode& q_node) {}
|
|
void ApplySummaryResult(const Param& param, const QSummaryResult& summary_result) {
|
|
alpha.MaxWith(summary_result.alpha);
|
|
}
|
|
void ApplyDelta(const Param& param, const Delta& delta) {
|
|
alpha.MaxWith(delta.alpha);
|
|
}
|
|
void ApplyPostponed(const Param& param,
|
|
const QPostponed& postponed, const QNode& q_node) {}
|
|
|
|
void StartReaccumulate(const Param& param, const QNode& q_node) {
|
|
alpha.InitEmptySet();
|
|
}
|
|
void Accumulate(const Param& param, const QResult& result) {
|
|
alpha |= DRange(result.alpha.max2, result.alpha.max1);
|
|
}
|
|
void Accumulate(const Param& param,
|
|
const QSummaryResult& result, index_t n_points) {
|
|
alpha |= result.alpha;
|
|
}
|
|
void FinishReaccumulate(const Param& param, const QNode& q_node) {}
|
|
};
|
|
|
|
struct PairVisitor {
|
|
public:
|
|
Alpha old_alpha;
|
|
Alpha alpha;
|
|
|
|
public:
|
|
void Init(const Param& param) {}
|
|
|
|
bool StartVisitingQueryPoint(const Param& param,
|
|
const QPoint& q, index_t q_index,
|
|
const RNode& r_node,
|
|
const Delta& delta,
|
|
const QSummaryResult& unapplied_summary_results,
|
|
QResult* q_result, GlobalResult* global_result) {
|
|
double alpha_hi;
|
|
|
|
alpha = q_result->alpha;
|
|
old_alpha = q.info().alpha;
|
|
|
|
if (unlikely(q_index >= r_node.begin() && q_index < r_node.end())) {
|
|
alpha_hi = r_node.stat().rho.hi + old_alpha.max1;
|
|
} else {
|
|
double sim = AffinityCommon::Helpers::Similarity(
|
|
r_node.bound().MinDistanceSq(q.vec()));
|
|
alpha_hi = min(
|
|
min(sim, old_alpha.max1) + r_node.stat().rho.hi,
|
|
sim);
|
|
}
|
|
|
|
return (alpha_hi > alpha.max2);
|
|
}
|
|
void VisitPair(const Param& param,
|
|
const QPoint& q, index_t q_index, const RPoint& r, index_t r_index) {
|
|
double candidate_alpha;
|
|
double sim = AffinityCommon::Helpers::Similarity(
|
|
la::DistanceSqEuclidean(q.vec(), r.vec()));
|
|
|
|
if (likely(q_index != r_index)) {
|
|
candidate_alpha = min(
|
|
min(old_alpha.get(r_index), sim) + r.info().rho,
|
|
sim);
|
|
} else {
|
|
candidate_alpha = r.info().rho + old_alpha.get(r_index);
|
|
}
|
|
if (unlikely(candidate_alpha > alpha.max2)) {
|
|
if (unlikely(candidate_alpha > alpha.max1)) {
|
|
alpha.max2 = alpha.max1;
|
|
alpha.max1 = candidate_alpha;
|
|
alpha.max1_index = r_index;
|
|
alpha.max1_sim = sim;
|
|
} else {
|
|
alpha.max2 = candidate_alpha;
|
|
}
|
|
}
|
|
}
|
|
void FinishVisitingQueryPoint(const Param& param,
|
|
const QPoint& q, index_t q_index,
|
|
const RNode& r_node, const QSummaryResult& unapplied_summary_results,
|
|
QResult* q_result, GlobalResult* global_result) {
|
|
q_result->alpha = alpha;
|
|
}
|
|
};
|
|
|
|
class Algorithm {
|
|
public:
|
|
static bool ConsiderPairIntrinsic(const Param& param,
|
|
const QNode& q_node, const RNode& r_node,
|
|
const Delta& parent_delta, Delta* delta,
|
|
GlobalResult* global_result, QPostponed* q_postponed) {
|
|
double sim_lo = AffinityCommon::Helpers::SimilarityLo(
|
|
param, q_node, r_node);
|
|
delta->alpha.lo = min(min(q_node.stat().alpha.lo, sim_lo)
|
|
+ r_node.stat().rho.lo, sim_lo);
|
|
|
|
if (q_node.begin() < r_node.end() && r_node.begin() < q_node.end()) {
|
|
delta->alpha.hi =
|
|
q_node.stat().alpha.hi + r_node.stat().rho.hi;
|
|
} else {
|
|
double sim_hi = AffinityCommon::Helpers::Similarity(
|
|
q_node.bound().MinDistanceSq(r_node.bound()));
|
|
delta->alpha.hi = min(min(q_node.stat().alpha.hi, sim_hi)
|
|
+ r_node.stat().rho.hi, sim_hi);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
static bool ConsiderPairExtrinsic(const Param& param,
|
|
const QNode& q_node, const RNode& r_node, const Delta& delta,
|
|
const QSummaryResult& q_summary_result, const GlobalResult& global_result,
|
|
QPostponed* q_postponed) {
|
|
return (delta.alpha.hi >= q_summary_result.alpha.lo);
|
|
}
|
|
static bool ConsiderQueryTermination(const Param& param,
|
|
const QNode& q_node,
|
|
const QSummaryResult& q_summary_result, const GlobalResult& global_result,
|
|
QPostponed* q_postponed) {
|
|
return true;
|
|
}
|
|
static double Heuristic(const Param& param,
|
|
const QNode& q_node, const RNode& r_node, const Delta& delta) {
|
|
//return -delta.alpha.hi;
|
|
return r_node.bound().MinToMidSq(q_node.bound());
|
|
}
|
|
};
|
|
};
|
|
|
|
class AffinityRho {
|
|
public:
|
|
typedef AffinityCommon::Alpha Alpha;
|
|
|
|
typedef AffinityCommon::Point QPoint;
|
|
typedef AffinityCommon::Point RPoint;
|
|
|
|
typedef AffinityCommon::Param Param;
|
|
|
|
typedef AffinityCommon::Node QNode;
|
|
typedef AffinityCommon::Node RNode;
|
|
|
|
typedef BlankGlobalResult GlobalResult;
|
|
|
|
typedef BlankQPostponed QPostponed;
|
|
|
|
struct Delta {
|
|
public:
|
|
DRange d_rho;
|
|
|
|
OT_DEF(Delta) {
|
|
OT_MY_OBJECT(d_rho);
|
|
}
|
|
|
|
public:
|
|
void Init(const Param& param) {
|
|
}
|
|
};
|
|
|
|
struct QResult {
|
|
public:
|
|
double rho;
|
|
|
|
OT_DEF(QResult) {
|
|
OT_MY_OBJECT(rho);
|
|
}
|
|
|
|
public:
|
|
void Init(const Param& param) {
|
|
Reset();
|
|
}
|
|
void Seed(const Param& param, const QPoint& q_point) {}
|
|
void Reset() {
|
|
rho = 0;
|
|
}
|
|
void Postprocess(const Param& param,
|
|
const QPoint& q, index_t q_index, const RNode& r_root) {
|
|
// Subtract out the improperly computed rho and substitute a new one
|
|
double responsibility =
|
|
AffinityCommon::Helpers::Similarity(q.vec(), q.vec())
|
|
- q.info().alpha.get(q_index);
|
|
rho -= (responsibility + fabs(responsibility)) / 2;
|
|
double self_responsibility = param.pref - q.info().alpha.get(q_index);
|
|
rho += self_responsibility;
|
|
}
|
|
void ApplyPostponed(const Param& param,
|
|
const QPostponed& postponed, const QPoint& q, index_t q_index) {
|
|
}
|
|
};
|
|
|
|
typedef BlankQSummaryResult QSummaryResult;
|
|
|
|
struct PairVisitor {
|
|
public:
|
|
double prho;
|
|
double arho;
|
|
|
|
public:
|
|
void Init(const Param& param) {}
|
|
bool StartVisitingQueryPoint(const Param& param,
|
|
const QPoint& q, index_t q_index,
|
|
const RNode& r_node,
|
|
const Delta& delta,
|
|
const QSummaryResult& unapplied_summary_results,
|
|
QResult* q_result, GlobalResult* global_result) {
|
|
// do the point-node prune check
|
|
double sim_hi = AffinityCommon::Helpers::SimilarityHi(
|
|
param, q.vec(), q_index, r_node);
|
|
if (sim_hi < r_node.stat().alpha.lo) {
|
|
return false;
|
|
} else {
|
|
prho = arho = 0;
|
|
return true;
|
|
}
|
|
}
|
|
void VisitPair(const Param& param,
|
|
const QPoint& q, index_t q_index,
|
|
const RPoint& r, index_t r_index) {
|
|
double responsibility =
|
|
AffinityCommon::Helpers::Similarity(q.vec(), r.vec())
|
|
- r.info().alpha.get(q_index);
|
|
prho += responsibility;
|
|
arho += fabs(responsibility);
|
|
}
|
|
void FinishVisitingQueryPoint(const Param& param,
|
|
const QPoint& q, index_t q_index,
|
|
const RNode& r_node, const QSummaryResult& unapplied_summary_results,
|
|
QResult* q_result, GlobalResult* global_result) {
|
|
prho = (prho + arho) / 2;
|
|
q_result->rho += prho;
|
|
}
|
|
};
|
|
|
|
class Algorithm {
|
|
public:
|
|
static bool ConsiderPairIntrinsic(const Param& param,
|
|
const QNode& q_node, const RNode& r_node,
|
|
const Delta& parent_delta, Delta* delta,
|
|
GlobalResult* global_result, QPostponed* q_postponed) {
|
|
double sim_hi = AffinityCommon::Helpers::SimilarityHi(
|
|
param, q_node, r_node);
|
|
double sim_lo = AffinityCommon::Helpers::SimilarityLo(
|
|
param, q_node, r_node);
|
|
|
|
delta->d_rho.lo = math::ClampNonNegative(sim_lo - r_node.stat().alpha.hi)
|
|
* r_node.count();
|
|
delta->d_rho.hi = math::ClampNonNegative(sim_hi - r_node.stat().alpha.lo)
|
|
* r_node.count();
|
|
|
|
return delta->d_rho.hi != 0;
|
|
}
|
|
static bool ConsiderPairExtrinsic(const Param& param,
|
|
const QNode& q_node, const RNode& r_node, const Delta& delta,
|
|
const QSummaryResult& q_summary_result, const GlobalResult& global_result,
|
|
QPostponed* q_postponed) {
|
|
return true;
|
|
}
|
|
static bool ConsiderQueryTermination(const Param& param,
|
|
const QNode& q_node,
|
|
const QSummaryResult& q_summary_result, const GlobalResult& global_result,
|
|
QPostponed* q_postponed) {
|
|
return true;
|
|
}
|
|
static double Heuristic(const Param& param,
|
|
const QNode& q_node, const RNode& r_node, const Delta& delta) {
|
|
// TODO: If approximating, favor upper bound
|
|
return 0;
|
|
}
|
|
};
|
|
};
|
|
|
|
struct Cluster {
|
|
Vector exemplar;
|
|
Vector centroid;
|
|
index_t count;
|
|
};
|
|
|
|
template<typename Visitor>
|
|
struct VisitorReductor {
|
|
void Reduce(const Visitor& right, Visitor *left) const {
|
|
left->Accumulate(right);
|
|
}
|
|
};
|
|
|
|
struct ApplyAlphas {
|
|
public:
|
|
const AffinityCommon::Param *param;
|
|
double sum_alpha1;
|
|
double sum_alpha2;
|
|
double netsim;
|
|
|
|
OT_DEF(ApplyAlphas) {
|
|
OT_MY_OBJECT(sum_alpha1);
|
|
OT_MY_OBJECT(sum_alpha2);
|
|
OT_MY_OBJECT(netsim);
|
|
}
|
|
|
|
OT_FIX(ApplyAlphas) {
|
|
// the pointer shall not be sent over the internet
|
|
param = NULL;
|
|
}
|
|
|
|
public:
|
|
void Init(const AffinityCommon::Param *param_in) {
|
|
param = param_in;
|
|
sum_alpha1 = 0;
|
|
sum_alpha2 = 0;
|
|
netsim = 0;
|
|
}
|
|
|
|
void Update(index_t index,
|
|
AffinityCommon::Point *point, AffinityAlpha::QResult* result) {
|
|
point->info().alpha = result->alpha;
|
|
sum_alpha1 += result->alpha.max1;
|
|
sum_alpha2 += result->alpha.max2;
|
|
if (point->info().rho > 0) {
|
|
netsim += param->pref;
|
|
} else {
|
|
netsim += result->alpha.max1_sim;
|
|
}
|
|
}
|
|
|
|
void Accumulate(const ApplyAlphas& other) {
|
|
sum_alpha1 += other.sum_alpha1;
|
|
sum_alpha2 += other.sum_alpha2;
|
|
netsim += other.netsim;
|
|
}
|
|
};
|
|
|
|
inline double damp(double lambda, double prev, double next) {
|
|
return (prev - next) * lambda + next;
|
|
}
|
|
|
|
struct ApplyRhos {
|
|
public:
|
|
const AffinityCommon::Param *param;
|
|
index_t n_changed;
|
|
index_t hash;
|
|
index_t n_exemplars;
|
|
double squared_changed;
|
|
double squared_difference;
|
|
double sum;
|
|
|
|
OT_DEF(ApplyRhos) {
|
|
OT_MY_OBJECT(n_changed);
|
|
OT_MY_OBJECT(hash);
|
|
OT_MY_OBJECT(n_exemplars);
|
|
OT_MY_OBJECT(squared_difference);
|
|
OT_MY_OBJECT(squared_changed);
|
|
OT_MY_OBJECT(sum);
|
|
}
|
|
|
|
OT_FIX(ApplyRhos) {
|
|
// the pointer shall not be sent over the internet
|
|
param = NULL;
|
|
}
|
|
|
|
public:
|
|
void Init(const AffinityCommon::Param *param_in) {
|
|
param = param_in;
|
|
hash = 0;
|
|
n_changed = 0;
|
|
n_exemplars = 0;
|
|
squared_difference = 0;
|
|
squared_changed = 0;
|
|
sum = 0;
|
|
}
|
|
|
|
void Update(index_t index,
|
|
AffinityCommon::Point *point, AffinityRho::QResult* result) {
|
|
double old_rho = point->info().rho;
|
|
double new_rho = result->rho;
|
|
bool was_exemplar = (old_rho > 0);
|
|
bool wants_exemplar = (result->rho > 0);
|
|
|
|
if (was_exemplar != wants_exemplar) {
|
|
squared_changed += math::Sqr(new_rho - old_rho);
|
|
// Random damping so far has gotten to most consistent convergence.
|
|
new_rho = damp(math::Random(0.0, 1.0), old_rho, new_rho);
|
|
n_changed++;
|
|
hash ^= index;
|
|
}
|
|
|
|
new_rho = damp(param->lambda, old_rho, new_rho);
|
|
|
|
//double idampfact =
|
|
// pow(fabs(old_rho) / (fabs(old_rho - new_rho) + fabs(old_rho)), param->dampness);
|
|
//new_rho = damp((1 - idampfact), old_rho, new_rho);
|
|
// First, damp according to the specified damping factor.
|
|
//new_rho = damp(param->lambda, old_rho, new_rho);
|
|
// Next, damp according to a variable damping factor that penalizes
|
|
// large changes.
|
|
//double dampfact = atan2(param->dampness * fabs(new_rho - old_rho),
|
|
// fabs(old_rho)) * (2.0 / math::PI);
|
|
|
|
// NOTE: The bottom two give better netsims on some problems but they're
|
|
// quite bad for convergence.
|
|
#ifdef EXPONENTIAL_DAMPING
|
|
double rel_diff = fabs(new_rho - old_rho) / (fabs(old_rho) + 1e-99);
|
|
double dampfact = 1 - exp(-rel_diff * 1);
|
|
// use lambda about 0.95 with exponential damping
|
|
dampfact *= param->lambda;
|
|
dampfact -= math::Random(0.0, 1.0) * param->random;
|
|
new_rho = damp(dampfact, old_rho, new_rho);
|
|
#endif
|
|
|
|
#ifdef SQUARED_DAMPING
|
|
double v = fabs(old_rho) / (fabs(old_rho) + fabs(new_rho - old_rho));
|
|
//double rel_diff = fabs(new_rho - old_rho) / (fabs(old_rho) + 1e-99);
|
|
double dampfact = 1.0 - math::Sqr(v);
|
|
// use lambda about 0.95 with exponential damping
|
|
dampfact *= param->lambda;
|
|
dampfact -= math::Random(0.0, 1.0) * param->random;
|
|
new_rho = damp(dampfact, old_rho, new_rho);
|
|
#endif
|
|
|
|
//fprintf(stderr, "%e %e %e\n", factor, sign, old_rho);
|
|
|
|
bool now_exemplar = (new_rho > 0);
|
|
|
|
squared_difference += math::Sqr(new_rho - old_rho);
|
|
sum += new_rho;
|
|
|
|
if (now_exemplar) {
|
|
n_exemplars++;
|
|
}
|
|
|
|
point->info().rho = new_rho;
|
|
}
|
|
|
|
void Accumulate(const ApplyRhos& other) {
|
|
n_changed += other.n_changed;
|
|
n_exemplars += other.n_exemplars;
|
|
squared_difference += other.squared_difference;
|
|
squared_changed += other.squared_changed;
|
|
sum += other.sum;
|
|
hash ^= other.hash;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Statistics gatherer for per-iteration times for affinity propagation.
|
|
*/
|
|
class AffinityTimer {
|
|
private:
|
|
double last_alpha_micros_;
|
|
double last_rho_micros_;
|
|
double sum_times_;
|
|
ArrayList<double> iteration_times_;
|
|
|
|
static const int M = 1000000;
|
|
|
|
public:
|
|
void Init();
|
|
void RecordTimes(timer *alpha_timer, timer *rho_timer);
|
|
void Report(datanode *module){}
|
|
};
|
|
|
|
void AffinityTimer::Init() {
|
|
iteration_times_.Init();
|
|
last_alpha_micros_ = 0;
|
|
last_rho_micros_ = 0;
|
|
sum_times_ = 0;
|
|
}
|
|
|
|
void AffinityTimer::RecordTimes(timer *alpha_timer, timer *rho_timer) {
|
|
double alpha_micros = alpha_timer->total.micros;
|
|
double rho_micros = rho_timer->total.micros;
|
|
double elapsed_alpha = alpha_micros - last_alpha_micros_;
|
|
double elapsed_rho = rho_micros - last_rho_micros_;
|
|
double elapsed = elapsed_alpha + elapsed_rho;
|
|
*iteration_times_.AddBack() = elapsed;
|
|
sum_times_ += elapsed;
|
|
last_alpha_micros_ = alpha_micros;
|
|
last_rho_micros_ = rho_micros;
|
|
fprintf(stderr, " -- time: %.3f = %.3f a + %.3f r; mean is %.3f\n",
|
|
elapsed/M, elapsed_alpha/M, elapsed_rho/M,
|
|
sum_times_/iteration_times_.size()/M);
|
|
}
|
|
|
|
/**
|
|
* Affinity propagation.
|
|
*/
|
|
void AffinityMain(datanode *module, const char *gnp_name) {
|
|
AffinityCommon::Param *param;
|
|
AffinityTimer timestats;
|
|
const int TREE_CHANNEL = 300;
|
|
const int ALPHA_CHANNEL = 350;
|
|
const int RHO_CHANNEL = 360;
|
|
const int REDUCE_CHANNEL = 370;
|
|
const int DONE_CHANNEL = 390;
|
|
int conv_it = fx_param_int(module, "affinity/conv_it", 50);
|
|
int conv_thresh = fx_param_int(module, "affinity/conv_thresh", 5);
|
|
int stable_iterations = 0;
|
|
int maxit = fx_param_int(module, "affinity/maxit", 1000);
|
|
index_t n_points;
|
|
|
|
if (!rpc::is_root()) {
|
|
// turn off fastexec output
|
|
fx_silence();
|
|
}
|
|
|
|
timestats.Init();
|
|
|
|
param = new AffinityCommon::Param();
|
|
param->Init(fx_submodule(module, gnp_name, gnp_name));
|
|
|
|
ThorTree<AffinityCommon::Param,
|
|
AffinityCommon::Point, AffinityCommon::Node> tree;
|
|
DistributedCache alphas;
|
|
DistributedCache rhos;
|
|
|
|
// One thing to note: alpha and rho are never taking up
|
|
// RAM at the same time!
|
|
double alpha_mb = fx_param_double(module, "alpha/megs", 200);
|
|
double rho_mb = fx_param_double(module, "rho/megs", 100);
|
|
timer *timer_alpha = fx_timer(module, "all_alpha");
|
|
timer *timer_rho = fx_timer(module, "all_rho");
|
|
|
|
fx_timer_start(module, "read");
|
|
DistributedCache *points_cache = new DistributedCache();
|
|
n_points = thor::ReadPoints<AffinityCommon::Point>(
|
|
*param, TREE_CHANNEL + 0, TREE_CHANNEL + 1,
|
|
fx_submodule(module, "data", "data"), points_cache);
|
|
fx_timer_stop(module, "read");
|
|
|
|
AffinityCommon::Point example_point;
|
|
CacheArray<AffinityCommon::Point>::GetDefaultElement(points_cache,
|
|
&example_point);
|
|
param->dim = example_point.vec().length();
|
|
|
|
fx_timer_start(module, "tree");
|
|
thor::CreateKdTree<AffinityCommon::Point, AffinityCommon::Node>(
|
|
*param, TREE_CHANNEL + 2, TREE_CHANNEL + 3,
|
|
fx_submodule(module, "tree", "tree"), n_points, points_cache, &tree);
|
|
fx_timer_stop(module, "tree");
|
|
|
|
AffinityAlpha::QResult alpha_default;
|
|
alpha_default.Init(*param);
|
|
tree.CreateResultCache(ALPHA_CHANNEL, alpha_default,
|
|
alpha_mb, &alphas);
|
|
AffinityRho::QResult rho_default;
|
|
rho_default.Init(*param);
|
|
tree.CreateResultCache(RHO_CHANNEL, rho_default,
|
|
rho_mb, &rhos);
|
|
|
|
for (int iter = 0;;) {
|
|
iter++;
|
|
|
|
fx_timer_start(module, "all_alpha");
|
|
thor::RpcDualTree<AffinityAlpha, DualTreeDepthFirst<AffinityAlpha> >(
|
|
fx_submodule(module, "gnp", "iter/%d/alpha", iter), 200,
|
|
*param, &tree, &tree, &alphas, NULL);
|
|
ApplyAlphas apply_alphas;
|
|
apply_alphas.Init(param);
|
|
tree.Update<AffinityAlpha::QResult>(&alphas, &apply_alphas);
|
|
rpc::Reduce(REDUCE_CHANNEL+0, VisitorReductor<ApplyAlphas>(), &apply_alphas);
|
|
if (rpc::is_root()) {
|
|
fprintf(stderr, ANSI_RED"--- %3d: alpha: max1=%.2e, max2=%.2e, netsim=%f"ANSI_CLEAR"\n",
|
|
iter, apply_alphas.sum_alpha1 / n_points,
|
|
apply_alphas.sum_alpha2 / n_points,
|
|
apply_alphas.netsim / n_points);
|
|
}
|
|
alphas.ResetElements();
|
|
fx_timer_stop(module, "all_alpha");
|
|
|
|
fx_timer_start(module, "all_rho");
|
|
thor::RpcDualTree<AffinityRho, DualTreeDepthFirst<AffinityRho> >(
|
|
fx_submodule(module, "gnp", "iter/%d/rho", iter), 200,
|
|
*param, &tree, &tree, &rhos, NULL);
|
|
ApplyRhos apply_rhos;
|
|
apply_rhos.Init(param);
|
|
tree.Update<AffinityRho::QResult>(&rhos, &apply_rhos);
|
|
rpc::Reduce(REDUCE_CHANNEL+1, VisitorReductor<ApplyRhos>(), &apply_rhos);
|
|
if (rpc::is_root()) {
|
|
fprintf(stderr, ANSI_GREEN"--- %3d: rho: %"LI"d exemplars (%"LI"d changed, rms diff=%.1e, for c=%.1e, hash=%"LI"d)"ANSI_CLEAR"\n",
|
|
iter, apply_rhos.n_exemplars, apply_rhos.n_changed,
|
|
sqrt(apply_rhos.squared_difference / n_points),
|
|
sqrt(apply_rhos.squared_changed / apply_rhos.n_changed),
|
|
apply_rhos.hash);
|
|
}
|
|
rhos.ResetElements();
|
|
fx_timer_stop(module, "all_rho");
|
|
|
|
if (rpc::is_root()) {
|
|
timestats.RecordTimes(timer_alpha, timer_rho);
|
|
}
|
|
|
|
Broadcaster<bool> done;
|
|
|
|
if (rpc::is_root()) {
|
|
// TODO: Better termination condition
|
|
if (apply_rhos.n_changed < conv_thresh) {
|
|
stable_iterations++;
|
|
} else {
|
|
stable_iterations = 0;
|
|
}
|
|
done.SetData(iter >= maxit || stable_iterations >= conv_it);
|
|
}
|
|
|
|
done.Doit(DONE_CHANNEL);
|
|
|
|
if (done.get()) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
timestats.Report(module);
|
|
|
|
if (fx_param_bool(module, "brute_netsim", false)) {
|
|
// Calculating netsim using brute force
|
|
fprintf(stderr, "calculating netsim\n");
|
|
CacheArray<AffinityCommon::Point> points_array;
|
|
double netsim = 0;
|
|
points_array.Init(&tree.points(), BlockDevice::M_READ);
|
|
CacheReadIter<AffinityCommon::Point> r(&points_array, 0);
|
|
ArrayList<Vector> list;
|
|
list.Init();
|
|
for (index_t j = 0; j < n_points; j++) {
|
|
if (r->info().rho > 0) {
|
|
list.AddBack()->Copy(r->vec());
|
|
netsim += param->pref;
|
|
}
|
|
r.Next();
|
|
}
|
|
CacheReadIter<AffinityCommon::Point> q(&points_array, 0);
|
|
for (index_t i = 0; i < n_points; i++) {
|
|
double min_dist_sq = DBL_MAX;
|
|
for (index_t j = 0; j < list.size(); j++) {
|
|
double dist_sq = la::DistanceSqEuclidean(q->vec(), list[j]);
|
|
if (dist_sq < min_dist_sq) {
|
|
min_dist_sq = dist_sq;
|
|
}
|
|
}
|
|
netsim -= min_dist_sq;
|
|
|
|
q.Next();
|
|
}
|
|
fprintf(stderr, "netsim = %f\n", netsim / n_points);
|
|
fx_format_result(module, "netsim", "%.5e", netsim / n_points);
|
|
}
|
|
|
|
delete param;
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
fx_init(argc, argv);
|
|
rpc::Init();
|
|
|
|
srand(time(NULL));
|
|
|
|
AffinityMain(fx_root, "affinity");
|
|
|
|
rpc::Done();
|
|
fx_done();
|
|
}
|
|
|
|
|
|
#if 0
|
|
// The old source code!
|
|
|
|
//
|
|
// AffinityAlpha::Param param;
|
|
//
|
|
// param.Init(fx_submodule(module, gnp_name, gnp_name));
|
|
//
|
|
// TempCacheArray<AffinityAlpha::QPoint> data_points;
|
|
// TempCacheArray<AffinityAlpha::QNode> data_nodes;
|
|
//
|
|
// index_t n_block_points = fx_param_int(
|
|
// module, "n_block_points", 1024);
|
|
// index_t n_block_nodes = fx_param_int(
|
|
// module, "n_block_nodes", 128);
|
|
//
|
|
// datanode *data_module = fx_submodule(module, "data", "data");
|
|
//
|
|
// fx_timer_start(data_module, "read");
|
|
//
|
|
// Matrix data_matrix;
|
|
// MUST_PASS(data::Load(fx_param_str_req(data_module, ""), &data_matrix));
|
|
// index_t n_points = data_matrix.n_cols();
|
|
// index_t dimensionality = data_matrix.n_rows();
|
|
// AffinityAlpha::QPoint default_point;
|
|
// default_point.vec().Init(data_matrix.n_rows());
|
|
// param.BootstrapMonochromatic(&default_point, data_matrix.n_cols());
|
|
// data_points.Init(default_point, n_points, n_block_points);
|
|
// for (index_t i = 0; i < data_matrix.n_cols(); i++) {
|
|
// AffinityAlpha::QPoint *point = &data_points[i];
|
|
// point->vec().CopyValues(data_matrix.GetColumnPtr(i));
|
|
// }
|
|
// FindCovariance(data_matrix);
|
|
// data_matrix.Destruct();
|
|
// data_matrix.Init(0, 0);
|
|
//
|
|
// fx_timer_stop(data_module, "read");
|
|
//
|
|
// AffinityAlpha::QNode data_example_node;
|
|
// data_example_node.Init(dimensionality, param);
|
|
//
|
|
// // Initial conditions for alpha
|
|
// for (index_t i = 0; i < n_points; i++) {
|
|
// CacheWrite<AffinityAlpha::QPoint> point(&data_points, i);
|
|
// point->info().alpha.max1 = 0;
|
|
// point->info().alpha.max2 = param.pref;
|
|
// point->info().alpha.max1_index = i;
|
|
// if (rand() % 8 == 0) {
|
|
// point->info().rho = -param.pref / 2;
|
|
// } else {
|
|
// point->info().rho = 0;
|
|
// }
|
|
// }
|
|
// data_nodes.Init(data_example_node, 0, n_block_nodes);
|
|
// KdTreeHybridBuilder
|
|
// <AffinityAlpha::QPoint, AffinityAlpha::QNode, AffinityAlpha::Param>
|
|
// ::Build(data_module, param, &data_points, &data_nodes);
|
|
//
|
|
//
|
|
// // ---------------------------------------------------------------
|
|
// // The algorithm follows -----------------------------------------
|
|
//
|
|
// // All the above is not any different for affinity than for anything
|
|
// // else. Now, time for the iteration.
|
|
// int n_iter = fx_param_int(module, "n_iter", 10000);
|
|
// int iter;
|
|
// int n_convergence_iter = fx_param_int(module, "n_convergence_iter", 16);
|
|
// int convergence_thresh = fx_param_int(module, "convergence_thresh", 8);
|
|
// int stable_iter = 0;
|
|
// index_t n_changed = n_points / 2;
|
|
// index_t n_exemplars = 0;
|
|
// timer *timer_rho = fx_timer(module, "all_rho");
|
|
// timer *timer_alpha = fx_timer(module, "all_alpha");
|
|
//
|
|
// ArrayList<char> is_exemplar;
|
|
// is_exemplar.Init(n_points);
|
|
//
|
|
// ArrayList<double> iter_times_rho;
|
|
// ArrayList<double> iter_times_alpha;
|
|
// ArrayList<double> iter_times_total;
|
|
// iter_times_rho.Init();
|
|
// iter_times_alpha.Init();
|
|
// iter_times_total.Init();
|
|
//
|
|
// for (index_t i = 0; i < n_points; i++) {
|
|
// is_exemplar[i] = 0;
|
|
// }
|
|
//
|
|
// for (iter = 0; iter < n_iter; iter++)
|
|
// {
|
|
// double lambda = param.lambda;
|
|
// index_t n_alpha_changed = 0;
|
|
// double sum_alpha = 0;
|
|
// double sum_alpha2 = 0;
|
|
// double sum_rho = 0;
|
|
// index_t unclassifieds;
|
|
// double last_rho_time = timer_rho->total.cpu.tms_utime;
|
|
// double last_alpha_time = timer_alpha->total.cpu.tms_utime;
|
|
//
|
|
// n_exemplars = 0;
|
|
// unclassifieds = 0;
|
|
//
|
|
// fx_timer_start(module, "all_alpha");
|
|
// {
|
|
// TempCacheArray<AffinityAlpha::QResult> q_results_alpha;
|
|
//
|
|
// AffinityAlpha::QResult default_result_alpha;
|
|
// default_result_alpha.Init(param);
|
|
// q_results_alpha.Init(default_result_alpha, data_points.end_index(),
|
|
// data_points.n_block_elems());
|
|
//
|
|
// thor::ThreadedDualTreeSolver
|
|
// < AffinityAlpha, DualTreeDepthFirst<AffinityAlpha> >::Solve(
|
|
// fx_submodule(module, "threads", "iters/%d/alpha", iter), param,
|
|
// &data_points, &data_nodes, &data_points, &data_nodes,
|
|
// &q_results_alpha);
|
|
//
|
|
// for (index_t i = 0; i < n_points; i++) {
|
|
// AffinityAlpha::QPoint *point = &data_points[i];
|
|
// AffinityAlpha::QResult *result = &q_results_alpha[i];
|
|
//
|
|
// if (point->info().alpha.max1_index != result->alpha.max1_index) {
|
|
// n_alpha_changed++;
|
|
// }
|
|
//
|
|
// point->info().alpha = result->alpha;
|
|
///
|
|
// sum_alpha += result->alpha.max1;
|
|
// sum_alpha2 += result->alpha.max2;
|
|
//
|
|
// if (!is_exemplar[result->alpha.max1_index] && point->info().rho <= 0) {
|
|
// unclassifieds++;
|
|
// }
|
|
// }
|
|
//
|
|
// thor::StatFixer<
|
|
// AffinityAlpha::Param, AffinityAlpha::QPoint, AffinityAlpha::QNode>
|
|
// ::Fix(param, &data_points, &data_nodes);
|
|
// }
|
|
// fx_timer_stop(module, "all_alpha");
|
|
//
|
|
// fprintf(stderr,
|
|
// "\033[31m -------- %04d alpha: sum_alpha = %f, sum_alpha2 = %f, %"LI"d alphas changed, %"LI"d unclassifieds, %.3fsec cum. alpha\033[0m\n",
|
|
// iter, sum_alpha, sum_alpha2, n_alpha_changed, unclassifieds,
|
|
// timer_alpha->total.micros / 1.0e6);
|
|
//
|
|
// fx_timer_start(module, "all_rho");
|
|
// {
|
|
// double rho_squared_difference = 0;
|
|
// TempCacheArray<AffinityRho::QResult> q_results_rho;
|
|
//
|
|
// AffinityRho::QResult default_result_rho;
|
|
// default_result_rho.Init(param);
|
|
// q_results_rho.Init(default_result_rho, data_points.end_index(),
|
|
// data_points.n_block_elems());
|
|
//
|
|
// thor::ThreadedDualTreeSolver< AffinityRho, DualTreeDepthFirst<AffinityRho> >::Solve(
|
|
// fx_submodule(module, "threads", "iters/%d/rho", iter), param,
|
|
// &data_points, &data_nodes, &data_points, &data_nodes,
|
|
// &q_results_rho);
|
|
//
|
|
// n_changed = 0;
|
|
//
|
|
// for (index_t i = 0; i < n_points; i++) {
|
|
// AffinityAlpha::QPoint *point = &data_points[i];
|
|
// AffinityRho::QResult *result = &q_results_rho[i];
|
|
// double old_rho = point->info().rho;
|
|
// double new_rho = damp(lambda, old_rho, result->rho);
|
|
//
|
|
// if ((old_rho > 0) != (new_rho > 0)) {
|
|
// new_rho *= math::Random(0.4, 1.4);
|
|
// n_changed++;
|
|
// }
|
|
//
|
|
// rho_squared_difference += math::Sqr(new_rho - old_rho);
|
|
// sum_rho += new_rho;
|
|
//
|
|
// if (new_rho > 0) {
|
|
// is_exemplar[i] = 1;
|
|
// n_exemplars++;
|
|
// } else {
|
|
// is_exemplar[i] = 0;
|
|
// }
|
|
//
|
|
// point->info().rho = new_rho;
|
|
// }
|
|
//
|
|
//
|
|
// thor::StatFixer<
|
|
// AffinityAlpha::Param, AffinityAlpha::QPoint, AffinityAlpha::QNode>
|
|
// ::Fix(param, &data_points, &data_nodes);
|
|
//
|
|
// param.SetEpsilon(sqrt(rho_squared_difference / n_points));
|
|
// //param.SetEpsilon(0);
|
|
// }
|
|
// fx_timer_stop(module, "all_rho");
|
|
//
|
|
// fprintf(stderr, "\033[32m------------- %04d rho: %"LI"d rhos changed, (%"LI"d exemplars), sum_rho = %f, eps = %f, %.3fsec cum. rho\033[0m\n",
|
|
// iter, n_changed, n_exemplars,
|
|
// sum_rho, param.eps,
|
|
// timer_rho->total.micros / 1.0e6);
|
|
//
|
|
// *iter_times_rho.AddBack() = timer_rho->total.cpu.tms_utime - last_rho_time;
|
|
// *iter_times_alpha.AddBack() = timer_alpha->total.cpu.tms_utime - last_alpha_time;
|
|
// *iter_times_total.AddBack() = (timer_rho->total.cpu.tms_utime - last_rho_time)
|
|
// + (timer_alpha->total.cpu.tms_utime - last_alpha_time);
|
|
//
|
|
// if (n_changed < convergence_thresh) {
|
|
// stable_iter++;
|
|
// } else {
|
|
// stable_iter = 0;
|
|
// }
|
|
//
|
|
// if (stable_iter >= n_convergence_iter && iter > 20) {
|
|
// break;
|
|
// }
|
|
// }
|
|
//
|
|
// fx_format_result(module, "n_iterations", "%d", iter);
|
|
// fx_format_result(module, "n_exemplars", "%d", n_exemplars);
|
|
//
|
|
// TimeStats(fx_submodule(module, "iter_times_rho", "iter_times_rho"),
|
|
// iter_times_rho);
|
|
// TimeStats(fx_submodule(module, "iter_times_alpha", "iter_times_alpha"),
|
|
// iter_times_alpha);
|
|
// TimeStats(fx_submodule(module, "iter_times_total", "iter_times_total"),
|
|
// iter_times_total);
|
|
// fx_format_result(module, "n_points", "%"LI"d", n_points);
|
|
//
|
|
// // This will take too long if there are too many exemplars.
|
|
// if (n_exemplars >= 10000) {
|
|
// NONFATAL("Too many exemplars (%"LI"d >= 10000), NOT performing clustering.\n",
|
|
// n_exemplars);
|
|
// } else {
|
|
// FindExemplars(module, param, dimensionality, n_points, &data_points);
|
|
// }
|
|
//}
|
|
//
|
|
///*
|
|
//void FindExemplars(datanode *module, const AffinityCommon::Param& param,
|
|
// index_t dimensionality, index_t n_points,
|
|
// CacheArray<AffinityAlpha::QPoint> *data_points) {
|
|
// fx_timer_start(module, "exemplars");
|
|
//
|
|
// ArrayList<Cluster> clusters;
|
|
// ArrayList<index_t> assignments;
|
|
// CacheReadIter<AffinityAlpha::QPoint> point(data_points, 0);
|
|
//
|
|
// clusters.Init();
|
|
//
|
|
// for (index_t point_i = 0; point_i < n_points; point_i++, point.Next()) {
|
|
// if (point->info().rho > 0) {
|
|
// Cluster *cluster = clusters.AddBack();
|
|
// cluster->exemplar.Copy(point->vec());
|
|
// cluster->centroid.Init(dimensionality);
|
|
// cluster->centroid.SetZero();
|
|
// cluster->count = 0;
|
|
// }
|
|
// }
|
|
//
|
|
// // Run all nearest neighbors to assign clusters and find centroids
|
|
// assignments.Init(n_points);
|
|
//
|
|
// point.SetIndex(0);
|
|
// for (index_t point_i = 0; point_i < n_points; point_i++, point.Next()) {
|
|
// double best_distsq = DBL_MAX;
|
|
// index_t best_k = -1;
|
|
//
|
|
// for (index_t k = 0; k < clusters.size(); k++) {
|
|
// double distsq = la::DistanceSqEuclidean(
|
|
// clusters[k].exemplar, point->vec());
|
|
// if (unlikely(distsq < best_distsq)) {
|
|
// best_distsq = distsq;
|
|
// best_k = k;
|
|
// }
|
|
// }
|
|
//
|
|
// assignments[point_i] = best_k;
|
|
// clusters[best_k].count++;
|
|
// la::AddTo(point->vec(), &clusters[best_k].centroid);
|
|
// }
|
|
//
|
|
// // Divide centroids by size
|
|
// for (index_t i = 0; i < clusters.size(); i++) {
|
|
// la::Scale(1.0 / clusters[i].count, &clusters[i].centroid);
|
|
// }
|
|
//
|
|
// // Calculate net similarity
|
|
// double netsim = param.pref * clusters.size();
|
|
//
|
|
// point.SetIndex(0);
|
|
// for (index_t point_i = 0; point_i < n_points; point_i++, point.Next()) {
|
|
// Cluster *cluster = &clusters[assignments[point_i]];
|
|
// double distsq = la::DistanceSqEuclidean(cluster->exemplar, point->vec());
|
|
// netsim -= distsq;
|
|
// }
|
|
//
|
|
// fx_timer_start(module, "exemplars");
|
|
// fx_format_result(module, "netsim", "%f", netsim);
|
|
//
|
|
// // Make a matrix of exemplars so we can save it to file
|
|
// Matrix m;
|
|
// m.Init(dimensionality, clusters.size());
|
|
//
|
|
// for (index_t i = 0; i < clusters.size(); i++) {
|
|
// Vector dest;
|
|
// m.MakeColumnVector(i, &dest);
|
|
// dest.CopyValues(clusters[i].exemplar);
|
|
// }
|
|
//
|
|
// data::Save("exemplars.txt", m);
|
|
//}
|
|
//
|
|
//void FindCovariance(const Matrix& dataset) {
|
|
// Matrix m;
|
|
// Vector sums;
|
|
//
|
|
// m.Init(dataset.n_rows(), dataset.n_cols());
|
|
// sums.Init(dataset.n_rows());
|
|
// sums.SetZero();
|
|
//
|
|
// for (index_t i = 0; i < dataset.n_cols(); i++) {
|
|
// Vector s;
|
|
// Vector d;
|
|
// dataset.MakeColumnSubvector(i, 0, dataset.n_rows(), &s);
|
|
// m.MakeColumnVector(i, &d);
|
|
// d.CopyValues(s);
|
|
// la::AddTo(s, &sums);
|
|
// }
|
|
//
|
|
// la::Scale(-1.0 / dataset.n_cols(), &sums);
|
|
// for (index_t i = 0; i < dataset.n_cols(); i++) {
|
|
// Vector d;
|
|
// m.MakeColumnVector(i, &d);
|
|
// la::AddTo(sums, &d);
|
|
// }
|
|
//
|
|
// Matrix cov;
|
|
//
|
|
// la::MulTransBInit(m, m, &cov);
|
|
// la::Scale(1.0 / (dataset.n_cols() - 1), &cov);
|
|
//
|
|
// cov.PrintDebug("covariance");
|
|
//
|
|
// Vector d;
|
|
// Matrix u; // eigenvectors
|
|
// Matrix ui; // the inverse of eigenvectors
|
|
//
|
|
// la::EigenvectorsInit(cov, &d, &u);
|
|
// d.PrintDebug("covariance_eigenvectors");
|
|
// la::TransposeInit(u, &ui);
|
|
//
|
|
// for (index_t i = 0; i < d.length(); i++) {
|
|
// d[i] = 1.0 / sqrt(d[i]);
|
|
// }
|
|
//
|
|
// la::ScaleRows(d, &ui);
|
|
//
|
|
// Matrix cov_inv_half;
|
|
// la::MulInit(u, ui, &cov_inv_half);
|
|
//
|
|
// Matrix final;
|
|
// la::MulInit(cov_inv_half, m, &final);
|
|
//
|
|
// for (index_t i = 0; i < dataset.n_cols(); i++) {
|
|
// Vector s;
|
|
// Vector d;
|
|
// dataset.MakeColumnSubvector(i, 0, dataset.n_rows()-1, &d);
|
|
// final.MakeColumnVector(i, &s);
|
|
// d.CopyValues(s);
|
|
// }
|
|
//}
|
|
//
|
|
//inline double damp(double lambda, double prev, double next) {
|
|
// return lambda * prev + (1 - lambda) * next;
|
|
//}
|
|
//
|
|
//void TimeStats(datanode *module, const ArrayList<double>& list) {
|
|
// double v_avg;
|
|
// MinHeap<double, char> heap;
|
|
// double f = 1.0 / sysconf(_SC_CLK_TCK);
|
|
//
|
|
// heap.Init();
|
|
// v_avg = 0;
|
|
// for (index_t i = 0; i < list.size(); i++) {
|
|
// v_avg += list[i];
|
|
// heap.Put(list[i], 0);
|
|
// }
|
|
// v_avg /= list.size();
|
|
//
|
|
// double v_min;
|
|
// double v_med;
|
|
// double v_max;
|
|
//
|
|
// v_min = heap.top_key();
|
|
// while (heap.size() > list.size() / 2) {
|
|
// heap.PopOnly();
|
|
// }
|
|
// v_med = heap.top_key();
|
|
// while (heap.size() > 1) {
|
|
// heap.PopOnly();
|
|
// }
|
|
// v_max = heap.top_key();
|
|
//
|
|
// fx_format_result(module, "min", "%f", v_min*f);
|
|
// fx_format_result(module, "med", "%f", v_med*f);
|
|
// fx_format_result(module, "max", "%f", v_max*f);
|
|
// fx_format_result(module, "avg", "%f", v_avg*f);
|
|
// fx_format_result(module, "sum", "%f", v_avg*list.size()*f);
|
|
//}
|
|
//*/
|
|
#endif
|