# 通知設計書 2-TerminateOnNaN

## 概要

本ドキュメントは、TensorFlow Kerasの`TerminateOnNaN`コールバックに関する通知設計書である。`TerminateOnNaN`はNaN損失検出時にトレーニングを終了させる通知を発行する。

### 本通知の処理概要

`TerminateOnNaN`は、各バッチの終了時に損失値を検査し、NaN（非数）またはInf（無限大）を検出した場合にトレーニングを即座に停止させるコールバックである。

**業務上の目的・背景**：機械学習モデルのトレーニング中、数値的不安定性（学習率が高すぎる、データに異常値がある等）により損失がNaNやInfになることがある。そのまま学習を続けても意味がなく、計算リソースが無駄になる。`TerminateOnNaN`はこの状況を早期に検出し、不要な計算を防止する。

**通知の送信タイミング**：各バッチ終了時（`on_batch_end`）に`logs`辞書から`loss`値を取得し、NaN/Infであるかを検査する。

**通知の受信者**：Kerasモデル（`self.model`）。`model.stop_training = True`を設定することで、トレーニングループに停止を通知する。また、標準出力にメッセージを出力することでユーザにも通知する。

**通知内容の概要**：無効な損失値が検出されたこと、および該当バッチ番号。

**期待されるアクション**：トレーニングの即時停止。ユーザは学習率の調整、データの確認、モデル構造の見直し等を行う。

## 通知種別

アプリ内通知（`model.stop_training`フラグ設定 + 標準出力への出力）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期 |
| 優先度 | 高（異常検出による即座の停止） |
| リトライ | 無し |

### 送信先決定ロジック

常にトレーニング中のモデル（`self.model`）に対して`stop_training = True`を設定する。

## 通知テンプレート

### メール通知の場合

該当なし

### 本文テンプレート

```
Batch %d: Invalid loss, terminating training
```

### 添付ファイル

該当なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| batch | バッチ番号 | on_batch_endの引数 | Yes |
| loss | 損失値 | logs.get('loss') | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| バッチ終了 | on_batch_end | np.isnan(loss) or np.isinf(loss) | NaNまたはInfの損失検出時 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| loss is None | logsにlossキーが存在しない場合はスキップ |
| lossが正常値 | NaN/Infでない正常な数値の場合はスキップ |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[on_batch_end] --> B[logsからloss取得]
    B --> C{loss is not None?}
    C -->|No| D[スキップ]
    C -->|Yes| E[sync_to_numpy_or_python_type変換]
    E --> F{np.isnan or np.isinf?}
    F -->|No| G[正常終了]
    F -->|Yes| H[メッセージ出力]
    H --> I[model.stop_training = True]
    I --> J[トレーニング停止]
```

## データベース参照・更新仕様

### 参照テーブル一覧

該当なし（インメモリ処理のみ）

### 更新テーブル一覧

該当なし（インメモリ処理のみ）

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| 型変換エラー | lossがテンソルでnumpy変換に失敗 | tf_utils.sync_to_numpy_or_python_typeで安全に変換 |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 該当なし |
| リトライ間隔 | 該当なし |
| リトライ対象エラー | 該当なし |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | 制限なし（1回のみ発火） |
| 1日あたり上限 | 制限なし |

### 配信時間帯

制限なし（トレーニング実行中に随時）

## セキュリティ考慮事項

特になし。損失値のみを扱い、個人情報や機密データは含まない。

## 備考

- `_supports_tf_logs = True`が設定されており、TensorFlowテンソルのlogsを直接受け取れる
- `sync_to_numpy_or_python_type`で明示的にnumpy変換を行ってからNaN/Inf判定する
- 1回のNaN/Inf検出でトレーニングが停止するため、一時的な異常にも反応する

---

## コードリーディングガイド

本通知を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | callbacks.py | `tensorflow/python/keras/callbacks.py` | `TerminateOnNaN`クラス定義（行950-965）。シンプルな構造で`_supports_tf_logs = True`のみがインスタンス変数 |

**読解のコツ**: `_supports_tf_logs = True`はバッチフック呼び出し時にTensorFlowテンソルのまま受け取れることを意味する。ただし、NaN判定前にnumpy変換が必要。

#### Step 2: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | callbacks.py | `tensorflow/python/keras/callbacks.py` | `on_batch_end`メソッド（行958-965）。全処理がこの1メソッドに集約 |

**主要処理フロー**:
1. **行959**: `logs = logs or {}` でNone対策
2. **行960**: `loss = logs.get('loss')` でloss値を取得
3. **行962**: `loss = tf_utils.sync_to_numpy_or_python_type(loss)` でnumpy変換
4. **行963**: `np.isnan(loss) or np.isinf(loss)` でNaN/Inf判定
5. **行964**: `print`でメッセージ出力
6. **行965**: `self.model.stop_training = True` でトレーニング停止

#### Step 3: TFログサポートの仕組みを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | callbacks.py | `tensorflow/python/keras/callbacks.py` | `CallbackList._process_logs`（行282-290）。`_supports_tf_logs`フラグによるログ変換のスキップ |
| 3-2 | tf_utils.py | `tensorflow/python/keras/utils/tf_utils.py` | `sync_to_numpy_or_python_type`関数。TFテンソルからnumpy/Pythonスカラーへの変換 |

### プログラム呼び出し階層図

```
model.fit() [バッチループ]
    |
    +-- CallbackList.on_batch_end()
            +-- CallbackList._call_batch_hook('end')
                    +-- TerminateOnNaN.on_batch_end(batch, logs)
                            +-- logs.get('loss')
                            +-- tf_utils.sync_to_numpy_or_python_type(loss)
                            +-- np.isnan(loss) / np.isinf(loss)
                            +-- model.stop_training = True  [NaN/Inf検出時]
```

### データフロー図

```
[入力]                   [処理]                         [出力]

バッチ終了時のlogs  ---> TerminateOnNaN.on_batch_end() ---> model.stop_training = True
  {loss: NaN}            1. loss値取得                        (トレーニング停止)
                         2. numpy変換
                         3. NaN/Inf判定                 ---> 標準出力メッセージ
                                                              "Batch N: Invalid loss..."
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| callbacks.py | `tensorflow/python/keras/callbacks.py` | ソース | TerminateOnNaNクラス定義 |
| tf_utils.py | `tensorflow/python/keras/utils/tf_utils.py` | ソース | sync_to_numpy_or_python_type変換関数 |
