qml icon indicating copy to clipboard operation
qml copied to clipboard

Replace usage of `qml.init` with `Template.shape`

Open josh146 opened this issue 4 years ago • 10 comments

The init module has been deprecated, and the recommend approach for generating initial weights is to use the Template.shape method:

>>> from pennylane.templates import StronglyEntanglingLayers
>>> qml.init.strong_ent_layers_normal(n_layers=3, n_wires=2) # deprecated
>>> np.random.random(StronglyEntanglingLayers.shape(n_layers=3, n_wires=2))  # new approach

We should update demos using the init module to instead use the shape method.

josh146 avatar Apr 23 '21 15:04 josh146

is this meant to work for user-defined templates too? Are templates supposed to have only trainable parameters or only data parameters?

In the image there's a template we defined during qhack, what would be the correct approach to randomly initialize only the parameters, not the x here?

image

emgilfuster avatar May 06 '21 08:05 emgilfuster

@emgilfuster that is a good question. @mariaschuld, I believe the @qml.template decorator is deprecated?

josh146 avatar May 06 '21 08:05 josh146

for information completeness, here's another image: image

The random_params function is how we used to initialize the parameters. weights is the naive implementation of the new approach, which is of coursed doomed.

emgilfuster avatar May 06 '21 08:05 emgilfuster

Wait, I am not sure I follow. The init functions are still valid as they were, since they just generate random arrays of the right shape. We can leave them in the code base for a while longer, but encourage users to rather query the template's shape method.

This method is not implemented by default, i.e. it depends on the implementation of the template what shapes are returned and in which format. Of course, the old design of templates via the decorator (which is still possible, just discouraged) means that a template is not a class, and does not elegantly support adding such method [although one could probably hack this somehow].

In short: One should code up templates as Operation subclasses and if they take tensors, one should add a shape method. But the old logic is still entirely supported.

mariaschuld avatar May 06 '21 09:05 mariaschuld

The port to the new design is quite new and there may be a mix of both logics for a while, which we'll start to clean up soon...

mariaschuld avatar May 06 '21 09:05 mariaschuld

I understand, thanks @mariaschuld and @josh146 !

emgilfuster avatar May 06 '21 09:05 emgilfuster

This thread actually gave me an idea - we could update the @template decorator to create new-style templates. For example,

def template(n_params, n_wires=qml.operation.AnyWires, shapes_fn=lambda: None):

    def _wrapper(fn):
        class Template(qml.operation.Operation):
            num_params = n_params
            num_wires = n_wires
            par_domain = "A"

            def __init__(self, *args, **kwargs):
                active_tape = qml.tape.get_active_tape()

                if active_tape is None:
                    with qml.tape.QuantumTape() as tape:
                        fn(*args, **kwargs)
                else:
                    with active_tape.stop_recording(), qml.tape.QuantumTape() as tape:
                        fn(*args, **kwargs)

                self.expanded_tape = tape
                super().__init__(*args[:n_params], wires=kwargs["wires"])

            def expand(self):
                return self.expanded_tape

            shapes = staticmethod(shapes_fn)

        return Template
    return _wrapper

We can then apply this new decorator to our template function, and automatically:

  • Turn it into an operation
  • Add an expand method
  • Add a static shape method
  • Automatically add in all required abstract class attributes
def block(x, params, wires):
    for w in wires:
        qml.Hadamard(wires=w)
        qml.RZ(x, wires=w)
        qml.RY(params[0][w], wires=w)

    qml.broadcast(qml.CRZ, wires, "ring", parameters=params[1])

# Decorator turns `ansatz` into a Template operation
@template(
    n_params=2,
    shapes_fn=lambda n_layers, n_wires: (tuple(), [n_layers, 2, n_wires])
)
def ansatz(x, params, wires):
    for j, block_params in enumerate(params):
        block(x, block_params, wires)

dev = qml.device("default.qubit", wires=3)

@qml.qnode(dev)
def circuit(*weights):
    ansatz(*weights, wires=[0, 1, 2])
    return qml.expval(qml.PauliZ(0))

We can use our template now for built-in parameter initialization:

>>> weights = [np.random.random(i) for i in ansatz.shapes(n_layers=2, n_wires=3)]
>>> print(qml.draw(circuit)(*weights))
 0: ──H──RZ(0.194)──RY(0.563)──╭C─────────────────────────╭RZ(0.457)──H──────────RZ(0.194)──RY(0.985)──╭C───────────────────────╭RZ(0.14)──┤ ⟨Z⟩
 1: ──H──RZ(0.194)──RY(0.14)───╰RZ(0.776)──╭C──────────H──│───────────RZ(0.194)──RY(0.376)─────────────╰RZ(0.0197)──╭C──────────│──────────┤
 2: ──H──RZ(0.194)──RY(0.685)──────────────╰RZ(0.695)─────╰C──────────H──────────RZ(0.194)──RY(0.132)───────────────╰RZ(0.432)──╰C─────────┤

josh146 avatar May 06 '21 09:05 josh146

We could even take this further, and allow for backwards pass registration:

# Decorator turns `ansatz` into a Template operation
@template(n_params=2, shapes_fn=lambda n_layers, n_wires: (tuple(), [n_layers, 2, n_wires]))
def ansatz(x, params, wires):

    # forward pass
    for j, block_params in enumerate(params):
        block(x, block_params, wires)

    # backward pass
    def grad(x, params, wires):
        for j, block_params in enumerate(params):
            block(x, block_params, wires)

    return grad

josh146 avatar May 06 '21 10:05 josh146

That's quite nice!

The only complexity is the call of super in init - often it makes more sense to hand-extract which inputs make it to the final "gate params". But having the decorator as a simple class-construction helper would be cool.

mariaschuld avatar May 06 '21 12:05 mariaschuld

Yes, I was thinking that as well at first. But then I thought about it more - the decorator would only be used in cases like the current decorator, where ansatz parameters ~ gate parameters? So it should work fine there, and more advanced users/developers would instead graduate to the Operator subclassing...

josh146 avatar May 06 '21 12:05 josh146

Is someone working on it?

yusharth avatar Nov 23 '22 09:11 yusharth