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 os DataFrames do BigQuery em um notebook Python no BigQuery Studio para gerar insights com base nos 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 incorporação de texto na Vertex AI
  • Criar agrupamentos usando o BigQuery ML
  • Resumir clusters com um LLM pelo 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 neste 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 do seletor de projetos, selecione ou crie um projeto do Google Cloud.
  2. Verifique se o faturamento está ativado para seu 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 no ao lado do botão +.
  3. Selecione Notebook Python.
  4. Feche o seletor de modelos.
  5. Selecione + Código para criar uma nova célula de código.
  6. Instale a versão mais recente do pacote BigQuery DataFrames na célula de código.Digite o comando a seguir.
    %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

Inicie o pacote 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 a filtragem semelhante ao pandas. Alguns recursos do pandas que exigem uma ordenação ou um índice rígido podem não funcionar.

Banco de dados de reclamações do consumidor

O banco de dados de reclamações do consumidor é fornecido no BigQuery pelo Programa de conjuntos de dados públicos do Google Cloud. Esta é uma coleção de reclamações sobre produtos e serviços financeiros do consumidor, e os dados são coletados pelo Departamento de Proteção Financeira ao Consumidor dos Estados Unidos.

No BigQuery, consulte a tabela bigquery-public-data.cfbp_complaints.complaint_database para analisar o banco de dados de reclamações do consumidor. 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 fazer o download de uma pequena amostra dos dados.

Execute esta célula:

feedback.peek()

Saída 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

Saída 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()

Saída 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 aos do 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 booleano, podemos fazer algumas agregações para saber mais sobre as 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 os métodos de visualização do Pandas neste DataFrame transferido.

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 o contexto importante. Alguns estados têm populações maiores do que outros. Faça uma mesclagem com um conjunto de dados populacionais, como a Pesquisa da Comunidade Americana do Escritório 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 Comunitária Americana identifica os estados por GEOID. Faça a mesclagem com a tabela de estados para conferir 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, una 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 diagrama 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 valores discrepantes quando comparamos a população com o número de reclamações. Deixamos como exercício para o leitor plotar com rótulos de pontos para identificá-los. Da mesma forma, crie algumas hipóteses sobre por que isso pode estar acontecendo (por exemplo, diferentes dados demográficos, diferentes números 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, muitas das informações úteis no banco de dados de reclamações estão no conteúdo de texto da reclamação.

A IA e as técnicas tradicionais, como a análise de sentimento, o "bag of words" e o word2vec, podem extrair algumas informações quantitativas de dados não estruturados. Mais recentemente, os modelos de "embedding de vetor", que sã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

A execução de 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 reclamações 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 01/12/2022 e apenas 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, faça a agregação com uma chamada para o método groupby para remover duplicações de linhas.

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

feedback.shape

Gerar embeddings

Os DataFrames do BigQuery geram vetores de incorporação usando a classe TextEmbeddingGenerator. Isso é baseado no método ML.GENERATE_EMBEDDING, no BigQuery ML, que chama os modelos de incorporação 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 as embeddings. Esses vetores representam o significado semântico do texto conforme é entendido pelo modelo de embedding de texto.

feedback_embeddings.peek()

Saída 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 incorporação:

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

A geração de incorporações funciona com 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 as inclusões para incluir apenas linhas em que não ocorreu nenhum erro.

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

6. Criar clusters usando embeddings de texto

Agora, faça a clusterização das embeddings usando k-means. Para esta demonstração, use um número arbitrário de grupos (ou 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 todos os erros 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 para o Gemini resumir as reclamações. A engenharia de comando é uma área emergente, mas há bons exemplos na Internet, como 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()

Use o Gemini para criar 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 do Google Cloud para este tutorial, exclua-o para evitar cobranças adicionais de tabelas ou outros recursos criados.

9. Parabéns!

Você analisou dados estruturados e não estruturados usando DataFrames do BigQuery. No 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