Quantum Circuit Learning
Quantum Circuit Learning (QCL) is an algorithm for applying quantum computers to machine learning. It is designed with the operation of the medium-scale quantum computer NISQ, which does not have an error correction function, in mind.
Reference: Quantum Circuit Learning
Since it is difficult to implement from scratch, implement it with qiskit while referring to the qulacs code in Quantum Native Dojo. It is.
python
# coding : utf-8
import numpy as np
import matplotlib.pyplot as plt
from functools import reduce
from scipy.optimize import minimize
import time
from qiskit import QuantumRegister, QuantumCircuit, ClassicalRegister, execute
from qiskit.quantum_info.analysis import average_data
from qiskit import BasicAer
from qiskit.quantum_info.operators import Operator
from qiskit.aqua.utils import tensorproduct
I_mat = np.array([[1, 0], [0, 1]])
X_mat = np.array([[0, 1], [1, 0]])
Z_mat = np.array([[1, 0], [0, -1]])
def classical_minimize(cost_func, theta_init, method='Nelder-Mead'):
    print('Do classical_minimize !')
    start_time = time.time()
    result = minimize(cost_func, theta_init, method=method)
    print('runnning time; {}'.format(time.time() - start_time))
    print('opt_cost: {}'.format(result.fun))
    theta_opt = result.x
    return theta_opt
class SimpleQuantumCircuitLearning:
    def __init__(self, nqubit, func=lambda x: np.sin(x*np.pi), num_x_train=50, c_depth=3, time_step=0.77):
        self.nqubit = nqubit
        self.func_to_learn = func
        self.num_x_train = num_x_train
        self.c_depth = c_depth
        self.time_step = time_step
        self.x_train = None
        self.y_train = None
        self.InitialTheta = None
        self.time_evol_gate = None
    def initialize(self):
        random_seed = 0
        np.random.seed(random_seed)
        x_min = -1.
        x_max = 1.
        self.x_train = x_min + (x_max - x_min) * np.random.rand(self.num_x_train)
        self.y_train = self.func_to_learn(self.x_train)
    def add_noise(self, mag_noise=0.05):
        self.y_train = self.y_train + mag_noise * np.random.rand(self.num_x_train)
    def input_encode(self, x) -> QuantumCircuit:
        qr = QuantumRegister(self.nqubit)
        cr = ClassicalRegister(self.nqubit)
        qc = QuantumCircuit(qr, cr)
        angle_y = np.arcsin(x)
        angle_z = np.arccos(x ** 2)
        for i in range(self.nqubit):
            qc.ry(angle_y, i)
            qc.rz(angle_z, i)
        return qc
    def make_fullgate(self, list_SiteAndOperator):
        list_Site = [SiteAndOperator[0] for SiteAndOperator in list_SiteAndOperator]
        list_SingleGates = []
        cnt = 0
        for i in range(self.nqubit):
            if i in list_Site:
                list_SingleGates.append(list_SiteAndOperator[cnt][1])
                cnt += 1
            else:
                list_SingleGates.append(I_mat)
        return reduce(np.kron, list_SingleGates)
    def hamiltonian(self, time_step):
        ham = np.zeros((2**self.nqubit, 2**self.nqubit), dtype=complex)
        for i in range(self.nqubit):
            Jx = -1. + 2. * np.random.rand()
            ham += Jx * self.make_fullgate([[i, X_mat]])
            for j in range(i+1, self.nqubit):
                J_ij = -1. + 2. * np.random.rand()
                ham += J_ij * self.make_fullgate([[i, Z_mat], [j, Z_mat]])
        diag, eigen_vecs = np.linalg.eigh(ham)
        time_evol_op = np.dot(np.dot(eigen_vecs, np.diag(np.exp(-1j*time_step*diag))), eigen_vecs.T.conj())
        self.time_evol_gate = Operator(time_evol_op)
    def initial_theta(self):
        np.random.seed(9999)
        theta = np.array([2*np.pi*np.random.rand() for i in range(3 * self.nqubit * self.c_depth)])
        return theta
    def U_out(self, qc, theta):
        qc.unitary(self.time_evol_gate, range(self.nqubit), label='time_evol_gate')
        theta = np.reshape(theta, (3, self.nqubit, self.c_depth))
        for depth in range(self.c_depth):
            for q1 in range(self.nqubit):
                qc.rx(theta[depth][q1][0], q1)
                qc.rz(theta[depth][q1][1], q1)
                qc.rx(theta[depth][q1][2], q1)
        return qc
    def Z0(self):
        tensor = Z_mat
        for i in range(1, self.nqubit):
            tensor = tensorproduct(tensor, I_mat)
        return tensor
    def run_circuit(self, qc):
        NUM_SHOTS = 10000
        seed = 1234
        backend = BasicAer.get_backend('qasm_simulator')
        qc.measure(range(self.nqubit), range(self.nqubit))
        results = execute(qc, backend, shots=NUM_SHOTS, seed_simulator=seed).result()
        return results.get_counts(qc)
    def qcl_pred(self, x, theta):
        qc = self.input_encode(x)
        qc = self.U_out(qc, theta)
        counts = self.run_circuit(qc)
        expectation = average_data(counts, self.Z0())
        return expectation
    def cost_func(self, theta):
        y_pred = [self.qcl_pred(x, theta) for x in self.x_train]
        L = ((y_pred - self.y_train)**2).mean()
        return L
    def minim(self, InitialTheta):
        theta_opt = classical_minimize(self.cost_func, InitialTheta)
        return theta_opt
if __name__ == '__main__':
    x_min = - 1.
    x_max = 1.
    QCL = SimpleQuantumCircuitLearning(nqubit=3)
    QCL.initialize()
    QCL.add_noise(mag_noise=0.05)
    QCL.hamiltonian(time_step=0.77)
    initial_theta = QCL.initial_theta()
    initial_cost = QCL.cost_func(initial_theta)
    ##########################################################
    x_min = - 1.
    x_max = 1.
    xlist = np.arange(x_min, x_max, 0.02)
    y_init = [QCL.qcl_pred(x, initial_theta) for x in xlist]
    plt.plot(xlist, y_init)
    plt.show()
    ##########################################################
    print('initial_cost: {}'.format(initial_cost))
    theta_opt = QCL.minim(initial_theta)
    plt.figure(figsize=(10, 6))
    plt.plot(QCL.x_train, QCL.y_train, "o", label='Teacher')
    plt.plot(xlist, y_init, '--', label='Initial Model Prediction', c='gray')
    y_pred = np.array([QCL.qcl_pred(x, theta_opt) for x in xlist])
    plt.plot(xlist, y_pred, label='Final Model Prediction')
    plt.legend()
    plt.show()
python
    def __init__(self, nqubit, func=lambda x: np.sin(x*np.pi), num_x_train=50, c_depth=3, time_step=0.77):
        self.nqubit = nqubit
        self.func_to_learn = func
        self.num_x_train = num_x_train
        self.c_depth = c_depth
        self.time_step = time_step
        self.x_train = None
        self.y_train = None
        self.InitialTheta = None
        self.time_evol_gate = None
    
    #Training data
    def initialize(self):
        random_seed = 0
        np.random.seed(random_seed)
        x_min = -1.
        x_max = 1.
        self.x_train = x_min + (x_max - x_min) * np.random.rand(self.num_x_train)
        self.y_train = self.func_to_learn(self.x_train)
    #Add noise
    def add_noise(self, mag_noise=0.05):
        self.y_train = self.y_train + mag_noise * np.random.rand(self.num_x_train)
The output of the result is as follows.
initial_cost: 0.20496500050465266
opt_cost: 0.13626398715932975

Looking at the results, it seems that the maximum and minimum values of Final Model Prediction are small.
This is because the value of 
    def Z0(self):
        tensor = Z_mat
        for i in range(1, self.nqubit):
            tensor = tensorproduct(tensor, I_mat)
        return tensor
So, if you use <2Z>
    def Z0(self):
        tensor = Z_mat * 2
        for i in range(1, self.nqubit):
            tensor = tensorproduct(tensor, I_mat)
        return tensor

And opt_cost
opt_cost: 0.03618545796607254
And improvement is seen.
As a result of using <2.5Z> as a trial,

opt_cost: 0.021796774423019367
Roughly speaking, I implemented QCL with Qiskit. Since the description method is completely different from Qulacs and blueqat, I feel that it is difficult to get started due to the large amount of literature. By the way, I felt that the coefficient of Z also needed to be optimized.
It's a rather crude article, but please forgive me because it's mainly implemented.
Recommended Posts