Exploración de algoritmos de aprendizaje automático supervisado
Publicado: 2022-03-11El objetivo principal de esta lectura es comprender suficiente metodología estadística para poder aprovechar los algoritmos de aprendizaje automático en la biblioteca scikit-learn de Python y luego aplicar este conocimiento para resolver un problema clásico de aprendizaje automático.
La primera parada de nuestro viaje nos llevará a través de una breve historia del aprendizaje automático. Luego nos sumergiremos en diferentes algoritmos. En nuestra última parada, usaremos lo que aprendimos para resolver el problema de predicción de la tasa de supervivencia del Titanic.
Algunas renuncias:
- Soy un ingeniero de software full-stack, no un experto en algoritmos de aprendizaje automático.
- Supongo que conoces algo básico de Python.
- Esto es exploratorio, por lo que no se explican todos los detalles como lo harían en un tutorial.
Con eso anotado, ¡vamos a sumergirnos!
Una introducción rápida a los algoritmos de aprendizaje automático
Tan pronto como te aventuras en este campo, te das cuenta de que el aprendizaje automático es menos romántico de lo que piensas. Inicialmente, estaba lleno de esperanzas de que, después de aprender más, podría construir mi propio Jarvis AI, que pasaría todo el día codificando software y ganando dinero para mí, así podría pasar días enteros al aire libre leyendo libros, conduciendo una motocicleta, y disfrutar de un estilo de vida imprudente mientras mi Jarvis personal hace que mis bolsillos sean más profundos. Sin embargo, pronto me di cuenta de que la base de los algoritmos de aprendizaje automático son las estadísticas, que personalmente encuentro aburridas y poco interesantes. Afortunadamente, resultó que las estadísticas "aburridas" tienen algunas aplicaciones fascinantes.
Pronto descubrirá que para llegar a esas fascinantes aplicaciones, necesita comprender muy bien las estadísticas. Uno de los objetivos de los algoritmos de aprendizaje automático es encontrar dependencias estadísticas en los datos suministrados.
Los datos proporcionados pueden ser cualquier cosa, desde comparar la presión arterial con la edad hasta encontrar texto escrito a mano según el color de varios píxeles.
Dicho esto, tenía curiosidad por ver si podía usar algoritmos de aprendizaje automático para encontrar dependencias en funciones hash criptográficas (SHA, MD5, etc.); sin embargo, realmente no puedes hacerlo porque las primitivas criptográficas adecuadas se construyen de tal manera que eliminan las dependencias y producen resultados significativamente difíciles de predecir. Creo que, dada una cantidad infinita de tiempo, los algoritmos de aprendizaje automático podrían descifrar cualquier modelo criptográfico.
Desafortunadamente, no tenemos tanto tiempo, por lo que debemos encontrar otra forma de extraer criptomonedas de manera eficiente. ¿Hasta dónde hemos llegado hasta ahora?
Una breve historia de los algoritmos de aprendizaje automático
Las raíces de los algoritmos de aprendizaje automático provienen de Thomas Bayes, un estadístico inglés que vivió en el siglo XVIII. Su artículo Un ensayo para resolver un problema en la doctrina de las posibilidades sustenta el teorema de Bayes, que se aplica ampliamente en el campo de la estadística.
En el siglo XIX, Pierre-Simon Laplace publicó Theorie analytique des probabilites , ampliando el trabajo de Bayes y definiendo lo que conocemos hoy como el Teorema de Bayes. Poco antes, Adrien-Marie Legendre había descrito el método de los “mínimos cuadrados”, también muy utilizado hoy en día en el aprendizaje supervisado.
El siglo XX es el período en el que la mayoría de los descubrimientos de conocimiento público se han realizado en este campo. Andrey Markov inventó las cadenas de Markov, que utilizó para analizar poemas. Alan Turing propuso una máquina de aprendizaje que podría volverse artificialmente inteligente, básicamente presagiando algoritmos genéticos. Frank Rosenblatt inventó el Perceptrón , lo que provocó un gran entusiasmo y una gran cobertura en los medios.
Pero luego, la década de 1970 vio mucho pesimismo en torno a la idea de la IA y, por lo tanto, una reducción de la financiación, por lo que este período se denomina invierno de la IA . El redescubrimiento de la retropropagación en la década de 1980 provocó un resurgimiento en la investigación del aprendizaje automático. Y hoy, es un tema candente una vez más.
El difunto Leo Breiman distinguió entre dos paradigmas de modelado estadístico: modelado de datos y modelado algorítmico. “Modelado algorítmico” significa más o menos los algoritmos de aprendizaje automático como el bosque aleatorio .
El aprendizaje automático y las estadísticas son campos estrechamente relacionados. Según Michael I. Jordan, las ideas del aprendizaje automático, desde los principios metodológicos hasta las herramientas teóricas, han tenido una larga prehistoria en las estadísticas. También sugirió la ciencia de datos como un término de marcador de posición para el problema general en el que los especialistas en aprendizaje automático y los estadísticos están trabajando implícitamente.
Categorías de algoritmos de aprendizaje automático
El campo del aprendizaje automático se basa en dos pilares principales llamados aprendizaje supervisado y aprendizaje no supervisado . Algunas personas también consideran que un nuevo campo de estudio , el aprendizaje profundo, está separado de la cuestión del aprendizaje supervisado frente al no supervisado.
El aprendizaje supervisado es cuando a una computadora se le presentan ejemplos de entradas y sus salidas deseadas. El objetivo de la computadora es aprender una fórmula general que asigna entradas a salidas. Esto se puede desglosar aún más en:
- Aprendizaje semisupervisado , que es cuando la computadora recibe un conjunto de entrenamiento incompleto y faltan algunos resultados.
- Aprendizaje activo , que es cuando la computadora solo puede obtener etiquetas de entrenamiento para un conjunto muy limitado de instancias. Cuando se usan de forma interactiva, sus conjuntos de entrenamiento se pueden presentar al usuario para que los etiquete.
- Aprendizaje por refuerzo , que es cuando los datos de entrenamiento solo se brindan como retroalimentación a las acciones del programa en el entorno dinámico, como conducir un vehículo o jugar un juego contra un oponente.
Por el contrario, el aprendizaje no supervisado es cuando no se asigna ninguna etiqueta y depende del algoritmo encontrar la estructura en su entrada. El aprendizaje no supervisado puede ser un objetivo en sí mismo cuando solo necesitamos descubrir patrones ocultos.
El aprendizaje profundo es un nuevo campo de estudio inspirado en la estructura y función del cerebro humano y basado en redes neuronales artificiales en lugar de solo conceptos estadísticos. El aprendizaje profundo se puede utilizar tanto en enfoques supervisados como no supervisados.
En este artículo, solo revisaremos algunos de los algoritmos de aprendizaje automático supervisado más simples y los usaremos para calcular las posibilidades de supervivencia de un individuo en el trágico hundimiento del Titanic. Pero, en general, si no está seguro de qué algoritmo usar, un buen lugar para comenzar es la hoja de trucos del algoritmo de aprendizaje automático de scikit-learn.
Modelos básicos de aprendizaje automático supervisado
Quizás el algoritmo más fácil posible es la regresión lineal. A veces, esto se puede representar gráficamente como una línea recta, pero a pesar de su nombre, si hay una hipótesis polinomial, esta línea podría ser una curva. De cualquier manera, modela las relaciones entre la variable dependiente escalar $y$ y uno o más valores explicativos indicados por $x$.
En términos sencillos, esto significa que la regresión lineal es el algoritmo que aprende la dependencia entre cada $x$ y $y$ conocidos, de modo que luego podemos usarlo para predecir $y$ para una muestra desconocida de $x$.
En nuestro primer ejemplo de aprendizaje supervisado, usaremos un modelo de regresión lineal básico para predecir la presión arterial de una persona dada su edad. Este es un conjunto de datos muy simple con dos características significativas: edad y presión arterial.
Como ya se mencionó anteriormente, la mayoría de los algoritmos de aprendizaje automático funcionan al encontrar una dependencia estadística en los datos que se les proporcionan. Esta dependencia se llama hipótesis y generalmente se denota por $h(\theta)$.
Para averiguar la hipótesis, comencemos cargando y explorando los datos.
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>]
En el gráfico anterior, cada punto azul representa nuestra muestra de datos y la línea azul es la hipótesis que nuestro algoritmo necesita aprender. Entonces, ¿qué es exactamente esta hipótesis de todos modos?
Para resolver este problema, necesitamos aprender la dependencia entre $x$ y $y$, que se denota por $y = f(x)$. Por lo tanto, $f(x)$ es la función objetivo ideal. El algoritmo de aprendizaje automático intentará adivinar la función de hipótesis $h(x)$ que es la aproximación más cercana a la desconocida $f(x)$.
La forma más simple posible de hipótesis para el problema de regresión lineal se ve así: $h_\theta(x) = \theta_0 + \theta_1 * x$. Tenemos una sola variable escalar de entrada $x$ que genera una sola variable escalar $y$, donde $\theta_0$ y $\theta_1$ son parámetros que necesitamos aprender. El proceso de ajustar esta línea azul en los datos se llama regresión lineal. Es importante entender que solo tenemos un parámetro de entrada $x_1$; sin embargo, muchas funciones de hipótesis también incluirán la unidad de sesgo ($x_0$). Así que nuestra hipótesis resultante tiene la forma de $h_\theta(x) = \theta_0 * x_0 + \theta_1 * x_1$. Pero podemos evitar escribir $x_0$ porque casi siempre es igual a 1.
Volviendo a la línea azul. Nuestra hipótesis parece $h(x) = 84 + 1,24x$, lo que significa que $\theta_0 = 84$ y $\theta_1 = 1,24$. ¿Cómo podemos derivar automáticamente esos valores $\theta$?
Necesitamos definir una función de costo . Esencialmente, lo que hace la función de costo es simplemente calcular el error cuadrático medio entre la predicción del modelo y la salida real.
\[J(\theta) = \frac{1}{2m}\sum_{i=1}^m(h_\theta(x^{(i)}) - y^{(i)})^2\ ]Por ejemplo, nuestra hipótesis predice que para alguien de 48 años, su presión arterial debería ser $h(48) = 84 + 1,24 * 48 = 143 mmHg$; sin embargo, en nuestra muestra de entrenamiento tenemos el valor de $130 mmHg$. Por lo tanto, el error es $(143 - 130)^2 = 169$. Ahora necesitamos calcular este error para cada entrada en nuestro conjunto de datos de entrenamiento, luego sumarlo ($\sum_{i=1}^m(h_\theta(x^{(i)}) - y^{(i )})^2$) y sacar el valor medio de eso.
Esto nos da un solo número escalar que representa el costo de la función. Nuestro objetivo es encontrar valores de $\theta$ tales que la función de costo sea la más baja; en otras palabras, queremos minimizar la función de costo. Con suerte, esto parecerá intuitivo: si tenemos un valor de función de costo pequeño, esto significa que el error de predicción también es pequeño.
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
Ahora, necesitamos encontrar valores de $\theta$ tales que el valor de nuestra función de costo sea mínimo. Pero cómo hacemos eso?
\[minJ(\theta) = \frac{1}{2m}\sum_{i=1}^m(h_\theta(x^{(i)}) - y^{(i)})^2\ ]Hay varios algoritmos posibles, pero el más popular es el descenso de gradiente . Para comprender la intuición detrás del método de descenso de gradiente, primero representémoslo en el gráfico. En aras de la simplicidad, supondremos una hipótesis más simple $h(\theta) = \theta_1 * x$. A continuación, trazaremos un gráfico 2D simple donde $x$ es el valor de $\theta$ y $y$ es la función de costo en este punto.
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()
La función de costo es convexa, lo que significa que en el intervalo $[a, b]$ solo hay un mínimo. Lo que nuevamente significa que los mejores parámetros $\theta$ están en el punto donde la función de costo es mínima.
Básicamente, el descenso de gradiente es un algoritmo que intenta encontrar el conjunto de parámetros que minimizan la función. Comienza con un conjunto inicial de parámetros e iterativamente da pasos en la dirección negativa del gradiente de la función.
Si calculamos la derivada de una función de hipótesis en un punto específico, esto nos dará una pendiente de la recta tangente a la curva en ese punto. Esto significa que podemos calcular la pendiente en cada punto del gráfico.
La forma en que funciona el algoritmo es esta:
- Elegimos un punto de partida aleatorio (random $\theta$).
- Calcule la derivada de la función de costo en este punto.
- Da el pequeño paso hacia la pendiente $\theta_j := \theta_j - \lambda * \frac{\partial}{\partial \theta_j} * J(\theta)$.
- Repita los pasos 2-3 hasta que converjamos.
Ahora, la condición de convergencia depende de la implementación del algoritmo. Podemos detenernos después de 50 pasos, después de algún umbral o cualquier otra cosa.
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
No implementaremos esos algoritmos en este artículo. En su lugar, utilizaremos scikit-learn
, una biblioteca de aprendizaje automático de Python de código abierto ampliamente adoptada. Proporciona muchas API muy útiles para diferentes problemas de minería de datos y aprendizaje automático.
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 datos estadísticos
Cuando se trabaja con datos para problemas de aprendizaje automático, es importante reconocer diferentes tipos de datos. Podemos tener datos numéricos (continuos o discretos), categóricos u ordinales.
Los datos numéricos tienen significado como una medida. Por ejemplo, la edad, el peso, la cantidad de bitcoins que posee una persona o la cantidad de artículos que la persona puede escribir por mes. Los datos numéricos se pueden dividir en tipos discretos y continuos.
- Los datos discretos representan datos que se pueden contar con números enteros, por ejemplo, el número de habitaciones en un apartamento o el número de lanzamientos de monedas.
- Los datos continuos no necesariamente se pueden representar con números enteros. Por ejemplo, si está midiendo la distancia que puede saltar, puede ser 2 metros, 1,5 metros o 1,652245 metros.
Los datos categóricos representan valores como el género de la persona, estado civil, país, etc. Estos datos pueden tomar valores numéricos, pero esos números no tienen significado matemático. No puede agregarlos juntos.
Los datos ordinales pueden ser una combinación de los otros dos tipos, en el sentido de que las categorías se pueden numerar de una manera matemáticamente significativa. Un ejemplo común son las calificaciones: a menudo se nos pide que califiquemos las cosas en una escala del uno al diez, y solo se permiten números enteros. Si bien podemos usar esto numéricamente, por ejemplo, para encontrar una calificación promedio para algo, a menudo tratamos los datos como si fueran categóricos cuando se trata de aplicarles métodos de aprendizaje automático.
Regresión logística
La regresión lineal es un algoritmo impresionante que nos ayuda a predecir valores numéricos, por ejemplo, el precio de la casa con el tamaño específico y la cantidad de habitaciones. Sin embargo, a veces, también podemos querer predecir datos categóricos, para obtener respuestas a preguntas como:
- ¿Es esto un perro o un gato?
- ¿Este tumor es maligno o benigno?
- ¿Este vino es bueno o malo?
- ¿Este correo electrónico es spam o no?
O incluso:
- ¿Qué número está en la imagen?
- ¿A qué categoría pertenece este correo electrónico?
Todas estas preguntas son específicas del problema de clasificación . Y el algoritmo de clasificación más simple se llama regresión logística , que eventualmente es lo mismo que la regresión lineal excepto que tiene una hipótesis diferente.
En primer lugar, podemos reutilizar la misma hipótesis lineal $h_\theta(x) = \theta^TX$ (esto está en forma vectorizada). Mientras que la regresión lineal puede generar cualquier número en el intervalo $[a, b]$, la regresión logística solo puede generar valores en $[−1, 1]$, que es la probabilidad de que el objeto entre en una categoría determinada o no.
Usando una función sigmoidea , podemos convertir cualquier valor numérico para representar un valor en el intervalo $[−1, 1]$.
\[f(x) = \frac{1}{1 + e^x}\]Ahora, en lugar de $x$, necesitamos pasar una hipótesis existente y por lo tanto obtendremos:
\[f(x) = \frac{1}{1 + e^{\theta_0 + \theta_1 * x_1 + ... + \theta_n * x_n}}\]Después de eso, podemos aplicar un umbral simple que diga que si la hipótesis es mayor que cero, este es un valor verdadero, de lo contrario, falso.
\[h_\theta(x) = \begin{casos} 1 & \mbox{si} \theta^TX > 0 \\ 0 & \mbox{otro} \end{casos}\]Esto significa que podemos usar la misma función de costo y el mismo algoritmo de descenso de gradiente para aprender una hipótesis para la regresión logística.
En nuestro próximo ejemplo de algoritmo de aprendizaje automático, aconsejaremos a los pilotos del transbordador espacial si deben o no usar el control de aterrizaje automático o manual. Tenemos un conjunto de datos muy pequeño, 15 muestras, que consta de seis características y la verdad del terreno .
En los algoritmos de aprendizaje automático, el término " verdad fundamental " se refiere a la precisión de la clasificación del conjunto de entrenamiento para las técnicas de aprendizaje supervisado.
Nuestro conjunto de datos está completo, lo que significa que no faltan características; sin embargo, algunas de las funciones tienen un "*" en lugar de la categoría, lo que significa que esta función no importa. Reemplazaremos todos esos asteriscos con ceros.
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%
¿Validación?
En el ejemplo anterior, validamos el rendimiento de nuestro modelo utilizando los datos de aprendizaje. Sin embargo, ¿es esta ahora una buena opción, dado que nuestro algoritmo puede ajustar o sobreajustar los datos? Echemos un vistazo al ejemplo más simple cuando tenemos una característica que representa el tamaño de una casa y otra que representa su precio.
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()

El modelo de algoritmo de aprendizaje automático es inadecuado si no puede generalizar ni los datos de entrenamiento ni las nuevas observaciones. En el ejemplo anterior, usamos una hipótesis lineal simple que en realidad no representa el conjunto de datos de entrenamiento real y tendrá un rendimiento muy bajo. Por lo general, el ajuste insuficiente no se analiza, ya que se puede detectar fácilmente con una buena métrica.
Si nuestro algoritmo recuerda cada una de las observaciones que se mostraron, tendrá un rendimiento deficiente en las nuevas observaciones fuera del conjunto de datos de entrenamiento. Esto se llama sobreajuste . Por ejemplo, un modelo polinomial de grado 30 pasa por la mayoría de los puntos y tiene una puntuación muy buena en el conjunto de entrenamiento, pero cualquier cosa fuera de eso tendría un mal desempeño.
Nuestro conjunto de datos consta de una característica y es fácil de trazar en un espacio 2D; sin embargo, en la vida real, podemos tener conjuntos de datos con cientos de características, lo que los hace imposibles de trazar visualmente en el espacio euclidiano. ¿Qué otras opciones tenemos para ver si el modelo se está infraajustando o sobreajustando?
Es hora de presentarle el concepto de la curva de aprendizaje . Este es un gráfico simple que traza el error cuadrático medio sobre el número de muestras de entrenamiento.
En los materiales de aprendizaje, normalmente verá gráficos similares a estos:
Sin embargo, en la vida real, es posible que no obtenga una imagen tan perfecta. Tracemos la curva de aprendizaje para cada uno de nuestros 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()
En nuestro escenario simulado, la línea azul, que representa el puntaje de entrenamiento, parece una línea recta. En realidad, sigue disminuyendo ligeramente; de hecho, puede ver esto en el gráfico polinomial de primer grado, pero en los demás es demasiado sutil para notarlo con esta resolución. Al menos vemos claramente que existe una gran brecha entre las curvas de aprendizaje para el entrenamiento y las observaciones de prueba con un escenario de "alto sesgo".
En el gráfico de tasa de aprendizaje "normal" en el medio, puede ver cómo se unen las líneas de puntaje de entrenamiento y puntaje de prueba.
Y en el gráfico de "varianza alta", puede ver que con un número bajo de muestras, los puntajes de la prueba y el entrenamiento son muy similares; sin embargo, cuando aumenta la cantidad de muestras, el puntaje de entrenamiento permanece casi perfecto mientras que el puntaje de la prueba se aleja de él.
Podemos arreglar modelos de ajuste insuficiente (también llamados modelos con alto sesgo ) si usamos una hipótesis no lineal, por ejemplo, la hipótesis con más características polinómicas.
Nuestro modelo de sobreajuste ( alta varianza ) pasa por todos los ejemplos que se muestran; sin embargo, cuando introducimos datos de prueba, la brecha entre las curvas de aprendizaje se amplía. Podemos usar la regularización, la validación cruzada y más muestras de datos para corregir los modelos de sobreajuste.
Validación cruzada
Una de las prácticas comunes para evitar el sobreajuste es retener parte de los datos disponibles y usarlos como un conjunto de prueba. Sin embargo, cuando evaluamos diferentes configuraciones del modelo, como el número de características polinómicas, todavía corremos el riesgo de sobreajustar el conjunto de prueba porque los parámetros pueden modificarse para lograr el rendimiento óptimo del estimador y, debido a eso, nuestro conocimiento sobre el conjunto de prueba puede fuga en el modelo. Para resolver este problema, necesitamos aferrarnos a una parte más del conjunto de datos, que se denomina "conjunto de validación". El entrenamiento continúa en el conjunto de entrenamiento y, cuando creemos que hemos logrado el rendimiento óptimo del modelo, podemos hacer una evaluación final utilizando el conjunto de validación.
Sin embargo, al dividir los datos disponibles en tres conjuntos, reducimos drásticamente la cantidad de muestras que se pueden usar para entrenar los modelos, y los resultados pueden depender de una elección aleatoria particular para el par de conjuntos de entrenamiento y validación.
Una solución a este problema es un procedimiento llamado validación cruzada. En la validación cruzada estándar de $k$-fold, dividimos los datos en $k$ subconjuntos, llamados folds. Luego, entrenamos iterativamente el algoritmo en pliegues $k-1$ mientras usamos el pliegue restante como conjunto de prueba (llamado "pliegue de reserva").
La validación cruzada le permite ajustar los parámetros solo con su conjunto de entrenamiento original. Esto le permite mantener su conjunto de prueba como un conjunto de datos verdaderamente invisible para seleccionar su modelo final.
Hay muchas más técnicas de validación cruzada, como dejar P fuera , $k$-fold estratificado , barajar y dividir , etc., pero están más allá del alcance de este artículo.
regularización
Esta es otra técnica que puede ayudar a resolver el problema del sobreajuste del modelo. La mayoría de los conjuntos de datos tienen un patrón y algo de ruido. El objetivo de la regularización es reducir la influencia del ruido en el modelo.
Hay tres técnicas principales de regularización: Lasso, Tikhonov y red elástica.
La regularización L1 (o regularización Lasso ) seleccionará algunas características para reducirlas a cero, de modo que no jugarán ningún papel en el modelo final. L1 puede verse como un método para seleccionar características importantes.
La regularización L2 (o regularización Tikhonov ) obligará a que todas las características sean relativamente pequeñas, de modo que tendrán menos influencia en el modelo.
La red elástica es la combinación de L1 y L2.
Normalización (Escalado de funciones)
El escalado de características también es un paso importante al preprocesar los datos. Nuestro conjunto de datos puede tener características con valores $[-\infty, \infty]$ y otras características con una escala diferente. Este es un método para estandarizar los rangos de valores independientes.
El escalado de características también es un proceso importante para mejorar el rendimiento de los modelos de aprendizaje. En primer lugar, el descenso de gradiente convergerá mucho más rápido si todas las características se escalan a la misma norma. Además, muchos algoritmos, por ejemplo, máquinas de vectores de soporte (SVM), funcionan calculando la distancia entre dos puntos y si una de las características tiene valores amplios, entonces la distancia se verá muy influenciada por esta característica.
Máquinas de vectores de soporte
SVM es otro algoritmo de aprendizaje automático ampliamente popular que se puede utilizar para problemas de clasificación y regresión. En SVM, trazamos cada observación como un punto en un espacio dimensional $n$ donde $n$ es el número de características que tenemos. El valor de cada característica es el valor de coordenadas particulares. Luego, tratamos de encontrar un hiperplano que separe dos clases lo suficientemente bien.
Después de identificar el mejor hiperplano, queremos agregar márgenes, lo que separaría aún más las dos clases.
SVM es muy efectivo cuando la cantidad de funciones es muy alta o si la cantidad de funciones es mayor que la cantidad de muestras de datos. Sin embargo, dado que SVM opera sobre una base vectorial, es crucial normalizar los datos antes del uso.
Redes neuronales
Los algoritmos de redes neuronales son probablemente el campo más emocionante de los estudios de aprendizaje automático. Las redes neuronales intentan imitar cómo se conectan entre sí las neuronas del cerebro.
Así es como se ve una red neuronal. Combinamos muchos nodos juntos donde cada nodo toma un conjunto de entradas, les aplica algunos cálculos y genera un valor.
Existe una gran variedad de algoritmos de redes neuronales para el aprendizaje supervisado y no supervisado. Las redes neuronales se pueden usar para conducir automóviles autónomos, jugar juegos, aterrizar aviones, clasificar imágenes y más.
El infame Titanic
El RMS Titanic fue un transatlántico de pasajeros británico que se hundió en el Océano Atlántico Norte el 15 de abril de 1912 después de chocar con un iceberg. Había alrededor de 2224 tripulantes y pasajeros, y más de 1500 murieron, lo que lo convierte en uno de los desastres marítimos comerciales más mortíferos de todos los tiempos.
Ahora, dado que entendemos la intuición detrás de los algoritmos de aprendizaje automático más básicos que se utilizan para los problemas de clasificación, podemos aplicar nuestro conocimiento para predecir el resultado de supervivencia de las personas a bordo del Titanic.
Nuestro conjunto de datos se tomará prestado de la plataforma de competencias de ciencia de datos 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)
PasajeroId | Sobrevivió | Pclase | Nombre | Sexo | Años | SibEsp | Tostar | Billete | Tarifa | Cabina | embarcado | |
0 | 1 | 0 | 3 | Braund, Sr. Owen Harris | masculino | 22.0 | 1 | 0 | A/5 21171 | 7.2500 | Yaya | S |
1 | 2 | 1 | 1 | Cumings, Sra. John Bradley (Florence Briggs Th... | mujer | 38.0 | 1 | 0 | ordenador personal 17599 | 71.2833 | C85 | C |
2 | 3 | 1 | 3 | Heikkinen, Srta. Laina | mujer | 26,0 | 0 | 0 | PIEDRAS/O2. 3101282 | 7.9250 | Yaya | S |
3 | 4 | 1 | 1 | Futrelle, Sra. Jacques Heath (Lily May Peel) | mujer | 35,0 | 1 | 0 | 113803 | 53.1000 | C123 | S |
4 | 5 | 0 | 3 | Allen, Sr. William Henry | masculino | 35,0 | 0 | 0 | 373450 | 8.0500 | Yaya | S |
Nuestro primer paso sería cargar y explorar los datos. Tenemos 891 registros de prueba; cada registro tiene la siguiente estructura:
- PassengerId – ID del pasajero a bordo
- supervivencia: si la persona sobrevivió o no al accidente
- pclass: clase de ticket, por ejemplo, 1.°, 2.°, 3.°
- gender – Género del pasajero: Masculino o Femenino
- nombre – Título incluido
- edad – Edad en años
- sibsp – Número de hermanos/cónyuges a bordo del Titanic
- parch – Número de padres/hijos a bordo del Titanic
- billete – número de billete
- tarifa - tarifa de pasajero
- cabina – Número de cabina
- embarcado - puerto de embarque
Este conjunto de datos contiene datos numéricos y categóricos. Por lo general, es una buena idea profundizar en los datos y, en base a eso, generar suposiciones. Sin embargo, en este caso, omitiremos este paso e iremos directamente a las predicciones.
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 | mujer | masculino |
Capt | 0 | 1 |
Col | 0 | 2 |
Countess | 1 | 0 |
Don | 0 | 1 |
Dr | 1 | 6 |
Jonkheer | 0 | 1 |
señora | 1 | 0 |
Importante | 0 | 2 |
Maestría | 0 | 40 |
Extrañar | 182 | 0 |
Mlle | 2 | 0 |
Mme | 1 | 0 |
Sres | 0 | 517 |
Señora | 125 | 0 |
Milisegundo | 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 | Maestría | 0.575000 |
1 | Extrañar | 0.702703 |
2 | Sres | 0.156673 |
3 | Señora | 0.793651 |
4 | Otro | 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 | Años | 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()
Nombre | 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 | Neural Net | 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
- Una introducción a la teoría del aprendizaje automático y sus aplicaciones: un tutorial visual con ejemplos