
Если вы хотите понять, что такое нейронная сеть и как она работает, лучше сначала заглянуть сюда. Эта статья — о практическом применении нейросетей на iOS. В частности — о распознавании речи и определении её характеристик. С примерами кода покажем, как это реализовали в одном из наших проектов. Начнём с фреймворка, который будем использовать. Затем объясним, как создать модель и обучить её на данных из приложения, а также как анализировать результаты.
Какой фреймворк использовать?
CML (Core Machine Learning) — это фреймворк от Apple, позволяющий добавлять в приложения для iOS возможности машинного обучения. Он появился в 2016 году как расширение наработок по работе с матрицами и векторной алгеброй (объединённых в фреймворк Accelerate) и вычислениям на графической технологии Metal — основных инструментах для нейросетей.

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

Благодаря интеграции готовой натренированной модели в CoreML получается мощный и гибкий инструмент для работы с нейронными сетями. В него можно импортировать практически все популярные нейросети:
- BERT, GPT — для задач с естественным языком (на котором мы говорим каждый день),
- нейронные сети для задач классификации изображений и других подобных задач
Ограничение одно: число компонент тензора должно быть <= 5. То есть, не больше пяти измерений.
Стоит уточнить, что такое модель нейронной сети. Это результат обучения нейронной сети — взвешенный граф с наилучшей комбинацией весов. У него есть вход, а на выходе — какой-то результат.
Как на iOS определить токсичность фразы в реальном времени?
Алгоритм, описанный ниже, можно использовать для анализа общей характеристики речи. Рассмотрим на конкретном примере — «токсичности».
Итак, чтобы определить токсичность фразы, нужно разбить задачу на несколько этапов:
- Подготовить тренировочные данные с токсичными и нетоксичными фразами;
- Получить модель нейронной сети, обученную на наборе данных;
- Записать фразу;
- Отправить фразу в библиотеку SFSpeechRecognition для анализа голоса и получить её в текстовом виде;
- Отправить текст обученной модели на классификацию и получить результат.
Если описать это с помощью диаграммы, то задача выглядит так:

Фаза 1: подготовка данных для обучения модели классификации текста
Чтобы получить обученную модель, можно выбрать один из двух путей:
- Разработать нейронную сеть самостоятельно и обучить с её помощью модель;
- Взять готовую модель и нейронную сеть, дообучить её на своих данных и использовать, например, Python.
Чтобы упростить процесс, выберем второй путь. У Apple есть отличный набор инструментов, поэтому начиная с Xcode 13 отладка моделей стала максимально простой.
Для начала запустим утилиту CreateML — она уже входит в Xcode — и создадим новый проект. Выбираем TextClassification (Apple использует для этого нейросеть BERT) и создаём проект. Откроется окно, в которое можно загрузить подготовленные данные.
Утилита принимает на вход два набора данных:
- набор, на котором модель будет дообучаться;
- набор, с которым будут сравниваться полученные результаты.
Формат данных должен быть JSON или CSV. Структура набора данных должна соответствовать шаблону:
Для json:
[
{
"text": "The movie was fantastic!",
"label": "positive"
}, {
"text": "Very boring. Fell asleep.",
"label": "negative"
}, {
"text": "It was just OK.",
"label": "neutral"
} ...
]
Для csv:
text,label
"The movie was fantastic!",positive
"Very boring. Fell asleep.",negative
"It was just OK.",neutral
Данные подготовлены — можно загружать и начинать обучение модели.

Как понять, что всё готово и работает?
Оценить полученные результаты довольно просто. Для каждого проекта обучения есть такие сводки.

Precision — показывает, насколько хорошо модель находит нужную цель (в данном случае — фразу, которую нужно охарактеризовать), не выдавая при этом лишние результаты.
Recall — показывает, насколько хорошо модель находит нужную цель.
F1 score — метрика, которая объединяет точность и полноту алгоритма. Её рассчитывают по формуле:

Чем выше Precision и Recall, тем лучше. Однако на практике достичь максимума по обоим показателям одновременно невозможно.
Остается только экспортировать полученную модель в формате *.mlmodel.
Фаза 2: получение аудиосигнала и отправка на распознавание речи
Speech framework переводит речь в текст на iOS. В нём уже есть обученная модель. Так как наша задача — распознавать речь в реальном времени, первым делом нужно получать аудиоданные в виде образцов AVAudioPCMBuffer и передавать их распознавателю.
class AudioRecordService {
private var audioEngine: AVAudioEngine?
func start() {
do {
audioEngine = try configureAudioEngine()
} catch {
audioRecordingEvents.onNext(.error(.startingAudioEngineError))
}
}
private func configureAudioEngine() throws -> AVAudioEngine {
let audioEngine = AVAudioEngine()
let inputNode = audioEngine.inputNode
let recordingFormat = inputNode.outputFormat(forBus: 0)
inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { [weak self] buffer, _ in
self?.audioRecordingEvents.onNext(.audioBuffer(buffer))
}
audioEngine.prepare()
try audioEngine.start()
return audioEngine
}
}
Устанавливаем ответвление на нулевую шину — образцы начнут поступать, как только количество аудиокадров достигнет 1024. Кстати, объект AVAudioNode может иметь несколько входных и выходных шин.
Полученный буфер нужно отправить на распознавание речи:
- Создадим перечисление для обработки ошибок
enum SpeechReconitionError {
case nativeError(String)
case creatingTaskError
}
- Создадим перечисление для событий распознавания
enum SpeechReconitionEvents {
case phrase(result: String, isFinal: Bool)
case error(SpeechReconitionError)
}
- Создадим объект SFSpeechRecognizer
private var request: SFSpeechAudioBufferRecognitionRequest?
private var reconitionTask: SFSpeechRecognitionTask?
private let recognizer: SFSpeechRecognizer?
init() {
recognizer = SFSpeechRecognizer(locale: Locale.preferredLanguages[0])
}
- Конфигурируем recognizer и запускаем задачу распознавания
func configureRecognition() {
request = SFSpeechAudioBufferRecognitionRequest()
if #available(iOS 16.0, *) {
request?.addsPunctuation = true
}
if let supports = recognizer?.supportsOnDeviceRecognition, supports {
request?.requiresOnDeviceRecognition = true
}
request?.shouldReportPartialResults = true
guard let request else {
stopRecognition()
events.onNext(.error(.creatingTaskError))
return
}
reconitionTask = recognizer?.recognitionTask(with: request, resultHandler: recognitionTaskHandler(result:error:))
}
- Функция для добавления аудиобуферов в очередь распознавания
func transcribeFromBuffer(buffer: AVAudioPCMBuffer) {
request?.append(buffer)
}
- Конфигурируем обработчик результатов
private func recognitionTaskHandler(result: SFSpeechRecognitionResult?, error: Error?) {
if let result = result {
events.onNext(.phrase(result: result.bestTranscription.formattedString, isFinal: result.isFinal))
if result.isFinal {
eraseRecognition()
}
}
if let error {
events.onNext(.error(.nativeError(error.localizedDescription)))
return
}
}
private func eraseRecognition() {
reconitionTask?.cancel()
request = nil
reconitionTask = nil
}
Процесс распознавания запустится сразу же после configureRecognition(). Дальше передаем полученные аудиобуферы в метод transcribeFromBuffer(buffer: AVAudioPCMBuffer).
Процесс распознавания требует времени — примерно 0,5-1 сек. Поэтому результат приходит асинхронно в функцию ecognitionTaskHandler(result: SFSpeechRecognitionResult?, error: Error?). SFSpeechRecognitionResult и содержит результаты распознавания последнего аудиобуфера, а также результаты всех предыдущих распознаваний! То есть на экране пользователь видит последнее распознанное предложение и все, что было распознано раньше.
Также распознавание не всегда происходит непосредственно на устройстве. Когда оффлайн распознавание недоступно, образцы AVAudioPCMBuffer отправляются на сервера Apple и процесс происходит там. Для проверки и принудительного использования оффлайн режима используем команду:
if let supports = recognizer?.supportsOnDeviceRecognition, supports {
request?.requiresOnDeviceRecognition = true
}
Apple утверждает, что результаты на устройстве хуже, чем в онлайн-режиме. Но для онлайн-использования есть ограничения.

Фаза 3: классификация речи
Важно: главное правило использования нейронных сетей для классификации речи — чем больше контекста, тем выше точность.
Сначала импортируем ML-модель в проект как обычный файл. Затем создаём экземпляр класса модели. Имя файла будет совпадать с названием класса.
init?() {
do {
let config = MLModelConfiguration()
config.computeUnits = .all
if #available(iOS 16, *) {
config.computeUnits = .cpuAndNeuralEngine
}
mlModel = try ToxicTextClassificatorConditionalAlgoritm(configuration: MLModelConfiguration()).model
if let mlModel {
predicator = try NLModel(mlModel: mlModel)
}
} catch {
print("Can not initilaize ToxicTextClassificatorConditionalAlgoritm")
return nil
}
}
NLModel — это объект, с которым придётся работать дальше.
После создания модель готова принимать текст для классификации.
Сделаем перечисление возможных исходов классификации.
enum PredictResult: String {
case toxic
case positive
}
Теперь попробуем получить результат!
func predictResult(phrase: String) -> PredictResult? {
guard let predict = predicator?.predictedLabel(for: phrase),
let result = PredictResult(rawValue: predict) else { return nil }
return result
}
Мы анализируем фразу в реальном времени. Это значит, что фрагменты, полученные на втором этапе, сразу поступают на классификацию. Из-за этого неизбежно снижается точность.
Как повысить точность полученных результатов?
а) Если пунктуации нет, классифицируем текст в том виде, в котором он поступил после распознавания, и сохраняем результат. Для этого создадим функцию, которая будет принимать распознанный текст и флаг, указывающий, что распознавание речи завершено.
Reminder: фраза будет приходить каждый раз больше на N слов, потому что SFSpeechRecognitionResult возвращает результаты распознавания последнего аудиобуфера и результаты всех предыдущих распознаваний.
func analyze(phrase: String, isFinalResult: Bool) {
guard let predict = predictResult(phrase: phrase) else {
if isFinalResult, let result = predictResult {
event.onNext(.finalResult(result))
}
return
}
predictResult = predict
}
б) Если пунктуации нет*, но нужно снизить накладные расходы на классификацию, берём только последние N слов из предложения. Однако это сильно снизит точность результатов.
*Чтобы добавить функцию расстановки пунктуации (пока доступна только на английском):
if #available(iOS 16.0, *) {
request?.addsPunctuation = true
}
Повысить точность и снизить накладные расходы на вычисления можно с помощью алгоритма разделения текста на предложения в заданной пропорции. Например, если в тексте три предложения, можно делить их в соотношении 2:1 или 1:2 — то есть сначала анализировать первые два, а потом одно, или наоборот.

Важно: Обязательно запросите разрешения на использование микрофона и анализ речи.
Альтернативные способы получить MLModel
Набор утилит для Python CoreML tools, который позволяет конвертировать модель, обученную с помощью других нейронных сетей, в формат mlmodel:
- CoreMl tools for TensorFlow
- CoreMl tools for PyTorch
TensorFlow Lite для iOS. Он позволяет работать с моделями, натренированными с помощью TensorFlow.
Мы с радостью поможем внедрить функцию распознавания тона речи в ваше приложение или разработать его целиком. Свяжитесь с нами — обсудим проект, дадим примерную оценку стоимости и сроков разработки, проведем первичную аналитику бесплатно.
