Introducción
A veces se confunde con regresión lineal por novatos – debido a compartir el término regresión – regresión logística es muy diferente de regresión lineal. Mientras que la regresión lineal predice valores como 2, 2.45, 6.77 o valores continuoshaciéndolo un regresión algoritmo, regresión logística predice valores como 0 o 1, 1 o 2 o 3, que son valores discretoshaciéndolo un clasificación algoritmo. si, se llama regresión pero es un clasificación algoritmo. Más sobre esto en un momento.
Por lo tanto, si su problema de ciencia de datos involucra valores continuos, puede aplicar un regresión algoritmo (la regresión lineal es uno de ellos). De lo contrario, si se trata de clasificar entradas, valores discretos o clases, puede aplicar un clasificación algoritmo (la regresión logística es uno de ellos).
En esta guía, realizaremos una regresión logística en Python con la biblioteca Scikit-Learn. También explicaremos por qué la palabra "regresión" está presente en el nombre y cómo funciona la regresión logística.
Para ello, primero cargaremos datos que serán clasificados, visualizados y preprocesados. Luego, construiremos un modelo de regresión logística que comprenderá esos datos. Luego, este modelo se evaluará y se empleará para predecir valores en función de la nueva entrada.
Motivación
La empresa para la que trabaja se asoció con una granja agrícola turca. Esta asociación implica la venta de semillas de calabaza. Las semillas de calabaza son muy importantes para la nutrición humana. Contienen una buena proporción de carbohidratos, grasas, proteínas, calcio, potasio, fósforo, magnesio, hierro y zinc.
En el equipo de ciencia de datos, su tarea es diferenciar entre los tipos de semillas de calabaza simplemente usando datos, o clasificando los datos según el tipo de semilla.
La granja turca trabaja con dos tipos de semillas de calabaza, una se llama Çerçevelik y el otro Ürgüp Sivrisi.
Para clasificar las semillas de calabaza, su equipo ha seguido el documento de 2021 “El uso de métodos de aprendizaje automático en la clasificación de semillas de calabaza (Cucurbita pepo L.). Recursos genéticos y evolución de cultivos” de Koklu, Sarigil y Ozbek: en este documento, hay una metodología para fotografiar y extraer las medidas de las semillas de las imágenes.
Después de completar el proceso descrito en el documento, se extrajeron las siguientes medidas:
- Área – el número de píxeles dentro de los bordes de una semilla de calabaza
- Perímetro – la circunferencia en píxeles de una semilla de calabaza
- Longitud del eje principal – también la circunferencia en píxeles de una semilla de calabaza
- Longitud del eje menor – la pequeña distancia del eje de una semilla de calabaza
- Excentricidad – la excentricidad de una semilla de calabaza
- área convexa – el número de píxeles de la capa convexa más pequeña en la región formada por la semilla de calabaza
- Grado – la relación entre el área de una semilla de calabaza y los píxeles del cuadro delimitador
- Diámetro equivalente – la raíz cuadrada de la multiplicación del área de la semilla de calabaza por cuatro dividida por pi
- Compacidad – la proporción del área de la semilla de calabaza con respecto al área del círculo con la misma circunferencia
- Solidez – la condición convexa y convexa de las semillas de calabaza
- Redondez – la ovalidad de las semillas de calabaza sin considerar las distorsiones de sus bordes
- Relación de aspecto – la relación de aspecto de las semillas de calabaza
Esas son las medidas con las que tienes que trabajar. Además de las medidas, también está el Clase etiqueta para los dos tipos de semillas de calabaza.
Para comenzar a clasificar las semillas, importemos los datos y comencemos a verlos.
Comprender el conjunto de datos
Nota: Puede descargar el conjunto de datos de calabaza esta página.
Después de descargar el conjunto de datos, podemos cargarlo en una estructura de marco de datos usando el pandas
biblioteca. Dado que es un archivo de Excel, usaremos el read_excel()
método:
import pandas as pd
fpath = 'dataset/pumpkin_seeds_dataset.xlsx'
df = pd.read_excel(fpath)
Una vez que se cargan los datos, podemos echar un vistazo rápido a las primeras 5 filas usando el head()
método:
df.head()
Esto resulta en:
Area Perimeter Major_Axis_Length Minor_Axis_Length Convex_Area Equiv_Diameter Eccentricity Solidity Extent Roundness Aspect_Ration Compactness Class
0 56276 888.242 326.1485 220.2388 56831 267.6805 0.7376 0.9902 0.7453 0.8963 1.4809 0.8207 Çerçevelik
1 76631 1068.146 417.1932 234.2289 77280 312.3614 0.8275 0.9916 0.7151 0.8440 1.7811 0.7487 Çerçevelik
2 71623 1082.987 435.8328 211.0457 72663 301.9822 0.8749 0.9857 0.7400 0.7674 2.0651 0.6929 Çerçevelik
3 66458 992.051 381.5638 222.5322 67118 290.8899 0.8123 0.9902 0.7396 0.8486 1.7146 0.7624 Çerçevelik
4 66107 998.146 383.8883 220.4545 67117 290.1207 0.8187 0.9850 0.6752 0.8338 1.7413 0.7557 Çerçevelik
Aquí tenemos todas las medidas en sus respectivas columnas, nuestra Características, y también la Clase columna, nuestra dirigidos, que es el último en el marco de datos. Podemos ver cuantas medidas tenemos usando el shape
atributo:
df.shape
El resultado es:
(2500, 13)
El resultado de la forma nos dice que hay 2500 entradas (o filas) en el conjunto de datos y 13 columnas. Como sabemos que hay una columna de destino, esto significa que tenemos 12 columnas de características.
Ahora podemos explorar la variable objetivo, la semilla de calabaza. Class
. Como vamos a predecir esa variable, es interesante ver cuántas muestras de cada semilla de calabaza tenemos. Por lo general, cuanto menor sea la diferencia entre el número de instancias en nuestras clases, más equilibrada será nuestra muestra y mejores serán nuestras predicciones.
Esta inspección se puede hacer contando cada muestra de semilla con el value_counts()
método:
df['Class'].value_counts()
El código anterior muestra:
Çerçevelik 1300
Ürgüp Sivrisi 1200
Name: Class, dtype: int64
Podemos ver que hay 1300 muestras del Çerçevelik semilla y 1200 muestras de la Ürgüp Sivrisi semilla. Note que la diferencia entre ellos es de 100 muestras, una diferencia muy pequeña, lo cual es bueno para nosotros e indica que no hay necesidad de reequilibrar el número de muestras.
Veamos también las estadísticas descriptivas de nuestras características con el describe()
para ver qué tan bien distribuidos están los datos. También transpondremos la tabla resultante con T
para facilitar la comparación entre estadísticas:
df.describe().T
La tabla resultante es:
count mean std min 25% 50% 75% max
Area 2500.0 80658.220800 13664.510228 47939.0000 70765.000000 79076.00000 89757.500000 136574.0000
Perimeter 2500.0 1130.279015 109.256418 868.4850 1048.829750 1123.67200 1203.340500 1559.4500
Major_Axis_Length 2500.0 456.601840 56.235704 320.8446 414.957850 449.49660 492.737650 661.9113
Minor_Axis_Length 2500.0 225.794921 23.297245 152.1718 211.245925 224.70310 240.672875 305.8180
Convex_Area 2500.0 81508.084400 13764.092788 48366.0000 71512.000000 79872.00000 90797.750000 138384.0000
Equiv_Diameter 2500.0 319.334230 26.891920 247.0584 300.167975 317.30535 338.057375 417.0029
Eccentricity 2500.0 0.860879 0.045167 0.4921 0.831700 0.86370 0.897025 0.9481
Solidity 2500.0 0.989492 0.003494 0.9186 0.988300 0.99030 0.991500 0.9944
Extent 2500.0 0.693205 0.060914 0.4680 0.658900 0.71305 0.740225 0.8296
Roundness 2500.0 0.791533 0.055924 0.5546 0.751900 0.79775 0.834325 0.9396
Aspect_Ration 2500.0 2.041702 0.315997 1.1487 1.801050 1.98420 2.262075 3.1444
Compactness 2500.0 0.704121 0.053067 0.5608 0.663475 0.70770 0.743500 0.9049
Mirando la tabla, al comparar los personalizado y desviación estándar (std
) columnas, se puede ver que la mayoría de las características tienen una media que está lejos de la desviación estándar. Eso indica que los valores de los datos no están concentrados alrededor del valor medio, sino más dispersos alrededor de él; en otras palabras, tienen alta variabilidad.
Además, al mirar el mínimo (min
) y máximas (max
) columnas, algunas características, como Area
y Convex_Area
, tienen grandes diferencias entre los valores mínimo y máximo. Esto significa que esas columnas tienen datos muy pequeños y también valores de datos muy grandes, o mayor amplitud entre valores de datos.
Con alta variabilidad, alta amplitud y características con diferentes unidades de medida, la mayoría de nuestros datos se beneficiarían de tener la misma escala para todas las características o de ser escamoso. El escalado de datos centrará los datos en torno a la media y reducirá su varianza.
Este escenario probablemente también indica que hay valores atípicos y extremos en los datos. Por lo tanto, lo mejor es tener algunos tratamiento atípico además de escalar los datos.
Hay algunos algoritmos de aprendizaje automático, por ejemplo, algoritmos basados en árboles como Clasificación aleatoria de bosques, que no se ven afectados por una gran varianza de datos, valores atípicos y valores extremos. Regresión logística es diferente, se basa en una función que categoriza nuestros valores, y los parámetros de esa función pueden verse afectados por valores que están fuera de la tendencia general de los datos y tienen una varianza alta.
Entenderemos más sobre la regresión logística en un momento cuando lleguemos a implementarla. Por ahora, podemos seguir explorando nuestros datos.
Nota: Hay un dicho popular en Informática: “Basura entra, basura sale” (GIGO), que es muy adecuado para el aprendizaje automático. Esto significa que cuando tenemos datos basura (medidas que no describen los fenómenos en sí mismos, datos que no se entendieron y no se prepararon bien de acuerdo con el tipo de algoritmo o modelo), probablemente generarán una salida incorrecta que no funcionará. un día a día.
Esta es una de las razones por las que es tan importante explorar, comprender los datos y cómo funciona el modelo elegido. Al hacer eso, podemos evitar poner basura en nuestro modelo, poniendo valor en él y obteniendo valor.
Visualización de los datos
Hasta ahora, con las estadísticas descriptivas, tenemos una instantánea algo abstracta de algunas cualidades de los datos. Otro paso importante es visualizarlo y confirmar nuestra hipótesis de alta varianza, amplitud y valores atípicos. Para ver si lo que hemos observado hasta ahora se muestra en los datos, podemos trazar algunos gráficos.
También es interesante ver cómo se relacionan las características con las dos clases que se pronosticarán. Para hacer eso, vamos a importar el seaborn
paquete y use el pairplot
gráfico para ver cada distribución de características y cada separación de clase por característica:
import seaborn as sns
sns.pairplot(data=df, hue='Class')
Nota: El código anterior puede tardar un poco en ejecutarse, ya que el diagrama de pares combina diagramas de dispersión de todas las características (puede) y también muestra las distribuciones de características.
Mirando el gráfico de pares, podemos ver que en la mayoría de los casos los puntos de la Çerçevelik
clase están claramente separados de los puntos de la Ürgüp Sivrisi
clase. O los puntos de una clase están a la derecha cuando los otros están a la izquierda, o algunos están arriba mientras que otros están abajo. Si tuviéramos que usar algún tipo de curva o línea para separar las clases, esto demuestra que es más fácil separarlas, si estuvieran mezcladas, la clasificación sería una tarea más difícil.
En Eccentricity
, Compactness
y Aspect_Ration
columnas, algunos puntos que están "aislados" o que se desvían de la tendencia general de los datos (valores atípicos) también se detectan fácilmente.
Al mirar la diagonal desde la parte superior izquierda hasta la parte inferior derecha del gráfico, observe que las distribuciones de datos también están codificadas por colores de acuerdo con nuestras clases. Las formas de distribución y la distancia entre ambas curvas son otros indicadores de cuán separables son, cuanto más lejos, mejor. En la mayoría de los casos, no se superponen, lo que implica que son más fáciles de separar, contribuyendo también a nuestra tarea.
En secuencia, también podemos trazar los diagramas de caja de todas las variables con el sns.boxplot()
método. La mayoría de las veces, es útil orientar los diagramas de caja horizontalmente, para que las formas de los diagramas de caja sean las mismas que las formas de distribución, podemos hacerlo con el orient
argumento:
sns.boxplot(data=df, orient='h')
En el gráfico anterior, observe que Area
y Convex_Area
tienen una magnitud tan alta en comparación con las magnitudes de las otras columnas, que aplastan las otras gráficas de caja. Para poder ver todos los diagramas de caja, podemos escalar las características y trazarlas nuevamente.
Antes de hacer eso, entendamos que si hay valores de características que están íntimamente relacionados con otros valores, por ejemplo, si hay valores que también aumentan cuando otros valores de características aumentan, teniendo un correlación positiva; o si hay valores que hacen lo contrario, se hacen más pequeños mientras otros valores se hacen más pequeños, teniendo una correlación negativa.
Es importante tener en cuenta esto porque tener relaciones sólidas en los datos podría significar que algunas columnas se derivaron de otras columnas o que tienen un significado similar para nuestro modelo. Cuando eso sucede, los resultados del modelo pueden estar sobreestimados y queremos resultados más cercanos a la realidad. Si hay correlaciones fuertes, también significa que podemos reducir la cantidad de características y usar menos columnas, lo que hace que el modelo sea más parsimonioso.
Nota: La correlación por defecto calculada con el corr()
el método es el Coeficiente de correlación de Pearson. Este coeficiente se indica cuando los datos son cuantitativos, normalmente distribuidos, no tienen valores atípicos y tienen una relación lineal.
Otra opción sería calcular Coeficiente de correlación de Spearman. El coeficiente de Spearman se usa cuando los datos son ordinales, no lineales, tienen cualquier distribución y tienen valores atípicos. Tenga en cuenta que nuestros datos no se ajustan totalmente a las suposiciones de Pearson o Spearman (también hay más métodos de correlación, como el de Kendall). Dado que nuestros datos son cuantitativos y es importante para nosotros medir su relación lineal, utilizaremos el coeficiente de Pearson.
Echemos un vistazo a las correlaciones entre las variables y luego podemos pasar a preprocesar los datos. Calcularemos las correlaciones con el corr()
método y visualizarlos con Seaborn's heatmap()
. El tamaño estándar del mapa de calor tiende a ser pequeño, por lo que importaremos matplotlib
(motor/biblioteca de visualización general sobre la que está construido Seaborn) y cambie el tamaño con figsize
:
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 10))
correlations = df.corr()
sns.heatmap(correlations, annot=True)
En este mapa de calor, los valores más cercanos a 1 o -1 son los valores a los que debemos prestar atención. El primer caso, denota una alta correlación positiva y el segundo, una alta correlación negativa. Ambos valores, si no superan 0.8 o -0.8, serán beneficiosos para nuestro modelo de regresión logística.
Cuando existen correlaciones altas como la de 0.99
entre Aspec_Ration
y Compactness
, esto significa que podemos elegir usar solo Aspec_Ration
o solo Compactness
, en lugar de ambos (ya que casi serían iguales predictores el uno del otro). Lo mismo vale para Eccentricity
y Compactness
con un -0.98
correlación, por Area
y Perimeter
con un 0.94
correlación y algunas otras columnas.
Preprocesamiento de los datos
Como ya hemos explorado los datos durante un tiempo, podemos comenzar a procesarlos previamente. Por ahora, usemos todas las funciones para la predicción de clase. Después de obtener un primer modelo, una línea base, podemos eliminar algunas de las columnas altamente correlacionadas y compararlas con la línea base.
Las columnas de características serán nuestras X
datos y la columna de clase, nuestra y
datos objetivo:
y = df['Class']
X = df.drop(columns=['Class'], axis=1)
Convertir características categóricas en características numéricas
En cuanto a nuestro Class
columna: sus valores no son números, esto significa que también debemos transformarlos. Hay muchas formas de hacer esta transformación; aquí, usaremos el replace()
método y reemplazar Çerçevelik
a 0
y Ürgüp Sivrisi
a 1
.
y = y.replace('Çerçevelik', 0).replace('Ürgüp Sivrisi', 1)
¡Tenga en cuenta el mapeo! Cuando lea los resultados de su modelo, querrá volver a convertirlos al menos en su mente, o volver a convertirlos en el nombre de clase para otros usuarios.
División de datos en conjuntos de entrenamiento y prueba
En nuestra exploración, notamos que las características necesitaban escalarse. Si hiciéramos la escala ahora, o de forma automática, escalaríamos los valores con la totalidad de X
y y
. En ese caso, introduciríamos fuga de datos, ya que los valores del futuro conjunto de prueba habrían afectado la escala. La fuga de datos es una causa común de resultados irreproducibles y un alto rendimiento ilusorio de los modelos de ML.
Pensar en la escala muestra que primero debemos dividir X
y y
datos más en el tren y los conjuntos de prueba y luego a cómodo un escalador en el conjunto de entrenamiento, y para transformar tanto el tren como los conjuntos de prueba (sin que el conjunto de prueba impacte en el escalador que hace esto). Para ello, utilizaremos Scikit-Learn's train_test_split()
método:
from sklearn.model_selection import train_test_split
SEED = 42
X_train, X_test, y_train, y_test = train_test_split(X, y,
test_size=.25,
random_state=SEED)
Fijar test_size=.25
es garantizar que estemos utilizando el 25 % de los datos para pruebas y el 75 % para capacitación. Esto podría omitirse, una vez que sea la división predeterminada, pero el Pitónico forma de escribir código aconseja que ser "explícito es mejor que implícito".
Nota: La oración "explícito es mejor que implícito" es una referencia a El zen de pitóno PEP20. Presenta algunas sugerencias para escribir código Python. Si se siguen esas sugerencias, el código se considera Pitónico. Puedes saber más al respecto esta página.
Después de dividir los datos en conjuntos de prueba y entrenamiento, es una buena práctica observar cuántos registros hay en cada conjunto. Eso se puede hacer con el shape
atributo:
X_train.shape, X_test.shape, y_train.shape, y_test.shape
Esto muestra:
((1875, 12), (625, 12), (1875,), (625,))
Podemos ver que después de la división, tenemos 1875 registros para entrenamiento y 625 para pruebas.
Datos de escala
Una vez que tenemos listos nuestros conjuntos de entrenamiento y prueba, podemos proceder a escalar los datos con Scikit-Learn StandardScaler
objeto (u otros escaladores proporcionados por la biblioteca). Para evitar fugas, el raspador se instala en el X_train
Los datos y los valores del tren se utilizan luego para escalar, o transformar, tanto el tren como los datos de prueba:
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
Ya que normalmente llamarás:
scaler.fit(X_train)
X_train = scaler.transform(X_train)
X_test = scaler.transform(X_test)
Las dos primeras líneas se pueden colapsar con un singular fit_transform()
call, que encaja el scaler en el plató, y lo transforma de una vez. Ahora podemos reproducir los gráficos de diagramas de caja para ver la diferencia después de escalar los datos.
Teniendo en cuenta que la escala elimina los nombres de las columnas, antes de trazar, podemos organizar los datos del tren en un marco de datos con los nombres de las columnas nuevamente para facilitar la visualización:
column_names = df.columns[:12]
X_train = pd.DataFrame(X_train, columns=column_names)
sns.boxplot(data=X_train, orient='h')
¡Finalmente podemos ver todos nuestros diagramas de caja! Observe que todos ellos tienen valores atípicos y las características que presentan una distribución más alejada de la normal (que tienen curvas sesgadas hacia la izquierda o hacia la derecha), como Solidity
, Extent
, Aspect_Ration
y Compactedness
, son los mismos que tuvieron mayores correlaciones.
Eliminación de valores atípicos con el método IQR
Ya sabemos que la regresión logística puede verse afectada por valores atípicos. Una de las formas de tratarlos es usar un método llamado Rango intercuartil or RIC. El paso inicial del método IQR es dividir los datos de nuestro tren en cuatro partes, llamadas cuartiles. El primer cuartil, Q1, asciende al 25% de los datos, el segundo, Q2, al 50%, el tercero, Q3, al 75%, y el último, Q4, al 100%. Las cajas en el diagrama de caja están definidas por el método IQR y son una representación visual del mismo.
Considerando un diagrama de caja horizontal, la línea vertical de la izquierda marca el 25% de los datos, la línea vertical del medio, el 50% de los datos (o la mediana), y la última línea vertical de la derecha, el 75% de los datos. . Cuanto más uniforme sea el tamaño de ambos cuadrados definidos por las líneas verticales, o cuanto más en el medio esté la línea vertical mediana, significa que nuestros datos están más cerca de la distribución normal o menos sesgados, lo que es útil para nuestro análisis.
Además de la caja IQR, también hay líneas horizontales a ambos lados de la misma. Esas líneas marcan los valores mínimos y máximos de distribución definidos por
$$
Mínimo = Q1 – 1.5*RIQ
$$
y
$$
Máximo = Q3 + 1.5*RIQ
$$
IQR es exactamente la diferencia entre Q3 y Q1 (o Q3 – Q1) y es el punto de datos más central. Por eso, al encontrar el IQR, terminamos filtrando los valores atípicos en los extremos de los datos, o en los puntos mínimo y máximo. Los diagramas de caja nos dan un adelanto de cuál será el resultado del método IQR.
Podemos usar Pandas quantile()
método para encontrar nuestros cuantiles, y iqr
del desplegable scipy.stats
paquete para obtener el rango de datos intercuartiles para cada columna:
from scipy.stats import iqr
Q1 = X_train.quantile(q=.25)
Q3 = X_train.quantile(q=.75)
IQR = X_train.apply(iqr)
Ahora que tenemos Q1, Q3 e IQR, podemos filtrar los valores más cercanos a la mediana:
minimum = X_train < (Q1-1.5*IQR)
maximum = X_train > (Q3+1.5*IQR)
filter = ~(minimum | maximum).any(axis=1)
X_train = X_train[filter]
Después de filtrar nuestras filas de entrenamiento, podemos ver cuántas de ellas todavía están en los datos con shape
:
X_train.shape
Esto resulta en:
(1714, 12)
Podemos ver que el número de filas pasó de 1875 a 1714 después del filtrado. Esto significa que 161 filas contenían valores atípicos o el 8.5 % de los datos.
Nota: Se recomienda que el filtrado de valores atípicos, la eliminación de valores de NaN y otras acciones que implican el filtrado y la limpieza de datos permanezcan por debajo o hasta el 10 % de los datos. Intente pensar en otras soluciones si su filtrado o eliminación supera el 10% de sus datos.
Después de eliminar los valores atípicos, estamos casi listos para incluir datos en el modelo. Para el ajuste del modelo, utilizaremos datos de trenes. X_train
se filtra, pero ¿qué pasa con y_train
?
y_train.shape
Esto produce:
(1875,)
Darse cuenta de y_train
todavía tiene 1875 filas. Necesitamos igualar el número de y_train
filas al número de X_train
filas y no solo arbitrariamente. Necesitamos eliminar los valores de y de las instancias de semillas de calabaza que eliminamos, que probablemente estén dispersas por el y_train
establecer. el filtrado X_train
todavía tiene sus índices originales y el índice tiene espacios en los que eliminamos los valores atípicos. Entonces podemos usar el índice de la X_train
DataFrame para buscar los valores correspondientes en y_train
:
y_train = y_train.iloc[X_train.index]
Después de hacer eso, podemos mirar el y_train
forma de nuevo:
y_train.shape
Qué salidas:
(1714,)
Consulte nuestra guía práctica y práctica para aprender Git, con las mejores prácticas, los estándares aceptados por la industria y la hoja de trucos incluida. Deja de buscar en Google los comandos de Git y, de hecho, aprenden ella!
Ahora, y_train
también tiene 1714 filas y son las mismas que las X_train
filas ¡Finalmente estamos listos para crear nuestro modelo de regresión logística!
Implementación del modelo de regresión logística
¡La parte más difícil ya está hecha! El preprocesamiento suele ser más difícil que el desarrollo de modelos, cuando se trata de usar bibliotecas como Scikit-Learn, que han simplificado la aplicación de modelos ML a solo un par de líneas.
Primero, importamos el LogisticRegression
clase e instanciarlo, creando un LogisticRegression
:
from sklearn.linear_model import LogisticRegression
logreg = LogisticRegression(random_state=SEED)
En segundo lugar, ajustamos los datos de nuestro tren a la logreg
modelo con el fit()
método, y predecir nuestros datos de prueba con el predict()
método, almacenando los resultados como y_pred
:
logreg.fit(X_train.values, y_train)
y_pred = logreg.predict(X_test)
¡Ya hemos hecho predicciones con nuestro modelo! Veamos las primeras 3 filas en X_train
para ver qué datos hemos utilizado:
X_train[:3]
El código anterior genera:
Area Perimeter Major_Axis_Length Minor_Axis_Length Convex_Area Equiv_Diameter Eccentricity Solidity Extent Roundness Aspect_Ration Compactness
0 -1.098308 -0.936518 -0.607941 -1.132551 -1.082768 -1.122359 0.458911 -1.078259 0.562847 -0.176041 0.236617 -0.360134
1 -0.501526 -0.468936 -0.387303 -0.376176 -0.507652 -0.475015 0.125764 0.258195 0.211703 0.094213 -0.122270 0.019480
2 0.012372 -0.209168 -0.354107 0.465095 0.003871 0.054384 -0.453911 0.432515 0.794735 0.647084 -0.617427 0.571137
Y en las primeras 3 predicciones en y_pred
para ver los resultados:
y_pred[:3]
Esto resulta en:
array([0, 0, 0])
Para esas tres filas, nuestras predicciones fueron que eran semillas de primera clase, Çerçevelik
.
Con regresión logística, en lugar de predecir la clase final, como 0
, también podemos predecir la probabilidad que tiene la fila de pertenecer a la 0
clase. Esto es lo que realmente sucede cuando la regresión logística clasifica los datos y la predict()
Luego, el método pasa esta predicción a través de un umbral para devolver una clase "dura". Para predecir la probabilidad de pertenecer a una clase, predict_proba()
se usa:
y_pred_proba = logreg.predict_proba(X_test)
También echemos un vistazo a los primeros 3 valores de las predicciones de probabilidades y:
y_pred_proba[:3]
Qué salidas:
# class 0 class 1
array([[0.54726628, 0.45273372],
[0.56324527, 0.43675473],
[0.86233349, 0.13766651]])
Ahora, en lugar de tres ceros, tenemos una columna para cada clase. En la columna de la izquierda, comenzando con 0.54726628
, son las probabilidades de los datos pertenecientes a la clase 0
; y en la columna de la derecha, empezando por 0.45273372
, son la probabilidad de que pertenezca a la clase 1
.
Nota: Esta diferencia en la clasificación también se conoce como en las y suave predicción. La predicción dura encuadra la predicción en una clase, mientras que las predicciones blandas generan la probabilidades de la instancia perteneciente a una clase.
Hay más información sobre cómo se realizó la salida prevista. en realidad no fue 0
, pero un 55% de probabilidad de clase 0
, y un 45% de probabilidad de clase 1
. Esto muestra cómo los primeros tres X_test
puntos de datos, pertenecientes a la clase 0
, son realmente claros solo con respecto al tercer punto de datos, con una probabilidad del 86%, y no tanto para los primeros dos puntos de datos.
Cuando se comunican resultados mediante métodos de aprendizaje automático, por lo general es mejor devolver una clase suave y la probabilidad asociada como la "confianza" de esa clasificación.
Hablaremos más sobre cómo se calcula eso cuando profundicemos en el modelo. En este momento, podemos continuar con el siguiente paso.
Evaluación del modelo con informes de clasificación
El tercer paso es ver cómo se comporta el modelo en los datos de prueba. Podemos importar Scikit-Learn classification_report()
y pasar nuestro y_test
y y_pred
como argumentos. Después de eso, podemos imprimir su respuesta.
El informe de clasificación contiene las métricas de clasificación más utilizadas, como precisión, recordar, puntuación f1y la exactitud.
- Precisión: para entender qué valores de predicción correctos fueron considerados correctos por nuestro clasificador. La precisión dividirá esos valores positivos verdaderos por todo lo que se predijo como positivo:
$$
precision = frac{texto{verdadero positivo}}{texto{verdadero positivo} + texto{falso positivo}}
$$
- Recordar: para comprender cuántos de los verdaderos positivos fueron identificados por nuestro clasificador. El recuerdo se calcula dividiendo los verdaderos positivos por cualquier cosa que debería haber sido pronosticada como positiva:
$$
recordar = frac{texto{verdadero positivo}}{texto{verdadero positivo} + texto{falso negativo}}
$$
- Puntuación F1: es el equilibrado o Significado armonico de precisión y recuerdo. El valor más bajo es 0 y el más alto es 1. Cuando
f1-score
es igual a 1, significa que todas las clases se predijeron correctamente; esta es una puntuación muy difícil de obtener con datos reales:
$$
texto{f1-score} = 2* frac{texto{precisión} * texto{recordar}}{texto{precisión} + texto{recordar}}
$$
- Exactitud: describe cuántas predicciones acertó nuestro clasificador. El valor de precisión más bajo es 0 y el más alto es 1. Ese valor se suele multiplicar por 100 para obtener un porcentaje:
$$
precision = frac{texto{número de predicciones correctas}}{texto{número total de predicciones}}
$$
Nota: Es extremadamente difícil obtener una precisión del 100% en cualquier dato real, si eso sucede, tenga en cuenta que podría estar ocurriendo alguna fuga o algo incorrecto: no hay consenso sobre un valor de precisión ideal y también depende del contexto. Un valor del 70 %, lo que significa que el clasificador cometerá errores en el 30 % de los datos, o por encima del 70 %, tiende a ser suficiente para la mayoría de los modelos.
from sklearn.metrics import classification_report
cr = classification_report(y_test, y_pred)
print(cr)
A continuación, podemos ver el resultado del informe de clasificación:
precision recall f1-score support
0 0.83 0.91 0.87 316
1 0.90 0.81 0.85 309
accuracy 0.86 625
macro avg 0.86 0.86 0.86 625
weighted avg 0.86 0.86 0.86 625
Este es nuestro resultado. Darse cuenta de precision
, recall
, f1-score
y accuracy
todas las métricas son muy altas, por encima del 80%, lo cual es ideal, pero esos resultados probablemente se vieron influenciados por altas correlaciones y no se mantendrán a largo plazo.
La precisión del modelo es del 86 %, lo que significa que se equivoca en la clasificación el 14 % de las veces. Tenemos esa información general, pero sería interesante saber si el 14% de errores ocurre con respecto a la clasificación de la clase. 0
o clase 1
. Para identificar qué clases se identifican erróneamente como cuáles y con qué frecuencia, podemos calcular y trazar un matriz de confusión de las predicciones de nuestro modelo.
Evaluación del modelo con una matriz de confusión
Calculemos y luego tracemos la matriz de confusión. Después de hacer eso, podemos entender cada parte de ella. Para trazar la matriz de confusión, usaremos Scikit-Learn confusion_matrix()
, que importaremos desde el metrics
módulo.
La matriz de confusión es más fácil de visualizar usando un Seaborn heatmap()
. Entonces, después de generarlo, pasaremos nuestra matriz de confusión como argumento para el mapa de calor:
from sklearn.metrics import confusion_matrix
cm = confusion_matrix(y_test, y_pred)
sns.heatmap(cm, annot=True, fmt='d')
- Matriz de confusión: la matriz muestra cuántas muestras acertó o erró el modelo para cada clase. Los valores que fueron correctos y correctamente predichos se llaman verdaderos positivos, y los que se pronosticaron como positivos pero no lo fueron se denominan falsos positivos. La misma nomenclatura de verdaderos negativos y falsos negativos se utiliza para valores negativos;
Al observar el gráfico de la matriz de confusión, podemos ver que tenemos 287
valores que fueron 0
y predicho como 0
- o verdaderos positivos para clase 0
(las semillas de Çerçevelik). También tenemos 250
verdaderos positivos para la clase 1
(Semillas de Ürgüp Sivrisi). Los verdaderos positivos siempre se ubican en la matriz diagonal que va desde la esquina superior izquierda a la esquina inferior derecha.
También tenemos 29
valores que se suponía que eran 0
, pero predicho como 1
(falsos positivos) y 59
valores que fueron 1
y predicho como 0
(falsos negativos). Con esos números, podemos entender que el error que más comete el modelo es que predice falsos negativos. Por lo tanto, en su mayoría puede terminar clasificando una semilla de Ürgüp Sivrisi como una semilla de Çerçevelik.
Este tipo de error también se explica por el 81% de recuerdo de clase. 1
. Observe que las métricas están conectadas. Y la diferencia en el retiro proviene de tener 100 muestras menos de la clase Ürgüp Sivrisi. Esta es una de las implicaciones de tener solo unas pocas muestras menos que la otra clase. Para mejorar aún más la recuperación, puede experimentar con pesos de clase o usar más muestras de Ürgüp Sivrisi.
Hasta ahora, hemos ejecutado la mayoría de los pasos tradicionales de la ciencia de datos y hemos utilizado el modelo de regresión logística como una caja negra.
Nota: Si quieres ir más allá, utiliza Validación cruzada (CV) y búsqueda en cuadrícula buscar, respectivamente, el modelo que más generaliza respecto a los datos, y los mejores parámetros del modelo que se eligen antes del entrenamiento, o hiperparámetros.
Idealmente, con CV y Grid Search, también podría implementar una forma concatenada de realizar pasos de preprocesamiento de datos, división de datos, modelado y evaluación, lo cual se simplifica con Scikit-Learn. tuberías.
Ahora es el momento de abrir la caja negra y mirar dentro, para profundizar en la comprensión de cómo funciona la regresión logística.
Profundizando en cómo funciona realmente la regresión logística
La regresión palabra no está allí por accidente, para entender lo que hace la regresión logística, podemos recordar lo que su hermano, la regresión lineal, hace con los datos. La fórmula de regresión lineal fue la siguiente:
$$
y = b_0 + b_1 * x_1 + b_2 * x_2 + b_3 * x_3 + lpuntos + b_n * x_n
$$
en que b0 fue el intercepto de la regresión, b1 el coeficiente y x1 los datos.
Esa ecuación resultó en una línea recta que se usó para predecir nuevos valores. Recordando la introducción, la diferencia ahora es que no predeciremos nuevos valores, sino una clase. Así que esa línea recta necesita cambiar. Con la regresión logística, introducimos una no linealidad y la predicción ahora se realiza utilizando una curva en lugar de una línea:
Observe que mientras la línea de regresión lineal continúa y está compuesta por infinitos valores continuos, la curva de regresión logística se puede dividir en el medio y tiene extremos en valores 0 y 1. Esa forma de "S" es la razón por la que clasifica los datos: los puntos que están más cerca o caen en el extremo superior pertenecen a la clase 1, mientras que los puntos que están en el cuadrante inferior o más cerca de 0 pertenecen a la clase 0. El medio de la “S” es el medio entre 0 y 1, 0.5 – es el umbral para los puntos de regresión logística.
Ya entendemos la diferencia visual entre regresión logística y lineal, pero ¿qué pasa con la fórmula? La fórmula para la regresión logística es la siguiente:
$$
y = b_0 + b_1 * x_1 + b_2 * x_2 + b_3 * x_3 + lpuntos + b_n * x_n
$$
También se puede escribir como:
$$
y_{prob} = fracción{1}{1 + e^{(b_0 + b_1 * x_1 + b_2 * x_2 + b_3 * x_3 + ldots + b_n * x_n)}}
$$
O incluso escribirse como:
$$
y_{prob} = fracción{e^{(b_0 + b_1 * x_1 + b_2 * x_2 + b_3 * x_3 + ldots + b_n * x_n)}}{1 + e^{(b_0 + b_1 * x_1 + b_2 * x_2 + b_3 * x_3 + ldots + b_n * x_n)}}
$$
En la ecuación anterior, tenemos la probabilidad de entrada, en lugar de su valor. Tiene 1 como numerador, por lo que puede dar como resultado un valor entre 0 y 1, y 1 más un valor en su denominador, por lo que su valor es 1 y algo, esto significa que el resultado de la fracción entera no puede ser mayor que 1 .
¿Y cuál es el valor que está en el denominador? Está e, la base del logaritmo natural (aproximadamente 2.718282), elevado a la potencia de regresión lineal:
$$
e^{(b_0 + b_1 * x_1 + b_2 * x_2 + b_3 * x_3 + ldots + b_n * x_n)}
$$
Otra forma de escribirlo sería:
$$
ln izquierda( frac{p}{1-p} derecha) = {(b_0 + b_1 * x_1 + b_2 * x_2 + b_3 * x_3 + ldots + b_n * x_n)}
$$
En esa última ecuación, ln es el logaritmo natural (base e) y p es la probabilidad, por lo que el logaritmo de la probabilidad del resultado es el mismo que el resultado de la regresión lineal.
En otras palabras, con el resultado de la regresión lineal y el logaritmo natural, podemos llegar a la probabilidad de que una entrada pertenezca o no a una clase diseñada.
Todo el proceso de derivación de la regresión logística es el siguiente:
$$
p{X} = fracción{e^{(b_0 + b_1 * x_1 + b_2 * x_2 + b_3 * x_3 + ldots + b_n * x_n)}}{1 + e^{(b_0 + b_1 * x_1 + b_2 * x_2 + b_3 * x_3 + ldots + b_n * x_n)}}
$$
$$
p(1 + e^{(b_0 + b_1 * x_1 + b_2 * x_2 + b_3 * x_3 + ldots + b_n * x_n)}) = e^{(b_0 + b_1 * x_1 + b_2 *x_2 + b_3 * x_3 + ldots + b_n * x_n)}
$$
$$
p + p*e^{(b_0 + b_1 * x_1 + b_2 * x_2 + b_3 * x_3 + ldots + b_n * x_n)} = e^{(b_0 + b_1 * x_1 + b_2 *x_2 + b_3 * x_3 + ldots + b_n*x_n)}
$$
p
=
e
(
b
0
+
b
1
*
x
1
+
b
2
*
x
2
+
b
3
*
x
3
+
...
+
b
n
*
x
n
)
-
p
*
e
(
b
0
+
b
1
*
x
1
+
b
2
*
x
2
+
b
3
*
x
3
+
...
+
b
n
*
x
n
)
$$
fracción{p}{1-p} = e^{(b_0 + b_1 * x_1 + b_2 *x_2 + b_3 * x_3 + ldots + b_n * x_n)}
$$
$$
ln izquierda( frac{p}{1-p} derecha) = (b_0 + b_1 * x_1 + b_2 *x_2 + b_3 * x_3 + ldots + b_n * x_n)
$$
Esto significa que el modelo de regresión logística también tiene coeficientes y un valor de intersección. Porque usa una regresión lineal y le agrega un componente no lineal con el logaritmo natural (e
).
Podemos ver los valores de los coeficientes y la intersección de nuestro modelo, de la misma manera que lo hicimos para la regresión lineal, usando coef_
y intercept_
propiedades:
logreg.coef_
Que muestra los coeficientes de cada una de las 12 características:
array([[ 1.43726172, -1.03136968, 0.24099522, -0.61180768, 1.36538261,
-1.45321951, -1.22826034, 0.98766966, 0.0438686 , -0.78687889,
1.9601197 , -1.77226097]])
logreg.intercept_
Eso da como resultado:
array([0.08735782])
Con los coeficientes y los valores de intercepción, podemos calcular las probabilidades previstas de nuestros datos. Consigamos el primero X_test
valores de nuevo, como un ejemplo:
X_test[:1]
Esto devuelve la primera fila de X_test
como una matriz NumPy:
array([[-1.09830823, -0.93651823, -0.60794138, -1.13255059, -1.0827684 ,
-1.12235877, 0.45891056, -1.07825898, 0.56284738, -0.17604099,
0.23661678, -0.36013424]])
Siguiendo la ecuación inicial:
$$
p{X} = fracción{e^{(b_0 + b_1 * x_1 + b_2 * x_2 + b_3 * x_3 + ldots + b_n * x_n)}}{1 + e^{(b_0 + b_1 * x_1 + b_2 * x_2 + b_3 * x_3 + ldots + b_n * x_n)}}
$$
En python tenemos:
import math
lin_reg = logreg.intercept_[0] +
((logreg.coef_[0][0]* X_test[:1][0][0])+
(logreg.coef_[0][1]* X_test[:1][0][1])+
(logreg.coef_[0][2]* X_test[:1][0][2])+
(logreg.coef_[0][3]* X_test[:1][0][3])+
(logreg.coef_[0][4]* X_test[:1][0][4])+
(logreg.coef_[0][5]* X_test[:1][0][5])+
(logreg.coef_[0][6]* X_test[:1][0][6])+
(logreg.coef_[0][7]* X_test[:1][0][7])+
(logreg.coef_[0][8]* X_test[:1][0][8])+
(logreg.coef_[0][9]* X_test[:1][0][9])+
(logreg.coef_[0][10]* X_test[:1][0][10])+
(logreg.coef_[0][11]* X_test[:1][0][11]))
px = math.exp(lin_reg)/(1 +(math.exp(lin_reg)))
px
Esto resulta en:
0.45273372469369133
Si volvemos a mirar el predict_proba
resultado de la primera X_test
línea, tenemos:
logreg.predict_proba(X_test[:1])
Esto significa que la ecuación de regresión logística original nos da la probabilidad de entrada con respecto a la clase 1
, para averiguar qué probabilidad es para la clase 0
, podemos simplemente:
1 - px
Note que ambos px
y 1-px
son idénticos a predict_proba
resultados. Así es como se calcula la regresión logística y por qué regresión es parte de su nombre. Pero ¿qué pasa con el término logístico?
El término logístico viene de logit, que es una función que ya hemos visto:
$$
ln izquierda( frac{p}{1-p} derecha)
$$
Lo acabamos de calcular con px
y 1-px
. Este es el logit, también llamado registro de probabilidades ya que es igual al logaritmo de las probabilidades donde p
es una probabilidad.
Conclusión
En esta guía, hemos estudiado uno de los algoritmos de clasificación de aprendizaje automático más fundamentales, es decir regresión logística.
Inicialmente, implementamos la regresión logística como una caja negra con la biblioteca de aprendizaje automático de Scikit-Learn, y luego la entendimos paso a paso para tener claro por qué y de dónde provienen los términos regresión y logística.
También hemos explorado y estudiado los datos, entendiendo que es una de las partes más cruciales de un análisis de ciencia de datos.
A partir de aquí, te aconsejo que juegues con regresión logística multiclase, regresión logística para más de dos clases: puede aplicar el mismo algoritmo de regresión logística para otros conjuntos de datos que tengan varias clases e interpretar los resultados.
Nota: Una buena colección de conjuntos de datos está disponible esta página para que juegues.
También te aconsejaría estudiar la L1 y la L2 regularizaciones, son una forma de “penalizar” los datos más altos para que se acerquen más a lo normal, manteniendo la complejidad del modelo, para que el algoritmo pueda llegar a un mejor resultado. La implementación de Scikit-Learn que usamos ya tiene regularización L2 por defecto. Otra cosa a tener en cuenta es la diferencia solucionadores, Tales como lbgs
, que optimizan el rendimiento del algoritmo de regresión logística.
También es importante echar un vistazo a la estadístico enfoque de la regresión logística. Tiene supuestos sobre el comportamiento de los datos, y sobre otras estadísticas que deben contener para garantizar resultados satisfactorios, tales como:
- las observaciones son independientes;
- no hay multicolinealidad entre las variables explicativas;
- no hay valores atípicos extremos;
- existe una relación lineal entre las variables explicativas y el logit de la variable de respuesta;
- el tamaño de la muestra es suficientemente grande.
Observe cuántos de esos supuestos ya estaban cubiertos en nuestro análisis y tratamiento de datos.
¡Espero que siga explorando lo que la regresión logística tiene para ofrecer en todos sus diferentes enfoques!