作者 | mczhao,携程高级研发经理,关注自然语言处理技术领域。
概览
随着消费热点和网红新梗的不断涌现,在电商平台的NLP任务中,经常会出现一些之前没有见过的词。这些词不在系统已有的词库中,被称为"未登录词"。
一方面,词库中词的缺失影响了一些基于词库的分词器的分词质量,间接影响了文本召回质量和高亮提示的质量,即用户文本搜索的准确性和搜索结果的可解释性。
另一方面,在主流NLP深度学习算法BERT/Transformer等等中,对中文处理时经常使用字向量来代替词向量。理论上使用词向量的效果应当是更好的,但是由于未登录词的原因,在实践过程中使用字向量的效果更好。如果词库更加完善,那么使用词向量的效果将优于使用字向量的效果。
综上,新词发现是我们当下需要解决的问题。
一、传统无监督的方法
中文新词发现这个问题,在业界已经有了较为成熟的解法。输入是一些语料,将这些文本做NGram切分以后,产生候选片段。计算这些片段的一些统计特征,再根据这些特征判断这个片段是不是一个词。
业界主流的做法是统计和观察这三个方面的指标:热度、内聚度、左右邻字丰富度。描述这三个指标的文章网上也有很多,这里简单介绍一下,细节可以参考Hello NLP和Smooth NLP的两篇新词发现文章。
1.1 热度
使用词频来表示热度。统计所有语料的所有片段的出现次数,那些高频的片段往往就是一个词。
1.2 内聚度
使用点互信息衡量内聚度:
例如,我们判断汉庭是不是一个词,log(P("汉庭")/P("汉")P("庭"))。汉庭成词的概率,和"汉庭"的热度成正比,和"汉"、"庭"的单字热度成反比。这个很好理解,像是汉字中最常出现的字"的",随便一个汉字和"的"搭配的概率非常高,但是并不意味这"x的"或者"的x"就是一个词,这里"的"的单字热度就起了一个抑制的作用。
1.3 左右邻字丰富度
左右邻接熵来表示左右字的丰富程度。左右邻接熵就是候选词片段左边或者右边出现的字的分布的随机性。可以拆开看左边的熵和右边的熵,也可以把两个熵合并为一个指标。
例如,"香格里拉"这个片段其热度和内聚度都非常高,对应其子片段"香格里"的热度和内聚度也很高,但是因为"香格里"后面大部分情况都出现"拉"字,它的右邻接熵很低,对其成词起抑制作用,可以判断出"香格里"三字不能单独成词。
二、经典方法的局限性
经典方法的问题在于需要人工设置阈值参数。一个NLP专家在了解当前语料中片段的概率分布以后,将这些指标通过公式组合或者独立使用,然后设定阈值来作为判断标准,使用这个标准判断的结果也可以做到很高的准确度。
但概率分布或者说词频并不是一成不变的,随着语料库越来越丰富,或者语料的加权热度(通常是对应的商品热度)波动变化,专家设定的公式中的参数和阈值也需要不断调整。这就浪费了很多人力,使人工智能工程师沦为调参侠。
三、基于深度学习的新词发现
3.1 词频概率分布图
上述业界已有算法的三个指标,根本来源的特征只有一个,就是词频。在统计学的方法中,通常会把一些简单又关键的统计量以图片的方式展示,比如直方图、箱线图等等,即使没有模型介入,光凭人看,还是能够一眼做出正确的判断。可以把语料切出所有长度限定的片段,把片段的词频归一化为0-255,映射为二维矩阵,行表示起始的字符,列表示终止的字符,一个像素点就是一个片段,像素点的明暗程度就是这个候选词片段的热度。
上图是"浦东机场华美达酒店"这个短句的词频概率分布图,我们惊喜地发现,光凭我们的肉眼,也大致可以分出一些较为明亮的、等腰直角三角形的区块,比如:"浦东"、"浦东机场"、"机场"、"华美达酒店"等等。这些区块可以判断出对应的片段正是我们需要的词。
3.2 经典图像分割算法
通过观察词频概率分布图,我们可以把一个短句分词问题转变为一个图像分割问题。早期的图像分割算法,和上述的新词发现算法差不多,也是基于阈值的检测边缘灰度变化的算法,随着技术发展,现在一般使用深度学习算法,其中比较著名的是U-Net图像分割算法。
U-Net的前半部分使用卷积下采样,提取多层不同粒度的特征,后半部分上采样,将这些特征在同一分辨率下concat起来,最后通过全连接层+Softmax得到像素级别的分类结果。
3.3 基于卷积网络的新词发现算法
对词频概率分布图的切分和对图的切分类似,都是将位置相邻并且灰度相近的部分切出来。所以对短句的切分,也可以参考图像分割算法,使用全卷积网络来做。使用卷积来做的原因是,无论我们在切割短句或者图像的时候,都更多的关注局部信息,就是靠近切割边缘那些像素点。使用多层网络的原因,多层的池化可以表现出对不同层特征的阈值判断,例如我们对地图地形切割的时候既要考虑坡度(一阶导/差分)还需要考虑坡度的变化(二阶导/差分),两者分别取阈值并且组合方式不仅仅是简单的线性加权而是串行的网络。
对于新词发现场景我们设计如下的算法:
- 先把短句的词频分布图用0填充到24x24;
- 先有两个3x3的卷积层,并输出4通道;
- 把两个卷积层concat起来,再做一次3x3的卷积,并且输出单通道;
- 损失函数使用logistic=T,所以最后一层不用做softmax输出即可用于分类;
相比于U-Net,有如下差异:
1)放弃了下采样和上采样,原因是一般用来分割的短句比较短,词频分布图的分辨率本就不高,所以模型也随之简化了。
2)U-Net是三分类(分块1、分块2、在边缘上),此算法只需要二分类(像素点是否是一个词)。所以最后输出的结果也是不一样的,U-Net输出一些连续的分块和分割线,而我们只需要某个点是不是阳性的。
下图是训练完模型以后,用模型预测的结果。我们可以看到输出结果中,"上海"(上这一行、海这一列)、"虹桥"、"商务区"这三个词对应的像素点被识别了出来。
使用训练好的模型,输入携程地标库中的地标名称,可以自动切分和发现出一些新词,如下图,虽然有个别badcase,总体上准确率还可以。
将这些词导入到词库以后,搜索分词的准确率上升,分词结果的词库覆盖率上升。因为搜索分词中一般倾向过召回而杜绝漏召回,业界有更激进的按字分词召回的做法,而准确率一般通过后续的排序解决。所以分词准确率提升了,在用户看来搜索结果准确率并没有明显提升。但是可以解决部分因分词错误导致的高亮提示不正确的问题。
四、模型内部分析
如果想探究模型是怎么生效的,可以查看中间层的卷积核。我们先将模型卷积层的卷积核个数从4简化到1,训练以后,通过TensorFlow的API查看中间层:model.get_layer('Conv2').__dict__。我们发现Conv2层的卷积核如下:
可以看到第一行和第二行对模型的效果是相反的,对应了该像素点的上一行减掉当前行的差分(带权重),如果灰度差异越大,这个像素点代表的字符串越有可能成词。
还可以看到第一行第二列0.04505884的绝对值比较小,可能是因为第一行减第二行的正向参数和第三列减第二列的负向参数相互抵消。
五、优化空间
本文描述的是一个结构非常简单的全卷积网络模型,还有很大的提升空间。
一是扩展特征选取范围。比如,本文中输入特征只有词频,如果把左右邻接熵也纳入输入特征,切分的效果会更加精准。
二是增加网络深度。通过模型分析,发现第一层卷积主要是为了应对那些用0填充的像素点产生的case,实际关注真实热度的卷积只有一层,如果是3x3的卷积核只能看到一阶差分结果,当前像素的的前后第二行和第二列就没有考虑到。可以适当扩大卷积核大小或者加深网络,来使模型的视野更大。但加深网络也会带来过拟合的问题。
最后,这个模型不仅仅可以用来补充词库以提高分词效果,并且可以直接用作分词的参考,在分词流程的候选词召回和分词路径打分这两个步骤中都可以应用这个模型的预测结果。