一句话理解
BERT 推理 = 把分词得到的数字送入模型 → 输出 logits → softmax 转概率 → argmax 取类别 → 查表返回答案。
📖 前置知识
本篇笔记承接 BERT 分词过程详解:第 1 步 从文字到数字。 假设你已经了解:分词器如何将文字转为
input_ids、attention_mask、token_type_ids三个数组。⚠️ 核心认知:BERT 分类模型只能输出"类别编号"
BERT 分类模型的全部输出 = 一个类别编号(如 12)。 它不会说话、不会生成文字、不会调用工具。 所谓的"回答问题",是代码拿到类别编号后查 categories.json 返回预设答案——这一步是 Python 代码做的,不是 AI 做的。
BERT = 一个只会指手指的人 你问他问题 → 他指向 17 个按钮中的第 12 个 就这么多。 按钮后面写着什么答案 → 是你提前贴上去的(categories.json) 按了按钮之后发生什么 → 是你写的代码控制的
承接分词笔记的输出(以"今天天气如何"为例):
input_ids = [101, 791, 1921, 1921, 3698, 1963, 862, 102, 0, 0, ... 0]
[CLS] 今 天 天 气 如 何 [SEP] 填充...
attention_mask = [1, 1, 1, 1, 1, 1, 1, 1, 0, 0, ... 0]
真实内容=1 填充=0
token_type_ids = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ... 0]
单句任务全是 0
↓
第 1 步:模型推理(数字 → logits)
三个数组送入 BERT 模型
模型输出 N 个数字(logits),每个数字对应一个类别的原始得分
↓
第 2 步:softmax 转概率
logits → softmax 公式 → 概率(0~1,总和=1)
↓
第 3 步:argmax 取最大值
概率最高的那个 → 类别编号
↓
第 4 步:查表返回答案
类别编号 → 查 categories.json → 返回预设答案
三个数组送入 BERT 模型(本项目用 ONNX 格式部署,也可以用 PyTorch 格式)
↓
模型输出 17 个数字(logits):
💡 为什么是 17 个?
logits 数量 = 类别数量。本项目 categories.json 定义了 17 个类别(如"打招呼"、"问天气"等),所以模型输出 17 个数字——每个数字对应一个类别的"原始得分"。如果是 82 个类别,就会输出 82 个 logits。
读者可能会问:这个模型和 categories.json 从哪来?
┌─────────────────────────────────────────────────────────────────────┐
│ BERT 模型的"使用流程" │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 第 1 阶段:预训练模型 / 基座模型(bert-base-chinese) │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 来源:Google 发布 │ │
│ │ 能力:理解中文语言(词向量、句法、语义) │ │
│ │ 不能:分类到你的业务类别(不知道"打招呼"是什么) │ │
│ │ │ │
│ │ 类比:一个"懂中文的大学毕业生" │ │
│ │ ├── 他懂语言、懂语法、能理解句子意思 │ │
│ │ └── 但他不知道你们公司的业务 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ 微调(Fine-tuning) │
│ 用你的训练数据教它分类 │
│ ↓ │
│ 第 2 阶段:微调模型 / 分类模型 / 业务模型(hqx-bert-base-cs) │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 来源:你在 bert-base-chinese 基础上训练 │ │
│ │ 能力:理解中文 + 分类到你的 17 个业务类别 │ │
│ │ │ │
│ │ 类比:这个毕业生经过"入职培训"后 │ │
│ │ ├── 仍然懂中文 │ │
│ │ └── 现在还能把用户问题分类到你们公司的 17 个业务类别 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
完整流程:
1. 下载 bert-base-chinese(预训练模型 / 基座模型,懂中文但不懂你的业务)
2. 准备训练数据(问题 → 类别编号)
3. 微调训练(教它"打招呼"是类别12,"问天气"是类别5...)
4. 导出微调模型(PyTorch 或 ONNX 格式)
5. 准备 categories.json(类别编号 → 预设答案)
6. 部署使用(分词 → 推理 → 查表返回答案)
严格来说,training_data 文件夹里的过程叫"微调(Fine-tuning)":
┌─────────────────────────────────────────────────────────────────┐
│ 模型开发的两个阶段 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 阶段 1:预训练(Pre-training) │
│ ├── 做什么:在海量文本上训练模型学习语言 │
│ ├── 数据量:数十GB文本(如整个中文维基百科) │
│ ├── 算力需求:数千GPU小时 │
│ ├── 产出:bert-base-chinese(懂中文,但不懂你的业务) │
│ └── 谁做的:Google │
│ │
│ 阶段 2:微调(Fine-tuning)← training_data 里的是这个 │
│ ├── 做什么:在预训练模型基础上,用你的数据教它做特定任务 │
│ ├── 数据量:几千条问答对(train.jsonl, val.jsonl) │
│ ├── 算力需求:几十分钟~几小时(Colab T4 就够) │
│ ├── 产出:hqx-bert-base-cs(能分类到你的 17 个类别) │
│ └── 谁做的:你 │
│ │
└─────────────────────────────────────────────────────────────────┘
为什么叫"微调"而不是"训练"?
├── "训练"是广义说法(都可以用)
├── "微调"更精确:强调"在已有基础上做小幅调整"
└── 学术/技术文档推荐用"微调"
模型命名约定:
bert-base-chinese → 预训练模型 / 基座模型
hqx-bert-base-cs → 微调模型 / 分类模型 / 业务模型
💡 模型内部发生了什么?
这里只展示了"输入 → 输出"的黑盒结果。如果想了解模型内部 12 层 Transformer 如何逐层"理解"输入,请看 BERT 的 12 层 Transformer:每层在做什么?
[-0.08, -0.38, 0.14, -0.74, -0.31, 1.19, -0.58, -0.55,
-0.26, 0.23, -0.37, 0.16, 1.68, 0.08, -0.53, -0.30, 0.68]
类别0 类别1 类别2 类别3 类别4 类别5 类别6 类别7
类别8 类别9 类别10 类别11 类别12 类别13 类别14 类别15 类别16
这些数字叫 logits(原始得分):
正数 → 模型觉得"可能是这个类别"
负数 → 模型觉得"不太可能"
越大 → 越可能
logits → softmax 公式 → 概率(0~1,总和=1)
P(i) = e^(score_i) / Σ e^(score_j)
转换后:
类别 12:0.2432(24.32%) ← 最高!
类别 5: 约 15%
类别 16:约 10%
其余类别:各占几个百分点
→ argmax = 12(类别编号)
→ 置信度 = 24.32%
一句话:把模型输出的"原始得分"(logits)转成"概率"(0~1,总和=1)。
为什么需要 softmax?
模型输出的 logits:
[-0.08, -0.38, 0.14, -0.74, ..., 1.68, ...]
问题:
├── 有正有负,不好理解
├── 数值大小没有上限
└── 不能直接说"类别12有1.68的把握"——1.68是什么意思?
softmax 的作用:
├── 把所有数字转成 0~1 之间的概率
├── 所有概率加起来 = 1(100%)
└── 现在可以说"类别12有24.32%的把握"
具体例子:
原始 logits(模型直接输出的):
类别0: -0.08 类别5: 1.19 类别12: 1.68
类别1: -0.38 类别6: -0.58 ...
...
经过 softmax 后:
类别0: 0.04 (4%) 类别5: 0.15 (15%) 类别12: 0.24 (24%) ← 最高
...
所有概率加起来 = 1.00 (100%)
类比理解:
想象 17 个学生考试:
logits = 原始分数(可能有负分,不好比较)
学生0: -0.08分 学生5: 1.19分 学生12: 1.68分
softmax = 把原始分数转成"排名百分比"
学生0: 4% 学生5: 15% 学生12: 24% ← 最高
现在一目了然:学生12表现最好(24%)
类别 12 → 查 categories.json
{
"12": {
"category": "打招呼",
"answer": "你好呀~ 我是海小星,海启星的对外形象官!有什么想了解的尽管问我😊"
}
}
→ 返回预设答案
"今天天气如何"
↓ 分词器(见上一篇笔记)
token IDs: [101, 791, 1921, 1921, 3698, 1963, 862, 102, 0, ...]
↓ BERT 模型
logits: [-0.08, -0.38, ..., 1.68, ...] ← 17 个原始得分
↓ softmax
概率: [0.04, 0.03, ..., 0.24, ...] ← 17 个概率
↓ argmax
类别 ID = 12,置信度 = 24.32%
↓ 查 categories.json
答案 = "你好呀~ 我是海小星..."
BERT 分类模型不是"理解"问题,而是"模式匹配":
├── 训练数据中,"今天天气如何"可能和打招呼的样本更相似
├── 它没有"查天气"的工具能力,只能从 17 个类别中选最接近的
└── 24.32% 的置信度说明模型也不太确定(不是特别有把握)
这就是 BERT 分类 + 查表的局限:
只能从预设类别中选,不能真正"理解"和"执行"
→ 真正的天气查询需要 LLM + 工具调用(Function Calling)
import onnxruntime as ort
import numpy as np
# 加载 ONNX 模型(也可以用 PyTorch 模型,代码结构类似)
session = ort.InferenceSession("model.onnx")
# 假设 encoding 是分词器的输出(见上一篇笔记)
input_ids = np.array([encoding.ids], dtype=np.int64)
attention_mask = np.array([encoding.attention_mask], dtype=np.int64)
token_type_ids = np.array([encoding.type_ids], dtype=np.int64)
# 模型推理
outputs = session.run(None, {
"input_ids": input_ids,
"attention_mask": attention_mask,
"token_type_ids": token_type_ids,
})
# outputs[0] 就是 logits(每个类别的原始得分)
logits = outputs[0]
# softmax 转概率
probabilities = np.exp(logits) / np.sum(np.exp(logits))
# argmax 取类别
predicted_class = np.argmax(probabilities)
confidence = probabilities[predicted_class]
print(f"类别: {predicted_class}, 置信度: {confidence:.2%}")
BERT 推理 = 数字 → 答案 的翻译过程
输入:三个数组(input_ids、attention_mask、token_type_ids)
↓
模型推理:三个数组 → logits(每个类别的原始得分)
↓
softmax:logits → 概率(0~1,总和=1)
↓
argmax:概率 → 类别编号(得分最高的那个)
↓
查表:类别编号 → categories.json → 预设答案
关键概念:
logits → 模型输出的原始得分(正数=可能,负数=不太可能)
softmax → 把得分转为概率(方便理解"有多大把握")
argmax → 取概率最高的类别
查表 → 类别编号对应的答案(是代码写的,不是 AI 生成的)
📝 创建:2026-09-14
🔄 标签:BERT、推理、softmax、argmax、分类、categories.json