Минимизация суммы столбцов массива в Matlab

У меня большой массив (приблизительно 250 000 x 10). Каждая строка содержит 1s или -1s. Например:

data(1, :) = [1, -1, -1, -1, -1, -1, -1, -1, 1, -1];

Мне нужно выбрать наборы из n строк, чтобы среднее значение абсолютных сумм столбцов было минимизировано (как можно ближе к нулю). Итак, в этом примере игрушек, где n = 2:

[ 1  1  1  1]
[-1 -1 -1 -1]
[-1  1 -1  1]

Я бы выбрал строки 1 и 2, поскольку они суммируются до [0 0 0 0] (среднее значение 0), что является минимальным возможным при n = 2.


Я попробовал предложенный ниже метод (найти дополнительные пары), но для моего набора данных это может только сформировать сбалансированное подмножество из 23 тыс. строк. Итак, мне нужна аппроксимация, которая генерирует подмножество размером n строк, но с минимальными средствами абсолютных сумм столбцов.

Лучший подход, который я нашел до сих пор, заключается в следующем: выберите начальное подмножество, итеративно добавьте каждую строку из остальной части в базу и сохраните ее, если она улучшит среднее значение абсолютных сумм столбцов. Это очень грубо, и я уверен, что есть лучшие способы. Он также склонен зависеть от ложных минимумов, поэтому необходимо добавить непредвиденные обстоятельства:

shuffle = randperm(size(data));
data_shuffled = data(shuffle, :);

base = data_shuffled(1:30000, :);
pool = data_shuffled(30001:end, :);

best_mean = mean(abs(sum(base, 1)));
best_matrix = base;
n = 100000;

for k = 1:20

    for i = 1:size(pool, 1)
        temp = pool (i, :);

        if(~isnan(temp(1)))
            temp_sum = sum(temp, 1);
            new_sum = temp_sum + sum(best, 1);
            temp_mean = mean(abs(new_sum));

            if(temp_mean < best_mean)
                best_mean = temp_mean;
                best_matrix = vertcat(best_matrix, temp);
                pool(i, :) = NaN(1, 10);            
            end
        end
    end

    if(size(best_matrix, 1) > n)
        return
    end

end

Это достигает среднего значения абсолютных сумм столбцов ~ 17 000, что не так уж плохо. Повторение с разными семенами, вероятно, немного улучшит его.

В идеале, вместо того, чтобы просто добавлять новый элемент в конец best_matrix, я бы заменил его на какой-то элемент, чтобы добиться наилучшего улучшения.

Обновление: я не хочу выделять конкретные данные набора данных, потому что все решения должны быть применимы к любым матрицам в указанном формате.

Спасибо всем, кто внес свой вклад!

Ответы

Ответ 1

Как утверждают другие, оптимальное решение может быть невозможно, поэтому я сосредоточусь на конкретных случаях.

Сначала я предполагаю независимость распределений для каждого столбца.

Затем я работаю на пространстве аккумулятора, чтобы уменьшить размер данных и скорость кода.

Я делаю это, беря каждый -1 как 0 и рассматривая каждую строку как число и добавляя 1, чтобы избежать работы с 0 в качестве индекса, например:

data(1,:)=[-1 1 -1 1 -1 1 -1 1 -1 1] -> '0101010101' -> 341 -> 342

С этим мы можем накапливать данные как:

function accum=mat2accum(data)

[~,n]=size(data);
indexes=bin2dec(num2str((data+1)/2))+1;
accum=accumarray(indexes,1,[2^n 1]);

Первый случай, который я рассматриваю, - это когда сумма каждого столбца является небольшим числом по сравнению с размером данных, это означает, что во всех столбцах имеется одинаковое количество 1 и -1.

sum(data) << size(data)

В этом случае вы можете найти все пары, которые отменяют друг друга, например:

data(1,:)=[-1 1 -1 1 -1 1 -1 1 -1 1] -> '0101010101' -> 341 -> 342
data(2,:)=[1 -1 1 -1 1 -1 1 -1 1 -1] -> '1010101010' -> 682 -> 683

И мы знаем, что каждая пара будет находиться в зеркальном положении в индексе аккумулятора, поэтому мы можем получить все возможные пары с помощью:

function [accumpairs, accumleft]=getpairs(accum)

accumpairs=min([accum,accum(end:-1:1)],[],2);
accumleft=accum-accumpairs;

Со случайными сгенерированными данными я смог получить > 100 к пар в наборе из 250 тыс. строк, а подмножество пар будет иметь сумму, равную нулю в каждом столбце. Поэтому, если 1 и -1 распределены одинаково, этого может быть достаточно.


Второй случай, который я рассматривал, заключался в том, что сумма каждого столбца далека от нуля, а это означает, что существует большая диспропорция между 1 и -1.

abs(sum(data)) >> 0

Инвертируя каждый столбец, где сумма отрицательна, что не повлияет на данные, так как в конце можно снова инвертировать эти столбцы. Его можно заставить диспропорцию быть больше 1, чем -1. И, выбирая сначала возможные пары этих данных, диспропорция еще более выражена.

С данными, подготовленными как таковые, можно рассматривать проблему, чтобы свести к минимуму число 1 в требуемом наборе. Для этого сначала мы рандомизируем возможные индексы, затем вычисляем и сортируем вес Хэмминга (число 1 в двоичном представлении) каждого индекса, а затем собираем данные с наименьшим весом Хэмминга.

function [accumlast,accumleft]=resto(accum,m)

[N,~]=size(accum);
columns=log2(N);
indexes=randperm(N)'; %'
[~,I]=sort(sum((double(dec2bin(indexes-1,columns))-48),2));
accumlast=zeros(N,1);

for k=indexes(I)' %'
    accumlast(k)=accum(k);
    if sum(accumlast)>=m
        break
    end
end

accumleft=accum-accumlast;

С произвольно сгенерированными данными, где было примерно в 2 раза больше 1, чем -1, а сумма каждого столбца составляла около 80 тыс., я могу найти подмножество из 100 тыс. данных с суммой около 5 тыс. в каждом столбце.


Третий случай, когда некоторые суммы столбцов близки к нулю, а некоторые - нет. В этом случае вы выделяете столбцы в те, у которых большая сумма, и те, которые имеют небольшую сумму, а затем сортируют данные по весу помех столбцов большой суммы и получают пары столбцов малой суммы внутри каждого из столбцов большого столбца, Это создаст матрицу с числом возможных пар, количеством строк несанкционированного доступа и суммой неоплачиваемых строк небольших столбцов для каждого индекса столбцов большой суммы.

Теперь вы можете использовать эту информацию, чтобы сохранить текущую сумму и посмотреть, какие индексы столбцов большой суммы будут добавлены к вашему подмножеству, а также если в каждом случае стоит добавить притчу или неоплачиваемые данные.

function [accumout,accumleft]=getseparated(accum, bigcol, smallcol, m)

data=accum2mat(accum);

'indexing'
bigindex=bin2dec(num2str((data(:,bigcol)+1)/2))+1;
[~,bn]=size(bigcol);
[~,sn]=size(smallcol);

'Hamming weight'
b_ind=randperm(2^bn)'; %'
[~,I]=sort(sum((double(dec2bin(b_ind-1,bn))-48),2));

temp=zeros(2^bn,4+sn);

w=waitbar(0,'Processing');
for k=1:2^bn;
    small_data=data(bigindex==b_ind(I(k)),smallcol);
    if small_data
        small_accum=mat2accum(small_data);
        [small_accumpairs, small_accum]=getpairs(small_accum);
        n_pairs=sum(small_accumpairs);
        n_non_pairs=sum(small_accum);
        sum_non_pairs=sum(accum2mat(small_accum));
    else
        n_pairs=0;
        n_non_pairs=0;
        sum_non_pairs=zeros(1,sn);
    end
    ham_weight=sum((double(dec2bin(b_ind(I(k))-1,bn))-48),2);
    temp(k,:)=[b_ind(I(k)) n_pairs n_non_pairs ham_weight sum_non_pairs];
    waitbar(k/2^bn);
end

close(w)

pair_ind=1;
nonpair_ind=1;
runningsum=[0 0 0 0 0 0 0 0 0 0];
temp2=zeros(2^bn,2);

while sum(sum(temp2))<=m
     if pair_ind<=2^bn
         pairsum=[(((double(dec2bin((temp(pair_ind,1)-1),bn))-48)*2)-1)*temp(pair_ind,2) zeros(1,sn)];
     end
     if nonpair_ind<=2^bn
         nonpairsum=[(((double(dec2bin((temp(nonpair_ind,1)-1),bn))-48)*2)-1)*temp(nonpair_ind,3) temp(nonpair_ind,5:5+sn-1)];
     end
     if nonpair_ind==(2^bn)+1
         temp2(pair_ind,1)=temp(pair_ind,2);
         runningsum=runningsum+pairsum;
         pair_ind=pair_ind+1;
     elseif pair_ind==(2^bn)+1
         temp2(nonpair_ind,2)=temp(nonpair_ind,3);
         runningsum=runningsum+nonpairsum;
         nonpair_ind=nonpair_ind+1;
     elseif sum(abs(runningsum+pairsum))<=sum(abs(runningsum+nonpairsum))
         temp2(pair_ind,1)=temp(pair_ind,2);
         runningsum=runningsum+pairsum;
         pair_ind=pair_ind+1;
     elseif sum(abs(runningsum+pairsum))>sum(abs(runningsum+nonpairsum))
         temp2(nonpair_ind,2)=temp(nonpair_ind,3);
         runningsum=runningsum+nonpairsum;
         nonpair_ind=nonpair_ind+1;
     end
end

accumout=zeros(2^(bn+sn),1);

for k=1:2^bn
    if temp2(k,:)
        small_data=data(bigindex==temp(k,1),smallcol);
        if small_data
            small_accum=mat2accum(small_data);
            [small_accumpairs, small_accum]=getpairs(small_accum);
            pairs=accum2mat(small_accumpairs);
            non_pairs=accum2mat(small_accum);
        else
            pairs=zeros(1,sn);
            non_pairs=zeros(1,sn);
        end
        if temp2(k,1)
            datatemp=zeros(temp2(k,1),sn+bn);
            datatemp(:,bigcol)=((double(dec2bin(ones(temp2(k,1),1)*(temp(k,1)-1),bn))-48)*2)-1;
            datatemp(:,smallcol)=pairs;
            accumout=accumout+mat2accum(datatemp);
        end
        if temp2(k,2)
            datatemp=zeros(temp2(k,2),sn+bn);
            datatemp(:,bigcol)=((double(dec2bin(ones(temp2(k,2),1)*(temp(k,1)-1),bn))-48)*2)-1;
            datatemp(:,smallcol)=non_pairs;
            accumout=accumout+mat2accum(datatemp);
        end
    end
end

accumleft=accum-accumout;

С данными, состоящими из 5 столбцов первого случая и 5 столбцов второго случая, можно было построить набор из 100 тыс. строк с < 1k суммы в столбцах малых сумм и между 10k и 30k в большой из них.

Стоит отметить, что размер данных, размер требуемого подмножества и распределение 1 и -1 будут иметь большое влияние на производительность этих алгоритмов.

Ответ 2

Как насчет следующего подхода. Если 10 столбцов имеют только значения +1 и -1, возможно только 1024 различных строк. Итак, наши данные теперь:

  • матрица размером 1024 x 10 a(i,j) с коэффициентами -1 и +1. Эта матрица имеет все возможные (уникальные) строки.
  • вектор v(i), сколько раз мы видели строку i.

Теперь мы можем написать простую задачу смешанного целочисленного программирования следующим образом:

введите описание изображения здесь

Примечания:

  • У нас есть только 1024 целочисленных переменных
  • Мы устанавливаем верхнюю границу по x (i), которая указывает, сколько раз может быть выбрана строка
  • Мы используем так называемый метод разделения переменных для моделирования абсолютных значений и сохраняем линейную модель
  • Сведение к минимуму среднего равносильно минимизации суммы (разность является постоянным фактором)
  • Строка о optcr сообщает решателю MIP, чтобы найти проверенные глобальные оптимальные решения.
  • Хороший MIP-решатель должен быстро находить решения. Я тестировал некоторые случайные данные с использованием строк 250 тыс. И N = 100. Я действительно считаю, что это непростая проблема.
  • Повторить: этот метод предоставляет проверенные глобальные оптимальные решения.
  • Более подробную информацию можно найти здесь.

Ответ 3

Эта проблема, к сожалению, выходит за рамки регулярной (непрерывной) оптимизации. Ваша проблема, которую можно параметризовать следующим образом:

min_{S∈S_n} Σ_{j∈S}|Σ_i data_ji|

Где S_n - набор n-элементов комбинаций индексов j∈{0,...,250000}, также может быть переписано как очень похожая регулярная задача квадратичного целочисленного программирования в x:

min_x x'* data *data' *x
0<=x<=1 and x*1=n

Где data - ваша матрица 250000 * 10, а x - вектор комбинаций 250000 * 1, которые мы ищем. (Теперь мы оптимизируем сумму квадратов вместо суммы абсолютных значений...)

Эта проблема проверена как NP-hard, что означает найти глобальный минимизатор, вы должны пойти через все возможные комбинации n рисунков в 250000 возможных, что равно биномиальному коэффициенту (250000 n), равному 250000!/(n!*(250000-n)!)...

Так что удачи...;)

ИЗМЕНИТЬ

Если вы собираетесь решить эту эвристику, так как я предполагаю, что вам понадобится решение, используйте эвристику здесь вместо ваш подход.

Ответ 4

Поскольку ваши ответы, казалось, указывали на то, что вам было интересно найти большие последовательности (больше n), код ниже пытается найти наибольшее n, позволяющее удалить до 10% строк (например, 25 000). Это код минимизирует sum( abs( sum( data, 1))) полного набора данных, удаляя лучшую строку из набора до 25 000 раз. Это должно быть таким же, как минимизация среднего (ваша заявленная проблема). Код использует индексы в диапазоне [1, 1024] для эффективности до тех пор, пока последний результат не будет получен на последнем этапе. Переменная порядка равна 10 (ваша заявленная проблема), соответствующая 2^10 = 1024 возможным векторам строк. Индекс для заданного вектора строки, например [-1 -1 -1 -1 -1 -1 -1 -1 1], определяется путем установки всех значений -1 в 0 и принятия двоичного представления. Итак, в этом примере индекс для вектора строки [0 0 0 0 0 0 0 0 0 1] = 1. (Обратите внимание, что индекс 1 фактически преобразован в 2, так как MATLAB не допускает индекс 0.)

Я проверил это для равномерного случайного распределения (простой случай) и, как правило, сходится к истинному мин (т.е. sum( abs( sum( data, 1))) = 0) после удаления ~ 1000 строк. Нажмите здесь, чтобы запустить приведенный ниже примерный пример для случайного случайного случая в AlgorithmHub. При каждом запуске будет выбран новый случайный набор и, как правило, потребуется около 30 секунд для завершения этой инфраструктуры.

Нажмите здесь, чтобы загрузить файл csv вашего набора данных и запустить пример кода на AlgorithmHub. Ссылка на output.cvs позволит вам загрузить результаты. Код должен быть легко изменен для поддержки вашего метода добавления новых строк, если вы хотите получить конкретный n. Использование идеи индекса должно с соответствующей справочной таблицей (lut) поможет сохранить эффективность. В противном случае, если вы хотите получить большой большой n, вы можете продолжать удалять строки, даже если сумма равна 0 (минимум).

% Generate data set as vector of length order with elements in set {1,-1}.
tic();
rows  = 250000;
order = 10;
rowFraction = 0.1;
maxRowsToRemove = rows * rowFraction;
data  = rand( rows, order);
data( data >= 0.5) =  1;
data( data <  0.5) = -1;

% Convert data to an index to one of 2^order vectors of 1 or -1.
% We set the -1 values to 0 and get the binary representation of the
% vector of binary values.
a = data;
a( a==-1)=0;
ndx    = zeros(1,length(a));
ndx(:) = a(:,1)*2^9+a(:,2)*2^8+a(:,3)*2^7+a(:,4)*2^6+a(:,5)*2^5+...
         a(:,6)*2^4+a(:,7)*2^3+a(:,8)*2^2+a(:,9)*2+a(:,10) + 1;

% Determine how many of each index we have in data pool.
bins        = zeros( 1, 2^order);
binsRemoved = zeros( 1, 2^order);
for ii = 1:length( ndx)
    bins( ndx(ii)) = bins( ndx(ii)) + 1;
end

colSum = sum(data,1);
sumOfColSum = sum(abs(colSum));
absSum = sumOfColSum;
lut = genLutForNdx( order);

nRemoved = 0;
curSum = colSum;
for ii = 1:maxRowsToRemove
    if ( absSum == 0)
        disp( sprintf( '\nminimum solution found'));
        break;
    end
    ndxR = findNdxToRemove( curSum, bins, lut);
    if ndxR > 0
        bins( ndxR) = bins( ndxR) - 1;
        binsRemoved( ndxR) = binsRemoved( ndxR) + 1;
        curSum = curSum - lut( ndxR, :);
        nRemoved = nRemoved + 1;
        absSum = sum( abs( curSum));
    else
        disp( sprintf( '\nearly termination'));
        break;
    end
end

stat1 = sprintf( ...
    'stats-L1: original sum = %d, final sum = %d, num rows removed = %d',...
    sumOfColSum, absSum, nRemoved);
stat2 = sprintf( ...
    'stats-L2: iter = %d, run time = %.2f sec\n', ii, toc());
disp( stat1);
disp( stat2);

% Show list of indicies removed along with the number of each removed.
binRndx   = find( binsRemoved != 0);
ndxRemovedHist = [binRndx', binsRemoved(binRndx(:))'];
disp( sprintf( '%s\t%s', 'INDEX', 'NUM_REMOVED'));
for ii = 1: length( ndxRemovedHist)
    disp( sprintf( '%d\t%d', ndxRemovedHist(ii,1), ndxRemovedHist(ii,2)));
end

% Generate the modified data array from the list of removed elements.
modData = data;
lr      = [];
for ii = 1: length( ndxRemovedHist)
    sr = find( ndx==ndxRemovedHist(ii,1));
    lr = [lr, sr(1:ndxRemovedHist(ii,2))];
end
modData( lr, :) = [];
disp( sprintf( 'modified data array in variable "modData"'));

% ****************************************************
% Generate data set as vector of length order with elements in set {1,-1}.
tic();
rows  = 250000;
order = 10;
rowFraction = 0.1;
maxRowsToRemove = rows * rowFraction;
data  = rand( rows, order);
data( data >= 0.5) =  1;
data( data <  0.5) = -1;

% Convert data to an index to one of 2^order vectors of 1 or -1.
% We set the -1 values to 0 and get the binary representation of the
% vector of binary values.
a = data;
a( a==-1)=0;
ndx    = zeros(1,length(a));
ndx(:) = a(:,1)*2^9+a(:,2)*2^8+a(:,3)*2^7+a(:,4)*2^6+a(:,5)*2^5+...
         a(:,6)*2^4+a(:,7)*2^3+a(:,8)*2^2+a(:,9)*2+a(:,10) + 1;

% Determine how many of each index we have in data pool.
bins        = zeros( 1, 2^order);
binsRemoved = zeros( 1, 2^order);
for ii = 1:length( ndx)
    bins( ndx(ii)) = bins( ndx(ii)) + 1;
end

colSum = sum(data,1);
sumOfColSum = sum(abs(colSum));
absSum = sumOfColSum;
lut = genLutForNdx( order);

nRemoved = 0;
curSum = colSum;
for ii = 1:maxRowsToRemove
    if ( absSum == 0)
        disp( sprintf( '\nminimum solution found'));
        break;
    end
    ndxR = findNdxToRemove( curSum, bins, lut);
    if ndxR > 0
        bins( ndxR) = bins( ndxR) - 1;
        binsRemoved( ndxR) = binsRemoved( ndxR) + 1;
        curSum = curSum - lut( ndxR, :);
        nRemoved = nRemoved + 1;
        absSum = sum( abs( curSum));
    else
        disp( sprintf( '\nearly termination'));
        break;
    end
end

stat1 = sprintf( ...
    'stats-L1: original sum = %d, final sum = %d, num rows removed = %d',...
    sumOfColSum, absSum, nRemoved);
stat2 = sprintf( ...
    'stats-L2: iter = %d, run time = %.2f sec\n', ii, toc());
disp( stat1);
disp( stat2);

% Show list of indicies removed along with the number of each removed.
binRndx   = find( binsRemoved != 0);
ndxRemovedHist = [binRndx', binsRemoved(binRndx(:))'];
disp( sprintf( '%s\t%s', 'INDEX', 'NUM_REMOVED'));
for ii = 1: length( ndxRemovedHist)
    disp( sprintf( '%d\t%d', ndxRemovedHist(ii,1), ndxRemovedHist(ii,2)));
end

% Generate the modified data array from the list of removed elements.
modData = data;
lr      = [];
for ii = 1: length( ndxRemovedHist)
    sr = find( ndx==ndxRemovedHist(ii,1));
    lr = [lr, sr(1:ndxRemovedHist(ii,2))];
end
modData( lr, :) = [];
disp( sprintf( 'modified data array in variable "modData"'));

% ****************************************************
function ndx = findNdxToRemove( curSum, bins, lut)

% See if ideal index to remove exists in current bin set.  We look at the
% sign of each element of the current sum to determine index to remove  
aa = zeros( size( curSum));
if (isempty( find( curSum == 0)))

    aa( curSum <  0) = 0;
    aa( curSum >  0) = 1;
    ndx  = aa(1)*2^9+aa(2)*2^8+aa(3)*2^7+aa(4)*2^6+aa(5)*2^5+...
           aa(6)*2^4+aa(7)*2^3+aa(8)*2^2+aa(9)*2+aa(10) + 1; 

    if( bins(ndx) > 0)
       % Optimal row to remove was found directly.
        return;
    end
end

% Serach through all the non-empty indices that remain for best to remove.
delta      =  0;
ndx        = -1;
minSum     = sum( abs( curSum));
minSumOrig = minSum;
bestNdx    = -1;
firstFound =  1;
for ii = 1:length( bins)
    if ( bins(ii) > 0)
        tmp = sum( abs( curSum - lut( ii,:)));
        if ( firstFound) 
            minSum = tmp;
            bestNdx = ii;
            firstFound = 0;
        elseif ( tmp < minSum)
            minSum   = tmp;
            bestNdx = ii;
        end
    end
end
ndx = bestNdx;