Практические руководства
Как адаптировать многоязычную модель T5 к одному языку
Загружайте вложения только для токенов вашего языка, чтобы уменьшить размер модели
T5 - это преобразователь кодировщика-декодера от Google, который когда-то был SOTA по нескольким проблемам NLU и NLG, и до сих пор очень полезен в качестве основы для задач seq2seq, таких как резюмирование текста. Первая модель T5 была только для английского, затем последовала массовая многоязычная версия. Эта модель охватывает 101 язык и действительно огромна.
В этом посте показано, как извлечь одноязычную модель из многоязычной, удалив ее избыточные вложения. Это снижает количество параметров более чем в два раза без существенной потери качества. Наш результат - для русского, но вы можете попробовать его с любым другим из 101 языка, поддерживаемого mT5.
Выбор словаря
Идея аналогична изложенной в статье Загрузите то, что вам нужно: небольшие версии многоязычного BERT. Мы используем исходный токенизатор для обработки русского корпуса, подсчета частот различных токенов и сохранения только тех токенов, которые использовались достаточно часто, отсекая все остальные.
Мы также сохраняем небольшое количество английских токенов в модели, чтобы сделать ее двуязычной. Это необходимо для того, чтобы модель могла передавать знания с английского на русский, а также потому, что английские слова и фразы часто встречаются в современных русских текстах.
Начнем с загрузки существующей многоязычной модели.
import torch from transformers import T5ForConditionalGeneration, T5Tokenizer tokenizer = T5Tokenizer.from_pretrained("google/mt5-base") model = T5ForConditionalGeneration.from_pretrained('google/mt5-base')
Модель состоит в основном из встраиваний: 33% ее параметров - это входные вложения (общие для кодировщика и декодера), а 33% - выходные вложения.
def msize(m): return sum(p.numel() for p in m.parameters()) print(msize(model.shared) / msize(model)) # 0.3298 print(msize(model.lm_head) / msize(model)) # 0.3298
Чтобы оценить частоту различных токенов, мы берем корпус предложений на русском и английском языках из Лейпцигской коллекции корпусов. Мы используем эти два языка, потому что хотим, чтобы наша модель в конечном итоге была двуязычной.
import pandas as pd import csv from collections import Counter from tqdm.auto import tqdm, trange df_ru = pd.read_csv('rus-ru_web-public_2019_1M-sentences.txt', sep='\t', header=None, quoting=csv.QUOTE_NONE) df_ru.columns = ['idx', 'text'] cnt_ru = Counter() for text in tqdm(df_ru.text): cnt_ru.update(tokenizer.encode(text)) print(len(cnt_ru), len(cnt_ru)/tokenizer.vocab_size) # 58438 0.2336
После подсчета токенов в корпусе русского языка мы обнаруживаем, что было использовано только 23% модельного словаря. Более того, топ-20 тысяч токенов составляют более 99% российского корпуса. Для английского статистика аналогичная.
for top in 10_000, 20_000, 30_000: print(top, sum(v for k, v in cnt_ru.most_common(top)) / sum(cnt_ru.values())) # 10000 0.9645 # 20000 0.9940 # 30000 0.9982
Решаем использовать следующий состав лексики:
- 1К топовых токенов оригинального токенизатора (на всякий случай)
- Топ 10К английской лексики
- Топ 20К русской лексики
- 100 специальных жетонов, которые использует T5
Это дает нам словарный запас из 30K токенов, 12% от 250K токенов в многоязычной версии.
new_tokens = set(range(1000)) for i, (k, v) in enumerate(cnt_en.most_common(10_000)): if k not in new_tokens: new_tokens.add(k) for i, (k, v) in enumerate(cnt_ru.most_common(25_000)): if len(new_tokens) == 29_900: print(i, 'Russan tokens are included') break if k not in new_tokens: new_tokens.add(k) for t in range(tokenizer.vocab_size - 100, tokenizer.vocab_size): new_tokens.add(t) print(len(new_tokens)) kept_ids = sorted(new_tokens)
Обновление модели
Обновить нейронную сеть просто: достаточно заменить параметры ее входных и выходных вложений. Это уменьшает размер модели на 58% (с 2,2 ГБ до 0,9 ГБ).
new_size = len(kept_ids) new_emb = torch.nn.Embedding(new_size, model.shared.embedding_dim) new_head = torch.nn.Linear(in_features=model.lm_head.in_features, out_features=new_size, bias=False) for new_id, old_id in enumerate(kept_ids): new_emb.weight.data[new_id] = model.shared.weight.data[old_id] new_head.weight.data[new_id] = model.lm_head.weight.data[old_id] model.shared.weight = new_emb.weight model.lm_head.weight = new_head.weight model.config.__dict__['vocab_size'] = new_size model.config.__dict__['_name_or_path'] = 'cointegrated/rut5-base'
Обновление токенизатора на удивление сложнее. T5 использует токенизатор Sentencepiece, который реализован на C и непрозрачен для Python. К счастью, мы можем загрузить его модель и развернуть ее в Python, используя его представление Protobuf.
! wget https://raw.githubusercontent.com/google/sentencepiece/master/src/sentencepiece_model.proto ! protoc --python_out=. sentencepiece_model.proto import sentencepiece_model_pb2 as spmp smp = tokenizer.sp_model.serialized_model_proto() m = spmp.ModelProto() m.ParseFromString(smp) print('the loaded model has pieces:', len(m.pieces)) new_pieces = [m.pieces[idx] for idx in kept_ids] print('the new pieces:', len(new_pieces)) # replace the content of the first 30K pieces for i, p in enumerate(new_pieces): m.pieces[i].piece = p.piece m.pieces[i].score = p.score m.pieces[i].type = p.type # drop the remaining pieces n = len(new_pieces) for i in trange(len(m.pieces) - n): m.pieces.pop(len(m.pieces) - 1) print(len(m.pieces)) with open('new_sp.model', 'wb') as f: f.write(m.SerializeToString()) new_tokenizer = T5Tokenizer('new_sp.model', extra_ids=0)
Теперь мы можем сохранить новую модель и новый токенизатор.
new_tokenizer.save_pretrained('rut5-base') model.save_pretrained('rut5-base')
Весь код для создания моделей до этого этапа доступен на Github. Российская модель Т5 доступна в репозитории Huggingface.
Откровенно говоря, эта модель сама по себе бесполезна, потому что mT5 был обучен только неконтролируемой задаче предсказания пропущенных слов. Однако эту модель можно настроить для многих других задач: обобщение текста, перевод, создание диалоговых ответов, перефразирование и т. Д. В следующем посте мы покажем, как выполнить такую тонкую настройку. Подпишитесь, чтобы оставаться в курсе!
Сообщение написал Дэвид Дейл (https://daviddale.ru), ученый-исследователь в области НЛП и разработчик чат-ботов.