Hello, I’m trying to implement a qGAN in pennylane with the pytorch interface, and I am running into a ValueError. I couldn’t isolate the error to figure out what is causing it. Here is the code below:
import pennylane as qml
import torch
from pennylane import numpy as np
from matplotlib import pyplot as plt
from torch.autograd import Variable
from scipy.stats import lognorm, norm, triang
import matplotlib.pyplot as plt
from torch.optim import Adam
from torch import nn
!pip install pylatexenc
num_qubits = 3 #TODO: make this like 6 or sm bs
num_layers = 3
num_discrete_values = 8
params = np.random.normal(0, np.pi, size = (num_layers * 2 + 1) * num_qubits)
params = Variable(torch.tensor(params), requires_grad = True)
# define generator
def rotation_layer(w):
for i in range(num_qubits):
qml.RY(w[i], wires=i)
def entangling_block(w):
for i in range(num_qubits):
qml.CZ(wires = [i, (i+1)%num_qubits])
dev = qml.device("default.qubit", wires=num_qubits, shots = 10000)
@qml.qnode(dev, interface='torch')
def generator(w, num_layers = 3):
rotation_layer(w[:num_qubits])
for i in range(1, num_layers*2 + 1, 2):
entangling_block(w[num_qubits * (i) : num_qubits * (i+1)])
rotation_layer(w[num_qubits * (i+1) : num_qubits * (i+2)])
return qml.probs()
# define classical discriminator
class Discriminator(nn.Module):
def __init__(self, input_size):
super(Discriminator, self).__init__()
self.linear_input = nn.Linear(input_size, 20)
self.leaky_relu = nn.LeakyReLU(0.2)
self.linear20 = nn.Linear(20, 1)
self.sigmoid = nn.Sigmoid()
def forward(self, input: torch.Tensor) -> torch.Tensor:
x = self.linear_input(input)
x = self.leaky_relu(x)
x = self.linear20(x)
x = self.sigmoid(x)
return x
# generate data
def gauss(x,mu,sigma,A):
return A*exp(-(x-mu)**2/2/sigma**2)
def generate_data(which = "log-normal", num_discrete_values = 8):
x = np.linspace(0,7, num_discrete_values)
if which == "log-normal":
rv = lognorm.pdf(x, 1)
elif which == "triangular":
"""
triangular distribution with:
lower limit l = 0
upper limit u = 7
mode m = 2
"""
a = 0
b = 7
mode = 2
list_of_candidates = range(a, b+1)
rv = triang.pdf(x, mode/(b-a), a, b)
else: #bimodal
"""
bimodal distribution with:
two superimposed Guassian distributions with
mu_1 = 0.5
sigma_1 = 1
mu_2 = 3.5
sigma_2 = 0.5
"""
gauss1 = norm.pdf(x, 0.5, 1)
gauss2 = norm.pdf(x, 3.5, 0.5)
rv = (gauss1 + gauss2) / (np.sum(gauss1) + np.sum(gauss2))
# fig, ax = plt.subplots(1, 1)
# ax.plot(x, rv,'r-', lw=5, alpha=0.6, label='lognorm pdf')
return x, rv
# cost function
def gen_loss(x): # maximize the discriminator misclassifying fake data
# print(f"gen loss input: {x}")
loss = torch.log(x)
# loss = torch.log(1 - x)
# print(f"loss: {loss}")
return -loss
def disc_loss(real, fake): # log(real) = maxed when disc correctly classifies real data; log(1 - fake) = maxed when disc correctly classifies fake data
# print(f"disc loss input: {real}, {fake}")
loss = torch.log(real) + torch.log(1 - fake)
# print(f"loss: {loss}")
return -loss # discriminator is updated via gradient ASCENT
discriminator = Discriminator(input_size = num_discrete_values)
# optimizers
lr = 0.01
b1 = 0.7 # first momentum
b2 = 0.999 # second momentum
generator_optimizer = Adam([params], lr=lr, betas=(b1, b2), weight_decay=0.005)
discriminator_optimizer = Adam(discriminator.parameters(), lr=lr, betas=(b1, b2), weight_decay=0.005)
# visualize training process
from IPython.display import clear_output
def plot_training_progress():
# we don't plot if we don't have enough data
if len(generator_loss_values) < 2:
return
clear_output(wait=True)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(18, 9))
# Generator Loss
ax1.set_title("Loss")
ax1.plot(generator_loss_values, label="generator loss", color="royalblue")
ax1.plot(discriminator_loss_values, label="discriminator loss", color="magenta")
ax1.legend(loc="best")
ax1.set_xlabel("Iteration")
ax1.set_ylabel("Loss")
ax1.grid()
# Relative Entropy
ax2.set_title("Relative entropy")
ax2.plot(entropy_values)
ax2.set_xlabel("Iteration")
ax2.set_ylabel("Relative entropy")
ax2.grid()
plt.show()
# training loop
# logic inspired by https://towardsdatascience.com/build-a-super-simple-gan-in-pytorch-54ba349920e4
import time
from scipy.stats import multivariate_normal, entropy
n_epochs = 50
num_qnn_outputs = 2**num_qubits
grid_elements, prob_data = generate_data(num_discrete_values)
generator_loss_values = []
discriminator_loss_values = []
entropy_values = []
start = time.time()
for epoch in range(n_epochs):
valid = torch.ones(num_qnn_outputs, 1, dtype=torch.float)
fake = torch.zeros(num_qnn_outputs, 1, dtype=torch.float)
# zero gradients
generator_optimizer.zero_grad()
# generate real data
real_dist = torch.tensor(prob_data, dtype=torch.float)
# print(real_dist)
# generate fake data
gen_dist = generator(params)
gen_dist = gen_dist.type(torch.float)
# print(gen_dist)
# train generator using discriminator output & true labels
# We invert the labels here and don't train the discriminator because we want the generator
# to make things the discriminator classifies as true.
discriminator_optimizer.zero_grad()
disc_out_fake = discriminator(gen_dist)
# print(disc_out_fake)
generator_loss = gen_loss(disc_out_fake)
# print(generator_loss)
generator_loss_values.append(generator_loss.detach())
generator_loss.backward(retain_graph=True) # NO GRAD???
generator_optimizer.step()
# train discrminator on both a true and a generated data and average
disc_out_real = discriminator(real_dist)
discriminator_loss = disc_loss(disc_out_real, disc_out_fake)
# print(discriminator_loss)
discriminator_loss_values.append(discriminator_loss.detach().item())
discriminator_loss.backward()
discriminator_optimizer.step()
entropy_value = entropy(gen_dist.detach().squeeze().numpy(), prob_data) # RELATIVE ENTROPY
entropy_values.append(entropy_value)
plot_training_progress()
elapsed = time.time() - start
print(f"Fit in {elapsed:2f} sec")
# TODO: initialization strategy
And here is the full error message:
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
/tmp/ipykernel_51/102425105.py in <cell line: 172>()
199 generator_loss_values.append(generator_loss.detach())
200
--> 201 generator_loss.backward(retain_graph=True) # NO GRAD???
202 generator_optimizer.step()
203
/opt/conda/envs/pennylane/lib/python3.9/site-packages/torch/_tensor.py in backward(self, gradient, retain_graph, create_graph, inputs)
394 create_graph=create_graph,
395 inputs=inputs)
--> 396 torch.autograd.backward(self, gradient, retain_graph, create_graph, inputs=inputs)
397
398 def register_hook(self, hook):
/opt/conda/envs/pennylane/lib/python3.9/site-packages/torch/autograd/__init__.py in backward(tensors, grad_tensors, retain_graph, create_graph, grad_variables, inputs)
171 # some Python versions print out the first line of a multi-line function
172 # calls in the traceback and some print out the last line
--> 173 Variable._execution_engine.run_backward( # Calls into the C++ engine to run the backward pass
174 tensors, grad_tensors_, retain_graph, create_graph, inputs,
175 allow_unreachable=True, accumulate_grad=True) # Calls into the C++ engine to run the backward pass
/opt/conda/envs/pennylane/lib/python3.9/site-packages/torch/autograd/function.py in apply(self, *args)
251 "of them.")
252 user_fn = vjp_fn if vjp_fn is not Function.vjp else backward_fn
--> 253 return user_fn(self, *args)
254
255 def apply_jvp(self, *args):
/opt/conda/envs/pennylane/lib/python3.9/site-packages/pennylane/interfaces/torch.py in backward(ctx, *dy)
167
168 with qml.tape.Unwrap(*ctx.tapes):
--> 169 vjp_tapes, processing_fn = qml.gradients.batch_vjp(
170 ctx.tapes,
171 dy,
/opt/conda/envs/pennylane/lib/python3.9/site-packages/pennylane/gradients/vjp.py in batch_vjp(tapes, dys, gradient_fn, shots, reduction, gradient_kwargs)
523 # Loop through the tapes and dys vector
524 for tape, dy in zip(tapes, dys):
--> 525 g_tapes, fn = vjp(tape, dy, gradient_fn, shots=shots, gradient_kwargs=gradient_kwargs)
526 reshape_info.append(len(g_tapes))
527 processing_fns.append(fn)
/opt/conda/envs/pennylane/lib/python3.9/site-packages/pennylane/gradients/vjp.py in vjp(tape, dy, gradient_fn, shots, gradient_kwargs)
379 pass
380
--> 381 gradient_tapes, fn = gradient_fn(tape, shots=shots, **gradient_kwargs)
382
383 def processing_fn(results, num=None):
/opt/conda/envs/pennylane/lib/python3.9/site-packages/pennylane/transforms/batch_transform.py in __call__(self, *targs, **tkwargs)
329 # Input is a quantum tape.
330 # tapes, fn = some_transform(tape, *transform_args)
--> 331 return self._tape_wrapper(*targs, **tkwargs)(qnode)
332
333 if isinstance(qnode, (qml.QNode, qml.ExpvalCost)):
/opt/conda/envs/pennylane/lib/python3.9/site-packages/pennylane/transforms/batch_transform.py in <lambda>(tape)
419
420 def _tape_wrapper(self, *targs, **tkwargs):
--> 421 return lambda tape: self.construct(tape, *targs, **tkwargs)
422
423
/opt/conda/envs/pennylane/lib/python3.9/site-packages/pennylane/transforms/batch_transform.py in construct(self, tape, *args, **kwargs)
401 tape = self.expand_fn(tape, *args, **kwargs)
402
--> 403 tapes, processing_fn = self.transform_fn(tape, *args, **kwargs)
404
405 if processing_fn is None:
/opt/conda/envs/pennylane/lib/python3.9/site-packages/pennylane/gradients/parameter_shift.py in param_shift(tape, argnum, shifts, gradient_recipes, fallback_fn, f0, broadcast, shots)
1902 return [], lambda _: np.zeros((tape.output_dim, 0))
1903
-> 1904 gradient_analysis(tape, grad_fn=param_shift)
1905 method = "analytic" if fallback_fn is None else "best"
1906 diff_methods = grad_method_validation(method, tape)
/opt/conda/envs/pennylane/lib/python3.9/site-packages/pennylane/gradients/gradient_transform.py in gradient_analysis(tape, use_graph, grad_fn)
67
68 elif (tape._graph is not None) or use_graph:
---> 69 if not any(tape.graph.has_path(op, ob) for ob in tape.observables):
70 # there is no influence of this operation on any of the observables
71 info["grad_method"] = "0"
/opt/conda/envs/pennylane/lib/python3.9/site-packages/pennylane/gradients/gradient_transform.py in <genexpr>(.0)
67
68 elif (tape._graph is not None) or use_graph:
---> 69 if not any(tape.graph.has_path(op, ob) for ob in tape.observables):
70 # there is no influence of this operation on any of the observables
71 info["grad_method"] = "0"
/opt/conda/envs/pennylane/lib/python3.9/site-packages/pennylane/circuit_graph.py in has_path(self, a, b)
491 self._graph,
492 self._graph.nodes().index(a),
--> 493 self._graph.nodes().index(b),
494 weight_fn=None,
495 default_weight=1.0,
ValueError: probs(wires=[]) is not in list
Here is the version information:
Name: PennyLane
Version: 0.28.0
Summary: PennyLane is a Python quantum machine learning library by Xanadu Inc.
Home-page: https://github.com/XanaduAI/pennylane
Author:
Author-email:
License: Apache License 2.0
Location: /opt/conda/envs/pennylane/lib/python3.9/site-packages
Requires: appdirs, autograd, autoray, cachetools, networkx, numpy, pennylane-lightning, requests, retworkx, scipy, semantic-version, toml
Required-by: PennyLane-Cirq, PennyLane-Lightning, PennyLane-qiskit, pennylane-qulacs, PennyLane-SF
Platform info: Linux-5.4.209-116.367.amzn2.x86_64-x86_64-with-glibc2.31
Python version: 3.9.15
Numpy version: 1.23.5
Scipy version: 1.10.0
Installed devices:
- default.gaussian (PennyLane-0.28.0)
- default.mixed (PennyLane-0.28.0)
- default.qubit (PennyLane-0.28.0)
- default.qubit.autograd (PennyLane-0.28.0)
- default.qubit.jax (PennyLane-0.28.0)
- default.qubit.tf (PennyLane-0.28.0)
- default.qubit.torch (PennyLane-0.28.0)
- default.qutrit (PennyLane-0.28.0)
- null.qubit (PennyLane-0.28.0)
- cirq.mixedsimulator (PennyLane-Cirq-0.28.0)
- cirq.pasqal (PennyLane-Cirq-0.28.0)
- cirq.qsim (PennyLane-Cirq-0.28.0)
- cirq.qsimh (PennyLane-Cirq-0.28.0)
- cirq.simulator (PennyLane-Cirq-0.28.0)
- lightning.qubit (PennyLane-Lightning-0.28.2)
- strawberryfields.fock (PennyLane-SF-0.20.1)
- strawberryfields.gaussian (PennyLane-SF-0.20.1)
- strawberryfields.gbs (PennyLane-SF-0.20.1)
- strawberryfields.remote (PennyLane-SF-0.20.1)
- strawberryfields.tf (PennyLane-SF-0.20.1)
- qiskit.aer (PennyLane-qiskit-0.28.0)
- qiskit.basicaer (PennyLane-qiskit-0.28.0)
- qiskit.ibmq (PennyLane-qiskit-0.28.0)
- qiskit.ibmq.circuit_runner (PennyLane-qiskit-0.28.0)
- qiskit.ibmq.sampler (PennyLane-qiskit-0.28.0)
- qulacs.simulator (pennylane-qulacs-0.28.0)