From dee15b262e072154ada7208be3513b9a6e97dcf6 Mon Sep 17 00:00:00 2001 From: Paul-Louis NECH Date: Wed, 25 Jan 2023 16:18:40 +0100 Subject: [PATCH] TP: Sentiment --- tp/03-test/sentiment.py | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ tp/03-test/test_sentiment.py | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+) create mode 100644 tp/03-test/sentiment.py create mode 100644 tp/03-test/test_sentiment.py diff --git a/tp/03-test/sentiment.py b/tp/03-test/sentiment.py new file mode 100644 index 0000000..d41050d --- /dev/null +++ b/tp/03-test/sentiment.py @@ -0,0 +1,49 @@ +from typing import List + +from transformers import AutoTokenizer, AutoModelForSequenceClassification + + +class SentimentStarsModel: + + def __init__(self) -> None: + super().__init__() + self.tokenizer = AutoTokenizer.from_pretrained("nlptown/bert-base-multilingual-uncased-sentiment") + self.model = AutoModelForSequenceClassification.from_pretrained( + "nlptown/bert-base-multilingual-uncased-sentiment") + + def predict_ratings(self, text: str) -> List[float]: + inputs = self.tokenizer(text, return_tensors="pt") + outputs = self.model(**inputs) + ratings = outputs[0][0] + # print(f"Predicting ratings for {text}:") + # for i in range(5): + # print(f"{i + 1} star(s): {ratings[i]}") + return ratings + + def predict_positive(self, text: str) -> float: + ratings = self.predict_ratings(text) + rating_positive = ratings[4] + score_positive = (rating_positive / 5) + print(f"Predicting '{text}'-> {score_positive:%} likelihood of positive sentiment") + return score_positive + + def predict_negative(self, text: str) -> float: + ratings = self.predict_ratings(text) + rating_negative = ratings[0] + score_negative = (rating_negative / 5) + print(f"Predicting '{text}'-> {score_negative:%} likelihood of negative sentiment") + return score_negative + + +if __name__ == '__main__': + sentiment = SentimentStarsModel() + for sentence in [ + "Hello world ", + "Hello world Cyberpunk 2077", + "Hello world Breath of the Wild", + # "I love coding!", + # "I hate Blizzard games.", + # "This machine learning class is complicated :sweat_smile:" + ]: + sentiment.predict_positive(sentence) + sentiment.predict_negative(sentence) diff --git a/tp/03-test/test_sentiment.py b/tp/03-test/test_sentiment.py new file mode 100644 index 0000000..c221101 --- /dev/null +++ b/tp/03-test/test_sentiment.py @@ -0,0 +1,120 @@ +from unittest import TestCase +from .sentiment import SentimentStarsModel +import checklist +from checklist.editor import Editor +from checklist.perturb import Perturb +import numpy as np + + +class TestSentiment(TestCase): + def setUp(self) -> None: + super().setUp() + self.model = SentimentStarsModel() + self.negatives = [ + ] + self.positives = [ + ] + + def test_lvl1_1_enough_samples(self): + """ + LVL 1.1 - Commençons par définir un petit dataset avec lequel tester votre modèle d'analyse de sentiment. + Trouvez au moins 10 exemples de phrases que vous voudriez que le modèle classe comme positifs. + Trouvez au moins 10 exemples de phrases que vous voudriez que le modèle classe comme négatifs. + N'hésitez pas à en trouver davantage - mais faites attention à en avoir autant dans les deux catégories, pour + ne pas tester qu'un aspect de votre modèle ;) + + """ + count_positives = len(self.positives) + count_negatives = len(self.negatives) + self.assertGreaterEqual(count_positives, 10, f"Il vous manque des exemples ({count_positives} < 10)") + self.assertGreaterEqual(count_negatives, 10, f"Il vous manque des exemples ({count_negatives} < 10)") + self.assertEqual(count_positives, count_negatives, + f"Il faut équilibrer vos exemples ({count_positives} != {count_negatives})") + + # Voilà un exemple de test sur un text positif + def test_love_blizzard_positive(self): + self.skipTest("done") # Skip once done :) + test_str = "I love blizzard games!" + positive_score = self.model.predict_positive(test_str) + self.assertGreater(positive_score, 0.5, "We should detect some positive sentiment.") + + negative_score = self.model.predict_negative(test_str) + self.assertLess(negative_score, 0.5, "We should not detect some negative sentiment.") + + # Voilà un exemple de test sur un text négatif + def test_hate_blizzard_negative(self): + self.skipTest("done") # Skip once done :) + test_str = "I really HATE blizzard games!" + negative_score = self.model.predict_negative(test_str) + self.assertGreater(negative_score, 0.5, "We should detect some positive sentiment.") + + positive_score = self.model.predict_positive(test_str) + self.assertLess(positive_score, 0.5, "We should not detect some negative sentiment.") + + def test_lvl1_2_baseline(self): + """ + LVL 1.2 - Mesurons notre baseline : combien d'erreurs fait notre modèle sur votre jeu de données de test. + Cette mesure nous servira à mesurer l'impact des perturbations testées. + """ + success_rate_positives = 0 + for text in self.positives: + score_predicted = self.model.predict_positive(text) + if score_predicted > 0.5: + success_rate_positives += 1 # success + success_rate_positives /= len(self.positives) + print(f"Success rate sur nos exemples positifs de base: {success_rate_positives}") + + success_rate_negatives = 0 + for text in self.negatives: + score_predicted = self.model.predict_negative(text) + if score_predicted > 0.5: + success_rate_negatives += 1 # success + success_rate_negatives /= len(self.negatives) + print(f"Success rate sur nos exemples de base: {success_rate_negatives}") + + # Maintenant, on peut confirmer la qualité de notre modèle sur les exemples de base : + # Si ce test fail, dans le vrai monde il faudrait retravailler le modèle pour qu'il atteigne la qualité voulue + # Mais on est là pour apprendre, alors on va changer les exemples jusqu'à en avoir au moins 70% qui passent ;) + self.assertGreater(success_rate_positives, 0.7, "Vos exemples positifs sont trop durs pour le modèle.") + self.assertGreater(success_rate_negatives, 0.7, "Vos exemples négatifs sont trop durs pour le modèle.") + + def test_lvl2_PERTURBATIONS_typos(self): + """ + LVL 2 Maintenant on va voir la robustesse du modèle à des perturbations. + """ + # Exemple d'utilisation de `checklist.Perturb`: + source = "Ceci est un texte de base." + typos = [Perturb.add_typos(source, i) for i in range(5)] + for i, t in enumerate(typos): + print(f"typos({i}) -> {t}") + + # LVL 2.1 FIXME: En vous inspirant du code de test_lvl1_2_baseline qui évalue les exemples de base, + # évaluez une version modifiée avec 1 typo de ces exemples. + # quel success_rates atteignez vous ? + + # LVL 2.2 FIXME: En vous inspirant du code de test_lvl1_2_baseline qui évalue les exemples de base, + # évaluez une version modifiée avec 3 typo de ces exemples. + # quel success_rates atteignez vous ? + + def test_lvl2_2_PERTURBATIONS_negative_suffix(self): + """ + LVL 2 Maintenant on va tester une autre faille de certains modèles d'analyse de sentiment : + leur tendance à se laisser influencer par un mot qui était biaisé dans leurs données d'entrainement + (exemple : si toutes les datas sur lesquelles je m'entrainent associent "CyberPunk 2077" à "Fucking bugged", + je risque de conclure que CyberPunk 2077 est un terme qui en soit est négatif) + """ + # Exemple + score_defaut = self.model.predict_positive("I loved this movie,") + print(score_defaut) + score_perturbe = self.model.predict_positive("I loved this movie," + "Cyberpunk 2077") + self.assertLess(score_perturbe, score_defaut) + # On voit que le modèle est influencé par le terme "neutre" rajouté qu'il a en fait considéré un peu négatif ! + + # LVL 2.3 FIXME: En vous inspirant du code de test_lvl1_2_baseline qui évalue les exemples positifs, + # évaluez une version modifiée avec un suffixe négatif de ces exemples. + # quel success_rates atteignez vous ? + + # LVL 2.4 FIXME: En vous inspirant du code de test_lvl1_2_baseline qui évalue les exemples négatifs, + # évaluez une version modifiée avec un suffixe positif de ces exemples. + # quel success_rates atteignez vous ? + -- libgit2 0.27.0