outputs = [int(np.argmax(logit, axis=1))
for logit in output_logits]
# If there is an EOS symbol in outputs, cut them
# at that point. (Если в выводах есть символ EOS, обрезаем их в этом месте)
if data_utils.EOS_ID in outputs:
outputs = outputs[: outputs.index(data_utils.EOS_ID)]
# Print out French sentence corresponding to outputs. (Выдаем французское предложение, соответствующее выводам)
print(" ".join([tf.compat.as_str(rev_fr_vocab[output])
for output in outputs]))
print("> ", end="")
sys.stdout.flush()
sentence = sys.stdin.readline()
На этом мы заканчиваем высокоуровневое рассмотрение обучения и использования моделей. Мы сильно абстрагировались от самой модели, и некоторым пользователям достаточно и этого. Но следует рассказать и об особенностях функции step(). Она отвечает за расчет целевой функции модели, соответствующее обновление весов и задание графа вычислений для модели. Начнем с первого.
Функция step() получает несколько аргументов: сессия TensorFlow, список векторов для ввода в кодер, входные данные для декодера, целевые веса, выбранное во время обучения значение bucket_id и логический флаг forward_only, который определяет, использовать градиентную оптимизацию для обновления весов или заморозить их. Отметим, что изменение этого флага с False на True позволило нам декодировать произвольное предложение и оценить работу модели на тестовом наборе данных:
def step(self, session, encoder_inputs, decoder_inputs,
target_weights, bucket_id, forward_only):
После нескольких защитных проверок того, что все векторы имеют совместимые размеры, мы готовим функции для прямого и обратного прохода. Метод прямого прохода получает всю информацию, изначально переданную функции step(), то есть необходимую при вычислении величины ошибки для одного примера:
# Check if the sizes match. (Проверяем, сходятся ли размеры)
encoder_size, decoder_size = self.buckets[bucket_id]
if len(encoder_inputs)!= encoder_size:
raise ValueError("Encoder length must be equal to the one in bucket,"
" %d!= %d." % (len(
encoder_inputs), encoder_size))
if len(decoder_inputs)!= decoder_size:
raise ValueError("Decoder length must be equal to the one
in bucket,"
" %d!= %d." % (len(decoder_inputs),
decoder_size))
if len(target_weights)!= decoder_size:
raise ValueError("Weights length must be equal to the one
in bucket,"
" %d!= %d." % (len(target_weights),
decoder_size))
# Input feed: encoder inputs, decoder inputs, target_weights,
# as provided. (Ввод: вводы кодера, вводы декодера, target_weights в данном порядке)
input_feed = {}
for l in xrange(encoder_size):
input_feed[self.encoder_inputs[l].name] = encoder_inputs[l]
for l in xrange(decoder_size):
input_feed[self.decoder_inputs[l].name] = decoder_inputs[l]
input_feed[self.target_weights[l].name] = target_weights[l]
# Since our targets are decoder inputs shifted by one,
# we need one more. (Поскольку наши цели — вводы декодера со сдвигом 1, нужна еще одна цель)
last_target = self.decoder_inputs[decoder_size].name
input_feed[last_target] = np.zeros([self.batch_size],
dtype=np.int32)
Метод обратного прохода, если ошибка рассчитана и необходимо ее распространить обратно по сети, содержит операцию обновления, которая выполняет стохастический градиентный спуск, вычисляет норму градиента и потери на пакет:
# Output feed: depends on whether we do a backward step or
# not. (Вывод: зависит от того, делается шаг назад или нет)
if not forward_only:
output_feed = [self.updates[bucket_id], # Update Op that
# does SGD. (Операция обновления запускает обратный градиентный спуск)
self.gradient_norms[bucket_id], # Gradient
# norm. (норма градиента)
self.losses[bucket_id]] # Loss for this
# batch. (Потери на пакет)
else:
output_feed = [self.losses[bucket_id]] # Loss for this
# batch. (потери на пакет)
for l in xrange(decoder_size): # Output logits. (Исходящие логиты)
output_feed.append(self.outputs[bucket_id][l])
Два этих метода передаются в session.run(). В зависимости от флага forward_only возвращаются либо норма градиента и ошибка для сбора статистики, либо выходные данные для декодирования:
outputs = session.run(output_feed, input_feed)
if not forward_only:
return outputs[1], outputs[2], None #, attns
# Gradient norm, loss, no outputs. (Норма градиента, потери, без исходящих)
else:
return None, outputs[0], outputs[1:] #, attns
# No gradient norm, loss, outputs. (Без нормы градиента, потерь, исходящие данные)
Теперь можно изучить саму модель. Ее конструктор задает граф вычислений на основе созданных высокоуровневых компонентов. Сначала дадим краткий обзор метода create_model(), вызывающего этот конструктор, а затем подробно рассмотрим сам конструктор.
Метод create_model() сам по себе несложен: он задействует ряд определенных пользователем или заданных по умолчанию флагов, таких как размеры английского и французского словарей и размер пакета для создания модели с помощью конструктора seq2seq_model.Seq2SeqModel. Очень интересен здесь флаг use_fp16 flag. Используется тип данных в массивах NumPy с пониженной точностью, что приводит к ускорению работы ценой некоторых потерь в точности. Но часто 16-битных представлений потерь и обновлений градиента достаточно: они нередко близки по величине к 32-битным. Создать модель можно при помощи следующего кода:
def create_model(session, forward_only):
Читать дальше
Конец ознакомительного отрывка
Купить книгу