2.1 文本预处理的流程
在介绍词向量之前,我们需要先对文本预处理的流程有基本的了解。文本中通常包含缩写和标点符号等标记,以汉语为代表的东亚语言在书写时通常没有表明单词之间间隔的空白标记,这些都需要提前进行处理,产生一个词的序列作为后续语义分析模型的输入。
Python中可以选择的分词库有很多,英文常用的有NLTK[48],spaCy[49]等;中文常用的有jieba,THULAC[50],PKUSEG[51]等。NLTK是一个比较老牌的自然语言处理工具包,除分词器外还集成了很多其他模块,例如语料库、句法分析等;spaCy则刚刚崛起,以工业级的强度著称,分词速度非常快;jieba是最早的Python中文分词库之一,久经考验;THULAC和PKUSEG都出自高校,前者准确率高、速度快,后者则专注于为不同垂直领域的文本提供专用的分词工具。下面两个是分别用NLTK和jieba做词例化的例句,其中英文选自莎士比亚的《安东尼与克莉奥佩特拉》[52],中文选自鲁迅的《故乡》[53]:
1. import nltk 2. import jieba 3. 4. sentence = ″There’s beggary in the love that can be reckoned.″ 5. tokenizer =nltk.tokenize.NLTKWordTokenizer() 6. print(tokenizer.tokenize(sentence)) 7. 8. sentence = ″其实地上本没有路,走的人多了,也便成了路。″ 9. print(jieba.lcut(sentence))
在分词结果(由词例组成的列表)中,缩写There's被拆分成There和's两个词,所有的标点符号也被单独拆开。
1. [′there′, ″′s″, ′beggary′, ′in′, ′the′, ′love′, ′that′, ′can′, ′be′, ′reckoned′, ′.′] 2. [′其实′,′地上′,′本′,′没有′,′路′,′,′,′走′,′的′,′人′,′多′,′了′,′,′,′ 也′,′便′,′成′,′了′,′路′,′。′]
词例化的动机可以通过一个简单的例子来说明。假设语料库中“路”在句中或句首出现过100次,逗号“,”和句号“。”分别出现过1000次,句末紧跟标点的“路,”和“路。”各自只出现过一次,那么:
· 通过把句末单词和其后的标点拆分开,词表中将只包含“路”、逗号“,”和句号“。”,并且这三个词型各自分别出现过至少100次。
· 如果不拆分句末单词和其后的标点,那么“路,”和“路。”分别只会出现一次,同时词表中还会包含大量其他带有句末逗号“,”或句号“。”的出现频率很低的单词。
如果一个特征在训练集中出现的频率过低,模型将很难对它进行充分学习,这被称为数据稀疏性(Data Sparsity)问题。假如不进行这样的切分,后续的模块将不知道There's与There和's的关系,会把“路,”和“路。”当成两个完全不同的单词,使得待处理的词表变大,数据更加稀疏,模型参数估计更不准确。
当然,如何对语料进行处理也依赖于具体的任务。如果任务本身比较简单,就可以采取比较粗放的语料处理方式,例如仅仅根据空格来切分单词,或是直接以字为单位进行处理。
值得一提的是,很多任务上目前最先进的模型并不是以词为输入粒度的。越来越多的模型开始使用子词单元,即将单词进一步切分成词根、词缀等更小的部分。这种切分不是基于语言学家的知识,而是基于语料库中的统计量:如果语料库中某几个字符总是在一起出现,那么子词切分算法就会把它们当成一个整体来看待;否则就倾向于进一步拆分出更小的单元,直至单个字符。这种方法可以更好地利用单词的形态信息,特别是在单词形态变化较多的语言中,进一步减少数据稀疏性和未登录词(Out-Of-Vocabulary Word)(2)的出现。同时,子词单元还可以用于迁移亲属语言或者外来词的知识,实现跨语言的知识共享,例如在英德翻译中共享英文和德文的子词词表,可以让两种语言中的同源词训练得更加充分。常用的子词切分算法有字节对编码(Byte Pair Encoding)[54]和SentencePiece[55]等。在2013年左右,自然语言处理系统的词表大小经常多达上百万;而在子词单元流行开以后,常见模型只需要处理十万以内的词表。