程序设计实习(二,三)

非均匀采样构造数据摘要问题

例如,有一个序列 \(a\),我们只能采样,要求估计 \(\sum a_i\)。有一个方法是均匀随机采样 \(k\) 个点求平均数,然后再用它们来估计总和。这的确是无偏估计(期望是对的),然而在极端情况下,比如 \(a_0=\cdots=a_{n-1}=0,a_n=1\) 的时候,返回的结果却很有可能和期望相差较大。这时我们可能就不能均匀随机了。

或者另一种说法是,对于这组数据想要让答案差比较大的概率被控制,需要进行 \(\Omega(n)\) 次采样。

一般地,我们希望返回的答案是无偏估计,并且有大概率和真实的答案相差很小。

  • 1-median 的数据摘要

这个问题是这样的,输出 \(n\) 个整数 \(A=(a_1,\cdots,a_n)\),预处理若干信息,要求高效回答:给定整数 \(q\),查询

\[\text{cost}(A,q):=\sum_{i=1}^n|a_i-q| \]

当然我们都知道可以二分之类的,不过如果我把他改成高维的欧式距离阁下又当如何应对呢(笑)

对于这个问题,均匀随机采样是没办法保证大概率和期望相差较小的。例子就是前面那个数据。

摘要数据结构:我们的算法是取一个子集 \(S\subseteq A\),这是 \(A\) 中元素的一个“摘要”。以及权重函数 \(w:S\to \mathbb R\),使得

\[\forall q,\sum_{x\in S}w(x)|x-q|\in (1\pm \epsilon)\text{cost}(A,q) \]

也就是任意的询问点 \(q\) 均满足这个采样和真实答案的相对误差不超过 \(\epsilon\)


我们考虑把我们卡掉的数据,它比较的两极分化,于是我们考虑能不能把数据划分成若干部分,每一部分都相对均匀。在分布的比较均匀的时候,可以想象到均匀采样的效果是比较好的。

假如 \(A=(a_1,\cdots,a_n)\) 满足:存在某个中心点 \(c\),使得每个点到 \(c\) 的距离都在 \([r,2r]\) 之内,那么对于任意询问点 \(q\),它和这个点集内部的点 \(a\) 的距离设为 \(X_a=|q-a|\)(这是一维的情形,高维就改成相应的 \(L_2\) 距离),那么容易证明对任意 \(a,b\),都有 \(|X_a-X_b|\le 4r\)(三角形不等式)。

那么现在相当于,有一系列数据,它们的极差不超过 \(4r\);在此前提下我们进行均匀随机采样,假设我们希望相对误差不超过 \(\epsilon\),对于 \(\delta>0\),需要采样多少次才能使得失败概率 \(<\delta\) 呢?

  • Hoeffding inequality

对于独立随机变量 \(X_1,\cdots,X_m\in [s,t]\),令 \(X=\sum X_i\),有

\[\text{Pr}[X-\mathbb E[X]\ge z]\le 2\exp\left(-\frac{2z^2}{m(t-s)^2}\right) \]

那么这里我们的 \(\mathbb E[X]\) 就是真实的 \(\text{cost}(A,q)\)

假设取样个数是 \(n\),总点数为 \(|P|\),那么所有随机变量 \(X_i\) 都定义为随机取一个点和查询点 \(q\) 之间的距离,乘上它的权重 \(\frac{|P|}{n}\)。对于 \(s,t\),我们知道所有点到 \(q\) 之间的距离极差不超过 \(4r\),即所有 \(X_i\) 都被控制在一个大小为 \(\frac{4r|P|}{n}\) 的区间内。也就是 \(t-s\le \frac{4r|P|}{n}\)。取 \(z=\epsilon|P|r\),我们就得到

\[\text{Pr}[X-\mathbb E[X]\ge \epsilon|P|r]\le 2\exp\left(-\frac{2n\epsilon^2|P|^2r^2}{16r^2|P|^2}\right)=2e^{-\Theta(\epsilon^2n)} \]

希望 \(e^{-\Theta(\epsilon^2n)}=\delta\),那么有 \(n=\log(1/\delta)\cdot(1/\epsilon^2)\)

现在我们考虑对于一般的点集,考虑取 \(r_0\),然后把点集划分为距离在 \([r_0\cdot 2^i,r_0\cdot 2^{i+1})\)\(\Theta(\log (V/r_0))\) 个点集 \(P_i\),在每个 \(P_i\) 上均匀取样,那么每个 \(P_i\) 上只要取 \(O(\log(1/\delta)\cdot(1/\epsilon^2))\) 个点,就可以让这个 \(P_i\) 上产生的误差不超过 \(r|P_i|\epsilon\);我们取中心点 \(c\),使得 \(\text{cost}(A,c)\) 是最小的,那么总误差就满足:

\[\forall q,\sum_{i}r|P_i|\epsilon\le \epsilon\sum_ir|P_i|\le \epsilon\cdot\text{cost}(A,c)\le \epsilon\cdot \text{cost}(A,q) \]

综上我们得到了这样的算法:

  • \(r_0\),以及全局重心 \(c\),把点集划分为若干部分,第 \(i\) 部分距离 \(c\) 的距离在 \([r_0\cdot 2^i,r_0\cdot 2^{i+1})\) 之间
  • 对每一部分分别独立取样 \(O(1/\epsilon^2\cdot\log(1/\delta))\) 个点
  • 对于查询,在每一部分内分别对取样点计算贡献

当然,这里由于我们有 \(Q\) 次查询以及 \(\log (V/r_0)\) 个集合,我们的失败概率是多个结合起来,不过根据 union bound 大概也只需要把 \(\delta\) 再除掉 \(1/\log(Q\log(V/r_0))\) 就好了。

当然高维的情况计算最优 \(c\) 可能没那么容易,不过我们可以直接随机取 \(c\),期望也是一个 \(2\) 近似。

推广至一般的 \(k\) median,即每次查询给定一个集合 \(|C|=k\),查询

\[\text{cost}(A,C)=\sum_{x\in A}\min_{y\in C}\|x-y\| \]

这时也可以构造类似的摘要:先随机取一个 \(C\) 使得 \(\text{cost}(A,C)\) 较小,然后根据每个点距离 \(C\) 中哪个点最近分成 \(k\) 类,每一类之内再和上面一样构造 \(m\log(V/r_0)\) 大小的摘要集即可。

哈希与 Count-min Sketch

例如现在有这样的问题:要求维护一个数据结构,支持插入/删除一个 \(x\),或者查询 \(x\) 出现了多少次;当然借助比较传统的哈希表大概能做到线性于不同元素个数的空间,以及几乎 \(O(1)\) 的查询,不过线性空间还是太大了。

考虑这样一个算法,对于询问 \(x\),若答案为 \(C_x\),他能以大概率返回一个值 \(C'_x\) 满足 \(C_x\le C'_x\le C_x+\epsilon N\),其中 \(N\) 是插入的次数。具体来说,我们就取一个 \(m\),设 \(x\) 的范围是 \([n]\),构造一个 \([n]\to [m]\) 的随机哈希 \(h\),然后每次就把 \(C[h(x)]\) 加上 \(1\) 或者减去 \(1\)

那么如果哈希没有冲突自然是最好的,得到的答案就是真实值;如果冲突了那么也只会变大,有

\[\mathbb E[C[h(x)]]=\sum_y\text{Pr}[h(x)=h(y)]C_y\le C_x+N/m \]

考虑随机变量 \(X=C[h(x)]-C_x\),有 \(\mathbb E[X]\le N/m\),根据 Markov Inequality:

\[\text{Pr}[X\ge \epsilon N]\le\mathbb E[X]/(\epsilon N)\le \frac{1}{m\epsilon} \]

\(m=\frac{2}{\epsilon}\),即可得到一个以 \(\frac{1}{2}\) 概率输出一个 \(C[h(x)]\) 满足 \(C_x\le C[h(x)]\le C_x+\epsilon N\) 的算法。

进一步重复 \(T\) 遍取最小值(因为答案总是偏大),即可将正确率提升至 \(1-0.5^T\)

\(T=\Theta(\log n)\) 时就达到 \(1-1/\text{poly}(n)\)

  • 近似 \(k\)-HH

实际应用经常遇见这样的问题:有若干数据,你需要找到其中出现次数相对比较多的数据。这个问题叫做 heavy-hitter

具体来说,会有一个 \(1\le k\le N\),你需要返回给定的 \(N\) 个数中出现至少 \(N/k\) 次的数据。

当然由于是大数据算法,我们需要实现一个数据流算法,也就是只能一个一个访问元素,访问之后立刻扔掉,并且动态维护一些东西。

首先这种问题我们肯定是少不了一个桶的,然而空间不能和元素个数相关,于是我们考虑用前面的 count-min sketch hash,把它当一个哈希的黑盒来用。

由于这个有误差,此时我们稍微放宽一些条件:我们需要必须能返回所有出现至少 \(N/k\) 的元素,且有可能返回一些其他元素,它们的出现次数不少于 \(N/k-\epsilon N\) 次。

剩下的是一些数据结构方面的维护,一般 \(k\) 是比较小的,我们再维护当前总元素个数 \(M\),以及所有 count-min sketch 返回值至少为 \(M/k\) 的元素。每次插入的时候先 check 新元素是否符合要求,再遍历当前列表把不符合的删掉。单次的复杂度是 \(O(k)\) 的,这个已经差不多可以接受了。

posted @ 2025-03-12 10:51  云浅知处  阅读(97)  评论(0)    收藏  举报