# 機能設計書 37-自動微分（Backpropagation）

## 概要

本ドキュメントは、TensorFlowにおける自動微分（逆方向モード / バックプロパゲーション）機能の設計を記載する。`tf.GradientTape` クラスを中心とするテープベースの自動微分機構であり、`tensorflow/python/eager/backprop.py` に実装される。

### 本機能の処理概要

**業務上の目的・背景**：ニューラルネットワークの訓練において、損失関数のパラメータに対する勾配を計算する必要がある。自動微分は、計算グラフを記録し、連鎖律に基づいて勾配を自動的に計算する機構である。TensorFlow 2.xではEagerモードをサポートする `GradientTape` が標準的な方法となっている。

**機能の利用シーン**：モデルの訓練ループにおいて、損失の計算とパラメータ更新の間に勾配を取得するために使用される。`with tf.GradientTape() as tape:` ブロック内で順伝播を実行し、`tape.gradient()` で勾配を取得する。

**主要な処理内容**：
1. `GradientTape` コンテキストマネージャで操作を記録するテープを管理
2. `watch()` メソッドで監視対象のテンソル/変数を指定
3. `gradient()` メソッドで目的テンソルのソーステンソルに対する勾配を計算
4. `jacobian()` メソッドでヤコビアン行列を計算
5. 高階微分はテープのネストにより実現
6. `persistent=True` で複数回の勾配計算を許可

**関連システム・外部連携**：C++で実装された `pywrap_tfe` モジュール（テープ管理）、`imperative_grad` モジュール（勾配計算エンジン）に依存。

**権限による制御**：特になし。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | 該当なし | - | 画面機能マッピングに関連画面なし |

## 機能種別

計算処理（自動微分）

## 入力仕様

### GradientTapeコンストラクタ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| persistent | bool | No | 複数回gradient()を呼べるか。デフォルトFalse | - |
| watch_accessed_variables | bool | No | 訓練可能変数を自動監視するか。デフォルトTrue | - |

### gradient() メソッド

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| target | Tensor / list | Yes | 微分対象（通常はスカラー損失） | - |
| sources | Tensor / Variable / list | Yes | 微分変数 | - |
| output_gradients | Tensor / list | No | 出力勾配の初期値 | targetと同じ形状 |
| unconnected_gradients | UnconnectedGradients | No | 未接続勾配の扱い。デフォルトNONE | NONE or ZERO |

### watch() メソッド

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| tensor | Tensor / Variable / list | Yes | 監視対象 | float/complex型のみ微分可能 |

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| gradients | Tensor / list[Tensor] / None | ソースに対する勾配。接続がない場合はNone |

### 出力先

呼び出し元に勾配テンソルとして返却。

## 処理フロー

### 処理シーケンス

```
1. GradientTape.__enter__: テープをスタックにプッシュ
   └─ tape.push_new_tape(persistent, watch_accessed_variables)
2. watch(): テンソル/変数を監視対象に追加
   └─ tape.watch() / tape.watch_variable()
3. 順伝播: コンテキスト内の操作が自動的にテープに記録
   └─ pywrap_tfe.TFE_Py_RecordGradient()
4. GradientTape.__exit__: テープをスタックからポップ
   └─ tape.pop_tape()
5. gradient(): 勾配計算
   └─ imperative_grad.imperative_grad()で逆方向に辿る
   └─ _gradient_function()で各Opの勾配関数を呼び出し
```

### フローチャート

```mermaid
flowchart TD
    A[with GradientTape as tape] --> B[_push_tape: テープ作成・スタックプッシュ]
    B --> C[tape.watch: 監視対象登録]
    C --> D[順伝播: 操作がテープに記録]
    D --> E[__exit__: _pop_tape]
    E --> F[tape.gradient: 勾配計算要求]
    F --> G[imperative_grad.imperative_grad]
    G --> H[各OpのGrad関数を逆順に呼び出し]
    H --> I[勾配テンソルを返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-37-1 | テープライフサイクル | persistent=Falseの場合、gradient()は1回のみ呼び出し可能 | gradient()呼び出し時 |
| BR-37-2 | 自動監視 | watch_accessed_variables=Trueの場合、trainable=Trueの変数は自動監視 | コンテキスト内のVariable操作時 |
| BR-37-3 | 型制約 | float/complex型のテンソルのみ微分可能。他の型はNone返却 | watch()およびgradient()時 |
| BR-37-4 | 高階微分 | テープのネストにより高階微分を計算可能 | 内側のtape.gradient()が外側のテープで記録される |
| BR-37-5 | 未接続勾配 | unconnected_gradients=ZEROの場合、接続のない変数にはゼロ勾配を返す | gradient()呼び出し時 |
| BR-37-6 | 勾配関数解決 | ops._gradient_registryからOp名に対応する勾配関数をルックアップ | 各Opの逆方向パス |

### 計算ロジック

連鎖律に基づく逆方向自動微分:
```
dy/dx = dy/dz1 * dz1/dz2 * ... * dzn/dx
```
各Opの勾配関数は `@ops.RegisterGradient` で登録され、`_gradient_function` から呼び出される。

## データベース操作仕様

### 操作別データベース影響一覧

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| - | - | - | データベース操作なし |

### テーブル別操作詳細

データベース操作は発生しない。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| RuntimeError | テープ再入 | recording中のテープに再度__enter__ | 新しいテープを作成 |
| ValueError | 未監視変数 | implicit_val_and_gradで変数がない場合 | watchで監視対象を指定 |
| ValueError | Noneリターン | 微分対象関数がNoneを返した場合 | 値を返すようにする |
| LookupError | 未登録勾配 | Opの勾配関数が未登録 | RegisterGradientで登録 |

### リトライ仕様

リトライは不要。

## トランザクション仕様

特になし。テープは個別のスレッドで独立して動作する。

## パフォーマンス要件

- テープ操作のオーバーヘッドは最小（C++実装）
- persistent=Trueの場合、テープが保持され続けるためメモリ消費が増加
- stop_recordingコンテキストマネージャでメモリ最適化が可能

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

特になし。

## 備考

- _op_attr_type_cache によりOp属性型のキャッシュを行い、パフォーマンスを向上
- _MockOpクラスはEagerモードで勾配関数に渡すための擬似Opオブジェクト

---

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

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

### 推奨読解順序

#### Step 1: テープ管理の仕組み

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | backprop.py | `tensorflow/python/eager/backprop.py` | GradientTapeクラスの構造 |

**主要処理フロー**:
1. **704行目**: `@tf_export("GradientTape", "autodiff.GradientTape")` でエクスポート
2. **801-819行目**: `__init__`でpersistent, watch_accessed_variablesを設定
3. **821-824行目**: `__enter__`で_push_tape呼び出し
4. **826-829行目**: `__exit__`で_pop_tape呼び出し
5. **831-842行目**: `_push_tape`でtape.push_new_tapeを呼び出し
6. **864-884行目**: `watch`メソッドでテンソル/変数を監視対象に登録

**読解のコツ**: テープはスタック構造で管理される。最も内側のテープが操作を記録し、外側のテープは内側の勾配計算自体を記録できる（高階微分）。

#### Step 2: 勾配関数の呼び出しメカニズム

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | backprop.py | `tensorflow/python/eager/backprop.py` | _gradient_function |

**主要処理フロー**:
- **118-150行目**: `_gradient_function` - Op名から勾配関数をルックアップし実行
- **136行目**: `_MockOp` でEagerモードの擬似Op作成
- **137行目**: `ops._gradient_registry.lookup(op_name)` で勾配関数取得
- **142-150行目**: ControlFlowV2判定とname_scope付与

#### Step 3: 低レベルの勾配計算エンジン

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | imperative_grad.py | `tensorflow/python/eager/imperative_grad.py` | imperative_grad関数 |

**読解のコツ**: `imperative_grad` はC++の `pywrap_tfe.TFE_Py_TapeGradient` を呼び出す薄いPythonラッパー。実際の逆方向トラバーサルはC++で実装されている。

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

```
tf.GradientTape (backprop.py)
    |
    +-- __enter__ / __exit__
    |       +-- tape.push_new_tape / tape.pop_tape (pywrap_tfe)
    |
    +-- watch(tensor)
    |       +-- tape.watch / tape.watch_variable (pywrap_tfe)
    |
    +-- gradient(target, sources)
    |       +-- imperative_grad.imperative_grad()
    |           +-- pywrap_tfe.TFE_Py_TapeGradient()  [C++]
    |               +-- _gradient_function(op_name, ...)
    |                   +-- ops._gradient_registry.lookup(op_name)
    |                   +-- grad_fn(mock_op, *out_grads)
    |
    +-- jacobian(target, sources)
    |       +-- parallel_for.pfor / batch_jacobian
    |
    +-- stop_recording()
            +-- _pop_tape / _push_tape
```

### データフロー図

```
[順伝播]                    [テープ記録]              [逆方向]

input tensor -------> Op実行 -> テープに記録 ------> 勾配関数呼び出し
      |                  |                              |
      v                  v                              v
output tensor       (inputs, attrs, outputs)     dL/dinputs
      |                                              |
      v                                              v
loss (scalar) -----------------------------------> tape.gradient()
                                                     |
                                                     v
                                                 勾配テンソル
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| backprop.py | `tensorflow/python/eager/backprop.py` | ソース | GradientTapeクラスの主実装 |
| tape.py | `tensorflow/python/eager/tape.py` | ソース | テープ管理のPythonインターフェース |
| imperative_grad.py | `tensorflow/python/eager/imperative_grad.py` | ソース | 勾配計算エンジン（C++ラッパー） |
| pywrap_tfe.py | `tensorflow/python/pywrap_tfe.py` | バインディング | C++テープ実装へのバインディング |
| backprop_util.py | `tensorflow/python/eager/backprop_util.py` | ソース | IsTrainable等のユーティリティ |
| default_gradient.py | `tensorflow/python/ops/default_gradient.py` | ソース | zeros_like, ones_likeデフォルト勾配 |
