cqlib-qml 0.1.0b1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cqlib_qml/__init__.py +58 -0
- cqlib_qml/_configuration.py +53 -0
- cqlib_qml/_numerics.py +31 -0
- cqlib_qml/algorithms/HQNN_classification.py +265 -0
- cqlib_qml/algorithms/QKM.py +362 -0
- cqlib_qml/algorithms/QNN_classification.py +245 -0
- cqlib_qml/algorithms/QSVM.py +340 -0
- cqlib_qml/algorithms/VQC.py +578 -0
- cqlib_qml/algorithms/__init__.py +3 -0
- cqlib_qml/ansatz/BasicQNN.py +153 -0
- cqlib_qml/ansatz/CRADL.py +133 -0
- cqlib_qml/ansatz/CRAML.py +177 -0
- cqlib_qml/ansatz/HE_ansatz.py +239 -0
- cqlib_qml/ansatz/__init__.py +45 -0
- cqlib_qml/ansatz/ansatz.py +1209 -0
- cqlib_qml/data/__init__.py +63 -0
- cqlib_qml/data/data.py +286 -0
- cqlib_qml/data/data_preprocess.py +315 -0
- cqlib_qml/differentiator/__init__.py +47 -0
- cqlib_qml/differentiator/adjoint.py +509 -0
- cqlib_qml/differentiator/parameter_shift.py +284 -0
- cqlib_qml/encoder/FRQI.py +404 -0
- cqlib_qml/encoder/NEQR.py +163 -0
- cqlib_qml/encoder/QubitLattice.py +127 -0
- cqlib_qml/encoder/ZZFeature.py +159 -0
- cqlib_qml/encoder/__init__.py +56 -0
- cqlib_qml/encoder/amplitude.py +162 -0
- cqlib_qml/encoder/angle.py +121 -0
- cqlib_qml/encoder/basis.py +93 -0
- cqlib_qml/encoder/image_encoder.py +131 -0
- cqlib_qml/layer/__init__.py +56 -0
- cqlib_qml/layer/activation.py +438 -0
- cqlib_qml/layer/layer.py +297 -0
- cqlib_qml/layer/linear.py +328 -0
- cqlib_qml/loss.py +407 -0
- cqlib_qml/models/HQNN.py +171 -0
- cqlib_qml/models/QNN.py +154 -0
- cqlib_qml/models/__init__.py +44 -0
- cqlib_qml/models/module.py +397 -0
- cqlib_qml/optimizer.py +684 -0
- cqlib_qml/scheduler.py +614 -0
- cqlib_qml/utils/__init__.py +37 -0
- cqlib_qml/utils/grad_matrix.py +29 -0
- cqlib_qml-0.1.0b1.dist-info/METADATA +131 -0
- cqlib_qml-0.1.0b1.dist-info/RECORD +48 -0
- cqlib_qml-0.1.0b1.dist-info/WHEEL +5 -0
- cqlib_qml-0.1.0b1.dist-info/licenses/LICENSE +201 -0
- cqlib_qml-0.1.0b1.dist-info/top_level.txt +1 -0
cqlib_qml/__init__.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# cqlib_qml/__init__.py
|
|
2
|
+
"""
|
|
3
|
+
Cqlib-QML: Quantum machine learning package based on Cqlib.
|
|
4
|
+
|
|
5
|
+
This package provides quantum machine learning building blocks on top of
|
|
6
|
+
the `cqlib <https://github.com/cq-lib/cqlib>`_ quantum computing library,
|
|
7
|
+
including data encoders, parameterized quantum circuits (ansatz),
|
|
8
|
+
differentiators, neural network models, and quantum algorithms.
|
|
9
|
+
|
|
10
|
+
Available Subpackages:
|
|
11
|
+
- algorithms: Quantum machine learning algorithms (QKM, QSVM, VQC).
|
|
12
|
+
- ansatz: Parameterized quantum circuits (HEAnsatz, BasicQNN, CRADL, CRAML).
|
|
13
|
+
- data: Classical data loading and preprocessing utilities.
|
|
14
|
+
- differentiator: Gradient computation strategies (adjoint, parameter-shift).
|
|
15
|
+
- encoder: Quantum data encoders (Amplitude, Angle, Basis, FRQI, NEQR).
|
|
16
|
+
- layer: Hybrid quantum-classical layers (Linear, activations).
|
|
17
|
+
- models: Quantum neural network models (QNN, HQNN).
|
|
18
|
+
- utils: Gradient matrix computation and other utilities.
|
|
19
|
+
|
|
20
|
+
Examples:
|
|
21
|
+
>>> import numpy as np
|
|
22
|
+
>>> from cqlib_qml.ansatz import HEAnsatz
|
|
23
|
+
>>> from cqlib_qml.models import QNN
|
|
24
|
+
>>>
|
|
25
|
+
>>> ansatz = HEAnsatz(n_qubits=4, d=2, layers=["RY", "CX"])
|
|
26
|
+
>>> ansatz.set_measurement(readouts=[0])
|
|
27
|
+
>>> model = QNN(ansatz=ansatz)
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from . import algorithms, ansatz, data, differentiator, encoder, layer, models, utils
|
|
31
|
+
from .loss import BCELoss, CrossEntropy, HingeLoss, LossFun, MSELoss, SoftmaxCrossEntropy
|
|
32
|
+
from .optimizer import AdaGrad, Adam, OptimizerBase, OptimizerInitializer, RMSProp, SGD
|
|
33
|
+
|
|
34
|
+
__version__ = "0.1.0-beta.1"
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
"algorithms",
|
|
38
|
+
"ansatz",
|
|
39
|
+
"data",
|
|
40
|
+
"differentiator",
|
|
41
|
+
"encoder",
|
|
42
|
+
"layer",
|
|
43
|
+
"models",
|
|
44
|
+
"utils",
|
|
45
|
+
"LossFun",
|
|
46
|
+
"HingeLoss",
|
|
47
|
+
"MSELoss",
|
|
48
|
+
"BCELoss",
|
|
49
|
+
"CrossEntropy",
|
|
50
|
+
"SoftmaxCrossEntropy",
|
|
51
|
+
"OptimizerBase",
|
|
52
|
+
"OptimizerInitializer",
|
|
53
|
+
"SGD",
|
|
54
|
+
"AdaGrad",
|
|
55
|
+
"RMSProp",
|
|
56
|
+
"Adam",
|
|
57
|
+
"__version__",
|
|
58
|
+
]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Parse constructor-like configuration without evaluating Python code."""
|
|
2
|
+
import ast
|
|
3
|
+
import inspect
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def parse_configuration(value, registry, nested=None):
|
|
7
|
+
"""Accept a registered name or keyword-only call with literal values.
|
|
8
|
+
|
|
9
|
+
Nested calls are restricted to the supplied scheduler registry. Attribute
|
|
10
|
+
access, positional arguments and expanded arguments are never evaluated.
|
|
11
|
+
"""
|
|
12
|
+
try:
|
|
13
|
+
tree = ast.parse(value.strip(), mode='eval').body
|
|
14
|
+
except (SyntaxError, TypeError, AttributeError) as exc:
|
|
15
|
+
raise ValueError(f'Invalid configuration: {value!r}') from exc
|
|
16
|
+
|
|
17
|
+
def construct(node, constructors):
|
|
18
|
+
if isinstance(node, ast.Name):
|
|
19
|
+
name, keywords = node.id, []
|
|
20
|
+
elif isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and not node.args:
|
|
21
|
+
name, keywords = node.func.id, node.keywords
|
|
22
|
+
else:
|
|
23
|
+
raise ValueError('Configuration must be a registered name or keyword-only call')
|
|
24
|
+
constructor = constructors.get(name.casefold())
|
|
25
|
+
if constructor is None:
|
|
26
|
+
raise ValueError(f'Unsupported configuration name: {name}')
|
|
27
|
+
allowed = {key for key, parameter in inspect.signature(constructor).parameters.items()
|
|
28
|
+
if parameter.kind in (parameter.POSITIONAL_OR_KEYWORD, parameter.KEYWORD_ONLY)}
|
|
29
|
+
kwargs = {}
|
|
30
|
+
for keyword in keywords:
|
|
31
|
+
key = keyword.arg
|
|
32
|
+
if key not in allowed or key in kwargs:
|
|
33
|
+
raise ValueError(f'Unknown or duplicate parameter: {key}')
|
|
34
|
+
if isinstance(keyword.value, ast.Call) and key == 'lr_scheduler' and nested:
|
|
35
|
+
kwargs[key] = construct(keyword.value, nested)
|
|
36
|
+
else:
|
|
37
|
+
try:
|
|
38
|
+
kwargs[key] = ast.literal_eval(keyword.value)
|
|
39
|
+
except (ValueError, TypeError) as exc:
|
|
40
|
+
raise ValueError(f'Parameter {key} must be a literal') from exc
|
|
41
|
+
return constructor(**kwargs)
|
|
42
|
+
|
|
43
|
+
return construct(tree, registry)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def same_parameter_value(old, new):
|
|
47
|
+
"""Compare scalar configurations without assuming array equality is scalar."""
|
|
48
|
+
if old is new:
|
|
49
|
+
return True
|
|
50
|
+
try:
|
|
51
|
+
return bool(old == new)
|
|
52
|
+
except (TypeError, ValueError):
|
|
53
|
+
return False
|
cqlib_qml/_numerics.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Internal scaling helpers that avoid complex reciprocal overflow."""
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def scaled_vector(vector):
|
|
6
|
+
"""Return a vector at unit component scale and its real scale factor.
|
|
7
|
+
|
|
8
|
+
Divide real and imaginary components separately: complex division can
|
|
9
|
+
overflow its reciprocal for a finite, subnormal real denominator.
|
|
10
|
+
Exact zero is returned unchanged with scale zero.
|
|
11
|
+
"""
|
|
12
|
+
vector = np.asarray(vector)
|
|
13
|
+
if not np.isfinite(vector).all():
|
|
14
|
+
raise ValueError("Vector components must be finite.")
|
|
15
|
+
scale = max(np.max(np.abs(vector.real), initial=0.),
|
|
16
|
+
np.max(np.abs(vector.imag), initial=0.))
|
|
17
|
+
if scale == 0:
|
|
18
|
+
return vector.copy(), scale
|
|
19
|
+
if np.iscomplexobj(vector):
|
|
20
|
+
scaled = np.empty(vector.shape, dtype=np.complex128)
|
|
21
|
+
scaled.real = vector.real / scale
|
|
22
|
+
scaled.imag = vector.imag / scale
|
|
23
|
+
else:
|
|
24
|
+
scaled = vector / scale
|
|
25
|
+
return scaled, scale
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def stable_norm(vector):
|
|
29
|
+
"""Compute a Euclidean norm without squaring unscaled components."""
|
|
30
|
+
scaled, scale = scaled_vector(vector)
|
|
31
|
+
return scale * np.linalg.norm(scaled)
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# cqlib_qml/algorithms/HQNN_classification.py
|
|
2
|
+
"""
|
|
3
|
+
Hybrid Quantum Neural Network for classification tasks.
|
|
4
|
+
|
|
5
|
+
This module provides a complete training pipeline for Hybrid Quantum Neural Networks
|
|
6
|
+
(HQNNs) on classification problems. It includes training and validation loops with
|
|
7
|
+
checkpoint saving, TensorBoard logging, and configuration management.
|
|
8
|
+
|
|
9
|
+
The HQNN combines a parameterized quantum circuit (ansatz) with a classical
|
|
10
|
+
fully-connected layer, leveraging the strengths of both quantum and classical
|
|
11
|
+
computing paradigms.
|
|
12
|
+
|
|
13
|
+
Examples:
|
|
14
|
+
>>> from cqlib_qml.algorithms.HQNN_classification import train, validate
|
|
15
|
+
>>> from cqlib_qml.models import HQNN
|
|
16
|
+
>>> from cqlib_qml.ansatz import HEAnsatz
|
|
17
|
+
>>> from cqlib_qml.encoder import FRQI
|
|
18
|
+
>>>
|
|
19
|
+
>>> # Setup model
|
|
20
|
+
>>> ansatz = HEAnsatz(n_qubits=5, d=3, layers=["RY", "CX"])
|
|
21
|
+
>>> model = HQNN(ansatz=ansatz, out_dim=3)
|
|
22
|
+
>>>
|
|
23
|
+
>>> # Train
|
|
24
|
+
>>> for epoch in range(EPOCHS):
|
|
25
|
+
... train(epoch, 0, model, train_loader, loss_fun, model_path, EPOCHS, tb)
|
|
26
|
+
... validate(epoch, model, test_loader, loss_fun, BATCH_SIZE, tb)
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
import time
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
import tqdm
|
|
32
|
+
import yaml
|
|
33
|
+
import numpy as np
|
|
34
|
+
from typing import Optional, Any
|
|
35
|
+
|
|
36
|
+
from cqlib_qml.data.data_preprocess import get_mnist_dataloader
|
|
37
|
+
from cqlib_qml.models import HQNN
|
|
38
|
+
from cqlib_qml.ansatz import *
|
|
39
|
+
from cqlib_qml.encoder import *
|
|
40
|
+
from cqlib_qml.loss import *
|
|
41
|
+
from cqlib_qml.optimizer import *
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
import torch.utils.tensorboard
|
|
45
|
+
|
|
46
|
+
TENSORBOARD_AVAILABLE = True
|
|
47
|
+
except ImportError:
|
|
48
|
+
TENSORBOARD_AVAILABLE = False
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def train(
|
|
52
|
+
ep: int,
|
|
53
|
+
it_start: int,
|
|
54
|
+
net: HQNN,
|
|
55
|
+
train_loader,
|
|
56
|
+
loss_fun,
|
|
57
|
+
model_path: str,
|
|
58
|
+
total_epochs: int,
|
|
59
|
+
tb: Optional[Any] = None,
|
|
60
|
+
) -> None:
|
|
61
|
+
"""
|
|
62
|
+
Train the HQNN model for one epoch.
|
|
63
|
+
|
|
64
|
+
This function iterates through the training data, computes forward and
|
|
65
|
+
backward passes, updates model parameters, and logs training metrics.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
ep (int): Current epoch number (0-indexed).
|
|
69
|
+
it_start (int): Starting iteration index within the epoch. Used for
|
|
70
|
+
resuming training from a checkpoint.
|
|
71
|
+
net (HQNN): The Hybrid Quantum Neural Network model to train.
|
|
72
|
+
train_loader: DataLoader for training data.
|
|
73
|
+
loss_fun: Loss function instance.
|
|
74
|
+
model_path (str): Path to save checkpoints.
|
|
75
|
+
total_epochs (int): Total number of epochs for training.
|
|
76
|
+
tb (optional): TensorBoard SummaryWriter instance for logging.
|
|
77
|
+
If None, logging is disabled. Defaults to None.
|
|
78
|
+
|
|
79
|
+
Note:
|
|
80
|
+
The function uses the global variable `EPOCH` defined in the main script
|
|
81
|
+
for determining the latest checkpoint.
|
|
82
|
+
|
|
83
|
+
Examples:
|
|
84
|
+
>>> for epoch in range(10):
|
|
85
|
+
... train(epoch, 0, model, train_loader, loss_fun, "./checkpoints/", 10, tensorboard_writer)
|
|
86
|
+
"""
|
|
87
|
+
loader = tqdm.tqdm(train_loader, desc="Training epoch {}".format(ep + 1), leave=True)
|
|
88
|
+
for it, (x_train, y_train) in enumerate(loader):
|
|
89
|
+
if it < it_start:
|
|
90
|
+
continue
|
|
91
|
+
it_start = 0
|
|
92
|
+
expectations = net.forward(x_train) # (batch_size, n_classes)
|
|
93
|
+
# Prepare labels for CrossEntropy loss
|
|
94
|
+
y_true = np.zeros(expectations.shape)
|
|
95
|
+
for i in range(len(y_train)):
|
|
96
|
+
idx = y_train[i]
|
|
97
|
+
y_true[i, idx] = 1
|
|
98
|
+
y_pred = expectations
|
|
99
|
+
|
|
100
|
+
# Compute loss
|
|
101
|
+
loss = loss_fun(y_pred, y_true)
|
|
102
|
+
# Optimize: backward pass and parameter update
|
|
103
|
+
net.backward(loss_fun.grads())
|
|
104
|
+
net.update(cur_loss=loss)
|
|
105
|
+
net.zero_grad()
|
|
106
|
+
|
|
107
|
+
# Compute accuracy
|
|
108
|
+
correct = sum(y_pred.argmax(axis=1) == y_train)
|
|
109
|
+
accuracy = correct / len(y_train)
|
|
110
|
+
loader.set_postfix(it=it, loss="{:.3f}".format(loss), accuracy="{:.3f}".format(accuracy))
|
|
111
|
+
# Log metrics
|
|
112
|
+
if tb is not None:
|
|
113
|
+
tb.add_scalar("train/loss", loss, ep * len(loader) + it)
|
|
114
|
+
tb.add_scalar("train/accuracy", accuracy, ep * len(loader) + it)
|
|
115
|
+
# Save checkpoint
|
|
116
|
+
latest = (ep + 1) == total_epochs and (it + 1) == len(loader)
|
|
117
|
+
if (it != 0 and it % 30 == 0) or latest:
|
|
118
|
+
net.save_checkpoint(model_path, ep, it, latest)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def validate(ep: int, net: HQNN, test_loader, loss_fun, batch_size: int, tb: Optional[Any] = None) -> tuple:
|
|
122
|
+
"""
|
|
123
|
+
Validate the HQNN model on the test dataset.
|
|
124
|
+
|
|
125
|
+
This function evaluates the model's performance on the test set and
|
|
126
|
+
computes average loss and accuracy across all batches.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
ep (int): Current epoch number (0-indexed).
|
|
130
|
+
net (HQNN): The Hybrid Quantum Neural Network model to validate.
|
|
131
|
+
test_loader: DataLoader for test data.
|
|
132
|
+
loss_fun: Loss function instance.
|
|
133
|
+
batch_size (int): Batch size used in the dataloader.
|
|
134
|
+
tb (optional): TensorBoard SummaryWriter instance for logging.
|
|
135
|
+
If None, logging is disabled. Defaults to None.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
tuple: (avg_loss, avg_acc) where:
|
|
139
|
+
- avg_loss (float): Average loss over the validation set.
|
|
140
|
+
- avg_acc (float): Average accuracy over the validation set.
|
|
141
|
+
|
|
142
|
+
Note:
|
|
143
|
+
Model is evaluated in non-trainable mode.
|
|
144
|
+
|
|
145
|
+
Examples:
|
|
146
|
+
>>> avg_loss, avg_acc = validate(epoch, model, test_loader, loss_fun, 32, tensorboard_writer)
|
|
147
|
+
>>> print(f"Validation: loss={avg_loss:.4f}, acc={avg_acc:.4f}")
|
|
148
|
+
"""
|
|
149
|
+
loader_val = tqdm.tqdm(test_loader, desc="Validating epoch {}".format(ep + 1), leave=True)
|
|
150
|
+
total_loss = 0.0
|
|
151
|
+
total_samples = 0
|
|
152
|
+
total_correct = 0
|
|
153
|
+
for it, (x_test, y_test) in enumerate(loader_val):
|
|
154
|
+
expectations_val = net.forward(x_test, trainable=False)
|
|
155
|
+
# Prepare labels for CrossEntropy loss
|
|
156
|
+
y_true_val = np.zeros(expectations_val.shape)
|
|
157
|
+
for i in range(len(y_test)):
|
|
158
|
+
idx = y_test[i]
|
|
159
|
+
y_true_val[i, idx] = 1
|
|
160
|
+
y_pred_val = expectations_val
|
|
161
|
+
|
|
162
|
+
loss_val = loss_fun(y_pred_val, y_true_val)
|
|
163
|
+
count = len(y_test)
|
|
164
|
+
total_samples += count
|
|
165
|
+
total_loss += loss_val if getattr(loss_fun, "reduction", "mean") == "sum" else loss_val * count
|
|
166
|
+
|
|
167
|
+
# Compute accuracy
|
|
168
|
+
correct_val = sum(y_pred_val.argmax(axis=1) == y_test)
|
|
169
|
+
total_correct += correct_val
|
|
170
|
+
accuracy_val = correct_val / len(y_test)
|
|
171
|
+
loader_val.set_postfix(
|
|
172
|
+
it=it,
|
|
173
|
+
loss="{:.3f}".format(loss_val),
|
|
174
|
+
accuracy="{:.3f}".format(accuracy_val),
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
if not total_samples:
|
|
178
|
+
raise ValueError("Validation dataset must not be empty")
|
|
179
|
+
avg_loss = total_loss / total_samples
|
|
180
|
+
avg_acc = total_correct / total_samples
|
|
181
|
+
if tb is not None:
|
|
182
|
+
tb.add_scalar("validation/loss", avg_loss, ep)
|
|
183
|
+
tb.add_scalar("validation/accuracy", avg_acc, ep)
|
|
184
|
+
print("Validation Average Loss: {}, Accuracy: {}".format(avg_loss, avg_acc))
|
|
185
|
+
return avg_loss, avg_acc
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
if __name__ == "__main__":
|
|
189
|
+
"""
|
|
190
|
+
Main execution block for training HQNN on MNIST dataset.
|
|
191
|
+
|
|
192
|
+
This script demonstrates a complete training pipeline including:
|
|
193
|
+
1. Data loading and preprocessing (MNIST with FRQI encoding)
|
|
194
|
+
2. Model initialization (HEAnsatz + HQNN)
|
|
195
|
+
3. Training loop with progress tracking
|
|
196
|
+
4. Validation and checkpoint saving
|
|
197
|
+
5. Configuration logging
|
|
198
|
+
|
|
199
|
+
Configuration:
|
|
200
|
+
- Dataset: MNIST (classes 0, 1, 2)
|
|
201
|
+
- Image size: 4x4 pixels
|
|
202
|
+
- Grayscale: 2 levels
|
|
203
|
+
- Encoding: FRQI (Flexible Representation of Quantum Images)
|
|
204
|
+
- Ansatz: HEAnsatz (Hardware-Efficient)
|
|
205
|
+
- Loss: SoftmaxCrossEntropy
|
|
206
|
+
- Optimizer: Adam (lr=0.01)
|
|
207
|
+
- Epochs: 10
|
|
208
|
+
- Batch size: 32
|
|
209
|
+
"""
|
|
210
|
+
# Training parameters
|
|
211
|
+
EPOCH = 10
|
|
212
|
+
BATCH_SIZE = 32
|
|
213
|
+
LR = 0.01
|
|
214
|
+
SEED = 0
|
|
215
|
+
ep_start = 0
|
|
216
|
+
it_start = 0
|
|
217
|
+
np.random.seed(SEED)
|
|
218
|
+
|
|
219
|
+
classes = [0, 1, 2]
|
|
220
|
+
RESIZE = (4, 4)
|
|
221
|
+
GRAYSCALE = 2
|
|
222
|
+
n_pixels = RESIZE[0] * RESIZE[1]
|
|
223
|
+
# Initialize encoder, loss function, and optimizer
|
|
224
|
+
encoding = FRQI(n_pixels, GRAYSCALE)
|
|
225
|
+
loss_fun = SoftmaxCrossEntropy()
|
|
226
|
+
optimizer = Adam(lr=LR)
|
|
227
|
+
|
|
228
|
+
# Setup quantum circuit parameters
|
|
229
|
+
pos_qubits = int(np.log2(n_pixels))
|
|
230
|
+
color_qubits = 1
|
|
231
|
+
n_qubits = pos_qubits + color_qubits
|
|
232
|
+
|
|
233
|
+
# Initialize ansatz and model
|
|
234
|
+
ansatz = HEAnsatz(n_qubits, d=5, layers=["RZ", "RY", "RZ", "CX"])
|
|
235
|
+
train_loader, test_loader = get_mnist_dataloader(classes, RESIZE, encoding, BATCH_SIZE, GRAYSCALE)
|
|
236
|
+
net = HQNN(ansatz=ansatz, out_dim=len(classes), optimizer=optimizer)
|
|
237
|
+
|
|
238
|
+
# Setup logging and checkpoint directories
|
|
239
|
+
now_time = time.strftime("%Y-%m-%d-%H_%M_%S", time.localtime(time.time()))
|
|
240
|
+
model_path = "./HQNN_MNIST_" + now_time + "/"
|
|
241
|
+
Path(model_path).mkdir(parents=True, exist_ok=True)
|
|
242
|
+
tb = torch.utils.tensorboard.SummaryWriter(log_dir=model_path + "logs") if TENSORBOARD_AVAILABLE else None
|
|
243
|
+
|
|
244
|
+
# Save configuration
|
|
245
|
+
config = {
|
|
246
|
+
"classes": str(classes),
|
|
247
|
+
"encoding": str(encoding),
|
|
248
|
+
"ansatz": str(ansatz),
|
|
249
|
+
"grayscale": GRAYSCALE,
|
|
250
|
+
"resize": str(RESIZE),
|
|
251
|
+
"LR": LR,
|
|
252
|
+
"loss_fun": str(loss_fun),
|
|
253
|
+
"seed": SEED,
|
|
254
|
+
}
|
|
255
|
+
with open(model_path + "config.yaml", "w") as config_file:
|
|
256
|
+
config_file.write(yaml.dump(config))
|
|
257
|
+
|
|
258
|
+
# Training loop
|
|
259
|
+
for ep in range(ep_start, EPOCH):
|
|
260
|
+
if ep > ep_start:
|
|
261
|
+
it_start = 0
|
|
262
|
+
train(ep, it_start, net, train_loader, loss_fun, model_path, EPOCH, tb)
|
|
263
|
+
avg_loss, avg_acc = validate(ep, net, test_loader, loss_fun, BATCH_SIZE, tb)
|
|
264
|
+
with open(model_path + "result.yaml", "a") as result_file:
|
|
265
|
+
result_file.write("Validation Average Loss: {}, Accuracy: {}\n".format(avg_loss, avg_acc))
|