はじめに
$~$SPSS Modeler ストリームの引き継ぎ、困っていませんか?SPSS Modeler を使用していると、他者が作成した SPSS Modeler を使ったストリームを引き継ぐことがあります。
$~$SPSS Modeler に精通している人はストリームを見ればすぐに理解できる場合もありますが、SPSS Modeler の初心者の方が引き継ぐ場合は、一からストリームを理解するのは大変です。また、ストリームが大規模かつ複雑なものであった場合は、経験者の方でも読み解くのに時間がかかることでしょう。
$~$こんな時、ストリームの設計書が予め用意されていれば、引き継ぎの助けになるはずです。しかし、完成したストリームは試行錯誤の結果であることが多く、予め作った設計書通りにならないケースがあります。また、完成したストリーム用の設計書を後付けで作成するはずが、実際には作成されないケースも少なくありません。
$~$もし、ストリームの設計書を自動的に作成できれば、引き継ぎの負荷を下げることができるのではないでしょうか。そこで今回は、IBM Bob を使ってストリームから設計書を作成してみましたので、その結果を共有します。
IBM Bob によるSPSS Modeler の設計書自動作成
ストリームから Jython スクリプトの作成
あまり知られていませんが、SPSS Modeler では、作成したストリームから Jython スクリプトを生成することができます。以下の手順を行うことで、Jython スクリプトを取得できます。
- ストリームを構成する全てのノードを選択して、Ctl + C ( mac なら Command + C )を実行します(図1.)
- 右クリックを押して、ストリームのプロパティを開きます(図2.)
- 「実行」タブに移動します(図3.)
- 「スクリプト」が「Pyhon」になっていることを確認し、コード入力エリアにカーソルを合わせます
- Shift + Ctl + P (Shift + Command + P)を実行するとコードエリアに Jython コードが出力されます(図4.)
- コードエリアに書かれた Jython コードをカット・アンド・ペーストすることで、テキストエディタに保管できます
生成されたPython のサンプルは以下のとおりです。
variablefile_node1 = modeler.script.stream().createAt("variablefile", u"Credit Data", 102, 352)
# variablefile_node1 = modeler.script.stream().findByID("id8DN11F93C2XH")
# variablefile_node1 = modeler.script.stream().findByType("variablefile", u"Credit Data 2")
variablefile_node1.setPropertyValue("use_custom_name", True)
variablefile_node1.setPropertyValue("full_filename", u"~/work/ibm_bob/spss modeler/tree_credit_ja.csv")
variablefile_node1.setPropertyValue("custom_name", u"Credit Data 2")
variablefile_node1.setPropertyValue("encoding", u"UTF-8")
type_node1 = modeler.script.stream().createAt("type", u"Define Fields", 246, 352)
# type_node1 = modeler.script.stream().findByID("id5TN1ZM1L5IBT")
# type_node1 = modeler.script.stream().findByType("type", u"Define Fields2")
type_node1.setPropertyValue("metadata", [])
type_node1.setKeyedPropertyValue("role", u"クレジットカード", u"Input")
type_node1.setKeyedPropertyValue("role", u"年齢", u"Input")
type_node1.setKeyedPropertyValue("role", u"所得", u"Input")
type_node1.setKeyedPropertyValue("role", u"車ローン", u"Input")
type_node1.setKeyedPropertyValue("role", u"信用度", u"Target")
type_node1.setKeyedPropertyValue("role", u"教育", u"Input")
type_node1.setKeyedPropertyValue("values", u"クレジットカード", [1.0, 2.0])
type_node1.setKeyedPropertyValue("values", u"年齢", [20.003, 63.35])
type_node1.setKeyedPropertyValue("values", u"所得", [1.0, 2.0, 3.0])
type_node1.setKeyedPropertyValue("values", u"車ローン", [1.0, 2.0])
type_node1.setKeyedPropertyValue("values", u"信用度", [0.0, 1.0])
type_node1.setKeyedPropertyValue("values", u"教育", [1.0, 2.0])
type_node1.setKeyedPropertyValue("description", u"クレジットカード", u"クレジットカード保有状況")
type_node1.setKeyedPropertyValue("description", u"年齢", u"顧客の年齢")
type_node1.setKeyedPropertyValue("description", u"所得", u"所得レベル")
type_node1.setKeyedPropertyValue("description", u"車ローン", u"車ローン保有状況")
type_node1.setKeyedPropertyValue("description", u"信用度", u"信用度スコア")
type_node1.setKeyedPropertyValue("description", u"教育", u"教育レベル")
type_node1.setKeyedPropertyValue("type", u"クレジットカード", u"Set")
type_node1.setKeyedPropertyValue("type", u"年齢", u"Range")
type_node1.setKeyedPropertyValue("type", u"所得", u"Set")
type_node1.setKeyedPropertyValue("type", u"車ローン", u"Set")
type_node1.setKeyedPropertyValue("type", u"信用度", u"Flag")
type_node1.setKeyedPropertyValue("type", u"教育", u"Set")
type_node1.setKeyedPropertyValue("measure_type", u"クレジットカード", u"Set")
type_node1.setKeyedPropertyValue("measure_type", u"年齢", u"Range")
type_node1.setKeyedPropertyValue("measure_type", u"所得", u"Set")
type_node1.setKeyedPropertyValue("measure_type", u"車ローン", u"Set")
type_node1.setKeyedPropertyValue("measure_type", u"信用度", u"Flag")
type_node1.setKeyedPropertyValue("measure_type", u"教育", u"Set")
type_node1.setPropertyValue("custom_name", u"Define Fields2")
type_node1.setKeyedPropertyValue("max_list_length", u"クレジットカード", 0)
type_node1.setKeyedPropertyValue("max_list_length", u"年齢", 0)
type_node1.setKeyedPropertyValue("max_list_length", u"所得", 0)
type_node1.setKeyedPropertyValue("max_list_length", u"車ローン", 0)
type_node1.setKeyedPropertyValue("max_list_length", u"信用度", 0)
type_node1.setKeyedPropertyValue("max_list_length", u"教育", 0)
type_node1.setKeyedPropertyValue("field_metadata", u"クレジットカード", [u"Set", u"Input", u"Specify", [1.0, 2.0], [], 8, u"None", u"クレジットカード保有状況"])
type_node1.setKeyedPropertyValue("field_metadata", u"年齢", [u"Range", u"Input", u"Specify", [20.003, 63.35], [], 8, u"None", u"顧客の年齢"])
type_node1.setKeyedPropertyValue("field_metadata", u"所得", [u"Set", u"Input", u"Specify", [1.0, 2.0, 3.0], [], 8, u"None", u"所得レベル"])
type_node1.setKeyedPropertyValue("field_metadata", u"車ローン", [u"Set", u"Input", u"Specify", [1.0, 2.0], [], 8, u"None", u"車ローン保有状況"])
type_node1.setKeyedPropertyValue("field_metadata", u"信用度", [u"Flag", u"Target", u"Specify", [0.0, 1.0], [], 8, u"None", u"信用度スコア"])
type_node1.setKeyedPropertyValue("field_metadata", u"教育", [u"Set", u"Input", u"Specify", [1.0, 2.0], [], 8, u"None", u"教育レベル"])
type_node1.setPropertyValue("default_value_mode", u"Read")
type_node1.setKeyedPropertyValue("max_string_length", u"クレジットカード", 8)
type_node1.setKeyedPropertyValue("max_string_length", u"年齢", 8)
type_node1.setKeyedPropertyValue("max_string_length", u"所得", 8)
type_node1.setKeyedPropertyValue("max_string_length", u"車ローン", 8)
type_node1.setKeyedPropertyValue("max_string_length", u"信用度", 8)
type_node1.setKeyedPropertyValue("max_string_length", u"教育", 8)
type_node1.setPropertyValue("metadata_all", [])
type_node1.setKeyedPropertyValue("missing_values", u"クレジットカード", [])
type_node1.setKeyedPropertyValue("missing_values", u"年齢", [])
type_node1.setKeyedPropertyValue("missing_values", u"所得", [])
type_node1.setKeyedPropertyValue("missing_values", u"車ローン", [])
type_node1.setKeyedPropertyValue("missing_values", u"信用度", [])
type_node1.setKeyedPropertyValue("missing_values", u"教育", [])
type_node1.setPropertyValue("missing_metadata", [])
type_node1.setPropertyValue("use_custom_name", True)
partition_node1 = modeler.script.stream().createAt("partition", u"Train/Test Split", 402, 352)
# partition_node1 = modeler.script.stream().findByID("id2IH11CRMG7YB")
# partition_node1 = modeler.script.stream().findByType("partition", u"Train/Test Split2")
partition_node1.setPropertyValue("training_size", 70)
partition_node1.setPropertyValue("use_custom_name", True)
partition_node1.setPropertyValue("custom_name", u"Train/Test Split2")
partition_node1.setPropertyValue("testing_size", 30)
partition_node1.setPropertyValue("training_label", u"訓練")
applyc50_node1 = modeler.script.stream().createAt("applyc50", u"信用度", 546, 472)
# applyc50_node1 = modeler.script.stream().findByID("id3BH117Z5BKG9")
# applyc50_node1 = modeler.script.stream().findByType("applyc50", u"信用度2")
applyc50_node1.setPropertyValue("use_custom_name", True)
applyc50_node1.setPropertyValue("calculate_variable_importance", True)
applyc50_node1.setPropertyValue("custom_name", u"信用度2")
applyc50_node1.setPropertyValue("updating_node_id", u"id76T1ZB8UNHGB")
applyc50_node1.setPropertyValue("refresh_link_state", u"TurnOn")
applyc50_node1.setPropertyValue("builder_stream_id", u"id6MA118CNQDGE")
applyc50_node1.setPropertyValue("builder_node_id", u"id76T1ZB8UNHGB")
analysis_node1 = modeler.script.stream().createAt("analysis", u"Model Evaluation", 702, 457)
# analysis_node1 = modeler.script.stream().findByID("id7XZXXJP8R4R")
# analysis_node1 = modeler.script.stream().findByType("analysis", u"Model Evaluation2")
analysis_node1.setPropertyValue("use_custom_name", True)
analysis_node1.setPropertyValue("custom_name", u"Model Evaluation2")
table_node1 = modeler.script.stream().createAt("table", u"Results", 702, 352)
# table_node1 = modeler.script.stream().findByID("id2M7117NWHLQA")
# table_node1 = modeler.script.stream().findByType("table", u"Results2")
table_node1.setPropertyValue("use_custom_name", True)
table_node1.setPropertyValue("custom_name", u"Results2")
c50_node1 = modeler.script.stream().createAt("c50", u"C5.0 Model", 546, 352)
# c50_node1 = modeler.script.stream().findByID("id76T1ZB8UNHGB")
# c50_node1 = modeler.script.stream().findByType("c50", u"信用度2")
c50_node1.setPropertyValue("use_custom_name", True)
c50_node1.setPropertyValue("custom_name", u"信用度2")
modeler.script.stream().link(variablefile_node1, type_node1)
modeler.script.stream().link(type_node1, partition_node1)
modeler.script.stream().link(partition_node1, applyc50_node1)
modeler.script.stream().link(partition_node1, c50_node1)
modeler.script.stream().link(applyc50_node1, analysis_node1)
modeler.script.stream().link(applyc50_node1, table_node1)
Jython スクリプトから設計書の作成
設計書指示書の準備
SPSS Modeler から作成した、Jython スクリプトをインプットとして IBM Bob に設計書を作成させてみます。
どのような設計書を作成して欲しいかを指示書としてまとめました。
# Stream 設計書
## ストリーム概要
- ストリームの処理概要を記載。
## 使用するデータ項目
- 使用するデータ項目名と内容について記載。
## 使用する統計モデル
- 使用する統計モデルの名前と用途について記載。
## 個別ノードの処理
- ノードの名前と処理概要について記載。
IBM Bob へのプロンプト
設計指示書の準備ができたので、IBM Bob に対して以下のプロンプトを入力します。
設計指示書.md の記述に従って、stream_script.py の設計書を書いてください。
実行後、以下の結果が得られました。
# Stream 基本設計書
## ストリーム概要
本ストリームは、顧客の信用度を予測するための機械学習モデルを構築・評価するSPSS Modelerストリームです。クレジットカード保有状況、年齢、所得、車ローン、教育レベルなどの顧客属性データを入力として、C5.0決定木アルゴリズムを用いて信用度を予測します。データの読み込み、フィールド定義、訓練/テストデータの分割、モデル構築、予測、評価までの一連の処理を自動化します。
## 使用するデータ項目
| 項目名 | 役割 | データ型 | 値範囲 | 説明 |
|--------|------|----------|--------|------|
| クレジットカード | Input | Set | [1.0, 2.0] | クレジットカード保有状況 |
| 年齢 | Input | Range | [20.003, 63.35] | 顧客の年齢 |
| 所得 | Input | Set | [1.0, 2.0, 3.0] | 所得レベル |
| 車ローン | Input | Set | [1.0, 2.0] | 車ローン保有状況 |
| 教育 | Input | Set | [1.0, 2.0] | 教育レベル |
| 信用度 | Target | Flag | [0.0, 1.0] | 信用度スコア(予測対象) |
**データソース**: `~/work/ibm_bob/spss modeler/tree_credit_ja.csv` (UTF-8エンコーディング)
## 使用する統計モデル
### C5.0 決定木モデル
- **用途**: 顧客の信用度を分類予測
- **アルゴリズム**: C5.0決定木アルゴリズム
- **特徴**:
- 変数重要度の計算を有効化
- 訓練データとテストデータを70:30の比率で分割
- 5つの入力変数(クレジットカード、年齢、所得、車ローン、教育)から信用度を予測
## 個別ノードの処理
### 1. Credit Data (variablefile_node1)
- **ノードタイプ**: variablefile(データ読み込み)
- **処理概要**: CSVファイルから顧客データを読み込む
- **設定内容**:
- ファイルパス: `~/work/ibm_bob/spss modeler/tree_credit_ja.csv`
- エンコーディング: UTF-8
- カスタム名: "Credit Data 2"
### 2. Define Fields (type_node1)
- **ノードタイプ**: type(フィールド定義)
- **処理概要**: データ項目の役割、データ型、値範囲、説明を定義
- **設定内容**:
- 6つのフィールド(クレジットカード、年齢、所得、車ローン、教育、信用度)のメタデータを設定
- 信用度をTarget(目的変数)、その他をInput(説明変数)として定義
- 各フィールドの測定尺度(Set/Range/Flag)を指定
- カスタム名: "Define Fields2"
### 3. Train/Test Split (partition_node1)
- **ノードタイプ**: partition(データ分割)
- **処理概要**: データを訓練用とテスト用に分割
- **設定内容**:
- 訓練データ: 70%
- テストデータ: 30%
- 訓練ラベル: "訓練"
- カスタム名: "Train/Test Split2"
### 4. C5.0 Model (c50_node1)
- **ノードタイプ**: c50(モデル構築)
- **処理概要**: C5.0決定木アルゴリズムを用いてモデルを構築
- **設定内容**:
- カスタム名: "信用度2"
- 訓練データを使用してモデルを学習
### 5. 信用度 (applyc50_node1)
- **ノードタイプ**: applyc50(モデル適用)
- **処理概要**: 構築したC5.0モデルをテストデータに適用して予測を実行
- **設定内容**:
- 変数重要度の計算を有効化
- カスタム名: "信用度2"
- モデルノードID: "id76T1ZB8UNHGB"との連携
### 6. Model Evaluation (analysis_node1)
- **ノードタイプ**: analysis(モデル評価)
- **処理概要**: 予測結果を分析し、モデルの精度を評価
- **設定内容**:
- カスタム名: "Model Evaluation2"
- 予測値と実測値を比較して評価指標を算出
### 7. Results (table_node1)
- **ノードタイプ**: table(結果表示)
- **処理概要**: 予測結果をテーブル形式で表示
- **設定内容**:
- カスタム名: "Results2"
- 予測された信用度スコアと元データを表示
## ストリームフロー
Credit Data → Define Fields → Train/Test Split → C5.0 Model
↓
信用度(モデル適用)→ Model Evaluation
↓
Results
**処理の流れ**:
1. CSVファイルからデータを読み込み
2. フィールドの役割とデータ型を定義
3. データを訓練用(70%)とテスト用(30%)に分割
4. 訓練データでC5.0モデルを構築
5. テストデータにモデルを適用して予測
6. 予測結果を評価
7. 予測結果をテーブルで表示
まとめ
簡単なストリームではありますが、ストリームから Jython コードを生成し、そのコードから設計書(のようなもの)を作成することができました。
今回使用したプロンプトはシンプルなものですので、工夫の余地があるかと思いますが、参考になれば幸いです。