Aprendendo TensorFlow 2.0 — Regressão com tf.keras

Source: Deep Learning on Medium


⚠️ Esse é o primeiro post da série sobre TensorFlow 2.0 que estou fazendo. Se não quiser perder os outros post da série, é só me seguir no Medium.

A versão Alpha Release do TensorFlow 2.0 já saiu e com ela muitas novidades legais estão chegando (inclusive eu comentei sobre algumas delas nesse artigo). Uma das novidades mais legal é, sem dúvida, o módulo tf.keras, que além de ser uma interface melhorada do Keras, vai facilitar muito a vida de quem mexe com TensorFlow.

Nesse post, nós vamos aprender na prática um pouqinho sobre o tf.keras resolvendo problemas de regressão. Para quem não lembra, regressão é um tipo de problema de Aprendizagem Supervisionada onde a saída pode assumir qualquer valor real e não apenas um valor discreto (cachorro/gato, por exemplo). Dito isso, vamos resolver problemas de regressão linear, quadrática, cúbica, logarítmica e exponencial. E aí, bora lá?


💻 O notebook com o código encontra-se ao final desse post.

Regressão Linear 📈

O primeiro problemas que vamos resolver é um problema de regressão linear simples. Para gerar os nossos dados, vamos utilizar o método make_regression do scikit-learn:

x, y = make_regression(n_samples=100, n_features=1, noise=15, random_state=42)
y = y.reshape(-1, 1)
plt.scatter(x, y)

Ao rodar esse código, nós vamos obter o seguinte gráfico:

Tanto nesse quanto nos próximos problemas, a variável x vai representar a nossa variável independente, enquanto a variável y irá representar a variável dependente (resposta).

Com o problema em mãos, chegou a hora de construir o nosso modelo. Como esse é um problema de regressão linear simples, nós vamos projetar um Perceptron, ou seja, vamos criar uma rede com apenas um neurônio. Com o tf.keras, é muito fácil fazer isso:

model = tf.keras.models.Sequential()
model.add(tf.keras.layers.Dense(units=1, activation='linear', input_shape=(x.shape[1], )))

Repare que o input_shape é igual ao número de colunas de x (x.shape[1]), que representa a quantida de atributos (features) que nossos dados têm — nesse caso, apenas um atributo.

O próximo passo agora é compilar o nosso modelo. Isto é, vamos dizer ao nosso modelo basicamente 2 coisas: o otimizador que a rede vai utilizar para aprender o problema; e a métrica de perda (loss). No nosso caso, vamos utilizar o Stochastic Gradient Descent (SGD) como otimizador e a Mean Squared Error (MSE) como métrica de perda. Em código, isso se converte para:

model.compile(optimizer=tf.keras.optimizers.SGD(lr=0.1), loss='mse')

O último passo agora é treinar o nosso modelo! Para isso, é só chamar o método fit:

hist = model.fit(x, y, epochs=15, verbose=0)

Pronto! Nosso modelo já está treinado. Será que ele aprendeu bem? Para verificar isso, que tal darmos uma olhada no gráfico da loss e verificar a reta que o nosso algoritmo aprendeu?

Repare que a nossa perda se aproxima do zero lá para 4ª época e que a nossa reta se ajustou muito bem aos dados. Que tal comparar nosso modelo com o resultado do scikit-learn?

from sklearn.linear_model import LinearRegression
reg = LinearRegression()
reg.fit(x, y)
print(reg.coef_, reg.intercept_)
# [[45.78520483]] [1.74767298]

E agora, vamos imprimir os pesos que o nosso modelo aprendeu:

print(model.get_weights())
# [array([[46.445107]]), array([1.5304443]])

Agora que a gente resolveu esse problema, que tal a gente dificultar um pouquinho? 😉


Regressão Quadrática

Vamos agora resolver um problema de regressão quadrática, isto é, a nossa variável dependente (y) cresce quadraticamente em relação a nossa variável independente (x). Basicamente, vamos tentar resolver o seguinte gráfico:

nesse gráfico e nos demais, os valores de x foram normalizados entre [0, 1], enquanto y permanece inalterado

Como o nosso problema não é mais linear, vamos precisar de uma rede um pouco mais complexa para resolver o nosso problema. Primeiramente, vamos adicionar mais camadas com a função de ativação ReLU. Também vamos treinar a nossa rede por mais epochs e usar um otimizador mais avançado (Adam). Entretanto, a função de ativação da última camada continuará sendo a Linear. A função de perda também será a mesma.

model = tf.keras.models.Sequential()
model.add(tf.keras.layers.Dense(units=100, activation=’relu’, input_shape=(x.shape[1], )))
model.add(tf.keras.layers.Dense(units=100, activation=’relu’))
model.add(tf.keras.layers.Dense(units=100, activation=’relu’))
model.add(tf.keras.layers.Dense(units=1, activation=’linear’))
model.compile(optimizer=’adam’, loss=’mse’)
hist = model.fit(x, y, epochs=300, verbose=0)

Treinando essa rede, após 300 epochs, chegaremos nos seguintes resultados:

Ainda está muito fácil! Que tal complicar mais um pouquinho?


Regressão Cúbica

Será que a nossa rede consegue resolver um problema de regressão cúbica? Vamos tentar com a seguinte equação:

Para simplificar, vamos considerar que x está entre [-4, 4]. E, para dificultar, vamos adicionar um pouco de ruído:

x, y = make_cubic(n_samples=100, x_min=-4, x_max=4, a=1, b=0, c=-10, d=0, noise=3)

Utilizando a mesma rede que desenvolvemos na regressão quadrática, pelo mesmo número de epochs, nós vamos conseguir o seguinte resultado:

Tá ficando interessante! Mas, que tal dificultar um pouco mais ainda?


Regressão Logarítimica

O penúltimo problema que vamos resolver é uma regressão logarítmica. Ou seja, y = log(x). De novo, utilizando a mesma rede, chegamos nos seguinte resultado:

Bem legal, né?


Regressão Exponencial

Por fim, nosso último problema será resolver uma função exponencial, isto é, y = e^x. Novamente, utilizando a mesma rede:


Se liga aí que é hora da revisão!

Nesse post nós aprendemos a resolver diferentes problemas de regressão utilizando o tf.keras. De forma geral, isso é o que você deve fazer para resolver um problema de regressão:

  • a primeira camada é responsável por informar o input_shape. No caso de redes neurais, o valor do input_shape sempre será igual ao número de colunas dos seus dados — input_shape=(x.shape[1], );
  • A função de ativação da última camada deve ser sempre a Linear. Nas camadas escondidas, você pode utilizar qualquer uma;
  • A quantidade de neurônios na última camada deve ser sempre igual ao número de saídas que você tem. Em todos os problemas que resolvemos, tínhamos apenas 1 saída. Entretanto, existem problemas com mais de uma saída. Por exemplo, você poderia tentar estimar a idade e a altura de uma pessoa baseado em determinados atributos. Novamente, nas camadas escondidas, você pode utilizar quantos neurônios desejar;
  • A função de custo pode ser qualquer métrica desenvolvida para problemas de regressão. Nos nossos problemas, utilizamos a MSE. Mas, você também poderia utilizar a Média dos Erros Absolutos (MAE), Soma dos Erros Quadrados (SSE), Soma dos Erros Absolutos (SAE), etc.

Por fim, repare que utilizamos uma mesma rede (3 camadas escondidas com 100 neurônios e ativação ReLU) para resolver todos os problemas. Minha intenção foi mostrar que uma mesma arquitetura pode aprender a resolver problemas diferentes. Entretanto, vale salientar que essa rede pode não ser a mais indicada para cada problema e que ela também não é de propósito geral, ou seja, capaz de resolver qualquer problema de regressão.

Uma regra básica no treinamento de redes neurais é: tudo vai depender dos seus dados!

Gimme the code, please!

Todo o código desse post foi desenvolvido no Collab, uma ferramenta que roda Jupyter Notebooks sem precisar instalar nada desenvolvida pelo Google. Logo, você pode ter acesso ao código aqui. Se quiser rodar o código, é só clicar em File ➡️ Save a copy in Drive. Depois que a sua cópia for feita, você pode rodar o código à vontade!

Com o código em mãos, que tal fazer alguns testes nos problemas que vimos? Você pode tentar novas arquiteturas (com menos/mais camadas, menos/mais neurônios), diferentes funções de ativação, outros otimizadores, menos/mais epochs… Enfim, as possibilidades são infinitas. Deixe a sua criatividade tomar conta! Se conseguir algum resultado legal, compartilha aí nos comentários! 👍

Ah, e se você gostou do post, deixe algumas 👏👏👏. E não esquece: no próximo post vamos aprender a resolver problemas de classificação binária com TensorFlow 2.0. Fica ligado! Até lá.