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

watsonx.aiで、日本語文からの項目抽出(Entity Extraction)を行います。LLMにはIBMのgranite-8b-japaneseを使ってみます。[プロンプト・ラボでやったこと](https://qiita.com/kawada2017/items/77fbaec5bc4cf3995800)のPython版になります。[プロジェクトの準備](https://qiita.com/kawada2017/items/77fbaec5bc4cf3995800#%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AE%E6%BA%96%E5%82%99)までは同じ手順になりますので、プロジェクトを作成しておいてください。

具体的には以下のような保険請求文書から、車両タイプと車種、日付、時間帯と場所の情報をJSONで抽出して、PadasのDataFrameにしてみます。



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

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

* テスト環境
    * watsonx.ai (クラウド)
    * python:3.10.9
    * ibm_watson_machine_learning:1.0.353

# APIキーとプロジェクトIDの取得
## APIキーの作成
[watsonx](https://jp-tok.dataplatform.cloud.ibm.com/wx/home?context=wx)のハンバーガーメニューから「アクセス（IAM)」にアクセスします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/af5f2319-9cfd-30be-d789-085c73ddab9e.png)

「APIキー」を選び「作成」クリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/2d02d098-1ae4-8895-1951-f552749b14a9.png)

適当な名前をつけて「作成」します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/7f1f8b5d-4ea6-be9c-5783-db4074701411.png)

「コピー」や「ダウンロード」をして保存しておいてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/02de5c4b-4f79-bd70-cca8-5fa0858bad2d.png)

## プロジェクトID取得
プロジェクトに入り、「管理」タブの「一般」を選び、「プロジェクト ID」をコピーします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/f1c73211-921c-fe94-3088-c99854847a63.png)


# Jupyter Notebookからの実行
Jupyter Notebookで実行してみます。
watsonxの上のJupyterでも実行できますし、VS Codeなどから手元のPC環境でも実行できます。

まずpipで[ibm-watson-machine-learning](https://ibm.github.io/watson-machine-learning-sdk/)のパッケージを導入しておいてください。

```bash:Python 環境に wml API をインストールしてください
pip install ibm-watson-machine-learning
```

ibm-watson-machine-learningから必要なモジュールをインポートします。
```python:ibm_watson_machine_learningのモジュールをインポート
from ibm_watson_machine_learning.foundation_models import Model
from ibm_watson_machine_learning.metanames import GenTextParamsMetaNames as GenParams
from ibm_watson_machine_learning.foundation_models.utils.enums import ModelTypes, DecodingMethods
```

プロジェクトID、APIキーは先ほど取得したものを入力します。

```python:環境依存情報
#プロジェクトID
watsonx_project_id = "xxxxxxxxxxxxxxxx"
#APIキー
api_key = "xxxxxxxxxxx"

```
エンドポイントURLはリージョンに合わせて以下から選びます。ここではgranite-8b-japaneseモデルを使いたいので、東京リージョンのエンドポイントURLを選んでいます。

https://cloud.ibm.com/apidocs/machine-learning#endpoint-url

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




基盤モデルを選択しています。
ModelTypesは以下で定義されています。

https://ibm.github.io/watson-machine-learning-sdk/model.html#ibm_watson_machine_learning.foundation_models.utils.enums.ModelTypes

ただ、ibm-watson-machine-learning-1.0.353では、まだgranite-8b-japaneseが定義されていなかったので、model_type = 'ibm/granite-8b-japanese'で文字列で定義しています。

サポートされているモデルは以下に掲載されています。

https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/fm-models.html?context=wx&locale=ja&audience=wdp

```python: # 基盤モデルの選択
model_type = 'ibm/granite-8b-japanese'
# model_type = ModelTypes.ELYZA_JAPANESE_LLAMA_2_7B_INSTRUCT
```

次にモデルパラメーターを設定していきます。
次のパラメーターを指定しています。以下に定義があります。

https://ibm.github.io/watson-machine-learning-sdk/model.html#enums

各パラメーターの意味は以下です。

https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/fm-model-parameters.html?context=wx&locale=ja&audience=wdp

```python: モデルパラメータの指定
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
)
```

プロンプト・ラボとの対応は以下のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/0be63bd8-30a2-3d67-4ebe-f6c80e0d4e20.png)


プロンプトを作成します。
ここではFew shotでプロンプトを作っています。つまり「保険請求文書」と「JSON」の例をいくつか用意しています。そして最終的に結果を得たい「保険請求文書」を{input_text}で与えるようにしています。このように関数化することで、複数の「保険請求文書」に対して繰り返して処理を行うことも可能にしています。なお[f-stringで記述しているのでJSONにある「{」は「{{」でエスケープしています](https://qiita.com/simonritchie/items/74f544944ee11a226613#f-strings%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8B%E3%82%A8%E3%82%B9%E3%82%B1%E3%83%BC%E3%83%97)。

```python: # プロンプトの作成
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
```

以下で生成を実行しています。「ステーションワゴン型車両、スバルアウトバック・・・」の文書からJSONで固有表現を抽出します。
model.generateにプロンプトを入れているだけです。

```python: 生成の実行

# プロンプトに入力テキストを入力
complete_prompt = get_prompt('ステーションワゴン型車両、スバルアウトバック、2024年1月5日午後4時頃、神奈川県横須賀市で自宅近くの道路で大雨による洪水が発生し、浸水によりエンジンと電子システムに損傷が発生しました。保険金のお支払いをお願いします。')

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

import pprint
pprint.pprint(generated_response)
```
generated_responseはdict型になっていて、この'results'の中に結果が含まれています。うまくJSONに抽出できていることがわかります。

```bash
{'created_at': '2024-03-25T02:40:59.769Z',
 'model_id': 'ibm/granite-8b-japanese',
 'results': [{'generated_text': '{"車両タイプ": "ステーションワゴン型車両", "車種": "スバルアウトバック", '
                                '"日付": "2024年1月5日", "時間帯": "午後4時頃", "場所": '
                                '"神奈川県横須賀市"}',
              'generated_token_count': 48,
              'input_token_count': 588,
              'stop_reason': 'stop_sequence'}]}
```

実行したプロンプトと生成結果を出力してみます。

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

```

```bash:
--------------------------------- Generated output -----------------------------------
Prompt: 以下の保険請求文書の説明から、車両タイプと車種、日付、時間帯と場所に関する詳細を含む 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時頃", "場所": "千葉県柏市"}
  
    保険請求文書:
    ステーションワゴン型車両、スバルアウトバック、2024年1月5日午後4時頃、神奈川県横須賀市で自宅近くの道路で大雨による洪水が発生し、浸水によりエンジンと電子システムに損傷が発生しました。保険金のお支払いをお願いします。

    JSON:
---------------------------------------------------------------------------------------------
Generated output: {"車両タイプ": "ステーションワゴン型車両", "車種": "スバルアウトバック", "日付": "2024年1月5日", "時間帯": "午後4時頃", "場所": "神奈川県横須賀市"}
*********************************************************************************************
```

プロンプト・ラボとの対応は以下のようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/0166b941-d3d8-0320-0ed1-6c6f62c82288.png)

最後にJSONをpandasのDataFrameに変換しています。


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


```python:JSONのdf化
import pandas as pd
import json
df = pd.DataFrame(json.loads(response_text), index=[0])
print(df)
```
#### 参考　JSON文字列をPandas DataFrameにロードする
https://ja.linux-console.net/?p=26678#google_vignette


# サンプルプログラム

https://github.com/hkwd/spsssample/blob/master/240325watsonxai/watsonx_pub.ipynb

# 参考

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

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