[PYTHON] Comment faire une traduction japonais-anglais

Comment faire une traduction japonais-anglais

Nous allons implémenter la traduction japonais-anglais avec tensorflow et keras.

Voici la table des matières de cet article.

  1. Détails de l'environnement et du jeu de données (#env)
  2. [Flux de base](# flux de base)
  3. Prétraitement des données (#preprocess)
  4. [Construire le modèle](#construct model)
  5. Apprentissage
  6. évaluer

Les détails du code sont publiés sur github, veuillez donc vous y référer. Japanese-English_Translation Puisqu'il est enregistré au format .pyinb, il peut être facilement déplacé avec google colab. Je publierai le code lorsque j'ai étudié le traitement du langage naturel il y a longtemps. (Publier un peu organisé)

Nous sommes impatients de vous aider.

Détails de l'environnement et de l'ensemble de données

Environnement matériel gooble colab

Environnement logiciel python3 tensorflow (version2.3.1)

base de données small_parallel_enja

small_parallel_enja est un petit ensemble de données de quelques phrases extraites du corpus Tanaka. Il a été pré-traité et est très facile à utiliser. Étant donné que l'ensemble de données est divisé en données d'entraînement, données de vérification et données de test, il n'est pas nécessaire de le diviser. S'il y a suffisamment de ressources, il peut être possible d'effectuer une vérification croisée en utilisant un mélange de données d'apprentissage et de données de vérification en tant que données d'apprentissage. (Pour ceux qui ont plusieurs GPU dans la cuisine)

Flux de base Nous procéderons selon le flux suivant.

1. Prétraitement des données

2. Construction de modèles

3. Apprentissage

4. Évaluation

Eh bien, c'est normal ww

Prétraitement des données C'est assez simple car j'utilise les données qui ont été prétraitées.

Tokenize utilise l'API keras intégrée à tensorflow. tf.keras.preprocessing.text.Tokenizer

Il est assez facile à utiliser et un exemple est présenté ci-dessous.

tokenizer = tf.keras.preprocessing.text.Tokenizer(oov="<unk>")
tokenizer.fit_on_texts(texts)
sequences = tokenizer.texts_to_sequences(texts)

Créez une instance de tf.keras.preprocessing.text.Tokenizer et Je vais vous dire les mots à utiliser pour cette instance avec fit_on_texts (textes). En faisant cela, vous gérez des mots uniques en interne. Après cela, tout ce que vous avez à faire est de numériser chaque phrase avec tokenizer.texts_to_sequences (textes). textes fait référence à des ensembles de données textuelles. Le format des textes doit être une liste de chaînes.

texts = ["I am Niwaka", "Hello !", .., "Wow !"]

Le code ci-dessus est un exemple du format de texte transmis à l'instance tf.keras.preprocessing.text.Tokenizer.

Comment utiliser tf.keras.preprocessing.text.Tokenizer Vous pouvez le découvrir en cliquant sur le lien ci-dessus.

Afin d'effectuer l'apprentissage par mini-lot, la forme des données dans le mini-lot doit correspondre. Cependant, les données en langage naturel sont généralement de longueur variable </ strong>. Par conséquent, contrairement à d'autres ensembles de données, il doit être conçu.

Il existe deux manières de gérer les données variables.

1.padding 2. Définissez la taille du lot sur 1 (si la longueur de la série chronologique est de plusieurs centaines de niveaux, dois-je l'utiliser?)

Ici, nous utilisons un remplissage de 1. Le remplissage est une méthode pour créer la longueur L en remplissant une valeur spéciale pour les données qui ne correspondent pas à la longueur maximale de la série chronologique dans le mini-lot. Il semble que 0 soit défini comme une valeur spéciale dans tensorflow et keras.

Par exemple, supposons que vous ayez un ensemble de données avec une longueur non uniforme, telle que:

sequences = [
  [12, 45],
  [3, 4, 7],
  [4],
]

La longueur maximale de l'ensemble de données ci-dessus est de 3. Lorsque le remplissage est effectué, ce sera comme suit.

padded_sequences = [
  [12, 45, 0],
  [3, 4, 7],
  [4, 0, 0],
]

Dans tensorflow, le traitement du remplissage des données textuelles est tf.keras.preprocessing.sequence.pad_sequences </ Il est fourni par une API appelée a>.

Pour l'utiliser, ajoutez la longueur à tf.keras.preprocessing.sequence.pad_sequences Donnez simplement le jeu de données que vous souhaitez unifier en entrée. L'argument padding spécifie s'il faut remplir 0 après ou 0 avant. En spécifiant "post", 0 est rempli avec post-remplissage. Je pense que vous pouvez l'utiliser comme vous le souhaitez.

padded_sequences = tf.keras.preprocessing.sequence.pad_sequences(sequences, padding="post")

Vous pouvez maintenant apprendre par mini-batch. Mais il y a un problème ici. Il s'agit de savoir comment le modèle interprète 0. </ strong>

Si possible, vous voulez pouvoir ignorer la valeur spéciale de 0. Sinon, il est peu judicieux d'utiliser un RNN capable de gérer des longueurs variables.

tensorflow fournit une fonctionnalité appelée Masquage </ strong>. Le masquage est une fonction qui ignore la valeur à l'étape spécifiée. Cela permet de traiter collectivement des données de longueur variable. (Les données de longueur variable peuvent être gérées sans utiliser de masquage, mais une valeur spéciale de 0 sera également incluse dans le modèle. C'est désagréable, je voudrais donc l'éviter.)

Pour plus d'informations, veuillez suivre le lien ci-dessous. Sur le lien, des explications détaillées sur l'utilisation de la création et du rembourrage dans tensorflow et keras sont écrites. Masking and padding with Keras

Dans tensorflow, le masquage est activé des manières suivantes:

  1. Ajoutez tf.keras.layers.Masking
  2. Définissez l'argument mask_zero de tf.keras.layers.Embedding sur True.
  3. Passez-le directement au calque qui utilise le masque. (C'est un moyen simple, peut-être que je l'utilise pour les vidéos, etc.)

Ici, nous utilisons 2.

Dans le modèle utilisé cette fois, l'incorporation génère automatiquement un masque, et ce masque est automatiquement propagé au calque suivant.

Construction du modèle Utilisez le modèle Seq2Seq comme modèle. Qu'est-ce que Seq2Seq? Séquence Un modèle qui transforme les données en d'autres données de séquence. Les données de séquence sont ici des données de séries chronologiques.

L'interface de Seq2Seq est

Séquence après conversion= Seq2Seq(Séquence avant la conversion)

est.

Par exemple, supposons que vous saisissiez «Je suis étudiant» dans le modèle Seq2Seq.

"I am student ." = Seq2Seq("Je suis étudiant.")

Seq2Seq se compose de deux modules. Le premier est Encoder </ strong> et le second est Decoder </ strong>. Les données de séquence sont codées par Encoder et produisent des fonctionnalités incompréhensibles pour les humains. Entrez-le et le jeton de démarrage dans le décodeur pour obtenir d'autres données de séquence. Ici, le jeton de départ est un mot spécial qui signifie le début d'une séquence.

L'interface entre l'encodeur et le décodeur est décrite ci-dessous dans un pseudo langage.

Caractéristiques que les humains ne peuvent pas comprendre= Encoder(Séquence avant la conversion)
Séquence après conversion= Decoder(Caractéristiques que les humains ne peuvent pas comprendre,<start>jeton)    

RNN est utilisé dans chaque module. Le mécanisme spécifique est Visualizing A Neural Machine Translation Model (Mechanics of Seq2seq Models With Attention) Vous pouvez le voir en lisant la première partie de. Cet article concerne l'attention, mais il décrit également Seq2Seq.

Seq2Seq utilise RNN. RNN est efficace pour traiter les langages naturels car il peut gérer des données de longueur variable. De plus, les poids utilisés à chaque étape sont partagés, ce qui réduit l'augmentation des paramètres. Cependant, sachez que les données avec une longueur de série chronologique trop longue entraîneront une disparition du gradient et une explosion du gradient lors de la propagation de l'erreur. Même si la longueur de la série chronologique n'est que de 10, cela revient au développement de 10 couches sur l'axe des temps. GRU est adopté pour RNN. GRU est un modèle RNN qui ne perd pas facilement son gradient.

Le schéma de modèle du décodeur et du codeur utilisé cette fois est le suivant.

スクリーンショット 2020-11-16 19.01.01.png Encodeur de la figure 1

スクリーンショット 2020-11-16 19.01.07.png Décodeur de la figure 2

J'utilise un seul RNN pour chacun des encodeurs et décodeurs. Comme l'ensemble de données est petit, j'ai choisi un modèle plus petit.

Voici le code utilisant tensorflow et keras.

Il utilise l'API fonctionnelle. Si vous souhaitez représenter des données de longueur variable, spécifiez None pour la forme de tf.keras.Input.

Pour utiliser l'API fonctionnelle, veuillez suivre le lien ci-dessous. The Functional API Voici le code d'implémentation du modèle. Le modèle, l'encodeur et le décodeur sont disponibles respectivement. Le modèle est préparé pour l'apprentissage. Les paramètres obtenus par apprentissage avec model.fit sont lus dans l'encodeur et le décodeur. Le traitement diffère entre l'apprentissage et l'inférence.

def CreateEncoderModel(vocab_size):
  units = 128
  emb_layer = tf.keras.layers.Embedding(vocab_size, units, mask_zero=True)#masque pour activer le remplissage_zero=True
  gru_layer  = tf.keras.layers.GRU(units)
  encoder_inputs = tf.keras.Input(shape=(None,))
  outputs = emb_layer(encoder_inputs)
  outputs = gru_layer(outputs)
  
  encoder = tf.keras.Model(encoder_inputs, outputs)

  return encoder

def CreateDecoderModel(vocab_size):
  units = 128

  emb_layer = tf.keras.layers.Embedding(vocab_size, units, mask_zero=True)#masque pour activer le remplissage_zero=True
  gru_layer  = tf.keras.layers.GRU(units, return_sequences=True)
  dense_layer = tf.keras.layers.Dense(vocab_size, activation="softmax")

  decoder_inputs  = tf.keras.Input(shape=(None,))
  encoder_outputs = tf.keras.Input(shape=(None,))

  outputs = emb_layer(decoder_inputs)
  outputs = gru_layer(outputs, initial_state=encoder_outputs)
  outputs = dense_layer(outputs)
  
  decoder = tf.keras.Model([decoder_inputs, encoder_outputs], outputs)

  return decoder

def CreateModel(seed, ja_vocab_size, en_vocab_size):
  tf.random.set_seed(seed)
  encoder = CreateEncoderModel(ja_vocab_size)
  decoder = CreateDecoderModel(en_vocab_size)

  encoder_inputs = tf.keras.Input(shape=(None,))
  decoder_inputs = tf.keras.Input(shape=(None,))

  encoder_outputs = encoder(encoder_inputs)
  decoder_outputs = decoder([decoder_inputs, encoder_outputs])
  
  model = tf.keras.Model([encoder_inputs, decoder_inputs], decoder_outputs)
  model.compile(optimizer='adam',
                loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),
                metrics=['accuracy'])
  return model, encoder, decoder

Apprentissage Cherchons avec les tailles de lots 32, 64, 128.

Les paramètres expérimentaux sont indiqués ci-dessous.

  1. Le nombre d'unités RNN est de 128
  2. Comment mettre à jour le poids est Adam (le taux d'apprentissage reste la valeur par défaut)
  3. Le nombre d'époque est de 2
  4. La méthode d'évaluation est BLEU

Nous avons arrêté l'époque deux fois et évalué les données de validation avec le deuxième modèle d'époque. BLEU est un index utilisé pour mesurer la qualité des traductions.

Voici le code d'apprentissage.

bleu_scores = []
batch_size_list = [32, 64, 128]
for batch_size in batch_size_list:
  model, encoder, decoder = CreateModel(seed, len(ja_tokenizer.word_index)+1, len(en_tokenizer.word_index)+1)
  model.fit([train_ja_sequences, train_en_sequences[:, :-1]], train_en_sequences[:, 1:], batch_size=batch_size, epochs=2)
  model.save(str(batch_size)+"model.h5")
  encoder.load_weights(str(batch_size)+"model.h5", by_name=True)
  decoder.load_weights(str(batch_size)+"model.h5", by_name=True)
  bleu_score = Evaluate(valid_ja, valid_en, encoder, decoder)
  bleu_scores.append(bleu_score)

Le BLEU est mesuré avec la fonction Evaluate. (L'implémentation est publiée sur github.) Le modèle a été enregistré en utilisant chaque taille de lot comme nom.

La méthode Decode génère le mot correspondant à la probabilité maximale de chaque étape. J'ai décidé avidement le mot. (Méthode gourmande) En fait, il est préférable d'utiliser Beam Search. Beam Search est un algorithme de recherche avec des conditions gourmandes légèrement relâchées. Même si vous décidez avidement du mot pour chaque étape, vous ne savez pas si ce sera la solution optimale, il est donc préférable d'utiliser Beam Search. Les explications suivantes seront utiles pour la recherche de faisceaux. C5W3L03 Beam Search Le lien est une vidéo, c'est donc une bonne idée de le regarder quand vous avez le temps.

Dans keras, je me demande si le masque est appliqué lors du calcul de la valeur de perte. J'ai entendu dire que c'était appliqué, mais je ne suis pas sûr parce que je ne sais pas ce qui se passe là-bas.

Si vous ne vous sentez pas à l'aise, la mise en œuvre de la fonction de coût de Traduction automatique neuronale avec attention peut être utile. Cependant, veuillez noter que l'implémentation de la destination du lien est assez difficile pour ceux qui n'ont appris qu'avec model.fit. La mise en œuvre de la fonction de coût lié est mise en œuvre de sorte que le coût au moment de l'étape de masquage ne soit pas inclus dans le coût final.

Les résultats expérimentaux sont représentés graphiquement. スクリーンショット 2020-11-18 16.22.00.png Fig.3 Résultats expérimentaux

Veuillez noter que l'image est approximative.

Le meilleur BLEU pour les données de validation est la taille du lot de 32, utilisez donc 32 pour vous recycler. Comme vous pouvez le voir sur la figure 3, des lots plus petits peuvent donner de meilleurs résultats. Avant de commencer l'évaluation, mélangez les données d'entraînement et les données de vérification et réentraînez-vous. Pour réapprendre, j'ai mis le numéro d'époque à 10. Tout le reste est identique.

train_and_valid_ja_sequences = tf.concat([train_ja_sequences, valid_ja_sequences], 0)
train_and_valid_en_sequences = tf.concat([train_en_sequences, valid_en_sequences], 0)

best_model, best_encoder, best_decoder = CreateModel(seed, len(ja_tokenizer.word_index)+1, len(en_tokenizer.word_index)+1)
best_model.fit([train_and_valid_ja_sequences, train_and_valid_en_sequences[:, :-1]], train_and_valid_en_sequences[:, 1:], batch_size=32, epochs=10)
best_model.save("best_model.h5")

Si vous utilisez un GPU, vous n'obtiendrez pas toujours les mêmes résultats.

Évaluation C'était BLEU 0,19 pour les données de test. (Le maximum est 1) Je ne sais pas parce que je ne l'ai pas comparé avec d'autres, mais je pense que c'est un résultat assez terrible www

Le code de traitement des données de test est le suivant.

best_encoder.load_weights("best_model.h5", by_name=True)
best_decoderbest_decoder.load_weights("best_model.h5", by_name=True)
bleu_score = Evaluate(test_ja, test_en, best_encoder, best_decoder)
print("bleu on test_dataset:")
print(bleu_score)

C'est une question simple, mais il semble y avoir plusieurs méthodes d'évaluation BLEU. (Il semble y avoir des fonctions de lissage.) Il semble qu'il ne soit pas unifié, mais dites-moi qui le connaît. S'il n'est pas unifié, alors BLEU devrait être mesuré avec la fonction de lissage qui fonctionne le mieux ... est-ce une fourmi? ...

Enfin

Je terminerai cet article avec quelques moyens d'améliorer la précision.

  1. Inversez les données d'entrée
  2. Incorporer l'attention dans le modèle
  3. Utilisez stop_word.
  4. Ensemble
  5. Approfondissez la couche (n'oubliez pas d'utiliser la connexion sautée)
  6. Partagez les pondérations de la couche Embedding et de la couche entièrement connectée
  7. Changer de modèle en Transformer
  8. Changer la méthode de réglage du poids initial

J'ai répertorié ceux que vous pouvez trouver autant que vous le souhaitez en recherchant sur le net. Ce n'est pas parce que vous l'utilisez que BLEU s'améliorera. Si vous êtes intéressé, veuillez demander à un enseignant Google.

Je suis nouveau dans le traitement du langage naturel, donc je serais heureux si vous pouviez me dire s'il y a quelque chose qui ne va pas.

Références 1.small_parallel_enja 2.Masking and padding with Keras 3.The Functional API 4. Visualizing A Neural Machine Translation Model (Mechanics of Seq2seq Models With Attention) 5.Neural machine translation with attention 6.C5W3L03 Beam Search

Recommended Posts