对间隔数组进行下采样的算法

时间:2018-12-30 14:34:21

标签: algorithm intervals downsampling

我有一个由N个不同长度的间隔组成的排序数组。我正在用蓝色/绿色交替绘制这些间隔。

我正试图找到一种方法或算法来对间隔数组进行“下采样”,以生成外观上相似的图,但元素更少。

理想情况下,我可以编写一些函数,在该函数中可以将目标输出间隔数作为参数传递。输出长度只需接近目标即可。

input = [
  [0, 5, "blue"],
  [5, 6, "green"],
  [6, 10, "blue"],
  // ...etc
]

output = downsample(input, 25)
// [[0, 10, "blue"], ... ]

下面是我要完成的任务的图片。在此示例中,输入大约有250个间隔,输出大约有25个间隔。输入长度可以有很大的不同。

enter image description here

5 个答案:

答案 0 :(得分:7)

更新1:

下面是我最初删除的原始帖子,因为在显示方程式时遇到了问题,而且我对它是否真的不那么充满信心。但是后来,我发现可以使用DP(动态编程)有效地解决我描述的优化问题。

所以我做了一个示例C ++实现。结果如下:

example1 example2 Gif

这是一个live demo,您可以在浏览器中使用它(请确保浏览器支持WebGL2,例如Chrome或Firefox)。加载页面需要一点时间。

这是C ++实现:link


更新2:

结果证明,所提出的解决方案具有以下不错的特性-我们可以轻松控制 F 1 F 2 < / sub> 。只需将成本函数更改为 F(α) = F 1 + αF 2 >,其中α> = 1.0是免费参数。 DP算法保持不变。

以下是使用相同数量的间隔 N 的不同α值的一些结果:

different_alpha_values

Live demo (需要WebGL2)

可以看出,较高的α表示覆盖原始输入间隔更为重要,即使这意味着覆盖其间的更多背景。


原始帖子

尽管已经提出了一些好的算法,但我想提出一种稍微不同寻常的方法-将任务解释为优化问题。虽然,我不知道如何有效地解决优化问题(甚至根本无法在合理的时间内解决该问题),但对于纯粹作为一个概念的人来说,它可能是有用的。


首先,在不失一般性的前提下,让我们将 blue 颜色声明为背景。我们将在其上方绘制 N 绿色间隔( N 是OP描述中为downsample()函数提供的数字) 。 i th 间隔由其起始坐标0 <= x i max < / sub>和宽度 w i > = 0(x max 是输入的最大坐标)。

我们还定义数组 G(x)输入数据中间隔[0,x)中的绿色单元格的数量。该数组可以很容易地预先计算。我们将使用它来快速计算任意间隔[x,y]中的绿色单元格的数量-即: G(y)-G(x)

我们现在可以针对优化问题介绍成本函数的第一部分:

F_1

F 1 越小,我们生成的间隔就越好覆盖输入间隔,因此我们将正在搜索将其最小化的 x i w i 。理想情况下,我们希望 F 1 = 0,这意味着间隔不覆盖背景的任何(这当然是不可能的,因为< strong> N 小于输入间隔。

但是,此函数不足以描述问题,因为很明显,我们可以通过以下空白间隔将其最小化: F 1 (x,0) = 0。相反,我们希望从输入间隔中覆盖尽可能多的内容。让我们介绍与该要求相对应的成本函数的第二部分:

F_2

F 2 越小,覆盖的输入间隔越多。理想情况下,我们希望 F 2 = 0,这意味着我们覆盖了所有输入矩形。但是,最小化 F 2 与最小化 F 1 竞争。

最后,我们可以陈述我们的优化问题:找到使最小化的 x i w i > F = F 1 + F 2

F


如何解决这个问题?不确定。也许可以使用一些元启发式方法进行全局优化,例如模拟退火或差分进化。这些通常易于实现,尤其是对于这种简单的成本函数。

最好的情况是存在某种有效地解决它的DP算法,但这不太可能。

答案 1 :(得分:1)

我建议使用K均值,它是一种用于对数据进行分组的算法(此处更详细的解释:https://en.wikipedia.org/wiki/K-means_clustering和此处https://scikit-learn.org/stable/modules/generated/sklearn.cluster.KMeans.html) 这将简要说明该函数的外观,希望对您有所帮助。

from sklearn.cluster import KMeans
import numpy as np

def downsample(input, cluster = 25):

    # you will need to group your labels in a nmpy array as shown bellow
    # for the sake of example I will take just a random array
    X = np.array([[1, 2], [1, 4], [1, 0],[4, 2], [4, 4], [4, 0]])

    # n_clusters will be the same as desired output
    kmeans = KMeans(n_clusters= cluster, random_state=0).fit(X)

    # then you can iterate through labels that was assigned to every entr of your input
    # in our case the interval
    kmeans_list = [None]*cluster
    for i in range(0, X.shape[0]):
        kmeans_list[kmeans.labels_[i]].append(X[i])

    # after that you will basicly have a list of lists and every inner list will contain all points that corespond to a
    # specific label

    ret = [] #return list
    for label_list in kmeans_list:
        left = 10001000 # a big enough number to exced anything that you will get as an input
        right = -left   # same here
        for entry in label_list:
            left = min(left, entry[0])
            right = max(right, entry[1])
        ret.append([left,right])

    return ret

答案 2 :(得分:1)

您可以执行以下操作:

  1. 写出将整个条形划分为间隔的点作为数组[a[0], a[1], a[2], ..., a[n-1]]。在您的示例中,该数组为[0, 5, 6, 10, ... ]
  2. 计算双间隔长度a[2]-a[0], a[3]-a[1], a[4]-a[2], ..., a[n-1]-a[n-3]并找到其中的最小值。设为a[k+2]-a[k]。如果存在两个或多个相等的长度最小值,请随机选择其中之一。在您的示例中,您应该获取数组[6, 5, ... ]并通过它搜索最小值。
  3. 交换间隔(a[k], a[k+1])(a[k+1], a[k+2])。基本上,您需要分配a[k+1]=a[k]+a[k+2]-a[k+1]来保留长度,并在此之后从数组中删除点a[k]a[k+2],因为两对相同颜色的间隔现已合并为两个较大的间隔。因此,在此步骤之后,蓝色和绿色间隔的数量每减少一个。
  4. 如果对当前的间隔数感到满意,请结束该过程,否则转到步骤1。

您执行了第2步以减少“色偏”,因为在第3步中,左间隔向右移动a[k+2]-a[k+1],右间隔向左移动a[k+1]-a[k] 。这些距离的总和a[k+2]-a[k]可以看作是您要在整个图片中引入的变化量。


此方法的主要优点:

  1. 这是简单
  2. 它对两种颜色均不提供偏好。您无需将一种颜色指定为背景,将另一种颜色指定为绘画颜色。该图片可以同时被视为“蓝绿色”和“蓝绿色”。当两种颜色仅描述某个过程在时间或空间上扩展的两个相反状态(例如位0/1,“是/否”答案)时,这反映了一个非常普遍的用例。
  3. 它始终保持颜色之间的平衡,即在缩小过程中每种颜色的间隔之和保持不变。因此,图片的总亮度不会改变。重要的是,在某些情况下,总亮度可以被视为“完整性的指标”。

答案 3 :(得分:1)

我建议您使用Haar wavelet。这是一种非常简单的算法,通常用于为网站上的大图片提供渐进式加载功能。

Here,您可以了解它如何与2D功能配合使用。那是您可以使用的。 las,该文档使用乌克兰语,但是使用C ++代码,因此可读性:)

enter image description here

This document提供了3D对象的示例:

enter image description here

您可以在Wavelets for Computer Graphics: A Primer Part 1y中找到有关如何使用Haar小波压缩的伪代码。

答案 4 :(得分:1)

这是动态编程的另一种尝试,与Georgi Gerganov的尝试略有不同,尽管尝试制定动态程序的想法可能是受他的回答启发的。无论是实现还是概念都不能保证是合理的,但我确实提供了带有可视示例的代码草图:)

在这种情况下,搜索空间不依赖于单位总宽度,而是依赖于间隔数。现在是O(N * n^2)时间和O(N * n)空间,其中Nn分别是目标间隔和给定数量的(绿色)间隔,因为我们假设任何新选择的绿色间隔必须以两个绿色间隔为边界(而不是任意延伸到背景中)。

该想法还利用前缀和的想法来计算具有多数元素的游程。我们在看到目标元素时加1(在本例中为绿色),而对其他元素减1(该算法也适用于具有并行前缀和跟踪的多个元素)。 (我不确定总是要确保将候选间隔限制为目标颜色的大部分,但是根据期望的结果,这可能是一种有用的启发式方法。它也是可调整的-我们可以轻松地对其进行调整以检查是否存在其他差异小于1/2。)

Georgi Gerganov的程序寻求最小化的地方,而这个动态的程序寻求最大化两个比率的地方。令h(i, k)代表绿色间隔的最佳序列,直到i个给定间隔为止,利用k间隔,其中每个间隔都可以拉回到某个先前绿色间隔的左边缘。我们推测

h(i, k) = max(r + C*r1 + h(i-l, k-1))

其中,在当前候选间隔中,r是绿色与拉伸长度的比率,r1是绿色与总给定绿色的比率。 r1乘以可调整的常数,可以使绿色覆盖物的重量更大。 l是延伸的长度。

JavaScript代码(用于调试,它包括一些额外的变量和日志行):

function rnd(n, d=2){
  let m = Math.pow(10,d)
  return Math.round(m*n) / m;
}

function f(A, N, C){
  let ps = [[0,0]];
  let psBG = [0];
  let totalG = 0;
  A.unshift([0,0]);

  for (let i=1; i<A.length; i++){
    let [l,r,c] = A[i];
    
    if (c == 'g'){
      totalG += r - l;
      let prevI = ps[ps.length-1][1];
      let d = l - A[prevI][1];
      let prevS = ps[ps.length-1][0];
      ps.push(
        [prevS - d, i, 'l'],
        [prevS - d + r - l, i, 'r']
      );
      psBG[i] = psBG[i-1];

    } else {
      psBG[i] = psBG[i-1] + r - l;
    }
  }
  //console.log(JSON.stringify(A));
  //console.log('');
  //console.log(JSON.stringify(ps));
  //console.log('');
  //console.log(JSON.stringify(psBG));

  let m = new Array(N + 1);
  m[0] = new Array((ps.length >> 1) + 1);
  for (let i=0; i<m[0].length; i++)
    m[0][i] = [0,0];

  // for each in N
  for (let i=1; i<=N; i++){
    m[i] = new Array((ps.length >> 1) + 1);
    for (let ii=0; ii<m[0].length; ii++)
      m[i][ii] = [0,0];

    // for each interval
    for (let j=i; j<m[0].length; j++){
      m[i][j] = m[i][j-1];

      for (let k=j; k>i-1; k--){
        // our anchors are the right
        // side of each interval, k's are the left
        let jj = 2*j;
        let kk = 2*k - 1;

        // positive means green
        // is a majority
        if (ps[jj][0] - ps[kk][0] > 0){
          let bg = psBG[ps[jj][1]] - psBG[ps[kk][1]];
          let s = A[ps[jj][1]][1] - A[ps[kk][1]][0] - bg;
          let r = s / (bg + s);
          let r1 = C * s / totalG;
          let candidate = r + r1 + m[i-1][j-1][0];
          
          if (candidate > m[i][j][0]){
            m[i][j] = [
              candidate,
              ps[kk][1] + ',' + ps[jj][1],
              bg, s, r, r1,k,m[i-1][j-1][0]
            ];
          }
        }
      }
    }
  }
  /*
  for (row of m)
    console.log(JSON.stringify(
      row.map(l => l.map(x => typeof x != 'number' ? x : rnd(x)))));
  */
  let result = new Array(N);
  let j = m[0].length - 1;
  for (let i=N; i>0; i--){
    let [_,idxs,w,x,y,z,k] = m[i][j];
    let [l,r] = idxs.split(',');
    result[i-1] = [A[l][0], A[r][1], 'g'];
    j = k - 1;
  }

  return result;
}

function show(A, last){
  if (last[1] != A[A.length-1])
    A.push(last);

  let s = '';
  let j;

  for (let i=A.length-1; i>=0; i--){
    let [l, r, c] = A[i];
    let cc = c == 'g' ? 'X' : '.';
    for (let j=r-1; j>=l; j--)
      s = cc + s;
    if (i > 0)
      for (let j=l-1; j>=A[i-1][1]; j--)
        s = '.' + s
  }

  for (let j=A[0][0]-1; j>=0; j--)
    s = '.' + s

  console.log(s);
  return s;
}

function g(A, N, C){
  const ts = f(A, N, C);
  //console.log(JSON.stringify(ts));
  show(A, A[A.length-1]);
  show(ts, A[A.length-1]);
}


var a = [
  [0,5,'b'],
  [5,9,'g'],
  [9,10,'b'],
  [10,15,'g'],
  [15,40,'b'],
  [40,41,'g'],
  [41,43,'b'],
  [43,44,'g'],
  [44,45,'b'],
  [45,46,'g'],
  [46,55,'b'],
  [55,65,'g'],
  [65,100,'b']
];

// (input, N, C)
g(a, 2, 2);
console.log('');
g(a, 3, 2);
console.log('');
g(a, 4, 2);
console.log('');
g(a, 4, 5);