文本分类聚合
编辑文本分类聚合
编辑一种多桶聚合,用于将半结构化文本分组到桶中。每个 text
字段都会使用自定义分析器重新分析。然后对生成的标记进行分类,创建类似格式的文本值的桶。此聚合最适合于机器生成的文本,例如系统日志。仅使用前 100 个分析标记来对文本进行分类。
在 8.3.0 版中,用于分类的算法发生了彻底改变。因此,此聚合在某些节点版本为 8.3.0 或更高版本而其他节点版本低于 8.3.0 的混合版本集群中将无法工作。如果您遇到与此更改相关的错误,请将集群中的所有节点升级到同一版本。
参数
编辑-
categorization_analyzer
-
(可选,对象或字符串) 分类分析器指定在对文本进行分类之前如何对其进行分析和标记化。语法与在 分析端点 中定义
analyzer
所使用的语法非常相似。此属性不能与categorization_filters
同时使用。categorization_analyzer
字段可以指定为字符串或对象。如果它是字符串,则必须引用 内置分析器 或由其他插件添加的分析器。如果它是一个对象,则具有以下属性categorization_analyzer
的属性-
char_filter
- (字符串或对象的数组) 一个或多个 字符过滤器。除了内置的字符过滤器之外,其他插件还可以提供更多字符过滤器。此属性是可选的。如果未指定,则在分类之前不应用任何字符过滤器。如果您正在自定义分析器的某些其他方面,并且需要实现等效于
categorization_filters
(在自定义分析器的某些其他方面时不允许使用)的功能,请在此处将其添加为 模式替换字符过滤器。 -
tokenizer
- (字符串或对象) 应用字符过滤器后要使用的 分词器 的名称或定义。如果
categorization_analyzer
指定为对象,则此属性是强制性的。机器学习提供了一个名为ml_standard
的分词器,它以一种已被确定对各种日志文件格式(英语日志)产生良好分类结果的方式进行标记化。如果您想使用该分词器但更改字符或标记过滤器,请在您的categorization_analyzer
中指定"tokenizer": "ml_standard"
。此外,ml_classic
分词器可用,其标记化方式与产品旧版本(6.2 之前)中不可自定义的分词器相同。ml_classic
是 6.2 版到 7.13 版中的默认分类分词器,因此,如果您需要与这些版本中创建的作业的默认值相同的分类,请在您的categorization_analyzer
中指定"tokenizer": "ml_classic"
。
从 Elasticsearch 8.10.0 开始,使用新的版本号来跟踪机器学习插件中的配置和状态更改。此新版本号与产品版本分离,并将独立递增。
-
filter
- (字符串或对象的数组) 一个或多个 标记过滤器。除了内置的标记过滤器之外,其他插件还可以提供更多标记过滤器。此属性是可选的。如果未指定,则在分类之前不应用任何标记过滤器。
-
-
categorization_filters
- (可选,字符串数组) 此属性需要一个正则表达式数组。这些表达式用于从分类字段值中过滤掉匹配的序列。您可以使用此功能通过排除在定义类别时要考虑的序列来微调分类。例如,您可以排除日志文件中出现的 SQL 语句。此属性不能与
categorization_analyzer
同时使用。如果您只想定义在标记化之前应用的简单正则表达式过滤器,则设置此属性是最简单的方法。如果您还想自定义分词器或标记化后过滤,请改用categorization_analyzer
属性并将过滤器包含为pattern_replace
字符过滤器。 -
field
- (必填,字符串) 要分类的半结构化文本字段。
-
max_matched_tokens
- (可选,整数) 此参数现在不起作用,但为了与原始的 8.3.0 之前的实现兼容而允许使用。
-
max_unique_tokens
- (可选,整数) 此参数现在不起作用,但为了与原始的 8.3.0 之前的实现兼容而允许使用。
-
min_doc_count
- (可选,整数) 桶要返回到结果中的最少文档数。
-
shard_min_doc_count
- (可选,整数) 在合并之前,桶要从分片返回的最少文档数。
-
shard_size
- (可选,整数) 在合并所有结果之前,要从每个分片返回的分类桶的数量。
-
similarity_threshold
- (可选,整数,默认值:
70
) 必须匹配的令牌权重最小百分比,才能将文本添加到类别桶中。必须介于 1 和 100 之间。值越大,类别越窄。较大的值会增加内存使用量并创建更窄的类别。 -
size
- (可选,整数,默认值:
10
) 要返回的桶数。
响应主体
编辑-
key
- (字符串) 由(由
categorization_analyzer
提取的)对包含在类别中的输入字段的所有值的公共令牌组成。 -
doc_count
- (整数) 与类别匹配的文档数。
-
max_matching_length
- (整数) 来自包含少量标记的短消息的类别也可能与来自更长消息派生的包含许多标记的类别匹配。
max_matching_length
指示应考虑属于该类别的消息的最大长度。在搜索与类别匹配的消息时,应排除任何长度超过max_matching_length
的消息。使用此字段可以防止对短消息类别的成员的搜索匹配更长的消息。 -
regex
- (字符串) 将匹配包含在类别中的输入字段的所有值的正则表达式。如果包含在类别中的值之间存在排序差异,则
regex
可能不会包含key
中的每个术语。但是,在简单情况下,regex
将是按顺序排列的术语连接成一个正则表达式,该表达式允许它们之间存在任意部分。不建议将regex
作为搜索已分类的原始文档的主要机制。使用正则表达式进行搜索非常慢。相反,应使用key
字段中的术语来搜索匹配的文档,因为术语搜索可以使用倒排索引,因此速度快得多。但是,在某些情况下,使用regex
字段来测试尚未建立索引的一小组消息是否与类别匹配或确认key
中的术语是否按正确顺序出现在所有匹配的文档中可能很有用。
基本用法
编辑示例
resp = client.search( index="log-messages", filter_path="aggregations", aggs={ "categories": { "categorize_text": { "field": "message" } } }, ) print(resp)
const response = await client.search({ index: "log-messages", filter_path: "aggregations", aggs: { categories: { categorize_text: { field: "message", }, }, }, }); console.log(response);
POST log-messages/_search?filter_path=aggregations { "aggs": { "categories": { "categorize_text": { "field": "message" } } } }
响应
{ "aggregations" : { "categories" : { "buckets" : [ { "doc_count" : 3, "key" : "Node shutting down", "regex" : ".*?Node.+?shutting.+?down.*?", "max_matching_length" : 49 }, { "doc_count" : 1, "key" : "Node starting up", "regex" : ".*?Node.+?starting.+?up.*?", "max_matching_length" : 47 }, { "doc_count" : 1, "key" : "User foo_325 logging on", "regex" : ".*?User.+?foo_325.+?logging.+?on.*?", "max_matching_length" : 52 }, { "doc_count" : 1, "key" : "User foo_864 logged off", "regex" : ".*?User.+?foo_864.+?logged.+?off.*?", "max_matching_length" : 52 } ] } } }
这是一个使用 categorization_filters
的示例
resp = client.search( index="log-messages", filter_path="aggregations", aggs={ "categories": { "categorize_text": { "field": "message", "categorization_filters": [ "\\w+\\_\\d{3}" ] } } }, ) print(resp)
const response = await client.search({ index: "log-messages", filter_path: "aggregations", aggs: { categories: { categorize_text: { field: "message", categorization_filters: ["\\w+\\_\\d{3}"], }, }, }, }); console.log(response);
POST log-messages/_search?filter_path=aggregations { "aggs": { "categories": { "categorize_text": { "field": "message", "categorization_filters": ["\\w+\\_\\d{3}"] } } } }
请注意 foo_<number>
标记如何不属于类别结果
{ "aggregations" : { "categories" : { "buckets" : [ { "doc_count" : 3, "key" : "Node shutting down", "regex" : ".*?Node.+?shutting.+?down.*?", "max_matching_length" : 49 }, { "doc_count" : 1, "key" : "Node starting up", "regex" : ".*?Node.+?starting.+?up.*?", "max_matching_length" : 47 }, { "doc_count" : 1, "key" : "User logged off", "regex" : ".*?User.+?logged.+?off.*?", "max_matching_length" : 52 }, { "doc_count" : 1, "key" : "User logging on", "regex" : ".*?User.+?logging.+?on.*?", "max_matching_length" : 52 } ] } } }
这是一个使用 categorization_filters
的示例。默认分析器使用 ml_standard
分词器,该分词器类似于空格分词器,但会过滤掉可能被解释为十六进制数字的标记。默认分析器还使用 first_line_with_letters
字符过滤器,以便仅考虑多行消息的第一行有意义的行。但是,标记可能是已知的易变标记(格式化的用户名、电子邮件等)。在这种情况下,最好提供自定义 categorization_filters
来过滤掉这些标记以获得更好的类别。这些过滤器还可以减少内存使用量,因为类别中存储在内存中的标记更少。(如果存在足够不同的用户名、电子邮件等的示例,则会自然地形成类别,将它们丢弃为变量,但对于仅存在一个示例的小输入数据,这种情况不会发生。)
resp = client.search( index="log-messages", filter_path="aggregations", aggs={ "categories": { "categorize_text": { "field": "message", "categorization_filters": [ "\\w+\\_\\d{3}" ], "similarity_threshold": 11 } } }, ) print(resp)
const response = await client.search({ index: "log-messages", filter_path: "aggregations", aggs: { categories: { categorize_text: { field: "message", categorization_filters: ["\\w+\\_\\d{3}"], similarity_threshold: 11, }, }, }, }); console.log(response);
POST log-messages/_search?filter_path=aggregations { "aggs": { "categories": { "categorize_text": { "field": "message", "categorization_filters": ["\\w+\\_\\d{3}"], "similarity_threshold": 11 } } } }
生成的类别现在非常广泛,合并了日志组。(similarity_threshold
为 11% 通常太低。50% 以上的设置通常更好。)
{ "aggregations" : { "categories" : { "buckets" : [ { "doc_count" : 4, "key" : "Node", "regex" : ".*?Node.*?", "max_matching_length" : 49 }, { "doc_count" : 2, "key" : "User", "regex" : ".*?User.*?", "max_matching_length" : 52 } ] } } }
此聚合可以同时具有子聚合,并且它本身可以是子聚合。这允许如下收集每日热门类别和热门样本文档。
resp = client.search( index="log-messages", filter_path="aggregations", aggs={ "daily": { "date_histogram": { "field": "time", "fixed_interval": "1d" }, "aggs": { "categories": { "categorize_text": { "field": "message", "categorization_filters": [ "\\w+\\_\\d{3}" ] }, "aggs": { "hit": { "top_hits": { "size": 1, "sort": [ "time" ], "_source": "message" } } } } } } }, ) print(resp)
const response = await client.search({ index: "log-messages", filter_path: "aggregations", aggs: { daily: { date_histogram: { field: "time", fixed_interval: "1d", }, aggs: { categories: { categorize_text: { field: "message", categorization_filters: ["\\w+\\_\\d{3}"], }, aggs: { hit: { top_hits: { size: 1, sort: ["time"], _source: "message", }, }, }, }, }, }, }, }); console.log(response);
POST log-messages/_search?filter_path=aggregations { "aggs": { "daily": { "date_histogram": { "field": "time", "fixed_interval": "1d" }, "aggs": { "categories": { "categorize_text": { "field": "message", "categorization_filters": ["\\w+\\_\\d{3}"] }, "aggs": { "hit": { "top_hits": { "size": 1, "sort": ["time"], "_source": "message" } } } } } } } }
{ "aggregations" : { "daily" : { "buckets" : [ { "key_as_string" : "2016-02-07T00:00:00.000Z", "key" : 1454803200000, "doc_count" : 3, "categories" : { "buckets" : [ { "doc_count" : 2, "key" : "Node shutting down", "regex" : ".*?Node.+?shutting.+?down.*?", "max_matching_length" : 49, "hit" : { "hits" : { "total" : { "value" : 2, "relation" : "eq" }, "max_score" : null, "hits" : [ { "_index" : "log-messages", "_id" : "1", "_score" : null, "_source" : { "message" : "2016-02-07T00:00:00+0000 Node 3 shutting down" }, "sort" : [ 1454803260000 ] } ] } } }, { "doc_count" : 1, "key" : "Node starting up", "regex" : ".*?Node.+?starting.+?up.*?", "max_matching_length" : 47, "hit" : { "hits" : { "total" : { "value" : 1, "relation" : "eq" }, "max_score" : null, "hits" : [ { "_index" : "log-messages", "_id" : "2", "_score" : null, "_source" : { "message" : "2016-02-07T00:00:00+0000 Node 5 starting up" }, "sort" : [ 1454803320000 ] } ] } } } ] } }, { "key_as_string" : "2016-02-08T00:00:00.000Z", "key" : 1454889600000, "doc_count" : 3, "categories" : { "buckets" : [ { "doc_count" : 1, "key" : "Node shutting down", "regex" : ".*?Node.+?shutting.+?down.*?", "max_matching_length" : 49, "hit" : { "hits" : { "total" : { "value" : 1, "relation" : "eq" }, "max_score" : null, "hits" : [ { "_index" : "log-messages", "_id" : "4", "_score" : null, "_source" : { "message" : "2016-02-08T00:00:00+0000 Node 5 shutting down" }, "sort" : [ 1454889660000 ] } ] } } }, { "doc_count" : 1, "key" : "User logged off", "regex" : ".*?User.+?logged.+?off.*?", "max_matching_length" : 52, "hit" : { "hits" : { "total" : { "value" : 1, "relation" : "eq" }, "max_score" : null, "hits" : [ { "_index" : "log-messages", "_id" : "6", "_score" : null, "_source" : { "message" : "2016-02-08T00:00:00+0000 User foo_864 logged off" }, "sort" : [ 1454889840000 ] } ] } } }, { "doc_count" : 1, "key" : "User logging on", "regex" : ".*?User.+?logging.+?on.*?", "max_matching_length" : 52, "hit" : { "hits" : { "total" : { "value" : 1, "relation" : "eq" }, "max_score" : null, "hits" : [ { "_index" : "log-messages", "_id" : "5", "_score" : null, "_source" : { "message" : "2016-02-08T00:00:00+0000 User foo_325 logging on" }, "sort" : [ 1454889720000 ] } ] } } } ] } } ] } } }