Python + Keras + LSTM: اصنع مترجم نص في نصف ساعة

مرحبا يا هبر.

في الجزء السابق ، نظرت إلى إنشاء تمييز بسيط للنص على أساس شبكة عصبية. اليوم سوف نستخدم نهجًا مماثلًا ونكتب مترجمًا تلقائيًا للنصوص من الإنجليزية إلى الألمانية.



بالنسبة لأولئك الذين يهتمون بكيفية عمل ذلك ، فإن التفاصيل قيد التنفيذ.

ملاحظة : هذا المشروع الخاص باستخدام شبكة عصبية للترجمة هو برنامج تعليمي حصري ، وبالتالي فإن السؤال "لماذا" لم يتم النظر فيه. للمتعة فقط. لا أقصد إثبات أن هذه الطريقة أو تلك أفضل أو أسوأ ، لقد كان من المثير للاهتمام التحقق مما يحدث. إن الطريقة المستخدمة أدناه مبسطة ، بالطبع ، لكنني آمل ألا يأمل أحد في أن نكتب رسالة Lingvo ثانية خلال نصف ساعة.

جمع البيانات


تم استخدام ملف موجود على الشبكة يحتوي على عبارات إنجليزية وألمانية مفصولة بعلامات جدولة كمجموعة بيانات المصدر. مجموعة من العبارات تبدو مثل هذا:

Hi. Hallo! Hi. Grüß Gott! Run! Lauf! Wow! Potzdonner! Wow! Donnerwetter! Fire! Feuer! Help! Hilfe! Help! Zu Hülf! Stop! Stopp! Wait! Warte! Go on. Mach weiter. Hello! Hallo! I ran. Ich rannte. I see. Ich verstehe. ... 

يحتوي الملف على 192 ألف سطر وله حجم 13 ميغابايت. نقوم بتحميل النص في الذاكرة وتقسيم البيانات إلى كتلتين ، لكلمات إنجليزية وألمانية.

 def read_text(filename): with open(filename, mode='rt', encoding='utf-8') as file: text = file.read() sents = text.strip().split('\n') return [i.split('\t') for i in sents] data = read_text("deutch.txt") deu_eng = np.array(data) deu_eng = deu_eng[:30000,:] print("Dictionary size:", deu_eng.shape) # Remove punctuation deu_eng[:,0] = [s.translate(str.maketrans('', '', string.punctuation)) for s in deu_eng[:,0]] deu_eng[:,1] = [s.translate(str.maketrans('', '', string.punctuation)) for s in deu_eng[:,1]] # convert text to lowercase for i in range(len(deu_eng)): deu_eng[i,0] = deu_eng[i,0].lower() deu_eng[i,1] = deu_eng[i,1].lower() 

قمنا أيضًا بتحويل جميع الكلمات إلى أحرف صغيرة وإزالة علامات الترقيم.

والخطوة التالية هي إعداد البيانات للشبكة العصبية. الشبكة لا تعرف ما هي الكلمات ، وتعمل حصرا مع الأرقام. لحسن الحظ بالنسبة لنا ، تحتوي keras بالفعل على فئة Tokenizer ، والتي تحل محل الكلمات في الجمل بالرموز الرقمية.

يتضح استخدامه ببساطة مع مثال:

 from keras.preprocessing.text import Tokenizer from keras.preprocessing.sequence import pad_sequences s = "To be or not to be" eng_tokenizer = Tokenizer() eng_tokenizer.fit_on_texts([s]) seq = eng_tokenizer.texts_to_sequences([s]) seq = pad_sequences(seq, maxlen=8, padding='post') print(seq) 

سيتم استبدال العبارة "لتكون أو لا تكون" بالصفيف [1 2 3 4 1 2 0 0] ، حيث يصعب تخمينه ، 1 = إلى ، 2 = يكون ، 3 = أو ، 4 = لا. يمكننا بالفعل تقديم هذه البيانات إلى الشبكة العصبية.

تدريب الشبكة العصبية


بياناتنا جاهزة رقميا. نقسم الصفيف إلى كتلتين لإدخال (الخطوط الإنجليزية) وإخراج (الخطوط الألمانية) البيانات. سنقوم أيضًا بإعداد وحدة منفصلة للتحقق من صحة عملية التعلم.

 # split data into train and test set train, test = train_test_split(deu_eng, test_size=0.2, random_state=12) # prepare training data trainX = encode_sequences(eng_tokenizer, eng_length, train[:, 0]) trainY = encode_sequences(deu_tokenizer, deu_length, train[:, 1]) # prepare validation data testX = encode_sequences(eng_tokenizer, eng_length, test[:, 0]) testY = encode_sequences(deu_tokenizer, deu_length, test[:, 1]) 

الآن يمكننا إنشاء نموذج للشبكة العصبية وبدء التدريب. كما ترون ، تحتوي الشبكة العصبية على طبقات LSTM لها خلايا ذاكرة. على الرغم من أنه من المحتمل أن يعمل على شبكة "منتظمة" ، يمكن لأولئك الذين يرغبون في التحقق بنفسهم.

 def make_model(in_vocab, out_vocab, in_timesteps, out_timesteps, n): model = Sequential() model.add(Embedding(in_vocab, n, input_length=in_timesteps, mask_zero=True)) model.add(LSTM(n)) model.add(Dropout(0.3)) model.add(RepeatVector(out_timesteps)) model.add(LSTM(n, return_sequences=True)) model.add(Dropout(0.3)) model.add(Dense(out_vocab, activation='softmax')) model.compile(optimizer=optimizers.RMSprop(lr=0.001), loss='sparse_categorical_crossentropy') return model eng_vocab_size = len(eng_tokenizer.word_index) + 1 deu_vocab_size = len(deu_tokenizer.word_index) + 1 eng_length, deu_length = 8, 8 model = make_model(eng_vocab_size, deu_vocab_size, eng_length, deu_length, 512) num_epochs = 40 model.fit(trainX, trainY.reshape(trainY.shape[0], trainY.shape[1], 1), epochs=num_epochs, batch_size=512, validation_split=0.2, callbacks=None, verbose=1) model.save('en-de-model.h5') 

التدريب في حد ذاته يبدو مثل هذا:



العملية ، كما ترون ، ليست سريعة ، وتستغرق حوالي نصف ساعة على Core i7 + GeForce 1060 لمجموعة من 30 ألف سطر. في نهاية التدريب (يجب أن يتم ذلك مرة واحدة فقط) ، يتم حفظ النموذج في ملف ، ثم يمكن إعادة استخدامه.

للحصول على الترجمة ، نستخدم دالة Forecast_classes ، التي نقدم مدخلات منها بضع عبارات بسيطة. يتم استخدام الدالة get_word لعكس الكلمات إلى أرقام.

 model = load_model('en-de-model.h5') def get_word(n, tokenizer): if n == 0: return "" for word, index in tokenizer.word_index.items(): if index == n: return word return "" phrs_enc = encode_sequences(eng_tokenizer, eng_length, ["the weather is nice today", "my name is tom", "how old are you", "where is the nearest shop"]) preds = model.predict_classes(phrs_enc) print("Preds:", preds.shape) print(preds[0]) print(get_word(preds[0][0], deu_tokenizer), get_word(preds[0][1], deu_tokenizer), get_word(preds[0][2], deu_tokenizer), get_word(preds[0][3], deu_tokenizer)) print(preds[1]) print(get_word(preds[1][0], deu_tokenizer), get_word(preds[1][1], deu_tokenizer), get_word(preds[1][2], deu_tokenizer), get_word(preds[1][3], deu_tokenizer)) print(preds[2]) print(get_word(preds[2][0], deu_tokenizer), get_word(preds[2][1], deu_tokenizer), get_word(preds[2][2], deu_tokenizer), get_word(preds[2][3], deu_tokenizer)) print(preds[3]) print(get_word(preds[3][0], deu_tokenizer), get_word(preds[3][1], deu_tokenizer), get_word(preds[3][2], deu_tokenizer), get_word(preds[3][3], deu_tokenizer)) 

النتائج


الآن ، في الواقع ، الشيء الأكثر فضولاً هو النتائج. من المثير للاهتمام معرفة كيفية تعلم الشبكة العصبية و "تذكر" المراسلات بين الجمل الإنجليزية والألمانية. أخذت على وجه التحديد عبارات 2 أسهل وأصعب 2 لمعرفة الفرق.

5 دقائق من التدريب

"الجو جميل اليوم" - "das ist ist tom"
"اسمي توم" - "wie für tom tom"
"كم عمرك" - "wie geht ist es"
"أين يوجد أقرب متجر" - "wo ist der"

كما ترون ، حتى الآن هناك عدد قليل من "الزيارات". هناك جزء من عبارة "كم عمرك" يخلط بين الشبكة العصبية وعبارة "كيف حالك" وأنتج ترجمة "wie geht ist es" (كيف حالك؟). في عبارة "أين ..." ، حددت الشبكة العصبية الفعل فقط وأنتجت الترجمة "wo ist der" (أين هو؟) ، والتي ، من حيث المبدأ ، لا تخلو من معنى. بشكل عام ، تقريبًا نفس الشيء الذي يترجم إلى الألمانية قادمًا جديدًا إلى المجموعة A1 ؛)

10 دقائق من التدريب

"الجو جميل اليوم" - "das haus ist bereit"
"اسمي توم" - "mein heiße heiße tom"
"كم عمرك" - "wie alt sind sie"
"أين يوجد أقرب متجر" - "wo ist paris"

بعض التقدم مرئي. العبارة الأولى هي خارج المكان تماما. في العبارة الثانية ، "تعلمت" الشبكة العصبية الفعل heißen (يسمى) ، ولكن "mein heiße heiße tom" لا تزال غير صحيحة ، على الرغم من أنه يمكنك بالفعل تخمين المعنى. العبارة الثالثة صحيحة بالفعل. في الجزء الرابع ، الجزء الأول الصحيح هو "wo ist" ، ولكن تم استبدال أقرب متجر لسبب ما بباريس.

30 دقيقة من التدريب

"الجو جميل اليوم" - "das ist ist aus"
"اسمي توم" - "توم" ist mein name "
"كم عمرك" - "wie alt sind sie"
"أين يوجد أقرب متجر" - "wo ist der"

كما ترى ، العبارة الثانية أصبحت صحيحة ، على الرغم من أن التصميم يبدو غير عادي إلى حد ما. العبارة الثالثة صحيحة ، لكن الجملتين الأولى والرابعة لم يتم "تعلمهما" بعد. مع هذا من أجل توفير الكهرباء ، انتهيت من العملية.

استنتاج


كما ترون ، من حيث المبدأ ، هذا يعمل. أود أن أحفظ لغة جديدة بهذه السرعة :) بالطبع ، النتيجة ليست مثالية حتى الآن ، ولكن التدريب على مجموعة كاملة من 190 ألف خط سيستغرق أكثر من ساعة واحدة.

بالنسبة لأولئك الذين يرغبون في تجربة من تلقاء أنفسهم ، شفرة المصدر هي تحت المفسد. يمكن للبرنامج من الناحية النظرية استخدام أي زوج من اللغات ، وليس الإنجليزية والألمانية فقط (يجب أن يكون الملف بترميز UTF-8). تظل مسألة جودة الترجمة مفتوحة أيضًا ، فهناك شيء يجب اختباره.

keras_translate.py
 import os # os.environ["CUDA_VISIBLE_DEVICES"] = "-1" # Force CPU os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' # 0 = all messages are logged, 3 - INFO, WARNING, and ERROR messages are not printed import string import re import numpy as np import pandas as pd from keras.models import Sequential from keras.layers import Dense, LSTM, Embedding, RepeatVector from keras.preprocessing.text import Tokenizer from keras.callbacks import ModelCheckpoint from keras.preprocessing.sequence import pad_sequences from keras.models import load_model from keras import optimizers from sklearn.model_selection import train_test_split import matplotlib.pyplot as plt pd.set_option('display.max_colwidth', 200) # Read raw text file def read_text(filename): with open(filename, mode='rt', encoding='utf-8') as file: text = file.read() sents = text.strip().split('\n') return [i.split('\t') for i in sents] data = read_text("deutch.txt") deu_eng = np.array(data) deu_eng = deu_eng[:30000,:] print("Dictionary size:", deu_eng.shape) # Remove punctuation deu_eng[:,0] = [s.translate(str.maketrans('', '', string.punctuation)) for s in deu_eng[:,0]] deu_eng[:,1] = [s.translate(str.maketrans('', '', string.punctuation)) for s in deu_eng[:,1]] # Convert text to lowercase for i in range(len(deu_eng)): deu_eng[i,0] = deu_eng[i,0].lower() deu_eng[i,1] = deu_eng[i,1].lower() # Prepare English tokenizer eng_tokenizer = Tokenizer() eng_tokenizer.fit_on_texts(deu_eng[:, 0]) eng_vocab_size = len(eng_tokenizer.word_index) + 1 eng_length = 8 # Prepare Deutch tokenizer deu_tokenizer = Tokenizer() deu_tokenizer.fit_on_texts(deu_eng[:, 1]) deu_vocab_size = len(deu_tokenizer.word_index) + 1 deu_length = 8 # Encode and pad sequences def encode_sequences(tokenizer, length, lines): # integer encode sequences seq = tokenizer.texts_to_sequences(lines) # pad sequences with 0 values seq = pad_sequences(seq, maxlen=length, padding='post') return seq # Split data into train and test set train, test = train_test_split(deu_eng, test_size=0.2, random_state=12) # Prepare training data trainX = encode_sequences(eng_tokenizer, eng_length, train[:, 0]) trainY = encode_sequences(deu_tokenizer, deu_length, train[:, 1]) # Prepare validation data testX = encode_sequences(eng_tokenizer, eng_length, test[:, 0]) testY = encode_sequences(deu_tokenizer, deu_length, test[:, 1]) # Build NMT model def make_model(in_vocab, out_vocab, in_timesteps, out_timesteps, n): model = Sequential() model.add(Embedding(in_vocab, n, input_length=in_timesteps, mask_zero=True)) model.add(LSTM(n)) model.add(Dropout(0.3)) model.add(RepeatVector(out_timesteps)) model.add(LSTM(n, return_sequences=True)) model.add(Dropout(0.3)) model.add(Dense(out_vocab, activation='softmax')) model.compile(optimizer=optimizers.RMSprop(lr=0.001), loss='sparse_categorical_crossentropy') return model print("deu_vocab_size:", deu_vocab_size, deu_length) print("eng_vocab_size:", eng_vocab_size, eng_length) # Model compilation (with 512 hidden units) model = make_model(eng_vocab_size, deu_vocab_size, eng_length, deu_length, 512) # Train model num_epochs = 250 history = model.fit(trainX, trainY.reshape(trainY.shape[0], trainY.shape[1], 1), epochs=num_epochs, batch_size=512, validation_split=0.2, callbacks=None, verbose=1) # plt.plot(history.history['loss']) # plt.plot(history.history['val_loss']) # plt.legend(['train','validation']) # plt.show() model.save('en-de-model.h5') # Load model model = load_model('en-de-model.h5') def get_word(n, tokenizer): if n == 0: return "" for word, index in tokenizer.word_index.items(): if index == n: return word return "" phrs_enc = encode_sequences(eng_tokenizer, eng_length, ["the weather is nice today", "my name is tom", "how old are you", "where is the nearest shop"]) print("phrs_enc:", phrs_enc.shape) preds = model.predict_classes(phrs_enc) print("Preds:", preds.shape) print(preds[0]) print(get_word(preds[0][0], deu_tokenizer), get_word(preds[0][1], deu_tokenizer), get_word(preds[0][2], deu_tokenizer), get_word(preds[0][3], deu_tokenizer)) print(preds[1]) print(get_word(preds[1][0], deu_tokenizer), get_word(preds[1][1], deu_tokenizer), get_word(preds[1][2], deu_tokenizer), get_word(preds[1][3], deu_tokenizer)) print(preds[2]) print(get_word(preds[2][0], deu_tokenizer), get_word(preds[2][1], deu_tokenizer), get_word(preds[2][2], deu_tokenizer), get_word(preds[2][3], deu_tokenizer)) print(preds[3]) print(get_word(preds[3][0], deu_tokenizer), get_word(preds[3][1], deu_tokenizer), get_word(preds[3][2], deu_tokenizer), get_word(preds[3][3], deu_tokenizer)) print() 


القاموس نفسه كبير جدًا بحيث لا يمكن إرفاقه بالمقال ، فالرابط موجود في التعليقات.

كالعادة ، كل التجارب الناجحة.

Source: https://habr.com/ru/post/ar470706/


All Articles