# DANE dla przewidywania zmiennej ciągłej - funkcja sinus z drobnym szumem
import torch
123)
torch.manual_seed(
= torch.linspace(0,10,500).view(-1,1)
X_cont = torch.sin(X_cont)
y_cont = y_cont + 0.1*(torch.rand(500).view(-1,1)-0.5)
y_cont_noise
= X_cont.clone().detach().requires_grad_(True)
x_train = y_cont_noise.clone().detach().requires_grad_(True)
y_train
# wyktres
import matplotlib.pyplot as plt
=(8,4))
plt.figure(figsize-1,1), color="tab:grey", alpha=0.6, label="dokładne rozwiązanie")
plt.plot(X_cont, y_cont.view(="dane treningowe")
plt.scatter(X_cont, y_cont_noise, label"off")
plt.axis(
plt.legend() plt.show()
Obliczenia hybrydowe
W przypadku obliczeń (kwantowych) hybrydowe
oznacza strategię mieszania klasycznych i kwantowych obliczeń. Idea ta jest podstawowym elementem optymalizacji obwodów wariacyjnych, gdzie kwantowy obwód optymalizowany jest z wykorzystaniem klasycznego ko-procesora.
Najczęściej, obwody (i komputery) kwantowe będziemy wykorzystywać do oszacowania (obliczania) średnich z wyników pomiarów (wartość oczekiwana obserwabli), które złozyć mozna do pojedynczej klasycznej funkcji kosztu. Pozwola nam to oszacować jak dobrze wybrane obwody kwantowe dopasowują się do danych. Przykładem moze być model realizowany jako variational quantum eigensolver
Peruzzo 2013
W ogólności łatwo wyobrazić sobie bardziej interesujący sposób w którym mozna łączyć składniki klasyczne i kwantowe w większy i bardziej złozony układ Kazdy element (czy to kwantowy czy klasyczny) mozna w takim obrazku przedstawić jako klasyczny bądź kwantowy node
.
Klasyczne i kwantowe nody mozemy składać w dowolny acykliczny graf (DAG). Informacja w takim grafie przebiega w ustalonym kierunku oraz nie występują w nim cykle (pętle).
Jednym z przykładów takiego DAG’a są sieci neuronowe.
Poniewaz mozemy obliczać gradienty variacyjnych obwodów kwantowych, obliczenia hybrydowe są kompatybilne z algorytmem propagacji wstecznej. Potwierdza to możliwość trenowania obwodów kwantowych w taki sam sposób w jaki trenuje sie klasyczne sieci neuronowe.
Korzystając z biblioteki PyTorch możemy generować sieci neuronowe korzystając z modułu nn
. Każdy taki model składa się z elementarnych warstw (ang. layers). Bilioteka PennyLane pozwala przetworzyć obiekt QNode
do obiektu torch.nn
.
W pierwszym kroku stwórzmy dwa zestawy danych. Pierwszy dotyczyć będzie wartości ciągłej, natomiast drugi będzie realizował proces klasyfikacji.
Dane muszą być przekształcone do obiektu tensora realizowanego w bibliotece torch.
Zanim przejdziemy do daleszego etapy zdefiniujmy dodatkowe funkcje przydatne do eksploracji sieci neuronowych.
def mse(y, y_pred) -> torch.Tensor:
return torch.mean((y-y_pred)**2)
def special_loss_fn(y, y_pred) -> torch.Tensor:
return mse(y, y_pred) + torch.mean((y_pred - torch.sin(x))**2)
def train(X, Y, model, optimiser, iteration, lossfn, callback = None):
""" Dodatkowa funkcja pozwalająca wykonać trenowanie naszej sieci neuronowej"""
for i in range(iteration):
optimiser.zero_grad()= model(X)
prediction = lossfn(Y, prediction)
loss
loss.backward()
optimiser.step()if callback != None:
callback(model, loss)
= []
losses
def callback(model, loss):
losses.append(loss.item())= torch.linspace(0,10,500).view(-1,1)
x =True)
clear_output(wait= model(x).detach()
prediction =(6,2.5))
plt.figure(figsize0].detach(), torch.sin(x)[:,0].detach(), label="Exact solution", color="tab:grey", alpha=0.6)
plt.plot(x[:,0].detach(), prediction[:,0], label="QML solution", color="tab:green")
plt.plot(x[:,f"Training step {len(losses)}")
plt.title(
plt.legend()
plt.show()
=(6,2.5))
plt.figure(figsize'Lossfn Visualised')
plt.title(
plt.plot(losses) plt.show()
W następnym kroku zdefiniujmy obiekt realizujący obwód kwantowy: QNode, który chcemy podpiąć pod torch.nn
. Dla uproszczenia sytuacji przyjmiemy obwód wykorzystujący 2 kubity.
import pennylane as qml
= 2
n_qubits = qml.device("default.qubit", wires=n_qubits)
dev # NASZ kwantowy PQC - parametryzowany obwód kwantowy dla jednej warstwy ukrytej
@qml.qnode(dev)
def qnode(inputs, weights):
=range(n_qubits))
qml.AngleEmbedding(inputs, wires=range(n_qubits))
qml.BasicEntanglerLayers(weights, wiresreturn [qml.expval(qml.PauliZ(wires=i)) for i in range(n_qubits)]
Obwód ten pobiera dane wejściowe i przetwarza je za pomocą zdefiniowanego obwodu kodującego dane Angle Embedding
. Następnie wynik tej operacji,czyli dane zanurzone do przestrzeni Hilberta stanów, są przetwarzane (obracane przez parametryczne bramki z wagami) jest przez ansatz
(model kwantowy) z wykorzystaniem gotowego obwodu realizowanego jako BasicENtanglerLayers
.
Całość można zrozumieć jako jedna wartwa nn.Linear
.
Biblioteka PennyLane udostępnia obiekt TorchLayer
, który pozwala na taką transformację. Zanim jednak go użyjemy musimy utworzyć słownik z wagami.
= 5
n_layers
= {"weights": (n_layers, n_qubits)}
weight_shapes
= qml.qnn.TorchLayer(qnode, weight_shapes) qlayer
class QN(torch.nn.Module):
'''Classical -> Quantum -> Classical'''
def __init__(self, n_input: int, n_output: int, quanutm_layer):
super().__init__()
self.layers = torch.nn.Sequential(
torch.nn.Linear(n_input, n_qubits),
quanutm_layer,
torch.nn.Linear(n_qubits, n_output)
)
def forward(self, x):
return self.layers(x)
Dla przypadku estymacji funkcji sinus mamy jedną zmienną (x_cont
) wejściową która zostanie połączona z dwoma kubitami następnie na wyjściu mamy również jedną zmienną (y_cont
).
= QN(1, 1, qlayer)
reg_qmodel print(reg_qmodel)
QN(
(layers): Sequential(
(0): Linear(in_features=1, out_features=2, bias=True)
(1): <Quantum Torch Layer: func=qnode>
(2): Linear(in_features=2, out_features=1, bias=True)
)
)
from IPython.display import clear_output
=1e-3
learning_rate
= torch.optim.Adam(reg_qmodel.parameters(), lr=learning_rate)
optimiser
200, mse, callback) train(x_train, y_train, reg_qmodel, optimiser,
Klasyfikacja z wykorzystaniem kwantowej sieci neuronowej
import torch
import pennylane.numpy as np
from sklearn.datasets import make_moons
123)
torch.manual_seed(
= make_moons(n_samples=200, noise=0.1)
X, y
# create torch
= torch.from_numpy(X).to(torch.float32)
X
= torch.from_numpy(y).view(-1,1)
y_
= ["#1f77b4" if y_ == 0 else "#ff7f0e" for y_ in y] # kolorowanie
c "off")
plt.axis(0], X[:, 1], c=c)
plt.scatter(X[:,
plt.show()
= torch.scatter(torch.zeros((200, 2)), 1, y_, 1).to(torch.float32)
y_hot
# X = X.clone().detach().requires_grad_(True)
class QN2(torch.nn.Module):
'''Classical -> Quantum -> Classical'''
def __init__(self, quanutm_layer):
super().__init__()
self.layers = torch.nn.Sequential(
2, 2),
torch.nn.Linear(
quanutm_layer,2, 2),
torch.nn.Linear(=1)
torch.nn.Softmax(dim
)
def forward(self, x):
return self.layers(x)
= QN2(qlayer)
qclassifier print(qclassifier)
QN2(
(layers): Sequential(
(0): Linear(in_features=2, out_features=2, bias=True)
(1): <Quantum Torch Layer: func=qnode>
(2): Linear(in_features=2, out_features=2, bias=True)
(3): Softmax(dim=1)
)
)
= torch.optim.SGD(qclassifier.parameters(), lr=0.2)
opt = torch.nn.L1Loss() loss
y_hot.dtype, X.dtype
(torch.float32, torch.float32)
= 5
batch_size = 200 // batch_size
batches
= torch.utils.data.DataLoader(
data_loader list(zip(X, y_hot)), batch_size=5, shuffle=True, drop_last=True
)
= 6
epochs
for epoch in range(epochs):
= 0
running_loss
for xs, ys in data_loader:
opt.zero_grad()
= loss(qclassifier(xs), ys)
loss_evaluated
loss_evaluated.backward()
opt.step()
+= loss_evaluated
running_loss
= running_loss / batches
avg_loss print("Average loss over epoch {}: {:.4f}".format(epoch + 1, avg_loss))
= qclassifier(X)
y_pred = torch.argmax(y_pred, axis=1).detach().numpy()
predictions
= [1 if p == p_true else 0 for p, p_true in zip(predictions, y)]
correct = sum(correct) / len(correct)
accuracy print(f"Accuracy: {accuracy * 100}%")
Average loss over epoch 1: 0.4480
Average loss over epoch 2: 0.2601
Average loss over epoch 3: 0.1887
Average loss over epoch 4: 0.1660
Average loss over epoch 5: 0.1565
Average loss over epoch 6: 0.1561
Accuracy: 86.0%
class HybridModel(torch.nn.Module):
def __init__(self):
super().__init__()
self.clayer_1 = torch.nn.Linear(2, 4)
self.qlayer_1 = qml.qnn.TorchLayer(qnode, weight_shapes)
self.qlayer_2 = qml.qnn.TorchLayer(qnode, weight_shapes)
self.clayer_2 = torch.nn.Linear(4, 2)
self.softmax = torch.nn.Softmax(dim=1)
def forward(self, x):
= self.clayer_1(x)
x = torch.split(x, 2, dim=1)
x_1, x_2 = self.qlayer_1(x_1)
x_1 = self.qlayer_2(x_2)
x_2 = torch.cat([x_1, x_2], axis=1)
x = self.clayer_2(x)
x return self.softmax(x)
= HybridModel() model
= torch.optim.SGD(model.parameters(), lr=0.2)
opt = 6
epochs
for epoch in range(epochs):
= 0
running_loss
for xs, ys in data_loader:
opt.zero_grad()
= loss(model(xs), ys)
loss_evaluated
loss_evaluated.backward()
opt.step()
+= loss_evaluated
running_loss
= running_loss / batches
avg_loss print("Average loss over epoch {}: {:.4f}".format(epoch + 1, avg_loss))
= model(X)
y_pred = torch.argmax(y_pred, axis=1).detach().numpy()
predictions
= [1 if p == p_true else 0 for p, p_true in zip(predictions, y)]
correct = sum(correct) / len(correct)
accuracy print(f"Accuracy: {accuracy * 100}%")
Average loss over epoch 1: 0.4315
Average loss over epoch 2: 0.2551
Average loss over epoch 3: 0.1925
Average loss over epoch 4: 0.1639
Average loss over epoch 5: 0.1567
Average loss over epoch 6: 0.1542
Accuracy: 87.0%