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

Fix how trainable args are counted for gradients in GradientDescentOptimizer and NesterovMomentumOptimizer #1495

Merged
merged 25 commits into from
Aug 6, 2021

Conversation

antalszava
Copy link
Contributor

@antalszava antalszava commented Aug 4, 2021

Context
The GradientDescentOptimizer and NesterovMomentumOptimizer optimizers seem to have an issue with a cost function that takes one trainable and one non-trainable argument:

import pennylane as qml
from pennylane import numpy as np

dev = qml.device('default.qubit', wires=2)

@qml.qnode(dev, diff_method='parameter-shift')
def circuit(x):
    qml.RX(x, wires=0)
    return qml.expval(qml.PauliZ(0))

def cost(x, target):
    return circuit(x)

opt = qml.GradientDescentOptimizer(stepsize=10)
x = np.tensor(0.0, requires_grad=True)
ev = np.tensor(0.7781, requires_grad=False)

x, cost_val = opt.step_and_cost(cost, x, ev)

Raises

~/xanadu/pennylane/pennylane/optimize/gradient_descent.py in apply_grad(self, grad, args)
    153             if getattr(arg, "requires_grad", True):
    154                 x_flat = _flatten(arg)
--> 155                 grad_flat = _flatten(grad[trained_index])
    156                 trained_index += 1
    157 

IndexError: invalid index to scalar variable.

Although the cost function does not depend on target, the error is still raised. If we remove that argument, the gradient is computed well:

import pennylane as qml
from pennylane import numpy as np

dev = qml.device('default.qubit', wires=2)

@qml.qnode(dev, diff_method='parameter-shift')
def circuit(x):
    qml.RX(x, wires=0)
    return qml.expval(qml.PauliZ(0))


def cost(x):
    return circuit(x)

opt = qml.GradientDescentOptimizer(stepsize=10)
x = np.tensor(0.0, requires_grad=True)
ev = np.tensor(0.7781, requires_grad=False)

x, cost_val = opt.step_and_cost(cost, x)

For both optimizers, there is a part of the logic, where the output gradient is adjusted:

        if len(args) == 1:
            grad = (grad,)

Changes made

Changes how the output gradient is adjusted such that only the trainable arguments are considered.

Related issues

PennyLaneAI/qml#309

@codecov
Copy link

codecov bot commented Aug 4, 2021

Codecov Report

Merging #1495 (ad0eb38) into master (c0cdff2) will increase coverage by 0.00%.
The diff coverage is 100.00%.

Impacted file tree graph

@@           Coverage Diff           @@
##           master    #1495   +/-   ##
=======================================
  Coverage   98.36%   98.36%           
=======================================
  Files         183      183           
  Lines       13163    13171    +8     
=======================================
+ Hits        12948    12956    +8     
  Misses        215      215           
Impacted Files Coverage Δ
pennylane/optimize/gradient_descent.py 100.00% <100.00%> (ø)
pennylane/optimize/nesterov_momentum.py 100.00% <100.00%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update c0cdff2...ad0eb38. Read the comment docs.

@antalszava antalszava marked this pull request as ready for review August 4, 2021 16:02
@antalszava antalszava changed the title Update gradient descent Fix how trainable args are counted for gradients in GradientDescentOptimizer and NesterovMomentumOptimizer Aug 4, 2021
Comment on lines +132 to +135
num_trainable_args = 0
for arg in args:
if getattr(arg, "requires_grad", True):
num_trainable_args += 1
Copy link
Member

Choose a reason for hiding this comment

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

@antalszava you might be able to use the new

num_trainable_args = len(qml.math.get_trainable_indices(args))

functionality I just merged into the math module!

Copy link
Member

Choose a reason for hiding this comment

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

(optional, though)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This didn't seem to have worked for some tests 🤔

Copy link
Member

Choose a reason for hiding this comment

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

No worries! We'll need to remember to update this in the future, if requires_grad=False by default

Copy link
Contributor

Choose a reason for hiding this comment

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

The issue seems to be that if the argument is not a PennyLane NumPy tensor, then it'll be considered to belong to the "numpy" interface (instead of the "autgrad" interface), and all "numpy" arguments automatically evaluate as False when calling utils.requires_grad(arg). So, whenever an arg is simply a float get_trainable_indices would consider it non-trainiable, while that seems to not be what's wanted here.

Copy link
Member

@josh146 josh146 left a comment

Choose a reason for hiding this comment

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

Really nice catch @antalszava 💯

I've left some minor comments - only important one is to add one additional test case.

tests/test_optimize.py Show resolved Hide resolved
tests/test_optimize.py Outdated Show resolved Hide resolved
tests/test_optimize.py Outdated Show resolved Hide resolved
Comment on lines +132 to +135
num_trainable_args = 0
for arg in args:
if getattr(arg, "requires_grad", True):
num_trainable_args += 1
Copy link
Member

Choose a reason for hiding this comment

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

(optional, though)

tests/test_optimize.py Outdated Show resolved Hide resolved
@@ -747,16 +747,41 @@ def reset(opt):
opt.reset()


@pytest.fixture
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed to this structure, as it seems that the previous code left state in between test cases. This made 2 tests fail:

Before: https://github.com/PennyLaneAI/pennylane/runs/3244508688

Copy link
Contributor

Choose a reason for hiding this comment

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

Nice! 💯

Copy link
Member

@josh146 josh146 left a comment

Choose a reason for hiding this comment

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

Thanks @antalszava 👨‍🍳

Comment on lines +132 to +135
num_trainable_args = 0
for arg in args:
if getattr(arg, "requires_grad", True):
num_trainable_args += 1
Copy link
Member

Choose a reason for hiding this comment

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

No worries! We'll need to remember to update this in the future, if requires_grad=False by default

Copy link
Contributor

@thisac thisac left a comment

Choose a reason for hiding this comment

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

Nice fix @antalszava!

@@ -747,16 +747,41 @@ def reset(opt):
opt.reset()


@pytest.fixture
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice! 💯

Comment on lines +132 to +135
num_trainable_args = 0
for arg in args:
if getattr(arg, "requires_grad", True):
num_trainable_args += 1
Copy link
Contributor

Choose a reason for hiding this comment

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

The issue seems to be that if the argument is not a PennyLane NumPy tensor, then it'll be considered to belong to the "numpy" interface (instead of the "autgrad" interface), and all "numpy" arguments automatically evaluate as False when calling utils.requires_grad(arg). So, whenever an arg is simply a float get_trainable_indices would consider it non-trainiable, while that seems to not be what's wanted here.

@antalszava antalszava merged commit 53e1dc8 into master Aug 6, 2021
@antalszava antalszava deleted the ch7853-fix_grad_desc_mult_args branch August 6, 2021 09:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants