使用 Elasticsearch 轻松进行文本分类
Elasticsearch 在用作搜索和分析引擎方面广为人知,但其作为文本挖掘 API 的功能却鲜少有人知道。
在这篇文章中,我将介绍如何使用 Elasticsearch 进行文本分类。我拥有计算语言学背景,并且在文本挖掘领域有多年的自由职业者工作经验,曾有机会在不同的场景中实施和测试以下技术。
当我第一次偶然发现 Elasticsearch 时,就被它的易用性、速度和配置选项所吸引。每次使用 Elasticsearch,我都能找到一种更为简单的方法来解决我一贯通过传统的自然语言处理 (NLP) 工具和技术来解决的问题。
在某个时刻,我意识到,它可以直接用来解决很多问题,而如果采用我以前学到的方法,则需要从头开始构建解决方案。
大多数的 NLP 任务都是从一个标准的预处理管道开始的:
诸如句法分析之类的一些 NLP 任务需要进行深入的语言学分析。
对于这类任务,Elasticsearch 并没有直接提供理想的架构和数据格式。也就是说,对于超出分词级别的任务,需要编写或使用访问全文的定制插件。
但是,诸如分类、聚类、关键字提取、度量相似度等任务,只需一个标准化词袋(可能经过加权处理)来表示给定的文档即可。
第 1 步和第 2 步可通过 Elasticsearch 中的采集附件处理器插件(5.0 之前版本为映射工具附件插件)来完成。
这些插件的原始文本提取基于 Apache Tika,这个工具包可处理最常见的数据格式(HTML/PDF/Word 等)。
第 4 步到第 6 步可通过开箱即用的语言分析器来完成。
映射示例:
{ "properties":{ "content":{ "type":"text", "analyzer":"german" } } }
如果给定字段的映射类型是“text”(5.0 之前为“analyzed string”),并且分析器被设置为 Elasticsearch 本地支持的语言之一,则系统会在索引时自动执行词汇切分、词干分解和停用词删除。
因此,从获取 Apache Tika 支持的任何类型的文档,到词袋表示形式,既不需要定制代码,也不需要其他工具。
而且,在运行 Elasticsearch 时,还可以通过 REST API 调用语言分析器。
curl -XGET "http://localhost:9200/_analyze?analyzer=english" -d' { "text" :"This is a test." }' { "tokens":[ { "token":"test", "start_offset":10, "end_offset":14, "type":"<ALPHANUM>", "position":3 } ] }
非 Elasticsearch 方法通常有:
使用定制代码收集文本,手工或使用 Tika 库进行文档解析,使用传统的 NLP 库或 API,如 NLTK、OpenNLP、Stanford NLP、Spacy 或某研究部门开发的任何其他 API。然而,研究部门开发的工具通常对企业环境都不是很有用。数据格式通常是专有的,工具经常需要在命令行上编译和执行,并且结果往往只是以竖线分隔的标准输出。REST API 则完全不同。
另一方面,使用 Elasticsearch 语言分析器,您只需配置映射和索引数据即可。预处理会在索引时自动执行。
传统的文本分类方法
文本分类是传统上采用监督型机器学习解决的一项任务,用于训练模型的输入是一组标记文档,其最小表示形式是一个带有两个字段的 JSON 文档:
“content”和“category”
传统上,文本分类可以使用 SciKit Learn、Weka、NLTK、Apache Mahout 等工具来解决。
创建模型
大多数机器学习算法都需要一个向量空间模型表示的数据。这个特征空间通常类似于给定数据集中的 10,000 个最重要的词。那么,如何衡量一个词的重要性呢?
通常会使用 TF-IDF 算法,这是上世纪七十年代发明的一个公式。TF-IDF 是一个权重,用于在给定文档中相对于数据集的其余部分对某个术语进行评分。如果某文档中一个术语的 TF-IDF 分数很高,就意味着它是一个非常独特的关键字,可通过这个词将该文档与所有其他文档区分开来。
在一个文档子集中,TF-IDF 得分最高的关键字可以表示一个主题。在文本分类中,一个特征空间具有 n 个 TF-IDF 总分最高的词是很常见的。
将每个文档转换为一个特征向量,然后使用每个类/类别的所有训练实例创建一个模型。之后,可以根据这个模型对新文档进行分类。因此,需要将文档转换为一个特征向量,然后计算所有的相似性,并使用得分最高的类别标记文档。
使用 Elasticsearch 进行文本分类
所有这些问题都可以用 Elasticsearch(或 Lucene)以更简单的方式解决。
您只需执行 4 步:
- 配置映射("content" : "text"、"category" : "keyword")
- 索引文档
- 运行 More Like This 查询(MLT 查询)
- 编写一个小脚本,按分数聚合查询的命中次数
PUT sample POST sample/document/_mapping { "properties":{ "content":{ "type":"text", "analyzer":"english" }, "category":{ "type":"text", "analyzer":"english", "fields":{ "raw":{ "type":"keyword" } } } } } POST sample/document/1 { "category":"Apple (Fruit)", "content":"Granny Smith, Royal Gala, Golden Delicious and Pink Lady are just a few of the thousands of different kinds of apple that are grown around the world!You can make dried apple rings at home - ask an adult to help you take out the core, thinly slice the apple and bake the rings in the oven at a low heat." } POST sample/document/2 { "category":"Apple (Company)", "content":"Apple is an American multinational technology company headquartered in Cupertino, California, that designs, develops, and sells consumer electronics, computer software, and online services.Its hardware products include the iPhone smartphone, the iPad tablet computer, the Mac personal computer, the iPod portable media player, the Apple Watch smartwatch, and the Apple TV digital media player.Apple's consumer software includes the macOS and iOS operating systems, the iTunes media player, the Safari web browser, and the iLife and iWork creativity and productivity suites.Its online services include the iTunes Store, the iOS App Store and Mac App Store, Apple Music, and iCloud." }
对于文本挖掘来说,MLT 查询非常重要。
工作原理?MLT 查询可以处理任意文本,提取与实际“模型”相关的前 n 个关键字,并使用这些关键字运行布尔匹配查询。此查询通常用于收集相似的文档。
如果所有文档都有一个类/类别标签,并且每个类有相似数量的训练实例,那么这就相当于分类。只需使用输入文档作为 like 字段运行一个 MLT 查询,并编写一个小脚本来聚合前 n 次命中的分数和类别即可。
GET sample/document/_search { "query":{ "more_like_this":{ "fields":[ "content", "category" ], "like":"The apple tree (Malus pumila, commonly and erroneously called Malus domestica) is a deciduous tree in the rose family best known for its sweet, pomaceous fruit, the apple.It is cultivated worldwide as a fruit tree, and is the most widely grown species in the genus Malus.The tree originated in Central Asia, where its wild ancestor, Malus sieversii, is still found today.Apples have been grown for thousands of years in Asia and Europe, and were brought to North America by European colonists.Apples have religious and mythological significance in many cultures, including Norse, Greek and European Christian traditions.", "min_term_freq":1, "max_query_terms":20 } } }
这个示例仅用于阐释工作流,真正的分类会需要更多的数据。如果通过这个例子不能得到任何实际结果也不必担心,只需添加更多的数据,它就会发挥效用。
下面是一个小型 Python 脚本,可处理响应并为输入文档返回最有可能的类别。
from operator import itemgetter def get_best_category(response): categories = {} for hit in response['hits']['hits']: score = hit['_score'] for category in hit['_source']['category']: if category not in categories: categories[category] = score else: categories[category] += score if len(categories) > 0: sortedCategories = sorted(categories.items(), key=itemgetter(1), reverse=True) category = sortedCategories[0][0] return category
这就是您的 Elasticsearch 文本分类器!
用例
文本分类是 NLP 在现实世界中一个很常见的用例。比如电子商务数据(产品)。很多人都借助加盟链接来经营电子商务商店。这些数据由多家商店提供,通常都带有一个类别标签,但每家商店的类别标签都不相同。因此类别系统需要统一,所有数据需要根据新的类别树重新分类。再比如商业智能应用程序,其中的公司网站需要根据行业(美发店、面包房等)进行分类。
评估
我用一个标准文本分类数据集对这一方法进行了评估:20 个新闻组数据集。利用高质量分数阈值(仅包括 12% 的文档)实现了最高精度(92% 的正确标签)。当标记所有文档时(100% 撤回),72% 的预测是正确的。
对于 20 个新闻组数据集的文本分类,最佳算法通常是 SVM 和朴素贝叶斯。它们在整个数据集上的平均精度会更高。
那么,既然有更好的算法,为什么还要考虑使用 Elasticsearch 进行分类呢?
有几个现实的原因:训练一个 SVM 模型需要花费大量时间。特别是当您在一家初创公司工作,或需要快速适应各种客户或用例时,这可能会是一个棘手的问题。另外,您可能无法在每次数据变更时都对模型进行重新培训。我在一家德国大银行的项目中曾亲身经历过这个难题。这种情况下,您用过时的模型肯定不会带来好的结果。
而使用 Elasticsearch 方法,不仅可在索引时进行训练,还可在任何时间点动态更新模型,而且应用程序的停机时间为零。如果您的数据存储在 Elasticsearch 中,则不需要任何额外的基础设施。通常,在第一页您就可以获得 10% 以上的高精度结果。这在很多应用程序中足以给人留下良好的第一印象。
既然有其他工具,为什么还要使用 Elasticsearch 呢?
因为您的数据已经存在,它会预先计算底层的统计数据。就像是免费得到一些 NLP 一样!
Saskia Vola 曾在海德堡大学学习计算语言学,并于 2009 年开始涉足文本挖掘领域的工作。在柏林创业几年后,她决定成为一名全职自由职业者,享受数字游民的生活。随着她收到的相关项目邀请越来越多,她决定为 NLP/AI 自由职业者开办一个名为 textminers.io 的平台。