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