Clasificación de Aves a través de Espectrogramas

Descripción de población y muestra

Para el desarrollo del presente proyecto, se eligió un conjunto de datos que contiene imágenes de espectrogramas previamente procesados, disponibles en el sitio web Kaggle. Los audios utilizados para generar los espectrogramas, fueron obtenidos del sitio web Xeno-Canto, en el cual se encuentran registros de sonidos de toda clase de fauna alrededor del mundo. Los audios con los cantos de las aves fueron convertidos en espectrogramas utilizando una transformada de Fourier de tamaño 2048 y luego se les aplicó una transformación logarítmica.

Los espectrogramas consisten en una representación visual que muestra cómo se distribuyen las frecuencias en una señal de sonido. Esta representación gráfica puede revelar detalles específicos, como frecuencias elevadas o cambios en la amplitud, que podrían no ser perceptibles incluso si se encuentran dentro del rango auditivo humano.

La muestra inicial de datos para este proyecto son espectrogramas de 152 especies de aves de la población total de especies de aves presentes alrededor del mundo, con audios tomados en distintas fechas y por diferentes autores.

Durante la realización del análisis exploratorio, y por limitaciones para aplicar técnicas de balanceo de datos, se seleccionan como muestra las 6 especies con mayor cantidad de datos.

Diccionario de variables

Los datos utilizados en el proyecto son imágenes de espectrogramas de 152 especies de aves presentes alrededor del mundo. Como se verá más adelante, la cantidad de espectrogramas para cada especie no es igual, por lo que estamos ante un conjunto de datos desbalanceado.

  • Variable dependiente: Etiquetas con la abreviatura del nombre común de las aves.

  • Variables explicativas: Vector de características extraídas de imágenes de espectrogramas del espectro de frecuencias de la emisión sonora del canto de las aves.

Los espectrogramas, se utilizaron para extraer patrones visuales y características importantes presentes en las imágenes, que permitan identificar las especies de aves. Estas características son extraídas al momento de implementar los modelos, y no se tienen de antemano.

Algunas de las características extraídas de los espectrogramas por los modelos son:

  • Estadísticas de color: Los estadísticos de distribución de color son comunes en la recuperación de imágenes, ya que describen la variación de la intensidad del color en una imagen. En el caso de los espectrogramas de sonido, estos estadísticos se aplican a cada imagen monocroma, permitiendo describir cómo varía la intensidad del sonido en regiones definidas en términos de tiempo y frecuencia.

  • Direccionalidad: La direccionalidad de la imagen es una característica importante para describir la textura de una imagen. Describe la dirección en la que se concentra o dispersa la textura de la imagen.

Se tiene tambien metada sobre los audio de las aves, entre la cual se puede encontrar los siguientes datos:

  • primary_label: representa el código de la especie de ave. Será la etiqueta que se utilice para identificar la especie.
  • secondary_labels: especies de fondo anotadas por el autor de la grabación. Una lista vacía no significa que no haya aves de fondo audibles, solo que no fueron identificadas.
  • author: el usuario de eBird que provee la grabación.
  • rating: Valor flotante entre 0,0 y 5,0 como indicador de la calificación de calidad en Xeno-canto y el número de especies de fondo, donde 5,0 es el más alto y 1,0 el más bajo. 0,0 significa que esta grabación aún no tiene calificación de usuario.
  • type: Detalles sobre el audio y la especie grabada. Puede tener información sobre el tipo de sonido emitido por la ave, canto o llamado, también información sexo del ave, o si es un adulto o juvenil, entre otro tipo de información.
  • latitude y longitude: coordenadas geográficas del lugar donde fue grabado el audio.
  • scentific_name: nombre científico de la ave.
  • common_name: nombre común del ave en ingles.
  • time: hora del día en la que fue tomada la grabación.

Analisis Exploratorio (EDA)

Para la exploración de los datos, se utilizaron los audios originales con los cuales se generaron los espectogramas, ya que estos nos podrian ayudar a entender las diferencias entre los cantos de las aves, y como esto puede ayudar a identificarlas.

library(readr)
library(plotly)
library(dplyr)
library(knitr)
library(stringr)
library(av)
data <- read_csv("C:/Users/kaes1/Desktop/MachineLearningUN/Proyecto/train_metadata.csv")
head(data) %>% kable()
primary_label secondary_labels type latitude longitude scientific_name common_name author license rating time url filename
afrsil1 [] [‘call’, ‘flight call’] 12.3910 -1.4930 Euodice cantans African Silverbill Bram Piot Creative Commons Attribution-NonCommercial-ShareAlike 3.0 2.5 08:00 https://www.xeno-canto.org/125458 afrsil1/XC125458.ogg
afrsil1 [‘houspa’, ‘redava’, ‘zebdov’] [‘call’] 19.8801 -155.7254 Euodice cantans African Silverbill Dan Lane Creative Commons Attribution-NonCommercial-ShareAlike 4.0 3.5 08:30 https://www.xeno-canto.org/175522 afrsil1/XC175522.ogg
afrsil1 [] [‘call’, ‘song’] 16.2901 -16.0321 Euodice cantans African Silverbill Bram Piot Creative Commons Attribution-NonCommercial-ShareAlike 4.0 4.0 11:30 https://www.xeno-canto.org/177993 afrsil1/XC177993.ogg
afrsil1 [] [‘alarm call’, ‘call’] 17.0922 54.2958 Euodice cantans African Silverbill Oscar Campbell Creative Commons Attribution-NonCommercial-ShareAlike 4.0 4.0 11:00 https://www.xeno-canto.org/205893 afrsil1/XC205893.ogg
afrsil1 [] [‘flight call’] 21.4581 -157.7252 Euodice cantans African Silverbill Ross Gallardy Creative Commons Attribution-NonCommercial-ShareAlike 4.0 3.0 16:30 https://www.xeno-canto.org/207431 afrsil1/XC207431.ogg
afrsil1 [] [‘flight call’] 21.4581 -157.7252 Euodice cantans African Silverbill Ross Gallardy Creative Commons Attribution-NonCommercial-ShareAlike 4.0 4.0 16:30 https://www.xeno-canto.org/207432 afrsil1/XC207432.ogg

La metadata contiene 13 columnas de información y 14852 registros.

dim(data)
[1] 14852    13
data %>% colnames()
 [1] "primary_label"    "secondary_labels" "type"             "latitude"        
 [5] "longitude"        "scientific_name"  "common_name"      "author"          
 [9] "license"          "rating"           "time"             "url"             
[13] "filename"        
  • Verificación de datos faltantes:
colSums(is.na(data)) %>%  kable()
x
primary_label 0
secondary_labels 0
type 0
latitude 0
longitude 0
scientific_name 0
common_name 0
author 0
license 0
rating 0
time 0
url 0
filename 0

La información de la metadata del conjunto de datos, tiene información sobre el tipo de canto de la especie (llamado, canción, alerta), las coordenadas en las cuales se realizó la grabación del audio, quien fue el autor de la grabación, la duración de la grabación, entre otros datos.

summary(data)
##  primary_label      secondary_labels       type              latitude     
##  Length:14852       Length:14852       Length:14852       Min.   :-71.88  
##  Class :character   Class :character   Class :character   1st Qu.: 28.26  
##  Mode  :character   Mode  :character   Mode  :character   Median : 40.87  
##                                                           Mean   : 35.78  
##                                                           3rd Qu.: 51.12  
##                                                           Max.   : 78.98  
##    longitude        scientific_name    common_name           author         
##  Min.   :-176.632   Length:14852       Length:14852       Length:14852      
##  1st Qu.: -96.164   Class :character   Class :character   Class :character  
##  Median :  -6.061   Mode  :character   Mode  :character   Mode  :character  
##  Mean   : -30.218                                                           
##  3rd Qu.:  10.898                                                           
##  Max.   : 179.361                                                           
##    license              rating          time               url           
##  Length:14852       Min.   :0.000   Length:14852       Length:14852      
##  Class :character   1st Qu.:3.000   Class :character   Class :character  
##  Mode  :character   Median :4.000   Mode  :character   Mode  :character  
##                     Mean   :3.719                                        
##                     3rd Qu.:4.500                                        
##                     Max.   :5.000                                        
##    filename        
##  Length:14852      
##  Class :character  
##  Mode  :character  
##                    
##                    
## 

Las especies seleccionadas para la clasificación son:

birds<-list('brnowl', 'comsan', 'houspa', 'mallar3', 'norcar','skylar')

Especies

Tyto alba (brnowl)

Búho pálido, blanco abajo y anaranjado con manchas grises arriba. Disco facial en forma de corazón blanco. Caza principalmente roedores en áreas abiertas por la noche. Duerme en edificios viejos y cajas de nidos durante el día. Ampliamente distribuido en todos los continentes, excepto en la Antártica. Por la noche, parece muy blanco en faros. Su llamado es apropiado para Halloween: un chillido escalofriante.

Actitis hypoleucos (comsan)

Migradora parcial en la península ibérica con individuos invernantes, en paso e incluso veraneantes procedentes de las poblaciones europeas del norte y centro. Escasa población reproductora. Aparecen en una amplia variedad de hábitat acuáticos, pero selecciona para reproducirse orillas pedregosas de ríos o lagos. Generalmente individuos solitarios, pero también pueden observarse pequeños grupos. Evita bandos mixtos con otras especies. Camina con un distintivo y constante movimiento “arriba-abajo” de la cola, tipo wagtail. Tiene plumaje pardusco en el dorso, donde destaca el plumaje blanco del hombro que se ve como una pequeña franja blanca entre el ala plegada y el pecho.

Anas platyrhynchos (mallar3)

El típico pato dentro de su rango, se encuentra en cualquier lugar donde hay agua, incluyendo parques de ciudades, arroyos en el jardín y diferentes humedales. Los machos tienen cabeza verde, pecho castaño y cuerpo gris. Las hembras son marrones moteadas con manchas anaranjadas y negras en el pico. Las barras blancas enfrente y atrás del parche azul del ala son más conspicuas comparadas a las de American Black Duck y el Mottled Duck.

Alauda arvensis (skylar)

Bastante común en campos abiertos, especialmente pastizales en terrenos escabrosos, páramos, tierras de cultivo, aeropuertos. Como la mayoría de los aláudidos, normalmente es poco visible en el suelo y mejor detectada por la voz. Posee un canto prolongado en vuelo, compuesto de gorjeos y trinos, a menudo tan alto que apenas se ve una mancha en el cielo, si es que está visible. Si está posada, destaca el plumaje rayado marrón, cresta tupida distintiva, y anillo ocular ancho, difuso y pálido. Más corpulenta que un bisbita pero y a diferencia de ésta no suele menear su parte trasera. En vuelo muestra lados blancos de la cola, así como un borde posterior blanco y estrecho en las alas.

Cardinalis cardinalis (norcar)

El cardenal norteño es un pájaro cantor de tamaño medio con una longitud corporal de 21-23 cm. Tiene un distintivo penacho y una máscara en la cara que es negra en el macho y gris en la hembra. Presenta dimorfismo sexual en su coloración; el plumaje del macho es rojo brillante, mientras que el de la hembra es de un tono opaco que mezcla rojo y café. El cardenal norteño es predominantemente granívoro, pero también se alimenta de insectos y fruta. El macho tiene un comportamiento territorial, delimitando su territorio con su canto

Passer domesticus (houspa)

Ampliamente distribuido y abundante en ciudades, vecindarios y granjas. Evita bosques densos. Las bandadas se agrupan en densos arbustos, moviéndose y vocalizando entre ellos. Los machos tienen baberos negros elegantes, nuca rufa brillante, y alas con brillantes tonos marrones y caqui. Las partes inferiores son gris pálido. Las hembras son marrón uniforme con una cara linda y ceja más clara. Nativo de Eurasia; introducido a Norteamérica y no está estrechamente relacionado con otros gorriones de allí.

Filtramos la data para obtener los registros de las especies que nos interesan, como también aquellas columnas con información relevante:

sub_data <- data %>% 
  select(-c('author', 'license','url',
                                 'filename')) %>% 
  filter(primary_label %in% birds)
head(sub_data) %>% kable()
primary_label secondary_labels type latitude longitude scientific_name common_name rating time
brnowl [] [‘alarm call’] 52.3009 6.7620 Tyto alba Barn Owl 4 21:30
brnowl [] [‘song’] -20.7900 -42.8882 Tyto alba Barn Owl 4 20:30
brnowl [] [‘call’] 18.5206 73.8398 Tyto alba Barn Owl 4 23:09
brnowl [] [’‘, ’clicks’] -21.5500 -47.7167 Tyto alba Barn Owl 4 05:30
brnowl [] [‘begging call’] 0.0640 32.4790 Tyto alba Barn Owl 4 23:30
brnowl [] [‘begging call’, ‘call’] 0.0640 32.4790 Tyto alba Barn Owl 5 00:30
# c('secondary_labels','type', 'author', 'license','url',
#                                  'filename')
sub_data <- sub_data %>% 
  rename(etiqueta=primary_label, 
         etiqueta2=secondary_labels,
         latitud=latitude,
         type=type,
         longitud=longitude,
         Nombre_cientifico=scientific_name,
         Nombre_comun=common_name,
         tiempo=time)
head(sub_data) %>%  kable()
etiqueta etiqueta2 type latitud longitud Nombre_cientifico Nombre_comun rating tiempo
brnowl [] [‘alarm call’] 52.3009 6.7620 Tyto alba Barn Owl 4 21:30
brnowl [] [‘song’] -20.7900 -42.8882 Tyto alba Barn Owl 4 20:30
brnowl [] [‘call’] 18.5206 73.8398 Tyto alba Barn Owl 4 23:09
brnowl [] [’‘, ’clicks’] -21.5500 -47.7167 Tyto alba Barn Owl 4 05:30
brnowl [] [‘begging call’] 0.0640 32.4790 Tyto alba Barn Owl 4 23:30
brnowl [] [‘begging call’, ‘call’] 0.0640 32.4790 Tyto alba Barn Owl 5 00:30

Se tienen 3000 registros en total de seis especies de aves:

dim(sub_data)
## [1] 3000    9

Distribución de los datos

Primary_label

Inicialmente se tienen registros de espectrogramas para 152 especies de aves. La distribución de datos para cada especie se puede observar a continuación:

# Frecuencia de cada especie de ave
library(forcats)
fig <- data %>% 
  count(primary_label) %>% 
  mutate(primary_label = fct_reorder(primary_label, n, .desc = T)) %>% 
    plot_ly(x=~primary_label, y=~n,
            marker = list(color = 'rgb(118, 215, 196)',
                      line = list(color = 'rgb(14, 102, 85)',
                                  width = 1.5))) %>% 
  add_bars()
fig %>% 
  layout(title = list(text="Frecuencia de Especies de aves", y = 0.95),
         xaxis = list(title = 'Especies'),
         yaxis = list(title = 'Frecuencia'))

Solo hay 6 especies de aves con 500 registros. Por esta razón, y la imposibilidad actual de aplicar técnicas de aumetación de datos para los datos, es que se decide trabajar con estás 6 especies , ya que son las que tienen mayor cantidad de datos.

De aquí en adelante los análisis se realizarán solo para estás 6 especies.

secondary_label

La etiqueta secundaria es una etiqueta asignada por la persona que generó la grabación, indicando que el audio podría tener más de una etiqueta o clasificación, debido a la presencia de otras sonidos de aves en la grabación, principalmente aves que se encuentran en el trasfondo de la grabación.

# Determinar si existe una etiqueta secundaria
sub_data$etiqueta2 <- str_replace_all(sub_data$etiqueta2, "[\\[\\]']", "")
sub_data$et2_count <- lengths(strsplit(sub_data$etiqueta2, ','))
sub_data %>% 
  group_by(et2_count) %>% 
  summarise(conteo = n()) %>%  kable()
et2_count conteo
0 2814
1 165
2 16
3 3
4 2

La mayoría de audios no registran sonidos de más de una especie de ave.

rating

La variable de rating permite identificar la calidad que tiene el audio con referencia al sonido emitido por el ave. En el siguiente grafico se puede observar, que la mayoría de audios tienen un puntaje de 4, seguido por un puntaje de 5, lo que indica que los audios con los que se trabajará son adecuados para entrenar el modelo predictivo.

library(forcats)

fig <- sub_data %>% 
  count(rating) %>% 
  # mutate(rating = fct_reorder(rating, n, .desc = T)) %>% 
    plot_ly(x=~rating, y=~n, 
            marker = list(color = 'rgb(118, 215, 196)',
            line = list(color = 'rgb(14, 102, 85)',
                        width = 1.5))) %>% 
  add_bars()
fig %>% 
  layout(title = list(text="Puntaje del audio", y = 0.95),
         xaxis = list(title = 'Rating'),
         yaxis = list(title = 'Frecuencia'))

En el siguiente grafico se puede observar como se distribuye el puntaje por especie de ave. Para todas las ves, la media del puntaje es 4, y todas, a excepción de skylar, tiene mas del 50% de los datos con un puntaje igual o superior a 4. Para el caso de la especie skylar, alrededor del 75% de los audios, tienen una puntuación igual o inferior a 4, por lo que está ave podría ser la que tenga desventaja en el modelo, al momento de realizar el entrenamiento, pero esto se verificará al entrenar el modelo y evaluar los resultados.

library(forcats)

fig <- sub_data %>% 
  # mutate(rating = fct_reorder(rating, n, .desc = T)) %>% 
    plot_ly(x=~rating, type = "box", color=~etiqueta) 
fig %>% 
  layout(title = list(text="Puntaje del audio", y = 0.95),
         xaxis = list(title = 'Rating'),
         yaxis = list(title = 'Frecuencia'))

Tiempo

Esta variable representa la hora del día en la que se registró la grabación. Con base en esta variable, se creará una variable categórica que indique el momento del día en que se tomo la grabación: madrugada, mañana, tarde, noche.

library(lubridate)
sub_data$hora <- hour(hm(sub_data$tiempo))
sub_data <- sub_data %>% 
  mutate(etapaDia = case_when(
                              hora >= 0 & hora <=6 ~"Madrugada",
                              hora > 6 & hora <= 12 ~"Mañana",
                              hora > 12 & hora <= 18 ~"Tarde",
                              hora > 18~"Noche",
                            T ~"No especifica"))
fig <- sub_data %>% 
  group_by(etiqueta, etapaDia) %>% 
  summarise(conteo=n()) %>% 
  plot_ly(x = ~etapaDia, y = ~conteo, color = ~etiqueta) %>% 
    layout(title = list(text="Momento del dia en que se tomo el audio", y = 0.95),
         xaxis = list(title = 'Momento del dia'),
         yaxis = list(title = 'Frecuencia'))

fig

Se logra apreciar que para el caso de la especie brnnowl, la mayoría de las grabaciones fueron tomadas en horas de la madrugada, seguido por la noche, lo que es acorde al hecho a que está especie es nocturna. Similar ocurre con la especie comsan, que tambien tiene mayor registro de audio en esos momentos del día, aunque esta especie no es nocturna, por lo que se esperaría mayor registro de audio en momentos como la mañana en lugar de la noche. Para las demás especie, el momento del día en que mayor cantidad de audio se registraron fue en la mañana, seguido de la madrugada.

Distribución Geográfica (MAPA)

Al tener el registro de latitud y longitud de los audios, se puede plasmar un mapa que ayude a observar la distribución geográfica de las especies.

# Mapa de distribución geográfica de las especies

fig <- plot_geo(sub_data, lat = ~latitud, lon = ~longitud)
fig <- fig %>% add_markers(
    text = ~paste(paste("Nombre Común:", Nombre_comun), paste("Nombre Científico:", Nombre_cientifico), sep = "<br />"),
    color = ~etiqueta, symbol = I("circle"), size = I(8), hoverinfo = "text", colors = "viridis"
  ) %>% 
  layout(showlegend=T,
          title = 'Distribución geográfica de las aves', 
         geo = list(showland= T, landcolor = toRGB("gray95"),
                    showcountries = T, subunitcolor = toRGB("gray85"),
                    countrycolor = toRGB("gray85"),
                    countrywidth = 0.5,
                    subunitwidth = 0.5))
fig
  • En el mapa se puede observar, que la especie norcar se encuentra distribuida alrededor de México, Estados Unidos y Hawaii.

  • La especie skylar, se encuentra distribuida principalmente por Europa, con una presencia reducida en algunos países asiáticos, Australia y en algunos puntos de Estados Unidos

  • Para el caso de la especie mallar3, se encuentra distribuida principalmente por Europa, con una presencia reducida en algunos países asiáticos, como Japón, Nueva Zelanda, Estados Unidos y Colombia.

  • La especie houspa tiene presencia en varios puntos de todo el continente Americano, también en Europa, parte de Asia, el continente Africano, Australia y Nueva Zelanda.

  • La especie comsan se puede observar en gran parte de Europa, algunos puntos de África y Asia.

  • Finalmente, la especie brnowl, se encuentra principalmente en Europa, con presencia en algunos puntos del continente Americano, el continente Africano y en algunos países de Asia.

Type (Tipo de sonido)

Esta variable contiene detalles sobre el audio y y el ave presente en el. Se puede encontrar información como el tipo de vocalización emitida por el ave (canto o llamado), el sexo del ave, o en que etapa se encuentra (adulto o juvenil), entre otra información referente a los sonidos que se pueden escuchar en el audio.

# limpieza de la variable type
# se eliminan algunos símbolos, se coloque todo en minúscula y luego se crea una lista con todos los string en la variable
# luego se separan los string para poder obtener los valores único de la columna y poder identificar palabras mas frecuentes
sub_data$type <- str_replace_all(sub_data$type, "[\\[\\]'\\s]", "") %>% 
  str_to_lower()

unique_values <- str_c(sub_data$type, collapse = ",") %>% 
  strsplit(',')
# unique_values[[1]] %>% unique()
head(sub_data) %>% kable()
etiqueta etiqueta2 type latitud longitud Nombre_cientifico Nombre_comun rating tiempo et2_count hora etapaDia
brnowl alarmcall 52.3009 6.7620 Tyto alba Barn Owl 4 21:30 0 21 Noche
brnowl song -20.7900 -42.8882 Tyto alba Barn Owl 4 20:30 0 20 Noche
brnowl call 18.5206 73.8398 Tyto alba Barn Owl 4 23:09 0 23 Noche
brnowl ,clicks -21.5500 -47.7167 Tyto alba Barn Owl 4 05:30 0 5 Madrugada
brnowl beggingcall 0.0640 32.4790 Tyto alba Barn Owl 4 23:30 0 23 Noche
brnowl beggingcall,call 0.0640 32.4790 Tyto alba Barn Owl 5 00:30 0 0 Madrugada

Sexo

Del campo type, se extrae el sexo del ave. Se tienen 4 categorias para esta nueva variable: Hembra, Macho, Ambos y No Especifica. La categoría Ambos, indica que hay tanto una hembra como un macho en el audio.

# creacción de variable sexo
sub_data%>% 
  mutate(sexo=case_when(str_detect(type, "female") & str_detect(type, ",male") ~ "Ambos",
                        str_detect(type, "female")~"Hembra",
                        str_detect(type, ",male")~"Macho",
                        T~"No Especifica")) -> sub_data
# grafico de barras
sub_data %>%
  group_by(sexo) %>% 
  summarise(conteo=n()) %>% 
  plot_ly(x= ~conteo, y=~sexo,  orientation ="h" ) %>% 
  add_bars( marker = list(color = 'rgb(118, 215, 196)',
                      line = list(color = 'rgb(14, 102, 85)',
                                  width = 1.5))) %>% 
      layout(title = list(text="Sexo del Ave"),
         xaxis = list(title = 'Sexo'),
         yaxis = list(title = 'Frecuencia'))
# grafico de barras
sub_data %>%
  group_by(etiqueta, sexo) %>% 
  summarise(conteo=n()) %>% 
  plot_ly(x= ~conteo, y=~sexo,  orientation ="h", color = ~etiqueta) %>% 
  add_bars() %>% 
      layout(title = list(text="Sexo del Ave"),
         xaxis = list(title = 'Sexo'),
         yaxis = list(title = 'Frecuencia'))

En la mayoría de registros de audio, no se especifica el sexo del ave. De las aves en las que se logra identificar el sexo, la mayoria son machos, o hay presencia tanto de un macho como una hembra en la grabación.

Edad

Otro campo que se extrae de la variable type es la edad del ave. Por edad se refiere a si el ave esta en etapa juvenil o es un adulto. Tambien se tiene una categoría en caso no se especifique la edad del ave.

sub_data %>% 
  mutate(edad=case_when(str_detect(type, "adult")~"adulto",
                        str_detect(type, "juvenile")~"juvenil",
                        T~"No Especifica")) -> sub_data
sub_data %>%
  group_by(edad) %>% 
  summarise(conteo=n()) %>% 
  plot_ly(x= ~conteo, y=~edad,  orientation ="h" ) %>% 
  add_bars( marker = list(color = 'rgb(118, 215, 196)',
                      line = list(color = 'rgb(14, 102, 85)',
                                  width = 1.5))) %>% 
      layout(title = list(text="Edad del Ave"),
         xaxis = list(title = 'Edad'),
         yaxis = list(title = 'Frecuencia'))
# grafico de barras
sub_data %>%
  group_by(etiqueta, edad) %>% 
  summarise(conteo=n()) %>% 
  plot_ly(x= ~conteo, y=~edad,  orientation ="h", color = ~etiqueta) %>% 
  add_bars() %>% 
      layout(title = list(text="Edad del Ave"),
         xaxis = list(title = 'Edad'),
         yaxis = list(title = 'Frecuencia'))

Similar a la variable sexo, para la mayoría de audios no se especifica la edad del ave. La mayoría de aves en las que se logra identificar la edad, son adultos.

Tipo de Vocalización

Otro campo que se logra extraer de la variable type, es el tipo de vocalización del ave. Las vocalizaciones de las aves se puede dividir principalmente en 2 categorías: canto y llamado.

Los cantos son vocalizaciones complejas y elaboradas que suelen ser emitidas por los machos durante la temporada de reproducción. Los cantos sirven para atraer a las hembras, defender el territorio y establecer jerarquías sociales. Las hembras tambien pueden emitir cantos, pero es mas común por parte de los machos en muchas especies.

Los llamados son vocalizaciones más simples y breves que pueden ser emitidas por aves de ambos sexos. Los llamados sirven para comunicarse con otros miembros de la misma especie, para alertar de peligros o para identificarse entre sí.

sub_data %>% 
  mutate(tipo=case_when(str_detect(type, "call")~"llamado",
                        str_detect(type, "song")~"canto",
                        T~"No Especifica")) -> sub_data
sub_data %>%
  group_by(tipo) %>% 
  summarise(conteo=n()) %>% 
  plot_ly(x= ~conteo, y=~tipo,  orientation ="h" ) %>% 
  add_bars( marker = list(color = 'rgb(118, 215, 196)',
                      line = list(color = 'rgb(14, 102, 85)',
                                  width = 1.5))) %>% 
      layout(title = list(text="Tipo de vocalización del Ave"),
         xaxis = list(title = 'Vocalización'),
         yaxis = list(title = 'Frecuencia'))
# grafico de barras
sub_data %>%
  group_by(etiqueta, tipo) %>% 
  summarise(conteo=n()) %>% 
  plot_ly(x= ~conteo, y=~tipo,  orientation ="h", color = ~etiqueta) %>% 
  add_bars() %>% 
      layout(title = list(text="Tipo de vocalización del Ave"),
         xaxis = list(title = 'Tipo'),
         yaxis = list(title = 'Frecuencia'))

Aunque los cantos y llamados sean emitidos por la misma especie de aves, el patrón del sonido y su frecuencia será diferente, por lo que al incluir ambos tipos de vocalizaciones en un modelo, aumenta la cantidad de patrones que debe aprender el modelo, esto considerando también que los llamados pueden tener distintas variaciones para una misma especie, según la intención que se tenga.

Se logra observar que la mayoría de las vocalizaciones son del tipo llamado. Para el caso de las especies skylar y norcar, se tienen mas registros de vocalizaciones de tipo canto.

Muestras de audios

Para entender porque los cantos de las aves pueden ayudar en la identificación de especies, se muestran a continuación algunos ejemplos de audio obtenidos desde el repositorio de Xeno-canto.

  • brnowl
  • houspa
  • comsan
  • mallar3
  • norcar
  • skylar

Visualización de ondas de sonido

Para una muestra de 6 especies, se grafican las ondas sonoras extraídas de los audios. Los audios originalmente tienen diferentes duraciones, desde segundos hasta minutos, pero para facilitar su comparación, se grafican solo10 segundos de cada uno. Se logra apricar como la amplitud y frecuencia de las ondas es diferente de una especie a otra.

# audios 
brnowl <- "C:/Users/kaes1/Desktop/MachineLearningUN/Audios/train_audio/brnowl/XC112875.ogg"
comsan <- "C:/Users/kaes1/Desktop/MachineLearningUN/Audios/train_audio/comsan/XC37488.ogg"
houspa <- "C:/Users/kaes1/Desktop/MachineLearningUN/Audios/train_audio/houspa/XC124181.ogg"
mallar3 <- "C:/Users/kaes1/Desktop/MachineLearningUN/Audios/train_audio/mallar3/XC142907.ogg"
norcar <- "C:/Users/kaes1/Desktop/MachineLearningUN/Audios/train_audio/norcar/XC54006.ogg"
skylar <- "C:/Users/kaes1/Desktop/MachineLearningUN/Audios/train_audio/skylar/XC139781.ogg"

audio_list <- list(brnowl,comsan, houspa,mallar3, norcar, skylar)

Es posible extraer de los audios su duración y la frecuencia de muestreo (sample_rate). La frecuencia de muestreo es la cantidad de muestra de sonido que se captan por segundo, permitiendo tener mayor detalle del sonido emitido por las aves. Ejemplo:

av_media_info(brnowl)
$duration
[1] 41.32572

$video
NULL

$audio
  channels sample_rate  codec frames bitrate layout
1        2       32000 vorbis     NA  112000 stereo

Como todos los audios tienen la misma frecuencia de muestreo, no se detallará mucho en esa información.

Para una muestra de 6 especies, se grafican las ondas sonoras extraídas de los audios. Originalmente los aduios tienen diferentes duraciones, desde segundos hasta minutos, pero para observar mejor las diferencias, se tomó una muestra de 5 segundos para cada audio. Se puede observar la diferencia en amplitud de las ondas de una especie a otra, y que los ciclos son mas largos de una especie a otra a lo largo del tiempo.

count<- 1
for (audio in audio_list){
  pcm_data <- read_audio_bin(audio, channels = 1, end_time = 5)
  df <- data.frame(amplitud = pcm_data,
                   tiempo = seq(1, length(pcm_data), 1)/32000)
  df <- df %>%  mutate(amplitud = pcm_data)
  nam <- paste("plot",count, sep = "")
  assign(nam, plot_ly(df, x=~tiempo, y=~amplitud, type = 'scatter', mode = 'lines', name = birds[count]) %>%
           config(fig, staticPlot = TRUE) %>% 
           layout(showlegend=T))
  count = count + 1
}
plot1
plot2
plot3
plot4
plot5
plot6

Espectrograma

Para el desarrollo de los modelos predictivos, se utilizarán los espectrogramas pre-procesados de los audios de las aves. En el siguiente espectrograma de ejemplo, correspondiente al audio de la especie brnowl, se puede apreciar como el espectrograma es una representación gráfica de la intensidad de la señal de audio en función de la frecuencia y el tiempo. El color en el espectrograma ayuda a a identificar la intensidad del sonido, entre más fuerte el color, mas fuerte es el sonido emitido. Para el caso del espectrograma a continuación, las zonas amarillas marcan los momentos donde el sonido es más intenso.

# lectura del audio para extraer caracteristicas 
fft_data <- read_audio_fft(brnowl)

# guardar espectrograma
png(file="C:/Users/kaes1/Documents/DataViz/spectrogram.png",
width=500, height=350)
plot(fft_data)
dev.off()

Espectrograma dinámico

A través del siguiente espectrograma dinámico, se podrá visualizar con mayor facilidad la relación entre el sonido emitido y la intensidad del mismo a lo largo del tiempo. Se puede ver como en los momentos en los que el sonido es más intenso y continuo, se marcan las bandas amarillas en el gráfico.

# código para generar espectrograma dinámico
library(av)
av_audio_convert(audio1, 'short.mp3') # convertir audio de formato .ogg a .mp3
av_spectrogram_video('short.mp3', output = 'spectrogram.mp4', width = 1280, height = 720, res = 144)

Preparación de los datos

Preparación de imagenes

Previo a la complementación de los modelos, es necesario convertir las imágenes de los espectrogramas para las 6 especies en arreglos de matrices.

Inicialmente se separan las imágenes en train y test. Para esto se crean 2 nuevos folder donde se dividirán las imágenes con un split, con el 80% de las imágenes para el train, y el 20% restantes para el test.

El código utilizado para realizar esta separación de los datos es el siguiente:

folder_list <- birds
folder_list %>% as.character()
## [1] "brnowl"  "comsan"  "houspa"  "mallar3" "norcar"  "skylar"
# set.seed(123)
# 
# folder_path <- paste0('C:/Users/kaes1/Desktop/MachineLearningUN/Proyecto/Pruebas/', folder_list, "/")
# folder_path
# 
# folder_train <- 'C:/Users/kaes1/Desktop/MachineLearningUN/Proyecto/train/'
# folder_test <- 'C:/Users/kaes1/Desktop/MachineLearningUN/Proyecto/test/'
# 
# # Obtener la lista de carpetas en la carpeta "Pruebas"
# lista_carpetas <- list.dirs(folder_path)
# lista_carpetas
# 
# # Obtener el número de imágenes en cada carpeta
# numero_imagenes <- unlist(lapply(lista_carpetas, function(x) {
#   length(list.files(x))
# }))
# numero_imagenes
# 
# # Calcular el número de imágenes para el conjunto de entrenamiento
# numero_imagenes_entrenamiento <- round(0.8 * 500)
# numero_imagenes_entrenamiento
# 
# # Crear una lista con las imágenes para el conjunto de entrenamiento
# imagenes_entrenamiento <- lapply(lista_carpetas, function(x) {
#   sample(list.files(x), numero_imagenes_entrenamiento)
# })
# 
# # Mover las imágenes del conjunto de entrenamiento a la carpeta "train"
# for (i in 1:length(imagenes_entrenamiento)) {
#   for (imagen in imagenes_entrenamiento[[i]]) {
#     print(paste0(folder_path[i], imagen))
#     file.copy(from = paste0(folder_path[i], imagen), to= paste0(folder_train, folder_list[[i]] ,"/",imagen))
#   }
# }
# 
# # Obtener el número de imágenes restantes
# numero_imagenes_restantes <- 500 - numero_imagenes_entrenamiento

# Crear lista vacia para guardar las imagenes que se guardaran en test
# imagenes_prueba <- list()
# for (i in 1:length(folder_list)){
#   imagenes_prueba[[i]] <-list.files(lista_carpetas[i])[!list.files(lista_carpetas[i]) %in% imagenes_entrenamiento[[i]]]
# }
# 
# # Mover las imágenes del conjunto de prueba a la carpeta "test"
# for (i in 1:length(imagenes_prueba)) {
#   for (imagen in imagenes_prueba[[i]]) {
#     print(paste0(folder_path[i], imagen))
#     file.copy(from = paste0(folder_path[i], imagen), to= paste0(folder_test, folder_list[[i]] ,"/",imagen))
#   }
# }

A continuación se activa un ambiente en Python, el cual es necesario para utilizar las librerías de manipulación de imágenes.

library(reticulate)
reticulate::use_condaenv("tf_image", required = TRUE)
# reticulate::py_install("scipy")
# library(tidyverse)
library(imager)
library(keras)
library(caret)
library(purrr)
options(scipen = 999)

Carpetas con las imágenes de entrenamientos de las 6 especies:

set.seed(123)
folder_path <- paste0('C:/Users/kaes1/Desktop/MachineLearningUN/Proyecto/train/', folder_list, "/")

folder_path
## [1] "C:/Users/kaes1/Desktop/MachineLearningUN/Proyecto/train/brnowl/" 
## [2] "C:/Users/kaes1/Desktop/MachineLearningUN/Proyecto/train/comsan/" 
## [3] "C:/Users/kaes1/Desktop/MachineLearningUN/Proyecto/train/houspa/" 
## [4] "C:/Users/kaes1/Desktop/MachineLearningUN/Proyecto/train/mallar3/"
## [5] "C:/Users/kaes1/Desktop/MachineLearningUN/Proyecto/train/norcar/" 
## [6] "C:/Users/kaes1/Desktop/MachineLearningUN/Proyecto/train/skylar/"

En el siguiente código se obtiene un listado con cada uno de los espectrogramas para cada especie:

# Nombres de los archivos
file_name <-purrr::map(folder_path, 
                 function(x) paste0(x, list.files(x))
                 ) %>% 
  unlist()
head(file_name)
## [1] "C:/Users/kaes1/Desktop/MachineLearningUN/Proyecto/train/brnowl/XC115605.jpeg"
## [2] "C:/Users/kaes1/Desktop/MachineLearningUN/Proyecto/train/brnowl/XC120974.jpeg"
## [3] "C:/Users/kaes1/Desktop/MachineLearningUN/Proyecto/train/brnowl/XC121413.jpeg"
## [4] "C:/Users/kaes1/Desktop/MachineLearningUN/Proyecto/train/brnowl/XC138038.jpeg"
## [5] "C:/Users/kaes1/Desktop/MachineLearningUN/Proyecto/train/brnowl/XC138041.jpeg"
## [6] "C:/Users/kaes1/Desktop/MachineLearningUN/Proyecto/train/brnowl/XC138613.jpeg"

Cantidad de imágenes de entrenamiento:

length(file_name)
## [1] 2400

A continuación se muestran algunas de las imágenes de entrenamiento:

sample_image <- sample(file_name, 12)

# cargar imagenes
img <- map(sample_image, load.image)

par(mfrow = c(2, 4)) 
map(img, plot)

## [[1]]
## Image. Width: 295 pix Height: 1025 pix Depth: 1 Colour channels: 3 
## 
## [[2]]
## Image. Width: 316 pix Height: 1025 pix Depth: 1 Colour channels: 3 
## 
## [[3]]
## Image. Width: 267 pix Height: 1025 pix Depth: 1 Colour channels: 3 
## 
## [[4]]
## Image. Width: 232 pix Height: 1025 pix Depth: 1 Colour channels: 3 
## 
## [[5]]
## Image. Width: 2303 pix Height: 1025 pix Depth: 1 Colour channels: 3 
## 
## [[6]]
## Image. Width: 499 pix Height: 1025 pix Depth: 1 Colour channels: 3 
## 
## [[7]]
## Image. Width: 1567 pix Height: 1025 pix Depth: 1 Colour channels: 3 
## 
## [[8]]
## Image. Width: 1551 pix Height: 1025 pix Depth: 1 Colour channels: 3 
## 
## [[9]]
## Image. Width: 744 pix Height: 1025 pix Depth: 1 Colour channels: 3 
## 
## [[10]]
## Image. Width: 484 pix Height: 1025 pix Depth: 1 Colour channels: 3 
## 
## [[11]]
## Image. Width: 988 pix Height: 1025 pix Depth: 1 Colour channels: 3 
## 
## [[12]]
## Image. Width: 814 pix Height: 1025 pix Depth: 1 Colour channels: 3

Se puede obtener información sobre la estructura de las imágenes: alto, ancho y canales de color.

# información de la imagen
img <- load.image(file_name[1])
img
## Image. Width: 529 pix Height: 1025 pix Depth: 1 Colour channels: 3

La anterior imagen tiene un ancho de 1292 pixeles y un alto de 1025, profundidad de 1, y con 3 canales de color.

Ahora se genera una función que permita extraer el ancho y alto de las imágenes. Esto es necesario, debido a que las imágenes deben tener las mismas dimensiones antes de ser ingresadas en los modelos.

# Función para adquirir en ancho y alto de la imagen
get_dim <- function(x){
  img <- load.image(x)

  df_img <- data.frame(height = height(img),
                       width = width(img),
                       filename = x
                       )
  return(df_img)
}

get_dim(file_name[1])
##   height width
## 1   1025   529
##                                                                       filename
## 1 C:/Users/kaes1/Desktop/MachineLearningUN/Proyecto/train/brnowl/XC115605.jpeg

Se escoge a continuación una muestra de imágenes, para identificar el ancho y alto de cada una. Al observar los resultados, se logra apreciar que las imágenes de entrenamiento tienen la misma altura, pero distintos anchos. Para poder utilizar estas imágenes para los modelos de clasificación, se deben redimensionar todas las imágenes para que tengan el mismo ancho y altura.

set.seed(123)
sample_file <- sample(file_name, 500)

file_dim <- map_df(sample_file, get_dim)

summary(file_dim)
##      height         width           filename        
##  Min.   :1025   Min.   :  158.0   Length:500        
##  1st Qu.:1025   1st Qu.:  418.8   Class :character  
##  Median :1025   Median :  859.0   Mode  :character  
##  Mean   :1025   Mean   : 2038.8                     
##  3rd Qu.:1025   3rd Qu.: 1849.0                     
##  Max.   :1025   Max.   :62882.0

Se establecen las dimensiones que tendrán las iamgenes, y el tamaño del lote de entrenamiento para la red neuronal convolucional que se entrenará como primer modelo.

# ancho y alto deseado 
target_size <- c(224, 224)

# tamaño del batch para el modelo
batch_size <- 32

Tambien se deben escalar las imágenes para poder trabajar con ellas. Para esto se normalizan las características de entrada dividiendo cada valor de píxel por 255. Esto escala los valores de píxel en el rango [0, 1].

# Generador de imagenes
train_data_gen <- image_data_generator(rescale = 1/255, 
                                       validation_split = 0.2 
                                       )

Ahora se genera el dataset de entrenamiento y el de validación. Se van a manejar los 3 canales de color, las dimensiones de las imágenes serán de 224 x 224, y se normalizaran los pixeles.

# Training Dataset
train_image_array_gen <- flow_images_from_directory(directory = "C:/Users/kaes1/Desktop/MachineLearningUN/Proyecto/train/", # 
                                                    target_size = target_size, 
                                                    color_mode = "rgb", 
                                                    batch_size = batch_size , 
                                                    seed = 123,  
                                                    subset = "training",
                                                    generator = train_data_gen
                                                    )
## Found 1920 images belonging to 6 classes.
# Validation Dataset
val_image_array_gen <- flow_images_from_directory(directory = "C:/Users/kaes1/Desktop/MachineLearningUN/Proyecto/train/",
                                                  target_size = target_size, 
                                                  color_mode = "rgb", 
                                                  batch_size = batch_size ,
                                                  seed = 123,
                                                  subset = "validation", 
                                                  generator = train_data_gen
                                                  )
## Found 480 images belonging to 6 classes.

Se verifica, a continuación, si la proporción de datos en el set de entranmeinto para cada especie es igual.

train_samples <- train_image_array_gen$n # numero de datos de entrenamiento
valid_samples <- val_image_array_gen$n # numero de datos de validacion

output_n <- n_distinct(train_image_array_gen$classes) # numero de clases 

# proporción de datos por clase
table("\nFrequency" = factor(train_image_array_gen$classes)
      ) %>% 
  prop.table()
## 
## Frequency
##         0         1         2         3         4         5 
## 0.1666667 0.1666667 0.1666667 0.1666667 0.1666667 0.1666667

Modelado

Selección de métrica

La métrica seleccionada para evaluar los modelos es AUC-ROC (Área bajo la curva ROC). Se escoge esta métrica debido a que es relevante cuando se necesita evaluar el rendimiento del modelo en problemas de clasificación multiclase con técnicas de “one-vs-all”, y permite medir la capacidad del modelo para distinguir entre clases. Tambien, se requiere que el modelo sea capaz de clasificar igual de bien, todas las especies, sin tener prioridad una sobre la otra.

Las dimensiones de cada imagen de entrada para el modelo de red neuronal son:

# tamaño de los datos de entrada
c(target_size, 3) 
## [1] 224 224   3

CNN

Entrenamiento

El primer modelo que se entrenará es una red neuronal convolucional (CNN). La arquitectura propuesta para este red neuronal es la siguiente:

# Definir el modelo
model <- keras_model_sequential()
weight_decay = 1e-4

# Añadir capas al modelo
model %>%
  
  layer_conv_2d(filters = 32, kernel_size = 3, strides = 1, padding = 'same',
                activation = 'relu', kernel_regularizer = regularizer_l2(weight_decay), 
                input_shape = c(target_size, 3) ) %>%
  
  layer_max_pooling_2d(pool_size = c(2, 2)) %>%
  
  layer_dropout(rate = 0.2) %>%
  
  layer_conv_2d(filters = 64, kernel_size = 3, padding = 'same', activation = 'relu',
                kernel_regularizer = regularizer_l2(weight_decay)) %>%
  
  layer_max_pooling_2d(pool_size = c(2, 2)) %>%
  
  layer_dropout(rate = 0.3) %>%
  
  layer_conv_2d(filters = 128, kernel_size = 3, padding = 'same', activation = 'relu',
                kernel_regularizer = regularizer_l2(weight_decay)) %>%
  
  layer_max_pooling_2d(pool_size = c(2, 2)) %>%
  
  layer_dropout(rate = 0.4) %>%
  
  layer_flatten() %>%
  layer_dense(units = 256, activation = 'relu') %>%
  layer_dense(units = 6, activation = 'softmax')

model
## Model: "sequential"
## ________________________________________________________________________________
##  Layer (type)                       Output Shape                    Param #     
## ================================================================================
##  conv2d_2 (Conv2D)                  (None, 224, 224, 32)            896         
##  max_pooling2d_2 (MaxPooling2D)     (None, 112, 112, 32)            0           
##  dropout_2 (Dropout)                (None, 112, 112, 32)            0           
##  conv2d_1 (Conv2D)                  (None, 112, 112, 64)            18496       
##  max_pooling2d_1 (MaxPooling2D)     (None, 56, 56, 64)              0           
##  dropout_1 (Dropout)                (None, 56, 56, 64)              0           
##  conv2d (Conv2D)                    (None, 56, 56, 128)             73856       
##  max_pooling2d (MaxPooling2D)       (None, 28, 28, 128)             0           
##  dropout (Dropout)                  (None, 28, 28, 128)             0           
##  flatten (Flatten)                  (None, 100352)                  0           
##  dense_1 (Dense)                    (None, 256)                     25690368    
##  dense (Dense)                      (None, 6)                       1542        
## ================================================================================
## Total params: 25,785,158
## Trainable params: 25,785,158
## Non-trainable params: 0
## ________________________________________________________________________________

Para entrenar el modelo se considera la función de perdida de categorical crossentropy, y una tasa de aprendizaje de 0.01.

model %>% 
  compile(
    loss = "categorical_crossentropy",
    optimizer = optimizer_adam(lr = 0.01),
    metrics = c("accuracy", metric_auc())
  )

# Ajuste del modelo
history <- model %>% 
  fit(
  # training data
  train_image_array_gen,

  # training epochs
  steps_per_epoch = as.integer(train_samples / batch_size), 
  epochs = 10, 
  
  # validation data
  validation_data = val_image_array_gen,
  validation_steps = as.integer(valid_samples / batch_size)
)
# save_model_tf(model, "cnn_aves_1")
# write.csv(history, "historyCNN_1.csv")

Se carga el modelo previamente entrenado para evitar variaciones en los resultados:

model <- load_model_tf("C:\\Users\\kaes1\\Documents\\DataViz\\App_Aves\\cnn_aves_1")
historyCNN1 <- read.csv("C:\\Users\\kaes1\\Documents\\DataViz\\App_Aves\\APP_AVES\\historyCNN_1.csv")

Del proceso de entrenamiento se extrae la métrica del AUC para cada una de las épocas de entrenamiento, y para los datos del train y de validación. Tambien se tiene información de la perdida del modelo, y de la métrica de exactitud o Accuracy.

 # grafica de perdida train vs val
loss_df <- historyCNN1[historyCNN1$metric=="loss", ]

fig1 <- loss_df %>% 
 plot_ly(x=~epoch, y=~value, color = ~data, type="scatter", mode = 'lines+markers') %>% 
 layout(legend = list(orientation = 'v', y = 1, x=0.5), 
        title = "Loss - Train vs Val")
fig1
 # grafica de auc train vs val

auc_df <- historyCNN1[historyCNN1$metric=="auc_2", ]

fig2 <- auc_df %>% 
 plot_ly(x=~epoch, y=~value, color = ~data, type="scatter", mode = 'lines+markers') %>% 
 layout(legend = list(orientation = 'v', y = 0.1, x=0.5), 
        title = "AUC - Train vs Val")
fig2   
 # Graficar Accuracy
acc_df <- historyCNN1[historyCNN1$metric=="accuracy", ]
   
fig3<- acc_df %>% 
 plot_ly(x=~epoch, y=~value, color = ~data, type="scatter", mode = 'lines+markers') %>% 
 layout(legend = list(orientation = 'v', y = 0.1, x=0.5),
        title="Accuracy - Train vs Val")
fig3   

Para hacer el test del modelo, se deben transformar tambien las imágenes de prueba, aplicando el re-dimensionamiento y re-escalando los pixeles de las imágenes.

# funcion para convertir las imagenes en arreglos vectoriales]
image_prep <- function(x) {
  arrays <- lapply(x, function(path) {
    img <- image_load(path, target_size = target_size, 
                      grayscale = F 
                      )
    
    x <- image_to_array(img)
    x <- array_reshape(x, c(1, dim(x)))
    x <- x/255 # rescale image pixel
  })
  do.call(abind::abind, c(arrays, list(along = 1)))
}

Predicción con el test set

Se obtiene el listado de imágenes del set de prueba:

test_path <- 'C:/Users/kaes1/Desktop/MachineLearningUN/Proyecto/test/'
test_files <- list()

for (folder in folder_list) {
 test_files <- append(test_files, paste0(paste0(test_path, folder),"/",list.files(paste0(test_path, folder))))
}
test_data <- data.frame(file_name = test_files %>% as.character()) %>% 
  mutate(class = str_extract(test_files %>% as.character(), "brnowl|comsan|houspa|mallar3|norcar|skylar"))

head(test_data, 5) %>% kable()
file_name class
C:/Users/kaes1/Desktop/MachineLearningUN/Proyecto/test/brnowl/XC112875.jpeg brnowl
C:/Users/kaes1/Desktop/MachineLearningUN/Proyecto/test/brnowl/XC145819.jpeg brnowl
C:/Users/kaes1/Desktop/MachineLearningUN/Proyecto/test/brnowl/XC149279.jpeg brnowl
C:/Users/kaes1/Desktop/MachineLearningUN/Proyecto/test/brnowl/XC152588.jpeg brnowl
C:/Users/kaes1/Desktop/MachineLearningUN/Proyecto/test/brnowl/XC186611.jpeg brnowl

Ahora se procesan las imágenes para que tengan la estructura requerida por el modelo de CNN. Se verifica que los datos si tengan la estructura especificada.

test_x <- image_prep(test_data$file_name)
dim(test_x)
## [1] 600 224 224   3

Se procede a hacer la predicción con los datos de prueba, y se obtiene una matriz con las probabilidades de clasificación para cada una de las especies.

mtx_test <- model %>% predict(test_x) 
## 19/19 - 9s - 9s/epoch - 498ms/step
head(mtx_test, 5) 
##            [,1]            [,2]          [,3]       [,4]      [,5]
## [1,] 0.21947034 0.0000008590754 0.00003032223 0.02016852 0.7603290
## [2,] 0.48916000 0.0072226556949 0.06774273515 0.22360452 0.2108659
## [3,] 0.08020142 0.1564177870750 0.08535960317 0.31130502 0.2898542
## [4,] 0.78099644 0.0099687641487 0.00296757231 0.01959453 0.1830159
## [5,] 0.55572104 0.0347285307944 0.00390776759 0.21312954 0.1858690
##                [,6]
## [1,] 0.000000960562
## [2,] 0.001404142473
## [3,] 0.076861850917
## [4,] 0.003456914797
## [5,] 0.006644142326

Ahora se obtiene la clasificación final para cada registro, obteniendo la posición en la matriz de la probabilidad más grande.

pred_test <- mtx_test %>% k_argmax()
prob_test <- apply(mtx_test, 1, max, na.rm=TRUE)
head(pred_test, 10)
## tf.Tensor([4 0 3 0 0 4 0 0 4 0], shape=(10), dtype=int64)

Para facilitar la compresión de los resultados, se hace la decodificación de la variable respuesta, para poder identificar a que ave se hace referencia.

# Convertir encoding a etiqueta
decode <- function(x){
  case_when(x == 0 ~ "brnowl",
            x == 1 ~ "comsan",
            x == 2 ~ "houspa",
            x == 3 ~ "mallar3",
            x == 4 ~ "norcar",
            x == 5 ~ "skylar"
            
            )
}

pred_test <- sapply(pred_test, decode) 

head(pred_test, 10)
##  [1] "norcar"  "brnowl"  "mallar3" "brnowl"  "brnowl"  "norcar"  "brnowl" 
##  [8] "brnowl"  "norcar"  "brnowl"

Matrix de Confusión

A continuación se obtiene la matriz de confusión para predicción realizada. En los resultados obtenidos, se observa la matriz de confusión, junto con otras métricas de desempeño, como la especificidad, sensibilidad, Accuracy, etc.

confusionMatrix(as.factor(pred_test), 
                as.factor(test_data$class)
                ) -> cmatrix
cmatrix
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction brnowl comsan houspa mallar3 norcar skylar
##    brnowl      69      0      3      12      3      0
##    comsan       7     91      3       8      5     18
##    houspa       6      4     86       2     10     24
##    mallar3      7      1      0      65      0      1
##    norcar      10      2      6      13     82      6
##    skylar       1      2      2       0      0     51
## 
## Overall Statistics
##                                                
##                Accuracy : 0.74                 
##                  95% CI : (0.7029, 0.7747)     
##     No Information Rate : 0.1667               
##     P-Value [Acc > NIR] : < 0.00000000000000022
##                                                
##                   Kappa : 0.688                
##                                                
##  Mcnemar's Test P-Value : 0.0000000004847      
## 
## Statistics by Class:
## 
##                      Class: brnowl Class: comsan Class: houspa Class: mallar3
## Sensitivity                 0.6900        0.9100        0.8600         0.6500
## Specificity                 0.9640        0.9180        0.9080         0.9820
## Pos Pred Value              0.7931        0.6894        0.6515         0.8784
## Neg Pred Value              0.9396        0.9808        0.9701         0.9335
## Prevalence                  0.1667        0.1667        0.1667         0.1667
## Detection Rate              0.1150        0.1517        0.1433         0.1083
## Detection Prevalence        0.1450        0.2200        0.2200         0.1233
## Balanced Accuracy           0.8270        0.9140        0.8840         0.8160
##                      Class: norcar Class: skylar
## Sensitivity                 0.8200       0.51000
## Specificity                 0.9260       0.99000
## Pos Pred Value              0.6891       0.91071
## Neg Pred Value              0.9626       0.90993
## Prevalence                  0.1667       0.16667
## Detection Rate              0.1367       0.08500
## Detection Prevalence        0.1983       0.09333
## Balanced Accuracy           0.8730       0.75000

En el siguiente gráfico se observan las métricas de F1-Score, Precision y Recall. Se logra ver que los valores de Recall para cada especie son los que tienen mayor dispersión, siendo la especie skylar la que tiene el valor mas bajo, lo que indica que el modelo solo logró identificar correctamente el 51% de los espectrogramas pertenecientes a esta especie. Por otro lado, esta especie tiene una Precision alta, ya que el 91% de los espectrogramas clasificados en esa especie, si pertenencia a esa especie. Similar ocurre con las especies brnowl y mallar3

Lo contrario sucede con la especie houspa, la cual tiene un Recall, 86%, pero una Precision mas baja, 65%. Similar a esto ocurre con la especie comsan y norcar.

library(tibble)
library(tidyr)
cmatrix$byClass %>% as.data.frame() -> metrics_pcf
metrics_pcf %>% rownames_to_column() -> metrics_pcf
metrics_pcf$rowname <- str_replace(metrics_pcf$rowname, "Class: ", "")
metrics_pcf <- metrics_pcf %>% select(rowname, Precision, Recall, F1)

metrics_pcf <- gather(metrics_pcf,
                 "Precision", "Recall", "F1",
                 key = "Metrica",
                 value = "Score")

fig <- plot_ly(metrics_pcf, x = ~Metrica, y = ~Score, color = ~rowname) 
fig %>% add_markers(size=20) %>% layout(title="Metricas de desempeño")
cmatrix$table %>% as.data.frame() %>%  plot_ly(x = ~Prediction, y =~Reference, z= ~Freq, type = "heatmap") %>% 
  layout(title="Matriz de Confusión")-> fig
fig

Se puede ver en la matriz, que las especies con menor número de clasificaciones correctas son brnowl, mallar3 y skylar. La especie con mayor número de clasificaciones correctas es comsan, seguida de houspa y norcar. Esto podría indicar que el modelo tiene un sesgo hacia la especie estas tres especies, y no es tan bueno para identificar las demás especies.

Curva ROC

A continuación se traza la curva ROC para cada una de las especies, bajo la modalidad de One vs All, donde se mira que tan bueno es el modelo para identificar cada especie individualmente, frente a las demás.

library(fastDummies)
library(yardstick)
mtx_test %>% as.data.frame() -> y_scores
names(y_scores) <- paste0("pred_", birds)

Se obtienen las etiqueta de respuesta con one hot encoding.

y_onehot <- dummy_cols(test_data$class)
colnames(y_onehot) <- c("drop", birds)
y_onehot <- subset(y_onehot, select = -c(drop))

Ahora se gráfica para cada especie la Curva ROC, junto con el área bajo la curva (AUC), correspondiente.

z <- cbind(y_scores, y_onehot)

z$brnowl <- as.factor(z$brnowl)
roc_brnowl <- roc_curve(data = z, brnowl, pred_brnowl)
roc_brnowl$specificity <- 1 - roc_brnowl$specificity
colnames(roc_brnowl) <- c('threshold', 'tpr', 'fpr')
auc_brnowl <- roc_auc(data = z, brnowl, pred_brnowl)
auc_brnowl <- auc_brnowl$.estimate
brnowl <- paste('brnowl (AUC=',toString(round(1-auc_brnowl,2)),')',sep = '')


z$houspa <- as.factor(z$houspa)
roc_houspa <- roc_curve(data = z, houspa, pred_houspa)
roc_houspa$specificity <- 1 - roc_houspa$specificity
colnames(roc_houspa) <- c('threshold', 'tpr', 'fpr')
auc_houspa <- roc_auc(data = z, houspa, pred_houspa)
auc_houspa <- auc_houspa$.estimate
houspa <- paste('houspa (AUC=',toString(round(1-auc_houspa,2)),')',sep = '')

z$comsan <- as.factor(z$comsan)
roc_comsan <- roc_curve(data = z, comsan, pred_comsan)
roc_comsan$specificity <- 1 - roc_comsan$specificity
colnames(roc_comsan) <- c('threshold', 'tpr', 'fpr')
auc_comsan <- roc_auc(data = z, comsan, pred_comsan)
auc_comsan <- auc_comsan$.estimate
comsan <- paste('comsan (AUC=',toString(round(1-auc_comsan,2)),')',sep = '')

z$mallar3 <- as.factor(z$mallar3)
roc_mallar3 <- roc_curve(data = z, mallar3, pred_mallar3)
roc_mallar3$specificity <- 1 - roc_mallar3$specificity
colnames(roc_mallar3) <- c('threshold', 'tpr', 'fpr')
auc_mallar3 <- roc_auc(data = z, mallar3, pred_mallar3)
auc_mallar3 <- auc_mallar3$.estimate
mallar3 <- paste('mallar3 (AUC=',toString(round(1-auc_mallar3,2)),')',sep = '')

z$norcar <- as.factor(z$norcar)
roc_norcar <- roc_curve(data = z, norcar, pred_norcar)
roc_norcar$specificity <- 1 - roc_norcar$specificity
colnames(roc_norcar) <- c('threshold', 'tpr', 'fpr')
auc_norcar <- roc_auc(data = z, norcar, pred_norcar)
auc_norcar <- auc_norcar$.estimate
norcar <- paste('norcar (AUC=',toString(round(1-auc_norcar,2)),')',sep = '')

z$skylar <- as.factor(z$skylar)
roc_skylar <- roc_curve(data = z, skylar, pred_skylar)
roc_skylar$specificity <- 1 - roc_skylar$specificity
colnames(roc_skylar) <- c('threshold', 'tpr', 'fpr')
auc_skylar <- roc_auc(data = z, skylar, pred_skylar)
auc_skylar <- auc_skylar$.estimate
skylar <- paste('skylar (AUC=',toString(round(1-auc_skylar,2)),')',sep = '')

En general, los valores de AUC se encuentran todos por encima de 0.7, lo que indica que el modelo es bueno para distinguir a las especies individualmente. Sin embargo, los análisis realizados con las otras métricas indican que el modelo podría tener un sesgo hacia algunas especies, lo cual se debería confirmar en analisis mas profundos de los resultados del modelo.

fig <- plot_ly()%>%
  add_segments(x = 0, xend = 1, y = 0, yend = 1, line = list(dash = "dash", color = 'black'), showlegend = FALSE) %>%
  add_trace(data = roc_brnowl,x = ~fpr, y = ~tpr, mode = 'lines', name = brnowl, type = 'scatter')%>%
  add_trace(data = roc_comsan,x = ~fpr, y = ~tpr, mode = 'lines', name = comsan, type = 'scatter')%>%
  add_trace(data = roc_houspa,x = ~fpr, y = ~tpr, mode = 'lines', name = houspa, type = 'scatter')%>%
    add_trace(data = roc_mallar3,x = ~fpr, y = ~tpr, mode = 'lines', name = mallar3, type = 'scatter')%>%
    add_trace(data = roc_norcar,x = ~fpr, y = ~tpr, mode = 'lines', name = norcar, type = 'scatter')%>%
    add_trace(data = roc_skylar,x = ~fpr, y = ~tpr, mode = 'lines', name =skylar, type = 'scatter')%>%
  layout(xaxis = list(
    title = "False Positive Rate"
  ), yaxis = list(
    title = "True Positive Rate"
  ),legend = list(x = 0.8, y = 0.1))
fig

CNN-SVC

El segundo modelo a evaluar, utiliza CNN para extraer características de las imágenes y luego el algortimo Support Vector Classifier para entrenar el modelo. Este modelo utilizará la red convolucional entrenada anteriormente para extraer atributos de las imágenes, teniendo en cuenta la penúltima capa de la red.

Se crea un data frame con las rutas de las imágenes y la etiqueta correspondiente a la especie para cada una.

train_path <- 'C:/Users/kaes1/Desktop/MachineLearningUN/Proyecto/train/'
train_files <- list()

for (folder in folder_list) {
 train_files <- append(train_files, paste0(paste0(train_path, folder),"/",list.files(paste0(train_path, folder))))
}
train_data <- data.frame(file_name = train_files %>% as.character()) %>% 
  mutate(class = str_extract(train_files %>% as.character(), "brnowl|comsan|houspa|mallar3|norcar|skylar"))

head(train_data, 5) %>% kable()
file_name class
C:/Users/kaes1/Desktop/MachineLearningUN/Proyecto/train/brnowl/XC115605.jpeg brnowl
C:/Users/kaes1/Desktop/MachineLearningUN/Proyecto/train/brnowl/XC120974.jpeg brnowl
C:/Users/kaes1/Desktop/MachineLearningUN/Proyecto/train/brnowl/XC121413.jpeg brnowl
C:/Users/kaes1/Desktop/MachineLearningUN/Proyecto/train/brnowl/XC138038.jpeg brnowl
C:/Users/kaes1/Desktop/MachineLearningUN/Proyecto/train/brnowl/XC138041.jpeg brnowl

Debido a limitantes en cuanto a la memoria del equipo de computo utilizado, para entrenar este modelo híbrido, se considera un tamaño de entrenamiento menor al utilizado en el modelo de CNN. En este caso el set de entrenamiento tiene un tamaño de 1200 imágenes.

train_data_ <-  createDataPartition(y =train_data$class, p = 0.5, times = 1, list =F )
train_data_<- train_data[train_data_,]
dim(train_data_)
## [1] 1200    2
train_x <- image_prep(train_data_$file_name)
dim(train_x)
## [1] 1200  224  224    3

Se crea una función que permite hacer uso del modelo CNN, y de la capa número 11 (la capa densa previa a la clasificación) para extraer los atributos de las imágenes de entrenamiento.

feature_extractor <- keras_model(
  inputs = model$inputs,
  outputs = get_layer(model, index = 11)$output
)

# extraer atributos de los datos de entrenamiento
features <- feature_extractor(train_x)

En la capa número 11 de la CNN, se tiene como salida 256 características extraídas de las imágenes, las cuales serán nuestros datos de entrenamiento para el modelo de SVC (SVM).

# features[[11]] %>% as.matrix() %>% as.data.frame() -> data_prueba
features %>% as.matrix() %>% as.data.frame() -> data_train
data_train <- data_train %>% mutate("Etiqueta" = train_data_$class %>% as.factor())
data_train %>% head() %>%  kable()
V1 V2 V3 V4 V5 V6 V7 V8 V9 V10 V11 V12 V13 V14 V15 V16 V17 V18 V19 V20 V21 V22 V23 V24 V25 V26 V27 V28 V29 V30 V31 V32 V33 V34 V35 V36 V37 V38 V39 V40 V41 V42 V43 V44 V45 V46 V47 V48 V49 V50 V51 V52 V53 V54 V55 V56 V57 V58 V59 V60 V61 V62 V63 V64 V65 V66 V67 V68 V69 V70 V71 V72 V73 V74 V75 V76 V77 V78 V79 V80 V81 V82 V83 V84 V85 V86 V87 V88 V89 V90 V91 V92 V93 V94 V95 V96 V97 V98 V99 V100 V101 V102 V103 V104 V105 V106 V107 V108 V109 V110 V111 V112 V113 V114 V115 V116 V117 V118 V119 V120 V121 V122 V123 V124 V125 V126 V127 V128 V129 V130 V131 V132 V133 V134 V135 V136 V137 V138 V139 V140 V141 V142 V143 V144 V145 V146 V147 V148 V149 V150 V151 V152 V153 V154 V155 V156 V157 V158 V159 V160 V161 V162 V163 V164 V165 V166 V167 V168 V169 V170 V171 V172 V173 V174 V175 V176 V177 V178 V179 V180 V181 V182 V183 V184 V185 V186 V187 V188 V189 V190 V191 V192 V193 V194 V195 V196 V197 V198 V199 V200 V201 V202 V203 V204 V205 V206 V207 V208 V209 V210 V211 V212 V213 V214 V215 V216 V217 V218 V219 V220 V221 V222 V223 V224 V225 V226 V227 V228 V229 V230 V231 V232 V233 V234 V235 V236 V237 V238 V239 V240 V241 V242 V243 V244 V245 V246 V247 V248 V249 V250 V251 V252 V253 V254 V255 V256 Etiqueta
0 0 0 0 0 0.0000000 4.095268 0.0000000 0 0.000000 0 0 0.3114984 0 0 0 0 0 0 0 0 2.6722460 0.000000 0 0 0 0 4.8335247 1.1404301 0 0.0000000 0.0000000 0 0 3.933488 0 0 2.7904599 0.9762580 0 0 0 0 0 0 0 0 0 0 0 0.5368122 0.000000 0 0 0.1748784 0 0 0 0 0 0.0000000 0 0.0000000 0 0 0 0.0000000 0.0000000 0 0 0.000000 0 0.9116227 0 0 0.0000000 0 0 1.7585027 0 0 0 0.0000000 0.0000000 0 0 0 0 4.7284317 0 0 0.0000000 0 0 0 0 0 0 0 2.2397804 0 0.000000 0 0.000000 0 0.0000000 0 0 0 1.3591661 0 0 0.000000 0.0000000 0 0 0 0.3659574 0 0 0 0 0 1.896008 2.8641152 3.863311 0 1.933670 0 0 0.7497728 0.0000000 0 2.0146630 0.0000000 0 0.0000000 0.0000000 0 0 0 0 0 0 1.5253204 0 0 0.0000000 0 1.4339927 0 0.0000000 0.000000 0 0 1.6377847 0.0000000 0 0 0 0 0 0.0000000 0.0000000 0 0 0 0.000000 0 0 0 0 0.000000 0 1.2742332 0 0.0000000 1.2591665 0 0 0 0.000000 1.8320678 0 0.000000 6.512539 0.2761008 0.0000000 0 0 0 0.0000000 0.0586148 0 0 0 1.3436822 0 0 0 0 0 7.539764 0.0000000 0 0 0 0 0 0 0.000000 0 0 0 0 0 0 6.0218320 0.0000000 0 0 0 1.6417649 0 0 0 0 0.0000000 0.0000000 0.000000 6.8083215 0 0 0.0000000 0 0 0 0 0 0.0000000 0.0000000 0.0000000 2.0688179 0 0 0 0 0.0000000 0.0000000 0 0 0 0.0000000 0.0000000 0 1.1673108 brnowl
0 0 0 0 0 0.0000000 1.599350 0.0000000 0 0.000000 0 0 0.0000000 0 0 0 0 0 0 0 0 0.8579725 0.000000 0 0 0 0 1.4664874 0.6829753 0 1.2492045 0.1787683 0 0 0.000000 0 0 0.0000000 1.2365676 0 0 0 0 0 0 0 0 0 0 0 0.8127297 0.000000 0 0 0.0000000 0 0 0 0 0 0.0000000 0 1.4855475 0 0 0 0.1224712 0.0993032 0 0 0.000000 0 1.0961142 0 0 0.0000000 0 0 0.2499478 0 0 0 0.5817227 1.1906317 0 0 0 0 0.3115691 0 0 0.0000000 0 0 0 0 0 0 0 0.9617633 0 0.000000 0 0.000000 0 0.0000000 0 0 0 1.5659139 0 0 0.000000 0.0000000 0 0 0 0.0000000 0 0 0 0 0 0.000000 0.0000000 3.316641 0 0.000000 0 0 0.0000000 0.3138341 0 0.0000000 0.0000000 0 0.0000000 0.0000000 0 0 0 0 0 0 1.1601341 0 0 0.5555165 0 1.5459980 0 0.0000000 0.000000 0 0 0.5931144 0.4090937 0 0 0 0 0 0.6017492 0.0000000 0 0 0 0.000000 0 0 0 0 0.000000 0 1.1343136 0 0.8481758 2.2824538 0 0 0 0.000000 0.9529957 0 0.000000 1.236655 0.6336030 0.0000000 0 0 0 0.5012078 0.3418051 0 0 0 1.2812595 0 0 0 0 0 2.282794 0.1827353 0 0 0 0 0 0 0.000000 0 0 0 0 0 0 0.8350202 0.0744637 0 0 0 0.2335745 0 0 0 0 0.1754461 0.6433563 0.000000 1.7338541 0 0 0.0000000 0 0 0 0 0 0.6404943 0.0000000 0.0000000 0.8125281 0 0 0 0 0.0000000 0.0000000 0 0 0 0.0000000 1.0578792 0 1.8652761 brnowl
0 0 0 0 0 0.0093805 1.702689 0.0102292 0 0.000000 0 0 1.3540025 0 0 0 0 0 0 0 0 1.0352443 0.000000 0 0 0 0 1.5350363 0.7289062 0 0.0000000 1.6358494 0 0 0.000000 0 0 0.1608445 2.2594497 0 0 0 0 0 0 0 0 0 0 0 0.0000000 0.000000 0 0 0.0000000 0 0 0 0 0 0.0000000 0 0.6357194 0 0 0 1.2472582 0.0000000 0 0 0.000000 0 0.7336864 0 0 0.0000000 0 0 0.1708977 0 0 0 0.4177704 0.0000000 0 0 0 0 2.9152501 0 0 0.3979488 0 0 0 0 0 0 0 1.6791049 0 0.000000 0 0.000000 0 0.2133019 0 0 0 0.5545927 0 0 0.000000 0.0000000 0 0 0 0.9323499 0 0 0 0 0 1.225428 0.4632379 2.764140 0 0.000000 0 0 1.0892242 0.8864596 0 2.3255830 0.0000000 0 0.0000000 0.0000000 0 0 0 0 0 0 0.3671372 0 0 0.0276069 0 0.2424701 0 0.0000000 0.000000 0 0 1.6605121 1.8573461 0 0 0 0 0 0.5964531 0.0000000 0 0 0 0.000000 0 0 0 0 0.000000 0 1.0027039 0 0.0000000 0.9195682 0 0 0 0.000000 1.1218784 0 0.000000 2.816195 1.0281137 0.0000000 0 0 0 0.0000000 0.6556016 0 0 0 1.2844177 0 0 0 0 0 2.703387 0.2386850 0 0 0 0 0 0 0.000000 0 0 0 0 0 0 0.9703039 0.8853645 0 0 0 0.7550423 0 0 0 0 0.4353263 0.0000000 0.000000 2.5666807 0 0 0.0000000 0 0 0 0 0 0.0000000 0.0000000 0.0000000 1.4322324 0 0 0 0 0.0000000 0.0000000 0 0 0 0.3999239 0.0086259 0 1.5367923 brnowl
0 0 0 0 0 0.0000000 2.729846 0.0000000 0 0.000000 0 0 1.9332145 0 0 0 0 0 0 0 0 1.2581893 0.000000 0 0 0 0 5.8051152 1.4008520 0 0.0000000 0.0000000 0 0 2.558706 0 0 1.6030109 0.0990227 0 0 0 0 0 0 0 0 0 0 0 1.0463351 0.000000 0 0 0.5423173 0 0 0 0 0 0.0000000 0 0.0000000 0 0 0 0.0000000 0.0000000 0 0 0.000000 0 2.4336166 0 0 1.0437125 0 0 3.1865640 0 0 0 0.0000000 0.0000000 0 0 0 0 4.4723945 0 0 0.0000000 0 0 0 0 0 0 0 4.3613334 0 0.000000 0 0.000000 0 0.0000000 0 0 0 0.8626181 0 0 0.000000 0.0000000 0 0 0 1.8683480 0 0 0 0 0 2.681714 3.7669499 3.004904 0 1.746822 0 0 0.2970047 2.2519963 0 3.2201660 0.0000000 0 0.0000000 0.0000000 0 0 0 0 0 0 1.1864356 0 0 1.9881301 0 0.5284050 0 0.0000000 1.503568 0 0 0.5651565 0.8141333 0 0 0 0 0 0.0000000 0.0000000 0 0 0 0.000000 0 0 0 0 0.000000 0 0.5326567 0 0.0000000 0.0000000 0 0 0 0.000000 3.4030352 0 0.000000 5.620147 1.2766055 0.0000000 0 0 0 0.8547119 2.8912432 0 0 0 1.5435052 0 0 0 0 0 6.801619 0.0000000 0 0 0 0 0 0 0.000000 0 0 0 0 0 0 5.4757848 0.0000000 0 0 0 0.4539595 0 0 0 0 0.0000000 0.0000000 0.000000 5.5048013 0 0 0.0000000 0 0 0 0 0 0.3324038 0.6802742 0.0000000 1.4583850 0 0 0 0 0.0462072 0.0000000 0 0 0 0.0000000 0.0416832 0 0.0000000 brnowl
0 0 0 0 0 0.0000000 1.437081 0.0000000 0 0.000000 0 0 0.0000000 0 0 0 0 0 0 0 0 1.3511713 0.000000 0 0 0 0 2.7790980 0.5645185 0 0.5831023 0.0000000 0 0 1.528416 0 0 1.7285373 0.3240692 0 0 0 0 0 0 0 0 0 0 0 0.6725212 0.000000 0 0 0.0000000 0 0 0 0 0 0.0000000 0 0.8424231 0 0 0 0.0000000 0.0000000 0 0 0.000000 0 0.2568500 0 0 0.3617605 0 0 0.8905838 0 0 0 0.0000000 0.3752418 0 0 0 0 1.5192214 0 0 0.0000000 0 0 0 0 0 0 0 1.2374264 0 0.000000 0 0.000000 0 0.0000000 0 0 0 2.0468795 0 0 0.000000 0.7181521 0 0 0 0.0000000 0 0 0 0 0 1.003707 1.7500626 3.442236 0 1.082618 0 0 0.6032333 0.0000000 0 0.5356644 0.0000000 0 0.0000000 0.0000000 0 0 0 0 0 0 1.6199259 0 0 0.6464055 0 2.1359303 0 0.0000000 0.000000 0 0 0.0000000 0.0000000 0 0 0 0 0 0.1583769 0.0000000 0 0 0 0.000000 0 0 0 0 0.000000 0 1.6832955 0 0.0000000 2.3868797 0 0 0 0.000000 1.2314152 0 0.000000 3.312789 0.1726089 0.0000000 0 0 0 0.0000000 0.1509822 0 0 0 1.1275598 0 0 0 0 0 3.666617 0.4820879 0 0 0 0 0 0 0.000000 0 0 0 0 0 0 2.3885102 0.3548058 0 0 0 0.3480557 0 0 0 0 0.0000000 0.0000000 0.000000 2.8224757 0 0 0.0000000 0 0 0 0 0 0.0492274 0.0000000 0.0000000 0.4407604 0 0 0 0 0.0000000 0.1984698 0 0 0 0.0000000 0.9331498 0 0.8684898 brnowl
0 0 0 0 0 0.7491397 0.000000 0.0000000 0 1.254609 0 0 2.3915436 0 0 0 0 0 0 0 0 0.7237469 3.282775 0 0 0 0 0.2753317 0.0000000 0 0.0305919 1.9239441 0 0 0.000000 0 0 1.3363261 0.3248642 0 0 0 0 0 0 0 0 0 0 0 0.0000000 1.085112 0 0 0.0000000 0 0 0 0 0 0.5924836 0 0.1287143 0 0 0 0.6675426 1.1850716 0 0 2.519614 0 0.0000000 0 0 0.0000000 0 0 0.0277741 0 0 0 0.0000000 0.0000000 0 0 0 0 2.9232311 0 0 0.0000000 0 0 0 0 0 0 0 0.2043781 0 0.195048 0 1.535261 0 2.0095220 0 0 0 0.0000000 0 0 1.216752 0.1421603 0 0 0 2.8397863 0 0 0 0 0 1.519932 0.2883985 1.766955 0 0.000000 0 0 2.4140182 0.0000000 0 3.0386386 0.4330733 0 0.8744311 0.0757642 0 0 0 0 0 0 0.9058105 0 0 0.0000000 0 0.0000000 0 0.4571917 2.692991 0 0 0.0000000 3.0729089 0 0 0 0 0 1.2491103 0.9769859 0 0 0 0.840005 0 0 0 0 1.037019 0 1.2264234 0 0.0000000 0.0000000 0 0 0 1.414796 0.0000000 0 1.603285 3.525072 0.0000000 0.6354234 0 0 0 0.0000000 0.0000000 0 0 0 0.1569368 0 0 0 0 0 0.000000 0.9947975 0 0 0 0 0 0 1.093628 0 0 0 0 0 0 0.2310589 0.0000000 0 0 0 0.0000000 0 0 0 0 0.0000000 0.5278572 0.852129 0.7612255 0 0 0.6241956 0 0 0 0 0 0.0000000 0.0000000 0.3823068 0.7021058 0 0 0 0 1.8511873 1.3413703 0 0 0 0.3742258 0.0200090 0 2.2245958 brnowl
dim(data_train)
## [1] 1200  257

Para los datos de prueba se aplica el mismo proceso, con el fin de que se tenga la misma estructura que los datos de entrenamiento.

features_test <- feature_extractor(test_x)
features_test %>% as.matrix() %>% as.data.frame() -> data_test
data_test <- data_test %>% mutate("Etiqueta" = test_data$class %>% as.factor())
data_test %>% head() %>% kable()
V1 V2 V3 V4 V5 V6 V7 V8 V9 V10 V11 V12 V13 V14 V15 V16 V17 V18 V19 V20 V21 V22 V23 V24 V25 V26 V27 V28 V29 V30 V31 V32 V33 V34 V35 V36 V37 V38 V39 V40 V41 V42 V43 V44 V45 V46 V47 V48 V49 V50 V51 V52 V53 V54 V55 V56 V57 V58 V59 V60 V61 V62 V63 V64 V65 V66 V67 V68 V69 V70 V71 V72 V73 V74 V75 V76 V77 V78 V79 V80 V81 V82 V83 V84 V85 V86 V87 V88 V89 V90 V91 V92 V93 V94 V95 V96 V97 V98 V99 V100 V101 V102 V103 V104 V105 V106 V107 V108 V109 V110 V111 V112 V113 V114 V115 V116 V117 V118 V119 V120 V121 V122 V123 V124 V125 V126 V127 V128 V129 V130 V131 V132 V133 V134 V135 V136 V137 V138 V139 V140 V141 V142 V143 V144 V145 V146 V147 V148 V149 V150 V151 V152 V153 V154 V155 V156 V157 V158 V159 V160 V161 V162 V163 V164 V165 V166 V167 V168 V169 V170 V171 V172 V173 V174 V175 V176 V177 V178 V179 V180 V181 V182 V183 V184 V185 V186 V187 V188 V189 V190 V191 V192 V193 V194 V195 V196 V197 V198 V199 V200 V201 V202 V203 V204 V205 V206 V207 V208 V209 V210 V211 V212 V213 V214 V215 V216 V217 V218 V219 V220 V221 V222 V223 V224 V225 V226 V227 V228 V229 V230 V231 V232 V233 V234 V235 V236 V237 V238 V239 V240 V241 V242 V243 V244 V245 V246 V247 V248 V249 V250 V251 V252 V253 V254 V255 V256 Etiqueta
0 0 0 0 0 0.0000000 1.2924961 0.0000000 0 2.4994304 0 0 3.5738227 0 0.0000000 0 0 0 0.6826257 0 0 0.6723131 1.7733587 0 0 0 0 1.3926028 0.0247156 0 0.0000000 1.3422905 0 0 0.0000000 0 0 1.9995286 2.1572645 0.0000000 0 0 0 0 0 0 0 0 0 0 0.0000000 0.0000000 0 0.0000000 0 0 0 0.0000000 0.0000000 0 0.0061890 0.0000000 0.0000000 0.0000000 0 0 1.7184130 1.2315964 0 0 3.8235514 0 0.1957238 0 0 0.0000000 0 0 0.0168864 0 0 0 0.0000000 0.0000000 0 0.0000000 0 0 5.5355434 0 0 1.4268826 0 0 0 0 0 0 0 0.0000000 0 1.5783286 0 0.1244965 0.0000000 1.9136266 0 0 0 0.0000000 0 0 0.2904300 0.0000000 0 0 0 4.4571571 0 0.0000000 0 0 0 4.1637383 1.8586329 0.2278192 0 0.0000000 0.0000000 0 3.4148593 0 0 5.8102260 0.0000000 0 1.1697819 1.3786825 0 0 0 0.0000000 0 0 0.1279225 0.0000000 0 0 0 0.0000000 0 0.0000000 3.3408852 0 0.0000000 2.8269448 4.9766259 0 0 0 0 0 0.3388359 0.0000000 0 0 0 0.0000000 0 0 0 0 0.5934628 0.0000000 0.5502988 0 0.0000000 0.0000000 0 0 0 0.0000000 0.7968898 0 1.5642762 6.928985 0.0000000 0.0000000 0 0 0 0 0 0 0 0 0.0000000 0 0 0 0 0 2.5359983 0.7665680 0 0 0 0 0 0 0 0 0 0 0 0 0 2.054675 0.9667661 0 0 0 0 0 0 0 0.1289459 0.0000000 0.0000000 0.5654110 3.8019326 0.0000000 0 0.0000000 0 0 0 0 0.0000000 0 0 1.9738314 0.9515577 0 0 0 0 2.2044528 1.0431120 0 0 0 1.4395833 0.0000000 0.0000000 3.3065062 brnowl
0 0 0 0 0 0.0000000 1.5992473 0.0000000 0 0.0000000 0 0 1.0136169 0 0.0000000 0 0 0 0.0000000 0 0 3.1315501 0.0000000 0 0 0 0 2.4217653 0.0000000 0 0.0000000 0.0000000 0 0 0.7688549 0 0 2.8907096 0.4920647 0.0000000 0 0 0 0 0 0 0 0 0 0 0.2607192 0.0000000 0 0.0000000 0 0 0 0.0000000 0.0000000 0 0.2157747 0.0000000 0.0000000 0.0000000 0 0 0.0000000 0.0000000 0 0 0.0000000 0 0.0369997 0 0 0.3247199 0 0 1.1622095 0 0 0 0.0000000 0.0000000 0 0.0000000 0 0 2.6628895 0 0 0.0000000 0 0 0 0 0 0 0 1.0330309 0 0.0000000 0 0.0000000 0.0000000 0.0000000 0 0 0 0.3946924 0 0 0.0000000 0.8058538 0 0 0 0.1009878 0 0.0000000 0 0 0 2.5858471 2.9474413 1.9075390 0 0.1207725 0.0000000 0 2.2333758 0 0 2.8929739 0.0000000 0 0.0000000 0.7259248 0 0 0 0.0000000 0 0 1.2965603 0.0000000 0 0 0 0.3671431 0 0.0000000 0.4125247 0 0.0000000 0.9872715 0.0000000 0 0 0 0 0 0.4055552 0.0000000 0 0 0 0.0000000 0 0 0 0 0.0000000 0.0000000 1.7884418 0 0.0000000 0.0166351 0 0 0 0.0000000 0.5960693 0 0.0000000 5.518256 0.0000000 0.0000000 0 0 0 0 0 0 0 0 0.1347430 0 0 0 0 0 4.3712044 0.3694189 0 0 0 0 0 0 0 0 0 0 0 0 0 2.610106 0.1381464 0 0 0 0 0 0 0 0.0000000 0.0000000 0.0000000 0.0000000 3.8802555 0.0000000 0 0.0000000 0 0 0 0 0.0000000 0 0 0.0000000 0.7984514 0 0 0 0 0.0000000 1.0492208 0 0 0 0.2416426 0.0000000 0.0000000 0.0000000 brnowl
0 0 0 0 0 1.0673450 0.2060866 0.0328277 0 0.5128776 0 0 0.5495389 0 0.7832950 0 0 0 0.0000000 0 0 0.8179271 0.4912317 0 0 0 0 0.3389853 0.1548842 0 0.0000000 0.3744937 0 0 0.0000000 0 0 0.3938503 0.8851022 0.5354774 0 0 0 0 0 0 0 0 0 0 0.0000000 0.4516591 0 0.0000000 0 0 0 0.5812237 0.1419531 0 0.0150988 0.1082983 1.2650800 0.0000000 0 0 0.2046922 0.3128758 0 0 0.1923476 0 0.0593493 0 0 0.1641651 0 0 0.0000000 0 0 0 0.3897396 0.0000000 0 0.0000000 0 0 0.9380839 0 0 0.5071983 0 0 0 0 0 0 0 0.0000000 0 0.0000000 0 0.0000000 0.4703529 0.0000000 0 0 0 0.1245156 0 0 0.2562150 0.1963778 0 0 0 0.6187017 0 0.0000000 0 0 0 0.5835611 0.2903357 0.0000000 0 0.0000000 0.6939293 0 0.6992871 0 0 1.0212266 0.1563512 0 0.5680910 0.3003742 0 0 0 0.2877271 0 0 0.5402616 0.0000000 0 0 0 0.0000000 0 0.0000000 0.5758789 0 0.0000000 0.3825066 0.6963439 0 0 0 0 0 1.3952950 0.8139858 0 0 0 0.9731820 0 0 0 0 0.3498079 0.0000000 0.7373812 0 0.0000000 0.3571740 0 0 0 0.1793147 0.0000000 0 0.5424211 1.090568 0.6485954 1.0235597 0 0 0 0 0 0 0 0 0.8757402 0 0 0 0 0 0.4815039 0.2957773 0 0 0 0 0 0 0 0 0 0 0 0 0 0.000000 0.1295030 0 0 0 0 0 0 0 0.0000000 0.6032312 0.0000000 0.3841419 0.3091415 1.0010939 0 0.2707671 0 0 0 0 0.0000000 0 0 0.0000000 0.3161008 0 0 0 0 0.0000000 0.1163471 0 0 0 1.0962415 0.0000000 0.0136235 0.7869931 brnowl
0 0 0 0 0 0.0000000 3.7123501 0.0000000 0 0.0000000 0 0 0.5922796 0 0.0000000 0 0 0 0.0000000 0 0 0.4896729 0.0000000 0 0 0 0 2.3790755 0.0000000 0 0.4451896 0.0000000 0 0 0.0000000 0 0 1.5314711 2.5270572 0.0000000 0 0 0 0 0 0 0 0 0 0 0.5339685 0.0000000 0 0.0000000 0 0 0 0.0000000 0.0000000 0 0.0000000 0.0000000 0.0000000 0.0000000 0 0 3.3451583 0.7028925 0 0 0.0000000 0 0.0000000 0 0 0.0000000 0 0 1.7330815 0 0 0 0.0000000 2.8070986 0 0.0000000 0 0 2.4937348 0 0 0.4784874 0 0 0 0 0 0 0 0.0000000 0 0.0000000 0 0.0000000 0.0000000 0.2130513 0 0 0 0.3763815 0 0 0.0000000 0.0000000 0 0 0 0.0000000 0 0.0000000 0 0 0 1.6272109 2.1154993 1.3108498 0 0.0000000 0.0000000 0 0.4560526 0 0 0.1194836 0.0000000 0 0.2910635 0.0000000 0 0 0 0.0000000 0 0 1.3749994 0.0000000 0 0 0 0.8578896 0 0.0000000 1.1677858 0 0.0000000 2.6036143 1.3946562 0 0 0 0 0 0.0000000 0.0000000 0 0 0 0.0000000 0 0 0 0 0.0000000 0.0000000 1.0654479 0 1.2176459 0.0000000 0 0 0 0.0000000 1.1381500 0 0.0000000 5.551582 0.0000000 0.0000000 0 0 0 0 0 0 0 0 0.0000000 0 0 0 0 0 4.7729993 0.7292109 0 0 0 0 0 0 0 0 0 0 0 0 0 2.580240 0.0000000 0 0 0 0 0 0 0 0.0000000 0.0000000 0.9658454 0.0000000 5.4092383 0.0000000 0 0.0000000 0 0 0 0 0.0000000 0 0 0.0000000 2.5222847 0 0 0 0 0.0000000 0.0000000 0 0 0 1.5665561 0.2974497 0.0000000 5.5358038 brnowl
0 0 0 0 0 0.5311926 0.4604540 0.0000000 0 1.0916005 0 0 0.8647839 0 0.0778172 0 0 0 1.0888983 0 0 1.0236897 0.9964413 0 0 0 0 0.0000000 0.0000000 0 0.5095775 0.2699146 0 0 0.0000000 0 0 0.5828849 0.9375163 0.2493034 0 0 0 0 0 0 0 0 0 0 0.6282428 0.2731145 0 0.3510465 0 0 0 0.1666357 0.2097434 0 0.5361524 0.0000000 0.1236145 0.2225445 0 0 1.3524625 0.6040018 0 0 1.5422758 0 0.0000000 0 0 0.0000000 0 0 0.0000000 0 0 0 0.0000000 0.7584683 0 0.1193925 0 0 0.8765886 0 0 0.3194905 0 0 0 0 0 0 0 0.0000000 0 0.8064339 0 0.8349546 0.2374499 0.4402788 0 0 0 0.0000000 0 0 0.5871882 0.3855236 0 0 0 0.7264945 0 0.0925503 0 0 0 0.8112730 0.3968012 0.1360287 0 0.0000000 0.4000210 0 1.0396501 0 0 1.0668768 0.5066570 0 1.2286739 0.6574475 0 0 0 0.0631540 0 0 0.1215553 0.0163576 0 0 0 0.2189044 0 0.2631668 0.4654229 0 0.8133233 0.6046466 1.4312438 0 0 0 0 0 0.0168743 0.5066019 0 0 0 0.5622612 0 0 0 0 0.2137953 0.1011770 0.4056310 0 0.5118788 0.0023194 0 0 0 0.4305627 0.0000000 0 0.6286870 1.385173 0.0000000 0.8564832 0 0 0 0 0 0 0 0 0.0000000 0 0 0 0 0 0.1999744 0.5062732 0 0 0 0 0 0 0 0 0 0 0 0 0 0.000000 0.6274086 0 0 0 0 0 0 0 0.5651075 0.0000000 0.9460803 1.3742232 0.5498376 0.4292404 0 0.5320232 0 0 0 0 0.2797573 0 0 0.5396003 0.5566669 0 0 0 0 0.6632013 0.7123907 0 0 0 1.1075326 0.0000000 0.0000000 1.6674074 brnowl
0 0 0 0 0 0.0000000 0.3852804 0.0000000 0 0.0000000 0 0 0.2166380 0 0.0000000 0 0 0 0.0000000 0 0 0.3866364 0.0000000 0 0 0 0 0.0885244 0.0000000 0 0.9743259 1.1156108 0 0 0.0000000 0 0 0.4819248 1.1567235 0.0000000 0 0 0 0 0 0 0 0 0 0 0.0000000 0.0000000 0 0.0000000 0 0 0 0.0000000 0.0000000 0 0.4052711 0.0000000 1.0214072 0.0000000 0 0 1.4627149 0.2202607 0 0 0.0000000 0 0.0000000 0 0 0.0000000 0 0 0.0000000 0 0 0 0.0000000 1.2410139 0 0.0000000 0 0 0.5395079 0 0 0.0721159 0 0 0 0 0 0 0 0.0939701 0 0.0000000 0 0.0000000 0.1756098 0.9258081 0 0 0 0.3158387 0 0 0.0000000 0.2592193 0 0 0 0.4114191 0 0.0000000 0 0 0 1.2250283 0.6159782 1.2879263 0 0.0000000 0.8940997 0 1.1122795 0 0 1.1742551 0.0000000 0 0.1334045 0.0000000 0 0 0 0.0000000 0 0 0.1433116 0.0000000 0 0 0 0.3466769 0 0.0000000 0.2289651 0 0.1327045 0.1702967 0.7481097 0 0 0 0 0 1.1016251 0.0000000 0 0 0 0.0000000 0 0 0 0 0.2832889 0.1056723 0.3039291 0 0.9519450 0.7727736 0 0 0 0.0000000 0.2740760 0 0.7773185 1.876878 0.0000000 0.0000000 0 0 0 0 0 0 0 0 0.2979112 0 0 0 0 0 1.4355847 0.1439811 0 0 0 0 0 0 0 0 0 0 0 0 0 0.000000 1.0588044 0 0 0 0 0 0 0 0.0000000 0.3536555 1.0849590 0.0000000 1.2044659 0.1781115 0 0.0000000 0 0 0 0 0.2229282 0 0 0.0000000 0.0923070 0 0 0 0 0.0000000 0.8651920 0 0 0 1.3669646 0.2815698 0.0000000 0.9582288 brnowl

Entrenamiento

Para entrenar el modelo de SVM, se realiza una hiperparametrización para los parámetros C y gamma del algoritmo, utilizando un kernel lineal, y como métrica de evaluación el AUC.

library(e1071)
svm_cv <- tune("svm", Etiqueta ~ ., data = data_train, kernel = 'linear', 
               ranges = list(cost = c(0.001, 0.01, 0.1, 1, 5, 10, 20),
                             gamma = c(0.5, 1, 2, 3, 4, 5, 10)),
               metric='ROC')

Se muestra a continuación un resumen con los resultados del mejor modelo encontrado:

summary(svm_cv$best.model)
## 
## Call:
## best.tune(METHOD = "svm", train.x = Etiqueta ~ ., data = data_train, 
##     ranges = list(cost = c(0.001, 0.01, 0.1, 1, 5, 10, 20), gamma = c(0.5, 
##         1, 2, 3, 4, 5, 10)), kernel = "linear", metric = "ROC")
## 
## 
## Parameters:
##    SVM-Type:  C-classification 
##  SVM-Kernel:  linear 
##        cost:  0.1 
## 
## Number of Support Vectors:  486
## 
##  ( 99 54 99 77 75 82 )
## 
## 
## Number of Classes:  6 
## 
## Levels: 
##  brnowl comsan houspa mallar3 norcar skylar

Los valores de parámetros que permitieron obtener el mejor modelo son los siguientes:

print(paste0("Best C: ", svm_cv$best.model$cost))
## [1] "Best C: 0.1"
print(paste0("Best Gamma:", svm_cv$best.model$gamma))
## [1] "Best Gamma:0.5"

Se entrena el modelo con los mejores parámetros:

modelo_svc <- svm(Etiqueta ~ ., data = data_train, 
                  kernel = "linear", 
                  cost = 0.1, 
                  gamma = 0.5,
                  scale = TRUE, probability = T)
# saveRDS(modelo_svc, file = "cnnsvc.rda")

Predicción con el test set

Se realiza la predicción de especies con el modelo CNN-SVC, con el cual se obtienen las probabilidades de clasificación de cada especie para cada espectrograma.

modelCNNSVC <- readRDS("C:\\Users\\kaes1\\Documents\\DataViz\\App_Aves\\APP_AVES\\cnnsvc.rda")

pred_prob <- predict(modelCNNSVC, data_test %>% select(-c("Etiqueta")),  probability =T)

Matriz de confusión

Se obtiene la matriz de confusión para los resultados obtenidos con la predicción.

confusionMatrix(as.factor(pred_prob), 
                as.factor(test_data$class)
                ) -> cmatrix
cmatrix
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction brnowl comsan houspa mallar3 norcar skylar
##    brnowl      69      4      2      10      9      5
##    comsan       4     83      2       2      2      8
##    houspa       6      4     78       2      8     10
##    mallar3     13      4      1      80      3      1
##    norcar       6      2      5       6     76      3
##    skylar       2      3     12       0      2     73
## 
## Overall Statistics
##                                              
##                Accuracy : 0.765              
##                  95% CI : (0.729, 0.7984)    
##     No Information Rate : 0.1667             
##     P-Value [Acc > NIR] : <0.0000000000000002
##                                              
##                   Kappa : 0.718              
##                                              
##  Mcnemar's Test P-Value : 0.7317             
## 
## Statistics by Class:
## 
##                      Class: brnowl Class: comsan Class: houspa Class: mallar3
## Sensitivity                 0.6900        0.8300        0.7800         0.8000
## Specificity                 0.9400        0.9640        0.9400         0.9560
## Pos Pred Value              0.6970        0.8218        0.7222         0.7843
## Neg Pred Value              0.9381        0.9659        0.9553         0.9598
## Prevalence                  0.1667        0.1667        0.1667         0.1667
## Detection Rate              0.1150        0.1383        0.1300         0.1333
## Detection Prevalence        0.1650        0.1683        0.1800         0.1700
## Balanced Accuracy           0.8150        0.8970        0.8600         0.8780
##                      Class: norcar Class: skylar
## Sensitivity                 0.7600        0.7300
## Specificity                 0.9560        0.9620
## Pos Pred Value              0.7755        0.7935
## Neg Pred Value              0.9522        0.9469
## Prevalence                  0.1667        0.1667
## Detection Rate              0.1267        0.1217
## Detection Prevalence        0.1633        0.1533
## Balanced Accuracy           0.8580        0.8460

Este modelo tiene mejoras en cuanto las métricas de F1-Score, Precision y Recall, ya que para especies como comsan, mallar3, norcar, no hay mucha diferencia entre los valores de Precision y Recall. Esto indica que para estas especies, el modelo es capaz de identificar gran parte de los espectrogramas perteneciente a una especie especifica de manera correcta, y que la mayoria de espectrogramas fueron clasificados correctamente para esa especie.

Para la especie brnowll, se tiene tienen valores bajos de Precison y Recall, y para la especie skylar, aunque se mantiene la diferencia entre Precision y Recall, hubo mejoras en cuanto al valor del Recall, por lo que este modelo permite identificar correctamente más espectrogramas para este especie.

library(tibble)
library(tidyr)
cmatrix$byClass %>% as.data.frame() -> metrics_pcf1
metrics_pcf1 %>% rownames_to_column() -> metrics_pcf1
metrics_pcf1$rowname <- str_replace(metrics_pcf1$rowname, "Class: ", "")
metrics_pcf1 <- metrics_pcf1 %>% select(rowname, Precision, Recall, F1)

metrics_pcf1 <- gather(metrics_pcf1,
                 "Precision", "Recall", "F1",
                 key = "Metrica",
                 value = "Score")

fig <- plot_ly(metrics_pcf1, x = ~Metrica, y = ~Score, color = ~rowname) 
fig %>%  add_markers(size=20) %>% layout(title="Metricas de desempeño")
# matriz de confusion
cmatrix$table %>% as.data.frame() %>%  plot_ly(x = ~Prediction, y =~Reference, z= ~Freq, type = "heatmap") %>% 
  layout(title="Matriz de Confusión")-> fig
fig

Se logra observar que la tasa de clasificación para algunas especies tuvo mejoras frente al modelo de CNN. Especies como mallar3 y skylar tuvieron mejoras en cuanto a la cantidad de espectrogramas clasificados correctamente. Otros como comsan, tuvieron una disminución en la clasificación correcta del espectrograma, pero se mantiene dentro de valores aceptables.

Curva ROC

A continuación se traza la curva ROC para cada una de las especies, bajo la modalidad de One vs All, donde se mira que tan bueno es el modelo para identificar cada especie individualmente, frente a las demás.

attr(pred_prob,"probabilities") %>%  as.data.frame() -> y_scores2
names(y_scores2) <- paste0("pred_", birds)
y_onehot <- dummy_cols(test_data$class)
colnames(y_onehot) <- c("drop", birds)
y_onehot <- subset(y_onehot, select = -c(drop))
z <- cbind(y_scores2, y_onehot)

z$brnowl <- as.factor(z$brnowl)
roc_brnowl <- roc_curve(data = z, brnowl, pred_brnowl)
roc_brnowl$specificity <- 1 - roc_brnowl$specificity
colnames(roc_brnowl) <- c('threshold', 'tpr', 'fpr')
auc_brnowl <- roc_auc(data = z, brnowl, pred_brnowl)
auc_brnowl <- auc_brnowl$.estimate
brnowl <- paste('brnowl (AUC=',toString(round(1-auc_brnowl,2)),')',sep = '')


z$houspa <- as.factor(z$houspa)
roc_houspa <- roc_curve(data = z, houspa, pred_houspa)
roc_houspa$specificity <- 1 - roc_houspa$specificity
colnames(roc_houspa) <- c('threshold', 'tpr', 'fpr')
auc_houspa <- roc_auc(data = z, houspa, pred_houspa)
auc_houspa <- auc_houspa$.estimate
houspa <- paste('houspa (AUC=',toString(round(1-auc_houspa,2)),')',sep = '')

z$comsan <- as.factor(z$comsan)
roc_comsan <- roc_curve(data = z, comsan, pred_comsan)
roc_comsan$specificity <- 1 - roc_comsan$specificity
colnames(roc_comsan) <- c('threshold', 'tpr', 'fpr')
auc_comsan <- roc_auc(data = z, comsan, pred_comsan)
auc_comsan <- auc_comsan$.estimate
comsan <- paste('comsan (AUC=',toString(round(1-auc_comsan,2)),')',sep = '')

z$mallar3 <- as.factor(z$mallar3)
roc_mallar3 <- roc_curve(data = z, mallar3, pred_mallar3)
roc_mallar3$specificity <- 1 - roc_mallar3$specificity
colnames(roc_mallar3) <- c('threshold', 'tpr', 'fpr')
auc_mallar3 <- roc_auc(data = z, mallar3, pred_mallar3)
auc_mallar3 <- auc_mallar3$.estimate
mallar3 <- paste('mallar3 (AUC=',toString(round(1-auc_mallar3,2)),')',sep = '')

z$norcar <- as.factor(z$norcar)
roc_norcar <- roc_curve(data = z, norcar, pred_norcar)
roc_norcar$specificity <- 1 - roc_norcar$specificity
colnames(roc_norcar) <- c('threshold', 'tpr', 'fpr')
auc_norcar <- roc_auc(data = z, norcar, pred_norcar)
auc_norcar <- auc_norcar$.estimate
norcar <- paste('norcar (AUC=',toString(round(1-auc_norcar,2)),')',sep = '')

z$skylar <- as.factor(z$skylar)
roc_skylar <- roc_curve(data = z, skylar, pred_skylar)
roc_skylar$specificity <- 1 - roc_skylar$specificity
colnames(roc_skylar) <- c('threshold', 'tpr', 'fpr')
auc_skylar <- roc_auc(data = z, skylar, pred_skylar)
auc_skylar <- auc_skylar$.estimate
skylar <- paste('skylar (AUC=',toString(round(1-auc_skylar,2)),')',sep = '')
fig <- plot_ly()%>%
  add_segments(x = 0, xend = 1, y = 0, yend = 1, line = list(dash = "dash", color = 'black'), showlegend = FALSE) %>%
  add_trace(data = roc_brnowl,x = ~fpr, y = ~tpr, mode = 'lines', name = brnowl, type = 'scatter')%>%
  add_trace(data = roc_comsan,x = ~fpr, y = ~tpr, mode = 'lines', name = comsan, type = 'scatter')%>%
  add_trace(data = roc_houspa,x = ~fpr, y = ~tpr, mode = 'lines', name = houspa, type = 'scatter')%>%
    add_trace(data = roc_mallar3,x = ~fpr, y = ~tpr, mode = 'lines', name = mallar3, type = 'scatter')%>%
    add_trace(data = roc_norcar,x = ~fpr, y = ~tpr, mode = 'lines', name = norcar, type = 'scatter')%>%
    add_trace(data = roc_skylar,x = ~fpr, y = ~tpr, mode = 'lines', name =skylar, type = 'scatter')%>%
  layout(xaxis = list(
    title = "False Positive Rate"
  ), yaxis = list(
    title = "True Positive Rate"
  ),legend = list(x = 0.8, y = 0.1))
fig

Se pueden observar que los resultados de AUC de cada especie para este modelo, tiene mejoras frente al modelo de solo CNN. Principalmente, las especies brnowl y skylar, que alcanzaron un mayor AUC. Para las demás especies también se obtuvo un mayor AUC o se mantuvo el obtenido con el modelo CNN.

CNN-Random Forest

El proceso de entrenamiento de este modelo es similar al del modelo de CNN-SVC. Se utilizan como datos de entrenamiento las características extraídas de las imágenes por medio de CNN, y luego se aplica Random Forest como algoritmo para ajustar el modelo. En este modelo de Random Forest, se busca hiperparametrizar el parámetro mtry, la cual indica el número de atributos elegidos al azar para entrenar el árbol. Cambien se tiene una validación cruzada de 10 folds, y se utiliza el AUC para elegir el mejor modelo.

Entrenamiento

cv <- trainControl(method='cv', number=10,  classProbs = TRUE,)
forest <- train(
        
        Etiqueta~., 
        data=data_train, 
        method='rf', 
        trControl=cv,
        metric='ROC', 
        probability = T
          )

forest$bestTune
##   mtry
## 2  129

El valor del parámetro mtry, que permite obtener el mejor modelo de Random Forest es 129. El modelo entrenado con este parámetro se guarda en la variable forest.

# saveRDS(forest, file = "cnnrf.rda")

Predicción con el test set

Para realizar la predicción con el modelo de Random Forest, se aplica el mismo proceso que para el modelo de SVC.

modelCNNRF <- readRDS("C:\\Users\\kaes1\\Documents\\DataViz\\App_Aves\\APP_AVES\\cnnrf.rda")

pred_label <- predict(modelCNNRF, data_test %>% select(-c("Etiqueta")))
pred_prob_ <- predict(modelCNNRF, data_test %>% select(-c("Etiqueta")),  type = "prob")

Matriz de Confusión

A continuación se obtiene la matriz de confusión para la predicción realizada.

confusionMatrix(as.factor(pred_label), 
                as.factor(test_data$class)
                ) -> cmatrix
 cmatrix
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction brnowl comsan houspa mallar3 norcar skylar
##    brnowl      71      1      5      16      3      3
##    comsan       5     88      3       1      2     10
##    houspa       5      6     77       1     10     11
##    mallar3     10      1      0      76      7      1
##    norcar       7      1      4       5     76      4
##    skylar       2      3     11       1      2     71
## 
## Overall Statistics
##                                              
##                Accuracy : 0.765              
##                  95% CI : (0.729, 0.7984)    
##     No Information Rate : 0.1667             
##     P-Value [Acc > NIR] : <0.0000000000000002
##                                              
##                   Kappa : 0.718              
##                                              
##  Mcnemar's Test P-Value : 0.4143             
## 
## Statistics by Class:
## 
##                      Class: brnowl Class: comsan Class: houspa Class: mallar3
## Sensitivity                 0.7100        0.8800        0.7700         0.7600
## Specificity                 0.9440        0.9580        0.9340         0.9620
## Pos Pred Value              0.7172        0.8073        0.7000         0.8000
## Neg Pred Value              0.9421        0.9756        0.9531         0.9525
## Prevalence                  0.1667        0.1667        0.1667         0.1667
## Detection Rate              0.1183        0.1467        0.1283         0.1267
## Detection Prevalence        0.1650        0.1817        0.1833         0.1583
## Balanced Accuracy           0.8270        0.9190        0.8520         0.8610
##                      Class: norcar Class: skylar
## Sensitivity                 0.7600        0.7100
## Specificity                 0.9580        0.9620
## Pos Pred Value              0.7835        0.7889
## Neg Pred Value              0.9523        0.9431
## Prevalence                  0.1667        0.1667
## Detection Rate              0.1267        0.1183
## Detection Prevalence        0.1617        0.1500
## Balanced Accuracy           0.8590        0.8360
library(tibble)
library(tidyr)
cmatrix$byClass %>% as.data.frame() -> metrics_pcf
metrics_pcf %>% rownames_to_column() -> metrics_pcf
metrics_pcf$rowname <- str_replace(metrics_pcf$rowname, "Class: ", "")
metrics_pcf <- metrics_pcf %>% select(rowname, Precision, Recall, F1)

metrics_pcf <- gather(metrics_pcf,
                 "Precision", "Recall", "F1",
                 key = "Metrica",
                 value = "Score")

fig <- plot_ly(metrics_pcf, x = ~Metrica, y = ~Score, color = ~rowname) 
fig %>% add_markers(size=20) %>%  layout(title="Metricas de desempeño")

Para este modelo, se tienen mejoras en las métricas por especie, frente a los modelos anteriores, principalmente con la especie brnowl, que auqnue sigue siendo la especie con las metricas mas bajas, alcanza valores superiores al 0.7 en todas las métricas. La especie comsan vuelve a ser la que tiene el mejor Recall, por lo que el modelo logra identificar correctamente la mayoria de muestras pertenecientes a esta especie.

cmatrix$table %>% as.data.frame() %>%  plot_ly(x = ~Prediction, y =~Reference, z= ~Freq, type = "heatmap") %>% 
  layout(title="Matriz de Confusión")-> fig
fig

Se observa en la matriz de confusión que hubo mejoras en la clasificación para todas las especies, principalmente en las especies brnowl, mallar3 y skylar, para las cuales se tuvieron los resultados mas bajos en el modelo inicial.

Curva ROC

Finalmente se realiza la curva ROC para los resultados obtenidos de la predicción con Random Forest.

pred_prob_ %>%  as.data.frame() -> y_scores3
names(y_scores3) <- paste0("pred_", birds)
y_onehot <- dummy_cols(test_data$class)
colnames(y_onehot) <- c("drop", birds)
y_onehot <- subset(y_onehot, select = -c(drop))
z <- cbind(y_scores3, y_onehot)

z$brnowl <- as.factor(z$brnowl)
roc_brnowl <- roc_curve(data = z, brnowl, pred_brnowl)
roc_brnowl$specificity <- 1 - roc_brnowl$specificity
colnames(roc_brnowl) <- c('threshold', 'tpr', 'fpr')
auc_brnowl <- roc_auc(data = z, brnowl, pred_brnowl)
auc_brnowl <- auc_brnowl$.estimate
brnowl <- paste('brnowl (AUC=',toString(round(1-auc_brnowl,2)),')',sep = '')


z$houspa <- as.factor(z$houspa)
roc_houspa <- roc_curve(data = z, houspa, pred_houspa)
roc_houspa$specificity <- 1 - roc_houspa$specificity
colnames(roc_houspa) <- c('threshold', 'tpr', 'fpr')
auc_houspa <- roc_auc(data = z, houspa, pred_houspa)
auc_houspa <- auc_houspa$.estimate
houspa <- paste('houspa (AUC=',toString(round(1-auc_houspa,2)),')',sep = '')

z$comsan <- as.factor(z$comsan)
roc_comsan <- roc_curve(data = z, comsan, pred_comsan)
roc_comsan$specificity <- 1 - roc_comsan$specificity
colnames(roc_comsan) <- c('threshold', 'tpr', 'fpr')
auc_comsan <- roc_auc(data = z, comsan, pred_comsan)
auc_comsan <- auc_comsan$.estimate
comsan <- paste('comsan (AUC=',toString(round(1-auc_comsan,2)),')',sep = '')

z$mallar3 <- as.factor(z$mallar3)
roc_mallar3 <- roc_curve(data = z, mallar3, pred_mallar3)
roc_mallar3$specificity <- 1 - roc_mallar3$specificity
colnames(roc_mallar3) <- c('threshold', 'tpr', 'fpr')
auc_mallar3 <- roc_auc(data = z, mallar3, pred_mallar3)
auc_mallar3 <- auc_mallar3$.estimate
mallar3 <- paste('mallar3 (AUC=',toString(round(1-auc_mallar3,2)),')',sep = '')

z$norcar <- as.factor(z$norcar)
roc_norcar <- roc_curve(data = z, norcar, pred_norcar)
roc_norcar$specificity <- 1 - roc_norcar$specificity
colnames(roc_norcar) <- c('threshold', 'tpr', 'fpr')
auc_norcar <- roc_auc(data = z, norcar, pred_norcar)
auc_norcar <- auc_norcar$.estimate
norcar <- paste('norcar (AUC=',toString(round(1-auc_norcar,2)),')',sep = '')

z$skylar <- as.factor(z$skylar)
roc_skylar <- roc_curve(data = z, skylar, pred_skylar)
roc_skylar$specificity <- 1 - roc_skylar$specificity
colnames(roc_skylar) <- c('threshold', 'tpr', 'fpr')
auc_skylar <- roc_auc(data = z, skylar, pred_skylar)
auc_skylar <- auc_skylar$.estimate
skylar <- paste('skylar (AUC=',toString(round(1-auc_skylar,2)),')',sep = '')
fig <- plot_ly()%>%
  add_segments(x = 0, xend = 1, y = 0, yend = 1, line = list(dash = "dash", color = 'black'), showlegend = FALSE) %>%
  add_trace(data = roc_brnowl,x = ~fpr, y = ~tpr, mode = 'lines', name = brnowl, type = 'scatter')%>%
  add_trace(data = roc_comsan,x = ~fpr, y = ~tpr, mode = 'lines', name = comsan, type = 'scatter')%>%
  add_trace(data = roc_houspa,x = ~fpr, y = ~tpr, mode = 'lines', name = houspa, type = 'scatter')%>%
    add_trace(data = roc_mallar3,x = ~fpr, y = ~tpr, mode = 'lines', name = mallar3, type = 'scatter')%>%
    add_trace(data = roc_norcar,x = ~fpr, y = ~tpr, mode = 'lines', name = norcar, type = 'scatter')%>%
    add_trace(data = roc_skylar,x = ~fpr, y = ~tpr, mode = 'lines', name =skylar, type = 'scatter')%>%
  layout(xaxis = list(
    title = "False Positive Rate"
  ), yaxis = list(
    title = "True Positive Rate"
  ),legend = list(x = 0.8, y = 0.1))
fig

Se logra observar que los valores de AUC obtenidos por medio de este modelo para cada una de las especies, son mayores a los obtenidos con el modelo inicial de CNN, y un poco mayores a los obtenidos con el modelo de SVM. Esto indica que el modelo si es bueno para identificar cada especie individualmente, frente a las demás especies. Por lo que la capacidad del modelo de discernir entre especies es buena.

Conclusiones

  • El modelo de CNN es bueno para identificar las especies de aves a través de los espectrogramas, pero se obtienen mejores resultados al utilizarlo como herramienta para la extracción de características, y posteriormente aplicar algoritmos de aprendizaje automática mas sencillos y clásicos.

  • A pesar que los modelos logran clasificar correctamente la mayoría de los espectrogramas, se logra observar que para algunas especies los valores de métricas como AUC, Precision o Recall, siempre se mantienen bajos frente a las otras especies, como es el caso de la especie brnowl. Se tendría que detallar mas en los datos de entrenamiento para esta especie, con el fin de determinar si estos afectan la capacidad de los modelos de identificar esa especie, considerando factores como la duración de los audios originales, el tipo de dispositivo de grabación, sonido mono o estéreo, o el tipo de sonido emitido por las aves, llamado o canto.