En este ejercicio construiremos un identificador de lenguaje que distinga inglés, francés, italiano, portugués, español y turco.
Usaremos un modelo de n-gramas de caracteres (tejas).
Colecciones estándar de frases en varios lenguajes pueden encontrarse en http://corpora.uni-leipzig.de . Revisa el contenido de estos archivos:
library(tidyverse)
## ── Attaching packages ─────────────────────────────────────────────────────────────────────────────────────── tidyverse 1.3.0 ──
## ✓ ggplot2 3.2.1 ✓ purrr 0.3.3
## ✓ tibble 2.1.3 ✓ dplyr 0.8.3
## ✓ tidyr 1.0.0 ✓ stringr 1.4.0
## ✓ readr 1.3.1 ✓ forcats 0.4.0
## ── Conflicts ────────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
## x dplyr::filter() masks stats::filter()
## x dplyr::lag() masks stats::lag()
# no extraer, solo listar
archivos <- utils::unzip("../../datos/id_lenguaje/corpus_id_lenguaje.zip",
list = TRUE)
archivos #Contiene 6 idiomas: ENG, FRA, ITA, POR, SPA, TUR
Extraemos el contenido de los archivos tar y seleccionamos los archivos que contienen las oraciones:
# esto corre en bash
unzip -o ../../datos/id_lenguaje/corpus_id_lenguaje.zip -d ../../datos/id_lenguaje/
## Archive: ../../datos/id_lenguaje/corpus_id_lenguaje.zip
## inflating: ../../datos/id_lenguaje/eng_news_2005_10K-text.tar
## inflating: ../../datos/id_lenguaje/fra_news_2005-2008_10K-text.tar
## inflating: ../../datos/id_lenguaje/ita_news_2005-2009_10K-text.tar
## inflating: ../../datos/id_lenguaje/por_newscrawl_2011_10K-text.tar
## inflating: ../../datos/id_lenguaje/spa_news_2006_10K-text.tar
## inflating: ../../datos/id_lenguaje/tur_news_2005_10K-text.tar
descomp <- lapply(archivos$Name,
function(archivo) {
utils::untar(
tarfile = paste0('../../datos/id_lenguaje/', archivo),
exdir = '../../datos/id_lenguaje/descomp')
})
archivos_d <-
list.files(path = '../../datos/id_lenguaje/descomp',
full.names = TRUE) %>%
keep(function(x) str_detect(x, "sentences"))
Por ejemplo, tenemos:
leer_oraciones <- function(archivo, n_max = -1, skip = 0){
oraciones <- read_lines(archivo, n_max = n_max, skip = skip)
oraciones %>% str_replace_all("^[0-9]*[\t]", "")
#str_replace_all Replace matched patterns in a string.
}
#leer_oraciones(archivos_d[1], n_max = 2) #Inglés
#leer_oraciones(archivos_d[2], n_max = 2) #Francés
leer_oraciones(archivos_d[3], n_max = 2) #Italiano
## [1] "A Francoforte in evidenza il tecnologico Infineon (+10%), seguito dai finanziari Deutsche Bank (+6%) dopo che il numero uno Josef Ackerman ha ribadito che il gruppo non ricorrerà agli aiuti di stato, Deutsche Post (+10,4%) e Postbank (+6,2%)."
## [2] "Ogni peschereccio ne consuma circa 800 litri al giorno: dalla sera al rientro il motore sempre in moto."
#leer_oraciones(archivos_d[4], n_max = 2) #Portugués
#leer_oraciones(archivos_d[5], n_max = 2) #Español
#leer_oraciones(archivos_d[6], n_max = 2) #Turco
Identificar un lenguaje puede hacerse con n-gramas de caracteres (o tejas). Calculamos la probabilidad de cada lenguaje a partir de un modelo del lenguaje a partir de las secuencias de caracteres que contiene.
Las primeras funciones que necesitamos son tokenizador en caracteres, que podemos escribir sin dificultad:
library(tidytext)
token_chr <- function(textos, n = 3L){ #tejas tamaño 3
caracteres <- str_split(textos, pattern = '') %>%
map(function(x) { c(rep('_', n - 1), x) }) #añade
n_gramas <- tokenizers:::generate_ngrams_batch(caracteres,
ngram_max = n, ngram_min = n, ngram_delim = '')
n_gramas
}
token_chr("Un día soleado.") #Español
## [[1]]
## [1] "__U" "_Un" "Un " "n d" " dí" "día" "ía " "a s" " so" "sol" "ole" "lea"
## [13] "ead" "ado" "do."
token_chr("A sunny day.") #Inglés
## [[1]]
## [1] "__A" "_A " "A s" " su" "sun" "unn" "nny" "ny " "y d" " da" "day" "ay."
token_chr("Una giornata di sole.") #Italiano
## [[1]]
## [1] "__U" "_Un" "Una" "na " "a g" " gi" "gio" "ior" "orn" "rna" "nat" "ata"
## [13] "ta " "a d" " di" "di " "i s" " so" "sol" "ole" "le."
token_chr("An sonnigen Tag.") #Alemán
## [[1]]
## [1] "__A" "_An" "An " "n s" " so" "son" "onn" "nni" "nig" "ige" "gen" "en "
## [13] "n T" " Ta" "Tag" "ag."
token_chr("Une journée ensoleillée.") #Francés
## [[1]]
## [1] "__U" "_Un" "Une" "ne " "e j" " jo" "jou" "our" "urn" "rné" "née" "ée "
## [13] "e e" " en" "ens" "nso" "sol" "ole" "lei" "eil" "ill" "llé" "lée" "ée."
Y ahora escribimos la función que produce los conteos en un conjunto de entrenamiento. En este ejemplo, utilizamos un “vocabulario” de caracteres fijo (que aparecen más de un número f_min de veces). Los caracteres que no están en el vocabulario los sustituimos con \(<unk>\), que en este caso denotamos como \(*\)
conteo_chr <- function(archivo, n = 4L, n_max = n_max, f_min = 3){ #tamaño 4
#Solo se considera en el vocabulario, las tejas del conjunto de entrenamiento
#que aparezcan al menos 3 veces
df <- data_frame(txt = leer_oraciones(archivo, n_max = n_max))
vocabulario <- df %>% unnest_tokens(input = txt, output = n_grama,
token = token_chr, n = 1) %>%
group_by(n_grama) %>% tally() %>% arrange(n)
#A continuación filtramos letras en vocabulario (más de f_min apariciones)
vocab_v <- filter(vocabulario, n > f_min) %>% pull(n_grama)
V <- length(vocab_v) #es el total de secuencias de caracteres que existen
pattern <- paste(c("[^", vocab_v, "]"), collapse = '')
conteo <- df %>%
#sustituimos todos los caracteres que no estén en vocab_v por * (unknown)
mutate(txt = str_replace_all(txt, pattern = pattern, '*' )) %>%
unnest_tokens(input = txt, output = n_grama,
token = token_chr, n = n) %>%
separate(n_grama, sep = n - 1, into = c('w_0', 'w_1')) %>%
#dividimos en w_0 teja de tamaño 3 y w_1 siguiente letra
group_by(w_0, w_1) %>%
summarise(num = length(w_1)) %>%
group_by(w_0) %>%
mutate(denom = sum(num)) %>%
arrange(desc(num)) %>%
mutate(log_p = log(num + 1) - log(denom + V)) # suavizamiento de Laplace
#calculamos log probabilidades como resta porque al despejar a p obtenemos división
list(conteo = conteo, vocab = vocab_v, n = n)
}
Ahora hacemos los conteos para las primeras 5 mil frases (el resto lo usamos para evaluar modelos)
frances <- conteo_chr(archivos_d[2], n_max = 5000)
## Warning: `data_frame()` is deprecated, use `tibble()`.
## This warning is displayed once per session.
ingles <- conteo_chr(archivos_d[1], n_max = 5000)
frances$conteo %>% head(100)
ingles$conteo %>% head(100)
Pregunta: cuáles son las tejas más frecuentes en inglés?
Ordenamos las tejas en inglés con mayor log probabilidad:
Verificamos que las más comunes son preposiciones: with, and, of, by; o bien, palabras cortas: said, his, was.
arrange(ingles$conteo,desc(ingles$conteo$log_p)) %>% head(100)
Necesitaremos una función para evaluar la probabilidad de una frase dado cada modelo (nota que sería buena idea refactorizar esta función junto la función anterior):
log_p <- function(modelo){
n <- modelo$n
vocab <- modelo$vocab
V <- length(vocab)
pattern <- paste(c("[^", vocab, "]"), collapse = '')
log_p_mod <- function(frases){
dat <- data_frame(txt = frases) %>%
mutate(txt = str_replace_all(txt, pattern = pattern, '*')) %>%
unnest_tokens(input = txt, output = n_grama,
token = token_chr, n = n) %>%
separate(n_grama, sep = n - 1, into = c('w_0', 'w_1')) %>%
left_join(modelo$conteo %>% select('w_0','denom'), by ='w_0') %>%
left_join(modelo$conteo %>% select('w_0','w_1','num'), by = c('w_0','w_1')) %>%
mutate(denom = ifelse(is.na(denom), V, denom + V)) %>%
mutate(num = ifelse(is.na(num), 1, num + 1)) %>%
mutate(log_p = log(num) - log(denom))
mean(dat$log_p)
}
}
frances_log_p <- log_p(frances)
ingles_log_p <- log_p(ingles)
Y evaluamos la probabilidad de una frase bajo cada modelo:
frances_1 <- frances_log_p("C'est un bon exemple")
ingles_1 <- ingles_log_p("C'est un bon exemple")
prob_no_norm <- exp(c(fr = frances_1, en = ingles_1))
prob_no_norm
## fr en
## 0.14237573 0.03679424
Si estamos solamente comparando inglés con francés, podemos normalizar las probabilidades obtenidas:
prob_norm <- prob_no_norm/sum(prob_no_norm)
round(prob_norm, 3)
## fr en
## 0.795 0.205
frances_1 <- frances_log_p('This is a short example')
ingles_1 <- ingles_log_p('This is a short example')
prob_no_norm <- exp(c(fr = frances_1, en = ingles_1))
prob_norm <- prob_no_norm/sum(prob_no_norm)
round(prob_norm, 3)
## fr en
## 0.229 0.771
Finalmente, podemos ahora evaluar los modelos con los conjuntos de prueba (puedes cambiar el tamaño de los n-gramas y el filtro de caracteres desconocidos para ver cómo se desempeñan):
El modelo para francés (0.11%) se desempeñó ligeramente mejor que el de inglés (0.107%)
frances_prueba <- leer_oraciones(archivos_d[2], skip = 5000)
ingles_prueba <- leer_oraciones(archivos_d[1], skip = 5000)
frances_log_p(frances_prueba)
## [1] -2.207059
ingles_log_p(ingles_prueba)
## [1] -2.23477
Aumentamos el tamaño de los n-gramas (tejas de tamaño 6) y
token_chr_v2 <- function(textos, n = 6L){ #tejas tamaño 6
caracteres <- str_split(textos, pattern = '') %>%
map(function(x) { c(rep('_', n - 1), x) }) #añade
n_gramas <- tokenizers:::generate_ngrams_batch(caracteres,
ngram_max = n, ngram_min = n, ngram_delim = '')
n_gramas
}
token_chr_v2("A sunny day.") #Inglés
## [[1]]
## [1] "_____A" "____A " "___A s" "__A su" "_A sun" "A sunn" " sunny" "sunny "
## [9] "unny d" "nny da" "ny day" "y day."
token_chr_v2("Une journée ensoleillée.") #Francés
## [[1]]
## [1] "_____U" "____Un" "___Une" "__Une " "_Une j" "Une jo" "ne jou" "e jour"
## [9] " journ" "journé" "ournée" "urnée " "rnée e" "née en" "ée ens" "e enso"
## [17] " ensol" "ensole" "nsolei" "soleil" "oleill" "leillé" "eillée" "illée."
bajamos el filtro de caracteres desconocidos a 2 para ver cómo se desempeñan):
conteo_chr_v2 <- function(archivo, n = 6L, n_max = n_max, f_min = 2){
#Solo se considera en el vocabulario, las tejas del conjunto de entrenamiento
#que aparezcan al menos 2 veces
df <- data_frame(txt = leer_oraciones(archivo, n_max = n_max))
vocabulario <- df %>% unnest_tokens(input = txt, output = n_grama,
token = token_chr_v2, n = 1) %>%
group_by(n_grama) %>% tally() %>% arrange(n)
#A continuación filtramos letras en vocabulario (más de f_min apariciones)
vocab_v <- filter(vocabulario, n > f_min) %>% pull(n_grama)
V <- length(vocab_v) #es el total de secuencias de caracteres que existen
pattern <- paste(c("[^", vocab_v, "]"), collapse = '')
conteo <- df %>%
#sustituimos todos los caracteres que no estén en vocab_v por * (unknown)
mutate(txt = str_replace_all(txt, pattern = pattern, '*' )) %>%
unnest_tokens(input = txt, output = n_grama,
token = token_chr_v2, n = n) %>%
separate(n_grama, sep = n - 1, into = c('w_0', 'w_1')) %>%
#dividimos en w_0 teja de tamaño 5 y w_1 siguiente letra
group_by(w_0, w_1) %>%
summarise(num = length(w_1)) %>%
group_by(w_0) %>%
mutate(denom = sum(num)) %>%
arrange(desc(num)) %>%
mutate(log_p = log(num + 1) - log(denom + V)) # suavizamiento de Laplace para asignar probabilidaes a palabras unknown
#calculamos log probabilidades como resta porque al despejar a p obtenemos división
list(conteo = conteo, vocab = vocab_v, n = n)
}
frances_v2 <- conteo_chr_v2(archivos_d[2], n_max = 5000)
ingles_v2 <- conteo_chr_v2(archivos_d[1], n_max = 5000)
frances_v2$conteo %>% head(100)
ingles_v2$conteo %>% head(100)
Volvemos a revisar las tejas más frecuentes en inglés: (en general la log probabilidad es más chica pues estamos siendo más estrictos al aumentar el tamaño de tejas)
arrange(ingles_v2$conteo,desc(ingles_v2$conteo$log_p)) %>% head(100)
Finalmente, podemos ahora evaluar los modelos con los conjuntos de prueba
log_p_v2 <- function(modelo){
n <- modelo$n
vocab <- modelo$vocab
V <- length(vocab)
pattern <- paste(c("[^", vocab, "]"), collapse = '')
log_p_mod <- function(frases){
dat <- data_frame(txt = frases) %>%
mutate(txt = str_replace_all(txt, pattern = pattern, '*')) %>%
unnest_tokens(input = txt, output = n_grama,
token = token_chr_v2, n = n) %>%
separate(n_grama, sep = n - 1, into = c('w_0', 'w_1')) %>%
left_join(modelo$conteo %>% select('w_0','denom'), by ='w_0') %>%
left_join(modelo$conteo %>% select('w_0','w_1','num'), by = c('w_0','w_1')) %>%
mutate(denom = ifelse(is.na(denom), V, denom + V)) %>%
mutate(num = ifelse(is.na(num), 1, num + 1)) %>%
mutate(log_p = log(num) - log(denom))
mean(dat$log_p)
}
}
frances_log_p_v2 <- log_p_v2(frances_v2)
ingles_log_p_v2 <- log_p_v2(ingles_v2)
El performance bajó considerablemente debido a que estamos perdiendo del vocabulario tejas comunes de tamaño más pequeño y estamos añadiendo ruido al considerar vocabulario de tejas con conteo de sólo 2 repeticiones. Notamos además que el francés (0.072%) sigue siendo más acertado que el de inglés (0.067%)
frances_prueba_v2 <- leer_oraciones(archivos_d[2], skip = 5000)
ingles_prueba_v2 <- leer_oraciones(archivos_d[1], skip = 5000)
frances_log_p_v2(frances_prueba_v2)
## [1] -2.62069
ingles_log_p_v2(ingles_prueba_v2)
## [1] -2.694913
Pregunta: - Escoge algún otro idioma además de francés e inglés. Construye su modelo de lenguaje como hicimos arriba.
Haremos el ejercicio con Español: usando los parámetros dados inicialmente
español <- conteo_chr(archivos_d[5], n_max = 5000)
español$conteo %>% head(100)
Pregunta: cuáles son las tejas más frecuentes en Español?
Ordenamos las tejas en español con mayor log probabilidad:
Verificamos que las más comunes son artículos y preposiciones: los, las, el, que; o bien, combinaciones antes y después de la preposición que
arrange(español$conteo,desc(español$conteo$log_p)) %>% head(100)
español_log_p <- log_p(español)
Y evaluamos la probabilidad de la primer frase vista previamente bajo cada modelo notando que las primeras 2 se mantienen exactamente igual sin normalización, sigue detentando con mayor probabilidad que la frase es del idioma francés, español es ligeramente mayor que inglés:
español_1 <- español_log_p("C'est un bon exemple")
frances_1 <- frances_log_p("C'est un bon exemple")
ingles_1 <- ingles_log_p("C'est un bon exemple")
prob_no_norm <- exp(c(fr = frances_1, en = ingles_1, es=español_1))
prob_no_norm
## fr en es
## 0.14237573 0.03679424 0.03695630
Si estamos solamente comparando inglés, francés y español, podemos normalizar las probabilidades obtenidas:
prob_norm <- prob_no_norm/sum(prob_no_norm)
round(prob_norm, 3)
## fr en es
## 0.659 0.170 0.171
Pregunta: - Muestra algunos ejemplos de cómo identifica correcta o incorrectamente el lenguaje en distintas frases.
En el siguiente ejemplo, identifica correctamente el lenguaje Español aunque da alta probabilidad a francés por la coincidencia en las tejas que existen a partir de las palabras “restaurant”, “un” en el idioma francés
f<-c("comeré en un restaurante")
español_f <- español_log_p(f)
frances_f <- frances_log_p(f)
ingles_f <- ingles_log_p(f)
prob_no_norm <- exp(c(fr = frances_f, en = ingles_f, es=español_f))
prob_norm <- prob_no_norm/sum(prob_no_norm)
round(prob_norm, 3)
## fr en es
## 0.320 0.226 0.453
Vuelve a predecir correctamente dando mayor probabilidad a Español:
f<-c("es un bello collage")
español_f <- español_log_p(f)
frances_f <- frances_log_p(f)
ingles_f <- ingles_log_p(f)
prob_no_norm <- exp(c(fr = frances_f, en = ingles_f, es=español_f))
prob_norm <- prob_no_norm/sum(prob_no_norm)
round(prob_norm, 3)
## fr en es
## 0.236 0.285 0.480
Predice incorrectamente Francés y le da la menor probabilidad a Español que es lo correcto
f<-c("checa tu mail")
español_f <- español_log_p(f)
frances_f <- frances_log_p(f)
ingles_f <- ingles_log_p(f)
prob_no_norm <- exp(c(fr = frances_f, en = ingles_f, es=español_f))
prob_norm <- prob_no_norm/sum(prob_no_norm)
round(prob_norm, 3)
## fr en es
## 0.486 0.279 0.235
Predice incorrectamente Inglés, siendo Español
f<-c("buen resumen")
español_f <- español_log_p(f)
frances_f <- frances_log_p(f)
ingles_f <- ingles_log_p(f)
prob_no_norm <- exp(c(fr = frances_f, en = ingles_f, es=español_f))
prob_norm <- prob_no_norm/sum(prob_no_norm)
round(prob_norm, 3)
## fr en es
## 0.229 0.488 0.283
frances_1 <- frances_log_p('This is a short example')
ingles_1 <- ingles_log_p('This is a short example')
prob_no_norm <- exp(c(fr = frances_1, en = ingles_1))
prob_norm <- prob_no_norm/sum(prob_no_norm)
round(prob_norm, 3)
## fr en
## 0.229 0.771
Pregunta - (Extra) Muestra la matriz de confusión del clasificador de estos tres lenguajes.
Parece que hay un error en la lectura de las oraciones en español cuando usamos skip:
length(leer_oraciones(archivos_d[5],skip = 0))
## [1] 10000
leer_oraciones(archivos_d[5],skip = 4968)
## [1] "Con 22 años, Pedro alterna el fútbol con sus estudios de Empresariales, y pese a su carácter ambicioso, reconoce que \"no me veo de nuevo de titular este domingo, porque se recuperan varios compañeros."
## [2] "José María Aljama explicó que no se instalarán asientos junto al escenario, ya que se espera «que el público esté de pie y baile con la música», aunque sí permanecerán abiertas las terrazas de la Corredera."
## [3] "Jueves 31 de Agosto de 2006 / Prensa mundial repudia el berrinche de AMLO REACCIONES."
## [4] "Ahora, siete meses despus, Isabel es otra persona."
## [5] "Una de las razones del auge de los fertilizantes mezcla es la exactitud en su formulación y el consecuente ahorro en volumen de producto que se traduce en una mejor logística."
## [6] "Resiste los malos efectos del calor y humedad de los trópicos mejor que cualquier otro explosivo que existe en el mercado."
## [7] "Su supuesto secuestrador, ahora prófugo de la Justicia, es un joven de 25 años cuyo padre vive a media cuadra de la casa de Romina."
## [8] "Una antena puede cubrir un área determinada sin necesidad de cables."
## [9] "Testigos presenciales explicaron que el siniestro se produjo al tocarse las dos aeronaves cuando realizaban maniobras de aproximación a la pista."
## [10] "El presidente de EU, George W. Bush, viajó hoy a la frontera con México, donde visitará un centro federal de instrucción policial y hablará sobre la seguridad fronteriza y la ley de inmigración para la que ha propuesto una reforma general."
## [11] "Después de hacer todos los procedimientos de rigor, se tomó la decisión de aprobar la Notaría Séptima para el sector de Cuba."
## [12] "Sánchez estuvo acompañado por su ayudante de campo, Carlos Aragonés, por el presidente de la Federación Boliviana de Fútbol (FBF), Carlos Chávez; y por David Paniagua, del gremio de futbolistas profesionales de Bolivia."
## [13] "La constitución actual es una mala copia de la de 1955, en el apogeo de la Era."
## [14] "Por aquel entonces, se cerraban operaciones astronómicas por la mayoría de jugadores."
## [15] "En este último país se les pidió que localizaran y rescataran a un madrileño de 27 años que se había perdido en un río infectado de cocodrilos."
## [16] "En las últimas semanas, Afganistán fue escenario de algunos delos peores combates desde la guerra de 2001, en la que Estados Unidos derrocó al entonces gobernante movimiento ultraintegrista talibán."
## [17] "La carrera de este año comenzó el viernes 30 con el tramo León-Benavente."
## [18] "¿Se perdió una artista por una voluntaria?"
## [19] "\"Soy consciente de que el preámbulo ha dado mucho que hablar\", reconoció Rajoy."
## [20] "La batalla del Contrato del Primer Empleo (CPE) se traslada al Parlamento: el partido gobernante UMP anunció ayer que presentará una proposición de ley para modificarlo, mientras que la oposición de izquierda contraatacará con otra para derogarlo."
## [21] "Alonso hizo de todo."
## [22] "Se negociará también un sistema de bajas incentivadas para trabajadores menores de 52 años, de acogimiento voluntario del empleado y aceptación por parte de la empresa."
## [23] "En ese sitio dos sujetos con una pistola de juguete y con lujo de violencia amagaron a Samuel Bernal Zúñiga, de 36 años, y lo bajaron de su taxi modelo Atos, placas 632 SAE."
## [24] "El Papa declaró después que había perdonado a Ali Agca y le visitó en el 1983 en la cárcel romana de Rebbibia."
## [25] "Apoyar en su gestión a Mel Zelaya, darle una tregua de dos años de gobierno, y con la dirección del partido y sus bases, consolidar al máximo y en la realidad al Poder Ciudadano."
## [26] "Está muy desmejorada."
leer_oraciones(archivos_d[5],skip = 4969)
## character(0)
#Como Español sólo cuenta con 4,968 deberíamos repetir todo el código de arriba cambiando skip=4000 por ejemplo, pero YOLO:
español_prueba <- leer_oraciones(archivos_d[5], skip = 4968)
frances_prueba <- leer_oraciones(archivos_d[2], skip = 4968)
ingles_prueba <- leer_oraciones(archivos_d[1], skip = 4968)
fr_fr<-frances_log_p(frances_prueba)
fr_en<-ingles_log_p(frances_prueba)
fr_es<-español_log_p(frances_prueba)
en_fr<-frances_log_p(ingles_prueba)
en_en<-ingles_log_p(ingles_prueba)
en_es<-español_log_p(ingles_prueba)
es_fr<-frances_log_p(español_prueba)
es_en<-ingles_log_p(español_prueba)
es_es<-español_log_p(español_prueba)
Llenamos la matriz de confusión con las log probabilidades:
matriz_confusion <- matrix(c(fr_fr,fr_en,fr_es,en_fr,en_en,en_es,es_fr,es_en,es_es), nrow = 3, dimnames = list(c("fr.pred","en.pred","es.pred"), c("fr","en","es")))
matriz_confusion
## fr en es
## fr.pred -2.205104 -3.348855 -3.071010
## en.pred -3.228237 -2.221502 -3.409234
## es.pred -3.094688 -3.446318 -2.146958
Ahora normalizamos y vemos que las predicciones en general no son tan buenas:
mat_norm<-exp(matriz_confusion)
for(i in 1:3)
{
mat_norm[i,]<-mat_norm[i,]/sum(mat_norm[i,])
}
round(mat_norm, 3)
## fr en es
## fr.pred 0.575 0.183 0.242
## en.pred 0.219 0.599 0.183
## es.pred 0.233 0.164 0.602