Skip to content

Speech and PA7/Git Lab:聲音怎麼變成 Whisper 讀得懂的輸入|Stanford CS124 第 8 週

2026年8月22日1 分鐘
TL;DR第 8 週接著講語音:聲音怎麼產生、怎麼從波形變成模型吃的 log mel 頻譜、Whisper 怎麼用 encoder-decoder 和 cross-attention 把聲音轉成文字。PA6b 把文字轉成語音再轉回來,分析哪些資訊不見了。實作課四介紹 PA7 的 agent 流程並教小組用 GitHub 協作。

🌏 English version

這是 Stanford CS124 導讀的第 8 週。上一週講了語音辨識的任務和詞錯誤率,這一週講它怎麼做到:聲音是一串連續的氣壓變化,要經過哪些步驟才能交給 Transformer。

這一週有三塊:

  • 語音的技術內容:上週語音現場課投影片的後半,加上這週的指定閱讀,教科書 2025 年 8 月版的第 14 章(語音學)與第 15 章(語音辨識)。
  • PA6b:文字轉語音、語音轉文字,然後分析錯誤。
  • 實作課四:PA7 概覽與 GitHub。這是必須到場的實作課,沒有錄影,題目公開。

聲音是怎麼來的

投影片說明為什麼做語音辨識要懂一點語音學:模型出錯的時候,你需要一套語言來描述它錯在哪裡。描述不出來的錯誤就量不了,也修不了。

把聲音寫下來

英文的拼字和發音對不上,所以需要另一套符號,一個符號對一個音。投影片用的是 ARPAbet,早期為了英文語音辨識研究設計的,只用一般的英文字母:

詞ARPAbet
beadb iy d
clackk l ae k
clockk l aa k
supers uw p axr
suppers ah p axr

super 和 supper 拼字只差一個字母,母音完全不同。ARPAbet 只涵蓋美式英語;跨語言的分析要用國際音標(IPA)。

發聲的過程

  1. 空氣從肺部往上送。
  2. 通過喉部的聲帶,聲帶被氣流推開又彈回來,快速振動,一個週期大約百分之一秒。
  3. 聲波經過口腔,被舌頭、嘴巴、嘴唇的形狀塑造成不同的音。

聲帶不是每個音都振動。所有母音和 d、b、g、z、m、n 這些有聲子音會振動;t、p、k、s、f 這些無聲子音不會。投影片的小實驗:把手放在喉嚨上,先發 sssss,再發 zzzzz,後者摸得到振動。

子音是氣流在某處受阻產生的聲音。例如 p:先把嘴唇閉上,氣壓在後面累積,放開時爆出來。母音則是氣流不受阻,靠舌頭的位置改變口腔的形狀來區分。

從波形到模型的輸入

波形與頻譜

麥克風錄到的是氣壓隨時間的變化,畫出來就是波形:橫軸是時間,縱軸是氣壓。母音的波形有規律的重複(聲帶的振動),b 這類子音看得到一段安靜(閉合)接著一個爆點(釋放)。

波形本身不好用。關鍵的觀念是:任何複雜的波,都可以拆成很多個不同頻率的正弦波相加。 把一小段聲音拆開,看每個頻率各有多少能量,叫頻譜。口腔的形狀會增強某些頻率、減弱另一些,不同的母音在頻譜上有不同的高峰。

頻譜只描述一個瞬間。把一小段一小段的頻譜沿著時間排起來,就是 spectrogram:橫軸時間,縱軸頻率,顏色深淺是能量。

切 frame

「一小段」要多長?做法是用一個視窗在波形上滑動:

  • 視窗寬 25 毫秒。
  • 每次往右移 10 毫秒。

每個視窗裡的訊號叫一個 frame。一秒鐘的聲音有 100 個 frame,相鄰的 frame 有重疊。

Mel 刻度

人耳對頻率的敏感度不是均勻的:低頻分得很細,高頻就粗了。同樣差 100 Hz,100 Hz 和 200 Hz 聽起來差很多,7,000 Hz 和 7,100 Hz 聽起來的差距就小得多。Mel 刻度是模仿這個特性的頻率刻度,換算公式是:

mel(f) = 1127 × ln(1 + f ÷ 700)

代幾個數字進去(本文自己算的):

頻率mel 值
1,000 Hz1,000
4,000 Hz2,146
8,000 Hz2,840

從 0 到 1,000 Hz 佔了 1,000 個 mel;從 4,000 到 8,000 Hz 寬了四倍,卻只佔 694 個 mel。

實際的做法是準備一組濾波器(Whisper 用 128 個),在 mel 刻度上等距排列,所以低頻的濾波器又密又窄,高頻的又疏又寬。每個濾波器收集它那個頻段的能量,最後取對數,因為人對音量的感受也是對數的。

結果是:每個 frame 變成一個 128 維的向量,叫 log mel 頻譜。聲音到這裡變成了一串向量,和文字變成一串 token 向量是同樣的格式。

卷積層

進 Transformer 之前還要過一道卷積層。它的運作方式是:拿一個很短的權重向量(kernel),沿著時間一格一格滑過輸入,每到一個位置就和那裡的幾個值算內積。內積高,表示訊號在那個位置長得像這個 kernel。所以卷積是在訊號裡找特定的局部樣式。

本文自擬一個例子。輸入是 [0, 0, 5, 5, 0],kernel 是 [−1, 0, 1],寬度 3,輸入兩端各補一個 0:

位置視窗裡的三個值內積
10, 0, 00
20, 0, 55
30, 5, 55
45, 5, 0−5
55, 0, 0−5

這個 kernel 在訊號上升的地方輸出正值,下降的地方輸出負值,平的地方輸出 0。它是一個「偵測變化」的 kernel。實際的 kernel 不是人設計的,是訓練學出來的。

Whisper 的卷積層 kernel 寬度是 3,也就是一次看 3 個 frame。它同時把序列長度減半,變成每 20 毫秒一個向量。

Whisper:encoder-decoder

為什麼用 encoder-decoder

第 6 週介紹過三種架構。語音辨識用的是第三種,encoder-decoder。投影片說明這種架構適合輸入和輸出差很多的情況:

  • 一個英文詞寫成文字,平均是 1.3 個 BPE token。
  • 同一個詞說出來大約 250 毫秒,是 25 個 frame。

聲音那一端的序列,長度是文字的 19 倍左右,而且兩邊的「詞彙」完全不同:一邊是聲學向量,一邊是文字 token。

兩個部分

Encoder 處理聲音:

  1. 每 10 毫秒算一個 128 維的 log mel 頻譜。
  2. 通過卷積層抽取特徵,降到每 20 毫秒一個向量。
  3. 加上位置向量。
  4. 通過很多層 Transformer block。

輸出是一串向量,每個代表一小段聲音。

Decoder 產生文字,和第 7 週的語言模型幾乎一樣:由左到右,一次產生一個 BPE token,最後一樣是 unembedding 加 softmax。

Cross-attention

差別在於 decoder 的每個 block 多了一個零件:cross-attention。第 7 週的 attention,query、key、value 都來自同一個序列。Cross-attention 把來源拆開:

  • Query 來自 decoder,也就是目前已經產生的文字。
  • Key 和 value 來自 encoder 的輸出,也就是聲音。

投影片的說法是:根據我目前為止的文字表示,去聲音輸入裡找相關的部分。Decoder 要產生下一個詞的時候,用已經寫出的字當線索,去聲音裡找「接下來那一段」。

訓練

Loss 還是第 6 週的那一個:正確的下一個 token 的機率取負對數。唯一的差別是條件多了一項,除了前面的文字,還有整段聲音。

訓練資料是 68 萬小時的語音配上文字稿,從網路上抓來並經過大量過濾,其中 11.8 萬小時是英文以外的 96 種語言。資料切成 30 秒一段。訓練是多工的:同一個模型同時學辨識、翻譯等任務,用特殊的標記告訴它這一筆要做哪一件。

還沒解決的部分

投影片的結論是:語音辨識在很多實際情況下已經夠好用,十年來進步很大,但還沒有解決。

  • 雜訊。 Whisper 在稍微吵一點的語音上比人差很多,很吵的更差。投影片舉了一段足球轉播的例子:播報員激動地喊,Whisper 的輸出變成同一句話不斷重複。
  • 資料少的語言。 在 FLEURS 資料集上,有 1,000 小時以上訓練資料的語言(英文、西班牙文、法文、德文等)WER 在 10% 以下;資料少的語言(緬甸文、寮文等)WER 超過 100%。

PA6b

PA6b 用現成的語音服務跑一條「文字 → 語音 → 文字」的流程,然後分析結果。它需要一個 Cartesia 的帳號和 API key。README 說免費帳號有用量限制,但足夠完成作業。key 用環境變數提供,不寫進程式裡。

第一部分:文字轉語音。 repo 附了一份 sampled_sentences.json,也可以換成自己在 PA6a 取樣出來的句子。python tts.py 把它們轉成 sampled_sentences_speech.wav。

第二部分:語音轉文字。 python speech_to_text.py 把音檔轉回 sampled_sentences_speech.txt。README 另外建議自己在吵雜的環境裡把那些句子唸一次、錄下來、轉成文字,比較品質。

第三部分:錯誤分析。 比較原始文字和最後轉回來的文字,回答三題:

  1. 找出三種不同的錯誤或資訊遺失,各舉一個實例,說明比較可能是合成那一段還是辨識那一段造成的。
  2. 從這些錯誤推測辨識模型的訓練資料和設計取捨:它擅長什麼內容,不擅長什麼。
  3. 原文的格式(大小寫、換行、標點)在轉回來之後不見了。選一種,說明為什麼現在的系統保留不了它,以及要怎樣才保留得了。

第三題點出一件容易忽略的事:有些資訊本來就不在聲音裡。換行沒有聲音,大小寫沒有聲音。標點也只剩停頓和語調這種間接的線索。這些不是辨識「錯」了,是那一段流程本來就不太攜帶它們。

第四部分:自動字幕與無障礙。 自動字幕讓內容立刻有字幕可看,但美國的聾人協會曾經成功控告大型機構,理由是自動字幕達不到法律要求的平等使用標準。作業問兩題:把「溝通」這麼多面向的東西壓成詞錯誤率一個數字,代表什麼?符合法規和真正提供平等的使用機會,差別在哪裡?另外,高品質的字幕是為身心障礙者設計、結果很多人受益的例子,還有哪些人在用字幕?

上一週說過 WER 不看語意。這一題把同一個限制放進真實的後果裡:WER 也不看字幕出現的時間對不對、有沒有標出誰在說話、語氣如何。

第五部分:壓力測試。 找或錄一段 10 到 15 秒的聲音,內容是明顯的地方口音、非母語口音,或有自然的不流暢(口吃、長停頓、um 和 uh),丟給同一個辨識系統,看它在哪裡出錯。

實作課四:PA7 與 GitHub

這堂實作課必須到場,議程有三項。

PA7 概覽。 PA7 要做一個電影推薦 agent,用 DSPy 這個函式庫。實作課的講義說明 agent 的運作方式:對使用者的每個要求,agent 先想這個任務需要哪個工具,決定要傳什麼參數,拿到工具的結果後再想下一步。這個迴圈在輸出裡看得到:

thought_i     「使用者要推薦 → 呼叫 recommend_movies」
    ↓
tool_name_i   recommend_movies
tool_args_i   { user_name: "Peter", k: 3 }
    ↓
observation_i ["Back to the Future (1985)", ...]
    ↓
thought_(i+1)

工具是你自己實作的 Python 函式。作業分兩部分:第一部分(68 分)做基本的 agent,用協同過濾推薦電影並協助訂票;第二部分(32 分)加上網頁搜尋與記憶。協同過濾是下一週的內容。

講義特別提醒:每跑一次互動介面都會消耗 API 額度,所以要先把自己寫的函式單獨測好,再跑整個 agent。選課的學生每組會拿到一筆 Together AI 的額度;自學者要自己處理。

GitHub。 PA7 規定三到四人一組,所以需要版本控制。講義帶著做一次完整的設定:一個人建立私人的 repository、邀請組員、每個人裝 GitHub Desktop 並把 repository 複製到自己的電腦,接著練習改檔、同步,以及兩個人改到同一處時怎麼解衝突。

小組約定。 最後十分鐘讓各組談好怎麼分工、怎麼溝通。

自我檢查

  1. 3 秒鐘的聲音,用 25 毫秒的視窗、10 毫秒的間隔,大約有幾個 frame?經過 Whisper 的卷積層之後剩幾個向量?答案:每秒 100 個 frame,3 秒約 300 個;降到每 20 毫秒一個之後約 150 個。
  2. 用 mel 公式算 700 Hz 的 mel 值。答案:1127 × ln(1 + 1) = 1127 × 0.693 = 781。
  3. 輸入 [0, 0, 5, 5, 0] 換成 kernel [1, 1, 1](兩端補 0),輸出是什麼?這個 kernel 在做什麼?答案:[0, 5, 10, 10, 5]。它把相鄰三個值加起來,是平滑(模糊)的效果。
  4. Cross-attention 的 query、key、value 各來自哪裡?答案:query 來自 decoder(已經產生的文字),key 和 value 來自 encoder 的輸出(聲音)。
  5. 把「Hello, World!」合成語音再辨識回來,得到「hello world」。哪些差異算是辨識錯誤?答案:嚴格說都不算。大小寫在聲音裡沒有對應的訊號;標點只有停頓和語調這種間接的線索。辨識系統如果輸出了標點,多半是它根據語言習慣補上的。

這週和後面的關係

  • 下一週教協同過濾,也就是 PA7 第一部分要實作的推薦演算法,並且完成整個 agent。
  • 第 1 週導論投影片列的元件裡有 Conv Nets,指的就是這週的卷積層。

系列導覽:上一篇 第 7 週:Transformers and Speech Processing|下一篇 第 9 週:Collaborative Filtering and LLM Agents

更新紀錄

  • 2026-10-13:依公開投影片重寫。補上語音學基礎、波形到 log mel 頻譜的步驟、卷積層例子、Whisper 的架構與 cross-attention、訓練資料與限制;PA6b 改成逐部分說明;實作課四的連結從已失效的舊檔名換成 Lab4_Agent.md,並依其內容說明 PA7 的 agent 迴圈與 GitHub 流程;新增自我檢查。

參考資料