Meu código Python é uma rede neural
(blog.gabornyeki.com)- Programas de extração de informações ambíguas tendem a virar código espaguete à medida que regras de exceção se acumulam, e essa lógica baseada em estado pode ser repensada como uma rede neural recorrente (RNN)
- Um exemplo de encontrar referências a código C em mensagens de code review é implementado como um classificador escrito à mão que rastreia padrões de tokens como
identifier–open_paren–close_parencomo estados - Essa regra mostra 100% de precisão no exemplo, mas deixa passar casos como
if (err) goto cleanup;, fazendo o recall ficar em 50%; conforme regras são adicionadas,Statee ramificações ficam mais complexos - A mesma máquina de estados pode ser codificada como o estado oculto e os cálculos de camadas de uma RNN; para torná-la treinável, são necessários ReLU, sigmoid e pesos e vieses treináveis em vez de funções indicadoras binárias
- Implementações como Elman RNN, GRU e LSTM no PyTorch e o problema de desvanecimento do gradiente em longas sequências de tokens tornam-se limitações práticas do treinamento; o próprio processo de definir dataset, rótulos e função de perda também ajuda no projeto de regras manuais
Como a extração de dados ambíguos vira código espaguete
- Programas de pesquisa que extraem informações de dados brutos veem suas regras ficarem rapidamente complexas quando os dados não seguem uma especificação clara ou têm formatos incomuns
- Exemplos de tarefas incluem identificar empresas e executivos em notícias, rotular contratos de compras públicas por tipo de serviço e decidir se mensagens de engenheiros contêm código de programa
- Se você quiser uma saída perfeita, pode inspecionar cada observação cuidadosamente e escrever testes unitários para casos representativos
- Tanto R quanto Python oferecem bibliotecas de teste para isso
- Em situações que exigem regras de julgamento complexas, como apelidos, sinônimos e a fronteira entre inglês e código, regras escritas à mão ficam frágeis com facilidade
- Algoritmos de treinamento de redes neurais mudam o problema para encontrar essas combinações de regras nos dados, em vez de uma pessoa continuar ajustando-as manualmente
Encontrando referências a código em mensagens de code review
- O objetivo é detectar se uma mensagem enviada durante um code review faz referência explícita a código de programa
- Presume-se que a base de código observada seja escrita em C
- Mensagens representativas incluem referências a código como:
render_ipa_alloc()FTPSACKdebug_error()NULLIS_ERROR()aarch64,amd64if (err) goto cleanup;
- Candidatas a regras simples falham de maneiras diferentes
- Uma regra que considera código quando um parêntese vem depois de uma palavra captura casos como
render_ipa_alloc(), mas deixa passarif (err) goto cleanup; - Uma regra que considera código palavras totalmente em maiúsculas captura
FTPSACKeIS_ERROR(), mas gera falso positivo para abreviações comoAFAICT - Uma regra que considera código palavras que não são inglês pode marcar falsamente até termos de engenharia ou nomes de arquitetura como código
- Uma regra que considera código quando um parêntese vem depois de uma palavra captura casos como
- Para melhorar as regras 2 e 3, seria necessária uma lista de abreviações e termos técnicos como
AFAICT,LGTM,USD,COVID,aarch64eamd64
Classificador feito com máquina de estados escrita à mão
- O algoritmo simples decide em duas etapas se uma mensagem contém código
- Pré-processamento: converte a mensagem em uma sequência de tokens que refletem elementos sintáticos do código C
- Inferência: verifica se a sequência de tokens satisfaz as regras
- A Rule 1 considera o padrão
underscore_identifier–open_paren–close_parencomo referência a código - A implementação em Python armazena o estado dos tokens anteriores em uma data class
Stateprevious_was_identifierprevious_was_open_parenprevious_previous_was_identifierseen_code
contains_codepercorre os tokens chamandoprocesse, ao final, retornastate.seen_codeprocessdefineseen_codecomoTruequando o token atual éclose_paren, o anterior éopen_parene o anterior a esse é um identificador- Esse classificador não tem falsos positivos e mostra 100% de precisão no exemplo, mas deixa muitos casos passarem, então o recall fica em 50%
- Ao adicionar a Rule 2, os campos de
Statee as ramificaçõesif/elif/elseaumentam, e quanto mais as regras são refinadas, mais difícil fica a manutenção
Levando a máquina de estados para uma RNN
contains_codeeprocesssão uma máquina de estados, e máquinas de estados podem ser codificadas como redes neurais recorrentes (RNNs)- Uma RNN processa a sequência de tokens um por um e aproxima a probabilidade condicional de que a mensagem contenha código
- O valor correspondente ao
Stateem Python é representado pelo estado oculto da RNNState_0é o estado inicial- Cada
State_té calculado passando o token atual e o estado anterior para a funçãof - O estado final passa pela camada de saída
ge vira o resultado da classificação
- A RNN do exemplo usa três camadas ocultas
- A primeira camada armazena ou copia o token atual e o estado anterior
- A segunda camada verifica o padrão correspondente à Rule 1
- A terceira camada lembra se já viu um padrão de código
- Tokens são representados como vetores binários em formato one-hot
- Para imitar exatamente o algoritmo escrito à mão, é possível usar a função indicadora binária
1{x > 0}- As camadas ocultas podem ser mantidas como valores binários
- Mas isso é inadequado para treinamento, porque a derivada é 0 em quase todos os lugares
- Também seria possível verificar o padrão pelo produto de
identifier,open_pareneclose_paren, mas, em camadas ocultas binárias, a mesma verificação pode ser expressa por soma - Giles et al. (1992) é citado como um caso de uso de RNN de segunda ordem para descobrir máquinas de estados
Transformando em uma rede treinável
- Para treinar, usa-se ReLU em vez da função indicadora binária
- Constantes numéricas são substituídas por pesos e vieses, e o gradiente descendente estima esses parâmetros
- A camada de saída calcula o valor final de probabilidade com uma função de ativação sigmoid
- Esse formato pode ser colocado no PyTorch e treinado, mas, se treinado como está, não tem desempenho excelente
- Um dos motivos para o desempenho insuficiente é que a arquitetura não é convencional, então uma parte maior do procedimento de treinamento roda no código de conexão em Python, aproveitando menos as implementações das bibliotecas C++ do PyTorch
Implementação em PyTorch e limitações de mensagens longas
- torch.nn.RNN, do PyTorch, oferece uma implementação baseada em Elman RNN
- A arquitetura do exemplo e a Elman RNN diferem na forma de conexão das camadas ocultas
- Na arquitetura do exemplo, a primeira camada do token
trecebe como entrada a terceira camada do tokent-1, e cada camada recebe como entrada apenas a camada imediatamente anterior - Na Elman RNN, cada camada oculta também recebe como entrada o estado do instante anterior da mesma camada
- A primeira camada oculta da Elman RNN não recebe como entrada a camada final do instante anterior
- Na arquitetura do exemplo, a primeira camada do token
- Mensagens reais de code review podem ser longas, e mensagens longas levam a sequências longas de tokens
- Em sequências longas, mesmo que o gradiente descendente funcione em teoria, podem surgir problemas de estabilidade numérica por causa do desvanecimento do gradiente
- Elman RNNs também podem ser vulneráveis a esse problema, e GRU ou LSTM podem ter desempenho melhor na tarefa de detecção de código
Disciplina orientada por dados
- RNNs transformam combinações de regras difíceis de manipular manualmente em um alvo de treinamento e forçam uma definição mais clara do problema
- Para treinar a rede, é preciso:
- escolher um dataset de treinamento e um dataset de validação
- rotular previamente
- definir uma função de perda que explicite o que o classificador deve alcançar e o que deve evitar
- Esse processo revela zonas cinzentas inesperadas e torna os critérios de julgamento mais claros
- Essa disciplina orientada por dados é útil também para problemas resolvidos com algoritmos escritos à mão, não apenas com redes neurais
1 comentários
Opiniões do Hacker News
O texto não trata muito de testes nem da obtenção de dados de treinamento, mas isso parece ser o ponto central.
Um código que sentimos entender é aquele para o qual nós mesmos provamos, ainda que informalmente, que suas propriedades se generalizam para todas as entradas. Por exemplo, um algoritmo de ordenação ordena não só as listas testadas, mas qualquer lista.
A parte incerta em redes neurais está em não saber como elas vão se generalizar. Se uma entrada não vista for só um pouco diferente, não há propriedades garantidas; e, para começo de conversa, pode ser um problema em que é difícil especificar matematicamente a propriedade desejada.
Se for possível definir uma propriedade com clareza suficiente para usá-la em testes baseados em propriedades, como QuickCheck, dá para criar um grande volume de testes ou dados de treinamento por meio de aleatorização. Basta escrever testes que, partindo de um exemplo desejado, gerem possíveis variações de exemplos positivos e negativos.
Não é uma prova, mas é um ponto de partida. Pelo menos, se for possível provar, dá para saber o que precisa ser provado.
Com algo assim, depender de código espaguete e depender de uma rede neural parecem coisas bem parecidas. Se você quiser satisfazer também outras propriedades, basta acrescentar mais um teste baseado em propriedades. A rede neural poderia ser treinada em vez de modificada diretamente, mas também há assistência de IA para modificar código.
Ainda assim, acho que eu confiaria mais no código. Pelo menos dá para fazer debugging.
É um texto interessante se lido como uma forma de criar uma rede neural que execute uma tarefa prática. Mas, se na próxima vez em que eu precisar fazer parsing de uma entrada alguém disser que vai seguir exatamente esse método, sinceramente não sei o que dizer.
O autor pega o problema difícil de fazer parsing de entradas arbitrárias com padrões definidos de forma frouxa e diz, corretamente, que isso tem grande chance de virar código espaguete difícil de ler.
Só que, como alternativa, propõe um código tão difícil de ler que ainda estamos estudando como ele funciona: uma rede neural.
Dá para entender, mas não devemos supervalorizar algo totalmente impossível de interpretar em relação a algo apenas “feio”. Para algumas tarefas, um modelo de aprendizado de máquina pode ser o certo, mas em muitos casos, mesmo exigindo esforço, é melhor ter algo cujo funcionamento possamos ler e verificar do que algo em que isso é impossível.
No fim, aprendizado de máquina apenas oferece mais uma opção. Se ela se encaixa bem depende dos resultados da avaliação e do nível de determinismo e explicabilidade exigido pelo algoritmo escolhido.
O que chama atenção é se uma RNN é a escolha certa. Ela precisa de treinamento e pode exigir muito mais exemplos do que os disponíveis. Por outro lado, parece possível criar dados sintéticos de casos positivos e negativos com base nas regras conhecidas.
Um sistema em que dá para ver por que ele se comporta daquele jeito é bom, mas não significa nada se ele continua dando respostas erradas. No uso real, muitas vezes obter a resposta certa é mais importante do que saber como se chegou a ela.
Redes neurais contam com o teorema da aproximação universal de funções. Ele diz que é possível representar ou codificar qualquer função até o nível de precisão desejado[0].
Mas não há um teorema dizendo que essa aproximação pode ser aprendida, nem como ela é aprendida.
[0] https://en.m.wikipedia.org/wiki/Universal_approximation_theo...
Isso está longe de explicar como redes neurais realistas e úteis, como redes neurais convolucionais, transformers e LSTMs, realmente funcionam.
Curiosamente, esse teorema trata de redes com uma única camada. Na prática, redes com várias camadas funcionam muito melhor.
É um texto realmente bom e, embora eu não tenha entendido completamente os conceitos matemáticos mais profundos em torno de RNNs, ele provoca várias ideias.
Parece parecido com algo que venho explorando ultimamente: criar apps conectando-os a um algoritmo de encadeamento para frente. O autor usa uma RNN; eu estou construindo em cima do algoritmo Rete.
Também acho poderosa a ideia de pensar em consumir a string de entrada caractere por caractere. Assim, deixamos a lógica de inferência para o algoritmo e escrevemos apenas uma camada muito fina de entrada e saída; o algoritmo cuida do resto.
O que torna este texto bom é que ele explica, a partir da experiência de treinamento, o que realmente significa transformar uma função em uma RNN, comparando isso com a RNN “com baterias incluídas” que vem no PyTorch.
A pergunta é: foi dito que seria preciso adicionar três camadas ocultas à rede para modelar o estado; por que três? Fico curioso se isso é resultado das regras específicas que ele estava tentando implementar, ou se é o número de camadas geralmente usado ao implementar regras desse formato nessa arquitetura. Também me pergunto se uma estrutura Elman talvez conseguisse fazer isso com menos camadas.
A primeira camada reúne o que é possível saber a partir do token atual e o que já se sabia após o cálculo do token anterior. A segunda camada verifica se a regra de decisão é satisfeita e decide se o token atual parece código de programa. A terceira camada compara essa decisão com as decisões referentes aos tokens anteriores.
Acho que isso também poderia ser comprimido em uma única camada oculta. Como ReLU é suficiente para capturar a não linearidade, parece possível. Ainda não examinei o suficiente a correspondência com a estrutura Elman, então não sei responder.
RNNs foram completamente absorvidas pelos transformers? Fico me perguntando se dá para esquecer como lidar com RNNs e focar só em transformers
Ele mostra que uma definição específica e estreita de transformer — ou seja, transformers com mascaramento causal — é equivalente a RNNs, e vice-versa
De forma parecida, Mamba(https://arxiv.org/abs/2312.00752), uma arquitetura em alta hoje, também tem unidades equivalentes a RNNs com gates. Pelo que sei, por motivos de desempenho, usa uma CNN equivalente no treinamento e uma RNN na inferência
Na verdade, essa é a principal vantagem dos transformers. Relações de curta e longa distância não são mais difíceis nem mais fáceis. Mas, teoricamente, uma RNN pode lembrar um passado infinitamente distante
Trabalhei com machine learning/LLMs como engenheiro de pesquisa nos últimos 7 anos e também em um laboratório de pesquisa de uma FAANG, mas sempre só pensei que deveria aprender RNNs; nunca aprendi de fato nem precisei delas
Se isso parece interessante, vale dar uma olhada em programação genética. Vejo como uma abordagem mais simples para o mesmo problema, sem exigir matemática
Os programas são recombinados com base na árvore de sintaxe abstrata e, se você fornece alguma heurística, o programa é otimizado de acordo com esse critério. A mágica está na função heurística, e você pode escolher o que quiser otimizar: velocidade, tamanho do programa, minimização de estruturas complexas ou chamadas de função, eficiência de rede, ou alguma combinação disso
https://youtu.be/tTMpKrKkYXo
https://www.human-competitive.org/
Recentemente escrevi um post de blog explorando a ideia de fazer interface com LLMs locais para esse tipo de tarefa ambígua
Acho que isso faz mais sentido do que codificar diretamente uma rede neural. Parece mais prático usar algo como llama.cpp para avaliar se um modelo pequeno consegue resolver o problema como está; se não conseguir, fazer fine-tuning e então integrar programaticamente com o llama.cpp usando o wrapper desejado
Redes neurais recorrentes podem ser usadas para computação arbitrária, e a equivalência com máquinas de Turing também já foi provada. Mas são completamente impraticáveis para essa tarefa
A abordagem deste texto parece, de algum modo, uma máquina de estados aprendida. O texto se beneficiaria de um resumo mais longo, e “Python” não parece ter nenhuma relação real. Aprender a semântica real de Python seria bem difícil, dada a natureza da linguagem: não é que exista um padrão, a linguagem é aquilo que o CPython faz
Então por que ela não conseguiria lidar com linguagem natural formal, como comentários de code review?
Nesse caso, a inferência era executada com uma entrada aleatória para gerar “Shakespeare” aleatório, mas a estrutura e o estilo da linguagem ainda assim foram aprendidos pela RNN. Talvez também possa ser usada para classificação
Primeiro você compila Python para uma rede neural e depois encaixa isso, meio na marra, em uma rede neural baseada em transformers
Assim, uma Transformer Virtual Machine (TVM) consegue executar programas arbitrários
Usando transfer learning — isto é, sobrepondo pesos uns aos outros —, um LLM pode “nascer” com algoritmos profundamente codificados