AI 対応の BigQuery DataFrames パッケージを使用して、構造化データと非構造化データから分析情報を取得

1. 概要

このラボでは、BigQuery Studio の Python ノートブックから BigQuery DataFrames を使用して、Python でデータから分析情報を取得します。Google の生成 AI を使用して、非構造化テキストデータを分析して可視化します。

Python ノートブックを作成して、一般公開されている顧客の苦情データベースを分類し、要約します。これは、任意の非構造化テキストデータで動作するように調整できます。

目標

このラボでは、次のタスクの実行方法について学びます。

  • BigQuery Studio で Python ノートブックを有効にして使用する
  • BigQuery DataFrames パッケージを使用して BigQuery に接続する
  • BigQuery ML と Vertex AI のテキスト エンベディング エンドポイントへの接続を使用して、非構造化テキストデータからエンベディングを作成する
  • BigQuery ML を使用したクラスタ エンベディング
  • BigQuery ML を介して LLM でクラスタを要約する

2. 必要なもの

  • ブラウザ(ChromeFirefox など)
  • 課金を有効にした Google Cloud プロジェクト

始める前に

この Codelab の手順を行うには、BigQuery Studio が有効になっている Google Cloud プロジェクトと、接続された請求先アカウントが必要です。

  1. Google Cloud コンソールのプロジェクト選択ページで、Google Cloud プロジェクトを選択または作成します。
  2. Google Cloud プロジェクトに対して課金が有効になっていることを確認します。詳しくは、プロジェクトで課金が有効になっているかどうかを確認する方法をご覧ください。
  3. 手順に沿って、アセット管理に関する BigQuery Studio を有効にする

BigQuery Studio を準備する

空のノートブックを作成してランタイムに接続します。

  1. Google Cloud コンソールで BigQuery Studio に移動します。
  2. [+] ボタンの横にある [] をクリックします。
  3. [Python ノートブック] を選択します。
  4. テンプレート セレクタを閉じます。
  5. [+ コード] を選択して、新しいコードセルを作成します。
  6. コードセルから BigQuery DataFrames パッケージの最新バージョンをインストールします。次のコマンドを入力します。
    %pip install --upgrade bigframes --quiet
    
    [🞂?] ボタンをクリックするか、Shift + Enter キーを押してコードセルを実行します。

3. 一般公開データセットを読み取る

新しいコードセルで次のコードを実行して、BigQuery DataFrames パッケージを初期化します。

import bigframes.pandas as bpd

bpd.options.bigquery.ordering_mode = "partial"

注: このチュートリアルでは、試験運用版の「部分並べ替えモード」を使用します。このモードでは、pandas のようなフィルタリングと組み合わせることで、より効率的なクエリを実行できます。厳密な順序付けやインデックスが必要な pandas の機能は動作しない場合があります。

消費者苦情データベース

消費者苦情データベースは、Google Cloud の一般公開データセット プログラムを通じて BigQuery で提供されています。これは、消費者向け金融商品およびサービスに関する苦情を収集したもので、データは米国の消費者金融保護局によって収集されています。

BigQuery で bigquery-public-data.cfbp_complaints.complaint_database テーブルをクエリして、消費者苦情データベースを分析します。bigframes.pandas.read_gbq() メソッドを使用して、クエリ文字列またはテーブル ID から DataFrame を作成します。

新しいコードセルで次のコードを実行して、「feedback」という名前の DataFrame を作成します。

feedback = bpd.read_gbq(
    "bigquery-public-data.cfpb_complaints.complaint_database"
)

DataFrame の基本情報を確認する

DataFrame.peek() メソッドを使用して、データの小さなサンプルをダウンロードします。

このセルを実行します。

feedback.peek()

想定される出力:

  date_received                  product ... timely_response  consumer_disputed complaint_id  
0    2014-03-05  Bank account or service ...            True              False       743665   
1    2014-01-21  Bank account or service ...            True              False       678608   
2    2020-12-31          Debt collection ...            True               <NA>      4041190   
3    2014-02-12          Debt collection ...            True              False       714350   
4    2015-02-23          Debt collection ...            True              False      1251358   

注: head() では並べ替えが必要であり、データのサンプルを可視化する場合、通常は peek() よりも効率が低くなります。

pandas と同様に、DataFrame.dtypes プロパティを使用して、使用可能なすべての列とそれに対応するデータ型を確認します。これらは pandas と互換性のある方法で公開されます。

このセルを実行します。

feedback.dtypes

想定される出力:

date_received                   date32[day][pyarrow]
product                              string[pyarrow]
subproduct                           string[pyarrow]
issue                                string[pyarrow]
subissue                             string[pyarrow]
consumer_complaint_narrative         string[pyarrow]
company_public_response              string[pyarrow]
company_name                         string[pyarrow]
state                                string[pyarrow]
zip_code                             string[pyarrow]
tags                                 string[pyarrow]
consumer_consent_provided            string[pyarrow]
submitted_via                        string[pyarrow]
date_sent_to_company            date32[day][pyarrow]
company_response_to_consumer         string[pyarrow]
timely_response                              boolean
consumer_disputed                            boolean
complaint_id                         string[pyarrow]
dtype: object

DataFrame.describe() メソッドは、DataFrame から基本的な統計情報をクエリします。この DataFrame には数値列がないため、null 以外の値の数と個別の値の数の概要が表示されます。

このセルを実行します。

# Exclude some of the larger columns to make the query more efficient.
feedback.drop(columns=[
  "consumer_complaint_narrative",
  "company_public_response",
  "company_response_to_consumer",
]).describe()

想定される出力:

         product  subproduct    issue  subissue  company_name    state ... timely_response  consumer_disputed  complaint_id
count    3458906     3223615  3458906   2759004       3458906  3417792 ...         3458906             768399       3458906
nunique       18          76      165       221          6694       63 ...               2                  2       3458906

4. データを探索する

実際の苦情を確認する前に、DataFrame で pandas のようなメソッドを使用してデータを可視化します。

DataFrame を可視化する

DataFrame.plot.hist() など、組み込みの可視化メソッドがいくつかあります。この DataFrame には主に文字列とブール値のデータが含まれているため、まず集計を行い、さまざまな列について詳しく調べることができます。

各州から寄せられた苦情の数をカウントします。

complaints_by_state = (
  feedback.groupby(
    "state", as_index=False,
  ).size()
  .rename(columns={"size": "total_complaints"})
  .sort_values(by="total_complaints", ascending=False)
)

DataFrame.to_pandas() メソッドを使用して、これを pandas DataFrame に変換します。

complaints_pd = complaints_by_state.head(10).to_pandas()

このダウンロードした DataFrame で pandas の可視化メソッドを使用します。

complaints_pd.plot.bar(x="state", y="total_complaints")

苦情の件数が最も多い州としてカリフォルニア州を示す棒グラフ

他のデータセットと結合する

以前は、州ごとに苦情を確認していましたが、これでは重要なコンテキストが失われます。州によって人口は異なります。米国国勢調査局の全米コミュニティ調査bigquery-public-data.geo_us_boundaries.states テーブルなどの人口データセットと結合します。

us_states = bpd.read_gbq("bigquery-public-data.geo_us_boundaries.states")
us_survey = bpd.read_gbq("bigquery-public-data.census_bureau_acs.state_2020_5yr")

# Ensure there are leading 0s on GEOIDs for consistency across tables.
us_states = us_states.assign(
    geo_id=us_states["geo_id"].str.pad(2, fillchar="0")
)

us_survey = us_survey.assign(
    geo_id=us_survey["geo_id"].str.pad(2, fillchar="0")
)

米国コミュニティ調査では、州を GEOID で識別します。州テーブルと結合して、2 文字の州コード別の人口を取得します。

pops = us_states.set_index("geo_id")[["state"]].join(
  us_survey.set_index("geo_id")[["total_pop"]]
)

次に、このデータを苦情データベースと結合して、人口と苦情の数を比較します。

complaints_and_pops = complaints_by_state.set_index("state").join(
    pops.set_index("state")
)

散布図を作成して、州の人口と苦情の数を比較します。

(
  complaints_and_pops
  .to_pandas()
  .plot.scatter(x="total_pop", y="total_complaints")
)

人口と苦情を比較した散布図

人口と苦情の数を比較すると、いくつかの州が外れ値のように見えます。これらのポイントを特定するには、ポイントラベルをプロットして確認してください。同様に、その理由に関する仮説(ユーザー属性の違い、金融サービス会社の数など)を立ててテストします。

5. エンベディングを計算する

多くの場合、重要な情報はテキスト、音声、画像などの非構造化データに隠されています。この例では、苦情データベース内の有用な情報の多くが、苦情のテキスト コンテンツに含まれています。

AI と従来の手法(感情分析、バッグ オブ ワード、word2vec など)では、非構造化データから定量的な情報を抽出できます。最近では、LLM と密接に関連する「ベクトル エンベディング」モデルを使用して、テキストのセマンティック情報を表す浮動小数点数を生成できます。

データベースのサブセットを選択する

ベクトル エンベディング モデルの実行では、他のオペレーションよりも多くのリソースが使用されます。費用と割り当ての問題を軽減するため、このチュートリアルの残りの部分ではデータのサブセットを選択します。

import bigframes.pandas as bpd

bpd.options.bigquery.ordering_mode = "partial"

feedback = bpd.read_gbq(
    "bigquery-public-data.cfpb_complaints.complaint_database"
)

# Note: if not using ordering_mode = "partial", you must specify these in read_gbq
# for these to affect query efficiency.
# feedback = bpd.read_gbq(
#    "bigquery-public-data.cfpb_complaints.complaint_database",
#     columns=["consumer_complaint_narrative"],
#     filters= [
#         ("consumer_complaint_narrative", "!=", ""),
#         ("date_received", "==", "2022-12-01")])

feedback.shape

2022 年 12 月 1 日に提出された申し立ては約 1,000 件で、データベース全体の行数は約 350 万件です(feedback.shape で確認)。

2022-12-01 のデータと consumer_complaint_narrative 列のみを選択します。

import datetime

feedback = feedback[
    # Filter rows by passing in a boolean Series.
    (feedback["date_received"] == datetime.date(2022, 12, 1))
    & ~(feedback["date_received"].isnull())
    & ~(feedback["consumer_complaint_narrative"].isnull())
    & (feedback["consumer_complaint_narrative"] != "")
    & (feedback["state"] == "CA")

    # Uncomment the following if using free credits for a workshop.
    # Billing accounts with free credits have limited Vertex AI quota.
    # & (feedback["product"] == "Mortgage")
][
    # Filter columns by passing in a list of strings.
    ["consumer_complaint_narrative"]
]

feedback.shape

pandas の drop_duplicates メソッドは、一致する最初の行または最後の行を選択し、それに関連付けられたインデックスを保持しようとするため、行の完全な順序付けが必要です。

代わりに、groupby メソッドを呼び出して集計し、重複する行を削除します。

feedback = (
  feedback.groupby("consumer_complaint_narrative", as_index=False)
  .size()
)[["consumer_complaint_narrative"]]

feedback.shape

エンベディングを生成する

BigQuery DataFrames は、TextEmbeddingGenerator クラスを使用してエンベディング ベクトルを生成します。これは、BigQuery ML の ML.GENERATE_EMBEDDING メソッドに基づいています。このメソッドは、Vertex AI から提供されるテキスト エンベディング モデルを呼び出します。

from bigframes.ml.llm import TextEmbeddingGenerator

embedding_model = TextEmbeddingGenerator(
    model_name="text-embedding-004"
)
feedback_embeddings = embedding_model.predict(feedback)

エンベディングの例を見てみましょう。これらのベクトルは、テキスト エンベディング モデルによって理解されるテキストの意味論的意味を表します。

feedback_embeddings.peek()

想定される出力:

                        ml_generate_embedding_result  \
0  [ 7.36380890e-02  2.11779331e-03  2.54309829e-...   
1  [-1.10935252e-02 -5.53950183e-02  2.01338865e-...   
2  [-7.85628427e-03 -5.39347418e-02  4.51385677e-...   
3  [ 0.02013054 -0.0224789  -0.00164843  0.011354...   
4  [-1.51684484e-03 -5.02693094e-03  1.72322839e-...   

これらのベクトルは多くの次元を持ちます。単一のエンベディング ベクトルを見てみましょう。

feedback_embeddings["ml_generate_embedding_result"].peek().iloc[0]

エンベディングの生成は「部分的な成功」契約に基づいて行われます。つまり、一部の行にエラーがあり、エンベディングが生成されない可能性があります。エラー メッセージは 'ml_generate_embedding_status' 列に表示されます。空の場合はエラーはありません。

エンベディングをフィルタして、エラーが発生していない行のみを含めます。

mask = feedback_embeddings["ml_generate_embedding_status"] == ""
valid_embeddings = feedback_embeddings[mask]
valid_embeddings.shape

6. テキスト エンベディングを使用してクラスタ化する

次に、k 平均法を使用してエンベディングをクラスタリングします。このデモでは、任意の数のグループ(セントロイド)を使用します。本番環境向けのソリューションでは、シルエット法などの手法を使用して重心の数を調整する必要があります。

from bigframes.ml.cluster import KMeans

num_clusters = 5
cluster_model = KMeans(n_clusters=num_clusters)
cluster_model.fit(valid_embeddings["ml_generate_embedding_result"])
clusters = cluster_model.predict(valid_embeddings)
clusters.peek()

エンベディングの失敗をすべて削除します。

mask = clusters["ml_generate_embedding_status"] == ""
clusters = clusters[mask]

ピクチャーを表示して、中心点ごとのコメントの分布を確認します。

clusters.groupby("CENTROID_ID").size()

7. クラスタを要約します。

各クラスタに関連するコメントを入力し、Gemini に苦情の要約を依頼します。プロンプト エンジニアリングは新しい分野ですが、https://www.promptingguide.ai/ など、インターネット上にも優れた例があります。

from bigframes.ml.llm import GeminiTextGenerator

preamble = "What is the main concern in this list of user complaints:"
suffix = "Write the main issue using a formal tone."

# Now let's sample the raw comments and get the LLM to summarize them.
prompts = []
for centroid_id in range(1, num_clusters + 1):
  cluster = clusters[clusters["CENTROID_ID"] == centroid_id]
  comments = "\n".join(["- {0}".format(x) for x in cluster.content.peek(40)])
  prompts.append("{}:\n{}\n{}".format(preamble, comments, suffix))

prompt_df = bpd.DataFrame(prompts)
gemini = GeminiTextGenerator(model_name="gemini-1.5-flash-001")
issues = gemini.predict(X=prompt_df, temperature=0.0)
issues.peek()

Gemini を使用して、要約からレポートを作成します。

from IPython.display import display, Markdown

prompt = "Turn this list of issues into a short, concise report:"
for value in issues["ml_generate_text_llm_result"]:
  prompt += "- {}".format(value)
prompt += "Using a formal tone, write a markdown text format report."

summary_df = bpd.DataFrame(([prompt]))
summary = gemini.predict(X=summary_df, temperature=0.0)

report = (summary["ml_generate_text_llm_result"].values[0])
display(Markdown(report))

8. クリーンアップ

このチュートリアル用に新しい Google Cloud プロジェクトを作成した場合は、作成したテーブルやその他のリソースの追加料金が発生しないように、プロジェクトを削除できます。

9. 完了

BigQuery DataFrame を使用して、構造化データと非構造化データを分析しました。ここまで、Google Cloud の一般公開データセット、BigQuery Studio の Python ノートブック、BigQuery ML、Vertex AI、BigQuery Studio の自然言語から Python への機能について学習しました。お疲れさまでした。

次のステップ