Gerar saída do PaliGemma com o Keras

Ver em ai.google.dev Executar no Google Colab Abrir na Vertex AI Ver código-fonte no GitHub

Os modelos PaliGemma têm recursos multimodais, permitindo gerar resultados usando dados de entrada de texto e imagem. É possível usar dados de imagem com esses modelos para fornecer mais contexto aos seus pedidos ou usar o modelo para analisar o conteúdo das imagens. Este tutorial mostra como usar o PaliGemma com o Keras para analisar imagens e responder a perguntas sobre elas.

O que há neste notebook

Este notebook usa o PaliGemma com o Keras e mostra como:

  • Instalar o Keras e as dependências necessárias
  • Baixe o PaliGemmaCausalLM, uma variante pré-treinada do PaliGemma para modelagem causal de linguagem visual, e use-o para criar um modelo.
  • Testar a capacidade do modelo de inferir informações sobre imagens fornecidas

Antes de começar

Antes de ler este notebook, você precisa ter familiaridade com o código Python e com o treinamento de modelos de linguagem grandes (LLMs). Não é necessário conhecer o Keras, mas um conhecimento básico sobre ele é útil ao ler o código de exemplo.

Configuração

As seções a seguir explicam as etapas preliminares para usar um notebook com um modelo PaliGemma, incluindo acesso ao modelo, obtenção de uma chave de API e configuração do ambiente de execução do notebook.

Acessar o PaliGemma

Antes de usar o PaliGemma pela primeira vez, peça acesso ao modelo pelo Kaggle seguindo estas etapas:

  1. Faça login no Kaggle ou crie uma conta se ainda não tiver uma.
  2. Acesse o card de modelo do PaliGemma e clique em Solicitar acesso.
  3. Preencha o formulário de consentimento e aceite os Termos e Condições.

Configurar sua chave de API

Para usar o PaliGemma, você precisa informar seu nome de usuário do Kaggle e uma chave de API do Kaggle.

Para gerar uma chave de API do Kaggle, abra a página Configurações no Kaggle e clique em Criar novo token. Isso aciona o download de um arquivo kaggle.json que contém suas credenciais de API.

Em seguida, no Colab, selecione Secrets (🔑) no painel à esquerda e adicione seu nome de usuário e chave de API do Kaggle. Armazene seu nome de usuário com o nome KAGGLE_USERNAME e sua chave de API com o nome KAGGLE_KEY.

Selecione o ambiente de execução

Para concluir este tutorial, você precisa ter um ambiente de execução do Colab com recursos suficientes para executar o modelo PaliGemma. Nesse caso, você pode usar uma GPU T4:

  1. No canto superior direito da janela do Colab, clique no menu suspenso ▾ (Opções de conexão adicionais).
  2. Selecione Alterar tipo de ambiente de execução.
  3. Em Acelerador de hardware, selecione GPU T4.

Defina as variáveis de ambiente

Defina as variáveis de ambiente para KAGGLE_USERNAME, KAGGLE_KEY e KERAS_BACKEND.

import os
from google.colab import userdata

# Set up environmental variables
os.environ["KAGGLE_USERNAME"] = userdata.get('KAGGLE_USERNAME')
os.environ["KAGGLE_KEY"] = userdata.get('KAGGLE_KEY')
os.environ["KERAS_BACKEND"] = "jax"

Instalar o Keras

Execute a célula abaixo para instalar o Keras.

pip install -U -q keras-nlp keras-hub kagglehub

Importar dependências e configurar o Keras

Instale as dependências necessárias para este notebook e configure o back-end do Keras. Você também vai definir o Keras para usar bfloat16 para que a estrutura use menos memória.

import keras
import keras_hub
import numpy as np
import PIL
import requests
import io
import matplotlib
import re
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from PIL import Image

keras.config.set_floatx("bfloat16")

Carregar o modelo

Agora que você configurou tudo, baixe o modelo pré-treinado e crie alguns métodos utilitários para ajudar o modelo a gerar respostas. Nesta etapa, você vai baixar um modelo usando PaliGemmaCausalLM do Keras Hub. Essa classe ajuda você a gerenciar e executar a estrutura do modelo de linguagem visual causal do PaliGemma. Um modelo de linguagem visual causal prevê o próximo token com base nos anteriores. O Keras Hub oferece implementações de muitas arquiteturas de modelos conhecidas.

Crie o modelo usando o método from_preset e imprima o resumo dele. Esse processo leva cerca de um minuto para ser concluído.

paligemma = keras_hub.models.PaliGemmaCausalLM.from_preset("kaggle://keras/paligemma2/keras/pali_gemma2_mix_3b_224")
paligemma.summary()

Criar métodos utilitários

Para ajudar você a gerar respostas do seu modelo, crie dois métodos utilitários:

  • crop_and_resize:método auxiliar para read_img. Esse método corta e redimensiona a imagem para o tamanho transmitido, de modo que a imagem final seja redimensionada sem distorcer as proporções.
  • read_img:método auxiliar para read_img_from_url. Esse método abre a imagem, redimensiona para que ela se ajuste às restrições do modelo e a coloca em uma matriz que pode ser interpretada pelo modelo.
  • read_img_from_url:recebe uma imagem por um URL válido. Você precisa desse método para transmitir a imagem ao modelo.

Você vai usar read_img_from_url na próxima etapa deste notebook.

def crop_and_resize(image, target_size):
    width, height = image.size
    source_size = min(image.size)
    left = width // 2 - source_size // 2
    top = height // 2 - source_size // 2
    right, bottom = left + source_size, top + source_size
    return image.resize(target_size, box=(left, top, right, bottom))

def read_image(url, target_size):
    contents = io.BytesIO(requests.get(url).content)
    image = PIL.Image.open(contents)
    image = crop_and_resize(image, target_size)
    image = np.array(image)
    # Remove alpha channel if necessary.
    if image.shape[2] == 4:
        image = image[:, :, :3]
    return image

def parse_bbox_and_labels(detokenized_output: str):
  matches = re.finditer(
      '<loc(?P<y0>\d\d\d\d)><loc(?P<x0>\d\d\d\d)><loc(?P<y1>\d\d\d\d)><loc(?P<x1>\d\d\d\d)>'
      ' (?P<label>.+?)( ;|$)',
      detokenized_output,
  )
  labels, boxes = [], []
  fmt = lambda x: float(x) / 1024.0
  for m in matches:
    d = m.groupdict()
    boxes.append([fmt(d['y0']), fmt(d['x0']), fmt(d['y1']), fmt(d['x1'])])
    labels.append(d['label'])
  return np.array(boxes), np.array(labels)

def display_boxes(image, boxes, labels, target_image_size):
  h, l = target_size
  fig, ax = plt.subplots()
  ax.imshow(image)
  for i in range(boxes.shape[0]):
      y, x, y2, x2 = (boxes[i]*h)
      width = x2 - x
      height = y2 - y
      # Create a Rectangle patch
      rect = patches.Rectangle((x, y),
                               width,
                               height,
                               linewidth=1,
                               edgecolor='r',
                               facecolor='none')
      # Add label
      plt.text(x, y, labels[i], color='red', fontsize=12)
      # Add the patch to the Axes
      ax.add_patch(rect)

  plt.show()

def display_segment_output(image, bounding_box, segment_mask, target_image_size):
    # Initialize a full mask with the target size
    full_mask = np.zeros(target_image_size, dtype=np.uint8)
    target_width, target_height = target_image_size

    for bbox, mask in zip(bounding_box, segment_mask):
        y1, x1, y2, x2 = bbox
        x1 = int(x1 * target_width)
        y1 = int(y1 * target_height)
        x2 = int(x2 * target_width)
        y2 = int(y2 * target_height)

        # Ensure mask is 2D before converting to Image
        if mask.ndim == 3:
            mask = mask.squeeze(axis=-1)
        mask = Image.fromarray(mask)
        mask = mask.resize((x2 - x1, y2 - y1), resample=Image.NEAREST)
        mask = np.array(mask)
        binary_mask = (mask > 0.5).astype(np.uint8)


        # Place the binary mask onto the full mask
        full_mask[y1:y2, x1:x2] = np.maximum(full_mask[y1:y2, x1:x2], binary_mask)
    cmap = plt.get_cmap('jet')
    colored_mask = cmap(full_mask / 1.0)
    colored_mask = (colored_mask[:, :, :3] * 255).astype(np.uint8)
    if isinstance(image, Image.Image):
        image = np.array(image)
    blended_image = image.copy()
    mask_indices = full_mask > 0
    alpha = 0.5

    for c in range(3):
        blended_image[:, :, c] = np.where(mask_indices,
                                          (1 - alpha) * image[:, :, c] + alpha * colored_mask[:, :, c],
                                          image[:, :, c])

    fig, ax = plt.subplots()
    ax.imshow(blended_image)
    plt.show()

Gerar saída

Depois de carregar o modelo e criar métodos utilitários, você pode enviar um comando ao modelo com dados de imagem e texto para gerar respostas. Os modelos do PaliGemma são treinados com uma sintaxe de comandos específica para tarefas específicas, como answer, caption e detect. Para mais informações sobre a sintaxe de comandos do PaliGemma, consulte Comandos e instruções do sistema do PaliGemma.

Prepare uma imagem para uso em um comando de geração usando o seguinte código para carregar uma imagem de teste em um objeto:

target_size = (224, 224)
image_url = 'https://storage.googleapis.com/keras-cv/models/paligemma/cow_beach_1.png'
cow_image = read_image(image_url, target_size)
matplotlib.pyplot.imshow(cow_image)

Responder em um idioma específico

O exemplo de código a seguir mostra como pedir ao modelo PaliGemma informações sobre um objeto que aparece em uma imagem fornecida. Este exemplo usa a sintaxe answer {lang} e mostra outras perguntas em outros idiomas:

prompt = 'answer en where is the cow standing?\n'
# prompt = 'svar no hvor står kuen?\n'
# prompt = 'answer fr quelle couleur est le ciel?\n'
# prompt = 'responda pt qual a cor do animal?\n'

output = paligemma.generate(
    inputs={
        "images": cow_image,
        "prompts": prompt,
    }
)
print(output)

Usar o comando detect

O exemplo de código a seguir usa a sintaxe de comando detect para localizar um objeto na imagem fornecida. O código usa as funções parse_bbox_and_labels() e display_boxes() definidas anteriormente para interpretar a saída do modelo e mostrar as caixas delimitadoras geradas.

prompt = 'detect cow\n'
output = paligemma.generate(
    inputs={
        "images": cow_image,
        "prompts": prompt,
    }
)
boxes, labels = parse_bbox_and_labels(output)
display_boxes(cow_image, boxes, labels, target_size)

Usar o comando segment

O exemplo de código a seguir usa a sintaxe de comando segment para localizar a área de uma imagem ocupada por um objeto. Ele usa a biblioteca big_vision do Google para interpretar a saída do modelo e gerar uma máscara para o objeto segmentado.

Antes de começar, instale a biblioteca big_vision e as dependências dela, conforme mostrado neste exemplo de código:

import os
import sys

# TPUs with
if "COLAB_TPU_ADDR" in os.environ:
  raise "It seems you are using Colab with remote TPUs which is not supported."

# Fetch big_vision repository if python doesn't know about it and install
# dependencies needed for this notebook.
if not os.path.exists("big_vision_repo"):
  !git clone --quiet --branch=main --depth=1 \
     https://github.com/google-research/big_vision big_vision_repo

# Append big_vision code to python import path
if "big_vision_repo" not in sys.path:
  sys.path.append("big_vision_repo")


# Install missing dependencies. Assume jax~=0.4.25 with GPU available.
!pip3 install -q "overrides" "ml_collections" "einops~=0.7" "sentencepiece"

Para este exemplo de segmentação, carregue e prepare uma imagem diferente que inclua um gato.

cat = read_image('https://big-vision-paligemma.hf.space/file=examples/barsik.jpg', target_size)
matplotlib.pyplot.imshow(cat)

Esta é uma função para ajudar a analisar a saída de segmento do PaliGemma

import  big_vision.evaluators.proj.paligemma.transfers.segmentation as segeval
reconstruct_masks = segeval.get_reconstruct_masks('oi')
def parse_segments(detokenized_output: str) -> tuple[np.ndarray, np.ndarray]:
  matches = re.finditer(
      '<loc(?P<y0>\d\d\d\d)><loc(?P<x0>\d\d\d\d)><loc(?P<y1>\d\d\d\d)><loc(?P<x1>\d\d\d\d)>'
      + ''.join(f'<seg(?P<s{i}>\d\d\d)>' for i in range(16)),
      detokenized_output,
  )
  boxes, segs = [], []
  fmt_box = lambda x: float(x) / 1024.0
  for m in matches:
    d = m.groupdict()
    boxes.append([fmt_box(d['y0']), fmt_box(d['x0']), fmt_box(d['y1']), fmt_box(d['x1'])])
    segs.append([int(d[f's{i}']) for i in range(16)])
  return np.array(boxes), np.array(reconstruct_masks(np.array(segs)))

Consultar o PaliGemma para segmentar o gato na imagem

prompt = 'segment cat\n'
output = paligemma.generate(
    inputs={
        "images": cat,
        "prompts": prompt,
    }
)

Visualizar a máscara gerada pelo PaliGemma

bboxes, seg_masks = parse_segments(output)
display_segment_output(cat, bboxes, seg_masks, target_size)

Comandos em lote

Você pode fornecer mais de um comando de solicitação em uma única solicitação como um lote de instruções. O exemplo a seguir demonstra como estruturar o texto do comando para fornecer várias instruções.

prompts = [
    'answer en where is the cow standing?\n',
    'answer en what color is the cow?\n',
    'describe en\n',
    'detect cow\n',
    'segment cow\n',
]
images = [cow_image, cow_image, cow_image, cow_image, cow_image]
outputs = paligemma.generate(
    inputs={
        "images": images,
        "prompts": prompts,
    }
)
for output in outputs:
    print(output)