MG--查询

查询 就是如何利用建好的索引来找到想要的文本,那么会介绍两种查询,一种是布尔查询(Boolean Query),另一种是排名查询(Ranked Query).
布尔查询 包含了一个术语列表,这些术语通过布尔操作符相连(and,or,not),查询的答案是满足规定条件的文档。
布尔查询的缺点是会不可避免的返回很多无关的答案,而且查询一些小的变动会导致结果完全不同。因为布尔查询是机械的,必须精确匹配布尔表达式的文档才是候选结果。
如‘data AND compression’ 和 ‘text AND compression’ 在人看来意思差不多,不过对于布尔查询就会返回完全不一样的结果。
尽管如此,布尔查询几十年来作为信息检索系统的主要技术满足了无数用户的需求。这个技术虽简单但确实是高效和可行的。

排名查询 就是通过度量每个文档和查询的相似度,把前r个最匹配的文档作为答案返回。这种查询相对于布尔查询,更灵活,不过也更复杂。
一直以来,人们花费了巨大的努力来研究查询的相似度度量问题和其他能够保持高召回率和精确率的rank策略。
最简单的rank策略就是简单计算查询术语在文档中出现的次数,这个方法称为‘坐标匹配 coordinate matching’。那么这个方法简单是简单,不过存在明显的缺陷,一是这个方法歧视短文档,二是当查询中包含常用词时,会歧视不包含此常用词的文档。
更复杂的rank技术就会考虑到文档的长度,并为每个术语分配数值权重,如余弦法。
排名查询更布尔查询比较,会达到比较出色的查询表现,但是这种方法需要更复杂的索引信息,并且处理成本更大。

访问字典的方法
在切入正题前,先解决几个问题,首先就是怎样高效的从倒排文件的字典中找到查询术语,从而得到这个术语的倒排列表地址。
倒排文件索引的字典存了哪些内容,术语和倒排列表的地址这个时必须的,然后还有些支持查询的辅助信息,如为了让倒排列表按词频升序被访问,存放包含术语的文档数量,其实就是列表中文档个数。
当然当这个字典比较小的时候,没什么好说的,我们要讨论的是当字典很大的时候,选取怎样的字典数据结构来尽量节省内存和时间的消耗。

定长字符串
最简单直观的方法就是用数组,每个单元包含一个字符串和两个整数域,这个字典数组按照字符串进行排序的,那么可以通过二分查找来定位某个单词,速度是很快的。
缺点就是耗费大量内存,这儿的字符串是定长的,需要为每个字符串分配size上限的空间,从而浪费了很多空间。

可终结字符串
那么针对上面的定长字符串的优化是把字典中的字符串存储成一个连续的长串,那么会节省很多空间,因为每个字符串只耗费了本身大小的空间,而不是原来的定长的大小上限。
这样必须在字典项中加个指针来指向该术语在长串中的开始位置,因为是连续存储的,所以下个术语的开始位置就是这个术语的结尾位置(所以无需额外存储)。

4项成块
上面那个方案需要为每个术语分配一个4字节的指针指向术语在长串中的位置,可能有些浪费。
那么把长串分块,每4个术语分成一块,只为每个块分配一个指针,这样通过二分法可以先找到某个块,对于一个块而言,怎么样来标识术语的分界了。
在每个术语前加一个字节的长度域,这样就可以知道术语的结束位置了。通过这个方法可以进一步的节省空间耗费。

前端编码
这个是一个比较有效的改进,就是在字典中术语是排序的,那么一个术语和前一个或前几个术语间会有一些共同的前缀,比如jezer,jezerit,jeziah,jeziel
前端编码包含共同前缀的字符数,后缀字符数,后缀
那么jezerit可编码为52it
jeziah可编码为33iah
这样确实可以节省很多空间,据说大约可以节省40%的空间。
但有个问题是,这样编码后,无法使用二分搜索了, 对于一个前端编码也许要往前找很多个术语才能知道这个术语具体是什么。
好吧,那就找个折中的办法,上面不是按照4个术语一块对长串进行分块了吗,那么对每块的第一个术语不采用前端编码,这样又可以采用二分法查找了。
对于jezer,jezerit,jeziah,jeziel这个块编码如下
 5jezer
52it
33iah
4 el
为了数据一致性,第一个术语前面加了5这个冗余数据,而最后一个术语不需记录后缀字符数,因为通过下个块的开始指针就知道这个术语的结束。

最小完美哈希函数

哈希函数可以将n个值映射到大小为m的范围内,最简单就是h(x) = x mod m,其中n/m为装载因子,这个值越小,发生冲突的可能性就越小。
冲突是不可避免的,而且比想象中更容易发生
生日悖论(birthday paradox),一年365天,要多少人,才能使得出现生日相同的人的概率大于0.5了?
这个问题就是m=365, 当n等于多少时,冲突概率大于0.5, 答案是23,就是说23个人当中出现生日相同的概率就已经大于50%了,所以发现生日相同的人不必大惊小怪。
n个值放到m个slot中,第一个肯定是不冲突的,第二个不冲突的概率(m-1)/m,第三个不冲突的概率(m-2)/m.....第i个(m-i+1)/m
所以连续插i个不冲突的概率是这些概率的连乘,m!/(m-n)!mn

完美哈希函数,就是对于任意的xi , xj ,当且仅当i=j时才有h(xi )=h(xj ),即不会出现冲突的hash函数。
最小完美哈希函数,MPHF(minimal perfect hash function), 就是m=n的完美哈希函数,装载因子为1。
保序的(order preserving)最小完美哈希函数,OPMPHF,具备若xi <xj , 则有h(xi )<h(xj )的最小完美哈希函数。

通用的完美hash函数是不存在的,MPHF都是针对某一个键值集合有效的,换个集合就不行了,所以都是要对单一静态集合预先计算的,但是这样确实能节省很多空间。
现在字典里面必须存储每个术语的字符串用于查找,如果有个MPHF可以将术语字符串直接映射成术语在数组中的地址,那么我们在字典中就不需要存储术语字符串了。
当然这个是假设这个字典是相对固定的,不太变的,不然就不可行了。
好吧,说的那么玄乎,这个最小完美哈希函数到底怎么得到。。。
先随机生成两个hash函数h1 ,h2 ,对字典中的每个术语计算hash值,假设字典术语个数为n,那么h1 和h2 的映射范围m要>=n,
术语        h1     h2     h
jezebel     5     9     0
jezer       5     7     1
jezerit     10    12    2
jeziah      6     10    3
jeziel      13    7     4
这儿以上面5个术语组成的字典为例,h1 和h2 两列表示通过随机选取的hash函数计算术语得到的hash值, h列是预期从保序的最小完美哈希函数得到的hash值。
当我们最终得到h列的hash值,它确实是保序,最小,并完美的。
假设有个hash函数g,可以求出h
h(t) = (g(h1 (t))+ g(h2 (t))) mod n
那么现在我只需要求出这个hash函数g,我们就可以达成我们的目标。

生成的随机函数h1和h2的映射范围是m,我们假设0~m-1为图中的m个顶点,每个术语形成一条两个顶点之间的一条边,这条边的两个顶点,分别是h1 (t),h2 (t),这条边的权值是h(t).
现在就可以来算g,从图上任意一顶点i开始,假设g[i]=0,例如我们上面的例子,
jezebel是条从顶点5到顶点9的边,权值为0,那我们从顶点5开始算,设g[5]=0
那么0+g[9]=0,所以g[9]=0
jezer, g[5]+g[7]=1,so g[7]=1
jeziel,g[13]+g[7]=4,so g[13]=3
然后没有相连的顶点了,所以再找个没有算过的顶点,再开始算,设g[12]=0
jezerit,g[10]+g[12]=3,so g[10]=2
jeziah, g[6]+ g[10] = 3, so g[6]=1
对于我们这个例子g函数就算完成了,目的达成。

这边有个问题,就是上面这个方法必须保证是个无环图,否则这个计算过程不会停止,会一直算下去了。
所以在算g前,必须先检测这个图是否是无环图,如果不是,需要从新生成随机函数h1 ,h2 ,直到得到一个无环图。
对于h1 ,h2 ,我们可以事先定义好随机函数族,
如h1 (t) = (∑ti *wi ) mod m,这样w就是可变参数,随机生成不同的w就会生成不同随机函数。
那么这个图是个无环图的概率有多大,这个取决于h1 ,h2 随机函数映射范围m值的选取,明显m越大,图的顶点越多,图就越稀疏,有环的可能性就越小。
m至少要>=n的,说是m=3n的时候,只需要检查1.7个图就可以发现一个无环图,但问题是存储g消耗的空间开销太大,相当于为每个术语字符串分配3个4字节的整数,还不如直接存储字符串了,都可以存12个字符了。
那么如果你不加大m,可以增加维度,随机生成3个随机函数h1 ,h2 ,h3 来算,思路一样。
这样的情况下,m/n大约为1.23的情况下,就可以在常数次的尝试中找到无环图。
这个方法真的很复杂,不过确实可以在空间上节省一些空间,而且应该可以达到很快的查找速度,因为直接可以算出位置不用找,局限就是只能针对静态的集合。

基于磁盘的字典存储
当然最简单降低字典对主存消耗的方法就是把字典放到磁盘上去,然后为了加快查找速度,只在内存中保留字典的索引,查找是从内存的索引中读出该术语属于的磁盘块号,然后去磁盘读出内容。
当然这个方法和上面内存中操作的方法比,肯定是慢了很多。

术语部分制定的查询
当给定的查询术语包含通配符,比如lab*,应该怎么处理。

字符串暴力匹配 brute-force string matching
暴力匹配,这个名字起的很摆,很简单,就是一个个术语进行模式匹配了,对于前面字典数据结构,可以通过每个块的第一个术语来缩小搜索范围。

用n-gram索引
如果允许使用额外的内存,就是拿空间去换时间,就可以实现快速匹配。
这个方法就是不光index这个词,把一个词分成n个语素进行index,比如labor分为la, ab, bo, or, $l, r$,这儿$表示字符串端字符,对这些语素进行index。
然后比如你要查询lab*r, 这个查询词可分为la, ab, $l, r$, 那么可以去查询同时包含这些语素的词,当然labor是符合的。
同样同时包含这些语素的词不一定就是你要找的,如laaber,lavacaber,所以对这些词还需要进行精确的模式匹配检查,以找到正确的答案。
n-gram只是过滤并且降低进行模式匹配的字符串数量的一种机制。

循环字典 Rotated lexicon
这个方法需要使用更多的内存,总之算法的优化不是拿空间换时间,就是时间换空间,有得必有失,看你更看重什么,和人生一样。。。
这个方法是这样的,举个例子,对于术语labor,我们还要索引以下几种形式,
labor$, abor$l, bor$la, or$lab, r$labo, $labor
有意思,为什么要index这些形式了,因为对于一般查询术语,我们可以通过二分法去查找,其实当通配符在词的末尾时,我们还是可以用二分法查找的。
比如lab*, 我们可以通过二分法找到所有有lab前缀的术语。但是对于通配符在词的其他任意位置,二分法就不好用了,比如lab*r, 或*bor, 怎么办了?
好这个方法就是把这个通配符循环到末尾去,如lab*r, 变成r$lab*, 这样我们又可以用二分法去查找了, 但是前提是我们对每个术语的这种形式要index过。
对于有多个通配符的情况,如果通配符在头尾,如*abo*, 这个好处理,旋转成abo*
如果不是头尾,这个有些复杂了,如la*o*r,我们只能找出通过旋转可以发现的最长字符序列来做查询,然后对查询出的候选集进行模式匹配筛选。
r$la*o*, 我们只能先去找所有以r$la为前缀的字符串的候选集,然后对这些词进行进一步的模式匹配来发现满足*o*的。

 

布尔查询
现在言归正传,最简单的查询类型就是布尔查询,查询术语由连接符AND,OR和NOT组成,使用倒排文件索引可以相对直接的处理这种查询。
由几个查询术语组成的布尔查询就是对这几个查询术语的倒排列表的并归操作。
在前面的倒排文件的字典中记录术语的频率,布尔查询就会用到这个辅助数据。
如在AND查询,合取, 会先对查询术语的频率进行排序,从频率最小的查询术语开始处理,这个是合情理的,合取这个候选列表只会越来越小,所以从频率最小,即倒排列表最小的开始,节省空间,而且一旦在某一个术语把所有文档都排除了的话,就不需处理剩下的查询术语。
另外从频率低的查询术语开始处理可以更加高效,可以拿二分查询来代替并归操作,因为倒排列表是有序的,所以二分查询可行。
例如候选文档集60,而一个术语的倒排列表长为60000,并归操作耗费要60000+60,而其实我们可以拿60个文档号去大列表里面去二分搜索,这样只需要log60000×60,1000左右。
但是这儿有个问题,倒排索引是压缩存储的,破坏了随机访问的性质,这样的话使二分查询无法进行,而只能使用线性的并归方法,所以使用压缩可以大量节约倒排列表的存储空间,但这使得合取查询付出了更多的处理时间,一切技术都有利弊啥。
那么如果一定要让这个压缩过的索引支持随机访问,那么也是可以的,就是加上个辅助的索引来间隔记录索引值,就是把索引列表分块,记录块头的索引值,和前面的字典存储分块机制很象。
这还是那个时间换空间,空间换时间的问题, 万变不离其中。

排名查询
布尔查询并非信息检索的唯一方法, 如果待查文档的精确子集已知的话,布尔查询很合适。但是一般情况下需要的信息并不是精确已知的,这时候我们就要考虑相似度,返回相似度较高的结果。
那么现在考虑的问题就是,怎样来度量一个查询术语和一个文档的相似度。

坐标匹配
这是一种比布尔查询简单的回答‘yes or no’更为灵活的方法,它计算查询术语出现在每个文档中的次数,次数越多, 相似度就越高。
这样一种简单的确定相似度的过程也可以形式化为查询向量和文档向量求内积。
当然这样一种简单的方法是有很多缺点的,
首先,没有考虑到词频,出现一次和出现多次没有区别
其次,没有考虑词的罕见性,词和词在信息量上是不同的
再者,就是歧视短文档
那么有问题就解决,先引入一些概念
wd,t ,术语t在文档中的权重,文档术语权重,document-term weight
wq,t ,术语t在查询向量中的权重
wt ,术语的权重(全局的,绝对的)
ft ,术语t的文档数
rd,t ,术语在文档中的相对词频
rq,t ,术语在查询中的相对词频
|Dd |,文档Dd的长度

那么这些概念有什么关系,

wd,t = rd,t

wq,t = rq,t * wt

rd,t 的取值有多种选择,这边列举3种,具体用哪一种看需要

rd,t =1,只考虑是否出现

rd,t = fd,t ,考虑出现的频率

rd,t =1+log(fd,t ),考虑出现次数的重要性,随出现次数增多,对rd,t 的影响趋于平缓,因为用了log

wt 的取值也有多种选择,这边列举2种

wt =1/ft ,词在越多文档出现,区分度越低,信息量越小,所以权值越低

wt =log(1+N/ft ),N为文档总数,log也是用来平缓曲线,不然ft =1的权重会被设为ft =2的两倍

|Dd |=∑fd,i 就是文档Dd 的长度

就用这些概念计算相似度就可以解决上面提到的问题, M(Q,Dd)= ∑Wd,t ·Wq,t /|Dd |

 

向量空间模型

更简单更直观的方法是把向量放到向量空间模型中去考虑相似度。

比较熟悉的方法是计算欧式距离 来度量向量间的相似度,

M(Q,Dd)= √∑|wd,t -wq,t |2  

不过这个方法有个明显的问题是,文档越长,和查询向量之间的距离就越大,会歧视长文档。

 

那么比合理的方法是考虑两个向量的夹角来度量向量的相似度,余弦法

X·Y =|X||Y|cos

cos(Q,Dd ) = Q·Dd /|Q||Dd |=  1/Wq Wd * ∑Wd,t ·Wq,t

其中Wd = √∑Wd,t2

wd,t = rd,t

wq,t = rq,t * wt

rd,t =1+log(fd,t )

rq,t =1

wt =log(1+N/ft )

Wq 不需要算的,因为对于任何查询都是一个常数,不会影响排名。那么上的式子变为,

cos(Q,Dd ) = Q·Dd /|Q||Dd |=  1/Wq Wd * ∑(1+log(fd,t ))·log(1+N/ft )

 

余弦法的实现

上面这个公式计算余弦,如果在大集合上直接计算,时间和空间的耗费都是高的,要想让余弦法可以被实际使用,必须要优化。

 

文档内频率

对于上面那个公式,Wq 不用算,Wd 和N全局的,ft 存放在倒排文件字典中,那么只要知道 fd,t 就可以算出。

那么如果要方便的得到这个fd,t ,就需要扩展现有的倒排列表

原来的倒排列表形式就是<3;1,2,5>,那么先除了要记录term出现的文档号,还好记录在这个文档中出现的频率

<3;(1,2),(2,1),(5,2)>

 好, 一个老问题出现了,直接存太浪费空间了啊,因为大部分出现频率应该都集中在一个较小的范围中, 压缩呗,一元编码,r编码。。。参考索引压缩技术。

 

余弦值计算方法

那么如上所述,Wd 假设已经被算好并存放在内存的一个数组中, 这块内存要能被d索引。

对于每一个document,我们都需要term by term的计算Wd,t ·Wq,t ,然后把值加起来,所以我们需要一个能被文档号d索引到的累加器集合。可以想象一下过程,对于每一个term,取出它的倒排列表,对于列表中的所有文档计算Wd,t ·Wq,t ,并把结果累加到各个d所对应的累加器上。

把每个累加器都除上Wd ,进行一个归一化操作

对累加器集合进行排序,取前r个最大值,作为结果返回。

对于这个算法,有3个问题,

首先Wq 不用算,因为对于特定的查询,这个值是个常数,不会影响rank。

再者,耗费的内存是很多的。

最后, 当r<<N的时候,对累加器集合整体排序是很浪费的。

 

Wd 所需的内存

继续优化,对于Wd 假设已经被算好并存放在内存的一个数组中,这个是比较耗内存的,对于TREC文档集合740000个文档,这个数组就需要3M内存。

那么优化方法很多,

把W数组放到磁盘上,时间换空间,所以会慢

在倒排列表里面存 fd,t 的时候,直接存成 fd,t /Wd ,这个方法不错,不过你要知道索引是要压缩编码的,这个一除变成了浮点数,需要个16或32bit取存储,原来压缩过可能只要2bit

都放磁盘慢,都放内存占空间,只能折中了,如果部分主存可用,可以在主存中存储近似权重,并且能够使用近似权重从磁盘权重文件中取出存储的精确文档权重。

这个方法比较复杂,不过有点意思。。。

这个算法首先一点,就是设计一种编码来以较少的比特数的代价来表示权重近似值

这个编码思路和方法应该可以作为一个通用的方法,研究一下

设对x的近似值进行编码,L,U 分别是x值的上下界,x的取值范围为[L,U]

如果我们要用bbit对这个值的近似值进行编码,那么我们编码的取值范围为[0,2b )

一个自然的想法就是,编码c= |(x-L)/(U-L) * 2b |

这就相当于把编码取值范围分为大小均匀的桶,用桶号来编码近似值。

这边桶是均匀分布的,当x特别小的时候(靠近L),会产生比较大的误差,因为均匀的桶,x再小最多也只会用第一个桶去编码,而第一个桶的上届就比原来的x大多了,而Wd 是作为分母的,所以当x比较小时,一个细小的误差都会导致, 1/Wd 产生一个较大的误差

所以想到采用宽度成几何基数增长的桶,这样可以让起始桶的宽度足够小,来减少误差

U = L*Bmax(c) ,max(c)=2b 从这个等式算出B

对于这样的编码方式, g(c)=L * Bc , x范围[g(c), g(c+1))

 

怎样利用这个近似值来优化计算,减少对硬盘的访问

1.首先利用Wd 近似值去计算余弦值,然后排序取出Top r

大家知道由编码c算出的值是Wd 取值范围的下届,那么Wd 在分母,那么算出的是余弦值的上届,也就是说算出的是余弦值可能取的最大值

2.对Top r 取精确Wd 值,并重新计算Top r的余弦值,得到Top r的余弦值集合H,记录下H中的最小值,min{H}

现在的状况是Top r的余弦值是精确的,剩余的文档的余弦值是极大值

3.那么对把剩余文档的余弦值和min{H}进行比较,如果小,那么肯定排不进Top r了,你极大值都比别人小,精确值就更小了

4.如果大的话,就取出该文档的精确Wd 值,计算精确余弦值,如果确实大于min{H},就和Top r中的一个文档进行替换,并更新min{H}

最后我们用这个方法,只需要访问比r次略多次的硬盘,就可以找出Top r

这个算法比较精彩,我当时在看之前也想过, 不过没想出这么好的。。。

 

累加器内存

上面提到了需要一个累加器,这个累加器的实现两种方法

第一种方法就是上面说的用一个简单的N项数组,对应N个文档,可以用文档号d进行快速存取,需要耗费4N字节。

第二种就是优化的方法,其实我们没有必要建N个累加器,因为对于一个查询,绝大部分的累加器是为0的,即一个查询只和一小部分文档有关,其他的文档 完全没有共同的词。所以可以用hash表来存储和查找,hash表非常适合非0累加器比文档数少很多这种情况,当然如果非0累加器很多那可能就划不来了。

很多论文都讨论如何通过限制非0累加器来进行优化, 最简单的办法给累加器的个数定一个可接受的先验界限,priori bound,当达到界限就不增加累加器的个数。实际上,只允许高权重术语选择累加器,而低权重术语就没有机会了。

这个方法其实就是只由高权重术语来选择rank候选集,这样不但能够减少内存的使用,还能节约时间。当累加器界限达到是有两种策略,

最简单的是当达到界限时,不处理剩余的所有术语,称为退出策略,这样很快,但是会影响查询精确性

还有一种当达到累加器上限后,还是处理剩余的术语,但每个术语的倒排列表无需被全部处理,只需处理累加器集合中有的文档,其他文档都可以忽略,达到跳读的效果,称为持续策略,实际上采用这种策略的检索效果比精确的余弦法还要好。

限制累加器的数目,可以采用启发式的规则,比如剪枝pruning策略,按照wt 降序排列,只要满足wt > max{wt }/3,即可创建累加器。

还有一种限制非0累加器的算法,比较复杂,不光根据wt ,还要根据 fd,t 来筛选累加器,因为有些wt 小, 但fd,t 很大,也会带来比较大的余弦贡献,应该说这样的考虑更合理一些。

不过这种方法需要在倒排列表中改变结构,从按照文档号排序变成按照 fd,t 排序,这样一搞,这个倒排列表只能用于rank查询,对于布尔查询就低效了,所以不具体描述了。

 

排序

最后一个优化的环节就是排序,用传统的排序算法对N个记录进行排序至少需要NlogN次比较,如果N=1000000,就需要2000万次比较。

各种方法都被用来降低排序的时间,最直观的就是尽量限制累加器的数目,这样会大大减少需要排序的记录数,肯定会快很多。

那么还有一个可以大大提高排序效率的是,关键是r(需要展示的文档数)在大部分情况下远小于文档总数N,那么所以从集合中找出r个最大值和排序整个集合是完全不同的命题。这就是个 r-selection问题,解决这个问题的标准方案就是堆。

对于N=100万的记录, 建最大堆,需要小于200万次比较,然后去前100个值的比较次数4000,所以工作量只有全排列的10%,堆真是个高效的数据结构啊。

 

那么排序完后,我们需要取前r个记录的文档地址,那么显然,要么把所有文档的文档地址放内存里,这样快但占内存,要么都放磁盘上,这样对于每个查询,我们要多做r次磁盘读取操作。

最好的是只在内存中放前r个的文档地址,其实也就是在前面需要读磁盘的时候随便把文档地址读出来。

记不记得前面存储Wd 的方案和算法,可以把文档地址的读取加到那个算法里面去,那个算法改成这样,

1.首先利用Wd 近似值去计算余弦值,然后排序取出Top r

2.对Top r 取精确Wd和文档地址 ,并重新计算Top r的余弦值,得到Top r的余弦值集合H,记录下H中的最小值,min{H}

可以看到,我们在读取Wd的同时读取文档地址,就是说我们先吧 Wd 和文档地址一同存在了文件中。

3.那么对把剩余文档的余弦值和min{H}进行比较,如果小,那么肯定排不进Top r了,你极大值都比别人小,精确值就更小了

4.如果大的话,就取出该文档的精确Wd和文档地址 ,计算精确余弦值,如果确实大于min{H},就和Top r中的一个文档进行替换,并更新min{H}

用了这样的方法就巧妙的将文档地址的读取一起完成了, 对于min{H}的取得,可以堆H集合建立最小堆,就方便了。

可见使用排名查询,实现余弦法是件比较复杂的事,实在佩服这些牛人为了提高效率和节省资源所想出那么多有意思的算法。

 

检索效果评价

简单的说就是看精确率和召回率

通俗的说,精确率就是找到的结果中有多少是正确的, 召回率就是正确的结果中我们找到了多少

这两个指标很难都达到很高,鱼和熊掌难以兼得,所以看情况去balance,看项目中更看重什么。

当然还有其他的指标,这边就不说了



posted on 2011-07-04 20:39  fxjwind  阅读(686)  评论(2编辑  收藏  举报