Na jornada do aprendizado de máquina, compreender os algoritmos de otimização é fundamental. Entre essas técnicas, a descida de gradiente destaca-se como um método crucial para ajustar os parâmetros de um modelo e minimizar a função de custo. Neste artigo, exploraremos a implementação e a comparação dos algoritmos de descida de gradiente em três abordagens diferentes: Python puro, NumPy e TensorFlow.
Implementação da Descida de Gradiente em Python Puro
Para uma compreensão profunda do conceito de descida de gradiente, começaremos com uma implementação em Python puro. Considere o seguinte código, que estima os parâmetros de uma regressão linear:
def py_descent(x, d, mu, N_epochs):
N = len(x)
f = 2 / N
y = [0] * N
w = [0, 0]
grad = [0, 0]
for _ in range(N_epochs):
np.subtract(d, y, out=y)
grad[0] = f * sum(y)
grad[1] = f * sum(i * j for i, j in zip(y, x))
w = [i + mu * j for i, j in zip(w, grad)]
y = [w[0] + w[1] * i for i in x]
return w
mu = 0.001
N_epochs = 10000
t0 = time.time()
py_w = py_descent(x_list, d_list, mu, N_epochs)
t1 = time.time()
print(py_w)
print('Tempo de Solução: {:.2f} segundos'.format(round(t1 - t0, 2)))
Neste programa, estimamos os parâmetros w_0 e w_1 de uma regressão linear. A cada iteração, calculamos os gradientes e atualizamos os pesos para minimizar a função de custo.
Utilizando NumPy para Maior Eficiência
Apesar de educativa, a implementação anterior não é eficiente para conjuntos de dados grandes. Aqui, entra o NumPy, uma biblioteca poderosa para computação numérica. Veja como a implementação usando NumPy parece:
def np_descent(x, d, mu, N_epochs):
d = d.squeeze()
N = len(x)
f = 2 / N
y = np.zeros(N)
err = np.zeros(N)
w = np.zeros(2)
grad = np.empty(2)
for _ in range(N_epochs):
np.subtract(d, y, out=err)
grad[:] = f * np.sum(err), f * (err @ x)
w = w + mu * grad
y = w[0] + w[1] * x
return w
np_w = np_descent(x, d, mu, N_epochs)
print(np_w)
O uso extensivo de operações vetorizadas do NumPy melhora significativamente a eficiência do algoritmo.
Implementação com TensorFlow
O TensorFlow é uma biblioteca de código aberto desenvolvida inicialmente pela equipe Google Brain para computação numérica. Ele permite que você crie gráficos de operações para a execução eficiente de cálculos. Vamos ver como implementar o algoritmo de descida de gradiente com TensorFlow:
def tf_descent(X_tf, d_tf, mu, N_epochs):
N = X_tf.get_shape().as_list()[0]
f = 2 / N
w = tf.Variable(tf.zeros((2, 1)), name="w_tf")
y = tf.matmul(X_tf, w, name="y_tf")
e = y - d_tf
grad = f * tf.matmul(tf.transpose(X_tf), e)
training_op = tf.assign(w, w - mu * grad)
init = tf.global_variables_initializer()
with tf.Session() as sess:
init.run()
for _ in range(N_epochs):
sess.run(training_op)
opt = w.eval()
return opt
X_tf = tf.constant(X, dtype=tf.float32, name="X_tf")
d_tf = tf.constant(d, dtype=tf.float32, name="d_tf")
tf_w = tf_descent(X_tf, d_tf, mu, N_epochs)
print(tf_w)
Neste trecho de código, construímos um gráfico computacional com operações do TensorFlow para executar a descida de gradiente. O uso de tensores e gráficos computacionais é uma das principais características do TensorFlow.
Comparando Desempenho e Conclusão
Ao comparar os tempos de execução das três abordagens, temos:
- Implementação em Python puro: 18.65 segundos
- Implementação com NumPy: 0.32 segundos
- Implementação com TensorFlow: 1.20 segundos
O NumPy oferece uma melhoria considerável em relação ao Python puro, enquanto o TensorFlow, embora não tão eficiente quanto o NumPy, ainda oferece um desempenho significativamente melhor do que a abordagem pura.
Em resumo, compreender a descida de gradiente é essencial para a construção de modelos de aprendizado de máquina. Ao implementar e comparar esses algoritmos nas abordagens de Python puro, NumPy e TensorFlow, você ganha insights valiosos sobre a eficiência e a flexibilidade dessas ferramentas.