Blog de Hinault Romaric (.NET Core, ASP.NET Core, Azure, DevOps)
par , 28/02/2020 à 11h00 (836 Affichages)
Dans la première partie de ce billet de blog, nous avons créer l'application d'exemple et apporter les modifications nécessaires à cette dernière pour rechercher des tweets sur Twitter en utilisant l'API TwwtinviAPI.
Dans ce second billet, nous allons mettre à jour notre application afin qu'elle puisse utiliser Azure Text Analytics API pour analyser les tweets et détecter les sentiments exprimés par ces derniers.
C’est quoi Azure Text Analytics ?
Text Analytics est un service cognitif de traitement en langage naturel de texte brut. Ce service inclut la détection de la langue de l’utilisateur, la détection des mots clés, la détection d’entités et l’analyse des sentiments.
Après analyse d’un texte, Text Analytics détermine la langue dans laquelle le texte est écrit et attribut un score à ce dernier, compris entre 0 et 1. Plus le score est proche de 1, plus la réponse retournée par le service est précise. Text Analytics supporte près de 120 langues.
Text Analytics est capable d’analyser le texte et ressortir le sentiment exprimé par l’auteur du texte. La valeur est comprise entre 0 et 1. Un score proche de 1 est un sentiment position, tandis qu’un score proche de 0 est un sentiment négatif. Cette fonctionnalité est pratique pour analyser les retours des utilisateurs sur un produit donné.
Pour utiliser Text Analytics, vous devez disposer d’un compte Microsoft Azure. Vous pouvez en créer un gratuitement si vous n’en disposez pas d’un.
Création du service Azure Text Analytics
La première chose à faire sera de créer le service Azure Text Analytics en utilisant le portail Azure (https://portal.azure.com/).
Cliquez sur “Créer une ressource”, dans la zone de saisie de la fenêtre qui va s’afficher, saisissez “Text Analytics”, puis sélectionnez ce qui sera affiché dans la liste déroulante :
Cliquez sur Créer.
Dans la fenêtre qui va s’afficher, renseigner les informations sur votre service (nom, emplacement, groupe de ressources et pricing). Prenez F0 pour le pricing. Ce dernier est gratuit. La tarification est fonction de l’utilisation. La tarification gratuite permet un maximum de 5 000 appels au service sur 30 jours.
Une fois les informations renseignées, cliquez sur Créer.
Le service sera créé et déployé dans le groupe de ressources correspondant. Un message de confirmation sera affiché dans le portail.
Dans la vue d’ensemble du service, veuillez noter le endpoint et les clés d’utilisation mis à votre disposition.
Ajout du package Text Analytics API
Revenez à votre application dans Visual Studio. Vous allez utiliser le gestionnaire Nuget pour installer le package Microsoft.Azure.CognitiveServices.Language.Text :
Ensuite, vous devez modifier le fichier de configuration appsettings.json, pour y ajouter les informations de connexion à votre service TextAnalytics :
1 2 3 4 5
| "Textanalytics": {
"SubscriptionKey": "f158c05875xxxxxxxxxxxxx",
"EndPoint": "https://canadacentral.api.cognitive.microsoft.com"
} |
Subscriptionkey doit contenir la clé de souscription au service obtenu depuis le portail Azure.
Endpoint doit contenir l’adresse du service, sans le numéro de version : https://[region].api.cognitive.microsoft.com.
Le fichier AppSettings au complet devrait ressembler à ce qui suit :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| {
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"Twitter": {
"ConsumerKey": "6vm8vRNxxxxxxxxx",
"ConsumerSecret": "PgXYSeJAL4xOgxxxxxxxxxxxxxxxxxxx",
"AccesToken": "143790187-cEFwappMEH3fJ4Hxxxxxxxxxxxxxxxxxxx",
"AccesTokenSecret": "RIwUSDOqq7U1IkONVNFCSBxxxxxxxxxxxxxxxxxxxx"
},
"Textanalytics": {
"SubscriptionKey": "f158c058756e4xxxxxxxxxxxxxxx",
"EndPoint": "https://canadacentral.api.cognitive.microsoft.com"
},
"AllowedHosts": "*"
} |
Accès au service
Dans le dossier Services, vous allez ajouter l’interface ITextAnalyticsService avec le code suivant :
1 2 3 4 5 6
| public interface ITextAnalyticsService
{
Task<IList<ResultModel>> Sentiment(IList<ResultModel> result);
Task<IList<ResultModel>> Language(IList<string> tweett);
} |
En effet, nous allons implémenter deux méthodes. La première pour la détection de la langue de l’utilisateur et la seconde pour détecter les sentiments. À ce stade, vous devez créer une classe TextAnalycticsService qui implémente l’interface ItextAnalyticsService :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| /// <summary>
/// Service pour l'analyse sentimentale
/// </summary>
public class TextAnalyticsService : ITextAnalyticsService
{
/// <summary>
/// Journalisation
/// </summary>
private readonly ILogger<TextAnalyticsService> _logger;
/// <summary>
/// Client pour l'accès à l'API TextAnalytics
/// </summary>
private readonly ITextAnalyticsClient _client;
/// <summary>
///
/// </summary>
/// <param name="logger">Requis pour la journalisation</param>
/// <param name="client">Requis pour le client TexteAnalytics</param>
public TextAnalyticsService(ILogger<TextAnalyticsService> logger, ITextAnalyticsClient client)
{
_logger = logger;
_client = client;
}
public Task<IList<ResultModel>> Language(IList<string> tweett)
{
throw new NotImplementedException();
}
public Task<IList<ResultModel>> Sentiment(IList<ResultModel> result)
{
throw new NotImplementedException();
}
} |
Le constructeur de cette classe doit prendre en paramètre les objets de type ILogger, ITextAnalyticsClient. Les instances correspondantes seront fournies par le conteneur d’injection de dépendance de ASP.NET Core.
Passons à l’implémentation des méthodes pour la détection de la langue et l’analyse sentimentale.
Détection de la langue :
La méthode DetectLanguageAsync du Client TextAnalytics API est utilisée pour effectuer la détection de la langue. Elle prend en paramètre un objet de type LanguageBatchInput. Cet objet est initialisé en passant en paramètre une liste de LanguageInput.
Le LanguageInput est une entité ayant une propriété Text, qui est le texte à analyser et une propriété Id qui est l’identifiant attribué au texte.
Nous devons mettre en place le code permettant d’initialiser le LanguageBatchInput en utilisant la liste des tweets :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public async Task<IList<ResultModel>> Language(IList<string> tweets)
{
//Journalisation
_logger.LogInformation("TextAnalyticsService.Language called");
//Initialisation d'une liste de LanguageInput pour la requête
var languagesInput = new List<LanguageInput>();
//Création du document qui sera analysé. Ajout des tweets au document en attribuant à chacun des identifiant.
int i = 1;
foreach (string tweet in tweets)
{
languagesInput.Add(new LanguageInput(id:i.ToString(), text:tweet));
i++;
}
//..
} |
Ensuite, vous devez appeler _client.DetectLanguageAsync pour exécuter votre requête. Cette fonction va retourner un objet de type LanguageBatchResult contenant une propriété Documents représentant les résultats pour chaque texte de la requête. Vous devez itérer sur cette liste. Chaque document est une entité de type DetectedLanguage qui dispose des propriétés Name pour la langue détectée, Iso6391Name pour le format Iso de la langue détectée et le Score qui représente le dégrée de confiance que le service attribut au résultat qu’il retourne. Plus ce score est proche de 0, plus le risque d’erreur dans l’information retournée est considérable.
Le code pour cette partie est le suivant :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| //Passage du document à l'API pour la détection de la langue
var langResults = await _client.DetectLanguageAsync(false, new LanguageBatchInput(languagesInput));
//Initialisation de l'objet de retour
var resultsModel = new List<ResultModel>();
//AJout des résultats de la détection de la langue au resulatModels
foreach(var document in langResults.Documents)
{
resultsModel.Add(new ResultModel { Language = document.DetectedLanguages[0].Name,
LanguageIso = document.DetectedLanguages[0].Iso6391Name, Id = document.Id,
Text = tweets.ElementAt(int.Parse(document.Id) - 1) });
_logger.LogInformation($"Document ID: {document.Id} , Language: {document.DetectedLanguages[0].Name}");
}
return resultsModel; |
Analyse sentimentale
La méthode SentimentAsync du Client TextAnalytics API est utilisée pour effectuer l’analyse sentimentale. Elle prend en paramètre un objet de type MultiLanguageBatchInput. Cet objet est initialisé en passant en paramètre une liste de MultiLanguageInput.
Le MultiLanguageInput est une entité ayant une propriété Text, qui est le texte à analyser, une propriété Language qui est la langue du texte au format ISO et une propriété Id qui est l’identifiant attribué au texte.
Nous devons mettre en place le code permettant d’initialiser le MultiLanguageBatchInput :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public async Task<IList<ResultModel>> Sentiment(IList<ResultModel> result)
{
//Journalisation
_logger.LogInformation("TextAnalyticsService.Sentiment called");
//Initialisation d'une liste de MultiLanguageInput pour la requete
var multilanguagesInput = new List<MultiLanguageInput>();
//Création du document qui sera analysé. Ajout du texte, de la langue du texte et l'ID.
foreach (var item in result)
{
multilanguagesInput.Add(new MultiLanguageInput(item.LanguageIso, item.Id, item.Text));
}
//
} |
Vous devez appeler _client.SentimentAsync pour exécuter votre requête. Cette fonction va retourner un objet de type SentimentBatchResult contenant une propriété Documents représentant les résultats pour chaque élément de la requête. Vous devez itérer sur cette liste. Chaque document est une entité de type SentimentBatchResultItem qui dispose une propriété Score qui représente le score attribué suite à l’analyse sentimentale du texte. Une valeur proche de 0 signifie un sentiment négatif, tandis qu’un score proche de 1 signifie un sentiment positif.
Le code pour cette partie est le suivant :
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| //Passage du document à l'API pour la detectiion des sentiments
var sentimentResults = await _client.SentimentAsync(
false,
new MultiLanguageBatchInput(multilanguagesInput));
//Lecture des reponses et ajout du score aux resultats
foreach (var document in sentimentResults.Documents)
{
result.Single(x => x.Id == document.Id).Score = document.Score.Value.ToString();
_logger.LogInformation($"Document ID: {document.Id} , Sentiment Score: {document.Score:0.00}");
}
return result; |
Le code au complet de la classe TextAnalyticsService est le suivant :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
| /// <summary>
/// Service pour l'analyse sentimentale
/// </summary>
public class TextAnalyticsService : ITextAnalyticsService
{
/// <summary>
/// Journalisation
/// </summary>
private readonly ILogger<TextAnalyticsService> _logger;
/// <summary>
/// Client pour l'accès à l'API TextAnalytics
/// </summary>
private readonly ITextAnalyticsClient _client;
/// <summary>
///
/// </summary>
/// <param name="logger">Requis pour la journalisation</param>
/// <param name="client">Requis pour le client TexteAnalytics</param>
public TextAnalyticsService(ILogger<TextAnalyticsService> logger, ITextAnalyticsClient client)
{
_logger = logger;
_client = client;
}
/// <summary>
/// Detection de la langue
/// </summary>
/// <param name="tweets">Liste des tweets à utiliser</param>
/// <returns>Liste de type ResultModel</returns>
public async Task<IList<ResultModel>> Language(IList<string> tweets)
{
//Journalisation
_logger.LogInformation("TextAnalyticsService.Language called");
//Initialisation d'une liste de LanguageInput pour la requête
var languagesInput = new List<LanguageInput>();
//Création du document qui sera analysé. Ajout des tweets au document en attribuant à chacun des identifiant.
int i = 1;
foreach (string tweet in tweets)
{
languagesInput.Add(new LanguageInput(id:i.ToString(), text:tweet));
i++;
}
//Passage du document à l'API pour la détection de la langue
var langResults = await _client.DetectLanguageAsync(false, new LanguageBatchInput(languagesInput));
//Initialisation de l'objet de retour
var resultsModel = new List<ResultModel>();
//AJout des résultats de la détection de la langue au resulatModels
foreach(var document in langResults.Documents)
{
resultsModel.Add(new ResultModel { Language = document.DetectedLanguages[0].Name,
LanguageIso = document.DetectedLanguages[0].Iso6391Name, Id = document.Id,
Text = tweets.ElementAt(int.Parse(document.Id) - 1) });
_logger.LogInformation($"Document ID: {document.Id} , Language: {document.DetectedLanguages[0].Name}");
}
return resultsModel;
}
/// <summary>
/// Detection des sentiments
/// </summary>
/// <param name="result"></param>
/// <returns>Liste de type ResultModel</returns>
public async Task<IList<ResultModel>> Sentiment(IList<ResultModel> result)
{
//Journalisation
_logger.LogInformation("TextAnalyticsService.Sentiment called");
//Initialisation d'une liste de MultiLanguageInput pour la requete
var multilanguagesInput = new List<MultiLanguageInput>();
//Création du document qui sera analysé. Ajout du texte, de la langue du texte et l'ID.
foreach (var item in result)
{
multilanguagesInput.Add(new MultiLanguageInput(item.LanguageIso, item.Id, item.Text));
}
//Passage du document à l'API pour la detectiion des sentiments
var sentimentResults = await _client.SentimentAsync(
false,
new MultiLanguageBatchInput(multilanguagesInput));
//Lecture des reponses et ajout du score aux resultats
foreach (var document in sentimentResults.Documents)
{
result.Single(x => x.Id == document.Id).Score = document.Score.Value.ToString();
_logger.LogInformation($"Document ID: {document.Id} , Sentiment Score: {document.Score:0.00}");
}
return result;
}
} |
Création du ServiceClientCredentials
Le client TextAnalytics prend en paramètre un objet de type ServiceClientCredentials. Nous devons implémenter une classe qui hérite de ce type.
Cette classe permettra d’ajouter à l’entête de la requête HTTP la clé d’accès au service TexteAnalytics sur le portail Azure. Vous devez donc créer une nouvelle classe ApiKeyServiceClientCredentials, avec le code suivant :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class ApiKeyServiceClientCredentials : ServiceClientCredentials
{
private string _apiKey;
public ApiKeyServiceClientCredentials(string apikey)
{
this._apiKey = apikey;
}
public override Task ProcessHttpRequestAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Add("Ocp-Apim-Subscription-Key", _apiKey);
return base.ProcessHttpRequestAsync(request, cancellationToken);
}
} |
Edition du Startup.cs
Maintenant nous allons modifier la méthode ConfigureServices de la classe et ajouter nos deux services et le client TextAnalytics au conteneur d’IoC :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
//Ajout du client TextAnalytics au conteneur d'IoC
services.AddSingleton<ITextAnalyticsClient>(s=> new TextAnalyticsClient(new ApiKeyServiceClientCredentials(Configuration.GetValue<string>("Textanalytics:SubscriptionKey")))
{
Endpoint = Configuration.GetValue<string>("Textanalytics:EndPoint")
});
//Ajout du service TweetsSearch au conteneur d'IoC
services.AddTransient<ITweetsSearch, TweetsSearch>();
//Ajout du service TextAnalyticsService au conteneur d'IoC
services.AddTransient<ITextAnalyticsService, TextAnalyticsService>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
} |
Mise à jour de la vue
Nous allons commencer par modifier le fichier Index.cshtml.cs. Nous devons injecter notre service ITextAnalyticsService via le constructeur de classe IndexModel et modifier le code appeler la fonction de détection de la langue, ensuite la fonction d’analyse sentimentale.
Ci-dessous le nouveau code de cette classe :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| public class IndexModel : PageModel
{
/// <summary>
/// Liste des Tweets
/// </summary>
public IList<string> Tweets { get; set; }
public IList<ResultModel> Results { get; set; }
/// <summary>
/// Paramètre de recherche
/// </summary>
[Required]
[StringLength(50)]
[BindProperty]
public string Tag { get; set; }
/// <summary>
/// Recherche avec Twitter
/// </summary>
private readonly ITweetsSearch _tweetsSearch;
private readonly ITextAnalyticsService _textAnalyticsService;
/// <summary>
/// Initialisation dune nouvelle instance de la classe PageModel
/// </summary>
/// <param name="tweetsSearch">Requis pour injecter le service ITweetsSearch </param>
public IndexModel(ITweetsSearch tweetsSearch, ITextAnalyticsService textAnalyticsService)
{
_tweetsSearch = tweetsSearch;
_textAnalyticsService = textAnalyticsService;
}
/// <summary>
/// Méthode appelée lors d'une requête Get
/// </summary>
public void OnGet()
{
Results = new List<ResultModel>();
}
/// <summary>
/// Méthode appelée lors d'une requête Post
/// </summary>
public void OnPost()
{
Results = new List<ResultModel>();
Tweets = _tweetsSearch.GetTweets(Tag);
if(Tweets.Any())
{
Results = _textAnalyticsService.Language(Tweets).Result;
Results = _textAnalyticsService.Sentiment(Results).Result;
}
}
} |
Pour finir, nous devons modifier notre vue (Index.cshtml) pour afficher la langue et le score de l’analyse sentimentale :
À l’exécution, vous devez obtenir ce qui suit :
Le code complet de cet exemple est disponible sur mon GitHub à l’adresse suivante :
https://github.com/hinault/tweets-sentiment-analysis