logo

Introducción

Las Máquinas de vector soporte (Support Vector Machines SVMs) son un conjunto de algoritmos de aprendizaje supervisados que desarrollan métodos relacionados con los problemas de clasificación y regresión.

Originariamente se desarrollaron como un método de clasificación binaria, pero su aplicación se ha extendido a problemas de clasificación múltiple. SVMs ha resultado ser uno de los mejores clasificadores para un amplio abanico de situaciones, por lo que se considera uno de los referentes dentro del ámbito de aprendizaje estadístico y machine learning.

SVMs, intuitivamente, es un modelo que trata de separar los puntos de entrenamiento de las diferentes clases mediante un espacio lo más amplio posible, para que cuando se tengan nuevas observaciones puedan ser clasificadas correctamente en función de su proximidad.

El concepto “separación máxima” es donde reside la característica fundamental de SVMs. En este tipo de algoritmos se busca el hiperplano que tenga la máxima distancia (margen) con los puntos que estén más cerca de él mismo. Es por ello, que también se conoce a las SVMs como clasificadores de margen máximo. De esta forma, los puntos del vector que son etiquetados con una categoría estarán a un lado del hiperplano y los casos que se encuentren en la otra categoría estarán al otro lado.

¿Qué es un Hiperplano?

En un espacio de \(p\)-dimensiones, un hiperplano se define como un subespacio plano de dimensión \(p−1\). Por ejemplo, en un espacio de bidimensional, el hiperplano es un subespacio unidimensinoal, es decir, una recta. En un espacio tridimensional, el hiperplano es bidimensional, como un plano convencional. Aunque para dimensiones \(p>3\) no es intuitivo visualizar un hiperplano, el concepto de subespacio plano de dimensión \(p−1\), se mantiene.

La ecuación matemática de un hiperplano general para \(p\)-dimensiones, se puede escribir como: \[ \beta_0+\beta_1x_1+\beta_2x_2+\cdots+\beta_px_p=0 \] Todos los puntos definidos por el vector \({\bf x}=(x_1,x_2,\dots,x_p)\) que cumplen la ecuación pertenecen al hiperplano.

De esta manera, cualquier vector \({\bf x}\) que no este en el hiperplano, será: \[ \beta_0+\beta_1x_1+\beta_2x_2 + \cdots+\beta_px_p>0 \] o bien,

\[ \beta_0+\beta_1x_1+\beta_2x_2+\cdots+\beta_px_p<0 \] Es decir, \({\bf x}\) cae a un lado o al otro del hiperplano y con esto podríamos clasificarlo. La siguiente imagen muestra el hiperplano de un espacio bidimensional. La ecuación que describe el hiperplano (una recta) es \(1+2x_1+3x_2=0\). La región azul representa el espacio en el que se encuentran todos los puntos para los que \(1+2x_1+3x_2>0\) y la región roja el de los puntos para los que \(1+2x_1+3x_2<0\).

Clasificación binaria

Para lograr comprender el funcionamiento de este clasificador, trabajaremos inicialmente un ejemplo de clasificación binaria. Existen dos posibles escenarios.

Perfectamente separable:

Supongamos que las observaciones pueden separarse linealmente de forma perfecta en dos clases. En este escenario, el clasificador más simple consiste en asignar cada observación a una clase según el lado del hiperplano en el que se encuentre.

Sin embargo, cuando los datos son perfectamente separables, existen infinitos hiperplanos que pueden realizar dicha separación. Por ello, se requiere un criterio que permita seleccionar uno de ellos como el clasificador óptimo.

La solución consiste en seleccionar, como clasificador óptimo, el denominado hiperplano óptimo de separación: aquel que maximiza la distancia (o margen) con respecto a las observaciones de entrenamiento más cercanas. Aunque esta idea resulta intuitiva, no puede aplicarse directamente, ya que implicaría evaluar infinitos hiperplanos posibles. En su lugar, se recurre a técnicas de optimización convexa que permiten encontrar dicho hiperplano de manera eficiente

La imagen anterior muestra el hiperplano óptimo para un conjunto de datos de entrenamiento. Las tres observaciones equidistantes ubicadas a lo largo de las líneas punteadas indican el ancho del margen. Estas observaciones se denominan vectores de soporte, ya que, al ser vectores en un espacio de dimensión \(p\), definen el hiperplano óptimo de separación.

Cualquier modificación en estas observaciones conlleva un cambio en la posición o la orientación del hiperplano. En contraste, las observaciones que no son vectores de soporte no influyen en el hiperplano, por lo que alterarlas no tiene ningún efecto sobre la solución del clasificador.

Cuasi-separable:

El hiperplano óptimo descrito anteriormente es una forma muy simple y natural de clasificación siempre y cuando las observaciones permitan separarse linealmente de forma perfecta. En la gran mayoría de casos reales, no se sucede esto, por lo que no existe un hiperplano optimo de separación, como el ejemplo que se muestra en la siguiente figura.

Para solucionar esta situación, se extiende el concepto de hiperplano óptimo, buscando ahora un hiperplano que separe lo mejor posible las clases y que cometa el menor error. A este tipo de hiperplano se le conoce como clasificadores de vector soporte o SVMs de margen suave.

Clasificadores de vector soporte:

El hiperplano óptimo tiene poca aplicación práctica, ya que rara vez se encuentran observaciones en los que las clases sean perfecta y linealmente separables. De hecho, incluso cumpliéndose estas condiciones ideales, en las que exista un hiperplano capaz de separar perfectamente las observaciones en dos clases, esta aproximación sigue presentando dos inconvenientes:

  • Dado que el hiperplano tiene que separar perfectamente las observaciones, es muy sensible a variaciones en los datos. Incluir una nueva observación puede suponer cambios muy grandes en el hiperplano de separación (poca robustez).

  • Que se ajuste perfectamente a las observaciones de entrenamiento para separarlas todas correctamente suele conllevar problemas de sobreajuste (Overfitting).

Por estas razones, es preferible crear un clasificador basado en un hiperplano que, aunque no separe perfectamente, sea más robusto y tenga mayor capacidad predictiva al aplicarlo a nuevas observaciones (menor problema de sobreajuste). Esto es exactamente lo que consiguen los clasificadores de vector soporte. Para lograrlo, en lugar de buscar el margen de clasificación más ancho posible que consigue que las observaciones estén en el lado correcto del margen; se permite que ciertas observaciones estén en el lado incorrecto del margen o incluso del hiperplano.

En la siguiente imagen se muestra un ejemplo ilustrativo. La línea continua representa el hiperplano, mientras que las líneas discontinuas indican los márgenes a cada lado.

Las observaciones 2, 3, 4, 5, 6 y 10 se encuentran fuera del margen y en el lado correcto del hiperplano, por lo tanto, están correctamente clasificadas. Las observaciones 1, 7, 8 y 9, aunque se encuentran dentro del margen, también están en el lado correcto del hiperplano, por lo que igualmente se consideran bien clasificadas.

Por otro lado, las observaciones 11 y 12 se ubican en el lado incorrecto del hiperplano, lo que implica una clasificación errónea. En general, cualquier observación que se encuentre en el lado equivocado del hiperplano, independientemente de si está dentro o fuera del margen, se considera mal clasificada.

En R, se puede utilizar la función svm() de la librería e1071 para ajustar un clasificador de vector de soporte. Si se especifica el argumento kernel = "linear", el modelo utilizará un hiperplano lineal como frontera de decisión.

El argumento cost controla la penalización aplicada a las observaciones que violan el margen, y suele seleccionarse mediante técnicas de validación cruzada. Este parámetro también se conoce como hiperparámetro \(C\).

  • Cuando \(C\) toma valores pequeños, el margen es más amplio y se permite que más observaciones queden dentro del margen, convirtiéndose así en vectores de soporte. Esto genera un modelo más flexible, con mayor sesgo pero menor varianza.

  • Cuando \(C\) es grande, se penaliza fuertemente cualquier violación del margen, lo que produce un margen más estrecho, un menor número de vectores de soporte y un clasificador con menor sesgo pero mayor varianza.

Entonces el trabajo interesante consiste en lograra ajustar lo mejor posible este parámetro, para conseguir una mejor clasificación.

Ejemplos en R

Ejemplo 1:

Para este primer ejemplo, simulamos datos con dos clases relativamente sencillos de clasificar.

set.seed(10111)
data1 <- data.frame(x1 = rnorm(20),
                    x2 = rnorm(20))

data1[11:20,] <- data1[11:20,] + 1.2

y  = c(rep(-1,10), rep(1,10))
data1$y <- as.factor(y)

library(ggplot2)
ggplot(data = data1, aes(x = x1, y = x2, color = y)) +
  geom_point(size = 6) +
  theme_bw() +
  theme(legend.position = "none")

La representación gráfica de los datos muestra que los grupos no son linealmente separables.

Solución paso a paso:

Paso 1

Preparación inicial y limpieza de los datos:

En este caso, ambos predictores \((x_1, x_2)\) tienen la misma escala por lo que no es necesario estandarizarlos. En aquellas situaciones en las que las escalas son distintas, sí hay que estandarizarlos, de lo contrario, los predictores de mayor magnitud serán más importantes.

Paso 2

Dividir los datos en conjunto de entrenamiento y prueba

Para este primer ejemplo de datos simulados, se trabajó con todos los datos de entrenamiento y se crearán datos nuevos para el conjunto de prueba.

Paso 3

Aplicar el método de Clasificador de vector soporte

library(e1071)
svm1  <- svm(y ~ x1 + x2, data1, kernel = "linear",cost = 10, scale = F)
summary(svm1)
## 
## Call:
## svm(formula = y ~ x1 + x2, data = data1, kernel = "linear", cost = 10, 
##     scale = F)
## 
## 
## Parameters:
##    SVM-Type:  C-classification 
##  SVM-Kernel:  linear 
##        cost:  10 
## 
## Number of Support Vectors:  4
## 
##  ( 2 2 )
## 
## 
## Number of Classes:  2 
## 
## Levels: 
##  -1 1
# Observaciones que actúan como vector soporte
svm1$index
## [1]  1  4 16 20

En el ajuste anterior se ha empleado un valor del hiperparámetro cost = 10. Este determina el balance sesgo-varianza y por lo tanto, es crítico para la capacidad predictiva del modelo.

Paso 4

Validar la estabilidad del modelo

No se aplico en este ejemplo, pero se trabajo en validación cruzada para elegir el mejor valor para parametro cost().

La librería incluye la función tune() que realiza validación cruzada para identificar el valor óptimo. Para poder realizar el proceso debes incluir el modelo svm y un vector ranges con los valores que se quieren evaluar.

set.seed(1)
svm_vc <- tune("svm", y ~ x1 + x2, data = data1,
               kernel = 'linear',
               ranges = list(cost = c(0.001, 0.01, 0.1, 1, 5, 10, 20, 50, 100,150,200)))
summary(svm_vc)
## 
## Parameter tuning of 'svm':
## 
## - sampling method: 10-fold cross validation 
## 
## - best parameters:
##  cost
##     5
## 
## - best performance: 0.1 
## 
## - Detailed performance results:
##       cost error dispersion
## 1    0.001  0.50  0.4714045
## 2    0.010  0.50  0.4714045
## 3    0.100  0.15  0.2415229
## 4    1.000  0.25  0.2635231
## 5    5.000  0.10  0.2108185
## 6   10.000  0.10  0.2108185
## 7   20.000  0.10  0.2108185
## 8   50.000  0.10  0.2108185
## 9  100.000  0.10  0.2108185
## 10 150.000  0.10  0.2108185
## 11 200.000  0.10  0.2108185

El proceso de validación cruzada muestra que el valor de penalización con el que se consigue menor error es 5 o superior. La función tune() almacena el mejor modelo de entre todos los que se han comparado.

mejor_modelo <- svm_vc$best.model

Paso 5

Interpretación de los resultados finales

Una vez obtenido el modelo final, se puede calcular la tasa de aciertos para los datos de entrenamiento y para nuevas observaciones empleando la función predict().

pred1 <- predict(object = mejor_modelo,data1)

# Matriz de confusión
MC1 <- table(pred1, data1$y)
MC1
##      
## pred1 -1 1
##    -1  9 1
##    1   1 9
# Tasa de aciertos
total  <- sum(MC1) 
TA1    <- sum(MC1[1,1]+MC1[2,2])/total
TA1
## [1] 0.9

Conseguimos una tasa de aciertos para los datos de entrenamiento del \(90\%\).

set.seed(1981)
prueba <- data.frame(x1 = rnorm(10),
                     x2 = rnorm(10))

prueba[6:10,] <- prueba[6:10,] + 1.2

y  = c(rep(-1,5), rep(1,5))
prueba$y <- as.factor(y)

# Predicciones
pred1_p <- predict(object = mejor_modelo, prueba)

# Matriz de confusión
MC1_p <- table(pred1_p, prueba$y)
MC1_p
##        
## pred1_p -1 1
##      -1  4 0
##      1   1 5
# Tasa de aciertos
total    <- sum(MC1_p) 
TA1_p    <- sum(MC1_p[1,1]+MC1_p[2,2])/total
TA1_p
## [1] 0.9

Máquinas de vector soporte

El clasificador de vector soporte consigue buenos resultados cuando el límite de separación entre clases es aproximadamente lineal. Si no lo es, su capacidad decae drásticamente. Una estrategia para enfrentarse a escenarios en los que la separación de los grupos es de tipo no lineal consiste en expandir las dimensiones del espacio original.

El hecho de que los grupos no sean linealmente separables en el espacio original no significa que no lo sean en un espacio de mayor dimensión. Las imágenes siguientes muestran como dos grupos, cuya separación en dos dimensiones no es lineal, sí lo es al añadir una tercera dimensión.

El método de máquinas de vector soporte (SVMs) se puede considerar como una extensión del clasificador de vector soporte obtenida al aumentar la dimensión de los datos. Los límites de separación lineales generados en el espacio aumentado se convierten en límites de separación no lineales al proyectarlos en el espacio original.

Pero aumentando la dimensión de los datos antes de aplicar el algoritmo, la pregunta inmediata es ¿Cómo se aumenta la dimensión y qué dimensión sería la correcta?

La dimensión de un conjunto de datos puede transformarse combinando o modificando cualquiera de sus dimensiones. Por ejemplo, se puede transformar un espacio de dos dimensiones en uno de tres aplicando la siguiente función: \[ f(x_1,x_2)=\left(x_1,x_2,5x_1x_2\right) \] Esta sería una de las infinitas trasformaciones posibles, ¿Cómo saber cuál es la adecuada? Es aquí donde los kernels entran en juego. Un kernel() es una función que devuelve el resultado del producto interno entre dos vectores, realizado en un nuevo espacio dimensional distinto al espacio original en el que se encuentran los vectores. Gracias a los kernels, se puede obtener el resultado para cualquier dimensión. En la literatura se han estudiado distintos kernels. A continuación se listan algunos de los más utilizados:

Kernel lineal:

\[ k({\bf x},{\bf x^t}) = {\bf x}\cdot {\bf x}^t \]

Es el caso ya estudiado de clasificador de vector soporte.

Kernel polinómico:

\[ k({\bf x},{\bf x}^t) = \left({\bf x}\cdot {\bf x}^t+c\right)^d \]

Si \(d>1\) se generan límites de decisión no lineales. No se recomienda valores de \(d>5\) por problemas de sobreajuste.

Kernel Gausiano:

Se generan límites de decisión ovalados por medio de variables normales. \[ {\displaystyle K(\mathbf {x} ,\mathbf {x'} )=\exp \left(-{\frac {\|\mathbf {x} -\mathbf {x}^t \|^{2}}{2\gamma ^{2}}}\right)} \]

Los kernels descritos son solo unos pocos de los muchos que existen. Cada uno tiene una serie de hiperparámetros cuyo valor óptimo puede encontrarse mediante validación cruzada. No puede decirse que haya uno mejor que el resto, depende en gran medida de la naturaleza del problema que se esté tratando. Ahora bien, El kernel Gausiano suele ser bastante recomendando, ya que solo tiene dos hiperparámetros que optimizar (\(\gamma\) y la penalización \(C\), común a todos los SVM) y que su flexibilidad puede ir desde un clasificador lineal a uno muy complejo.

Ejemplo 2:

Para este ejemplo se emplea un conjunto de datos publicado en el libro Elements of Statistical Learning que contiene observaciones simuladas con una función no lineal en un espacio de dos dimensiones (2 predictores).

Solución paso a paso:

Paso 1

Preparación inicial y limpieza de los datos:

Descargamos la base de datos y convertimos la variable dependiente como facctor.

load(url("https://web.stanford.edu/~hastie/ElemStatLearn/datasets/ESL.mixture.rda"))
data2 <- data.frame(ESL.mixture$x, y = ESL.mixture$y)

# Transformamos a factor
data2$y <- as.factor(data2$y)

#Visualizamos
ggplot(data2, aes(x = X1, y = X2, color = y)) +
  geom_point(size =2.5) +
  theme_bw() +
  theme(legend.position = "none")

Paso 3

Aplicar el método de Clasificador de vector soporte

Para utilizar le kernel gausiano se coloca kernel = "radial", en cuyo caso hay que indicar el hiperparámetro parámetro \(\gamma\). Además de los hiperparámetros propios de cada kernel, todo SVM tiene también el hiperparámetro de penalización \(C\). Veamos como podemos por medio de validación cruzada encontrar los mejores parámetros.

library(e1071)
# Como los datos se han simulado en una misma escala, no es necesario estandarizarlos
set.seed(1234)
svm_vc <- tune("svm", y ~ X1 + X2, data = data2, kernel = "radial",
               ranges = list(cost = c(0.001, 0.01, 0.1, 1, 5, 10, 20),
                             gamma = c(0.5, 1, 2, 3, 4, 5, 10)))

svm_vc$best.parameters
##    cost gamma
## 32    1     4
modelo_svm_rbf <- svm_vc$best.model

De entre todos los modelos estudiados, empleando cost = 1 y gamma = 4 se logra conseguir el menor error.

Paso 5

Interpretación de los resultados finales

Veamos las predicciones conseguidas con este modelo.

pred2 <- predict(object = modelo_svm_rbf,data2)

# Matriz de confusión
MC2 <- table(pred2, data2$y)
MC2
##      
## pred2  0  1
##     0 84 10
##     1 16 90
# Tasa de aciertos
total  <- sum(MC2) 
TA2    <- sum(MC2[1,1]+MC2[2,2])/total
TA2
## [1] 0.87

Máquinas de vector soporte para más de dos clases

El concepto de hiperplano de separación en el que se basan los SVMs no se generaliza de forma natural para más de dos clases. Se han desarrollado numerosas estrategias con el fin de aplicar este método de clasificación a situaciones con \(k>2\), de entre ellos, los más empleados son: one-versus-one, one-versus-all y DAGSVM.

Uno contra uno (one-versus-one):

La estrategia de uno contra uno consiste en generar un total de \(K(K-1)/2\) SVMs, comparando todos los posibles pares de clases. Para generar una predicción se emplean cada uno de los \(K(K-1)/2\) clasificadores, registrando el número de veces que la observación es asignada a cada una de las clases. Finalmente, se considera que la observación pertenece a la clase a la que ha sido asignada con más frecuencia. La principal desventaja de esta estrategia es que el número de modelos necesarios se dispara a medida que aumenta el número de clases, por lo que no es aplicable en todos los escenarios.

Uno contra todos (one-versus-all):

Esta estrategia consiste en ajustar \(K\) SVMs distintos, cada uno comparando una de las \(K\) clases frente a las restantes \(K-1\) clases. Como resultado, se obtiene un hiperplano de clasificación para cada clase. Para obtener una predicción, se emplean cada uno de los \(K\) clasificadores y se asigna la observación a la clase para la que la predicción resulte positiva. Esta aproximación, aunque sencilla, puede causar inconsistencias, ya que puede ocurrir que más de un clasificador resulte positivo, asignando así una misma observación a diferentes clases. Otro inconveniente adicional es que cada clasificador se entrena de forma no balanceada. Por ejemplo, si el conjunto de datos contiene 100 clases con 10 observaciones por clase, cada clasificador se ajusta con 10 observaciones positivas y 990 negativas.

DAGSVM:

DAGSVM es una mejora del método uno contra uno. La estrategia seguida es la misma, pero consiguen reducir su tiempo de ejecución eliminando comparaciones innecesarias. Suponga un conjunto de datos con cuatro clases \((A, B, C, D)\) y 6 clasificadores entrenados con cada posible par de clases \((A-B, A-C, A-D, B-C, B-D, C-D)\). Se inician las comparaciones con el clasificador \((A-D)\) y se obtiene como resultado que la observación pertenece a la clase \(A\), o lo que es equivalente, que no pertenece a la clase \(D\). Con esta información se pueden excluir todas las comparaciones que contengan la clase \(D\), puesto que se sabe que no pertenece a este grupo. En la siguiente comparación se emplea el clasificador \((A-C)\) y se predice que es \(A\). Con esta nueva información se excluyen todas las comparaciones que contengan \(C\). Finalmente solo queda emplear el clasificador \((A-B)\) y asignar la observación al resultado devuelto. Siguiendo esta estrategia, en lugar de emplear los 6 clasificadores, solo ha sido necesario emplear 3. DAGSVM tiene las mismas ventajas que el método uno contra uno pero mejorando el rendimiento.

Ejemplo 3:

Solución paso a paso:

Paso 1

Preparación inicial y limpieza de los datos:

Para este tercer ejercicio, utilizamos la base de datos khan que se encuentra dentro de la librería ISLR . Esta base contiene información sobre 83 muestras pertenecientes a 4 tipos distintos de tumores. Para cada una de las muestras se dispone del perfil de expresión de 2308 genes. Se pretende crear un clasificador multiclase basado en SVMs que permita predecir el tipo de tumor en función de la expresión de los genes. El conjunto de datos está dividido en entrenamiento (xtrain, ytrain) y conjunto de prueba (xtest, ytest), 63 observaciones destinadas a entrenamiento y 20 a la evaluación del modelo.

library(ISLR)
data("Khan")

\[ \]

Paso 2

Dividir los datos en conjunto de entrenamiento y prueba

Esto datos ya vienen separados en entrenamiento y prueba, por lo tanto no fue necesario realizarla, pues se usará la que división que viene establecida.

dim(Khan$xtrain)
## [1]   63 2308
dim(Khan$xtest)
## [1]   20 2308

\[ \]

Paso 3

Aplicar el método de Clasificador de vector soporte

En este tipo de escenario, en el que el número de predictores es varios órdenes de magnitud mayor que el de observaciones, los modelos son propensos a sufrir sobreajuste. Esto sugiere que, de entre los diferentes tipos de kernels, sea adecuado emplear el de menor flexibilidad, el kernel lineal.

library(e1071)
y <-  as.factor(Khan$ytrain)
x <-  Khan$xtrain
entrenamiento <- data.frame(y,x)

svm_vc <- tune("svm", y ~ ., data = entrenamiento, kernel = 'linear',
               ranges = list(cost = c(0.0001,0.0005,0.01, 0.1, 1, 5, 10, 20)))

# Mejor modelo
modelo_svm <- svm_vc$best.model
modelo_svm
## 
## Call:
## best.tune(METHOD = "svm", train.x = y ~ ., data = entrenamiento, 
##     ranges = list(cost = c(1e-04, 5e-04, 0.01, 0.1, 1, 5, 10, 20)), 
##     kernel = "linear")
## 
## 
## Parameters:
##    SVM-Type:  C-classification 
##  SVM-Kernel:  linear 
##        cost:  0.01 
## 
## Number of Support Vectors:  58

\[ \]

Paso 5

Interpretación de los resultados finales

Calculamos la matriz de confusión para los datos de entrenamiento.

# Matriz de confusión
pred3 <-  modelo_svm$fitted
MC3   <- table(Prediccion = pred3, Clase_real = entrenamiento$y)
MC3
##           Clase_real
## Prediccion  1  2  3  4
##          1  8  0  0  0
##          2  0 23  0  0
##          3  0  0 12  0
##          4  0  0  0 20

El modelo obtenido tiene un error de entrenamiento de \(0\%\), es capaz de clasificar correctamente todas las observaciones empleadas para crearlo. Pero un error de entrenamiento muy bajo puede puede ser un indicativo de sobreajuste, lo que haría que el modelo no fuese capaz de predecir correctamente nuevas observaciones. Para evaluar si es este el caso, se emplea el modelo para predecir las 20 observaciones del conjunto de prueba.

yp <- as.factor(Khan$ytest)
xp <- Khan$xtest
prueba <- data.frame(yp,xp)
pred3p <- predict(modelo_svm,prueba)

MC3p   <- table(prediccion = pred3p, clase_real = prueba$yp)
MC3p
##           clase_real
## prediccion 1 2 3 4
##          1 3 0 0 0
##          2 0 6 2 0
##          3 0 0 4 0
##          4 0 0 0 5
TA     <- mean(prueba$yp == pred3p)
TA
## [1] 0.9

De las 20 observaciones de prueba, el modelo predice correctamente 18 y falla en 2, su tasa de aciertos es \(90\%\), mostrando que si el modelo llega a tener sobreajuste, no se ve afectado para clasificar.

\[ \]