El objetivo de este análisis es construir y evaluar modelos de redes neuronales para predecir el rendimiento de combustible de un automóvil (mpg) basándonos en sus características técnicas. Se comparará el mejor modelo de red neuronal contra un modelo de Regresión Lineal Múltiple para determinar cuál es más adecuado para este conjunto de datos.

library(stats)
library(psych)
library(MASS)
library(ISLR)
library(fRegression)
library(vcd)
## Loading required package: grid
## 
## Attaching package: 'vcd'
## The following object is masked from 'package:ISLR':
## 
##     Hitters
library(dplyr)
## 
## Attaching package: 'dplyr'
## The following object is masked from 'package:MASS':
## 
##     select
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(openxlsx)
library(mlbench)
library(magrittr)
library(neuralnet)
## 
## Attaching package: 'neuralnet'
## The following object is masked from 'package:dplyr':
## 
##     compute
library(keras)
## The keras package is deprecated. Use the keras3 package instead.
library(caret)
## Loading required package: ggplot2
## 
## Attaching package: 'ggplot2'
## The following objects are masked from 'package:psych':
## 
##     %+%, alpha
## Loading required package: lattice
library(png)
library(NeuralNetTools)

Leer base de datos

Se carga la base de datos mtcars. La variable a predecir es ‘mpg’ (Millas por galón).

# Cargamos el dataset 'mtcars'
data("mtcars") 
data <- mtcars
str(data)
## 'data.frame':    32 obs. of  11 variables:
##  $ mpg : num  21 21 22.8 21.4 18.7 18.1 14.3 24.4 22.8 19.2 ...
##  $ cyl : num  6 6 4 6 8 6 8 4 4 6 ...
##  $ disp: num  160 160 108 258 360 ...
##  $ hp  : num  110 110 93 110 175 105 245 62 95 123 ...
##  $ drat: num  3.9 3.9 3.85 3.08 3.15 2.76 3.21 3.69 3.92 3.92 ...
##  $ wt  : num  2.62 2.88 2.32 3.21 3.44 ...
##  $ qsec: num  16.5 17 18.6 19.4 17 ...
##  $ vs  : num  0 0 1 1 0 1 0 1 1 1 ...
##  $ am  : num  1 1 1 0 0 0 0 0 0 0 ...
##  $ gear: num  4 4 4 3 3 3 3 4 4 4 ...
##  $ carb: num  4 4 1 1 2 1 4 2 2 4 ...
head(data)
##                    mpg cyl disp  hp drat    wt  qsec vs am gear carb
## Mazda RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
## Mazda RX4 Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
## Datsun 710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1
## Hornet 4 Drive    21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
## Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2
## Valiant           18.1   6  225 105 2.76 3.460 20.22  1  0    3    1
# Verificamos que no haya NAs
colSums(is.na(data))
##  mpg  cyl disp   hp drat   wt qsec   vs   am gear carb 
##    0    0    0    0    0    0    0    0    0    0    0

Ajustar Red Neuronal

set.seed(13)
# Dividimos los datos en 80% entrenamiento y 20% prueba
train_idx <- createDataPartition(y = data$mpg, p = 0.8, list = FALSE, times = 1)
data_train <- data[train_idx,]
data_test <- data[-train_idx,]

# Entrenamos el primer modelo con datos sin escalar
RN <- neuralnet(mpg ~ .,
                data = data_train,
                hidden = c(6, 4), # Arquitectura adaptada para mtcars
                linear.output = TRUE,
                lifesign = 'full',
                threshold = 0.05,
                rep = 1)
## hidden: 6, 4    thresh: 0.05    rep: 1/1    steps:      90   error: 254.39195    time: 0.03 secs

Explicación e Interpretación set.seed(13): Se establece una semilla para que los resultados aleatorios (como la inicialización de pesos de la red y la partición de datos) sean reproducibles.

createDataPartition: Dividimos el dataset en un 80% para entrenamiento (data_train) y un 20% para prueba (data_test). Esto es fundamental para evaluar si nuestro modelo generaliza bien a datos que no ha visto antes.

neuralnet: Entrenamos un primer modelo de red neuronal (RN).

Fórmula: mpg ~ . indica que mpg es la variable objetivo y el resto de columnas son las predictoras.

hidden = c(6, 4): Esta es la arquitectura elegida para este primer modelo: dos capas ocultas, la primera con 6 neuronas y la segunda con 4.

linear.output = TRUE: Es un parámetro crucial. Se establece en TRUE porque estamos en un problema de regresión (predecir un valor numérico continuo) y no de clasificación.

Nota importante: Este modelo se entrena con los datos originales, sin estandarizar. Es una primera aproximación, pero la buena práctica para redes neuronales es estandarizar las variables, como se hará a continuación.

Estandarización de Variables

# Separamos predictores y variable objetivo.
# NOTA: En `mtcars`, 'mpg' es la columna 1, a diferencia de 'medv' en 'Boston'.
training_predictors <- as.matrix(data_train[, -1]) 
training_target <- data_train[, 1] # Se extrae como vector, no matriz

test_predictors <- as.matrix(data_test[, -1])
test_target <- data_test[, 1]

# Estandarizamos los predictores usando la media y desv. estándar de los datos de entrenamiento
m <- colMeans(training_predictors)
s <- apply(training_predictors, 2, sd)
training_scaled <- scale(training_predictors, center = m, scale = s)
test_scaled <- scale(test_predictors, center = m, scale = s)

¿Por qué estandarizar?: Las variables del dataset (disp, hp, wt, etc.) tienen escalas muy diferentes. La estandarización (o escalado) convierte todas las variables a una escala común (media 0, desviación estándar 1). Esto ayuda a que el algoritmo de entrenamiento de la red neuronal converja más rápido y de manera más estable, evitando que las variables con magnitudes más grandes dominen el proceso de aprendizaje.

Proceso:

Se calculan la media (m) y la desviación estándar (s) únicamente de los datos de entrenamiento.

Se usan estos mismos valores (m y s del conjunto de entrenamiento) para escalar tanto el conjunto de entrenamiento como el de prueba. Esto es vital para evitar “filtrar” información del conjunto de prueba al de entrenamiento, lo que garantiza una evaluación del modelo más realista y honesta.

Reajustar modelos con Variables Estandarizadas

# Creamos un nuevo dataframe con los predictores escalados y el objetivo también escalado
# Se une de esta forma para que la fórmula "mpg ~ ." funcione correctamente.
data_train_S <- as.data.frame(training_scaled)
data_train_S$mpg <- (training_target - mean(training_target)) / sd(training_target)


# Modelo 1 Estándar (RNS): Arquitectura 6,4
RNS <- neuralnet(mpg ~ .,
                 data = data_train_S,
                 hidden = c(6, 4),
                 linear.output = TRUE,
                 lifesign = 'full',
                 rep = 1,
                 stepmax = 200000)
## hidden: 6, 4    thresh: 0.01    rep: 1/1    steps:     550   error: 0.04339  time: 0.2 secs
# Modelo 2 Estándar (RNS2): Arquitectura 5 (una sola capa)
RNS2 <- neuralnet(mpg ~ .,
                  data = data_train_S,
                  hidden = 5,
                  linear.output = TRUE,
                  lifesign = 'full',
                  rep = 1,
                  stepmax = 200000)
## hidden: 5    thresh: 0.01    rep: 1/1    steps:     794  error: 0.00529  time: 0.13 secs

Ahora que tenemos los datos estandarizados, volvemos a entrenar las redes neuronales.

Se crea un nuevo dataframe data_train_S que contiene tanto los predictores como la variable objetivo (mpg) escalados. Esto se hace para que el modelo neuralnet pueda optimizar mejor sus pesos internos.

Visualizar la Red Neuronal ajustada (Modelo 1)

plotnet(RNS, 
        circle_col = 'lightblue', 
        arrow_col = 'darkgreen',
        node_labs = TRUE,
        show_weights = TRUE,  
        cex_val = 0.7)        
Visualización del Modelo 1 (RNS)

Visualización del Modelo 1 (RNS)

Visualizar la Red Neuronal ajustada (Modelo 2)

plotnet(RNS2, 
        circle_col = 'lightblue', 
        arrow_col = 'darkgreen',
        node_labs = TRUE,
        show_weights = TRUE,  
        cex_val = 0.7)        
Visualización del Modelo 2 (RNS2)

Visualización del Modelo 2 (RNS2)

Realizar Predicciones (Modelo 1)

# Preparamos los datos de prueba estandarizados
data_test_S <- as.data.frame(test_scaled)

# Predecimos usando el modelo RNS2
RNS_Predictions_S <- predict(RNS, data_test_S)

# "Des-estandarizamos" las predicciones para poder compararlas con los valores reales
RNS_Pred <- RNS_Predictions_S * sd(training_target) + mean(training_target)

# Calculamos las métricas de rendimiento para la Red Neuronal
RSS_nn <- sum((RNS_Pred - test_target)^2)
MSE_nn <- RSS_nn / length(test_target) 
R2_nn <- 1 - RSS_nn / sum((test_target - mean(training_target))^2)

print("--- Métricas Red Neuronal (RNS) ---")
## [1] "--- Métricas Red Neuronal (RNS) ---"
print(paste("MSE:", MSE_nn))
## [1] "MSE: 56.2216245560376"
print(paste("R-cuadrado:", R2_nn))
## [1] "R-cuadrado: -0.00506075824237473"

Des-estandarización: Para que las predicciones sean comparables con los valores reales de mpg, revertimos el proceso de escalado multiplicando por la desviación estándar original y sumando la media original de training_target. Esto nos da las predicciones en la escala original de millas por galón (RNS_Pred).

Un valor R² negativo en una red neuronal o modelo de regresión indica que el modelo se ajusta tan mal a los datos que es peor que una simple predicción del valor promedio de la variable objetivo.

Realizar Predicciones (Modelo 2)

# Preparamos los datos de prueba estandarizados
data_test_S <- as.data.frame(test_scaled)

# Predecimos usando el modelo RNS2
RNS2_Predictions_S <- predict(RNS2, data_test_S)

# "Des-estandarizamos" las predicciones para poder compararlas con los valores reales
RNS2_Pred <- RNS2_Predictions_S * sd(training_target) + mean(training_target)

# Calculamos las métricas de rendimiento para la Red Neuronal
RSS_nn2 <- sum((RNS2_Pred - test_target)^2)
MSE_nn2 <- RSS_nn2 / length(test_target) 
R2_nn2 <- 1 - RSS_nn2 / sum((test_target - mean(training_target))^2)

print("--- Métricas Red Neuronal (RNS2) ---")
## [1] "--- Métricas Red Neuronal (RNS2) ---"
print(paste("MSE:", MSE_nn2))
## [1] "MSE: 32.7931158441729"
print(paste("R-cuadrado:", R2_nn2))
## [1] "R-cuadrado: 0.413765181356094"

MSE (Error Cuadrático Medio): 32.79 R-cuadrada: 0.4137. No indica rendimiento alto.

Modelo de Regresión Lineal Múltiple (LRM)

# Entrenamos el modelo de regresión lineal
LRM <- lm(mpg ~ ., data = data_train)
LRMPred <- predict(LRM, data_test[, -1])

# Calculamos las métricas de rendimiento para la Regresión Lineal
RSS_lrm <- sum((LRMPred - test_target)^2)
MSE_lrm <- RSS_lrm / length(test_target)
R2_lrm <- 1 - RSS_lrm / sum((test_target - mean(training_target))^2)

print("--- Métricas Regresión Lineal (LRM) ---")
## [1] "--- Métricas Regresión Lineal (LRM) ---"
print(paste("MSE:", MSE_lrm))
## [1] "MSE: 8.31027951301478"
print(paste("R-cuadrado:", R2_lrm))
## [1] "R-cuadrado: 0.851439087815193"

MSE: 8.31. Este valor es significativamente más bajo que el 32.79 de la red neuronal, lo que indica que los errores de la regresión son mucho menores.

R-cuadrada: 0.8514. Este resultado es excelente. Significa que el modelo lineal es capaz de explicar el 85.1% de la variabilidad en mpg, un rendimiento muy superior al de la red neuronal.

Parece ser que el modelo 2 de 5 neuronas es mejor que el modelo 1. Por lo cual este será el que se compara contra el modelo de regresión lineal múltiple

Comparar modelos gráficamente

# dev.off() 
plot(data_test$mpg, RNS2_Pred, col = 'red', main = 'Valores Reales vs. Predicciones',
     xlab = "MPG Real", ylab = "MPG Predicho", pch = 19, cex = 1)
points(data_test$mpg, LRMPred, col = 'blue', pch = 15, cex = 1)
abline(0, 1, lwd = 2) # Línea de referencia y=x
legend('bottomright', legend = c('Red Neuronal (5)', 'Regresión Lineal'), 
       pch = c(19, 15), col = c('red', 'blue'))

Comparación de modelos

# El comando par(mfrow=c(1,2)) configura el área de visualización
# para mostrar los próximos gráficos en una cuadrícula de 1 fila y 2 columnas.
par(mfrow=c(1,2))

# --- Gráfico 1: Red Neuronal ---
plot(data_test$mpg, RNS2_Pred, 
     col='red', 
     main='Real vs. Predicho (Red Neuronal)',
     xlab="MPG Real", 
     ylab="MPG Predicho",
     pch=19, 
     cex=1)
abline(0, 1, lwd=2) # Línea de referencia y=x
legend('bottomright', legend='NN', pch=18, col='red', bty='n')

# --- Gráfico 2: Regresión Lineal ---
plot(data_test$mpg, LRMPred, 
     col='blue', 
     main='Real vs. Predicho (Regresión)',
     xlab="MPG Real", 
     ylab="MPG Predicho",
     pch=15, 
     cex=1)
abline(0, 1, lwd=2) # Línea de referencia y=x
legend('bottomright', legend='RLM', pch=18, col='blue', bty='n', cex=.95)

# (Opcional) Restablece la configuración para que los siguientes gráficos se muestren de uno en uno.
par(mfrow=c(1,1))

El Modelo Lineal es superior para este conjunto de datos, el modelo de Regresión Lineal Múltiple es considerablemente mejor que la red neuronal. Tanto las métricas (MSE y R²) como el análisis visual de los gráficos lo confirman.

Las redes neuronales son herramientas muy poderosas, pero generalmente requieren una gran cantidad de datos para aprender patrones complejos y no lineales.