Algorithm 二元向量的条件抽样(?)

Algorithm 二元向量的条件抽样(?),algorithm,random,Algorithm,Random,我正试图为我的问题找到一个名字,这样我就不必在编写解决它的算法时重新发明轮子 我有2000个二进制(行)向量,我需要从中选择500个。在选取的样本中,我做列和,我希望我的样本尽可能接近列和的预定义分布。我将处理20到60个专栏 一个很小的例子: 在向量之外: 110 010 011 110 100 我需要选择2来获取列和2,1,0。解决方案(在这种情况下是准确的)是 到目前为止我的想法 人们可能会称之为一个二进制多维背包,但我没有找到任何算法 线性规划可能会有所帮助,但我需要一些逐步的解释,

我正试图为我的问题找到一个名字,这样我就不必在编写解决它的算法时重新发明轮子

我有2000个二进制(行)向量,我需要从中选择500个。在选取的样本中,我做列和,我希望我的样本尽可能接近列和的预定义分布。我将处理20到60个专栏

一个很小的例子:

在向量之外:

110
010
011
110
100
我需要选择2来获取列和
2,1,0
。解决方案(在这种情况下是准确的)是

到目前为止我的想法
  • 人们可能会称之为一个二进制多维背包,但我没有找到任何算法
  • 线性规划可能会有所帮助,但我需要一些逐步的解释,因为我没有这方面的经验
  • 由于精确解并不总是可行的,类似于模拟退火的方法可以很好地工作
  • 考虑到CSP应该比ILP快得多,想到了一种使用约束求解器(constraint Solver)的黑客方法——首先将约束设置得很紧,然后逐渐放松,直到找到解决方案为止

首先,我将对这个问题做出一些假设:

  • 无论所选解决方案的列和在目标上还是在目标下,其权重相同
  • 第一列、第二列和第三列的和在解决方案中的权重相等(即,如果有一个解决方案,而第一列和的权重为1,第三列和的权重为1,则该解决方案的权重相等)
我能想到的最接近这个问题是背包问题,它本身可以被认为是背包问题的一个特例

然而,这两个问题都是NP完全问题。这意味着没有多项式时间算法可以解决这些问题,即使很容易验证解决方案

如果我是你,这个问题的两个最有效的解决方案是
线性规划
机器学习

根据您在此问题中优化的列数,使用线性规划,您可以控制您希望解决方案的微调程度,以换取时间。你应该仔细阅读,因为这是相当简单和有效的

使用机器学习,您需要大量的数据集(向量集和解决方案集)。你甚至不需要指定你想要什么,很多机器学习算法通常可以根据你的数据集推断出你想要它们优化什么


这两种解决方案各有利弊,您应该根据具体情况和问题集自行决定使用哪种解决方案。

我具体而实用的建议(如果近似保证适用于您)是采用最大熵法(在Boyd和Vandenberghe的《凸优化》一书的第7章中;您可能会发现使用您最喜欢的搜索引擎的几种实现)找到行索引上的最大熵概率分布,这样(1)没有行索引的可能性大于1/500(2)所选行向量的期望值是预定义分布的1/500。给定此分布,以500倍于其分布可能性的概率独立选择每一行,这将平均为500行。如果需要正好500行,请重复此操作,直到正好得到500行(由于浓度限制,不应进行太多尝试)。

这绝对可以建模为(整数!)线性规划(许多问题都可以)。一旦有了它,您可以使用诸如
lpsolve
之类的程序来解决它

我们将
向量i建模为
xi
,它可以是
0
1

然后,对于每个列c,我们都有一个约束:

全部总和(x_i*c列i的值)=c列的目标

以您为例,在这种情况下,您可能会看到:

min: ;
+x1 +x4 +x5 >= 2;
+x1 +x4 +x5 <= 2;
+x1 +x2 +x3 +x4 <= 1;
+x1 +x2 +x3 +x4 >= 1;
+x3 <= 0;
+x3 >= 0;
bin x1, x2, x3, x4, x5;
min:;
+x1+x4+x5>=2;

+x1+x4+x5如果你对基于启发式的搜索方法很满意,这里有一个

检查列表,找出每个位字符串和目标之间的数字差异的最小平方和。例如,如果我们在寻找2,1,0,并且我们的得分为0,1,0,我们将按以下方式进行:

以数字差异为例: 2,0,1

将数字差平方: 4,0,1

总数: 五,

作为补充说明,在进行启发式搜索时,在评分时对差异进行平方是一种常见的方法。在您的情况下,这是有意义的,因为第一个数字为1英寸的位字符串对我们来说更有趣。在您的情况下,此简单算法将首先选择110,然后选择100,这将是最佳解决方案


在任何情况下,都可以对此进行一些优化,如果您正在寻找这种方法,我将在这里发布它们,但这是算法的核心。

您有一个给定的目标二进制向量。您希望从
N
中选择与目标和最接近的
M
向量。让我们说y你用欧几里得距离来衡量一个选择是否比另一个好

如果你想要一个精确的和,看看k-和问题,它是的一个推广。这个问题比的更难,因为你想要一个精确数量的元素添加到目标值。有,但这意味着在你的情况下超过2000^250*7.6>10^826个运算(在有利的情况下,向量运算的代价是1)

第一个结论:不要试图得到一个精确的结果,除非你的向量有一些可以降低复杂性的特征

以下是一种方法:

  • 按1的数量对向量进行排序:
    111…
    首先,
    000…
    最后
  • 使用子集合求和
  • 你有一个关于
    K
    元素的近似解。由于元素的顺序(大元素排在第一位),
    Kmin: ;
    +x1 +x4 +x5 >= 2;
    +x1 +x4 +x5 <= 2;
    +x1 +x2 +x3 +x4 <= 1;
    +x1 +x2 +x3 +x4 >= 1;
    +x3 <= 0;
    +x3 >= 0;
    bin x1, x2, x3, x4, x5;
    
    import random
    
    def distance(x, y):
        return abs(x-y)
    
    def show(ls):
        if len(ls) < 10:
            return str(ls)
        else:
            return ", ".join(map(str, ls[:5]+("...",)+ls[-5:]))
    
    def find(is_xs, target):
        # see https://en.wikipedia.org/wiki/Subset_sum_problem#Pseudo-polynomial_time_dynamic_programming_solution
        S = [(0, ())] # we store indices along with values to get the path
        for i, x in is_xs:
            T = [(x + t, js + (i,)) for t, js in S]
            U = sorted(S + T)
            y, ks = U[0]
            S = [(y, ks)]
    
            for z, ls in U:
                if z == target: # use the euclidean distance here if you want an approximation
                    return ls
    
                if z != y and z < target:
                    y, ks = z, ls
                    S.append((z, ls))
    
        ls = S[-1][1] # take the closest element to target
        return ls
    
    N = 2000
    M = 500
    target = 1000
    
    xs = [random.randint(0, 10) for _ in range(N)]
    print ("Take {} numbers out of {} to make a sum of {}", M, xs, target)
    
    xs = sorted(xs, reverse = True)
    is_xs = list(enumerate(xs))
    
    print ("Sorted numbers: {}".format(show(tuple(is_xs))))
    
    ls = find(is_xs, target)
    print("FIRST TRY: {} elements ({}) -> {}".format(len(ls), show(ls), sum(x for i, x in is_xs if i in ls)))
    
    splits = 0
    while len(ls) < M:
        first_x = xs[ls[0]]
    
        js_ys = [(i, x) for i, x in is_xs if i not in ls and x != first_x]
        replace = find(js_ys, first_x)
        splits += 1
        if len(replace) < 2 or len(replace) + len(ls) - 1 > M or sum(xs[i] for i in replace) != first_x:
            print("Give up: can't replace {}.\nAdd the lowest elements.")
            ls += tuple([i for i, x in is_xs if i not in ls][len(ls)-M:])
            break
    
        print ("Replace {} (={}) by {} (={})".format(ls[:1], first_x, replace, sum(xs[i] for i in replace)))
        ls = tuple(sorted(ls[1:] + replace)) # use a heap?
        print("{} elements ({}) -> {}".format(len(ls), show(ls), sum(x for i, x in is_xs if i in ls)))
    
    print("AFTER {} splits, {} -> {}".format(splits, ls, sum(x for i, x in is_xs if i in ls)))