- View
Aprender a representar dados
As redes neurais podem ajudar-nos a isolar certos atributos abstratos nos dados e a aprender representações adequadas para resolver problemas.
Nos exemplos que utilizámos até agora, baseámo-nos, na maioria das vezes, na existência de um determinado conjunto de atributos no conjunto de dados. De facto, muitos domínios geram dados com esta estrutura — atributos dispostos em colunas e instâncias em linhas individuais. Como vimos na parte introdutória da preparação de dados, mesmo quando dispomos destes atributos, não é nada intuitivo decidir quais devemos escolher para criar um modelo. Isso colocou-nos na posição de experimentar diferentes combinações ou de desenvolver técnicas que nos ajudem na seleção de atributos.
Devido à complexidade das funções que modelam, as redes neurais têm a capacidade de aprender a filtrar e agrupar os atributos relevantes. Esta propriedade das redes neurais é especialmente importante quando trabalhamos com dados não tabulares — já levantámos várias vezes a questão de como representar, por exemplo, imagens, texto ou gravações áudio. Apesar de termos conhecimento sobre estes formatos, é difícil descrever com precisão o que contêm de forma concisa e utilizável. Isto, entre outras coisas, motivou a aplicação do paradigma de programação orientado a dados (data-driven programming). As redes neurais conseguem (como veremos em breve) aprender atributos abstratos a partir dos dados na sua forma original, que são úteis para resolver problemas com sucesso.
A seguir, vamos apresentar as redes neurais convolucionais, usadas principalmente no trabalho com imagens e vídeos, para aprender atributos visuais da entrada, e depois as redes neurais recorrentes e os transformers, tipos de redes neurais usados para aprender os atributos de dados sequenciais, como texto ou som.
Redes Neurais Convolucionais
As redes neurais convolucionais são um tipo de rede neuronal utilizado principalmente na área da visão por computador, para trabalhar com imagens e conteúdos de vídeo
As imagens a preto e branco são representadas por matrizes de píxeis. O número de linhas dessa matriz corresponde à altura da imagem, enquanto o número de colunas corresponde à sua largura. Os valores individuais dos píxeis são números que variam entre 0 e 255, onde 0 representa preto e 255 representa branco. Todos os valores intermédios representam tons de cinzento. Consegues adivinhar o que está por trás da imagem representada pela matriz de píxeis abaixo? Se te afastares o suficiente e desenhares os contornos nas zonas de tons mais escuros, talvez consigas perceber o que está na imagem. Ajuda o facto de, na comunidade de aprendizagem automática, as imagens de gatos serem uma escolha bastante comum.

Também se pode verificar o próximo bloco e ver qual é a imagem.

Tal como para nós, humanos, é difícil perceber o que está numa imagem representada por um conjunto de números, o mesmo acontece com as redes neurais convolucionais. Estas começam a sua análise da imagem reconhecendo primeiro elementos simples, como linhas horizontais e verticais, e depois combinam-nos e adicionam complexidade, até chegarem a descrições mais complexas da imagem, que as ajudam a resolver a tarefa em questão. A pergunta natural que se coloca agora é: como é que se começa por contornos simples e se constrói a partir daí para obter estruturas mais complexas? A resposta será dada pelo operador de convolução.
A forma mais simples de introduzir o operador de convolução é através de ilustrações. Imaginemos que temos, à entrada, uma matriz de 6x6 píxeis que representa a imagem, e uma pequena matriz, o chamado filtro, com dimensões de 3x3 píxeis. Suponhamos que estas são as matrizes mostradas na imagem abaixo. Começamos a aplicar o operador de convolução sobre a imagem (iremos marcá-lo com o símbolo✷ ) ao sobrepor a parte superior esquerda da imagem com o filtro, depois multiplicamos os valores correspondentes e escrevemos a soma desses valores numa nova matriz. Esta nova matriz será o resultado da aplicação do operador de convolução.

Convolução - Passo 1
Continuaremos a aplicar o operador de convolução: vamos sobrepor o filtro com a parte da imagem localizada no canto superior esquerdo, mas para que agora seja deslocado um pixel da borda esquerda, ou seja, em comparação com a posição anterior. Novamente, vamos multiplicar os valores individuais, somá-los e escrevê-los na matriz resultante.

Convolução - Passo 2
O filtro pode ser movido uma posição para a direita até a borda. Então precisamos baixá-lo uma posição para baixo e colocá-lo de volta ao lado da borda. Em seguida, podemos continuar o processo até chegarmos ao canto inferior direito. Como resultado desta operação, obteremos uma matriz de dimensões de 4x4 pixels, cujos valores são mostrados na imagem abaixo (certifique-se de ter certeza!).

Convolução - o resultado
A distância que o filtro se desloca em cada iteração é definida por um hiperparâmetro chamado deslocamento (stride). No nosso caso, o deslocamento teve o valor de 1, porque movemos o filtro uma posição para a direita (e depois uma posição para baixo). Para podermos influenciar as dimensões da matriz resultante ao aplicar a operação de convolução (normalmente queremos preservar as dimensões da matriz de entrada), podemos adicionar uma moldura à volta da imagem inicial. Essa moldura é, geralmente, um bloco de zeros, uns ou números cujos valores correspondem ao píxel mais próximo da imagem. Esta técnica chama-se preenchimento (padding) e a sua largura é sempre indicada aquando da aplicação da operação de convolução. O preenchimento é especialmente importante quando as características que queremos que o nosso modelo aprenda estão próximas da margem da imagem.
A animação abaixo ilustra todo o processo de aplicação dos filtros à imagem, ou seja, à sua matriz. Como podes ver, foi utilizado um deslocamento de tamanho 1 e um preenchimento de tamanho 0.

Animação da operação de convolução
Se aplicarmos o filtro do exemplo anterior usando a operação de convolução sobre a imagem inicial do gatinho, obtemos a imagem abaixo. Podes ver que todas as linhas verticais que aparecem na imagem foram realçadas.

Extração de borda vertical
Agora que sabemos como separar arestas verticais e horizontais, podemos combinar de várias maneiras para destacar linhas que não são apenas horizontais e verticais. Ao combinar ainda mais estes resultados, podemos até extrair contornos esféricos. Isto é o que queríamos dizer quando dissemos que começamos com características claras e fáceis de aprender e, em seguida, construímos passo a passo sobre a complexidade das características que podemos aprender.
As camadas de uma rede neural caracterizadas pela aplicação do operador convolucional são chamadas camadas convolucionais . Enquanto os pioneiros no campo da visão computacional criaram filtros manualmente, o objetivo de treinar redes neurais convolucionais é aprender por si mesmos os valores que figuram nelas.
Na imagem abaixo, pode-se ver as representações dos filtros aprendidos nas camadas de uma rede neural convolucional que reconhece rostos. Na camada mais baixa, são algumas linhas horizontais, verticais e diagonais, na segunda camada já são contornos que correspondem a partes da face como nariz, olhos e boca, enquanto na terceira camada são filtros que correspondem aos contornos da face.

Além da operação convolucional, as redes convolucionais também são caracterizadas pela operação de agregação. Como o nome sugere, o objetivo desta operação é agregar, ou seja, consolidar as entradas. Na imagem abaixo, pode-se ver dois tipos de operadores de agregação: o que usa o máximo e o que usa a média para agregar as informações. Assim como o operador convolucional, este operador é aplicado a blocos de entrada percebendo o bloco e realizando o cálculo necessário nele. O valor obtido desta forma é inserido numa nova matriz. Na figura, ambos os operadores são aplicados a blocos 2x2. Intuitivamente falando, enfatizamos a parte mais dominante com máximos, enquanto que ao calcular a média, levamos em conta a contribuição de todas as partes.

Ao aplicar a operação de agregação, obtemos a capacidade de reduzir a dimensão da entrada, mas ao mesmo tempo reter algumas das informações contidas. Na imagem, consegue-se ver que, aplicando o operador de agregação, reduzimos a matriz de 4x4 para 2x2. Por que razão precisamos de reduzir as dimensões? Como resultado, a rede precisa nos dar uma resposta específica, digamos se há um gato na imagem ou não, para o qual precisamos de um pequeno número de neurónios.
As camadas de uma rede neural que são caracterizadas pela aplicação do operador de agregação são chamadas camadas de agregação. Não há parâmetros adicionais neles que a rede precise aprender, mas, como vimos, eles nos ajudam a controlar as dimensões das matrizes com as quais trabalhamos.
Agora que sabemos quais são os blocos de construção de uma rede neural convolucional, vamos ver como a podemos ligar e obter um modelo funcional que pode nos ajudar a resolver a tarefa de classificação. Vamos imaginar que precisamos resolver uma tarefa de classificação multiclasse em que para cada imagem de um personagem de desenho animado precisamos determinar se é Piu-Piu, Pateta ou Pato Donald. Vejamos uma ilustração da arquitetura de uma rede neural convolucional profunda que escolhemos para resolver esta tarefa e discutir qual é a motivação para a sua criação.

No início da rede, existe uma camada de entrada que contém os pixéis da imagem. A seguir, temos uma camada convolucional (um bloco azul). O objetivo desta camada é utilizar o operador de convolução para extrair o primeiro conjunto de atributos abstratos. Depois disso, colocamos uma camada de pooling (bloco verde), seguida por outra camada convolucional (bloco laranja) e outra camada de pooling (bloco vermelho). Na prática, as camadas convolucionais e de pooling são combinadas e frequentemente colocadas lado a lado, pois as camadas de pooling têm como função resumir aquilo que as camadas convolucionais aprenderam. A segunda camada convolucional permite-nos aplicar um segundo operador de convolução aos atributos já extraídos pela primeira camada convolucional, formando assim atributos mais complexos da imagem. Este bloco de camadas é seguido por uma camada (bloco cinzento) cuja tarefa é "corrigir" a matriz (ou mais precisamente, o tensor) que obtivemos até esse ponto e reorganizar os seus valores de modo a que fiquem todos dispostos lado a lado numa única matriz unidimensional. As camadas com esta função chamam-se camadas de achatamento (flattening layers). As camadas com esta função chamam-se camadas de achatamento (flattening layers). Além da camada de entrada (já corrigida), também vemos na imagem uma camada escondida (hidden layer) e uma camada de saída, na qual existem exatamente três neurónios – um para cada uma das personagens de banda desenhada. Os valores de saída destes neurónios correspondem à probabilidade de que a imagem de entrada pertença à classe que cada um representa. No caso da imagem do Twitter que temos na entrada, podemos notar que o valor de saída do terceiro neurónio – que corresponde exatamente a essa classe – é o mais elevado e tem o valor 0,7.
Agora também podemos ver qual é a arquitetura da VGGNet, uma rede convolucional popular que é ativamente utilizada na prática.
A VGGNet é uma rede neuronal convolucional profunda desenvolvida pela equipa de Oxford chamada Visual Geometry Group (daí o nome VGGNet). No prestigiado Large Scale Visual Recognition Challenge, realizado em 2014, esta rede revelou-se a melhor na resolução do problema de localização de objetos numa imagem e a segunda melhor no problema de classificação de objetos a partir de uma imagem. Para a tarefa de classificação, foram utilizadas mais de 1,2 milhões de imagens do conjunto ImageNet (que já conhecemos), com classificação possível em 1000 classes distintas. Para treinar esta rede foram necessários entre 15 e 20 dias, utilizando 4 placas gráficas NVIDIA Titan Black (as melhores da época). Antes da VGGNet, a rede mais destacada nestas tarefas era a AlexNet, que ficou conhecida por ter introduzido a prática de usar placas gráficas para treinar redes neuronais, permitindo assim o avanço significativo da área de aprendizagem profunda (deep learning).
A arquitectura da rede VGG-16, uma versão da rede com 16 camadas, está representada na imagem abaixo. Como podemos ver, a rede espera à entrada uma imagem a cores com dimensões de 244x244 píxeis, e combina camadas convolucionais com camadas de pooling (com máximo). Termina com uma rede neuronal totalmente conectada com 1000 neurónios na saída, onde cada neurónio corresponde a uma classe específica do conjunto ImageNet. A própria rede possui 138 milhões de parâmetros e necessita de cerca de 500 MB de memória para os armazenar.

Ecrã de rede VGG-16
Aumentar o número de camadas da parte convolucional da rede geralmente conduz a melhores resultados na prática. No entanto, o número de camadas não pode aumentar indefinidamente. Isto deve-se não só às limitações de recursos, tempo e custo, mas também às propriedades matemáticas das redes neuronais profundas, que dificultam ainda mais a aplicação do algoritmo de retropropagação e o próprio treino da rede.
Se tiveres interesse neste problema matemático, podes tentar ler mais sobre os gradientes que desaparecem e explodem, especialmente na parte relativa às redes convolucionais e às ligações residuais (residual connections).
Antes de entrarmos na tarefa de trabalhar com redes neurais convolucionais, vamos analisar a questão do trabalho com imagens coloridas. Até agora, ainda não as mencionámos.
Quando precisamos de apresentar uma imagem a cores, aquela que utiliza o formato de cor RGB e exibe todas as cores como combinações de vermelho, verde e azul, usamos três matrizes. Uma matriz é atribuída a cada uma das cores. O número de matrizes que usamos para representar imagens chama-se canais. Assim, imagens a preto e branco têm apenas um canal, enquanto imagens a cores têm três canais.

Exibição de imagens que usam o formato de cor RGB
A presença de cores afeta o desempenho da operação convolucional, ajustando o número de canais do filtro para o número de canais da imagem à qual estamos aplicando-o. Depois, é necessário emparelhar cada um dos canais de filtro com o canal de imagem (vermelho com vermelho, azul com azul e verde com verde) e executar a operação convolucional como se estivesse trabalhando com um único canal. Então, precisamos adicionar as matrizes que obtemos dessa maneira e declarar a matriz resultante como o resultado final. Na imagem abaixo, podemos ver dois filtros numa camada convolucional que são aplicados sobre a imagem de entrada de cores. Como resultado da aplicação de cada um desses filtros, obteremos matrizes separadas que, quando "fundidas", representam o resultado final da camada convolucional. Na prática, vários filtros são geralmente colocados ao nível de uma camada convolucional, de modo que os tensores são obtidos como resultados. Operações convolucionais são então aplicadas a esses tensores da mesma maneira - apenas o cuidado é tomado para garantir que o número de canais de filtro corresponde à dimensão do tensor (digamos, para a próxima aplicação no exemplo que consideramos, este seria o número 2) e que o canal de entrada correspondente é emparelhado com o canal de filtro correspondente.

Aplicando o operador convolucional a entradas multicanal
Quanto à operação de pooling, ela é aplicada a cada canal da imagem de entrada. Por exemplo, se a imagem de entrada tiver 3 canais, a operação de pooling será aplicada a cada canal separadamente. Isto também significa que a operação de pooling preserva o número de canais no momento da aplicação. Na imagem abaixo podes ver uma ilustração deste processo.
Aplicando operadores de agregação a entradas multicanal
Esta seção é emparelhada com o Jupyter Notebook 09-VGG-16_network_and_classification.ipynb. Para acompanhar o conteúdo, clique no link e, em seguida, clique
no botão para abrir o conteúdo no Google Colab . Se estiveres a visualizar os blocos de notas no teu computador local, localiza o bloco de notas com o mesmo nome entre os conteúdos e executa-o. Para instruções mais detalhadas, consulta a secção Zona prática e a aula Jupyter Notebooks para a prática.
Agora vamos experimentar como a rede convolucional VGG-16 realmente funciona! Não esquecer de seguir o caderno com o código enquanto se lê a aula.
Uma vez treinado, um modelo de rede neural pode ser partilhado com a comunidade, disponibilizando os parâmetros que o compõem. Neste exemplo, iremos utilizar o modelo disponível na biblioteca Keras. A biblioteca Keras é uma biblioteca de código aberto amplamente utilizada na comunidade de aprendizagem automática (machine learning). Para tirar partido do modelo de rede VGG-16, precisamos de executar os seguintes dois comandos:
de tensorflow.keras.applications importar VGG16
modelo = VGG16(pesos = 'imagenet')
Podemos ler as informações sobre o modelo que carregamos usando a função model.summary(). Seu resultado é uma descrição das camadas de rede seguida de informações sobre os tamanhos de entrada que essas camadas esperam. Agora, pode executar o comando:
modelo.resumo()
Não te preocupes se não compreenderes todos os detalhes que são exibidos após a execução deste comando. É importante saber que a entrada deve ser uma imagem a cores com dimensões de 224x224 píxeis (é por isso que aparece 224, 224, 3 ao lado da camada de entrada) e que tens uma das 1000 classes na saída. Podes também comparar a impressão com a imagem da VGG-16 que discutimos anteriormente para descobrires mais informações.
É importante salientar que não iremos treinar a rede VGG-16 — usaremos apenas um modelo já treinado. Por isso, não devemos alterar os parâmetros do modelo durante a sua utilização — cada parâmetro tem a sua própria contribuição. O número total de parâmetros do modelo, que podemos verificar no resumo da rede, é de pouco mais de 138 milhões.
A ideia é que, a imagem em que vamos testar o modelo seja uma imagem arbitrária da web. Para fazer isso, usaremos várias bibliotecas Python padrão. Por padrão, a função URL ucitaj_sliku nos ajudará a arrastar a imagem que desejamos.
def ucitaj_sliku (url_putanja):
resposta = request.urlopen(url_putanja).read()
retornar Image.open(BytesIO(reply))
Para o teste, escolhemos uma imagem de um Golden Retriever do endereço https://unsplash.com/photos/x5oPmHmY3kQ , que pode ser usada livremente. Podes escolher a imagem que quiseres! É importante ter em mente que a classe do objeto na imagem deve ser conhecida pelo modelo. Como o modelo VGG-16 foi treinado em mais de 1,2 milhão de imagens, ele conhece muitas classes, até 1000 diferentes. O Golden Retriever é um deles. Se dermos ao modelo uma imagem com um objeto que ele não conhece, ele nos dará previsões das classes cujas imagens mais se assemelham às nossas. Veremos no final quais classes se assemelham ao Golden Retriever.

O herói da história do modelo VGG-16
Como a imagem que precisa ser encaminhada para o modelo precisa ser especialmente preparada, faremos o seguinte:
definir suas dimensões para 224x224 e dizer-lhe para usar três canais de cor RGB:
test_slika = test_slika.resize((224, 224))
test_slika = test_slika.convert('RGB')
Transforme a imagem num formato matricial adequado:
matrix _oblik _test_slike = image.img_to_array(test_slika)
make a package that contains our picture:
packet = np.expand_dims(matricni_oblik_test_slike, axis = 0)
Execute o pré-processamento numérico de imagens na forma de normalização:
test _skup _slika = preprocess_input(packet)
Só assim poderemos transmitir o modelo de classificação. A função que nos ajudará é chamada de previsão.
predictions _modela = model.predict(test_skup_slika)
A variável predikcije_modela, na qual armazenámos as previsões do modelo, é uma matriz com 1000 elementos, e contém as probabilidades de pertencer a cada uma das 1000 classes que o modelo reconhece.
Para extrair a classe à qual a nossa imagem pertence, podemos usar a função decode_predictions, que nos devolverá as 5 classes mais prováveis, juntamente com as respetivas probabilidades e nomes. Isto permite-nos ter uma noção de quão confiante está o modelo na classificação. Depois de executarmos o próximo comando, obteremos informações sobre as classes mais prováveis.
najverovatnije_klase = decode_predictions(predikcije_modela)[0]
Quando representamos graficamente essas previsões com o código descrito abaixo, obtemos um gráfico com barras que nos permite analisar mais facilmente os resultados.
ime_klase = [tuesday[1] for tuesday and najverovatnije_klase]
probability _pripadnosti = [tuesday[2] for tuesday and najverovatnije_klase]
plt .figure(figsize=(10, 4))
plt .bar (name _klase , verovatnoca_pripadnosti, color =['teal', 'yellow', 'green', 'blue', 'orange'])
plt .title ('The Five Most Likely Classes')
plt .ylabel ('Probability of Affiliation')
plt.show()

Como podemos ver, o modelo previu com grande certeza (a probabilidade foi de 0,804) que a imagem escolhida era de um golden retriever. Algumas das outras classes que o modelo considerou foram outros tipos de retrievers. Curiosamente, uma bola de ténis também apareceu na lista de resultados. Provavelmente porque, no conjunto de treino, também existem imagens de retrievers a correr atrás de bolas de ténis. Este comportamento do modelo deve ser investigado mais a fundo na prática.