Перейти до основного вмісту

Кодування даних

Вступ та позначення

Щоб використовувати квантовий алгоритм, класичні дані потрібно якимось чином завантажити у квантову схему. Зазвичай це називають кодуванням даних, але також використовують термін завантаження даних. Пригадай з попередніх уроків поняття відображення ознак — відображення ознак даних з одного простору в інший. Сам по собі перенос класичних даних на квантовий комп'ютер є своєрідним відображенням і може називатися відображенням ознак. На практиці вбудовані відображення ознак у Qiskit (наприклад, z_Feature Map та ZZ Feature Map) зазвичай включають шари обертань та шари заплутування, що розширюють стан до великої кількості вимірів у просторі Гільберта. Цей процес кодування є критично важливою частиною алгоритмів квантового машинного навчання і безпосередньо впливає на їхні обчислювальні можливості.

Деякі з описаних нижче методів кодування можна ефективно моделювати класично; це особливо легко побачити у методах кодування, що породжують добуткові стани (тобто не заплутують кубіти). Пам'ятай, що квантова перевага найімовірніша тоді, коли квантоподібна складність набору даних добре відповідає методу кодування. Тому дуже ймовірно, що тобі доведеться написати власні схеми кодування. Тут ми демонструємо широкий спектр можливих стратегій кодування, щоб ти міг порівняти їх між собою й зрозуміти, що взагалі можливо. Є кілька дуже загальних тверджень щодо корисності методів кодування. Наприклад, efficient_su2 (див. нижче) з повною схемою заплутування набагато більш схильний захоплювати квантові особливості даних, ніж методи, що породжують добуткові стани (наприклад, z_feature_map). Але це не означає, що efficient_su2 є достатнім або достатньо підходящим для твого набору даних, щоб забезпечити квантове прискорення. Це вимагає ретельного розгляду структури даних, що моделюються або класифікуються. Також існує компроміс щодо глибини схеми: багато відображень ознак, що повністю заплутують кубіти в схемі, дають дуже глибокі схеми — надто глибокі для отримання придатних результатів на сучасних квантових комп'ютерах.

Позначення

Набір даних — це множина з MM векторів даних: X={x(j)j[M]}\text{X} = \{\vec{x}^{(j)}\,|\,j\in [M]\}, де кожен вектор є NN-вимірним, тобто x(j)=(x1(j),,xN(j))RN\vec{x}^{(j)}=(\vec{x}^{(j)}_1,\ldots,\vec{x}^{(j)}_N)\in\mathbb{R}^N. Це можна розширити на комплексні ознаки. У цьому уроці ми іноді використовуватимемо ці позначення для повного набору (X)(\text{X}) та його конкретних елементів, наприклад x(j)\vec{x}^{(j)}. Але переважно ми говоритимемо про завантаження одного вектора з набору даних за раз і часто просто посилатимемося на один вектор із NN ознак як x\vec{x}.

Крім того, для посилання на відображення ознак Φ\Phi вектора даних x\vec{x} зазвичай використовують символ Φ(x)\Phi(\vec{x}). У квантових обчисленнях зокрема прийнято позначати відображення через U(x)U(\vec{x}) — нотація, що підкреслює унітарну природу цих операцій. Обидва символи можна правомірно використовувати для одного й того ж; обидва є відображеннями ознак. У цьому курсі ми зазвичай використовуємо:

  • Φ(x)\Phi(\vec{x}) — коли говоримо про відображення ознак у машинному навчанні загалом, і
  • U(x)U(\vec{x}) — коли обговорюємо реалізації відображень ознак у вигляді схем.

Нормалізація та втрата інформації

У класичному машинному навчанні ознаки тренувальних даних часто «нормалізують» або масштабують, що нерідко покращує роботу моделі. Один із поширених способів — мін-макс нормалізація або стандартизація. При мін-макс нормалізації стовпці ознак матриці даних X\text{X} (скажімо, ознака kk) нормалізуються:

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}]\}}

де min і max — мінімум і максимум ознаки kk по MM векторам даних у наборі X\text{X}. Усі значення ознак потрапляють у одиничний інтервал: xk(i)[0,1]x^{'(i)}_k \in [0,1] для всіх i[M]i\in [M], k[N]k\in[N].

Нормалізація — також фундаментальне поняття в квантовій механіці та квантових обчисленнях, але воно дещо відрізняється від мін-макс нормалізації. Нормалізація в квантовій механіці вимагає, щоб довжина (у контексті квантових обчислень — 2-норма) вектора стану ψ|\psi\rangle дорівнювала одиниці: ψ=ψψ=1\|\psi\|=\sqrt{\langle\psi|\psi\rangle} = 1, що забезпечує суму ймовірностей вимірювань, рівну 1. Стан нормалізується діленням на 2-норму, тобто масштабуванням

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

У квантових обчисленнях і квантовій механіці це не нормалізація, яку люди нав'язують даним, а фундаментальна властивість квантових станів. Залежно від схеми кодування це обмеження може впливати на масштабування твоїх даних. Наприклад, при амплітудному кодуванні (див. нижче) вектор даних нормалізується x(j)=1\vert\vec{x}^{(j)}\vert = 1, як того вимагає квантова механіка, і це впливає на масштаб даних, що кодуються. При фазовому кодуванні рекомендується масштабувати значення ознак так, щоб xi(j)(0,2π]\vec{x}^{(j)}_i \in (0,2\pi], щоб уникнути втрати інформації через ефект модуля 2π2\pi при кодуванні у фазовий кут кубіта[1,2].

Методи кодування

У наступних кількох розділах ми посилатимемося на невеликий приклад класичного набору даних Xex\text{X}_\text{ex}, що складається з M=5M=5 векторів даних, кожен із N=3N=3 ознаками:

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)\}

У введених вище позначеннях можна сказати, наприклад, що 1st1^\text{st} ознака 4th4^\text{th} вектора даних нашого набору Xex\text{X}_{\text{ex}} є x1(4)=5\vec{x}^{(4)}_1 = 5.

Базисне кодування

Базисне кодування кодує класичний рядок із PP бітів у обчислювальний базисний стан PP-кубітної системи. Розглянемо, наприклад, 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). Це можна представити як 44-бітний рядок (0101)(0101), а в 44-кубітній системі — як квантовий стан 0101|0101\rangle. Загалом, для PP-бітного рядка: xk(j)=(b1,b2,...,bP)\vec{x}^{(j)}_k = (b_1, b_2, ... , b_P), відповідний PP-кубітний стан є xk(j)=b1,b2,...,bP|x^{(j)}_k\rangle = | b_1, b_2, ... , b_P \rangle, де bn{0,1}b_n \in \{0,1\} при n=1,,Pn = 1 , \dots , P. Зверни увагу, що це стосується лише однієї ознаки.

Базисне кодування у квантових обчисленнях представляє кожен класичний біт як окремий кубіт, відображаючи двійкове представлення даних безпосередньо на квантові стани в обчислювальному базисі. Коли потрібно закодувати кілька ознак, кожна ознака спершу перетворюється у двійкову форму, а потім призначається окремій групі кубітів — по одній групі на ознаку — де кожен кубіт відповідає одному біту двійкового представлення цієї ознаки.

Як приклад, закодуємо вектор (5, 7, 0).

Припустимо, всі ознаки зберігаються у чотирьох бітах (більше, ніж потрібно, але достатньо для представлення будь-якого однозначного числа в десятковій системі):

5 → binary 0101

7 → binary 0111

0 → binary 0000

Ці бітові рядки призначаються трьом наборам із чотирьох кубітів кожен, тому загальний 12-кубітний базисний стан:

010101110000∣0101 0111 0000⟩

Тут перші чотири кубіти представляють першу ознаку, наступні чотири — другу ознаку, а останні чотири — третю ознаку. Наведений нижче код перетворює вектор даних (5,7,0) у квантовий стан і узагальнений для роботи з іншими однозначними ознаками.

# 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

Перевір своє розуміння

Прочитай питання нижче, подумай над відповіддю, а потім натисни на трикутник, щоб побачити рішення.

Напиши код для кодування першого вектора нашого прикладного набору даних Xex\text{X}_{\text{ex}}:

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

за допомогою базисного кодування.

Відповідь:

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')

Амплітудне кодування

Амплітудне кодування кодує дані в амплітуди квантового стану. Воно представляє нормалізований класичний NN-вимірний вектор даних x(j)\vec{x}^{(j)} як амплітуди nn-кубітного квантового стану ψ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

де NN — та сама розмірність векторів даних, що й раніше, xi(j)\vec{x}^{(j)}_iii-й елемент x(j)\vec{x}^{(j)}, а i|i\rangleii-й обчислювальний базисний стан. Тут α\alpha — константа нормалізації, яка визначається з даних, що кодуються. Це умова нормалізації, яку накладає квантова механіка:

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

Загалом це відрізняється від мін-макс нормалізації, що застосовується до кожної ознаки по всіх векторах даних. Те, як саме це буде вирішуватися, залежатиме від твоєї задачі. Але від умови нормалізації квантової механіки, наведеної вище, нікуди не дітися.

При амплітудному кодуванні кожна ознака вектора даних зберігається як амплітуда різного квантового стану. Оскільки система з nn кубітів має 2n2^n амплітуд, амплітудне кодування NN ознак вимагає nlog2(N)n \ge \mathrm{log}_2(N) кубітів.

Як приклад, закодуємо перший вектор нашого прикладного набору даних Xex\text{X}_\text{ex}, x(1)=(4,8,5)\vec{x}^{(1)} = (4,8,5), за допомогою амплітудного кодування. Нормалізуючи отриманий вектор, отримуємо:

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}

і отриманий 2-кубітний квантовий стан буде:

ψ(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)

У наведеному прикладі кількість ознак вектора N=3N=3 не є степенем 2. Коли NN не є степенем 2, ми просто вибираємо кількість кубітів nn таку, що 2nN2^n\geq N, і доповнюємо вектор амплітуд «нейтральними» константами (тут — нулем).

Як і при базисному кодуванні, після обчислення стану, що кодуватиме наш набір даних, у Qiskit можна скористатися функцією initialize для його підготовки:

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

Перевага амплітудного кодування — вже згадана вимога лише log2(N)\mathrm{log}_2(N) кубітів. Однак наступні алгоритми мають оперувати амплітудами квантового стану, а методи підготовки та вимірювання квантових станів зазвичай є неефективними.

Перевір своє розуміння

Прочитай питання нижче, подумай над відповідями, а потім натисни на трикутники, щоб побачити рішення.

Запиши нормалізований стан для кодування такого вектора (складеного з двох векторів нашого прикладного набору даних):

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

за допомогою амплітудного кодування.

Відповідь:

Щоб закодувати 6 чисел, нам потрібно мати принаймні 6 доступних станів, на амплітудах яких можна закодувати дані. Для цього знадобляться 3 кубіти. Використовуючи невідомий коефіцієнт нормалізації α\alpha, запишемо:

ψ=α(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)

Зверни увагу, що

ψψ=α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}}

Отже, нарешті,

ψ=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)

Для того ж вектора даних x=(9,8,6,2,9,2)\vec{x}=(9,8,6,2,9,2) напиши код для створення схеми, що завантажує ці ознаки за допомогою амплітудного кодування.

Відповідь:

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"

Може знадобитися працювати з дуже великими векторами даних. Розглянь вектор

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).

Напиши код для автоматизації нормалізації та генерації квантової схеми для амплітудного кодування.

Відповідь:

Є багато можливих відповідей. Ось код, що виводить кілька проміжних кроків:

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"

Чи бачиш ти переваги амплітудного кодування над базисним? Якщо так, поясни.

Відповідь:

Може бути кілька відповідей. Одна з них: оскільки базисні стани впорядковані фіксовано, це амплітудне кодування зберігає порядок закодованих чисел. Крім того, воно, як правило, є більш щільним.

Перевага амплітудного кодування полягає в тому, що для NN-вимірного (NN-ознакового) вектора даних xx\vec{x}\rightarrow|\vec{x}\rangle потрібно лише log2(N)\log_2(N) кубітів. Однак амплітудне кодування загалом є неефективною процедурою, що вимагає довільної підготовки стану, яка є експоненційною за кількістю вентилів CNOT. Іншими словами, підготовка стану має поліноміальну часову складність O(N)\mathcal O(N) за кількістю вимірів, де N=2nN = 2^n, а nn — кількість кубітів. Амплітудне кодування «забезпечує експоненційну економію простору ціною експоненційного збільшення часу»[3]; однак у певних випадках можна досягти часової складності O(logN)\mathcal O(\log N)[4]. Для наскрізного квантового прискорення необхідно враховувати часову складність завантаження даних.

Кутове кодування

Кутове кодування становить інтерес у багатьох моделях QML, що використовують відображення ознак Паулі, наприклад квантові машини опорних векторів (QSVM) та варіаційні квантові схеми (VQC), серед інших. Кутове кодування тісно пов'язане з фазовим кодуванням і щільним кутовим кодуванням, які розглядаються нижче. Тут ми використовуватимемо «кутове кодування» для позначення обертання в θ\theta, тобто обертання від осі zz, що виконується, наприклад, за допомогою вентиля RXR_X або RYR_Y[1,3]. Насправді можна кодувати дані в будь-якому обертанні або їх комбінації. Але RYR_Y є загальноприйнятим у літературі, тому ми наголошуємо саме на ньому.

При застосуванні до одного кубіта кутове кодування надає обертання навколо осі Y, пропорційне значенню даних. Розглянемо кодування однієї (kthk^\text{th}) ознаки з jthj^\text{th} вектора даних у наборі, 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.

Альтернативно, кутове кодування можна виконувати за допомогою вентилів RX(θ)R_X(\theta), однак закодований стан матиме комплексну відносну фазу порівняно з RY(θ)R_Y(\theta).

Кутове кодування відрізняється від двох попередніх розглянутих методів у кількох аспектах. При кутовому кодуванні:

  • Кожне значення ознаки відображається на відповідний кубіт, xk(j)Qk\vec{x}^{(j)}_k \rightarrow Q_k, залишаючи кубіти у добутковому стані.
  • Кодується одне числове значення за раз, а не цілий набір ознак точки даних.
  • Для NN ознак даних потрібно nn кубітів, де nNn\leq N. Тут часто виконується рівність. Ми побачимо, як можливе n<Nn<N, у наступних кількох розділах.
  • Отримана схема має константну глибину (як правило, глибина дорівнює 1 до транспіляції).

Квантова схема константної глибини робить цей метод особливо придатним для сучасного квантового обладнання. Додаткова перевага кодування даних за допомогою θ\theta (і зокрема, нашого вибору кутового кодування по осі Y) полягає в тому, що воно створює квантові стани з дійсними значеннями, що може бути корисним для певних застосувань. При обертанні навколо осі Y дані відображаються вентилем обертання RY(θ)R_Y(\theta) на дійсний кут θ(0,2π]\theta \in (0, 2\pi] (Qiskit RYGate). Як і при фазовому кодуванні (див. нижче), рекомендується масштабувати дані так, щоб xk(j)(0,2π]\vec{x}^{(j)}_k \in (0,2\pi], щоб запобігти втраті інформації та іншим небажаним ефектам.

Наступний код Qiskit обертає один кубіт із початкового стану 0|0\rangle, щоб закодувати значення даних xk(j)=12π\vec{x}^{(j)}_k=\frac{1}{2}\pi.

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

Визначимо функцію для візуалізації дії на вектор стану. Деталі визначення функції не важливі, але здатність візуалізувати вектори стану та їх зміни є важливою.

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

Це була лише одна ознака одного вектора даних. При кодуванні NN ознак у кути обертання nn кубітів, скажімо для jthj^\text{th} вектора даних x(j)=(x1,...,xN),\vec{x}^{(j)} = (x_1,...,x_N), закодований добутковий стан матиме такий вигляд:

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

Зазначимо, що це еквівалентно

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

Перевір себе

Прочитай запитання нижче, подумай над відповідями, а потім натисни трикутники, щоб побачити рішення.

Закодуй вектор даних x=(0,π/4,π/2)\vec{x} = (0, \pi/4, \pi/2) за допомогою кутового кодування, як описано вище.

Відповідь:

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;

Якщо використовувати кутове кодування, як описано вище, скільки кубітів потрібно для кодування 5 ознак?

Відповідь: 5

Фазове кодування

Фазове кодування дуже схоже на кутове кодування, описане вище. Фазовий кут кубіта — це речовий кут ϕ\phi навколо осі zz від осі +xx. Дані відображаються за допомогою фазового повороту P(ϕ)=eiϕ/2RZ(ϕ)P(\phi) = e^{i\phi/2}R_Z(\phi), де ϕ(0,2π]\phi \in (0,2\pi] (детальніше дивись у Qiskit PhaseGate). Рекомендується масштабувати дані так, щоб xk(j)(0,2π]\vec{x}^{(j)}_k \in (0,2\pi]. Це запобігає втраті інформації та іншим потенційно небажаним ефектам[1,2].

Кубіт зазвичай ініціалізується у стані 0|0\rangle, який є власним станом оператора фазового повороту — тобто стан кубіта спочатку потрібно повернути, перш ніж реалізувати фазове кодування. Тому логічно ініціалізувати стан воротами Адамара: H0=+=12(0+1)H|0\rangle = |+\rangle = \textstyle\frac{1}{\sqrt{2}}(|0\rangle + |1\rangle). Фазове кодування на одному кубіті означає надання відносної фази, пропорційної значенню даних:

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).

Процедура фазового кодування відображає кожне значення ознаки на фазу відповідного кубіта, xk(j)Qk\vec{x}^{(j)}_k \rightarrow Q_k. Загалом фазове кодування має глибину схеми 2, включаючи шар Адамара, що робить його ефективною схемою кодування. Фазово-закодований багатокубітний стан (nn кубітів для N=nN=n ознак) є добутковим станом:

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).

Наведений нижче код Qiskit спочатку готує початковий стан одного кубіта, обертаючи його воротами Адамара, а потім знову обертає його фазовими воротами, щоб закодувати ознаку даних xk(j)=12π\vec{x}^{(j)}_k=\frac{1}{2}\pi.

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

Обертання у ϕ\phi можна візуалізувати за допомогою функції plot_Nstates, яку ми визначили раніше.

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

Output of the previous code cell

На графіку сфери Блоха показано поворот навколо осі Z: +P(12π)+|+\rangle \rightarrow P(\frac{1}{2}\pi)|+\rangle, де xk(j)=12π\vec{x}^{(j)}_k=\frac{1}{2}\pi. Світло-зелена стрілка показує кінцевий стан.

Фазове кодування використовується в багатьох квантових картах ознак, зокрема у ZZ і ZZZZ картах ознак, а також у загальних картах ознак Паулі та інших.

Перевір себе

Прочитай запитання нижче, подумай над відповідями, а потім натисни трикутники, щоб побачити рішення.

Скільки кубітів потрібно для зберігання 8 ознак за допомогою фазового кодування, як описано вище?

Відповідь: 8

Напиши код для вектора 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) з використанням фазового кодування.

Відповідь:

Варіантів може бути багато. Ось один приклад:

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;

Щільне кутове кодування

Щільне кутове кодування (ЩКК, Dense Angle Encoding — DAE) — це комбінація кутового та фазового кодування. ЩКК дозволяє закодувати два значення ознак в одному кубіті: один кут — обертанням навколо осі Y, а другий — обертанням навколо осі zz: xk(j),\vec{x}^{(j)}_k, x(j)θ,ϕ\vec{x}^{(j)}_\ell \rightarrow \theta, \phi. Воно кодує дві ознаки таким чином:

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.

Кодування двох ознак даних на один кубіт дає зменшення необхідної кількості кубітів у 22 рази. Узагальнюючи це на більшу кількість ознак, вектор даних x=(x1,...,xN)\vec{x} = (x_1,...,x_N) можна закодувати як:

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

ЩКК можна узагальнити до довільних функцій двох ознак замість синусоїдальних функцій, використаних тут. Це називається загальним кубітним кодуванням (general qubit encoding)[7].

Як приклад ЩКК, наведений нижче код кодує та візуалізує кодування ознак x1=θ=3π/8x_1=\theta = 3\pi/8 і 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

Перевір себе

Прочитай запитання нижче, подумай над відповідями, а потім натисни трикутники, щоб побачити рішення.

Виходячи з викладеного вище, скільки кубітів потрібно для кодування 6 ознак за допомогою щільного кодування?

Відповідь: 3

Напиши код для завантаження вектора 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) з використанням щільного кутового кодування.

Відповідь:

Зверни увагу, що список доповнено нулем «0», щоб уникнути проблеми з одним невикористаним параметром у схемі кодування.

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;

Кодування за допомогою вбудованих карт ознак

Кодування у довільних точках

Кутове кодування, фазове кодування та щільне кодування формують добуткові стани, де кожна ознака кодується на окремому кубіті (або дві ознаки на кубіт). Це відрізняє їх від базисного та амплітудного кодування, які використовують заплутані стани. Тут немає відповідності 1:1 між ознакою даних і кубітом. Наприклад, в амплітудному кодуванні одна ознака може бути амплітудою стану 01|01\rangle, а інша — амплітудою стану 10|10\rangle. Загалом методи, що кодують у добуткових станах, дають більш дрібні схеми та можуть зберігати 1 або 2 ознаки на кожному кубіті. Методи, що використовують заплутаність та пов'язують ознаку зі станом, а не з кубітом, дають глибші схеми та в середньому можуть зберігати більше ознак на кубіт.

Проте кодування не обов'язково має бути повністю у добуткових або повністю у заплутаних станах, як в амплітудному кодуванні. Фактично багато схем кодування, вбудованих у Qiskit, дозволяють кодувати як до, так і після шару заплутання — на відміну від кодування лише на початку. Це відоме як «повторне завантаження даних» (data reuploading). Для ознайомлення з пов'язаними роботами дивись посилання [5] та [6].

У цьому розділі ми будемо використовувати та візуалізувати кілька вбудованих схем кодування. Усі методи в цьому розділі кодують NN ознак як повороти на NN параметризованих воротах на nn кубітах, де nNn \leq N. Зверни увагу, що максимізація завантаження даних для заданої кількості кубітів — не єдиний критерій. У багатьох випадках глибина схеми може бути ще важливішим фактором, ніж кількість кубітів.

Efficient SU2

Поширений і корисний приклад кодування із заплутанням — схема Qiskit efficient_su2. Вражає те, що ця схема може, наприклад, кодувати 8 ознак лише на 2 кубітах. Подивимося на це і спробуємо зрозуміти, як це можливо.

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

Записуючи стан, ми будемо дотримуватися конвенції Qiskit, де найменш значущі кубіти розташовуються праворуч, як у q2,q1,q0|q_2,q_1,q_0\rangle або q2q1q0.|q_2\rangle\otimes|q_1\rangle\otimes|q_0\rangle. Ці стани можуть дуже швидко ускладнюватися, і цей рідкісний приклад допомагає пояснити, чому такі стани рідко записують явно.

Наша система починається у стані 00.|00\rangle. До першого бар'єра (точка, яку ми позначаємо b1b1) наші стани такі:

ψ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)

Це просто щільне кодування, яке ми вже бачили. Тепер після воріт CNOT, у другому бар'єрі (b2b2), наш стан такий:

ψ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}

Тепер застосовуємо останній набір однокубітних поворотів і збираємо подібні стани, щоб отримати:

ψ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*}

Це, мабуть, важко розібрати. Натомість просто відступи назад і подумай, скільки параметрів ми завантажили у стан: вісім. При цьому у нас лише чотири обчислювальні базисні стани. На перший погляд може здатися, що ми завантажили більше параметрів, ніж має сенс, оскільки кінцевий стан можна записати як ψfinal=c000+c101+c210+c311\psi_\text{final} = c_0|00\rangle+c_1|01\rangle+c_2|10\rangle+c_3|11\rangle. Зверни увагу, однак, що кожен коефіцієнт є комплексним числом! У такому записі:

ψ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

Видно, що у нас справді є вісім параметрів у стані, на яких можна закодувати вісім ознак.

Збільшуючи кількість кубітів і кількість повторень шарів заплутання та поворотів, можна кодувати значно більше даних. Явний запис хвильових функцій швидко стає нездійсненним. Але ми все ще можемо спостерігати кодування в дії. Тут ми кодуємо вектор даних x\vec{x} з 12 ознаками у 3-кубітну схему efficient_su2, використовуючи кожні з параметризованих воріт для кодування різних ознак.

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)

У цьому векторі даних ознаки показані у певному порядку. Окремо від контексту не має значення, кодуються вони в цьому порядку чи у зворотному. Важливо відстежувати порядок і бути послідовним. Зверни увагу на схемі, що efficient_su2 передбачає певний порядок кодування: спочатку заповнюється перший шар параметризованих воріт від кубіта 0 до кубіта 2, а потім відбувається перехід до наступного шару. Це не є ані відповідним, ані невідповідним нотації з молодшим байтом (little-endian), оскільки тут порядок ознак даних не можна встановити за кубітом апріорі, до того як задано схему кодування.

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

Замість збільшення кількості кубітів можна збільшити кількість повторень шарів заплутання та поворотів. Але є межі корисності такої кількості повторень. Як уже зазначалося, існує компроміс: схеми з більшою кількістю кубітів або більшою кількістю повторень шарів заплутання та поворотів можуть зберігати більше параметрів, але це супроводжується збільшенням глибини схеми. Ми повернемося до глибин деяких вбудованих карт ознак далі. Кілька наступних методів кодування, вбудованих у Qiskit, містять у своїй назві словосполучення «карта ознак» (feature map). Повторимо: кодування даних у квантову схему і є відображенням ознак у тому сенсі, що воно переносить дані у новий простір — простір Гільберта задіяних кубітів. Відношення між розмірністю вихідного простору ознак і простором Гільберта залежатиме від схеми, яку ти використовуєш для кодування.

Карта ознак ZZ

Карта ознак ZZ (КОZ, ZZ Feature Map — ZFM) може бути інтерпретована як природне розширення фазового кодування. КОZ складається з чергуючих шарів однокубітних воріт: шарів воріт Адамара та шарів фазових воріт. Нехай вектор даних x\vec{x} має NN ознак. Квантова схема, що виконує відображення ознак, подається у вигляді унітарного оператора, який діє на початковий стан:

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

де 0N|0\rangle^{\otimes N}NN-кубітний основний стан. Ця нотація використовується для узгодженості з посиланням [4] Havlicek et al. Ознаки даних xix_i відображаються один до одного з відповідними кубітами. Наприклад, якщо у векторі даних є 8 ознак, тоді використовується 8 кубітів. Схема КОZ складається з rr повторень підсхеми, що включає шари воріт Адамара та шари фазових воріт. Шар Адамара складається з воріт Адамара, що діють на кожен кубіт у nn-кубітному регістрі, HHH=HnH \otimes H \otimes \dots \otimes H = H^{\otimes n}, на одному й тому самому кроці алгоритму. Це описання також стосується шару фазових воріт, у якому на ii-й кубіт діють ворота P(xi)P(\vec{x}_i). Кожні ворота PP мають одну ознаку як аргумент, але шар фазових воріт (P(x1)P(xk)P(xN)P(\vec{x}_1)\otimes\ldots P(\vec{x}_k)\otimes\ldots P(\vec{x}_N)) є функцією вектора даних. Повний унітарний оператор схеми КОZ з одним повторенням такий:

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 повторень цього унітарного оператора дадуть:

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]

Ознаки даних xkx_k відображаються на фазові ворота однаково у всіх rr повтореннях. Стан карти ознак КОZ є добутковим станом і ефективно моделюється на класичному комп'ютері[4].

Для початку розглянемо невеликий приклад: двокубітна схема КОZ кодується за допомогою Qiskit і малюється, щоб показати просту структуру схеми. У прикладі реалізується одне повторення r=1r=1 із вектором даних x=(12π,13π)\vec{x} = \left(\textstyle\frac{1}{2}\pi, \textstyle\frac{1}{3}\pi\right). Зверни увагу, що це записано у стандартному порядку вектора в Python, тобто 00-й елемент — це 12π.\textstyle\frac{1}{2}\pi. Ми можемо закодувати цю 00-у ознаку на 00-й кубіт або на NN-й. Знову ж таки, не завжди можна встановити єдине відображення 1:1 між порядком ознак і порядком кубітів, оскільки різні карти ознак кодують різну кількість ознак на кожен кубіт. Знову ж таки, важливо усвідомлювати, де кодується кожна ознака. При передачі списку параметрів до карти ознак ZZ вона кодуватиме ознаку 0 зі списку на найменш значущий кубіт із параметризованим вентилем, тобто на кубіт 0. Тому ми будемо дотримуватися цієї конвенції, роблячи це вручну. Ми закодуємо 12π\textstyle\frac{1}{2}\pi на 00-й кубіт, а 13π\textstyle\frac{1}{3}\pi — на 11-й кубіт.

Унітарний оператор схеми КОZ діє на початковий стан таким чином:

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).

Формулу перегрупували навколо тензорного добутку, щоб підкреслити операції на кожному кубіті. Наведений нижче код Qiskit явно використовує ворота Адамара та фазові ворота, щоб показати структуру КОZ:

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

Тепер закодуємо той самий вектор даних x=(12π,13π)\vec{x} = \left(\textstyle\frac{1}{2}\pi, \textstyle\frac{1}{3}\pi\right) у схему КОZ з трьома повтореннями, r=3r=3, використовуючи клас Qiskit z_feature_map, що загалом дає нам квантову карту ознак UZFM(x)\mathscr{U}_{\text{ZFM}}(\vec{x}). За замовчуванням у класі z_feature_map параметри β\beta множаться на 2 перед відображенням у фазові ворота: βP(θ=2β)\beta \rightarrow P(\theta = 2\beta). Щоб відтворити ті самі кодування, що й вище, ділимо на 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

Очевидно, це інше відображення порівняно з ручним розрахунком вище, але зверни увагу на послідовність у порядку параметрів: 12π\textstyle\frac{1}{2}\pi знову закодовано на 00-й кубіт.

Ти можеш використовувати КОZ через клас ZFM у Qiskit; також можеш використовувати цю структуру як натхнення для побудови власного відображення ознак.

Карта ознак ZZZZ

Карта ознак ZZZZ (КOZZ, ZZZZ Feature Map — ZZFM) розширює КОZ, включаючи двокубітні заплутуючі ворота, а саме ворота ZZZZ-повороту RZZ(θ)R_{ZZ}(\theta). Вважається, що КOZZ загалом важко обчислити на класичному комп'ютері, на відміну від КОZ.

RZZ(θ)R_{ZZ}(\theta) реалізує ZZZZ-взаємодію та є максимально заплутуючим при θ=12π\theta = \textstyle{\frac{1}{2}}\pi. RZZ(θ)R_{ZZ}(\theta) можна розкласти у серію воріт на двох кубітах, як показано в наведеному нижче коді Qiskit із використанням ворота RZZ та методу класу QuantumCircuitdecompose. Ми кодуємо одну ознаку вектора даних 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

Як це часто буває, ми бачимо це у вигляді єдиного вентиля, поки не використаємо .decompose(), щоб побачити всі складові ворота.

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

Output of the previous code cell

Дані відображаються за допомогою фазового повороту P(θ)=eiθ/2RZ(θ)P(\theta) = e^{i\theta/2}R_Z(\theta) на другому кубіті. Ворота RZZ(θ)R_{ZZ}(\theta) заплутують два кубіти, на які вони діють, зі ступенем заплутаності, що визначається значенням закодованої ознаки.

Повна схема КOZZ складається з воріт Адамара та фазових воріт, як у КОZ, після чого йде заплутання, описане вище. Одне повторення схеми КOZZ:

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},

де UZZ(x)U_{ZZ}(\vec{x}) містить шар ZZ-воріт, структурований за схемою заплутання. Кілька схем заплутання показано в блоках коду нижче. Структура UZZ(x)U_{ZZ}(\vec{x}) також включає функцію, що комбінує ознаки даних кубітів, які заплутуються, таким чином. Скажімо, ворота RZZR_{ZZ} застосовуються до кубітів pp і qq. У шарі фазових воріт на цих кубітах є фазові ворота, що кодують xp\vec{x}_p і xq\vec{x}_q відповідно. Аргумент θq,p\theta_{q,p} воріт RZZ,q,p(θq,p)R_{ZZ,q,p}(\theta_{q,p}) буде не просто однією з цих ознак чи іншою, а функцією, яку часто позначають ϕ\phi (не плутати з азимутальним кутом):

θ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).

Ми побачимо це в кількох прикладах нижче. Розширення на кілька повторень аналогічне випадку 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].

Оскільки оператори ускладнилися, спочатку закодуємо вектор даних x=(x0,x1)\vec{x} = (x_0, x_1) з допомогою двокубітної КOZZ з одним повторенням, використовуючи такий код:

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

За замовчуванням у Qiskit ознаки (x1,x2)(\vec{x}_1, \vec{x}_2) відображаються разом у RZZ(θ)R_{ZZ}(\theta) за допомогою цієї функції відображення: θ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). Qiskit дозволяє користувачу налаштовувати функцію ϕ\phi (або ϕS\phi_S, де SS — множина пар кубітів, з'єднаних через ворота RZZR_{ZZ}) як крок попередньої обробки.

Переходячи до чотиривимірного вектора даних x=(x1,x2,x3,x4)\vec{x} = (\vec{x}_1, \vec{x}_2, \vec{x}_3, \vec{x}_4) і відображення на чотирикубітну КOZZ з одним повторенням, ми можемо почати бачити відображення ϕ\phi для різних пар кубітів. Також можна побачити значення «лінійного» заплутання:

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

У схемі лінійного заплутання заплутуються найближчі сусідні (за нумерацією) пари кубітів у цій схемі. У Qiskit є інші вбудовані схеми заплутання, зокрема circular та full.

Карта ознак Паулі

Карта ознак Паулі (КОП, Pauli Feature Map — PFM) — це узагальнення КОZ і КOZZ до використання довільних воріт Паулі. Карта ознак Паулі має дуже схожу форму з двома попередніми картами ознак. Для rr повторень кодування NN ознак вектора x\vec{x}:

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

Для КОП U(x)U(\vec{x}) узагальнюється до унітарного оператора розкладу Паулі. Тут ми представляємо більш узагальнену форму розглянутих до цього часу карт ознак:

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),

де σi\sigma_i — оператор Паулі, σiI,X,Y,Z\sigma_i \in {I,X,Y,Z}. Тут I\mathcal{I} — множина всіх зв'язків між кубітами, визначених картою ознак, включаючи множину кубітів, на які діють однокубітні ворота. Тобто для карти ознак, у якій на кубіт 0 діють фазові ворота, а на кубіти 2 і 3 діють ворота RZZR_{ZZ}, множина I\mathcal{I} включатиме {{0},{2,3}}\{\{0\},\{2,3\}\}. SS пробігає по всіх елементах цієї множини. У попередніх картах ознак функція ϕS(x)\phi_S(\vec{x}) була пов'язана виключно або з однокубітними воротами, або виключно з двокубітними воротами. Тут ми визначаємо її в загальному вигляді:

ϕ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}

Для документації дивись документацію класу Qiskit Pauli feature map). У КOZZ оператор σi\sigma_i обмежений ZiZ_i.

Один зі способів зрозуміти наведений вище унітарний оператор — через аналогію з пропагатором у фізичній системі. Наведений вище унітарний оператор є оператором унітарної еволюції, exp(itH)\exp(it\mathcal{H}), для гамільтоніана H\mathcal{H}, подібного до моделі Ізінга, де часовий параметр tt замінено значеннями даних для керування еволюцією. Розклад цього унітарного оператора дає схему КОП. Зв'язки заплутання в SS можна інтерпретувати як ізінгові зв'язки у спіновій ґратці. Розглянемо приклад з операторами Паулі YY та XXXX, що представляють ці ізінгові взаємодії. Qiskit надає клас pauli_feature_map для створення КОП з вибором однокубітних і nn-кубітних воріт, які в цьому прикладі передаватимуться як рядки Паулі 'Y' та 'XX'. Зазвичай nn дорівнює 1 або 2 для однокубітних та двокубітних взаємодій відповідно. Схема заплутання — «лінійна» (linear), тобто з'єднуються лише найближчі сусідні кубіти в квантовій схемі. Зверни увагу, що це не відповідає найближчим сусіднім кубітам на самому квантовому комп'ютері, оскільки ця квантова схема є рівнем абстракції.

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 надає параметр α\alpha у картах ознак Паулі для керування масштабуванням поворотів Паулі.

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)

Стандартне значення α\alpha дорівнює 22. Оптимізуючи його значення в інтервалі, наприклад, [0,4],[0,4], можна краще узгодити квантове ядро з даними.

Нижче ми візуалізуємо різні карти ознак Паулі для двокубітних схем, щоб краще уявити діапазон можливостей.

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)

Вивід попередньої комірки коду

Наведене вище, звичайно, можна розширити, додавши інші перестановки та повторення матриць Паулі. Тебе заохочують поекспериментувати з цими варіантами.

Огляд вбудованих карт ознак

Ти познайомився з кількома схемами кодування даних у квантову схему:

  • Базисне кодування
  • Амплітудне кодування
  • Кутове кодування
  • Фазове кодування
  • Щільне кодування

Ти бачив, як будувати власні карти ознак за допомогою цих схем кодування, а також розглянув чотири вбудовані карти ознак, які використовують кутове та фазове кодування:

  • Efficient SU2
  • Z feature map
  • ZZ feature map
  • Pauli feature map

Ці вбудовані карти ознак відрізнялися одна від одної в кількох аспектах:

  • Глибина схеми для заданої кількості закодованих ознак
  • Кількість кубітів, необхідних для заданої кількості ознак
  • Ступінь заплутування (очевидно, пов'язаний з іншими відмінностями)

Код нижче застосовує ці чотири вбудовані карти ознак для кодування набору ознак і будує графік двокубітної глибини отриманої схеми. Оскільки частота помилок двокубітних вентилів значно вища, ніж однокубітних, найбільший інтерес може становити саме глибина двокубітних вентилів. У наведеному нижче коді ми отримуємо кількість усіх вентилів у схемі, спочатку розкладаючи схему, а потім використовуючи count_ops(). Тут двокубітні вентилі, які нас цікавлять, — це вентилі cx:

# 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()

Загалом карти ознак Паулі та ZZ призводять до більшої глибини схеми й більшої кількості двокубітних вентилів порівняно з efficient_su2 та Z feature map.

Оскільки карти ознак, вбудовані в Qiskit, є широко застосовними, нам часто не потрібно проектувати власні, особливо на етапі навчання. Однак експерти з квантового машинного навчання, найімовірніше, повернуться до теми розробки власних карт ознак, коли зіткнуться з двома складними завданнями:

  1. Сучасне залізо: наявність шуму й великі накладні витрати коду виправлення помилок означають, що сьогоднішні застосунки мають враховувати такі речі, як апаратна ефективність і мінімізація глибини двокубітних вентилів.

  2. Відображення, що підходять до конкретного завдання: одна справа — сказати, що zz_feature_map, наприклад, важко симулювати класично, а отже, вона є цікавою. Зовсім інша — щоб zz_feature_map ідеально підходила саме твоєму завданню машинного навчання або набору даних. Продуктивність різних параметризованих квантових схем на різних типах даних є активною сферою досліджень.

Завершуємо нотаткою про апаратну ефективність.

Апаратно-ефективне відображення ознак

Апаратно-ефективне відображення ознак — це таке відображення, яке враховує обмеження реальних квантових комп'ютерів з метою зменшення шуму й помилок під час обчислень. При запуску квантових схем на квантових комп'ютерах найближчого покоління існує чимало стратегій зменшення шуму, притаманного залізу. Одна з основних стратегій апаратної ефективності — мінімізація глибини квантової схеми, щоб шум і декогеренція мали менше часу для спотворення обчислень. Глибина квантової схеми — це кількість вирівняних у часі кроків вентилів, необхідних для завершення всього обчислення (після оптимізації схеми)[5]. Пам'ятай, що глибина абстрактної логічної схеми може бути значно меншою, ніж глибина після транспіляції схеми для реального квантового комп'ютера.

Транспіляція — це процес перетворення квантової схеми з високорівневої абстракції у форму, готову до запуску на реальному квантовому комп'ютері, з урахуванням апаратних обмежень. Квантовий комп'ютер має власний набір однокубітних і двокубітних вентилів. Це означає, що всі вентилі в коді Qiskit мають бути транспільовані у набір нативних апаратних вентилів. Наприклад, в ibm_torino — QPU із процесором Heron r1, завершеним у 2023 році, — нативними або базисними вентилями є \{CZ, ID, RZ, SX, X\}. Це двокубітний вентиль controlled-Z і однокубітні вентилі: тотожність, ZZ-обертання, квадратний корінь із NOT і NOT відповідно, що утворюють універсальний набір. При реалізації багатокубітних вентилів як еквівалентних підсхем потрібні фізичні двокубітні вентилі CZCZ, а також інші однокубітні вентилі, доступні в залізі. Крім того, для виконання двокубітного вентиля на парі фізично не з'єднаних кубітів додаються вентилі SWAP для переміщення станів між кубітами з метою забезпечення зв'язку, що неминуче подовжує схему. Для більшого контролю й можливості налаштування конвеєр транспілятора можна керувати за допомогою Qiskit Pass Manager. Докладніше про транспіляцію дивись у документації Qiskit Transpiler.

У роботі Havlicek et al. 2019 [2] одним із способів досягнення апаратної ефективності є використання карти ознак ZZZZ, оскільки вона є розкладом другого порядку (дивись розділ «Карта ознак ZZZZ» вище). Розклад NN-го порядку містить NN-кубітні вентилі. Квантові комп'ютери IBM® не мають нативних NN-кубітних вентилів при N>2N>2, тому для їхньої реалізації потрібен розклад на двокубітні вентилі CNOT, доступні в залізі. Другий спосіб мінімізації глибини — вибір топології зв'язків ZZZZ, яка безпосередньо відповідає зв'язкам архітектури. Додатково автори оптимізують роботу, орієнтуючись на більш продуктивну, відповідним чином з'єднану апаратну підсхему. Варто також розглянути мінімізацію кількості повторень карти ознак і вибір налаштованої низькоглибинної або «лінійної» схеми заплутування замість «повної», яка заплутує всі кубіти.

Зображення кодування даних

На наведеній вище графіці показано мережу вузлів і ребер, що відповідно представляють фізичні кубіти та апаратні зв'язки. Показано карту з'єднань і продуктивність ibm_torino з усіма можливими двокубітними вентилями зв'язку CZ. Кубіти кольорово закодовані за шкалою, що базується на часі релаксації T1 у мікросекундах (мкс), де більший час T1 є кращим і відображається світлішим відтінком. Ребра з'єднань кольорово закодовані за похибкою CZ, де темніші відтінки є кращими. Інформацію про специфікацію залізо можна отримати зі схеми конфігурації апаратного бекенду IBMQBackend.configuration().

Джерела

  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()