COS 交易酱的简易分词算法

http://cosjiaoyi.com 是个针对 cosplay 交易信息的搜索引擎,目前基本完成了对新浪微博特定信息的采集和入库,以及基于拼接 mysql 查询语句的搜索功能。

接下来还有很多要做的事情,包括扩展采集来源、改善垃圾信息过滤、优化前端等工作。不过今天先来讲一下打算做的自制分词算法。

对于动漫这一特定领域,很多常规的分词算法没有多大意义了。因为动漫圈存在大量的仅仅因为“好玩”而产生的各种昵称、诡称、简称。比如 “身陷” 指的是衣服的尺码,包括身高、三围等数据;比如 “穹妹” 指的是动画《缘之空》中的女主角兼主角他妹;比如 “面交” 当然是指当面交易的简称;比如 “炮萝” “秀萝” “叽萝” “咩萝” “丐萝” 都是网游剑网三里的职业人物角色的简称,哦,剑三又别称 “基三”,等等等等……还有一大现象是中英文数字合并成为一个词汇,比如 “PO主”。另外,又因为拼音输入法的原因,会出现“身限”=“身陷” 等现象。由于本来这些词汇也就是圈子生造词,也难以适用词汇纠错的方法。

wordsegment普通分词算法结果

对于业务场景而言,这里面的很多词汇都是没有多大意义的。而“黑猫”、“弹丸”、“小天使”之类的词能成为高频词,也仅仅是因为它们更接近于“普通词汇”,可以被常规的分词算法认出来。而剑三当中的 “X萝” 系列,甚至剑三这个词汇本身,都因为被分词算法切分,而没有进入这个图表中。

当然,技术上来说,只要有合理的分词词库,辅以相应的参数调整,还是可以做出一套适用于动漫分词的,但考虑到分词算法往往有几百个核心词汇、几千个附加词汇,每个词汇还都有词性标注,想想就算了……

在之前没考虑过这个问题,一是因为没有实际的业务数据积累,其实也没有考虑到这个问题,经过一段时间的推广和数据积累,现在可以研究了。二是因为对于 mySQL 的 like %…% 语法,尽管效率不高,但在目前的场景下,也没有遭受到多少压力。三是因为,呃,忙,呃,之前没时间做,呃,之后其实也不一定有时间做……

那简易分词打算怎么做呢?

现阶段的核心目的,还是在尽可能减少时间的前提下,能正确理解用户输入的“穹妹旗袍” 是指 “穹妹”+“旗袍”,这两个词汇不一定需要放在一起,只要在同一条微博中出现即可。

  1. 只考虑对 QueryRequest 的 wordstring 进行处理,而不考虑入库数据。内部查询在没有遇到性能问题前,依然使用 like %…%。目的仅仅是为了正确理解用户输入的查询,不至于因为用户输入“剑三的炮萝” 因为无法整串匹配到而搜不到结果,这种情况下应当可以正确处理为“剑三”+“炮萝”
  2. 只分析已经记录在服务器的 SearchQuery 表,不考虑对正文文本的分析。
  3. 和普通分词一样,还是基于词汇表,以及替代词汇表。先考虑分词,再考虑戳憋字。
  4. 基于业务,其实有大量的词汇是不需要的,比如“小天使”、“帮扩”等。对于用户而言,进入到本网站,场景就是搜索需要的东西,因而都是以具像物名词为主。没人会在搜索框里填 “小天使”、“随便” 这类词汇。甚至 “二手” 这个词都基本不会出现。

所以简易规则如下,可以覆盖 99% 的实际需求,计算量很小。

  1. 先对 querystring 根据 devide-word-list 进行切分,devide-word-list 包括 “ ”(半角空格)、“ ”(全角空格)、“的”,可能还会添加。但注意,没有“和”。因为在动漫中,“和”字会经常作为成词字或者人物姓名字出现,不能定义为分隔符。“日和” “和服” 等。由于并不是真的基于语义的分词,所以无法判断是成词字还是分隔字,根据历史搜索统计,只有 4% 的“和”是分隔词,交给用户自己调整没有问题。由于 devide-word-list 的数据量非常少,添加也很谨慎,所以可以直接写进代码里。
  2. 对结果的 querystring[] 根据 known-word-list,切分为 已知词块未知词块 的数组。known-word-list 的生成办法下述。
  3. 对于未知词块,根据词块长度,反复循环,进行简单切分,直到最细:
    • 词块中有英文串的,当英文串字母小于等于两个时,视为一个汉字,不切分。
    • 当英文串大于两个汉字的,视为一个词,对其前后切分,分为三段。
    • 当有数字现出时,视为一个词,对其前后切分,分为三段。
    • 对大于等于四字的词块,直接按两个字一词进行切分。(注意之前把1/2 个英文字母视作一个汉字,这里和中文等同视之)。
  4. 将结果的 querystring[] 数组,按每词之间 and 关系,对数据库进行查询。

比如:求黑子的篮球的火神大我红毛 私信po我。

  1. 变为 [求黑子,篮球,火神大我红毛,私信po我] (“的”和“空格”为分隔词)
  2. 变为 [求,黑子,篮球,火神大我红,毛,私信po我](“求”和“毛”为定义的已知词。
  3. 变为 [求,黑子,篮球,火神,大我,红,毛,私信,po我](“po”视为一个汉字)
  4. 实际 Search Request 即 求+黑子+篮球+火神+大我+红+毛+私信+po我

当然,通常没有这么长的需求,更多的是如下的:

剑三定国炮萝160包邮

  1. 还是 [剑三定国炮萝160包邮],因为没有分隔。
  2. [剑三,定国炮萝160,包邮],剑三和包邮应该会加作已知词。
  3. [剑三,定国炮萝,160,包邮],160 是数字单独成词。
  4. [剑三,定国,炮萝,160,包邮],“定国炮萝” 四字切分为两字两字。这就是最后的搜索请求词组了。

当然,在这种规则下,“雪露女武神”,会变成 “雪露”、“女武”、“神”,但又有什么关系呢,如果一个词汇真的必须是三字词,我使用两字“女武”+一字“神”,依然可以搜到库中的正确结果,这就足够了。

接下来说说怎么做已知词汇表。

  1. 人工整理,优先满足历史搜索词汇表。
  2. 暴力分词法:
    1. 直接把采集数据的正文部分全部提取。
    2. 把空格串、数字串、大于两字的英文串、符号串、链接地址、转发标志、“的” 等都视为分隔符替换掉,形成最初的文本块。
    3. 暴力地把所有文本块全部循环切分成两字一组,然后统计相同词汇并排序。注意是循环切分,比如“小天使”块应当切分成“小天”和“天使” 两部分。
    4. 再暴力地把所有文本块全部切分成三字一组,然后统计相同词汇并排序。
    5. 人工筛选排序最前的 1000 组词汇,足够了。

这个当然也有缺点,但在具体的场景下也已经够用了。动漫词汇尽管创意繁多,但基于交流之用,最常用的词汇还是排名比较靠前的。再通过人工筛选过滤掉通用但无用的词汇,就可以获得有效的 know-word-list 了。这种暴力切分法并不是我第一个用,http://www.guokr.com/blog/76814/ 这篇文章就讲到了用暴力切分法去切分古文词汇。其实还是挺实用的,毕竟我这里最后有个人工筛选过程。

====================下面是严肃的分割线=====================

其实中文分语无非两个部分:

  1. 句子匹配已登录词时,多种匹配方式如何选取最优?
  2. 未登录词如何鉴别词性?这中间又细分为如何通过大量文本收录未登录词,转为已登录词;以及在某段文本中确切出现的未登录词,如何进行处理两种情况。

而以上方案,按分词原则来看,其实都有些反其道而行:

  • 故意适当缩小已登录词,相对扩大未登录词范围。——这个是由业务实际情况经取舍后决定的。确实业务中只需要登录掉大部分 “专属通用词” 即可达到需要的效果。
  • 未登录词一律视为双字词。——这个是实际统计结果后决定的。
  • 把输入的正确性,以及由此导致的结果的正确性,责任全部交给用户自己去解决。——也是业务实际,以及以上两项得到的。

这些简化的前提,最重要的一点在于,Cos 交易酱并不需要真的去处理分词以后的结果,只需要确保分词结果能正确地,范围合适地在库中搜索到用户需要的数据即可。