25acf9db6e
横向对比云端 gummy 与本地开源模型(faster-whisper/SenseVoice/Paraformer), 重点覆盖中英混说,产出准确率(CER/WER/MER)/速度(延迟/RTF)/资源(cpu/mem/模型大小) 对比报告。公共集(ASCEND/AISHELL/LibriSpeech)统一走 HF 适配器 + 自定义 JSONL manifest。 gummy 引擎对照 server/internal/asr/gummy.go 协议移植。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
90 lines
3.0 KiB
Python
90 lines
3.0 KiB
Python
"""通用 HuggingFace 数据集适配器(流式)。
|
|
|
|
config 条目示例:
|
|
- name: ascend
|
|
type: hf
|
|
hf_id: CAiRE/ASCEND
|
|
split: test
|
|
text_field: transcription
|
|
audio_field: audio # 默认 audio
|
|
lang: zh-en
|
|
domain: general
|
|
trust_remote_code: false
|
|
|
|
流式(streaming=True)避免为跑小样而下载整个数据集;每条把音频物料化为 16k wav 缓存。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import io
|
|
import os
|
|
from collections.abc import Iterator
|
|
|
|
import soundfile as sf
|
|
|
|
from ..manifest import Sample
|
|
from .base import Dataset, write_wav_16k
|
|
|
|
|
|
class HFDataset(Dataset):
|
|
def __init__(self, cfg: dict, audio_cache: str):
|
|
self.name = cfg["name"]
|
|
self.hf_id = cfg["hf_id"]
|
|
self.hf_config = cfg.get("hf_config")
|
|
self.split = cfg.get("split", "test")
|
|
self.text_field = cfg["text_field"]
|
|
self.audio_field = cfg.get("audio_field", "audio")
|
|
self.lang = cfg.get("lang", "zh")
|
|
self.domain = cfg.get("domain", "general")
|
|
self.trust_remote_code = cfg.get("trust_remote_code", False)
|
|
self.cache_dir = os.path.join(audio_cache, self.name)
|
|
|
|
def samples(self, limit: int | None = None) -> Iterator[Sample]:
|
|
from datasets import Audio, load_dataset
|
|
|
|
ds = load_dataset(
|
|
self.hf_id,
|
|
self.hf_config,
|
|
split=self.split,
|
|
streaming=True,
|
|
trust_remote_code=self.trust_remote_code,
|
|
)
|
|
# 关闭自动解码(新版 datasets 需 torchcodec):拿原始 bytes/path 自己用 soundfile 读,避开重依赖。
|
|
ds = ds.cast_column(self.audio_field, Audio(decode=False))
|
|
n = 0
|
|
for i, row in enumerate(ds):
|
|
if limit is not None and n >= limit:
|
|
break
|
|
text = (row.get(self.text_field) or "").strip()
|
|
if not text:
|
|
continue
|
|
audio = row.get(self.audio_field)
|
|
if not audio:
|
|
continue
|
|
wav_path = os.path.join(self.cache_dir, f"{i:06d}.wav")
|
|
if not os.path.exists(wav_path):
|
|
array, sr = self._decode(audio)
|
|
if array is None:
|
|
continue
|
|
write_wav_16k(array, sr, wav_path)
|
|
n += 1
|
|
yield Sample(
|
|
id=f"{self.name}-{i:06d}",
|
|
audio_path=wav_path,
|
|
ref_text=text,
|
|
lang=self.lang,
|
|
domain=self.domain,
|
|
dataset=self.name,
|
|
)
|
|
|
|
@staticmethod
|
|
def _decode(audio: dict):
|
|
"""从未解码的 HF 音频列读出 (array, sr):优先 bytes,其次本地 path。"""
|
|
if audio.get("bytes"):
|
|
data, sr = sf.read(io.BytesIO(audio["bytes"]), dtype="float32", always_2d=False)
|
|
return data, sr
|
|
if audio.get("path") and os.path.exists(audio["path"]):
|
|
data, sr = sf.read(audio["path"], dtype="float32", always_2d=False)
|
|
return data, sr
|
|
return None, None
|