iT邦幫忙

2026 iThome 鐵人賽

DAY 5
0
AI Engineering

從 AI Engineering 到 Quantum Machine Learning:30 天打造 QML 實驗與加速開發流程系列 第 5

Day 05|從一般機器學習流程看懂 QML 流程|Understanding QML Through the Classical ML Workflow

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20260918/20183919gwWatUjS1y.png

前四天,我們已經會用向量描述量子狀態、用量子閘改變振幅,也能從量測結果整理機率。今天把這些步驟接起來:給程式一個普通數字,它如何經過量子電路,再回到一般電腦可以比較與保存的數值?

先用 x = 0.25 當例子。程式把它轉成角度 π/4,依角度執行電路,再將一批量測結果換算成分數。理想分數約是 0.7071;有限次量測可能得到 0.70 或其他接近的值。我們會沿路算出這個答案,並檢查差異來自資料轉換、電路、讀取方式,還是抽樣波動。

這是量子機器學習中從輸入到輸出的前向計算(forward pass)。本日先把這條路徑驗證清楚;角度由輸入決定,還沒有從答案學習參數,也沒有訓練完成的分類器。

This chapter follows an ordinary number through data encoding, a quantum circuit, measurement counts, and numerical post-processing. A worked example connects each stage to the known function cos(πx), making both implementation errors and finite-sampling effects easier to identify. The implementation verifies a fixed forward pass; it does not train parameters or evaluate generalization. The article, notebook, and saved results provide a traceable path from equations to outputs.


1. 先畫出一筆資料走過的路

Day1 用花瓣尺寸說明模型如何預測種類。真正的分類工作可能有很多輸入,也需要已知種類作為答案。今天先縮小問題,只接收一個介於 0 與 1 的數字,輸出一個介於 −1 與 +1 的分數。

選擇這個小例子的原因,是每一步都有能手算的答案。若完整程式的輸出不對,就能回查哪一段開始偏離。

從輸入到量測分數:一般計算、量子電路與結果評估的分工

階段 本日做什麼? 以 x = 0.25 為例
輸入 確認數值在合法範圍 0.25 位於 0 到 1 之間
編碼 將數值轉成操作角度 θ = π × 0.25 = π/4
電路 先 RY,再 CNOT 振幅由這個角度決定
量測 累積兩位元結果的次數 理想上只出現 00、11
後處理 依第一個位元記分,再取平均 理想平均約為 0.7071
評估與保存 對照公式,保留設定與誤差 與 cos(π/4) 比較

編碼就是把輸入轉成電路能使用的操作設定;後處理則是把量測結果整理成任務需要的數值。這兩段與誤差計算,都由一般程式完成。量子電路只負責流程中的一部分。

本日用 NumPy 與 CUDA-Q 在一般電腦上模擬這段量子運算。NumPy 是數值計算套件;CUDA-Q 則讓我們以量子位元與操作來描述電路,再指定由哪個模擬器執行。

2. 定義輸入:0.25 在這裡代表什麼?

本日資料由程式直接產生,沒有讀取花卉資料集:

0、0.125、0.25、0.375、0.5、0.625、0.75、0.875、1

這九個數字在 0 到 1 之間等距排列,程式使用 np.linspace(0, 1, 9) 產生。它們是用來檢查流程的輸入點,並不是訓練集或測試集。

如果後續改用真實花瓣長度,就要先說明如何把公分轉成這個範圍。例如何時把某個長度映射到 0、另一個長度映射到 1,以及新資料超出範圍時怎麼處理,都是資料處理的一部分。

本日採取明確規則:輸入必須是有限數值,而且介於 0 與 1,包含兩個端點。負數、大於 1、無限大或無效數值都回報錯誤,不會悄悄改成最接近的邊界。

這種事先約定可接受條件的做法,稱為輸入契約。如果把 1.2 與 5 都截成 1,雖然程式可能繼續執行,卻會把不同資料變成相同輸入;是否允許這種資訊損失,需要由任務決定。

真實資料若需要從樣本估計縮放範圍,應只用訓練資料決定規則,再套用到驗證與測試資料。否則評估資料的資訊就可能提前影響模型流程。

3. 把輸入變成角度,但先別把它叫作學到的參數

本日的編碼規則很簡單:

θ = πx

θ 讀作 theta,是角度,單位為弧度;π 代表半圈。輸入 x 從 0 走到 1,角度便從 0 走到 π。

pipeline.py 中的 encode 函式負責檢查並轉換輸入。以下是可獨立執行的簡化示範:

import numpy as np


def encode(value: float) -> float:
    if not np.isfinite(value) or not 0 <= value <= 1:
        raise ValueError("input must be finite and in [0, 1]")
    return float(np.pi * value)


print(f"theta = {encode(0.25):.6f} radians")

輸出約為 theta = 0.785398 radians,也就是 π/4。np.isfinite 用來排除無限大與無效數值。

這個角度會隨資料改變,但不是從資料「學出來」的。程式沒有比較答案後再修正 θ = πx,也沒有另一組待調整的權重。函式有輸入參數,與模型有可訓練參數,是兩件不同的事。

4. 從角度算出電路狀態

先準備兩個位元都為 0,記作 |00⟩。左邊是 q0,右邊是 q1,沿用 Day4 的順序。接著對 q0 做 RY,再以 q0 控制 q1 的翻轉:

q0:|0⟩ ──RY(πx)──●──量測
                  │
q1:|0⟩ ──────────X──量測

RY 是 Day3 的可調旋轉閘,CNOT 則是 Day4 的受控反相閘。把兩章的規則接起來:

初始:|00⟩

RY 之後:cos(πx/2)|00⟩ + sin(πx/2)|10⟩

CNOT 之後:cos(πx/2)|00⟩ + sin(πx/2)|11⟩

CNOT 保留 00,把 10 變成 11。因此,只有這兩種結果有非零振幅。

代入 x = 0.25,半角為 π/8,得到:

00 的振幅 = cos(π/8) ≈ 0.92388
11 的振幅 = sin(π/8) ≈ 0.38268

P(00) ≈ 0.85355
P(11) ≈ 0.14645
P(01) = P(10) = 0

這裡仍要先算振幅,再取絕對值平方得到機率。也可以選三個容易核對的輸入,檢查電路是否接對:

x 輸出狀態 直接量測
0 |00⟩ 必定得到 00
0.5 (|00⟩ + |11⟩)/√2 00、11 各一半,也就是 Day4 的貝爾態
1 |11⟩ 必定得到 11

CUDA-Q 的電路定義在 cudaq_pipeline.py。下面節錄其操作區塊,需在匯入 CUDA-Q 的模組中使用:

import cudaq


@cudaq.kernel
def encoded_pair(theta: float, measure: bool):
    q = cudaq.qvector(2)
    ry(theta, q[0])
    x.ctrl(q[0], q[1])
    if measure:
        mz(q[0])
        mz(q[1])

@cudaq.kernel 標記描述量子操作的程式區塊。measure=True 時執行量測,用於實際產生本章分數;False 則供模擬器取得完整狀態以核對數值。CUDA-Q 的 sample 取得計數,get_state 取得模擬狀態資訊,兩者提供的證據不同。CUDA-Q 執行方式文件

5. 量測計數如何變成一個分數?

電路給我們的是一批位元結果,例如某次出現 00,另一次出現 11。要把它們變成數值,還需要選擇記分規則。

本日只看第一個位元 q0:量到 0 記 +1,量到 1 記 −1。四種結果的分數如下:

結果 q0q1 q0 是多少? 記分
00 0 +1
01 0 +1
10 1 −1
11 1 −1

假設 n00 表示 00 出現幾次,其餘同理,平均分數便是:

prediction = (n00 + n01 − n10 − n11) / shots

shots 是總次數。依理論機率計算的這種平均叫作 q0 的 Z 期望值,記為 ⟨Z0⟩;用有限次數得到的平均,則是它的估計值。分數範圍是 −1 到 +1,不是 0 到 1 的機率。

回到 x = 0.25。假設 1,000 次得到 850 個 00、150 個 11,這組教學用假設計數的分數為:

prediction = (850 − 150) / 1,000 = 0.70

若用理論機率計算,則約為 0.85355 − 0.14645 = 0.70711。兩者的差距可以來自有限次抽樣,不能只因沒有剛好相等就認定電路錯誤。

程式中的 prediction_from_counts 會檢查結果標籤、非負整數計數,以及總次數是否大於零。即使本日理想電路只有 00、11,公式仍保留 01、10,方便正確處理其他電路或不同條件下的計數。

為什麼不能沿用 Day4 的關聯值?

Day4 將「兩個位元相同」記 +1、「不同」記 −1,計算的是:

兩位元關聯值 = P(00) + P(11) − P(01) − P(10)

本日電路只產生相同結果,所以這個關聯值一直是 1。它無法顯示 x 從 0 到 1 時,00 與 11 的比例如何改變。

同一份計數,換一種記分方式,就可能得到完全不同的輸出。選擇讀取什麼量,通常稱為選擇可觀測量;它是模型設計的一部分,不是量測完隨便挑一個看起來合理的數字。

6. 理想答案為什麼是 cos(πx)?

依前面的電路狀態:

P(q0 = 0) = cos²(πx/2)
P(q0 = 1) = sin²(πx/2)

理想分數 = cos²(πx/2) − sin²(πx/2)
         = cos(πx)

最後一步使用三角函數的倍角關係 cos²(a) − sin²(a) = cos(2a)。能直接從公式算出的答案稱為解析答案,本日把它當成檢查流程的參考值。

x = 0、0.5、1 的理想分數依序為 +1、0、−1;x = 0.25 則是 cos(π/4) = 1/√2 ≈ 0.7071

這裡的目標答案不是另外收集的花卉種類,也不是未知函數。我們事先知道它正好是這段電路的理想輸出,因此是在檢查「程式與抽樣是否符合已知規則」,不能解讀成模型從資料發現了這條規則。

第二個量子位元是這項輸出的必要條件嗎?

移除 CNOT,只保留 q0 的 RY,q0 量到 0、1 的機率仍是相同的 cos²(πx/2)sin²(πx/2)。因此本日的 ⟨Z0⟩ 不變。

CNOT 讓兩位元形成共同狀態,並銜接 Day4,但對目前這個輸出沒有必要性。是否需要某個元件,可以透過「移除它,固定其他條件,看觀察結果是否改變」來檢查。這比只看電路有幾個量子位元,更能說明設計的作用。

7. 用平方誤差衡量差距,先確認差距代表什麼

假設 x = 0.25 的分數是前面的 0.70,理想值約是 0.70710678,這筆誤差便是:

差值 = 0.70 − 0.70710678 ≈ −0.00710678
平方誤差 ≈ 0.00005051

平方讓正負誤差都變成非負數,不會在相加時互相抵消。將多筆平方誤差加總再除以筆數,得到平均平方誤差(MSE)

MSE 只是一個比較數值的規則。它在訓練中可以當作損失函數,指引參數調整;但本日沒有調參數,所以它衡量的是固定流程的輸出離已知答案有多遠,主要反映有限次抽樣誤差,而不是模型學習不足。

能先估計誤差大概多大嗎?

可以。每次記分只有 +1 或 −1,因此每次分數的平方都是 1。若平均分數為 m = cos(πx),單次分數的變異數就是 1 − m²;變異數描述圍繞平均值的平方波動大小。

將 N 次彼此獨立、條件相同的記分取平均,估計值的變異數為:

Var(prediction) = (1 − cos²(πx)) / N

本例的平均估計沒有系統性偏差,所以反覆重做時,對理想值的預期平方誤差也等於這個變異數。

輸入 x 理想分數 N = 1,000 時的預期平方誤差
0 +1 0,因為每次都得到同一結果
0.25 約 0.7071 0.0005
0.5 0 0.001,正負一各半,波動最大
1 −1 0,因為每次都得到同一結果

這也說明「理想答案是 0」不代表每一批平均都剛好是 0。x = 0.5 正是抽樣波動最大的輸入。

對九個等距輸入,sin²(πx) 相加為 4,因此每筆 1,000 次時,平均預期平方誤差是:

4 / (9 × 1,000) = 4/9000 ≈ 0.00044444

增加抽樣次數能降低這項誤差,但它不會替模型學到新規則。若編碼或記分公式寫錯,多抽樣也不會自動修正這種錯誤。

8. 實驗怎麼執行,結果怎麼讀?

執行條件與環境

每個後端都使用相同的九個輸入,每筆抽樣 1,000 次,再用三組種子 42、43、44 重複,得到 27 筆結果。種子是控制程式隨機序列的起始設定。本程式再由重複種子與輸入編號產生各筆抽樣種子,實際值會保存在資料中,方便追溯。

三種後端分別是 NumPy 數值抽樣、CUDA-Q CPU 模擬 qpp-cpu、CUDA-Q GPU 模擬 nvidia。後端表示負責執行的程式或硬體;本章三者都是一般電腦上的模擬。

在專案根目錄沿用既有虛擬環境:

source .venv/bin/activate
python -m pip install -r requirements-day05.txt
python -m pip check

若尚未建立環境,先用 python3.12 -m venv .venv 建立專案套件資料夾。本日已記錄的組合是 NumPy 2.2.6、CUDA-Q 0.15.1,加上執行筆記本的套件;完整版本見 requirements-day05-lock.txt。這是既有驗證的版本,不代表最新安裝組合。

先執行一條完整流程

python articles/day05/pipeline.py --backend numpy
python articles/day05/pipeline.py --backend qpp-cpu

有可用的 NVIDIA GPU 環境時,再執行:

python articles/day05/pipeline.py --backend nvidia

資料寫入 results/day05/<backend>/,重跑相同後端會更新該目錄。如果想比較更多抽樣次數並另存結果:

python articles/day05/pipeline.py --backend qpp-cpu --shots 10000 --output-dir /tmp/day05-more-shots

predictions.csv 逐筆記錄輸入、角度、種子、shots、四種計數、目標、分數與平方誤差。summary.json 保存環境、電路、記分方式、MSE,以及每組種子的 MSE。CSV 是表格文字檔,JSON 則以欄位名稱保存結構化資訊。

保存結果支持什麼結論?

以下為 2026-09-06 保存的預設實驗,各後端 27 筆的平均平方誤差:

方法 MSE 這個比較的作用
NumPy 有限次抽樣 約 0.00047117 一般數值參考實作
CUDA-Q CPU 有限次抽樣 約 0.00036461 核對電路框架的整段流程
CUDA-Q GPU 有限次抽樣 約 0.00036461 指定 GPU 模擬環境的保存結果
永遠輸出 0 約 0.55555556 簡單對照,確認分數確實隨輸入變化
直接計算 cos(πx) 0,依定義 目標本來就由這條公式產生

完整數據見 results/day05,歷史設備與驗證範圍見 ENVIRONMENT.md

抽樣 MSE 與前面預估的 0.00044444 在相近尺度;三組種子的平均不必剛好等於期望值。CUDA-Q 這次比 NumPy 的 MSE 小,也不能據此認定它更準,因為它們以有限樣本估計相同理論值,抽樣序列可能不同。

固定輸出 0 的方法很弱,擊敗它不足以證明量子方法有優勢。本題已有直接計算 cos(πx) 的一般解,而且沒有抽樣誤差;這個對照正好界定本章的用途是流程驗證。

CPU 與 GPU 保存的計數相同,也不是所有環境都必須滿足的條件。耗時包含編譯與初始化,沒有控制暖機與系統負載,因此這份結果不能當成 GPU 加速評測。

9. 筆記本:把每一步的說明、程式與答案放在一起

Day5 筆記本 先回顧振幅、量測與貝爾態,再執行編碼、計數換分數,以及 NumPy/CUDA-Q CPU 的完整流程。每個文字段落後面都有可以執行的程式區塊,稱為儲存格。

在編輯器開啟它,選擇專案 .venv/bin/python 作為執行環境,再由第一格依序執行全部內容。這能避免只執行後半段,卻沿用先前留在記憶體裡的變數。

也可以透過專案腳本完整重跑:

python articles/day05/execute_notebook.py

execute_notebook.py 使用目前 Python 啟動暫時的 Jupyter 執行程序,遇到儲存格錯誤便停止,成功後更新筆記本輸出。這種將全部儲存格重新執行的方式,可檢查筆記本是否能從頭重現。nbclient 執行文件

Jupyter 的執行程序也稱為 kernel,但它和 CUDA-Q 描述量子操作的 quantum kernel 是不同概念。筆記本中的檔案保存範例使用暫存目錄;正式實驗資料由 pipeline.py 寫入結果目錄。

本章相關檔案可依下列問題查找:

想核對什麼? 入口
輸入檢查、計數轉分數、MSE pipeline.py
CUDA-Q 的 RY、CNOT 與量測 cudaq_pipeline.py
逐步執行與檢查輸出 筆記本
流程各階段的分工 流程圖
執行版本、歷史 GPU 結果與限制 ENVIRONMENT.md

程式直接重用 Day3 的旋轉閘與 Day4 的 CNOT 定義。這讓同一個操作只有一份主要實作,後續核對或修正時比較容易保持一致。

10. 如何檢查這條流程,而不只看最後 MSE?

先執行既有測試:

python -m unittest discover -s articles/day05 -p 'test_*.py' -v

在可用的 GPU 環境核對整合測試:

DAY05_TARGET=nvidia python -m unittest discover -s articles/day05 -p 'test_cudaq_pipeline.py' -v

測試要覆蓋不同責任。輸入端檢查非法數值會被拒絕;電路端對照解析振幅;後處理用包含 01、10 的非對稱計數檢查位元順序;保存端再核對每筆平方誤差與總體 MSE 是否一致。

例如,只看本日理想的 00、11 計數,即使把左右位元讀反也不容易發現。加入 01、10 測試,才會讓 q0 與 q1 的記分差異出現。好的測試需要能區分正確與錯誤實作的輸入。

如果結果異常,可以沿流程定位:

觀察 下一步核對
x = 0 的分數不是 +1 角度、初始狀態、位元順序與記分正負號
所有輸入都得到 1 是否誤用兩位元關聯值代替 Z0
x = 0.5 的有限次分數不等於 0 先看 shots 與理論波動,而非立即判定失敗
MSE 很小,但不知道是否學會了 是否真的有參數更新與獨立評估資料
GPU 時間較短或較長 是否把初始化、負載與計時範圍控制一致

11. 距離「模型訓練」還缺哪一步?

現在的流程是:輸入決定角度,固定電路產生分數,再與已知公式比較。訓練則還需要一組獨立的可調權重、用來衡量任務誤差的損失函數,以及選擇如何更新權重的最佳化器。

本日:輸入 x → 固定的轉換與電路 → 分數 → 核對誤差

後續:輸入 x + 目前權重 → 分數 → 與答案比較
                                  ↓
                         更新權重,再次執行

要判斷模型能否處理沒見過的資料,還需要保留未參與訓練與設定選擇的資料,檢查泛化能力。本章九個已知輸入的函數核對,不能替代這個評估。

前五天累積的是一條可以追查的證據路徑:從狀態定義、操作推導,到量測計數、分數與保存指標。接下來加入訓練時,就能分辨變化來自模型參數、資料處理或量測波動,而不是只看一條誤差曲線猜原因。

12. 來源與下一篇

Day 06 會整理 CUDA-Q、CUDA、cuQuantum 的分工,說明如何確認程式實際使用的環境與執行後端,再進入後續可訓練電路的實作。

延伸研究

[N1] Shreeya Sanjeev Gokhale et al. “A review of quantum machine learning algorithms, applications, and emerging advantages.” Discover Computing 29, 226 (2026);綜述論文。原始來源完整書目

Tip : 本章目標在於建立可重跑的實驗流程;延伸閱讀時,可將研究中的資料處理、電路執行與結果分析分開記錄,建立完整成本觀念。


上一篇
Day 04|疊加、糾纏與量測:建立第一個貝爾態|Superposition, Entanglement, and Measurement: Building the First Bell State
下一篇
Day 06|CUDA-Q、CUDA、cuQuantum 有何不同?|What’s the Difference Between CUDA-Q, CUDA, and cuQuantum?
系列文
從 AI Engineering 到 Quantum Machine Learning:30 天打造 QML 實驗與加速開發流程10
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言