Open hifi-hyp opened 1 year ago
Hi hifi-hyp,
The newly suggested way to use quantum device is to initialize it in forward
like below: I will update the notebook soon. You may have a look at python script. Thanks for the feedback!
class QuanvolutionFilter(tq.QuantumModule):
def __init__(self):
super().__init__()
self.n_wires = 4
self.encoder = tq.GeneralEncoder(
[
{"input_idx": [0], "func": "ry", "wires": [0]},
{"input_idx": [1], "func": "ry", "wires": [1]},
{"input_idx": [2], "func": "ry", "wires": [2]},
{"input_idx": [3], "func": "ry", "wires": [3]},
]
)
self.q_layer = tq.RandomLayer(n_ops=8, wires=list(range(self.n_wires)))
self.measure = tq.MeasureAll(tq.PauliZ)
def forward(self, x, use_qiskit=False):
bsz = x.shape[0]
qdev = tq.QuantumDevice(self.n_wires, bsz=bsz, device=x.device)
size = 28
x = x.view(bsz, size, size)
data_list = []
for c in range(0, size, 2):
for r in range(0, size, 2):
data = torch.transpose(
torch.cat(
(x[:, c, r], x[:, c, r + 1], x[:, c + 1, r], x[:, c + 1, r + 1])
).view(4, bsz),
0,
1,
)
if use_qiskit:
data = self.qiskit_processor.process_parameterized(
qdev, self.encoder, self.q_layer, self.measure, data
)
else:
self.encoder(qdev, data)
self.q_layer(qdev)
data = self.measure(qdev)
data_list.append(data.view(bsz, 4))
result = torch.cat(data_list, dim=1).float()
return result
Hi @Hanrui-Wang , thanks for your reply! However, after I use the updated code you provide, I still cannot reproduce the results for Quanvolution Neural Network. The test accuracy is only around 32% after 15-epoch training.
Here is my training code, which is the same as the notebook you provide.
import torch
import torch.nn.functional as F
import torch.optim as optim
import numpy as np
import torchquantum as tq
import random
from torchquantum.datasets import MNIST
from torch.optim.lr_scheduler import CosineAnnealingLR
class QuanvolutionFilter(tq.QuantumModule):
def __init__(self):
super().__init__()
self.n_wires = 4
self.encoder = tq.GeneralEncoder(
[
{"input_idx": [0], "func": "ry", "wires": [0]},
{"input_idx": [1], "func": "ry", "wires": [1]},
{"input_idx": [2], "func": "ry", "wires": [2]},
{"input_idx": [3], "func": "ry", "wires": [3]},
]
)
self.q_layer = tq.RandomLayer(n_ops=8, wires=list(range(self.n_wires)))
self.measure = tq.MeasureAll(tq.PauliZ)
def forward(self, x, use_qiskit=False):
bsz = x.shape[0]
qdev = tq.QuantumDevice(self.n_wires, bsz=bsz, device=x.device)
size = 28
x = x.view(bsz, size, size)
data_list = []
for c in range(0, size, 2):
for r in range(0, size, 2):
data = torch.transpose(
torch.cat(
(x[:, c, r], x[:, c, r + 1], x[:, c + 1, r], x[:, c + 1, r + 1])
).view(4, bsz),
0,
1,
)
if use_qiskit:
data = self.qiskit_processor.process_parameterized(
qdev, self.encoder, self.q_layer, self.measure, data
)
else:
self.encoder(qdev, data)
self.q_layer(qdev)
data = self.measure(qdev)
data_list.append(data.view(bsz, 4))
result = torch.cat(data_list, dim=1).float()
return result
class HybridModel(torch.nn.Module):
def __init__(self):
super().__init__()
self.qf = QuanvolutionFilter()
self.linear = torch.nn.Linear(4*14*14, 10)
def forward(self, x, use_qiskit=False):
with torch.no_grad():
x = self.qf(x, use_qiskit)
x = self.linear(x)
return F.log_softmax(x, -1)
class HybridModel_without_qf(torch.nn.Module):
def __init__(self):
super().__init__()
self.linear = torch.nn.Linear(28*28, 10)
def forward(self, x, use_qiskit=False):
x = x.view(-1, 28*28)
x = self.linear(x)
return F.log_softmax(x, -1)
random.seed(42)
np.random.seed(42)
torch.manual_seed(42)
dataset = MNIST(
root='./mnist_data',
train_valid_split_ratio=[0.9, 0.1],
n_test_samples=300,
n_train_samples=500,
)
dataflow = dict()
for split in dataset:
sampler = torch.utils.data.RandomSampler(dataset[split])
dataflow[split] = torch.utils.data.DataLoader(
dataset[split],
batch_size=10,
sampler=sampler,
num_workers=8,
pin_memory=True)
use_cuda = torch.cuda.is_available()
device = torch.device("cuda" if use_cuda else "cpu")
model = HybridModel().to(device)
model_without_qf = HybridModel_without_qf().to(device)
n_epochs = 15
optimizer = optim.Adam(model.parameters(), lr=5e-3, weight_decay=1e-4)
scheduler = CosineAnnealingLR(optimizer, T_max=n_epochs)
accu_list1 = []
loss_list1 = []
accu_list2 = []
loss_list2 = []
def train(dataflow, model, device, optimizer):
for feed_dict in dataflow['train']:
inputs = feed_dict['image'].to(device)
targets = feed_dict['digit'].to(device)
outputs = model(inputs)
loss = F.nll_loss(outputs, targets)
optimizer.zero_grad()
loss.backward()
optimizer.step()
print(f"loss: {loss.item()}", end='\r')
def valid_test(dataflow, split, model, device, qiskit=False):
target_all = []
output_all = []
with torch.no_grad():
for feed_dict in dataflow[split]:
inputs = feed_dict['image'].to(device)
targets = feed_dict['digit'].to(device)
outputs = model(inputs, use_qiskit=qiskit)
target_all.append(targets)
output_all.append(outputs)
target_all = torch.cat(target_all, dim=0)
output_all = torch.cat(output_all, dim=0)
_, indices = output_all.topk(1, dim=1)
masks = indices.eq(target_all.view(-1, 1).expand_as(indices))
size = target_all.shape[0]
corrects = masks.sum().item()
accuracy = corrects / size
loss = F.nll_loss(output_all, target_all).item()
print(f"{split} set accuracy: {accuracy}")
print(f"{split} set loss: {loss}")
return accuracy, loss
for epoch in range(1, n_epochs + 1):
# train
print(f"Epoch {epoch}:")
train(dataflow, model, device, optimizer)
print(optimizer.param_groups[0]['lr'])
# valid
accu, loss = valid_test(dataflow, 'test', model, device, )
accu_list1.append(accu)
loss_list1.append(loss)
scheduler.step()
@hifi-hyp Try initializing the q_device
within the double loop and lose the torch.no_grad()
in the forward
method of the class HybridModel
:
for c in range(0, size, 2):
for r in range(0, size, 2):
qdev = tq.QuantumDevice(self.n_wires, bsz=bsz, device=x.device)
class HybridModel(torch.nn.Module):
def __init__(self):
super().__init__()
self.qf = QuanvolutionFilter()
self.linear = torch.nn.Linear(4*14*14, 10)
def forward(self, x, use_qiskit=False):
x = self.qf(x, use_qiskit)
x = self.linear(x)
return F.log_softmax(x, -1)
initializing the
q_device
within the double loop and lose thetorch.no_grad()
in theforward
method of the classHybridModel
:
Can you explain why doing this can increase the accuracy on the test set?
initializing the
q_device
within the double loop and lose thetorch.no_grad()
in theforward
method of the classHybridModel
:Can you explain why doing this can increase the accuracy on the test set?
The initiated quantum device is the leaf of backward propagation, i.e. the propagation ends at it. Thus, it should be put at the same level of the layers applied on it, the layers being self.encoder(qdev, data)
, self.q_layer(qdev)
and data = self.measure(qdev)
. If initiated outside the double loops, the backward propagation would give an error as we would be trying to propagate multiple times while the computational graph created by pytorch is deleted after one propagation by design.
Losing torch.no_grad()
is just so that we are able to do backward propagation.
In each iteration, the state of the
self.q_deivce
will be changed. Why is the changedself.q_device
in the previous iteration used in the next iteration here?