第三回 | ChatGPT

# 目的
前回の記事では、エンティティ（キーワード）の感情分析を行いました。今回は、商品をカテゴリーごとに評価するための処理を説明します。

具体的には、商品のレビューに登場するエンティティをカテゴリーごとに分類していきます。例えば商品がスピーカーの場合、評価軸として「音質」「音量」「価格」「サイズ」などのカテゴリーを考えます。この時、レビュー内で「音質」「高音質」「重低音」などのエンティティが言及されている場合、それらを「音質」カテゴリーに分類します。これにより、「音質」というカテゴリーでの商品の評価（どれくらいポジティブ/ネガティブな言及がされているか）を判断することができます。
  
上記はスピーカーを例に挙げていますが、商品レビューの内容によっては「音質」「音量」「価格」「サイズ」以外のカテゴリーが必要とされる場合もあります。また、化粧品や家電商品など様々な商品レビューを分析する際には、商品に合わせた様々なカテゴリーが必要となります。毎回カテゴリーを考えるのは手間がかかるため、OpneAI APIを利用して自動的にカテゴリーを生成することにしました。更に、言及されているエンティティをカテゴリーに分類する作業もOpneAI APIを利用することで自動化を試みました。これにより、[前回の記事](https://qiita.com/venect_qiita/items/83607a124cad9dcb4e8a)で得られたエンティティの感情分析結果をカテゴリー毎に集計し、商品がどのカテゴリーで強みや課題を持つのかを明らかにします。


# OpneAI APIで行うこと
* 商品の評価軸となるカテゴリーの作成
* 言及されているエンティティをカテゴリーに分類

# 環境
### 開発環境
- **OS**: macOS

### 使用したツールのバージョン

- **Python**: 3.10.12
- **openai**: 0.28.1


# OpneAI APIの使用方法
まず、OpneAIのAPIを使用するためには、OpneAIのAPIキーを取得する必要があります。
具体的な手順は[OpenAIの公式ドキュメント](https://platform.openai.com/docs/quickstart)をご確認ください。

なお、APIは従量課金制であり使用料金には注意が必要です。
[料金計算方法](https://zenn.dev/umi_mori/books/chatbot-chatgpt/viewer/how_to_calculate_openai_api_prices)

OpneAIのAPIでは、トークン数に応じて料金が変動します。
以下のサイトによりトークン数を計算してくれます。
[OpenAI公式サイト](https://platform.openai.com/tokenizer)


## サンプルコード
以下がOpneAI APIを用いて質問を行い、回答を取得する関数です。
```python
import openai

def ask_chatgpt(key, model, message, temperature=0):
    openai.api_key = key
    completion = openai.ChatCompletion.create(
            model = model,
            messages=[
                {
                    "role":"system","content":"あなたはプロの分析者です。",
                    "role":"user","content":message,
                }
            ],
            max_tokens   = 1024,
            n           = 1,
            stop        = "}",
            temperature = temperature,
    )

    response = completion.choices[0].message.content
    return response
```

openai.ChatCompletion.create関数で使用されているパラメータについては、[OpenAIの公式ドキュメント](https://platform.openai.com/docs/api-reference/chat/create)をご確認ください。
上記のコードだけで、OpenAIが提供するGPT-4などのLLMを使用した自然言語の生成が可能です。とても簡単ですね。
   
# 商品の評価軸となるカテゴリーの作成
続いて、カテゴリーの作成に関して具体的にどのようなプロンプトを使用したかを解説します。
先ほどのサンプルコードのmessage引数にプロンプトを設定することになりますが、以下のようにしてmessage変数を作成しています。

### 変数の設定
```python
kind_of_product = "扇風機"
category_count = "3"
category_function = "特徴"

# レビューに登場するエンティティのリスト
input_entities = ['風','強い','簡単'、'ボタン','簡単','大きい','大きさ','小さい','弱い','風量']

message = f"商品のレビューに登場する単語をカテゴライズして分析するためのカテゴリーを作成したいです。\n{kind_of_product}の{category_function}を{category_count}つのカテゴリーに分類してください。Pythonのリストで定義し、変数名はcategoriesとします。\nなお、出力されたcategoriesリストは、Entityリストの単語を分類するために使用されます。\n\n# アウトプット形式\ncategories = [\n    'category1',\n    'category2',\n    'category3',\n    'category4',\n]\n\n# Entityリスト\nEntity = {input_entities}"
```
なお、弊社で開発したシステム（Webアプリケーション）ではkind_of_product,category_count,category_functionの値はユーザーが入力できるようにしています。

<details><summary>実際のmessageの内容はこちらを展開して確認可能です。</summary>

```python
商品のレビューに登場する単語をカテゴライズして分析するためのカテゴリーを作成したいです。
扇風機の特徴を3つのカテゴリーに分類してください。Pythonのリストで定義し、変数名はcategoriesとします。
なお、出力されたcategoriesリストは、Entityリストの単語を分類するために使用されます。

# アウトプット形式
categories = [
    'category1',
    'category2',
    'category3',
    'category4',
]

# Entityリスト
Entity = ['風','強い','簡単'、'ボタン','簡単','大きい','大きさ','小さい','弱い','風量']
```
</details>

<br>

### OpenAI APIへのリクエストの実行

```python
key = "xxxxx" # 取得したAPIキーを設定してください
model = 'gpt-4-0125-preview' # お好きなモデルを設定してください

response = ask_chatgpt(key, model, message)
print(response)
```
<br>

### 出力結果
```python
categories = [
    '風量・風力',
    '操作性・機能性',
    'デザイン・サイズ',
]
```



上記のように扇風機の商品の評価軸となるカテゴリーを生成することができました。  
プロンプト作成において、以下の点を工夫しています。

* **最初にカテゴリー作成をする目的を伝える**
    * このプロンプトが何を目的にしたものかを先に伝えることで、より私たちが求めている回答を得やすくなります。今回の例では以下の一文を加えることで、単に扇風機のカテゴリーを作成する依頼をするよりも正確性が上がった感覚があります。
        * <u>商品のレビューに登場する単語をカテゴライズして分析するためのカテゴリーを作成したいです。</u>
<br>

* **アウトプットの例を明示する**
    * 今回Pythonのプログラム内で使用したいという都合上、レスポンスはPythonのリスト形式で出力させたいという思惑がありました。単にPythonのリストで欲しいと書くだけではうまく出力されないこともあったため、出力の形式を明確に記載するようにしています。

<br>

* **この後に実施するカテゴリー分類に使用するエンティティをプロンプトに含める**
    * このプロンプトはカテゴリー作成のみを実施しているのでエンティティは本来必要無いのですが、後でエンティティをカテゴリー分類するときにレビューにあまり関係ないカテゴリーが作成されるとうまく分析ができません。そのため、このプロンプトはカテゴリー作成だけをするプロンプトですが、後にこのエンティティを分類するために使用する旨を記載しています。


# 言及されているエンティティをカテゴリーに分類
次は言及されたエンティティを上記の```categories```に分類します。今回は先ほどのカテゴリー作成でも使用した、以下のエンティティを分類してみます。
```python
['風','強い','簡単','ボタン','簡単','大きい','大きさ','小さい','弱い','風量']
```

### 変数の設定
```python
categories = "categories = [\n'風量・風力',\n'操作性・機能性',\n'デザイン・サイズ',\n]"
input_entities = "['風','強い','簡単','ボタン','簡単','大きい','大きさ','小さい','弱い','風量']"

second_message = f"商品レビューに登場する頻出単語をカテゴリーに分類したいと考えています。\nインプットデータのEntityリストの要素をcategoriesリストの要素をキーとしてカテゴライズし、その結果をpythonの辞書型で変数名entity_categoriesで定義して下さい。\nその際、以下のルールに従って下さい。\n# ルール\n・categoriesの要素をentity_categoriesのkeyにして下さい。\n・Entityの文字列は変更しないで下さい。\n・1つのEntity要素は1つのcategories要素に紐づきます。複数カテゴリに属する、複数回Entity要素が使用されることは許されません。\n\n# アウトプット形式\nentity_categories = {{\n    'category1': ['word1', 'word2'],\n    'category2': ['word3', 'word4'],\n    'category3': ['word5', 'word6'],\n    'category4': ['word7', 'word8'],\n }}\n\n# インプットデータ\n###\n{categories}\nEntity = {input_entities}\n###"
```
なお、上記の例では文字列をベタ書きしていますが、実際のシステムではcategoriesは先ほどのカテゴリー作成で作成されたものを使用し、input_entitiesは形態素解析などで自動で作成するようにしています。

<details><summary>実際のsecond_messageの内容はこちらを展開して確認可能です。</summary>

```python
商品レビューに登場する頻出単語をカテゴリーに分類したいと考えています。
インプットデータのEntityリストの要素をcategoriesリストの要素をキーとしてカテゴライズし、その結果をpythonの辞書型で変数名entity_categoriesで定義して下さい。
その際、以下のルールに従って下さい。
# ルール
・categoriesの要素をentity_categoriesのkeyにして下さい。
・Entityの文字列は変更しないで下さい。
・1つのEntity要素は1つのcategories要素に紐づきます。複数カテゴリに属する、複数回Entity要素が使用されることは許されません。

# アウトプット形式
entity_categories = {
    'category1': ['word1', 'word2'],
    'category2': ['word3', 'word4'],
    'category3': ['word5', 'word6'],
    'category4': ['word7', 'word8'],
 }

# インプットデータ
###
categories = [
'風量・風力',
'操作性・機能性',
'デザイン・サイズ',
]
Entity = ['風','強い','簡単'、'ボタン','簡単','大きい','大きさ','小さい','弱い','風量']
###
```
</details>

<br>

### OpenAI APIへのレスポンスの実行

```python
response = ask_chatgpt(key, model, second_message)
print(response)
```


### 出力結果

```python
entity_categories = {
    '風量・風力': ['風', '強い', '風量', '弱い'],
    '操作性・機能性': ['簡単', 'ボタン'],
    'デザイン・サイズ': ['大きい', '大きさ', '小さい'],
}
```

上記のように商品レビューで言及されたエンティティを生成された特徴カテゴリーに分類することができました。
こちらのプロンプト作成においては、以下の点がポイントです。
* **最初にカテゴリー作成をする目的を伝える**
    * 先ほどのカテゴリー作成と同様です。今回は以下のような文言としました。
        * <u>商品レビューに登場する頻出単語をカテゴリーに分類したいと考えています。</u>

<br>

* **アウトプットの例を明示する**
    * こちらもカテゴリー作成と同様です。

<br>

* **一度にカテゴリー作成と分類を実施しない**
    * 今回カテゴリー作成とエンティティの分類は2回に分けて実施していますが、1回のプロンプトで両方実施すれば良いのでは？という疑問をお持ちの方もいらっしゃるかと思います。
    確かに1回で両方実施することも可能ではあるのですが、生成AIは人間と同じで1回のリクエストで複数のことを実施すると精度が下がる傾向があるようです。ですので、今回はカテゴリー作成と、エンティティの分類の2回に分けて実施することとしました。
    実際に1回のプロンプトで実施することも試してみましたが、2回に分けた方が精度は良いように感じました。


プロンプト作成以外のポイントとして、実際のシステムでは以下のような処理を加えています。
なお、以下の処理もChatGPTにプログラムの作成を手伝ってもらっています。

* **entity_categoriesから重複を削除する**
    * プロンプト内で同じエンティティは使わないように記載しているものの、複数のカテゴリーに対して1つのエンティティが使用されることがありました。そのため、重複がある場合には削除する処理を実装しています。

<br>

* **entity_categoriesで使用されなかったエンティティのリストを作成し、ユーザーが必要に応じて追加・削除できるようにする**
    * GPTが生成したカテゴリーやエンティティの分類が必ずしも正しいわけではないため、ユーザーが修正できるようなUIを準備しています。

# まとめ
今回は生成AIを使用した商品レビューのカテゴリー作成・分類について記事にしてみました。
OpenAI APIを使うことで、簡単にLLMを使用したカテゴリー作成やエンティティ分類を実施することができました。プロンプトでは得たい回答を得るために工夫すべき点があることが理解でき、また、LLMに全て頼るのではなく前後で求める結果を得るための前処理・後処理を組み込むことも大切だと感じました。
皆様に少しでも参考にしていただけると嬉しいです。

# その他記事へのリンク
* [第一回 | 商品レビュー分析のリニューアルに伴う自動化システム構築について](https://qiita.com/venect_qiita/items/afc8e35a579f494d7d60)
* [第二回 | Google Natural Language API](https://qiita.com/venect_qiita/items/83607a124cad9dcb4e8a)
* 第三回 | ChatGPT
* [第四回 | MeCab](https://qiita.com/venect_qiita/items/5123b266478c4796db02)