3 问题陈述
在概述算法之前,先对问题进行形式化定义。当提到近似依赖关系时,我们需要量化近似程度。为此,错误率忽略了自反元组对,我们对其进行了轻微改动。
定义1(AFD/AUCC错误率):给定数据集和一个候选AFD,它的错误率为。
类似的,AUCC的错误率为。
5 错误率估计
5.1 PLI缓存
positionList indices(PLIs) 又名精简划分 (stripped partitions)
定义2(PLI):给定模式R的一个关系实例r,一个簇(cluster)指的是再属性集合X上值相同的元组集合。X的PLI指的是除了大小为1的所有簇的集合。PLI的大小指的是包含的元组个数。
例2:属性Last_name的PLI包含簇{1,4}和{3,5}分别对应值Smith和Miller。PLI不包含大小为1的簇{2}。
很多现有工作(例如TANE)都使用了PLIs这一数据结构,原因如下:
- PLIs允许在数据上创建集中的样本(此点只针对Pyro算法)
- PLIs占用内存少,因为其只存储元组的索引,
- PLIs利于计算
g1错误 XY的PLIs可以简单通过X和Y的PLIs进行交运算得到。
例3:PLI的交运算。从表1可得First_name的PLIs为,Last_name的PLIs为。
第一步,把转化为属性向量。属性向量的每一维表示该元组在PLIs中属于的簇编号(从1开始),0表示该元组的属性值只出现了一次,因此不输入PLIs中的任何簇。
第二步,探测中的每个元组并对其分组,第个元组的group key是。对于元组的group key为1,的group key为2,即。大小大于1的group组成了新的PLI,即。因为只有和在Last_name和First_name上值都相等。
这两步操作其实就是对和按簇进行了交运算。
PLIs的交运算计算代价昂贵。因此,Pyro把计算好的PLIs存入PLI缓存方便后续使用。缓存PLIs在DUCC算法中被提出并被DFD算法采用,然后,并没有给出PLI缓存的数据结构。已经被证明,FDEP算法的集合树适用于索引和查找PLIs。
如图2所示,Pyro的PLI缓存借鉴了FDEP的策略:本质上是个前缀树将属性集合和相应的缓存的PLI关联起来。当Pyro请求某个PLI例如时,可能其不在缓存中,计算时需要遵循以下准则:
(1) 通过最少的PLI交运算获得
(2) 在计算时,如果用探测,我们希望比较小。
准则1希望处理的交运算的数量较少,准则2希望每个交运算的处理效率较高。算法2考虑了上述两条准则,利用PLI缓存为PLI请求提供服务。
假设我们想根据图2所示的PLI缓存计算。首先,我们在缓存中查找所有子集的PLIs(第1行)。这一步在前缀树里很好实现。
在检索到的所有PLIs里,选择大小最小的一个(第2行)。在本例中是大小为23的。这个最小的PLI将会在第一次交运算中进行探测。之所以选择大小最小的PLI进行探测是因为交运算得到的结果大小一定小于用于探测的PLI的大小。这满足准则2。
接下来,我们需要决定被探测的PLI。我们需要不断地寻找能提供更多新属性的PLI(3-7行),这样可以减少交运算的次数,符合准则1。在本例中,我们选择,可以提供两个新属性,接着是。最终所有的属性至少会出现在选择的三个PLIs中的某一个里。注意到Pyro会缓存单个属性的PLIs,这样可以响应任何PLI请求。
在选择好PLIs后,我们对其进行交运算。根据准则2,尽量先使用小PLIs(8-10行),这样能快速减少结果PLI的大小。在本例中,交运算的顺序是。相较用单个属性的PLIs做交运算,节省了两次交运算。另外,的大小远小于单个属性的PLIs大小。因此,PLI缓存很好地满足了准则1和2。
最终,我们随机缓存中间和最终结果(第11行)。缓存所有计算的PLIs将会快速填满缓存,这些缓存有些是冗余的且之后不会再次使用。
5.2 评估候选依赖项
这一部分介绍如何根据计算错误率。对于AUCC候选项,我们只需要计数每个簇内的元组对(1-2行),这些元组恰好是违反UCC的元组对。AFD错误率的计算相对复杂,根据定义,违反的元组对是在上值相同且在上值不同。我们不直接计数这些元组对,相反,对于每一个簇,我们计算在上值也相同的元组对个数(4-8行),然后用簇内元组对总数减去在上值相同的元组对个数。计算在和上值都相同的元组对个数需要用到和。注意我们不用计数中0的个数,因为0代表单个值(不存在元组对)。
5.3 评估函数依赖错误率
Pyro的一个关键思想是通过估计依赖候选项的错误率,然后只进行一些有针对性的错误率计算,从而避免代价高昂的基于PLI的错误率计算。事实上,错误率计算可能比错误率估计慢几个数量级。一般来说,我们可以通过比较元组的一个子集——或者更好:元组对的子集——来估计错误率,并将遇到的违反依赖候选项的数量外推到整个关系。这种错误率估计比穷尽所有元组对以发现依赖关系的算法更高效。该方法的基础是一致集样本。
定义3(一致集样本):给定模式的一个关系实例,,它们的一致集为。进一步地,是中元组对的采样,可以根据推导出一致集样本,其中表示了中每个一致集的出现次数。
例4:假设我们随机地从表1中选取3个元组对,相应的一致集样本为。
现在从一个一致集样本AS中估计AFD和AUCC的错误率,我们定义了一个查询,该查询返回一致集样本中包含某些属性集合且不包含进一步的属性集合的一致集的数量:
Pyro使用单热编码有效地将一致集存储为位掩码。这样一致集样本可以保存在内存中,并通过对一致集样本的全面扫描有效地执行。所以,我们可以将违反该AFD的元组对个数记为,除以即可得到AFD候选项的错误率的估计。相应的,对于AUCC的候选项,可以用估计错误率。
引理1:是用一致集样本估计的AFD和AUCC的错误率,是真实的错误率。那么,是无偏的,且的概率为:
证明:采样个元组对并测试它们是否违反AUCC和AFD候选项遵循二项分布。该分布的均值,即一致集样本预期违反数量为,上述误差界限可以由二项分布的累积分布函数立即推导出来。
有趣的是,我们的错误率估计的准确性不依赖于输入关系的大小,这使得它具有高度的可扩展性。相反,我们它受到实际错误率的影响,当时,二项分布的方差最大。然而一般情况下,我们只需要在接近0时进行准确估计。例如对于和,。
然而,如果我们对整个关系只有一个一致集样本,这个样本可能需要很大才能在需要的时候实现高精度估计:我们的二项分布的错误率估计的标准差是,与样本容量的平方根成反比。假设有个属性,每个属性的AUCC错误率为0.0101,的错误率为0.0099,在这种情况下,除单个属性外的任意属性集合都是最小AUCC,总共有个。因此,我们需要更多的一致集样本去预测真正的最小AUCC,这需要很高的代价。
为了从小一致集样本中提供高精度的错误率估计,Pyro使用集中采样。然而,结果样本必须是随机的,以保证上述估计量的无偏性。为了解决这个矛盾,我们仅采样某些特定属性集的超集的一致集。这样的采样很好实现:对于,我们仅从同一个簇内采样元组对。例如,在表一中,,那么我们将仅在中采样元组对或仅在中采样。
具体来说,为了采样一个在上值相同的元组对,首先以的概率选择一个簇,其中是所有在上值相同的元组对个数。也就是说,选中簇的概率与其元组对个数成正比。然后,我们从中随机抽取两个元组,每个元组被抽中的概率都是。这样下来,任何在上值相同的元组对被采样的概率相同,都是。最后,我们计算采样的元组对的一致集,并获得一个集中但随机的一致集样本。
基于,若,我们可以估计AFD候选项和AUCC候选项。在关系上的AUCC候选项的错误率为:
AFD候选项的错误率为:
其中
定理1:给定AUCC候选项或AFD候选项,我们基于样本的集中估计是无偏的,且的概率为。
证明:错误率估计的第一项为所有在上值相同的元组对中违反候选依赖项的元组对的比例;引理1显示了它们的无偏性和误差边界。因为所有违反候选项的元组对必须在上值相同,错误率估计的第二项的常数将这个“集中”估计外推到整个关系,从而保证无偏性并通过常数因子缩小误差边界。
定理1解释了为什么集中样本是有效的。再次考虑表1中的ZIP列:在所有10个元组对中,只有4个在ZIP上值相同,因此在ZIP-集中的样本估计的准确度是相同样本量和置信水平下非集中估计的 倍。在更大的、真实世界的数据集中,这种效应甚至更强。例如,对于具有1000个等分布值的属性,在其上的集中估计会将误差边界缩小倍。因此,创建和使用多个集中样本比使用单个的广泛样本更有效。事实上,Pyro为每个属性创建一个集中样本并且只使用集中样本。
在解释了集中一致集样本以及如何使用它们估计AFD和AUCC的错误率后,还需要说明Pyro如何响应候选依赖项的错误率估计请求。在不失一般性的前提下,假设所讨论的依赖候选项是AUCC候选项。对于PLIs,无论何时Pyro创建一个一致集样本,都会将其缓存到一个前缀树中(参见图1和5.1节)。Pyro首先确定所有-集中的一致集样本,,这可以高效完成,因为AS缓存是一个前缀树。然后,Pyro选取采样比最高的一致集样本,原因是更大的一致集样本和更小的采样焦点产生更精确的错误率估计。更重要的是,当的总体非常小(几乎是一个UCC),那么集中样本可能非常详尽,错误率估计将是准确的。
搜索空间遍历
要发现依赖关系,重要的不仅是高效地评估上一节中解释的依赖候选项;搜索空间遍历策略也是至关重要的。与Tane(系统地遍历搜索空间的大部分)和Ducc/Dfd(在搜索空间中执行随机遍历)相比,Pyro采用了一种新颖的分治策略;它分离出搜索空间中的一部分,估计该子空间中最小依赖项的位置,然后验证这一估计。之后,相当一部分搜索空间可以被剪枝。
在详细阐述其各个阶段之前,让我们用图3的示例更具体地概述Pyro的遍历策略。搜索可以认为是一场烟花表演——这正是Pyro这个名字的来源。它由多个回合组成,从单一属性开始,即发射台。在我们的例子中,Pyro选择作为发射台,并在搜索空间中上升(像火炮一样),直到它检测到依赖项(第一步,6.1节)。从这个被称为峰值的依赖项开始,Pyro下降(就像一个爆炸的火炮),并估计泛化峰值的所有最小依赖项的位置(第二步,6.2节),这是的情况。然后,它通过检查互补的未检查的依赖候选项,即(第三步,6.3节)来验证估计。
这就完成了第一轮搜索,如图3所示,Pyro使用发现的非依赖项和依赖项大大缩小了后续几轮搜索的搜索空间。事实上,发现的(非)依赖项被存储到一个前缀树中(5.1)节,以有效地确定下列依赖候选项是否已经被剪枝。最后,在下一轮搜索中,Pyro可能回遇到一个修剪过的发射台;在图3中,我们再次选择。在这种情况下,Pyro逃逸发射台进入搜索空间中未被剪枝的部分(第四步,6.4节)。
6.1 上升
上升阶段应该有效地确定给定搜索空间中的某些依赖项,然后形成后续下降阶段的输入。算法4描述了Pyro如何实现这一点。上升从发射台开始,在那里有状态未知的最小依赖候选项。因此,单个属性作为初始发射台。Pyro估计它们的错误率,并选择错误率最小的一个,例如图3中的属性,假设它会迅速推导出依赖关系(第1行)。
然后,Pyro贪婪地将该属性添加到发射台,以最大限度地减少(估计的)依赖错误率(第7行),直到满足依赖项(第6行)或不能再添加属性(第8行)。后者发生在根本没有属性可添加或所有可能的候选项都是前几轮已知的依赖项时。在本例中,我们将其声明为最大非依赖项,以用于剪枝,并停止当前搜索(第15行)。然而,如果我们遇到一个依赖项,就像图3中的,我们将从这个依赖项开始下降阶段(第13行)。
6.2 下降
给定来自上升阶段的依赖项,称为峰值,Pyro下降以估计泛化的所有最小依赖项的位置。算法5概述了Pyro如何执行这种估计。首先,被放置到一个新的优先队列中,该队列按照估计的错误率排序(第2行)。Pyro查看优先队列中最小的的元素(最初是)(第4行),检查它是否被一些已经估计的最小依赖项剪枝(初始不会是这个情况),然后用调用trickle-down-from函数,其目的是估计恰好有一个最小依赖(将泛化)的位置,或者如果和其泛化都不被估计为依赖项(第12行)则返回。
在我们的例子中,我们最初使用峰值调用trickle-down-from。它创建了一个优先队列,对的直接泛化例如等按照错误率估计进行排序(第16行)。这些泛化是潜在的最小依赖项,因此任何误差小于的泛化都将递归调用trickle-down-from(17-21行)。如果递归产生最小的依赖候选项,Pyro立即报告它。在图3中,我们递归地访问,然后访问。和都不是依赖项,因此可能是最小依赖项。最后,我们计算的错误率,如果它是一个依赖项就返回它(22-23行)。如果不是一个依赖项,那么我们将在上创建一个集中样本以便对和峰值之间的候选依赖项获得更好的错误率估计,并在上继续搜索。
最后,我们将添加到估计的最小依赖项中,并再次从峰值优先队列(第4行)查看,其中仍包含原始的峰值。然而,现在队列里还有所谓的最小依赖。我们不会轻易地将从队列里删除,因为可能会有进一步的最小依赖泛化它。因此,我们确定了所有最大依赖候选项,它们是的子集,但不是的超集。如下所述,Pyro通过计算的最小命中集,即和,并将它们从中删除(8-10行),从而确定这些候选项,从而产生和。这些形成了新的峰值,从中继续搜索最小依赖项。在我们的例子中,我们估计两者都是非依赖项,并将它们从队列中删除(第13行)。
现在更详细地解释命中集地计算。形式上,一个集合是一个集合族(这里是一组属性集)的命中集,如果它与每一个集合都相交。如果它的子集都不是命中集,则它是最小命中集。在接下来的遍历步骤中也使用了最小命中集的计算,进一步构成了一个NP-Hard问题。面对这种计算复杂度,问题需要尽可能高效地解决。算法6显示了Pyro如何为一组属性集计算所有最小命中集。
首先,我们初始化一个集合前缀树(5.1节),将空集合作为初始解(第2行)。接下来,我们按大小对属性集进行排序(3-4行)。如果包含两个集合和且,我们希望先处理,因为任何与相交的集合也会与相交。然后一个接一个地迭代有序输入集(第5行)。假设,我们有,我们通过查找的补集,删除所有与不相交的当前命中集(6-7行);在我们的例子中,的补集是,的初始解空集是它的一个子集。最后,我们将所有被移除的集合与中所有属性结合起来,以重新建立命中集属性(8-11行)。例如,将与组合将产生两个新的命中集和。然而,这些新的命中集可能不是最小的。因此,在将一个新的命中集添加到集合前缀树前,我们先检查树中是否存在一个最小命中集是的子集。处理完中所有属性后,该树中包含了所有最小命中集。
6.3 验证
虽然从trickle-down-from阶段估计的最小依赖项只包含验证过的依赖项,但不知道这些依赖项是否是最小的以及是否是完整的。例如,我们可能错误地将几个依赖关系在上升或下降阶段视为非依赖关系。Pyro用尽可能少的错误计算来验证的完整性。是完整的当且仅当任何依赖候选项是某个的具体化或者是个非依赖项。
要测试这一点,只需要测试峰值以下所谓的最大非依赖项,即泛化且其特殊化都是已知依赖项的依赖候选项。如果这些最大候选项确实是非依赖项,那么它们的所有泛化也是非依赖项,也是完全的。在算法5中,Pyro通过计算中所有元素的最小命中集,并将它们从中移除,得到这些候选对象,记为。在我们的例子中,的命中集为 ,因此需要检查和。
中的候选项有两种可能的结果。如果这些候选项确实是非依赖项,那么确实是完整的。如果包含依赖项,是不完整的。尽管如此,我们可以利用这一结果缩小搜索范围。
让表示中的依赖项,假设是依赖项,则。未覆盖的任何依赖项一定是中某个依赖项的泛化,因为任何候选要么是中某个元素的超集,要么是中某个元素的子集。更进一步的,令表示中的非依赖项。在修改后的例子里,我们有。中的元素为关于峰值的最大非依赖项,也就是说它们的所有超集也是的子集都是依赖项。我们现在可以确定依赖候选项,它们不是中任何最大非依赖项的子集,表示为:我们对中所有元素关于求补集,然后计算它们的最小命中集。对于,有。由此可见,未涵盖的任何依赖项都是中某个候选依赖项的专门化和中某个候选依赖项的泛化,即在我们的例子中,未知的最小依赖项一定是的超集且是的子集。
因此,Pyro可以使用这些依赖候选项创建一个搜索子空间,并递归地处理它,包括本节中介绍的所有步骤。此外,Pyro在这个子空间工作时增加了一致集样本的大小,以减小关于最小依赖关系的新的错误预测概率。不过,在另一个错误预测的情况下,Pyro可以递归地创建新的子空间。不过,递归肯定会终止,因为子空间在不断缩小。然而,在我们的实验中,我们甚至很少看到递归深度为2。在递归之后,Pyro最终需要检查中的哪些依赖项没有产生泛化的最小依赖项。这些依赖项一直都是最小的,必须如实报告。
6.4 逃逸
在每一轮搜索中,大部分搜索空间都可以被剪枝。如图3所示,发射台现在也可能被剪枝。除非发射台被发现是(最小)依赖项,我们不能丢弃:可能仍存在发射台的超集是未被发现的最小依赖项。
无论何时Pyro捡到一个剪枝过的发射台,它都需要通过添加最少的属性来将其从被剪枝的搜索空间部分中逃逸出来。让我们假设Pyro再次拿起发射台,为了确实它是否被剪枝,Pyro确定所有之前访问过的都是的超集的峰值,在我们的例子中是。同样,命中集计算现在可以确定添加到的最小属性集,使得不再是的子集了:Pyro计算的命中集,即。通过把添加到,我们可以得到唯一的最小逃逸(图3的第4步)。注意该操作与算法5中的峰值重定位完全相反。然而,因为我们有发射台,它是的一个子集,我们最终不得不丢弃,所有未知的依赖候选项确实是的超集。
由于Pyro维护发射台,使得它们恰好形成了最小的未测试依赖候选项。当搜索空间不再包含发射台时,搜索空间就完成了。因此,Pyro最终将终止于搜索空间的完成依赖集。