Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quantum-kittens
GitHub Repository: quantum-kittens/platypus
Path: blob/main/translations/ja/ch-machine-learning/machine-learning-qiskit-pytorch.ipynb
3860 views
Kernel: Python 3

PyTorchとQiskitを用いた量子古典ハイブリッド・ニューラル・ネットワーク

機械学習 (Machine learning、ML) は、データから一般化された情報を数学的に抽出しようとする学際的分野を自分自身で確立しました。量子コンピューターを利用することで、機械学習を補完するため量子力学の原理を利用したり、あるいはその逆を行う興味深い研究分野が増加しています。 難しい計算を量子コンピューターに任せることで古典MLアルゴリズムを拡張するにせよ、古典MLアルゴリズムを用いて量子アルゴリズムを最適化するにせよ、どちらも量子機械学習(quantum machine learning、QML)の多種多様な傘下にあるのです。

この章では、古典的なニューラル・ネットワークを部分的に量子化して、ハイブリッド量子古典ニューラル・ネットワークを作成する方法を探ります。Qiskit と最先端のオープンソース・ソフトウェアである PyTorch を統合して、簡単な例をコード化します。この例の目的は、Qiskitと既存のMLツールとの統合が容易であることを示し、MLの専門家が量子コンピューティングで何が可能かを探求することを奨励することにあります

目次

  1. どのように動作するのか? 1.1 導入

  2. 量子はどのように画像を入力するのか?

  3. いざ、コーディング! 3.1 インポート 3.2 Qiskitを用いた「量子クラス」の作成 3.3 PyTorchを用いた「量子古典クラス」の作成 3.4 データのロードと前処理 3.5 ハイブリッド・ニューラル・ネットワークの作成 3.6 ネットワークの学習 3.7 ネットワークのテスト

  4. 次は何?

1. どのように動作するのか?

図.1 は、本章で構築するフレームワークを描いたものです。最終的には、手書きの数字を分類しようとする、量子古典ハイブリッド・ニューラル・ネットワークを構築します。 この図に示されているエッジはすべて下方を向いていますが、方向性は視覚的に示されていないことに注意してください。

1.1 導入

ここで語られる古典ニューラル・ネットワークの背景には、関連する知識や共通の用語の確立も含まれますが、俄然とてもハイレベルなものです。もし古典ニューラル・ネットワークに深く踏み込みたい場合は、YouTuberによるよくできたビデオシリーズ 3Blue1Brown をご覧ください。または、もし既に古典ネットワークに詳しい場合は 次のセクションにスキップ できます。

ニューロンと重み

ニューラル・ネットワークは、最終的には、ニューロンと呼ばれる小さなビルディング・ブロックを構成することで作られる精巧な機能です。ニューロン は、通常シンプルで計算しやすい非線形の関数で、1つ以上の入力を1つの実数にマップします。ニューロンの一つの出力は、通常他のニューロンへの入力としてコピーされフィードされます。図示する場合、ニューロンをグラフ内のノードとして表し、あるニューロンの出力が他のニューロンへの入力としてどのように使用されるかを示すため、ノード間の有向エッジを描画します。また、グラフ内の各エッジは、しばしば重みと呼ばれるスカラー値に関連付けられることに注意する必要があります。ここでの考え方は、ニューロンへの各入力はそれぞれ異なるスカラー値が乗じられた後、集められて1つの値に処理されるということです。ニューラル・ネットワークの学習の主たる目的は、特定の方法でネットワークが振る舞うよう重みを決めることです。

フィードフォワード・ニューラル・ネットワーク

また、私たちが関心のあるタイプのニューラル・ネットワークは、フィードフォワード・ニューラル・ネットワーク (Feed-Forward Neural Network, FFNN) と呼ばれることにも注目する必要があります。これは、ニューラル・ネットワークにデータが流れるとき、データは一度訪れているニューロンに戻ることがないことを意味します。言い換えるならば、このニューラル・ネットワークを記述するグラフは、有向非巡回グラフ (Directed Acyclic Graph, DAG) だということです。さらに、ニューラル・ネットワークの同じ層にあるニューロンは、それらの間にエッジがないことも条件となります。

層のIO構造

ニューラル・ネットワークへの入力は、古典的な(実数値の)ベクトルです。入力ベクトルの各成分は、ネットワークのグラフ構造に従って、異なる重みを乗じられ、ニューロンの層にフィードされます。 層内の各ニューロンが評価された後、結果は新しいベクトルに集められ、 i番目の成分はi番目のニューロンの出力を記録します。この新しいベクトルは、新しい層の入力として処理され、以下同様に処理されます。ネットワークの最初と最後以外の層は、隠れ層 という標準的用語を使って表現しましょう。

2. 量子はどのように画像を入力するのか?

量子古典ニューラル・ネットワークを構築するには、パラメータ化された量子回路を使用してニューラル・ネットワークの隠れ層を実装する方法があります。 ここで「パラメータ化された量子回路」とは、各ゲートの回転角が古典入力ベクトルの成分によって指定される量子回路を意味します。ニューラル・ネットワークの前の層からの出力が集められ、パラメーター化された回路の入力として使用されます。これらの量子回路の測定統計は集められ、続くの層の入力として使用することができます。以下に簡単な例を示します:

ここで、σ\sigma非線形関数hih_i は各隠れ層におけるニューロン ii の値です。R(hi)R(h_i)hih_i に等しい角の回転ゲートを表し、yyはこのハイブリッド・ネットワークから生成される最終予測値になります。

誤差逆伝播法はどうか?

古典MLに詳しいならば、 量子回路が含まれる場合、どのように勾配を計算するのか という疑問がすぐ浮かぶでしょう。これには、最急降下法 といった、強力な最適化手法を列挙する必要があるでしょう。少し技術的になりますが、手短に言えば、量子回路をブラック・ボックスとみなすとき、パラメーターに対するこのブラック・ボックスの勾配は次のように計算できます:

ここで、θ\theta は量子回路のパラメーターを、ss は巨視的シフトを表します。すると、勾配は回路を θ+s\theta+sθs\theta - s で評価した時の差になります。このように、より大きな誤差伝播ルーチンの一部として、量子回路を体系的に区別することができます。量子回路パラメータの勾配を計算するこの閉形式ルールは、パラメーター・シフト・ルール として知られています。

3. いざ、コーディング !

3.1 インポート

最初に、QiskitやPyTorchを含め、必要で便利なパッケージをインポートします。

import numpy as np import matplotlib.pyplot as plt import torch from torch.autograd import Function from torchvision import datasets, transforms import torch.optim as optim import torch.nn as nn import torch.nn.functional as F import qiskit from qiskit.visualization import *

3.2 Qiskitを用いた「量子クラス」の作成

Qiskitの量子関数をクラスに簡単に導入することができます。初めに、学習する量子パラメータの数と、量子回路で使用したいショットの数を指定します。 この例では、簡単にするため、1つの学習量子パラメーター θ\thetaを持つ1量子ビットの回路を使用します。単純化するため回路をハードコードし、角度 θ\thetaRYRY- 回転を使用して、回路の出力を学習させます。回路は次のようになります:

zz-基底で出力を測定するため、期待値 σz\sigma_z を計算します。 σz=izip(zi)\sigma_\mathbf{z} = \sum_i z_i p(z_i) これがハイブリッド・ニューラル・ネットワークとどのように結びついているかは、後ほどご紹介します。

class QuantumCircuit: """ This class provides a simple interface for interaction with the quantum circuit """ def __init__(self, n_qubits, backend, shots): # --- Circuit definition --- self._circuit = qiskit.QuantumCircuit(n_qubits) all_qubits = [i for i in range(n_qubits)] self.theta = qiskit.circuit.Parameter('theta') self._circuit.h(all_qubits) self._circuit.barrier() self._circuit.ry(self.theta, all_qubits) self._circuit.measure_all() # --------------------------- self.backend = backend self.shots = shots def run(self, thetas): job = qiskit.execute(self._circuit, self.backend, shots = self.shots, parameter_binds = [{self.theta: theta} for theta in thetas]) result = job.result().get_counts(self._circuit) counts = np.array(list(result.values())) states = np.array(list(result.keys())).astype(float) # Compute probabilities for each state probabilities = counts / self.shots # Get state expectation expectation = np.sum(states * probabilities) return np.array([expectation])

この実装をテストしてみましょう。

simulator = qiskit.Aer.get_backend('qasm_simulator') circuit = QuantumCircuit(1, simulator, 100) print('Expected value for rotation pi {}'.format(circuit.run([np.pi])[0])) circuit._circuit.draw()
Expected value for rotation pi 0.46
Image in a Jupyter notebook

3.3 PyTorchを用いた「量子古典クラス」の作成

量子回路を定義できましたので、 PyTorchを使って誤差逆伝播法に必要な関数を作成できます。順伝播と逆伝播 には、Qiskitクラスの要素が含まれています。 逆伝播は、上記で導入した有限差分公式を使用して、分析勾配を直接計算します。

class HybridFunction(Function): """ Hybrid quantum - classical function definition """ @staticmethod def forward(ctx, input, quantum_circuit, shift): """ Forward pass computation """ ctx.shift = shift ctx.quantum_circuit = quantum_circuit expectation_z = ctx.quantum_circuit.run(input[0].tolist()) result = torch.tensor([expectation_z]) ctx.save_for_backward(input, result) return result @staticmethod def backward(ctx, grad_output): """ Backward pass computation """ input, expectation_z = ctx.saved_tensors input_list = np.array(input.tolist()) shift_right = input_list + np.ones(input_list.shape) * ctx.shift shift_left = input_list - np.ones(input_list.shape) * ctx.shift gradients = [] for i in range(len(input_list)): expectation_right = ctx.quantum_circuit.run(shift_right[i]) expectation_left = ctx.quantum_circuit.run(shift_left[i]) gradient = torch.tensor([expectation_right]) - torch.tensor([expectation_left]) gradients.append(gradient) gradients = np.array([gradients]).T return torch.tensor([gradients]).float() * grad_output.float(), None, None class Hybrid(nn.Module): """ Hybrid quantum - classical layer definition """ def __init__(self, backend, shots, shift): super(Hybrid, self).__init__() self.quantum_circuit = QuantumCircuit(1, backend, shots) self.shift = shift def forward(self, input): return HybridFunction.apply(input, self.quantum_circuit, self.shift)

3.4 データのロードと前処理

以上をすべてをまとめる:

MNIST データ・セット の2種類の数字(0または1)のイメージを分類するための、簡単なハイブリッド・ニューラル・ネットワークを作成します。まずMNISTをロードし、0と1を含む画像をフィルタリングします。これらは、分類するニューラル・ネットワークの入力として扱われます。

学習データ

# Concentrating on the first 100 samples n_samples = 100 X_train = datasets.MNIST(root='./data', train=True, download=True, transform=transforms.Compose([transforms.ToTensor()])) # Leaving only labels 0 and 1 idx = np.append(np.where(X_train.targets == 0)[0][:n_samples], np.where(X_train.targets == 1)[0][:n_samples]) X_train.data = X_train.data[idx] X_train.targets = X_train.targets[idx] train_loader = torch.utils.data.DataLoader(X_train, batch_size=1, shuffle=True)
n_samples_show = 6 data_iter = iter(train_loader) fig, axes = plt.subplots(nrows=1, ncols=n_samples_show, figsize=(10, 3)) while n_samples_show > 0: images, targets = data_iter.__next__() axes[n_samples_show - 1].imshow(images[0].numpy().squeeze(), cmap='gray') axes[n_samples_show - 1].set_xticks([]) axes[n_samples_show - 1].set_yticks([]) axes[n_samples_show - 1].set_title("Labeled: {}".format(targets.item())) n_samples_show -= 1
Image in a Jupyter notebook

テスト・データ

n_samples = 50 X_test = datasets.MNIST(root='./data', train=False, download=True, transform=transforms.Compose([transforms.ToTensor()])) idx = np.append(np.where(X_test.targets == 0)[0][:n_samples], np.where(X_test.targets == 1)[0][:n_samples]) X_test.data = X_test.data[idx] X_test.targets = X_test.targets[idx] test_loader = torch.utils.data.DataLoader(X_test, batch_size=1, shuffle=True)

ここまでで、データをロードし、1つの学習パラメータを含む量子回路を作成するクラスをコーディングしました。この量子パラメータは、他の古典パラメータとともに古典ニューラル・ネットワークに入力され、ハイブリッド・ニューラル・ネットワークを形成します。また、逆伝播・順伝播関数を作ることにより、誤差逆伝播法を実現しニューラル・ネットワークを最適化することができました。最後に、PyTorchが提供する最適化技術を使って、パラメーターの学習を開始できるよう、ニューラル・ネットワーク・アーキテクチャーを指定する必要があります。

3.5 ハイブリッド・ニューラル・ネットワークの作成

NEAT PyTorchパイプラインを使って、ニューラルネットワーク・アーキテクチャを構築できます。量子層(すなわち量子回路)を挿入する場合、ネットワークは次元に対して互換性が必要です。この例の量子回路はパラメータを1つ含んでいるので、ネットワークがニューロンをサイズ1まで縮小するようにしなければなりません。最終的に、2層の全結合層からなる典型的な畳み込みニューラル・ネットワークを作成します。全結合層の最後のニューロンの値は、θ\thetaパラメーターとして量子回路に与えられます。回路測定は、σz\sigma_zの測定によってもたらされる0または1の最終予測値として与えられます。

class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.conv1 = nn.Conv2d(1, 6, kernel_size=5) self.conv2 = nn.Conv2d(6, 16, kernel_size=5) self.dropout = nn.Dropout2d() self.fc1 = nn.Linear(256, 64) self.fc2 = nn.Linear(64, 1) self.hybrid = Hybrid(qiskit.Aer.get_backend('qasm_simulator'), 100, np.pi / 2) def forward(self, x): x = F.relu(self.conv1(x)) x = F.max_pool2d(x, 2) x = F.relu(self.conv2(x)) x = F.max_pool2d(x, 2) x = self.dropout(x) x = x.view(1, -1) x = F.relu(self.fc1(x)) x = self.fc2(x) x = self.hybrid(x) return torch.cat((x, 1 - x), -1)

3.6 ネットワークの学習

ハイブリッド・ネットワークを学習させる全ての材料が揃いました!複数のエポックに渡り学習させるため、PyTorchの最適化法学習率損失関数/目的関数 などを指定することができます。この例では、Adam最適化ツール 、学習率0.001、負の対数尤度損失関数 を使用します。

model = Net() optimizer = optim.Adam(model.parameters(), lr=0.001) loss_func = nn.NLLLoss() epochs = 20 loss_list = [] model.train() for epoch in range(epochs): total_loss = [] for batch_idx, (data, target) in enumerate(train_loader): optimizer.zero_grad() # Forward pass output = model(data) # Calculating loss loss = loss_func(output, target) # Backward pass loss.backward() # Optimize the weights optimizer.step() total_loss.append(loss.item()) loss_list.append(sum(total_loss)/len(total_loss)) print('Training [{:.0f}%]\tLoss: {:.4f}'.format( 100. * (epoch + 1) / epochs, loss_list[-1]))
Training [5%] Loss: -0.7380 Training [10%] Loss: -0.9153 Training [15%] Loss: -0.9282 Training [20%] Loss: -0.9393 Training [25%] Loss: -0.9463 Training [30%] Loss: -0.9515 Training [35%] Loss: -0.9585 Training [40%] Loss: -0.9637 Training [45%] Loss: -0.9713 Training [50%] Loss: -0.9657 Training [55%] Loss: -0.9710 Training [60%] Loss: -0.9758 Training [65%] Loss: -0.9765 Training [70%] Loss: -0.9804 Training [75%] Loss: -0.9873 Training [80%] Loss: -0.9887 Training [85%] Loss: -0.9894 Training [90%] Loss: -0.9873 Training [95%] Loss: -0.9904 Training [100%] Loss: -0.9870

学習グラフをプロットします。

plt.plot(loss_list) plt.title('Hybrid NN Training Convergence') plt.xlabel('Training Iterations') plt.ylabel('Neg Log Likelihood Loss')
Text(0, 0.5, 'Neg Log Likelihood Loss')
Image in a Jupyter notebook

3.7 ネットワークのテスト

model.eval() with torch.no_grad(): correct = 0 for batch_idx, (data, target) in enumerate(test_loader): output = model(data) pred = output.argmax(dim=1, keepdim=True) correct += pred.eq(target.view_as(pred)).sum().item() loss = loss_func(output, target) total_loss.append(loss.item()) print('Performance on test data:\n\tLoss: {:.4f}\n\tAccuracy: {:.1f}%'.format( sum(total_loss) / len(total_loss), correct / len(test_loader) * 100) )
Performance on test data: Loss: -0.9847 Accuracy: 100.0%
n_samples_show = 6 count = 0 fig, axes = plt.subplots(nrows=1, ncols=n_samples_show, figsize=(10, 3)) model.eval() with torch.no_grad(): for batch_idx, (data, target) in enumerate(test_loader): if count == n_samples_show: break output = model(data) pred = output.argmax(dim=1, keepdim=True) axes[count].imshow(data[0].numpy().squeeze(), cmap='gray') axes[count].set_xticks([]) axes[count].set_yticks([]) axes[count].set_title('Predicted {}'.format(pred.item())) count += 1
Image in a Jupyter notebook

4. 次は何?

ハイブリッド・ニューラルネットワークを作成することは確かに可能ですが、実際に何らかの恩恵があったでしょうか?

事実、このネットワークの古典的な層は、量子層がなくとも完全に良く(実際には、より良く)学習されます。さらに、ここで学習された量子層は、エンタングルメントを全く生成しないことに気が付いたかもしれません。つまり、この特定のアーキテクチャーを拡張する際は、古典的にシミュレーション可能であるということです。これは、ハイブリッド・ニューラル・ネットワークを使用して量子優位性を実現したい場合は、このコードを拡張し、より洗練された量子層を組み込むことから始める必要があることを意味します。

この演習のポイントは、興味のある要素が実際にあるかを調べるため、MLと量子コンピューティングの技術の統合を考えることでした。PyTorchとQiskitのおかげで、これは少し楽になっています。

import qiskit qiskit.__qiskit_version__
{'qiskit-terra': '0.14.2', 'qiskit-aer': '0.5.2', 'qiskit-ignis': '0.3.3', 'qiskit-ibmq-provider': '0.7.2', 'qiskit-aqua': '0.7.3', 'qiskit': '0.19.6'}