Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enabling PSR, analytical diff with pytorch and moving interfaces to dedicated module #42

Open
wants to merge 39 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
bed5146
feat: psr draft
MatteoRobbiati Oct 22, 2024
309a26a
refactor: use nshots value to choose if use samples or not
MatteoRobbiati Oct 22, 2024
5871804
fix: expectation decoding test
MatteoRobbiati Oct 22, 2024
c1534e1
working on an example using PSR
MatteoRobbiati Oct 25, 2024
687e843
feat: PSR working but x is padded
MatteoRobbiati Oct 28, 2024
e015b86
reminder about diffrule
MatteoRobbiati Oct 28, 2024
fa83ef2
docs: tutorial on models
MatteoRobbiati Oct 28, 2024
ea51b91
Merge branch 'model' into diffrules
MatteoRobbiati Nov 1, 2024
a412b5e
fixing PSR and moving interfaces to qiboml.interfaces
MatteoRobbiati Nov 1, 2024
bb7d7a0
test: add diffrules test
MatteoRobbiati Nov 4, 2024
5880001
refactor: moving interfaces to proper module
MatteoRobbiati Nov 4, 2024
6110339
chore: add qibojit to test deps
MatteoRobbiati Nov 4, 2024
c5af099
chore: tf and torch are not optional
MatteoRobbiati Nov 4, 2024
1b75cae
tests: rm tf import
MatteoRobbiati Nov 4, 2024
bd228bc
fix: adapting PSR padding to expval shape
MatteoRobbiati Nov 4, 2024
a9e2481
test: enable PSR test in test_models_interface
MatteoRobbiati Nov 4, 2024
46de4e9
test: fix seed in diffrules test
MatteoRobbiati Nov 4, 2024
69b8f12
feat: analytic as property
MatteoRobbiati Nov 5, 2024
52ec4cd
chore: set optional == True for tf and torch in our deps
MatteoRobbiati Nov 7, 2024
2e341f1
fix: rm unused __init__ method from PSR class
MatteoRobbiati Nov 7, 2024
9bdf627
fix: adapt padding to x shape
MatteoRobbiati Nov 7, 2024
16df115
fix: remove useless passed qubits list
MatteoRobbiati Nov 7, 2024
d279f13
tests: fix diffrules tests grad calculation
MatteoRobbiati Nov 7, 2024
1842a1d
Update tests/test_models_interfaces.py
MatteoRobbiati Nov 7, 2024
5ae1ade
tests: adapt interfaces tests to analytical as property
MatteoRobbiati Nov 7, 2024
7552520
Merge branch 'diffrules' of github.com:qiboteam/qiboml into diffrules
MatteoRobbiati Nov 7, 2024
ae04a26
chore: restoring tf and torch non-optional because of lint complainings
MatteoRobbiati Nov 7, 2024
921485f
tests: increase nshots in diffrules test
MatteoRobbiati Nov 7, 2024
590cf5c
fix random seed in diffrules tests
MatteoRobbiati Nov 7, 2024
45b2063
Update src/qiboml/models/decoding.py
MatteoRobbiati Nov 12, 2024
55f7a06
chore: updating lock after merging main to solve conflicts
MatteoRobbiati Nov 12, 2024
208ea10
feat: add gates_encoding_feature method
MatteoRobbiati Nov 13, 2024
eac535e
feat: drafting gradient wrt data
MatteoRobbiati Nov 13, 2024
6e3870d
fix: working derivative wrt x
MatteoRobbiati Nov 13, 2024
3a8e66f
refactor: moving gradient wrt data into a method of PSR class
MatteoRobbiati Nov 14, 2024
1c0c3c8
fix: rm gates_encoding_feature from abstractmethods
MatteoRobbiati Nov 14, 2024
f8d944a
Update src/qiboml/operations/differentiation.py
MatteoRobbiati Nov 15, 2024
73dd6c7
test: reducing decimals in PSR test
MatteoRobbiati Nov 15, 2024
824613f
fix: wrt --> from in psr
MatteoRobbiati Nov 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 80 additions & 42 deletions src/qiboml/operations/differentiation.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,85 @@ def evaluate(


class PSR(DifferentiationRule):
"""
Compute the gradient of the expectation value of a target observable w.r.t
features and parameters contained in a quantum model using the parameter shift
rules.
"""

def evaluate(self, x: ndarray, encoding, training, decoding, backend, *parameters):
if decoding.output_shape != (1, 1):
raise_error(
NotImplementedError,
"Parameter Shift Rule only supports expectation value decoding.",
)
x_copy = deepcopy(x)
x_size = backend.to_numpy(x).size
# construct circuit
x = encoding(x) + training
circuit = encoding(x) + training

# compute first gradient part, wrt data
gradient = self.gradient_wrt_data(
data=x,
encoding=encoding,
circuit=circuit,
decoding=decoding,
backend=backend,
)

# compute second gradient part, wrt parameters
for i in range(len(parameters)):
gradient.append(
self.one_parameter_shift(
circuit=circuit,
decoding=decoding,
parameters=parameters,
parameter_index=i,
backend=backend,
)
)
return gradient

def one_parameter_shift(
self, circuit, decoding, parameters, parameter_index, backend
):
"""Compute one derivative of the decoding strategy w.r.t. a target parameter."""
gate = circuit.associate_gates_with_parameters()[parameter_index]
generator_eigenval = gate.generator_eigenvalue()
Comment on lines +79 to +80
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One question, this has to be computed every time, right? There is no way of caching it, because it depends on the value of the parameter, right?

s = np.pi / (4 * generator_eigenval)

tmp_params = backend.cast(parameters, copy=True)
tmp_params = self.shift_parameter(tmp_params, parameter_index, s, backend)

circuit.set_parameters(tmp_params)
forward = decoding(circuit)

tmp_params = self.shift_parameter(tmp_params, parameter_index, -2 * s, backend)

circuit.set_parameters(tmp_params)
backward = decoding(circuit)
return generator_eigenval * (forward - backward)

def gradient_wrt_data(
MatteoRobbiati marked this conversation as resolved.
Show resolved Hide resolved
self,
data,
encoding,
circuit,
decoding,
backend,
):
"""
Compute the gradient w.r.t. data.

Args:
data: data;
encoding: encoding part of the quantum model. It is used to check whether
parameter shift rules can be used to compute the gradient.
circuit: all the quantum circuit, composed of encoding + eventual trainable
layer.
decoding: decoding part of the quantum model. In the PSR the decoding
is usually a qiboml.models.decoding.Expectation layer.
backend: qibo backend on which the circuit execution is performed-
"""
x_size = backend.to_numpy(data).size
# what follows now works for encodings in which the angle is equal to the feature
# TODO: adapt this strategy to the more general case of a callable(x, params)
if encoding.hardware_differentiable:
Expand All @@ -61,65 +128,36 @@ def evaluate(self, x: ndarray, encoding, training, decoding, backend, *parameter
for enc_gate in gates_encoding_xk:
# search for the target encoding gate in the circuit
generator_eigenval = enc_gate.generator_eigenvalue()
# TODO: the following shift value is valid only for rotation-like gates
shift = np.pi / (4 * generator_eigenval)
for gate in x.queue:
for gate in circuit.queue:
if gate == enc_gate:
original_parameter = deepcopy(gate.parameters)
gate.parameters = shifted_x_component(
x=x_copy,
x=data,
index=k,
shift_value=shift,
backend=backend,
)
forward = decoding(x)
forward = decoding(circuit)
gate.parameters = shifted_x_component(
x=x_copy,
x=data,
index=k,
shift_value=-2 * shift,
backend=backend,
)
backward = decoding(x)
backward = decoding(circuit)
derivative_k += float(
generator_eigenval * (forward - backward)
)
# restore original parameter
gate.parameters = original_parameter
gradients = [np.array([[[der for der in x_gradient]]])]
x_gradient.append(derivative_k)
return [np.array([[[der for der in x_gradient]]])]

else:
# pad the gradients in case data are not uploaded into gates
gradients = [np.array([[(0.0,) * x_size]])]

for i in range(len(parameters)):
gradients.append(
self.one_parameter_shift(
circuit=x,
decoding=decoding,
parameters=parameters,
parameter_index=i,
backend=backend,
)
)
return gradients

def one_parameter_shift(
self, circuit, decoding, parameters, parameter_index, backend
):
"""Compute one derivative of the decoding strategy w.r.t. a target parameter."""
gate = circuit.associate_gates_with_parameters()[parameter_index]
generator_eigenval = gate.generator_eigenvalue()
s = np.pi / (4 * generator_eigenval)

tmp_params = backend.cast(parameters, copy=True)
tmp_params = self.shift_parameter(tmp_params, parameter_index, s, backend)

circuit.set_parameters(tmp_params)
forward = decoding(circuit)

tmp_params = self.shift_parameter(tmp_params, parameter_index, -2 * s, backend)

circuit.set_parameters(tmp_params)
backward = decoding(circuit)
return generator_eigenval * (forward - backward)
return [np.array([[(0.0,) * x_size]])]

@staticmethod
def shift_parameter(parameters, i, epsilon, backend):
Expand Down
Loading