Все мы сталкивались с услугами онлайн-переводчиков. Он переводит ввод с одного языка на другой. Но задумывались ли вы, как это делается ?! В этой статье давайте попробуем понять, как это делается, а также попробуем создать один простой переводчик с использованием Python.

Эта статья вдохновлена ​​блогом Введение в последовательное обучение в Keras.

Перед созданием переводчика нам нужно знать некоторые концепции глубокого обучения, поэтому давайте приступим к изучению этих концепций.

Что такое последовательное обучение?

Последовательность в последовательность (seq2seq) - это создание моделей для преобразования последовательностей из одного домена в другой. Например, преобразовать текст на из одного языка в другой или преобразовать голос одного человека в голос другого человека и т. Д. Итак, вопрос в том, почему не нужно? Используем ли мы для этой цели такие модели, как простые слои LSTM или GRU? или почему мы используем для этой цели методику конкретной последовательности для моделирования последовательности? Ответ прост: если входы и выходы имеют одинаковую длину, тогда мы можем легко использовать простые слои LSTM и GRU, но здесь входы и выходы имеют разную длину (возьмите случай языкового перевода , количество слов в предложении на английском языке не будет равно количеству слов в предложении, которое представляет такое же значение на французском языке), поэтому мы вынуждены использовать другой подход для этой цели.

Поскольку входная и выходная последовательности имеют разную длину, вся входная последовательность принимается во внимание, чтобы начать прогнозирование целевого выхода. Теперь посмотрим, как это работает.

В последовательном подходе мы сталкиваемся с двумя разными архитектурами.

  1. Архитектура обучения
  2. Архитектура тестирования (модель вывода)

В архитектуре обучения и тестирования есть кодировщик и декодер. Архитектура кодировщика остается одинаковой в обоих случаях, но архитектура декодера имеет небольшие отличия.

Архитектура обучения

Как показано на рис. 1, в обучающей архитектуре у нас есть два раздела: кодер и декодер. Кодировщик (слой RNN) принимает каждый символ (английский символ) в качестве входных данных и преобразует их в какое-то скрытое представление. Все эти скрытые представления затем передаются через функцию F, так что она создает один закодированный вектор.

В декодере (слой RNN) начинается с закодированного вектора и символа начальной последовательности (SOS, здесь в нашем случае мы используем '\ t') в качестве входных данных, затем нейронная сеть вынуждена создавать соответствующую цель. каким-либо образом обновляя его характеристики (так называемое принуждение учителя). Начиная со следующего временного шага, вводом будет каждый символ (в данном случае французские символы) и предыдущее состояние декодера. Фактически, декодер учится генерировать targets[t+1...] заданный targets[...t], обусловленный входной последовательностью.

Архитектура тестирования (модель вывода)

Как показано на рис. 2, архитектура тестирования (используемая для прогнозирования выходных данных) также имеет две части: часть кодера и часть декодера. Кодировщик (уровень RNN) работает как уровень кодировщика в обучающей архитектуре (т.е. он принимает вводимый символ за символом и создает один закодированный вектор).

Теперь слой декодера (ранее обученная сеть) принимает закодированный вектор и начальную последовательность в качестве входных данных и пытается создать первый целевой символ (угадайте результат), а начиная со следующего временного шага, кодировщик принимает ранее предсказанный символ. и состояние декодера в качестве ввода и пытается произвести целевой вывод. Этот процесс повторяется до тех пор, пока не будет предсказана последовательность остановки (EOS) (см. Рис. 2).

Давайте код!

Наш первый шаг к созданию переводчика - это настройка среды с установленными необходимыми библиотеками.

pip install keras
pip install tensorflow
pip install numpy

Давайте создадим файл с именем train.py

Теперь давайте импортируем библиотеки и определим параметры

from keras.models import Model
from keras.layers import Input, LSTM, Dense
import numpy as np
batch_size = 64      # Batch size for training.
epochs = 100         # Number of epochs to train for.
latent_dim = 256     # Latent dimensionality of the encoding space.
num_samples = 10000  # Number of samples to train on.

Следующий шаг - подготовка набора данных. мы будем использовать набор пар английских предложений и их французский перевод, которые вы можете скачать с сайта manythings.org/anki. После загрузки набора данных мы просто устанавливаем путь для доступа к набору данных, как показано ниже.

# Path to the data txt file on disk.
path = 'fra-eng/fra.txt'

Следующий шаг - векторизация данных. Итак, чтобы векторизовать данные, мы начинаем с чтения каждой строки в текстовом файле и добавления ее в список.

input_texts = []
target_texts = []
input_characters = set()
target_characters = set()
with open(path, 'r', encoding='utf-8') as f:
    lines = f.read().split('\n')
print(lines[1:5])
Output
['Hi.\tSalut !\tCC-BY 2.0 (France) Attribution: tatoeba.org #538123 (CM) & #509819 (Aiji)', 'Hi.\tSalut.\tCC-BY 2.0 (France) Attribution: tatoeba.org #538123 (CM) & #4320462 (gillux)', 'Run!\tCours\u202f!\tCC-BY 2.0 (France) Attribution: tatoeba.org #906328 (papabear) & #906331 (sacredceltic)', 'Run!\tCourez\u202f!\tCC-BY 2.0 (France) Attribution: tatoeba.org #906328 (papabear) & #906332 (sacredceltic)']

В данном руководстве мы рассматриваем только первые 10000 строк набора данных. мы хотим заполнить английский текст как вводимый текст и французский текст как целевые тексты.

for line in lines[:10000]:
    input_text, target_text, _ = line.split('\t')
print(input_text,target_text)
Output
.......
Someone called. Quelqu'un a téléphoné.
Stay out of it. Ne t'en mêle pas !
Stay out of it. Ne vous en mêlez pas !
Stop grumbling. Arrête de râler.
Stop grumbling. Arrête de ronchonner.
Stop poking me. Arrête de m'asticoter !
.......

Нам нужно определить символ начальной последовательности и символ конечной последовательности для целевого текста. мы используем ‘tab’ как начало символа и ‘\ n’ как конец символа.

Примечание: тот же цикл for (показанный выше) используется ниже. Это сделано для объяснения концепции. Новые добавленные строки выделены полужирным буквами.

for line in lines[: 10000]:    
    input_text, target_text, _ = line.split('\t')
    # We use "tab" as the "start sequence" character
    # for the targets, and "\n" as "end sequence" character.
    target_text = '\t' + target_text + '\n'
    input_texts.append(input_text)
    target_texts.append(target_text)

До сих пор мы заполняли английские тексты для ввода текстов и французские тексты для целевых текстовых списков соответственно.

Теперь нам нужен уникальный список символов как для английских, так и для французских текстов. Для этого мы добавляем все уникальные символы в текстах в соответствующие списки, как показано ниже.

for line in lines[: 10000]:
    input_text, target_text, _ = line.split('\t')
    # We use "tab" as the "start sequence" character
    # for the targets, and "\n" as "end sequence" character.
    target_text = '\t' + target_text + '\n'
    input_texts.append(input_text)
    target_texts.append(target_text)
    for char in input_text:
        if char not in input_characters:
            input_characters.add(char)
    for char in target_text:
        if char not in target_characters:
            target_characters.add(char)
print(input_characters)
print(target_characters)
output:
{'?', 's', 'Q', 'o', 'v', '8', '0', 'R', 'L', 'n', 'T', 'I', 'H', ',', '9', 'B', 'W', 'l', 'm', 'A', ' ', 'f', 'U', 'k', 'y', '1', 'c', '5', 'V', 'O', 'h', ':', 'j', 'e', 'z', '$', '&', 'C', 'q', 'M', '%', 'w', 'r', 'i', 'g', 'b', '-', '2', '7', 'P', 'Y', 'd', 'N', 'S', 'D', "'", '!', '6', '.', 'x', '3', 'F', 't', 'J', 'K', 'E', 'a', 'u', 'G', 'p'}
{'ô', '?', 'Q', 'o', 'ê', '0', 'à', 'm', 'f', '5', 'V', '«', 'O', 'j', 'e', '&', '\xa0', 'M', 'i', '2', 'd', 'D', "'", 'ï', 'K', 'J', 'E', '1', 'è', 'À', 't', 'é', '\u202f', 'v', ')', 'B', 'œ', '’', 'l', '(', 'c', ':', '$', '\t', 'C', 'q', 'N', 'S', 'x', '3', 'p', '8', 'R', 'L', 'T', 'I', '9', 'É', 'A', 'k', 'y', 'û', 'z', 'r', '»', '-', 'P', 'Y', '!', '.', 'a', 'u', 'Ç', 's', 'ç', 'n', 'H', ',', 'U', ' ', 'Ê', 'ë', '\n', 'h', 'ù', '%', 'g', 'b', '\u2009', 'F', 'â', 'G', 'î'}

Затем мы хотим определить некоторые параметры, которые будут использоваться для части разработки функций.

input_characters: отсортированный список вводимых символов.

target_characters: отсортированный список целевых символов.

num_encoder_tokens: длина списка вводимых символов

num_decoder_tokens: длина целевого списка символов

max_encoder_seq_length: максимальная длина текста во входном наборе.

max_decoder_seq_length: длина максимальной длины текста в целевом наборе.

input_characters = sorted(list(input_characters))
target_characters = sorted(list(target_characters))
num_encoder_tokens = len(input_characters)
num_decoder_tokens = len(target_characters)
max_encoder_seq_length = max([len(txt) for txt in input_texts])
max_decoder_seq_length = max([len(txt) for txt in target_texts])

нам также нужен символ для отображения индекса ввода и целевого списка символов.

input_token_index = dict([(char, i) for i, char in enumerate(input_characters)])
target_token_index = dict([(char, i) for i, char in enumerate(target_characters)])
print(input_token_index)
print(target_token_index)
Output
{' ': 0, '!': 1, '$': 2, '%': 3, '&': 4, "'": 5, ',': 6, '-': 7, '.': 8, '0': 9, '1': 10, '2': 11, '3': 12, '5': 13, '6': 14, '7': 15, '8': 16, '9': 17, ':': 18, '?': 19, 'A': 20, 'B': 21, 'C': 22, 'D': 23, 'E': 24, 'F': 25, 'G': 26, 'H': 27, 'I': 28, 'J': 29, 'K': 30, 'L': 31, 'M': 32, 'N': 33, 'O': 34, 'P': 35, 'Q': 36, 'R': 37, 'S': 38, 'T': 39, 'U': 40, 'V': 41, 'W': 42, 'Y': 43, 'a': 44, 'b': 45, 'c': 46, 'd': 47, 'e': 48, 'f': 49, 'g': 50, 'h': 51, 'i': 52, 'j': 53, 'k': 54, 'l': 55, 'm': 56, 'n': 57, 'o': 58, 'p': 59, 'q': 60, 'r': 61, 's': 62, 't': 63, 'u': 64, 'v': 65, 'w': 66, 'x': 67, 'y': 68, 'z': 69}
{'\t': 0, '\n': 1, ' ': 2, '!': 3, '$': 4, '%': 5, '&': 6, "'": 7, '(': 8, ')': 9, ',': 10, '-': 11, '.': 12, '0': 13, '1': 14, '2': 15, '3': 16, '5': 17, '8': 18, '9': 19, ':': 20, '?': 21, 'A': 22, 'B': 23, 'C': 24, 'D': 25, 'E': 26, 'F': 27, 'G': 28, 'H': 29, 'I': 30, 'J': 31, 'K': 32, 'L': 33, 'M': 34, 'N': 35, 'O': 36, 'P': 37, 'Q': 38, 'R': 39, 'S': 40, 'T': 41, 'U': 42, 'V': 43, 'Y': 44, 'a': 45, 'b': 46, 'c': 47, 'd': 48, 'e': 49, 'f': 50, 'g': 51, 'h': 52, 'i': 53, 'j': 54, 'k': 55, 'l': 56, 'm': 57, 'n': 58, 'o': 59, 'p': 60, 'q': 61, 'r': 62, 's': 63, 't': 64, 'u': 65, 'v': 66, 'x': 67, 'y': 68, 'z': 69, '\xa0': 70, '«': 71, '»': 72, 'À': 73, 'Ç': 74, 'É': 75, 'Ê': 76, 'à': 77, 'â': 78, 'ç': 79, 'è': 80, 'é': 81, 'ê': 82, 'ë': 83, 'î': 84, 'ï': 85, 'ô': 86, 'ù': 87, 'û': 88, 'œ': 89, '\u2009': 90, '’': 91, '\u202f': 92}

Разработка функций

Согласно Википедии, Разработка функций - это процесс использования предметных знаний данных для создания функций, которые заставляют работать алгоритмы машинного обучения. Разработка функций является фундаментальной для применения машинного обучения, она сложна и дорога.

Теперь пришло время создать функции. Для генерации вектора признаков мы используем горячую кодировку. Чтобы узнать больше о горячем кодировании, посмотрите это видео.

Пояснение: горячая кодировка

Чтобы сгенерировать функции, нам сначала нужно определить переменные для хранения быстро закодированных данных. мы используем массивы 3D numpy для хранения данных с горячим кодированием. Первое измерение соответствует количеству рассмотренных нами текстов-примеров (здесь 10000). Второе измерение обозначает максимальную длину последовательности кодера / декодера (что означает длину самого длинного текста в выборках), а третье измерение обозначает количество уникальных символов, присутствующих в input_charecter / target_charecter.

Мы используем три переменные для хранения данных.

  1. encoder_input_data: входные данные кодировщика хранят данные входного текста с горячим кодированием (английский текст).
  2. decoder_input_data: входные данные декодера хранят один входной текст с горячим кодированием (соответствующий французский текст).
  3. decoder_target_data: целевые данные декодера хранят целевые данные с горячим кодированием (т.е. данные, которые должны быть сгенерированы в соответствии с decoder_input_data).

encoder_input_data = np.zeros((len(input_texts), max_encoder_seq_length, num_encoder_tokens),dtype='float32')
decoder_input_data = np.zeros((len(input_texts), max_decoder_seq_length, num_decoder_tokens),dtype='float32')
decoder_target_data = np.zeros((len(input_texts), max_decoder_seq_length, num_decoder_tokens),dtype='float32')
for i, (input_text, target_text) in enumerate(zip(input_texts, target_texts)):
    for t, char in enumerate(input_text):
        encoder_input_data[i, t, input_token_index[char]] = 1.
    encoder_input_data[i, t + 1:, input_token_index[' ']] = 1.
    for t, char in enumerate(target_text):
        # decoder_target_data is ahead of decoder_input_data by one timestep
        decoder_input_data[i, t, target_token_index[char]] = 1.
        if t > 0:
            # decoder_target_data will be ahead by one timestep
            # and will not include the start character.
            decoder_target_data[i, t - 1, target_token_index[char]] = 1.
    decoder_input_data[i, t + 1:, target_token_index[' ']] = 1.
    decoder_target_data[i, t:, target_token_index[' ']] = 1.

На данный момент мы завершили подготовку векторов признаков, затем нам нужно передать эти входные данные вектора признаков в соответствующие модели кодировщика и декодера.

Построение модели

Кодировщик: сначала мы определяем входную последовательность для кодировщика. Входной сигнал подается на кодировщик посимвольно. Для encoder_LSTM мы установили return_state = True. Это означает, что мы получаем окончательное закодированное скрытое представление в конце входной последовательности. Это окончательное закодированное представление затем используется для инициализации состояния декодера.

# Define an input sequence and process it.
encoder_inputs = Input(shape=(None, num_encoder_tokens))
encoder = LSTM(latent_dim, return_state=True)
encoder_outputs, state_h, state_c = encoder(encoder_inputs)
# We discard `encoder_outputs` and only keep the states.
encoder_states = [state_h, state_c]

Декодер: сначала мы определяем входную последовательность для декодера. Входными данными в декодер для первого целевого символа будет состояние окончательного кодирования и первый символ из однократно закодированных decoder_input_data. Начиная со следующего шага, входными данными для декодера будут состояние ячейки, скрытое состояние (скрытые представления) и следующий символ из данных декодера с горячим кодированием.

Здесь, в декодере LSTM, мы указываем return_sequences = True вместе с return_state = True, это означает, что мы учитываем вывод декодера и два состояния декодера на каждом временном шаге.

# Set up the decoder, using `encoder_states` as initial state.
decoder_inputs = Input(shape=(None, num_decoder_tokens))
# We set up our decoder to return full output sequences,
# and to return internal states as well. We don't use the
# return states in the training model, but we will use them in inference.
decoder_lstm = LSTM(latent_dim, return_sequences=True, return_state=True)
decoder_outputs, _, _ = decoder_lstm(decoder_inputs,
                                     initial_state=encoder_states)
decoder_dense = Dense(num_decoder_tokens, activation='softmax')
decoder_outputs = decoder_dense(decoder_outputs)

Обучение

Модель, которую мы собираемся обучить, довольно проста. Модель будет обучена со 100 эпохами с оптимизатором RMSprop.

model = Model([encoder_inputs, decoder_inputs], decoder_outputs)
# Run training
model.compile(optimizer='rmsprop', loss='categorical_crossentropy',
              metrics=['accuracy'])
model.fit([encoder_input_data, decoder_input_data],       decoder_target_data,
          batch_size=batch_size,
          epochs=epochs,
          validation_split=0.2)

На обучение нашей модели ушло много времени. Сохраним наш прогресс:

# Save model
model.save('s2s.h5')
print(model.summary())

Сохраним код в train.py

Тестирование (режим вывода)

Для тестирования мы начинаем с загрузки сохраненного файла (s2s.h5) в новый файл python. Назовем новый файл predic.py.

имя файла: прогноз.py (то же расположение, что и train.py)

Для начала мы должны инициализировать (например, input_characters, input token_index, и т. Д.) Все переменные, как это было сделано в train.py. Это делается для восстановления модели кодер-декодер для прогнозирования.

from __future__ import print_function
from keras.models import Model, load_model
from keras.layers import Input, LSTM, Dense
import numpy as np
batch_size = 64  # Batch size for training.
epochs = 100  # Number of epochs to train for.
latent_dim = 256  # Latent dimensionality of the encoding space.
num_samples = 10000  # Number of samples to train on.
# Vectorize the data.
input_texts = []
target_texts = []
input_characters = set()
target_characters = set()
path = 'fra-eng/fra.txt'
with open(path, 'r', encoding='utf-8') as f:
    lines = f.read().split('\n')
for line in lines[: min(num_samples, len(lines) - 1)]:
    input_text, target_text, _ = line.split('\t')
    # We use "tab" as the "start sequence" character
    # for the targets, and "\n" as "end sequence" character.
    target_text = '\t' + target_text + '\n'
    input_texts.append(input_text)
    target_texts.append(target_text)
    for char in input_text:
        if char not in input_characters:
            input_characters.add(char)
    for char in target_text:
        if char not in target_characters:
            target_characters.add(char)
input_characters = sorted(list(input_characters))
target_characters = sorted(list(target_characters))
num_encoder_tokens = len(input_characters)
num_decoder_tokens = len(target_characters)
max_encoder_seq_length = max([len(txt) for txt in input_texts])
max_decoder_seq_length = max([len(txt) for txt in target_texts])
print('Number of samples:', len(input_texts))
print('Number of unique input tokens:', num_encoder_tokens)
print('Number of unique output tokens:', num_decoder_tokens)
print('Max sequence length for inputs:', max_encoder_seq_length)
print('Max sequence length for outputs:', max_decoder_seq_length)
input_token_index = dict(
    [(char, i) for i, char in enumerate(input_characters)])
target_token_index = dict(
    [(char, i) for i, char in enumerate(target_characters)])
encoder_input_data = np.zeros(
    (len(input_texts), max_encoder_seq_length, num_encoder_tokens),
    dtype='float32')
for i, input_text in enumerate(input_texts):
    for t, char in enumerate(input_text):
        encoder_input_data[i, t, input_token_index[char]] = 1.

Теперь загрузим модель.

# Restore the model and construct the encoder and decoder.
model = load_model('s2s.h5')

После загрузки сохраненной модели приступим к реконструкции кодировщика.

encoder_input: здесь мы фактически указываем тип принимаемого входного кодировщика. (т.е. Tensor («input_1: 0», shape = (None, None, 70), dtype = float32)). аналогичным образом мы определяем параметры encoder_outputs, state_h_enc (скрытое состояние) и state_c_enc (состояние ячейки) (скрытое состояние и состояние ячейки являются скрытыми представлениями).

Наконец, используя входной тензор (encoder_input) и выходной тензор (скрытые представления, например, state_h_enc, state_c_enc), мы создаем из него модель. Это делается с помощью функции Model () в keras.

encoder_inputs = model.input[0]   # input_1
encoder_outputs, state_h_enc, state_c_enc = model.layers[2].output   # lstm_1
encoder_states = [state_h_enc, state_c_enc]
encoder_model = Model(encoder_inputs, encoder_states)

После создания модели кодировщика приступим к построению декодера.

decoder_inputs = model.input[1]   # input_2
decoder_state_input_h = Input(shape=(latent_dim,), name='input_3')
decoder_state_input_c = Input(shape=(latent_dim,), name='input_4')
decoder_states_inputs = [decoder_state_input_h, decoder_state_input_c]
decoder_lstm = model.layers[3]
decoder_outputs, state_h_dec, state_c_dec = decoder_lstm(
    decoder_inputs, initial_state=decoder_states_inputs)
decoder_states = [state_h_dec, state_c_dec]
decoder_dense = model.layers[4]
decoder_outputs = decoder_dense(decoder_outputs)
decoder_model = Model(
    [decoder_inputs] + decoder_states_inputs,
    [decoder_outputs] + decoder_states)

Затем мы создаем два словаря обратного просмотра для декодирования обратных последовательностей.

# Reverse-lookup token index to decode sequences back to
# something readable.
reverse_input_char_index = dict(
    (i, char) for char, i in input_token_index.items())
reverse_target_char_index = dict(
    (i, char) for char, i in target_token_index.items())

Наконец, давайте напишем функцию прогнозирования. (Функция декодирования)

# Decodes an input sequence.  Future work should support beam search.
def decode_sequence(input_seq):
    # Encode the input as state vectors.
    states_value = encoder_model.predict(input_seq)
# Generate empty target sequence of length 1.
    target_seq = np.zeros((1, 1, num_decoder_tokens))
    # Populate the first character of target sequence with the start character.
    target_seq[0, 0, target_token_index['\t']] = 1.
# Sampling loop for a batch of sequences
    # (to simplify, here we assume a batch of size 1).
    stop_condition = False
    decoded_sentence = ''
    while not stop_condition:
        output_tokens, h, c = decoder_model.predict(
            [target_seq] + states_value)
# Sample a token
        sampled_token_index = np.argmax(output_tokens[0, -1, :])
        sampled_char = reverse_target_char_index[sampled_token_index]
        decoded_sentence += sampled_char
# Exit condition: either hit max length
        # or find stop character.
        if (sampled_char == '\n' or
           len(decoded_sentence) > max_decoder_seq_length):
            stop_condition = True
# Update the target sequence (of length 1).
        target_seq = np.zeros((1, 1, num_decoder_tokens))
        target_seq[0, 0, sampled_token_index] = 1.
# Update states
        states_value = [h, c]
return decoded_sentence

!!!!!!!! Давайте переведем !!!!!!

input_sentence = "How are you?"
test_sentence_tokenized = np.zeros(
    (1, max_encoder_seq_length, num_encoder_tokens), dtype='float32')
for t, char in enumerate(input_sentence):
    test_sentence_tokenized[0, t, input_token_index[char]] = 1.
print("Input: ", input_sentence)
print("Translated: ",decode_sequence(test_sentence_tokenized))
Output
Input:  How are you?
Translated:  Comment allez-vous ?

Ссылки

  1. Последовательность для последовательного обучения с моделями нейронной сети кодер-декодер, доктор Анант Санкар
  2. Машинный перевод с использованием последовательного обучения.
  3. Десятиминутное введение в последовательное обучение в Керасе
  4. Как построить кодировщик из загруженной модели в Керасе?
  5. Нейронный машинный перевод - Использование seq2seq с Keras
  6. Модели от последовательности к последовательности (Seq2Seq) в журнале Deep Learning - AI