62: m_distance(distanceFromString(distance.toStdString()))
63, m_start(startFromString(start.toStdString()))
64, m_emptyact(emptyactFromString(emptyact.toStdString()))
65, m_iReps(std::max(replicates, qint32(1)))
68, m_rng(std::random_device{}())
90, m_iReps(std::max(replicates, qint32(1)))
93, m_rng(std::random_device{}())
115KMeansStart KMeans::startFromString(
const std::string& name)
151 VectorXd Xnorm =
X.array().pow(2).rowwise().sum().sqrt();
152 for (qint32 i = 0; i < n; ++i)
155 X.row(i) /= Xnorm(i);
161 X.array() -= (
X.rowwise().sum().array() /
static_cast<double>(p)).replicate(1, p);
162 VectorXd Xnorm =
X.array().pow(2).rowwise().sum().sqrt();
163 for (qint32 i = 0; i < n; ++i)
166 X.row(i) /= Xnorm(i);
171 RowVectorXd Xmins, Xmaxs;
176 qWarning(
"KMeans: Uniform initialization is not supported for Hamming distance.");
179 Xmins =
X.colwise().minCoeff();
180 Xmaxs =
X.colwise().maxCoeff();
186 Del = MatrixXd::Constant(n, k, std::numeric_limits<double>::quiet_NaN());
189 double totsumDBest = std::numeric_limits<double>::max();
197 std::uniform_int_distribution<qint32> sampleDist(0, n - 1);
199 for (qint32 rep = 0; rep < m_iReps; ++rep)
204 C = MatrixXd::Zero(k, p);
205 for (qint32 i = 0; i < k; ++i)
207 for (qint32 j = 0; j < p; ++j)
209 std::uniform_real_distribution<double> dist(Xmins[j], Xmaxs[j]);
210 C(i, j) = dist(m_rng);
214 C.array() -= (C.array().rowwise().sum() / p).replicate(1, p).array();
218 C = MatrixXd::Zero(k, p);
219 for (qint32 i = 0; i < k; ++i)
220 C.row(i) =
X.row(sampleDist(m_rng));
225 idx = VectorXi::Zero(n);
226 d = VectorXd::Zero(n);
228 for (qint32 i = 0; i < n; ++i)
229 d[i] = D.row(i).minCoeff(&idx[i]);
231 m = VectorXi::Zero(k);
232 for (qint32 i = 0; i < n; ++i)
238 bool converged = batchUpdate(
X, C, idx);
242 converged = onlineUpdate(
X, C, idx);
245 qWarning(
"KMeans: Failed to converge during replicate %d.", rep);
248 VectorXi nonempties = (m.array() > 0).cast<
int>();
249 qint32 count = nonempties.sum();
251 MatrixXd C_tmp(count, C.cols());
253 for (qint32 i = 0; i < k; ++i)
255 C_tmp.row(ci++) = C.row(i);
257 MatrixXd D_tmp = distfun(
X, C_tmp);
259 for (qint32 i = 0; i < k; ++i)
263 D.col(i) = D_tmp.col(ci);
264 C.row(i) = C_tmp.row(ci);
270 d = VectorXd::Zero(n);
271 for (qint32 i = 0; i < n; ++i)
275 sumD = VectorXd::Zero(k);
276 for (qint32 i = 0; i < n; ++i)
277 sumD[idx[i]] += d[i];
279 totsumD = sumD.sum();
282 if (totsumD < totsumDBest)
284 totsumDBest = totsumD;
297 if (emptyErrCnt == m_iReps)
312bool KMeans::batchUpdate(
const MatrixXd&
X, MatrixXd& C, VectorXi& idx)
317 for (i = 0; i < n; ++i)
321 for (i = 0; i < k; ++i)
324 previdx = VectorXi::Zero(n);
325 prevtotsumD = std::numeric_limits<double>::max();
327 MatrixXd D = MatrixXd::Zero(n, k);
330 bool converged =
false;
338 gcentroids(
X, idx, changed, C_new, m_new);
339 MatrixXd D_new = distfun(
X, C_new);
341 for (qint32 i = 0; i < changed.rows(); ++i)
343 C.row(changed[i]) = C_new.row(i);
344 D.col(changed[i]) = D_new.col(i);
345 m[changed[i]] = m_new[i];
349 VectorXi empties = VectorXi::Zero(changed.rows());
350 for (qint32 i = 0; i < changed.rows(); ++i)
354 if (empties.sum() > 0)
365 for (qint32 i = 0; i < n; ++i)
366 totsumD += D(i, idx[i]);
369 if (prevtotsumD <= totsumD)
374 gcentroids(
X, idx, changed, C_rev, m_rev);
375 C.block(0, 0, k, C.cols()) = C_rev;
376 m.block(0, 0, k, 1) = m_rev;
381 if (iter >= m_iMaxit)
386 prevtotsumD = totsumD;
389 for (qint32 i = 0; i < n; ++i)
390 d[i] = D.row(i).minCoeff(&nidx[i]);
393 std::vector<int> movedVec;
395 for (qint32 i = 0; i < n; ++i)
397 if (nidx[i] != previdx[i])
398 movedVec.push_back(i);
402 std::vector<int> movedFinal;
403 movedFinal.reserve(movedVec.size());
404 for (
int mi : movedVec)
406 if (D(mi, previdx[mi]) > d[mi])
407 movedFinal.push_back(mi);
410 if (movedFinal.empty())
416 for (
int mi : movedFinal)
420 std::vector<int> tmp;
421 tmp.reserve(2 * movedFinal.size());
422 for (
int mi : movedFinal)
424 tmp.push_back(idx[mi]);
425 tmp.push_back(previdx[mi]);
427 std::sort(tmp.begin(), tmp.end());
428 tmp.erase(std::unique(tmp.begin(), tmp.end()), tmp.end());
430 changed.resize(tmp.size());
431 for (
size_t i = 0; i < tmp.size(); ++i)
439bool KMeans::onlineUpdate(
const MatrixXd&
X, MatrixXd& C, VectorXi& idx)
442 MatrixXd Xmid1, Xmid2;
445 Xmid1 = MatrixXd::Zero(k, p);
446 Xmid2 = MatrixXd::Zero(k, p);
447 for (qint32 i = 0; i < k; ++i)
451 MatrixXd Xsorted(m[i], p);
453 for (qint32 j = 0; j < n; ++j)
455 Xsorted.row(c++) =
X.row(j);
457 for (qint32 j = 0; j < p; ++j)
458 std::sort(Xsorted.col(j).data(), Xsorted.col(j).data() + Xsorted.rows());
460 qint32 nn =
static_cast<qint32
>(std::floor(0.5 * m[i])) - 1;
463 Xmid1.row(i) = Xsorted.row(nn);
464 Xmid2.row(i) = Xsorted.row(nn + 1);
468 Xmid1.row(i) = Xsorted.row(nn);
469 Xmid2.row(i) = Xsorted.row(nn + 2);
473 Xmid1.row(i) = Xsorted.row(0);
474 Xmid2.row(i) = Xsorted.row(0);
481 VectorXi changed(m.rows());
483 for (qint32 i = 0; i < m.rows(); ++i)
485 changed[count++] = i;
486 changed.conservativeResize(count);
488 qint32 lastmoved = 0;
491 bool converged =
false;
493 while (iter < m_iMaxit)
498 for (qint32 j = 0; j < changed.rows(); ++j)
500 qint32 i = changed[j];
501 VectorXi mbrs = VectorXi::Zero(n);
502 for (qint32 l = 0; l < n; ++l)
506 VectorXi sgn = 1 - 2 * mbrs.array();
508 for (qint32 l = 0; l < n; ++l)
512 Del.col(i) = (
static_cast<double>(m[i]) / (
static_cast<double>(m[i]) + sgn.cast<
double>().array()));
513 Del.col(i).array() *= (
X.rowwise() - C.row(i)).array().pow(2).rowwise().sum().array();
518 for (qint32 j = 0; j < changed.rows(); ++j)
520 qint32 i = changed[j];
523 MatrixXd ldist = Xmid1.row(i).replicate(n, 1) -
X;
524 MatrixXd rdist =
X - Xmid2.row(i).replicate(n, 1);
525 VectorXd mbrs = VectorXd::Zero(n);
526 for (qint32 l = 0; l < n; ++l)
529 MatrixXd sgn = ((-2 * mbrs).array() + 1).replicate(1, p);
530 rdist = sgn.array() * rdist.array();
531 ldist = sgn.array() * ldist.array();
533 for (qint32 l = 0; l < n; ++l)
536 for (qint32 h = 0; h < p; ++h)
537 sum += std::max(0.0, std::max(rdist(l, h), ldist(l, h)));
543 Del.col(i) = (
X.rowwise() - C.row(i)).array().abs().rowwise().sum();
549 MatrixXd normC = C.array().pow(2).rowwise().sum().sqrt();
550 for (qint32 j = 0; j < changed.rows(); ++j)
552 qint32 i = changed[j];
553 MatrixXd XCi =
X * C.row(i).transpose();
555 VectorXi mbrs = VectorXi::Zero(n);
556 for (qint32 l = 0; l < n; ++l)
560 VectorXi sgn = 1 - 2 * mbrs.array();
561 double A =
static_cast<double>(m[i]) * normC(i, 0);
564 Del.col(i) = 1 + sgn.cast<
double>().array() *
565 (A - (B + 2 * sgn.cast<
double>().array() * m[i] * XCi.array() + 1).sqrt());
572 prevtotsumD = totsumD;
574 VectorXi nidx = VectorXi::Zero(n);
575 VectorXd minDel = VectorXd::Zero(n);
576 for (qint32 i = 0; i < n; ++i)
577 minDel[i] = Del.row(i).minCoeff(&nidx[i]);
580 std::vector<int> movedVec;
582 for (qint32 i = 0; i < n; ++i)
583 if (previdx[i] != nidx[i])
584 movedVec.push_back(i);
587 std::vector<int> movedFinal;
588 movedFinal.reserve(movedVec.size());
589 for (
int mi : movedVec)
590 if (Del(mi, previdx[mi]) > minDel(mi))
591 movedFinal.push_back(mi);
593 if (movedFinal.empty())
595 if ((iter == iter1) || nummoved > 0)
602 int bestMoved = movedFinal[0];
603 int bestDist = ((movedFinal[0] - lastmoved) % n + n) % n;
604 for (
size_t i = 1; i < movedFinal.size(); ++i)
606 int d_i = ((movedFinal[i] - lastmoved) % n + n) % n;
610 bestMoved = movedFinal[i];
613 int movedPt = bestMoved;
615 if (movedPt <= lastmoved)
618 if (iter >= m_iMaxit)
625 qint32 oidx = idx[movedPt];
626 qint32 nidx_pt = nidx[movedPt];
627 totsumD += Del(movedPt, nidx_pt) - Del(movedPt, oidx);
629 idx[movedPt] = nidx_pt;
636 C.row(nidx_pt) += (
X.row(movedPt) - C.row(nidx_pt)) / m[nidx_pt];
637 C.row(oidx) -= (
X.row(movedPt) - C.row(oidx)) / m[oidx];
642 onidx << oidx, nidx_pt;
644 for (qint32 h = 0; h < 2; ++h)
646 qint32 ci = onidx[h];
647 MatrixXd Xsorted(m[ci], p);
649 for (qint32 j = 0; j < n; ++j)
651 Xsorted.row(c++) =
X.row(j);
653 for (qint32 j = 0; j < p; ++j)
654 std::sort(Xsorted.col(j).data(), Xsorted.col(j).data() + Xsorted.rows());
656 qint32 nn =
static_cast<qint32
>(std::floor(0.5 * m[ci])) - 1;
657 if ((m[ci] % 2) == 0)
659 C.row(ci) = 0.5 * (Xsorted.row(nn) + Xsorted.row(nn + 1));
660 Xmid1.row(ci) = Xsorted.row(nn);
661 Xmid2.row(ci) = Xsorted.row(nn + 1);
665 C.row(ci) = Xsorted.row(nn + 1);
668 Xmid1.row(ci) = Xsorted.row(nn);
669 Xmid2.row(ci) = Xsorted.row(nn + 2);
673 Xmid1.row(ci) = Xsorted.row(0);
674 Xmid2.row(ci) = Xsorted.row(0);
681 C.row(nidx_pt).array() += (
X.row(movedPt) - C.row(nidx_pt)).array() / m[nidx_pt];
682 C.row(oidx).array() += (
X.row(movedPt) - C.row(oidx)).array() / m[oidx];
685 VectorXi sorted_onidx(2);
686 sorted_onidx << oidx, nidx_pt;
687 std::sort(sorted_onidx.data(), sorted_onidx.data() + sorted_onidx.rows());
688 changed = sorted_onidx;
696MatrixXd KMeans::distfun(
const MatrixXd&
X,
const MatrixXd& C)
698 const qint32 nclusts = C.rows();
699 MatrixXd D = MatrixXd::Zero(n, nclusts);
704 for (qint32 i = 0; i < nclusts; ++i)
705 D.col(i) = (
X.rowwise() - C.row(i)).rowwise().squaredNorm();
709 for (qint32 i = 0; i < nclusts; ++i)
710 D.col(i) = (
X.rowwise() - C.row(i)).cwiseAbs().rowwise().sum();
716 VectorXd normC = C.rowwise().norm();
717 for (qint32 i = 0; i < nclusts; ++i)
719 RowVectorXd C_normed = C.row(i) / normC(i);
720 D.col(i) = (1.0 - (
X * C_normed.transpose()).array()).cwiseMax(0.0);
726 for (qint32 i = 0; i < nclusts; ++i)
727 D.col(i) = (
X.rowwise() - C.row(i)).cwiseAbs().rowwise().sum() / p;
736void KMeans::gcentroids(
const MatrixXd&
X,
const VectorXi& index,
const VectorXi& clusts,
737 MatrixXd& centroids, VectorXi& counts)
739 const qint32 num = clusts.rows();
740 centroids = MatrixXd::Constant(num, p, std::numeric_limits<double>::quiet_NaN());
741 counts = VectorXi::Zero(num);
743 for (qint32 i = 0; i < num; ++i)
746 std::vector<int> members;
748 for (qint32 j = 0; j < index.rows(); ++j)
749 if (index[j] == clusts[i])
750 members.push_back(j);
752 counts[i] =
static_cast<qint32
>(members.size());
762 centroids.row(i) = RowVectorXd::Zero(p);
763 for (
int j : members)
764 centroids.row(i) +=
X.row(j);
765 centroids.row(i) /= counts[i];
771 MatrixXd Xsorted(counts[i], p);
773 for (
int j : members)
774 Xsorted.row(c++) =
X.row(j);
776 for (qint32 j = 0; j < p; ++j)
777 std::sort(Xsorted.col(j).data(), Xsorted.col(j).data() + Xsorted.rows());
779 qint32 nn =
static_cast<qint32
>(std::floor(0.5 * counts[i])) - 1;
780 if (counts[i] % 2 == 0)
781 centroids.row(i) = 0.5 * (Xsorted.row(nn) + Xsorted.row(nn + 1));
783 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.
bool calculate(const Eigen::MatrixXd &X, qint32 kClusters, Eigen::VectorXi &idx, Eigen::MatrixXd &C, Eigen::VectorXd &sumD, Eigen::MatrixXd &D)
KMeans(QString distance=QString("sqeuclidean"), QString start=QString("sample"), qint32 replicates=1, QString emptyact=QString("error"), bool online=true, qint32 maxit=100)