GPT-4VのFew Shot Learning

## 概要
　GPT-4V(GPT-4 Trubo with Vision)が登場して数ヶ月。日本語は苦手とのこと[^1]だったので、そんな使えないんだろうと思ってましたが、実は結構イケるとも聞いたので今回触ってみようと思います。
　本記事の目的は、GPT-4Vを触ったことがない人に向けて、１分くらいで「あぁ、こんな感じなのね」とか「がんばればアレに使えそうだな」といったことを感じてもらうことを目的としています。
 
[^1]:。`Non-English: The model may not perform optimally when handling images with text of non-Latin alphabets, such as Japanese or Korean.` https://platform.openai.com/docs/guides/vision/limitations

### 今回のタスク
　本記事では、コインパーキングの看板情報を読み取ることをタスクをとして設定します。
　看板からの情報だけでも、１時間毎の料金、土日や時間帯による金額の差、入庫から何時間まではいくら、といったように様々な情報や表記があります。今回はシンプルなタスクにしたいので、ある１社（タイムズ）の看板だけを対象とします。また単位時間あたりの課金額（基本料金というらしい）の読み取りを対象とします。

※図はコインパーキング各社の看板。住所や電話番号は記事に貼り付ける際、塗りつぶしています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75296/05bf036e-8dc5-c99d-58d9-e72699c77c74.png)

## 方針

　精度良く情報を抽出するにはプロンプトエンジニアリングを頑張る必要があります。ただ闇雲に様々なテクニックを試すのは大変なのでAzure OpenAI Serviceの[GPT-4 Turbo with Vision を使用したプロンプト設計の概要](https://learn.microsoft.com/ja-jp/azure/ai-services/openai/concepts/gpt-4-v-prompt-engineering)を参考にします。
　今回のタスクでは「例の追加（adding-examples）」を試すと精度が上がりそうです。これはいわゆる**Few Shot Learning**と呼ばれる方法で、ユーザーが準備した入出力の例をプロンプトに入れて回答精度を上げる方法です。
 
※図はGPT-4Vのfew shot learningの例(GPT-4 Turbo with Vision を使用したプロンプト設計の概要より)
<img width="400" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75296/5342ef57-13e4-e16c-1da4-fc433f3df48a.png">


 

## コード

### tips
　コードは[公式サイト](https://platform.openai.com/docs/guides/vision)に乗っているコードとほぼ同じなので解説は省略します。細かい留意点のみ以下にまとめます。

* **Few Shotの書き方**
  * Few Shotの入出力例はそれぞれ`user`と`assistant`ロールに記載しました
  * すべて`user`ロールに書く意見も見かけましたが[^2]、GPT-4Vで私が試した所ロールを分けたほうが回答が安定するため分けることにします
  * 私見：いつもlangchainに頼り切っているので、どちらのほうが適切なのか把握していません。もし知っている方がいましたら教えてください<(_ _)>
* **max_tokens(出力トークン数の上限)**
  * デフォルトのmax_tokensは非常に小さいため、適当な値を設定しましょう
  * 私見：とりあえず、最大の4096を設定しておけば良いのでは？
* **detail**
  *  入力画像を解像度高く読み取る`detail : high`か、粗く読み取る`detail : low`か選ぶことができます
  *  lowの場合、消費トークン数は85tokenで固定です
  *  hightのトークン数の計算は少し複雑です[^3]。先程の看板画像程度なら1000token弱程度ですが、画像のピクセル数に応じて増加します
  *  私見：今回の看板から文字を読取る場合だと、lowでは難しそうでした

[^3]:https://openai.com/pricing

[^2]:https://community.openai.com/t/few-shot-learning-with-gpt-4-is-it-needed-and-what-is-best-practice/455716

### FewShotコード例

以下の4つの画像をFew-Shotに使用し、出力してほしい回答文を4つ作成します。質問文も出力してほしい回答文と整合性があるような質問文を設定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75296/251252b7-0a8e-1048-23e4-e574c0e8ed4e.png)
``` python3
question ="""この看板の画像から、基本料金の情報を抽出し箇条書きで出力してください。
出力方法は「* XXの基本料金はYY分ごとにZZ円です。」という形式で出力してください。
最大料金は記載してはいけません。"""

qa_examples = [
    {
        "input_path":"./sampleimgs/signboard_001.png",
        "output":"* 8:00-22:00の基本料金は40分ごとに220円です。\n* 22:00-8:00の基本料金は60分ごとに110円です。"
    },
    {
        "input_path":"./sampleimgs/signboard_002.png",
        "output":"* 8:00-22:00の基本料金は60分ごとに330円です。\n* 22:00-8:00の基本料金は60分ごとに110円です。"
    },
    {
        "input_path":"./sampleimgs/signboard_003.png",
        "output":"* 8:00〜20:00の基本料金は12分ごとに440円です。\n* 20:00〜8:00の基本料金は60分ごとに110円です。"
    },
    {
        "input_path":"./sampleimgs/signboard_004.png",
        "output":"* 終日の基本料金は12分ごとに110円です。"
    },
]

few_shot_messages = []
for qa_example in qa_examples:
    few_shot_messages.append({
        "role": "user",
        "content": [
            {"type": "text", "text":question},
            {"type": "image_url","image_url": {"url": f"data:image/jpeg;base64,"+encode_image(qa_example['input_path']), "detail":"high"}},
        ],    
    })
    few_shot_messages.append({
        "role": "assistant",
        "content":[{"type": "text", "text": qa_example['output']}]
    })
```

Few Shotを会話履歴の先頭に付けて、実行します。
```python
image_path = "./sampleimgs/signboard_005.png"
client = OpenAI()
response = client.chat.completions.create(
    model="gpt-4-vision-preview",
    messages= few_shot_messages + [
        {
            "role": "user",
            "content":[
                {"type": "text", "text": question},
                {"type": "image_url","image_url": {
                    "url": f"data:image/jpeg;base64,"+encode_image(image_path), 
                    "detail":"high"
                }},
            ],
        },
    ],
    max_tokens=4096,
)
print(response)
```
## 結果
9つの画像で、GPT-4V + Few-Shotを使用して情報抽出した結果をまとめます。

||入力  |出力  |備考  |
|:---:|:---:|:---:|:---:|
|1|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75296/a9922500-e9c1-a527-c842-45298f02edb6.png)|終日の基本料金は10分ごとに330円です。|適切に回答できています。<br><br>completion_tokens=20, prompt_tokens=4438, total_tokens=4458|
|2|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75296/34632b2a-9b92-df9b-c6bf-77ac287dbb0a.png)|8:00〜22:00の基本料金は12分ごとに440円です。<br>22:00〜8:00の基本料金は60分ごとに110円です。|適切に回答できています。<br><br>completion_tokens=48, prompt_tokens=4438, total_tokens=4486|
|3|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75296/e66ff6e5-73cf-707d-ff34-a2f286d9e268.png)|8:00〜20:00の基本料金は30分ごとに330円です。<br>20:00〜8:00の基本料金は60分ごとに110円です。|適切に回答できています。<br><br>completion_tokens=48, prompt_tokens=4778, total_tokens=4826|
|4|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75296/81730579-ac3f-cbd6-b3e9-239fa5a7f799.png)|終日の基本料金は30分ごとに110円です。|12分440円の情報が回答できていません。30分110円も終日でないため、誤りです。<bar>画像がぼやけているのが原因でしょうか？<br><br>completion_tokens=48, prompt_tokens=4778, total_tokens=4826|
|5|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75296/b7df043a-ccc3-c2b7-27a5-73dadf756181.png)|8:00〜19:00の基本料金は10分ごとに330円です。<br>19:00〜8:00の基本料金は60分ごとに110円です。|適切に回答できています。<br><br>completion_tokens=48, prompt_tokens=4778, total_tokens=4826|
|6|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75296/e796d2e1-3a0e-7286-92a1-671d6df5c9b1.png)|月〜土の基本料金は15分ごとに440円です。<br>日・祝の基本料金は15分ごとに440円です。|適切に回答できています。<br><br>completion_tokens=48, prompt_tokens=5118, total_tokens=5166|
|7|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75296/ee29abda-07a7-170f-3aaf-a7614ff272fe.png)|8:00〜20:00の基本料金は15分ごとに330円です。<br>20:00〜8:00の基本料金は60分ごとに110円です。|適切に回答できています。<br><br>completion_tokens=56, prompt_tokens=4778, total_tokens=4834|
|8|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75296/ad4ead49-2378-2164-1197-de5e85953e7e.png)|0:00〜24:00の基本料金は20分ごとに440円です。|適切に回答できています。<br><br>completion_tokens=24, prompt_tokens=4778, total_tokens=4802|
|9|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/75296/9c9dfae8-935c-100a-b806-cc5737bb835f.png)|0:00〜24:00（全日）の基本料金は15分ごとに440円です。<br>8:00〜18:00の基本料金は30分ごとに500円です。|「8:00〜18:00の基本料金は30分ごとに500円です。」は誤りです。<br><br>completion_tokens=28, prompt_tokens=5118, total_tokens=5146|

　というわけで、だいたい正確に回答できますが、画像が荒い場合やフォーマットが少し異なる場合上手く回答できないこともあるようです。また今回は、回答文を短く指定したためChain of Thoughtをさせることができなかったことも、精度を挙げられなかった理由かもしれません。
 
ちなみに、
* 回答ひとつあたり応答時間は10数秒程度でした
* 金額は入力が5000token(0.05ドル)、出力が50token(0.0015ドル)程度のため8円くらいですかね
    * 高いですね、画像を４枚も使ってfew-shotしてるからでしょう

## GPT-4Vをさらに使いこなすには

Xで見かけたのですが、画像に座標を埋め込んでおくと精度が上がるらしいです。

https://twitter.com/webbigdata/status/1763380312820887820