Escolar Documentos
Profissional Documentos
Cultura Documentos
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.
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.
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
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).
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.
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.
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.
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
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.
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
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.
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.
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:
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.
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.
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):
Fora[3]: (150, 4)
Fora[4]: (150,)
Para resumir, o layout esperado de recursos e valores alvo é visualizado na Figura 5-13.
Com esses dados formatados corretamente, podemos passar a considerar a API do estimador do 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.
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.
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.
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):
4. Ajuste o modelo aos seus dados chamando o método fit() da instância do modelo.
• 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.
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):
rng = np.random.RandomState(42) x = 10
* rng.rand(50) x - 1 +
rng.randn(50) y = 2 *
plt.scatter(x, y);
Com esses dados disponíveis, podemos usar a receita descrita anteriormente. Vamos percorrer o
processo:
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:
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 .
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:
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 :
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.
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:
Fora[8]: (50, 1)
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:
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ê:
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:
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.
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 :
Com os dados organizados, podemos seguir nossa receita para prever os rótulos:
Finalmente, podemos usar o utilitário Precision_score para ver a fração de rótulos previstos que
correspondem ao seu valor real:
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!
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.
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.
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):
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.
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.
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.
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.
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.
Usaremos a interface de acesso a dados do Scikit-Learn e daremos uma olhada nestes dados:
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):
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[25]: (1797,)
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:
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):
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.
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:
Agora que previmos nosso modelo, podemos avaliar sua precisão comparando os valores verdadeiros do
conjunto de teste com as previsões:
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) :
Figura 5-20. Uma matriz de confusão mostrando a frequência de erros de classificação por nosso
classificador
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):
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.
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.
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.
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.
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:
X = íris.dados y
= íris.target
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)
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!
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:
Saída[5]: 0,90666666666666662
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.
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.
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:
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.
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:
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:
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.,
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.
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.
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.
É 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.
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 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:
• 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.
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.
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
Agora vamos criar alguns dados aos quais ajustaremos nosso modelo:
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):
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)?
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.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.
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):
Figura 5-29. O modelo ideal com validação cruzada para os dados da Figura 5-27
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
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.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.
• 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.
Com essas características em mente, esperaríamos que uma curva de aprendizado se parecesse
qualitativamente com a mostrada na Figura 5-32.
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).
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
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.
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.
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:
Observe que, como um estimador normal, isso ainda não foi aplicado a nenhum dado. Chamar o método fit() ajustará o
Agora que isso está adequado, podemos solicitar os melhores parâmetros da seguinte forma:
Em[20]: grid.best_params_
Finalmente, se desejarmos, podemos usar o melhor modelo e mostrar o ajuste aos nossos dados usando o código anterior
(Figura 5-34):
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.
Figura 5-34. O modelo mais adequado determinado através de uma pesquisa automática em grade
Resumo Nesta
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.
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:
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ê:
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']
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:
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
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:
vec = ContVectorizer()
X = vec.fit_transform(amostra)
X
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:
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:
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.
Por exemplo, estes dados claramente não podem ser bem descritos por uma linha reta
(Figura 5-35):
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):
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:
[[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):
Figura 5-37. Um ajuste linear para recursos polinomiais derivados dos dados
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:
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 :
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 :
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:
Para agilizar esse tipo de pipeline de processamento, o Scikit-Learn fornece um objeto pipeline, que
pode ser usado da seguinte forma:
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.
print(model.predict(X))
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.
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.
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:
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):
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.
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.
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);
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 :
É 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.
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:
dados = fetch_20newsgroups()
dados.target_names
Em [8]:
categorias = ['talk.religion.misc', 'soc.religion.christian', 'sci.space', 'comp.graphics']
Em[9]: imprimir(train.data[5])
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.
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:
Com este pipeline, podemos aplicar o modelo aos dados de treinamento e prever rótulos para os
dados de teste:
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
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:
Vamos experimentar:
Fora[15]: 'soc.religion.christian'
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.
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:
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:
• 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.
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:
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):
Podemos usar o estimador LinearRegression do Scikit-Learn para ajustar esses dados e construir a
linha de melhor ajuste (Figura 5-43):
modelo.fit(x[:, np.newaxis], y)
plt.dispersão(x, y)
plt.plot(xfit, yfit);
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_:
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:
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.
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
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
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.
Esta projeção polinomial é útil o suficiente para ser incorporada ao Scikit-Learn, usando o transformador
PolynomialFeatures :
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:
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):
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!
É 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.
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
@staticmethod def
_gauss_basis(x, y, largura, eixo=Nenhum): arg = (x - y) /
largura return np.exp(-0,5 *
np.sum(arg ** 2, eixo))
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):
plt.scatter(x, y)
plt.plot(xfit, model.predict(xfit[:, np.newaxis]))
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):
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))
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.
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):
Figura 5-49. Regularização Ridge (L2 ) aplicada ao modelo excessivamente complexo (compare com a
Figura 5-48)
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):
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).
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.
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:
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:
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):
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):
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:
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
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):
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:
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:
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
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!
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:
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):
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!
plt.xlim(-1, 3,5);
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.
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);
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.
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):
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):
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_
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):
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)
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.
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):
Figura 5-58. O primeiro quadro da visualização interativa do SVM (veja o apêndice online para a
versão completa)
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):
clf = SVC(kernel='linear').fit(X, y)
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:
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):
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):
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.
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):
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:
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).
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:
Vamos representar graficamente algumas dessas faces para ver com o que estamos trabalhando (Figura 5-64):
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:
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:
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:
Vamos dar uma olhada em algumas das imagens de teste junto com seus valores previstos
(Figura 5-65):
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:
Poderíamos também exibir a matriz de confusão entre essas classes (Figura 5.66):
Isso nos ajuda a ter uma noção de quais rótulos provavelmente serão confundidos pelo estimador.
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.
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.
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.
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.
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.
Considere os seguintes dados bidimensionais, que possuem um dos quatro rótulos de classe
(Figura 5-68):
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');
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.
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 :
Vamos escrever uma função utilitária rápida para nos ajudar a visualizar a saída do classificador:
# 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)
ax.set(xlim=xlim, ylim=ylim)
Em[5]: visualize_classifier(DecisionTreeClassifier(), X, y)
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):
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
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.
É 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):
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.
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):
bolsa.fit(X, y)
visualize_classifier(bolsa, X, y)
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.
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.
Considere os seguintes dados, extraídos da combinação de uma oscilação rápida e lenta (Figura 5-76):
y = modelo(x)
plt.errorbar(x, y, 0,3, fmt='o');
Usando o regressor de floresta aleatório, podemos encontrar a curva de melhor ajuste da seguinte forma
(Figura 5-77):
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!
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.
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)
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
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.
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() ).
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.
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:
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):
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.
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[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):
# dados do gráfico
Se representarmos graficamente esses componentes principais ao lado dos dados originais, veremos os gráficos
mostrados na Figura 5-82.
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.
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:
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):
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.
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.
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:
(1797, 64)
(1797, 2)
Podemos agora representar graficamente os dois primeiros componentes principais de cada ponto para aprender
sobre os dados (Figura 5-84):
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.
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:
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!
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:
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.
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):
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.
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.
Vamos ver como isso fica com os dados dos dígitos. Primeiro, plotaremos vários dados de entrada
sem ruído (Figura 5-88):
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)
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:
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):
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:
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
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:
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:
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');
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)
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.
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.
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.
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.
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:
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)
X = X[máscara]
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');
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):
X2 = girar(X, 20) + 5
plt.scatter(X2[:, 0], X2[:, 1], **colorir) plt.axis('equal');
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:
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):
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.
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):
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):
ax.view_init(azim=70, elev=50)
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):
Figura 5-99. A incorporação MDS dos dados tridimensionais recupera a entrada até uma rotação
e reflexão
certos relacionamentos dentro dos dados. No caso do MDS, a quantidade preservada é a distância
entre cada par de pontos.
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:
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):
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”.
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):
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.
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.
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);
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!
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.
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.
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
Se você estiver interessado em entender como isso funciona, sugiro executar cada um dos métodos nos
dados desta seção.
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:
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):
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):
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.
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:
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:
proj = model.fit_transform(dados)
ax.plot(proj[:, 0], proj[:, 1], '.k')
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 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:
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):
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):
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);
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):
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.
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
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:
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:
• 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):
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:
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):
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
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.
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.
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.
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):
enquanto Verdadeiro:
centros = novos_centros
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.
expectativa Há algumas questões que você deve conhecer ao usar o algoritmo de maximização
de expectativa.
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).
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 ).
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):
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.
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:
Agora podemos verificar a precisão do nosso agrupamento não supervisionado em encontrar dígitos
semelhantes nos dados:
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):
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:
# 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.
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):
Em[19]: china.shape