Как стать автором
Обновить

Комментарии 10

>>> поэтому для train и test буду брать предложения длиной 417, а не 512 как по умолчанию, для того, чтобы уменьшить разреженность данных...

Ох... все в кучу. Какая "разреженность данных"! У вас на входе берта вектор в 512 токенов, считая стартовый и оконечный. Вы берете предобученный токенайзер, который знает эту размерность и все, что вам нужно сделать, это выставить padding=True, truncation=True, что бы недостающее забивалось, лишнее обрезалось - все! А вы зачем-то проверяете длину строк... Вы же знаете, да, что токен ≠ символ? то, что у вас 417 символов в строке не говорит о том, что у вас 417 токенов будет. Их будет меньше: токен ближе к понятию слова, иногда - части слова, но не букве. Вы берете датасет и говорите ему забивать до 417 токенов (хотя по факту в токенах там я дума будет не больше 200 всегда), но при этом на входе у вас ожидается 512... Хм.. Полагаю это вообще должно давать ошибку, при условии, что вы не используете модифицированную модель берта со входом 417. Проверить не могу, тк. from_pretrained('rubert_base_cased_sentence/') - это что-то с вашего локального диска, а не из хаба хуггинов. Но если брать from_pretrained('DeepPavlov/rubert-base-cased-sentence') - будет ошибка, к гадалке не ходить хотя, может и не будет (у хуггингов модель навороченная - может приводить к нужной размерности сама), но это все равно глупо!

Ну и так - по уму надо бы конечно зафризить параметры самого берта и обучать только классификатор:

for param in model.bert.parameters():

    param.requires_grad = False

Иначе это в общем не совсем Fine-Tune. Хотя это, конечно по-всякому бывает.

tokenizer("Привет, мир!", return_tensors='pt', max_length = 10, padding = 'max_length', truncation=True)

>>> {'input_ids': tensor([[ 101, 77527, 128, 6913, 106, 102, 0, 0, 0, 0]], device='cuda:0'), 'token_type_ids': tensor([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], device='cuda:0'), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 0, 0, 0, 0]], device='cuda:0')}

101 = [CLS], 102 = [SEP], 128 = ",", 106 = "!"

Итого слова "Привет" и "мир" кодируются каждое в единственный токен 77527 и 6913 соответственно.

Пример, приведенный вами, отлично показывает, что Bert работает именно с токенами, а не с символами.

С токенами, понимаете? Не со словами...

В приеденном выше примере split() разобьет строку на две. А токенов там, после токенайзера - шесть!

@Nehc Спасибо за интерес к публикации.

Я понимаю, в чем отличие токена от символа, и в статье проверялась не длина строк в символах, а именно количество слов (токенов в строке)

str(i).SPLIT()

А можете привести пример воспроизводимого кода, в котором показано, что padding=True, truncation=True будет достаточно, для обрезания и с той и с другой стороны предложения. Способом, описанным в статье мы и правда ограничиваем кол-во токенов в предложении. Если вы считаете, что “колхозный” метод, то поделитесь правильным, думаю всем будет интересно.

Обновляя веса в Bert и расширяя его изначальный словарь мы как раз-таки и добиваемся отличной работы модели для конкретной задачи. В чем смысл навешивать лишний слой нейронки поверх Bert, если сам Bert не меняет свои предобученные веса. С тем же успехом можно достать из Bert эмбеддинги и обучить на них небольшую нейронку для их классификации.

split - еще хуже! Вы в таком случае обрезаете половину значащего текста.

Токены, это токены! Не слова, не символы. И как строка будет перекодирована в токены - зависит от токенайзера: в "ванильном" мультиязычном берте в тестовой строке - 24 токена, в DeepPavlov - 16, после split() - 9! См. пример.

>>> А можете привести пример воспроизводимого кода, в котором показано, что padding=True, truncation=True будет достаточно, для обрезания и с той и с другой стороны предложения.

Да. см ссылку выше. Классификатор берта знает размер входа модели, что называется "из коробки" - там достаточно этих двух параметров. DeepPavlov пошли своим путем - у них не знает. :) Но там достаточно добавить просто ограничение в 512 и все будет хорошо. Не нужно ничего пытаться высчитывать - зачем? Где вы взяли эту чушь про "разреженность данных"?

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

Ну как бы в этом сущность fine tuning вообще-то. :) И да - можно обучить небольшую сеть на эмбеддингах - это тоже вариант, но усложняет pipeline. А смысл очень простой: берт обучался на большом релевантном датасете, в нем контролировались важные параметры вроде перплексии и те, кто его обучали добились почти идеальных эмбеддингов для широкого класса задач. Вы своим дообучением на небольшой выборке веса не улучшите, а с большой вероятностью добьетесь оверфита на тестовой выборке в ущерб генерализации. Иногда без этого никак (когда задача узкая и универсальных эмбеддингов недостаточно), но у вас явно не тот случай: оценка тональности отзывов - хрестоматийный пример!

Ну и да - при чем тут "лишний слой"? Вы же используете BertForSequenceClassification - а это как раз и есть берт, плюс небольшой классификатор на выходе. :) Так что слой там уже и так есть, нужно просто немного настроить обучение, что бы обучался только этот слой - это еще и значительно быстрее! И уже только если вы понимаете, что на основе ванильных эмбеддингов этот классификатор не может решить задачу, тогда решать уже - действительно брать с берта эмбеддинги и обучать что-то посерьезнее, или пожертвовать предобученными весами берта.

И еще: словарь берта - это токенайзер. Так что словарь вы точно не расширяете... обучение токенайзера - отдельная тема и это уж точно не файн-тюнинг.

Спасибо за наглядный пример со split, учту в будущем

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

Еще интересный момент, который я упустил... Используемая вами функция batch_encode_plus, при настройках padding=True, truncation=True сделает ровно то, что вы и хотели - создаст батч с токенизированными входными последовательностями равной длины. Если при этом токенайзер знает максимальную размерность входа, или вы укажете параметр max_length - то не больше максимальной, но! Важно понимать, что при padding=True - он не будет добивать до максимальной, если ВСЕ последовательности в сете короче. Т.е. они будут просто самой большой длины из тех, что есть. А вот что бы их сделать ровно max_length не больше и не меньше, нужно указывать padding = 'max_length'. добавил пару примеров в colab.

Классная статья! а как модель понимает где жалоба, а где нет, чтобы учиться?

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации