Skip to content
88linPublic

About

In-browser cat meow context classifier — record/upload a meow, local AI tells you if it is brushing / waiting for food / isolation. Zero upload, pure client-side.

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

28 Commits

Folders and files

Repository files navigation

喵语通 MeowScope 🐱

License: MIT Zero dependency Privacy

在浏览器里听懂猫在说什么。 录音或上传猫叫,本地 AI 实时判断它在哪种情境下发声(被刷毛 / 等饭 / 被隔离)——所有计算都在你的浏览器完成,音频不会上传到任何服务器。

在线体验:https://88lin.github.io/meowscope/ (或直接打开 public/index.html 本地跑)

产品演示:录音 → 分析 → 频谱图 → 判定

分享到微信 / X / Telegram 时的链接预览图: MeowScope 分享预览图

分析结果可以一键生成 1200×630 的分享卡片(含波形、梅尔频谱图、三条概率、置信度),直接保存或复制:

分享卡片示例

它能听懂什么

情境 场景 特点
🪮 被刷毛 brushing 主人梳毛时 短促、音调平缓
🍚 等饭 waiting_for_food 站在食盆前 急促重复、最有辨识度
🚪 被隔离 isolation 被独自留在陌生环境 鸣叫更长、音更高

先说实话:喵语没有百科全书。这个模型只做情境分类,不做"翻译句子"。同一声喵在不同猫、不同场合含义可以完全不同(学界称为 semantic aliasing,语义混叠)。

诚实的成绩单

训练/测试按猫划分(模型从未听过测试集里那只猫的声音),不是作弊的随机划分:

指标 数值 说明
按猫分组 5 折交叉验证 60.2% 训练与测试永远来自不同的猫
留一猫验证 (LOSO) 59.8% 每折只留一只猫当测试集
macro-F1(三类平均) 52.6% 把少数类也算进来,防止靠「一律猜被隔离」刷分
多数类基线 50.2% 完全不训练、一律猜「被隔离」的分数——这才是真正的地板
随机猜 33.3% 三选一

细节见 public/metrics.json。请特别注意多数类基线 50.2%:isolation 占了数据集一半,所以「60% 准确率」相对随机的 33% 看起来不错,但相对不训练也就高了 10 个百分点。这是我们选择公开多数类基线的理由。

浏览器里的判定与 Python 训练侧逐位一致(不是"接近"):CI 有端到端对拍闸门,同一段音频两侧算出的三类概率完全相同。这条闸门是补出来的——见下方「跑一遍校验」里判定闸门存在的理由。

为什么不去追文献里的 90%+

工作 报告准确率 评测方式
Ntalampiras 2019(同源数据,DAG-HMM) 95.94% 10 折随机划分
Pandeya 2018 91.13% 随机划分
Ferdiana 2021 88.47% 随机划分
Purrai 81.1% 随机划分
本模型 59.8–60.2% 按猫划分

同一只猫的几十段录音被随机分到训练集和测试集,模型只要记住「这只猫的声音长什么样」就能拿高分——这叫个体泄漏。我们检索到的公开猫叫分类工作中,没有一篇报告按个体划分的数字。所以那些 90%+ 与本项目的 60% 不是同一把尺子,差距主体是评测协议,不是模型。

参照系:Nicastro & Owren (2003) 与 Prato-Previde et al. (2020) 测得人类分辨陌生猫发声情境仅 26-41% 正确——本模型高于陌生人,但远低于「养猫人听自家猫」。

已知上限与下一步

我们已经用实验确认了这条手工特征路线的天花板(下面都是按猫划分、多种子重复评估的结果):

尝试 结果
更丰富的谱特征(谱对比度/带宽/平坦度,107 维) 准确率持平,macro-F1 略升——加特征已经不涨分了
改用 MLP / SVM / 梯度提升 / 集成 准确率持平或下降(MLP 在 LOSO 上 macro-F1 更高,但在 5 折上明显更差,不稳定)
数据增强(加噪、变速、音量) 反而降分:440 条样本下增强主要引入噪声,不是新信息
采样率从 22.05k 降到 8k(对齐数据集原生采样率) 降分(8k LOSO 53.6% vs 22.05k 56.8%)——假设被证伪
把随机森林从 400 棵换到 150 棵 + 深度上限 10 精度不变,模型体积降到 1/3 ✅

结论:继续堆手工特征不会带来质变。我们把希望寄托在预训练音频表征上,并做了真实实验(见下节)——结论是数据量不变的前提下,通用预训练帮不上忙;蒸馏管线被验证可用,等数据众筹见效后值得用领域匹配的 embedding 重试。

预训练表征实验(2026-10)

用 MIT/ast-finetuned-audioset-10-10-0.4593(AudioSet 527 类预训练的 ViT,冻结)提 embedding,与线上管线同一协议(LOSO 按猫划分、21 折、线性重采样)。线上基线被复现(59.8%),说明协议对齐:

配置 LOSO 准确率 macro-F1 与基线的差(配对 bootstrap 95% CI)
MFCC+RF(线上基线) 59.8% 0.526 —
AST CLS embedding + LR 59.1% 0.543 未单独检验
AST CLS embedding + RF 60.2% 0.499 未单独检验
AST 平均 embedding + LR(最强 teacher) 62.5% 0.578 [-2.3, +7.7],P(更优)=0.84
AST logits + LR 53.9% 0.503 负向
MFCC + AST 拼接 + LR 56.4% 0.521 负向
蒸馏 student(MFCC+RF,只学 teacher 软标签) 62.1% 0.551 [-2.3, +6.8],P=0.83

三个诚实的结论:

  1. 通用预训练在这份数据上只值 +2~3 个点,且不显著(CI 均跨 0)。Ghani 等 (2023) 报告 0.74–0.92 用的是 Perch——在鸟和家养动物叫声上预训练的领域匹配表征;AudioSet 是通用声音,猫叫只是其中一小片。差距主体在预训练域和数据量,不在方法本身。
  2. 蒸馏管线是通的:student 完全不看真实标签、只学 teacher 软标签,拿到 62.1%,与 teacher(62.5%)无显著差异(P=0.43),方向上也超过看真实标签训练的自己(59.8%)。机制有效,缺的是一个更强的 teacher。
  3. 蒸馏不改变部署形态:student 就是线上同款 MFCC+RF,浏览器里照样跑。数据众筹见效后,用领域匹配 embedding(Perch 类)做 teacher 重蒸,是这条路线的正确下一步。

复现:python3 scripts/ast_experiment.py(首次需 AST 权重,之后读 build/ 缓存;数值记录见 docs/ast_experiment.json)。

用起来顺手吗(交互)

  • 四种方式喂音频:录音、选择文件、把文件拖到卡片上、Ctrl/⌘+V 粘贴
  • 空格键开始 / 停止录音(焦点不在按钮或输入框上时)
  • 暗色模式:跟随系统,也可用右上角 🌙 手动切换(记在 localStorage)
  • 置信度环:结果区直接显示把握有多大,配三条概率条
  • 梅尔频谱图:把「模型真正看到的输入」直接画出来,不用猜 AI 在想什么
  • 声学特征行:音高中位数 / 音高抖动 / 时长,判定依据一目了然
  • 长录音自动截取:训练数据全部落在 1–4 秒,所以超过 4 秒的音频会自动定位到音量最高的发声段再分析;波形照画整段,高亮处就是实际分析的那一段,下方注明时长。实测一段 14 秒录音里 2 秒的猫叫:整段分析会错判成 isolation 42%,截取后回到 brushing 70%(与单独分析那 2 秒的 72% 几乎相同)
  • 最近几次:连续录几段时可在结果下方来回切换回看,方便对比(只存内存)
  • 对照示例:一键播放判定类别在数据集里的真实猫叫,跟自己的录音比一比
  • 导出分享图:1200×630 PNG,含波形、梅尔频谱图、三类概率、日期
  • 移动端 44px 触控目标;浅色/深色两套配色均满足 WCAG AA 正文对比度

工作原理

音频 → 22.05kHz 单声道 → 95 维特征(MFCC + 差分 + 谱统计 + YIN 音高 + 时长)
     → 随机森林 150 棵(深度上限 10)→ 三类概率
  • 特征:MFCC 用纯 JS 实现了 librosa 的完整管线(slaney mel 滤波器组、periodic Hann、DCT-II ortho、power→dB top_db=80),与 Python 逐位对拍,CI 每次提交都会跑(python3 scripts/parity_check.py)。STFT 用 radix-2 FFT 实现,与改造前的朴素 DFT 对拍最大相对差 ~2e-12。
  • 训练/推理一致性:浏览器里的特征提取必须与 scripts/train.py 完全一致,否则模型等于在用另一套输入。曾经这里踩过坑——谱质心/谱滚降按功率谱算(librosa 用幅度谱)、分帧少了 center padding、delta 边缘处理不同、过零率差 1 个计数,导致质心 3729 vs 2075 这种量级的偏差。现在由 CI 闸门守住。
  • 模型:随机森林——在 440 样本的小数据上比深度网络更稳、且无需张量库。150 棵、深度上限 10,导出为紧凑数组格式的 JSON,216KB(gzip 后几十 KB)。
  • 速度(2 秒音频实测,Chromium):特征提取 ~40ms + YIN 音高 ~120ms + 森林推理 ~1ms,合计约 0.2 秒。改用 FFT 之前,朴素 DFT 让特征提取要 1.1–1.4 秒。
  • 重采样一致性:训练加载器(train.py:_load_linear)与网页 wav 解析路径使用完全相同的线性插值,保证训练/推理看到同一段波形。浏览器录音(webm/mp3)走 Web Audio 解码 + 同样的降采样。

复现

# 1. 数据:CatMeows (Zenodo, CC-BY-NC)
curl -L -o dataset.zip https://zenodo.org/api/records/4008297/files/dataset.zip/content
python3 -c "import zipfile; zipfile.ZipFile('dataset.zip').extractall('data/')"
# 解压后 wav 在 data/dataset/,文件名前缀 B/F/I 即类别
# 数据集是 CC BY-NC:可研究可开源分发,禁止商用

# 2. 训练
pip install librosa scikit-learn joblib numpy scipy
python3 scripts/train.py         # 输出 public/metrics.json + public/model.joblib

# 3. 导出为浏览器模型(可选深度:改脚本里的 max_depth)
python3 scripts/export_js.py     # 输出 public/forest.json(紧凑数组格式)

# 4. 特征对拍验证(Node >= 18)
python3 scripts/parity_check.py  # JS↔Python 逐位对拍,不需要下载数据集

分享图片

项目里有两张图,用途不同:

图片 用途 尺寸
public/og-image.png 站点分享预览:把链接发到微信 / X / Telegram / Slack 时自动显示的大图 1200×630
结果卡片(运行时生成) 单次分析结果:点「📷 分享 / ⬇️ 保存图片」现画,含波形、梅尔频谱图、三类概率、置信度、日期 1200×630(2x 超采样)
  • OG 图由 scripts/make_og_image.py 生成(需要一个 CJK 字体,用 MEOWSCOPE_FONT_BOLD / MEOWSCOPE_FONT_REGULAR 指定):
    python3 scripts/make_og_image.py     # → public/og-image.png
  • 结果卡片在浏览器里用 Canvas 现画,中英文随页面语言;优先调用系统分享,桌面端退化为复制到剪贴板(可直接粘进微信),再退化为下载 PNG。文件名形如 meowscope-waiting_for_food-67-20261011.png。

校验(CI 每次提交都跑)

闸门 命令 查什么
特征对拍 python3 scripts/parity_check.py JS 特征提取 ↔ Python librosa 是否一致(不需要数据集)
资源自洽 node scripts/check_assets.cjs index.html 引用的资源是否都存在、中英文案 key 是否一致
UI 渲染 node scripts/ui_check.cjs 真实浏览器里量:进度条/频谱图画没画出来、置信度环、移动端是否溢出、明暗对比度
数据众筹 node scripts/contribute_check.cjs 假麦克风录样本 → 打包 zip → 系统 unzip 真解压 → manifest 字段 → 音频不落 localStorage
端到端判定 node scripts/judgement_check.cjs 浏览器判出的三类概率必须等于 Python;并断言喂给森林的向量是 95 维

判定闸门存在的理由:parity_check 只验证「每个特征算得对」,不验证「喂给模型的是不是模型期望的那个向量」。我们在这里栽过一次——6 维音高特征忘了拼进森林输入,485 个用到音高的决策节点拿到 undefined、比较恒为 false 而全走右分支,特征逐个都对,判定却是错的,所有旧闸门全绿。现在直接比最终概率。

UI 闸门存在的理由:只查「文字对比度」「几何溢出」抓不到元素压根没画出来的问题。 进度条填充是个 <span>,行内元素会忽略 width/height,实际渲染是 0×0—— 页面上什么都不显示,控制台也没有任何报错。现在由 scripts/ui_check.cjs 在真实浏览器里量填充宽度与标注百分比是否吻合。

本地跑:

npm install && npx playwright install chromium
(cd public && python3 -m http.server 8931 &) ; sleep 2
npm run check

(站点本体仍然零依赖零构建——package.json 里的东西只在开发期校验时用, 不会进 public/,也不影响页面加载。)

目录

public/              # 网站本体(单页应用,零依赖零构建)
  index.html         # 页面 + 交互 + 推理 UI
  features.js        # 纯 JS MFCC 特征提取(与 librosa 对拍)
  forest.json        # 随机森林模型(JSON 决策树,按需 fetch)
  metrics.json       # 训练指标,页面「诚实的数据」一节自动渲染
  demos.js           # 三段示例猫叫(base64,点击时才加载)
  i18n.js            # 中/英文案
  og-image.png       # 社交分享预览图(1200×630)
  favicon.svg / icon-192.png / icon-512.png
  llms.txt           # 给生成式引擎看的站点速览(GEO)
  robots.txt / sitemap.xml
  manifest.webmanifest / sw.js   # PWA,可离线安装
scripts/
  train.py           # 训练 + 按猫分组交叉验证
  export_js.py       # sklearn 森林 → JSON
  pyfeat.py          # Python 参考特征(对拍用)
  test_parity.cjs    # Node 对拍测试
  make_og_image.py   # 生成 OG 分享图
docs/
  demo.gif                # 产品演示(README 顶部内嵌播放)
  share-card-example.png  # 分享卡片示例

根目录另有 CONTRIBUTING.md(参与指南)与 .github/ISSUE_TEMPLATE/data_contribution.yml(数据贡献表单)。

训练产物(forest.json / features.js / metrics.json)只有 public/ 这一份来源;scripts/train.py、export_js.py 产出的新模型直接覆盖 public/ 下的同名文件即可。

给生成式引擎看的事实(GEO)

为了让 AI 搜索引擎引用到准确数字,站点提供了三样东西:

  • public/llms.txt:站点速览 + 关键事实(准确率、数据集、技术栈、许可)+ 链接。
  • index.html 内嵌 JSON-LD:一个 WebApplication(功能、免费、仓库、数据集)和一个 FAQPage(4 条问答,含准确率与隐私)。
  • robots.txt 与 sitemap.xml。

切换页面语言时,<title>、meta description、og:*、twitter:* 会同步更新,分享预览不会出现「中文页面配英文描述」。

引用 / 致谢

  • 数据集:CatMeows — Schork & Petry 等,Zenodo 4008297(CC-BY-NC,仅供研究)。
  • 相关工作:Ntalampiras(猫叫情境识别)、Pandeya & Lee(猫叫分类)、Meow-Omni 1(多模态动物意图理解,2025)。

教它认识你家猫(个性化)

除了通用模型,页面支持纯本地个性化训练:三个情境各录 3 段自家猫叫声,浏览器内用 softmax 回归训练专属分类头(复用同一条 95 维特征管线),存 localStorage,零上传。训练后可一键在「通用 / 专属」模型间切换。

帮它听更多猫的叫声(数据贡献)

模型只学过 21 只猫(缅因猫、欧洲短毛猫)——对狸花猫、橘猫、布偶……没有验证过。这是目前对项目最有价值的贡献方式,比代码更缺:

  1. 页面「帮它听更多猫的叫声」一节:选情境 → 录 1–5 秒(可多段)→ 可填品种/年龄
  2. 点「打包下载」得到 zip:音频原件 + 标签 + manifest.json——本地生成,你不发出去就不存在上传
  3. 开一个 data contribution issue,把 zip 拖进评论区

贡献音频以 CC0-1.0 开放(含商业使用自由)。详见 CONTRIBUTING.md。

License

代码 MIT。数据集版权归 CatMeows 作者(CC-BY-NC)。社区贡献的音频为 CC0-1.0。

About

In-browser cat meow context classifier — record/upload a meow, local AI tells you if it is brushing / waiting for food / isolation. Zero upload, pure client-side.

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages