웨이크워드 / 키워드 감지

SpeechWakeWord 모듈은 온디바이스 키워드 스포터를 실행합니다: 문구 목록을 등록하고 오디오 청크를 밀어 넣으면 감지 결과를 받습니다. icefall의 스트리밍 Zipformer 트랜스듀서(3.49M 매개변수, Apache-2.0)에 기반하며, INT8 팔레트화로 CoreML에 컴파일되었습니다.

영어만 지원

출고되는 체크포인트는 gigaspeech KWS 파인튜닝입니다. 영어 이외의 키워드는 별도의 icefall 파인튜닝과 재익스포트가 필요합니다.

아키텍처

단계설명
fbankkaldi 호환(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.04M 시리즈에서 실시간의 26배
Recall (12개 키워드)88%LibriSpeech test-clean, 158개 긍정 발화
거짓 양성 / 발화0.2760개 부정 발화
CoreML INT8 vs PyTorch FP3299%출력 일치도

튜닝된 기본값: 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-3M3.49M~4 MBaufklarer/KWS-Zipformer-3M-CoreML-INT8

파이프라인 통합

모듈은 StreamingVADProvider를 반영하는 WakeWordProvider 프로토콜을 제공하므로, 음성 파이프라인이 VAD, 웨이크워드 또는 양쪽에 의한 활성화를 게이트할 수 있습니다. WakeWordStreamingAdapter는 로드된 감지기 + 단일 세션을 재사용 가능한 provider 객체로 감쌉니다.

let adapter = try WakeWordStreamingAdapter(detector: detector)
// pipeline.configure(wakeWord: adapter)

소스