Skip to main content
naive_bayes (NAIVE_BAYES) 字典使用多项式 朴素贝叶斯 模型对文本进行分类,这是文本分类中的标准事件模型:它根据输入中各个 n-gram 在每个类别中出现的频次为其评分。你需要提供一张按类别统计的 n-gram 计数 表,字典会在加载时一次性将其编译为模型,随后用该模型对你传入的任意文本进行分类。 它适用于快速、轻量级的文本分类任务,例如情感分析、topic 或垃圾信息标注,以及语言或文字系统检测。 你可以使用以下三个函数查询该字典: 普通的 dictGet 也能用于分类 (参见说明) 。还有一个函数 naiveBayesNgrams 不执行分类——它会以与该字典相同的方式将文本拆分为 n-gram,因此你可以从原始文本构建训练数据 (参见从原始文本构建训练数据) 。

快速入门

这里我们构建一个用于情感分析的、基于标记模式的 unigram (n = 1) 模型。 1. 创建一个源表,用于存储各类别的 n-gram 计数:
2. 插入训练数据 —— 单个词 (unigram) 及其在正类 (1) 和负类 (0) 中各自的出现频次:
3. 创建使用 NAIVE_BAYES 布局的字典
PRIMARY KEY ngramngram 列设为键——但对 NAIVE_BAYES 字典来说,这个“键”是你传入用于分类的文本,而不是可供查找的存储值 (参见 字典结构) 。LAYOUT 用于配置模型:class_attribute 'class_id'class_id 标记为类别标签 (因此,另一个属性 count 表示每个类别的出现次数) ,n 1 使用 unigram,mode 'token' 会将文本拆分为按空白分隔的词 (参见 布局参数) 。 4. 分类naiveBayesClassifier 返回类别 id:
1 对应正类,这是根据我们在步骤 2 中插入的训练数据得出的。
同样,0 对应负类。 通过 dictGet 也能得到相同的结果:
获取该预测结果的概率,或各类别的概率:
预测结果为类别 0 (负类) ,概率为 0.64
naiveBayesClassifierWithAllProbs 返回所有类别,并按可能性从高到低排序,各类别概率之和为 1.0——此处负类为 0.64,正类为 0.36

工作原理

训练 (在加载时) 。 每个源行都是一个 (n-gram, class, count) 观测值。字典加载时,这些行会被一次性编译进模型中。重复的 (n-gram, class) 行会被累加,count = 0 的行则会被忽略。 分类 (在查询时) 。 要对字符串进行分类,模型会:
  1. 根据 moden 将其拆分为 n-gram (请参见标记化模式) 。
  2. 结合类别先验以及输入中各个 n-gram 在该类别中出现的频次,为每个类别打分。
  3. 按得分对类别排序。得分最高的类别就是 naiveBayesClassifier 返回的预测结果;naiveBayesClassifierWithProbnaiveBayesClassifierWithAllProbs 还会返回概率——分别是该类别的概率,或全部类别的概率。
影响每个类别得分的因素有两个。第一个是 alpha,用于平滑处理。平滑可以防止模型仅仅因为某个 n-gram 在训练时未出现在某个类别中,就给该类别打零分。较小的 alpha 会让模型更依赖训练数据,因此某个类别的得分可能远高于其他类别;但当训练数据较少或分布不均时,也可能使模型过于敏感。较大的 alpha 会降低 n-gram 计数的影响,因此不同类别的得分会更接近。如果 alpha 非常大,n-gram 信息几乎不起作用,得分主要由类别先验决定 (下一段会介绍) 。 第二个因素是类别先验——也就是模型在查看文本之前,对每个类别可能性的预先假设。它相当于在考虑任何 n-gram 之前,每个类别已有的初始得分,因此先验越高,该类别就越可能被预测出来。其设置方式取决于 priors_mode。默认情况下 (proportional) ,训练数据中 n-gram 总数更多的类别会以更高的初始得分起步。使用 uniform 时,每个类别的起点都相同,因此结果仅由 n-gram 决定。使用 explicit 时,你可以自行设置每个类别的初始值。请参见先验模式 如果某个 n-gram 从未在训练数据中出现过,它会被忽略:它不属于模型的词汇表,因此既不会对任何类别有利,也不会对任何类别不利。 该算法遵循用于文本分类的多项式朴素贝叶斯模型;请参见 Manning, Raghavan & Schütze, Introduction to Information Retrieval, ch. 13 (Text Classification and Naive Bayes)

字典结构

NAIVE_BAYES 字典具有固定的结构:
  • PRIMARY KEY 是单个 String 列——即 n-gram。在查询时,这个”key”是你传入用于分类的文本,而不是存储的查找键。
  • 此外,必须声明恰好两个无符号整数属性:类标签和出现次数。类别 ID 在内部始终使用 UInt32,因此类标签必须能放入 UInt32 (最大为 4294967295) ,即使你将其属性声明为 UInt64 也是如此。超出范围的值会在字典加载时被拒绝,而不是在创建字典时。声明的类型也是一样:如果源类别 ID 或计数无法放入所声明的属性类型,加载会失败,而不会被静默截断。
  • class_attribute 布局参数用于指定哪个属性是类标签;另一个属性会自动视为计数。这两个属性可以按任意顺序声明。
源表保存的是预聚合计数:每个 (n-gram, class) 一行,表示该 n-gram 在该类中出现了多少次。你可以通过对语料库进行标记化并对结果分组来生成这些计数,可以在你自己的训练管道中完成,也可以在 ClickHouse 中基于原始已标注文本完成 (参见 Build training data from raw text) 。字典只使用这些计数。 更新模型。 由于该模型是由表支撑的字典,因此可通过更新表并重新加载来重新训练:

布局参数

你可以使用 CREATE DICTIONARY DDL (如上面的快速入门所示) 或在 XML 配置文件中定义字典;关于该文件应放在哪里,请参见字典布局。下面的示例设置了所有布局选项,方便你查看完整配置——只有 class_attributenmode 是必需的,其余项的默认值见上表。在配置文件中,先验会写成重复的 prior 元素 (每个类一个,如下所示) ;bytecodepoint 的填充标记写成数字 (配置无法承载原始字节) ;而 token 字面量会在需要时进行 XML 转义,因此 <s> 会变成 &lt;s&gt;

标记化模式

mode 决定什么是“标记”,因此也决定 n-gram 的具体形式。源 n-gram 必须由相同的 moden 生成。
  • byte — 每个标记都是单个字节;不假定输入为 UTF-8。设 n = 2'abc' 会生成字节二元组 'ab''bc'适用于 对任意字节序列进行语言或编码检测,以及任何子字符层面特征很重要的数据。通常与 n >= 2 搭配使用。
  • codepoint — 每个标记都是一个 Unicode 码点;输入会按 UTF-8 解释。设 n = 1'café' 会生成码点 'c''a''f''é'适用于 文字系统和语言检测,以及在空白词边界不可靠时的短文本或 CJK 文本。 (源 n-gram 必须是有效的 UTF-8;查询输入会以宽松方式解码——参见 说明。)
  • token — 每个标记都是一个由 ASCII 空白字符 (空格、制表符、换行符、回车符、换页符、垂直制表符;连续出现会合并为一个分隔符) 分隔的单词。非 ASCII 的 Unicode 空白字符,如 U+00A0 (不间断空格) 或 U+2003 (em 空格) ,会作为分隔符,而是保留在标记内部。只有空白字符会触发拆分——不会转为小写,也不会去掉任何字符——因此 'Hello, World!' 会变成标记 'Hello,''World!' (逗号、! 和大写字母都会保留) ,在 n = 2 时,它们会组成唯一的二元组 'Hello, World!'适用于 以空格分词的语言中的词级分类——情感、topic、垃圾信息、句子语言识别。

先验模式

先验是模型在查看文本之前对每个类的预先判断。priors_mode 用于选择如何设置先验。
  • proportional (默认) — 每个类的先验与其在训练数据中的 n-gram 总计数成正比——也就是该类 count 列的总和,而不是其行数或训练文档数——因此,出现越频繁的类,初始概率就越高。在以下情况下选择它:训练时各类的占比 (按 n-gram 总计数计算) 与您预期在查询时出现的频率一致。无需额外提供任何内容——它会根据源计数自动推导得出。
  • uniform — 开始时每个类的可能性都完全相同,因此没有哪个类会先天占优,预测结果完全由输入中的 n-gram 决定。在以下情况下选择它:各类分布均衡,或者训练频率并不能反映每个类在查询时的实际出现频率。无需提供任何内容。
  • explicit — 您通过 priors [(0, 0.6), (1, 0.4)] 显式提供先验:每个类对应一个 (class, probability) 对,每个概率都大于 0 且不超过 1,并且总和为 1.0在以下情况下选择它:您知道真实的基础比例,并且它们与训练数据不同——例如,即使训练集是均衡的,生产环境流量中也只有 1% 是垃圾信息。请根据各类在真实场景中的预期占比来计算它们。

边界标记 (padding)

默认情况下,padding 处于关闭状态。它只在 n > 1 时才有意义,因为它能让模型利用文本开头和结尾的信号,从而提高准确性。 为什么有帮助。n > 1 时,文本中间的 n-gram 同时具有完整的左侧和右侧上下文,但第一个和最后一个标记则没有。加入边界标记后,会生成用于表示“文本开始”和“文本结束”的 n-gram,这样模型就能学习与位置相关的模式——例如,某个词在消息开头时特别有辨识度,或者某个字符在词语末尾时更常见。 你必须这样做:
  1. 分别决定两侧是否启用。 start_tokenend_token 是相互独立的——可以只设置其中一个、同时设置两个,或者两个都不设置。空值表示该侧不进行填充。
  2. 选择不常见的值,避免与真实数据冲突,例如 byte 使用 0x01 / 0xFFcodepoint 使用 U+10FFFE / U+10FFFF,或 token 使用 <s> / </s>
  3. 用相同的填充方式生成训练 n-gram。 字典会对查询输入进行填充,但绝不会对你的源数据做填充,因此边界标记必须预先包含在你加载的 n-gram 中。要最简单地确保两者一致,可以用 naiveBayesNgrams 构建源数据,并传入与 layout 相同的 start_tokenend_token (以及 nmode) ——它生成的正是字典在查询时产生的带填充 n-gram。
填充标记的格式取决于 mode:
  • byte — 表示字节值的数字,可用十进制或 0x 十六进制表示 (因此 '1''0x01' 相同) :
  • codepoint — 表示 UTF-8 码点的数字,可用十进制或 0x 十六进制表示 (因此 '1114110''0x10FFFE' 相同) :
  • token — 标记字符串的字面值:

从原始文本构建训练数据

如果你是从已标注的原始文本开始,而不是从预聚合计数开始,请使用 naiveBayesNgrams 函数将其拆分为 n-gram。为它提供与字典 布局 相同的 nmodestart_tokenend_token,它就会精确生成字典所需的 n-gram,因此训练数据会与模型在查询时看到的内容一致。 给定一个由 (class_id, text) 行组成的表,只需一次 GROUP BY 就能构建出 (ngram, class_id, count) 输入:
training_data 现在可作为 NAIVE_BAYES 字典的有效源 (这里使用的是标记 unigram;请调整 nmode 参数以匹配你的布局) 。该字典会完全按输入原样对查询内容进行分词,因此如果训练文本是小写,而查询文本不是小写,它们的 n-gram 就不会匹配,模型准确性也会受到影响。
先验和文档计数proportional 先验 (默认值) 是按每个类别的n-gram 总数加权的,而不是按其文档数加权。如果你想使用经典的文档频率先验 (documents_in_class / total_documents) ,请从原始 docs 表中计算,并通过 priors_mode 'explicit' 传入:
然后,从 training_data 创建字典,传入上面计算出的显式先验,并对新的评论进行分类:
类别 1 表示正类,0 表示负类,因此这两条评论都被正确分类。

更多示例

字节模式 — 字节二元组 (n = 2mode 'byte';类 0 = 由字母 ad 构成的字符串,类 1 = 字母 xz) :
码位模式 — 逐字符检测书写系统 (n = 1mode 'codepoint';类 0 = Latin,1 = Cyrillic) :
使用 store_source 读回训练数据
基于原始文本的语言检测 —— 对带边界填充的短词进行检测 (n = 2mode 'codepoint';类 0 = 英语,1 = 西班牙语) 。训练用的 n-grams 通过 naiveBayesNgrams 基于原始单词构建,而边界标记——同时传递给该函数和 布局——可让模型利用每个单词的首尾字母:

注意事项

  • 计算型字典语义。 这是一个计算型字典:dictGet(dict, '<class_attribute>', text) 会对 text 进行分类 (键是待分类的输入,而不是存储的键) ,count 属性无法查询,且 dictHas 始终返回 1
  • 加载时的源数据验证。 每个源 n-gram 都必须与配置的 nmode 匹配 (在 codepoint 模式下,还必须是有效的 UTF-8) ;只要不匹配,加载就会失败。由于零计数行会被忽略 (参见工作原理) ,如果源数据为空,或只包含零计数行,就没有可用于训练的数据,因此会加载失败。
  • 查询时的分词较为宽松。 与源数据验证不同,查询输入永远不会被拒绝。在 codepoint 模式下,无效的 UTF-8 字节会按尽力而为的方式解码,而不会导致查询失败;在 token 模式下,只有 ASCII 空白字符才会分隔单词 (如 U+00A0 这类 Unicode 空白仍会保留在标记内部) 。即使输入格式不合法,也仍然会进行分类——通常会依据先验概率,因为其中的 n-grams 不会与已训练的内容匹配。
最后修改于 2026年7月23日