SPSS Modelerで生成系AIのwatsonx.aiをつかって項目抽出

SPSS Modelerで、watsonx.aiで生成系AIのwatsonx.aiをつかって項目抽出します。

SPSS Modelerで日本語文をwatsonx.aiに与えて、項目抽出(Entity Extraction)をした結果を受け取ります。「拡張の変換ノード」のNative Pythonを使います。Pythonプログラムのロジックは以下で解説しています。

watsonx.aiで日本語文からの項目抽出(Python編)

https://qiita.com/spssfun2017/items/1ddf432a12fd289ad9a7



具体的には保険請求文書から、車両タイプと車種、日付、時間帯と場所の情報を抽出してModelerで表示します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/a0c3e92b-1a1d-0f67-fe8c-048f4e1c0995.png)


    

* サンプルストリーム

https://github.com/hkwd/spsssample/raw/master/240325watsonxai/240221llmEntity3.str

* テスト環境
    * Modeler 18.5
    * Windows 11 64bit
    * watsonx.ai (クラウド)
    * python:3.10.9
    * ibm_watson_machine_learning:1.0.353

# 1. ibm_watson_machine_learningの導入

Modeler内のPythonにibm_watson_machine_learningを導入します。

管理者としてコマンドプロンプトを起動します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/afac60b0-544b-355a-4ac0-7660a14affb3.png)

以下のコマンドで導入します。

```powershell
"C:\Program Files\IBM\SPSS\Modeler\18.5\python_venv\Scripts\python.exe" -m pip install ibm_watson_machine_learning
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/e3ddc99e-dfc5-a418-dbfe-8f4230b6e098.png)

# 2. 入力
入力テキストを用意しています。「INPUT_TEXT」という列名にしています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/373b2213-03c5-772d-bde3-af7d159ffb9c.png)

# 3. 拡張のエクスポートノード

APIキーとプロジェクトIDは以下を参考に取得してください。

https://qiita.com/spssfun2017/items/1ddf432a12fd289ad9a7#api%E3%82%AD%E3%83%BC%E3%81%A8%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88id%E3%81%AE%E5%8F%96%E5%BE%97

「拡張のエクスポートノード」に以下を入力して実行します。
APIキーとプロジェクトIDは上で取得したものに置き換えてください。。
watsonx_project_id = "XXXXXXXXXXXXXXXXXXXXXXXXX"
api_key = "XXXXXXXXXXXXXXXXXXXXXXXX"

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/6b7f064e-17bc-5664-6fd7-89e834e8747f.png)

```python:modeler_entitiryextract_jp.py
# 入出力列の定義
fld_input = 'INPUT_TEXT'
fld_outputs = ['車両タイプ', '車種', '日付', '時間帯', '場所']


"""
This code sample shows how to invoke Large Language Models (LLMs) deployed in watsonx.ai.
Documentation: # https://ibm.github.io/watson-machine-learning-sdk/foundation_models.html#
You will need to provide your IBM Cloud API key and a watonx.ai project id (any project)
for accessing watsonx.ai
This example shows a Generate use case

このコード サンプルは、watsonx.ai にデプロイされた大規模言語モデル (LLM) を呼び出す方法を示します。
ドキュメント: # https://ibm.github.io/watson-machine-learning-sdk/foundation_models.html#
watsonx.ai にアクセスするためIBM Cloud API キーと watonx.ai プロジェクト ID (任意のプロジェクト) を提供する必要があります。
この例は、生成の使用例を示しています。
"""

# Install the wml api your Python env prior to running this example:
# この例を実行する前に、Python 環境に wml API をインストールしてください
# pip install ibm-watson-machine-learning

# テストした環境
# ibm_watson_machine_learning:1.0.353
# python:3.10.9 (tags/v3.10.9:1dd9be6, Dec  6 2022, 20:01:21) [MSC v.1934 64 bit (AMD64)]

# ibm_watson_machine_learningのモジュールをインポート
from ibm_watson_machine_learning.foundation_models.utils.enums import ModelTypes, DecodingMethods
from ibm_watson_machine_learning.metanames import GenTextParamsMetaNames as GenParams
from ibm_watson_machine_learning.foundation_models import Model

# 環境依存情報
# APIキー
api_key = "XXXXXXXXXXXXXXXXXXXXXXXX"
# プロジェクトID
watsonx_project_id = "XXXXXXXXXXXXXXXXXXXXXXXXX"

# エンドポイントURL
url = "https://jp-tok.ml.cloud.ibm.com"


# モデルの定義
def get_model():
    # 基盤モデルの選択
    model_type = 'ibm/granite-8b-japanese'
    # model_type = ModelTypes.ELYZA_JAPANESE_LLAMA_2_7B_INSTRUCT
    # モデルパラメータの指定
    generate_params = {
        GenParams.DECODING_METHOD: DecodingMethods.GREEDY,
        GenParams.REPETITION_PENALTY: 1,
        GenParams.STOP_SEQUENCES: ["}"],
        GenParams.MIN_NEW_TOKENS: 0,
        GenParams.MAX_NEW_TOKENS: 800
    }

    model = Model(
        model_id=model_type,
        params=generate_params,
        credentials={
            "apikey": api_key,
            "url": url
        },
        project_id=watsonx_project_id
    )
    return model

# プロンプトの作成
def get_prompt(input_text):

    # Get the complete prompt by replacing variables
    # 変数を置き換えて完全なプロンプトを取得します

    complete_prompt = f"""
    以下の保険請求文書の説明から、車両タイプと車種、日付、時間帯と場所に関する詳細を含む JSON オブジェクトを生成してください。

    保険請求文書:
    ハイブリッドセダン型車両、トヨタプリウス、2024年2月15日午後3時頃、神奈川県横浜市の都市部の交差点で信号待ち中に後方から追突され、後部バンパーが損傷しました。追突事故により、車両の後部パネルにもひびが入り、修理が必要です。保険金の支払いを申請します。

    JSON:
    {{"車両タイプ": "ハイブリッドセダン型車両", "車種": "トヨタプリウス", "日付": "2024年2月15日", "時間帯": "午後3時頃", "場所": "神奈川県横浜市"}}


    保険請求文書:
    SUV型車両、ランドローバーディスカバリー、2024年3月1日午前10時頃、埼玉県川口市の高速道路上で車線変更中に側面から衝突され、ドライバーサイドのドアとフェンダーが損傷しました。修理には大規模なボディパネルの交換が必要です。修理費用の請求をいたします。

    JSON:
    {{"車両タイプ": "SUV型車両", "車種": "ランドローバーディスカバリー", "日付": "2024年3月1日", "時間帯": "午前10時頃", "場所": "埼玉県川口市"}}


    保険請求文書:
    コンパクトカー型車両、ホンダシビック、2024年1月10日午後8時頃、東京都世田谷区の住宅街の駐車場で車両が盗難に遭いました。警察に被害届を提出し、車両の所在が確認できないため、保険金の支払いをお願いします。

    JSON:
    {{"車両タイプ": "コンパクトカー型車両", "車種": "ホンダシビック", "日付": "2024年1月10日", "時間帯": "午後8時頃", "場所": "東京都世田谷区"}}

    保険請求文書:
    ピックアップトラック型車両、フォードF-150、2024年2月20日午後2時頃、千葉県柏市のショッピングセンターの駐車場で後退中に他の車両に接触し、リアバンパーとトランクに損傷が発生しました。修理費用の請求を申し上げます。

    JSON:
    {{"車両タイプ": "ピックアップトラック型車両", "車種": "フォードF-150", "日付": "2024年2月20日", "時間帯": "午後2時頃", "場所": "千葉県柏市"}}
  
    保険請求文書:
    {input_text}

    JSON:
    """

    return complete_prompt

# 生成の実行
def get_output(input_text):

    # Instantiate the model
    # モデルをインスタンス化する
    model = get_model()

    # プロンプトに入力テキストを入力
    complete_prompt = get_prompt(input_text)

    # LLMから結果を取得
    generated_response = model.generate(prompt=complete_prompt)
    response_text = generated_response['results'][0]['generated_text']

    # print model response
    # モデル応答を出力します
    # print("--------------------------------- Generated output -----------------------------------")
    # print("Prompt: " + complete_prompt.strip())
    # print("---------------------------------------------------------------------------------------------")
    # print("Generated output: " + response_text)
    # print("*********************************************************************************************")

    return response_text


# ネイティブ Python APIのパッケージ
import modelerpy
import pandas as pd
import json

# データモデルの参照時
if modelerpy.isComputeDataModelOnly():
    modelerDataModel = modelerpy.getDataModel()
    outputDataModel = None

    # Compute output data model here
    # 出力データモデルの設定。取得列を追加
    outputDataModel = modelerDataModel
    for fld_name in fld_outputs:
        outputDataModel.addField(modelerpy.Field(fld_name, "string", measure="nominal"))
    modelerpy.setOutputDataModel(outputDataModel)

# データ出力時
else:
    modelerData = modelerpy.readPandasDataframe()
    outputData = modelerData

    # スコアリング
    # LLM生成。戻り値はpd.Series
    entities = modelerData[fld_input].apply(get_output)
    # JSONのDataFrame化
    df_entities = pd.json_normalize(entities.map(json.loads))
    # 元データと結合
    outputData = pd.concat([outputData, df_entities], axis=1)
    print(outputData)

    # データの出力
    modelerpy.writePandasDataframe(outputData)

```


# 4.テスト実行
「テーブルノード」を接続して実行すると期待通り保険請求文書から抽出した項目がModelerの列に取得できています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/87dfb1b9-11d7-1db0-c4c3-7df977bb8898.png)


# 5.コードの確認

Pythonからのwatsonx.aiの利用方法については[この記事](https://qiita.com/spssfun2017/items/1ddf432a12fd289ad9a7#jupyter-notebook%E3%81%8B%E3%82%89%E3%81%AE%E5%AE%9F%E8%A1%8C)で説明していますので、Modelerと連携部分を説明します。

以下で入力列と出力列を定義しています。
出力列は複数あるのでリストにしています。
```python:入出力列の定義
fld_input='INPUT_TEXT'
fld_outputs=['車両タイプ', '車種', '日付', '時間帯', '場所']
```

以下で、データモデルを定義しています。

```python:データモデルの参照時
if modelerpy.isComputeDataModelOnly():
    modelerDataModel = modelerpy.getDataModel()

    #Compute output data model here
    #出力データモデルの設定。取得列を追加
    outputDataModel = modelerDataModel
    for fld_name in fld_outputs:
        outputDataModel.addField(modelerpy.Field(fld_name, "string", measure="nominal"))
    modelerpy.setOutputDataModel(outputDataModel)
```

modelerpy.getDataModel()で入力データのデータモデルを取得しています。ここでは「INPUT_TEXT」列の情報を取得しています。
outputDataModel.addField(modelerpy.Field(fld_name, "string", measure="nominal"))で最初に定義した出力列である「車両タイプ」、「車種」、「日付」、「時間帯」、「場所」をstring型の名義型で定義しています。


以下で、出力データを作っています。

```python:データ出力時
else:
    modelerData = modelerpy.readPandasDataframe()
    outputData = modelerData


    #スコアリング
    #LLM生成。戻り値はpd.Series
    entities = modelerData[fld_input].apply(get_output)
    #JSONのDataFrame化
    df_entities = pd.json_normalize(entities.map(json.loads))
    #元データと結合
    outputData=pd.concat([outputData,df_entities], axis=1)
    print(outputData)

    #データの出力
    modelerpy.writePandasDataframe(outputData)
```

modelerData[fld_input].apply(get_output)でwatsonx.aiに「INPUT_TEXT」の保険請求文書を渡して、以下のようなJSONを得ています。
~~~md:Examples (Output:)
JSON:
{"車両タイプ": "ハイブリッドセダン型車両", 
"車種": "トヨタプリウス", 
"日付": "2024年2月15日", 
"時間帯": "午後3時頃",
"場所": "神奈川県横浜市"}
~~~

pd.json_normalize(entities.map(json.loads))で上のJSONから、以下のようなPandasのDataFrameに変換しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/79af333f-9a22-8f30-2ace-5d24b049aead.png)


pd.concat([outputData,df_entities], axis=1)で元データとJSONの結果のDataFrameを以下のように結合しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/a0c3e92b-1a1d-0f67-fe8c-048f4e1c0995.png)


# 参照

#### watsonx.aiで日本語文からの項目抽出(プロンプト・ラボ編) #watsonx.ai - Qiita 

https://qiita.com/spssfun2017/items/77fbaec5bc4cf3995800

#### watsonx.aiで日本語文からの項目抽出(Python編) #Python - Qiita 

https://qiita.com/spssfun2017/items/1ddf432a12fd289ad9a7