本來 Chatbot 寫得很開心
有一天主管突然跑來跟我說
「欸欸,你要不要接個 ASR 上去」
於是再度開啟我的語音處理之路
聲音是一種縱波
一般而言麥克風上有振膜
用來紀錄聲波的振幅
連續的振幅可以形成一個聲音
採樣率 Sample Rate
是在將聲音
從類比訊號轉為數位訊號的過程中
出現的概念
用來表示聲音訊號的取樣頻率
例如 48K 代表一秒取樣 48,000 次
現在的麥克風多能錄製到 40K 以上
一般電話多為 8K 左右
但是語音辨識通常 8K 或 16K 就夠了
因此需要做 Downsampling
語音處理通常使用 Frame 作為單位
常見的單位是 10 ms 作為一個 Frame
也有 20 ms 或 30 ms 的
因此對 16K 的聲音訊號而言
一個 10 ms 的 Frame 會有 160 個樣本點
通常使用 Bit Depth 來描述樣本點的資料型態
通常為 16 Bit 也就是 2 Bytes 整數
把樣本直接存起來的檔案也可稱為
Pulse-Code Modulation, PCM
常見的 Wave 檔 (.wav)
就是加了 44 Byte Header 的 PCM
語音活性檢測 Voice Activity Detection, VAD
用來檢查現在有沒有人在講話
一般而言 VAD 的運算量比較小,而 ASR 運算量比較大
所以通常在 VAD 被觸發一段時間後才開始做 ASR
這樣可以節省計算資源
端點偵測 End-Point Detection, EPD
與 VAD 不同的地方在於
VAD 只針對某個 Frame 是否為人聲
而 EPD 通常觀察一段連續的 VAD 狀態
用來決定是否啟動語音辨識
Automatic Speech Recognition
也稱為 Speech-To-Text, STT
傳統 ASR 通常由
聲學模型 Acoustic Model, AM
語言模型 Language Model, LM
所組成
AM 主要的目的是辨識聲音訊號的音節
就好比在辨識我們講話的注音
其結果可能像是「ㄋㄧˊ ㄏㄠˇ ㄚ」
LM 則用來看這些音節的順序
可能構成哪些合理的句子
以「ㄋㄧˊ ㄏㄠˇ ㄚ」為例
LM 就會算出「你好啊」的機率比較高
而不是「泥豪阿」
關鍵詞偵測 Keyword Spotting, KWS
是一種形式比較單純的語音辨識
只辨識特定的關鍵字,來避免語音誤動
像是 Hey Siri 與 OK Google 就是 KWS
講了這麼多
其實 Whisper 也不是這種傳統做法
而是直接用 Transformers 訓練一個
Encoder-Decoder 模型
透過 PyAudio 套件錄音
Linux 上需要安裝以下套件
sudo apt install portaudio19-dev python3-pyaudio
import pyaudio recorder = pyaudio.PyAudio() stream = recorder.open( format=pyaudio.paInt16, channels=1, rate=16000, input=True, frames_per_buffer=160, ) # 10ms = 1 Frame, 5s = 5000ms = 500 Frames frames = [stream.read(160) for _ in range(500)]
透過 Python 內建的 wave
套件存成音檔
import wave with wave.open("output.wav", "wb") as wf: wf.setnchannels(1) wf.setsampwidth(2) # 16 Bits = 2 Bytes wf.setframerate(16000) wf.writeframes(b"".join(frames))
透過 webrtcvad 套件可以輕鬆達成
每次輸入必須是一個完整的 Frame
import webrtcvad vad = webrtcvad.Vad() vad.set_mode(1) sample_rate = 16000 frame = b"\x00\x00" * 160 # 2 Bit x 1 Frame print(vad.is_speech(frame, sample_rate))
透過 Faster Whisper 輕鬆調用 ASR 模型
from faster_whisper import WhisperModel model = WhisperModel( "large-v2", device="cuda", compute_type="int8_float16", # 8-Bit Quantization ) segments, info = model.transcribe("audio.mp3") print(f"Language: {info.language}") print(f"Lang Prob: {info.language_probability}") for seg in segments: print(f"[{seg.start:.2f}s -> {seg.end}s] {seg.text}")
一般而言會需要一個 VAD Buffer
長度大概 80 到 120 個 Frames
當這個 Buffer 假設有 80% 都是觸發狀態
代表發現聲音的開始端點
在進入 ASR 之前
通常會有一個 5 秒的 Buffer 用來存聲音訊號
在 EPD 觸發之後,把這個 Buffer 送入 ASR
避免在 EPD 之前有漏字的狀況
進入 ASR 階段後持續計算 VAD Buffer
如果 Buffer 只剩 20% 是觸發狀態就結束 ASR
所以會有三個狀態:未觸發 > 觸發 > 完成
完整程式碼放在 GitHub 上
使用 Markdown+Math 插件
可以在 VSCode 裡面渲染 Mathjax 語法
目的是將數學公式放入 Slides 所以需要設定小括號
{ "mdmath.delimiters": "brackets" }