BERT — Bidirectional Encoder Representations from Transformers
Здесь не будет рассказываться о том, что такое BERT, как это работает и для чего применяется — в сети об этом достаточно информации.
Эта статья про личный опыт — как конкретно у меня получилось запустить BERT с чистого Colab по конкретным описаниям.
Исходники
Изучение материала основывалось на следующих статьях:
Визуализируя нейронный машинный перевод (seq2seq модели с механизмом внимания)
Transformer в картинках
BERT, ELMO и Ко в картинках (как в NLP пришло трансферное обучение)
Ваш первый BERT: иллюстрированное руководство
A Visual Notebook to Using BERT for the First TIme.ipynb
Применяемые фрагменты кода
Код в целом без подробностей и комментариев, так как все подробно изложено в приведенных статьях.
# устанавливаем трансформеры
!pip install transformers
# устанавливаем библиотеки
import numpy as np
import pandas as pd
import torch
import transformers as ppb
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import cross_val_score
import warnings
warnings.filterwarnings('ignore')
# скачиваем dataset
df = pd.read_csv('https://github.com/clairett/pytorch-sentiment-classification/raw/master/data/SST2/train.tsv', delimiter='\t', header=None)
# выбираем фрагмент для ускорения и экономии времени
batch_1 = df[:2000]
# проверяем, что с данными все ок
print(batch_1[:5])
# Загружаем предобученную модель и токенизаторы
model_class, tokenizer_class, pretrained_weights = (ppb.DistilBertModel, ppb.DistilBertTokenizer, 'distilbert-base-uncased')
tokenizer = tokenizer_class.from_pretrained(pretrained_weights)
model = model_class.from_pretrained(pretrained_weights)
# токенизируем
tokenized = batch_1[0].apply((lambda x: tokenizer.encode(x, add_special_tokens=True)))
# делаем из списков массив, чтобы была одинаковая длина
max_len = 0
for i in tokenized.values:
if len(i) > max_len:
max_len = len(i)
padded = np.array([i + [0]*(max_len-len(i)) for i in tokenized.values])
# маскируем сделанные добавления
attention_mask = np.where(padded != 0, 1, 0)
# создаем входной вектор из матрицы токенов
input_ids = torch.tensor(padded)
attention_mask = torch.tensor(attention_mask)
with torch.no_grad():
last_hidden_states = model(input_ids, attention_mask=attention_mask)
# разделяем данные на обучающую и тестовую выборки
labels = batch_1[1]
train_features, test_features, train_labels, test_labels = train_test_split(features, labels)
# обучаем модель логистической регрессии на обучающей выборке
lr_clf = LogisticRegression()
lr_clf.fit(train_features, train_labels)
На данном этапе работы по обучению модели, описанные в статьях, закончены.
Ссылка на блокнот Colab
Проверяем корректность
Прежде всего посмотрим показатели точности:
# модель обучена, смотрим метрики
print('# train:',lr_clf.score(train_features, train_labels))
print('# test:',lr_clf.score(test_features, test_labels))
# train: 0.906
# test: 0.844
Теперь проверим «визуально», что у нас в принципе получилось обучить модель. Для этого возьмем размеченные фрагменты из датасета, посчитаем их сами с помощью полученных коэффициентов и сравним результаты.
# распечатаем фрагмент из датасета
print(batch_1[:5])
0 a stirring , funny and finally transporting re... 1
1 apparently reassembled from the cutting room f... 0
2 they presume their audience wo n't sit still f... 0
3 this is a visually stunning rumination on love... 1
4 jonathan parker 's bartleby should have been t... 1
# посчитаем сами и сравним
def LR(x): return (1 if x > 0 else 0)
def correct(LR, label): return True if LR - label == 0 else False
sum_false = 0
N = 5
for i in range (N):
if i and i % 100 == 0: print(i)
array = np.array(tokenized[i])
array = np.pad(array, (0, max_len - len(array)), 'constant')
attention_mask_this = np.where(array != 0, 1, 0)
input_ids_this = torch.tensor([array])
attention_mask_this = torch.tensor(attention_mask_this)
with torch.no_grad():
last_hidden_states_this = model(input_ids_this, attention_mask=attention_mask_this)
features_this = last_hidden_states_this[0][:,0,:].numpy()
sum = np.dot(features_this,lr_clf.coef_[0])
if N < 20: print(i, labels[i], LR(sum), correct(LR(sum), labels[i]), sum)
if correct(LR(sum), labels[i]) == True: sum_false += 1
print()
print(sum_false/(i+1))
0 1 1 True [3.85857511]
1 0 0 True [-6.51904703]
2 0 0 True [-2.21694384]
3 1 1 True [3.65850942]
4 1 0 False [-0.32528463]
0.8
Видим, что 1 ошибка из 5, то есть совпадает с ожидаемой точностью.
Можно сравнивать и больше строк, меняя N, и видно, что вывод модели совпадает с заданной разметкой в параметрах заявленной точности, то есть считаем, что модель понята и «перенесена» корректно.
Анализируем свой текст
Теперь проанализируем свои фрагменты текста, то есть зададим новые фрагменты и выведем оценку с помощью полученных коэффициентов.
# задаем свои фрагменты
texts = [
'All is good',
'it is so bad',
'nice to meet you',
'it is so rainy',
'he is a stupid',
'I like my car',
'have a nice day'
]
# Классифицируем свои фрагменты
for text in texts:
array = tokenizer.encode(text, add_special_tokens=True)
array = np.pad(array, (0, max_len - len(array)), 'constant')
attention_mask_this = np.where(array != 0, 1, 0)
input_ids_this = torch.tensor([array])
attention_mask_this = torch.tensor(attention_mask_this)
with torch.no_grad():
last_hidden_states_this = model(input_ids_this, attention_mask=attention_mask_this)
features_this = last_hidden_states_this[0][:,0,:].numpy()
sum = np.dot(features_this,lr_clf.coef_[0])
print(text, ':', sum, ':', LR(sum))
All is good : [1.90097556] : 1
it is so bad : [-1.86999172] : 0
nice to meet you : [4.96587936] : 1
it is so rainy : [-2.53429642] : 0
he is a stupid : [-2.4318853] : 0
I like my car : [0.2118134] : 1
have a nice day : [2.30864912] : 1
1 - позитивно
0 - негативно
Видим, что модель корректно классифицирует представленные фрагменты.
Результат
В результате модель корректно классифицирует фрагменты текста на позитивный/негативный.
На следующем этапе предполагается протестировать разные наборы данных и сделать варианты модели для русского языка.