■ はじめに
全4回にわたる導入編では、最適化に向けた設計フローを軸に、対象回路、設計条件、Datalinkを用いたADSデータ転送、Pandasによる機械学習用データセット作成までを解説しました。本記事では、機械学習を用いたサロゲートモデルの構築 について説明します。
■ 導入編のあらすじ
導入編で示した設計フローマップを下記の通り再掲します。
「回路 → ADS → CSV → MLデータ → 最適化」という一連の流れの中で サロゲートモデル(代理モデル) を構築し、仕様を満たす Band Pass Filter(BPF)回路の部品定数を探索します。
具体的な設計目標は以下の通りです。
- 中心周波数: 450 〜 520MHz
- ピークゲイン: 既存BPF (400 〜 470MHz) と同程度
- イメージ除去比: 50 dB以上
また、回路設計✕機械学習 の概念実証を目的とするため、以下の簡略化条件を設定します。
- 入出力ポートは理想的な50Ω
- RLC受動素子は理想モデル
- 寄与度の高い "支配的3パラメータ" のみを調整対象とする
■ サロゲートモデルの特徴
大量のシミュレーション結果を学習し、回路の応答特性を出力するモデルをサロゲートモデルと呼びます。このモデルを構築することで下記のメリットが得られます。
1. 定数探索の大幅な時間短縮
グリッドサーチによる探索手法では、シミュレーションの時間コストが爆発的に増加するため現実的ではありません。一方、サロゲートモデルでは、任意の部品定数に対し、学習済みモデルがわずか数秒で特性を出力することができます。
2. Optuna と組み合わせた高速なパラメータ最適化が可能
探索的に定数範囲を絞りつつ、高速なパラメータ最適化が可能です。自動的な処理ができるので、設計の経験値が必要となりません。
3. “過度な精度”を必要としない
最終的な特性確認は再度ADSで行うため、サロゲートモデルには 「傾向を正しく捉える」精度があれば十分 です。本データセットは物理法則に基づいて生成されているため、特徴量設計や過度なハイパーパラメータ調整を行わなくても、比較的安定した学習が可能です。
むしろ、過度なチューニングはサロゲートモデル構築の妨げとなる可能性もあります。
■ ADSシミュレーション構成
シミュレーション条件は以下の通りです。
- リファレンス: C1 〜 C13 (コンデンサ)、L1 〜 L3 (コイル)
- スイープ範囲: 0.25 pF 〜 50 pF(コンデンサ)、0.25 nH 〜 50 nH(コイル)
- VTUNE: 0.5 〜 4.5 V
目的変数:ピーク周波数 (fpeak)、ゲイン (gain)、イメージ抑圧量 (img)
■ サロゲートモデルの実装
概要は以下の通り。
- モデル:LightGBM
- パラメータ:ほぼデフォルト
- 評価指標:MAE
- KFOLD数:5
- スタート定数:400Mverの値とする
- fpeak、gain、imageを予測する3モデルを構築
本記事では重要な部分のみを抜粋します。
コードの全体像については、記事下部に掲載したGitHubリポジトリを参照してください。
● 【データセット作成】
⇨ fpeak、gain、image に関する3種類のデータセットを作成する
# 共通カラム + T12
ncolumn = ["C1","C2","C3","C4","C5","C6","C7",\
"C8","C9","C10","C11","C12","C13",\
"L1","L2","L3","VTUNE12"]
# [訓練データ]
# 「共通カラム(REF)」 + 「ターゲット列」のデータフレームを作る
train_df1 = pd.concat([df[ncolumn],df["fpeak"]],axis=1) # ピーク周波数
train_df2 = pd.concat([df[ncolumn],df["gain"]],axis=1) # ゲイン
train_df3 = pd.concat([df[ncolumn],df["img"]],axis=1) # イメージ
# [テストデータ]
# 訓練データからターゲット列を落とす
test_df1 = train_df1.drop("fpeak",axis=1)
test_df2 = train_df2.drop("gain",axis=1)
test_df3 = train_df3.drop("img",axis=1)
# ピーク周波数のみ桁が大きく、学習が不安定になるためLOGスケールに変換
train_df1["fpeak"] = np.log(train_df1["fpeak"])
● 【モデル構築】
⇨ LightGBM の3モデルを構築
ライブラリ読み込み
import lightgbm as lgb
from sklearn.model_selection import KFold # K分割
# パラメータ設定
# (サロゲートモデルは過剰なチューニングしないこと)
lgbm_params = {
'objective': 'regression',
"device": "cpu",
'metric': 'mae',
'boosting_type': 'gbdt', # BoostingのType
'learning_rate': 0.01, # 学習率 default = 0.1
'random_state': 42,
'verbose': -1 # ログ出力の制御
}
# 訓練データから除外するカラム(ターゲット)
RMV1 = ["fpeak"]
RMV2 = ["gain"]
RMV3 = ["img"]
# 各データフレームの特徴量(ターゲットだけ外す)
FEATURES1 = [c for c in train_df1.columns if not c in RMV1]
FEATURES2 = [c for c in train_df2.columns if not c in RMV2]
FEATURES3 = [c for c in train_df3.columns if not c in RMV3]
def model_LGB(train, test, FEATURES, Target):
FOLDS = 5
kf = KFold(n_splits=FOLDS, shuffle=True, random_state=42)
pred_lgb = np.zeros(len(test))
models = []
# ★ Foldごとの学習履歴を保存
evals_result = []
for i, (train_index, valid_index) in enumerate(kf.split(train)):
print("#" * 25)
print(f"### Fold {i+1}")
print("#" * 25)
x_train = train.loc[train_index, FEATURES].copy()
y_train = train.loc[train_index, Target]
x_valid = train.loc[valid_index, FEATURES].copy()
y_valid = train.loc[valid_index, Target]
lgb_train = lgb.Dataset(x_train, y_train)
lgb_eval = lgb.Dataset(x_valid, y_valid, reference=lgb_train)
# ★ Fold専用のdictを用意
eval_result_fold = {}
model_lgb = lgb.train(
lgbm_params,
lgb_train,
num_boost_round=2000,
valid_sets=[lgb_train, lgb_eval],
valid_names=["train", "valid"],
callbacks=[
lgb.early_stopping(stopping_rounds=100, verbose=False),
lgb.record_evaluation(eval_result_fold),
lgb.log_evaluation(100),
]
)
models.append(model_lgb)
evals_result.append(eval_result_fold)
pred_lgb[valid_index] = model_lgb.predict(
x_valid,
num_iteration=model_lgb.best_iteration
)
return models, pred_lgb, evals_result
● 【学習】
⇨ 3モデルの学習を行い、学習曲線を表示
# 1.ピーク周波数
model1 = []
lgb_output1 = []
eval_result1 = {}
model1, lgb_output1, eval_result1 = model_LGB(train_df1, test_df1, FEATURES1, RMV1)
# 2.ゲイン
model2 = []
lgb_output2 = []
eval_result2 = {}
model2, lgb_output2, eval_result2 = model_LGB(train_df2, test_df2, FEATURES2, RMV2)
# 3.イメージ
model3 = []
lgb_output3 = []
eval_result3 = {}
model3, lgb_output3, eval_result3 = model_LGB(train_df3, test_df3, FEATURES3, RMV3)
import matplotlib.pyplot as plt
# 最後のFoldを代表として描画
evals = [eval_result1[-1], eval_result2[-1], eval_result3[-1]]
titles = ["fpeak", "gain", "image"]
plt.figure(figsize=(15, 4))
for i in range(3):
plt.subplot(1, 3, i+1)
plt.plot(evals[i]['train']['l1'], label='train MAE')
plt.plot(evals[i]['valid']['l1'], label='valid MAE')
plt.title(titles[i])
plt.xlabel('Iterations')
plt.ylabel('MAE')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
各モデルの学習状況を確認するため、MAE (L1 Loss) の推移を 「fpeak」「gain」「image」 の3モデルについて横並びで可視化します。
学習曲線は代表 Fold の挙動を可視化します。
■ 相互作用の取り扱い
高周波回路の諸特性は、複数の部品定数の組み合わせによって複雑に決まります。これらの特性は基本的にトレードオフの関係にあるため、本来は複数部品の組み合わせデータを学習する必要があります。基本方針として、1部品定数のスイープ結果 (シングルスイープ) を基にし、重要な上位3部品を選定します。
重要かどうかの判断にはSHAP値を利用します。SHAPは本来、Beeswarm(ドット)プロット、2つ以上のSHAP比較、SHAP依存プロットから特性のトレードオフを語ることができるのですが、本記事では概念実証を目的としているため、SHAPの Global Importance (全体寄与度)のFOLD平均だけに注目し、どの部品が支配的かを特定することのみに着目します。
■ 重要特徴量の選定
freq、gain、imgの上位3部品をSHAP値から選定します。
● 【SHAP値計算】
import shap
import numpy as np
shap.initjs()
models = [model1, model2, model3]
# ============================================================
# SHAP用サンプル
# ============================================================
X_sample = [
train_df1.drop("fpeak", axis=1).sample(1000, random_state=42),
train_df2.drop("gain", axis=1).sample(1000, random_state=42),
train_df3.drop("img", axis=1).sample(1000, random_state=42)
]
# ============================================================
# SHAP上位特徴量抽出
# ============================================================
def get_shap_topk(shap_values, X, k=5):
mean_abs = np.mean(np.abs(shap_values), axis=0)
top_idx = np.argsort(mean_abs)[-k:]
return shap_values[:, top_idx], X.iloc[:, top_idx]
# ============================================================
# fold平均SHAP計算
# ============================================================
shapval = []
for j, X in enumerate(X_sample):
shap_sum = np.zeros((X.shape[0], X.shape[1]))
for m in models[j]:
explainer = shap.TreeExplainer(m)
shap_sum += explainer.shap_values(X)
shapval.append(shap_sum / len(models[j]))
# ============================================================
# 上位特徴量取得
# ============================================================
shap_top, X_top = zip(*[
get_shap_topk(sv, X, k=5)
for sv, X in zip(shapval, X_sample)
])
● 【SHAPグラフ表示】
⇨ 寄与度上位パラメータの表示
import shap
import matplotlib.pyplot as plt
plt.figure(figsize=(25, 8))
titles = ["fpeak", "gain", "image"]
for i in range(3):
ax1 = plt.subplot(1, 3, i+1)
shap.summary_plot(
shap_top[i],
X_top[i],
plot_type="bar",
show=False,
plot_size=None
)
# タイトル
ax1.set_title(titles[i], fontsize=22)
# x軸(importance)
ax1.tick_params(axis='x', labelsize=18)
# y軸(特徴量名)
for label in ax1.get_yticklabels():
label.set_fontsize(18)
plt.tight_layout()
plt.show()
■ まとめ
- ADSシミュレーション結果を用いて LightGBM によるサロゲートモデルを構築し、SHAP値を用いて支配的な部品パラメータの抽出を行った
- 本手法により、全パラメータを同時に探索することなく、次工程である 最適化対象を3部品に絞り込むことが可能 となった
次回は、選定した重要部品に対する複数部品シミュレーションの結果をデータセットに追加し、Optunaを用いてパラメータ最適化を行います。
■ 参考文献
特になし
■ GitHubコード
■ 本シリーズの記事一覧



