1. Code
  2. Coding Fundamentals
  3. Machine Learning

Cómo codificar el procesamiento del lenguaje natural en Android con IBM Watson

Scroll to top
This post is part of a series called IBM Watson Machine Learning.
Use Machine Learning to Recognize Images With IBM Watson

Spanish (Español) translation by Naudys Angulo (you can also view the original English article)

Gracias a la creciente ola de inteligencia artificial, los usuarios de hoy en día esperan aplicaciones que sean inteligentes y conscientes de los contextos en los que se utilizan. Watson de IBM ofrece una serie de servicios relacionados con el lenguaje natural que puedes utilizar para crear este tipo de aplicaciones.

Por ejemplo, puedes utilizar su servicio de Comprensión del Lenguaje Natural para extraer palabras clave, entidades, sentimientos y muchos otros detalles semánticos de cualquier texto que el usuario esté leyendo. Y si el texto está en un idioma extranjero, puedes utilizar el servicio de traducción de idiomas para identificar el idioma y traducirlo a uno que el usuario entienda.

En este tutorial, voy a presentarte algunos de estos servicios mostrándote cómo crear una aplicación que pueda traducir páginas web alemanas al inglés y extraer de ellas sentimientos, entidades importantes y emociones.

Antes de continuar, te sugiero que leas el siguiente tutorial introductorio sobre los servicios de Watson de IBM:

1. Activación de los servicios

Hoy vamos a trabajar con tres servicios de Watson, y cada uno de ellos necesita ser activado por separado. Así que abre tu panel de control de IBM Bluemix y pulsa el botón Crear.

El primer servicio que vamos a activar es el de Conversión de Documentos, que nos permite convertir documentos HTML, PDF y DOCX a texto plano o JSON. Selecciónalo del catálogo, dale un nombre significativo y pulsa el botón Crear.

Configure Document Conversion serviceConfigure Document Conversion serviceConfigure Document Conversion service

A continuación, vuelve al catálogo y elija el servicio Traductor de idiomas. Este servicio es compatible con varios idiomas ampliamente hablados y puede, por defecto, manejar texto en tres dominios: noticias, conversaciones y patentes. Mientras que los dos primeros dominios son adecuados para la mayoría de los textos, el último puede ser más preciso para los textos que contienen muchos términos técnicos o legales.

En su página de configuración, dale al servicio un nombre significativo y pulsa el botón Crear.

Configuring Language Translator serviceConfiguring Language Translator serviceConfiguring Language Translator service

Vuelve al catálogo y elige el servicio de Comprensión del Lenguaje Natural. Utilizaremos este servicio para extraer sentimientos, entidades y emociones del texto no estructurado. De nuevo, dale un nombre significativo en la pantalla de configuración y pulsa el botón Crear.

Configuring Natural Language Understanding serviceConfiguring Natural Language Understanding serviceConfiguring Natural Language Understanding service

Si ahora abres el panel de control, deberías ver algo como esto:

Three active services in the dashboardThree active services in the dashboardThree active services in the dashboard

Los tres servicios tienen credenciales de acceso únicas asociadas a ellos. Debes anotarlas todas porque las necesitará más adelante. Para determinar las credenciales de cualquier servicio, selecciónalo en el tablero, abre su pestaña de Credenciales de servicio y pulsa el botón Ver credenciales.

2. Configuración del proyecto

Para poder utilizar estos tres servicios en un proyecto de Android Studio, debemos añadir el Watson Java SDK como una dependencia de implementación en el archivo build.gradle del módulo de la aplicación.

1
implementation 'com.ibm.watson.developer_cloud:java-sdk:3.9.1'

Además, usaremos la librería Fuel como cliente HTTP, así que añádela también como dependencia de implementación.

1
implementation 'com.github.kittinunf.fuel:fuel-android:1.10.0'

Tanto Fuel como el Watson Java SDK solo pueden funcionar si nuestra aplicación tiene el permiso de INTERNET, así que pídelo en el archivo de manifiesto.

1
<uses-permission android:name="android.permission.INTERNET"/>

A continuación, añade etiquetas <string> que contengan los nombres de usuario y las contraseñas de los tres servicios al archivo strings.xml.

1
<string name="document_conversion_username">USERNAME1</string>
2
<string name="document_conversion_password">PASSWORD1</string>
3
4
<string name="language_translator_username">USERNAME2</string>
5
<string name="language_translator_password">PASSWORD2</string>
6
7
<string name="natural_language_understanding_username">USERNAME3</string>
8
<string name="natural_language_understanding_password">PASSWORD3</string>

Por último, para mantener nuestro código conciso, en este tutorial usaremos Kotlin en lugar de Java, así que asegúrate de haber habilitado el soporte de Kotlin.

3. Uso del servicio de conversión de documentos

Utilizaremos el servicio de conversión de documentos de Watson para convertir las páginas web HTML en texto plano. Para permitir que el usuario escriba la dirección de una página web, añade un widget EditText al diseño de tu actividad. Además, incluye un widget TextView para mostrar el contenido de la página web como texto sin formato. Para asegurarte de que los contenidos de las páginas web largas no se truncan, te sugiero que los coloques dentro de un widget ScrollView.

1
<EditText
2
    android:layout_width="match_parent"
3
    android:layout_height="wrap_content"
4
    android:id="@+id/documentURL"
5
    android:inputType="textUri"
6
    android:hint="URL"
7
    android:imeOptions="actionGo"
8
    />
9
10
<ScrollView
11
    android:layout_width="wrap_content"
12
    android:layout_height="match_parent"
13
    android:layout_below="@+id/documentURL">
14
    <TextView
15
        android:layout_width="match_parent"
16
        android:layout_height="match_parent"
17
        android:id="@+id/documentContents"
18
        />
19
</ScrollView>

En el código anterior, puedes ver que el atributo imeOptions del widget EditText está configurado como actionGo. Esto permite a los usuarios pulsar un botón "Go" en sus teclados virtuales cuando han terminado de escribir la dirección. Para escuchar ese evento de pulsación de botón, añade el siguiente código Kotlin al método onCreate() de tu actividad:

1
documentURL.setOnEditorActionListener { _, action, _ ->
2
    if (action == EditorInfo.IME_ACTION_GO) {
3
        
4
        // More code here

5
                
6
    }
7
    false
8
}

Dentro del escuchador de eventos, lo primero que tenemos que hacer es determinar la URL que el usuario escribió. Podemos hacerlo fácilmente accediendo a la propiedad text del widget EditText. Una vez que tenemos la URL, podemos usar el método httpGet() de Fuel para descargar el contenido de la página web.

Como queremos que el método httpGet() se ejecute de forma asíncrona, debemos añadirle una llamada de retorno utilizando el método responseString(), que también nos permite procesar el contenido descargado como una cadena.

1
val url:String = documentURL.text.toString()
2
url.httpGet().responseString { _, _, result ->
3
    val (document, _) = result
4
    if (err == null) {
5
        // More code here

6
    }
7
}

Ahora es el momento de crear una instancia de la clase DocumentConversion, que tiene todos los métodos que necesitamos para interactuar con el servicio de conversión de documentos. Su constructor espera una fecha de versión junto con las credenciales de acceso al servicio.

1
val documentConverter = DocumentConversion(
2
    DocumentConversion.VERSION_DATE_2015_12_01,
3
    resources.getString(R.string.document_conversion_username),
4
    resources.getString(R.string.document_conversion_password)
5
)

El Watson Java SDK no nos permite pasar directamente cadenas al servicio de Conversión de Documentos. En su lugar, necesita objetos de File. Por lo tanto, vamos a crear un archivo temporal utilizando el método createTempFile() de la clase File, y escribiremos en él el contenido de la página web que hemos descargado utilizando el método writeText().

1
val tempFile = File.createTempFile("temp_file", null)
2
tempFile.writeText(document, Charsets.UTF_8)

Llegados a este punto, podemos llamar al método convertDocumentToText() y pasarle el archivo temporal para iniciar la conversión. El método también espera el tipo MIME del archivo temporal, así que no olvides incluirlo. Una vez completada la conversión, puedes mostrar el texto sin formato simplemente asignándolo a la propiedad text del widget TextView.

El siguiente código muestra cómo realizar la conversión dentro de un nuevo hilo y actualizar el TextView en el hilo de la UI:

1
AsyncTask.execute {
2
    val plainText = documentConverter
3
                     .convertDocumentToText(tempFile, "text/html")
4
                     .execute()
5
    runOnUiThread {
6
        documentContents.text = plainText
7
    }
8
}

Puedes ejecutar la aplicación ahora y escribir la URL de una página web alemana para ver cómo funciona el servicio de conversión de documentos.

A German webpage converted to plain textA German webpage converted to plain textA German webpage converted to plain text

4. Uso del servicio de traducción de idiomas

Con el servicio Traductor de idiomas, ahora convertiremos el texto plano, que está en alemán, al inglés.

En lugar de actualizar nuestro diseño, para permitir al usuario iniciar manualmente la traducción, vamos a añadir un menú a nuestra actividad. Para ello, comienza por crear un nuevo archivo de recursos de menú y añade el siguiente código:

1
<?xml version="1.0" encoding="utf-8"?>
2
<menu xmlns:android="https://schemas.android.com/apk/res/android"
3
    xmlns:app="http://schemas.android.com/apk/res-auto">
4
5
    <item android:id="@+id/action_translate"
6
        android:title="Translate"
7
        app:showAsAction = "never" />
8
9
    <item android:id="@+id/action_analyze"
10
        android:title="Analyze"
11
        app:showAsAction = "never" />
12
    
13
</menu>

Como puedes ver, el código anterior crea un menú con dos opciones: traducir y analizar. En este paso, trabajaremos solo con la primera opción.

Para renderizar el menú, debemos inflarlo dentro del método onCreateOptionsMenu() de nuestra actividad.

1
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
2
    menuInflater.inflate(R.menu.my_menu, menu)
3
    return super.onCreateOptionsMenu(menu)
4
}

Sobreescribiendo el método onOptionsItemSelected(), podemos saber cuándo el usuario utiliza el menú. Además, podemos determinar qué elemento ha pulsado el usuario comprobando el itemId. El siguiente código comprueba si el usuario eligió la opción translate.

1
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
2
    if(item?.itemId == R.id.action_translate) {
3
        // More code here

4
    }
5
    return true
6
}

Al igual que el servicio de documentos, el servicio de traductor de idiomas también tiene una clase dedicada que nos permite interactuar con él. Como habrás adivinado, se llama LanguageTranslator. Para crear una instancia de la clase, solo tenemos que pasar las credenciales de acceso al servicio a su constructor.

1
val translator = LanguageTranslator(
2
        resources.getString(R.string.language_translator_username),
3
        resources.getString(R.string.language_translator_password)
4
)

La clase tiene un método translate() que podemos utilizar ahora para traducir nuestro texto alemán al inglés. Como sus argumentos, estos esperan el texto a traducir como cadena, el idioma actual del texto y el idioma deseado.

Cuando la traducción se complete con éxito, tendremos acceso a un objeto TranslationResult, cuya propiedad firstTranslation contiene el texto traducido.

El siguiente código muestra cómo realizar la traducción y mostrar el resultado en el widget TextView.

1
AsyncTask.execute {
2
    val translatedDocument = translator
3
                                .translate(
4
                                    documentContents.text
5
                                                    .toString(),
6
                                    Language.GERMAN,
7
                                    Language.ENGLISH
8
                                ).execute()
9
    runOnUiThread {
10
        documentContents.text = translatedDocument
11
                                .firstTranslation
12
    }
13
}

Ahora puedes volver a ejecutar la aplicación, escribir la URL de una página web en alemán y utilizar el menú para traducir su contenido al inglés.

Text in German translated to EnglishText in German translated to EnglishText in German translated to English

5. Uso del servicio de comprensión del lenguaje natural

Por último, para realizar un análisis semántico del texto traducido y extraer varios detalles importantes del mismo, podemos utilizar la clase NaturalLanguageUnderstanding, que sirve de cliente para el servicio Natural Language Understanding.

El siguiente código muestra cómo inicializar el cliente solo cuando el usuario pulsa la segunda opción del menú que hemos creado en el paso anterior:

1
if(item?.itemId == R.id.action_analyze) {
2
    val analyzer = NaturalLanguageUnderstanding(
3
            NaturalLanguageUnderstanding.VERSION_DATE_2017_02_27,
4
            resources.getString(
5
                R.string.natural_language_understanding_username),
6
            resources.getString(
7
                R.string.natural_language_understanding_password)
8
    )
9
10
    // More code here    

11
}

En comparación con los otros servicios relacionados con el lenguaje natural, el uso del servicio de Comprensión del Lenguaje Natural es un poco más complicado, principalmente porque tiene un gran número de características.

Por ahora, digamos que queremos determinar el sentimiento general del texto traducido y extraer todas las entidades principales que este menciona. Cada entidad puede tener una emoción y un sentimiento asociados, así que digamos que también queremos extraerlos.

Para decirle al servicio que queremos extraer todas las entidades y las emociones y sentimientos asociados a ellas, necesitamos un objeto EntitiesOptions, que puede crearse utilizando la clase EntitiesOptions.Builder.

1
val entityOptions = EntitiesOptions.Builder()
2
        .emotion(true)
3
        .sentiment(true)
4
        .build()

Del mismo modo, para decirle al servicio que queremos el sentimiento general del texto, necesitamos un objeto SentimentOptions.

1
val sentimentOptions = SentimentOptions.Builder()
2
        .document(true)
3
        .build()

Los objetos SentimentOptions y EntitiesOptions deben unirse ahora para formar un objeto Features, que puede utilizarse para componer un objeto AnalyzeOptions. El objeto AnalyzeOptions es el más importante de todos los objetos anteriores porque es donde se especifica el texto que se quiere analizar.

1
val features = Features.Builder()
2
        .entities(entityOptions)
3
        .sentiment(sentimentOptions)
4
        .build()
5
6
val analyzerOptions = AnalyzeOptions.Builder()
7
        .text(documentContents.text.toString())
8
        .features(features)
9
        .build()

Una vez que el objeto AnalyzeOptions está listo, podemos pasarle el método analyze() para iniciar el análisis.

1
AsyncTask.execute {
2
    val results = analyzer.analyze(analyzerOptions).execute()
3
4
    // More code here

5
}

El resultado del análisis es un objeto AnalysisResults, que contiene toda la información que hemos pedido.

Para determinar el sentimiento global del texto, primero debemos extraer la puntuación global del sentimiento utilizando la propiedad sentiment.document.score. La puntuación de sentimiento no es más que un número de punto flotante. Si es cero, el sentimiento es neutral. Si es negativo o positivo, el sentimiento también es negativo o positivo.

1
val overallSentimentScore = results.sentiment.document.score
2
var overallSentiment = "Positive"
3
if(overallSentimentScore < 0.0)
4
    overallSentiment = "Negative"
5
if(overallSentimentScore == 0.0)
6
    overallSentiment = "Neutral"
7
8
var output = "Overall sentiment: ${overallSentiment}\n\n"

A continuación, recorriendo la lista de entidades presentes en el objeto AnalysisResults, podemos procesar cada entidad individualmente. Por defecto, cada entidad tiene un tipo asociado. Por ejemplo, el servicio puede saber si una entidad es una persona, una empresa o un vehículo. Actualmente, puede identificar más de 450 tipos diferentes de entidades.

Dado que lo pedimos, cada entidad tendrá ahora también una puntuación de sentimiento y emociones asociadas a ella.

Podemos determinar la puntuación de sentimiento simplemente utilizando la propiedad sentiment.score. Sin embargo, determinar la emoción asociada a una entidad no es tan sencillo. Watson soporta actualmente cinco emociones: ira, alegría, asco, miedo y tristeza. Cada entidad tendrá las cinco emociones, pero diferentes valores asociados a cada una de ellas, especificando el grado de confianza del servicio en que la emoción es correcta. Por lo tanto, para determinar la emoción correcta, debemos elegir la que tenga el valor más alto.

El siguiente código enumera cada entidad junto con su tipo, puntuación de sentimiento y emoción:

1
for(entity in results.entities) {
2
    output += "${entity.text} (${entity.type})\n"
3
    
4
    val validEmotions = arrayOf("Anger", "Joy", "Disgust",
5
                                "Fear", "Sadness")
6
    val emotionValues = arrayOf(
7
            entity.emotion.anger,
8
            entity.emotion.joy,
9
            entity.emotion.disgust,
10
            entity.emotion.fear,
11
            entity.emotion.sadness
12
    )
13
    val currentEmotion = validEmotions[
14
                            emotionValues.indexOf(
15
                                emotionValues.max()
16
                            )
17
                         ]
18
                        
19
    output += "Emotion: ${currentEmotion}, " +
20
            "Sentiment: ${entity.sentiment.score}" +
21
            "\n\n"
22
}

Para mostrar la salida que hemos generado, podemos volver a actualizar el widget TextView.

1
runOnUiThread {
2
    documentContents.text = output
3
}

Llegados a este punto, puedes volver a ejecutar la aplicación para ver los tres servicios trabajando juntos.

Results of semantic analysisResults of semantic analysisResults of semantic analysis

Conclusión

Ahora ya sabes cómo utilizar tres de los servicios relacionados con el lenguaje natural más utilizados que ofrece Watson. En este tutorial, también has visto lo fácil que es utilizar el SDK Java de Watson para hacer que todos los servicios funcionen juntos para crear una aplicación Android inteligente.

Para saber más sobre los servicios y el SDK, puedes consultar el repositorio GitHub del SDK. Y para aprender más sobre el uso del aprendizaje automático de Watson en tus propias aplicaciones, ¡consulta algunas de nuestras otras publicaciones aquí en Envato Tuts+!