Kvantová jádra
Úvod do kvantových jader
„Metoda kvantových jader" označuje jakoukoli metodu, která využívá kvantové počítače k odhadnutí kernel. V tomto kontextu se pojmem „kernel" rozumí matice jádra nebo její jednotlivé prvky. Připomeňme, že feature map je zobrazení z do kde obvykle a cílem tohoto zobrazení je, aby kategorie dat byly oddělitelné nadrovinou. Funkce kernel bere jako argumenty vektory ve feature-mapovaném prostoru a vrací jejich skalární součin, tedy s . V klasickém přístupu nás zajímají feature mapy, pro které je funkce kernel snadno vyhodnotitelná. To často znamená nalézt takovou funkci kernel, pro niž lze skalární součin ve feature-mapovaném prostoru vyjádřit pomocí původních datových vektorů, aniž bychom museli a vůbec sestrojovat. V metodě kvantových jader je feature mapping provedeno kvantovým obvodem a kernel se odhaduje pomocí měření na tomto obvodu a relativních pravděpodobností měření.
V této lekci prozkoumáme hloubky předpřipravených kódovacích obvodů, které využívají výrazné provázání, a porovnáme je s hloubkami obvodů, které si napíšeme sami. Nejde o to prosazovat jednu metodu před druhou. Možná zjistíš, že předpřipravené obvody jsou příliš hluboké a že provázání ve vlastnoručně sestaveném obvodu nestačí k tomu, aby bylo užitečné. Obojí je zde ukázáno pouze proto, aby sis mohl/a dále bádat.
Než podrobně projdeme odhad matice jádra, nastíníme pracovní postup pomocí jazyka vzorů Qiskit.
Krok 1: Namapuj klasické vstupy na kvantový problém
- Vstup: Trénovací datová sada
- Výstup: Abstraktní obvod pro výpočet prvku matice jádra
S ohledem na datovou sadu je výchozím bodem zakódování dat do kvantového obvodu. Jinými slovy, musíme namapovat naše data do Hilbertova prostoru stavů našeho kvantového počítače. Toho dosáhneme sestrojením obvodu závislého na datech. Existuje mnoho způsobů, jak to udělat, a předchozí lekce nastínila řadu možností. Můžeš sestrojit vlastní obvod pro zakódování dat nebo použít předpřipravený feature map jako zz_feature_map. V této lekci uděláme obojí.
Pamatuj, že pro výpočet jediného prvku matice jádra budeme chtít zakódovat dva různé body, abychom mohli odhadnout jejich skalární součin. Kompletní kvantový kernel workflow bude samozřejmě zahrnovat mnoho takových skalárních součinů mezi namapovanými datovými vektory i klasické metody strojového učení. Ale jádrovým krokem, který se opakuje, je odhad jediného prvku matice jádra. K tomu vybereme obvod závislý na datech a namapujeme dva datové vektory do feature prostoru.

Pro úlohu generování matice jádra nás zajímá zejména pravděpodobnost naměření stavu , ve kterém jsou všechny qubity ve stavu . Abychom to pochopili, uvažme, že obvod zodpovědný za zakódování a mapování jednoho datového vektoru lze zapsat jako a ten zodpovědný za zakódování a mapování jako , a označme namapované stavy
Tyto stavy jsou mapováním dat do vyšších dimenzí, takže hledaný prvek kernel je skalární součin
Pokud na výchozí počáteční stav aplikujeme oba obvody a , pravděpodobnost následného naměření stavu je
To je přesně hodnota, kterou hledáme (s výhradou ). Měřicí vrstva našeho obvodu vrátí pravděpodobnosti měření (nebo tzv. „kvazi-pravděpodobnosti", pokud jsou použity určité metody zmírnění chyb). Pravděpodobnost, o kterou nám jde, je pravděpodobnost nulového stavu .
Krok 2: Optimalizuj problém pro kvantové spuštění
- Vstup: Abstraktní obvod, neoptimalizovaný pro konkrétní backend
- Výstup: Cílový obvod a observabla, optimalizované pro vybraný QPU
V tomto kroku použijeme funkci generate_preset_pass_manager z Qiskit, abychom pro náš obvod specifikovali rutinu optimalizace s ohledem na skutečný kvantový počítač, na kterém plánujeme experiment spustit. Nastavíme optimization_level=3, což znamená, že použijeme přednastavený pass manager poskytující nejvyšší úroveň optimalizace. V tomto kontextu se „optimalizací" rozumí optimalizace implementace obvodu na skutečném kvantovém počítači. To zahrnuje například výběr fyzických qubitů odpovídajících qubitům v abstraktním kvantovém obvodu tak, aby se minimalizovala hloubka hradel, nebo výběr fyzických qubitů s nejnižšími dostupnými chybovými sazbami. Toto přímo nesouvisí s optimalizací problému strojového učení (jako jsou klasické optimalizátory jako COBYLA).
Podle toho, jak implementuješ krok 2, možná budeš muset obvod optimalizovat více než jednou, protože každý pár bodů zapojený do prvku matice vytvoří jiný obvod k měření.
Krok 3: Spusť pomocí primitiv Qiskit Runtime
- Vstup: Cílový obvod
- Výstup: Rozdělení pravděpodobnosti
Pomocí primitivu Sampler z Qiskit Runtime rekonstruuj rozdělení pravděpodobnosti stavů získaných vzorkováním obvodu. Všimni si, že se na to může odkazovat jako na „rozdělení kvazi-pravděpodobnosti", termín platný v případech, kdy je problémem šum a kdy jsou zavedeny dodatečné kroky, například zmírnění chyb. V takových případech se součet všech pravděpodobností nemusí rovnat přesně 1; odtud „kvazi-pravděpodobnost".
Krok 4: Post-processing, vrácení výsledku v klasickém formátu
- Vstup: Rozdělení pravděpodobnosti
- Výstup: Jeden prvek matice jádra nebo celá matice jádra při opakování
Vypočítej pravděpodobnost naměření na kvantovém obvodu a zaplň matici jádra na pozici odpovídající dvěma použitým datovým vektorům. Abychom vyplnili celou matici jádra, musíme spustit kvantový experiment pro každý prvek. Jakmile máme matici jádra, můžeme ji použít v mnoha klasických algoritmech strojového učení, které přijímají pre-calculated kernels. Například: qml_svc = SVC(kernel="precomputed"). Potom můžeme využít klasické workstreams k aplikaci našeho modelu na testovací data a získat skóre přesnosti. Pokud nebudeme s naším skóre přesnosti spokojeni, možná budeme muset přehodnotit aspekty našeho výpočtu, například feature map.
Osnova lekce
V této lekci provedeme tyto kroky několika způsoby, abychom co nejlépe využili tvůj čas na skutečných kvantových počítačích. Metodu kvantového kernel aplikujeme na:
- Jeden prvek matice jádra pro data s relativně malým počtem příznaků, s využitím skutečného backend, abychom mohli snadno sledovat, co se děje v každém kroku.
- Celou datovou sadu s relativně malým počtem příznaků, s využitím simulovaného backend, abychom viděli, jak se kvantový workstream propojuje s klasickými metodami strojového učení.
- Jeden prvek matice jádra pro data s mnoha příznaky, s využitím skutečného kvantového počítače. Celou matici jádra pro velkou datovou sadu odhadovat nebudeme, abychom respektovali čas na kvantových počítačích IBM®.
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy pandas qiskit qiskit-ibm-runtime scikit-learn
# If you have not already, install scikit learn
#!pip install scikit-learn
Položka kernel matice
Krok 1: Mapování klasických vstupů na kvantový problém
Uvažujme nejprve datovou sadu s jen několika features, řekněme 10. Datová sada může být libovolně velká, protože počítáme prvky kernel matice jeden po druhém. Potřebujeme alespoň dva body, takže začneme s tím (v dalším příkladu importujeme celou datovou sadu). Importujme několik potřebných balíčků:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# Two mock data points, including category labels, as in training
small_data = [
[-0.194, 0.114, -0.006, 0.301, -0.359, -0.088, -0.156, 0.342, -0.016, 0.143, 1],
[-0.1, 0.002, 0.244, 0.127, -0.064, -0.086, 0.072, 0.043, -0.053, 0.02, -1],
]
# Data points with labels removed, for inner product
train_data = [small_data[0][:-1], small_data[1][:-1]]
Můžeme zkusit použít z_feature_map.
# from qiskit.circuit.library import zz_feature_map
# fm = zz_feature_map(feature_dimension=np.shape(train_data)[1], entanglement='linear', reps=1)
from qiskit.circuit.library import z_feature_map
fm = z_feature_map(feature_dimension=np.shape(train_data)[1])
unitary1 = fm.assign_parameters(train_data[0])
unitary2 = fm.assign_parameters(train_data[1])
Výše uvedené dva unitáry přesně odpovídají a popsaným v úvodu. Můžeme je zkombinovat pomocí unitary_overlap. Jako vždy chceme mít přehled o hloubce našeho obvodu.
from qiskit.circuit.library import unitary_overlap
overlap_circ = unitary_overlap(unitary1, unitary2)
overlap_circ.measure_all()
print("circuit depth = ", overlap_circ.decompose().depth())
overlap_circ.decompose().draw("mpl", scale=0.6, style="iqp")
circuit depth = 9
Krok 2: Optimalizace problému pro kvantové spuštění
Začneme výběrem nejméně vytíženého backend, poté optimalizujeme náš obvod pro spuštění na tomto backend.
# Import needed packages
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
# Get the least busy backend
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=fm.num_qubits
)
print(backend)
<IBMBackend('ibm_brisbane')>
# Apply level 3 optimization to our overlap circuit
pm = generate_preset_pass_manager(optimization_level=3, backend=backend)
overlap_ibm = pm.run(overlap_circ)
U složitých obvodů tento krok podstatně zvýší hloubku obvodu, protože mapuje na nativní hradla pro skutečné kvantové počítače a informace může být nutné přesunout z qubitu na qubit. V tomto jednoduchém případě je hloubka téměř nezměněna.
print("circuit depth = ", overlap_ibm.decompose().depth())
overlap_ibm.decompose().depth(lambda instr: len(instr.qubits) > 1)
circuit depth = 10
1
Krok 3: Spuštění pomocí Qiskit Runtime Primitives
Syntaxe pro spuštění na simulátoru je níže zakomentována. Pro tuto datovou sadu s malým počtem features je spuštění na simulátoru stále možné. Pro výpočty v utility měřítku obvykle simulace není proveditelná. Simulátory by měly být používány pouze k ladění zmenšeného kódu.
# Run this for a simulator
# from qiskit.primitives import StatevectorSampler
# from qiskit_ibm_runtime import Options, Session, Sampler
# num_shots = 10000
# Evaluate the problem using state vector-based primitives from Qiskit
# sampler = StatevectorSampler()
# results = sampler.run([overlap_circ], shots=num_shots).result()
# .get_counts() returns counts associated with a state labeled by bit results such as |001101...01>.
# counts_bit = results[0].data.meas.get_counts()
# .get_int_counts returns the same counts, but labeled by integer equivalent of the above bit string.
# counts = results[0].data.meas.get_int_counts()
# Benchmarked on an Eagle processor, 7-11-24, took 4 sec.
# Import our runtime primitive
from qiskit_ibm_runtime import Session, SamplerV2 as Sampler
num_shots = 10000
# Use sampler and get the counts
sampler = Sampler(mode=backend)
results = sampler.run([overlap_ibm], shots=num_shots).result()
# .get_counts() returns counts associated with a state labeled by bit results such as |001101...01>.
counts_bit = results[0].data.meas.get_counts()
# .get_int_counts returns the same counts, but labeled by integer equivalent of the above bit string.
counts = results[0].data.meas.get_int_counts()
Krok 4: Post-processing, vrácení výsledku v klasickém formátu
Jak je popsáno v úvodu, nejužitečnějším měřením je zde pravděpodobnost naměření nulového stavu .
counts.get(0, 0.0) / num_shots
0.6525
Toto je výsledek, který jsme chtěli: odhad skalárního součinu (až do kvadrátu modulu) vektorů odpovídajících dvěma datovým bodům. Pokud chceme zobrazit celé rozložení pravděpodobností měření (nebo kvazipravděpodobností), můžeme tak učinit pomocí funkce plot_distribution, jak je ukázáno níže. Je vidět, že pro velký počet qubit se takovéto obrázky rychle stávají nepřehledné.
from qiskit.visualization import plot_distribution
plot_distribution(counts_bit)

Případně lze definovat vizualizaci jako ta níže, která zobrazuje pouze 10 nejpravděpodobnějších měření. To může být důležité pro ladění nebo získání lepší intuice o datech. Avšak pravděpodobnost měření nulového stavu je naším prvkem kernel matice.
def visualize_counts(probs, num_qubits):
"""Visualize the outputs from the Qiskit Sampler primitive."""
zero_prob = probs.get(0, 0.0)
top_10 = dict(sorted(probs.items(), key=lambda item: item[1], reverse=True)[:10])
top_10.update({0: zero_prob})
by_key = dict(sorted(top_10.items(), key=lambda item: item[0]))
xvals, yvals = list(zip(*by_key.items()))
xvals = [bin(xval)[2:].zfill(num_qubits) for xval in xvals]
plt.bar(xvals, yvals)
plt.xticks(rotation=75)
plt.title("Results of sampling")
plt.xlabel("Measured bitstring")
plt.ylabel("Counts")
plt.show()
visualize_counts(counts, overlap_circ.num_qubits)
Z těchto informací o jediném skalárním součinu mezi dvěma datovými body ve vyšším dimenzionálním feature prostoru můžeme říci pouze to, že jejich překryv je poměrně velký ve srovnání s maximálním překryvem (který by byl 1.0). To může být indikátorem, že tyto dva datové body jsou si nějak podobné a budou zařazeny do stejné třídy. Nebo to může být indikátorem, že náš feature map není efektivní při mapování do prostoru, kde podobná data mají silný překryv a nepodobná data mají malý překryv. Abychom věděli, co je pravda, musíme aplikovat náš feature map na celou sadu dat a zjistit, zda lze výslednou kernel matici zpracovat tak, aby efektivně separovala třídy s vysokou přesností.
Stojí za zmínku, že jsme použili z_feature_map, který vedl k nízké transpilované hloubce dvouqubitová hradla (hloubka 1). Pokud se tvé obvod stanou příliš hluboké, povede to jistě k velkému šumu, a to sníží pravděpodobnost měření nulového stavu na velmi nízkou hodnotu, i když je tvůj feature map dobře přizpůsoben tvým datům. Například opakování výše uvedeného procesu s použitím zz_feature_map a , entanglement='linear', reps=1 přineslo dist.get(0,0.0) = 0.0015 při použití stejných datových bodů. Je to kvůli mnohem větším hloubkám obvod a hloubkám dvouqubitová hradla z zz_feature_map. Obrázek níže ukazuje rozložení pravděpodobnosti pro tento výpočet.

Vyplatí se vyzkoušet si několik datových bodů ze stejné kategorie a zjistit, jak nízká musí být tvá hloubka, abys dosáhl dobrých výsledků. Následující je přibližné doporučení, které jistě má výjimky. Obecně by transpilovaná hloubka dvouqubitová hradla 10 nebo méně neměla být problém. Transpilovaná hloubka dvouqubitová hradla 50–60 je na hranici současného stavu techniky a bude vyžadovat pokročilé zmírňování chyb a další nástroje. Výsledky mezi těmito hodnotami se mohou lišit v závislosti na podobnosti dat, expresivitě feature map, šířce obvodu a dalších faktorech. Krok post-processingu by obvykle zahrnoval také klasické procesy strojového učení. V další části rozšíříme tento proces na celou datovou sadu a ukážeme pracovní postup klasického strojového učení.
Ověř si porozumění
Přečti si níže uvedené otázky, zamysli se nad odpověďmi a poté klikni na trojúhelníky pro zobrazení řešení.
V 10-qubitovém kvantovém obvodu, kolik různých stavů obecně může být naměřeno?
Odpověď:
neboli 1024.
Předpokládejme, že někdo nový v kvantovém výpočetnictví se pokusí použít kvantový obvod s velmi vysokou hloubkou dvouqubitová hradla a nepoužije zmírňování chyb. Předpokládejme dále, že to vede k chybovosti 10 % na každém qubitu. Pokud je skutečný (bezchybný) prvek kernel matice odpovídající tomuto obvodu velmi velký, řekněme 1.0, jaká by byla pravděpodobnost naměření všech 10 qubitů ve stavu, kdy je každý qubit |0>?
Odpověď:
Pravděpodobnost, že každý qubit bude správně nalezen ve stavu |0>, je 0.90. Pravděpodobnost, že všech 10 qubit bude nalezeno ve správném stavu, je neboli přibližně 35 %.
Vysvětli vlastními slovy, proč je tak důležité sledovat hloubky obvodů. To platí obecně, ale vysvětli to v kontextu kvantového odhadování kernel.
Odpověď:
V tomto pracovním postupu QKE jsou naše odhady založeny na měřeních nulového stavu, tedy stavu, ve kterém je každý qubit nalezen ve stavu . Velmi hluboké obvody zavedou vysoké chybovosti. Když se tato chybovost složí přes mnoho qubitů, sníží to pravděpodobnost měření nulového stavu výrazně.
Plná kernel matice
V této části rozšíříme výše uvedený postup na binární klasifikaci celé datové sady. To přinese dvě důležité součásti: (1) nyní můžeme implementovat klasické strojové učení v post-processingu a (2) můžeme získat skóre přesnosti pro naše trénování.
Krok 1: Namapuj klasické vstupy na kvantový problém
Nyní naimportujeme existující datovou sadu pro naši klasifikaci. Tato datová sada se skládá ze 128 řádků (datových bodů) a 14 příznaků na každém bodě. Patnáctý prvek označuje binární kategorii každého bodu (). Datová sada je importována níže, nebo si ji můžeš prohlédnout a zobrazit její strukturu zde.
Pro trénování použijeme prvních 90 datových bodů a dalších 30 bodů pro testování.
!wget https://raw.githubusercontent.com/qiskit-community/prototype-quantum-kernel-training/main/data/dataset_graph7.csv
df = pd.read_csv("dataset_graph7.csv", sep=",", header=None)
# Prepare training data
train_size = 90
X_train = df.values[0:train_size, :-1]
train_labels = df.values[0:train_size, -1]
# Prepare testing data
test_size = 30
X_test = df.values[train_size : train_size + test_size, :-1]
test_labels = df.values[train_size : train_size + test_size, -1]
--2024-07-11 23:05:22-- https://raw.githubusercontent.com/qiskit-community/prototype-quantum-kernel-training/main/data/dataset_graph7.csv
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.110.133, 185.199.111.133, 185.199.109.133, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.110.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 49405 (48K) [text/plain]
Saving to: ‘dataset_graph7.csv.15’
dataset_graph7.csv. 100%[===================>] 48.25K --.-KB/s in 0.02s
2024-07-11 23:05:23 (2.11 MB/s) - ‘dataset_graph7.csv.15’ saved [49405/49405]
Již nyní se připravíme na ukládání více výstupů tím, že sestavíme kernel matici a testovací matici vhodných rozměrů.
# Empty kernel matrix
num_samples = np.shape(X_train)[0]
kernel_matrix = np.full((num_samples, num_samples), np.nan)
test_matrix = np.full((test_size, num_samples), np.nan)
Nyní vytvoříme feature map pro kódování a mapování našich klasických dat v kvantovém obvodu. Můžeme si sestavit vlastní feature map nebo použít předpřipravenou. Klidně upravte feature map níže nebo se přepiň zpět na ZFeatureMap. Vždy ale dávej pozor na hloubku obvodu. Vzpomeň si, že v předchozím příkladu se 6 qubity byla hloubka transpilovaného obvodu při použití zz_feature_map neúnosně vysoká. S rostoucí velikostí a složitostí obvodů může hloubka rychle narůst do bodu, kdy šum pohltí naše výsledky. Kdykoli víš něco o struktuře svých dat, co by mohlo napovědět, jaká struktura feature map by byla nejužitečnější, je vhodné vytvořit vlastní feature map na míru, která tuto znalost využívá.
from qiskit.circuit import Parameter, ParameterVector, QuantumCircuit
# Prepare feature map for computing overlap
num_features = np.shape(X_train)[1]
num_qubits = int(num_features / 2)
# To use a custom feature map use the lines below.
entangler_map = [[0, 2], [3, 4], [2, 5], [1, 4], [2, 3], [4, 6]]
fm = QuantumCircuit(num_qubits)
training_param = Parameter("θ")
feature_params = ParameterVector("x", num_qubits * 2)
fm.ry(training_param, fm.qubits)
for cz in entangler_map:
fm.cz(cz[0], cz[1])
for i in range(num_qubits):
fm.rz(-2 * feature_params[2 * i + 1], i)
fm.rx(-2 * feature_params[2 * i], i)
Kroky 2 a 3: Optimalizuj problém a spusť pomocí primitiv
Sestavíme overlap obvod, a kdybychom v tomto příkladu běželi na skutečném kvantovém počítači, optimalizovali bychom ho pro spuštění jako dříve. V tomto případě ale chceme projít všechny datové body a vypočítat plnou kernel matici. Pro každý pár datových vektorů a vytvoříme jiný overlap obvod. Proto musíme optimalizovat náš obvod pro každý pár datových bodů. Kroky 2 a 3 by tedy byly prováděny společně v rámci více iterací.
Níže uvedená buňka kódu provádí přesně stejný postup jako dříve pro jeden pár datových bodů. Tentokrát je jednoduše spuštěna uvnitř dvou smyček for a na konci je přidán řádek kernel_matrix[x_1,x_2] = ... pro ukládání výsledků každého výpočtu. Všimni si, že jsme využili symetrie kernel matice ke snížení počtu výpočtů na 1/2. Diagonální prvky jsme jednoduše nastavili na 1, jak by tomu mělo být v nepřítomnosti šumu. V závislosti na tvé implementaci a požadované přesnosti bys mohl diagonální prvky využít také k odhadu šumu nebo jeho poznání pro účely mitigace chyb.
Jakmile je kernel matice plně naplněna, zopakujeme postup pro testovací data a naplníme test_matrix. Ta je ve skutečnosti také kernel maticí; dáváme jí jen jiný název, abychom je od sebe odlišili.
# To use a simulator
from qiskit.primitives import StatevectorSampler
# Remember to insert your token in the QiskitRuntimeService constructor to use real quantum computers
# service = QiskitRuntimeService()
# backend = service.least_busy(
# operational=True, simulator=False, min_num_qubits=fm.num_qubits
# )
num_shots = 10000
# Evaluate the problem using state vector-based primitives from Qiskit.
sampler = StatevectorSampler()
for x1 in range(0, train_size):
for x2 in range(x1 + 1, train_size):
unitary1 = fm.assign_parameters(list(X_train[x1]) + [np.pi / 2])
unitary2 = fm.assign_parameters(list(X_train[x2]) + [np.pi / 2])
# Create the overlap circuit
overlap_circ = unitary_overlap(unitary1, unitary2)
overlap_circ.measure_all()
# These lines run the qiskit sampler primitive.
counts = (
sampler.run([overlap_circ], shots=num_shots)
.result()[0]
.data.meas.get_int_counts()
)
# Assign the probability of the 0 state to the kernel matrix, and the transposed element (since this is an inner product)
kernel_matrix[x1, x2] = counts.get(0, 0.0) / num_shots
kernel_matrix[x2, x1] = counts.get(0, 0.0) / num_shots
# Fill in on-diagonal elements with 1, again, since this is an inner-product corresponding to probability (or alter the code to check these entries and verify they yield 1)
kernel_matrix[x1, x1] = 1
print("training done")
# Similar process to above, but for testing data.
for x1 in range(0, test_size):
for x2 in range(0, train_size):
unitary1 = fm.assign_parameters(list(X_test[x1]) + [np.pi / 2])
unitary2 = fm.assign_parameters(list(X_train[x2]) + [np.pi / 2])
# Create the overlap circuit
overlap_circ = unitary_overlap(unitary1, unitary2)
overlap_circ.measure_all()
counts = (
sampler.run([overlap_circ], shots=num_shots)
.result()[0]
.data.meas.get_int_counts()
)
test_matrix[x1, x2] = counts.get(0, 0.0) / num_shots
print("test matrix done")
training done
test matrix done
Krok 4: Post-processing, vrať výsledek v klasickém formátu
Nyní, když máme kernel matici a podobně formátovanou test_matrix z kvantových kernel metod, můžeme použít klasické algoritmy strojového učení k vytváření predikcí o testovacích datech a ověření jejich přesnosti. Začneme importem sklearn.svc z knihovny Scikit-Learn, klasifikátoru podpůrných vektorů (SVC). Musíme specifikovat, že chceme, aby SVC použil naše předpočítané kernel pomocí kernel = precomputed.
# import a support vector classifier from a classical ML package.
from sklearn.svm import SVC
# Specify that you want to use a pre-computed kernel matrix
qml_svc = SVC(kernel="precomputed")
Pomocí SVC.fit nyní můžeme vložit kernel matici a trénovací štítky a získat fit. SVC.score pak ohodnotí naše testovací data oproti tomuto fitu pomocí test_matrix a vrátí naši přesnost.
# Feed in the pre-computed matrix and the labels of the training data. The classical algorithm gives you a fit.
qml_svc.fit(kernel_matrix, train_labels)
# Now use the .score to test your data, using the matrix of test data, and test labels as your inputs.
qml_score_precomputed_kernel = qml_svc.score(test_matrix, test_labels)
print(f"Precomputed kernel classification test score: {qml_score_precomputed_kernel}")
Precomputed kernel classification test score: 1.0
Vidíme, že přesnost našeho natrénovaného modelu byla 100 %. To je skvělé a ukazuje to, že QKE může fungovat. To je ale velmi odlišné od kvantové výhody. Klasické kernely by pravděpodobně dokázaly tento klasifikační problém vyřešit se 100% přesností také. Stále zbývá mnoho práce na charakterizaci různých typů dat a datových vztahů, abychom zjistili, kde budou kvantové kernely v současné éře utility nejužitečnější. Necháváme na tobě, abys upravil části tohoto workflow a zkoumal účinnost různých kvantových feature map. Zde je několik věcí k zamyšlení:
- Jak robustní je přesnost? Platí pro široké typy dat, nebo jen pro tato konkrétní trénovací data?
- Jaká struktura v tvých datech tě vede k podezření, že kvantová feature map je užitečná?
- Jak je přesnost ovlivněna zvýšením/snížením množství trénovacích dat?
- Jaké feature mapy můžeš použít a jak se výsledky liší v závislosti na feature mapách?
- Jak jsou přesnost a doba běhu ovlivněny zvyšováním počtu příznaků?
- Které trendy, pokud vůbec nějaké, očekáváš, že budou platit na skutečných kvantových počítačích?
Škálování na více příznaků a qubitů
V této části zopakujeme výpočet jediného prvku matice, ale pro mnohem větší počet příznaků, čímž nastíníme cestu ke škálování směrem k praktické využitelnosti. Omezení na jediný prvek matice je zvoleno proto, abychom mohli celý postup ukázat bez nadměrného čerpání přiděleného času na kvantových počítačích.
Krok 1: Přeměň klasické vstupy na kvantový problém
Budeme předpokládat, že máme jako výchozí bod datovou sadu, ve které má každý datový bod 42 příznaků. Stejně jako v prvním příkladu vypočítáme jediný prvek kernel matice, k čemuž jsou potřeba dva datové body. Oba níže uvedené body mají 42 příznaků a jednu kategoriální proměnnou ().
# Two mock data points, including category labels, as in training
large_data = [
[
-0.028,
-1.49,
-1.698,
0.107,
-1.536,
-1.538,
-1.356,
-1.514,
-0.109,
-1.8,
-0.122,
-1.651,
-1.955,
-0.123,
-1.732,
0.091,
-0.048,
-0.128,
-0.026,
0.082,
-1.263,
0.065,
0.004,
-0.055,
-0.08,
-0.173,
-1.734,
-0.39,
-1.451,
0.078,
-1.578,
-0.025,
-0.184,
-0.119,
-1.336,
0.055,
-0.204,
-1.578,
0.132,
-0.121,
-1.599,
-0.187,
-1,
],
[
-1.414,
-1.439,
-1.606,
0.246,
-1.673,
0.002,
-1.317,
-1.262,
-0.178,
-1.814,
0.013,
-1.619,
-1.86,
-0.25,
-0.212,
-0.214,
-0.033,
0.071,
-0.11,
-1.607,
0.441,
-0.143,
-0.009,
-1.655,
-1.579,
0.381,
-1.86,
-0.079,
-0.088,
-0.058,
-1.481,
-0.064,
-0.065,
-1.507,
0.177,
-0.131,
-0.153,
0.07,
-1.627,
0.593,
-1.547,
-0.16,
-1,
],
]
train_data = [large_data[0][:-1], large_data[1][:-1]]
Vzpomeň si, že zz_feature_map generoval poměrně hluboké obvod v případě relativně malého počtu příznaků (14 příznaků). Jak zvyšujeme počet příznaků, musíme hloubku obvodu pečlivě sledovat. Abychom to ilustrovali, nejprve zkusíme použít zz_feature_map a zkontrolujeme hloubku výsledného obvod.
from qiskit.circuit.library import zz_feature_map
fm = zz_feature_map(
feature_dimension=np.shape(train_data)[1], entanglement="linear", reps=1
)
unitary1 = fm.assign_parameters(train_data[0])
unitary2 = fm.assign_parameters(train_data[1])
from qiskit.circuit.library import unitary_overlap
overlap_circ = unitary_overlap(unitary1, unitary2)
overlap_circ.measure_all()
print("circuit depth = ", overlap_circ.decompose(reps=2).depth())
print(
"two-qubit depth",
overlap_circ.decompose().depth(lambda instr: len(instr.qubits) > 1),
)
# overlap_circ.draw("mpl", scale=0.6, style="iqp")
circuit depth = 251
two-qubit depth 165
Jak bylo popsáno dříve, přesné určení toho, kdy je hloubka příliš velká, je složité. Ale dvojqubitová hloubka větší než 100, dokonce i před transpilací, je nepřijatelná. Proto jsou v této lekci tak zdůrazňovány vlastní feature mapy. Pokud víš něco o struktuře celé své datové sady, měl bys navrhnout entanglement mapu s ohledem na tuto strukturu. Zde, jelikož počítáme pouze skalární součin mezi dvěma takovými datovými body, jsme upřednostnili nízkou hloubku obvodu před jakýmkoli detailním zvažováním datové struktury.
from qiskit.circuit import Parameter, ParameterVector, QuantumCircuit
# Prepare feature map for computing overlap
entangler_map = [
[3, 4],
[2, 5],
[1, 4],
[2, 3],
[4, 6],
[7, 9],
[10, 11],
[9, 12],
[8, 11],
[9, 10],
[11, 13],
[14, 16],
[17, 18],
[16, 19],
[15, 18],
[16, 17],
[18, 20],
]
# Use the entangler map above to build a feature map
num_features = np.shape(train_data)[1]
num_qubits = int(num_features / 2)
fm = QuantumCircuit(num_qubits)
training_param = Parameter("θ")
feature_params = ParameterVector("x", num_qubits * 2)
fm.ry(training_param, fm.qubits)
for cz in entangler_map:
fm.cz(cz[0], cz[1])
for i in range(num_qubits):
fm.rz(-2 * feature_params[2 * i + 1], i)
fm.rx(-2 * feature_params[2 * i], i)
from qiskit.circuit.library import unitary_overlap
# Assign features of each data point to a unitary, an instance of the general feature map.
unitary1 = fm.assign_parameters(list(train_data[0]) + [np.pi / 2])
unitary2 = fm.assign_parameters(list(train_data[1]) + [np.pi / 2])
# Create the overlap circuit
overlap_circ = unitary_overlap(unitary1, unitary2)
overlap_circ.measure_all()
Zatím se nebudeme obtěžovat kontrolou hloubek, protože skutečně důležitá je transpilovaná dvojqubitová hloubka.
Krok 2: Optimalizace problému pro kvantové spuštění
Začneme výběrem nejméně vytíženého backendu a poté optimalizujeme náš obvod pro spuštění na tomto backendu.
# Import needed packages
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
# Get the least busy backend
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=fm.num_qubits
)
print(backend)
<IBMBackend('ibm_brisbane')>
U malých úloh preset pass manager často spolehlivě vrátí stejný obvod se stejnou hloubkou. Ale u velmi velkých a složitých obvodů může pass manager pokaždé vrátit jiný transpilovaný obvod. Je to proto, že používá heuristiky, a protože velmi velké obvody mají komplikovaný prostor možných optimalizací. Často je užitečné transpilovat několikrát a vzít nejplošší obvod. Tím vznikne pouze klasická režie a výsledky z kvantového počítače se mohou podstatně zlepšit.
Zde transpilujeme obvod unitárního překryvu 20krát a podíváme se na hloubky získaných obvodů.
# Apply level 3 optimization to our overlap circuit
transpiled_qcs = []
transpiled_depths = []
transpiled_twoqubit_depths = []
for i in range(1, 20):
pm = generate_preset_pass_manager(optimization_level=3, backend=backend)
overlap_ibm = pm.run(overlap_circ)
transpiled_qcs.append(overlap_ibm)
transpiled_depths.append(overlap_ibm.decompose().depth())
transpiled_twoqubit_depths.append(
overlap_ibm.decompose().depth(lambda instr: len(instr.qubits) > 1)
)
print("circuit depth = ", overlap_ibm.decompose().depth())
circuit depth = 61
print(transpiled_depths)
print(transpiled_twoqubit_depths)
[61, 60, 60, 69, 60, 60, 60, 65, 60, 60, 69, 61, 77, 77, 65, 60, 60, 77, 61]
[13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13]
Zde vidíš, že při různých transpilačních průchodech dochází k určité variabilitě celkové hloubky hradel. Náš obvod ještě není dostatečně hluboký/široký, abychom pozorovali variabilitu v transpilovaných hloubkách dvou-qubitových operací. Použijeme transpiled_qcs[1], který má hloubku 60 – o něco nižší než hloubka nejhlubšího získaného obvodu, která byla 77.
overlap_ibm = transpiled_qcs[1]
Krok 3: Spuštění pomocí Qiskit Runtime Primitives
S tím, jak se přibližujeme k praktickému využití, simulátory přestanou být užitečné. Zde je uvedena pouze syntaxe pro skutečné kvantové počítače.
# Run on ibm_osaka, 7-12-24, required 22 sec.
# Import our runtime primitive
from qiskit_ibm_runtime import SamplerV2 as Sampler
# Open a Runtime session:
session = Session(backend=backend)
num_shots = 10000
# Use sampler and get the counts
sampler = Sampler(mode=session)
options = sampler.options
options.dynamical_decoupling.enable = True
options.twirling.enable_gates = True
counts = (
sampler.run([overlap_ibm], shots=num_shots).result()[0].data.meas.get_int_counts()
)
# Close session after done
session.close()
Krok 4: Post-processing, vrácení výsledku v klasickém formátu
Jak bylo popsáno v úvodu, nejužitečnějším měřením je zde pravděpodobnost naměření nulového stavu .
counts.get(0, 0.0) / num_shots
0.0138
Tento postup pro jeden prvek kernel matice by mohl být zopakován pro další páry dat z tvé sady, čímž by se získala úplná kernel matice. Rozměr kernel matice je dán počtem bodů v trénovacích datech, nikoli počtem příznaků. Výpočetní náklady na manipulaci s kernel maticí za účelem vytvoření prediktivního modelu tedy nerostou s počtem příznaků ani qubitů. I u relativně malých datových sad s velkým počtem příznaků by data stále musela odpovídat feature map, která umožní efektivní klasifikaci.
Škálování a budoucí práce
Metoda kernel vyžaduje, abychom co nejpřesněji měřili stav . Chyby hradlo a chyby čtení však způsobují nenulovou pravděpodobnost , že libovolný qubit bude chybně naměřen ve stavu . I se zjednodušujícím předpokladem, že pravděpodobnost by měla být , je pro mnoho příznaků zakódovaných na bitech pravděpodobnost správného naměření všech bitů jako snížena na . S rostoucím se tato metoda stává stále méně spolehlivou. Překonání této obtíže a škálování odhadu kernelu na stále více příznaků je oblastí současného výzkumu. Chceš-li se o tomto problému dozvědět více, podívej se na práci autorů Thanasilp, Wang, Cerezo a Holmes. Doporučujeme ti prozkoumat, co lze s dnešními kvantovými počítači dělat, a zároveň se těšit na to, co bude možné v éře opravy chyb.
Opakování
Výpočet kvantového kernelu zahrnuje:
- výpočet prvků kernel matice pomocí párů trénovacích datových bodů
- zakódování dat a jejich mapování prostřednictvím feature map
- optimalizaci obvodu pro spuštění na skutečných kvantových počítačích / backendech
Kvantový kernel pak může být použit v klasických algoritmech strojového učení, jak je ukázáno v této lekci.
Při používání kvantových kernelů mej na paměti několik klíčových věcí:
- Je pravděpodobné, že datová sada bude profitovat z metod kvantového kernelu?
- Zkus různé feature mapy a různá schémata provázání.
- Je hloubka obvodu přijatelná?
- Zkus spustit pass manager několikrát a použij obvod s nejmenší hloubkou, jaký získáš.
Metody kvantového kernelu jsou potenciálně výkonné nástroje při správném souladu mezi datovými sadami s kvantově vhodnými příznaky a vhodnou kvantovou feature map. Pro lepší pochopení toho, kde jsou kvantové kernely pravděpodobně užitečné, doporučujeme přečíst Liu, Arunachalam & Temme (2021).