【问题标题】:Minimising the sum of array columns in Matlab在Matlab中最小化数组列的总和
【发布时间】:2016-06-03 10:14:51
【问题描述】:

我有一个大数组(大约 250,000 x 10)。每行包含 1 或 -1。例如:

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 的末尾,而是将其与一些元素交换,以实现最佳改进。

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

感谢所有做出贡献的人!

【问题讨论】:

  • 循环遍历所有可能的行组合是一个选项还是您需要程序快速?
  • 如果程序需要几个小时是可以接受的。不过,n 将在 100k 左右。我想知道是否有动态编程解决方案?
  • 哇,我们说的是 C(250k,100k),这是巨大的,所以没有蛮力的选择。但是只有 2^10 (1024) 个可能的行,因此根据数据的分布,1 和 -1 的任何组合应该出现大约 250 次。也许您可以尝试将行与相反的行配对并收集尽可能多的对,希望是 50k 对。
  • 首先,我很确定你想最小化 的绝对值,否则你应该选择尽可能多(和尽可能大)负值的行.其次,您确定要最小化 mean 和吗?这意味着总和为 [1000 -1000 1000 -1000] 的一组行(平均值为 0)优于总和为 [0 0 1 0] 的一组行。
  • @NoelSegura:我认为你成功了。它超过 2^10 次迭代,因为现在您可以多次选择同一行,但它小到足以暴力破解。

标签: arrays algorithm matlab optimization statistics


【解决方案1】:

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

  1. 一个 1024 x 10 矩阵a(i,j),具有 -1 和 +1 系数。该矩阵具有所有不同的可能(唯一)行。
  2. 一个向量v(i),其中包含我们看到第 i 行的次数。

现在我们可以写一个简单的混合整数规划问题如下:

注意事项:

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

【讨论】:

  • 很好...有 MIP 的 MATLAB 工具箱吗?
  • 是的。 optimization toolbox 有一个 MIP 求解器。更高级的求解器:Cplex 和 Gurobi 也有 Matlab API。
  • 是否有适用于 MATLAB 的免费求解器(高质量)?谢谢。
  • YALMIP 是一个不错的工具箱,它支持 CBC(一个非常好的开源 MIP 求解器)。上述模型上带有 CBC 的结果是here
  • 抱歉,我不太了解那个工具。
【解决方案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 的数量。为此,我们首先将可能的索引随机化,然后计算并排序每个索引的汉明权重(二进制表示中 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 比 -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 的分布情况都会对算法的性能产生很大影响。

【讨论】:

    【解决方案3】:

    遗憾的是,这个问题超出了常规(连续)优化的范围。你的问题,可以参数化如下:

    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,也就是说要找到全局最小化器,你必须在250000个可能性中遍历n个所有可能的组合,这等于二项式系数( 250000 n),等于250000!/(n!*(250000-n)!)...

    祝你好运... ;)

    编辑

    如果您要启发式地解决这个问题,因为我认为您将需要一个解决方案,请使用启发式 here 而不是您的方法。

    【讨论】:

    • 我不认为'NP-hard => 你必须通过所有组合'的说法是正确的。例如。 MIP 求解器不会访问所有可能的解决方案。
    • 是的,我写得很快,但是整数编程通过归约到顶点覆盖问题是 NP 困难的。但是你说得对,那里的大问题解决复杂性似乎比那里的详尽搜索要小......到时候会编辑答案!
    【解决方案4】:

    由于您的回复似乎表明您有兴趣找到更大的序列(更大的 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 行后收敛到一个真正的最小值(即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;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-23
      • 2012-02-16
      相关资源
      最近更新 更多