
前四天,我們已經會用向量描述量子狀態、用量子閘改變振幅,也能從量測結果整理機率。今天把這些步驟接起來:給程式一個普通數字,它如何經過量子電路,再回到一般電腦可以比較與保存的數值?
先用 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.
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 則讓我們以量子位元與操作來描述電路,再指定由哪個模擬器執行。
本日資料由程式直接產生,沒有讀取花卉資料集:
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,雖然程式可能繼續執行,卻會把不同資料變成相同輸入;是否允許這種資訊損失,需要由任務決定。
真實資料若需要從樣本估計縮放範圍,應只用訓練資料決定規則,再套用到驗證與測試資料。否則評估資料的資訊就可能提前影響模型流程。
本日的編碼規則很簡單:
θ = π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,也沒有另一組待調整的權重。函式有輸入參數,與模型有可訓練參數,是兩件不同的事。
先準備兩個位元都為 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 執行方式文件
電路給我們的是一批位元結果,例如某次出現 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 將「兩個位元相同」記 +1、「不同」記 −1,計算的是:
兩位元關聯值 = P(00) + P(11) − P(01) − P(10)
本日電路只產生相同結果,所以這個關聯值一直是 1。它無法顯示 x 從 0 到 1 時,00 與 11 的比例如何改變。
同一份計數,換一種記分方式,就可能得到完全不同的輸出。選擇讀取什麼量,通常稱為選擇可觀測量;它是模型設計的一部分,不是量測完隨便挑一個看起來合理的數字。
依前面的電路狀態:
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,但對目前這個輸出沒有必要性。是否需要某個元件,可以透過「移除它,固定其他條件,看觀察結果是否改變」來檢查。這比只看電路有幾個量子位元,更能說明設計的作用。
假設 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
增加抽樣次數能降低這項誤差,但它不會替模型學到新規則。若編碼或記分公式寫錯,多抽樣也不會自動修正這種錯誤。
每個後端都使用相同的九個輸入,每筆抽樣 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 加速評測。
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 定義。這讓同一個操作只有一份主要實作,後續核對或修正時比較容易保持一致。
先執行既有測試:
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 時間較短或較長 | 是否把初始化、負載與計時範圍控制一致 |
現在的流程是:輸入決定角度,固定電路產生分數,再與已知公式比較。訓練則還需要一組獨立的可調權重、用來衡量任務誤差的損失函數,以及選擇如何更新權重的最佳化器。
本日:輸入 x → 固定的轉換與電路 → 分數 → 核對誤差
後續:輸入 x + 目前權重 → 分數 → 與答案比較
↓
更新權重,再次執行
要判斷模型能否處理沒見過的資料,還需要保留未參與訓練與設定選擇的資料,檢查泛化能力。本章九個已知輸入的函數核對,不能替代這個評估。
前五天累積的是一條可以追查的證據路徑:從狀態定義、操作推導,到量測計數、分數與保存指標。接下來加入訓練時,就能分辨變化來自模型參數、資料處理或量測波動,而不是只看一條誤差曲線猜原因。
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 : 本章目標在於建立可重跑的實驗流程;延伸閱讀時,可將研究中的資料處理、電路執行與結果分析分開記錄,建立完整成本觀念。