量子回路を実機で実行するたびに、QPU時間と費用がかかります。VQEやQAOAのような変分アルゴリズムは、収束させるために数十万回もの回路実行を必要とすることがあります。クラウドQPUでは1タスクあたり$0.075〜$0.90かかるため、これはすぐに膨れ上がります。
朗報は、ほとんどのデフォルト実装が極めてショット非効率であることです。適切なテクニックを使えば、結果の品質を犠牲にすることなくショット要求量を50〜90%削減できます。
デフォルトではなぜショット数がこれほど多いのか
VQEはパウリ演算子の期待値を計算します。ハミルトニアンはパウリ項の和に分解され、各項ごとに個別の回路実行が必要になります。H₂(4量子ビット)のような分子では、パウリ項は約15個です。より大きな分子ではその数は爆発的に増えます。
| 分子 | 量子ビット数 | パウリ項数 | 素朴なショット数/反復 |
|---|---|---|---|
| H₂ | 4 | 15 | 15,000 |
| LiH | 12 | 631 | 631,000 |
| BeH₂ | 14 | 666 | 666,000 |
| H₂O | 14 | 1,086 | 1,086,000 |
オプティマイザの反復を200回とすると、H₂Oは素朴には2億1,700万ショットを必要とします。以下のテクニックでこれを80〜95%削減できます。
テクニック1: 測定のグループ化(最大の効果)
多くのパウリ項は可換であり、個別にではなく単一の回路実行で同時に測定できます。可換なオブザーバブルをグループ化することは、利用できる最大の削減手段です。
from qiskit.primitives import StatevectorEstimator
from qiskit_nature.second_q.mappers import JordanWignerMapper
from qiskit_algorithms import VQE
from qiskit_algorithms.optimizers import COBYLA
# Qiskit automatically groups commuting Paulis in the Estimator primitive
# This reduces shots from O(n_terms) to O(n_groups) — often 5-10x reduction
estimator = StatevectorEstimator()
# With PennyLane, use grouping explicitly:
import pennylane as qml
H = qml.Hamiltonian(coeffs, observables)
# Group commuting terms — usually reduces term count by 5-10x
groups = qml.grouping.group_observables(observables, grouping_type='qwc')
print(f"Original terms: {len(observables)}, Groups: {len(groups)}")
# Original terms: 631, Groups: 68 (for LiH)
期待される削減効果: 5〜15× — 典型的な化学ハミルトニアンにおいて。
テクニック2: ショット節約型オプティマイザ
L-BFGS-BやADAMのような古典的オプティマイザは、ノイズのない関数評価を前提としており、結果にノイズがある場合に必要以上の勾配評価を要求します。ショット節約型オプティマイザは、測定の分散に基づいてショットを適応的に割り当てます。
from pennylane.optimize import AdaptiveOptimizer, ShotAdaptiveOptimizer
dev = qml.device("default.qubit", wires=4)
@qml.qnode(dev)
def circuit(params):
# ansatz
...
return qml.expval(H)
# ShotAdaptiveOptimizer: allocates more shots to high-variance directions
opt = ShotAdaptiveOptimizer(min_shots=10)
params = np.random.uniform(-np.pi, np.pi, n_params)
for i in range(100):
params, _, shots_used = opt.step_and_cost(circuit, params)
print(f"Step {i}: shots used = {shots_used}")
このオプティマイザは評価あたり少ないショット数から始め、勾配が不確かな場合にのみショット数を増やします。典型的なVQE実行では、固定ショットのCOBYLAと比べて5〜10×少ないショット数で済みます。
テクニック3: パラメータシフト勾配(評価回数を減らす)
素朴な有限差分による勾配推定 (f(x+ε) - f(x))/ε は、εが小さいと分散が大きく、εが大きいとバイアスが大きくなります。パラメータシフト則は、パラメータあたりわずか2回の回路評価で厳密な勾配を与えます。
# PennyLane uses parameter-shift by default for gradients
@qml.qnode(dev, diff_method="parameter-shift") # 2 evals per param
def circuit(params):
...
# Compare to finite-difference (requires 1 eval per param but biased):
@qml.qnode(dev, diff_method="finite-diff") # 1 eval but approximate
# For large circuits, use "best" — PennyLane chooses adjoint on simulator,
# parameter-shift on hardware
@qml.qnode(dev, diff_method="best")
def circuit(params):
...
n 個のパラメータに対して、パラメータシフトは勾配ステップあたり 2n 回の評価を要します。n が大きい場合は、勾配を用いないオプティマイザ(COBYLA、SPSA、Nelder-Mead)を使いましょう。
テクニック4: SPSA — 確率的勾配推定
同時摂動確率近似(SPSA)は、すべてのパラメータを同時に摂動させることで、パラメータ数に関係なくわずか2回の回路評価で勾配全体を推定します。
from qiskit_algorithms.optimizers import SPSA
# SPSA: 2 evaluations per step regardless of parameter count
# vs parameter-shift: 2n evaluations per step
optimizer = SPSA(maxiter=300, learning_rate=0.1, perturbation=0.05)
# For 10 parameters:
# - Parameter shift: 2×10 = 20 evals/step × 300 steps = 6,000 total
# - SPSA: 2 evals/step × 300 steps = 600 total ← 10x reduction
最適な場面: 回路のパラメータ数が多い場合(> 10)。トレードオフとしてステップあたりの収束は遅くなりますが、総ショット数は少なくなります。
テクニック5: 早期終了 + 分散しきい値
結果がすでに十分に良い場合、回路を最後まで実行しないようにします。
from pennylane.optimize import AdamOptimizer
import numpy as np
opt = AdamOptimizer(stepsize=0.02)
params = init_params.copy()
prev_energy = float('inf')
for step in range(max_steps):
params, energy = opt.step_and_cost(circuit, params)
# Stop when change is below shot-noise floor
variance = 1.0 / np.sqrt(shots_per_eval) # shot noise floor
if abs(energy - prev_energy) < variance:
print(f"Converged at step {step}")
break
prev_energy = energy
多くのVQE実行は早期に頭打ちになります。それ以上続けても、ノイズの変動に対してショットを浪費するだけです。
テクニック6: ウォームスタート
QAOAやVQEのパラメータをランダムではなく、関連する古典解から初期化します。
# For QAOA on Max-Cut: warm start from a greedy classical solution
import networkx as nx
G = nx.from_edgelist(edges)
classical_cut = nx.algorithms.approximation.one_exchange(G)
# Map classical solution to initial QAOA angles
# γ₀ ≈ π/4 for a good cut, β₀ ≈ π/8
init_gamma = [np.pi / 4]
init_beta = [np.pi / 8]
# Warm-started QAOA typically converges in 30-50% fewer iterations
すべてを組み合わせる: 実践的なVQEテンプレート
import pennylane as qml
import numpy as np
from pennylane.optimize import ShotAdaptiveOptimizer
dev = qml.device("default.qubit", wires=n_qubits, shots=512)
# 1. Group commuting terms (5-10x reduction in circuit count)
grouped_H = qml.Hamiltonian(*qml.grouping.group_observables(H))
@qml.qnode(dev, diff_method="parameter-shift")
def ansatz(params):
# Hardware-efficient ansatz
for i in range(n_qubits):
qml.RY(params[i], wires=i)
for i in range(n_qubits - 1):
qml.CNOT(wires=[i, i + 1])
return qml.expval(grouped_H)
# 2. Use shot-adaptive optimizer
opt = ShotAdaptiveOptimizer(min_shots=50)
# 3. Warm start
params = warm_start_params(H)
# 4. Run with early stopping
for step in range(300):
params, energy, shots = opt.step_and_cost(ansatz, params)
if check_convergence(energy, shots):
break
print(f"Ground state energy: {energy:.4f} Ha")
クイックリファレンス: 手法別のショット予算
| 手法 | ショット数/ステップ | 最適な場面 |
|---|---|---|
| COBYLA + 固定ショット | n_terms × shots | パラメータ数が少ない場合 |
| パラメータシフト + Adam | 2n_params × shots | 微分可能な回路 |
| SPSA | 2 × shots | パラメータ数が多い場合 |
| ShotAdaptiveOptimizer | 適応的 | 一般的なVQE |
| グループ化されたパウリ項 | ÷5〜15× | 常に最初に適用 |
まず測定のグループ化を適用してください。これは単一で最大の効果を持ち、オプティマイザや回路に変更を加える必要がありません。