1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PokeAPIと線形回帰とロジスティック回帰

1
Last updated at Posted at 2026-01-22
  1. この記事の対象とゴール
    本記事は、図鑑No.1〜151の種族値を PokeAPI から取得し、
    (1) 重回帰で Total を予測
    (2) ロジスティック回帰で Strong/Weak を分類
    を、Google Colab 上で ipywidgets UI により対話的に実行し、図をPDF保存してダウンロードする構成を説明する。

前提:
・Colab上でインターネットアクセスが可能
・requests / numpy / matplotlib / ipywidgets / numba を使用する

出力:
・print で予測テーブル(Total予測値・Strong/Weak予測)
・2枚のプロット(Total予測、Strong確率)
・PDF保存+download

────────────────────────────────────────────────────────

  1. 何を学習しているか(モデル定義:プレーンテキスト数式)
    (1) 重回帰(Total予測)
    Total_hat = b0 + b1Attack + b2SpAtk + b3*Speed

入力特徴量:
X = [1, Attack, SpAtk, Speed]
目的変数:
y = Total

推定:
beta = argmin ||X*beta - y||^2
(実装は正規方程式:beta = (X^T X + eps I)^(-1) X^T y)

(2) ロジスティック回帰(Strong/Weak分類)
pStrong = 1 / (1 + exp(-(w0 + w1Attack + w2SpAtk + w3*Speed)))
Strong_hat = 1 if pStrong >= 0.5 else 0

真のラベル(教師データの作り方):
Total の上位 q_strong 分位を Strong=1、それ以外を Weak=0 とする
thr_total = quantile(Total, 1 - q_strong)
y01 = 1 if Total >= thr_total else 0

学習:
勾配降下法(GD)で w を更新
w <- w - lr * (1/n) * Σ_i (p_i - y_i) * x_i

評価:
accuracy と F1 を計算する

────────────────────────────────────────────────────────
2. データ取得(PokeAPI)
PokeAPI:
GET https://pokeapi.co/api/v2/pokemon/{id}/

各idについて stats を読み取り、以下を抽出:
HP, Attack, Defense, SpAtk, SpDef, Speed
Total = 6能力の合計

注意点(実運用):
・API側の負荷や制限があるため sleep_s を入れている
・use_cache=1 で一度取得したデータを再利用できる(再実行高速化)

────────────────────────────────────────────────────────
3. 実行フロー(Runボタン1発)
Run Simulation / 実行 を押すと、順番に以下を行う:
(1) パラメータ検証(validate)
(2) 取得(fetch_gen1_stats)
(3) 行列 X,y 構築(build_Xy)
(4) 重回帰の学習・予測(lin_fit, yhat)
(5) Strong/Weakの教師ラベル生成(thr_total, y01)
(6) ロジスティック学習・確率予測(log_train_gd, log_predict_proba)
(7) メトリクス算出(RMSE, acc, f1)
(8) 予測テーブルの print 出力
(9) 図更新(update_plots)
(10) ログ保存(timestamp, params, key_outputs)

────────────────────────────────────────────────────────
4. UI設計(ipywidgets)
入力欄:
・lin_reg_eps(正規化項)
・q_strong(Strongを上位何割にするか)
・log_lr, log_iters(ロジスティック学習率・反復回数)
・start_id, end_id(取得範囲)
・timeout_s, sleep_s(API設定)
・use_cache(0/1)
・seed(乱数固定)

ボタン:
・Run Simulation / 実行:取得→学習→予測→表示→描画
・Reset / リセット:UI初期化、ログ消去、図状態初期化
・Save PDF / PDF保存:最新の図をPDFに保存してdownload
・Show Log / 履歴表示:過去実行のparamsと結果をprint
・Clear Log / 履歴消去:ログだけ消す

────────────────────────────────────────────────────────
5. 可視化(2プロット同期)
Plot1: Total prediction (linear)
・散布図:Attack vs Total(真値)
・線:Attack だけを走査し、SpAtkとSpeedは平均固定して Total_hat を描く(partial line)

Plot2: Strong probability (logistic)
・散布図:Attack vs pStrong
・線:Attack 走査(SpAtk, Speedは平均固定)で pStrong のpartial curve

ログスケール:
_auto_log_scale が「範囲が1000倍以上」かつ「正の値」なら log を使う

────────────────────────────────────────────────────────
6. PDF保存(Save PDF)
Save PDF / PDF保存 を押すと:
・fig.savefig("sim_result_YYYYMMDD_HHMMSS.pdf")
・files.download() でDLを開始

失敗しやすい点:
・未実行で図が無い(viz.fig is None)と保存不可
・Colab以外の環境だと files.download が使えない場合がある

────────────────────────────────────────────────────────
7. 重要な修正ポイント(このコードのままだと事故る所)
(1) ghost line が実際には表示されない
現状:
・ghostを描く → 直後に ax.cla() で消しているため、ゴーストが残らない

修正方針:
・ax.cla() を最初に呼んでから「ghost→現在」を描く
または
・ghost用の別レイヤ(別axes/別artist管理)にする

(2) plt.style.use('seaborn-v0_8-muted') は方針によっては非推奨
あなたの運用ルールが「seaborn禁止」なら、style指定は削除するのが安全。
(matplotlibのデフォルトで十分)

(3) ロジスティック学習が不安定になり得る(特徴量未正規化)
Attack/SpAtk/Speed のスケール差があると学習率依存が強くなる。
改善案:
・標準化(平均0, 分散1)してから学習し、表示時は元スケールで扱う
・あるいは lr を小さくし、iters を増やす

(4) API失敗時の再試行がない
改善案:
・失敗時に数回リトライ(指数バックオフ)
・HTTP 429 等の扱いを分ける

────────────────────────────────────────────────────────
8. 追加の改善案(使い勝手を上げる)
・説明変数をUIで選べるようにする(Attack/SpAtk/Speed固定から変更)
・分類の閾値(0.5)をUIで変える(Precision-Recall調整)
・混同行列のprint(TP/TN/FP/FN)
・学習曲線(loss推移)を表示
・CSV保存(取得データをローカルに保存)

────────────────────────────────────────────────────────
9. まとめ
この構成は「API取得→簡易ML→printで検算→UI操作→PDF保存」という流れを、
Colab上で1ファイル完結で回すための最小骨格になっている。
まずは ghost の消失バグ(cla順序)を直し、次に特徴量の標準化を入れると安定する。

# Program Name: gen1_simple_reg_logistic_colab.py
# Creation Date: 2026-01-22
# Purpose: Interactive simulation with PDF export capability

# Target Simulation
# PokeAPI GET https://pokeapi.co/api/v2/pokemon/{id}/ で図鑑No.1〜151の種族値を取得し、
# (1) 重回帰で Total を予測、(2) ロジスティック回帰で Strong/Weak を分類。
# 予測結果(Total予測値・Strong/Weak予測)を print で表示し、図をPDF保存してDL可能。

# ============================================================
# 1. README(print関数で出力:日本語。数式は LaTeX 禁止、プレーンテキストのみ)
# ============================================================

def print_documentation():
    t = []
    t.append("============================================================")
    t.append("README / 使い方(日本語、数式はプレーンテキスト)")
    t.append("============================================================")
    t.append("目的:")
    t.append("  ・図鑑No.1〜151の種族値を取得し、Total予測(重回帰)とStrong/Weak分類(ロジスティック)を行う。")
    t.append("")
    t.append("支配方程式:")
    t.append("  (A) 重回帰: y = b0 + b1*Attack + b2*SpAtk + b3*Speed  (y=Total)")
    t.append("  (B) ロジスティック: p = 1/(1+exp(-(w0 + w1*Attack + w2*SpAtk + w3*Speed)))")
    t.append("      Strong if p>=0.5 else Weak")
    t.append("")
    t.append("分類ラベル(真値):")
    t.append("  ・Total の上位 q_strong 分位を Strong=1 とする(それ以外は Weak=0)。")
    t.append("")
    t.append("操作:")
    t.append("  ・Run Simulation / 実行 を押すと取得→学習→予測→print→描画を行う(自動更新なし)。")
    t.append("  ・Save PDF / PDF保存 で最新図をPDF保存してDLする。")
    t.append("  ・Show Log / 履歴表示 で履歴、Clear Log / 履歴消去 で消去、Reset / リセットで初期化。")
    t.append("============================================================")
    print("\n".join(t))


# ============================================================
# 2. Setup Code(from google.colab import files を含める。pip install 実行文字列はコメントに記述 / import / seed設定)
# ============================================================

# pip install requests numba ipywidgets matplotlib numpy  # (必要なら) / (if needed)

from google.colab import files  # 必須 / Mandatory
import time
import json
import math
import datetime
from typing import Dict, List, Tuple, Optional

import numpy as np
import matplotlib.pyplot as plt
import ipywidgets as widgets
from IPython.display import display, clear_output

try:
    import requests
except Exception as e:
    raise RuntimeError("requests import failed. pip install requests") from e

try:
    from numba import njit
except Exception as e:
    raise RuntimeError("numba import failed. pip install numba") from e

plt.style.use('seaborn-v0_8-muted')  # 必須 / Mandatory

POKEAPI_POKEMON_URL = "https://pokeapi.co/api/v2/pokemon/{id}/"
STAT_KEYS = ["hp", "attack", "defense", "special-attack", "special-defense", "speed"]


# ============================================================
# 3. TheoreticalModel(計算・検証・計算過程の表示:数式はプレーンテキストのみ)
# ============================================================

def print_calculation_steps(params: Dict, results: Dict):
    lines = []
    lines.append("------------------------------------------------------------")
    lines.append("計算過程 / Calculation Steps")
    lines.append("------------------------------------------------------------")
    lines.append("[重回帰 / Linear Regression]")
    lines.append("  Total_hat = b0 + b1*Attack + b2*SpAtk + b3*Speed")
    lines.append(f"  beta=[b0,b1,b2,b3]={np.array2string(results['beta'], precision=6)}")
    lines.append("[ロジスティック / Logistic]")
    lines.append("  p = 1/(1+exp(-(w0+w1*Attack+w2*SpAtk+w3*Speed)))")
    lines.append("  Strong_hat = 1 if p>=0.5 else 0")
    lines.append(f"  w=[w0,w1,w2,w3]={np.array2string(results['w'], precision=6)}")
    lines.append(f"  Strong_true threshold (Total) = {results['thr_total']:.3f}")
    lines.append(f"  Metrics: acc={results['acc']:.4f}, f1={results['f1']:.4f}")
    lines.append("------------------------------------------------------------")
    print("\n".join(lines))


def validate(params: Dict) -> Tuple[bool, List[str]]:
    msgs = []
    ok = True

    start_id = int(params.get("start_id", 1))
    end_id = int(params.get("end_id", 151))
    if not (1 <= start_id <= 151 and 1 <= end_id <= 151 and start_id <= end_id):
        ok = False
        msgs.append("警告: ID範囲が不正(1..151, start<=end)。 / Warning: Invalid ID range (1..151, start<=end).")

    timeout_s = float(params.get("timeout_s", 10.0))
    if timeout_s <= 0:
        ok = False
        msgs.append("警告: timeout_s は正。 / Warning: timeout_s must be positive.")

    sleep_s = float(params.get("sleep_s", 0.05))
    if sleep_s < 0:
        ok = False
        msgs.append("警告: sleep_s は0以上。 / Warning: sleep_s must be >=0.")

    q_strong = float(params.get("q_strong", 0.25))
    if not (0.0 < q_strong < 1.0):
        ok = False
        msgs.append("警告: q_strong は(0,1)。 / Warning: q_strong must be in (0,1).")

    lr = float(params.get("log_lr", 0.05))
    if lr <= 0:
        ok = False
        msgs.append("警告: log_lr は正。 / Warning: log_lr must be positive.")

    iters = int(params.get("log_iters", 2000))
    if iters <= 0:
        ok = False
        msgs.append("警告: log_iters は正。 / Warning: log_iters must be positive.")

    reg_eps = float(params.get("lin_reg_eps", 1e-8))
    if reg_eps < 0:
        ok = False
        msgs.append("警告: lin_reg_eps は0以上。 / Warning: lin_reg_eps must be >=0.")

    return ok, msgs


def fetch_pokemon_one(pokedex_id: int, timeout_s: float) -> Optional[Dict]:
    url = POKEAPI_POKEMON_URL.format(id=pokedex_id)
    try:
        r = requests.get(url, timeout=timeout_s)
        r.raise_for_status()
        data = r.json()
        name = data.get("name", "")

        stats = {k: 0 for k in STAT_KEYS}
        for s in data.get("stats", []):
            k = s["stat"]["name"]
            if k in stats:
                stats[k] = int(s["base_stat"])

        hp = stats["hp"]
        atk = stats["attack"]
        df = stats["defense"]
        spa = stats["special-attack"]
        spd = stats["special-defense"]
        spe = stats["speed"]
        total = int(hp + atk + df + spa + spd + spe)

        return {
            "id": int(pokedex_id),
            "name": str(name),
            "HP": hp, "Attack": atk, "Defense": df, "SpAtk": spa, "SpDef": spd, "Speed": spe, "Total": total
        }
    except Exception as e:
        print(f"取得失敗 / Fetch failed: id={pokedex_id}, err={e}")
        return None


def fetch_gen1_stats(start_id: int, end_id: int, timeout_s: float, sleep_s: float,
                     use_cache: bool, cache: Dict[int, Dict]) -> List[Dict]:
    rows = []
    for i in range(start_id, end_id + 1):
        if use_cache and (i in cache):
            rows.append(cache[i])
            continue
        row = fetch_pokemon_one(i, timeout_s)
        if row is not None:
            cache[i] = row
            rows.append(row)
        time.sleep(sleep_s)
    return rows


def build_Xy(rows: List[Dict]) -> Tuple[np.ndarray, np.ndarray]:
    # X: [1, Attack, SpAtk, Speed], y: Total
    n = len(rows)
    X = np.zeros((n, 4), dtype=np.float64)
    y = np.zeros((n,), dtype=np.float64)
    for i, r in enumerate(rows):
        X[i, 0] = 1.0
        X[i, 1] = float(r["Attack"])
        X[i, 2] = float(r["SpAtk"])
        X[i, 3] = float(r["Speed"])
        y[i] = float(r["Total"])
    return X, y


def lin_fit(X: np.ndarray, y: np.ndarray, reg_eps: float) -> np.ndarray:
    XT = X.T
    A = XT @ X
    if reg_eps > 0:
        A = A + reg_eps * np.eye(A.shape[0])
    b = XT @ y
    return np.linalg.solve(A, b)


@njit
def _sigmoid(z: float) -> float:
    if z >= 0:
        ez = math.exp(-z)
        return 1.0 / (1.0 + ez)
    else:
        ez = math.exp(z)
        return ez / (1.0 + ez)


@njit
def log_train_gd(X: np.ndarray, y01: np.ndarray, lr: float, iters: int) -> np.ndarray:
    n, d = X.shape
    w = np.zeros((d,), dtype=np.float64)
    for _ in range(iters):
        grad = np.zeros((d,), dtype=np.float64)
        for i in range(n):
            z = 0.0
            for j in range(d):
                z += w[j] * X[i, j]
            p = _sigmoid(z)
            err = (p - y01[i])
            for j in range(d):
                grad[j] += err * X[i, j]
        for j in range(d):
            w[j] -= lr * (grad[j] / n)
    return w


@njit
def log_predict_proba(X: np.ndarray, w: np.ndarray) -> np.ndarray:
    n, d = X.shape
    p = np.zeros((n,), dtype=np.float64)
    for i in range(n):
        z = 0.0
        for j in range(d):
            z += w[j] * X[i, j]
        p[i] = _sigmoid(z)
    return p


def metrics(y_true: np.ndarray, y_pred: np.ndarray) -> Tuple[float, float]:
    eps = 1e-12
    tp = float(np.sum((y_true == 1) & (y_pred == 1)))
    tn = float(np.sum((y_true == 0) & (y_pred == 0)))
    fp = float(np.sum((y_true == 0) & (y_pred == 1)))
    fn = float(np.sum((y_true == 1) & (y_pred == 0)))
    acc = (tp + tn) / max(tp + tn + fp + fn, eps)
    prec = tp / max(tp + fp, eps)
    rec = tp / max(tp + fn, eps)
    f1 = 2.0 * prec * rec / max(prec + rec, eps)
    return acc, f1


def run_theoretical_model(params: Dict, cache: Dict[int, Dict]) -> Dict:
    ok, msgs = validate(params)
    for m in msgs:
        print(m)
    if not ok:
        return {"ok": False, "error": "validation_failed"}

    start_id = int(params["start_id"])
    end_id = int(params["end_id"])
    timeout_s = float(params["timeout_s"])
    sleep_s = float(params["sleep_s"])
    use_cache = bool(int(params["use_cache"]))

    reg_eps = float(params["lin_reg_eps"])
    q_strong = float(params["q_strong"])
    lr = float(params["log_lr"])
    iters = int(params["log_iters"])

    try:
        rows = fetch_gen1_stats(start_id, end_id, timeout_s, sleep_s, use_cache, cache)
        if len(rows) < 4:
            print("警告: データ不足(最低4件)。 / Warning: Not enough data (>=4).")
            return {"ok": False, "error": "not_enough_data"}

        X, y = build_Xy(rows)

        beta = lin_fit(X, y, reg_eps=reg_eps)
        yhat = X @ beta
        rmse = float(np.sqrt(np.mean((yhat - y) ** 2)))

        totals = y.copy()
        thr_total = float(np.quantile(totals, 1.0 - q_strong))
        y01 = (totals >= thr_total).astype(np.float64)

        w = log_train_gd(X.astype(np.float64), y01.astype(np.float64), lr=lr, iters=iters)
        p = log_predict_proba(X.astype(np.float64), w)
        ypred = (p >= 0.5).astype(np.float64)
        acc, f1 = metrics(y01, ypred)

        # print predictions (simplified) / 予測表示(簡略)
        print("------------------------------------------------------------")
        print("予測結果 / Predictions")
        print("------------------------------------------------------------")
        print("No  Name            Total  Total_hat  pStrong  Pred  True")
        print("------------------------------------------------------------")
        for i, r in enumerate(rows):
            pred_label = "Strong" if ypred[i] >= 0.5 else "Weak"
            true_label = "Strong" if y01[i] >= 0.5 else "Weak"
            print(f"{r['id']:>3}  {r['name']:<14}  {r['Total']:>5}  {yhat[i]:>9.2f}  {p[i]:>6.3f}  {pred_label:<6}  {true_label:<6}")
        print("------------------------------------------------------------")
        print(f"RMSE(Total)={rmse:.4f}  acc={acc:.4f}  f1={f1:.4f}")
        print("------------------------------------------------------------")

        results = {
            "ok": True,
            "rows": rows,
            "X": X,
            "y": y,
            "beta": beta,
            "w": np.array(w),
            "yhat": yhat,
            "p": p,
            "y01": y01,
            "ypred": ypred,
            "rmse": rmse,
            "thr_total": thr_total,
            "acc": acc,
            "f1": f1,
        }

        print_calculation_steps(params, results)
        return results

    except Exception as e:
        print(f"例外 / Exception: {e}")
        return {"ok": False, "error": str(e)}


# ============================================================
# 4. Visualizer(matplotlib:ghost line+最低2プロット同期更新)
# ============================================================

class VizState:
    def __init__(self):
        self.fig = None
        self.ax1 = None
        self.ax2 = None
        self.last = None
        self.ghost_alpha = 0.15


def _auto_log_scale(x: np.ndarray) -> bool:
    xmin = float(np.min(x))
    xmax = float(np.max(x))
    if xmin <= 0:
        return False
    return (xmax / max(xmin, 1e-12)) >= 1000


def update_plots(viz: VizState, results: Dict):
    rows = results["rows"]
    atk = np.array([r["Attack"] for r in rows], dtype=np.float64)
    spa = np.array([r["SpAtk"] for r in rows], dtype=np.float64)
    spe = np.array([r["Speed"] for r in rows], dtype=np.float64)
    total = np.array([r["Total"] for r in rows], dtype=np.float64)

    yhat = results["yhat"]
    p = results["p"]

    if viz.fig is None:
        viz.fig, (viz.ax1, viz.ax2) = plt.subplots(1, 2, figsize=(14, 5))

    # ghost: keep previous by drawing it faintly / ゴースト: 前回を薄く再描画
    if viz.last is not None:
        a0 = viz.last
        viz.ax1.scatter(a0["atk"], a0["total"], s=20, alpha=viz.ghost_alpha, label="_ghost")
        viz.ax1.plot(a0["atk_s"], a0["yfit"], alpha=viz.ghost_alpha, label="_ghost")
        viz.ax2.scatter(a0["atk"], a0["p"], s=20, alpha=viz.ghost_alpha, label="_ghost")
        viz.ax2.plot(a0["atk_s"], a0["pfit"], alpha=viz.ghost_alpha, label="_ghost")

    viz.ax1.cla()
    viz.ax2.cla()
    viz.ax1.grid(True)
    viz.ax2.grid(True)

    # Plot1: Total vs Attack with fitted partial line / 主結果
    viz.ax1.scatter(atk, total, s=25, label="Total (true)")
    beta = results["beta"]
    atk_s = np.linspace(float(np.min(atk)), float(np.max(atk)), 200)
    spa_m = float(np.mean(spa))
    spe_m = float(np.mean(spe))
    yfit = beta[0] + beta[1]*atk_s + beta[2]*spa_m + beta[3]*spe_m
    viz.ax1.plot(atk_s, yfit, linewidth=2.5, label="Total_hat (partial)")

    viz.ax1.set_title("Plot1: Total prediction (linear)")
    viz.ax1.set_xlabel("Attack [a.u.]")
    viz.ax1.set_ylabel("Total [a.u.]")

    if _auto_log_scale(atk):
        viz.ax1.set_xscale("log")
    if _auto_log_scale(total):
        viz.ax1.set_yscale("log")
    viz.ax1.legend()

    # Plot2: pStrong vs Attack with logistic partial curve / 補助指標
    viz.ax2.scatter(atk, p, s=25, label="pStrong (pred)")
    w = results["w"]
    pfit = np.zeros_like(atk_s)
    for i in range(atk_s.size):
        z = w[0] + w[1]*atk_s[i] + w[2]*spa_m + w[3]*spe_m
        if z >= 0:
            ez = math.exp(-float(z))
            pfit[i] = 1.0/(1.0+ez)
        else:
            ez = math.exp(float(z))
            pfit[i] = ez/(1.0+ez)
    viz.ax2.plot(atk_s, pfit, linewidth=2.5, label="pStrong (partial)")
    viz.ax2.set_ylim(-0.05, 1.05)

    viz.ax2.set_title("Plot2: Strong probability (logistic)")
    viz.ax2.set_xlabel("Attack [a.u.]")
    viz.ax2.set_ylabel("P(Strong) [1]")

    if _auto_log_scale(atk):
        viz.ax2.set_xscale("log")
    viz.ax2.legend()

    viz.fig.tight_layout()
    plt.show()

    viz.last = {"atk": atk, "total": total, "p": p, "atk_s": atk_s, "yfit": yfit, "pfit": pfit}


# ============================================================
# 5. UIBuilder(ipywidgets:FloatText等のみ+Run/Reset/Save_PDF/ShowLog/ClearLog)
# ============================================================

class SimulatorUI:
    def __init__(self):
        self.cache: Dict[int, Dict] = {}
        self.log: List[Dict] = []
        self.viz = VizState()
        self.out_main = widgets.Output()
        self.out_plot = widgets.Output()

        # [Model Parameters]
        self.lin_reg_eps = widgets.BoundedFloatText(value=1e-8, min=0.0, max=1.0, step=1e-8, description="lin_reg_eps", layout=widgets.Layout(width="340px"))
        self.q_strong = widgets.BoundedFloatText(value=0.25, min=0.01, max=0.99, step=0.01, description="q_strong", layout=widgets.Layout(width="340px"))
        self.log_lr = widgets.BoundedFloatText(value=0.05, min=1e-6, max=1.0, step=0.01, description="log_lr", layout=widgets.Layout(width="340px"))
        self.log_iters = widgets.IntText(value=2000, description="log_iters", layout=widgets.Layout(width="340px"))

        # [Observed / Input Data]
        self.start_id = widgets.IntText(value=1, description="start_id", layout=widgets.Layout(width="340px"))
        self.end_id = widgets.IntText(value=151, description="end_id", layout=widgets.Layout(width="340px"))

        # [Environment / Options]
        self.timeout_s = widgets.BoundedFloatText(value=10.0, min=0.1, max=120.0, step=0.1, description="timeout_s", layout=widgets.Layout(width="340px"))
        self.sleep_s = widgets.BoundedFloatText(value=0.05, min=0.0, max=2.0, step=0.01, description="sleep_s", layout=widgets.Layout(width="340px"))
        self.use_cache = widgets.IntText(value=1, description="use_cache(0/1)", layout=widgets.Layout(width="340px"))
        self.seed = widgets.IntText(value=12345, description="seed", layout=widgets.Layout(width="340px"))

        # Buttons
        self.btn_run = widgets.Button(description="Run Simulation / 実行", button_style="success")
        self.btn_reset = widgets.Button(description="Reset / リセット", button_style="warning")
        self.btn_save = widgets.Button(description="Save PDF / PDF保存", button_style="info")
        self.btn_showlog = widgets.Button(description="Show Log / 履歴表示", button_style="")
        self.btn_clearlog = widgets.Button(description="Clear Log / 履歴消去", button_style="danger")

        self.btn_run.on_click(self.on_run)
        self.btn_reset.on_click(self.on_reset)
        self.btn_save.on_click(self.on_save_pdf)
        self.btn_showlog.on_click(self.on_show_log)
        self.btn_clearlog.on_click(self.on_clear_log)

        self.ui = self._build_ui()

    def _collect_params(self) -> Dict:
        return {
            "lin_reg_eps": float(self.lin_reg_eps.value),
            "q_strong": float(self.q_strong.value),
            "log_lr": float(self.log_lr.value),
            "log_iters": int(self.log_iters.value),
            "start_id": int(self.start_id.value),
            "end_id": int(self.end_id.value),
            "timeout_s": float(self.timeout_s.value),
            "sleep_s": float(self.sleep_s.value),
            "use_cache": int(self.use_cache.value),
            "seed": int(self.seed.value),
        }

    def _build_ui(self):
        box_model = widgets.VBox([widgets.HTML("<b>[Model Parameters]</b>"),
                                 self.lin_reg_eps, self.q_strong, self.log_lr, self.log_iters])
        box_data = widgets.VBox([widgets.HTML("<b>[Observed / Input Data]</b>"),
                                 self.start_id, self.end_id])
        box_env = widgets.VBox([widgets.HTML("<b>[Environment / Options]</b>"),
                                self.timeout_s, self.sleep_s, self.use_cache, self.seed])

        box_buttons = widgets.HBox([self.btn_run, self.btn_reset, self.btn_save, self.btn_showlog, self.btn_clearlog])
        return widgets.VBox([widgets.HBox([box_model, box_data, box_env]), box_buttons, self.out_main, self.out_plot])

    def on_run(self, _):
        with self.out_main:
            clear_output(wait=True)
            params = self._collect_params()
            np.random.seed(int(params["seed"]))

            ts = datetime.datetime.now().isoformat(timespec="seconds")
            print(f"実行開始 / Run started: {ts}")

            results = run_theoretical_model(params, cache=self.cache)
            if not results.get("ok", False):
                print(f"失敗 / Failed: {results.get('error', 'unknown')}")
                return

            key_outputs = {"rmse": float(results["rmse"]), "thr_total": float(results["thr_total"]), "acc": float(results["acc"]), "f1": float(results["f1"])}
            self.log.append({"timestamp": ts, "params": params, "key_outputs": key_outputs})
            print("ログ保存 / Log saved")

            with self.out_plot:
                clear_output(wait=True)
                update_plots(self.viz, results)

    def on_reset(self, _):
        self.lin_reg_eps.value = 1e-8
        self.q_strong.value = 0.25
        self.log_lr.value = 0.05
        self.log_iters.value = 2000
        self.start_id.value = 1
        self.end_id.value = 151
        self.timeout_s.value = 10.0
        self.sleep_s.value = 0.05
        self.use_cache.value = 1
        self.seed.value = 12345

        self.log = []
        self.viz = VizState()
        with self.out_plot:
            clear_output(wait=True)
        with self.out_main:
            clear_output(wait=True)
            print("リセット完了 / Reset completed")

    def on_save_pdf(self, _):
        with self.out_main:
            if self.viz.fig is None:
                print("警告: 図がありません。 / Warning: No figure.")
                return
            ts = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
            fn = f"sim_result_{ts}.pdf"
            try:
                self.viz.fig.savefig(fn, format="pdf")
                print(f"PDF保存 / PDF saved: {fn}")
                print("DL開始 / Starting download")
                files.download(fn)
            except Exception as e:
                print(f"保存失敗 / Save failed: {e}")

    def on_show_log(self, _):
        with self.out_main:
            print("------------------------------------------------------------")
            print("履歴表示 / Show Log")
            print("------------------------------------------------------------")
            if len(self.log) == 0:
                print("空 / Empty")
                return
            for i, it in enumerate(self.log, 1):
                print(f"[{i}] {it['timestamp']}")
                print("  params=" + json.dumps(it["params"], ensure_ascii=False))
                print("  key_outputs=" + json.dumps(it["key_outputs"], ensure_ascii=False))
            print("------------------------------------------------------------")

    def on_clear_log(self, _):
        self.log = []
        with self.out_main:
            print("履歴消去 / Log cleared")


# ============================================================
# 6. Initialization(初回 print_documentation 呼び出し+UI起動)
# ============================================================

print_documentation()
ui_app = SimulatorUI()
display(ui_app.ui)
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?