Серия записей модели BERT (2): реализация модели внимания

Нейронные сети

Обзор:

В предыдущей статье "Введение в серию моделей BERT: введение в механизм внимания", на примере машинного перевода вEncoder-Decoderмодель иМодель вниманияОбъясняются основные принципы модели.Эта статья, в сочетании с объяснением предыдущей статьи, реализует задействованные модели и подробно объясняет их, что поможет нам еще больше укрепить наше понимание. Контента в статье много, но принцип на самом деле относительно прост.Пример основан на python и tensorflow 2.4.Не беда, если вы не изучали его раньше.Мы подробно объяснили ключевые места в коде и сотрудничайте с tensorflow, чтобы получить официальную документацию, понять это не проблема.

Прежде чем начать, перечислите конкретную последовательность реализации с четкими целями, которая более способствует обучению:

  1. предварительная обработка текста
  2. Реализация кодировщика
  3. Реализация декодера
  4. Реализация модели внимания
  5. обучение модели
  6. английский->китайский перевод

Код, использованный в статье, был отправленGitHub.com/rotbit/Дьявол. …, но целью этого кода не является создание пригодного для использования коммерческого продукта, поэтому он не преследует фактический оптимальный эффект. Если это поможет нам лучше понять модели кодировщика, декодера и внимания, то мы достигнем ожидаемой цели.

1. Предварительная обработка текста

Что касается обработки текста, то это на самом деле очень просто, то есть преобразование нашего предложения в вектор, представленный числом, например, «Вы уже поели?» в вектор «[2,543,56,12,76]». Причина этого в том, что компьютер неграмотен и знает только 1010, поэтому его необходимо преобразовать в информацию, которую компьютер может понять. Конкретно, как это сделать, сначала на блок-схеме.

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

seg_char: разделение китайских иероглифов

# 把句子按字分开,不破坏英文结构 
# 例如: "我爱tensorflow" -> "['我', '爱', 'tenforflow']"
def seg_char(sent):
    # 首先分割 英文 以及英文和标点
    pattern_char_1 = re.compile(r'([\W])')
    parts = pattern_char_1.split(sent)
    parts = [p for p in parts if len(p.strip())>0]
    # 分割中文
    pattern = re.compile(r'([\u4e00-\u9fa5])')
    chars = pattern.split(sent)
    chars = [w for w in chars if len(w.strip())>0]
    return chars

Для вышеизложенного нам нужно только знать, что такое ввод и как выглядит вывод.

preprocess_sentence: предварительная обработка предложения

# 文本预处理,用空格按字拆分文本
# w 需进行处理的文本
# type 文本类型 0:英文  1:中文
def preprocess_sentence(w, type):
    if type == 0:
        w = re.sub(r"([?.!,¿])", r" \1 ", w)
        w = re.sub(r'[" "]+', " ", w)
    if type == 1:
        #seg_list = jieba.cut(w)
        seg_list = seg_char(w)
        w = " ".join(seg_list)
    w = '<start> ' + w + ' <end>'
    return w

Давайте запустим его и посмотрим, что такое ввод и вывод.

en = "I love tensorflow."
pre_en = preprocess_sentence(en, 0)
print("pre_en=", pre_en)
cn = "我爱tenforflow"
pre_cn = preprocess_sentence(cn, 1)
print("pre_cn=", pre_cn)

вывод:

pre_en= <start> I love tensorflow .  <end>
pre_cn= <start> 我 爱 tenforflow <end>

Давайте посмотрим на этот вывод, мы ввелиВесь текст разделен пробелами, И вДобавлено в начале и в конце,,существуетПервый плюс-идентификатор используется для обозначения начала и конца текста при последующем обучении модели..

create_dataset: загрузка текста, предварительная обработка

# path 数据存储路径
# num_examples 读入记录条数
# 加载文本
def create_dataset(path, num_examples):
    lines = io.open(path, encoding='UTF-8').read().strip().split('\n')
    # 英文文本
    english_words = []
    # 中文文本
    chinese_words = []
    for l in lines[:num_examples]:
        word_arrs = l.split('\t')
        if len(word_arrs) < 2:
            continue
        english_w = preprocess_sentence(word_arrs[0], 0)
        chinese_w = preprocess_sentence(word_arrs[1], 1)
        english_words.append(english_w)
        chinese_words.append(chinese_w)
    # 返回[('<start> Hi .  <end>', '<start> 嗨 。 <end>')]
    return english_words, chinese_words

Используемый набор данных можно скачать отсюдаcmnt.txtДавайте посмотрим на данные в нескольких наборах данных. Строка набора данных является образцом. Видно, что он будет разделен на три столбца, первый столбец — английский язык, второй столбец — китайский перевод, соответствующий английскому, а третий столбец нам не нужен, просто его выбрасываем.Функция create_dataset состоит в том, чтобы читать такой текст и возвращать обработанные китайские и английские списки после обработки.

Hi.	嗨。	CC-BY 2.0 (France) Attribution: tatoeba.org #538123 (CM) & #891077 (Martha)
Hi.	你好。	CC-BY 2.0 (France) Attribution: tatoeba.org #538123 (CM) & #4857568 (musclegirlxyp)
Run.	你用跑的。	CC-BY 2.0 (France) Attribution: tatoeba.org #4008918 (JSakuragi) & #3748344 (egg0073)
Wait!	等等!	CC-BY 2.0 (France) Attribution: tatoeba.org #1744314 (belgavox) & #4970122 (wzhd)

Это старое правило: просто запустите код и посмотрите, как выглядит результат.

# 从cmn.txt读入4条记录
inp_lang, targ_lang = create_dataset('cmn.txt', 4)
print("inp_lang={}, targ_lang={}".format(inp_lang, targ_lang))

Результат вывода: видно, что выходные данные на китайском и английском языках представляют собой два отдельных списка, а китайский и английский переводы двух списков находятся во взаимно однозначном соответствии согласно индексу, например, inp_lang[0]=' Привет. ', соответствующий перевод на китайский язык будет targ_lang='Привет. '

inp_lang=[
    '<start> Hi .  <end>',
    '<start> Hi .  <end>',
    '<start> Run .  <end>',
    '<start> Wait !  <end>'
],

targ_lang=[
    '<start> 嗨 。 <end>',
    '<start> 你 好 。 <end>', 
    '<start> 你 用 跑 的 。 <end>',
    '<start> 等 等 ! <end>'
]

load_dataset, tokenize: создать словарь, текстовый вектор

# # 文本内容转向量
def tokenize(lang):
    lang_tokenizer = tf.keras.preprocessing.text.Tokenizer(filters='')
    lang_tokenizer.fit_on_texts(lang)
    tensor = lang_tokenizer.texts_to_sequences(lang)
    tensor = tf.keras.preprocessing.sequence.pad_sequences(tensor,
                                                           padding='post')
    return tensor, lang_tokenizer

def load_dataset(path, num_examples=None):
    inp_lang, targ_lang = create_dataset(path, num_examples)
    input_tensor, inp_lang_tokenizer = tokenize(inp_lang)
    target_tensor, targ_lang_tokenizer = tokenize(targ_lang)
    return input_tensor, target_tensor, inp_lang_tokenizer, targ_lang_tokenizer

Запустите load_dataset:

inp_tensor, targ_tensor, inp_tokenizer, targ_tokenizer = load_dataset("cmn.txt", 4)
print("inp_tensor={}, inp_tokenizer={}".format(input_tensor, inp_lang_tokenizer.index_word))

Взгляните на результат

inp_tensor=[[1 4 3 2]
 [1 4 3 2]
 [1 5 3 2]
 [1 6 7 2]], 
inp_tokenizer={1: '<start>', 2: '<end>', 3: '.', 4: 'hi', 5: 'run', 6: 'wait', 7: '!'}

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

На этом работа по предварительной обработке текста заканчивается.

2. Реализация энкодера:

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

import tensorflow as tf

# encoder
class Encoder(tf.keras.Model):
     # vocab_size: 词典表大小
     # embedding_dim:词嵌入维度 
     # enc_uints: 编码RNN节点数量 
     # batch_sz 批大小
    def __init__(self, vocab_size, embedding_dim, enc_units, batch_sz):
        super(Encoder, self).__init__()
        self.batch_sz = batch_sz # 批大小
        self.enc_units = enc_units # 编码单元个数(RNN单元个数)
        # Embedding 把一个整数转为一个固定长度的稠密向量
        self.embedding = tf.keras.layers.Embedding(vocab_size, embedding_dim)
        # 创建一个rnn层
        self.rnn = tf.keras.layers.SimpleRNN(self.enc_units,
                                       return_sequences=True,
                                       return_state=True)

    def call(self, x, hidden):
        x = self.embedding(x)
        output, state = self.rnn(x, initial_state=hidden)
        return output, state

    # 张量的概念 tf.Tensor https://www.tensorflow.org/guide/tensor
    def initialize_hidden_state(self):
        return tf.zeros((self.batch_sz, self.enc_units))

Разберем значение параметров

Значение параметров функции __init__:

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

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

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

batch_sz: размер партии, в глубоком обучении функция потерь, вычисляемая при каждом обновлении параметра, рассчитывается не только по одному {данные: метка}, а взвешивается набором {данные: метка} Размер партии равен размеру партии.

В дополнение к инициализированной функции _init_ кодировщик также имеет функцию вызова. Функция вызова — это логика, которая фактически выполняет действие кодирования. Давайте посмотрим на анализ конкретных параметров функции вызова.

Значение параметра и значение вывода функции вызова:

x: обучающая выборка, то есть векторизованный текст, обработанные данные, возвращаемые load_dataset. представляет собой матрицу BATCH_SIZE * длина выборки, т. е. x представляет собой выборочные данные BATCH_SIZE.

hidden:BATCH_SIZE * матрица enc_units.Значение скрытого слоя рекуррентной нейронной сети зависит не только от текущего входа x, но и от скрытого значения предыдущего скрытого слоя, поэтому необходимо ввести скрытое значение предыдущего входа hidden. Здесь, когда вызывается функция вызова, это начальное состояние, поэтому нам нужно только указать начальное значение.

Вот и возникает вопрос, а почему скрыта матрица BATCH_SIZE * enc_uint?

Проще говоря, при обучении модели мы вводим выборки BATCH, а во-вторых, наша RNN определяет нейроны enc_uints, другими словами, для каждого введенного слова будут выходные значения нейрона enc_uints. следовательно,Скрытый слой нашего вывода RNN: BATCH_SIZE * word_size * enc_uints._, _, где размер_слова — количество слов в выборке.

Итак, для нашего начального значения нам нужно ввести только выборки BATCH_SIZE, а количество слов в выборке равно 1, то естьСкрытый параметр функции вызова представляет собой матрицу BATCH_SIZE * 1 * enc_uints.

вывод: BATCH_SIZE *размер_слова * enc_uints, где размер_слова — количество слов в образце

Понимание ввода и вывода очень полезно для понимания кода, так много было сказано выше, вот картинка для подведения итогов.

Схема потока данных энкодера

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

# 加载样本数据
input_tensor, target_tensor, inp_lang, targ_lang=preprocess.load_dataset("./cmn.txt", 30000)

# 采用80-20的比例切分训练集和验证集
input_tensor_train, input_tensor_val, target_tensor_train, \
    target_tensor_val = train_test_split(input_tensor, target_tensor, test_size=0.2)

# 创建一个 tf.data 数据集
BUFFER_SIZE = len(input_tensor_train)
BATCH_SIZE = 32
steps_per_epoch = len(input_tensor_train)//BATCH_SIZE
embedding_dim = 256 # embedding维度
units = 512
vocab_inp_size = len(inp_lang.word_index)+1
vocab_tar_size = len(targ_lang.word_index)+1

dataset = tf.data.Dataset.from_tensor_slices((input_tensor_train, target_tensor_train))
dataset = dataset.batch(BATCH_SIZE, drop_remainder=True)

# 调用encoder
encoder = encoder.Encoder(vocab_inp_size, embedding_dim, units, BATCH_SIZE)
# 初始化一个隐藏状态
sample_hidden = encoder.initialize_hidden_state()
# 执行编码
sample_output, sample_hidden = encoder(example_input_batch, sample_hidden)
print ('output shape:(batch size, sequence length, units){}'.format(sample_output.shape))
print ('Hidden state shape: (batch size, units) {}'.format(sample_hidden.shape))

Результат энкодера:

output shape: (batch size, sequence length, units) (32, 36, 512)
Hidden state shape: (batch size, units) (32, 512)

Реализация Encoder объясняется здесь, но. . . Наш контент еще не закончился.

Здесь мы начинаем говорить о реализации декодера.Функция декодера заключается в переводе текста, закодированного кодировщиком, в целевой текст.Ну, да, функция настолько проста.Мы также используем RNN для реализации декодера.Посмотрите в коде.

3. Реализация декодера

import tensorflow as tf
import attention

class Decoder(tf.keras.Model):
    # vocab_size 词典大小
    # embedding_dim 词嵌入维度
    # dec_uints 解码RNN输出神经元数
    # batch_sz 批大小
    def __init__(self, vocab_size, embedding_dim, dec_units, batch_sz, attention):
        super(Decoder, self).__init__()
        self.batch_sz = batch_sz
        self.dec_units = dec_units
        self.embedding = tf.keras.layers.Embedding(vocab_size, embedding_dim)
        self.rnn = tf.keras.layers.SimpleRNN(self.dec_units,
                                       return_sequences=True,
                                       return_state=True)
        self.fc = tf.keras.layers.Dense(vocab_size)
        self.attention = attention

    # x 是输出目标词语[教师强制](这儿是个整数,是单词在词表中的index)
    def call(self, x, hidden, enc_output):
        # 编码器输出 (enc_output) 的形状 == (批大小,最大长度,隐藏层大小)
        # context_vector 的shape == (批大小,隐藏层大小)
        # attention_weight == (批大小,最大长度, 1)
        context_vector, attention_weights = self.attention(hidden, enc_output)
        #print("context_vector.shape={}".format(context_vector.shape))
        # x 在通过嵌入层后的形状 == (批大小,1,嵌入维度)
        x = self.embedding(x)

        # x 在拼接 (concatenation) 后的形状 == (批大小,1,嵌入维度 + 隐藏层大小)[特征拼接]
        x = tf.concat([tf.expand_dims(context_vector, 1), x], axis=-1)
        #print("x.shape={}".format(x.shape))

        # 将合并后的向量传送到 RNN, rnn需要的shape是(batch_size, time_step, feature)
        output, state = self.rnn(x)
        #print("output 1.shape={}".format(output.shape))

        # 输出的形状 == (批大小 * 1,隐藏层大小)
        # 将合并后的向量传送到 RNN, rnn需要的shape是(batch_size, time_step, feature),time_step这个维度没什么意义,
        # 在全连接层可以去掉,这里去掉
        output = tf.reshape(output, (-1, output.shape[2]))
        # 输出的形状 == (批大小,vocab),输出所有单词概率
        x = self.fc(output)
        return x, state, attention_weights

Давайте также проанализируем параметры Decoder

функция вызоваразбор параметров

x:Результат перевода предыдущего ввода, например: «машинное обучение» => «машинное обучение»,

1. Если текущий перевод "машинный", то x - это идентификатор "",

2. Если текущий перевод — «обучение», то x здесь — «машина».

Этот метод обучения выходных данных предыдущего ввода в качестве функции текущего ввода называется форсированием учителя. Это быстрый и эффективный способ обучения рекуррентной модели нейронной сети. Заинтересованные студенты, перейдите по ссылке "Professor Forcing: A New Algorithm for Training Recurrent Networks

hidden: Состояние скрытого слоя, возвращаемое кодировщиком, форма скрытого — BATCH_SIZE * enc_uints.

enc_output: Результат кодирования Encoder, форма BATCH_SIZE * word**_**size * enc_uints.

Декодер также имеет параметр внимания, который представляет собой функцию для вычисления внимания, которое передается здесь как параметр. Метод расчета внимания находится в "Введение в серию моделей BERT: введение в механизм внимания«Я это уже говорил, тут много говорить не буду. Мы реализуем расчетный метод подсчета внимания в виде скалярного произведения.

4. Реализация модели внимания:

class DotProductAttention(tf.keras.layers.Layer):
  def __init__(self):
    super(DotProductAttention, self).__init__()

  def call(self, query, value):
    # 32 * 512 * 1
    hidden = tf.expand_dims(query, -1)
    # 计算点积
    score = tf.matmul(value, hidden)
    attention_weights = tf.nn.softmax(score, axis=1)
    context_vector = attention_weights * value
    # 求和
    context_vector = tf.reduce_sum(context_vector, axis=1)
    return context_vector, attention_weights

Основные части определены, давайте запустим

import tensorflow as tf
import decoder
import attention
import encoder
import preprocess

# 加载、预处理数据
input_tensor, target_tensor, inp_lang, targ_lang = preprocess.load_dataset("./cmn.txt", 30000)


# 公共参数定义
BUFFER_SIZE = len(input_tensor)
BATCH_SIZE = 32
steps_per_epoch = len(input_tensor)//BATCH_SIZE
embedding_dim = 256 # 词向量维度
units = 512
vocab_inp_size = len(inp_lang.word_index)+1
vocab_tar_size = len(targ_lang.word_index)+1

# 数据集
dataset = tf.data.Dataset.from_tensor_slices((input_tensor, target_tensor)).shuffle(BUFFER_SIZE)
dataset = dataset.batch(BATCH_SIZE, drop_remainder=True)


# 定义encoder
encoder = encoder.Encoder(vocab_inp_size, embedding_dim, units, BATCH_SIZE)
sample_hidden = encoder.initialize_hidden_state()
sample_output, sample_hidden = encoder(example_input_batch, sample_hidden)
print ('encoder output shape: (batch size, sequence length, units) {}'.format(sample_output.shape))
print ('encoder Hidden state shape: (batch size, units) {}'.format(sample_hidden.shape))

# 定义注意力
attention_layer = attention.DotProductAttention()
context_vector, attention_weights = attention_layer(sample_hidden, sample_output)
print ('context_vector shape:  {}'.format(context_vector.shape))
print ('attention_weights state: {}'.format(attention_weights.shape))

# 定义decoder
dec_input = tf.expand_dims([targ_lang.word_index['<start>']] * BATCH_SIZE, 1)
decoder = decoder.Decoder(vocab_tar_size, embedding_dim, units, BATCH_SIZE, attention_layer)
dec_output, dec_state, attention_weights = decoder(dec_input, sample_hidden, sample_output)
print ('decoder shape: (batch size, sequence length, units) {}'.format(dec_output.shape))
print ('decoder Hidden state shape: (batch size, units) {}'.format(dec_state.shape))

5. Обучение модели:

Мы реализовали Encoder, Decoder и Attention, а затем можем приступить к определению шагов обучения модели. На этапе предварительной обработки данных мы использовали

 dataset.batch(BATCH_SIZE, drop_remainder=True)

Обучающие данные упорядочены по размеру BATCH_SIZE, поэтому наша наименьшая единица для каждого обучения — это набор данных размером BATCH_SIZE. Рассмотрим конкретные этапы обучения.

Единая ПАКЕТНАЯ тренировка

import tensorflow as tf
import optimizer

# 单个样本的模型训练
# encoder 定义好的encoder模型
# decoder 定义好的decoder模型
# inp 训练数据,待翻译文本的张量
# targ 训练据, 目标文本的张量
# targ_lang 目标文本的词典
# enc_hidden encoder返回的隐藏层状态
def train_step(encoder, decoder, inp, targ, targ_lang, enc_hidden, BATCH_SIZE):
  loss = 0

  with tf.GradientTape() as tape:
    enc_output, enc_hidden = encoder(inp, enc_hidden)
    dec_hidden = enc_hidden
    dec_input = tf.expand_dims([targ_lang.word_index['<start>']] * BATCH_SIZE, 1)

    # 以文本长度为主,遍历所有词语
    for t in range(1, targ.shape[1]):
      # 将编码器输出 (enc_output) 传送至解码器
      predictions, dec_hidden, _ = decoder(dec_input, dec_hidden, enc_output)

      # 这里输入的是一个batch
      loss += optimizer.loss_function(targ[:, t], predictions)

      # 教师强制 - 将目标词作为下一个输入,一个batch的循环
      dec_input = tf.expand_dims(targ[:, t], 1)
  batch_loss = (loss / int(targ.shape[1]))
  variables = encoder.trainable_variables + decoder.trainable_variables
  gradients = tape.gradient(loss, variables)
  optimizer.optimizer.apply_gradients(zip(gradients, variables))
  return batch_loss

Общий тренировочный процесс:

# 模型训练
def train(epochs):
    EPOCHS = epochs

    for epoch in range(EPOCHS):
        enc_hidden = encoder.initialize_hidden_state()
        total_loss = 0

        # dataset最多有steps_per_epoch个元素
        for (batch, (inp, targ)) in enumerate(dataset.take(len(input_tensor))):
            batch_loss = train_function.train_step(encoder, decoder, inp, targ, targ_lang, enc_hidden, BATCH_SIZE)
            total_loss += batch_loss

            if batch % 100 == 0:
                print('Epoch {} Batch {} Loss {:.4f}'.format(epoch + 1,
                                                             batch,
                                                             batch_loss.numpy()))

6. английский-> китайский перевод

# 预测目标解码词语
def evaluate(sentence):

    sentence = preprocess.preprocess_sentence(sentence, 0)

    inputs = [inp_lang.word_index[i] for i in sentence.split(' ')]
    inputs = tf.keras.preprocessing.sequence.pad_sequences([inputs],
                                                           maxlen=max_length_inp,
                                                           padding='post')
    inputs = tf.convert_to_tensor(inputs)

    result = ''

    hidden = [tf.zeros((1, units))]
    enc_out, enc_hidden = encoder(inputs, hidden)
    dec_hidden = enc_hidden
    dec_input = tf.expand_dims([targ_lang.word_index['<start>']], 0)

    # max_length_targ 解码张量的最大长度
    for t in range(max_length_targ):
        predictions, dec_hidden, attention_weights = decoder(dec_input,
                                                             dec_hidden,
                                                             enc_out)
        tf.reshape(attention_weights, (-1, ))

        predicted_id = tf.argmax(predictions[0]).numpy()

        result += targ_lang.index_word[predicted_id] + ' '

        if targ_lang.index_word[predicted_id] == '<end>':
            return result, sentence

        # 预测的 ID 被输送回模型
        dec_input = tf.expand_dims([predicted_id], 0)

    return result, sentence

# 翻译
def translate(sentence):
    result, sentence = evaluate(sentence)

    print('Input: %s' % (sentence))
    print('Predicted translation: {}'.format(result))

Запустить его:

train(20)
translate("hello")
translate("he is swimming in the river")

выходной результат

Epoch 20 Batch 300 Loss 0.5712
Epoch 20 Batch 400 Loss 0.4970
Epoch 20 Batch 500 Loss 0.5692
Epoch 20 Batch 600 Loss 0.6004
Epoch 20 Batch 700 Loss 0.6078

Input: <start> hello <end>
Predicted translation: 你 好 。 <end> 

Input: <start> he is swimming in the river<end>
Predicted translation: 我  <end> 

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

1. Объем данных недостаточен, всего более 3000 наборов данных.

2. Недостаточное время обучения, дальнейшая оптимизация и увеличение количества итераций.

3. Модель «Внимание» еще можно оптимизировать, используется только метод расчета внимания скалярного произведения, и есть еще лучший метод расчета.

4. Используется модель RNN, а также ее можно заменить нейронными сетями типа lstm и gru для отладки

Ссылаться на:

woohoo.tensorflow.org/tutorials/he…

GitHub.com/rotbit/Дьявол. …