我有一个由N个不同长度的间隔组成的排序数组。我正在用蓝色/绿色交替绘制这些间隔。
我正试图找到一种方法或算法来对间隔数组进行“下采样”,以生成外观上相似的图,但元素更少。
理想情况下,我可以编写一些函数,在该函数中可以将目标输出间隔数作为参数传递。输出长度只需接近目标即可。
input = [
[0, 5, "blue"],
[5, 6, "green"],
[6, 10, "blue"],
// ...etc
]
output = downsample(input, 25)
// [[0, 10, "blue"], ... ]
下面是我要完成的任务的图片。在此示例中,输入大约有250个间隔,输出大约有25个间隔。输入长度可以有很大的不同。
答案 0 :(得分:7)
下面是我最初删除的原始帖子,因为在显示方程式时遇到了问题,而且我对它是否真的不那么充满信心。但是后来,我发现可以使用DP(动态编程)有效地解决我描述的优化问题。
所以我做了一个示例C ++实现。结果如下:
这是一个live demo,您可以在浏览器中使用它(请确保浏览器支持WebGL2,例如Chrome或Firefox)。加载页面需要一点时间。
这是C ++实现:link
结果证明,所提出的解决方案具有以下不错的特性-我们可以轻松控制 F 1 和 F 2 < / sub> 。只需将成本函数更改为 F(α) = F 1 + αF 2 >,其中α> = 1.0是免费参数。 DP算法保持不变。
以下是使用相同数量的间隔 N 的不同α值的一些结果:
Live demo (需要WebGL2)
可以看出,较高的α表示覆盖原始输入间隔更为重要,即使这意味着覆盖其间的更多背景。
尽管已经提出了一些好的算法,但我想提出一种稍微不同寻常的方法-将任务解释为优化问题。虽然,我不知道如何有效地解决优化问题(甚至根本无法在合理的时间内解决该问题),但对于纯粹作为一个概念的人来说,它可能是有用的。
首先,在不失一般性的前提下,让我们将 blue 颜色声明为背景。我们将在其上方绘制 N 绿色间隔( N 是OP描述中为downsample()
函数提供的数字) 。 i th 间隔由其起始坐标0 <= x i
我们还定义数组 G(x)为输入数据中间隔[0,x)中的绿色单元格的数量强>。该数组可以很容易地预先计算。我们将使用它来快速计算任意间隔[x,y]中的绿色单元格的数量-即: G(y)-G(x)。
我们现在可以针对优化问题介绍成本函数的第一部分:
F 1 越小,我们生成的间隔就越好覆盖输入间隔,因此我们将正在搜索将其最小化的 x i , w i 。理想情况下,我们希望 F 1 = 0,这意味着间隔不覆盖背景的任何(这当然是不可能的,因为< strong> N 小于输入间隔。
但是,此函数不足以描述问题,因为很明显,我们可以通过以下空白间隔将其最小化: F 1 (x,0) = 0。相反,我们希望从输入间隔中覆盖尽可能多的内容。让我们介绍与该要求相对应的成本函数的第二部分:
F 2 越小,覆盖的输入间隔越多。理想情况下,我们希望 F 2 = 0,这意味着我们覆盖了所有输入矩形。但是,最小化 F 2 与最小化 F 1 竞争。
最后,我们可以陈述我们的优化问题:找到使最小化的 x i , w i > F = F 1 + F 2
如何解决这个问题?不确定。也许可以使用一些元启发式方法进行全局优化,例如模拟退火或差分进化。这些通常易于实现,尤其是对于这种简单的成本函数。
最好的情况是存在某种有效地解决它的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)
您可以执行以下操作:
[a[0], a[1], a[2], ..., a[n-1]]
。在您的示例中,该数组为[0, 5, 6, 10, ... ]
。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, ... ]
并通过它搜索最小值。(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]
,因为两对相同颜色的间隔现已合并为两个较大的间隔。因此,在此步骤之后,蓝色和绿色间隔的数量每减少一个。您执行了第2步以减少“色偏”,因为在第3步中,左间隔向右移动a[k+2]-a[k+1]
,右间隔向左移动a[k+1]-a[k]
。这些距离的总和a[k+2]-a[k]
可以看作是您要在整个图片中引入的变化量。
此方法的主要优点:
0/1
,“是/否”答案)时,这反映了一个非常普遍的用例。答案 3 :(得分:1)
我建议您使用Haar wavelet。这是一种非常简单的算法,通常用于为网站上的大图片提供渐进式加载功能。
Here,您可以了解它如何与2D功能配合使用。那是您可以使用的。 las,该文档使用乌克兰语,但是使用C ++代码,因此可读性:)
This document提供了3D对象的示例:
您可以在Wavelets for Computer Graphics: A Primer Part 1y中找到有关如何使用Haar小波压缩的伪代码。
答案 4 :(得分:1)
这是动态编程的另一种尝试,与Georgi Gerganov的尝试略有不同,尽管尝试制定动态程序的想法可能是受他的回答启发的。无论是实现还是概念都不能保证是合理的,但我确实提供了带有可视示例的代码草图:)
在这种情况下,搜索空间不依赖于单位总宽度,而是依赖于间隔数。现在是O(N * n^2)
时间和O(N * n)
空间,其中N
和n
分别是目标间隔和给定数量的(绿色)间隔,因为我们假设任何新选择的绿色间隔必须以两个绿色间隔为边界(而不是任意延伸到背景中)。
该想法还利用前缀和的想法来计算具有多数元素的游程。我们在看到目标元素时加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);