Skip to content

Update serialising methods and instructions #951

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jun 12, 2025
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .pylintdict
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ deterministically
diag
dicts
diederik
dill
dimensionality
dir
discretization
Expand Down Expand Up @@ -500,6 +501,7 @@ sdg
seealso
semidefinite
serializable
serializablemodelmixin
shalev
shanno
shende
Expand Down
23 changes: 17 additions & 6 deletions qiskit_machine_learning/algorithms/classifiers/qsvc.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of a Qiskit project.
#
# (C) Copyright IBM 2021, 2024.
# (C) Copyright IBM 2021, 2025.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
Expand Down Expand Up @@ -34,13 +34,24 @@ class QSVC(SVC, SerializableModelMixin):
Read more in the `scikit-learn user guide
<https://scikit-learn.org/stable/modules/svm.html#svm-classification>`_.
**Example**
Examples:
.. code-block::
.. code-block::
from qiskit_machine_learning.kernels import FidelityQuantumKernel
from qiskit_machine_learning.algorithms import QSVC
kernel = FidelityQuantumKernel()
qsvc = QSVC(quantum_kernel=kernel)
qsvc.fit(X_train, y_train)
y_pred = qsvc.predict(X_test)
# Save the trained model
qsvc.to_dill('qsvc_model.dill')
# Load the model for later use
loaded_qsvc = QSVC.from_dill('qsvc_model.dill')
score = loaded_qsvc.score(X_test, y_test)
qsvc = QSVC(quantum_kernel=qkernel)
qsvc.fit(sample_train,label_train)
qsvc.predict(sample_test)
"""

def __init__(self, *, quantum_kernel: Optional[BaseKernel] = None, **kwargs):
Expand Down
60 changes: 52 additions & 8 deletions qiskit_machine_learning/algorithms/serializable_model.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of a Qiskit project.
#
# (C) Copyright IBM 2021, 2023.
# (C) Copyright IBM 2021, 2025.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
Expand All @@ -15,37 +15,70 @@

import dill

from ..utils.deprecation import issue_deprecation_msg


class SerializableModelMixin:
"""
Provides convenient methods for saving and loading models.
Provides convenient methods for saving and loading models via dill serialization.
.. warning::
The legacy :meth:`save` and :meth:`load` methods are deprecated in v0.9.0
and will be removed in a future release. Please use :meth:`to_dill`
and :meth:`from_dill` respectively.
"""

def save(self, file_name: str) -> None:
def to_dill(self, file_name: str) -> None:
"""
Saves this model to the specified file. Internally, the model is serialized via ``dill``.
All parameters are saved, including a primitive instance that is referenced by internal
objects. That means if a model is loaded from a file and is used, for instance, for
inference, the same primitive will be used even if a cloud primitive was used.
.. warning::
Replaces the deprecated :meth:`save` method.
Args:
file_name: a file name or path where to save the model.
file_name: Path where the serialized model will be written.
Example:
.. code-block::
model.to_dill('model_state.dill')
"""
with open(file_name, "wb") as handler:
dill.dump(self, handler)

def save(self, *args) -> None:
"""Backwards compatibility with :meth:`to_dill`, deprecated in v0.9.0."""
issue_deprecation_msg(
msg="SerializableModelMixin.save() is deprecated.",
version="0.9.0",
remedy="Use the to_dill() method instead.",
period="4 months",
)
self.to_dill(*args)

@classmethod
def load(cls, file_name: str) -> Any:
def from_dill(cls, file_name: str) -> Any:
"""
Loads a model from the file. If the loaded model is not an instance of the class whose
Loads a model from a file. If the loaded model is not an instance of the class whose
method was called, then a warning is raised. Nevertheless, the loaded model may be a valid
model.
Replaces the deprecated :meth:`load` method.
Args:
file_name: a file name or path to load a model from.
file_name: Path to the dill file containing the serialized model.
Returns:
A loaded model.
An instance of the model loaded from disk.
Example:
.. code-block::
loaded = MyModel.from_dill('model_state.dill')
Raises:
TypeError: if a loaded model is not an instance of the expected class.
Expand All @@ -55,3 +88,14 @@ def load(cls, file_name: str) -> Any:
if not isinstance(model, cls):
raise TypeError(f"Loaded model is of class {type(model)}. Expected class: {cls}.")
return model

@classmethod
def load(cls, *args) -> Any:
"""Backwards compatibility with :meth:`from_dill`, deprecated in v0.9.0."""
issue_deprecation_msg(
msg="SerializableModelMixin.load() is deprecated.",
version="0.9.0",
remedy="Use the from_dill() classmethod instead.",
period="4 months",
)
return cls.from_dill(*args)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
upgrade:
- |
Added new methods :meth:`to_dill` and :meth:`from_dill`, with improved docstrings and examples.
Deprecated :meth:`save` and :meth:`load` in favor of :meth:`to_dill` and :meth:`from_dill`.
deprecations:
- |
The methods :meth:`SerializableModelMixin.save` and :meth:`SerializableModelMixin.load` have
been deprecated and will be removed (target v0.9.0). Please use
:meth:`SerializableModelMixin.to_dill` and :meth:`SerializableModelMixin.from_dill` instead.
Original file line number Diff line number Diff line change
Expand Up @@ -208,9 +208,9 @@ def test_save_load(self):

# save/load, change the quantum instance and check if predicted values are the same
file_name = os.path.join(tempfile.gettempdir(), "pegasos.model")
regressor.save(file_name)
regressor.to_dill(file_name)
try:
regressor_load = PegasosQSVC.load(file_name)
regressor_load = PegasosQSVC.from_dill(file_name)
loaded_model_predicts = regressor_load.predict(test_features)

np.testing.assert_array_almost_equal(original_predicts, loaded_model_predicts)
Expand All @@ -222,7 +222,7 @@ class FakeModel(SerializableModelMixin):
pass

with self.assertRaises(TypeError):
FakeModel.load(file_name)
FakeModel.from_dill(file_name)

finally:
os.remove(file_name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,9 @@ def test_save_load(self):

# save/load, change the quantum instance and check if predicted values are the same
file_name = os.path.join(tempfile.gettempdir(), "qsvc.model")
classifier.save(file_name)
classifier.to_dill(file_name)
try:
classifier_load = QSVC.load(file_name)
classifier_load = QSVC.from_dill(file_name)
loaded_model_predicts = classifier_load.predict(test_features)

np.testing.assert_array_almost_equal(original_predicts, loaded_model_predicts)
Expand All @@ -119,7 +119,7 @@ class FakeModel(SerializableModelMixin):
pass

with self.assertRaises(TypeError):
FakeModel.load(file_name)
FakeModel.from_dill(file_name)

finally:
os.remove(file_name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -390,9 +390,9 @@ def test_save_load(self, qnn_type):
# save/load, change the quantum instance and check if predicted values are the same
with tempfile.TemporaryDirectory() as dir_name:
file_name = os.path.join(dir_name, "classifier.model")
classifier.save(file_name)
classifier.to_dill(file_name)

classifier_load = NeuralNetworkClassifier.load(file_name)
classifier_load = NeuralNetworkClassifier.from_dill(file_name)
loaded_model_predicts = classifier_load.predict(test_features)

np.testing.assert_array_almost_equal(original_predicts, loaded_model_predicts)
Expand All @@ -404,7 +404,7 @@ class FakeModel(SerializableModelMixin):
pass

with self.assertRaises(TypeError):
FakeModel.load(file_name)
FakeModel.from_dill(file_name)

@idata((True, False))
def test_num_classes_data(self, one_hot):
Expand Down
6 changes: 3 additions & 3 deletions test/algorithms/classifiers/test_pegasos_qsvc.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,9 +227,9 @@ def test_save_load(self):

# save/load, change the quantum instance and check if predicted values are the same
file_name = os.path.join(tempfile.gettempdir(), "pegasos.model")
regressor.save(file_name)
regressor.to_dill(file_name)
try:
regressor_load = PegasosQSVC.load(file_name)
regressor_load = PegasosQSVC.from_dill(file_name)
loaded_model_predicts = regressor_load.predict(test_features)

np.testing.assert_array_almost_equal(original_predicts, loaded_model_predicts)
Expand All @@ -241,7 +241,7 @@ class FakeModel(SerializableModelMixin):
pass

with self.assertRaises(TypeError):
FakeModel.load(file_name)
FakeModel.from_dill(file_name)

finally:
os.remove(file_name)
Expand Down
6 changes: 3 additions & 3 deletions test/algorithms/classifiers/test_qsvc.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,9 @@ def test_save_load(self):

# save/load, change the quantum instance and check if predicted values are the same
file_name = os.path.join(tempfile.gettempdir(), "qsvc.model")
classifier.save(file_name)
classifier.to_dill(file_name)
try:
classifier_load = QSVC.load(file_name)
classifier_load = QSVC.from_dill(file_name)
loaded_model_predicts = classifier_load.predict(test_features)

np.testing.assert_array_almost_equal(original_predicts, loaded_model_predicts)
Expand All @@ -121,7 +121,7 @@ class FakeModel(SerializableModelMixin):
pass

with self.assertRaises(TypeError):
FakeModel.load(file_name)
FakeModel.from_dill(file_name)

finally:
os.remove(file_name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,9 @@ def test_save_load(self):

with tempfile.TemporaryDirectory() as dir_name:
file_name = os.path.join(dir_name, "qsvr.model")
regressor.save(file_name)
regressor.to_dill(file_name)

regressor_load = QSVR.load(file_name)
regressor_load = QSVR.from_dill(file_name)
loaded_model_predicts = regressor_load.predict(test_features)

np.testing.assert_array_almost_equal(original_predicts, loaded_model_predicts)
Expand All @@ -149,7 +149,7 @@ class FakeModel(SerializableModelMixin):
pass

with self.assertRaises(TypeError):
FakeModel.load(file_name)
FakeModel.from_dill(file_name)


if __name__ == "__main__":
Expand Down
6 changes: 3 additions & 3 deletions test/algorithms/regressors/test_neural_network_regressor.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,9 +189,9 @@ def test_save_load(self):
# save/load, change the quantum instance and check if predicted values are the same
with tempfile.TemporaryDirectory() as dir_name:
file_name = os.path.join(dir_name, "regressor.model")
regressor.save(file_name)
regressor.to_dill(file_name)

regressor_load = NeuralNetworkRegressor.load(file_name)
regressor_load = NeuralNetworkRegressor.from_dill(file_name)
loaded_model_predicts = regressor_load.predict(test_features)

np.testing.assert_array_almost_equal(original_predicts, loaded_model_predicts)
Expand All @@ -203,7 +203,7 @@ class FakeModel(SerializableModelMixin):
pass

with self.assertRaises(TypeError):
FakeModel.load(file_name)
FakeModel.from_dill(file_name)

def test_untrained(self):
"""Test untrained regressor."""
Expand Down
6 changes: 3 additions & 3 deletions test/algorithms/regressors/test_qsvr.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,9 @@ def test_save_load(self):

with tempfile.TemporaryDirectory() as dir_name:
file_name = os.path.join(dir_name, "qsvr.model")
regressor.save(file_name)
regressor.to_dill(file_name)

regressor_load = QSVR.load(file_name)
regressor_load = QSVR.from_dill(file_name)
loaded_model_predicts = regressor_load.predict(test_features)

np.testing.assert_array_almost_equal(original_predicts, loaded_model_predicts)
Expand All @@ -144,7 +144,7 @@ class FakeModel(SerializableModelMixin):
pass

with self.assertRaises(TypeError):
FakeModel.load(file_name)
FakeModel.from_dill(file_name)


if __name__ == "__main__":
Expand Down
Loading