# 1. Regressão Linear
## 1.1. Univariada

Existem diversos problemas na natureza para os quais procura-se obter valores de saída dado um conjunto de dados de entrada. Suponha o problema de predizer os valores de imóveis de uma determinada cidade, conforme apresentado na Figura 1, em que podemos observer vários pontos que representam diferentes imóveis, cada qual com seu preço de acordo com o seu tamanho. 

Em problemas de **regressão**, objetiva-se estimar valores de saída de acordo com um conjunto de valores de entrada. Desta forma, considerando o problema anterior, a ideia consiste em estimar o preço de uma casa de acordo com o seu tamanho, isto é, gostaríamos de encontrar uma **linha reta** que melhor se adequa ao conjunto de pontos na Figura 1.


```python
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split 
from sklearn.linear_model import LinearRegression
from sklearn import metrics
from matplotlib import pyplot
import numpy
```


```python
# gerando um conjunto de pontos aleatórios para um problema de regressão linear ***
x, y = make_regression(n_samples=100, n_features=1, noise=5.7)

# apresenta o conjunto de dados criado no passo anterior ***
fig = pyplot.figure(figsize=(15,7))

pyplot.subplot(1, 2, 1)
pyplot.scatter(x,y)
pyplot.xlabel("Tamanho ($m^2$)")
pyplot.ylabel("Preço (R\$x$10^3$)")
pyplot.title("(a)")

# executando regressor linear
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=0) #criando partições
model = LinearRegression()  
model.fit(x_train, y_train) # treinando o algoritmo

pyplot.subplot(1, 2, 2)
pyplot.scatter(x,y)
pyplot.plot(x, model.predict(x), color = 'red')
pyplot.xlabel("Tamanho ($m^2$)")
pyplot.ylabel("Preço (R\$x$10^3$)")
pyplot.title("(b)")

fig.tight_layout(pad=10)
fig.suptitle("Figura 1: Exemplo de conjunto de dados de valores imobiliários: (a) conjunto de dados de entrada e (b) linha reta estimada via regressão linear.", y=0.18)
pyplot.show()
```

Seja um conjunto de dados ${\cal D}=\{(x_1,y_1),(x_2,y_2),\ldots,(x_m,y_m)\}$ tal que $x_i\in\Re$ denota o conjunto dos dados de **entrada** (isto é, o tamanho da casa) e $y_i\in\Re$ representa o seu valor. Além disso, seja ${\cal D}_{tr}\subset {\cal D}$ o chamado **conjunto de treinamento** e ${\cal D}_{ts}\subset {\cal D}\backslash{\cal D}_{tr}$ o **conjunto de teste**. Usualmente, técnicas de aprendizado de máquina são avaliadas em conjuntos de treinamento e teste disjuntos, ou seja, temos que ${\cal D}_{tr}$ e ${\cal D}_{ts}$ são denominados **partições** do conjunto original ${\cal D}$. Em nosso exemplo, temos que $x_i$ e $y_i$ correspondem ao tamanho e preço do imóvel, respectivamente.

Basicamente, um algoritmo de regressão linear recebe como entrada um conjunto de dados de treinamento e objetiva estimar uma função linear (reta) a qual chamamos de **função hipótese**, dada por:
\begin{equation}
h_\textbf{w}(x) = w_0+w_1x,
\tag{1}
\end{equation}
em que $\textbf{w}=[w_0\ w_1]$ corresponde aos parâmetros do modelo, sendo que $w_0,w_1\in\Re$. Dependendo dos valores assumidos por $\textbf{w}$, a função hipótese pode assumir diferentes comportamentos, conforme ilustra a Figura 2.


```python
fig = pyplot.figure(figsize=(15,7))

x = numpy.arange(-10, 10, 0.5)

pyplot.subplot(2, 2, 1)
y = 1.5 + 0*x #h_w(x) = 1.5 + w_1*0
pyplot.plot(x, y, color = "red")
pyplot.title("$h_w(x) = 1.5$   $(w_0 = 1$ e $w_1 = 1)$")

pyplot.subplot(2, 2, 2)
y = 0 + 0.5*x #h_w(x) = 0 + 0.5*x
pyplot.plot(x, y, color = "red")
pyplot.title("$h_w(x) = 0.5x$   $(w_0 = 0$ e $w_1 = 0.5)$")

pyplot.subplot(2, 2, 3)
y = 1 + 0.5*x #h_w(x) = 1 + 0.5*x
pyplot.plot(x, y, color = "red")
pyplot.title("$h_w(x) = 1 + 0.5x$   $(w_0 = 1$ e $w_1 = 0.5)$")

pyplot.subplot(2, 2, 4)
y = 0 - 0.5*x #h_w(x) = 0 - 0.5*x
pyplot.plot(x, y, color = "red")
pyplot.title("$h_w(x) = -0.5x$   $(w_0 = 0$ e $w_1 = -0.5)$")

fig.tight_layout(pad=2)
fig.suptitle("Figura 2: Exemplos de diferentes funções hipótese.", y=0.01)
pyplot.show()
```

De maneira geral, o objetivo da regressão linear é encontrar valores para $\textbf{w}=[w_0\ w_1]$ de tal forma que $h_w(x_i)$ é o mais próximo possível de $y_i$ considerando o conjunto de treinamento ${\cal D}_{tr}$, $\forall i\in\{1,2,\ldots,m^\prime\}$, em que $m^\prime=\left|{\cal D}_{tr}\right|$. Em outras palavras, o objetivo consiste em resolver o seguinte problema de minimização:

\begin{equation}
\label{e.mse}
\underset{\textbf{w}}{\operatorname{argmin}}\frac{1}{2m^\prime}\sum_{i=1}^{m^\prime}(h_\textbf{w}(x_i)-y_i)^2.
\tag{3}
\end{equation}
Essa equação é também conhecida por **erro médio quadrático**, do inglês *Minimum Square Error* (MSE). Uma outra denominação bastante comum é a de **função de custo**. Note que $h_w(x_i)$ representa o **preço estimado** do imóvel pela técnica de regressão linear, ao passo que $y_i$ denota o seu **valor rea**l dado pelo conjunto de treinamento.

Podemos simplificar a Equação \ref{e.mse} e reescrevê-la da seguinte maneira:

\begin{equation}
\label{e.mse_simplified}
\underset{\textbf{w}}{\operatorname{argmin}}J(\textbf{w}),
\tag{4}
\end{equation}
em que $J(\textbf{w})=\frac{1}{2m^\prime}\sum_{i=1}^{m^\prime}(h_\textbf{w}(x_i)-y_i)^2$. Partindo desta premissa, vamos simplificar um pouco mais a notação e assumir que nossa função hipótese cruza a origem do plano cartesiano:

\begin{equation}
\label{e.hypothesis_origin}
h_w(\textbf{x}) = w_1x,
\tag{5}
\end{equation}
ou seja, $w_0=0$. Neste caso, nosso problema de otimização restringe-se a encontrar $w_1$ que minimiza a seguinte equação:

\begin{equation}
\label{e.mse_simplified_origin}
\underset{w_1}{\operatorname{argmin}}J(w_1).
\tag{6}
\end{equation}

Como exemplo, suponha o seguinte conjunto de treinamento ${\cal D}_{tr}=\{(1,1),(2,2),(3,3)\}$, o qual é ilustrado na Figura 3a. Como pode ser observado, a função hipótese que modela esse conjunto de treinamento é dada por $h_\textbf{w}(x)=x$, ou seja, $\textbf{w}=[0\ 1]$, conforme apresentado na Figura 3b.


```python
fig = pyplot.figure(figsize=(13,7))
x = numpy.arange(1, 4, 1)

pyplot.subplot(2, 2, 1)
y = x #h_w(x) = x
pyplot.scatter(x,y)
pyplot.title("(a)")

pyplot.subplot(2, 2, 2)
pyplot.scatter(x,y)
pyplot.plot(x, x, color = "red")
pyplot.title("(b)")

fig.suptitle("Figura 3: (a) conjunto de treinamento ($m^\prime=3$) e (b) função hipótese que intercepta os dados.", y=0.47)

pyplot.show()
```

Na prática, a ideia consiste em testar diferentes valores de $w_1$ e calcular o valor de $J(w_1)$. Aquele que minimizar a função de custo, é o valor de $w_1$ a ser utilizado no modelo (função hipótese). Suponha que tomemos como valor inicial $w_1 = 1$, ou seja, o "chute" correto. Neste caso, temos que:

\begin{equation}
\begin{split}
J(w_1) & =\frac{1}{2m^\prime}\sum_{i=1}^{m^\prime}(h_\textbf{w}(x_i)-y_i)^2 \\
 & = \frac{1}{2m^\prime}\sum_{i=1}^{m^\prime}(w_1x_i-y_i)^2 \\
 & = \frac{1}{2\times3}\left[(1-1)^2+(2-2)^2+(3-3)^2\right] \\
 & = \frac{1}{6}\times 0 = 0.
\end{split}
\tag{7}
\end{equation}
Neste caso, temos que $J(w_1) = 0$ para $w_1 = 1$, ou seja, o custo é o menor possível dado que achamos a função hipótese **exata** que intercepta os dados. Agora, suponha que tivéssemos escolhido $w_1 = 0.5$. Neste caso, a funcão de custo seria calculada da seguinte forma:

\begin{equation}
\begin{split}
J(w_1) & =\frac{1}{2m^\prime}\sum_{i=1}^{m^\prime}(h_\textbf{w}(x_i)-y_i)^2 \\
 & = \frac{1}{2m^\prime}\sum_{i=1}^{m^\prime}(w_1x_i-y_i)^2 \\
 & = \frac{1}{2\times3}\left[(0.5-1)^2+(1-2)^2+(1.5-3)^2\right] \\
 & = \frac{1}{6}\times (0.25+1+2.25) \approx 0.58.
\end{split}
\tag{8}
\end{equation}
Neste caso, nosso erro foi ligeiramente maior. Caso continuemos a calcular $J(w_1)$ para diferentes valores de $w_1$, obteremos o gráfico ilustrado na Figura 4.


```python
def J(w_1, x, y):
    
    error = numpy.zeros(len(w_1))
    for i in range(len(w_1)):
        error[i] = 0
        for j in range(3):
            error[i] = error[i] + numpy.power(w_1[i]*x[j]-y[j], 2)
    return error

w_1 = numpy.arange(-7,10,1) #criando um vetor 
error = J(w_1, x, y) 

pyplot.plot(w_1, error, color = "red")
pyplot.xlabel("$w_1$")
pyplot.ylabel("$J(w_1)$")
pyplot.title("Figura 4: Comportamento da função de custo para diferentes valores de $w_1$.", y=-0.27)
pyplot.show()
```

Portanto, temos que $w_1=1$ é o valor que minimiza $J(w_1)$ para o exemplo citado anteriormente. Voltando à função de custo dada pela Equação \ref{e.mse}, a questão que temos agora é: como podemos encontrar valores plausíveis para o vetor de parâmetros $\textbf{w}=[w_0\ w_1]$. Uma abordagem simples seria testar uma combinação de valores aleatórios para $w_0$ e $w_1$ e tomar aqueles que minimizam $J(\textbf{w})$. Entretanto, essa heurística não garante que bons resultados sejam alcançados, principalmente em situações mais complexas.

Uma abordagem bastante comum para esse problema de otimização é fazer uso da ténica conhecida por **Gradiente Descentente** (GD), a qual consiste nos seguintes passos gerais:
1. Escolha valores aleatórios para $w_0$ e $w_1$.
2. Iterativamente, modifique os valores de $w_0$ e $w_1$ de tal forma a minimizar $J(\textbf{w})$.

A grande questão agora é qual heurística utilizar para atualizar os valores do vetor $\textbf{w}$. A técnica de GD faz uso das **derivadas parciais** da função de custo para guiar o processo de otimização no sentido do mínimo da função por meio da seguinte regra de atualização dos pesos:

\begin{equation}
\label{e.update_rule_GD}
w^{t+1}_j = w^{t}_j - \alpha\frac{\partial J(\textbf{w})}{\partial w_j},\ j\in\{0,1\},
\tag{9}
\end{equation}
em que $\alpha$ corresponde à chamada **taxa de aprendizado**.

Um questionamento bastante comum diz repeito ao termo derivativo, ou seja, como podemos calculá-lo. Para fins de explicação, suponha que tenhamos apenas o parâmetro $w_1$ para otimizar, ou seja, a nossa função hipótese é dada pela Equação \ref{e.hypothesis_origin}. Neste caso, o objetivo é minimizar $J(w_1)$ para algum valor de $w_1$. Na prática, o que significa a derivada em um dado ponto? A Figura \ref{f.derivada} ilustra melhor esta situação.


```python
# Código basedo em https://stackoverflow.com/questions/54961306/how-to-plot-the-slope-tangent-line-of-parabola-at-any-point

# Definindo a parábola
def f(x): 
    return x**2

# Definindo a derivada da parábola
def slope(x): 
    return 2*x

# Definindo conjunto de dados para x
x = numpy.linspace(-5,5,100)

# Escolhendo pontos para traçar as retas tangentes
x1 = -3
y1 = f(x1)

x2 = 3
y2 = f(x2)

x3 = 0
y3 = f(x3)


# Definindo intervalo de dados em x para plotar a reta tangente
xrange1 = numpy.linspace(x1-1, x1+1, 10)
xrange2 = numpy.linspace(x2-1, x2+1, 10)
xrange3 = numpy.linspace(x3-1, x3+1, 10)

# Definindo a reta tangente
# y = m*(x - x1) + y1
def tangent_line(x, x1, y1):
    return slope(x1)*(x - x1) + y1

# Desenhando as figuras
fig = pyplot.figure(figsize=(13,9))

pyplot.subplot2grid((2,4),(0,0), colspan = 2)
pyplot.title("Decaimento < 0.")
pyplot.plot(x, f(x))
pyplot.scatter(x1, y1, color='C1', s=50)
pyplot.plot(xrange1, tangent_line(xrange1, x1, y1), 'C1--', linewidth = 2)

pyplot.subplot2grid((2,4),(0,2), colspan = 2)
pyplot.title("Decaimento > 0.")
pyplot.plot(x, f(x))
pyplot.scatter(x2, y2, color='C1', s=50)
pyplot.plot(xrange2, tangent_line(xrange2, x2, y2), 'C1--', linewidth = 2)

pyplot.subplot2grid((2,4),(1,1), colspan = 2)
pyplot.title("Decaimento = 0.")
pyplot.plot(x, f(x))
pyplot.scatter(x3, y3, color='C1', s=50)
pyplot.plot(xrange3, tangent_line(xrange3, x3, y3), 'C1--', linewidth = 2)
```

dc9d31a82d1bdb8e49ee6418dc7ecc6fe555554b
ipynb
Jupyter Notebook
machine_learning/aula_1/aula_1.ipynb
jppbsi/lectures
f0366901f71eb86489547a471cc959272d6abdc3
Apache-2.0
2020-03-12T11:50:21.000Z
2021-04-16T20:08:36.000Z
machine_learning/aula_1/aula_1.ipynb
jppbsi/lectures
f0366901f71eb86489547a471cc959272d6abdc3
Apache-2.0
machine_learning/aula_1/aula_1.ipynb
jppbsi/lectures
f0366901f71eb86489547a471cc959272d6abdc3
Apache-2.0
2020-07-02T17:59:47.000Z
2021-04-17T00:02:16.000Z
Qwen/Qwen-72B
1. YES
2. YES