Monthly Archives: November 2016

Reconnaissance vocale et analyse de texte dans vos applications Xamarin.iOS

Bien qu’existante depuis très longtemps dans l’univers informatique, la reconnaissance vocale est une fonctionnalité encore peu utilisée au sein des applications (aussi bien sur mobile que sur desktop).

Depuis l’annonce de Siri par Apple en 2011, les assistants personnels ont le vent en poupe et une nouvelle étape a été franchie sur iOS 10 lors de la WWDC 2016 puisque le moteur de reconnaissance vocal utilisé par Siri est désormais accessible aux développeurs.

Voyons comment utiliser celui-ci pour intégrer la reconnaissance vocale au sein d’une application Xamarin.iOS, et comment le coupler à des APIs plus anciennes mais relativement méconnues, pour faire de l’analyse de texte.

Speech Recognition API au service des développeurs

Comme nous l’évoquions précédemment, Apple a rendu disponible dans iOS 10 le moteur qui propulse Siri pour la reconnaissance vocale. Pour cela, une nouvelle classe est à la disposition des développeurs : SFSpeechRecognizer.

Ce qu’il est important de noter pour utiliser cette classe, c’est qu’une connexion réseau est nécessaire car tout ce qui sera enregistré via le microphone, sera envoyé à Apple pour que les serveurs qui propulsent Siri, traitent et analysent les informations avant de vous renvoyer la transcription du texte.

Cette phase de communication avec les serveurs Apple a lieu tout au long du processus de reconnaissance vocale (vous récupérez la transcription au fil de l’eau), ce qui fait que si vous utilisez la fonctionnalité trop souvent, cela a tendance à consommer beaucoup de données (sur votre forfait 3G/4G) et d’énergie sur la batterie de votre téléphone/tablette.

C’est pourquoi Apple impose certaines limitations (ex: 1mn maximum pour chaque enregistrement audio) et a également mis en place des quotas d’utilisation permettant à une application d’effectuer un nombre limité de requêtes par jour (un message d’erreur est renvoyé si le quota est dépassé).

Comme pour tout ce qui touche à la vie privée chez Apple, avant de pouvoir utiliser cette API, il est nécessaire de demander l’autorisation à l’utilisateur et de réagir en conséquence selon la réponse qu’il donnera :

SFSpeechRecognizer.RequestAuthorization((SFSpeechRecognizerAuthorizationStatus status) =>
{
  switch (status)
  {
    case SFSpeechRecognizerAuthorizationStatus.Authorized:
      break;
    case SFSpeechRecognizerAuthorizationStatus.Denied:
      break;
    case SFSpeechRecognizerAuthorizationStatus.NotDetermined:
      break;
    case SFSpeechRecognizerAuthorizationStatus.Restricted:
      break;
  }
});

Pour que tout ceci fonctionne sans soucis, il est également nécessaire d’ajouter 2 lignes dans le fichier Info.plist, afin d’indiquer à l’application que vous avez besoin d’utiliser la reconnaissance vocale et le microphone.

Speech Recognition - Info.plist

Maintenant que les bases sont posées pour pouvoir utiliser les composants nécessaires, il ne reste plus qu’à écrire le code. Celui-ci repose sur 3 classes importantes :

  • AVAudioEngine ==> Le moteur audio permettant d’enregistrer ce que l’utilisateur dira via le microphone de son téléphone ou de sa tablette
  • SFSpeechRecognizer ==> Le moteur de reconnaissance vocal qui communiquera avec les serveurs Apple
  • SFSpeechAudioBufferRecognitionRequest ==> Le buffer audio qui servira à stocker les données renvoyées par le moteur audio avant d’être envoyées chez Apple pour analyse

Un exemple de code étant toujours plus parlant qu’un long discours, voici ci-dessous le code associé à notre application de démonstration, qui initie l’enregistrement quand l’utilisateur tape sur un bouton, et met fin à l’enregistrement quand l’utilisateur relâche ce même bouton.

private AVAudioEngine AudioEngine;
private SFSpeechRecognizer SpeechRecognizer;
private SFSpeechAudioBufferRecognitionRequest LiveSpeechRequest;
private SFSpeechRecognitionTask RecognitionTask;
private string sentence;

partial void StartSpeechRecognition(UIButton sender)
{
  SpeechRecognizer = new SFSpeechRecognizer(NSLocale.FromLocaleIdentifier("fr"));
  if (!SpeechRecognizer.Available)
    return;

  AudioEngine = new AVAudioEngine();
  if (AudioEngine.InputNode == null)
    return;

  NSError audioError;
  LiveSpeechRequest = new SFSpeechAudioBufferRecognitionRequest();

  var recordingFormat = AudioEngine.InputNode.GetBusOutputFormat(0);
  AudioEngine.InputNode.InstallTapOnBus(0, 1024, recordingFormat, (AVAudioPcmBuffer buffer, AVAudioTime when) => LiveSpeechRequest.Append(buffer));
  AudioEngine.Prepare();
  AudioEngine.StartAndReturnError(out audioError);
  if (audioError != null)
    return;

  RecognitionTask = SpeechRecognizer.GetRecognitionTask(LiveSpeechRequest, (SFSpeechRecognitionResult result, NSError taskError) =>
  {
    if (taskError != null)
      return;

    if (!result.Final)
    {
      SpeechLabel.Text = result.BestTranscription.FormattedString;
      return;
    }
 
    sentence = result.BestTranscription.FormattedString;
    ParseSpeechRecognition();
  });
}

partial void StopSpeechRecognition(UIButton sender)
{
  AudioEngine?.Stop();
  AudioEngine?.InputNode?.RemoveTapOnBus(0);
  LiveSpeechRequest?.EndAudio();
}

Outre l’initialisation du moteur audio via la définition de paramètres sur la source d’enregistrement (AudioEngine.InputNode), la partie importante repose sur la fonction de “callback” passée à la méthode GetRecognitionTask, qui permet de récupérer la transcription du texte (result.BestTranscription.FormattedString) au fil de l’eau, et de savoir si la reconnaissance est terminée ou non (result.Final).

Une fois tous les éléments assemblés et en réalisant un peu de cosmétique autour de l’interface graphique, cela donne le résultat visible dans la vidéo ci-dessous :

Comme on peut le voir dans cette vidéo, avec juste quelques lignes de code, il est possible d’intégrer la reconnaissance vocale dans une application. Mais hormis si l’on souhaite juste permettre à l’utilisateur de remplir des zones de texte sans rien saisir au clavier, il serait plus intéressant de pouvoir analyser ce texte.

NSLinguisticTagger : Une classe méconnue mais très pratique

C’est à ce moment qu’une classe relativement méconnue surgit, bien qu’elle existe dans iOS depuis la version 5.0. Cette classe porte un nom relativement explicite : NSLinguisticTagger.

Celle classe est capable, à partir d’un texte plus ou moins long, de “tagger” celui-ci, c’est-à-dire d’identifier tous ses composants (noms, pronoms, verbes, adverbes, déterminants, etc…) mais également d’identifier certains éléments marquants (noms de lieux, noms de sociétés…). Malheureusement cette dernière possibilité ne fonctionne qu’en Anglais.

Différentes langues sont supportées par NSLinguisticTagger (Anglais, Français, Allemand…) afin de couvrir les langues les plus fréquemment rencontrées sur iOS.

Comment fonctionne tout ceci ? C’est relativement simple comme on peut le voir dans l’exemple de code ci-dessous :

private void ParseSpeechRecognition()
{
  var options = NSLinguisticTaggerOptions.OmitWhitespace | NSLinguisticTaggerOptions.OmitPunctuation | NSLinguisticTaggerOptions.JoinNames;
  var schemes = NSLinguisticTagger.GetAvailableTagSchemesForLanguage("fr");
  var tagger = new NSLinguisticTagger(schemes, options);
  tagger.AnalysisString = sentence;
  tagger.EnumerateTagsInRange(new NSRange(0, sentence.Length), NSLinguisticTag.SchemeLexicalClass, options, HandleNSLingusticEnumerator);
}

private void HandleNSLingusticEnumerator(NSString tag, NSRange tokenRange, NSRange sentenceRange, ref bool stop)
{
  var token = sentence.Substring((int)tokenRange.Location, (int)tokenRange.Length);
  Console.WriteLine($"{token} = {tag}");
}

A partir d’une liste d’options (ignorer les espaces, ignorer la ponctuation, etc…) et d’une liste de types d’éléments analysables pour une langue (appelées “schemes”), on énumère les “tags” contenus dans le texte à analyser (AnalysisString).

La méthode permettant d’énumérer les tags du texte, prend en paramètre une fonction de “callback” qui sera appelée pour chaque tag identifié.

Si on exécute le code précédent avec la phrase utilisée dans la vidéo de notre application de démonstration, nous obtenons le résultat ci-dessus dans la fenêtre de sortie de Xamarin Studio.

2016-11-27 09:02:57.714 SpeechDemo[919:415497] C' = Pronoun
2016-11-27 09:02:57.714 SpeechDemo[919:415497] est = Verb
2016-11-27 09:02:57.714 SpeechDemo[919:415497] bientôt = Adverb
2016-11-27 09:02:57.715 SpeechDemo[919:415497] Noël = Noun
2016-11-27 09:02:57.715 SpeechDemo[919:415497] j' = Pronoun
2016-11-27 09:02:57.715 SpeechDemo[919:415497] espère = Verb
2016-11-27 09:02:57.716 SpeechDemo[919:415497] que = Conjunction
2016-11-27 09:02:57.716 SpeechDemo[919:415497] vous = Pronoun
2016-11-27 09:02:57.716 SpeechDemo[919:415497] passerez = Verb
2016-11-27 09:02:57.716 SpeechDemo[919:415497] tous = Adjective
2016-11-27 09:02:57.717 SpeechDemo[919:415497] de = Preposition
2016-11-27 09:02:57.717 SpeechDemo[919:415497] bonnes = Adjective
2016-11-27 09:02:57.717 SpeechDemo[919:415497] fêtes = Noun

Comme on le voit ci-dessus, à partir des informations renvoyées par NSLinguisticTagger, il est possible d’envisager les traitements nécessaires à la logique propre à une application (ex: extraire les noms et rechercher des valeurs particulières pour filtrer des résultats).

Pour aller encore plus loin…

Comme nous l’avons vu dans cet article, en couplant la reconnaissance vocale, désormais disponible dans iOS 10, avec un outil de taggage comme NSLinguisticTagger, de nouveaux horizons s’ouvrent pour intégrer des fonctionnalités inédites au sein des applications mobiles.

Toutefois si vous voulez aller encore plus, il existe d’autres éléments qu’il est toujours bon de connaitre, à commencer par la catégorisation de contenus.

Si vous réalisez des applications pour OSX, Apple a mis à disposition un framework appelé Latent Semantic Mapping (LSM) qui permet de catégoriser un contenu (ex: une page web, un fichier…) à partir d’une base d’autres contenus. Une session dédiée à ce framework avait été présentée lors de la WWDC 2011 si vous souhaitez en savoir plus à ce sujet.

Si vous voulez faire une analyse plus poussée du texte renvoyé par la reconnaissance vocale (ex: extraire des entités, identifier le sujet de fond d’un document, etc…) il existe différentes APIs basées sur le Machine Learning qui satisferont tous vos désirs.

On pourra citer comme exemple un projet sur lequel mise beaucoup Microsoft, à savoir Cognitive Services et plus particulièrement la brique Language Understanding Intelligent Service (LUIS). Des alternatives telles que TextRazor méritent également le coup d’oeil.

Akavache : Stockage de données en cache dans vos applications Xamarin

Afin de palier aux problèmes de connectivité inhérents à toutes applications mobiles, le stockage de données sur le périphérique de l’utilisateur est une quasi nécessité, sauf si l’on accepte que l’utilisateur ne puisse utiliser l’application sans connexion internet (parti pris de nombreuses personnes).

La solution la plus souvent utilisée dans le monde des applications mobiles repose sur SQLite, qui a l’avantage d’être à la fois léger, performant et d’utiliser les standards du marché puisqu’il s’agit d’un moteur de base de données SQL classique.

Ce type de base de données a toutefois quelques inconvénients dont le plus important concerne la difficulté de modifier le schéma de la base lors d’une montée de version de l’application.

Afin de palier à ces problèmes, différentes alternatives existent (stockage clé/valeur, base NoSQL, etc…) et nous allons aborder l’une de celles-ci nommée Akavache.

Présentation de ce qu’est / ce que n’est pas Akavache

AkavacheComme sa description l’indique sur son repository Github (https://github.com/akavache/Akavache) vu qu’Akavache est un projet open-source, il s’agit d’un système de stockage de données asynchrone de type clé/valeur à destination des applications native (iOS, Android, UWP, WPF, OSX…) .

Un système de stockage clé/valeur ne fonctionne pas comme une base de données, ce qui sous-entend que pour pouvoir récupérer une donnée, il est nécessaire de connaitre la clé utilisée par celle-ci, ou bien de faire une lecture séquentielle des données pour retrouver la valeur souhaitée.

Les valeurs stockées par Akavache sont gérées sous forme de tableau d’octets, quels que soient leur forme originale (document JSON, image, chaine de caractères…). Il est possible de définir pour chacune des clés stockées, une date d’expiration du contenu au delà de laquelle les données seront automatiquement supprimées.

Sous le capot, Akavache utilise une base de données SQLite pour stocker les données. Cette base de données ne contient qu’une seule table servant à stocker les clés, valeurs et quelques autres éléments (type de données, date d’expiration…).

Pour être clair, Akavache n’est pas là pour concurrencer les bases de données comme SQLite, mais pour offrir une alternative sur une utilisation très ciblée qu’est la mise en cache de données.

Installation au sein d’une application Xamarin

Pour utiliser Akavache au sein d’une application Xamarin, rien de plus simple car cette solution se présente sous la forme de paquets NuGet, qu’il est possible de trouver assez facilement via la boite de dialogue que vous pouvez voir ci-dessous.

Akavache - NuGet Packages

On peut voir ci-dessous que la dernière version publiée (5.0.0) a été finalisée il y a seulement quelques jours (le 4 Novembre 2016).

Utilisation des APIs proposées par la bibliothèque

Comme pour toute application .NET, tout commence par référencer l’espace de noms :

using Akavache;

Une fois cette opération effectuée, tout se passe via l’utilisation de la classe nommée BlobCache, sur laquelle il est nécessaire de définir le nom de l’application (utilisé pour créé la base de données SQLite).

BlobCache.ApplicationName = "AkavacheDemo";

Il ne reste plus ensuite qu’à insérer et récupérer des données. Pour cela il existe différentes possibilités :

var userAccount = new UserAccount
{
  Id = 12345,
  FirstName = "Stephane",
  LastName = "Cordonnier"
};

BlobCache.InMemory.InsertObject("userAccount", userAccount, TimeSpan.FromDays(31));
BlobCache.LocalMachine.InsertObject("userAccount", userAccount, TimeSpan.FromDays(31));
BlobCache.Secure.InsertObject("userAccount", userAccount, TimeSpan.FromDays(31));
BlobCache.UserAccount.InsertObject("userAccount", userAccount, TimeSpan.FromDays(31));

BlobCache.LocalMachine.GetObject<UserAccount>("userAccount").Subscribe(u =>
{
  Console.WriteLine($"{u.Id} - {u.FirstName} - {u.LastName}");
});

BlobCache.LocalMachine.GetAllObjects<UserAccount>().Subscribe(userAccounts =>
{
  foreach (var u in userAccounts)
    Console.WriteLine($"{u.Id} - {u.FirstName} - {u.LastName}");
});

La classe UserAccount est un POCO, initialisé et stocké dans différents conteneurs mis à disposition par Akavache. Ces conteneurs sont au nombre de quatre :

  • InMemory => Stockage non persistent (données perdues au redémarrage de l’application)
  • LocalMachine => Stockage persistent dans une base SQLite
  • Secure => Stockage persistent dans une base SQLite avec cryptage des données
  • UserAccount => Stockage persistent dans une base SQLite. La base de données est stockée dans un répertoire qui est automatiquement sauvegardé par le mécanisme standard de la plateforme (ex : synchronisation iTunes / iCloud sur iOS)

Pour récupérer les données, il suffit d’interroger le même conteneur que celui utilisé pour le stockage. Si la clé de l’objet recherché est connue, la méthode GetObject<T>(…) est disponible. Pour récupérer tous les objets en se basant sur leur type, il suffit d’utiliser la méthode GetAllObjects<T>().

La suppression de données se fait de la même manière en appelant les méthodes InvalidateObject, InvalidateObjects ou InvalidateAllObjects.

Téléchargement et mise en cache de données

Outre la mise en cache de données réalisée par programmation, Akavache propose des fonctionnalités de téléchargement et de mise en cache de données directement depuis Internet.

BlobCache.UserAccount.DownloadUrl(...);
BlobCache.UserAccount.LoadImageFromUrl(...);

Ces fonctionnalités sont très pratiques pour la mise en cache de données provenant par exemple de webservices renvoyant des flux JSON. En revanche pour ce qui est de la mise en cache d’images, il faut garder à l’esprit que les données sont stockées en arrière plan dans une base SQLite, et il n’est pas forcément recommandé de stocker de gros flux binaires (ex: des images HD) dans ce type de base de données.

Conclusion

Si vous cherchez une solution rapide à mettre en oeuvre pour faire de la mise en cache simple de données, Akavache est une solution efficace et performante qui mérite que l’on s’attarde dessus.

En revanche si vous cherchez une solution puissante de stockage et d’interrogation comme peut l’être une base de données SQLite, mais sans les inconvénients de celle-ci (ex: gestion du schéma de la base), alors Akavache n’est clairement pas la solution adaptée.

Il faudra alors plutôt se diriger vers d’autres solutions telles que les bases NoSQL, ce qui fera certainement l’objet d’un futur article sur ce blog.