In [ ]:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import accuracy_score
from mpl_toolkits.mplot3d import Axes3D
%matplotlib inline
A Regressão Logística, apesar do nome, é uma técnica utilizada para fazer classificação binária. Nesse caso, ao invés de prever um valor contínuo, a nossa saída é composta de apenas dois valores: 0 ou 1, em geral. Para fazer a regressão logística, utilizamos como função de ativação a função conhecida como sigmoid. Tal função, é descrita pela seguinte fórmula:
$$\widehat{y} = \frac{1}{1+e^{-z}} = \frac{e^z}{1+e^z}$$No caso de redes neurais, em geral consideramos $z(w,b) = xw^T + b$.
A função de custo da regressão logística é chamada de entropia cruzada (do inglês, cross-entropy) e é definida pela seguinte fórmula:
$$J(z) = -\frac{1}{N}\sum_{i}^N y_i\log(\widehat{y}_i) + (1-y_i)\log(1-\widehat{y}_i)$$Onde $N$ é quantidade de amostras e $y_i$ representa o valor da $i$-ésima amostra (0 ou 1). Lembrando que $\widehat{y}_i$ é agora calculada agora utilizando a função sigmoid, como mostrado na seção anterior.
Repare também que:
Para calcular a derivada da nossa função de custo $J(z)$, primeiramente vamos calcular $\log(\widehat{y}_i)$:
$$\log(\widehat{y}_i) = log\frac{1}{1+e^{-z}} = log(1) - log(1+e^{-z}) = -log(1+e^{-z})$$E $\log(1-\widehat{y}_i)$:
$$\log(1-\widehat{y}_i) = log \left(1-\frac{1}{1+e^{-z}}\right) = log(e^{-z}) - log(1+e^{-z}) = -z -log(1+e^{-z})$$Substituindo as duas equações anteriores na fórmula da função de custo, temos:
$$J(z) = -\frac{1}{N}\sum_{i}^N \left[-y_i\log(1+e^{-z}) + (1-y_i)(-z -\log(1+e^{-z}))\right]$$Efetuando as distribuições, podemos simplificar a equação acima para:
$$J(z) = -\frac{1}{N}\sum_{i}^N \left[y_iz -z -\log(1+e^{-z})\right]$$Uma vez que:
$$-z -\log(1+e^{-z}) = -\left[\log e^{z} + log(1+e^{-z})\right] = -log(1+e^z)$$Temos:
$$J(z) = -\frac{1}{N}\sum_{i}^N \left[y_iz -\log(1+e^z)\right]$$Como a derivada da diferença é igual a diferença das derivadas, podemos calcular cada derivada individualmente em relação a $w$:
$$\frac{\partial}{\partial w_i}y_iz = y_ix_i,\quad \frac{\partial}{\partial w_i}\log(1+e^z) = \frac{x_ie^z}{1+e^z} = x_i \widehat{y}_i$$e em relação à $b$:
$$\frac{\partial}{\partial b}y_iz = y_i,\quad \frac{\partial}{\partial b}\log(1+e^z) = \frac{e^z}{1+e^z} = \widehat{y}_i$$Assim, a derivada da nossa função de custo $J(z)$ é:
$$\frac{\partial}{\partial w_i}J(z) = \sum_i^N (y_i - \widehat{y}_i)x_i$$$$\frac{\partial}{\partial b}J(z) = \sum_i^N (y_i - \widehat{y}_i)$$Por fim, repare que o gradiente de J ($\nabla J$) é exatamente o mesmo que o gradiente da função de custo do Perceptron Linear. Portanto, os pesos serão atualizados da mesma maneira. O que muda é a forma como calculamos $\widehat{y}$ (agora usando a função sigmoid) e a função de custo $J$.
In [ ]:
df = pd.read_csv('data/anuncios.csv')
print(df.shape)
df.head()
In [ ]:
x, y = df.idade.values.reshape(-1,1), df.comprou.values.reshape(-1,1)
print(x.shape, y.shape)
In [ ]:
plt.scatter(x, y, c=y, cmap='bwr')
plt.xlabel('idade')
plt.ylabel('comprou?')
In [ ]:
minmax = MinMaxScaler(feature_range=(-1,1))
x = minmax.fit_transform(x.astype(np.float64))
print(x.min(), x.max())
Vamos utilizar o sklearn como gabarito para nossa implementação. Entretanto, como a Regressão Logística do sklearn faz uma regularização L2 automaticamente, temos de definir $C=10^{15}$ para "anular" a regularização. O parâmetro $C$ define a inversa da força da regularização (ver documentação). Logo, quanto menor for o $C$, maior será a regularização e menores serão os valores dos pesos e bias.
In [ ]:
clf_sk = LogisticRegression(C=1e15)
clf_sk.fit(x, y.ravel())
print(clf_sk.coef_, clf_sk.intercept_)
print(clf_sk.score(x, y))
In [ ]:
x_test = np.linspace(x.min(), x.max(), 100).reshape(-1,1)
y_sk = clf_sk.predict_proba(x_test)
plt.scatter(x, y, c=y, cmap='bwr')
plt.plot(x_test, y_sk[:,1], color='black')
plt.xlabel('idade')
plt.ylabel('comprou?')
In [ ]:
# implemente a função sigmoid aqui
In [ ]:
# implemente o neurônio sigmoid aqui
In [ ]:
x_test = np.linspace(x.min(), x.max(), 100).reshape(-1,1)
y_sk = clf_sk.predict_proba(x_test)
y_pred = sigmoid(np.dot(x_test, w.T) + b)
plt.scatter(x, y, c=y, cmap='bwr')
plt.plot(x_test, y_sk[:,1], color='black', linewidth=7.0)
plt.plot(x_test, y_pred, color='yellow')
plt.xlabel('idade')
plt.ylabel('comprou?')
In [ ]:
print('Acurácia pelo Scikit-learn: {:.2f}%'.format(clf_sk.score(x, y)*100))
y_pred = np.round(sigmoid(np.dot(x, w.T) + b))
print('Acurária pela nossa implementação: {:.2f}%'.format(accuracy_score(y, y_pred)*100))
In [ ]:
x, y = df[['idade', 'salario']].values, df.comprou.values.reshape(-1,1)
print(x.shape, y.shape)
In [ ]:
fig = plt.figure(figsize=(8,6))
ax = fig.add_subplot(111, projection='3d')
ax.scatter3D(x[:,0], x[:,1], y, c=y.ravel())
In [ ]:
minmax = MinMaxScaler(feature_range=(-1,1))
x = minmax.fit_transform(x.astype(np.float64))
print(x.min(), x.max())
In [ ]:
clf_sk = LogisticRegression(C=1e15)
clf_sk.fit(x, y.ravel())
print(clf_sk.coef_, clf_sk.intercept_)
print(clf_sk.score(x, y))
In [ ]:
D = x.shape[1]
w = 2*np.random.random((1, D))-1 # [1x2]
b = 2*np.random.random()-1 # [1x1]
learning_rate = 1.0 # <- tente estimar a learning rate
for step in range(1): # <- tente estimar a #epochs
# calcule a saida do neuronio sigmoid
z =
y_pred =
error = y - y_pred # [400x1]
w = w + learning_rate*np.dot(error.T, x)
b = b + learning_rate*error.sum()
if step%100 == 0:
# implemente a entropia cruzada (1 linhas)
cost =
print('step {0}: {1}'.format(step, cost))
print('w: ', w)
print('b: ', b)
In [ ]:
x1 = np.linspace(x[:, 0].min(), x[:, 0].max())
x2 = np.linspace(x[:, 1].min(), x[:, 1].max())
x1_mesh, x2_mesh = np.meshgrid(x1, x2)
x1_mesh = x1_mesh.reshape(-1, 1)
x2_mesh = x2_mesh.reshape(-1, 1)
x_mesh = np.hstack((x1_mesh, x2_mesh))
y_pred = sigmoid(np.dot(x_mesh, w.T) + b)
fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d')
ax.scatter3D(x[:,0], x[:,1], y, c=y.ravel())
ax.plot_trisurf(x1_mesh.ravel(), x2_mesh.ravel(), y_pred.ravel(), alpha=0.3, shade=False)
In [ ]:
print('Acurácia pelo Scikit-learn: {:.2f}%'.format(clf_sk.score(x, y)*100))
y_pred = np.round(sigmoid(np.dot(x, w.T) + b))
print('Acurária pela nossa implementação: {:.2f}%'.format(accuracy_score(y, y_pred)*100))