В этой статье рассказывается, как использовать BERT для встраивания предложений и использовать это встраивание для точной настройки последующих задач. Я также расскажу о сходстве предложений для кластеризации предложений или сопоставления намерений.

В этой статье я рассмотрю только некоторые детали BERT, так как в Интернете уже есть множество отличных статей и руководств, рассказывающих об этом.

Хорошо, это начать его!

Встраивание предложения BERT для последующей задачи

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

BERT предлагает следующие 4 примера последующих задач:

Чтобы использовать BERT, вам необходимо подготовить входные данные для BERT.

  1. идентификатор токена: индекс каждого текста в корпусе BERT.
  2. маска внимания: поскольку мы будем дополнять каждое предложение до одинаковой длины, ему нужна маска внимания, чтобы слой внутреннего внимания знал, какие слова являются словами-заполнителями, и маскировал их.
  3. идентификатор сегмента: если вашей последующей задаче необходимо ввести два предложения (например, классификация пар предложений, ответ на вопрос), идентификатор сегмента используется для различения первого и второго предложения. Если ваша задача имеет только одно предложение в качестве входных данных, вам нужно только создать постоянный массив с любым индексом.

Давайте создадим эти входные тензоры шаг за шагом, тогда вам станет ясно.

  1. импортировать пакет и загрузить токенизатор и модель:
import torch  
from transformers import BertTokenizer,BertModel

tokenizer = BertTokenizer.from_pretrained('bert-base-uncased') 
model = BertModel.from_pretrained("bert-base-uncased")

# models: https://huggingface.co/models?sort=downloads

2. токенизировать последовательность

sentence = 'I really enjoyed this movie a lot.'

tokens = tokenizer.tokenize(sentence)
print(tokens)
# ['i', 'really', 'enjoyed', 'this', 'movie', 'a', 'lot', '.']

3. Добавьте токены [CLS] и [SEP]

tokens = ['[CLS]'] + tokens + ['[SEP]']
tokens
# ['[CLS]', 'i', 'really', 'enjoyed', 'this', 'movie', 'a', 'lot', '.', '[SEP]']

4. Заполнение ввода

T=15
padded_tokens = tokens + ['[PAD]' for _ in range(T-len(tokens))]
print("Padded tokens are \n {} ".format(padded_tokens))
attn_mask = [ 1 if token != '[PAD]' else 0 for token in padded_tokens  ]
print("Attention Mask are \n {} ".format(attn_mask))

5. Создайте список токенов сегмента

seg_ids = [0 for _ in range(len(padded_tokens))]

6. Создайте входной тензор для всего этого

sent_ids = tokenizer.convert_tokens_to_ids(padded_tokens)
token_ids = torch.tensor(sent_ids).unsqueeze(0) 
attn_mask = torch.tensor(attn_mask).unsqueeze(0) 
seg_ids   = torch.tensor(seg_ids).unsqueeze(0)

print(token_ids)
print(attn_mask)
print(seg_ids)

# tensor([[ 101, 1045, 2428, 5632, 2023, 3185, 1037, 2843, 1012, 102, 0, 0, 0, 0, 0]])
# tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0]])
# tensor([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])

7. вывод модели

На данный момент мы уже подготовили входной запрос BERT, мы готовы передать все это в модель, а затем получить встраивание предложения.

output = model(token_ids, attention_mask=attn_mask,token_type_ids=seg_ids)
last_hidden_state, pooler_output = output[0], output[1]

print(last_hidden_state.shape) #hidden states of each token
print(pooler_output.shape) #hidden states of [cls] (forward one linear layer and Tanh activation)

По сути, pooler_output — это то, что нам нужно. Нам нужно только добавить некоторый линейный слой, чтобы создать выходной слой для нашей последующей задачи для точной настройки нейронной сети. Например:

from transformers import BertTokenizer,BertModel

class BERT_classifier(nn.Module):
    def __init__(self, bertmodel, num_label):
        super(BERT_classifier, self).__init__()
        self.bertmodel = bertmodel
        self.classifier = nn.Linear(bertmodel.config.hidden_size, num_label)

    def forward(self, wrapped_input):
        hidden = self.bertmodel(**wrapped_input)
        last_hidden_state, pooler_output = hidden[0], hidden[1]
        logits = self.classifier(pooler_output)

        return logits

bert = BertModel.from_pretrained("bert-base-uncased")
model = BERT_classifier(bert, 2)

вы также можете просто использовать встроенную структуру модели в Huggingface, например. BertForSequenceClassification, BertForQuestionAnswering

from transformers import BertForSequenceClassification
model = BertForSequenceClassification.from_pretrained('bert-base-uncased', num_labels=2)

# this has same model structure as above

Вы могли заметить, что с помощью BERT очень легко делать выводы, основное усилие заключается в создании входного тензора BERT.

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

Мы можем просто использовать приведенную ниже команду, чтобы заменить шаги 2,3,4,5.

wrapped_input = tokenizer(sentence, max_length=15, add_special_tokens=True, truncation=True, 
                          padding='max_length', return_tensors="pt")
                          
wrapped_input
#{'input_ids': tensor([[ 101, 1045, 2428, 5632, 2023, 3185, 1037, 2843, 1012, 102, 0, 0, 0, 0, 0]]), 
  'token_type_ids': tensor([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]), 
  'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0]])}

Примечание: padding=True будет дополняться до максимальной длины набора,
с другой стороны, если padding=‘max_length’, то будет дополняться до длины «max_length»

распакуйте этот дикт и загрузите его в модель, тогда мы получим встраивание

output = model(**wrapped_input)
last_hidden_state, pooler_output = output[0], output[1]

Упражнение

В качестве примера я беру обзор фильмов IMDb (BERTforSequenceClassification). Цель состоит в том, чтобы классифицировать, является ли обзор фильма положительным или отрицательным.

По сути, это просто конвейер обучения PyTorch, поэтому я не буду много говорить в этой статье. Я поместил блокнот jupyter в этот репозиторий github, вы можете обратиться к нему.

Значение acc ~ 92,5% и набор тестов = 23084 / 25000 (92,336%)

[epoch 1]train on 24000 data......
100%|██████████| 1500/1500 [09:24<00:00,  2.66it/s]
training set: average loss: 0.0168, acc: 21350/24000(88.958%)
validation on 1000 data......
Val set:Average loss:0.0120, acc:928/1000(92.800%)
elapse: 575.06s

[epoch 2]train on 24000 data......
100%|██████████| 1500/1500 [09:15<00:00,  2.70it/s]
training set: average loss: 0.0094, acc: 22685/24000(94.521%)
validation on 1000 data......
Val set:Average loss:0.0126, acc:936/1000(93.600%)
elapse: 566.25s

[epoch 3]train on 24000 data......
100%|██████████| 1500/1500 [09:19<00:00,  2.68it/s]
training set: average loss: 0.0054, acc: 23321/24000(97.171%)
validation on 1000 data......
Val set:Average loss:0.0166, acc:925/1000(92.500%)
elapse: 569.87s 

[epoch 4]train on 24000 data......
100%|██████████| 1500/1500 [09:18<00:00,  2.69it/s]
training set: average loss: 0.0032, acc: 23621/24000(98.421%)
validation on 1000 data......
Val set:Average loss:0.0196, acc:925/1000(92.500%)
elapse: 568.86s 

[epoch 5]train on 24000 data......
100%|██████████| 1500/1500 [09:21<00:00,  2.67it/s]
training set: average loss: 0.0021, acc: 23743/24000(98.929%)
validation on 1000 data......
Val set:Average loss:0.0180, acc:925/1000(92.500%)
elapse: 572.23s

Сходство предложений

Когда вы пытаетесь выполнить кластеризацию предложений/документов или сопоставление намерений, вам нужно будет сделать сходство предложений.

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

Напоминаем, что предварительная подготовка BERT была обучена с помощью MaskedLM, NextSentencePrediction, поэтому первоначальной целью BERT является не создание осмысленного встраивания предложения, а выполнение какой-либо конкретной последующей задачи.

Комментарий Джейкоба Девлина: я не уверен, что это за векторы, так как BERT не генерирует осмысленные векторы предложений. Кажется, что это усреднение токенов слов для получения вектора предложения, но мы никогда не предполагали, что это будет генерировать осмысленные представления предложений. И даже если они являются достойными представлениями при вводе в DNN, обученную для последующей задачи, это не означает, что они будут иметь смысл с точки зрения косинусного расстояния. (Поскольку косинусное расстояние представляет собой линейное пространство, где все измерения имеют одинаковый вес). (https://github.com/google-research/bert/issues/164#issuecomment-441324222)

Если вы хотите использовать BERT для определения сходства предложений, ближайшей задачей должна быть классификация пар предложений.

входные данные — это два предложения, которые вы хотите сравнить, и цель состоит в том, имеют ли эти два предложения одинаковое значение или нет.

Однако этот метод неэффективен. Представьте, что у вас есть 100 предложений и вы хотите узнать сходство каждой пары предложений, тогда вам нужно упреждать BERT C(100, 2) = 4950 раз.

Прямой способ — обучить осмысленное вложение, тогда вектор встраивания будет содержать «смысл» предложения. Вам нужно только вычислить сходство вектора встраивания, чтобы получить сходство предложения.

На ум может прийти Сиамские сети. Упреждайте два слоя BERT по отдельности, затем используйте Контрастные потери или Триплетные потери для обучения встраиванию.

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

Вы можете обратиться к Предложение-BERT: встраивание предложений с использованием сиамских сетей BERT. Основная идея состоит в том, чтобы просто использовать BERT для обучения сиамской сети.

Авторы рассматривают три способа стратегий объединения: использование выходных данных CLS-токена, вычисление среднего значения всех выходных векторов (MEAN-стратегия) и вычисление максимального времени выходных векторов (MAX-стратегия) и наконец, выбрав стратегию MEAN в качестве опции по умолчанию.

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

Упражнение

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

Сначала импортируйте пакет и загрузите модель.

from sentence_transformers import SentenceTransformer, util

model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')  # multi-language model

sentences = [
    'what is the weather tomorrow',
    'will it rain tomorrow',
    'Will the weather be hot in the future',
    'what time is it',
    'could you help me translate this setence',
    'play some jazz music'
]

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

embedding = model.encode(sentences, convert_to_tensor=False)
embedding.shape
#(6, 384)

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

Затем попарно вычисляем сходство косинусов и наблюдаем за результатом.

cosine_scores = util.cos_sim(embedding, embedding)

d = {}
for i, v1 in enumerate(sentences):
    for j, v2 in enumerate(sentences):
        if i >= j:
            continue
        d[v1 + ' vs. ' + v2] = cosine_scores[i][j].item()

# sort by score
d_sorted = dict(sorted(d.items(), key=lambda x: x[1], reverse=True))
d_sorted

{'what is the weather tomorrow vs. will it rain tomorrow': 0.8252906203269958,
 'what is the weather tomorrow vs. Will the weather be hot in the future': 0.6635355949401855,
 'will it rain tomorrow vs. Will the weather be hot in the future': 0.5936063528060913,
 'what is the weather tomorrow vs. what time is it': 0.47494661808013916,
 'will it rain tomorrow vs. what time is it': 0.4440332055091858,
 'Will the weather be hot in the future vs. what time is it': 0.33612486720085144,
 'could you help me translate this setence vs. play some jazz music': 0.1588955670595169,
 'what is the weather tomorrow vs. play some jazz music': 0.11192889511585236,
 'will it rain tomorrow vs. play some jazz music': 0.09996305406093597,
 'will it rain tomorrow vs. could you help me translate this setence': 0.09915214776992798,
 'what time is it vs. could you help me translate this setence': 0.09021759033203125,
 'what is the weather tomorrow vs. could you help me translate this setence': 0.08801298588514328,
 'Will the weather be hot in the future vs. could you help me translate this setence': 0.07638849318027496,
 'what time is it vs. play some jazz music': 0.054117172956466675,
 'Will the weather be hot in the future vs. play some jazz music': 0.027871515601873398}

Оценка кажется вполне разумной!

Ссылка

НЛП-Трансформеры:BERT的应用、训练和优化

Подобие установки BERT

Трансформеры предложений