{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Capítulo 6: Modelos Generativos e Multimodais\n", "\n", "Este capítulo percorre a IA generativa em duas direções complementares. Primeiro, aprendemos a **usar** um modelo multimodal pronto (a família Gemini, do Google) através de sua **API**, para **analisar imagens**: descrever cenas, extrair informações em formato estruturado, ler textos e localizar objetos, tudo sem treinar nenhum modelo. Em seguida, passamos a **construir** modelos generativos a partir do zero, estudando a intuição dos principais paradigmas (autoencoders variacionais, redes adversariais e modelos de difusão), com foco prático em GANs sobre o MNIST e em Stable Diffusion para geração de imagens condicionada por texto.\n", "\n", "A ordem é proposital. Começar consumindo um modelo multimodal por API é a porta de entrada mais rápida para a IA generativa. O contraste com o treinamento de GANs e de modelos de difusão deixa claro o que cada abordagem exige e o que cada uma oferece." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Análise de Imagens com a API do Gemini\n", "\n", "Os **modelos multimodais** (ou *Vision-Language Models*, VLMs) recebem imagens e texto na mesma requisição e respondem em linguagem natural. Eles são, na prática, modelos **generativos**: dado um prompt e uma imagem, *geram* um texto como resposta. Isso os torna o ponto de partida ideal para este capítulo, pois permitem analisar imagens com poucas linhas de código, sem coletar dados nem treinar redes.\n", "\n", "No Capítulo 4 (seção *LLMs de Visão e Uso de APIs*) apresentamos a família Gemini e comparamos provedores via OpenRouter. Aqui o foco é prático e mais aprofundado: vamos configurar o acesso à API e aplicá-la a três tarefas de **análise de imagens**: saída estruturada em JSON, extração de texto (OCR) e detecção de objetos por prompt.\n", "\n", "### Criando e configurando a chave de API\n", "\n", "Para usar a API você precisa de uma **chave de API** (*API key*), gratuita dentro dos limites de uso para estudo. Acesse o **Google AI Studio** em [aistudio.google.com/app/apikey](https://aistudio.google.com/app/apikey), faça login e clique em **Create API key**. Copie a chave: ela é um segredo, então trate-a como uma senha e nunca a escreva no código nem a publique no GitHub.\n", "\n", "O ponto importante é que **a chave não vai no código**. No Colab, você a cola uma única vez no painel de **Secrets** (ícone de uma chave na barra lateral): crie um segredo chamado `GEMINI_API_KEY`, cole o valor e habilite o acesso para o notebook. O código apenas lê a chave de lá. Feito isso, instale o SDK e crie o cliente:\n", "\n", "```python\n", "!pip install -U -q google-genai\n", "```\n", "\n", "```python\n", "from google import genai\n", "from google.genai import types\n", "from google.colab import userdata\n", "\n", "# A chave vem do painel \"Secrets\" do Colab (você a colou lá, não aqui)\n", "client = genai.Client(api_key=userdata.get(\"GEMINI_API_KEY\"))\n", "\n", "from google.genai import errors\n", "\n", "# Modelos (flash) em ordem de preferência; se um estiver sobrecarregado (503),\n", "# cai para o próximo. Para tarefas mais exigentes, use um modelo \"pro\" (ex.: gemini-2.5-pro).\n", "MODELOS = [\"gemini-2.5-flash\", \"gemini-2.0-flash\", \"gemini-2.5-flash-lite\"]\n", "\n", "def gerar_conteudo(contents, config=None):\n", " \"\"\"Tenta cada modelo da lista e retorna a primeira resposta que funcionar.\"\"\"\n", " for modelo in MODELOS:\n", " try:\n", " return client.models.generate_content(model=modelo, contents=contents, config=config)\n", " except errors.ServerError as e: # 503/500: modelo ocupado, tenta o próximo\n", " print(f\"{modelo} indisponível ({e.code}); tentando o próximo...\")\n", " except errors.ClientError as e:\n", " if e.code == 429: # cota gratuita do dia esgotada neste modelo\n", " print(f\"{modelo} sem cota hoje ({e.code}); tentando o próximo...\")\n", " else:\n", " raise # outros erros (400, 403...) param na hora\n", " raise RuntimeError(\"Todos os modelos da lista falharam (indisponíveis ou sem cota). Tente novamente mais tarde.\")\n", "```\n", "\n", "Além do cliente, definimos uma **lista de modelos** (`MODELOS`) e a função `gerar_conteudo`. Modelos populares às vezes ficam sobrecarregados (`503`) ou esgotam a cota gratuita do dia (`429`). Em vez de falhar, `gerar_conteudo` tenta cada modelo da lista em ordem (cada modelo tem cota separada) e devolve a primeira resposta que funcionar. Por isso, nos exemplos a seguir chamamos `gerar_conteudo(...)` em vez de `client.models.generate_content(...)` diretamente.\n", "\n", "### Descrição de imagem (verificando o acesso)\n", "\n", "Antes das análises mais elaboradas, vale confirmar que o acesso funciona com uma descrição simples. Para o exemplo ser autossuficiente, baixamos uma imagem da internet com `wget` na própria célula (uma foto de rua com um ônibus e pedestres), carregamos os bytes e os enviamos junto com uma instrução em texto:\n", "\n", "```python\n", "# Baixa uma imagem de exemplo da internet\n", "!wget -q https://raw.githubusercontent.com/ultralytics/ultralytics/main/ultralytics/assets/bus.jpg -O onibus.jpg\n", "\n", "with open(\"onibus.jpg\", \"rb\") as f:\n", " image_bytes = f.read()\n", "\n", "# Exibe a imagem que será enviada ao modelo\n", "import matplotlib.pyplot as plt\n", "from PIL import Image\n", "\n", "plt.imshow(Image.open(\"onibus.jpg\"))\n", "plt.axis(\"off\")\n", "plt.show()\n", "\n", "response = gerar_conteudo(\n", " contents=[\n", " types.Part.from_bytes(data=image_bytes, mime_type=\"image/jpeg\"),\n", " \"Descreva esta imagem em duas frases.\",\n", " ],\n", " config=types.GenerateContentConfig(temperature=0.2),\n", ")\n", "print(response.text)\n", "```\n", "\n", "A célula exibe a imagem e imprime a descrição gerada pelo modelo:\n", "\n", "\"Foto\n", "\n", "```text\n", "Dois homens de casaco e óculos de sol caminham numa calçada, com um ônibus elétrico azul da EMT Madrid ao fundo. O ônibus exibe mensagens como \"100% elétrico\" e \"cero emisiones\", e a cena se passa numa rua urbana ensolarada com um edifício amarelo e varandas no segundo plano.\n", "```\n", "\n", "> **Observação**: usamos `temperature=0.2` (valor baixo) porque, em tarefas de análise, queremos respostas mais determinísticas e fiéis à imagem, e não criatividade.\n", "\n", "### Saída estruturada em JSON\n", "\n", "Texto livre é difícil de usar dentro de um programa. Para integrar a resposta a um pipeline (salvar em banco, gerar um relatório, alimentar outro código), pedimos ao modelo uma **saída estruturada**: definimos um *schema* e o modelo responde em JSON válido seguindo exatamente esse formato. No SDK isso é feito com `response_mime_type=\"application/json\"` e `response_schema`, usando classes do **Pydantic** para descrever os campos:\n", "\n", "```python\n", "from pydantic import BaseModel, Field\n", "\n", "# Schema da análise: o modelo é obrigado a preencher estes campos\n", "class AnaliseImagem(BaseModel):\n", " descricao: str = Field(description=\"Descrição curta da cena\")\n", " objetos: list[str] = Field(description=\"Lista dos objetos visíveis\")\n", " cores_predominantes: list[str]\n", " contem_texto: bool\n", " n_pessoas: int = Field(description=\"Número de pessoas na imagem\")\n", "\n", "response = gerar_conteudo(\n", " contents=[\n", " types.Part.from_bytes(data=image_bytes, mime_type=\"image/jpeg\"),\n", " \"Analise a imagem e preencha os campos solicitados.\",\n", " ],\n", " config=types.GenerateContentConfig(\n", " response_mime_type=\"application/json\",\n", " response_schema=AnaliseImagem,\n", " temperature=0.1,\n", " ),\n", ")\n", "\n", "print(response.text) # JSON em texto\n", "analise = response.parsed # objeto AnaliseImagem já validado\n", "print(analise.objetos)\n", "print(\"Tem texto?\", analise.contem_texto)\n", "```\n", "\n", "\n", "A saída impressa pela célula:\n", "\n", "```text\n", "{\n", " \"descricao\": \"Uma cena de rua movimentada com um ônibus elétrico azul e branco, várias pessoas caminhando e edifícios em tons de bege ao fundo.\",\n", " \"objetos\": [\"ônibus\", \"pessoas\", \"edifício\", \"janelas\", \"sacadas\", \"sinal de trânsito\", \"postes de rua\", \"calçada\"],\n", " \"cores_predominantes\": [\"azul\", \"bege\", \"branco\", \"preto\", \"verde\", \"cinza\"],\n", " \"contem_texto\": true,\n", " \"n_pessoas\": 4\n", "}\n", "['ônibus', 'pessoas', 'edifício', 'janelas', 'sacadas', 'sinal de trânsito', 'postes de rua', 'calçada']\n", "Tem texto? True\n", "```\n", "\n", "A grande vantagem é que `response.parsed` devolve um objeto Python já validado pelo Pydantic. Em vez de interpretar um texto livre, acessamos `analise.objetos`, `analise.n_pessoas` e os demais campos com segurança de tipos.\n", "\n", "### Extração de texto (OCR)\n", "\n", "A mesma ideia resolve **OCR** (reconhecimento óptico de caracteres): em vez de uma biblioteca dedicada, pedimos ao modelo que transcreva o texto presente na imagem. VLMs lidam bem com fotos de documentos, placas e cartazes, inclusive com texto manuscrito e layout irregular. Para reproduzir, baixe a ficha de cadastro de exemplo abaixo (com campos preenchidos à mão) e faça o upload dela no seu Colab (painel de arquivos à esquerda, botão de upload). Depois é só ler o arquivo. Troque pela sua própria imagem com texto se quiser:\n", "\n", "\"Ficha\n", "\n", "Baixar documento.jpg\n", "\n", "```python\n", "with open(\"documento.jpg\", \"rb\") as f:\n", " doc_bytes = f.read()\n", "\n", "response = gerar_conteudo(\n", " contents=[\n", " types.Part.from_bytes(data=doc_bytes, mime_type=\"image/jpeg\"),\n", " \"Transcreva todo o texto visível nesta imagem, preservando a ordem de leitura.\",\n", " ],\n", " config=types.GenerateContentConfig(temperature=0.0),\n", ")\n", "print(response.text)\n", "```\n", "\n", "\n", "A saída impressa pela célula:\n", "\n", "```text\n", "AURORA COMÉRCIO\n", "\n", "Ficha de Cadastro de Cliente\n", "\n", "Nome: Mariana Costa Silva\n", "CPF: 123.456.789-00\n", "Telefone: (84) 99654-3210\n", "E-mail: mariana.silva@email.com\n", "Endereço: Rua das Acácias, 128, Natal/RN\n", "Data: 24/06/2026\n", "Assinatura: Mariana Silva\n", "```\n", "\n", "> **Dica**: para campos específicos (nome, CPF, telefone, data), combine OCR com saída estruturada: defina um schema com esses campos e o modelo retorna os dados já organizados, prontos para uso.\n", "\n", "### Detecção de objetos por prompt: caixas delimitadoras\n", "\n", "VLMs também conseguem **localizar** objetos, retornando **caixas delimitadoras** (*bounding boxes*) a partir de uma instrução em linguagem natural. Diferente do YOLO (Capítulo 5), o vocabulário é aberto: podemos pedir \"encontre o ônibus e os pedestres\" sem que essas classes tenham sido pré-definidas.\n", "\n", "O Gemini retorna coordenadas no formato `[ymin, xmin, ymax, xmax]`, normalizadas no intervalo de 0 a 1000 (independente do tamanho real da imagem). Reutilizando a foto do ônibus já carregada (`image_bytes`), pedimos uma lista estruturada de caixas e depois convertemos para pixels:\n", "\n", "```python\n", "from pydantic import BaseModel\n", "\n", "class Caixa(BaseModel):\n", " label: str\n", " box_2d: list[int] # [ymin, xmin, ymax, xmax], normalizado de 0 a 1000\n", "\n", "response = gerar_conteudo(\n", " contents=[\n", " types.Part.from_bytes(data=image_bytes, mime_type=\"image/jpeg\"),\n", " \"Detecte os objetos principais. Retorne box_2d como \"\n", " \"[ymin, xmin, ymax, xmax] normalizado de 0 a 1000.\",\n", " ],\n", " config=types.GenerateContentConfig(\n", " response_mime_type=\"application/json\",\n", " response_schema=list[Caixa],\n", " temperature=0.0,\n", " ),\n", ")\n", "\n", "caixas = response.parsed\n", "for c in caixas:\n", " print(c.label, c.box_2d)\n", "```\n", "\n", "Com as caixas em mãos, convertemos de 0-1000 para coordenadas em pixels e as desenhamos sobre a imagem com o Matplotlib:\n", "\n", "```python\n", "import matplotlib.pyplot as plt\n", "import matplotlib.patches as patches\n", "from PIL import Image\n", "\n", "img = Image.open(\"onibus.jpg\")\n", "largura, altura = img.size\n", "\n", "fig, ax = plt.subplots(figsize=(10, 8))\n", "ax.imshow(img)\n", "\n", "for c in caixas:\n", " ymin, xmin, ymax, xmax = c.box_2d\n", " # Converte de 0-1000 para pixels\n", " x = xmin / 1000 * largura\n", " y = ymin / 1000 * altura\n", " w = (xmax - xmin) / 1000 * largura\n", " h = (ymax - ymin) / 1000 * altura\n", "\n", " ax.add_patch(patches.Rectangle((x, y), w, h, fill=False, edgecolor=\"red\", linewidth=2))\n", " ax.text(x, y - 5, c.label, color=\"white\", fontsize=10,\n", " bbox=dict(facecolor=\"red\", alpha=0.7, pad=1))\n", "\n", "ax.axis(\"off\")\n", "plt.show()\n", "```\n", "\n", "\n", "O resultado, com as caixas desenhadas sobre a imagem:\n", "\n", "\"Foto\n", "\n", "> **Observação**: a detecção por prompt é flexível e dispensa treinamento, mas costuma ser mais lenta e menos precisa na localização fina do que um detector dedicado como o YOLO. Para contagens exatas, objetos pequenos ou vídeo em tempo real, prefira um modelo treinado; para prototipagem e classes incomuns, o VLM resolve rápido.\n", "\n", "### Boas práticas e limitações\n", "\n", "* **Custo e cota**: cada chamada consome *tokens* (de texto e de imagem) e pode gerar custo. O plano gratuito tem limites de requisições por minuto; para lotes grandes, controle a frequência das chamadas.\n", "* **Alucinação**: o modelo pode responder com confiança mesmo quando erra. Em domínios sensíveis (saúde, jurídico), trate a saída como sugestão e valide com um especialista.\n", "* **Temperatura baixa**: para análise factual, use `temperature` próxima de 0, reservando valores altos para tarefas criativas.\n", "* **Privacidade**: não envie imagens com dados pessoais ou sigilosos sem base legal (revisite a LGPD, discutida no Capítulo 4).\n", "* **VLM ou modelo treinado**: para classes fixas, alta precisão de localização ou tempo real, um detector treinado (YOLO) ainda é a melhor escolha. O VLM se destaca na flexibilidade e na rapidez de prototipagem.\n", "\n", "Com esse panorama do **uso** de um modelo multimodal pronto, passamos agora a **construir** modelos generativos a partir do zero." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Introdução às Redes Generativas\n", "\n", "As **redes generativas** (ou **modelos generativos**) são algoritmos de aprendizado de máquina que tentam **capturar a distribuição dos dados** de treinamento para, a partir disso, **gerar novas amostras** semelhantes às originais. Enquanto modelos discriminativos aprendem a distinguir categorias (por exemplo, “é gato” ou “não é gato”), modelos generativos buscam entender “como” os dados foram criados, de modo a sintetizar exemplares que sigam o mesmo padrão estatístico.\n", "\n", "### Por que usar modelos generativos?\n", "\n", "* **Criação de Conteúdo**: gerar imagens, texto ou áudio novos (arte, roteiros, trilhas sonoras).\n", "* **Aumento de Dados**: sintetizar exemplos para treinar outros modelos em cenários com poucos dados reais.\n", "* **Compressão e Representação**: aprender uma representação latente que capture as características principais dos dados (por exemplo, em autoencoders).\n", "* **Análise de Distribuição**: avaliar se certas regiões de espaço de dados são pouco exploradas ou gerar variações seguras para testes.\n", "\n", "\n", "## Principais Famílias de Modelos Generativos\n", "\n", "- **Modelos de Mistura Gaussiana (GMM)**\n", "\n", " * **Ideia principal**: aproximam a distribuição dos dados por uma soma de distribuições normais multivariadas.\n", " * **Semântica prática**: cada componente gaussiano “modela” um cluster nos dados; a mistura (peso de cada componente) explica a probabilidade final.\n", " * **Pontos-chave**:\n", "\n", " * Fácil de entender e implementar em baixa dimensão.\n", " * Não escala bem para imagens de alta resolução.\n", " * Serve como introdução conceitual sobre “estimativa de densidade”.\n", "\n", "- **Variational Autoencoders (VAEs)**\n", "\n", " * **Ideia principal**: combinam um **encoder** que mapeia dados de entrada $x$ para uma distribuição latente $q_\\phi(z \\mid x)$ (normalmente $\\mathcal{N}(\\mu, \\sigma^2)$) e um **decoder** que reconstrói $x$ a partir de $z$.\n", " * **Objetivo de Treinamento**: otimizar um limite inferior da evidência (ELBO), composto por:\n", "\n", " 1. Automínimo erro de reconstrução (por exemplo, MSE).\n", " 2. Divergência de Kullback–Leibler ($\\mathrm{KL}$) entre $q_\\phi(z \\mid x)$ e uma distribuição pré-definida $p(z)$, tipicamente $\\mathcal{N}(0,I)$.\n", " * **Pontos-chave**:\n", "\n", " * Permitem amostrar diretamente do espaço latente (gera variações suaves).\n", " * Produzem amostras que podem ficar um pouco “borradas” em imagens.\n", " * Treinamento relativamente estável e eficiente em comparação com GANs.\n", "\n", "- **Redes Generativas Adversariais (GANs)**\n", "\n", " * **Ideia principal**: GANs consistem em duas redes neurais que competem entre si em um jogo de soma zero. A primeira, chamada Gerador ($G$), busca criar amostras que pareçam reais; a segunda, chamada Discriminador ($D$), tenta distinguir o que é real do que foi gerado. Essa competição força ambas as redes a melhorarem constantemente.\n", "\n", " * **Gerador ($G$)**: a partir de um vetor aleatório $z\\sim p(z)$, tenta gerar uma amostra $G(z)$ que pareça real.\n", " * **Discriminador ($D$)**: recebe amostras reais ou geradas e tenta distinguir “real” de “falso”.\n", " * **O jogo minimax**:\n", "\n", " $$\n", " \\min_G \\max_D \\; \\mathbb{E}_{x\\sim p_\\text{data}}[\\log D(x)] \\;+\\; \\mathbb{E}_{z\\sim p(z)}[\\log(1 - D(G(z)))].\n", " $$\n", " * **Pontos-chave**:\n", "\n", " * Podem gerar imagens nítidas e de alta resolução.\n", " * Exigem cuidado para evitar instabilidades (colapso de modo, onde o gerador produz poucas variações).\n", " * Sucesso em domínios como geração de rostos (e.g., StyleGAN) e arte abstrata.\n", "\n", "- **Modelos de Difusão (Diffusion Models)**\n", "\n", " * **Ideia principal**: Modelos de Difusão simulam um **processo de ruído progressivo** em uma imagem real e treinam uma rede neural para **reverter esse ruído passo a passo**, recuperando a imagem original. Esse processo permite gerar novas amostras a partir de puro ruído, com alta qualidade e estabilidade.\n", "\n", " * **Processo para frente (“noising”)**: parte-se de uma amostra real $x_0$ e, em cada passo $t$, adiciona-se ruído gaussiano para obter $x_t$. Quando $t$ for grande (por ex., $T\\approx1000$), $x_T$ é praticamente ruído puro.\n", " * **Processo reverso (“denoising”)**: uma rede neural ($\\epsilon_\\theta$) é treinada para remover ruído de $x_t$ e recuperar $x_{t-1}$, até chegar a $x_0$.\n", " * **Pontos-chave**:\n", "\n", " * Geralmente mais estáveis que GANs.\n", " * Produzem amostras de altíssima qualidade (sem artefatos típicos de GANs).\n", " * Inferir uma imagem exige dezenas ou centenas de etapas de denoising, tornando a geração mais lenta.\n", "\n", "Tabela comparativa dos principais modelos apresentados:\n", "\n", "| Modelo | Abordagem | Vantagens Principais | Limitações Notáveis | Aplicações Típicas |\n", "| ---------------------- | ----------------------------------- | -------------------------------------------------------------- | ------------------------------------------------ | ---------------------------------------------------------- |\n", "| **GMM** | Mistura de gaussianas | Simples, interpretável, útil para dados de baixa dimensão | Escala mal para imagens ou dados complexos | Clusterização, estimativa de densidade básica |\n", "| **VAE** | Codificação latente probabilística | Treinamento estável, interpolações suaves no espaço latente | Geração de imagens pode ser borrada | Reconstrução e geração de dados; compressão |\n", "| **GAN** | Competição Gerador vs Discriminador | Gera imagens realistas e de alta qualidade | Instabilidade no treinamento, colapso de modo | Geração de faces (StyleGAN), arte, deepfakes |\n", "| **Modelos de Difusão** | Ruído + reversão progressiva | Amostras de altíssima qualidade, sem artefatos típicos de GANs | Lento na geração (múltiplas etapas de denoising) | Imagens hiper-realistas, arte, medicina, vídeo (e.g. SDXL) |\n", "\n", "\n", "\n", "> **Observação**: a seguir, focaremos em **GANs** e **Stable Diffusion** (um diffusion model voltado para texto → imagem). \n", "\n", "## Redes Generativas Adversariais (GANs)\n", "\n", "**Conceito e Arquitetura Básica**\n", "\n", "As Redes Generativas Adversariais (GANs) são uma classe de modelos generativos compostos por duas redes neurais que competem entre si em um jogo adversarial: o **Gerador** tenta produzir amostras realistas, enquanto o **Discriminador** tenta distinguir se uma amostra é real ou foi gerada.\n", "\n", "**Gerador $G_\\theta$**\n", "\n", "* Recebe como entrada um vetor aleatório $z \\sim p(z)$, normalmente amostrado de uma distribuição gaussiana $\\mathcal{N}(0, I)$ ou uniforme.\n", "* Gera uma amostra sintética $G(z)$, com aparência similar à de dados reais.\n", "* É geralmente implementado com redes convolucionais transpostas (ou técnicas de upsampling).\n", "\n", "**Discriminador $D_\\phi$**\n", "\n", "* Recebe como entrada uma amostra $x$ (real ou gerada).\n", "* Retorna uma probabilidade $D(x) \\in [0, 1]$, indicando o quão \"real\" é a entrada.\n", "* É implementado como uma rede convolucional tradicional, atuando como um classificador binário.\n", "\n", "\n", "**Funções de Custo (Loss) e Treinamento**\n", "\n", "**Notação de Esperança Matemática $\\mathbb{E}$**\n", "\n", "A notação $\\mathbb{E}_{x \\sim p(x)}[f(x)]$ representa a **esperança matemática** (ou média esperada) da função $f(x)$ quando a variável $x$ é amostrada de uma distribuição $p(x)$. Na prática, essa média é estimada por um minibatch de amostras durante o treinamento.\n", "\n", "**Objetivo do Discriminador**\n", "\n", "O discriminador busca **maximizar** a seguinte função de custo:\n", "\n", "$$\n", "\\mathcal{L}_D = \\mathbb{E}_{x \\sim p_\\text{data}(x)}[\\log D(x)] + \\mathbb{E}_{z \\sim p(z)}[\\log(1 - D(G(z)))]\n", "$$\n", "\n", "> Onde:\n", ">\n", "> * $p_\\text{data}(x)$: distribuição real dos dados.\n", "> * $p(z)$: distribuição aleatória de entrada do gerador.\n", "> * $D(x)$: probabilidade atribuída pelo discriminador de que $x$ seja real.\n", "\n", "**Objetivo do Gerador**\n", "\n", "O gerador busca **minimizar** a capacidade do discriminador de identificar amostras falsas. Existem duas versões da loss:\n", "\n", "* **Versão original**:\n", "\n", "$$\n", "\\mathcal{L}_G = \\mathbb{E}_{z \\sim p(z)}[\\log(1 - D(G(z)))]\n", "$$\n", "\n", "* **Versão modificada (mais estável)**:\n", "\n", "$$\n", "\\mathcal{L}_G = \\mathbb{E}_{z \\sim p(z)}[-\\log D(G(z))]\n", "$$\n", "\n", "> Esta versão é mais comum na prática, pois evita gradientes muito pequenos nas fases iniciais do treinamento.\n", "\n", "**Treinamento Adversarial**\n", "\n", "O processo de treinamento alterna entre dois passos:\n", "\n", "1. **Atualização de $D_\\phi$**:\n", "\n", " * Amostra um batch de dados reais $x \\sim p_\\text{data}(x)$.\n", " * Amostra um batch de ruído $z \\sim p(z)$, gera $G(z)$.\n", " * Otimiza os parâmetros $\\phi$ do discriminador para maximizar $\\mathcal{L}_D$.\n", "\n", "2. **Atualização de $G_\\theta$**:\n", "\n", " * Gera novas amostras $G(z)$ com $z \\sim p(z)$.\n", " * Otimiza os parâmetros $\\theta$ do gerador para minimizar $\\mathcal{L}_G$.\n", "\n", "**Jogo Minimax**\n", "\n", "O treinamento das GANs é formulado como um **jogo de soma zero** entre duas redes com objetivos opostos:\n", "\n", "$$\n", "\\min_G \\max_D \\; \\mathbb{E}_{x \\sim p_\\text{data}(x)}[\\log D(x)] + \\mathbb{E}_{z \\sim p(z)}[\\log(1 - D(G(z)))]\n", "$$\n", "\n", "Esse é o **jogo minimax**: o discriminador tenta maximizar a função, e o gerador tenta minimizá-la. No equilíbrio, $G$ produz amostras indistinguíveis dos dados reais e $D(x) \\approx 0{,}5$.\n", "\n", "**Variações Populares de GANs**\n", "\n", "* **DCGAN** (Deep Convolutional GAN):\n", " Estrutura convolucional profunda adaptada para imagens. Usa **Strided Convolutions**, **Batch Normalization**, e **LeakyReLU**.\n", "\n", "* **WGAN** (Wasserstein GAN):\n", " Substitui a função de perda original pela **distância de Wasserstein**, oferecendo **estabilidade e interpretabilidade**.\n", "\n", "* **StyleGAN / StyleGAN2**:\n", " Introduz camadas de controle de estilo e um mapeamento intermediário para o vetor $z$, permitindo controle sobre características visuais como **iluminação**, **expressão**, e **pose**.\n", "\n", "**Exemplo Prático de GAN em PyTorch**\n", "\n", "Abaixo, um exemplo mínimo para treinar um GAN em um conjunto de imagens (por exemplo, MNIST ou CIFAR-10). Aqui usamos MNIST (imagens 28×28 em escala de cinza) apenas para fins ilustrativos.\n", "\n", "```python\n", "import os\n", "import torch\n", "import torch.nn as nn\n", "import torch.optim as optim\n", "from torchvision import datasets, transforms, utils\n", "from torch.utils.data import DataLoader\n", "\n", "# 1) CONFIGURAÇÕES GERAIS\n", "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\") # Usa GPU se disponível\n", "latent_dim = 100 # Dimensão do vetor latente (entrada do gerador)\n", "batch_size = 128 # Tamanho do lote\n", "lr = 0.0002 # Taxa de aprendizado\n", "epochs = 30 # Número de épocas de treinamento\n", "sample_dir = \"samples\" # Pasta para salvar imagens geradas\n", "os.makedirs(sample_dir, exist_ok=True)\n", "\n", "# 2) CARREGAMENTO DO DATASET (MNIST)\n", "transform = transforms.Compose([\n", " transforms.ToTensor(), # Converte imagens para tensores\n", " transforms.Normalize([0.5], [0.5]) # Normaliza para o intervalo [-1, 1]\n", "])\n", "dataset = datasets.MNIST(root=\"./data\", train=True, download=True, transform=transform)\n", "dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True)\n", "\n", "# 3) DEFINIÇÃO DO GERADOR\n", "class Generator(nn.Module):\n", " def __init__(self):\n", " super().__init__()\n", " self.net = nn.Sequential(\n", " nn.Linear(latent_dim, 256),\n", " nn.LeakyReLU(0.2, inplace=True),\n", " nn.Linear(256, 512),\n", " nn.BatchNorm1d(512),\n", " nn.LeakyReLU(0.2, inplace=True),\n", " nn.Linear(512, 1024),\n", " nn.BatchNorm1d(1024),\n", " nn.LeakyReLU(0.2, inplace=True),\n", " nn.Linear(1024, 28*28), # Saída do gerador deve ter o tamanho da imagem do MNIST (784)\n", " nn.Tanh() # Saída entre [-1, 1] para combinar com a normalização do dataset\n", " )\n", "\n", " def forward(self, z):\n", " img = self.net(z) # Gera imagem a partir de vetor latente\n", " return img.view(-1, 1, 28, 28) # Reshape para imagem (1 canal, 28x28)\n", "\n", "# 4) DEFINIÇÃO DO DISCRIMINADOR\n", "class Discriminator(nn.Module):\n", " def __init__(self):\n", " super().__init__()\n", " self.net = nn.Sequential(\n", " nn.Linear(28*28, 512),\n", " nn.LeakyReLU(0.2, inplace=True),\n", " nn.Linear(512, 256),\n", " nn.LeakyReLU(0.2, inplace=True),\n", " nn.Linear(256, 1),\n", " nn.Sigmoid() # Produz probabilidade de ser imagem real\n", " )\n", "\n", " def forward(self, img):\n", " flat = img.view(-1, 28*28) # Achata a imagem para vetor\n", " return self.net(flat)\n", "\n", "# 5) INSTANCIAÇÃO DE MODELOS, FUNÇÃO DE PERDA E OTIMIZADORES\n", "generator = Generator().to(device)\n", "discriminator = Discriminator().to(device)\n", "\n", "criterion = nn.BCELoss() # Binary Cross Entropy para classificação binária (real ou falsa)\n", "optim_G = optim.Adam(generator.parameters(), lr=lr, betas=(0.5, 0.999))\n", "optim_D = optim.Adam(discriminator.parameters(), lr=lr, betas=(0.5, 0.999))\n", "\n", "# 6) FUNÇÕES AUXILIARES PARA GERAR RÓTULOS\n", "def real_labels(size):\n", " return torch.ones(size, 1, device=device) # Rótulo \"1\" para imagens reais\n", "\n", "def fake_labels(size):\n", " return torch.zeros(size, 1, device=device) # Rótulo \"0\" para imagens falsas\n", "\n", "# 7) LOOP DE TREINAMENTO DO GAN\n", "for epoch in range(epochs):\n", " for i, (imgs, _) in enumerate(dataloader):\n", " batch_size_i = imgs.size(0)\n", " imgs = imgs.to(device)\n", "\n", " # ----- TREINAMENTO DO DISCRIMINADOR -----\n", " # Gera imagens falsas a partir de ruído\n", " z = torch.randn(batch_size_i, latent_dim, device=device)\n", " fake_imgs = generator(z)\n", "\n", " # Avalia imagens reais e falsas\n", " real_out = discriminator(imgs)\n", " fake_out = discriminator(fake_imgs.detach()) # Detach para não propagar gradientes para o gerador\n", "\n", " # Calcula a perda do discriminador\n", " loss_D_real = criterion(real_out, real_labels(batch_size_i))\n", " loss_D_fake = criterion(fake_out, fake_labels(batch_size_i))\n", " loss_D = (loss_D_real + loss_D_fake) / 2\n", "\n", " # Backpropagation e otimização do discriminador\n", " optim_D.zero_grad()\n", " loss_D.backward()\n", " optim_D.step()\n", "\n", " # ----- TREINAMENTO DO GERADOR -----\n", " # Tenta enganar o discriminador: quer que imagens falsas sejam classificadas como reais\n", " output = discriminator(fake_imgs)\n", " loss_G = criterion(output, real_labels(batch_size_i)) # Compara com rótulo real (1)\n", "\n", " # Backpropagation e otimização do gerador\n", " optim_G.zero_grad()\n", " loss_G.backward()\n", " optim_G.step()\n", "\n", " # Exibe progresso a cada 200 lotes\n", " if i % 200 == 0:\n", " print(f\"Epoch [{epoch+1}/{epochs}] Batch [{i}/{len(dataloader)}] \"\n", " f\"Loss D: {loss_D.item():.4f}, Loss G: {loss_G.item():.4f}\")\n", "\n", " # ----- GERA E SALVA AMOSTRAS -----\n", " with torch.no_grad():\n", " z = torch.randn(16, latent_dim, device=device)\n", " sample_imgs = generator(z).cpu()\n", " sample_grid = utils.make_grid(sample_imgs, nrow=4, normalize=True)\n", " utils.save_image(sample_grid, f\"{sample_dir}/epoch_{epoch+1:03d}.png\")\n", "\n", "print(\"Treinamento concluído! Amostras salvas na pasta 'samples/'.\")\n", "```\n", "\n", "### Resumo do que o código faz:\n", "\n", "* **Treina um GAN (Generative Adversarial Network)** usando o conjunto de dados MNIST.\n", "* O **gerador** aprende a criar imagens falsas de dígitos manuscritos.\n", "* O **discriminador** tenta distinguir imagens reais (do MNIST) de imagens falsas (do gerador).\n", "* Ambos são treinados de forma adversarial: um tenta enganar o outro.\n", "* Ao final de cada época, são geradas 16 imagens para avaliação visual do progresso do gerador.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Modelos de Difusão e Stable Diffusion**\n", "\n", "O processo de diffusion models consiste em dois momentos principais: inserir ruído progressivamente numa imagem real e, em seguida, aprender a reverter esse processo para gerar novas amostras. Primeiro, parte-se de uma imagem original $x_0$ e adiciona-se ruído gaussiano em vários passos $t=1,2,\\dots,T$, de modo que, quando $t$ é grande, a imagem se transforma em praticamente puro ruído. Formalmente, cada passo é descrito como:\n", "\n", "$$\n", "x_t = \\sqrt{\\alpha_t}\\,x_{t-1} + \\sqrt{1 - \\alpha_t}\\,\\epsilon_t,\\quad \\epsilon_t \\sim \\mathcal{N}(0,I),\n", "$$\n", "\n", "onde $\\alpha_t \\in (0,1)$ controla o nível de ruído em $t$. À medida que avança-se de $t=1$ até $T$, o modelo aplica mais ruído até que $x_T$ seja quase aleatório.\n", "\n", "O segundo momento é o processo inverso, chamado denoising. Treina-se uma rede $\\epsilon_\\theta(x_t, t)$ para estimar o ruído $\\epsilon_t$ presente em $x_t$. Com essa predição $\\hat{\\epsilon} = \\epsilon_\\theta(x_t, t)$, obtém-se uma aproximação de $x_{t-1}$ a partir de:\n", "\n", "$$\n", "x_{t-1} \\approx \\frac{1}{\\sqrt{\\alpha_t}}\\Bigl(x_t - \\frac{1 - \\alpha_t}{\\sqrt{1 - \\bar\\alpha_t}}\\,\\hat{\\epsilon}\\Bigr) + \\sigma_t\\,n,\\quad n\\sim \\mathcal{N}(0,I),\n", "$$\n", "\n", "onde $\\bar\\alpha_t = \\prod_{s=1}^{t} \\alpha_s$ e $\\sigma_t$ é um termo de ruído adaptativo para garantir diversidade nas amostras. Ao repetir esse passo regressivamente de $t=T$ até $t=1$, chega-se a uma nova imagem $x_0$ gerada pelo modelo.\n", "\n", "Esse tipo de arquitetura costuma ser mais estável que GANs porque não envolve um jogo adversarial direto. Além disso, tende a produzir imagens de alta qualidade e com grande diversidade, podendo ser facilmente condicionado a textos, máscaras, esboços ou outros sinais auxiliares.\n", "\n", "\n", "**Visão Geral do Stable Diffusion**\n", "\n", "O Stable Diffusion é uma implementação eficiente de diffusion models para geração de imagens a partir de texto, baseada em duas ideias-chave: trabalhar no espaço latente e usar um mecanismo de atenção para incorporar informações textuais.\n", "\n", "* **Espaço Latente (Latent Diffusion)**\n", " Em vez de aplicar difusão diretamente em pixels de alta resolução (por exemplo, tensores $512 \\times 512 \\times 3$), o Stable Diffusion aproveita um autoencoder pré-treinado (VAE) para comprimir cada imagem para um espaço de dimensão muito menor (por exemplo, $\\mathbb{R}^{4\\times 64\\times 64}$). No treinamento, adiciona-se ruído apenas nesse espaço latente. Na geração, amostra-se um vetor latente puro ($z_T \\sim \\mathcal{N}(0, I)$) e aplica-se o processo de denoising nesse espaço reduzido. Ao fim, o decoder do VAE reconstrói a imagem em pixels na resolução original.\n", "\n", "* **Condicionamento em Texto**\n", " Um encoder de texto (tipicamente o CLIP Text Encoder) converte o prompt em embeddings. Esses vetores de texto entram em uma U-Net de denoising via cross-attention: em cada passo de remoção de ruído, a U-Net recebe o latente atual $z_t$, o índice do passo $t$ e os embeddings do texto, e produz uma estimativa de ruído $\\hat{\\epsilon}$ alinhada ao significado do prompt. Para reforçar a fidelidade ao texto, adota-se classifier-free guidance, que mistura a predição de ruído com e sem contexto textual usando um fator de peso $w$.\n", "\n", "* **Pipeline de Geração**\n", "\n", " 1. Converter o prompt em token IDs e embeddings.\n", " 2. Amostrar ruído latente $z_T \\sim \\mathcal{N}(0,I)$ no espaço comprimido (por exemplo, $\\mathbb{R}^{4\\times64\\times64}$).\n", " 3. Para $t = T, T-1, \\dots, 1$:\n", "\n", " * A U-Net recebe $z_t$, o passo $t$ e os embeddings do texto.\n", " * Prediz o ruído $\\hat{\\epsilon}$.\n", " * Atualiza $z_{t-1}$ pela fórmula de denoising, usando $\\hat{\\epsilon}$ e os coeficientes $\\alpha_t$.\n", " 4. Quando $z_0$ é obtido, passa-se pelo decoder do VAE para gerar a imagem final em alta resolução (por exemplo, $512\\times512\\times3$).\n", "\n", "* **Vantagens do Stable Diffusion**\n", "\n", " * Processar no espaço latente reduz dramaticamente o custo computacional comparado a difundir em pixels.\n", " * A qualidade das imagens geradas costuma ser muito alta, com poucos artefatos.\n", " * Além de texto→imagem, suporta tarefas como inpainting, outpainting e outras modalidades condicionadas (máscaras, rascunhos, etc.).\n", "\n", "**Exemplo Prático Funcional de Stable Diffusion**\n", "\n", "A seguir, um exemplo simples que já funciona em um notebook (local ou Colab). Ele usa a biblioteca `diffusers` para carregar um pipeline do Stable Diffusion, gerar uma imagem a partir de um prompt e exibi-la.\n", "\n", "```python\n", "# Instalação das dependências (execute apenas uma vez no notebook/Colab)\n", "!pip install -q diffusers transformers accelerate safetensors\n", "\n", "import torch\n", "from diffusers import StableDiffusionPipeline\n", "from PIL import Image\n", "import matplotlib.pyplot as plt\n", "from IPython.display import display\n", "\n", "# Verifica se há GPU disponível\n", "device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n", "print(f\"Dispositivo em uso: {device}\")\n", "\n", "# Carrega o modelo Stable Diffusion 2.1\n", "model_id = \"stabilityai/stable-diffusion-2-1\"\n", "pipe = StableDiffusionPipeline.from_pretrained(\n", " model_id,\n", " torch_dtype=torch.float16 if device == \"cuda\" else torch.float32\n", ").to(device)\n", "\n", "# Define o prompt (descrição textual da imagem)\n", "prompt = \"Uma floresta encantada ao entardecer, estilo pintura digital\"\n", "\n", "# Parâmetros de geração\n", "num_steps = 50 # Passos de denoising\n", "guidance = 7.5 # Peso para aderência ao prompt\n", "\n", "# Função para gerar imagem a partir do prompt\n", "def gerar_imagem(prompt, steps=50, guidance_scale=7.5, height=512, width=512):\n", " with torch.autocast(device) if device == \"cuda\" else torch.no_grad():\n", " resultado = pipe(\n", " prompt=prompt,\n", " height=height,\n", " width=width,\n", " num_inference_steps=steps,\n", " guidance_scale=guidance_scale\n", " )\n", " return resultado.images[0]\n", "\n", "# Função para exibir imagem (compatível com diferentes ambientes)\n", "def exibir_imagem(imagem, titulo=\"Imagem Gerada\"):\n", " try:\n", " # Método 1: Para Jupyter/Colab - usando matplotlib\n", " plt.figure(figsize=(10, 10))\n", " plt.imshow(imagem)\n", " plt.axis('off')\n", " plt.title(titulo)\n", " plt.show()\n", " except:\n", " try:\n", " # Método 2: Para Jupyter/Colab - usando IPython display\n", " display(imagem)\n", " except:\n", " try:\n", " # Método 3: Método padrão do PIL\n", " imagem.show()\n", " except:\n", " # Método 4: Salvar arquivo localmente\n", " nome_arquivo = \"imagem_gerada.png\"\n", " imagem.save(nome_arquivo)\n", " print(f\"Imagem salva como: {nome_arquivo}\")\n", "\n", "# Geração e exibição da imagem\n", "print(\"Gerando imagem... (pode levar alguns minutos)\")\n", "imagem = gerar_imagem(prompt, num_steps, guidance)\n", "\n", "# Exibe a imagem usando múltiplos métodos\n", "exibir_imagem(imagem, f\"Prompt: {prompt}\")\n", "\n", "# Salva a imagem também\n", "imagem.save(\"floresta_encantada.png\")\n", "print(\"Imagem salva como: floresta_encantada.png\")\n", "\n", "# Função adicional para gerar múltiplas imagens\n", "def gerar_multiplas_imagens(prompt, quantidade=4, steps=50, guidance_scale=7.5):\n", " imagens = []\n", " for i in range(quantidade):\n", " print(f\"Gerando imagem {i+1}/{quantidade}...\")\n", " img = gerar_imagem(prompt, steps, guidance_scale)\n", " imagens.append(img)\n", " \n", " # Salva cada imagem\n", " nome_arquivo = f\"imagem_{i+1}.png\"\n", " img.save(nome_arquivo)\n", " \n", " # Exibe todas as imagens em uma grade\n", " fig, axes = plt.subplots(2, 2, figsize=(15, 15))\n", " fig.suptitle(f\"Prompt: {prompt}\", fontsize=16)\n", " \n", " for i, (img, ax) in enumerate(zip(imagens, axes.flat)):\n", " ax.imshow(img)\n", " ax.axis('off')\n", " ax.set_title(f\"Variação {i+1}\")\n", " \n", " plt.tight_layout()\n", " plt.show()\n", " \n", " return imagens\n", "\n", "# Exemplo de uso da função para múltiplas imagens\n", "# imagens_multiplas = gerar_multiplas_imagens(prompt, quantidade=4)\n", "\n", "```\n", "**Outros Modelos de Diffusão Relevantes**\n", "\n", "* **DDPM (Denoising Diffusion Probabilistic Models):** o design original, aplicando difusão diretamente em pixels.\n", "* **DDIM (Denoising Diffusion Implicit Models):** permite gerar amostras consistentes com muito menos passos via uma discretização alternativa.\n", "* **Score-Based Models:** estimam o gradiente do log-densidade $\\nabla_x \\log p_t(x)$ em cada etapa, em vez de predizer explicitamente o ruído.\n", "\n", "Essas variantes compartilham a mesma ideia central: adicionar ruído a partir de dados reais e então aprender a reverter esse processo, mas diferem na forma de parametrizar ou acelerar a sequência de passos. Cada uma pode ser adaptada a diferentes cenários de geração, seja por velocidade, qualidade ou requisitos de hardware.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Referências e Conteúdo Extra\n", "\n", "- **APIs e modelos multimodais**\n", " - [Gemini API: entendimento de imagens](https://ai.google.dev/gemini-api/docs/image-understanding)\n", " - [Gemini API: saída estruturada (structured output)](https://ai.google.dev/gemini-api/docs/structured-output)\n", " - [Google AI Studio (criar chave de API)](https://aistudio.google.com/app/apikey)\n", "- **Modelos generativos**\n", " - [Diffusers — Hugging Face](https://huggingface.co/docs/diffusers/index)\n", " - [Stable Diffusion — Stability AI](https://stability.ai/stable-image)" ] } ], "metadata": { "colab": { "provenance": [], "toc_visible": true }, "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.20" } }, "nbformat": 4, "nbformat_minor": 4 }