Используя RNN для генерации текста, он всегда предсказывает одну и ту же букву - PullRequest
1 голос
/ 09 марта 2020

Я пытаюсь создать простой генератор текста (имени), используя RNN. Я прекрасно создаю модель, но когда я пытаюсь предсказать значения, я всегда получаю одну и ту же букву.

Мой код выглядит следующим образом:

from tensorflow.keras.activations import softmax
from tensorflow.keras.losses import categorical_crossentropy
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense

# parameters
LSTM_NODES = 100
MAX_NAME_LEN = 30
STOP_MARKER = '.'

# hyper-parameters
EPOCHS = 10

# read _names.train into an array
names = open('names.train', encoding='utf-8').read().strip().split('\n')

# precompute the number of samples
SAMPLES = 0
for name in names:
    for _ in name:
        SAMPLES = SAMPLES + 1

# get a sorted list of all unique characters used
corpus = sorted(list({l for name in names for l in name}))

# the first letter in the corpus must be the stop indicator
corpus.insert(0, STOP_MARKER)

# write out the corpus so that the predict script can use it
open('corpus.txt', 'w').write('\n'.join(corpus))

# calculate the input shape for the network
input_shape = (MAX_NAME_LEN, len(corpus))

# Creating a mapping from unique characters to indices
char2idx = {u:i for i, u in enumerate(corpus)}
idx2char = np.array(corpus)


def get_text(sample):
    t = ''
    for x in sample:
        n = idx2char[np.argmax(x)]
        t = t + n
    return t


# I need a 3-D array, samples x character position x character one-hot encoded
X = np.zeros((SAMPLES, MAX_NAME_LEN, len(corpus)), int)
Y = np.zeros((SAMPLES, len(corpus)), int)

# for each sample name
for name in names:
    # number of samples for this name is equal to the number of letters (we add one letter per loop)
    for i in range(len(name)):
        j = 0
        # create one sample
        while j <= i:
            one_hot_letter = np.zeros(len(corpus), int)
            one_hot_letter[char2idx[name[j]]] = 1
            X[i, j] = one_hot_letter
            j = j + 1
        # get the next character in the sequence
        one_hot_next = np.zeros(len(corpus), int)
        if j < len(name):
            one_hot_next[char2idx[name[j]]] = 1
        # add this character to the Y sample
        Y[i] = one_hot_next
        # print this sample
        print('X={} Y={}'.format(get_text(X[i]), idx2char[np.argmax(Y[i])]))

# build the model
model = Sequential()
model.add(LSTM(LSTM_NODES, input_shape=input_shape))
model.add(Dense(input_shape[1], activation=softmax))
model.compile(loss=categorical_crossentropy, optimizer='adam')
model.summary()

# train the model
model.fit(X, Y, epochs=EPOCHS)

# save the model
model.save('model.h5')

# try a sample prediction
# first letter is the seed
SEED = 'M'
name = SEED
x = np.zeros((1, input_shape[0], input_shape[1]), int)
one_hot_letter = np.zeros(len(corpus), int)
one_hot_letter[char2idx[SEED]] = 1
x[0, 0] = one_hot_letter
for i in range(1, MAX_NAME_LEN):
    predictions = model.predict(x)
    # get the next letter and add it to the prediction
    next_letter = np.zeros(input_shape[1], int)
    next_letter[np.argmax(predictions[0])] = 1
    x[0, i] = next_letter
    name = name + idx2char[np.argmax(next_letter)]
    print(name)

В конце выводится:

Mww
Mwww
Mwwww
Mwwwww
Mwwwwww
Mwwwwwww
Mwwwwwwww
Mwwwwwwwww
Mwwwwwwwwww
Mwwwwwwwwwww
Mwwwwwwwwwwww
Mwwwwwwwwwwwww
Mwwwwwwwwwwwwww
Mwwwwwwwwwwwwwww
Mwwwwwwwwwwwwwwww
Mwwwwwwwwwwwwwwwww
Mwwwwwwwwwwwwwwwwww
Mwwwwwwwwwwwwwwwwwww
Mwwwwwwwwwwwwwwwwwwww
Mwwwwwwwwwwwwwwwwwwwww
Mwwwwwwwwwwwwwwwwwwwwww
Mwwwwwwwwwwwwwwwwwwwwwww
Mwwwwwwwwwwwwwwwwwwwwwwww
Mwwwwwwwwwwwwwwwwwwwwwwwww
Mwwwwwwwwwwwwwwwwwwwwwwwwww
Mwwwwwwwwwwwwwwwwwwwwwwwwwww
Mwwwwwwwwwwwwwwwwwwwwwwwwwwww
Mwwwwwwwwwwwwwwwwwwwwwwwwwwwww

Есть идеи, что может быть не так? Мои образцы в порядке, я думаю. Я использовал их в другом примере, написанном кем-то другим, и они дали разные результаты. У меня 280 образцов. Вот как выглядит names.train:

Adaldrida
Celendine
Gloriana
Pimpernel
Tanta
Alfrida
Cora
Goldilocks
Melba

Полный результат обучения:

[snip]
X=Valde......................... Y=m
X=Valdem........................ Y=a
X=Valdema....................... Y=r
X=Valdemar...................... Y=.
2020-03-09 13:38:26.827190: I tensorflow/core/platform/cpu_feature_guard.cc:142] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
2020-03-09 13:38:26.843439: I tensorflow/compiler/xla/service/service.cc:168] XLA service 0x7fa8f211d590 initialized for platform Host (this does not guarantee that XLA will be used). Devices:
2020-03-09 13:38:26.843450: I tensorflow/compiler/xla/service/service.cc:176]   StreamExecutor device (0): Host, Default Version
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
lstm (LSTM)                  (None, 100)               58800     
_________________________________________________________________
dense (Dense)                (None, 46)                4646      
=================================================================
Total params: 63,446
Trainable params: 63,446
Non-trainable params: 0
_________________________________________________________________
Train on 1795 samples
Epoch 1/10
1795/1795 [==============================] - 2s 1ms/sample - loss: 0.0168
Epoch 2/10
1795/1795 [==============================] - 1s 462us/sample - loss: 0.0167
Epoch 3/10
1795/1795 [==============================] - 1s 445us/sample - loss: 0.0164
Epoch 4/10
1795/1795 [==============================] - 1s 450us/sample - loss: 0.0163
Epoch 5/10
1795/1795 [==============================] - 1s 449us/sample - loss: 0.0162
Epoch 6/10
1795/1795 [==============================] - 1s 453us/sample - loss: 0.0160
Epoch 7/10
1795/1795 [==============================] - 1s 593us/sample - loss: 0.0159
Epoch 8/10
1795/1795 [==============================] - 1s 599us/sample - loss: 0.0160
Epoch 9/10
1795/1795 [==============================] - 1s 442us/sample - loss: 0.0160
Epoch 10/10
1795/1795 [==============================] - 1s 440us/sample - loss: 0.0160
Mw
Mww
Mwww
Mwwww
Mwwwww
Mwwwwww
Mwwwwwww
Mwwwwwwww
Mwwwwwwwww
Mwwwwwwwwww
Mwwwwwwwwwww
[snip]```

Ответы [ 3 ]

3 голосов
/ 13 марта 2020

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

1) вы недостаточно хорошо обучили модель и привести к неполноценной проблеме. Я думаю, что ваша модель не слишком подходит, потому что в вашем наборе данных, похоже, нет только «w», следующего за «M». (Может быть, я ошибаюсь, вы можете проверить свой набор данных и ваше выходное распределение)

2) Это происходит из ваших char2idx и idx2char картографирования. Поскольку вы пытаетесь создать словарь сопоставления из индекса и символа из самого корпуса, это может привести к различному сопоставлению для каждого корпуса. Вы можете решить эту проблему, создав общий словарь сопоставления для каждого символа и используя его для обучения и прогнозирования в каждом наборе данных для обеспечения согласованности сопоставления (вы можете сохранить словарь сопоставления как файл JSON). Например, буква «а» всегда будет [1,0,0,0, ...], буква «А» всегда будет [0,1,0,0, ...] в каждом обучении и прогнозировании. Более того, поскольку вы пытаетесь сделать базовое предсказание персонажа, вам нужно, чтобы номер выходного слоя был равен номеру вашего персонажа, чтобы вы могли иметь распределение вероятностей по всем возможным символам.

Совет

В целях отладки вы должны распечатать распределение вероятностей, в данном случае это переменная predictions, просто чтобы увидеть распределение вашей модели, чтобы вы знали, что ваша модель не подходит или overfit.

1 голос
/ 19 марта 2020

Об этом спрашивали и отвечали до здесь, на SO .

Подводя итог, проблема называется дегенерация текста и (удивительный) ответ на ранее заданный вопрос ссылается на превосходную статью, посвященную именно этой проблеме . Процитируем авторов:

Почему текст, полученный с помощью чистой выборки, настолько выродился? В этой работе мы показываем, что « ненадежный хвост » виноват. Этот ненадежный хвост состоит из десятков тысяч токенов-кандидатов с относительно низкой вероятностью, которые чрезмерно представлены в совокупности.

Они считают, что простая выборка может привести к циклам или повторениям, поскольку генеративные модели сохранят токены выборки, которые образуют последовательности, которые слишком вероятны, т. е. повторение менее «удивительно», чем новые токены. Сравните базовую линию жадной выборки с методами, которые явно обрезают ненадежный хвост: Figure

Рисунок 9, Holtzman et al. (2020)

Итак, во-первых, , как Ронакрит упомянул , вы должны вывести вектор логитов (вероятностей) поверх фиксированного словаря токенов.

Затем выберите стратегию декодирования, немного более сложную, чем жадная выборка. TensorFlow поставляется с поиском луча и top-k , но я бы порекомендовал начать со случайного декодирования, для которого вы можете увидеть отработанную реализацию в учебнике по генерации текста TensorFlow :

# Generate probabilities from the model.
for input_example_batch, target_example_batch in dataset.take(1):
  example_batch_predictions = model(input_example_batch)
  print(example_batch_predictions.shape, "# (batch_size, sequence_length, vocab_size)")

# Sample from model outputs.
sampled_indices = tf.random.categorical(example_batch_predictions[0], num_samples=1)
sampled_indices = tf.squeeze(sampled_indices,axis=-1).numpy()

# Look up tokens from the dictionary.
print("Input: \n", repr("".join(idx2char[input_example_batch[0]])))
print()
print("Next Char Predictions: \n", repr("".join(idx2char[sampled_indices ])))
0 голосов
/ 19 марта 2020

Итак, я попытался провести простой эксперимент с вашим кодом и снизить скорость обучения на оптимизаторе Адама до 0,00001. Ниже приведен результат:

Train on 1453 samples
Epoch 1/30

AEcOOOOONfffffwfwffwfpXWHfHHfA
BEEcOOOfffffwfwfwzffwfzffAdfWH
CEEccOOffffffwfwfwzfffpigMIwHH
FEcOOOOffffffwfwfwfzffwfWgCwHH
MEEcOOcffffffwfwfwfpXW.WggCIwH
PEEcOfffffffwfwfwzffwfzfzfAfAd

1453/1453 - 10s - loss: 0.0162
Epoch 2/30

AEEEcOcOOOOOffffwffwffpbWAHfHH
BEEEEccOOOfffffwffwffpbWAHfHHf
CEEEcEcOOOffffffwffwfpbWAHfHHf
FEEEccOOOOOfffffwffwffpbWAHfHH
MEEEcOOOOOOcOOONfffwfffFFooewG
PEEEcOOOOOOONcffffwffwffWpbAHH

1453/1453 - 4s - loss: 0.0162
Epoch 3/30

AEEEEcOOOOOcOOOOONcuuuullNulXy
BEEEEcOOOOOOOONcOOONEOEFFFFFFC
CEEEEcOOOOOOOONcOOONEOEFFFFFFC
FEEEEcOOOcOOOOOONcOOEOEOEFFFFF
MEEEEcOOOOOOOONcOOONEOEFFFFFFC
PEEEEcOOOOOOOONcOOONEOEFFFFFFC

1453/1453 - 4s - loss: 0.0162
Epoch 4/30

AEEEEOOOOOOOOONcOOONEOEFFFFFFF
BEEEEEcOOOOOOOOONcOOEOEOEFFFFF
CEEEEEcOOOOOOOOONcOOEOEOEFFFFF
FEEEEOOOOOOOcOOOONONcuuuulllXy
MEEEEOOOOOOOOONcOOONEOEFFFFFFF
PEEEEEcOOOOOOOOONcOOEOEOEFFFFF

1453/1453 - 4s - loss: 0.0162
Epoch 5/30

AEEEEOOOOOOOOOOONcOOEOEOEFFFFF
BEEEEEOOOOOOOOOONcOOEOEOEFFFFF
CEEEEEOOOOOOOOOONcOOEOEOEFFFFF
FEEEEOOOOOOOOOOONcOOEOEOEFFFFF
MEEEEOOOOOOOOOOONcOOEOEOEFFFFF
PEEEEEOOOOOOOOOONcOOEOEOEFFFFF

1453/1453 - 4s - loss: 0.0162
Epoch 6/30

AEEEEOOOOOOOOOOOONcOOEOEEFFFFF
BEEEEEOOOOOOOOOOONcOOEOEEFFFFF
CEEEEEOOOOOOOOOOOONcOEOEEFFFFF
FEEEEOOOOOOOOOOOONcOOEOEEFFFFF
MEEEEOOOOOOOOOOOONcOOEOEEFFFFF
PEEEEEOOOOOOOOOOOONcOEOEEFFFFF

...

Epoch 26/30

AOOOOOOOOOOOEOEOOEOEOEOEEEEEEE
BOOOOOOOOOOOOEOEOEOOEEOEEEEEEE
COOOOOOOOOOOOEOEOEOOEOEEEEEEEE
FOOOOOOOOOOOOOEOEOOEOEEOEEEEEE
MOOOOOOOOOOOOEOEOEOEOEOEEEEEEE
POOOOOOOOOOOOEOEOEOOEOEEEEEEEE

1453/1453 - 4s - loss: 0.0159
Epoch 27/30

AOEEEEEEEEEEEEEEOEOEOEOEEEEEEE
BOEEEEEEEEEEEEEEOEOEOOEEEEEEEE
COEEEEEEEEEEEEEEOEOOEOEEEEEEEE
FOOEEEEEEEEEEEEEOOEOEOEOEEEEEE
MOEEEEEEEEEEEEEEOEOEOEOEEEEEEE
POEEEEEEEEEEEEEEOEOOEOEEEEEEEE

1453/1453 - 4s - loss: 0.0155
Epoch 28/30

AEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
BEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
CEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
FEEEEEEEEEEEEEEEEEEEEEEEOEEEEE
MEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
PEEEEEEEEEEEEEEEEEEEEEEEEEEEEE

1453/1453 - 4s - loss: 0.0165
Epoch 29/30

AEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
BEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
CEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
FEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
MEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
PEEEEEEEEEEEEEEEEEEEEEEEEEEEEE

1453/1453 - 4s - loss: 0.0158
Epoch 30/30

AEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
BEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
CEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
FEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
MEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
PEEEEEEEEEEEEEEEEEEEEEEEEEEEEE

1453/1453 - 4s - loss: 0.0159

Как вы видите, изначально это не так всегда генерируйте одну и ту же букву, но в 29-ю эпоху она генерирует одну и ту же букву, это означает, что ваша скорость обучения была слишком высокой.

Но, конечно, есть еще проблема, которую она, кажется, не изучает ( недооценка), на этот раз вам нужно провести больше экспериментов, я думаю, что это более вероятно, потому что, как я моделирую ввод и вывод, по моему опыту, если я хочу сделать следующий прогноз, я обычно моделирую его следующим образом:

X -> Y
Adaldrida -> daldrida.

И получать выходные данные с каждого временного шага, а не только с последнего временного шага, используя return_sequences=True, аналогично тому, что описывают ссылка . в отличие от того, как вы это моделируете:

X -> Y
A -> d
Ad -> a
Ada -> l
etc.

Используя такой тип моделирования ввода-вывода, я думаю, что вы столкнетесь с такими проблемами, как после первого символа будет слишком много нулей, и сеть проигнорирует первый символ, вы можете попытаться использовать предварительное заполнение, чтобы конечный ноль был в начале, а не в конце, или вы также можете попытаться изменить последовательность.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...