Skip to content
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