Receba insights de dados estruturados e não estruturados usando o pacote BigQuery DataFrames com capacidade de IA

1. Visão geral

Neste laboratório, você vai usar o BigQuery DataFrames de um notebook Python no BigQuery Studio para extrair insights dos dados usando Python. Use a IA generativa do Google para analisar e visualizar dados de texto não estruturados.

Você vai criar um notebook Python para categorizar e resumir um banco de dados público de reclamações de clientes. Isso pode ser adaptado para funcionar com qualquer dado de texto não estruturado.

Objetivos

Neste laboratório, você aprenderá a fazer o seguinte:

  • Ativar e usar notebooks Python no BigQuery Studio
  • Conectar-se ao BigQuery usando o pacote BigQuery DataFrames
  • Criar embeddings de dados de texto não estruturados usando o BigQuery ML e a conexão com um endpoint de embedding de texto na Vertex AI
  • Agrupar embeddings usando o BigQuery ML
  • Resumir clusters com um LLM usando o BigQuery ML

2. Requisitos

  • Use um navegador, como o Chrome ou o Firefox.
  • Tenha um projeto do Google Cloud com o faturamento ativado.

Antes de começar

Para seguir as instruções deste codelab, você vai precisar de um projeto do Google Cloud com o BigQuery Studio ativado e uma conta de faturamento conectada.

  1. No console do Google Cloud, na página de seletor de projetos, selecione ou crie um projeto do Google Cloud.
  2. Verifique se o faturamento está ativado para o projeto do Google Cloud. Saiba como verificar se o faturamento está ativado em um projeto.
  3. Siga as instruções para ativar o BigQuery Studio para gerenciamento de recursos.

Preparar o BigQuery Studio

Crie um notebook vazio e conecte-o a um ambiente de execução.

  1. Acesse o BigQuery Studio no console do Google Cloud.
  2. Clique em ao lado do botão +.
  3. Selecione Notebook Python.
  4. Feche o seletor de modelos.
  5. Selecione + Código para criar uma célula de código.
  6. Instale a versão mais recente do pacote BigQuery DataFrames na célula de código. Digite o seguinte comando:
    %pip install --upgrade bigframes --quiet
    
    Clique no botão 🞂 ou pressione Shift + Enter para executar a célula de código.

3. Ler um conjunto de dados público

Inicialize o pacote do BigQuery DataFrames executando o seguinte em uma nova célula de código:

import bigframes.pandas as bpd

bpd.options.bigquery.ordering_mode = "partial"

Observação: neste tutorial, usamos o "modo de ordenação parcial" experimental, que permite consultas mais eficientes quando usado com filtragem semelhante ao pandas. Alguns recursos do pandas que exigem uma ordenação ou um índice estrito podem não funcionar.

Banco de dados de reclamações de consumidores

O Consumer Complaint Database (em inglês) é fornecido no BigQuery pelo Programa de conjuntos de dados públicos do Google Cloud. É uma coleção de reclamações sobre produtos e serviços financeiros para consumidores. Os dados são coletados pelo Consumer Financial Protection Bureau (CFPB, na sigla em inglês) dos Estados Unidos.

No BigQuery, consulte a tabela bigquery-public-data.cfbp_complaints.complaint_database para analisar o banco de dados de reclamações dos consumidores. Use o método bigframes.pandas.read_gbq() para criar um DataFrame com base em uma string de consulta ou um ID de tabela.

Execute o seguinte em uma nova célula de código para criar um DataFrame chamado "feedback":

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

Descobrir informações básicas sobre um DataFrame

Use o método DataFrame.peek() para baixar uma pequena amostra dos dados.

Execute esta célula:

feedback.peek()

Resposta esperada:

  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   

Observação: head() exige ordenação e geralmente é menos eficiente do que peek() se você quiser visualizar uma amostra de dados.

Assim como no pandas, use a propriedade DataFrame.dtypes para conferir todas as colunas disponíveis e os tipos de dados correspondentes. Eles são expostos de maneira compatível com pandas.

Execute esta célula:

feedback.dtypes

Resposta esperada:

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

O método DataFrame.describe() consulta algumas estatísticas básicas do DataFrame. Como esse DataFrame não contém colunas numéricas, ele mostra um resumo da contagem de valores não nulos e do número de valores distintos.

Execute esta célula:

# 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()

Resposta esperada:

         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. Como analisar os dados

Antes de analisar as reclamações reais, use os métodos semelhantes ao pandas no DataFrame para visualizar os dados.

Visualizar o DataFrame

Há vários métodos de visualização integrados, como DataFrame.plot.hist(). Como esse DataFrame contém principalmente dados de string e booleanos, podemos primeiro fazer alguma agregação para saber mais sobre várias colunas.

Conte quantas reclamações são recebidas de cada estado.

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

Converta isso em um DataFrame do pandas usando o método DataFrame.to_pandas().

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

Use métodos de visualização do pandas no DataFrame baixado.

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

gráfico de barras mostrando a Califórnia como o estado com mais reclamações

Mesclar com outros conjuntos de dados

Antes, você analisou as reclamações por estado, mas isso perde um contexto importante. Alguns estados têm populações maiores que outros. Faça uma junção com um conjunto de dados de população, como a Pesquisa da Comunidade Americana do Bureau do Censo dos EUA e a tabela 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")
)

A Pesquisa da Comunidade Americana identifica os estados por GEOID. Faça uma junção com a tabela de estados para receber a população por código de estado de duas letras.

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

Agora, junte isso ao banco de dados de reclamações para comparar a população com o número de reclamações.

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

Crie um gráfico de dispersão para comparar as populações dos estados com o número de reclamações.

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

um gráfico de dispersão comparando a população com as reclamações

Alguns estados parecem ser outliers ao comparar a população com o número de reclamações. O leitor pode fazer um exercício para criar um gráfico com rótulos de pontos e identificar esses valores. Da mesma forma, crie algumas hipóteses sobre por que isso pode acontecer (por exemplo, diferentes dados demográficos, número diferente de empresas de serviços financeiros etc.) e teste-as.

5. Calcular embeddings

Muitas vezes, informações importantes ficam ocultas em dados não estruturados, como texto, áudio ou imagens. Neste exemplo, grande parte das informações úteis no banco de dados de reclamações está contida no conteúdo de texto da reclamação.

A IA e as técnicas tradicionais, como análise de sentimentos, "saco de palavras" e word2vec, podem extrair algumas informações quantitativas de dados não estruturados. Mais recentemente, os modelos de "embedding de vetor", que estão intimamente relacionados aos LLMs, podem criar uma sequência de números de ponto flutuante que representam as informações semânticas do texto.

Selecionar um subconjunto do banco de dados

Executar um modelo de embedding de vetor usa mais recursos do que outras operações. Para reduzir custos e problemas de cota, selecione um subconjunto dos dados para o restante deste tutorial.

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

Há cerca de 1.000 denúncias enviadas em 01/12/2022 em comparação com quase 3,5 milhões de linhas no banco de dados total (confira com feedback.shape).

Selecione apenas os dados de 2022-12-01 e a coluna 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

O método drop_duplicates do pandas exige uma ordenação total das linhas porque tenta selecionar a primeira ou a última linha correspondente e preservar o índice associado a ela.

Em vez disso, agregue com uma chamada ao método groupby para remover linhas duplicadas.

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

feedback.shape

Gerar embeddings

Os DataFrames do BigQuery geram vetores de embeddings usando a classe TextEmbeddingGenerator. Isso se baseia no método ML.GENERATE_EMBEDDING no BigQuery ML, que chama os embeddings de texto fornecidos pela Vertex AI.

from bigframes.ml.llm import TextEmbeddingGenerator

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

Confira como são os embeddings. Esses vetores representam o significado semântico do texto conforme entendido pelo modelo de embedding de texto.

feedback_embeddings.peek()

Resposta esperada:

                        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-...   

Esses vetores têm muitas dimensões. Confira um único vetor de embedding:

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

A geração de incorporações opera sob um contrato de "sucesso parcial". Isso significa que algumas linhas podem ter erros e não gerar uma incorporação. As mensagens de erro são expostas pela coluna 'ml_generate_embedding_status'. Vazio significa que não há erros.

Filtre os encodings para incluir apenas as linhas em que nenhum erro ocorreu.

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

6. Agrupar usando embeddings de texto

Agora, agrupe os embeddings usando k-means. Para esta demonstração, use um número arbitrário de grupos (ou seja, centroides). Uma solução de qualidade de produção precisa ajustar o número de centroides usando uma técnica como o método de silhueta.

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()

Remova as falhas de incorporação.

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

Confira a distribuição de comentários por centroide.

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

7. Resumir os clusters

Forneça alguns comentários associados a cada centroide e peça ao Gemini para resumir as reclamações. A engenharia de comandos é uma área emergente, mas há bons exemplos na Internet, como https://www.promptingguide.ai/ (em inglês).

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()

Usar o Gemini para escrever um relatório com base nos resumos.

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. Limpar

Se você criou um projeto na nuvem do Google para este tutorial, exclua-o para evitar cobranças extras por tabelas ou outros recursos criados.

9. Parabéns!

Você analisou dados estruturados e não estruturados usando BigQuery DataFrames. Ao longo do caminho, você conheceu os conjuntos de dados públicos do Google Cloud, os notebooks Python no BigQuery Studio, o BigQuery ML, a Vertex AI e os recursos de linguagem natural para Python do BigQuery Studio. Ótimo trabalho!

Próximas etapas