[PYTHON] Avec LSTM + Embedding, j'ai réaffirmé l'importance du prétraitement en PNL tout en créant un modèle pour discriminer les émotions des tweets.

motivation

Il y a beaucoup de connexions, et j'ai décidé de résoudre le problème de classification de texte, qui classifie "La réponse de l'élève est-elle une médaille d'or, une médaille d'argent, une médaille de bronze ou hors de portée?"

En pratique, je n'avais pas tellement résolu la tâche de traitement du langage naturel, alors j'ai décidé de résoudre le problème de "l'analyse des émotions du sentiment Analayis" qui était passionnant chez Kaggle et de le pratiquer.

À travers divers essais et erreurs, j'ai trouvé que le prétraitement (traitement de texte) est important pour les tâches de traitement du langage naturel (plus que d'autres tâches), je vais donc le résumer dans cet article.

Résumé des données

Des étudiants et des praticiens du monde entier se disputent leurs capacités, et l'ensemble de données «Ensemble de données Sentiment140 avec 1,6 million de tweets» est partagé à partir du site du concours d'analyse de données en ligne appelé Kaggle, alors utilisez ces données. Faire.

Ce ne sont pas les données utilisées dans le concours, mais un ensemble de données partagé par les bénévoles de Kaggle, "Veuillez l'utiliser pour une analyse émotionnelle." Avec 1,6 million de tweets, c'est un ensemble de données assez riche. Voici un tableau avec un résumé des données. image.png [Cliquez ici pour l'ensemble de données]: https://www.kaggle.com/kazanova/sentiment140

Lorsqu'une tâche reçoit un texte (tweet), ce tweet est-il positif? C'est un problème de classification binaire qui prédit s'il est négatif.

Stratégie de modélisation

Nous avons adopté la méthode consistant à "fixer l'architecture du modèle et modifier le prétraitement de diverses manières". (En fait, j'ai essayé diverses choses) Dans cet article, je donnerai la priorité à la clarté et la partagerai brièvement avec le flux suivant. image.png

Vanila LSTM est la structure LSTM la plus basique (classique) et est un modèle avec les caractéristiques suivantes.

  1. Une couche cachée (couche d'unité LSTM)
  2. Une couche dense pour la sortie

Le code (API séquentielle Keras) est le suivant.

model = Sequential()
model.add(LSTM(32, activation='tanh', input_shape=(n_steps, n_features)))
model.add(Dense(2,activation='softmax'))
model.compile(loss = 'categorical_crossentropy', optimizer='adam',metrics = ['accuracy'])

[Référence: https://machinelearningmastery.com/how-to-develop-lstm-models-for-time-series-forecasting/]

Maintenant, à partir de ce qui suit, je vais partager sur la mise en œuvre réelle.

Tout d'abord, importez les bibliothèques et les données nécessaires
from collections import defaultdict, Counter
import time
import re
import string
import pandas as pd

import nltk
from nltk.corpus import stopwords 
from keras.preprocessing.sequence import pad_sequences
from keras.preprocessing.text import one_hot
from sklearn.model_selection import train_test_split

from keras.models import Sequential
from keras.layers import Embedding,LSTM, Dense
from keras.callbacks import EarlyStopping

df = pd.read_csv("twitter_sentiment_kaggle.csv", encoding="latin-1",header=0, 
                 names=['sentiment','id','date','flag','user','text'], usecols=["id", "sentiment", "text"])
print(df.head(2))

Je ne pense pas qu'il soit nécessaire d'expliquer ici, alors je vais passer ensuite.

ÉTAPE 1: Prétraitement pour nettoyer la version 1 des tweets

Que signifie nettoyer un tweet? En d'autres termes, il s'agit de «supprimer les lettres et les symboles qui interfèrent avec l'apprentissage».

Pour cette tâche, le modèle doit "lire (lier) les émotions des tweets", de sorte que les informations "@username" et "URL" se mettent en travers du chemin.

Dans les tâches NLP, un tel "travail pour supprimer des symboles et des caractères que vous ne voulez pas que le modèle apprenne" est généralement appelé "prétraitement".

Eh bien, le prétraitement que j'ai essayé en premier cette fois est énuméré ci-dessous. Il n'y a rien de spécial et tous sont des pré-traitements de base.

[** Prétraitement effectué **]

  1. Réduisez tous les mots
  2. Supprimez l'URL
  3. Encoder les pictogrammes
  4. Remplacez les symboles inutiles pour la modélisation par le blanc "" - @usernames --Autres que les nombres et les alphabets --Omettez trois caractères consécutifs ou plus à deux (par exemple, "awwwww" ---> "aww")
  5. Tokenize le tweet et supprimez les mots vides et la ponctuation
def clean_text(text_data):  
    #Définition du codage, des expressions régulières, des mots vides, de la ponctuation--->Les bénévoles le définissent sur le net, alors empruntons-le.
    URL_PATTERN = r"((http://)[^ ]*|(https://)[^ ]*|( www\.)[^ ]*)"
    
    EMOJI_ENCODER_DICT = {':)': 'smile', ':-)': 'smile', ':))': 'smile', ';d': 'wink', ':-E': 'vampire', ':(': 'sad', ':-(': 'sad', ':-<': 'sad', ':P': 'raspberry', ':O': 'surprised',
                          ':-@': 'shocked', ':@': 'shocked',':-$': 'confused', ':\\': 'annoyed', ':#': 'mute', ':X': 'mute', ':^)': 'smile', ':-&': 'confused', 
                          '$_$': 'greedy','@@': 'eyeroll', ':-!': 'confused', ':-D': 'smile', ':-0': 'yell', 'O.o': 'confused','<(-_-)>': 'robot', 'd[-_-]b': 'dj', 
                          ":'-)": 'sadsmile', ';)': 'wink', ';-)': 'wink', 'O:-)': 'angel','O*-)': 'angel','(:-D': 'gossip', '=^.^=': 'cat'}
    
    USER_NAME_PATTERN = r'@[^\s]+'
    NON_ALPHA_PATTERN = r"[^A-Za-z0-9]"
    
    SEQUENCE_DETECT_PATTERN = r"(.)\1\1+"
    SEQUENCE_REPLACE_PATTERN = r"\1\1"
    
    ENGLISH_STOPWORDS = stopwords.words('english') 
    PUNCTUATIONS = string.punctuation.split()
    
    ###############################Pré-traiter les tweets########################################
    clean_tweets = []
    for each_tweet in text_data:
        #Rendre toutes les lettres plus basses
        each_tweet = each_tweet.lower()
        
        #Effacer l'URL
        each_tweet = re.sub(URL_PATTERN, "", each_tweet).strip()
        
        #Omettre 3 caractères consécutifs ou plus à 2
        each_tweet = re.sub(SEQUENCE_DETECT_PATTERN, SEQUENCE_REPLACE_PATTERN, each_tweet)
        
        #Encoder les pictogrammes
        for key in EMOJI_ENCODER_DICT.keys():
            each_tweet = each_tweet.replace(key, " EMOJI " + EMOJI_ENCODER_DICT[key])
        
        ###Supprimer divers symboles qui ne sont pas nécessaires pour la modélisation###
        # ”@Supprimer les noms d’utilisateur »
        each_tweet = re.sub(USER_NAME_PATTERN, "", each_tweet)
        
        #Supprimer tout sauf les nombres et les alphabets
        each_tweet = re.sub(NON_ALPHA_PATTERN, " ", each_tweet)
        
        ###Tokenize tweets(L'élément est une liste de chaque mot,)Et supprimez les mots vides et la ponctuation###
        tokenizer = nltk.TweetTokenizer(preserve_case=False, strip_handles=True,  reduce_len=True)
        tweet_tokens = tokenizer.tokenize(each_tweet)

        #Suppression des mots vides et de la ponctuation
        clean_tweet_sentence = ' '
        for word in tweet_tokens: #Regardez chaque mot
            if (word not in ENGLISH_STOPWORDS and  word not in PUNCTUATIONS):
                clean_tweet_sentence += (word+' ')
                
        clean_tweets.append(clean_tweet_sentence)
    return clean_tweets
#########################################################################################
#Nettoyer les tweets
t = time.time()
clean_tweets_list = clean_text(df["text"])
print(f'Les tweets sont devenus magnifiques.')
print(f'Temps d'exécution du code: {round(time.time()-t)} seconds')

#Tweets nettoyés'clean_tweet'Pour ajouter à une nouvelle colonne
df["clean_text"] = clean_tweets_list

#Voir les résultats
print(df[["text", "clean_text"]].head(2))

Résultat après le nettoyage: les URL et @Username ont été correctement supprimés.

image.png

Étape 2: codez les caractères

Après avoir nettoyé le tweet à l'ÉTAPE 1 ci-dessus, nous traiterons le tweet pour entraîner le modèle. En d'autres termes, chaque caractère (mot anglais) contenu dans le tweet est représenté par un nombre. Puisque le modèle ne peut reconnaître que des nombres, c'est une tâche nécessaire, n'est-ce pas?

Le travail spécifique effectué est résumé ci-dessous.

  1. ** Hash (One Hot Encoding) chaque caractère contenu dans le tweet ** --Hashing signifie "attribuer un numéro d'index à chaque caractère". --Par exemple, "I love LSTM" est converti en [100, 240, 600]. --Ce prétraitement est nécessaire pour exécuter correctement la fonction d'incorporation, qui sera décrite plus loin.
  1. ** Appliquer Padding / Truncating aux tweets hachés ** ――Padding / Truncating signifie simplement "unifier le nombre d'éléments dans la liste". --Par exemple, supposons que deux tweets, "I love LSTM" et "I prefer GRU over LSTM", sont hachés respectivement à [100, 240, 600] et "100,250,900,760,600".
def encode_with_oneHot(text, total_vocab_freq, max_tweet_length):
    #Un encodage et un remplissage à chaud/Effectuer la troncature
    encoded_tweets_oneHot = []
    for each_tweet in text:
        each_encoded_tweet = one_hot(each_tweet, total_vocab_freq)
        encoded_tweets_oneHot.append(each_encoded_tweet)
    each_encoded_tweets_oneHot_pad = pad_sequences(encoded_tweets_oneHot, maxlen=max_tweet_length, 
                                                   padding="post", truncating="post")
    return each_encoded_tweets_oneHot_pad
###################################################################################################
###Encoder des tweets propres###
#Connaître le nombre de fois où un mot apparaît
vocab_dict = defaultdict(int)
for each_t in df["clean_text_after_others"]:
    for w in each_t.split():
        vocab_dict[w] += 1
total_vocab_freq   = len(vocab_dict.keys())#Compter le nombre total de mots

#Comprendre la longueur des phrases
sentence_length_dict = defaultdict(int)
for i, each_t in enumerate(df["clean_text_after_others"]):
    sentence_length_dict[i] = len(each_t.split())
max_tweet_length = max(sentence_length_dict.values())#Comptez la plus longue phrase

#Courir
t = time.time()
one_hot_texts = encode_with_oneHot(df["clean_text"], total_vocab_freq, max_tweet_length)
print(f'Un encodage à chaud des tweets est terminé')
print(f'Temps d'exécution du code: {round(time.time()-t)} seconds')
Résultat de l'exécution: les tweets sont correctement hachés ligne par ligne.

image.png

ÉTAPE 3: Modélisation

Dans STEP2, le tweet a été haché (quantifié = encodage One Hot), il est donc prêt pour la modélisation. Voici le partage de code.

embedding_length = 32
model = Sequential()
model.add(Embedding(input_dim=total_vocab_freq+1, output_dim=embedding_length, input_length=max_tweet_length, mask_zero=True))
model.add(LSTM(units=32))
model.add(Dense(2,activation='softmax'))
model.compile(loss = 'categorical_crossentropy', optimizer='adam',metrics = ['accuracy'])
print(model.summary())

Comme mentionné précédemment dans "Stratégie de modélisation", il s'agit de la structure LSTM classique, "Vanilla LSTM".

Cependant, j'ai ajouté une couche Embedding devant la couche LSTM. La raison en est que les tweets hachés seuls ne peuvent pas lire le sens sémantique.

Cette incorporation est une fonction qui renvoie une matrice de n'importe quelle dimension avec le mot haché comme clé. Chaque élément de cette matrice reçoit une signification sémantique.

En d'autres termes, qu'est-ce que cela signifie? Les mots peuvent être calculés comme "roi-homme + femme = reine". Comme c'est important, je vais le répéter, mais puisque chaque mot reçoit une représentation matricielle (Sémantique), une telle opération est possible.

Le LSTM peut désormais apprendre les relations entre les mots.

En ce qui concerne l'intégration, l'article de @ 9ryuuuuu est très facile à comprendre, veuillez donc vous y référer. https://qiita.com/9ryuuuuu/items/e4ee171079ffa4b87424

Maintenant, formons les données et commençons à entraîner le modèle.

#Mise en forme des données
y = pd.get_dummies(df["sentiment"]).values
X_train, X_test, y_train, y_test = train_test_split(one_hot_texts, y, test_size = 0.2, random_state = 123, stratify=y)
print(X_train.shape,y_train.shape)
print(X_test.shape,y_test.shape)

#Commencer à apprendre
batch_size = 256
callback = [EarlyStopping(monitor='val_loss', patience=2,  verbose=1)]
hist = model.fit(X_train, y_train, epochs=5, batch_size=batch_size, callbacks=callback, verbose=1, validation_split=0.1)

#Afficher l'exactitude des données de validation
import numpy as np
print("Validation Accuracy:",round(np.mean(hist.history['val_accuracy']), 4))

Confirmation de l'exactitude des données de vérification

C'était 78%. Ce nombre est aussi précis que les autres implémentations de kaggle DL.

image.png

L'apprentissage automatique, qui utilise tf-idf + n-grammes comme entrée, est un peu moins précis. Dans le domaine d'observation, il est d'environ 68 à 78%.

Ainsi, avec ce modèle, la valeur de l'écart est d'environ 55 à 60. (Devine) [Référence: https://www.kaggle.com/kazanova/sentiment140/notebooks]

À partir de là, mettez simplement à jour le prétraitement et essayez de le mettre en œuvre à nouveau, sans changer l'architecture du modèle.

Il y a eu deux améliorations majeures.

Amélioration 1

La première amélioration est la gestion des «mots d'arrêt». Les mots d'arrêt sont des mots tels que «non», «non» et «en haut», qui sont un groupe de mots qui sont habituellement effacés dans le monde du traitement du langage naturel. Cependant, comme le montre l'exemple ci-dessous, suite à la suppression de «non», la signification du tweet était très différente, j'ai donc opté pour le prétraitement sans supprimer les mots vides. ..

image.png

Point d'amélioration 2

La deuxième amélioration concerne "le nombre de mots dans le tweet". Après le prétraitement, il y a un tweet avec un seul mot, comme le tweet n'est que "play", j'ai donc supprimé ces données.

Résultats après amélioration

La précision s'est améliorée. Le résultat est 82% des données de validation. C'est le résultat de l'implémentation DL supérieure (valeur de déviation 60 ~ 65?), Donc je suis content. image.png

Planifiez à partir de maintenant

Aux niveaux Bronze Master et Grand Master, j'obtiens 88 à 92% des résultats, donc Il y a encore place à amélioration pour moi. Je pensais que de nombreux Grands Maîtres l'avaient implémenté avec CNN + LSTM.

Cependant, dans cette tâche, j'ai une politique de prétraitement minutieux, et je pense que la précision atteindra 90%.

En effet, de nombreux domaines doivent encore être améliorés par un prétraitement. Par exemple, le prétraitement suivant. image.png

Mais c'est en fait assez difficile, n'est-ce pas? Est-ce l'anglais pour chaque tweet? Je dois porter un jugement, mais l'exactitude de la bibliothèque qui rend le jugement est si mauvaise qu'elle est inutile. image.png

Il s'agit de la phrase qui est produite par la fonction langdetect.detect, qui est jugée «pas en anglais», mais elle contient clairement une phrase en anglais.

L'automatisation est donc difficile, que dois-je faire à propos de ce prétraitement? En attente. Peut-être que Kaggler trouve également cela difficile, personne (à mon avis) n'a fait ce prétraitement.

Je souhaite donc continuer la vérification et mettre à jour l'article.

Merci d'avoir regardé jusqu'à présent.

Recommended Posts

Avec LSTM + Embedding, j'ai réaffirmé l'importance du prétraitement en PNL tout en créant un modèle pour discriminer les émotions des tweets.
J'ai fait une erreur en récupérant la hiérarchie avec MultiIndex of pandas
J'ai essayé de créer un modèle avec l'exemple d'Amazon SageMaker Autopilot
J'ai essayé de faire quelque chose comme un chatbot avec le modèle Seq2Seq de TensorFlow
Le concept de référence en Python s'est effondré un instant, j'ai donc expérimenté un peu.
L'histoire de la création d'un «espace de discussion sur l'esprit et le temps» exclusivement pour les ingénieurs de l'entreprise
J'ai écrit un doctest dans "J'ai essayé de simuler la probabilité d'un jeu de bingo avec Python"
Mesurer l'importance des entités avec un outil de forêt aléatoire
Remarques sur l'intégration du langage de script dans les scripts bash
J'ai fait une fonction pour vérifier le modèle de DCGAN
Analysez le modèle thématique pour devenir romancier avec GensimPy3
J'ai essayé de prédire le nombre de personnes infectées au niveau national de la nouvelle corona avec un modèle mathématique
L'histoire de la création d'un Bot qui affiche les membres actifs dans un canal spécifique de Slack avec Python
Un débutant qui programme depuis 2 mois a tenté d'analyser le PIB réel du Japon en séries chronologiques avec le modèle SARIMA.
Utilisez le vecteur appris par word2vec dans la couche Embedding de LSTM
[Introduction à StyleGAN] J'ai joué avec "The Life of a Man" ♬
J'ai recherché une carte similaire de Hearthstone avec Deep Learning
J'ai créé beaucoup de fichiers pour la connexion RDP avec Python
L'histoire de la création d'un pilote standard pour db avec python.
J'ai essayé de faire une étrange citation pour Jojo avec LSTM
L'histoire de la recherche d'un magasin BOT (AI LINE BOT) pour Go To EAT dans la préfecture de Chiba (1)