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. 요구사항

  • 브라우저(Chrome 또는 Firefox 등)
  • 결제가 사용 설정된 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에 제공됩니다. 소비자 금융 상품 및 서비스에 대한 불만사항 모음으로, 미국 소비자 금융 보호국(CFPB)에서 데이터를 수집합니다.

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 클래스를 통해 임베딩 벡터를 생성합니다. 이는 Vertex AI에서 제공하는 텍스트 임베딩 모델을 호출하는 BigQuery ML의 ML.GENERATE_EMBEDDING 메서드를 기반으로 합니다.

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 DataFrames를 사용하여 구조화된 데이터와 비정형 데이터를 분석했습니다. 이 과정에서 Google Cloud의 공개 데이터 세트, BigQuery Studio의 Python 노트북, BigQuery ML, Vertex AI, BigQuery Studio의 자연 언어 대 Python 기능을 살펴봤습니다. 앞으로의 활동이 더욱 기대됩니다.

다음 단계