Random forest es otra técnica de aprendizaje automático que construye múltiples árboles de decisión aleatorios no correlacionados a partir del subconjunto de datos de entrenamiento y agrupa sus resultados para proporcionar una decisión final o resultado, evitando el problema del sobreajuste. Este algorítmo también se utiliza para la clasificación y la regresión; el resultado final en caso de clasificación utiliza la predicción de la moda, mientras que utiliza la media en el caso de la regresión.
Relación entre CART y Random Forest. Es importante saber que Random Forest es simplemente una “bosque” compuesto por cientos de estos árboles CART. Mientras que un solo árbol CART puede ser inestable o cometer errores de precisión (overfitting), el Random Forest promedia muchos árboles para ser mucho más robusto.
En R se puede implementar este procedimiento de dos maneras:
El primer procedimiento es similar al visto en el caso de CART, y consiste en la conversión de la imagen en una matriz y la posterior aplicación de Random Forest usando el paquete randomForest.
La segunda es mediante una función integrada en el paquete RStoolbox directamente a la imagen original.
Ambos métodos serán analizados en esta sección. En primer lugar, deben activarse los paquetes necesarios para realizar este ejercicio.
library(randomForest) # Para ejecutar el primer procedimiento
## Warning: package 'randomForest' was built under R version 4.3.3
## randomForest 4.7-1.2
## Type rfNews() to see new features/changes/bug fixes.
library(caret) # Para la creación de la matriz de confusión
## Warning: package 'caret' was built under R version 4.3.3
## Loading required package: ggplot2
##
## Attaching package: 'ggplot2'
## The following object is masked from 'package:randomForest':
##
## margin
## Loading required package: lattice
## Warning: package 'lattice' was built under R version 4.3.2
Se comienza la actividad activando, si fuera necesario, los restantes paquetes
library(terra) # Importación y análisis de imágenes
## terra 1.8.93
library(RStoolbox) # Visualización de imágenes
## This is version 1.0.2.2 of RStoolbox
library(ggplot2) # Visualización de imágenes
library(sf) # Importación de datos formato vectorial
## Linking to GEOS 3.11.2, GDAL 3.8.2, PROJ 9.3.1; sf_use_s2() is TRUE
library(tidyverse)
## Warning: package 'tidyverse' was built under R version 4.3.2
## Warning: package 'readr' was built under R version 4.3.2
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.2.0 ✔ readr 2.1.5
## ✔ forcats 1.0.1 ✔ stringr 1.6.0
## ✔ lubridate 1.9.5 ✔ tibble 3.3.1
## ✔ purrr 1.2.1 ✔ tidyr 1.3.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::combine() masks randomForest::combine()
## ✖ tidyr::extract() masks terra::extract()
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ✖ purrr::lift() masks caret::lift()
## ✖ ggplot2::margin() masks randomForest::margin()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
A continuación se importa el fichero que contiene los valores de
reflectividad según coberturas de suelo con la función
read.csv2, a la que se añade el argumento
rownames = 1 para convertir la primera columna en nombres
de fila.
df_muestra <- read.csv2("D:/G174_2026/LABORATORIO_7_Clasificacion_imagenes_PIXEL/datos/supervisada/df_muestra.csv", row.names = 1)
Puede verficarse a continuación
df_muestra
También se importará la escena Landsat.
imagen <- rast("D:/G174_2026/LABORATORIO_7_Clasificacion_imagenes_PIXEL/datos/supervisada/imagen.tif")
imagen
## class : SpatRaster
## size : 1245, 1497, 7 (nrow, ncol, nlyr)
## resolution : 30, 30 (x, y)
## extent : 594090, 639000, 4190190, 4227540 (xmin, xmax, ymin, ymax)
## coord. ref. : WGS 84 / UTM zone 10N (EPSG:32610)
## source : imagen.tif
## names : coast~rosol, blue, green, red, NIR, SWIR1, ...
## min values : 0.09641791, 0.0748399, 0.04259216, 0.02084067, 0.0008457669, -0.007872183, ...
## max values : 0.73462820, 0.7177562, 0.69246972, 0.78617686, 1.0124315023, 1.043204546, ...
Para aplicar el paquete randomForest a la clasificación de una imagen, se deben convertir los valores de cada banda de esta última en una matriz.
imagen_matriz <- values(imagen, dataframe = TRUE) # Extraer los valores como una matriz (o data.frame)
imagen_matriz <- na.omit(imagen_matriz) # Eliminar los valores NA
A continuación, el dataframe df_muestra se dividirá en
dos subconjuntos, uno de entrenamiento (con el 70 % de los casos) y otro
de verificación (con el 30% restante).
# Crear el set de entrenamiento (70%)
df_entrenamiento <- df_muestra %>%
sample_frac(0.7)
# Crear el set de verificación con el resto (30%). Usamos anti_join para asegurar que no se repitan filas
df_verificacion <- df_muestra %>%
anti_join(df_entrenamiento)
## Joining with `by = join_by(categoria, blue, green, red, NIR, SWIR1, SWIR2)`
Ahora aplicaremos el modelo randomForest al conjunto de datos de
entrenamiento con, digamos, 2000 árboles de decisión (argumento
ntree) de la función randomForest(). Como el
propósito es la clasificación, el método de "Class" se
asigna en el argumento strata. La columna “Class” es la
variable dependiente (que debermos transformar en factor) mientras que
las columnas restantes son las independientes.
rf_mod_inicial <- randomForest(as.factor(categoria)~.,
data = df_entrenamiento,
ntree = 2000,
strata = "Class",
importance = T)
rf_mod_inicial
##
## Call:
## randomForest(formula = as.factor(categoria) ~ ., data = df_entrenamiento, ntree = 2000, strata = "Class", importance = T)
## Type of random forest: classification
## Number of trees: 2000
## No. of variables tried at each split: 2
##
## OOB estimate of error rate: 1.43%
## Confusion matrix:
## abierto agua barbecho cultivos urbano class.error
## abierto 42 0 0 0 0 0.0000000
## agua 0 57 0 0 0 0.0000000
## barbecho 0 0 13 0 0 0.0000000
## cultivos 0 0 0 9 0 0.0000000
## urbano 2 0 0 0 17 0.1052632
una de las mayores ventajas de Random Forest es su capacidad de autoevaluarse sin necesidad de un conjunto de datos externo. Cuando Random Forest construye cada uno de sus cientos de árboles, no utiliza todos tus datos de entrenamiento para cada árbol. En su lugar, toma una muestra aleatoria (técnica llamada bootstrapping). Los datos que se quedan fuera de ese árbol específico se llaman “Out-Of-Bag” (fuera de la bolsa). El algoritmo usa esos datos “sobrantes” para probar el árbol inmediatamente. Como consecuencia, la tasa de error OOB es una validación interna muy buena: si el error OOB es del 5%, significa que el modelo tiene una precisión esperada del 95% con datos nuevos.
A diferencia de otros modelos donde el analista genera la matriz al final, Random Forest proporciona una durante el entrenamiento. Esta matriz compara lo que el modelo predijo para esos datos “fuera de la bolsa” frente a su clase real, indicando qué clases se están confundiendo (por ejemplo, si está confundiendo “Urbano” con “Suelo desnudo”).
Para optimizar el número de árboles (ntree) se representa una gráfica de error. Al principio el error es alto y, a medida que añades árboles, el error cae drásticamente hasta que se estabiliza (se vuelve plano). El punto óptimo es aquel donde añadir más árboles ya no reduce el error: usar más árboles de los necesarios solo hace que el proceso sea más lento sin ganar precisión.
plot(rf_mod_inicial, lwd=rep(2, 3))
legend("right",
legend = c("OOB Error", "FPR", "FNR"),
lwd=rep(2, 3),
lty = c(1,2,3),
col = c("black", "red", "green"))
La gráfica muestra que la tasa de error es constante a partir de unos 700 árboles. Por lo tanto, es posible mejorar el modelo alterando el número de árboles.
rf_mod_final <- randomForest(as.factor(categoria)~.,
data = df_entrenamiento,
ntree = 700,
strata = "Class",
importance = T)
rf_mod_final
##
## Call:
## randomForest(formula = as.factor(categoria) ~ ., data = df_entrenamiento, ntree = 700, strata = "Class", importance = T)
## Type of random forest: classification
## Number of trees: 700
## No. of variables tried at each split: 2
##
## OOB estimate of error rate: 1.43%
## Confusion matrix:
## abierto agua barbecho cultivos urbano class.error
## abierto 42 0 0 0 0 0.0000000
## agua 0 57 0 0 0 0.0000000
## barbecho 0 0 13 0 0 0.0000000
## cultivos 0 0 0 9 0 0.0000000
## urbano 2 0 0 0 17 0.1052632
Al haber proporcionado un número óptimo de árboles, la estimación OOB de la tasa de error ha disminuido, por lo que se puede aplicar este último modelo entrenado sobre el conjunto de datos de verificación y evaluar su precisión.
rf_prediccion <- predict(rf_mod_final, df_verificacion)
Creación de la matriz de confusión del conjunto de datos de verificacion
confusionMatrix(rf_prediccion, as.factor(df_verificacion$categoria))
## Confusion Matrix and Statistics
##
## Reference
## Prediction abierto agua barbecho cultivos urbano
## abierto 14 0 0 0 0
## agua 0 21 0 0 0
## barbecho 1 0 8 0 0
## cultivos 0 0 0 10 0
## urbano 0 0 0 0 6
##
## Overall Statistics
##
## Accuracy : 0.9833
## 95% CI : (0.9106, 0.9996)
## No Information Rate : 0.35
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.9781
##
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: abierto Class: agua Class: barbecho Class: cultivos
## Sensitivity 0.9333 1.00 1.0000 1.0000
## Specificity 1.0000 1.00 0.9808 1.0000
## Pos Pred Value 1.0000 1.00 0.8889 1.0000
## Neg Pred Value 0.9783 1.00 1.0000 1.0000
## Prevalence 0.2500 0.35 0.1333 0.1667
## Detection Rate 0.2333 0.35 0.1333 0.1667
## Detection Prevalence 0.2333 0.35 0.1500 0.1667
## Balanced Accuracy 0.9667 1.00 0.9904 1.0000
## Class: urbano
## Sensitivity 1.0
## Specificity 1.0
## Pos Pred Value 1.0
## Neg Pred Value 1.0
## Prevalence 0.1
## Detection Rate 0.1
## Detection Prevalence 0.1
## Balanced Accuracy 1.0
La salida de la función confusionMatrix muestra claramente que la precisión de clasificación del modelo evaluado sobre el conjunto de datos de verificación es buena.
La importancia de cada variable independiente en el proceso de clasificación se obtiene creando el siguiente gráfico.
MeanDecrease Accuracy representa
cuánto disminuye la precisión del modelo si se omite esa variable en
particular.
MeanDecreaseGini mide la
importancia de la variable según el índice de Gini, que se utiliza para
calcular las divisiones en los árboles. Un valor alto indica una mayor
importancia de esa variable en el modelo.
Ambos gráficos muestran que la reflectancia en las bandas NIR y SWIR-1 tiene la mayor importancia en comparación con la reflectancia en las restantes bandas en el modelo desarrollado.
varImpPlot(rf_mod_final)
La matriz que refleja la importancia de cada variable en cada clase
se puede obtener de la función importance().
importance(rf_mod_final)
## abierto agua barbecho cultivos urbano MeanDecreaseAccuracy
## blue 8.934408 5.791187 14.17053 16.84382 22.84785 25.75628
## green 14.304054 8.148588 19.49085 14.96796 22.84940 19.71687
## red 19.840186 9.512785 13.68675 18.40315 13.02815 21.68146
## NIR 28.100774 21.826396 23.68766 23.23257 18.40018 30.52013
## SWIR1 35.933082 21.169826 22.01441 18.25820 17.78745 34.71597
## SWIR2 14.423152 14.585700 16.86767 13.38689 13.88361 19.05014
## MeanDecreaseGini
## blue 10.27008
## green 10.78394
## red 12.40887
## NIR 24.98732
## SWIR1 27.14967
## SWIR2 13.65014
El número de veces que cada variable es usada por el algorítmo
randomForest puede ser obtenido con la funcion varUsed.
varUsed(rf_mod_final)
## [1] 788 659 706 887 892 660
Si desea conocer el efecto de los valores de reflectancia de una banda particular al predecir una clase específica, se puede crear un diagrama de dependencia parcial. Este gráfico representa el efecto marginal de una variable en la probabilidad de una clase. Supongamos que desea verificar el efecto de la variación en los valores de reflectancia NIR al predecir la clase “cultivos” en el modelo.
partialPlot(rf_mod_final, df_entrenamiento, NIR, "cultivos")
O también urbano
partialPlot(rf_mod_final, df_entrenamiento, red, "urbano")
En este gráfico, podemos ver que cuando el valor de reflectancia NIR es superior a 0,35, existe la mayor probabilidad de predicción de la clase ‘cultivos’. De manera similar, se puede crear un gráfico de dependencia parcial de la clase “agua” en la banda roja
Si desea obtener un árbol de decisión único del modelo randomForest,
debe utilizarse la función getTree(). En el siguiente
ejemplo se extrae el primer árbol de decisión del modelo
desarrollado.
getTree(rf_mod_final, 1, labelVar = TRUE)
## left daughter right daughter split var split point status prediction
## 1 2 3 NIR 0.09201556 1 <NA>
## 2 0 0 <NA> 0.00000000 -1 agua
## 3 4 5 SWIR1 0.33029534 1 <NA>
## 4 6 7 blue 0.13289451 1 <NA>
## 5 8 9 green 0.18210104 1 <NA>
## 6 10 11 red 0.07130501 1 <NA>
## 7 0 0 <NA> 0.00000000 -1 urbano
## 8 0 0 <NA> 0.00000000 -1 abierto
## 9 0 0 <NA> 0.00000000 -1 urbano
## 10 0 0 <NA> 0.00000000 -1 cultivos
## 11 12 13 SWIR1 0.27961412 1 <NA>
## 12 0 0 <NA> 0.00000000 -1 barbecho
## 13 0 0 <NA> 0.00000000 -1 abierto
Preparar datos e índices
imagen_matriz_completa <- values(imagen, dataframe = TRUE)
indices_validos <- which(complete.cases(imagen_matriz_completa))
imagen_matriz_limpia <- na.omit(imagen_matriz_completa)
Clasificar
prediccion_rf <- predict(rf_mod_final,
imagen_matriz_limpia)
Crear el ráster y rellenar con IDs numéricos
raster_rf <- rast(imagen[[1]]) # Usamos la primera capa como molde
values(raster_rf) <- NA # Limpiar valores previos
raster_rf[indices_validos] <- as.numeric(prediccion_rf)
Comenzaremos asignando nombres a las clases. Para ello es necesario cConvertir la columna categoria a factor
df_entrenamiento$categoria <- as.factor(df_entrenamiento$categoria)
A continuación se extraen los nombres de las clases del modelo original
nombres_clases <- levels(df_entrenamiento$categoria)
print(nombres_clases)
## [1] "abierto" "agua" "barbecho" "cultivos" "urbano"
Se crea la tabla de niveles para el ráster
levels(raster_rf) <- data.frame(ID = 1:length(nombres_clases),
Clase = nombres_clases)
Finalmente, se dibujará la imagen con los píxeles clasificados según los resultados de randomForest.
ggR(raster_rf, geom_raster = TRUE) + # PLot with ggplot2
ggtitle("Clasificación con randomForest") + # Título
labs(x="Longitud (m)", y="Latitud (m)") + # Etiquetas de los ejes X e Y
theme(plot.title=element_text(hjust =0.5, # Título: alineación en el centro
size =30), # Título: tamaño
axis.title=element_text(size=20), # Etiquetas de los ejes: tamaño
legend.key.size=unit(1, "cm"), # Leyenda: tamaño.
legend.title=element_text(size =20), # Leyenda: tamaño del título.
legend.text =element_text(size =15)) + # Leyenda: tamaño del texto.
# Usamos scale_fill_manual para asignar colores exactos a los nombres
scale_fill_manual(values = c("grey", "burlywood", "wheat", "white", "darkgreen", "skyblue"),
name = "Categoría")
Para no favorecer la acumulación de objetos en el Global environment, se eliminan a continuación.
rm(list=ls())
Para aplicar Random Forest usando el paquete RStoolbox, se debe importar el fichero vectorial que contiene los sitios de entrenamiento.
puntos_entrenamiento_rf <- vect("D:/G174_2026/LABORATORIO_7_Clasificacion_imagenes_PIXEL/datos/supervisada/puntos_entrenamiento.geojson")
puntos_entrenamiento_rf
## class : SpatVector
## geometry : points
## dimensions : 200, 2 (geometries, attributes)
## extent : -121.922, -121.4192, 37.85161, 38.15608 (xmin, xmax, ymin, ymax)
## source : puntos_entrenamiento.geojson
## coord. ref. : lon/lat WGS 84 (EPSG:4326)
## names : id uso
## type : <chr> <chr>
## values : 1 cultivos
## 5 abierto
## 5 abierto
También se importará la escena Landsat.
imagen <- rast("D:/G174_2026/LABORATORIO_7_Clasificacion_imagenes_PIXEL/datos/supervisada/imagen.tif")
imagen
## class : SpatRaster
## size : 1245, 1497, 7 (nrow, ncol, nlyr)
## resolution : 30, 30 (x, y)
## extent : 594090, 639000, 4190190, 4227540 (xmin, xmax, ymin, ymax)
## coord. ref. : WGS 84 / UTM zone 10N (EPSG:32610)
## source : imagen.tif
## names : coast~rosol, blue, green, red, NIR, SWIR1, ...
## min values : 0.09641791, 0.0748399, 0.04259216, 0.02084067, 0.0008457669, -0.007872183, ...
## max values : 0.73462820, 0.7177562, 0.69246972, 0.78617686, 1.0124315023, 1.043204546, ...
Ambos deben estar proyectados en el mismo Sistema de referencia de coordenadas (CRS). Para confirmar si ambos ficheros comparten el mismo Sistema de coordenadas de referencia,
crs(imagen) == crs(puntos_entrenamiento_rf)
## [1] FALSE
Si poseyeran sistemas de proyección diferentes, se reproyectará el fichero en formato vectorial al mismo CRS del fichero raster.
puntos_entrenamiento_rf <- project(puntos_entrenamiento_rf, crs(imagen))
Verificar nuevamente que los CRS coincidan
identical(crs(imagen), crs(puntos_entrenamiento_rf))
## [1] TRUE
Para comprobar si los puntos caen dentro de la imagen, es decir, verificar la si la extensión (bounding box) de los puntos está contenida en la de la imagen:
ext(imagen) >= ext(puntos_entrenamiento_rf)
## [1] TRUE
Dado que la función Rstoolbox::superClass() requiere que
el fichero vectorial esté en formato sf, se procede a su
transformación.
puntos_entrenamiento_rf <- st_as_sf(puntos_entrenamiento_rf)
Una vez ambos tipos de ficheros comparten el mismo sistema de
proyección, se puede iniciar el procedimiento de clasificación. En la
función superClass() debe asignarse el argumento
model = "rf". Además, debe utilizarse un 70 % de los datos
para el entrenamiento y el 30 % de los datos restantes para la
verificación asignando el argumento trainPartition = 0.7.
La descripción de los restantes parámetros se proporciona en la sección
dedicada al procedimiento de máxima verosimilitud.
rf_mod_rstoolbox <- superClass(imagen,
trainData = puntos_entrenamiento_rf,
responseCol = "uso",
nSamples = 500,
model = "rf",
mode = "classification",
trainPartition = 0.7,
tuneLength = 3,
kfold =10)
rf_mod_rstoolbox
## superClass results
## ************ Validation **************
## $validation
## Confusion Matrix and Statistics
##
## Reference
## Prediction abierto agua barbecho cultivos urbano
## abierto 15 0 0 0 1
## agua 0 23 0 0 0
## barbecho 0 0 6 0 0
## cultivos 0 0 0 5 0
## urbano 0 0 0 0 6
##
## Overall Statistics
##
## Accuracy : 0.9821
## 95% CI : (0.9045, 0.9995)
## No Information Rate : 0.4107
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.9753
##
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: abierto Class: agua Class: barbecho Class: cultivos
## Sensitivity 1.0000 1.0000 1.0000 1.00000
## Specificity 0.9756 1.0000 1.0000 1.00000
## Pos Pred Value 0.9375 1.0000 1.0000 1.00000
## Neg Pred Value 1.0000 1.0000 1.0000 1.00000
## Prevalence 0.2679 0.4107 0.1071 0.08929
## Detection Rate 0.2679 0.4107 0.1071 0.08929
## Detection Prevalence 0.2857 0.4107 0.1071 0.08929
## Balanced Accuracy 0.9878 1.0000 1.0000 1.00000
## Class: urbano
## Sensitivity 0.8571
## Specificity 1.0000
## Pos Pred Value 1.0000
## Neg Pred Value 0.9800
## Prevalence 0.1250
## Detection Rate 0.1071
## Detection Prevalence 0.1071
## Balanced Accuracy 0.9286
##
## *************** Map ******************
## $map
## class : SpatRaster
## size : 1245, 1497, 1 (nrow, ncol, nlyr)
## resolution : 30, 30 (x, y)
## extent : 594090, 639000, 4190190, 4227540 (xmin, xmax, ymin, ymax)
## coord. ref. : WGS 84 / UTM zone 10N (EPSG:32610)
## source(s) : memory
## name : uso_supervised
## min value : 1
## max value : 5
La salida del código R muestra que el modelo randomForest desarrollado tiene muy buena precisión. Podemos ver los atributos de la imagen raster ya clasificada y las etiquetas correspondintes.
rf_mod_rstoolbox$map
## class : SpatRaster
## size : 1245, 1497, 1 (nrow, ncol, nlyr)
## resolution : 30, 30 (x, y)
## extent : 594090, 639000, 4190190, 4227540 (xmin, xmax, ymin, ymax)
## coord. ref. : WGS 84 / UTM zone 10N (EPSG:32610)
## source(s) : memory
## name : uso_supervised
## min value : 1
## max value : 5
rf_mod_rstoolbox$classMapping
## classID class
## 1 1 abierto
## 2 2 agua
## 3 3 barbecho
## 4 4 cultivos
## 5 5 urbano