Prerequisite
倒排索引是所有支持全文搜索的数据库的基础。比如 i am iron man
和 i will be soon back
,欲查找 be
,先查第一句,再查第二局,这是正排;将每个单词提取出来形成一个排序,i {1, 2}
、am {1}
和 be {2}
形成一个排序,再要搜索 be
的时候,立刻就搜索到了,并且知道对应第二句,这就是倒排。
MongoDB 内置的默认分词器,是按空格分切即可,这是因为英文默认的,那么倒排的最小单位就是单词。如果文档中存在中文,句子直接没有空格,那么倒排的最小单位就是一个字了,解决这个问题也很简单,就是找到最合适的中文分词。
一元分词和二元分词
所谓一元分词:就是一个字一个字地切分,把字当成词(我是团子
-> 我
、是
、团
、子
)
所谓二元分词:就是按两字两个分词(我是团子
-> 我是
、是团
、团子
)
很正常的想到,如果将二元分词,作为中文的倒排最小单位,那么就可以全文检索中文了,那么来写实现吧 ~
编写索引程序
先来写二元分词生成器,这个很简单
def bigram_tokenize(word):
return ' '.join(word[i:i + 2] for i in range(len(word)) if i + 2 <= len(word))
print(bigram_tokenize('我是团子')) # 我是 是团 团子
再对 MongoDB 建立二元分词索引,采用的方法是:创建 _t
字段,用于储存一个句子的全部二元分词即可
我选择使用 pymongo 实现
"""
# 实际文档
{
_id: ObjectId("630d708529bd493430410366"),
desc: '切换至聚合模式',
method: 'put',
uri: '/api/v2/mail_setting',
body: '{\n\t"general": {\n\t\t"browse_mode": 1\n\t}\n}'
}
"""
# 编写二元分词索引程序(用 pymongo 实现)
from pymongo import MongoClient
client = MongoClient('localhost', 27017)
db = client["邮件"]
collection = db['邮件多选']
def build_fts():
# 在 _t 字段建立全文索引
collection.create_index([('_t', 'text')])
# 在 _t 字段储存 desc 的全部二元分词
for item in collection.find():
collection.update_one({'_id': item['_id']}, {'$set': {'_t': bigram_tokenize(item['desc'])}})
if __name__ == "__main__":
build_fts()
二元分词索引
那么再来看一下,如何实现检索的,不妨在 MongoDB 数据库中查看
# 获取全部索引
# db.邮件多选.getIndexes()
# 在 _t 字段建立全文索引
# db.邮件多选.ensureIndex({_t:"text"})
# 获取集合下全部文档
db.邮件多选.find().pretty()
[
{
_id: ObjectId("630d708529bd493430410366"),
desc: '切换至聚合模式',
method: 'put',
uri: '/api/v2/mail_setting',
body: '{\n\t"general": {\n\t\t"browse_mode": 1\n\t}\n}',
_t: '切换 换至 至聚 聚合 合模 模式'
},
{
_id: ObjectId("630d708529bd493430410367"),
desc: '切换至非聚合模式',
method: 'put',
uri: '/api/v2/mail_setting',
body: '{\n\t"general": {\n\t\t"browse_mode": 2\n\t}\n}',
_t: '切换 换至 至非 非聚 聚合 合模 模式'
},
{
_id: ObjectId("630d74dac79d6354805b86c3"),
desc: 'all',
method: 'put',
uri: '/api/v2/mail_setting',
body: '{\n\t"general": {\n\t\t"browse_mode": 1\n\t}\n}',
params_dict: { mail_id: '1438', mbox: 'INBOX' },
extracts_dict: { result: '$.result' },
validations_dict: { '$.result': 'ok' },
_t: 'al ll'
}
]
# 检索目标分词
db.邮件多选.find({$text:{$search:"聚合"}})
[
{
_id: ObjectId("630d708529bd493430410366"),
desc: '切换至聚合模式',
method: 'put',
uri: '/api/v2/mail_setting',
body: '{\n\t"general": {\n\t\t"browse_mode": 1\n\t}\n}',
_t: '切换 换至 至聚 聚合 合模 模式'
},
{
_id: ObjectId("630d708529bd493430410367"),
desc: '切换至非聚合模式',
method: 'put',
uri: '/api/v2/mail_setting',
body: '{\n\t"general": {\n\t\t"browse_mode": 2\n\t}\n}',
_t: '切换 换至 至非 非聚 聚合 合模 模式'
}
]
再用 pymongo 来实现
from pymongo import MongoClient
client = MongoClient('localhost', 27017)
db = client["邮件"]
collection = db['邮件多选']
def bigram_tokenize(word):
return ' '.join(word[i:i + 2] for i in range(len(word)) if i + 2 <= len(word))
# 全文检索
for post in collection.find({'$text': {'$search': f'"{bigram_tokenize("聚合模式")}"'}}):
pprint.pprint(post)
{'_id': ObjectId('630d708529bd493430410366'),
'_t': '切换 换至 至聚 聚合 合模 模式',
'body': '{\n\t"general": {\n\t\t"browse_mode": 1\n\t}\n}',
'desc': '切换至聚合模式',
'method': 'put',
'uri': '/api/v2/mail_setting'}
{'_id': ObjectId('630d708529bd493430410367'),
'_t': '切换 换至 至非 非聚 聚合 合模 模式',
'body': '{\n\t"general": {\n\t\t"browse_mode": 2\n\t}\n}',
'desc': '切换至非聚合模式',
'method': 'put',
'uri': '/api/v2/mail_setting'}
重点就在于,只用检索其中的文字(如 “切换至聚合模式” 中的 “聚合模式”),就可以检索出来
本质就在于,二元分词之间有空格隔开,所以可以像英语那样搜索得到
初步结果,可以实现中文全文检索,但效率不高,因为一个句子的二元分词占用太多,如果可以缩减分词数量就好了
优化
结巴中文分词优化,最流行的Python中文分词组件,它有一种搜索引擎模式,在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词
import jieba
import paddle
import pprint
paddle.enable_static()
test = "批量星标(前两封邮件打星标)"
jieba.enable_paddle()
strs=[test]
for str in strs:
seg_list = jieba.cut(str,use_paddle=True) # 使用 paddle 模式
print("Paddle Mode: " + '/'.join(list(seg_list)))
seg_list = jieba.cut(test, cut_all=True)
print("Full Mode: " + "/ ".join(seg_list)) # 全模式
seg_list = jieba.cut(test, cut_all=False)
print("Default Mode: " + "/ ".join(seg_list)) # 精确模式
seg_list = jieba.cut(test) # 默认是精确模式
print(", ".join(seg_list))
seg_list = jieba.cut_for_search(test) # 搜索引擎模式
print(", ".join(seg_list))
Paddle Mode: 批量/星标/(/前/两封/邮件/打/星标/)
Full Mode: 批量/ 星/ 标/ (/ 前/ 两封/ 邮件/ 打/ 星/ 标/ )
Default Mode: 批量/ 星标/ (/ 前/ 两封/ 邮件/ 打星标/ )
批量, 星标, (, 前, 两封, 邮件, 打星标, )
批量, 星标, (, 前, 两封, 邮件, 打星标, )
其他优化
- 组合全文索引(Compound textIndex)
- 用户体验优化
- 实时性优化