Gemini と ChatGPT4 のスクレイピングのテキスト抽出タスクにおける精度比較 

# 概要

- Gemini と GPT4 と GPT3.5 のスクレイピングのテキスト抽出タスクにおける精度を比較しました。
- GPT4 が圧倒的に優れていて、GPT3.5 と Gemini は同程度の精度でした。

# 結論

後述する前提のもとに次の結果が出ました。

後述の前提から ChatGPT にやや有利であるとも考えられ、またそもそもデータも少ないため、GPT3.5 と Gemini pro の差は誤差のようなものだと考えて良いと思います。
概ね予想通りの結果がでたので、 Gemini ultra に期待しています。
なお実用性を考えて chatGPT は turbo モデルを使用しています。

| model              | 正解数 | 不正解数 | 正解率 |
| ------------------ | ------ | -------- | ------ |
| gpt-4-1106-preview | 39     | 11       | 78%    |
| gpt-3.5-turbo-1106 | 21     | 29       | 42%    |
| gemini-pro         | 18     | 31       | 36%    |

# 動機

今回の記事を書いた動機としては２つあります。

- 個人開発で使うときにどの生成 AI を使うか精度に基づいて選定するため。
- 「生成 AI を試した」という情報は色々と出てくるものの、精度や前提を共有していることが少ないため、まずは自分から情報発信して啓蒙するため。

1 つ目の動機は僕個人として重要な動機です。スクレイピングに ChatGPT を使うことで実装コストを下げることができることはわかりましたが、うまく情報を取得できないこともあります。
なるべく精度を上げたいので、ChatGPT か Gemini か真面目に検討しています。

2 つ目の動機は少しでもより良い世の中になってほしいという思いを込めています。
google は信頼していますし、正しく検証しているに違いありませんが、すごい性能のものができました！と言われても鵜呑みにしてはいけません。色んな人が検証することでクリーンな業界に保つことができます。

また検証にはある程度の数が必要で、前提も積極的に共有する必要があります。精度比較は前提が非常に重要であり、前提が変われば性能の比較結果も変わり得ます。
AI に関する情報は人目を引き付けることを目的として、ろくに前提が共有されていないことが多い印象を受けます。
個人的にはあまり健全とは思っていないため、このような記事で少しでも貢献することができればと思います。

# 前提

## プロンプトの準備

もともと GPT3.5 用に用意していたものをほぼそのまま使っています。
プロンプトの原型ができた経緯については以前書いた検証記事をご覧ください。

https://qiita.com/ogatango/items/df7b42e05a5852c665d0

https://qiita.com/ogatango/items/3ef5e7e87f66ed971411

https://qiita.com/ogatango/items/44b72b060b2767ae2fa4

今回の検証をするにあたり、上記の記事のプロンプトに加えて新たに追加したプロンプトがあります。

- 複数の日付が含まれる場合はそれぞれの日付を抽出する。
- 入場無料の場合は (料金を) 0
- 出力文字列に```json は含めてはいけない。

1 つ目は１つの web ページ上で複数日にまたがるコンサートの情報が掲載されている場合の対策です。
2 つ目は入場無料の場合に料金が 0 であることを抽出できないことがあったため、その対策です。
3 つ目は Gemini が高確率で```json... のようにマークダウンで返してきてしまうのでその対策です。ただしあまり効果がありませんでした。

結果として chatGPT と Gemini で共通のプロンプトは次のようになりました。

````python
"""
 次のフォーマットで値を抽出せよ。
{
    "player":[{"player_name": 演奏者や演奏団体名, "role": 楽器名もしくは役割}],
    "program": [{"composer": 作曲者, "music": 曲名}],
    "title": コンサートのタイトル,
    "concert_date": コンサートの開催日でフォーマットは 0000-00-00,
    "start_time": コンサートの開演時間でフォーマットは 00:00,
    "price":{チケット種別: チケット料金で単位を付けない数値型。入場無料の場合は 0}
}
キーは必ず含ませる。
JSON以外の情報を含めてはいけない。
```json は含めてはいけない。
複数の日付が含まれる場合はそれぞれの日付を抽出する。
player,program,title,priceには元のテキストに含まれる文字列だけを値として使う。
該当する情報がない場合 null にする。
"""
````

なるべく chatgpt と Gemini でプロンプトを揃えていますが、一部異なるところがあります。
そもそもモデルが異なればタスクを実行するのに得意なプロンプトも変わるはずなので今のままでは chagGPT に有利な可能性があります。Gemini 用のプロンプトも本腰を入れて模索するべきだとは思いますが、なかなか手間がかかるので基本的には流用しています。

### chatgpt に渡したプロンプト

chatgpt はメッセージを渡すときに質問とその答えを事前に用意することができます。

```python
messages=[
    {
        "role": "system",
        "content": command,
    },
    {
        "role": "user",
        "content": ask1, # 質問例を渡す
    },
    {
        "role": "assistant",
        "content": answer1, # 回答例を渡す
    },
    {
        "role": "user",
        "content": text,
    }
]
```

このようにすることで精度が上がることが確認できています。
chatGPT には典型的な例と空文字列が渡されたときの例の 2 つを事前に渡しています。

### Gemini に渡したプロンプト

Gemini の場合、 role として USER と MODEL が用意されているため、chatGPT 　と同じような処理ができるかもしれません。ただ手元で次のような contents を渡しても返って精度が下がってしまいました。渡し方に問題があるのかもしれません。

```python
contents = [
  {"role": "USER", "parts": [{"text": text}],
  {"role": "MODEL", "parts": [{"text": answer}]
]
```

ひとまず Gemini には 1 つのメッセージ内で例を渡すことで、そうでない場合と比較して精度を上げることができました。

````python
"""
    次のフォーマットで値を抽出せよ。
{
    "player":[{"player_name": 演奏者や演奏団体名, "role": 楽器名もしくは役割}],
    "program": [{"composer": 作曲者, "music": 曲名}],
    "title": コンサートのタイトル,
    "concert_date": コンサートの開催日でフォーマットは 0000-00-00,
    "start_time": コンサートの開演時間でフォーマットは 00:00,
    "price":{チケット種別: チケット料金で単位を付けない数値型。入場無料の場合は 0}
}
キーは必ず含ませる。
JSON以外の情報を含めてはいけない。
```json は含めてはいけない。
複数の日付が含まれる場合はそれぞれの日付を抽出する。
player,program,title,priceには元のテキストに含まれる文字列だけを値として使う。
該当する情報がない場合 null にする。

質問例1
日時
2023年3月18日（土） 15:00開演
料金
友の会 優待価格
A 5,800円
一般価格
A 6,300円　B 5,300円　C 4,200円　BOX 7,400円

指揮／カーチュン・ウォン
ヴァイオリン／パトリツィア・コパチンスカヤ
曲目／ハルトマン：葬送協奏曲
　　　ラヴェル：ツィガーヌ
　　　ベルリオーズ：幻想交響曲 作品14

回答例1
{
"player":[
    {"player_name": "パトリツィア・コパチンスカヤ", "role": "ヴァイオリン"},
    {"player_name": "カーチュン・ウォン", "role": "指揮"}
    ],
"program": [{"composer": "ハルトマン", "music": "葬送協奏曲"}, {"composer": "ラヴェル", "music": "ツィガーヌ"}, {"composer": "ベルリオーズ", "music": "幻想交響曲 作品14"}],
"title": null,
"concert_date": "2023-03-18",
"start_time": "15:00",
"price":{
    "友の会 優待価格 A": 5800,
    "一般価格 A": 6300,
    "一般価格 B": 5300,
    "一般価格 C": 4200,
    "一般価格 BOX": 7400
}
}
質問例2（空文字列）

回答例2
{
"player": null,
"program": null,
"title": null,
"concert_date": null,
"start_time": null,
"price": null
}"""
````

## 後処理

Gemini ではどうしてもマークダウンで JSON にしてくるのが抑えきれなかったため、chatgpt, Gemini の両方ともテキスト抽出後の処理として両端の ``` という文字と先頭の json という文字を削除する処理を入れています。

## パラメータ

以前 chatGPT でよさそうなパラメータを検証したところ、 temperature を 0 にするのが良さそうでした。
temperature を 0 にした理由としては、ランダム性を少なくするためです。scraping のような正解が一意に定まるタスクにおいてはランダム性はないのが望ましいでしょう。
Gemini に対しては次のように generation_config に 設定を渡すことができます。

```python
config = {"temperature": 0}
contents = [
    prompt,
    text,
]
response = model.generate_content(
    contents,
    generation_config=config,
)
```

## 使うデータ

入力は次の通りで、コンサートの情報が載っている web ページをまるごとテキストして取得したものになります。

```
\n\n\n\n\n\n佐伯周子ピアノリサイタル シューベルト ピアノソナタ完全全曲演奏会 | 東京文化会館\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n本文へ移動\n\n\n個人情報の取り扱いについて\n本ウェブサイト（以下「本サイト」といいます）では、お客様の本サイトの利用の向上、アクセス履歴に基づく広告、本サイトの利用状況の把握等の目的で、クッキー、タグ等の技術を使用します。「同意する」ボタンや本サイトをクリックすることで、上記の目的のためにクッキーを使用すること、また、皆様のデータを提携先や委託先と共有することに同意いただいたものとみなします。同意の取消方法を含めたより詳しい情報は、「クッキーポリシー」をご覧ください。※その他個人情報の取扱いについては、東京都歴史文化財団プライバシーポリシーをご参照ください。\n同意する\n\n\n\n\n\n\n\n\n\n\n\nLanguage\nEnglish\n簡体中文\n繁體中文\n한국어\n\n\nバリアフリー情報\nサイトマップ\nよくあるご質問\n\n\n\n\n\n\n\n\nチケット購入\n交通アクセス\n\n\n\nホームHome\n公演情報Explore Events\n\n公演情報Explore Events\n全ての公演情報\n\n過去の公演情報\n\n\n主催公演\n\n主催公演一覧\n東京音楽コンクール\nワークショップ（参加する）\n託児サービス\n\n\n公演情報一覧へ\n\n\n施設案内Venues\n\n施設案内Venues\n大ホール\n小ホール\n音楽資料室\n\n音楽資料室について\n利用案内\nパート譜の団体貸出\nFAQ（よくあるご質問）\n音楽資料室だより\n\n\n館内マップ\nレストラン・ショップ\n\nフォレスティーユ精養軒\nCafē  HIBIKI\nWaltz\n匠音\n\n\nチケットサービス\n施設案内一覧へ\n\n\n施設を借りるVenue Rentals\n\n施設を借りるVenue Rentals\nホール利用案内\n\nお申込み\n利用の決定から利用料金の納入まで\n公演に係わる準備\n公演当日\nご利用に際してのお願い\n各種申請用紙\nホール概要\nホールの利用時間と利用料金\n附属設備利用料金\n楽屋の概要と利用料金\n\nリハーサル室の利用\r\n                        \nお申込み\nご利用当日\nご利用に際してのお願い\n施設の概要・利用料金\n\n\n\n\n\n\n会議室・応接室利用案内\n\nお申込み\nご利用当日\nご利用に際してのお願い\n施設の概要・利用料金\n附属設備・利用料金\n各種申請用紙\n\n\nロケーション撮影\n施設を借りる一覧へ\n\n\n東京文化会館についてAbout\n\n東京文化会館についてAbout\n館長あいさつ\n音楽監督あいさつ\n施設概要\n建築について\n広報誌・ガイドブック\n\n音脈\n館紹介リーフレット\nアニュアル・レポート\n社会包摂につながるアート活動のためのガイドブック\n\n\n東京文化会館の取り組み\n東京文化会館オフィシャル・パートナー\n東京文化会館メンバーズ\nご支援のお願い\n上野周辺紹介\n採用情報\nウェブサイトについて（ウェブサイト利用規約等）\nウェブアクセシビリティ\nクッキーポリシー\n東京文化会館について一覧へ\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nホーム\n公演情報\n佐伯周子ピアノリサイタル シューベルト ピアノソナタ完全全曲演奏会\n\n\n公演情報Explore Events\n\n\n\n\n\n\n            佐伯周子ピアノリサイタル シューベルト ピアノソナタ完全全曲演奏会                    \n\n\n\n\n\n\n日程\n\n                                                    2024年2月6日（火）19:00開演（18:30開場）\n\n\n\n会場\n東京文化会館 小ホール\n\n\n\nジャンル\nコンサート\n\n\n\n\n\n\n\n\n\n出演\n\nピアノ：佐伯周子\n\n\n\n曲目\n\nシューベルト：\nピアノソナタ イ短調 D845\nピアノソナタ イ長調 D664\nピアノソナタ 嬰へ短調 D571＋D570\n\n\n\n\nチケット情報\n\n\n\n                    料金（税込）\n                \n\n自由3,000円 学生1,000円\n\n\n\n\n発売日\n\n\n2023年10月2日（月）\n\n\n\nインフォメーション\n\n\n\nお問合せ\n\nピアノミュージックジャパン　080-5528-3281\n\n\n\n\n\n\n\n\n\n\n東京文化会館Tokyo Bunka Kaikan\n\n〒110-8716東京都台東区上野公園5-45\n\n\n\n東京文化会館 TEL\n03-3828-2111\n\n\n\n\nチケットサービス\n03-5685-0650\n\n\n\n\nよくあるご質問\nお問合せ\n関連リンク\n\n\n\n\n\n公演情報Explore Events\n\n全ての公演情報\n主催公演\n\n\n\n施設案内Venues\n\n大ホール\n小ホール\n音楽資料室\n館内マップ\nレストラン・ショップ\nチケットサービス\n\n\n\n施設を借りるVenue Rentals\n\nホール利用案内\n会議室・応接室利用案内\nロケーション撮影\n\n\n\n東京文化会館についてAbout\n\n館長あいさつ\n音楽監督あいさつ\n施設概要\n建築について\n広報誌・ガイドブック\n東京文化会館の取り組み\n東京文化会館オフィシャル・パートナー\n東京文化会館メンバーズ\nご支援のお願い\n上野周辺紹介\n採用情報\nウェブサイトについて（ウェブサイト利用規約等）\nウェブアクセシビリティ\nクッキーポリシー\n\n\n\n\n\n\nCopy Rights 2019 Tokyo Bunka Kaikan\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
```

AI の手にかかれば次のようなきれいなデータが抽出できます。

```json
{
  "player": [
    {
      "player_name": "佐伯周子",
      "role": "ピアノ"
    }
  ],
  "program": [
    {
      "composer": "シューベルト",
      "music": "ピアノソナタ イ短調 D845"
    },
    {
      "composer": "シューベルト",
      "music": "ピアノソナタ イ長調 D664"
    },
    {
      "composer": "シューベルト",
      "music": "ピアノソナタ 嬰へ短調 D571＋D570"
    }
  ],
  "title": "佐伯周子ピアノリサイタル シューベルト ピアノソナタ完全全曲演奏会",
  "concert_date": "2024-02-06",
  "start_time": "19:00",
  "price": {
    "自由": 3000,
    "学生": 1000
  }
}
```

web サイトとしては[びわ湖ホール](https://www.biwako-hall.or.jp/performance) と [東京文化会館](https://www.t-bunka.jp/stage/) の公演情報からランダムに 50 個選んで使用しました。
この記事を見た人が少しでもコンサートに興味を持っていただけるとクラシックファンとしてはとても嬉しいです。

# 抽出の具体例の紹介

抽出タスクの具体的なイメージを持っていただくために具体例を用意しました。

## GPT4 の気が利く例

例えば[次の Web ページ](https://www.biwako-hall.or.jp/performance/20230128_century-orchestra)のテキストを GPT4 も GPT3.5 も Gemini も全体的にうまく取れていますが、GPT4 は料金のところをよりうまく取ることができています。

料金は次のようなテキストになっています。

```json
S席：4,500円（4,000円）
A席：3,500円（3,000円）
B席：2,500円（2,000円）
※全席指定・税込
※（ ）内びわ湖ホール友の会会員料金／びわ湖ホールのみの取り扱い
```

GPT3.5 は次のような JSON データを摘出しました。

```json
"price": {
    "S席": 4500,
    "A席": 3500,
    "B席": 2500
  }
```

それに対して GPT4 は次のような JSON データを抽出しました。

```json
 "price": {
    "S席": 4500,
    "A席": 3500,
    "B席": 2500,
    "友の会会員 S席": 4000,
    "友の会会員 A席": 3000,
    "友の会会員 B席": 2000
  }
```

( ) 内の料金が友の会会員のみの料金であることからうまいこと抽出していて感動しました。
GPT4 のほうが気が利く感じがします。

## GPT4 でもうまく抽出できなかった例

次のように 1 日に複数回公演がある例でよく失敗していました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/415560/cd21ac36-fe1b-65ae-b829-4decc584bea3.png)
本当はこんな感じで取ってほしいのですが、

```json
"concert_date": [
    "2024-03-20",
    "2024-03-20"
  ],
  "start_time": [
    "13:00",
    "17:00"
  ],
```

実際にはこんな感じになってしまいました。

```json
"concert_date": "2024-03-20",
  "start_time": null,
```

日も開演時間も異なる場合はうまく抽出できているのですが、公演日もしくは開演時間が 1 つの場合はうまくいかないケースが多かったです。
これはプロンプトを改善することでなんとかなるかもしれないのでもうすこし検証してみたいです。

## Gemini の失敗例

GPT4 の失敗例のような失敗もあるのですが、興味深い失敗例がありました。

あるコンサートの抽出結果です。

```json
{
  "player": [
    {
      "player_name": "輝本力",
      "role": "バス"
    },
    {
      "player_name": "武部明人",
      "role": "メテススパライナ"
    },
    {
      "player_name": "瞬見興子",
      "role": "スプラトイナ"
    },
    {
      "player_name": "木水直子",
      "role": "スパラトイナ"
    },
    {
      "player_name": "福成紀素子",
      "role": "スパラトイナ"
    }
  ],
  "program": [
    {
      "composer": "ムスルグシキスキー",
      "music": "『死の歌と芋めー』\n第1本 子先歌\n第2本 セルバード\n第3本 トレプルク\n第4本 大会大"
    }
  ],
  "title": "二次係ロシア日語言学研究会 第25回詩合時出控探催酒屋 「一指しのロシアの歌を集して」",
  "concert_date": "2024-02-26",
  "start_time": "19:00",
  "price": {
    "自田": 4000,
    "学生": 3000
  }
}
```

本来 `メゾソプラノ`と抽出すべきところを `メテススパライナ`となっていたり、料金の `自由`のところが `自田`となっています。
何が起きているんでしょう。
自由が自田になるのはまるでテキストを目で見て写し間違えたかのような挙動です。
Gemini はマルチモーダルを特に推しているようですが、テキストで推測が難しかったら自動的にテキストを画像として見たりするんでしょうか。このあたり詳しい方がいたらぜひ教えていただきたいです。

具体例については休日に時間をとって、もう少し追加したいです。

# おまけ

処理速度もついでに測ってみました。

| model              | 平均処理時間 |
| ------------------ | ------------ |
| gpt-4-1106-preview | 44.95 秒     |
| gpt-3.5-turbo-1106 | 19.73 秒     |
| Gemini-pro         | 5.83 秒      |

ただこれは実行タイミングでかなりブレる印象です。 gpt-3.5 に間違ったプロンプトをわたしてしまったとき平均 8 秒程度で回っていたのでもっと様々なタイミング、多くのデータ量でやらないと正しい検証結果が得られそうにないです。

# 最後に

諸般の事情 [^1] により個人開発をしばらくやめていましたが、 AI が実用的で便利なので開発意欲が湧いてきました。
AI を機能の一つとして組み込んだり、CodeRabbit にレビューしてもらったり、AI が本格的に開発に関わるようになってきたように感じます。

AI を正しく使い、正しく発展させていくためにも、今後も検証記事は出していきたいです。

# 参考資料

[Vertex AI SDK のイントロ](https://cloud.google.com/vertex-ai/docs/generative-ai/multimodal/sdk-for-Gemini/Gemini-sdk-overview-reference?hl=en)
[マルチモーダルリクエストの送り方の例](https://cloud.google.com/vertex-ai/docs/generative-ai/multimodal/send-multimodal-prompts)

[^1]: おもに splatoon にハマっていた