watsonx.aiで日本語文のセンチメント分析(Python編)

watsonx.aiで、日本語文のセンチメント分析（感情分析）を行います。LLMにはIBMのgranite-8b-japaneseを使ってみます。[プロンプト・ラボでやったこと](https://qiita.com/kawada2017/items/77fbaec5bc4cf3995800)のPython版になります。[プロジェクトの準備](https://qiita.com/kawada2017/items/b30de62c08c7676879fa#%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)までは同じ手順になりますので、プロジェクトを作成しておいてください。

具体的にはスマートフォンのレビュー文を「ポジティブ」か「ネガティブ」に分類してみます。



~~~md:Examples (Input:)
レビュー:
驚くほど軽量で、持ち運びが便利です。また、バッテリーの持続時間も素晴らしく、長時間使用しても安心です。
~~~

~~~md:Examples (Output:)
感情:
ポジティブ
~~~

* テスト環境
    * 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:環境依存情報
#APIキー
api_key = "xxxxxxxxxxx"
#プロジェクトID
watsonx_project_id = "xxxxxxxxxxxxxxxx"
```
エンドポイント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.1,
    GenParams.MIN_NEW_TOKENS: 0,
    GenParams.MAX_NEW_TOKENS: 200
}

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/e40e3f64-30fe-c671-66ef-5af12665d0b4.png)



プロンプトを作成します。
ここではFew-shotでプロンプトを作っています。複数の「レビュー」と「感情」の組合せの例をいくつか用意しています。そして最終的に結果を得たい「レビュー」を{input_text}で与えるようにしています。このように関数化することで、複数の「レビュー」に対して繰り返して処理を行うことも可能にしています。

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

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

    complete_prompt = f"""
    このスマートフォンに関するフィードバックを分析し、顧客のポジティブな/ネガティブな感情を特定してください。

    レビュー:
    驚くほど軽量で、持ち運びが便利です。また、バッテリーの持続時間も素晴らしく、長時間使用しても安心です。
    感情:
    ポジティブ


    レビュー:
    カメラの性能が他の機種に比べるとやや劣っています。特に暗所での撮影時には、ノイズが目立ちます。
    感情:
    ネガティブ


    レビュー:
    デザインは非常に洗練されており、手にしっくりと馴染みます。操作性も高く、使いやすさに定評があります。
    感情:
    ポジティブ

    レビュー:
    画面の解像度がやや低いです。テキストや画像を拡大すると、粗さが目立ちます。
    感情:
    ネガティブ


    レビュー:
    {input_text}

    感情:
    """

    return complete_prompt
```

以下で生成を実行しています。「カメラ機能は優れており、高品質な写真や・・・」の文書をポジティブかネガティブで分類しています。
model.generateにプロンプトを入れているだけです。

```python: 生成の実行

# プロンプトに入力テキストを入力
complete_prompt = get_prompt('カメラ機能は優れており、高品質な写真や動画を撮影できます。さまざまな撮影モードやエフェクトも豊富です。')

# 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'の中に結果が含まれています。「ポジティブ」が戻ってきており、うまく感情を分類できていることがわかります。

```bash
{'created_at': '2024-03-27T05:02:27.186Z',
 'model_id': 'ibm/granite-8b-japanese',
 'results': [{'generated_text': 'ポジティブ',
              'generated_token_count': 2,
              'input_token_count': 198,
              'stop_reason': 'eos_token'}]}
```

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

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

```

```bash:
--------------------------------- Generated output -----------------------------------
Prompt: このスマートフォンに関するフィードバックを分析し、顧客のポジティブな/ネガティブな感情を特定してください。

    レビュー:
    驚くほど軽量で、持ち運びが便利です。また、バッテリーの持続時間も素晴らしく、長時間使用しても安心です。
    感情:
    ポジティブ


    レビュー:
    カメラの性能が他の機種に比べるとやや劣っています。特に暗所での撮影時には、ノイズが目立ちます。
    感情:
    ネガティブ


    レビュー:
    デザインは非常に洗練されており、手にしっくりと馴染みます。操作性も高く、使いやすさに定評があります。
    感情:
    ポジティブ

    レビュー:
    画面の解像度がやや低いです。テキストや画像を拡大すると、粗さが目立ちます。
    感情:
    ネガティブ


    レビュー:
    カメラ機能は優れており、高品質な写真や動画を撮影できます。さまざまな撮影モードやエフェクトも豊富です。

    感情:
---------------------------------------------------------------------------------------------
Generated output: ポジティブ
*********************************************************************************************
```

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/223763/f73fe9a6-9de5-5716-463f-5b71652f3e6b.png)


# サンプルプログラム

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

# 参考

watsonx.aiで日本語文のセンチメント分析(プロンプト・ラボ編)

https://qiita.com/kawada2017/items/b30de62c08c7676879fa