Explorando algoritmos de aprendizado de máquina supervisionado
Publicados: 2022-03-11O principal objetivo desta leitura é entender a metodologia estatística suficiente para poder alavancar os algoritmos de aprendizado de máquina na biblioteca scikit-learn do Python e, em seguida, aplicar esse conhecimento para resolver um problema clássico de aprendizado de máquina.
A primeira parada de nossa jornada nos levará a uma breve história do aprendizado de máquina. Então vamos mergulhar em diferentes algoritmos. Em nossa parada final, usaremos o que aprendemos para resolver o problema de previsão da taxa de sobrevivência do Titanic.
Algumas isenções de responsabilidade:
- Sou um engenheiro de software full-stack, não um especialista em algoritmos de aprendizado de máquina.
- Suponho que você conheça um pouco de Python básico.
- Isso é exploratório, portanto, nem todos os detalhes são explicados como em um tutorial.
Com isso anotado, vamos mergulhar!
Uma rápida introdução aos algoritmos de aprendizado de máquina
Assim que você se aventura nesse campo, percebe que o aprendizado de máquina é menos romântico do que você imagina. Inicialmente, eu estava cheio de esperanças de que, depois de aprender mais, seria capaz de construir meu próprio Jarvis AI, que passaria o dia todo codificando software e ganhando dinheiro para mim, para que eu pudesse passar dias inteiros ao ar livre lendo livros, dirigindo uma motocicleta, e desfrutando de um estilo de vida imprudente enquanto meu Jarvis pessoal faz meus bolsos mais fundo. No entanto, logo percebi que a base dos algoritmos de aprendizado de máquina são as estatísticas, que pessoalmente acho chatas e desinteressantes. Felizmente, as estatísticas “chatas” têm algumas aplicações muito fascinantes.
Você logo descobrirá que para chegar a essas aplicações fascinantes, você precisa entender muito bem as estatísticas. Um dos objetivos dos algoritmos de aprendizado de máquina é encontrar dependências estatísticas nos dados fornecidos.
Os dados fornecidos podem ser qualquer coisa, desde verificar a pressão arterial em relação à idade até encontrar texto manuscrito com base na cor de vários pixels.
Dito isto, eu estava curioso para ver se eu poderia usar algoritmos de aprendizado de máquina para encontrar dependências em funções de hash criptográficas (SHA, MD5, etc.) que eles eliminem dependências e produzam resultados significativamente difíceis de prever. Acredito que, com uma quantidade infinita de tempo, os algoritmos de aprendizado de máquina podem quebrar qualquer modelo de criptografia.
Infelizmente, não temos muito tempo, então precisamos encontrar outra maneira de minerar criptomoedas com eficiência. Até onde chegamos até agora?
Uma breve história dos algoritmos de aprendizado de máquina
As raízes dos algoritmos de aprendizado de máquina vêm de Thomas Bayes, um estatístico inglês que viveu no século XVIII. Seu artigo An Essay Towards Solving a Problem in the Doctrine of Chances sustenta o Teorema de Bayes, que é amplamente aplicado no campo da estatística.
No século 19, Pierre-Simon Laplace publicou Theorie analytique des probabilites , expandindo o trabalho de Bayes e definindo o que conhecemos hoje como Teorema de Bayes. Pouco antes disso, Adrien-Marie Legendre havia descrito o método dos “mínimos quadrados”, também amplamente utilizado hoje em aprendizado supervisionado.
O século 20 é o período em que a maioria das descobertas conhecidas publicamente foram feitas neste campo. Andrey Markov inventou as cadeias de Markov, que ele usou para analisar poemas. Alan Turing propôs uma máquina de aprendizado que poderia se tornar artificialmente inteligente, basicamente prenunciando algoritmos genéticos. Frank Rosenblatt inventou o Perceptron , provocando grande entusiasmo e grande cobertura na mídia.
Mas então a década de 1970 viu muito pessimismo em torno da ideia de IA – e, portanto, financiamento reduzido – então esse período é chamado de inverno de IA . A redescoberta da retropropagação na década de 1980 causou um ressurgimento na pesquisa de aprendizado de máquina. E hoje, é um tema quente mais uma vez.
O falecido Leo Breiman distinguiu entre dois paradigmas de modelagem estatística: modelagem de dados e modelagem algorítmica. “Modelagem algorítmica” significa mais ou menos os algoritmos de aprendizado de máquina como a floresta aleatória .
Aprendizado de máquina e estatística são campos intimamente relacionados. De acordo com Michael I. Jordan, as ideias de aprendizado de máquina, desde os princípios metodológicos até as ferramentas teóricas, têm uma longa pré-história na estatística. Ele também sugeriu ciência de dados como um termo de reserva para o problema geral em que especialistas em aprendizado de máquina e estatísticos estão trabalhando implicitamente.
Categorias de algoritmos de aprendizado de máquina
O campo de aprendizado de máquina se baseia em dois pilares principais chamados aprendizado supervisionado e aprendizado não supervisionado . Algumas pessoas também consideram um novo campo de estudo – aprendizado profundo – separado da questão do aprendizado supervisionado versus não supervisionado.
A aprendizagem supervisionada é quando um computador é apresentado com exemplos de entradas e suas saídas desejadas. O objetivo do computador é aprender uma fórmula geral que mapeia entradas para saídas. Isso pode ser ainda dividido em:
- Aprendizado semi-supervisionado , que é quando o computador recebe um conjunto de treinamento incompleto com algumas saídas ausentes
- Aprendizagem ativa , que é quando o computador só pode obter rótulos de treinamento para um conjunto muito limitado de instâncias. Quando usados interativamente, seus conjuntos de treinamento podem ser apresentados ao usuário para rotulagem.
- Aprendizado por reforço , que é quando os dados de treinamento são dados apenas como feedback às ações do programa no ambiente dinâmico, como dirigir um veículo ou jogar uma partida contra um oponente
Em contraste, o aprendizado não supervisionado ocorre quando nenhum rótulo é fornecido e cabe ao algoritmo encontrar a estrutura em sua entrada. O aprendizado não supervisionado pode ser um objetivo em si mesmo quando só precisamos descobrir padrões ocultos.
O aprendizado profundo é um novo campo de estudo inspirado na estrutura e função do cérebro humano e baseado em redes neurais artificiais, em vez de apenas conceitos estatísticos. O aprendizado profundo pode ser usado em abordagens supervisionadas e não supervisionadas.
Neste artigo, analisaremos apenas alguns dos algoritmos de aprendizado de máquina supervisionados mais simples e os usaremos para calcular as chances de sobrevivência de um indivíduo no trágico naufrágio do Titanic. Mas, em geral, se você não tiver certeza de qual algoritmo usar, um bom lugar para começar é a folha de dicas do algoritmo de aprendizado de máquina do scikit-learn.
Modelos básicos de aprendizado de máquina supervisionado
Talvez o algoritmo mais fácil possível seja a regressão linear. Às vezes, isso pode ser representado graficamente como uma linha reta, mas, apesar do nome, se houver uma hipótese polinomial, essa linha pode ser uma curva. De qualquer forma, ele modela as relações entre a variável dependente escalar $y$ e um ou mais valores explicativos denotados por $x$.
Em termos leigos, isso significa que a regressão linear é o algoritmo que aprende a dependência entre cada $x$ e $y$ conhecido, de modo que mais tarde podemos usá-lo para prever $y$ para uma amostra desconhecida de $x$.
Em nosso primeiro exemplo de aprendizado supervisionado, usaremos um modelo básico de regressão linear para prever a pressão arterial de uma pessoa de acordo com sua idade. Este é um conjunto de dados muito simples com duas características significativas: idade e pressão arterial.
Como já mencionado acima, a maioria dos algoritmos de aprendizado de máquina funciona encontrando uma dependência estatística nos dados fornecidos a eles. Essa dependência é chamada de hipótese e geralmente é denotada por $h(\theta)$.
Para descobrir a hipótese, vamos começar carregando e explorando os dados.
import matplotlib.pyplot as plt from pandas import read_csv import os # Load data data_path = os.path.join(os.getcwd(), "data/blood-pressure.txt") dataset = read_csv(data_path, delim_whitespace=True) # We have 30 entries in our dataset and four features. The first feature is the ID of the entry. # The second feature is always 1. The third feature is the age and the last feature is the blood pressure. # We will now drop the ID and One feature for now, as this is not important. dataset = dataset.drop(['ID', 'One'], axis=1) # And we will display this graph %matplotlib inline dataset.plot.scatter(x='Age', y='Pressure') # Now, we will assume that we already know the hypothesis and it looks like a straight line h = lambda x: 84 + 1.24 * x # Let's add this line on the chart now ages = range(18, 85) estimated = [] for i in ages: estimated.append(h(i)) plt.plot(ages, estimated, 'b')
[<matplotlib.lines.Line2D at 0x11424b828>]
No gráfico acima, cada ponto azul representa nossa amostra de dados e a linha azul é a hipótese que nosso algoritmo precisa aprender. Então, o que exatamente é essa hipótese?
Para resolver este problema, precisamos aprender a dependência entre $x$ e $y$, que é denotada por $y = f(x)$. Portanto $f(x)$ é a função alvo ideal. O algoritmo de aprendizado de máquina tentará adivinhar a função de hipótese $h(x)$ que é a aproximação mais próxima da desconhecida $f(x)$.
A forma mais simples possível de hipótese para o problema de regressão linear é assim: $h_\theta(x) = \theta_0 + \theta_1 * x$. Temos uma única variável escalar de entrada $x$ que gera uma única variável escalar $y$, onde $\theta_0$ e $\theta_1$ são parâmetros que precisamos aprender. O processo de ajuste dessa linha azul nos dados é chamado de regressão linear. É importante entender que temos apenas um parâmetro de entrada $x_1$; no entanto, muitas funções de hipótese também incluirão a unidade de viés ($x_0$). Portanto, nossa hipótese resultante tem uma forma de $h_\theta(x) = \theta_0 * x_0 + \theta_1 * x_1$. Mas podemos evitar escrever $x_0$ porque quase sempre é igual a 1.
Voltando à linha azul. Nossa hipótese se parece com $h(x) = 84 + 1,24x$, o que significa que $\theta_0 = 84$ e $\theta_1 = 1,24$. Como podemos derivar automaticamente esses valores $\theta$?
Precisamos definir uma função de custo . Essencialmente, o que a função de custo faz é simplesmente calcular a raiz quadrada média do erro entre a previsão do modelo e a saída real.
\[J(\theta) = \frac{1}{2m}\sum_{i=1}^m(h_\theta(x^{(i)}) - y^{(i)})^2\ ]Por exemplo, nossa hipótese prevê que, para alguém com 48 anos, sua pressão arterial deve ser $h(48) = 84 + 1,24 * 48 = 143mmHg$; porém, em nossa amostra de treinamento, temos o valor de $130 mmHg$. Portanto, o erro é $(143 - 130)^2 = 169$. Agora precisamos calcular esse erro para cada entrada em nosso conjunto de dados de treinamento e somar ($\sum_{i=1}^m(h_\theta(x^{(i)}) - y^{(i) )})^2$) e tire o valor médio disso.
Isso nos dá um único número escalar que representa o custo da função. Nosso objetivo é encontrar valores de $\theta$ tais que a função de custo seja a mais baixa; em outras palavras, queremos minimizar a função de custo. Esperamos que isso pareça intuitivo: se tivermos um valor de função de custo pequeno, isso significa que o erro de previsão também é pequeno.
import numpy as np # Let's calculate the cost for the hypothesis above h = lambda x, theta_0, theta_1: theta_0 + theta_1 * x def cost(X, y, t0, t1): m = len(X) # the number of the training samples c = np.power(np.subtract(h(X, t0, t1), y), 2) return (1 / (2 * m)) * sum(c) X = dataset.values[:, 0] y = dataset.values[:, 1] print('J(Theta) = %2.2f' % cost(X, y, 84, 1.24))
J(Theta) = 1901.95
Agora, precisamos encontrar esses valores de $\theta$ de modo que o valor da nossa função de custo seja mínimo. Mas como nós fazemos isso?
\[minJ(\theta) = \frac{1}{2m}\sum_{i=1}^m(h_\theta(x^{(i)}) - y^{(i)})^2\ ]Existem vários algoritmos possíveis, mas o mais popular é o gradiente descendente . Para entender a intuição por trás do método gradiente descendente, vamos primeiro plotá-lo no gráfico. Por uma questão de simplicidade, vamos assumir uma hipótese mais simples $h(\theta) = \theta_1 * x$. Em seguida, vamos traçar um gráfico 2D simples onde $x$ é o valor de $\theta$ e $y$ é a função de custo neste ponto.
import matplotlib.pyplot as plt fig = plt.figure() # Generate the data theta_1 = np.arange(-10, 14, 0.1) J_cost = [] for t1 in theta_1: J_cost += [ cost(X, y, 0, t1) ] plt.plot(theta_1, J_cost) plt.xlabel(r'$\theta_1$') plt.ylabel(r'$J(\theta)$') plt.show()
A função de custo é convexa, o que significa que no intervalo $[a, b]$ existe apenas um mínimo. O que novamente significa que os melhores parâmetros $\theta$ estão no ponto em que a função de custo é mínima.
Basicamente, gradiente descendente é um algoritmo que tenta encontrar o conjunto de parâmetros que minimizam a função. Ele começa com um conjunto inicial de parâmetros e iterativamente dá passos na direção negativa do gradiente da função.
Se calcularmos a derivada de uma função de hipótese em um ponto específico, isso nos dará uma inclinação da linha tangente à curva naquele ponto. Isso significa que podemos calcular a inclinação em cada ponto do gráfico.
A forma como o algoritmo funciona é esta:
- Escolhemos um ponto de partida aleatório (aleatório $\theta$).
- Calcule a derivada da função de custo neste ponto.
- Dê o pequeno passo em direção à inclinação $\theta_j := \theta_j - \lambda * \frac{\partial}{\partial \theta_j} * J(\theta)$.
- Repita os passos 2-3 até convergir.
Agora, a condição de convergência depende da implementação do algoritmo. Podemos parar após 50 passos, após algum limite ou qualquer outra coisa.
import math # Example of the simple gradient descent algorithm taken from Wikipedia cur_x = 2.5 # The algorithm starts at point x gamma = 0.005 # Step size multiplier precision = 0.00001 previous_step_size = cur_x df = lambda x: 2 * x * math.cos(x) # Remember the learning curve and plot it while previous_step_size > precision: prev_x = cur_x cur_x += -gamma * df(prev_x) previous_step_size = abs(cur_x - prev_x) print("The local minimum occurs at %f" % cur_x)
The local minimum occurs at 4.712194
Não implementaremos esses algoritmos neste artigo. Em vez disso, utilizaremos o scikit-learn
amplamente adotado, uma biblioteca de aprendizado de máquina Python de código aberto. Ele fornece muitas APIs muito úteis para diferentes problemas de mineração de dados e aprendizado de máquina.
from sklearn.linear_model import LinearRegression # LinearRegression uses the gradient descent method # Our data X = dataset[['Age']] y = dataset[['Pressure']] regr = LinearRegression() regr.fit(X, y) # Plot outputs plt.xlabel('Age') plt.ylabel('Blood pressure') plt.scatter(X, y, color='black') plt.plot(X, regr.predict(X), color='blue') plt.show() plt.gcf().clear()
<matplotlib.figure.Figure at 0x120fae1d0>
print( 'Predicted blood pressure at 25 yo = ', regr.predict(25) ) print( 'Predicted blood pressure at 45 yo = ', regr.predict(45) ) print( 'Predicted blood pressure at 27 yo = ', regr.predict(27) ) print( 'Predicted blood pressure at 34.5 yo = ', regr.predict(34.5) ) print( 'Predicted blood pressure at 78 yo = ', regr.predict(78) )
Predicted blood pressure at 25 yo = [[ 122.98647692]] Predicted blood pressure at 45 yo = [[ 142.40388395]] Predicted blood pressure at 27 yo = [[ 124.92821763]] Predicted blood pressure at 34.5 yo = [[ 132.20974526]] Predicted blood pressure at 78 yo = [[ 174.44260555]]
Tipos de dados estatísticos
Ao trabalhar com dados para problemas de aprendizado de máquina, é importante reconhecer diferentes tipos de dados. Podemos ter dados numéricos (contínuos ou discretos), categóricos ou ordinais.
Os dados numéricos têm significado como medida. Por exemplo, idade, peso, número de bitcoins que uma pessoa possui ou quantos artigos a pessoa pode escrever por mês. Os dados numéricos podem ser divididos em tipos discretos e contínuos.
- Dados discretos representam dados que podem ser contados com números inteiros, por exemplo, número de quartos em um apartamento ou número de lançamentos de moedas.
- Dados contínuos não podem necessariamente ser representados com números inteiros. Por exemplo, se você estiver medindo a distância que pode saltar, pode ser 2 metros, ou 1,5 metros, ou 1,652245 metros.
Os dados categóricos representam valores como sexo da pessoa, estado civil, país, etc. Esses dados podem ter valor numérico, mas esses números não têm significado matemático. Você não pode adicioná-los juntos.
Os dados ordinais podem ser uma mistura dos outros dois tipos, em que as categorias podem ser numeradas de maneira matematicamente significativa. Um exemplo comum são as classificações: muitas vezes nos pedem para classificar as coisas em uma escala de um a dez, e apenas números inteiros são permitidos. Embora possamos usar isso numericamente – por exemplo, para encontrar uma classificação média para algo – geralmente tratamos os dados como se fossem categóricos quando se trata de aplicar métodos de aprendizado de máquina a eles.
Regressão Logística
A regressão linear é um algoritmo incrível que nos ajuda a prever valores numéricos, por exemplo, o preço da casa com o tamanho específico e o número de quartos. No entanto, às vezes, também podemos querer prever dados categóricos, para obter respostas a perguntas como:
- Isso é um cachorro ou um gato?
- Este tumor é maligno ou benigno?
- Esse vinho é bom ou ruim?
- Este e-mail é spam ou não?
Ou ainda:
- Qual número está na imagem?
- A qual categoria este e-mail pertence?
Todas essas questões são específicas do problema de classificação . E o algoritmo de classificação mais simples é chamado de regressão logística , que eventualmente é o mesmo que regressão linear , exceto que tem uma hipótese diferente.
Em primeiro lugar, podemos reutilizar a mesma hipótese linear $h_\theta(x) = \theta^TX$ (esta na forma vetorizada). Enquanto a regressão linear pode produzir qualquer número no intervalo $[a, b]$, a regressão logística só pode produzir valores em $[−1, 1]$, que é a probabilidade do objeto cair em uma determinada categoria ou não.
Usando uma função sigmóide , podemos converter qualquer valor numérico para representar um valor no intervalo $[−1, 1]$.
\[f(x) = \frac{1}{1 + e^x}\]Agora, em vez de $x$, precisamos passar uma hipótese existente e, portanto, obteremos:
\[f(x) = \frac{1}{1 + e^{\theta_0 + \theta_1 * x_1 + ... + \theta_n * x_n}}\]Depois disso, podemos aplicar um limiar simples dizendo que se a hipótese for maior que zero, este é um valor verdadeiro, caso contrário, falso.
\[h_\theta(x) = \begin{cases} 1 & \mbox{if } \theta^TX > 0 \\ 0 & \mbox{else} \end{cases}\]Isso significa que podemos usar a mesma função de custo e o mesmo algoritmo de gradiente descendente para aprender uma hipótese para regressão logística.
Em nosso próximo exemplo de algoritmo de aprendizado de máquina, aconselharemos os pilotos do ônibus espacial se devem ou não usar o controle de pouso automático ou manual. Temos um conjunto de dados muito pequeno - 15 amostras - que consiste em seis recursos e a verdade geral .
Em algoritmos de aprendizado de máquina, o termo “ verdade do terreno ” refere-se à precisão da classificação do conjunto de treinamento para técnicas de aprendizado supervisionado.
Nosso conjunto de dados está completo, o que significa que não há recursos ausentes; no entanto, alguns dos recursos têm um “*” em vez da categoria, o que significa que esse recurso não importa. Substituiremos todos esses asteriscos por zeros.
from sklearn.linear_model import LogisticRegression # Data data_path = os.path.join(os.getcwd(), "data/shuttle-landing-control.csv") dataset = read_csv(data_path, header=None, names=['Auto', 'Stability', 'Error', 'Sign', 'Wind', 'Magnitude', 'Visibility'], na_values='*').fillna(0) # Prepare features X = dataset[['Stability', 'Error', 'Sign', 'Wind', 'Magnitude', 'Visibility']] y = dataset[['Auto']].values.reshape(1, -1)[0] model = LogisticRegression() model.fit(X, y) # For now, we're missing one important concept. We don't know how well our model # works, and because of that, we cannot really improve the performance of our hypothesis. # There are a lot of useful metrics, but for now, we will validate how well # our algorithm performs on the dataset it learned from. "Score of our model is %2.2f%%" % (model.score(X, y) * 100)
Score of our model is 73.33%
Validação?
No exemplo anterior, validamos o desempenho do nosso modelo usando os dados de aprendizado. No entanto, essa agora é uma boa opção, já que nosso algoritmo pode ser subajustado ou superajustado aos dados? Vamos dar uma olhada no exemplo mais simples quando temos uma característica que representa o tamanho de uma casa e outra que representa seu preço.
from sklearn.pipeline import make_pipeline from sklearn.preprocessing import PolynomialFeatures from sklearn.linear_model import LinearRegression from sklearn.model_selection import cross_val_score # Ground truth function ground_truth = lambda X: np.cos(15 + np.pi * X) # Generate random observations around the ground truth function n_samples = 15 degrees = [1, 4, 30] X = np.linspace(-1, 1, n_samples) y = ground_truth(X) + np.random.randn(n_samples) * 0.1 plt.figure(figsize=(14, 5)) models = {} # Plot all machine learning algorithm models for idx, degree in enumerate(degrees): ax = plt.subplot(1, len(degrees), idx + 1) plt.setp(ax, xticks=(), yticks=()) # Define the model polynomial_features = PolynomialFeatures(degree=degree) model = make_pipeline(polynomial_features, LinearRegression()) models[degree] = model # Train the model model.fit(X[:, np.newaxis], y) # Evaluate the model using cross-validation scores = cross_val_score(model, X[:, np.newaxis], y) X_test = X plt.plot(X_test, model.predict(X_test[:, np.newaxis]), label="Model") plt.scatter(X, y, edgecolor='b', s=20, label="Observations") plt.xlabel("x") plt.ylabel("y") plt.ylim((-2, 2)) plt.title("Degree {}\nMSE = {:.2e}".format( degree, -scores.mean())) plt.show()

O modelo de algoritmo de aprendizado de máquina está subajustado se não puder generalizar nem os dados de treinamento nem as novas observações. No exemplo acima, usamos uma hipótese linear simples que não representa realmente o conjunto de dados de treinamento real e terá um desempenho muito ruim. Normalmente, o underfitting não é discutido, pois pode ser facilmente detectado com uma boa métrica.
Se nosso algoritmo se lembrar de cada observação que foi mostrada, ele terá um desempenho ruim em novas observações fora do conjunto de dados de treinamento. Isso é chamado de sobreajuste . Por exemplo, um modelo polinomial de 30º grau passa pela maioria dos pontos e tem uma pontuação muito boa no conjunto de treinamento, mas qualquer coisa fora disso teria um desempenho ruim.
Nosso conjunto de dados consiste em um recurso e é simples de plotar no espaço 2D; no entanto, na vida real, podemos ter conjuntos de dados com centenas de recursos, o que os torna impossíveis de plotar visualmente no espaço euclidiano. Que outras opções temos para ver se o modelo está underfitting ou overfitting?
É hora de apresentá-lo ao conceito da curva de aprendizado . Este é um gráfico simples que plota o erro quadrático médio sobre o número de amostras de treinamento.
Nos materiais de aprendizagem, você geralmente verá gráficos semelhantes a estes:
No entanto, na vida real, você pode não obter uma imagem tão perfeita. Vamos traçar a curva de aprendizado para cada um de nossos modelos.
from sklearn.model_selection import learning_curve, ShuffleSplit # Plot learning curves plt.figure(figsize=(20, 5)) for idx, degree in enumerate(models): ax = plt.subplot(1, len(degrees), idx + 1) plt.title("Degree {}".format(degree)) plt.grid() plt.xlabel("Training examples") plt.ylabel("Score") train_sizes = np.linspace(.6, 1.0, 6) # Cross-validation with 100 iterations to get a smoother mean test and training # score curves, each time with 20% of the data randomly selected as a validation set. cv = ShuffleSplit(n_splits=100, test_size=0.2, random_state=0) model = models[degree] train_sizes, train_scores, test_scores = learning_curve( model, X[:, np.newaxis], y, cv=cv, train_sizes=train_sizes, n_jobs=4) train_scores_mean = np.mean(train_scores, axis=1) test_scores_mean = np.mean(test_scores, axis=1) plt.plot(train_sizes, train_scores_mean, 'o-', color="r", label="Training score") plt.plot(train_sizes, test_scores_mean, 'o-', color="g", label="Test score") plt.legend(loc = "best") plt.show()
Em nosso cenário simulado, a linha azul, que representa a pontuação do treinamento, parece uma linha reta. Na realidade, ainda diminui um pouco - você pode ver isso no gráfico polinomial de primeiro grau, mas nos outros é muito sutil para dizer nesta resolução. Pelo menos vemos claramente que há uma enorme lacuna entre as curvas de aprendizado para treinamento e observações de teste com um cenário de “alto viés”.
No gráfico de taxa de aprendizado “normal” no meio, você pode ver como a pontuação do treinamento e as linhas de pontuação do teste se unem.
E no gráfico de “alta variância”, você pode ver que com um número baixo de amostras, as pontuações do teste e do treinamento são muito semelhantes; no entanto, quando você aumenta o número de amostras, a pontuação do treinamento permanece quase perfeita enquanto a pontuação do teste aumenta.
Podemos corrigir modelos de underfitting (também chamados de modelos com viés alto ) se usarmos uma hipótese não linear, por exemplo, a hipótese com mais características polinomiais.
Nosso modelo de sobreajuste ( alta variância ) passa por todos os exemplos mostrados; no entanto, quando introduzimos dados de teste, a lacuna entre as curvas de aprendizado aumenta. Podemos usar regularização, validação cruzada e mais amostras de dados para corrigir modelos de overfitting.
Validação cruzada
Uma das práticas comuns para evitar o overfitting é manter parte dos dados disponíveis e usá-los como um conjunto de teste. No entanto, ao avaliar diferentes configurações do modelo, como o número de recursos polinomiais, ainda corremos o risco de superajustar o conjunto de teste porque os parâmetros podem ser ajustados para alcançar o desempenho ideal do estimador e, por causa disso, nosso conhecimento sobre o conjunto de teste pode vazamento no modelo. Para resolver esse problema, precisamos manter mais uma parte do conjunto de dados, que é chamado de “conjunto de validação”. O treinamento prossegue no conjunto de treinamento e, quando achamos que alcançamos o desempenho ideal do modelo, podemos fazer uma avaliação final utilizando o conjunto de validação.
No entanto, ao particionar os dados disponíveis em três conjuntos, reduzimos drasticamente o número de amostras que podem ser usadas para treinar os modelos, e os resultados podem depender de uma escolha aleatória específica para o par de conjuntos de treinamento-validação.
Uma solução para este problema é um procedimento chamado validação cruzada. Na validação cruzada padrão $k$-fold, particionamos os dados em subconjuntos $k$, chamados folds. Em seguida, treinamos iterativamente o algoritmo em $k-1$ dobras enquanto usamos a dobra restante como o conjunto de teste (chamado de “dobra de retenção”).
A validação cruzada permite ajustar parâmetros apenas com seu conjunto de treinamento original. Isso permite que você mantenha seu conjunto de teste como um conjunto de dados realmente invisível para selecionar seu modelo final.
Existem muito mais técnicas de validação cruzada, como leave P out , stratified $k$-fold , shuffle and split , etc., mas elas estão além do escopo deste artigo.
Regularização
Esta é outra técnica que pode ajudar a resolver o problema do overfitting do modelo. A maioria dos conjuntos de dados tem um padrão e algum ruído. O objetivo da regularização é reduzir a influência do ruído no modelo.
Existem três técnicas principais de regularização: Lasso, Tikhonov e rede elástica.
A regularização L1 (ou regularização Lasso ) selecionará alguns recursos para serem reduzidos a zero, de modo que eles não desempenhem nenhum papel no modelo final. L1 pode ser visto como um método para selecionar características importantes.
A regularização L2 (ou regularização Tikhonov ) forçará todos os recursos a serem relativamente pequenos, de modo que fornecerão menos influência no modelo.
A rede elástica é a combinação de L1 e L2.
Normalização (escalonamento de recursos)
O dimensionamento de recursos também é uma etapa importante durante o pré-processamento dos dados. Nosso conjunto de dados pode ter recursos com valores $[-\infty, \infty]$ e outros recursos com escala diferente. Este é um método para padronizar os intervalos de valores independentes.
O dimensionamento de recursos também é um processo importante para melhorar o desempenho dos modelos de aprendizado. Em primeiro lugar, a descida do gradiente convergirá muito mais rápido se todos os recursos forem dimensionados para a mesma norma. Além disso, muitos algoritmos - por exemplo, máquinas de vetor de suporte (SVM) - funcionam calculando a distância entre dois pontos e, se um dos recursos tiver valores amplos, a distância será altamente influenciada por esse recurso.
Máquinas de vetor de suporte
O SVM é outro algoritmo de aprendizado de máquina amplamente popular que pode ser usado para problemas de classificação e regressão. No SVM, plotamos cada observação como um ponto no espaço $n$-dimensional onde $n$ é o número de feições que temos. O valor de cada recurso é o valor de determinadas coordenadas. Então, tentamos encontrar um hiperplano que separe duas classes suficientemente bem.
Após identificarmos o melhor hiperplano, queremos adicionar margens, o que separaria ainda mais as duas classes.
O SVM é muito eficaz onde o número de recursos é muito alto ou se o número de recursos for maior que o número de amostras de dados. No entanto, como o SVM opera em uma base vetorial, é crucial normalizar os dados antes do uso.
Redes neurais
Algoritmos de redes neurais são provavelmente o campo mais interessante de estudos de aprendizado de máquina. As redes neurais tentam imitar como os neurônios do cérebro estão conectados.
É assim que uma rede neural se parece. Combinamos muitos nós, onde cada nó recebe um conjunto de entradas, aplica alguns cálculos neles e gera um valor.
Há uma enorme variedade de algoritmos de redes neurais para aprendizado supervisionado e não supervisionado. As redes neurais podem ser usadas para dirigir carros autônomos, jogar, pousar aviões, classificar imagens e muito mais.
O infame Titanic
O RMS Titanic foi um navio de passageiros britânico que afundou no Oceano Atlântico Norte em 15 de abril de 1912 após colidir com um iceberg. Havia cerca de 2.224 tripulantes e passageiros, e mais de 1.500 morreram, tornando-se um dos desastres marítimos comerciais mais mortais de todos os tempos.
Agora, uma vez que entendemos a intuição por trás dos algoritmos de aprendizado de máquina mais básicos usados para problemas de classificação, podemos aplicar nosso conhecimento para prever o resultado de sobrevivência para aqueles a bordo do Titanic.
Nosso conjunto de dados será emprestado da plataforma de competições de ciência de dados Kaggle.
import os from pandas import read_csv, concat # Load data data_path = os.path.join(os.getcwd(), "data/titanic.csv") dataset = read_csv(data_path, skipinitialspace=True) dataset.head(5)
ID do passageiro | Sobreviveu | Classe P | Nome | Sexo | Idade | SibSp | Parch | Bilhete | Tarifa | Cabine | Embarcou | |
0 | 1 | 0 | 3 | Braund, Sr. Owen Harris | macho | 22,0 | 1 | 0 | A/5 21171 | 7,2500 | NaN | S |
1 | 2 | 1 | 1 | Cumings, Sra. John Bradley (Florence Briggs Th... | fêmea | 38,0 | 1 | 0 | PC 17599 | 71.2833 | C85 | C |
2 | 3 | 1 | 3 | Heikkinen, Srta. Laina | fêmea | 26,0 | 0 | 0 | STON/O2. 3101282 | 7,9250 | NaN | S |
3 | 4 | 1 | 1 | Futrelle, Sra. Jacques Heath (Lily May Peel) | fêmea | 35,0 | 1 | 0 | 113803 | 53,1000 | C123 | S |
4 | 5 | 0 | 3 | Allen, Sr. William Henry | macho | 35,0 | 0 | 0 | 373450 | 8,0500 | NaN | S |
Nosso primeiro passo seria carregar e explorar os dados. Temos 891 registros de testes; cada registro tem a seguinte estrutura:
- passageiroId – ID do passageiro a bordo
- sobrevivência - Se a pessoa sobreviveu ou não ao acidente
- pclass – Classe do bilhete, por exemplo, 1º, 2º, 3º
- sexo – Sexo do passageiro: Masculino ou feminino
- nome – Título incluído
- idade – idade em anos
- sibsp – Número de irmãos/cônjuges a bordo do Titanic
- parch – Número de pais/filhos a bordo do Titanic
- bilhete – número do bilhete
- tarifa – tarifa do passageiro
- cabine – número da cabine
- embarcado – Porto de embarque
Este conjunto de dados contém dados numéricos e categóricos. Normalmente, é uma boa ideia mergulhar mais fundo nos dados e, com base nisso, fazer suposições. No entanto, neste caso, vamos pular esta etapa e ir direto para as previsões.
import pandas as pd # We need to drop some insignificant features and map the others. # Ticket number and fare should not contribute much to the performance of our models. # Name feature has titles (eg, Mr., Miss, Doctor) included. # Gender is definitely important. # Port of embarkation may contribute some value. # Using port of embarkation may sound counter-intuitive; however, there may # be a higher survival rate for passengers who boarded in the same port. dataset['Title'] = dataset.Name.str.extract(' ([A-Za-z]+)\.', expand=False) dataset = dataset.drop(['PassengerId', 'Ticket', 'Cabin', 'Name'], axis=1) pd.crosstab(dataset['Title'], dataset['Sex'])
Title \ Sex | fêmea | macho |
Capt | 0 | 1 |
Col | 0 | 2 |
Countess | 1 | 0 |
vestir | 0 | 1 |
Dr. | 1 | 6 |
Jonkheer | 0 | 1 |
Senhora | 1 | 0 |
Principal | 0 | 2 |
Mestre | 0 | 40 |
Perde | 182 | 0 |
Mlle | 2 | 0 |
Mme | 1 | 0 |
Senhor | 0 | 517 |
Sra | 125 | 0 |
Ms | 1 | 0 |
Rev | 0 | 6 |
Sir | 0 | 1 |
# We will replace many titles with a more common name, English equivalent, # or reclassification dataset['Title'] = dataset['Title'].replace(['Lady', 'Countess','Capt', 'Col',\ 'Don', 'Dr', 'Major', 'Rev', 'Sir', 'Jonkheer', 'Dona'], 'Other') dataset['Title'] = dataset['Title'].replace('Mlle', 'Miss') dataset['Title'] = dataset['Title'].replace('Ms', 'Miss') dataset['Title'] = dataset['Title'].replace('Mme', 'Mrs') dataset[['Title', 'Survived']].groupby(['Title'], as_index=False).mean()
Título | Survived | |
0 | Mestre | 0.575000 |
1 | Perde | 0.702703 |
2 | Senhor | 0.156673 |
3 | Sra | 0.793651 |
4 | De outros | 0.347826 |
# Now we will map alphanumerical categories to numbers title_mapping = { 'Mr': 1, 'Miss': 2, 'Mrs': 3, 'Master': 4, 'Other': 5 } gender_mapping = { 'female': 1, 'male': 0 } port_mapping = { 'S': 0, 'C': 1, 'Q': 2 } # Map title dataset['Title'] = dataset['Title'].map(title_mapping).astype(int) # Map gender dataset['Sex'] = dataset['Sex'].map(gender_mapping).astype(int) # Map port freq_port = dataset.Embarked.dropna().mode()[0] dataset['Embarked'] = dataset['Embarked'].fillna(freq_port) dataset['Embarked'] = dataset['Embarked'].map(port_mapping).astype(int) # Fix missing age values dataset['Age'] = dataset['Age'].fillna(dataset['Age'].dropna().median()) dataset.head()
Survived | Pclass | Sexo | Idade | SibSp | Parch | Tarifa | Embarked | Título | |
0 | 0 | 3 | 0 | 22,0 | 1 | 0 | 7.2500 | 0 | 1 |
1 | 1 | 1 | 1 | 38,0 | 1 | 0 | 71.2833 | 1 | 3 |
2 | 1 | 3 | 1 | 26,0 | 0 | 0 | 7.9250 | 0 | 2 |
3 | 1 | 1 | 1 | 35,0 | 1 | 0 | 53.1000 | 0 | 3 |
4 | 0 | 3 | 0 | 35,0 | 0 | 0 | 8.0500 | 0 | 1 |
At this point, we will rank different types of machine learning algorithms in Python by using scikit-learn
to create a set of different models. It will then be easy to see which one performs the best.
- Logistic regression with varying numbers of polynomials
- Support vector machine with a linear kernel
- Support vector machine with a polynomial kernel
- Neural network
For every single model, we will use $k$-fold validation.
from sklearn.model_selection import KFold, train_test_split from sklearn.pipeline import make_pipeline from sklearn.preprocessing import PolynomialFeatures, StandardScaler from sklearn.neural_network import MLPClassifier from sklearn.svm import SVC # Prepare the data X = dataset.drop(['Survived'], axis = 1).values y = dataset[['Survived']].values X = StandardScaler().fit_transform(X) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state = None) # Prepare cross-validation (cv) cv = KFold(n_splits = 5, random_state = None) # Performance p_score = lambda model, score: print('Performance of the %s model is %0.2f%%' % (model, score * 100)) # Classifiers names = [ "Logistic Regression", "Logistic Regression with Polynomial Hypotheses", "Linear SVM", "RBF SVM", "Neural Net", ] classifiers = [ LogisticRegression(), make_pipeline(PolynomialFeatures(3), LogisticRegression()), SVC(kernel="linear", C=0.025), SVC(gamma=2, C=1), MLPClassifier(alpha=1), ]
# iterate over classifiers models = [] trained_classifiers = [] for name, clf in zip(names, classifiers): scores = [] for train_indices, test_indices in cv.split(X): clf.fit(X[train_indices], y[train_indices].ravel()) scores.append( clf.score(X_test, y_test.ravel()) ) min_score = min(scores) max_score = max(scores) avg_score = sum(scores) / len(scores) trained_classifiers.append(clf) models.append((name, min_score, max_score, avg_score)) fin_models = pd.DataFrame(models, columns = ['Name', 'Min Score', 'Max Score', 'Mean Score'])
fin_models.sort_values(['Mean Score']).head()
Nome | Min Score | Max Score | Mean Score | |
2 | Linear SVM | 0.793296 | 0.821229 | 0.803352 |
0 | Logistic Regression | 0.826816 | 0.860335 | 0.846927 |
4 | Rede Neural | 0.826816 | 0.860335 | 0.849162 |
1 | Logistic Regression with Polynomial Hypotheses | 0.854749 | 0.882682 | 0.869274 |
3 | RBF SVM | 0.843575 | 0.888268 | 0.869274 |
Ok, so our experimental research says that the SVM classifier with a radial basis function (RBF) kernel performs the best. Now, we can serialize our model and re-use it in production applications.
import pickle svm_model = trained_classifiers[3] data_path = os.path.join(os.getcwd(), "best-titanic-model.pkl") pickle.dump(svm_model, open(data_path, 'wb'))
Machine learning is not complicated, but it's a very broad field of study, and it requires knowledge of math and statistics in order to grasp all of its concepts.
Right now, machine learning and deep learning are among the hottest topics of discussion in Silicon Valley, and are the bread and butter of almost every data science company, mainly because they can automate many repetitive tasks including speech recognition, driving vehicles, financial trading, caring for patients, cooking, marketing, and so on.
Now you can take this knowledge and solve challenges on Kaggle.
This was a very brief introduction to supervised machine learning algorithms. Luckily, there are a lot of online courses and information about machine learning algorithms. I personally would recommend starting with Andrew Ng's course on Coursera.
Recursos
- Andrew Ng's course on Coursera
- Kaggle datasets
- A deep learning reading list
- A list of free books on machine learning algorithms, data mining, deep learning, and related topics
- Uma introdução à teoria do aprendizado de máquina e suas aplicações: um tutorial visual com exemplos