# 機能設計書 51-One-vs-Rest分類

## 概要

本ドキュメントは、Apache Spark MLlibが提供するOne-vs-Rest（OVR）分類機能の設計を記述する。本機能は、多クラス分類問題を複数の二値分類問題に分解するメタ学習アルゴリズムである。

### 本機能の処理概要

One-vs-Rest分類は、多クラス分類を効率的に解くための戦略的アプローチを提供する。k個のクラスが存在する場合、各クラスに対して「そのクラス vs 残り全てのクラス」という二値分類器を訓練し、予測時には全ての分類器のスコアを比較して最も高いスコアのクラスを選択する。

**業務上の目的・背景**：多くの二値分類アルゴリズム（ロジスティック回帰、SVM等）は、本質的に二値分類しかサポートしていない。しかし実務では3つ以上のクラスに分類する必要がある場面が多い。One-vs-Rest戦略により、任意の二値分類器を多クラス分類に拡張することが可能となる。

**機能の利用シーン**：テキスト分類（記事をスポーツ・政治・経済等に分類）、画像分類、障害カテゴリ分類など、クラス数が3以上の分類タスクで、既存の高性能な二値分類器を活用したい場合に利用される。

**主要な処理内容**：
1. ラベル列からクラス数を自動判定（メタデータまたは最大ラベル値から算出）
2. 各クラスについて、対象クラス=1、その他=0としたラベルを生成
3. 各クラスの二値分類器を並列に訓練（parallelismパラメータで制御可能）
4. 予測時は全モデルのraw predictionを取得し、最大値のクラスを予測結果とする

**関連システム・外部連携**：任意のClassifierサブクラス（LogisticRegression, LinearSVC等）をベース分類器として利用可能。MLWritable/MLReadableインターフェースによるモデルの永続化に対応。

**権限による制御**：特になし。Sparkの標準的なアクセス制御に従う。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | 本機能に直接関連する画面はなし |

## 機能種別

計算処理（機械学習 - 多クラス分類のためのメタ学習）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| classifier | Classifier | Yes | ベースとなる二値分類器 | MLWritableを実装していること |
| labelCol | String | No | ラベル列名（デフォルト: "label"） | スキーマに存在すること |
| featuresCol | String | No | 特徴量列名（デフォルト: "features"） | スキーマに存在すること |
| predictionCol | String | No | 予測列名（デフォルト: "prediction"） | - |
| rawPredictionCol | String | No | 生予測列名（デフォルト: "rawPrediction"） | - |
| parallelism | Int | No | 並列訓練数（デフォルト: 1） | 1以上 |
| weightCol | String | No | 重み列名 | ベース分類器がHasWeightColの場合のみ有効 |

### 入力データソース

DataFrame形式のデータセット。ラベル列は0から始まる連続整数、特徴量列はVector型。

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| prediction | Double | 予測クラスラベル（最大スコアのクラスインデックス） |
| rawPrediction | Vector | 各クラスの二値分類器の生予測スコアベクトル |

### 出力先

入力DataFrameに予測列を追加して返却。モデルはMLWriter経由でHDFS等に永続化可能。

## 処理フロー

### 処理シーケンス

```
1. スキーマ検証
   └─ transformSchemaでラベル・特徴量列の型を検証
2. クラス数の判定
   └─ メタデータから取得、またはラベル列の最大値+1で計算
3. データ準備
   └─ ラベル・特徴量・（重み）列を選択、必要に応じてキャッシュ
4. 各クラスの二値分類器訓練（並列実行可能）
   └─ クラスiに対して: label==i -> 1.0, otherwise -> 0.0
   └─ ベース分類器のコピーにparamMapを設定してfit
5. モデル生成
   └─ OneVsRestModelにラベルメタデータとモデル配列を格納
```

### フローチャート

```mermaid
flowchart TD
    A[開始: fit] --> B[スキーマ検証]
    B --> C[クラス数判定]
    C --> D[weightCol使用判定]
    D --> E[学習データ選択・キャッシュ]
    E --> F{各クラスの訓練<br/>並列実行}
    F --> G[クラス0: 二値分類器訓練]
    F --> H[クラス1: 二値分類器訓練]
    F --> I[クラスk-1: 二値分類器訓練]
    G --> J[全モデル収集]
    H --> J
    I --> J
    J --> K[OneVsRestModel生成]
    K --> L[終了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-51-01 | クラス番号 | ラベルは0から始まる連続整数であること | 訓練時 |
| BR-51-02 | 最小モデル数 | モデル配列は1つ以上のモデルを含むこと | モデル生成時 |
| BR-51-03 | 重み列無視 | ベース分類器がHasWeightColでない場合、weightColは無視される | 訓練時 |
| BR-51-04 | 予測ルール | 全クラスの生予測スコアのうち最大値のクラスインデックスを予測値とする | 予測時 |

### 計算ロジック

- 訓練時ラベル変換: `when(label === classIndex, 1.0).otherwise(0.0)`
- 予測: `array_argmax(rawPredictions)` で最大スコアのクラスインデックスを選択
- raw prediction: 各モデルのclass=1に対する生予測スコア（`vector_get(rawPrediction, 1)`）を収集

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

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

本機能はデータベース操作を行わない。データはSpark DataFrame上で処理される。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | UnsupportedOperationException | ベース分類器がMLWritableを実装していない場合（保存時） | MLWritable実装の分類器を使用 |
| - | UnsupportedOperationException | ローカルファイルシステムへの保存/読込時 | HDFS等の分散ファイルシステムを使用 |
| - | RequireError | モデル配列が空の場合 | 1つ以上のクラスを持つデータを使用 |

### リトライ仕様

特になし。各二値分類器の訓練はベース分類器のリトライ仕様に依存する。

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

Sparkの遅延評価に基づく。明示的なトランザクション管理は行わない。

## パフォーマンス要件

- parallelismパラメータにより、複数クラスの訓練を並列実行可能
- 入力データセットが非永続の場合、自動的にMEMORY_AND_DISKレベルでキャッシュ
- ストリーミングデータの場合はキャッシュを行わない

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

Sparkの標準的なセキュリティ機構に従う。特別なセキュリティ要件はなし。

## 備考

- Spark 1.4.0で導入、Spark 2.0.0でMLWritable対応
- ベース分類器のinput/output列名はOneVsRest側の設定で上書きされる
- ProbabilisticClassificationModelの場合、probabilityCol出力を抑制する

---

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

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

### 推奨読解順序

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

まず、OneVsRest関連の型定義とパラメータ構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | OneVsRest.scala | `mllib/src/main/scala/org/apache/spark/ml/classification/OneVsRest.scala` | ClassifierTypeTrait（47-55行目）で使用される存在型の理解 |
| 1-2 | OneVsRest.scala | 同上 | OneVsRestParams（60-73行目）でclassifierパラメータの定義を確認 |

**読解のコツ**: Scalaの存在型（forSome構文）が使用されており、任意のClassifierサブタイプを受け入れるための仕組みである。

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

訓練処理のエントリーポイントであるfit()メソッドを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | OneVsRest.scala | `mllib/src/main/scala/org/apache/spark/ml/classification/OneVsRest.scala` | OneVsRest.fit()（388-470行目）が訓練のエントリーポイント |

**主要処理フロー**:
1. **388行目**: `fit`メソッド開始、instrumented計装付き
2. **399-404行目**: クラス数の判定（メタデータ優先、なければ最大値+1）
3. **407-415行目**: weightCol使用可否の判定
4. **432-452行目**: 各クラスの二値分類器をFuture経由で並列訓練
5. **453-454行目**: 全Futureの完了を待機してモデル配列を取得
6. **463-469行目**: ラベルメタデータを構築しOneVsRestModelを生成

#### Step 3: 予測処理を理解する

OneVsRestModel.transform()による予測処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | OneVsRest.scala | 同上 | OneVsRestModel.transform()（179-246行目）で予測処理のフロー確認 |

**主要処理フロー**:
- **196行目**: アキュムレータ列（空配列）を追加
- **206-219行目**: foldLeftで各モデルの予測を順次追加（raw predictionのclass=1スコアを蓄積）
- **228-241行目**: rawPredictionCol（ベクトル化）とpredictionCol（argmax）を生成

#### Step 4: 永続化処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | OneVsRest.scala | 同上 | OneVsRestParams.saveImpl/loadImpl（94-122行目）でモデルの保存・読込処理を確認 |
| 4-2 | OneVsRest.scala | 同上 | OneVsRestModelWriter（275-293行目）で各サブモデルの保存処理を確認 |

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

```
OneVsRest.fit(dataset)
    |
    +-- transformSchema(schema)
    |       +-- validateAndTransformSchema()
    |
    +-- MetadataUtils.getNumClasses(labelSchema)
    |
    +-- [並列] Range(0, numClasses).map { index =>
    |       +-- classifier.fit(trainingDataset, paramMap)
    |   }
    |
    +-- ThreadUtils.awaitResult(future, Duration.Inf)
    |
    +-- new OneVsRestModel(uid, labelMetadata, models)

OneVsRestModel.transform(dataset)
    |
    +-- transformSchema(schema)
    |
    +-- models.foldLeft(newDataset) { (df, model) =>
    |       +-- tmpModel.transform(df)
    |       +-- array_append(accCol, vector_get(rawPred, 1))
    |   }
    |
    +-- array_to_vector(accCol)  [rawPrediction]
    +-- array_argmax(accCol)     [prediction]
```

### データフロー図

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

DataFrame            OneVsRest.fit()
  (label,            +-- クラス数判定                  OneVsRestModel
   features,         +-- 各クラスの二値化                (models[k],
   weight)           +-- 並列にk個の二値分類器訓練        labelMetadata)

DataFrame            OneVsRestModel.transform()
  (features)         +-- 各モデルのraw prediction取得    DataFrame
                     +-- accumulatorで結合               (prediction,
                     +-- argmaxで最大クラス選択            rawPrediction)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| OneVsRest.scala | `mllib/src/main/scala/org/apache/spark/ml/classification/OneVsRest.scala` | ソース | OneVsRest Estimator/Model/Params の全実装 |
| Classifier.scala | `mllib/src/main/scala/org/apache/spark/ml/classification/Classifier.scala` | ソース | ベース分類器の抽象クラス定義 |
| ClassificationModel.scala | `mllib/src/main/scala/org/apache/spark/ml/classification/ClassificationModel.scala` | ソース | 分類モデルの基底クラス |
| DefaultParamsWriter.scala | `mllib/src/main/scala/org/apache/spark/ml/util/ReadWrite.scala` | ソース | モデルの保存・読込ユーティリティ |
