Explorer le prédicteur de mot suivant! – Dhruvil Shah
Comment le clavier de votre téléphone sait-il ce que vous souhaitez saisir ensuite? La prédiction de langue est une application de traitement du langage naturel – PNL qui s’intéresse à la prédiction du texte donné dans le texte précédent. Les réponses automatiques ou suggérées sont des types populaires de prédiction linguistique. La première étape vers la prédiction de la langue est la sélection d’un modèle de langue. Cet article montre différentes approches que l’on peut adopter pour créer le Next Word Predictor – que vous avez dans des applications comme Whatsapp ou toute autre application de messagerie.
Il existe généralement deux modèles que vous pouvez utiliser pour développer le prédicteur / prédicteur de mot suivant: 1) le modèle N-grammes 2) la mémoire à court terme (LSTM). Nous allons passer en revue chaque modèle et conclure lequel est le meilleur.
Si vous descendez le n-grammes chemin, vous devrez vous concentrer sur les «chaînes de Markov» pour prédire la probabilité de chaque mot ou caractère suivant en fonction du corpus de formation. Vous trouverez ci-dessous l’extrait de code de cette approche. Dans cette approche, la longueur de séquence de un est utilisée pour prédire le mot suivant. Cela signifie que nous prédirons le mot suivant donné dans le mot précédent.
Importation des modules nécessaires: word_tokenize, defaultdict, Compteur
import re
from nltk.tokenize import word_tokenize
from collections import defaultdict, Counter
Création de la classe MarkovChain contenant des méthodes:
class MarkovChain:
def __init__(self):
self.lookup_dict = defaultdict(list)def _preprocess(self, string):
cleaned = re.sub(r’W+’, ' ', string).lower()
tokenized = word_tokenize(cleaned)
return tokenizeddef add_document(self, string):
preprocessed_list = self._preprocess(string)
pairs = self.__generate_tuple_keys(preprocessed_list)
for pair in pairs:
self.lookup_dict[pair[0]].append(pair[1])def __generate_tuple_keys(self, data):
if len(data) < 1:
return
for i in range(len(data) - 1):
yield [ data[i], data[i + 1] ]def generate_text(self, string):
if len(self.lookup_dict) > 0:
print("Next word suggestions:", Counter(self.lookup_dict[string]).most_common()[:3])
return
Lorsque nous créons une instance de la classe ci-dessus, un dictionnaire par défaut est initialisé. Il existe une méthode pour prétraiter le corpus de formation que nous ajoutons via le .add_document () méthode. Lorsque nous ajoutons un document à l’aide du .add_document () , des paires sont créées pour chaque mot unique. Comprenons cela avec un exemple: si notre corpus de formation était «Comment allez-vous? Combien de jours depuis notre dernière rencontre? Comment vont tes parents? » notre dictionnaire de recherche, après prétraitement et ajout du document, serait:
{ 'how': ['are', 'many', 'are'], 'are': ['you', 'your'],
'you': ['how'], 'many': ['days'], 'days': ['since'],
'since': ['we'], 'we': ['last'], 'last': ['met'], 'met': ['how'],
'your': ['parents']}
Chaque mot unique en tant que clé et sa liste de mots suivants en tant que valeur sont ajoutés à notre dictionnaire de recherche lookup_dict.
Lorsque nous entrons un mot, il sera recherché dans le dictionnaire et les mots les plus courants dans sa liste de mots suivants seront suggérés. Ici, le nombre maximum de suggestions de mots est de trois, comme nous l’avons dans nos claviers. Vous trouverez ci-dessous l’exemple en cours de cette approche pour la longueur d’une séquence. La sortie contient des mots suggérés et leur fréquence respective dans la liste. Lorsque nous entrons le mot «comment», il est recherché dans le dictionnaire et les trois mots les plus courants de sa liste de mots suivants sont choisis. Ici, le mot «plusieurs» apparaît 1531 fois, ce qui signifie que la séquence de mots «combien» apparaît 1531 fois dans le corpus de formation.
De plus, dans la méthode expliquée ci-dessus, nous pouvons avoir une longueur de séquence de 2 ou 3 ou plus. Pour cela, nous devrons changer une partie du code ci-dessus.
class MarkovChain:
"""
Previous code continued
"""
def add_document(self, string):
preprocessed_list = self._preprocess(string)
pairs = self.__generate_tuple_keys(preprocessed_list)
for pair in pairs:
self.lookup_dict[pair[0]].append(pair[1])
pairs2 = self.__generate_2tuple_keys(preprocessed_list)
for pair in pairs2:
self.lookup_dict[tuple([pair[0], pair[1]])].append(pair[2])
pairs3 = self.__generate_3tuple_keys(preprocessed_list)
for pair in pairs3:
self.lookup_dict[tuple([pair[0], pair[1], pair[2]])].append(pair[3])def __generate_tuple_keys(self, data):
if len(data) < 1:
return
for i in range(len(data) - 1):
yield [ data[i], data[i + 1] ]#to add two words tuple as key and the next word as value
def __generate_2tuple_keys(self, data):
if len(data) < 2:
return
for i in range(len(data) - 2):
yield [ data[i], data[i + 1], data[i+2] ]#to add three words tuple as key and the next word as value
def __generate_3tuple_keys(self, data):
if len(data) < 3:
return
for i in range(len(data) - 3):
yield [ data[i], data[i + 1], data[i+2], data[i+3] ]def oneword(self, string):
def twowords(self, string):
return Counter(self.lookup_dict[string]).most_common()[:3]
suggest = Counter(self.lookup_dict[tuple(string)]).most_common()[:3]
if len(suggest)==0:
return self.oneword(string[-1])
return suggestdef threewords(self, string):
suggest = Counter(self.lookup_dict[tuple(string)]).most_common()[:3]
if len(suggest)==0:
return self.twowords(string[-2:])
return suggestdef morewords(self, string):
def generate_text(self, string):
return self.threewords(string[-3:])
if len(self.lookup_dict) > 0:
tokens = string.split(" ")
if len(tokens)==1:
print("Next word suggestions:", self.oneword(string))
elif len(tokens)==2:
print("Next word suggestions:", self.twowords(string.split(" ")))
elif len(tokens)==3:
print("Next word suggestions:", self.threewords(string.split(" ")))
elif len(tokens)>3:
print("Next word suggestions:", self.morewords(string.split(" ")))
return
Brisons le code. Les méthodes .__ generate_2tuple_keys () et .__ generate_3tuple_keys () sont de stocker les séquences de longueur deux et trois respectivement et leur liste de mots suivants. Maintenant, notre code a la force de prédire les mots sur la base de jusqu’à trois mots précédents. Voyons notre nouveau dictionnaire de recherche lookup_dict pour l’exemple: «Comment ça va? Combien de jours depuis notre dernière rencontre? Comment vont tes parents? »
{
"""
Same as before
"""
('how', 'are'): ['you', 'your'],
...
('how', 'many'): ['days'],
('many', 'days'): ['since'],
...
('how', 'are', 'you'): ['how'],
...
('how', 'many', 'days'): ['since'],
...
}
De nouvelles paires sont ajoutées au dictionnaire par rapport à la précédente. La classe MarkovChain que nous avons créé ci-dessus gère n’importe quelle longueur d’une séquence que nous avons entrée. Si nous saisissons un mot, la méthode ‘un mot’ sera appelé et ce sera le même que le précédent. Pour la longueur d’entrée deux ou trois, les méthodes «deux mots‘ et ‘trois mots»Seront appelés respectivement. Ce que ces méthodes font, c’est qu’elles recherchent les trois mots les plus courants du dictionnaire de recherche, étant donné les mots d’entrée. Lorsque les mots saisis sont supérieurs à quatre, les trois derniers seront traités. Lorsqu’il rencontre un mot inconnu, ce mot sera ignoré et le reste de la chaîne sera traité. Regardez la figure ci-dessous pour dissiper tout doute. Ce chiffre est basé sur un corpus de formation différent. Le côté gauche montre l’entrée et le côté droit, la sortie.
Vous trouverez ci-dessous le résultat de cette approche:
La sortie ci-dessus est basée sur un ensemble de données différent et plus grand qui a été utilisé pour cette approche. Le lien de GitHub pour cette approche est cette. Vous pouvez y trouver le code ci-dessus.
Limites:
Les chaînes de Markov n’ont pas de mémoire. L’adoption de cette approche présente de nombreuses limites. Prenons un exemple: «J’ai mangé tellement de grillades…» Le mot suivant «sandwichs» sera prédit en fonction du nombre de fois où des «sandwichs grillés» sont apparus ensemble dans les données de formation. Comme nous recevons des suggestions basées uniquement sur la fréquence, il existe de nombreux scénarios où cette approche pourrait échouer.
Une approche plus avancée, utilisant un modèle de langage neuronal, consiste à utiliser la mémoire à court terme à long terme (LSTM). Le modèle LSTM utilise l’apprentissage en profondeur avec un réseau de «cellules» artificielles qui gèrent la mémoire, ce qui les rend mieux adaptées à la prédiction de texte que les réseaux de neurones traditionnels et d’autres modèles.
« L’herbe est toujours … »
Le mot suivant est simplement «vert» et pourrait être prédit par la plupart des modèles et réseaux.
Mais pour la phrase, « C’est l’hiver et il y a eu peu de soleil, l’herbe est toujours … », nous devons connaître le contexte plus loin dans la phrase pour prédire le mot suivant « brun ».
Les RNN standard et d’autres modèles de langage deviennent moins précis lorsque l’écart entre le contexte et le mot à prédire augmente. Voici quand LSTM est utilisé pour résoudre le problème de dépendance à long terme, car il dispose de cellules de mémoire pour se souvenir du contexte précédent. Vous pouvez en savoir plus sur les réseaux LSTM ici.
Commençons par coder et définir notre modèle LSTM. Dans la construction de notre modèle, tout d’abord, une couche d’intégration, deux couches LSTM empilées de 50 unités chacune sont utilisées.
Keras propose une couche d’intégration qui peut être utilisée pour les réseaux de neurones sur les données texte. La couche d’intégration est initialisée avec des poids aléatoires et apprend les incorporations pour tous les mots du jeu de données d’apprentissage. Il requiert les données d’entrée sous une forme codée entière. Cette étape de préparation des données peut être effectuée à l’aide de API Tokenizer également fourni par Keras. En savoir plus sur l’incorporation de la couche ici.
Les deux couches LSTM sont suivies de deux couches entièrement connectées ou denses. La première couche a 50 unités et la deuxième couche dense est notre couche de sortie (softmax) et a le nombre d’unités égal à la taille du vocabulaire. Comme pour chaque entrée, le modèle prédira le prochain mot de notre vocabulaire en fonction de la probabilité. L’entropie croisée catégorielle est utilisée comme fonction de perte.
Prétraitement des données:
Pour l’entrée dans la couche Embedding, nous devons d’abord utiliser Tokenizer à partir de keras.processing.text pour encoder nos chaînes d’entrée. Ce que nous faisons dans le prétraitement est simple: nous créons d’abord deséquations. Ensuite, nous l’encodons dans la forme entière à l’aide du Tokenizer.
from keras.preprocessing.text import Tokenizer
import nltk
from nltk.tokenize import word_tokenize
import numpy as np
import re
from keras.utils import to_categorical
from doc3 import training_doc3cleaned = re.sub(r'W+', ' ', training_doc3).lower()
tokens = word_tokenize(cleaned)
train_len = 4
text_sequences = []for i in range(train_len,len(tokens)):
seq = tokens[i-train_len:i]
text_sequences.append(seq)sequences = {}
count = 1for i in range(len(tokens)):
if tokens[i] not in sequences:
sequences[tokens[i]] = count
count += 1tokenizer = Tokenizer()
tokenizer.fit_on_texts(text_sequences)
sequences = tokenizer.texts_to_sequences(text_sequences)#vocabulary size increased by 1 for the cause of padding
vocabulary_size = len(tokenizer.word_counts)+1
n_sequences = np.empty([len(sequences),train_len], dtype='int32')for i in range(len(sequences)):
n_sequences[i] = sequences[i]train_inputs = n_sequences[:,:-1]
train_targets = n_sequences[:,-1]
train_targets = to_categorical(train_targets, num_classes=vocabulary_size)
seq_len = train_inputs.shape[1]
Comprenons ce qui se passe dans le code ci-dessus avec un exemple: «Comment ça va? Combien de jours depuis notre dernière rencontre? Comment vont tes parents? ». Nous nettoyons d’abord notre corpus et le symbolisons à l’aide de Expressions régulières, et word_tokenize de la bibliothèque nltk. Que fait le «Séquences» dictionnaire faire? Ci-dessous est le «Séquences» dictionnaire avant d’utiliser le tokenizer.
{'how': 1, 'are': 2, 'you': 3, 'many': 4, 'days': 5, 'since': 6, 'we': 7, 'last': 8, 'met': 9, 'your': 10, 'parents': 11}
Notre «Text_sequences» liste conserve toutes les séquences dans notre corpus de formation et ce serait:
[['how', 'are', 'you', 'how'], ['are', 'you', 'how', 'many'], ['you', 'how', 'many', 'days'], ['how', 'many', 'days', 'since'], ['many', 'days', 'since', 'we'], ['days', 'since', 'we', 'last'], ['since', 'we', 'last', 'met'], ['we', 'last', 'met', 'how'], ['last', 'met', 'how', 'are'], ['met', 'how', 'are', 'your']]
Après avoir utilisé le tokenizer, nous avons les séquences ci-dessus sous la forme codée. Les chiffres ne sont que les index des mots respectifs du «séquences » dictionnaire avant réaffectation.
[[1, 2, 9, 1], [2, 9, 1, 3], [9, 1, 3, 4], [1, 3, 4, 5], [3, 4, 5, 6], [4, 5, 6, 7], [5, 6, 7, 8], [6, 7, 8, 1], [7, 8, 1, 2], [8, 1, 2, 10]]
Une fois que nous avons nos séquences sous forme codée, les données d’apprentissage et les données cibles sont définies en divisant les séquences en étiquettes d’entrée et de sortie. Comme pour cet exemple, nous allons prédire le mot suivant sur la base de trois mots précédents, donc dans la formation, nous utilisons les trois premiers mots comme entrée et le dernier mot comme étiquette qui doit être prédite par le modèle. Notre ‘training_inputs » serait désormais:
[[1 2 9] [2 9 1] [9 1 3] [1 3 4] [3 4 5] [4 5 6] [5 6 7] [6 7 8] [7 8 1] [8 1 2]]
Ensuite, nous convertissons nos étiquettes de sortie en vecteurs un-chaud, c’est-à-dire en combinaisons de 0 et de 1. Vecteurs un-chaud en ‘train_targets » ressemblerait à:
[[0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
...
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]]
Pour la première étiquette cible «comment», l’index était «1» dans le dictionnaire de séquence. Dans la forme codée, vous vous attendriez donc à «1» à la place de l’index 1 dans le premier vecteur unique de «train_targets ».
Remarque: Le code ci-dessus est expliqué pour le texte «Comment allez-vous? Combien de jours depuis notre dernière rencontre? Comment vont tes parents? » pour une explication plus simple. Mais en réalité, un plus grand ensemble de données est utilisé.
Construire le modèle:
Maintenant, nous formons nos Séquentiel modèle comportant 5 couches: une couche d’incorporation, deux couches LSTM et deux couches denses. Dans la couche d’entrée de notre modèle, c’est-à-dire la couche d’intégration, la longueur d’entrée est définie sur la taille d’une séquence qui est 3 pour cet exemple. (Remarque: Nous divisons les données pour les intrants d’entraînement et les objectifs d’entraînement comme 3 à 1, donc lorsque nous entrons dans notre modèle pour la prédiction, nous devrons fournir un vecteur de 3 longueurs.)
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import LSTM
from keras.layers import Embedding
model = Sequential()
model.add(Embedding(vocabulary_size, seq_len, input_length=seq_len))
model.add(LSTM(50,return_sequences=True))
model.add(LSTM(50))
model.add(Dense(50,activation='relu'))
model.add(Dense(vocabulary_size, activation='softmax'))# compiling the network
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
model.fit(train_inputs,train_targets,epochs=500,verbose=1)
Prédire les mots:
Une fois que notre modèle est formé, nous pouvons donner une entrée sous la forme codée et obtenir les trois mots les plus probables du softmax comme indiqué ci-dessous.
from keras.preprocessing.sequence import pad_sequencesinput_text = input().strip().lower()
encoded_text = tokenizer.texts_to_sequences([input_text])[0]
pad_encoded = pad_sequences([encoded_text], maxlen=seq_len, truncating='pre')
print(encoded_text, pad_encoded)for i in (model.predict(pad_encoded)[0]).argsort()[-3:][::-1]:
pred_word = tokenizer.index_word[i]
print("Next word suggestion:",pred_word)
Dans le code ci-dessus, nous utilisons le remplissage parce que nous avons formé notre modèle sur des séquences de longueur 3, donc lorsque nous saisissons 5 mots, le remplissage s’assurera que les trois derniers mots sont pris en entrée dans notre modèle. Que se passe-t-il lorsque nous saisissons moins de 3 mots? Nous n’obtiendrons pas les meilleurs résultats! La même chose se produit lorsque nous saisissons un mot inconnu, car le vecteur unique contient 0 dans l’index de ce mot. Ce que nous pouvons faire à l’avenir est d’ajouter des séquences de longueur 2 (entrées) à 1 (étiquette cible) et 1 (entrée) à 1 (étiquette cible) comme nous l’avons fait ici 3 (entrées) à 1 (étiquette cible) pour le meilleur résultats.
Vous trouverez ci-dessous la sortie finale de notre modèle prédisant les 3 prochains mots sur la base des mots précédents.
La sortie ci-dessus montre la forme vectorielle de l’entrée avec les mots suggérés. [6, 4, 3] est le «Encoded_text» et[[[6, 4, 3]]est le ‘pad_encoded ».
Remarque: Ici, nous divisons nos données en 3 (entrées) en 1 (étiquette cible). Par conséquent, nous devons saisir trois mots.
Le lien de GitHub pour le code ci-dessus est cette. Vous pouvez y trouver le code de l’approche LSTM.
Ci-dessus, nous avons vu que l’approche n-grammes est inférieure à l’approche LSTM car les LSTM ont la mémoire pour se souvenir du contexte plus en arrière dans le corpus de texte. Lors du démarrage d’un nouveau projet, vous voudrez peut-être envisager l’un des cadres pré-formés existants en recherchant sur Internet des implémentations open-source. De cette façon, vous n’aurez pas à repartir de zéro et vous n’aurez pas à vous soucier du processus de formation ou des hyperparamètres.
De nombreux travaux récents dans le domaine du traitement du langage naturel comprennent le développement et la formation de modèles neuronaux afin d’approximer la façon dont nos cerveaux humains exercent vers le langage. Cette approche d’apprentissage en profondeur permet aux ordinateurs d’imiter le langage humain d’une manière beaucoup plus efficace.