Масштабирование от крошечных до больших объемов данных НЛП и диалогов

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

Однако сбор и аннотирование обучающих данных в домене требует значительного времени и ресурсов. К счастью, существует ряд высококачественных методов расширения для искусственного расширения текстовых наборов данных, включая методы, использующие современные языковые модели, такие как BERT и GPT-2.

СОДЕРЖАНИЕ

1. Создание более продолжительных разговоров (с использованием GPT-2)

2. Вставка слов (с использованием BERT)

3. Обратный перевод (также известный как спиннинг)

4. Замена синонимов (с фильтрацией POS)

5. Сдвиг

1. Создание более продолжительных разговоров (с помощью GPT-2)

Учитывая крошечный набор данных всего из трех разговоров:

Масштабная языковая модель Open-AI GPT-2 была обучена на таком большом количестве данных, что может генерировать очень реалистичные предложения. Мы можем использовать этот факт для создания новых примеров вариантов, расширяя последнее предложение каждого разговора (например, «у меня просто плохое настроение» → «… потому что я проиграл в квалификаторах»):

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

Сначала нам нужно установить GPT-2-simple, библиотеку с открытым исходным кодом, разработанную, чтобы упростить доступ к этой мощной языковой модели. Мы загружаем версию модели с параметрами 774M и загружаем ее.

!pip3 install gpt-2-simple
from nltk.tokenize import sent_tokenize
import gpt_2_simple as gpt2
model_name = "774M"
gpt2.download_gpt2(model_name=model_name)
gpt2.load_gpt2(
  gpt2.start_tf_sess(),
  model_name=model_name
)

Мы создаем небольшую функцию, которая принимает в качестве входных данных пример беседы и вызывает GPT-2 для генерации следующих 100 слов, которые, по ее мнению, могут последовать за этой беседой (мы решили расширить беседу одним предложением [: n + 1 ], но не стесняйтесь изменять это, чтобы расширить разговор).

def _extend_conversation(conversation_as_string):
  generated_samples = gpt2.generate(
    sess,
    model_name=model_name,
    prefix=conversation_as_string,
    length=100,
    return_as_list = True
  )
  n = len(
    sent_tokenize(
      conversation_as_string
    )
  )
  return sent_tokenize(
    generated_samples[0]
  )[:n+1]

2. Вставка слов (с помощью BERT)

BERT от Google - еще одна мощная языковая модель, которая произвела революцию в НЛП, но ее обучение немного отличается от других языковых моделей, таких как GPT-2. Это различие делает его хорошо подходящим для предсказания замаскированных слов; где слово замаскировано (скрыто), а BERT использует слова, окружающие это замаскированное слово, чтобы предсказать, каким может быть замаскированное слово. Например. «Однажды она [МАСКА] по коридору» → «Однажды она побежала по коридору»

Мы вставили маски между словами в полном предложении, и могли обмануть BERT, чтобы он предсказал новые слова и расширил предложение от середины (а не до конца). Например. «лисица» → «[МАСКА] лиса» → «коричневая лиса» → «[МАСКА] коричневая лисица» → «полосатая коричневая лисица ”…

Поэтому мы используем BERT, итеративно помещая маску между каждым словом в данном разговоре, чтобы создать несколько вариантов разговоров. Мы могли бы даже повторить этот процесс, чтобы создать еще больше вариаций, вставив маски и во вновь созданные варианты диалогов.

Чтобы использовать этот метод, мы должны сначала установить библиотеку с открытым исходным кодом pytorch-pretrained-bert и загрузить языковую модель вместе с сопровождающим ее токенизатором.

!pip3 install -U pytorch-pretrained-bert
from pytorch_pretrained_bert import BertTokenizer, BertModel, BertForMaskedLM
import torch
model_name = 'bert-base-uncased'
bert_tokeniser = BertTokenizer.from_pretrained(model_name)
bert_model = BertForMaskedLM.from_pretrained(model_name)

Нам также необходимо создать функцию, которая соответствующим образом форматирует входную строку с помощью специальных токенов ([CLS], [SEP]), разбивает строку на токены (используя соответствующий токенизатор модели), а затем вставляет специальный токен маски ([MASK]) чтобы указать слово, которое мы хотим, чтобы модель предсказывала.

def _format_model_input(text, tokeniser, insert_mask_at_idx):
  tokens = tokeniser.tokenize(
    f"[CLS] {text} [SEP]"
  )
  tokens_with_mask = tokens[:insert_mask_at_idx] + [
    "[MASK]"
  ] + tokens[insert_mask_at_idx:]
  return torch.tensor(
    [
      tokeniser.convert_tokens_to_ids(tokens_with_mask)
    ]
  )

Для вывода модели мы хотим вернуть предложение с дополнительным словом, вставленным между двумя другими словами. Поэтому мы создаем функцию, которая преобразует индексы токенов обратно в слова (с использованием того же токенизатора), а затем извлекаем индекс токена, предсказанный моделью, соответствующей местоположению токена маски (и таким же образом преобразуем его в слово ). Затем мы объединяем слова в одну строку, немного очищаем ее, удаляя все специальные символы токенизации (например, ##, [CLS] в начале, [SEP] в конце и т. Д.).

def _format_model_output(model_output, token_idxs, tokeniser, masked_idx):
  tokens = tokeniser.convert_ids_to_tokens(
    token_idxs.tolist()[0]
  )
  tokens[masked_idx] = tokeniser.convert_ids_to_tokens(
    [
      torch.argmax(
        model_output[0, masked_idx]
      ).item()
    ]
  )[0]
  return ' '.join(tokens[1:-1]).replace("##","")

Теперь мы определяем функцию для соединения всего вместе; форматирование ввода, модель BERT, форматирование вывода.

def _insert_mask_and_predict(sentence, model, tokeniser, masked_idx):
  tokens_with_mask_inserted = _format_model_input(
    text = sentence,
    tokeniser = tokeniser,
    insert_mask_at_idx = masked_idx,
  )
  segment_ids = torch.tensor(
    [[0]*len(tokens_with_mask_inserted)]
  )
  with torch.no_grad():
    return _format_model_output(
      model_output = model(
        tokens_with_mask_inserted,
        segment_ids
      ),
      tokeniser = tokeniser,
      token_idxs = tokens_with_mask_inserted,
      masked_idx = masked_idx,
  )

Вы могли заметить, что функция требует указать, в какую позицию вы хотите вставить маску. Ну, а почему бы не каждую позицию по очереди? Вот что делает следующая функция; итеративно создавая варианты, помещая маску в каждый индекс в предложении, пока он не достигнет конца и не завершится ошибкой, после чего он вернет все вновь созданные варианты.

def _insert_words(example):
  new_examples = [example]
  idx = 1
  try:
    while True:
      new_examples.append(
        _insert_mask_and_predict(
          sentence = example,
          model = bert_model,
          tokeniser = bert_tokeniser,
          masked_idx = idx
        )
      )
      idx += 1
  except:
    new_examples.pop()
    return new_examples

3. Обратный перевод (также известный как спиннинг)

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

Сначала нам нужно импортировать textblob или любую другую библиотеку с открытым исходным кодом с доступом к бесплатному переводу.

from textblob import TextBlob

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

def _spin_text(text, foreign_language):
  try:
    spun_text = _clean_word(
      TextBlob(
        TextBlob(text).translate(
          from_lang="en",
          to=foreign_language
        ).raw
      ).translate(
        from_lang=foreign_language,
        to="en"
      ).raw
    )
    return spun_text if spun_text != _clean_word(text) else None
  except:
    return None

Если перевод не удался или составное предложение оказалось идентичным оригиналу (без учета изменений форматирования или пунктуации), функция возвращает None.

from string import punctuation
def _clean_word(word):
  return word.lower().strip(punctuation)

4. Подстановка синонимов (с фильтрацией POS)

Более классический прием - выбрать слово в предложении и заменить им один из его синонимов. Этот метод может создавать огромное количество вариантов, заменяя в разговоре одно слово за раз (например, «Я просто в плохом настроении» → «Я просто в плохом настроении»).

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

Фактически, замена комбинаций слов приведет к экспоненциально большому пулу вариантов для любого данного примера. Однако этот метод требует тщательной фильтрации (ниже мы покажем вам один метод фильтрации с использованием тегов POS), поскольку многие из этих вариантов не будут звучать естественно, если синонимы подставляются вслепую. Например, «плохое настроение» → «плохое настроение» или «приятно познакомиться, Карла» → «приятно соответствовать тебе, Карла» и т. Д.

Сначала мы загружаем соответствующие файлы из NLTK (старой, но гигантской библиотеки NLP).

import nltk
nltk.download('wordnet')
nltk.download('punkt')
nltk.download('averaged_perceptron_tagger')
from nltk.corpus import wordnet as wn

Мы определяем функцию, которая извлекает синонимы для слова из WordNet (массивная, вручную подобранная сеть слов и их различные отношения друг с другом).

def synonyms(word, pos_tag):
  return list(
    {
      lemma.replace("_"," ").replace("-"," ") for synset in wn.synsets(
        _clean_word(word),
        pos_tag,
      ) for lemma in synset.lemma_names()
    }
  )

Мы добавили сюда фильтр, который учитывает часть речи слова перед извлечением любых синонимов (т. Е. Является ли это существительным, глаголом, прилагательным, наречием и т. Д.). Например. Слово «тест» может использоваться как существительное, глагол и т. Д .:

Затем мы определяем другую функцию для автоматического определения тега части речи:

К счастью, у NLTK есть несколько предварительно обученных POS-тегеров, которые значительно упрощают нашу жизнь:

def _infer_pos_tags(tokens):
  return [
    (
      token,
      _convert_nltk_to_wordnet_tag(nltk_tag)
    ) for token,nltk_tag in nltk.pos_tag(tokens)
  ]

Эта функция принимает токены, а не строку, поэтому обязательно разбейте строку на слова или используйте один из токенизаторов, предоставленных NLTK (nltk.word_tokenize (some_string_to_be_tokenised)). Функция выводит результирующие теги POS, возвращаемые устройством тегов, но сначала преобразует их в нотацию POS, необходимую для совместимости с WordNet:

def _convert_nltk_to_wordnet_tag(pos_tag):
  if pos_tag.startswith("N"):
    return wn.NOUN
  if pos_tag.startswith("V"):
    return wn.VERB
  if pos_tag.startswith("R"):
    return wn.ADV
  if pos_tag.startswith("J"):
    return wn.ADJ

5. Сдвиг

Наш последний метод - очень простой, который можно применить к любым данным временного ряда (например, разговорам). Положение каждого предложения (или точки данных) в разговоре (обучающий пример) просто сдвигается (смещается) на одно место, чтобы получить допустимый вариант. Например, «привет», «как дела», «хорошо, спасибо»… → «как дела», «хорошо, спасибо»…

Вы также можете комбинировать образцы временных рядов (разговоры), добавляя их друг к другу, создавая «новый», более длинный пример:

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

Заключение

Мы в Wluper объединили вышеперечисленные пять техник, добавили немного волшебного соуса и смогли в 10 000 раз улучшить обучающие данные для реальных разговоров, даже не исчерпывая количество возможных вариантов каждой из этих пяти техник по отдельности. могут предложить. Мощный, но эффективный.

Помимо современных языковых моделей, некоторые из наших нераскрытых методов дополнения НЛП включают многозадачное обучение, полу-контролируемое и даже неконтролируемое обучение . Конкретным примером может служить кластеризация. отличный способ найти естественные вариации для простых намерений, таких как приветствия, запрос альтернатив и т. д.
Мы используем алгоритмы кластеризации для анализа общедоступных наборов данных и обнаружения групп фраз, которые имеют некоторые основные семантические отношения. Хотя не будет сказано явно, какие общие черты имеют эти кластеры, их можно вывести с помощью других методов. Кроме того, для решения таких неоднозначностей может использоваться начальная фраза с известной меткой класса.

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

Вы можете найти фрагменты кода для вышеупомянутого расширения данных NLP и Dialogue в репозитории dsag на нашей странице Github.

Вам понравился этот пост? Пожалуйста, оставьте несколько аплодисментов и не стесняйтесь делиться.

Есть комментарии или вопросы? Сообщите нам свое мнение.

Подпишитесь на нас также в Twitter и LinkedIn, чтобы узнать больше.