Вы находитесь на странице: 1из 442

Aprendizado Profundo com Python

François Chollet
direito autoral
Para informações on-line e pedidos deste e de outros livros da Manning,
visite www.manning.com . O editor oferece descontos neste livro quando solicitado em
quantidade. Para mais informações por favor entre em contato

Departamento de Vendas Especiais

Manning Publications Co.

20 Baldwin Road

Caixa postal 761

Ilha de abrigo, NY 11964

Email: orders@manning.com

© 2018 Manning Publications Co. Todos os direitos reservados.

Nenhuma parte desta publicação pode ser reproduzida, armazenada em um sistema de


recuperação ou transmitida, de qualquer forma ou meio eletrônico, mecânico, fotocópia ou
outro, sem permissão prévia por escrito do editor.

Muitas das designações usadas pelos fabricantes e vendedores para distinguir seus produtos são
reivindicadas como marcas registradas. Quando essas designações aparecem no livro e a
Manning Publications estava ciente de uma reivindicação de marca comercial, as designações
foram impressas em maiúsculas iniciais ou em maiúsculas.

Reconhecendo a importância de preservar o que foi escrito, é política de Manning ter os


livros que publicamos impressos em papel sem ácido, e fazemos nossos melhores esforços para
esse fim. Reconhecendo também nossa responsabilidade de conservar os recursos de nosso
planeta, os livros Manning são impressos em papel que é pelo menos 15% reciclado e processado
sem o uso de cloro elementar.

Manning Publications Co.

20 Baldwin Road

Caixa postal 761

Ilha de abrigo, NY 11964

Editor de desenvolvimento: Toni Arritola

Editor de desenvolvimento técnico: Jerry Gaines

Editor de revisões: Aleksandar Dragosavljević

Editor de projetos: Tiffany Taylor

Copyeditor: Tiffany Taylor

Revisor: Katie Tennant

Revisores técnicos: Alex Ott e Richard Tobias


Tipógrafo: Dottie Marsico

Cover designer: Marija Tudor

ISBN 9781617294433

Impresso nos Estados Unidos da América

1 2 3 4 5 6 7 8 9 10 - EBM - 22 21 20 19 18 17

Índice Breve
direito autoral

Índice Breve

Índice

Prefácio

Agradecimentos

Sobre este livro

Sobre o autor

Sobre a capa

1. Fundamentos da Aprendizagem Profunda

Capítulo 1. O que é aprendizado profundo?

Capítulo 2. Antes de começar: os blocos de construção matemáticos das redes neurais

Capítulo 3. Iniciando com Redes Neurais

Capítulo 4. Fundamentos do aprendizado de máquina

2. Aprendizagem profunda na prática

Capítulo 5. Aprendizagem profunda para visão computacional

Capítulo 6. Aprendizado Profundo para Texto e Sequências

Capítulo 7. Práticas recomendadas de aprendizado avançado

Capítulo 8. Aprendizagem Profunda Generativa

Capítulo 9. Conclusões

Apêndice A. Instalando o Keras e suas dependências no Ubuntu

Apêndice B. Executando Notebooks Jupyter em uma Instância de GPU do EC2

Índice
Lista de Figuras

Lista de mesas

Lista de Listagens

Prefácio
Se você pegou este livro, provavelmente está ciente do progresso extraordinário que o
aprendizado profundo representou para o campo da inteligência artificial no passado
recente. Em apenas cinco anos, passamos de reconhecimento de imagem e transcrição de fala
quase inutilizáveis a desempenho sobre-humano nessas tarefas.

As conseqüências desse progresso súbito se estendem a quase todos os setores. Mas, para
começar a implantar a tecnologia de aprendizagem profunda em todos os problemas que ela
poderia resolver, precisamos torná-la acessível ao maior número de pessoas possível, incluindo
não especialistas - pessoas que não são pesquisadores nem estudantes de pós-graduação. Para
que o aprendizado profundo atinja todo o seu potencial, precisamos democratizá-lo
radicalmente.

Quando lancei a primeira versão da estrutura de aprendizagem profunda de Keras em março de


2015, a democratização da IA não era o que eu tinha em mente. Eu vinha fazendo pesquisa em
aprendizado de máquina há vários anos e construí Keras para me ajudar com meus próprios
experimentos. Mas ao longo de 2015 e 2016, dezenas de milhares de novas pessoas entraram no
campo da aprendizagem profunda; muitos deles pegaram Keras porque era - e ainda é - o
framework mais fácil de se começar. Enquanto observava dezenas de recém-chegados usando
Keras de maneiras inesperadas e poderosas, passei a me importar profundamente com a
acessibilidade e a democratização da IA. Percebi que quanto mais divulgamos essas tecnologias,
mais úteis e valiosas elas se tornam. A acessibilidade rapidamente se tornou um objetivo
explícito no desenvolvimento de Keras, e em poucos anos, a comunidade de desenvolvedores
Keras fez conquistas fantásticas nessa frente. Colocamos o aprendizado profundo nas mãos de
dezenas de milhares de pessoas, que, por sua vez, o estão usando para resolver problemas
importantes que nem sabíamos que existiam até recentemente.

O livro que você está segurando é outro passo no caminho para tornar o aprendizado profundo
disponível para o maior número de pessoas possível. Keras sempre precisou de um curso
complementar para abordar simultaneamente os fundamentos da aprendizagem profunda, os
padrões de uso do Keras e as melhores práticas de aprendizado profundo. Este livro é o meu
melhor esforço para produzir esse curso. Escrevi isso com foco em tornar os conceitos por trás
do aprendizado profundo e sua implementação tão acessíveis quanto possível. Fazer isso não
exigiu que eu emburrecesse nada - acredito fortemente que não há idéias difíceis no aprendizado
profundo. Espero que você ache este livro valioso e que lhe permita começar a criar aplicativos
inteligentes e resolver os problemas que são importantes para você.

Agradecimentos
Gostaria de agradecer à comunidade de Keras por tornar este livro possível. A Keras cresceu
para ter centenas de colaboradores de código aberto e mais de 200.000 usuários. Suas
contribuições e comentários transformaram Keras no que é hoje.

Eu também gostaria de agradecer ao Google por apoiar o projeto Keras. Foi fantástico ver o
Keras adotado como API de alto nível do TensorFlow. Uma integração suave entre o Keras e o
TensorFlow beneficia muito os usuários do TensorFlow e os usuários do Keras e torna a
aprendizagem profunda acessível à maioria.
Quero agradecer às pessoas da Manning que tornaram este livro possível: a editora Marjan Bace
e todos os integrantes das equipes de produção e editoriais, incluindo Christina Taylor, Janet
Vail, Tiffany Taylor, Katie Tennant, Dottie Marsico e muitos outros que trabalharam nos
bastidores. .

Muito obrigado aos revisores técnicos chefiados por Aleksandar Dragosavljević - Diego Acuña
Rozas, Geoff Barto, David Blumenthal-Barby, Abel Brown, Clark Dorman, Clark Gaylord,
Thomas Heiman, Wilson Mar, Sumit Pal, Vladimir Pasman, Gustavo Patino, Peter Rabinovitch,
Alvin Raj, Claudio Rodriguez, Srdjan Santic, Richard Tobias, Martin Verzilli, William E.
Wheeler e Daniel Williams - e os colaboradores do fórum. Suas contribuições incluíam erros
técnicos, erros na terminologia, erros de digitação e sugestões de tópicos. Cada um passou pelo
processo de revisão e cada feedback implementado pelos tópicos do fórum moldou e moldou o
manuscrito.

No lado técnico, um agradecimento especial a Jerry Gaines, que serviu como editor técnico do
livro; e Alex Ott e Richard Tobias, que serviram como revisores técnicos do livro. Eles são os
melhores editores técnicos que eu poderia ter esperado.

Finalmente, gostaria de expressar minha gratidão à minha esposa Maria por ser extremamente
favorável ao desenvolvimento de Keras e à escrita deste livro.

Sobre este livro


Este livro foi escrito para quem deseja explorar a aprendizagem profunda a partir do zero ou
ampliar sua compreensão da aprendizagem profunda. Seja você um engenheiro praticante de
aprendizado de máquina, um desenvolvedor de software ou um estudante universitário, você
encontrará valor nessas páginas.

Este livro oferece uma exploração prática e prática de aprendizado profundo. Evita a notação
matemática, preferindo explicar conceitos quantitativos através de trechos de código e construir
uma intuição prática sobre as idéias centrais de aprendizado de máquina e aprendizado
profundo.

Você aprenderá com mais de 30 exemplos de código que incluem comentários detalhados,
recomendações práticas e explicações simples de alto nível de tudo o que você precisa saber para
começar a usar o aprendizado profundo para resolver problemas concretos.

Os exemplos de código usam o framework de aprendizado profundo Python Keras, com o


TensorFlow como um mecanismo backend. Keras, uma das estruturas de aprendizagem
profunda mais populares e de crescimento mais rápido, é amplamente recomendada como a
melhor ferramenta para começar a aprender profundamente.

Depois de ler este livro, você terá uma sólida compreensão do que é o aprendizado profundo,
quando é aplicável e quais são suas limitações. Você estará familiarizado com o fluxo de trabalho
padrão para abordar e resolver problemas de aprendizado de máquina, e saberá como resolver
problemas comumente encontrados. Você poderá usar o Keras para resolver problemas reais
que vão desde a visão do computador até o processamento de linguagem natural: classificação
de imagens, previsão de timeseries, análise de sentimentos, geração de imagens e textos e muito
mais.

QUEM DEVERIA LER ESSE LIVRO

Este livro foi escrito para pessoas com experiência em programação em Python que desejam
iniciar aprendizado de máquina e aprendizado profundo. Mas este livro também pode ser
valioso para muitos tipos diferentes de leitores:
 Se você é um cientista de dados familiarizado com o aprendizado de máquina, este livro
fornecerá uma introdução sólida e prática ao aprendizado profundo, o subcampo de
aprendizado de máquina que mais cresce e é mais significativo.
 Se você é um especialista em aprendizado profundo que está procurando começar com o
framework Keras, você verá que este livro é o melhor curso de Keras disponível.
 Se você é um estudante de pós-graduação que estuda aprendizagem profunda em um
ambiente formal, você verá que este livro é um complemento prático à sua educação,
ajudando você a criar intuição em torno do comportamento de redes neurais profundas
e a familiarizá-lo com as melhores práticas.

Mesmo pessoas tecnicamente conscientes que não codificam regularmente acharão este livro
útil como uma introdução aos conceitos básicos e avançados de aprendizagem profunda.

Para usar Keras, você precisará de uma proficiência em Python razoável. Além disso, a
familiaridade com a biblioteca Numpy será útil, embora não seja necessária. Você não precisa de
experiência anterior com aprendizado de máquina ou aprendizado profundo: este livro cobre
desde o início todos os fundamentos necessários. Você não precisa de um histórico de
matemática avançado - a matemática do ensino médio deve ser suficiente para seguir adiante.

ROTEIRO

Este livro está estruturado em duas partes. Se você não tem experiência anterior com
aprendizado de máquina, eu recomendo fortemente que você complete a parte 1 antes de
abordar a parte 2 . Começaremos com exemplos simples e, à medida que o livro avança, nos
aproximamos cada vez mais das técnicas mais avançadas.

A Parte 1 é uma introdução de alto nível à aprendizagem profunda, fornecendo contexto e


definições e explicando todas as noções necessárias para iniciar o aprendizado de máquina e
redes neurais:

 O Capítulo 1 apresenta um contexto essencial e um conhecimento básico sobre IA,


aprendizado de máquina e aprendizado profundo.
 O Capítulo 2 introduz conceitos fundamentais necessários para abordar a aprendizagem
profunda: tensores, operações de tensor, descida de gradiente e retropropagação. Este
capítulo também apresenta o primeiro exemplo do livro de uma rede neural funcional.
 O Capítulo 3 inclui tudo o que você precisa para começar com as redes neurais: uma
introdução ao Keras, nossa estrutura de aprendizagem profunda de escolha; um guia
para configurar sua estação de trabalho; e três exemplos de código fundacional com
explicações detalhadas. No final deste capítulo, você será capaz de treinar redes neurais
simples para lidar com tarefas de classificação e regressão, e você terá uma ideia sólida
do que está acontecendo em segundo plano ao treiná-las.
 O Capítulo 4 explora o fluxo de trabalho canônico de aprendizado de máquina. Você
também aprenderá sobre armadilhas comuns e suas soluções.

A parte 2 faz um mergulho profundo em aplicações práticas de aprendizagem profunda em visão


computacional e processamento de linguagem natural. Muitos dos exemplos introduzidos nesta
parte podem ser usados como modelos para resolver problemas que você encontrará na prática
real da aprendizagem profunda:

 O Capítulo 5 examina uma série de exemplos práticos de visão computacional, com foco
na classificação de imagens.
 O Capítulo 6 oferece prática com técnicas para processamento de dados de sequência,
como texto e timeseries.
 O Capítulo 7 introduz técnicas avançadas para a construção de modelos de aprendizagem
profunda de última geração.
 O capítulo 8 explica modelos gerativos: modelos de aprendizagem profunda capazes de
criar imagens e texto, com resultados surpreendentemente artísticos.
 O Capítulo 9 é dedicado a consolidar o que você aprendeu ao longo do livro, além de
abrir perspectivas sobre as limitações da aprendizagem profunda e explorar seu
provável futuro.

REQUISITOS DE SOFTWARE / HARDWARE

Todos os exemplos de código deste livro usam o framework de aprendizagem profunda Keras
( https://keras.io ), que é de código aberto e gratuito para download. Você precisará de acesso a
uma máquina UNIX; É possível usar o Windows também, mas não recomendo. O Apêndice
A orienta você pela configuração completa.

Também recomendo que você tenha uma GPU NVIDIA recente em sua máquina, como um
TITAN X. Isso não é necessário, mas tornará sua experiência melhor, permitindo que você
execute os exemplos de código várias vezes mais rápido. Consulte a seção 3.3 para obter mais
informações sobre a configuração de uma estação de trabalho de aprendizagem profunda.

Se você não tiver acesso a uma estação de trabalho local com uma GPU NVIDIA recente, poderá
usar um ambiente de nuvem. Em particular, você pode usar as instâncias do Google Cloud
(como uma instância n1-padrão-8 com um complemento NVIDIA Tesla K80) ou instâncias de
GPU do Amazon Web Services (AWS) (como uma instância p2.xlarge). O Apêndice B apresenta
em detalhes um possível fluxo de trabalho em nuvem que executa uma instância do AWS via
notebooks Jupyter, acessível em seu navegador.

CÓDIGO FONTE

Todos os exemplos de código neste livro estão disponíveis para download como notebooks
Jupyter no site do livro, www.manning.com/books/deep-learning-with-python , e no GitHub
em https://github.com/fchollet/deep- aprendizagem-com-python-notebooks .

FÓRUM DO LIVRO

A compra do Deep Learning with Python inclui acesso gratuito a um fórum privado da Manning
Publications, no qual você pode fazer comentários sobre o livro, fazer perguntas técnicas e
receber ajuda do autor e de outros usuários. Para acessar o fórum,
acesse https://forums.manning.com/forums/deep-learning-with-python . Você também pode aprender
mais sobre os fóruns de Manning e as regras de conduta
em https://forums.manning.com/forums/about .

O compromisso de Manning com nossos leitores é proporcionar um local onde um diálogo


significativo entre os leitores individuais e entre os leitores e o autor possa ocorrer. Não é um
compromisso com qualquer quantidade específica de participação por parte do autor, cuja
contribuição para o fórum permanece voluntária (e não remunerada). Sugerimos que você tente
fazer-lhe algumas perguntas desafiadoras para que seu interesse não seja divulgado! O fórum e
os arquivos das discussões anteriores estarão acessíveis no site do editor, contanto que o livro
esteja em impressão.
Sobre o autor

François Chollet trabalha em deep learning no Google em Mountain View, CA. Ele é o criador da
biblioteca de aprendizado profundo de Keras, além de colaborador da estrutura de aprendizado
de máquina do TensorFlow. Ele também faz pesquisas de aprendizado profundo, com foco na
visão computacional e na aplicação do aprendizado de máquina ao raciocínio formal. Seus
trabalhos foram publicados nas principais conferências da área, incluindo a Conferência sobre
Visão Computacional e Reconhecimento de Padrões (CVPR), a Conferência e Workshop sobre
Sistemas de Processamento de Informações Neurais (NIPS), a Conferência Internacional sobre
Representações de Aprendizagem (ICLR) e outros. .

Sobre a capa
A figura na capa do Deep Learning with Python é legendada em “Hábito da Dama Persa em
1568”. A ilustração foi tirada de Thomas Jefferys, “ Uma Coleção dos Vestidos de Diferentes
Nações, Antigas e Modernas (quatro volumes), Londres, publicada entre 1757 e 1772. A página
de rosto declara que são gravuras de placas de cobre coloridas à mão, aumentadas com goma
arábica.

Thomas Jefferys (1719–1771) foi chamado de “Geógrafo do Rei George III”. Ele era um
cartógrafo inglês que era o principal fornecedor de mapas de sua época. Ele gravou e imprimiu
mapas para o governo e outros órgãos oficiais e produziu uma ampla gama de mapas e atlas
comerciais, especialmente da América do Norte. Seu trabalho como cartógrafo despertou o
interesse pela alfândega local das terras que ele pesquisou e mapeou, que estão brilhantemente
expostas nesta coleção. O fascínio com terras distantes e viagens a lazer eram fenômenos
relativamente novos no final do século XVIII, e coleções como essa eram populares,
introduzindo tanto o turista quanto o viajante de poltrona aos habitantes de outros países.

A diversidade dos desenhos nos volumes de Jefferys fala vividamente da singularidade e


individualidade das nações do mundo há cerca de 200 anos. Os códigos de vestimenta mudaram
desde então, e a diversidade por região e país, tão rica na época, desapareceu. Agora é difícil
dizer aos habitantes de um continente de outro. Talvez, tentando enxergar isso de maneira
otimista, trocamos uma diversidade cultural e visual por uma vida pessoal mais variada - ou
uma vida intelectual e técnica mais variada e interessante.

Numa época em que é difícil distinguir um livro de computador de outro, Manning celebra a
inventividade e a iniciativa do negócio de computadores com capas de livros baseadas na rica
diversidade da vida regional de dois séculos atrás, trazida de volta à vida pelas fotos de Jefferys.

Parte 1. Fundamentos da Aprendizagem Profunda


Os capítulos 1 - 4 deste livro lhe dará uma compreensão fundamental do que o aprendizado
profundo é, o que pode conseguir, e como ele funciona. Ele também o familiarizará com o fluxo
de trabalho canônico para resolver problemas de dados usando aprendizado profundo. Se você
ainda não é altamente conhecedor de aprendizagem profunda, você deve definitivamente
começar lendo a parte 1na íntegra antes de passar para as aplicações práticas na parte 2 .

Capítulo 1. O que é aprendizado profundo?


Este capítulo cobre

 Definições de alto nível de conceitos fundamentais


 Linha do tempo do desenvolvimento de aprendizado de máquina
 Fatores-chave por trás da crescente popularidade da aprendizagem profunda e do
potencial futuro

Nos últimos anos, a inteligência artificial (IA) tem sido objeto de intensa agitação da
mídia. Aprendizado de máquina, aprendizado profundo e IA surgem em inúmeros artigos,
muitas vezes fora das publicações voltadas para a tecnologia. É prometido um futuro de chatbots
inteligentes, carros autônomos e assistentes virtuais - um futuro às vezes pintado em uma luz
sombria e outras vezes como utópico, onde os trabalhos humanos serão escassos e a maior parte
da atividade econômica será manipulada por robôs ou AI Agentes Para um praticante futuro ou
atual de aprendizado de máquina, é importante ser capaz de reconhecer o sinal no ruído, de
modo que você possa identificar desenvolvimentos que mudam o mundo a partir de
comunicados de imprensa exagerados. Nosso futuro está em jogo, e é um futuro em que você
tem um papel ativo a desempenhar: depois de ler este livro, você será um daqueles que
desenvolvem os agentes de IA. Então, vamos abordar estas questões: O que o aprendizado
profundo alcançou até agora? Quão significativo é isso? Para onde estamos indo em
seguida? Você deve acreditar no hype?

Este capítulo fornece um contexto essencial sobre inteligência artificial, aprendizado de


máquina e aprendizado profundo.

1.1. INTELIGÊNCIA ARTIFICIAL, APRENDIZADO DE MÁQUINA E


APRENDIZADO PROFUNDO

Primeiro, precisamos definir claramente o que estamos falando quando mencionamos a IA. O
que são inteligência artificial, aprendizado de máquina e aprendizado profundo (veja a figura
1.1 )? Como eles se relacionam?

Figura 1.1. Inteligência artificial, aprendizado de máquina e aprendizado profundo

1.1.1. Inteligência artificial


A inteligência artificial nasceu na década de 1950, quando um punhado de pioneiros do campo
incipiente da ciência da computação começou a perguntar se os computadores poderiam
"pensar" - uma questão cujas ramificações ainda estamos explorando hoje. Uma definição
concisa do campo seria a seguinte: o esforço para automatizar tarefas intelectuais
normalmente executadas por humanos.. Como tal, a IA é um campo geral que engloba
aprendizado de máquina e aprendizado profundo, mas que também inclui muitas outras
abordagens que não envolvem nenhum aprendizado. Os primeiros programas de xadrez, por
exemplo, envolviam apenas regras codificadas elaboradas por programadores e não se
qualificavam como aprendizado de máquina. Por um tempo bastante longo, muitos especialistas
acreditavam que a inteligência artificial em nível humano poderia ser alcançada fazendo com
que os programadores elaborassem um conjunto suficientemente grande de regras explícitas
para manipular o conhecimento. Essa abordagem é conhecida como AI simbólica e foi o
paradigma dominante na IA dos anos 1950 até o final dos anos 80. Atingiu seu pico de
popularidade durante o boom dos sistemas especialistas dos anos 80.

Embora a IA simbólica tenha se mostrado adequada para resolver problemas lógicos bem
definidos, como jogar xadrez, tornou-se intratável descobrir regras explícitas para resolver
problemas mais complexos e difusos, como classificação de imagem, reconhecimento de fala e
tradução de linguagem. Uma nova abordagem surgiu para tomar o lugar da IA
simbólica: aprendizado de máquina .

1.1.2. Aprendizado de máquina


Na Inglaterra vitoriana, Lady Ada Lovelace era amiga e colaboradora de Charles Babbage, o
inventor do Mecanismo Analítico : o primeiro computador mecânico de propósito
geral. Embora visionária e muito à frente de seu tempo, a AnalyticalO motor não foi concebido
como um computador de propósito geral quando foi projetado nas décadas de 1830 e 1840,
porque o conceito de computação de propósito geral ainda estava para ser inventado. Significava
apenas como uma maneira de usar operações mecânicas para automatizar certos cálculos do
campo da análise matemática - daí o nome Mecanismo Analítico. Em 1843, Ada Lovelace
observou a invenção: “A máquina analítica não tem pretensões de originar nada. Pode fazer o
que quisermos para ordenar que realize ... Sua província é nos ajudar a disponibilizar aquilo
com que já estamos familiarizados. ”

Esta observação foi mais tarde citada pelo pioneiro da IA Alan Turing como “objeção de Lady
Lovelace” em seu importante trabalho de 1950, “Computing Machinery and Intelligence” [ 1 ], que
introduziu o teste de Turing e os principais conceitos que viriam a formar a IA. Turing estava
citando Ada Lovelace enquanto ponderava se os computadores de uso geral poderiam ser
capazes de aprender e originalidade, e chegou à conclusão de que eles poderiam.

AM Turing, "Computing Machinery and Intelligence", mente 59, não. 236 (1950): 433-460.

O aprendizado de máquina surge dessa questão: um computador poderia ir além de “o que


sabemos como fazer para executar” e aprender sozinho como realizar uma tarefa específica? Um
computador poderia nos surpreender? Em vez de os programadores elaborarem manualmente
as regras de processamento de dados, um computador poderia aprender automaticamente essas
regras observando os dados?

Esta questão abre a porta para um novo paradigma de programação. Na programação clássica, o
paradigma da IA simbólica, as regras de entrada de seres humanos (um programa) e os dados a
serem processados de acordo com essas regras, e as respostas de saída (ver figura 1.2 ). Com o
aprendizado de máquina, os dados de entrada dos seres humanos, bem como as respostas
esperadas dos dados, e fora as regras. Essas regras podem ser aplicadas a novos dados para
produzir respostas originais.
Figura 1.2 Aprendizado de máquina: um novo paradigma de programação

Um sistema de aprendizado de máquina é treinado em vez de programado explicitamente. Ele é


apresentado com muitos exemplos relevantes para uma tarefa, e encontra estrutura estatística
nesses exemplos que, eventualmente, permite que o sistema crie regras para automatizar a
tarefa. Por exemplo, se você quisesse automatizar a tarefa de marcar suas fotos de férias,
poderia apresentar um sistema de aprendizado de máquina com muitos exemplos de imagens já
marcadas por humanos, e o sistema aprenderia regras estatísticas para associar imagens
específicas a marcas específicas.

Embora o aprendizado de máquina tenha começado a florescer apenas nos anos 90, ele se
tornou rapidamente o subcampo de IA mais popular e mais bem-sucedido, uma tendência
impulsionada pela disponibilidade de hardware mais rápido e conjuntos de dados maiores. O
aprendizado de máquina está estreitamente relacionado à estatística matemática, mas difere das
estatísticas de várias maneiras importantes. Ao contrário das estatísticas, o aprendizado de
máquina tende a lidar com conjuntos de dados grandes e complexos (como um conjunto de
dados de milhões de imagens, cada um consistindo em dezenas de milhares de pixels) para os
quais análises estatísticas clássicas, como análises Bayesianas, seriam impraticáveis. Como
resultado, o aprendizado de máquina, e especialmente o aprendizado profundo, exibe
relativamente pouca teoria matemática - talvez muito pouco - e é orientado para a engenharia.

1.1.3. Aprendendo representações de dados


Para definir a aprendizagem profunda e entender a diferença entre aprendizado profundo e
outras abordagens de aprendizado de máquina, primeiro precisamos de uma ideia do que
algoritmos de aprendizado de máquina fazem . Apenas afirmei que o aprendizado de máquina
descobre regras para executar uma tarefa de processamento de dados, dados exemplos do que é
esperado. Então, para fazer aprendizado de máquina, precisamos de três coisas:

 Pontos de dados de entrada - Por exemplo, se a tarefa for reconhecimento de fala,


esses pontos de dados podem ser arquivos de som de pessoas falando. Se a tarefa for a
marcação de imagens, elas podem ser imagens.
 Exemplos da saída esperada - Em uma tarefa de reconhecimento de voz, essas
podem ser transcrições geradas por humanos de arquivos de som. Em uma tarefa de
imagem, as saídas esperadas podem ser tags como "cachorro", "gato" e assim por diante.
 Uma maneira de medir se o algoritmo está fazendo um bom trabalho - isso
é necessário para determinar a distância entre a saída atual do algoritmo e sua saída
esperada. A medição é usada como um sinal de feedback para ajustar a maneira como o
algoritmo funciona. Essa etapa de ajuste é o que chamamos de aprendizado .

Um modelo de aprendizado de máquina transforma seus dados de entrada em saídas


significativas, um processo que é “aprendido” pela exposição a exemplos conhecidos de entradas
e saídas. Portanto, o problema central no aprendizado de máquina e no aprendizado profundo
é transformar significativamente os dados: em outras palavras,
aprender representações úteis dos dados de entrada à mão - representações que nos aproximam
do resultado esperado. Antes de irmos adiante: o que é uma representação? Em essência, é uma
maneira diferente de analisar dados - para representar ou codificar dados. Por exemplo, uma
imagem colorida pode ser codificada no formato RGB (vermelho-verde-azul) ou no formato
HSV (valor de saturação-matiz): estas são duas representações diferentes dos mesmos
dados. Algumas tarefas que podem ser difíceis com uma representação podem tornar-se fáceis
com outra. Por exemplo, a tarefa “selecionar todos os pixels vermelhos na imagem” é mais
simples no formato RGB, enquanto “tornar a imagem menos saturada” é mais simples no
formato HSV. Os modelos de aprendizado de máquina têm tudo a ver com encontrar
representações apropriadas para seus dados de entrada - transformações dos dados que o
tornam mais acessível à tarefa em questão, como uma tarefa de classificação.

Vamos tornar isso concreto. Considere um eixo x, um eixo y e alguns pontos representados por
suas coordenadas no sistema (x, y), como mostrado na figura 1.3 .

Figura 1.3. Alguns dados de amostra

Como você pode ver, temos alguns pontos brancos e alguns pontos pretos. Digamos que
queremos desenvolver um algoritmo que possa pegar as coordenadas (x, y) de um ponto e
produzir se o ponto provavelmente é preto ou branco. Nesse caso,

 As entradas são as coordenadas dos nossos pontos.


 Os resultados esperados são as cores dos nossos pontos.
 Uma maneira de medir se nosso algoritmo está fazendo um bom trabalho pode ser, por
exemplo, a porcentagem de pontos que estão sendo classificados corretamente.

O que precisamos aqui é de uma nova representação de nossos dados que separa de maneira
clara os pontos brancos dos pontos pretos. Uma transformação que poderíamos usar, entre
muitas outras possibilidades, seria uma mudança coordenada, ilustrada na figura 1.4 .

Figura 1.4 Coordenar mudança

Neste novo sistema de coordenadas, pode-se dizer que as coordenadas de nossos pontos são
uma nova representação de nossos dados. E é bom! Com essa representação, o problema de
classificação preto / branco pode ser expresso como uma regra simples: “Pontos pretos são tais
que x> 0” ou “Pontos brancos são tais que x <0.” Essa nova representação basicamente resolve o
problema de classificação.
Neste caso, definimos a mudança de coordenadas manualmente. Mas se, em vez disso,
tentássemos procurar sistematicamente por diferentes possíveis mudanças de coordenadas e
usássemos como feedback a porcentagem de pontos sendo classificados corretamente, então
estaríamos fazendo aprendizado de máquina. Aprendizagem , no contexto da aprendizagem de
máquina, descreve um processo de pesquisa automática para melhores representações.

Todos os algoritmos de aprendizado de máquina consistem em encontrar automaticamente


essas transformações que transformam dados em representações mais úteis para uma
determinada tarefa. Essas operações podem ser mudanças de coordenadas, como você acabou
de ver, ou projeções lineares (que podem destruir informações), traduções, operações não
lineares (como “selecionar todos os pontos como x> 0”), e assim por diante. Algoritmos de
aprendizado de máquina geralmente não são criativos encontrar essas transformações; eles
estão apenas procurando através de um conjunto pré-definido de operações, chamado de espaço
de hipótese.

Então, é isso que o aprendizado de máquina é tecnicamente: procurar por representações úteis
de alguns dados de entrada, dentro de um espaço predefinido de possibilidades, usando a
orientação de um sinal de feedback. Essa ideia simples permite resolver uma gama
extremamente ampla de tarefas intelectuais, do reconhecimento de fala à condução autônoma
de automóveis.

Agora que você entende o que entendemos por aprendizado , vamos dar uma olhada no que
torna o aprendizado profundo(Deep Learning) especial.

1.1.4. O “profundo” no aprendizado profundo


A aprendizagem profunda (Deep Learning) é um subcampo específico de aprendizado de
máquina: uma nova abordagem das representações de aprendizado a partir de dados que
enfatizam o aprendizado de camadas sucessivas de representações cada vez mais
significativas. A profunda na aprendizagem profunda não é uma referência a qualquer tipo de
entendimento mais profundo alcançado pela abordagem; em vez disso, significa essa ideia de
camadas sucessivas de representações. Quantas camadas contribuem para um modelo dos
dados é chamado à profundidade do modelo. Outros nomes apropriados para o campo
poderiam ter sido aprendizado de representações em camadas e representações
hierárquicas. O aprendizado profundo moderno geralmente envolve dezenas ou até centenas de
camadas sucessivas de representações - e todos são aprendidos automaticamente a partir da
exposição a dados de treinamento. Enquanto isso, outras abordagens para aprendizado de
máquina tendem a se concentrar em aprender apenas uma ou duas camadas de representações
dos dados; Portanto, às vezes eles são chamados de aprendizado superficial .

No aprendizado profundo, essas representações em camadas são (quase sempre) aprendidas por
meio de modelos denominados redes neurais , estruturados em camadas literais empilhadas
umas sobre as outras. O termo rede neural é uma referência à neurobiologia, mas, embora
alguns dos conceitos centrais da aprendizagem profunda tenham sido desenvolvidos, em parte,
pela inspiração de nossa compreensão do cérebro, os modelos de aprendizagem
profunda não sãomodelos do cérebro. Não há evidências de que o cérebro implemente algo
como os mecanismos de aprendizagem usados em modelos modernos de aprendizagem
profunda. Você pode se deparar com artigos de ciência pop proclamando que o aprendizado
profundo funciona como o cérebro ou foi modelado a partir do cérebro, mas esse não é o
caso. Seria confuso e contraproducente para os recém-chegados ao campo pensar na
aprendizagem profunda como sendo de alguma forma relacionada à neurobiologia; você não
precisa dessa mortalha da mística e do mistério de “como nossa mente”, e você pode esquecer
qualquer coisa que tenha lido sobre ligações hipotéticas entre a aprendizagem profunda e a
biologia. Para nossos propósitos, a aprendizagem profunda é uma estrutura matemática para
aprender representações a partir de dados.

Como são as representações aprendidas por um algoritmo de aprendizagem profunda? Vamos


examinar como uma rede com várias camadas de profundidade (veja a figura 1.5 ) transforma
uma imagem de um dígito para reconhecer qual é o dígito.
Figura 1.5. Uma rede neural profunda para classificação de dígitos

Como você pode ver na figura 1.6 , a rede transforma a imagem do dígito em representações cada
vez mais diferentes da imagem original e cada vez mais informativa sobre o resultado final. Você
pode pensar em uma rede profunda como uma operação de destilação de informações em vários
estágios, em que as informações passam por filtros sucessivos e são cada vez
mais purificadas (ou seja, úteis em relação a algumas tarefas).

Figura 1.6 Representações profundas aprendidas por um modelo de classificação por dígitos

Então, isso é o que a aprendizagem profunda é, tecnicamente: uma maneira em


vários estágios de aprender representações de dados. É uma ideia simples - mas,
como se vê, mecanismos muito simples, suficientemente dimensionados, podem
acabar parecendo mágicos.

1.1.5. Entendendo como funciona a aprendizagem profunda, em três


figuras
Nesse ponto, você sabe que o aprendizado de máquina é sobre o mapeamento de entradas
(como imagens) para destinos (como o rótulo "gato"), o que é feito observando muitos exemplos
de entrada e destinos. Você também sabe que as redes neurais profundas fazem essa entrada-
alvo mapeamento através de uma sequência profunda de transformações de dados simples
(camadas) e que essas transformações de dados são aprendidas pela exposição a
exemplos. Agora vamos ver como esse aprendizado acontece, concretamente.
A especificação do que uma camada faz com seus dados de entrada é armazenada nos pesos da
camada , que em essência são um monte de números. Em termos técnicos, diríamos que a
transformação implementada por uma camada é parametrizada por seus pesos (veja a figura
1.7 ). (Os pesos são também, por vezes chamados de parâmetros de uma camada.) Neste
contexto, a aprendizagemsignifica encontrar um conjunto de valores para os pesos de todas as
camadas em uma rede, de modo que a rede mapeie corretamente as entradas de exemplo para
seus destinos associados. Mas aqui está a coisa: uma rede neural profunda pode conter dezenas
de milhões de parâmetros. Encontrar o valor correto para todos eles pode parecer uma tarefa
difícil, especialmente considerando que modificar o valor de um parâmetro afetará o
comportamento de todos os outros!

Figura 1.7. Uma rede neural é parametrizada por seus pesos.

Para controlar algo, primeiro você precisa ser capaz de observá-lo. Para controlar a saída de uma
rede neural, você precisa ser capaz de medir até que ponto essa saída é do que você
esperava. Esse é o trabalho da função de perda da rede, também chamada de função objetivo . A
função loss leva as previsões da rede e do verdadeiro alvo (o que você queria que a rede produza)
e calcula uma pontuação de distância, capturando o quão bem a rede tem feito neste exemplo
específico (veja a figura 1.8 ).

Figura 1.8. Uma função de perda mede a qualidade da saída da rede.


O truque fundamental na aprendizagem profunda é usar essa pontuação como um sinal de
feedback para ajustar um pouco o valor dos pesos, em uma direção que reduzirá a pontuação da
perda para o exemplo atual (consulte a figura 1.9 ). Esse ajuste é o trabalho do otimizador , que
implementa o que é chamado de algoritmo Backpropagation : o algoritmo central em deep
learning. O próximo capítulo explica com mais detalhes como funciona a retropropagação.

Figura 1.9. A pontuação da perda é usada como um sinal de feedback para ajustar os pesos.

Inicialmente, os pesos da rede recebem valores aleatórios, portanto, a rede implementa apenas
uma série de transformações aleatórias. Naturalmente, sua saída está longe do ideal, e a
pontuação da perda é, portanto, muito alta. Mas com todos os exemplos dos processos de rede,
os pesos são ajustados um pouco na direção correta e a pontuação da perda diminui. Este é
o loop de treinamento , que, repetido um número suficiente de vezes (normalmente dezenas de
iterações em milhares de exemplos), produz valores de peso que minimizam a função de
perda. Uma rede com uma perda mínima é aquela em que as saídas são tão próximas quanto
possível dos alvos: uma rede treinada. Mais uma vez, é um mecanismo simples que, uma vez
escalado, acaba parecendo mágico.

1.1.6. Que aprendizado profundo alcançou até agora


Embora o aprendizado profundo seja um subcampo de aprendizado de máquina bastante
antigo, ele só ganhou destaque no início de 2010. Nos poucos anos desde que alcançou nada
menos que uma revolução no campo, com resultados notáveis em problemas perceptuais, como
ver e ouvir - problemas envolvendo habilidades que parecem naturais e intuitivas para os
humanos, mas há muito tempo são ilusórios para as máquinas.

Em particular, a aprendizagem profunda alcançou os seguintes avanços, todos em áreas


historicamente difíceis de aprendizado de máquina:

 Classificação de imagem ao nível humano próximo


 Reconhecimento de fala em nível quase humano
 Transcrição de caligrafia quase humana
 Tradução automática aprimorada
 Conversão aprimorada de conversão de texto em fala
 Assistentes digitais como o Google Now e o Amazon Alexa
 Condução autónoma ao nível quase humano
 Segmentação de anúncios aprimorada, usada pelo Google, Baidu e Bing
 Resultados de pesquisa aprimorados na web
 Capacidade de responder perguntas em linguagem natural
 Superhuman Go jogando

Ainda estamos explorando toda a extensão do que o aprendizado profundo pode fazer. Nós
começamos a aplicá-lo a uma ampla variedade de problemas, fora da percepção da máquina e
do entendimento da linguagem natural, como o raciocínio formal. Se for bem sucedido, isso
pode anunciar uma era em que o aprendizado profundo ajuda os seres humanos na ciência, no
desenvolvimento de software e muito mais.

1.1.7. Não acredite no hype de curto prazo


Embora o aprendizado profundo tenha levado a realizações notáveis nos últimos anos, as
expectativas sobre o que o campo poderá alcançar na próxima década tendem a ser muito
maiores do que o que provavelmente será possível. Embora algumas aplicações que mudam o
mundo, como carros autônomos, já estejam ao alcance, é provável que muitas mais continuem
indefinidas por um longo tempo, como sistemas de diálogo confiáveis, tradução automática em
nível humano em idiomas arbitrários e compreensão em linguagem natural em nível
humano. Em particular, falar de inteligência geral em nível humano não deve ser levado muito
a sério. O risco com altas expectativas para o curto prazo é que, como a tecnologia não consegue
entregar, o investimento em pesquisa vai secar, desacelerando o progresso por um longo tempo.

Isso aconteceu antes. Duas vezes no passado, a IA passou por um ciclo de intenso otimismo,
seguido por desapontamento e ceticismo, com uma carência de financiamento como
resultado. Começou com a IA simbólica nos anos 60. Naqueles primeiros dias, projeções sobre
IA estavam voando alto. Um dos pioneiros e proponentes mais conhecidos da abordagem
simbólica da IA foi Marvin Minsky, que afirmou em 1967: “Dentro de uma geração ... o
problema de criar 'inteligência artificial' será substancialmente resolvido.” Três anos depois, em
1970 , ele fez uma predição mais precisamente quantificada: “De três a oito anos teremos uma
máquina com a inteligência geral de um ser humano médio.” Em 2016, Essa conquista ainda
parece estar longe no futuro - até agora, não temos como prever quanto tempo levará -, mas nos
anos 60 e início dos anos 70, vários especialistas acreditavam que ela estava bem próxima
(assim como muitas pessoas hoje). Alguns anos depois, quando essas altas expectativas não se
concretizaram, pesquisadores e fundos do governo se afastaram do campo, marcando o início da
primeiraAI inverno (uma referência a um inverno nuclear, porque isso foi logo após o auge da
Guerra Fria).

Não seria o último. Na década de 1980, uma nova abordagem da inteligência artificial
simbólica, sistemas especialistas , começou a ganhar força entre as grandes empresas. Algumas
histórias de sucesso iniciais desencadearam uma onda de investimentos, com corporações em
todo o mundo iniciando seus próprios departamentos internos de IA para desenvolver sistemas
especialistas. Por volta de 1985, as empresas gastavam mais de US $ 1 bilhão por ano com a
tecnologia; mas no início da década de 1990, esses sistemas mostraram-se caros para serem
mantidos, difíceis de escalar e limitados em escopo, e os juros diminuíram. Assim começou o
segundo inverno da IA.

Podemos estar atualmente testemunhando o terceiro ciclo de excitação e decepção da IA - e


ainda estamos na fase de intenso otimismo. É melhor moderar nossas expectativas a curto prazo
e garantir que as pessoas menos familiarizadas com o lado técnico do campo tenham uma ideia
clara do que o aprendizado profundo pode e não pode oferecer.

1.1.8. A promessa da IA
Embora possamos ter expectativas irrealistas de curto prazo para a IA, a imagem de longo prazo
está brilhando. Estamos apenas começando a aplicar a aprendizagem profunda a muitos
problemas importantes para os quais ela pode se mostrar transformadora, desde diagnósticos
médicos a assistentes digitais. A pesquisa da IA avançou surpreendentemente rapidamente nos
últimos cinco anos, em grande parte devido a um nível de financiamento nunca antes visto na
curta história da IA, mas até agora relativamente pouco desse progresso entrou nos produtos e
processos que formam o nosso mundo. A maioria das descobertas de pesquisa de aprendizagem
profunda ainda não foi aplicada, ou pelo menos não aplicada a toda a gama de problemas que
podem resolver em todas as indústrias. Seu médico ainda não usa AI, e nem o seu
contador. Você provavelmente não usa tecnologias de IA no seu dia-a-dia. É claro, você pode
fazer perguntas simples ao smartphone e obter respostas razoáveis, obter recomendações de
produtos bastante úteis na Amazon.com e pesquisar "aniversário" no Google Fotos e encontrar
instantaneamente essas fotos da festa de aniversário da sua filha mês. Isso está muito longe de
onde tais tecnologias costumavam ficar. Mas essas ferramentas ainda são apenas acessórios
para nossas vidas diárias. A AI ainda está em transição para ser fundamental na maneira como
trabalhamos, pensamos e vivemos. e você pode pesquisar por "aniversário" no Google Fotos e
encontrar instantaneamente as fotos da festa de aniversário da sua filha no mês passado. Isso
está muito longe de onde tais tecnologias costumavam ficar. Mas essas ferramentas ainda são
apenas acessórios para nossas vidas diárias. A AI ainda está em transição para ser fundamental
na maneira como trabalhamos, pensamos e vivemos. e você pode pesquisar por "aniversário" no
Google Fotos e encontrar instantaneamente as fotos da festa de aniversário da sua filha no mês
passado. Isso está muito longe de onde tais tecnologias costumavam ficar. Mas essas
ferramentas ainda são apenas acessórios para nossas vidas diárias. A AI ainda está em transição
para ser fundamental na maneira como trabalhamos, pensamos e vivemos.

Neste momento, pode parecer difícil acreditar que a IA possa ter um grande impacto no nosso
mundo, porque ainda não está amplamente implementada - por exemplo, em 1995, teria sido
difícil acreditar no impacto futuro da Internet. . Naquela época, a maioria das pessoas não via
como a internet era relevante para elas e como isso mudaria suas vidas. O mesmo é verdade
para a aprendizagem profunda e a IA hoje. Mas não se engane: a IA está chegando. Em um
futuro não tão distante, a IA será sua assistente, até mesmo sua amiga; Ele responderá suas
perguntas, ajudará a educar seus filhos e cuidará de sua saúde. Ele entregará seus mantimentos
à sua porta e levará você do ponto A ao ponto B. Será sua interface para um mundo cada vez
mais complexo e com muita informação. E ainda mais importante

No caminho, podemos enfrentar alguns contratempos e talvez um novo inverno de IA - da


mesma maneira que a indústria da internet foi exagerada em 1998-1999 e sofreu um acidente
que secou o investimento durante o início dos anos 2000. Mas nós vamos chegar lá
eventualmente. A IA acabará sendo aplicada a quase todos os processos que compõem nossa
sociedade e nosso dia a dia, da mesma forma que a internet é hoje.

Não acredite no hype de curto prazo, mas acredite na visão de longo prazo. Pode demorar um
pouco para que a IA seja implantada em seu verdadeiro potencial - um potencial cuja extensão
total ninguém ainda ousou sonhar -, mas a IA está chegando e transformará nosso mundo de
uma forma fantástica.

1.2. ANTES DA APRENDIZAGEM PROFUNDA: UM BREVE HISTÓRICO DE


APRENDIZADO DE MÁQUINA

A aprendizagem profunda alcançou um nível de atenção pública e investimento da indústria


nunca antes visto na história da IA, mas não é a primeira forma bem-sucedida de aprendizado
de máquina. É seguro dizer que a maioria dos algoritmos de aprendizado de máquina usados
atualmente na indústria não são algoritmos de aprendizado profundo. A aprendizagem
profunda nem sempre é a ferramenta certa para o trabalho - às vezes não há dados suficientes
para que o aprendizado profundo seja aplicável e, às vezes, o problema é melhor resolvido por
um algoritmo diferente. Se o aprendizado profundo é seu primeiro contato com o aprendizado
de máquina, então você pode se encontrar numa situação em que tudo o que você tem é o
martelo de aprendizagem profunda, e todo problema de aprendizado de máquina começa a
parecer um prego.

Uma discussão detalhada das abordagens clássicas de aprendizado de máquina está fora do
escopo deste livro, mas vamos examiná-las brevemente e descrever o contexto histórico no qual
elas foram desenvolvidas. Isso nos permitirá colocar a aprendizagem profunda no contexto mais
amplo da aprendizagem de máquina e entender melhor de onde vem a aprendizagem profunda e
por que ela é importante.
1.2.1. Modelagem probabilística
A modelagem probabilística é a aplicação dos princípios da estatística à análise de dados. Foi
uma das primeiras formas de aprendizado de máquina, e ainda é amplamente usada até
hoje. Um dos algoritmos mais conhecidos nesta categoria é o algoritmo Naive Bayes.

Naive Bayes é um tipo de classificador de aprendizado de máquina baseado na aplicação do


teorema de Bayes, assumindo que os recursos nos dados de entrada são todos independentes
(uma suposição forte ou “ingênua”, que é de onde vem o nome). Essa forma de análise de dados
é anterior aos computadores e foi aplicada manualmente décadas antes de sua primeira
implementação no computador (provavelmente desde a década de 1950). O teorema de Bayes e
as bases das estatísticas remontam ao século XVIII, e são tudo o que você precisa para começar
a usar os classificadores Naive Bayes.

Um modelo estreitamente relacionado é a regressão logística (logreg for short), que às vezes é
considerada o “hello world” da moderna aprendizagem de máquina. Não se deixe enganar pelo
nome - logreg é um algoritmo de classificação, e não um algoritmo de regressão. Muito parecido
com Naive Bayes, logreg antecede a computação por um longo tempo, mas ainda é útil até hoje,
graças à sua natureza simples e versátil. Geralmente, é a primeira coisa que um cientista de
dados tentará em um conjunto de dados para ter uma ideia da tarefa de classificação em
questão.

1.2.2. Redes neurais precoces


As primeiras iterações de redes neurais foram completamente suplantadas pelas variantes
modernas abordadas nestas páginas, mas é útil estar ciente de como a aprendizagem profunda
se originou. Embora as ideias centrais das redes neurais tenham sido investigadas em formas de
brinquedo já nos anos 50, a abordagem levou décadas para começar. Por muito tempo, a peça
que faltava era uma maneira eficiente de treinar grandes redes neurais. Isso mudou em meados
da década de 1980,quando várias pessoas independentemente redescobriram o algoritmo
Backpropagation - uma maneira de treinar cadeias de operações paramétricas usando
otimização de gradiente descendente (mais adiante no livro, definiremos precisamente esses
conceitos) - e começamos a aplicá-lo a redes neurais.

A primeira aplicação prática bem-sucedida de redes neurais veio em 1989 da Bell Labs, quando
Yann LeCun combinou as idéias anteriores de redes neurais convolucionais e retropropagação, e
aplicou-as ao problema de classificar dígitos manuscritos. A rede resultante, apelidada
de LeNet , foi usada pelo Serviço Postal dos Estados Unidos na década de 1990 para automatizar
a leitura de códigos postais em envelopes de correio.

1.2.3. Métodos do kernel


Como as redes neurais começaram a ganhar algum respeito entre os pesquisadores nos anos 90,
graças a esse primeiro sucesso, uma nova abordagem para o aprendizado de máquina alcançou a
fama e rapidamente enviou redes neurais de volta ao esquecimento: os métodos do kernel. Os
métodos de kernel são um grupo de algoritmos de classificação, sendo o mais conhecido deles
a máquina de vetores de suporte (SVM). A formulação moderna de um SVM foi desenvolvida
por Vladimir Vapnik e Corinna Cortes no início dos anos 90 no Bell Labs e publicada em
1995, [ 2 ] embora uma formulação linear antiga tenha sido publicada por Vapnik e Alex
Chervonenkis já em 1963. [ 3 ]

Vladimir Vapnik e Corinna Cortes, “Redes de Suporte-Vetor”, Machine Learning 20, no. 3 (1995): 273-297.

3
Vladimir Vapnik e Alexey Chervonenkis, “Uma Nota sobre Uma Classe de Perceptrons”, Automação e Controle Remoto 25 (1964).

SVMs visam resolver problemas de classificação, encontrando bons limites de decisão (ver figura
1.10 ) entre dois conjuntos de pontos pertencentes a duas categorias diferentes. Um limite de
decisão pode ser considerado uma linha ou superfície separando seus dados de treinamento em
dois espaços correspondentes a duas categorias. Para classificar novos pontos de dados, você só
precisa verificar de que lado do limite de decisão eles se encaixam.

Figura 1.10. Um limite de decisão

SVMs continuam a encontrar esses limites em duas etapas:

1. Os dados são mapeados para uma nova representação de alta dimensão onde o limite de
decisão pode ser expresso como um hiperplano (se os dados forem bidimensionais,
como na figura 1.10 , um hiperplano seria uma linha reta).
2. Um bom limite de decisão (um hiperplano de separação) é calculado tentando
maximizar a distância entre o hiperplano e os pontos de dados mais próximos de cada
classe, uma etapa chamada maximizar a margem . Isso permite que o limite seja
generalizado para novas amostras fora do conjunto de dados de treinamento.

A técnica de mapeamento de dados para uma representação de alta dimensão, em que um


problema de classificação se torna mais simples, pode parecer boa no papel, mas, na prática, é
muitas vezes intratável do ponto de vista computacional. É aí que entra o truque do kernel (a
ideia chave de que os métodos do kernel são nomeados). Aqui está a essência: para encontrar
uma boa decisãohiperplanos no novo espaço de representação, você não precisa calcular
explicitamente as coordenadas de seus pontos no novo espaço; você só precisa calcular a
distância entre pares de pontos naquele espaço, o que pode ser feito eficientemente usando
uma função do kernel . Uma função kernel é uma operação tratável computacionalmente que
mapeia quaisquer dois pontos em seu espaço inicial para a distância entre esses pontos em seu
espaço de representação de destino, ignorando completamente o cálculo explícito da nova
representação. As funções do kernel são tipicamente criadas manualmente, em vez de
aprendidas com dados - no caso de um SVM, apenas o hiperplano de separação é aprendido.

Na época em que foram desenvolvidos, os SVMs exibiam um desempenho de última geração em


problemas simples de classificação e eram um dos poucos métodos de aprendizado de máquina
apoiados por ampla teoria e passíveis de análise matemática séria, tornando-os bem
compreendidos e facilmente interpretáveis. Por causa dessas propriedades úteis, os SVMs
tornaram-se extremamente populares no campo por um longo tempo.

Mas os SVMs mostraram-se difíceis de escalar para grandes conjuntos de dados e não
forneceram bons resultados para problemas perceptivos, como a classificação de imagens. Como
um SVM é um método superficial, a aplicação de um SVM a problemas de percepção requer
primeiro a extração de representações úteis manualmente (uma etapa chamada engenharia de
recursos ), que é difícil e frágil.
1.2.4. Árvores de decisão, florestas aleatórias e máquinas de aumento
de gradiente
As árvores de decisão são estruturas semelhantes a fluxogramas que permitem classificar
pontos de dados de entrada ou prever valores de saída de acordo com entradas (consulte a figura
1.11 ). Eles são fáceis de visualizar e interpretar. As árvores de decisões aprendidas a partir de
dados começaram a receber interesse de pesquisa significativo nos anos 2000 e, em 2010, elas
eram frequentemente preferidas aos métodos de kernel.

Figura 1.11. Uma árvore de decisão: os parâmetros aprendidos são as questões sobre os dados. Uma pergunta poderia ser,
por exemplo, "O coeficiente 2 nos dados é maior que 3,5?"

Em particular, o algoritmo Random Forest introduziu uma abordagem prática e robusta no


aprendizado da árvore de decisão que envolve a construção de um grande número de árvores de
decisão especializadas e, em seguida, o agrupamento de suas saídas. Florestas aleatórias são
aplicáveis a uma ampla gama de problemas - você poderia dizer que elas são quase sempre o
segundo melhor algoritmo para qualquer tarefa de aprendizado de máquina superficial. Quando
o popular site de competição de aprendizado de máquina Kaggle ( http://kaggle.com ) começou
em 2010, as florestas aleatórias rapidamente se tornaram as favoritas na plataforma - até 2014,
quando máquinas de aumento de gradienteassumiu. Uma máquina de intensificação de
gradiente, muito parecida com uma floresta aleatória, é uma técnica de aprendizado de máquina
baseada em modelos de previsão fracos, geralmente árvores de decisão. istousa o intensificador
de gradiente , uma maneira de melhorar qualquer modelo de aprendizado de máquina
treinando iterativamente novos modelos especializados em abordar os pontos fracos dos
modelos anteriores. Aplicado a árvores de decisão, o uso da técnica de aumento de gradiente
resulta em modelos que superam estritamente as florestas aleatórias na maioria das vezes,
enquanto possuem propriedades semelhantes. Pode ser um dos melhores, se não o melhor,
algoritmo para lidar com dados não perceptivos hoje. Juntamente com a aprendizagem
profunda, é uma das técnicas mais utilizadas nas competições de Kaggle.

1.2.5. Voltar para redes neurais


Por volta de 2010, embora as redes neurais tenham sido quase completamente rejeitadas pela
comunidade científica em geral, várias pessoas que ainda trabalham em redes neurais
começaram a fazer descobertas importantes: os grupos de Geoffrey Hinton na Universidade de
Toronto, Yoshua Bengio na Universidade de Montreal. , Yann LeCun na New York University e
IDSIA na Suíça.

Em 2011, Dan Ciresan da IDSIA começou a ganhar competições acadêmicas de classificação de


imagem com redes neurais profundas treinadas pela GPU - o primeiro sucesso prático da
moderna aprendizagem profunda. Mas o divisor de águas veio em 2012, com a entrada do grupo
de Hinton no desafio anual de classificação de imagem em larga escala ImageNet. O desafio do
ImageNet era notoriamente difícil na época, consistindo em classificar imagens em cores de alta
resolução em 1.000 categorias diferentes após o treinamento em 1.4 milhões de imagens. Em
2011, a precisão dos cinco primeiros do modelo vencedor, baseada nas abordagens clássicas da
visão computacional, foi de apenas 74,3%. Então, em 2012, uma equipe liderada por Alex
Krizhevsky e orientada por Geoffrey Hinton conseguiu alcançar uma precisão de mais de cinco
anos de 83,6% - um avanço significativo. A competição tem sido dominada por redes neurais
profundas de convolução todos os anos desde então. Em 2015, o vencedor alcançou uma
precisão de 96,4%, e a tarefa de classificação no ImageNet foi considerada um problema
completamente resolvido.

Desde 2012, redes neurais convolucionais profundas ( convnets) tornou-se o algoritmo para
todas as tarefas de visão computacional; mais geralmente, eles trabalham em todas as tarefas
perceptivas. Nas principais conferências sobre visão computacional em 2015 e 2016, foi quase
impossível encontrar apresentações que não envolvessem as connas de alguma forma. Ao
mesmo tempo, o aprendizado profundo também encontrou aplicativos em muitos outros tipos
de problemas, como o processamento de linguagem natural. Ele substituiu completamente as
SVMs e as árvores de decisão em uma ampla gama de aplicações. Por exemplo, durante vários
anos, a Organização Europeia para Pesquisa Nuclear, CERN, usou métodos baseados em árvores
de decisão para análise de dados de partículas do detector ATLAS no Large Hadron Collider
(LHC);

1.2.6. O que torna o aprendizado profundo diferente


A principal razão pela qual a aprendizagem profunda decolou tão rapidamente é que ofereceu
melhor desempenho em muitos problemas. Mas essa não é a única razão. Aprendizagem
profunda também fazsolução de problemas muito mais fácil, porque automatiza completamente
o que costumava ser a etapa mais crucial em um fluxo de trabalho de aprendizado de máquina:
engenharia de recursos.

Técnicas anteriores de aprendizado de máquina - aprendizado superficial - envolviam apenas a


transformação dos dados de entrada em um ou dois espaços de representação sucessivos,
geralmente por meio de transformações simples, como projeções não-lineares (SVMs) de alta
dimensão ou árvores de decisão. Mas as representações refinadas requeridas por problemas
complexos geralmente não podem ser alcançadas por tais técnicas. Como tal, os humanos
tiveram que se esforçar para tornar os dados de entrada iniciais mais acessíveis ao
processamento por esses métodos: eles tinham que criar manualmente boas camadas de
representações para seus dados. Isso é chamado de engenharia de recursos. A aprendizagem
profunda, por outro lado, automatiza completamente essa etapa: com o aprendizado profundo,
você aprende todos os recursos em uma única passagem, em vez de ter que criá-los por conta
própria. Isso simplificou enormemente os fluxos de trabalho de aprendizado de máquina,
muitas vezes substituindo sofisticados pipelines de múltiplos estágios por um modelo de
aprendizado profundo simples, de ponta a ponta.

Você pode perguntar: se o ponto crucial da questão é ter várias camadas sucessivas de
representações, os métodos superficiais poderiam ser aplicados repetidamente para emular os
efeitos da aprendizagem profunda? Na prática, há retornos que diminuem rapidamente para
aplicações sucessivas de métodos de aprendizado superficial, porque a primeira camada de
representação ideal em um modelo de três camadas não é a primeira camada ideal em um
modelo de camada única ou de camada dupla . O que é transformador na aprendizagem
profunda é que ela permite que um modelo aprenda todas as camadas de
representação conjuntamente , ao mesmo tempo, em vez de em sucessão ( avidamentecomo é
chamado). Com o recurso de aprendizagem conjunta, sempre que o modelo ajusta um de seus
recursos internos, todos os outros recursos que dependem dele se adaptam automaticamente à
mudança, sem a necessidade de intervenção humana. Tudo é supervisionado por um único sinal
de feedback: cada mudança no modelo atende ao objetivo final. Isso é muito mais poderoso do
que empilhar avidamente modelos superficiais, porque permite que representações abstratas e
complexas sejam aprendidas, dividindo-as em longas séries de espaços intermediários
(camadas); Cada espaço é apenas uma simples transformação da anterior.

Essas são as duas características essenciais de como a aprendizagem profunda aprende com os
dados: o modo incremental, camada por camada, no qual as representações cada vez mais
complexas são desenvolvidas , e o fato dessas representações incrementais intermediárias
serem aprendidas em conjunto , sendo cada camada atualizada para seguir ambos as
necessidades representacionais da camada acima e as necessidades da camada abaixo. Juntas,
essas duas propriedades tornaram o aprendizado profundo muito mais bem-sucedido do que as
abordagens anteriores de aprendizado de máquina.

1.2.7. O moderno cenário de aprendizado de máquina


Uma ótima maneira de entender o panorama atual dos algoritmos e ferramentas de aprendizado
de máquina é observar as competições de aprendizado de máquina no Kaggle. Devido ao seu
ambiente altamente competitivo (alguns concursos têm milhares de participantes e prêmios de
milhões de dólares) e à grande variedade de problemas de aprendizado de máquina cobertos, a
Kaggle oferece uma maneira realista de avaliar o que funciona e o que não funciona. Então, que
tipo de algoritmo está ganhando competições de maneira confiável? Quais ferramentas os
principais participantes usam?

Em 2016 e 2017, a Kaggle foi dominada por duas abordagens: máquinas de aumento de
gradiente e aprendizado profundo. Especificamente, o aumento de gradiente é usado para
problemas em que dados estruturados estão disponíveis, enquanto a aprendizagem profunda é
usada para problemas de percepção, como a classificação de imagens. Praticantes do primeiro
quase sempre usam a excelente biblioteca XGBoost, que oferece suporte para os dois idiomas
mais populares da ciência de dados: Python e R. Enquanto isso, a maioria dos participantes do
Kaggle usando deep learning usam a biblioteca Keras, devido à sua facilidade de uso. ,
flexibilidade e suporte do Python.

Essas são as duas técnicas com as quais você deve estar mais familiarizado para ter sucesso no
aprendizado de máquina aplicado hoje: máquinas de aumento de gradiente, para problemas de
aprendizado superficial; e aprendizado profundo, para problemas perceptivos. Em termos
técnicos, isso significa que você precisa estar familiarizado com XGBoost e Keras - as duas
bibliotecas que atualmente dominam as competições de Kaggle. Com este livro na mão, você já
está um grande passo mais perto.

1.3. POR QUE APRENDER PROFUNDAMENTE? PORQUE AGORA?

As duas principais idéias de aprendizagem profunda para visão computacional - redes neurais
convolucionais e retropropagação - já eram bem compreendidas em 1989. O algoritmo LSTM
(Long Short-Term Memory), fundamental para a aprendizagem profunda de timeseries, foi
desenvolvido em 1997 e mal mudou desde então. Então, por que o aprendizado profundo só
decolou depois de 2012? O que mudou nessas duas décadas?

Em geral, três forças técnicas estão impulsionando avanços no aprendizado de máquina:

 Hardware
 Conjuntos de dados e benchmarks
 Avanços algorítmicos

Como o campo é guiado por descobertas experimentais e não por teoria, avanços algorítmicos só
se tornam possíveis quando dados e hardware apropriados estão disponíveis para experimentar
novas ideias (ou escalar idéias antigas, como é frequentemente o caso). Aprendizado de
máquina não é matemática ou física, onde grandes avanços podem ser feitos com uma caneta e
um pedaço de papel. É uma ciência de engenharia.

Os gargalos reais durante os anos 1990 e 2000 foram dados e hardware. Mas eis o que
aconteceu durante esse tempo: a internet decolou e os chips gráficos de alto desempenho foram
desenvolvidos para as necessidades do mercado de jogos.

1.3.1. Hardware
Entre 1990 e 2010, os CPUs prontos para uso ficaram mais rápidos por um fator de
aproximadamente 5.000. Como resultado, hoje em dia é possível executar pequenos modelos de
aprendizagem profunda em seu laptop, enquanto isso seria intratável há 25 anos.

Mas os modelos típicos de aprendizagem profunda usados na visão computacional ou no


reconhecimento de fala exigem ordens de magnitude mais poder computacional do que o seu
laptop pode oferecer. Ao longo da década de 2000, empresas como a NVIDIA e AMD investiram
bilhões de dólares no desenvolvimento rápido e massivo de chips paralelos (GPUs) para
impulsionar os gráficos de videogames cada vez mais realistas - supercomputadores baratos e de
propósito único projetados para renderizar complexos Cenas em 3D na sua tela em tempo
real. Esse investimento veio beneficiar a comunidade científica quando, em 2007, a NVIDIA
lançou o CUDA ( https://developer.nvidia.com/about-cuda), uma interface de programação para sua
linha de GPUs. Um pequeno número de GPUs começou a substituir grandes clusters de CPUs
em vários aplicativos altamente paralelizáveis, começando com a modelagem física. Redes
neurais profundas, consistindo principalmente de muitas pequenas multiplicações de matrizes,
também são altamente paralelizáveis; e por volta de 2011, alguns pesquisadores começaram a
escrever implementações CUDA de redes neurais - Dan Ciresan [ 4 ] e Alex Krizhevsky [ 5 ] estavam
entre os primeiros.

Veja “Redes Neurais Convolucionais Flexíveis e de Alto Desempenho para Classificação de Imagens”, Anais da 22ª Conferência

Internacional Conjunta sobre Inteligência Artificial (2011), www.ijcai.org/Proceedings/11/Papers/210.pdf .

Veja “Classificação ImageNet com Redes Neurais Convolucionais Profundas”, Avanços em Sistemas de Processamento de Informação

Neural 25 (2012), http://mng.bz/2286 .

O que aconteceu é que o mercado de jogos subsidiou a supercomputação para a próxima geração
de aplicativos de inteligência artificial. Às vezes, grandes coisas começam como jogos. Hoje, a
NVIDIA TITAN X, uma GPU de jogos que custou US $ 1.000 no final de 2015, pode fornecer um
pico de 6,6 TFLOPS em precisão única: 6,6 trilhõesfloat32operações por segundo. Isso é cerca
de 350 vezes mais do que você pode sair de um laptop moderno. Em um TITAN X, leva apenas
alguns dias para treinar um modelo ImageNet do tipo que teria vencido a competição ILSVRC
há alguns anos. Enquanto isso, grandes empresas treinam modelos de aprendizagem profunda
em grupos de centenas de GPUs de um tipo desenvolvido especificamente para as necessidades
de aprendizado profundo, como o NVIDIA Tesla K80. O grande poder computacional desses
clusters é algo que nunca teria sido possível sem as GPUs modernas.

Além do mais, a indústria de aprendizagem profunda está começando a ir além das GPUs e está
investindo em chips cada vez mais especializados e eficientes para o aprendizado profundo. Em
2016, em sua convenção anual de E / S, o Google revelou seu projeto de unidade de
processamento de tensor (TPU): um novo design de chip desenvolvido para operar redes
neurais profundas, que é 10 vezes mais rápido e muito mais eficiente em termos energéticos que
GPUs de última geração.

1.3.2. Dados
A IA às vezes é anunciada como a nova revolução industrial. Se a aprendizagem profunda é a
máquina a vapor dessa revolução, então os dados são o seu carvão: a matéria-prima que
alimenta nossas máquinas inteligentes, sem a qual nada seria possível. Quando se trata de
dados, além do progresso exponencial no hardware de armazenamento nos últimos 20 anos
(seguindo a lei de Moore), o jogo foi a ascensão da Internet, tornando viável a coleta e
distribuição de grandes conjuntos de dados para aprendizado de máquina. . Hoje, grandes
empresas trabalham com conjuntos de dados de imagens, conjuntos de dados de vídeo e
conjuntos de dados em linguagem natural que não poderiam ter sido coletados sem a
Internet. As tags de imagem geradas pelo usuário no Flickr, por exemplo, têm sido um tesouro
de dados para a visão computacional. Então, são vídeos do YouTube.

Se há um conjunto de dados que tem sido um catalisador para o aumento do aprendizado


profundo, é o conjunto de dados do ImageNet, que consiste em 1,4 milhão de imagens que
foram anotadas manualmente com 1.000 categorias de imagens (1 categoria por imagem). Mas o
que torna o ImageNet especial não é apenas seu tamanho grande, mas também a competição
anual associada a ele. [ 6 ]

O Desafio de Reconhecimento Visual de Grande Escala do ImageNet (ILSVRC), www.image-net.org/challenges/LSVRC .

Como Kaggle vem demonstrando desde 2010, as competições públicas são uma excelente
maneira de motivar os pesquisadores e engenheiros a se esforçarem. Ter benchmarks comuns
que os pesquisadores competem para vencer ajudou muito o recente aumento do aprendizado
profundo.

1.3.3. Algoritmos
Além de hardware e dados, até o final dos anos 2000, estávamos perdendo uma maneira
confiável de treinar redes neurais muito profundas. Como resultado, as redes neurais ainda
eram bastante rasas,usando apenas uma ou duas camadas de representações; assim, eles não
foram capazes de brilhar contra métodos rasos mais refinados, como SVMs e florestas
aleatórias. A questão principal era a propagação de gradientes através de pilhas profundas de
camadas. O sinal de feedback usado para treinar redes neurais desapareceria à medida que o
número de camadas aumentasse.

Isso mudou por volta de 2009–2010 com o advento de várias melhorias algorítmicas simples,
mas importantes, que permitiram uma melhor propagação de gradiente:

 Melhores funções de ativação para camadas neurais


 Melhores esquemas de inicialização de peso , começando com o pré-treinamento em
camadas, que foi rapidamente abandonado
 Melhores esquemas de otimização , como RMSProp e Adam

Somente quando essas melhorias começaram a permitir modelos de treinamento com 10 ou


mais camadas, o aprendizado profundo começou a brilhar.

Por fim, em 2014, 2015 e 2016, formas ainda mais avançadas de ajudar na propagação de
gradientes foram descobertas, como a normalização de lotes, conexões residuais e convoluções
separadas por profundidade. Hoje podemos treinar a partir de modelos que são milhares de
camadas de profundidade.

1.3.4. Uma nova onda de investimento


À medida que o aprendizado profundo se tornou o novo estado da arte da visão computacional
em 2012–2013 e, finalmente, para todas as tarefas de percepção, os líderes da indústria
tomaram nota. O que se seguiu foi uma onda gradual de investimento na indústria muito além
de qualquer coisa previamente vista na história da IA.

Em 2011, pouco antes de a aprendizagem profunda ter sido o centro das atenções, o
investimento total em capital de risco em IA foi de cerca de US $ 19 milhões, que foi quase
inteiramente para aplicações práticas de abordagens de aprendizado de máquina rasa. Em 2014,
havia chegado a impressionantes US $ 394 milhões. Dezenas de startups lançadas nesses três
anos, tentando capitalizar o hype de aprendizado profundo. Enquanto isso, grandes empresas de
tecnologia como Google, Facebook, Baidu e Microsoft investiram em departamentos de pesquisa
interna em quantias que provavelmente superariam o fluxo de dinheiro de capital de
risco. Apenas alguns números surgiram: em 2013, o Google adquiriu a DeepMind, startup de
aprendizado profundo, por US $ 500 milhões, a maior aquisição de uma empresa de IA da
história. Em 2014, O Baidu iniciou um centro de pesquisa de aprendizagem profunda no Vale do
Silício, investindo US $ 300 milhões no projeto. A startup de hardware de aprendizagem
profunda Nervana Systems foi adquirida pela Intel em 2016 por mais de US $ 400 milhões.

O aprendizado de máquina - em particular, o aprendizado profundo - tornou-se central para a


estratégia de produto desses gigantes da tecnologia. No final de 2015, o CEO do Google, Sundar
Pichai, declarou: “O aprendizado de máquina é uma maneira central e transformadora pela qual
estamos repensando como estamos fazendo tudo. Estamos a aplicá-lo em todos os nossos
produtos, seja na pesquisa, nos anúncios, no YouTube ou no Google Play. E estamos nos
primeiros dias, mas você nos verá - de uma maneira sistemática - aplicar o aprendizado de
máquina em todas essas áreas. ” [ 7 ]

Sundar Pichai, chamada de salário do alfabeto, 22 de outubro de 2015.

Como resultado dessa onda de investimento, o número de pessoas trabalhando em aprendizado


profundo foi de apenas cinco anos, passando de algumas centenas para dezenas de milhares, e o
progresso da pesquisa atingiu um ritmo frenético. Atualmente, não há sinais de que essa
tendência diminuirá em breve.

1.3.5. A democratização da aprendizagem profunda


Um dos principais fatores que impulsionam esse fluxo de novos rostos em aprendizado
profundo tem sido a democratização dos conjuntos de ferramentas usados no campo. Nos
primórdios, o aprendizado profundo exigia conhecimentos significativos em C ++ e CUDA, que
poucas pessoas possuíam. Atualmente, as habilidades básicas de script do Python são suficientes
para realizar pesquisas avançadas em aprendizado profundo. Isso tem sido impulsionado
principalmente pelo desenvolvimento do Theano e do TensorFlow - dois frameworks simbólicos
de manipulação de tensores para Python que suportam a autodiferenciação, simplificando
enormemente a implementação de novos modelos - e pelo surgimento de bibliotecas amigáveis
ao usuário como Keras, que faz Aprendizagem profunda tão fácil quanto manipular blocos de
LEGO. Após seu lançamento no início de 2015, Keras rapidamente se tornou a solução de
aprendizagem profunda para um grande número de novas startups,

1.3.6. Vai durar?


Existe alguma coisa especial sobre as redes neurais profundas que as torna a abordagem “certa”
para as empresas estarem investindo em e para os pesquisadores? Ou aprender profundamente
é apenas uma moda passageira que pode não durar? Ainda estaremos usando redes neurais
profundas daqui a 20 anos?

O aprendizado profundo tem várias propriedades que justificam seu status como uma revolução
da IA, e está aqui para ficar. Podemos não estar usando redes neurais daqui a duas décadas, mas
o que usarmos herdará diretamente da aprendizagem profunda moderna e de seus principais
conceitos. Essas propriedades importantes podem ser classificadas em três categorias:

 Simplicidade - Aprendizagem profunda elimina a necessidade de engenharia de


recursos, substituindo dutos complexos, frágeis e pesados pela engenharia por modelos
treináveis, de ponta a ponta, que são tipicamente construídos usando apenas cinco ou
seis operações de tensores diferentes.
 Escalabilidade - A aprendizagem profunda é altamente passível de paralelização em
GPUs ou TPUs, de modo que pode aproveitar ao máximo a lei de Moore. Além disso, os
modelos de aprendizagem profunda são treinados pela iteração de pequenos lotes de
dados, permitindo que eles sejam treinados em conjuntos de dados de tamanho
arbitrário. (O único gargalo é a quantidade de energia computacional paralela
disponível, que, graças à lei de Moore, é uma barreira que se move rapidamente.)
 Versatilidade e reutilização - Diferentemente de muitas abordagens anteriores de
aprendizado de máquina, modelos de aprendizagem profunda podem ser treinados em
dados adicionais sem reiniciar do zero, tornando-os viáveis para o aprendizado on-line
contínuo - uma propriedade importante para modelos de produção muito
grandes. Além disso, os modelos de aprendizagem profunda treinados são reutilizáveis
e, portanto, reutilizáveis: por exemplo, é possível adotar um modelo de aprendizagem
profunda treinado para classificação de imagens e colocá-lo em um pipeline de
processamento de vídeo. Isso nos permite reinvestir o trabalho anterior em modelos
cada vez mais complexos e poderosos. Isso também torna o aprendizado profundo
aplicável a conjuntos de dados relativamente pequenos.

O aprendizado profundo só está no centro das atenções há alguns anos, e ainda não
estabelecemos o escopo completo do que ele pode fazer. A cada mês que passa, aprendemos
sobre novos casos de uso e melhorias de engenharia que levantam limitações
anteriores. Seguindo uma revolução científica, o progresso geralmente segue uma curva
sigmóide: começa com um período de progresso rápido, que gradualmente se estabiliza à
medida que os pesquisadores atingem limitações difíceis, e então outras melhorias se tornam
incrementais. A aprendizagem profunda em 2017 parece estar na primeira metade desse
sigmóide, com muito mais progresso nos próximos anos.

Capítulo 2. Antes de começarmos: os blocos de


construção matemáticos das redes neurais
Este capítulo cobre

 Um primeiro exemplo de uma rede neural


 Tensores e operações tensoras
 Como as redes neurais aprendem via retropropagação e gradiente descendente

Entender o aprendizado profundo requer familiaridade com muitos conceitos matemáticos


simples: tensores, operações de tensor, diferenciação, descida de gradiente e assim por
diante. Nosso objetivo neste capítulo será construir sua intuição sobre essas noções sem se
tornar excessivamente técnico. Em particular, nos afastaremos da notação matemática, o que
pode ser desanimador para aqueles sem nenhum conhecimento de matemática e não é
estritamente necessário para explicar bem as coisas.

Para adicionar algum contexto para tensores e gradiente de descida, vamos começar o capítulo
com um exemplo prático de uma rede neural. Então, examinaremos cada novo conceito que foi
introduzido, ponto por ponto. Tenha em mente que esses conceitos serão essenciais para você
entender os exemplos práticos que virão nos capítulos seguintes!

Depois de ler este capítulo, você terá uma compreensão intuitiva de como as redes neurais
funcionam e poderá seguir para os aplicativos práticos - que serão iniciados no capítulo 3 .

2.1. UMA PRIMEIRA OLHADA EM UMA REDE NEURAL

Vejamos um exemplo concreto de uma rede neural que usa a biblioteca Python Keras para
aprender a classificar dígitos manuscritos. A menos que você já tenha experiência com Keras ou
bibliotecas similares, você não entenderá tudo sobre esse primeiro exemplo
imediatamente. Você provavelmente ainda nem instalou o Keras; isso é bom. No próximo
capítulo, vamos revisar cada elemento no exemplo e explicá-los em detalhes. Então não se
preocupe se alguns passos parecerem arbitrários ou parecerem mágicos para você! Temos que
começar de algum lugar.

O problema que estamos tentando resolver aqui é classificar imagens em escala de cinza de
dígitos manuscritos (28 × 28 pixels) em suas 10 categorias (0 a 9). Usaremos o conjunto de
dados MNIST, um clássico da comunidade de aprendizado de máquina, que tem estado em
torno de quase o mesmo tempo que o próprio campo e foi intensamente estudado. É um
conjunto de 60.000 imagens de treinamento, além de 10.000 imagens de teste, montadas pelo
Instituto Nacional de Padrões e Tecnologia (o NIST no MNIST) na década de 1980. Você pode
pensar em “resolver” o MNIST como o “Hello World” de aprendizado profundo - é o que você faz
para verificar se seus algoritmos estão funcionando como esperado. À medida que você se tornar
um praticante de aprendizado de máquina, verá o MNIST aparecer repetidas vezes, em artigos
científicos, publicações em blogs e assim por diante. Você pode ver algumas amostras MNIST
emfigura 2.1 .

Figura 2.1. MNIST dígitos da amostra

Nota sobre classes e rótulos

No aprendizado de máquina, uma categoria em um problema de classificação é chamada


de classe . Pontos de dados são chamados de amostras . A classe associada a uma amostra
específica é chamada de rótulo .

Você não precisa tentar reproduzir este exemplo em sua máquina agora. Se você desejar,
primeiro precisará configurar o Keras, que é abordado na seção 3.3 .

O conjunto de dados MNIST vem pré-carregado em Keras, na forma de um conjunto de quatro


matrizes Numpy.

Listagem 2.1. Carregando o conjunto de dados MNIST em Keras

de keras.datasets import mnist

(train_images, train_labels), (test_images, test_labels) = mnist.load_data ()

train_imagese train_labelsformar o conjunto de treinamento , os dados que o modelo


aprenderá. O modelo será então testado no conjunto de
teste , test_imagese test_labels.As imagens são codificadas como matrizes Numpy, e as
etiquetas são uma matriz de dígitos, variando de 0 a 9. As imagens e os rótulos têm uma
correspondência de um para um.

Vamos dar uma olhada nos dados de treinamento:

>>> train_images.shape

(60000, 28, 28)

>>> len (train_labels)

60000
>>> train_labels

array ([5, 0, 4, ..., 5, 6, 8], dtype = uint8)

E aqui estão os dados de teste:

>>> test_images.shape

(10000, 28, 28)

>>> len (test_labels)

10000

>>> test_labels

array ([7, 2, 1, ..., 4, 5, 6], dtype = uint8)

O fluxo de trabalho será o seguinte: Primeiro, alimentaremos a rede neural com os dados de
treinamento train_imagese train_labels. A rede aprenderá então a associar imagens e
rótulos. Por fim, solicitaremos que a rede produza previsões test_imagese verificaremos se
essas previsões correspondem aos rótulos test_labels.

Vamos construir a rede - mais uma vez, lembre-se de que você não deve entender tudo sobre
esse exemplo ainda.

Listagem 2.2. A arquitetura de rede

de modelos de importação keras

das camadas de importação keras

rede = models.Sequential ()

network.add (layers.Dense (512, activation = 'relu', input_shape = (28 *


28,)))

network.add (layers.Dense (10, ativação = 'softmax'))

O principal bloco de construção de redes neurais é a camada , um módulo de processamento de


dados que você pode imaginar como um filtro para dados. Alguns dados entram e saem de uma
forma mais útil. Especificamente, as camadas extraem representações dos dados alimentados
nelas - com sorte, representações que são mais significativas para o problema em questão. A
maior parte do aprendizado profundo consiste em encadear camadas simples que
implementarão uma forma de destilaçãoprogressiva de dados . Um modelo de aprendizagem
profunda é como uma peneira para processamento de dados, feita de uma sucessão de filtros de
dados cada vez mais refinados - as camadas.

Aqui, nossa rede consiste em uma sequência de duas Densecamadas, que são camadas
neurais densamente conectadas (também chamadas totalmente conectadas ). A segunda (e
última) camada é uma camada softmax de 10 vias , o que significa que ela retornará uma matriz
de 10 pontuações de probabilidade (somando 1). Cada pontuação será a probabilidade de a
imagem do dígito atual pertencer a uma das nossas classes de 10 dígitos.

Para tornar a rede pronta para o treinamento, precisamos escolher mais três coisas, como parte
da etapa de compilação :
 Uma função de perda - como a rede poderá medir seu desempenho nos dados de
treinamento e, assim, como será capaz de se orientar na direção certa.
 Um otimizador - O mecanismo pelo qual a rede se atualizará com base nos dados que
ela vê e na sua função de perda.
 Métricas a serem monitoradas durante o treinamento e os testes - Aqui, nos
preocuparemos apenas com a precisão (a fração das imagens que foram classificadas
corretamente).

O objetivo exato da função de perda e o otimizador serão esclarecidos nos próximos dois
capítulos.

Listagem 2.3. O passo de compilação

network.compile (optimizer = 'rmsprop',

perda = 'categorical_crossentropy',

métricas = ['precisão']]

Antes do treinamento, pré-processaremos os dados remodelando-os no formato esperado pela


rede e dimensionando-os de forma que todos os valores estejam no [0,
1]intervalo. Anteriormente, nossas imagens de treinamento, por exemplo, eram armazenadas
em uma matriz de forma (60000, 28, 28)do tipo uint8com valores no [0,
255]intervalo. Nós o transformamos em uma float32matriz de formas (60000, 28 *
28)com valores entre 0 e 1.

Listagem 2.4. Preparando os dados da imagem

train_images = train_images.reshape ((60000, 28 * 28))

train_images = train_images.astype ('float32') / 255

test_images = test_images.reshape ((10000, 28 * 28))

test_images = test_images.astype ('float32') / 255

Também precisamos codificar categoricamente os rótulos, um passo explicado no capítulo 3 .

Listagem 2.5. Preparando os rótulos

de keras.utils import to_categorical

train_labels = to_categorical (train_labels)

test_labels = to_categorical (test_labels)

Agora estamos prontos para treinar a rede, que em Keras é feita por meio de uma chamada
ao fitmétodo da rede - ajustamos o modelo aos dados de treinamento:

>>> network.fit (train_images, train_labels, épocas = 5, batch_size = 128)

Época 1/5

60000/60000 [==============================] - 9s - perda: 0,2524 - acc:


0,9273
Época 2/5

51328/60000 [========================> .....] - ETA: 1s - perda: 0,1035 - acc:


0,9692

Duas quantidades são exibidas durante o treinamento: a perda da rede sobre os dados de
treinamento e a precisão da rede sobre os dados de treinamento.

Nós alcançamos rapidamente uma precisão de 0,989 (98,9%) nos dados de treinamento. Agora
vamos verificar se o modelo também funciona bem no conjunto de testes:

>>> test_loss, test_acc = network.evaluate (test_images, test_labels)

>>> print ('test_acc:', test_acc)

test_acc: 0,9785

A precisão do conjunto de testes é de 97,8% - um pouco abaixo da precisão do conjunto de


treinamento. Essa lacuna entre a precisão do treinamento e a precisão do teste é um exemplo
de overfitting : o fato de que os modelos de aprendizado de máquina tendem a ter um
desempenho pior em novos dados do que em seus dados de treinamento. Overfitting é um
tópico central no capítulo 3 .

Isso conclui nosso primeiro exemplo - você acabou de ver como você pode construir e treinar
uma rede neural para classificar dígitos manuscritos em menos de 20 linhas de código
Python. No próximo capítulo, vou entrar em detalhes sobre cada peça em movimento que
acabamos de visualizar e esclarecer o que está acontecendo nos bastidores. Você aprenderá
sobre tensores, os objetos de armazenamento de dados indo para a rede; operações de tensor, de
que camadas são feitas; e gradiente descendente, que permite que sua rede aprenda com seus
exemplos de treinamento.

2.2. REPRESENTAÇÕES DE DADOS PARA REDES NEURAIS

No exemplo anterior, começamos a partir de dados armazenados em matrizes Numpy


multidimensionais, também chamados de tensores . Em geral, todos os sistemas atuais de
aprendizado de máquina usam tensores como estrutura básica de dados. Os tensores são
fundamentais para o campo - tão fundamentais que o TensorFlow do Google recebeu o nome
deles. Então, o que é um tensor?

Em seu núcleo, um tensor é um recipiente para dados - quase sempre dados numéricos. Então, é
um contêiner para números. Você pode já estar familiarizado com matrizes, que são tensores
2D: tensores são uma generalização de matrizes para um número arbitrário de dimensões
(observe que, no contexto de tensores, uma dimensão é freqüentemente chamada de eixo ).

2.2.1. Escalas (tensores 0D)


Um tensor que contém apenas um número é chamado de escalar (ou tensor escalar, ou tensor
0-dimensional, ou tensor 0D). Em Numpy, um número float32ou float64é um tensor
escalar (ou matriz escalar). Você pode exibir o número de eixos de um tensor Numpy através
do ndimatributo; um tensor escalar tem 0 eixos ( ndim == 0). O número de eixos de um tensor
também é chamado de rank. Aqui está um escalar Numpy:

>>> import numpy como np

>>> x = np.array (12)

>>> x
matriz (12)

>>> x.ndim

2.2.2. Vetores (tensores 1D)


Uma matriz de números é chamada vetor ou tensor 1D. Um tensor 1D é dito ter exatamente um
eixo. A seguir, um vetor Numpy:

>>> x = np.array ([12, 3, 6, 14])

>>> x

matriz ([12, 3, 6, 14])

>>> x.ndim

Este vetor tem cinco entradas e, portanto, é chamado de vetor de 5 dimensões . Não confunda
um vetor 5D com um tensor 5D! Um vetor 5D possui apenas um eixo e possui cinco dimensões
ao longo de seu eixo, enquanto um tensor 5D possui cinco eixos (e pode ter qualquer número de
dimensões ao longo de cada eixo). A dimensionalidade pode denotar o número de entradas ao
longo de um eixo específico (como no caso do nosso vetor 5D) ou o número de eixos em um
tensor (como um tensor 5D), que pode ser confuso às vezes. Neste último caso, é tecnicamente
mais correto falar sobre um tensor de rank 5 (o rank de um tensor é o número de eixos), mas a
notação ambígua 5D tensor é comum independentemente.

2.2.3. Matrizes (tensores 2D)


Uma matriz de vetores é uma matriz ou um tensor 2D. Uma matriz possui dois eixos
(geralmente referenciados a linhas e colunas ). Você pode interpretar visualmente uma matriz
como uma grade retangular de números. Esta é uma matriz Numpy:

>>> x = np.array ([[5, 78, 2, 34, 0],

[6, 79, 3, 35, 1],

[7, 80, 4, 36, 2]])

>>> x.ndim

As entradas do primeiro eixo são chamadas de linhas e as entradas do segundo eixo são
chamadas de colunas . No exemplo anterior, [5, 78, 2, 34, 0]é a primeira linha de xe [5,
6, 7]é a primeira coluna.

2.2.4. Tensores 3D e tensores de maior dimensão


Se você compactar essas matrizes em uma nova matriz, obterá um tensor 3D, que pode ser
interpretado visualmente como um cubo de números. A seguir, um tensor 3D da Numpy:

>>> x = np.array ([[[5, 78, 2, 34, 0],

[6, 79, 3, 35, 1],


[7, 80, 4, 36, 2]],

[[5, 78, 2, 34, 0],

[6, 79, 3, 35, 1],

[7, 80, 4, 36, 2]],

[[5, 78, 2, 34, 0],

[6, 79, 3, 35, 1],

[7, 80, 4, 36, 2]]])

>>> x.ndim

Empacotando tensores 3D em uma matriz, você pode criar um tensor 4D e assim por diante. No
aprendizado profundo, você geralmente manipula tensores que são 0D a 4D, embora você possa
ir até 5D se processar dados de vídeo.

2.2.5. Chaves de atributo


Um tensor é definido por três atributos principais:

 Número de eixos (rank) - Por exemplo, um tensor 3D tem três eixos e uma matriz
possui dois eixos. Isso também é chamado de tensorndimem bibliotecas Python, como o
Numpy.
 Forma - Esta é uma tupla de inteiros que descreve quantas dimensões o tensor tem ao
longo de cada eixo. Por exemplo, o exemplo da matriz anterior tem forma(3, 5)e o
exemplo de tensor 3D tem forma(3, 3, 5). Um vetor tem uma forma com um único
elemento, como, por exemplo(5,), um escalar tem uma forma vazia().
 Tipo de dados (geralmente chamado dtypeem bibliotecas Python) - este é o tipo de
dados contidos no tensor; por exemplo, o tipo de um tensor poderia
ser float32, uint8, float64, e assim por diante. Em raras ocasiões, você pode ver
um chartensor. Observe que os tensores de string não existem no Numpy (ou na
maioria das outras bibliotecas), porque os tensores residem em segmentos de memória
pré-alocados e contíguos: e strings, com tamanho variável, impediriam o uso dessa
implementação.

Para tornar isso mais concreto, vamos olhar para os dados que processamos no exemplo
MNIST. Primeiro, nós carregamos o conjunto de dados MNIST:

de keras.datasets import mnist

(train_images, train_labels), (test_images, test_labels) = mnist.load_data ()

Em seguida, exibimos o número de eixos do tensor train_images, o ndimatributo:

>>> print (train_images.ndim)

Aqui está a sua forma:

>>> print (train_images.shape)

(60000, 28, 28)


E este é o seu tipo de dados, o dtypeatributo:

>>> print (train_images.dtype)

uint8

Então, o que temos aqui é um tensor 3D de inteiros de 8 bits. Mais precisamente, é uma matriz
de 60.000 matrizes de 28 × 8 inteiros. Cada uma dessas matrizes é uma imagem em tons de
cinza, com coeficientes entre 0 e 255.

Vamos mostrar o quarto dígito neste tensor 3D, usando a biblioteca Matplotlib (parte do pacote
científico padrão do Python); veja a figura 2.2 .

Figura 2.2 A quarta amostra do nosso conjunto de dados

Listagem 2.6. Exibindo o quarto dígito

dígito = train_images [4]

import matplotlib.pyplot como plt

plt.imshow (digit, cmap = plt.cm.binary)

plt.show ()

2.2.6. Manipulando tensores em Numpy


No exemplo anterior, selecionamos um dígito específico ao lado do primeiro eixo usando a
sintaxe train_images[i]. A seleção de elementos específicos em um tensor é chamada
de fatiamento do tensor . Vamos examinar as operações de fatiamento de tensor que você pode
fazer em matrizes Numpy.

O exemplo a seguir seleciona os dígitos de # 10 a # 100 (# 100 não está incluído) e os coloca em
uma matriz de forma (90, 28, 28):

>>> my_slice = train_images [10: 100]

>>> print (my_slice.shape)

(90, 28, 28)


É equivalente a essa notação mais detalhada, que especifica um índice inicial e um índice de
parada para a fatia ao longo de cada eixo do tensor. Observe que :é equivalente a selecionar o
eixo inteiro:

>>> my_slice = train_images [10: 100,:,:] 1

>>> my_slice.shape

(90, 28, 28)

>>> my_slice = train_images [10: 100, 0:28, 0:28] 2

>>> my_slice.shape

(90, 28, 28)

 1 Equivalente ao exemplo anterior


 2 Igualmente equivalente ao exemplo anterior

Em geral, você pode selecionar entre dois índices ao longo de cada eixo do tensor. Por exemplo,
para selecionar 14 × 14 pixels no canto inferior direito de todas as imagens, faça o seguinte:

my_slice = train_images [:, 14 :, 14:]

Também é possível usar índices negativos. Assim como os índices negativos nas listas do
Python, eles indicam uma posição relativa ao final do eixo atual. Para cortar as imagens em
patches de 14 × 14 pixels centralizados no meio, faça o seguinte:

my_slice = train_images [:, 7: -7, 7: -7]

2.2.7. A noção de lotes de dados


Em geral, o primeiro eixo (eixo 0, porque a indexação começa em 0) em todos os tensores de
dados que você encontrará em aprendizado profundo, será o eixo de amostras (às vezes
chamado de dimensão de amostras ). No exemplo MNIST, amostras são imagens de dígitos.

Além disso, os modelos de aprendizagem profunda não processam um conjunto de dados inteiro
de uma só vez; em vez disso, eles dividem os dados em pequenos lotes. Concretamente, aqui está
um lote de nossos dígitos MNIST, com tamanho de lote de 128:

batch = train_images [: 128]

E aqui está o próximo lote:

batch = train_images [128: 256]

E o nth lote:

batch = train_images [128 * n: 128 * (n + 1)]

Ao considerar esse tensor de lote, o primeiro eixo (eixo 0) é chamado de eixo de lote ou
de lote . Este é um termo que você encontrará frequentemente ao usar o Keras e outras
bibliotecas de aprendizado profundo.

2.2.8. Exemplos do mundo real de tensores de dados


Vamos tornar os tensores de dados mais concretos com alguns exemplos semelhantes aos que
você encontrará mais tarde. Os dados que você irá manipular quase sempre se enquadram em
uma das seguintes categorias:

 Dados vetoriais - tensores 2D de forma(samples, features)


 Dados de séries temporais ou dados de sequência - tensores 3D de
forma(samples, timesteps, features)
 Imagens - tensores 4D de forma(samples, height, width,
channels)ou(samples, channels, height, width)
 Vídeo - tensores 5D de forma(samples, frames, height, width,
channels)ou(samples, frames, channels, height, width)

2.2.9. Dados vetoriais


Este é o caso mais comum. Nesse conjunto de dados, cada ponto de dados único pode ser
codificado como um vetor e, assim, um lote de dados será codificado como um tensor 2D (ou
seja, uma matriz de vetores), onde o primeiro eixo é o eixo das amostras e o segundo eixo é o
eixo das características .

Vamos dar uma olhada em dois exemplos:

 Um conjunto de dados atuarial de pessoas, onde consideramos a idade, o CEP e a renda


de cada pessoa. Cada pessoa pode ser caracterizada como um vetor de 3 valores e,
assim, um conjunto de dados completo de 100.000 pessoas pode ser armazenado em
um tensor 2D de forma (100000, 3).
 Um conjunto de dados de documentos de texto, onde representamos cada documento
pela contagem de quantas vezes cada palavra aparece nele (de um dicionário de 20.000
palavras comuns). Cada documento pode ser codificado como um vetor de 20.000
valores (uma contagem por palavra no dicionário) e, assim, um conjunto de dados
inteiro de 500 documentos pode ser armazenado em um tensor de forma (500,
20000).

2.2.10. Dados de séries temporais ou dados de sequência


Sempre que o tempo é importante em seus dados (ou a noção de ordem de seqüência), faz
sentido armazená-lo em um tensor 3D com um eixo de tempo explícito. Cada amostra pode ser
codificada como uma seqüência de vetores (um tensor 2D) e, assim, um lote de dados será
codificado como um tensor 3D (ver figura 2.3 ).

Figura 2.3. Um tensor de dados de séries temporais em 3D

O eixo do tempo é sempre o segundo eixo (eixo do índice 1), por convenção. Vamos ver alguns
exemplos:

 Um conjunto de dados de preços de ações. A cada minuto, armazenamos o preço atual


da ação, o preço mais alto no último minuto e o preço mais baixo no último
minuto. Assim, cada minuto é codificado como um vetor 3D, um dia inteiro de
negociação é codificado como um tensor de forma 2D (390, 3)(há 390 minutos em
um dia de negociação), e 250 dias de dados podem ser armazenados em um tensor 3D
de forma (250, 390, 3). Aqui, cada amostra seria um dia de dados.
 Um conjunto de dados de tweets, onde codificamos cada tweet como uma sequência de
280 caracteres de um alfabeto de 128 caracteres únicos. Nessa configuração, cada
caractere pode ser codificado como um vetor binário de tamanho 128 (um vetor de
zeros, exceto para uma entrada no índice correspondente ao caractere). Então, cada
tweet pode ser codificado como um tensor 2D de forma (280, 128), e um conjunto de
dados de 1 milhão de tweets pode ser armazenado em um tensor de forma (1000000,
280, 128).

2.2.11. Dados da imagem


As imagens geralmente têm três dimensões: altura, largura e profundidade de cor. Embora as
imagens em escala de cinza (como os dígitos MNIST) possuam apenas um único canal de cores e
possam ser armazenadas em tensores 2D, por convenção os tensores de imagem são sempre 3D,
com um canal de cor unidimensional para imagens em escala de cinza. Um lote de 128 imagens
em escala de cinza de tamanho 256 × 256 poderia, portanto, ser armazenado em um tensor de
forma (128, 256, 256, 1), e um lote de 128 imagens coloridas poderia ser armazenado em
um tensor de forma (128, 256, 256, 3)(consulte a figura 2.4 ).

Figura 2.4. Um tensor de dados de imagem 4D (primeira convenção de canais)

Existem duas convenções para formas de tensores de imagens: a última convenção


de canais (usada pelo TensorFlow) e a convenção de canais-primeira (usada por Theano). A
estrutura da máquina-learning TensorFlow, do Google, coloca o eixo cor profundidade no
final: (samples, height, width, color_depth). Enquanto isso, Theano coloca o eixo
de profundidade de cor logo após o eixo de lote: (samples, color_depth, height,
width). Com a convenção de Theano, os exemplos anteriores se tornariam (128, 1, 256,
256)e (128, 3, 256, 256). A estrutura Keras fornece suporte para ambos os formatos.

2.2.12. Dados de vídeo


Os dados de vídeo são um dos poucos tipos de dados do mundo real para os quais você precisará
de tensores 5D. Um vídeo pode ser entendido como uma seqüência de quadros, cada quadro
sendo uma imagem colorida. Como cada quadro pode ser armazenado em um tensor
3D (height, width, color_depth), uma seqüência de quadros pode ser armazenada em
um tensor 4D (frames, height, width, color_depth)e, assim, um lote de vídeos
diferentes pode ser armazenado em um tensor de 5D de forma (-samples, frames,
height, width, color_depth).
Por exemplo, um videoclipe de 144 segundos e 144 x 256 vídeos do YouTube, com 4 quadros por
segundo, teria 240 quadros. Um lote de quatro desses videoclipes seria armazenado em um
tensor de forma (4, 240, 144, 256, 3). Isso é um total de 106,168,320 valores! Se
o dtypetensor fosse float32, então cada valor seria armazenado em 32 bits, então o tensor
representaria 405 MB. Pesado! Os vídeos que você encontra na vida real são muito mais leves,
porque não são armazenados float32e são geralmente compactados por um fator grande
(como no formato MPEG).

2.3. AS ENGRENAGENS DAS REDES NEURAIS: OPERAÇÕES TENSORIAIS

Assim como qualquer programa de computador pode ser reduzido a um pequeno conjunto de
operações binárias em entradas binárias (AND, OR, NOR e assim por diante), todas as
transformações aprendidas por redes neurais profundas podem ser reduzidas a um punhado
de operações tensorasaplicadas a tensores. de dados numéricos. Por exemplo, é possível
adicionar tensores, multiplicar tensores e assim por diante.

Em nosso exemplo inicial, estávamos construindo nossa rede empilhando Densecamadas umas
sobre as outras. Uma instância da camada Keras é assim:

keras.layers.Dense (512, activation = 'relu')

Essa camada pode ser interpretada como uma função, que toma como entrada um tensor 2D e
retorna outro tensor 2D - uma nova representação para o tensor de entrada. Especificamente, a
função é a seguinte (onde Wé um tensor 2D e bé um vetor, ambos os atributos da camada):

saída = relu (ponto (W, entrada) + b)

Vamos descompactar isso. Nós temos três operações de tensor aqui: um produto de ponto
( dot) entre o tensor de entrada e um tensor chamado W; uma adição ( +) entre o tensor 2D
resultante e um vetor b; e, finalmente, uma reluoperação. relu(x)é max(x, 0).

Nota

Embora esta seção lide inteiramente com expressões de álgebra linear, você não encontrará
nenhuma notação matemática aqui. Descobri que os conceitos matemáticos podem ser mais
facilmente dominados por programadores sem histórico matemático, se forem expressos como
trechos curtos do Python, em vez de equações matemáticas. Então, vamos usar o código Numpy
por toda parte.

2.3.1. Operações elementares


A reluoperação e a adição são operações elementares : operações que são aplicadas
independentemente a cada entrada nos tensores sendo considerados. Isso significa que essas
operações são altamente receptivas a implementações massivamente paralelas
(implementações vetorizadas , um termo que vem da arquitetura de supercomputadores
de processadores vetoriais do período 1970-1990). Se você quiser escrever uma implementação
ingênua do Python de uma operação baseada em elementos, use um forloop, como nessa
implementação ingênua de uma reluoperação baseada em elementos :

def naive_relu (x):

assert len (x.shape) == 2 1


x = x.copy () 2

para i no intervalo (x.shape [0]):

para j no intervalo (x.shape [1]):

x [i, j] = max (x [i, j], 0)

return x

 1 x é um tensor 2D numpy.
 2 Evite substituir o tensor de entrada.

Você faz o mesmo para adição:

def naive_add (x, y):

assert len (x.shape) == 2 1

assert x.shape == y.shape

x = x.copy () 2

para i no intervalo (x.shape [0]):

para j no intervalo (x.shape [1]):

x [i, j] + = y [i, j]

return x

 1 x e y são tensores 2D numpy.


 2 Evite substituir o tensor de entrada.

No mesmo princípio, você pode fazer multiplicação, subtração e assim por diante.

Na prática, ao lidar com arrays Numpy, essas operações estão disponíveis como funções Numpy
internas bem otimizadas, que delegam o trabalho pesado a uma implementação de
Subprogramas de Álgebra Linear Básica (BLAS) se você tiver uma instalada (o que deve). BLAS
são rotinas de manipulação de tensor de baixo nível, altamente paralelas e eficientes que são
tipicamente implementadas em Fortran ou C.

Então, no Numpy, você pode fazer a seguinte operação element-wise, e será muito rápido:

import numpy como np

z = x + y 1

z = np.maximum (z, 0.) 2

 1 adição elementar
 2 Relé elementar
2.3.2. Radiodifusão
Nossa implementação ingênua anterior naive_addsuporta apenas a adição de tensores 2D com
formas idênticas. Mas na Densecamada introduzida anteriormente, adicionamos um tensor 2D
com um vetor. O que acontece com a adição quando as formas dos dois tensores que estão sendo
adicionados diferem?

Quando possível, e se não houver ambigüidade, o menor tensor será transmitido para
corresponder à forma do maior tensor. Broadcasting consiste em duas etapas:

1. Eixos (chamados eixos de transmissão ) são adicionados ao menor tensor para


combinar ndimcom o maior tensor.
2. O menor tensor é repetido ao lado desses novos eixos para combinar com a forma
completa do tensor maior.

Vamos dar uma olhada em um exemplo concreto. Considere Xcom forma (32, 10)e ycom
forma (10,). Primeiro, adicionamos um primeiro eixo vazio a ycuja forma se torna (1,
10). Então, nós repetimos y32 vezes ao lado deste novo eixo, de modo que acabamos com um
tensor Ycom forma(32, 10), Y[i, :] == ypara onde iin range(0, 32). Neste ponto,
podemos continuar a adicionar Xe Y, porque eles têm a mesma forma.

Em termos de implementação, nenhum novo tensor 2D é criado, porque isso seria terrivelmente
ineficiente. A operação de repetição é inteiramente virtual: acontece no nível algorítmico e não
no nível da memória. Mas pensar no vetor sendo repetido 10 vezes ao lado de um novo eixo é
um modelo mental útil. Aqui está como uma implementação ingênua seria:

def naive_add_matrix_and_vector (x, y):

assert len (x.shape) == 2 1

afirmar len (y.shape) == 1 2

assert x.shape [1] == y.shape [0]

x = x.copy () 3

para i no intervalo (x.shape [0]):

para j no intervalo (x.shape [1]):

x [i, j] + = y [j]

return x

 1 x é um tensor 2D numpy.
 2 y é um vetor Numpy.
 3 Evite substituir o tensor de entrada.

Com a transmissão, você geralmente pode aplicar operações com elementos de dois tensores se
um tensor tiver forma (a, b, ... n, n + 1, ... m)e o outro tiver forma (n, n + 1,
... m). A transmissão irá então acontecer automaticamente para eixos aatravés n - 1.

O exemplo a seguir aplica a maximumoperação elementar a dois tensores de diferentes formas


por meio da transmissão:

import numpy como np


x = np.random.random ((64, 3, 32, 10)) 1

y = np.random.random ((32, 10)) 2

z = np.maximum (x, y) 3

 1 x é um tensor aleatório com forma (64, 3, 32, 10).


 2 y é um tensor aleatório com forma (32, 10).
 3 A saída z tem forma (64, 3, 32, 10) como x.

2.3.3. Ponto Tensor


A operação de ponto, também chamada de produto tensorial (não confundir com um produto
elementar) é a operação tensora mais comum e mais útil. Contrariamente às operações
elementares, combina entradas nos tensores de entrada.

Um produto element-wise é feito com o *operador em Numpy, Keras, Theano e


TensorFlow. dotusa uma sintaxe diferente no TensorFlow, mas tanto no Numpy quanto no
Keras é feito usando o dotoperador padrão :

import numpy como np

z = np.dot (x, y)

Em notação matemática, você notaria a operação com um ponto ( .):

z = x. y

Matematicamente, o que a operação de ponto faz? Vamos começar com o produto escalar de
dois vetores xe y. É calculado da seguinte forma:

def naive_vector_dot (x, y):

assert len (x.shape) == 1 1

afirmar len (y.shape) == 1 1

assert x.shape [0] == y.shape [0]

z = 0.

para i no intervalo (x.shape [0]):

z + = x [i] * y [i]

retorno z

 1 x e y são vetores numpy.

Você deve ter notado que o produto escalar entre dois vetores é escalar e que somente vetores
com o mesmo número de elementos são compatíveis para um produto escalar.
Você também pode pegar o produto de ponto entre uma matriz xe um vetor y, que retorna um
vetor onde os coeficientes são os produtos de ponto entre ye as linhas de x. Você implementa da
seguinte maneira:

import numpy como np

def naive_matrix_vector_dot (x, y):

assert len (x.shape) == 2 1

afirmar len (y.shape) == 1 2

assert x.shape [1] == y.shape [0] 3

z = np.zeros (x.shape [0]) 4

para i no intervalo (x.shape [0]):

para j no intervalo (x.shape [1]):

z [i] + = x [i, j] * y [j]

retorno z

 1 x é uma matriz numpy.


 2 y é um vetor Numpy.
 3 A primeira dimensão de x deve ser a mesma que a 0ª dimensão de y!
 4 Esta operação retorna um vetor de 0s com a mesma forma de y.

Você também pode reutilizar o código que escrevemos anteriormente, o que destaca a relação
entre um produto vetor de matriz e um produto vetorial:

def naive_matrix_vector_dot (x, y):

z = np.zeros (x.shape [0])

para i no intervalo (x.shape [0]):

z [i] = naive_vector_dot (x [i,:], y)

retorno z

Note que, assim que um dos dois tensores tem um valor ndimmaior que 1, dotnão é mais
simétrico, o que significa que dot(x, y)não é o mesmo que dot(y, x).

Naturalmente, um produto de ponto generaliza para tensores com um número arbitrário de


eixos. As aplicações mais comuns podem ser o produto escalar entre duas matrizes. Você pode
pegar o produto de ponto de duas matrizes xe y( dot(x, y)) se e somente se x.shape[1] ==
y.shape[0]. O resultado é uma matriz com forma (x.shape[0], y.shape[1]), onde os
coeficientes são os produtos vetoriais entre as linhas de xe as colunas de y. Aqui está a
implementação ingênua:

def naive_matrix_dot (x, y):

assert len (x.shape) == 2 1


afirma len (y.shape) == 2 1

afirma x.shape [1] == y.shape [0] 2

z = np.zeros ((x.shape [0], y.shape [1])) 3

para i na faixa (x.shape [0]): 4

para j na faixa (y.shape [1]): 5

row_x = x [i,:]

column_y = y [:, j]

z [i, j] = naive_vector_dot (linha_x, coluna_a)

retorno z

 1 x e y são matrizes Numpy.


 2 A primeira dimensão de x deve ser a mesma que a 0ª dimensão de y!
 3 Esta operação retorna uma matriz de 0s com uma forma específica.
 4 itera as linhas de x ...
 5 ... e sobre as colunas de y.

Para entender a compatibilidade das formas de pontos, é útil visualizar os tensores de entrada e
saída, alinhando-os conforme mostrado na figura 2.5 .

Figura 2.5. Diagrama de caixa de produto matricial

x, ye zsão retratados como retângulos (caixas literais de coeficientes). Como as linhas e xas
colunas de ydevem ter o mesmo tamanho, segue-se que a largura do xmostocoincidir com a
altura de y. Se você desenvolver novos algoritmos de aprendizado de máquina, provavelmente
estará desenhando esses diagramas com frequência.

Mais genericamente, você pode pegar o produto escalar entre tensores de dimensões mais altas,
seguindo as mesmas regras para compatibilidade de formas conforme descrito anteriormente
para o caso 2D:
(a, b, c, d). (d,) -> (a, b, c)

(a, b, c, d). (d, e) -> (a, b, c, e)

E assim por diante.

2.3.4. Remodelação do tensor


Um terceiro tipo de operação do tensor que é essencial para entender é o remodelamento do
tensor . Embora não tenha sido usado nas Densecamadas em nosso primeiro exemplo de rede
neural, usamos isso quando pré-processamos os dados de dígitos antes de alimentá-los em
nossa rede:

train_images = train_images.reshape ((60000, 28 * 28))

Remodelar um tensor significa reorganizar suas linhas e colunas para corresponder a uma
forma de destino. Naturalmente, o tensor reconfigurado tem o mesmo número total de
coeficientes que o tensor inicial. A reformulação é melhor compreendida através de exemplos
simples:

>>> x = np.array ([[0., 1.],

[2., 3.]

[4., 5.]])

>>> print (x.shape)

(3, 2)

>>> x = x.reshape ((6, 1))

>>> x

array ([[0],

[1.]

[2.]

[3.]

[4.]

[5.]])

>>> x = x.reshape ((2, 3))

>>> x

array ([[0., 1., 2.],

[3., 4., 5.]])


Um caso especial de remodelação que é comumente encontrado é
a transposição . Transpor uma matriz significa trocar suas linhas e suas colunas, de modo
que x[i, :]se torne x[:, i]:

>>> x = np.zeros ((300, 20)) 1

>>> x = np.transpose (x)

>>> print (x.shape)

(20, 300)

 1 Cria uma matriz de zeros de forma (300, 20)

2.3.5. Interpretação geométrica de operações tensoriais


Como o conteúdo dos tensores manipulados por operações de tensor pode ser interpretado
como coordenadas de pontos em algum espaço geométrico, todas as operações de tensor têm
uma interpretação geométrica. Por exemplo, vamos considerar a adição. Vamos começar com o
seguinte vetor:

A = [0,5, 1]

É um ponto em um espaço 2D (veja a figura 2.6 ). É comum imaginar um vetor como uma flecha
ligando a origem ao ponto, como mostra a figura 2.7 .

Figura 2.6 Um ponto em um espaço 2D

Figura 2.7. Um ponto em um espaço 2D retratado como uma flecha


Vamos considerar um novo ponto B = [1, 0.25], que adicionaremos ao anterior. Isto é feito
geometricamente encadeando as setas vetoriais, com a localização resultante sendo o vetor
representando a soma dos dois vetores anteriores (veja a figura 2.8 ).

Figura 2.8. Interpretação geométrica da soma de dois vetores

Em geral, operações geométricas elementares, como transformações afins, rotações,


escalonamento e assim por diante, podem ser expressas como operações de tensor. Por
exemplo, uma rotação de um vetor 2D por um ângulo teta pode ser obtida através de um
produto de ponto com uma matriz 2 × 2 R = [u, v], onde ue vambos são vetores do plano: u
= [cos(theta), sin(theta)]e v = [-sin(theta), cos(theta)].

2.3.6. Uma interpretação geométrica da aprendizagem profunda


Você acabou de aprender que as redes neurais consistem inteiramente de cadeias de operações
de tensor e que todas essas operações de tensor são apenas transformações geométricas dos
dados de entrada. Segue-se que você pode interpretar uma rede neural como uma
transformação geométrica muito complexa em um espaço de alta dimensão, implementada por
meio de uma longa série de etapas simples.

Em 3D, a seguinte imagem mental pode ser útil. Imagine duas folhas de papel colorido: uma
vermelha e outra azul. Coloque um em cima do outro. Agora, amassem juntos em uma pequena
bola. Essa bolinha de papel amassada são os dados de entrada, e cada folha de papel é uma
classe de dados em um problema de classificação. O que uma rede neural (ou qualquer outro
modelo de aprendizado de máquina) pretende fazer é descobrir uma transformação da bola de
papel que a descompactaria, de modo a tornar as duas classes novamente separáveis. Com o
aprendizado profundo, isso seria implementado como uma série de transformações simples do
espaço 3D, como aquelas que você poderia aplicar na esfera de papel com os dedos, um
movimento de cada vez.

Figura 2.9. Uncrumpling uma variedade complicada de dados

Desenrolando bolas de papel é o que a aprendizagem de máquina é: encontrar representações


claras para variedades de dados complexas e altamente dobradas. Neste ponto, você deve ter
uma boa intuição de por que o aprendizado profundo se sobressai nisso: é preciso uma
abordagem de decomposição gradual de uma transformação geométrica complicada em uma
longa cadeia de elementos elementares, que é basicamente a estratégia que um ser humano
seguiria para desenrole uma bola de papel. Cada camada em uma rede profunda aplica uma
transformação que desemaranha os dados um pouco - e uma pilha profunda de camadas torna
tratável um processo de desalinhamento extremamente complicado.

2.4. O MECANISMO DAS REDES NEURAIS: OTIMIZAÇÃO BASEADA EM


GRADIENTE

Como você viu na seção anterior, cada camada neural do nosso primeiro exemplo de rede
transforma seus dados de entrada da seguinte forma:

saída = relu (ponto (W, entrada) + b)

Nesta expressão, We bsão tensores que são atributos da camada. Eles são chamados
de pesos ou parâmetros treináveis da camada ( kernele os biasatributos,
respectivamente). Esses pesos contêm as informações aprendidas pela rede da exposição aos
dados de treinamento.

Inicialmente, essas matrizes de peso são preenchidas com pequenos valores aleatórios (uma
etapa chamada inicialização aleatória ). É claro que não há razão para esperar
que relu(dot(W, input) + b), quando We baleatoriamente, surjam representações
úteis. As representações resultantes não têm sentido, mas são um ponto de partida. O que vem a
seguir é ajustar gradualmente esses pesos, com base em um sinal de feedback. Esse ajuste
gradual, também chamado de treinamento , é basicamente o aprendizado da aprendizagem de
máquina.

Isso acontece dentro do que é chamado de loop de treinamento , que funciona da seguinte
maneira. Repita essas etapas em um loop, o quanto for necessário:

1. Desenhe um lote de amostras de treinamento xe alvos correspondentes y.


2. Execute a rede em x(uma etapa chamada o passe para frente ) para obter
previsões y_pred.
3. Calcule a perda da rede no lote, uma medida da incompatibilidade entre y_prede y.
4. Atualize todos os pesos da rede de uma maneira que reduza um pouco a perda nesse
lote.

Você acabará tendo uma rede que tem uma perda muito baixa em seus dados de treinamento:
uma baixa incompatibilidade entre as previsões y_prede os alvos esperados y. A rede aprendeu
a mapear suas entradas para corrigir os alvos. De longe, pode parecer mágica, mas quando você
reduz a passos elementares, acaba por ser simples.

A etapa 1 parece bastante fácil - apenas código de E / S. Os passos 2 e 3 são apenas a aplicação
de um punhado de operações tensoriais, para que você possa implementar essas etapas apenas
com base no que aprendeu na seção anterior. A parte difícil é o passo 4: atualizar os pesos da
rede. Dado um coeficiente de peso individual na rede, como você pode calcular se o coeficiente
deve ser aumentado ou diminuído e quanto?

Uma solução ingênua seria congelar todos os pesos na rede, exceto o coeficiente escalar que está
sendo considerado, e tentar valores diferentes para esse coeficiente. Digamos que o valor inicial
do coeficiente seja 0,3. Após o envio direto de um lote de dados, a perda da rede no lote é de
0,5. Se você alterar o valor do coeficiente para 0.35 e executar novamente o passo para frente, a
perda aumentará para 0.6. Mas se você diminuir o coeficiente para 0,25, a perda cai para
0,4. Nesse caso, parece que atualizar o coeficiente em -0,05contribuiria para minimizar a
perda. Isso teria que ser repetido para todos os coeficientes da rede.

Mas tal abordagem seria horrivelmente ineficiente, porque você precisaria calcular dois passes
para frente (que são caros) para cada coeficiente individual (dos quais existem muitos,
geralmente milhares e às vezes até milhões). Uma abordagem muito melhor é aproveitar o fato
de que todas as operações usadas na rede são diferenciáveis e calcular o gradiente da perda em
relação aos coeficientes da rede. Você pode então mover os coeficientes na direção oposta do
gradiente, diminuindo assim a perda.

Se você já sabe o que significa diferenciável e o que é um gradiente , você pode pular para
a seção 2.4.3 . Caso contrário, as duas seções a seguir ajudarão você a entender esses conceitos.

2.4.1. O que é um derivado?


Considere uma função contínua e suave f(x) = y, mapeando um número real xpara um novo
número real y. Como a função é contínua , uma pequena mudança xsó pode resultar em uma
pequena mudança y- é essa a intuição por trás da continuidade. Vamos dizer que você
aumenta xpor um fator pequeno epsilon_x: isso resulta em uma
pequena epsilon_ymudança para y:

f (x + epsilon_x) = y + epsilon_y

Além disso, como a função é suave (sua curva não possui ângulos abruptos),
quando epsilon_xé pequena o suficiente, em torno de um determinado ponto p, é possível
aproximar-se fcomo uma função linear de inclinação a, de modo que ela epsilon_yse torna a
* epsilon_x:

f (x + epsilon_x) = y + a * epsilon_x

Obviamente, esta aproximação linear é válida apenas quando xestá perto o suficiente p.

A inclinação aé chamado o derivado de fno p. Se afor negativo, isso significa que uma pequena
mudança xao redor presultará em uma diminuição f(x)(como mostrado na figura 2.10 ); e
se afor positivo, uma pequena alteração xresultará em um aumento de f(x). Além disso, o
valor absoluto de a(a magnitude da derivada) indica a rapidez com que esse aumento ou
diminuição ocorrerá.

Figura 2.10. Derivativo de femp

Para cada função diferenciável f(x)( diferenciável significa “pode ser derivado”: por exemplo,
funções suaves e contínuas podem ser derivadas), existe uma função derivada f'(x)que mapeia
valores xpara a inclinação da aproximação linear local fnaquelespontos. Por exemplo, a
derivada de cos(x)é -sin(x), a derivada de f(x) = a * xis f'(x) = ae assim por diante.

Se você está tentando atualizar xpor um fator epsilon_xpara minimizar f(x), e você sabe o
derivado de f, então seu trabalho está feito: a derivada descreve completamente
como f(x)evolui à medida que você muda x. Se você quiser reduzir o valor de f(x), você só
precisa se mover xum pouco na direção oposta da derivada.
2.4.2. Derivada de uma operação tensorial: o gradiente
Um gradiente é a derivada de uma operação tensorial. É a generalização do conceito de
derivativos para funções de entradas multidimensionais: isto é, para funções que tomam
tensores como entradas.

Considere um vetor de entrada x, uma matriz W, um destino ye uma função de perda loss. Você
pode usar Wpara calcular um candidato de destino y_prede calcular a perda ou
incompatibilidade entre o candidato de destino y_prede o destino y:

y_pred = dot (W, x)

loss_value = perda (y_pred, y)

Se as entradas de dados xe yestiverem congeladas, isso pode ser interpretado como uma função
mapeando valores Wpara valores de perda:

loss_value = f (W)

Vamos dizer que o valor atual de Wé W0. Então a derivada do fponto W0é um
tensor gradient(f)(W0)com a mesma forma W, onde cada coeficiente gradient(f)
(W0)[i, j]indica a direção e a magnitude da mudança loss_valueobservada durante a
modificação W0[i, j]. Esse tensor gradient(f)(W0)é o gradiente da função f(W) =
loss_valueem W0.

Você viu anteriormente que a derivada de uma função f(x)de um coeficiente único pode ser
interpretada como a inclinação da curva de f. Do mesmo modo, gradient(f)(W0)pode ser
interpretado como o tensor de descrever a curvatura de f(W)torno W0.

Por esta razão, da mesma maneira que, para uma função f(x), você pode reduzir o valor
de f(x)mover xum pouco na direção oposta da derivada, com uma função f(W)de um tensor,
você pode reduzir f(W)movendo-se Wna direção oposta de o gradiente: por exemplo, W1 = W0
- step * gradient(f)(W0)(onde stepé um pequeno fator de escala). Isso significa ir
contra a curvatura, que intuitivamente deve colocá-lo mais baixo na curva. Note que o fator de
escala stepé necessário porque gradient(f)(W0)apenas aproxima a curvatura quando você
está perto W0, então você não quer se afastar muito W0.

2.4.3. Descida de gradiente estocástica


Dada uma função diferenciável, é teoricamente possível encontrar seu mínimo analiticamente:
sabe-se que o mínimo de uma função é um ponto em que a derivada é 0, então tudo que você
precisa fazer é encontrar todos os pontos onde a derivada vai para 0 e verificar para qual destes
pontos, a função tem o valor mais baixo.

Aplicado a uma rede neural, isso significa encontrar analiticamente a combinação de valores de
peso que produza a menor função de perda possível. Isso pode ser feito resolvendo a
equação gradient(f)(W) = 0para W. Esta é uma equação polinomial de N variáveis,
onde N é o número de coeficientes na rede. Embora seja possível resolver tal equação para N = 2
ou N = 3, fazê-lo é intratável para redes neurais reais, onde o número de parâmetros nunca é
menor que alguns milhares e pode ser várias dezenas de milhões.

Em vez disso, você pode usar o algoritmo de quatro etapas descrito no início desta seção:
modifique os parâmetros pouco a pouco com base no valor de perda atual em um lote aleatório
de dados. Como você está lidando com uma função diferenciável, é possível calcular seu
gradiente, o que oferece uma maneira eficiente de implementar a etapa 4. Se você atualizar os
pesos na direção oposta do gradiente, a perda será um pouco menor a cada vez:
1. Desenhe um lote de amostras de treinamento xe alvos correspondentes y.
2. Execute a rede xpara obter previsões y_pred.
3. Calcule a perda da rede no lote, uma medida da incompatibilidade entre y_prede y.
4. Calcule o gradiente da perda em relação aos parâmetros da rede (um passo para trás ).
5. Mova os parâmetros um pouco na direção oposta do gradiente - por exemplo W = step
* gradient- reduzindo a perda no lote um pouco.

Bastante fácil! O que acabei de descrever é chamado de gradiente descendente estocástico


em mini-lote (mini-lote SGD). O termo estocástico refere-se ao fato de que cada lote de dados é
desenhado aleatoriamente (o estocástico é um sinônimo científico de aleatório ). A Figura
2.11 ilustra o que acontece em 1D, quando a rede tem apenas um parâmetro e você tem apenas
uma amostra de treinamento.

Figura 2.11. SGD abaixo de uma curva de perda 1D (um parâmetro que pode ser aprendido)

Como você pode ver, intuitivamente é importante escolher um valor razoável para
o stepfator. Se for muito pequeno, a descida da curva levará muitas iterações e poderá ficar
presa em um mínimo local. Se stepfor muito grande, suas atualizações podem levar você a
locais completamente aleatórios na curva.

Observe que uma variante do algoritmo mini-batch SGD seria desenhar uma única amostra e
destino em cada iteração, em vez de desenhar um lote de dados. Isso seria verdadeiro SGD (em
oposição ao mini-lote SGD). Alternativamente, indo ao extremo oposto, você poderia executar
todos os passos em todos os dados disponíveis, o que é chamado de lote SGD . Cada atualização
seria mais precisa, mas muito mais cara. O compromisso eficiente entre esses dois extremos é
usar mini-lotes de tamanho razoável.

Embora a figura 2.11 ilustre gradiente descendente em um espaço de parâmetro 1D, na prática
você usará gradiente descendente em espaços altamente dimensionais: cada coeficiente de peso
em uma rede neural é uma dimensão livre no espaço, e pode haver dezenas de milhares ou até
milhões deles. Para ajudá-lo a construir a intuição sobre superfícies de perda, você também
pode visualizar a descida de gradiente ao longo de uma superfície de perda 2D, como mostrado
na figura 2.12. Mas você não pode visualizar como o processo real de treinamento de uma rede
neural se parece - você não pode representar um espaço de 1.000.000 dimensões de uma
maneira que faça sentido para os seres humanos. Como tal, é bom ter em mente que as intuições
que você desenvolve através dessas representações de baixa dimensão nem sempre são precisas
na prática. Isso tem sido historicamente uma fonte de problemas no mundo da pesquisa em
aprendizagem profunda.
Figura 2.12. Gradiente descendo uma superfície de perda 2D (dois parâmetros aprendíveis)

Além disso, existem várias variantes do SGD que diferem levando em consideração as
atualizações de peso anteriores ao calcular a próxima atualização de peso, em vez de apenas
observar o valor atual dos gradientes. Há, por exemplo, o SGD com impulso, assim como o
Adagrad, o RMSProp e vários outros. Essas variantes são conhecidas como métodos de
otimização ou otimizadores . Em particular, o conceito de momento , que é usado em muitas
dessas variantes, merece sua atenção. Momentum aborda dois problemas com SGD: velocidade
de convergência e mínimos locais. Considere a figura 2.13 , que mostra a curva de uma perda
como uma função de um parâmetro de rede.

Figura 2.13. Um mínimo local e um mínimo global

Como você pode ver, em torno de um determinado valor de parâmetro, há um mínimo local :
em torno desse ponto, mover para a esquerda resultaria em aumento da perda, mas também se
moveria para a direita. Se o parâmetro em consideração estivesse sendo otimizado via SGD com
uma pequena taxa de aprendizado, então o processo de otimização ficaria preso no mínimo local
em vez de chegar ao mínimo global.

Você pode evitar esses problemas usando o momentum, que inspira-se na física. Uma imagem
mental útil aqui é pensar no processo de otimização como uma pequena bola rolando pela curva
de perda. Se tiver força suficiente, a bola não ficará presa em uma ravina e terminará no mínimo
global. O momento é implementado movendo a bola em cada etapa com base não apenas no
valor de inclinação atual (aceleração de corrente), mas também na velocidade atual (resultante
da aceleração passada). Na prática, isso significa atualizar o parâmetro com wbase não apenas
no valor do gradiente atual, mas também na atualização do parâmetro anterior, como nesta
implementação ingênua:
past_velocity = 0.

momento = 0,1 1

enquanto perda> 0,01: 2

w, perda, gradiente = get_current_parameters ()

velocity = past_velocity * momentum + learning_rate * gradiente

w = w + momentum * velocity - learning_rate * gradiente

past_velocity = velocidade

update_parameter (w)

 1 fator de momentum constante


 2 loop de otimização

2.4.4. Derivações de encadeamento: o algoritmo Backpropagation


No algoritmo anterior, assumimos casualmente que, como uma função é diferenciável, podemos
calcular explicitamente sua derivada. Na prática, uma função de rede neural consiste em muitas
operações de tensor encadeadas, cada uma das quais tem uma derivada simples e
conhecida. Por exemplo, esta é uma rede fcomposta por três operações de tensores, a, b, e c,
com matrizes de peso W1, W2e W3:

f (W1, W2, W3) = a (W1, b (W2, c (W3)))

Cálculo diz-nos que uma tal cadeia de funções pode ser derivado usando a seguinte identidade,
chamada regra da cadeia : f(g(x)) = f'(g(x)) * g'(x). A aplicação da regra da cadeia
ao cálculo dos valores de gradiente de uma rede neural dá origem a um algoritmo
chamadoRetropropagação (também às vezes chamado de diferenciação no modo reverso ). A
retropropagação começa com o valor da perda final e trabalha de volta das camadas superiores
para as camadas inferiores, aplicando a regra da cadeia para calcular a contribuição de cada
parâmetro no valor da perda.

Hoje em dia, e por muitos anos, as pessoas implementarão redes em estruturas modernas
capazes de diferenciação simbólica , como o TensorFlow. Isto significa que, dada uma cadeia de
operações com uma derivada conhecida, eles podem calcular uma função gradientepara a cadeia
(aplicando a regra da cadeia) que mapeia valores de parâmetros de rede para valores de
gradiente. Quando você tem acesso a essa função, a passagem para trás é reduzida para uma
chamada para essa função de gradiente. Graças à diferenciação simbólica, você nunca precisará
implementar o algoritmo Backpropagation manualmente. Por esse motivo, não
desperdiçaremos seu tempo e seu foco em derivar a formulação exata do algoritmo
Backpropagation nessas páginas. Tudo o que você precisa é de um bom entendimento de como
funciona a otimização baseada em gradiente.

2.5. OLHANDO PARA O NOSSO PRIMEIRO EXEMPLO

Você chegou ao final deste capítulo e agora você deve ter uma compreensão geral do que está
acontecendo nos bastidores de uma rede neural. Vamos voltar ao primeiro exemplo e revisar
cada peça à luz do que você aprendeu nas três seções anteriores.

Estes foram os dados de entrada:

(train_images, train_labels), (test_images, test_labels) = mnist.load_data ()


train_images = train_images.reshape ((60000, 28 * 28))

train_images = train_images.astype ('float32') / 255

test_images = test_images.reshape ((10000, 28 * 28))

test_images = test_images.astype ('float32') / 255

Agora você entende que as imagens de entrada são armazenadas em tensores de Numpy, que
são aqui formatados como float32tensores de forma (60000, 784)(dados de treinamento)
e (10000, 784)(dados de teste), respectivamente.

Esta foi a nossa rede:

rede = models.Sequential ()

network.add (layers.Dense (512, activation = 'relu', input_shape = (28 *


28,)))

network.add (layers.Dense (10, ativação = 'softmax'))

Agora você entende que essa rede consiste em uma cadeia de duas Densecamadas, que cada
camada aplica algumas operações de tensor simples aos dados de entrada e que essas operações
envolvem tensores de peso. Os tensores de peso, que são atributos das camadas, são onde
o conhecimento da rede persiste.

Este foi o passo de compilação de rede:

network.compile (optimizer = 'rmsprop',

perda = 'categorical_crossentropy',

métricas = ['precisão']]

Agora você entende que categorical_crossentropyé a função de perda que é usada como
um sinal de feedback para aprender os tensores de peso e que a fase de treinamento tentará
minimizar. Você também sabe que essa redução da perda acontece via gradiente estocástico em
mini-lote. As regras exatas que regem um uso específico do gradiente descendente são definidas
pelo rmspropotimizador passado como o primeiro argumento.

Finalmente, este foi o loop de treinamento:

network.fit (train_images, train_labels, epochs = 5, batch_size = 128)

Agora você entende o que acontece quando você liga fit: a rede começará a iterar nos dados de
treinamento em mini-lotes de 128 amostras, 5 vezes (cada iteração em todos os dados de
treinamento é chamada de época ). Em cada iteração, a rede calculará os gradientes dos pesos
em relação à perda no lote e atualizará os pesos de acordo. Após estas 5 épocas, a rede terá
realizado 2.345 atualizações de gradiente (469 por época), e a perda da rede será
suficientemente baixa para que a rede seja capaz de classificar dígitos manuscritos com alta
precisão.

Neste ponto, você já sabe muito do que há para saber sobre redes neurais.

Resumo do capítulo
 Aprender significa encontrar uma combinação de parâmetros do modelo que minimize
uma função de perda para um determinado conjunto de amostras de dados de
treinamento e seus destinos correspondentes.
 A aprendizagem acontece desenhando lotes aleatórios de amostras de dados e seus
alvos, e calculando o gradiente dos parâmetros de rede em relação à perda no lote. Os
parâmetros da rede são então movidos um pouco (a magnitude do movimento é
definida pela taxa de aprendizado) na direção oposta do gradiente.
 Todo o processo de aprendizado é possibilitado pelo fato de que as redes neurais são
cadeias de operações de tensores diferenciáveis e, portanto, é possível aplicar a regra de
derivação da cadeia para encontrar a função de gradiente mapeando os parâmetros
atuais e o lote atual de dados para um valor de gradiente.
 Dois conceitos-chave que você verá com frequência em capítulos futuros
são perda e otimizadores . Essas são as duas coisas que você precisa definir antes de
começar a alimentar os dados em uma rede.
 A perda é a quantidade que você tentará minimizar durante o treinamento, por isso
deve representar uma medida de sucesso para a tarefa que você está tentando resolver.
 O otimizador especifica a maneira exata na qual o gradiente da perda será usado para
atualizar parâmetros: por exemplo, pode ser o otimizador RMSProp, o SGD com
impulso e assim por diante.

Capítulo 3. Começando com redes neurais


Este capítulo cobre

 Componentes principais de redes neurais


 Uma introdução a Keras
 Configurando uma estação de trabalho de aprendizagem profunda
 Usando redes neurais para resolver problemas básicos de classificação e regressão

Este capítulo foi projetado para você começar a usar redes neurais para resolver problemas
reais. Você consolidará o conhecimento adquirido em nosso primeiro exemplo prático
no capítulo 2 e aplicará o que aprendeu a três novos problemas que abrangem os três casos de
uso mais comuns de redes neurais: classificação binária, classificação multiclasse e escalar.
regressão.

Neste capítulo, vamos dar uma olhada mais de perto nos principais componentes das redes
neurais que introduzimos no capítulo 2 : camadas, redes, funções objetivas e
otimizadores. Vamos dar uma rápida introdução a Keras, a biblioteca de aprendizado profundo
do Python que usaremos ao longo do livro. Você configurará uma estação de trabalho de
aprendizado profundo, com suporte a TensorFlow, Keras e GPU. Vamos mergulhar em três
exemplos introdutórios de como usar redes neurais para resolver problemas reais:

 Classificando críticas de filmes como positivas ou negativas (classificação binária)


 Classificação de notícias por tópico (classificação multiclasse)
 Estimando o preço de uma casa, dados de imóveis (regressão)

No final deste capítulo, você poderá usar redes neurais para resolver problemas simples de
máquinas, como classificação e regressão sobre dados vetoriais. Você então estará pronto para
começar a construir uma compreensão baseada em teoria, mais baseada em princípios, no
aprendizado de máquina no capítulo 4 .
3.1. ANATOMIA DE UMA REDE NEURAL

Como você viu nos capítulos anteriores, o treinamento de uma rede neural gira em torno dos
seguintes objetos:

 Camadas , que são combinadas em uma rede (ou modelo )


 Os dados de entrada e os destinos correspondentes
 A função de perda , que define o sinal de feedback usado para aprender
 O otimizador , que determina como o aprendizado ocorre

Você pode visualizar sua interação conforme ilustrado na figura 3.1 : a rede, composta de
camadas que estão encadeadas, mapeia os dados de entrada para as previsões. A função de
perda compara essas previsões com as metas, produzindo um valor de perda: uma medida de
quão bem as previsões da rede correspondem ao esperado. O otimizador usa esse valor de perda
para atualizar os pesos da rede.

Figura 3.1. Relação entre a rede, camadas, função de perda e otimizador

Vamos dar uma olhada mais de perto em camadas, redes, funções de perda e otimizadores.

3.1.1. Camadas: os blocos de construção do aprendizado profundo


A estrutura de dados fundamental em redes neurais é a camada na qual você foi introduzido
no capítulo 2 . Uma camada é um módulo de processamento de dados que recebe como entrada
um ou mais tensores e que gera um ou mais tensores. Algumas camadas são sem estado, mas
com mais frequência as camadas têm um estado: os pesos da camada , um ou vários tensores
aprendidos com a descida de gradiente estocástica, que juntos contêm o conhecimento da rede .

Diferentes camadas são apropriadas para diferentes formatos de tensores e diferentes tipos de
processamento de dados. Por exemplo, dados vetoriais simples, armazenados em tensores 2D de
forma (samples, features), são freqüentemente processados por camadas densamente
conectadas , também chamadas de camadas totalmente conectadas ou densas (a Denseclasse
em Keras). Os dados de sequência, armazenados em tensores 3D de forma (samples,
timesteps, features), são tipicamente processados por camadas recorrentes , como
uma LSTMcamada. Os dados de imagem, armazenados em tensores 4D, são geralmente
processados por camadas de convolução 2D ( Conv2D).
Você pode pensar em camadas como os blocos LEGO de aprendizagem profunda, uma metáfora
explicitada por estruturas como Keras. A criação de modelos de aprendizagem profunda em
Keras é feita agrupando camadas compatíveis para formar pipelines de transformação de dados
úteis. A noção de compatibilidade de camada aqui se refere especificamente ao fato de que toda
camada aceitará apenas tensores de entrada de uma certa forma e retornará tensores de saída de
uma certa forma. Considere o seguinte exemplo:

das camadas de importação keras

camada = layers.Dense (32, input_shape = (784,)) 1

 1 Uma camada densa com 32 unidades de saída

Estamos criando uma camada que aceitará apenas como tensores 2D de entrada, onde a
primeira dimensão é 784 (o eixo 0, a dimensão do lote, não é especificado e, portanto, qualquer
valor seria aceito). Essa camada retornará um tensor em que a primeira dimensão foi
transformada em 32.

Assim, esta camada só pode ser conectada a uma camada a jusante que espera vetores de 32
dimensões como sua entrada. Ao usar o Keras, você não precisa se preocupar com a
compatibilidade, porque as camadas que você adiciona aos seus modelos são construídas
dinamicamente para corresponder à forma da camada de entrada. Por exemplo, suponha que
você escreva o seguinte:

de modelos de importação keras

das camadas de importação keras

model = models.Sequential ()

model.add (layers.Dense (32, input_shape = (784,)))

model.add (layers.Dense (32))

A segunda camada não recebeu um argumento de forma de entrada - em vez disso, ele inferiu
automaticamente sua forma de entrada como sendo a forma de saída da camada que veio antes.

3.1.2. Modelos: redes de camadas


Um modelo de aprendizagem profunda é um gráfico acíclico direcionado de camadas. A
instância mais comum é uma pilha linear de camadas, mapeando uma única entrada para uma
única saída.

Mas à medida que você avança, você estará exposto a uma variedade muito maior de topologias
de rede. Alguns comuns incluem o seguinte:

 Redes de duas filiais


 Redes Multihead
 Blocos de criação

A topologia de uma rede define um espaço de hipótese . Você pode lembrar que no capítulo
1 definimos aprendizado de máquina como “procurando representações úteis de alguns dados
de entrada, dentro de um espaço predefinido de possibilidades, usando a orientação de um sinal
de feedback”. Ao escolher uma topologia de rede, você restringe seu espaço de
possibilidades (espaço de hipóteses) para uma série específica de operações de tensor,
mapeando dados dedados de saída. O que você estará procurando é um bom conjunto de valores
para os tensores de peso envolvidos nessas operações tensoras.

Escolher a arquitetura de rede correta é mais uma arte do que uma ciência; e embora existam
algumas práticas e princípios que você possa confiar, somente a prática pode ajudá-lo a se
tornar um arquiteto de rede neural adequado. Os próximos capítulos ensinarão a você princípios
explícitos para a construção de redes neurais e o ajudarão a desenvolver a intuição sobre o que
funciona ou não para problemas específicos.

3.1.3. Funções de perda e otimizadores: chaves para configurar o


processo de aprendizagem
Depois que a arquitetura de rede é definida, você ainda precisa escolher mais duas coisas:

 Função de perda (função objetivo) - A quantidade que será minimizada durante o


treinamento. Representa uma medida de sucesso para a tarefa em questão.
 Otimizador— Determina como a rede será atualizada com base na função de
perda. Ele implementa uma variante específica do gradiente estocástico de descida
(SGD).

Uma rede neural que possui múltiplas saídas pode ter múltiplas funções de perda (uma por
saída). Mas o processo gradiente-descendente deve ser baseado em um único valor de perda
escalar; Assim, para redes multiloss, todas as perdas são combinadas (via média) em uma única
grandeza escalar.

Escolher a função objetiva correta para o problema certo é extremamente importante: sua rede
aceitará qualquer atalho possível para minimizar a perda; Portanto, se o objetivo não se
correlacionar totalmente com o sucesso da tarefa em questão, sua rede acabará fazendo coisas
que você pode não querer. Imagine uma AI estúpida e onipotente treinada via SGD, com essa
função objetiva mal escolhida: “maximizar o bem-estar médio de todos os seres humanos vivos.”
Para tornar seu trabalho mais fácil, essa IA pode escolher matar todos os humanos, exceto
alguns, e se concentrar o bem-estar dos restantes - porque o bem-estar médio não é afetado pela
quantidade de humanos que restam. Isso pode não ser o que você pretendia!

Felizmente, quando se trata de problemas comuns, como classificação, regressão e predição de


seqüência, existem diretrizes simples que você pode seguir para escolher a perda correta. Por
exemplo, você usará crossentropy binária para um problema de classificação de duas classes,
crossentropy categórica para um problema de classificação de muitas classes, erro de média
quadrática para um problema de regressão, classificação temporal conexionista (CTC) para um
problema de aprendizado de seqüência e em breve. Somente quando você estiver trabalhando
em novos problemas de pesquisa, você terá que desenvolver suas próprias funções
objetivas. Nos próximos capítulos, detalharemos explicitamente quais funções de perda escolher
para uma ampla gama de tarefas comuns.

3.2. INTRODUÇÃO A KERAS

Ao longo deste livro, os exemplos de código usam Keras ( https://keras.io ). Keras é uma estrutura
de aprendizado profundo para Python que fornece uma maneira conveniente de definir e treinar
quase qualquer tipo de modelo de aprendizado profundo. Keras foi inicialmente desenvolvido
para pesquisadores, com o objetivo de permitir a experimentação rápida.

Keras possui os seguintes recursos principais:

 Ele permite que o mesmo código seja executado perfeitamente na CPU ou na GPU.
 Tem uma API amigável que facilita a prototipagem rápida de modelos de aprendizagem
profunda.
 Ele tem suporte embutido para redes convolucionais (para visão computacional), redes
recorrentes (para processamento sequencial) e qualquer combinação de ambos.
 Ele suporta arquiteturas de rede arbitrárias: modelos com múltiplas entradas ou
múltiplas saídas, compartilhamento de camadas, compartilhamento de modelos e assim
por diante. Isso significa que Keras é apropriado para construir essencialmente
qualquer modelo de aprendizagem profunda, de uma rede adversária generativa a uma
máquina de Turing neural.

Keras é distribuído sob a licença MIT permissiva, o que significa que pode ser usado livremente
em projetos comerciais. É compatível com qualquer versão do Python de 2.7 a 3.6 (a partir de
meados de 2017).

A Keras tem mais de 200.000 usuários, desde pesquisadores acadêmicos e engenheiros, tanto
de startups quanto de grandes empresas, até estudantes de pós-graduação e amadores. Keras é
usado no Google, Netflix, Uber, CERN, Yelp, Square e centenas de startups trabalhando em uma
ampla gama de problemas. Keras também é uma estrutura popular no Kaggle, o site de
competição de aprendizado de máquina, onde quase todas as competições recentes de
aprendizado profundo foram vencidas usando modelos Keras.

Figura 3.2. Interesse da pesquisa na web do Google por diferentes estruturas de aprendizagem profunda ao longo do tempo

3.2.1. Keras, TensorFlow, Theano e CNTK


Keras é uma biblioteca de nível de modelo, fornecendo blocos de construção de alto nível para o
desenvolvimento de modelos de aprendizagem profunda. Ele não lida com operações de baixo
nível, como manipulação e diferenciação de tensores. Em vez disso, ele conta com uma
biblioteca de tensores especializada e bem otimizada para fazê-lo, servindo como
o mecanismo de back-end de Keras. Ao invés de escolher uma biblioteca de tensores simples e
amarrar a implementação de Keras a essa biblioteca, Keras lida com o problema de uma
maneira modular (veja a figura 3.3).); Assim, vários motores backend diferentes podem ser
conectados perfeitamente em Keras. Atualmente, as três implementações de back-end existentes
são o backend TensorFlow, o back-end Theano e o back-end do Microsoft Cognitive Toolkit
(CNTK). No futuro, é provável que o Keras seja estendido para funcionar com mecanismos de
execução ainda mais profundos.
Figura 3.3. O software de aprendizado profundo e a pilha de hardware

TensorFlow, CNTK e Theano são algumas das principais plataformas de aprendizado profundo
hoje. Theano ( http://deeplearning.net/software/theano ) é desenvolvido pelo laboratório MILA
da Université de Montréal , o TensorFlow ( www.tensorflow.org ) é desenvolvido pelo Google e
pelo CNTK ( https://github.com/Microsoft / CNTK) é desenvolvido pela Microsoft. Qualquer pedaço
de código que você escreve com Keras pode ser executado com qualquer um desses backends
sem ter que alterar nada no código: você pode alternar facilmente entre os dois durante o
desenvolvimento, o que geralmente é útil - por exemplo, se um desses backends provar ser mais
rápido para uma tarefa específica. Recomendamos usar o backend do TensorFlow como padrão
para a maioria de suas necessidades de aprendizado profundo, porque ele é o mais amplamente
adotado, escalável e pronto para produção.

Via TensorFlow (ou Theano, ou CNTK), o Keras é capaz de rodar sem problemas em CPUs e
GPUs. Quando executado na CPU, o próprio TensorFlow está agrupando uma biblioteca de
baixo nível para operações de tensor chamada Eigen ( http://eigen.tuxfamily.org ). Na GPU, o
Tensor-Flow encapsula uma biblioteca de operações de aprendizagem profunda bem otimizadas
chamada biblioteca de rede neural profunda NVIDIA CUDA (cuDNN).

3.2.2. Desenvolvendo com Keras: uma visão geral rápida


Você já viu um exemplo de um modelo Keras: o exemplo MNIST. O fluxo de trabalho típico do
Keras se parece com esse exemplo:

1. Defina seus dados de treinamento: tensores de entrada e tensores de destino.


2. Defina uma rede de camadas (ou modelo ) que mapeie suas entradas para seus destinos.
3. Configure o processo de aprendizado escolhendo uma função de perda, um otimizador e
algumas métricas para monitorar.
4. Iterar em seus dados de treinamento, chamando o fit()método do seu modelo.

Há duas maneiras de definir um modelo: usando a Sequentialclasse (apenas para pilhas de


camadas lineares, que é a arquitetura de rede mais comum, de longe) ou a API funcional (para
gráficos acíclicos direcionados de camadas, que permitem construir arquiteturas completamente
arbitrárias) .

Como um lembrete, aqui está um modelo de duas camadas definido usando


a Sequentialclasse (note que estamos passando a forma esperada dos dados de entrada para a
primeira camada):

de modelos de importação keras

das camadas de importação keras

model = models.Sequential ()

model.add (layers.Dense (32, activation = 'relu', input_shape = (784,)))


model.add (layers.Dense (10, ativação = 'softmax'))

E aqui está o mesmo modelo definido usando a API funcional:

input_tensor = layers.Input (forma = (784,))

x = layers.Dense (32, activation = 'relu') (input_tensor)

output_tensor = layers.Dense (10, ativação = 'softmax') (x)

model = models.Model (inputs = input_tensor, outputs = output_tensor)

Com a API funcional, você está manipulando os tensores de dados que o modelo processa e
aplicando camadas a esse tensor como se fossem funções.

Nota

Um guia detalhado sobre o que você pode fazer com a API funcional pode ser encontrado
no capítulo 7 . Até o capítulo 7 , usaremos a Sequentialclasse apenas em nossos exemplos de
código.

Depois que sua arquitetura de modelo for definida, não importa se você usou
um Sequentialmodelo ou a API funcional. Todas as etapas a seguir são as mesmas.

O processo de aprendizado é configurado na etapa de compilação, na qual você especifica as


funções do otimizador e da perda que o modelo deve usar, bem como as métricas que deseja
monitorar durante o treinamento. Aqui está um exemplo com uma função de perda única, que é
de longe o caso mais comum:

de otimizadores de importação keras

model.compile (optimizer = optimizers.RMSprop (lr = 0.001),

perda = 'mse',

métricas = ['precisão']]

Finalmente, o processo de aprendizagem consiste em passar matrizes Numpy de dados de


entrada (e os dados de destino correspondentes) para o modelo através do fit()método,
semelhante ao que você faria no Scikit-Learn e em várias outras bibliotecas de aprendizado de
máquina:

model.fit (input_tensor, target_tensor, batch_size = 128, épocas = 10)

Nos próximos capítulos, você criará uma intuição sólida sobre que tipo de arquiteturas de rede
funciona para diferentes tipos de problemas, como escolher a configuração de aprendizado
correta e como ajustar um modelo até obter os resultados desejados. Examinaremos três
exemplos básicos nas seções 3.4 , 3.5 e 3.6 : um exemplo de classificação de duas classes, um
exemplo de classificação de muitas classes e um exemplo de regressão.
3.3. CONFIGURANDO UMA ESTAÇÃO DE TRABALHO DE APRENDIZAGEM
PROFUNDA

Antes de começar a desenvolver aplicativos de aprendizagem profunda, você precisa configurar


sua estação de trabalho. É altamente recomendado, embora não seja estritamente necessário,
que você execute código de aprendizado profundo em uma GPU NVIDIA moderna. Algumas
aplicações - em particular, o processamento de imagens com redes convolucionais e
processamento sequencial com redes neurais recorrentes - serão terrivelmente lentas na CPU,
mesmo em uma CPU multicore rápida. E mesmo para aplicativos que podem ser realisticamente
executados na CPU, você geralmente verá o aumento da velocidade por um fator ou 5 ou 10
usando uma GPU moderna. Se você não quiser instalar uma GPU em sua máquina, considere a
possibilidade de realizar suas experiências em uma instância da GPU do AWS EC2 ou no Google
Cloud Platform. Mas observe que as instâncias de GPU na nuvem podem se tornar caras ao
longo do tempo.

Se você está executando localmente ou na nuvem, é melhor usar uma estação de trabalho
Unix. Embora seja tecnicamente possível usar o Keras no Windows (todos os três back-ends do
Keras suportam o Windows), nós não o recomendamos. Nas instruções de instalação no apêndice
A , vamos considerar uma máquina Ubuntu. Se você é um usuário do Windows, a solução mais
simples para fazer tudo funcionar é configurar uma inicialização dupla do Ubuntu na sua
máquina. Pode parecer um incômodo, mas usar o Ubuntu economizará muito tempo e
problemas a longo prazo.

Note-se que, a fim de usar Keras, você precisa instalar TensorFlow ou CNTK ou Theano (ou
todos eles, se você quer ser capaz de alternar entre os três backends). Neste livro, vamos nos
concentrar no TensorFlow, com algumas instruções sobre o Theano. Nós não vamos cobrir o
CNTK.

3.3.1. Notebooks Jupyter: a maneira preferida de realizar


experimentos de aprendizado profundo
Os notebooks Jupyter são uma ótima maneira de executar experimentos de aprendizado
profundo - em particular, os muitos exemplos de código deste livro. Eles são amplamente
utilizados nas comunidades de ciência de dados e aprendizado de máquina. Um notebook é um
arquivo gerado pelo aplicativo Jupyter Notebook ( https://jupyter.org ), que você pode editar no
seu navegador. Ele mistura a capacidade de executar código Python com recursos de edição de
texto para anotar o que você está fazendo. Um notebook também permite que você quebrar
experimentos longos em pedaços menores que podem ser executados de forma independente, o
que torna o desenvolvimento interativo e significa que você não tem que executar novamente
todo o seu código anterior se algo der errado no final de um experimento.

Recomendamos usar os notebooks Jupyter para começar a usar o Keras, embora isso não seja
um requisito: você também pode executar scripts Python autônomos ou executar código de
dentro de um IDE como o PyCharm. Todos os exemplos de código deste livro estão disponíveis
como cadernos de código aberto; você pode baixá-los no site do livro
em www.manning.com/books/deep-learning-with-python.

3.3.2. Obtendo Keras executando: duas opções


Para começar na prática, recomendamos uma das duas opções a seguir:

 Use o oficial EC2 Deep Learning AMI ( https://aws.amazon.com/amazon-ai/amis ) e execute


experiências Keras como notebooks Jupyter no EC2. Faça isso se você ainda não tiver
uma GPU em sua máquina local. O Apêndice B fornece um guia passo a passo.
 Instale tudo do zero em uma estação de trabalho Unix local. Você pode então executar
os notebooks Jupyter locais ou uma base de código Python regular. Faça isso se você já
tiver uma GPU NVIDIA de ponta. O Apêndice A fornece um guia passo a passo específico
do Ubuntu.

Vamos dar uma olhada em alguns dos compromissos envolvidos na escolha de uma opção sobre
a outra.

3.3.3. Executando trabalhos de aprendizado profundo na nuvem: prós


e contras
Se você ainda não tem uma GPU que possa ser usada para aprendizado profundo (uma GPU
NVIDIA recente e de alta qualidade), a execução de experimentos de aprendizagem profunda na
nuvem é uma maneira simples e econômica de começar sem ter que comprar qualquer hardware
adicional. Se você estiver usando os notebooks Jupyter, a experiência de executar na nuvem não
é diferente da execução local. A partir de meados de 2017, a oferta de nuvem que facilita o início
da aprendizagem profunda é definitivamente o AWS EC2. O Apêndice B fornece um guia passo a
passo para executar os notebooks Jupyter em uma instância de GPU EC2.

Mas se você é um usuário pesado de aprendizado profundo, essa configuração não é sustentável
a longo prazo - ou mesmo por mais de algumas semanas. As instâncias do EC2 são caras: o tipo
de instância recomendado no apêndice B (a p2.xlargeinstância, que não fornecerá muita
energia) custa US $ 0,90 por hora a partir de meados de 2017. Enquanto isso, uma sólida GPU
de classe de consumidor custará entre US $ 1.000 e US $ 1.500 - um preço que tem sido
bastante estável ao longo do tempo, mesmo com as especificações das GPUs melhorando. Se
você é sério sobre aprendizagem profunda, você deve configurar uma estação de trabalho local
com uma ou mais GPUs.

Em suma, o EC2 é uma ótima maneira de começar. Você poderia seguir os exemplos de código
deste livro inteiramente em uma instância da GPU do EC2. Mas se você for um usuário
experiente em aprendizado profundo, adquira suas próprias GPUs.

3.3.4. Qual é a melhor GPU para aprendizado profundo?


Se você vai comprar uma GPU, qual você deve escolher? A primeira coisa a notar é que deve ser
uma GPU NVIDIA. A NVIDIA é a única empresa de computação gráfica que investiu
pesadamente em aprendizado profundo até o momento, e as modernas estruturas de
aprendizado profundo só podem ser executadas em placas NVIDIA.

A partir de meados de 2017, recomendamos o NVIDIA TITAN Xp como o melhor cartão no


mercado para aprendizado profundo. Para orçamentos mais baixos, você pode considerar a GTX
1060. Se você estiver lendo essas páginas em 2018 ou mais tarde, reserve um tempo para
consultar on-line as recomendações mais recentes, pois novos modelos são lançados todos os
anos.

A partir desta seção, vamos supor que você tenha acesso a uma máquina com o Keras e suas
dependências instaladas - de preferência com suporte a GPU. Certifique-se de terminar este
passo antes de prosseguir. Siga os guias passo a passo nos apêndices e procure on-line se
precisar de mais ajuda. Não há escassez de tutoriais sobre como instalar o Keras e as
dependências comuns de aprendizagem profunda.

Podemos agora mergulhar em exemplos práticos de Keras.

3.4. CLASSIFICANDO RESENHAS DE FILMES: UM EXEMPLO DE CLASSIFICAÇÃO


BINÁRIA

A classificação de duas classes, ou classificação binária, pode ser o tipo de problema de


aprendizado de máquina mais amplamente aplicado. Neste exemplo, você aprenderá a
classificar as resenhas de filmes como positivas ou negativas, com base no conteúdo de texto das
resenhas.

3.4.1. O conjunto de dados do IMDB


Você trabalhará com o conjunto de dados do IMDB: um conjunto de 50.000 avaliações
altamente polarizadas do Internet Movie Database. Eles são divididos em 25.000 avaliações
para treinamento e 25.000 avaliações para testes, cada conjunto consistindo em 50% de
avaliações negativas e 50% positivas.

Por que usar treinamentos e conjuntos de testes separados? Porque você nunca deve testar um
modelo de aprendizado de máquina nos mesmos dados que você usou para treiná-lo! Só porque
um modelo tem um bom desempenho em seus dados de treinamento, não significa que ele
funcionará bem em dados que nunca viu; e o que importa é o desempenho de seu modelo em
novos dados (porque você já conhece os rótulos de seus dados de treinamento - obviamente,
você não precisa do seu modelo para prevê-los). Por exemplo, é possível que seu modelo possa
simplesmente memorizar um mapeamento entre suas amostras de treinamento e seus alvos, o
que seria inútil para a tarefa de prever alvos para dados que o modelo nunca viu
antes. Analisaremos esse ponto com muito mais detalhes no próximo capítulo.

Assim como o conjunto de dados MNIST, o conjunto de dados do IMDB é fornecido com o
Keras. Já foi pré-processado: as revisões (seqüências de palavras) foram transformadas em
sequências de inteiros, onde cada inteiro representa uma palavra específica em um dicionário.

O código a seguir carregará o conjunto de dados (quando você executá-lo pela primeira vez,
cerca de 80 MB de dados serão baixados para sua máquina).

Listagem 3.1. Carregando o conjunto de dados do IMDB

de keras.datasets import imdb

(train_data, train_labels), (test_data, test_labels) = imdb.load_data (

num_words = 10000)

O argumento num_words=10000significa que você só manterá as 10 mil palavras mais


frequentes nos dados de treinamento. Palavras raras serão descartadas. Isso permite que você
trabalhe com dados vetoriais de tamanho gerenciável.

As variáveis train_datae test_datasão listas de revisões; cada revisão é uma lista de índices
de palavras (codificando uma sequência de palavras). train_labelse test_labelssão listas
de 0s e 1s, onde 0 significa negativo e 1 significa positivo :

>>> train_data [0]

[1, 14, 22, 16, ... 178, 32]

>>> train_labels [0]

Como você está se restringindo às 10.000 palavras mais frequentes, nenhum índice de palavras
excederá 10.000:

>>> max ([max (sequência) para sequência em train_data])


9999

Para chutes, veja como você pode decodificar rapidamente um desses comentários de volta para
as palavras em inglês:

word_index = imdb.get_word_index () 1

reverse_word_index = dict (

[(valor, chave) para (chave, valor) em word_index.items ()]) 2

decoded_review = '' .join (

[reverse_word_index.get (i - 3, '?') para i em train_data [0]]) 3

 1 word_index é um dicionário que mapeia palavras para um índice inteiro.


 2 Inverte-o, mapeando índices inteiros para palavras
 3 Decodifica a revisão. Observe que os índices são compensados por 3
porque 0, 1 e 2 são índices reservados para “preenchimento”, “início da
sequência” e “desconhecido”.

3.4.2. Preparando os dados


Você não pode alimentar listas de inteiros em uma rede neural. Você tem que transformar suas
listas em tensores. Existem duas maneiras de fazer isso:

 Pad suas listas para que todos tenham o mesmo comprimento, transformá-los em um
tensor inteiro de forma (samples, word_indices)e, em seguida, usar como
primeira camada em sua rede uma camada capaz de lidar com tais tensores inteiros
(a Embeddingcamada, que abordaremos em detalhes mais adiante no livro).
 Um hot codificar suas listas para transformá-los em vetores de 0s e 1s. Isso significaria,
por exemplo, transformar a sequência [3, 5]em um vetor de 10.000 dimensões que
seria todos os 0s, exceto os índices 3 e 5, que seriam 1s. Então você poderia usar como a
primeira camada em sua rede uma Densecamada, capaz de manipular dados vetoriais
de ponto flutuante.

Vamos com a última solução para vetorizar os dados, o que você fará manualmente para
máxima clareza.

Listagem 3.2. Codificando as seqüências inteiras em uma matriz binária

import numpy como np

def vectorize_sequences (sequências, dimensão = 10000):

results = np.zeros ((len (sequências), dimensão)) 1

para i, sequência em enumerar (sequências):

resultados [i, sequência] = 1. 2

retornar resultados

x_train = vectorize_sequences (train_data) 3


x_test = vetorize_sequences (test_data) 4

 1 Cria uma matriz de forma toda zero (len (sequências), dimensão)


 2 Define índices específicos de resultados [i] a 1s
 3 Dados de treinamento vetorizados
 4 Dados de teste vetorizados

Veja como são as amostras agora:

>>> x_train [0]

array ([0., 1., 1., ..., 0., 0., 0.])

Você também deve vetorizar suas etiquetas, o que é simples:

y_train = np.asarray (train_labels) .astype ('float32')

y_test = np.asarray (test_labels) .astype ('float32')

Agora os dados estão prontos para serem alimentados em uma rede neural.

3.4.3. Construindo sua rede


Os dados de entrada são vetores e os rótulos são escalares (1s e 0s): essa é a configuração mais
fácil que você encontrará. Um tipo de rede que tem um bom desempenho em um tal problema é
uma simples pilha de totalmente ligados ( Dense) camadas
com reluactivações: Dense(16,activation='relu').

O argumento sendo passado para cada Densecamada (16) é o número de unidades ocultas da
camada. Uma unidade oculta é uma dimensão no espaço de representação da camada. Você
pode se lembrar do capítulo 2 que cada Densecamada com uma reluativação implementa a
seguinte cadeia de operações de tensor:

saída = relu (ponto (W, entrada) + b)

Ter 16 unidades ocultas significa que a matriz de peso Wterá forma (input_dimension, 16):
o produto de ponto com Wprojetará os dados de entrada em um espaço de representação de 16
dimensões (e, em seguida, você adicionará o vetor de polarização be aplicará
a reluoperação). Você pode entender intuitivamente a dimensionalidade de seu espaço de
representação como “quanta liberdade você está permitindo que a rede tenha ao aprender
representações internas.” Ter unidades mais ocultas (um espaço de representação mais alta)
permite que sua rede aprenda representações mais complexas , mas torna a rede mais
computacionalmente cara e pode levar ao aprendizado de padrões indesejados (padrões que
melhorarão o desempenho nos dados de treinamento, mas não nos dados de teste).

Há duas decisões de arquitetura importantes a serem feitas sobre essa pilha de Densecamadas:

 Quantas camadas usar


 Quantas unidades ocultas escolher para cada camada

No capítulo 4 , você aprenderá princípios formais para guiá-lo a fazer essas escolhas. Por
enquanto, você terá que confiar em mim com a seguinte opção de arquitetura:

 Duas camadas intermediárias com 16 unidades ocultas cada


 Uma terceira camada que produzirá a previsão escalar em relação ao sentimento da
revisão atual
As camadas intermediárias usarão relucomo sua função de ativação, e a camada final usará
uma ativação sigmóide para gerar uma probabilidade (uma pontuação entre 0 e 1, indicando a
probabilidade de a amostra ter o alvo “1”: qual a probabilidade revisão deve ser
positiva). A relu(unidade linear retificada) é uma função destinada a zerar valores negativos
(veja a figura 3.4 ), enquanto um sigmoide “esmaga” valores arbitrários no [0, 1]intervalo
(veja a figura 3.5 ), gerando algo que pode ser interpretado como uma probabilidade.

Figura 3.4. A função de unidade linear retificada


Figura 3.5 A função sigmóide

A figura 3.6 mostra como é a rede. E aqui está a implementação Keras, semelhante ao exemplo
MNIST que você viu anteriormente.

Figura 3.6 A rede de três camadas

Listagem 3.3. A definição do modelo

de modelos de importação keras

das camadas de importação keras


model = models.Sequential ()

model.add (layers.Dense (16, activation = 'relu', input_shape = (10000,))))

model.add (layers.Dense (16, activation = 'relu'))

model.add (layers.Dense (1, activation = 'sigmoid'))

Quais são as funções de ativação e por que elas são necessárias?

Sem uma função de ativação como relu(também chamada de não-linearidade ),


a Densecamada consistiria em duas operações lineares - um produto de ponto e uma adição:

saída = ponto (W, entrada) + b

Assim, a camada só poderia aprender transformações lineares ( transformações afins) dos


dados de entrada: o espaço de hipótese da camada seria o conjunto de todas as transformações
lineares possíveis dos dados de entrada em um espaço de 16 dimensões. Esse espaço de hipótese
é muito restrito e não se beneficiaria de várias camadas de representações, porque uma pilha
profunda de camadas lineares ainda implementaria uma operação linear: adicionar mais
camadas não estenderia o espaço da hipótese.

Para ter acesso a um espaço de hipóteses muito mais rico que se beneficiaria de representações
profundas, você precisa de uma não-linearidade ou função de ativação. relué a função de
ativação mais popular na aprendizagem profunda, mas há muitos outros candidatos, que
contam com nomes semelhante estranho: prelu, elue assim por diante.

Finalmente, você precisa escolher uma função de perda e um otimizador. Como você está
enfrentando um problema de classificação binária e a saída da sua rede é uma probabilidade
(você termina sua rede com uma camada de unidade única com uma ativação sigmóide), é
melhor usarbinary_crossentropyperda. Não é a única opção viável: você poderia usar, por
exemplo mean_squared_error. Mas crossentropy é geralmente a melhor escolha quando
você está lidando com modelos que geram probabilidades. Crossentropy é uma quantidade do
campo da Teoria da Informação que mede a distância entre as distribuições de probabilidade
ou, neste caso, entre a distribuição da verdade e suas predições.

Aqui está a etapa em que você configura o modelo com o rmspropotimizador e


a binary_crossentropyfunção de perda. Observe que você também monitorará a precisão
durante o treinamento.

Listagem 3.4. Compilando o modelo

model.compile (optimizer = 'rmsprop',

perda = 'binary_crossentropy',

métricas = ['precisão']]

Você está passando o seu otimizador, função de perda, e métricas como cordas, que é possível
porque rmsprop, binary_crossentropye accuracysão empacotados como parte de
Keras. Às vezes você pode querer configurar os parâmetros do seu otimizador ou passar uma
função de perda personalizada ou função métrica. O primeiro pode ser feito passando uma
instância de classe do otimizador como optimizerargumento, conforme mostrado na listagem
3.5 ; o último pode ser feito passando objetos de função como losse / ou metricsargumentos,
como mostrado na listagem 3.6 .
Listagem 3.5. Configurando o otimizador

de otimizadores de importação keras

model.compile (optimizer = optimizers.RMSprop (lr = 0.001),

perda = 'binary_crossentropy',

métricas = ['precisão']]

Listagem 3.6. Usando perdas e métricas personalizadas

das perdas de importação keras

de métricas de importação de keras

model.compile (optimizer = optimizers.RMSprop (lr = 0.001),

loss = perdas.binary_crossentropy,

métricas = [metrics.binary_accuracy])

3.4.4. Validando sua abordagem


Para monitorar durante o treinamento a precisão do modelo em dados nunca antes vistos, você
criará um conjunto de validação ao separar 10.000 amostras dos dados de treinamento
originais.

Listagem 3.7. Deixando de lado um conjunto de validação

x_val = x_train [: 10000]

partial_x_train = x_train [10000:]

y_val = y_train [: 10000]

partial_y_train = y_train [10000:]

Agora você vai treinar o modelo por 20 épocas (20 iterações sobre todas as amostras
no x_traine y_traintensores), em mini-lotes de 512 amostras. Ao mesmo tempo, você
monitorará a perda e a precisão nas 10.000 amostras que você separou. Você faz isso passando
os dados de validação como o validation_dataargumento.

Listagem 3.8. Treinando seu modelo

model.compile (optimizer = 'rmsprop',

perda = 'binary_crossentropy',

metrics = ['acc'])

history = model.fit (partial_x_train,

partial_y_train,
épocas = 20,

batch_size = 512,

validation_data = (x_val, y_val))

Na CPU, isso levará menos de 2 segundos por época - o treinamento termina em 20


segundos. No final de cada época, há uma pequena pausa enquanto o modelo calcula sua perda e
precisão nas 10.000 amostras dos dados de validação.

Observe que a chamada model.fit()retorna um Historyobjeto. Este objeto tem um


membro history, que é um dicionário que contém dados sobre tudo o que aconteceu durante o
treinamento. Vamos dar uma olhada nisso:

>>> history_dict = history.history

>>> history_dict.keys ()

[u'acc ', u'loss', u'val_acc ', u'val_loss']

O dicionário contém quatro entradas: uma por métrica que estava sendo monitorada durante o
treinamento e durante a validação. Nas duas listas a seguir, vamos usar o Matplotlib para plotar
as perdas de treinamento e validação lado a lado (ver figura 3.7 ), bem como a precisão do
treinamento e validação (ver figura 3.8 ). Observe que seus próprios resultados podem variar um
pouco devido a uma inicialização aleatória diferente da sua rede.

Figura 3.7. Perda de treinamento e validação


Figura 3.8. Precisão de treinamento e validação

Listagem 3.9. Traçando a perda de treinamento e validação

import matplotlib.pyplot como plt

history_dict = history.history

loss_values = history_dict ['perda']

val_loss_values = history_dict ['val_loss']

épocas = intervalo (1, len (acc) + 1)

plt.plot (epochs, loss_values, 'bo', label = 'perda de treino') 1

plt.plot (epochs, val_loss_values, 'b', label = 'perda de validação') 2

plt.title ('Perda de treinamento e validação')

plt.xlabel ('Épocas')

plt.ylabel ('Loss')

plt.legend ()

plt.show ()

 1 "bo" é para "ponto azul".


 2 “b” é para “linha azul sólida”.

Listagem 3.10. Plotando a precisão de treinamento e validação

plt.clf () 1
acc_values = history_dict ['acc']

val_acc_values = history_dict ['val_acc']

plt.plot (epochs, acc, 'bo', label = 'Treinar acc')

plt.plot (epochs, val_acc, 'b', label = 'Validação acc')

plt.title ('Precisão de treinamento e validação')

plt.xlabel ('Épocas')

plt.ylabel ('Loss')

plt.legend ()

plt.show ()

 1 Limpa a figura

Como você pode ver, a perda de treinamento diminui a cada época, e a precisão do treinamento
aumenta a cada época. Isso é o que você esperaria ao executar a otimização de gradiente-descida
- a quantidade que você está tentando minimizar deve ser menor a cada iteração. Mas esse não é
o caso da perda e precisão da validação: elas parecem atingir o pico na quarta época. Este é um
exemplo do que alertamos anteriormente: um modelo que tenha um desempenho melhor nos
dados de treinamento não é necessariamente um modelo que funcionará melhor em dados
nunca antes vistos. Em termos precisos, o que você está vendo é overfitting: após a segunda
época, você está super otimizando os dados de treinamento e acaba aprendendo representações
específicas dos dados de treinamento e não generaliza os dados fora do conjunto de
treinamento.

Neste caso, para evitar overfitting, você pode parar de treinar após três épocas. Em geral, você
pode usar uma variedade de técnicas para atenuar o overfitting, que abordaremos no capítulo 4 .

Vamos treinar uma nova rede a partir do zero por quatro épocas e depois avaliá-la nos dados de
teste.

Listagem 3.11. Reciclagem de um modelo a partir do zero

model = models.Sequential ()

model.add (layers.Dense (16, activation = 'relu', input_shape = (10000,))))

model.add (layers.Dense (16, activation = 'relu'))

model.add (layers.Dense (1, activation = 'sigmoid'))

model.compile (optimizer = 'rmsprop',

perda = 'binary_crossentropy',

métricas = ['precisão']]
model.fit (x_train, y_train, epochs = 4, batch_size = 512)

results = model.evaluate (x_test, y_test)

Os resultados finais são os seguintes:

>>> resultados

[0.2929924130630493, 0.88327999999999995]

Essa abordagem bastante ingênua alcança uma precisão de 88%. Com abordagens de última
geração, você deve conseguir aproximar-se de 95%.

3.4.5. Usando uma rede treinada para gerar previsões sobre novos
dados
Depois de ter treinado uma rede, você vai querer usá-lo em um ambiente prático. Você pode
gerar a probabilidade de as análises serem positivas usando o predictmétodo:

>>> model.predict (x_test)

array ([[0.98006207]

[0.99758697]

[0,99975556]

...

[0,82167041]

[0,02885115]

[0.65371346]], dtype = float32)

Como você pode ver, a rede está confiante para algumas amostras (0,99 ou mais, ou 0,01 ou
menos), mas menos confiante para outras (0,6, 0,4).

3.4.6. Outras experiências


Os experimentos a seguir ajudarão a convencê-lo de que as escolhas de arquitetura que você fez
são bastante razoáveis, embora ainda haja espaço para melhorias:

 Você usou duas camadas ocultas. Tente usar uma ou três camadas ocultas e veja como
isso afeta a validação e a precisão do teste.
 Tente usar camadas com mais unidades ocultas ou menos unidades ocultas: 32
unidades, 64 unidades e assim por diante.
 Tente usar a msefunção de perda em vez de binary_crossentropy.
 Tente usar a tanhativação (uma ativação que era popular nos primeiros dias das redes
neurais) em vez de relu.

3.4.7. Empacotando
Veja o que você deve tirar deste exemplo:

 Você normalmente precisa fazer um bom preprocessamento em seus dados brutos para
poder alimentá-los - como tensores - em uma rede neural. Seqüências de palavras
podem ser codificadas como vetores binários, mas também existem outras opções de
codificação.
 Pilhas de Densecamadas com reluativações podem resolver uma ampla gama de
problemas (incluindo a classificação de sentimentos), e você provavelmente as usará
com frequência.
 Em um problema de classificação binária (duas classes de saída), sua rede deve
terminar com uma Densecamada com uma unidade e uma sigmoidativação: a saída
de sua rede deve ser um escalar entre 0 e 1, codificando uma probabilidade.
 Com essa saída sigmoide escalar em um problema de classificação binária, a função de
perda que você deve usar é binary_crossentropy.
 O rmspropotimizador é geralmente uma escolha boa o suficiente, seja qual for o seu
problema. Essa é uma coisa a menos para você se preocupar.
 À medida que melhoram seus dados de treinamento, as redes neurais eventualmente
começam a se ajustar demais e acabam obtendo resultados cada vez piores em dados
que nunca viram antes. Certifique-se de sempre monitorar o desempenho nos dados
que estão fora do conjunto de treinamento.

3.5. CLASSIFICANDO NEWSWIRES: UM EXEMPLO DE CLASSIFICAÇÃO


MULTICLASSE

Na seção anterior, você viu como classificar as entradas de vetor em duas classes mutuamente
exclusivas usando uma rede neural densamente conectada. Mas o que acontece quando você
tem mais de duas classes?

Nesta seção, você construirá uma rede para classificar as notícias da Reuters em 46 tópicos
mutuamente exclusivos. Porque você tem muitas classes, esse problema é uma instância
de classificação multiclasse ; e como cada ponto de dados deve ser classificado em apenas uma
categoria, o problema é mais especificamente uma instância de classificação de rótulo único e
multiclasse . Se cada ponto de dados pudesse pertencer a várias categorias (neste caso, tópicos),
você estaria enfrentando um problema de classificação multiclasse e multicamada .

3.5.1. O conjunto de dados da Reuters


Você trabalhará com o conjunto de dados da Reuters , um conjunto de pequenas pesquisas e
seus tópicos, publicado pela Reuters em 1986. Trata-se de um conjunto de dados de brinquedos
simples e amplamente usado para a classificação de textos. Existem 46 tópicos
diferentes; alguns tópicos são mais representados que outros, mas cada tópico tem pelo menos
10 exemplos no conjunto de treinamento.

Como o IMDB e MNIST, o conjunto de dados da Reuters vem embalado como parte de
Keras. Vamos dar uma olhada.

Listagem 3.12. Carregando o conjunto de dados Reuters

de keras.datasets import reuters

(train_data, train_labels), (test_data, test_labels) = reuters.load_data (

num_words = 10000)

Assim como no conjunto de dados do IMDB, o argumento num_words=10000restringe os


dados às 10.000 palavras que ocorrem com mais frequência encontradas nos dados.

Você tem 8.982 exemplos de treinamento e 2.246 exemplos de teste:


>>> len (train_data)

8982

>>> len (test_data)

2246

Assim como nas resenhas do IMDB, cada exemplo é uma lista de inteiros (índices de palavras):

>>> train_data [10]

[1, 245, 273, 207, 156, 53, 74, 160, 26, 14, 46, 296, 26, 39, 74, 2979,

3554, 14, 46, 4689, 4329, 86, 61, 3499, 4795, 14, 61, 451, 4329, 17, 12]

Veja como você pode decodificá-lo de volta às palavras, caso esteja curioso.

Listagem 3.13. Decodificando newswires de volta ao texto

word_index = reuters.get_word_index ()

reverse_word_index = dict ([(valor, chave) para (chave, valor) em


word_index.items ()])

decoded_newswire = '' .join ([reverse_word_index.get (i - 3, '?') para i em

train_data [0]])
1

 1 Observe que os índices são compensados por 3 porque 0, 1 e 2 são índices


reservados para “preenchimento”, “início da sequência” e “desconhecido”.

O rótulo associado a um exemplo é um número inteiro entre 0 e 45 - um índice de tópicos:

>>> train_labels [10]

3.5.2. Preparando os dados


Você pode vetorizar os dados com o mesmo código exato do exemplo anterior.

Listagem 3.14. Codificando os dados

import numpy como np

def vectorize_sequences (sequências, dimensão = 10000):

results = np.zeros ((len (sequências), dimensão))

para i, sequência em enumerar (sequências):

resultados [i, sequência] = 1.

retornar resultados
x_train = vectorize_sequences (train_data) 1

x_test = vetorize_sequences (test_data) 2

 1 Dados de treinamento vetorizados


 2 Dados de teste vetorizados

Para vetorizar os rótulos, há duas possibilidades: você pode converter a lista de rótulos como um
tensor inteiro ou usar uma codificação simples. Codificação One-hot é um formato amplamente
utilizado para dados categóricos, também chamado de codificação categórica . Para uma
explicação mais detalhada da codificação de um-quente, veja a seção 6.1 . Nesse caso, a
codificação de um dos tags a quente consiste na incorporação de cada rótulo como um vetor all-
zero com um 1 no lugar do índice de rótulo. Aqui está um exemplo:

def to_one_hot (rótulos, dimensão = 46):

results = np.zeros ((len (etiquetas), dimensão))

para i, label in enumerate (labels):

resultados [i, label] = 1.

retornar resultados

one_hot_train_labels = to_one_hot (train_labels) 1

one_hot_test_labels = to_one_hot (test_labels) 2

 1 etiquetas de treinamento vetorizadas


 2 etiquetas de teste vetorizadas

Observe que há uma maneira interna de fazer isso em Keras, que você já viu em ação no
exemplo MNIST:

de keras.utils.np_utils import to_categorical

one_hot_train_labels = to_categorical (train_labels)

one_hot_test_labels = to_categorical (test_labels)

3.5.3. Construindo sua rede


Esse problema de classificação de tópicos é semelhante ao problema de classificação anterior de
revisão de filmes: em ambos os casos, você está tentando classificar pequenos trechos de
texto. Mas há uma nova restrição aqui: o número de classes de saída passou de 2 para 46. A
dimensionalidade do espaço de saída é muito maior.

Em uma pilha de Densecamadas como essa que você está usando, cada camada só pode acessar
as informações presentes na saída da camada anterior. Se uma camada soltar algumas
informações relevantes para o problema de classificação, essas informações nunca poderão ser
recuperadas por camadas posteriores: cada camada pode se tornar um gargalo de
informações. No exemplo anterior, você usou camadas intermediárias de 16 dimensões, mas um
espaço de 16 dimensões pode ser muito limitado para aprender a separar 46 classes diferentes:
essas camadas pequenas podem atuar como gargalos de informações, descartando
permanentemente informações relevantes.
Por esse motivo, você usará camadas maiores. Vamos com 64 unidades.

Listagem 3.15. Definição de modelo

de modelos de importação keras

das camadas de importação keras

model = models.Sequential ()

model.add (layers.Dense (64, activation = 'relu', input_shape = (10000,))))

model.add (layers.Dense (64, activation = 'relu'))

model.add (layers.Dense (46, activation = 'softmax'))

Há duas outras coisas que você deve observar sobre essa arquitetura:

 Você termina a rede com uma Densecamada de tamanho 46. Isso significa que para
cada amostra de entrada, a rede produzirá um vetor de 46 dimensões. Cada entrada
neste vetor (cada dimensão) codificará uma classe de saída diferente.
 A última camada usa uma softmaxativação. Você viu esse padrão no exemplo
MNIST. Isso significa que a rede produzirá uma distribuição de probabilidade sobre as
46 classes de saída diferentes - para cada amostra de entrada, a rede produzirá um vetor
de saída de 46 dimensões, onde output[i]está a probabilidade de que a amostra
pertença à classe i. As 46 pontuações somam 1.

A melhor função de perda para usar neste caso é categorical_crossentropy. Ele mede a
distância entre duas distribuições de probabilidade: aqui, entre a distribuição de probabilidade
produzida pela rede e a distribuição real dos rótulos. Minimizando a distância entre essas duas
distribuições, você treina a rede para produzir algo o mais próximo possível dos rótulos
verdadeiros.

Listagem 3.16. Compilando o modelo

model.compile (optimizer = 'rmsprop',

perda = 'categorical_crossentropy',

métricas = ['precisão']]

3.5.4. Validando sua abordagem


Vamos separar 1.000 amostras nos dados de treinamento para usar como um conjunto de
validação.

Listagem 3.17. Deixando de lado um conjunto de validação

x_val = x_train [: 1000]

partial_x_train = x_train [1000:]

y_val = one_hot_train_labels [: 1000]

partial_y_train = one_hot_train_labels [1000:]


Agora, vamos treinar a rede por 20 épocas.

Listagem 3.18. Treinando o modelo

history = model.fit (partial_x_train,

partial_y_train,

épocas = 20,

batch_size = 512,

validation_data = (x_val, y_val))

E finalmente, vamos mostrar suas curvas de perda e precisão (veja as figuras 3.9 e 3.10 ).

Figura 3.9. Perda de treinamento e validação

Figura 3.10. Precisão de treinamento e validação

Listagem 3.19. Traçando a perda de treinamento e validação

import matplotlib.pyplot como plt


perda = history.history ['perda']

val_loss = history.history ['val_loss']

épocas = intervalo (1, len (perda) + 1)

plt.plot (epochs, loss, 'bo', label = 'perda de treino')

plt.plot (epochs, val_loss, 'b', label = 'Perda de validação')

plt.title ('Perda de treinamento e validação')

plt.xlabel ('Épocas')

plt.ylabel ('Loss')

plt.legend ()

plt.show ()

Listagem 3.20. Plotando a precisão de treinamento e validação

plt.clf () 1

acc = history.history ['acc']

val_acc = history.history ['val_acc']

plt.plot (epochs, acc, 'bo', label = 'Treinar acc')

plt.plot (epochs, val_acc, 'b', label = 'Validação acc')

plt.title ('Precisão de treinamento e validação')

plt.xlabel ('Épocas')

plt.ylabel ('Loss')

plt.legend ()

plt.show ()

 1 Limpa a figura

A rede começa a se sobrepor depois de nove épocas. Vamos treinar uma nova rede a partir do
zero por nove épocas e depois avaliá-la no conjunto de testes.
Listagem 3.21. Reciclagem de um modelo a partir do zero

model = models.Sequential ()

model.add (layers.Dense (64, activation = 'relu', input_shape = (10000,))))

model.add (layers.Dense (64, activation = 'relu'))

model.add (layers.Dense (46, activation = 'softmax'))

model.compile (optimizer = 'rmsprop',

perda = 'categorical_crossentropy',

métricas = ['precisão']]

model.fit (partial_x_train,

partial_y_train,

épocas = 9,

batch_size = 512,

validation_data = (x_val, y_val))

results = model.evaluate (x_test, one_hot_test_labels)

Aqui estão os resultados finais:

>>> resultados

[0.9565213431445807, 0.79697239536954589]

Essa abordagem atinge uma precisão de ~ 80%. Com um problema de classificação binária
equilibrada, a precisão alcançada por um classificador puramente aleatório seria de 50%. Mas
neste caso é mais próximo de 19%, então os resultados parecem muito bons, pelo menos quando
comparados a uma linha de base aleatória:

>>> cópia de importação

>>> test_labels_copy = copy.copy (test_labels)

>>> np.random.shuffle (test_labels_copy)

>>> hits_array = np.array (test_labels) == np.array (test_labels_copy)

>>> float (np.sum (hits_array)) / len (test_labels)

0,18655387355298308

3.5.5. Gerando previsões sobre novos dados


Você pode verificar se o predictmétodo da instância do modelo retorna uma distribuição de
probabilidade sobre todos os 46 tópicos. Vamos gerar previsões de tópicos para todos os dados
de teste.
Listagem 3.22. Gerando previsões para novos dados

predictions = model.predict (x_test)

Cada entrada predictionsé um vetor de comprimento 46:

>>> previsões [0] .shape

(46)

Os coeficientes neste vetor somam 1:

>>> np.sum (previsões [0])

1,0

A maior entrada é a classe prevista - a classe com maior probabilidade:

>>> np.argmax (previsões [0])

3.5.6. Uma maneira diferente de lidar com os rótulos e a perda


Nós mencionamos anteriormente que outra maneira de codificar os rótulos seria lançá-los como
um tensor inteiro, assim:

y_train = np.array (train_labels)

y_test = np.array (test_labels)

A única coisa que essa abordagem mudaria é a escolha da função de perda. A função de perda
utilizadas na lista 3,21 , categorical_crossentropy, espera que os rótulos de seguir uma
codificação categórica. Com rótulos inteiros, você deve
usar sparse_categorical_crossentropy:

model.compile (optimizer = 'rmsprop',

loss = 'sparse_categorical_crossentropy',

metrics = ['acc'])

Esta nova função de perda ainda é matematicamente igual


a categorical_crossentropy; só tem uma interface diferente.

3.5.7. A importância de ter camadas intermediárias suficientemente


grandes
Mencionamos anteriormente que, como as saídas finais são em 46 dimensões, você deve evitar
camadas intermediárias com muito menos que 46 unidades ocultas. Agora vamos ver o que
acontece quando você introduz um gargalo de informações por ter camadas intermediárias que
são significativamente menores que 46-dimensional: por exemplo, 4-dimensional.

Listagem 3.23. Um modelo com um gargalo de informações

model = models.Sequential ()

model.add (layers.Dense (64, activation = 'relu', input_shape = (10000,))))


model.add (layers.Dense (4, activation = 'relu'))

model.add (layers.Dense (46, activation = 'softmax'))

model.compile (optimizer = 'rmsprop',

perda = 'categorical_crossentropy',

métricas = ['precisão']]

model.fit (partial_x_train,

partial_y_train,

épocas = 20,

batch_size = 128,

validation_data = (x_val, y_val))

A rede agora atinge a precisão de validação de ~ 71%, uma queda absoluta de 8%. Esta queda é
principalmente devido ao fato de que você está tentando comprimir muita informação
(informação suficiente para recuperar os hiperplanos de separação de 46 classes) em um espaço
intermediário que é muito baixa-dimensional. A rede é capaz de encaixar a maioria das
informações necessárias nessas representações em oito dimensões, mas não em todas elas.

3.5.8. Outras experiências


 Tente usar camadas maiores ou menores: 32 unidades, 128 unidades e assim por diante.
 Você usou duas camadas ocultas. Agora tente usar uma única camada oculta ou três
camadas ocultas.

3.5.9. Empacotando
Veja o que você deve tirar deste exemplo:

 Se você está tentando classificar pontos de dados entre N aulas, sua rede deve terminar
com uma Densecamada de tamanho N .
 Em um problema de classificação multiclasse de rótulo único, sua rede deve terminar
com uma softmaxativação para que ela libere uma distribuição de probabilidade sobre
as N classes de saída.
 A crossentropy categórica é quase sempre a função de perda que você deve usar para
esses problemas. Ele minimiza a distância entre as distribuições de probabilidade
produzidas pela rede e a distribuição real dos destinos.
 Existem duas maneiras de lidar com rótulos na classificação multiclasse:
 Codificando os rótulos por meio de codificação categórica (também conhecida
como codificação de um-quente) e
usando categorical_crossentropycomo uma função de perda
 Codificando os rótulos como números inteiros e usando
a sparse_categorical_-crossentropyfunção de perda
Se você precisar classificar os dados em um grande número de categorias, evite a criação de
gargalos na rede devido às camadas intermediárias muito pequenas.
3.6. PREVISÃO DE PREÇOS DA HABITAÇÃO: UM EXEMPLO DE REGRESSÃO

Os dois exemplos anteriores foram considerados problemas de classificação, em que o objetivo


era prever um único rótulo discreto de um ponto de dados de entrada. Outro tipo comum de
problema de aprendizado de máquina é a regressão , que consiste em prever um valor contínuo
em vez de um rótulo discreto: por exemplo, prever a temperatura amanhã, dados dados
meteorológicos; ou prever o tempo que um projeto de software levará para concluir, dadas suas
especificações.

Nota

Não confunda a regressão com a regressão logística do algoritmo . Confusamente, a regressão


logística não é um algoritmo de regressão - é um algoritmo de classificação.

3.6.1. O conjunto de dados do Boston Housing Price


Você tentará prever o preço médio das casas em um determinado subúrbio de Boston em
meados da década de 1970, dados dados sobre o subúrbio na época, como a taxa de
criminalidade, a alíquota do imposto sobre a propriedade local e assim por diante. O conjunto
de dados que você usa tem uma diferença interessante em relação aos dois exemplos
anteriores. Tem relativamente poucos pontos de dados: apenas 506, divididos entre 404
amostras de treinamento e 102 amostras de teste. E cada recurso nos dados de entrada (por
exemplo, a taxa de criminalidade) tem uma escala diferente. Por exemplo, alguns valores são
proporções, que levam valores entre 0 e 1; outros levam valores entre 1 e 12, outros entre 0 e 100
e assim por diante.

Listagem 3.24. Carregando o conjunto de dados do alojamento de Boston

de keras.datasets import boston_housing

(train_data, train_targets), (test_data, test_targets) =

boston_housing.load_data ()

Vamos dar uma olhada nos dados:

>>> train_data.shape

(404, 13)

>>> test_data.shape

(102, 13)

Como você pode ver, você tem 404 amostras de treinamento e 102 amostras de teste, cada uma
com 13 características numéricas, como taxa de criminalidade per capita, número médio de
quartos por habitação, acessibilidade a rodovias e assim por diante.

Os alvos são os valores medianos dos lares ocupados pelos proprietários, em milhares de
dólares:

>>> train_targets
[15.2, 42.3, 50. ... 19.4, 19.4, 29.1]

Os preços são tipicamente entre US $ 10.000 e US $ 50.000. Se isso soa barato, lembre-se que
isso foi em meados da década de 1970, e esses preços não são ajustados pela inflação.

3.6.2. Preparando os dados


Seria problemático alimentar em valores de rede neural que todos tenham intervalos muito
diferentes. A rede pode ser capaz de se adaptar automaticamente a esses dados heterogêneos,
mas definitivamente tornaria o aprendizado mais difícil. Uma prática recomendada
generalizada para lidar com esses dados é fazer a normalização do recurso: para cada recurso
nos dados de entrada (uma coluna na matriz de dados de entrada), você subtrai a média do
recurso e divide pelo desvio padrão, de modo que o recurso é centrado em torno de 0 e tem um
desvio padrão da unidade. Isso é feito facilmente no Numpy.

Listagem 3.25. Normalizando os dados

mean = train_data.mean (eixo = 0)

train_data - = mean

std = train_data.std (eixo = 0)

train_data / = std

test_data - = mean

test_data / = std

Observe que as quantidades usadas para normalizar os dados de teste são calculadas usando os
dados de treinamento. Você nunca deve usar em seu fluxo de trabalho qualquer quantidade
computada nos dados de teste, mesmo para algo tão simples quanto a normalização de dados.

3.6.3. Construindo sua rede


Porque tão poucas amostras estão disponíveis, você usará uma rede muito pequena com duas
camadas ocultas, cada uma com 64 unidades. Em geral, quanto menos dados de treinamento
você tiver, pior será o overfitting, e usar uma pequena rede é uma forma de mitigar o overfitting.

Listagem 3.26. Definição de modelo

de modelos de importação keras

das camadas de importação keras

def build_model ():

model = models.Sequential () 1

model.add (layers.Dense (64, ativação = 'relu',

input_shape = (train_data.shape [1],)))

model.add (layers.Dense (64, activation = 'relu'))

model.add (layers.Dense (1))


model.compile (optimizer = 'rmsprop', perda = 'mse', métricas = ['mae'])

modelo de retorno

 1 Como você precisará instanciar o mesmo modelo várias vezes, use uma
função para construí-lo.

A rede termina com uma única unidade e nenhuma ativação (será uma camada linear). Essa é
uma configuração típica para a regressão escalar (uma regressão na qual você está tentando
prever um único valor contínuo). A aplicação de uma função de ativação restringiria o intervalo
que a saída pode levar; por exemplo, se você aplicou uma sigmoidfunção de ativação na última
camada, a rede só poderia aprender a prever valores entre 0 e 1. Aqui, porque a última camada é
puramente linear, a rede fica livre para aprender a prever valores em qualquer intervalo.

Note que você compila a rede com a msefunção loss - erro quadrado médio , o quadrado da
diferença entre as predições e os alvos. Esta é uma função de perda amplamente utilizada para
problemas de regressão.

Você também está monitorando uma nova métrica durante o treinamento: erro absoluto
médio(MAE). É o valor absoluto da diferença entre as previsões e os alvos. Por exemplo, um
MAE de 0,5 sobre esse problema significaria que suas previsões estão em US $ 500 em média.

3.6.4. Validando sua abordagem usando a validação do K-fold


Para avaliar sua rede enquanto você continua ajustando seus parâmetros (como o número de
épocas usadas para treinamento), você pode dividir os dados em um conjunto de treinamento e
um conjunto de validação, como você fez nos exemplos anteriores. Mas como você tem poucos
pontos de dados, o conjunto de validação acabaria sendo muito pequeno (por exemplo, cerca de
100 exemplos). Como consequência, as pontuações de validação podem mudar muito
dependendo de quais pontos de dados você escolheu usar para validação e quais escolheram
para treinamento: as pontuações de validação podem ter uma alta variação com relação à
divisão de validação. Isso impediria que você avaliasse com confiança seu modelo.

A melhor prática em tais situações é usar a validação cruzada K-fold (veja a figura
3.11 ). Consiste em dividir os dados disponíveis em partições K (normalmente K = 4 ou 5),
instanciar K modelos idênticos e treinar cada um em partições K - 1 enquanto avalia a partição
restante. O escore de validação para o modelo utilizado é então a média dos escores de
validação K obtidos. Em termos de código, isso é simples.

Figura 3.11. Validação cruzada de 3 dobras


Listagem 3.27. Validação de K-fold

import numpy como np

k = 4

num_val_samples = len (train_data) // k

num_epochs = 100

all_scores = []

para eu na faixa (k):

print ('processando fold #', i)

val_data = train_data [i * num_val_samples: (i + 1) * num_val_samples]


1

val_targets = train_targets [i * num_val_samples: (i + 1) *


num_val_samples]

partial_train_data = np.concatenate (
2

[train_data [: i * num_val_samples],

train_data [(i + 1) * num_val_samples:]],

eixo = 0)

partial_train_targets = np.concatenate (

[train_targets [: i * num_val_samples],

train_targets [(i + 1) * num_val_samples:]],

eixo = 0)

model = build_model ()
3

model.fit (partial_train_data, partial_train_targets,


4

epochs = num_epochs, batch_size = 1, verbose = 0)

val_mse, val_mae = model.evaluate (val_data, val_targets, verbose = 0)


5

all_scores.append (val_mae)

 1 Prepara os dados de validação: dados da partição #k


 2 Prepara os dados de treinamento: dados de todas as outras partições
 3 Cria o modelo Keras (já compilado)
 4 Treina o modelo (no modo silencioso, verbose = 0)
 5 Avalia o modelo nos dados de validação

Executando isso com num_epochs = 100os seguintes resultados:

>>> all_scores

[2.588258957792037, 3.1289568449719116, 3.1856116051248984,


3.0763342615401386]

>>> np.mean (all_scores)

2.9947904173572462

As diferentes execuções mostram, de fato, pontuações de validação bastante diferentes, de 2,6 a


3,2. A média (3,0) é uma métrica muito mais confiável do que qualquer pontuação única - esse é
o ponto inteiro da validação cruzada de K-fold. Neste caso, você está fora em US $ 3.000, em
média, o que é significativo, considerando que os preços variam de US $ 10.000 a US $ 50.000.

Vamos tentar treinar a rede um pouco mais: 500 épocas. Para manter um registro de quão bem
o modelo faz em cada época, você modificará o loop de treinamento para salvar o log de
pontuação de validação por época.

Listagem 3.28. Salvando os logs de validação em cada dobra

num_epochs = 500

all_mae_histories = []

para eu na faixa (k):

print ('processando fold #', i)

val_data = train_data [i * num_val_samples: (i + 1) * num_val_samples]


1

val_targets = train_targets [i * num_val_samples: (i + 1) *


num_val_samples]

partial_train_data = np.concatenate (
2

[train_data [: i * num_val_samples],

train_data [(i + 1) * num_val_samples:]],

eixo = 0)

partial_train_targets = np.concatenate (

[train_targets [: i * num_val_samples],

train_targets [(i + 1) * num_val_samples:]],

eixo = 0)

model = build_model ()
3

history = model.fit (partial_train_data, partial_train_targets,


4
validation_data = (val_data, val_targets),

epochs = num_epochs, batch_size = 1, verbose = 0)

mae_history = history.history ['val_mean_absolute_error']

all_mae_histories.append (mae_history)

 1 Prepara os dados de validação: dados da partição #k


 2 Prepara os dados de treinamento: dados de todas as outras partições
 3 Cria o modelo Keras (já compilado)
 4 Treina o modelo (no modo silencioso, verbose = 0)

Você pode então calcular a média das pontuações MAE por época para todas as dobras.

Listagem 3.29. Construindo a história de pontuações de validação K-fold médias sucessivas

average_mae_history = [

np.mean ([x [i] para x em all_mae_histories]) para i em range


(num_epochs)]

Vamos traçar isso; veja a figura 3.12 .

Figura 3.12. Validação MAE por época

Listagem 3.30. Pontuação de validação de plotagem

import matplotlib.pyplot como plt

plt.plot (intervalo (1, len (average_mae_history) + 1), average_mae_history)

plt.xlabel ('Épocas')

plt.ylabel ('Validação MAE')

plt.show ()
Pode ser um pouco difícil ver o gráfico, devido a problemas de dimensionamento e variação
relativamente alta. Vamos fazer o seguinte:

 Omita os primeiros 10 pontos de dados, que estão em uma escala diferente do restante
da curva.
 Substitua cada ponto por uma média móvel exponencial dos pontos anteriores, para
obter uma curva suave.

O resultado é mostrado na figura 3.13 .

Figura 3.13. Validação MAE por época, excluindo os primeiros 10 pontos de dados

Listagem 3.31. Plotagem de pontuações de validação, excluindo os primeiros 10 pontos de dados

def smooth_curve (pontos, fator = 0,9):

smoothed_points = []

para o ponto em pontos:

if smoothed_points:

previous = smoothed_points [-1]

smoothed_points.append (anterior * fator + ponto * (1 - fator))

outro:

smoothed_points.append (ponto)

return smoothed_points

smooth_mae_history = smooth_curve (average_mae_history [10:])

plt.plot (intervalo (1, len (smooth_mae_history) + 1), smooth_mae_history)

plt.xlabel ('Épocas')
plt.ylabel ('Validação MAE')

plt.show ()

De acordo com este enredo, a validação do MAE deixa de melhorar significativamente após 80
épocas. Passado esse ponto, você começa overfitting.

Quando terminar de ajustar outros parâmetros do modelo (além do número de épocas, você
também pode ajustar o tamanho das camadas ocultas), é possível treinar um modelo de
produção final em todos os dados de treinamento, com os melhores parâmetros e, em seguida,
observe seu desempenho nos dados de teste.

Listagem 3.32. Treinando o modelo final

model = build_model () 1

model.fit (train_data, train_targets, 2

epochs = 80, batch_size = 16, verbose = 0)

test_mse_score, test_mae_score = model.evaluate (test_data, test_targets)

 1 Obtém um modelo novo e compilado


 2 Treina-o na totalidade dos dados

Aqui está o resultado final:

>>> test_mae_score

2,5532484335057877

Você ainda está fora por cerca de US $ 2.550.

3.6.5. Empacotando
Veja o que você deve tirar deste exemplo:

 A regressão é feita usando diferentes funções de perda do que as que usamos para
classificação. O erro quadrático médio (MSE) é uma função de perda comumente usada
para regressão.
 Da mesma forma, as métricas de avaliação a serem usadas para a regressão diferem
daquelas usadas para classificação; naturalmente, o conceito de precisão não se aplica à
regressão. Uma métrica de regressão comum é o erro absoluto médio (MAE).
 Quando os recursos nos dados de entrada possuem valores em intervalos diferentes,
cada recurso deve ser dimensionado de forma independente como uma etapa de pré-
processamento.
 Quando há poucos dados disponíveis, usar a validação de dobra em K é uma ótima
maneira de avaliar um modelo de maneira confiável.
 Quando há poucos dados de treinamento disponíveis, é preferível usar uma pequena
rede com poucas camadas ocultas (normalmente apenas uma ou duas), para evitar um
overfitting severo.

Resumo do capítulo

 Agora você pode manipular os tipos mais comuns de tarefas de aprendizado de máquina
em dados vetoriais: classificação binária, classificação multiclasse e regressão
escalar. As seções “Conclusão” no início do capítulo resumem os pontos importantes
que você aprendeu sobre esses tipos de tarefas.
 Você geralmente precisa pré-processar os dados brutos antes de alimentá-los em uma
rede neural.
 Quando seus dados tiverem recursos com intervalos diferentes, dimensione cada
recurso de forma independente como parte do pré-processamento.
 À medida que o treinamento progride, as redes neurais eventualmente começam a se
sobrepor e obtêm resultados piores em dados nunca antes vistos.
 Se você não tiver muitos dados de treinamento, use uma pequena rede com apenas uma
ou duas camadas ocultas, para evitar um overfitting severo.
 Se seus dados estiverem divididos em várias categorias, você poderá causar gargalos de
informações se tornar as camadas intermediárias muito pequenas.
 A regressão usa diferentes funções de perda e diferentes métricas de avaliação do que a
classificação.
 Quando você está trabalhando com poucos dados, a validação do K-fold pode ajudar a
avaliar seu modelo de maneira confiável.

Capítulo 4. Fundamentos de aprendizado de máquina


Este capítulo cobre

 Formas de aprendizado de máquina além da classificação e regressão


 Procedimentos formais de avaliação para modelos de aprendizado de máquina
 Preparando dados para aprendizado profundo
 Engenharia de recursos
 Enfrentar overfitting
 O fluxo de trabalho universal para abordar problemas de aprendizado de máquina

Depois dos três exemplos práticos no capítulo 3 , você deve estar começando a se familiarizar
com a maneira de abordar problemas de classificação e regressão usando redes neurais, e você
testemunhou o problema central do aprendizado de máquina: overfitting. Este capítulo irá
formalizar parte de sua nova intuição em uma sólida estrutura conceitual para atacar e resolver
problemas de aprendizagem profunda. Vamos consolidar todos esses conceitos - avaliação de
modelos, pré-processamento de dados e engenharia de recursos, e combater o ajuste excessivo -
em um fluxo de trabalho detalhado de sete etapas para lidar com qualquer tarefa de aprendizado
de máquina.

4.1. QUATRO RAMOS DE APRENDIZADO DE MÁQUINA

Nos nossos exemplos anteriores, você se familiarizou com três tipos específicos de problemas de
aprendizado de máquina: classificação binária, classificação multiclasse e regressão
escalar. Todos os três são exemplos de aprendizado supervisionado , em que o objetivo é
aprender a relação entre as entradas de treinamento e as metas de treinamento.

O aprendizado supervisionado é apenas a ponta do iceberg - o aprendizado de máquina é um


vasto campo com uma complexa taxonomia de subcampo. Os algoritmos de aprendizado de
máquina geralmente se enquadram em quatro categorias amplas, descritas nas seções a seguir.

4.1.1. Aprendizado supervisionado


Este é, de longe, o caso mais comum. Consiste em aprender a mapear dados de entrada para
alvos conhecidos (também chamados de anotações ), dado um conjunto de exemplos
(freqüentemente anotados por humanos). Todos os quatro exemplos que você encontrou neste
livro até agora foram exemplos canônicos de aprendizado supervisionado. Geralmente, quase
todas as aplicações de aprendizado profundo que estão em evidência hoje em dia pertencem a
essa categoria, como reconhecimento óptico de caracteres, reconhecimento de fala, classificação
de imagens e tradução de idiomas.

Embora a aprendizagem supervisionada consista principalmente em classificação e regressão,


também existem variantes mais exóticas, incluindo as seguintes (com exemplos):

 Geração de seqüências - Dada uma imagem, preveja uma legenda descrevendo-a. A


geração de sequência pode, às vezes, ser reformulada como uma série de problemas de
classificação (como prever repetidamente uma palavra ou token em uma sequência).
 Previsão da árvore de sintaxe - Dada uma sentença, preveja sua decomposição em
uma árvore de sintaxe.
 Detecção de objetos - Com uma imagem, desenhe uma caixa delimitadora em torno
de certos objetos dentro da imagem. Isso também pode ser expresso como um problema
de classificação (dado muitas caixas delimitadoras candidatas, classificar o conteúdo de
cada uma delas) ou como um problema comum de classificação e regressão, onde as
coordenadas da caixa delimitadora são previstas via regressão vetorial.
 Segmentação de imagem - Dada uma imagem, desenhe uma máscara de nível de
pixel em um objeto específico.

4.1.2. Aprendizagem não supervisionada


Este ramo de aprendizado de máquina consiste em encontrar transformações interessantes dos
dados de entrada sem a ajuda de quaisquer alvos, para fins de visualização de dados,
compactação de dados ou remoção de dados, ou para entender melhor as correlações presentes
nos dados disponíveis. O aprendizado não supervisionado é o pão com manteiga da análise de
dados e, com frequência, é uma etapa necessária para entender melhor um conjunto de dados
antes de tentar resolver um problema de aprendizado supervisionado. Redução de
dimensionalidade e agrupamento são categorias bem conhecidas de aprendizado não
supervisionado.

4.1.3. Aprendizagem auto-supervisionada


Esta é uma instância específica de aprendizado supervisionado, mas é diferente o suficiente para
merecer sua própria categoria. A aprendizagem auto-supervisionada é uma aprendizagem
supervisionada semrótulos anotados por humanos - você pode pensar nisso como um
aprendizado supervisionado sem nenhum ser humano no circuito. Ainda existem rótulos
envolvidos (porque o aprendizado tem que ser supervisionado por algo), mas eles são gerados a
partir dos dados de entrada, geralmente usando um algoritmo heurístico.

Por exemplo, os autoencodificadores são uma instância bem conhecida de aprendizado auto-
supervisionado, onde os alvos gerados são a entrada, não modificada. Da mesma forma, tentar
prever o próximo quadro em um vídeo, dados quadros anteriores ou a próxima palavra em um
texto, dadas as palavras anteriores, são exemplos de aprendizado auto-supervisionado
(aprendizado temporariamente supervisionado)., neste caso: a supervisão vem de dados de
entrada futuros). Observe que a distinção entre aprendizado supervisionado, auto-
supervisionado e não supervisionado pode ser confusa às vezes - essas categorias são mais de
um contínuo sem fronteiras sólidas. A aprendizagem auto-supervisionada pode ser
reinterpretada como aprendizagem supervisionada ou não supervisionada, dependendo de se
você prestar atenção ao mecanismo de aprendizagem ou ao contexto de sua aplicação.

Nota

Neste livro, vamos nos concentrar especificamente no aprendizado supervisionado, porque é de


longe a forma dominante de aprendizado profundo hoje, com uma ampla gama de aplicativos do
setor. Também daremos uma olhada mais rápida no aprendizado autodidático nos próximos
capítulos.
4.1.4. Aprendizagem por reforço
Muito negligenciado, esse ramo do aprendizado de máquina recentemente começou a receber
muita atenção depois que o Google DeepMind aplicou-o com sucesso ao aprendizado de jogos de
atari (e, mais tarde, aprendeu a jogar Go no nível mais alto). No aprendizado por reforço,
um agente recebe informações sobre seu ambiente e aprende a escolher ações que maximizem
alguma recompensa. Por exemplo, uma rede neural que “olha” para uma tela de videogame e
gera ações do jogo para maximizar sua pontuação pode ser treinada via aprendizado por reforço.

Atualmente, o aprendizado por reforço é principalmente uma área de pesquisa e ainda não teve
significativos sucessos práticos além dos jogos. Com o tempo, no entanto, esperamos ver o
aprendizado de reforço assumir uma gama cada vez maior de aplicativos do mundo real: carros
autônomos, robótica, gerenciamento de recursos, educação e assim por diante. É uma ideia cuja
hora chegou ou virá em breve.

Glossário de classificação e regressão

Classificação e regressão envolvem muitos termos especializados. Você se deparou com alguns
deles em exemplos anteriores, e você verá mais deles em capítulos futuros. Eles possuem
definições precisas específicas de aprendizado de máquina, e você deve estar familiarizado com
elas:

 Amostra ou entrada - um ponto de dados que entra no seu modelo.


 Previsão ou saída - o que sai do seu modelo.
 Alvo - a verdade. O que seu modelo idealmente deveria ter previsto, de acordo com
uma fonte externa de dados.
 Erro de previsão ou valor de perda - Uma medida da distância entre a previsão do seu
modelo e o alvo.
 Classes— Um conjunto de rótulos possíveis para escolher em um problema de
classificação. Por exemplo, ao classificar fotos de gatos e cachorros, “cachorro” e “gato”
são as duas classes.
 Rótulo— Uma instância específica de uma anotação de classe em um problema de
classificação. Por exemplo, se a imagem # 1234 é anotada como contendo a classe
“cachorro”, então “cachorro” é um rótulo da imagem # 1234.
 Ground-truth ou annotations - Todos os alvos para um conjunto de dados,
normalmente coletados por humanos.
 Classificação binária - Uma tarefa de classificação em que cada amostra de entrada
deve ser categorizada em duas categorias exclusivas.
 Classificação multiclasse - Uma tarefa de classificação em que cada amostra de
entrada deve ser categorizada em mais de duas categorias: por exemplo, a classificação
de dígitos manuscritos.
 Classificação Multilabel - Uma tarefa de classificação em que cada amostra de
entrada pode receber vários rótulos. Por exemplo, uma determinada imagem pode
conter um gato e um cachorro e deve ser anotada com o rótulo "gato" e o rótulo de
"cão". O número de rótulos por imagem é geralmente variável.
 Regressão escalar - Uma tarefa em que o alvo é um valor escalar contínuo. A
previsão dos preços das casas é um bom exemplo: os diferentes preços-alvo formam um
espaço contínuo.
 Vetor de regressão - Uma tarefa em que o alvo é um conjunto de valores contínuos:
por exemplo, um vetor contínuo. Se você está fazendo regressão contra múltiplos
valores (como as coordenadas de uma caixa delimitadora em uma imagem), então você
está fazendo regressão vetorial.
 Mini-lote ou lote - Um pequeno conjunto de amostras (normalmente entre 8 e 128)
processadas simultaneamente pelo modelo. O número de amostras é geralmente uma
potência de 2, para facilitar a alocação de memória na GPU. Ao treinar, um mini-lote é
usado para calcular uma única atualização de gradiente-descida aplicada aos pesos do
modelo.

4.2. AVALIANDO MODELOS DE APRENDIZADO DE MÁQUINA

Nos três exemplos apresentados no capítulo 3 , dividimos os dados em um conjunto de


treinamento, um conjunto de validação e um conjunto de testes. A razão para não avaliar os
modelos nos mesmos dados em que foram treinados rapidamente se tornou evidente: depois de
apenas algumas épocas, todos os três modelos começaram a se sobrepor . Ou seja, seu
desempenho em dados nunca antes vistos começou a atrasar (ou piorar) em comparação com
seu desempenho nos dados de treinamento - o que sempre melhora à medida que o treinamento
progride.

No aprendizado de máquina, o objetivo é alcançar modelos que generalizem - que funcionem


bem em dados nunca antes vistos - e overfitting seja o obstáculo central. Você só pode controlar
o que pode observar, por isso é crucial poder medir com segurança o poder de generalização do
seu modelo. As seções a seguir examinam estratégias para mitigar o overfitting e maximizar a
generalização. Nesta seção, vamos nos concentrar em como medir a generalização: como avaliar
modelos de aprendizado de máquina.

4.2.1. Conjuntos de treinamento, validação e teste


Avaliar um modelo sempre se resume a dividir os dados disponíveis em três conjuntos:
treinamento, validação e teste. Você treina nos dados de treinamento e avalia seu modelo nos
dados de validação. Uma vez que seu modelo esteja pronto para o horário nobre, você o testa
uma última vez nos dados de teste.

Você pode perguntar, por que não ter dois conjuntos: um conjunto de treinamento e um
conjunto de testes? Você treinaria nos dados de treinamento e avaliaria os dados de teste. Muito
mais simples!

A razão é que desenvolver um modelo sempre envolve ajustar sua configuração: por exemplo,
escolher o número de camadas ou o tamanho das camadas (chamadas de hiper-parâmetros do
modelo, para distingui-las dos parâmetros , que são os pesos da rede) . Você faz esse ajuste
usando como sinal de feedback o desempenho do modelo nos dados de validação. Em essência,
esse ajuste é uma forma de aprendizado : uma busca por uma boa configuração em algum
espaço de parâmetros. Como resultado, o ajuste da configuração do modelo com base em seu
desempenho no conjunto de validação pode resultar rapidamente em overfitting para o
conjunto de validação , mesmo que seu modelo nunca seja diretamente treinado nele.

Central para esse fenômeno é a noção de vazamentos de informações . Toda vez que você ajusta
um hiperparâmetro de seu modelo com base no desempenho do modelo no conjunto de
validação, algumas informações sobre os dados de validação vazam no modelo. Se você fizer isso
apenas uma vez, para um parâmetro, muito poucos bits de informações vazarão e seu conjunto
de validação permanecerá confiável para avaliar o modelo. Mas se você repetir isso várias vezes -
executando uma experiência, avaliando o conjunto de validação e modificando seu modelo como
resultado -, você vazará uma quantidade cada vez mais significativa de informações sobre o
conjunto de validação no modelo.

No final do dia, você vai acabar com um modelo que funciona artificialmente bem nos dados de
validação, porque é para isso que você o otimizou. Você se preocupa com o desempenho em
dados completamente novos, não nos dados de validação, portanto, é necessário usar um
conjunto de dados completamente diferente, nunca visto antes, para avaliar o modelo: o
conjunto de dados de teste. Seu modelo não deveria ter acesso a nenhuma informação sobre o
conjunto de testes, mesmo indiretamente.Se alguma coisa sobre o modelo foi ajustada com base
no desempenho do conjunto de testes, então sua medida de generalização será falha.
Dividir seus dados em treinamento, validação e conjuntos de testes pode parecer simples, mas
há algumas maneiras avançadas de fazer isso que podem ser úteis quando há poucos dados
disponíveis. Vamos revisar três receitas de avaliação clássicas: validação simples, validação K-
fold e validação de K-fold iterada com embaralhamento.

Validação simples de espera

Separe alguma fração de seus dados como seu conjunto de testes. Treine os dados restantes e
avalie no conjunto de testes. Como você viu nas seções anteriores, a fim de evitar vazamentos de
informações, você não deve ajustar seu modelo com base no conjunto de testes e,
portanto, também deve reservar um conjunto de validação.

Esquematicamente, a validação de hold-out é semelhante à figura 4.1 . A listagem a seguir


mostra uma implementação simples.

Figura 4.1. Separação de validação simples

Listagem 4.1. Validação Hold-out

num_validation_samples = 10000

np.random.shuffle (data) 1

validation_data = data [: num_validation_samples] 2

data = data [num_validation_samples:]

training_data = data [:] 3

model = get_model () 4

model.train (training_data) 4

validation_score = model.avaluate (validation_data) 4

# Neste momento você pode ajustar seu modelo,

# treinar novamente, avaliá-lo, afiná-lo novamente ...


modelo = get_model () 5

model.train (np.concatenate ([training_data, 5

validation_data])) 5

test_score = model.evaluate (test_data) 5

 1 Embaralhar os dados geralmente é apropriado.


 2 Define o conjunto de validação
 3 Define o conjunto de treino
 4 Treina um modelo nos dados de treinamento e os avalia nos dados de
validação
 5 Depois de ajustar seus hiperparâmetros, é comum treinar seu modelo
final do zero em todos os dados não testados disponíveis.

Este é o protocolo de avaliação mais simples e sofre de uma falha: se houver poucos dados
disponíveis, seus conjuntos de testes e validação podem conter poucas amostras para
representar estatisticamente os dados disponíveis. Isso é fácil de reconhecer: se diferentes
rodadas aleatórias aleatórias dos dados antes da divisão resultarem em medidas muito
diferentes de desempenho do modelo, então você está tendo esse problema. A validação de
dobra em K e a validação de dobra em iteração repetida são duas maneiras de resolver isso,
conforme discutido a seguir.

Validação de K-fold

Com essa abordagem, você divide seus dados em partições K de tamanho igual. Para cada
partição i, treine um modelo nas partições restantes do K - 1 e avalie - o na partição i. Sua
pontuação final é, então, as médias dos K pontuações obtidas. Esse método é útil quando o
desempenho do seu modelo mostra uma variação significativa com base na divisão do teste de
trem. Como validação de hold-out, esse método não o isenta de usar um conjunto de validação
distinto para a calibração do modelo.

Esquematicamente, a validação cruzada K-fold se parece com a figura 4.2 . A Listagem 4.2 mostra
uma implementação simples.

Figura 4.2. Validação tripla

Listagem 4.2. Validação cruzada de dobra em K

k = 4
num_validation_samples = len (data) // k

np.random.shuffle (data)

validation_scores = []

para dobrar no intervalo (k):

validation_data = data [num_validation_samples * dobra: 1

num_validation_samples * (fold + 1)] 1

training_data = data [: num_validation_samples * fold] + 2

dados [num_validation_samples * (fold + 1):] 2

model = get_model () 3

model.train (training_data)

validation_score = model.evaluate (validation_data)

validation_scores.append (validation_score)

validation_score = np.average (validation_scores) 4

model = get_model () 5

model.train (data) 5

test_score = model.evaluate (test_data) 5

 1 Seleciona a partição de dados de validação


 2 Usa o restante dos dados como dados de treinamento. Observe que o
operador + é a concatenação de lista, não o somatório.
 3 Cria uma nova instância do modelo (não treinada)
 4 Pontuação de validação: média dos escores de validação das k dobras
 5 Treina o modelo final em todos os dados não testados disponíveis

Validação de K-fold iterada com shuffling

Este é para situações em que você tem relativamente poucos dados disponíveis e você precisa
avaliar seu modelo com a maior precisão possível. Eu acho que é extremamente útil em
competições de Kaggle. Consiste em aplicar várias vezes a validação de dobra em K,
embaralhando os dados toda vez antes de dividir K maneiras. A pontuação final é a média das
pontuações obtidas em cada execução de validação do K-fold. Note que você acaba treinando e
avaliando modelos P × K (onde P é o número de iterações que você usa), o que pode ser muito
caro.

4.2.2. Coisas a ter em mente


Fique atento ao seguinte quando estiver escolhendo um protocolo de avaliação:
 Representatividade dos dados - Você quer que seu conjunto de treinamento e teste
sejam representativos dos dados disponíveis. Por exemplo, se você está tentando
classificar imagens de dígitos e está começando a partir de uma variedade de amostras
onde as amostras são ordenadas por sua classe, considerando os primeiros 80% da
matriz como seu conjunto de treinamento e os 20% restantes como o seu conjunto de
testes resultará em seu conjunto de treinamento contendo apenas as classes 0 a 7,
enquanto o seu conjunto de testes contém apenas as classes 8 a 9. Isso parece um erro
ridículo, mas é surpreendentemente comum. Por esse motivo, você devealeatoriamente
embaralhar seus dados antes de dividi-los em conjuntos de treinamento e teste.
 A flecha do tempo - Se você está tentando prever o futuro dado o passado (por
exemplo, o clima de amanhã, movimentos de estoque e assim por diante),
você não deve embaralhar aleatoriamente seus dados antes de dividi-los, pois isso criará
um temporal vazamento : seu modelo será efetivamente treinado em dados do
futuro. Em tais situações, você deve sempre certificar-se de que todos os dados no seu
conjunto de teste sejam posteriores aos dados no conjunto de treinamento.
 Redundância em seus dados Se alguns pontos de dados em seus dados aparecerem
duas vezes (bastante comum em dados do mundo real), a organização dos dados e sua
divisão em um conjunto de treinamento e um conjunto de validação resultarão em
redundância entre os conjuntos de treinamento e validação. Com efeito, você estará
testando parte de seus dados de treinamento, o que é a pior coisa que você pode
fazer! Certifique-se de que seu conjunto de treinamento e validação estejam separados.

4.3. PRÉ-PROCESSAMENTO DE DADOS, ENGENHARIA DE RECURSOS E


APRENDIZADO DE RECURSOS

Além da avaliação do modelo, uma questão importante que devemos abordar antes de nos
aprofundarmos no desenvolvimento do modelo é a seguinte: como preparar os dados e os alvos
de entrada antes de alimentá-los em uma rede neural? Muitas técnicas de pré-processamento de
dados e engenharia de recursos são específicas de domínio (por exemplo, específicas para dados
de texto ou dados de imagem); cobriremos os capítulos a seguir quando os encontrarmos em
exemplos práticos. Por enquanto, analisaremos os princípios básicos comuns a todos os
domínios de dados.

4.3.1. Pré-processamento de dados para redes neurais


O pré-processamento de dados visa tornar os dados brutos disponíveis mais acessíveis às redes
neurais. Isso inclui vetorização, normalização, manipulação de valores ausentes e extração de
recursos.

Vectorização

Todas as entradas e alvos em uma rede neural devem ser tensores de dados de ponto flutuante
(ou, em casos específicos, tensores de inteiros). Quaisquer que sejam os dados que você precise
processar - som, imagens, texto - você precisa primeiro se transformar em tensores, um passo
chamado vetorização de dados . Por exemplo, nos dois exemplos de classificação de texto
anteriores, começamos a partir do texto representado como listas de inteiros (representando
sequências de palavras), e usamos uma codificação a quente para transformá-las em um tensor
de float32dados. Nos exemplos de classificação de dígitos e previsão de preços de casas, os
dados já vinham em forma vetorizada, então você pode pular essa etapa.

Normalização de valor

No exemplo de classificação por dígitos, você começou a partir de dados de imagem codificados
como inteiros no intervalo de 0 a 255, codificando valores em escala de cinza. Antes de inserir
esses dados em sua rede, você tinha que convertê-los em float32e dividir por 255, para que
você acabasse com valores de ponto flutuante no intervalo de 0 a 1. Da mesma forma, ao prever
os preços da habitação, você começou a partir de recursos que levaram a uma variedade de
intervalos - alguns recursos tinham pequenos valores de ponto flutuante, outros tinham valores
inteiros bastante grandes. Antes de alimentar esses dados em sua rede, você tinha que
normalizar cada recurso de forma independente, de modo que tivesse um desvio padrão de 1 e
uma média de 0.

Em geral, não é seguro inserir dados de rede neural que utilizem valores relativamente grandes
(por exemplo, inteiros de vários dígitos, que são muito maiores do que os valores iniciais obtidos
pelos pesos de uma rede) ou dados heterogêneos (por exemplo, exemplo, dados em que um
recurso está no intervalo de 0 a 1 e outro no intervalo de 100 a 200). Isso pode acionar grandes
atualizações de gradiente que impedirão a convergência da rede. Para facilitar o aprendizado da
sua rede, seus dados devem ter as seguintes características:

 Tome pequenos valores - Normalmente, a maioria dos valores deve estar no


intervalo de 0 a 1.
 Seja homogêneo - ou seja, todos os recursos devem ter valores em aproximadamente
o mesmo intervalo.

Além disso, a seguinte prática de normalização mais rigorosa é comum e pode ajudar, embora
nem sempre seja necessário (por exemplo, você não fez isso no exemplo de classificação por
dígitos):

 Normalize cada recurso independentemente para ter uma média de 0.


 Normalize cada recurso independentemente para ter um desvio padrão de 1.

Isso é fácil de fazer com matrizes Numpy:

x - = x.mean (eixo = 0) 1

x / = x.std (eixo = 0)

 1 Supondo que x é uma matriz de dados 2D de forma (amostras,


características)

Manipulando Valores em Falta

Às vezes, você pode ter valores ausentes em seus dados. Por exemplo, no exemplo do preço
interno, a primeira característica (a coluna do índice 0 nos dados) era a taxa de criminalidade
per capita. E se esse recurso não estivesse disponível para todas as amostras? Você teria, então,
valores ausentes nos dados de treinamento ou teste.

Em geral, com redes neurais, é seguro inserir valores omissos como 0, com a condição de que 0
já não é um valor significativo. A rede aprenderá com a exposição aos dados que o valor 0
significa dados perdidos e começará a ignorar o valor.

Observe que, se você está esperando valores ausentes nos dados de teste, mas a rede foi treinada
em dados sem nenhum valor ausente, a rede não terá aprendido a ignorar os valores
ausentes! Nessa situação, você deve gerar artificialmente amostras de treinamento com entradas
ausentes: copie algumas amostras de treinamento várias vezes e descarte alguns dos recursos
que você espera estarem ausentes nos dados de teste.

4.3.2. Engenharia de recursos


Engenharia de recursos é o processo de usar seu próprio conhecimento sobre os dados e sobre o
algoritmo de aprendizado de máquina disponível (neste caso, uma rede neural) para fazer o
algoritmo funcionar melhor aplicando transformações codificadas (não aprendidas) aos dados
antes de irem no modelo. Em muitos casos, não é razoável esperar que um modelo de
aprendizado de máquina seja capaz de aprender com dados completamente arbitrários. Os
dados precisam ser apresentados ao modelo de forma a facilitar o trabalho do modelo.
Vamos dar uma olhada em um exemplo intuitivo. Suponha que você esteja tentando desenvolver
um modelo que possa tomar como entrada uma imagem de um relógio e possa mostrar a hora
do dia (veja a figura 4.3 ).

Figura 4.3. Engenharia de recursos para ler a hora em um relógio

Se você optar por usar os pixels brutos da imagem como dados de entrada, você terá um
problema difícil de aprendizado de máquina em mãos. Você precisará de uma rede neural
convolucional para resolvê-la, e terá que gastar bastante recursos computacionais para treinar a
rede.

Mas se você já entende o problema em um nível alto (você entende como os humanos lêem o
tempo em um relógio), então você pode criar recursos de entrada muito melhores para um
algoritmo de aprendizado de máquina: por exemplo, é fácil escrever Script de linha em Python
para seguir os pixels pretos dos ponteiros do relógio e gerar as coordenadas (x, y) da ponta de
cada mão. Em seguida, um algoritmo simples de aprendizado de máquina pode aprender a
associar essas coordenadas à hora apropriada do dia.

Você pode ir ainda mais longe: faça uma alteração de coordenadas e expresse as coordenadas (x,
y) como coordenadas polares em relação ao centro da imagem. Sua entrada se tornará o
ângulo thetade cada ponteiro do relógio. Neste ponto, seus recursos estão tornando o
problema tão fácil que nenhum aprendizado de máquina é necessário; uma simples operação de
arredondamento e busca de dicionário são suficientes para recuperar a hora aproximada do dia.

Essa é a essência da engenharia de recursos: facilitar um problema expressando-o de maneira


mais simples. Geralmente, requer a compreensão do problema em profundidade.

Antes do aprendizado profundo, a engenharia de recursos costumava ser crítica, porque os


algoritmos clássicos superficiais não tinham espaços de hipóteses suficientemente ricos para
aprender recursos úteis por si mesmos. A maneira como você apresentou os dados ao algoritmo
foi essencial para o seu sucesso. Por exemplo, antes que as redes neurais convolucionais se
tornassem bem sucedidas no problema de classificação de dígitos do MNIST, as soluções eram
tipicamente baseadas em recursos codificados como o número de loops em uma imagem de
dígitos, a altura de cada dígito em uma imagem, um histograma de valores de pixel, e assim por
diante.

Felizmente, o aprendizado profundo moderno elimina a necessidade da maioria dos recursos de


engenharia, porque as redes neurais são capazes de extrair automaticamente recursos úteis de
dados brutos. Isso significa que você não precisa se preocupar com engenharia de recursos,
desde que esteja usando redes neurais profundas? Não, por dois motivos:
 Bons recursos ainda permitem que você resolva problemas de maneira mais elegante,
usando menos recursos. Por exemplo, seria ridículo resolver o problema de ler um
mostrador de relógio usando uma rede neural convolucional.
 Bons recursos permitem que você resolva um problema com muito menos dados. A
capacidade dos modelos de aprendizagem profunda de aprender recursos por conta
própria depende da disponibilidade de muitos dados de treinamento; Se você tiver
apenas algumas amostras, o valor da informação em seus recursos se torna crítico.

4.4. OVERFITTING E UNDERFITTING

Em todos os três exemplos do capítulo anterior - previsão de resenhas de filmes, classificação de


tópico e regressão do preço interno - o desempenho do modelo nos dados de validação retidos
sempre atingiu o pico após algumas épocas e começou a se degradar: o modelo começou
rapidamente para overfit aos dados de treinamento. Overfitting acontece em todos os problemas
de aprendizado de máquina. Aprender a lidar com o overfitting é essencial para dominar o
aprendizado de máquinas.

A questão fundamental no aprendizado de máquina é a tensão entre otimização e


generalização. Otimização refere-se ao processo de ajuste de um modelo para obter o melhor
desempenho possível nos dados de treinamento (o aprendizado em aprendizado de máquina ),
enquanto a generalização se refere ao desempenho do modelo treinado em dados nunca antes
vistos. O objetivo do jogo é obter uma boa generalização, é claro, mas você não controla a
generalização; você só pode ajustar o modelo com base em seus dados de treinamento.

No início do treinamento, a otimização e a generalização são correlacionadas: quanto menor a


perda nos dados de treinamento, menor a perda nos dados de teste. Enquanto isso está
acontecendo, seu modelo é considerado inadequado : ainda há progresso a ser feito; a rede
ainda não modelou todos os padrões relevantes nos dados de treinamento. Mas depois de um
certo número de iterações nos dados de treinamento, a generalização pára de melhorar, e as
métricas de validação param e começam a degradar: o modelo está começando a se
sobrepor. Isto é, ele está começando a aprender padrões específicos aos dados de treinamento,
mas que são enganosos ou irrelevantes quando se trata de novos dados.

Para impedir que um modelo aprenda padrões errôneos ou irrelevantes encontrados nos dados
de treinamento, a melhor solução é obter mais dados de treinamento . Um modelo treinado em
mais dados naturalmente generalizará melhor. Quando isso não for possível, a próxima melhor
solução é modular a quantidade de informações que seu modelo pode armazenar ou adicionar
restrições sobre as informações que podem ser armazenadas. Se uma rede só puder se dar ao
luxo de memorizar um pequeno número de padrões, o processo de otimização forçará o foco nos
padrões mais proeminentes, que têm uma chance melhor de generalizar bem.

O processamento do combate super ajuste dessa forma é chamado de regularização . Vamos


rever algumas das técnicas de regularização mais comuns e aplicá-las na prática para melhorar o
modelo de classificação de filmes da seção 3.4 .

4.4.1. Reduzindo o tamanho da rede


A maneira mais simples de evitar o overfitting é reduzir o tamanho do modelo: o número de
parâmetros que podem ser aprendidos no modelo (que é determinado pelo número de camadas
e pelo número de unidades por camada). Em aprendizado profundo, o número de parâmetros
que podem ser aprendidos em um modelo é geralmente chamado de capacidade do
modelo . Intuitivamente, um modelo com mais parâmetros tem mais capacidade de
memorizaçãoe, portanto, pode facilmente aprender um mapeamento perfeito, semelhante a um
dicionário, entre as amostras de treinamento e seus alvos - um mapeamento sem qualquer
poder de generalização. Por exemplo, um modelo com 500.000 parâmetros binários poderia
facilmente ser feito para aprender a classe de cada dígito no conjunto de treinamento MNIST:
precisaríamos de apenas 10 parâmetros binários para cada um dos 50.000 dígitos. Mas tal
modelo seria inútil para classificar novas amostras de dígitos. Tenha sempre isso em mente: os
modelos de aprendizagem profunda tendem a ser bons em se adequar aos dados de
treinamento, mas o verdadeiro desafio é a generalização, e não o ajuste.

Por outro lado, se a rede tiver recursos limitados de memorização, ela não conseguirá aprender
esse mapeamento com tanta facilidade; assim, para minimizar sua perda, ele terá que recorrer a
representações comprimidas de aprendizagem que tenham poder preditivo em relação aos alvos
- precisamente o tipo de representação em que estamos interessados. Ao mesmo tempo, tenha
em mente que você deve usar modelos que têm parâmetros suficientes que não são adequados:
seu modelo não deve passar fome por recursos de memorização. Existe um compromisso a ser
encontrado entre muita capacidade e capacidade insuficiente .

Infelizmente, não existe uma fórmula mágica para determinar o número correto de camadas ou
o tamanho certo para cada camada. Você deve avaliar uma matriz de arquiteturas diferentes (no
seu conjunto de validação, não em seu conjunto de testes, é claro) para encontrar o tamanho
correto do modelo para seus dados. O fluxo de trabalho geral para encontrar um tamanho de
modelo apropriado é começar com relativamente poucas camadas e parâmetros e aumentar o
tamanho das camadas ou adicionar novas camadas até que você veja retornos decrescentes em
relação à perda de validação.

Vamos tentar isso na rede de classificação de revisão de filmes. A rede original é mostrada a
seguir.

Listagem 4.3. Modelo original

de modelos de importação keras

das camadas de importação keras

model = models.Sequential ()

model.add (layers.Dense (16, activation = 'relu', input_shape = (10000,))))

model.add (layers.Dense (16, activation = 'relu'))

model.add (layers.Dense (1, activation = 'sigmoid'))

Agora vamos tentar substituí-lo por essa rede menor.

Listagem 4.4. Versão do modelo com menor capacidade

model = models.Sequential ()

model.add (layers.Dense (4, activation = 'relu', input_shape = (10000,))))

model.add (layers.Dense (4, activation = 'relu'))

model.add (layers.Dense (1, activation = 'sigmoid'))

A Figura 4.4 mostra uma comparação das perdas de validação da rede original e da rede
menor. Os pontos são os valores de perda de validação da rede menor, e os cruzamentos são a
rede inicial (lembre-se, uma perda de validação menor sinaliza um modelo melhor).
Figura 4.4. Efeito da capacidade do modelo na perda de validação: experimentando um modelo menor

Como você pode ver, a rede menor inicia o overfitting mais tarde do que a rede de referência
(depois de seis épocas em vez de quatro), e seu desempenho degrada mais lentamente quando
começa a sobreaproveitamento.

Agora, por diversão, vamos adicionar a este benchmark uma rede que tem muito mais
capacidade - muito mais do que o problema exige.

Listagem 4.5. Versão do modelo com maior capacidade

model = models.Sequential ()

model.add (layers.Dense (512, activation = 'relu', input_shape = (10000,))))

model.add (layers.Dense (512, activation = 'relu'))

model.add (layers.Dense (1, activation = 'sigmoid'))

A Figura 4.5 mostra como as maiores tarifas de rede são comparadas à rede de referência. Os
pontos são os valores de perda de validação da rede maior e os cruzamentos são a rede inicial.
Figura 4.5. Efeito da capacidade do modelo na perda de validação: experimentando um modelo maior

A rede maior começa a sobreaproveitar quase imediatamente, depois de apenas uma época, e se
adapta muito mais severamente. Sua perda de validação também é mais ruidosa.

Enquanto isso, a figura 4.6 mostra as perdas de treinamento para as duas redes. Como você pode
ver, a rede maior recebe sua perda de treinamento perto de zero muito rapidamente. Quanto
mais capacidade a rede tiver, mais rapidamente ela poderá modelar os dados de treinamento
(resultando em uma baixa perda de treinamento), mas mais suscetível será o superajuste
(resultando em uma grande diferença entre o treinamento e a perda de validação).

Figura 4.6 Efeito da capacidade do modelo na perda de treino: experimentando um modelo maior

4.4.2. Adicionando regularização de peso


Você pode estar familiarizado com o princípio da navalha de Occam : dadas duas explicações
para algo, a explicação mais provável de ser correta é a mais simples - aquela que faz menos
suposições. Essa ideia também se aplica aos modelos aprendidos pelas redes neurais: dados
alguns dados de treinamento e uma arquitetura de rede, vários conjuntos de valores de peso
( modelos múltiplos ) poderiam explicar os dados. Modelos mais simples são menos propensos a
se adaptarem do que os complexos.
Um modelo simples neste contexto é um modelo em que a distribuição de valores de parâmetros
tem menos entropia (ou um modelo com menos parâmetros, como você viu na seção
anterior). Assim, uma maneira comum de mitigar o overfitting é colocar restrições na
complexidade de uma rede, forçando seus pesos a obter apenas valores pequenos, o que torna a
distribuição de valores de peso mais regular. Isso é chamado de regularização de peso , e é feito
adicionando à função de perda da rede um custoassociado a grandes pesos. Esse custo vem em
dois sabores:

 Regularização de L1— O custo adicionado é proporcional ao valor absoluto dos


coeficientes de peso (a norma L1 dos pesos).
 Regularização de L2 - O custo adicionado é proporcional ao quadrado do valor dos
coeficientes de peso (a norma L2 dos pesos). A regularização de L2 também é chamada
de decaimento de pesono contexto de redes neurais. Não deixe que o nome diferente o
confunda: a queda de peso é matematicamente igual à regularização de L2.

Em Keras, a regularização de peso é adicionada ao passar instâncias de regularizador de


peso para camadas como argumentos de palavras-chave. Vamos adicionar a regularização de
peso L2 à rede de classificação de revisão de filme.

Listagem 4.6. Adicionando regularização de peso L2 ao modelo

de keras import regularizadores

model = models.Sequential ()

model.add (layers.Dense (16, kernel_regularizer = regularizers.l2 (0.001),

activation = 'relu', input_shape = (10000,))))

model.add (layers.Dense (16, kernel_regularizer = regularizers.l2 (0.001),

ativação = 'relu'))

model.add (layers.Dense (1, activation = 'sigmoid'))

l2(0.001)significa que cada coeficiente na matriz de peso da camada aumentará 0.001 *


weight_coefficient_valuea perda total da rede. Observe que, como essa penalidade
é adicionada apenas no momento do treinamento , a perda dessa rede será muito maior no
treinamento do que no tempo de teste.

A Figura 4.7 mostra o impacto da penalidade de regularização de L2. Como você pode ver, o
modelo com regularização L2 (pontos) tornou-se muito mais resistente ao overfitting do que o
modelo de referência (cruzamentos), embora ambos os modelos tenham o mesmo número de
parâmetros.
Figura 4.7. Efeito da regularização do peso L2 na perda de validação

Como alternativa à regularização L2, você pode usar um dos seguintes reguladores de peso
Keras.

Listagem 4.7. Regularizadores de peso diferentes disponíveis em Keras

de keras import regularizadores

regularizadores.l1 (0,001) 1

regularizadores.l1_l2 (l1 = 0,001, l2 = 0,001) 2

 1 regularização de L1
 2 Regularização simultânea de L1 e L2

4.4.3. Adicionando o dropout


O abandono é uma das técnicas de regularização mais eficazes e mais usadas para redes neurais,
desenvolvida por Geoff Hinton e seus alunos da Universidade de Toronto. O dropout, aplicado a
uma camada, consiste em abandonar aleatoriamente (definindo para zero) vários recursos de
saída da camada durante o treinamento. Digamos que uma determinada camada normalmente
retornaria um vetor [0,2, 0,5, 1,3, 0,8, 1,1] para uma determinada amostra de entrada durante o
treinamento. Após aplicar o dropout, este vetor terá algumas entradas zero distribuídas
aleatoriamente: por exemplo, [0, 0.5, 1.3, 0, 1.1]. A taxa de abandonoé a fração dos recursos que
são zerados; normalmente é definido entre 0,2 e 0,5. No tempo de teste, nenhuma unidade é
descartada; em vez disso, os valores de saída da camada são reduzidos por um fator igual à taxa
de desistência, para equilibrar o fato de que mais unidades estão ativas do que no tempo de
treinamento.

Considere uma matriz Numpy contendo a saída de uma camada layer_output, de


forma (batch_size, features). No tempo de treinamento, zeramos aleatoriamente uma
fração dos valores na matriz:

layer_output * = np.random.randint (0, alto = 2, tamanho = layer_output.shape)


1
 1 No tempo de treinamento, cai 50% das unidades na saída

No tempo de teste, reduzimos a saída pela taxa de desistência. Aqui, nós aumentamos em 0,5
(porque anteriormente descartamos metade das unidades):

layer_output * = 0,5 1

 1 no tempo de teste

Observe que esse processo pode ser implementado executando as duas operações no momento
do treinamento e deixando a saída inalterada no momento do teste, o que geralmente é o modo
como é implementado na prática (consulte a figura 4.8 ):

layer_output * = np.random.randint (0, alto = 2, tamanho = layer_output.shape)


1

layer_output / = 0.5 2

 1 no tempo de treinamento
 2 Observe que estamos ampliando bastante o dimensionamento neste caso.

Figura 4.8. Dropout aplicado a uma matriz de ativação no momento do treinamento, com o reescalonamento acontecendo
durante o treinamento. No tempo de teste, a matriz de ativação permanece inalterada.

Essa técnica pode parecer estranha e arbitrária. Por que isso ajudaria a reduzir o
overfitting? Hinton diz que foi inspirado, entre outras coisas, por um mecanismo de prevenção
de fraude usado pelos bancos. Em suas próprias palavras, “fui ao meu banco. Os caixas
continuaram mudando e perguntei a um deles por quê. Ele disse que não sabia, mas eles se
mudaram muito. Achei que deveria ser porque exigiria a cooperação entre os funcionários para
fraudar com sucesso o banco. Isso me fez perceber que remover aleatoriamente um subconjunto
diferente de neurônios em cada exemplo evitaria conspirações e, assim, reduziria o overfitting
”. [ 1 ] A ideia central é que introduzir ruído nos valores de saída de uma camada pode quebrar
padrões de acaso que não são significante (o que Hinton se refere comoconspirações ), que a
rede começará a memorizar se não houver ruído.

Veja o tópico do Reddit “AMA: Nós somos a equipe do Google Brain. Adoraríamos responder suas perguntas sobre aprendizado de máquina

”, http://mng.bz/XrsS .

Em Keras, você pode introduzir o dropout em uma rede através da Dropoutcamada, que é
aplicada à saída da camada logo antes dela:

model.add (layers.Dropout (0.5))

Vamos adicionar duas Dropoutcamadas na rede do IMDB para ver o quão bem elas estão em
reduzir o overfitting.
Listagem 4.8. Adicionando o dropout à rede do IMDB

model = models.Sequential ()

model.add (layers.Dense (16, activation = 'relu', input_shape = (10000,))))

model.add (layers.Dropout (0.5))

model.add (layers.Dense (16, activation = 'relu'))

model.add (layers.Dropout (0.5))

model.add (layers.Dense (1, activation = 'sigmoid'))

A figura 4.9 mostra um gráfico dos resultados. Mais uma vez, isso é uma clara melhoria em
relação à rede de referência.

Figura 4.9. Efeito do abandono na perda de validação

Para recapitular, estas são as formas mais comuns de evitar overfitting em redes neurais:

 Obtenha mais dados de treinamento.


 Reduza a capacidade da rede.
 Adicione a regularização de peso.
 Adicione o dropout.

4.5. O FLUXO DE TRABALHO UNIVERSAL DE APRENDIZADO DE MÁQUINA

Nesta seção, apresentaremos um plano universal que você pode usar para atacar e resolver
qualquer problema de aprendizado de máquina. O projeto une os conceitos sobre os quais você
aprendeu neste capítulo: definição de problemas, avaliação, engenharia de recursos e combate
ao overfitting.

4.5.1. Definindo o problema e montando um conjunto de dados


Primeiro, você deve definir o problema em questão:
 Quais serão seus dados de entrada? O que você está tentando prever? Você só pode
aprender a prever algo se tiver dados de treinamento disponíveis: por exemplo, você só
pode aprender a classificar o sentimento de resenhas de filmes se tiver as resenhas de
filmes e anotações de opinião disponíveis. Como tal, a disponibilidade de dados é
geralmente o fator limitante neste estágio (a menos que você tenha meios para pagar
pessoas para coletar dados para você).
 Que tipo de problema você está enfrentando? É classificação binária? Classificação
multiclasse? Regressão escalar? Regressão vetorial? Multiclasse, classificação
multilabel? Algo mais, como agrupamento, geração ou aprendizado de
reforço? Identificar o tipo de problema guiará sua escolha de arquitetura de modelo,
função de perda e assim por diante.

Você não pode passar para o próximo estágio até saber quais são suas entradas e saídas e quais
dados serão usados. Esteja ciente das hipóteses que você faz neste estágio:

 Você supõe que suas saídas podem ser previstas de acordo com suas entradas.
 Você supõe que seus dados disponíveis são suficientemente informativos para aprender
a relação entre entradas e saídas.

Até que você tenha um modelo de trabalho, estas são meras hipóteses, esperando para serem
validadas ou invalidadas. Nem todos os problemas podem ser resolvidos; só porque você
montou exemplos de entradas X e metas Y não significa que X contém informações suficientes
para prever Y. Por exemplo, se você está tentando prever os movimentos de uma ação no
mercado de ações, dado seu recente histórico de preços, é improvável que você tenha sucesso,
porque o histórico de preços não contém muita informação preditiva.

Uma classe de problemas insolúveis dos quais você deve estar ciente são problemas não
estacionários. Suponha que você esteja tentando criar um mecanismo de recomendação para
roupas, esteja treinando em um mês de dados (agosto) e queira começar a gerar recomendações
no inverno. Uma grande questão é que os tipos de roupas que as pessoas compram mudam de
estação para estação: a compra de roupas é um fenômeno não-estacionário na escala de alguns
meses. O que você está tentando modelar mudanças ao longo do tempo. Nesse caso, o
movimento correto é treinar constantemente seu modelo em dados do passado recente, ou
reunir dados em uma escala de tempo em que o problema é estacionário. Para um problema
cíclico como a compra de roupas, dados de alguns anos serão suficientes para capturar a
variação sazonal - mas lembre-se de fazer da época do ano uma entrada do seu modelo!

Tenha em mente que o aprendizado de máquina só pode ser usado para memorizar padrões que
estão presentes em seus dados de treinamento. Você só pode reconhecer o que viu antes. Usar o
aprendizado de máquina treinado em dados passados para prever o futuro é supor que o futuro
se comportará como o passado. Isso geralmente não é o caso.

4.5.2. Escolhendo uma medida de sucesso


Para controlar algo, você precisa ser capaz de observá-lo. Para alcançar o sucesso, você deve
definir o que entende por sucesso - precisão? Precisão e recordação? Taxa de retenção de
clientes? Sua métrica para o sucesso guiará a escolha de uma função de perda: o que seu modelo
otimizará. Deve alinhar-se diretamente com seus objetivos de nível mais alto, como o sucesso do
seu negócio.

Para problemas de classificação balanceada, em que todas as classes são igualmente prováveis, a
precisão e a área sob a curva característica de operação do receptor (ROC AUC) são métricas
comuns. Para problemas com desequilíbrio de classe, você pode usar precisão e
recuperação. Para problemas de classificação ou classificação multilabel, você pode usar a
precisão média média. E não é incomum ter que definir sua própria métrica personalizada para
medir o sucesso. Para ter uma noção da diversidade de métricas de sucesso de aprendizado de
máquina e como elas se relacionam com diferentes domínios de problemas, é útil navegar nas
competições de ciência de dados no Kaggle ( https://kaggle.com ); eles exibem uma ampla gama
de problemas e métricas de avaliação.
4.5.3. Decidindo sobre um protocolo de avaliação
Uma vez que você saiba o que você está procurando, você deve estabelecer como você medirá
seu progresso atual. Analisamos anteriormente três protocolos de avaliação comuns:

 Mantendo um conjunto de validação de holdout - O caminho a percorrer


quando você tem muitos dados
 Fazendo a validação cruzada de dobra em K - A escolha certa quando você tem
poucas amostras para validação de retenção para ser confiável
 Fazendo a validação iterativa de dobra em K— Para realizar uma avaliação de
modelo altamente precisa quando há poucos dados disponíveis

Basta escolher um desses. Na maioria dos casos, o primeiro funcionará bem o suficiente.

4.5.4. Preparando seus dados


Depois de saber em que você está treinando, para o que você está otimizando e como avaliar sua
abordagem, você está quase pronto para começar a treinar modelos. Mas primeiro, você deve
formatar seus dados de uma forma que possa ser inserida em um modelo de aprendizado de
máquina - aqui, vamos assumir uma rede neural profunda:

 Como você viu anteriormente, seus dados devem ser formatados como tensores.
 Os valores obtidos por esses tensores devem normalmente ser redimensionados para
valores pequenos: por exemplo, no intervalo [-1, 1] ou [0, 1].
 Se recursos diferentes usarem valores em intervalos diferentes (dados heterogêneos), os
dados deverão ser normalizados.
 Você pode querer fazer alguns recursos de engenharia, especialmente para problemas
com pequenos dados.

Quando seus tensores de dados de entrada e dados de destino estiverem prontos, você poderá
começar a treinar modelos.

4.5.5. Desenvolvendo um modelo que faz melhor que uma linha de base
Seu objetivo nesse estágio é obter poder estatístico : isto é, desenvolver um modelo pequeno que
seja capaz de vencer uma linha de base idiota. No exemplo de classificação de dígitos MNIST,
qualquer coisa que obtiver uma precisão maior que 0,1 pode ser considerada como tendo poder
estatístico; no exemplo do IMDB, é qualquer coisa com uma precisão maior que 0,5.

Note que nem sempre é possível obter poder estatístico. Se você não conseguir superar uma
linha de base aleatória depois de tentar várias arquiteturas razoáveis, pode ser que a resposta à
pergunta que você está fazendo não esteja presente nos dados de entrada. Lembre-se que você
faz duas hipóteses:

 Você supõe que suas saídas podem ser previstas de acordo com suas entradas.
 Você supõe que os dados disponíveis são suficientemente informativos para aprender a
relação entre entradas e saídas.

Pode ser que essas hipóteses sejam falsas e, nesse caso, você deve voltar à prancheta.

Supondo que as coisas vão bem, você precisa fazer três opções-chave para construir seu
primeiro modelo de trabalho:

 Ativação da última camada - Isso estabelece restrições úteis na saída da rede. Por
exemplo, o exemplo de classificação do IMDB usadosigmoidna última camada; o
exemplo de regressão não usou nenhuma ativação da última camada; e assim por
diante.
 Função de perda - isso deve corresponder ao tipo de problema que você está
tentando resolver. Por exemplo, o exemplo do IMDB usadobinary_crossentropy, o
exemplo de regressão usadomsee assim por diante.
 Configuração de otimização - qual otimizador você usará? Qual será a sua taxa de
aprendizado? Na maioria dos casos, é seguro acompanharrmspropsua taxa de
aprendizado padrão.

Em relação à escolha de uma função de perda, observe que nem sempre é possível otimizar
diretamente a métrica que mede o sucesso de um problema. Às vezes não há uma maneira fácil
de transformar uma métrica em uma função de perda; funções de perda, afinal, precisam ser
computáveis, dado apenas um mini lote de dados (idealmente, uma função de perda deve ser
computável para um único ponto de dados) e deve ser diferenciável (caso contrário, não é
possível usar retropropagação para treinar sua rede). Por exemplo, a métrica de classificação
amplamente utilizada ROC AUC não pode ser diretamente otimizada. Portanto, em tarefas de
classificação, é comum otimizar para uma métrica proxy de ROC AUC, como crossentropy. Em
geral, você pode esperar que quanto menor a crossentropy, mais alta será a ROC AUC.

A Tabela 4.1 pode ajudá-lo a escolher uma ativação da última camada e uma função de perda
para alguns tipos de problemas comuns.

Tabela 4.1. Escolhendo a função correta de ativação e perda da última camada para o seu modelo

Tipo de problema Ativação da última camada Função de perda

Classificação binária sigmóide binary_crossentropy

Classificação multiclasse e rótulo único softmax categorical_crossentropy

Multiclasse, classificação multilabel sigmóide binary_crossentropy

Regressão a valores arbitrários Nenhum mse

Regressão para valores entre 0 e 1 sigmóide mse ou binary_crossentropy

4.5.6. Expandir: desenvolvendo um modelo que overfits


Uma vez que você tenha obtido um modelo que tenha poder estatístico, a questão é: o seu
modelo é suficientemente poderoso? Tem camadas e parâmetros suficientes para modelar
adequadamente o problema? Por exemplo, uma rede com uma única camada oculta com duas
unidades teria poder estatístico no MNIST, mas não seria suficiente para resolver bem o
problema. Lembre-se de que a tensão universal no aprendizado de máquina é entre otimização e
generalização; o modelo ideal é aquele que fica bem na fronteira entre underfitting e
overfitting; entre subcapacidade e sobrecapacidade. Para descobrir onde essa fronteira se
encontra, primeiro você deve cruzá-la.

Para descobrir o tamanho do modelo que você precisará, você deve desenvolver um modelo que
seja overfits. Isso é bastante fácil:

1. Adicione camadas.
2. Faça as camadas maiores.
3. Treinar para mais épocas.

Sempre monitore a perda de treinamento e a perda de validação, bem como os valores de


treinamento e validação para qualquer métrica que seja importante para você. Quando você vê
que o desempenho do modelo nos dados de validação começa a se degradar, você terá
superdimensionado.

A próxima etapa é começar a regularizar e ajustar o modelo, para chegar o mais próximo
possível do modelo ideal que nem os modelos nem os superpesados.

4.5.7. Regularizando seu modelo e ajustando seus hiperparâmetros


Essa etapa levará o máximo de tempo: você modificará seu modelo repetidamente, treiná-lo,
avaliar seus dados de validação (não os dados de teste, neste ponto), modificá-lo novamente e
repetir até que o modelo seja tão bom quanto pode obter. Estas são algumas das coisas que você
deve tentar:

 Adicione o dropout.
 Experimente diferentes arquiteturas: adicione ou remova camadas.
 Adicione a regularização L1 e / ou L2.
 Experimente diferentes hiperparâmetros (como o número de unidades por camada ou a
taxa de aprendizado do otimizador) para encontrar a configuração ideal.
 Como opção, faça uma iteração na engenharia de recursos: adicione novos recursos ou
remova recursos que não parecem ser informativos.

Esteja ciente do seguinte: toda vez que você usa o feedback de seu processo de validação para
ajustar seu modelo, você vaza informações sobre o processo de validação no modelo. Repetida
apenas algumas vezes, isso é inócuo; mas feito sistematicamente ao longo de várias iterações, ele
acabará fazendo com que seu modelo se sobreponha ao processo de validação (mesmo que
nenhum modelo seja diretamente treinado em qualquer um dos dados de validação). Isso torna
o processo de avaliação menos confiável.

Depois de desenvolver uma configuração de modelo satisfatória, você pode treinar seu modelo
de produção final em todos os dados disponíveis (treinamento e validação) e avaliá-lo uma
última vez no conjunto de testes. Se o desempenho no conjunto de testes for significativamente
pior que o desempenho medido nos dados de validação, isso pode significar que o procedimento
de validação não era confiável, ou que você começou a ajustar demais os dados de validação
enquanto ajustava os parâmetros do modelo. Nesse caso, convém alternar para um protocolo de
avaliação mais confiável (como a validação de dobra repetida).

Resumo do capítulo

 Defina o problema em questão e os dados sobre os quais você treinará. Colete esses
dados ou anote-os com rótulos, se necessário.
 Escolha como você medirá o sucesso em seu problema. Quais métricas você monitorará
nos seus dados de validação?
 Determine seu protocolo de avaliação: validação de retenção? Validação de K-fold? Qual
parte dos dados você deve usar para validação?
 Desenvolva um primeiro modelo que faça melhor do que uma linha de base básica: um
modelo com poder estatístico.
 Desenvolva um modelo que overfits.
 Regularize seu modelo e ajuste seus hiperparâmetros, com base no desempenho nos
dados de validação. Muitas pesquisas sobre aprendizado de máquina tendem a se
concentrar apenas nesta etapa - mas mantenham o quadro geral em mente.
Parte 2. Aprendizagem profunda na prática
Capítulos 5 - 9 vai ajudar você a ganhar intuição prática sobre como resolver problemas do
mundo real, usando o aprendizado profundo, e irá familiarizá-lo com as melhores práticas
profundas-learning essenciais. A maioria dos exemplos de código no livro está concentrada
neste segundo semestre.

Capítulo 5. Aprendizagem profunda para visão


computacional
Este capítulo cobre

 Compreender redes neurais convolucionais (convnets)


 Usando o aumento de dados para reduzir o overfitting
 Usando uma convnet pré-treinada para fazer a extração de recursos
 Afinar uma convolta pré-treinada
 Visualizar o que as conversas aprendem e como elas tomam decisões de classificação

Este capítulo apresenta redes neurais convolucionais, também conhecidas como " convnets" ,
um tipo de modelo de aprendizagem profunda quase universalmente usado em aplicações de
visão computacional. Você aprenderá a aplicar convnets a problemas de classificação de
imagens - em particular aqueles que envolvem conjuntos de dados de treinamento pequenos,
que são o caso de uso mais comum se você não for uma grande empresa de tecnologia.

5.1. INTRODUÇÃO A CONVNETS

Estamos prestes a mergulhar na teoria do que são as conversinhas e por que elas tiveram tanto
sucesso em tarefas de visão computacional. Mas primeiro, vamos dar uma olhada prática em um
exemplo simples de convnet. Ele usa uma convnet para classificar dígitos MNIST, uma tarefa
que realizamos no capítulo 2 usando uma rede densamente conectada (a precisão do nosso teste
era de 97,8%). Mesmo que a convnet seja básica, sua precisão vai sair da água do modelo
densamente conectado do capítulo 2 .

As linhas de código a seguir mostram como é uma convnet básica. É uma pilha
de Conv2De MaxPooling2Dcamadas. Você verá em um minuto exatamente o que eles fazem.

Listagem 5.1. Instanciando uma pequena convnet

das camadas de importação keras

de modelos de importação keras

model = models.Sequential ()

model.add (layers.Conv2D (32, (3, 3), ativação = 'relu', input_shape = (28,


28, 1)))

model.add (layers.MaxPooling2D ((2, 2)))

model.add (layers.Conv2D (64, (3, 3), ativação = 'relu'))

model.add (layers.MaxPooling2D ((2, 2)))

model.add (layers.Conv2D (64, (3, 3), ativação = 'relu'))


Importante, uma convnet toma como tensores de entrada de forma (image_height,
image_width, image_channels)(não incluindo a dimensão do lote). Neste caso, vamos
configurar a convnet para processar entradas de tamanho (28, 28, 1), que é o formato das
imagens MNIST. Faremos isso passando o argumento input_shape=(28, 28, 1)para a
primeira camada.

Vamos exibir a arquitetura do convnet até agora:

>>> model.summary ()

________________________________________________________________

Camada (tipo) Forma de saída Param #

================================================== ==============

conv2d_1 (Conv2D) (Nenhum, 26, 26, 32) 320

________________________________________________________________

maxpooling2d_1 (MaxPooling2D) (Nenhum, 13, 13, 32) 0

________________________________________________________________

conv2d_2 (Conv2D) (Nenhum, 11, 11, 64) 18496

________________________________________________________________

maxpooling2d_2 (MaxPooling2D) (Nenhum, 5, 5, 64) 0

________________________________________________________________

conv2d_3 (Conv2D) (Nenhum, 3, 3, 64) 36928

================================================== ==============

Total de Params: 55.744

Params treináveis: 55.744

Params não treináveis: 0

Você pode ver que a saída de toda Conv2De MaxPooling2Dcamada é um tensor 3D de


forma (height, width, channels). As dimensões de largura e altura tendem a diminuir à
medida que você se aprofunda na rede. O número de canais é controlado pelo primeiro
argumento passado para as Conv2Dcamadas (32 ou 64).

O próximo passo é alimentar o último tensor de saída (de forma (3, 3, 64)) em uma rede de
classificadores densamente conectada, como aqueles com os quais você já está familiarizado:
uma pilha de Densecamadas. Esses classificadores processam vetores, que são 1D, enquanto a
saída de corrente é um tensor 3D. Primeiro temos que achatar as saídas 3D para 1D e depois
adicionar algumas Densecamadas na parte superior.

Listagem 5.2. Adicionando um classificador no topo da convnet

model.add (layers.Flatten ())

model.add (layers.Dense (64, activation = 'relu'))

model.add (layers.Dense (10, ativação = 'softmax'))


Faremos uma classificação de 10 direções, usando uma camada final com 10 saídas e uma
ativação softmax. Veja como a rede se parece agora:

>>> model.summary ()

Camada (tipo) Forma de saída Param #

================================================== ==============

conv2d_1 (Conv2D) (Nenhum, 26, 26, 32) 320

________________________________________________________________

maxpooling2d_1 (MaxPooling2D) (Nenhum, 13, 13, 32) 0

________________________________________________________________

conv2d_2 (Conv2D) (Nenhum, 11, 11, 64) 18496

________________________________________________________________

maxpooling2d_2 (MaxPooling2D) (Nenhum, 5, 5, 64) 0

________________________________________________________________

conv2d_3 (Conv2D) (Nenhum, 3, 3, 64) 36928

________________________________________________________________

flatten_1 (achatar) (nenhum, 576) 0

________________________________________________________________

dense_1 (denso) (nenhum, 64) 36928

________________________________________________________________

denso_2 (denso) (nenhum, 10) 650

================================================== ==============

Total de params: 93.322

Params treináveis: 93.322

Params não treináveis: 0

Como você pode ver, as (3, 3, 64)saídas são achatadas em vetores de forma (576,)antes de
passar por duas Densecamadas.

Agora, vamos treinar a convnet nos dígitos MNIST. Vamos reutilizar muito do código do
exemplo MNIST no capítulo 2 .

Listagem 5.3. Treinando o convnet em imagens MNIST

de keras.datasets import mnist

de keras.utils import to_categorical

(train_images, train_labels), (test_images, test_labels) = mnist.load_data ()


train_images = train_images.reshape ((60000, 28, 28, 1))

train_images = train_images.astype ('float32') / 255

test_images = test_images.reshape ((10000, 28, 28, 1))

test_images = test_images.astype ('float32') / 255

train_labels = to_categorical (train_labels)

test_labels = to_categorical (test_labels)

model.compile (optimizer = 'rmsprop',

perda = 'categorical_crossentropy',

métricas = ['precisão']]

model.fit (train_images, train_labels, epochs = 5, batch_size = 64)

Vamos avaliar o modelo nos dados de teste:

>>> test_loss, test_acc = model.evaluate (test_images, test_labels)

>>> test_acc

0,99080000000000001

Enquanto a rede densamente conectada do capítulo 2 teve uma precisão de teste de 97,8%, a
convnet básica tem uma precisão de teste de 99,3%: diminuímos a taxa de erro em 68%
(relativa). Não é ruim!

Mas por que esta convnet simples funciona tão bem, em comparação com um modelo
densamente conectado? Para responder a isso, vamos mergulhar no que
as camadas Conv2De MaxPooling2Dfazem.

5.1.1. A operação de convolução


A diferença fundamental entre uma camada densamente conectada e uma camada de
convolução é a seguinte: Densecamadas aprendem padrões globais em seu espaço de recursos
de entrada (por exemplo, para um dígito MNIST, padrões envolvendo todos os pixels), enquanto
camadas de convolução aprendem padrões locais (consulte a figura 5.1 ) : no caso de imagens,
padrões encontrados em pequenas janelas 2D das entradas. No exemplo anterior, essas janelas
eram todas 3 × 3.
Figura 5.1 As imagens podem ser divididas em padrões locais, como bordas, texturas e assim por diante.

Esta característica chave dá às duas propriedades interessantes:

 Os padrões que eles aprendem são invariantes na tradução. Depois de


aprender um certo padrão no canto inferior direito de uma imagem, um convnet pode
reconhecê-lo em qualquer lugar: por exemplo, no canto superior esquerdo. Uma rede
densamente conectada teria que aprender o padrão novamente se aparecesse em um
novo local. Isso torna os dados de convnets eficientes ao processar imagens (porque o
mundo visual é fundamentalmente invariante à tradução ): eles precisam de menos
amostras de treinamento para aprender representações que tenham poder de
generalização.
 Eles podem aprender hierarquias espaciais de padrões (veja a figura
5.2 ). Uma primeira camada de convolução aprende pequenos padrões locais, como
bordas, uma segunda camada de convolução aprende padrões maiores feitos dos
recursos das primeiras camadas, e assim por diante. Isso permite que os convnets
aprendam com eficiência conceitos visuais cada vez mais complexos e abstratos
(porque o mundo visual é fundamentalmente espacialmente hierárquico ).
Figura 5.2 O mundo visual forma uma hierarquia espacial de módulos visuais: bordas hiperlocais se combinam em objetos
locais, como olhos ou ouvidos, que se combinam em conceitos de alto nível, como "gato".

As convoluções operam sobre tensores 3D, chamados de mapas de características , com dois
eixos espaciais ( altura e largura ), bem como um eixo de profundidade (também chamado
de eixo dos canais ). Para uma imagem RGB, a dimensão do eixo de profundidade é 3, porque a
imagem tem três canais de cores: vermelho, verde e azul. Para uma imagem em preto e branco,
como os dígitos MNIST, a profundidade é 1 (níveis de cinza). A operação de convolução extrai os
patches de seu mapa de recursos de entrada e aplica a mesma transformação a todos esses
patches, produzindo um mapa de recursos de saída. Este mapa de recursos de saída ainda é um
tensor 3D: tem largura e altura. Sua profundidade pode ser arbitrária, porque a profundidade de
saída é um parâmetro da camada, e acanais diferentes nesse eixo de profundidade não mais
representam cores específicas como na entrada RGB; em vez disso, eles representam filtros . Os
filtros codificam aspectos específicos dos dados de entrada: em um nível alto, um único filtro
pode codificar o conceito “presença de uma face na entrada”, por exemplo.

No exemplo MNIST, a primeira camada de convolução obtém um mapa de características de


tamanho (28, 28, 1)e gera um mapa de características de tamanho (26, 26, 32): calcula
32 filtros sobre sua entrada. Cada um desses 32 canais de saída contém uma grade de valores de
26 × 26, que é um mapa de resposta do filtro sobre a entrada, indicando a resposta desse
padrão de filtro em diferentes locais na entrada (consulte a figura 5.3 ). Isso é o que o
termo mapa de recursos significa: cada dimensão no eixo de profundidade é uma característica
(ou filtro), e o tensor 2D output[:, :, n]é o mapa espacial 2D da resposta desse filtro sobre
a entrada.
Figura 5.3. O conceito de um mapa de resposta : um mapa 2D da presença de um padrão em diferentes locais em uma entrada

Convoluções são definidas por dois parâmetros principais:

 Tamanho dos remendos extraídos das entradas - Estes são tipicamente 3 × 3


ou 5 × 5. No exemplo, eles eram 3 × 3, o que é uma escolha comum.
 Profundidade do mapa de recursos de saída - O número de filtros calculados
pela convolução. O exemplo começou com uma profundidade de 32 e terminou com
uma profundidade de 64.

Em Keras Conv2Dcamadas, estes parâmetros são os primeiros parâmetros passados para a


camada: Conv2D(output_depth, (window_height, window_width)).

Uma convolução funciona deslizando essas janelas de tamanho 3 × 3 ou 5 × 5 pelo mapa de


recursos de entrada 3D, parando em todos os locais possíveis e extraindo o patch 3D dos
recursos adjacentes (forma (window_height, window_width, input_depth)). Cada um
desses remendos 3D é então transformado (através de um produto tensorial com a mesma
matriz de peso aprendida, chamada de núcleo de convolução ) em um vetor de formato
1D (output_depth,). Todos esses vetores são então reagrupados espacialmente em um mapa
de saída 3D da forma (height, width, output_depth). Cada localização espacial no
mapa de recursos de saída corresponde ao mesmo local no mapa de recursos de entrada (por
exemplo, o canto inferior direito da saída contém informações sobre o canto inferior direito da
entrada). Por exemplo, com janelas 3 × 3, o vetoroutput[i, j, :]vem do patch
3D input[i-1:i+1, j-1:j+1, :]. O processo completo está detalhado na figura 5.4 .
Figura 5.4 Como funciona a convolução

Observe que a largura e a altura da saída podem diferir da largura e da altura da entrada. Eles
podem diferir por dois motivos:

 Efeitos de borda, que podem ser neutralizados pelo preenchimento do mapa de recursos
de entrada
 O uso de strides , que eu vou definir em um segundo

Vamos dar uma olhada mais profunda nessas noções.

Noções básicas sobre efeitos de borda e preenchimento

Considere um mapa de recursos 5 × 5 (total de 25 tiles). Existem apenas 9 blocos em torno dos
quais você pode centralizar uma janela 3 × 3, formando uma grade 3 × 3 (veja a figura
5.5 ). Assim, o mapa de recursos de saída será 3 × 3. Ele encolhe um pouco: exatamente dois
blocos ao lado de cada dimensão, neste caso. Você pode ver este efeito de borda em ação no
exemplo anterior: você começa com 28 × 28 entradas, que se tornam 26 × 26 após a primeira
camada de convolução.
Figura 5.5 Locais válidos de 3 × 3 patches em um mapa de recursos de entrada de 5 × 5

Se você deseja obter um mapa de recursos de saída com as mesmas dimensões espaciais da
entrada, use o preenchimento . O preenchimento consiste em adicionar um número apropriado
de linhas e colunas em cada lado do mapa de recursos de entrada, de modo a possibilitar a
instalação de janelas de convolução central em torno de cada bloco de entrada. Para uma janela
3 × 3, você adiciona uma coluna à direita, uma coluna à esquerda, uma linha na parte superior e
uma linha na parte inferior. Para uma janela 5 × 5, você adiciona duas linhas (veja a figura 5.6 ).

Figura 5.6. Preenchendo uma entrada de 5 × 5 para poder extrair 25 3 x 3 patches

Em Conv2Dcamadas, o preenchimento é configurável por meio do paddingargumento, o que


leva dois valores "valid":, o que significa sem preenchimento (somente locais de janela válidos
serão usados); e "same", o que significa “pad de forma a ter uma saída com a mesma largura e
altura que a entrada”. O paddingargumento é padronizado "valid".

Entendendo os passos da convolução

O outro fator que pode influenciar o tamanho da saída é a noção de passadas . A descrição da
convolução até agora assumiu que os blocos centrais das janelas de convolução são todos
contíguos. Mas a distância entre duas janelas sucessivas é um parâmetro da convolução,
chamado seu stride , cujo padrão é 1. É possível ter convoluções distribuídas : convoluções com
um passo maior que 1. Na figura 5.7 , é possível ver as correções extraídas por um Convolução 3
× 3 com passada 2 sobre uma entrada de 5 × 5 (sem preenchimento).
Figura 5.7. Patches de convolução 3 × 3 com 2 × 2 passos

Usando stride 2 significa que a largura e a altura do mapa de recursos são reduzidos em um
fator de 2 (além de quaisquer alterações induzidas pelos efeitos de borda). Convoluções
circulares raramente são usadas na prática, embora possam ser úteis para alguns tipos de
modelos; é bom estar familiarizado com o conceito.

Para diminuir a resolução de mapas de recursos, em vez de passadas, costumamos usar


a operação de pool máximo , que você viu em ação no primeiro exemplo de convnet. Vamos
olhá-lo com mais profundidade.

5.1.2. A operação de pool máximo


No exemplo convnet, você deve ter notado que o tamanho dos mapas de recursos é reduzido à
metade após cada MaxPooling2Dcamada. Por exemplo, antes das
primeiras MaxPooling2Dcamadas, o mapa de recursos é 26x26, mas a operação de
agrupamento máximo a divide para 13x13. Essa é a função do agrupamento máximo: reduzir de
forma agressiva os mapas de recursos, muito parecido com as convoluções de strided.

O agrupamento máximo consiste em extrair janelas dos mapas de recursos de entrada e gerar o
valor máximo de cada canal. É conceitualmente semelhante à convolução, exceto que, em vez de
transformar os patches locais por meio de uma transformação linear aprendida (o kernel de
convolução), eles são transformados por meio de uma maxoperação tensorial codificada . Uma
grande diferença da convolução é que o pool máximo geralmente é feito com janelas 2 × 2 e
stride 2, para reduzir a resolução dos mapas de recursos por um fator de 2. Por outro lado, a
convolução é tipicamente feita com janelas 3 × 3 e não stride (passo 1).

Por que reduzir o recurso de mapas dessa maneira? Por que não remover as camadas de pool
máximo e manter os mapas de recursos razoavelmente grandes em todo o caminho? Vamos ver
essa opção. A base convolucional do modelo ficaria assim:

model_no_max_pool = models.Sequential ()

model_no_max_pool.add (layers.Conv2D (32, (3, 3), ativação = 'relu',

input_shape = (28, 28, 1)))

model_no_max_pool.add (layers.Conv2D (64, (3, 3), activation = 'relu'))

model_no_max_pool.add (layers.Conv2D (64, (3, 3), activation = 'relu'))


Aqui está um resumo do modelo:

>>> model_no_max_pool.summary ()

Camada (tipo) Forma de saída Param #

================================================== ==============

conv2d_4 (Conv2D) (Nenhum, 26, 26, 32) 320

________________________________________________________________

conv2d_5 (Conv2D) (Nenhum, 24, 24, 64) 18496

________________________________________________________________

conv2d_6 (Conv2D) (Nenhum, 22, 22, 64) 36928

================================================== ==============

Total de Params: 55.744

Params treináveis: 55.744

Params não treináveis: 0

O que há de errado com essa configuração? Duas coisas:

 Não é propício para aprender uma hierarquia espacial de recursos. As janelas 3 × 3 na


terceira camada conterão apenas informações provenientes de janelas 7 × 7 na entrada
inicial. Os padrões de alto nível aprendidos pelo convnet ainda serão muito pequenos
com relação à entrada inicial, o que pode não ser suficiente para aprender a classificar
dígitos (tente reconhecer um dígito observando-o apenas através de janelas com 7 × 7
pixels! ). Precisamos dos recursos da última camada de convolução para conter
informações sobre a totalidade da entrada.
 O mapa de características final tem 22 × 22 × 64 = 30.976 coeficientes totais por
amostra. Isso é enorme. Se você fosse achatá-lo para colocar uma Densecamada de
tamanho 512 no topo, essa camada teria 15,8 milhões de parâmetros. Isso é muito
grande para um modelo tão pequeno e resultaria em um overfitting intenso.

Em suma, o motivo para usar a redução da resolução é reduzir o número de coeficientes de


mapa de características a serem processados, bem como induzir hierarquias de filtros espaciais
fazendo camadas de convolução sucessivas olharem para janelas cada vez maiores (em termos
da fração da entrada original). eles cobrem).

Observe que o pool máximo não é a única maneira de obter essa redução de resolução. Como
você já sabe, você também pode usar strides na camada de convolução anterior. E você podeuse
pool médio em vez de pool máximo, em que cada patch de entrada local é transformado,
tomando o valor médio de cada canal sobre o patch, em vez do valor máximo. Mas o pool
máximo tende a funcionar melhor do que essas soluções alternativas. Em suma, a razão é que os
recursos tendem a codificar a presença espacial de algum padrão ou conceito sobre os diferentes
blocos do mapa de recursos (portanto, o termo mapa de recursos ), e é mais informativo
observar a presença máxima de diferentes recursos do que na sua presença média. Portanto, a
estratégia de subamostragem mais razoável é primeiro produzir mapas densos de recursos (por
meio de convoluções não-carregadas) e então observar a ativação máxima dos recursos em
pequenos trechos, em vez de olhar para janelas mais esparsas das entradas (por meio de
convoluções escalonadas) ou calcular a média de entrada correções, o que pode causar a perda
ou a diluição de informações de presença de recursos.
Nesse ponto, você deve compreender os fundamentos das redes de convecção - mapas de
recursos, convolução e pool máximo - e saber como construir uma pequena convnet para
resolver um problema de brinquedo, como a classificação de dígitos MNIST. Agora vamos
passar para aplicativos mais úteis e práticos.

5.2. TREINANDO UMA CONVNET DO ZERO EM UM PEQUENO CONJUNTO DE


DADOS

Ter que treinar um modelo de classificação de imagens usando muito poucos dados é uma
situação comum, que você provavelmente encontrará na prática se já fez a visão computacional
em um contexto profissional. Um número “pequeno” de amostras pode significar de algumas
centenas a algumas dezenas de milhares de imagens. Como exemplo prático, nos
concentraremos na classificação de imagens como cães ou gatos, em um conjunto de dados
contendo 4.000 fotos de gatos e cães (2.000 gatos, 2.000 cães). Usaremos 2.000 fotos para
treinamento - 1.000 para validação e 1.000 para testes.

Nesta seção, analisaremos uma estratégia básica para resolver esse problema: treinar um novo
modelo a partir do zero usando os poucos dados que você tem. Você começará ingenuamente
treinando uma pequena convenção nas 2.000 amostras de treinamento, sem qualquer
regularização, para definir uma linha de base para o que pode ser alcançado. Isso levará você a
uma precisão de classificação de 71%. Nesse ponto, a questão principal será overfitting. Em
seguida, introduziremos o aumento de dados , uma técnica poderosa para atenuar o overfitting
na visão computacional. Usando o aumento de dados, você aprimora a rede para alcançar uma
precisão de 82%.

Na próxima seção, analisaremos duas outras técnicas essenciais para aplicar o aprendizado
profundo a conjuntos de dados pequenos: extração de recursos com uma rede pré-
treinada (que atingirá uma precisão de 90% a 96%) e ajuste fino de uma rede pré-planejada (
isto te levará a uma precisão final de 97%). Juntas, essas três estratégias - treinar um pequeno
modelo a partir do zero, fazer extração de recurso usando um modelo pré-treinado e ajustar um
modelo pré-treinado - constituirão sua futura caixa de ferramentas para resolver o problema de
classificação de imagens com pequenos conjuntos de dados.

5.2.1. A relevância da aprendizagem profunda para problemas com


pequenos dados
Às vezes, você ouvirá que o aprendizado profundo só funciona quando muitos dados estão
disponíveis. Isso é válido em parte: uma característica fundamental da aprendizagem profunda
é que ela pode encontrar recursos interessantes nos dados de treinamento por conta própria,
sem necessidade de engenharia manual de recursos, e isso só pode ser alcançado quando há
muitos exemplos de treinamento disponíveis. Isso é especialmente verdadeiro para problemas
em que as amostras de entrada são muito dimensionais, como imagens.

Mas o que constitui muitas amostras é relativo - em relação ao tamanho e à profundidade da


rede que você está tentando treinar, para iniciantes. Não é possível treinar um convnet para
resolver um problema complexo com apenas algumas dezenas de amostras, mas algumas
centenas podem bastar se o modelo for pequeno e bem regularizado e a tarefa for simples. Como
as convnets aprendem recursos locais, invariantes à tradução, elas são altamente eficientes em
termos de dados em problemas de percepção. O treinamento de uma convnet do zero em um
conjunto de dados de imagem muito pequeno ainda renderá resultados razoáveis, apesar de
uma relativa falta de dados, sem a necessidade de qualquer engenharia de recurso
personalizado. Você verá isso em ação nesta seção.

Além disso, os modelos de aprendizagem profunda são altamente reutilizáveis por natureza:
você pode usar um modelo de classificação de imagem ou de fala para texto treinado em um
conjunto de dados em grande escala e reutilizá-lo em um problema significativamente diferente
com pequenas alterações. Especificamente, no caso da visão computacional, muitos modelos
pré-treinados (geralmente treinados no conjunto de dados Image-Net) estão agora disponíveis
publicamente para download e podem ser usados para inicializar modelos poderosos de visão
com poucos dados. Isso é o que você fará na próxima seção. Vamos começar por colocar as mãos
nos dados.

5.2.2. Download dos dados


O conjunto de dados Dogs vs. Cats que você usará não é empacotado com Keras. Ele foi
disponibilizado pela Kaggle como parte de uma competição de visão computacional no final de
2013, quando os convnets não eram mainstream. Você pode baixar o conjunto de dados original
em www.kaggle.com/c/dogs-vs-cats/data (você precisará criar uma conta Kaggle se ainda não tiver
uma - não se preocupe, o processo é indolor ).

As imagens são JPEGs em cores de resolução média. A Figura 5.8 mostra alguns exemplos.

Figura 5.8. Amostras do conjunto de dados Dogs vs. Cats. Os tamanhos não foram modificados: as amostras são heterogêneas
em tamanho, aparência e assim por diante.

Sem surpresa, a competição de cães e gatos Kaggle em 2013 foi vencida pelos participantes que
usaram as convnets. As melhores entradas alcançaram até 95% de precisão. Neste exemplo, você
ficará bastante próximo a essa precisão (na próxima seção), embora você treine seus modelos
com menos de 10% dos dados disponíveis para os concorrentes.

Este conjunto de dados contém 25.000 imagens de cães e gatos (12.500 de cada classe) e é 543
MB (compactado). Depois de baixá-lo e descompactá-lo, você criará um novo conjunto de dados
contendo três subconjuntos: um conjunto de treinamento com 1.000 amostras de cada classe,
um conjunto de validação com 500 amostras de cada classe e um conjunto de teste com 500
amostras de cada classe.

A seguir está o código para fazer isso.

Listagem 5.4. Copiando imagens para diretórios de treinamento, validação e teste

importar os, shutil


original_dataset_dir = '/ Usuários / fchollet / Downloads /
kaggle_original_data' 1

base_dir = '/ Usuários / fchollet / Downloads / cats_and_dogs_small'


2

os.mkdir (base_dir)

train_dir = os.path.join (base_dir, 'train') 3

os.mkdir (train_dir)

validation_dir = os.path.join (base_dir, 'validation') 3

os.mkdir (validation_dir)

test_dir = os.path.join (base_dir, 'test') 3

os.mkdir (test_dir)

train_cats_dir = os.path.join (train_dir, 'cats') 4

os.mkdir (train_cats_dir) 4

train_dogs_dir = os.path.join (train_dir, 'dogs') 5

os.mkdir (train_dogs_dir) 5

validation_cats_dir = os.path.join (validation_dir, 'cats') 6

os.mkdir (validation_cats_dir) 6

validation_dogs_dir = os.path.join (validation_dir, 'dogs') 7

os.mkdir (validation_dogs_dir) 7

test_cats_dir = os.path.join (test_dir, 'cats') 8

os.mkdir (test_cats_dir) 8

test_dogs_dir = os.path.join (test_dir, 'dogs') 9

os.mkdir (test_dogs_dir) 9
fnames = ['cat. {} .jpg'.format (i) para i no intervalo (1000)]
10

para fname em fnames:


10

src = os.path.join (original_dataset_dir, fname)


10

dst = os.path. unir-se (train_cats_dir, fname)


10

shutil.copyfile (src, dst)


10

fnames = ['cat. {}. jpg'.format (i) para i no intervalo (1000, 1500)]


11

para fname em fnames:


11

src = os.path.join (original_dataset_dir, fname)


11

dst = os. path.join (validation_cats_dir, fname)


11

shutil.copyfile (src, dst)


11

fnames = ['cat. {} .jpg'.format (i) para i no intervalo (1500, 2000)]


12

para fname em fnames:


12

src = os.path.join (original_dataset_dir, fname)


12

dst = os. path.join (test_cats_dir, fname)


12

shutil.copyfile (src, dst)


12

fnames = ['dog. {}. jpg'.format (i) para i no intervalo (1000)]


13

para fname em fnames:


13

src = os.path.join (original_dataset_dir, fname)


13

dst = os.path. junte-se (train_dogs_dir, fname)


13
shutil.copyfile (src, dst)
13

fnames = ['cão. {}. jpg'.format (i) para i no intervalo (1000, 1500)]


14

para fname em fnames:


14

src = os.path.join (original_dataset_dir, fname)


14

dst = os.path.join (validation_dogs_dir, fname)


14

shutil.copyfile (src, dst)


14

fnames = ['dog. {}. jpg'.format (i) para i no intervalo (1500, 2000)]


15

para fname em fnames:


15

src = os.path.join (original_dataset_dir, fname)


15

dst = os. path.join (test_dogs_dir, fname)


15

shutil.copyfile (src, dst)


15

 1 Caminho para o diretório em que o conjunto de dados original foi


descompactado
 2 Diretório onde você armazenará seu conjunto de dados menor
 3 Diretórios para as divisões de treinamento, validação e teste
 4 Diretório com fotos do gato do treinamento
 5 Diretório com fotos de cachorro de treinamento
 6 Diretório com fotos de gato de validação
 7 Diretório com fotos de cachorro de validação
 8 Diretório com fotos de gato de teste
 9 Diretório com fotos de cachorro de teste
 10 Copia as primeiras 1.000 imagens de gatos para train_cats_dir
 11 Copia as próximas 500 imagens de gatos para validation_cats_dir
 12 Copia as próximas 500 imagens de gatos para test_cats_dir
 13 Copia as primeiras 1.000 imagens de cães para train_dogs_dir
 14 Copia as próximas 500 imagens de cão para validation_dogs_dir
 15 Copia as próximas 500 imagens de cães para test_dogs_dir

Como verificação de integridade, vamos contar quantas fotos estão em cada divisão de
treinamento (trem / validação / teste):

>>> print ('imagens de gato de treinamento total:', len (os.listdir


(train_cats_dir)))

imagens de gato de treinamento total: 1000

>>> print ('imagens do cão de treino total:', len (os.listdir


(train_dogs_dir)))
imagens de cão de treinamento total: 1000

>>> print ('imagens de gato de validação total:', len (os.listdir


(validation_cats_dir)))

imagens de gato de validação total: 500

>>> print ('imagens do cão de validação total:', len (os.listdir


(validation_dogs_dir)))

imagens de cão de validação total: 500

>>> print ('imagens do gato de teste total:', len (os.listdir


(test_cats_dir)))

imagens de gato de teste total: 500

>>> print ('total de imagens do cão de teste:', len (os.listdir


(test_dogs_dir)))

imagens totais do cão do teste: 500

Então você realmente tem 2.000 imagens de treinamento, 1.000 imagens de validação e 1.000
imagens de teste. Cada divisão contém o mesmo número de amostras de cada classe: trata-se de
um problema de classificação binária equilibrado, o que significa que a precisão da classificação
será uma medida apropriada de sucesso.

5.2.3. Construindo sua rede


Você construiu uma pequena convnet para o MNIST no exemplo anterior, portanto você deve
estar familiarizado com essas convnets. Você reutilizará a mesma estrutura geral: a convnet será
uma pilha de camadas alternadas Conv2D(com reluativação) e MaxPooling2Dcamadas.

Mas como você está lidando com imagens maiores e um problema mais complexo, você
aumentará a sua rede de acordo: ele terá mais um estágio Conv2D+ MaxPooling2D. Isso serve
tanto para aumentar a capacidade da rede quanto para reduzir ainda mais o tamanho dos mapas
de recursos, para que eles não sejam excessivamente grandes quando você alcança
a Flattencamada. Aqui, porque você começa a partir de entradas de tamanho 150 × 150 (uma
escolha um pouco arbitrária), você acaba com mapas de recursos de tamanho 7 × 7 antes
da Flattencamada.

Nota

A profundidade dos mapas de recursos aumenta progressivamente na rede (de 32 para 128),
enquanto o tamanho dos mapas de recursos diminui (de 148 × 148 para 7 × 7). Este é um padrão
que você verá em quase todas as convnets.

Como você está atacando um problema de classificação binária, você terminará a rede com uma
única unidade (uma Densecamada de tamanho 1) e uma sigmoidativação. Esta unidade
codificará a probabilidade de que a rede esteja olhando para uma classe ou outra.

Listagem 5.5. Instanciando uma pequena convnet para classificação de cães vs. gatos

das camadas de importação keras

de modelos de importação keras


model = models.Sequential ()

model.add (layers.Conv2D (32, (3, 3), ativação = 'relu',

input_shape = (150, 150, 3)))

model.add (layers.MaxPooling2D ((2, 2)))

model.add (layers.Conv2D (64, (3, 3), ativação = 'relu'))

model.add (layers.MaxPooling2D ((2, 2)))

model.add (layers.Conv2D (128, (3, 3), ativação = 'relu'))

model.add (layers.MaxPooling2D ((2, 2)))

model.add (layers.Conv2D (128, (3, 3), ativação = 'relu'))

model.add (layers.MaxPooling2D ((2, 2)))

model.add (layers.Flatten ())

model.add (layers.Dense (512, activation = 'relu'))

model.add (layers.Dense (1, activation = 'sigmoid'))

Vejamos como as dimensões dos mapas de recursos mudam a cada camada sucessiva:

>>> model.summary ()

Camada (tipo) Forma de saída Param #

================================================== ==============

conv2d_1 (Conv2D) (Nenhuma, 148, 148, 32) 896

________________________________________________________________

maxpooling2d_1 (MaxPooling2D) (Nenhum, 74, 74, 32) 0

________________________________________________________________

conv2d_2 (Conv2D) (Nenhuma, 72, 72, 64) 18496

________________________________________________________________

maxpooling2d_2 (MaxPooling2D) (Nenhum, 36, 36, 64) 0

________________________________________________________________

conv2d_3 (Conv2D) (Nenhuma, 34, 34, 128) 73856

________________________________________________________________

maxpooling2d_3 (MaxPooling2D) (Nenhum, 17, 17, 128) 0

________________________________________________________________

conv2d_4 (Conv2D) (Nenhuma, 15, 15, 128) 147584


________________________________________________________________

maxpooling2d_4 (MaxPooling2D) (Nenhum, 7, 7, 128) 0

________________________________________________________________

flatten_1 (achatar) (nenhum, 6272) 0

________________________________________________________________

dense_1 (denso) (nenhum, 512) 3211776

________________________________________________________________

denso_2 (denso) (nenhum, 1) 513

================================================== ==============

Params totais: 3.453.121

Params treináveis: 3.453.121

Params não treináveis: 0

Para a etapa de compilação, você irá com o RMSpropotimizador, como de costume. Como você
terminou a rede com uma única unidade sigmoid, você usará crossentropy binária como a perda
(como lembrete, confira a tabela 4.1 para uma folha de cheats sobre qual função de perda usar
em várias situações).

Listagem 5.6. Configurando o modelo para treinamento

de otimizadores de importação keras

model.compile (loss = 'binary_crossentropy',

optimizer = optimizers.RMSprop (lr = 1e-4),

metrics = ['acc'])

5.2.4. Pré-processamento de dados


Como você já deve saber, os dados devem ser formatados em tensores de ponto flutuante
adequadamente pré-processados antes de serem alimentados na rede. Atualmente, os dados
ficam em uma unidade como arquivos JPEG, portanto, as etapas para inseri-los na rede são
aproximadamente os seguintes:

1. Leia os arquivos de imagem.


2. Decodifique o conteúdo JPEG para grades RGB de pixels.
3. Converta-os em tensores de ponto flutuante.
4. Reescala os valores de pixel (entre 0 e 255) para o intervalo [0, 1] (como você sabe, as
redes neurais preferem lidar com pequenos valores de entrada).

Pode parecer um pouco assustador, mas felizmente Keras tem utilitários para cuidar desses
passos automaticamente. Keras tem um módulo com ferramentas auxiliares de processamento
de imagem, localizado em keras.preprocessing.image. Em particular, ele contém a
classe ImageDataGenerator, que permite configurar rapidamente geradores Python que
podem transformar automaticamente os arquivos de imagem no disco em lotes de tensores pré-
processados. Isso é o que você vai usar aqui.
Listagem 5.7. Usando ImageDataGeneratorpara ler imagens de diretórios

de keras.preprocessing.image import ImageDataGenerator

train_datagen = ImageDataGenerator (rescale = 1. / 255) 1

test_datagen = ImageDataGenerator (reescalonamento = 1. / 255) 1

train_generator = train_datagen.flow_from_directory (

train_dir, 2

target_size = (150, 150) 3

batch_size = 20,

class_mode = 'binary') 4

validation_generator = test_datagen.flow_from_directory (

validation_dir,

target_size = (150, 150)

batch_size = 20,

class_mode = 'binary')

 1 Resgata todas as imagens em 1/255


 2 Diretório de destino
 3 Redimensiona todas as imagens para 150 × 150
 4 Como você usa a perda binary_crossentropy, você precisa de rótulos
binários.

Entendendo os geradores Python

Um gerador Python é um objeto que age como um iterador: é um objeto que você pode usar
com o for... inoperador. Geradores são construídos usando o yieldoperador.

Aqui está um exemplo de um gerador que produz inteiros:

gerador de def ():

i = 0

enquanto verdadeiro:

i + = 1

rendimento i

para item no gerador ():


imprimir (item)

se item> 4:

pausa

Imprime isto:

1 2 3 4 5

Vejamos a saída de um desses geradores: ele gera lotes de 150 x 150 imagens RGB (forma (20,
150, 150, 3)) e rótulos binários (forma (20,)). São 20amostras em cada lote (o tamanho do
lote). Observe que o gerador gera esses lotes indefinidamente: faz um loop indefinidamente
sobre as imagens na pasta de destino. Por esse motivo, você precisa breakdo loop de iteração
em algum momento:

>>> para data_batch, labels_batch em train_generator:

>>> print ('forma de lote de dados:', data_batch.shape)

>>> print ('forma de lote de rótulos:', labels_batch.shape)

>>> break

forma de lote de dados: (20, 150, 150, 3)

forma de lote de rótulos: (20,)

Vamos ajustar o modelo aos dados usando o gerador. Você faz isso usando
o fit_generatormétodo, o equivalente fita geradores de dados como este. Ele espera como
seu primeiro argumento um gerador Python que produzirá lotes de entradas e destinos
indefinidamente, como este. Como os dados estão sendo gerados indefinidamente, o modelo
Keras precisa saber quantas amostras extrair do gerador antes de declarar uma época. Este é o
papel do steps_per_epochargumento: depois de ter tirado steps_per_epochlotes do
gerador - isto é, depois de ter corrido para steps_per_epochdegraus de gradiente
descendente - o processo de adaptação irá para a próxima época. Nesse caso, os lotes são 20
amostras, portanto, serão necessários 100 lotes até você ver sua meta de 2.000 amostras.

Ao usar fit_generator, você pode passar um validation_dataargumento, como


no fitmétodo. É importante observar que esse argumento pode ser um gerador de dados, mas
também pode ser uma tupla de matrizes Numpy. Se você passar um gerador
como validation_data, então este gerador deverá gerar lotes de dados de validação
indefinidamente; Portanto, você também deve especificar o validation_stepsargumento,
que informa ao processo quantos lotes devem ser extraídos do gerador de validação para
avaliação.

Listagem 5.8. Ajustando o modelo usando um gerador de lotes

history = model.fit_generator (

train_generator,

steps_per_epoch = 100,

épocas = 30,

validation_data = validation_generator,

validation_steps = 50)
É uma boa prática sempre salvar seus modelos após o treinamento.

Listagem 5.9. Salvando o modelo

model.save ('cats_and_dogs_small_1.h5')

Vamos traçar a perda e a precisão do modelo sobre os dados de treinamento e validação durante
o treinamento (ver figuras 5.9 e 5.10 ).

Figura 5.9. Precisão de treinamento e validação

Figura 5.10. Perda de treinamento e validação

Listagem 5.10. Exibindo curvas de perda e precisão durante o treinamento

import matplotlib.pyplot como plt

acc = history.history ['acc']

val_acc = history.history ['val_acc']


perda = history.history ['perda']

val_loss = history.history ['val_loss']

épocas = intervalo (1, len (acc) + 1)

plt.plot (epochs, acc, 'bo', label = 'Treinar acc')

plt.plot (epochs, val_acc, 'b', label = 'Validação acc')

plt.title ('Precisão de treinamento e validação')

plt.legend ()

plt.figure ()

plt.plot (epochs, loss, 'bo', label = 'perda de treino')

plt.plot (epochs, val_loss, 'b', label = 'Perda de validação')

plt.title ('Perda de treinamento e validação')

plt.legend ()

plt.show ()

Essas parcelas são características de overfitting. A precisão do treinamento aumenta


linearmente ao longo do tempo, até atingir quase 100%, enquanto a precisão de validação
diminui em 70-72%. A perda de validação atinge o seu mínimo após apenas cinco épocas e
depois diminui, enquanto a perda de treino continua a diminuir linearmente até atingir quase 0.

Como você tem relativamente poucas amostras de treinamento (2.000), o overfitting será sua
preocupação número um. Você já sabe sobre várias técnicas que podem ajudar a atenuar o
overfitting, como o abandono e a perda de peso (regularização de L2). Agora vamos trabalhar
com um novo, específico para visão computacional e usado quase que universalmente ao
processar imagens com modelos de aprendizagem profunda: aumento de dados .

5.2.5. Usando o aumento de dados


O overfitting é causado por ter muito poucas amostras para aprender, tornando-o incapaz de
treinar um modelo que possa generalizar para novos dados. Dados dados infinitos, seu modelo
seria exposto a todos os aspectos possíveis da distribuição de dados em mãos: você nunca se
daria demais. O aumento de dados utiliza a abordagem de gerar mais dados de treinamento a
partir de amostras de treinamento existentes, aumentando as amostras por meio de várias
transformações aleatórias que geram imagens de aparência confiável. O objetivo é que no tempo
de treinamento, seu modelo nunca verá a mesma foto duas vezes. Isso ajuda a expor o modelo a
mais aspectos dos dados e a generalizar melhor.
Em Keras, isso pode ser feito configurando um número de transformações aleatórias a serem
executadas nas imagens lidas pela ImageDataGeneratorinstância. Vamos começar com um
exemplo.

Listagem 5.11. Configurando uma configuração de aumento de dados via ImageDataGenerator

datagen = ImageDataGenerator (

rotation_range = 40,

width_shift_range = 0,2,

height_shift_range = 0,2,

shear_range = 0,2,

zoom_range = 0,2,

horizontal_flip = True,

fill_mode = 'mais próximo')

Estas são apenas algumas das opções disponíveis (para mais informações, consulte a
documentação do Keras). Vamos rapidamente passar por cima deste código:

 rotation_range é um valor em graus (0-180), um intervalo dentro do qual é possível


girar imagens aleatoriamente.
 width_shifte height_shiftsão intervalos (como uma fração da largura total ou
altura) dentro dos quais se podem traduzir imagens na vertical ou na horizontal.
 shear_range é para aplicar aleatoriamente transformações de cisalhamento.
 zoom_range é para zoom aleatoriamente dentro de fotos.
 horizontal_flip é para inverter aleatoriamente metade das imagens
horizontalmente - relevante quando não há suposições de assimetria horizontal (por
exemplo, imagens do mundo real).
 fill_mode é a estratégia usada para preencher os pixels recém-criados, que podem
aparecer após uma rotação ou uma alteração de largura / altura.

Vamos ver as imagens aumentadas (veja a figura 5.11 ).


Figura 5.11. Geração de fotos de gatos via aumento aleatório de dados

Listagem 5.12. Exibindo algumas imagens de treinamento aumentadas aleatoriamente

de imagem de importação keras.preprocessing 1

fnames = [os.path.join (train_cats_dir, fname) para

fname em os.listdir (train_cats_dir)]

img_path = fnames [3] 2

img = image.load_img (img_path, target_size = (150, 150)) 3

x = image.img_to_array (img) 4

x = x.reshape ((1,) + x.shape) 5

i = 0 6
para lote em datagen.flow (x, batch_size = 1): 6

pt.figura (i) 6

imgplot = plt.imshow (image.array_to_img (lote [0])) 6

i + = 1 6

se i % 4 == 0: 6

quebra 6

plt.show ()

 1 Módulo com utilitários de pré-processamento de imagens


 2 Escolhe uma imagem para aumentar
 3 Lê a imagem e redimensiona
 4 Converte-o em um array Numpy com forma (150, 150, 3)
 5 Remodela para (1, 150, 150, 3)
 6 Gera lotes de imagens transformadas aleatoriamente. Loops
indefinidamente, então você precisa quebrar o loop em algum momento!

Se você treinar uma nova rede usando essa configuração de aumento de dados, a rede nunca
verá a mesma entrada duas vezes. Mas as entradas que ele vê ainda estão pesadamente inter-
relacionadas, porque elas vêm de um pequeno número de imagens originais - você não pode
produzir novas informações, você pode apenas remixar informações existentes. Como tal, isso
pode não ser suficiente para se livrar completamente do overfitting. Para combater ainda mais o
overfitting, você também adicionará uma Dropoutcamada ao seu modelo, logo antes do
classificador densamente conectado.

Listagem 5.13. Definindo uma nova convnet que inclui o dropout

model = models.Sequential ()

model.add (layers.Conv2D (32, (3, 3), ativação = 'relu',

input_shape = (150, 150, 3)))

model.add (layers.MaxPooling2D ((2, 2)))

model.add (layers.Conv2D (64, (3, 3), ativação = 'relu'))

model.add (layers.MaxPooling2D ((2, 2)))

model.add (layers.Conv2D (128, (3, 3), ativação = 'relu'))

model.add (layers.MaxPooling2D ((2, 2)))

model.add (layers.Conv2D (128, (3, 3), ativação = 'relu'))

model.add (layers.MaxPooling2D ((2, 2)))

model.add (layers.Flatten ())

model.add (layers.Dropout (0.5))

model.add (layers.Dense (512, activation = 'relu'))

model.add (layers.Dense (1, activation = 'sigmoid'))


model.compile (loss = 'binary_crossentropy',

optimizer = optimizers.RMSprop (lr = 1e-4),

metrics = ['acc'])

Vamos treinar a rede usando aumento e desistência de dados.

Listagem 5.14. Treinando o convnet usando geradores de aumento de dados

train_datagen = ImageDataGenerator (

reescala = 1. / 255,

rotation_range = 40,

width_shift_range = 0,2,

height_shift_range = 0,2,

shear_range = 0,2,

zoom_range = 0,2,

horizontal_flip = True,)

test_datagen = ImageDataGenerator (rescale = 1. / 255) 1

train_generator = train_datagen.flow_from_directory (

train_dir, 2

target_size = (150, 150), 3

batch_size = 32,

class_mode = 'binary') 4

validation_generator = test_datagen.flow_from_directory (

validation_dir,

target_size = (150, 150)

batch_size = 32,

class_mode = 'binary')

history = model.fit_generator (

train_generator,
steps_per_epoch = 100,

épocas = 100,

validation_data = validation_generator,

validation_steps = 50)

 1 Observe que os dados de validação não devem ser aumentados!


 2 Diretório de destino
 3 Redimensiona todas as imagens para 150 × 150
 4 Como você usa a perda binary_crossentropy, você precisa de rótulos
binários.

Vamos salvar o modelo - você o usará na seção 5.4 .

Listagem 5.15. Salvando o modelo

model.save ('cats_and_dogs_small_2.h5')

E vamos plotar os resultados novamente: veja as figuras 5.12 e 5.13 . Graças ao aumento e ao
abandono de dados, você não está mais com overfitting: as curvas de treinamento estão
acompanhando de perto as curvas de validação. Agora você alcança uma precisão de 82%, uma
melhoria relativa de 15% em relação ao modelo não regularizado.

Figura 5.12. Precisão de treinamento e validação com aumento de dados


Figura 5.13. Perda de treinamento e validação com aumento de dados

Usando técnicas de regularização ainda mais e ajustando os parâmetros da rede (como o


número de filtros por camada de convolução ou o número de camadas na rede), você pode obter
uma precisão ainda melhor, provavelmente até 86%. ou 87%. Mas seria difícil ir mais alto
simplesmente treinando sua própria convette do zero, porque você tem poucos dados para
trabalhar. Como próximo passo para melhorar sua precisão neste problema, você terá que usar
um modelo pré-treinado, que é o foco das próximas duas seções.

5.3. USANDO UMA CONVOLTA PRÉ-TREINADA

Uma abordagem comum e altamente eficaz para o aprendizado profundo em pequenos


conjuntos de imagens é usar uma rede pré-planejada. Uma rede pré-estratificadaé uma rede
salva que foi previamente treinada em um grande conjunto de dados, geralmente em uma tarefa
de classificação de imagem em larga escala. Se este conjunto de dados original for grande o
suficiente e genérico o suficiente, então a hierarquia espacial de recursos aprendidos pela rede
pré-treinada pode efetivamente atuar como um modelo genérico do mundo visual e, portanto,
suas características podem ser úteis para muitos problemas diferentes de visão computacional.
embora esses novos problemas possam envolver classes completamente diferentes daquelas da
tarefa original. Por exemplo, você pode treinar uma rede no Image-Net (onde as classes são
principalmente animais e objetos do dia-a-dia) e redirecionar essa rede treinada para algo tão
remoto quanto a identificação de itens de mobília em imagens.

Nesse caso, vamos considerar uma grande convnet treinada no conjunto de dados do ImageNet
(1,4 milhão de imagens rotuladas e 1.000 classes diferentes). O ImageNet contém muitas classes
de animais, incluindo diferentes espécies de gatos e cães, e você pode esperar ter um bom
desempenho no problema de classificação entre cães e gatos.

Você usará a arquitetura VGG16, desenvolvida por Karen Simonyan e Andrew Zisserman em
2014; é uma arquitetura convnet simples e amplamente usada para o ImageNet. [ 1 ] Embora seja
um modelo antigo, longe do atual estado da arte e um pouco mais pesado do que muitos outros
modelos recentes, eu o escolhi porque sua arquitetura é semelhante ao que você já conhece e é
fácil de entender sem introduzir qualquer novos conceitos. Esse pode ser seu primeiro encontro
com um desses nomes de modelos bonitinhos - VGG, ResNet, Inception, Inception-ResNet,
Xception e assim por diante; você vai se acostumar com eles, porque eles vão aparecer com
frequência se você continuar fazendo aprendizado profundo para visão computacional.

1
Karen Simonyan e Andrew Zisserman, “Redes Convolucionais Muito Profundas para Reconhecimento de Imagem em Larga Escala”, arXiv

(2014), https://arxiv.org/abs/1409.1556 .

Existem duas maneiras de usar uma rede pré-estratificada: extração de recursos e ajuste
fino . Nós vamos cobrir os dois. Vamos começar com a extração de recursos.

5.3.1. Extração de recurso


A extração de recursos consiste em usar as representações aprendidas por uma rede anterior
para extrair recursos interessantes de novas amostras. Esses recursos são então executados
através de um novo classificador, que é treinado do zero.

Como você viu anteriormente, os convnets usados para classificação de imagens compreendem
duas partes: eles começam com uma série de camadas de agrupamento e convolução e
terminam com um classificador densamente conectado. A primeira parte é chamada de base
convolucional do modelo. No caso de convnets, a extração de características consiste em tomar
a base convolucional de umrede previamente treinada, executando os novos dados através dela e
treinando um novo classificador em cima da saída (veja a figura 5.14 ).

Figura 5.14. Classificadores de troca, mantendo a mesma base convolucional

Por que apenas reutilizar a base convolucional? Você poderia reutilizar o classificador
densamente conectado também? Em geral, isso deve ser evitado. A razão é que as
representações aprendidas pela base convolucional tendem a ser mais genéricas e, portanto,
mais reutilizáveis: os mapas de características de uma convnet são mapas de presença de
conceitos genéricos sobre uma imagem, que provavelmente serão úteis independentemente da
visão computacional. problema na mão. Mas as representações aprendidas pelo classificador
serão necessariamente específicas para o conjunto de classes nas quais o modelo foi treinado -
elas conterão apenas informações sobre a probabilidade de presença dessa ou daquela classe em
toda a imagem. Além disso, as representações encontradas em camadas densamente conectadas
não contêm mais nenhuma informação sobreonde os objetos estão localizados na imagem de
entrada: essas camadas eliminam a noção de espaço, enquanto a localização do objeto ainda é
descrita pelos mapas de características convolucionais. Para problemas em que a localização do
objeto é importante, os recursos densamente conectados são em grande parte inúteis.

Observe que o nível de generalidade (e, portanto, a capacidade de reutilização) das


representações extraídas por camadas de convolução específicas depende da profundidade da
camada no modelo. Camadas que vêm antes no modelo extraem mapas de recursos locais
altamente genéricos (como bordas visuais, cores e texturas), enquanto camadas mais altas
extraem conceitos mais abstratos (como "orelha de gato" ou "olho de cachorro") . Portanto, se o
novo conjunto de dados for muito diferente do conjunto de dados no qual o modelo original foi
treinado, talvez seja melhor usar apenas as primeiras camadas do modelo para fazer a extração
de recursos, em vez de usar toda a base convolucional.

Nesse caso, como o conjunto de classes ImageNet contém várias classes de cães e gatos, é
provável que seja benéfico reutilizar as informações contidas nas camadas densamente
conectadas do modelo original. Mas vamos escolher não, para cobrir o caso mais geral em que o
conjunto de classes do novo problema não se sobrepõe ao conjunto de classes do modelo
original. Vamos colocar isso em prática usando a base convolucional da rede VGG16, treinada no
ImageNet, para extrair recursos interessantes de imagens de gatos e cachorros e depois treinar
um classificador de cães contra gatos sobre esses recursos.

O modelo VGG16, entre outros, vem pré-empacotado com Keras. Você pode importá-lo
do keras.applicationsmódulo. Aqui está a lista de modelos de classificação de imagens
(todos pré-concebidos no conjunto de dados do ImageNet) que estão disponíveis como parte
de keras.applications:

 Xception
 Inception V3
 ResNet50
 VGG16
 VGG19
 MobileNet

Vamos instanciar o modelo VGG16.

Listagem 5.16. Instanciando a base convolucional VGG16

de keras.applications importar VGG16

conv_base = VGG16 (pesos = 'imagenet',

include_top = False,

input_shape = (150, 150, 3))

Você passa três argumentos para o construtor:

 weights especifica o ponto de verificação de peso a partir do qual inicializar o modelo.


 include_toprefere-se a incluir (ou não) o classificador densamente conectado no topo
da rede. Por padrão, esse classificador densamente conectado corresponde às 1.000
classes do ImageNet. Como você pretende usar seu próprio classificador densamente
conectado (com apenas duas classes: cate dog), não é necessário incluí-lo.
 input_shapeé a forma dos tensores de imagem que você irá alimentar na rede. Este
argumento é puramente opcional: se você não passar, a rede poderá processar entradas
de qualquer tamanho.

Aqui está o detalhe da arquitetura da base convolucional do VGG16. É semelhante às convnets


simples com as quais você já está familiarizado:
>>> conv_base.summary ()

Camada (tipo) Forma de saída Param #

================================================== ==============

input_1 (InputLayer) (Nenhum, 150, 150, 3) 0

________________________________________________________________

block1_conv1 (Convolution2D) (Nenhum, 150, 150, 64) 1792

________________________________________________________________

block1_conv2 (Convolution2D) (Nenhum, 150, 150, 64) 36928

________________________________________________________________

block1_pool (MaxPooling2D) (Nenhum, 75, 75, 64) 0

________________________________________________________________

block2_conv1 (Convolution2D) (nenhum, 75, 75, 128) 73856

________________________________________________________________

block2_conv2 (Convolution2D) (nenhum, 75, 75, 128) 147584

________________________________________________________________

block2_pool (MaxPooling2D) (Nenhum, 37, 37, 128) 0

________________________________________________________________

block3_conv1 (Convolution2D) (nenhum, 37, 37, 256) 295168

________________________________________________________________

block3_conv2 (Convolution2D) (nenhum, 37, 37, 256) 590080

________________________________________________________________

block3_conv3 (Convolution2D) (nenhum, 37, 37, 256) 590080

________________________________________________________________

block3_pool (MaxPooling2D) (Nenhum, 18, 18, 256) 0

________________________________________________________________

block4_conv1 (Convolution2D) (Nenhum, 18, 18, 512) 1180160

________________________________________________________________

block4_conv2 (Convolution2D) (Nenhum, 18, 18, 512) 2359808

________________________________________________________________

block4_conv3 (Convolution2D) (Nenhum, 18, 18, 512) 2359808

________________________________________________________________
block4_pool (MaxPooling2D) (Nenhum, 9, 9, 512) 0

________________________________________________________________

block5_conv1 (Convolution2D) (Nenhum, 9, 9, 512) 2359808

________________________________________________________________

block5_conv2 (Convolution2D) (Nenhum, 9, 9, 512) 2359808

________________________________________________________________

block5_conv3 (Convolution2D) (Nenhum, 9, 9, 512) 2359808

________________________________________________________________

block5_pool (MaxPooling2D) (Nenhum, 4, 4, 512) 0

================================================== ==============

Params totais: 14.714.688

Params treináveis: 14.714.688

Params não treináveis: 0

O mapa de recursos final tem forma (4, 4, 512). Esse é o recurso em cima do qual você vai
colocar um classificador densamente conectado.

Neste ponto, existem duas maneiras de proceder:

 Executando a base convolucional sobre seu conjunto de dados, registrando sua saída em
um array Numpy no disco e, em seguida, usando esses dados como entrada para um
classificador autônomo e densamente semelhante àquele que você viu na parte 1 deste
livro. Essa solução é rápida e barata de ser executada, porque requer apenas a execução
da base convolucional uma vez para cada imagem de entrada, e a base convolucional é,
de longe, a parte mais cara do pipeline. Mas pela mesma razão, essa técnica não
permitirá que você use o aumento de dados.
 Estendendo o modelo você tem ( conv_base) adicionando Densecamadas no topo, e
executando a coisa toda para terminar nos dados de entrada. Isso permitirá que você
use o aumento de dados, porque toda imagem de entrada passa pela base convolucional
toda vez que é vista pelo modelo. Mas pela mesma razão, esta técnica é muito mais cara
que a primeira.

Nós vamos cobrir as duas técnicas. Vamos percorrer o código necessário para configurar o
primeiro: registrando a saída de conv_baseseus dados e usando essas saídas como entradas
para um novo modelo.

Extração rápida de recursos sem aumento de dados

Você começará executando instâncias do anteriormente


introduzido ImageDataGeneratorpara extrair imagens como matrizes Numpy, bem como
seus rótulos. Você extrairá recursos dessas imagens chamando o predictmétodo
do conv_basemodelo.

Listagem 5.17. Extraindo recursos usando a base convolucional pré-traçada

importar os

import numpy como np


de keras.preprocessing.image import ImageDataGenerator

base_dir = '/ Usuários / fchollet / Downloads / cats_and_dogs_small'

train_dir = os.path.join (base_dir, 'train')

validation_dir = os.path.join (base_dir, 'validation')

test_dir = os.path.join (base_dir, 'test')

datagen = ImageDataGenerator (rescale = 1. / 255)

batch_size = 20

def extract_features (diretório, amostra_conta):

features = np.zeros (shape = (sample_count, 4, 4, 512))

labels = np.zeros (shape = (sample_count))

gerador = datagen.flow_from_directory (

diretório,

target_size = (150, 150)

batch_size = batch_size,

class_mode = 'binary')

i = 0

para inputs_batch, labels_batch no gerador:

features_batch = conv_base.predict (inputs_batch)

apresenta [i * batch_size: (i + 1) * batch_size] = features_batch

labels [i * batch_size: (i + 1) * batch_size] = labels_batch

i + = 1

se eu * batch_size> = sample_count:

quebrar
1

retornar recursos, rótulos

train_features, train_labels = extract_features (train_dir, 2000)

validation_features, validation_labels = extract_features (validation_dir,


1000)

test_features, test_labels = extract_features (test_dir, 1000)


 1 Observe que, como os geradores geram dados indefinidamente em um
loop, você deve interromper depois que cada imagem tiver sido vista uma
vez.

Os recursos extraídos estão atualmente em forma (samples, 4, 4, 512). Você os


alimentará em um classificador densamente conectado, então primeiro você deve achatá-los
para (samples, 8192):

train_features = np.reshape (train_features, (2000, 4 * 4 * 512))

validation_features = np.reshape (validation_features, (1000, 4 * 4 * 512))

test_features = np.reshape (test_features, (1000, 4 * 4 * 512))

Neste ponto, você pode definir seu classificador densamente conectado (observe o uso de
dropout para regularização) e treiná-lo nos dados e rótulos que você acabou de gravar.

Listagem 5.18. Definindo e treinando o classificador densamente conectado

de modelos de importação keras

das camadas de importação keras

de otimizadores de importação keras

model = models.Sequential ()

model.add (layers.Dense (256, activation = 'relu', input_dim = 4 * 4 * 512))

model.add (layers.Dropout (0.5))

model.add (layers.Dense (1, activation = 'sigmoid'))

model.compile (optimizer = optimizers.RMSprop (lr = 2e-5),

perda = 'binary_crossentropy',

metrics = ['acc'])

history = model.fit (train_features, train_labels,

épocas = 30,

batch_size = 20,

validation_data = (validation_features,
validation_labels))

O treinamento é muito rápido, porque você só precisa lidar com duas Densecamadas - uma
época leva menos de um segundo, mesmo na CPU.

Vejamos as curvas de perda e precisão durante o treinamento (veja as figuras 5.15 e 5.16 ).
Figura 5.15. Precisão de treinamento e validação para extração simples de recursos

Figura 5.16. Perda de treinamento e validação para extração simples de recursos

Listagem 5.19. Plotando os resultados

import matplotlib.pyplot como plt

acc = history.history ['acc']

val_acc = history.history ['val_acc']

perda = history.history ['perda']

val_loss = history.history ['val_loss']

épocas = intervalo (1, len (acc) + 1)


plt.plot (epochs, acc, 'bo', label = 'Treinar acc')

plt.plot (epochs, val_acc, 'b', label = 'Validação acc')

plt.title ('Precisão de treinamento e validação')

plt.legend ()

plt.figure ()

plt.plot (epochs, loss, 'bo', label = 'perda de treino')

plt.plot (epochs, val_loss, 'b', label = 'Perda de validação')

plt.title ('Perda de treinamento e validação')

plt.legend ()

plt.show ()

Você alcança uma precisão de validação de cerca de 90% - muito melhor do que alcançou na
seção anterior, com o modelo pequeno treinado do zero. Mas os gráficos também indicam que
você está se sobrecarregando quase desde o início - apesar de usar o abandono com uma taxa
razoavelmente grande. Isso porque essa técnica não usa o aumento de dados, o que é essencial
para evitar o overfitting com pequenos conjuntos de imagens.

Extração de recursos com aumento de dados

Agora, vamos rever a segunda técnica que mencionei para fazer a extração de recursos, que é
muito mais lenta e mais cara, mas que permite usar o aumento de dados durante o treinamento:
estendendo o conv_basemodelo e executando-o de ponta a ponta nas entradas.

Nota

Essa técnica é tão cara que você só deve tentar se tiver acesso a uma GPU - ela é absolutamente
intratável na CPU. Se você não pode executar o seu código na GPU, então a técnica anterior é o
caminho a percorrer.

Como os modelos se comportam como camadas, você pode adicionar um modelo


(como conv_base) a um Sequentialmodelo como se fosse adicionar uma camada.

Listagem 5.20. Adicionando um classificador densamente conectado no topo da base convolucional

de modelos de importação keras

das camadas de importação keras


model = models.Sequential ()

model.add (conv_base)

model.add (layers.Flatten ())

model.add (layers.Dense (256, activation = 'relu'))

model.add (layers.Dense (1, activation = 'sigmoid'))

É assim que o modelo se parece agora:

>>> model.summary ()

Camada (tipo) Forma de saída Param #

================================================== ==============

vgg16 (Modelo) (Nenhum, 4, 4, 512) 14714688

________________________________________________________________

flatten_1 (achatar) (nenhum, 8192) 0

________________________________________________________________

dense_1 (denso) (nenhum, 256) 2097408

________________________________________________________________

denso_2 (denso) (nenhum, 1) 257

================================================== ==============

Params totais: 16.812.353

Params treináveis: 16.812.353

Params não treináveis: 0

Como você pode ver, a base convolucional do VGG16 tem 14.714.688 parâmetros, o que é muito
grande. O classificador que você está adicionando no topo tem 2 milhões de parâmetros.

Antes de compilar e treinar o modelo, é muito importante congelar a base


convolucional. Congelaruma camada ou conjunto de camadas significa impedir que seus pesos
sejam atualizados durante o treinamento. Se você não fizer isso, as representações que foram
previamente aprendidas pela base convolucional serão modificadas durante o
treinamento. Como as Densecamadas no topo são inicializadas aleatoriamente, atualizações
muito grandes de peso seriam propagadas pela rede, destruindo efetivamente as representações
aprendidas anteriormente.

Em Keras, você congela uma rede configurando seu trainableatributo para False:

>>> print ('Esse é o número de pesos treináveis'

'antes de congelar a base conv:', len (model.trainable_weights))

Este é o número de pesos treináveis antes de congelar a base da conv: 30

>>> conv_base.trainable = False


>>> print ('Esse é o número de pesos treináveis'

'após o congelamento da base conv:', len (model.trainable_weights))

Este é o número de pesos treináveis após o congelamento da base da conv: 4

Com essa configuração, somente os pesos das duas Densecamadas que você adicionou serão
treinados. Isso é um total de quatro tensores de peso: dois por camada (a principal matriz de
peso e o vetor de polarização). Observe que, para que essas alterações sejam efetivadas, você
deve primeiro compilar o modelo. Se você modificar a treinabilidade de peso após a compilação,
recompile o modelo ou essas alterações serão ignoradas.

Agora você pode começar a treinar seu modelo com a mesma configuração de aumento de dados
usada no exemplo anterior.

Listagem 5.21. Treinar o modelo de ponta a ponta com uma base convolucional congelada

de keras.preprocessing.image import ImageDataGenerator

de otimizadores de importação keras

train_datagen = ImageDataGenerator (

reescala = 1. / 255,

rotation_range = 40,

width_shift_range = 0,2,

height_shift_range = 0,2,

shear_range = 0,2,

zoom_range = 0,2,

horizontal_flip = True,

fill_mode = 'mais próximo')

test_datagen = ImageDataGenerator (rescale = 1. / 255) 1

train_generator = train_datagen.flow_from_directory (

train_dir, 2

target_size = (150, 150), 3

batch_size = 20,

class_mode = 'binary') 4

validation_generator = test_datagen.flow_from_directory (

validation_dir,
target_size = (150, 150)

batch_size = 20,

class_mode = 'binary')

model.compile (loss = 'binary_crossentropy',

optimizer = optimizers.RMSprop (lr = 2e-5),

metrics = ['acc'])

history = model.fit_generator (

train_generator,

steps_per_epoch = 100,

épocas = 30,

validation_data = validation_generator,

validation_steps = 50)

 1 Observe que os dados de validação não devem ser aumentados!


 2 Diretório de destino
 3 Redimensiona todas as imagens para 150 × 150
 4 Como você usa a perda binary_crossentropy, você precisa de rótulos
binários.

Vamos plotar os resultados novamente (veja as figuras 5.17 e 5.18 ). Como você pode ver, você
alcança uma precisão de validação de cerca de 96%. Isso é muito melhor do que você conseguiu
com o pequeno convnet treinado a partir do zero.

Figura 5.17. Precisão de treinamento e validação para extração de recursos com aumento de dados
Figura 5.18. Perda de treinamento e validação para extração de recursos com aumento de dados

5.3.2. Afinação
Outra técnica amplamente utilizada para reutilização de modelos, complementar à extração de
características, é o ajuste fino (consulte a figura 5.19 ). O ajuste fino consiste no
descongelamento de algumas das camadas superiores de uma base de modelo congelada usada
para a extração de recursos e no treinamento conjunto da parte recém-adicionada do modelo
(neste caso, o classificador totalmente conectado) e dessas camadas superiores. Isso é
chamado de ajuste fino porque ajusta ligeiramente as representações mais abstratas do modelo
que está sendo reutilizado, a fim de torná-las mais relevantes para o problema em questão.
Figura 5.19. Afinar o último bloco convolucional da rede VGG16
Afirmei anteriormente que é necessário congelar a base de convolução do VGG16 para poder
treinar um classificador inicializado aleatoriamente no topo. Pela mesma razão, só é possível
ajustar as camadas superiores da base convolucional, uma vez que o classificador no topo já
tenha sido treinado. Se o classificador ainda não estiver treinado, o sinal de erro propagado pela
rede durante o treinamento será muito grande, e as representações aprendidas anteriormente
pelas camadas sendo ajustadas serão destruídas. Assim, as etapas para o ajuste fino de uma rede
são as seguintes:

1. Adicione sua rede personalizada sobre uma rede básica já treinada.


2. Congele a rede básica.
3. Treine a parte que você adicionou.
4. Descongelar algumas camadas na rede base.
5. Treine em conjunto essas duas camadas e a parte que você adicionou.

Você já concluiu as três primeiras etapas ao fazer a extração de recursos. Vamos prosseguir com
o passo 4: você irá descongelar o seu conv_basee então congelar camadas individuais dentro
dele.

Como lembrete, é assim que sua base convolucional se parece:

>>> conv_base.summary ()

Camada (tipo) Forma de saída Param #

================================================== ==============

input_1 (InputLayer) (Nenhum, 150, 150, 3) 0

________________________________________________________________

block1_conv1 (Convolution2D) (Nenhum, 150, 150, 64) 1792

________________________________________________________________

block1_conv2 (Convolution2D) (Nenhum, 150, 150, 64) 36928

________________________________________________________________

block1_pool (MaxPooling2D) (Nenhum, 75, 75, 64) 0

________________________________________________________________

block2_conv1 (Convolution2D) (nenhum, 75, 75, 128) 73856

________________________________________________________________

block2_conv2 (Convolution2D) (nenhum, 75, 75, 128) 147584

________________________________________________________________

block2_pool (MaxPooling2D) (Nenhum, 37, 37, 128) 0

________________________________________________________________

block3_conv1 (Convolution2D) (nenhum, 37, 37, 256) 295168

________________________________________________________________

block3_conv2 (Convolution2D) (nenhum, 37, 37, 256) 590080

________________________________________________________________
block3_conv3 (Convolution2D) (nenhum, 37, 37, 256) 590080

________________________________________________________________

block3_pool (MaxPooling2D) (Nenhum, 18, 18, 256) 0

________________________________________________________________

block4_conv1 (Convolution2D) (Nenhum, 18, 18, 512) 1180160

________________________________________________________________

block4_conv2 (Convolution2D) (Nenhum, 18, 18, 512) 2359808

________________________________________________________________

block4_conv3 (Convolution2D) (Nenhum, 18, 18, 512) 2359808

________________________________________________________________

block4_pool (MaxPooling2D) (Nenhum, 9, 9, 512) 0

________________________________________________________________

block5_conv1 (Convolution2D) (Nenhum, 9, 9, 512) 2359808

________________________________________________________________

block5_conv2 (Convolution2D) (Nenhum, 9, 9, 512) 2359808

________________________________________________________________

block5_conv3 (Convolution2D) (Nenhum, 9, 9, 512) 2359808

________________________________________________________________

block5_pool (MaxPooling2D) (Nenhum, 4, 4, 512) 0

================================================== ==============

Total de params: 14714688

Você vai afinar os últimos três camadas convolucionais, o que significa que todas as camadas
até block4_pooldeve ser congelado, e as
camadas block5_conv1, block5_conv2e block5_conv3deve ser treinável.

Por que não ajustar mais camadas? Por que não ajustar toda a base convolucional? Você
poderia. Mas você precisa considerar o seguinte:

 Camadas anteriores na base convolucional codificam recursos mais genéricos e


reutilizáveis, enquanto as camadas superiores codificam recursos mais especializados. É
mais útil ajustar os recursos mais especializados, porque esses são os que precisam ser
reaproveitados em seu novo problema. Haveria retornos de rápida diminuição no ajuste
fino das camadas inferiores.
 Quanto mais parâmetros você está treinando, mais você corre o risco de overfitting. A
base convolucional tem 15 milhões de parâmetros, então seria arriscado tentar treiná-lo
em seu pequeno conjunto de dados.
Assim, nessa situação, é uma boa estratégia ajustar apenas as duas ou três camadas superiores
na base convolucional. Vamos configurar isso, começando de onde você parou no exemplo
anterior.

Listagem 5.22. Congelando todas as camadas até uma específica

conv_base.trainable = True

set_trainable = False

para camada em conv_base.layers:

se layer.name == 'block5_conv1':

set_trainable = True

se set_trainable:

layer.trainable = True

outro:

layer.trainable = False

Agora você pode começar a sintonizar a rede. Você fará isso com o otimizador RMSProp, usando
uma taxa de aprendizado muito baixa. A razão para usar uma baixa taxa de aprendizado é que
você quer limitar a magnitude das modificações que você faz nas representações das três
camadas que você está ajustando. Atualizações que são muito grandes podem prejudicar essas
representações.

Listagem 5.23. Ajustando o modelo

model.compile (loss = 'binary_crossentropy',

optimizer = optimizers.RMSprop (lr = 1e-5),

metrics = ['acc'])

history = model.fit_generator (

train_generator,

steps_per_epoch = 100,

épocas = 100,

validation_data = validation_generator,

validation_steps = 50)

Vamos plotar os resultados usando o mesmo código de plotagem de antes (veja as figuras
5.20 e 5.21 ).
Figura 5.20. Treinamento e validação de precisão para o ajuste fino

Figura 5.21. Perda de treinamento e validação para ajuste fino

Essas curvas parecem barulhentas. Para torná-los mais legíveis, você pode suavizá-los,
substituindo cada perda e precisão por médias móveis exponenciais dessas quantidades. Aqui
está uma função de utilidade trivial para fazer isso (veja as figuras 5.22 e 5.23 ).
Figura 5.22. Curvas suavizadas para precisão de treinamento e validação para ajuste fino

Figura 5.23. Curvas suavizadas para perda de treinamento e validação para ajuste fino

Listagem 5.24. Suavizando as parcelas

def smooth_curve (pontos, fator = 0,8):

smoothed_points = []

para o ponto em pontos:

if smoothed_points:

previous = smoothed_points [-1]

smoothed_points.append (anterior * fator + ponto * (1 - fator))

outro:

smoothed_points.append (ponto)
return smoothed_points

plt.plot (épocas,

smooth_curve (acc), 'bo', label = 'Treinamento suavizado acc')

plt.plot (épocas,

smooth_curve (val_acc), 'b', label = 'Validação suavizada acc')

plt.title ('Precisão de treinamento e validação')

plt.legend ()

plt.figure ()

plt.plot (épocas,

smooth_curve (perda), 'bo', label = 'perda de treino suavizada')

plt.plot (épocas,

smooth_curve (val_loss), 'b', label = 'Perda de validação suavizada')

plt.title ('Perda de treinamento e validação')

plt.legend ()

plt.show ()

A curva de precisão de validação parece muito mais limpa. Você está vendo uma boa melhoria
absoluta de 1% na precisão, de cerca de 96% para acima de 97%.

Note que a curva de perda não mostra nenhuma melhora real (na verdade, está se
deteriorando). Você pode se perguntar, como a precisão pode permanecer estável ou melhorar
se a perda não estiver diminuindo? A resposta é simples: o que você exibe é uma média de
valores de perdas pontuais; mas o que importa para a precisão é a distribuição dos valores de
perda, não sua média, porque a precisão é o resultado de um limiar binário da probabilidade de
classe prevista pelo modelo. O modelo ainda pode estar melhorando, mesmo que isso não esteja
refletido na perda média.

Agora você pode finalmente avaliar este modelo nos dados de teste:

test_generator = test_datagen.flow_from_directory (

test_dir,

target_size = (150, 150)

batch_size = 20,

class_mode = 'binary')
test_loss, test_acc = model.evaluate_generator (test_generator, etapas = 50)

print ('teste acc:', test_acc)

Aqui você obtém uma precisão de teste de 97%. Na competição original de Kaggle em torno
deste conjunto de dados, este teria sido um dos principais resultados. Mas usando técnicas
modernas de aprendizagem profunda, você conseguiu alcançar esse resultado usando apenas
uma pequena fração dos dados de treinamento disponíveis (cerca de 10%). Existe uma enorme
diferença entre poder treinar 20.000 amostras em comparação com 2.000 amostras!

5.3.3. Empacotando
Aqui está o que você deve tirar dos exercícios nas duas últimas seções:

 As conversas são o melhor tipo de modelos de aprendizado de máquina para tarefas de


visão computacional. É possível treinar um a partir do zero, mesmo em um conjunto de
dados muito pequeno, com resultados decentes.
 Em um pequeno conjunto de dados, o overfitting será o principal problema. O aumento
de dados é uma maneira poderosa de combater o overfitting quando você está
trabalhando com dados de imagem.
 É fácil reutilizar uma convnet existente em um novo conjunto de dados por meio da
extração de recursos. Essa é uma técnica valiosa para trabalhar com conjuntos de dados
de imagem pequena.
 Como complemento à extração de recursos, você pode usar o ajuste fino, que adapta a
um novo problema algumas das representações aprendidas anteriormente por um
modelo existente. Isso aumenta o desempenho um pouco mais.

Agora você tem um conjunto sólido de ferramentas para lidar com problemas de classificação de
imagens - em particular, com pequenos conjuntos de dados.

5.4. VISUALIZANDO O QUE AS CONVERSAS APRENDEM

Costuma-se dizer que os modelos de aprendizagem profunda são “caixas pretas”: representações
de aprendizado que são difíceis de extrair e apresentar de uma forma legível por
humanos. Embora isso seja parcialmente verdadeiro para certos tipos de modelos de
aprendizagem profunda, definitivamente não é verdade para os convnets. As representações
aprendidas por convnets são altamente receptivas à visualização, em grande parte porque
são representações de conceitos visuais . Desde 2013, uma ampla gama de técnicas foi
desenvolvida para visualizar e interpretar essas representações. Não pesquisaremos todos eles,
mas abordaremos três dos mais acessíveis e úteis:

 Visualizando saídas de convnet intermediárias (ativações intermediárias)


- Útil para entender como as camadas de convnet sucessivas transformam suas entradas
e para obter uma primeira ideia do significado dos filtros de convnet individuais.
 Visualizando filtros de convnets - Útil para entender precisamente a que padrão
visual ou conceito cada filtro em uma convnet é receptivo.
 Visualizando heatmaps de ativação de classe em uma imagem - Útil para
entender quais partes de uma imagem foram identificadas como pertencentes a uma
determinada classe, permitindo que você localize objetos em imagens.

Para o primeiro método - visualização de ativação - você usará a pequena convnet que você
treinou do zero no problema de classificação cães versus gatos na seção 5.2 . Para os próximos
dois métodos, você usará o modelo VGG16 apresentado na seção 5.3 .

5.4.1. Visualizando ativações intermediárias


A visualização de ativações intermediárias consiste em exibir os mapas de recursos que são
emitidos por várias camadas de convolução e pooling em uma rede, dada uma determinada
entrada (a saída de uma camada é geralmente chamada de ativação , a saída da função de
ativação). Isso dá uma visão de como uma entrada é decomposta nos diferentes filtros
aprendidos pela rede. Você deseja visualizar mapas de recursos com três dimensões: largura,
altura e profundidade (canais). Cada canal codifica recursos relativamente independentes,
portanto, a maneira correta de visualizar esses mapas de recursos é traçar, independentemente,
o conteúdo de cada canal como uma imagem 2D. Vamos começar carregando o modelo que você
salvou na seção 5.2 :

>>> de keras.models import load_model

>>> model = load_model ('cats_and_dogs_small_2.h5')

>>> model.summary () <1> Como lembrete.

________________________________________________________________

Camada (tipo) Forma de saída Param #

================================================== ==============

conv2d_5 (Conv2D) (Nenhuma, 148, 148, 32) 896

________________________________________________________________

maxpooling2d_5 (MaxPooling2D) (Nenhum, 74, 74, 32) 0

________________________________________________________________

conv2d_6 (Conv2D) (Nenhum, 72, 72, 64) 18496

________________________________________________________________

maxpooling2d_6 (MaxPooling2D) (Nenhum, 36, 36, 64) 0

________________________________________________________________

conv2d_7 (Conv2D) (Nenhum, 34, 34, 128) 73856

________________________________________________________________

maxpooling2d_7 (MaxPooling2D) (Nenhum, 17, 17, 128) 0

________________________________________________________________

conv2d_8 (Conv2D) (Nenhuma, 15, 15, 128) 147584

________________________________________________________________

maxpooling2d_8 (MaxPooling2D) (Nenhum, 7, 7, 128) 0

________________________________________________________________

flatten_2 (achatar) (nenhum, 6272) 0

________________________________________________________________

dropout_1 (Saque) (Nenhum, 6272) 0


________________________________________________________________

denso_3 (denso) (nenhum, 512) 3211776

________________________________________________________________

denso_4 (denso) (nenhum, 1) 513

================================================== ==============

Params totais: 3.453.121

Params treináveis: 3.453.121

Params não treináveis: 0

Em seguida, você receberá uma imagem de entrada - uma foto de um gato, não parte das
imagens em que a rede foi treinada.

Listagem 5.25. Pré-processamento de uma única imagem

img_path =
'/Users/fchollet/Downloads/cats_and_dogs_small/test/cats/cat.1700.jpg'

de imagem de importação keras.preprocessing 1

import numpy como np

img = image.load_img (img_path, target_size = (150, 150))

img_tensor = image.img_to_array (img)

img_tensor = np.expand_dims (img_tensor, axis = 0)

img_tensor / = 255. 2

<1> Sua forma é (1, 150, 150, 3)

imprimir (img_tensor.shape)

 1 Pré-processa a imagem em um tensor 4D


 2 Lembre-se de que o modelo foi treinado em entradas que foram pré-
processadas dessa maneira.

Vamos exibir a imagem (veja a figura 5.24 ).


Figura 5.24. A imagem do gato do teste

Listagem 5.26. Exibindo a imagem do teste

import matplotlib.pyplot como plt

plt.imshow (img_tensor [0])

plt.show ()

Para extrair os mapas de recursos que você deseja examinar, você criará um modelo Keras que
utiliza lotes de imagens como entrada e gera as ativações de todas as camadas de convolução e
pooling. Para fazer isso, você usará a classe Keras Model. Um modelo é instanciado usando dois
argumentos: um tensor de entrada (ou lista de tensores de entrada) e um tensor de saída (ou
lista de tensores de saída). A classe resultante é um modelo Keras, assim como
os Sequentialmodelos com os quais você está familiarizado, mapeando as entradas
especificadas para as saídas especificadas. O que diferencia a Modelclasse é que ela permite
modelos com múltiplas saídas, ao contrário Sequential. Para mais informações sobre
a Modelclasse, consulte a seção 7.1 .

Listagem 5.27. Instanciando um modelo a partir de um tensor de entrada e uma lista de tensores de saída

de modelos de importação keras

layer_outputs = [layer.output para layer em model.layers [: 8]]


1

activation_model = models.Model (entradas = model.input, outputs =


layer_outputs) 2

 1 Extrai as saídas das oito camadas superiores


 2 Cria um modelo que retornará essas saídas, dado o modelo de entrada

Quando alimentado com uma entrada de imagem, este modelo retorna os valores das ativações
de camada no modelo original. Esta é a primeira vez que você encontrou um modelo de
múltiplas saídas neste livro: até agora, os modelos que você viu tiveram exatamente uma
entrada e uma saída. No caso geral, um modelo pode ter qualquer número de entradas e
saídas. Este tem uma entrada e oito saídas: uma saída por ativação de camada.

Listagem 5.28. Executando o modelo no modo de previsão

ativações = activation_model.predict (img_tensor) 1

 1 Retorna uma lista de cinco matrizes Numpy: uma ativação de matriz por
camada

Por exemplo, esta é a ativação da primeira camada de convolução para a entrada de imagem de
gato:

>>> first_layer_activation = ativações [0]

>>> print (first_layer_activation.shape)

(1, 148, 148, 32)

É um mapa de recursos de 148 × 148 com 32 canais. Vamos tentar desenhar o quarto canal da
ativação da primeira camada do modelo original (veja a figura 5.25 ).

Figura 5.25. Quarto canal da ativação da primeira camada na imagem do gato de teste

Listagem 5.29. Visualizando o quarto canal

import matplotlib.pyplot como plt

plt.matshow (first_layer_activation [0,:,:, 4], cmap = 'viridis')

Este canal parece codificar um detector de borda diagonal. Vamos tentar o sétimo canal (veja
a figura 5.26 ) - mas note que seus próprios canais podem variar, porque os filtros específicos
aprendidos pelas camadas de convolução não são deterministas.
Figura 5.26. Sétimo canal da ativação da primeira camada na foto do gato de teste

Listagem 5.30. Visualizando o sétimo canal

plt.matshow (first_layer_activation [0,:,:, 7], cmap = 'viridis')

Este parece um detector de "ponto verde brilhante", útil para codificar os olhos de gato. Neste
ponto, vamos plotar uma visualização completa de todas as ativações na rede (veja a figura
5.27 ). Você irá extrair e traçar cada canal em cada um dos oito mapas de ativação, e você irá
empilhar os resultados em um grande tensor de imagem, com canais empilhados lado a lado.
Figura 5.27. Cada canal de ativação de cada camada na imagem do gato de teste
Listagem 5.31. Visualizando todos os canais em todas as ativações intermediárias

layer_names = [] 1

para camada em model.layers [: 8]:


1

layer_names.append (layer.name) 1

images_per_row = 16

para layer_name, layer_activation em zip (layer_names, ativations): 2

n_features = layer_activation.shape [-1] 3

size = layer_activation.shape [1] 4

n_cols = n_features // images_per_row 5

display_grid = np.zeros ((tamanho * n_cols, images_per_row * size))

para col no intervalo (n_cols):


6

para linha no intervalo (images_per_row):

channel_image = layer_activation [0,

:,:

col * images_per_row + row]

channel_image - = channel_image.mean () 7

channel_image / = channel_image.std ()

channel_image * = 64

channel_image + = 128

channel_image = np.clip (channel_image, 0, 255) .astype ('uint8')

display_grid [col * size: (col + 1) * tamanho,


8

linha * tamanho: (linha + 1) * tamanho] =


channel_image

escala = 1. / tamanho

plt.figure (figsize = (scale * display_grid.shape [1],


scale * display_grid.shape [0]))

plt.title (layer_name)

plt.grid (falso)

plt.imshow (display_grid, aspect = 'auto', cmap = 'viridis')

 1 Nomes das camadas, para que você possa tê-las como parte do seu enredo
 2 Exibe os mapas de recursos
 3 Número de recursos no mapa de recursos
 4 O mapa de recursos tem forma (1, tamanho, tamanho, n_features).
 5 Telhas os canais de ativação nesta matriz
 6 telhas cada filtro em uma grade horizontal grande
 7 Pós-processa o recurso para torná-lo visualmente palatável
 8 Exibe a grade

Há algumas coisas a serem observadas aqui:

 A primeira camada atua como uma coleção de vários detectores de borda. Nesse estágio,
as ativações retêm quase todas as informações presentes no quadro inicial.
 À medida que você sobe, as ativações tornam-se cada vez mais abstratas e menos
visualmente interpretáveis. Eles começam a codificar conceitos de nível mais alto, como
“orelha de gato” e “olho de gato”. Apresentações mais altas têm cada vez menos
informações sobre o conteúdo visual da imagem e cada vez mais informações
relacionadas à classe da imagem.
 A dispersão das ativações aumenta com a profundidade da camada: na primeira
camada, todos os filtros são ativados pela imagem de entrada; mas nas camadas
seguintes, mais e mais filtros estão em branco. Isso significa que o padrão codificado
pelo filtro não é encontrado na imagem de entrada.

Acabamos de evidenciar uma importante característica universal das representações aprendidas


pelas redes neurais profundas: as feições extraídas por uma camada tornam-se cada vez mais
abstratas com a profundidade da camada. As ativações das camadas superiores levam menos e
menos informações sobre a entrada específica sendo vistas, e mais e mais informações sobre o
alvo (neste caso, a classe da imagem: gato ou cachorro). Uma rede neural profunda atua
efetivamente como um pipeline de destilação de informações , com dados brutos entrando
(neste caso, imagens RGB) e sendo repetidamente transformados para que informações
irrelevantes sejam filtradas (por exemplo, a aparência visual específica da imagem) e
informações úteis são ampliadas e refinadas (por exemplo, a classe da imagem).

Isso é análogo ao modo como humanos e animais percebem o mundo: depois de observar uma
cena por alguns segundos, um humano pode lembrar quais objetos abstratos estavam presentes
nele (bicicleta, árvore), mas não consegue se lembrar da aparência específica desses objetos. Na
verdade, se você tentou desenhar uma bicicleta genérica da memória, é provável que não
consiga acertar remotamente, mesmo que tenha visto milhares de bicicletas durante a sua vida
(veja, por exemplo, a figura 5.28 ). Experimente agora mesmo: esse efeito é absolutamente
real. Seu cérebro aprendeu a abstrair completamente sua entrada visual - para transformá-lo em
conceitos visuais de alto nível enquanto filtra detalhes visuais irrelevantes - tornando
tremendamente difícil lembrar como as coisas ao seu redor parecem.
Figura 5.28. Esquerda: tenta tirar uma bicicleta da memória. Direita: como deve ser uma bicicleta esquemática.

5.4.2. Visualizando filtros convnet


Outra maneira fácil de inspecionar os filtros aprendidos por convnets é exibir o padrão visual
para o qual cada filtro deve responder. Isso pode ser feito com a subida do gradiente no espaço
de entrada : aplicando gradiente descendente ao valor da imagem de entrada de uma convnet
de modo a maximizar a resposta de um filtro específico, a partir de uma imagem de entrada em
branco. A imagem de entrada resultante será aquela em que o filtro escolhido é responsivo ao
máximo.

O processo é simples: você criará uma função de perda que maximiza o valor de um
determinado filtro em uma determinada camada de convolução e, em seguida, usará a descida
de gradiente estocástica para ajustar os valores da imagem de entrada para maximizar esse valor
de ativação . Por exemplo, aqui está uma perda para a ativação do filtro 0 na
camada block3_conv1da rede VGG16, pré-configurada no ImageNet.

Listagem 5.32. Definindo o tensor de perda para visualização de filtro

de keras.applications importar VGG16

de backend de importação de keras como K

model = VGG16 (pesos = 'imagenet',

include_top = False)

layer_name = 'block3_conv1'

filter_index = 0

layer_output = model.get_layer (layer_name) .output

loss = K.mean (layer_output [:,:,:, filter_index])

Para implementar gradiente descendente, você precisará do gradiente dessa perda em relação à
entrada do modelo. Para fazer isso, você usará a gradientsfunção empacotada com
o backendmódulo de Keras.
Listagem 5.33. Obtendo o gradiente da perda em relação à entrada

grads = K.gradients (loss, model.input) [0] 1

 1 A chamada para gradientes retorna uma lista de tensores (de tamanho 1


neste caso). Portanto, você mantém apenas o primeiro elemento - que é um
tensor.

Um truque não óbvio a ser usado para ajudar o processo gradiente-descendente a ocorrer sem
problemas é normalizar o tensor do gradiente dividindo-o pela sua norma L2 (a raiz quadrada
da média do quadrado dos valores no tensor). Isso garante que a magnitude das atualizações
feitas na imagem de entrada esteja sempre dentro do mesmo intervalo.

Listagem 5.34. Truque de normalização de gradiente

graduados / = (K.sqrt (K.mean (K.square (grados))) + 1e-5) 1

 1 Adicione 1e – 5 antes de dividir para evitar dividir acidentalmente por 0.

Agora você precisa de uma maneira de calcular o valor do tensor de perda e do tensor de
gradiente, dada uma imagem de entrada. Você pode definir uma função de backend Keras para
fazer isso: iterateé uma função que usa um tensor Numpy (como uma lista de tensores de
tamanho 1) e retorna uma lista de dois tensores de Numpy: o valor de perda e o valor do
gradiente.

Listagem 5.35. Obtendo valores de saída numpy dados valores de entrada numpy

iterar = K.function ([model.input], [perda, grados])

import numpy como np

loss_value, grads_value = iterar ([np.zeros ((1, 150, 150, 3))])

Neste ponto, você pode definir um loop de Python para fazer uma descida de gradiente
estocástica.

Listagem 5.36. Maximização de perdas via gradiente descendente estocástico

input_img_data = np.random.random ((1, 150, 150, 3)) * 20 + 128. 1

passo = 1. 2 3

para i na faixa (40): 3

loss_value, grads_value = iterar ([input_img_data]) 4 3

input_img_data + = grads_value * passo 5 3

 1 Começa a partir de uma imagem cinza com algum ruído


 2 Magnitude de cada atualização gradiente
 3 corre subida gradiente para 40 passos
 4 Calcula o valor da perda e o valor do gradiente
 5 Ajusta a imagem de entrada na direção que maximiza a perda
O tensor de imagem resultante é um tensor de ponto flutuante de forma (1, 150, 150, 3),
com valores que podem não ser números inteiros dentro de [0, 255]. Portanto, você precisa pós-
processar este tensor para transformá-lo em uma imagem exibível. Você faz isso com a seguinte
função de utilidade direta.

Listagem 5.37. Função de utilidade para converter um tensor em uma imagem válida

def deprocess_image (x):

x - = x.mean () 1

x / = (x.std () + 1e-5) 1

x * = 0,1 1

x + = 0,5 2

x = np.clip (x, 0, 1) 2

x * = 255 3

x = np.clip (x, 0, 255) .astype ('uint8') 3

return x 3

 1 Normaliza o tensor: centra em 0, assegura que std é 0.1


 2 clipes para [0, 1]
 3 Converte para um array RGB

Agora você tem todas as peças. Vamos colocá-los juntos em uma função Python que recebe
como entrada um nome de camada e um índice de filtro, e retorna um tensor de imagem válido
representando o padrão que maximiza a ativação do filtro especificado.

Listagem 5.38. Função para gerar visualizações de filtro

def generate_pattern (layer_name, filter_index, size = 150):

layer_output = model.get_layer (layer_name) .output 1

perda = K.mean (layer_output [:,:,:, filter_index]) 1

grads = K.gradients (loss, model.input) [0] 2

graduados / = (K.sqrt (K.mean (K.square (grados))) + 1e-5)


3

iterate = K.function ([model.input], [loss, grads]) 4

input_img_data = np.random.random ((1, tamanho, tamanho, 3)) * 20 + 128.


5
passo = 1. 6

para i no intervalo (40):


6

loss_value, grads_value = iterar ([input_img_data]) 6

input_img_data + = grads_value * passo 6

img = input_img_data [0]

retornar deprocess_image (img)

 1 Cria uma função de perda que maximiza a ativação do enésimo filtro da


camada em consideração
 2 Calcula o gradiente da imagem de entrada em relação a essa perda
 3 truque de normalização: normaliza o gradiente
 4 Retorna a perda e os graduados dados à imagem de entrada
 5 Inicia a partir de uma imagem cinza com algum ruído
 6 corre subida gradiente para 40 passos

Vamos tentar (veja a figura 5.29 ):

>>> plt.imshow (generate_pattern ('block3_conv1', 0))

Figura 5.29. Padrão que o canal zeroth na camada block3_conv1responde ao máximo

Parece que o filtro 0 na camada block3_conv1responde a um padrão de bolinhas. Agora a


parte divertida: você pode começar a visualizar todos os filtros em todas as camadas. Para
simplificar, você só vai olhar para os primeiros 64 filtros em cada camada, e você só vai olhar
para a primeira camada de cada bloco de convolução
( block1_conv1, block2_conv1, block3_conv1, block4_conv1, block5_conv1). Você
organizará as saídas em uma grade de 8 × 8 de 64 × 64 padrões de filtro, com algumas margens
pretas entre cada padrão de filtro (ver figuras 5.30 - 5.33 ).
Figura 5.30. Padrões de filtro para camada block1_conv1

Figura 5.31. Padrões de filtro para camada block2_conv1


Figura 5.32. Padrões de filtro para camada block3_conv1

Figura 5.33. Padrões de filtro para camada block4_conv1

Listagem 5.39. Gerando uma grade de todos os padrões de resposta do filtro em uma camada

layer_name = 'block1_conv1'
tamanho = 64

margem = 5

resultados = np.zeros ((8 * tamanho + 7 * margem, 8 * tamanho + 7 * margem,


3)) 1

para i na faixa (8):


2

para j na faixa (8):


3

filter_img = generate_pattern (layer_name, i + (j * 8), tamanho =


tamanho) 4

horizontal_start = i * tamanho + i * margem


5

horizontal_end = horizontal_start + tamanho


5

vertical_start = j * tamanho + j * margem


5

vertical_end = vertical_start + tamanho


5

resultados [horizontal_start: horizontal_end,


5

vertical_start: vertical_end,:] = filter_img 5

plt.figure (figsize = (20, 20))


6

plt.imshow (resultados)
6

 1 Imagem vazia (preta) para armazenar resultados


 2 Itera sobre as linhas da grade de resultados
 3 Itera sobre as colunas da grade de resultados
 4 Gera o padrão para o filtro i + (j * 8) em layer_name
 5 Coloca o resultado no quadrado (i, j) da grelha de resultados
 6 Exibe a grade de resultados

Essas visualizações de filtro informam muito sobre como as camadas convnet veem o mundo:
cada camada em uma convnet aprende uma coleção de filtros de modo que suas entradas
possam ser expressas como uma combinação dos filtros. Isso é semelhante a como a
transformada de Fourier decompõe os sinais em um banco de funções cosseno. Os filtros nesses
bancos de filtros convnet se tornam cada vez mais complexos e refinados à medida que você
sobe no modelo:
 Os filtros da primeira camada no modelo ( block1_conv1) codificam bordas e cores
direcionais simples (ou bordas coloridas, em alguns casos).
 Os filtros de block2_conv1codificar texturas simples feitas de combinações de arestas
e cores.
 Os filtros nas camadas superiores começam a assemelhar-se a texturas encontradas em
imagens naturais: penas, olhos, folhas e assim por diante.

5.4.3. Visualizando heatmaps de ativação de classe


Vou apresentar mais uma técnica de visualização: uma que é útil para entender quais partes de
uma determinada imagem levaram uma convnet a sua decisão final de classificação. Isso é útil
para depurar o processo de decisão de um convnet, particularmente no caso de um erro de
classificação. Também permite localizar objetos específicos em uma imagem.

Essa categoria geral de técnicas é chamada de visualização de mapa de ativação de


classe (CAM) e consiste em produzir heatmaps de ativação de classe sobre imagens de
entrada. Um mapa de calor de ativação de classe é uma grade 2D de pontuações associada a uma
classe de saída específica, calculada para cada local em qualquer imagem de entrada, indicando
a importância de cada local em relação à classe em consideração. Por exemplo, dada uma
imagem alimentada em uma convnet cães versus gatos, a visualização CAM permite que você
gere um mapa de calor para a classe “gato”, indicando como partes diferentes da imagem são
semelhantes à da gata, e também um mapa de calor para a classe. “Cachorro”, indicando como
são as partes semelhantes a cães da imagem.

A implementação específica que você usará é aquela descrita em “Grad-CAM: Explicações


Visuais de Redes Profundas por Localização Baseada em Gradiente.” [ 2 ] É muito simples:
consiste em pegar o mapa de características de saída de uma camada de convolução, dado uma
imagem de entrada e a pesagem de todos os canais desse mapa de recursos pelo gradiente da
classe em relação ao canal. Intuitivamente, uma maneira de entender esse truque é que você
está ponderando um mapa espacial de "quão intensamente a imagem de entrada ativa diferentes
canais" por "quão importante cada canal é em relação à classe", resultando em um mapa
espacial de "como intensamente a imagem de entrada ativa a classe. ”

Ramprasaath R. Selvaraju e outros, arXiv (2017), https://arxiv.org/abs/1610.02391 .

Demonstraremos essa técnica usando a rede VGG16 pré-treinada novamente.

Listagem 5.40. Carregando a rede VGG16 com pesos pré-tratados

de keras.applications.vgg16 import VGG16

modelo = VGG16 (pesos = 'imagenet') 1

 1 Observe que você inclui o classificador densamente conectado na parte


superior; em todos os casos anteriores, você descartou.

Considere a imagem de dois elefantes africanos mostrados na figura 5.34 (sob uma licença
Creative Commons), possivelmente uma mãe e seu filhote, passeando pela savana. Vamos
converter essa imagem em algo que o modelo VGG16 possa ler: o modelo foi treinado em
imagens de tamanho 224 × 244, pré-processadas de acordo com algumas regras que são
empacotadas na função de
utilidade keras.applications.vgg16.preprocess_input. Portanto, você precisa
carregar a imagem, redimensioná-la para 224 × 224, convertê-la em
um float32tensor Numpy e aplicar essas regras de pré-processamento.

Figura 5.34. Imagem de teste de elefantes africanos

Listagem 5.41 Pré-processamento de uma imagem de entrada para o VGG16

da imagem de importação keras.preprocessing

de keras.applications.vgg16 import preprocess_input, decode_predictions

import numpy como np

img_path = '/Users/fchollet/Downloads/creative_commons_elephant.jpg' 1

img = image.load_img (img_path, target_size = (224, 224)) 2

x = image.img_to_array (img) 3

x = np.expand_dims (x, eixo = 0) 4

x = preprocess_input (x) 5

 1 Caminho local para a imagem de destino


 2 Python Imaging Library (PIL) imagem do tamanho 224 × 224
 3 float32 Matriz numpy de forma (224, 224, 3)
 4 Adiciona uma dimensão para transformar a matriz em um lote de
tamanho (1, 224, 224, 3)
 5 Pré-processa o lote (isso faz a normalização de cor nos canais)
Agora você pode executar a rede pré-treinada na imagem e decodificar seu vetor de previsão de
volta para um formato legível:

>>> preds = model.predict (x)

>>> print ('Predicted:', decode_predictions (preds, top = 3) [0])

Previsto: ', [(u'n02504458', u'African_elephant ', 0,92546833),

(u'n01871265 ', u'tusker', 0.070257246),

(u'n02504013 ', u'indian_elephant', 0,0042589349)]

As três principais classes previstas para esta imagem são as seguintes:

 Elefante africano (com 92,5% de probabilidade)


 Tusker (com 7% de probabilidade)
 Elefante indiano (com probabilidade de 0,4%)

A rede reconheceu a imagem como contendo uma quantidade indeterminada de elefantes


africanos. A entrada no vetor de previsão que foi ativada ao máximo é a correspondente à classe
“elefante africano”, no índice 386:

>>> np.argmax (preds [0])

386

Para visualizar quais partes da imagem são mais parecidas com elefantes africanos, vamos
configurar o processo Grad-CAM.

Listagem 5.42. Configurando o algoritmo Grad-CAM

african_e66lephant_output = model.output [:, 386] 1

last_conv_layer = model.get_layer ('block5_conv3') 2

grads = K.gradients (african_elephant_output, last_conv_layer.output) [0] 3

pooled_grads = K.mean (grads, axis = (0, 1, 2))


4

iterate = K.function ([model.input],

[pooled_grads, last_conv_layer.output [0]]) 5

pooled_grads_value, conv_layer_output_value = iterar ([x]) 6

para i no intervalo (512):


7
conv_layer_output_value [:,:, i] * = pooled_grads_value [i] 7

heatmap = np.mean (conv_layer_output_value, axis = -1)


8

 1 entrada “elefante africano” no vetor de previsão


 2 Mapa do recurso de saída da camada block5_conv3, a última camada
convolucional do VGG16
 3 Gradiente da classe “elefante africano” em relação ao mapa de
características de saída de block5_conv3
 4 Vetor de forma (512,), em que cada entrada é a intensidade média do
gradiente sobre um canal de mapa de características específico
 5 Permite acessar os valores das quantidades que você acabou de definir:
pooled_grads e o mapa do recurso de saída de block5_conv3, dado um
exemplo de imagem
 6 Valores dessas duas grandezas, como matrizes de Numpy, dadas a
imagem de amostra de dois elefantes
 7 Multiplica cada canal na matriz de mapa de recursos por "quão
importante é este canal" em relação à classe "elefante"
 8 O significado do canal do mapa de recursos resultante é o mapa de calor
da ativação da classe.

Para fins de visualização, você também normalizará o mapa de calor entre 0 e 1. O resultado é
mostrado na figura 5.35 .

Figura 5.35. Calor de ativação de classe de elefante africano sobre a imagem de teste

Listagem 5.43. Pós-processamento de heatmap

mapa de calor = np.maximum (mapa de calor, 0)

mapa de calor / = np.max (mapa de calor)

plt.matshow (mapa de calor)

Finalmente, você usará o OpenCV para gerar uma imagem que sobrepõe a imagem original no
mapa de calor que você acabou de obter (veja a figura 5.36 ).
Figura 5.36. Sobrepondo o mapa de calor de ativação de classe na imagem original

Listagem 5.44. Sobrepondo o mapa de calor com a imagem original

importar cv2

img = cv2.imread (img_path) 1

mapa de calor = cv2.resize (mapa de calor, (img.shape [1], img.shape [0]))


2

mapa de calor = np.uint8 (255 * mapa de calor)


3

heatmap = cv2.applyColorMap (mapa de calor, cv2.COLORMAP_JET)


4

superimposed_img = mapa de calor * 0,4 + img


5

cv2.imwrite ('/ Users / fchollet / Downloads / elephant_cam.jpg',


superimposed_img) 6

 1 Usa o cv2 para carregar a imagem original


 2 Redimensiona o mapa de calor para o mesmo tamanho da imagem
original
 3 Converte o mapa de calor em RGB
 4 Aplica o mapa de calor à imagem original
 5 0,4 aqui é um fator de intensidade do mapa de calor.
 6 Salva a imagem no disco

Esta técnica de visualização responde a duas questões importantes:

 Por que a rede achou que essa imagem continha um elefante africano?
 Onde está o elefante africano localizado na foto?

Em particular, é interessante notar que as orelhas do filhote de elefante estão fortemente


ativadas: é provavelmente assim que a rede pode dizer a diferença entre elefantes africanos e
indianos.

Resumo do capítulo

 Os convnets são a melhor ferramenta para atacar problemas de classificação visual.


 Reúne o trabalho aprendendo uma hierarquia de padrões e conceitos modulares para
representar o mundo visual.
 As representações que aprendem são fáceis de inspecionar - as convnets são o oposto
das caixas pretas!
 Agora você é capaz de treinar seu próprio convnet do zero para resolver um problema de
classificação de imagem.
 Você entende como usar o aumento de dados visuais para combater o overfitting.
 Você sabe como usar uma convnet pré-treinada para fazer extração e ajuste de recursos.
 Você pode gerar visualizações dos filtros aprendidos pelos seus convnets, bem como
heatmaps da atividade de classe.

Capítulo 6. Aprendizagem profunda para texto e


sequências
Este capítulo cobre

 Pré-processamento de dados de texto em representações úteis


 Trabalhando com redes neurais recorrentes
 Usando 1D convnets para processamento sequencial

Este capítulo explora modelos de aprendizagem profunda que podem processar textos
(entendidos como seqüências de palavras ou sequências de caracteres), séries de tempo e dados
de sequência em geral. Os dois algoritmos de aprendizagem profunda fundamentais para o
processamento de sequências são as redes neurais recorrentes e as conversões 1D , a versão
unidimensional das convnets 2D que abordamos nos capítulos anteriores. Vamos discutir ambas
as abordagens neste capítulo.

Aplicações desses algoritmos incluem o seguinte:

 Classificação de documentos e classificação de timeseries, como identificar o tópico de


um artigo ou o autor de um livro
 Comparações de séries temporais, como a estimativa de quão próximos dois
documentos estão relacionados ou dois tickers de ações
 Aprendizagem de seqüência a sequência, como decodificação de uma sentença em inglês
para francês
 Análise de sentimentos, como classificar o sentimento de tweets ou resenhas de filmes
como positivos ou negativos
 Previsão de séries temporais, como previsão do tempo futuro em um determinado local,
dados meteorológicos recentes

Os exemplos deste capítulo concentram-se em duas tarefas estreitas: análise de sentimento no


conjunto de dados do IMDB, uma tarefa que abordamos anteriormente no livro e previsão de
temperatura. Mas as técnicas demonstradas para essas duas tarefas são relevantes para todos os
aplicativos listados e muito mais.

6.1. TRABALHANDO COM DADOS DE TEXTO

O texto é uma das formas mais difundidas de dados de sequência. Pode ser entendido como uma
sequência de caracteres ou uma sequência de palavras, mas é mais comum trabalhar no nível
das palavras. Os modelos de processamento de sequências de aprendizado profundo
introduzidos nas seções a seguir podem usar texto para produzir uma forma básica de
compreensão de linguagem natural, suficiente para aplicativos que incluem classificação de
documentos, análise de sentimentos, identificação de autores e até perguntas e respostas (QA)
(em um contexto restrito). É claro, tenha em mente ao longo deste capítulo que nenhum desses
modelos de aprendizagem profunda realmente entende o texto em um sentido humano; em vez
disso, esses modelos podem mapear a estrutura estatística da linguagem escrita, o que é
suficiente para resolver muitas tarefas textuais simples.

Como todas as outras redes neurais, os modelos de aprendizagem profunda não aceitam como
texto bruto de entrada: eles só funcionam com tensores numéricos. Vectorizing text é o processo
de transformar texto em tensores numéricos. Isso pode ser feito de várias maneiras:

 Segmente o texto em palavras e transforme cada palavra em um vetor.


 Segmente o texto em caracteres e transforme cada caractere em um vetor.
 Extraia n-gramas de palavras ou caracteres e transforme cada n-grama em um vetor. N-
grams são grupos sobrepostos de várias palavras ou caracteres consecutivos.

Coletivamente, as diferentes unidades em que você pode dividir o texto (palavras, caracteres ou
n-gramas) são chamadas de tokens , e dividir o texto em tais tokens é chamado
de tokenização . Todos os processos de vetorização de vetores consistem em aplicar algum
esquema de tokenização e, em seguida, associar vetores numéricos aos tokens gerados. Esses
vetores, compactados em tensores de seqüência, são alimentados em redes neurais
profundas. Existem várias maneiras de associar um vetor a um token. Nesta seção, apresentarei
dois principais: codificação de tokens de um ponto a quente e incorporação de
token (normalmente usado exclusivamente para palavras e denominado incorporação de
palavras). O restante desta seção explica essas técnicas e mostra como usá-las para passar do
texto bruto para um tensor Numpy que você pode enviar para uma rede Keras.
Figura 6.1. Do texto aos tokens aos vetores

Noções básicas sobre n-grams e bag-of-words

Word n-grams são grupos de N (ou menos) palavras consecutivas que você pode extrair de uma
frase. O mesmo conceito também pode ser aplicado a caracteres em vez de palavras.

Aqui está um exemplo simples. Considere a frase “O gato sentou-se no tapete”. Ele pode ser
decomposto no seguinte conjunto de 2 gramas:

{"O", "O gato", "gato", "gato sentado", "sentado"

"sentado", "ligado", "no", "o", "o tapete", "tapete"}

Também pode ser decomposto no seguinte conjunto de 3 gramas:

{"O", "O gato", "gato", "gato sentado", "O gato sentado",

"sentado", "sentado", "ligado", "sentado gato", "no", "o",

"sentou-se no", "o tapete", "tapete", "no tapete"}

Tal conjunto é chamado de saco de 2 gramas ou saco de 3 gramas , respectivamente. O


termo sacoaqui se refere ao fato de que você está lidando com um conjunto de símbolos em vez
de uma lista ou sequência: os tokens não têm uma ordem específica. Essa família de métodos de
tokenização é chamada de bag-of-words .

Como o bag-of-words não é um método de tokenização que preserva a ordem (os tokens gerados
são entendidos como um conjunto, não uma sequência e a estrutura geral das sentenças é
perdida), ele tende a ser usado em processamento de linguagem superficial modelos, em vez de
modelos de aprendizagem profunda. A extração de n-grams é uma forma de engenharia de
recursos, e o aprendizado profundo acaba com esse tipo de abordagem rígida e quebradiça,
substituindo-a por aprendizado de recurso hierárquico. As convnets unidimensionais e as redes
neurais recorrentes, apresentadas mais adiante neste capítulo, são capazes de aprender
representações para grupos de palavras e caracteres sem serem explicitamente informados
sobre a existência de tais grupos, observando sequências contínuas de palavras ou
caracteres. Por esta razão, Não vamos cobrir mais n-gramas neste livro. Mas lembre-se de que
eles são uma ferramenta de engenharia de recursos poderosa e inevitável ao usar modelos de
processamento de texto simples e superficiais, como a regressão logística e florestas aleatórias.
6.1.1. Uma quente codificação de palavras e caracteres
Uma codificação quente é a maneira mais comum e básica de transformar um token em um
vetor. Você viu isso em ação nos exemplos iniciais do IMDB e Reuters no capítulo 3 (feito com
palavras, nesse caso). Ele consiste em associar um índice inteiro única com cada palavra e, em
seguida, transformar este índice inteiro i num vector binário de tamanho N (o tamanho do
vocabulário); o vetor é todo zeros, exceto pela i ésima entrada, que é 1.

Naturalmente, uma codificação a quente também pode ser feita no nível do personagem. Para
indubitavelmente levar para casa o que é uma codificação quente e como implementá-la,
as listagens 6.1e 6.2 mostram dois exemplos de brinquedo: um para palavras e outro para
caracteres.

Listagem 6.1. Codificação one-hot no nível da palavra (exemplo de brinquedo)

import numpy como np

samples = ['O gato sentou no tatame.', 'O cachorro comeu meu dever de casa.']
1

token_index = {} 2

para amostra em amostras:

por palavra em sample.split (): 3

se a palavra não estiver no token_index:

token_index [word] = len (token_index) + 1 4

max_length = 10 5

results = np.zeros (forma = (len (amostras),

comprimento máximo,

max (token_index.values ()) + 1)) 6

para i, amostra em enumerar (amostras):

para j, palavra na lista (enumerate (sample.split ())) [: max_length]:

índice = token_index.get (palavra)

resultados [i, j, index] = 1.

 1 Dados iniciais: uma entrada por amostra (neste exemplo, uma amostra é
uma sentença, mas poderia ser um documento inteiro)
 2 Cria um índice de todos os tokens nos dados
 3 Tokeniza as amostras pelo método de divisão. Na vida real, você também
tira pontos e caracteres especiais das amostras.
 4 Atribui um índice exclusivo para cada palavra única. Observe que você
não atribui o índice 0 a nada.
 5 Vectoriza as amostras. Você só considerará as primeiras palavras
max_length em cada amostra.
 6 Aqui é onde você armazena os resultados.

Listagem 6.2. Codificação one-hot em nível de caractere (exemplo de brinquedo)

string de importação

samples = ['O gato sentou no tatame.', 'O cachorro comeu meu dever de casa.']

caracteres = string.printable 1

token_index = dict (zip (intervalo (1, len (caracteres) + 1), caracteres))

max_length = 50

results = np.zeros ((len (amostras), max_length, max (token_index.keys ()) +


1))

para i, amostra em enumerar (amostras):

para j, caractere em enumerar (amostra):

índice = token_index.get (caractere)

resultados [i, j, index] = 1.

 1 Todos os caracteres ASCII imprimíveis

Observe que o Keras possui utilitários integrados para fazer uma codificação de texto quente no
nível de palavra ou nível de caractere, a partir de dados de texto bruto. Você deve usar esses
utilitários, pois eles cuidam de vários recursos importantes, como descartar caracteres especiais
de strings e levar em conta apenas as N palavras mais comuns em seu conjunto de dados (uma
restrição comum, para evitar lidar com espaços vetoriais de entrada muito grandes ).

Listagem 6.3. Usando Keras para codificação one-hot em nível de palavra

do Tokenizer de importação keras.preprocessing.text

samples = ['O gato sentou no tatame.', 'O cachorro comeu meu dever de casa.']

tokenizer = Tokenizer (num_words = 1000) 1

tokenizer.fit_on_texts (amostras) 2

seqüências = tokenizer.texts_to_sequences (samples) 3


one_hot_results = tokenizer.texts_to_matrix (amostras, modo = 'binário')
4

word_index = tokenizer.word_index 5

print ('Encontrado% s tokens exclusivos'% len (word_index))

 1 Cria um tokenizador, configurado para levar em conta apenas as 1.000


palavras mais comuns
 2 Constrói o índice da palavra
 3 Constrói o índice da palavra
 4 Você também pode obter diretamente as representações binárias
simples. Modos de vetorização diferentes da codificação de um-quente são
suportados por este tokenizer.
 5 Como você pode recuperar o índice de palavras que foi calculado

Uma variante da codificação one-hot é o chamado truque de hash quente , que você pode usar
quando o número de tokens exclusivos no seu vocabulário for muito grande para manipular
explicitamente. Em vez de atribuir explicitamente um índice a cada palavra e manter uma
referência desses índices em um dicionário, você pode dividir as palavras em vetores de
tamanho fixo. Isso geralmente é feito com uma função de hash muito leve. A principal vantagem
deste método é que ele acaba com a manutenção de um índice de palavras explícito, que
economiza memória e permite a codificação online dos dados (você pode gerar vetores de token
imediatamente, antes de ver todos os dados disponíveis). A única desvantagem dessa
abordagem é que ela é suscetível a colisões de hash: duas palavras diferentes podem acabar com
o mesmo hash, e subseqüentemente qualquer modelo de aprendizado de máquina olhando para
esses hashes não será capaz de dizer a diferença entre essas palavras. A probabilidade de
colisões de hash diminui quando a dimensionalidade do espaço de hash é muito maior do que o
número total de tokens exclusivos sendo hash.

Listagem 6.4. Codificação one-hot no nível da palavra com truque de hashing (exemplo de brinquedo)

samples = ['O gato sentou no tatame.', 'O cachorro comeu meu dever de casa.']

dimensionalidade = 1000 1

max_length = 10

resultados = np.zeros ((len (amostras), max_length, dimensionality))

para i, amostra em enumerar (amostras):

para j, palavra na lista (enumerate (sample.split ())) [: max_length]:

índice = abs (hash (palavra))% dimensionalidade


2

resultados [i, j, index] = 1.

 1 Armazena as palavras como vetores de tamanho 1.000. Se você tiver cerca


de 1.000 palavras (ou mais), verá muitas colisões de hash, o que diminuirá
a precisão desse método de codificação.
 2 Hashes a palavra em um índice inteiro aleatório entre 0 e 1.000
6.1.2. Usando o Word Embeddings
Outra maneira popular e poderosa de associar um vetor a uma palavra é o uso de vetores de
palavrasdensas , também chamadas de incorporação de palavras . Considerando que os
vetores obtidos através de uma codificação quente são binários, esparsos (na maior parte feitos
de zeros), e muito alta-dimensional (mesma dimensionalidade que o número de palavras no
vocabulário), a palavra embeddings são vetores de ponto flutuante de baixa dimensão (que é,
vetores densos, ao contrário de vetores esparsos); veja a figura 6.2. Ao contrário da palavra
vetores obtidos por meio de uma codificação simples, os embeddings de palavras são aprendidos
a partir dos dados. É comum ver a incorporação de palavras que são 256-dimensionais, 512-
dimensionais ou 1.024-dimensionais ao lidar com vocabulários muito grandes. Por outro lado,
palavras quentes de codificação geralmente levam a vetores de 20.000 ou mais dimensões
(capturando um vocabulário de 20.000 tokens, neste caso). Assim, os embeddings de palavras
contêm mais informações em menos dimensões.

Figura 6.2. Enquanto as representações de palavras obtidas a partir de uma codificação ou hash quente são esparsas, de alta
dimensão e codificadas, as incorporações de palavras são densas, relativamente pouco dimensionais e aprendidas com os
dados.

Existem duas maneiras de obter embeddings de palavras:

 Aprenda a incorporação de palavras em conjunto com a tarefa principal de que você


gosta (como classificação de documentos ou previsão de sentimentos). Nesta
configuração, você começa com vetores de palavras aleatórias e, em seguida, aprende
vetores de palavras da mesma maneira que aprende os pesos de uma rede neural.
 Carregue em seu modelo de incorporação de palavras que foram pré-computadas
usando uma tarefa de aprendizado de máquina diferente daquela que você está
tentando resolver. Isso é chamado de incorporação de palavras pré-encadeadas .

Vamos ver os dois.

Aprendendo a palavra incorporação com a camada Embedding

A maneira mais simples de associar um vetor denso a uma palavra é escolher o vetor
aleatoriamente. O problema com essa abordagem é que o espaço de incorporação resultante não
tem estrutura: por exemplo, as palavras precisas e exatas podem ter inclusões completamente
diferentes, embora sejam intercambiáveis na maioria das sentenças. É difícil para uma rede
neural profunda dar sentido a um espaço de incorporação tão ruidoso e não estruturado.

Para obter um pouco mais abstrato, as relações geométricas entre os vetores de palavras devem
refletir as relações semânticas entre essas palavras. Os embeddings do Word destinam-se a
mapear a linguagem humana para um espaço geométrico. Por exemplo, em um espaço de
incorporação razoável, você esperaria que os sinônimos fossem incorporados em vetores de
palavras semelhantes; e, em geral, você esperaria que a distância geométrica (como a distância
L2) entre quaisquer dois vetores de palavras se relacionasse com a distância semântica entre as
palavras associadas (palavras significando que coisas diferentes são embutidas em pontos
distantes umas das outras, enquanto palavras relacionadas são mais perto). Além da distância,
você pode querer direções específicas no espaço de incorporação para ser significativo. Para
tornar isso mais claro, vamos dar uma olhada em um exemplo concreto.

Na figura 6.3 , quatro palavras estão embutidas em um plano


2D: gato , cachorro , lobo e tigre . Com as representações vetoriais que escolhemos aqui,
algumas relações semânticas entre essas palavras podem ser codificadas como transformações
geométricas. Por exemplo, o mesmo vetor nos permite ir de gatoa tigre e de cão a lobo : esse
vetor pode ser interpretado como o vetor “do animal de estimação ao animal selvagem”. Da
mesma forma, outro vetor nos permite ir de cão para gato e de lobo para tigre, o que poderia ser
interpretado como um vetor "do canino ao felino".

Figura 6.3. Um exemplo de brinquedo de um espaço de incorporação de palavras

Em espaços reais de incorporação de palavras, exemplos comuns de transformações geométricas


significativas são vetores de “gênero” e vetores “plurais”. Por exemplo, adicionando um vetor
“feminino” ao vetor “rei”, obtemos o vetor “rainha”. Adicionando um vetor “plural”, obtemos
“reis”. Os espaços de incorporação de palavras geralmente apresentam milhares de tais vetores
interpretáveis e vetores potencialmente úteis.

Existe algum espaço de incorporação de palavras ideal que mapeie perfeitamente a linguagem
humana e possa ser usado para qualquer tarefa de processamento de linguagem
natural? Possivelmente, mas ainda temos que computar qualquer coisa do tipo. Além disso, não
existe linguagem humana- Existem muitas línguas diferentes e elas não são isomórficas, porque
uma linguagem é o reflexo de uma cultura específica e de um contexto específico. Mas, de forma
mais pragmática, o que faz um bom espaço para a palavra depende muito de sua tarefa: o espaço
perfeito para incorporar palavras a um modelo de análise de opinião de filme em inglês pode
parecer diferente do espaço ideal para uma linguagem jurídica em inglês. - modelo de
classificação de documentos, porque a importância de certos relacionamentos semânticos varia
de tarefa para tarefa.

Portanto, é razoável aprender um novo espaço de incorporação a cada nova tarefa. Felizmente, a
retropropagação torna isso fácil, e o Keras torna isso ainda mais fácil. É sobre aprender os pesos
de uma camada: a Embeddingcamada.

Listagem 6.5. Instanciando uma Embeddingcamada

de keras.layers import Embutimento


embedding_layer = Incorporação (1000, 64) 1

 1 A camada Incorporação leva pelo menos dois argumentos: o número de


tokens possíveis (aqui, 1.000: 1 + índice máximo de palavras) e a
dimensionalidade dos envoltórios (aqui, 64).

A Embeddingcamada é melhor entendida como um dicionário que mapeia índices inteiros (que
representam palavras específicas) para vetores densos. Ele recebe inteiros como entrada,
procura esses inteiros em um dicionário interno e retorna os vetores associados. É efetivamente
uma pesquisa de dicionário (veja a figura 6.4 ).

Figura 6.4. A embeddingcamada

A Embeddingcamada toma como entrada um tensor 2D de inteiros, de forma (samples,


sequence_length), onde cada entrada é uma seqüência de inteiros. Ele pode incorporar
sequências de comprimentos variáveis: por exemplo, você poderia inserir a Embeddingcamada
nos lotes de exemplo anteriores com formas (32, 10)(lote de 32 sequências de comprimento
10) ou (64, 15)(lote de 64 sequências de comprimento 15). Todas as seqüências em um lote
devem ter o mesmo comprimento (embora seja necessário empacotá-las em um único tensor),
portanto as seqüências mais curtas que outras devem ser preenchidas com zeros e as sequências
mais longas devem ser truncadas.

Essa camada retorna um tensor de ponto flutuante 3D de forma (samples,


sequence_length, embedding_dimensionality). Tal tensor 3D pode então ser
processado por uma camada RNN ou uma camada de convolução 1D (ambas serão introduzidas
nas seções seguintes).

Quando você instancia uma Embeddingcamada, seus pesos (seu dicionário interno de vetores
de token) são inicialmente aleatórios, assim como com qualquer outra camada. Durante o
treinamento, esses vetores de palavras são gradualmente ajustados via retropropagação,
estruturando o espaço em algo que o modelo a jusante pode explorar. Uma vez totalmente
treinado, o espaço de incorporação mostrará muita estrutura - um tipo de estrutura
especializada para o problema específico para o qual você está treinando seu modelo.

Vamos aplicar essa ideia à tarefa de previsão do sentimento de revisão de filme do IMDB com a
qual você já está familiarizado. Primeiro, você preparará rapidamente os dados. Você restringirá
as resenhas de filmes às 10 mil palavras mais comuns (como fez na primeira vez que trabalhou
com esse conjunto de dados) e cortará as resenhas depois de apenas 20 palavras. A rede
iráaprenda embedings de 8 dimensões para cada uma das 10.000 palavras, gire as sequências
inteiras de entrada (tensor inteiro 2D) em sequências embutidas (tensor de flutuação 3D),
achatar o tensor para 2D e treinar uma única Densecamada no topo para classificação.

Listagem 6.6. Carregando os dados do IMDB para uso com uma Embeddingcamada

de keras.datasets import imdb

do pré-processamento de importação do keras

max_features = 10000 1

maxlen = 20 2
(x_train, y_train), (x_test, y_test) = imdb.load_data (

num_words = max_features) 3

x_train = preprocessing.sequence.pad_sequences (x_train, maxlen = maxlen 4

x_test = preprocessing.sequence.pad_sequences (x_test, maxlen = maxlen)

 1 Número de palavras a considerar como recursos


 2 Corta o texto após este número de palavras (entre as palavras mais
comuns max_features)
 3 Carrega os dados como listas de inteiros
 4 Ativa as listas de inteiros em um tensor inteiro 2D de forma (samples,
maxlen)

Listagem 6.7. Usando uma Embeddingcamada e um classificador nos dados do IMDB

de keras.models import Sequential

de keras.layers import Flatten, denso

model = Sequential ()

model.add (Incorporação (10000, 8, input_length = maxlen))


1

model.add (Flatten ())


2

model.add (denso (1, ativação = 'sigmóide'))


3

model.compile (optimizer = 'rmsprop', loss = 'binary_crossentropy', métricas =


['acc'])

model.summary ()

history = model.fit (x_train, y_train,

épocas = 10,

batch_size = 32,

validation_split = 0,2)

 1 Especifica o comprimento máximo de entrada para a camada


Incorporação, para que você possa nivelar posteriormente as entradas
incorporadas. Após a camada Embutindo, as ativações têm forma (samples,
maxlen, 8).
 2 Aplaina o tensor 3D de encaixes em um tensor 2D de forma (amostras,
maxlen * 8)
 3 Adiciona o classificador no topo

Você chega a uma precisão de validação de ~ 76%, o que é muito bom, considerando que você
está olhando apenas as primeiras 20 palavras em cada revisão. Mas observe que apenas achatar
as sequências incorporadas e treinar uma única Densecamada no topo leva a um modelo que
trata cada palavra na sequência de entrada separadamente, sem considerar as relações entre
palavras e a estrutura da sentença (por exemplo, este modelo provavelmente trataria ambos
filme é uma bomba ”e“ este filme é a bomba ”como sendo críticas negativas). EstáÉ muito
melhor adicionar camadas recorrentes ou camadas convolucionais 1D sobre as sequências
incorporadas para aprender recursos que levam em conta cada seqüência como um todo. É nisso
que vamos nos concentrar nas próximas seções.

Uso de embeddings de palavras pré-rotuladas

Às vezes, você tem tão poucos dados de treinamento disponíveis que você não pode usar seus
dados sozinho para aprender uma incorporação apropriada de seu vocabulário específica da
tarefa. O que fazes, então?

Em vez de aprender as incorporações de palavras juntamente com o problema que você deseja
resolver, é possível carregar vetores de incorporação a partir de um espaço de incorporação pré-
computado que você sabe que é altamente estruturado e exibe propriedades úteis - que
capturam aspectos genéricos da estrutura da linguagem. O raciocínio por trás do uso de
incorporação de palavras pré-formatadas no processamento de linguagem natural é semelhante
ao uso de convnets pré-formatados na classificação de imagens: você não tem dados suficientes
disponíveis para aprender recursos realmente poderosos, mas espera os recursos de que precisa
ser razoavelmente genérico - ou seja, recursos visuais comuns ou recursos semânticos. Nesse
caso, faz sentido reutilizar os recursos aprendidos em um problema diferente.

Tais incorporações de palavras são geralmente computadas usando estatísticas de ocorrências


de palavras (observações sobre quais palavras co-ocorrem em sentenças ou documentos),
usando uma variedade de técnicas, algumas envolvendo redes neurais, outras não. A ideia de um
espaço denso e de baixa dimensão para as palavras, computado de maneira não supervisionada,
foi inicialmente explorada por Bengio et al. no início dos anos 2000, [ 1 ] mas só começou a
decolar em aplicações de pesquisa e indústria após o lançamento de um dos mais famosos e
bem-sucedidos esquemas de incorporação de palavras: o algoritmo Word2vec
( https://code.google.com/ archive / p / word2vec ), desenvolvido por Tomas Mikolov no
Google em 2013. As dimensões do Word2vec capturam propriedades semânticas específicas,
como gênero.

Yoshua Bengio et al., Modelos de Linguagem Probabilística Neural (Springer, 2003).

Existem vários bancos de dados pré-computados de incorporação de palavras que você pode
baixar e usar em uma Embeddingcamada Keras . Word2vec é um deles. Outro popular é
chamado de Vetores Globais para Representação de Palavras
(GloVe, https://nlp.stanford.edu/projects/glove ), que foi desenvolvido por pesquisadores de
Stanford em 2014. Esta técnica de incorporação é baseada na fatoração de uma matriz de co-
estatísticas de ocorrências. Seus desenvolvedores disponibilizaram integrações pré-computadas
para milhões de tokens ingleses, obtidos a partir de dados da Wikipedia e dados de
rastreamento comuns.

Vejamos como você pode começar a usar os envios do GloVe em um modelo Keras. O mesmo
método é válido para incorporações do Word2vec ou qualquer outro banco de dados de
incorporação de palavras. Você também usará este exemplo para atualizar as técnicas de
tokenização de texto introduzidas há alguns parágrafos: você começará a partir do texto não
processado e trabalhará para cima.
6.1.3. Juntando tudo: do texto bruto à incorporação de palavras
Você usará um modelo semelhante ao que acabamos de ler: embutir sentenças em sequências de
vetores, achatá-las e treinar uma Densecamada no topo. Mas você vai fazerEntão, usando
embeddings de palavras pré-rotuladas; e em vez de usar os dados do IMDB pretokenized
embalados em Keras, você vai começar do zero, baixando os dados do texto original.

Download dos dados do IMDB como texto não processado

Primeiro, vá para http://mng.bz/0tIo e faça o download do conjunto de dados bruto do


IMDB. Descompacte-o.

Agora, vamos coletar as avaliações individuais de treinamento em uma lista de strings, uma
string por revisão. Você também coletará os rótulos de revisão (positivo / negativo) em
uma labelslista.

Listagem 6.8. Processando os rótulos dos dados brutos do IMDB

importar os

imdb_dir = '/ Usuários / fchollet / Downloads / aclImdb'

train_dir = os.path.join (imdb_dir, 'train')

labels = []

textos = []

para label_type em ['neg', 'pos']:

dir_name = os.path.join (train_dir, label_type)

para fname em os.listdir (dir_name):

se fname [-4:] == '.txt':

f = open (os.path.join (dir_name, fname))

texts.append (f.read ())

f.close ()

if label_type == 'neg':

labels.append (0)

outro:

labels.append (1)

Como otimizar os dados

Vamos vetorizar o texto e preparar uma divisão de treinamento e validação, usando os conceitos
introduzidos anteriormente nesta seção. Como a incorporação de palavras pré-concebidas é
particularmente útil em problemas em que há poucos dados de treinamento disponíveis (caso
contrário, é provável que as integrações específicas da tarefa os superem), adicionaremos a
seguinte alteração: restringir os dados de treinamento às primeiras 200 amostras. Então, você
aprenderá a classificar as resenhas de filmes depois de ver apenas 200 exemplos.

Listagem 6.9. Como otimizar o texto dos dados brutos do IMDB

do Tokenizer de importação keras.preprocessing.text

de keras.preprocessing.sequence import pad_sequences

import numpy como np

maxlen = 100 1

training_samples = 200 2

validation_samples = 10000 3

max_words = 10000 4

tokenizer = Tokenizer (num_words = max_words)

tokenizer.fit_on_texts (textos)

seqüências = tokenizer.texts_to_sequences (textos)

word_index = tokenizer.word_index

print ('Encontrado% s tokens exclusivos'% len (word_index))

data = pad_sequences (sequências, maxlen = maxlen)

labels = np.asarray (rótulos)

print ('Forma do tensor de dados:', data.shape)

print ('Forma do tensor da etiqueta:', labels.shape)

indices = np.arange (data.shape [0]) 5

np.random.shuffle (índices)

dados = dados [índices]

labels = labels [indices]

x_train = data [: training_samples]


y_train = labels [: training_samples]

x_val = data [training_samples: training_samples + validation_samples]

y_val = labels [training_samples: training_samples + validation_samples]

 1 Corta comentários após 100 palavras


 2 Trens em 200 amostras
 3 valida em 10.000 amostras
 4 Considera apenas as primeiras 10.000 palavras no conjunto de dados
 5 Divide os dados em um conjunto de treinamento e um conjunto de
validação, mas primeiro embaralha os dados, porque você está começando
com dados em que as amostras são ordenadas (todas negativas primeiro,
depois todas positivas)

Download da palavra GloVe Embeddings

Vá para https://nlp.stanford.edu/projects/glove e faça o download dos pré-ingressos da


Wikipédia em inglês em 2014. É um arquivo zip de 822 MB chamado glove.6B.zip, contendo
vetores de incorporação 100-dimensional para 400.000 palavras (ou tokens não-palavra-
chave). Descompacte.

Pré-processamento dos envoldings

Vamos analisar o arquivo descompactado (um arquivo .txt) para construir um índice que
mapeia palavras (como strings) para sua representação vetorial (como vetores numéricos).

Listagem 6.10. Analisando o arquivo de incorporação de palavras do GloVe

glove_dir = '/Users/fchollet/Downloads/glove.6B'

embeddings_index = {}

f = open (os.path.join (glove_dir, 'glove.6B.100d.txt'))

para linha em f:

valores = line.split ()

palavra = valores [0]

coefs = np.asarray (valores [1:], dtype = 'float32')

embeddings_index [palavra] = coefs

f.close ()

print ('Encontrado% s vetores de palavras'.% len (embeddings_index))

Em seguida, você construirá uma matriz de incorporação que poderá ser carregada em
uma Embeddingcamada. Deve ser uma matriz de forma (max_words, embedding_dim),
em que cada entrada i contém o embedding_dimvector -dimensional para a palavra de
índice i no índice palavra de referência (construído durante tokenization). Observe que o índice
0 não deve representar qualquer palavra ou token - é um marcador de posição.
Listagem 6.11. Preparando a matriz de incorporação de palavras do GloVe

embedding_dim = 100

embedding_matrix = np.zeros ((max_words, embedding_dim))

para word, i em word_index.items ():

se eu <max_words:

embedding_vector = embeddings_index.get (palavra)

if embedding_vector não é nenhum:

embedding_matrix [i] = embedding_vector 1

 1 Palavras não encontradas no índice de incorporação serão todas zeros.

Definindo um modelo

Você usará a mesma arquitetura de modelo de antes.

Listagem 6.12. Definição de modelo

de keras.models import Sequential

de keras.layers import Embutimento, achatar, denso

model = Sequential ()

model.add (Incorporação (max_words, embedding_dim, input_length = maxlen))

model.add (Flatten ())

model.add (denso (32, ativação = 'relu'))

model.add (denso (1, ativação = 'sigmóide'))

model.summary ()

Carregando os envoltórios do GloVe no modelo

A Embeddingcamada tem uma matriz de peso única: uma matriz flutuante 2D em que cada
entrada i é o vetor de palavras que se pretende associar ao índice i . Simples o
suficiente. Carregue a matriz GloVe que você preparou na Embeddingcamada, a primeira
camada no modelo.

Listagem 6.13. Como carregar embeddings de palavras pré-formatadas na Embeddingcamada

model.layers [0] .set_weights ([embedding_matrix])

model.layers [0] .trainable = False

Além disso, você congelará a Embeddingcamada (defina seu trainableatributo como False),
seguindo a mesma lógica com a qual já está familiarizado no contexto de recursos convnet pré-
tratados: quando partes de um modelo são pré-tratadas (como sua Embeddingcamada) e partes
são inicializadas aleatoriamente (como seu classificador), as partes pré-tratadas não devem ser
atualizadas durante o treinamento, para evitar esquecer o que elas já sabem. O grandeas
atualizações de gradiente acionadas pelas camadas inicializadas aleatoriamente seriam
prejudiciais aos recursos já aprendidos.

Treinando e avaliando o modelo

Compile e treine o modelo.

Listagem 6.14. Treinamento e avaliação

model.compile (optimizer = 'rmsprop',

perda = 'binary_crossentropy',

metrics = ['acc'])

history = model.fit (x_train, y_train,

épocas = 10,

batch_size = 32,

validation_data = (x_val, y_val))

model.save_weights ('pre_trained_glove_model.h5')

Agora, plote o desempenho do modelo ao longo do tempo (veja figuras 6.5 e 6.6 ).
Figura 6.5. Perda de treinamento e validação ao usar embeddings de palavras pré-rotuladas
Figura 6.6. Precisão de treinamento e validação ao usar embeddings de palavras pré-rotuladas

Listagem 6.15. Plotando os resultados

import matplotlib.pyplot como plt

acc = history.history ['acc']

val_acc = history.history ['val_acc']

perda = history.history ['perda']

val_loss = history.history ['val_loss']

épocas = intervalo (1, len (acc) + 1)

plt.plot (epochs, acc, 'bo', label = 'Treinar acc')

plt.plot (epochs, val_acc, 'b', label = 'Validação acc')

plt.title ('Precisão de treinamento e validação')

plt.legend ()

plt.figure ()

plt.plot (epochs, loss, 'bo', label = 'perda de treino')

plt.plot (epochs, val_loss, 'b', label = 'Perda de validação')

plt.title ('Perda de treinamento e validação')

plt.legend ()
plt.show ()

O modelo rapidamente começa a overfitting, o que não é surpreendente, dado o pequeno


número de amostras de treinamento. A precisão da validação tem alta variância pelo mesmo
motivo, mas parece atingir os 50 maiores.

Observe que sua milhagem pode variar: como você tem poucas amostras de treinamento, o
desempenho depende muito de exatamente as 200 amostras escolhidas - e você as escolhe
aleatoriamente. Se isto funcionar mal para você, tente escolher um conjunto aleatório diferente
de 200 amostras, para o bem do exercício (na vida real, você não consegue escolher seus dados
de treinamento).

Você também pode treinar o mesmo modelo sem carregar a palavra pré-encadeada e sem
congelar a camada de incorporação. Nesse caso, você aprenderá uma incorporação específica de
tarefas dos tokens de entrada, que geralmente é mais poderosa do que os envoltórios de palavras
pré-roteados quando muitos dados estão disponíveis. Mas neste caso, você tem apenas 200
amostras de treinamento. Vamos tentar (veja as figuras 6.7 e 6.8 ).

Figura 6.7. Perda de treinamento e validação sem usar embeddings de palavras pré-rotuladas
Figura 6.8. Precisão de treinamento e validação sem usar embeddings de palavras pré-rotuladas

Listagem 6.16. Treinando o mesmo modelo sem a incorporação de palavras pré-encadeadas

de keras.models import Sequential

de keras.layers import Embutimento, achatar, denso

model = Sequential ()

model.add (Incorporação (max_words, embedding_dim, input_length = maxlen))

model.add (Flatten ())

model.add (denso (32, ativação = 'relu'))

model.add (denso (1, ativação = 'sigmóide'))

model.summary ()

model.compile (optimizer = 'rmsprop',

perda = 'binary_crossentropy',

metrics = ['acc'])

history = model.fit (x_train, y_train,

épocas = 10,

batch_size = 32,

validation_data = (x_val, y_val))

A precisão de validação fica parada nos 50s baixos. Portanto, neste caso, a incorporação de
palavras pré-formatadas supera os embeddings aprendidos em conjunto. Se você aumentar o
número de amostras de treinamento, isso rapidamente deixará de ser o caso - experimente-o
como um exercício.
Finalmente, vamos avaliar o modelo nos dados de teste. Primeiro, você precisa tokenizar os
dados de teste.

Listagem 6.17. Como organizar os dados do conjunto de testes

test_dir = os.path.join (imdb_dir, 'test')

labels = []

textos = []

para label_type em ['neg', 'pos']:

dir_name = os.path.join (test_dir, label_type)

para fname in sorted (os.listdir (dir_name)):

se fname [-4:] == '.txt':

f = open (os.path.join (dir_name, fname))

texts.append (f.read ())

f.close ()

if label_type == 'neg':

labels.append (0)

outro:

labels.append (1)

seqüências = tokenizer.texts_to_sequences (textos)

x_test = pad_sequences (sequências, maxlen = maxlen)

y_test = np.asarray (rótulos)

Em seguida, carregue e avalie o primeiro modelo.

Listagem 6.18. Avaliando o modelo no conjunto de teste

model.load_weights ('pre_trained_glove_model.h5')

model.evaluate (x_test, y_test)

Você obtém uma precisão de teste chocante de 56%. Trabalhar com apenas algumas amostras de
treinamento é difícil!

6.1.4. Empacotando
Agora você pode fazer o seguinte:
 Transforme o texto bruto em algo que uma rede neural pode processar
 Use a Embeddingcamada em um modelo Keras para aprender sobre incorporações de
token específicas da tarefa
 Use encadeamentos pré-formatados para obter um impulso extra em pequenos
problemas de processamento de linguagem natural

6.2. COMPREENDER REDES NEURAIS RECORRENTES

Uma característica importante de todas as redes neurais que você viu até agora, como redes e
convnets densamente conectadas, é que elas não têm memória. Cada entrada mostrada a eles é
processada independentemente, sem estado mantido entre entradas. Com essas redes, para
processar uma seqüência ou uma série temporal de pontos de dados, você precisa mostrar a
sequência inteira para a rede de uma vez: transformá-la em um único ponto de dados. Por
exemplo, isso é o que você fez no exemplo do IMDB: uma revisão completa do filme foi
transformada em um único vetor grande e processada de uma só vez. Essas redes são chamadas
de redes feedforward .

Em contraste, quando você está lendo a sentença atual, você está processando palavra por
palavra - ou melhor, reveste os olhos com sacadas oculares - enquanto mantém memórias do
que veio antes; isso lhe dá uma representação fluida do significado transmitido por essa
sentença. A inteligência biológica processa as informações de forma incremental, mantendo um
modelo interno do que está processando, construído a partir de informações passadas e
constantemente atualizado à medida que novas informações chegam.

Uma rede neural recorrente (RNN) adota o mesmo princípio, ainda que em uma versão
extremamente simplificada: processa seqüências, iterando os elementos de sequência e
mantendo um estadocontendo informações relativas ao que foi visto até o momento. Na
verdade, um RNN é um tipo de rede neural que possui um loop interno (consulte a figura
6.9 ). O estado da RNN é redefinido entre o processamento de duas seqüências independentes
diferentes (como duas revisões diferentes do IMDB), portanto, você considera uma sequência
como um único ponto de dados: uma única entrada na rede. O que muda é que esse ponto de
dados não é mais processado em uma única etapa; em vez disso, a rede faz um loop interno
sobre os elementos da sequência.

Figura 6.9. Uma rede recorrente: uma rede com um loop

Para tornar claras essas noções de loop e state , vamos implementar o forward forward de um
brinquedo RNN em Numpy. Este RNN toma como entrada uma seqüência de vetores, que você
codificará como um tensor 2D de tamanho (timesteps, input_features). Faz um loop
sobre timesteps, e em cada timestep, considera seu estado atual em te a entrada em t(de
shape (input_features,), e combina-os para obter a saída em t. Você então definirá o
estado para a próxima etapa para ser a saída anterior. Para o primeiro timestep, a saída anterior
não está definida, portanto, não há estado atual, portanto, você inicializará o estado como um
vetor all-zero chamado de estado inicial da rede.

No pseudocódigo, este é o RNN.


Listagem 6.19. Pseudocódigo RNN

state_t = 0 1

para input_t em input_sequence: 2

output_t = f (entrada_t, estado_t)

state_t = output_t 3

 1 O estado em t
 2 Itera sobre os elementos da sequência
 3 A saída anterior se torna o estado da próxima iteração.

Pode mesmo carne para fora a função f: a transformação da entrada e uma saída em estado vai
ser parametrizado por duas matrizes, We U, e um vector de polarização. É semelhante à
transformação operada por uma camada densamente conectada em uma rede feedforward.

Listagem 6.20. Pseudocódigo mais detalhado para o RNN

state_t = 0

para input_t em input_sequence:

output_t = ativação (ponto (W, entrada_t) + ponto (U, estado_t) + b)

state_t = output_t

Para tornar essas noções absolutamente inequívocas, vamos escrever uma implementação
ingênua do Numpy do passe para frente do RNN simples.

Listagem 6.21. Implementação Numpy de um simples RNN

import numpy como np

timesteps = 100 1

input_features = 32 2

output_features = 64 3

inputs = np.random.random ((timesteps, input_features)) 4

state_t = np.zeros ((output_features,)) 5

W = np.random.random ((output_features, input_features)) 6

U = np.random.random ((output_features, output_features)) 6

b = np.random.random ((output_features,)) 6
successive_outputs = []

para input_t em entradas: 7

output_t = np.tanh (np.dot (W, entrada_t) + np.dot (U, estado_t) + b)


8

successive_outputs.append (output_t) 9

state_t = output_t 10

final_output_sequence = np.concatenate (successive_outputs, axis = 0) 11

 1 Número de timesteps na sequência de entrada


 2 Dimensionalidade do espaço do recurso de entrada
 3 Dimensionalidade do espaço do recurso de saída
 4 dados de entrada: ruído aleatório por causa do exemplo
 5 Estado inicial: um vetor a zero
 6 Cria matrizes de peso aleatório
 7 input_t é um vetor de forma (input_features,).
 8 Combina a entrada com o estado atual (a saída anterior) para obter a
saída atual
 9 Armazena essa saída em uma lista
 10 Atualiza o estado da rede para o próximo timestep
 11 A saída final é um tensor de forma 2D (timesteps, output_features).

Fácil: em resumo, um RNN é um forloop que reutiliza quantidades calculadas durante a


iteração anterior do loop, nada mais. É claro que existem muitos RNNs diferentes que se
encaixam nessa definição que você poderia criar - este exemplo é uma das formulações mais
simples de RNN. Os RNNs são caracterizados por sua função step, como a seguinte função neste
caso (veja a figura 6.10 ):

output_t = np.tanh (np.dot (W, entrada_t) + np.dot (U, estado_t) + b)

Figura 6.10. Um simples RNN, desenrolado ao longo do tempo


Nota

Neste exemplo, a saída final é um tensor 2D de forma (timesteps, output_features),


onde cada timestep é a saída do loop no momento t. Cada passo de tempo tno tensor de saída
contém informações sobre Timesteps 0a tna entrada sequência-o sobre toda passado. Por esse
motivo, em muitos casos, você não precisa dessa sequência completa de saídas; você só precisa
da última saída ( output_tno final do loop), porque ela já contém informações sobre toda a
sequência.

6.2.1. Uma camada recorrente em Keras


O processo que você acabou de implementar ingenuamente no Numpy corresponde a uma
camada Keras real - a SimpleRNNcamada:

de keras.layers importam SimpleRNN

Há uma pequena diferença: SimpleRNNprocessa lotes de seqüências, como todas as outras


camadas de Keras, não uma única sequência como no exemplo de Numpy. Isso significa que ele
recebe entradas de forma (batch_size, timesteps, input_features), em vez
de (timesteps, input_features).

Como todas as camadas recorrentes em Keras, SimpleRNNpode ser executado em dois modos
diferentes: pode retornar as sequências completas de saídas sucessivas para cada timestep (um
tensor 3D de forma (batch_size, timesteps, output_features)) ou somente a última
saída para cada seqüência de entrada (um tensor 2D de forma (batch_size,
output_features)). Esses dois modos são controlados
pelo return_sequencesargumento do construtor. Vamos ver um exemplo que
usa SimpleRNNe retorna apenas a saída no último momento:

>>> from keras.models import Sequential

>>> de keras.layers import Embedding, SimpleRNN

>>> model = Sequencial ()

>>> model.add (Incorporação (10000, 32))

>>> model.add (SimpleRNN (32))

>>> model.summary ()

________________________________________________________________

Camada (tipo) Forma de saída Param #

================================================== ==============

embedding_22 (Incorporação) (None, None, 32) 320000

________________________________________________________________

simplernn_10 (SimpleRNN) (nenhum, 32) 2080

================================================== ==============

Total de Params: 322.080


Params treináveis: 322,080

Params não treináveis: 0

O exemplo a seguir retorna a sequência de estado completa:

>>> model = Sequencial ()

>>> model.add (Incorporação (10000, 32))

>>> model.add (SimpleRNN (32, return_sequences = True))

>>> model.summary ()

________________________________________________________________

Camada (tipo) Forma de saída Param #

================================================== ==============

embedding_23 (Incorporação) (None, None, 32) 320000

________________________________________________________________

simplernn_11 (SimpleRNN) (nenhum, nenhum, 32) 2080

================================================== ==============

Total de Params: 322.080

Params treináveis: 322,080

Params não treináveis: 0

Às vezes é útil empilhar várias camadas recorrentes, uma após a outra, para aumentar o poder
de representação de uma rede. Em tal configuração, você precisa obter todas as camadas
intermediárias para retornar a sequência completa de saídas:

>>> model = Sequencial ()

>>> model.add (Incorporação (10000, 32))

>>> model.add (SimpleRNN (32, return_sequences = True))

>>> model.add (SimpleRNN (32, return_sequences = True))

>>> model.add (SimpleRNN (32, return_sequences = True))

>>> model.add (SimpleRNN (32)) 1

>>> model.summary ()

________________________________________________________________

Camada (tipo) Forma de saída Param #

================================================== ==============

embedding_24 (Incorporação) (Nenhum, Nenhum, 32) 320000

________________________________________________________________
simplernn_12 (SimpleRNN) (nenhum, nenhum, 32) 2080

________________________________________________________________

simplernn_13 (SimpleRNN) (nenhum, nenhum, 32) 2080

________________________________________________________________

simplernn_14 (SimpleRNN) (nenhum, nenhum, 32) 2080

________________________________________________________________

simplernn_15 (SimpleRNN) (nenhum, 32) 2080

================================================== ==============

Params totais: 328.320

Params treináveis: 328.320

Params não treináveis: 0

 1 Última camada só retorna a última saída

Agora, vamos usar esse modelo no problema de classificação e revisão de filmes do


IMDB. Primeiro, pré-processe os dados.

Listagem 6.22. Preparando os dados do IMDB

de keras.datasets import imdb

da sequência de importação keras.preprocessing

max_features = 10000 1

maxlen = 500 2

batch_size = 32

print ('Carregando dados ...')

(input_train, y_train), (input_test, y_test) = imdb.load_data (

num_words = max_features)

print (len (input_train), 'sequências de trem')

print (len (input_test), 'sequências de teste')

print ('seqüências Pad (amostras x tempo)')

input_train = sequence.pad_sequences (input_train, maxlen = maxlen)

input_test = sequence.pad_sequences (input_test, maxlen = maxlen)

print ('input_train shape:', input_train.shape)


print ('input_test shape:', input_test.shape)

 1 Número de palavras a considerar como recursos


 2 Corta textos depois de tantas palavras (entre as palavras mais comuns
max_features)

Vamos treinar uma rede recorrente simples usando uma Embeddingcamada e


uma SimpleRNNcamada.

Listagem 6.23. Treinando o modelo com Embeddinge SimpleRNNcamadas

de keras.layers importar denso

model = Sequential ()

model.add (Incorporação (max_features, 32))

model.add (SimpleRNN (32))

model.add (denso (1, ativação = 'sigmóide'))

model.compile (optimizer = 'rmsprop', loss = 'binary_crossentropy', métricas =


['acc'])

history = model.fit (input_train, y_train,

épocas = 10,

batch_size = 128,

validation_split = 0,2)

Agora, vamos mostrar a perda e precisão de treinamento e validação (veja as figuras


6.11 e 6.12 ).
Figura 6.11. Perda de treinamento e validação no IMDB com simplernn
Figura 6.12. Treinamento e validação de validação no IMDB com simplernn

Listagem 6.24. Plotando resultados

import matplotlib.pyplot como plt

acc = history.history ['acc']

val_acc = history.history ['val_acc']

perda = history.history ['perda']

val_loss = history.history ['val_loss']

épocas = intervalo (1, len (acc) + 1)

plt.plot (epochs, acc, 'bo', label = 'Treinar acc')

plt.plot (epochs, val_acc, 'b', label = 'Validação acc')

plt.title ('Precisão de treinamento e validação')

plt.legend ()

plt.figure ()

plt.plot (epochs, loss, 'bo', label = 'perda de treino')

plt.plot (epochs, val_loss, 'b', label = 'Perda de validação')

plt.title ('Perda de treinamento e validação')


plt.legend ()

plt.show ()

Como lembrete, no capítulo 3 , a primeira abordagem ingênua a este conjunto de dados levou
você a uma precisão de teste de 88%. Infelizmente, essa pequena rede recorrente não apresenta
um bom desempenho em comparação com essa linha de base (apenas 85% de precisão de
validação). Parte do problema é que suas entradas consideram apenas as primeiras 500
palavras, em vez de sequências completas - portanto, o RNN tem acesso a menos informações
do que o modelo de linha de base anterior. O restante do problema é que SimpleRNNnão é bom
no processamento de sequências longas, como texto. Outros tipos de camadas recorrentes têm
um desempenho muito melhor. Vamos ver algumas camadas mais avançadas.

6.2.2. Entendendo as camadas LSTM e GRU


SimpleRNNnão é a única camada recorrente disponível em Keras. Existem dois
outros: LSTMe GRU. Na prática, você sempre usará um desses, porque SimpleRNNgeralmente é
muito simplista para ser de uso real. SimpleRNNtem um grande problema: embora
teoricamente deva ser capaz de reter no tempo tinformações sobre insumos vistos antes de
muitos prazos, na prática, tais dependências de longo prazo são impossíveis de aprender. Isto é
devido ao problema do gradiente de fuga, um efeito que é semelhante ao que é observado em
redes não recorrentes (feedforward networks) com várias camadas de profundidade: à medida
que você continua adicionando camadas a uma rede, a rede acaba ficando inatingível. As razões
teóricas para esse efeito foram estudadas por Hochreiter, Schmidhuber e Bengio no início dos
anos 90. [ 2 ] As camadas LSTMe GRUsão projetadas para resolver esse problema.

Veja, por exemplo, Yoshua Bengio, Patrice Simard e Paolo Frasconi, “Aprender Dependências de Longo Prazo com Descida de Gradiente é

Difícil”, IEEE Transactions on Neural Networks 5, no. 2 (1994).

Vamos considerar a LSTMcamada. O algoritmo LSTM (Long Short-Term Memory) subjacente


foi desenvolvido por Hochreiter e Schmidhuber em 1997; [ 3 ] foi o ponto culminante de suas
pesquisas sobre o problema do gradiente de desaparecimento.

Sepp Hochreiter e Jürgen Schmidhuber, “Long Short-Term Memory”, Neural Computation 9, no. 8 (1997).

Essa camada é uma variante da SimpleRNNcamada que você já conhece; Ele adiciona uma
maneira de transportar informações através de muitos timesteps. Imagine uma correia
transportadora correndo paralela à seqüência que você está processando. As informações da
sequência podem saltar para a correia transportadora em qualquer ponto, ser transportadas
para um intervalo de tempo mais recente e pular, intactas, quando você precisar. Isso é
essencialmente o que o LSTM faz: ele salva informações para mais tarde, evitando que os sinais
antigos desapareçam gradualmente durante o processamento.

Para entender isso em detalhes, vamos começar a partir da SimpleRNNcélula (veja a figura
6.13 ). Como você terá muitas matrizes de ponderação, indexe as matrizes We Una célula com a
letra o( Woe Uo) para saída .
Figura 6.13. O ponto de partida de uma lstmcamada: umsimplernn

Vamos adicionar a esta imagem um fluxo de dados adicional que transporta informações através
de timesteps. Chame seus valores em diferentes timesteps Ct, onde C significa carry . Esta
informação terá o seguinte impacto na célula: será combinada com a conexão de entrada e a
conexão recorrente (através de uma transformação densa: um produto de ponto com uma
matriz de peso seguido por uma adição de polarização e a aplicação de uma função de ativação)
e isso afetará o estado sendo enviado para o próximo timestep (via uma função de ativação e
uma operação de multiplicação). Conceitualmente, o fluxo de dados carry é uma maneira de
modular a próxima saída e o próximo estado (veja a figura 6.14 ). Simples até agora.

Figura 6.14. Indo de um simplernnpara um lstm: adicionando uma faixa de transporte

Agora, a sutileza: a maneira como o próximo valor do fluxo de dados carry é calculado. Envolve
três transformações distintas. Todos os três têm a forma de uma SimpleRNNcélula:

y = ativação (ponto (estado_t, U) + ponto (entrada_t, W) + b)

Mas todas as três transformações têm suas próprias matrizes de peso, que você índice com as
letras i, f, e k. Aqui está o que você tem até agora (pode parecer um pouco arbitrário, mas tenha
paciência comigo).

Listagem 6.25. Detalhes do pseudocódigo da arquitetura LSTM (1/2)

output_t = ativação (ponto (state_t, Uo) + ponto (input_t, Wo) + ponto (C_t,
Vo) + bo)
i_t = ativação (ponto (estado_t, Ui) + ponto (entrada_t, Wi) + bi)

f_t = ativação (ponto (estado_t, Uf) + ponto (entrada_t, Wf) + bf)

k_t = ativação (ponto (state_t, Uk) + ponto (input_t, Wk) + bk)

Pode obter o novo estado carry (o próximo c_t), combinando i_t, f_te k_t.

Listagem 6.26. Detalhes do pseudocódigo da arquitetura LSTM (2/2)

c_t + 1 = i_t * k_t + c_t * f_t

Adicione isto como mostrado na figura 6.15 . E é isso. Não é tão complicado - apenas um pouco
complexo.

Figura 6.15. Anatomia de um lstm

Se você quiser ser filosófico, pode interpretar o que cada uma dessas operações deve fazer. Por
exemplo, você pode dizer que multiplicar c_te f_té uma maneira de esquecer deliberadamente
informações irrelevantes no fluxo de dados de transporte. Enquanto isso, i_te k_tfornecer
informações sobre o presente, atualizando a faixa de transporte com novas informações. Mas no
final do dia, essas interpretações não significam muito, porque o que essas
operações realmentefazer é determinado pelo conteúdo dos pesos parametrizando-os; e os
pesos são aprendidos de uma maneira completa, recomeçando a cada rodada de treinamento,
tornando impossível creditar essa ou aquela operação com uma finalidade específica. A
especificação de uma célula RNN (como acabamos de descrever) determina seu espaço de
hipótese - o espaço no qual você procurará uma boa configuração de modelo durante o
treinamento - mas não determina o que a célula faz; isso é até os pesos das células. A mesma
célula com diferentes pesos pode estar fazendo coisas muito diferentes. Assim, a combinação de
operações que compõem uma célula RNN é melhor interpretada como um conjunto
de restrições em sua pesquisa, não como um design no sentido de engenharia.

Para um pesquisador, parece que a escolha de tais restrições - a questão de como implementar
células RNN - é melhor deixar para algoritmos de otimização (como algoritmos genéticos ou
processos de aprendizado por reforço) do que para engenheiros humanos. E no futuro, é assim
que vamos construir redes. Em resumo: você não precisa entender nada sobre a arquitetura
específica de uma LSTMcélula; como um ser humano, não deveria ser seu trabalho entendê-
lo. Basta ter em mente o que a LSTMcélula deve fazer: permitir que informações passadas sejam
reinjetadas mais tarde, combatendo assim o problema do gradiente de desaparecimento.

6.2.3. Um exemplo concreto de LSTM em Keras


Agora vamos mudar para questões mais práticas: você irá configurar um modelo usando
uma LSTMcamada e treiná-lo nos dados do IMDB (veja as figuras 6.16 e 6.17 ). A rede é
semelhante à SimpleRNNque acabou de ser apresentada. Você especifica apenas a
dimensionalidade de saída da LSTMcamada; deixe todos os outros argumentos (existem muitos)
nos padrões de Keras. Keras tem bons padrões, e as coisas quase sempre “simplesmente
funcionam” sem que você precise gastar tempo sintonizando parâmetros manualmente.

Figura 6.16. Perda de treinamento e validação no IMDB com LSTM

Figura 6.17. Treinamento e validação de validação no IMDB com LSTM

Listagem 6.27. Usando a LSTMcamada em Keras

de keras.layers importar LSTM

model = Sequential ()

model.add (Incorporação (max_features, 32))

model.add (LSTM (32))


model.add (denso (1, ativação = 'sigmóide'))

model.compile (optimizer = 'rmsprop',

perda = 'binary_crossentropy',

metrics = ['acc'])

history = model.fit (input_train, y_train,

épocas = 10,

batch_size = 128,

validation_split = 0,2)

Desta vez, você atinge até 89% de precisão de validação. Nada mal: certamente muito melhor
que a SimpleRNNrede - em grande parte porque o LSTM sofre muito menos com o problema do
gradiente de desaparecimento - e um pouco melhor do que a abordagem totalmente conectada
do capítulo 3 , embora você esteja olhando menos dados do que estava no capítulo 3 . Você
está truncando sequências depois de 500 timesteps, enquanto no capítulo 3 , você estava
considerando sequências completas.

Mas esse resultado não é inovador para essa abordagem computacionalmente intensiva. Por que
o desempenho do LSTM não é melhor? Uma razão é que você não fez nenhum esforço para
ajustar hiperparâmetros, como a dimensionalidade de incorporação ou a dimensionalidade de
saída do LSTM. Outro pode ser falta de regularização. Mas honestamente, a principal razão é
que analisar a estrutura global e de longo prazo das revisões (o que a LSTM é boa) não é útil
para um problema de análise de sentimentos. Esse problema básico é bem resolvido
observando-se quais palavras ocorrem em cada revisão e com que frequência. Isso é o que a
primeira abordagem totalmente conectada olhou. Mas existem problemas de processamento de
linguagem natural muito mais difíceis por aí, onde a força do LSTM se tornará aparente: em
particular,

6.2.4. Empacotando
Agora você entende o seguinte:

 O que são os RNNs e como funcionam


 O que é o LSTM e por que ele funciona melhor em seqüências longas do que um RNN
ingênuo
 Como usar camadas Keras RNN para processar dados de seqüência

A seguir, analisaremos vários recursos mais avançados de RNNs, que podem ajudar você a
aproveitar ao máximo seus modelos de sequência de aprendizado profundo.

6.3. USO AVANÇADO DE REDES NEURAIS RECORRENTES

Nesta seção, revisaremos três técnicas avançadas para melhorar o desempenho e o poder de
generalização de redes neurais recorrentes. No final da seção, você saberá mais sobre o que há
para saber sobre o uso de redes recorrentes com o Keras. Vamos demonstrar todos os três
conceitos sobre um problema de previsão de temperatura, onde você tem acesso a uma série
temporal de pontos de dados provenientes de sensores instalados no telhado de um edifício,
como temperatura, pressão do ar e umidade, que você usa para prever qual a temperatura será
24 horas após o último ponto de dados. Este é um problema bastante desafiador que exemplifica
muitas dificuldades comuns encontradas quando se trabalha com timeseries.
Nós vamos cobrir as seguintes técnicas:

 Desistência recorrente - Esta é uma maneira específica e interna de usar o


dropout para combater o overfitting em camadas recorrentes.
 Empilhamento de camadas recorrentes - Isso aumenta o poder de representação
da rede (ao custo de maiores cargas computacionais).
 Camadas Recorrentes Bidirecionais - Apresentam as mesmas informações para
uma rede recorrente de diferentes maneiras, aumentando a precisão e mitigando os
problemas de esquecimento.

6.3.1. Um problema de previsão de temperatura


Até agora, os únicos dados de sequência que cobrimos foram dados de texto, como o conjunto de
dados do IMDB e o conjunto de dados da Reuters. Mas dados de sequência são encontrados em
muitos mais problemas do que apenas processamento de linguagem. Em todos os exemplos
desta seção, você jogará com um conjunto de dados de timeseries meteorológicos registrado na
Estação Meteorológica do Instituto Max Planck de Biogeoquímica em Jena, na Alemanha. [ 4 ]

Olaf Kolle, www.bgc-jena.mpg.de/wetter .

Neste conjunto de dados, 14 quantidades diferentes (tais temperatura do ar, pressão


atmosférica, umidade, direção do vento, e assim por diante) foram registradas a cada 10
minutos, ao longo de vários anos. Os dados originais remontam a 2003, mas este exemplo está
limitado aos dados de 2009–2016. Este conjunto de dados é perfeito para aprender a trabalhar
com séries temporais numéricas. Você o usará para criar um modelo que tome como entrada
alguns dados do passado recente (alguns dias de pontos de dados) e preveja a temperatura do ar
em 24 horas no futuro.

Faça o download e descompacte os dados da seguinte forma:

cd ~ / Downloads

mkdir jena_climate

cd jena_climate

wget https://s3.amazonaws.com/keras-datasets/jena_climate_2009_2016.csv.zip

descompacte jena_climate_2009_2016.csv.zip

Vamos dar uma olhada nos dados.

Listagem 6.28. Inspecionando os dados do conjunto de dados meteorológicos de Jena

importar os

data_dir = '/ users / fchollet / Downloads / jena_climate'

fname = os.path.join (data_dir, 'jena_climate_2009_2016.csv')

f = aberto (fname)
data = f.read ()

f.close ()

linhas = data.split ('\ n')

cabeçalho = linhas [0] .split (',')

linhas = linhas [1:]

imprimir (cabeçalho)

print (len (linhas))

Isso gera uma contagem de 420.551 linhas de dados (cada linha é um timestep: um registro de
uma data e 14 valores relacionados ao clima), bem como o seguinte cabeçalho:

["Data hora",

"p (mbar)",

"T (degC)",

"Tpot (K)",

"Tdew (degC)",

"rh (%)",

"VPmax (mbar)",

"VPact (mbar)",

"VPdef (mbar)",

"sh (g / kg)",

"H2OC (mmol / mol)",

"rho (g / m ** 3)",

"wv (m / s)",

"max. wv (m / s)",

"wd (deg)"]

Agora, converta todas as 420.551 linhas de dados em uma matriz Numpy.

Listagem 6.29. Analisando os dados

import numpy como np

float_data = np.zeros ((len (linhas), len (cabeçalho) - 1))

para i, linha em enumerar (linhas):


valores = [float (x) para x em line.split (',') [1:]]

float_data [i,:] = valores

Por exemplo, aqui está o gráfico da temperatura (em graus Celsius) ao longo do tempo (veja
a figura 6.18 ). Nesta trama, você pode ver claramente a periodicidade anual da temperatura.

Figura 6.18. Temperatura em todo o intervalo temporal do conjunto de dados (° C)

Listagem 6.30. Plotando a série de tempo da temperatura

de pyplot de importação matplotlib como plt

temp = float_data [:, 1] <1> temperatura (em graus Celsius)

plt.plot (intervalo (len (temp)), temp)

Aqui está um gráfico mais estreito dos primeiros 10 dias de dados de temperatura (ver figura
6.19 ). Como os dados são registrados a cada 10 minutos, você obtém 144 pontos de dados por
dia.
Figura 6.19. Temperatura nos primeiros 10 dias do conjunto de dados (° C)

Listagem 6.31. Plotando os primeiros 10 dias da série de tempo de temperatura

plt.plot (intervalo (1440), temp [: 1440])

Neste gráfico, você pode ver a periodicidade diária, especialmente evidente nos últimos 4
dias. Observe também que esse período de 10 dias deve ser proveniente de um mês de inverno
bastante frio.

Se você estivesse tentando prever a temperatura média no mês seguinte, dados alguns meses de
dados anteriores, o problema seria fácil, devido à periodicidade confiável dos dados em escala
anual. Mas olhando para os dados em uma escala de dias, a temperatura parece muito mais
caótica. É esta timeseries previsível em uma escala diária? Vamos descobrir.

6.3.2. Preparando os dados


A formulação exata do problema será a seguinte: dados dados que vão desde o
tempo lookback(um intervalo de tempo é de 10 minutos) e amostrados a
cada stepstimesteps, você pode prever a temperatura em tempo delay? Você usará os
seguintes valores de parâmetro:

 lookback = 720—Observations retornará 5 dias.


 steps = 6—Observações serão amostradas em um ponto de dados por hora.
 delay = 144—Os alertas serão 24 horas no futuro.

Para começar, você precisa fazer duas coisas:

 Pré-processe os dados para um formato que uma rede neural possa ingerir. Isso é fácil:
os dados já são numéricos, então você não precisa fazer nenhuma vetorização. Mas cada
timeseries nos dados está em uma escala diferente (por exemplo, a temperatura é
tipicamente entre -20 e +30, mas a pressão atmosférica, medida em mbar, é em torno
de 1.000). Você irá normalizar cada série de tempo de forma independente, para que
todos eles tomem pequenos valores em uma escala similar.
 Escreva um gerador Python que use a matriz atual de dados flutuantes e forneça lotes de
dados do passado recente, junto com uma temperatura-alvo no futuro. Como as
amostras no conjunto de dados são altamente redundantes (a amostra N e a
amostra N + 1 terão a maioria de seus timesteps em comum), seria um desperdício
atribuir explicitamente cada amostra. Em vez disso, você gerará as amostras
rapidamente usando os dados originais.

Você pré-processará os dados subtraindo a média de cada timeseries e dividindo pelo desvio
padrão. Você usará os primeiros 200.000 timesteps como dados de treinamento, portanto
calcule a média e o desvio padrão apenas nessa fração dos dados.

Listagem 6.32. Normalizando os dados

mean = float_data [: 200000] .mean (eixo = 0)

float_data - = mean

std = float_data [: 200000] .std (eixo = 0)

float_data / = std

A Listagem 6.33 mostra o gerador de dados que você usará. Ela produz uma tupla (samples,
targets), onde samplesestá um lote de dados de entrada e targetsé a matriz
correspondente de temperaturas alvo. Leva os seguintes argumentos:

 data- A matriz original de dados de ponto flutuante, que você normalizou na listagem
6.32 .
 lookback—Quantos timesteps retornam os dados de entrada devem ir.
 delay- Quantos timesteps no futuro o alvo deveria ser.
 min_indexe max_index—Indices na datamatriz que delimitam quais
timesteps serão extraídos. Isso é útil para manter um segmento dos dados para
validação e outro para teste.
 shuffle—Para misturar as amostras ou desenhá-las em ordem cronológica.
 batch_size—O número de amostras por lote.
 step—O período, em tempo, no qual você amostra dados. Você configurará para 6 para
desenhar um ponto de dados a cada hora.

Listagem 6.33. Gerador gerando amostras de timeseries e seus alvos

def gerador (dados, lookback, delay, min_index, max_index,

shuffle = False, batch_size = 128, passo = 6):

se max_index for None:

max_index = len (data) - atraso - 1

i = min_index + lookback

enquanto 1:

se shuffle:

linhas = np.random.randint (

min_index + lookback, max_index, size = batch_size)

outro:

if i + batch_size> = max_index:

i = min_index + lookback

linhas = np.arange (i, min (i + batch_size, max_index))


i + = len (linhas)

samples = np.zeros ((len (linhas),

lookback // step,

data.shape [-1]))

targets = np.zeros ((len (linhas),))

para j, linha em enumerar (linhas):

índices = intervalo (linhas [j] - lookback, linhas [j], etapa)

samples [j] = data [indices]

alvos [j] = dados [linhas [j] + atraso] [1]

produzir amostras, alvos

Agora, vamos usar a generatorfunção abstrata para instanciar três geradores: um para
treinamento, um para validação e um para teste. Cada um examinará diferentes segmentos
temporais dos dados originais: o gerador de treinamento analisa os primeiros 200.000
timesteps, o gerador de validação examina os 100.000 a seguir e o gerador de teste examina o
restante.

Listagem 6.34. Preparando os geradores de treinamento, validação e teste

lookback = 1440

etapa = 6

atraso = 144

batch_size = 128

train_gen = generator (float_data,

lookback = lookback

atraso = atraso

min_index = 0,

max_index = 200000,

shuffle = Verdadeiro

passo = passo

batch_size = batch_size)

val_gen = gerador (float_data,

lookback = lookback

atraso = atraso

min_index = 200001,
max_index = 300000,

passo = passo

batch_size = batch_size)

test_gen = generator (float_data,

lookback = lookback

atraso = atraso

min_index = 300001,

max_index = None,

passo = passo

batch_size = batch_size)

val_steps = (300000 - 200001 - lookback) 1

test_steps = (len (float_data) - 300001 - lookback) 2

 1 Quantas etapas para desenhar a partir de val_gen, a fim de ver todo o


conjunto de validação
 2 Quantas etapas para desenhar a partir do test_gen, a fim de ver todo o
conjunto de testes

6.3.3. Uma linha de base de bom senso e sem aprendizado de máquina


Antes de começar a usar modelos de aprendizagem profunda de caixa preta para resolver o
problema de previsão de temperatura, vamos tentar uma abordagem simples e de senso
comum. Ele servirá como uma verificação de sanidade e estabelecerá uma linha de base que
você terá que superar para demonstrar a utilidade dos modelos de aprendizado de máquina
mais avançados. Essas linhas de base de bom senso podem ser úteis quando você está
abordando um novo problema para o qual não há solução conhecida (ainda). Um exemplo
clássico é o das tarefas de classificação desequilibradas, em que algumas classes são muito mais
comuns do que outras. Se seu conjunto de dados contém 90% de instâncias de classe A e 10% de
instâncias de classe B, então uma abordagem de senso comum para a tarefa de classificação é
sempre prever “A” quando apresentada com uma nova amostra. Tal classificador é 90% preciso
no geral, e qualquer abordagem baseada em aprendizado deve, portanto, superar essa
pontuação de 90% para demonstrar utilidade. Às vezes, essas linhas de base elementares podem
ser surpreendentemente difíceis de serem superadas.

Neste caso, a série de tempos de temperatura pode ser seguramente assumida como contínua
(as temperaturas de amanhã provavelmente estarão próximas das temperaturas de hoje), bem
como periódicas com um período diário. Assim, uma abordagem de senso comum é sempre
prever que a temperatura daqui a 24 horas será igual à temperatura no momento. Vamos avaliar
essa abordagem usando a métrica de erro absoluto médio (MAE):

np.mean (np.abs (preds - targets))

Aqui está o ciclo de avaliação.


Listagem 6.35. Computando a linha de base do senso comum MAE

def evalu_naive_method ():

batch_maes = []

para o intervalo no intervalo (val_steps):

samples, targets = next (val_gen)

preds = samples [:, -1, 1]

mae = np.mean (np.abs (preds - alvos))

batch_maes.append (mae)

print (np.mean (batch_maes))

evaluate_naive_method ()

Isso produz um MAE de 0,29. Como os dados de temperatura foram normalizados para serem
centralizados em 0 e têm um desvio padrão de 1, esse número não é imediatamente
interpretável. Isso se traduz em um erro absoluto médio de 0,29 × temperature_stdgraus
Celsius: 2,57 ° C.

Listagem 6.36. Convertendo o MAE de volta para um erro Celsius

celsius_mae = 0,29 * std [1]

Esse é um erro absoluto médio razoavelmente grande. Agora o jogo é usar seu conhecimento de
aprendizagem profunda para fazer melhor.

6.3.4. Uma abordagem básica de aprendizado de máquina


Da mesma forma que é útil estabelecer uma linha de base de senso comum antes de tentar
abordagens de aprendizado de máquina, é útil tentar modelos simples e baratos de aprendizado
de máquina (como redes pequenas e densamente conectadas) antes de examinar modelos
complicados e computacionalmente caros como RNNs. Essa é a melhor maneira de garantir que
qualquer complexidade adicional que você enfrente no problema seja legítima e forneça
benefícios reais.

A listagem a seguir mostra um modelo totalmente conectado que inicia com o achatamento dos
dados e, em seguida, o executa por meio de duas Densecamadas. Observe a falta de função de
ativação na última Densecamada, o que é típico de um problema de regressão. Você usa o MAE
como a perda. Como você avalia exatamente os mesmos dados e com a mesma métrica que fez
com a abordagem de senso comum, os resultados serão diretamente comparáveis.

Listagem 6.37. Treinando e avaliando um modelo densamente conectado

de keras.models import Sequential

das camadas de importação keras

de keras.optimizers import RMSprop

model = Sequential ()
model.add (layers.Flatten (input_shape = (lookback // passo, float_data.shape
[-1])))

model.add (layers.Dense (32, activation = 'relu'))

model.add (layers.Dense (1))

model.compile (optimizer = RMSprop (), perda = 'mae')

history = model.fit_generator (train_gen,

steps_per_epoch = 500,

épocas = 20,

validation_data = val_gen,

validation_steps = val_steps)

Vamos exibir as curvas de perda para validação e treinamento (veja a figura 6.20 ).

Figura 6.20. Perda de treinamento e validação na tarefa de previsão de temperatura do Jena com uma rede simples e
densamente conectada

Listagem 6.38. Plotando resultados

import matplotlib.pyplot como plt

perda = history.history ['perda']

val_loss = history.history ['val_loss']

épocas = intervalo (1, len (perda) + 1)

plt.figure ()
plt.plot (epochs, loss, 'bo', label = 'perda de treino')

plt.plot (epochs, val_loss, 'b', label = 'Perda de validação')

plt.title ('Perda de treinamento e validação')

plt.legend ()

plt.show ()

Algumas das perdas de validação estão próximas da linha de base de não-aprendizado, mas não
de forma confiável. Isso mostra o mérito de ter essa linha de base em primeiro lugar: não é fácil
superá-la. Seu senso comum contém muitas informações valiosas que um modelo de
aprendizado de máquina não tem acesso.

Você pode se perguntar, se existe um modelo simples e de bom desempenho para ir dos dados
até os alvos (a linha de base do senso comum), por que o modelo que você está treinando não o
encontra e melhora? Porque esta solução simples não é o que sua configuração de treinamento
está procurando. O espaço dos modelos em que você está procurando uma solução - ou seja, o
espaço da sua hipótese - é o espaço de todas as redes possíveis de duas camadas com a
configuração que você definiu. Essas redes já são bastante complicadas. Quando você está
procurando por umCom um espaço de modelos complicados, a linha de base simples e de bom
desempenho pode ser desaprendida, mesmo que seja tecnicamente parte do espaço de
hipóteses. Essa é uma limitação bastante significativa do aprendizado de máquina em geral: a
menos que o algoritmo de aprendizado seja codificado para procurar um tipo específico de
modelo simples, o aprendizado de parâmetro pode, às vezes, falhar em encontrar uma solução
simples para um problema simples.

6.3.5. Uma primeira linha de base recorrente


A primeira abordagem totalmente conectada não funcionou bem, mas isso não significa que o
aprendizado de máquina não seja aplicável a esse problema. A abordagem anterior primeiro
nivelou as séries de tempo, o que removeu a noção de tempo dos dados de entrada. Vamos, em
vez disso, examinar os dados como eles são: uma sequência em que causalidade e ordem são
importantes. Você experimentará um modelo de processamento de sequência recorrente - ele
deve ser o ajuste perfeito para esses dados de sequência, precisamente porque explora a
ordenação temporal dos pontos de dados, diferentemente da primeira abordagem.

Em vez da LSTMcamada apresentada na seção anterior, você usará a GRUcamada desenvolvida


por Chung et al. em 2014. [ 5 ] As camadas de unidades recorrentes (GRU) funcionam usando o
mesmo princípio que o LSTM, mas são um pouco simplificadas e, portanto, mais baratas de
serem executadas (embora possam não ter tanto poder representacional quanto o LSTM). Esse
trade-off entre custo computacional e poder representacional é visto em toda parte no
aprendizado de máquina.

Junyoung Chung et al., “Avaliação Empírica de Redes Neurais Recorrentes Conectadas na Modelagem de Seqüências”, Conferência sobre

Sistemas de Processamento de Informações Neurais (2014), https://arxiv.org/abs/1412.3555 .

Listagem 6.39. Treinando e avaliando um modelo baseado em GRU

de keras.models import Sequential


das camadas de importação keras

de keras.optimizers import RMSprop

model = Sequential ()

model.add (layers.GRU (32, input_shape = (Nenhum, float_data.shape [-1])))

model.add (layers.Dense (1))

model.compile (optimizer = RMSprop (), perda = 'mae')

history = model.fit_generator (train_gen,

steps_per_epoch = 500,

épocas = 20,

validation_data = val_gen,

validation_steps = val_steps)

A Figura 6.21 mostra os resultados. Muito melhor! Você pode superar significativamente a
linha de base de senso comum, demonstrando o valor do aprendizado de máquina, bem como a
superioridade das redes recorrentes em comparação com redes densas de achatamento de
seqüência nesse tipo de tarefa.

Figura 6.21. Perda de treinamento e validação na tarefa de previsão de temperatura de Jena com uma GRU

O novo MAE de validação de ~ 0,265 (antes de começar a sobrealcançar significativamente) se


traduz em um erro absoluto médio de 2,35 ° C após a desnormalização. Isso é um ganho sólido
no erro inicial de 2,57 ° C, mas você provavelmente ainda tem uma pequena margem de
melhoria.

6.3.6. Usando dropout recorrente para combater overfitting


É evidente a partir das curvas de treinamento e validação que o modelo está sendo overfitting:
as perdas de treinamento e validação começam a divergir consideravelmente após algumas
épocas. Você já está familiarizado com uma técnica clássica para combater esse fenômeno:
dropout, que zera aleatoriamente as unidades de entrada de uma camada para quebrar as
correlações de acaso nos dados de treinamento aos quais a camada está exposta. Mas como
aplicar corretamente o dropout em redes recorrentes não é uma questão trivial. Há muito se
sabe que a aplicação do abandono antes de uma camada recorrente dificulta a aprendizagem,
em vez de ajudar na regularização. Em 2015, Yarin Gal, como parte de sua tese de doutorado em
aprendizado profundo bayesiano, [ 6 ]determinou a maneira correta de usar o dropout com uma
rede recorrente: a mesma máscara de dropout (o mesmo padrão de unidades descartadas) deve
ser aplicada a cada timestep, em vez de uma máscara de dropout que varia aleatoriamente de
timestep a timestep. Além do mais, a fim de regularizar as representações formadas pelos
portais recorrentes de camadas como GRUe LSTM, uma máscara de dropout constante no tempo
deve ser aplicada às ativações recorrentes internas da camada (uma máscara de
dropout recorrente ). Usar a mesma máscara de abandono a cada timestep permite que a rede
propague adequadamente seu erro de aprendizado ao longo do tempo; uma máscara de
abandono temporal aleatória interromperia esse sinal de erro e seria prejudicial ao processo de
aprendizagem.

Veja Yarin Gal, “Incerteza na Aprendizagem Profunda (Tese de Doutorado)”, 13 de outubro de

2016, http://mlg.eng.cam.ac.uk/yarin/blog_2248.html .

Yarin Gal fez sua pesquisa usando Keras e ajudou a construir esse mecanismo diretamente nas
camadas recorrentes de Keras. Cada camada recorrente em Keras possui dois argumentos
relacionados a dropout:, dropoutum float especificando a taxa de dropout para unidades de
entrada da camada, e recurrent_dropoutespecificando a taxa de dropout das unidades
recorrentes. Vamos adicionar dropout e dropout recorrente à GRUcamada e ver como isso afeta o
overfitting. Como as redes que estão sendo regularizadas com o dropout sempre demoram mais
para convergir totalmente, você treinará a rede para o dobro de épocas.

Listagem 6.40. Treinamento e avaliação de um modelo baseado em GRU regularizado por desistência

de keras.models import Sequential

das camadas de importação keras

de keras.optimizers import RMSprop

model = Sequential ()

model.add (layers.GRU (32,

abandono = 0,2,

recurrent_dropout = 0,2,

input_shape = (Nenhum, float_data.shape [-1])))

model.add (layers.Dense (1))

model.compile (optimizer = RMSprop (), perda = 'mae')

history = model.fit_generator (train_gen,


steps_per_epoch = 500,

épocas = 40,

validation_data = val_gen,

validation_steps = val_steps)

A Figura 6.22 mostra os resultados. Sucesso! Você não está mais exagerando durante as
primeiras 30 épocas. Mas embora você tenha pontuações de avaliação mais estáveis, suas
melhores pontuações não são muito menores do que eram anteriormente.

Figura 6.22. Perda de treinamento e validação na tarefa de previsão de temperatura de Jena com um GRU regularizado por
abandono

6.3.7. Empilhando camadas recorrentes


Como você não está mais com overfitting, mas parece ter atingido um gargalo de desempenho,
considere aumentar a capacidade da rede. Lembre-se da descrição do fluxo de trabalho
universal de aprendizado de máquina: geralmente é uma boa ideia aumentar a capacidade de
sua rede até que o overfitting se torne o principal obstáculo (supondovocê já está tomando
medidas básicas para atenuar o overfitting, como o uso de dropout). Contanto que você não
esteja super adaptando muito mal, você provavelmente estará abaixo da capacidade.

Aumentar a capacidade de rede normalmente é feito aumentando o número de unidades nas


camadas ou adicionando mais camadas. O empilhamento recorrente de camadas é uma forma
clássica de construir redes recorrentes mais poderosas: por exemplo, o que atualmente alimenta
o algoritmo do Google Tradutor é uma pilha de sete LSTMcamadas grandes - isso é enorme.

Para empilhar camadas recorrentes umas em cima das outras em Keras, todas as camadas
intermediárias devem retornar sua sequência completa de saídas (um tensor 3D) em vez de sua
saída no último intervalo de tempo. Isso é feito especificando return_sequences=True.

Listagem 6.41. Treinamento e avaliação de um modelo GRU empilhado regularizado por desistência

de keras.models import Sequential

das camadas de importação keras

de keras.optimizers import RMSprop


model = Sequential ()

model.add (layers.GRU (32,

abandono = 0,1,

recurrent_dropout = 0,5,

return_sequences = Verdadeiro,

input_shape = (Nenhum, float_data.shape [-1])))

model.add (layers.GRU (64, ativação = 'relu',

abandono = 0,1,

recurrent_dropout = 0,5))

model.add (layers.Dense (1))

model.compile (optimizer = RMSprop (), perda = 'mae')

history = model.fit_generator (train_gen,

steps_per_epoch = 500,

épocas = 40,

validation_data = val_gen,

validation_steps = val_steps)

A Figura 6.23 mostra os resultados. Você pode ver que a camada adicionada melhora os
resultados um pouco, embora não significativamente. Você pode tirar duas conclusões:

 Como você ainda não está super adaptando muito mal, você pode aumentar com
segurança o tamanho de suas camadas em uma busca pela melhoria da perda de
validação. Isto tem um custo computacional não desprezível, no entanto.
 Adicionar uma camada não ajudou por um fator significativo, portanto, você pode estar
vendo retornos decrescentes de aumentar a capacidade da rede neste momento.
Figura 6.23. Perda de treinamento e validação na tarefa de previsão de temperatura Jena com uma rede GRU empilhada

6.3.8. Usando RNNs bidirecionais


A última técnica introduzida nesta seção é chamada de RNNs bidirecionais . Um RNN
bidirecional é uma variante RNN comum que pode oferecer um desempenho maior do que um
RNN regular em determinadas tarefas. É freqüentemente usado no processamento de
linguagem natural - você pode chamar isso de canivete suíço de aprendizado profundo para
processamento de linguagem natural.

Os RNNs são notavelmente dependentes da ordem ou dependentes do tempo: eles processam os


timesteps de suas sequências de entrada em ordem, e embaralhar ou reverter os timesteps pode
mudar completamente as representações que o RNN extrai da sequência. Esta é precisamente a
razão pela qual eles têm um bom desempenho em problemas onde a ordem é significativa, como
o problema de previsão de temperatura. Um RNN bidirecional explora a sensibilidade do pedido
de RNNs: consiste em usar dois RNNs regulares, como o GRUeLSTMcamadas com as quais você
já está familiarizado, cada qual processa a sequência de entrada em uma direção (cronológica e
anticronologicamente) e, em seguida, mescla suas representações. Ao processar uma sequência
nos dois sentidos, um RNN bidirecional pode capturar padrões que podem ser ignorados por
um RNN unidirecional.

Notavelmente, o fato de que as camadas RNN nesta seção processaram sequências em ordem
cronológica (datas mais antigas primeiro) pode ter sido uma decisão arbitrária. Pelo menos, é
uma decisão que não fizemos nenhuma tentativa de questionar até agora. Os RNNs poderiam
ter funcionado bem o suficiente se processassem seqüências de entrada em ordem
anticronológica, por exemplo (newest timepeps first)? Vamos tentar isso na prática e ver o que
acontece. Tudo o que você precisa fazer é gravar uma variante do gerador de dados onde as
seqüências de entrada são revertidas ao longo da dimensão de tempo (substitua a última linha
por yield samples[:, ::-1, :], targets). Treinando a mesma GRUrede de camada
única usada no primeiro experimento desta seção, você obtém os resultados mostrados
na figura 6.24 .
Figura 6.24. Perda de treinamento e validação na tarefa de previsão de temperatura de Jena com uma GRU treinada em
sequências invertidas

A GRU de ordem reversa tem um desempenho muito baixo até mesmo na linha de base de senso
comum, indicando que, nesse caso, o processamento cronológico é importante para o sucesso de
sua abordagem. Isso faz todo o sentido: o subjacenteGRUA camada normalmente será melhor
para lembrar o passado recente do que o passado distante e, naturalmente, os pontos de dados
climáticos mais recentes são mais preditivos do que os pontos de dados mais antigos para o
problema (é isso que torna a linha de base de senso comum bastante forte). Assim, a versão
cronológica da camada é obrigada a superar a versão de ordem inversa. É importante ressaltar
que isso não é verdade para muitos outros problemas, incluindo a linguagem natural:
intuitivamente, a importância de uma palavra para entender uma frase geralmente não depende
de sua posição na sentença. Vamos tentar o mesmo truque no exemplo do LSTM IMDB
da seção 6.2 .

Listagem 6.42. Treinar e avaliar um LSTMusando sequências invertidas

de keras.datasets import imdb

da sequência de importação keras.preprocessing

das camadas de importação keras

de keras.models import Sequential

max_features = 10000 1

maxlen = 500 2

(x_train, y_train), (x_test, y_test) = imdb.load_data (

num_words = max_features) 3

x_train = [x [:: - 1] para x em x_train] 4

x_test = [x [:: - 1] para x em x_test] 4


x_train = sequence.pad_sequences (x_train, maxlen = maxlen) 5

x_test = seqüência.pad_sequências (x_teste, maxlen = maxlen) 5

model = Sequential ()

model.add (layers.Embedding (max_features, 128))

model.add (layers.LSTM (32))

model.add (layers.Dense (1, activation = 'sigmoid'))

model.compile (optimizer = 'rmsprop',

perda = 'binary_crossentropy',

metrics = ['acc'])

history = model.fit (x_train, y_train,

épocas = 10,

batch_size = 128,

validation_split = 0,2)

 1 Número de palavras a considerar como recursos


 2 Corta textos após este número de palavras (entre as palavras mais
comuns max_features)
 3 Carrega dados
 4 inverte seqüências
 5 seqüências de Pads

Você obtém desempenho quase idêntico ao da ordem cronológica LSTM. Notavelmente, em tal
conjunto de dados de texto, inverteu-ordem de processamento funciona tão bem como o
processamento cronológica, confirmando a hipótese de que, embora a ordem das
palavras faz questão em linguagem compreensão, que pedir que você usa não é crucial. É
importante ressaltar que um RNN treinado em sequências invertidas aprenderá diferentes
representações do que um treinado nas sequências originais, da mesma forma que você teria
modelos mentais diferentes se o tempo fluísse para trás no mundo real - se você vivesse uma
vida onde morresse no primeiro dia e nasceram no seu último dia. Em aprendizado de máquina,
representações diferentes, mas úteisSempre vale a pena explorar, e quanto mais eles diferem,
melhor: eles oferecem um novo ângulo a partir do qual analisar seus dados, capturando aspectos
dos dados que foram perdidos por outras abordagens e, portanto, podem ajudar a melhorar o
desempenho em uma tarefa. Essa é a intuição por trás do conjunto , um conceito que
exploraremos no capítulo 7 .

Um RNN bidirecional explora essa ideia para melhorar o desempenho de RNNs de ordem
cronológica. Ele examina sua sequência de entrada nos dois sentidos (consulte a figura 6.25 ),
obtendo representações potencialmente mais ricas e capturando padrões que podem ter sido
perdidos somente pela versão de ordem cronológica.
Figura 6.25. Como funciona uma camada RNN bidirecional

Para instanciar um RNN bidirecional em Keras, você usa a Bidirectionalcamada, que toma
como primeiro argumento uma instância de camada recorrente. Bidirectionalcria uma
segunda instância separada dessa camada recorrente e usa uma instância para processar as
seqüências de entrada em ordem cronológica e a outra instância para processar as seqüências de
entrada em ordem inversa. Vamos tentar na tarefa de análise de sentimentos do IMDB.

Listagem 6.43. Treinamento e avaliação de um bidirecional LSTM

model = Sequential ()

model.add (layers.Embedding (max_features, 32))

model.add (layers.Bidirectional (layers.LSTM (32)))

model.add (layers.Dense (1, activation = 'sigmoid'))

model.compile (optimizer = 'rmsprop', loss = 'binary_crossentropy', métricas =


['acc'])

history = model.fit (x_train, y_train,

épocas = 10,

batch_size = 128,

validation_split = 0,2)

Ele executa um pouco melhor que o normal que LSTMvocê tentou na seção anterior, alcançando
mais de 89% de precisão na validação. Também parece sobrecarregar mais rapidamente, o que
não é surpreendente porque uma camada bidirecional tem duas vezes mais parâmetros que um
cronológico LSTM. Com alguma regularização, a abordagem bidirecional provavelmente seria
um bom desempenho nessa tarefa.

Agora vamos tentar a mesma abordagem na tarefa de previsão de temperatura.

Listagem 6.44. Treinando um bidirecional GRU

de keras.models import Sequential

das camadas de importação keras


de keras.optimizers import RMSprop

model = Sequential ()

model.add (layers.Bidirectional (

layers.GRU (32), input_shape = (Nenhum, float_data.shape [-1])))

model.add (layers.Dense (1))

model.compile (optimizer = RMSprop (), perda = 'mae')

history = model.fit_generator (train_gen,

steps_per_epoch = 500,

épocas = 40,

validation_data = val_gen,

validation_steps = val_steps)

Isso funciona tão bem quanto a GRUcamada normal . É fácil entender por quê: toda a capacidade
preditiva deve vir da metade cronológica da rede, porque a metade anticronológica é conhecida
por estar gravemente abaixo do desempenho nessa tarefa (novamente, porque o passado recente
é muito mais importante do que o passado distante neste caso). ).

6.3.9. Indo ainda mais longe


Há muitas outras coisas que você poderia tentar, a fim de melhorar o desempenho no problema
de previsão de temperatura:

 Ajuste o número de unidades em cada camada recorrente na configuração


empilhada. As escolhas atuais são em grande parte arbitrárias e, portanto,
provavelmente sub-ótimas.
 Ajuste a taxa de aprendizado usada pelo RMSpropotimizador.
 Tente usar LSTMcamadas em vez de GRUcamadas.
 Tente usar um regressor densamente conectado maior no topo das camadas
recorrentes: isto é, uma Densecamada maior ou até mesmo uma pilha
de Densecamadas.
 Não se esqueça de, eventualmente, executar os modelos com melhor desempenho (em
termos de validação MAE) no conjunto de testes! Caso contrário, você desenvolverá
arquiteturas que estão super adaptando ao conjunto de validação.

Como sempre, o aprendizado profundo é mais uma arte do que uma ciência. Podemos fornecer
diretrizes que sugiram o que provavelmente funcionará ou não em determinado problema, mas,
em última análise, todo problema é único; você terá que avaliar empiricamente estratégias
diferentes. Atualmente, não há uma teoria que lhe diga com antecedência o que você deve fazer
para solucionar um problema de maneira ideal. Você deve iterar.

6.3.10. Empacotando
Veja o que você deve tirar desta seção:
 Como você aprendeu no capítulo 4 , ao abordar um novo problema, é bom primeiro
estabelecer linhas de base de bom senso para sua métrica escolhida. Se você não tem
uma base para vencer, você não pode dizer se você está fazendo um progresso real.
 Tente modelos simples antes dos caros, para justificar a despesa adicional. Às vezes, um
modelo simples se tornará sua melhor opção.
 Quando você tem dados em que a ordenação temporal é importante, as redes
recorrentes são um ótimo ajuste e superam facilmente os modelos que primeiro nivelam
os dados temporais.
 Para usar o dropout com redes recorrentes, você deve usar uma máscara de dropout de
constante de tempo e máscara de dropout recorrente. Estes são construídos em
camadas recorrentes Keras, então tudo que você tem a fazer é usar
os argumentos dropoute recurrent_dropoutde camadas recorrentes.
 Os RNNs empilhados fornecem mais poder representacional do que uma única camada
RNN. Eles também são muito mais caros e, portanto, nem sempre valem a
pena. Embora eles ofereçam ganhos claros em problemas complexos (como a tradução
automática), eles nem sempre são relevantes para problemas menores e mais simples.
 Os RNNs bidirecionais, que analisam uma sequência nos dois sentidos, são úteis em
problemas de processamento de linguagem natural. Mas eles não são fortes em dados
de sequência, onde o passado recente é muito mais informativo do que o início da
sequência.

Nota

Há dois conceitos importantes que não abordaremos em detalhes aqui: atenção recorrente e
mascaramento de sequência. Ambos tendem a ser especialmente relevantes para o
processamento de linguagem natural e não são particularmente aplicáveis ao problema de
previsão de temperatura. Vamos deixá-los para estudo futuro fora deste livro.

Mercados e aprendizado de máquina

Alguns leitores são obrigados a tomar as técnicas que introduzimos aqui e testá-las no problema
da previsão do preço futuro dos títulos no mercado de ações (ou taxas de câmbio, e assim por
diante). Os mercados têm características estatísticas muito diferentes dos fenômenos naturais,
como padrões climáticos. Tentar usar o aprendizado de máquina para vencer mercados, quando
você só tem acesso a dados disponíveis publicamente, é um esforço difícil, e é provável que você
perca tempo e recursos sem nada para mostrar.

Lembre-se sempre de que, quando se trata de mercados, o desempenho passado não é um bom
indicador de retornos futuros - olhar no espelho retrovisor é uma maneira ruim de dirigir. O
aprendizado de máquina, por outro lado, é aplicável a conjuntos de dados em que o
passado é um bom preditor do futuro.

6.4. PROCESSAMENTO DE SEQUÊNCIAS COM CONVNETS

No capítulo 5 , você aprendeu sobre redes neurais convolucionais (convnets) e como elas
funcionam particularmente bem em problemas de visão computacional, devido à sua capacidade
de operar de forma convolucional , extraindo recursos de correções de entrada locais e
permitindo modularidade de representação e eficiência de dados. As mesmas propriedades que
fazem as redes se destacarem na visão computacional também as tornam altamente relevantes
para o processamento de seqüências. O tempo pode ser tratado como uma dimensão espacial,
como a altura ou a largura de uma imagem 2D.
Tais capas 1D podem ser competitivas com RNNs em certos problemas de processamento de
sequência, geralmente a um custo computacional consideravelmente mais
barato. Recentemente, os modelos 1D, normalmente usados com núcleos dilatados, foram
usados com grande sucesso para geração de áudio e tradução automática. Além desses sucessos
específicos, há muito tempo se sabe que pequenas convnets 1D podem oferecer uma alternativa
rápida aos RNNs para tarefas simples, como classificação de texto e previsão de timeseries.

6.4.1. Compreender a convolução 1D para dados sequenciais


As camadas de convolução introduzidas anteriormente eram convoluções 2D, extraindo patches
2D de tensores de imagem e aplicando uma transformação idêntica em cada patch. Da mesma
forma, você pode usar convoluções 1D, extraindo patches 1D locais (subsequências) de
sequências (veja a figura 6.26).

Figura 6.26. Como funciona a convolução 1D: cada timestep de saída é obtido de um patch temporal na sequência de entrada.

Essas camadas de convolução 1D podem reconhecer padrões locais em uma sequência. Como a
mesma transformação de entrada é executada em cada patch, um padrão aprendido em uma
determinada posição em uma sentença pode ser reconhecido posteriormente em uma posição
diferente, tornando invariante de tradução de convés 1D (para traduções temporais). Por
exemplo, uma seqüência de processamento de caracteres de convecção 1D usando janelas de
convolução de tamanho 5 deve ser capaz de aprender palavras ou fragmentos de tamanho 5 ou
menos, e deve ser capaz de reconhecer essas palavras em qualquer contexto em uma sequência
de entrada. Uma convã 1D de nível de personagem é, portanto, capaz de aprender sobre a
morfologia da palavra.

6.4.2. 1D pooling para dados de sequência


Você já está familiarizado com operações de pool 2D, como pooling médio 2D e pool máximo,
usado em convnets para reduzir a resolução de imagem de tensores. A operação de pool 2D tem
um equivalente de 1D: extrair patches 1D (subsequences) de uma entrada e gerar o valor
máximo (pool máximo) ou valor médio (pool médio). Assim como nas convnets 2D, isso é usado
para reduzir o comprimento de entradas 1D ( subamostragem ).

6.4.3. Implementando uma conv. 1D


Em Keras, você usa um convnet 1D através da Conv1Dcamada, que tem uma interface
semelhante a Conv2D. Leva como tensores 3D de entrada com forma (samples, time,
features)e retorna tensores 3D de formato similar. A janela de convolução é uma janela 1D
no eixo temporal: eixo 1 no tensor de entrada.

Vamos construir uma simples convecção 1D de duas camadas e aplicá-la à tarefa de classificação
de sentimentos do IMDB com a qual você já está familiarizado. Como lembrete, este é o código
para obter e pré-processar os dados.

Listagem 6.45. Preparando os dados do IMDB

de keras.datasets import imdb

da sequência de importação keras.preprocessing

max_features = 10000

max_len = 500

print ('Carregando dados ...')

(x_train, y_train), (x_test, y_test) = imdb.load_data (num_words =


max_features)

print (len (x_train), 'sequências de trem')

print (len (x_test), 'sequências de teste')

print ('seqüências Pad (amostras x tempo)')

x_train = sequence.pad_sequences (x_train, maxlen = max_len)

x_test = sequence.pad_sequences (x_test, maxlen = max_len)

print ('x_train shape:', x_train.shape)

print ('x_test shape:', x_test.shape)

As capas 1D são estruturadas da mesma forma que suas contrapartes 2D, que você usou
no capítulo 5 : elas consistem em uma pilha Conv1De MaxPooling1Dcamadas, terminando
em uma camada de pool global ou uma Flattencamada, que transformam as saídas 3D em
saídas 2D, permitindo você adicionar uma ou mais Densecamadas ao modelo para classificação
ou regressão.

Uma diferença, no entanto, é o fato de que você pode se dar ao luxo de usar janelas de
convolução maiores com os modelos 1D. Com uma camada de convolução 2D, uma janela de
convolução 3 × 3 contém 3 × 3 = 9 vetores de recursos; mas com uma camada de convolução 1D,
uma janela de convolução de tamanho 3 contém apenas 3 vetores de recursos. Você pode assim
facilmente ter janelas de convolução 1D de tamanho 7 ou 9.

Este é o exemplo 1D convnet para o conjunto de dados do IMDB.

Listagem 6.46. Treinamento e avaliação de uma simples convenção 1D nos dados do IMDB

de keras.models import Sequential


das camadas de importação keras

de keras.optimizers import RMSprop

model = Sequential ()

model.add (layers.Embedding (max_features, 128, input_length = max_len))

model.add (layers.Conv1D (32, 7, ativação = 'relu'))

model.add (layers.MaxPooling1D (5))

model.add (layers.Conv1D (32, 7, ativação = 'relu'))

model.add (layers.GlobalMaxPooling1D ())

model.add (layers.Dense (1))

model.summary ()

model.compile (optimizer = RMSprop (lr = 1e-4),

perda = 'binary_crossentropy',

metrics = ['acc'])

history = model.fit (x_train, y_train,

épocas = 10,

batch_size = 128,

validation_split = 0,2)

As figuras 6.27 e 6.28 mostram os resultados de treinamento e validação. A precisão da


validação é um pouco menor que a do LSTM, mas o tempo de execução é mais rápido tanto na
CPU quanto na GPU (o aumento exato na velocidade irá variar muito dependendo da sua
configuração exata). Neste ponto, você poderia treinar novamente este modelo para o número
certo de épocas (oito) e executá-lo no conjunto de testes. Essa é uma demonstração convincente
de que uma conv. 1D pode oferecer uma alternativa rápida e barata a uma rede recorrente em
uma tarefa de classificação de sentimentos no nível da palavra.
Figura 6.27. Perda de treinamento e validação no IMDB com uma simples convulsão 1D

Figura 6.28. Treinamento e validação de validação no IMDB com uma simples convulsão 1D

6.4.4. Combinando CNNs e RNNs para processar seqüências longas


Como os 1D convnets processam os patches de entrada de forma independente, eles não são
sensíveis à ordem dos timesteps (além de uma escala local, o tamanho das janelas de
convolução), ao contrário dos RNNs. É claro que, para reconhecer padrões de longo prazo, é
possível empilhar muitas camadas de convolução e agrupar camadas, resultando em camadas
superiores que verão longos trechos das entradas originais, mas ainda é uma maneira bastante
fraca de induzir a sensibilidade do pedido. Uma forma de evidenciar essa fraqueza é tentar usar
convecções 1D no problema de previsão de temperatura, em que a sensibilidade à ordem é
fundamental para produzir boas previsões. O exemplo seguinte reutiliza as seguintes variáveis
definidas anteriormente: float_data, train_gen, val_gen, e val_steps.

Listagem 6.47. Treinar e avaliar uma simples convecção 1D nos dados de Jena

de keras.models import Sequential

das camadas de importação keras


de keras.optimizers import RMSprop

model = Sequential ()

model.add (layers.Conv1D (32, 5, ativação = 'relu',

input_shape = (Nenhum, float_data.shape [-1])))

model.add (layers.MaxPooling1D (3))

model.add (layers.Conv1D (32, 5, ativação = 'relu'))

model.add (layers.MaxPooling1D (3))

model.add (layers.Conv1D (32, 5, ativação = 'relu'))

model.add (layers.GlobalMaxPooling1D ())

model.add (layers.Dense (1))

model.compile (optimizer = RMSprop (), perda = 'mae')

history = model.fit_generator (train_gen,

steps_per_epoch = 500,

épocas = 20,

validation_data = val_gen,

validation_steps = val_steps)

A Figura 6.29 mostra os MAEs de treinamento e validação.


Figura 6.29. Perda de treinamento e validação na tarefa de previsão de temperatura de Jena com uma simples convecção 1D

A validação do MAE permanece nos 0,40s: você não pode nem superar a linha de base do senso
comum usando a pequena convnet. Novamente, isso acontece porque o convnet procura
padrões em qualquer ponto da entrada das séries de tempo e não tem conhecimento da posição
temporal de um padrão que vê (no início, no final e assim por diante). Como os pontos de dados
mais recentes devem ser interpretados de maneira diferente dos pontos de dados mais antigos,
no caso desse problema de previsão específico, a convnet falha ao produzir resultados
significativos. Essa limitação de convnets não é um problema com os dados do IMDB, porque os
padrões de palavras-chave associados a um sentimento positivo ou negativo são informativos
independentemente de onde eles são encontrados nas sentenças de entrada.

Uma estratégia para combinar a velocidade e a leveza das conversas com a sensibilidade à
ordem dos RNNs é usar uma convnet 1D como uma etapa de pré-processamento antes de uma
RNN (consulte a figura 6.30 ). Isso é especialmente benéfico quando você está lidando com
sequências que são tão longas que elas não podem ser processadas de maneira realista com
RNNs, como sequências com milhares de etapas. A convnet transformará a longa seqüência de
entrada em seqüências muito mais curtas (com resolução reduzida) de recursos de nível
superior. Essa seqüência de recursos extraídos se torna a entrada para a parte RNN da rede.

Figura 6.30. Combinando uma convnet 1D e um RNN para processar seqüências longas

Essa técnica não é vista com freqüência em trabalhos de pesquisa e aplicações práticas,
possivelmente porque não é bem conhecida. É eficaz e deveria ser mais comum. Vamos tentar
no conjunto de dados de previsão de temperatura. Como essa estratégia permite manipular
seqüências muito mais longas, é possível observar dados de mais tempo (aumentando
o lookbackparâmetro do gerador de dados) ou ver as séries de tempo de alta resolução
(diminuindo o stepparâmetro do gerador). Aqui, um pouco arbitrariamente, você usará
um stepque é metade do tamanho, resultando em uma série de tempo duas vezes mais longa,
onde oos dados de temperatura são amostrados a uma taxa de 1 ponto por 30 minutos. O
exemplo reutiliza a generatorfunção definida anteriormente.

Listagem 6.48. Preparando geradores de dados de alta resolução para o conjunto de dados do Jena

etapa = 3 1

lookback = 720 2

atraso = 144 2

train_gen = generator (float_data,

lookback = lookback
atraso = atraso

min_index = 0,

max_index = 200000,

shuffle = Verdadeiro

passo = passo)

val_gen = gerador (float_data,

lookback = lookback

atraso = atraso

min_index = 200001,

max_index = 300000,

passo = passo)

test_gen = generator (float_data,

lookback = lookback

atraso = atraso

min_index = 300001,

max_index = None,

passo = passo)

val_steps = (300000 - 200001 - lookback) // 128

test_steps = (len (float_data) - 300001 - lookback) // 128

 1 Anteriormente definido para 6 (1 ponto por hora); agora 3 (1 ponto por 30


min)
 2 inalterado

Este é o modelo, começando com duas Conv1Dcamadas e seguindo com uma GRUcamada. A
Figura 6.31mostra os resultados.
Figura 6.31. Perda de treinamento e validação na tarefa de previsão de temperatura de Jena com uma convecção 1D seguida
por uma gru

Listagem 6.49. Modelo combinando uma base convolucional 1D e uma GRUcamada

de keras.models import Sequential

das camadas de importação keras

de keras.optimizers import RMSprop

model = Sequential ()

model.add (layers.Conv1D (32, 5, ativação = 'relu',

input_shape = (Nenhum, float_data.shape [-1])))

model.add (layers.MaxPooling1D (3))

model.add (layers.Conv1D (32, 5, ativação = 'relu'))

model.add (layers.GRU (32, dropout = 0.1, recurrent_dropout = 0.5))

model.add (layers.Dense (1))

model.summary ()

model.compile (optimizer = RMSprop (), perda = 'mae')

history = model.fit_generator (train_gen,

steps_per_epoch = 500,

épocas = 20,

validation_data = val_gen,
validation_steps = val_steps)

A julgar pela perda de validação, essa configuração não é tão boa quanto a
regularização GRUsozinha, mas é significativamente mais rápida. Ele analisa o dobro de dados, o
que, nesse caso, não parece ser muito útil, mas pode ser importante para outros conjuntos de
dados.

6.4.5. Empacotando
Veja o que você deve tirar desta seção:

 Da mesma forma que as réplicas 2D funcionam bem para processar padrões visuais no
espaço 2D, as réplicas 1D têm um bom desempenho no processamento de padrões
temporais. Eles oferecem uma alternativa mais rápida para os RNNs em alguns
problemas, em particular tarefas de processamento de linguagem natural.
 Tipicamente, as conversas 1D são estruturadas de maneira muito semelhante a seus
equivalentes 2D do mundo da visão computacional: elas consistem em pilhas
de Conv1Dcamadas e Max-Pooling1Dcamadas, terminando em uma operação de
agrupamento global ou operação de achatamento.
 Como os RNNs são extremamente caros para processar sequências muito longas, mas as
convntas 1D são baratas, pode ser uma boa ideia usar uma convnet 1D como uma etapa
de pré-processamento antes de uma RNN, encurtando a sequência e extraindo
representações úteis para o RNN processar.

Resumo do capítulo

 Neste capítulo, você aprendeu as seguintes técnicas, que são amplamente aplicáveis a
qualquer conjunto de dados de dados de sequência, do texto às séries de tempo:
 Como tokenizar texto
 Que palavra é a incorporação e como usá-las
 Quais são as redes recorrentes e como usá-las
 Como empilhar camadas RNN e usar RNNs bidirecionais para construir
modelos de processamento de seqüência mais poderosos
 Como usar convnets 1D para processamento sequencial
 Como combinar convnets 1D e RNNs para processar seqüências longas
Você pode usar RNNs para regressão de timeseries (“predizendo o futuro”), classificação de
timeseries, detecção de anomalias em timeseries e rotulagem de seqüências (como identificação
de nomes ou datas em sentenças).
Da mesma forma, você pode usar as convés 1D para tradução automática (modelos
convolucionais seqüência a seqüência, como SliceNet [ a ] ), classificação de documentos e
correção ortográfica.

uma

Veja https://arxiv.org/abs/1706.03059 .

Se a ordem global é importante em seus dados de sequência, é preferível usar uma rede
recorrente para processá-la. Este é tipicamente o caso de timeseries, onde o passado recente é
provavelmente mais informativo do que o passado distante.
Se a ordenação global não for fundamentalmente significativa , então as torneiras 1D
funcionarão pelo menos tão bem e serão mais baratas. Este é frequentemente o caso dos dados
de texto, em que uma palavra-chave encontrada no início de uma frase é tão significativa quanto
uma palavra-chave encontrada no final.
Capítulo 6. Aprendizagem profunda para texto e
sequências
Este capítulo cobre

 Pré-processamento de dados de texto em representações úteis


 Trabalhando com redes neurais recorrentes
 Usando 1D convnets para processamento sequencial

Este capítulo explora modelos de aprendizagem profunda que podem processar textos
(entendidos como seqüências de palavras ou sequências de caracteres), séries de tempo e dados
de sequência em geral. Os dois algoritmos de aprendizagem profunda fundamentais para o
processamento de sequências são as redes neurais recorrentes e as conversões 1D , a versão
unidimensional das convnets 2D que abordamos nos capítulos anteriores. Vamos discutir ambas
as abordagens neste capítulo.

Aplicações desses algoritmos incluem o seguinte:

 Classificação de documentos e classificação de timeseries, como identificar o tópico de


um artigo ou o autor de um livro
 Comparações de séries temporais, como a estimativa de quão próximos dois
documentos estão relacionados ou dois tickers de ações
 Aprendizagem de seqüência a sequência, como decodificação de uma sentença em inglês
para francês
 Análise de sentimentos, como classificar o sentimento de tweets ou resenhas de filmes
como positivos ou negativos
 Previsão de séries temporais, como previsão do tempo futuro em um determinado local,
dados meteorológicos recentes

Os exemplos deste capítulo concentram-se em duas tarefas estreitas: análise de sentimento no


conjunto de dados do IMDB, uma tarefa que abordamos anteriormente no livro e previsão de
temperatura. Mas as técnicas demonstradas para essas duas tarefas são relevantes para todos os
aplicativos listados e muito mais.

6.1. TRABALHANDO COM DADOS DE TEXTO

O texto é uma das formas mais difundidas de dados de sequência. Pode ser entendido como uma
sequência de caracteres ou uma sequência de palavras, mas é mais comum trabalhar no nível
das palavras. Os modelos de processamento de sequências de aprendizado profundo
introduzidos nas seções a seguir podem usar texto para produzir uma forma básica de
compreensão de linguagem natural, suficiente para aplicativos que incluem classificação de
documentos, análise de sentimentos, identificação de autores e até perguntas e respostas (QA)
(em um contexto restrito). É claro, tenha em mente ao longo deste capítulo que nenhum desses
modelos de aprendizagem profunda realmente entende o texto em um sentido humano; em vez
disso, esses modelos podem mapear a estrutura estatística da linguagem escrita, o que é
suficiente para resolver muitas tarefas textuais simples.

Como todas as outras redes neurais, os modelos de aprendizagem profunda não aceitam como
texto bruto de entrada: eles só funcionam com tensores numéricos. Vectorizing text é o processo
de transformar texto em tensores numéricos. Isso pode ser feito de várias maneiras:

 Segmente o texto em palavras e transforme cada palavra em um vetor.


 Segmente o texto em caracteres e transforme cada caractere em um vetor.
 Extraia n-gramas de palavras ou caracteres e transforme cada n-grama em um vetor. N-
grams são grupos sobrepostos de várias palavras ou caracteres consecutivos.
Coletivamente, as diferentes unidades em que você pode dividir o texto (palavras, caracteres ou
n-gramas) são chamadas de tokens , e dividir o texto em tais tokens é chamado
de tokenização . Todos os processos de vetorização de vetores consistem em aplicar algum
esquema de tokenização e, em seguida, associar vetores numéricos aos tokens gerados. Esses
vetores, compactados em tensores de seqüência, são alimentados em redes neurais
profundas. Existem várias maneiras de associar um vetor a um token. Nesta seção, apresentarei
dois principais: codificação de tokens de um ponto a quente e incorporação de
token (normalmente usado exclusivamente para palavras e denominado incorporação de
palavras). O restante desta seção explica essas técnicas e mostra como usá-las para passar do
texto bruto para um tensor Numpy que você pode enviar para uma rede Keras.

Figura 6.1. Do texto aos tokens aos vetores

Noções básicas sobre n-grams e bag-of-words

Word n-grams são grupos de N (ou menos) palavras consecutivas que você pode extrair de uma
frase. O mesmo conceito também pode ser aplicado a caracteres em vez de palavras.

Aqui está um exemplo simples. Considere a frase “O gato sentou-se no tapete”. Ele pode ser
decomposto no seguinte conjunto de 2 gramas:

{"O", "O gato", "gato", "gato sentado", "sentado"

"sentado", "ligado", "no", "o", "o tapete", "tapete"}

Também pode ser decomposto no seguinte conjunto de 3 gramas:

{"O", "O gato", "gato", "gato sentado", "O gato sentado",

"sentado", "sentado", "ligado", "sentado gato", "no", "o",

"sentou-se no", "o tapete", "tapete", "no tapete"}

Tal conjunto é chamado de saco de 2 gramas ou saco de 3 gramas , respectivamente. O


termo sacoaqui se refere ao fato de que você está lidando com um conjunto de símbolos em vez
de uma lista ou sequência: os tokens não têm uma ordem específica. Essa família de métodos de
tokenização é chamada de bag-of-words .

Como o bag-of-words não é um método de tokenização que preserva a ordem (os tokens gerados
são entendidos como um conjunto, não uma sequência e a estrutura geral das sentenças é
perdida), ele tende a ser usado em processamento de linguagem superficial modelos, em vez de
modelos de aprendizagem profunda. A extração de n-grams é uma forma de engenharia de
recursos, e o aprendizado profundo acaba com esse tipo de abordagem rígida e quebradiça,
substituindo-a por aprendizado de recurso hierárquico. As convnets unidimensionais e as redes
neurais recorrentes, apresentadas mais adiante neste capítulo, são capazes de aprender
representações para grupos de palavras e caracteres sem serem explicitamente informados
sobre a existência de tais grupos, observando sequências contínuas de palavras ou
caracteres. Por esta razão, Não vamos cobrir mais n-gramas neste livro. Mas lembre-se de que
eles são uma ferramenta de engenharia de recursos poderosa e inevitável ao usar modelos de
processamento de texto simples e superficiais, como a regressão logística e florestas aleatórias.

6.1.1. Uma quente codificação de palavras e caracteres


Uma codificação quente é a maneira mais comum e básica de transformar um token em um
vetor. Você viu isso em ação nos exemplos iniciais do IMDB e Reuters no capítulo 3 (feito com
palavras, nesse caso). Ele consiste em associar um índice inteiro única com cada palavra e, em
seguida, transformar este índice inteiro i num vector binário de tamanho N (o tamanho do
vocabulário); o vetor é todo zeros, exceto pela i ésima entrada, que é 1.

Naturalmente, uma codificação a quente também pode ser feita no nível do personagem. Para
indubitavelmente levar para casa o que é uma codificação quente e como implementá-la,
as listagens 6.1e 6.2 mostram dois exemplos de brinquedo: um para palavras e outro para
caracteres.

Listagem 6.1. Codificação one-hot no nível da palavra (exemplo de brinquedo)

import numpy como np

samples = ['O gato sentou no tatame.', 'O cachorro comeu meu dever de casa.']
1

token_index = {} 2

para amostra em amostras:

por palavra em sample.split (): 3

se a palavra não estiver no token_index:

token_index [word] = len (token_index) + 1 4

max_length = 10 5

results = np.zeros (forma = (len (amostras),

comprimento máximo,

max (token_index.values ()) + 1)) 6

para i, amostra em enumerar (amostras):

para j, palavra na lista (enumerate (sample.split ())) [: max_length]:


índice = token_index.get (palavra)

resultados [i, j, index] = 1.

 1 Dados iniciais: uma entrada por amostra (neste exemplo, uma amostra é
uma sentença, mas poderia ser um documento inteiro)
 2 Cria um índice de todos os tokens nos dados
 3 Tokeniza as amostras pelo método de divisão. Na vida real, você também
tira pontos e caracteres especiais das amostras.
 4 Atribui um índice exclusivo para cada palavra única. Observe que você
não atribui o índice 0 a nada.
 5 Vectoriza as amostras. Você só considerará as primeiras palavras
max_length em cada amostra.
 6 Aqui é onde você armazena os resultados.

Listagem 6.2. Codificação one-hot em nível de caractere (exemplo de brinquedo)

string de importação

samples = ['O gato sentou no tatame.', 'O cachorro comeu meu dever de casa.']

caracteres = string.printable 1

token_index = dict (zip (intervalo (1, len (caracteres) + 1), caracteres))

max_length = 50

results = np.zeros ((len (amostras), max_length, max (token_index.keys ()) +


1))

para i, amostra em enumerar (amostras):

para j, caractere em enumerar (amostra):

índice = token_index.get (caractere)

resultados [i, j, index] = 1.

 1 Todos os caracteres ASCII imprimíveis

Observe que o Keras possui utilitários integrados para fazer uma codificação de texto quente no
nível de palavra ou nível de caractere, a partir de dados de texto bruto. Você deve usar esses
utilitários, pois eles cuidam de vários recursos importantes, como descartar caracteres especiais
de strings e levar em conta apenas as N palavras mais comuns em seu conjunto de dados (uma
restrição comum, para evitar lidar com espaços vetoriais de entrada muito grandes ).

Listagem 6.3. Usando Keras para codificação one-hot em nível de palavra

do Tokenizer de importação keras.preprocessing.text

samples = ['O gato sentou no tatame.', 'O cachorro comeu meu dever de casa.']

tokenizer = Tokenizer (num_words = 1000) 1


tokenizer.fit_on_texts (amostras) 2

seqüências = tokenizer.texts_to_sequences (samples) 3

one_hot_results = tokenizer.texts_to_matrix (amostras, modo = 'binário')


4

word_index = tokenizer.word_index 5

print ('Encontrado% s tokens exclusivos'% len (word_index))

 1 Cria um tokenizador, configurado para levar em conta apenas as 1.000


palavras mais comuns
 2 Constrói o índice da palavra
 3 Constrói o índice da palavra
 4 Você também pode obter diretamente as representações binárias
simples. Modos de vetorização diferentes da codificação de um-quente são
suportados por este tokenizer.
 5 Como você pode recuperar o índice de palavras que foi calculado

Uma variante da codificação one-hot é o chamado truque de hash quente , que você pode usar
quando o número de tokens exclusivos no seu vocabulário for muito grande para manipular
explicitamente. Em vez de atribuir explicitamente um índice a cada palavra e manter uma
referência desses índices em um dicionário, você pode dividir as palavras em vetores de
tamanho fixo. Isso geralmente é feito com uma função de hash muito leve. A principal vantagem
deste método é que ele acaba com a manutenção de um índice de palavras explícito, que
economiza memória e permite a codificação online dos dados (você pode gerar vetores de token
imediatamente, antes de ver todos os dados disponíveis). A única desvantagem dessa
abordagem é que ela é suscetível a colisões de hash: duas palavras diferentes podem acabar com
o mesmo hash, e subseqüentemente qualquer modelo de aprendizado de máquina olhando para
esses hashes não será capaz de dizer a diferença entre essas palavras. A probabilidade de
colisões de hash diminui quando a dimensionalidade do espaço de hash é muito maior do que o
número total de tokens exclusivos sendo hash.

Listagem 6.4. Codificação one-hot no nível da palavra com truque de hashing (exemplo de brinquedo)

samples = ['O gato sentou no tatame.', 'O cachorro comeu meu dever de casa.']

dimensionalidade = 1000 1

max_length = 10

resultados = np.zeros ((len (amostras), max_length, dimensionality))

para i, amostra em enumerar (amostras):

para j, palavra na lista (enumerate (sample.split ())) [: max_length]:

índice = abs (hash (palavra))% dimensionalidade


2
resultados [i, j, index] = 1.

 1 Armazena as palavras como vetores de tamanho 1.000. Se você tiver cerca


de 1.000 palavras (ou mais), verá muitas colisões de hash, o que diminuirá
a precisão desse método de codificação.
 2 Hashes a palavra em um índice inteiro aleatório entre 0 e 1.000

6.1.2. Usando o Word Embeddings


Outra maneira popular e poderosa de associar um vetor a uma palavra é o uso de vetores de
palavrasdensas , também chamadas de incorporação de palavras . Considerando que os
vetores obtidos através de uma codificação quente são binários, esparsos (na maior parte feitos
de zeros), e muito alta-dimensional (mesma dimensionalidade que o número de palavras no
vocabulário), a palavra embeddings são vetores de ponto flutuante de baixa dimensão (que é,
vetores densos, ao contrário de vetores esparsos); veja a figura 6.2. Ao contrário da palavra
vetores obtidos por meio de uma codificação simples, os embeddings de palavras são aprendidos
a partir dos dados. É comum ver a incorporação de palavras que são 256-dimensionais, 512-
dimensionais ou 1.024-dimensionais ao lidar com vocabulários muito grandes. Por outro lado,
palavras quentes de codificação geralmente levam a vetores de 20.000 ou mais dimensões
(capturando um vocabulário de 20.000 tokens, neste caso). Assim, os embeddings de palavras
contêm mais informações em menos dimensões.

Figura 6.2. Enquanto as representações de palavras obtidas a partir de uma codificação ou hash quente são esparsas, de alta
dimensão e codificadas, as incorporações de palavras são densas, relativamente pouco dimensionais e aprendidas com os
dados.

Existem duas maneiras de obter embeddings de palavras:

 Aprenda a incorporação de palavras em conjunto com a tarefa principal de que você


gosta (como classificação de documentos ou previsão de sentimentos). Nesta
configuração, você começa com vetores de palavras aleatórias e, em seguida, aprende
vetores de palavras da mesma maneira que aprende os pesos de uma rede neural.
 Carregue em seu modelo de incorporação de palavras que foram pré-computadas
usando uma tarefa de aprendizado de máquina diferente daquela que você está
tentando resolver. Isso é chamado de incorporação de palavras pré-encadeadas .
Vamos ver os dois.

Aprendendo a palavra incorporação com a camada Embedding

A maneira mais simples de associar um vetor denso a uma palavra é escolher o vetor
aleatoriamente. O problema com essa abordagem é que o espaço de incorporação resultante não
tem estrutura: por exemplo, as palavras precisas e exatas podem ter inclusões completamente
diferentes, embora sejam intercambiáveis na maioria das sentenças. É difícil para uma rede
neural profunda dar sentido a um espaço de incorporação tão ruidoso e não estruturado.

Para obter um pouco mais abstrato, as relações geométricas entre os vetores de palavras devem
refletir as relações semânticas entre essas palavras. Os embeddings do Word destinam-se a
mapear a linguagem humana para um espaço geométrico. Por exemplo, em um espaço de
incorporação razoável, você esperaria que os sinônimos fossem incorporados em vetores de
palavras semelhantes; e, em geral, você esperaria que a distância geométrica (como a distância
L2) entre quaisquer dois vetores de palavras se relacionasse com a distância semântica entre as
palavras associadas (palavras significando que coisas diferentes são embutidas em pontos
distantes umas das outras, enquanto palavras relacionadas são mais perto). Além da distância,
você pode querer direções específicas no espaço de incorporação para ser significativo. Para
tornar isso mais claro, vamos dar uma olhada em um exemplo concreto.

Na figura 6.3 , quatro palavras estão embutidas em um plano


2D: gato , cachorro , lobo e tigre . Com as representações vetoriais que escolhemos aqui,
algumas relações semânticas entre essas palavras podem ser codificadas como transformações
geométricas. Por exemplo, o mesmo vetor nos permite ir de gatoa tigre e de cão a lobo : esse
vetor pode ser interpretado como o vetor “do animal de estimação ao animal selvagem”. Da
mesma forma, outro vetor nos permite ir de cão para gato e de lobo para tigre, o que poderia ser
interpretado como um vetor "do canino ao felino".

Figura 6.3. Um exemplo de brinquedo de um espaço de incorporação de palavras

Em espaços reais de incorporação de palavras, exemplos comuns de transformações geométricas


significativas são vetores de “gênero” e vetores “plurais”. Por exemplo, adicionando um vetor
“feminino” ao vetor “rei”, obtemos o vetor “rainha”. Adicionando um vetor “plural”, obtemos
“reis”. Os espaços de incorporação de palavras geralmente apresentam milhares de tais vetores
interpretáveis e vetores potencialmente úteis.

Existe algum espaço de incorporação de palavras ideal que mapeie perfeitamente a linguagem
humana e possa ser usado para qualquer tarefa de processamento de linguagem
natural? Possivelmente, mas ainda temos que computar qualquer coisa do tipo. Além disso, não
existe linguagem humana- Existem muitas línguas diferentes e elas não são isomórficas, porque
uma linguagem é o reflexo de uma cultura específica e de um contexto específico. Mas, de forma
mais pragmática, o que faz um bom espaço para a palavra depende muito de sua tarefa: o espaço
perfeito para incorporar palavras a um modelo de análise de opinião de filme em inglês pode
parecer diferente do espaço ideal para uma linguagem jurídica em inglês. - modelo de
classificação de documentos, porque a importância de certos relacionamentos semânticos varia
de tarefa para tarefa.
Portanto, é razoável aprender um novo espaço de incorporação a cada nova tarefa. Felizmente, a
retropropagação torna isso fácil, e o Keras torna isso ainda mais fácil. É sobre aprender os pesos
de uma camada: a Embeddingcamada.

Listagem 6.5. Instanciando uma Embeddingcamada

de keras.layers import Embutimento

embedding_layer = Incorporação (1000, 64) 1

 1 A camada Incorporação leva pelo menos dois argumentos: o número de


tokens possíveis (aqui, 1.000: 1 + índice máximo de palavras) e a
dimensionalidade dos envoltórios (aqui, 64).

A Embeddingcamada é melhor entendida como um dicionário que mapeia índices inteiros (que
representam palavras específicas) para vetores densos. Ele recebe inteiros como entrada,
procura esses inteiros em um dicionário interno e retorna os vetores associados. É efetivamente
uma pesquisa de dicionário (veja a figura 6.4 ).

Figura 6.4. A embeddingcamada

A Embeddingcamada toma como entrada um tensor 2D de inteiros, de forma (samples,


sequence_length), onde cada entrada é uma seqüência de inteiros. Ele pode incorporar
sequências de comprimentos variáveis: por exemplo, você poderia inserir a Embeddingcamada
nos lotes de exemplo anteriores com formas (32, 10)(lote de 32 sequências de comprimento
10) ou (64, 15)(lote de 64 sequências de comprimento 15). Todas as seqüências em um lote
devem ter o mesmo comprimento (embora seja necessário empacotá-las em um único tensor),
portanto as seqüências mais curtas que outras devem ser preenchidas com zeros e as sequências
mais longas devem ser truncadas.

Essa camada retorna um tensor de ponto flutuante 3D de forma (samples,


sequence_length, embedding_dimensionality). Tal tensor 3D pode então ser
processado por uma camada RNN ou uma camada de convolução 1D (ambas serão introduzidas
nas seções seguintes).

Quando você instancia uma Embeddingcamada, seus pesos (seu dicionário interno de vetores
de token) são inicialmente aleatórios, assim como com qualquer outra camada. Durante o
treinamento, esses vetores de palavras são gradualmente ajustados via retropropagação,
estruturando o espaço em algo que o modelo a jusante pode explorar. Uma vez totalmente
treinado, o espaço de incorporação mostrará muita estrutura - um tipo de estrutura
especializada para o problema específico para o qual você está treinando seu modelo.

Vamos aplicar essa ideia à tarefa de previsão do sentimento de revisão de filme do IMDB com a
qual você já está familiarizado. Primeiro, você preparará rapidamente os dados. Você restringirá
as resenhas de filmes às 10 mil palavras mais comuns (como fez na primeira vez que trabalhou
com esse conjunto de dados) e cortará as resenhas depois de apenas 20 palavras. A rede
iráaprenda embedings de 8 dimensões para cada uma das 10.000 palavras, gire as sequências
inteiras de entrada (tensor inteiro 2D) em sequências embutidas (tensor de flutuação 3D),
achatar o tensor para 2D e treinar uma única Densecamada no topo para classificação.

Listagem 6.6. Carregando os dados do IMDB para uso com uma Embeddingcamada

de keras.datasets import imdb


do pré-processamento de importação do keras

max_features = 10000 1

maxlen = 20 2

(x_train, y_train), (x_test, y_test) = imdb.load_data (

num_words = max_features) 3

x_train = preprocessing.sequence.pad_sequences (x_train, maxlen = maxlen 4

x_test = preprocessing.sequence.pad_sequences (x_test, maxlen = maxlen)

 1 Número de palavras a considerar como recursos


 2 Corta o texto após este número de palavras (entre as palavras mais
comuns max_features)
 3 Carrega os dados como listas de inteiros
 4 Ativa as listas de inteiros em um tensor inteiro 2D de forma (samples,
maxlen)

Listagem 6.7. Usando uma Embeddingcamada e um classificador nos dados do IMDB

de keras.models import Sequential

de keras.layers import Flatten, denso

model = Sequential ()

model.add (Incorporação (10000, 8, input_length = maxlen))


1

model.add (Flatten ())


2

model.add (denso (1, ativação = 'sigmóide'))


3

model.compile (optimizer = 'rmsprop', loss = 'binary_crossentropy', métricas =


['acc'])

model.summary ()

history = model.fit (x_train, y_train,

épocas = 10,

batch_size = 32,
validation_split = 0,2)

 1 Especifica o comprimento máximo de entrada para a camada


Incorporação, para que você possa nivelar posteriormente as entradas
incorporadas. Após a camada Embutindo, as ativações têm forma (samples,
maxlen, 8).
 2 Aplaina o tensor 3D de encaixes em um tensor 2D de forma (amostras,
maxlen * 8)
 3 Adiciona o classificador no topo

Você chega a uma precisão de validação de ~ 76%, o que é muito bom, considerando que você
está olhando apenas as primeiras 20 palavras em cada revisão. Mas observe que apenas achatar
as sequências incorporadas e treinar uma única Densecamada no topo leva a um modelo que
trata cada palavra na sequência de entrada separadamente, sem considerar as relações entre
palavras e a estrutura da sentença (por exemplo, este modelo provavelmente trataria ambos
filme é uma bomba ”e“ este filme é a bomba ”como sendo críticas negativas). EstáÉ muito
melhor adicionar camadas recorrentes ou camadas convolucionais 1D sobre as sequências
incorporadas para aprender recursos que levam em conta cada seqüência como um todo. É nisso
que vamos nos concentrar nas próximas seções.

Uso de embeddings de palavras pré-rotuladas

Às vezes, você tem tão poucos dados de treinamento disponíveis que você não pode usar seus
dados sozinho para aprender uma incorporação apropriada de seu vocabulário específica da
tarefa. O que fazes, então?

Em vez de aprender as incorporações de palavras juntamente com o problema que você deseja
resolver, é possível carregar vetores de incorporação a partir de um espaço de incorporação pré-
computado que você sabe que é altamente estruturado e exibe propriedades úteis - que
capturam aspectos genéricos da estrutura da linguagem. O raciocínio por trás do uso de
incorporação de palavras pré-formatadas no processamento de linguagem natural é semelhante
ao uso de convnets pré-formatados na classificação de imagens: você não tem dados suficientes
disponíveis para aprender recursos realmente poderosos, mas espera os recursos de que precisa
ser razoavelmente genérico - ou seja, recursos visuais comuns ou recursos semânticos. Nesse
caso, faz sentido reutilizar os recursos aprendidos em um problema diferente.

Tais incorporações de palavras são geralmente computadas usando estatísticas de ocorrências


de palavras (observações sobre quais palavras co-ocorrem em sentenças ou documentos),
usando uma variedade de técnicas, algumas envolvendo redes neurais, outras não. A ideia de um
espaço denso e de baixa dimensão para as palavras, computado de maneira não supervisionada,
foi inicialmente explorada por Bengio et al. no início dos anos 2000, [ 1 ] mas só começou a
decolar em aplicações de pesquisa e indústria após o lançamento de um dos mais famosos e
bem-sucedidos esquemas de incorporação de palavras: o algoritmo Word2vec
( https://code.google.com/ archive / p / word2vec ), desenvolvido por Tomas Mikolov no
Google em 2013. As dimensões do Word2vec capturam propriedades semânticas específicas,
como gênero.

Yoshua Bengio et al., Modelos de Linguagem Probabilística Neural (Springer, 2003).

Existem vários bancos de dados pré-computados de incorporação de palavras que você pode
baixar e usar em uma Embeddingcamada Keras . Word2vec é um deles. Outro popular é
chamado de Vetores Globais para Representação de Palavras
(GloVe, https://nlp.stanford.edu/projects/glove ), que foi desenvolvido por pesquisadores de
Stanford em 2014. Esta técnica de incorporação é baseada na fatoração de uma matriz de co-
estatísticas de ocorrências. Seus desenvolvedores disponibilizaram integrações pré-computadas
para milhões de tokens ingleses, obtidos a partir de dados da Wikipedia e dados de
rastreamento comuns.

Vejamos como você pode começar a usar os envios do GloVe em um modelo Keras. O mesmo
método é válido para incorporações do Word2vec ou qualquer outro banco de dados de
incorporação de palavras. Você também usará este exemplo para atualizar as técnicas de
tokenização de texto introduzidas há alguns parágrafos: você começará a partir do texto não
processado e trabalhará para cima.

6.1.3. Juntando tudo: do texto bruto à incorporação de palavras


Você usará um modelo semelhante ao que acabamos de ler: embutir sentenças em sequências de
vetores, achatá-las e treinar uma Densecamada no topo. Mas você vai fazerEntão, usando
embeddings de palavras pré-rotuladas; e em vez de usar os dados do IMDB pretokenized
embalados em Keras, você vai começar do zero, baixando os dados do texto original.

Download dos dados do IMDB como texto não processado

Primeiro, vá para http://mng.bz/0tIo e faça o download do conjunto de dados bruto do


IMDB. Descompacte-o.

Agora, vamos coletar as avaliações individuais de treinamento em uma lista de strings, uma
string por revisão. Você também coletará os rótulos de revisão (positivo / negativo) em
uma labelslista.

Listagem 6.8. Processando os rótulos dos dados brutos do IMDB

importar os

imdb_dir = '/ Usuários / fchollet / Downloads / aclImdb'

train_dir = os.path.join (imdb_dir, 'train')

labels = []

textos = []

para label_type em ['neg', 'pos']:

dir_name = os.path.join (train_dir, label_type)

para fname em os.listdir (dir_name):

se fname [-4:] == '.txt':

f = open (os.path.join (dir_name, fname))

texts.append (f.read ())

f.close ()

if label_type == 'neg':

labels.append (0)
outro:

labels.append (1)

Como otimizar os dados

Vamos vetorizar o texto e preparar uma divisão de treinamento e validação, usando os conceitos
introduzidos anteriormente nesta seção. Como a incorporação de palavras pré-concebidas é
particularmente útil em problemas em que há poucos dados de treinamento disponíveis (caso
contrário, é provável que as integrações específicas da tarefa os superem), adicionaremos a
seguinte alteração: restringir os dados de treinamento às primeiras 200 amostras. Então, você
aprenderá a classificar as resenhas de filmes depois de ver apenas 200 exemplos.

Listagem 6.9. Como otimizar o texto dos dados brutos do IMDB

do Tokenizer de importação keras.preprocessing.text

de keras.preprocessing.sequence import pad_sequences

import numpy como np

maxlen = 100 1

training_samples = 200 2

validation_samples = 10000 3

max_words = 10000 4

tokenizer = Tokenizer (num_words = max_words)

tokenizer.fit_on_texts (textos)

seqüências = tokenizer.texts_to_sequences (textos)

word_index = tokenizer.word_index

print ('Encontrado% s tokens exclusivos'% len (word_index))

data = pad_sequences (sequências, maxlen = maxlen)

labels = np.asarray (rótulos)

print ('Forma do tensor de dados:', data.shape)

print ('Forma do tensor da etiqueta:', labels.shape)

indices = np.arange (data.shape [0]) 5

np.random.shuffle (índices)
dados = dados [índices]

labels = labels [indices]

x_train = data [: training_samples]

y_train = labels [: training_samples]

x_val = data [training_samples: training_samples + validation_samples]

y_val = labels [training_samples: training_samples + validation_samples]

 1 Corta comentários após 100 palavras


 2 Trens em 200 amostras
 3 valida em 10.000 amostras
 4 Considera apenas as primeiras 10.000 palavras no conjunto de dados
 5 Divide os dados em um conjunto de treinamento e um conjunto de
validação, mas primeiro embaralha os dados, porque você está começando
com dados em que as amostras são ordenadas (todas negativas primeiro,
depois todas positivas)

Download da palavra GloVe Embeddings

Vá para https://nlp.stanford.edu/projects/glove e faça o download dos pré-ingressos da


Wikipédia em inglês em 2014. É um arquivo zip de 822 MB chamado glove.6B.zip, contendo
vetores de incorporação 100-dimensional para 400.000 palavras (ou tokens não-palavra-
chave). Descompacte.

Pré-processamento dos envoldings

Vamos analisar o arquivo descompactado (um arquivo .txt) para construir um índice que
mapeia palavras (como strings) para sua representação vetorial (como vetores numéricos).

Listagem 6.10. Analisando o arquivo de incorporação de palavras do GloVe

glove_dir = '/Users/fchollet/Downloads/glove.6B'

embeddings_index = {}

f = open (os.path.join (glove_dir, 'glove.6B.100d.txt'))

para linha em f:

valores = line.split ()

palavra = valores [0]

coefs = np.asarray (valores [1:], dtype = 'float32')

embeddings_index [palavra] = coefs

f.close ()

print ('Encontrado% s vetores de palavras'.% len (embeddings_index))


Em seguida, você construirá uma matriz de incorporação que poderá ser carregada em
uma Embeddingcamada. Deve ser uma matriz de forma (max_words, embedding_dim),
em que cada entrada i contém o embedding_dimvector -dimensional para a palavra de
índice i no índice palavra de referência (construído durante tokenization). Observe que o índice
0 não deve representar qualquer palavra ou token - é um marcador de posição.

Listagem 6.11. Preparando a matriz de incorporação de palavras do GloVe

embedding_dim = 100

embedding_matrix = np.zeros ((max_words, embedding_dim))

para word, i em word_index.items ():

se eu <max_words:

embedding_vector = embeddings_index.get (palavra)

if embedding_vector não é nenhum:

embedding_matrix [i] = embedding_vector 1

 1 Palavras não encontradas no índice de incorporação serão todas zeros.

Definindo um modelo

Você usará a mesma arquitetura de modelo de antes.

Listagem 6.12. Definição de modelo

de keras.models import Sequential

de keras.layers import Embutimento, achatar, denso

model = Sequential ()

model.add (Incorporação (max_words, embedding_dim, input_length = maxlen))

model.add (Flatten ())

model.add (denso (32, ativação = 'relu'))

model.add (denso (1, ativação = 'sigmóide'))

model.summary ()

Carregando os envoltórios do GloVe no modelo

A Embeddingcamada tem uma matriz de peso única: uma matriz flutuante 2D em que cada
entrada i é o vetor de palavras que se pretende associar ao índice i . Simples o
suficiente. Carregue a matriz GloVe que você preparou na Embeddingcamada, a primeira
camada no modelo.

Listagem 6.13. Como carregar embeddings de palavras pré-formatadas na Embeddingcamada

model.layers [0] .set_weights ([embedding_matrix])


model.layers [0] .trainable = False

Além disso, você congelará a Embeddingcamada (defina seu trainableatributo como False),
seguindo a mesma lógica com a qual já está familiarizado no contexto de recursos convnet pré-
tratados: quando partes de um modelo são pré-tratadas (como sua Embeddingcamada) e partes
são inicializadas aleatoriamente (como seu classificador), as partes pré-tratadas não devem ser
atualizadas durante o treinamento, para evitar esquecer o que elas já sabem. O grandeas
atualizações de gradiente acionadas pelas camadas inicializadas aleatoriamente seriam
prejudiciais aos recursos já aprendidos.

Treinando e avaliando o modelo

Compile e treine o modelo.

Listagem 6.14. Treinamento e avaliação

model.compile (optimizer = 'rmsprop',

perda = 'binary_crossentropy',

metrics = ['acc'])

history = model.fit (x_train, y_train,

épocas = 10,

batch_size = 32,

validation_data = (x_val, y_val))

model.save_weights ('pre_trained_glove_model.h5')

Agora, plote o desempenho do modelo ao longo do tempo (veja figuras 6.5 e 6.6 ).
Figura 6.5. Perda de treinamento e validação ao usar embeddings de palavras pré-rotuladas
Figura 6.6. Precisão de treinamento e validação ao usar embeddings de palavras pré-rotuladas

Listagem 6.15. Plotando os resultados

import matplotlib.pyplot como plt

acc = history.history ['acc']

val_acc = history.history ['val_acc']

perda = history.history ['perda']

val_loss = history.history ['val_loss']

épocas = intervalo (1, len (acc) + 1)

plt.plot (epochs, acc, 'bo', label = 'Treinar acc')

plt.plot (epochs, val_acc, 'b', label = 'Validação acc')

plt.title ('Precisão de treinamento e validação')

plt.legend ()

plt.figure ()

plt.plot (epochs, loss, 'bo', label = 'perda de treino')

plt.plot (epochs, val_loss, 'b', label = 'Perda de validação')

plt.title ('Perda de treinamento e validação')

plt.legend ()
plt.show ()

O modelo rapidamente começa a overfitting, o que não é surpreendente, dado o pequeno


número de amostras de treinamento. A precisão da validação tem alta variância pelo mesmo
motivo, mas parece atingir os 50 maiores.

Observe que sua milhagem pode variar: como você tem poucas amostras de treinamento, o
desempenho depende muito de exatamente as 200 amostras escolhidas - e você as escolhe
aleatoriamente. Se isto funcionar mal para você, tente escolher um conjunto aleatório diferente
de 200 amostras, para o bem do exercício (na vida real, você não consegue escolher seus dados
de treinamento).

Você também pode treinar o mesmo modelo sem carregar a palavra pré-encadeada e sem
congelar a camada de incorporação. Nesse caso, você aprenderá uma incorporação específica de
tarefas dos tokens de entrada, que geralmente é mais poderosa do que os envoltórios de palavras
pré-roteados quando muitos dados estão disponíveis. Mas neste caso, você tem apenas 200
amostras de treinamento. Vamos tentar (veja as figuras 6.7 e 6.8 ).

Figura 6.7. Perda de treinamento e validação sem usar embeddings de palavras pré-rotuladas
Figura 6.8. Precisão de treinamento e validação sem usar embeddings de palavras pré-rotuladas

Listagem 6.16. Treinando o mesmo modelo sem a incorporação de palavras pré-encadeadas

de keras.models import Sequential

de keras.layers import Embutimento, achatar, denso

model = Sequential ()

model.add (Incorporação (max_words, embedding_dim, input_length = maxlen))

model.add (Flatten ())

model.add (denso (32, ativação = 'relu'))

model.add (denso (1, ativação = 'sigmóide'))

model.summary ()

model.compile (optimizer = 'rmsprop',

perda = 'binary_crossentropy',

metrics = ['acc'])

history = model.fit (x_train, y_train,

épocas = 10,

batch_size = 32,

validation_data = (x_val, y_val))

A precisão de validação fica parada nos 50s baixos. Portanto, neste caso, a incorporação de
palavras pré-formatadas supera os embeddings aprendidos em conjunto. Se você aumentar o
número de amostras de treinamento, isso rapidamente deixará de ser o caso - experimente-o
como um exercício.
Finalmente, vamos avaliar o modelo nos dados de teste. Primeiro, você precisa tokenizar os
dados de teste.

Listagem 6.17. Como organizar os dados do conjunto de testes

test_dir = os.path.join (imdb_dir, 'test')

labels = []

textos = []

para label_type em ['neg', 'pos']:

dir_name = os.path.join (test_dir, label_type)

para fname in sorted (os.listdir (dir_name)):

se fname [-4:] == '.txt':

f = open (os.path.join (dir_name, fname))

texts.append (f.read ())

f.close ()

if label_type == 'neg':

labels.append (0)

outro:

labels.append (1)

seqüências = tokenizer.texts_to_sequences (textos)

x_test = pad_sequences (sequências, maxlen = maxlen)

y_test = np.asarray (rótulos)

Em seguida, carregue e avalie o primeiro modelo.

Listagem 6.18. Avaliando o modelo no conjunto de teste

model.load_weights ('pre_trained_glove_model.h5')

model.evaluate (x_test, y_test)

Você obtém uma precisão de teste chocante de 56%. Trabalhar com apenas algumas amostras de
treinamento é difícil!

6.1.4. Empacotando
Agora você pode fazer o seguinte:
 Transforme o texto bruto em algo que uma rede neural pode processar
 Use a Embeddingcamada em um modelo Keras para aprender sobre incorporações de
token específicas da tarefa
 Use encadeamentos pré-formatados para obter um impulso extra em pequenos
problemas de processamento de linguagem natural

6.2. COMPREENDER REDES NEURAIS RECORRENTES

Uma característica importante de todas as redes neurais que você viu até agora, como redes e
convnets densamente conectadas, é que elas não têm memória. Cada entrada mostrada a eles é
processada independentemente, sem estado mantido entre entradas. Com essas redes, para
processar uma seqüência ou uma série temporal de pontos de dados, você precisa mostrar a
sequência inteira para a rede de uma vez: transformá-la em um único ponto de dados. Por
exemplo, isso é o que você fez no exemplo do IMDB: uma revisão completa do filme foi
transformada em um único vetor grande e processada de uma só vez. Essas redes são chamadas
de redes feedforward .

Em contraste, quando você está lendo a sentença atual, você está processando palavra por
palavra - ou melhor, reveste os olhos com sacadas oculares - enquanto mantém memórias do
que veio antes; isso lhe dá uma representação fluida do significado transmitido por essa
sentença. A inteligência biológica processa as informações de forma incremental, mantendo um
modelo interno do que está processando, construído a partir de informações passadas e
constantemente atualizado à medida que novas informações chegam.

Uma rede neural recorrente (RNN) adota o mesmo princípio, ainda que em uma versão
extremamente simplificada: processa seqüências, iterando os elementos de sequência e
mantendo um estadocontendo informações relativas ao que foi visto até o momento. Na
verdade, um RNN é um tipo de rede neural que possui um loop interno (consulte a figura
6.9 ). O estado da RNN é redefinido entre o processamento de duas seqüências independentes
diferentes (como duas revisões diferentes do IMDB), portanto, você considera uma sequência
como um único ponto de dados: uma única entrada na rede. O que muda é que esse ponto de
dados não é mais processado em uma única etapa; em vez disso, a rede faz um loop interno
sobre os elementos da sequência.

Figura 6.9. Uma rede recorrente: uma rede com um loop

Para tornar claras essas noções de loop e state , vamos implementar o forward forward de um
brinquedo RNN em Numpy. Este RNN toma como entrada uma seqüência de vetores, que você
codificará como um tensor 2D de tamanho (timesteps, input_features). Faz um loop
sobre timesteps, e em cada timestep, considera seu estado atual em te a entrada em t(de
shape (input_features,), e combina-os para obter a saída em t. Você então definirá o
estado para a próxima etapa para ser a saída anterior. Para o primeiro timestep, a saída anterior
não está definida, portanto, não há estado atual, portanto, você inicializará o estado como um
vetor all-zero chamado de estado inicial da rede.

No pseudocódigo, este é o RNN.


Listagem 6.19. Pseudocódigo RNN

state_t = 0 1

para input_t em input_sequence: 2

output_t = f (entrada_t, estado_t)

state_t = output_t 3

 1 O estado em t
 2 Itera sobre os elementos da sequência
 3 A saída anterior se torna o estado da próxima iteração.

Pode mesmo carne para fora a função f: a transformação da entrada e uma saída em estado vai
ser parametrizado por duas matrizes, We U, e um vector de polarização. É semelhante à
transformação operada por uma camada densamente conectada em uma rede feedforward.

Listagem 6.20. Pseudocódigo mais detalhado para o RNN

state_t = 0

para input_t em input_sequence:

output_t = ativação (ponto (W, entrada_t) + ponto (U, estado_t) + b)

state_t = output_t

Para tornar essas noções absolutamente inequívocas, vamos escrever uma implementação
ingênua do Numpy do passe para frente do RNN simples.

Listagem 6.21. Implementação Numpy de um simples RNN

import numpy como np

timesteps = 100 1

input_features = 32 2

output_features = 64 3

inputs = np.random.random ((timesteps, input_features)) 4

state_t = np.zeros ((output_features,)) 5

W = np.random.random ((output_features, input_features)) 6

U = np.random.random ((output_features, output_features)) 6

b = np.random.random ((output_features,)) 6
successive_outputs = []

para input_t em entradas: 7

output_t = np.tanh (np.dot (W, entrada_t) + np.dot (U, estado_t) + b)


8

successive_outputs.append (output_t) 9

state_t = output_t 10

final_output_sequence = np.concatenate (successive_outputs, axis = 0) 11

 1 Número de timesteps na sequência de entrada


 2 Dimensionalidade do espaço do recurso de entrada
 3 Dimensionalidade do espaço do recurso de saída
 4 dados de entrada: ruído aleatório por causa do exemplo
 5 Estado inicial: um vetor a zero
 6 Cria matrizes de peso aleatório
 7 input_t é um vetor de forma (input_features,).
 8 Combina a entrada com o estado atual (a saída anterior) para obter a
saída atual
 9 Armazena essa saída em uma lista
 10 Atualiza o estado da rede para o próximo timestep
 11 A saída final é um tensor de forma 2D (timesteps, output_features).

Fácil: em resumo, um RNN é um forloop que reutiliza quantidades calculadas durante a


iteração anterior do loop, nada mais. É claro que existem muitos RNNs diferentes que se
encaixam nessa definição que você poderia criar - este exemplo é uma das formulações mais
simples de RNN. Os RNNs são caracterizados por sua função step, como a seguinte função neste
caso (veja a figura 6.10 ):

output_t = np.tanh (np.dot (W, entrada_t) + np.dot (U, estado_t) + b)

Figura 6.10. Um simples RNN, desenrolado ao longo do tempo


Nota

Neste exemplo, a saída final é um tensor 2D de forma (timesteps, output_features),


onde cada timestep é a saída do loop no momento t. Cada passo de tempo tno tensor de saída
contém informações sobre Timesteps 0a tna entrada sequência-o sobre toda passado. Por esse
motivo, em muitos casos, você não precisa dessa sequência completa de saídas; você só precisa
da última saída ( output_tno final do loop), porque ela já contém informações sobre toda a
sequência.

6.2.1. Uma camada recorrente em Keras


O processo que você acabou de implementar ingenuamente no Numpy corresponde a uma
camada Keras real - a SimpleRNNcamada:

de keras.layers importam SimpleRNN

Há uma pequena diferença: SimpleRNNprocessa lotes de seqüências, como todas as outras


camadas de Keras, não uma única sequência como no exemplo de Numpy. Isso significa que ele
recebe entradas de forma (batch_size, timesteps, input_features), em vez
de (timesteps, input_features).

Como todas as camadas recorrentes em Keras, SimpleRNNpode ser executado em dois modos
diferentes: pode retornar as sequências completas de saídas sucessivas para cada timestep (um
tensor 3D de forma (batch_size, timesteps, output_features)) ou somente a última
saída para cada seqüência de entrada (um tensor 2D de forma (batch_size,
output_features)). Esses dois modos são controlados
pelo return_sequencesargumento do construtor. Vamos ver um exemplo que
usa SimpleRNNe retorna apenas a saída no último momento:

>>> from keras.models import Sequential

>>> de keras.layers import Embedding, SimpleRNN

>>> model = Sequencial ()

>>> model.add (Incorporação (10000, 32))

>>> model.add (SimpleRNN (32))

>>> model.summary ()

________________________________________________________________

Camada (tipo) Forma de saída Param #

================================================== ==============

embedding_22 (Incorporação) (None, None, 32) 320000

________________________________________________________________

simplernn_10 (SimpleRNN) (nenhum, 32) 2080

================================================== ==============

Total de Params: 322.080


Params treináveis: 322,080

Params não treináveis: 0

O exemplo a seguir retorna a sequência de estado completa:

>>> model = Sequencial ()

>>> model.add (Incorporação (10000, 32))

>>> model.add (SimpleRNN (32, return_sequences = True))

>>> model.summary ()

________________________________________________________________

Camada (tipo) Forma de saída Param #

================================================== ==============

embedding_23 (Incorporação) (None, None, 32) 320000

________________________________________________________________

simplernn_11 (SimpleRNN) (nenhum, nenhum, 32) 2080

================================================== ==============

Total de Params: 322.080

Params treináveis: 322,080

Params não treináveis: 0

Às vezes é útil empilhar várias camadas recorrentes, uma após a outra, para aumentar o poder
de representação de uma rede. Em tal configuração, você precisa obter todas as camadas
intermediárias para retornar a sequência completa de saídas:

>>> model = Sequencial ()

>>> model.add (Incorporação (10000, 32))

>>> model.add (SimpleRNN (32, return_sequences = True))

>>> model.add (SimpleRNN (32, return_sequences = True))

>>> model.add (SimpleRNN (32, return_sequences = True))

>>> model.add (SimpleRNN (32)) 1

>>> model.summary ()

________________________________________________________________

Camada (tipo) Forma de saída Param #

================================================== ==============

embedding_24 (Incorporação) (Nenhum, Nenhum, 32) 320000

________________________________________________________________
simplernn_12 (SimpleRNN) (nenhum, nenhum, 32) 2080

________________________________________________________________

simplernn_13 (SimpleRNN) (nenhum, nenhum, 32) 2080

________________________________________________________________

simplernn_14 (SimpleRNN) (nenhum, nenhum, 32) 2080

________________________________________________________________

simplernn_15 (SimpleRNN) (nenhum, 32) 2080

================================================== ==============

Params totais: 328.320

Params treináveis: 328.320

Params não treináveis: 0

 1 Última camada só retorna a última saída

Agora, vamos usar esse modelo no problema de classificação e revisão de filmes do


IMDB. Primeiro, pré-processe os dados.

Listagem 6.22. Preparando os dados do IMDB

de keras.datasets import imdb

da sequência de importação keras.preprocessing

max_features = 10000 1

maxlen = 500 2

batch_size = 32

print ('Carregando dados ...')

(input_train, y_train), (input_test, y_test) = imdb.load_data (

num_words = max_features)

print (len (input_train), 'sequências de trem')

print (len (input_test), 'sequências de teste')

print ('seqüências Pad (amostras x tempo)')

input_train = sequence.pad_sequences (input_train, maxlen = maxlen)

input_test = sequence.pad_sequences (input_test, maxlen = maxlen)

print ('input_train shape:', input_train.shape)


print ('input_test shape:', input_test.shape)

 1 Número de palavras a considerar como recursos


 2 Corta textos depois de tantas palavras (entre as palavras mais comuns
max_features)

Vamos treinar uma rede recorrente simples usando uma Embeddingcamada e


uma SimpleRNNcamada.

Listagem 6.23. Treinando o modelo com Embeddinge SimpleRNNcamadas

de keras.layers importar denso

model = Sequential ()

model.add (Incorporação (max_features, 32))

model.add (SimpleRNN (32))

model.add (denso (1, ativação = 'sigmóide'))

model.compile (optimizer = 'rmsprop', loss = 'binary_crossentropy', métricas =


['acc'])

history = model.fit (input_train, y_train,

épocas = 10,

batch_size = 128,

validation_split = 0,2)

Agora, vamos mostrar a perda e precisão de treinamento e validação (veja as figuras


6.11 e 6.12 ).
Figura 6.11. Perda de treinamento e validação no IMDB com simplernn
Figura 6.12. Treinamento e validação de validação no IMDB com simplernn

Listagem 6.24. Plotando resultados

import matplotlib.pyplot como plt

acc = history.history ['acc']

val_acc = history.history ['val_acc']

perda = history.history ['perda']

val_loss = history.history ['val_loss']

épocas = intervalo (1, len (acc) + 1)

plt.plot (epochs, acc, 'bo', label = 'Treinar acc')

plt.plot (epochs, val_acc, 'b', label = 'Validação acc')

plt.title ('Precisão de treinamento e validação')

plt.legend ()

plt.figure ()

plt.plot (epochs, loss, 'bo', label = 'perda de treino')

plt.plot (epochs, val_loss, 'b', label = 'Perda de validação')

plt.title ('Perda de treinamento e validação')


plt.legend ()

plt.show ()

Como lembrete, no capítulo 3 , a primeira abordagem ingênua a este conjunto de dados levou
você a uma precisão de teste de 88%. Infelizmente, essa pequena rede recorrente não apresenta
um bom desempenho em comparação com essa linha de base (apenas 85% de precisão de
validação). Parte do problema é que suas entradas consideram apenas as primeiras 500
palavras, em vez de sequências completas - portanto, o RNN tem acesso a menos informações
do que o modelo de linha de base anterior. O restante do problema é que SimpleRNNnão é bom
no processamento de sequências longas, como texto. Outros tipos de camadas recorrentes têm
um desempenho muito melhor. Vamos ver algumas camadas mais avançadas.

6.2.2. Entendendo as camadas LSTM e GRU


SimpleRNNnão é a única camada recorrente disponível em Keras. Existem dois
outros: LSTMe GRU. Na prática, você sempre usará um desses, porque SimpleRNNgeralmente é
muito simplista para ser de uso real. SimpleRNNtem um grande problema: embora
teoricamente deva ser capaz de reter no tempo tinformações sobre insumos vistos antes de
muitos prazos, na prática, tais dependências de longo prazo são impossíveis de aprender. Isto é
devido ao problema do gradiente de fuga, um efeito que é semelhante ao que é observado em
redes não recorrentes (feedforward networks) com várias camadas de profundidade: à medida
que você continua adicionando camadas a uma rede, a rede acaba ficando inatingível. As razões
teóricas para esse efeito foram estudadas por Hochreiter, Schmidhuber e Bengio no início dos
anos 90. [ 2 ] As camadas LSTMe GRUsão projetadas para resolver esse problema.

Veja, por exemplo, Yoshua Bengio, Patrice Simard e Paolo Frasconi, “Aprender Dependências de Longo Prazo com Descida de Gradiente é

Difícil”, IEEE Transactions on Neural Networks 5, no. 2 (1994).

Vamos considerar a LSTMcamada. O algoritmo LSTM (Long Short-Term Memory) subjacente


foi desenvolvido por Hochreiter e Schmidhuber em 1997; [ 3 ] foi o ponto culminante de suas
pesquisas sobre o problema do gradiente de desaparecimento.

Sepp Hochreiter e Jürgen Schmidhuber, “Long Short-Term Memory”, Neural Computation 9, no. 8 (1997).

Essa camada é uma variante da SimpleRNNcamada que você já conhece; Ele adiciona uma
maneira de transportar informações através de muitos timesteps. Imagine uma correia
transportadora correndo paralela à seqüência que você está processando. As informações da
sequência podem saltar para a correia transportadora em qualquer ponto, ser transportadas
para um intervalo de tempo mais recente e pular, intactas, quando você precisar. Isso é
essencialmente o que o LSTM faz: ele salva informações para mais tarde, evitando que os sinais
antigos desapareçam gradualmente durante o processamento.

Para entender isso em detalhes, vamos começar a partir da SimpleRNNcélula (veja a figura
6.13 ). Como você terá muitas matrizes de ponderação, indexe as matrizes We Una célula com a
letra o( Woe Uo) para saída .
Figura 6.13. O ponto de partida de uma lstmcamada: umsimplernn

Vamos adicionar a esta imagem um fluxo de dados adicional que transporta informações através
de timesteps. Chame seus valores em diferentes timesteps Ct, onde C significa carry . Esta
informação terá o seguinte impacto na célula: será combinada com a conexão de entrada e a
conexão recorrente (através de uma transformação densa: um produto de ponto com uma
matriz de peso seguido por uma adição de polarização e a aplicação de uma função de ativação)
e isso afetará o estado sendo enviado para o próximo timestep (via uma função de ativação e
uma operação de multiplicação). Conceitualmente, o fluxo de dados carry é uma maneira de
modular a próxima saída e o próximo estado (veja a figura 6.14 ). Simples até agora.

Figura 6.14. Indo de um simplernnpara um lstm: adicionando uma faixa de transporte

Agora, a sutileza: a maneira como o próximo valor do fluxo de dados carry é calculado. Envolve
três transformações distintas. Todos os três têm a forma de uma SimpleRNNcélula:

y = ativação (ponto (estado_t, U) + ponto (entrada_t, W) + b)

Mas todas as três transformações têm suas próprias matrizes de peso, que você índice com as
letras i, f, e k. Aqui está o que você tem até agora (pode parecer um pouco arbitrário, mas tenha
paciência comigo).

Listagem 6.25. Detalhes do pseudocódigo da arquitetura LSTM (1/2)

output_t = ativação (ponto (state_t, Uo) + ponto (input_t, Wo) + ponto (C_t,
Vo) + bo)
i_t = ativação (ponto (estado_t, Ui) + ponto (entrada_t, Wi) + bi)

f_t = ativação (ponto (estado_t, Uf) + ponto (entrada_t, Wf) + bf)

k_t = ativação (ponto (state_t, Uk) + ponto (input_t, Wk) + bk)

Pode obter o novo estado carry (o próximo c_t), combinando i_t, f_te k_t.

Listagem 6.26. Detalhes do pseudocódigo da arquitetura LSTM (2/2)

c_t + 1 = i_t * k_t + c_t * f_t

Adicione isto como mostrado na figura 6.15 . E é isso. Não é tão complicado - apenas um pouco
complexo.

Figura 6.15. Anatomia de um lstm

Se você quiser ser filosófico, pode interpretar o que cada uma dessas operações deve fazer. Por
exemplo, você pode dizer que multiplicar c_te f_té uma maneira de esquecer deliberadamente
informações irrelevantes no fluxo de dados de transporte. Enquanto isso, i_te k_tfornecer
informações sobre o presente, atualizando a faixa de transporte com novas informações. Mas no
final do dia, essas interpretações não significam muito, porque o que essas
operações realmentefazer é determinado pelo conteúdo dos pesos parametrizando-os; e os
pesos são aprendidos de uma maneira completa, recomeçando a cada rodada de treinamento,
tornando impossível creditar essa ou aquela operação com uma finalidade específica. A
especificação de uma célula RNN (como acabamos de descrever) determina seu espaço de
hipótese - o espaço no qual você procurará uma boa configuração de modelo durante o
treinamento - mas não determina o que a célula faz; isso é até os pesos das células. A mesma
célula com diferentes pesos pode estar fazendo coisas muito diferentes. Assim, a combinação de
operações que compõem uma célula RNN é melhor interpretada como um conjunto
de restrições em sua pesquisa, não como um design no sentido de engenharia.

Para um pesquisador, parece que a escolha de tais restrições - a questão de como implementar
células RNN - é melhor deixar para algoritmos de otimização (como algoritmos genéticos ou
processos de aprendizado por reforço) do que para engenheiros humanos. E no futuro, é assim
que vamos construir redes. Em resumo: você não precisa entender nada sobre a arquitetura
específica de uma LSTMcélula; como um ser humano, não deveria ser seu trabalho entendê-
lo. Basta ter em mente o que a LSTMcélula deve fazer: permitir que informações passadas sejam
reinjetadas mais tarde, combatendo assim o problema do gradiente de desaparecimento.

6.2.3. Um exemplo concreto de LSTM em Keras


Agora vamos mudar para questões mais práticas: você irá configurar um modelo usando
uma LSTMcamada e treiná-lo nos dados do IMDB (veja as figuras 6.16 e 6.17 ). A rede é
semelhante à SimpleRNNque acabou de ser apresentada. Você especifica apenas a
dimensionalidade de saída da LSTMcamada; deixe todos os outros argumentos (existem muitos)
nos padrões de Keras. Keras tem bons padrões, e as coisas quase sempre “simplesmente
funcionam” sem que você precise gastar tempo sintonizando parâmetros manualmente.

Figura 6.16. Perda de treinamento e validação no IMDB com LSTM

Figura 6.17. Treinamento e validação de validação no IMDB com LSTM

Listagem 6.27. Usando a LSTMcamada em Keras

de keras.layers importar LSTM

model = Sequential ()

model.add (Incorporação (max_features, 32))

model.add (LSTM (32))


model.add (denso (1, ativação = 'sigmóide'))

model.compile (optimizer = 'rmsprop',

perda = 'binary_crossentropy',

metrics = ['acc'])

history = model.fit (input_train, y_train,

épocas = 10,

batch_size = 128,

validation_split = 0,2)

Desta vez, você atinge até 89% de precisão de validação. Nada mal: certamente muito melhor
que a SimpleRNNrede - em grande parte porque o LSTM sofre muito menos com o problema do
gradiente de desaparecimento - e um pouco melhor do que a abordagem totalmente conectada
do capítulo 3 , embora você esteja olhando menos dados do que estava no capítulo 3 . Você
está truncando sequências depois de 500 timesteps, enquanto no capítulo 3 , você estava
considerando sequências completas.

Mas esse resultado não é inovador para essa abordagem computacionalmente intensiva. Por que
o desempenho do LSTM não é melhor? Uma razão é que você não fez nenhum esforço para
ajustar hiperparâmetros, como a dimensionalidade de incorporação ou a dimensionalidade de
saída do LSTM. Outro pode ser falta de regularização. Mas honestamente, a principal razão é
que analisar a estrutura global e de longo prazo das revisões (o que a LSTM é boa) não é útil
para um problema de análise de sentimentos. Esse problema básico é bem resolvido
observando-se quais palavras ocorrem em cada revisão e com que frequência. Isso é o que a
primeira abordagem totalmente conectada olhou. Mas existem problemas de processamento de
linguagem natural muito mais difíceis por aí, onde a força do LSTM se tornará aparente: em
particular,

6.2.4. Empacotando
Agora você entende o seguinte:

 O que são os RNNs e como funcionam


 O que é o LSTM e por que ele funciona melhor em seqüências longas do que um RNN
ingênuo
 Como usar camadas Keras RNN para processar dados de seqüência

A seguir, analisaremos vários recursos mais avançados de RNNs, que podem ajudar você a
aproveitar ao máximo seus modelos de sequência de aprendizado profundo.

6.3. USO AVANÇADO DE REDES NEURAIS RECORRENTES

Nesta seção, revisaremos três técnicas avançadas para melhorar o desempenho e o poder de
generalização de redes neurais recorrentes. No final da seção, você saberá mais sobre o que há
para saber sobre o uso de redes recorrentes com o Keras. Vamos demonstrar todos os três
conceitos sobre um problema de previsão de temperatura, onde você tem acesso a uma série
temporal de pontos de dados provenientes de sensores instalados no telhado de um edifício,
como temperatura, pressão do ar e umidade, que você usa para prever qual a temperatura será
24 horas após o último ponto de dados. Este é um problema bastante desafiador que exemplifica
muitas dificuldades comuns encontradas quando se trabalha com timeseries.
Nós vamos cobrir as seguintes técnicas:

 Desistência recorrente - Esta é uma maneira específica e interna de usar o


dropout para combater o overfitting em camadas recorrentes.
 Empilhamento de camadas recorrentes - Isso aumenta o poder de representação
da rede (ao custo de maiores cargas computacionais).
 Camadas Recorrentes Bidirecionais - Apresentam as mesmas informações para
uma rede recorrente de diferentes maneiras, aumentando a precisão e mitigando os
problemas de esquecimento.

6.3.1. Um problema de previsão de temperatura


Até agora, os únicos dados de sequência que cobrimos foram dados de texto, como o conjunto de
dados do IMDB e o conjunto de dados da Reuters. Mas dados de sequência são encontrados em
muitos mais problemas do que apenas processamento de linguagem. Em todos os exemplos
desta seção, você jogará com um conjunto de dados de timeseries meteorológicos registrado na
Estação Meteorológica do Instituto Max Planck de Biogeoquímica em Jena, na Alemanha. [ 4 ]

Olaf Kolle, www.bgc-jena.mpg.de/wetter .

Neste conjunto de dados, 14 quantidades diferentes (tais temperatura do ar, pressão


atmosférica, umidade, direção do vento, e assim por diante) foram registradas a cada 10
minutos, ao longo de vários anos. Os dados originais remontam a 2003, mas este exemplo está
limitado aos dados de 2009–2016. Este conjunto de dados é perfeito para aprender a trabalhar
com séries temporais numéricas. Você o usará para criar um modelo que tome como entrada
alguns dados do passado recente (alguns dias de pontos de dados) e preveja a temperatura do ar
em 24 horas no futuro.

Faça o download e descompacte os dados da seguinte forma:

cd ~ / Downloads

mkdir jena_climate

cd jena_climate

wget https://s3.amazonaws.com/keras-datasets/jena_climate_2009_2016.csv.zip

descompacte jena_climate_2009_2016.csv.zip

Vamos dar uma olhada nos dados.

Listagem 6.28. Inspecionando os dados do conjunto de dados meteorológicos de Jena

importar os

data_dir = '/ users / fchollet / Downloads / jena_climate'

fname = os.path.join (data_dir, 'jena_climate_2009_2016.csv')

f = aberto (fname)
data = f.read ()

f.close ()

linhas = data.split ('\ n')

cabeçalho = linhas [0] .split (',')

linhas = linhas [1:]

imprimir (cabeçalho)

print (len (linhas))

Isso gera uma contagem de 420.551 linhas de dados (cada linha é um timestep: um registro de
uma data e 14 valores relacionados ao clima), bem como o seguinte cabeçalho:

["Data hora",

"p (mbar)",

"T (degC)",

"Tpot (K)",

"Tdew (degC)",

"rh (%)",

"VPmax (mbar)",

"VPact (mbar)",

"VPdef (mbar)",

"sh (g / kg)",

"H2OC (mmol / mol)",

"rho (g / m ** 3)",

"wv (m / s)",

"max. wv (m / s)",

"wd (deg)"]

Agora, converta todas as 420.551 linhas de dados em uma matriz Numpy.

Listagem 6.29. Analisando os dados

import numpy como np

float_data = np.zeros ((len (linhas), len (cabeçalho) - 1))

para i, linha em enumerar (linhas):


valores = [float (x) para x em line.split (',') [1:]]

float_data [i,:] = valores

Por exemplo, aqui está o gráfico da temperatura (em graus Celsius) ao longo do tempo (veja
a figura 6.18 ). Nesta trama, você pode ver claramente a periodicidade anual da temperatura.

Figura 6.18. Temperatura em todo o intervalo temporal do conjunto de dados (° C)

Listagem 6.30. Plotando a série de tempo da temperatura

de pyplot de importação matplotlib como plt

temp = float_data [:, 1] <1> temperatura (em graus Celsius)

plt.plot (intervalo (len (temp)), temp)

Aqui está um gráfico mais estreito dos primeiros 10 dias de dados de temperatura (ver figura
6.19 ). Como os dados são registrados a cada 10 minutos, você obtém 144 pontos de dados por
dia.
Figura 6.19. Temperatura nos primeiros 10 dias do conjunto de dados (° C)

Listagem 6.31. Plotando os primeiros 10 dias da série de tempo de temperatura

plt.plot (intervalo (1440), temp [: 1440])

Neste gráfico, você pode ver a periodicidade diária, especialmente evidente nos últimos 4
dias. Observe também que esse período de 10 dias deve ser proveniente de um mês de inverno
bastante frio.

Se você estivesse tentando prever a temperatura média no mês seguinte, dados alguns meses de
dados anteriores, o problema seria fácil, devido à periodicidade confiável dos dados em escala
anual. Mas olhando para os dados em uma escala de dias, a temperatura parece muito mais
caótica. É esta timeseries previsível em uma escala diária? Vamos descobrir.

6.3.2. Preparando os dados


A formulação exata do problema será a seguinte: dados dados que vão desde o
tempo lookback(um intervalo de tempo é de 10 minutos) e amostrados a
cada stepstimesteps, você pode prever a temperatura em tempo delay? Você usará os
seguintes valores de parâmetro:

 lookback = 720—Observations retornará 5 dias.


 steps = 6—Observações serão amostradas em um ponto de dados por hora.
 delay = 144—Os alertas serão 24 horas no futuro.

Para começar, você precisa fazer duas coisas:

 Pré-processe os dados para um formato que uma rede neural possa ingerir. Isso é fácil:
os dados já são numéricos, então você não precisa fazer nenhuma vetorização. Mas cada
timeseries nos dados está em uma escala diferente (por exemplo, a temperatura é
tipicamente entre -20 e +30, mas a pressão atmosférica, medida em mbar, é em torno
de 1.000). Você irá normalizar cada série de tempo de forma independente, para que
todos eles tomem pequenos valores em uma escala similar.
 Escreva um gerador Python que use a matriz atual de dados flutuantes e forneça lotes de
dados do passado recente, junto com uma temperatura-alvo no futuro. Como as
amostras no conjunto de dados são altamente redundantes (a amostra N e a
amostra N + 1 terão a maioria de seus timesteps em comum), seria um desperdício
atribuir explicitamente cada amostra. Em vez disso, você gerará as amostras
rapidamente usando os dados originais.

Você pré-processará os dados subtraindo a média de cada timeseries e dividindo pelo desvio
padrão. Você usará os primeiros 200.000 timesteps como dados de treinamento, portanto
calcule a média e o desvio padrão apenas nessa fração dos dados.

Listagem 6.32. Normalizando os dados

mean = float_data [: 200000] .mean (eixo = 0)

float_data - = mean

std = float_data [: 200000] .std (eixo = 0)

float_data / = std

A Listagem 6.33 mostra o gerador de dados que você usará. Ela produz uma tupla (samples,
targets), onde samplesestá um lote de dados de entrada e targetsé a matriz
correspondente de temperaturas alvo. Leva os seguintes argumentos:

 data- A matriz original de dados de ponto flutuante, que você normalizou na listagem
6.32 .
 lookback—Quantos timesteps retornam os dados de entrada devem ir.
 delay- Quantos timesteps no futuro o alvo deveria ser.
 min_indexe max_index—Indices na datamatriz que delimitam quais
timesteps serão extraídos. Isso é útil para manter um segmento dos dados para
validação e outro para teste.
 shuffle—Para misturar as amostras ou desenhá-las em ordem cronológica.
 batch_size—O número de amostras por lote.
 step—O período, em tempo, no qual você amostra dados. Você configurará para 6 para
desenhar um ponto de dados a cada hora.

Listagem 6.33. Gerador gerando amostras de timeseries e seus alvos

def gerador (dados, lookback, delay, min_index, max_index,

shuffle = False, batch_size = 128, passo = 6):

se max_index for None:

max_index = len (data) - atraso - 1

i = min_index + lookback

enquanto 1:

se shuffle:

linhas = np.random.randint (

min_index + lookback, max_index, size = batch_size)

outro:

if i + batch_size> = max_index:

i = min_index + lookback

linhas = np.arange (i, min (i + batch_size, max_index))


i + = len (linhas)

samples = np.zeros ((len (linhas),

lookback // step,

data.shape [-1]))

targets = np.zeros ((len (linhas),))

para j, linha em enumerar (linhas):

índices = intervalo (linhas [j] - lookback, linhas [j], etapa)

samples [j] = data [indices]

alvos [j] = dados [linhas [j] + atraso] [1]

produzir amostras, alvos

Agora, vamos usar a generatorfunção abstrata para instanciar três geradores: um para
treinamento, um para validação e um para teste. Cada um examinará diferentes segmentos
temporais dos dados originais: o gerador de treinamento analisa os primeiros 200.000
timesteps, o gerador de validação examina os 100.000 a seguir e o gerador de teste examina o
restante.

Listagem 6.34. Preparando os geradores de treinamento, validação e teste

lookback = 1440

etapa = 6

atraso = 144

batch_size = 128

train_gen = generator (float_data,

lookback = lookback

atraso = atraso

min_index = 0,

max_index = 200000,

shuffle = Verdadeiro

passo = passo

batch_size = batch_size)

val_gen = gerador (float_data,

lookback = lookback

atraso = atraso

min_index = 200001,
max_index = 300000,

passo = passo

batch_size = batch_size)

test_gen = generator (float_data,

lookback = lookback

atraso = atraso

min_index = 300001,

max_index = None,

passo = passo

batch_size = batch_size)

val_steps = (300000 - 200001 - lookback) 1

test_steps = (len (float_data) - 300001 - lookback) 2

 1 Quantas etapas para desenhar a partir de val_gen, a fim de ver todo o


conjunto de validação
 2 Quantas etapas para desenhar a partir do test_gen, a fim de ver todo o
conjunto de testes

6.3.3. Uma linha de base de bom senso e sem aprendizado de máquina


Antes de começar a usar modelos de aprendizagem profunda de caixa preta para resolver o
problema de previsão de temperatura, vamos tentar uma abordagem simples e de senso
comum. Ele servirá como uma verificação de sanidade e estabelecerá uma linha de base que
você terá que superar para demonstrar a utilidade dos modelos de aprendizado de máquina
mais avançados. Essas linhas de base de bom senso podem ser úteis quando você está
abordando um novo problema para o qual não há solução conhecida (ainda). Um exemplo
clássico é o das tarefas de classificação desequilibradas, em que algumas classes são muito mais
comuns do que outras. Se seu conjunto de dados contém 90% de instâncias de classe A e 10% de
instâncias de classe B, então uma abordagem de senso comum para a tarefa de classificação é
sempre prever “A” quando apresentada com uma nova amostra. Tal classificador é 90% preciso
no geral, e qualquer abordagem baseada em aprendizado deve, portanto, superar essa
pontuação de 90% para demonstrar utilidade. Às vezes, essas linhas de base elementares podem
ser surpreendentemente difíceis de serem superadas.

Neste caso, a série de tempos de temperatura pode ser seguramente assumida como contínua
(as temperaturas de amanhã provavelmente estarão próximas das temperaturas de hoje), bem
como periódicas com um período diário. Assim, uma abordagem de senso comum é sempre
prever que a temperatura daqui a 24 horas será igual à temperatura no momento. Vamos avaliar
essa abordagem usando a métrica de erro absoluto médio (MAE):

np.mean (np.abs (preds - targets))

Aqui está o ciclo de avaliação.


Listagem 6.35. Computando a linha de base do senso comum MAE

def evalu_naive_method ():

batch_maes = []

para o intervalo no intervalo (val_steps):

samples, targets = next (val_gen)

preds = samples [:, -1, 1]

mae = np.mean (np.abs (preds - alvos))

batch_maes.append (mae)

print (np.mean (batch_maes))

evaluate_naive_method ()

Isso produz um MAE de 0,29. Como os dados de temperatura foram normalizados para serem
centralizados em 0 e têm um desvio padrão de 1, esse número não é imediatamente
interpretável. Isso se traduz em um erro absoluto médio de 0,29 × temperature_stdgraus
Celsius: 2,57 ° C.

Listagem 6.36. Convertendo o MAE de volta para um erro Celsius

celsius_mae = 0,29 * std [1]

Esse é um erro absoluto médio razoavelmente grande. Agora o jogo é usar seu conhecimento de
aprendizagem profunda para fazer melhor.

6.3.4. Uma abordagem básica de aprendizado de máquina


Da mesma forma que é útil estabelecer uma linha de base de senso comum antes de tentar
abordagens de aprendizado de máquina, é útil tentar modelos simples e baratos de aprendizado
de máquina (como redes pequenas e densamente conectadas) antes de examinar modelos
complicados e computacionalmente caros como RNNs. Essa é a melhor maneira de garantir que
qualquer complexidade adicional que você enfrente no problema seja legítima e forneça
benefícios reais.

A listagem a seguir mostra um modelo totalmente conectado que inicia com o achatamento dos
dados e, em seguida, o executa por meio de duas Densecamadas. Observe a falta de função de
ativação na última Densecamada, o que é típico de um problema de regressão. Você usa o MAE
como a perda. Como você avalia exatamente os mesmos dados e com a mesma métrica que fez
com a abordagem de senso comum, os resultados serão diretamente comparáveis.

Listagem 6.37. Treinando e avaliando um modelo densamente conectado

de keras.models import Sequential

das camadas de importação keras

de keras.optimizers import RMSprop

model = Sequential ()
model.add (layers.Flatten (input_shape = (lookback // passo, float_data.shape
[-1])))

model.add (layers.Dense (32, activation = 'relu'))

model.add (layers.Dense (1))

model.compile (optimizer = RMSprop (), perda = 'mae')

history = model.fit_generator (train_gen,

steps_per_epoch = 500,

épocas = 20,

validation_data = val_gen,

validation_steps = val_steps)

Vamos exibir as curvas de perda para validação e treinamento (veja a figura 6.20 ).

Figura 6.20. Perda de treinamento e validação na tarefa de previsão de temperatura do Jena com uma rede simples e
densamente conectada

Listagem 6.38. Plotando resultados

import matplotlib.pyplot como plt

perda = history.history ['perda']

val_loss = history.history ['val_loss']

épocas = intervalo (1, len (perda) + 1)

plt.figure ()
plt.plot (epochs, loss, 'bo', label = 'perda de treino')

plt.plot (epochs, val_loss, 'b', label = 'Perda de validação')

plt.title ('Perda de treinamento e validação')

plt.legend ()

plt.show ()

Algumas das perdas de validação estão próximas da linha de base de não-aprendizado, mas não
de forma confiável. Isso mostra o mérito de ter essa linha de base em primeiro lugar: não é fácil
superá-la. Seu senso comum contém muitas informações valiosas que um modelo de
aprendizado de máquina não tem acesso.

Você pode se perguntar, se existe um modelo simples e de bom desempenho para ir dos dados
até os alvos (a linha de base do senso comum), por que o modelo que você está treinando não o
encontra e melhora? Porque esta solução simples não é o que sua configuração de treinamento
está procurando. O espaço dos modelos em que você está procurando uma solução - ou seja, o
espaço da sua hipótese - é o espaço de todas as redes possíveis de duas camadas com a
configuração que você definiu. Essas redes já são bastante complicadas. Quando você está
procurando por umCom um espaço de modelos complicados, a linha de base simples e de bom
desempenho pode ser desaprendida, mesmo que seja tecnicamente parte do espaço de
hipóteses. Essa é uma limitação bastante significativa do aprendizado de máquina em geral: a
menos que o algoritmo de aprendizado seja codificado para procurar um tipo específico de
modelo simples, o aprendizado de parâmetro pode, às vezes, falhar em encontrar uma solução
simples para um problema simples.

6.3.5. Uma primeira linha de base recorrente


A primeira abordagem totalmente conectada não funcionou bem, mas isso não significa que o
aprendizado de máquina não seja aplicável a esse problema. A abordagem anterior primeiro
nivelou as séries de tempo, o que removeu a noção de tempo dos dados de entrada. Vamos, em
vez disso, examinar os dados como eles são: uma sequência em que causalidade e ordem são
importantes. Você experimentará um modelo de processamento de sequência recorrente - ele
deve ser o ajuste perfeito para esses dados de sequência, precisamente porque explora a
ordenação temporal dos pontos de dados, diferentemente da primeira abordagem.

Em vez da LSTMcamada apresentada na seção anterior, você usará a GRUcamada desenvolvida


por Chung et al. em 2014. [ 5 ] As camadas de unidades recorrentes (GRU) funcionam usando o
mesmo princípio que o LSTM, mas são um pouco simplificadas e, portanto, mais baratas de
serem executadas (embora possam não ter tanto poder representacional quanto o LSTM). Esse
trade-off entre custo computacional e poder representacional é visto em toda parte no
aprendizado de máquina.

Junyoung Chung et al., “Avaliação Empírica de Redes Neurais Recorrentes Conectadas na Modelagem de Seqüências”, Conferência sobre

Sistemas de Processamento de Informações Neurais (2014), https://arxiv.org/abs/1412.3555 .

Listagem 6.39. Treinando e avaliando um modelo baseado em GRU

de keras.models import Sequential


das camadas de importação keras

de keras.optimizers import RMSprop

model = Sequential ()

model.add (layers.GRU (32, input_shape = (Nenhum, float_data.shape [-1])))

model.add (layers.Dense (1))

model.compile (optimizer = RMSprop (), perda = 'mae')

history = model.fit_generator (train_gen,

steps_per_epoch = 500,

épocas = 20,

validation_data = val_gen,

validation_steps = val_steps)

A Figura 6.21 mostra os resultados. Muito melhor! Você pode superar significativamente a
linha de base de senso comum, demonstrando o valor do aprendizado de máquina, bem como a
superioridade das redes recorrentes em comparação com redes densas de achatamento de
seqüência nesse tipo de tarefa.

Figura 6.21. Perda de treinamento e validação na tarefa de previsão de temperatura de Jena com uma GRU

O novo MAE de validação de ~ 0,265 (antes de começar a sobrealcançar significativamente) se


traduz em um erro absoluto médio de 2,35 ° C após a desnormalização. Isso é um ganho sólido
no erro inicial de 2,57 ° C, mas você provavelmente ainda tem uma pequena margem de
melhoria.

6.3.6. Usando dropout recorrente para combater overfitting


É evidente a partir das curvas de treinamento e validação que o modelo está sendo overfitting:
as perdas de treinamento e validação começam a divergir consideravelmente após algumas
épocas. Você já está familiarizado com uma técnica clássica para combater esse fenômeno:
dropout, que zera aleatoriamente as unidades de entrada de uma camada para quebrar as
correlações de acaso nos dados de treinamento aos quais a camada está exposta. Mas como
aplicar corretamente o dropout em redes recorrentes não é uma questão trivial. Há muito se
sabe que a aplicação do abandono antes de uma camada recorrente dificulta a aprendizagem,
em vez de ajudar na regularização. Em 2015, Yarin Gal, como parte de sua tese de doutorado em
aprendizado profundo bayesiano, [ 6 ]determinou a maneira correta de usar o dropout com uma
rede recorrente: a mesma máscara de dropout (o mesmo padrão de unidades descartadas) deve
ser aplicada a cada timestep, em vez de uma máscara de dropout que varia aleatoriamente de
timestep a timestep. Além do mais, a fim de regularizar as representações formadas pelos
portais recorrentes de camadas como GRUe LSTM, uma máscara de dropout constante no tempo
deve ser aplicada às ativações recorrentes internas da camada (uma máscara de
dropout recorrente ). Usar a mesma máscara de abandono a cada timestep permite que a rede
propague adequadamente seu erro de aprendizado ao longo do tempo; uma máscara de
abandono temporal aleatória interromperia esse sinal de erro e seria prejudicial ao processo de
aprendizagem.

Veja Yarin Gal, “Incerteza na Aprendizagem Profunda (Tese de Doutorado)”, 13 de outubro de

2016, http://mlg.eng.cam.ac.uk/yarin/blog_2248.html .

Yarin Gal fez sua pesquisa usando Keras e ajudou a construir esse mecanismo diretamente nas
camadas recorrentes de Keras. Cada camada recorrente em Keras possui dois argumentos
relacionados a dropout:, dropoutum float especificando a taxa de dropout para unidades de
entrada da camada, e recurrent_dropoutespecificando a taxa de dropout das unidades
recorrentes. Vamos adicionar dropout e dropout recorrente à GRUcamada e ver como isso afeta o
overfitting. Como as redes que estão sendo regularizadas com o dropout sempre demoram mais
para convergir totalmente, você treinará a rede para o dobro de épocas.

Listagem 6.40. Treinamento e avaliação de um modelo baseado em GRU regularizado por desistência

de keras.models import Sequential

das camadas de importação keras

de keras.optimizers import RMSprop

model = Sequential ()

model.add (layers.GRU (32,

abandono = 0,2,

recurrent_dropout = 0,2,

input_shape = (Nenhum, float_data.shape [-1])))

model.add (layers.Dense (1))

model.compile (optimizer = RMSprop (), perda = 'mae')

history = model.fit_generator (train_gen,


steps_per_epoch = 500,

épocas = 40,

validation_data = val_gen,

validation_steps = val_steps)

A Figura 6.22 mostra os resultados. Sucesso! Você não está mais exagerando durante as
primeiras 30 épocas. Mas embora você tenha pontuações de avaliação mais estáveis, suas
melhores pontuações não são muito menores do que eram anteriormente.

Figura 6.22. Perda de treinamento e validação na tarefa de previsão de temperatura de Jena com um GRU regularizado por
abandono

6.3.7. Empilhando camadas recorrentes


Como você não está mais com overfitting, mas parece ter atingido um gargalo de desempenho,
considere aumentar a capacidade da rede. Lembre-se da descrição do fluxo de trabalho
universal de aprendizado de máquina: geralmente é uma boa ideia aumentar a capacidade de
sua rede até que o overfitting se torne o principal obstáculo (supondovocê já está tomando
medidas básicas para atenuar o overfitting, como o uso de dropout). Contanto que você não
esteja super adaptando muito mal, você provavelmente estará abaixo da capacidade.

Aumentar a capacidade de rede normalmente é feito aumentando o número de unidades nas


camadas ou adicionando mais camadas. O empilhamento recorrente de camadas é uma forma
clássica de construir redes recorrentes mais poderosas: por exemplo, o que atualmente alimenta
o algoritmo do Google Tradutor é uma pilha de sete LSTMcamadas grandes - isso é enorme.

Para empilhar camadas recorrentes umas em cima das outras em Keras, todas as camadas
intermediárias devem retornar sua sequência completa de saídas (um tensor 3D) em vez de sua
saída no último intervalo de tempo. Isso é feito especificando return_sequences=True.

Listagem 6.41. Treinamento e avaliação de um modelo GRU empilhado regularizado por desistência

de keras.models import Sequential

das camadas de importação keras

de keras.optimizers import RMSprop


model = Sequential ()

model.add (layers.GRU (32,

abandono = 0,1,

recurrent_dropout = 0,5,

return_sequences = Verdadeiro,

input_shape = (Nenhum, float_data.shape [-1])))

model.add (layers.GRU (64, ativação = 'relu',

abandono = 0,1,

recurrent_dropout = 0,5))

model.add (layers.Dense (1))

model.compile (optimizer = RMSprop (), perda = 'mae')

history = model.fit_generator (train_gen,

steps_per_epoch = 500,

épocas = 40,

validation_data = val_gen,

validation_steps = val_steps)

A Figura 6.23 mostra os resultados. Você pode ver que a camada adicionada melhora os
resultados um pouco, embora não significativamente. Você pode tirar duas conclusões:

 Como você ainda não está super adaptando muito mal, você pode aumentar com
segurança o tamanho de suas camadas em uma busca pela melhoria da perda de
validação. Isto tem um custo computacional não desprezível, no entanto.
 Adicionar uma camada não ajudou por um fator significativo, portanto, você pode estar
vendo retornos decrescentes de aumentar a capacidade da rede neste momento.
Figura 6.23. Perda de treinamento e validação na tarefa de previsão de temperatura Jena com uma rede GRU empilhada

6.3.8. Usando RNNs bidirecionais


A última técnica introduzida nesta seção é chamada de RNNs bidirecionais . Um RNN
bidirecional é uma variante RNN comum que pode oferecer um desempenho maior do que um
RNN regular em determinadas tarefas. É freqüentemente usado no processamento de
linguagem natural - você pode chamar isso de canivete suíço de aprendizado profundo para
processamento de linguagem natural.

Os RNNs são notavelmente dependentes da ordem ou dependentes do tempo: eles processam os


timesteps de suas sequências de entrada em ordem, e embaralhar ou reverter os timesteps pode
mudar completamente as representações que o RNN extrai da sequência. Esta é precisamente a
razão pela qual eles têm um bom desempenho em problemas onde a ordem é significativa, como
o problema de previsão de temperatura. Um RNN bidirecional explora a sensibilidade do pedido
de RNNs: consiste em usar dois RNNs regulares, como o GRUeLSTMcamadas com as quais você
já está familiarizado, cada qual processa a sequência de entrada em uma direção (cronológica e
anticronologicamente) e, em seguida, mescla suas representações. Ao processar uma sequência
nos dois sentidos, um RNN bidirecional pode capturar padrões que podem ser ignorados por
um RNN unidirecional.

Notavelmente, o fato de que as camadas RNN nesta seção processaram sequências em ordem
cronológica (datas mais antigas primeiro) pode ter sido uma decisão arbitrária. Pelo menos, é
uma decisão que não fizemos nenhuma tentativa de questionar até agora. Os RNNs poderiam
ter funcionado bem o suficiente se processassem seqüências de entrada em ordem
anticronológica, por exemplo (newest timepeps first)? Vamos tentar isso na prática e ver o que
acontece. Tudo o que você precisa fazer é gravar uma variante do gerador de dados onde as
seqüências de entrada são revertidas ao longo da dimensão de tempo (substitua a última linha
por yield samples[:, ::-1, :], targets). Treinando a mesma GRUrede de camada
única usada no primeiro experimento desta seção, você obtém os resultados mostrados
na figura 6.24 .
Figura 6.24. Perda de treinamento e validação na tarefa de previsão de temperatura de Jena com uma GRU treinada em
sequências invertidas

A GRU de ordem reversa tem um desempenho muito baixo até mesmo na linha de base de senso
comum, indicando que, nesse caso, o processamento cronológico é importante para o sucesso de
sua abordagem. Isso faz todo o sentido: o subjacenteGRUA camada normalmente será melhor
para lembrar o passado recente do que o passado distante e, naturalmente, os pontos de dados
climáticos mais recentes são mais preditivos do que os pontos de dados mais antigos para o
problema (é isso que torna a linha de base de senso comum bastante forte). Assim, a versão
cronológica da camada é obrigada a superar a versão de ordem inversa. É importante ressaltar
que isso não é verdade para muitos outros problemas, incluindo a linguagem natural:
intuitivamente, a importância de uma palavra para entender uma frase geralmente não depende
de sua posição na sentença. Vamos tentar o mesmo truque no exemplo do LSTM IMDB
da seção 6.2 .

Listagem 6.42. Treinar e avaliar um LSTMusando sequências invertidas

de keras.datasets import imdb

da sequência de importação keras.preprocessing

das camadas de importação keras

de keras.models import Sequential

max_features = 10000 1

maxlen = 500 2

(x_train, y_train), (x_test, y_test) = imdb.load_data (

num_words = max_features) 3

x_train = [x [:: - 1] para x em x_train] 4

x_test = [x [:: - 1] para x em x_test] 4


x_train = sequence.pad_sequences (x_train, maxlen = maxlen) 5

x_test = seqüência.pad_sequências (x_teste, maxlen = maxlen) 5

model = Sequential ()

model.add (layers.Embedding (max_features, 128))

model.add (layers.LSTM (32))

model.add (layers.Dense (1, activation = 'sigmoid'))

model.compile (optimizer = 'rmsprop',

perda = 'binary_crossentropy',

metrics = ['acc'])

history = model.fit (x_train, y_train,

épocas = 10,

batch_size = 128,

validation_split = 0,2)

 1 Número de palavras a considerar como recursos


 2 Corta textos após este número de palavras (entre as palavras mais
comuns max_features)
 3 Carrega dados
 4 inverte seqüências
 5 seqüências de Pads

Você obtém desempenho quase idêntico ao da ordem cronológica LSTM. Notavelmente, em tal
conjunto de dados de texto, inverteu-ordem de processamento funciona tão bem como o
processamento cronológica, confirmando a hipótese de que, embora a ordem das
palavras faz questão em linguagem compreensão, que pedir que você usa não é crucial. É
importante ressaltar que um RNN treinado em sequências invertidas aprenderá diferentes
representações do que um treinado nas sequências originais, da mesma forma que você teria
modelos mentais diferentes se o tempo fluísse para trás no mundo real - se você vivesse uma
vida onde morresse no primeiro dia e nasceram no seu último dia. Em aprendizado de máquina,
representações diferentes, mas úteisSempre vale a pena explorar, e quanto mais eles diferem,
melhor: eles oferecem um novo ângulo a partir do qual analisar seus dados, capturando aspectos
dos dados que foram perdidos por outras abordagens e, portanto, podem ajudar a melhorar o
desempenho em uma tarefa. Essa é a intuição por trás do conjunto , um conceito que
exploraremos no capítulo 7 .

Um RNN bidirecional explora essa ideia para melhorar o desempenho de RNNs de ordem
cronológica. Ele examina sua sequência de entrada nos dois sentidos (consulte a figura 6.25 ),
obtendo representações potencialmente mais ricas e capturando padrões que podem ter sido
perdidos somente pela versão de ordem cronológica.
Figura 6.25. Como funciona uma camada RNN bidirecional

Para instanciar um RNN bidirecional em Keras, você usa a Bidirectionalcamada, que toma
como primeiro argumento uma instância de camada recorrente. Bidirectionalcria uma
segunda instância separada dessa camada recorrente e usa uma instância para processar as
seqüências de entrada em ordem cronológica e a outra instância para processar as seqüências de
entrada em ordem inversa. Vamos tentar na tarefa de análise de sentimentos do IMDB.

Listagem 6.43. Treinamento e avaliação de um bidirecional LSTM

model = Sequential ()

model.add (layers.Embedding (max_features, 32))

model.add (layers.Bidirectional (layers.LSTM (32)))

model.add (layers.Dense (1, activation = 'sigmoid'))

model.compile (optimizer = 'rmsprop', loss = 'binary_crossentropy', métricas =


['acc'])

history = model.fit (x_train, y_train,

épocas = 10,

batch_size = 128,

validation_split = 0,2)

Ele executa um pouco melhor que o normal que LSTMvocê tentou na seção anterior, alcançando
mais de 89% de precisão na validação. Também parece sobrecarregar mais rapidamente, o que
não é surpreendente porque uma camada bidirecional tem duas vezes mais parâmetros que um
cronológico LSTM. Com alguma regularização, a abordagem bidirecional provavelmente seria
um bom desempenho nessa tarefa.

Agora vamos tentar a mesma abordagem na tarefa de previsão de temperatura.

Listagem 6.44. Treinando um bidirecional GRU

de keras.models import Sequential

das camadas de importação keras


de keras.optimizers import RMSprop

model = Sequential ()

model.add (layers.Bidirectional (

layers.GRU (32), input_shape = (Nenhum, float_data.shape [-1])))

model.add (layers.Dense (1))

model.compile (optimizer = RMSprop (), perda = 'mae')

history = model.fit_generator (train_gen,

steps_per_epoch = 500,

épocas = 40,

validation_data = val_gen,

validation_steps = val_steps)

Isso funciona tão bem quanto a GRUcamada normal . É fácil entender por quê: toda a capacidade
preditiva deve vir da metade cronológica da rede, porque a metade anticronológica é conhecida
por estar gravemente abaixo do desempenho nessa tarefa (novamente, porque o passado recente
é muito mais importante do que o passado distante neste caso). ).

6.3.9. Indo ainda mais longe


Há muitas outras coisas que você poderia tentar, a fim de melhorar o desempenho no problema
de previsão de temperatura:

 Ajuste o número de unidades em cada camada recorrente na configuração


empilhada. As escolhas atuais são em grande parte arbitrárias e, portanto,
provavelmente sub-ótimas.
 Ajuste a taxa de aprendizado usada pelo RMSpropotimizador.
 Tente usar LSTMcamadas em vez de GRUcamadas.
 Tente usar um regressor densamente conectado maior no topo das camadas
recorrentes: isto é, uma Densecamada maior ou até mesmo uma pilha
de Densecamadas.
 Não se esqueça de, eventualmente, executar os modelos com melhor desempenho (em
termos de validação MAE) no conjunto de testes! Caso contrário, você desenvolverá
arquiteturas que estão super adaptando ao conjunto de validação.

Como sempre, o aprendizado profundo é mais uma arte do que uma ciência. Podemos fornecer
diretrizes que sugiram o que provavelmente funcionará ou não em determinado problema, mas,
em última análise, todo problema é único; você terá que avaliar empiricamente estratégias
diferentes. Atualmente, não há uma teoria que lhe diga com antecedência o que você deve fazer
para solucionar um problema de maneira ideal. Você deve iterar.

6.3.10. Empacotando
Veja o que você deve tirar desta seção:
 Como você aprendeu no capítulo 4 , ao abordar um novo problema, é bom primeiro
estabelecer linhas de base de bom senso para sua métrica escolhida. Se você não tem
uma base para vencer, você não pode dizer se você está fazendo um progresso real.
 Tente modelos simples antes dos caros, para justificar a despesa adicional. Às vezes, um
modelo simples se tornará sua melhor opção.
 Quando você tem dados em que a ordenação temporal é importante, as redes
recorrentes são um ótimo ajuste e superam facilmente os modelos que primeiro nivelam
os dados temporais.
 Para usar o dropout com redes recorrentes, você deve usar uma máscara de dropout de
constante de tempo e máscara de dropout recorrente. Estes são construídos em
camadas recorrentes Keras, então tudo que você tem a fazer é usar
os argumentos dropoute recurrent_dropoutde camadas recorrentes.
 Os RNNs empilhados fornecem mais poder representacional do que uma única camada
RNN. Eles também são muito mais caros e, portanto, nem sempre valem a
pena. Embora eles ofereçam ganhos claros em problemas complexos (como a tradução
automática), eles nem sempre são relevantes para problemas menores e mais simples.
 Os RNNs bidirecionais, que analisam uma sequência nos dois sentidos, são úteis em
problemas de processamento de linguagem natural. Mas eles não são fortes em dados
de sequência, onde o passado recente é muito mais informativo do que o início da
sequência.

Nota

Há dois conceitos importantes que não abordaremos em detalhes aqui: atenção recorrente e
mascaramento de sequência. Ambos tendem a ser especialmente relevantes para o
processamento de linguagem natural e não são particularmente aplicáveis ao problema de
previsão de temperatura. Vamos deixá-los para estudo futuro fora deste livro.

Mercados e aprendizado de máquina

Alguns leitores são obrigados a tomar as técnicas que introduzimos aqui e testá-las no problema
da previsão do preço futuro dos títulos no mercado de ações (ou taxas de câmbio, e assim por
diante). Os mercados têm características estatísticas muito diferentes dos fenômenos naturais,
como padrões climáticos. Tentar usar o aprendizado de máquina para vencer mercados, quando
você só tem acesso a dados disponíveis publicamente, é um esforço difícil, e é provável que você
perca tempo e recursos sem nada para mostrar.

Lembre-se sempre de que, quando se trata de mercados, o desempenho passado não é um bom
indicador de retornos futuros - olhar no espelho retrovisor é uma maneira ruim de dirigir. O
aprendizado de máquina, por outro lado, é aplicável a conjuntos de dados em que o
passado é um bom preditor do futuro.

6.4. PROCESSAMENTO DE SEQUÊNCIAS COM CONVNETS

No capítulo 5 , você aprendeu sobre redes neurais convolucionais (convnets) e como elas
funcionam particularmente bem em problemas de visão computacional, devido à sua capacidade
de operar de forma convolucional , extraindo recursos de correções de entrada locais e
permitindo modularidade de representação e eficiência de dados. As mesmas propriedades que
fazem as redes se destacarem na visão computacional também as tornam altamente relevantes
para o processamento de seqüências. O tempo pode ser tratado como uma dimensão espacial,
como a altura ou a largura de uma imagem 2D.
Tais capas 1D podem ser competitivas com RNNs em certos problemas de processamento de
sequência, geralmente a um custo computacional consideravelmente mais
barato. Recentemente, os modelos 1D, normalmente usados com núcleos dilatados, foram
usados com grande sucesso para geração de áudio e tradução automática. Além desses sucessos
específicos, há muito tempo se sabe que pequenas convnets 1D podem oferecer uma alternativa
rápida aos RNNs para tarefas simples, como classificação de texto e previsão de timeseries.

6.4.1. Compreender a convolução 1D para dados sequenciais


As camadas de convolução introduzidas anteriormente eram convoluções 2D, extraindo patches
2D de tensores de imagem e aplicando uma transformação idêntica em cada patch. Da mesma
forma, você pode usar convoluções 1D, extraindo patches 1D locais (subsequências) de
sequências (veja a figura 6.26).

Figura 6.26. Como funciona a convolução 1D: cada timestep de saída é obtido de um patch temporal na sequência de entrada.

Essas camadas de convolução 1D podem reconhecer padrões locais em uma sequência. Como a
mesma transformação de entrada é executada em cada patch, um padrão aprendido em uma
determinada posição em uma sentença pode ser reconhecido posteriormente em uma posição
diferente, tornando invariante de tradução de convés 1D (para traduções temporais). Por
exemplo, uma seqüência de processamento de caracteres de convecção 1D usando janelas de
convolução de tamanho 5 deve ser capaz de aprender palavras ou fragmentos de tamanho 5 ou
menos, e deve ser capaz de reconhecer essas palavras em qualquer contexto em uma sequência
de entrada. Uma convã 1D de nível de personagem é, portanto, capaz de aprender sobre a
morfologia da palavra.

6.4.2. 1D pooling para dados de sequência


Você já está familiarizado com operações de pool 2D, como pooling médio 2D e pool máximo,
usado em convnets para reduzir a resolução de imagem de tensores. A operação de pool 2D tem
um equivalente de 1D: extrair patches 1D (subsequences) de uma entrada e gerar o valor
máximo (pool máximo) ou valor médio (pool médio). Assim como nas convnets 2D, isso é usado
para reduzir o comprimento de entradas 1D ( subamostragem ).

6.4.3. Implementando uma conv. 1D


Em Keras, você usa um convnet 1D através da Conv1Dcamada, que tem uma interface
semelhante a Conv2D. Leva como tensores 3D de entrada com forma (samples, time,
features)e retorna tensores 3D de formato similar. A janela de convolução é uma janela 1D
no eixo temporal: eixo 1 no tensor de entrada.

Vamos construir uma simples convecção 1D de duas camadas e aplicá-la à tarefa de classificação
de sentimentos do IMDB com a qual você já está familiarizado. Como lembrete, este é o código
para obter e pré-processar os dados.

Listagem 6.45. Preparando os dados do IMDB

de keras.datasets import imdb

da sequência de importação keras.preprocessing

max_features = 10000

max_len = 500

print ('Carregando dados ...')

(x_train, y_train), (x_test, y_test) = imdb.load_data (num_words =


max_features)

print (len (x_train), 'sequências de trem')

print (len (x_test), 'sequências de teste')

print ('seqüências Pad (amostras x tempo)')

x_train = sequence.pad_sequences (x_train, maxlen = max_len)

x_test = sequence.pad_sequences (x_test, maxlen = max_len)

print ('x_train shape:', x_train.shape)

print ('x_test shape:', x_test.shape)

As capas 1D são estruturadas da mesma forma que suas contrapartes 2D, que você usou
no capítulo 5 : elas consistem em uma pilha Conv1De MaxPooling1Dcamadas, terminando
em uma camada de pool global ou uma Flattencamada, que transformam as saídas 3D em
saídas 2D, permitindo você adicionar uma ou mais Densecamadas ao modelo para classificação
ou regressão.

Uma diferença, no entanto, é o fato de que você pode se dar ao luxo de usar janelas de
convolução maiores com os modelos 1D. Com uma camada de convolução 2D, uma janela de
convolução 3 × 3 contém 3 × 3 = 9 vetores de recursos; mas com uma camada de convolução 1D,
uma janela de convolução de tamanho 3 contém apenas 3 vetores de recursos. Você pode assim
facilmente ter janelas de convolução 1D de tamanho 7 ou 9.

Este é o exemplo 1D convnet para o conjunto de dados do IMDB.

Listagem 6.46. Treinamento e avaliação de uma simples convenção 1D nos dados do IMDB

de keras.models import Sequential


das camadas de importação keras

de keras.optimizers import RMSprop

model = Sequential ()

model.add (layers.Embedding (max_features, 128, input_length = max_len))

model.add (layers.Conv1D (32, 7, ativação = 'relu'))

model.add (layers.MaxPooling1D (5))

model.add (layers.Conv1D (32, 7, ativação = 'relu'))

model.add (layers.GlobalMaxPooling1D ())

model.add (layers.Dense (1))

model.summary ()

model.compile (optimizer = RMSprop (lr = 1e-4),

perda = 'binary_crossentropy',

metrics = ['acc'])

history = model.fit (x_train, y_train,

épocas = 10,

batch_size = 128,

validation_split = 0,2)

As figuras 6.27 e 6.28 mostram os resultados de treinamento e validação. A precisão da


validação é um pouco menor que a do LSTM, mas o tempo de execução é mais rápido tanto na
CPU quanto na GPU (o aumento exato na velocidade irá variar muito dependendo da sua
configuração exata). Neste ponto, você poderia treinar novamente este modelo para o número
certo de épocas (oito) e executá-lo no conjunto de testes. Essa é uma demonstração convincente
de que uma conv. 1D pode oferecer uma alternativa rápida e barata a uma rede recorrente em
uma tarefa de classificação de sentimentos no nível da palavra.
Figura 6.27. Perda de treinamento e validação no IMDB com uma simples convulsão 1D

Figura 6.28. Treinamento e validação de validação no IMDB com uma simples convulsão 1D

6.4.4. Combinando CNNs e RNNs para processar seqüências longas


Como os 1D convnets processam os patches de entrada de forma independente, eles não são
sensíveis à ordem dos timesteps (além de uma escala local, o tamanho das janelas de
convolução), ao contrário dos RNNs. É claro que, para reconhecer padrões de longo prazo, é
possível empilhar muitas camadas de convolução e agrupar camadas, resultando em camadas
superiores que verão longos trechos das entradas originais, mas ainda é uma maneira bastante
fraca de induzir a sensibilidade do pedido. Uma forma de evidenciar essa fraqueza é tentar usar
convecções 1D no problema de previsão de temperatura, em que a sensibilidade à ordem é
fundamental para produzir boas previsões. O exemplo seguinte reutiliza as seguintes variáveis
definidas anteriormente: float_data, train_gen, val_gen, e val_steps.

Listagem 6.47. Treinar e avaliar uma simples convecção 1D nos dados de Jena

de keras.models import Sequential

das camadas de importação keras


de keras.optimizers import RMSprop

model = Sequential ()

model.add (layers.Conv1D (32, 5, ativação = 'relu',

input_shape = (Nenhum, float_data.shape [-1])))

model.add (layers.MaxPooling1D (3))

model.add (layers.Conv1D (32, 5, ativação = 'relu'))

model.add (layers.MaxPooling1D (3))

model.add (layers.Conv1D (32, 5, ativação = 'relu'))

model.add (layers.GlobalMaxPooling1D ())

model.add (layers.Dense (1))

model.compile (optimizer = RMSprop (), perda = 'mae')

history = model.fit_generator (train_gen,

steps_per_epoch = 500,

épocas = 20,

validation_data = val_gen,

validation_steps = val_steps)

A Figura 6.29 mostra os MAEs de treinamento e validação.


Figura 6.29. Perda de treinamento e validação na tarefa de previsão de temperatura de Jena com uma simples convecção 1D

A validação do MAE permanece nos 0,40s: você não pode nem superar a linha de base do senso
comum usando a pequena convnet. Novamente, isso acontece porque o convnet procura
padrões em qualquer ponto da entrada das séries de tempo e não tem conhecimento da posição
temporal de um padrão que vê (no início, no final e assim por diante). Como os pontos de dados
mais recentes devem ser interpretados de maneira diferente dos pontos de dados mais antigos,
no caso desse problema de previsão específico, a convnet falha ao produzir resultados
significativos. Essa limitação de convnets não é um problema com os dados do IMDB, porque os
padrões de palavras-chave associados a um sentimento positivo ou negativo são informativos
independentemente de onde eles são encontrados nas sentenças de entrada.

Uma estratégia para combinar a velocidade e a leveza das conversas com a sensibilidade à
ordem dos RNNs é usar uma convnet 1D como uma etapa de pré-processamento antes de uma
RNN (consulte a figura 6.30 ). Isso é especialmente benéfico quando você está lidando com
sequências que são tão longas que elas não podem ser processadas de maneira realista com
RNNs, como sequências com milhares de etapas. A convnet transformará a longa seqüência de
entrada em seqüências muito mais curtas (com resolução reduzida) de recursos de nível
superior. Essa seqüência de recursos extraídos se torna a entrada para a parte RNN da rede.

Figura 6.30. Combinando uma convnet 1D e um RNN para processar seqüências longas

Essa técnica não é vista com freqüência em trabalhos de pesquisa e aplicações práticas,
possivelmente porque não é bem conhecida. É eficaz e deveria ser mais comum. Vamos tentar
no conjunto de dados de previsão de temperatura. Como essa estratégia permite manipular
seqüências muito mais longas, é possível observar dados de mais tempo (aumentando
o lookbackparâmetro do gerador de dados) ou ver as séries de tempo de alta resolução
(diminuindo o stepparâmetro do gerador). Aqui, um pouco arbitrariamente, você usará
um stepque é metade do tamanho, resultando em uma série de tempo duas vezes mais longa,
onde oos dados de temperatura são amostrados a uma taxa de 1 ponto por 30 minutos. O
exemplo reutiliza a generatorfunção definida anteriormente.

Listagem 6.48. Preparando geradores de dados de alta resolução para o conjunto de dados do Jena

etapa = 3 1

lookback = 720 2

atraso = 144 2

train_gen = generator (float_data,

lookback = lookback
atraso = atraso

min_index = 0,

max_index = 200000,

shuffle = Verdadeiro

passo = passo)

val_gen = gerador (float_data,

lookback = lookback

atraso = atraso

min_index = 200001,

max_index = 300000,

passo = passo)

test_gen = generator (float_data,

lookback = lookback

atraso = atraso

min_index = 300001,

max_index = None,

passo = passo)

val_steps = (300000 - 200001 - lookback) // 128

test_steps = (len (float_data) - 300001 - lookback) // 128

 1 Anteriormente definido para 6 (1 ponto por hora); agora 3 (1 ponto por 30


min)
 2 inalterado

Este é o modelo, começando com duas Conv1Dcamadas e seguindo com uma GRUcamada. A
Figura 6.31mostra os resultados.
Figura 6.31. Perda de treinamento e validação na tarefa de previsão de temperatura de Jena com uma convecção 1D seguida
por uma gru

Listagem 6.49. Modelo combinando uma base convolucional 1D e uma GRUcamada

de keras.models import Sequential

das camadas de importação keras

de keras.optimizers import RMSprop

model = Sequential ()

model.add (layers.Conv1D (32, 5, ativação = 'relu',

input_shape = (Nenhum, float_data.shape [-1])))

model.add (layers.MaxPooling1D (3))

model.add (layers.Conv1D (32, 5, ativação = 'relu'))

model.add (layers.GRU (32, dropout = 0.1, recurrent_dropout = 0.5))

model.add (layers.Dense (1))

model.summary ()

model.compile (optimizer = RMSprop (), perda = 'mae')

history = model.fit_generator (train_gen,

steps_per_epoch = 500,

épocas = 20,

validation_data = val_gen,
validation_steps = val_steps)

A julgar pela perda de validação, essa configuração não é tão boa quanto a
regularização GRUsozinha, mas é significativamente mais rápida. Ele analisa o dobro de dados, o
que, nesse caso, não parece ser muito útil, mas pode ser importante para outros conjuntos de
dados.

6.4.5. Empacotando
Veja o que você deve tirar desta seção:

 Da mesma forma que as réplicas 2D funcionam bem para processar padrões visuais no
espaço 2D, as réplicas 1D têm um bom desempenho no processamento de padrões
temporais. Eles oferecem uma alternativa mais rápida para os RNNs em alguns
problemas, em particular tarefas de processamento de linguagem natural.
 Tipicamente, as conversas 1D são estruturadas de maneira muito semelhante a seus
equivalentes 2D do mundo da visão computacional: elas consistem em pilhas
de Conv1Dcamadas e Max-Pooling1Dcamadas, terminando em uma operação de
agrupamento global ou operação de achatamento.
 Como os RNNs são extremamente caros para processar sequências muito longas, mas as
convntas 1D são baratas, pode ser uma boa ideia usar uma convnet 1D como uma etapa
de pré-processamento antes de uma RNN, encurtando a sequência e extraindo
representações úteis para o RNN processar.

Resumo do capítulo

 Neste capítulo, você aprendeu as seguintes técnicas, que são amplamente aplicáveis a
qualquer conjunto de dados de dados de sequência, do texto às séries de tempo:
 Como tokenizar texto
 Que palavra é a incorporação e como usá-las
 Quais são as redes recorrentes e como usá-las
 Como empilhar camadas RNN e usar RNNs bidirecionais para construir
modelos de processamento de seqüência mais poderosos
 Como usar convnets 1D para processamento sequencial
 Como combinar convnets 1D e RNNs para processar seqüências longas
Você pode usar RNNs para regressão de timeseries (“predizendo o futuro”), classificação de
timeseries, detecção de anomalias em timeseries e rotulagem de seqüências (como identificação
de nomes ou datas em sentenças).
Da mesma forma, você pode usar as convés 1D para tradução automática (modelos
convolucionais seqüência a seqüência, como SliceNet [ a ] ), classificação de documentos e
correção ortográfica.

uma

Veja https://arxiv.org/abs/1706.03059 .

Se a ordem global é importante em seus dados de sequência, é preferível usar uma rede
recorrente para processá-la. Este é tipicamente o caso de timeseries, onde o passado recente é
provavelmente mais informativo do que o passado distante.
Se a ordenação global não for fundamentalmente significativa , então as torneiras 1D
funcionarão pelo menos tão bem e serão mais baratas. Este é frequentemente o caso dos dados
de texto, em que uma palavra-chave encontrada no início de uma frase é tão significativa quanto
uma palavra-chave encontrada no final.
Capítulo 7. Práticas recomendadas avançadas de
aprendizagem profunda
Este capítulo cobre

 A API funcional Keras


 Usando retornos de chamada Keras
 Trabalhando com a ferramenta de visualização TensorBoard
 Práticas recomendadas importantes para o desenvolvimento de modelos de última
geração

Este capítulo explora uma série de ferramentas poderosas que o aproximarão da capacidade de
desenvolver modelos de última geração em problemas difíceis. Usando a API funcional Keras,
você pode construir modelos semelhantes a gráficos, compartilhar uma camada em diferentes
entradas e usar modelos Keras como as funções do Python. Os callbacks da Keras e a ferramenta
de visualização baseada no navegador TensorBoard permitem monitorar modelos durante o
treinamento. Também discutiremos várias outras práticas recomendadas, incluindo a
normalização de lotes, conexões residuais, otimização de hiperparâmetros e modelagem de
conjuntos.

7.1. INDO ALÉM DO MODELO SEQUENCIAL: A API FUNCIONAL KERAS

Até agora, todas as redes neurais introduzidas neste livro foram implementadas usando
o Sequentialmodelo. O Sequentialmodelo faz a suposição de que a rede tem exatamente
uma entrada e exatamente uma saída, e que consiste em uma pilha linear de camadas (consulte
a figura 7.1 ).

Figura 7.1 Um sequentialmodelo: uma pilha linear de camadas

Essa é uma suposição comumente verificada; a configuração é tão comum que conseguimos
cobrir muitos tópicos e aplicativos práticos nestas páginas usando apenas a Sequentialclasse
de modelo. Mas esse conjunto de suposições é muito inflexível em vários casos. Algumas redes
exigem várias entradas independentes, outras exigem várias saídas e algumas redes têm
ramificações internas entre camadas, o que as torna semelhantes a gráficos de camadas, em vez
de pilhas lineares de camadas.

Algumas tarefas, por exemplo, exigem recursos multimodaisEntradas: eles mesclam dados
provenientes de diferentes fontes de entrada, processando cada tipo de dado usando diferentes
tipos de camadas neurais. Imagine um modelo de aprendizagem profunda tentando prever o
preço de mercado mais provável de uma peça de roupa usada, usando as seguintes entradas:
metadados fornecidos pelo usuário (como a marca, a idade e assim por diante), fornecidos pelo
usuário descrição de texto e uma imagem do item. Se você tivesse apenas os metadados
disponíveis, você poderia codificá-los e usar uma rede densamente conectada para prever o
preço. Se você tivesse apenas a descrição de texto disponível, você poderia usar uma RNN ou
uma convecção 1D. Se você tivesse apenas a foto, você poderia usar uma convnet 2D. Mas como
você pode usar os três ao mesmo tempo? Uma abordagem ingênua seria treinar três modelos
separados e, em seguida, fazer uma média ponderada de suas previsões. Mas isso pode ser sub-
ótimo, porque as informações extraídas pelos modelos podem ser redundantes. A melhor
maneira éem conjunto, aprendemos um modelo mais preciso dos dados usando um modelo que
pode ver todas as modalidades de entrada disponíveis simultaneamente: um modelo com três
ramificações de entrada (ver figura 7.2 ).

Figura 7.2 Um modelo de múltiplas entradas

Da mesma forma, algumas tarefas precisam prever vários atributos de destino dos dados de
entrada. Dado o texto de um romance ou conto, você pode querer classificá-lo automaticamente
por gênero (como romance ou suspense), mas também prever a data aproximada em que foi
escrito. Claro, você poderia treinar dois modelos separados: um para o gênero e outro para a
data. Mas, como esses atributos não são estatisticamente independentes, você pode criar um
modelo melhor aprendendo a prever o gênero e a data juntos ao mesmo tempo. Tal modelo
conjunto teria então duas saídas, ou cabeças (ver figura 7.3).). Devido às correlações entre
gênero e data, saber a data de um romance ajudaria o modelo a aprender representações ricas e
precisas do espaço de novos gêneros, e vice-versa.

Figura 7.3. Um modelo multi-saída (ou multihead)

Além disso, muitas arquiteturas neurais recentemente desenvolvidas exigem topologia de rede
não-linear: redes estruturadas como gráficos acíclicos direcionados. A família de Iniciação de
redes (desenvolvido por Szegedy et ai. Em Google), [ 1 ] , por exemplo, depende de módulos de
Iniciação , onde a entrada é processado por vários ramos convolucionais paralelos cujas saídas
são então integrado de volta para um único tensor (ver figura 7,4 ). Há também a tendência
recente de adicionar conexões residuais a um modelo, que começou com a família de redes
ResNet (desenvolvida por He et al. Na Microsoft). [ 2 ]Uma conexão residual consiste em reinjetar
representações anteriores no fluxo de dados a jusante, adicionando um tensor de saída passado
a um tensor de saída posterior (consulte a figura 7.5 ), que ajuda a evitar a perda de informações
ao longo do fluxo de processamento de dados. Existem muitos outros exemplos de tais redes
semelhantes a gráficos.

Christian Szegedy et al., “Enfrentando as Convoluções”, Conferência sobre Visão Computacional e Reconhecimento de Padrões

(2014), https://arxiv.org/abs/1409.4842 .

Kaiming He et al., “Deep Residual Learning for Image Recognition”, Conferência sobre Visão Computacional e Reconhecimento de Padrões

(2015), https://arxiv.org/abs/1512.03385 .

Figura 7.4. Um módulo de iniciação: um subgrafo de camadas com vários ramos convolucionais paralelos

Figura 7.5 Uma conexão residual: reinjeção de informação prévia a jusante via adição de mapa de característica

Esses três casos de uso importantes - modelos com várias entradas, modelos com várias saídas e
modelos semelhantes a gráficos - não são possíveis quando se usa apenas
a Sequentialclasse do modelo em Keras. Mas há outra maneira muito mais geral e flexível de
usar Keras: a API funcional . Esta seção explica em detalhes o que é, o que pode fazer e como
usá-lo.

7.1.1. Introdução à API funcional


Na API funcional, você manipula diretamente os tensores e usa camadas como funções que
usam tensores e retornam tensores (daí a API funcional de nomes ):

de importação de keras Entrada, camadas

input_tensor = Entrada (forma = (32,)) 1

densa = camadas.Densas (32, ativação = 'relu') 2

output_tensor = denso (input_tensor) 3

 1 tensor
 2 Uma camada é uma função.
 3 Uma camada pode ser chamada em um tensor e retorna um tensor.

Vamos começar com um exemplo mínimo que mostra lado a lado


um Sequentialmodelo simples e seu equivalente na API funcional:

de keras.models import Sequential, Model

das camadas de importação keras

de importação de keras

seq_model = Sequencial () 1

seq_model.add (layers.Dense (32, activation = 'relu', input_shape = (64,)))

seq_model.add (layers.Dense (32, activation = 'relu'))

seq_model.add (layers.Dense (10, ativação = 'softmax'))

input_tensor = Entrada (forma = (64,)) 2

x = camadas.Densidade (32, ativação = 'relu') (sensor_de_entrada)


2

x = camadas.Densidade (32, ativação = 'relu') (x)


2

output_tensor = layers.Dense (10, ativação = 'softmax') (x) 2

model = Model (input_tensor, output_tensor) 3


model.summary () 4

 1 modelo sequencial, que você já conhece


 2 Seu equivalente funcional
 3 A classe Model transforma um tensor de entrada e um tensor de saída em
um modelo.
 4 Vamos dar uma olhada nisso!

Isto é o que a chamada para model.summary()exibe:

_________________________________________________________________

Camada (tipo) Forma de saída Param #

================================================== ===============

input_1 (InputLayer) (Nenhum, 64) 0

_________________________________________________________________

dense_1 (denso) (nenhum, 32) 2080

_________________________________________________________________

denso_2 (denso) (nenhum, 32) 1056

_________________________________________________________________

denso_3 (denso) (nenhum, 10) 330

================================================== ===============

Total de Params: 3.466

Parads treináveis: 3.466

Params não treináveis: 0

A única parte que pode parecer um pouco mágica neste ponto é instanciar um Modelobjeto
usando apenas um tensor de entrada e um tensor de saída. Nos bastidores, Keras recupera todas
as camadas envolvidas em ir de input_tensora output_tensor, trazendo-osjuntos em uma
estrutura de dados semelhante a um gráfico - a Model. Naturalmente, a razão pela qual
funciona é que output_tensorfoi obtida pela transformação repetida input_tensor. Se
você tentou criar um modelo a partir de entradas e saídas que não estavam relacionadas, você
obteria um RuntimeError:

>>> unrelated_input = Input (forma = (32,))

>>> bad_model = model = Model (unrelated_input, output_tensor)

RuntimeError: Gráfico desconectado: não pode

obter valor para o tensor

Tensor ("input_1: 0", shape = (?, 64), dtype = float32) na camada


"input_1".

Este erro diz-lhe, em essência, que Keras não conseguiu alcançar input_1o tensor de saída
fornecido.
Quando se trata de compilar, treinar ou avaliar tal instância Model, a API é a mesma que a
de Sequential:

model.compile (optimizer = 'rmsprop', loss = 'categorical_crossentropy')


1

import numpy como np 2

x_train = np.random.random ((1000, 64))

y_train = np.random.random ((1000, 10))

model.fit (x_train, y_train, epochs = 10, batch_size = 128)


3

pontuação = model.evaluate (x_train, y_train)


4

 1 Compila o modelo
 2 Gera dados fictícios de Numpy para treinar
 3 Treina o modelo por 10 épocas
 4 Avalia o modelo

7.1.2. Modelos com múltiplas entradas


A API funcional pode ser usada para criar modelos que tenham várias entradas. Em geral, esses
modelos mesclam seus diferentes ramos de entrada usando uma camada que pode combinar
vários tensores: adicionando-os, concatenando-os e assim por diante. Isso geralmente é feito
através de uma operação de fusão Keras tais
como keras.layers.add, keras.layers.concatenatee assim por diante. Vejamos um
exemplo muito simples de um modelo de múltiplas entradas: um modelo de resposta a
perguntas.

Um modelo típico de resposta a perguntas tem duas entradas: uma pergunta de linguagem
natural e um trecho de texto (como um artigo de notícias) que fornece informações a serem
usadas para responder à pergunta. O modelo deve então produzir uma resposta: na
configuração mais simples possível, esta é uma resposta de uma palavra obtida através de um
softmax sobre algum vocabulário pré-definido (ver figura 7.6 ).
Figura 7.6 Um modelo de perguntas e respostas

A seguir, um exemplo de como você pode construir um modelo desse tipo com a API
funcional. Você configura dois ramos independentes, codificando a entrada de texto e a entrada
de pergunta como vetores de representação; então, concatenar esses vetores; e, finalmente,
adicione um classificador softmax no topo das representações concatenadas.

Listagem 7.1. Implementação de API funcional de um modelo de resposta a perguntas de duas entradas

da importação de keras.models

das camadas de importação keras

de importação de keras

text_vocabulary_size = 10000

question_vocabulary_size = 10000

answer_vocabulary_size = 500

text_input = Entrada (forma = (Nenhum,), dtype = 'int32', nome = 'texto')


1

embedded_text = layers.Embedding (

64, text_vocabulary_size) (text_input) 2

encoded_text = layers.LSTM (32) (embedded_text) 3

question_input = Entrada (forma = (None,),

dtype = 'int32'
name = 'question') 4

embedded_question = layers.Embedding (

32, question_vocabulary_size) (question_input)

encoded_question = layers.LSTM (16) (embedded_question)

concatenado = layers.concatenate ([encoded_text, encoded_question],

eixo = -1) 5

answer = layers.Dense (answer_vocabulary_size,

ativação = 'softmax') (concatenado) 6

model = Model ([text_input, question_input], answer) 7

model.compile (optimizer = 'rmsprop',

perda = 'categorical_crossentropy',

metrics = ['acc'])

 1 A entrada de texto é uma seqüência de comprimento inteiro de


inteiros. Observe que você pode, opcionalmente, nomear as entradas.
 2 Incorpora as entradas em uma sequência de vetores de tamanho 64
 3 Codifica os vetores em um único vetor por meio de um LSTM
 4 O mesmo processo (com diferentes instâncias de camada) para a questão
 5 Concatena a pergunta codificada e o texto codificado
 6 Adiciona um classificador softmax no topo
 7 Na instanciação do modelo, você especifica as duas entradas e a saída.

Agora, como você treina esse modelo de duas entradas? Existem duas APIs possíveis: você pode
fornecer ao modelo uma lista de matrizes Numpy como entradas ou pode alimentá-lo com um
dicionário que mapeia nomes de entrada para matrizes Numpy. Naturalmente, a última opção
só está disponível se você der nomes às suas entradas.

Listagem 7.2. Dados de alimentação para um modelo de múltiplas entradas

import numpy como np

num_samples = 1000

max_length = 100

text = np.random.randint (1, text_vocabulary_size,


tamanho = (num_samples, max_length))
1

question = np.random.randint (1, question_vocabulary_size,

size = (num_samples, max_length))

respostas = np.random.randint (0, 1,

size = (num_samples, answer_vocabulary_size))


2

model.fit ([texto, pergunta], respostas, épocas = 10, batch_size = 128)


3

model.fit ({'text': text, 'question': pergunta}, respostas,


4

epochs = 10, batch_size = 128)


4

 1 Gera dados fictícios do Numpy


 2 Respostas são um-quente codificado, não inteiros
 3 Ajuste usando uma lista de entradas
 4 Ajuste usando um dicionário de entradas (somente se as entradas forem
nomeadas)

7.1.3. Modelos multi-saída


Da mesma forma, você pode usar a API funcional para criar modelos com várias saídas (ou
várias cabeças ). Um exemplo simples é uma rede que tenta prever simultaneamente
propriedades diferentes dos dados, como uma rede que recebe como entrada uma série de
postagens de mídias sociais de uma única pessoa anônima e tenta prever atributos dessa pessoa,
como idade, sexo e nível de renda (ver figura 7.7 ).

Figura 7.7. Um modelo de mídia social com três cabeças

Listagem 7.3. Implementação de API funcional de um modelo de três saídas

das camadas de importação keras

de importação de keras

da importação de keras.models
vocabulary_size = 50000

num_income_groups = 10

posts_input = Entrada (forma = (None,), dtype = 'int32', nome = 'posts')

embedded_posts = layers.Embedding (256, vocabulary_size) (posts_input)

x = layers.Conv1D (128, 5, ativação = 'relu') (embedded_posts)

x = layers.MaxPooling1D (5) (x)

x = layers.Conv1D (256, 5, ativação = 'relu') (x)

x = layers.Conv1D (256, 5, ativação = 'relu') (x)

x = layers.MaxPooling1D (5) (x)

x = layers.Conv1D (256, 5, ativação = 'relu') (x)

x = layers.Conv1D (256, 5, ativação = 'relu') (x)

x = layers.GlobalMaxPooling1D () (x)

x = layers.Dense (128, ativação = 'relu') (x)

age_prediction = layers.Dense (1, name = 'age') (x) 1

income_prediction = layers.Dense (num_income_groups,

ativação = 'softmax',

nome = 'rendimento') (x)

gender_prediction = layers.Dense (1, ativação = 'sigmoid', name = 'gender')


(x)

model = Model (posts_input,

[idade_predição, predição da renda, predicção de gênero])

 1 Observe que as camadas de saída recebem nomes.

É importante ressaltar que o treinamento desse modelo requer a capacidade de especificar


diferentes funções de perda para diferentes chefes de rede: por exemplo, a previsão de idade é
uma tarefa de regressão escalar, mas a predição de gênero é uma tarefa de classificação binária,
exigindo um procedimento de treinamento diferente. Mas como o gradiente descendente requer
que você minimize um escalar , você deve combinar essas perdas em um único valor para
treinar o modelo. A maneira mais simples de combinar perdas diferentes é somar todas elas. Em
Keras, você pode usar uma lista ou um dicionário de perdas compilepara especificar objetos
diferentes para saídas diferentes; os valores das perdas resultantes são somados em uma perda
global, que é minimizada durante o treinamento.

Listagem 7.4. Opções de compilação de um modelo multi-output: múltiplas perdas

model.compile (optimizer = 'rmsprop',


loss = ['mse', 'categorical_crossentropy',
'binary_crossentropy'])

model.compile (optimizer = 'rmsprop', 1

perda = {'idade': 'mse', 1

'receita': 'categorical_crossentropy', 1

'sexo': 'binary_crossentropy'}) 1

 1 Equivalente (possível somente se você der nomes às camadas de saída)

Note que as contribuições de perda muito desequilibradas farão com que as representações do
modelo sejam otimizadas preferencialmente para a tarefa com a maior perda individual, às
custas das outras tarefas. Para remediar isso, você pode atribuir diferentes níveis de importância
aos valores de perda em sua contribuição para a perda final. Isto é útil, em particular, se os
valores das perdas usarem diferentes escalas. Por exemplo, a perda de erro quadrático médio
(MSE) usada para a tarefa de regressão de idade normalmente leva um valor em torno de 3–5,
enquanto a perda de entropia cruzada usada para a tarefa de classificação de gênero pode ser tão
baixa quanto 0,1. Nessa situação, para equilibrar a contribuição das diferentes perdas, você pode
atribuir um peso de 10 à perda de crossentropy e um peso de 0,25 à perda de MSE.

Listagem 7.5. Opções de compilação de um modelo de múltiplas saídas: ponderação de perda

model.compile (optimizer = 'rmsprop',

loss = ['mse', 'categorical_crossentropy',


'binary_crossentropy'],

loss_weights = [0,25, 1, 10.])

model.compile (optimizer = 'rmsprop', 1

perda = {'age': 'mse', 1

'income': 'categorical_crossentropy', 1

'gender': 'binary_crossentropy'}, 1

loss_weights = {'age': 0,25 , 1

'rendimento': 1., 1

'sexo': 10.}) 1

 1 Equivalente (possível somente se você der nomes às camadas de saída)

Assim como no caso de modelos com várias entradas, você pode passar dados do Numpy para o
modelo para treinamento, seja por meio de uma lista de matrizes ou por meio de um dicionário
de matrizes.

Listagem 7.6. Dados de alimentação para um modelo de múltiplas saídas

model.fit (posts, [age_targets, income_targets, gender_targets], 1

epochs = 10, batch_size = 64)


model.fit (posts, {'age': age_targets, 2

'income': income_targets, 2

'gender': gender_targets}, 2

epochs = 10, batch_size = 64) 2

 1 age_targets, income_targets e gender_targets são considerados matrizes


Numpy.
 2 Equivalente (possível somente se você der nomes às camadas de saída)

7.1.4. Gráficos acíclicos direcionados de camadas


Com a API funcional, você não apenas pode criar modelos com várias entradas e várias saídas,
mas também pode implementar redes com uma topologia interna complexa. As redes neurais
em Keras podem ser grafos acíclicos de camadas arbitrariamente dirigidos . O
qualificador acíclico é importante: esses gráficos não podem ter ciclos. É impossível que um
tensor xse torne a entrada de uma das camadas geradas x. Os únicos loops
de processamento permitidos (isto é, conexões recorrentes) são aqueles internos a camadas
recorrentes.

Vários componentes comuns da rede neural são implementados como gráficos. Dois notáveis
são módulos de Iniciação e conexões residuais. Para entender melhor como a API funcional
pode ser usada para construir gráficos de camadas, vamos dar uma olhada em como você pode
implementar ambas em Keras.

Módulos de criação

Inception [ 3 ] é um tipo popular de arquitetura de rede para redes neurais convolucionais; foi
desenvolvido por Christian Szegedy e seus colegas do Google em 2013-2014,
inspirado na arquitetura anterior de rede em rede . [ 4 ]Consiste em uma pilha de módulos que se
parecem com pequenas redes independentes, divididas em vários ramos paralelos. A forma mais
básica de um módulo de Iniciação tem de três a quatro ramificações começando com uma
convolução de 1 × 1, seguida por uma convolução 3 × 3 e terminando com a concatenação dos
recursos resultantes. Essa configuração ajuda a rede a aprender separadamente os recursos
espaciais e os recursos do canal, o que é mais eficiente do que aprendê-los em conjunto. Versões
mais complexas de um módulo de criação também são possíveis, geralmente envolvendo
operações de pooling, diferentes tamanhos de convolução espacial (por exemplo, 5 × 5 em vez de
3 × 3 em algumas ramificações) e ramificações sem uma convolução espacial (somente 1 × 1
convolução). Um exemplo de tal módulo, tirado do Inception V3, é mostrado emfigura 7.8 .

https://arxiv.org/abs/1409.4842 .

Min Lin, Qiang Chen e Shuicheng Yan, “Rede em Rede”, Conferência Internacional sobre Representações de Aprendizagem

(2013),https://arxiv.org/abs/1312.4400 .
Figura 7.8. Um módulo de iniciação

O propósito de 1 × 1 convoluções

Você já sabe que convoluções extraem patches espaciais ao redor de cada bloco em um tensor de
entrada e aplicam a mesma transformação a cada patch. Um caso extremo é quando os patches
extraídos consistem em um único bloco. A operação de convolução se torna equivalente a
executar cada vetor de bloco por meio de uma Densecamada: ele computará recursos que
combinam informações dos canais do tensor de entrada, mas não misturará informações no
espaço (porque ele está observando um bloco de cada vez) ). Tais convoluções 1 × 1 (também
chamadas de convoluções pontuais) são apresentados nos módulos de Iniciação, onde eles
contribuem para fatorar o aprendizado de recurso em termos de canal e aprendizado de recurso
espacial - uma coisa razoável a ser feita se você assumir que cada canal é altamente
autocorrelacionado no espaço, mas canais diferentes podem não ser altamente correlacionados
um com o outro.

Veja como você implementaria o módulo apresentado na figura 7.8 usando a API funcional. Este
exemplo assume a existência de um tensor de entrada 4D x:

das camadas de importação keras

branch_a = layers.Conv2D (128, 1,

ativação = 'relu', strides = 2) (x)


1

branch_b = layers.Conv2D (128, 1, ativação = 'relu') (x)


2

branch_b = layers.Conv2D (128, 3, ativação = 'relu', passadas = 2) (branch_b)


2

branch_c = layers.AveragePooling2D (3, strides = 2) (x)


3
branch_c = camadas.Conv2D (128, 3, ativação = 'relu') (branch_c)
3

branch_d = layers.Conv2D (128, 1, ativação = 'relu') (x)

branch_d = layers.Conv2D (128, 3, ativação = 'relu') (branch_d)

branch_d = layers.Conv2D (128, 3, ativação = 'relu', strides = 2) (branch_d)

output = layers.concatenate (

[branch_a, branch_b, branch_c, branch_d], eixo = -1)


4

 1 Cada ramificação possui o mesmo valor de passada (2), o que é necessário


para manter todas as saídas de ramificação do mesmo tamanho para que
você possa concatená-las.
 2 Neste ramo, o striding ocorre na camada de convolução espacial.
 3 Neste ramo, o striding ocorre na camada de pool média.
 4 Concatena as saídas de ramificação para obter a saída do módulo

Observe que a arquitetura completa do Inception V3 está disponível em Keras


como keras.applications.inception_v3.InceptionV3, incluindo pesos pré-criados
no conjunto de dados do ImageNet. Outro modelo estreitamente relacionado disponível como
parte do módulo de aplicativos Keras é o Xception . [ 5 ] Xception, que significa início extremo, é
uma arquitetura convnet vagamente inspirada pelo Inception. Ele leva a idéia de separar o
aprendizado de recursos sábios e espaciais ao seu extremo lógico e substitui os módulos
Inception por convoluções separáveis em profundidade que consistem em uma convolução em
profundidade (uma convolução espacial onde cada canal de entrada é tratado separadamente)
seguida por um convolução pontual (uma convolução de 1 × 1) - efetivamente, uma forma
extrema de um módulo de Iniciação, em que os recursos espaciais e os recursos do canal são
totalmente separados. O Xception tem aproximadamente o mesmo número de parâmetros que o
Inception V3, mas mostra melhor desempenho no tempo de execução e maior precisão no
ImageNet, bem como outros conjuntos de dados de grande escala, devido a um uso mais
eficiente dos parâmetros do modelo.

François Chollet, “Xception: Aprendizado Profundo com Convoluções Separáveis Profundas”, Conferência sobre Visão Computacional e

Reconhecimento de Padrões (2017), https://arxiv.org/abs/1610.02357 .

Conexões residuais

As conexões residuais são um componente de rede semelhante a um gráfico comum encontrado


em muitas arquiteturas de rede pós-2015, incluindo o Xception. Eles foram introduzidos por He
et al. da Microsoft em sua entrada vitoriosa no desafio ILSVRC ImageNet no final de
2015. [ 6 ] Eles abordam dois problemas comuns que afetam qualquer modelo de aprendizagem
profunda em grande escala: gradientes desaparecidos e gargalos representacionais. Em geral,
adicionar conexões residuais a qualquer modelo que tenha mais de 10 camadas provavelmente
será benéfico.

He et al., “Aprendizado Residual Profundo para Reconhecimento de Imagem”, https://arxiv.org/abs/1512.03385 .


Uma conexão residual consiste em disponibilizar a saída de uma camada anterior como entrada
para uma camada posterior, criando efetivamente um atalho em uma rede seqüencial. Em vez
de ser concatenada à ativação posterior, a saída anterior é somada à ativação posterior, que
pressupõe que ambas as ativações sejam do mesmo tamanho. Se tiverem tamanhos diferentes,
você poderá usar uma transformação linear para remodelar a ativação anterior na forma de
destino (por exemplo, uma Densecamada sem uma ativação ou, para mapas de características
convolucionais, uma convolução de 1 x 1 sem uma ativação).

Veja como implementar uma conexão residual em Keras quando os tamanhos do mapa de
recursos forem os mesmos, usando conexões residuais de identidade. Este exemplo assume a
existência de um tensor de entrada 4D x:

das camadas de importação keras

x = ...

y = camadas.Conv2D (128, 3, ativação = 'relu', preenchimento = 'mesmo') (x)


1

y = layers.Conv2D (128, 3, ativação = 'relu', preenchimento = 'same') (y)

y = layers.Conv2D (128, 3, ativação = 'relu', preenchimento = 'same') (y)

y = layers.add ([y, x]) 2

 1 Aplica uma transformação para x


 2 Adiciona o x original de volta aos recursos de saída

E o seguinte implementa uma conexão residual quando os tamanhos do mapa de características


diferem, usando uma conexão residual linear (novamente, assumindo a existência de um tensor
de entrada 4D x):

das camadas de importação keras

x = ...

y = layers.Conv2D (128, 3, ativação = 'relu', padding = 'same') (x)

y = layers.Conv2D (128, 3, ativação = 'relu', preenchimento = 'same') (y)

y = layers.MaxPooling2D (2, strides = 2) (y)

residual = layers.Conv2D (128, 1, strides = 2, padding = 'same') (x) 1

y = layers.add ([y, residual]) 2

 1 Usa uma convolução de 1 × 1 para diminuir linearmente o tensor x


original para a mesma forma que y
 2 Adiciona o tensor residual de volta aos recursos de saída
Gargalos Representacionais na Aprendizagem Profunda

Em um Sequentialmodelo, cada camada de representação sucessiva é construída sobre a


anterior, o que significa que só tem acesso às informações contidas na ativação da camada
anterior. Se uma camada é muito pequena (por exemplo, possui recursos que são muito baixa-
dimensional), então o modelo será limitado pela quantidade de informação que pode ser
comprimida nas ativações dessa camada.

Você pode entender esse conceito com uma analogia de processamento de sinais: se você tem
um pipeline de processamento de áudio que consiste em uma série de operações, cada uma das
quais toma como entrada a saída da operação anterior, então se uma operação corta seu sinal
para um faixa de baixa freqüência (por exemplo, 0-15 kHz), as operações a jusante nunca serão
capazes de recuperar as freqüências perdidas. Qualquer perda de informação é permanente. As
conexões residuais, ao reinjetar informações anteriores a jusante, resolvem parcialmente esse
problema para modelos de aprendizagem profunda.

Gradientes de desaparecimento em aprendizagem profunda

A retropropagação, o algoritmo mestre usado para treinar redes neurais profundas, funciona
propagando um sinal de feedback da perda de saída para camadas anteriores. Se esse sinal de
realimentação tiver que ser propagado por meio de uma pilha profunda de camadas, o sinal
pode se tornar tênue ou até mesmo ser totalmente perdido, tornando a rede inatingível. Esse
problema é conhecido como gradientes de desaparecimento .

Esse problema ocorre tanto com redes profundas quanto com redes recorrentes em seqüências
muito longas - em ambos os casos, um sinal de feedback deve ser propagado por meio de uma
longa série de operações. Você já está familiarizado com a solução que a LSTMcamada usa para
resolver esse problema em redes recorrentes: ela introduz uma trilha de transporte que propaga
informações paralelas à trilha de processamento principal. Conexões residuais funcionam de
maneira semelhante em redes deep feedforward, mas são ainda mais simples: elas introduzem
uma informação puramente linear que acompanha a pilha da camada principal, ajudando a
propagar gradientes através de pilhas de camadas arbitrariamente profundas.

7.1.5. Compartilhamento de peso de camada


Um recurso mais importante da API funcional é a capacidade de reutilizar uma instância de
camada várias vezes. Quando você chama uma instância de camada duas vezes, em vez de
instanciar uma nova camada para cada chamada, você reutiliza os mesmos pesos em todas as
chamadas. Isso permite que você construa modelos que tenham ramificações compartilhadas -
várias ramificações que compartilham o mesmo conhecimento e executam as mesmas
operações. Ou seja, eles compartilham as mesmas representações e aprendem essas
representações simultaneamente para diferentes conjuntos de entradas.

Por exemplo, considere um modelo que tenta avaliar a semelhança semântica entre duas
sentenças. O modelo tem duas entradas (as duas sentenças para comparar) e gera uma
pontuação entre 0 e 1, em que 0 significa sentenças não relacionadas e 1 significa sentenças que
são idênticas ou reformulações uma da outra. Esse modelo pode ser útil em muitos aplicativos,
incluindo deduplicação de consultas em linguagem natural em um sistema de diálogo.

Nesta configuração, as duas sentenças de entrada são intercambiáveis, porque semelhança


semântica é uma relação simétrica: a semelhança de A para B é idêntica à semelhança de B para
A. Por esse motivo, não faria sentido aprender dois modelos independentes para processar cada
sentença de entrada. Em vez disso, você quer processar ambos com uma única LSTMcamada. As
representações dessa LSTMcamada (seus pesos) são aprendidas com base nas duas entradas
simultaneamente. Isso é o que chamamos de modelo LSTM siamês ou um LSTM
compartilhado .

Veja como implementar esse modelo usando o compartilhamento de camada (reutilização de


camada) na API funcional Keras:

das camadas de importação keras

de importação de keras

da importação de keras.models

lstm = layers.LSTM (32) 1

left_input = Entrada (forma = (Nenhum, 128))


2

left_output = lstm (entrada esquerda)


2

right_input = Entrada (forma = (Nenhum, 128))


3

right_output = lstm (right_input) 3

mesclado = layers.concatenate ([left_output, right_output], axis = -1) 4

previsões = layers.Dense (1, activation = 'sigmoid') (mesclado) 4

model = Model ([left_input, right_input], previsões) 5

model.fit ([left_data, right_data], targets) 5

 1 Instancia uma única camada LSTM, uma vez


 2 Construindo o ramo esquerdo do modelo: as entradas são seqüências de
tamanho variável de vetores de tamanho 128.
 3 Construindo o ramo direito do modelo: quando você chama uma
instância de camada existente, você reutiliza seus pesos.
 4 Constrói o classificador no topo
 5 Instanciar e treinar o modelo: quando você treina esse modelo, os pesos
da camada LSTM são atualizados com base nas duas entradas.

Naturalmente, uma instância de camada pode ser usada mais de uma vez - ela pode ser
chamada arbitrariamente várias vezes, reutilizando o mesmo conjunto de pesos todas as vezes.

7.1.6. Modelos como camadas


É importante ressaltar que, na API funcional, os modelos podem ser usados como você usaria
camadas - na verdade, você pode pensar em um modelo como uma "camada maior". Isso é
verdadeiro para as classes Sequentiale Model. Isso significa que você pode chamar um
modelo em um tensor de entrada e recuperar um tensor de saída:
y = modelo (x)

Se o modelo possui múltiplos tensores de entrada e múltiplos tensores de saída, ele deve ser
chamado com uma lista de tensores:

y1, y2 = modelo ([x1, x2])

Quando você chama uma instância de modelo, você reutiliza os pesos do modelo, exatamente
como acontece quando você chama uma instância de camada. Chamar uma instância, seja uma
instância de camada ou uma instância de modelo, sempre reutilizará as representações
aprendidas existentes da instância - o que é intuitivo.

Um exemplo prático simples do que você pode construir reutilizando uma instância de modelo é
um modelo de visão que usa uma câmera dupla como entrada: duas câmeras paralelas,
separadas por alguns centímetros (uma polegada). Tal modelo pode perceber profundidade, o
que pode ser útil em muitas aplicações. Você não deve precisar de dois modelos independentes
para extrair recursos visuais da câmera esquerda e da câmera direita antes de mesclar os dois
feeds. Esse processamento de baixo nível pode ser compartilhado entre as duas entradas: isto é,
feito por camadas que usam os mesmos pesos e, portanto, compartilham as mesmas
representações. Veja como você implementaria um modelo de visão siamês (base convolucional
compartilhada) em Keras:

das camadas de importação keras

de aplicativos de importação keras

de importação de keras

xception_base = applications.Xception (pesos = Nenhum,

include_top = Falso) 1

left_input = Entrada (forma = (250, 250, 3)) 2

right_input = Entrada (forma = (250, 250, 3)) 2

left_features = xception_base (left_input) 3

right_input = xception_base (right_input) 3 right_input = xception_base


(entrada_direita) 3

merged_features = layers.concatenate (

[left_features, right_input], eixo = -1) 4

 1 O modelo básico de processamento de imagem é a rede Xception (apenas


base convolucional).
 2 As entradas são imagens RGB de 250 × 250.
 3 Chama o mesmo modelo de visão duas vezes
 4 Os recursos mesclados contêm informações do feed visual direito e do
feed visual esquerdo.
7.1.7. Empacotando
Isso conclui nossa introdução à API funcional Keras - uma ferramenta essencial para a
construção de arquiteturas avançadas de redes neurais profundas. Agora você sabe o seguinte:

 Para sair da SequentialAPI sempre que precisar de algo mais do que uma pilha linear
de camadas
 Como construir modelos Keras com várias entradas, várias saídas e topologia de rede
interna complexa, usando a API funcional Keras
 Como reutilizar os pesos de uma camada ou modelo em diferentes ramificações de
processamento, chamando a mesma camada ou instância de modelo várias vezes

7.2. INSPEÇÃO E MONITORAMENTO DE MODELOS DE APRENDIZADO


PROFUNDO USANDO KERAS CALLBAKKS E TENSORBOARD

Nesta seção, analisaremos maneiras de obter maior acesso e controle sobre o que acontece
dentro de seu modelo durante o treinamento. Lançar uma corrida de treinamento em um
grande conjunto de dados para dezenas de épocas
usando model.fit()ou model.fit_generator()pode ser um pouco como lançar um avião
de papel: além do impulso inicial, você não tem nenhum controle sobre sua trajetória ou seu
ponto de pouso. Se você quiser evitar resultados ruins (e, portanto, desperdiçar aviões de papel),
é mais inteligente usar não um avião de papel, mas um drone capaz de detectar seu ambiente,
enviar dados para seu operador e tomar decisões de direção automaticamente com base em seu
estado atual . As técnicas que apresentamos aqui transformarão a chamada model.fit()de
um avião de papel em um drone inteligente e autônomo que pode se auto-introspectar e agir
dinamicamente.

7.2.1. Usando callbacks para atuar em um modelo durante o


treinamento
Quando você está treinando um modelo, há muitas coisas que você não pode prever desde o
começo. Em particular, você não pode dizer quantas épocas serão necessárias para obter uma
perda de validação ideal. Os exemplos até agora adotaram a estratégia de treinar para épocas
suficientes que você começa overfitting, usando a primeira corrida para descobrir o número
adequado de épocas para treinar e, finalmente, lançando uma nova corrida de treinamento a
partir do zero usando este número ideal. Claro, essa abordagem é um desperdício.

Uma maneira muito melhor de lidar com isso é parar o treinamento quando você mede que a
perda de validação não está mais melhorando. Isso pode ser conseguido usando um retorno de
chamada Keras. Um retorno de chamada é um objeto (uma instância de classe que implementa
métodos específicos) que é passado para o modelo na chamada fite que é chamado pelo
modelo em vários pontos durante o treinamento. Ele tem acesso a todos os dados disponíveis
sobre o estado do modelo e seu desempenho, e pode agir: interromper o treinamento, salvar um
modelo, carregar um conjunto de pesos diferente ou alterar o estado do modelo.

Aqui estão alguns exemplos de maneiras de usar os retornos de chamada:

 Checkpointing de modelo - Salvando os pesos atuais do modelo em diferentes


pontos durante o treinamento.
 Parada antecipada - Interromper o treinamento quando a perda de validação não
estiver mais melhorando (e, claro, salvar o melhor modelo obtido durante o
treinamento).
 Ajustar dinamicamente o valor de determinados parâmetros durante o
treinamento - como a taxa de aprendizado do otimizador.
 Registrando as métricas de treinamento e validação durante o
treinamento, ou visualizando as representações aprendidas pelo modelo
conforme elas são atualizadas - A barra de progresso do Keras com a qual você
está familiarizado é um retorno de chamada!

O keras.callbacksmódulo inclui vários retornos de chamada internos (isso não é uma lista
exaustiva):

keras.callbacks.ModelCheckpoint

keras.callbacks.EarlyStopping

keras.callbacks.LearningRateScheduler

keras.callbacks.ReduceLROnPlateau

keras.callbacks.CSVLogger

Vamos rever alguns deles para lhe dar uma idéia de como usá-
los: ModelCheckpoint, EarlyStopping, e ReduceLROnPlateau.

Os retornos de chamada do ModelCheckpoint e EarlyStopping

Você pode usar o EarlyStoppingretorno de chamada para interromper o treinamento quando


a métrica de destino que está sendo monitorada tiver parado de melhorar por um número fixo
de épocas. Por exemplo, este retorno de chamada permite que você interrompa o treinamento
assim que você começar o overfitting, evitando assim ter que retreinar seu modelo para um
número menor de épocas. Esse retorno de chamada é normalmente usado em combinação
com ModelCheckpoint, o que permite salvar continuamente o modelo durante o treinamento
(e, opcionalmente, salvar apenas o melhor modelo atual até o momento: a versão do modelo que
obteve o melhor desempenho no final de uma época):

importar keras

callbacks_list = [ 1

keras.callbacks.EarlyStopping ( 2

monitor = 'acc', 3

paciência = 1, 4

keras.callbacks.ModelCheckpoint ( 5 filepath

= 'my_model.h5', 6

monitor = 'val_loss', 7

save_best_only = True, 7

model.compile (optimizer = 'rmsprop',

perda = 'binary_crossentropy',
métricas = ['acc']) 8

model.fit (x, y, 9

epochs = 10, 9

batch_size = 32, 9

callbacks = callbacks_list, 9

validation_data = (x_val, y_val)) 9

 1 Os retornos de chamada são passados para o modelo por meio do


argumento de callbacks em ajuste, que recebe uma lista de retornos de
chamada. Você pode passar qualquer número de retornos de chamada.
 2 Interrompe o treinamento quando a melhoria é interrompida
 3 Monitora a precisão de validação do modelo
 4 Interrompe o treinamento quando a precisão parou de melhorar por
mais de uma época (isto é, duas épocas)
 5 Salva os pesos atuais após cada época
 6 Caminho para o arquivo de modelo de destino
 7 Estes dois argumentos significam que você não irá sobrescrever o arquivo
do modelo, a menos que o val_loss tenha melhorado, o que permite que
você mantenha o melhor modelo visto durante o treinamento.
 8 Você monitora a precisão, por isso deve fazer parte das métricas do
modelo.
 9 Observe que, como o retorno de chamada monitora a perda de validação e
a precisão da validação, você precisa passar validation_data para a
chamada a ser ajustada.

O retorno de chamada do ReduceLROnPlateau

Você pode usar esse retorno de chamada para reduzir a taxa de aprendizado quando a perda de
validação parar de melhorar. Reduzir ou aumentar a taxa de aprendizado em caso de perda de
platô é uma estratégia eficaz para sair dos mínimos locais durante o treinamento. O exemplo a
seguir usa o ReduceLROnPlateauretorno de chamada:

callbacks_list = [

keras.callbacks.ReduceLROnPlateau (

monitor = 'val_loss' 1

fator = 0.1, 2

paciência = 10, 3

model.fit (x, y, 4

epochs = 10, 4

batch_size = 32, 4
callbacks = callbacks_list, 4

validation_data = (x_val, y_val)) 4

 1 Monitora a perda de validação do modelo


 2 Divide a taxa de aprendizado em 10 quando acionado
 3 O retorno de chamada é acionado depois que a perda de validação parou
de melhorar por 10 épocas.
 4 Como o retorno de chamada monitorará a perda de validação, você
precisará passar validation_data para a chamada para ajustar.

Escrevendo seu próprio retorno de chamada

Se você precisar executar uma ação específica durante o treinamento que não esteja coberta por
um dos retornos de chamada internos, poderá escrever seu próprio retorno de
chamada. Callbacks são implementados por meio da subclasse da
classe keras.callbacks.Callback. Você pode então implementar qualquer número dos
seguintes métodos nomeados de forma transparente, que são chamados em vários pontos
durante o treinamento:

on_epoch_begin 1

on_epoch_end 2

on_batch_begin 3

on_batch_end 4

on_train_begin 5

on_train_end 6

 1 Chamado no início de cada época


 2 Chamado no final de cada época
 3 Chamado logo antes de processar cada lote
 4 Chamado logo após o processamento de cada lote
 5 Chamado no início do treinamento
 6 Chamado no final do treinamento

Todos esses métodos são chamados com um logsargumento, que é um dicionário que contém
informações sobre o lote, época ou treinamento anterior: métricas de treinamento e validação e
assim por diante. Além disso, o retorno de chamada tem acesso aos seguintes atributos:

 self.model- A instância do modelo a partir da qual o retorno de chamada está sendo


chamado
 self.validation_data—O valor do que foi passado fitcomo dados de validação

Aqui está um exemplo simples de um retorno de chamada personalizado que salva em disco
(como matrizes Numpy) as ativações de cada camada do modelo no final de cada época,
calculado na primeira amostra do conjunto de validação:

importar keras

import numpy como np


classe ActivationLogger (keras.callbacks.Callback):

def set_model (self, model):

self.model = modelo 1

layer_outputs = [layer.output para camada em model.layers]

self.activations_model = keras.models.Model (model.input,

layer_outputs) 2

def on_epoch_end (auto, época, registros = Nenhum):

se self.validation_data for None:

raise RuntimeError ('Requer dados_da_impressao.')

validation_sample = self.validation_data [0] [0: 1] 3

ativations = self.activations_model.predict (validation_sample)

f = aberto ('activations_at_epoch_' + str (época) + '.npz', 'w')


4

np.savez (f, ativações) 4

f.close () 4

 1 Chamado pelo modelo pai antes do treinamento, para informar o retorno


de chamada de qual modelo será chamado
 2 Instância do modelo que retorna as ativações de todas as camadas
 3 Obtém a primeira amostra de entrada dos dados de validação
 4 Salva matrizes no disco

Isso é tudo o que você precisa saber sobre os retornos de chamada - o resto são detalhes
técnicos, que você pode facilmente consultar. Agora você está equipado para realizar qualquer
tipo de registro ou intervenção pré-programada em um modelo Keras durante o treinamento.

7.2.2. Introdução ao TensorBoard: o framework de visualização do


TensorFlow
Para fazer uma boa pesquisa ou desenvolver bons modelos, você precisa de um feedback rico e
frequente sobre o que está acontecendo dentro de seus modelos durante seus
experimentos. Esse é o objetivo de executar experimentos: obter informações sobre o
desempenho de um modelo - o máximo possível de informações. Fazer progresso é um processo
ou loop iterativo: você começa com uma ideia e a expressa como um experimento, tentando
validar ou invalidar sua ideia. Você executa essa experiência e processa as informações
geradas. Isso inspira sua próxima ideia. Quanto mais iterações desse loop você conseguir
executar, mais refinadas e poderosas suas ideias se tornarão. O Keras ajuda você a passar da
ideia para o experimento no menor tempo possível, e as GPUs rápidas podem ajudá-lo a sair do
experimento para o resultado o mais rápido possível. Mas e quanto ao processamento dos
resultados da experiência? É aí que entra o TensorBoard.

Figura 7.9. O loop do progresso

Esta seção apresenta o TensorBoard, uma ferramenta de visualização baseada em navegador


que acompanha o TensorFlow. Observe que ele está disponível apenas para modelos Keras
quando você estiver usando o Keras com o backend TensorFlow.

O objetivo principal do TensorBoard é ajudá-lo a monitorar visualmente tudo o que acontece


dentro do seu modelo durante o treinamento. Se você está monitorando mais informações do
que apenas a perda final do modelo, você pode desenvolver uma visão mais clara do que o
modelo faz e o que não faz, e você pode progredir mais rapidamente. O TensorBoard oferece
acesso a vários recursos interessantes, tudo no seu navegador:

 Visualmente monitorando métricas durante o treinamento


 Visualizando sua arquitetura de modelo
 Visualizando histogramas de ativações e gradientes
 Explorando os embeddings em 3D

Vamos demonstrar esses recursos em um exemplo simples. Você treinará uma conv. 1D na
tarefa de análise de sentimentos do IMDB.

O modelo é semelhante ao que você viu na última seção do capítulo 6 . Você considerará apenas
as 2.000 palavras no vocabulário do IMDB, para tornar mais fácil a visualização de encartes de
palavras.

Listagem 7.7. Modelo de classificação de texto para usar com o TensorBoard

importar keras

das camadas de importação keras

de keras.datasets import imdb

da sequência de importação keras.preprocessing

max_features = 2000 1

max_len = 500 2

(x_train, y_train), (x_test, y_test) = imdb.load_data (num_words =


max_features)
x_train = sequence.pad_sequences (x_train, maxlen = max_len)

x_test = sequence.pad_sequences (x_test, maxlen = max_len)

model = keras.models.Sequential ()

model.add (layers.Embedding (max_features, 128,

input_length = max_len,

nome = 'embed'))

model.add (layers.Conv1D (32, 7, ativação = 'relu'))

model.add (layers.MaxPooling1D (5))

model.add (layers.Conv1D (32, 7, ativação = 'relu'))

model.add (layers.GlobalMaxPooling1D ())

model.add (layers.Dense (1))

model.summary ()

model.compile (optimizer = 'rmsprop',

perda = 'binary_crossentropy',

metrics = ['acc'])

 1 Número de palavras a considerar como recursos


 2 Corta textos após este número de palavras (entre as palavras mais
comuns max_features)

Antes de começar a usar o TensorBoard, você precisa criar um diretório onde armazene os
arquivos de log gerados.

Listagem 7.8. Criando um diretório para arquivos de log do TensorBoard

$ mkdir my_log_dir

Vamos iniciar o treinamento com uma TensorBoardinstância de retorno de chamada. Esse


retorno de chamada gravará eventos de log no disco no local especificado.

Listagem 7.9. Treinando o modelo com um TensorBoardretorno de chamada

retornos de chamada = [

keras.callbacks.TensorBoard (

log_dir = 'my_log_dir', 1

histogram_freq = 1, 2

embeddings_freq = 1, 3

]
history = model.fit (x_train, y_train,

épocas = 20,

batch_size = 128,

validation_split = 0,2,

retornos de chamada = retornos de chamada)

 1 Arquivos de log serão gravados neste local.


 2 Registra histogramas de ativação a cada 1 época
 3 registros que incorporam dados a cada 1 época

Neste ponto, você pode iniciar o servidor TensorBoard a partir da linha de comando, instruindo-
o a ler os registros que o callback está gravando atualmente. O tensorboardutilitário deve ter
sido instalado automaticamente na sua máquina no momento em que você instalou o
TensorFlow (por exemplo, via pip):

$ tensorboard --logdir = my_log_dir

Você pode então navegar para http: // localhost: 6006 e ver o treinamento do seu modelo (veja
a figura 7.10 ). Além de gráficos ao vivo das métricas de treinamento e validação, você obtém
acesso à guia Histogramas, onde você pode encontrar belas visualizações de histogramas de
valores de ativação obtidos por suas camadas (veja a figura 7.11 ).

Figura 7.10. TensorBoard: monitoramento de métricas


Figura 7.11. TensorBoard: histogramas de ativação

A guia Embeddings fornece uma maneira de inspecionar os locais de incorporação e os


relacionamentos espaciais das 10.000 palavras no vocabulário de entrada, conforme aprendido
pela Embeddingcamada inicial . Como o espaço de incorporação é de 128 dimensões, o
TensorBoard o reduz automaticamente para 2D ou 3D usando um algoritmo de redução de
dimensionalidade de sua escolha: análise de componente principal (PCA) ou incorporação de
vizinho estocástico distribuído em t (t-SNE). Na figura 7.12Na nuvem de pontos, você pode ver
claramente dois clusters: palavras com uma conotação positiva e palavras com uma conotação
negativa. A visualização torna imediatamente óbvio que os embeddings treinados em conjunto
com um objetivo específico resultam em modelos que são completamente específicos da tarefa
subjacente - essa é a razão pela qual a incorporação de palavras genéricas pré-tratadas
raramente é uma boa ideia.
Figura 7.12. TensorBoard: visualização interativa 3D de incorporação de palavras

A guia Gráficos mostra uma visualização interativa do gráfico de operações de baixo nível do
TensorFlow subjacente ao seu modelo Keras (consulte a figura 7.13 ). Como você pode ver, há
muito mais acontecendo do que você esperaria. O modelo que você acabou de construir pode
parecer simples quando definido em Keras - uma pequena pilha de camadas básicas - mas, sob o
capô, você precisa construir uma estrutura gráfica bastante complexa para fazê-lo
funcionar. Muito disso está relacionado ao processo gradiente-descendente. Esse diferencial de
complexidade entre o que você vê e o que você está manipulando é a principal motivação para
usar o Keras como sua maneira de construir modelos, em vez de trabalhar com o TensorFlow
bruto para definir tudo do zero. Keras torna seu fluxo de trabalho muito mais simples.
Figura 7.13 TensorBoard: Visualização do gráfico TensorFlow

Note que o Keras também fornece outra maneira mais limpa de plotar modelos como gráficos de
camadas, em vez de gráficos de operações do TensorFlow: o
utilitário keras.utils.plot_model. Usá-lo requer que você tenha instalado o
Python pydote as pydot-ngbibliotecas, assim como a graphvizbiblioteca. Vamos dar uma
olhada rápida:

de keras.utils import plot_model

plot_model (model, to_file = 'model.png')

Isso cria a imagem PNG mostrada na figura 7.14 .


Figura 7.14. Um gráfico de modelo como um gráfico de camadas, gerado com plot_model

Você também tem a opção de exibir informações de forma no gráfico de camadas. Este exemplo
visualiza a topologia de modelo usando plot_modele a show_shapesopção (veja a figura
7.15 ):

de keras.utils import plot_model

plot_model (model, show_shapes = True, to_file = 'model.png')


Figura 7.15 Um gráfico de modelo com informações de forma

7.2.3. Empacotando
 Os callbacks da Keras fornecem uma maneira simples de monitorar modelos durante o
treinamento e executar ações automaticamente com base no estado do modelo.
 Quando você está usando o TensorFlow, o TensorBoard é uma ótima maneira de
visualizar a atividade do modelo em seu navegador. Você pode usá-lo em modelos Keras
através do TensorBoardretorno de chamada.

7.3. APROVEITAR O MELHOR DOS SEUS MODELOS

Experimentar arquiteturas cegas funciona bem o suficiente se você precisar de algo que funcione
bem. Nesta seção, vamos além de "funciona bem" para "funciona muito bem e vence
competições de aprendizado de máquina", oferecendo-lhe um guia rápido para um conjunto de
técnicas indispensáveis para a construção de aprendizado profundo de ponta. modelos.

7.3.1. Padrões de Arquitetura Avançada


Nós cobrimos um importante padrão de projeto em detalhes na seção anterior: conexões
residuais. Existem mais dois padrões de projeto que você deve conhecer: normalização e
convolução separável em profundidade. Esses padrões são especialmente relevantes quando
você está construindo convnets profundos de alto desempenho, mas eles são comumente
encontrados em muitos outros tipos de arquiteturas também.
Normalização em lote

A normalização é uma categoria ampla de métodos que buscam fazer com que diferentes
amostras vistas por um modelo de aprendizado de máquina sejam mais semelhantes entre si, o
que ajuda o modelo a aprender e generalizar bem com novos dados. A forma mais comum de
normalização de dados é aquela que você já viu várias vezes neste livro: centralizar os dados em
0 subtraindo a média dos dados e dando aos dados um desvio padrão da unidade dividindo os
dados por seu desvio padrão. Na verdade, isso pressupõe que os dados seguem uma distribuição
normal (ou gaussiana) e garante que essa distribuição seja centralizada e dimensionada para a
variação da unidade:

normalized_data = (data - np.mean (dados, eixo = ...)) / np.std (dados, eixo =


...)

Exemplos anteriores normalizaram os dados antes de inseri-los nos modelos. Mas normalização
de dados deve ser uma preocupação após cada transformação operada pela rede: mesmo que os
dados entrando em um Denseou Conv2Drede tem uma variação de 0 média e unidade, não há
nenhuma razão para esperar a priori que este será o caso para os dados que saem.

A normalização em lote é um tipo de camada ( BatchNormalizationem Keras) introduzida


em 2015 por Ioffe e Szegedy; [ 7 ] pode normalizar de forma adaptativa os dados, mesmo quando a
média e a variância mudam com o tempo durante o treinamento. Ele trabalha internamente
mantendo uma média móvel exponencial da média e da variância dos dados observados durante
o treinamento. O principal efeito da normalização do lote é que ele ajuda na propagação de
gradiente - muito parecido com conexões residuais - e, portanto, permite redes mais
profundas. Algumas redes muito profundas só podem ser treinadas se incluírem
várias BatchNormalizationcamadas. Por exemplo, BatchNormalizationé usado
generosamente em muitas das arquiteturas convnet avançadas que vêm com o Keras, como
ResNet50, Inception V3 e Xception.

Sergey Ioffe e Christian Szegedy, “Normalização de Lote: Acelerando o Treinamento em Rede Profunda pela Redução da Mudança de

Covariância Interna”, Anais da 32ª Conferência Internacional sobre Aprendizado de Máquina (2015), https://arxiv.org/abs/1502.03167.

A BatchNormalizationcamada é tipicamente usada após uma camada convolucional ou


densamente conectada:

conv_model.add (layers.Conv2D (32, 3, activation = 'relu')) 1

conv_model.add (layers.BatchNormalization ())

dense_model.add (layers.Dense (32, activation = 'relu')) 2

dense_model.add (layers.BatchNormalization ())

 1 Após uma camada Conv


 2 Depois de uma camada densa

A BatchNormalizationcamada recebe um axisargumento, que especifica o eixo do recurso


que deve ser normalizado. Este argumento é padronizado como -1, o último eixo no tensor de
entrada. Esse é o valor correto ao usar Densecamadas, Conv1Dcamadas, camadas RNN
e Conv2Dcamadas com o data_formatdefinido como "channels_last". Mas no caso de uso
de nicho de Conv2Dcamadas com data_formatdefinido para "channels_first", o eixo de
recursos é o eixo 1; o axisargumento BatchNormalizationdeve ser definido como 1.
Renormalização em lote

Uma melhoria recente em relação à normalização de lotes é a renormalização de lotes ,


introduzida pela Ioffe em 2017. [ a ] Ela oferece benefícios claros sobre a normalização de lotes,
sem custo aparente. No momento em que escrevo, é muito cedo para dizer se ele irá suplantar a
normalização em lote - mas acho que é provável. Ainda mais recentemente, Klambauer et
al. introduzidas redes neurais auto-normalizadora , [ b ] que conseguem manter os dados
normalizados depois de passar através de qualquer Densecamada, utilizando uma função de
activação específico ( selu) e um inicializador específico (lecun_normal). Esse esquema,
embora altamente interessante, está limitado a redes densamente conectadas por enquanto, e
sua utilidade ainda não foi amplamente replicada.

uma

Sergey Ioffe, “Renormalização em lote: para reduzir a dependência de minibatch em modelos normalizados em lote”

(2017), https://arxiv.org/abs/1702.03275 .

Günter Klambauer et al., “Redes Neurais Auto-Normalizantes”, Conferência sobre Sistemas de Processamento de Informação Neural

(2017), https://arxiv.org/abs/1706.02515 .

Convolução Separável Profunda

E se eu dissesse que há uma camada que você pode usar como substituto para Conv2Disso, isso
tornará seu modelo mais leve (menos parâmetros de peso treinável) e mais rápido (menos
operações de ponto flutuante) e fará com que ele execute alguns pontos percentuais melhor em
sua tarefa? Isso é precisamente o que a camada de convolução separável em profundidade faz (-
SeparableConv2D). Esta camada executa uma convolução espacial em cada canal de sua
entrada, independentemente, antes de misturar os canais de saída através de uma convolução
pontual (uma convolução de 1 × 1), como mostrado na figura 7.16.. Isso equivale a separar o
aprendizado de recursos espaciais e o aprendizado de recursos de canal, o que faz muito sentido
se você assumir que os locais espaciais na entrada são altamente correlacionados, mas canais
diferentes são bastante independentes. Requer significativamente menos parâmetros eenvolve
menos cálculos, resultando em modelos menores e mais rápidos. E como é uma maneira mais
representacionalmente eficiente de executar a convolução, ela tende a aprender representações
melhores usando menos dados, resultando em modelos com melhor desempenho.
Figura 7.16 Convolução separável em profundidade: uma convolução em profundidade seguida por uma convolução pontual

Essas vantagens se tornam especialmente importantes quando você está treinando modelos
pequenos do zero em dados limitados. Por exemplo, aqui está como você pode criar uma
convnet separável leve e profunda para uma tarefa de classificação de imagem (classificação
categórica softmax) em um pequeno conjunto de dados:

de keras.models import Sequential, Model

das camadas de importação keras

altura = 64

largura = 64

canais = 3

num_classes = 10

model = Sequential ()

model.add (layers.SeparableConv2D (32, 3,

ativação = 'relu',

input_shape = (altura, largura, canais,)))

model.add (layers.SeparableConv2D (64, 3, activation = 'relu'))

model.add (layers.MaxPooling2D (2))

model.add (layers.SeparableConv2D (64, 3, activation = 'relu'))

model.add (layers.SeparableConv2D (128, 3, activation = 'relu'))

model.add (layers.MaxPooling2D (2))


model.add (layers.SeparableConv2D (64, 3, activation = 'relu'))

model.add (layers.SeparableConv2D (128, 3, activation = 'relu'))

model.add (layers.GlobalAveragePooling2D ())

model.add (layers.Dense (32, activation = 'relu'))

model.add (layers.Dense (num_classes, activation = 'softmax'))

model.compile (optimizer = 'rmsprop', loss = 'categorical_crossentropy')

Quando se trata de modelos de larga escala, as convoluções separáveis em profundidade são a


base da arquitetura Xception, uma convnet de alto desempenho que vem com o Keras. Você
pode ler mais sobre o embasamento teórico para convoluções separáveis em profundidade e
Xception em meu artigo “Xception: Deep Learning with Depthwise Separable Convolutions.” [ 8 ]

Veja a nota 5 acima.

7.3.2. Otimização de hiperparâmetros


Ao construir um modelo de aprendizagem profunda, você precisa tomar muitas decisões
aparentemente arbitrárias: quantas camadas você deve empilhar? Quantas unidades ou filtros
devem ir em cada camada? Você deve usar relucomo ativação ou uma função diferente? Você
deve usar BatchNormalizationdepois de uma determinada camada? Quanto dropout você
deve usar? E assim por diante. Esses parâmetros de nível de arquitetura são chamados
de hiperparâmetros para diferenciá-los dos parâmetros de um modelo, que são treinados por
retropropagação.

Na prática, engenheiros e pesquisadores experientes em aprendizado de máquina criam intuição


ao longo do tempo sobre o que funciona e o que não funciona quando se trata dessas escolhas -
eles desenvolvem habilidades de ajuste de hiperparâmetros. Mas não há regras formais. Se você
quer chegar ao limite do que pode ser alcançado em uma determinada tarefa, você não pode se
contentar com escolhas arbitrárias feitas por um humano falível. Suas decisões iniciais são
quase sempre abaixo do ideal, mesmo que você tenha boa intuição. Você pode refinar suas
escolhas aprimorando-as manualmente e treinando novamente o modelo repetidamente - é isso
que os engenheiros e pesquisadores de aprendizado de máquina gastam a maior parte do tempo
fazendo. Mas não deve ser seu trabalho como humano mexer com hiperparâmetros durante
todo o dia - é melhor deixar para uma máquina.

Assim, você precisa explorar o espaço de possíveis decisões automaticamente, sistematicamente,


de uma maneira baseada em princípios. Você precisa pesquisar o espaço da arquitetura e
encontrar os de melhor desempenho empiricamente. É disso que se trata o campo da otimização
automática de hiperparâmetros: é um campo de pesquisa inteiro e importante.

O processo de otimização de hiperparâmetros normalmente se parece com isso:

1. Escolha um conjunto de hiperparâmetros (automaticamente).


2. Construa o modelo correspondente.
3. Ajuste-o aos seus dados de treinamento e meça o desempenho final nos dados de
validação.
4. Escolha o próximo conjunto de hiperparâmetros para tentar (automaticamente).
5. Repetir.
6. Eventualmente, meça o desempenho em seus dados de teste.

A chave para esse processo é o algoritmo que usa esse histórico de desempenho de validação,
considerando vários conjuntos de hiperparâmetros, para escolher o próximo conjunto de
hiperparâmetros a ser avaliado. Muitas técnicas diferentes são possíveis: otimização bayesiana,
algoritmos genéticos, pesquisa aleatória simples e assim por diante.

Treinar os pesos de um modelo é relativamente fácil: você calcula uma função de perda em um
mini lote de dados e, em seguida, usa o algoritmo Backpropagation para mover os pesos na
direção correta. A atualização de hiperparâmetros, por outro lado, é extremamente
desafiadora. Considere o seguinte:

 A computação do sinal de feedback (esse conjunto de hiperparâmetros leva a um


modelo de alto desempenho nessa tarefa?) Pode ser extremamente caro: ele exige a
criação e o treinamento de um novo modelo a partir do zero no conjunto de dados.
 O espaço do hyperparameter é tipicamente feito de decisões discretas e, portanto, não é
contínuo ou diferenciável. Assim, você normalmente não pode fazer descida de
gradiente no espaço do hiperparâmetro. Em vez disso, você deve confiar em técnicas de
otimização livres de gradiente, que naturalmente são muito menos eficientes que a
descida de gradiente.

Como esses desafios são difíceis e o campo ainda é jovem, atualmente só temos acesso a
ferramentas muito limitadas para otimizar modelos. Muitas vezes, verifica-se que a pesquisa
aleatória (escolhendo hiperparâmetros para avaliar aleatoriamente, repetidamente) é a melhor
solução, apesar de ser a mais ingênua. Mas uma ferramenta que eu encontrei melhor do que a
pesquisa aleatória é Hyperopt ( https://github.com/hyperopt/hyperopt ), uma biblioteca Python para
otimização de hyperparameter que usa internamente árvores de estimadores Parzen para prever
conjuntos de hiperparâmetros que provavelmente funcionarão bem. Outra biblioteca chamada
Hyperas ( https://github.com/maxpumperla/hyperas ) integra Hyperopt para uso com modelos
Keras. Faça o check-out.

Nota

Uma questão importante a ser lembrada ao fazer a otimização automática do hiperparâmetro


em escala é o overfitting do conjunto de validação. Como você está atualizando hiperparâmetros
com base em um sinal que é calculado usando seus dados de validação, você está efetivamente
treinando-os nos dados de validação e, assim, eles se adaptam rapidamente aos dados de
validação. Tenha sempre isso em mente.

No geral, a otimização de hiperparâmetros é uma técnica poderosa que é um requisito absoluto


para se obter modelos de última geração em qualquer tarefa ou para vencer competições de
aprendizado de máquina. Pense nisso: uma vez, as pessoas criaram os recursos criados em
modelos de aprendizado de máquina superficiais. Isso foi muito abaixo do ideal. Agora, o
aprendizado profundo automatiza a tarefa de engenharia de recursos hierárquicos - os recursos
são aprendidos usando um sinal de feedback, não ajustado à mão, e é assim que deve ser. Da
mesma forma, você não deve criar suas arquiteturas de modelo; você deve otimizá-los de uma
maneira baseada em princípios. No momento em que escrevo, o campo da otimização
automática de hiperparâmetros é muito jovem e imaturo, como o aprendizado profundo foi há
alguns anos, mas espero que aumente nos próximos anos.

7.3.3. Modelo conjunto


Outra técnica poderosa para obter os melhores resultados possíveis em uma tarefa é o conjunto
de modelos . O agrupamento consiste em agrupar as previsões de um conjunto de diferentes
modelos para produzir melhores previsões. Se você olhar para competições de aprendizado de
máquina,em particular no Kaggle, você verá que os vencedores usam conjuntos muito grandes
de modelos que inevitavelmente superam qualquer modelo único, não importa o quão bom seja.

O Ensemble baseia-se no pressuposto de que diferentes modelos bons treinados


independentemente provavelmente serão bons por diferentes razões: cada modelo analisa
aspectos ligeiramente diferentes dos dados para fazer suas previsões, obtendo parte da
"verdade", mas não de tudo. Você pode estar familiarizado com a antiga parábola dos cegos e do
elefante: um grupo de cegos se depara com um elefante pela primeira vez e tenta entender o que
ele é ao tocá-lo. Cada homem toca uma parte diferente do corpo do elefante - apenas uma parte,
como o tronco ou uma perna. Então os homens descrevem um ao outro o que é um elefante: “É
como uma cobra”, “como um pilar ou uma árvore”, e assim por diante. Os cegos são
essencialmente modelos de aprendizado de máquina tentando entender a multiplicidade dos
dados de treinamento, cada um de sua própria perspectiva, usando suas próprias suposições
(fornecidas pela arquitetura exclusiva do modelo e pela inicialização única de peso
aleatório). Cada um deles obtém parte da verdade dos dados, mas não toda a
verdade. Agrupando suas perspectivas, você pode obter uma descrição muito mais precisa dos
dados. O elefante é uma combinação de partes: nem um único cego entende direito, mas,
entrevistados juntos, conseguem contar uma história bastante precisa.

Vamos usar a classificação como exemplo. A maneira mais fácil de agrupar as previsões de um
conjunto de classificadores (para agrupar os classificadores ) é calcular a média de suas
previsões no tempo de inferência:

preds_a = model_a.predict (x_val) 1

preds_b = model_b.predict (x_val) 1

preds_c = model_c.predict (x_val) 1

preds_d = model_d.predict (x_val) 1

final_preds = 0,25 * (preds_a + preds_b + preds_c + preds_d) 2

 1 Use quatro modelos diferentes para calcular as previsões iniciais.


 2 Essa nova matriz de previsão deve ser mais precisa do que qualquer uma
das iniciais.

Isso funcionará somente se os classificadores forem mais ou menos igualmente bons. Se um


deles é significativamente pior do que os outros, as previsões finais podem não ser tão boas
quanto o melhor classificador do grupo.

Uma maneira mais inteligente de agrupar classificadores é fazer uma média ponderada, em que
os pesos são aprendidos nos dados de validação - normalmente, os classificadores melhores
recebem um peso maior e os classificadores inferiores recebem um peso menor. Para procurar
por um bom conjunto de pesos, você pode usar busca aleatória ou um algoritmo de otimização
simples, como Nelder-Mead:

preds_a = model_a.predict (x_val)

preds_b = model_b.predict (x_val)

preds_c = model_c.predict (x_val)

preds_d = model_d.predict (x_val)


final_preds = 0.5 * preds_a + 0.25 * preds_b + 0.1 * preds_c + 0.15 * preds_d
1

 1 Supõe-se que estes pesos (0,5, 0,25, 0,1, 0,15) sejam aprendidos
empiricamente.

Existem muitas variantes possíveis: você pode fazer uma média exponencial das previsões, por
exemplo. Em geral, uma média ponderada simples com pesos otimizados nos dados de
validação fornece uma linha de base muito forte.

A chave para tornar o trabalho conjunto é a diversidade do conjunto de


classificadores. Diversidade é força. Se todos os cegos apenas tocassem a tromba do elefante,
eles concordariam que os elefantes são como cobras, e ficariam para sempre ignorantes da
verdade do elefante. A diversidade é o que faz o trabalho conjunto. Em termos de aprendizado
de máquina, se todos os seus modelos forem influenciados da mesma maneira, seu conjunto
manterá esse mesmo viés. Se seus modelos forem influenciados de maneiras diferentes , os
vieses se anularão mutuamente e o conjunto será mais robusto e mais preciso.

Por esse motivo, você deve montar modelos que sejam tão bons quanto possível , sendo os mais
diferentes possíveis . Isso normalmente significa usar arquiteturas m