原文出处:How to solve 90% of NLP problems: a step-by-step guide
原作者:Emmanuel Ameison
文本信息无处不在
不管你在老牌公司还是在致力于推出新服务,你总是可以利用文本信息来验证、提升你的产品以及扩展产品功能。学习文本信息并从中提取含义的研究被称为自然语言处理(NLP)。
NLP是一个很大的领域,每天都会产生令人激动的新成果。Insight通过和数百个公司合作后,发现以下几个应用场景在实践中出现的频率最高:
1. 识别不同的用户/客户类别(比如预测用户流失,生命周期价值,产品偏好分析)
2. 精确识别和提取不同类别的反馈(正面和负面评价/观点,特定属性的提及比如衣服的尺寸/是否合身)
3. 根据意图给文本分类(比如区分普通的请求和紧急请求)
尽管网络上有很多的NLP论文和教程,我们发现很难找到一个合适的指南来告诉我们如何有效地从头至尾处理这些问题。
这篇文章告诉你什么
在一年中指导了数百个项目, 获取了来自全美国顶级团队的指导后,我们希望通过这篇文章来解释如何为上述问题建立好的机器学习方案。我们将从最简单的方法开始,然后过渡到更多的细节,比如特征工程、词向量和深度学习。
通过阅读文本, 你将学会:
1. 收集、准备和验证数据;
2. 建立简单的模型,必要时过渡到深度学习;
3. 理解和解释你的模型,保证你确实捕获了有效信息而不是噪声。
我们把这篇文章定义为手把手教程,也可以作为高效通用方法的概述。
这篇文章有一个配套的notebook来运用和展示这些技术,你可以运行这些代码来更好地学习。
第一步:收集数据
示例数据源
任何机器学习的问题都是从数据开始, 比如邮件、帖子或者推文的列表。文本信息的常见来源有:
1. 产品评价(Amazon,Yelp 以及 App store上的各种APP)
2. UGC(推文/微博、Facebook帖子, StackOverflow问题)
3. 故障排除 (客户需求,售后问题, 聊天记录)
“Disasters on Social Media” 数据集
这篇文章中,我们采用Figure Eight上的“Disasters on Social Media”数据集,以下是数据集简介:
收集者通过搜索“ablaze”(大火), “quarantine”(隔离)和 “pandemonium”(乌烟瘴气)查看了10000多条推文,然后对每条推文进行标注,标注为是否在谈论一个灾难(负类可能是一个玩笑,或者灾难电影的评论等)
我们的任务是预测这些推文是关于灾难还是关于其他话题(比如电影)的不相关评论。为什么要做这件事情呢? 一个潜在的应用场景就是可以监测社交网站然后把紧急情况通知到执法人员,但是要排除对亚当·桑德勒(Adam Sandler)最新电影的评论。这个任务的挑战是正负类样本都是通过相同的搜索关键词找到的,我们需要用更细微的差别来区分它们。
在这篇文章里,我们把谈论实际发生灾难的推文标注为“disaster”, 其他的称为“irrelevant”。
标签
因为我们已经有了标注好的数据,所以我们知道哪些推文是属于哪个类别的。如Richard Socher在下面的推文中所说的,找到足够的标注数据来训练模型往往比优化一个复杂的无监督算法要简单、便宜并且更快。
第二步: 数据清理
我们遵从的第一原则是:你的模型只会和你的数据一样好。
数据科学家的一个关键技能是能够辨明下一步的工作应该是优化模型还是提升数据质量。先看数据并进行数据清理是一个很好的经验法则。一个干净的数据集可以让你的模型去学习有意义的特征而不是对噪声进行过拟合。
这是清理数据的清单:(详情请查看代码)
1. 移除所有不相关的字符, 比如非字母数字的字符;
2. 对文本进行分词, 得到单独的词;
3. 清除所有不相关的词,比如“@”推文事件或者网址;
4. 把所有的字符转成小写, 以便于把“hello” 和“HELLO” 当做相同的内容
5. 把拼写错误或者替代拼写的词组合为单个表示形式(例如“ cool” /“ kewl” /“ cooool”)
6. 考虑词形(比如把“am”, "are", "is"统一转为“be”)
经过这些步骤并检查其他错误后,我们就可以使用这些干净、有标签的数据来训练模型了。
第三步:找到好的特征表示
机器学习模型的输入是数值。比如处理图片的模型,它们用矩阵表示每个颜色通道中的每个像素的强度。
我们的数据集是一系列的句子,为了用算法从这些句子中提取有效的模式(pattern), 我们需要找到一种可以让算法理解的表示方式,比如一连串数字。
一位有效编码(One-hot encoding)
文本对于计算机的一种自然表示是把每个字符分别转换成数字编码(比如ASCII码)。 如果我们把这种简单的表示形式提供给分类器,分类器就只能根据我们的数据从头开始学习单词的结构, 这对于大多数数据集来说是不可能的。 我们需要使用更高层次的方法。
比如, 我们可以对数据集中的所有词构建一个词汇表,然后把每个词和词汇表的索引关联起来。 只要句子中的单词在这个词汇表里,它就可以用一个数字列表来表示。我们在这个数字列表的每个位置记录该单词在句子中的出现次数。 这个就是所谓的“Bag of Words” 模型, 这种表示忽略了单词在句子中的顺序, 如下图所示:
向量可视化(Visualizing the embeddings)
“Disasters of Social Media”这个数据集大概有20000个单词,意味着每个句子都要用一个长度为20000的向量表示。 因为一个句子中含有的单词个数较少,所以这个向量中大部分都是0。
为了验证我们的Embeddings是否抓取到了这个问题的相关信息(比如这个推文是否是关于灾难的), 我们可以通过可视化来看分类效果。因为这个词汇表很大,我们不可能基于20000个维度去把它可视化, 这时候降维方法(比如PCA)就可以数据降成二维, 如下图所示。
从上图来看,这个分类效果并不好, 可能是由于embedding,也可能是降维引起的。我们可以用它来训练模型,从而验证Bag of words这个特征是否有用。
第四步:分类
刚开始处理问题的时候,最好从最简单的方法开始。 对于分类问题来说,逻辑回归(Logistic Regression)因其通用性和易解释性而广受欢迎。 它的训练非常简单, 结果也是可以解释的,因为你可以轻松地从模型中提取最重要的系数。
我们把数据分类训练集和测试集, 训练集用于训练模型,测试集用于验证它的分类效果。 训练之后,我们得到了75.4%的准确度, 不算太坏。如果只是随机猜测哪个属于“irrelevant”的话, 准确度只有57%。 然而,哪怕75%对于我们的使用场景来说已经足够好了,我们还是需要更好地理解这个模型。
第五步:模型验证
混淆矩阵(Confusion Matrix)
验证的第一步是理解模型的错误,弄清楚哪种错误是最不希望发生的。在我们的例子中,FP(false positives)是指把不是在谈论真实灾难的推文分类成“disaster”, 而FN(false negatives)是指把真正在谈论灾难的推文分类成“irrelevant”。如果我们希望检测到每个可能的灾难事件,我们就要尽可能降低FN。如果我们的资源有限没有能力处理那么多事件,那么我们就需要降低FP以降低不必要的警报。混淆矩阵(Confusion Matrix)是用来展示这些信息的好方法, 它很好地把模型的预测结果和实际的标签进行了对比。 理想情况下,这个矩阵是一个从左上角到右下角的一条对角线(预测和真实情况完全符合)。
我们分类器的预测结果是FN的比例比FP要高, 也就是说这个模型的主要错误是错误地把“disaster”的推文分类成了“irrelevant”。如果误报对执法部门造成高昂的成本,那么这可能是良好的bias。
模型解释
为了验证模型并解释它的预测行为,查看哪些词是真正用于决策的很重要。如果数据有偏差(bias),分类器的预测准确度很高, 但是模型在实际应用的效果却不好。 这里我们画出了两个类别(disaster,irrelevant)中最重要的一些词。 对于Bag of Words 和逻辑回归来说,画出最重要的词很简单,因为我们在预测的时候只是对模型中用到的系数进行了提取和排序。
我们的模型正确地选择了一些模式(hiroshima, massacre), 但是对于一些无意义的词(heyoo, x1392)明显地表现出了过拟合。 目前我们的Bag of Words 模型处理的是一个巨大的词汇表,每个词的权重都是一致的。然而,有一些词出现的频率非常高,并且对预测来说是噪声。 下一步,我们将尝试引入单词的频率来表示句子,看一下我们是不是可以从数据中找到更多的信息。
第六步: 词汇结构
TF-IDF
为了让模型侧重那些更有意义的词,我, 可以在Bag of Words的基础上用TF-IDF score(Term Frequency, Inverse Document Frequency)。TF-IDF根据单词在数据集中的出现频率来计算权重,对那些频率很高的噪声词汇降权。这是PCS对我们新embeddings方法的投影。
从上图中我们可以看到这两个颜色的分布有了明显地区别。 这便于分类模型更好地识别不同的类别。我们来看一下这个新方法是不是会有更好的分类效果。对这个embeddings重新进行了逻辑回归训练后,我们得到了76.2%的准确度。
这是个非常微小的提升。那我们的模型是不是选择了更重要的词呢?如果我们在防止模型“作弊”的同时获得了更好的效果,那我们就可以认为这个新模型是一个好的升级。
这些单词看起来比之前的相关性要好很多。所以尽管新模型的指标(metrics)只提升了一点点,但是我们对模型用到的单词更自信,这使得我们上线模型时觉得更自在。
第七步: 利用语义
Word2Vec
我们的最新模型可以选择出更有信息的单词,但是我们在上线模型后很可能会遇到训练集中没有的单词。所以我们的模型对包含新词的推文的分类准确度不会很好,即使这些新词和我们词汇表中的词非常接近。
为了解决这个问题,我们需要捕获单词的语义,意味着我们需要明白单词“good”和“positive”之间的相似度比“apricot”和“continent”之间的相似度要高。我们将用Word2Vec这个工具来捕获单词的语义。
使用预训练(pre-trained)的单词
Word2Vec是一种查找单词连续嵌入方式的技术。 它通过读取海量的文本并记住哪些词会在相似的语境中出现来学习。基于足够的数据训练后,它会对词汇表中的每个单词产生一个300维的向量,相似的单词的向量距离更接近。
这篇论文的作者有一个开源模型,这个模型是基于一个海量的语料库训练的,我们可以在我们的模型中运用这个模型的训练结果来增强对语义的理解。这些预训练好的向量在这个资料库中。
句子级别的表示
一种快速获取单词embedding的方式是把句子中所有词的Word2Vec得分取一个均值。下面是我们之前提到的Bag of Words方法,但是这次我们去掉了句子的语法,保留了语义信息。
下面是新embedding方法的视图。
这次两个颜色的分布差别更明显了,说明这个新的embedding方法对分类模型的帮助会更大。通过对相同模型(逻辑回归)的第三次训练, 我们得到了77.7%的准确度,这是我们迄今为止得到的最好表现了。下面我们来检查一下这个模型。
复杂程度和可解释性之间的平衡
因为这次我们采用的embedding方式不像之前一样对每个单词产生一个向量,所以分析哪些单词在分类器中表现得相关性更高就比较困难。 我们仍然可以获得逻辑回国模型的系数,但是这些系数是跟300维的embeddings相关,而不是跟单词相关。
因此为了获取这样微小的准确度提升而损失模型的可可解释性不是一个好的权衡决策。然而借助于像LIME 这样的黑盒解释器,我们可以对一些复杂分类器的机理有更好的理解。
LIME
LIME在GitHub上有开源包。它是一个黑盒解释器,根据不停变动对一个特定例子的输入(在我们的例子中表现为从句子中删除单词)然后观察预测结果变化来解释分类器的决策。
我们来看一下我们数据集中一些句子的解释。
然而我们并没有时间去探究数据集里成千上万的例子。 所以我们会在一个有代表性的测试集上跑LIME来看哪些词表现出了比较强的相关性。通过这个方法,我们可以跟之前的方法一样拿到单词重要性得分,从而验证模型。
从上述结果来看,这个模型找出了相关性程度比较高的单词,结果易于解释。这些词看起来比之前所有模型中的词的相关性看起来都要高,因此我们可以放心地部署到产品中。
第八步:通过端到端的方法利用语法
我们已经介绍了快速、高效的方法来生成紧凑句子的embedding。然而,因为忽略了单词在句子中的出现顺序,我们失去了重要的语法信息。如果这些模型在结果上表现不好,你可以利用更复杂的模型,把整个句子作为一个输入来预测标签而不是建立中间表示。一种常用的方法是把一个句子当做包含所有单词向量的序列,可以用Word2Vec或者其他更新的方法,比如 GloVe 或者CoVe。 这就是我们下面要做的。
卷积神经网络在句子分类中的应用这篇论文训练了一个非常快速并且表现很好的入门级深度学习架构。卷积神经网络主要由于其在图像数据中的表现而被人熟知,它们在文本相关的任务中表现非常好,而且比大多数复杂的NLP模型(比如LSTMs和Encoder/Decoder架构)要快很多。这个模型保留了单词在句子中的顺序,并能学到哪些单词序列可以很好地帮助我们预测这种有用信息。和之前的模型相反,它能够区分 “Alex eats plants”和“Plants eat Alex”的不同。
相比之前的模型(详情请看代码), 训练这个模型的工作量并不会多很多, 但是给出的预测结果却比之前好很多,准确度达到79.5%。和上述的其他模型一样,我们下一步要做的是使用本文中提到的方法来探究和解释这个模型,来验证它确实是可以用于部署的最佳模型。至此,你应该可以自如地解决这些问题了。
结语
以下是对本文描述的方法的一个快速总结:
1. 从快速、简单的模型开始
2. 解释预测行为
3. 理解模型错误
4. 利用上述信息来决定下一步是提升数据还是优化模型
这些方法应用在了特定的示例案例(利用量身定做的模型来理解和利用短文本比如tweets)上, 但是这些思想可以广泛应用于各种问题。希望这些可以帮助到你,我们也希望能够得到你的反馈。你可以在文章下方留言,或者通过 @EmmanuelAmeisen或Twitter联系我们。