当我们在搜索引擎查询时,经常会用到关键词搜索,同样,很多网站有标签的功能,会根据网页自动生成标签,标签实际上就是该网页的关键词。
那么我们是如何通过关键词实现相关性搜索的呢?例如一个文档中出现频率越高的词是不是就是关键词呢?显然不是,一般一篇文档中出现最多的基本都是"的“、“是”、“你”等等这类词语,这些属于stop word,又比如一些词语“应用”、“文档”等等,这些词语虽然出现频率高,但是在大多数文档中都会出现,显然也不能算作关键词。那有什么好的方法来提取关键词呢?
TF-IDF算法是一种经典的统计算法,在elasticsearch5之前相关性搜索用的都是该算法(es5之后使用BM25算法)。该算法可以用来评估一字词对于一个文件集或一个语料库中的其中一份文件的重要程度。字词的重要性随着它在文件中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降。
TF:term frequence,词频。这个比较好理解,就是一个词在当前文本的出现频率。
IDF:inverse document frequency,逆向文本频率,是一个词语普遍重要性的度量。也就是说一些普通词,因此越普通的词,它的IDF越低。
计算方法:
TF:词语出现的次数除以该文件的总词语数。假如一篇文件的总词语数是100个,而词语“数据库”出现了3次,那么“数据库”一词在该文件中的词频就是3/100=0.03。
IDF:计算公式为log(全部文档数/检索词出现的文档数)。例如如果“数据库”一词在1,000份文件出现过,而文件总数是10,000,000份的话,其逆向文件频率就是 log(10,000,000 / 1,000)=4。
最终,“数据库”这个词语的分数为0.03 * 4=0.12,TF-IDF越高,说明这个词在该文本中越关键。
说了这么多,言归正传,在pg中相关性该如何使用呢,如何计算所有词语的IDF呢?
例子:
计算所有词(包括stop word)的idf
测试表,每条记录包含一个PK,同时包含一个文本
bill=# create table doc(id int primary key, info text);
CREATE TABLE
bill=# insert into doc values (1,'hi i am bill');
INSERT 0 1
bill=# insert into doc values (2,'hi i am abc');
INSERT 0 1
使用对应的分词(ts_config)配置,对每个文本进行分词,并计算出word在整表的idf,记录数越多,IDF越准确(类似文本训练)
bill=# select * from ts_debug('a b c hello i am bill');
alias | description | token | dictionaries | dictionary | lexemes
-----------+-----------------+-------+----------------+--------------+---------
asciiword | Word, all ASCII | a | {english_stem} | english_stem | {}
blank | Space symbols | | {} | |
asciiword | Word, all ASCII | b | {english_stem} | english_stem | {b}
blank | Space symbols | | {} | |
asciiword | Word, all ASCII | c | {english_stem} | english_stem | {c}
blank | Space symbols | | {} | |
asciiword | Word, all ASCII | hello | {english_stem} | english_stem | {hello}
blank | Space symbols | | {} | |
asciiword | Word, all ASCII | i | {english_stem} | english_stem | {}
blank | Space symbols | | {} | |
asciiword | Word, all ASCII | am | {english_stem} | english_stem | {}
blank | Space symbols | | {} | |
asciiword | Word, all ASCII | bill | {english_stem} | english_stem | {bill}
(13 rows)
统计IDF:
bill=# with t1 as (
bill(# select count(*) as cnt from doc
bill(# ),
bill-# t2 as (
bill(# select id, alias, token from
bill(# (
bill(# select id,(ts_debug(info)).* from doc
bill(# ) t
bill(# group by id, alias, token
bill(# )
bill-# select t2.token, t2.alias, log(t1.cnt/count(t2.*)) as idf from t1,t2 group by t2.token,t2.alias,t1.cnt;
token | alias | idf
-------+-----------+-------------------
am | asciiword | 0
| blank | 0
abc | asciiword | 0.301029995663981
hi | asciiword | 0
i | asciiword | 0
bill | asciiword | 0.301029995663981
(6 rows)
那么我们又该如何提取文档中的关键词呢?
计算每条记录(假设每篇文本一条记录)有多少词
bill=# set default_text_search_config='pg_catalog.english';
SET
bill=# select id, length(to_tsvector(info)) as cnt from doc;
id | cnt
----+-----
1 | 2
2 | 2
(2 rows)
计算每篇文档,每个词出现了多少次
bill=# select id, (ts_stat('select to_tsvector(info) from doc where id='||id)).* from doc;
id | word | ndoc | nentry
----+------+------+--------
1 | hi | 1 | 1
1 | bill | 1 | 1
2 | hi | 1 | 1
2 | abc | 1 | 1
(4 rows)