Как обучить чат-бота с помощью TensorFlow (с github)

искусственный интеллект TensorFlow глубокое обучение Нейронные сети робот

предисловие

В практической инженерии есть несколько чат-ботов, которые напрямую используют глубокое обучение для реализации сквозных чат-ботов, но здесь мы рассмотрим, как реализовать простого чат-бота с использованием модели глубокого обучения 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

GitHub.com/sea-boateng/color…

Ниже приведенырекламироватьиСвязанное Чтение

========Время рекламы========

Моя новая книга «Анализ дизайна ядра Tomcat» продана на Jingdong, нуждающиеся друзья могут перейти кitem.JD.com/12185360.Контракт…Зарезервировать. Спасибо друзья.

Зачем писать «Анализ проектирования ядра Tomcat»

=========================

Связанное чтение:

«Нейронная сеть LSTM»

«Рекуррентная нейронная сеть»

«Модель глубокого обучения seq2seq»

«Нейронные сети для машинного обучения»

«Нейронная сеть ГРУ»

Добро пожаловать, чтобы следовать:

这里写图片描述
напишите сюда описание фото