Python + Keras + LSTM: lakukan penerjemah teks dalam waktu setengah jam

Hai, Habr.

Pada bagian sebelumnya, saya melihat cara membuat pengenalan teks sederhana berdasarkan jaringan saraf. Hari ini kita akan menggunakan pendekatan yang sama dan menulis penerjemah otomatis teks dari Bahasa Inggris ke Bahasa Jerman.



Bagi mereka yang tertarik dengan cara kerjanya, detailnya ada di bawah potongan.

Catatan : proyek ini menggunakan jaringan saraf untuk terjemahan bersifat mendidik, oleh karena itu pertanyaan "mengapa" tidak dipertimbangkan. Hanya untuk bersenang-senang. Saya tidak bermaksud membuktikan bahwa metode ini atau itu lebih baik atau lebih buruk, hanya menarik untuk memeriksa apa yang terjadi. Metode yang digunakan di bawah ini, tentu saja, disederhanakan, tetapi saya harap tidak ada yang berharap bahwa kita akan menulis Lingvo kedua dalam setengah jam.

Pengumpulan data


File yang ditemukan di jaringan yang mengandung frasa bahasa Inggris dan Jerman yang dipisahkan oleh tab digunakan sebagai dataset sumber. Seperangkat frasa terlihat seperti ini:

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. ... 

File berisi 192 ribu baris dan memiliki ukuran 13 MB. Kami memuat teks ke dalam memori dan memecah data menjadi dua blok, untuk kata-kata bahasa Inggris dan Jerman.

 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() 

Kami juga mengonversi semua kata menjadi huruf kecil dan menghapus tanda baca.

Langkah selanjutnya adalah menyiapkan data untuk jaringan saraf. Jaringan tidak tahu apa kata-kata itu, dan bekerja secara eksklusif dengan angka. Untungnya bagi kami, keras sudah memiliki kelas Tokenizer bawaan, yang menggantikan kata-kata dalam kalimat dengan kode digital.

Penggunaannya hanya digambarkan dengan contoh:

 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) 

Frasa "menjadi atau tidak menjadi" akan diganti oleh array [1 2 3 4 1 2 0 0], di mana tidak sulit untuk menebak, 1 = to, 2 = be, 3 = atau, 4 = not, Kami sudah dapat mengirimkan data ini ke jaringan saraf.

Pelatihan jaringan saraf


Data kami siap secara digital. Kami membagi array menjadi dua blok untuk data input (garis Inggris) dan output (garis Jerman). Kami juga akan menyiapkan unit terpisah untuk memvalidasi proses pembelajaran.

 # 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]) 

Sekarang kita dapat membuat model jaringan saraf dan memulai pelatihannya. Seperti yang Anda lihat, jaringan saraf berisi lapisan LSTM yang memiliki sel memori. Meskipun mungkin akan bekerja pada jaringan "biasa", mereka yang ingin dapat memeriksa sendiri.

 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') 

Pelatihan itu sendiri terlihat seperti ini:



Prosesnya, seperti yang Anda lihat, tidak cepat, dan memakan waktu sekitar setengah jam pada Core i7 + GeForce 1060 untuk satu set 30 ribu baris. Pada akhir pelatihan (hanya perlu dilakukan sekali), model disimpan ke file, dan kemudian dapat digunakan kembali.

Untuk mendapatkan terjemahan, kami menggunakan fungsi predict_classes, input yang kami kirimkan beberapa frasa sederhana. Fungsi get_word digunakan untuk membalikkan kata menjadi angka.

 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)) 

Hasil


Sebenarnya, yang paling aneh adalah hasilnya. Sangat menarik untuk melihat bagaimana jaringan saraf belajar dan "mengingat" korespondensi antara frase bahasa Inggris dan Jerman. Saya secara khusus mengambil 2 frasa lebih mudah dan 2 lebih sulit untuk melihat perbedaannya.

5 menit pelatihan

“Cuacanya bagus hari ini” - “das ist ist tom”
"Nama saya tom" - "wie für tom tom"
"Berapa umurmu" - "wie geht ist es"
"Di mana toko terdekat" - "wo ist der"

Seperti yang Anda lihat, sejauh ini ada beberapa "hit". Sepotong frasa "berapa umurmu" membingungkan jaringan saraf dengan frasa "apa kabar" dan menghasilkan terjemahan "wie geht ist es" (apa kabar?). Dalam frasa "di mana ..." jaringan saraf hanya mengidentifikasi kata kerja di mana dan menghasilkan terjemahan "wo ist der" (di mana itu?), Yang, pada prinsipnya, bukan tanpa makna. Secara umum, hampir sama dengan menerjemahkan ke dalam bahasa Jerman pendatang baru ke grup A1;)

10 menit pelatihan

“Cuacanya bagus hari ini” - “das haus ist bereit”
"Nama saya tom" - "mein heiße heiße tom"
"Berapa umurmu" - "wie alt sind sie"
"Di mana toko terdekat" - "wo ist paris"

Beberapa kemajuan terlihat. Ungkapan pertama benar-benar tidak pada tempatnya. Pada frasa kedua, jaringan saraf “mempelajari” kata kerja heißen (disebut), tetapi “mein heiße heiße tom” masih salah, meskipun Anda sudah bisa menebak artinya. Frasa ketiga sudah benar. Pada bagian keempat, bagian pertama yang benar adalah "wo ist", tetapi toko terdekat karena alasan tertentu digantikan oleh paris.

30 menit pelatihan

“Cuacanya bagus hari ini” - “das ist ist aus”
"Namaku tom" - "" tom "ist mein name"
"Berapa umurmu" - "wie alt sind sie"
"Di mana toko terdekat" - "wo ist der"

Seperti yang Anda lihat, frasa kedua telah menjadi benar, meskipun desainnya terlihat agak tidak biasa. Frase ketiga benar, tetapi frasa 1 dan 4 belum "dipelajari". Dengan ini untuk menghemat listrik, saya menyelesaikan prosesnya.

Kesimpulan


Seperti yang Anda lihat, pada prinsipnya, ini bekerja. Saya ingin menghafal bahasa baru dengan kecepatan seperti itu :) Tentu saja, hasilnya tidak sempurna sejauh ini, tetapi pelatihan pada set lengkap 190 ribu baris akan memakan waktu lebih dari satu jam.

Bagi mereka yang ingin bereksperimen sendiri, kode sumbernya ada di bawah spoiler. Program ini secara teoritis dapat menggunakan pasangan bahasa apa saja, tidak hanya bahasa Inggris dan Jerman (file harus dalam pengkodean UTF-8). Masalah kualitas terjemahan juga tetap terbuka, ada sesuatu untuk diuji.

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() 


Kamus itu sendiri terlalu besar untuk dilampirkan ke artikel, tautan ada di komentar.

Seperti biasa, semua percobaan berhasil.

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


All Articles