☰
tfjs-examples 意图分类器:Snips NLU Benchmark 原始数据集的结构解析与使用指南
2026/10/15 17:37:23 网站建设 项目流程
  • 示例工程

【免费下载链接】tfjs-examples

Examples built with TensorFlow.js

项目地址:https://gitcode.com/gh_mirrors/tf/tfjs-examples
点击查看免费下载

导读

在 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):

  1. 意图分类器(intent classifier):将用户查询分类到 AddToPlaylist、GetWeather、PlayMusic 三个意图之一;
  2. Token Tagger(槽位标注器):识别天气类查询中与"地点"相关的 token。

注意:本仓库为只读仓库,数据文件仅用于查看、分析与复现训练流程,不涉及任何修改操作。

原始数据在仓库中的位置

原始 JSON 文件存放在 intent-classifier/training/data/raw/ 目录下,共三个文件:

文件意图键样本数
train_AddToPlaylist_full.jsonAddToPlaylist1942
train_GetWeather_full.jsonGetWeather2000
train_PlayMusic_full.jsonPlayMusic2000

三个文件合计约 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-csvtraining/raw_to_csv.js意图分类器training/data/intents.csv
yarn csv-to-tensorstraining/csv_to_tensors.js意图分类器intents_as_tensors.json+intent_metadata.json
yarn raw-to-tagged-tokenstraining/raw_to_tagged_tokens.jstoken taggerintents_tagged_tokens.ndjson
yarn tokens-to-embeddingstraining/tokens_to_embeddings.jstoken taggertoken_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 ]

关键逻辑包括:

  1. 分词:复用 util.js 中的tokenizeSentence,用正则/\b/按单词边界切分,再 trim 并丢弃空串;
  2. 实体到标签的映射:entityToTag将geographic_poi、state、country、city四类"地点类"实体统一映射为LOC标签,其余实体或空实体全部归为TOK;
  3. 标签体系: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--outPathtraining/data/intents.csvCSV 输出路径
csv_to_tensors.js--gpu关闭使用@tensorflow/tfjs-node-gpu(需 CUDA/CuDNN)
--batchSize32USE 嵌入批次大小
--srcPath/--outFolderdata/intents.csv/data/输入输出
raw_to_tagged_tokens.js--outPathdata/intents_tagged_tokens.ndjsonNDJSON 输出路径
tokens_to_embeddings.js--batchSize512token 嵌入批次大小
--gpu关闭同上
train_intent.js--epochs32训练轮数
train_tagger.js--epochs20训练轮数
--batchSize32训练批次大小
--modelTypebidirectional-lstm可选lstm/dense
--sequenceLength30序列最大长度
--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

项目地址:https://gitcode.com/gh_mirrors/tf/tfjs-examples
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询