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.
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. [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.
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.
Vanila LSTM est la structure LSTM la plus basique (classique) et est un modèle avec les caractéristiques suivantes.
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.
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.
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é **]
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))
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.
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')
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))
C'était 78%. Ce nombre est aussi précis que les autres implémentations de kaggle DL.
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.
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. ..
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.
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.
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.
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.
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