量子二分类
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')
查看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个量子比特,定义了inputs
和weights
两个参数,使用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)
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
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)