Você está na página 1de 137

Machine Translated by Google

Figura 5-5. Uma visão tridimensional dos dados de regressão

Observe que o plano do recurso 1 – recurso 2 aqui é o mesmo do gráfico bidimensional anterior;
neste caso, entretanto, representamos os rótulos tanto pela cor quanto pela posição do eixo
tridimensional. Deste ponto de vista, parece razoável que ajustar um plano através destes dados
tridimensionais nos permitiria prever o rótulo esperado para qualquer conjunto de parâmetros
de entrada. Voltando à projeção bidimensional, quando ajustamos tal plano obtemos o resultado
mostrado na Figura 5-6.

Figura 5-6. Uma representação do modelo de regressão

O que é aprendizado de máquina? | 337


Machine Translated by Google

Este plano de ajuste nos dá o que precisamos para prever rótulos para novos pontos. Visualmente,
encontramos os resultados mostrados na Figura 5-7.

Figura 5-7. Aplicando o modelo de regressão a novos dados

Tal como acontece com o exemplo de classificação, isto pode parecer bastante trivial num pequeno número
de dimensões. Mas o poder desses métodos é que eles podem ser aplicados e avaliados diretamente no
caso de dados com muitos, muitos recursos.

Por exemplo, isto é semelhante à tarefa de calcular a distância às galáxias observadas através de um
telescópio – neste caso, poderíamos usar as seguintes características e rótulos:

• recurso 1, recurso 2, etc. brilho de cada galáxia em um dos vários comprimentos de onda
ou cores

• distância do rótulo ou redshift da galáxia

As distâncias para um pequeno número destas galáxias podem ser determinadas através de um conjunto
independente de observações (normalmente mais caras). Poderíamos então estimar as distâncias às
galáxias restantes usando um modelo de regressão adequado, sem a necessidade de empregar a
observação mais cara em todo o conjunto. Nos círculos da astronomia, isso é conhecido como o problema
do “desvio para o vermelho fotométrico”.

Alguns algoritmos de regressão importantes que discutiremos são regressão linear (consulte “Em
profundidade: Regressão linear” na página 390), máquinas de vetores de suporte (consulte “Em
profundidade: máquinas de vetores de suporte” na página 405) e regressão de floresta aleatória (consulte
“Em profundidade: árvores de decisão e florestas aleatórias” na página 421).

Clustering: inferindo rótulos em dados não

rotulados As ilustrações de classificação e regressão que acabamos de ver são exemplos de algoritmos
de aprendizagem supervisionada, nos quais estamos tentando construir um modelo que irá prever rótulos
para novos dados. A aprendizagem não supervisionada envolve modelos que descrevem dados sem
referência a quaisquer rótulos conhecidos.

338 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Um caso comum de aprendizagem não supervisionada é o “clustering”, no qual os dados são


automaticamente atribuídos a um certo número de grupos discretos. Por exemplo, poderíamos ter
alguns dados bidimensionais como os mostrados na Figura 5-8.

Figura 5-8. Dados de exemplo para clustering

A olho nu, fica claro que cada um desses pontos faz parte de um grupo distinto. Dada esta entrada,
um modelo de agrupamento utilizará a estrutura intrínseca dos dados para determinar quais pontos
estão relacionados. Usando o algoritmo k-means muito rápido e intuitivo (veja “Em profundidade:
agrupamento de k-means” na página 462), encontramos os clusters mostrados na Figura

5-9. k-means ajusta-se a um modelo que consiste em k centros de cluster; os centros ideais são
considerados aqueles que minimizam a distância de cada ponto ao seu centro atribuído. Novamente,
isto pode parecer um exercício trivial em duas dimensões, mas à medida que os nossos dados se
tornam maiores e mais complexos, tais algoritmos de agrupamento podem ser utilizados para extrair
informações úteis do conjunto de dados.

Discutiremos o algoritmo k-means com mais profundidade em “Em profundidade: agrupamento de k-


means” na página 462. Outros algoritmos de agrupamento importantes incluem modelos de mistura
gaussiana (consulte “Em profundidade: modelos de mistura gaussiana” na página 476) e clustering
espectral (veja a documentação de clustering do Scikit-Learn).

O que é aprendizado de máquina? | 339


Machine Translated by Google

Figura 5-9. Dados rotulados com um modelo de cluster k-means

Redução de dimensionalidade: Inferindo a estrutura de dados

não rotulados A redução de dimensionalidade é outro exemplo de algoritmo não supervisionado,


no qual rótulos ou outras informações são inferidos a partir da estrutura do próprio conjunto de dados.
A redução da dimensionalidade é um pouco mais abstrata do que os exemplos que vimos antes,
mas geralmente procura extrair alguma representação de dados de baixa dimensão que de alguma
forma preserva qualidades relevantes do conjunto de dados completo. Diferentes rotinas de
redução de dimensionalidade medem essas qualidades relevantes de maneiras diferentes, como
veremos em “Detalhado: Aprendizado múltiplo” na página 445.

Como exemplo disso, considere os dados mostrados na Figura 5-10.

Visualmente, fica claro que há alguma estrutura nesses dados: eles são traçados a partir de uma
linha unidimensional disposta em espiral dentro desse espaço bidimensional. De certa forma, você
poderia dizer que esses dados são “intrinsecamente” apenas unidimensionais, embora esses
dados unidimensionais estejam incorporados em um espaço de dimensão superior. Um modelo
de redução de dimensionalidade adequado neste caso seria sensível a esta estrutura embutida
não linear e seria capaz de extrair esta representação de dimensionalidade inferior.

340 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Figura 5-10. Dados de exemplo para redução de dimensionalidade

A Figura 5-11 apresenta uma visualização dos resultados do algoritmo Isomap, um algoritmo de aprendizado
múltiplo que faz exatamente isso.

Figura 5-11. Dados com rótulo aprendidos por meio de redução de dimensionalidade

Observe que as cores (que representam a variável latente unidimensional extraída) mudam uniformemente ao
longo da espiral, o que indica que o algoritmo detectou de fato a estrutura que vimos a olho nu. Tal como
acontece com os exemplos anteriores, o poder de

O que é aprendizado de máquina? | 341


Machine Translated by Google

algoritmos de redução de dimensionalidade tornam-se mais claros em casos de dimensões superiores.


Por exemplo, podemos desejar visualizar relacionamentos importantes dentro de um conjunto de dados que possui
100 ou 1.000 recursos. Visualizar dados de 1.000 dimensões é um desafio, e uma maneira de tornar isso mais
gerenciável é usar uma técnica de redução de dimensionalidade para reduzir os dados a duas ou três dimensões.

Alguns algoritmos importantes de redução de dimensionalidade que discutiremos são a análise de componentes
principais (veja “Em profundidade: Análise de componentes principais” na página 433) e vários algoritmos de
aprendizagem múltipla, incluindo Isomap e incorporação linear local (veja “Em profundidade: Aprendizagem múltipla
”na página 445).

Resumo Aqui

vimos alguns exemplos simples de alguns dos tipos básicos de abordagens de aprendizado de máquina. Escusado
será dizer que há uma série de detalhes práticos importantes que ignoramos, mas espero que esta seção tenha
sido suficiente para lhe dar uma ideia básica de quais tipos de problemas as abordagens de aprendizado de
máquina podem resolver.

Resumindo, vimos o seguinte:

Aprendizagem supervisionada

Modelos que podem prever rótulos com base em dados de treinamento rotulados

Classificação
Modelos que prevêem rótulos como duas ou mais categorias discretas

Regressão
Modelos que prevêem rótulos contínuos

Aprendizagem não supervisionada

Modelos que identificam estrutura em dados não rotulados

Agrupamento
Modelos que detectam e identificam grupos distintos nos dados

Redução de dimensionalidade
Modelos que detectam e identificam estruturas de dimensões inferiores em dados de dimensões superiores

Nas seções seguintes nos aprofundaremos nessas categorias e veremos alguns exemplos mais interessantes de
onde esses conceitos podem ser úteis.

Todas as figuras da discussão anterior são geradas com base em cálculos reais de aprendizado de máquina; o
código por trás deles pode ser encontrado no apêndice online.

342 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Apresentando o Scikit-Learn
Existem diversas bibliotecas Python que fornecem implementações sólidas de uma variedade de
algoritmos de aprendizado de máquina. Um dos mais conhecidos é o Scikit-Learn, um pacote que
fornece versões eficientes de um grande número de algoritmos comuns. Scikit-Learn é
caracterizado por uma API limpa, uniforme e simplificada, bem como por uma API muito útil e
documentação on-line completa. Um benefício dessa uniformidade é que, uma vez que você entenda
entender o uso básico e a sintaxe do Scikit-Learn para um tipo de modelo, mudando para um
novo modelo ou algoritmo é muito simples.

Esta seção fornece uma visão geral da API Scikit-Learn; uma compreensão sólida de
esses elementos da API formarão a base para a compreensão da prática mais profunda
discussão de algoritmos e abordagens de aprendizado de máquina nos capítulos seguintes.

Começaremos cobrindo a representação de dados no Scikit-Learn, seguido pela cobertura do


API Estimator e, finalmente, veremos um exemplo mais interessante de uso dessas ferramentas
para explorar um conjunto de imagens de dígitos manuscritos.

Representação de dados no Scikit-Learn


O aprendizado de máquina trata da criação de modelos a partir de dados: por esse motivo, começaremos por
discutir como os dados podem ser representados para serem compreendidos pelo computador.
A melhor maneira de pensar sobre os dados no Scikit-Learn é em termos de tabelas de dados.

Dados como tabela

Uma tabela básica é uma grade bidimensional de dados, na qual as linhas representam dados individuais.
elementos comuns do conjunto de dados, e as colunas representam quantidades relacionadas a cada um dos
esses elementos. Por exemplo, considere o conjunto de dados Iris, analisado por Ronald
Fisher em 1936. Podemos baixar este conjunto de dados na forma de um Pandas DataFrame
usando a biblioteca Seaborn:

In[1]: importar seaborn como sns


íris = sns.load_dataset('íris')
íris.head()

Fora[1]: sepal_length sepal_width petal_length petal_width espécie


0 5.1 3.5 1.4 0,2 sedoso
1 4.9 3,0 1.4 0,2 sedoso
2 4,7 3,2 1,3 0,2 sedoso
3 4,6 3,1 1,5 0,2 sedoso
4 5,0 3.6 1.4 0,2 sedoso

Aqui cada linha dos dados refere-se a uma única flor observada, e o número de linhas
é o número total de flores no conjunto de dados. Em geral, nos referiremos às linhas de
a matriz como amostras e o número de linhas como n_samples.

Apresentando o Scikit-Learn | 343


Machine Translated by Google

Da mesma forma, cada coluna dos dados refere-se a uma informação quantitativa específica que descreve
cada amostra. Em geral, nos referiremos às colunas da matriz como recursos e ao número de colunas
como n_features.

Matriz de recursos

Este layout de tabela deixa claro que a informação pode ser pensada como uma matriz ou matriz
numérica bidimensional, que chamaremos de matriz de recursos. Por convenção, essa matriz de recursos
é frequentemente armazenada em uma variável chamada X. A matriz de recursos é considerada
bidimensional, com formato [n_samples, n_features], e geralmente está contida em um array NumPy ou
em um Pandas DataFrame, embora alguns modelos Scikit-Learn também aceitem matrizes esparsas
SciPy.

As amostras (ou seja, linhas) sempre se referem aos objetos individuais descritos pelo conjunto de dados.
Por exemplo, a amostra pode ser uma flor, uma pessoa, um documento, uma imagem, um arquivo de
som, um vídeo, um objeto astronômico ou qualquer outra coisa que você possa descrever com um
conjunto de medidas quantitativas.

As características (isto é, colunas) referem-se sempre às observações distintas que descrevem cada
amostra de forma quantitativa. Os recursos geralmente têm valor real, mas podem ter valor booleano ou
discreto em alguns casos.

Matriz alvo

Além da matriz de características X, geralmente também trabalhamos com um rótulo ou matriz alvo, que
por convenção normalmente chamaremos de y. O array de destino geralmente é unidimensional, com
comprimento n_samples, e geralmente está contido em um array NumPy ou Série Pandas . A matriz de
destino pode ter valores numéricos contínuos ou classes/rótulos discretos. Embora alguns estimadores
do Scikit-Learn lidem com vários valores alvo na forma de um array alvo bidimensional [n_samples,
n_targets] , trabalharemos principalmente com o caso comum de um array alvo unidimensional.

Freqüentemente, um ponto de confusão é como a matriz de destino difere das outras colunas de recursos.
A característica distintiva da matriz alvo é que geralmente é a quantidade que queremos prever a partir
dos dados: em termos estatísticos, é a variável dependente. Por exemplo, nos dados anteriores podemos
desejar construir um modelo que possa prever as espécies de flores com base em outras medições; neste
caso, a coluna de espécies seria considerada a feição.

344 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Com esse array alvo em mente, podemos usar o Seaborn (discutido anteriormente em “Visualização com
Seaborn” na página 311) para visualizar os dados de forma conveniente (veja a Figura 5-12):

In[2]: %matplotlib importação


inline do mar como sns; sns.set()
sns.pairplot(íris, matiz='espécie', tamanho=1,5);

Figura 5-12. Uma visualização do conjunto de dados Iris

Para uso no Scikit-Learn, extrairemos a matriz de recursos e a matriz de destino do


DataFrame, o que podemos fazer usando algumas das operações do Pandas DataFrame discutidas no
Capítulo 3:

Em[3]: X_iris = iris.drop('espécie', eixo=1)


X_íris.forma

Fora[3]: (150, 4)

In[4]: y_iris = íris['espécie'] y_iris.shape

Fora[4]: (150,)

Para resumir, o layout esperado de recursos e valores alvo é visualizado na Figura 5-13.

Apresentando o Scikit-Learn | 345


Machine Translated by Google

Figura 5-13. Layout de dados do Scikit-Learn

Com esses dados formatados corretamente, podemos passar a considerar a API do estimador do Scikit-Learn.

API Estimadora do Scikit-Learn


A API Scikit-Learn foi projetada com os seguintes princípios orientadores em mente, conforme descrito no
documento da API Scikit-Learn:

Consistência
Todos os objetos compartilham uma interface comum extraída de um conjunto limitado de métodos, com
documentação consistente.

Inspeção
Todos os valores de parâmetros especificados são expostos como atributos públicos.

Hierarquia de objetos limitada


Apenas algoritmos são representados por classes Python; os conjuntos de dados são representados em
formatos padrão (matrizes NumPy, Pandas DataFrames, matrizes esparsas SciPy) e os nomes dos
parâmetros usam strings Python padrão.

Composição
Muitas tarefas de aprendizado de máquina podem ser expressas como sequências de algoritmos mais
fundamentais, e o Scikit-Learn faz uso disso sempre que possível.

Padrões sensíveis
Quando os modelos exigem parâmetros especificados pelo usuário, a biblioteca define um valor padrão
apropriado.

346 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Na prática, esses princípios tornam o Scikit-Learn muito fácil de usar, uma vez compreendidos os princípios
básicos. Cada algoritmo de aprendizado de máquina no Scikit-Learn é implementado por meio da API Estimator,
que fornece uma interface consistente para uma ampla variedade de aplicativos de aprendizado de máquina.

Noções básicas da API

Mais comumente, as etapas para usar a API do estimador Scikit-Learn são as seguintes (analisaremos alguns
exemplos detalhados nas seções a seguir):

1. Escolha uma classe de modelo importando a classe de estimador apropriada do Scikit-


Aprender.

2. Escolha os hiperparâmetros do modelo instanciando esta classe com os valores desejados.

3. Organize os dados em uma matriz de características e vetor alvo após a discussão


de antes.

4. Ajuste o modelo aos seus dados chamando o método fit() da instância do modelo.

5. Aplique o modelo a novos dados:

• Para aprendizagem supervisionada, frequentemente prevemos rótulos para dados desconhecidos usando
o método predizer() .

• Para aprendizagem não supervisionada, muitas vezes transformamos ou inferimos propriedades dos dados
usando o método transform() ou predizer() .

Passaremos agora por vários exemplos simples de aplicação de métodos de aprendizagem supervisionada e não
supervisionada.

Exemplo de aprendizagem supervisionada: regressão linear

simples Como exemplo desse processo, vamos considerar uma regressão linear simples – ou seja, o caso comum
de ajustar uma linha a dados x, y. Usaremos os seguintes dados simples para nosso exemplo de regressão (Figura
5-14):

Em [5]: importe matplotlib.pyplot como plt


importar numpy como np

rng = np.random.RandomState(42) x = 10
* rng.rand(50) x - 1 +
rng.randn(50) y = 2 *
plt.scatter(x, y);

Apresentando o Scikit-Learn | 347


Machine Translated by Google

Figura 5-14. Dados para regressão linear

Com esses dados disponíveis, podemos usar a receita descrita anteriormente. Vamos percorrer o
processo:

1. Escolha uma classe de modelo.

No Scikit-Learn, cada classe de modelo é representada por uma classe Python. Assim, por exemplo, se
quisermos calcular um modelo de regressão linear simples, podemos importar a classe de regressão linear:

Em [6]: de sklearn.linear_model importar LinearRegression

Observe que também existem outros modelos de regressão linear mais gerais; você pode ler mais sobre
eles na documentação do módulo sklearn.linear_model .

2. Escolha os hiperparâmetros do modelo.

Um ponto importante é que uma classe de modelo não é o mesmo que uma instância de um modelo.

Depois de decidirmos sobre nossa classe de modelo, ainda existem algumas opções abertas para nós.
Dependendo da classe de modelo com a qual estamos trabalhando, talvez precisemos responder a uma
ou mais perguntas como as seguintes:

• Gostaríamos de ajustar o deslocamento (ou seja, interceptação)?


• Gostaríamos que o modelo fosse normalizado?

• Gostaríamos de pré-processar nossos recursos para adicionar flexibilidade ao modelo?

• Que grau de regularização gostaríamos de usar no nosso modelo? • Quantos

componentes do modelo gostaríamos de usar?

348 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Estes são exemplos das escolhas importantes que devem ser feitas depois que a classe do modelo
for selecionada. Essas escolhas são frequentemente representadas como hiperparâmetros ou
parâmetros que devem ser definidos antes que o modelo seja ajustado aos dados. No Scikit-Learn,
escolhemos hiperparâmetros passando valores na instanciação do modelo. Exploraremos como você
pode motivar quantitativamente a escolha de hiperparâmetros em “Hiperparâmetros e validação de
modelo” na página 359.

Para nosso exemplo de regressão linear, podemos instanciar a classe LinearRegression e especificar
que gostaríamos de ajustar a interceptação usando o hiperparâmetro fit_inter exceto :

In[7]: modelo = modelo LinearRegression(fit_intercept=True)

Out[7]: LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1,


normalize=False)

Tenha em mente que quando o modelo é instanciado, a única ação é armazenar esses valores de
hiperparâmetros. Em particular, ainda não aplicamos o modelo a nenhum dado: a API Scikit-Learn
deixa muito clara a distinção entre a escolha do modelo e a aplicação do modelo aos dados.

3. Organize os dados em uma matriz de recursos e um vetor de destino.

Anteriormente, detalhamos a representação de dados do Scikit-Learn, que requer uma matriz de recursos
bidimensional e uma matriz de destino unidimensional. Aqui, nossa variável alvo y já está na forma
correta (uma matriz de comprimento n_samples ), mas precisamos massagear os dados x para torná-los
uma matriz de tamanho [n_samples, n_features].
Neste caso, isso equivale a uma simples remodelagem da matriz unidimensional:

Em[8]: X = x[:, np.newaxis]


X.forma

Fora[8]: (50, 1)

4. Ajuste o modelo aos seus dados.

Agora é hora de aplicar nosso modelo aos dados. Isso pode ser feito com o método fit() do modelo:

Em[9]: model.fit(X, y)

Fora[9]:
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1,
normalize=False)

Este comando fit() faz com que ocorram vários cálculos internos dependentes do modelo, e os resultados
desses cálculos são armazenados em atributos específicos do modelo que o usuário pode explorar. No
Scikit-Learn, por convenção, todos os parâmetros do modelo que foram aprendidos durante o processo
fit() possuem sublinhados à direita; por exemplo, neste modelo linear, temos o seguinte:

Apresentando o Scikit-Learn | 349


Machine Translated by Google

Em[10]: modelo.coef_

Fora[10]: array([1.9776566])

Em[11]: model.intercept_

Fora[11]: -0,90331072553111635

Esses dois parâmetros representam a inclinação e a interceptação do ajuste linear simples aos
dados. Comparando com a definição dos dados, vemos que eles estão muito próximos da inclinação
de entrada de 2 e interceptação de –1.

Uma questão que surge frequentemente diz respeito à incerteza em tais parâmetros do modelo
interno. Em geral, o Scikit-Learn não fornece ferramentas para tirar conclusões dos próprios
parâmetros internos do modelo: interpretar os parâmetros do modelo é muito mais uma questão de
modelagem estatística do que uma questão de aprendizado de máquina. O aprendizado de máquina
concentra-se antes no que o modelo prevê. Se você quiser se aprofundar no significado dos
parâmetros de ajuste no modelo, outras ferramentas estão disponíveis, incluindo o pacote StatsModels
Python.
5. Preveja rótulos para dados desconhecidos.

Uma vez treinado o modelo, a principal tarefa do aprendizado de máquina supervisionado é avaliá-lo
com base no que ele diz sobre novos dados que não faziam parte do conjunto de treinamento. No
Scikit-Learn, podemos fazer isso usando o método predizer() . Para fins deste exemplo, nossos
“novos dados” serão uma grade de valores de x, e perguntaremos quais valores de y o modelo prevê:

Em[12]: xfit = np.linspace(-1, 11)

Como antes, precisamos forçar esses valores de x em uma matriz de recursos [n_samples,
n_features] , após a qual podemos alimentá-los no modelo:

Em[13]: Xfit = xfit[:, np.newaxis] yfit =


model.predict(Xfit)

Finalmente, vamos visualizar os resultados traçando primeiro os dados brutos e, em seguida, este
modelo se ajusta (Figura 5-15):

Em[14]: plt.scatter(x, y)
plt.plot(xfit, yfit);

Normalmente avalia-se a eficácia do modelo comparando os seus resultados com alguma linha de
base conhecida, como veremos no próximo exemplo.

350 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Figura 5-15. Uma regressão linear simples ajustada aos dados

Exemplo de aprendizagem supervisionada:

classificação Iris Vamos dar uma olhada em outro exemplo desse processo, usando o conjunto de
dados Iris que discutimos anteriormente. Nossa questão será esta: dado um modelo treinado em uma
parte dos dados do Iris, quão bem podemos prever os rótulos restantes?

Para esta tarefa, usaremos um modelo generativo extremamente simples conhecido como Gaussian
ingênuo Bayes, que prossegue assumindo que cada classe é extraída de uma distribuição Gaussiana
alinhada ao eixo (veja “Em profundidade: Classificação Naive Bayes” na página 382 para mais detalhes ) .
Por ser tão rápido e não ter hiperparâmetros para escolher, o Bayes ingênuo gaussiano costuma ser um
bom modelo para usar como classificação de linha de base, antes de explorar se melhorias podem ser
encontradas por meio de modelos mais sofisticados.

Gostaríamos de avaliar o modelo com base em dados que ele nunca viu antes e, portanto, dividiremos
os dados em um conjunto de treinamento e um conjunto de teste. Isso poderia ser feito manualmente,
mas é mais conveniente usar a função utilitária train_test_split :

In[15]: de sklearn.cross_validation importar train_test_split


Xtrain, Xtest, ytrain, ytest = train_test_split(X_iris, y_iris, random_state=1)

Com os dados organizados, podemos seguir nossa receita para prever os rótulos:

Em [16]: de sklearn.naive_bayes import GaussianNB # 1. escolha a classe do modelo


model = GaussianNB() # 2. instanciar modelo # 3.
model.fit(Xtrain, ytrain) ajustar modelo aos dados
y_model = model.predict(Xtest) # 4. prever novos dados

Finalmente, podemos usar o utilitário Precision_score para ver a fração de rótulos previstos que
correspondem ao seu valor real:

Apresentando o Scikit-Learn | 351


Machine Translated by Google

Em [17]: de sklearn.metrics importar precisão_score


precisão_score (ytest, y_model)

Fora[17]: 0,97368421052631582

Com uma precisão superior a 97%, vemos que mesmo este algoritmo de classificação muito ingênuo é
eficaz para este conjunto de dados específico!

Exemplo de aprendizado não supervisionado:

dimensionalidade do Iris Como exemplo de problema de aprendizado não supervisionado, vamos dar uma
olhada na redução da dimensionalidade dos dados do Iris para visualizá-los mais facilmente. Lembre-se de
que os dados do Iris são quadridimensionais: há quatro características registradas para cada amostra.

A tarefa da redução da dimensionalidade é perguntar se existe uma representação de menor dimensão


adequada que retenha as características essenciais dos dados. Freqüentemente, a redução da
dimensionalidade é usada como auxílio na visualização de dados; afinal, é muito mais fácil plotar dados em
duas dimensões do que em quatro dimensões ou mais!

Aqui usaremos a análise de componentes principais (PCA; consulte “Em profundidade: análise de
componentes principais” na página 433), que é uma técnica rápida de redução de dimensionalidade linear.
Pediremos ao modelo que retorne dois componentes – ou seja, uma representação bidimensional dos dados.

Seguindo a sequência de etapas descritas anteriormente, temos:

In[18]:
from sklearn.decomposition import PCA # 1. Escolha a classe do modelo
model = PCA(n_components=2) # 2. Instancie o modelo com hiperparâmetros model.fit(X_iris)
# 3. Ajuste aos dados. Observe que y não está especificado!
X_2D = model.transform(X_iris) # 4. Transforme os dados em duas dimensões

Agora vamos plotar os resultados. Uma maneira rápida de fazer isso é inserir os resultados no Iris DataFrame
original e usar o lmplot da Seaborn para mostrar os resultados (Figura 5-16):

In[19]: íris['PCA1'] = X_2D[:, 0] íris['PCA2']


= X_2D[:, 1] sns.lmplot("PCA1",
"PCA2", hue='espécie', dados =íris, fit_reg=Falso);

Vemos que na representação bidimensional, as espécies estão bastante bem separadas, embora o algoritmo
PCA não tivesse conhecimento dos rótulos das espécies! Isto indica-nos que uma classificação relativamente
simples será provavelmente eficaz no conjunto de dados, como vimos antes.

352 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Figura 5-16. Os dados Iris projetados para duas dimensões

Aprendizado não supervisionado:

clustering Iris A seguir, veremos como aplicar clustering aos dados Iris. Um algoritmo de agrupamento tenta
encontrar grupos distintos de dados sem referência a nenhum rótulo. Aqui usaremos um poderoso método de
agrupamento chamado modelo de mistura gaussiana (GMM), discutido com mais detalhes em “Em profundidade:
modelos de mistura gaussiana” na página 476. Um GMM tenta modelar os dados como uma coleção de bolhas
gaussianas.

Podemos ajustar o modelo de mistura gaussiana da seguinte forma:

Em [20]:
de sklearn.mixture importar modelo # 1. Escolha a classe do modelo
GMM = GMM (n_components = 3,
covariance_type='full') # 2. Instancie o modelo com hiperparâmetros model.fit(X_iris)
# 3. Ajuste aos dados. Observe que y não está
especificado! y_gmm = modelo.predict(X_iris) # 4. Determine os rótulos do cluster

Como antes, adicionaremos o rótulo do cluster ao Iris DataFrame e usaremos Seaborn para representar
graficamente os resultados (Figura 5-17):

In[21]:
iris['cluster'] = y_gmm
sns.lmplot("PCA1", "PCA2", data=iris, hue='species', col='cluster',
fit_reg=False);

Ao dividir os dados por número de cluster, vemos exatamente quão bem o algoritmo GMM recuperou o rótulo
subjacente: a espécie setosa está perfeitamente separada dentro do cluster 0, enquanto permanece uma pequena
quantidade de mistura entre versicolor e virginica. Isto significa que mesmo sem um especialista para nos dizer
os rótulos das espécies das flores individuais, as medidas destas flores são suficientemente distintas para que
possamos identificar automaticamente a presença destes diferentes grupos de espécies com um simples gesto.

Apresentando o Scikit-Learn | 353


Machine Translated by Google

algoritmo de agrupamento! Esse tipo de algoritmo pode fornecer ainda mais pistas aos especialistas da área
sobre a relação entre as amostras que estão observando.

Figura 5-17. k-means clusters nos dados Iris

Aplicação: Explorando dígitos manuscritos Para demonstrar

esses princípios em um problema mais interessante, vamos considerar uma parte do problema de reconhecimento
óptico de caracteres: a identificação de dígitos manuscritos. Na natureza, esse problema envolve localizar e
identificar caracteres em uma imagem. Aqui pegaremos um atalho e usaremos o conjunto de dígitos pré-
formatados do Scikit-Learn, que está embutido na biblioteca.

Carregando e visualizando os dados dos dígitos

Usaremos a interface de acesso a dados do Scikit-Learn e daremos uma olhada nestes dados:

In[22]: de sklearn.datasets import load_digits digits =


load_digits()
digits.images.shape

Fora[22]: (1797, 8, 8)

Os dados das imagens são uma matriz tridimensional: 1.797 amostras, cada uma consistindo em uma grade 8×8
de pixels. Vamos visualizar os primeiros cem deles (Figura 5-18):

Em [23]: importe matplotlib.pyplot como plt

fig, eixos = plt.subplots(10, 10, figsize=(8, 8),


subplot_kw={'xticks':[], 'yticks':[]},
gridspec_kw=dict(hspace=0,1, wspace=0,1) )

para i, machado em enumerar (axes.flat):


ax.imshow(dígitos.images[i], cmap='binário', interpolação='mais próximo')
ax.text(0,05, 0,05, str(dígitos.target[i]),
transform=ax.transAxes, color='verde')

354 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Figura 5-18. Os dados de dígitos manuscritos; cada amostra é representada por uma grade 8×8 de
pixels

Para trabalhar com esses dados no Scikit-Learn, precisamos de uma representação bidimensional
[n_samples, n_features] . Podemos fazer isso tratando cada pixel da imagem como um recurso - isto
é, achatando as matrizes de pixels para que tenhamos uma matriz de valores de pixels de
comprimento 64 representando cada dígito. Além disso, precisamos do array alvo, que fornece o
rótulo previamente determinado para cada dígito. Essas duas quantidades são incorporadas ao
conjunto de dados de dígitos nos atributos de dados e de destino , respectivamente:

In[24]: X = dígitos.dados
X.forma

Fora[24]: (1797, 64)

In[25]: y = dígitos.target y.shape

Fora[25]: (1797,)

Vemos aqui que existem 1.797 amostras e 64 recursos.

Aprendizagem não supervisionada: redução da

dimensionalidade Gostaríamos de visualizar nossos pontos dentro do espaço de parâmetros de 64


dimensões, mas é difícil visualizar efetivamente pontos em um espaço de dimensões tão altas. Em
vez disso, reduziremos as dimensões para 2, utilizando um método não supervisionado. Aqui, usaremos um

Apresentando o Scikit-Learn | 355


Machine Translated by Google

algoritmo de aprendizado múltiplo chamado Isomap (consulte “Em profundidade: aprendizado múltiplo” na página
445) e transforme os dados em duas dimensões:

Em [26]: de sklearn.manifold import Isomap iso =


Isomap(n_components=2)
iso.fit(digits.data)
data_projected = iso.transform(digits.data)
data_projected.shape

Fora[26]: (1797, 2)

Vemos que os dados projetados agora são bidimensionais. Vamos representar graficamente esses dados para ver
se podemos aprender alguma coisa com sua estrutura (Figura 5-19):

Em [27]: plt.scatter(data_projected[:, 0], data_projected[:, 1], c=digits.target, edgecolor='none',


alpha=0.5,
cmap=plt.cm.get_cmap('spectral' , 10))
plt.colorbar(label=' rótulo de dígito', ticks=range(10))
plt.clim(-0,5, 9,5);

Figura 5-19. Uma incorporação isomap dos dados dos dígitos

Este gráfico nos dá uma boa intuição sobre quão bem vários números são separados no espaço maior de 64
dimensões. Por exemplo, zeros (em preto) e uns (em roxo) têm muito pouca sobreposição no espaço de parâmetros.
Intuitivamente, isso faz sentido: um zero está vazio no meio da imagem, enquanto um um geralmente terá tinta no
meio.
Por outro lado, parece haver um espectro mais ou menos contínuo entre uns e quatros: podemos compreender isto
percebendo que algumas pessoas desenham uns com “chapéus”, o que os faz parecerem-se com quatros.

No geral, porém, os diferentes grupos parecem estar bastante bem separados no espaço de parâmetros: isto diz-
nos que mesmo um algoritmo de classificação supervisionado muito simples deve funcionar adequadamente nestes
dados. Vamos tentar.

356 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Classificação em dígitos

Vamos aplicar um algoritmo de classificação aos dígitos. Tal como aconteceu com os dados Iris anteriormente,
dividiremos os dados em um conjunto de treinamento e teste e ajustaremos um modelo gaussiano ingênuo de Bayes:

In[28]: Xtrain, Xtest, ytrain, ytest = train_test_split(X, y, random_state=0)

Em [29]: de sklearn.naive_bayes importar modelo


GaussianNB =
GaussianNB() model.fit(Xtrain,
ytrain) y_model = model.predict(Xtest)

Agora que previmos nosso modelo, podemos avaliar sua precisão comparando os valores verdadeiros do
conjunto de teste com as previsões:

Em [30]: de sklearn.metrics importar precisão_score


precisão_score (ytest, y_model)

Fora[30]: 0,83333333333333337

Mesmo com este modelo extremamente simples, encontramos cerca de 80% de precisão na classificação
dos dígitos! No entanto, esse número único não nos diz onde erramos — uma boa maneira de fazer isso
é usar a matriz de confusão, que podemos calcular com o Scikit-Learn e plotar com o Seaborn (Figura
5.20) :

In[31]: de sklearn.metrics importar confusão_matrix

mat = matriz_confusão(ytest, y_model)

sns.heatmap(mat, square=True, annot=True, cbar=False)


plt.xlabel(' valor previsto ') plt.ylabel('
valor verdadeiro ');

Figura 5-20. Uma matriz de confusão mostrando a frequência de erros de classificação por nosso
classificador

Apresentando o Scikit-Learn | 357


Machine Translated by Google

Isso nos mostra onde tendem a estar os pontos mal rotulados: por exemplo, um grande número de dois
aqui são classificados erroneamente como uns ou oitos. Outra forma de obter intuição sobre as
características do modelo é representar graficamente as entradas novamente, com seus rótulos previstos.
Usaremos verde para rótulos corretos e vermelho para rótulos incorretos (Figura 5-21):

Em [32]: fig, eixos = plt.subplots(10, 10, figsize=(8, 8),


subplot_kw={'xticks':[], 'yticks':[]},
gridspec_kw=dict(hspace=0.1 , wespaço = 0,1))

para i, machado em enumerar (axes.flat):


ax.imshow(dígitos.images[i], cmap='binário', interpolação='mais próximo')
ax.text(0,05, 0,05, str(y_model[i]),
transform=ax.transAxes,
color='verde' if (ytest[i] == y_model[i]) else 'vermelho')

Figura 5-21. Dados mostrando rótulos corretos (verde) e incorretos (vermelho); para uma versão colorida
deste gráfico, consulte o apêndice online

Examinando esse subconjunto de dados, podemos obter informações sobre onde o algoritmo pode não
estar funcionando de maneira ideal. Para ir além da nossa taxa de classificação de 80%, poderíamos
passar para um algoritmo mais sofisticado, como máquinas de vetores de suporte (consulte “Detalhes:
Máquinas de vetores de suporte” na página 405) ou florestas aleatórias (consulte “Detalhes: Árvores de
decisão e Random Forests” na página 421), ou outra abordagem de classificação.

358 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Resumo
Nesta seção cobrimos os recursos essenciais da representação de dados do Scikit-Learn
e da API do estimador. Independentemente do tipo de estimador, o mesmo padrão de
importação/instanciação/ajuste/previsão é válido. Munido dessas informações sobre a
API do estimador, você pode explorar a documentação do Scikit-Learn e começar a testar
vários modelos em seus dados.

Na próxima seção, exploraremos talvez o tópico mais importante do aprendizado de máquina: como
selecionar e validar seu modelo.

Hiperparâmetros e validação de modelo


Na seção anterior, vimos a receita básica para aplicar um modelo de aprendizado de máquina supervisionado:

1. Escolha uma classe de modelo

2. Escolha os hiperparâmetros do modelo

3. Ajuste o modelo aos dados de treinamento

4. Use o modelo para prever rótulos para novos dados

As duas primeiras partes disso – a escolha do modelo e a escolha dos hiperparâmetros – são talvez a parte
mais importante do uso eficaz dessas ferramentas e técnicas. Para fazer uma escolha informada,
precisamos de uma forma de validar se nosso modelo e nossos hiperparâmetros se ajustam bem aos
dados. Embora isso possa parecer simples, existem algumas armadilhas que você deve evitar para fazer
isso de forma eficaz.

Pensando na validação do modelo Em


princípio, a validação do modelo é muito simples: depois de escolher um modelo e seus
hiperparâmetros, podemos estimar quão eficaz ele é aplicando-o a alguns dos dados de
treinamento e comparando a previsão com o valor conhecido.

As seções a seguir mostram primeiro uma abordagem ingênua para validação de modelo e por que ela
falha, antes de explorar o uso de conjuntos de validação e validação cruzada para uma avaliação de modelo
mais robusta.

Validação de modelo da maneira

errada Vamos demonstrar a abordagem ingênua de validação usando os dados do Iris, que vimos na seção
anterior. Começaremos carregando os dados:

In[1]: de sklearn.datasets import load_iris iris =


load_iris()

Hiperparâmetros e validação de modelo | 359


Machine Translated by Google

X = íris.dados y
= íris.target

A seguir escolhemos um modelo e hiperparâmetros. Aqui usaremos um classificador de k-vizinhos com


n_neighbors=1. Este é um modelo muito simples e intuitivo que diz “o rótulo de um ponto desconhecido é
igual ao rótulo do seu ponto de treinamento mais próximo”:

Em [2]: de sklearn.neighbors import KNeighborsClassifier model =


KNeighborsClassifier(n_neighbors=1)

Em seguida, treinamos o modelo e o usamos para prever rótulos para dados que já conhecemos:

Em[3]: model.fit(X, y)
y_model = modelo.predict(X)

Finalmente, calculamos a fração de pontos rotulados corretamente:

In[4]: de sklearn.metrics importar precisão_score


precisão_pontuação(y, y_model)

Fora[4]: 1,0

Vemos uma pontuação de precisão de 1,0, o que indica que 100% dos pontos foram rotulados corretamente
pelo nosso modelo! Mas isso está realmente medindo a precisão esperada? Será que realmente
encontramos um modelo que esperamos estar correto 100% das vezes?

Como você deve ter percebido, a resposta é não. Na verdade, esta abordagem contém uma falha
fundamental: ela treina e avalia o modelo nos mesmos dados. Além disso, o modelo do vizinho mais
próximo é um estimador baseado em instância que simplesmente armazena os dados de treinamento e
prevê rótulos comparando novos dados com esses pontos armazenados; exceto em casos inventados, ele
obterá sempre 100% de precisão!

Validação de modelo da maneira certa:

conjuntos de validação Então, o que pode ser feito? Podemos ter uma noção melhor do desempenho de
um modelo usando o que é conhecido como conjunto de validação; isto é, retemos algum subconjunto de
dados do treinamento do modelo e, em seguida, usamos esse conjunto de validação para verificar o
desempenho do modelo. Podemos fazer essa divisão usando o utilitário train_test_split no Scikit-Learn:

In[5]: from sklearn.cross_validation import train_test_split # divide os


dados com 50% em cada conjunto X1, X2, y1,
y2 = train_test_split(X, y, random_state=0, train_size=0.5)

# ajusta o modelo em um conjunto de dados


model.fit(X1, y1)

# avalia o modelo no segundo conjunto de dados y2_model


= model.predict(X2)
Precision_score(y2, y2_model)

Saída[5]: 0,90666666666666662

360 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Vemos aqui um resultado mais razoável: o classificador do vizinho mais próximo tem cerca de 90% de
precisão neste conjunto de validação. O conjunto de validação é semelhante aos dados desconhecidos,
porque o modelo não os “viu” antes.

Validação de modelo via validação cruzada

Uma desvantagem de usar um conjunto de validação para validação de modelo é que perdemos uma parte
de nossos dados para o treinamento do modelo. No caso anterior, metade do conjunto de dados não
contribui para o treinamento do modelo! Isso não é o ideal e pode causar problemas – especialmente se o
conjunto inicial de dados de treinamento for pequeno.

Uma maneira de resolver isso é usar validação cruzada – ou seja, fazer uma sequência de ajustes onde
cada subconjunto de dados é usado tanto como conjunto de treinamento quanto como conjunto de validação.
Visualmente, pode parecer algo como a Figura 5-22.

Figura 5-22. Visualização de validação cruzada dupla

Aqui fazemos dois testes de validação, usando alternadamente cada metade dos dados como um conjunto
de validação. Usando os dados divididos de antes, poderíamos implementá-los assim:

Em [6]: y2_model = model.fit (X1, y1).predict (X2)


y1_model = model.fit (X2, y2).predict (X1)
precisão_score (y1, y1_model), precisão_score (y2, y2_model)

Fora[6]: (0,95999999999999996, 0,90666666666666662)

O resultado são duas pontuações de precisão, que poderíamos combinar (por exemplo, calculando a média)
para obter uma medida melhor do desempenho do modelo global. Esta forma específica de validação
cruzada é uma validação cruzada dupla – aquela em que dividimos os dados em dois conjuntos e usamos
cada um deles como um conjunto de validação.

Poderíamos expandir essa ideia para usar ainda mais testes e mais dobras nos dados – por exemplo, a
Figura 5.23 é uma representação visual da validação cruzada quíntupla.

Hiperparâmetros e validação de modelo | 361


Machine Translated by Google

Figura 5-23. Visualização de validação cruzada quíntupla

Aqui dividimos os dados em cinco grupos e usamos cada um deles para avaliar o ajuste do
modelo nos outros 4/5 dos dados. Isso seria um tanto tedioso de fazer manualmente e,
portanto, podemos usar a rotina de conveniência cross_val_score do Scikit-Learn para fazer
isso de forma sucinta:

Em [7]: de sklearn.cross_validation import cross_val_score


cross_val_score(model, X, y, cv=5)

Fora[7]: array([ 0,96666667, 0,96666667, 0,93333333, 0,93333333, 1. ])

Repetir a validação em diferentes subconjuntos de dados nos dá uma ideia ainda melhor do
desempenho do algoritmo.

O Scikit-Learn implementa vários esquemas de validação cruzada que são úteis em situações
específicas; eles são implementados por meio de iteradores no módulo cross_validation . Por
exemplo, podemos querer ir para o caso extremo em que o nosso número de dobras é igual ao
número de pontos de dados; isto é, treinamos em todos os pontos, exceto um em cada
tentativa. Este tipo de validação cruzada é conhecido como validação cruzada deixe um de
fora e pode ser usado da seguinte forma:

In[8]: de sklearn.cross_validation importar LeaveOneOut


pontuações = cross_val_score(modelo, X, y, cv=LeaveOneOut(len(X)))
pontuações

Fora[8]: array([ 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
1., 1., 1. , 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1. ., 1., 1., 1., 1., 1.,
1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 0., 1., 0., 1., 1., 1., 1., 1.,

362 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

1., 1., 1., 1., 1., 0., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1. , 1., 1., 1., 1., 1., 1., 1.,
1., 1., 1., 1., 0., 1., 1., 1., 1., 1. ., 1., 1., 1., 1., 1., 1., 1., 0., 1., 1., 1., 1., 1., 1.,
1., 1., 1., 1., 1., 1., 1., 0., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1. , 1., 1., 1., 1., 1.])

Como temos 150 amostras, a validação cruzada de deixar um de fora produz pontuações para 150
tentativas, e a pontuação indica uma previsão bem-sucedida (1,0) ou malsucedida (0,0).
Tomar a média destes dá uma estimativa da taxa de erro:

Em[9]: pontuações.mean()

Fora[9]: 0,9599999999999996

Outros esquemas de validação cruzada podem ser usados de forma semelhante. Para obter uma
descrição do que está disponível no Scikit-Learn, use IPython para explorar o submódulo
sklearn.cross_validation ou dê uma olhada na documentação de validação cruzada online do Scikit-Learn.

Selecionando o melhor modelo

Agora que vimos os fundamentos da validação e validação cruzada, nos aprofundaremos um pouco
mais na seleção de modelos e na seleção de hiperparâmetros. Essas questões são alguns dos aspectos
mais importantes da prática do aprendizado de máquina, e acho que essas informações são
frequentemente ignoradas em tutoriais introdutórios ao aprendizado de máquina.

De fundamental importância é a seguinte questão: se o nosso estimador apresenta um desempenho


insatisfatório, como devemos avançar? Existem várias respostas possíveis:

• Use um modelo mais complicado/mais flexível • Use

um modelo menos complicado/menos flexível •

Colete mais amostras de treinamento

• Colete mais dados para adicionar recursos a cada amostra

A resposta a esta pergunta é muitas vezes contra-intuitiva. Em particular, às vezes usar um modelo
mais complicado dará resultados piores, e adicionar mais amostras de treinamento pode não melhorar
seus resultados! A capacidade de determinar quais etapas irão melhorar seu modelo é o que separa os
profissionais de aprendizado de máquina bem-sucedidos dos malsucedidos.

Hiperparâmetros e validação de modelo | 363


Machine Translated by Google

A compensação entre viés e variância

Fundamentalmente, a questão do “melhor modelo” consiste em encontrar um ponto ideal no equilíbrio


entre viés e variância. Considere a Figura 5.24, que apresenta dois ajustes de regressão para o
mesmo conjunto de dados.

Figura 5-24. Um modelo de regressão de alto viés e alta variância

É claro que nenhum destes modelos se ajusta particularmente bem aos dados, mas falham de
diferentes maneiras.

O modelo à esquerda tenta encontrar um ajuste em linha reta através dos dados. Como os dados
são intrinsecamente mais complicados do que uma linha reta, o modelo linear nunca será capaz de
descrever bem esse conjunto de dados. Diz-se que tal modelo não se ajusta aos dados; isto é, não
possui flexibilidade de modelo suficiente para considerar adequadamente todos os recursos dos
dados. Outra maneira de dizer isso é que o modelo tem viés alto.

O modelo à direita tenta ajustar um polinômio de ordem superior por meio dos dados.
Aqui, o ajuste do modelo tem flexibilidade suficiente para levar em conta quase perfeitamente as
características finas dos dados, mas mesmo que descreva com muita precisão os dados de
treinamento, sua forma precisa parece refletir mais as propriedades de ruído específicas dos dados,
em vez de refletir mais as propriedades de ruído específicas dos dados. as propriedades intrínsecas
de qualquer processo que gerou esses dados. Diz-se que tal modelo superajusta os dados; isto é,
tem tanta flexibilidade de modelo que o modelo acaba contabilizando erros aleatórios, bem como a
distribuição de dados subjacente. Outra forma de dizer isso é que o modelo possui alta variância.

Para ver isso sob outro prisma, considere o que acontece se usarmos esses dois modelos para
prever o valor y para alguns dados novos. Nos diagramas da Figura 5-25, os pontos vermelhos/mais
claros indicam dados que foram omitidos do conjunto de treinamento.

364 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Figura 5-25. Pontuações de treinamento e validação em modelos de alto viés e alta variância

A pontuação aqui é o R 2
pontuação, ou coeficiente de determinação, que mede quão
2 = 1 indica
bem, um modelo funciona em relação a uma média simples dos valores alvo. R é uma
2
correspondência perfeita, R = 0 indica que o modelo não faz melhor do que simplesmente calcular a média dos
dados, e valores negativos significam modelos ainda piores. A partir das pontuações associadas a estes dois
modelos, podemos fazer uma observação que se aplica de forma mais geral:

• Para modelos de alto viés, o desempenho do modelo no conjunto de validação é semelhante


importante para o desempenho no conjunto de treinamento.

• Para modelos de alta variância, o desempenho do modelo no conjunto de validação é muito pior do que o
desempenho no conjunto de treinamento.

Se imaginarmos que temos alguma capacidade de ajustar a complexidade do modelo, esperaríamos que a
pontuação de treinamento e a pontuação de validação se comportassem conforme ilustrado na Figura 5.26.

O diagrama mostrado na Figura 5-26 costuma ser chamado de curva de validação e vemos os seguintes recursos
essenciais:

• A pontuação do treinamento é sempre superior à pontuação da validação. Este é geralmente o caso: o


modelo se ajustará melhor aos dados que viu do que aos dados que viu.
não visto.

• Para modelos de complexidade muito baixa (um modelo de alto viés), os dados de treinamento são
inadequados, o que significa que o modelo é um preditor fraco tanto para os dados de treinamento quanto
para quaisquer dados anteriormente não vistos.

• Para modelos de complexidade muito alta (um modelo de alta variância), os dados de treinamento são
superajustados, o que significa que o modelo prevê muito bem os dados de treinamento, mas falha para
quaisquer dados não vistos anteriormente.

• Para algum valor intermediário, a curva de validação tem um máximo. Este nível de
a complexidade indica um compromisso adequado entre viés e variância.

Hiperparâmetros e validação de modelo | 365


Machine Translated by Google

Figura 5-26. Um esquema da relação entre complexidade do modelo, pontuação de treinamento e


pontuação de validação

Os meios de ajustar a complexidade do modelo variam de modelo para modelo; quando discutirmos
modelos individuais em profundidade nas seções posteriores, veremos como cada modelo permite tal
ajuste.

Curvas de validação no Scikit-Learn

Vejamos um exemplo de uso de validação cruzada para calcular a curva de validação para uma classe
de modelos. Aqui usaremos um modelo de regressão polinomial: este é um modelo linear generalizado
no qual o grau do polinômio é um parâmetro ajustável. Por exemplo, um polinômio de grau 1 ajusta
uma linha reta aos dados; para os parâmetros do modelo a e b:

y = machado + b

Um polinômio de grau 3 ajusta uma curva cúbica aos dados; para parâmetros do modelo a, b, c, d:

y = ax3 + bx2 + cx + d

Podemos generalizar isso para qualquer número de características polinomiais. No Scikit-Learn,


podemos implementar isso com uma regressão linear simples combinada com o pré-processador
polinomial. Usaremos um pipeline para encadear essas operações (discutiremos recursos polinomiais
e pipelines mais detalhadamente em “Engenharia de recursos” na página 375):

366 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Em [10]: de sklearn.preprocessing importar PolynomialFeatures de


sklearn.linear_model importar LinearRegression de
sklearn.pipeline importar make_pipeline

def Regressão Polinomial(grau=2, **kwargs):


retornar make_pipeline(PolynomialFeatures(grau),
Regressão Linear(**kwargs))

Agora vamos criar alguns dados aos quais ajustaremos nosso modelo:

In[11]: importar numpy como np

def make_data(N, err=1.0, rseed=1): #


amostra aleatoriamente os dados
rng = np.random.RandomState(rseed)
X = rng.rand(N, 1) ** 2 y = 10
- 1. / (X.ravel() + 0,1) se err > 0:

y += err * rng.randn(N)
retornar X, y

X, y = fazer_dados(40)

Agora podemos visualizar nossos dados, juntamente com ajustes polinomiais de vários graus
(Figura 5-27):

Em [12]: % matplotlib embutido


import matplotlib.pyplot como plt
import seaborn; seaborn.set() # formatação do gráfico

X_test = np.linspace(-0,1, 1,1, 500)[:, Nenhum]

plt.scatter(X.ravel(), y, color='black') axis = plt.axis()


para grau em [1, 3, 5]:

y_test = PolynomialRegression(grau).fit(X, y).predict(X_test) plt.plot(X_test.ravel(),


y_test, label='degree={0}'.format(degree)) plt.xlim(- 0,1, 1,0) plt.ylim(-2, 12)
plt.legend(loc='melhor');

O botão que controla a complexidade do modelo, neste caso, é o grau do polinômio, que pode ser
qualquer número inteiro não negativo. Uma pergunta útil a ser respondida é esta: que grau de polinômio
fornece uma compensação adequada entre viés (underfitting) e variância (overfitting)?

Hiperparâmetros e validação de modelo | 367


Machine Translated by Google

Figura 5-27. Três modelos polinomiais diferentes ajustados a um conjunto de dados

Podemos progredir nisso visualizando a curva de validação para esses dados e modelo específicos; podemos fazer
isso diretamente usando a rotina de conveniência validação_curve fornecida pelo Scikit-Learn. Dado um modelo,
dados, nome de parâmetro e um intervalo para explorar, esta função calculará automaticamente a pontuação de
treinamento e a pontuação de validação em todo o intervalo (Figura 5-28):

Em [13]:
de sklearn.learning_curve importar validação_curve grau =
np.arange(0, 21) train_score,
val_score = validação_curve(PolynomialRegression(), X, y, 'polynomialfeatures__degree',
grau, cv=7)

plt.plot(grau, np.median(train_score, 1), color='azul', label='pontuação de treinamento') plt.plot(grau,


np.median(val_score, 1), color='vermelho', rótulo ='pontuação de validação') plt.legend(loc='melhor')
plt.ylim(0, 1) plt.xlabel('grau')

plt.ylabel('pontuação');

Isto mostra precisamente o comportamento qualitativo que esperamos: a pontuação do treinamento é em todos os
lugares superior à pontuação da validação; a pontuação do treinamento melhora monotonicamente com o aumento da
complexidade do modelo; e a pontuação de validação atinge um máximo antes de cair à medida que o modelo se torna
superajustado.

368 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Figura 5-28. As curvas de validação para os dados na Figura 5-27 (cf. Figura 5-26)

A partir da curva de validação, podemos ler que o equilíbrio ideal entre viés e variância é
encontrado para um polinômio de terceira ordem; podemos calcular e exibir esse ajuste sobre os
dados originais da seguinte forma (Figura 5-29):

Em [14]: plt.scatter(X.ravel(), y) lim =


plt.axis() y_test =
PolynomialRegression(3).fit(X, y).predict(X_test) plt.plot(X_test.ravel( ),
y_teste); plt.axis(lim);

Figura 5-29. O modelo ideal com validação cruzada para os dados da Figura 5-27

Hiperparâmetros e validação de modelo | 369


Machine Translated by Google

Observe que encontrar esse modelo ideal não exigiu, na verdade, que calculássemos a pontuação de
treinamento, mas examinar a relação entre a pontuação de treinamento e a pontuação de validação pode
nos fornecer informações úteis sobre o desempenho do modelo.

Curvas de aprendizado

Um aspecto importante da complexidade do modelo é que o modelo ideal geralmente dependerá do


tamanho dos seus dados de treinamento. Por exemplo, vamos gerar um novo conjunto de dados com um
fator de mais cinco pontos (Figura 5-30):

Em[15]: X2, y2 = make_data(200)


plt.scatter(X2.ravel(), y2);

Figura 5-30. Dados para demonstrar curvas de aprendizado

Duplicaremos o código anterior para traçar a curva de validação para este conjunto de dados maior; para
referência, vamos plotar também os resultados anteriores (Figura 5-31):

Em [16]:
grau = np.arange (21)
train_score2, val_score2 = validação_curve(PolynomialRegression(), X2, y2,
'polynomialfeatures__degree', grau,
cv=7)

plt.plot(grau, np.median(train_score2, 1), color='azul', label='pontuação


de treinamento') plt.plot(grau,
np.median(val_score2, 1), color='vermelho', rótulo ='pontuação de validação') plt.plot(grau,
np.median(train_score, 1), color='azul', alfa=0,3,
estilo de linha='tracejado')
plt.plot(grau, np.median(val_score, 1), color='vermelho', alfa=0,3,
linestyle='tracejado')
plt.legend(loc=' centro inferior')
plt.ylim(0, 1)

370 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

plt.xlabel('grau')
plt.ylabel('pontuação');

Figura 5-31. Curvas de aprendizado para o modelo polinomial ajustado aos dados da Figura 5-30

As linhas sólidas mostram os novos resultados, enquanto as linhas tracejadas mais fracas mostram os resultados do conjunto

de dados menor anterior. Fica claro pela curva de validação que o conjunto de dados maior pode suportar um modelo muito
mais complicado: o pico aqui é provavelmente em torno de um grau de 6, mas mesmo um modelo de grau 20 não está
superajustando seriamente os dados – a validação e as pontuações de treinamento permanecem muito próximas.

Assim vemos que o comportamento da curva de validação não possui uma, mas duas entradas importantes: a complexidade
do modelo e o número de pontos de treinamento. Muitas vezes é útil explorar o comportamento do modelo em função do
número de pontos de treinamento, o que podemos fazer usando subconjuntos cada vez maiores de dados para ajustar nosso
modelo. Um gráfico da pontuação de treinamento/validação em relação ao tamanho do conjunto de treinamento é conhecido
como curva de aprendizado.

O comportamento geral que esperaríamos de uma curva de aprendizado é este:

• Um modelo de uma determinada complexidade se ajustará demais a um pequeno conjunto de dados: isso significa que a
pontuação do treinamento será relativamente alta, enquanto a pontuação da validação será relativamente

baixa. • Um modelo de uma determinada complexidade será insuficiente para um grande conjunto de dados: isso significa que o
a pontuação de treinamento diminuirá, mas a pontuação de validação aumentará.

• Um modelo nunca dará, exceto por acaso, uma pontuação melhor ao conjunto de validação do que ao conjunto de
treinamento: isso significa que as curvas devem continuar se aproximando, mas
nunca cruze.

Hiperparâmetros e validação de modelo | 371


Machine Translated by Google

Com essas características em mente, esperaríamos que uma curva de aprendizado se parecesse
qualitativamente com a mostrada na Figura 5-32.

Figura 5-32. Esquema mostrando a interpretação típica das curvas de aprendizado

A característica notável da curva de aprendizado é a convergência para uma pontuação específica à medida
que o número de amostras de treinamento aumenta. Em particular, uma vez que você tenha pontos
suficientes para a convergência de um modelo específico, adicionar mais dados de treinamento não o ajudará!
A única maneira de aumentar o desempenho do modelo neste caso é usar outro modelo (geralmente mais
complexo).

Curvas de aprendizado no Scikit-Learn

Scikit-Learn oferece um utilitário conveniente para calcular essas curvas de aprendizado a partir de seus
modelos; aqui calcularemos uma curva de aprendizado para nosso conjunto de dados original com um
modelo polinomial de segunda ordem e um polinômio de nona ordem (Figura 5-33):

In[17]:
de sklearn.learning_curve importar learning_curve

fig, ax = plt.subplots(1, 2, figsize=(16, 6))


fig.subplots_adjust(esquerda=0,0625, direita=0,95, wspace=0,1)

para i, grau em enumerar([2, 9]):


N, train_lc, val_lc = learning_curve(PolynomialRegression(grau),
X, y, cv=7,
train_sizes=np.linspace(0,3, 1, 25))

machado[i].plot(N, np.mean(train_lc, 1), color='azul', rótulo='pontuação de treinamento')


ax[i].plot(N, np.mean(val_lc, 1), cor ='vermelho', label='pontuação de validação')
ax[i].hlines(np.mean([train_lc[-1], val_lc[-1]]), N[0], N[-1], cor ='cinza', estilo de linha='tracejado')

372 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

ax[i].set_ylim(0, 1)
ax[i].set_xlim(N[0], N[-1])
ax[i].set_xlabel(' tamanho do treinamento')
ax[i].set_ylabel('pontuação ')
ax[i].set_title('degree = {0}'.format(degree), size=14)
ax[i].legend(loc='best')

Figura 5-33. Curvas de aprendizado para um modelo de baixa complexidade (esquerda) e um modelo de alta
complexidade (direita)

Este é um diagnóstico valioso porque nos dá uma representação visual de como nosso modelo responde ao aumento
dos dados de treinamento. Em particular, quando a sua curva de aprendizagem já convergiu (ou seja, quando as
curvas de treinamento e validação já estão próximas uma da outra), adicionar mais dados de treinamento não
melhorará significativamente o ajuste! Esta situação é vista no painel esquerdo, com a curva de aprendizado para o
modelo grau 2.

A única maneira de aumentar a pontuação convergida é usar um modelo diferente (geralmente mais complicado).
Vemos isso no painel direito: ao passar para um modelo muito mais complicado, aumentamos a pontuação de
convergência (indicada pela linha tracejada), mas às custas de uma maior variância do modelo (indicada pela diferença
entre o treinamento e o valor). pontuações de validação). Se adicionarmos ainda mais pontos de dados, a curva de
aprendizagem do modelo mais complicado acabaria por convergir.

Traçar uma curva de aprendizado para sua escolha específica de modelo e conjunto de dados pode ajudá-lo a tomar
esse tipo de decisão sobre como avançar na melhoria de sua análise.

Validação na prática: pesquisa em grade

A discussão anterior tem como objetivo fornecer alguma intuição sobre a compensação entre viés e variância e sua
dependência da complexidade do modelo e do tamanho do conjunto de treinamento. Na prática, os modelos geralmente
têm mais de um botão para girar e, portanto, os gráficos de validação e curvas de aprendizado mudam de linhas para
superfícies multidimensionais. Nestes casos, tais visualizações são difíceis e preferiríamos simplesmente encontrar o
modelo específico que maximiza a pontuação de validação.

Hiperparâmetros e validação de modelo | 373


Machine Translated by Google

Scikit-Learn fornece ferramentas automatizadas para fazer isso no módulo grid_search . Aqui está um exemplo de uso de
pesquisa em grade para encontrar o modelo polinomial ideal. Exploraremos uma grade tridimensional de características do
modelo – a saber, o grau polinomial, a bandeira que nos diz se devemos ajustar a interceptação e a bandeira que nos diz
se devemos normalizar o problema. Podemos configurar isso usando o metaestimador GridSearchCV do Scikit-Learn:

In[18]: de sklearn.grid_search importar GridSearchCV

param_grid = {'polynomialfeatures__degree': np.arange(21),


'linearregression__fit_intercept': [Verdadeiro, Falso],
'linearregression__normalize': [Verdadeiro, Falso]}

grade = GridSearchCV(PolynomialRegression(), param_grid, cv=7)

Observe que, como um estimador normal, isso ainda não foi aplicado a nenhum dado. Chamar o método fit() ajustará o

modelo em cada ponto da grade, acompanhando as pontuações ao longo do caminho:

Em[19]: grid.fit(X, y);

Agora que isso está adequado, podemos solicitar os melhores parâmetros da seguinte forma:

Em[20]: grid.best_params_

Out[20]: {'linearregression__fit_intercept': Falso,


'linearregression__normalize': Verdadeiro,
'polynomialfeatures__degree': 4}

Finalmente, se desejarmos, podemos usar o melhor modelo e mostrar o ajuste aos nossos dados usando o código anterior
(Figura 5-34):

Em[21]: modelo = grid.best_estimator_

plt.scatter(X.ravel(), y) lim =
plt.axis() y_test =
model.fit(X, y).predict(X_test) plt.plot(X_test.ravel(),
y_test, hold=True) ; plt.axis(lim);

A pesquisa em grade oferece muito mais opções, incluindo a capacidade de especificar uma função de pontuação
personalizada, paralelizar os cálculos, fazer pesquisas aleatórias e muito mais. Para obter informações, consulte os
exemplos em “Em profundidade: estimativa de densidade do kernel” na página 491 e “Aplicativo: um pipeline de detecção
de rosto” na página 506, ou consulte a documentação de pesquisa em grade do Scikit-Learn.

374 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Figura 5-34. O modelo mais adequado determinado através de uma pesquisa automática em grade

Resumo Nesta

seção, começamos a explorar o conceito de validação de modelo e otimização de hiperparâmetros, concentrando-nos


nos aspectos intuitivos do trade-off viés-variância e como ele entra em jogo ao ajustar modelos aos dados. Em particular,
descobrimos que o uso de um conjunto de validação ou abordagem de validação cruzada é vital ao ajustar parâmetros,
a fim de evitar ajuste excessivo para modelos mais complexos/flexíveis.

Nas seções posteriores, discutiremos os detalhes de modelos particularmente úteis e falaremos sobre quais ajustes estão
disponíveis para esses modelos e como esses parâmetros livres afetam a complexidade do modelo. Lembre-se das
lições desta seção enquanto lê e aprende sobre essas abordagens de aprendizado de máquina!

Engenharia de recursos
As seções anteriores descrevem as ideias fundamentais do aprendizado de máquina, mas todos os exemplos pressupõem
que você tenha dados numéricos em um formato organizado [ n_samples, n_features] . No mundo real, os dados
raramente chegam nesta forma. Com isso em mente, uma das etapas mais importantes no uso do aprendizado de
máquina na prática é a engenharia de recursos – ou seja, pegar todas as informações que você tem sobre o seu
problema e transformá-las em números que você pode usar para construir sua matriz de recursos.

Nesta seção, abordaremos alguns exemplos comuns de tarefas de engenharia de recursos: recursos para representar
dados categóricos, recursos para representar texto e recursos para representar imagens. Além disso, discutiremos
recursos derivados para aumentar a complexidade do modelo e imputação de dados ausentes. Freqüentemente, esse
processo é conhecido como vetorização, pois envolve a conversão de dados arbitrários em vetores bem comportados.

Engenharia de recursos | 375


Machine Translated by Google

Recursos categóricos
Um tipo comum de dados não numéricos são os dados categóricos. Por exemplo, imagine
você está explorando alguns dados sobre preços de habitação e junto com recursos numéricos
como “preço” e “quartos”, você também tem informações de “bairro”. Por exemplo,
seus dados podem ser parecidos com isto:

Em[1]: dados = [
{'preço': 850000, 'quartos': 4, 'bairro': 'Queen Anne'},
{'preço': 700000, 'quartos': 3, 'bairro': 'Fremont'},
{'preço': 650000, 'quartos': 3, 'bairro': 'Wallingford'},
{'preço': 600.000, 'quartos': 2, 'bairro': 'Fremont'}
]

Você pode ficar tentado a codificar esses dados com um mapeamento numérico direto:

Em[2]: {'Queen Anne': 1, 'Fremont': 2, 'Wallingford': 3};

Acontece que esta geralmente não é uma abordagem útil no Scikit-Learn: o pacote
modelos fazem a suposição fundamental de que características numéricas refletem
quantidades. Assim, tal mapeamento implicaria, por exemplo, que a Rainha Ana <Freÿ
mont < Wallingford, ou mesmo aquele Wallingford - Queen Anne = Fremont, que (nicho
piadas demográficas à parte) não faz muito sentido.

Neste caso, uma técnica comprovada é usar a codificação one-hot, que efetivamente cria
colunas extras indicando a presença ou ausência de uma categoria com valor 1 ou 0,
respectivamente. Quando seus dados vêm como uma lista de dicionários, o DictVector do Scikit-Learn
izer fará isso por você:

Em [3]: de sklearn.feature_extraction importar DictVectorizer


vec = DictVectorizer(esparso=Falso, dtype=int)
vec.fit_transform(dados)

Fora[3]: array([[ [ [ [ 0, 1, 0, 850000, 4],


1, 0, 0, 700000, 3],
0, 0, 1, 650000, 3],
1, 0, 0, 600000, 2]], dtype=int64)

Observe que a coluna de vizinhança foi expandida em três colunas separadas,


representando os três rótulos de vizinhança, e que cada linha tenha um 1 na coluna
associada à sua vizinhança. Com essas características categóricas assim codificadas, você
pode prosseguir normalmente com o ajuste de um modelo Scikit-Learn.

Para ver o significado de cada coluna, você pode inspecionar os nomes dos recursos:

Em[4]: vec.get_feature_names()

Fora[4]: ['neighbourhood=Fremont',
'bairro = Rainha Anne',
'bairro=Wallingford',
'preço',
'quartos']

376 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Há uma clara desvantagem dessa abordagem: se a sua categoria tiver muitos valores possíveis, isso
poderá aumentar bastante o tamanho do seu conjunto de dados. No entanto, como os dados codificados
contêm principalmente zeros, uma saída esparsa pode ser uma solução muito eficiente:

In[5]: vec = DictVectorizer(sparse=True, dtype=int)


vec.fit_transform(dados)

Out[5]: <matriz esparsa 4x5 do tipo '<class 'numpy.int64'>'


com 12 elementos armazenados no formato Compressed Sparse Row>

Muitos (embora ainda não todos) dos estimadores do Scikit-Learn aceitam essas entradas esparsas ao
ajustar e avaliar modelos. sklearn.preprocessing.OneHotEncoder e sklearn.feature_extraction.FeatureHasher
são duas ferramentas adicionais que o Scikit-Learn inclui para oferecer suporte a esse tipo de
codificação.

Características do texto

Outra necessidade comum na engenharia de recursos é converter texto em um conjunto de valores


numéricos representativos. Por exemplo, a maior parte da mineração automática de dados de mídias
sociais depende de alguma forma de codificação do texto como números. Um dos métodos mais simples
de codificação de dados é por contagem de palavras: você pega cada trecho de texto, conta as
ocorrências de cada palavra dentro dele e coloca os resultados em uma tabela.

Por exemplo, considere o seguinte conjunto de três frases:

In[6]: amostra = ['problema do mal', 'rainha


má', 'problema
do horizonte']

Para uma vetorização desses dados com base na contagem de palavras, poderíamos construir uma
coluna representando a palavra “problema”, a palavra “mal”, a palavra “horizonte” e assim por diante.
Embora seja possível fazer isso manualmente, podemos evitar o tédio usando o CountVectorizer do
Scikit-Learn:

Em [7]: de sklearn.feature_extraction.text importar CountVectorizer

vec = ContVectorizer()
X = vec.fit_transform(amostra)
X

Out[7]: <matriz esparsa 3x5 do tipo '<class 'numpy.int64'>'


com 7 elementos armazenados no formato Compressed Sparse Row>

O resultado é uma matriz esparsa que registra o número de vezes que cada palavra aparece; é mais
fácil inspecionar se convertermos isso em um DataFrame com colunas rotuladas:

Em [8]: importe pandas como


pd pd.DataFrame(X.toarray(), columns=vec.get_feature_names())

Engenharia de recursos | 377


Machine Translated by Google

Fora[8]: horizonte maligno da rainha problemática


0 1 0 10 1
1 1 0 0 0 1
2 0 1 0 1 0

No entanto, existem alguns problemas com esta abordagem: a contagem bruta de palavras leva a dificuldades.
turas que colocam muito peso em palavras que aparecem com muita frequência, e isso pode ser
abaixo do ideal em alguns algoritmos de classificação. Uma abordagem para corrigir isso é conhecida como
frequência de termo – frequência inversa de documento (TF – IDF), que pondera a contagem de palavras
por uma medida da frequência com que aparecem nos documentos. A sintaxe para computação
esses recursos são semelhantes ao exemplo anterior:

Em [9]: de sklearn.feature_extraction.text importar TfidfVectorizer


vec = TfidfVectorizer()
X = vec.fit_transform(amostra)
pd.DataFrame(X.toarray(), colunas=vec.get_feature_names())

Fora[9]: horizonte do mal do problema rainha


0 0,517856 0,000000 0,680919 0,517856 0,000000
1 0,605349 0,000000 0,000000 0,000000 0,795961
2 0,000000 0,795961 0,000000 0,605349 0,000000

Para obter um exemplo de uso de TF – IDF em um problema de classificação, consulte “Em profundidade: Naive
Classificação de Bayes” na página 382.

Recursos de imagem

Outra necessidade comum é codificar imagens adequadamente para análise de aprendizado de máquina.
A abordagem mais simples é a que usamos para os dados de dígitos em “Apresentando o Scikit-Learn” na
página 343: simplesmente usando os próprios valores de pixel. Mas dependendo do
aplicação, tais abordagens podem não ser ideais.

Um resumo abrangente das técnicas de extração de características para imagens está muito além
escopo desta seção, mas você pode encontrar excelentes implementações de muitos dos
abordagens padrão no projeto Scikit-Image. Para obter um exemplo de uso do Scikit-Learn e do Scikit-Image
juntos, consulte “Aplicativo: um pipeline de detecção de rosto” na página
506.

Recursos derivados

Outro tipo útil de recurso é aquele que é matematicamente derivado de alguma entrada
características. Vimos um exemplo disso em “Hiperparâmetros e validação de modelo” em
página 359 quando construímos recursos polinomiais a partir de nossos dados de entrada. Nós vimos isso
poderíamos converter uma regressão linear em uma regressão polinomial não alterando o
modelo, mas transformando a entrada! Isso às vezes é conhecido como função base
regressão e é explorado mais detalhadamente em “Em profundidade: regressão linear” na página 390.

378 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Por exemplo, estes dados claramente não podem ser bem descritos por uma linha reta
(Figura 5-35):

Em [10]: % matplotlib importação


inline numpy como
np importação matplotlib.pyplot como plt

x = np.array([1, 2, 3, 4, 5]) y =
np.array([4, 2, 1, 3, 7]) plt.scatter(x,
y);

Figura 5-35. Dados que não são bem descritos por uma linha reta

Ainda assim, podemos ajustar uma linha aos dados usando LinearRegression e obter o resultado ideal
(Figura 5-36):

Em [11]: de sklearn.linear_model import LinearRegression X = x[:,


np.newaxis] model =
LinearRegression().fit(X, y) yfit =
model.predict(X) plt.scatter(x,
y) plt .plot(x, yfit);

Figura 5-36. Um ajuste em linha reta ruim

Engenharia de recursos | 379


Machine Translated by Google

Está claro que precisamos de um modelo mais sofisticado para descrever o relacionamento
entre x e y. Podemos fazer isso transformando os dados, adicionando colunas extras de
recursos para gerar mais flexibilidade no modelo. Por exemplo, podemos adicionar polinômio
recursos para os dados desta forma:

In[12]: de sklearn.preprocessing importar PolynomialFeatures


poli = PolynomialFeatures(grau=3, include_bias=False)
X2 = poli.fit_transform(X)
imprimir (X2)

[[1.] 1. 1.
[8.] 2. 4.
[3.9.27.]
[4.16.64.]
[5.25.125.]]

A matriz de características derivada tem uma coluna representando x, e uma segunda coluna representa
2 3
ressentido x , e uma terceira coluna representando x . Calculando uma regressão linear em
esta entrada expandida fornece um ajuste muito mais próximo dos nossos dados (Figura 5-37):

Em[13]: modelo = LinearRegression().fit(X2, y)


yfit = modelo.predict(X2)
plt.dispersão(x, y)
plt.plot(x, yfit);

Figura 5-37. Um ajuste linear para recursos polinomiais derivados dos dados

Esta ideia de melhorar um modelo não mudando o modelo, mas transformando o


entradas, é fundamental para muitos dos métodos de aprendizado de máquina mais poderosos. Nós
explore essa ideia mais detalhadamente em “Em profundidade: regressão linear” na página 390 no contexto
de regressão de função de base. De forma mais geral, este é um caminho motivacional para o poder
conjunto completo de técnicas conhecidas como métodos de kernel, que exploraremos em “Detalhamento:
Máquinas de vetores de suporte” na página 405.

380 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Imputação de dados ausentes

Outra necessidade comum na engenharia de recursos é lidar com dados ausentes. Nós discutimos
o tratamento de dados ausentes em DataFrames em “Tratamento de dados ausentes” na página 119,
e vi que frequentemente o valor NaN é usado para marcar valores ausentes. Por exemplo, nós
pode ter um conjunto de dados parecido com este:

In[14]: de numpy importação nan


X = np.array([[ nan, 0, 3 ],
[ 3, 7, 9 ],
[ 3, 5, 2 ],
[ 4, em, 6 ],
[ 8, 8, 1 ]])
y = np.array([14, 16, -1, 8, -5])

Ao aplicar um modelo típico de aprendizado de máquina a esses dados, precisaremos primeiro


substitua esses dados ausentes por algum valor de preenchimento apropriado. Isso é conhecido como imputação
solução de valores faltantes, e as estratégias variam de simples (por exemplo, substituição de valores faltantes
com a média da coluna) a sofisticado (por exemplo, usando preenchimento de matriz ou um
modelo robusto para lidar com esses dados).

As abordagens sofisticadas tendem a ser muito específicas da aplicação e não vamos nos aprofundar
neles aqui. Para uma abordagem de imputação de linha de base, usando a média, mediana ou a maioria
valor frequente, o Scikit-Learn fornece a classe Imputer :

In[15]: de sklearn.preprocessing import Imputer


imp = Imputer(estratégia='média')
X2 = imp.fit_transform(X)
X2

Fora[15]: matriz([[ 4,5, 0. [ 3. 7. , 3.],


[ 3. [ 4., [ 8. , 9.],
, 5. , 2.],
, 5. , 6.],
, 8. , 1.]])

Vemos que nos dados resultantes, os dois valores ausentes foram substituídos pelos
média dos valores restantes na coluna. Esses dados imputados podem então ser alimentados
diretamente em, por exemplo, um estimador LinearRegression :

Em[16]: modelo = LinearRegression().fit(X2, y)


modelo.prever(X2)

Fora[16]:
matriz([13.14869292, 14.3784627 , -1,15539732, 10,96606197, -5,33782027])

Pipelines de recursos

Com qualquer um dos exemplos anteriores, pode rapidamente tornar-se tedioso fazer a transformação
mações à mão, especialmente se você deseja encadear várias etapas. Por exemplo,
podemos querer um pipeline de processamento parecido com este:

Engenharia de recursos | 381


Machine Translated by Google

1. Imputar valores ausentes usando a média 2.

Transformar recursos em quadráticos 3.

Ajustar uma regressão linear

Para agilizar esse tipo de pipeline de processamento, o Scikit-Learn fornece um objeto pipeline, que
pode ser usado da seguinte forma:

In[17]: de sklearn.pipeline importar make_pipeline

modelo = make_pipeline(Imputador(estratégia='média'),
Recursos polinomiais (grau = 2),
Regressão linear())

Este pipeline se parece e funciona como um objeto Scikit-Learn padrão e aplicará todas as etapas
especificadas a quaisquer dados de entrada.

In[18]: model.fit(X, y) # X com valores faltantes, acima print(y)

print(model.predict(X))

[14 16 -1 8 -5] [ 14.


16. -1. 8.-5.]

Todas as etapas do modelo são aplicadas automaticamente. Observe que, para simplificar esta
demonstração, aplicamos o modelo aos dados nos quais ele foi treinado; é por isso que ele foi capaz de
prever perfeitamente o resultado (consulte “Hiperparâmetros e validação de modelo” na página 359 para
uma discussão mais aprofundada sobre isso).

Para obter alguns exemplos de pipelines do Scikit-Learn em ação, consulte a seção a seguir sobre a
classificação ingênua de Bayes, bem como “Em profundidade: regressão linear” na página 390 e “Em
profundidade: máquinas de vetores de suporte” na página 405.

Em profundidade: classificação Naive Bayes


As quatro seções anteriores forneceram uma visão geral dos conceitos de aprendizado de máquina.
Nesta seção e nas seguintes, examinaremos mais de perto vários algoritmos específicos para
aprendizagem supervisionada e não supervisionada, começando aqui com a classificação ingênua de
Bayes.

Os modelos Naive Bayes são um grupo de algoritmos de classificação extremamente rápidos e simples
que geralmente são adequados para conjuntos de dados de dimensões muito altas. Por serem tão
rápidos e terem poucos parâmetros ajustáveis, eles acabam sendo muito úteis como uma linha de
base rápida e suja para um problema de classificação. Esta seção se concentrará em uma explicação
intuitiva de como funcionam os classificadores Bayes ingênuos, seguida por alguns exemplos deles em
ação em alguns conjuntos de dados.

382 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Classificação Bayesiana Os

classificadores Naive Bayes são construídos com base em métodos de classificação bayesiana.
Estes baseiam-se no teorema de Bayes, que é uma equação que descreve a relação de
probabilidades condicionais de quantidades estatísticas. Na classificação Bayesiana, estamos
interessados em encontrar a probabilidade de um rótulo dados alguns recursos observados, que
podemos escrever como recursos PL. O teorema de Bayes nos diz como expressar isso em termos
de quantidades que podemos calcular mais diretamente:

P apresenta LPL
Recursos de PL = Recursos P

Se estivermos tentando decidir entre dois rótulos – vamos chamá-los de L1 e L2 – então uma
maneira de tomar essa decisão é calcular a razão das probabilidades posteriores para cada
rótulo:

PL 1 características P apresenta L 1 PL 1
=
PL 1 características P apresenta L 2 PL
2

Tudo o que precisamos agora é de algum modelo pelo qual possamos calcular P características
Li para cada rótulo. Tal modelo é chamado de modelo generativo porque especifica o processo
aleatório hipotético que gera os dados. Especificar este modelo generativo para cada rótulo é a
parte principal do treinamento de tal classificador Bayesiano. A versão geral de tal etapa de
treinamento é uma tarefa muito difícil, mas podemos torná-la mais simples através do uso de
algumas suposições simplificadoras sobre a forma deste modelo.

É aqui que entra o “ingênuo” em “ingênuo Bayes”: se fizermos suposições muito ingênuas
sobre o modelo generativo para cada rótulo, poderemos encontrar uma aproximação
grosseira do modelo generativo para cada classe e então prosseguir com o Classificação Bayesiana.
Diferentes tipos de classificadores Bayes ingênuos baseiam-se em diferentes suposições
ingênuas sobre os dados, e examinaremos algumas delas nas seções a seguir. Começamos
com as importações padrão:

Em [1]: % matplotlib inline


import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns; sns.set()

Gaussian Naive Bayes

Talvez o classificador ingênuo de Bayes mais fácil de entender seja o Bayes ingênuo gaussiano.
Neste classificador, a suposição é que os dados de cada rótulo são extraídos de uma distribuição
gaussiana simples. Imagine que você tem os seguintes dados (Figura 5-38):

Em profundidade: Classificação Naive Bayes | 383


Machine Translated by Google

In[2]: de sklearn.datasets importar make_blobs


X, y = make_blobs(100, 2, centros=2, random_state=2, cluster_std=1,5) plt.scatter(X[:,
0], X[:, 1], c=y, s=50, cmap= 'RdBu');

Figura 5-38. Dados para a classificação gaussiana ingênua de Bayes

Uma maneira extremamente rápida de criar um modelo simples é assumir que os dados são descritos por
uma distribuição gaussiana sem covariância entre dimensões. Podemos ajustar esse modelo simplesmente
encontrando a média e o desvio padrão dos pontos dentro de cada rótulo, que é tudo o que você precisa
para definir tal distribuição. O resultado dessa suposição gaussiana ingênua é mostrado na Figura 5-39.

Figura 5-39. Visualização do modelo gaussiano ingênuo de Bayes

384 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

As elipses aqui representam o modelo generativo gaussiano para cada rótulo, com maior probabilidade em
direção ao centro das elipses. Com este modelo generativo implementado para cada classe, temos uma receita
simples para calcular a probabilidade P apresenta L1 para qualquer ponto de dados e, assim, podemos calcular
rapidamente a razão posterior e determinar qual rótulo é o mais provável para um determinado ponto.

Este procedimento é implementado no estimador sklearn.naive_bayes.GaussianNB do Scikit-Learn :

Em [3]: de sklearn.naive_bayes importar modelo


GaussianNB =
GaussianNB() model.fit (X, y);

Agora vamos gerar alguns novos dados e prever o rótulo:

Em[4]: rng = np.random.RandomState(0)


Xnew = [-6, -14] + [14, 18] * rng.rand(2000, 2) ynew =
model.predict(Xnew)

Agora podemos representar graficamente esses novos dados para ter uma ideia de onde está o limite de decisão
(Figura 5-40):

In[5]: plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='RdBu') lim = plt.axis()
plt.scatter(Xnew[ :,
0], Xnew[:, 1], c=ynew, s=20, cmap='RdBu', alfa=0,1) plt.axis(lim);

Figura 5-40. Visualização da classificação gaussiana ingênua de Bayes

Vemos um limite ligeiramente curvo nas classificações - em geral, o limite no Bayes ingênuo gaussiano é
quadrático.

Uma boa parte desse formalismo bayesiano é que ele permite naturalmente a classificação probabilística, que
podemos calcular usando o método predizer_proba :

Em profundidade: Classificação Naive Bayes | 385


Machine Translated by Google

Em[6]: yprob = model.predict_proba(Xnew)


yprob[-8:].redonda(2)

Fora[6]: array([[ 0,89, 0,11],


[1.0.], ,
[1.0.], ,
[1.0.], ,
[1.0.], ,
[1.0.], ,
[0.1.], ,
[0,15, 0,85]])

As colunas fornecem as probabilidades posteriores do primeiro e do segundo rótulos, respectivamente.


Se você está procurando estimativas de incerteza em sua classificação, Bayesian
abordagens como essa podem ser uma abordagem útil.

É claro que a classificação final só será tão boa quanto os pressupostos do modelo que
levar a isso, e é por isso que o ingênuo Bayes gaussiano muitas vezes não produz resultados muito bons
resultados. Ainda assim, em muitos casos – especialmente à medida que o número de recursos se torna grande – este
suposição não é prejudicial o suficiente para evitar que o ingênuo Bayes gaussiano seja um
método útil.

Multinomial Ingênuo Bayes


A suposição gaussiana que acabamos de descrever não é de forma alguma a única suposição simples
que poderia ser usado para especificar a distribuição generativa para cada rótulo. Outro útil
exemplo é Bayes multinomial ingênuo, onde os recursos são considerados gerados
de uma distribuição multinomial simples. A distribuição multinomial descreve o
probabilidade de observar contagens entre uma série de categorias e, portanto, multinomiais
ingênuo Bayes é mais apropriado para recursos que representam contagens ou taxas de contagem.

A ideia é exatamente a mesma de antes, exceto que em vez de modelar os dados


distribuição com o gaussiano de melhor ajuste, modelamos a distribuição de dados com um melhor ajuste
distribuição multinomial.

Exemplo: Classificação de texto

Um lugar onde o Bayes ingênuo multinomial é frequentemente usado é na classificação de texto, onde
os recursos estão relacionados à contagem de palavras ou frequências nos documentos a serem
classificado. Discutimos a extração de tais recursos do texto em “Feature Engi-
olhando” na página 375; aqui usaremos os recursos de contagem esparsa de palavras do 20
Corpus de grupos de notícias para mostrar como podemos classificar esses documentos curtos em
categorias.

Vamos baixar os dados e dar uma olhada nos nomes dos alvos:

Em [7]: de sklearn.datasets importar fetch_20newsgroups

386 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

dados = fetch_20newsgroups()
dados.target_names

Fora [7]: ['alt.atheism',


'comp.graphics',
'comp.os.ms-windows.misc',
'comp.sys.ibm.pc.hardware',
'comp.sys.mac.hardware ',
'comp.windows.x',
'misc.forsale',
'rec.autos',
'rec.motorcycles',
'rec.sport.baseball',
'rec.sport.hockey',
'sci.crypt',
'sci.electronics',
'sci.med',
'sci.space',
'soc.religion.christian',
'talk.politics.guns',
'talk.politics.mideast',
'talk.politics.misc',
'talk.religion.misc']

Para simplificar, selecionaremos apenas algumas dessas categorias e baixaremos o conjunto de


treinamento e teste:

Em [8]:
categorias = ['talk.religion.misc', 'soc.religion.christian', 'sci.space', 'comp.graphics']

trem = fetch_20newsgroups(subset='treinar', categorias=categorias) teste =


fetch_20newsgroups(subset='teste', categorias=categorias)

Aqui está uma entrada representativa dos dados:

Em[9]: imprimir(train.data[5])

De: dmcgee@uluhe.soest.hawaii.edu (Don McGee)


Assunto: Originador da
Audição Federal:
dmcgee@uluhe Organização: Escola de Ciência e Tecnologia do Oceano e da
Terra Distribuição: EUA
Linhas: 10

Fato ou boato....? Madalyn Murray O'Hare, uma ateia que eliminou o uso da leitura da
Bíblia e da oração nas escolas públicas há 15 anos, agora vai comparecer perante a FCC
com uma petição para parar a leitura do Evangelho nas vias aéreas da América. E ela
também está fazendo campanha para retirar programas, músicas, etc. de Natal das escolas
públicas. Se for verdade, envie um correio para Federal Communications Commission
1919 H Street Washington DC 20054 expressando sua oposição ao pedido dela. Número da
petição de referência
2493.

Em profundidade: Classificação Naive Bayes | 387


Machine Translated by Google

Para usar esses dados para aprendizado de máquina, precisamos ser capazes de converter o
conteúdo de cada string em um vetor de números. Para isso, usaremos o vetorizador TF–IDF
(discutido em “Engenharia de recursos” na página 375) e criaremos um pipeline que o anexa a um
classificador Bayes multinomial ingênuo:

Em [10]: de sklearn.feature_extraction.text importar TfidfVectorizer


de sklearn.naive_bayes importar MultinomialNB de
sklearn.pipeline importar make_pipeline

modelo = make_pipeline(TfidfVectorizer(), MultinomialNB())

Com este pipeline, podemos aplicar o modelo aos dados de treinamento e prever rótulos para os
dados de teste:

Em [11]: model.fit(train.data, train.target) rótulos =


model.predict(test.data)

Agora que previmos os rótulos dos dados de teste, podemos avaliá-los para aprender sobre o
desempenho do estimador. Por exemplo, aqui está a matriz de confusão entre os rótulos verdadeiro
e previsto para os dados de teste (Figura 5-41):

Em [12]:
de sklearn.metrics importar confusão_matrix mat =
confusão_matrix(test.target, rótulos)
sns.heatmap(mat.T, square=True, annot=True, fmt='d', cbar=False,
xticklabels=train.target_names, yticklabels=train.target_names) plt.xlabel('
rótulo verdadeiro') plt.ylabel('
rótulo previsto');

Figura 5-41. Matriz de confusão para o classificador de texto Bayes multinomial ingênuo

388 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Evidentemente, mesmo este classificador muito simples consegue separar com sucesso a conversa
sobre espaço da conversa sobre computador, mas fica confuso entre falar sobre religião e falar sobre
Cristianismo. Esta é talvez uma área de confusão esperada!

O mais legal aqui é que agora temos as ferramentas para determinar a categoria de qualquer string,
usando o método predict() deste pipeline. Aqui está uma função utilitária rápida que retornará a previsão
para uma única string:

Em[13]: def predizer_category(s, trem=trem, modelo=modelo):


pred = model.predict([s])
return train.target_names[pred[0]]

Vamos experimentar:

In[14]: predizer_category('enviando uma carga útil para a ISS')

Fora[14]: 'espaço científico'

In[15]: predizer_category('discutindo islamismo versus ateísmo')

Fora[15]: 'soc.religion.christian'

In[16]: predizer_category('determinando a resolução da tela')

Saída[16]: 'comp.graphics'

Lembre-se de que isso nada mais é sofisticado do que um simples modelo de probabilidade para a
frequência (ponderada) de cada palavra na string; no entanto, o resultado é impressionante. Mesmo um
algoritmo muito ingênuo, quando usado com cuidado e treinado em um grande conjunto de dados de
alta dimensão, pode ser surpreendentemente eficaz.

Quando usar Naive Bayes Como os

classificadores bayesianos ingênuos fazem suposições tão rigorosas sobre os dados, eles geralmente
não terão um desempenho tão bom quanto um modelo mais complicado. Dito isto, eles têm várias
vantagens:

• Eles são extremamente rápidos tanto para treinamento quanto para

previsão • Eles fornecem previsão probabilística direta • Eles

geralmente são facilmente interpretáveis • Eles

têm poucos (ou nenhum) parâmetros ajustáveis

Essas vantagens significam que um classificador Bayesiano ingênuo costuma ser uma boa escolha
como classificação de linha de base inicial. Se funcionar adequadamente, parabéns: você tem um
classificador muito rápido e interpretável para o seu problema. Se não funcionar bem, você poderá
começar a explorar modelos mais sofisticados, com algum conhecimento básico de quão bem eles
devem funcionar.

Os classificadores Naive Bayes tendem a ter um desempenho especialmente bom em um dos seguintes
situações:

Em profundidade: Classificação Naive Bayes | 389


Machine Translated by Google

• Quando as suposições ingênuas realmente correspondem aos dados (muito raro na prática) •

Para categorias muito bem separadas, quando a complexidade do modelo é menos importante •

Para dados de dimensões muito altas, quando a complexidade do modelo é menos importante

Os dois últimos pontos parecem distintos, mas na verdade estão relacionados: à medida que a dimensão de
um conjunto de dados cresce, é muito menos provável que quaisquer dois pontos sejam encontrados
próximos (afinal, eles devem estar próximos em todas as dimensões para serem próximos). geral). Isto
significa que os clusters em dimensões altas tendem a ser mais separados, em média, do que os clusters
em dimensões baixas, assumindo que as novas dimensões realmente acrescentam informação. Por esse
motivo, classificadores simplistas como o ingênuo Bayes tendem a funcionar tão bem ou melhor que
classificadores mais complicados à medida que a dimensionalidade aumenta: uma vez que você tenha
dados suficientes, até mesmo um modelo simples pode ser muito poderoso.

Em profundidade: regressão linear


Assim como o ingênuo Bayes (discutido anteriormente em “Em profundidade: classificação do Naive Bayes”
na página 382) é um bom ponto de partida para tarefas de classificação, os modelos de regressão linear são
um bom ponto de partida para tarefas de regressão. Esses modelos são populares porque podem ser
ajustados muito rapidamente e são muito interpretáveis. Você provavelmente está familiarizado com a forma
mais simples de um modelo de regressão linear (ou seja, ajustar uma linha reta aos dados), mas esses
modelos podem ser estendidos para modelar comportamentos de dados mais complicados.

Nesta seção, começaremos com uma rápida explicação intuitiva da matemática por trás desse problema
bem conhecido, antes de prosseguirmos para ver como os modelos lineares podem ser generalizados para
dar conta de padrões mais complicados nos dados. Começamos com as importações padrão:

In[1]: %matplotlib importação


inline matplotlib.pyplot as plt import
seaborn as sns; sns.set() importa
numpy como np

Regressão Linear Simples

Começaremos com a regressão linear mais familiar, um ajuste linear aos dados. Um ajuste em linha reta é
um modelo da forma y = ax + b onde a é comumente conhecido como inclinação e b é comumente conhecido
como interceptação.

Considere os seguintes dados, que estão espalhados em uma linha com inclinação 2 e interceptação de –5
(Figura 5-42):

Em [2]: rng = np.random.RandomState(1) x =


10 * rng.rand(50) x - 5 +
rng.randn(50) y = 2 *
plt.scatter(x, y);

390 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Figura 5-42. Dados para regressão linear

Podemos usar o estimador LinearRegression do Scikit-Learn para ajustar esses dados e construir a
linha de melhor ajuste (Figura 5-43):

Em [3]: de sklearn.linear_model importar modelo LinearRegression


= LinearRegression(fit_intercept=True)

modelo.fit(x[:, np.newaxis], y)

xfit = np.linspace(0, 10, 1000) yfit =


model.predict(xfit[:, np.newaxis])

plt.dispersão(x, y)
plt.plot(xfit, yfit);

Figura 5-43. Um modelo de regressão linear

Em profundidade: regressão linear | 391


Machine Translated by Google

A inclinação e a interceptação dos dados estão contidas nos parâmetros de ajuste do modelo, que no Scikit-
Learn são sempre marcados por um sublinhado à direita. Aqui os parâmetros relevantes são coef_ e intercept_:

In[4]: print("Inclinação do modelo: ", model.coef_[0])


print("Interceptação do modelo:", model.intercept_)

Inclinação do modelo: 2,02720881036


Interceptação do modelo: -4,99857708555

Vemos que os resultados estão muito próximos dos insumos, como poderíamos esperar.

Entretanto, o estimador LinearRegression é muito mais capaz do que isso – além de ajustes simples em linha
reta, ele também pode lidar com modelos lineares multidimensionais da forma:

y = a0 + a1 x1 + a2 x2 +

onde existem vários valores de x . Geometricamente, isso é semelhante a ajustar um plano a pontos em três
dimensões ou a ajustar um hiperplano a pontos em dimensões superiores.

A natureza multidimensional de tais regressões torna-as mais difíceis de visualizar, mas podemos ver um
desses ajustes em ação construindo alguns dados de exemplo, usando
Operador de multiplicação de matrizes do NumPy:

Em[5]: rng = np.random.RandomState(1)


X = 10 * rng.rand(100, 3) y =
0,5 + np.dot(X, [1,5, -2., 1.])

model.fit(X, y)
imprimir(model.intercept_)
imprimir(model.coef_)
0,5
[1,5 -2. 1.]

Aqui os dados de y são construídos a partir de três valores aleatórios de x , e a regressão linear recupera os
coeficientes usados para construir os dados.

Dessa forma, podemos usar o estimador LinearRegression único para ajustar linhas, planos ou hiperplanos
aos nossos dados. Ainda parece que esta abordagem estaria limitada a relações estritamente lineares entre
variáveis, mas acontece que também podemos relaxar isto.

Regressão de função básica Um

truque que você pode usar para adaptar a regressão linear a relacionamentos não lineares entre variáveis é
transformar os dados de acordo com funções básicas. Já vimos uma versão disso antes, no pipeline
PolynomialRegression usado em “Hyperparâmetros

392 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

e validação de modelo” na página 359 e “Engenharia de recursos” na página 375. A ideia é pegar nosso
modelo linear multidimensional:

y = a0 + a1 x1 + a2 x2 + a3 x3 aÿ +

e construa x1 , x2 , x3 e assim por diante a partir de nossa entrada unidimensional x. Ou seja, deixamos
xn = f n x , onde f é alguma função que transforma nossos dados.
n

Por exemplo, se f n x = x n , nosso modelo se torna uma regressão polinomial:

2 3º +
y = a0 + a1 x + a2 x + a3x _

Observe que este ainda é um modelo linear – a linearidade se refere ao fato de que os coeficientes
nunca se multiplicam ou dividem entre si. O que efetivamente fizemos foi pegar nossos valores
unidimensionais de x e projetá-los em uma dimensão superior, de modo que um ajuste linear possa
ajustar relações mais complicadas entre x e y.

Funções de base polinomial

Esta projeção polinomial é útil o suficiente para ser incorporada ao Scikit-Learn, usando o transformador
PolynomialFeatures :

Em [6]: de sklearn.preprocessing import PolynomialFeatures x =


np.array([2, 3, 4]) poly =
PolynomialFeatures(3, include_bias=False)
poly.fit_transform(x[:, None])

Fora[6]: array([[ 2., 4., 8.], [ 3., 9., 27.],


[ 4., 16., 64.]])

Vemos aqui que o transformador converteu a nossa matriz unidimensional numa matriz tridimensional
tomando o expoente de cada valor. Essa nova representação de dados de dimensão superior pode
então ser inserida em uma regressão linear.

Como vimos em “Engenharia de recursos” na página 375, a maneira mais limpa de fazer isso é usar
um pipeline. Vamos fazer um modelo polinomial de 7º grau desta forma:

In[7]: de sklearn.pipeline importar make_pipeline


poli_model = make_pipeline(PolynomialFeatures(7),
Regressão linear())

Com essa transformação implementada, podemos usar o modelo linear para ajustar relações muito
mais complicadas entre x e y. Por exemplo, aqui está uma onda senoidal com ruído (Figura 5-44):

Em profundidade: regressão linear | 393


Machine Translated by Google

Em [8]: rng = np.random.RandomState(1) x =


10 * rng.rand(50) y =
np.sin(x) + 0,1 * rng.randn(50)

poly_model.fit(x[:, np.newaxis], y) yfit =


poly_model.predict(xfit[:, np.newaxis])

plt.dispersão(x, y)
plt.plot(xfit, yfit);

Figura 5-44. Um ajuste polinomial linear para dados de treinamento não lineares

Nosso modelo linear, através do uso de funções de base polinomial de 7ª ordem, pode fornecer um ajuste
excelente para esses dados não lineares!

Funções de base gaussiana

É claro que outras funções básicas são possíveis. Por exemplo, um padrão útil é ajustar um modelo que não seja
uma soma de bases polinomiais, mas uma soma de bases gaussianas. O resultado pode ser algo como a Figura
5-45.

394 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Figura 5-45. Uma função de base gaussiana ajustada a dados não lineares

As regiões sombreadas no gráfico mostrado na Figura 5-45 são as funções básicas escalonadas e, quando
somadas, reproduzem a curva suave através dos dados. Essas funções de base gaussiana não são
incorporadas ao Scikit-Learn, mas podemos escrever um transformador personalizado que as criará, conforme
mostrado aqui e ilustrado na Figura 5-46 (os transformadores do Scikit-Learn são implementados como classes
Python; lendo o código-fonte do Scikit-Learn é uma boa maneira de ver como eles podem ser criados):

In[9]: de
sklearn.base importar BaseEstimator, TransformerMixin

classe GaussianFeatures (BaseEstimator, TransformerMixin):


"""Recursos gaussianos uniformemente espaçados para entrada unidimensional"""

def __init__(self, N, fator_largura=2,0):


próprio.N = N
self.width_factor = largura_fator

@staticmethod def
_gauss_basis(x, y, largura, eixo=Nenhum): arg = (x - y) /
largura return np.exp(-0,5 *
np.sum(arg ** 2, eixo))

def fit(self, X, y=Nenhum):


# cria N centros espalhados ao longo do intervalo de dados
self.centers_ = np.linspace(X.min(), X.max(), self.N) self.width_ = self.width_factor
* (self.centers_[1] - self .centers_[0]) retorne a si mesmo

def transformação(self, X):

Em profundidade: regressão linear | 395


Machine Translated by Google

retornar self._gauss_basis(X[:, :, np.newaxis], self.centers_, self.width_,


axis=1)

gauss_model = make_pipeline(GaussianFeatures(20),
Regressão linear())
gauss_model.fit(x[:, np.newaxis], y) yfit =
gauss_model.predict(xfit[:, np.newaxis])

plt.dispersão(x, y)
plt.plot(xfit, yfit) plt.xlim(0,
10);

Figura 5-46. Um ajuste de função de base gaussiana calculado com um transformador personalizado

Colocamos este exemplo aqui apenas para deixar claro que não há nada de mágico nas funções de base
polinomial: se você tiver algum tipo de intuição no processo de geração de seus dados que o faça pensar que
uma base ou outra pode ser apropriada, você pode usar eles também.

Regularização A

introdução de funções básicas em nossa regressão linear torna o modelo muito mais flexível, mas também pode
levar rapidamente ao overfitting (consulte “Hiperparâmetros e validação de modelo” na página 359 para uma
discussão sobre isso). Por exemplo, se escolhermos muitas funções de base gaussiana, teremos resultados
que não parecem tão bons (Figura 5-47):

Em[10]: modelo = make_pipeline(GaussianFeatures(30),


LinearRegression())
model.fit(x[:, np.newaxis], y)

plt.scatter(x, y)
plt.plot(xfit, model.predict(xfit[:, np.newaxis]))

396 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

plt.xlim(0, 10)
plt.ylim(-1,5, 1,5);

Figura 5-47. Um modelo de função de base excessivamente complexo que ajusta demais os dados

Com os dados projetados para uma base tridimensional, o modelo tem muita flexibilidade e
vai para valores extremos entre locais onde é limitado pelos dados.
Podemos ver a razão disso se traçarmos os coeficientes das bases gaussianas em relação
às suas localizações (Figura 5-48):

In[11]: def based_plot(model, title=None): fig, ax =


plt.subplots(2, sharex=True) model.fit(x[:,
np.newaxis], y) ax[0].scatter (x, y)
ax[0].plot(xfit,
model.predict(xfit[:, np.newaxis])) ax[0].set(xlabel='x', ylabel='y',
ylim=( -1,5, 1,5))

se título:
machado[0].set_title(título)

ax[1].plot(model.steps[0][1].centers_,
model.steps[1][1].coef_)
ax[1].set(xlabel=' localização base',
ylabel='coeficiente' ,
xlim=(0, 10))

modelo = make_pipeline(GaussianFeatures(30), LinearRegression())


based_plot(modelo)

Em profundidade: regressão linear | 397


Machine Translated by Google

Figura 5-48. Os coeficientes das bases gaussianas no modelo excessivamente complexo

O painel inferior na Figura 5-48 mostra a amplitude da função base em cada local. Este é um
comportamento típico de overfitting quando as funções de base se sobrepõem: os coeficientes das
funções de base adjacentes explodem e se cancelam. Sabemos que tal comportamento é
problemático e seria bom se pudéssemos limitar explicitamente tais picos no modelo, penalizando
grandes valores dos parâmetros do modelo. Essa penalidade é conhecida como regularização e
apresenta diversas formas.

Regressão Ridge ( regularização L2)

Talvez a forma mais comum de regularização seja conhecida como regressão de crista ou
regularização L2 , às vezes também chamada de regularização de Tikhonov. Isto prossegue
penalizando a soma dos quadrados (2-normas) dos coeficientes do modelo; neste caso, a penalidade
no ajuste do modelo seria:

N 2
P = ÿÿn = 1ÿn

onde ÿ é um parâmetro livre que controla a intensidade da penalidade. Este tipo de modelo
penalizado é incorporado ao Scikit-Learn com o estimador Ridge (Figura 5-49):

Em [12]: de sklearn.linear_model import Ridge model


= make_pipeline(GaussianFeatures(30), Ridge(alpha=0.1)) based_plot(model,
title='Ridge Regression')

398 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Figura 5-49. Regularização Ridge (L2 ) aplicada ao modelo excessivamente complexo (compare com a
Figura 5-48)

O parâmetro ÿ é essencialmente um botão que controla a complexidade do modelo resultante. No limite ÿ 0,


recuperamos o resultado da regressão linear padrão; no limite ÿ ÿ, todas as respostas do modelo serão
suprimidas. Uma vantagem da regressão de crista em particular é que ela pode ser calculada de forma muito
eficiente – com um custo computacional pouco maior do que o modelo de regressão linear original.

Regularização de laço (L1 )

Outro tipo de regularização muito comum é conhecido como laço, e envolve penalizar a soma dos valores
absolutos (normas 1) dos coeficientes de regressão:

N
P = ÿÿn = 1 ÿn

Embora isto seja conceitualmente muito semelhante à regressão de crista, os resultados podem diferir
surpreendentemente: por exemplo, devido a razões geométricas, a regressão de laço tende a favorecer
modelos esparsos sempre que possível; isto é, preferencialmente define os coeficientes do modelo exatamente como zero.

Podemos ver esse comportamento duplicando o gráfico mostrado na Figura 5-49, mas usando coeficientes
normalizados L1 (Figura 5-50):

Em [13]: de sklearn.linear_model import Lasso model


= make_pipeline(GaussianFeatures(30), Lasso(alpha=0.001)) based_plot(model,
title='Lasso Regression')

Em profundidade: regressão linear | 399


Machine Translated by Google

Figura 5-50. Regularização Lasso (L1 ) aplicada ao modelo excessivamente complexo (compare com
a Figura 5-48)

Com a penalidade da regressão laço, a maioria dos coeficientes são exatamente zero, com o
comportamento funcional sendo modelado por um pequeno subconjunto das funções básicas
disponíveis. Tal como acontece com a regularização de crista, o parâmetro ÿ ajusta a força da
penalidade e deve ser determinado através, por exemplo, de validação cruzada (consulte
“Hiperparâmetros e validação de modelo” na página 359 para uma discussão sobre isso).

Exemplo: previsão do tráfego de bicicletas

Como exemplo, vamos verificar se podemos prever o número de viagens de bicicleta na ponte Fremont,
em Seattle, com base no clima, na estação do ano e em outros fatores. Já vimos esses dados em
“Trabalhando com séries temporais” na página 188.

Nesta secção, juntaremos os dados sobre bicicletas com outro conjunto de dados e tentaremos
determinar até que ponto os factores meteorológicos e sazonais – temperatura, precipitação e horas de
luz do dia – afectam o volume de tráfego de bicicletas neste corredor. Felizmente, a NOAA disponibiliza
seus dados diários de estação meteorológica (usei o ID da estação USW00024233) e podemos
facilmente usar o Pandas para unir as duas fontes de dados. Realizaremos uma regressão linear
simples para relacionar o clima e outras informações com a contagem de bicicletas, a fim de estimar
como uma mudança em qualquer um desses parâmetros afeta o número de ciclistas em um determinado
dia.

Em particular, este é um exemplo de como as ferramentas do Scikit-Learn podem ser usadas numa
estrutura de modelação estatística, na qual se assume que os parâmetros do modelo têm um significado
interpretável. Conforme discutido anteriormente, esta não é uma abordagem padrão no aprendizado de
máquina, mas tal interpretação é possível para alguns modelos.

400 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Vamos começar carregando os dois conjuntos de dados, indexando por data:

Em [14]:
importar pandas
como pd contagens = pd.read_csv('fremont_hourly.csv', index_col='Date', parse_dates=True)
weather = pd.read_csv('599021.csv', index_col='DATE', parse_dates =Verdadeiro)

A seguir calcularemos o tráfego diário total de bicicletas e colocaremos isso em seu próprio DataFrame:

In[15]: diariamente = counts.resample('d', how='sum')


daily['Total'] = daily.sum(axis=1) daily =
daily[['Total']] # remove outras colunas

Vimos anteriormente que os padrões de uso geralmente variam de dia para dia; vamos explicar isso em nossos dados
adicionando colunas binárias que indicam o dia da semana:

In[16]: dias = ['Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb', 'Dom'] para i no intervalo(7):
diariamente[dias[i] ] =
(daily.index.dayofweek == i).astype(float)

Da mesma forma, podemos esperar que os passageiros se comportem de forma diferente nos feriados; vamos adicionar
um indicador disso também:

Em [17]: de pandas.tseries.holiday import USFederalHolidayCalendar cal =


USFederalHolidayCalendar() feriados
= cal.holidays('2012', '2016') daily =
daily.join(pd.Series(1, index=holidays, name= 'feriado'))
diariamente['feriado'].fillna(0, inplace=True)

Também podemos suspeitar que as horas do dia afetariam o número de pessoas que viajam; vamos usar o cálculo
astronômico padrão para adicionar essas informações (Figura 5-51):

In[18]: def hours_of_daylight(data, eixo=23,44, latitude=47,61):


"""Calcule as horas de luz do dia para a data especificada""" dias
= (data - pd.datetime(2000, 12, 21)).dias m = (1. -
np.tan(np.radians(latitude))
* np.tan(np.radianos(eixo) * np.cos(dias * 2 * np.pi / 365,25)))
retorne 24. * np.degrees(np.arccos(1 - np.clip(m, 0, 2))) / 180.

diariamente['daylight_hrs'] = list(map(hours_of_daylight, daily.index))


daily[['daylight_hrs']].plot();

Em profundidade: regressão linear | 401


Machine Translated by Google

Figura 5-51. Visualização das horas de luz do dia em Seattle

Também podemos adicionar a temperatura média e a precipitação total aos dados. Além disso
Em relação aos centímetros de precipitação, vamos adicionar uma bandeira que indica se um dia está seco
(tem precipitação zero):

In[19]: # temperaturas estão em 1/10 graus C; converter para C


clima['TMIN'] /= 10
clima['TMAX'] /= 10
clima['Temp (C)'] = 0,5 * (clima['TMIN'] + clima['TMAX'])

# o precipitado está em 1/10 mm; converter para polegadas


clima['PRCP'] /= 254
clima[' dia seco'] = (clima['PRCP'] == 0).astype(int)

daily = daily.join(clima[['PRCP', 'Temp (C)', 'dia seco']])

Finalmente, vamos adicionar um contador que aumenta a partir do dia 1 e mede quantos anos
passou. Isto nos permitirá medir qualquer aumento ou diminuição anual observado na
travessias diárias:

In[20]: diário['anual'] = (diário.index - diário.index[0]).dias / 365.

Agora nossos dados estão em ordem e podemos dar uma olhada neles:

Em[21]: daily.head()

Fora[21]:
Total Seg Ter Qua Qui Sex Sáb Dom feriado daylight_hrs \\
Data
03-10-2012 3521 0 0 1 0 0 0 0 0 11.277359
04/10/2012 3475 0 0 0 1 0 0 0 0 11.219142
05-10-2012 3148 0 0 0 0 1 0 0 0 11.161038
06-10-2012 2006 0 0 0 0 0 1 0 0 11.103056

402 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

07/10/2012 2142 0 0 0 0 0 0 1 0 11.045208

PRCP Temp (C) dia seco anual


Data
03/10/2012 0 13h35 1 0,000000
04/10/2012 0 13h60 10,002740
05/10/2012 0 15h30 10,005479
06/10/2012 0 15,85 10,008219
07/10/2012 0 15,85 10,010959

Com isso implementado, podemos escolher as colunas a serem usadas e ajustar uma regressão linear
modelo para nossos dados. Definiremos fit_intercept = False, porque os sinalizadores diários são essencialmente
operam essencialmente como suas próprias interceptações específicas do dia:

Em[22]:
column_names = ['Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb', 'Dom', 'feriado',
'daylight_hrs', 'PRCP', 'dia seco', 'Temp (C)', 'anual']
X = diariamente[nomes_colunas]
y = diariamente['Total']

modelo = LinearRegression(fit_intercept=False)
modelo.fit(X, y)
diariamente['previsto'] = model.predict(X)

Finalmente, podemos comparar visualmente o tráfego total e previsto de bicicletas (Figura 5-52):

In[23]: diariamente[['Total', 'previsto']].plot(alpha=0.5);

Figura 5-52. A previsão do nosso modelo de tráfego de bicicletas

É evidente que perdemos algumas características importantes, especialmente durante o verão


tempo. Ou nossos recursos não estão completos (ou seja, as pessoas decidem se vão de carona para o trabalho
baseado em mais do que apenas estes) ou existem algumas relações não lineares que temos

Em profundidade: regressão linear | 403


Machine Translated by Google

não levou em consideração (por exemplo, talvez as pessoas andem menos tanto com temperamento alto quanto baixo).
atividades). No entanto, a nossa aproximação grosseira é suficiente para nos dar alguns insights,
e podemos dar uma olhada nos coeficientes do modelo linear para estimar quanto
cada recurso contribui para a contagem diária de bicicletas:

In[24]: params = pd.Series(model.coef_, index=X.columns)


parâmetros

Fora[24]: Meu 503.797330


ter 612.088879
qua 591.611292
Coletar 481.250377
sex 176.838999
Sentado -1104.321406
Sol -1134.610322
feriado -1187.212688
dia_horas 128.873251
Dia -665.185105
seco PRCP 546.185613
Temp (C) 65.194390
dtipo 27.865349
anual: float64

Esses números são difíceis de interpretar sem alguma medida de sua incerteza.
Podemos calcular essas incertezas rapidamente usando reamostragens bootstrap dos dados:

In[25]: de sklearn.utils importar reamostra


np.random.seed(1)
err = np.std([model.fit(*resample(X, y)).coef_
para i no intervalo (1000)], 0)

Com esses erros estimados, vejamos novamente os resultados:

Em [26]: print(pd.DataFrame({'efeito': params.round(0),


'erro': err.round(0)}))

erro de efeito
Meu 504 85
ter 612 82
qua 592 82
Coletar 481 85
sex 177 81
Sentado -1104 79
Sol -1135 82
feriado -1187 164
dia_horas 129 9
Dia -665 62
seco PRCP 546 33
Temp (C) 65 4
anual 28 18

Vemos primeiro que há uma tendência relativamente estável na linha de base semanal: há
muito mais passageiros durante a semana do que nos fins de semana e feriados. Vemos isso para cada

404 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

hora adicional de luz do dia, mais 129 ± 9 pessoas optam por pedalar; um aumento de um grau
Celsius na temperatura incentiva 65 ± 4 pessoas a pegarem suas bicicletas; um dia seco significa
uma média de 546 ± 33 ciclistas a mais; e cada centímetro de precipitação significa que mais
665 ± 62 pessoas deixam suas bicicletas em casa. Uma vez contabilizados todos estes efeitos,
vemos um aumento modesto de 28 ± 18 novos ciclistas diários a cada ano.

É quase certo que faltam algumas informações relevantes em nosso modelo. Por exemplo,
os efeitos não lineares (tais como os efeitos da precipitação e da temperatura fria) e as
tendências não lineares dentro de cada variável (tais como a relutância em conduzir a
temperaturas muito frias e muito quentes) não podem ser contabilizados neste modelo.
Além disso, descartamos algumas informações mais refinadas (como a diferença entre uma
manhã chuvosa e uma tarde chuvosa) e ignoramos as correlações entre os dias (como o
possível efeito de uma terça-feira chuvosa nos números de quarta-feira, ou o efeito de um
dia ensolarado inesperado após uma série de dias chuvosos). Todos esses efeitos são
potencialmente interessantes e agora você tem as ferramentas para começar a explorá-los, se desejar!

Em profundidade: máquinas de vetores de suporte

Máquinas de vetores de suporte (SVMs) são uma classe particularmente poderosa e flexível de
algoritmos supervisionados para classificação e regressão. Nesta seção, desenvolveremos a
intuição por trás das máquinas de vetores de suporte e seu uso em problemas de classificação.
Começamos com as importações padrão:

Em [1]: % matplotlib importação


inline numpy as np
importar matplotlib.pyplot como plt
das estatísticas de importação do scipy

# use os padrões de plotagem do


Seaborn, importe o seaborn como sns; sns.set()

Motivando máquinas de vetores de suporte

Como parte de nossa discussão sobre a classificação Bayesiana (consulte “Em profundidade:
Classificação Naive Bayes” na página 382), aprendemos um modelo simples que descreve a
distribuição de cada classe subjacente e usamos esses modelos generativos para deter
probabilisticamente - meus rótulos para novos pontos. Esse foi um exemplo de classificação
generativa; aqui consideraremos, em vez disso, a classificação discriminativa: em vez de modelar
cada classe, simplesmente encontraremos uma linha ou curva (em duas dimensões) ou
variedade (em múltiplas dimensões) que divide as classes umas das outras.

Como exemplo disso, considere o caso simples de uma tarefa de classificação, na qual as duas
classes de pontos estão bem separadas (Figura 5-53):

Em profundidade: Máquinas de vetores de suporte | 405


Machine Translated by Google

Em [2]: de sklearn.datasets.samples_generator import make_blobs X, y =


make_blobs(n_samples=50, centers=2,
random_state=0, cluster_std=0.60)
plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='outono');

Figura 5-53. Dados simples para classificação

Um classificador discriminativo linear tentaria traçar uma linha reta separando os dois conjuntos de dados e,
assim, criar um modelo para classificação. Para dados bidimensionais como os mostrados aqui, esta é uma
tarefa que poderíamos realizar manualmente. Mas imediatamente vemos um problema: há mais de uma
linha divisória possível que pode discriminar perfeitamente entre as duas classes!

Podemos desenhá-los da seguinte forma (Figura 5-54):

Em[3]: xfit = np.linspace(-1, 3.5)


plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='outono') plt. plot([0,6],
[2,1], 'x', cor = 'vermelho', marcador largura da borda = 2, tamanho do marcador = 10)

para m, b em [(1, 0,65), (0,5, 1,6), (-0,2, 2,9)]: plt.plot(xfit, m


* xfit + b, '-k')

plt.xlim(-1, 3,5);

406 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Figura 5-54. Três classificadores discriminativos lineares perfeitos para nossos dados

São três separadores muito diferentes que, no entanto, discriminam perfeitamente entre estas amostras.
Dependendo de qual você escolher, um novo ponto de dados (por exemplo, aquele marcado pelo “X” na
Figura 5-54) receberá um rótulo diferente! Evidentemente, a nossa simples intuição de “traçar uma linha
entre as classes” não é suficiente e precisamos pensar um pouco mais profundamente.

Máquinas de vetores de suporte: maximizando a margem As máquinas

de vetores de suporte oferecem uma maneira de melhorar isso. A intuição é esta: em vez de simplesmente
traçar uma linha de largura zero entre as classes, podemos traçar em torno de cada linha uma margem de
alguma largura, até o ponto mais próximo. Aqui está um exemplo de como isso pode parecer (Figura 5-55):

In[4]:
xfit = np.linspace(-1, 3.5)
plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='outono')

para m, b, d em [(1, 0,65, 0,33), (0,5, 1,6, 0,55), (-0,2, 2,9, 0,2)]:
yfit = m * xfit + b
plt.plot(xfit, yfit, '-k')
plt.fill_between(xfit, yfit - d, yfit + d, edgecolor='none', color='#AAAAAA', alfa=0,4 )

plt.xlim(-1, 3,5);

Em profundidade: Máquinas de vetores de suporte | 407


Machine Translated by Google

Figura 5-55. Visualização de “margens” dentro de classificadores discriminativos

Nas máquinas de vetores de suporte, a linha que maximiza esta margem é aquela que escolheremos
como modelo ótimo. Máquinas de vetores de suporte são um exemplo desse estimador de margem
máxima.

Ajustando uma máquina de

vetores de suporte Vamos ver o resultado de um ajuste real a esses dados: usaremos o classificador
de vetores de suporte do Scikit-Learn para treinar um modelo SVM nesses dados. Por enquanto,
usaremos um kernel linear e definiremos o parâmetro C para um número muito grande (discutiremos
o significado deles com mais profundidade em breve):

In[5]: from sklearn.svm import SVC # "Classificador de vetor de suporte" model


= SVC(kernel='linear', C=1E10) model.fit(X,
y)

Saída[5]: SVC(C=10000000000,0, cache_size=200, class_weight=Nenhum, coef0=0,0,


decisão_function_shape=Nenhum, grau=3, gama='auto', kernel='linear', max_iter=-1,
probabilidade=Falso, estado_aleatório=Nenhum, encolhimento=Verdadeiro,
tol=0,001, detalhado=Falso)

Para visualizar melhor o que está acontecendo aqui, vamos criar uma função rápida e conveniente
que traçará os limites de decisão do SVM para nós (Figura 5-56):

Em [6]: def plot_svc_decision_function(modelo, ax=None, plot_support=True):


"""Planeje a função de decisão para um SVC bidimensional""" se ax for
Nenhum:
machado =
plt.gca() xlim =
ax.get_xlim() ylim = ax.get_ylim()

# cria grade para avaliar o modelo x =


np.linspace(xlim[0], xlim[1], 30)

408 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

y = np.linspace(topo[0], topo[1], 30)


Y, X = np.meshgrid(y, x) xy =
np.vstack([X.ravel(), Y.ravel()]).T P =
model.decision_function(xy).reshape(X.shape)

# plota limites de decisão e margens


ax.contour(X, Y, P, cores='k',
níveis=[-1, 0, 1], alfa=0,5, estilos
de linha=['--', '-', '--'])

# plotar vetores de suporte


se plot_support:
ax.scatter(model.support_vectors_[:, 0],
model.support_vectors_[:, 1],
s=300, linewidth=1, facecolors='none');
ax.set_xlim(xlim)
ax.set_ylim(ylim)

In[7]: plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='outono')


plot_svc_decision_function(model);

Figura 5-56. Um classificador de máquina de vetores de suporte ajustado aos dados, com margens (linhas
tracejadas) e vetores de suporte (círculos) mostrados

Esta é a linha divisória que maximiza a margem entre os dois conjuntos de pontos.
Observe que alguns pontos de treinamento apenas tocam a margem; eles são indicados pelos círculos pretos
na Figura 5-56. Esses pontos são os elementos essenciais desse ajuste e são conhecidos como vetores de
suporte e dão nome ao algoritmo. No Scikit-Learn, a identidade desses pontos é armazenada no atributo
support_vectors_ do classificador:

Em[8]: model.support_vectors_

Fora [8]: matriz ([[ 0,44359863, 3,11530945],


[2,33812285, 3,43116792],
[2,06156753, 1,96918596]])

Em profundidade: Máquinas de vetores de suporte | 409


Machine Translated by Google

A chave para o sucesso deste classificador é que, para o ajuste, apenas a posição dos vetores de suporte
importa; quaisquer pontos mais distantes da margem que estejam do lado correto não modificam o ajuste!
Tecnicamente, isto ocorre porque estes pontos não contribuem para a função de perda utilizada para ajustar
o modelo, pelo que a sua posição e número não importam, desde que não ultrapassem a margem.

Podemos ver isso, por exemplo, se traçarmos o modelo aprendido a partir dos primeiros 60 pontos e dos
primeiros 120 pontos deste conjunto de dados (Figura 5-57):

Em[9]: def plot_svm(N=10, ax=Nenhum):


X, y = make_blobs(n_samples=200, centros=2,
random_state=0, cluster_std=0,60)
X = X[:N]
y = y[:N]
modelo = SVC(kernel='linear', C=1E10)
modelo.fit(X, y)

machado = machado
ou plt.gca() ax.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='outono')
ax.set_xlim(-1, 4)
ax.set_ylim(-1, 6)
plot_svc_decision_function(modelo, ax)

fig, ax = plt.subplots(1, 2, figsize=(16, 6))


fig.subplots_adjust(esquerda=0,0625, direita=0,95, wspace=0,1)
para axi, N em zip(ax, [60, 120] ):
plot_svm(N, axi)
axi.set_title('N = {0}'.format(N))

Figura 5-57. A influência de novos pontos de treinamento no modelo SVM

No painel esquerdo vemos o modelo e os vetores de suporte para 60 pontos de treinamento. No painel direito,
duplicamos o número de pontos de treinamento, mas o modelo não mudou: os três vetores de suporte do
painel esquerdo ainda são os vetores de suporte do painel direito. Esta insensibilidade ao comportamento
exato de pontos distantes é um dos pontos fortes do modelo SVM.

410 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Se você estiver executando este notebook ao vivo, poderá usar os widgets interativos do IPython
para visualizar esse recurso do modelo SVM de forma interativa (Figura 5-58):

In[10]: da interação de importação de ipywidgets , corrigido


interact(plot_svm, N=[10, 200], ax=fixed(None));

Figura 5-58. O primeiro quadro da visualização interativa do SVM (veja o apêndice online para a
versão completa)

Além dos limites lineares: Kernel SVM

Onde o SVM se torna extremamente poderoso é quando é combinado com kernels. Já vimos uma
versão de kernels antes, nas regressões de função de base de “Em profundidade: regressão linear”
na página 390. Lá projetamos nossos dados em um espaço de dimensão superior definido por
polinômios e funções de base gaussianas e, assim, fomos capazes de ajustar para relacionamentos
não lineares com um classificador linear.

Nos modelos SVM, podemos usar uma versão da mesma ideia. Para motivar a necessidade de
kernels, vejamos alguns dados que não são linearmente separáveis (Figura 5-59):

Em [11]: de sklearn.datasets.samples_generator importar make_circles


X, y = make_circles(100, fator=0,1, ruído=0,1)

clf = SVC(kernel='linear').fit(X, y)

plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='outono')


plot_svc_decision_function(clf, plot_support=False);

Em profundidade: Máquinas de vetores de suporte | 411


Machine Translated by Google

Figura 5-59. Um classificador linear tem um desempenho ruim para limites não lineares

É claro que nenhuma discriminação linear será capaz de separar estes dados. Mas podemos tirar uma
lição das regressões da função básica em “Em profundidade: regressão linear” na página 390 e pensar
em como podemos projetar os dados em uma dimensão superior, de modo que um separador linear seja
suficiente. Por exemplo, uma projeção simples que poderíamos usar seria calcular uma função de base
radial centrada no aglomerado do meio:

Em[12]: r = np.exp(-(X ** 2).sum(1))

Podemos visualizar essa dimensão extra de dados usando um gráfico tridimensional – se você estiver
executando este notebook ao vivo, poderá usar os controles deslizantes para girar o gráfico
(Figura 5-60):

Em [13]: de mpl_toolkits importar mplot3d

def plot_3D(elev=30, azim=30, X=X, y=y):


machado = plt.subplot(projeção='3d')
ax.scatter3D(X[:, 0], X[:, 1], r, c=y, s=50, cmap='outono')
ax.view_init(elev=elev, azim=azim)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('r ')

interagir(plot_3D, elev=[-90, 90], azip=(-180, 180),


X=fixo(X), y=fixo(y));

412 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Figura 5-60. Uma terceira dimensão adicionada aos dados permite a separação linear

Podemos ver que com esta dimensão adicional, os dados tornam-se trivialmente separáveis
linearmente, desenhando um plano de separação em, digamos, r=0,7.

Aqui tivemos que escolher e afinar cuidadosamente a nossa projeção; se não tivéssemos centralizado
nossa função de base radial no local correto, não teríamos visto resultados tão claros e linearmente
separáveis. Em geral, a necessidade de fazer tal escolha é um problema: gostaríamos de, de alguma
forma, encontrar automaticamente as melhores funções básicas para usar.

Uma estratégia para esse fim é calcular uma função básica centrada em cada ponto do conjunto de
dados e deixar o algoritmo SVM analisar os resultados. Este tipo de transformação de função base
é conhecida como transformação kernel, pois é baseada em uma relação de similaridade (ou kernel)
entre cada par de pontos.

Um problema potencial com esta estratégia - projetar N pontos em N dimensões - é que ela pode se
tornar muito intensiva em termos computacionais à medida que N cresce. Entretanto, devido a um
pequeno procedimento conhecido como truque do kernel, um ajuste nos dados transformados pelo
kernel pode ser feito implicitamente – isto é, sem nunca construir a representação N- dimensional
completa da projeção do kernel! Esse truque do kernel está integrado ao SVM e é uma das razões
pelas quais o método é tão poderoso.

No Scikit-Learn, podemos aplicar SVM kernelizado simplesmente alterando nosso kernel linear para
um kernel RBF (função de base radial), usando o hiperparâmetro do modelo de kernel (Figura 5-61):

Em[14]: clf = SVC(kernel='rbf', C=1E6)


clf.fit(X, y)

Saída[14]: SVC(C=1000000,0, cache_size=200, class_weight=Nenhum, coef0=0,0,


decisão_function_shape=Nenhum, grau=3, gama='auto', kernel='rbf', max_iter=-1,
probabilidade=Falso, estado_aleatório=Nenhum, encolhimento=Verdadeiro,
tol=0,001, detalhado=Falso)

Em profundidade: Máquinas de vetores de suporte | 413


Machine Translated by Google

Em [15]: plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap='outono')


plot_svc_decision_function(clf)
plt.scatter(clf.support_vectors_[: , 0], clf.support_vectors_[:, 1],
s=300, lw=1, facecolors='nenhum');

Figura 5-61. Kernel SVM ajustado aos dados

Usando esta máquina de vetores de suporte kernelizado, aprendemos um limite de decisão não linear
adequado. Essa estratégia de transformação do kernel é usada frequentemente no aprendizado de máquina
para transformar métodos lineares rápidos em métodos não lineares rápidos, especialmente para modelos
nos quais o truque do kernel pode ser usado.

Ajustando o SVM: suavizando margens

Nossa discussão até agora centrou-se em conjuntos de dados muito claros, nos quais existe um limite de
decisão perfeito. Mas e se seus dados tiverem alguma sobreposição? Por exemplo, você pode ter dados
como estes (Figura 5-62):

In[16]: X, y = make_blobs(n_samples=100, centers=2,


random_state=0, cluster_std=1.2)
plt.scatter(X[:, 0], X[:, 1], c=y, s =50, cmap='outono');

414 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Figura 5-62. Dados com algum nível de sobreposição

Para lidar com esse caso, a implementação do SVM tem um fator de correção que “suaviza” a margem;
isto é, permite que alguns pontos penetrem na margem se isso permitir um melhor ajuste. A dureza da
margem é controlada por um parâmetro de ajuste, mais conhecido como C. Para C muito grande, a
margem é dura e os pontos não podem ficar nela. Para C menor, a margem é mais suave e pode crescer
até abranger alguns pontos.

O gráfico mostrado na Figura 5-63 fornece uma imagem visual de como uma mudança no parâmetro C
afeta o ajuste final, através da suavização da margem:

In[17]: X, y = make_blobs(n_samples=100, centers=2,


random_state=0, cluster_std=0,8)

fig, ax = plt.subplots(1, 2, figsize=(16, 6))


fig.subplots_adjust(esquerda=0,0625, direita=0,95, wspace=0,1)

para axi, C em zip(ax, [10.0, 0.1]): modelo


= SVC(kernel='linear', C=C).fit(X, y) axi.scatter(X[:,
0], X[ :, 1], c=y, s=50, cmap='outono') plot_svc_decision_function(model,
axi) axi.scatter(model.support_vectors_[:, 0],
model.support_vectors_[:, 1], s=300 , lw=1,
facecolors='nenhum'); axi.set_title('C
= {0:.1f}'.format(C), tamanho=14)

Em profundidade: Máquinas de vetores de suporte | 415


Machine Translated by Google

Figura 5-63. O efeito do parâmetro C no ajuste do vetor de suporte

O valor ideal do parâmetro C dependerá do seu conjunto de dados e deve ser ajustado por meio de
validação cruzada ou procedimento semelhante (consulte “Hiperparâmetros e validação de modelo”
na página 359 para obter mais informações).

Exemplo: Reconhecimento facial

Como exemplo de máquinas de vetores de suporte em ação, vamos dar uma olhada no problema
de reconhecimento facial. Usaremos o conjunto de dados Labeled Faces in the Wild, que consiste
em vários milhares de fotos coletadas de várias figuras públicas. Um buscador para o conjunto de
dados está integrado ao Scikit-Learn:

In[18]: de sklearn.datasets importar fetch_lfw_people


faces = fetch_lfw_people(min_faces_per_person=60)
imprimir(faces.target_names)
imprimir(faces.images.shape)

['Ariel Sharon' 'Colin Powell' 'Donald Rumsfeld' 'George W Bush'


'Gerhard Schroeder' 'Hugo Chávez' 'Junichiro Koizumi' 'Tony Blair'] (1348, 62, 47)

Vamos representar graficamente algumas dessas faces para ver com o que estamos trabalhando (Figura 5-64):

In[19]: fig, ax = plt.subplots(3, 5) for i, axi in


enumerate(ax.flat):
axi.imshow(faces.images[i], cmap='bone')
axi.set( xticks=[], yticks=[],
xlabel=faces.target_names[faces.target[i]])

416 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Figura 5-64. Exemplos do conjunto de dados Labeled Faces in the Wild

Cada imagem contém [62×47] ou quase 3.000 pixels. Poderíamos prosseguir simplesmente
usando cada valor de pixel como um recurso, mas muitas vezes é mais eficaz usar algum tipo de
pré-processador para extrair recursos mais significativos; aqui usaremos uma análise de
componentes principais (consulte “Em profundidade: análise de componentes principais” na
página 433) para extrair 150 componentes fundamentais para alimentar nosso classificador de
máquina de vetores de suporte. Podemos fazer isso de maneira mais direta, empacotando o pré-
processador e o classificador em um único pipeline:

Em [20]: de sklearn.svm importar SVC


de sklearn.decomposition importar RandomizedPCA
de sklearn.pipeline importar make_pipeline

pca = RandomizedPCA(n_components=150, whiten=True, random_state=42) svc


= SVC(kernel='rbf', class_weight='balanced') modelo =
make_pipeline(pca, svc)

Para testar a saída do nosso classificador, dividiremos os dados em um conjunto de treinamento


e teste:

In[21]: de sklearn.cross_validation importar train_test_split


Xtrain, Xtest, ytrain, ytest = train_test_split(faces.data, faces.target,
estado_aleatório=42)

Finalmente, podemos usar uma validação cruzada de busca em grade para explorar combinações
de parâmetros. Aqui ajustaremos C (que controla a dureza da margem) e gama (que controla o
tamanho do kernel da função de base radial) e determinaremos o melhor modelo:

In[22]: de sklearn.grid_search importar GridSearchCV


param_grid = {'svc__C': [1, 5, 10, 50],
'svc__gamma': [0,0001, 0,0005, 0,001, 0,005]}
grade = GridSearchCV(modelo, param_grid)

Em profundidade: Máquinas de vetores de suporte | 417


Machine Translated by Google

% tempo grid.fit(Xtrain, ytrain)


print(grid.best_params_)

Tempos de CPU: usuário 47,8 s, sys: 4,08 s, total: 51,8 s


Tempo de parede:
26 s {'svc__gamma': 0,001, 'svc__C': 10}

Os valores ideais ficam no meio da nossa grade; se caíssem nas bordas, gostaríamos de expandir a
grade para ter certeza de que encontramos o verdadeiro ótimo.

Agora, com este modelo de validação cruzada, podemos prever os rótulos dos dados de teste, que o
modelo ainda não viu:

Em[23]: modelo = grid.best_estimator_ yfit =


model.predict(Xtest)

Vamos dar uma olhada em algumas das imagens de teste junto com seus valores previstos
(Figura 5-65):

In[24]: fig, ax = plt.subplots(4, 6) para i, axi


in enumerate(ax.flat):
axi.imshow(Xtest[i].reshape(62, 47), cmap='bone' )
axi.set(xticks=[], yticks=[])
axi.set_ylabel(faces.target_names[yfit[i]].split()[-1], color='preto' if
yfit[i] == ytest [i] else 'red') fig.suptitle(' Nomes previstos;
rótulos incorretos em vermelho', size=14);

Figura 5-65. Rótulos previstos pelo nosso modelo

Desta pequena amostra, nosso estimador ideal rotulou incorretamente apenas uma única face (o rosto
de Bush na linha inferior foi rotulado erroneamente como Blair). Podemos ter uma ideia melhor do
desempenho do nosso estimador usando o relatório de classificação, que lista as estatísticas de
recuperação rótulo por rótulo:

418 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

In[25]: de sklearn.metrics importar rating_report


print(relatório_classificação(yteste, yfit,
target_names=faces.target_names))

precisão relembrar o suporte à pontuação f1

Ariel Sharon 0,65 0,73 0,69 15


Colin Powell 0,81 0,87 0,84 68
Donald Rumsfeld 0,75 0,87 0,81 31
George W. Bush 0,93 0,83 0,88 126
Gerhard Schroeder 0,86 0,78 0,82 23
Hugo Chavez 0,93 0,70 0,80 20
Junichiro Koizumi 0,80 1,00 0,89 12
Tony Blair 0,83 0,93 0,88 42

média / total 0,85 0,85 0,85 337

Poderíamos também exibir a matriz de confusão entre essas classes (Figura 5.66):

In[26]: de sklearn.metrics importar confusão_matrix


mat = matriz_confusão(yteste, yfit)
sns.heatmap(mat.T, square=True, annot=True, fmt='d', cbar=False,
xticklabels=faces.target_names,
yticklabels=faces.target_names)
plt.xlabel(' rótulo verdadeiro')
plt.ylabel(' rótulo previsto');

Figura 5-66. Matriz de confusão para os dados de rostos

Isso nos ajuda a ter uma noção de quais rótulos provavelmente serão confundidos pelo estimador.

Em profundidade: Máquinas de vetores de suporte | 419


Machine Translated by Google

Para uma tarefa de reconhecimento facial do mundo real, na qual as fotos não vêm pré-cortadas em grades bonitas,
a única diferença no esquema de classificação facial é a seleção de recursos: você precisaria usar um algoritmo
mais sofisticado para encontrar os rostos e extrair recursos que são independentes da pixelização. Para este tipo de
aplicação, uma boa opção é fazer uso do OpenCV, que entre outras coisas, inclui implementações pré-treinadas de
ferramentas de extração de características de última geração para imagens em geral e rostos em particular.

Resumo da máquina de vetores de suporte Vimos

aqui uma breve introdução intuitiva aos princípios por trás das máquinas de vetores de suporte. Esses métodos são
um método de classificação poderoso para uma série de
razões:

• A sua dependência de relativamente poucos vectores de apoio significa que são muito
modelos compactos e ocupam muito pouca memória. • Uma vez

treinado o modelo, a fase de predição é muito rápida. • Como são afetados apenas

por pontos próximos à margem, eles funcionam bem com dados de alta dimensão — até mesmo dados com mais
dimensões do que amostras, o que é um regime desafiador para outros algoritmos.

• Sua integração com métodos kernel os torna muito versáteis, capazes de se adaptar a diversos tipos de dados.

No entanto, os SVMs também apresentam várias desvantagens:

• A escala com o número de amostras N é N


3 na pior das hipóteses, ou N2 para

implementações eficientes. Para um grande número de amostras de treinamento, esse custo computacional
pode ser proibitivo.

• Os resultados dependem fortemente de uma escolha adequada para o parâmetro de suavização C. Este deve
ser escolhido cuidadosamente através de validação cruzada, o que pode ser caro à medida que os conjuntos
de dados aumentam de tamanho.

• Os resultados não possuem interpretação probabilística direta. Isto pode ser estimado através de uma validação
cruzada interna (ver o parâmetro de probabilidade do SVC), mas esta estimativa extra é dispendiosa.

Com essas características em mente, geralmente só recorro aos SVMs quando outros métodos mais simples, mais
rápidos e com menos uso intensivo de ajuste se mostram insuficientes para minhas necessidades.
No entanto, se você tiver ciclos de CPU para se comprometer com o treinamento e a validação cruzada de um SVM
em seus dados, o método pode levar a resultados excelentes.

420 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Em profundidade: árvores de decisão e florestas aleatórias


Anteriormente, examinamos em profundidade um classificador generativo simples (Bayes ingênuo;
consulte “Em profundidade: Classificação Naive Bayes” na página 382) e um classificador discriminativo
poderoso (máquinas de vetores de suporte; consulte “Em profundidade: máquinas de vetores de
suporte” na página 405). Aqui daremos uma olhada na motivação de outro algoritmo poderoso – um
algoritmo não paramétrico chamado florestas aleatórias. Florestas aleatórias são um exemplo de
método de conjunto, um método que se baseia na agregação dos resultados de um conjunto de
estimadores mais simples. O resultado um tanto surpreendente com tais métodos de conjunto é que a
soma pode ser maior que as partes; isto é, uma votação majoritária entre vários estimadores pode
acabar sendo melhor do que qualquer um dos estimadores individuais que votam! Veremos exemplos
disso nas seções seguintes. Começamos com as importações padrão:

Em [1]: % matplotlib inline


import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns; sns.set()

Motivando Florestas Aleatórias: Árvores de Decisão As

florestas aleatórias são um exemplo de um conjunto de aprendizes construído sobre árvores de


decisão. Por esse motivo, começaremos discutindo as próprias árvores de decisão.

As árvores de decisão são maneiras extremamente intuitivas de classificar ou rotular objetos: basta
fazer uma série de perguntas destinadas a focar na classificação. Por exemplo, se você quiser construir
uma árvore de decisão para classificar um animal que encontrar durante uma caminhada, poderá
construir aquela mostrada na Figura 5-67.

Figura 5-67. Um exemplo de árvore de decisão binária

Em profundidade: árvores de decisão e florestas aleatórias | 421


Machine Translated by Google

A divisão binária torna isso extremamente eficiente: em uma árvore bem construída, cada questão reduzirá o
número de opções aproximadamente pela metade, estreitando muito rapidamente as opções mesmo entre um
grande número de classes. O truque, claro, está em decidir quais perguntas fazer em cada etapa. Em
implementações de árvores de decisão de aprendizado de máquina, as questões geralmente assumem a forma
de divisões alinhadas aos eixos nos dados; isto é, cada nó na árvore divide os dados em dois grupos usando um
valor de corte dentro de um dos recursos. Vamos agora dar uma olhada em um exemplo.

Criando uma árvore de decisão

Considere os seguintes dados bidimensionais, que possuem um dos quatro rótulos de classe
(Figura 5-68):

In[2]: de sklearn.datasets importar make_blobs

X, y = make_blobs(n_samples=300, centers=4,
random_state=0, cluster_std=1.0)
plt.scatter(X[:, 0], X[:, 1], c=y, s=50, cmap= 'arco-íris');

Figura 5-68. Dados para o classificador de árvore de decisão

Uma árvore de decisão simples construída sobre estes dados irá dividir iterativamente os dados ao longo de um
ou outro eixo de acordo com algum critério quantitativo, e em cada nível atribuirá o rótulo da nova região de
acordo com uma votação majoritária de pontos dentro dela. A Figura 5-69 apresenta uma visualização dos
primeiros quatro níveis de um classificador de árvore de decisão para esses dados.

422 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Figura 5-69. Visualização de como a árvore de decisão divide os dados

Observe que após a primeira divisão, todos os pontos do ramo superior permanecem inalterados, portanto
não há necessidade de subdividir ainda mais este ramo. Exceto para nós que contêm todos de uma cor,
em cada nível cada região é novamente dividida ao longo de um dos dois recursos.

Este processo de ajuste de uma árvore de decisão aos nossos dados pode ser feito no Scikit-Learn com o
Estimador DecisionTreeClassifier :

Em [3]: de sklearn.tree import DecisionTreeClassifier tree =


DecisionTreeClassifier().fit(X, y)

Vamos escrever uma função utilitária rápida para nos ajudar a visualizar a saída do classificador:

Em[4]: def visualize_classifier(modelo, X, y, ax=None, cmap='rainbow'):


machado = machado ou plt.gca()

# Plote os pontos de treinamento


ax.scatter(X[:, 0], X[:, 1], c=y, s=30, cmap=cmap, clim=(y.min(),
y.max()) , zorder=3) ax.axis('apertado')
ax.axis('off') xlim =
ax.get_xlim()
ylim = ax.get_ylim()

# ajusta o estimador
model.fit(X, y) xx,
yy = np.meshgrid(np.linspace(*xlim, num=200),
np.linspace(*ylim, num=200))
Z = model.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape)

# Crie um gráfico de cores com os resultados


n_classes = len(np.unique(y))
contornos = ax.contourf(xx, yy, Z, alpha=0.3,
levels=np.arange(n_classes + 1) - 0.5, cmap=
cmap, clim=(y.min(), y.max()), zordem=1)

ax.set(xlim=xlim, ylim=ylim)

Agora podemos examinar a aparência da classificação da árvore de decisão (Figura 5-70):

Em[5]: visualize_classifier(DecisionTreeClassifier(), X, y)

Em profundidade: árvores de decisão e florestas aleatórias | 423


Machine Translated by Google

Figura 5-70. Visualização de uma classificação de árvore de decisão

Se você estiver executando este notebook ao vivo, poderá usar o script auxiliar incluído no apêndice on-
line para exibir uma visualização interativa do processo de construção da árvore de decisão (Figura 5-71):

In[6]: # helpers_05_08 é encontrado no apêndice online


# (https:// github.com/ jakevdp/ PythonDataScienceHandbook)
importar helpers_05_08
helpers_05_08.plot_tree_interactive(X, y);

Figura 5-71. Primeiro quadro do widget interativo da árvore de decisão; para a versão completa,
consulte o apêndice online

Observe que à medida que a profundidade aumenta, tendemos a obter regiões de classificação com
formatos muito estranhos; por exemplo, a uma profundidade de cinco, há uma região roxa alta e magra

424 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

entre as regiões amarela e azul. Está claro que isso é menos resultado da distribuição intrínseca e
verdadeira dos dados e mais resultado da amostragem específica ou das propriedades de ruído dos
dados. Ou seja, esta árvore de decisão, mesmo com apenas cinco níveis de profundidade, está
claramente a sobreajustar os nossos dados.

Árvores de decisão e

overfitting Esse overfitting acaba sendo uma propriedade geral das árvores de decisão; é muito fácil ir
muito fundo na árvore e, assim, ajustar os detalhes dos dados específicos, em vez das propriedades
gerais das distribuições das quais eles foram extraídos. Outra maneira de ver esse overfitting é observar
modelos treinados em diferentes subconjuntos de dados — por exemplo, na Figura 5.72 treinamos duas
árvores diferentes, cada uma com metade dos dados originais.

Figura 5-72. Um exemplo de duas árvores de decisão aleatórias

É claro que em alguns lugares, as duas árvores produzem resultados consistentes (por exemplo, nos
quatro cantos), enquanto em outros lugares, as duas árvores dão classificações muito diferentes (por
exemplo, nas regiões entre quaisquer dois grupos). A observação principal é que as inconsistências
tendem a acontecer onde a classificação é menos certa e, portanto, usando informações de ambas as
árvores, poderemos chegar a um resultado melhor!

Se você estiver executando este notebook ao vivo, a função a seguir permitirá exibir interativamente os
ajustes das árvores treinadas em um subconjunto aleatório de dados (Figura 5-73):

In[7]: # helpers_05_08 é encontrado no apêndice online


# (https:// github.com/ jakevdp/ PythonDataScienceHandbook)
importar helpers_05_08
helpers_05_08.randomized_tree_interactive(X, y)

Em profundidade: árvores de decisão e florestas aleatórias | 425


Machine Translated by Google

Figura 5-73. Primeiro quadro do widget interativo da árvore de decisão aleatória; para a versão completa,
consulte o apêndice online

Assim como a utilização de informações de duas árvores melhora os nossos resultados, poderíamos esperar
que a utilização de informações de muitas árvores melhoraria ainda mais os nossos resultados.

Conjuntos de estimadores: florestas aleatórias


Essa noção – de que vários estimadores de overfitting podem ser combinados para reduzir o efeito desse
overfitting – é o que fundamenta um método de conjunto chamado bagging. Bagging faz uso de um conjunto
(talvez um saco de surpresas) de estimadores paralelos, cada um dos quais superajusta os dados, e calcula
a média dos resultados para encontrar uma classificação melhor. Um conjunto de árvores de decisão
aleatórias é conhecido como floresta aleatória.

Podemos fazer esse tipo de classificação de ensacamento manualmente usando o Bagging do Scikit-Learn
Metaestimador do classificador conforme mostrado aqui (Figura 5-74):

Em [8]: de sklearn.tree importar DecisionTreeClassifier de


sklearn.ensemble importar BaggingClassifier

árvore = DecisionTreeClassifier() bag


= BaggingClassifier (árvore, n_estimadores = 100, max_samples = 0,8,
random_state = 1)

bolsa.fit(X, y)
visualize_classifier(bolsa, X, y)

426 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Figura 5-74. Limites de decisão para um conjunto de árvores de decisão aleatórias

Neste exemplo, randomizamos os dados ajustando cada estimador com um subconjunto aleatório
de 80% dos pontos de treinamento. Na prática, as árvores de decisão são randomizadas de forma
mais eficaz quando alguma estocasticidade é injetada na forma como as divisões são escolhidas;
dessa forma, todos os dados contribuem para o ajuste a cada vez, mas os resultados do ajuste
ainda possuem a aleatoriedade desejada. Por exemplo, ao determinar em qual recurso dividir, a
árvore aleatória pode selecionar entre os vários recursos principais. Você pode ler mais detalhes
técnicos sobre essas estratégias de randomização na documentação do Scikit-Learn e nas
referências contidas nela.

No Scikit-Learn, esse conjunto otimizado de árvores de decisão aleatórias é implementado no


estimador RandomForestClassifier , que cuida de toda a randomização automaticamente. Tudo o
que você precisa fazer é selecionar um número de estimadores, e ele ajustará muito rapidamente
(em paralelo, se desejado) o conjunto de árvores (Figura 5-75):

In[9]: de sklearn.ensemble importar RandomForestClassifier

modelo = RandomForestClassifier(n_estimators=100, random_state=0)


visualize_classifier(model, X, y);

Em profundidade: árvores de decisão e florestas aleatórias | 427


Machine Translated by Google

Figura 5-75. Limites de decisão para uma floresta aleatória, que é um conjunto otimizado de árvores
de decisão

Vemos que calculando a média de mais de 100 modelos perturbados aleatoriamente, terminamos com
um modelo geral que está muito mais próximo da nossa intuição sobre como o espaço de parâmetros
deve ser dividido.

Regressão Florestal Aleatória Na

seção anterior consideramos florestas aleatórias dentro do contexto de classificação. Florestas


aleatórias também podem funcionar no caso de regressão (isto é, variáveis contínuas em vez de
variáveis categóricas). O estimador a ser usado para isso é o RandomForestRegressor, e a sintaxe é
muito semelhante à que vimos anteriormente.

Considere os seguintes dados, extraídos da combinação de uma oscilação rápida e lenta (Figura 5-76):

Em[10]: rng = np.random.RandomState(42) x =


10 * rng.rand(200)

modelo def (x, sigma = 0,3):


oscilação rápida = np.sin (5 * x)
oscilação lenta = np.sin (0,5 * x) ruído =
sigma * rng.randn (len (x))

retornar oscilação lenta + oscilação rápida + ruído

y = modelo(x)
plt.errorbar(x, y, 0,3, fmt='o');

428 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Figura 5-76. Dados para regressão florestal aleatória

Usando o regressor de floresta aleatório, podemos encontrar a curva de melhor ajuste da seguinte forma
(Figura 5-77):

Em [11]: de sklearn.ensemble import RandomForestRegressor


floresta = RandomForestRegressor(200)
floresta.fit(x[:, Nenhum], y)

xfit = np.linspace(0, 10, 1000) yfit =


floresta.predict(xfit[:, None]) ytrue =
modelo(xfit, sigma=0)

plt.errorbar(x, y, 0,3, fmt='o', alfa=0,5) plt.plot(xfit,


yfit, '-r'); plt.plot(xfit, ytrue, '-k',
alfa=0,5);

Aqui o modelo verdadeiro é mostrado pela curva suave, enquanto o modelo de floresta aleatório é
mostrado pela curva irregular. Como você pode ver, o modelo florestal aleatório não paramétrico é
flexível o suficiente para ajustar os dados multiperíodos, sem a necessidade de especificar um modelo
multiperíodo!

Em profundidade: árvores de decisão e florestas aleatórias | 429


Machine Translated by Google

Figura 5-77. Modelo de floresta aleatório ajustado aos dados

Exemplo: Floresta Aleatória para Classificação de Dígitos

Anteriormente demos uma rápida olhada nos dados de dígitos manuscritos (veja “Apresentando o Scikit-
Learn” na página 343). Vamos usar isso novamente aqui para ver como o classificador de floresta aleatório
pode ser usado neste contexto.

In[12]: de sklearn.datasets import load_digits digits =


load_digits() digits.keys()

Out[12]: dict_keys(['target', 'data', 'target_names', 'DESCR', 'images'])

Para nos lembrar o que estamos vendo, visualizaremos os primeiros pontos de dados
(Figura 5-78):

In[13]:
# configura a figura fig
= plt.figure(figsize=(6, 6)) # tamanho da figura em polegadas
fig.subplots_adjust(left=0, right=1, bottom=0, top=1, hspace =0,05, wespaço=0,05)

# plote os dígitos: cada imagem tem 8x8 pixels para


i no intervalo (64): ax
= fig.add_subplot(8, 8, i + 1, xticks=[], yticks=[])
ax.imshow(digits.images[ i], cmap=plt.cm.binary, interpolação='mais próximo')

# rotula a imagem com o valor alvo ax.text(0, 7,


str(digits.target[i]))

430 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Figura 5-78. Representação dos dados dos dígitos

Podemos classificar rapidamente os dígitos usando uma floresta aleatória como segue (Figura 5-79):

Em[14]:
de sklearn.cross_validation importar train_test_split

Xtrain, Xtest, ytrain, ytest = train_test_split(dígitos.dados, dígitos.target,


estado_aleatório=0)
modelo = RandomForestClassifier (n_estimadores = 1000)
model.fit(Xtrain, ytrain)
ypred = model.predict(Xtest)

Podemos dar uma olhada no relatório de classificação deste classificador:

In[15]: das métricas de importação do sklearn


imprimir(metrics.classification_report(ypred, ytest))

precisão 1,00 relembrar o suporte à pontuação f1


0 0,97 0,99 38
1 1,00 0,98 0,99 44
2 0,95 1,00 0,98 42
0,98 0,96 0,97 46
34 0,97 1,00 0,99 37
5 0,98 0,96 0,97 49
6 1,00 1,00 1,00 52
7 1,00 0,96 0,98 50
8 0,94 0,98 0,96 46
9 0,96 0,98 0,97 46

média / total 0,98 0,98 0,98 450

Em profundidade: árvores de decisão e florestas aleatórias | 431


Machine Translated by Google

E para garantir, trace a matriz de confusão (Figura 5-79):

Em [16]: de sklearn.metrics importar confusão_matrix mat =


confusão_matrix(ytest, ypred)
sns.heatmap(mat.T, square=True, annot=True, fmt='d', cbar=False) plt.xlabel('
rótulo verdadeiro ') plt.ylabel('
rótulo previsto');

Figura 5-79. Matriz de confusão para classificação de dígitos com florestas aleatórias

Descobrimos que uma floresta aleatória simples e não ajustada resulta em uma classificação muito precisa
dos dados dos dígitos.

Resumo de florestas aleatórias Esta seção

contém uma breve introdução ao conceito de estimadores de conjunto e, em particular, ao modelo de floresta
aleatória – um conjunto de árvores de decisão aleatórias.
As florestas aleatórias são um método poderoso com diversas vantagens:

• Tanto o treinamento quanto a previsão são muito rápidos, devido à simplicidade das árvores de decisão
subjacentes. Além disso, ambas as tarefas podem ser paralelizadas diretamente, porque as árvores
individuais são entidades totalmente independentes. • As múltiplas árvores

permitem uma classificação probabilística: uma votação majoritária entre os estimadores fornece uma
estimativa da probabilidade (acessada no Scikit-Learn com o método predizer_proba() ).

• O modelo não paramétrico é extremamente flexível e, portanto, pode funcionar bem em


tarefas que são inadequadas por outros estimadores.

432 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

A principal desvantagem das florestas aleatórias é que os resultados não são facilmente interpretáveis;
isto é, se você quiser tirar conclusões sobre o significado do modelo de classificação, florestas
aleatórias podem não ser a melhor escolha.

Em profundidade: análise de componentes principais

Até agora, analisamos detalhadamente os estimadores de aprendizagem supervisionada: aqueles


estimadores que prevêem rótulos com base em dados de treinamento rotulados. Aqui começamos a
examinar vários estimadores não supervisionados, que podem destacar aspectos interessantes dos
dados sem referência a quaisquer rótulos conhecidos.

Nesta seção, exploramos aquele que é talvez um dos algoritmos não supervisionados mais amplamente
usados, a análise de componentes principais (PCA). PCA é fundamentalmente um algoritmo de
redução de dimensionalidade, mas também pode ser útil como uma ferramenta para visualização,
filtragem de ruído, extração de características e engenharia e muito mais. Após uma breve discussão
conceitual do algoritmo PCA, veremos alguns exemplos dessas aplicações adicionais. Começamos
com as importações padrão:

Em [1]: % matplotlib inline


import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns; sns.set()

Apresentando a análise de componentes principais A

análise de componentes principais é um método não supervisionado rápido e flexível para redução de
dimensionalidade em dados, que vimos brevemente em “Introduzindo o Scikit-Learn” na página 343.
Seu comportamento é mais fácil de visualizar observando um gráfico bidimensional. conjunto de dados.
Considere os seguintes 200 pontos (Figura 5-80):

Em[2]: rng = np.random.RandomState(1)


X = np.dot(rng.rand(2, 2), rng.randn(2, 200)).T
plt.scatter(X[:, 0], X[:, 1])
plt.axis('igual ');

A olho nu, fica claro que existe uma relação quase linear entre as variáveis x e y. Isso lembra os dados
de regressão linear que exploramos em “Em profundidade: regressão linear” na página 390, mas a
configuração do problema aqui é um pouco diferente: em vez de tentar prever os valores de y a partir
dos valores de x, o problema de aprendizagem não supervisionado tenta aprender sobre a relação
entre os valores x e y.

Em profundidade: análise de componentes principais | 433


Machine Translated by Google

Figura 5-80. Dados para demonstração do PCA

Na análise de componentes principais, quantifica-se essa relação encontrando uma lista dos eixos
principais nos dados e usando esses eixos para descrever o conjunto de dados. Usando o
estimador PCA do Scikit-Learn , podemos calcular isso da seguinte forma:

Em [3]: de sklearn.decomposition importar PCA


pca = PCA(n_components=2)
pca.fit(X)

Saída[3]: PCA(cópia=True, n_components=2, whiten=False)

O ajuste aprende algumas quantidades dos dados, principalmente os “componentes” e a “variância


explicada”:

Em[4]: imprimir(pca.components_)

[[ 0,94446029 0,32862557]
[ 0,32862557 -0,94446029]]

Em[5]: imprimir(pca.explained_variance_)

[0,75871884 0,01838551]

Para ver o que esses números significam, vamos visualizá-los como vetores sobre os dados de
entrada, usando os “componentes” para definir a direção do vetor e a “variância explicada” para
definir o comprimento quadrado do vetor (Figura 5 -81):

Em [6]: def draw_vector(v0, v1, ax=None): ax =


ax ou plt.gca()
arrowprops=dict(arrowstyle='->',
linewidth=2,
ShrinkA=0, ShrinkB=0)
ax.annotate('', v1, v0, arrowprops=arrowprops)

# dados do gráfico

434 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

plt.scatter(X[:, 0], X[:, 1], alpha=0.2) para


comprimento, vetor em zip(pca.explained_variance_, pca.components_):
v = vetor * 3 * np.sqrt(comprimento)
draw_vector(pca.mean_, pca.mean_ + v)
plt.axis('equal');

Figura 5-81. Visualização dos eixos principais nos dados

Esses vetores representam os eixos principais dos dados, e o comprimento mostrado na


Figura 5-81 é uma indicação de quão “importante” esse eixo é na descrição da distribuição
dos dados – mais precisamente, é uma medida da variância dos dados quando projetados
nesse eixo. A projeção de cada ponto de dados nos eixos principais são os “componentes
principais” dos dados.

Se representarmos graficamente esses componentes principais ao lado dos dados originais, veremos os gráficos
mostrados na Figura 5-82.

Figura 5-82. Eixos principais transformados nos dados

Em profundidade: análise de componentes principais | 435


Machine Translated by Google

Esta transformação dos eixos de dados para os eixos principais é como uma transformação ana, o que
basicamente significa que é composta por translação, rotação e escalonamento uniforme.

Embora este algoritmo para encontrar componentes principais possa parecer apenas uma curiosidade
matemática, ele acaba tendo aplicações de longo alcance no mundo do aprendizado de máquina e da exploração
de dados.

PCA como redução de

dimensionalidade Usar PCA para redução de dimensionalidade envolve zerar um ou mais dos menores
componentes principais, resultando em uma projeção de dimensão inferior dos dados que preserva a variância
máxima dos dados.

Aqui está um exemplo de uso de PCA como uma transformação de redução de dimensionalidade:

Em[7]: pca = PCA(n_components=1)


pca.fit(X)
X_pca = pca.transform(X)
print("forma original: ", X.shape) print("forma
transformada:", X_pca.shape)

forma original: (200, 2) forma


transformada: (200, 1)

Os dados transformados foram reduzidos a uma única dimensão. Para entender o efeito dessa redução de
dimensionalidade, podemos realizar a transformação inversa desses dados reduzidos e plotá-los junto com os
dados originais (Figura 5-83):

Em[8]: X_new = pca.inverse_transform(X_pca)


plt.scatter(X[:, 0], X[:, 1], alfa=0,2)
plt.scatter(X_new[:, 0], X_new[:, 1], alfa=0,8) plt.axis('igual
');

Figura 5-83. Visualização do PCA como redução de dimensionalidade

436 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Os pontos claros são os dados originais, enquanto os pontos escuros são a versão projetada.
Isso deixa claro o que significa uma redução de dimensionalidade do PCA: as informações ao longo do eixo principal
ou eixos menos importantes são removidas, deixando apenas o(s) componente(s) dos dados com a maior variância.
A fração de variância que é cortada (proporcional à dispersão de pontos em torno da linha formada na Figura 5-83) é
aproximadamente uma medida de quanta “informação” é descartada nesta redução de dimensionalidade.

Este conjunto de dados de dimensão reduzida é, em alguns sentidos, “bom o suficiente” para codificar as relações
mais importantes entre os pontos: apesar de reduzir a dimensão dos dados em 50%, a relação geral entre os pontos
de dados é maioritariamente preservada.

PCA para visualização: dígitos manuscritos A

utilidade da redução da dimensionalidade pode não ser totalmente aparente em apenas duas dimensões, mas torna-
se muito mais clara quando olhamos para dados de alta dimensão. Para ver isso, vamos dar uma olhada rápida na
aplicação do PCA aos dados de dígitos que vimos em “Detalhes: Árvores de Decisão e Florestas Aleatórias” na
página 421.

Começamos carregando os dados:

In[9]: de sklearn.datasets import load_digits digits =


load_digits() digits.data.shape

Fora[9]:
(1797, 64)

Lembre-se de que os dados consistem em imagens de 8×8 pixels, o que significa que são de 64 dimensões. Para
obter alguma intuição sobre as relações entre esses pontos, podemos usar o PCA para projetá-los para um número
mais gerenciável de dimensões, digamos duas:

In[10]: pca = PCA(2) # projeto de 64 para 2 dimensões


projetado = pca.fit_transform(digits.data)
print(digits.data.shape)
print(projected.shape)

(1797, 64)
(1797, 2)

Podemos agora representar graficamente os dois primeiros componentes principais de cada ponto para aprender
sobre os dados (Figura 5-84):

Em [11]: plt.scatter(projetado[:, 0], projetado[:, 1], c=digits.target,


edgecolor='none', alfa=0,5,
cmap=plt.cm.get_cmap('spectral' , 10))
plt.xlabel('componente 1')
plt.ylabel('componente 2')
plt.colorbar();

Em profundidade: análise de componentes principais | 437


Machine Translated by Google

Figura 5-84. PCA aplicado aos dados de dígitos manuscritos

Lembre-se do que esses componentes significam: os dados completos são uma nuvem de pontos de
64 dimensões e esses pontos são a projeção de cada ponto de dados ao longo das direções com a
maior variação. Essencialmente, encontramos o alongamento e a rotação ideais no espaço de 64
dimensões que nos permitem ver o layout dos dígitos em duas dimensões, e fizemos isso de maneira
não supervisionada – ou seja, sem referência aos rótulos.

O que significam os componentes?

Podemos ir um pouco mais longe aqui e começar a perguntar o que significam as dimensões reduzidas.
Este significado pode ser entendido em termos de combinações de vetores de base. Por exemplo, cada
imagem no conjunto de treinamento é definida por uma coleção de valores de 64 pixels, que
chamaremos de vetor x:

x = x1 , x2 , x3 e x64

Uma maneira de pensarmos sobre isso é em termos de pixels. Ou seja, para construir a imagem,
multiplicamos cada elemento do vetor pelo pixel que ele descreve e depois somamos os resultados
para construir a imagem:

imagem x = x1 · pixel 1 + x2 · pixel 2 + x3 · pixel 3 ÿÿx64 · pixel 64

Uma maneira de imaginarmos a redução da dimensão desses dados é zerar todos, exceto alguns
desses vetores de base. Por exemplo, se usarmos apenas os primeiros oito pixels, obteremos uma
projeção de oito dimensões dos dados (Figura 5-85), mas não reflete muito a imagem inteira:
descartamos quase 90% do píxeis!

438 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Figura 5-85. Uma redução ingênua da dimensionalidade alcançada pelo descarte de pixels

A linha superior de painéis mostra os pixels individuais e a linha inferior mostra a contribuição cumulativa
desses pixels para a construção da imagem. Usando apenas oito componentes baseados em pixels, só
podemos construir uma pequena porção da imagem de 64 pixels. Se continuássemos esta sequência e
utilizássemos todos os 64 pixels, recuperaríamos a imagem original.

Mas a representação pixelada não é a única escolha de base. Também podemos usar outras funções
básicas, cada uma contendo alguma contribuição predefinida de cada pixel, e escrever algo como:

imagem x = média + x1 · base 1 + x2 · base 2 + x3 3 base

O PCA pode ser pensado como um processo de escolha de funções de base ideais, de modo que somar
apenas as primeiras delas seja suficiente para reconstruir adequadamente a maior parte dos elementos
no conjunto de dados. Os componentes principais, que atuam como representação de baixa dimensão
dos nossos dados, são simplesmente os coeficientes que multiplicam cada um dos elementos desta série.
A Figura 5-86 é uma representação semelhante da reconstrução desse dígito usando a média mais as
primeiras oito funções básicas do PCA.

Figura 5-86. Uma redução de dimensionalidade mais sofisticada obtida pelo descarte dos componentes
principais menos importantes (compare com a Figura 5-85)

Ao contrário da base de pixels, a base PCA nos permite recuperar as características salientes da imagem
de entrada com apenas uma média mais oito componentes! A quantidade de cada pixel em cada
componente é o corolário da orientação do vetor no nosso exemplo bidimensional. Este é o sentido em
que o PCA fornece uma representação de baixa dimensão dos dados: ele descobre um conjunto de
funções básicas que são mais eficientes do que a base de pixels nativa dos dados de entrada.

Em profundidade: análise de componentes principais | 439


Machine Translated by Google

Escolha do número de

componentes Uma parte vital do uso do PCA na prática é a capacidade de estimar quantos
componentes são necessários para descrever os dados. Podemos determinar isso observando
a razão de variância explicada cumulativa como uma função do número de componentes (Figura 5-87):

Em [12]: pca = PCA().fit(digits.data)


plt.plot(np.cumsum(pca.explained_variance_ratio_))
plt.xlabel('número de componentes')
plt.ylabel('variância explicada cumulativa') ;

Figura 5-87. A variância explicada cumulativa, que mede quão bem o PCA preserva o
conteúdo dos dados

Esta curva quantifica quanto da variância total de 64 dimensões está contida nos primeiros N
componentes. Por exemplo, vemos que com os dígitos os primeiros 10 componentes contêm
aproximadamente 75% da variância, enquanto são necessários cerca de 50 componentes
para descrever perto de 100% da variância.

Aqui vemos que a nossa projeção bidimensional perde muita informação (conforme medido
pela variância explicada) e que precisaríamos de cerca de 20 componentes para reter 90%
da variância. Observar este gráfico para um conjunto de dados de alta dimensão pode ajudá-
lo a compreender o nível de redundância presente em múltiplas observações.

PCA como filtragem de

ruído PCA também pode ser usado como uma abordagem de filtragem para dados ruidosos.
A ideia é esta: quaisquer componentes com variância muito maior que o efeito do ruído devem
ser relativamente não afetados pelo ruído. Portanto, se você reconstruir os dados usando
apenas o maior subconjunto de componentes principais, deverá preferencialmente manter o
sinal e eliminar o ruído.

440 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Vamos ver como isso fica com os dados dos dígitos. Primeiro, plotaremos vários dados de entrada
sem ruído (Figura 5-88):

Em[13]: def plot_digits(dados):


fig, eixos = plt.subplots(4, 10, figsize=(10, 4),
subplot_kw={'xticks':[], 'yticks':[]},
gridspec_kw=dict(hspace=0.1, wspace=0.1)) para
i, machado em enumerar(axes.flat):
ax.imshow(dados[i].reshape(8, 8),
cmap='binário', interpolação='mais próximo',
clim=(0, 16))
plot_digits(digits.data)

Figura 5-88. Dígitos sem ruído

Agora vamos adicionar algum ruído aleatório para criar um conjunto de dados barulhento e replotá-lo
(Figura 5-89):

Em[14]: np.random.seed(42)
barulhento = np.random.normal(dígitos.dados, 4)
plot_digits(barulhento)

Figura 5-89. Dígitos com ruído aleatório gaussiano adicionado

Fica claro a olho nu que as imagens são barulhentas e contêm pixels espúrios. Vamos treinar um
PCA nos dados ruidosos, solicitando que a projeção preserve 50% da variância:

Em profundidade: análise de componentes principais | 441


Machine Translated by Google

In[15]: pca = PCA(0,50).fit(ruído)


pca.n_components_

Fora[15]: 12

Aqui, 50% da variação equivale a 12 componentes principais. Agora calculamos esses componentes e depois
usamos o inverso da transformada para reconstruir os dígitos filtrados (Figura 5-90):

In[16]: componentes = pca.transform(ruído) filtrado


= pca.inverse_transform(componentes)
plot_digits(filtrado)

Figura 5-90. Dígitos “denoizados” usando PCA

Essa propriedade de preservação de sinal/filtragem de ruído torna o PCA uma rotina de seleção de recursos
muito útil – por exemplo, em vez de treinar um classificador em dados de dimensões muito altas, você pode
treinar o classificador na representação de dimensão inferior, que servirá automaticamente para filtrar ruídos
aleatórios nas entradas.

Exemplo: Eigenfaces

Anteriormente, exploramos um exemplo de uso de uma projeção PCA como um seletor de recursos para
reconhecimento facial com uma máquina de vetores de suporte (“Em profundidade: Máquinas de vetores de
suporte” na página 405). Aqui vamos dar uma olhada para trás e explorar um pouco mais do que aconteceu
nisso. Lembre-se de que estávamos usando o conjunto de dados Labeled Faces in the Wild disponibilizado
através do Scikit-Learn:

Em [17]: de sklearn.datasets importar fetch_lfw_people


faces = fetch_lfw_people(min_faces_per_person=60)
imprimir(faces.target_names)
imprimir(faces.images.shape)

['Ariel Sharon' 'Colin Powell' 'Donald Rumsfeld' 'George W Bush'


'Gerhard Schroeder' 'Hugo Chávez' 'Junichiro Koizumi' 'Tony Blair']
(1348, 62, 47)

Vamos dar uma olhada nos eixos principais que abrangem este conjunto de dados. Como este é um grande
conjunto de dados, usaremos RandomizedPCA – ele contém um método randomizado para aproximadamente

442 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

calcular os primeiros N componentes principais muito mais rapidamente do que o estimador PCA padrão
e, portanto, é muito útil para dados de alta dimensão (aqui, uma dimensionalidade de quase 3.000).
Daremos uma olhada nos primeiros 150 componentes:

Em [18]: de sklearn.decomposition import RandomizedPCA pca


= RandomizedPCA(150)
pca.fit(faces.data)

Out[18]: RandomizedPCA(copy=True, iterated_power=3, n_components=150,


random_state=None, whiten=False)

Neste caso, pode ser interessante visualizar as imagens associadas aos primeiros componentes principais
(esses componentes são tecnicamente conhecidos como “vetores próprios”, portanto esses tipos de
imagens são frequentemente chamados de “faces próprias”). Como você pode ver na Figura 5-91, eles
são tão assustadores quanto parecem:

Em[19]: fig, eixos = plt.subplots(3, 8, figsize=(9, 4),


subplot_kw={'xticks':[], 'yticks':[]},
gridspec_kw=dict(hspace=0.1, wspace=0.1)) para
i, machado em enumerar(axes.flat):
ax.imshow(pca.components_ [i].reshape(62, 47), cmap='osso')

Figura 5-91. Uma visualização de autofaces aprendidas com o conjunto de dados LFW

Os resultados são muito interessantes e nos dão uma ideia de como as imagens variam: por exemplo, as
primeiras faces próprias (no canto superior esquerdo) parecem estar associadas ao ângulo de iluminação
na face, e os vetores principais posteriores parecem estar escolhendo destacar certas características,
como olhos, nariz e lábios. Vamos dar uma olhada na variância cumulativa desses componentes para ver
quanto das informações dos dados a projeção está preservando (Figura 5-92):

Em [20]: plt.plot(np.cumsum(pca.explained_variance_ratio_))
plt.xlabel('número de componentes')
plt.ylabel('variância explicada cumulativa');

Em profundidade: análise de componentes principais | 443


Machine Translated by Google

Figura 5-92. Variância explicada cumulativa para os dados LFW

Vemos que esses 150 componentes respondem por pouco mais de 90% da variância. Isso
nos levaria a acreditar que utilizando esses 150 componentes recuperaríamos a maior parte
das características essenciais dos dados. Para tornar isso mais concreto, podemos
comparar as imagens de entrada com as imagens reconstruídas a partir desses 150
componentes (Figura 5-93):
In[21]: # Calcula os componentes e faces projetadas pca =
RandomizedPCA(150).fit(faces.data) componentes
= pca.transform(faces.data) projected =
pca.inverse_transform(components)

In[22]: # Trace os resultados fig,


ax = plt.subplots(2, 10, figsize=(10, 2.5), subplot_kw={'xticks':
[], 'yticks':[]}, gridspec_kw=dict (hespaço=0,1,
wespaço=0,1))
para i no intervalo (10):
ax[0, i].imshow(faces.data[i].reshape(62, 47), cmap='binary_r') ax[1,
i].imshow(projected[i] .reshape(62, 47), cmap='binário_r')

machado[0, 0].set_ylabel('full-dim\ninput') ax[1,


0].set_ylabel('150-dim\nreconstrução');

Figura 5-93. Reconstrução PCA de 150 dimensões dos dados LFW

444 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

A linha superior aqui mostra as imagens de entrada, enquanto a linha inferior mostra a reconstrução
das imagens de apenas 150 dos cerca de 3.000 recursos iniciais. Esta visualização deixa claro por
que a seleção de recursos PCA usada em “Em profundidade: Máquinas de vetores de suporte” na
página 405 foi tão bem-sucedida: embora reduza a dimensionalidade dos dados em quase um fator
de 20, as imagens projetadas contêm informações suficientes para que possamos poderia, a olho
nu, reconhecer os indivíduos na imagem. O que isto significa é que o nosso algoritmo de classificação
precisa de ser treinado em dados de 150 dimensões em vez de dados de 3.000 dimensões, o que,
dependendo do algoritmo específico que escolhermos, pode levar a uma classificação muito mais
eficiente.

Resumo da análise de componentes principais Nesta

seção discutimos o uso da análise de componentes principais para redução de dimensionalidade,


para visualização de dados de alta dimensão, para filtragem de ruído e para seleção de recursos
em dados de alta dimensão. Devido à versatilidade e interpretabilidade da PCA, esta demonstrou
ser eficaz numa ampla variedade de contextos e disciplinas. Dado qualquer conjunto de dados de
alta dimensão, tendo a começar com PCA para visualizar a relação entre os pontos (como fizemos
com os dígitos), para entender a principal variação nos dados (como fizemos com as autofaces) e
para entender a dimensionalidade intrínseca (traçando a razão de variância explicada).

Certamente o PCA não é útil para todos os conjuntos de dados de alta dimensão, mas oferece um
caminho simples e eficiente para obter insights sobre dados de alta dimensão.

A principal fraqueza do PCA é que ele tende a ser altamente afetado por valores discrepantes nos
dados. Por esta razão, muitas variantes robustas de PCA foram desenvolvidas, muitas das quais
agem para descartar iterativamente pontos de dados que são mal descritos pelos componentes iniciais.
Scikit-Learn contém algumas variantes interessantes do PCA, incluindo RandomizedPCA e
SparsePCA, ambas também no submódulo sklearn.decomposition . O Randomi zedPCA, que
vimos anteriormente, usa um método não determinístico para aproximar rapidamente os primeiros
componentes principais em dados de dimensões muito altas, enquanto o SparsePCA introduz um
termo de regularização (consulte “Em profundidade: regressão linear” na página 390) que serve
para impor a dispersão dos componentes.

Nas seções a seguir, veremos outros métodos de aprendizagem não supervisionados que se
baseiam em algumas das ideias do PCA.

Em profundidade: aprendizagem múltipla

Vimos como a análise de componentes principais pode ser usada na tarefa de redução de
dimensionalidade – reduzindo o número de recursos de um conjunto de dados enquanto mantém
as relações essenciais entre os pontos. Embora o PCA seja flexível, rápido e facilmente interpretável,
ele não funciona tão bem quando há relacionamentos não lineares nos dados; veremos alguns
exemplos disso a seguir.

Em profundidade: aprendizagem múltipla | 445


Machine Translated by Google

Para resolver esta deficiência, podemos recorrer a uma classe de métodos conhecida como aprendizagem
múltipla – uma classe de estimadores não supervisionados que procura descrever conjuntos de dados como
variedades de baixa dimensão incorporadas em espaços de alta dimensão. Quando você pensa em uma
variedade, sugiro imaginar uma folha de papel: este é um objeto bidimensional que vive em nosso mundo
tridimensional familiar e pode ser dobrado ou enrolado em duas dimensões. Na linguagem da aprendizagem
múltipla, podemos pensar nesta folha como uma variedade bidimensional incorporada no espaço tridimensional.

Girar, reorientar ou esticar o pedaço de papel no espaço tridimensional não altera a geometria plana do papel:
tais operações são semelhantes a incorporações lineares. Se você dobrar, enrolar ou amassar o papel, ele ainda
será uma variedade bidimensional, mas a incorporação no espaço tridimensional não será mais linear.

Vários algoritmos de aprendizagem procurariam aprender sobre a natureza bidimensional fundamental do


papel, mesmo quando ele é contorcido para preencher o espaço tridimensional.
espaço.

Aqui demonstraremos uma série de métodos múltiplos, aprofundando-nos em algumas técnicas: escalonamento
multidimensional (MDS), incorporação localmente linear (LLE) e mapeamento isométrico (Isomap). Começamos
com as importações padrão:

In[1]: %matplotlib importação inline


matplotlib.pyplot as plt import seaborn as
sns; sns.set() importa numpy como np

Aprendizagem múltipla: “OLÁ”


Para tornar esses conceitos mais claros, vamos começar gerando alguns dados bidimensionais que podemos
usar para definir uma variedade. Aqui está uma função que irá criar dados no formato da palavra “OLÁ”:

Em[2]:
def make_hello(N=1000, rseed=42):
# Faça um gráfico com o texto “OLÁ”; salvar como PNG fig, ax =
plt.subplots(figsize=(4, 1)) fig.subplots_adjust(left=0,
right=1, bottom=0, top=1) ax.axis('off') ax.text (0,5, 0,4, 'OLÁ', va='centro',
ha='centro',
peso='negrito', tamanho=85) fig.savefig('hello.png') plt.close(fig)

# Abra este PNG e desenhe pontos aleatórios dele em


matplotlib.image import imread data =
imread('hello.png')[::-1, :, 0].T rng =
np.random.RandomState(rseed)
X = rng.rand(4 * N, 2) i, j = (X *
data.shape).astype(int).T máscara = (dados[i, j] < 1)

X = X[máscara]

446 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

X[:, 0] *= (dados.forma[0] / dados.forma[1])


X = X[:N]
retornar X[np.argsort(X[:, 0])]

Vamos chamar a função e visualizar os dados resultantes (Figura 5-94):

Em[3]: X = make_hello(1000)
colorize = dict(c=X[:, 0], cmap=plt.cm.get_cmap('rainbow', 5)) plt.scatter(X[:,
0], X[:, 1], **colorir) plt.axis('equal');

Figura 5-94. Dados para uso com aprendizagem múltipla

A saída é bidimensional e consiste em pontos desenhados no formato da palavra “OLÁ”. Este


formulário de dados nos ajudará a ver visualmente o que esses algoritmos estão fazendo.

Dimensionamento Multidimensional (MDS)

Olhando para dados como este, podemos ver que a escolha específica dos valores x e y do conjunto
de dados não é a descrição mais fundamental dos dados: podemos dimensionar, reduzir ou girar os
dados, e o “OLÁ” ainda será aparente. Por exemplo, se usarmos uma matriz de rotação para girar os
dados, os valores de x e y mudam, mas os dados ainda são fundamentalmente os mesmos (Figura
5-95):

In[4]: def girar(X, ângulo):


teta = np.deg2rad(ângulo)
R = [[np.cos(teta), np.sin(teta)], [-np.sin(teta),
np.cos(teta)]] retornar np.dot(X, R)

X2 = girar(X, 20) + 5
plt.scatter(X2[:, 0], X2[:, 1], **colorir) plt.axis('equal');

Em profundidade: aprendizagem múltipla | 447


Machine Translated by Google

Figura 5-95. Conjunto de dados girado

Isso nos diz que os valores de x e y não são necessariamente fundamentais para os relacionamentos nos dados. O
fundamental, neste caso, é a distância entre cada ponto e os demais pontos do conjunto de dados. Uma maneira
comum de representar isso é usar uma matriz de distância: para N pontos, construímos uma matriz N × N tal que a
entrada i, j contém a distância entre o ponto i e o ponto j. Vamos usar a função par wise_distances eficiente do Scikit-
Learn para fazer isso com nossos dados originais:

In[5]: de sklearn.metrics importar pairwise_distances D =


pairwise_distances(X)
D.forma

Fora[5]: (1000, 1000)


Conforme prometido, para nossos N=1.000 pontos, obtemos uma matriz 1.000×1.000, que pode ser visualizada
conforme mostrado na Figura 5-96:

Em [6]: plt.imshow(D, zorder=2, cmap='Blues', interpolation='mais próximo')


plt.colorbar();

448 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Figura 5-96. Visualização das distâncias aos pares entre os pontos

Se construirmos de forma semelhante uma matriz de distância para nossos dados girados e traduzidos, veremos
que é a mesma coisa:

Em [7]: D2 = distâncias_pares(X2)
np.allclose(D, D2)

Fora[7]: Verdadeiro

Esta matriz de distância nos dá uma representação de nossos dados que é invariante a rotações e translações,
mas a visualização da matriz não é totalmente intuitiva. Na representação apresentada na Figura 5-96, perdemos
qualquer sinal visível da estrutura interessante nos dados: o “OLÁ” que vimos antes.

Além disso, embora calcular esta matriz de distância a partir das coordenadas (x, y) seja simples, transformar
as distâncias novamente em coordenadas x e y é bastante difícil.
Isto é exatamente o que o algoritmo de escala multidimensional pretende fazer: dada uma matriz de distância
entre pontos, ele recupera uma representação de coordenadas D-dimensional dos dados. Vamos ver como
funciona para nossa matriz de distância, usando a dissimilaridade pré-computada para especificar que estamos
passando uma matriz de distância (Figura 5-97):

In[8]: de sklearn.manifold importar MDS


modelo = MDS(n_components=2, dissimilaridade='pré-computado', random_state=1) out
= model.fit_transform(D)
plt.scatter(out[:, 0], out[:, 1], **colorize) plt. eixo('igual');

Em profundidade: aprendizagem múltipla | 449


Machine Translated by Google

Figura 5-97. Uma incorporação MDS calculada a partir das distâncias entre pares

O algoritmo MDS recupera uma das possíveis representações de coordenadas bidimensionais dos
nossos dados, usando apenas a matriz de distância N × N que descreve a relação entre os pontos de
dados.

MDS como aprendizagem múltipla

A utilidade disto torna-se mais aparente quando consideramos o facto de que matrizes de distância
podem ser calculadas a partir de dados em qualquer dimensão. Assim, por exemplo, em vez de
simplesmente rodar os dados no plano bidimensional, podemos projetá-los em três dimensões usando
a seguinte função (essencialmente uma generalização tridimensional da matriz de rotação usada
anteriormente):

In[9]: def random_projection(X, dimension=3, rseed=42): afirmar


dimensão >= X.shape[1] rng =
np.random.RandomState(rseed)
C = rng.randn (dimensão, dimensão) e, V
= np.linalg.eigh (np.dot(C, CT)) return
np.dot(X, V[:X.shape[1]]);

X3 = projeção_aleatória(X, 3)
X3.forma

Fora[9]: (1000, 3)

Vamos visualizar esses pontos para ver com o que estamos trabalhando (Figura 5-98):

Em [10]: de mpl_toolkits import mplot3d ax =


plt.axes(projection='3d')
ax.scatter3D(X3[:, 0], X3[:, 1], X3[:, 2], **colorir)

ax.view_init(azim=70, elev=50)

450 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Figura 5-98. Dados incorporados linearmente em três dimensões

Podemos agora pedir ao estimador MDS para inserir esses dados tridimensionais, calcular a
matriz de distância e então determinar a incorporação bidimensional ideal para esta matriz de
distância. O resultado recupera uma representação dos dados originais (Figura 5-99):

In[11]: modelo = MDS(n_components=2, random_state=1) out3


= model.fit_transform(X3)
plt.scatter(out3[:, 0], out3[:, 1], **colorize) plt.axis ('igual');

Figura 5-99. A incorporação MDS dos dados tridimensionais recupera a entrada até uma rotação
e reflexão

Este é essencialmente o objetivo de um estimador de aprendizado múltiplo: dados dados


incorporados de alta dimensão, ele busca uma representação de baixa dimensão dos dados que preserve

Em profundidade: aprendizagem múltipla | 451


Machine Translated by Google

certos relacionamentos dentro dos dados. No caso do MDS, a quantidade preservada é a distância
entre cada par de pontos.

Incorporações não lineares: onde o MDS falha Nossa

discussão até agora considerou incorporações lineares, que consistem essencialmente em rotações,
translações e escalonamentos de dados em espaços de dimensões superiores. O problema do MDS
é quando a incorporação é não linear – isto é, quando vai além deste simples conjunto de operações.
Considere a seguinte incorporação, que pega a entrada e a contorce em um formato de “S” em três
dimensões:

Em[12]: def make_hello_s_curve(X):


t = (X[:, 0] - 2) * 0,75 * np.pi x = np.sin(t)
y = X[:, 1] z =
np.sign(t) *
(np.cos(t) - 1) retorne np.vstack((x, y,
z)).T

XS = make_hello_s_curve(X)

Novamente, trata-se de dados tridimensionais, mas podemos ver que a incorporação é muito mais
complicada (Figura 5-100):

Em [13]: de mpl_toolkits import mplot3d ax =


plt.axes(projection='3d')
ax.scatter3D(XS[:, 0], XS[:, 1], XS[:, 2], **colorize) ;

Figura 5-100. Dados incorporados de forma não linear em três dimensões

As relações fundamentais entre os pontos de dados ainda existem, mas desta vez os dados foram
transformados de forma não linear: foram embrulhados na forma de um “S”.

452 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Se tentarmos um algoritmo MDS simples com esses dados, ele não será capaz de “desembrulhar” essa
incorporação não linear e perderemos o controle das relações fundamentais na variedade incorporada (Figura
5-101):

In[14]: de sklearn.manifold importar modelo


MDS = MDS(n_components=2, random_state=2)
outS = model.fit_transform(XS)
plt.scatter(outS[:, 0], outS[:, 1], * *colorir) plt.axis('igual');

Figura 5-101. O algoritmo MDS aplicado aos dados não lineares; não consegue recuperar a estrutura
subjacente

A melhor incorporação linear bidimensional não desembrulha a curva S, mas, em vez disso, descarta o eixo y
original.

Variedades Não Lineares: Incorporação Localmente Linear Como

podemos avançar aqui? Recuando, podemos ver que a origem do problema é que o MDS tenta preservar
distâncias entre pontos distantes ao construir a incorporação. Mas e se, em vez disso, modificássemos o
algoritmo de forma que ele preservasse apenas as distâncias entre pontos próximos? A incorporação resultante
ficaria mais próxima do que desejamos.

Visualmente, podemos pensar nisso conforme ilustrado na Figura 5-102.

Em profundidade: aprendizagem múltipla | 453


Machine Translated by Google

Figura 5-102. Representação de ligações entre pontos dentro do MDS e LLE

Aqui cada linha tênue representa uma distância que deve ser preservada na incorporação.
À esquerda está uma representação do modelo utilizado pelo MDS: ele tenta preservar as distâncias
entre cada par de pontos no conjunto de dados. À direita está uma representação do modelo usado
por um algoritmo de aprendizado múltiplo chamado incorporação localmente linear (LLE): em vez
de preservar todas as distâncias, ele tenta preservar apenas as distâncias entre pontos vizinhos:
neste caso, os 100 mais próximos vizinhos de cada ponto.

Pensando no painel esquerdo, podemos ver porque o MDS falha: não há como nivelar esses dados
e ao mesmo tempo preservar adequadamente o comprimento de cada linha desenhada entre os
dois pontos. Para o painel direito, por outro lado, as coisas parecem um pouco mais otimistas.
Poderíamos imaginar desenrolar os dados de uma forma que mantivesse os comprimentos das
linhas aproximadamente iguais. Isto é precisamente o que o LLE faz, através de uma otimização
global de uma função de custo que reflete esta lógica.

LLE vem em vários sabores; aqui usaremos o algoritmo LLE modificado para recuperar a variedade
bidimensional incorporada. Em geral, o LLE modificado se sai melhor do que outras variantes do
algoritmo na recuperação de variedades bem definidas com muito pouca distorção (Figura 5-103):

Em [15]:
de sklearn.manifold import LocallyLinearEmbedding model
= LocallyLinearEmbedding(n_neighbors=100, n_components=2, method='modified',
eigen_solver='dense')
saída = model.fit_transform(XS)

fig, ax = plt.subplots()
ax.scatter(out[:, 0], out[:, 1], **colorizar) ax.set_ylim(0,15,
-0,15);

454 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Figura 5-103. A incorporação localmente linear pode recuperar os dados subjacentes de uma
entrada não quase incorporada

O resultado permanece um pouco distorcido em comparação com a nossa variedade original, mas
captura as relações essenciais nos dados!

Algumas reflexões sobre vários métodos Embora

essa história e motivação sejam convincentes, na prática, diversas técnicas de aprendizagem tendem
a ser tão complicadas que raramente são usadas para algo além da simples visualização qualitativa
de dados de alta dimensão.

A seguir estão alguns dos desafios específicos da aprendizagem múltipla, que contrastam mal com o
PCA:

• Na aprendizagem múltipla, não existe uma boa estrutura para lidar com dados faltantes. Em
contraste, existem abordagens iterativas simples para dados faltantes no PCA. • Na

aprendizagem múltipla, a presença de ruído nos dados pode causar um “curto-circuito” na variedade
e alterar drasticamente a incorporação. Em contraste, o PCA filtra naturalmente o ruído dos
componentes mais importantes.

• O resultado da incorporação múltipla é geralmente altamente dependente do número de vizinhos


escolhidos, e geralmente não existe uma maneira quantitativa sólida de escolher um número
ideal de vizinhos. Em contrapartida, a PCA não envolve tal escolha. • Na aprendizagem

múltipla, o número globalmente ideal de dimensões de produção é difícil de determinar. Por outro
lado, o PCA permite encontrar a dimensão de saída com base na variação explicada.

• Na aprendizagem múltipla, o significado das dimensões incorporadas nem sempre é


claro. No PCA, os componentes principais têm um significado muito claro.

Em profundidade: aprendizagem múltipla | 455


Machine Translated by Google

• Na aprendizagem múltipla, o custo computacional de vários métodos é dimensionado como O[N2 ]


ou O[N3 ]. Para PCA, existem abordagens aleatórias que geralmente são muito mais rápidas
(embora veja o pacote megaman para algumas implementações mais escaláveis de aprendizagem
múltipla).

Com tudo isso em questão, a única vantagem clara dos múltiplos métodos de aprendizagem em relação
ao PCA é a sua capacidade de preservar relações não lineares nos dados; por esse motivo, tendo a
explorar dados com métodos múltiplos somente depois de explorá-los primeiro com PCA.

O Scikit-Learn implementa diversas variantes comuns de aprendizagem múltipla além do Isomap e LLE:
a documentação do Scikit-Learn tem uma boa discussão e comparação entre elas. Com base na minha
própria experiência, daria as seguintes recomendações:

• Para problemas de brinquedo, como a curva S que vimos antes, a incorporação localmente linear
(LLE) e suas variantes (especialmente LLE modificada) funcionam muito bem. Isso é implementado
em sklearn.manifold.LocallyLinearEmbedding.

• Para dados de alta dimensão provenientes de fontes do mundo real, o LLE muitas vezes produz
resultados pobres, e o mapeamento isométrico (Isomap) parece geralmente levar a incorporações
mais significativas. Isso é implementado em sklearn.manifold.Isomap. • Para dados

altamente agrupados, a incorporação estocástica de vizinhos com distribuição t (t-SNE) parece


funcionar muito bem, embora possa ser muito lenta em comparação com outros métodos. Isso é
implementado em sklearn.manifold.TSNE.

Se você estiver interessado em entender como isso funciona, sugiro executar cada um dos métodos nos
dados desta seção.

Exemplo: Isomapa em Faces Um

lugar onde o aprendizado múltiplo é frequentemente usado é na compreensão da relação entre pontos
de dados de alta dimensão. Um caso comum de dados de alta dimensão são as imagens; por exemplo,
um conjunto de imagens com 1.000 pixels cada pode ser considerado como uma coleção de pontos em
1.000 dimensões – o brilho de cada pixel em cada imagem define a coordenada nessa dimensão.

Aqui vamos aplicar o Isomap em alguns dados de faces. Usaremos o conjunto de dados Labeled Faces
in the Wild, que vimos anteriormente em “Em profundidade: Máquinas de vetores de suporte” na página
405 e “Em profundidade: Análise de componentes principais” na página 433. A execução deste comando
fará o download dos dados e armazene-o em cache em seu diretório inicial para uso posterior:

Em [16]: de sklearn.datasets importar fetch_lfw_people


faces = fetch_lfw_people(min_faces_per_person=30)
faces.data.shape

Fora[16]: (2370, 2914)

456 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Temos 2.370 imagens, cada uma com 2.914 pixels. Em outras palavras, as imagens podem ser
pensadas como pontos de dados em um espaço de 2.914 dimensões!

Vamos visualizar rapidamente várias dessas imagens para ver com o que estamos trabalhando
(Figura 5-104):

In[17]: fig, ax = plt.subplots(4, 8, subplot_kw=dict(xticks=[], yticks=[]))


para i, axi in enumerate(ax.flat):
axi.imshow(faces.images[i], cmap='gray')

Figura 5-104. Exemplos de faces de entrada

Gostaríamos de traçar uma incorporação de baixa dimensão dos dados de 2.914 dimensões para
aprender as relações fundamentais entre as imagens. Uma maneira útil de começar é calcular um
PCA e examinar a razão de variância explicada, o que nos dará uma ideia de quantas características
lineares são necessárias para descrever os dados (Figura 5-105):

In[18]: de sklearn.decomposition importar RandomizedPCA


modelo = RandomizedPCA(100).fit(faces.data)
plt.plot(np.cumsum(model.explained_variance_ratio_))
plt.xlabel('n componentes')
plt.ylabel('variância cumulativa');

Vemos que para esses dados são necessários quase 100 componentes para preservar 90% da
variância. Isso nos diz que os dados são intrinsecamente dimensionais muito elevados – não podem
ser descritos linearmente com apenas alguns componentes.

Em profundidade: aprendizagem múltipla | 457


Machine Translated by Google

Figura 5-105. Variação acumulada da projeção do PCA

Quando for esse o caso, embeddings de variedades não lineares como LLE e Isomap podem
ser úteis. Podemos calcular uma incorporação de isomapa nessas faces usando o mesmo
padrão mostrado antes:

In[19]: de sklearn.manifold importar Isomap


modelo = Isomap(n_components=2)
proj = model.fit_transform(faces.data) proj.shape

Fora[19]: (2370, 2)

A saída é uma projeção bidimensional de todas as imagens de entrada. Para ter uma ideia
melhor do que a projeção nos diz, vamos definir uma função que produzirá miniaturas de
imagens nos locais das projeções:

Em [20]: do offsetbox de importação do matplotlib

def plot_components(dados, modelo, imagens=Nenhum,


ax=Nenhum, thumb_frac=0,05, cmap='cinza'):
machado = machado ou plt.gca()

proj = model.fit_transform(dados)
ax.plot(proj[:, 0], proj[:, 1], '.k')

se as imagens não forem Nenhuma:


min_dist_2 = (thumb_frac * max(proj.max(0) - proj.min(0))) ** 2
imagens_mostradas = np.array([2 * proj.max(0)])
para i no intervalo(data.shape [0]):
dist = np.sum((proj[i] - mostradas_images) ** 2, 1) if
np.min(dist) < min_dist_2: # não
mostra pontos muito próximos continue

458 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

imagens_mostradas = np.vstack([images_mostradas, proj[i]])


imagebox =
offsetbox.AnnotationBbox( offsetbox.OffsetImage(imagens[i],
cmap=cmap), proj[i])
ax.add_artist(caixa de imagem)

Chamando esta função agora, vemos o resultado (Figura 5-106):

Em[21]: fig, ax = plt.subplots(figsize=(10, 10))


plot_components(faces.data,
modelo=Isomapa(n_componentes=2),
imagens=faces.images[:, ::2, ::2])

Figura 5-106. Incorporação isomapa dos dados de rostos

O resultado é interessante: as duas primeiras dimensões do isomapa parecem descrever características


globais da imagem: a escuridão ou clareza geral da imagem da esquerda para a direita e a orientação
geral da face de baixo para cima. Isso nos dá uma boa indicação visual de alguns dos recursos
fundamentais de nossos dados.

Em profundidade: aprendizagem múltipla | 459


Machine Translated by Google

Poderíamos então classificar esses dados, talvez usando recursos múltiplos como entradas para o
algoritmo de classificação, como fizemos em “Detalhamento: Máquinas de Vetores de Suporte” na página
405.

Exemplo: Visualizando Estrutura em Dígitos Como outro

exemplo de uso de aprendizado múltiplo para visualização, vamos dar uma olhada no conjunto de dígitos
manuscritos do MNIST. Esses dados são semelhantes aos dígitos que vimos em “Em profundidade:
árvores de decisão e florestas aleatórias” na página 421, mas com muito mais pixels por imagem. Ele
pode ser baixado em http://mldata.org/ com o utilitário Scikit-Learn:

Em [22]: de sklearn.datasets importar fetch_mldata mnist


= fetch_mldata('MNIST original') mnist.data.shape

Fora[22]: (70000, 784)

Isso consiste em 70.000 imagens, cada uma com 784 pixels (ou seja, as imagens têm 28×28). Como
antes, podemos dar uma olhada nas primeiras imagens (Figura 5-107):

In[23]: fig, ax = plt.subplots(6, 8, subplot_kw=dict(xticks=[], yticks=[]))


para i, eixo em enumerar (ax.flat):
axi.imshow(mnist.data[1250 * i].reshape(28, 28), cmap='gray_r')

Figura 5-107. Exemplos de dígitos MNIST

Isso nos dá uma ideia da variedade de estilos de escrita manual no conjunto de dados.

Vamos calcular uma projeção de aprendizagem múltipla através dos dados, ilustrada na Figura 5-108.
Para velocidade aqui, usaremos apenas 1/30 dos dados, o que equivale a cerca de 2.000 pontos (devido
ao escalonamento relativamente pobre do aprendizado múltiplo, acho que alguns milhares de amostras
são um bom número para começar, para resultados relativamente rápidos). exploração antes de passar
para um cálculo completo):

460 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

In[24]:
# use apenas 1/30 dos dados: o conjunto de dados completo leva muito
tempo! dados =
mnist.data[::30] alvo = mnist.target[::30]

modelo = Isomap(n_components=2)
proj = model.fit_transform(dados)
plt.scatter(proj[:, 0], proj[:, 1], c=target, cmap=plt.cm.get_cmap('jet', 10)) plt.colorbar(ticks=range(10))
plt.clim(-0,5, 9,5);

Figura 5-108. Incorporação de isomapas dos dados de dígitos MNIST

O gráfico de dispersão resultante mostra algumas das relações entre os pontos de dados, mas está um
pouco lotado. Podemos obter mais informações observando apenas um número de cada vez (Figura 5-109):

In[25]: de sklearn.manifold importar Isomap

# Escolha 1/4 dos dígitos "1" para projetar dados =


mnist.data[mnist.target == 1][::4]

fig, ax = plt.subplots(figsize=(10, 10)) modelo =


Isomap(n_neighbors=5, n_components=2, eigen_solver='dense')
plot_components(dados, modelo, imagens=data.reshape((-1, 28, 28)),
machado = machado, polegar_frac = 0,05, cmap = 'cinza_r')

Em profundidade: aprendizagem múltipla | 461


Machine Translated by Google

Figura 5-109. Incorporação de isomapas apenas de 1s nos dados de dígitos

O resultado dá uma ideia da variedade de formas que o número “1” pode assumir no conjunto de
dados. Os dados ficam ao longo de uma ampla curva no espaço projetado, que parece traçar a
orientação do dígito. À medida que avança no gráfico, você encontra aqueles que possuem
chapéus e/ou bases, embora sejam muito esparsos no conjunto de dados. A projeção nos permite
identificar valores discrepantes que apresentam problemas de dados (ou seja, partes dos dígitos
vizinhos que se infiltraram nas imagens extraídas).

Agora, isto por si só pode não ser útil para a tarefa de classificação de dígitos, mas ajuda-nos a
compreender os dados e pode dar-nos ideias sobre como avançar, por exemplo, como podemos
querer pré-processar os dados. dados antes de construir um pipeline de classificação.

Em profundidade: agrupamento k-Means

Nas seções anteriores, exploramos uma categoria de modelos de aprendizado de máquina não
supervisionados: redução de dimensionalidade. Aqui passaremos para outra classe de modelos
de aprendizado de máquina não supervisionados: algoritmos de cluster. Algoritmos de cluster

462 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

procure aprender, a partir das propriedades dos dados, uma divisão ótima ou rotulagem discreta de grupos de
pontos.

Muitos algoritmos de clustering estão disponíveis no Scikit-Learn e em outros lugares, mas talvez o mais simples
de entender seja um algoritmo conhecido como clustering k-means, que é implementado em sklearn.cluster.KMeans.
Começamos com as importações padrão:

In[1]: %matplotlib importação


inline matplotlib.pyplot as plt import
seaborn as sns; sns.set() # para estilo de plotagem import
numpy as np

Apresentando k-Means O

algoritmo k-means procura um número predeterminado de clusters em um conjunto de dados multidimensional não
rotulado. Ele consegue isso usando uma concepção simples de como seria o cluster ideal:

• O “centro do cluster” é a média aritmética de todos os pontos pertencentes ao


conjunto.

• Cada ponto está mais próximo do seu próprio centro de cluster do que de outros centros de cluster.

Essas duas suposições são a base do modelo k-means. Em breve iremos nos aprofundar em como exatamente o
algoritmo chega a essa solução, mas por enquanto vamos dar uma olhada em um conjunto de dados simples e
ver o resultado k-means.

Primeiro, vamos gerar um conjunto de dados bidimensional contendo quatro blobs distintos. Para enfatizar que
este é um algoritmo não supervisionado, deixaremos os rótulos fora da visualização (Figura 5-110):

Em [2]: de sklearn.datasets.samples_generator importar make_blobs


X, y_true = make_blobs(n_samples=300, centers=4,
cluster_std=0,60, random_state=0)
dispersão(X[:, 0], X[:, 1], s=50);

A olho nu, é relativamente fácil distinguir os quatro clusters. O algoritmo k-means faz isso automaticamente e no
Scikit-Learn usa a API típica do estimador:

In[3]: de sklearn.cluster importar KMeans


kmeans = KMeans(n_clusters=4)
kmeans.fit(X)
y_kmeans = kmeans.predict(X)

Em profundidade: agrupamento k-Means | 463


Machine Translated by Google

Figura 5-110. Dados para demonstração de clustering

Vamos visualizar os resultados plotando os dados coloridos por esses rótulos. Também representaremos
graficamente os centros dos clusters conforme determinado pelo estimador k-means (Figura 5-111):

Em[4]: plt.scatter(X[:, 0], X[:, 1], c=y_kmeans, s=50, cmap='viridis')

centros = kmeans.cluster_centers_
plt.scatter(centros[:, 0], centros[:, 1], c='preto', s=200, alfa=0,5);

Figura 5-111. k-significa centros de cluster com clusters indicados por cor

A boa notícia é que o algoritmo k-means (pelo menos neste caso simples) atribui os pontos aos clusters de
forma muito semelhante à forma como poderíamos atribuí-los a olho nu. Mas você pode estar se perguntando
como esse algoritmo encontra esses clusters tão rapidamente! Afinal, o número de combinações possíveis de
atribuições de cluster é exponencial no número de dados

464 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

pontos - uma pesquisa exaustiva seria muito, muito cara. Felizmente para nós, uma pesquisa tão
exaustiva não é necessária; em vez disso, a abordagem típica para k-médias envolve uma abordagem
iterativa intuitiva conhecida como maximização de expectativa.

Algoritmo k-Means: Expectativa-Maximização A maximização da

expectativa (E – M) é um algoritmo poderoso que surge em uma variedade de contextos na ciência de


dados. k-means é uma aplicação do algoritmo particularmente simples e fácil de entender, e iremos
examiná-la brevemente aqui. Resumindo, a abordagem de maximização de expectativas consiste no
seguinte procedimento:

1. Adivinhe alguns centros de cluster

2. Repita até convergir

a. E-Step: atribuir pontos ao centro do cluster mais próximo b.

M-Step: defina os centros do cluster para a média

Aqui o “E-step” ou “etapa de expectativa” é assim chamado porque envolve a atualização de nossa
expectativa de a qual cluster cada ponto pertence. A “etapa M” ou “etapa de maximização” é assim
chamada porque envolve a maximização de alguma função de aptidão que define a localização dos
centros do cluster – neste caso, essa maximização é realizada tomando uma média simples dos dados
em cada cluster.

A literatura sobre este algoritmo é vasta, mas pode ser resumida da seguinte forma: em circunstâncias
típicas, cada repetição do passo E e do passo M sempre resultará em uma melhor estimativa das
características do cluster.

Podemos visualizar o algoritmo conforme mostrado na Figura 5-112.

Para a inicialização específica mostrada aqui, os clusters convergem em apenas três iterações. Para
uma versão interativa desta figura, consulte o código no apêndice online.

Figura 5-112. Visualização do algoritmo E – M para k-means

O algoritmo k-means é simples o suficiente para que possamos escrevê-lo em algumas linhas de código.
A seguir está uma implementação muito básica (Figura 5-113):

Em profundidade: agrupamento k-Means | 465


Machine Translated by Google

In[5]: de sklearn.metrics import pairwise_distances_argmin

def find_clusters(X, n_clusters, rseed=2): # 1.


Escolha aleatoriamente clusters rng
= np.random.RandomState(rseed) i =
rng.permutation(X.shape[0])[:n_clusters] centers = X[i ]

enquanto Verdadeiro:

# 2a. Atribuir rótulos com base nos rótulos centrais


mais próximos = pairwise_distances_argmin(X, centers)

#2b. Encontre novos centros a partir das médias dos


pontos new_centers = np.array([X[labels == i].mean(0)
para i no intervalo (n_clusters)])

#2c. Verifique a convergência se


np.all(centers == new_centers): break

centros = novos_centros

centros de devolução , etiquetas

centros, rótulos = find_clusters(X, 4)


plt.scatter(X[:, 0], X[:, 1], c=labels, s=50,
cmap='viridis');

Figura 5-113. Dados rotulados com k-means

A maioria das implementações bem testadas fará um pouco mais do que isso nos bastidores,
mas a função anterior fornece a essência da abordagem de maximização de expectativa.

466 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Advertências sobre maximização de

expectativa Há algumas questões que você deve conhecer ao usar o algoritmo de maximização
de expectativa.

O resultado globalmente ideal pode não ser


alcançado. Em primeiro lugar, embora seja garantido que o procedimento E-M melhore o
resultado em cada etapa, não há garantia de que conduzirá à melhor solução global. Por
exemplo, se usarmos uma semente aleatória diferente em nosso procedimento simples, as
suposições iniciais específicas levarão a resultados ruins (Figura 5-114):

In[6]: centros, rótulos = find_clusters(X, 4, rseed=0) plt.scatter(X[:,


0], X[:, 1], c=labels, s=50, cmap='viridis' );

Figura 5-114. Um exemplo de má convergência em k-means

Aqui a abordagem E-M convergiu, mas não convergiu para uma configuração globalmente
ideal. Por esse motivo, é comum que o algoritmo seja executado para múltiplas suposições
iniciais, como de fato o Scikit-Learn faz por padrão (definido pelo parâmetro n_init , cujo
padrão é 10).

O número de clusters deve ser selecionado


antecipadamente Outro desafio comum com k-means é que você deve dizer quantos clusters
você espera: ele não pode aprender o número de clusters a partir dos dados. Por exemplo,
se pedirmos ao algoritmo para identificar seis clusters, ele prosseguirá alegremente e
encontrará os seis melhores clusters (Figura 5-115):

Em[7]: rótulos = KMeans(6, random_state=0).fit_predict(X)


plt.scatter(X[:, 0], X[:, 1], c=rótulos, s=50,
cmap='viridis');

Em profundidade: agrupamento k-Means | 467


Machine Translated by Google

Figura 5-115. Um exemplo em que o número de clusters é mal escolhido

Se o resultado é significativo é uma questão difícil de responder definitivamente; uma abordagem


bastante intuitiva, mas que não discutiremos mais aqui, é chamada de análise de silhueta.

Alternativamente, você pode usar um algoritmo de agrupamento mais complicado que tenha uma
medida quantitativa melhor da aptidão por número de agrupamentos (por exemplo, modelos de
mistura gaussiana; consulte “Em profundidade: modelos de mistura gaussiana” na página 476) ou
que possa escolher um número adequado de clusters (por exemplo, DBSCAN, mudança de média
ou propagação de afinidade, todos disponíveis no submódulo sklearn.cluster ).

k-means é limitado aos limites lineares do cluster As


suposições fundamentais do modelo de k-means (os pontos estarão mais próximos do centro do
seu próprio cluster do que de outros) significam que o algoritmo será frequentemente ineficaz se
os clusters tiverem geometrias complicadas.

Em particular, os limites entre clusters k-means serão sempre lineares, o que significa que falhará
em limites mais complicados. Considere os seguintes dados, juntamente com os rótulos dos
clusters encontrados pela abordagem típica de k-means (Figura 5-116):

In[8]: de sklearn.datasets importar make_moons


X, y = make_moons(200, ruído=0,05, estado_aleatório=0)

Em[9]: rótulos = KMeans(2, random_state=0).fit_predict(X)


plt.scatter(X[:, 0], X[:, 1], c=rótulos, s=50,
cmap='viridis');

468 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

Figura 5-116. Falha de k-médias com limites não lineares

Esta situação é uma reminiscência da discussão em “Detalhamento: Máquinas de


Vetores de Suporte” na página 405, onde usamos uma transformação de kernel para
projetar os dados em uma dimensão superior onde uma separação linear é possível.
Poderíamos imaginar usar o mesmo truque para permitir que k-means descubra limites não lineares.

Uma versão deste k-means kernelizado é implementada no Scikit-Learn dentro do


estimador SpectralClustering . Ele usa o gráfico dos vizinhos mais próximos para calcular
uma representação de dimensão superior dos dados e, em seguida, atribui rótulos
usando um algoritmo k-means (Figura 5-117):

Em [10]: de sklearn.cluster import SpectralClustering model =


SpectralClustering(n_clusters=2,
affinity='nearest_neighbours',
atribua_labels='kmeans')
rótulos = model.fit_predict(X)
plt.scatter(X[:, 0], X[:, 1], c=labels, s=50,
cmap='viridis');

Vemos que com esta abordagem de transformação de kernel, o k-means kernelizado é


capaz de encontrar os limites não lineares mais complicados entre clusters.

Em profundidade: agrupamento k-Means | 469


Machine Translated by Google

Figura 5-117. Limites não lineares aprendidos por SpectralClustering

k-means pode ser lento para um grande número de


amostras Como cada iteração de k-means deve acessar todos os pontos do conjunto de dados,
o algoritmo pode ser relativamente lento à medida que o número de amostras aumenta. Você
pode estar se perguntando se esse requisito de usar todos os dados em cada iteração pode ser
relaxado; por exemplo, você pode usar apenas um subconjunto de dados para atualizar os
centros de cluster em cada etapa. Esta é a ideia por trás dos algoritmos k-means baseados em
lote, uma forma dos quais é implementada em sklearn.cluster.MiniBatchKMeans. A interface para
isso é a mesma do KMeans padrão; veremos um exemplo de seu uso à medida que continuarmos
nossa discussão.

Exemplos

Tomando cuidado com essas limitações do algoritmo, podemos usar k-means a nosso favor em uma
ampla variedade de situações. Vamos agora dar uma olhada em alguns exemplos.

Exemplo 1: k-médias em

dígitos Para começar, vamos dar uma olhada na aplicação de k-médias nos mesmos dados de dígitos
simples que vimos em “Detalhado: Árvores de decisão e florestas aleatórias” na página 421 e “Em
profundidade: Principal Análise de componentes” na página 433. Aqui tentaremos usar k-means para
tentar identificar dígitos semelhantes sem usar as informações originais do rótulo; isso pode ser
semelhante a uma primeira etapa na extração de significado de um novo conjunto de dados sobre o
qual você não possui nenhuma informação de rótulo a priori.

Começaremos carregando os dígitos e depois encontrando os clusters KMeans . Lembre-se de que


os dígitos consistem em 1.797 amostras com 64 características, onde cada uma das 64 características
é o brilho de um pixel em uma imagem 8×8:

470 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

In[11]: de sklearn.datasets import load_digits digits =


load_digits() digits.data.shape

Fora[11]: (1797, 64)

O clustering pode ser executado como fizemos antes:

Em[12]: kmeans = KMeans(n_clusters=10, random_state=0)


clusters = kmeans.fit_predict(digits.data)
kmeans.cluster_centers_.shape

Fora[12]: (10, 64)


O resultado são 10 clusters em 64 dimensões. Observe que os próprios centros do cluster são pontos
de 64 dimensões e podem ser interpretados como o dígito “típico” dentro do cluster. Vamos ver como
são esses centros de cluster (Figura 5-118):

Em[13]: fig, ax = plt.subplots(2, 5, figsize=(8, 3))


centros = kmeans.cluster_centers_.reshape(10, 8, 8) para eixo,
centro em zip(ax.flat, centros): axi.set(xticks=[],
yticks=[]) axi.imshow(centro,
interpolação= 'mais próximo', cmap=plt.cm.binary)

Figura 5-118. Centros de cluster aprendidos por k-means

Vemos que mesmo sem os rótulos, o KMeans é capaz de encontrar clusters cujos centros são dígitos
reconhecíveis, talvez com exceção de 1 e 8.

Como k-means não sabe nada sobre a identidade do cluster, os rótulos de 0 a 9 podem ser permutados.
Podemos corrigir isso combinando cada rótulo de cluster aprendido com os rótulos verdadeiros
encontrados neles:

In[14]: do modo de importação scipy.stats

rótulos = np.zeros_like(clusters) para i


no intervalo(10):
máscara = (clusters == i)
rótulos[mask] = mode(digits.target[mask])[0]

Agora podemos verificar a precisão do nosso agrupamento não supervisionado em encontrar dígitos
semelhantes nos dados:

Em profundidade: agrupamento k-Means | 471


Machine Translated by Google

In[15]: de sklearn.metrics importar precisão_score


precisão_score(dígitos.target, rótulos)

Fora[15]: 0,79354479688369506

Com apenas um algoritmo simples de k-means, descobrimos o agrupamento correto para 80% dos
dígitos de entrada! Vamos verificar a matriz de confusão para isso (Figura 5-119):

Em [16]: de sklearn.metrics importar confusão_matrix mat =


confusão_matrix(digits.target, rótulos) sns.heatmap(mat.T,
square=True, annot=True, fmt='d', cbar=False,
xticklabels=digits.target_names,
yticklabels=digits.target_names)
plt.xlabel(' rótulo verdadeiro')
plt.ylabel(' rótulo previsto');

Figura 5-119. Matriz de confusão para o classificador k-means

Como poderíamos esperar dos centros de cluster que visualizamos antes, o principal ponto de
confusão está entre os oitos e os uns. Mas isso ainda mostra que usando k-means, podemos
essencialmente construir um classificador de dígitos sem referência a quaisquer rótulos conhecidos!

Apenas por diversão, vamos tentar levar isso ainda mais longe. Podemos usar o algoritmo de
incorporação estocástica de vizinho distribuído em t (t-SNE) (mencionado em “Detalhamento:
Aprendizado múltiplo” na página 445) para pré-processar os dados antes de executar k-means. t-
SNE é um algoritmo de incorporação não linear que é particularmente adequado para preservar
pontos dentro de clusters. Vamos ver como funciona:

Em [17]: de sklearn.manifold importar TSNE

# Projete os dados: esta etapa levará vários segundos tsne =


TSNE(n_components=2, init='pca', random_state=0) digits_proj =
tsne.fit_transform(digits.data)

472 | Capítulo 5: Aprendizado de Máquina


Machine Translated by Google

# Calcula os clusters
kmeans = KMeans(n_clusters=10, random_state=0)
clusters = kmeans.fit_predict(digits_proj)

# Permutar os rótulos
rótulos = np.zeros_like(clusters) for i in
range(10): máscara =
(clusters == i) rótulos[mask]
= mode(digits.target[mask])[0]

# Calcula a precisão
exact_score(digits.target, rótulos)

Fora[17]: 0,93356149137451305

Isso representa quase 94% de precisão de classificação sem usar os rótulos. Este é o poder da
aprendizagem não supervisionada quando usada com cuidado: ela pode extrair informações do conjunto
de dados que podem ser difíceis de obter manualmente ou a olho nu.

Exemplo 2: k-means para compactação de

cores Uma aplicação interessante de clustering é a compactação de cores em imagens. Por exemplo,
imagine que você tem uma imagem com milhões de cores. Na maioria das imagens, um grande número
de cores não será utilizado e muitos pixels da imagem terão cores semelhantes ou até idênticas.

Por exemplo, considere a imagem mostrada na Figura 5-120, que é do módulo datasets do Scikit-Learn
(para que isso funcione, você terá que ter o pacote Pillow Python instalado):

In[18]: # Nota: isso requer a instalação do pacote de travesseiros


de sklearn.datasets importar load_sample_image china
= load_sample_image("china.jpg") ax =
plt.axes(xticks=[], yticks=[]) ax.imshow(china);

A imagem em si é armazenada em uma matriz tridimensional de tamanho (altura, largura, RGB),


contendo contribuições vermelho/azul/verde como números inteiros de 0 a 255:

Em[19]: china.shape

Fora[19]: (427, 640, 3)

Em profundidade: agrupamento k-Means | 473

Você também pode gostar