Resive world

Come world to Record life


  • Home

  • Tags

  • Categories

  • Archives

  • Sitemap

  • Search

最小哈希签名

Posted on 2016-09-17 | In Data Mining

最小哈希签名(minhashing signature)解决的问题是,如何用一个哈希方法来对一个集合(集合大小为n)中的子集进行保留相似度的映射(使他在内存中占用的字节数尽可能的少)。

其实哈希本身并不算难,难的是怎么保留两个子集的相似度的信息。所谓保留相似度,就是说我们能十分直观的从两个子集的哈希结果中看出他们的相似度。当然,朴素的办法就用是一个长度为n的二进制数的每个位来分别对应集合中的每个元素。不过当n比较大而待hash的集合的数目比较小的时候,这种方法的效率就太低了。这时候最常用的方法就是对集合进行最小哈希。

最小哈希

什么叫最小哈希,我的理解是,一个很大的集合进行哈希处理的过程其实是由很多小的哈希过程组成。而这些最小的哈希过程就被称为是最小哈希。最小哈希的具体内容就是把一个集合映射到一个编号上。具体的过程也很简单了。

比如对于集合$U=\{a,b,c,d,e\},S_1:\{a,d\},S_2:\{c\},S_3:\{b,d,e\},S_4:\{a,c,d\}$,我们用一个矩阵形式来表示他们:

$$\begin{matrix}No.&Item&S_1&S_2&S_3&S_4\\0&a&1&0&0&1\\1&b&0&0&1&0\\2&c&0&1&0&1\\3&d&1&0&1&1\\4&e&0&0&1&0\\\end{matrix}$$

那么,对他进行一次最小哈希就是在经过随机的行排列之后,对于每个集合,从上到下取第一个数值为1的那一行的行号。

比如对上面的矩阵进行随机行排列后变成:

$$\begin{matrix}No.&Item&S_1&S_2&S_3&S_4\\1&b&0&0&1&0\\4&e&0&0&1&0\\0&a&1&0&0&1\\3&d&1&0&1&1\\2&c&0&1&0&1\\\end{matrix}$$

那么每个集合的minhash结果就应该是$h(S_1)=0,h(S_2)=2,h(S_3)=1,h(S_4)=0$。

这就是minhash的基本方法。

最小哈希签名

在最小哈希的基础上,最小哈希签名也就很简单了。在最小哈希中,需要对每行进行随机行排列,如果是真随机排列的话显然计算消耗会特别大。因此实际上我们采用了一个替代方法,就是再用一个哈希函数,将行号进行哈希变换。比如当n=5时,对$0,1,2,3,4$这5个数,可以用$h(x)=(3x+1)%5$的方法进行映射,得到$1,4,2,0,3$。当然,随便找的$h(x)=ax+b$这种哈希函数显然可能会冲突,不过只要n和a互素,那么生成的一定是一个排列,这一点用同余类的知识很好证明。

不过显然,一次最小哈希的结果不能全面的表现出集合的特征。因此最小哈希签名采用了k个不同的哈希函数$h_1,h_2,h_3,…,h_k$,对于集合S,分别调用这些函数作为最小哈希的排列函数,构建出集合S的最小哈希签名$[h_1(S),h_2(S),h_3(S),…,h_k(S)]$。显然,这个签名所占的空间要远远小于用朴素的方法保存集合所需的空间。

保留相似度的哈希

为什么说这个最小哈希签名是一种保留相似度的哈希呢?其实也很好理解。

事实上,对于两个集合$S_1,S_2$来说,我们知道在前面的矩阵的所有行中,他们的值同时为1的行有$|S_1\cap S_2|$个;一个是1一个是0的行有$|S_1\cup S_2|-|S_1 \cap S_2|$个。那么在对行进行随机排列之后,从上往下数同时为1的行比一个为1一个为0的行先出现的概率就是$\frac{|S_1\cap S_2|}{|S_1\cup S_2|}$。而这恰巧就是两个集合的Jaccard相似度。也就是说在每一次最小哈希的过程中,两个集合的哈希值相等的概率就是两个集合的Jaccard相似度。这个性质就非常棒了,他保证了如果把最小哈希签名生成的向量当成集合,那么对两个集合进行最小哈希签名之后生成的集合之间的Jaccrad相似度的期望值与原集合的Jaccard相似度相等。

这就是所谓的保留相似度的哈希。

文档分割的shingling算法

Posted on 2016-09-16 | In Data Mining

shingling算法是最常见的文档分割算法,说白了就是将一个文档分解成由短字符构成的字符串集合。分割后的文档就可以通过Jaccard相似度等简单的度量标准进行相似度检测了。

k-shingling

对于任意一篇文档,我们把他当成一个字符串,那么他的k-shingling集合就被定义为文档中所有长度为k的子字符串的集合。比如字符串“abcdabd”,他的2-shingling集合就是{ab,bc,cd,da,bd}。

当然,所有类型的子字符串只会在集合中出现一次。不过实际的文档中可能会有连续的空格、TAB、回车或者标点符号之类的东西,一般可以把他们都变成一个空格来进行处理。

shingle大小的选择

显然,如果要使用k-shingling来对文档进行处理就要先确定这个k值,一般而言,要确保这个值足够大,保证任意的shingle在文档中出现的概率较低。不过也没有一个具体的选择方法,倒是有一些经验参数,比如对于英文邮件,可以取k=5;而对于英文研究论文,可以取k=9,因为邮件的长度一般比较低,而论文则相对较高。

一个简单变种

有一种简单的基于词的shingling方法,能够很有效的区分英文新闻与广告。这个应用在对网页进行查重的时候特别有用,因为网页上除了新闻本身之外,通常还会有不同的广告,干扰我们对相同新闻的判断。具体的做法也很简单,就是把shingle定义成一个停用词(就是”and”、”but”、“you”之类经常出现的词)加上后续的两个单词。这样做是因为广告通常比较精炼而较少包含停用词。

相似度度量标准之Jaccard相似度

Posted on 2016-09-16 | In Data Mining

定义

Jaccard相似度(杰卡德相似度)是一个用于衡量两个集合相似程度的度量标准,他的定义如下:给定两个集合$S,T$,那么我们记这两个集合的Jaccard相似度$SIM(S,T)$为:
$$SIM(S,T)=|S\cap T|/|S\cup T|$$
也就是两个集合交集的大小除以两个集合并集的大小。显然他的取值在[0,1]区间。

扩展

原始的Jaccard相似度定义的仅仅是两个集合(set)之间的相似度,而实际上更常见的情况是我们需要求两个包(bag,multiset)的相似度,即每个元素可能会出现多次。那么在这种情况下,Jaccard相似度的分子就便成了取每个元素在两个包中出现的最小次数之和,分母是两个包中元素的数目之和。比如$\{a,a,a,b\},\{a,a,b,b,c\}$之间的Jaccard相似度就是(2+1)/(4+5)=33%。

这里分子的设计是很容易理解的,那么为什么分母设计成两个集合中元素数目之和而不是并集(包的并集通常定义为元素的叠加)中的数目之和呢?因为那样会使最大的Jaccard相似度为1/2,而不是习惯理解的1。当然,我们也可以把包的并集中的元素数目定义为在两个集合中出现的最大次数,这样的度量标准也比较符合我们的认知习惯。

应用

Jaccard的应用很广,最常见的应用就是求两个文档的文本相似度,通过一定的办法(比如shinging)对文档进行分词,构成词语的集合,再计算Jaccard相似度即可。当然,用途还有很多,不过大多需要结合其他的技术。

一道习题

问:假定全集$U$有$n$个元素,随机选择两个子集$S,T$,每个子集都有m个元素,求$S,T$的Jaccard相似度的期望值。

解:显然,若有k个元素重合,那么贡献的Jaccard相似度就是$\frac{k}{2m-k}$,且这个事件出现的概率是$\frac{C^k_mC^{m-k}_{n-m}}{C^m_n}$,因此对这k种可能求和即可:

$Exp(SIM(S,T))=\sum^m_{k=0}\frac{k}{2m-k}\frac{C^k_mC^{m-k}_{n-m}}{C^m_n}$

SDL2安装指南

Posted on 2016-09-13 | In C/C++

SDL(Simple DirectMedia Layer)是一套开放源代码的跨平台多媒体开发库,使用C语言写成。SDL提供了数种控制图像、声音、输出入的函数,让开发者只要用相同或是相似的代码就可以开发出跨多个平台(Linux、Windows、Mac OS X等)的应用软件。目前SDL多用于开发游戏、模拟器、媒体播放器等多媒体应用领域。下面主要介绍一下在Windows下搭建SDL2开发环境的过程。

下载

下载自SDL官网,在Development Libraries中选择相应的版本。我这里选择的是Windows平台下的Visual C++版,因为我接下来使用的环境是VS2013。

文件

把文件下载下来解压后的文档树应该是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
└─SDL2-2.0.4
│ BUGS.txt
│ COPYING.txt
│ README-SDL.txt
│ README.txt
│ WhatsNew.txt
│
├─docs
│ doxyfile
│ README-android.md
│ README-cmake.md
│ README-directfb.md
│ ......
│
├─include
│ begin_code.h
│ close_code.h
│ SDL.h
│ SDL_assert.h
│ SDL_atomic.h
│ SDL_audio.h
│ ......
│
└─lib
├─x64
│ SDL2.dll
│ SDL2.lib
│ SDL2main.lib
│ SDL2test.lib
│
└─x86
SDL2.dll
SDL2.lib
SDL2main.lib
SDL2test.lib

主要包含使用说明、doc文档、头文件、以及库文件。这里的库文件包括x86和x64两种架构的,每种都含有一个动态链接库和三个静态链接库。

VS中的项目配置

SDL2说白了其实只是一个C语言库,因此配置他就跟配置其他任意的库一样,主要分为三步:

一、包含必需的头文件和库文件

因为我们需要能够include进SDL2的头文件,并且找到对应的实现代码(库文件),所以我们必需得让编译器能够找到他们。因此我们只需要在工程的项目->属性->配置属性->VC++目录< 里配置好相应的路径:

也就是修改图中的包含目录以及库目录,分别对应之前的include文件夹,以及lib/x86文件夹(VS默认的是x86架构)。

二、添加编译指令

既然用了第三方的库,那么我们在进行编译的时候肯定需要加上-l指令,从而指定编译进去的静态链接库。而按照微软一贯的保姆式作风,在VS中并不需要我们手动输入编译指令,只需要修改一下编译配置,然后就能直接编译了。这个配置在项目->属性->配置属性->链接器->输入->附加依赖项< 这里:

在这里面加上那三个静态库的名字即可(SDL2.lib、SDL2main.lib、SDL2test.lib)。

三、配置动态库

只配置了静态库已经是可以编译的了,但却是无法调试的,因为程序运行需要SDL2.dll这个动态库的支持。那么我们只需要将SDL2.dll加入电脑的PATH环境变量里或者是工程目录下,从而保证程序能找到他。

最后,针对SDL2还需要额外设置一个配置,就是程序的入口,具体原因不明。配置方法就是修改项目->属性->配置属性->链接器->系统->子系统,内容改成”窗口 (/SUBSYSTEM:WINDOWS)“即可:

搞好上面这个配置,理论上就能跑SDL2的程序了,那我就直接把下面这个显示图片的程序作为Hello World来测试一下吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include "SDL.h"
#include<iostream>
using namespace std;
int main(int argc, char** argv){
if (SDL_Init(SDL_INIT_EVERYTHING) == -1){
std::cout << SDL_GetError() << std::endl;
return 1;
}
SDL_Window *win = nullptr;
win = SDL_CreateWindow("Hello World!", 100, 100, 640, 480, SDL_WINDOW_SHOWN);
if (win == nullptr){
std::cout << SDL_GetError() << std::endl;
return 1;
}
SDL_Renderer *ren = nullptr;
ren = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
if (ren == nullptr){
std::cout << SDL_GetError() << std::endl;
return 1;
}
SDL_Surface *bmp = nullptr;
bmp = SDL_LoadBMP("test.bmp");
if (bmp == nullptr){
std::cout << SDL_GetError() << std::endl;
return 1;
}
SDL_Texture *tex = nullptr;
tex = SDL_CreateTextureFromSurface(ren, bmp);
SDL_FreeSurface(bmp);
SDL_RenderClear(ren);
SDL_RenderCopy(ren, tex, NULL, NULL);
SDL_RenderPresent(ren);
SDL_Delay(2000);
SDL_DestroyTexture(tex);
SDL_DestroyRenderer(ren);
SDL_DestroyWindow(win);
SDL_Quit();
return 0;
}

程序运行的结果就是显示test.bmp两秒钟。

QR二维码植入图片方法简析

Posted on 2016-09-12 | In Others

我们平时都经常见到二维码,用手机扫一扫就会显示当中的内容,内容大多是url的格式,方便人们访问站点。不过对于人来说,直接看二维码却并没有任何良好的印象。因此很多商家为了让自家的二维码更加形象,会想方设法地让自家的二维码更加形象生动,于是也就诞生出了很多种图像植入的方法。今天就把这些方法稍微汇总一下。

二维码

二维码可以说是现今人们生活中经常接触到的东西,简单的讲其实就是把条形码中的竖线变成点,从而增加一个数据维度,达到增加数据内容的目的。事实上二维码的种类有很多,曾经流行的二维码规范大概有下面这几种:

不过,最终一直流行到现在的二维码规范就剩下一个日本公司率先搞出来的QR二维码(Quick Response)了。QR二维码最典型的特征就是在图片的三个角有三个定位用的正方形,信息容量大、定位方便、数据保密和恢复能力强。有个日本的网站简要介绍了他的构造,连接在这里。接下来的说明主要是围绕这种二维码进行的讨论。

直接插入图片

直接插入图片的方法就是在二维码的中间插入设计好的图片,比如下面这些:

这样插入的图片比较清楚,显然会使人们对二维码的辨识度变高很多,也更容易被人们接受。但是这中插入图片的方法完全是利用了二维码本身的容错处理,或多或少的影响了二维码本身的容错性,原则上是不安全的方法。

修改点阵形状

所谓修改点阵形状,就是用类似设计师的眼光看待这个二维码,把原本的黑点和白点进行艺术化的修饰。这种方法虽然很难添加自己想要的图片,但是却十分具有艺术美感,让人印象深刻。也可以适当结合插入图片的方法,更加具有表现力。






当然,这种方法会增加扫码器识别的难度,万一图像做的过火了,简陋的扫码器扫描不出来就很尴尬了。

增加背景图案

简而言之就是为二维码增加背景,不过随之而来的问题跟上面一样,就是如何能让普通的扫码器快速识别出来。作为几年前百度内部hacthon的优胜项目,百度的工程师对点阵图案也做了一些处理,使二维码能呈现这样的图案:

基本想法就是把二维码的每个点变小,中间漏出的空隙就用来呈现背景图案。对于某些特定的背景而言,呈现的效果还是很不错的,而且识别起来也相对准确一点。不过对图片的要求也比较高,否则可能会干扰图像的识别。

与这个方法效果看上取类似的还有一种生成方法,这种方法也结合了下面的修改编码的算法,而且已经发表在springer里,他能够呈现这样的效果:

看上去和百度的差不多,不过其实技术含量要高出不知道多少倍。。。

修改编码算法

上述的方法都或多或少的影响了图像本身的识别能力,虽然看上去效果不错,但是都会或多或少的影响扫码器的识别。而下面的方法能够利用一定的编码技术和小技巧将一个二值图嵌入到二维码中,完全不影响二维码的识别。虽然显示的图像有点抽象,但是拿出来装逼还是很能够体现出技术含量的。




上面这些图案的生成其实还是十分繁琐的,需要深入了解QR二维码的实现以及RS编码的特征的编码人员才能真正实现出将二值图和二维码进行融合的算法。

讲述算法的原理的原文在这里,有个简单(蹩脚)的中文翻译版本在这里,他们实现的程序发布在了这里(不知道怎么回事时而登的上时而登不上)。

下面就简要介绍一下这个算法的基本思想。

首先我们要稍微了解一下QR二维码的基本构造。QR二维码的一个经典构造就是下面的这张图:

简单区分下就是有三类东西,一类是类似小正方形的东西和图中那些黑白相间的条纹,他们主要是用来帮助扫码器定位用的的,所有的QR二维码都具有的部分;还有一类就是格式信息的控制部分,他们就像是配置文件一样,定义了这张二维码的基本信息格式,是必不可少的,主要分布在定位符旁边,毕竟他们是不容许出错的;最后一类就是数据了,当然是经过编码的数据,按照一定的排列规则填充在剩下的空间里,具有一定的容错能力。

很显然数据是以二进制串的形式存储在图里的,也就是黑点和白点了。一张固定大小的图能保存的数据量显然也是固定的,QR二维码也定义了数据的终止符,也就是说当我们的数据写完之后,写上终止符,编码程序会自动在后面加上一些无用的数据(其实是一个循环的数据串),使得总长度等于最大数据量。这些数据按照下面的顺序写入二维码中:

或者换一张图就是这样:

不过如果直接将这个数据就这么显示出来,那么对于只需要保存少量信息的二维码来说,很大的空间都是相同的内容(就是用来填充的字符串),这样显然不美观。因此在把数据写进去之后,我们还会对他做一个mask处理,也就是通过一定的选择算法拿下面这八张图片中的一张图和这个图片按位求异或,保证黑点和白点的总体分布最均匀。

当然,我们得在二维码的格式控制区域说明我们用的是哪个掩膜,保证能够恢复。

这就是QR二维码直观的构造了。

但是即使知道了这个构造,我们也无法随意改动某个像素点,因为这些数据都是经过精心处理的,我们首先得知道他的编码方式才能在不破坏编码信息的基础上寻找修改点阵的办法。

事实上QR二维码对数据采用的是Reed-Solomon编码,具体细节在之前的文章里有所介绍。简单的理解就是他把数据块$(d_1,d_2,d_3,…,d_n)$变成了$(d_1,d_2,d_3,…,d_n,c_1,c_2,…,c_m)$,增加了具有容错能力的冗余码。显然,我们只能直接操控数据,而不能直接决定冗余码。那么我们该怎么修改数据才能既保证数据意义不变,又显示期望的图案呢?这就用到了一个小trick。

我们知道QR二维码基本是用来表示URL的,而URL有个特点,就是可以任意添加锚点(#)而不影响显示的结果,比如https://blog.mythsman.com和https://blog.mythsman.com#this_is_junk,这两个url在显示的时候是完全没有区别的。也就是说只要数据是url,我们就可以在他后面添加‘#’再加上任意的数据来控制二维码的图形。这样一来,经过编码后的数据就变成了三部分:开头是原始数据加上’#’符号,中间是我们用来绘图的数据,最后是校验数据块。对于图片而言就是两头是不可控的数据,中间是可控的数据。这样弄完之后,可以呈现的效果大概是这样:

可以很明显看到中间的图片显示的很清楚,只是两边都看不清。

可是这种图看上去还是太生硬了,很明显就是刻意修改的数据嘛。不过还好,我们还有另一个办法让他看上去更自然。

回顾下之前的Reed-Solomon编码,我们知道若将$b_1,b_2$的RS编码后的值$c_1,c_2$进行异或得到$c_3$,那么这个$c_3$恰好就是$b_1,b_2$异或后的值的RS编码。这样我们就可以在保证编码合法的情况下对数据矩阵进行初等变换。原本我们只能操作整个数据中间的区域,可是经过初等变换(也就是高斯约旦消元)之后,我们就可以把能够操作的区域和不能操作的区域进行混合,这样就能把图像的控制区域放大,只是这样会牺牲图像的清晰度。不过这种图像却会给人一种自然朦胧的感觉,还是非常炫的。

参考资料

二维码的生成细节和原理
二维码 QR码编码原理详解
QArt Codes
令人拍案叫绝的15个二维码
百度加入二维码之争,推出静态图像版、动态gif版“梦幻二维码”
百度百科-二维码

伽罗华域性质简析

Posted on 2016-09-11 | In Maths

伽罗华域名字听起来挺酷的,其实就是有限域。域这个东西由于他能够进行满足加减乘除四则运算,在加密解密、编码解码当中应用非常广泛。但是我们常见的实数域却无法直接在计算机中精确的保存,因此有限域这类能够支持四则运算而且能够用有限的编码精确保存的东西就非常有用了。

域

域的定义在各种与离散数学相关的地方都能看到,看上去定义的十分啰嗦,说白了其实就是从有理数抽象出来的一种集合,能加能减能乘能除,满足各种四则运算律。普通的域没啥特别之处,感觉实际上用处不大。

有限域($GF$域、伽罗华域)

有限域的性质相比域来说就诱人多了,除了域的通用特点外,他还能够将所有运算的值在有限的数位内表示出来。这对数的保存而言特别有利。 常见的有限域当然就是模素数域了,比如模7域$GF(7)=\{0,1,2,3,4,5,6\}$,这类域能够生成阶数为指定素数的域。但是很多情况下,我们需要域的阶数不是一个素数,而是一个类似于$2^n$之类的数,比如一个字节的大小$2^8$,如果能把这个范围的数用一个域来映射,那对很多计算是特别有帮助的。在要求不严格的情况下,我们当然可以弃掉一些信息,截取一个小一点的素数,比如对于256来说,我们可以选择251,来构成模251域。不过毕竟信息有丢失,我们需要一个更加方便安全的方法。

$GF(2^n)$域

这个域的出现解决了上面的问题,他的阶数恰巧适应了二进制表示,比如常见的$GF(2^8)$就能正好表示一个字节的数。他的基本思想就是把数字映射到一个多项式,把他的四则运算变成多项式之间特殊的四则运算。

映射方法

对于$GF(2^8)$来说,一个8bit的二进制数就直接按照各个位上的值依次放在$x^i$的系数上(显然这个多项式各个位上的系数只能是0或1)。比如5的二进制表示是101,那么他对应的多项式就是$x^2+1$。

多项式计算

多项式的计算是问题解决的核心,普通的多项式以及正常的四则运算显然不是一个域。为了保证多项式能够构成域,我们特地设计了下面的运算。

正如整数集合不能构成域,但是模素数集合却能构成域一样。普通的多项式不能构成域,但是模素数多项式却能构成域。所谓的素数多项式,就是指不能被因式分解的多项式,这么设计的理由跟模素数域中选用素数的理由一样。对于$GF(2^8)$,素数多项式的最高项一定是8次,至于低次位的是多少我们并不在意。这个素多项式,我们叫做本源多项式。

加减运算:这上面定义的加减运算就是各个位的系数进行异或操作,显然加法跟减法是一个意思。

乘除运算:如果按照加减法的定义直接进行乘除运算的话会比较麻烦,通常的做法是用逆元表进行计算。我们知道有限域的生成元g可以直接用其自身的幂表示整个$GF(2^n)$域,那么域中的每个元素都可以表示成$g^i$。我们可以根据生成元g轻松确定这个i,那么他的逆元就是$g^(n-i)$。这样我么就可以打一个逆元的表,若要求$a*b$,我们直接去求$(a^{-1}*b^{-1})^{-1}$即可,只要查三次表。

当然,在具体的运算过程中还有很多细节可以优化,比如乘法过程可以用异或和位移运算来加速等等。

参考资料

Finite Field Arithmetic and Reed-Solomon Coding
有限域GF(2^8)的四则运算及拉格朗日插值
DataMatrix编码2——伽罗华域运算

Reed-Solomon纠删码简析

Posted on 2016-09-11 | In Maths

Reed-Solomon编码(又叫RS编码、里德-所罗门编码)作为一种前向纠错编码,是一种很常见的数据冗余技术,也就是通过对数据增加冗余部分来保证当数据丢失时能够在一定程度上进行恢复。最典型的应用就是在现在最流行的QR二维码的编码设计中。

实现功能

他所解决的问题是:给定$n$个数据块$(d_1,d_2,d_3,…,d_n)$,对于一个确定的正整数$m$, RS编码能够根据这$n$个数据块生成$m$个校验块, $(c_1, c_2,…, c_m)$。  我们能够做到,从这$m+n$个数据(或校验)块中任取$n$块就能解码出原始数据。 即对于$n$个数据块进行RS编码后生成的$n+m$个数据块,我们能够容忍丢失至多$m$个数据块。

编码原理

实现的功能听上去很强大,其实原理却十分简单,只是他很巧妙的运用了矩阵运算的特点,说起来也十分简单。

一、对于需要进行冗余处理的$n$个数据块,我们把他写成列向量的形式,其中每个数据块都可以看做是一个数。

二、生成一个变换矩阵,这个矩阵由$n+m$行和$n$列组成,其中上面的$n\times n$的部分是一个单元矩阵,下面的$m\times n$的部分是一个范德蒙矩阵。

三、用这个变换矩阵左乘数据列向量得到$n+m$位冗余码列向量。

算式如下:

$$\begin{bmatrix}1&0&..&0&0\\0&1&..&0&0\\..&..&..&..&..\\0&0&..&1&0\\0&0&..&0&1\\1^0&1^1&..&1^{n-2}&1^{n-1}\\2^0&2^1&..&2^{n-2}&2^{n-1}\\..&..&..&..&..\\ (m-1)^0&(m-1)^1&..&(m-1)^{n-2}&(m-1)^{n-1}\\m^0&m^1&..&m^{n-2}&m^{n-1}\end{bmatrix}\times \begin{bmatrix}b_1\\b_2\\b_3\\..\\b_n\end{bmatrix}=\begin{bmatrix}b_1\\b_2\\b_3\\..\\b_n\\c_1\\c_2\\c_3\\..\\c_m\end{bmatrix}$$

上面使用单位矩阵显然是为了保证原数据块在编码后不发生变化,从而看上去只是增加了冗余码。

下面使用范德蒙矩阵其实是为了保证这个矩阵任取$n*n$的部分可逆。

解码原理

根据上面的编码方式,我们可以很容易理解他的解码方法。对于编码后的数据块$(b_1,b_2,…,b_n,c_1,c_2,…,c_m)$,只需要任取其中的$m$个数据块,假设为$(b_{a_1},b_{a_2},…,b_{a_x},c_{b_1},c_{b_2},…,c_{b_y})$其中$x+y=m$,再用$f_i$表示编码过程中那个矩阵的第$i$行。显然,下面的等式一定成立:
$$\begin{bmatrix}f_{a_1}\\f_{a_2}\\..\\f_{a_x}\\f_{n+b_1}\\f_{n+b_2}\\..\\f_{n+b_y}\end{bmatrix}\times\begin{bmatrix}b_1\\b_2\\b_3\\..\\b_n\end{bmatrix}=\begin{bmatrix}b_{a_1}\\b_{a_2}\\…\\b_{a_x}\\c_{b_1}\\c_{b_2}\\…\\c_{b_y}\end{bmatrix}$$

这样一来,我们就可以把中间的数据矩阵解出来了。

计算域

上面的方法理论上是能够做到数据冗余处理的,不过由于作为一种编码技术,RS编码需要处理的是特定长度的二进制数据,然而求矩阵逆的过程是在实数域内进行的。显然特定长度的二进制位是无法准确描述实数的。因此RS编码的计算域采用的是能够用二进制位精确编码的伽罗华域$GF(2^n)$。这个域的特性就是非常适合处理$[0-2^n)$范围内数据的四则运算,而且这里的四则运算大都通过位运算处理,效率比较高。实际生产中由于数据的量会比较大,为了加快整个计算的过程,通常会采用离散傅里叶变换(DFT)及其逆变换来进行编码实现。具体的介绍可以参见这一篇文章。

重要特征

由于RS编码采用的是伽罗华域$GF(2^n)$,那他就有一个很有趣的特点,那就是对于数据$b_1,b_2$以及其分别对应的冗余码$c_1,c_2$,我们可以迅速的知道对于数据$b_1\otimes b_2$,他的冗余码就是$c_1\otimes c_2$($\otimes $表示异或)。

这一点其实很好理解,根据之前的编码过程,我们很容易知道对于实数域,如果数据$b_1,b_2$分别对应的冗余码是$c_1,c_2$,那么数据$b_1+ b_2$的冗余码就是$c_1+ c_2$。而现在的运算域是伽罗华域,在伽罗华域中加法做的就是异或操作。

参考资料

Reed Solomon纠删码
里德-所罗门码(RS 码) 简介
Finite Field Arithmetic and Reed-Solomon Coding

幂定律和齐夫定律

Posted on 2016-09-09 | In Data Mining

幂定律

幂定律又叫幂律,大量的事实表明,很多现象都服从类似于幂函数$y=cx^a$的形式,其中$a$是幂,而且通常是负数。

幂定律可以非常直观的用马太效应(Matthew effect)解释,说白了其实就是所谓的“富者越富,穷者越穷”。例如图书的销售,本来销售好的图书可能会发布更多的广告,做更多的营销从而导致销量更多。这样如果将图书销售量按照名次排列下来就会发现图书销量会随着名次的提高而指数性的提高。

齐夫定律

齐夫定律(Zipf’s Law)其实可以说是幂定律的一种形式,只是由于在曾经一次语料库的统计分析中由于拟合效果很好而广为人知。简而言之,他的内容就是对于一个语料库而言,我们统计他当中每一个词$i$出现的频率$f_i$与该频率在所有的词语中间的排名$r_i$,发现$f_i\times r_i$竟然近似等于一个常数$C$。当然,这只是一个经验定律,并不是定理、更不是理论。不过在自然界中有很多现象都满足这样一条规律而已。

不过这个定律的具体形式也不仅仅局限于上面的那个等式,而是有可能会变成$f_i\times r_i^\alpha =C$等等一系列形式。毕竟只是一个经验公式,拟合的时候相应的调节下参数也不是不可。

齐夫定律的简单应用

除了拟合预测,齐夫定律还有一个很有用的应用,就是在之前的一个实验中,我需要在某个城市的地图上上随机生成一些点来模拟人的位置,那么我该如何模拟更加真实呢?现实生活中人肯定不会是标准均匀分布的,如果使用正态分布的话,所有人都是以一个点为中心分布似乎也不太合理。这是就可以用到这个齐夫定律了。我们可以先假定总共有$N$个人,我们可以把人这些人分为$m$个人群,每个人群都是以二维正态分布的方式(选择好方差)聚集在中心点周围,而中心点可以以随机分布的方式从图中直接获取。至于每个人群的人数,我们可以按照齐夫定律,选择每个人群的个数。选择的方法也很简单,假设第$i$个人群的人数是$a_i$,解下面的方程组即可:
$$\left\{\begin{aligned}&\sum a_i=N\\&i*a_i=j*a_j\end{aligned}\right.$$

这样最后生成的随机分布就非常具有说服力了。

用于文档关键字提取的TFIDF指标

Posted on 2016-09-08 | In Data Mining

关键字提取问题

在大规模网络文章整合的过程中,我们经常需要对某一篇文章提取关键字。比如对于某一篇关于计算机的文章,我们应该提取出类似于“计算机”、“编程”、“CPU”之类的符合人类认知习惯的关键词,但是这个过程却不是那么容易。现在,我们把问题归结为,在不使用机器学习方法的情况下,给定一个文档集,仅从单词频率等角度对文档集当中的某一篇文档进行考虑,期望能够对于该篇文章,我们能从文章中依次提取出最有代表性的关键词。

我们很容易想到的方法就是统计每个词的词频了,但是对于任何文章而言,出现频率最多的应该是一些音节助词等毫无意义的词语,比如中文里的“的”、英文里的“is”之类的词语。这些词语我们通常叫他“停用词”。显然,在统计词频的时候我们首先得把他们去掉。但是去掉这些常见停用词之后,剩下的词语中出现频率高就真的意味着能够代表文章的主旨么?显然不一定,虽然在某些情况下,某个能表现主旨的词语会在文章中多次出现,但是在有些情况下这个表现主旨的词语只会偶尔出现,不过与此相对应的是,这些词在不同主旨的文章中出现的次数却应该更少。

针对上述的特性,就有了TF.IDF指标。他的意义就是词项频率(Term  Frequency)乘以**逆文档频率(Inverse Document Frequency)**。

词项频率

词项频率就是指在该文档中某一个词出现的频率,这个数值越高原则上就代表这个词语的得分就越大。当然,由于文章的词数可能会比较大,我们需要对这个频率进行下归一化。也就是说,假定文档集有$N$篇文档,$f_{ij}$表示词项$i$在文档$j$中出现的次数(或频率),我们把他的词项频率$TF_{ij}$定义如下:$$TF_{ij}=\frac{f_{ij}}{max_kf_{kj}}$$

意思也很简单,就是把这个词的频率除以这个文档中频率最高的词的频率,作为他的词项频率。这个数值保证了词的得分与词在文章中的频率正相关。

逆文档频率

逆文档频率顾名思义就是代表这个数值与该词项在所有文档中出现的频率逆相关,正如前面所说,一个词在所有文档中出现的次数越少就表示这个词越有可能代表某一个特定的主题。因此,我们再假定词项$i$在$N$篇文档集中的$n$篇文章内出现,那么我们把这个他的逆文档频率定义如下:$$IDF_i=log_2\frac N {n_i}$$

就是把出现频率$\frac{n_i}N$取个倒数再取个对数。这样既保证了该指数与文档频率逆相关,又保证了这个值的影响程度不会太大。

TF.IDF指标

最后我们只要把上面这两个指数相乘就可以得到该词项在该文档中的权重了,即词项$i$在文档$j$中的得分为:$$TF_{ij}\times IDF_i$$

其实这也算是个经验公式了,不过在很多情况下还是很准的。

CCMT收购SuperSU以后

Posted on 2016-09-04 | In 异闻堂

大概没有比 SuperSU 声誉更好、认可度最高的软件。无论是移动设备、平板电脑,亦或者机顶盒、智能手表,只要是基于 Android 的设备,大家都会想到使用 Chainfire 的 SuperSU 来接管 Android 系统的 Root 权限。

Read more »
1…383940…58

574 posts
69 categories
286 tags
© 2024 Companyd
Powered by Hexo
|
Theme — NexT.Muse v5.1.4