웨이크워드 / 키워드 감지
SpeechWakeWord 모듈은 온디바이스 키워드 스포터를 실행합니다: 문구 목록을 등록하고 오디오 청크를 밀어 넣으면 감지 결과를 받습니다. icefall의 스트리밍 Zipformer 트랜스듀서(3.49M 매개변수, Apache-2.0)에 기반하며, INT8 팔레트화로 CoreML에 컴파일되었습니다.
출고되는 체크포인트는 gigaspeech KWS 파인튜닝입니다. 영어 이외의 키워드는 별도의 icefall 파인튜닝과 재익스포트가 필요합니다.
아키텍처
| 단계 | 설명 |
|---|---|
| fbank | kaldi 호환(25 ms / 10 ms, Povey 윈도우, 80 mel bin, high_freq=-400, CMVN 없음) |
| 인코더 | 6단계 인과적 Zipformer2(128차원), 45개 mel 프레임 입력 → 8 프레임 출력(40 ms / 프레임) — 3.3 MB INT8 |
| 디코더 | 무상태 트랜스듀서, BPE-500 어휘, 컨텍스트 크기 2 — 525 KB FP16 |
| Joiner | 선형 + tanh 출력 투영 — 160 KB INT8 |
| 디코딩 | 사용자 키워드의 Aho-Corasick ContextGraph에 대한 수정 빔 서치(beam=4) |
디스크에서 컴파일된 크기: 총 ~4 MB(encoder.mlmodelc + decoder.mlmodelc + joiner.mlmodelc). 런타임 메모리: 인코더 캐시 포함 ~6 MB.
성능
| 지표 | 값 | 비고 |
|---|---|---|
| RTF (CPU + Neural Engine) | 0.04 | M 시리즈에서 실시간의 26배 |
| Recall (12개 키워드) | 88% | LibriSpeech test-clean, 158개 긍정 발화 |
| 거짓 양성 / 발화 | 0.27 | 60개 부정 발화 |
| CoreML INT8 vs PyTorch FP32 | 99% | 출력 일치도 |
튜닝된 기본값: acThreshold=0.15, contextScore=0.5, numTrailingBlanks=1. 키워드별 오버라이드를 지원합니다.
CLI 사용법
일반 문구 형식(탐욕적 BPE — 일반적인 단어에 잘 동작):
audio wake recording.wav --keywords "hey soniqo"
audio wake recording.wav --keywords "hey soniqo:0.15:0.5" "cancel"
사전 토큰화 형식(sherpa-onnx 스타일 — 모델이 학습된 정확한 분해를 알고 있을 때 권장):
# Format: "phrase|piece1 piece2 ...:threshold:boost"
audio wake recording.wav \
--keywords "LIGHT UP|▁ L IGHT ▁UP:0.25:2.0"
# Multiple keywords + JSON output
audio wake recording.wav \
--keywords "LIGHT UP|▁ L IGHT ▁UP:0.25:2.0" \
"LOVELY CHILD|▁LOVE LY ▁CHI L D:0.25:2.0" \
--json
또는 키워드 파일, 한 줄에 한 항목 (#으로 주석):
audio wake recording.wav --keywords-file keywords.txt
Swift API
import SpeechWakeWord
// Load the model with your keyword list.
let detector = try await WakeWordDetector.fromPretrained(
keywords: [
KeywordSpec(phrase: "hey soniqo", acThreshold: 0.15, boost: 0.5),
KeywordSpec(phrase: "cancel")
]
)
// Streaming: push chunks, consume detections as they fire.
let session = try detector.createSession()
for chunk in micAudioChunks { // Float32 @ 16 kHz
for detection in try session.pushAudio(chunk) {
print("[\(detection.time(frameShiftSeconds: 0.04))s] \(detection.phrase)")
}
}
// Batch: single shot over a full buffer.
let detections = try detector.detect(audio: samples, sampleRate: 16000)
KeywordSpec
| 필드 | 의미 |
|---|---|
phrase | 표시 문구, 예: "hey soniqo". tokens가 nil일 때 탐욕적 BPE 인코딩의 소스로도 사용됩니다. |
acThreshold | 매칭된 구간에 대해 요구되는 평균 음향 확률. 0 → 튜닝된 기본값(0.15) 사용. |
boost | 토큰별 컨텍스트 부스트. 양수 값은 문구 트리거를 쉽게 만듭니다. 0 → 튜닝된 기본값(0.5). |
tokens | 선택적 명시적 BPE 조각 리스트. nil이 아니면 감지기가 모델의 tokens.txt에서 각 조각을 조회하고 탐욕적 BPE 인코더를 우회합니다. |
tokens를 사용할 때icefall KWS 어휘는 대문자 BPE입니다. 문구의 탐욕적 토큰화는 모델이 출력하도록 학습된 BPE 분해와 다른 분해를 선택할 수 있습니다 — "LIGHT UP"은 탐욕적으로 ▁LI GHT ▁UP로 인코딩되지만, 학습 시 분해는 ▁ L IGHT ▁UP입니다. TTS 합성이나 깨끗한 낭독 음성에서 감지가 명백한 매치를 놓친다면, sherpa-onnx 스타일의 사전 토큰화 형식을 시도해 보세요.
모델 다운로드
| 모델 | 매개변수 | 크기 | HuggingFace |
|---|---|---|---|
| KWS-Zipformer-3M | 3.49M | ~4 MB | aufklarer/KWS-Zipformer-3M-CoreML-INT8 |
파이프라인 통합
모듈은 StreamingVADProvider를 반영하는 WakeWordProvider 프로토콜을 제공하므로, 음성 파이프라인이 VAD, 웨이크워드 또는 양쪽에 의한 활성화를 게이트할 수 있습니다. WakeWordStreamingAdapter는 로드된 감지기 + 단일 세션을 재사용 가능한 provider 객체로 감쌉니다.
let adapter = try WakeWordStreamingAdapter(detector: detector)
// pipeline.configure(wakeWord: adapter)
소스
- Sources/SpeechWakeWord — Swift 모듈
- docs/models/kws-zipformer.md — 아키텍처 메모
- docs/inference/wake-word.md — 추론 파이프라인
- 업스트림: k2-fsa/icefall KWS 레시피 / pkufool/keyword-spotting-models