PennyLane 使用方法 : NumPy インターフェイス (翻訳)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 10/20/2019
* 本ページは、PennyLane : Using PennyLane の次のページを翻訳した上で適宜、補足説明したものです:
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
$$
\def\bra#1{\mathinner{\left\langle{#1}\right|}}
\def\ket#1{\mathinner{\left|{#1}\right\rangle}}
\def\braket#1#2{\mathinner{\left\langle{#1}\middle|#2\right\rangle}}
$$
使用方法 : NumPy インターフェイス
PennyLane は変分回路から成る量子ノードを他のプログラミングと機械学習フレームワークと統合しています。そのようなフレームワークはインターフェイスと呼ばれます。変分回路をどのようにプログラムするかの前のセクションで暗黙的に使用されたデフォルトのインターフェイスは NumPy です。
Note
このインターフェイスは PennyLane の QNode によりサポートされるデフォルト・インターフェイスです。
NumPy インターフェイスを使用する
PennyLane で NumPy インターフェイスを使用することは容易です、そしてデフォルトのアプローチです — 単に標準的な NumPy を使用しているように感じるでしょう、そのようにデザインされています、自動微分の追加された利益とともに。
貴方がしなければならないことの総ては PennyLane ライブラリと一緒に PennyLane により提供される NumPy のラップされたバージョンをインポートすることを確実にすることです :
import pennylane as qml from pennylane import numpy as np
これは autograd を通して提供されます、そして (np.sin, np.cos, np.exp, np.linalg, np.fft のような) 馴染みのある NumPy 関数とモジュール、加えて if ステートメント、及び for と while ループのような標準的な Python 構築概念を使用して古典的な計算の自動微分とバックプロパゲーションを有効にします。
QNode デコレータを通して
QNode デコレータは Pennylane で QNode を作成するための推奨される方法です。デフォルトで、総ての QNode は NumPy インターフェイスのために構築されますが、これは interface=’numpy’ キーワード引数を渡すことにより明示的に指定することもできます :
dev = qml.device('default.qubit', wires=2) @qml.qnode(dev, interface='numpy') def circuit1(phi, theta): qml.RX(phi[0], wires=0) qml.RY(phi[1], wires=1) qml.CNOT(wires=[0, 1]) qml.PhaseShift(theta, wires=0) return qml.expval(qml.PauliZ(0)), qml.expval(qml.Hadamard(1))
QNode circuit1() は NumPy-interfacing QNode で、int, float, リストとタプルのような標準的な Python データ型更に NumPy 配列を受け取り、そして NumPy 配列を返します。
それは今では任意の他の Python/NumPy 関数のように使用できます :
>>> phi = np.array([0.5, 0.1]) >>> theta = 0.2 >>> circuit1(phi, theta) array([ 0.87758256, 0.68803733])
QNode クラスを通して
イントロダクション では QNode オブジェクトをどのように直接インスタンス化するかが示されました、例えば、同じ量子関数を複数のデバイスに渡り再利用する、あるいは異なる古典的インターフェイスを使用することさえ望む場合です :
dev1 = qml.device('default.qubit', wires=2) dev2 = qml.device('forest.wavefunction', wires=2) def circuit2(phi, theta): qml.RX(phi[0], wires=0) qml.RY(phi[1], wires=1) qml.CNOT(wires=[0, 1]) qml.PhaseShift(theta, wires=0) return qml.expval(qml.PauliZ(0)), qml.expval(qml.Hadamard(1)) qnode1 = qml.QNode(circuit2, dev1) qnode2 = qml.QNode(circuit2, dev2)
デフォルトでは、この方法で作成された総ての QNode は NumPy interfacing QNode です。
量子勾配
NumPy-QNode の勾配を計算するために、提供される grad() 関数を単純に使用できます。
例えば、次の QNode を考えます :
dev = qml.device('default.qubit', wires=2) @qml.qnode(dev) def circuit3(phi, theta): qml.RX(phi[0], wires=0) qml.RY(phi[1], wires=1) qml.CNOT(wires=[0, 1]) qml.PhaseShift(theta, wires=0) return qml.expval(qml.PauliZ(0))
今は QNode パラメータ phi と theta の両者に関する、QNode 勾配関数を作成するために grad() を使用できます :
phi = np.array([0.5, 0.1]) theta = 0.2 dcircuit = qml.grad(circuit3, argnum=[0, 1])
特定のパラメータ値でこの勾配関数を評価します :
>>> dcircuit(phi, theta) (array([ -4.79425539e-01, 1.11022302e-16]), array(0.0))
最適化
NumPy インターフェイスを使用してハイブリッド古典的-量子モデルを最適化するために、提供される PennyLane optimizer を使用します。
例えば、NumPy-interfacing QNode (下) を重み x が 0.5 の最終的な期待値に繋がるように最適化できます :
dev = qml.device('default.qubit', wires=2) @qml.qnode(dev) def circuit4(x): qml.RX(x[0], wires=0) qml.RZ(x[1], wires=1) qml.CNOT(wires=[0, 1]) qml.RX(x[2], wires=0) return qml.expval(qml.PauliZ(0)) def cost(x): return np.abs(circuit4(x) - 0.5)**2 opt = qml.GradientDescentOptimizer(stepsize=0.4) steps = 100 params = np.array([0.011, 0.012, 0.05]) for i in range(steps): # update the circuit parameters params = opt.step(cost, params)
最終的な重みと回路値は :
>>> params array([ 0.19846757, 0.012 , 1.03559806]) >>> circuit4(params) 0.5
NumPy optimizer のより詳細については、チュートリアル、そして pennylane.optimize ドキュメントを確認してください。
ベクトル値 QNode と Jacobian
QNode が複数の期待値を返す場合に自動微分はどのように動作するのでしょう?
dev = qml.device('default.qubit', wires=2) @qml.qnode(dev) def circuit5(x): qml.RX(x, wires=0) qml.RX(x, wires=1) return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1))
もし grad() 関数を使用して circuit5 の勾配を素朴に計算しようとした場合 :
g1 = qml.grad(circuit5, argnum=0) g1(np.pi/2)
エラー・メッセージを得るでしょう。これは勾配はスカラー関数、i.e., シングル値を返す関数のためだけに定義されているからです。QNode が複数の期待値を返す場合、使用する正しい微分演算子は Jacobian 行列 です。これは PennyLane で jacobian() としてアクセスできます :
>>> j1 = qml.jacobian(circuit5, argnum=0) >>> j1(np.pi/2) array([-1. -1.])
jacobian() の出力は 2-次元ベクトルで、最初/ 2 番目の要素が入力パラメータに関する最初/ 2 番目の期待値の偏微分です。Jacobian 関数は勾配関数と同じシグネチャーを持ち、ユーザにどの引数が微分されるべきかを指定することを要求します。
複数の入力パラメータと複数の期待値を持つ関数のための Jacobian 行列を計算することを望む場合、これを行なうための推奨される方法は量子回路関数の内側でパラメータを単一のリスト/配列に結合してこの中にインデックスすることです。次の回路を考えてます :
@qml.qnode(dev) def circuit6(params): qml.RX(params[0], wires=0) qml.RZ(params[1], wires=0) qml.RX(params[2], wires=1) qml.CNOT(wires=[0, 1]) return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1))
それは 2 つの行と 3 つのカラムを持つ full Jacobian を持ちます :
>>> j2 = qml.jacobian(circuit6, argnum=0) >>> params = np.array([np.pi / 3, 0.25, np.pi / 2]) >>> j2(params) array([[-8.66025404e-01, -5.55111512e-17, 0.00000000e+00], [-4.71844785e-16, -1.38777878e-17, -5.00000000e-01]])
Warning
現在、pennylane.jacobian() は argnum がシングル整数である場合だけをサポートします。複数の引数を持つ量子関数のためには、full jacobian 行列を得るために引数を配列に結合する上のメソッドを使用してください。
進んだ Autorad 使用方法
PennyLane NumPy インターフェイスは NumPy コードの自動微分を有効にするために Python ライブラリ autograd を活用し、そして QNode にカプセル化された量子回路関数の勾配を提供するためにそれを拡張しています。NumPy コードを微分可能にするために、Autograd は NumPy のラップされたバージョンを提供しています (PennyLane では pennylane.numpy として公開されています)。
他のセクションで述べられているように、このインターフェイスを使用するとき、任意のハイブリッド計算は PennyLane で提供される NumPy のラップされたバージョンを使用してコーディングされるべきです。もし NumPy の vanilla バージョンを偶発的にインポートする場合、貴方のコードは自動的に微分可能にはなりません。
autograd が NumPy をラップする方法ゆえに、PennyLane NumPy インターフェイスはユーザに古典的計算を宣言するために新しいミニ言語を学習したり、あるいは基本 Python 制御フロー・ステートメント (if ステートメント、ループ等) を複製するような扱いにくい言語依存関数を起動することを要求しません。ユーザは Python と NumPy で一般的な多くの標準数値プログラミング実践を使用し続けることができます。
That being said, autograd’s coverage of NumPy is not complete. It is best to consult the autograd docs for a more complete overview of supported and unsupported features. We highlight a few of the major ‘gotchas’ here.
Do not use:
- Assignment to arrays, such as A[0, 0] = x.
- Implicit casting of lists to arrays, for example A = np.sum([x, y]). Make sure to explicitly cast to a NumPy array first, i.e., A = np.sum(np.array([x, y])) instead.
- A.dot(B) notation. Use np.dot(A, B) or A @ B instead.
- In-place operations such as a += b. Use a = a + b instead.
- Some isinstance checks, like isinstance(x, np.ndarray) or isinstance(x, tuple), without first doing from autograd.builtins import isinstance, tuple.
以上