
Представляем разбор применения алгоритмов машинного обучения с использованием технологий LSTM для создания текстов.
В итоге должен получиться генератор более-менее осмысленного текста. Способы создания текстов на специальную, определенную пользователем, тему затронуты не будут – но в целом, текст будет создан в том стиле, в котором написана «обучающая выборка».
Кстати об обучающей выборке: в качестве оной будут использованы народные сказки братьев Гримм. Эти тексты будут обработаны, разбиты на биграммы уровня символов, из которых будет составлен словарь из уникальных биграмм.
Набор необходимых данных лежит на https://www.cs.cmu.edu/~spok/grimmtmp/. Из текстовых файлов на этом сайте будут загружены и поделены на биграммы тексты первых 100 сказок. Получится что-то вроде: [‘th’, ‘e’, ‘ki’, ‘ng’, ‘w’, ‘as’…]. Для этого есть скрипт:
url = 'https://www.cs.cmu.edu/~spok/grimmtmp/' def dwnload(filename): print('Downloading file: ', dir_name+ os.sep+filename) filename, _ = urlretrieve(url + filename, dir_name+os.sep+filename) filenames = [format(i, '03d')+'.txt' for i in range(1,101)] for fn in filenames: dwnload(fn) def read_data(filename): with open(filename) as f: data = tensorflow.compat.as_str(f.read()) data = list(data.lower()) return data documents = [] for i in range(num_files): chars = read_data('folder/'+filenames[i]) # Break the data into bigrams two_grams = [''.join(chars[ch_i:ch_i+2]) for ch_i in range(0,len(chars)-2,2)] # Create a list of lists with bigrams documents.append(two_grams)
Использование символьных биграмм значительно уменьшает размер словаря по сравнению с использованием отдельных слов. И еще, для более быстрой обработки, все биграммы, встречающиеся в словаре менее 10 раз, будут заменены токеном (‘UNK’).
Хотя в TensorFlow и есть подбиблиотеки, где реализованы функции для работы с LSTM, они будут написаны самостоятельно, ведь важно не просто запустить код, но постараться разобраться в том, как это работает.
Для обучения с LSTM есть множество параметров и гиперпараметров. Все они имеют разный эффект, рассмотрим некоторые из них. Затем обсудим, как эти параметры используются для записи операций, выполняемых в LSTM. За этим последует понимание того, как будем последовательно передавать данные в LSTM. Обсудим, какими способами можно реализовать оптимизацию параметров с помощью отсечения градиента. И, наконец, рассмотрим возможности использования модели для вывода прогнозов, которые по сути являются биграммами, но в конечном итоге составят осмысленный текст.
Гиперпараметры:
• num_nodes: обозначает количество нейронов в памяти ячейки. Когда данных много, увеличение сложности памяти ячейки может дать лучшую производительность; однако в то же время это замедляет вычисления;
• batch_size: объем данных, обрабатываемых за один шаг. Увеличение этого параметра повышает производительность, но предъявляет более высокие требования к памяти;
• num_unrollings: количество временных шагов. Чем больше шагов, тем выше производительность, но при этом увеличиваются как требования к памяти, так и время вычислений.
• dropout: иначе — отсев (метод регуляризации), применяется чтобы уменьшить переобучение модели и получить лучшие результаты; этот параметр показывает, какое количество информации будет случайным образом удалено из входов/выходов/переменных перед передачей их для обработки. Это может приводить к повышению производительности.
num_nodes = 128 batch_size = 64 num_unrollings = 50 dropout = 0.0
Параметры:
• ix: это веса для входов функции;
• im: это веса, соединяющие скрытые слои с входами;
• ib: это смещение (bias).
# Connects the current input to the input gate ix = tf.Variable(tf.truncated_normal([vocabulary_size, num_nodes], stddev=0.02)) # Connects the previous hidden state to the input gate im = tf.Variable(tf.truncated_normal([num_nodes, num_nodes], stddev=0.02)) # Bias of the input gate ib = tf.Variable(tf.random_uniform([1, num_nodes],-0.02, 0.02))
Примерно так же будут обозначены веса для скрытых слоев нейронной сети и на выходе.
Для получения фактических прогнозов, определим слой softmax:
# Softmax Classifier weights and biases. w = tf.Variable(tf.truncated_normal([num_nodes, vocabulary_size], stddev=0.02)) b = tf.Variable(tf.random_uniform([vocabulary_size],-0.02,0.02))
Операции в ячейке LSTM:
• Вычисление выходных данных;
• Расчет состояния ячейки;
• Расчет внешнего скрытого состояния;
# Definition of the cell computation. def lstm_cell(i, o, state): """Create an LSTM cell""" input_gate = tf.sigmoid(tf.matmul(i, ix) + tf.matmul(o, im) + ib) forget_gate = tf.sigmoid(tf.matmul(i, fx) + tf.matmul(o, fm) + fb) update = tf.matmul(i, cx) + tf.matmul(o, cm) + cb state = forget_gate * state + input_gate * tf.tanh(update) output_gate = tf.sigmoid(tf.matmul(i, ox) + tf.matmul(o, om) + ob) return output_gate * tf.tanh(state), state
Входные (тренировочные) данные и лейблы:
Входные данные для обучения представляют собой список с последовательными пакетами данных, где каждый пакет данных имеет размер [batch_size, word_size]:
train_inputs, train_labels = [],[] # Defining unrolled training inputs for ui in range(num_unrollings): train_inputs.append(tf.placeholder(tf.float32, shape=[batch_size,vocabulary_size],name='train_inputs_%d'%ui)) train_labels.append(tf.placeholder(tf.float32, shape=[batch_size,vocabulary_size], name = 'train_labels_%d'%ui))
Данные для тестирования и валидации:
# Validation data placeholders valid_inputs = tf.placeholder(tf.float32, shape=[1,vocabulary_size],name='valid_inputs') valid_labels = tf.placeholder(tf.float32, shape=[1,vocabulary_size], name = 'valid_labels') # Text generation: batch 1, no unrolling. test_input = tf.placeholder(tf.float32, shape=[1, vocabulary_size], name = 'test_input')
Последовательные вычисления, для обработки данных:
Здесь рекурсивно рассчитываются выходы ячеек и логиты (модели логистических функций) для скрытых выходных данных из тренировочного сета.
# Keeps the calculated state outputs in all the unrollings # Used to calculate loss outputs = list() # These two python variables are iteratively updated each step of unrolling output = saved_output state = saved_state # Compute the hidden state (output) and cell state (state) # recursively for all the steps in unrolling for i in train_inputs: output, state = lstm_cell(i, output, state) output = tf.nn.dropout(output,keep_prob=1.0-dropout) # Append each computed output value outputs.append(output) # calculate the score values logits = tf.matmul(tf.concat(axis=0, values=outputs), w) + b
Далее, перед расчетом потерь, нужно убедиться, что значения выводов обновлены до самого последнего вычисленного значения. Для этого, добавим условие tf.control_dependencies:
with tf.control_dependencies([saved_output.assign(output), saved_state.assign(state)]): # Calculate the training loss by # concatenating the results from all the unrolled time steps loss = tf.reduce_mean( tf.nn.softmax_cross_entropy_with_logits_v2( logits=logits, labels=tf.concat(axis=0, values=train_labels)))
Также определяем логику прямого распространения ошибки для валидационных данных. Dropout на валидационных данных использован не будет:
# Validation phase related inference logic # Compute the LSTM cell output for validation data valid_output, valid_state = lstm_cell( valid_inputs, saved_valid_output, saved_valid_state) # Compute the logits valid_logits = tf.nn.xw_plus_b(valid_output, w, b)
Оптимайзер:
Определим процесс оптимизации. Будем использовать Adam-оптимайзер, который на сегодняшний день является одним из лучших на основе стохастического градиента. Переменная gstep в коде используется для уменьшения скорости обучения с течением времени. Подробности будут в следующем разделе. Кроме того, будем использовать отсечение градиента, чтобы избежать так называемых «взрывов» градиента:
# Decays learning rate everytime the gstep increases tf_learning_rate = tf.train.exponential_decay(0.001,gstep,decay_steps=1, decay_rate=0.5) # Adam Optimizer. And gradient clipping. optimizer = tf.train.AdamOptimizer(tf_learning_rate) gradients, v = zip(*optimizer.compute_gradients(loss)) # Clipping gradients gradients, _ = tf.clip_by_global_norm(gradients, 5.0) optimizer = optimizer.apply_gradients( zip(gradients, v))
Убывающая скорость обучения вместо постоянной — распространенный метод, используемый в глубоком обучении для повышения производительности и уменьшения переобучения. Ключевая идея в том, чтобы уменьшить скорость обучения (например, в 2 раза), если сложность проверки не уменьшается в течение заданного количества эпох.
Вот как это реализовано. Сначала определяем gstep и операцию увеличения gstep (inc_gstep), следующим образом:
# learning rate decay gstep = tf.Variable(0,trainable=False,name='global_step') # Running this operation will cause the value of gstep # to increase, while in turn reducing the learning rate inc_gstep = tf.assign(gstep, gstep+1)
Затем, всякий раз, когда потери на валидационной выборке не уменьшаются, вызываем inc_gstep следующим образом:
# decrease the learning rate decay_threshold = 5 # Keep counting perplexity increases decay_count = 0 min_perplexity = 1e10 # Learning rate decay logic def decay_learning_rate(session, v_perplexity): global decay_threshold, decay_count, min_perplexity # Decay learning rate if v_perplexity < min_perplexity: decay_count = 0 min_perplexity= v_perplexity else: decay_count += 1 if decay_count >= decay_threshold: print('\t Reducing learning rate') decay_count = 0 session.run(inc_gstep)
Прогнозирование:
Теперь можно делать прогнозы, просто применяя softmax к логитам, которые рассчитали ранее. Определим операцию прогнозирования для логитов на валидационных данных:
train_prediction = tf.nn.softmax(logits) # Make sure that the state variables are updated # before moving on to the next iteration of generation with tf.control_dependencies([saved_valid_output.assign(valid_output), saved_valid_state.assign(valid_state)]): valid_prediction = tf.nn.softmax(valid_logits)
Расчет потерь:
train_perplexity_without_exp = tf.reduce_sum( tf.concat(train_labels,0)*-tf.log(tf.concat( train_prediction,0)+1e-10))/(num_unrollings*batch_size) # Compute validation perplexity valid_perplexity_without_exp = tf.reduce_sum(valid_labels*-tf. log(valid_prediction+1e-10))
Чем выше значение потерь, тем хуже работает модель. Если потери большие следует попробовать изменить гиперпараметры. Есть и другие метрики для проверки качества модели, но лучше перейдем к следующему, финальному этапу.
Генерация текста:
Наконец, определим переменные и операции, необходимые для создания нового текста. Они определяются аналогично тому, что я сделал для обучающих данных. Только вместо проверки будет вывод результата:
# Text generation: batch 1, no unrolling. test_input = tf.placeholder(tf.float32, shape=[1, vocabulary_size], name = 'test_input') # Same variables for testing phase saved_test_output = tf.Variable(tf.zeros([1 num_nodes]), trainable=False, name='test_hidden') saved_test_state = tf.Variable(tf.zeros([1, num_nodes]), trainable=False, name='test_cell') # Compute the LSTM cell output for testing data test_output, test_state = lstm_cell( test_input, saved_test_output, saved_test_state) # Make sure that the state variables are updated # before moving on to the next iteration of generation with tf.control_dependencies([saved_test_output.assign(test_output), saved_test_state.assign(test_state)]): test_prediction = tf.nn.softmax(tf.nn.xw_plus_b(test_output, w, b)) # Reset test state reset_test_state = tf.group( saved_test_output.assign(tf.random_normal([1, num_nodes],stddev=0.05)), saved_test_state.assign(tf.random_normal([1, num_nodes],stddev=0.05)))
Пример сгенерированного текста:
they saw that the birds were at her bread, and threw behind him a comb which made a great ridge with a thousand times thousands of spikes. That was a collier. the nixie was at church, and thousands of spikes, they were flowers, however, and had hewn through the glass, the children had formed a hill of mirrors, and was so slippery that it was impossible for th nixie to cross it. then she thought, i will go home quickly and fetch my axe, and cut the hill of glass in half. long before she returned, however, and had hewn through the glass, the children saw her from afar, and he sat down close to it, and was so slippery that it was impossible for the nixie to cross it.
Текст на английском поскольку для обучения были использованы английские версии сказок братьев Гримм. Его можно перевести на русский и вы увидите, что содержание окажется вполне осмысленным. Так же можно использовать другие датасеты, или создать свой из интересных книг. Истории, созданные алгоритмами машинного обучения, гарантированно вас удивят.
Так же советую к прочтению книгу: Natural Language Processing with TensorFlow от автора Thushan Ganegedara.
ссылка на оригинал статьи https://habr.com/ru/post/656635/
Добавить комментарий