O teste de unidade é seu amigo
Há um ditado entre os escritores que "Toda escrita é reescrita" - ou seja, a maior parte da escrita está sendo revisada. Para programadores (ou pelo menos cientistas de dados), a expressão poderia ser reformulada como "Toda a codificação está depurando".
Sempre que você estiver escrevendo um código, é necessário verificar se ele funciona como pretendido. O melhor método que eu já encontrei para verificar a correção é dividir seu código em pequenos segmentos e verificar se cada segmento funciona. Isso pode ser feito comparando a saída do segmento com o que você sabe ser a resposta correta. Isso é chamado de teste de unidade . Escrever bons testes de unidade é uma peça essencial para se tornar um bom estatístico / cientista de dados / especialista em aprendizado de máquina / praticante de redes neurais. Simplesmente não há substituto.
Você precisa verificar se seu código está livre de bugs antes de ajustar o desempenho da rede! Caso contrário, você poderá reorganizar as cadeiras de praia no RMS Titanic .
Existem duas características das redes neurais que tornam a verificação ainda mais importante do que para outros tipos de aprendizado de máquina ou modelos estatísticos.
As redes neurais não são algoritmos "prontos para uso" da mesma forma que a floresta aleatória ou a regressão logística. Mesmo para redes simples e de feed-forward, o ônus é principalmente do usuário tomar várias decisões sobre como a rede é configurada, conectada, inicializada e otimizada. Isso significa escrever código, e escrever código significa depuração.
Mesmo quando um código de rede neural é executado sem gerar uma exceção, a rede ainda pode ter erros! Esses bugs podem até ser do tipo insidioso para o qual a rede treinará, mas ficarem presos em uma solução subótima ou a rede resultante não possui a arquitetura desejada. ( Este é um exemplo da diferença entre um erro sintático e semântico .)
Esta publicação do Medium , " Como código de aprendizado de máquina para teste de unidade ", de Chase Roberts, discute o teste de unidade para modelos de aprendizado de máquina com mais detalhes. Peguei emprestado este exemplo de código de buggy do artigo:
def make_convnet(input_image):
net = slim.conv2d(input_image, 32, [11, 11], scope="conv1_11x11")
net = slim.conv2d(input_image, 64, [5, 5], scope="conv2_5x5")
net = slim.max_pool2d(net, [4, 4], stride=4, scope='pool1')
net = slim.conv2d(input_image, 64, [5, 5], scope="conv3_5x5")
net = slim.conv2d(input_image, 128, [3, 3], scope="conv4_3x3")
net = slim.max_pool2d(net, [2, 2], scope='pool2')
net = slim.conv2d(input_image, 128, [3, 3], scope="conv5_3x3")
net = slim.max_pool2d(net, [2, 2], scope='pool3')
net = slim.conv2d(input_image, 32, [1, 1], scope="conv6_1x1")
return net
Você vê o erro? Muitas das operações diferentes não são realmente usadas porque os resultados anteriores são substituídos por novas variáveis. O uso desse bloco de código em uma rede ainda treinará e os pesos serão atualizados e a perda poderá até diminuir - mas o código definitivamente não está fazendo o que foi planejado. (O autor também é inconsistente quanto ao uso de aspas simples ou duplas, mas isso é puramente estilístico.)
Os erros de programação mais comuns pertencentes às redes neurais são
- Variáveis são criadas, mas nunca usadas (geralmente devido a erros de copiar e colar);
- As expressões para atualizações de gradiente estão incorretas;
- As atualizações de peso não são aplicadas;
- As funções de perda não são medidas na escala correta (por exemplo, a perda de entropia cruzada pode ser expressa em termos de probabilidade ou logits)
- A perda não é apropriada para a tarefa (por exemplo, usando a perda de entropia cruzada categórica para uma tarefa de regressão).
Rastrear antes de andar; Caminhe antes de executar
Redes neurais amplas e profundas, e redes neurais com fiação exótica, são a coisa mais importante no momento em aprendizado de máquina. Mas essas redes não surgiram totalmente formadas; seus designers construíram para eles a partir de unidades menores. Primeiro, construa uma pequena rede com uma única camada oculta e verifique se ela funciona corretamente. Em seguida, adicione incrementalmente a complexidade adicional do modelo e verifique se cada um deles também funciona.
Muito poucos neurónios em uma camada pode restringir a representação que a rede aprende, causando sub-montagem. Muitos neurônios podem causar excesso de ajustes, porque a rede "memoriza" os dados do treinamento.
Mesmo que você possa provar que, matematicamente, apenas um pequeno número de neurônios é necessário para modelar um problema, geralmente ocorre que ter "mais alguns" neurônios facilita o otimizador a encontrar uma configuração "boa". (Mas não creio que alguém entenda completamente por que esse é o caso.) Fornecemos um exemplo disso no contexto do problema XOR aqui: Minhas iterações não são necessárias para treinar NN para XOR com MSE <0,001 muito alto? .
Escolher o número de camadas ocultas permite que a rede aprenda uma abstração a partir dos dados brutos. Atualmente, o aprendizado profundo está em alta, e redes com um grande número de camadas têm mostrado resultados impressionantes. Porém, a adição de muitas camadas ocultas pode aumentar o risco de adaptação ou dificultar a otimização da rede.
Escolher uma fiação de rede inteligente pode fazer muito trabalho para você. Sua fonte de dados é passível de arquiteturas de rede especializadas? As redes neurais convolucionais podem obter resultados impressionantes em fontes de dados "estruturadas", dados de imagem ou áudio. Redes neurais recorrentes podem se dar bem em tipos de dados seqüenciais, como linguagem natural ou dados de séries temporais. As conexões residuais podem melhorar as redes de alimentação avançada.
O treinamento em rede neural é como uma trava
Para alcançar resultados de última geração, ou mesmo meramente bons, você deve ter configurado todas as peças configuradas para funcionarem bem juntas . Definir uma configuração de rede neural que realmente aprende é como escolher uma trava: todas as peças precisam ser alinhadas da maneira certa. Assim como não é suficiente ter um único copo no lugar certo, também não é suficiente ter apenas a arquitetura ou apenas o otimizador configurados corretamente.
Ajustar as opções de configuração não é tão simples como dizer que um tipo de opção de configuração (por exemplo, taxa de aprendizado) é mais ou menos importante que outro (por exemplo, número de unidades), pois todas essas opções interagem com todas as outras opções, portanto, uma a escolha pode se dar bem em combinação com outra escolha feita em outro lugar .
Esta é uma lista não exaustiva das opções de configuração que também não são opções de regularização ou opções de otimização numérica.
Todos esses tópicos são áreas ativas de pesquisa.
A inicialização da rede geralmente é negligenciada como uma fonte de bugs na rede neural. A inicialização em um intervalo muito grande pode definir pesos iniciais muito grandes, o que significa que neurônios únicos têm uma influência enorme sobre o comportamento da rede.
A principal diferença entre uma rede neural e um modelo de regressão é que uma rede neural é uma composição de muitas funções não lineares, chamadas funções de ativação . (Veja: Qual é a diferença essencial entre rede neural e regressão linear )
Os resultados clássicos da rede neural focaram-se nas funções de ativação sigmoidal (funções logísticas ou ). Um resultado recente descobriu que as unidades ReLU (ou similares) tendem a funcionar melhor porque possuem gradientes mais íngremes, portanto as atualizações podem ser aplicadas rapidamente. (Veja: Por que usamos ReLU em redes neurais e como usá-lo? ) Um aviso sobre ReLUs é o fenômeno "neurônio morto", que pode impedir a aprendizagem; relus com vazamento e variantes semelhantes evitam esse problema. Vejotanh
Existem várias outras opções. Consulte: Lista abrangente de funções de ativação em redes neurais com prós / contras
As conexões residuais são um desenvolvimento puro que pode facilitar o treinamento de redes neurais. "Aprendizagem residual profunda para reconhecimento de imagem"
Kaiming He, Xiangyu Zhang, Shaoqing Ren, Jian Sun In: CVPR. (2016). Além disso, alterar a ordem das operações dentro do bloco residual pode melhorar ainda mais a rede resultante. " Mapeamentos de identidade em redes residuais profundas ", de Kaiming He, Xiangyu Zhang, Shaoqing Ren e Jian Sun.
A otimização não convexa é difícil
A função objetivo de uma rede neural é apenas convexa quando não há unidades ocultas, todas as ativações são lineares e a matriz de projeto é de classificação completa - porque essa configuração é identicamente um problema de regressão comum.
Em todos os outros casos, o problema de otimização é não convexo e a otimização não convexa é difícil. Os desafios do treinamento de redes neurais são bem conhecidos (veja: Por que é difícil treinar redes neurais profundas? ). Além disso, as redes neurais têm um número muito grande de parâmetros, o que nos restringe apenas a métodos de primeira ordem (consulte: Por que o método de Newton não é amplamente utilizado no aprendizado de máquina? ). Esta é uma área de pesquisa muito ativa.
Definir uma taxa de aprendizado muito alta fará com que a otimização seja divergente, porque você pulará de um lado do "canyon" para o outro. Definir isso muito pequeno impedirá que você faça progressos reais e possivelmente permita que o ruído inerente ao SGD sobrecarregue suas estimativas de gradiente.
O recorte de gradiente redimensiona a norma do gradiente se estiver acima de algum limite. Eu costumava pensar que esse era um parâmetro de esquecer e esquecer, normalmente em 1,0, mas descobri que poderia criar um modelo de linguagem LSTM muito melhor definindo-o como 0,25. Não sei por que é isso.
O agendamento da taxa de aprendizado pode diminuir a taxa de aprendizado ao longo do treinamento. Na minha experiência, tentar usar o agendamento é muito parecido com o regex : ele substitui um problema ("Como faço para aprender a continuar após uma certa época?") Por dois problemas ("Como faço para aprender a continuar após uma certa época ? "e" Como escolho uma boa programação? "). Outras pessoas insistem que a programação é essencial. Eu vou deixar você decidir.
A escolha de um bom tamanho de minibatch pode influenciar indiretamente o processo de aprendizado, pois um minilote maior tenderá a ter uma variação menor ( lei do número maior ) do que um minilote menor. Você deseja que o minilote seja grande o suficiente para ser informativo sobre a direção do gradiente, mas pequeno o suficiente para que o SGD possa regularizar sua rede.
Existem várias variantes na descida do gradiente estocástico que usam momento, taxas de aprendizado adaptável, atualizações de Nesterov e assim por diante para melhorar o SGD da baunilha. Projetar um melhor otimizador é uma área de pesquisa ativa. Alguns exemplos:
Quando foi lançado, o otimizador de Adam gerou muito interesse. Mas algumas pesquisas recentes descobriram que o SGD com impulso pode superar os métodos de gradiente adaptativo para redes neurais. " O valor marginal dos métodos adaptativos de gradiente no aprendizado de máquina ", de Ashia C. Wilson, Rebecca Roelofs, Mitchell Stern, Nathan Srebro, Benjamin Recht
Mas, por outro lado, este artigo muito recente propõe um novo otimizador de taxa de aprendizado adaptativo que supostamente fecha a lacuna entre métodos de taxa adaptativa e SGD com impulso. " Fechando a lacuna de generalização dos métodos de gradiente adaptativo no treinamento de redes neurais profundas " por Jinghui Chen, Quanquan Gu
Foram observados métodos de gradiente adaptativo, que adotam informações históricas do gradiente para ajustar automaticamente a taxa de aprendizado, generalizando pior que a descida do gradiente estocástico (SGD), com impulso no treinamento de redes neurais profundas. Isso deixa como fechar um problema em aberto na lacuna de generalização dos métodos de gradiente adaptativo. Neste trabalho, mostramos que métodos de gradiente adaptativo, como Adam, Amsgrad, às vezes são "super adaptados". Nós projetamos um novo algoritmo, chamado Método de Estimação de Momento Parcialmente Adaptável (Padam), que unifica Adam / Amsgrad com SGD para obter o melhor dos dois mundos. Experimentos em benchmarks padrão mostram que Padam pode manter uma taxa de convergência rápida como Adam / Amsgrad enquanto generaliza e SGD no treinamento de redes neurais profundas.
Normalização
A escala dos dados pode fazer uma grande diferença no treinamento.
Antes de apresentar os dados a uma rede neural, padronizar os dados para ter 0 média e variação de unidade ou ficar em um pequeno intervalo como pode melhorar o treinamento. Isso equivale a pré-condicionamento e remove o efeito que uma escolha em unidades tem nos pesos da rede. Por exemplo, o comprimento em milímetros e o comprimento em quilômetros representam o mesmo conceito, mas estão em escalas diferentes. Os detalhes exatos de como padronizar os dados dependem da aparência dos dados.[ - 0,5 , 0,5 ]
A normalização da camada pode melhorar o treinamento da rede, mantendo uma média e um desvio padrão para as ativações dos neurônios. Não se sabe por que isso ajuda no treinamento e continua sendo uma área ativa de pesquisa.
Regularização
A escolha e o ajuste da regularização de rede é uma parte essencial da construção de um modelo que generalize bem (ou seja, um modelo que não seja adequado aos dados de treinamento). No entanto, no momento em que sua rede está lutando para diminuir a perda nos dados de treinamento - quando a rede não está aprendendo - a regularização pode ocultar qual é o problema.
Quando minha rede não aprende, desativo toda a regularização e verifico se a rede não regularizada funciona corretamente. Depois, adiciono cada peça de regularização novamente e verifico se cada uma delas funciona ao longo do caminho.
Essa tática pode identificar onde alguma regularização pode estar mal definida. Alguns exemplos são
eu2 regularização (também conhecida como decaimento de peso) ou a regularização são muito grandes, portanto, os pesos não podem se mover.eu1 1
Duas partes da regularização estão em conflito. Por exemplo, é amplamente observado que a normalização e o abandono da camada são difíceis de usar juntos. Como qualquer um por si só é muito útil, entender como usar os dois é uma área ativa de pesquisa.
Mantenha um diário de experiências
Quando configuro uma rede neural, não codifico nenhuma configuração de parâmetro. Em vez disso, faço isso em um arquivo de configuração (por exemplo, JSON) que é lido e usado para preencher os detalhes de configuração de rede em tempo de execução. Eu mantenho todos esses arquivos de configuração. Se eu fizer alguma modificação de parâmetro, faço um novo arquivo de configuração. Por fim, anexo como comentários todas as perdas por época para treinamento e validação.
O motivo pelo qual sou tão obsessivo com a retenção de resultados antigos é que isso facilita muito voltar e revisar as experiências anteriores. Também protege contra repetir erroneamente o mesmo experimento sem saída. Psicologicamente, também permite que você olhe para trás e observe "Bem, o projeto pode não estar onde eu quero que esteja hoje, mas estou progredindo em comparação com o que estava há semanas atrás".k
Como exemplo, eu queria aprender sobre os modelos de linguagem LSTM, então decidi criar um bot no Twitter que escrevesse novos tweets em resposta a outros usuários do Twitter. Eu trabalhei nisso no meu tempo livre, entre a pós-graduação e meu trabalho. Demorou cerca de um ano, e iteramos mais de 150 modelos diferentes antes de chegar a um modelo que fizesse o que eu queria: gerar novo texto em inglês que (meio que) faça sentido. (Um ponto chave importante, e parte do motivo de tantas tentativas, é que não foi suficiente obter apenas uma baixa perda fora da amostra, pois os primeiros modelos de baixa perda conseguiram memorizar os dados de treinamento, por isso, estava apenas reproduzindo blocos de texto em texto literalmente em resposta às solicitações - foram necessários alguns ajustes para tornar o modelo mais espontâneo e ainda ter baixa perda.)