Spanish
Idiomas
English
Bengali
French
Hindi
Italian
Japanese
Korean
Malayalam
Russian
Spanish
Tamil
Turkish
Vietnamese
Shortcuts

Nota

Esta página fue generada a partir de docs/tutorials/06_torch_runtime.ipynb.

Torch Runtime

En este tutorial, presentamos Torch Runtime y mostramos cómo usarlo a través de la clase TorchRuntimeClient en Qiskit Machine Learning. Torch Runtime aprovecha Qiskit Runtime para el aprendizaje automático híbrido cuántico-clásico basado en un Module de PyTorch. Permite entrenar modelos o predecir los resultados con modelos entrenados significativamente más rápido. A continuación, mostramos cómo usar Torch Runtime con dos ejemplos simples para tareas de regresión y clasificación.

1. Regresión

Primero, mostramos cómo usar Torch Runtime a través de TorchRuntimeClient usando el ejemplo de regresión simple. En el ejemplo, realizaremos una tarea de regresión en un conjunto de datos generado aleatoriamente siguiendo una onda sinusoidal.

[1]:
import numpy as np
import matplotlib.pyplot as plt

from torch import Tensor, manual_seed, is_tensor
from torch.nn import Linear, CrossEntropyLoss, MSELoss
from torch.optim import LBFGS, Adam
from torch.utils.data import Dataset, DataLoader

from qiskit import Aer, QuantumCircuit
from qiskit.utils import QuantumInstance, algorithm_globals
from qiskit.opflow import AerPauliExpectation
from qiskit.circuit import Parameter
from qiskit.circuit.library import RealAmplitudes, ZZFeatureMap
from qiskit_machine_learning.neural_networks import CircuitQNN, TwoLayerQNN
from qiskit_machine_learning.connectors import TorchConnector
from qiskit_machine_learning.runtime import TorchRuntimeClient, TorchRuntimeResult


# Set seed for random generators
seed = 42
manual_seed(seed)
algorithm_globals.random_seed = seed
[2]:
# Generate random dataset for the training
import matplotlib.pyplot as plt

np.random.seed(0)
num_samples = 20
eps = 0.2
lb, ub = -np.pi, np.pi
f = lambda x: np.sin(x)
X = (ub - lb) * np.random.rand(num_samples, 1) + lb
y = f(X) + eps * (2 * np.random.rand(num_samples, 1) - 1)

plt.figure()
plt.plot(np.linspace(lb, ub), f(np.linspace(lb, ub)), "r--")
plt.plot(X, y, "bo")
plt.show()
../_images/tutorials_06_torch_runtime_3_0.png

TorchRuntimeClient requiere un DataLoader de PyTorch como entrada para entrenar/predecir. Para ese propósito, creamos una clase de conjunto de datos de torch personalizada.

[3]:
# Create custom torch dataset class
class TorchDataset(Dataset):
    """Map-style dataset"""

    def __init__(self, X, y):
        self.X = Tensor(X).float()
        self.y = Tensor(y).float()

    def __len__(self):
        return len(self.X)

    def __getitem__(self, idx):
        import torch

        if torch.is_tensor(idx):
            idx = idx.tolist()

        X_i = self.X[idx]
        y_i = self.y[idx]

        # important: the dataset item must be returned as data,target
        return X_i, y_i


# Create a train loader
train_set = TorchDataset(X, y)
train_loader1 = DataLoader(train_set, batch_size=1, shuffle=False)

Crea una instancia de TorchConnector para envolver un modelo QNN y poder usar pytorch para entrenar el modelo, luego configura un optimizador y una función de pérdida como de costumbre.

[4]:
from qiskit.circuit import Parameter

# Construct simple feature map
param_x = Parameter("x")
feature_map = QuantumCircuit(1, name="fm")
feature_map.ry(param_x, 0)

# Construct simple feature map
param_y = Parameter("y")
ansatz = QuantumCircuit(1, name="vf")
ansatz.ry(param_y, 0)

# Construct QNN
qnn1 = TwoLayerQNN(1, feature_map, ansatz)
print(qnn1.operator)


initial_weights = 0.1 * (2 * algorithm_globals.random.random(qnn1.num_weights) - 1)
model1 = TorchConnector(qnn1, initial_weights)

# Define optimizer and loss function
optimizer1 = Adam(model1.parameters(), lr=0.1)
loss_func1 = MSELoss(reduction="sum")
ComposedOp([
  OperatorMeasurement(1.0 * Z),
  CircuitStateFn(
     ┌───────┐┌───────┐
  q: ┤ fm(x) ├┤ vf(y) ├
     └───────┘└───────┘
  )
])

Carga un proveedor y especifica un backend para el servicio en tiempo de ejecución.

[5]:
# Set up a provider and backend
from qiskit import IBMQ

IBMQ.load_account()

provider = IBMQ.get_provider(project="default")  # replace by your runtime provider
backend = provider.get_backend("ibmq_qasm_simulator")  # select a backend that supports the runtime

Crea una instancia de Torch Runtime Client con el modelo, el optimizador y otras configuraciones.

[6]:
torch_runtime_client = TorchRuntimeClient(
    provider=provider,
    model=model1,
    optimizer=optimizer1,
    loss_func=loss_func1,
    epochs=5,
    backend=backend,
)

Llama a fit() para entrenar el modelo

Llama al método fit en TorchRuntimeClient con el cargador de datos para entrenar el modelo.

[7]:
fit_result = torch_runtime_client.fit(train_loader=train_loader1)

Puedes acceder a la información de los resultados del entrenamiento consultando las propiedades de la variable fit_result, que es una instancia de la clase TorchRuntimeResult. Además, los parámetros del modelo en TorchRuntimeClient se actualizan con los entrenados.

[8]:
print("id: ", fit_result.job_id)
print("execution time: ", fit_result.execution_time)
print("model_state_dict: ", torch_runtime_client.model.state_dict())
id:  c6sqc05a2crk6k574olg
execution time:  10.647154569625854
model_state_dict:  OrderedDict([('weight', tensor([-1.5758])), ('_weights', tensor([-1.5758]))])

También puedes consultar la propiedad train_history, que es una lista de diccionarios, cada uno por época. En un diccionario puedes encontrar propiedades como: - epoch, índice de época - loss, valor de pérdida en esta época - forward_time, tiempo empleado en el paso hacia adelante (forward pass), en segundos - backward_time, tiempo empleado en el paso hacia atrás (backward pass), en segundos - epoch_time, tiempo de época, en segundos. «

Entrenamiento con validación

Torch Runtime también puede realizar la validación mientras se entrena un modelo pasando un cargador de datos de validación al método fit.

[9]:
# Create a validation dataloader
X_test = [[x] for x in np.linspace(lb, ub)]
y_test = [[y] for y in f(np.linspace(lb, ub))]
test_set = TorchDataset(X_test, y_test)
test_loader = DataLoader(test_set, batch_size=1, shuffle=False)
[10]:
torch_runtime_client = TorchRuntimeClient(
    provider=provider,
    model=model1,
    optimizer=optimizer1,
    loss_func=loss_func1,
    epochs=5,
    backend=backend,
)
[11]:
# Pass a train data loader and a validation data loader
fit_result = torch_runtime_client.fit(train_loader=train_loader1, val_loader=test_loader)
[12]:
print("id: ", fit_result.job_id)
print("execution time: ", fit_result.execution_time)
id:  c6sqc6la2crk6k574oo0
execution time:  13.473742246627808

Puedes consultar la propiedad val_history, que es una lista de diccionarios para los procesos de validación, cada uno por época. En un diccionario puedes encontrar las mismas propiedades que en train_history.

Llama a predict() para realizar la predicción

Llama al método predict en TorchRuntimeClient con el cargador de datos para realizar predicciones sobre los datos pasados utilizando el modelo entrenado.

[13]:
predict_result = torch_runtime_client.predict(data_loader=test_loader)
[14]:
print("id: ", predict_result.job_id)
print("execution time: ", predict_result.execution_time)
id:  c6sqcclg2jd5ui6ctu60
execution time:  0.5448770523071289
[15]:
# Plot the original function
plt.plot(np.linspace(lb, ub), f(np.linspace(lb, ub)), "r--")

# Plot the training data
plt.plot(X, y, "bo")
# # Plot the prediction result
y_ = []
for output in predict_result.prediction:
    y_.append(output.item())
plt.plot(X_test, y_, "g-")

plt.show()
../_images/tutorials_06_torch_runtime_26_0.png

Una línea roja, puntos azules y una línea verde en la gráfica muestran la función original, los datos de entrenamiento y una función construida a partir de los valores predichos, respectivamente.

Llama a score() para calcular un puntaje

Llama al método score en TorchRuntimeClient con el cargador de datos para calcular una puntuación, para el modelo entrenado. Debes pasar "regression\" o "classification" al argumento score_func para usar una de las funciones de puntaje predefinidas. Además, puedes pasar una función de puntuación personalizada definida como:

def score_func(model_output, target): -> score: float\n",

donde: - model_output son los valores predichos por el modelo, - target son valores fundamentales verdaderos.

Ten en cuenta que el resultado de la llamada a score también contiene valores predichos que se calcularon en el proceso de puntuación del modelo.

[16]:
score_result = torch_runtime_client.score(data_loader=test_loader, score_func="regression")
[17]:
print("id: ", score_result.job_id)
print("execution time: ", score_result.execution_time)
print("score: ", score_result.score)
id:  c6sqcfta2crk6k574ot0
execution time:  0.6559648513793945
score:  0.0004721035018463482

2. Clasificación

En segundo lugar, mostramos cómo realizar una tarea de clasificación simple usando Torch Runtime. En el ejemplo, efectuaremos una clasificación binaria en un conjunto de datos generado aleatoriamente.

[18]:
# Generate random dataset

# Select dataset dimension (num_inputs) and size (num_samples)
num_inputs = 2
num_samples = 20

# Generate random input coordinates (X) and binary labels (y)
X = 2 * algorithm_globals.random.random([num_samples, num_inputs]) - 1
y01 = 1 * (np.sum(X, axis=1) >= 0)  # in { 0,  1}, y01 will be used for CircuitQNN example
y = 2 * y01 - 1  # in {-1, +1}, y will be used for OplowQNN example

# Convert to torch Tensors
X_ = Tensor(X)
y01_ = Tensor(y01).reshape(len(y)).long()
y_ = Tensor(y).reshape(len(y), 1)

# Plot dataset
for x, y_target in zip(X, y):
    if y_target == 1:
        plt.plot(x[0], x[1], "bo")
    else:
        plt.plot(x[0], x[1], "go")
plt.plot([-1, 1], [1, -1], "--", color="black")
plt.show()
../_images/tutorials_06_torch_runtime_32_0.png
[19]:
# Create custom torch dataset class
class TorchDataset(Dataset):
    """Map-style dataset"""

    def __init__(self, X, y):
        self.X = Tensor(X)
        self.y = Tensor(y)

    def __len__(self):
        return len(self.X)

    def __getitem__(self, idx):
        import torch

        if torch.is_tensor(idx):
            idx = idx.tolist()

        X_i = self.X[idx]
        y_i = self.y[idx]

        # important: the dataset item must be returned as data,target
        return X_i, y_i
[20]:
y = y.reshape(20, 1)
train_set = TorchDataset(X, y)
train_loader = DataLoader(train_set, batch_size=1, shuffle=False)
[21]:
# Set up QNN
qnn = TwoLayerQNN(num_qubits=num_inputs)
print(qnn.operator)

# Set up PyTorch module
initial_weights = 0.1 * (2 * algorithm_globals.random.random(qnn.num_weights) - 1)
model = TorchConnector(qnn, initial_weights=initial_weights)
print("Initial weights: ", initial_weights)
ComposedOp([
  OperatorMeasurement(1.0 * ZZ),
  CircuitStateFn(
       ┌──────────────────────────┐»
  q_0: ┤0                         ├»
       │  ZZFeatureMap(x[0],x[1]) │»
  q_1: ┤1                         ├»
       └──────────────────────────┘»
  «     ┌──────────────────────────────────────────────────────────┐
  «q_0: ┤0                                                         ├
  «     │  RealAmplitudes(θ[0],θ[1],θ[2],θ[3],θ[4],θ[5],θ[6],θ[7]) │
  «q_1: ┤1                                                         ├
  «     └──────────────────────────────────────────────────────────┘
  )
])
Initial weights:  [ 0.06653564  0.04005302 -0.03752667  0.06645196  0.06095287 -0.02250432
 -0.04233438  0.0364991 ]
[22]:
# Define optimizer and loss
optimizer = Adam(model.parameters(), lr=0.1)
loss_func = MSELoss(reduction="sum")
[23]:
torch_runtime_client = TorchRuntimeClient(
    provider=provider,
    model=model,
    optimizer=optimizer,
    loss_func=loss_func,
    epochs=5,
    backend=backend,
)

Llama a fit() para entrenar el modelo

[24]:
fit_result = torch_runtime_client.fit(train_loader=train_loader, seed=42)
[25]:
print("id: ", fit_result.job_id)
print("execution time: ", fit_result.execution_time)
id:  c6sqcj5a2crk6k574oug
execution time:  23.410197257995605

También puedes consultar las propiedades train_history y val_history.

Llama a predict() para realizar la predicción

[26]:
# In this example, we use the same data loader for the prediction as well
predict_result = torch_runtime_client.predict(data_loader=train_loader)
[27]:
print("id: ", predict_result.job_id)
print("execution time: ", predict_result.execution_time)
id:  c6sqcsda2crk6k574p30
execution time:  0.5784239768981934
[28]:
# Plot results
# red == wrongly classified

y_predict = []
for out in predict_result.prediction:
    y_predict += [np.sign(out.item())]
y_predict = np.array(y_predict)
y_check = [i[0] for i in y]

for x, y_target, y_p in zip(X, y, y_predict):
    if y_target == 1:
        plt.plot(x[0], x[1], "bo")
    else:
        plt.plot(x[0], x[1], "go")
    if y_target != y_p:
        plt.scatter(x[0], x[1], s=200, facecolors="none", edgecolors="r", linewidths=2)
plt.plot([-1, 1], [1, -1], "--", color="black")
plt.show()
../_images/tutorials_06_torch_runtime_45_0.png

Los círculos rojos indican puntos de datos clasificados incorrectamente.

Llama a score() para calcular un puntaje

En el ejemplo, usamos la siguiente función de puntuación personalizada para calcular una puntuación. La función de puntuación devuelve 1, si el modelo entrenado clasificó correctamente la entrada. De lo contrario, devuelve 0. Al final, se calcula un promedio general en score().

[29]:
def score_func(out, target):
    from numpy import sign

    score = 0
    if sign(out.item()) == target.item():
        score = 1
    return score
[30]:
score_result = torch_runtime_client.score(data_loader=train_loader, score_func=score_func)
[31]:
print("id: ", score_result.job_id)
print("execution time: ", score_result.execution_time)
print("score: ", score_result.score)
id:  c6sqcvta2crk6k574p4g
execution time:  0.6043887138366699
score:  0.75

3. Cómo utilizar hooks en el entrenamiento

Qiskit Machine Learning ofrece una clase de enlace (hook) base, HookBase. Es una clase base para un hook que es un conjunto de funciones de devolución de llamada (callback) utilizadas en el proceso de entrenamiento. Los usuarios pueden implementar sus propias clases hook desde esta clase base para manejar procesos de devolución de llamada complicados. Esta estructura proporciona una gran flexibilidad en los procesos de devolución de llamada. Cada hook puede implementar 6 métodos, y cada método se llama antes/después de los procesos correspondientes durante el entrenamiento. La forma en que se llaman se demuestra en el siguiente fragmento:

hook.before_train()
for epoch in range(epochs):
    hook.before_epoch()
    for batch in train_loader:
        hook.before_step()
        train_step()
        hook.after_step()
        global_step += 1
    hook.after_epoch()
    epoch += 1
hook.after_train()

En los métodos hook, los usuarios pueden acceder al TorchTrainer a través de self.trainer para acceder a más propiedades de contexto (por ejemplo, modelo, iteración actual o configuración). El siguiente fragmento describe las propiedades disponibles que podrían ser útiles para un hook.

  • TorchTrainer tiene:

    • model: Un modelo para entrenar.

    • optimizer: Un optimizador utilizado para el entrenamiento.

    • loss_func: Una función de pérdida para el entrenamiento.

    • train_loader: Un objeto cargador de datos de PyTorch que contiene un conjunto de datos de entrenamiento.

    • val_loader: Un objeto cargador de datos de PyTorch que contiene un conjunto de datos de validación.

    • max_epoch: El número máximo de épocas de entrenamiento.

    • log_period: Un período de logging para un historial de entrenamiento y un historial de validación. De forma predeterminada, habrá registros en cada época. (log_period=1).

    • start_epoch: Una época inicial para el entrenamiento de arranque en caliente. Por defecto, 0.

    • epoch: El número actual de épocas.

    • global_step: El número actual de pasos.

    • train_logger: Un logger para un historial de entrenamiento. Utiliza train_logger.metrics para acceder a una lista de logs. Un log para cada época se almacena como un diccionario similar a TorchRuntimeResult.train_history

    • val_logger: Un logger para un historial de validación. Los logs se almacenan de la misma forma que en train_logger.

Los usuarios pueden pasar una única instancia de hook o una lista de instancias de hook al argumento hooks.

El siguiente ejemplo es un hook para detenerse antes de tiempo. Si la pérdida actual es menor que el umbral después de una época, el entrenamiento terminará.

[32]:
from qiskit_machine_learning.runtime import HookBase


class StopHook(HookBase):
    """For early stopping"""

    def __init__(self, loss_threshold):
        self._loss_threshold = loss_threshold

    def after_epoch(self):
        # This function is called after each epoch
        current_loss = self.trainer.train_logger.metrics[-1]["loss"]
        print("current loss: ", current_loss)
        # If current loss is smaller than the threshold,
        # set the current number of the epoch to the maximum number of the epochs to stop the training
        if current_loss < self._loss_threshold:
            self.trainer.epoch = self.trainer.max_epochs


stop_hook = StopHook(loss_threshold=0.05)
[33]:
torch_runtime_client = TorchRuntimeClient(
    provider=provider,
    model=model1,
    optimizer=optimizer1,
    loss_func=loss_func1,
    epochs=5,
    backend=backend,
)
[34]:
fit_result = torch_runtime_client.fit(train_loader=train_loader1, hooks=stop_hook)
[35]:
print("id: ", fit_result.job_id)
print("execution time: ", fit_result.execution_time)
print("train history: ")
for epoch_result in fit_result.train_history:
    print("  epoch", epoch_result["epoch"], ": loss", epoch_result["loss"])
id:  c6sqd35a2crk6k574p6g
execution time:  2.7064104080200195
train history:
  epoch 0 : loss 0.027795438258181093

Como podemos ver, el entrenamiento se interrumpió después de la primera época a pesar de que establecimos el número de épocas en 5 en la configuración de TorchRuntimeClient.

[36]:
import qiskit.tools.jupyter

%qiskit_version_table
%qiskit_copyright

Version Information

Qiskit SoftwareVersion
qiskit-terra0.19.1
qiskit-aer0.9.1
qiskit-ignis0.7.0
qiskit-ibmq-provider0.18.1
qiskit-aqua0.9.5
qiskit0.33.0
qiskit-nature0.3.0
qiskit-machine-learning0.3.0
System information
Python version3.8.6
Python compilerClang 12.0.0 (clang-1200.0.32.29)
Python builddefault, Mar 10 2021 14:41:09
OSDarwin
CPUs8
Memory (Gb)32.0
Wed Dec 15 17:26:01 2021 JST

This code is a part of Qiskit

© Copyright IBM 2017, 2021.

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
of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.

Any modifications or derivative works of this code must retain this
copyright notice, and modified files need to carry a notice indicating
that they have been altered from the originals.

[ ]: