@@ -12,30 +12,155 @@ last_update:
1212
1313---
1414
15-
16- 음성인식 최적화 경험은 해당 PoC 참고하여 재기재
15+ faster-whisper-large-v3 로 음성 인식 작업을 진행하였다.
1716
1817<!-- truncate-->
1918
20- [ https://doc.scenemaker.solbox.com/docs/poc/audio-bench/1 ] ( https://doc.scenemaker.solbox.com/docs/poc/audio-bench/1 )
19+ 최적화를 진행하였으며, 구체적 과정은
20+
21+ 1 . transcribe 실행. segment 결과 추출
22+ 2 . for 루프 검사, 결과물 필터하여 모델 최적화
23+
24+ 라고 할 수 있다.
25+
26+
27+ ---
28+
29+ ### 1. transcribe
30+
31+ ``` python
32+ # poc-stt-bench/ib/audio/whisper/whisper_stt.py
33+ # Line 97
34+
35+ def _do_transcribe (audio_np : np.ndarray, language : str ) -> tuple[list , float ]:
36+ """ Call faster-whisper transcribe. Returns segments (list) + mean avg_logprob.
37+
38+ When segments are empty, logprob = -inf (automatic loss in dual comparison).
39+ """
40+ segments_gen, _info = _model.transcribe(
41+ audio_np,
42+ language = language,
43+ beam_size = 5 ,
44+ no_speech_threshold = 0.6 ,
45+ log_prob_threshold = - 1.0 ,
46+ compression_ratio_threshold = 2.4 ,
47+ condition_on_previous_text = False ,
48+ repetition_penalty = 1.2 ,
49+ no_repeat_ngram_size = 3 ,
50+ )
51+ segs = list (segments_gen)
52+ if not segs:
53+ return segs, float (" -inf" )
54+ return segs, sum (s.avg_logprob for s in segs) / len (segs)
55+ ```
56+
57+ - 해당 파라미터 값 (beam_size, log_prob_threshold 등) 은, 패키지 함수 시그니처에 있는 것과 동일함을 확인
58+
59+
60+ ### 2. 결과물 필터
61+
62+ 2-1)
63+
64+ 2-2)
65+
66+ 2-3)
67+
68+ 2-4)
69+
70+ 2-5)
71+
72+ ``` python
73+ MIN_LOGPROB = - 1.0 # drop if avg_logprob < this. catch-all for hallucinations
74+ MIN_SEG_S = 0.2 # drop segments shorter than this. preserves short interjections like "네"/"그렇죠"
75+
76+ # When a short speech is detected as a non-main language, run both ko and lid and compare logprob.
77+ # Avoids 1-2s segments in Korean content being misclassified as ja/zh.
78+ MAIN_LANG = " ko"
79+ SHORT_SEG_S = 3.0 # below this duration and LID != MAIN_LANG -> dual transcribe
80+ MIN_DUAL_LOGPROB = - 0.6 # if both dual sides fall below this -> drop (hallucination/noise)
81+ LID_TRUST_PROB = 0.5 # LID prob below this + non-main lang -> force MAIN_LANG (LID itself untrustworthy)
82+ KO_MIN_HANGUL_RATIO = 0.3 # if Hangul ratio of ko transcription is below this -> drop. Cuts Whisper outputting kana/hanja hallucinations in ko mode
83+
84+ # poc-stt-bench/ib/audio/whisper/whisper_stt.py
85+
86+
87+ # 게이트1
88+ # Line 148
89+ # ── 1) VAD (raw)
90+ speech_ranges = vad.detect(raw_np, sr = TARGET_SR )
91+ log.info(f " audio { total_sec:.1f } s → VAD { len (speech_ranges)} speech segments " )
92+
93+ # ── 2) Per-speech LID(raw) + ASR(denoised)
94+ all_segments: list[dict ] = []
95+ for start_s, end_s in speech_ranges:
96+ s_idx = int (start_s * TARGET_SR )
97+ e_idx = int (end_s * TARGET_SR )
2198
22- ** 4. 시스템 설계 + 환각 처리 > 4.1 Whisper 측**
99+ # Line 170
100+ # A low prob like 0.23 means ko/de/ja/zh are all comparable = the model doesn't know -> ko is the natural assumption
23101
24- 내용을 기재하면 됨.
102+ # 게이트 3- LID_TRUST_PROB
103+ if lang_code != MAIN_LANG and prob < LID_TRUST_PROB :
104+ log.info(f " LID { lang_code} = { prob:.2f } < { LID_TRUST_PROB } → { MAIN_LANG } 강제 " )
105+ lang_code = MAIN_LANG
106+
107+ # 게이트 4 — dual transcribe + MIN_DUAL_LOGPROB (-0.6)
25108
26- - poc/poc-stt-bench 내부 코드 확인
27- - 레퍼런스 글 확인 및 간단 정리
109+ # 2c) transcribe — dual compare (ko vs lid) when short speech + non-main lang
110+ chunk_dur = end_s - start_s
111+ if chunk_dur < SHORT_SEG_S and lang_code != MAIN_LANG :
112+ segs_main, lp_main = _do_transcribe(chunk_den, MAIN_LANG )
113+ segs_lid, lp_lid = _do_transcribe(chunk_den, lang_code)
114+ if max (lp_main, lp_lid) < MIN_DUAL_LOGPROB :
115+ log.info(f " dual [ { _hms(start_s)} ~ { _hms(end_s)} | { chunk_dur:.1f } s] "
116+ f " lp( { MAIN_LANG } )= { lp_main:.2f } , lp( { lang_code} )= { lp_lid:.2f } → 양쪽 약함, drop " )
117+ continue
118+ if lp_main >= lp_lid:
119+ segments_out, chosen_lang = segs_main, MAIN_LANG
120+ log.info(f " dual [ { _hms(start_s)} ~ { _hms(end_s)} | { chunk_dur:.1f } s] "
121+ f " lp( { MAIN_LANG } )= { lp_main:.2f } ≥ lp( { lang_code} )= { lp_lid:.2f } → { MAIN_LANG } " )
122+ else :
123+ segments_out, chosen_lang = segs_lid, lang_code
124+ log.info(f " dual [ { _hms(start_s)} ~ { _hms(end_s)} | { chunk_dur:.1f } s] "
125+ f " lp( { lang_code} )= { lp_lid:.2f } > lp( { MAIN_LANG } )= { lp_main:.2f } → { lang_code} " )
126+ else :
127+ segments_out, _ = _do_transcribe(chunk_den, lang_code)
128+ chosen_lang = lang_code
129+
130+ # Line 197
131+ for seg in segments_out:
132+ text = (seg.text or " " ).strip()
133+ if not text:
134+ continue
135+ abs_start = float (seg.start) + start_s
136+ abs_end = float (seg.end) + start_s
137+ dur = abs_end - abs_start
138+ # 게이트 2 — MIN_LOGPROB (-1.0)
139+ if seg.avg_logprob < MIN_LOGPROB :
140+ log.info(f " drop [ { _hms(abs_start)} ~ { _hms(abs_end)} | { chosen_lang} ] lp= { seg.avg_logprob:.2f } | { text[:30 ]} " )
141+ continue
142+ if dur < MIN_SEG_S :
143+ log.info(f " drop [ { _hms(abs_start)} ~ { _hms(abs_end)} | { chosen_lang} ] dur= { dur:.2f } s | { text[:30 ]} " )
144+ continue
145+ # 게이트 5 — 한글 char 비율 게이트 (30%)
146+ if chosen_lang == MAIN_LANG :
147+ ratio = _hangul_ratio(text)
148+ if ratio < KO_MIN_HANGUL_RATIO :
149+ log.info(f " drop [ { _hms(abs_start)} ~ { _hms(abs_end)} | { chosen_lang} ] hangul= { ratio:.0% } | { text[:30 ]} " )
150+ continue
151+ # 통과한 것만 채택
152+ all_segments.append({
153+ " start" : abs_start,
154+ " end" : abs_end,
155+ " text" : text,
156+ " speaker" : None ,
157+ " language" : chosen_lang,
158+ })
159+
160+
161+ ```
28162
29- | 최적화 | 무엇 | 효과 |
30- | --- | --- | --- |
31- | ** Strategy 2** | VAD/LID=raw, STT=denoise | 감지 정확도 + 전사 품질 둘 다 |
32- | ** 언어 티어링** | Whisper 못하는 언어 버림 (nl/zh/vi 오판 차단) | 환각 제거 |
33- | ** LID 신뢰도 게이트** | 저신뢰 비-한국어 → 한국어 강제 | 한국어 특화 |
34- | ** logprob 필터** | 확신 낮은 세그먼트 drop | 환각 제거 |
35- | ** VAD 튜닝** | 단음절 버림, 발화 단위 분할 | 경계 오류 방지 |
36- | ** LLM 후처리 교정** | Qwen 1차 교정 (물고지→물고기) | 오탈자·동음이의 |
37163
38- ### 마무리
39164
40165---
41166
0 commit comments