BigQuery MLとマルチモーダルな Gemini による非構造データの構造化
概要
BigQuery ML では Gemini をはじめとする LLM を使いテキストの生成を行うことができます。
しかも SQL ベースで書くことができるため BigQuery に格納されたデータを手軽に処理することができます。
以下のようにプロンプトに指示を書くことで LLM 側で処理した結果を出力することができます。
SELECT *
FROM
ML.GENERATE_TEXT(
MODEL `mydataset.gemini_model`,
(SELECT 'What is the purpose of dreams?' AS prompt));
この ML.GENERATE_TEXT
にはテキストだけでなく、画像や音声、動画といったオブジェクトを入力することでマルチモーダルな処理を実現することができます。
今回は機能を確かめていきます。
料金感
今回使用する gemini-1.5-flash-002
ベースで話します。
まず、入力と出力で料金が違います。
入力が 128K 入力トークン以下の場合以下になります。
- 文字: $0.00001875 / 1000 文字
- 画像: $0.00002 / 画像
- 音声: $0.000002 / 秒
出力テキストは $0.000075 / 1,000 文字 となるため、文字数は抑えるとコスパが良さそうです。
今回検証する分には料金がほぼかからなそうで安心しました。
モデルごとにベースとなる料金が違うので詳しくはこちらを参照ください。
検証に使うオブジェクト
画像は Gemini で生成した猫の画像を使います。
物体検出ができるか調べたいので「写真に写っている動物はなんですか?」とでも聞いてみようと考えています。
次に PDF 形式の文章データとして、サンプルの請求書を入力してみます。
勘定された項目を構造化してくれるといいなと思っています。
音声は Vertex AI の Text to Speech を使ってサンプルで音声を作りました。
文字起こしができればいいと思っています。
今回は検証しませんが、注意として動画のみ制約があり最長 2 分の動画までしか入力に使えないようです。
手順
オブジェクトテーブルの作成
オブジェクトテーブルとは GCS などのオブジェクトストレージに格納されたファイルを BigQuery から参照することができるテーブルです。
オブジェクトテーブルをこちらのドキュメントを参考に作成します。
CREATE OR REPLACE EXTERNAL TABLE `bqml_gemini.objects`
WITH CONNECTION `asia-northeast1.vertexai-external-connection`
OPTIONS (
object_metadata = 'SIMPLE',
uris =
['gs://bqml_multimodal_hogehoge/*']);
bqml_gemini.objects
テーブルを SELECT してみると以下のような情報を参照することができます。
- uri
- 作成日時(unix time)
- content type
- size
- md5 hash
- 更新日時(timestamp)
このとき外部接続を作る必要があるが、Gemini を呼び出す際に使用するものと併用するため以下のロールを付与します。
- Storage オブジェクト閲覧者 ... オブジェクトテーブルから GCS に配置したファイルを読み取るためのロール
- Vertex AI ユーザー ... Gemini をデプロイしたリモートモデルを呼び出すためのロール
リモートモデルの作成
今回デプロイするモデルは gemini-1.5-flash-002
にしました。
(Gemini 2.0 リリース直後だったので使ってみたかったのですが、 asia-northeast1
ではまだ使えなかった)
CREATE OR REPLACE MODEL `bqml_gemini.gemini`
REMOTE WITH CONNECTION `asia-northeast1.vertexai-external-connection`
OPTIONS (ENDPOINT = 'gemini-1.5-flash-002');
使用できる LLM は以下を参照してください。
これで準備完了です。
実験
1. 動物の写真
以下のように SQL を実行してみます。
prompt
に画像をどのように解釈してほしいのかを含めます。
temperature
は回答の自由度を制御するためのパラメータで、[0.0 ~ 2.0] の範囲で指定しまう。
※ こちら ドキュメント では 2.0
までと記されていますが、実際は 1.0 までしか指定できませんでした。テキストと画像とで違いなどあるのでしょうか?
0
に近いほど確定的であり、最も高い確率のレスポンスが常に選択されることを意味します。
今回はサンプルの SQL で最も使用されている 0.2
に指定してみます。
SELECT
uri,
ml_generate_text_llm_result
FROM
ML.GENERATE_TEXT( MODEL `bqml_gemini.gemini`,
( select * from `bqml_gemini.objects` where content_type = 'image/jpeg' -- 画像はひとつなので content type で指定
),
STRUCT( 0.2 AS temperature,
'写っている動物はなんですか?' AS PROMPT,
TRUE AS FLATTEN_JSON_OUTPUT));
結果、猫とだけでなく特徴や背景まで説明してれています。
写真にはオレンジ色の猫が写っています。毛が長く、ふわふわしていて、明るい色の目を持っています。窓辺のカーペットの上に横たわっています。
2. 請求書のフォーマット
請求書が長い場合 max_output_tokens
で回答を長くします。
(課金体系が入出力のトークン長で決まるためデカくしすぎるとコスパが分かる可能性もあるので注意)
SELECT
uri,
ml_generate_text_llm_result
FROM
ML.GENERATE_TEXT( MODEL `bqml_gemini.gemini`,
( select * from `bqml_gemini.objects` where content_type = 'application/pdf'
),
STRUCT( 1024 AS max_output_tokens,
0.2 AS temperature,
'請求書から請求項目を JSON で構造化して出力してください。' AS PROMPT,
TRUE AS FLATTEN_JSON_OUTPUT));
結果、項目が抜き出せているいことが確認できました。
が、json 形式で出力してくださいと指定したせいか先頭に余計な文字列が付いているのが気になります。
```json
{
"請求書情報": {
"No": "123456-123",
"請求日": "2021/12/21",
"会社名": "株式会社 日本サンプル",
"住所": "〒123-1234 東京都世田谷区〇〇〇 1-2-3",
"電話番号": "03-1234-5678",
"メールアドレス": "info@japansample.com",
"請求先会社名": "株式会社サンプル 〇〇支社",
"請求先住所": "△△△ビル 1F",
"請求先担当者": "△△ □□ 様",
"請求金額(税込)": "¥407,957,393",
"支払期限": "2021/12/31"
},
"請求明細": [
{
"商品名/品目": "○○○○○○ サンプル タイプA",
"数量": "12,345,678 個数",
"単価": "10",
"金額": "123,456,780",
"備考": ""
},
{
"商品名/品目": "△△△△ システム機器( 自動調整タイプ )",
"数量": "2 台",
"単価": "123,456,789",
"金額": "246,913,578",
"備考": "担当:〇〇"
},
{
"商品名/品目": "△△△△ システムの取付作業",
"数量": "3 人",
"単価": "30,000",
"金額": "90,000",
"備考": ""
},
{
"商品名/品目": "△△△△ システムの操作説明 講習会",
"数量": "40 個数",
"単価": "4,000",
"金額": "160,000",
"備考": ""
},
{
"商品名/品目": "□□□□○○○○素材 ( ✖✖ を含む )",
"数量": "50 Kg",
"単価": "5,000",
"金額": "250,000",
"備考": ""
}
],
"合計": {
"小計(税抜)": "¥370,870,358",
"消費税(10%)": "¥37,087,035",
"合計(税込)": "¥407,957,393"
},
"備考欄": ""
}
3. 音声の文字起こし
音声は英語で「I'm a perfect human.」と男の人の声で再生されます。
SELECT
uri,
ml_generate_text_llm_result
FROM
ML.GENERATE_TEXT( MODEL `bqml_gemini.gemini`,
( select * from `bqml_gemini.objects` where content_type = 'audio/wav'
),
STRUCT( 0.2 AS temperature,
'次の音声を文字起こししてください' AS PROMPT,
TRUE AS FLATTEN_JSON_OUTPUT));
結果、文字起こししてくれただけでなく、日本語プロンプトだったからか日本語に訳してくれています。
私は完璧な人間です。
おまけ
temperature
を上げるとどの程度出力にブレがあるのか調べてみようと思います。
題材としては猫の画像を使ってみます。
temperature=0
の出力は一定で出力は変わらずでした。
temperature=1
で指定すると 2 回目の実行で早速出力内容が変化しました。
1回目の出力では temperature=0
より詳しい画像の説明が得られているのでよい結果が得られる可能性もあれば、2 回目のようにシンプルな出力を返すこともあるようです。
写真にはオレンジ色の猫が写っています。毛が長く、ふわふわしていて、明るい色の目を持っています。窓辺のカーペットの上に横たわっています。
写真にはオレンジ色の猫が写っています。毛がふさふさしていて、緑色の目と白いあごヒゲが特徴です。カーペットの上でくつろいでいて、窓から差し込む太陽の光を浴びています。
写真はオレンジ色の長毛の猫です。窓のそばのカーペットの上に横になっています。
結論
オブジェクトテーブルを活用することで、GCS に保存されている画像、PDF、音声といった非構造化データを、Geminiを用いて構造化できることが理解できました。
しかしながら、本番環境での利用にはいくつかの課題が存在します。
出力形式を詳細に制御することが難しく、例えばGPTのresponse_formatのようなパラメータがあれば、出力形式をJSONなどで明確に指定できて非常に便利だと感じました。
現状では、few-shot を用いて出力形式の例をいくつか提示することで精度を向上させることはできますが、入力トークン数が増加し、結果としてコストが高くなってしまうという問題があります。
また、LLMの出力であるため、常に同じ値や形式で結果が返されるとは限りません。
出力のランダム性を制御するパラメータ(temperatureなど)は存在しますが、入力データによっては出力が大きく変動することが多々あります。
そのため、出力のばらつきを考慮した運用設計が必要となります。
例えば、出力結果の検証プロセスを組み込むなどの対策が考えられます。
BigQuery ML の ML.GENERATE_TEXT
は便利でコスパも良いので、用法用量をしっかり守れば十分に結果が出せそうなことが確認できました。
Discussion