量子二分类

1. 固定随机参数

import numpy as np
import matplotlib.pyplot as plt
import torch
import torch.nn as nn
import random
from isqtools import IsqCircuit
from isqtools.backend import TorchBackend
from isqtools.neural_networks import TorchLayer
from sklearn import datasets, preprocessing


def setup_seed(seed):
    torch.manual_seed(seed)
    torch.cuda.manual_seed_all(seed)
    np.random.seed(seed)
    random.seed(seed)

setup_seed(222)

2. 创建二分类数据集

创建一个简单了二分类数据集。x为二维数组,归一化到[-pi, pi]这个范围,两种类别对应的y取值为{0, 1}。

x, y = datasets.make_classification(
    n_features=2,
    n_redundant=0,
    n_samples=100,
    n_clusters_per_class=1,
    class_sep=1.3,
)

scaler = preprocessing.MinMaxScaler(feature_range=(-np.pi, np.pi))
x = scaler.fit_transform(x)
plt.scatter(x[:, 0], x[:, 1], c=y, cmap='jet')

png

查看y

y
array([1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0,
       1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0,
       1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1,
       0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1,
       0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0])

3. 定义量子机器学习网络

isq文件命名为binary_classification.isq

import std;


param inputs[], weights[];
qbit q[2];
int pauli_inx[] = {2, 2};
// this means Z0Z1
// using arrays for pauli measurement, 
// X:0, Y:1, Z:2, I:3

procedure single_h(qbit q[]) {
    for i in 0:q.length {
        H(q[i]);
    }
}

procedure adjacent_cz(qbit q[]) {
    for i in 0:q.length-1 {
        CZ(q[i], q[i+1]);
    }
}

procedure encode_inputs(qbit q[], int start_idx) {
    for i in 0:q.length {
        Rz(inputs[i+start_idx], q[i]);
    }
}

procedure encode_weights(qbit q[], int start_idx) {
    for i in 0:q.length {
        Ry(weights[i+start_idx], q[i]);
    }
    for i in 0:q.length {
        Rx(weights[i+start_idx+q.length], q[i]);
    }
}

procedure pauli(int puali_idx[], qbit q[]) {
    for i in 0:q.length {
        if (puali_idx[i] == 0) {
            H(q[i]);
            M(q[i]);
        }
        if (puali_idx[i] == 1) {
            X2P(q[i]);
            M(q[i]);
        }
        if (puali_idx[i] == 2) {
            M(q[i]);
        }
        if (puali_idx[i] == 3) {
            continue;
        }
    }
}

procedure main() {

    single_h(q);
    encode_inputs(q, 0);
    adjacent_cz(q);

    encode_weights(q, 0);
    adjacent_cz(q);

    encode_weights(q, 4);
    adjacent_cz(q);

    pauli(pauli_inx, q);
}

创建量子机器学习网络,这里我们使用了2个量子比特,定义了inputsweights两个参数,使用paili测量(Z0Z1)得到结果。paili测量的结果在[-1, 1],我们做了简单的数学变换使得circuit最后的返回值范围在[0, 1],这样更符合我们的二分类问题。

根据circuit创建了qnn。除了传入weights的数目之外,我们还使用了vmap方法。使用vmap时制定了in_dims=(0, None),这表示对应circuit的第一个参数inputs的第一位(dim=0)使用向量化运算,第二个参数weights不使用向量化运算,这与神经网络运行时的batch模式相符合。

backend = TorchBackend()

qc = IsqCircuit(
    file="binary_classification.isq",
    backend=backend,
    sample=False,
)

def circuit(inputs, weights):

    result = qc.pauli_measure(
        inputs=inputs,
        weights=weights,
    )

    return (result+1)/2

qnn = TorchLayer(
    circuit=circuit,
    num_weights=8,
    is_vmap=True,
    in_dims=(0, None),
)

电路可视化。

from isqtools.draw import Drawer

dr = Drawer()
dr.plot(qc.qcis)

png

4. 定义损失函数

我们将数据集转换为torch.Tensor格式,并且将参数weights取出来保存到params中。随后定义了损失函数。

X = torch.from_numpy(x.astype(np.float32))
Y = torch.from_numpy(y.astype(np.float32))
params = list(qnn.parameters())
loss_fn = nn.BCELoss()

5. 训练二分类模型

使用pytorch的自动梯度功能,我们使用简单的梯度下降,实现了参数的更新。

learning_rate = 0.1
loss_list = []
epoch = 1000
for t in range(epoch):
    y_pred = qnn(X)
    loss = loss_fn(y_pred, Y)
    loss_list.append(loss.item())
    qnn.zero_grad()
    loss.backward()
    with torch.no_grad():
        for param in qnn.parameters():
            param -= learning_rate * param.grad

打印整个梯度下降过程中,损失的变化。

print("Final loss:", loss_list[-1])
step = np.linspace(0, epoch, epoch)
plt.plot(step,np.array(loss_list))
Final loss: 0.17722667753696442

png

6. 模型准确性验证

output = qnn(X)
output_zeros = torch.zeros_like(output)
output_ones = torch.ones_like(output)
y_predict = torch.where(output > 0.5, output_ones, output_zeros)
print("Accuracy:", sum(y_predict.cpu().detach().numpy() == y) / len(y))
Accuracy: 0.98

将训练得到的模型用于分类,我们发现分类效果较好,仅有少量错误。(红圈为分类错误数据)

plt.scatter(x[:, 0], x[:, 1], c=y, cmap='jet')
for X_, Y_, y_predict_ in zip(X, Y, y_predict):
    if Y_ != y_predict_:
        plt.scatter(X_[0], X_[1], s=200, facecolors="none", edgecolors="r", linewidths=2)

png