8个方法解决90%的NLP问题
文章来源:企鹅号 - IT派
一、收集数据
每一个机器学习问题都始于数据,比如一组邮件、帖子或是推文。文本信息的常见来源包括:
商品评价(来自 Amazon、Yelp 以及其他 App 商城)
用户产出的内容(推文、Facebook 的帖子、StackOverflow 的提问等)
问题解决(客户请求、技术支持、聊天记录)
“社交媒体中的灾难”数据集
在这篇文章中,我们将使用 CrowdFlower 提供的一个数据集,名为“社交媒体中的灾难(Disasters on Social Media)”。
贡献者们查看了超过 10000 条具有类似“着火”、“隔离”、“混乱”等搜索关键词的推文,然后标记这个推文是否和灾难事件有关(与之相反的是一些玩笑、电影点评或是一些非灾难性的事件)。
我们的任务是分辨出哪些推文是真正和灾难事件相关的,而不是一些类似电影描述的不相关话题。为什么呢?一个潜在的应用是针对突发事件对执法人员进行专门的提醒,而不会被其他无关信息,比如 Adam Sandler 新上映的电影所干扰。这项任务中一个特别的挑战是这两种情况在搜索推文的时候都用到了相同的检索词,所以我们只能通过细微的差别去区分他们。
在下面的文章中,我们将把与灾难事件相关的推文称为“灾难”,将其他推文称为“不相关的”。
标签
我们已经标注过数据,所以知道推文是如何分类的。比起优化一个复杂的无监督学习方法,寻找和标记足够多的数据来训练模型会更加快捷、简单和廉价。
二、数据清洗
数据科学家的一个必备技能是知道自己的下一步操作是处理模型还是数据。有一个好的经验法则是先观察数据然后进行数据清洗。一个干净的数据集能使模型学习到有意义的特征而不会被一些不相关的噪声影响。
可以借鉴下方的列表来进行数据清洗:
去除一切不相关的字符,比如任何非字母数字的字符
标记你的文本,将他们拆分为独立的单词
去除不相关的词语,比如 @这类提醒或是 url 链接
将所有字母转换成小写,这样“hello”,“Hello”,“HELLO”就会被当做同样的单词处理
将拼错的单词或是多种拼法的单词与某个特定的表达绑定(比如:“cool”/“kewl”/“cooool”)
考虑词形还原(比如将“am”,“are”,“is”都看做“be”)
完成这些步骤并检查完其他错误后,我们就可以使用这些干净的、标记过的数据进行模型训练了!
三、找到一种好的数据表达方式
机器学习模型通常以数值作为输入。这里的数据集是句子列表,为了让模型可以从数据中学到句子的特征模式,首先要找到一种方法来把它转换成模型能理解的形式,即数字列表。
独热编码(One-hot encoding)- 词袋模型(Bag of Words)
通常为计算机解释文本的方法是将每一个字符都编为一个独立的数字(例如 ASCII 码)。如果使用这种简单的表达来做分类器,需要我们的数据从头开始学习词语的结构,这对大多数数据集来说是很难实现的。所以我们需要一种更上层的方法。
例如,我们可以为数据集中的所有单词制作一张词表,然后将每个单词和一个唯一的索引关联。每个句子都是由一串数字组成,这串数字是词表中的独立单词对应的个数。通过列表中的索引,我们可以统计出句子中某个单词出现的次数。这种方法叫做 词袋模型,它完全忽略了句子中单词的顺序。如下图所示:
用词袋模型表示句子。句子在左边,模型表达在右边。向量中的每一个索引代表了一个特定的单词。
嵌入可视化
在“社交媒体中的灾难”样本词表中大概会有 20000 个单词,这意味着每句句子都会用一个长度为 20000 的向量来表示。向量的 大部分会被 0 填充,因为每句话只包含了词表中很小的一个子集。
为了看出嵌入的工作是否真正抓住了和问题相关的信息(比如推文是否与灾难相关),有一个好方法是将它们可视化,然后观察结果是否有很好的分布。考虑到词表通常很大,而且用 20000 维的数据做可视化是基本不可能的,所以我们使用了 PCA 这种技术将数据降到二维。绘制如下:
词袋嵌入模型的可视化结果
两个分类看起来没有很好的分离,这可能是我们选择的嵌入方法的特征或是单纯因为维度的减少引起的。为了了解词袋模型的特征是否会起一些作用,我们可以试着基于它训练一个分类器。
四、分类
当初次接触一个问题,通常来说最好的方法是先挑选一个能解决问题的最简单的工具。当提到数据分类时,一般最受欢迎的是通用性和可解释性兼具的逻辑回归算法。这种算法很容易训练而且结果也是可解释的,你可以很轻松地从模型中提取出最重要的一些系数。
我们将数据分为两个集合,训练集用于匹配模型,测试集用于观察应用在未知数据上的效果。训练后我们得到了 75.4% 的精确度。结果还不错!推测出现最多的类(“不相关”)只能达到 57%。但是,即使是 75% 的精确度也已经足够好了,我们决不能在还没有理解模型的情况下就开始应用它。
五、检验混淆矩阵
理解模型的第一步,是了解模型产生的错误分类,以及最不应该出现的错误。在我们的例子中,“误报”是指将不相关的推文分类为“灾难事件”,“漏报”是指将与灾难有关的推文归类为“与灾难无关的事件”。如果要优先处理潜在的灾难事件,那就要降低“漏报”。而如果资源受限,就要优先降低“误报”,减少错误的提醒。使用混淆矩阵可以很好地可视化这些信息,并将模型预测的结果与数据的真是标签进行比较。理想情况下,模型的预测结果与真实情况(人工标注)完全相符,这时候混淆矩阵是一条从左上角到右下角的对角矩阵。
混淆矩阵(绿色部分所占比例较高,蓝色部分的比例较低)
相比假阳性结果,我们的分类器产生了更多的假阴性结果。换句话说,模型中最常见的错误是将灾难性推文错误归类为不相关推文。如果假阳性结果的执法成本很高的话,那么我们分类器的这种偏差就是良性的。
解释和说明模型
为了验证模型并解释它的预测结果,我们需要明确模型用以进行判断的那些词汇。如果我们的数据有偏差,而分类器在样本数据中却能做出准确预测,那这样的模型就无法在现实世界中很好地推广。
在这里,我们可以用图表来表示灾难性推文与不相关推文两类预测中最重要的词汇。由于我们可以对模型的预测系数进行提取和排序,用词袋模型(bag-of-words)和Logistic回归模型很容易就能计算出单词的重要性。
词袋模型(bag-of-words):单词的重要性
我们的分类器能够正确识别出一些模式(如广岛、大屠杀等),但在一些毫无意义的词汇(如heyoo、x1392等)上还是出现了过拟合。词袋模型(bag-of-words)仅能处理庞大词汇表内的不同词汇,并对所有的词汇分配相同的权重。然而,其中一些词汇出现得非常频繁,但却只是预测结果的噪音数据。接下来,我们将试着找到一种能够表示词汇在句子中出现频率的方法,尽量让模型从数据中获取更多的信号。
六、词汇结构的统计
TF-IDF嵌入模型
为了让模型专注于学习更有意义的词汇,我们可以在词袋模型上面使用TF-IDF评分(术语频率,逆文档频率)。TF-IDF通过词汇在数据集中的稀有程度来评估它的重要性,适度削弱出现过于频繁的单词。下图是TF-IDF嵌入模型的PCA映射:
可视化TF-IDF嵌入模型
从中可以看出,两种颜色之间有了更清晰的区分,使这两类数据更易于被分类器分开。在新模型上训练Logistic回归,我们得到了76.2%的准确度,说明TF-IDF确实有助于提高识别性能。
尽管只是非常微小的改进,但我们的模型能否就此学到更重要的词汇呢?如果能得到更好的结果,同时还能避免模型在无关词汇上的过拟合,那TF-IDF嵌入模型就可以被认为是真正的“升级版”模型。
TF-IDF嵌入模型:单词的重要性
可以看到,新模型学到的词汇看起来相关度更高!尽管测试集的指标只是略有增加,但是我们对模型的识别性能更有把握,因此部署新模型的交互系统会让用户体验更为舒适。
七、语义信息的利用Word2Vec
TF-IDF嵌入模型能够学习到信号更高频的词汇。然而,如果部署该模型后,我们很可能会遇到一些训练集中从未出现过的词汇。先前的模型均无法正确分类这样的新数据,即便其中的词汇与训练集非常相似。
要解决这个问题,我们就要捕捉词汇的语义,这就意味着模型需要理解“好”与“积极”在语义上的距离要比“杏”和“大陆”更接近。这里的工具就是Word2Vec。
使用预训练的嵌入模型
Word2Vec是一种为单词查找连续嵌入的技术。通过阅读大量的文字,它能够学习并记忆那些倾向于在相似语境中出现的词汇。经过足够的数据训练之后,它会为词汇表中的每个单词都生成一个300维的向量,用以记录语义相近的词汇。
Word2Vec作者在一个非常大的语料库上预训练并开源了该模型。利用这一语料库,我们可以将一些语义知识纳入到我们的模型内。预训练好的词向量可以在本文的GitHub代码库中找到。
GitHub地址:https://github.com/hundredblocks/concrete_NLP_tutorial
句子分级表示
让分类器快速得到句子嵌入的方法,是先将句中所有词汇Word2Vec得分的平均化。这与此前词袋模型的做法类似,但这里我们在保留语义信息的同时只丢弃句法。
Word2vec模型的句子嵌入
利用前面的可视化技术对新模型绘图,结果如下:
Word2Vc嵌入模型的可视化结果
在这里,两组颜色的分离程度更大一些,这就意味着Word2Vec能够帮助分类器更好地分离这两种类别。再一次使用Logistic回归,得到77.7%的准确率,是我们迄今最好的结果!
复杂性/可解释性权衡取舍
与先前的模型不同,新模型无法将每个单词都表示成一维向量,因此很难看出哪些词汇与我们的分类结果相关度最高。尽管我们仍可使用Logistic回归的系数,但它们仅与嵌入的300个维度相关,而与词汇索引值并不相关。
模型准确率确实提高了,但完全做不了可解释性分析就有点得不偿失了。不过,对于更复杂的模型,我们可以利用LIME这样的“黑盒解释器”来稍微解释一下分类器具体是如何工作的。
LIME
LIME是Github上的一个开源软件包,它允许用户通过观察输入的扰动(比如在我们的例子中,从句中移除单词)来分析一个特定分类器的预测结果是如何变化的。
从下图来看它对我们数据集中几个句子的解释:
正确分类的灾难性词汇被归类为“相关”
这个词对分类的影响似乎不太明显
不过,我们没有时间去逐一探索数据集中的数千个样本。我们要做的是在代表性的测试样本上运行LIME,以此来分析哪些词汇对于分类预测的影响更大。这样,我们就可以像前面一样获取到单词的重要性分数,以验证模型的预测结果。
Word2Vec:单词的重要性
模型能够提取高度相关的词,这意味着它做出了可解释的决定。这些词汇的相关度是最高的,因此我们更愿意在实际生产中部署这样的模型。
八、使用端到端的方式训练语法特征
我们已经介绍过如何用快速有效的办法来生成紧凑的句子嵌入。然而,通过省略词汇的顺序,我们也放弃了语句的所有句法信息。如果简单的方法给不出令人满意的结果,那我们就用更为复杂的模型:将整个句子作为输入并预测标签,同时无需建立中间表示。一种常见的做法是把句子视为词向量的序列,如使用Word2Vec,或是GloVe、CoVe等更先进的方法。接下来我们详细讨论。
高效的端到端的训练体系结构(源)
用于句子分类的卷积神经网络(https://arxiv.org/abs/1408.5882)训练速度很快。它作为一种入门级的深度学习架构,能够很好地解决分类问题。尽管CNN声名主要源自它在图像处理方面的出色能力,但在文本相关任务上,它所提供的结果也相当优异。且相比多数复杂的NLP方法(如LSTM、Encoder/Decoder架构等),CNN训练速度也更快。它能够保留单词的顺序,很好地学习单词的序列特征以及其他有用信息。相对于先前的模型,它可以区分出“Alex eats plants”与“Plants eat Alex”之间差异。
相比先前的方法,该模型的训练不需更多的工作,但效果却好得多,准确率高达79.5%!与前面的步骤一样,下一步也要继续探索并可视化该模型的预测结果,以验证它是否为最佳模型。做到这一步,你应该能自己完成这里的操作。
写在最后
简单回顾一下,我们在各个步骤中所用的方法是这样的:
从一个简单的模型快速开始
解释模型的预测
理解模型分类中的错误样本
使用这些知识来决定下一步的部署。
上述八大步骤所用的模型是我们处理短文本时的几个特定实例,但其背后的解决方法已经广泛被用在各类NLP问题的实际处理上。