Статьи

Как кодировать обработку естественного языка на Android с помощью IBM Watson

Благодаря растущей волне искусственного интеллекта пользователи в наши дни стали ожидать, что приложения будут умными и осведомленными об условиях, в которых они используются. IBM Watson предлагает множество связанных с естественным языком сервисов, которые вы можете использовать для создания таких приложений.

Например, вы можете использовать его службу Natural Language Understanding для извлечения ключевых слов, сущностей, настроений и множества других семантических деталей из любого текста, который читает пользователь. А если текст написан на иностранном языке, вы можете использовать службу « Переводчик языков», чтобы определить язык и перевести его на тот язык, который понимает пользователь.

В этом уроке я собираюсь познакомить вас с некоторыми из этих сервисов, показывая, как создать приложение, которое может переводить немецкие веб-страницы на английский и извлекать из них чувства, важные сущности и эмоции.

Прежде чем продолжить, я предлагаю вам прочитать следующее вводное руководство по службам IBM Watson:

  • Машинное обучение
    Кодирование приложения для Android с помощью машинного обучения IBM Watson

Сегодня мы будем работать с тремя службами Watson, и каждый из них необходимо активировать отдельно. Поэтому откройте свою панель инструментов IBM Bluemix и нажмите кнопку Создать .

Первый сервис, который мы собираемся активировать, это сервис преобразования документов , который позволяет нам конвертировать документы HTML, PDF и DOCX в обычный текст или JSON. Выберите его из каталога, дайте ему осмысленное имя и нажмите кнопку « Создать» .

Настройка службы преобразования документов

Далее вернитесь в каталог и выберите услугу « Языковой переводчик» . Он поддерживает несколько широко распространенных языков и может по умолчанию обрабатывать текст в трех областях: новости, разговоры и патенты. Хотя первые два домена подходят для большинства текстов, последний домен может быть более точным для текстов, содержащих множество технических или юридических терминов.

На странице конфигурации дайте сервису осмысленное имя и нажмите кнопку « Создать» .

Настройка службы «Переводчик языков»

Вернитесь в каталог и выберите услугу « Понимание естественного языка» . Мы будем использовать этот сервис для извлечения чувств, сущностей и эмоций из неструктурированного текста. Опять же, дайте ему осмысленное имя на экране конфигурации и нажмите кнопку « Создать» .

Настройка службы понимания естественного языка

Если вы откроете панель мониторинга прямо сейчас, вы сможете увидеть что-то вроде этого:

Три активных сервиса в панели инструментов

Все три службы имеют уникальные учетные данные, связанные с ними. Вы должны записать их все, потому что они понадобятся вам позже. Чтобы определить учетные данные какой-либо службы, выберите ее на панели инструментов, откройте вкладку « Учетные данные службы » и нажмите кнопку « Просмотреть учетные данные» .

Чтобы иметь возможность использовать эти три службы в проекте Android Studio, мы должны добавить Watson Java SDK в качестве зависимости implementation в файл build.gradle модуля app .

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

Кроме того, мы будем использовать библиотеку Fuel в качестве HTTP-клиента, поэтому добавим ее также в качестве зависимости implementation .

1
implementation ‘com.github.kittinunf.fuel:fuel-android:1.10.0’

Как Fuel, так и Watson Java SDK могут работать только в том случае, если наше приложение имеет разрешение INTERNET , поэтому запросите его в файле манифеста.

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

Затем добавьте теги <string> содержащие имена пользователей и пароли всех трех служб, в файл strings.xml .

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

И наконец, для краткости нашего кода в этом руководстве мы будем использовать Kotlin вместо Java, поэтому убедитесь, что вы включили поддержку Kotlin.

Мы будем использовать сервис преобразования документов Watson для преобразования веб-страниц HTML в простой текст. Чтобы разрешить пользователю вводить адрес веб-страницы, добавьте виджет EditText в макет своей деятельности. Кроме того, TextView виджет TextView для отображения содержимого веб-страницы в виде простого текста. Чтобы убедиться, что содержимое длинных веб-страниц не усекается, я предлагаю вам поместить их в виджет ScrollView .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
<EditText
    android:layout_width=»match_parent»
    android:layout_height=»wrap_content»
    android:id=»@+id/documentURL»
    android:inputType=»textUri»
    android:hint=»URL»
    android:imeOptions=»actionGo»
    />
 
<ScrollView
    android:layout_width=»wrap_content»
    android:layout_height=»match_parent»
    android:layout_below=»@+id/documentURL»>
    <TextView
        android:layout_width=»match_parent»
        android:layout_height=»match_parent»
        android:id=»@+id/documentContents»
        />
</ScrollView>

В приведенном выше коде вы можете видеть, что атрибут imeOptions виджета EditText установлен в actionGo . Это позволяет пользователям нажимать кнопку «Перейти» на своих виртуальных клавиатурах после завершения ввода адреса. Чтобы прослушать это событие нажатия кнопки, добавьте следующий код Kotlin в метод onCreate() вашей деятельности:

1
2
3
4
5
6
7
8
documentURL.setOnEditorActionListener { _, action, _ ->
    if (action == EditorInfo.IME_ACTION_GO) {
         
        // More code here
                 
    }
    false
}

В прослушивателе событий первое, что нам нужно сделать, это определить URL-адрес, введенный пользователем. Это легко сделать, обратившись к свойству text виджета EditText . Получив URL, мы можем использовать метод httpGet() для загрузки содержимого веб-страницы.

Поскольку мы хотим, чтобы метод httpGet() выполнялся асинхронно, мы должны добавить к нему обратный вызов, используя метод responseString() , который также позволяет обрабатывать загруженное содержимое в виде строки.

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

Теперь пришло время создать экземпляр класса DocumentConversion , который имеет все методы, которые нам нужны для взаимодействия со службой преобразования документов. Его конструктор ожидает дату версии вместе с учетными данными службы.

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

Watson Java SDK не позволяет нам напрямую передавать строки в службу преобразования документов. Вместо этого ему нужны объекты File . Поэтому давайте теперь создадим временный файл, используя метод createTempFile() класса File , и запишем в него содержимое загруженной веб-страницы, используя метод writeText() .

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

На этом этапе мы можем вызвать метод convertDocumentToText() и передать ему временный файл, чтобы начать преобразование. Метод также ожидает MIME-тип временного файла, поэтому не забудьте включить его. После завершения преобразования вы можете отобразить простой текст, просто назначив его text свойству виджета TextView .

Следующий код показывает, как выполнить преобразование в новом потоке и обновить TextView в потоке пользовательского интерфейса:

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

Вы можете запустить приложение сейчас и ввести URL-адрес немецкой веб-страницы, чтобы увидеть, как работает служба преобразования документов.

Немецкая веб-страница преобразована в обычный текст

С сервисом Language Translator мы теперь преобразуем простой текст на немецком языке в английский.

Вместо того, чтобы обновлять наш макет, чтобы позволить пользователю вручную запустить перевод, давайте добавим меню в нашу деятельность. Для этого создайте новый файл ресурсов меню и добавьте в него следующий код:

01
02
03
04
05
06
07
08
09
10
11
12
13
<?xml version=»1.0″ encoding=»utf-8″?>
<menu xmlns:android=»http://schemas.android.com/apk/res/android»
    xmlns:app=»http://schemas.android.com/apk/res-auto»>
 
    <item android:id=»@+id/action_translate»
        android:title=»Translate»
        app:showAsAction = «never» />
 
    <item android:id=»@+id/action_analyze»
        android:title=»Analyze»
        app:showAsAction = «never» />
     
</menu>

Как видите, приведенный выше код создает меню с двумя параметрами: переводить и анализировать. На этом этапе мы будем работать только с первым вариантом.

Чтобы отобразить меню, мы должны надуть его внутри onCreateOptionsMenu() нашей деятельности.

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

Переопределив метод onOptionsItemSelected() , мы можем узнать, когда пользователь использует меню. Кроме того, мы можем определить, какой элемент пользователь нажал, проверив itemId . Следующий код проверяет, выбрал ли пользователь опцию перевода.

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

Как и служба документов, служба языковых переводчиков также имеет специальный класс, который позволяет нам взаимодействовать с ним. Как вы уже догадались, он называется LanguageTranslator . Чтобы создать экземпляр класса, нам нужно передать только конструктор учетные данные службы входа в систему.

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

В классе есть метод translate() который мы теперь можем использовать для перевода нашего немецкого текста на английский. В качестве аргументов он ожидает перевода текста в виде строки, текущего языка текста и желаемого языка.

После успешного завершения перевода у нас будет доступ к объекту TranslationResult , свойство firstTranslation которого содержит переведенный текст.

В следующем коде показано, как выполнить перевод и отобразить результат в виджете TextView .

01
02
03
04
05
06
07
08
09
10
11
12
13
AsyncTask.execute {
    val translatedDocument = translator
                                .translate(
                                    documentContents.text
                                                    .toString(),
                                    Language.GERMAN,
                                    Language.ENGLISH
                                ).execute()
    runOnUiThread {
        documentContents.text = translatedDocument
                                .firstTranslation
    }
}

Теперь вы можете снова запустить приложение, ввести URL-адрес немецкой веб-страницы и использовать меню для перевода его содержимого на английский язык.

Текст на немецком переводится на английский

Наконец, чтобы выполнить семантический анализ переведенного текста и извлечь из него различные важные детали, мы можем использовать класс NaturalLanguageUnderstanding , который служит клиентом для службы понимания естественного языка.

Следующий код показывает, как инициализировать клиент, только когда пользователь нажимает второй вариант меню, которое мы создали на предыдущем шаге:

01
02
03
04
05
06
07
08
09
10
11
if(item?.itemId == R.id.action_analyze) {
    val analyzer = NaturalLanguageUnderstanding(
            NaturalLanguageUnderstanding.VERSION_DATE_2017_02_27,
            resources.getString(
                R.string.natural_language_understanding_username),
            resources.getString(
                R.string.natural_language_understanding_password)
    )
 
    // More code here
}

По сравнению с другими службами, относящимися к естественному языку, использование службы понимания естественного языка несколько сложнее, в первую очередь потому, что она имеет большое количество функций.

А пока, скажем, мы хотим определить общее настроение переведенного текста и извлечь все основные сущности, которые он упоминает. С каждой сущностью могут быть связаны эмоции и чувства, поэтому предположим, что мы тоже хотим извлечь их.

Чтобы сообщить службе, что мы хотим извлечь все сущности и связанные с ними эмоции и чувства, нам нужен объект EntitiesOptions , который можно создать с помощью класса EntitiesOptions.Builder .

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

Точно так же, чтобы сообщить сервису, что мы хотим общего настроения текста, нам нужен объект SentimentOptions .

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

Объекты SentimentOptions и EntitiesOptions теперь необходимо связать вместе, чтобы сформировать объект Features , который можно использовать для создания объекта AnalyzeOptions . Объект AnalyzeOptions является наиболее важным из всех вышеперечисленных объектов, поскольку именно здесь вы указываете текст, который хотите проанализировать.

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

Когда объект AnalyzeOptions готов, мы можем передать ему метод analysis analyze() чтобы начать анализ.

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

Результатом анализа является объект AnalysisResults , содержащий всю запрашиваемую нами информацию.

Чтобы определить общее настроение текста, мы должны сначала извлечь общую оценку настроения, используя свойство sentiment.document.score . Оценка настроения — не что иное, как число с плавающей запятой. Если это ноль, настроение нейтральное. Если оно отрицательное или положительное, то настроение тоже отрицательное или положительное.

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

Затем, просматривая список entities в объекте AnalysisResults , мы можем обрабатывать каждую сущность по отдельности. По умолчанию с каждой сущностью связан тип. Например, служба может сказать, является ли организация лицом, компанией или транспортным средством. В настоящее время он может идентифицировать более 450 различных типов объектов.

Поскольку мы просили их, у каждой сущности теперь также будет оценка настроения и эмоции, связанные с ней.

Мы можем определить оценку настроения, просто используя свойство sentiment.score . Однако определить эмоцию, связанную с сущностью, не так просто. Ватсон в настоящее время поддерживает пять эмоций: гнев, радость, отвращение, страх и грусть. У каждой сущности будут все пять эмоций, но разные ценности связаны с каждой из них, определяя, насколько служба уверена, что эмоция правильная. Поэтому, чтобы определить правильные эмоции, мы должны выбрать ту, которая имеет наибольшее значение.

Следующий код перечисляет каждую сущность вместе с ее типом, оценкой настроения и эмоциями:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
for(entity in results.entities) {
    output += «${entity.text} (${entity.type})\n»
     
    val validEmotions = arrayOf(«Anger», «Joy», «Disgust»,
                                «Fear», «Sadness»)
    val emotionValues = arrayOf(
            entity.emotion.anger,
            entity.emotion.joy,
            entity.emotion.disgust,
            entity.emotion.fear,
            entity.emotion.sadness
    )
    val currentEmotion = validEmotions[
                            emotionValues.indexOf(
                                emotionValues.max()
                            )
                         ]
                         
    output += «Emotion: ${currentEmotion}, » +
            «Sentiment: ${entity.sentiment.score}» +
            «\n\n»
}

Чтобы отобразить сгенерированный нами вывод, мы можем снова обновить виджет TextView .

1
2
3
runOnUiThread {
    documentContents.text = output
}

На этом этапе вы можете снова запустить приложение, чтобы увидеть, как все три службы работают вместе.

Результаты семантического анализа

Теперь вы знаете, как пользоваться тремя наиболее широко используемыми услугами, связанными с естественным языком, которые предлагает Watson. В этом руководстве вы также увидели, как легко использовать Java SDK Watson, чтобы все службы работали вместе для создания умного приложения для Android.

Чтобы узнать больше об услугах и SDK, вы можете обратиться к репозиторию GitHub SDK. А чтобы узнать больше об использовании машинного обучения Watson в своих собственных приложениях, ознакомьтесь с некоторыми другими нашими статьями здесь на Envato Tuts +!

  • Android SDK
    Как использовать Google Cloud Machine Learning Services для Android
  • Android вещи
    Android вещи и машинное обучение
    Пол Требилкокс-Руис
  • Android SDK
    Введение в компоненты архитектуры Android
    Жестяная мегали
  • Машинное обучение
    Кодирование приложения для Android с помощью машинного обучения IBM Watson