Grafos: de BFS a Knowledge Graphs e GNNs
Grafo é a estrutura para dados de relacionamento — e o tema atravessa quatro mundos que raramente aparecem juntos: algoritmos de entrevista, ciência de redes, aprendizado de máquina em grafos, e grafos de conhecimento para IA. Esta apostila liga os quatro, do fundamento aplicado ao GraphRAG.
O capítulo 8 da apostila de Algoritmos com Python cobre BFS/DFS/Dijkstra/topológica/Union-Find em nível de entrevista — aqui isso é recap (módulo 2), e o foco é o grafo aplicado. O módulo 7 da apostila de NoSQL apresenta bancos de grafo; aqui a modelagem de grafo já foi tratada na Modelagem de Dados NoSQL (módulo 7). Knowledge Graphs para IA conectam com a apostila de RAG.
Vocabulário e representações
Objetivo: fixar o vocabulário (dirigido, ponderado, DAG, bipartido, esparso) e escolher a representação — lista de adjacência, matriz, CSR — pelo custo que ela impõe.
1.1 O vocabulário mínimo
| Termo | Significado | Exemplo |
|---|---|---|
| Nó / vértice, aresta | entidade e ligação | pessoa, "é amiga de" |
| Dirigido vs não-dirigido | a aresta tem sentido? | "segue" (dirigido); "amigo de" (não) |
| Ponderado | aresta com peso/custo | distância, tempo, custo, força da relação |
| Grau | nº de arestas de um nó (in/out se dirigido) | seguidores = grau de entrada |
| Caminho, ciclo | sequência de nós por arestas; caminho que volta ao início | rota; dependência circular |
| DAG | dirigido acíclico | dependências de build, pipeline, árvore de tarefas |
| Conexo / componente | subconjunto onde todos se alcançam | ilhas num grafo social |
| Bipartido | dois grupos, arestas só entre grupos | usuários × produtos; alunos × turmas |
| Esparso vs denso | |E| ≈ |V| vs |E| ≈ |V|² | quase todo grafo real é esparso |
1.2 As três representações
# Lista de adjacência — o padrão para grafos esparsos adj = { "A": [("B", 3), ("C", 1)], "B": [("C", 7)], "C": [] } # espaço O(V+E); vizinhos de um nó em O(grau); "existe aresta A-B?" em O(grau) # Matriz de adjacência — boa para grafos densos e operações de álgebra linear # A B C # A [ 0 3 1 ] espaço O(V²); "existe aresta?" em O(1); vizinhos em O(V) # CSR (Compressed Sparse Row) — o formato de bibliotecas de alto desempenho # dois arrays (indptr, indices [+ data]); cache-friendly, imutável; usado por SciPy, cuGraph, PyG
| Operação | Lista adj. | Matriz | CSR |
|---|---|---|---|
| Espaço | O(V+E) | O(V²) | O(V+E) |
| Iterar vizinhos de v | O(grau(v)) | O(V) | O(grau(v)), sequencial em memória |
| Existe aresta (u,v)? | O(grau(u)) | O(1) | O(grau(u)) |
| Adicionar aresta | O(1) | O(1) | caro (reconstruir) |
Grafo dinâmico e esparso (a esmagadora maioria): lista de adjacência (dict de listas, ou um banco de grafo). Grafo pequeno e denso, ou algoritmo que é multiplicação de matrizes (PageRank, caminhos via potências): matriz. Grafo grande, estático, processamento em lote/GPU: CSR.
Em entrevista, a primeira pergunta implícita de todo problema de grafo é "como você representa?". Dizer "lista de adjacência porque o grafo é esparso, o que dá O(V+E) de espaço e itera vizinhos em O(grau)" antes de codar mostra base. Reconhecer um problema como "isto é um DAG → ordenação topológica" ou "isto é bipartido → matching" economiza metade do tempo.
✏️ Exercício 1 — Modele e represente
Para cada caso, diga: dirigido ou não? ponderado? é um DAG? bipartido? qual representação: (a) malha rodoviária de uma cidade para calcular rotas; (b) dependências entre 5.000 tarefas de um pipeline de CI; (c) quais dos 2 milhões de usuários avaliaram quais dos 500 mil filmes.
Gabarito: (a) Dirigido (mão única existe), ponderado (tempo/distância), não é DAG (há ciclos — quarteirões), não bipartido. Lista de adjacência (esparso: cada cruzamento liga a poucos). (b) Dirigido, geralmente não ponderado, DAG (dependência circular seria erro — detectá-la é um uso), não bipartido. Lista de adjacência + ordenação topológica. (c) Não-dirigido, ponderado pela nota, bipartido (usuários de um lado, filmes do outro), esparso. Lista de adjacência ou matriz esparsa (CSR) — é a base de sistemas de recomendação e de filtragem colaborativa.
Travessia: BFS e DFS na prática
Objetivo: recap rápido de BFS e DFS e — o que importa aqui — o mapa de qual problema aplicado cada uma resolve.
2.1 Os dois esqueletos
# BFS — fila; visita por "camadas" (distância crescente em nº de arestas) from collections import deque def bfs(adj, s): dist = {s: 0}; q = deque([s]) while q: u = q.popleft() for v in adj[u]: if v not in dist: dist[v] = dist[u] + 1 q.append(v) return dist # DFS — pilha/recursão; vai fundo antes de voltar def dfs(adj, u, seen): seen.add(u) for v in adj[u]: if v not in seen: dfs(adj, v, seen)
Ambas: O(V+E) com lista de adjacência. BFS usa fila; DFS usa pilha (ou a de chamadas — cuidado com o limite de recursão em grafos grandes; use pilha explícita).
2.2 O mapa "problema → algoritmo"
| Problema | Ferramenta |
|---|---|
| Menor nº de saltos entre dois nós; "amigos até 3º grau" | BFS (grafo não ponderado) |
| Todos os nós alcançáveis / componentes conexos | BFS ou DFS (qualquer) |
| Existe ciclo? / detectar dependência circular | DFS com cores (branco/cinza/preto) — aresta para nó "cinza" = ciclo |
| Ordem válida de execução de tarefas com dependências | Ordenação topológica (DFS pós-ordem, ou Kahn com fila de grau 0) |
| Menor caminho em grade / labirinto (custo uniforme) | BFS |
| Menor caminho com pesos 0/1 | 0-1 BFS (deque) |
| Flood fill / regiões conectadas em imagem | BFS ou DFS |
| Bicoloração / testar se é bipartido | BFS/DFS alternando cor |
BFS acha o menor número de arestas, não o menor custo. Se as arestas têm pesos, o menor caminho pode ter mais saltos — aí é Dijkstra (módulo 3). Usar BFS em grafo ponderado é um erro clássico de entrevista.
Metade dos problemas de grafo em entrevista é "reconheça que é BFS/DFS e adapte". Sinais: "menor número de passos" → BFS; "todos os caminhos / combinações / detectar ciclo" → DFS/backtracking; "ordem respeitando pré-requisitos" → topológica. Saber a coloração de 3 estados da DFS para detecção de ciclo em grafo dirigido é esperado em vaga pleno+.
✏️ Exercício 2 — Escolha e justifique
(a) Um build system precisa dizer se a configuração tem dependência circular e, se não, em que ordem compilar. (b) Numa rede social, "mostre pessoas a até 2 conexões de mim". (c) Verificar se um mapa pode ser colorido com 2 cores sem vizinhos iguais.
Gabarito: (a) DFS com 3 cores para detectar ciclo em grafo dirigido; se não houver, ordenação topológica (Kahn ou DFS pós-ordem invertida) dá a ordem de compilação. (b) BFS a partir de "mim", parando na camada 2 (dist == 2) — grafo não ponderado, "número de saltos". (c) BFS/DFS bicoloração: colore o nó inicial, alterna a cor nos vizinhos; se encontrar uma aresta entre dois nós da mesma cor, não é bipartido/2-colorível.
Caminhos mínimos
Objetivo: escolher entre Dijkstra, A*, Bellman-Ford e Floyd-Warshall pelo que o problema tem — pesos negativos, heurística disponível, um par ou todos os pares.
3.1 A tabela de decisão
| Algoritmo | Resolve | Pesos negativos? | Complexidade |
|---|---|---|---|
| BFS | 1→todos, sem peso | — | O(V+E) |
| Dijkstra | 1→todos, pesos ≥ 0 | não | O((V+E) log V) com heap |
| A* | 1→1, pesos ≥ 0, com heurística admissível | não | ≤ Dijkstra na prática; ótimo se h é admissível |
| Bellman-Ford | 1→todos, aceita pesos negativos; detecta ciclo negativo | sim | O(V·E) |
| Floyd-Warshall | todos→todos | sim (sem ciclo negativo) | O(V³) |
| Johnson | todos→todos, esparso, com negativos | sim | O(V·E + V² log V) |
3.2 Dijkstra — o cavalo de batalha
import heapq def dijkstra(adj, s): # adj[u] = [(v, w), ...], w >= 0 dist = {s: 0}; pq = [(0, s)] while pq: d, u = heapq.heappop(pq) if d > dist.get(u, float("inf")): continue # entrada obsoleta for v, w in adj[u]: nd = d + w if nd < dist.get(v, float("inf")): dist[v] = nd heapq.heappush(pq, (nd, v)) return dist
- Por que não aceita peso negativo: Dijkstra "fecha" um nó ao tirá-lo do heap, assumindo que nenhum caminho futuro o melhora — uma aresta negativa quebra essa premissa.
- Reconstruir o caminho: guardar
prev[v] = ua cada relaxamento e caminhar de trás para frente.
3.3 A* — Dijkstra com palpite
A* prioriza pela soma f(n) = g(n) + h(n), onde g é o custo real até n e h é uma estimativa do custo de n até o destino. Se h nunca superestima (admissível) — ex.: distância em linha reta num mapa — A* acha o caminho ótimo explorando muito menos nós que Dijkstra. É o algoritmo de rotas de GPS e de pathfinding em jogos.
3.4 Bellman-Ford e Floyd-Warshall
- Bellman-Ford: relaxa todas as arestas V−1 vezes. Se na V-ésima passada ainda dá para relaxar, há ciclo negativo — o que é útil por si só (arbitragem em câmbio: um ciclo de conversões que gera lucro é um ciclo negativo no grafo de
−log(taxa)). - Floyd-Warshall: três loops aninhados;
d[i][j] = min(d[i][j], d[i][k] + d[k][j]). Simples, O(V³) — só para grafos pequenos (algumas centenas de nós) quando você quer a matriz completa de distâncias.
Perguntas: "por que Dijkstra não funciona com pesos negativos?" (fecha nós cedo demais), "quando usar A* em vez de Dijkstra?" (par único + heurística admissível), "como detectar arbitragem / ciclo negativo?" (Bellman-Ford na V-ésima iteração). Em system design de mapas/logística/jogos, saber posicionar A* e pré-computação (contraction hierarchies) é diferencial.
✏️ Exercício 3 — Qual algoritmo?
(a) App de entregas: melhor rota de um depósito para 1 endereço, ruas com tempo estimado, você tem as coordenadas. (b) Detectar se um conjunto de taxas de câmbio permite lucro por conversões em ciclo. (c) Num jogo de tabuleiro 20×20, distância mínima entre todos os pares de casas, com custos de terreno. (d) Menor caminho num grafo social não ponderado.
Gabarito: (a) A* — par único, pesos ≥ 0, heurística = distância em linha reta (admissível). (b) Bellman-Ford sobre o grafo de −log(taxa); ciclo negativo = arbitragem. (c) 400 nós, todos os pares, custos não-negativos → Floyd-Warshall (O(V³) ≈ 64 M, ok) ou Dijkstra a partir de cada nó. (d) BFS — sem pesos, "número de saltos".
Estrutura: MST, SCC, pontes
Objetivo: os algoritmos que revelam a forma do grafo — árvore geradora mínima, componentes fortemente conexos, e pontos frágeis (pontes e articulações).
4.1 Árvore Geradora Mínima (MST)
Conectar todos os nós com o menor custo total de arestas, sem ciclos. Aplicações: projetar redes (elétrica, fibra, água) com custo mínimo; clustering (remover as arestas mais caras da MST separa grupos); aproximações para o caixeiro viajante.
| Algoritmo | Ideia | Estrutura | Complexidade |
|---|---|---|---|
| Kruskal | ordena arestas por peso; adiciona a próxima se não formar ciclo | Union-Find (DSU) | O(E log E) |
| Prim | cresce a árvore a partir de um nó, sempre pela aresta mais barata que sai dela | heap | O(E log V) |
Kruskal brilha em grafo esparso; Prim, em denso (com heap) ou quando você já tem uma matriz.
4.2 Componentes Fortemente Conexos (SCC) grafo dirigido
Um SCC é um subconjunto máximo onde todo nó alcança todo outro (ida e volta). Contrair cada SCC num super-nó transforma qualquer grafo dirigido num DAG — o que permite ordenação topológica e análise de dependências mesmo com ciclos presentes.
- Tarjan (uma DFS, com
low-link) ou Kosaraju (duas DFS, uma no grafo transposto). Ambos O(V+E). - Usos: achar módulos com dependência circular num codebase; comunidades "de mão dupla" numa rede de citações/menções; resolver 2-SAT (satisfatibilidade) via SCC no grafo de implicações.
4.3 Pontes e pontos de articulação robustez
- Ponte: aresta cuja remoção desconecta o grafo. Ponto de articulação: nó cuja remoção desconecta.
- Achados por uma DFS com
discovery timeelow(o menor tempo alcançável por back-edges). - Aplicação direta: single points of failure numa rede (um roteador/enlace cuja queda parte a rede em duas), pontos críticos numa malha logística, gargalos estruturais.
MST e Union-Find caem muito (Kruskal é praticamente "DSU + ordenação"). SCC aparece em problemas de dependência e em 2-SAT. Pontes/articulações caem como "encontre o ponto único de falha da rede". Saber que contrair SCCs dá um DAG é uma sacada que resolve vários problemas de grafo dirigido com ciclo.
✏️ Exercício 4 — Estrutura revela o quê
(a) Uma operadora quer ligar 200 torres com fibra ao menor custo; você tem o custo de cada trecho possível. (b) Num microsserviço com 60 serviços, quais grupos têm dependência circular entre si? (c) Numa rede de 500 enlaces, quais enlaces, se caírem sozinhos, dividem a rede?
Gabarito: (a) MST — Kruskal (esparso) com Union-Find, ou Prim. O custo da MST é o mínimo para conectar tudo. (b) SCC (Tarjan/Kosaraju) sobre o grafo de dependências dirigido: cada SCC com mais de um serviço é um grupo com dependência circular — candidato a refatoração. (c) Pontes — DFS com disc/low; cada ponte é um enlace cuja queda desconecta, ou seja, um SPOF estrutural.
Fluxo e emparelhamento
Objetivo: reconhecer os problemas que viram fluxo máximo ou emparelhamento bipartido — muitas vezes disfarçados de "alocação", "capacidade" ou "corte".
5.1 Fluxo máximo / corte mínimo
Dado um grafo com capacidades nas arestas, uma fonte e um sorvedouro, qual o fluxo máximo que passa da fonte ao sorvedouro? O teorema max-flow / min-cut diz que esse valor é igual à capacidade do menor conjunto de arestas cuja remoção separa fonte e sorvedouro — o que torna "fluxo máximo" também a resposta para "corte mínimo" (segmentação, particionamento, confiabilidade).
- Ford-Fulkerson (achar caminhos aumentantes) / Edmonds-Karp (BFS para o caminho, O(V·E²)) / Dinic (O(V²·E), rápido na prática, o padrão).
- Aplicações: capacidade de uma rede (dados, água, tráfego); segmentação de imagem (graph cut); confiabilidade (quantas falhas a rede aguenta); project selection e outros problemas de otimização que se reduzem a min-cut.
5.2 Emparelhamento bipartido
Dois grupos (trabalhadores × tarefas, candidatos × vagas, alunos × projetos) e arestas de compatibilidade. O emparelhamento máximo é o maior conjunto de pares sem repetir ninguém. Resolve-se como um caso de fluxo máximo (fonte → grupo A → grupo B → sorvedouro, capacidades 1) ou com Hopcroft-Karp (O(E√V)).
- Assignment problem (emparelhamento de custo mínimo, cada aresta com um custo): algoritmo húngaro (O(V³)) ou min-cost max-flow.
- Teorema de Hall: existe emparelhamento perfeito do lado A sse e só se todo subconjunto S de A tem pelo menos |S| vizinhos — critério útil para provar (in)viabilidade.
Palavras-gatilho: "capacidade", "máximo de X simultâneos", "alocar cada A a no máximo um B", "menor conjunto de arestas para desconectar", "cada recurso serve k pedidos". Muitos problemas que parecem exigir força bruta são fluxo máximo com uma modelagem esperta (dividir um nó em "entrada/saída" para impor capacidade no nó, por exemplo).
Fluxo é o tópico "avançado" que separa candidatos em vagas mais competitivas e em competitivo. Não precisa implementar Dinic de cabeça, mas precisa reconhecer que "alocar entregadores a zonas com limite por zona" ou "quantos caminhos disjuntos existem entre A e B" são fluxo/matching, e saber que min-cut = max-flow.
✏️ Exercício 5 — Reduza a fluxo ou matching
(a) 40 médicos, cada um disponível em certos turnos; cada turno precisa de exatamente 3 médicos; cada médico faz no máximo 5 turnos na semana. Existe uma escala válida? (b) Numa rede, quantos enlaces preciso cortar, no mínimo, para isolar o datacenter A do datacenter B? (c) Atribuir 30 tarefas a 30 pessoas minimizando o custo total, dado o custo de cada pessoa em cada tarefa.
Gabarito: (a) Fluxo máximo: fonte → cada médico (capacidade 5) → turnos em que ele pode (cap. 1) → sorvedouro (cada turno com capacidade 3). Escala válida existe se o fluxo máximo = 3 × (nº de turnos). (b) É min-cut entre A e B = max-flow de A para B com capacidade 1 por enlace (ou a capacidade real, se quiser o corte de menor capacidade). (c) Assignment problem — algoritmo húngaro ou min-cost max-flow; emparelhamento perfeito de custo mínimo num grafo bipartido completo com custos.
Ciência de redes
Objetivo: medir a estrutura de redes reais — quem é importante (centralidade), quais os grupos (comunidades), quais ligações vão surgir (previsão de link) — com NetworkX e Neo4j GDS.
6.1 Centralidade: "importância" tem várias definições
| Medida | Captura | Exemplo de uso |
|---|---|---|
| Grau | quantas conexões diretas | influência local; nós populares |
| Intermediação (betweenness) | quantos caminhos mínimos passam pelo nó | "pontes" entre grupos; pontos de controle de fluxo; vulnerabilidade |
| Proximidade (closeness) | quão perto (em média) o nó está de todos os outros | melhor posição para espalhar informação rápido |
| Autovetor / PageRank | importância recursiva: você é importante se nós importantes te apontam | ranking de páginas, de papers, de contas; detecção de influência real vs inflada |
| Katz / HITS | variações (hubs e authorities no HITS) | ranking em grafos de citação/link |
Betweenness exata é O(V·E) (Brandes) — cara em grafos grandes; usa-se amostragem. PageRank é iterativo (multiplicação matriz-vetor até convergir), escala bem.
6.2 Detecção de comunidades
- Louvain: otimiza modularidade (densidade de arestas dentro dos grupos vs esperado ao acaso) de forma gulosa e hierárquica. Rápido, o padrão de fato.
- Leiden: melhora o Louvain garantindo comunidades bem conectadas (o Louvain às vezes produz grupos internamente desconexos). Prefira Leiden quando disponível.
- Label propagation: cada nó adota o rótulo mais comum entre vizinhos, iterando. Quase linear, mas instável.
- Usos: segmentação de clientes por interação, detecção de bolhas/eco em redes sociais, módulos funcionais em redes biológicas, anéis de fraude.
6.3 Previsão de link
Dado o grafo atual, quais arestas vão aparecer (ou estão faltando)? Base de "pessoas que você talvez conheça", recomendação, completar bases incompletas.
- Heurísticas locais: vizinhos em comum, Jaccard, Adamic-Adar (pondera vizinhos raros), preferential attachment (grau × grau).
- Baseadas em caminho: Katz, rooted PageRank.
- Aprendidas: embeddings de nó (módulo 7) + classificador, ou GNN (módulo 8).
- Avaliação: dividir arestas em treino/teste no tempo, medir AUC / precisão@k. Cuidado com data leakage temporal.
6.4 As ferramentas
import networkx as nx G = nx.karate_club_graph() pr = nx.pagerank(G) btw = nx.betweenness_centrality(G) comm = nx.community.louvain_communities(G, seed=42)
- NetworkX: didático e completo, Python puro — até ~10⁴–10⁵ nós. igraph / graph-tool: C por baixo, ordens de magnitude mais rápido. cuGraph (RAPIDS): GPU.
- Neo4j Graph Data Science (GDS): roda PageRank, Louvain/Leiden, node2vec, previsão de link etc. dentro do banco de grafo, sobre projeções em memória — o caminho de produção quando o grafo já vive no Neo4j.
Ciência de redes aparece em times de fraude, risco, growth, produto (recomendação) e pesquisa. O que se espera: saber que "centralidade" não é uma coisa só (grau ≠ betweenness ≠ PageRank) e qual usar para qual pergunta; conhecer Louvain/Leiden para comunidades; e citar o problema do vazamento temporal em previsão de link. Mão na massa com NetworkX + um dataset real vale muito no portfólio.
✏️ Exercício 6 — A medida certa
(a) Numa rede de colaboração entre pesquisadores, quem, se sair, mais fragmenta a colaboração entre subáreas? (b) Numa rede de transações, quais contas formam grupos densos que quase não transacionam para fora (possível lavagem)? (c) Numa rede de seguidores, quem tem influência "real" e não apenas muitos seguidores comprados?
Gabarito: (a) Betweenness alta — esses nós estão nos caminhos entre subáreas; removê-los desconecta grupos. (b) Detecção de comunidades (Louvain/Leiden) + checar a razão de arestas internas/externas de cada comunidade; grupos com modularidade alta e pouquíssimas arestas externas são suspeitos. (c) PageRank (ou autovetor): seguidores comprados são contas de baixo PageRank, então apontar-lhe não eleva o seu; influência real vem de ser apontado por contas que também são apontadas.
Embeddings de grafo
Objetivo: transformar nós (ou o grafo todo) em vetores densos que preservam a estrutura — para alimentar classificadores, busca por similaridade e clustering sem uma GNN.
7.1 A ideia
Um node embedding mapeia cada nó para um vetor de baixa dimensão (64–256) tal que nós estruturalmente próximos no grafo fiquem próximos no espaço vetorial. Com isso, tarefas de grafo viram tarefas tabulares: classificar nós, prever links (distância entre embeddings), agrupar, buscar "nós parecidos".
7.2 Os métodos baseados em passeio aleatório
| Método | Ideia |
|---|---|
| DeepWalk | gera passeios aleatórios (sequências de nós) e trata cada passeio como uma "frase" → aplica word2vec (skip-gram). Nós que aparecem em contextos parecidos ganham vetores parecidos. |
| node2vec | DeepWalk com passeio enviesável: os parâmetros p e q interpolam entre explorar a vizinhança (BFS-like → captura papel/homofilia) e ir longe (DFS-like → captura comunidade). |
| LINE | otimiza diretamente proximidade de 1ª ordem (vizinhos diretos) e 2ª ordem (vizinhanças parecidas); escala para grafos grandes. |
from node2vec import Node2Vec emb = Node2Vec(G, dimensions=128, walk_length=30, num_walks=200, p=1, q=1).fit() vec = emb.wv["42"] # vetor do nó 42 emb.wv.most_similar("42") # nós estruturalmente parecidos
7.3 Graph embeddings (o grafo inteiro) e KG embeddings
- Grafo → vetor (para classificar moléculas, comparar grafos): graph2vec, ou pooling de node embeddings, ou uma GNN com readout (módulo 8).
- Knowledge Graph embeddings (triplas sujeito-relação-objeto): TransE (relação ≈ translação:
h + r ≈ t), DistMult, ComplEx, RotatE. Usados para completar o KG (prever triplas faltantes) e para link prediction em bases de conhecimento.
DeepWalk/node2vec são transdutivos: aprendem um vetor por nó do grafo visto no treino. Um nó novo (usuário que acabou de entrar) não tem embedding sem re-treinar. Para grafos que crescem, prefira métodos indutivos — GraphSAGE (módulo 8) gera o embedding de um nó novo a partir das features dele e da vizinhança, sem re-treino.
Node embeddings são o "meio-termo" pragmático: mais poder que heurísticas, menos complexidade que GNN. Espera-se saber o que node2vec faz (passeios + word2vec), o papel dos parâmetros p/q (homofilia vs estrutura), a limitação transdutiva, e que KG embeddings (TransE e cia.) servem para completar bases de conhecimento.
✏️ Exercício 7 — Embedding serve aqui?
(a) Classificar 100 mil contas como fraude/não-fraude num grafo de transações que muda toda hora, com contas novas o tempo todo. (b) Recomendar "conexões" numa rede profissional razoavelmente estável. (c) Prever relações faltantes num grafo de conhecimento de produtos (marca, categoria, compatível-com).
Gabarito: (a) node2vec é ruim aqui — transdutivo, e o grafo muda com contas novas constantemente. Melhor: GraphSAGE (indutivo, usa features da conta) ou features de grafo calculadas online + modelo tabular. (b) node2vec funciona bem — grafo estável, tarefa de similaridade/link prediction; gerar embeddings, recomendar por vizinhança no espaço vetorial, re-treinar periodicamente. (c) KG embeddings (TransE/ComplEx/RotatE): treinar sobre as triplas existentes e pontuar triplas candidatas; as de score alto são as relações faltantes prováveis.
Graph Neural Networks
Objetivo: entender message passing — a mecânica comum a GCN, GraphSAGE e GAT — as tarefas que uma GNN resolve, e as armadilhas (over-smoothing, escala).
8.1 Message passing: a ideia central
Uma GNN calcula o vetor (embedding) de cada nó agregando os vetores dos vizinhos, camada após camada. Após k camadas, o vetor de um nó resume informação da sua vizinhança de k saltos. Cada camada faz, para todo nó v:
h_v^(k) = UPDATE( h_v^(k-1) , AGGREGATE( { h_u^(k-1) : u vizinho de v } ) )
| Arquitetura | Como agrega |
|---|---|
| GCN | média ponderada normalizada dos vizinhos (pelo grau) + transformação linear + não-linearidade |
| GraphSAGE | amostra um subconjunto de vizinhos e agrega (média, pooling, LSTM) — indutivo e escalável; funciona para nós novos |
| GAT | aprende pesos de atenção por vizinho — nem todo vizinho importa igual |
| GIN | agregação por soma + MLP; teoricamente tão expressiva quanto o teste de isomorfismo de Weisfeiler-Lehman |
8.2 As três tarefas
- Nível de nó: classificar/regredir nós (conta fraudulenta? categoria do artigo? risco do cliente?).
- Nível de aresta / link prediction: esta aresta existe/vai existir? (recomendação, completar KG, descoberta de fármacos — "esta proteína interage com aquela?").
- Nível de grafo: classificar o grafo inteiro via um readout (pooling) dos nós — propriedade de uma molécula, malícia de um binário representado como grafo de chamadas.
8.3 Armadilhas
- Over-smoothing: com muitas camadas, os embeddings de todos os nós convergem para o mesmo — a informação local se dilui. Na prática, 2–4 camadas. Técnicas: conexões residuais, jumping knowledge, normalização.
- Escala: um grafo com bilhões de arestas não cabe na GPU. Soluções: amostragem de vizinhança (GraphSAGE), amostragem de subgrafos (GraphSAINT, Cluster-GCN), e sistemas distribuídos.
- Grafos heterogêneos (vários tipos de nó/aresta): R-GCN, HGT — pesos por tipo de relação.
- Precisa de features de nó: sem features (só estrutura), use grau/embeddings estruturais como entrada, ou volte para node2vec.
- Baseline honesta: muitas vezes um MLP sobre features + algumas features de grafo (grau, PageRank, nº de triângulos) chega perto de uma GNN com muito menos custo. Sempre compare.
import torch_geometric.nn as gnn class Net(torch.nn.Module): def __init__(self, in_dim, hid, out): self.c1 = gnn.SAGEConv(in_dim, hid) self.c2 = gnn.SAGEConv(hid, out) def forward(self, x, edge_index): x = self.c1(x, edge_index).relu() return self.c2(x, edge_index)
Frameworks: PyTorch Geometric (PyG) e DGL são os dois padrões.
GNN é procurada em fraude, recomendação, bioinformática/farma, e cada vez mais em KG + LLM. O que se cobra: explicar message passing em uma frase, saber a diferença GCN (transdutivo, agregação fixa) vs GraphSAGE (indutivo, amostra) vs GAT (atenção), citar over-smoothing e o limite de 2–4 camadas, e — sinal de maturidade — insistir numa baseline (MLP + features de grafo) antes de partir para GNN.
✏️ Exercício 8 — GNN ou não, e qual?
(a) Detecção de fraude num grafo de transações com 200 M de nós que ganha nós novos toda hora, com features ricas por conta. (b) Classificar 3.000 moléculas por toxicidade (cada molécula é um grafo pequeno). (c) Recomendar itens num grafo usuário-item estável, sem features além da estrutura.
Gabarito: (a) GraphSAGE — indutivo (lida com nós novos sem re-treino), amostra vizinhança (escala para 200 M), usa as features das contas; tarefa de classificação de nó. Comparar com baseline MLP+features de grafo. (b) GNN de nível de grafo (GIN ou GCN + readout/pooling) — cada molécula é um grafo, a saída é uma propriedade do grafo inteiro; PyG tem datasets e loaders para isso. (c) Sem features, grafo estável: node2vec + similaridade, ou uma GNN que usa embeddings estruturais como entrada; link prediction. A GNN pode não valer a complexidade aqui.
Knowledge Graphs e GraphRAG
Objetivo: construir e usar um grafo de conhecimento — property graph vs RDF, ontologia, resolução de entidades, extração a partir de texto — e por que ele melhora RAG.
9.1 Property graph vs RDF/SPARQL
| Property Graph (Neo4j, Memgraph) | RDF / Triple Store (GraphDB, Blazegraph, Neptune) | |
|---|---|---|
| Unidade | nós e arestas com propriedades (chave-valor) | triplas <sujeito, predicado, objeto> |
| Consulta | Cypher / GQL | SPARQL |
| Esquema | flexível; rótulos e propriedades | ontologias formais (RDFS/OWL), inferência, IRIs globais |
| Forte em | desenvolvimento de aplicação, travessia, analytics (GDS) | interoperabilidade, dados ligados (Linked Data), raciocínio lógico, padrões W3C |
Para um KG interno de produto, property graph costuma ser mais produtivo. RDF ganha quando você precisa integrar com vocabulários públicos (schema.org, Wikidata), fazer inferência formal, ou publicar dados ligados.
9.2 Ontologia e resolução de entidades — onde a qualidade se decide
- Ontologia / esquema: quais tipos de nó e de relação existem e como se conectam (Pessoa
TRABALHA_EMEmpresa; EmpresaSEDIADA_EMCidade). Sem isso, o KG vira um amontoado inconsistente. Comece pequeno e específico ao caso de uso. - Resolução de entidades (entity resolution / linking): "IBM", "I.B.M." e "International Business Machines" nos textos são um nó. É o passo mais trabalhoso e mais impactante: heurísticas (normalização, similaridade), blocking para escalar, modelos de matching, e ligação a um identificador canônico (Wikidata QID, um id interno).
- Proveniência: cada aresta guarda de onde veio (documento, trecho, data, confiança). Essencial para auditar e para o GraphRAG citar fontes.
9.3 Construir um KG a partir de texto
- Extração de entidades (NER) e de relações — hoje frequentemente com um LLM guiado por um schema-alvo ("extraia triplas (Pessoa, CARGO_EM, Organização)").
- Normalização e resolução de entidades contra o KG existente.
- Carga das triplas/nós com proveniência; deduplicação de arestas.
- Validação: consistência com a ontologia, detecção de contradições, amostragem humana.
- Atualização incremental: novos documentos adicionam/reforçam arestas; versionar e permitir expiração de fatos.
9.4 GraphRAG tema em alta
RAG clássico recupera trechos por similaridade vetorial. Ele falha em perguntas que exigem conectar informação espalhada ("quais fornecedores da empresa A também fornecem para concorrentes dela?") e em perguntas globais ("quais os temas principais deste corpus?"). GraphRAG usa um KG construído do corpus:
- Recuperação por travessia: encontrar as entidades da pergunta no KG e trazer a subrede relevante (vizinhos, caminhos entre entidades) como contexto para o LLM — respostas multi-hop com as ligações explícitas.
- Resumo por comunidade (abordagem da Microsoft): detectar comunidades no KG (Leiden), pré-resumir cada uma, e responder perguntas globais agregando os resumos de comunidade.
- Híbrido: combinar recuperação vetorial de trechos + contexto do grafo; o grafo dá estrutura e citação, os trechos dão o texto literal.
- Custo: construir e manter o KG (extração com LLM é cara) — justifique com perguntas que o RAG vetorial comprovadamente erra. Não é substituto universal do RAG clássico.
Conecta com a apostila de IA Generativa & RAG (retrieval, avaliação) e com a Modelagem de Dados NoSQL (módulo 7, modelagem de grafo e KG).
KG + GraphRAG é uma das habilidades de IA mais procuradas e menos concorridas em 2026. O que diferencia: saber que a ontologia e a resolução de entidades determinam a qualidade (não o banco escolhido); saber quando GraphRAG vence RAG vetorial (multi-hop, perguntas globais) e quando não vale o custo; e ter construído um KG pequeno de ponta a ponta.
✏️ Exercício 9 — Projete o KG
Você tem 50 mil relatórios de incidentes de TI em texto livre. Objetivo: responder perguntas como "quais serviços foram afetados por incidentes causados pela mudança X?" e "qual componente aparece na maioria dos incidentes de rede?". Descreva ontologia, extração, resolução de entidades e a estratégia de recuperação.
Gabarito (esboço): Ontologia: nós Incidente, Servico, Componente, Mudanca, Causa; arestas AFETOU (Incidente→Servico), ENVOLVEU (Incidente→Componente), CAUSADO_POR (Incidente→Mudanca/Causa), DEPENDE_DE (Servico→Componente). Extração: LLM guiado pelo schema extrai triplas de cada relatório, com proveniência (id do relatório, data, trecho). Resolução de entidades: normalizar nomes de serviço/componente contra o CMDB/catálogo de serviços existente (id canônico); heurística + revisão dos casos de baixa confiança. Recuperação: para "serviços afetados pela mudança X" — achar o nó Mudanca X, seguir <-CAUSADO_POR- Incidente -AFETOU-> Servico (travessia multi-hop, resposta exata com fontes). Para "componente mais frequente em incidentes de rede" — filtrar incidentes por categoria "rede", contar ENVOLVEU por componente (analítico, não vetorial). RAG vetorial puro erraria a primeira (informação espalhada em vários relatórios) e a segunda (é agregação, não recuperação de trecho).
Visualização, mercado de trabalho e entrevistas
Objetivo: visualizar grafos sem virar "prato de espaguete", e converter o conteúdo em aprovação — o que cada trilha cobra, projetos e perguntas.
10.1 Visualização de grafos
- Layouts: force-directed (ForceAtlas2, Fruchterman-Reingold) para estrutura geral; hierárquico/Sugiyama para DAGs; circular/arc para poucos nós; matriz de adjacência para grafos densos (evita o "hairball").
- Ferramentas: Gephi (exploração desktop), Cytoscape.js e D3-force (web interativo), graphviz (DAGs estáticos), yFiles/sigma.js (grande escala), o próprio Neo4j Bloom.
- Grafos grandes: não plote 100 mil nós — filtre (k-core, maior componente), agregue (desenhe comunidades como super-nós), ou use amostragem. Codifique com tamanho (grau/centralidade), cor (comunidade) e espessura (peso).
- Acessibilidade: não dependa só de cor (→ apostila de WCAG); rótulos legíveis; alternativa em tabela.
10.2 Onde grafos aparecem nas vagas
| Trilha | O que cobram |
|---|---|
| Backend / SWE (entrevista) | BFS/DFS, topológica, Dijkstra, Union-Find/MST, detecção de ciclo; reconhecer o problema como grafo |
| Engenharia de Dados | DAGs de pipeline, linhagem de dados, modelagem de grafo, GDS/consultas de travessia |
| Fraude / Risco / Antifinanceiro | ciência de redes (centralidade, comunidades), grafos de entidade, GNN para detecção |
| ML / DS | node2vec, GNN (PyG/DGL), previsão de link, vazamento temporal, baselines |
| IA / LLM | Knowledge Graphs, extração com LLM, GraphRAG, resolução de entidades |
10.3 Roadmap (5 semanas)
- Sem. 1 — módulos 1–2: implementar do zero (sem lib) BFS, DFS, detecção de ciclo, topológica, componentes conexos; resolver 15 problemas de grafo.
- Sem. 2 — módulos 3–4: Dijkstra + reconstrução de caminho, A* num grid, Kruskal com Union-Find, Tarjan SCC.
- Sem. 3 — módulo 6: NetworkX num dataset real (rede de coautoria, transações, social) — PageRank, betweenness, Louvain; um relatório com achados.
- Sem. 4 — módulos 7–8: node2vec + classificador para classificação de nó; depois GraphSAGE em PyG na mesma tarefa, comparando com baseline MLP+features.
- Sem. 5 — módulo 9: construir um KG pequeno de um corpus (extração com LLM guiada por schema, resolução de entidades básica, Neo4j), e um GraphRAG que responde 5 perguntas multi-hop com citação.
10.4 Banco de perguntas (com a resposta que aprova)
Backend — "Como você representaria um grafo e por quê?"
Lista de adjacência para grafo esparso (a maioria): O(V+E) de espaço, itera vizinhos em O(grau). Matriz para grafo denso ou álgebra linear (PageRank via potências). CSR para grafo grande e estático em processamento em lote/GPU. A escolha decorre de densidade, se é dinâmico, e das operações dominantes.
Backend — "Por que Dijkstra não funciona com pesos negativos, e o que usar?"
Dijkstra fecha um nó ao retirá-lo do heap, assumindo que nenhum caminho futuro o melhora — uma aresta negativa viola isso. Com pesos negativos (sem ciclo negativo), use Bellman-Ford (O(V·E)), que também detecta ciclo negativo na V-ésima iteração; para todos-os-pares com negativos e grafo esparso, Johnson.
DS/ML — "node2vec vs GraphSAGE, quando cada um?"
node2vec: passeios aleatórios enviesáveis (p/q entre homofilia e estrutura) + word2vec; transdutivo — só nós vistos no treino, sem features. Bom para grafo estável sem features. GraphSAGE: agrega features de vizinhos amostrados; indutivo — gera embedding de nós novos sem re-treino, escala por amostragem. Bom para grafos que crescem e com features de nó. Em ambos os casos, comparar com baseline (MLP + features de grafo).
IA — "O que GraphRAG resolve que o RAG vetorial não resolve?"
Perguntas multi-hop (conectar fatos espalhados em vários documentos — "fornecedores de A que também atendem concorrentes") e perguntas globais ("temas principais do corpus"), que a recuperação por similaridade de trechos não cobre. GraphRAG constrói um KG do corpus e recupera por travessia (subrede entre as entidades da pergunta) e/ou por resumos de comunidade. Custo: extração do KG com LLM é cara — justifica-se onde o RAG vetorial comprovadamente falha; não é substituto universal.
Fraude — "Que medida de grafo para achar contas que intermediam grupos?"
Betweenness centrality — conta quantos caminhos mínimos passam por cada nó; alta betweenness = nó "ponte" entre grupos, típico de laranjas/intermediários. Complementar com detecção de comunidades (Leiden) para ver os grupos e com a razão de arestas internas/externas para achar clusters fechados.
10.5 Fontes
- Algoritmos: CLRS (capítulos de grafo); o capítulo 8 da apostila de Algoritmos com Python; problemas em plataformas de prática.
- Ciência de redes: Networks (Mark Newman); Network Science (Barabási, online e gratuito); documentação do NetworkX e do Neo4j GDS.
- GNN: curso CS224W (Stanford, Machine Learning with Graphs); tutoriais do PyTorch Geometric e do DGL; o livro Graph Representation Learning (Hamilton, gratuito).
- KG / GraphRAG: a documentação do GraphRAG da Microsoft; material do Neo4j sobre KG + LLM; papers de KG embeddings (TransE e sucessores).
- Apostilas irmãs: NoSQL, Modelagem de Dados NoSQL, IA Generativa & RAG, Redes Neurais, Análise de Dados.
Grafo é uma estrutura, não um nicho: o mesmo objeto responde "qual a rota mais curta" (Dijkstra/A*), "quem é o gargalo" (betweenness, pontes), "quais os grupos" (Louvain/Leiden), "esta ligação vai existir" (embeddings, GNN) e "como conectar fatos espalhados num corpus" (Knowledge Graph, GraphRAG). O trabalho é reconhecer qual pergunta você tem e trazer a ferramenta certa — do BFS de dez linhas ao GraphRAG.