最小化Matlab

时间:2016-02-22 04:47:06

标签: arrays algorithm matlab optimization statistics

我有一个大阵列(大约250,000 x 10)。每行包含1或-1。 E.g:

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时的最小值。

我尝试了下面建议的方法(寻找互补对),但对于我的数据集,这只能形成23k行的平衡子集。因此,我需要一个近似值,它生成一个大小为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的末尾,而是将其与某个元素交换,以便实现最佳改进。

更新:我不想提供数据集的具体细节,因为所有解决方案都应适用于指定格式的任何矩阵。

感谢所有贡献的人!

4 个答案:

答案 0 :(得分:3)

以下方法如何?由于10列仅具有+1和-1值,因此只有1024个不同的行可能。所以我们的数据现在是:

  1. 具有-1和+1系数的1024 x 10矩阵a(i,j)。该矩阵具有所有不同的可能(唯一)行。
  2. 一个向量v(i),其中有多少次我们看到了第i行。
  3. 现在我们可以编写一个简单的混合整数规划问题如下:

    enter image description here

    注意:

    • 我们只有1024个整数变量
    • 我们在x(i)上设置一个上限,指示可以选择行的次数
    • 我们使用所谓的变量分裂技术来模拟绝对值并保持模型线性
    • 最小化均值与最小化总和(差异是常数因子)相同
    • 关于optcr的一行告诉MIP求解器找到经过验证的全局最优解决方案
    • 一个好的MIP求解器应该能够很快找到解决方案。我使用250k行和N = 100测试了一些随机数据。 我实际上相信这是一个容易解决的问题。
    • 重申:此方法提供经过验证的全球最佳解决方案。
    • 可以找到更多详细信息here

答案 1 :(得分:2)

正如其他人所说,最佳解决方案可能是不可能的,所以我将专注于具体案例。

首先,我假设每列的分布独立。

然后我处理累加器空间以减小数据大小并加快代码速度。

我这样做是将每个-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;

使用随机生成的数据,我能够在一组250k行中获得> 100k对,并且每个列中的一对子集将具有等于零的和。因此,如果1和-1分布均匀,这可能就足够了。

我考虑的第二种情况是每列的总和远非零,这意味着在1和-1之间存在很大的不成比例。

abs(sum(data)) >> 0

通过反转和为负的每一列,这不会影响数据,因为最后可以再次反转这些列。它可以强制不成比例比-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;

随机生成的数据大约是-1的2倍,而每列的总和大约是80k,我可以找到100k数据的子集,每列的总和约为5k。

第三种情况,是某些列总和接近于零而有些则不是。在这种情况下,您将列分为具有大和的列和具有小和的那些,然后按大和列的汉明重量对数据进行排序,并获得每个大列索引内的小和列对。对于大和列的每个索引,这将创建一个矩阵,其中包含可能对的数量,非对应行的数量以及小列的不可行行的总和。

现在,您可以使用该信息来保持运行总和,并查看要添加到子集中的大和列的哪些索引,以及是否值得在每种情况下添加比喻或无法使用的数据。

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列组成的数据,可以构建一组100k行,其中小和列中的总和小于1k,大的大小在10k到30k之间。的。

值得注意的是,数据的大小,所需子集的大小以及1和-1的分布将对此算法的性能产生很大影响。

答案 2 :(得分:1)

遗憾的是,这个问题不属于常规(连续)优化的范围。您的问题,可以参数化如下:

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

其中S_n是索引j∈{0,...,250000}的n元素组合的集合,也可以在x中重写为非常相似的常规二次整数规划问题:

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

data是你的250000 * 10矩阵,x是我们正在寻找的250000 * 1组合向量。 (现在我们优化平方和而不是绝对值之和......)

这个问题被证明NP-hard,这意味着找到全局最小化器,你必须经历2500次可能的n次绘制的所有可能组合,这等于二项式系数(250000 n),等于250000!/(n!*(250000-n)!) ...

祝你好运......;)

修改

如果您打算以启发式方式解决此问题,因为我认为您需要一个解决方案,请使用启发式here代替您的方法。

答案 3 :(得分:0)

由于你的回答似乎表明你有兴趣寻找更大的序列(更大的n),下面的代码试图找到最大的n,允许最多10%的行(即25,000)被删除。也就是说,代码通过从设置中删除最佳行到25,000次来最小化完整数据集的sum( abs( sum( data, 1)))。这应该与最小化平均值(您声明的问题)相同。代码使用范围[1, 1024]中的索引来提高效率,直到最后一步产生最终输出。 order变量设置为10(您声明的问题),对应于2^10 = 1024个可能的行向量。通过将所有-1值设置为0并获取二进制表示,可以找到给定行向量的索引,例如[-1 -1 -1 -1 -1 -1 -1 -1 1]。所以在这个例子中,行向量的索引是[0 0 0 0 0 0 0 0 0 1] = 1。 (注意,索引1实际上转换为2,因为MATLAB不允许索引为0。)

我已经测试了这是一个统一的随机分布(一个简单的例子),并且在删除~1000行后它通常会收敛到真正的min(即sum( abs( sum( data, 1))) = 0)。 Click here to run the example code below for the uniform random case on AlgorithmHub。每次运行时都会选择一个新的随机集,通常需要大约30秒才能在该基础架构上完成。

Click here to upload a csv file of your data set and run the example code on 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;