Python + Keras + LSTM:半小时内完成文本翻译

哈Ha

上一部分中,我着眼于基于神经网络创建简单的文本识别。 今天,我们将使用类似的方法,并编写从英语到德语的文本自动翻译器。



对于那些对它的工作方式感兴趣的人,请注意细节。

注意 :这个使用神经网络进行翻译的项目完全是教育性的,因此不考虑“为什么”问题。 只是为了好玩。 我没有开始证明这种方法的优劣,只是检查发生的事情很有趣。 当然,下面使用的方法得到了简化,但是我希望没有人希望我们在半小时内写出第二本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. ... 

该文件包含19.2万行,大小为13 MB。 我们将文本加载到内存中,并将数据分为两个块,分别用于英语和德语单词。

 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 = to,2 = be,3 =或4 = not。 我们已经可以将这些数据提交到神经网络。

神经网络训练


我们的数据已经过数字化处理。 我们将数组分为两个块,分别用于输入(英语行)和输出(德语行)数据。 我们还将准备一个单独的单元来验证学习过程。

 # 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上花费了大约半小时的时间来建立3万行。 训练结束时(只需执行一次),模型将保存到文件中,然后可以重用。

为了获得翻译,我们使用predict_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”
“我叫汤姆”-“汤姆汤姆”
“您几岁了”-“您的年龄”
“最近的商店在哪里?”“ wo ist der”

如您所见,到目前为止,“点击数”很少。 短语“您几岁”的一个片段将神经网络与短语“您好吗”相混淆,并产生了翻译“ wie geht ist es es”(您好吗?)。 神经网络在短语“哪里是……”中仅识别动词“哪里”,并产生翻译“ wo ist der”(它在哪里?),这在原则上并非没有意义。 通常,它还会在A1组中翻译成德国新手;)

10分钟的训练

“今天天气很好”-“ das haus ist bereit”
“我的名字叫汤姆”-“ meinheißeheißetom”
“你几岁了”-“怀特·辛德·西”
“最近的商店在哪里”-“巴黎妇女”

可见一些进展。 第一个短语完全不合适。 在第二个短语中,神经网络“学习”了动词heißen(称为),但“ meinheißeheißetom”仍然是错误的,尽管您已经可以猜测其含义了。 第三个短语已经正确。 在第四部分中,正确的第一部分是“女士”,但最近的商店由于某种原因被巴黎所取代。

30分钟的训练

“今天天气很好”-“ das ist ist aus”
“我的名字叫汤姆”-“”汤姆“ ist mein name”
“你几岁了”-“怀特·辛德·西”
“最近的商店在哪里?”“ wo ist der”

如您所见,尽管设计看起来有些不同,但第二句话已经变得正确。 第三个短语是正确的,但是第一个和第四个短语尚未“学习”。 为了节省电力,我完成了该过程。

结论


如您所见,原则上这是可行的。 我想以这样的速度记住一种新语言:)当然,到目前为止,结果还不是很完美,但是在全套19万行的训练上将花费一个多小时。

对于那些想自己尝试的人,源代码位于破坏者的下面。 该程序理论上可以使用任何一对语言,不仅是英语和德语(文件应采用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/zh-CN470706/


All Articles