Da resolução de equações ao aprendizado profundo: um tutorial do TensorFlow Python
Publicados: 2022-03-11Ultimamente, tem havido alguns desenvolvimentos notáveis no mundo da inteligência artificial, desde muito progresso divulgado com carros autônomos até máquinas que agora compõem imitações de Chopin ou apenas sendo realmente boas em videogames.
No centro desses avanços estão várias ferramentas para ajudar a derivar o aprendizado profundo e outros modelos de aprendizado de máquina, com Torch, Caffe e Theano entre os que estão em primeiro plano. No entanto, desde que o Google Brain tornou-se open source em novembro de 2015 com sua própria estrutura, TensorFlow, vimos a popularidade dessa biblioteca de software disparar para ser a estrutura de aprendizado profundo mais popular.
Por que isso aconteceu? Os motivos incluem a riqueza de suporte e documentação disponível, sua prontidão para produção, a facilidade de distribuição de cálculos em uma variedade de dispositivos e uma excelente ferramenta de visualização: TensorBoard.
Por fim, o TensorFlow consegue combinar um conjunto abrangente e flexível de recursos técnicos com grande facilidade de uso.
Neste artigo, você entenderá a mecânica dessa ferramenta usando-a para resolver um problema numérico geral, bem fora do que o aprendizado de máquina geralmente envolve, antes de introduzir seus usos no aprendizado profundo com uma implementação simples de rede neural.
Antes de você começar
Um conhecimento básico de métodos de aprendizado de máquina é assumido. Se você precisa se atualizar, confira este post muito útil.
Como demonstraremos a API Python, uma compreensão do Numpy também é benéfica.
Para configurar o TensorFlow, siga as instruções encontradas aqui.
Se você estiver usando o Windows, deve-se notar que, no momento da redação, você deve usar o Python 3.4+, não o 2.7.
Então, quando estiver pronto, você poderá importar a biblioteca com:
import tensorflow as tf
Etapa 1 de 2 para uma solução do TensorFlow: criar um gráfico
A construção de programas TensorFlow geralmente consiste em duas etapas principais, a primeira delas é construir um gráfico computacional, que descreverá os cálculos que você deseja realizar, mas não os realizará ou manterá nenhum valor.
Como em qualquer grafo, temos nós e arestas. As arestas representam tensores, um tensor que representa uma matriz n-dimensional. Por exemplo, um tensor com dimensão (ou rank no TensorFlow speak) 0 é um escalar, rank 1 um vetor, rank 2 uma matriz e assim por diante.
Os nós representam operações que produzem um tensor de saída, tomando tensores como entradas, se necessário. Tais operações incluem adições ( tf.add
), multiplicações de matrizes ( tf.matmul
), e também a criação de constantes ( tf.constant
).
Então, vamos combinar alguns deles para o nosso primeiro gráfico.
a = tf.constant([2.5, 2]) b = tf.constant([3, 6], dtype=tf.float32) total = tf.add(a, b)
Aqui criamos três operações, duas delas para criar arrays 1-d constantes.
Os tipos de dados são inferidos a partir do argumento de valores passado, ou você pode denotá-los com o argumento dtype
. Se eu não tivesse feito isso para b
, então um int32
teria sido inferido e um erro lançado como tf.add
estaria tentando definir uma adição em dois tipos diferentes.
Etapa 2 de 2 para uma solução do TensorFlow: execute as operações
O gráfico está definido, mas para realmente fazer cálculos nele (ou em qualquer parte dele), precisamos configurar uma sessão do TensorFlow.
sess = tf.Session()
Alternativamente, se estivermos executando uma sessão em um shell interativo, como IPython, usaremos:
sess = tf.InteractiveSession()
O método run
no objeto de sessão é uma maneira de avaliar um tensor.
Portanto, para avaliar o cálculo de adição definido acima, passamos 'total', o Tensor para recuperar, que representa a saída do tf.add
op.
print(sess.run(total)) # [ 5.5 8. ]
Neste ponto, apresentamos a classe Variable do TensorFlow. Enquanto as constantes são uma parte fixa da definição do gráfico, as variáveis podem ser atualizadas. O construtor da classe requer um valor inicial, mas mesmo assim, as variáveis precisam de uma operação para inicializá-las explicitamente antes que quaisquer outras operações sejam realizadas.
As variáveis mantêm o estado do gráfico em uma determinada sessão, portanto, devemos observar o que acontece com várias sessões usando o mesmo gráfico para entender melhor as variáveis.
# Create a variable with an initial value of 1 some_var = tf.Variable(1) # Create op to run variable initializers init_op = tf.global_variables_initializer() # Create an op to replace the value held by some_var to 3 assign_op = some_var.assign(3) # Set up two instances of a session sess1 = tf.Session() sess2 = tf.Session() # Initialize variables in both sessions sess1.run(init_op) sess2.run(init_op) print(sess1.run(some_var)) # Outputs 1 # Change some_var in session1 sess1.run(assign_op) print(sess1.run(some_var)) # Outputs 3 print(sess2.run(some_var)) # Outputs 1 # Close sessions sess1.close() sess2.close()
Configuramos o gráfico e duas sessões.
Depois de executar a inicialização em ambas as sessões (se não executarmos isso e depois avaliarmos a variável, encontramos um erro), executamos apenas a operação de atribuição em uma sessão. Como se pode ver, o valor da variável persiste, mas não entre as sessões.
Alimentando o gráfico para resolver problemas numéricos
Outro conceito importante do TensorFlow é o espaço reservado. Enquanto as variáveis mantêm o estado, os espaços reservados são usados para definir quais entradas o gráfico pode esperar e seu tipo de dados (e, opcionalmente, sua forma). Em seguida, podemos alimentar os dados no gráfico por meio desses espaços reservados quando executamos o cálculo.
O gráfico do TensorFlow está começando a se assemelhar às redes neurais que queremos treinar, mas antes disso, vamos usar os conceitos para resolver um problema numérico comum do mundo financeiro.
Suponha que queremos encontrar y
em uma equação como esta:
para um dado v
(com C
e P
constantes).
Esta é uma fórmula para calcular o rendimento até o vencimento ( y
) de um título com valor de mercado v
, principal P
e cupom C
pagos semestralmente, mas com os fluxos de caixa descontados com capitalização contínua.
Basicamente, temos que resolver uma equação como essa com tentativa e erro, e escolheremos o método da bissecção para zerar nosso valor final para y
.
Primeiro, modelaremos esse problema como um gráfico do TensorFlow.
C
e P
são constantes fixas e fazem parte da definição do nosso gráfico. Desejamos ter um processo que refina os limites inferior e superior de y
. Portanto, esses limites (denotados a
e b
) são bons candidatos para variáveis que precisam ser alteradas após cada tentativa de y
(considerado o ponto médio de a
e b
).
# Specify the values our constant ops will output C = tf.constant(5.0) P = tf.constant(100.0) # We specify the initial values that our lower and upper bounds will be when initialised. # Obviously the ultimate success of this algorithm depends on decent start points a = tf.Variable(-10.0) b = tf.Variable(10.0) # We expect a floating number to be inserted into the graph v_target = tf.placeholder("float") # Remember the following operations are definitions, # none are carried out until an operation is evaluated in a session! y = (a+b)/2 v_guess = C*tf.exp(-0.5*y) + C*tf.exp(-y) + C*tf.exp(-1.5*y) + (C + P)*tf.exp(-2*y) # Operations to set temporary values (a_ and b_) intended to be the next values of a and b. # eg if the guess results in av greater than the target v, # we will set a_ to be the current value of y a_ = tf.where(v_guess > v_target, y, a) b_ = tf.where(v_guess < v_target, y, b) # The last stage of our graph is to assign the two temporary vals to our variables step = tf.group( a.assign(a_), b.assign(b_) )
Portanto, agora temos uma lista de operações e variáveis, qualquer uma das quais pode ser avaliada em relação a uma determinada sessão. Algumas dessas operações dependem de outras operações para serem executadas, portanto, executar, digamos, v_guess
desencadeará uma reação em cadeia para que outros tensores, como C
e P
, sejam avaliados primeiro.

Algumas dessas operações dependem de um espaço reservado para o qual um valor precisa ser especificado, então, como realmente alimentamos esse valor?
Isso é feito através do argumento feed_dict
na própria função de run
.
Se quisermos avaliar a_
, inserimos o valor para nosso espaço reservado v_target
, assim:
sess.run(a_, feed_dict={v_target: 100})
nos dando 0,0.
Conecte um v_target
de 130 e obtemos -10.0.
É a nossa operação “step” que na verdade requer que todas as outras operações sejam executadas como pré-requisito e, na verdade, executa o gráfico inteiro. É também uma operação que realmente altera o estado real em nossa sessão. Portanto, quanto mais executamos a etapa, mais empurramos incrementalmente nossas variáveis a
e b
em direção ao valor real de y
.
Então, digamos que nosso valor para v
em nossa equação seja igual a 95. Vamos configurar uma sessão e executar nosso gráfico nela 100 vezes.
# Set up a session and initialize variables sess = tf.Session() tf.global_variables_initializer().run() # Run the step operation (and therefore whole graph) 100 times for i in range (100): sess.run(step, feed_dict={v_target:95})
Se avaliarmos o tensor y
agora, obteremos algo parecido com uma resposta desejável
print(sess.run(y)) # 0.125163
Redes neurais
Agora que entendemos a mecânica do TensorFlow, podemos reunir isso com algumas operações adicionais de machine learning incorporadas ao TensorFlow para treinar uma rede neural simples.
Aqui, gostaríamos de classificar os pontos de dados em um sistema de coordenadas 2d dependendo se eles se enquadram em uma determinada região - um círculo de raio 0,5 centrado na origem.
Claro, isso pode ser verificado concretamente apenas verificando um determinado ponto (a,b)
se a^2 + b^2 < 0.5
, mas para os propósitos deste experimento de aprendizado de máquina, gostaríamos de passar um conjunto de treinamento: uma série de pontos aleatórios e se eles se enquadram na região pretendida. Aqui está uma maneira de criar isso:
import numpy as np NO_OF_RANDOM_POINTS = 100 CIRCLE_RADIUS = 0.5 random_spots = np.random.rand(NO_OF_RANDOM_POINTS, 2) * 2 - 1 is_inside_circle = (np.power(random_spots[:,0],2) + np.power(random_spots[:,1],2) < CIRCLE_RADIUS).astype(int)
Faremos uma rede neural com as seguintes características:
- Consiste em uma camada de entrada com dois nós, na qual alimentamos nossa série de vetores bidimensionais contidos em “random_spots”. Isso será representado por um espaço reservado aguardando os dados de treinamento.
- A camada de saída também terá dois nós, portanto, precisamos alimentar nossa série de rótulos de treinamento (“is_inside_circle”) em um espaço reservado para um escalar e, em seguida, converter esses valores em um vetor bidimensional único.
- Teremos uma camada oculta composta por três nós, então precisaremos usar variáveis para nossas matrizes de pesos e vetores de viés, pois esses são os valores que precisam ser refinados ao realizar o treinamento.
INPUT_LAYER_SIZE = 2 HIDDEN_LAYER_SIZE = 3 OUTPUT_LAYER_SIZE = 2 # Starting values for weights and biases are drawn randomly and uniformly from [-1, 1] # For example W1 is a matrix of shape 2x3 W1 = tf.Variable(tf.random_uniform([INPUT_LAYER_SIZE, HIDDEN_LAYER_SIZE], -1, 1)) b1 = tf.Variable(tf.random_uniform([HIDDEN_LAYER_SIZE], -1, 1)) W2 = tf.Variable(tf.random_uniform([HIDDEN_LAYER_SIZE, OUTPUT_LAYER_SIZE], -1, 1)) b2 = tf.Variable(tf.random_uniform([OUTPUT_LAYER_SIZE], -1, 1)) # Specifying that the placeholder X can expect a matrix of 2 columns (but any number of rows) # representing random spots X = tf.placeholder(tf.float32, [None, INPUT_LAYER_SIZE]) # Placeholder Y can expect integers representing whether corresponding point is in the circle # or not (no shape specified) Y = tf.placeholder(tf.uint8) # An op to convert to a one hot vector onehot_output = tf.one_hot(Y, OUTPUT_LAYER_SIZE)
Para completar a definição do nosso gráfico, definimos algumas operações que nos ajudarão a treinar as variáveis para chegar a um classificador melhor. Estes incluem os cálculos de matriz, funções de ativação e otimizador.
LEARNING_RATE = 0.01 # Op to perform matrix calculation X*W1 + b1 hidden_layer = tf.add(tf.matmul(X, W1), b1) # Use sigmoid activation function on the outcome activated_hidden_layer = tf.sigmoid(hidden_layer) # Apply next weights and bias (W2, b2) to hidden layer and then apply softmax function # to get our output layer (each vector adding up to 1) output_layer = tf.nn.softmax(tf.add(tf.matmul(activated_hidden_layer, W2), b2)) # Calculate cross entropy for our loss function loss = -tf.reduce_sum(onehot_output * tf.log(output_layer)) # Use gradient descent optimizer at specified learning rate to minimize value given by loss tensor train_step = tf.train.GradientDescentOptimizer(LEARNING_RATE).minimize(loss)
Tendo configurado nosso gráfico, é hora de configurar uma sessão e executar o “train_step” (que também executa qualquer operação de pré-requisito). Algumas dessas operações usam marcadores de posição, portanto, os valores para eles precisam ser fornecidos. Esta etapa de treinamento representa uma época em nosso algoritmo de aprendizado e, como tal, é feita um loop sobre o número de épocas que desejamos executar. Podemos executar outras partes do gráfico, como o tensor de “perda” para fins informativos.
EPOCH_COUNT = 1000 sess = tf.Session() tf.global_variables_initializer().run() for i in range(EPOCH_COUNT): if i%100 == 0: print('Loss after %d runs: %f' % (i, sess.run(loss, feed_dict={X: random_spots, Y: is_inside_circle}))) sess.run(train_step, feed_dict={X: random_spots, Y: is_inside_circle}) print('Final loss after %d runs: %f' % (i, sess.run(loss, feed_dict={X: random_spots, Y: is_inside_circle})))
Depois de treinarmos o algoritmo, podemos alimentar um ponto e obter a saída da rede neural da seguinte forma:
sess.run(output_layer, feed_dict={X: [[1, 1]]}) # Hopefully something close to [1, 0] sess.run(output_layer, feed_dict={X: [[0, 0]]}) # Hopefully something close to [0, 1]
Podemos classificar o ponto como fora do círculo se o primeiro membro do vetor de saída for maior que 0,5, dentro caso contrário.
Ao executar o tensor output_layer
para muitos pontos, podemos ter uma ideia de como o aluno imagina a região que contém os pontos classificados positivamente. Vale a pena brincar com o tamanho do conjunto de treinamento, a taxa de aprendizado e outros parâmetros para ver o quão perto podemos chegar do círculo pretendido.
Taxa de aprendizagem: 0,01
Épocas: 1000
Taxa de aprendizagem: 0,01
Épocas: 1000
Taxa de aprendizagem: 0,01
Épocas: 10.000
Taxa de aprendizagem: 0,001
Épocas: 10.000
Embrulhar
Esta é uma boa lição de que um maior conjunto de treinamento ou quantidade de época não é garantia para um bom aluno - a taxa de aprendizado deve ser ajustada adequadamente.
Esperamos que essas demonstrações tenham lhe dado uma boa visão dos princípios básicos do TensorFlow e forneçam uma base sólida para implementar técnicas mais complexas.
Não abordamos conceitos como o Tensorboard ou o treinamento de nossos modelos em GPUs, mas eles são bem abordados na documentação do TensorFlow. Várias receitas podem ser encontradas na documentação que podem ajudá-lo a se familiarizar com as tarefas emocionantes de aprendizado profundo usando essa estrutura poderosa!