Обучение модели на основе BERT вызывает ошибку OutOfMemory. Как я могу это исправить? - PullRequest
1 голос
/ 06 января 2020

Моя установка имеет графический процессор NVIDIA P100. Я работаю над моделью Google BERT, чтобы отвечать на вопросы. Я использую набор данных вопросов-ответов SQuAD, который задает мне вопросы и абзацы, из которых должны быть получены ответы, и мое исследование показывает, что эта архитектура должна быть в порядке, но я продолжаю получать ошибки OutOfMemory во время обучения:

ResourceExhaustedError: OOM при выделении тензора с формой [786432,1604] и типом float on / job: localhost / replica: 0 / task: 0 / device: GPU: 0 по распределителю GPU_0_bf c
[[{{ {node density_3 / kernel / Initializer / random_uniform / RandomUniform}}]] Подсказка: если вы хотите увидеть список распределенных тензоров, когда происходит OOM, добавьте report_tensor_allocations_upon_oom в RunOptions для информации о текущем распределении.

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

import json
import numpy as np
import pandas as pd
import os
assert os.path.isfile("train-v1.1.json"),"Non-existent file"
from tensorflow.python.client import device_lib
import tensorflow.compat.v1 as tf
#import keras
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
import re
regex = re.compile(r'\W+')
#Reading the files.
def readFile(filename):
  with open(filename) as file:
    fields = []
    JSON = json.loads(file.read())
    articles = []
    for article in JSON["data"]:
      articleTitle = article["title"]
      article_body = []
      for paragraph in article["paragraphs"]:
        paragraphContext = paragraph["context"]
        article_body.append(paragraphContext)
        for qas in paragraph["qas"]:
          question = qas["question"]
          answer = qas["answers"][0]
          fields.append({"question":question,"answer_text":answer["text"],"answer_start":answer["answer_start"],"paragraph_context":paragraphContext,"article_title":articleTitle})
      article_body = "\\n".join(article_body)
      article = {"title":articleTitle,"body":article_body}
      articles.append(article)
  fields = pd.DataFrame(fields)
  fields["question"] = fields["question"].str.replace(regex," ")
  assert not (fields["question"].str.contains("catalanswhat").any())
  fields["paragraph_context"] = fields["paragraph_context"].str.replace(regex," ")
  fields["answer_text"] = fields["answer_text"].str.replace(regex," ")
  assert not (fields["paragraph_context"].str.contains("catalanswhat").any())
  fields["article_title"] = fields["article_title"].str.replace("_"," ")
  assert not (fields["article_title"].str.contains("catalanswhat").any())
  return fields,JSON["data"]
trainingData,training_JSON = readFile("train-v1.1.json")
print("JSON dataset read.")
#Text preprocessing
## Converting text to skipgrams
print("Tokenizing sentences.")
strings = trainingData.drop("answer_start",axis=1)
strings = strings.values.flatten()

answer_start_train_one_hot = pd.get_dummies(trainingData["answer_start"])

# @title Keras-BERT Environment
import os
pretrained_path = 'uncased_L-12_H-768_A-12'
config_path = os.path.join(pretrained_path, 'bert_config.json')
checkpoint_path = os.path.join(pretrained_path, 'bert_model.ckpt')
vocab_path = os.path.join(pretrained_path, 'vocab.txt')
# Use TF_Keras
os.environ["TF_KERAS"] = "1"

# @title Load Basic Model
import codecs
from keras_bert import load_trained_model_from_checkpoint
token_dict = {}
with codecs.open(vocab_path, 'r', 'utf8') as reader:
    for line in reader:
        token = line.strip()
        token_dict[token] = len(token_dict)

model = load_trained_model_from_checkpoint(config_path, checkpoint_path)

#@title Model Summary
model.summary()

#@title Create tokenization stuff.
from keras_bert import Tokenizer

tokenizer = Tokenizer(token_dict)
def tokenize(text,max_len):
  tokenizer.tokenize(text)
  return tokenizer.encode(first=text,max_len=max_len)
def tokenize_array(texts,max_len=512):
  indices = np.zeros((texts.shape[0],max_len))
  segments = np.zeros((texts.shape[0],max_len))
  for i in range(texts.shape[0]):
    tokens = tokenize(texts[i],max_len)
    indices[i] = tokens[0]
    segments[i] = tokens[1]
  #print(indices.shape)
  #print(segments.shape)
  return np.stack([segments,indices],axis=1)

#@ Tokenize inputs.
def X_Y(dataset,answer_start_one_hot,batch_size=10):
    questions = dataset["question"]
    contexts = dataset["paragraph_context"]
    questions_tokenized = tokenize_array(questions.values)
    contexts_tokenized = tokenize_array(contexts.values)
    X = np.stack([questions_tokenized,contexts_tokenized],axis=1)
    Y = answer_start_one_hot
    return X,Y
def X_Y_generator(dataset,answer_start_one_hot,batch_size=10):
    while True:
        try:
            batch_indices = np.random.choice(np.arange(0,dataset.shape[0]),size=batch_size)
            dataset_batch = dataset.iloc[batch_indices]
            X,Y = X_Y(dataset_batch,answer_start_one_hot.iloc[batch_indices])
            max_int = pd.concat((trainingData["answer_start"],devData["answer_start"])).max()
            yield (X,Y)
        except Exception as e:
            print("Unhandled exception in X_Y_generator: ",e)
            raise

model.trainable = True

answers_network_checkpoint = ModelCheckpoint('answers_network-best.h5', verbose=1, monitor='val_loss',save_best_only=True, mode='auto')

input_layer = Input(shape=(2,2,512,))
print("input layer: ",input_layer.shape)
questions_input_layer = Lambda(lambda x: x[:,0])(input_layer)
context_input_layer = Lambda(lambda x: x[:,1])(input_layer)
print("questions input layer: ",questions_input_layer.shape)
print("context input layer: ",context_input_layer.shape)
questions_indices_layer = Lambda(lambda x: tf.cast(x[:,0],tf.float64))(questions_input_layer)
print("questions indices layer: ",questions_indices_layer.shape)
questions_segments_layer = Lambda(lambda x: tf.cast(x[:,1],tf.float64))(questions_input_layer)
print("questions segments layer: ",questions_segments_layer.shape)
context_indices_layer = Lambda(lambda x: tf.cast(x[:,0],tf.float64))(context_input_layer)
context_segments_layer = Lambda(lambda x: tf.cast(x[:,1],tf.float64))(context_input_layer)
questions_bert_layer = model([questions_indices_layer,questions_segments_layer])
print("Questions bert layer loaded.")
context_bert_layer = model([context_indices_layer,context_segments_layer])
print("Context bert layer loaded.")
questions_flattened = Flatten()(questions_bert_layer)
context_flattened = Flatten()(context_bert_layer)
combined = Concatenate()([questions_flattened,context_flattened])
#bert_dense_questions = Dense(256,activation="sigmoid")(questions_flattened)
#bert_dense_context = Dense(256,activation="sigmoid")(context_flattened)
answers_network_output = Dense(1604,activation="softmax")(combined)
#answers_network = Model(inputs=[input_layer],outputs=[questions_bert_layer,context_bert_layer])
answers_network = Model(inputs=[input_layer],outputs=[answers_network_output])
answers_network.summary()

answers_network.compile("adam","categorical_crossentropy",metrics=["accuracy"])

answers_network.fit_generator(
    X_Y_generator(
        trainingData,
        answer_start_train_one_hot,
        batch_size=10),
    steps_per_epoch=100,
    epochs=100,
    callbacks=[answers_network_checkpoint])

Размер моего словаря составляет около 83 000 слов. Любая модель с «хорошей» точностью / баллом F1 предпочтительна, но я также не продляю срок в 5 дней.

РЕДАКТИРОВАТЬ:

К сожалению, была одна вещь, которую я не упомянул: я на самом деле использую модуль keras-bert CyberZHG для предварительной обработки и для реальной модели BERT, поэтому некоторые оптимизации могут фактически нарушить код. Например, я попытался установить значение по умолчанию float16, но это вызвало ошибку совместимости.

EDIT # 2:

По запросу, вот код для моего полная программа:

Блокнот Jupyter

Ответы [ 3 ]

3 голосов
/ 09 января 2020

Проверьте Проблемы с нехваткой памяти на их странице github.

Зачастую из-за того, что размер пакета или длина последовательности слишком велики для размещения в памяти графического процессора, ниже приведены максимальные конфигурации пакетов для графического процессора объемом 12 ГБ, как указано в приведенной выше ссылке

System       | Seq Length | Max Batch Size
------------ | ---------- | --------------
`BERT-Base`  | 64         | 64
...          | 128        | 32
...          | 256        | 16
...          | 320        | 14
...          | 384        | 12
...          | 512        | 6
`BERT-Large` | 64         | 12
...          | 128        | 6
...          | 256        | 2
...          | 320        | 1
...          | 384        | 0
...          | 512        | 0

Обновление

Я вижу, что вы здесь делаете, этот tensor with shape[786432,1604], который вызывает ошибку, с последнего слоя Dense(1604,activation="softmax")(combined), где первое измерение 786432 = 768 * 1024 происходит из конкатенации 768-ых элементов bert двух 512 последовательностей, второе измерение 1604 Я полагаю, для всех возможных местоположений или интервалов предсказанного ответа.

Однако для задач маркировки последовательностей, таких как SQUAD, люди обычно не Не используйте такой большой полностью связанный слой. Вместо этого вы можете попробовать применить одинаковые веса для каждой позиции, а затем нормализовать выходы последовательности с помощью softmax. Таким образом, вы можете уменьшить количество параметров в последнем слое с 768*1024*1604 до чего-то вроде 768*2, где выходной размер 2 предназначен для прогнозирования начальной и конечной позиции ответа.

Есть пример из репозитория bert github, который показывает, как сделать SQUAD для моделей, похожих на bert. Также в статье BERT есть раздел, описывающий это.

2 голосов
/ 09 января 2020

Редактировать : я отредактировал свой ответ на месте, а не увеличив длину уже длинного ответа.

После просмотра проблема поднимается из последнего слоя в вашей модели. И я смог заставить его работать со следующими исправлениями / изменениями.

ResourceExhaustedError: OOM при выделении тензора с формой [786432,1604] и типа float on / job: localhost / replica: 0 / task: 0 / device: GPU: 0 по распределителю GPU_0_bfc [[{{node density_3 / kernel / Initializer / random_uniform / RandomUniform}}]] Подсказка: если вы хотите увидеть список распределенных тензоров при появлении OOM, добавьте report_tensor_allocations_upon_oom в RunOptions для информации о текущем распределении.

Итак, глядя на ошибку, проблема не в том, чтобы выделить массив [786432,1604]. Если вы делаете простой расчет, у вас есть выделенный массив 5GB (предполагается, что float32) Если это float64, это переходит к 10GB. Добавьте параметры из Bert и других слоев в модель, альт! у вас не хватает памяти.

Проблемы

Тип данных

Глядя на код, все эти слои в вашей сети ответов выдают float64, потому что вы указываете float64 для всех ваших Lambda слоев. Итак, мое первое предложение:

  • Глобальная настройка должна решить проблему tf.keras.backend.set_floatx('float16')

И в качестве меры предосторожности,

question_indices_layer = Input(shape=(256,), dtype='float16')
question_segments_layer = Input(shape=(256,), dtype='float16')
context_indices_layer = Input(shape=(256,), dtype='float16')
context_segments_layer = Input(shape=(256,), dtype='float16')
questions_bert_layer = model([question_indices_layer,question_segments_layer])
context_bert_layer = model([context_indices_layer,context_segments_layer])
questions_flattened = Flatten(dtype=tf.float16)(questions_bert_layer)
questions_flattened = Dense(64, activation='relu',dtype=tf.float16)(questions_flattened)
contexts_flattened = Flatten(dtype=tf.float16)(context_bert_layer)
contexts_flattened = Dense(64,activation="relu",dtype=tf.float16)
combined = Concatenate(dtype=tf.float16)([questions_flattened,contexts_flattened])
  • Теперь у вас будут все ваши слои float16.

Сокращение вывода перед последним softmax слоем

Еще одна вещь, которую вы можете сделать, не пропуская массивный [batch size, 512, 768] Выведите на плотный слой, вы получите sh, используя меньший слой или некоторое преобразование. Несколько вещей, которые вы можете попробовать:

  • Добавление более мелких плотных слоев, которые уменьшают размерность перед подачей его в слой 1604 softmax. Это значительно уменьшает параметры модели.
questions_flattened = Flatten(dtype=tf.float16)(questions_bert_layer)
questions_flattened = Dense(64, activation='relu',dtype=tf.float16)(questions_flattened)
contexts_flattened = Flatten(dtype=tf.float16)(context_bert_layer)
contexts_flattened = Dense(64,activation="relu",dtype=tf.float16)(contexts_flattened)
combined = Concatenate(dtype=tf.float16)([questions_flattened,contexts_flattened])
  • Суммирование / усреднение по времени измерения выхода question. Потому что вам важно только понять, в чем вопрос, поэтому было бы неплохо потерять позиционную информацию из этого вывода. Вы можете сделать это следующим образом:

questions_flattened = Lambda(lambda x: K.sum(x, axis=1))(questions_bert_layer)

  • Вместо Concatenate попробуйте Add(), чтобы не увеличивать размерность .

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

Длина или последовательность

Следующая проблема в том, что ваша длина ввода равна 512. Я не уверен, как вы пришли к этому числу, но я думаю, что вы можете добиться большего успеха ниже этого числа Например, вы получаете следующую статистику для questions и paragraphs.

count    175198.000000
mean         11.217582
std           3.597345
min           1.000000
25%           9.000000
50%          11.000000
75%          13.000000
max          41.000000
Name: question, dtype: float64

count    175198.000000
mean        123.791653
std          50.541241
min          21.000000
25%          92.000000
50%         114.000000
75%         147.000000
max         678.000000
Name: paragraph_context, dtype: float64

. Вы можете получить эту информацию как,

pd.Series(trainingData["question"]).str.split(' ').str.len().describe()

В качестве примера когда вы дополняете свои последовательности с помощью pad_sequences, вы не указываете maxlen, что приводит к заполнению предложений до максимальной длины, найденной в корпусе. Например, у вас есть контекст абзаца длиной 678 элементов, в котором 75% данных имеют длину менее 150 слов.

Я не совсем уверен, как эти значения играют в длину 512, но я надеюсь, ты понял мою точку зрения Судя по всему, кажется, что вы можете справиться с длиной 150.

Размер словаря

Вы также можете уменьшить словарный запас.

Хороший способ Решив, что это число будет означать, чтобы установить количество уникальных слов, которые появляются в вашем корпусе более чем n раз (n может быть 10-25 или лучше сделать дополнительный анализ и найти оптимальное значение.).

Например, вы можете получить vocabulary статистику следующим образом.

counts = sorted([(k, v) for k, v in list(textTokenizer.word_counts.items())], key=lambda x: x[1])

Что дает вам комбинации частот слов. Вы увидите, что около 37000 слов появляются менее чем (или приблизительно) в 10 раз. Таким образом, вы можете установить меньший размер словаря токенизатора.

textTokenizer = Tokenizer(num_words=50000, oov_token='unk')

Но имейте в виду, что word_index по-прежнему содержит все слова. Поэтому вам нужно убедиться, что вы удаляете эти редкие слова, когда передаете его как token_dict.

Размер партии

Кажется, вы устанавливаете batch_size=10, что должно быть хорошо. Но чтобы получить лучшие результаты (и, надеюсь, с большим объемом памяти, как только вы сделаете вышеупомянутые предложения), go для более высокого размера пакета, такого как 32 или 64, что улучшит производительность.

0 голосов
/ 09 января 2020

Ваша проблема в том, что при создании этого слоя Dense():

combined = Concatenate()([questions_flattened,context_flattened])
answers_network_output = Dense(1604,activation="softmax")(combined)

Concatenate() дает вам огромный слой, а когда вы подключаете его к Dense(1604, ...), вы получаете (786432,1604) тензор, который имеет значение 1.2G (вес + смещение, оба значения с плавающей точкой), которые легко переполнят вашу память графического процессора.

Чтобы проверить, верны ли мои предположения, попробуйте создать слой:

answers_network_output = Dense(1604,activation="softmax")(something_smaller)

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

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