Mostrando postagens com marcador Aprendizagem Computacional. Mostrar todas as postagens
Mostrando postagens com marcador Aprendizagem Computacional. Mostrar todas as postagens

sexta-feira, 26 de maio de 2017

Medindo consumo de memória em Python

O gerenciamento de memória de programas em Python é feito inteiramente pela linguagem. Isto significa que, por vezes, pode ser difícil controlar o uso de memória e não costuma ser claro o quanto cada objeto ocupa na memória nem quando seu espaço é liberado. Descreverei neste post o uso do módulo memory_profiler, que contém ferramentas para medir o consumo de memória de scripts Python.

Antes de tudo, o memory_profiler pode ser instalado via pip usando o seguinte comando. A opção -U pode ser usada para instalar o módulo na área do usuário em sistemas Unix-like.

$ pip install memory_profiler

Para usar este módulo basta decorar a função que desejamos medir o consumo de memória com o decorador @profile e executar o programa usando

$ python -m memory_profiler arquivo.py

Este comando imprime um relatório linha a linha mostrando o consumo de memória. Quando objetos são criados o consumo de memória aumenta, enquanto quando eles saem de contexto o consumo pode diminuir. O ponto fraco deste modo de execução é que ele só mostra o consumo medido "entre" as linhas do programa. Ou seja, se uma função for chamada e, durante sua execução alocar e liberar uma grande quantidade de memória estes valores não são contabilizados.

Também é disponibilizado o comando mprof, que executa um script e mede o consumo total de memória em pequenos intervalos de tempo. mprof, porém, não discrima onde a memória está sendo usada.


Irei exemplificar o uso do mprof com dois exemplos tirados da TRIOSlib, a biblioteca que desenvolvi durante meu doutorado. O objetivo da biblioteca é usar aprendizado de máquina para tratar problemas de processamento de imagens (mais detalhes aqui).

Uma das tarefas que mais consome memória na TRIOSlib é a extração de características. Um padrão é extraído de cada ponto das imagens de treinamento. Conjuntos de treinamento contendo várias centenas de milhares de padrões são comuns, mesmo usando um pequeno número de imagens de treinamento.

A TRIOSlib implementa dois modos para extração de características. No primeiro uma matriz é alocada com uma linha por pixel das imagens de treinamento. Cada padrão observado é armazenado em uma linha e, se houverem repetições, o mesmo padrão é armazenado diversas vezes. Neste caso o consumo depende do número de pixels das imagens de treinamento. Nos referimos a este modo como modo Array.

No segundo modo a matriz é trocada por um dicionário cujas chaves são padrões observados nas imagens. Se houverem repetições não há consumo extra de memória. Neste caso o consumo de memória depende do número de padrões únicos nas imagens. Quanto maior a repetição de padrões menor será o consumo relativo ao primeiro modo. Nos referimos a este tipo de execução como modo Dicionário ou Dict.

Apesar do memory_profiler possuir um modo que mostra o consumo de memória linha a linha, para operações longas como o treinamento de operadores pode ser mais interessante usar o comando mprof. Apesar de não ser possível identificar qual estrutura está consumindo memória, podemos ver o quanto a memória cresce e diminui conforme o programa executa. Também conseguimos, por meio do decorador @profile, marcar o início e o fim da execução de funções no código.

Para executar um script usamos o comando mprof run arquivo.py. Podemos plotar um gráfico com a última execução usando mprof plot. Usaremos o código abaixo como exemplo.

import trios
import trios.feature_extractors
import trios.classifiers

import numpy as np
from sklearn.tree import DecisionTreeClassifier

import sys


@profile
def main(ordered=True):
    imgset =
trios.Imageset.read('/media/igor/Data1/datasets/staffs/level1.set')
    testset =
trios.Imageset.read('/media/igor/Data1/datasets/staffs/test-
small.set')
    win = np.ones((7, 7), np.uint8)

    wop = trios.WOperator(win,
trios.classifiers.SKClassifier(DecisionTreeClassifier(),
ordered=ordered), trios.feature_extractors.RAWFeatureExtractor(win))

    dataset = wop.extractor.extract_dataset(imgset, ordered=ordered)
    if ordered:
        print(dataset[1].shape[0])
    else:
        print(len(dataset))

    tr2 = profile(wop.classifier.train)
    tr2(dataset, {})
    wop.trained = True

    print(wop.eval(testset[:1]))

if __name__ == '__main__':
    main(sys.argv[1] == 'ordered')

O decorator @profile irá marcar no gráfico o início e fim das funções main e WOperator.Classifier.train. (Lembre-se que um decorador é simplesmente uma função que retorna outra função. Fazemos isto na linha 21 para obter uma função de treinamento com @profile.) A escolha do modo de extração de características é feita pelo parâmetro ordered, passado pela linha de comando. ordered=True implica no uso de uma matriz para os padrões. ordered=False usa um dicionário. Veja abaixo o consumo de memória medidos em uma execução usando os dois modos.

Fig 1: Modo Array

Fig 2: Modo Dict

O consumo máximo de memória do modo Array é cerca de 4 vezes maior do que o modo Dict! A principal diferença entre os dois modos de execução é a quantidade de memória usada pelo método train. Enquanto o modo Array executa a extração de características muito mais rápido que o modo Dict, o treinamento é muito mais custoso tanto em termos de tempo quanto em memória. Isto pode ser explicado pelo fato de que o modo Dict condensa todas as observações repetidas em uma só linha ao treinar. Isto resulta em uma matriz de treinamento com 137.453 linhas, contra 5.595.585 linhas quando usamos o modo Array. A redução no número de linhas é possível atribuindo ao peso de cada exemplo de treinamento único o número de repetições observadas nas imagens. Infelizmente a maioria dos classificadores do scikit-learn não oferece suporte a definir pesos para cada instância de treinamento. Classificadores baseados em árvores de decisão (encontrados no pacote sklearn.tree) possuem este suporte e, como podemos ver isto faz uma grande diferença ao trabalhar com dados em que há repetição nos padrões de entrada observados.


Neste texto exemplifiquei a utilização do memory_profiler para medição de consumo de memória em Python e do decorador @profile para marcar o início e o fim da execução de diversas funções. Como pudemos ver, utilizar estruturas adequadas pode diminuir significativamente o consumo de memória e permitir processar uma maior quantidade de dados em memória.

sexta-feira, 5 de maio de 2017

O que é Regressão Logística?

\(\newcommand{\x}{\textbf{x}}\) \(\newcommand{\R}{\mathcal{R}}\) \(\newcommand{\x}{\textbf{x}}\) \(\newcommand{\y}{\textbf{y}}\)

Ao trabalhar com Regressão Logística nunca me lembro exatamente qual é o formato das funções de erro e se os labels são \(\{0, 1\}\) ou \(\{-1, 1\}\). Resolvi então criar este post detalhando todas as contas feitas para chegar nas diversas expressões usadas para definir a Regressão Logística. Primeiramente, supomos que temos um conjunto de treinamento \(\{(x_i, y_i)\}_{i=1}^N, x_i \in \R^m\) e iremos explorar tanto o caso em que \(y_i \in \{0, 1\}\) como o caso em que \(y_i \in \{-1, 1\}\). Denotamos \(\x\) e \(\y\) as variáveis correspondentes aos padrões de entrada \(x_i\) e aos rótulos \(y_i\).

Primeiramente, o quê é a Regressão Logística? Em outros textos do blog (veja mais em Resumo de mínimos quadrados parte 1 e parte 2) falamos um pouco sobre a Regressão Linear, um método para estimar parâmetros de uma função linear \(f:\R^m \rightarrow \R\). Os coeficientes \(w\) de \(f\) são determinados pelo seguinte problema de otimização.

\begin{equation} \min_{w \in \R^m} ||Xw - y||^2 = \sum_{i=1}^m (w^T x_i - y_i)^2, \end{equation}

Ou seja, a Regressão Linear minimiza a soma (ou média) dos erros ao quadrado (e por isso também é chamada de método dos mínimos quadrados, Least Squares em inglês). Este tipo de erro é adequado quando \(\y\) é uma variável contínua, pois o erro medido leva em conta a distância entre o valor predito \(w^T \x\) e o esperado \(\y\). Não é este o caso quando \(y_i\) é um conjunto discreto (e pequeno) de valores.

A Regressão Logística estima \(f\) para os casos em que \(\y\) é discreto usando probabilidades. Como podem ser frequentes os casos em que dois exemplos \(x_i\) e \(x_j, x_i = x_j\), possuem rótulos diferentes \(y_i \neq y_j\), vamos estimar as probabilidades \(P(\y=1 | x)\) e \(P(\y=0|x)\) (ou \(\y=-1\)). Decidimos o valor de \(f\) com base nas probabilidades calculadas.

\begin{equation} f(\x) = \begin{cases} 1 & P(\y = 1 | \x) > 0.5 \\ 0 \textrm{ ou } -1 & c.c. \end{cases} \end{equation}

Veremos que, assim como a Regressão Linear, a Regressão Logística é um modelo linear em \(\x\) e que podemos computar \(f\) sem calcular explicitamente as probabilidades \(P(\y=y|\x)\).

A motivação que costuma ser encontrada nos livros para a Regressão Logística é a de querermos modelar o log da razão entre as probabilidades como uma função linear. Eu acho isto confuso e nunca pensaria nisto, então bolei a seguinte intuição.

Gostaríamos de modelar \(P(\y=1|\x)\) como um afunção linear. A primeira opção seria usar

\begin{equation} P(\y=1|\x) = w^T \x, \end{equation}

porém isto claramente é um problema, já que \(w^T \x\) pode ser negativo. Podemos resolver isto usando exponenciação.

\begin{equation} P(\y=1|\x) = e^{w^T \x} \end{equation}

Continuamos fazendo uma combinação linear das variáveis de entrada, mas agora no expoente. Isto evita que as probabilidades sejam negativas, mas \(e^{w^T \x}\) pode ser tão grande quanto quisermos. Podemos então dividir isto por \(1+e^{w^T \x}\)! O resultado será sempre menor que 1 e maior que 0, resultando em

\begin{equation} P(\y = 1 | \x) = \frac{ e^{w^Tx} }{1 + e^{w^Tx} }. \end{equation}

Esta função tem o seguinte formato:


By Qef (talk) - Created from scratch with gnuplot, Public Domain, https://commons.wikimedia.org/w/index.php?curid=4310325


Um vetor de pesos \(w \in \R^m\) induz a função de decisão \(f_w(\x) = 1[P(\y = 1 | \x) > 0.5]\), como visto anteriormente. Porém, note que se \(w^T x = 0\), então

\begin{equation} \frac{ e^{w^Tx} }{1 + e^{w^Tx} } = \frac{e^0}{1+e^0} = \frac{1}{2}. \end{equation}

Portanto, \(P(\y = 1 | \x) > 0.5 \Leftrightarrow w^T \x > 0\)! Não precisamos calcular explicitamente \(P(\y = 1 | \x)\). As diferenças entre as duas formulações começam a aparecer neste ponto.

Labels \(\{0, 1\}\)

Neste caso, basta definir a função de decisão como \(f_w(x) = \frac{sign(w^T x) + 1}{2}\) . Se \(sign(w^Tx) = 1\) então \(f_w(x) = 1\), se \(sign(w^Tx) = -1\) \(f_w(x) = 0\).

Podemos definir o vetor de pesos \(w \in \R^m\) como o estimador de máxima verossimilhança da amostra \(\{(x_i, y_i)\}\). Queremos, portanto, resolver o seguinte problema de otimização:

\begin{equation} \max_{w \in \R^m} \prod_{i=1}^{N} P(\y=y_i|x_i) \end{equation}

Ao invés de maximizar a verossimilhança diretamente, é mais fácil maximizar seu log. Isto transforma o produtório em um somatório, resultando na seguinte expressão.

\begin{equation} \begin{aligned} \max_{w \in \R^m} & \sum_{i=1}^{N} \log P(\y=y_i|x_i) = \\ & \sum_{i=1}^{N} y_i \log P(\y=1|x_1) + (1 - y_i) \log P(\y=0|x_i) \end{aligned} \label{eq:min-log-01} \end{equation}

Examinando cada termo \(P(\y=y_i|x_i)\) separadamente para os casos \(y_i=1\) e \(y_i=0\), temos que

\begin{equation} \begin{aligned} \log P(\y=1|x_i) = & \log \left(\frac{e^{w^T x_i}}{1+e^{w^T x_i}} \right) = \\ & \log e^{w^T x_i} - \log (1 + e^{w^T x_i}) = \\ & w^T x_i - \log (1 + e^{w^T x_i}) \end{aligned} \end{equation}

e

\begin{equation} \begin{aligned} \log P(\y=0|x_i) = & \log \left(\frac{1}{1+e^{w^T x_i}} \right) = \\ & \log 1 - \log (1 + e^{w^T x_i}) = \\ & - \log (1 + e^{w^T x_i}) \end{aligned} \end{equation}

Colocando essas expressões de volta na equação anterior obtemos

\begin{equation} \begin{aligned} \max_{w \in \R^m} & \sum_{i=1}^{N} y_i \log P(\y=1|x_1) + (1 - y_i) \log P(\y=0|x_i) = \\ & \sum_{i=1}^{N} y_i (w^T x_i - \log (1+e^{w^T x_i} ) + (1 - y_i) (-\log (1+e^{w^T x_i})) = \\ & \sum_{i=1}^{N} y_i w^T x_i - y_i \log (1+e^{w^T x_i}) - \log(1+e^{w^T x_i}) + y_i\log (1+e^{w^T x_i}) = \\ & \sum_{i=1}^{N} y_i w^T x_i - \log (1+e^{w^T x+i}) \end{aligned} \end{equation}

Em Aprendizado de Máquina a maioria dos problemas de otimização são de minimização. Logo, a forma mais comum da Regresssão logística é

\begin{equation} \min_{w \in \R^m} \sum_{i=1}^{N} -y_i w^T x_i + \log(1+e^{w^T x_i}) \label{eq:regr-log-01} \end{equation}

Labels \(\{-1, 1\}\)

Dependendo da fonte estudada pode-se encontrar a Regressão Logística escrita usando labels \(\{-1, 1\}\) ao invés de \(\{0, 1\}\). Isto facilita na hora de definir a a função de decisão \(f(x) = sign(w^T x)\) e não torna o problema necessariamente mais complicado. As definições relativas às funções de probabilidade são iguais:

\begin{equation} P(\y=1|\x) = \frac{ e^{w^T \x} }{1 + e^{w^T \x} }. \end{equation} \begin{equation} P(\y=-1|\x) = \frac{1}{1 + e^{w^T \x} }. \end{equation}

Porém, note que

\begin{equation} \begin{aligned} P(\y=1|\x) = \frac{e^{w^T \x}}{1 + e^{w^T \x}} \\ \frac{e^{-w^T \x}}{e^{-w^T \x}} \frac{e^{w^T \x}}{1 + e^{w^T \x}} = \\ \frac{1}{e^{-w^T \x}(1+ e^{w^T \x})} = \\ \frac{1}{1 + e^{-w^T \x}} \end{aligned} \end{equation}

Esta expressão é idêntica a de \(P(\y=-1|x)\) a menos do sinal nas exponenciações. Como \(y \in \{-1, 1\}\), podemos definir então uma só expressão para as probabilidades.

\begin{equation} \begin{aligned} P(\y=y|\x) = & \frac{1}{1 + e^{-\y w^T \x}} \end{aligned}\end{equation}

Usamos o mesmo método (Máxima Verossimilhança) para encontrar um \(w\) que torne um conjunto de treinamento \(\{(x_i, y_i)\}\) o mais provável possível, mas desta vez usando a expressão acima para as probabilidades. As passagens abaixo iniciam já na log-Máxima Verossimilhança.

\begin{equation}\begin{aligned} \min_{w \in \R^m} & -\sum_{i=1}^N \log P(\y=y_i | x_i) = \\ & -\sum_{i=1}^N \log \frac{1}{1 + e^{-y_i w^T x_i}} = \\ & -\sum_{i=1}^N - \log (1 + e^{-y_i w^T x_i}) = \\ & \sum_{i=1}^N \log (1 + e^{-y_i w^T x_i}) \end{aligned}\end{equation}

Portanto, mostramos acima as duas formas mais comuns da Regressão Logística e vimos as funções de custo usadas em cada um dos casos. Espero que o post tenha sido útil. Comentários e dúvidas são muito benvindos nas seção de comentários abaixo!

sexta-feira, 13 de maio de 2016

Espaços de hipótese e algoritmos de aprendizagem

É comum durante o aprendizado de Machine Learning confundir os papéis do espaço espaço de hipóteses e do algoritmo de aprendizagem no determinação da hipótese encontrada. Para tirar esta dúvida resolvi escrever um pequeno exemplo que mostra dois algoritmos simples (Perceptron e Regressão Linear) explorando espaços de hipóteses completamente distintos.

Primeiramente, o espaço de hipóteses $\mathcal{H}$ contém a classe de funções consideradas por um algoritmo de aprendizagem, que irá escolher a função $g \in \mathcal{H}$ baseado em uma função de erro calculada em um conjunto de treinamento. A junção destas três partes é chamada de modelo de aprendizagem. Nesta aula sobre VC dimension, o prof. Mostafa argumenta que a capacidade de generalização de um modelo depende somente do espaço de hipóteses. Isto pode parecer contra-intuitivo, pois algoritmos diferentes podem resultar em soluções com desempenho muito diferente. Porém, como iremos ver nos exemplos abaixo, é o espaço de hipóteses que define a complexidade de um modelo (e, consequentemente sua capacidade de generalização) e o resultado apresentado pelo prof. Mostafa é um limitante inferior para o erro. Como não conhecemos a função objetivo $f$, que pode ser tão complexa quanto possível, existirá sempre uma parte de $f$ que não poderá ser capturada usando funções no espaço $\mathcal{H}$. Na aula sobre Bias-Variance, esta "margem" é definida como o bias.

Caso linearmente separável

Neste exemplo mostramos como diferentes algoritmos de aprendizado encontram diferentes hipóteses $g$ mesmo que estejam considerando o mesmo espaço de hipóteses $\mathcal{H} = \{f_w: f_w(x) = sign(w^T x) \}$.

Primeiramente, vamos amostrar duas populações de dois retângulos no plano.

In [9]:
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
import scipy as sp
import scipy.stats
import sklearn
import sklearn.linear_model

dados1 = sp.stats.uniform.rvs(size=100 * 2).reshape((100, 2))
dados1[:,0] = dados1[:,0] * 3 + 5
dados1[:,1] = dados1[:,1] * 0.5 - 2

dados2 = sp.stats.uniform.rvs(size=100 * 2).reshape((100, 2))
dados2[:,0] = dados2[:,0] * 2 
dados2[:,1] = dados2[:,1] * 5 + 2

plt.xlim((-2, 10))
plt.ylim((-10, 15))
plt.plot(dados1[:,0], dados1[:,1], 'ro')
plt.plot(dados2[:,0], dados2[:,1], 'bo')
Out[9]:
[<matplotlib.lines.Line2D at 0x2179590>]
In [11]:
def plot_decision(model, color, caption, min_=-2, max_=10):
    xx = np.linspace(min_, max_, 100)
    # Graças ao scikit learn e ao python, podemos passar  
    # model como parâmetro de plot_decision
    coef = model.coef_.reshape(-1)
    a = -coef[0] / coef[1]
    yy = xx * a - model.intercept_ / coef[1]
    plt.plot(xx, yy, color, label=caption)

Para selecionar a hipótese, usaremos dois algoritmos diferentes: perceptron e regressão linear. Em seguida, chamamos a função plot_decision para desenhar a superfície de separação. Escolhemos estes dois algoritmos pois ambos são algoritmos considerados "simples" por estimarem somente decisões lineares em $w$.

In [12]:
def treina_e_plota(X1, X2, min_=-2, max_=10, intercept=True):
    X = np.r_[X1, X2]
    y = np.r_[np.ones(X1.shape[0]), -np.ones(X2.shape[0])]

    perc = sklearn.linear_model.Perceptron(fit_intercept=intercept, n_iter=500)
    perc.fit(X, y)

    linreg = sklearn.linear_model.LinearRegression(fit_intercept=intercept)
    linreg.fit(X, y)

    plt.figure(figsize=(10, 10))
    plt.plot(X1[:,0], X1[:,1], 'ro')
    plt.plot(X2[:,0], X2[:,1], 'bo')
    plot_decision(perc, 'g', 'Perceptron', min_, max_)
    plot_decision(linreg, 'y', 'Regressão linear', min_, max_)
    plt.legend()
    plt.title('Caso linearmente separável')
    print('Score Perceptron', perc.score(X, y))
    print('Score LinRegression', np.sum(np.sign(linreg.predict(X)) == y) / X.shape[0])
    return perc, linreg

# Chama os algoritmos de treinamento.

treina_e_plota(dados1, dados2)
Score perceptorn 1.0
Score LinRegression 1.0
Out[12]:
(Perceptron(alpha=0.0001, class_weight=None, eta0=1.0, fit_intercept=True,
       n_iter=500, n_jobs=1, penalty=None, random_state=0, shuffle=True,
       verbose=0, warm_start=False),
 LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=False))

Como podemos ver, cada algoritmo de aprendizagem selecionou uma hipótese diferente, mas ambas separam perfeitamente as duas classes.

Caso não linearmente separável

Como visto na aula sobre modelos lineares, os modelos estimados pela Regressão Linear e pelo Perceptron são lineares em $w$, mas não necessariamente em $x$. Para estimar modelos não-lineares em $x$ criamos uma versão transformada $z$ de todas as entradas e estimamos $f(z) = w^T z$. É importante notar que ao fazer essa transformação o espaço de hipóteses muda. Iremos exemplificar esta transformação estimando uma superfície de decisão para os dados abaixo.

In [15]:
def generate_circle(loc, scale, A, B, inside, size):
    X = np.zeros((size, 2))
    center = loc + scale/2
    #print(center)
    while size > 0:
        point = scale * sp.stats.uniform.rvs(size=2) + loc
        #print(point, (point[0] - center[0])**2/A**2 + (point[1] - center[1])**2/B**2)
        if inside and (point[0] - center[0])**2/A**2 + (point[1] - center[1])**2/B**2 <= 1:
            X[size-1] = point
            size -= 1
        elif not inside and (point[0] - center[0])**2/A**2 + (point[1] - center[1])**2/B**2 > 1:
            X[size-1] = point
            size -= 1
        #size -= 1
    return X

plt.figure(figsize=(8, 8))

dados1 = generate_circle(np.array([-5, -5]), np.array([10, 10]), 5, 3, True, 500)
dados2 = generate_circle(np.array([-5, -5]), np.array([10, 10]), 5, 3, False, 500)
plt.plot(dados1[:,0], dados1[:,1], 'ro')
plt.plot(dados2[:,0], dados2[:,1], 'bo')
Out[15]:
[<matplotlib.lines.Line2D at 0x5203a90>]

Vamos explorar o espaço de hipóteses das funções de decisão elípticas $\mathcal{H} = \{ f : f(x; A, B) = sign(\frac{(x-c_x)^2}{A^2} + \frac{y - c_y)^2}{B^2} - 1) \}$ e, portanto, estimaremos o seguinte modelo linear:

$$ f(x; \alpha, \beta, \gamma) = sign( \alpha (x_1 - \overline{x_1})^2 + \beta (x_2 - \overline x_2)^2 + \gamma) = sign(\alpha z_1 + \beta z_2 + \gamma), $$

com

$$ z_i = (x_i - \overline x_i)^2 $$

Note que, dados $\alpha, \beta$ e $\gamma$, conseguimos recuperar os parâmetros $A$ e $B$ da elipse.

$$ A = \sqrt{\frac{-\gamma}{\alpha}}, B = \sqrt{\frac{-\gamma}{\beta}}. $$
In [16]:
media = dados1.mean(axis=0)

dados1_X = (dados1 - media)**2
dados2_X = (dados2 - media)**2

perc, linreg = treina_e_plota(dados1_X, dados2_X, -3, 30)
Score perceptorn 0.972
Score LinRegression 0.925

Podemos ver no gráfico acima uma diferença entre ambos algoritmos: como para a Regressão Linear a distância entre um ponto classificado incorretamente e o hiperplano faz diferença, a reta amarela é puxada para cima. Isto ocorre pois os pontos azuis estão mais espalhados verticalmente.

Note que a decisão linear estimada acima é feita no espaço transformado, não no espaço original. Veja abaixo as duas decisões acima plotadas no espaço original de características.

In [17]:
print('Perceptron', perc.coef_, perc.intercept_)
print('Lin Regression', linreg.coef_, linreg.intercept_)
A_perc, B_perc = np.sqrt(-perc.intercept_/perc.coef_)[0]
A_lin, B_lin = np.sqrt(-linreg.intercept_/linreg.coef_)

from matplotlib.patches import Ellipse

plt.figure(figsize=(8,8))
ax = plt.gca()
plt.xlim((-10, 10))
plt.ylim((-10, 10))


plt.plot(dados1[:,0], dados1[:,1], 'ro')
plt.plot(dados2[:,0], dados2[:,1], 'bo')
e_perc = Ellipse(xy=media, width=2*A_perc, height=2*B_perc)
e_perc.set_facecolor('b')
e_perc.set_alpha(0.5)
ax.add_patch(e_perc)
e_lin = Ellipse(xy=media, width=2*A_lin, height=2*B_lin)
e_lin.set_facecolor('y')
e_lin.set_alpha(0.5)
ax.add_patch(e_lin)
Perceptron [[ -63.16696553 -167.94716625]] [ 1476.]
Lin Regression [-0.03890952 -0.10278301] 1.13912219107
Out[17]:
<matplotlib.patches.Ellipse at 0x12fdf9d0>

Espero que este exemplo tenha sido significativo e que a diferença entre algoritmo de aprendizagem e espaço de hipóteses tenha sido esclarescida. Como sempre, quaisquer sugestões ou críticas são benvindas nos comentários.