Quantum machine learning by hand
In quantum machine learning a circuit is a function with parameters: its expectation value is the output, and you train the parameters. This chapter builds that idea step by step with QubitLab circuits: qubit rotation, gradients by the parameter-shift rule, data encoding, a variational classifier, kernels, and the VQE loop. Every part has an exercise that opens in the IDE.
Part 1 · A circuit is a function with parameters
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)Exercise 1 · Qubit rotation
Start from the angles (0.2, 0.3): the cost cos φ₁ cos φ₂ is 0.936, close to its maximum. An optimiser would walk downhill to the minimum −1. Skip it: 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.2) q[0]; ry(0.3) 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.032, matching cost 0.936.
Part 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.
Exercise 2 · Shift it by hand
At the angles (0.8, 0.3) the circuit gives f = cos(0.8)·cos(0.3) ≈ 0.6656, and the exact gradient is (−0.6853, −0.2059). Reproduce ∂f/∂φ₁ on the quantum side: run the circuit with φ₁ = 0.8 + π/2 and with φ₁ = 0.8 − π/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.8 - pi/2) rx(0.8 + pi/2) q[0]; ry(0.3) 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.3708)·cos(0.3) ≈ −0.6853 and f(−) = cos(−0.7708)·cos(0.3) ≈ +0.6853, so the gradient is (−0.6853 − 0.6853)/2 = −0.6853, exactly −sin(0.8)cos(0.3).
Part 3 · Getting data into qubits
| Encoding | How | Qubits for d features | Used in |
|---|---|---|---|
| Basis | bitstring → computational basis state (X gates) | d | Parity classifier (Part 4) |
| Angle | feature xᵢ → RY(xᵢ) on qubit i | d | Most NISQ classifiers |
| Amplitude | normalised vector → amplitudes (state preparation) | log₂ d | Small datasets such as Iris (2 features + 2 padding → 2 qubits) |
Exercise 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).
Part 4 · A variational classifier
A good first classifier learns the parity of a 4-bit string (is the number of 1s even or odd?). Basis-encode the input, apply a few layers of (a general rotation on every qubit, then CNOTs that link each qubit to the next and the last back to the first), read ⟨Z₀⟩ plus a trainable bias, and lower the square loss with any gradient-based optimiser. Parity is a fair test because no single input bit predicts the answer: the circuit has to combine all four. For real-valued data, amplitude encoding packs several features into fewer qubits.
Exercise 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 the usual convention y ∈ {−1, 1}.
Part 5 · Quantum kernels
A quantum kernel starts from 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⟩.
Exercise 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 %.
Part 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.
Exercise 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.