ブラックボックス最適化による撹拌機の設計パラメータの自動調整¶
撹拌機は、化学プロセスや食品製造など多くの分野で活用される重要な装置です。その設計や運転条件の設定は、混合効率や製品品質に大きな影響を与える一方、経験や試行錯誤に依存することが少なくありません。
本チュートリアルでは、撹拌機の設計に関わる 5 つのパラメータを対象に、イジングマシン活用によるブラックボックス最適化 (BBO) を用いて、最適な条件を見つけるアプローチを紹介します。これらのパラメータは、具体的に何を指すかは明示されていませんが、撹拌機の性能を左右する「設計上の選択すべき項目」として捉えてください。実際の設計や制御と同様に、「最適な条件はあるが、それがどういう効果があるかは必ずしも明確ではない」状態からスタートします。
本チュートリアルでは、パラメータの組み合わせに応じた撹拌性能(ここでは撹拌後の濃度のばらつき度合い)を、シミュレーターを通じて評価します。物理モデルの詳細やシミュレーターの中身を理解する必要はありません。目的関数の形状も構造も不明なまま、いかに効率よく最良の設計パラメータを見つけ出せるかが、ブラックボックス最適化の主眼です。
イジングマシン活用によるブラックボックス最適化の力を、工学的な課題設定の中で体験してみましょう。
本サンプルコードで用いる、機械学習と量子アニーリング・イジングマシンによるブラックボックス最適化の基本知識については、『量子アニーリング・イジングマシンによるブラックボックス最適化』をご覧ください。イジングマシン活用によるその他のブラックボックス最適化事例はこちらを参照してください。
本サンプルプログラムは以下の構成となっています。
- 1. 目的関数の説明
- 1.1. 撹拌シミュレーターの説明
- 1.2. ブラックボックス目的関数の実装
- 2. ブラックボックス最適化のプログラム実装(整数変数)
- 2.1. 決定変数クラスの定義
- 2.1.1. ドメインウォール・エンコーディングとは
- 2.1.2. 整数決定変数クラス
IntegerVariable
の説明
- 2.2. FM モデル実装
- 2.3. 機械学習関数の実装
- 2.4. ソルバークライアントの設定
- 2.5. イジングマシンによる最適化の実装
- 2.6. 初期学習データを生成する関数の実装
- 2.1. 決定変数クラスの定義
- 3. 最適化の実行
- 4. 最適化結果と履歴の評価
※本オンラインデモ & チュートリアル環境では、連続実行時間が20分程度に制限されている為、サンプルプログラム内の試行回数を極端に削減しています。条件を変更して最適化を試される場合など、実行時間が20分を超えることが想定される場合、本サンプルプログラムをご自身の環境にコピーした上で実行してください。その場合、本ノートブックに加え、シミュレーターに関する以下の .py ファイルを適宜ダウンロード後、次のようなディレクトリ構成で保存し、サンプルコードを実行してください。
├ fmqa_5_mixing.ipynb(本サンプルプログラム)
└ utils/
├ __init__.py
(空ファイル)
└ mixing.py
1. 目的関数の説明¶
1.1. 撹拌シミュレーターの説明¶
本チュートリアルでは、撹拌シミュレーターとして MixingSimulator
を用います。以下は、撹拌機の概念図です。
シミュレーターで考慮する撹拌機には、合計で 5 つの設計パラメータ x0, x1, x2, x3, x4
が設定されています。これらのパラメータをシミュレーターに入力すると、撹拌機の仕様が決定されます。液体中にある物質(黒色)が加えられた状態を初期状態とし、その撹拌機仕様に基づいて、一定時間にわたる撹拌がシミュレートされます。シミュレーションの結果として、混ざり具合(濃度の標準偏差)が出力されます。
ここで、撹拌機の制約上、これらのパラメータの値域は以下の通りとします。
# 撹各設計パラメータの上限・下限値
bounds: dict[str, tuple[int, int]] = {
"x0": (2, 10), # 2 <= x0 <= 10
"x1": (5, 20), # 5 <= x1 <= 20
"x2": (0, 45), # 0 <= x2 <= 45
"x3": (1, 5), # 1 <= x3 <= 5
"x4": (1, 4), # 1 <= x4 <= 4
}
撹拌シミュレーター MixingSimulator
の使用例は以下の通りです。
MixingSimulator
のメソッド simulate()
により、撹拌シミュレーションを実行し、最終的に得られた濃度場における濃度の標準偏差
c_std
が返却されます。完全に均一に撹拌された場合、c_std
はゼロとなります。また、メソッド
plot_evolution()
により、濃度場の時系列変化を表示することができます。
以下のシミュレーション結果を見ると、時間の経過とともに徐々に撹拌過程が進行し、濃度分布の標準偏差も小さくなる様子が示されています。最終的に、0.031 という濃度の標準偏差が得られ、濃度分布としては比較的不均一な結果となっています。
from utils.mixing import MixingSimulator
# 各設計パラメータ値域の中間値を使用
x0, x1, x2, x3, x4 = tuple(int(0.5 * (v[0] + v[1])) for v in bounds.values())
# 与えられたパラメータ値でシミュレーターを初期化
simulator = MixingSimulator(x0, x1, x2, x3, x4)
# 時刻 500 までの撹拌シミュレーションを実施し、濃度の標準偏差 c_std を取得
c_std = simulator.simulate(duration=500)
# 結果表示
print(f"{c_std=:.3f}") # 撹拌後の濃度の標準偏差
simulator.plot_evolution(num_snaps=5) # 撹拌過程の濃度分布の時系列変化をプロット
1.2. ブラックボックス関数の実装¶
上記で説明したシミュレーター MixingSimulator
に基づいて、ブラックボックス関数を実装します。以下のブラックボックス関数
blackbox
では、5
つの設計パラメータを引数として受け取り、ある時間長さ (duration
) のシミュレーションを実施、その結果得られる結果(最終的な物質濃度の標準偏差)を返却します。
本チュートリアルでは、撹拌後の濃度の標準偏差を最小とするような設計パラメータセットを探索することを目的とします。
def blackbox(x0: int, x1: int, x2: int, x3: int, x4: int) -> float:
s = MixingSimulator(x0, x1, x2, x3, x4)
c_std = s.simulate(duration=500)
s.plot_evolution()
print(f"{c_std=:.3f}")
return c_std
2. ブラックボックス最適化のプログラム実装¶
本チュートリアルで考慮するブラックボックス最適化手法は、FMQA と呼ばれるイジングマシンを活用したブラックボックス最適化手法です。
FMQA の処理フローは、こちらのチュートリアルの通りですが、以下のサイクルに基づいて実施します。
FMQA を含むブラックボックス最適化では、目的関数をブラックボックスとして取り扱うため、基本的にプログラム実装そのものを変更することなく応用することが可能です。したがって、ここで説明するブラックボックス最適化のプログラム実装については、必ずしも理解する必要はありません。
2.1. 決定変数クラスの定義¶
FMQA 中に活用されるイジングマシンが、直接扱える決定変数はバイナリ決定変数のみであるため、整数や実数などの非バイナリ決定変数を考慮する場合には、適切なエンコーディングを施す必要があります。本チュートリアルでは、以下で説明するドメインウォール・エンコーディングを考慮した整数決定クラスを定義し、非バイナリ決定変数を考慮します。
2.1.1. ドメインウォール・エンコーディングとは¶
ドメインウォール・エンコーディング (Domain-Wall Encoding) は、離散的な非バイナリ変数(たとえば、0〜k までの取りうる整数変数)をバイナリ変数に変換する手法の一つです。
ある整数変数 $x$ が $\{0, 1, \dots, k\}$ の $(k+1)$ 通りの値を取るとします。
ドメインウォール・エンコーディングでは、$k$ 個のバイナリ変数 $q_1, q_2, \dots, q_k$ を用いて、次のように値を表現します:
- $x = i$ のとき、変数列 $\boldsymbol{q}$ の先頭から $i$ 個が
1
、残りが0
となる - つまり、
1 → 0
に切り替わる位置(ドメインウォール)が $x$ の値を表します
例:$k = 4$ の場合¶
$x$ の値 | エンコードされたビット列 $\boldsymbol{q}$ |
---|---|
0 | [0, 0, 0, 0] |
1 | [1, 0, 0, 0] |
2 | [1, 1, 0, 0] |
3 | [1, 1, 1, 0] |
4 | [1, 1, 1, 1] |
このように、整数値の表現は「壁の位置(ドメインウォール)」によって一意に決まります。上記では、最小値 0 の整数変数について説明していますが、最小値が非ゼロの整数変数や、離散化を考慮することで実数変数にも適用可能です。
2.1.2. 整数決定変数クラス IntegerVariable
の説明¶
以下に実装する整数決定変数クラス IntegerVariable
は、Amplify SDK
のバイナリ変数を内部的に利用し、ドメインウォール・エンコーディングを適用することで、整数決定変数を効率的に表現します。
IntegerVariable
クラスの核となる機能は、整数値とバイナリ表現間の変換です。
-
encode(self, x: int) -> np.ndarray
:
このメソッドは、引数として渡された整数値x
を、対応するドメインウォール・エンコーディングに基づくバイナリベクトル (NumPy 配列) に変換します。例えば、bounds=(0, 5)
の変数でx=3
をエンコードすると、[1., 1., 1., 0., 0.]
のような配列が返されます。 -
decode(self, x: np.ndarray) -> int
:
このメソッドは、イジングマシンが計算したバイナリ変数の結果 (NumPy 配列) を受け取り、それを元の整数値にデコードします。ドメインウォール・エンコーディングに従い、配列内の 1 の数を数え、それに下限値を加えることで元の整数値を復元します。
また、IntegerVariable
クラスは、以下の 2 つのプロパティを提供します。
-
constraint
:
このプロパティは、この整数変数で考慮されているドメインウォール制約を返します。最適化においては、この制約を考慮する必要があります。これにより、イジングマシンが、関連する変数変数 (以下のbinary_variables
) をドメインウォール・エンコーディングに従って扱います。 -
binary_variables
:
この整数変数を構成する Amplify SDK のバイナリ変数ベクトルを返します。整数変数を直接バイナリ変数の組み合わせとして参照したい場合に利用できます。
下記の実装では、複数の IntegerVariable
をまとめて管理する Variables
も考慮されています。
import amplify
import numpy as np
class IntegerVariable:
"""ブラックボックス最適化のための整数決定変数クラス。ドメインウォール・エンコーディングによる整数のエンコードとデコードを行う。"""
def __init__(
self, bounds: tuple[int, int], variable_generator: amplify.VariableGenerator
):
self._bounds = bounds
self._q = variable_generator.array("Binary", bounds[1] - bounds[0])
self._constraint = amplify.domain_wall(self._q[::-1])
@property
def constraint(self) -> amplify.Constraint:
"""整数決定変数のエンコーディングに必要な制約を返却"""
return self._constraint
@property
def binary_variables(self) -> amplify.PolyArray:
"""整数決定変数を構成する Amplify SDK のバイナリ変数ベクトルを返却"""
return self._q
def encode(self, x: int) -> np.ndarray:
"""決定変数値をエンコードし、バイナリ化する関数"""
if x < self._bounds[0] or x > self._bounds[1]:
raise ValueError(f"x must be in {self._bounds}")
ret = np.zeros(len(self._q))
ret[0 : x - self._bounds[0]] = 1
return ret
def decode(self, x: np.ndarray) -> int:
"""バイナリ値を整数決定変数値にデコードする関数"""
if x.shape != self._q.shape:
raise ValueError(f"x must be of shape {self._q.shape}")
return x.sum() + self._bounds[0]
class Variables:
"""複数の整数決定変数から構成されるリストを管理するクラス。"""
def __init__(self, variable_list: list[IntegerVariable]):
self._variable_list = variable_list
def encode(self, x: list[int]) -> np.ndarray:
"""決定変数値をエンコードし、バイナリ化する関数"""
ret: list[int] = []
for i, var in enumerate(self._variable_list):
ret += var.encode(x[i]).tolist()
return np.array(ret)
def decode(self, x: np.ndarray) -> np.ndarray:
"""バイナリ値を整数決定変数値にデコードする関数"""
ret: list[int] = []
ista = 0
for var in self._variable_list:
iend = ista + len(var.binary_variables)
ret.append(var.decode(x[ista:iend]))
ista = iend
return np.array(ret, dtype=int)
@property
def constraints(self) -> amplify.ConstraintList:
"""全ての整数決定変数のエンコーディングに必要な制約を返却"""
return amplify.ConstraintList([var.constraint for var in self._variable_list])
@property
def binary_variables(self) -> amplify.PolyArray:
"""全ての整数決定変数を構成する Amplify SDK のバイナリ変数ベクトルを返却"""
ret = np.array([])
for var in self._variable_list:
ret = np.concatenate((ret, var.binary_variables)) # type: ignore
return amplify.PolyArray(ret.tolist())
def __getitem__(self, i: int) -> IntegerVariable:
return self._variable_list[i]
2.2. FM モデル実装¶
PyTorch を用いて、FM モデルを定義するクラス TorchFM
を実装します(通常の機械学習モデルの実装と同様)。FM
モデルは以下のような多項式で表される機械学習モデルです。ここで、$\boldsymbol{x}$ は変数、$d$
はブラックボックス関数への入力の長さを表す定数、$\boldsymbol{v}$、$\boldsymbol{w}$、$w_0$ はモデルのモデル係数(機械学習で言うところの重みやバイアス)、$k$
はパラメータのサイズを表すハイパーパラメータです。
$$ \begin{aligned} f(\boldsymbol{x} | \boldsymbol{w}, \boldsymbol{v}) &= \underset{\color{red}{\mathtt{out\_linear}}}{\underline{ w_0 + \sum_{i=1}^d w_i x_i} } + \underset{\color{red}{\mathtt{out\_quadratic}}}{\underline{\frac{1}{2} \left[\underset{\color{red}{\mathtt{out\_1}}}{\underline{ \sum_{f=1}^k\left(\sum_{i=1}^d v_{i f} x_i\right)^2 }} - \underset{\color{red}{\mathtt{out\_2}}}{\underline{ \sum_{f=1}^k\sum_{i=1}^d v_{i f}^2 x_i^2 }} \right] }} \end{aligned} $$
import torch
import torch.nn as nn
import numpy as np
# 乱数シードの固定
seed = 0
rng = np.random.default_rng(seed)
torch.manual_seed(seed)
class TorchFM(nn.Module):
def __init__(self, d: int, k: int):
"""モデルを構築する
Args:
d (int): 入力ベクトルのサイズ
k (int): パラメータ k
"""
super().__init__()
self.d = d
self.v = nn.Parameter(torch.randn((d, k)))
self.w = nn.Parameter(torch.randn((d,)))
self.w0 = nn.Parameter(torch.randn(()))
def forward(self, x: torch.Tensor) -> torch.Tensor:
"""入力 x を受け取って y の推定値を出力する"""
out_linear = torch.matmul(x, self.w) + self.w0
out_1 = torch.matmul(x, self.v).pow(2).sum(1)
out_2 = torch.matmul(x.pow(2), self.v.pow(2)).sum(1)
out_quadratic = 0.5 * (out_1 - out_2)
out = out_linear + out_quadratic
return out
def get_parameters(self) -> tuple[np.ndarray, np.ndarray, float]:
"""パラメータ v, w, w0 を出力する"""
np_v = self.v.detach().numpy().copy()
np_w = self.w.detach().numpy().copy()
np_w0 = self.w0.detach().numpy().copy()
return np_v, np_w, float(np_w0)
2.3. 機械学習関数の実装¶
次に、上記で定義した TorchFM
モデルを機械学習するための関数 train
を実装します。こちらも通常の機械学習と同様の手続きで処理を行いますが、ブラックボックス最適化において重要な FM モデルの性能指標として、学習済みモデルの予測値と真値との相関係数を別途表示しています。
from torch.utils.data import TensorDataset, DataLoader, random_split
from tqdm.auto import tqdm, trange
import copy
def train(
x: np.ndarray,
y: np.ndarray,
model: TorchFM,
) -> None:
"""FM モデルの学習を行う
Args:
x (np.ndarray): 学習データ (入力ベクトル)
y (np.ndarray): 学習データ (出力値)
model (TorchFM): TorchFM モデル
"""
# イテレーション数
epochs = 2000
# モデルの最適化関数
# optimizer = torch.optim.AdamW([model.v, model.w, model.w0], lr=0.1)
optimizer = torch.optim.AdamW(model.parameters(), lr=0.1) # type: ignore
# 損失関数
loss_func = nn.MSELoss()
# データセットの用意
x_tensor, y_tensor = (torch.from_numpy(x).float(), torch.from_numpy(y).float())
dataset = TensorDataset(x_tensor, y_tensor)
train_set, valid_set = random_split(dataset, [0.8, 0.2])
if len(valid_set) == 0:
valid_set = train_set
train_loader = DataLoader(train_set, batch_size=8, shuffle=True)
valid_loader = DataLoader(valid_set, batch_size=8, shuffle=True)
# 学習の実行
min_loss = 1e18 # 損失関数の最小値を保存
best_state = model.state_dict() # モデルの最も良いパラメータを保存
# `range` の代わりに `tqdm` モジュールを用いて進捗を表示
for _ in trange(epochs, leave=False):
# 学習過程
for x_train, y_train in train_loader:
optimizer.zero_grad()
pred_y = model(x_train)
loss = loss_func(pred_y, y_train)
loss.backward()
optimizer.step()
# 検証過程
with torch.no_grad():
loss = 0
for x_valid, y_valid in valid_loader:
out_valid = model(x_valid)
loss += loss_func(out_valid, y_valid)
if loss < min_loss:
# 損失関数の値が更新されたらパラメータを保存
best_state = copy.deepcopy(model.state_dict())
min_loss = loss
# モデルを学習済みパラメータで更新
model.load_state_dict(best_state)
# 学習された FM モデル予測値と真値との相関係数の表示
print(
f"corrcoef: {torch.corrcoef(torch.stack((model(x_tensor), y_tensor)))[0, 1].detach()}"
)
2.4. ソルバークライアントの設定¶
ブラックボックス最適化で活用するイジングマシン(ソルバークライアント)を設定します。今回は、Fixstars Amplify Annealing Engine (Amplify AE) を用います。ローカル環境等で実行する場合は、無料で取得可能な Fixstars Amplify の API トークンを入力してください。
from amplify import FixstarsClient
from datetime import timedelta
# ソルバークライアントを Amplify AE に設定
client = FixstarsClient()
# ローカル環境等で実行する場合はコメントを外して Amplify AEのアクセストークンを入力してください
# client.token = "AE/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
client.parameters.timeout = timedelta(milliseconds=10000)
2.5. イジングマシンによる最適化の実装¶
ここでは、上記 train
関数で学習した学習済み FM モデルを最適化するための関数を定義します。手順としては、
- 学習済み FM モデルのモデル係数に基づき、FM モデルと数学的に等価な QUBO モデルを Amplify SDK の決定変数を用いて構築。
- 構築した QUBO モデルをイジングマシンにより最適化
- 最適化結果を返却
from amplify import Model, solve, Poly
# 決定変数の生成
gen = amplify.VariableGenerator()
var_list = [IntegerVariable(bounds=b, variable_generator=gen) for b in bounds.values()]
variables = Variables(var_list)
# 上記の2行は以下と同じ処理
# x0 = IntegerVariable(bounds=bounds["x0"], variable_generator=gen)
# x1 = IntegerVariable(bounds=bounds["x1"], variable_generator=gen)
# x2 = IntegerVariable(bounds=bounds["x2"], variable_generator=gen)
# x3 = IntegerVariable(bounds=bounds["x3"], variable_generator=gen)
# x4 = IntegerVariable(bounds=bounds["x4"], variable_generator=gen)
# variables = Variables([x0, x1, x2, x3, x4])
# 決定変数エンコーディングに必要な制約条件
constraints = variables.constraints
def anneal(torch_model: TorchFM) -> np.ndarray:
"""FM モデルのパラメータを受け取り、それらのパラメータにより記述される FM モデルの最小値を与える x を求める"""
# TorchFM からパラメータ v, w, w0 を取得
v, w, w0 = torch_model.get_parameters()
# Amplify のバイナリ決定変数を取得
x = variables.binary_variables
# FM モデルと等価な QUBO モデル(目的関数)を作成
out_linear = w0 + (x * w).sum()
out_1 = ((x[:, np.newaxis] * v).sum(axis=0) ** 2).sum() # type: ignore
out_2 = ((x[:, np.newaxis] * v) ** 2).sum()
objective: Poly = out_linear + (out_1 - out_2) / 2
# Amplify モデルを定義
amplify_model = Model(objective, constraints)
# 最小化を実行(構築したモデルと、始めに作ったソルバークライアントを引数として渡す)
result = solve(amplify_model, client)
if len(result.solutions) == 0:
raise RuntimeError("No solution was found.")
# モデルを最小化する入力ベクトル(最適設計パラメータ候補)を返却
return x.evaluate(result.best.values).astype(int)
def generate_random_input() -> np.ndarray:
x: list[int] = []
for v_min, v_max in bounds.values():
x.append(rng.integers(v_min, v_max + 1))
return np.array(x)
def init_training_data(num_samples: int):
# n0 個の 長さ d の入力値を乱数を用いて作成
data: list[np.ndarray] = []
for _ in range(num_samples):
data.append(generate_random_input())
x = np.array(data)
# 入力値の重複が発生していたらランダムに値を変更して回避する
x = np.unique(x, axis=0)
while x.shape[0] != num_samples:
x = np.vstack((x, generate_random_input()))
x = np.unique(x, axis=0)
# blackbox 関数を評価して入力値に対応する n0 個の出力を得る
y = np.zeros(num_samples)
for i in range(num_samples):
y[i] = blackbox(*x[i])
return x, y
n_0 = 10 # 初期教師データの数
x, y = init_training_data(num_samples=n_0)
3. 最適化の実行¶
これまでに実装した関数やクラスを使って、n
サイクルからなるブラックボックス最適化を実行します。以下のコードでは、目的関数を評価できる回数 n = 5
回としています。これは、実行時間制限のある本デモ・チュートリアル環境における最低限の動作確認のための設定となります。本来のブラックボックス最適化の為の実行条件・実行例は、『FMQA 実行例』をご覧ください。
実行に際しては、まず、初期学習データの入力ベクトルをエンコードし、バイナリ値へ変換します。FM モデルの学習データはバイナリ化したもの x_encoded
を考慮しますが、ブラックボックス関数の評価時には整数決定変数へデコードしたもの x_hat_decoded
を考慮しています。
# FMQA サイクルの実行回数
n = 5 # 最低限の動作確認のため 5 回
# 初期学習データ (x) をバイナリ値にエンコーディング
x_encoded = np.array([variables.encode(x[i]) for i in range(x.shape[0])])
# N 回のイテレーションを実行
# `range` の代わりに `tqdm` モジュールを用いて進捗を表示
for i in trange(n):
# 機械学習モデルの作成
model = TorchFM(len(x_encoded[0]), k=10)
# モデル学習の実行
train(x_encoded, y, model)
# 学習済みモデルの最小値を与える入力ベクトルの値(バイナリ値にエンコード済み)を取得
x_hat = anneal(model)
# x_hat が学習データ内サンプルと同一の場合はランダムに再生成
while (x_hat == x_encoded).all(axis=1).any():
x_hat_random = generate_random_input()
x_hat = variables.encode(x_hat_random.tolist()) # type: ignore
print("deduplication")
# バイナリ決定変数値を整数決定変数値にデコード
x_hat_decoded = variables.decode(x_hat)
# 推定された入力ベクトルを用いてブラックボックス関数を評価
y_hat = blackbox(*x_hat_decoded)
# 評価した値をデータセットに追加
x_encoded = np.vstack((x_encoded, x_hat))
y = np.append(y, y_hat)
tqdm.write(f"FMQA cycle {i}: found y = {y_hat}; current best = {np.min(y)}")
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(6, 4))
ax = fig.add_subplot()
# 初期教師データ生成のブラックボックス関数の評価値
ax.plot(
range(-n_0 + 1, 1),
y[:n_0],
marker="o",
linestyle="-",
color="b",
)
# FMQA サイクルのブラックボックス関数の評価値
ax.plot(
range(1, n + 1),
y[n_0:],
marker="o",
linestyle="-",
color="r",
)
# 目的関数の最小値の更新履歴
ax.plot(
range(-n_0 + 1, n + 1),
[y[0]] + [min(y[:i]) for i in range(2, n_0 + n + 1)],
linestyle="--",
color="k",
)
ax.set_xlabel("number of iterations", fontsize=18)
ax.set_ylabel("f(x)", fontsize=18)
ax.tick_params(labelsize=18)
ax.set_yscale("log")
ax.set_ylim(1e-2, 2e-1)
plt.show()
print(f"best objective: {np.min(y):.3f}")
print(f"best solution: {variables.decode(x_encoded[np.argmin(y)]).tolist()}")
4.2. FMQA 実行例¶
一般的に、FixstarsClient
で採用されているヒューリスティクスというアルゴリズムの原理上、得られる解に完全な再現性はありませんが、本サンプルコードを実行した際に得られる、典型的な実行結果を以下に紹介します。
下図は、n = 50
とした場合の実行例で、最適化履歴(ブラックボックス関数値の推移および最良解の更新履歴)を示します。
各最適化サイクルにおける目的関数値の増減はありますが、平均的には最適化サイクルと共により均一に撹拌される設計パラメータが探索されている様子が示されています。本実行例では、最終的な最良解として、濃度の標準偏差 0.014 が得られており、その設計パラメータにおける撹拌過程も以下に示します。