SPSS Modelerで最適なクラスター数(のヒント)を探してみる その３

前回、前々回の記事からの続きです。

https://qiita.com/harrowharrow16/items/57b6696193aeb9faf546

https://qiita.com/harrowharrow16/items/1ad1c815a42bd8dd65c2

さて、前回はSPSS Modelerの拡張ノードを利用してシルエットスコアや誤差平方和（SSE）を計算してみました。また、拡張モデルノードを使ってモデル作成部分も実装したので紹介します。

**※ 2025/03/11 GitHubにストリームとデータをアップしました。**

https://github.com/harrow16/Qiita/tree/main/Clustering_Part3_0819



## SPSS Modelerの拡張ノード
　
さて今回も、SPSS ModelerでRやPythonのシンタックスを記述してデータ加工やモデル作成が行える拡張ノードを利用します。以下のように5種類あります。今回は、拡張モデルノードを利用してモデル作成及びクラスタリングの結果を取得してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3839172/53810fec-9e96-818b-4f27-52a5650ae3e9.png)


今回もv18.4以前のユーザーでも参考になるように、Python for Sparkでやってみます。　v18.4以前とv18.5の違いはネイティブPythonが利用できるようになったことです。詳しくは以下のページを参照してください。

https://qiita.com/spssfun2017/items/b619d5e3db8bb1ff816e



## 拡張モデルノードについて

拡張モデルノードは拡張の出力ノードと同様にR、Python for Spark、Pythonからシンタックスを選択できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3839172/ee49600a-c5ea-ce3d-4acc-6c12d7b0b696.png)

「モデル作成シンタックス」と「モデルスコアリングシンタックス」の２つを記述する必要があります。正確にはモデルスコアリングシンタックスは必須ではないのですが、ここで記述しておくと便利なので、拡張モデルノードに記述しておきましょう。



## 拡張モデルノードでK-meansモデルを作成する
使用するデータは、今回も変わらずirisのデータです。


データダウンロード先 :


https://archive.ics.uci.edu/dataset/53/iris

SPSS Modelerは v18.5を利用します。（本記事の内容はバージョン18.5でなくても問題なく実行できます。）



### 1.	作ったストリーム その3
前回と同じストリームを利用します。今回は、右側のモデル作成部分を紹介します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3839172/d7fcb1f8-f0d6-6af5-2b6c-ef19003ec515.png)


### 2.モデル作成シンタックス
#### ①.モデル作成シンタックス全体

```Python:モデル作成シンタックス

#---------------------------------------------------------
#必要なライブラリインポート
#---------------------------------------------------------
#SPSS用ランタイム
import spss.pyspark.runtime
#K-Means
from sklearn.cluster import KMeans
#モデル保存用
import pickle

#---------------------------------------------------------
#モデル保存用設定
#Pythonのモデルを利用する際にPySparkのモデルのやり方が利用できないため
#---------------------------------------------------------
#モデルファイルの保存パス
modelpath='c:/temp/modelpath/'
#作成モデルファイル名
modelfile='KM_MODEL.pkl'

#---------------------------------------------------------
#データ読込処理
#---------------------------------------------------------
#コンテキスト取得
ascontext = spss.pyspark.runtime.getContext()
#データフレーム形式によるデータ読込
sdf = ascontext.getSparkInputData()
#Pandasに変換
pdf = sdf.toPandas()

#---------------------------------------------------------
#説明変数、目的変数の設定
#---------------------------------------------------------
#入力データのスキーマ読み込み
inputSchema = ascontext.getSparkInputSchema()

#スキーマから変数名、ロール、データ型を判断して説明変数・目的変数を設定
#変数準備
predictors =[]
#カラム分ループ
for col in inputSchema:

    #説明変数ロール判定 - 入力
    if col.metadata['role']=='input':
        #説明変数配列に追加
        predictors.append(col.name)
 
print("説明変数 = ")
print( predictors )

#---------------------------------------------------------
#モデル作成
#---------------------------------------------------------
# k-meansモデル作成 - クラスタ数は3を指定
model = KMeans(n_clusters=3, random_state=0)
model.fit(pdf[predictors])

#---------------------------------------------------------
#モデル保存
#-------------------------------------------------------
#モデルをファイルシステムに保存
import pickle
pickle.dump(model, open(modelpath+modelfile, 'wb'))
print("モデル = " + modelpath + modelfile )

```

#### ②. シンタックス詳細
　では、パート毎に説明していきます。

##### a. ライブラリの読み込み
　ここでは、SPSS Modeler上でPythonを利用する時に必要なライブラリと、K-meansモデルのライブラリをインポートしています。
※.事前にscikit-learn（sklearn）をPython環境にインストールしておいてください。


```Python:ライブラリ読み込み
#---------------------------------------------------------
#必要なライブラリインポート
#---------------------------------------------------------
#SPSS用ランタイム
import spss.pyspark.runtime
#K-Means
from sklearn.cluster import KMeans
#モデル保存用
import pickle

```


##### b. モデル保存用設定
　scikit-learn（sklearn）のK-meansモデルを使用する場合はpickleを利用してモデルを保存する必要があるため、ファイルシステムに保存するための設定をここで記述します。設定は適宜変更してください。

```Python:モデル保存設定
#---------------------------------------------------------
#モデル保存用設定
#---------------------------------------------------------
#モデルファイルの保存パス
modelpath='c:/temp/modelpath/'
#作成モデルファイル名
modelfile='KM_MODEL.pkl'
```

##### c. データの読み込み
　SPSS ModelerからSparkデータフレーム形式でデータを受け取り、Pandasデータフレーム形式に変換します。

 
```Python:データ読み込み
#---------------------------------------------------------
#データ読込処理
#---------------------------------------------------------
#コンテキスト取得
ascontext = spss.pyspark.runtime.getContext()
#データフレーム形式によるデータ読込
sdf = ascontext.getSparkInputData()
#Pandasに変換
pdf = sdf.toPandas()
```

##### d. 説明変数の設定
　データ型ノードで設定したロールを参照して、K-meansに入力するデータを定義します。K-meansには説明変数のみでいいので、ロールが”入力”のデータのみ選択しています。

 
```Python:説明変数設定
#---------------------------------------------------------
#説明変数、目的変数の設定
#---------------------------------------------------------
#入力データのスキーマ読み込み
inputSchema = ascontext.getSparkInputSchema()

#スキーマから変数名、ロール、データ型を判断して説明変数・目的変数を設定
#変数準備
predictors =[]
#カラム分ループ
for col in inputSchema:

    #説明変数ロール判定 - 入力
    if col.metadata['role']=='input':
        #説明変数配列に追加
        predictors.append(col.name)
```


##### e. K-meansモデルの作成
　クラスタ数は"3"を指定してモデルを作成します。
 ```Python:K-meansモデル作成
#---------------------------------------------------------
#モデル作成
#---------------------------------------------------------
# k-meansモデル作成 - クラスタ数は3を指定
model = KMeans(n_clusters=3, random_state=0)
model.fit(pdf[predictors])
```


##### f. K-meansモデルの保存
　作成したモデルを保存します。
 ```Python:K-meansモデルの保存
#---------------------------------------------------------
#モデル保存
#-------------------------------------------------------
#モデルをファイルシステムに保存
pickle.dump(model, open(modelpath+modelfile, 'wb'))
```


### 3.モデルスコアリングシンタックス
#### ①.モデルスコアリングシンタックス全体

 ```Python:モデルスコアリングシンタックス全体
#---------------------------------------------------------
#必要なライブラリインポート
#---------------------------------------------------------
#SPSS用ランタイム
import spss.pyspark.runtime
#モデル取得用
import pickle
#データ型(Integer)
from pyspark.sql.types import *

#---------------------------------------------------------
#モデル取得用設定
#---------------------------------------------------------
#モデルファイルの保存パス
modelpath='c:/temp/modelpath/'
#作成モデルファイル名
modelfile='KM_MODEL.pkl'

#---------------------------------------------------------
#説明変数、目的変数の設定
#---------------------------------------------------------
#コンテキスト取得
ascontext = spss.pyspark.runtime.getContext()

#入力データのスキーマ読み込み
inputSchema = ascontext.getSparkInputSchema()

#スキーマから変数名、ロール、データ型を判断して説明変数・目的変数を設定
#変数準備
predictors =[]
#カラム分ループ
for col in inputSchema:

    #説明変数ロール判定 - 入力
    if col.metadata['role']=='input':
        #説明変数配列に追加
        predictors.append(col.name)
 

print("説明変数 = ")
print( predictors )

#クラスタリング結果フィールド用のカラム名をセット
prediction_field = "$KM-No"

#---------------------------------------------------------
#出力用スキーマの定義
#---------------------------------------------------------
#入力スキーマを出力スキーマにコピー
outputSchema = inputSchema
#クラスタ番号用フィールドを定義
outputSchema.fields.append(StructField(prediction_field,IntegerType(), nullable=True))
#スキーマに定義したフィールドを追加
ascontext.setSparkOutputSchema(outputSchema)

#---------------------------------------------------------
#スコアリング処理開始
#---------------------------------------------------------
if not ascontext.isComputeDataModelOnly():   
    #データ読込
    indf = ascontext.getSparkInputData()
    #Pythonモデルを利用するためにPandasに変換
    pdf = indf.toPandas()

    #---------------------------------------------------------
    #モデルの取得
    #---------------------------------------------------------
    #保存したモデルを読み込む 
    loaded_model = pickle.load(open(modelpath+modelfile, 'rb'))

    #---------------------------------------------------------
    #クラスタリング処理
    #---------------------------------------------------------
    #モデルに説明変数を渡して結果を取得
    pdf[prediction_field] = loaded_model.predict( pdf[predictors] )

    #---------------------------------------------------------
    #出力処理
    #---------------------------------------------------------
    #SparkSQLコンテキストを生成
    sqlCtx = ascontext.getSparkSQLContext()
    #出力スキーマに基づきデータフレーム形式でデータを格納
    outdf = sqlCtx.createDataFrame( pdf, schema = outputSchema )
    #データをSPSSに戻す
    ascontext.setSparkOutputData( outdf )
```

#### ②. シンタックス詳細
　では、パート毎に説明していきます。


##### a. ライブラリの読み込み
　ここでは、SPSS Modeler上でPythonを利用する時に必要なライブラリと、スキーマ用のデータ型定義、モデル利用のためのpickleをインポートしています。



```Python:ライブラリ読み込み
#---------------------------------------------------------
#必要なライブラリインポート
#---------------------------------------------------------
#SPSS用ランタイム
import spss.pyspark.runtime
#モデル取得用
import pickle
#データ型(Integer)
from pyspark.sql.types import *
```

##### b. モデル利用設定
　scikit-learn（sklearn）のK-meansモデルを使用する場合はpickleを利用してモデルを保存・利用する必要があるため、ファイルシステムからモデルを取得するための設定をここで記述します。設定は保存時と同じものを指定しましょう。

```Python:モデル利用設定
#---------------------------------------------------------
#モデル保存用設定
#---------------------------------------------------------
#モデルファイルの保存パス
modelpath='c:/temp/modelpath/'
#作成モデルファイル名
modelfile='KM_MODEL.pkl'
```

##### c. 説明変数及びスコアリング結果保存用の変数定義
　データ型ノードで設定したロールを参照して、K-meansに入力するデータを定義します。K-meansには説明変数のみでいいので、ロールが”入力”のデータのみ選択しています。
 
```Python:説明変数・結果用変数定義
#---------------------------------------------------------
#説明変数、目的変数の設定
#---------------------------------------------------------
#コンテキスト取得
ascontext = spss.pyspark.runtime.getContext()

#入力データのスキーマ読み込み
inputSchema = ascontext.getSparkInputSchema()

#スキーマから変数名、ロール、データ型を判断して説明変数・目的変数を設定
#変数準備
predictors =[]
#カラム分ループ
for col in inputSchema:

    #説明変数ロール判定 - 入力
    if col.metadata['role']=='input':
        #説明変数配列に追加
        predictors.append(col.name)
 
#クラスタリング結果フィールド用のカラム名をセット
prediction_field = "$KM-No"

```

##### d. 出力用スキーマ定義
　SPSS Modelerにデータを返す際に必要なスキーマ定義を実施。今回はクラスタリングの 結果も返す必要があるため、クラスタ番号用の定義も追加している。

```Python:出力用スキーマ定義
#---------------------------------------------------------
#出力用スキーマの定義
#---------------------------------------------------------
#入力スキーマを出力スキーマにコピー
outputSchema = inputSchema
#クラスタ番号用フィールドを定義
outputSchema.fields.append(StructField(prediction_field,IntegerType(), nullable=True))
#スキーマに定義したフィールドを追加
ascontext.setSparkOutputSchema(outputSchema)
```

##### e. 出力用スキーマ定義
　スコアリング処理を開始します。まずはデータの読み込みをして、Pandasに変換しています。 
 　直前にある、isComputeDataModelOnlyメソッドはデータモデル定義のみを扱うかどうかを判断させるメソッドです。if not となっているので、後続ノードがフィルターノード等のようにデータモデルのみ参照するノードの場合、実データは必要なく列名などの情報だけでいいので、if文の中の処理は行われません。テーブルノード等データを参照するノードの場合は、if文の中の処理が実行されます。

```Python:スコアリング処理、データ読み込み
#---------------------------------------------------------
#スコアリング処理開始
#---------------------------------------------------------
if not ascontext.isComputeDataModelOnly():   
    #データ読込
    indf = ascontext.getSparkInputData()
    #Pythonモデルを利用するためにPandasに変換
    pdf = indf.toPandas()
```


##### f. K-meansスコアリング処理
　保存してあるモデルを取得して、スコアリング処理をしています。結果は、事前に定義してある変数に格納します。

```Python:スコアリング処理、クラスタリング処理
    #---------------------------------------------------------
    #モデルの取得
    #---------------------------------------------------------
    #保存したモデルを読み込む 
    loaded_model = pickle.load(open(modelpath+modelfile, 'rb'))

    #---------------------------------------------------------
    #クラスタリング処理
    #---------------------------------------------------------
    #モデルに説明変数を渡して結果を取得
    pdf[prediction_field] = loaded_model.predict( pdf[predictors] )
```


##### g. データ出力処理
　SPSS Modelerにデータを返します。Sparkデータフレームにデータを格納してから返します。

 ```Python:スコアリング処理、データ出力処理
    #---------------------------------------------------------
    #出力処理
    #---------------------------------------------------------
    #SparkSQLコンテキストを生成
    sqlCtx = ascontext.getSparkSQLContext()
    #出力スキーマに基づきデータフレーム形式でデータを格納
    outdf = sqlCtx.createDataFrame( pdf, schema = outputSchema )
    #データをSPSSに戻す
    ascontext.setSparkOutputData( outdf )
```


### 4.クラスタリング結果をSPSS Modelerで確認する

シンタックスを記述した拡張モデルノードを実行すると、拡張モデルナゲットが作成されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3839172/47daf5e6-1e6f-2d23-3220-1ffb471ab287.png)

ナゲットを開いてみます。拡張モデルノードに書いたシンタックスが反映されていつことが分かります。このように、拡張モデルノードに書いたシンタックスはナゲットにコピーされます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3839172/f61c8acc-0390-6ee4-2d3c-9d4c7f52f911.png)


ナゲットのシンタックスを編集しようとすると、以下の説明が表示されます。説明の通りで、拡張モデルノードを再度実行したときに、そこに書かれたスコアリングシンタックスがコピーされるので、ナゲットのシンタックスは上書きされてしまいます。なので、スコアリングシンタックスは拡張モデルノードに書きましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3839172/71c5b0b9-5e7f-ede1-3976-a276bda33b0f.png)


最後にテーブルノードで結果を確認すると、$KM-Noの列にクラスタ番号が付与されていることが分かります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3839172/54d44633-4f54-addd-6238-66d49982e604.png)


## まとめ
ちょっと長い記事になってしまいましたが、このように、RやPythonを使ってSPSS Modelerに実装されていない機能やモデルを作成することができます。前回紹介したエルボー法等ですね、
プログラミングも苦手意識をもだずにチャレンジしてみてください。



## 参考情報

### SPSS Modeler ノードリファレンス目次

https://qiita.com/416nishimaki/items/503037195081d3323eee、


### SPSS Modeler 逆引きストリーム集（データ加工

https://qiita.com/416nishimaki/items/4bf813e6cba84b2a1008

### SPSS funさん記事

https://qiita.com/spssfun2017


### SPSS連載ブログバックナンバー

https://qiita.com/416nishimaki/items/24c054defbe7280bcf0e