# tkbo 使用説明書

## 概要

**tkbo** は、離散候補点集合に対するベイズ最適化・適応学習を行うための自作 Python パッケージである。

設計方針は以下である。

- `tkmlr` は回帰モデルの統一インターフェースを担当する。
- `tkbo` は次に評価すべき候補点を選ぶ適応学習を担当する。
- PHYSBO は標準探索 backend としても、posterior mean/std を返す surrogate としても使える。
- sklearn GPR など他の surrogate にも差し替えられる。
- acquisition 関数を自作・登録できる。
- `model` または `backend` パラメータで BO backend を切り替えられる。

---

## 基本概念

tkbo は以下の 3 層で構成される。

```text
surrogate    : f(x) の予測平均・標準偏差を返すモデル
acquisition  : 候補点ごとの有望度 score を計算する関数
backend      : ask/tell ループを管理する最適化器
```

---

## 主な backend

| model/backend | 内容 |
|---|---|
| `physbo` | PHYSBO native backend。PHYSBO の `bayes_search` を使う |
| `sklearn_gpr` | custom backend + sklearn GPR surrogate |
| `physbo_gp` | custom backend + PHYSBO surrogate |
| `custom` | surrogate と acquisition を明示指定 |
| `random` | ランダム探索 |
| `grid` | 未観測点を順に返す |

---

## 主な surrogate

| surrogate | 内容 |
|---|---|
| `sklearn_gpr` | sklearn GaussianProcessRegressor |
| `physbo_gp` | PHYSBO posterior mean/std wrapper |

---

## 主な acquisition

| acquisition | 内容 |
|---|---|
| `ei` | Expected Improvement |
| `pi` | Probability of Improvement |
| `ucb` | Upper Confidence Bound |
| `lcb` | Lower Confidence Bound |
| `stein` | Stein-lite score: uncertainty × local sensitivity |
| `entropy` | log(std) に基づく探索 score |

---

## 基本的な使い方

```python
import numpy as np
from tkbo import create_optimizer

X = np.linspace(0, 10, 101).reshape(-1, 1)
y = np.full(len(X), np.nan)

# 初期観測
idx0 = [0, 30, 80]
y[idx0] = np.sin(X[idx0, 0])

opt = create_optimizer(
    model="sklearn_gpr",
    acquisition="ei",
    surrogate__n_restarts_optimizer=10,
    random_seed=1,
)

opt.initialize(X, y=y)

result = opt.ask(n_points=1)
idx_next = result.indices[0]

print("next index:", idx_next)
print("next X:", result.X[0])
```

---

## ask/tell ループ

```python
for step in range(10):
    result = opt.ask(n_points=1)
    idx = result.indices[0]
    x = X[idx]

    # 実験またはシミュレーション
    y_new = experiment(x)

    opt.tell(idx, y_new)
```

---

## PHYSBO native backend

PHYSBO の標準 `bayes_search` をそのまま使う。

```python
opt = create_optimizer(
    model="physbo",
    score_mode="EI",
    num_rand_basis=200,
    interval=0,
    random_seed=1,
)

opt.initialize(X, y=y)
result = opt.ask(n_points=1)
```

注意：

- PHYSBO native backend では、利用できる score は PHYSBO 側の実装に依存する。
- 独自 acquisition を使いたい場合は `model="physbo_gp"` または `model="sklearn_gpr"` を使う。

---

## PHYSBO surrogate + 自作 acquisition

PHYSBO を posterior mean/std を返す回帰器として使い、score 計算は tkbo 側で行う。

```python
opt = create_optimizer(
    model="physbo_gp",
    acquisition="stein",
    surrogate__num_rand_basis=400,
    surrogate__interval=0,
    surrogate__score_mode="EI",
)

opt.initialize(X, y=y)
result = opt.ask(n_points=1)
```

---

## sklearn GPR surrogate

sklearn GaussianProcessRegressor を surrogate として使う。

```python
opt = create_optimizer(
    model="sklearn_gpr",
    acquisition="ucb",
    acquisition__kappa=2.0,
    surrogate__alpha=1.0e-8,
    surrogate__n_restarts_optimizer=20,
)

opt.initialize(X, y=y)
result = opt.ask(n_points=1)
```

sklearn GPR は `fit()` 時に対数周辺尤度を最大化して、カーネルのハイパーパラメータを最適化する。

---

## Stein-lite score

`stein` は厳密な Stein variational 法ではなく、実務向けの探索 heuristic である。

```text
score = std_weight * std + grad_weight * std * ||grad mean||
```

ここで `grad mean` は surrogate の予測平均を有限差分して求める。

```python
opt = create_optimizer(
    model="sklearn_gpr",
    acquisition="stein",
    acquisition__eps=1.0e-5,
    acquisition__std_weight=1.0,
    acquisition__grad_weight=1.0,
)
```

---

## Excel/CSV CLI

```bash
python -m tkbo.cli --mode ask --infile data.xlsx --target "max:y" --features x1,x2 --model sklearn_gpr --acquisition ei
```

PHYSBO native:

```bash
python -m tkbo.cli --mode ask --infile data.xlsx --target "max:y" --features x1,x2 --model physbo --acquisition EI
```

PHYSBO surrogate + Stein-lite:

```bash
python -m tkbo.cli --mode ask --infile data.xlsx --target "max:y" --features x1,x2 --model physbo_gp --acquisition stein
```

出力ファイル：

```text
data_tkbo_ask.xlsx
```

には以下が追加される。

| 列名 | 内容 |
|---|---|
| `tkbo_pred_mean` | 予測平均 |
| `tkbo_pred_std` | 予測標準偏差 |
| `tkbo_observed` | 観測済みか |
| `tkbo_suggested` | 次候補として選ばれたか |

---

## 新しい acquisition の追加

```python
import numpy as np
from tkbo.base import BaseAcquisition
from tkbo.registry import register_acquisition

class MyScore(BaseAcquisition):
    name = "myscore"

    def __call__(self, X, mean, std, y_observed=None, observed_mask=None, maximize=True, **kwargs):
        return mean + 3.0 * std

register_acquisition("myscore", MyScore)
```

使用：

```python
opt = create_optimizer(model="sklearn_gpr", acquisition="myscore")
```

---

## 新しい surrogate の追加

```python
from tkbo.base import BaseSurrogate
from tkbo.registry import register_surrogate

class MySurrogate(BaseSurrogate):
    def fit(self, X, y):
        ...
        return self

    def predict(self, X, return_std=False):
        ...
        if return_std:
            return mean, std
        return mean

register_surrogate("my_surrogate", MySurrogate)
```

使用：

```python
opt = create_optimizer(
    model="custom",
    surrogate="my_surrogate",
    acquisition="ei",
)
```

---

## 設計上の注意

### PHYSBO と custom acquisition

PHYSBO の `bayes_search(score="EI")` を直接使う場合、score は PHYSBO 側で定義されたものに限られる。

独自 score を使う場合は、以下の流れにする。

```text
PHYSBO / sklearn GPR で posterior mean/std を計算
tkbo acquisition で score を計算
score 最大の候補点を選ぶ
```

---

### refit_each_tell

`CustomOptimizer` は、`tell()` のたびに surrogate を作り直す。

これは内部状態の不整合を避けるための安全な実装である。

候補点数が非常に大きい場合は、将来的に incremental update 対応 backend を追加する。

---

## まとめ

tkbo は以下を目的とする。

```text
「候補点集合から次に評価すべき点を選ぶ」
```

そのために、

```text
surrogate + acquisition + backend
```

を分離し、PHYSBO だけでなく sklearn GPR や将来の BoTorch / Optuna なども取り込めるようにする。