[AI] WhisperX:有限 GPU 下的繁中/英文轉錄實驗
在 GTX 1050 Ti 4GB 上最佳化 WhisperX:有限 GPU 下的繁中/英文轉錄實驗
這篇紀錄的是一次「小 GPU 榨汁」實驗:不是靠頂級顯卡硬輾,而是在 GTX 1050 Ti 4GB 上,透過模型選擇、參數調整、顯存釋放與 two-pass 說話人分類,把 WhisperX 調到實務可用。
- GTX 1050 Ti 不適合跑
float16,這次以int8為主。 - 品質優先設定:
large-v2、batch_size=3、beam_size=8、chunk_size=30。 - 英文 10 分鐘音檔約 113.5 秒完成,約 5.3 倍即時速度。
- GPU peak 約 3813 MB,約吃到 4GB 顯存的 93%。
- 說話人分類加入 two-pass,改善自動判斷人數錯誤的問題。
測試平台
| CPU | AMD Ryzen 5 5500X3D |
|---|---|
| GPU | NVIDIA GTX 1050 Ti 4GB |
| 系統 | Windows |
| 主要工具 | WhisperX、faster-whisper / CTranslate2、pyannote、OpenCC |
GTX 1050 Ti 只有 4GB 顯存,而且這張 Pascal 架構的卡在 CTranslate2 下不適合使用 float16。
所以這次優化方向不是硬上更大的模型或更高精度,而是找出在 int8 條件下,這張卡可以穩定承受的最佳參數。
品質優先的參數組合
目前品質優先的設定如下:
{
"model": "large-v2",
"compute_type": "int8",
"batch_size": 3,
"chunk_size": 30,
"beam_size": 8
}
| 項目 | 實驗結論 |
|---|---|
compute_type |
int8 最穩;float16 不適合這張 GPU。 |
batch_size |
3 是甜蜜點;4 顯存更滿但速度反而下降。 |
beam_size |
8 品質與速度較平衡;12 沒看到明顯品質提升。 |
| 模型 | 繁中測試下,large-v2 比 large-v3 / large-v3-turbo 更穩。 |
英文 10 分鐘音檔效能
| 音檔長度 | 約 600 秒 |
|---|---|
| 總處理時間 | 約 113.5 秒 |
| 即時倍率 | 約 5.3 倍 realtime |
| GPU peak | 約 3813 MB |
| 顯存使用 | 約 4GB 的 93% |
這個結果對 GTX 1050 Ti 來說已經相當接近極限。重點不是把參數全部拉滿,而是在不讓顯存壓力拖慢流程的情況下,盡量吃滿 GPU。
簡化後的轉錄流程
完整專案有 benchmark、報告、speaker rename、two-pass diarization 等功能。不過核心概念可以簡化成下面這樣。
1. 選擇 device 與 compute type
def choose_device():
import torch
return "cuda" if torch.cuda.is_available() else "cpu"
def choose_compute_type(device):
import ctranslate2
supported = ctranslate2.get_supported_compute_types(device)
for candidate in ["int8", "int8_float32", "float32"]:
if candidate in supported:
return candidate
return "default"
2. 載入 WhisperX model
import whisperx
device = choose_device()
compute_type = choose_compute_type(device)
model = whisperx.load_model(
"large-v2",
device=device,
compute_type=compute_type,
language="zh",
asr_options={
"beam_size": 8,
"best_of": 8,
"condition_on_previous_text": False,
},
vad_method="silero",
)
3. 轉錄音檔
audio = whisperx.load_audio("meeting.m4a")
result = model.transcribe(
audio,
batch_size=3,
chunk_size=30,
language="zh",
)
4. ASR 與 alignment 分開載入
這是 4GB GPU 上很重要的一點:ASR model 用完後要釋放,再載入 alignment model。小顯存不適合讓兩個模型同時待在 GPU 裡。
import gc
import torch
del model
gc.collect()
torch.cuda.empty_cache()
align_model, align_meta = whisperx.load_align_model(
language_code=result["language"],
device=device,
)
result = whisperx.align(
result["segments"],
align_model,
align_meta,
audio,
device=device,
)
del align_model
gc.collect()
torch.cuda.empty_cache()
繁體中文後處理
Whisper 輸出的中文有時候會混簡體、空白或標點格式,所以加入簡單的繁中後處理。
import re
from opencc import OpenCC
cc = OpenCC("s2twp")
def postprocess_zh(text):
text = cc.convert(text)
text = re.sub(r"\s*([,。!?;:、])\s*", r"\1", text)
text = re.sub(r"(?<=[\u3400-\u9fff])\s+(?=[\u3400-\u9fff])", "", text)
return text.strip()
例如:
這 是 測試 , OK !
可以整理成:
這是測試,OK!
不過繁中逐字稿不能只看字錯率。例如「徵才」被辨識成「身材」,從 CER 看只是兩個字錯,但語意上完全不一樣。 這種錯誤如果直接拿去做會議摘要或 Action Item,很容易出事。
說話人分類:自動判斷人數是最大風險
說話人分類使用 pyannote。實驗中發現最大問題不是分段,而是「自動判斷說話人數」。
| 條件 | 結果 |
|---|---|
指定 --num-speakers 2 |
DER 約 8.86%,turn accuracy 100%,confusion 0%。 |
| 自動判斷 speaker 數 | 原本 2 人被判成 3 人,DER 上升到約 42.41%,turn accuracy 下降到約 66.67%。 |
所以實務上如果知道人數,應該直接指定。但很多真實會議一開始並不知道有幾個人,所以我又做了 two-pass diarization。
Two-pass diarization:先找長語音聲紋,再回頭分類
想法很直覺:
- 先跑一次 pyannote。
- 找出比較長、比較乾淨的說話段。
- 從長段建立聲紋。
- 合併相似聲紋。
- 再回頭分類整段音訊。
- 低信心片段標成
UNKNOWN,不要硬分。
聲紋分群
def cosine(a, b):
return float(a @ b)
def group_voiceprints(seed_vectors, threshold=0.60):
groups = []
used = set()
for i, vector in enumerate(seed_vectors):
if i in used:
continue
group = [i]
used.add(i)
for j, other in enumerate(seed_vectors):
if j in used:
continue
if cosine(vector, other) >= threshold:
group.append(j)
used.add(j)
groups.append(group)
return groups
建立 speaker center
import numpy as np
def make_voice_centers(embeddings, seed_groups):
centers = []
for group in seed_groups:
center = np.mean([embeddings[i] for i in group], axis=0)
center = center / np.linalg.norm(center)
centers.append(center)
return centers
回頭分類所有語音段
def assign_speaker(vector, centers, threshold=0.45):
scores = [cosine(vector, center) for center in centers]
best = int(np.argmax(scores))
if scores[best] < threshold:
return "UNKNOWN", scores[best]
return f"VOICE_{best:02}", scores[best]
這個方法在繁中雙人測試上,把原本 auto 判成 3 人的結果修正成 2 人,最後 DER 約 8.86%,turn accuracy 100%。
英文 10 分鐘音檔上,two-pass 額外成本大約 3.3 秒。相對完整 ASR + alignment + diarization,大約只增加 2% 左右時間,這個交換很划算。
Speaker rename
自動產生的 speaker 名稱通常長這樣:
VOICE_00
VOICE_01
UNKNOWN
實際閱讀很不方便,所以加了 speaker rename。
def rename_speakers(rows, mapping):
for row in rows:
speaker = row.get("speaker")
if speaker in mapping:
row["original_speaker"] = speaker
row["speaker"] = mapping[speaker]
return rows
使用時可以這樣指定:
--speaker-rename "VOICE_00=主持人,VOICE_01=客戶"
輸出的逐字稿就會變成:
主持人: 目前整理到六月的進度……
客戶: 真的是電瓶問題……
Confidence report 與 review report
逐字稿最怕的是「看起來很完整,但其實有錯」。所以我額外輸出兩種報告:
- confidence report:機器可讀 JSON。
- review report:人工可讀 Markdown。
def segment_confidence(segment):
scores = [
word["score"]
for word in segment.get("words", [])
if "score" in word
]
if not scores:
return None
return sum(scores) / len(scores)
def low_confidence_segments(segments, threshold=0.45):
results = []
for index, segment in enumerate(segments, 1):
confidence = segment_confidence(segment)
if confidence is not None and confidence < threshold:
results.append({
"index": index,
"start": segment["start"],
"end": segment["end"],
"speaker": segment.get("speaker"),
"confidence": confidence,
"text": segment.get("text", ""),
})
return results
review report 會額外標記:
- 低 confidence 片段
UNKNOWNspeaker- 太短的 speaker turn
- 空白逐字稿
- 中英文語言混雜
- 重複字元異常
這些不一定代表錯,但很適合丟給人工或下一階段 ChatGPT API 檢查。
v1.0 整合指令
目前整理成 v1.0,一個指令可以跑完整品質流程:
.\whisperx-env\Scripts\python.exe .\whisperx_optimized.py .\input\meeting.m4a `
--profile quality `
--language zh `
--two-pass-diarize `
--speaker-rename "VOICE_00=主持人,VOICE_01=客戶" `
--confidence-report `
--review-report
會輸出:
- JSON 完整結果
- SRT 字幕
- TXT 逐字稿
- two-pass diarization 診斷
- confidence report
- review report
最後心得
在有限 GPU 上,流程設計比盲目換大模型更重要。
GTX 1050 Ti 4GB 當然不是什麼強卡,但透過幾個策略,還是可以做出實用的本機逐字稿流程:
- 使用
int8 - 避免
float16 - 控制 batch,不硬塞到爆
- ASR / alignment 分開載入並釋放顯存
- 繁中後處理
- 說話人分類不要完全相信 auto
- two-pass 聲紋回頭分類
- 低信心片段獨立列出
目前的成果是:
- 英文 10 分鐘音檔約 113 秒完成品質優先轉錄
- GPU 使用峰值約 3.8GB
- 約吃到 4GB 顯存的 93%
- 繁中雙人對話可做到可用逐字稿與說話人分類
- two-pass 方法能改善 speaker auto 判斷錯誤
下一步預計做 v1.1:
- ChatGPT API 自動校稿
- 會議摘要
- Action Item 擷取
不過我會保留一個原則:原始逐字稿、校稿版、摘要、Action Items 要分開保存。 AI 可以協助整理,但不應該覆蓋原始紀錄。
備註:這篇文章中的程式碼是為了說明流程而簡化,完整版本包含 benchmark、報告輸出、two-pass 診斷與 speaker rename 等細節。
留言
張貼留言