Trabalhei com pesquisa toda a minha vida, então conheço um estereótipo de que pesquisadores escrevem códigos feios (por exemplo, veja aqui , aqui ou aqui ). Mas pensei: podemos consertar, certo? Então, várias vezes tentei projetar boas estruturas de pesquisa. Tentei trazer interfaces e criar boas abstrações usando livros de engenharia de software e blogs que gostava de ler.
Mas repetidamente todos esses esforços foram em vão. A maioria dos softwares de pesquisa em que trabalhei nunca foi para produção (embora alguns tenham entrado). Teria sido ótimo para minha saúde mental se alguém me contasse uma verdade simples: morrer código de pesquisa é na verdade o que deveria acontecer . Os pesquisadores não deveriam gastar muito tempo projetando-o em primeiro lugar.
Engenheiros de software profissionais sempre desprezam os pesquisadores que não usam as melhores práticas de software. Existem vários posts tentando elevar o nível do código de pesquisa (por exemplo, este excelente post e um manual de código de pesquisa ). Mas este post vai ao contrário: discute como não exagerar nas melhores práticas de software e, em vez disso, investir apenas na exploração rápida. É direcionado para empresas voltadas para pesquisa onde seu objetivo é testar muitas ideias rapidamente.
Um projeto de pesquisa bem-sucedido em uma empresa tem duas fases: exploração e aproveitamento. Na “exploração”, você deseja experimentar tantas soluções diversas quanto possível. Durante a “exploração” você tem que fortalecer a melhor solução e transformá-la em um produto útil.
As práticas ideais de software são bastante diferentes entre os dois. É por isso que as empresas costumam ter divisões separadas de pesquisa e de produtos. Todos os livros que você normalmente lê sobre design de software são principalmente sobre a segunda fase de “exploração”. Nesta fase, você está construindo as bases para um produto escalonável. É aqui que entram todos os padrões de design: APIs legais, registro, tratamento de erros e assim por diante.
Mas na primeira fase de “exploração” você não está construindo fundações que durarão para sempre. Na verdade, se a maioria dos seus esforços sobreviver, então você (por definição) não explorou o suficiente.
Muitas práticas neste post são exemplos do que normalmente se tornaria “dívida tecnológica”. É o que você ganha ao não escrever código limpo, reutilizável e bem abstraído. A dívida é sempre ruim? Preferimos nunca obter um empréstimo ou hipoteca, mas pedir dinheiro emprestado costuma ser uma boa estratégia na vida. Não há problema em se endividar para agir rapidamente e lucrar mais tarde.
Da mesma forma, ao não assumir dívidas técnicas, você pode atrasar sua pesquisa. A boa notícia é que na maioria das vezes você não precisa pagar. A maior parte do seu código de pesquisa provavelmente morrerá de qualquer maneira. Portanto, em média, você não sofrerá com toda a dívida tecnológica que contraiu.
Muitas arquiteturas de software e técnicas de refatoração são orientadas especificamente para melhorar a reutilização de código. Existem desvantagens genéricas na reutilização de código. Mas na produção eles são superados pelos benefícios bem conhecidos (por exemplo, veja este post típico ). Em projetos de pesquisa, a maior parte do código está destinada a cair no esquecimento. Esforçar-se pela reutilização de código pode, na verdade, atrasá-lo.
Limitar a reutilização de código é o tipo de dívida técnica que pode ser assumida em pesquisa. Há vários padrões de reutilização de código que quero discutir: adição de uma dependência desnecessária, cópia de código, manutenção de muitos códigos de pesquisa compartilhados, investimentos prematuros em design.
Se você conhece uma biblioteca com versões bem mantidas que irá acelerar você - vá em frente! Mas antes de assumir uma nova dependência, tente avaliar se vale a pena. Cada adicional o aproxima do inferno da dependência. Isso faz com que você invista tempo aprendendo e solucionando problemas. Veja mais armadilhas das dependências neste post conciso .
Provavelmente não há problema em depender de algo se:
Mas tenha cuidado com uma dependência se:
você não consegue descobrir como usá-lo rapidamente, é muito novo (ou muito antigo) ou ninguém parece saber sobre ele; não há documentos ou testes
é do seu monorepo e está sendo constantemente alterado por outras equipes
ele traz muitas outras dependências e ferramentas; ou é apenas difícil de instalar
e, finalmente, você sente que (ou algum LLM) pode escrever esse código em algumas horas.
Em vez de uma dependência explícita, você pode seguir um belo provérbio Go: “ um pouco de cópia é melhor do que um pouco de dependência ”, que é o nosso próximo tópico.
Alguns dizem que “ copiar e colar deveria ser ilegal ”. Mas, para minha surpresa, acabei argumentando a favor disso com bastante frequência. Copypaste pode ser a escolha ideal durante a fase de exploração.
Se você depende de uma função muito usada de outra parte da base de código, pode esquecer de alterá-la facilmente. É provável que você quebre algo para alguém e precise gastar um tempo precioso em revisões e correções de código. Mas se você copiar e colar o código necessário em sua pasta, poderá fazer o que quiser com ele. Isto é importante em projetos de pesquisa onde a experimentação é uma norma e não uma exceção. Especialmente se você não tiver certeza se as mudanças serão úteis para todos.
Acho que as bases de código de aprendizado profundo são mais adequadas para copiar e colar. Normalmente, a quantidade de código necessária para descrever um modelo e seu treinamento não é tão grande. Mas, ao mesmo tempo, pode ser muito matizado e difícil de generalizar. Scripts de treinamento compartilháveis tendem a crescer para um tamanho incontrolável: por exemplo, Hugging Face transformers
Trainer tem +4k linhas. Curiosamente, os transformadores optaram por copiar e colar no nível do modelo. Confira a postagem deles com o raciocínio por trás da política de "modelo de arquivo único". Veja mais recursos sobre a beleza de copiar e colar no final.
Uma alternativa ao copypaste é permanecer em uma filial. Mas sinto que isso traz muita sobrecarga no trabalho em equipe. Além disso, encontrei vários outros posts sobre a beleza do copypaste - veja mais posts na conclusão.
A manutenção de código compartilhado muito utilizado requer muito trabalho. Dê uma olhada no número torch.nn.Module
de linhas de arquivo plotadas em relação à versão Pytorch
. Você pode ver que mesmo as equipes de pesquisa mais avançadas lutam para manter a complexidade sob controle.
Não subestime o tempo e os recursos necessários para manter um grande código de pesquisa compartilhado. Quanto mais uma biblioteca de pesquisa é usada, mais complicada ela se torna. Isso acontece mais rápido do que em uma biblioteca típica porque cada direção de pesquisa tem um caso de uso ligeiramente diferente. Estabeleça regras muito rígidas sobre o que poderia ser contribuído de volta. Caso contrário, o código compartilhado se tornará frágil e repleto de opções, otimizações com erros e casos extremos. Como a maior parte do código de pesquisa desaparece, toda essa complexidade extra nunca mais será usada. Eliminar parte do seu código compartilhado liberará algum tempo para fazer pesquisas reais.
É verdade que você não deseja preparar muito seu código para o futuro, mesmo em produção. Tente implementar a solução mais simples possível que atenda aos requisitos. Mas no código de produção sempre há aspectos de manutenção a serem considerados. Por exemplo, tratamento de erros, velocidade, registro e modularização é o que normalmente você precisa pensar.
No código de pesquisa, nada disso importa. Você só quer provar rapidamente que uma ideia é boa ou ruim da maneira mais rápida possível e seguir em frente. Portanto, a simplicidade suja que se consegue sem quaisquer módulos ou APIs está totalmente ok!
Não perca tempo valioso com investimentos prematuros em software, como:
O objetivo de um projeto de pesquisa é encontrar uma nova solução. Ninguém sabe (por definição) como é. É semelhante a um processo de otimização em um cenário de pesquisa complicado e com informações limitadas. Para encontrar um bom mínimo, você precisa tentar vários caminhos, reconhecer caminhos bons e ruins e não ficar preso em mínimos locais. Para fazer tudo isso rapidamente, às vezes você precisa fazer investimentos em software em vez de contrair dívidas tecnológicas.
Existem vários caminhos de pesquisa diferentes que você deseja tentar. Existe um design, uma biblioteca ou uma otimização que economizaria tempo na maioria dos caminhos? Você deve ter cuidado para não projetar demais nada, porque nem sempre conhece todas as ideias que está prestes a experimentar. Isso é muito personalizado para cada projeto, mas aqui estão alguns exemplos:
Os pesquisadores devem ser capazes de iniciar novas ideias diversas rapidamente. Parece fácil no início do projeto. Mas depois torna-se gradualmente cada vez mais difícil à medida que as pessoas se enraízam nos seus caminhos de investigação favoritos. Para resolver isso, mudanças culturais e organizacionais são essenciais. Deveria haver um processo que interrompesse pesquisas não promissoras antes de investir muito dinheiro e emoção nelas. Dias regulares de demonstração e revisões técnicas por pares podem servir como estratégias eficazes para esse propósito. Também é importante encontrar um equilíbrio entre as pessoas que adotam uma ideia nova e brilhante e o encerramento adequado dos projetos atuais.
Mas esta é uma postagem sobre software, então aqui estão algumas práticas para facilitar a ramificação de novos projetos:
Código barulhento e com erros torna os resultados tão ambíguos e inconclusivos que todo o projeto será uma perda de tempo. Embora você não deva projetar demais as coisas, você pode facilmente seguir estas regras simples para evitar códigos confusos:
evite código com efeitos colaterais
o padrão é funções em vez de classes; e com classes, prefira encapsulamento versus herança
minimizar o comprimento de funções/classes/módulos; minimizar o número de instruções if
conheça bem python, mas use técnicas simples. Resista à tentação de entrar nas ervas daninhas intelectuais de metaclasses, decoradores e programação funcional.
É difícil trabalhar com software que produz resultados diferentes durante execuções diferentes. Se você tomou uma decisão importante, mas errada, com base em uma semente infeliz, perderá muito tempo se recuperando. Aqui estão algumas dicas ao lidar com software não determinístico:
A conclusão vem desta postagem sobre código de pesquisa:
“Você não se preocupa com [um bom design de software] porque o código não é o ponto. O código é uma ferramenta que dá a resposta que você precisa” .
É extremamente importante ter ótimas bases de codificação. Mas no final das contas, a exploração e o produto realmente útil é o que importa. Se você usa muito software de produção em pesquisa, perde o tempo necessário para descobrir algo novo. Em vez disso, descubra o que retarda o seu processo de exploração. Acelere os caminhos de pesquisa investindo em ramificações rápidas, tempo para resultados e código limpo e silencioso.
Seria uma loucura argumentar totalmente contra a reutilização de código. Quero apenas salientar que a reutilização de código deve ser uma atividade bem equilibrada. Na pesquisa, a proporção de código descartável é maior do que na produção. A balança inclina-se ainda mais contra a reutilização. Aqui estão mais algumas postagens excelentes com as armadilhas da reutilização de código:
E aqui estão mais algumas postagens defendendo práticas de copypasting:
Obrigado por ler! Acho que algumas partes são um pouco controversas, por favor me avise nos comentários!
Também aparece aqui .