Zum Hauptinhalt springen

Datenkodierung

Einführung und Notation

Um einen Quantenalgorithmus zu verwenden, müssen klassische Daten irgendwie in einen Quantenschaltkreis überführt werden. Dies wird üblicherweise als Daten-Kodierung bezeichnet, manchmal aber auch als Daten-Laden. Erinnere dich aus früheren Lektionen an das Konzept des Feature-Mappings – eine Abbildung von Datenmerkmalen aus einem Raum in einen anderen. Das bloße Übertragen klassischer Daten auf einen Quantencomputer ist eine Art Abbildung und könnte als Feature-Mapping bezeichnet werden. In der Praxis beinhalten die in Qiskit eingebauten Feature-Mappings (wie z_Feature Map und ZZ Feature Map) typischerweise Rotationsschichten und Verschränkungsschichten, die den Zustand auf viele Dimensionen im Hilbertraum ausdehnen. Dieser Kodierungsprozess ist ein wesentlicher Bestandteil von Quantenmaschinellen-Lern-Algorithmen und beeinflusst direkt deren Berechnungsmöglichkeiten.

Einige der unten vorgestellten Kodierungstechniken lassen sich effizient klassisch simulieren; das ist besonders leicht bei Kodierungsmethoden zu erkennen, die Produktzustände erzeugen (d. h. keine Qubits verschränken). Denke außerdem daran, dass Quanten-Nützlichkeit am wahrscheinlichsten dort zu finden ist, wo die quantenartige Komplexität des Datensatzes gut zur Kodierungsmethode passt. Es ist daher sehr wahrscheinlich, dass du eigene Kodierungsschaltkreise entwickeln wirst. Hier zeigen wir eine breite Auswahl möglicher Kodierungsstrategien, damit du sie vergleichen und Unterschiede erkennen kannst, und siehst, was möglich ist. Es gibt einige sehr allgemeine Aussagen über die Nützlichkeit von Kodierungstechniken. So ist es beispielsweise viel wahrscheinlicher, dass efficient_su2 (siehe unten) mit einem vollständigen Verschränkungsschema Quantenmerkmale von Daten erfasst als Methoden, die Produktzustände liefern (wie z_feature_map). Das bedeutet jedoch nicht, dass efficient_su2 ausreichend oder ausreichend gut auf deinen Datensatz abgestimmt ist, um eine Quantenbeschleunigung zu erzielen. Das erfordert eine sorgfältige Betrachtung der Struktur der zu modellierenden oder klassifizierenden Daten. Es gibt auch einen Abwägungsprozess bezüglich der Schaltkreistiefe, da viele Feature-Maps, die die Qubits in einem Schaltkreis vollständig verschränken, sehr tiefe Schaltkreise erzeugen – zu tief, um auf heutigen Quantencomputern brauchbare Ergebnisse zu erhalten.

Notation

Ein Datensatz ist eine Menge von MM Datenvektoren: X={x(j)j[M]}\text{X} = \{\vec{x}^{(j)}\,|\,j\in [M]\}, wobei jeder Vektor NN-dimensional ist, d. h. x(j)=(x1(j),,xN(j))RN\vec{x}^{(j)}=(\vec{x}^{(j)}_1,\ldots,\vec{x}^{(j)}_N)\in\mathbb{R}^N. Dies lässt sich auf komplexe Datenmerkmale erweitern. In dieser Lektion verwenden wir gelegentlich diese Notationen für die vollständige Menge (X)(\text{X}) und ihre spezifischen Elemente wie x(j)\vec{x}^{(j)}. Meistens werden wir uns jedoch auf das Laden eines einzelnen Vektors aus unserem Datensatz beziehen und oft einfach von einem einzelnen Vektor aus NN Merkmalen als x\vec{x} sprechen.

Außerdem ist es üblich, das Symbol Φ(x)\Phi(\vec{x}) für das Feature-Mapping Φ\Phi des Datenvektors x\vec{x} zu verwenden. Im Quantencomputing ist es insbesondere üblich, Abbildungen mit U(x)U(\vec{x}) zu bezeichnen – eine Notation, die den unitären Charakter dieser Operationen betont. Man könnte korrekterweise dasselbe Symbol für beides verwenden; beides sind Feature-Mappings. Im gesamten Kurs verwenden wir:

  • Φ(x)\Phi(\vec{x}), wenn wir allgemein über Feature-Mappings im Maschinellen Lernen sprechen, und
  • U(x)U(\vec{x}), wenn wir über Schaltkreisimplementierungen von Feature-Mappings sprechen.

Normalisierung und Informationsverlust

Im klassischen Maschinellen Lernen werden Trainings-Datenmerkmale häufig „normalisiert" oder umskaliert, was oft die Modellleistung verbessert. Eine gängige Methode ist die Min-Max-Normalisierung oder Standardisierung. Bei der Min-Max-Normalisierung werden Merkmalsspalten der Datenmatrix X\text{X} (z. B. Merkmal kk) normalisiert:

xk(i)=xk(i)min{xk(j)x(j)[X]}max{xk(j)x(j)[X]}min{xk(j)x(j)[X]}x^{'(i)}_k = \frac{x^{(i)}_k - \text{min}\{x^{(j)}_k\,|\,\vec{x}^{(j)}\in [\text{X}]\}}{\text{max}\{x^{(j)}_k\,|\,\vec{x}^{(j)}\in [\text{X}]\}-\text{min}\{x^{(j)}_k\,|\,\vec{x}^{(j)}\in [\text{X}]\}}

wobei min und max das Minimum bzw. Maximum von Merkmal kk über die MM Datenvektoren im Datensatz X\text{X} bezeichnen. Alle Merkmalswerte liegen dann im Einheitsintervall: xk(i)[0,1]x^{'(i)}_k \in [0,1] für alle i[M]i\in [M], k[N]k\in[N].

Normalisierung ist auch ein fundamentales Konzept in der Quantenmechanik und im Quantencomputing, unterscheidet sich jedoch leicht von der Min-Max-Normalisierung. Normalisierung in der Quantenmechanik erfordert, dass die Länge (im Kontext des Quantencomputings die 2-Norm) eines Zustandsvektors ψ|\psi\rangle gleich eins ist: ψ=ψψ=1\|\psi\|=\sqrt{\langle\psi|\psi\rangle} = 1, um sicherzustellen, dass die Messprobabilitäten sich zu 1 addieren. Der Zustand wird durch Division durch die 2-Norm normalisiert; das heißt, durch Umskalierung

ψψ1ψ|\psi\rangle\rightarrow\|\psi\|^{-1}|\psi\rangle

Im Quantencomputing und in der Quantenmechanik ist dies keine von Menschen auf die Daten aufgezwungene Normalisierung, sondern eine fundamentale Eigenschaft von Quantenzuständen. Je nach deinem Kodierungsschema kann diese Bedingung beeinflussen, wie deine Daten umskaliert werden. Bei der Amplitudenkodierung (siehe unten) beispielsweise wird der Datenvektor normalisiert x(j)=1\vert\vec{x}^{(j)}\vert = 1, wie es die Quantenmechanik erfordert, und das beeinflusst die Skalierung der kodierten Daten. Bei der Phasenkodierung wird empfohlen, Daten auf xi(j)(0,2π]\vec{x}^{(j)}_i \in (0,2\pi] umzuskalieren, um Informationsverluste durch den Modulo-2π2\pi-Effekt bei der Kodierung in einen Qubit-Phasenwinkel zu vermeiden[1,2].

Kodierungsmethoden

In den nächsten Abschnitten beziehen wir uns auf einen kleinen klassischen Beispieldatensatz Xex\text{X}_\text{ex}, bestehend aus M=5M=5 Datenvektoren mit jeweils N=3N=3 Merkmalen:

Xex={(4,8,5),(9,8,6),(2,9,2),(5,7,0),(3,7,5)}\text{X}_{\text{ex}}=\{(4,8,5),(9,8,6),(2,9,2),(5,7,0),(3,7,5)\}

In der oben eingeführten Notation könnte man beispielsweise sagen, das 1te1^\text{te} Merkmal des 4ten4^\text{ten} Datenvektors in unserem Datensatz Xex\text{X}_{\text{ex}} ist x1(4)=5\vec{x}^{(4)}_1 = 5.

Basiskodierung

Basiskodierung kodiert einen klassischen PP-Bit-String in einen Rechenbasisszustand eines PP-Qubit-Systems. Nehmen wir zum Beispiel x3(1)=5=0(23)+1(22)+0(21)+1(20)\vec{x}^{(1)}_3 = 5 = 0(2^3)+1(2^2)+0(2^1)+1(2^0). Dies lässt sich als 44-Bit-String (0101)(0101) darstellen und durch ein 44-Qubit-System als Quantenzustand 0101|0101\rangle. Allgemeiner gilt für einen PP-Bit-String: xk(j)=(b1,b2,...,bP)\vec{x}^{(j)}_k = (b_1, b_2, ... , b_P) – der entsprechende PP-Qubit-Zustand ist xk(j)=b1,b2,...,bP|x^{(j)}_k\rangle = | b_1, b_2, ... , b_P \rangle mit bn{0,1}b_n \in \{0,1\} für n=1,,Pn = 1 , \dots , P. Beachte, dass dies nur für ein einzelnes Merkmal gilt.

Basiskodierung im Quantencomputing repräsentiert jedes klassische Bit als separates Qubit und bildet die binäre Darstellung von Daten direkt auf Quantenzustände in der Rechenbasis ab. Wenn mehrere Merkmale kodiert werden müssen, wird jedes Merkmal zunächst in seine binäre Form umgewandelt und dann einer eigenen Qubit-Gruppe zugewiesen – eine Gruppe pro Merkmal – wobei jedes Qubit ein Bit in der Binärdarstellung dieses Merkmals widerspiegelt.

Als Beispiel kodieren wir den Vektor (5, 7, 0).

Angenommen, alle Merkmale werden in vier Bits gespeichert (mehr als nötig, aber genug, um jede einstellige ganze Zahl in Basis 10 darzustellen):

5 → binär 0101

7 → binär 0111

0 → binär 0000

Diese Bit-Strings werden drei Gruppen von je vier Qubits zugewiesen, sodass der gesamte 12-Qubit-Basiszustand lautet:

010101110000∣0101 0111 0000⟩

Dabei repräsentieren die ersten vier Qubits das erste Merkmal, die nächsten vier Qubits das zweite Merkmal und die letzten vier Qubits das dritte Merkmal. Der folgende Code konvertiert den Datenvektor (5,7,0) in einen Quantenzustand und ist so verallgemeinert, dass er dies auch für andere einstellige Merkmale tut.

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit
from qiskit import QuantumCircuit

# Data point to encode
x = 5 # binary: 0101
y = 7 # binary: 0111
z = 0 # binary: 0000

# Convert each to 4-bit binary list
x_bits = [int(b) for b in format(x, "04b")] # [0,1,0,1]
y_bits = [int(b) for b in format(y, "04b")] # [0,1,1,1]
z_bits = [int(b) for b in format(z, "04b")] # [0,0,0,0]

# Combine all bits
all_bits = x_bits + y_bits + z_bits # [0,1,0,1,0,1,1,1,0,0,0,0]

# Initialize a 12-qubit quantum circuit
qc = QuantumCircuit(12)

# Apply x-gates where the bit is 1
for idx, bit in enumerate(all_bits):
if bit == 1:
qc.x(idx)

qc.draw("mpl")

Output of the previous code cell

Überprüfe dein Verständnis

Lies die folgende Frage, überlege dir deine Antwort, und klicke dann auf das Dreieck, um die Lösung anzuzeigen.

Schreibe Code, um den ersten Vektor in unserem Beispieldatensatz Xex\text{X}_{\text{ex}} zu kodieren:

x(1)=(4,8,5)\vec{x}^{(1)}=(4,8,5)

mithilfe von Basiskodierung.

Antwort:

import math
from qiskit import QuantumCircuit

# Data point to encode
x = 4 # binary: 0100
y = 8 # binary: 1000
z = 5 # binary: 0101

# Convert each to 4-bit binary list
x_bits = [int(b) for b in format(x, '04b')] # [0,1,0,0]
y_bits = [int(b) for b in format(y, '04b')] # [1,0,0,0]
z_bits = [int(b) for b in format(z, '04b')] # [0,1,0,1]

# Combine all bits
all_bits = x_bits + y_bits + z_bits # [0,1,0,0,1,0,0,0,0,1,0,1]

# Initialize a 12-qubit quantum circuit
qc = QuantumCircuit(12)

# Apply x-gates where the bit is 1
for idx, bit in enumerate(all_bits):
if bit == 1:
qc.x(idx)

qc.draw('mpl')

Amplitudenkodierung

Amplitudenkodierung kodiert Daten in die Amplituden eines Quantenzustands. Sie repräsentiert einen normalisierten klassischen NN-dimensionalen Datenvektor x(j)\vec{x}^{(j)} als die Amplituden eines nn-Qubit-Quantenzustands ψx|\psi_x\rangle:

ψx(j)=1αi=1Nxi(j)i|\psi^{(j)}_x\rangle = \frac{1}{\alpha}\sum_{i=1}^N x^{(j)}_i |i\rangle

wobei NN dieselbe Dimension der Datenvektoren wie zuvor ist, xi(j)\vec{x}^{(j)}_i das ii-te Element von x(j)\vec{x}^{(j)} und i|i\rangle der ii-te Rechenbasiszustand. Hier ist α\alpha eine Normalisierungskonstante, die aus den zu kodierenden Daten bestimmt wird. Dies ist die durch die Quantenmechanik aufgezwungene Normalisierungsbedingung:

i=1Nxi(j)2=α2.\sum_{i=1}^N \left|x^{(j)}_i\right|^2 = \left|\alpha\right|^2.

Im Allgemeinen ist dies eine andere Bedingung als die Min-Max-Normalisierung, die für jedes Merkmal über alle Datenvektoren verwendet wird. Wie genau damit umgegangen wird, hängt von deinem Problem ab. An der quantenmechanischen Normalisierungsbedingung oben führt jedoch kein Weg vorbei.

Bei der Amplitudenkodierung wird jedes Merkmal eines Datenvektors als Amplitude eines anderen Quantenzustands gespeichert. Da ein System aus nn Qubits 2n2^n Amplituden bereitstellt, erfordert die Amplitudenkodierung von NN Merkmalen nlog2(N)n \ge \mathrm{log}_2(N) Qubits.

Als Beispiel kodieren wir den ersten Vektor in unserem Beispieldatensatz Xex\text{X}_\text{ex}, x(1)=(4,8,5)\vec{x}^{(1)} = (4,8,5), mithilfe von Amplitudenkodierung. Durch Normalisierung des resultierenden Vektors erhalten wir:

i=1Nxi(1)2=42+82+52=105=α2α=105\sum_{i=1}^N \left|x^{(1)}_i\right|^2 = 4^2+8^2+5^2 = 105 = \left|\alpha\right|^2 \rightarrow \alpha = \sqrt{105}

und der resultierende 2-Qubit-Quantenzustand wäre:

ψ(x(1))=1105(400+801+510+011)|\psi(\vec{x}^{(1)})\rangle = \frac{1}{\sqrt{105}}(4|00\rangle+8|01\rangle+5|10\rangle+0|11\rangle)

Im obigen Beispiel ist die Anzahl der Merkmale im Vektor N=3N=3 keine Potenz von 2. Wenn NN keine Potenz von 2 ist, wählen wir einfach eine Anzahl von Qubits nn so, dass 2nN2^n\geq N gilt, und füllen den Amplitudenvektor mit uninformativen Konstanten auf (hier eine Null).

Wie bei der Basiskodierung können wir in Qiskit die initialize-Funktion verwenden, um den Zustand vorzubereiten, sobald wir berechnet haben, welcher Zustand unseren Datensatz kodieren wird:

import math

desired_state = [
1 / math.sqrt(105) * 4,
1 / math.sqrt(105) * 8,
1 / math.sqrt(105) * 5,
1 / math.sqrt(105) * 0,
]

qc = QuantumCircuit(2)
qc.initialize(desired_state, [0, 1])

qc.decompose(reps=5).draw(output="mpl")

Output of the previous code cell

Ein Vorteil der Amplitudenkodierung ist der bereits erwähnte Bedarf von nur log2(N)\mathrm{log}_2(N) Qubits zur Kodierung. Allerdings müssen nachfolgende Algorithmen auf den Amplituden eines Quantenzustands operieren, und Methoden zur Vorbereitung und Messung der Quantenzustände neigen dazu, nicht effizient zu sein.

Überprüfe dein Verständnis

Lies die folgenden Fragen, überlege dir deine Antworten, und klicke dann auf die Dreiecke, um die Lösungen anzuzeigen.

Schreibe den normalisierten Zustand für die Kodierung des folgenden Vektors (bestehend aus zwei Vektoren unseres Beispieldatensatzes) auf:

x=(9,8,6,2,9,2)\vec{x}=(9,8,6,2,9,2)

mithilfe von Amplitudenkodierung.

Antwort:

Um 6 Zahlen zu kodieren, benötigen wir mindestens 6 verfügbare Zustände, auf deren Amplituden wir kodieren können. Dafür sind 3 Qubits erforderlich. Mit einem unbekannten Normalisierungsfaktor α\alpha lässt sich dies schreiben als:

ψ=α(9000+8001+6010+2011+9100+2101+0110+0111)|\psi\rangle = \alpha(9|000\rangle+8|001\rangle+6|010\rangle+2|011\rangle+9|100\rangle+2|101\rangle+0|110\rangle+0|111\rangle)

Beachte, dass

ψψ=α2×(92+82+62+22+92+22+02+02)=α2×(270)=1α=1270\langle \psi|\psi\rangle = |\alpha|^2\times(9^2+8^2+6^2+2^2+9^2+2^2+0^2+0^2) = |\alpha|^2\times(270)=1 \rightarrow \alpha = \frac{1}{\sqrt{270}}

Somit gilt schließlich:

ψ=1270(9000+8001+6010+2011+9100+2101+0110+0111)|\psi\rangle = \frac{1}{\sqrt{270}}(9|000\rangle+8|001\rangle+6|010\rangle+2|011\rangle+9|100\rangle+2|101\rangle+0|110\rangle+0|111\rangle)

Für denselben Datenvektor x=(9,8,6,2,9,2)\vec{x}=(9,8,6,2,9,2): Schreibe Code, um einen Schaltkreis zu erstellen, der diese Datenmerkmale mithilfe von Amplitudenkodierung lädt.

Antwort:

desired_state = [
9 / math.sqrt(270),
8 / math.sqrt(270),
6 / math.sqrt(270),
2 / math.sqrt(270),
9 / math.sqrt(270),
2 / math.sqrt(270),
0,
0,
]

print(desired_state)

qc = QuantumCircuit(3)
qc.initialize(desired_state, [0, 1, 2])
qc.decompose(reps=8).draw(output="mpl")

[0.5477225575051662, 0.48686449556014766, 0.36514837167011077, 0.12171612389003691, 0.5477225575051662, 0.12171612389003691, 0, 0]

"Output of the previous code cell"

Möglicherweise musst du mit sehr großen Datenvektoren umgehen. Betrachte den Vektor

x=(4,8,5,9,8,6,2,9,2,5,7,0,3,7,5).\vec{x}=(4,8,5,9,8,6,2,9,2,5,7,0,3,7,5).

Schreibe Code, um die Normalisierung zu automatisieren und einen Quantenschaltkreis für die Amplitudenkodierung zu erzeugen.

Antwort:

Es gibt viele mögliche Antworten. Hier ist Code, der einige Zwischenschritte ausgibt:

import numpy as np
from math import sqrt

init_list = [4, 8, 5, 9, 8, 6, 2, 9, 2, 5, 7, 0, 3, 7, 5]
qubits = round(np.log(len(init_list)) / np.log(2) + 0.4999999999)
need_length = 2**qubits
pad = need_length - len(init_list)
for i in range(0, pad):
init_list.append(0)

init_array = np.array(init_list) # Unnormalized data vector
length = sqrt(
sum(init_array[i] ** 2 for i in range(0, len(init_array)))
) # Vector length
norm_array = init_array / length # Normalized array
print("Normalized array:")
print(norm_array)
print()

qubit_numbers = []
for i in range(0, qubits):
qubit_numbers.append(i)
print(qubit_numbers)

qc = QuantumCircuit(qubits)
qc.initialize(norm_array, qubit_numbers)
qc.decompose(reps=7).draw(output="mpl")

Normalized array: [0.17342199 0.34684399 0.21677749 0.39019949 0.34684399 0.26013299 0.086711 0.39019949 0.086711 0.21677749 0.30348849 0. 0.1300665 0.30348849 0.21677749 0. ]

[0, 1, 2, 3]

"Output of the previous code cell"

Siehst du Vorteile der Amplitudenkodierung gegenüber der Basiskodierung? Falls ja, erkläre sie.

Antwort:

Es kann mehrere Antworten geben. Eine Antwort ist, dass die Amplitudenkodierung – aufgrund der festen Reihenfolge der Basiszustände – die Ordnung der kodierten Zahlen erhält. Sie wird oft auch dichter kodiert.

Ein Vorteil der Amplitudenkodierung ist, dass für einen NN-dimensionalen (NN-merkmaligen) Datenvektor xx\vec{x}\rightarrow|\vec{x}\rangle nur log2(N)\log_2(N) Qubits benötigt werden. Die Amplitudenkodierung ist jedoch generell ein ineffizientes Verfahren, das eine beliebige Zustandspräparation erfordert, die exponentiell in der Anzahl der CNOT-Gates ist. Anders ausgedrückt: Die Zustandspräparation hat eine polynomielle Laufzeitkomplexität von O(N)\mathcal O(N) in der Anzahl der Dimensionen, wobei N=2nN = 2^n und nn die Anzahl der Qubits ist. Amplitudenkodierung „bietet eine exponentielle Einsparung im Speicherbedarf auf Kosten einer exponentiellen Erhöhung der Zeit"[3]; jedoch sind Laufzeitsteigerungen auf O(logN)\mathcal O(\log N) in bestimmten Fällen erreichbar[4]. Für eine durchgehende Quantenbeschleunigung muss die Laufzeitkomplexität des Datenladens berücksichtigt werden.

Winkelkodierung

Winkelkodierung ist in vielen QML-Modellen, die Pauli-Feature-Maps verwenden – wie Quanten-Support-Vektor-Maschinen (QSVMs) und variationelle Quantenschaltkreise (VQCs) – von Interesse. Winkelkodierung ist eng verwandt mit Phasenkodierung und dichter Winkelkodierung, die weiter unten vorgestellt werden. Hier verwenden wir „Winkelkodierung" für eine Rotation in θ\theta, also eine Rotation weg von der zz-Achse, die beispielsweise durch ein RXR_X- oder ein RYR_Y-Gate erreicht wird[1,3]. Grundsätzlich kann man Daten in jeder Rotation oder Kombination von Rotationen kodieren. Aber RYR_Y ist in der Literatur weit verbreitet, weshalb wir es hier besonders betonen.

Wenn es auf ein einzelnes Qubit angewendet wird, verleiht die Winkelkodierung eine Y-Achsen-Rotation proportional zum Datenwert. Betrachte die Kodierung eines einzelnen (kk-ten) Merkmals aus dem jj-ten Datenvektor eines Datensatzes, xk(j)\vec{x}^{(j)}_k:

xk(j)=RY(θ=xk(j))0=cos(xk(j)2)0+sin(xk(j)2)1.|\vec{x}^{(j)}_k\rangle = R_Y(\theta=\vec{x}^{(j)}_k)|0\rangle = \textstyle\cos\left(\frac{\vec{x}^{(j)}_k}{2}\right)|0\rangle + \sin\left(\frac{\vec{x}^{(j)}_k}{2}\right)|1\rangle.

Alternativ kann die Winkelkodierung mit RX(θ)R_X(\theta)-Gates durchgeführt werden, obwohl der kodierte Zustand dann im Vergleich zu RY(θ)R_Y(\theta) eine komplexe relative Phase hätte.

Die Winkelkodierung unterscheidet sich von den beiden zuvor besprochenen Methoden in mehrfacher Hinsicht. Bei der Winkelkodierung gilt:

  • Jeder Merkmalswert wird auf ein entsprechendes Qubit abgebildet, xk(j)Qk\vec{x}^{(j)}_k \rightarrow Q_k, wodurch die Qubits in einem Produktzustand verbleiben.
  • Es wird jeweils ein numerischer Wert kodiert, nicht eine ganze Menge von Merkmalen eines Datenpunkts.
  • Für NN Datenmerkmale werden nn Qubits benötigt, wobei nNn\leq N gilt. Häufig gilt hier die Gleichheit. In den nächsten Abschnitten werden wir sehen, wie n<Nn<N möglich ist.
  • Der resultierende Schaltkreis hat eine konstante Tiefe (typischerweise Tiefe 1 vor der Transpilierung).

Der Quantenschaltkreis mit konstanter Tiefe macht ihn besonders geeignet für aktuelle Quantenhardware. Ein weiteres Merkmal der Datenkodierung mit θ\theta (und insbesondere unserer Wahl, Y-Achsen-Winkelkodierung zu verwenden) ist, dass reell wertige Quantenzustände erzeugt werden, die für bestimmte Anwendungen nützlich sein können. Bei der Y-Achsen-Rotation werden Daten mit einem Y-Achsen-Rotationsgate RY(θ)R_Y(\theta) mit einem reell wertigen Winkel θ(0,2π]\theta \in (0, 2\pi] kodiert (Qiskit RYGate). Wie bei der Phasenkodierung (siehe unten) empfehlen wir, Daten so umzuskalieren, dass xk(j)(0,2π]\vec{x}^{(j)}_k \in (0,2\pi] gilt, um Informationsverluste und andere unerwünschte Effekte zu vermeiden.

Der folgende Qiskit-Code rotiert ein einzelnes Qubit aus einem Anfangszustand 0|0\rangle, um einen Datenwert xk(j)=12π\vec{x}^{(j)}_k=\frac{1}{2}\pi zu kodieren.

from qiskit.quantum_info import Statevector
from math import pi

qc = QuantumCircuit(1)
state1 = Statevector.from_instruction(qc)
qc.ry(pi / 2, 0) # Phase gate rotates by an angle pi/2
state2 = Statevector.from_instruction(qc)
states = state1, state2

Wir definieren eine Funktion, um die Wirkung auf den Zustandsvektor zu visualisieren. Die Details der Funktionsdefinition sind nicht wichtig, aber die Fähigkeit, die Zustandsvektoren und ihre Änderungen zu visualisieren, ist es.

import numpy as np
from qiskit.visualization.bloch import Bloch
from qiskit.visualization.state_visualization import _bloch_multivector_data

def plot_Nstates(states, axis, plot_trace_points=True):
"""This function plots N states to 1 Bloch sphere"""
bloch_vecs = [_bloch_multivector_data(s)[0] for s in states]

if axis is None:
bloch_plot = Bloch()
else:
bloch_plot = Bloch(axes=axis)

bloch_plot.add_vectors(bloch_vecs)

if len(states) > 1:

def rgba_map(x, num):
g = (0.95 - 0.05) / (num - 1)
i = 0.95 - g * num
y = g * x + i
return (0.0, y, 0.0, 0.7)

num = len(states)
bloch_plot.vector_color = [rgba_map(x, num) for x in range(1, num + 1)]

bloch_plot.vector_width = 3
bloch_plot.vector_style = "simple"

if plot_trace_points:

def trace_points(bloch_vec1, bloch_vec2):
# bloch_vec = (x,y,z)
n_points = 15
thetas = np.arccos([bloch_vec1[2], bloch_vec2[2]])
phis = np.arctan2(
[bloch_vec1[1], bloch_vec2[1]], [bloch_vec1[0], bloch_vec2[0]]
)
if phis[1] < 0:
phis[1] = phis[1] + 2 * pi
angles0 = np.linspace(phis[0], phis[1], n_points)
angles1 = np.linspace(thetas[0], thetas[1], n_points)

xp = np.cos(angles0) * np.sin(angles1)
yp = np.sin(angles0) * np.sin(angles1)
zp = np.cos(angles1)
pnts = [xp, yp, zp]
bloch_plot.add_points(pnts)
bloch_plot.point_color = "k"
bloch_plot.point_size = [4] * len(bloch_plot.points)
bloch_plot.point_marker = ["o"]

for i in range(len(bloch_vecs) - 1):
trace_points(bloch_vecs[i], bloch_vecs[i + 1])

bloch_plot.sphere_alpha = 0.05
bloch_plot.frame_alpha = 0.15
bloch_plot.figsize = [4, 4]

bloch_plot.render()

plot_Nstates(states, axis=None, plot_trace_points=True)

Output of the previous code cell

Das war nur ein einzelnes Merkmal eines einzelnen Datenvektors. Wenn NN Merkmale in die Rotationswinkel von nn Qubits kodiert werden – beispielsweise für den jj-ten Datenvektor x(j)=(x1,...,xN)\vec{x}^{(j)} = (x_1,...,x_N) – sieht der kodierte Produktzustand so aus:

x(j)=k=1Ncos(xk(j))0+sin(xk(j))1|\vec{x}^{(j)}\rangle = \bigotimes^N_{k=1} \cos(\vec{x}^{(j)}_k)|0\rangle + \sin(\vec{x}^{(j)}_k)|1\rangle

Wir stellen fest, dass dies äquivalent ist zu

x(j)=k=1NRY(2xk(j))0.|\vec{x}^{(j)}\rangle = \bigotimes^N_{k=1} R_Y(2\vec{x}^{(j)}_k)|0\rangle.

Überprüfe dein Verständnis

Lies die folgenden Fragen, denke über deine Antworten nach und klicke dann auf die Dreiecke, um die Lösungen anzuzeigen.

Kodiere den Datenvektor x=(0,π/4,π/2)\vec{x} = (0, \pi/4, \pi/2) mit Angle Encoding, wie oben beschrieben.

Antwort:
qc = QuantumCircuit(3)
qc.ry(0, 0)
qc.ry(2 * math.pi / 4, 1)
qc.ry(2 * math.pi / 2, 2)
qc.draw(output="mpl")

&quot;Output of the previous code cell&quot;

Wie viele Qubits werden beim oben beschriebenen Angle Encoding benötigt, um 5 Merkmale zu kodieren?

Antwort: 5

Phasenkodierung

Phasenkodierung ähnelt dem oben beschriebenen Angle Encoding sehr stark. Der Phasenwinkel eines Qubits ist ein reellwertiger Winkel ϕ\phi um die zz-Achse, gemessen von der +x+x-Achse. Die Daten werden mit einer Phasenrotation kodiert: P(ϕ)=eiϕ/2RZ(ϕ)P(\phi) = e^{i\phi/2}R_Z(\phi), wobei ϕ(0,2π]\phi \in (0,2\pi] (siehe Qiskit PhaseGate für weitere Informationen). Es wird empfohlen, die Daten so zu skalieren, dass xk(j)(0,2π]\vec{x}^{(j)}_k \in (0,2\pi] gilt. Dadurch werden Informationsverluste und andere potenziell unerwünschte Effekte vermieden[1,2].

Ein Qubit wird häufig im Zustand 0|0\rangle initialisiert, der ein Eigenzustand des Phasenrotationsoperators ist. Das bedeutet, dass der Qubit-Zustand zunächst rotiert werden muss, bevor Phasenkodierung angewendet werden kann. Es ist daher sinnvoll, den Zustand mit einem Hadamard-Gate zu initialisieren: H0=+=12(0+1)H|0\rangle = |+\rangle = \textstyle\frac{1}{\sqrt{2}}(|0\rangle + |1\rangle). Phasenkodierung auf einem einzelnen Qubit bedeutet, eine relative Phase proportional zum Datenwert einzuprägen:

xk(j)=P(ϕ=xk(j))+=12(0+eixk(j)1).|\vec{x}^{(j)}_k\rangle = P(\phi=\vec{x}^{(j)}_k)|+\rangle = \textstyle\frac{1}{\sqrt{2}}\big(|0\rangle + e^{i\vec{x}^{(j)}_k}|1\rangle\big).

Das Phasenkodierungsverfahren bildet jeden Merkmalswert auf die Phase eines entsprechenden Qubits ab: xk(j)Qk\vec{x}^{(j)}_k \rightarrow Q_k. Insgesamt hat Phasenkodierung eine Circuit-Tiefe von 2, einschließlich der Hadamard-Schicht, was es zu einem effizienten Kodierungsschema macht. Der phasenkodierte Mehr-Qubit-Zustand (nn Qubits für N=nN=n Merkmale) ist ein Produktzustand:

x(j)=k=1NPk(ϕ=xk(j))+N=12Nk=1N(0+eixk(j)1).|\vec{x}^{(j)}\rangle = \bigotimes_{k=1}^{N} P_k(\phi = \vec{x}^{(j)}_k)|+\rangle^{\otimes N} = {\textstyle\frac{1}{\sqrt{2^N}}} \bigotimes_{k=1}^{N}\big(|0\rangle + e^{i\vec{x}^{(j)}_k}|1\rangle\big).

Der folgende Qiskit-Code bereitet zunächst den Anfangszustand eines einzelnen Qubits vor, indem es mit einem Hadamard-Gate rotiert wird, und dreht es dann erneut mit einem Phasengatter, um ein Datenmerkmal xk(j)=12π\vec{x}^{(j)}_k=\frac{1}{2}\pi zu kodieren.

qc = QuantumCircuit(1)
qc.h(0) # Hadamard gate rotates state down to Bloch equator
state1 = Statevector.from_instruction(qc)

qc.p(pi / 2, 0) # Phase gate rotates by an angle pi/2
state2 = Statevector.from_instruction(qc)

states = state1, state2

qc.draw("mpl", scale=1)

Output of the previous code cell

Wir können die Rotation in ϕ\phi mithilfe der zuvor definierten Funktion plot_Nstates visualisieren.

plot_Nstates(states, axis=None, plot_trace_points=True)

Output of the previous code cell

Die Bloch-Kugel-Darstellung zeigt die ZZ-Achsen-Rotation +P(12π)+|+\rangle \rightarrow P(\frac{1}{2}\pi)|+\rangle, wobei xk(j)=12π\vec{x}^{(j)}_k=\frac{1}{2}\pi. Der hellgrüne Pfeil zeigt den Endzustand.

Phasenkodierung wird in vielen Quanten-Feature-Maps verwendet, insbesondere in der ZZ- und ZZZZ-Feature-Map sowie allgemeinen Pauli-Feature-Maps, unter anderem.

Überprüfe dein Verständnis

Lies die folgenden Fragen, denke über deine Antworten nach und klicke dann auf die Dreiecke, um die Lösungen anzuzeigen.

Wie viele Qubits werden benötigt, um mit der oben beschriebenen Phasenkodierung 8 Merkmale zu speichern?

Antwort: 8

Schreibe Code, um den Vektor x(1)=(4,8,5,9,8,6,2,9,2,5,7,0)\vec{x}^{(1)}=(4,8,5,9,8,6,2,9,2,5,7,0) mit Phasenkodierung zu laden.

Antwort:

Es kann viele mögliche Antworten geben. Hier ist ein Beispiel:

phase_data = [4, 8, 5, 9, 8, 6, 2, 9, 2, 5, 7, 0]
qc = QuantumCircuit(len(phase_data))
for i in range(0, len(phase_data)):
qc.h(i)
qc.rz(phase_data[i] * 2 * math.pi / float(max(phase_data)), i)
qc.draw(output="mpl")

&quot;Output of the previous code cell&quot;

Dichtes Winkelkodieren

Dichtes Winkelkodieren (DAE, englisch: Dense Angle Encoding) ist eine Kombination aus Angle Encoding und Phasenkodierung. DAE ermöglicht es, zwei Merkmalswerte in einem einzigen Qubit zu kodieren: einen Winkel mit einer YY-Achsen-Rotation und den anderen mit einer zz-Achsen-Rotation: xk(j),\vec{x}^{(j)}_k, x(j)θ,ϕ\vec{x}^{(j)}_\ell \rightarrow \theta, \phi. Zwei Merkmale werden wie folgt kodiert:

xk(j),x(j)=RZ(ϕ=x(j))RY(θ=xk(j))0=cos(xk(j)2)0+eix(j)sin(xk(j)2)1.|\vec{x}^{(j)}_k,\vec{x}^{(j)}_\ell\rangle = R_Z(\phi=\vec{x}^{(j)}_\ell) R_Y(\theta=\vec{x}^{(j)}_k)|0\rangle = \cos\left(\frac{\vec{x}^{(j)}_k}{2}\right)|0\rangle + e^{i\vec{x}^{(j)}_\ell} \sin\left(\frac{\vec{x}^{(j)}_k}{2}\right)|1\rangle.

Durch die Kodierung von zwei Datenmerkmalen auf einem Qubit wird die Anzahl der erforderlichen Qubits um den Faktor 2 reduziert. Für mehr Merkmale kann der Datenvektor x=(x1,...,xN)\vec{x} = (x_1,...,x_N) wie folgt kodiert werden:

x=k=1N/2cos(x2k1)0+eix2ksin(x2k1)1|\vec{x}\rangle = \bigotimes_{k=1}^{N/2} \cos(x_{2k-1})|0\rangle + e^{i x_{2k}}\sin(x_{2k-1})|1\rangle

DAE kann auf beliebige Funktionen der beiden Merkmale verallgemeinert werden, anstatt der hier verwendeten Sinusfunktionen. Dies wird als allgemeine Qubit-Kodierung (general qubit encoding) bezeichnet[7].

Als Beispiel für DAE kodiert und visualisiert der folgende Code die Merkmale x1=θ=3π/8x_1=\theta = 3\pi/8 und x2=ϕ=7π/4x_2=\phi = 7\pi/4.

qc = QuantumCircuit(1)
state1 = Statevector.from_instruction(qc)
qc.ry(3 * pi / 8, 0)
state2 = Statevector.from_instruction(qc)
qc.rz(7 * pi / 4, 0)
state3 = Statevector.from_instruction(qc)
states = state1, state2, state3

plot_Nstates(states, axis=None, plot_trace_points=True)

Output of the previous code cell

Überprüfe dein Verständnis

Lies die folgenden Fragen, denke über deine Antworten nach und klicke dann auf die Dreiecke, um die Lösungen anzuzeigen.

Wie viele Qubits werden benötigt, um 6 Merkmale mit dichter Kodierung zu kodieren?

Antwort: 3

Schreibe Code, um den Vektor x(1)=(4,8,5,9,8,6,2,9,2,5,7,0,3,7,5)\vec{x}^{(1)}=(4,8,5,9,8,6,2,9,2,5,7,0,3,7,5) mit dichtem Winkelkodieren zu laden.

Antwort:

Beachte, dass die Liste mit einer „0" aufgefüllt wurde, um das Problem zu vermeiden, dass ein einzelner Parameter im Kodierungsschema ungenutzt bleibt.

dense_data = [4, 8, 5, 9, 8, 6, 2, 9, 2, 5, 7, 0, 3, 7, 5, 0]
qc = QuantumCircuit(int(len(dense_data) / 2))
entry = 0
for i in range(0, int(len(dense_data) / 2)):
qc.ry(dense_data[entry] * 2 * math.pi / float(max(dense_data)), i)
entry = entry + 1
qc.rz(dense_data[entry] * 2 * math.pi / float(max(dense_data)), i)
entry = entry + 1
qc.draw(output="mpl")

&quot;Output of the previous code cell&quot;

Kodierung mit integrierten Feature-Maps

Kodierung an beliebigen Punkten

Angle Encoding, Phasenkodierung und dichtes Kodieren erzeugen Produktzustände, bei denen ein Merkmal auf jedem Qubit (oder zwei Merkmale pro Qubit) kodiert wird. Das unterscheidet sich von Basis- und Amplitudenkodierung, bei denen verschränkte Zustände genutzt werden. Es gibt keine 1:1-Entsprechung zwischen Datenmerkmal und Qubit. Bei der Amplitudenkodierung könnte beispielsweise ein Merkmal die Amplitude des Zustands 01|01\rangle und ein anderes die Amplitude für 10|10\rangle sein. Methoden, die in Produktzuständen kodieren, liefern im Allgemeinen flachere Circuits und können 1 oder 2 Merkmale pro Qubit speichern. Methoden, die Verschränkung nutzen und ein Merkmal mit einem Zustand statt mit einem Qubit verknüpfen, erzeugen tiefere Circuits und können im Durchschnitt mehr Merkmale pro Qubit speichern.

Doch Kodierung muss weder ausschließlich in Produktzuständen noch ausschließlich in verschränkten Zuständen wie bei der Amplitudenkodierung erfolgen. Tatsächlich erlauben viele in Qiskit integrierte Kodierungsschemas die Kodierung sowohl vor als auch nach einer Verschränkungsschicht, und nicht nur am Anfang. Dies wird als „Data Reuploading" bezeichnet. Für verwandte Arbeiten siehe Referenzen [5] und [6].

In diesem Abschnitt werden wir einige der integrierten Kodierungsschemata verwenden und visualisieren. Alle Methoden in diesem Abschnitt kodieren NN Merkmale als Rotationen auf NN parametrisierten Gates auf nn Qubits, wobei nNn \leq N. Zu beachten ist, dass die Maximierung der Datenladung für eine gegebene Anzahl von Qubits nicht das einzige Kriterium ist. In vielen Fällen kann die Circuit-Tiefe ein noch wichtigeres Kriterium sein als die Qubit-Anzahl.

Efficient SU2

Ein gängiges und nützliches Beispiel für Kodierung mit Verschränkung ist Qiskits efficient_su2-Circuit. Beeindruckerweise kann dieser Circuit beispielsweise 8 Merkmale auf nur 2 Qubits kodieren. Schauen wir uns das an und versuchen dann zu verstehen, wie das möglich ist.

from qiskit.circuit.library import efficient_su2

circuit = efficient_su2(num_qubits=2, reps=1, insert_barriers=True)
circuit.decompose().draw(output="mpl")

Output of the previous code cell

Wenn wir unseren Zustand aufschreiben, verwenden wir die Qiskit-Konvention, bei der die niedrigstwertigen Qubits ganz rechts angeordnet sind, wie in q2,q1,q0|q_2,q_1,q_0\rangle oder q2q1q0.|q_2\rangle\otimes|q_1\rangle\otimes|q_0\rangle. Diese Zustände können sehr schnell sehr kompliziert werden, und dieses seltene Beispiel kann erklären, warum solche Zustände selten explizit ausgeschrieben werden.

Unser System startet im Zustand 00.|00\rangle. Bis zur ersten Barriere (einem Punkt, den wir mit b1b1 bezeichnen) sind unsere Zustände:

ψb1=(cos(θ12)0+sin(θ12)eiθ31)(cos(θ02)0+sin(θ02)eiθ21)|\psi\rangle_{b1} = \left(\cos\left(\frac{\theta_1}{2}\right)|0\rangle+\sin\left(\frac{\theta_1}{2}\right)e^{i\theta_3}|1\rangle\right)\otimes\left(\cos\left(\frac{\theta_0}{2}\right)|0\rangle+\sin\left(\frac{\theta_0}{2}\right)e^{i\theta_2}|1\rangle\right)

Das ist einfach dichtes Kodieren, das wir bereits kennen. Nach dem CNOT-Gate, an der zweiten Barriere (b2b2), ist unser Zustand:

ψb2=cos(θ12)cos(θ02)00+cos(θ12)sin(θ02)eiθ211+sin(θ12)cos(θ02)eiθ310+sin(θ12)sin(θ02)eiθ2eiθ301\begin{aligned} |\psi\rangle_{b2} = & \cos\left(\frac{\theta_1}{2}\right)\cos\left(\frac{\theta_0}{2}\right)|00\rangle+\cos\left(\frac{\theta_1}{2}\right)\sin\left(\frac{\theta_0}{2}\right)e^{i\theta_2}|11\rangle\\ + & \sin\left(\frac{\theta_1}{2}\right)\cos\left(\frac{\theta_0}{2}\right)e^{i\theta_3}|10\rangle+\sin\left(\frac{\theta_1}{2}\right)\sin\left(\frac{\theta_0}{2}\right)e^{i\theta_2}e^{i\theta_3}|01\rangle \end{aligned}

Nun wenden wir den letzten Satz von Einzelqubit-Rotationen an und fassen gleiche Zustände zusammen:

ψfinal=[cos(θ02)(cos(θ12)cos(θ52)sin(θ12)sin(θ52)eiθ3)cos(θ42)+sin(θ02)(cos(θ12)sin(θ52)sin(θ12)cos(θ52)eiθ3)sin(θ42)eiθ2]00+[cos(θ02)(cos(θ12)cos(θ52)sin(θ12)sin(θ52)eiθ3)sin(θ42)+sin(θ02)(cos(θ12)sin(θ52)+sin(θ12)cos(θ52)eiθ3)cos(θ42)eiθ2]eiθ601+[cos(θ02)(cos(θ12)sin(θ52)+sin(θ12)cos(θ52)eiθ3)cos(θ42)sin(θ02)(cos(θ12)cos(θ52)+sin(θ12)sin(θ52)eiθ3)sin(θ42)eiθ2]eiθ710+[cos(θ02)(cos(θ12)sin(θ52)+sin(θ12)cos(θ52)eiθ3)sin(θ42)+sin(θ02)(cos(θ12)cos(θ52)+sin(θ12)sin(θ52)eiθ3)cos(θ42)eiθ2]eiθ6eiθ711\begin{align*} |\psi\rangle_{\text{final}} = & \left[\cos\left(\frac{\theta_0}{2}\right)\left(\cos\left(\frac{\theta_1}{2}\right)\cos\left(\frac{\theta_5}{2}\right)-\sin\left(\frac{\theta_1}{2}\right)\sin\left(\frac{\theta_5}{2}\right)e^{i\theta_3}\right)\cos\left(\frac{\theta_4}{2}\right)\right.\\ + & \left.\sin\left(\frac{\theta_0}{2}\right)\left(\cos\left(\frac{\theta_1}{2}\right)\sin\left(\frac{\theta_5}{2}\right)-\sin\left(\frac{\theta_1}{2}\right)\cos\left(\frac{\theta_5}{2}\right)e^{i\theta_3}\right)\sin\left(\frac{\theta_4}{2}\right)e^{i\theta_2}\right] |00\rangle\\ + & \left[\cos\left(\frac{\theta_0}{2}\right)\left(\cos\left(\frac{\theta_1}{2}\right)\cos\left(\frac{\theta_5}{2}\right)-\sin\left(\frac{\theta_1}{2}\right)\sin\left(\frac{\theta_5}{2}\right)e^{i\theta_3}\right)\sin\left(\frac{\theta_4}{2}\right)\right.\\ + & \left.\sin\left(\frac{\theta_0}{2}\right)\left(-\cos\left(\frac{\theta_1}{2}\right)\sin\left(\frac{\theta_5}{2}\right)+\sin\left(\frac{\theta_1}{2}\right)\cos\left(\frac{\theta_5}{2}\right)e^{i\theta_3}\right)\cos\left(\frac{\theta_4}{2}\right)e^{i\theta_2}\right] e^{i\theta_6}|01\rangle\\ + & \left[\cos\left(\frac{\theta_0}{2}\right)\left(\cos\left(\frac{\theta_1}{2}\right)\sin\left(\frac{\theta_5}{2}\right)+\sin\left(\frac{\theta_1}{2}\right)\cos\left(\frac{\theta_5}{2}\right)e^{i\theta_3}\right)\cos\left(\frac{\theta_4}{2}\right)\right.\\ - & \left.\sin\left(\frac{\theta_0}{2}\right)\left(\cos\left(\frac{\theta_1}{2}\right)\cos\left(\frac{\theta_5}{2}\right)+\sin\left(\frac{\theta_1}{2}\right)\sin\left(\frac{\theta_5}{2}\right)e^{i\theta_3}\right)\sin\left(\frac{\theta_4}{2}\right)e^{i\theta_2}\right] e^{i\theta_7}|10\rangle\\ + & \left[\cos\left(\frac{\theta_0}{2}\right)\left(\cos\left(\frac{\theta_1}{2}\right)\sin\left(\frac{\theta_5}{2}\right)+\sin\left(\frac{\theta_1}{2}\right)\cos\left(\frac{\theta_5}{2}\right)e^{i\theta_3}\right)\sin\left(\frac{\theta_4}{2}\right)\right.\\ + & \left.\sin\left(\frac{\theta_0}{2}\right)\left(\cos\left(\frac{\theta_1}{2}\right)\cos\left(\frac{\theta_5}{2}\right)+\sin\left(\frac{\theta_1}{2}\right)\sin\left(\frac{\theta_5}{2}\right)e^{i\theta_3}\right)\cos\left(\frac{\theta_4}{2}\right)e^{i\theta_2}\right] e^{i\theta_6}e^{i\theta_7}|11\rangle \end{align*}

Das ist wahrscheinlich zu kompliziert, um es vollständig nachzuvollziehen. Tritt stattdessen einen Schritt zurück und überlege, wie viele Parameter wir in den Zustand geladen haben: acht. Wir haben dabei nur vier Rechenbasisszustände. Auf den ersten Blick mag es scheinen, als hätten wir mehr Parameter geladen, als sinnvoll ist, da der Endzustand als ψfinal=c000+c101+c210+c311\psi_\text{final} = c_0|00\rangle+c_1|01\rangle+c_2|10\rangle+c_3|11\rangle geschrieben werden kann. Beachte jedoch, dass jeder Vorfaktor komplex ist! Wenn man es so schreibt:

ψfinal=(a0+ib0)00+(a1+ib1)01+(a2+ib2)10+(a3+ib3)11\psi_\text{final} = (a_0+ib_0)|00\rangle+(a_1+ib_1)|01\rangle+(a_2+ib_2)|10\rangle+(a_3+ib_3)|11\rangle

sieht man, dass wir tatsächlich acht Parameter im Zustand haben, auf denen wir unsere acht Merkmale kodieren können.

Durch Erhöhung der Qubit-Anzahl und Erhöhung der Anzahl der Wiederholungen von Verschränkungs- und Rotationsschichten kann man deutlich mehr Daten kodieren. Das Aufschreiben der Wellenfunktionen wird schnell unlösbar. Aber wir können die Kodierung immer noch in Aktion sehen. Hier kodieren wir den Datenvektor x\vec{x} mit 12 Merkmalen auf einem 3-Qubit-efficient_su2-Circuit, wobei jedes der parametrisierten Gates ein anderes Merkmal kodiert.

x=(0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0,1.1,1.2)\vec{x} = (0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0,1.1,1.2)

In diesem Datenvektor sind die Merkmale in einer bestimmten Reihenfolge angegeben. Isoliert betrachtet spielt es keine Rolle, ob sie in dieser oder in umgekehrter Reihenfolge kodiert werden. Wichtig ist, die Reihenfolge zu verfolgen und konsistent zu bleiben. Beachte im Circuit-Diagramm, dass efficient_su2 eine bestimmte Kodierungsreihenfolge voraussetzt: Die erste Schicht parametrisierter Gates wird von Qubit 0 bis Qubit 2 befüllt, bevor zur nächsten Schicht übergegangen wird. Dies ist weder konsistent noch inkonsistent mit der Little-Endian-Notation, da die Datenmerkmale nicht a priori, also vor der Festlegung eines Kodierungs-Circuits, nach Qubit geordnet werden können.

x = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2]
circuit = efficient_su2(num_qubits=3, reps=1, insert_barriers=True)
encode = circuit.assign_parameters(x)
encode.decompose().draw(output="mpl")

Output of the previous code cell

Anstatt die Anzahl der Qubits zu erhöhen, könnte man auch die Anzahl der Wiederholungen von Verschränkungs- und Rotationsschichten erhöhen. Doch gibt es Grenzen, wie viele Wiederholungen sinnvoll sind. Wie bereits erwähnt, gibt es einen Kompromiss: Circuits mit mehr Qubits oder mehr Wiederholungen von Verschränkungs- und Rotationsschichten können mehr Parameter speichern, tun dies aber mit größerer Circuit-Tiefe. Wir werden weiter unten auf die Tiefen einiger integrierter Feature-Maps zurückkommen. Die nächsten Kodierungsmethoden, die in Qiskit integriert sind, haben „Feature Map" als Teil ihrer Namen. Halten wir noch einmal fest, dass das Kodieren von Daten in einen Quanten-Circuit eine Feature-Abbildung ist, in dem Sinne, dass sie Daten in einen neuen Raum überführt: den Hilbertraum der beteiligten Qubits. Die Beziehung zwischen der Dimensionalität des ursprünglichen Merkmalsraums und der des Hilbertraums hängt von dem für die Kodierung verwendeten Circuit ab.

ZZ-Feature-Map

Die ZZ-Feature-Map (ZFM) kann als natürliche Erweiterung der Phasenkodierung interpretiert werden. Die ZFM besteht aus abwechselnden Schichten von Einzelqubit-Gates: Hadamard-Gate-Schichten und Phasengatter-Schichten. Sei der Datenvektor x\vec{x} mit NN Merkmalen gegeben. Der Quanten-Circuit, der die Feature-Abbildung durchführt, wird als unitärer Operator dargestellt, der auf den Anfangszustand wirkt:

UZFM(x)0N=ϕ(x)\mathscr{U}_{\text{ZFM}}(\vec{x})|0\rangle^{\otimes N}=|\phi(\vec{x})\rangle

wobei 0N|0\rangle^{\otimes N} der NN-Qubit-Grundzustand ist. Diese Notation wird zur Konsistenz mit Referenz [4] (Havlicek et al.) verwendet. Die Datenmerkmale xix_i werden eins zu eins auf entsprechende Qubits abgebildet. Hat man beispielsweise 8 Merkmale in einem Datenvektor, verwendet man 8 Qubits. Der ZFM-Circuit besteht aus rr Wiederholungen eines Teilcircuits aus Hadamard-Gate-Schichten und Phasengatter-Schichten. Eine Hadamard-Schicht besteht aus einem Hadamard-Gate, das auf jedes Qubit in einem nn-Qubit-Register wirkt: HHH=HnH \otimes H \otimes \dots \otimes H = H^{\otimes n}, innerhalb derselben Algorithmusphase. Entsprechendes gilt für eine Phasengatter-Schicht, bei der das itei^\text{te} Qubit von P(xi)P(\vec{x}_i) beeinflusst wird. Jedes PP-Gate hat ein Merkmal als Argument, die Phasengatter-Schicht (P(x1)P(xk)P(xN)P(\vec{x}_1)\otimes\ldots P(\vec{x}_k)\otimes\ldots P(\vec{x}_N)) ist jedoch eine Funktion des Datenvektors. Der vollständige ZFM-Circuit-Unitär mit einer einzelnen Wiederholung ist:

UZFM=(P(x1)P(xk)P(xN)HN)=(k=1NP(xk))HN\mathscr{U}_{\text{ZFM}}=\big(P(\vec{x}_1)\otimes\ldots P(\vec{x}_k)\otimes\ldots P(\vec{x}_N)H^{\otimes N}\big)=\left(\bigotimes_{k = 1}^N P(\vec{x}_k)\right)H^{\otimes N}

rr Wiederholungen dieses Unitärs ergeben dann:

UZFM(r)(x)=s=1r[(k=1NP(xk))HN]\mathscr{U}^{(r)}_{\text{ZFM}}\left(\vec{x}\right)=\prod_{s=1}^{r}\left[\left(\bigotimes_{k = 1}^N P(\vec{x}_k)\right)H^{\otimes N}\right]

Die Datenmerkmale xkx_k werden in allen rr Wiederholungen auf die gleiche Weise auf die Phasengatter abgebildet. Der ZFM-Feature-Map-Zustand ist ein Produktzustand und für die klassische Simulation effizient[4].

Als kleines einführendes Beispiel wird ein Zwei-Qubit-ZFM-Circuit mit Qiskit kodiert und gezeichnet, um die einfache Circuit-Struktur zu zeigen. Im Beispiel wird eine einzelne Wiederholung, r=1r=1, mit dem Datenvektor x=(12π,13π)\vec{x} = \left(\textstyle\frac{1}{2}\pi, \textstyle\frac{1}{3}\pi\right) implementiert. Beachte, dass dies in der Standardreihenfolge eines Vektors in Python geschrieben ist, d. h. das 00-te Element ist 12π.\textstyle\frac{1}{2}\pi. Wir können dieses 00-te Merkmal auf unser 00-tes Qubit oder auf unser NN-tes kodieren. Es kann nicht immer eine einzelne 1:1-Abbildung von der Merkmalsreihenfolge auf die Qubit-Reihenfolge geben, da verschiedene Feature-Maps unterschiedlich viele Merkmale pro Qubit kodieren. Entscheidend ist wieder, dass wir wissen, wo jedes Merkmal kodiert wird. Wenn man der ZZ-Feature-Map eine Parameterliste übergibt, kodiert sie Merkmal 0 aus der Liste auf das niedrigswertige Qubit mit einem parametrisierten Gate, also Qubit 0. Wir folgen dieser Konvention beim manuellen Vorgehen. Wir kodieren 12π\textstyle\frac{1}{2}\pi auf dem 00-ten Qubit und 13π\textstyle\frac{1}{3}\pi auf dem 11-ten Qubit.

Der ZFM-Circuit-Unitäroperator wirkt folgendermaßen auf den Anfangszustand:

UZFM(xˉ)00=P(xˉ)2H200=(P(13π)H0)(P(12π)H0).\mathscr{U}_{\text{ZFM}}(\bar{x})|00\rangle = P(\bar{x})^{\otimes 2} H^{\otimes 2}|00\rangle = \left( P\left(\textstyle\frac{1}{3}\pi\right)H|0\rangle \right) \otimes \left(P\left(\textstyle\frac{1}{2}\pi\right)H|0\rangle\right).

Die Formel wurde um das Tensorprodukt herum umgestellt, um die Operationen auf jedem Qubit zu verdeutlichen. Der folgende Qiskit-Code verwendet Hadamard- und Phasengatter explizit, um die Struktur der ZFM zu zeigen:

qc0 = QuantumCircuit(1)
qc1 = QuantumCircuit(1)

qc0.h(0)
qc0.p(pi / 2, 0)

qc1.h(0)
qc1.p(pi / 3, 0)

# Combine circuits qc0 and qc1 into 1 circuit
qc = QuantumCircuit(2)
qc.compose(qc0, [0], inplace=True)
qc.compose(qc1, [1], inplace=True)

qc.draw("mpl", scale=1)

Output of the previous code cell

Wir kodieren nun denselben Datenvektor x=(12π,13π)\vec{x} = \left(\textstyle\frac{1}{2}\pi, \textstyle\frac{1}{3}\pi\right) in einen ZFM-Circuit mit drei Wiederholungen, r=3r=3, unter Verwendung der Qiskit-Klasse z_feature_map, was uns insgesamt die Quanten-Feature-Map UZFM(x)\mathscr{U}_{\text{ZFM}}(\vec{x}) liefert. Standardmäßig werden in der Klasse z_feature_map die Parameter β\beta mit 2 multipliziert, bevor sie auf das Phasengatter βP(θ=2β)\beta \rightarrow P(\theta = 2\beta) abgebildet werden. Um dieselben Kodierungen wie oben zu reproduzieren, dividieren wir durch 2.

from qiskit.circuit.library import z_feature_map

zfeature_map = z_feature_map(feature_dimension=2, reps=3)
zfeature_map = zfeature_map.assign_parameters([(1 / 2) * pi / 2, (1 / 2) * pi / 3])
zfeature_map.decompose().draw("mpl")

Output of the previous code cell

Dies ist offensichtlich eine andere Abbildung als die oben manuell durchgeführte, aber beachte die Konsistenz in der Parameterreihenfolge: 12π\textstyle\frac{1}{2}\pi wurde erneut auf dem 00-ten Qubit kodiert.

Du kannst die ZFM über Qiskits ZFM-Klasse verwenden; du kannst diese Struktur auch als Inspiration nutzen, um deine eigene Feature-Abbildung zu konstruieren.

ZZZZ-Feature-Map

Die ZZZZ-Feature-Map (ZZFM) erweitert die ZFM durch die Einbeziehung von Zwei-Qubit-Verschränkungsgattern, insbesondere des ZZZZ-Rotationsgates RZZ(θ)R_{ZZ}(\theta). Es wird vermutet, dass die ZZFM im Allgemeinen auf einem klassischen Computer aufwendig zu berechnen ist, im Gegensatz zur ZFM.

RZZ(θ)R_{ZZ}(\theta) implementiert eine ZZZZ-Wechselwirkung und ist für θ=12π\theta = \textstyle{\frac{1}{2}}\pi maximal verschränkend. RZZ(θ)R_{ZZ}(\theta) kann in eine Folge von Gates auf zwei Qubits zerlegt werden, wie der folgende Qiskit-Code unter Verwendung des RZZ-Gates und der QuantumCircuit-Klassenmethode decompose zeigt. Wir kodieren ein einzelnes Merkmal des Datenvektors x\vec{x}: xk=π.\vec{x}_k=\pi.

qc = QuantumCircuit(2)
qc.rzz(pi, 0, 1)
qc.draw("mpl", scale=1)

Output of the previous code cell

Wie oft üblich, sehen wir dies als einzelne gatterartige Einheit dargestellt, bis wir .decompose() verwenden, um alle einzelnen Gates zu sehen.

qc.decompose().draw("mpl", scale=1)

Output of the previous code cell

Daten werden mit einer Phasenrotation P(θ)=eiθ/2RZ(θ)P(\theta) = e^{i\theta/2}R_Z(\theta) auf dem zweiten Qubit abgebildet. Das RZZ(θ)R_{ZZ}(\theta)-Gate verschränkt die beiden Qubits, auf die es wirkt, in einem Ausmaß, das durch den kodierten Merkmalswert bestimmt wird.

Der vollständige ZZFM-Circuit besteht aus einem Hadamard-Gate und einem Phasengatter, wie in der ZFM, gefolgt von der oben beschriebenen Verschränkung. Eine einzelne Wiederholung des ZZFM-Circuits ist:

UZZFM(x)=UZZ(x)(P(x1)P(xk)P(xN)HN)=UZZ(x)(k=1NP(xk))HN,\mathscr{U}_{\text{ZZFM}}(\vec{x}) = U_{ZZ}(\vec{x})\big(P(\vec{x}_1)\otimes\ldots P(\vec{x}_k)\otimes\ldots P(\vec{x}_N)H^{\otimes N}\big)=U_{ZZ}(\vec{x})\left(\bigotimes_{k = 1}^N P(\vec{x}_k)\right)H^{\otimes N},

wobei UZZ(x)U_{ZZ}(\vec{x}) eine ZZ-Gate-Schicht enthält, die durch ein Verschränkungsschema strukturiert ist. Mehrere Verschränkungsschemata werden in den folgenden Codeblöcken gezeigt. Die Struktur von UZZ(x)U_{ZZ}(\vec{x}) enthält auch eine Funktion, die die Datenmerkmale der zu verschränkenden Qubits auf folgende Weise kombiniert. Angenommen, das RZZR_{ZZ}-Gate soll auf die Qubits pp und qq angewendet werden. In der Phasenschicht haben diese Qubits Phasengatter, die xp\vec{x}_p bzw. xq\vec{x}_q kodieren. Das Argument θq,p\theta_{q,p} von RZZ,q,p(θq,p)R_{ZZ,q,p}(\theta_{q,p}) ist nicht einfach eines dieser Merkmale, sondern eine Funktion, die häufig mit ϕ\phi bezeichnet wird (nicht zu verwechseln mit dem Azimutwinkel):

θq,pϕ(xq,xp)=2(πxq)(πxp).\theta_{q,p} \rightarrow \phi(\vec{x}_q, \vec{x}_p) = 2(\pi-\vec{x}_q)(\pi-\vec{x}_p).

Wir werden dies in mehreren Beispielen weiter unten sehen. Die Erweiterung auf mehrere Wiederholungen ist dieselbe wie im Fall der z_feature_map:

UZZFM(r)(x)=s=1r[UZZ(x)(k=1NP(xk))HN].\mathscr{U}^{(r)}_{\text{ZZFM}}\left(\vec{x}\right)=\prod_{s=1}^{r}\left[U_{ZZ}(\vec{x})\left(\bigotimes_{k = 1}^N P(\vec{x}_k)\right)H^{\otimes N}\right].

Da die Operatoren komplexer geworden sind, kodieren wir zunächst einen Datenvektor x=(x0,x1)\vec{x} = (x_0, x_1) mit einer Zwei-Qubit-ZZFM und einer Wiederholung mithilfe des folgenden Codes:

from qiskit.circuit.library import zz_feature_map

feature_dim = 2
zzfeature_map = zz_feature_map(
feature_dimension=feature_dim, entanglement="linear", reps=1
)
zzfeature_map.decompose(reps=1).draw("mpl", scale=1)

Output of the previous code cell

Standardmäßig werden in Qiskit die Merkmale (x1,x2)(\vec{x}_1, \vec{x}_2) gemeinsam auf RZZ(θ)R_{ZZ}(\theta) durch diese Abbildungsfunktion θ1,2=ϕ(x1,x2)=2(πx1)(πx2)\theta_{1,2} = \phi(\vec{x}_1, \vec{x}_2) = 2(\pi-\vec{x}_1)(\pi-\vec{x}_2) abgebildet. Qiskit erlaubt es dem Nutzer, die Funktion ϕ\phi (oder ϕS\phi_S, wobei SS die Menge der Qubit-Paare ist, die durch RZZR_{ZZ}-Gates gekoppelt sind) als Vorverarbeitungsschritt anzupassen.

Mit einem vierdimensionalen Datenvektor x=(x1,x2,x3,x4)\vec{x} = (\vec{x}_1, \vec{x}_2, \vec{x}_3, \vec{x}_4) und der Abbildung auf eine Vier-Qubit-ZZFM mit einer Wiederholung lässt sich die Abbildung ϕ\phi für verschiedene Qubit-Paare erkennen. Wir können auch die Bedeutung von „linearer" Verschränkung erkennen:

feature_dim = 4
zzfeature_map = zz_feature_map(
feature_dimension=feature_dim, entanglement="linear", reps=1
)
zzfeature_map.decompose().draw("mpl", scale=1)

Output of the previous code cell

Im linearen Verschränkungsschema werden nächste-Nachbarn-Paare (nummerierte) von Qubits in diesem Circuit verschränkt. Es gibt weitere integrierte Verschränkungsschemata in Qiskit, darunter circular und full.

Pauli-Feature-Map

Die Pauli-Feature-Map (PFM) ist die Verallgemeinerung der ZFM und ZZFM auf beliebige Pauli-Gates. Die Pauli-Feature-Map hat eine sehr ähnliche Form wie die beiden vorherigen Feature-Maps. Für rr Wiederholungen der Kodierung der NN Features des Vektors x\vec{x} gilt:

UPFM(x)=s=1rU(x)Hn.\mathscr{U}_{\text{PFM}}(\vec{x}) = \prod_{s=1}^{r} U(\vec{x}) H^{\otimes n}.

Bei der PFM wird U(x)U(\vec{x}) zu einem verallgemeinerten unitären Pauli-Entwicklungsoperator. Hier stellen wir eine allgemeinere Form der bisher betrachteten Feature-Maps vor:

U(x)=exp(iSIϕS(x)iSσi),U(\vec{x}) = \exp\left(i \sum_{S \in\mathcal{I}} \phi_S(\vec{x}) \prod_{i \in S} \sigma_i \right),

wobei σi\sigma_i ein Pauli-Operator ist, σiI,X,Y,Z\sigma_i \in {I,X,Y,Z}. Hierbei ist I\mathcal{I} die Menge aller Qubit-Konnektivitäten, wie sie durch die Feature-Map bestimmt werden, einschließlich der Menge der Qubits, auf die Einzelqubit-Gates wirken. Das heißt, für eine Feature-Map, bei der auf Qubit 0 ein Phase-Gate und auf die Qubits 2 und 3 ein RZZR_{ZZ}-Gate angewendet wird, enthält die Menge I\mathcal{I} die Elemente {{0},{2,3}}\{\{0\},\{2,3\}\}. SS durchläuft alle Elemente dieser Menge. In den vorherigen Feature-Maps war die Funktion ϕS(x)\phi_S(\vec{x}) entweder ausschließlich mit Einzelqubit-Gates oder ausschließlich mit Zweiqubit-Gates verknüpft. Hier definieren wir sie allgemein:

ϕS(x)={xiif S={i} (single-qubit)jS(πxj)if S2 (multi-qubit)\phi_S(\vec{x})= \begin{cases} x_i & \text{if } S= \{i\} \text{ (single-qubit)}\\ \prod_{j\in{S}}(\pi-x_j) & \text{if } |S|\ge2 \text{ (multi-qubit)}\\ \end{cases}

Die Dokumentation findest du in der Qiskit-Dokumentation zur Pauli feature map-Klasse). In der ZZFM ist der Operator σi\sigma_i auf ZiZ_i beschränkt.

Eine Möglichkeit, den obigen unitären Operator zu verstehen, ist die Analogie zum Propagator in einem physikalischen System. Der obige unitäre Operator ist ein unitärer Zeitentwicklungsoperator, exp(itH)\exp(it\mathcal{H}), für einen Hamiltonian H\mathcal{H}, ähnlich dem Ising-Modell, wobei der Zeitparameter tt durch Datenwerte ersetzt wird, um die Entwicklung anzutreiben. Die Entwicklung dieses unitären Operators ergibt den PFM-Circuit. Die Verschränkungskonnektivitäten in SS können als Ising-Kopplungen in einem Spin-Gitter interpretiert werden. Betrachten wir ein Beispiel mit Pauli-YY- und XXXX-Operatoren, die diese Ising-artigen Wechselwirkungen repräsentieren. Qiskit stellt eine pauli_feature_map-Klasse zur Verfügung, mit der eine PFM mit einer Auswahl von Einzel- und nn-Qubit-Gates instanziiert werden kann. In diesem Beispiel werden diese als Pauli-Strings 'Y' und 'XX' übergeben. Typischerweise ist nn 1 oder 2 für Einzel- bzw. Zweiqubit-Wechselwirkungen. Das Verschränkungsschema ist „linear", das heißt, es werden nur nächste-Nachbar-Qubits im Quantum Circuit gekoppelt. Beachte, dass dies nicht den nächsten Nachbar-Qubits auf dem eigentlichen Quantencomputer entspricht, da dieser Quantum Circuit eine Abstraktionsschicht darstellt.

from qiskit.circuit.library import pauli_feature_map

feature_dim = 3
pfmap = pauli_feature_map(
feature_dimension=feature_dim, entanglement="linear", reps=1, paulis=["Y", "XX"]
)

pfmap.decompose().draw("mpl", scale=1.5)

Output of the previous code cell

Qiskit stellt in Pauli-Feature-Maps einen Parameter α\alpha zur Verfügung, mit dem die Skalierung der Pauli-Rotationen gesteuert werden kann.

U(xˉ)=exp(iαS[n]ϕS(xˉ)iSσi)U(\bar{x}) = \exp\left(i \alpha \sum_{S\subseteq[n]} \phi_S(\bar{x}) \prod_{i \in S} \sigma_i \right)

Der Standardwert von α\alpha ist 22. Durch Optimierung seines Wertes im Intervall, zum Beispiel [0,4],[0,4], lässt sich ein Quanten-Kernel besser an die Daten anpassen.

Hier visualisieren wir verschiedene Pauli-Feature-Maps für Zweiqubit-Circuits, um einen besseren Überblick über die Bandbreite der Möglichkeiten zu erhalten.

from qiskit.visualization import circuit_drawer
import matplotlib.pyplot as plt

feature_dim = 2
fig, axs = plt.subplots(9, 2)
i_plot = 0
for paulis in [
["I"],
["X"],
["Y"],
["Z"],
["XX"],
["XY"],
["XZ"],
["YY"],
["YZ"],
["ZZ"],
["X", "ZZ"],
["Y", "ZZ"],
["Z", "ZZ"],
["X", "YZ"],
["Y", "YZ"],
["Z", "YZ"],
["YY", "ZZ"],
["XY", "ZZ"],
]:
pfmap = pauli_feature_map(feature_dimension=feature_dim, paulis=paulis, reps=1)
circuit_drawer(
pfmap.decompose(),
output="mpl",
style={"backgroundcolor": "#EEEEEE"},
ax=axs[int((i_plot - i_plot % 2) / 2), i_plot % 2],
)
axs[int((i_plot - i_plot % 2) / 2), i_plot % 2].title.set_text(paulis)
i_plot += 1

fig.set_figheight(16)
fig.set_figwidth(16)

Output of the previous code cell

Das obige Beispiel lässt sich natürlich um weitere Permutationen und Wiederholungen von Pauli-Matrizen erweitern. Lernende sind eingeladen, mit diesen Optionen zu experimentieren.

Überblick über die integrierten Feature-Maps

Du hast verschiedene Verfahren zur Kodierung von Daten in einen Quantum Circuit kennengelernt:

  • Basis-Kodierung
  • Amplitudenkodierung
  • Winkelkodierung
  • Phasenkodierung
  • Dichte Kodierung

Du hast gesehen, wie du eigene Feature-Maps mit diesen Kodierungsverfahren aufbaust, und du hast vier integrierte Feature-Maps kennengelernt, die Winkel- und Phasenkodierung nutzen:

  • Efficient SU2
  • Z-Feature-Map
  • ZZ-Feature-Map
  • Pauli-Feature-Map

Diese integrierten Feature-Maps unterscheiden sich in mehreren Punkten voneinander:

  • Die Tiefe bei einer gegebenen Anzahl kodierter Features
  • Die Anzahl der Qubits, die für eine bestimmte Anzahl von Features benötigt werden
  • Der Grad der Verschränkung (was offensichtlich mit den anderen Unterschieden zusammenhängt)

Der folgende Code wendet diese vier integrierten Feature-Maps auf die Kodierung eines Feature-Sets an und stellt die Zweiqubit-Tiefe des resultierenden Circuits dar. Da die Fehlerrate bei Zweiqubit-Gates deutlich höher ist als bei Einqubit-Gates, ist die Tiefe der Zweiqubit-Gates besonders interessant. Im folgenden Code ermitteln wir die Anzahl aller Gates in einem Circuit, indem wir den Circuit zunächst dekompilieren und dann count_ops() verwenden, wie unten gezeigt. Die Zweiqubit-Gates, die uns hier interessieren, sind 'cx'-Gates:

# Initializing parameters and empty lists for depths
x = [0.1, 0.2]
n_data = []
zz2gates = []
su22gates = []
z2gates = []
p2gates = []

# Generating feature maps
for n in range(3, 10):
x.append(n / 10)
zzcircuit = zz_feature_map(n, reps=1, insert_barriers=True)
zcircuit = z_feature_map(n, reps=1, insert_barriers=True)
su2circuit = efficient_su2(n, reps=1, insert_barriers=True)
pcircuit = pauli_feature_map(n, reps=1, paulis=["XX"], insert_barriers=True)
# Getting the cx depths
zzcx = zzcircuit.decompose().count_ops().get("cx")
zcx = zcircuit.decompose().count_ops().get("cx")
su2cx = su2circuit.decompose().count_ops().get("cx")
pcx = pcircuit.decompose().count_ops().get("cx")

# Appending the cx gate counts to the lists. We shift the zz and pauli data points, because they overlap.
n_data.append(n)
zz2gates.append(zzcx - 0.5)
z2gates.append(0)
su22gates.append(su2cx)
p2gates.append(pcx + 0.5)

# Plot the output
plt.plot(n_data, p2gates, "bo")
plt.plot(n_data, zz2gates, "ro")
plt.plot(n_data, su22gates, "yo")
plt.plot(n_data, z2gates, "go")
plt.ylabel("CX Gates")
plt.xlabel("Data elements")
plt.legend(["Pauli", "ZZ", "SU2", "Z"])
# plt.suptitle('zz_feature_map(n)')
plt.show()

Im Allgemeinen führen Pauli- und ZZ-Feature-Maps zu einer größeren Circuit-Tiefe und einer höheren Anzahl an 2-Qubit-Gates als efficient_su2 und Z-Feature-Maps.

Da die in Qiskit integrierten Feature-Maps sehr vielseitig einsetzbar sind, muss man in der Lernphase meist keine eigenen entwerfen. Experten für Quanten-Maschinelles-Lernen werden jedoch wahrscheinlich auf das Thema des Entwerfens eigener Feature-Mappings zurückkommen, wenn sie sich mit zwei komplexen Herausforderungen befassen:

  1. Moderne Hardware: Das Vorhandensein von Rauschen und der hohe Overhead von fehlerkorrigierendem Code bedeuten, dass aktuelle Anwendungen Aspekte wie Hardware-Effizienz und die Minimierung der Zweiqubit-Gate-Tiefe berücksichtigen müssen.

  2. Mappings, die zum jeweiligen Problem passen: Es ist eine Sache zu sagen, dass die zz_feature_map zum Beispiel klassisch schwer zu simulieren und daher interessant ist. Eine ganz andere Sache ist es, ob die zz_feature_map ideal für deine Machine-Learning-Aufgabe oder deinen Datensatz geeignet ist. Die Performance verschiedener parametrisierter Quanten-Circuits auf verschiedenen Datentypen ist ein aktives Forschungsgebiet.

Wir schließen mit einem Hinweis zur Hardware-Effizienz.

Hardware-effizientes Feature-Mapping

Ein hardware-effizientes Feature-Mapping berücksichtigt die Einschränkungen realer Quantencomputer mit dem Ziel, Rauschen und Fehler bei der Berechnung zu reduzieren. Beim Ausführen von Quantum Circuits auf Quantencomputern der nahen Zukunft gibt es viele Strategien zur Minderung des der Hardware innewohnenden Rauschens. Eine wichtige Strategie für Hardware-Effizienz ist die Minimierung der Tiefe des Quantum Circuits, damit Rauschen und Dekohärenz weniger Zeit haben, die Berechnung zu korrumpieren. Die Tiefe eines Quantum Circuits ist die Anzahl der zeitlich ausgerichteten Gate-Schritte, die zur Durchführung der gesamten Berechnung erforderlich sind (nach Circuit-Optimierung)[5]. Beachte, dass die Tiefe des abstrakten, logischen Circuits möglicherweise viel geringer ist als die Tiefe nach dem Transpilieren für einen echten Quantencomputer.

Transpilation ist der Prozess der Umwandlung des Quantum Circuits von einer abstrakten Darstellung auf hoher Ebene in eine Form, die auf einem echten Quantencomputer ausgeführt werden kann, wobei die Hardware-Einschränkungen berücksichtigt werden. Ein Quantencomputer verfügt über einen nativen Satz von Einzel- und Zweiqubit-Gates. Das bedeutet, dass alle Gates im Qiskit-Code in den Satz nativer Hardware-Gates transpiliert werden müssen. In ibm_torino zum Beispiel, einem QPU mit einem Heron-r1-Prozessor, der 2023 fertiggestellt wurde, sind die nativen bzw. Basis-Gates \{CZ, ID, RZ, SX, X\}. Dies sind das Zweiqubit-Controlled-Z-Gate und Einqubit-Gates namens Identity, ZZ-Rotation, Quadratwurzel von NOT und NOT, die zusammen einen universellen Satz bilden. Bei der Implementierung von Mehrqubit-Gates als äquivalente Teilschaltung werden physikalische Zweiqubit-CZCZ-Gates zusammen mit anderen in der Hardware verfügbaren Einqubit-Gates benötigt. Um ein Zweiqubit-Gate auf einem Qubitpaar anzuwenden, das nicht physisch gekoppelt ist, werden zudem SWAP-Gates hinzugefügt, um Qubit-Zustände zwischen Qubits zu verschieben und so die Kopplung zu ermöglichen – was zu einer unvermeidlichen Verlängerung des Circuits führt. Mit dem Argument optimization, das von 0 bis zu einem Höchstwert von 3 eingestellt werden kann, lässt sich die Optimierung steuern. Für mehr Kontrolle und Anpassbarkeit kann die Transpiler-Pipeline mit dem Qiskit Pass Manager verwaltet werden. Weitere Informationen zur Transpilation findest du in der Qiskit-Transpiler-Dokumentation.

In Havlicek et al. 2019 [2] erreichen die Autoren Hardware-Effizienz unter anderem dadurch, dass sie die ZZZZ-Feature-Map verwenden, da es sich um eine Entwicklung zweiter Ordnung handelt (siehe den Abschnitt „ZZZZ-Feature-Map" oben). Eine Entwicklung NN-ter Ordnung hat NN-Qubit-Gates. IBM®-Quantencomputer haben keine nativen NN-Qubit-Gates für N>2N>2; deren Implementierung würde daher eine Zerlegung in Zweiqubit-CNOT-Gates erfordern, die in der Hardware verfügbar sind. Eine zweite Möglichkeit, mit der die Autoren die Tiefe minimieren, besteht darin, eine ZZZZ-Kopplungstopologie zu wählen, die direkt auf die Architektur-Kopplungen abgebildet wird. Eine weitere Optimierung, die sie vornehmen, ist die Auswahl einer leistungsstärkeren, geeignet verbundenen Hardware-Teilschaltung. Weitere zu berücksichtigende Aspekte sind die Minimierung der Anzahl der Feature-Map-Wiederholungen und die Wahl eines maßgeschneiderten, tiefenoptimierten oder „linearen" Verschränkungsschemas anstelle des „vollen" Schemas, das alle Qubits verschränkt.

Data encoding image

Die obige Grafik zeigt ein Netzwerk von Knoten und Kanten, die physische Qubits bzw. Hardware-Kopplungen repräsentieren. Die Kopplungskarte und die Performance von ibm_torino sind mit allen möglichen Zweiqubit-CZ-Kopplungs-Gates dargestellt. Die Qubits sind farbkodiert auf einer Skala basierend auf der T1-Relaxationszeit in Mikrosekunden (μs), wobei längere T1-Zeiten besser sind und in einem helleren Farbton dargestellt werden. Die Kopplungskanten sind nach dem CZ-Fehler farbkodiert, wobei dunklere Farbtöne besser sind. Informationen zur Hardware-Spezifikation sind im Hardware-Backend-Konfigurationsschema IBMQBackend.configuration() abrufbar.

Referenzen

  1. Maria Schuld and Francesco Petruccione, Supervised Learning with Quantum Computers, Springer 2018, doi:10.1007/978-3-319-96424-9.
  2. Vojtech Havlicek et al., "Supervised Learning with Quantum Enhanced Feature Spaces." Nature, vol. 567 (2019): 209–212. https://arxiv.org/abs/1804.11326.
  3. Ryan LaRose and Brian Coyle, "Robust data encodings for quantum classifiers", Physical Review A 102, 032420 (2020), doi:10.1103/PhysRevA.102.032420, arXiv:2003.01695.
  4. Lou Grover and Terry Rudolph. "Creating Superpositions That Correspond to Efficiently Integrable Probability Distributions." arXiv:quant-ph/0208112, August 15, 2002, https://arxiv.org/abs/quant-ph/0208112.
  5. Adrián Pérez-Salinas, Alba Cervera-Lierta, Elies Gil-Fuster, José I. Latorre, "Data re-uploading for a universal quantum classifier", Quantum 4, 226 (2020), ArXiv.org/abs/1907.02085.
  6. Maria Schuld, Ryan Sweke, Johannes Jakob Meyer, "The effect of data encoding on the expressive power of variational quantum machine learning models", Phys. Rev. A 103, 032430 (2021), arxiv.org/abs/2008.08605
import qiskit

qiskit.version.get_version_info()