文本分类聚合

编辑

一个多桶聚合,将半结构化文本分组到桶中。每个 text 字段都使用自定义分析器重新分析。然后对生成的标记进行分类,创建格式相似的文本值的桶。此聚合最适用于机器生成的文本,如系统日志。只有前 100 个分析的标记用于对文本进行分类。

如果您的 JVM 分配了大量内存,但仍然收到此聚合的断路器异常,则可能是您正在尝试对格式不适合分类的文本进行分类。考虑添加 categorization_filters 或在 采样器多样化采样器随机采样器 下运行以探索创建的类别。

用于分类的算法在 8.3.0 版本中完全更改。因此,此聚合将无法在混合版本集群中使用,其中某些节点的版本为 8.3.0 或更高版本,而其他节点的版本低于 8.3.0。如果遇到与此更改相关的错误,请将集群中的所有节点升级到相同版本。

参数

编辑
categorization_analyzer

(可选,对象或字符串)categorization_analyzer 指定在分类之前如何分析和标记文本。其语法与在 Analyze 终端节点 中定义 analyzer 的语法非常相似。此属性不能与 categorization_filters 同时使用。

categorization_analyzer 字段可以指定为字符串或对象。如果它是字符串,则必须引用 内置分析器 或由其他插件添加的分析器。如果它是对象,则具有以下属性

categorization_analyzer 的属性
char_filter
(字符串或对象数组)一个或多个字符过滤器。除了内置的字符过滤器外,其他插件还可以提供更多的字符过滤器。此属性是可选的。如果未指定,则在分类之前不应用任何字符过滤器。如果您正在自定义分析器的某些其他方面,并且需要实现与 categorization_filters 等效的功能(当自定义分析器的其他方面时,不允许使用 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}"] 
      }
    }
  }
}

应用于分析标记的过滤器。它过滤掉像 bar_123 这样的标记。

请注意,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 
      }
    }
  }
}

应用于分析标记的过滤器。它过滤掉像 bar_123 这样的标记。

需要 11% 的标记权重才能匹配,然后再将消息添加到现有类别,而不是创建新类别。

生成的类别现在非常广泛,合并了日志组。(11% 的 similarity_threshold 通常太低。超过 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
                        ]
                      }
                    ]
                  }
                }
              }
            ]
          }
        }
      ]
    }
  }
}