- 示例工程
【免费下载链接】tfjs-examples
Examples built with TensorFlow.js
导读
在 TensorFlow.js 官方示例库 tfjs-examples 的 intent-classifier 子项目中,训练数据直接取自 Snips 发布的 NLU Benchmark 数据集。本文围绕该数据集的原始 JSON 格式展开,详细解析其分片文件结构、带实体标注的 clause 表示法、三大意图的数据统计,并完整串联从原始 JSON 到 CSV / NDJSON 再到 Tensor 的训练数据流水线。读完本文,你将掌握该示例项目数据层的完整工作原理,并能在本地复现从原始数据到模型训练的每一个命令。
数据来源与学术背景
intent-classifier/training/data/raw/README.md 明确指出,intent-classifier项目使用的数据来源于 Snips 的 nlu-benchmark 仓库。对应的学术引用为:
A. Coucke et al., "Snips Voice Platform: an embedded Spoken Language Understanding system for private-by-design voice interfaces," arXiv:1805.10190 [cs], May 2018.
这篇论文描述了一个面向"隐私优先"(private-by-design)的嵌入式语音界面所设计的 SLU(Spoken Language Understanding,口语理解)系统。而 nlu-benchmark 正是该工作中公开的自然语言理解评测数据集:它按意图(intent)与槽位(slot / entity)组织标注数据,被广泛用于意图识别(intent classification)与槽位填充(slot filling / token tagging)两类任务的基准测试。
在该示例中,数据被用来训练两类模型(详见 intent-classifier/README.md):
- 意图分类器(intent classifier):将用户查询分类到 AddToPlaylist、GetWeather、PlayMusic 三个意图之一;
- Token Tagger(槽位标注器):识别天气类查询中与"地点"相关的 token。
注意:本仓库为只读仓库,数据文件仅用于查看、分析与复现训练流程,不涉及任何修改操作。
原始数据在仓库中的位置
原始 JSON 文件存放在 intent-classifier/training/data/raw/ 目录下,共三个文件:
| 文件 | 意图键 | 样本数 |
|---|---|---|
train_AddToPlaylist_full.json | AddToPlaylist | 1942 |
train_GetWeather_full.json | GetWeather | 2000 |
train_PlayMusic_full.json | PlayMusic | 2000 |
三个文件合计约 5942 条标注样本(该数字由对仓库实际文件逐条统计得出)。文件名中的_full表示该文件包含该意图的完整训练集,train_前缀则表明它是官方划分的训练子集。
原始 JSON 的顶层结构
每个 JSON 文件的顶层是一个对象,对象的唯一键即意图名(intent),值是该意图下的样本数组。整体结构为:
{ "GetWeather": [ { "data": [ { "text": "What will the weather be " }, { "text": "this year", "entity": "timeRange" }, { "text": " in " }, { "text": "Horseshoe Lake State Fish and Wildlife Area", "entity": "geographic_poi" }, { "text": "?" } ] } ] }data 字段:clause 数组
每条样本的核心字段是data,它是一个由若干 clause(片段)组成的数组。每个 clause 有两种形态:
- 纯文本片段:仅有
text字段,表示这句自然语言中不含任何槽位的部分; - 带实体标注的片段:同时包含
text与entity字段,其中text是被标注槽位的文本内容,entity是该槽位的语义类别(slot label)。
例如上面的GetWeather样本,完整句子为:
"What will the weather be this year in Horseshoe Lake State Fish and Wildlife Area?"
其中this year被标注为timeRange(时间范围),Horseshoe Lake State Fish and Wildlife Area被标注为geographic_poi(地理兴趣点)。
clause 的拼接规则:重建完整查询
从源码 raw_to_csv.js 可以看到,parseRecords函数遍历每条样本的data数组,将所有 clause 的text依次拼接(memo.concat(curr.text))即得到完整的用户查询文本:
const queryText = data.reduce((memo, curr) => { return memo.concat(curr.text); }, '');然后将{query, intent}作为一条记录收集。这一拼接规则同样适用于 token tagger 的数据流水线(见下文raw_to_tagged_tokens.js)。
各意图的实体(entity)分布
对仓库中三个原始文件逐条统计 clause 级别的实体出现次数,可得到以下分布:
AddToPlaylist(1942 条):playlist(1943 次)、playlist_owner(1113 次)、music_item(889 次)、artist(739 次)、entity_name(594 次)。
GetWeather(2000 条):city(851 次)、country(500 次)、condition_temperature(476 次)、condition_description(454 次)、state(493 次)、geographic_poi(291 次)、current_location(273 次)、spatial_relation(209 次)、timeRange(1047 次)。
PlayMusic(2000 条):artist(1172 次)、music_item(792 次)、service(762 次)、year(635 次)、sort(347 次)、album(177 次)、playlist(149 次)、genre(144 次)、track(211 次)。
这些实体正是意图分类与槽位填充任务标注的语义槽位。从实体分布可以看出,天气类查询在槽位设计上比播放列表/音乐类查询更为多样,这也是项目单独为天气数据训练 token tagger 的原因之一。
两条数据流水线:从原始 JSON 到模型输入
package.json 中定义了四个数据处理脚本与一个一键聚合脚本。它们分别服务于"意图分类器"和"token tagger"两条流水线:
| 脚本命令 | 底层文件 | 服务对象 | 输出 |
|---|---|---|---|
yarn raw-to-csv | training/raw_to_csv.js | 意图分类器 | training/data/intents.csv |
yarn csv-to-tensors | training/csv_to_tensors.js | 意图分类器 | intents_as_tensors.json+intent_metadata.json |
yarn raw-to-tagged-tokens | training/raw_to_tagged_tokens.js | token tagger | intents_tagged_tokens.ndjson |
yarn tokens-to-embeddings | training/tokens_to_embeddings.js | token tagger | token_embeddings.ndjson |
执行yarn prep-data(等价于npm-run-all raw-to-csv csv-to-tensors raw-to-tagged-tokens tokens-to-embeddings)可顺序运行全部四个步骤。整个过程只需运行一次,耗时约 15~25 分钟,输出全部写入training/data目录。
流水线一:意图分类器
第一步:raw-to-csv。raw_to_csv.js 读取上述三个原始 JSON 文件,将每条样本规整为{query, intent}记录,再用d3-dsv的csvFormat写出两列 CSV(query,intent),默认输出到training/data/intents.csv(约 5942 行)。
第二步:csv-to-tensors。csv_to_tensors.js 负责将 CSV 转换为 Tensor:
- 定义三个标签
LABELS = ['AddToPlaylist', 'GetWeather', 'PlayMusic']; - 用
tf.util.shuffle打乱数据; - 标签用
tf.oneHot转为 one-hot 编码; - 查询文本用Universal Sentence Encoder(USE)批量嵌入,得到 512 维向量(batchSize 默认 32);
- 最终通过
tf.concat拼接所有批次,写出两个 JSON:intents_as_tensors.json:{xsArr, ysArr},即嵌入向量与 one-hot 标签数组;intent_metadata.json:{labels, xsShape, ysShape},用于训练时还原张量形状与反推标签。
训练时 train_intent.js 读取这两份文件,用tf.tensor(xsArr, metadata.xsShape)还原张量,并配合 intent_model.js 中的模型结构训练:
const model = tf.sequential(); model.add(tf.layers.dense({ inputShape: [512], // USE 嵌入维度 units: NUM_CLASSES, // 3 activation: 'softmax', })); model.compile({ optimizer: 'adam', loss: 'categoricalCrossentropy', metrics: ['accuracy'], });可见意图分类器是一个把 512 维嵌入直接映射到 3 类的单层 softmax 模型,数据规模完全放得进内存,因此使用model.fit(xs, ys, {epochs, validationSplit: 0.15})一次性训练(默认 32 个 epoch)。
流水线二:Token Tagger
第一步:raw-to-tagged-tokens。raw_to_tagged_tokens.js 只处理train_GetWeather_full.json(天气数据),输出 NDJSON 文件,每行格式为:
[ [{token: string, tag: string}, ...], intent: string ]关键逻辑包括:
- 分词:复用 util.js 中的
tokenizeSentence,用正则/\b/按单词边界切分,再 trim 并丢弃空串; - 实体到标签的映射:
entityToTag将geographic_poi、state、country、city四类"地点类"实体统一映射为LOC标签,其余实体或空实体全部归为TOK; - 标签体系:util.js 定义
TAGS = ['TOK', 'LOC', '__PAD__'],其中PAD_IDX = 2用于序列 padding。
第二步:tokens-to-embeddings。tokens_to_embeddings.js 统计全量查询中出现的 token,去重后用 USE 逐一嵌入(batchSize 默认 512),写出token_embeddings.ndjson,每行形如[token, embedding512维数组]。
训练环节。train_tagger.js 在训练时动态组装样本:按sequenceLength(默认 30)截断/补齐每个句子的 token 序列,不足部分用__PAD__填充(其嵌入用tf.ones([512])表示),将每个 token 替换为其 USE 嵌入,标签替换为 one-hot,最终形成[batchSize, sequenceLength, 512]的输入张量,通过model.fitDataset训练。tagger_model.js 支持三种架构(--modelType可选bidirectional-lstm、lstm、dense,默认双向 LSTM),并对每个时间步做timeDistributed的 softmax 分类。还可通过--boostLOCTag将classWeight设为[1, 1.5, 1],加重 LOC 标签的误分类惩罚。
数据文件的命令行参数速查
四个数据处理脚本与两个训练脚本均基于argparse实现命令行参数,默认值在源码中定义:
| 脚本 | 参数 | 默认值 | 说明 |
|---|---|---|---|
raw_to_csv.js | --outPath | training/data/intents.csv | CSV 输出路径 |
csv_to_tensors.js | --gpu | 关闭 | 使用@tensorflow/tfjs-node-gpu(需 CUDA/CuDNN) |
--batchSize | 32 | USE 嵌入批次大小 | |
--srcPath/--outFolder | data/intents.csv/data/ | 输入输出 | |
raw_to_tagged_tokens.js | --outPath | data/intents_tagged_tokens.ndjson | NDJSON 输出路径 |
tokens_to_embeddings.js | --batchSize | 512 | token 嵌入批次大小 |
--gpu | 关闭 | 同上 | |
train_intent.js | --epochs | 32 | 训练轮数 |
train_tagger.js | --epochs | 20 | 训练轮数 |
--batchSize | 32 | 训练批次大小 | |
--modelType | bidirectional-lstm | 可选lstm/dense | |
--sequenceLength | 30 | 序列最大长度 | |
--boostLOCTag | 关闭 | 加重 LOC 误分类惩罚 |
注意:所有脚本默认走 CPU 路径(@tensorflow/tfjs-node);使用 GPU 需要本机配置 CUDA 与 CuDNN,且依赖@tensorflow/tfjs-node-gpu(版本见 package.json)。
本地复现:从原始数据到完整模型
在 intent-classifier 目录下按顺序执行:
# 1. 安装依赖(yarn 或 npm) yarn # 2. 一键处理全部原始数据(约 15~25 分钟,只需一次) yarn prep-data # 3. 训练意图分类器 yarn train-intent # 4. 训练 token tagger yarn train-tagger # 5. 将 training/models 软链到 dist/models 并启动浏览器演示 yarn intent-app # 意图分类演示(聊天界面) yarn tagger-app # token tagger 演示(可视化为 token、标签与嵌入热图)训练产物默认输出到training/models目录:models/intent/(意图模型)、models/bidirectional-tagger/(默认 tagger),若改用--modelType lstm或dense则分别输出到models/lstm-tagger/、models/dense-tagger/。copy_models.js通过软链接将training/models挂到dist/models,供前端应用加载(copy_models.js)。
前端推理链路与训练时完全一致:app/index.js 先用 USE 将输入嵌入为 512 维向量,再送入意图模型预测,softmax 最大值低于阈值 0.90 时返回"不确定"表情,否则按标签返回对应图标;app/tagger.js 则复用训练时的分词函数tokenizeSentence与 padding 逻辑(tf.ones([toPad, EMBEDDING_DIM])补齐不足序列),逐 token 输出标签与置信度,并用 d3 把嵌入值渲染为可视化热图。
结语
Snips NLU Benchmark 的原始 JSON 以"意图 + clause 数组 + 实体标注"的结构承载了 5942 条、三大意图的高质量标注样本,是 intent-classifier 项目两条训练流水线的共同起点。理解data数组的拼接规则、实体到标签的映射,以及四个数据处理脚本各自产出的中间文件,就能完全掌控这个示例从原始数据到可运行模型的全部细节,并在此基础上自由调整批次、序列长度、模型架构等超参数。
- 示例工程
【免费下载链接】tfjs-examples
Examples built with TensorFlow.js
相关推荐
Snips NLU核心组件解析:意图分类器与槽位填充器的完美协作
Snips NLU核心组件解析:意图分类器与槽位填充器的完美协作 在当今快速发展的自然语言处理领域,Snips NLU作为一款开源的Python库,专门用于从文
人工智能NLP机器学习Snips NLU 训练数据集格式详解
Snips NLU 训练数据集格式详解 概述 Snips NLU 是一个强大的自然语言理解 NLU 引擎,它通过机器学习算法和训练数据来识别用户意图。本文将深入
人工智能NLP机器学习Snips NLU 数据模型与核心概念解析
Snips NLU 数据模型与核心概念解析 引言 自然语言理解 NLU 是人工智能领域的重要研究方向,而Snips NLU作为一个优秀的开源自然语言理解引擎,其
人工智能NLP机器学习
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考