Winnowing算法

 在介绍winnowing之前,我们先来了解一下有关copy-detection的知识。copy-detection主要用于检测文件或网页中相同的内容,判定是否存在拷贝、抄袭等行为以及程度。在下面的讨论中,我们将范围缩减到只考虑包含ASCII字符的文件,不考虑图片及视屏等内容。通常情况下,我们认为用于copy-detection的算法应该满足以下要求:
      1、无视空白符(Whitespace insensitivity)。在比较文件内容的过程中,我们通常不希望被空格、制表符、标点符号等影响我们判定的结果,因为它们并不是我们感兴趣的内容。在不同的应用下,我们感兴趣的内容会有些不同,比如在程序代码文件中,变量名通常是我们不感兴趣的。
      2、噪音抑制(Noise suppression)。主要是指应该排除一些可以接受的拷贝,比如成语、谚语、免责声明等,这些也不是我们感兴趣的。比如在程序代码文件中,你或许希望排除一些教师给定标程中的内容。
      3、位置无关性(Position independence)。在我完全拷贝一份文件后,简单的将文件内容调换一下位置,然后说这不是抄袭,我想没有人会认同吧。位置无关性就反映了这么一种性质,粗粒度的位置调换不会影响最终的判定。更进一步说,从原文件中添加或者删除一段内容,不应该影响到其他部分的判断。

       下面介绍一下基于文件指纹(所谓指纹,实际上就是将一段内容映射成一个数字,只要算法选择的好,每一段不同内容所映射的数字都不会相同,就好比人的指纹一样)的copy-detection的主要思想。

      k-grams是指字符串中长度为k的子串,这些子串满足“高度邻接”(k值自定义)。比如一个字符串abcd,那么将abcd分解成3-grams就是abc和bcd两个子串,每个子串就称为一个3-gram。可以看到第二个子串仅仅是在第一个子串的基础上向后移了一个位置,所以对于一个长度为n的字符串,当我们将它分成k-grams后,将会形成n-k+1个子串,且每个子串长度为k。接下来就可以将每个k-gram哈希为一个整数,如果将所有哈希值作为文件的指纹用于比较,显然是不高效的,也没有那个必要,所以只需选取哈希值的一个子集最为文件指纹即可。一种方法是选取所有满足0 mod p(模p余0)的哈希值,下面是一个具体的例子:
      原内容:A do run run run, a do run run
      删除无关内容:adorunrunrunadorunrun
      分解成5-grams:adoru dorun orunr runru unrun nrunr
                              runru unrun nruna runad unado nador
                              adoru dorun orunr runru unrun
       哈希5-grams为:77 72 42 17 98 50
                               17 98 8 88 67 39
                               77 72 42 17 98
       满足0 mod 4的哈希值:72 8 88 72

        这个方法有个缺点,它不能保证一定可以检测到文件中相同的部分。如果两个满足0 mod p的哈希值之间隔了很远,那么在这两个哈希值之间的内容就算相同,也不会被检测到。为了解决这个问题,我们可以定义一个大小为w的窗口(w值自定义)来分割哈希值,窗口内的内容也是“高度邻接”的。如上例中使用窗口分割为:

      使用w=4的窗口分割后:(77, 72, 42, 17 ) (72, 42, 17, 98)
                                      (42, 17, 98, 50) (17, 98, 50, 17 )
                                      (98, 50, 17, 98) (50, 17, 98, 8 )
                                      (17, 98, 8, 88) (98, 8, 88, 67)
                                      (8, 88, 67, 39) (88, 67, 39 , 77)
                                      (67, 39, 77, 72) (39, 77, 72, 42)
                                      (77, 72, 42, 17 ) (72, 42, 17, 98)

        经过分割后,我们只要保证每个窗口至少选出一个哈希值,就能避免哈希值之间距离太远的问题。同时,因为长度为w的窗口实际上对应了原文中长度t=k+w-1的子串,所以该方法保证了原文件中所有长度为t的子串,只要相同就一定不会被漏掉。接下来的问题就是如何选取哈希值了。简单但是不太健壮的策略是,我们可以选取每个窗口内第i个的哈希值作为代表,但是这个方法不具备位置无关性(要求3),插入和删除一段内容都会对结果造成影响。 实际上winnowing算法所实现的就是选取哈希值的策略。
      Winnowing算法所采用的策略是:选取每个窗口中最小的哈希值(显然两个窗口可能共享同一个最小值)。如果存在多个最小值,则选取最右边的。该策略既保证选取足够的指纹信息,又保证了不会产生太庞大的指纹。窗口分割示例中的红色部分就是通过winnowing选取出的指纹,在实际应用中,还可以记录哈希值出现的位置,用于跟踪相似内容出现的位置。如[17,3] [17,6] [8,8] [39,11] [17,15](下标从0开始,第二个值表示哈希值在原序列中出现的位置)。winnowing算法的代码如下:

void winnow(int w /*窗口的大小*/ ){   //窗口的大小不应该大于哈希值的总个数
   int *h=new int[w];   //存放哈希值的循环窗口
    int min=0;      //记录当前最小值在窗口中的位置
    for(int i=0;i<w-1;i++){
        h[i]=next_hash();
        if(h[i]==-1){       //当窗口值大于指纹总数时执行
             printf("[%d, %d]\n",h[min],pos);
             delete h;
             return;
        }
    }
   int r=w-1;       //循环遍历数组h,按顺序将原序列的哈希值添加到数组中
    int pos=min;     //记录最小值在原序列中出现的位置
   while(true){
        r=(r+1)%w;
        h[r]=next_hash();   //添加下一个哈希值
        if(h[r]==-1)break;  //假设以-1作为结束
        if(min==r){        //如果条件成立,说明前一个窗口的最小值将被移除窗口
            pos+=w;
            for(int i=(r-1+w)%w; i!=r; i=(i-1+w)%w){   //选取窗口中最小且最右的哈希值
                if(h[i]<h[min]){
                    pos-=(min+w-i)%w;
                    min=i;
                }
            }
            printf("[%d, %d]\n",h[min],pos);
       }
        else{                             //min!=r就说明当前最小的哈希值依然包含在窗口中
            if(h[r]<=h[min]){      //那么只需要比较新添加进的哈希值即可
                pos+=(r+w-min)%w;
                min=r;
                printf("[%d, %d]\n",h[min],pos);
            }
        }
   }
   delete h;
}

参考:Saul Schleimer, Daniel S. Wilkerson, and Alex Aiken. Winnowing: Local Algorithms for Document Fingerprinting.
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值