NLLanguage​Recognizer

Cuando viajo, una de mis actividades favoritas es escuchar a la gente cuando pasa y tratar de adivinar en qué idioma hablan. Y me inclino a pensar que, con el tiempo, me he vuelvo muy bueno en ello (aunque rara vez llego a saber si acierto).

Con suerte, reconozco alguna palabra o frase del idioma en cuestión con la que puedo estar familiarizado y deduzco cosas a partir de ahí. En cualquier otro caso, lo que hago es construir un inventario fonético con los tipos de sonido presentes. Por ejemplo, ¿está usando el hablante vibrantes múltiples alveolares sonoras ⟨r⟩, vibrantes alveolares simples ⟨ɾ⟩, o aproximantes alveolares ⟨ɹ⟩? ¿Son las vocales en su mayoría abiertas / cerradas; anteriores / posteriores? ¿Algún sonido inusual, como ⟨ʇ⟩?

O al menos, eso es lo que creo que hago. Sinceramente, todo esto ocurre inconsciente y automáticamente para todos nosotros respecto a cualquier tarea de reconocimiento del lenguaje, de forma que solo tenemos una remota idea de cómo llegamos de la entrada a la salida.

Las computadoras funcionan de manera similar. Después de muchas horas de entrenamiento, los modelos de aprendizaje automático pueden predecir el idioma de un texto con una precisión mucho mayor de la que se obtiene con un enfoque formalizado y secuencial.

El Machine learning (Aprendizaje Automático) ha sido el núcleo del procesamiento del lenguaje natural en las plataformas de Apple durante mucho tiempo, pero ha sido recientemente cuando los desarrolladores externos han tenido acceso para su aprovechamiento.


El framework de Lenguaje Natural, nuevo en iOS 12 y macOS 10.14, refina APIs lingüísticas existentes y expone nueva funcionalidad a los desarrolladores.

NLTagger es un NSLinguisticTagger renovado. NLTokenizer es un reemplazo de enumerateSubstrings(in:options:using:) (originalmente CFStringTokenizer). NLLanguageRecognizer extiende la funcionalidad comentada en NSLinguisticTagger a través de dominantLanguage, brindándonos pistas y predicciones acerca del texto analizado.

Reconociendo el idioma de un texto

Así se utiliza NLLanguageRecognizer para adivinar el idioma dominante en un texto dado:

import NaturalLanguage

let string = """
私はガラスを食べられます。それは私を傷つけません。
"""

let recognizer = NLLanguageRecognizer()
recognizer.processString(string)
recognizer.dominantLanguage // ja

Crea una instancia de NLLanguageRecognizer y llama al método processString(_:) pasándole una ristra. A partir de la propiedad dominantLanguage se accede a un objeto de tipo NLLanguage que contiene el tag BCP-47 del idioma predicho. En este caso, "ja", del japonés (日本語).

Obteniendo hipótesis de varios idiomas

Si estudiaste lingüística en la universidad o cursaste latín en el instituto, estarás familiarizado con algunos de los ejemplos más graciosos de homonimia lingüística entre el latín y el italiano moderno.

Por ejemplo, considera la siguiente frase:

CANE NERO MAGNA BELLA PERSICA!

Idioma Traducción
Latin ¡Canta, Nero, la gran Guerra Pérsica!
Italiano ¡El perro negro se come un bonito melocotón!

Para disgusto de Max Fisher, el latín no es uno de los idiomas soportados por NLLanguageRecognizer, así que no contaremos con ejemplos tan entretenidos como el anterior.

A poco que experimentes, te darás cuenta de que es bastante difícil que NLLanguageRecognizer adivine incorrectamente o con mala precisión. Es capaz de pasar de la desviación estándar al 95% de certeza con tan solo un puñado de palabras.

Después de un poco de ensayo y error, logramos que NLLanguageRecognizer se equivoque con un texto de tamaño no trivial, como el Artículo I de la Declaración Universal de Derechos Humanos en noruego bokmål:

let string = """
Alle mennesker er født frie og med samme menneskeverd og menneskerettigheter.
De er utstyrt med fornuft og samvittighet og bør handle mot hverandre i brorskapets ånd.
"""

let languageRecognizer = NLLanguageRecognizer()
languageRecognizer.processString(string)
recognizer.dominantLanguage // da (!)

La Declaración Universal de Derechos Humanos, es uno de los documentos más traducidos del mundo, con traducciones en más de 500 idiomas. Por esta razón, se usa muy a menudo para tareas de procesamiento del lenguaje natural.

El danés y el noruego bokmål son lenguajes muy similares, por lo que no es de extrañar que NLLanguageRecognizer predijera incorrectamente. (Para comparar, aquí tienes el texto equivalente en danés)

Podemos usar el método languageHypotheses(withMaximum:) para tener una idea de cuán segura era la conjetura de dominantLanguage:

languageRecognizer.languageHypotheses(withMaximum: 2)
Idioma Fiabilidad
Danés (da) 56%
Noruego bokmål (nb) 43%

En el momento en que escribí este artículo, la propiedad languageHints no estaba documentada, así que no está muy claro cómo debería usarse. Sin embargo, si pasamos un diccionario ponderado de pistas, parece tener el efecto esperado, reforzando las hipótesis:

languageRecognizer.languageHints = [.danish: 0.25, .norwegian: 0.75]
Idioma Fiabilidad (con pistas)
Danish (da) 30%
Noruego Bokmål (nb) 70%

¿Y qué podemos hacer una vez sabemos el idioma de una ristra?

Aquí tienes algunos casos de uso a considerar:

Comprobación ortográfica

Combina NLLanguageRecognizer con UITextChecker para comprobar la ortografía de las palabras de una ristra:

Crea un NLLanguageRecognizer e inicialízalo con una ristra llamando al método processString(_:):

let string = """
Wenn ist das Nunstück git und Slotermeyer?
Ja! Beiherhund das Oder die Flipperwaldt gersput!
"""

let languageRecognizer = NLLanguageRecognizer()
languageRecognizer.processString(string)
let dominantLanguage = languageRecognizer.dominantLanguage! // de

Luego, pasa el rawValue del objeto NLLanguage devuelto por la propiedad dominantLanguage al parámetro language de rangeOfMisspelledWord(in:range:startingAt:wrap:language:):

let textChecker = UITextChecker()

let nsString = NSString(string: string)
let stringRange = NSRange(location: 0, length: nsString.length)
var offset = 0

repeat {
    let wordRange =
            textChecker.rangeOfMisspelledWord(in: string,
                                              range: stringRange,
                                              startingAt: offset,
                                              wrap: false,
                                              language: dominantLanguage.rawValue)
    guard wordRange.location != NSNotFound else {
        break
    }

    print(nsString.substring(with: wordRange))

    offset = wordRange.upperBound
} while true

Cuando se le pasa El chiste más gracioso del mundo, se identifican las siguientes palabras como mal escritas:

  • Nunstück
  • Slotermeyer
  • Beiherhund
  • Flipperwaldt
  • gersput

Sintetización del habla

Puedes combinar NLLanguageRecognizer con AVSpeechSynthesizer para escuchar cualquier texto de un idioma leído en voz alta:

let string = """
Je m'baladais sur l'avenue le cœur ouvert à l'inconnu
    J'avais envie de dire bonjour à n'importe qui.
N'importe qui et ce fut toi, je t'ai dit n'importe quoi
    Il suffisait de te parler, pour t'apprivoiser.
"""

let languageRecognizer = NLLanguageRecognizer()
languageRecognizer.processString(string)
let language = languageRecognizer.dominantLanguage!.rawValue // fr

let speechSynthesizer = AVSpeechSynthesizer()
let utterance = AVSpeechUtterance(string: string)
utterance.voice = AVSpeechSynthesisVoice(language: language)
speechSynthesizer.speak(utterance)

No tiene la delicadeza lírica de Joe Dassin, pero ainsi va la vie.


Antes de entender algo, primero tienes que querer entender. Y el primer paso para entender el lenguaje natural es determinar su idioma.

NLLanguageRecognizer ofrece una potente interfaz a la funcionalidad responsable de muchas de las funciones inteligentes presentes en iOS y macOS. Intenta sacarle partido para tener un mayor entendimiento sobre tus usuarios.

Artículo siguiente

El tema de esta semana es Hashable y su tipo relacionado, Hasher. Juntos, conforman la funcionalidad subyacente de dos de las clases más queridas de Swift: Diccionary y Set.