Quantum machine learning, PennyLane style
PennyLane teaches QML as 'a circuit is a function with parameters, its expectation value is the output, and you train the parameters'. This chapter follows that path with QubitLab circuits: qubit rotation, gradients by the parameter-shift rule, data encoding, a variational classifier, kernels, and the VQE loop. Every node has an exercise that opens in the IDE.
Node 1 · A circuit is a function: the QNode idea
In PennyLane a QNode binds a quantum function to a device: the function takes classical numbers, applies gates, and returns a measurement such as the expectation value ⟨Z⟩. The smallest example is one qubit with RX(φ₁) then RY(φ₂): its output is ⟨Z⟩ = cos φ₁ cos φ₂, a number between −1 and 1. In QubitLab the same circuit is OpenQASM with angles, and ⟨Z⟩ = p(0) − p(1) from the Probabilities tab.
# PennyLane
dev = qml.device("default.qubit", wires=1)
@qml.qnode(dev)
def circuit(params):
qml.RX(params[0], wires=0)
qml.RY(params[1], wires=0)
return qml.expval(qml.PauliZ(0))
# QubitLab (OpenQASM 3): the same function
# rx(phi1) q[0]; ry(phi2) q[0]; → <Z> = p(0) − p(1)Codercise 1 · Qubit rotation
PennyLane's first tutorial starts at params (0.011, 0.012), cost 0.99987, and gradient descent (step 0.4) reaches cost −1.0 after about 40 steps at params (0, π). Skip the optimiser: set the angles to the optimum by hand and confirm ⟨Z⟩ = −1 from the probabilities.
OPENQASM 3.0; include "stdgates.inc"; qubit[1] q; bit[1] c; rx(0.011) q[0]; ry(0.012) q[0]; c[0] = measure q[0];
Hint
cos φ₁ cos φ₂ = −1 needs cos φ₂ = −1 with φ₁ = 0.
Check your answer
rx(0) ry(pi) gives |1⟩ with probability 100 %, so ⟨Z⟩ = 0 − 1 = −1. At the starting point p(1) ≈ 0.00007, matching cost 0.99987.
Node 2 · Gradients without backprop: the parameter-shift rule
You cannot differentiate through a real quantum computer, but for any gate of the form e^{-iθG/2} with G having eigenvalues ±1 (every Pauli rotation RX, RY, RZ, and controlled versions) the derivative of an expectation value is exactly a difference of two evaluations shifted by ±π/2. No finite-difference error, just two extra circuit runs per parameter.
Codercise 2 · Shift it by hand
At params (0.54, 0.12) PennyLane reports f = 0.8515 and gradient (−0.5104, −0.1027). Reproduce ∂f/∂φ₁: run the circuit with φ₁ = 0.54 + π/2 and with φ₁ = 0.54 − π/2, convert each probability pair into ⟨Z⟩ = p(0) − p(1), and take half the difference.
OPENQASM 3.0; include "stdgates.inc"; qubit[1] q; bit[1] c; // shifted +pi/2 (run again with 0.54 - pi/2) rx(0.54 + pi/2) q[0]; ry(0.12) q[0]; c[0] = measure q[0];
Hint
Use the ideal statevector target (exact probabilities), not shots, or the noise hides the digits.
Check your answer
f(+) = cos(2.1108)·cos(0.12) ≈ −0.5104 and f(−) = cos(−1.0308)·cos(0.12) ≈ +0.5104, so the gradient is (−0.5104 − 0.5104)/2 = −0.5104, exactly −sin(0.54)cos(0.12).
Node 3 · Getting data into qubits
| Encoding | How | Qubits for d features | Used in |
|---|---|---|---|
| Basis | bitstring → computational basis state (X gates) | d | PennyLane parity classifier |
| Angle | feature xᵢ → RY(xᵢ) on qubit i | d | Most NISQ classifiers |
| Amplitude | normalised vector → amplitudes (state preparation) | log₂ d | PennyLane Iris classifier (2 features + 2 padding → 2 qubits) |
Codercise 3 · Angle-encode a point
Encode x = (0.3, 1.2) with RY on two qubits and read the Bloch view in Debug mode. Then encode x = (0.3 + 2π, 1.2): why does the circuit not change?
OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; bit[2] c; ry(0.3) q[0]; ry(1.2) q[1]; c = measure q;
Hint
RY(θ) tilts the Bloch arrow by θ from +Z towards +X.
Check your answer
q0 → (sin 0.3, 0, cos 0.3) = (0.30, 0, 0.96), q1 → (0.93, 0, 0.36). Angles are periodic, so features must be scaled into one period (PennyLane rescales inputs for this reason).
Node 4 · A variational classifier
PennyLane's variational-classifier tutorial learns the parity function on 4 qubits: basis-encode each 4-bit input, apply 2 layers of (a general rotation on every qubit, then a ring of CNOTs 0→1, 1→2, 2→3, 3→0), read ⟨Z₀⟩ plus a trainable bias, and minimise the square loss with Nesterov momentum (step 0.5, batch 5, 100 iterations). It reaches training cost 1.7·10⁻⁵ and 100 % accuracy on both the 10 training and 6 unseen strings. The Iris version uses amplitude encoding on 2 qubits with 6 layers and also classifies the held-out 25 % perfectly.
Codercise 4 · One classifier layer
Build one layer of the parity classifier on the input 0101: basis encoding, a U rotation on every qubit, then the CNOT ring. Debug it: how many qubits are entangled after the ring, and what is ⟨Z₀⟩ = p(q0=0) − p(q0=1)?
OPENQASM 3.0; include "stdgates.inc"; qubit[4] q; bit[4] c; // basis encoding of 0101 (q0 = rightmost bit) x q[0]; x q[2]; // Rot = U(theta, omega, phi) on every qubit U(0.4, 0.1, 0.2) q[0]; U(0.9, 0.3, 0.1) q[1]; U(0.2, 0.5, 0.4) q[2]; U(0.7, 0.2, 0.6) q[3]; // ring of CNOTs cx q[0], q[1]; cx q[1], q[2]; cx q[2], q[3]; cx q[3], q[0]; c = measure q;
Hint
Sum p over outcomes whose last bit is 0, minus the rest. The Probabilities tab lists every outcome.
Check your answer
All four qubits are tagged entangled after the ring (each purity < 0.99). The most likely outcome is |0011⟩ at 68 % and ⟨Z₀⟩ ≈ −0.466 for these angles. Training adjusts the 12 angles and the bias until sign(⟨Z₀⟩ + b) equals the label: 0101 has an even number of ones, so its label is −1 in PennyLane's convention y ∈ {−1, 1}.
Node 5 · Quantum kernels
IBM's QML course and PennyLane both introduce kernels: a feature map |φ(x)⟩ = U(x)|0⟩ and the kernel k(x, x′) = |⟨φ(x)|φ(x′)⟩|². You never train the quantum part; a classical SVM uses the kernel matrix. On hardware the overlap is measured by running U(x) followed by U†(x′) and reading the probability of |0…0⟩.
Codercise 5 · Measure a kernel entry
Angle-encode x = (0.3, 1.2) then undo with x′ = (0.5, 1.0): k(x, x′) is the probability of 00. Predict it from cos² of half the angle differences before you run.
OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; bit[2] c; // U(x) ry(0.3) q[0]; ry(1.2) q[1]; // U^dagger(x') ry(-0.5) q[0]; ry(-1.0) q[1]; c = measure q;
Hint
For RY the overlap on one qubit is cos²((x − x′)/2).
Check your answer
k = cos²(−0.1) · cos²(0.1) ≈ 0.990 · 0.990 = 0.980, and the Probabilities tab shows |00⟩ at 98.0 %.
Node 6 · The variational loop (VQE)
IBM's 'Variational algorithm design' course names the parts: a reference state, an ansatz (variational form), a cost function built from expectation values, and a classical optimisation loop. VQE minimises ⟨ψ(θ)|H|ψ(θ)⟩ for a chemistry Hamiltonian H written as Paulis. QubitLab's cutting flow already reconstructs expectation values of Pauli strings, and the exercise below evaluates one term of the H₂ Hamiltonian by hand.
Codercise 6 · One VQE term
The two-qubit H₂ ansatz is RY(θ) on q0 followed by CNOT. For θ = 0.4 compute ⟨ZZ⟩ = p(00) + p(11) − p(01) − p(10) from the probabilities, then ⟨ZI⟩. Try θ = π/2.
OPENQASM 3.0; include "stdgates.inc"; qubit[2] q; bit[2] c; ry(0.4) q[0]; cx q[0], q[1]; c = measure q;
Hint
After the CNOT only 00 and 11 appear, so ⟨ZZ⟩ is 1 for every θ; ⟨ZI⟩ = cos θ.
Check your answer
p(00) = cos²(0.2) ≈ 0.961, p(11) ≈ 0.039: ⟨ZZ⟩ = 1, ⟨ZI⟩ = 0.961 − 0.039 = 0.921 = cos 0.4. At θ = π/2 the state is the Bell state and ⟨ZI⟩ = 0. A real VQE would combine such terms with the H₂ coefficients and let an optimiser move θ.
Keep going
- PennyLane · Basic tutorial: qubit rotation The RX·RY circuit, its gradient and gradient descent to |1⟩ in ~40 steps.
- PennyLane · Variational classifier Parity with basis encoding, Iris with amplitude encoding; Rot layers and CNOT rings.
- PennyLane · Parameter-shift rules Why the ±π/2 shift gives exact gradients for Pauli rotations.
- IBM · Quantum machine learning Classical ML review, data encoding, quantum kernels, QVCs and QNNs.
- IBM · Variational algorithm design Reference states, ansätze, cost functions, optimisation loops, applications.