предисловие
В практической инженерии есть несколько чат-ботов, которые напрямую используют глубокое обучение для реализации сквозных чат-ботов, но здесь мы рассмотрим, как реализовать простого чат-бота с использованием модели глубокого обучения seq2seq. В этой статье мы попытаемся использовать TensorFlow для обучения чат-бота на основе seq2seq, а робот будет отвечать на вопросы на основе обучения корпуса.
seq2seq
Принцип механизма seq2seq см. в предыдущей статье.«Модель глубокого обучения seq2seq».
Рекуррентная нейронная сеть
В модели seq2seq используются рекуррентные нейронные сети.Несколько популярных рекуррентных нейронных сетей включают RNN, LSTM и GRU. Принципы механизма этих трех рекуррентных нейронных сетей можно найти в предыдущих статьях.«Рекуррентная нейронная сеть» «Нейронная сеть LSTM» «Нейронная сеть ГРУ».
обучающая выборка
В основном какие-то QA пары,и много открытых данных можно скачать.Вот только небольшая подборка вопросов и ответов.Формат-первая строка вопросов,вторая строка ответов,третья строка вопросов,и четвертая строка ответов И так далее.
предварительная обработка данных
Для обучения вы должны преобразовать данные в числа.Вы можете использовать значения от 0 до n для представления всего словаря, и каждое значение представляет слово, которое определяется VOCAB_SIZE. Существуют также максимальная и минимальная длина вопросов и максимальная и минимальная длина ответов. Кроме того, должны быть определены символы UNK, GO, EOS и PAD, которые представляют неизвестные слова соответственно. Например, если вы превысите диапазон VOCAB_SIZE, вы будете учитывать неизвестные слова. GO представляет собой символ в начале декодера, EOS представляет собой символ в конце ответа, а PAD используется для заполнения, потому что все пары QA, помещенные в одну и ту же модель seq2seq, должны быть одинаковыми входными и выходными данными, поэтому необходимо заполнить более короткий вопрос или ответ PAD. .
limit = {
'maxq': 10,
'minq': 0,
'maxa': 8,
'mina': 3
}
UNK = 'unk'
GO = '<go>'
EOS = '<eos>'
PAD = '<pad>'
VOCAB_SIZE = 1000
Фильтр по ограничению длины QA.
def filter_data(sequences):
filtered_q, filtered_a = [], []
raw_data_len = len(sequences) // 2
for i in range(0, len(sequences), 2):
qlen, alen = len(sequences[i].split(' ')), len(sequences[i + 1].split(' '))
if qlen >= limit['minq'] and qlen <= limit['maxq']:
if alen >= limit['mina'] and alen <= limit['maxa']:
filtered_q.append(sequences[i])
filtered_a.append(sequences[i + 1])
filt_data_len = len(filtered_q)
filtered = int((raw_data_len - filt_data_len) * 100 / raw_data_len)
print(str(filtered) + '% filtered from original data')
return filtered_q, filtered_a
Также нам нужно получить частотную статистику всех слов во всем корпусе, а также посчитать первые n частотных слов как весь словарь по частоте, то есть соответствующий VOCAB_SIZE. Кроме того, нам также нужно получить индекс слова по значению индекса и индекс соответствующего значения индекса по слову.
def index_(tokenized_sentences, vocab_size):
freq_dist = nltk.FreqDist(itertools.chain(*tokenized_sentences))
vocab = freq_dist.most_common(vocab_size)
index2word = [GO] + [EOS] + [UNK] + [PAD] + [x[0] for x in vocab]
word2index = dict([(w, i) for i, w in enumerate(index2word)])
return index2word, word2index, freq_dist
Как было сказано ранее, в нашей модели seq2seq для энкодера длина задачи разная, поэтому если она недостаточно длинна, ее нужно заполнить PAD, например, задача "как дела", если длина установлена на 10, вам нужно заполнить ее словами «как дела, подушечка, подушечка, подушечка, подушечка, подушечка». Для декодера оно должно начинаться с GO и заканчиваться на EOS. Если оно недостаточно длинное, его необходимо заполнить. Например, «отлично, спасибо» следует обрабатывать как «иди, хорошо, спасибо eos pad pad pad pad pad ". Третье, с чем нужно иметь дело, это наша цель. Цель на самом деле такая же, как и входные данные декодера, но у нее просто есть смещение позиции. Например, если убрать go из приведенного выше, она станет "хорошо, спасибо, eos подушечка подушечка подушечка" подушечка подушечка".
def zero_pad(qtokenized, atokenized, w2idx):
data_len = len(qtokenized)
# +2 dues to '<go>' and '<eos>'
idx_q = np.zeros([data_len, limit['maxq']], dtype=np.int32)
idx_a = np.zeros([data_len, limit['maxa'] + 2], dtype=np.int32)
idx_o = np.zeros([data_len, limit['maxa'] + 2], dtype=np.int32)
for i in range(data_len):
q_indices = pad_seq(qtokenized[i], w2idx, limit['maxq'], 1)
a_indices = pad_seq(atokenized[i], w2idx, limit['maxa'], 2)
o_indices = pad_seq(atokenized[i], w2idx, limit['maxa'], 3)
idx_q[i] = np.array(q_indices)
idx_a[i] = np.array(a_indices)
idx_o[i] = np.array(o_indices)
return idx_q, idx_a, idx_o
def pad_seq(seq, lookup, maxlen, flag):
if flag == 1:
indices = []
elif flag == 2:
indices = [lookup[GO]]
elif flag == 3:
indices = []
for word in seq:
if word in lookup:
indices.append(lookup[word])
else:
indices.append(lookup[UNK])
if flag == 1:
return indices + [lookup[PAD]] * (maxlen - len(seq))
elif flag == 2:
return indices + [lookup[EOS]] + [lookup[PAD]] * (maxlen - len(seq))
elif flag == 3:
return indices + [lookup[EOS]] + [lookup[PAD]] * (maxlen - len(seq) + 1)
Затем указанные выше обработанные структуры сохраняются для использования во время обучения.
Построить график
encoder_inputs = tf.placeholder(dtype=tf.int32, shape=[batch_size, sequence_length])
decoder_inputs = tf.placeholder(dtype=tf.int32, shape=[batch_size, sequence_length])
targets = tf.placeholder(dtype=tf.int32, shape=[batch_size, sequence_length])
weights = tf.placeholder(dtype=tf.float32, shape=[batch_size, sequence_length])
Создайте четыре заполнителя, а именно заполнитель ввода кодировщика, заполнитель ввода декодера, целевой заполнитель декодера и заполнитель веса. где batch_size — это количество пакетов входных выборок, а sequence_length — это длина последовательности, которую мы определяем.
cell = tf.nn.rnn_cell.BasicLSTMCell(hidden_size)
cell = tf.nn.rnn_cell.MultiRNNCell([cell] * num_layers)
Создайте структуру рекуррентной нейронной сети, где используется структура LSTM, hidden_size — это количество скрытых слоев, а MultiRNNCell используется, потому что мы хотим создать более сложную сеть, а num_layers — это количество слоев LSTM.
results, states = tf.contrib.legacy_seq2seq.embedding_rnn_seq2seq(
tf.unstack(encoder_inputs, axis=1),
tf.unstack(decoder_inputs, axis=1),
cell,
num_encoder_symbols,
num_decoder_symbols,
embedding_size,
feed_previous=False
)
Используйте функцию embedding_rnn_seq2seq, которую TensorFlow подготовил для нас, чтобы построить структуру seq2seq.Конечно, мы также можем сами построить кодировщик и декодер из LSTM, но для удобства мы можем напрямую использовать embedding_rnn_seq2seq. Функция tf.unstack используется для расширения encoder_inputs и decoder_inputs в список, а num_encoder_symbols и num_decoder_symbols соответствуют номеру нашего словаря. embedding_size - это количество наших слоев встраивания. Переменная feed_previous очень важна. Если установлено значение False, это означает, что это этап обучения. Этап обучения будет использовать decoder_inputs как один из входов декодера, но когда feed_previous имеет значение True , это означает этап прогнозирования, а этап прогнозирования decoder_inputs отсутствует, поэтому мы можем полагаться только на вывод декодера в предыдущий момент как на вход в текущий момент.
logits = tf.stack(results, axis=1)
loss = tf.contrib.seq2seq.sequence_loss(logits, targets=targets, weights=weights)
pred = tf.argmax(logits, axis=2)
train_op = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(loss)
Затем используйте sequence_loss для создания потери.Здесь потеря рассчитывается в соответствии с выводом embedding_rnn_seq2seq.В то же время вывод также может быть использован для прогнозирования.Индекс, соответствующий наибольшему значению, является словом словаря, который используется оптимизатором AdamOptimizer.
Создать сеанс
with tf.Session() as sess:
ckpt = tf.train.get_checkpoint_state(model_dir)
if ckpt and ckpt.model_checkpoint_path:
saver.restore(sess, ckpt.model_checkpoint_path)
else:
sess.run(tf.global_variables_initializer())
epoch = 0
while epoch < 5000000:
epoch = epoch + 1
print("epoch:", epoch)
for step in range(0, 1):
print("step:", step)
train_x, train_y, train_target = loadQA()
train_encoder_inputs = train_x[step * batch_size:step * batch_size + batch_size, :]
train_decoder_inputs = train_y[step * batch_size:step * batch_size + batch_size, :]
train_targets = train_target[step * batch_size:step * batch_size + batch_size, :]
op = sess.run(train_op, feed_dict={encoder_inputs: train_encoder_inputs, targets: train_targets,
weights: train_weights, decoder_inputs: train_decoder_inputs})
cost = sess.run(loss, feed_dict={encoder_inputs: train_encoder_inputs, targets: train_targets,
weights: train_weights, decoder_inputs: train_decoder_inputs})
print(cost)
step = step + 1
if epoch % 100 == 0:
saver.save(sess, model_dir + '/model.ckpt', global_step=epoch + 1)
Создайте сеанс и начните выполнение.Здесь объект tf.train.Saver будет использоваться для сохранения и чтения модели.Для безопасности вы можете сохранять модель через равные промежутки времени.Следующий перезапуск продолжит обучение без перезапуска с нуля Например, количество пар QA невелико, поэтому оно напрямую отправляется в обучение как пакет за один раз, а не делится на несколько пакетов.
предсказывать
with tf.device('/cpu:0'):
batch_size = 1
sequence_length = 10
num_encoder_symbols = 1004
num_decoder_symbols = 1004
embedding_size = 256
hidden_size = 256
num_layers = 2
encoder_inputs = tf.placeholder(dtype=tf.int32, shape=[batch_size, sequence_length])
decoder_inputs = tf.placeholder(dtype=tf.int32, shape=[batch_size, sequence_length])
targets = tf.placeholder(dtype=tf.int32, shape=[batch_size, sequence_length])
weights = tf.placeholder(dtype=tf.float32, shape=[batch_size, sequence_length])
cell = tf.nn.rnn_cell.BasicLSTMCell(hidden_size)
cell = tf.nn.rnn_cell.MultiRNNCell([cell] * num_layers)
results, states = tf.contrib.legacy_seq2seq.embedding_rnn_seq2seq(
tf.unstack(encoder_inputs, axis=1),
tf.unstack(decoder_inputs, axis=1),
cell,
num_encoder_symbols,
num_decoder_symbols,
embedding_size,
feed_previous=True,
)
logits = tf.stack(results, axis=1)
pred = tf.argmax(logits, axis=2)
saver = tf.train.Saver()
with tf.Session() as sess:
module_file = tf.train.latest_checkpoint('./model/')
saver.restore(sess, module_file)
map = Word_Id_Map()
encoder_input = map.sentence2ids(['you', 'want', 'to', 'turn', 'twitter', 'followers', 'into', 'blog', 'readers'])
encoder_input = encoder_input + [3 for i in range(0, 10 - len(encoder_input))]
encoder_input = np.asarray([np.asarray(encoder_input)])
decoder_input = np.zeros([1, 10])
print('encoder_input : ', encoder_input)
print('decoder_input : ', decoder_input)
pred_value = sess.run(pred, feed_dict={encoder_inputs: encoder_input, decoder_inputs: decoder_input})
print(pred_value)
sentence = map.ids2sentence(pred_value[0])
print(sentence)
На этапе прогнозирования также создается та же модель, затем загружается модель, сохраненная во время обучения, а затем прогнозируется ответ на вопрос. На этапе прогнозирования мы можем использовать процессор для его выполнения, избегая использования графического процессора. Шаги создания графика в основном такие же, как и обучение, и параметры должны быть такими же, разница в том, что нам нужно установить для параметра feed_previous функции embedding_rnn_seq2seq значение True, потому что у нас нет ввода декодера. Кроме того, нам не нужны функция потерь и оптимизатор, просто предусмотрите функцию прогнозирования.
После создания сеанса начинается выполнение.Сначала загружается модель в каталоге моделей, затем тестируемая задача преобразуется в векторную форму, а затем выполняется прогноз.Вывод выглядит следующим образом:
['как ты это делаешь', '', '', '', '', ''].
github
Ниже приведенырекламироватьиСвязанное Чтение
========Время рекламы========
Моя новая книга «Анализ дизайна ядра Tomcat» продана на Jingdong, нуждающиеся друзья могут перейти кitem.JD.com/12185360.Контракт…Зарезервировать. Спасибо друзья.
Зачем писать «Анализ проектирования ядра Tomcat»
=========================
Связанное чтение:
«Модель глубокого обучения seq2seq»
«Нейронные сети для машинного обучения»
Добро пожаловать, чтобы следовать: