Coletânea Python do ZERO às Redes Neurais Artificiais

Fernando Feltrin · Capítulo 287 de 287

Páginas do PDF

Coletânea Python do ZERO às Redes Neurais Artificiais

Artigo - Classificador Multiclasse via TensorFlow e Keras

 

A partir do momento em que estamos buscando resolver

algum tipo de problema computacional por meio de redes neurais artificiais, é de suma importância definirmos exatamente que tipo de problema estamos trabalhando, para escolhermos corretamente as ferramentas que serão utilizadas, assim como o modelo de rede neural que melhor se adapta para aquele fim. Uma vez que uma rede neural é um modelo estrutural de processamento de dados visando encontrar padrões e aprender a partir dos mesmos.

É bastante comum termos modelos específicos de redes

neurais artificiais que realizam todo tipo de classificação, um desses modelos, chamado usualmente de rede neural convolucional, é um modelo especializado em resolver problemas de classificação a partir de imagens. Nesse sentido, existe toda uma área em machine learning e deep learning dedicada a realizar o processo chamado de convolução, onde as imagens, dadas as suas características de composição através de pixels e suas matrizes de cores, são convertidas para matrizes numéricas que por sua vez irão compor camadas de neurônios artificiais da rede neural para cruzamento dos dados.

 

Uma vez realizadas as devidas conversões, pixel a pixel, para arrays de um dataframe, é possível treinar uma rede neural artificial para que encontre padrões específicos e característicos de uma imagem a fim de classificar a mesma quanto a sua forma.

A nível de rede neural artificial convolucional, podemos,

utilizando de conceitos de machine learning, treinar uma rede para que diferencie, por exemplo, diferentes animais entre si, diferentes objetos quanto ao seu formato, diferentes pessoas quanto às suas características únicas que compõe seu rosto, e, como será exemplificado neste capítulo, diferenciar a partir de exames de imagem, com uma precisão muito maior do que a de um médico especialista, características de apresentação de uma anatomia normal ou patológica..

O exemplo que veremos trata justamente desse tipo de

classificação, realizado via rede neural artificial convolucional. A área da radiologia, também conhecida como diagnóstico por imagem, é o ramo da medicina onde por meio de exames como ultrassonografia, raios-x, tomografia computadorizada, ressonância magnética, cintilografia, entre outros exames, conseguimos obter imagens fidedignas da anatomia para que sejam feitos os devidos estudos em busca de quaisquer tipo de alteração morfológica, fisiológica ou funcional. Nesta área em particular, além dos equipamentos distintos para cada tipo de exame, existe toda uma equipe multidisciplinar composta por técnicos e médicos radiologistas.

 

O médico radiologista, profissional este que se dedica

exclusivamente a interpretar exames de imagem, possui formação médica somada a uma especialização que o treina a identificar características bastante minuciosas, como a diferença de densidade ou de atividade metabólica entre tecidos. O mesmo, com base em seu conhecimento da anatomia normal assim como a experiência em clínica médica, identifica em exames de imagem características que possam apresentar alterações da anatomia indicando patologias. O ponto a se destacar é que, por melhor e mais experiente que este tipo de profissional seja, ele possui devido as suas limitações como ser humano, uma margem de erro a ser considerada em cada interpretação de exame.

É nesse momento que a coisa começa a ficar interessante, no

sentido de que conseguimos elaborar e treinar uma rede neural artificial para que, a partir de uma base de dados previamente usada para aprendizado de máquina, possa realizar o processamento de novas imagens com nível de precisão maior do que a do próprio médico radiologista. Tenha em mente que toda subdivisão da área de inteligência artificial vem a somar, e muito, em várias áreas onde as mesmas estão sendo aplicadas, não longe disso, a área da medicina tem obtido bons e significativos progressos graças à usabilidade destas tecnologias e ferramentas. Tal tipo de tecnologia não visa substituir nenhum profissional desta área, mas servir como uma ferramenta que fornece um segundo viés de confirmação, para que sejam emitidos laudos mais precisos, e consequentemente tratamentos mais eficazes para os pacientes.

Nas páginas seguintes, estaremos elaborando e criando passo

a passo, uma rede neural artificial convolucional dedicada a processar e interpretar radiografias de tórax, onde a anatomia principal demonstrada são os pulmões e a região do mediastino da caixa torácica, em busca de alguma patologia do trato respiratório como pneumonia, tuberculose e derrame pleural. Para isso, usaremos da linguagem Python assim como duas de suas mais importantes bibliotecas para estruturação de redes neurais densas chamadas TensorFlow e Keras.

Uma das características interessantes da biblioteca Keras é o

fato da mesma, ter capacidade de criar redes neurais convolucionais partindo de amostras de imagens. Diferente de outros modelos onde é necessário aprendizado supervisionado, ou por reforço, ou conciliando labels sobre as imagens para que destaquem suas características, biblioteca Keras por meio de suas funções internas consegue fazer classificação de dois ou mais tipos de imagens simplesmente as separando em pastas de acordo com o seu tipo, convertendo tais tipos de dados para arrays que irão compor camadas de uma rede neural e passar por uma série de funções para os devidos reconhecimentos de padrões a partir destas imagens.

 

O que faremos, etapa por etapa, é a identificação do tipo de

problema computacional, neste caso uma classificação “multiclasse” a partir de imagens, posteriormente preparar uma base de dados a ser utilizada e dando sequência teremos de fato a codificação da estrutura da rede neural artificial e por fim teremos um modelo treinado para realização de testes a partir do mesmo.

 

Temos um problema computacional idealizado, hora de

estruturar o mesmo de forma lógica para que possamos construir um modelo. Para realizarmos a classificação a partir de radiografias de tórax, temos de preparar uma base de dados com imagens que serão processadas para o treinamento de nossa rede neural artificial convolucional. Para isso, podemos buscar datasets públicos de radiografias de tórax ou montar nossa própria base de dados, reunindo imagens separadas quanto às suas características. Nesse caso, é preciso construir uma base composta de imagens de exames sem alterações, e com as respectivas patologias separadas quanto ao seu tipo.

Imagem 01 - Estrutura de pastas para as amostras separando-as quanto ao seu tipo

Ao todo, montamos uma base com apenas 674 imagens com

as respectivas características de um exame “normal” ou apresentando algum tipo de patologia (pneumonia, derrame pleural e tuberculose), separados entre si. Importante salientar que assim como toda rede neural artificial, muito da eficiência de nossa rede em seu processo de aprendizado de máquina está diretamente ligado a base de dados usada para esse processo. Principalmente no que se refere a classificação a partir de imagens, uma base de dados mais robusta, com maior número de amostras (dependendo o caso, milhares ou até mesmo milhões de amostras), torna a eficiência de nossa rede neural muito maior.

Uma base de dados pequena pode impactar negativamente o

processo de aprendizado por reforço, assim como uma base de dados muito volumosa, se não preparada da maneira correta, pode gerar ruídos no processo de aprendizado de máquina. Note que aqui, especificamente, estamos usando uma base de dados de imagens, onde todas são muito parecidas entre si, ao menos aos olhos de um leigo, todas essas radiografias possuem basicamente as mesmas características, como extensão da caixa torácica, dois pulmões, mediastino, imagens em preto e branco, resolução parecida, etc…, essas características, para esse relativo volume pequeno de amostras, será o suficiente para nosso modelo inicial mas em uma aplicação real o ideal seria utilizarmos de uma base de dados maior.

Base de dados pronta, com as devidas amostras separadas em

pastas de acordo com seu tipo, assim como separadas em base de dados para treino e teste pela rede, podemos continuar nossa linha de raciocínio.

 

Imagem 02 - Amostras de radiografias de tórax

Para reforçar nosso entendimento, é interessante por hora

entendermos o processo de convolução de uma imagem. O processo de conversão de uma imagem em toda sua forma e estrutura, para uma matriz numérica que alimentará perceptrons de camadas de nossa rede neural artificial convolucional é um processo dividido em várias etapas.

Inicialmente teremos uma imagem fonte, com uma

determinada resolução dependendo o tipo de exame, que é convertida para preto e branco caso seja originalmente colorida, em seguida a imagem é mapeada e dividida em blocos menores, onde cada bloco será novamente mapeado em busca de características únicas e cada etapa desse mapeamento é indexada internamente como mapas de características. Na sequência, esse mapeamento inicial é replicado e novamente convertido em mapas indexados internamente, agora com as características de cada pixel, de cada bloco, de cada mapa, de cada posição da imagem original. Dando prosseguimento, cada mapa será escalonado e indexado algumas vezes até que por fim essas conversões resultarão em uma matriz de indexação com apenas uma coluna de dados numéricos. Essa coluna por sua vez, será normalizada em uma escala de 0 a 1 e dividida de forma que cada valor de cada linha da mesma se tornará um neurônio da camada de entrada na rede neural artificial. *Cada uma das imagens de nossa base de dados passará por esse processo ao alimentar nossa rede neural.

 

Imagem 03 - Representação das etapas do processo de convolução

 

Base de dados preparada, hora de partir para o código: Apenas

lembrando que as bibliotecas TensorFlow e Keras não vem nativamente instaladas junto a sua distribuição do Python, porém a instalação das mesmas é facilmente realizada via pip (pip install tensorflow, pip install keras) ou via conda, já que estaremos trabalhando em um ambiente virtual criado para este fim (conda install tensorflow, conda install keras). Uma vez instaladas as bibliotecas, podemos abrir nossa IDE, nesse caso em particular estarei usando a Spyder, ambiente de desenvolvimento muito usado para computação científica principalmente em função da sua maneira intuitiva de apresentar os dados das variáveis de forma visual, assim como a execução independente de cada bloco de código.

 

Imagem 04 - Ambiente da IDE Spyder

 

Apenas fazendo uma pequena observação, ao abrir o Spyder

não é necessário realizar nenhuma configuração, a não ser verificar se estamos usando o kernel correto uma vez que estamos trabalhando em um ambiente virtual isolado.

Imagem 05 - Seleção do kernel a ser utilizado para a execução da rede neural

 

Note que neste caso estarei utilizando a biblioteca TensorFlow

em sua versão 1.15, porém o código apresentado é totalmente compatível com as versões a partir da 2.0 da mesma.

Partindo para o código:

Como sempre, o processo se dá iniciando com as importações das bibliotecas, módulos, pacotes e ferramentas que estaremos usando ao longo do código.

 

from keras.layers import Conv2D, MaxPooling2D, Activation, Dropout, Flatten, Dense

from keras.preprocessing import image from keras.preprocessing.image import ImageDataGenerator from keras.models import Sequential from keras import backend as B import numpy as np

 

Repare que, para fins de performance, importamos

especificamente apenas os módulos das bibliotecas que de fato usaremos.

 

Sendo assim, inicialmente importamos do módulo layers da

biblioteca Keras as funções empacotadas Conv2D e MaxPooling2D que respectivamente servirão para criar a estrutura de neurônios por camada assim como a conversão das matrizes para duas dimensões; Activation será responsável pela camada de ativação e suas funções de ativação aplicadas sobre os neurônios; Dropout que é uma ferramenta que aleatoriamente seleciona e remove neurônios das camadas intermediárias, como uma espécie de filtro, para que seja testado o processamento sem os devidos neurônios, assim como seu impacto na performance dos ajustes dos pesos; Flatten que basicamente é uma ferramenta que converterá a última camada de matriz bidimensional para unidimensional, e dessa forma, cada perceptron empilhado destes se tornará um neurônio da camada de entrada da estrutura densa da rede; Dense que por sua vez permitirá criar camadas de neurônios conectadas entre si de forma a permitir, entre outras operações, a retroalimentação da rede e todo seu processo de aprendizado de máquina por meio dos ajustes dos pesos entre camadas; Da mesma forma, do módulo preprocessing importamos image, pacote que permitirá operações diretamente sobre arquivos de imagem, também importamos ImageDataGenerator, que possibilitará gerar uma base de dados aumentada, com amostras geradas pela própria rede neural a partir da base de dados inicial, para que o processo de reconhecimento de padrões a partir de imagens diretamente seja mais eficiente, haja visto que como mencionado anteriormente, temos uma base de dados bastante pequena para esse exemplo. Também importamos do módulo models Sequential, que nos permitirá a criação de camadas de neurônios subsequentes e conectadas de forma sequencial entre si. Por fim da biblioteca Keras importamos backend que nada mais é do que um pacote de funções de entrada e de comunicação com outras funções que podem ser oriundas de outras bibliotecas. Encerrando esse processo importamos a biblioteca Numpy que permitirá a utilização de dados em forma de matrizes numéricas, assim como a aplicação de funções lógicas e aritméticas sobre este tipo de dado em particular. data_treino =

'C:/Users/Fernando/Desktop/CNNMINISET/dataset/train/' data_teste =

'C:/Users/Fernando/Desktop/CNNMINISET/dataset/test/'

Em seguida criamos as variáveis que instanciação nossas

imagens por meio de seus diretórios. Para isso, criamos data_treino e data_teste com seus respectivos diretórios absolutos (no caso de execução deste código em outra IDE é perfeitamente possível a utilização dos caminhos relativos ‘dataset/train’ e ‘dataset/test’)

 

if B.image_data_format() == 'channels_first':

input_shape = (3, 150, 150)

else:

input_shape = (150, 150, 3)

Na sequência criamos uma simples estrutura condicional que

funcionará como uma estrutura de validação, pois na mesma estaremos realizando a conversão de formato da matriz gerada para que a mesma possa ser corretamente lida pela biblioteca Numpy e processada. Por meio da função image_data_format( ) simplesmente realizamos a verificação se os dados de entrada estão no formato aceito em ‘channels_first’, onde o primeiro formato (3, 150, 150) era lido pela biblioteca Keras usando o antigo núcleo Theano, e em versões mais recentes, a biblioteca Keras passou a ser incorporada ao TensorFlow e dessa forma tem de carregar as matrizes no formato aceito pelo núcleo TensorFlow, ou seja, (150, 150, 3).

 

data_gen_treino = ImageDataGenerator(rescale = 1. / 255,

shear_range = 0.2,

zoom_range = 0.2,

horizontal_flip = True)

 

data_gen_teste = ImageDataGenerator(rescale = 1. / 255)

 

Seguindo com o código podemos criar o bloco responsável

por realizar o aumento de nossa base de dados, o mesmo é feito atribuindo as variáveis data_gen_treino e data_gen_teste a função ImageDataGenerator( ) que no primeiro caso recebe alguns parâmetros definidos manualmente. O primeiro deles, rescale, será responsável por escalonar as imagens carregadas para um formato proporcional e uniforme entre si; shear_range que definirá um nível de angulação (em graus) que será aplicado sobre as imagens; zoom_range define o nível de zoom aplicado a imagem e horizontal_flip faz o simples espelhamento das imagens. Para os dados de teste é feito apenas o escalonamento das imagens, mantendo o resto dos parâmetros em default.

 

gerador_treino =

data_gen_treino.flow_from_directory(data_treino, target_size = (150, 150), batch_size = 20, class_mode = 'categorical')

 

Realizadas as parametrizações de como as imagens serão

carregadas, criamos o gerador em si, com a variável gerador_treino que recebe os dados lidos pela função flow_from_directory( ), parametrizada com os dados de data_treino, escalonados para 150x150 pixels por meio do método target_size, assim como definimos que os ajustes serão realizados a cada 20 amostras por meio do método batch_size e por fim, já que estamos classificando múltiplos tipos de “objetos”, definimos class_mode como ‘categorical’. gerador_teste = data_gen_teste.flow_from_directory(data_teste, target_size = (150, 150), batch_size = 20, class_mode = 'categorical')

 

Exatamente o mesmo processo é realizado, dessa vez,

atribuindo os dados gerados a uma base aumentada preparada para ser usada em testes.

 

model = Sequential()

model.add(Conv2D(32, (3,3), input_shape = input_shape)) model.add(Activation('relu')) model.add(MaxPooling2D(pool_size = (2,2))) model.add(Conv2D(32, (3,3)))

model.add(Activation('relu')) model.add(MaxPooling2D(pool_size = (2, 2))) model.add(Conv2D(32, (3,3)))

model.add(Activation('relu')) model.add(MaxPooling2D(pool_size = (2, 2))) model.add(Flatten())

model.add(Dense(64))

 

Uma vez que temos nossos dados preparados, podemos partir

para criação da estrutura de rede neural em si. Sendo assim, criamos um objeto de nome model que receberá atribuído para si uma série de estruturas de código responsáveis pela codificação lógica da rede. Inicialmente atribuímos a model a função Sequential( ), sem parâmetros mesmo, que por sua vez nos permitirá a criação de uma rede onde as camadas de neurônios que forem adicionadas serão interconectadas entre si, lidas e processadas de forma sequencial. Em seguida, por meio de Conv2D( ) criamos a primeira camada de convolução, onde realizamos a transformação dos dados das imagens para uma matriz 3x3, dividida em 32 neurônios, a mesma recebe uma função de ativação relu (Rectified Linear Unit) sobre si. Em seguida usando da função MaxPooling2D( ) parametrizada manualmente com valor para pool_size é realizada a etapa onde tal estrutura é redimensionada para uma matriz 2x2. Note que esta etapa se repete três vezes, criando camadas de neurônios operando dessa forma para que, de acordo com os reconhecimentos de padrões realizados, os mesmos se tornem estruturas de perceptrons com seus respectivos pesos reajustados. Na sequência é criada uma camada sob a função Flatten( ) que por sua vez realiza a última conversão das matrizes processadas até então para uma escala unidimensional, que por sua vez se tornarão os neurônios de ativação da camada de entrada da rede densa (até o momento estávamos trabalhando apenas nas camadas de convolução). Também é aplicada uma função Dropout( ) que removerá, de acordo com o parâmetro setado, 5% de amostras escolhidas aleatoriamente e, por fim, uma camada de saída com 4 saídas, referentes a quantidade de tipos de objetos em questão, que por sua vez passa pela função de ativação softmax que produz melhores resultados quando estamos classificando amostras em mais de 2 categorias.

 

model.summary( )

Imagem 06 - Sumário da estrutura da rede neural artificial, mostrando todas suas camadas e interconexões

Por meio da função summary( ) é possível verificar de forma

visual toda a estrutura interna da rede neural.

 

model.compile(loss = 'categorical_crossentropy',

optimizer = 'rmsprop',

metrics = ['accuracy'])

Dando continuidade, é criada a estrutura responsável pela fase

de compilação da rede, por meio da função compile( ) atribuída a model, parametrizada com loss (função de “perda”) como ‘categorical_crossentropy’ que por sua vez agrupará as amostras de acordo com seus respectivos tipos, a medida que a rede for reconhecendo seus padrões; optimizer definido como ‘rmsprop’, método que fará a chamada descida do gradiente, onde de acordo com uma taxa de aprendizado, irá conseguir realizar os devidos ajustes dos pesos em cada categoria de forma a “aprender” quais são os melhores e mais estáveis valores para cada padrão reconhecido; Por fim, metrics definido como ‘accuracy’ estipula que os padrões devidamente reconhecidos deverão ser baseados em parâmetros de margem de precisão em acertos.

 

model.fit_generator(gerador_treino,

steps_per_epoch = 100,

epochs = 1000,

validation_data = gerador_teste,

validation_steps = 50)

 

Encerrando o bloco referente a estrutura da rede neural em si,

definimos por meio da função fit_generator( ) alguns parâmetros para a execução da rede. Primeiramente passamos os dados contidos em gerador_teste, seguido de steps_per_epoch que define quantas vezes serão realizados os devidos processamentos entre as camadas da rede; epochs estipula quantas vezes toda a rede neural será novamente executada (assumindo que a cada nova execução a mesma utiliza os melhores valores de pesos encontrados e parte para um novo processamento a fim de a cada rodada aprender melhor os padrões reconhecidos pela rede); validation_data recebe os dados gerados anteriormente e atribuídos a gerador_teste; validation_steps por sua vez define um número de testes a serem realizados comparando os padrões encontrados na base de treino com os da base de teste. Importante salientar que aqui, apenas para fins de exemplo, estamos executando essa rede neural uma quantidade de vezes relativamente pequena, para melhores resultados você pode realizar testes aumentando tais valores.

 

Imagem 07 - Início do processamento da rede neural artificial convolucional

 

Ao executar este bloco de código a rede começará a ser

executada e você pode acompanhar o progresso do processamento da mesma via terminal.

model.save_weights('weights.h5') model.save('model.h5')

 

Apenas realizando uma codificação adicional, uma vez a rede

neural executada por completo, podemos salvar o estado da mesma, no que diz respeito aos valores dos pesos encontrados, para que sejam reutilizados. A ideia é que uma vez treinada a rede, poderemos realizar testes com a mesma, sem a necessidade de realizar todo o processo de aprendizado de máquina novamente. Isso é feito simplesmente por meio da função save_weights( ) e save( ) passando como parâmetro os nomes dos respectivos arquivos, nesse caso, ‘weights.h5’ e ‘model.h5’.

Imagem 08 - Término do processamento da rede neural artificial convolucional

 

Terminado o processamento da rede neural podemos verificar

os resultados e com base nos mesmos, saber se a mesma, de acordo com seus valores percentuais, atingiu uma margem satisfatória de acertos em seu treino (loss / accuracy) e em suas fases de teste (val_loss / val_accuracy). Note que inicialmente a rede havia começado com loss: 0.3284 e accuracy: 0874, em outras palavras, uma margem de falsos positivos de 32% e uma margem de acertos de 87%. Ao final do processamento da mesma, loss: 0.0932 e accuracy: 0.9675. Esses números melhoraram consideravelmente, loss inferior a 1% e margem de acertos por volta de 96%, validados com números bastante próximos a isso. Na verdade, esse resultado é muito bom se levarmos em consideração que havíamos uma base de dados tão pequena e que a rede foi executada um número relativamente baixo de vezes, possivelmente teríamos resultados ainda melhores se não fosse por esses dois fatores.

img_teste = image.load_img('dataset/amostra_teste/normal.jpeg', target_size = (150, 150))

 

img_teste = image.img_to_array(img_teste) img_teste = np.expand_dims(img_teste, axis = 0)

 

Rede neural devidamente treinada podemos começar a

executar testes a partir das mesmas. A finalidade dessa rede em especial é ser a ferramenta onde, ao submetermos uma nova imagem para ser analisada, a mesma será processada e classificada de acordo com os padrões encontrados previamente pela rede. Nesse contexto, criamos uma variável de nome img_teste que por sua vez recebe como atributo a função load_img( ) parametrizada com o caminho onde temos uma imagem a ser submetida a teste, apenas definimos manualmente um segundo parâmetro chamado target_size onde a imagem ao ser carregada será escalonada, independentemente de seu tamanho original, para 150x150 pixels.

Em seguida por meio da função img_to_array( ) realizamos a conversão da mesma para uma matriz numérica em forma de array Numpy. Por fim, chamamos a função expand_dims( ) passando como parâmetro os dados de img_teste e estipulando que a mesma será convertida sobre seu eixo 0, ou seja, a imagem carregada será convertida em uma matriz que será empilhada para que esses dados se tornem aqueles neurônios que alimentarão a camada de entrada da rede.

 

resultado_teste = model.predict(img_teste) print(resultado_teste)

 

resultado_final = resultado_teste

print(resultado_final[0,3])

 

if resultado_final[0,0].any() > 0.5:

print('Esta radiografia apresenta as estruturas do Tórax sem alterações significativas..')

elif resultado_final[0,1].any() > 0.5:

print('Esta radiografia apresenta características compatíveis com PNEUMONIA')

elif resultado_final[0,2].any() > 0.5:

print('Esta radiografia apresenta características compatíveis com DERRAME PLEURAL')

elif resultado_final[0,3].any() > 0.5:

print('Esta radiografia apresenta características compatíveis com TUBERCULOSE')

else:

print('A imagem fornecida não cumpre os requisitos mínimos para ser processada,')

print('ou o resultado do processamento da mesma é inconclusivo.')

 

Na sequência criamos uma variável de nome resultado_teste

que por sua vez, por meio da função predict( ) realizará de fato o processamento e teste da imagem carregada. Também por meio da função print( ) exibimos no terminal qual foi o resultado da classificação (0, 1, 2 ou 3). E para deixar este resultado mais interessante criamos uma simples estrutura condicional onde de acordo com o resultado obtido é exibida uma mensagem de acordo.

Imagem 09 - Resultado de teste exibida em terminal

Nesse caso, submetemos uma imagem que sabíamos ser uma

radiografia normal e, como esperado, o processamento da rede encontrou essa característica, exibindo ao final do teste a referente mensagem para este fim.

 

Concluindo nosso raciocínio, apenas reforçando o que foi dito

no início deste capítulo, com uma simples estrutura de rede neural artificial convolucional, baseada na biblioteca Keras, conseguimos criar uma excelente ferramenta que pode servir como um segundo viés de confirmação para os laudos do profissional médico radiologista, contribuindo e muito com a precisão dos diagnósticos, consequentemente dos tratamentos dos pacientes que indiretamente se beneficiam do uso desta ferramenta.

 

Código completo disponível em:

https://github.com/fernandofeltrin/Ciencia-de-Dados-e-

Aprendizado-de-Maquina/

Documentação da biblioteca Keras (em inglês):

 

https://keras.io/

Você terminou Coletânea Python do ZERO às Redes Neurais Artificiais

Fernando Feltrin

Livros como este

Depois do livro

Páginas que vieram com esta edição, guardadas aqui para consulta.