Я пытаюсь реализовать версию алгоритма нечетких C-средних в Java, и я пытаюсь провести некоторую оптимизацию, вычисляя всего один раз все, что может быть вычисляется только один раз.
Это итеративный алгоритм, касающийся обновления матрицы, пикселей x кластеры матрица принадлежности U
(сумма значений в строке должна быть 1.0), это правило обновления, которое я хочу оптимизировать:
где x - это элемент матрицы X
( пикселей x функции ), а v принадлежит матрице V
( кластеры x функции ). И m
- это параметр, который находится в диапазоне от 1,1
до бесконечности
, а c
- это количество кластеров. Используемое расстояние является евклидовой нормой.
Если бы мне пришлось реализовать эту формулу банальным образом, я бы сделал:
for(int i = 0; i < X.length; i++)
{
int count = 0;
for(int j = 0; j < V.length; j++)
{
double num = D[i][j];
double sumTerms = 0;
for(int k = 0; k < V.length; k++)
{
double thisDistance = D[i][k];
sumTerms += Math.pow(num / thisDistance, (1.0 / (m - 1.0)));
}
U[i][j] = (float) (1f / sumTerms);
}
}
Таким образом, некоторая оптимизация уже выполнена, я предварительно вычислил все возможные квадраты расстояний между X
и V
и сохранил их в матрице D
, но этого недостаточно, так как я дважды просматриваю элементы V
, в результате в два вложенных цикла.
Глядя на формулу, числитель дроби не зависит от суммы, поэтому я могу вычислить числитель и знаменатель независимо, а знаменатель можно вычислить только один раз для каждого пикселя.
Итак, я пришел к следующему решению:
int nClusters = V.length;
double exp = (1.0 / (m - 1.0));
for(int i = 0; i < X.length; i++)
{
int count = 0;
for(int j = 0; j < nClusters; j++)
{
double distance = D[i][j];
double denominator = D[i][nClusters];
double numerator = Math.pow(distance, exp);
U[i][j] = (float) (1f / (numerator * denominator));
}
}
Где я предварительно вычислил знаменатель в дополнительном столбце матрицы D
, когда вычислял расстояния:
for (int i = 0; i < X.length; i++)
{
for (int j = 0; j < V.length; j++)
{
double sum = 0;
for (int k = 0; k < nDims; k++)
{
final double d = X[i][k] - V[j][k];
sum += d * d;
}
D[i][j] = sum;
D[i][B.length] += Math.pow(1 / D[i][j], exp);
}
}
При этом я обнаружил численные различия между «банальное» вычисление и второе, приводящее к другому числовому значению в U
(не в первой итерации, но довольно скоро). Я предполагаю, что проблема в том, что очень маленькие числа возводятся в степень до высоких значений (элементы U
могут находиться в диапазоне от 0,0 до 1,0 и exp
, для m = 1,1
, is 10
) приводит к очень маленьким значениям, тогда как деление числителя и знаменателя и THEN возведение в степень результат кажется лучше в численном отношении. Проблема в том, что здесь требуется гораздо больше операций. 7633
Это первая строка матрицы D
оптимизированным способом (обратите внимание, что последнее значение является предварительно вычисленным знаменателем):
414.3880 44469.38 17300.090 1197.7657 2.0796E-26
Это первая строка U
не оптимизирована:
0,99997544 4.9366603E-21 6.216704E-17 2.4565863E-5
Это первая строка U
оптимизирована:
0.3220644 1.5900239E-21 2.0023086E-17 7.912171E-6
Последний набор значений показывает, что они сильно различаются из-за распространенной ошибки (я все еще надеюсь, что делаю некоторую ошибку) и даже из-за ограничения что сумма этих значений должна быть 1,0, нарушено.
Я что-то делаю не так? Есть ли возможное решение, как оптимизировать код и добиться его численной стабильности? Любые предложения или критика будут оценены.