57double nanMin(
const Ref<const RowVectorXd>& row,
int& col)
60 double best = std::numeric_limits<double>::quiet_NaN();
61 for (Index j = 0; j < row.size(); ++j) {
62 if (!std::isnan(row[j]) && (col < 0 || row[j] < best)) {
64 col =
static_cast<int>(j);
82: m_distance(distanceFromString(distance.toStdString()))
83, m_start(startFromString(start.toStdString()))
84, m_emptyact(emptyactFromString(emptyact.toStdString()))
85, m_iReps(std::max(replicates, qint32(1)))
88, m_rng(std::random_device{}())
107: m_distance(distance)
109, m_emptyact(emptyact)
110, m_iReps(std::max(replicates, qint32(1)))
113, m_rng(std::random_device{}())
128 if (name ==
"cityblock")
130 if (name ==
"cosine")
132 if (name ==
"correlation")
134 if (name ==
"hamming")
139KMeansStart KMeans::startFromString(
const std::string& name)
141 if (name ==
"uniform")
143 if (name ==
"cluster")
152 if (name ==
"singleton")
166 if (kClusters < 1 || X_in.rows() < kClusters)
169 const MatrixXd
X = normalizedRows(X_in);
175 RowVectorXd Xmins, Xmaxs;
178 qWarning(
"KMeans: Uniform initialization is not supported for Hamming distance.");
181 Xmins =
X.colwise().minCoeff();
182 Xmaxs =
X.colwise().maxCoeff();
185 double totsumDBest = std::numeric_limits<double>::max();
193 std::uniform_int_distribution<qint32> sampleDist(0, n - 1);
195 for (qint32 rep = 0; rep < m_iReps; ++rep) {
197 C = MatrixXd::Zero(k, p);
199 for (qint32 i = 0; i < k; ++i) {
200 for (qint32 j = 0; j < p; ++j) {
201 std::uniform_real_distribution<double> dist(Xmins[j], Xmaxs[j]);
202 C(i, j) = dist(m_rng);
206 C.array() -= (C.array().rowwise().sum() / p).replicate(1, p).array();
208 for (qint32 i = 0; i < k; ++i)
209 C.row(i) =
X.row(sampleDist(m_rng));
212 std::vector<qint32> order(n);
213 std::iota(order.begin(), order.end(), 0);
214 std::shuffle(order.begin(), order.end(), m_rng);
215 const qint32 nSub = std::max(k,
static_cast<qint32
>(std::floor(0.1 * n)));
216 MatrixXd Xsub(nSub, p);
217 for (qint32 i = 0; i < nSub; ++i)
218 Xsub.row(i) =
X.row(order[i]);
223 if (!preliminary.calculate(Xsub, k, idxSub, C, sumDSub, DSub)) {
225 if (emptyErrCnt == m_iReps)
230 for (qint32 i = 0; i < k; ++i)
231 if (!C.row(i).allFinite())
232 C.row(i) =
X.row(sampleDist(m_rng));
235 if (!runReplicate(
X, C, idx, sumD, D, rep)) {
237 if (emptyErrCnt == m_iReps)
243 if (totsumD < totsumDBest) {
244 totsumDBest = totsumD;
263 const MatrixXd& start,
269 if (start.rows() < 1 || start.cols() != X_in.cols() || X_in.rows() < start.rows())
272 const MatrixXd
X = normalizedRows(X_in);
273 k =
static_cast<qint32
>(start.rows());
277 C = normalizedRows(start);
278 return runReplicate(
X, C, idx, sumD, D, 0);
283MatrixXd KMeans::normalizedRows(
const MatrixXd&
X)
const
290 Xn.colwise() -= Xn.rowwise().mean();
291 const VectorXd norms = Xn.rowwise().norm();
292 for (Index i = 0; i < Xn.rows(); ++i) {
294 Xn.row(i) /= norms(i);
301bool KMeans::runReplicate(
const MatrixXd&
X, MatrixXd& C, VectorXi& idx, VectorXd& sumD, MatrixXd& D, qint32 rep)
304 Del = MatrixXd::Constant(n, k, std::numeric_limits<double>::quiet_NaN());
308 idx = VectorXi::Zero(n);
309 d = VectorXd::Zero(n);
311 for (qint32 i = 0; i < n; ++i)
312 d[i] = nanMin(D.row(i), idx[i]);
314 m = VectorXi::Zero(k);
315 for (qint32 i = 0; i < n; ++i)
319 bool converged =
false;
320 if (!batchUpdate(
X, C, idx, converged))
325 converged = onlineUpdate(
X, C, idx);
328 qWarning(
"KMeans: Failed to converge during replicate %d.", rep);
331 VectorXi nonempties = (m.array() > 0).cast<
int>();
332 qint32 count = nonempties.sum();
334 MatrixXd C_tmp(count, C.cols());
336 for (qint32 i = 0; i < k; ++i)
338 C_tmp.row(ci++) = C.row(i);
340 MatrixXd D_tmp = distfun(
X, C_tmp);
342 for (qint32 i = 0; i < k; ++i) {
344 D.col(i) = D_tmp.col(ci);
345 C.row(i) = C_tmp.row(ci);
348 D.col(i).setConstant(std::numeric_limits<double>::quiet_NaN());
349 C.row(i).setConstant(std::numeric_limits<double>::quiet_NaN());
354 d = VectorXd::Zero(n);
355 for (qint32 i = 0; i < n; ++i)
359 sumD = VectorXd::Zero(k);
360 for (qint32 i = 0; i < n; ++i)
361 sumD[idx[i]] += d[i];
363 totsumD = sumD.sum();
369bool KMeans::batchUpdate(
const MatrixXd&
X, MatrixXd& C, VectorXi& idx,
bool& converged)
375 for (qint32 i = 0; i < n; ++i)
379 for (qint32 i = 0; i < k; ++i)
382 previdx = VectorXi::Zero(n);
383 prevtotsumD = std::numeric_limits<double>::max();
385 MatrixXd D = MatrixXd::Zero(n, k);
395 gcentroids(
X, idx, changed, C_new, m_new);
396 MatrixXd D_new = distfun(
X, C_new);
398 for (qint32 i = 0; i < changed.rows(); ++i) {
399 C.row(changed[i]) = C_new.row(i);
400 D.col(changed[i]) = D_new.col(i);
401 m[changed[i]] = m_new[i];
405 std::vector<int> empties;
406 for (qint32 i = 0; i < changed.rows(); ++i)
407 if (m[changed[i]] == 0)
408 empties.push_back(changed[i]);
410 if (!empties.empty()) {
412 qWarning(
"KMeans: Empty cluster created at iteration %d.", iter);
415 std::vector<int> changedList(changed.data(), changed.data() + changed.size());
417 for (
int e : empties)
418 D.col(e).setConstant(std::numeric_limits<double>::quiet_NaN());
419 changedList.erase(std::remove_if(changedList.begin(), changedList.end(), [
this](
int c) { return m[c] == 0; }),
422 for (
int e : empties) {
425 for (qint32 i = 1; i < n; ++i)
426 if (D(i, idx[i]) > D(lonely, idx[lonely]))
428 qint32 from = idx[lonely];
430 for (from = 0; m[from] < 2; ++from) {
432 for (lonely = 0; idx[lonely] != from; ++lonely) {
435 C.row(e) =
X.row(lonely);
438 D.col(e) = distfun(
X, C.row(e));
442 gcentroids(
X, idx, VectorXi::Constant(1, from), C_from, m_from);
443 C.row(from) = C_from.row(0);
445 D.col(from) = distfun(
X, C.row(from));
446 if (std::find(changedList.begin(), changedList.end(), from) == changedList.end())
447 changedList.push_back(from);
449 std::sort(changedList.begin(), changedList.end());
451 changed = Map<VectorXi>(changedList.data(),
static_cast<Index
>(changedList.size()));
456 for (qint32 i = 0; i < n; ++i)
457 totsumD += D(i, idx[i]);
460 if (prevtotsumD <= totsumD) {
464 gcentroids(
X, idx, changed, C_rev, m_rev);
465 for (qint32 i = 0; i < changed.rows(); ++i) {
466 C.row(changed[i]) = C_rev.row(i);
467 m[changed[i]] = m_rev[i];
473 if (iter >= m_iMaxit)
478 prevtotsumD = totsumD;
481 for (qint32 i = 0; i < n; ++i)
482 d[i] = nanMin(D.row(i), nidx[i]);
485 std::vector<int> movedVec;
487 for (qint32 i = 0; i < n; ++i) {
488 if (nidx[i] != previdx[i])
489 movedVec.push_back(i);
493 std::vector<int> movedFinal;
494 movedFinal.reserve(movedVec.size());
495 for (
int mi : movedVec) {
496 if (D(mi, previdx[mi]) > d[mi])
497 movedFinal.push_back(mi);
500 if (movedFinal.empty()) {
505 for (
int mi : movedFinal)
509 std::vector<int> tmp;
510 tmp.reserve(2 * movedFinal.size());
511 for (
int mi : movedFinal) {
512 tmp.push_back(idx[mi]);
513 tmp.push_back(previdx[mi]);
515 std::sort(tmp.begin(), tmp.end());
516 tmp.erase(std::unique(tmp.begin(), tmp.end()), tmp.end());
518 changed.resize(tmp.size());
519 for (
size_t i = 0; i < tmp.size(); ++i)
527bool KMeans::onlineUpdate(
const MatrixXd&
X, MatrixXd& C, VectorXi& idx)
533 MatrixXd Xmid1, Xmid2;
534 if (medianCentroids) {
535 Xmid1 = MatrixXd::Zero(k, p);
536 Xmid2 = MatrixXd::Zero(k, p);
537 for (qint32 i = 0; i < k; ++i) {
539 MatrixXd Xsorted(m[i], p);
541 for (qint32 j = 0; j < n; ++j)
543 Xsorted.row(c++) =
X.row(j);
545 for (qint32 j = 0; j < p; ++j)
546 std::sort(Xsorted.col(j).data(), Xsorted.col(j).data() + Xsorted.rows());
548 qint32 nn =
static_cast<qint32
>(std::floor(0.5 * m[i])) - 1;
549 if ((m[i] % 2) == 0) {
550 Xmid1.row(i) = Xsorted.row(nn);
551 Xmid2.row(i) = Xsorted.row(nn + 1);
552 }
else if (m[i] > 1) {
553 Xmid1.row(i) = Xsorted.row(nn);
554 Xmid2.row(i) = Xsorted.row(nn + 2);
556 Xmid1.row(i) = Xsorted.row(0);
557 Xmid2.row(i) = Xsorted.row(0);
564 VectorXi changed(m.rows());
566 for (qint32 i = 0; i < m.rows(); ++i)
568 changed[count++] = i;
569 changed.conservativeResize(count);
571 qint32 lastmoved = 0;
574 bool converged =
false;
576 while (iter < m_iMaxit) {
579 for (qint32 j = 0; j < changed.rows(); ++j) {
580 qint32 i = changed[j];
581 VectorXi mbrs = VectorXi::Zero(n);
582 for (qint32 l = 0; l < n; ++l)
586 VectorXi sgn = 1 - 2 * mbrs.array();
588 for (qint32 l = 0; l < n; ++l)
592 Del.col(i) = (
static_cast<double>(m[i]) / (
static_cast<double>(m[i]) + sgn.cast<
double>().array()));
593 Del.col(i).array() *= (
X.rowwise() - C.row(i)).array().pow(2).rowwise().sum().array();
595 }
else if (medianCentroids) {
596 for (qint32 j = 0; j < changed.rows(); ++j) {
597 qint32 i = changed[j];
599 MatrixXd ldist = Xmid1.row(i).replicate(n, 1) -
X;
600 MatrixXd rdist =
X - Xmid2.row(i).replicate(n, 1);
601 VectorXd mbrs = VectorXd::Zero(n);
602 for (qint32 l = 0; l < n; ++l)
605 MatrixXd sgn = ((-2 * mbrs).array() + 1).replicate(1, p);
606 rdist = sgn.array() * rdist.array();
607 ldist = sgn.array() * ldist.array();
609 for (qint32 l = 0; l < n; ++l) {
611 for (qint32 h = 0; h < p; ++h)
612 sum += std::max(0.0, std::max(rdist(l, h), ldist(l, h)));
613 Del(l, i) = sum * medianScale;
616 Del.col(i) = (
X.rowwise() - C.row(i)).array().abs().rowwise().sum() * medianScale;
620 MatrixXd normC = C.array().pow(2).rowwise().sum().sqrt();
621 for (qint32 j = 0; j < changed.rows(); ++j) {
622 qint32 i = changed[j];
623 MatrixXd XCi =
X * C.row(i).transpose();
625 VectorXi mbrs = VectorXi::Zero(n);
626 for (qint32 l = 0; l < n; ++l)
630 VectorXi sgn = 1 - 2 * mbrs.array();
631 double A =
static_cast<double>(m[i]) * normC(i, 0);
634 Del.col(i) = 1 + sgn.cast<
double>().array() * (A - (B + 2 * sgn.cast<
double>().array() * m[i] * XCi.array() + 1).sqrt());
640 prevtotsumD = totsumD;
642 VectorXi nidx = VectorXi::Zero(n);
643 VectorXd minDel = VectorXd::Zero(n);
644 for (qint32 i = 0; i < n; ++i)
645 minDel[i] = nanMin(Del.row(i), nidx[i]);
648 std::vector<int> movedVec;
650 for (qint32 i = 0; i < n; ++i)
651 if (previdx[i] != nidx[i])
652 movedVec.push_back(i);
655 std::vector<int> movedFinal;
656 movedFinal.reserve(movedVec.size());
657 for (
int mi : movedVec)
658 if (Del(mi, previdx[mi]) > minDel(mi))
659 movedFinal.push_back(mi);
661 if (movedFinal.empty()) {
662 if ((iter == iter1) || nummoved > 0)
669 int bestMoved = movedFinal[0];
670 int bestDist = ((movedFinal[0] - lastmoved) % n + n) % n;
671 for (
size_t i = 1; i < movedFinal.size(); ++i) {
672 int d_i = ((movedFinal[i] - lastmoved) % n + n) % n;
673 if (d_i < bestDist) {
675 bestMoved = movedFinal[i];
678 int movedPt = bestMoved;
680 if (movedPt <= lastmoved) {
682 if (iter >= m_iMaxit)
689 qint32 oidx = idx[movedPt];
690 qint32 nidx_pt = nidx[movedPt];
691 totsumD += Del(movedPt, nidx_pt) - Del(movedPt, oidx);
693 idx[movedPt] = nidx_pt;
699 C.row(nidx_pt) += (
X.row(movedPt) - C.row(nidx_pt)) / m[nidx_pt];
700 C.row(oidx) -= (
X.row(movedPt) - C.row(oidx)) / m[oidx];
701 }
else if (medianCentroids) {
703 onidx << oidx, nidx_pt;
705 for (qint32 h = 0; h < 2; ++h) {
706 qint32 ci = onidx[h];
707 MatrixXd Xsorted(m[ci], p);
709 for (qint32 j = 0; j < n; ++j)
711 Xsorted.row(c++) =
X.row(j);
713 for (qint32 j = 0; j < p; ++j)
714 std::sort(Xsorted.col(j).data(), Xsorted.col(j).data() + Xsorted.rows());
716 qint32 nn =
static_cast<qint32
>(std::floor(0.5 * m[ci])) - 1;
717 if ((m[ci] % 2) == 0) {
718 C.row(ci) = 0.5 * (Xsorted.row(nn) + Xsorted.row(nn + 1));
719 Xmid1.row(ci) = Xsorted.row(nn);
720 Xmid2.row(ci) = Xsorted.row(nn + 1);
722 C.row(ci) = Xsorted.row(nn + 1);
724 Xmid1.row(ci) = Xsorted.row(nn);
725 Xmid2.row(ci) = Xsorted.row(nn + 2);
727 Xmid1.row(ci) = Xsorted.row(0);
728 Xmid2.row(ci) = Xsorted.row(0);
733 C.row(nidx_pt).array() += (
X.row(movedPt) - C.row(nidx_pt)).array() / m[nidx_pt];
734 C.row(oidx).array() += (
X.row(movedPt) - C.row(oidx)).array() / m[oidx];
737 VectorXi sorted_onidx(2);
738 sorted_onidx << oidx, nidx_pt;
739 std::sort(sorted_onidx.data(), sorted_onidx.data() + sorted_onidx.rows());
740 changed = sorted_onidx;
748MatrixXd KMeans::distfun(
const MatrixXd&
X,
const MatrixXd& C)
750 const qint32 nclusts = C.rows();
751 MatrixXd D = MatrixXd::Zero(n, nclusts);
753 switch (m_distance) {
755 for (qint32 i = 0; i < nclusts; ++i)
756 D.col(i) = (
X.rowwise() - C.row(i)).rowwise().squaredNorm();
760 for (qint32 i = 0; i < nclusts; ++i)
761 D.col(i) = (
X.rowwise() - C.row(i)).cwiseAbs().rowwise().sum();
766 VectorXd normC = C.rowwise().norm();
767 for (qint32 i = 0; i < nclusts; ++i) {
768 RowVectorXd C_normed = C.row(i) / normC(i);
769 D.col(i) = (1.0 - (
X * C_normed.transpose()).array()).cwiseMax(0.0);
775 for (qint32 i = 0; i < nclusts; ++i)
776 D.col(i) = (
X.rowwise() - C.row(i)).cwiseAbs().rowwise().sum() / p;
785void KMeans::gcentroids(
const MatrixXd&
X,
const VectorXi& index,
const VectorXi& clusts,
786 MatrixXd& centroids, VectorXi& counts)
788 const qint32 num = clusts.rows();
789 centroids = MatrixXd::Constant(num, p, std::numeric_limits<double>::quiet_NaN());
790 counts = VectorXi::Zero(num);
792 for (qint32 i = 0; i < num; ++i) {
794 std::vector<int> members;
796 for (qint32 j = 0; j < index.rows(); ++j)
797 if (index[j] == clusts[i])
798 members.push_back(j);
800 counts[i] =
static_cast<qint32
>(members.size());
804 switch (m_distance) {
808 centroids.row(i) = RowVectorXd::Zero(p);
809 for (
int j : members)
810 centroids.row(i) +=
X.row(j);
811 centroids.row(i) /= counts[i];
817 MatrixXd Xsorted(counts[i], p);
819 for (
int j : members)
820 Xsorted.row(c++) =
X.row(j);
822 for (qint32 j = 0; j < p; ++j)
823 std::sort(Xsorted.col(j).data(), Xsorted.col(j).data() + Xsorted.rows());
825 qint32 nn =
static_cast<qint32
>(std::floor(0.5 * counts[i])) - 1;
826 if (counts[i] % 2 == 0)
827 centroids.row(i) = 0.5 * (Xsorted.row(nn) + Xsorted.row(nn + 1));
829 centroids.row(i) = Xsorted.row(nn + 1);
K-means partitional clustering with multiple distance metrics, initialisations and empty-cluster poli...
Shared utilities (I/O helpers, spectral analysis, layout management, warp algorithms).
KMeansDistance
Distance metric for K-Means clustering.
KMeansEmptyAction
Action to take when a K-Means cluster becomes empty.
KMeansStart
Initialization strategy for K-Means clustering.
KMeans(QString distance=QString("sqeuclidean"), QString start=QString("sample"), qint32 replicates=1, QString emptyact=QString("singleton"), bool online=true, qint32 maxit=100)
bool calculate(const Eigen::MatrixXd &X, qint32 kClusters, Eigen::VectorXi &idx, Eigen::MatrixXd &C, Eigen::VectorXd &sumD, Eigen::MatrixXd &D)