Utiliser l'analyse des sentiments avec Python pour classer les critiques de films – Real Python

By | novembre 9, 2020

Cours Python en ligne

Analyse des sentiments est un outil puissant qui permet aux ordinateurs de comprendre le ton subjectif sous-jacent d'un texte. C'est quelque chose avec lequel les humains ont du mal, et comme vous pouvez l'imaginer, ce n'est pas toujours aussi facile pour les ordinateurs. Mais avec les bons outils et Python, vous pouvez utiliser l'analyse des sentiments pour mieux comprendre sentiment d'un morceau d'écriture.

Pourquoi voudriez-vous faire ça? Il existe de nombreuses utilisations de l'analyse des sentiments, telles que la compréhension de ce que les négociateurs en bourse pensent d'une entreprise en particulier en utilisant les données des médias sociaux ou en agrégeant les avis, ce que vous pourrez faire à la fin de ce didacticiel.

Ce didacticiel est idéal pour les praticiens débutants en apprentissage automatique qui souhaitent un guide axé sur le projet pour créer des pipelines d'analyse des sentiments avec spaCy.

Vous devez être familiarisé avec les techniques d'apprentissage automatique de base telles que la classification binaire, ainsi que les concepts qui les sous-tendent, tels que les boucles d'apprentissage, les lots de données, les pondérations et les biais. Si vous n'êtes pas familiarisé avec l'apprentissage automatique, vous pouvez démarrer votre aventure en apprenant la régression logistique.

Lorsque vous êtes prêt, vous pouvez suivre les exemples de ce didacticiel en téléchargeant le code source à partir du lien ci-dessous:

Utilisation du traitement du langage naturel pour prétraiter et nettoyer les données textuelles

Tout flux de travail d'analyse des sentiments commence par le chargement des données. Mais que faites-vous une fois les données chargées? Vous devez le traiter via un pipeline de traitement du langage naturel avant de pouvoir en faire quelque chose d'intéressant.

Les étapes nécessaires comprennent (mais ne sont pas limitées à) les suivantes:

  1. Tokenizing des phrases pour décomposer le texte en phrases, mots ou autres unités
  2. Suppression des mots vides comme «si», «mais», «ou», etc.
  3. Normaliser les mots en condensant toutes les formes d'un mot en une seule forme
  4. Vectoriser le texte en transformant le texte en représentation numérique pour la consommation de votre classificateur

Toutes ces étapes servent à réduire la bruit inhérente à tout texte lisible par l'homme et améliore la précision des résultats de votre classificateur. Il existe de nombreux outils formidables pour vous aider, tels que la boîte à outils en langage naturel, TextBlob et spaCy. Pour ce didacticiel, vous utiliserez spaCy.

Avant d'aller plus loin, assurez-vous que spaCy et son modèle anglais sont installés:

$ pip installer spacy
$ python -m spacy télécharger en_core_web_sm

La première commande installe spaCy et la seconde utilise spaCy pour télécharger son modèle en anglais. spaCy prend en charge un certain nombre de langues différentes, qui sont répertoriées sur le site Web spaCy.

Ensuite, vous apprendrez à utiliser spaCy pour vous aider avec les étapes de prétraitement que vous avez apprises précédemment, en commençant par la tokenisation.

Tokenisation

Tokenisation est le processus de décomposition de morceaux de texte en morceaux plus petits. spaCy est livré avec un pipeline de traitement par défaut qui commence par la tokenisation, ce qui rend ce processus un jeu d'enfant. Dans spaCy, vous pouvez effectuer soit une tokenisation de phrase, soit une tokenisation de mot:

  • Tokenisation de mot décompose le texte en mots individuels.
  • Tokenisation des phrases décompose le texte en phrases individuelles.

Dans ce didacticiel, vous allez utiliser la segmentation des mots pour séparer le texte en mots individuels. Tout d'abord, vous allez charger le texte dans spaCy, qui effectue le travail de tokenisation pour vous:

>>>

>>> importer spacy
>>> texte = "" "
Dave regarda la forêt brûler sur la colline,
à seulement quelques kilomètres de sa maison. La voiture avait
été emballé à la hâte et Marta était à l'intérieur essayant de contourner
le dernier des animaux de compagnie. «Où pourrait-elle être? il s'est demandé
alors qu'il continuait d'attendre que Marta apparaisse avec les animaux.
"" "
>>> nlp = spacy.charge("en_core_web_sm")
>>> doc = nlp(texte)
>>> token_list = [[[[jeton pour jeton dans doc]
>>> token_list
[[[[
, Dave, a regardé, comme, la, forêt, brûlé, en haut, sur, la, colline``
, seulement, quelques, miles, de, sa, maison,., La, voiture, avait,
, été, à la hâte, emballé, et, Marta, était, à l'intérieur, essayant, de, rond,
, vers le haut, le, dernier, des, animaux de compagnie,., ", Où, pourrait-elle, être,?
, comme, il, a continué, à, attendre, pour, Marta, à, apparaître, avec, les, animaux de compagnie,.,
]

Dans ce code, vous configurez un exemple de texte à tokeniser, chargez le modèle anglais de spaCy, puis tokenisez le texte en le passant dans le nlp constructeur. Ce modèle comprend un pipeline de traitement par défaut que vous pouvez personnaliser, comme vous le verrez plus loin dans la section projet.

Après cela, vous générez une liste de jetons et l'imprimez. Comme vous l'avez peut-être remarqué, «tokenisation de mots» est un terme légèrement trompeur, car les jetons capturés incluent la ponctuation et d'autres chaînes non-mots.

Les jetons sont un type de conteneur important dans spaCy et ont un ensemble très riche de fonctionnalités. Dans la section suivante, vous apprendrez à utiliser l'une de ces fonctionnalités pour filtrer les mots vides.

Suppression de mots vides

Arrêter les mots sont des mots qui peuvent être importants dans la communication humaine mais qui ont peu de valeur pour les machines. spaCy est livré avec une liste par défaut de mots vides que vous pouvez personnaliser. Pour l'instant, vous verrez comment utiliser les attributs de jeton pour supprimer les mots vides:

>>>

>>> filtered_tokens = [[[[jeton pour jeton dans doc si ne pas jeton.is_stop]
>>> filtered_tokens
[[[[
, Dave, regardé, forêt, brûlé, colline``
, miles, maison,., voiture,
à la hâte, emballé, Marta, à lintérieur, en essayant, rond,
, animaux domestiques,., ",?,", se demanda,
, a continué, attendez, Marta, apparaissent, animaux domestiques,.,
]

Dans une ligne de code Python, vous filtrez les mots vides du texte symbolisé en utilisant le .is_stop attribut de jeton.

Quelles différences remarquez-vous entre cette sortie et la sortie que vous avez obtenue après avoir tokenisé le texte? Une fois les mots vides supprimés, la liste des jetons est beaucoup plus courte et il y a moins de contexte pour vous aider à comprendre les jetons.

Normaliser les mots

Normalisation est un peu plus complexe que la tokenisation. Cela implique de condenser toutes les formes d'un mot en une seule représentation de ce mot. Par exemple, «regardé», «visionnage» et «montres» peuvent tous être normalisés en «montre». Il existe deux principales méthodes de normalisation:

  1. Tige
  2. Lemmatisation

Avec élever, un mot est coupé à sa tige, la plus petite unité de ce mot à partir de laquelle vous pouvez créer les mots descendants. Vous venez de voir un exemple de cela ci-dessus avec "montre". La racine tronque simplement la chaîne en utilisant des terminaisons communes, de sorte qu'elle manquera la relation entre «sentir» et «ressenti», par exemple.

Lemmatisation cherche à résoudre ce problème. Ce processus utilise une structure de données qui relie toutes les formes d'un mot à sa forme la plus simple, ou lemme. Parce que la lemmatisation est généralement plus puissante que la tige, c'est la seule stratégie de normalisation proposée par spaCy.

Heureusement, vous n'avez pas besoin de code supplémentaire pour ce faire. Cela se produit automatiquement, avec un certain nombre d'autres activités, telles que partie du balisage vocal et reconnaissance d'entité nommée-quand vous appelez nlp (). Vous pouvez inspecter le lemme de chaque jeton en profitant du .lemme_ attribut:

>>>

>>> lemmes = [[[[
...     F"Jeton: jeton, lemme: jeton.lemme_"
...     pour jeton dans filtered_tokens
... ]
>>> lemmes
['Jeton:nlemma:n''Token:Davelemma:Dave'['Jeton:nlemma:n''Token:Davelemma:Dave'['Token:nlemma:n''Token:Davelemma:Dave'['Token:nlemma:n''Token:Davelemma:Dave'
    'Jeton: surveillé, lemme: montre', 'Jeton: forêt, lemme: forêt',
    # ...
]

Tout ce que vous avez fait ici a été de générer une liste lisible de jetons et de lemmes en parcourant la liste filtrée de jetons, en tirant parti de la .lemme_ attribut pour inspecter les lemmes. Cet exemple montre uniquement les quelques premiers jetons et lemmes. Votre sortie sera beaucoup plus longue.

L'étape suivante consiste à représenter chaque jeton de manière à ce qu'une machine puisse le comprendre. C'est appelé vectorisation.

Vectoriser le texte

Vectorisation est un processus qui transforme un token en un vecteur, ou un tableau numérique qui, dans le contexte de la PNL, est unique et représente diverses fonctionnalités d'un jeton. Les vecteurs sont utilisés sous le capot pour trouver des similitudes de mots, classer du texte et effectuer d'autres opérations NLP.

Cette représentation particulière est un matrice dense, un dans lequel il existe des valeurs définies pour chaque espace du tableau. Ceci est contraire aux méthodes antérieures qui utilisaient tableaux clairsemés, dans lequel la plupart des espaces sont vides.

Comme les autres étapes, la vectorisation est prise en charge automatiquement avec le nlp () appel. Puisque vous avez déjà une liste d'objets jetons, vous pouvez obtenir la représentation vectorielle de l'un des jetons comme suit:

>>>

>>> filtered_tokens[[[[1].vecteur
tableau ([1837164614529226-161472110678362-06594443[1837164614529226-161472110678362-06594443[1837164614529226-161472110678362-06594443[1837164614529226-161472110678362-06594443
                                1.6417935, 0.5796405, 2.3021278, -0.13260496, 0.5750932,
                                1.5654886, -0.6938864, -0.59607106, -1.5377437, 1.9425622,
                            -2,4552505, 1,2321601, 1,0434952, -1,5102385, -0,5787632,
                                0,12055647, 3,6501784, 2,6160972, -0,5710199, -1,5221789,
                                0,00629176, 0,22760668, -1,922073, -1,6252862, -4,226225,
                            -3.495663, -3.312053, 0.81387717, -0.00677544, -0.11603224,
                                1.4620426, 3.0751472, 0.35958546, -0.22527039, -2.743926,
                                1.269633, 4.606786, 0.34034157, -2.1272311, 1.2619178,
                            -4.209798, 5.452852, 1.6940253, -2.5972986, 0.95049495,
                            -1,910578, -2,374927, -1,4227567, -2,2528825, -1,799806,
                                1.607501, 2.9914255, 2.8065152, -1.2510269, -0.54964066,
                            -0,49980402, -1,3882618, -0,470479, -2,9670253, 1,7884955,
                                4.5282774, -1.2602427, -0.14885521, 1.0419178, -0.08892632,
                            -1.138275, 2.242618, 1.5077229, -1.5030195, 2.528098,
                            -1,6761329, 0,16694719, 2,123961, 0,02546412, 0,38754445,
                                0.8911977, -0.07678384, -2.0690763, -1.1211847, 1.4821006,
                                1.1989193, 2.1933236, 0.5296372, 3.0646474, -1.7223308,
                            -1,3634219, -0,47471118, -1,7648507, 3,565178, -2,394205,
                            -1,3800384], dtype = float32)

Ici, vous utilisez le .vecteur attribut sur le deuxième jeton dans le filtered_tokens list, qui dans cet ensemble d'exemples est le mot Dave.

Maintenant que vous avez appris certaines des étapes typiques de prétraitement de texte dans spaCy, vous allez apprendre à classer du texte.

Utilisation de classificateurs d'apprentissage automatique pour prédire le sentiment

Votre texte est maintenant traité sous une forme compréhensible par votre ordinateur, vous pouvez donc commencer à le classer en fonction de son sentiment. Vous aborderez trois sujets qui vous donneront une compréhension générale de la classification par apprentissage automatique des données textuelles:

  1. Quels outils d'apprentissage automatique sont disponibles et comment ils sont utilisés
  2. Comment fonctionne la classification
  3. Comment utiliser spaCy pour la classification de texte

Tout d'abord, vous découvrirez certains des outils disponibles pour la classification du machine learning.

Outils d'apprentissage automatique

Il existe un certain nombre d'outils disponibles en Python pour résoudre les problèmes de classification. Voici quelques-uns des plus populaires:

Cette liste n'est pas exhaustive, mais ce sont les frameworks d'apprentissage automatique les plus largement utilisés disponibles en Python. Ce sont des frameworks volumineux et puissants qui prennent beaucoup de temps à vraiment maîtriser et comprendre.

TensorFlow est développé par Google et est l'un des frameworks d'apprentissage automatique les plus populaires. Vous l'utilisez principalement pour implémenter vos propres algorithmes d'apprentissage automatique, par opposition à l'utilisation d'algorithmes existants. C'est un niveau assez bas, ce qui donne beaucoup de puissance à l'utilisateur, mais il s'accompagne d'une courbe d'apprentissage abrupte.

PyTorch est la réponse de Facebook à TensorFlow et atteint la plupart des mêmes objectifs. Cependant, il est conçu pour être plus familier aux programmeurs Python et est devenu un framework très populaire à part entière. Comme ils ont des cas d'utilisation similaires, comparer TensorFlow et PyTorch est un exercice utile si vous envisagez d'apprendre un framework.

scikit-learn contraste avec TensorFlow et PyTorch. Il s'agit d'un niveau supérieur et vous permet d'utiliser des algorithmes d'apprentissage automatique prêts à l'emploi plutôt que de créer les vôtres. Ce qui lui manque en termes de personnalisation, il compense largement sa facilité d'utilisation, vous permettant de former rapidement des classificateurs en quelques lignes de code.

Heureusement, spaCy fournit un classificateur de texte intégré assez simple que vous découvrirez un peu plus tard. Cependant, il est tout d'abord important de comprendre le flux de travail général pour tout type de problème de classification.

Comment fonctionne la classification

Ne vous inquiétez pas, pour cette section, vous n'allez pas approfondir l'algèbre linéaire, les espaces vectoriels ou d'autres concepts ésotériques qui alimentent l'apprentissage automatique en général. Au lieu de cela, vous obtiendrez une introduction pratique au flux de travail et aux contraintes communes aux problèmes de classification.

Une fois que vous avez vos données vectorisées, un flux de travail de base pour la classification ressemble à ceci:

  1. Divisez vos données en ensembles de formation et d'évaluation.
  2. Sélectionnez une architecture de modèle.
  3. Utilisez les données d'entraînement pour entraîner votre modèle.
  4. Utilisez des données de test pour évaluer les performances de votre modèle.
  5. Utilisez votre modèle entraîné sur de nouvelles données pour générer des prédictions, qui dans ce cas seront un nombre compris entre -1,0 et 1,0.

Cette liste n'est pas exhaustive et il existe un certain nombre d'étapes et de variantes supplémentaires qui peuvent être effectuées pour tenter d'améliorer la précision. Par exemple, les praticiens de l'apprentissage automatique divisent souvent leurs ensembles de données en trois ensembles:

  1. Entraînement
  2. Validation
  3. Tester

le ensemble d'entraînement, comme son nom l'indique, est utilisé pour entraîner votre modèle. le ensemble de validation est utilisé pour aider à régler le hyperparamètres de votre modèle, ce qui peut conduire à de meilleures performances.

le ensemble d'essai est un ensemble de données qui incorpore une grande variété de données pour juger avec précision les performances du modèle. Les ensembles de tests sont souvent utilisés pour comparer plusieurs modèles, y compris les mêmes modèles à différentes étapes de la formation.

Maintenant que vous avez appris le déroulement général de la classification, il est temps de le mettre en action avec spaCy.

Comment utiliser spaCy pour la classification de texte

Vous avez déjà appris comment spaCy effectue une grande partie du prétraitement de texte pour vous avec le nlp () constructeur. Ceci est vraiment utile car la formation d'un modèle de classification nécessite de nombreux exemples pour être utile.

De plus, spaCy fournit une fonctionnalité de pipeline qui alimente une grande partie de la magie qui se produit sous le capot lorsque vous appelez nlp (). Le pipeline par défaut est défini dans un fichier JSON associé au modèle préexistant que vous utilisez (en_core_web_sm pour ce didacticiel), mais vous pouvez également en créer un à partir de zéro si vous le souhaitez.

Qu'est-ce que cela a à voir avec la classification? L'un des composants de pipeline intégrés fournis par spaCy est appelé textcat (court pour TextCategorizer), qui vous permet d'attribuer des catégories (ou Étiquettes) à vos données texte et utilisez-les comme données d'entraînement pour un réseau neuronal.

Ce processus générera un modèle entraîné que vous pourrez ensuite utiliser pour prédire le sentiment d'un morceau de texte donné. Pour profiter de cet outil, vous devez suivre les étapes suivantes:

  1. Ajouter le textcat composant au pipeline existant.
  2. Ajoutez des étiquettes valides au textcat composant.
  3. Chargez, mélangez et divisez vos données.
  4. Former le modèle, en évaluant sur chaque boucle d'entraînement.
  5. Utilisez le modèle entraîné pour prédire le sentiment des données non liées à la formation.
  6. Le cas échéant, enregistrez le modèle entraîné.

Dans la section suivante, vous apprendrez à rassembler tous ces éléments en créant votre propre projet: un analyseur de sentiment de critique de film.

Construire votre propre analyseur de sentiments PNL

Dans les sections précédentes, vous avez probablement remarqué quatre étapes majeures dans la création d'un pipeline d'analyse des sentiments:

  1. Chargement des données
  2. Prétraitement
  3. Former le classificateur
  4. Classification des données

Pour créer un analyseur de sentiments réel, vous passerez par chacune des étapes qui composent ces étapes. Vous utiliserez l'ensemble de données Large Movie Review compilé par Andrew Maas pour entraîner et tester votre analyseur de sentiments. Une fois que vous êtes prêt, passez à la section suivante pour charger vos données.

Chargement et prétraitement des données

Si vous ne l’avez pas déjà fait, téléchargez et extrayez le jeu de données Large Movie Review. Passez quelques minutes à fouiller, à examiner sa structure et à échantillonner certaines données. Cela vous indiquera comment vous chargez les données. Pour cette partie, vous utiliserez spaCy's textcat exemple à titre indicatif.

Vous pouvez (et devriez) décomposer l'étape de chargement en étapes concrètes pour aider à planifier votre codage. Voici un exemple:

  1. Chargez du texte et des étiquettes à partir des structures de fichiers et de répertoires.
  2. Mélangez les données.
  3. Divisez les données en ensembles de formation et de test.
  4. Renvoie les deux ensembles de données.

Ce processus est relativement autonome, il devrait donc être au moins sa propre fonction. En réfléchissant aux actions que cette fonction effectuerait, vous avez peut-être pensé à certains paramètres possibles.

Étant donné que vous divisez des données, la possibilité de contrôler la taille de ces divisions peut être utile. Divisé est un bon paramètre à inclure. Vous pouvez également souhaiter limiter le nombre total de documents que vous traitez avec un limite paramètre. Vous pouvez ouvrir votre éditeur préféré et ajouter cette signature de fonction:

def load_training_data(
    data_directory: str = "aclImdb / train",
    Divisé: flotte = 0,8,
    limite: int = 0
) -> tuple:

Avec cette signature, vous tirez parti des annotations de type de Python 3 pour indiquer clairement quels types votre fonction attend et ce qu’elle retournera.

Les paramètres ici vous permettent de définir le répertoire dans lequel vos données sont stockées ainsi que le rapport entre les données d'entraînement et les données de test. Un bon ratio pour commencer est de 80% des données pour les données d'entraînement et 20% pour les données de test. Sauf indication contraire, tout cela et le code suivant doivent résider dans le même fichier.

Ensuite, vous souhaiterez parcourir tous les fichiers de cet ensemble de données et les charger dans une liste:

importer os

def load_training_data(
    data_directory: str = "aclImdb / train",
    Divisé: flotte = 0,8,
    limite: int = 0
) -> tuple:
    # Charger à partir de fichiers
    Commentaires = []
    pour étiquette dans [[[["pos", "nég"]:
        étiqueté_directory = F"data_directory/étiquette"
        pour la revue dans os.listdir(étiqueté_directory):
            si la revue.se termine par(".SMS"):
                avec ouvert(F"étiqueté_directory/la revue") comme F:
                    texte = F.lis()
                    texte = texte.remplacer("
"
, " n n")
si texte.bande(): étiquette_spacy = "chats": "pos": "pos" == étiquette, "nég": "nég" == étiquette Commentaires.ajouter((texte, étiquette_spacy))

Bien que cela puisse sembler compliqué, ce que vous faites est de construire la structure de répertoire des données, de rechercher et d'ouvrir des fichiers texte, puis d'ajouter un tuple du contenu et un dictionnaire d'étiquettes au Commentaires liste.

La structure du dictionnaire d'étiquettes est un format requis par le modèle spaCy pendant la boucle d'entraînement, que vous verrez bientôt.

Étant donné que chaque avis est ouvert à ce stade, il est judicieux de remplacer le
Balises HTML dans les textes avec des retours à la ligne et à utiliser .bande() pour supprimer tous les espaces de début et de fin.

Pour ce projet, vous ne supprimerez pas immédiatement les mots vides de vos données d'entraînement, car cela pourrait changer le sens d'une phrase ou d'une expression, ce qui pourrait réduire le pouvoir prédictif de votre classificateur. Cela dépend quelque peu de la liste de mots vides que vous utilisez.

Après avoir chargé les fichiers, vous souhaitez les mélanger. Cela permet d'éliminer tout biais possible dans l'ordre dans lequel les données d'apprentissage sont chargées. Depuis le Aléatoire module facilite cette opération en une seule ligne, vous verrez également comment diviser vos données mélangées:

importer os
importer Aléatoire

def load_training_data(
    data_directory: str = "aclImdb / train",
    Divisé: flotte = 0,8,
    limite: int = 0
) -> tuple:
    # Charger à partir de fichiers
    Commentaires = []
    pour étiquette dans [[[["pos", "nég"]:
        étiqueté_directory = F"data_directory/étiquette"
        pour la revue dans os.listdir(étiqueté_directory):
            si la revue.se termine par(".SMS"):
                avec ouvert(F"étiqueté_directory/la revue") comme F:
                    texte = F.lis()
                    texte = texte.remplacer("
"
, " n n") si texte.bande(): étiquette_spacy = "chats": "pos": "pos" == étiquette, "nég": "nég" == étiquette Commentaires.ajouter((texte, étiquette_spacy)) Aléatoire.mélanger(Commentaires) si limite: Commentaires = Commentaires[:[:[:[:limite] Divisé = int(len(Commentaires) * Divisé) revenir Commentaires[:[:[:[:Divisé], Commentaires[[[[Divisé:]

Ici, vous mélangez vos données avec un appel à random.shuffle (). Ensuite, vous pouvez éventuellement tronquer et diviser les données en utilisant des mathématiques pour convertir la division en un certain nombre d'éléments qui définissent la limite de division. Enfin, vous retournez deux parties du Commentaires liste en utilisant des tranches de liste.

Voici un exemple de sortie, tronqué par souci de concision:

(
                `` Quand la tradition veut qu'un artiste passe (...) '',
                'cats': 'pos': True, 'neg': False
)

Pour en savoir plus sur la façon Aléatoire fonctionne, jetez un œil à Génération de données aléatoires en Python (Guide).

Maintenant que vous avez construit votre chargeur de données et que vous avez effectué un léger prétraitement, il est temps de créer le pipeline spaCy et la boucle de formation du classificateur.

Entraîner votre classificateur

L'assemblage du pipeline spaCy vous permet de créer et de former rapidement un réseau neuronal convolutif (CNN) pour la classification des données textuelles. Bien que vous l'utilisiez ici pour l'analyse des sentiments, il est suffisamment général pour fonctionner avec tout type de tâche de classification de texte, à condition que vous lui fournissiez les données d'entraînement et les étiquettes.

Dans cette partie du projet, vous vous occuperez de trois étapes:

  1. Modification du pipeline spaCy de base pour inclure textcat composant
  2. Créer une boucle de formation pour former le textcat composant
  3. Évaluer la progression de l'entraînement de votre modèle après un nombre donné de boucles d'entraînement

Tout d'abord, vous ajouterez textcat au pipeline spaCy par défaut.

Modification du pipeline spaCy pour inclure textcat

Pour la première partie, vous allez charger le même pipeline que vous l'avez fait dans les exemples au début de ce didacticiel, puis vous ajouterez le textcat composant s'il n'est pas déjà présent. Après cela, vous ajouterez les libellés utilisés par vos données ("pos" pour positif et "nég" pour négatif) à textcat. Une fois cela fait, vous serez prêt à créer la boucle d'entraînement:

importer os
importer Aléatoire
importer spacy

def train_model(
    données d'entraînement: liste,
    données de test: liste,
    itérations: int = 20
) -> Aucun:
    # Construire un pipeline
    nlp = spacy.charge("en_core_web_sm")
    si "textcat" ne pas dans nlp.pipe_names:
        textcat = nlp.create_pipe(
            "textcat", config="architecture": "simple_cnn"
        )
        nlp.add_pipe(textcat, dernier=Vrai)

Si vous avez consulté la documentation de spaCy textcat exemple déjà, alors cela devrait sembler assez familier. Tout d'abord, vous chargez le en_core_web_sm pipeline, puis vous vérifiez le .pipe_names attribut pour voir si le textcat le composant est déjà disponible.

Si ce n'est pas le cas, vous créez le composant (également appelé tuyau) avec .create_pipe (), en passant un dictionnaire de configuration. Il existe quelques options avec lesquelles vous pouvez travailler décrites dans le TextCategorizer Documentation.

Enfin, vous ajoutez le composant au pipeline en utilisant .add_pipe (), avec le dernier paramètre signifiant que ce composant doit être ajouté à la fin du pipeline.

Ensuite, vous gérerez le cas dans lequel le textcat composant est présent, puis ajoutez les étiquettes qui serviront de catégories pour votre texte:

importer os
importer Aléatoire
importer spacy

def train_model(
    données d'entraînement: liste,
    données de test: liste,
    itérations: int = 20
) -> Aucun:
    # Construire un pipeline
    nlp = spacy.charge("en_core_web_sm")
    si "textcat" ne pas dans nlp.pipe_names:
        textcat = nlp.create_pipe(
            "textcat", config="architecture": "simple_cnn"
        )
        nlp.add_pipe(textcat, dernier=Vrai)
    autre:
        textcat = nlp.get_pipe("textcat")

    textcat.add_label("pos")
    textcat.add_label("nég")

Si le composant est présent dans le pipeline chargé, il vous suffit d'utiliser .get_pipe () pour l'affecter à une variable afin de pouvoir y travailler. Pour ce projet, tout ce que vous allez faire avec lui, c'est ajouter les libellés de vos données afin que textcat sait quoi chercher. Vous ferez cela avec .add_label ().

Vous avez créé le pipeline et préparé le textcat composant pour les étiquettes qu'il utilisera pour la formation. Il est maintenant temps d'écrire la boucle d'entraînement qui permettra textcat pour classer les critiques de films.

Créez votre boucle d'entraînement pour vous entraîner textcat

Pour commencer la boucle d'entraînement, vous devez d'abord configurer votre pipeline pour entraîner uniquement les textcat composant, générer lots de données pour cela avec spaCy's minibatch () et composition () utilitaires, puis parcourez-les et mettez à jour votre modèle.

UNE lot n'est qu'un sous-ensemble de vos données. Le groupage de vos données vous permet de réduire l'empreinte mémoire pendant l'entraînement et de mettre à jour plus rapidement vos hyperparamètres.

Voici une mise en œuvre de la boucle d'entraînement décrite ci-dessus:

    1importer os
    2importer Aléatoire
    3importer spacy
    4de spacy.util importer minibatch, composition
    5
    6def train_model(
    sept    données d'entraînement: liste,
    8    données de test: liste,
    9    itérations: int = 20
dix) -> Aucun:
11    # Construire un pipeline
12    nlp = spacy.charge("en_core_web_sm")
13    si "textcat" ne pas dans nlp.pipe_names:
14        textcat = nlp.create_pipe(
15            "textcat", config="architecture": "simple_cnn"
16        )
17        nlp.add_pipe(textcat, dernier=Vrai)
18    autre:
19        textcat = nlp.get_pipe("textcat")
20
21    textcat.add_label("pos")
22    textcat.add_label("nég")
23
24    # Former uniquement textcat
25    training_excluded_pipes = [[[[
26        tuyau pour tuyau dans nlp.pipe_names si tuyau ! = "textcat"
27    ]

Aux lignes 25 à 27, vous créez une liste de tous les composants du pipeline qui ne sont pas textcat composant. Vous utilisez ensuite le nlp.disable () gestionnaire de contexte pour désactiver ces composants pour tout le code dans la portée du gestionnaire de contexte.

Vous êtes maintenant prêt à ajouter le code pour commencer l'entraînement:

importer os
importer Aléatoire
importer spacy
de spacy.util importer minibatch, composition

def train_model(
    données d'entraînement: liste,
    données de test: liste,
    itérations: int = 20
) -> None:
    # Build pipeline
    nlp = spacy.load("en_core_web_sm")
    si "textcat" ne pas dans nlp.pipe_names:
        textcat = nlp.create_pipe(
            "textcat", config="architecture": "simple_cnn"
        )
        nlp.add_pipe(textcat, last=True)
    else:
        textcat = nlp.get_pipe("textcat")

    textcat.add_label("pos")
    textcat.add_label("neg")

    # Train only textcat
    training_excluded_pipes = [[[[
        pipe for pipe dans nlp.pipe_names si pipe != "textcat"
    ]
    avec nlp.disable_pipes(training_excluded_pipes):
        optimizer = nlp.begin_training()
        # Training loop
        impression("Beginning training")
        batch_sizes = compounding(
            4.0, 32.0, 1.001
        )  # A generator that yields infinite series of input numbers

Here, you call nlp.begin_training(), which returns the initial optimizer function. This is what nlp.update() will use to update the weights of the underlying model.

You then use the compounding() utility to create a generator, giving you an infinite series of batch_sizes that will be used later by the minibatch() utility.

Now you’ll begin training on batches of data:

import os
import random
import spacy
de spacy.util import minibatch, compounding

def train_model(
    training_data: list,
    test_data: list,
    iterations: int = 20
) -> None:
    # Build pipeline
    nlp = spacy.load("en_core_web_sm")
    si "textcat" ne pas dans nlp.pipe_names:
        textcat = nlp.create_pipe(
            "textcat", config="architecture": "simple_cnn"
        )
        nlp.add_pipe(textcat, last=True)
    else:
        textcat = nlp.get_pipe("textcat")

    textcat.add_label("pos")
    textcat.add_label("neg")

    # Train only textcat
    training_excluded_pipes = [[[[
        pipe for pipe dans nlp.pipe_names si pipe != "textcat"
    ]
    avec nlp.disable_pipes(training_excluded_pipes):
        optimizer = nlp.begin_training()
        # Training loop
        impression("Beginning training")
        batch_sizes = compounding(
            4.0, 32.0, 1.001
        )  # A generator that yields infinite series of input numbers
        for je dans range(iterations):
            loss = 
            random.shuffle(training_data)
            batches = minibatch(training_data, size=batch_sizes)
            for batch dans batches:
                text, labels = zip(*batch)
                nlp.update(
                    text,
                    labels,
                    drop=0,2,
                    sgd=optimizer,
                    losses=loss
                )

Now, for each iteration that is specified in the train_model() signature, you create an empty dictionary called loss that will be updated and used by nlp.update(). You also shuffle the training data and split it into batches of varying size with minibatch().

For each batch, you separate the text and labels, then fed them, the empty loss dictionary, and the optimizer à nlp.update(). This runs the actual training on each example.

le dropout parameter tells nlp.update() what proportion of the training data in that batch to skip over. You do this to make it harder for the model to accidentally just memorize training data without coming up with a generalizable model.

This will take some time, so it’s important to periodically evaluate your model. You’ll do that with the data that you held back from the training set, also known as the holdout set.

Evaluating the Progress of Model Training

Since you’ll be doing a number of evaluations, with many calculations for each one, it makes sense to write a separate evaluate_model() function. In this function, you’ll run the documents in your test set against the unfinished model to get your model’s predictions and then compare them to the correct labels of that data.

Using that information, you’ll calculate the following values:

  • True positives are documents that your model correctly predicted as positive. For this project, this maps to the positive sentiment but generalizes in binary classification tasks to the class you’re trying to identify.

  • False positives are documents that your model incorrectly predicted as positive but were in fact negative.

  • True negatives are documents that your model correctly predicted as negative.

  • False negatives are documents that your model incorrectly predicted as negative but were in fact positive.

Because your model will return a score between 0 and 1 for each label, you’ll determine a positive or negative result based on that score. From the four statistics described above, you’ll calculate precision and recall, which are common measures of classification model performance:

  • Precision is the ratio of true positives to all items your model marked as positive (true et false positives). A precision of 1.0 means that every review that your model marked as positive belongs to the positive class.

  • Recall is the ratio of true positives to all reviews that are actually positive, or the number of true positives divided by the total number of true positives and false negatives.

le F-score is another popular accuracy measure, especially in the world of NLP. Explaining it could take its own article, but you’ll see the calculation in the code. As with precision and recall, the score ranges from 0 to 1, with 1 signifying the highest performance and 0 the lowest.

Pour evaluate_model(), you’ll need to pass in the pipeline’s tokenizer component, the textcat component, and your test dataset:

def evaluate_model(
    tokenizer, textcat, test_data: list
) -> dict:
    reviews, labels = zip(*test_data)
    reviews = (tokenizer(review) for review dans reviews)
    true_positives = 0
    false_positives = 1e-8  # Can't be 0 because of presence in denominator
    true_negatives = 0
    false_negatives = 1e-8
    for je, review dans enumerate(textcat.pipe(reviews)):
        true_label = labels[[[[je]
        for predicted_label, score dans review.cats.articles():
            # Every cats dictionary includes both labels. You can get all
            # the info you need with just the pos label.
            si (
                predicted_label == "neg"
            ):
                continue
            si score >= 0.5 et true_label == "pos":
                true_positives += 1
            elif score >= 0.5 et true_label == "neg":
                false_positives += 1
            elif score < 0.5 et true_label == "neg":
                true_negatives += 1
            elif score < 0.5 et true_label == "pos":
                false_negatives += 1
    precision = true_positives / (true_positives + false_positives)
    recall = true_positives / (true_positives + false_negatives)

    si precision + recall == 0:
        f_score = 0
    else:
        f_score = 2 * (precision * recall) / (precision + recall)
    return "precision": precision, "recall": recall, "f-score": f_score

In this function, you separate reviews and their labels and then use a generator expression to tokenize each of your evaluation reviews, preparing them to be passed in to textcat. The generator expression is a nice trick recommended in the spaCy documentation that allows you to iterate through your tokenized reviews without keeping every one of them in memory.

You then use the score et true_label to determine true or false positives and true or false negatives. You then use those to calculate precision, recall, and f-score. Now all that’s left is to actually call evaluate_model():

def train_model(training_data: list, test_data: list, iterations: int = 20):
    # Previously seen code omitted for brevity.
        # Training loop
        impression("Beginning training")
        impression("LosstPrecisiontRecalltF-score")
        batch_sizes = compounding(
            4.0, 32.0, 1.001
        )  # A generator that yields infinite series of input numbers
        for je dans range(iterations):
            loss = 
            random.shuffle(training_data)
            batches = minibatch(training_data, size=batch_sizes)
            for batch dans batches:
                text, labels = zip(*batch)
                nlp.update(
                    text,
                    labels,
                    drop=0,2,
                    sgd=optimizer,
                    losses=loss
                )
            avec textcat.model.use_params(optimizer.averages):
                evaluation_results = evaluate_model(
                    tokenizer=nlp.tokenizer,
                    textcat=textcat,
                    test_data=test_data
                )
                impression(
                    F"loss[[[['textcat']tevaluation_results[[[['precision']"
                    F"tevaluation_results[[[['recall']"
                    F"tevaluation_results[[[['f-score']"
                )

Here you add a print statement to help organize the output from evaluate_model() and then call it with the .use_params() context manager in order to use the model in its current state. You then call evaluate_model() and print the results.

Once the training process is complete, it’s a good idea to save the model you just trained so that you can use it again without training a new model. After your training loop, add this code to save the trained model to a directory called model_artifacts located within your working directory:

# Save model
avec nlp.use_params(optimizer.averages):
    nlp.to_disk("model_artifacts")

This snippet saves your model to a directory called model_artifacts so that you can make tweaks without retraining the model. Your final training function should look like this:

def train_model(
    training_data: list,
    test_data: list,
    iterations: int = 20
) -> None:
    # Build pipeline
    nlp = spacy.load("en_core_web_sm")
    si "textcat" ne pas dans nlp.pipe_names:
        textcat = nlp.create_pipe(
            "textcat", config="architecture": "simple_cnn"
        )
        nlp.add_pipe(textcat, last=True)
    else:
        textcat = nlp.get_pipe("textcat")

    textcat.add_label("pos")
    textcat.add_label("neg")

    # Train only textcat
    training_excluded_pipes = [[[[
        pipe for pipe dans nlp.pipe_names si pipe != "textcat"
    ]
    avec nlp.disable_pipes(training_excluded_pipes):
        optimizer = nlp.begin_training()
        # Training loop
        impression("Beginning training")
        impression("LosstPrecisiontRecalltF-score")
        batch_sizes = compounding(
            4.0, 32.0, 1.001
        )  # A generator that yields infinite series of input numbers
        for je dans range(iterations):
            impression(F"Training iteration je")
            loss = 
            random.shuffle(training_data)
            batches = minibatch(training_data, size=batch_sizes)
            for batch dans batches:
                text, labels = zip(*batch)
                nlp.update(text, labels, drop=0,2, sgd=optimizer, losses=loss)
            avec textcat.model.use_params(optimizer.averages):
                evaluation_results = evaluate_model(
                    tokenizer=nlp.tokenizer,
                    textcat=textcat,
                    test_data=test_data
                )
                impression(
                    F"loss[[[['textcat']tevaluation_results[[[['precision']"
                    F"tevaluation_results[[[['recall']"
                    F"tevaluation_results[[[['f-score']"
                )

    # Save model
    avec nlp.use_params(optimizer.averages):
        nlp.to_disk("model_artifacts")

In this section, you learned about training a model and evaluating its performance as you train it. You then built a function that trains a classification model on your input data.

Classifying Reviews

Now that you have a trained model, it’s time to test it against a real review. For the purposes of this project, you’ll hardcode a review, but you should certainly try extending this project by reading reviews from other sources, such as files or a review aggregator’s API.

The first step with this new function will be to load the previously saved model. While you could use the model in memory, loading the saved model artifact allows you to optionally skip training altogether, which you’ll see later. Here’s the test_model() signature along with the code to load your saved model:

def test_model(input_data: str=TEST_REVIEW):
    #  Load saved trained model
    loaded_model = spacy.load("model_artifacts")

In this code, you define test_model(), which includes the input_data parameter. You then load your previously saved model.

The IMDB data you’re working with includes an unsup directory within the training data directory that contains unlabeled reviews you can use to test your model. Here’s one such review. You should save it (or a different one of your choosing) in a TEST_REVIEW constant at the top of your file:

import os
import random
import spacy
de spacy.util import minibatch, compounding

TEST_REVIEW = """
Transcendently beautiful in moments outside the office, it seems almost
sitcom-like in those scenes. When Toni Colette walks out and ponders
life silently, it's gorgeous.

The movie doesn't seem to decide
whether it's slapstick, farce, magical realism, or drama, but the best of it doesn't matter. (The worst is sort of tedious - like Office Space with less humor.) """

Next, you’ll pass this review into your model to generate a prediction, prepare it for display, and then display it to the user:

def test_model(input_data: str = TEST_REVIEW):
    #  Load saved trained model
    loaded_model = spacy.load("model_artifacts")
    # Generate prediction
    parsed_text = loaded_model(input_data)
    # Determine prediction to return
    si parsed_text.cats[[[["pos"] > parsed_text.cats[[[["neg"]:
        prediction = "Positive"
        score = parsed_text.cats[[[["pos"]
    else:
        prediction = "Negative"
        score = parsed_text.cats[[[["neg"]
    impression(
        F"Review text: input_datanPredicted sentiment: prediction"
        F"tScore: score"
    )

In this code, you pass your input_data into your loaded_model, which generates a prediction in the cats attribute of the parsed_text variable. You then check the scores of each sentiment and save the highest one in the prediction variable.

You then save that sentiment’s score to the score variable. This will make it easier to create human-readable output, which is the last line of this function.

You’ve now written the load_data(), train_model(), evaluate_model(), et test_model() les fonctions. That means it’s time to put them all together and train your first model.

Connecting the Pipeline

So far, you’ve built a number of independent functions that, taken together, will load data and train, evaluate, save, and test a sentiment analysis classifier in Python.

There’s one last step to make these functions usable, and that is to call them when the script is run. You’ll use the if __name__ == "__main__": idiom to accomplish this:

si __name__ == "__main__":
    train, test = load_training_data(limit=2500)
    train_model(train, test)
    impression("Testing model")
    test_model()

Here you load your training data with the function you wrote in the Loading and Preprocessing Data section and limit the number of reviews used to 2500 total. You then train the model using the train_model() function you wrote in Training Your Classifier and, once that’s done, you call test_model() to test the performance of your model.

What did your model predict? Do you agree with the result? What happens if you increase or decrease the limit parameter when loading the data? Your scores and even your predictions may vary, but here’s what you should expect your output to look like:

$ python pipeline.py
Training model
Beginning training
Loss    Precision       Recall  F-score
11.293997120810673      0.7816593886121546      0.7584745762390477      0.7698924730851658
1.979159922178951       0.8083333332996527      0.8220338982702527      0.8151260503859189
[...]
0.000415042785704145    0.7926829267970453      0.8262711864056664      0.8091286306718204
Testing model
Review text:
Transcendently beautiful in moments outside the office, it seems almost
sitcom-like in those scenes. When Toni Colette walks out and ponders
life silently, it's gorgeous.

The movie doesn't seem to decide
whether it's slapstick, farce, magical realism, or drama, but the best of it doesn't matter. (The worst is sort of tedious - like Office Space with less humor.) Predicted sentiment: Positive Score: 0.8773064017295837

As your model trains, you’ll see the measures of loss, precision, and recall and the F-score for each training iteration. You should see the loss generally decrease. The precision, recall, and F-score will all bounce around, but ideally they’ll increase. Then you’ll see the test review, sentiment prediction, and the score of that prediction—the higher the better.

You’ve now trained your first sentiment analysis machine learning model using natural language processing techniques and neural networks with spaCy! Here are two charts showing the model’s performance across twenty training iterations. The first chart shows how the loss changes over the course of training:

Loss over training iterations

While the above graph shows loss over time, the below chart plots the precision, recall, and F-score over the same training period:

The precision, recall, and f-score of the model over training iterations

In these charts, you can see that the loss starts high but drops very quickly over training iterations. The precision, recall, and F-score are pretty stable after the first few training iterations. What could you tinker with to improve these values?

Conclusion

Congratulations on building your first sentiment analysis model in Python! What did you think of this project? Not only did you build a useful tool for data analysis, but you also picked up on a lot of the fundamental concepts of natural language processing and machine learning.

In this tutorial, you learned how to:

  • Utilisation natural language processing techniques
  • Use a machine learning classifier to determine the sentiment of processed text data
  • Build your own NLP pipeline with spaCy

You now have the basic toolkit to build more models to answer any research questions you might have. If you’d like to review what you’ve learned, then you can download and experiment with the code used in this tutorial at the link below:

What else could you do with this project? See below for some suggestions.

Next Steps With Sentiment Analysis and Python

This is a core project that, depending on your interests, you can build a lot of functionality around. Here are a few ideas to get you started on extending this project:

  • The data-loading process loads every review into memory during load_data(). Can you make it more memory efficient by using generator functions instead?

  • Rewrite your code to remove stop words during preprocessing or data loading. How does the mode performance change? Can you incorporate this preprocessing into a pipeline component instead?

  • Use a tool like Click to generate an interactive command-line interface.

  • Deploy your model to a cloud platform like AWS and wire an API to it. This can form the basis of a web-based tool.

  • Explore the configuration parameters pour le textcat pipeline component and experiment with different configurations.

  • Explore different ways to pass in new reviews to generate predictions.

  • Parametrize options such as where to save and load trained models, whether to skip training or train a new model, and so on.

This project uses the Large Movie Review Dataset, which is maintained by Andrew Maas. Thanks to Andrew for making this curated dataset widely available for use.