Доменные термины добавили в токенизатор, и дообучение падает — в чём ошибка?
Классификатор фармтекстов просел, потому что токенизатор режет названия препаратов на восемь и более кусков. В токенизатор добавили четыреста доменных терминов, и дообучение теперь падает внутри поиска по эмбеддингам.
Ограничения:
- чекпоинт
bert-base-uncasedнадо переиспользовать, обучение только дообучением; - расширенный словарь токенизатора менять нельзя.
tok = AutoTokenizer.from_pretrained("bert-base-uncased")
tok.add_tokens(DRUG_TERMS) # 400 доменных терминов
model = AutoModelForSequenceClassification.from_pretrained(
"bert-base-uncased", num_labels=12
)
batch = tok(texts, padding=True, truncation=True, return_tensors="pt")
loss = model(**batch, labels=labels).loss # IndexError в эмбеддинге
Найдите и исправьте ошибку.
Добавление токенов расширяет словарь токенизатора, но не матрицу эмбеддингов модели, поэтому новые id выходят за её пределы и обучение падает. Вызовите resize_token_embeddings после add_tokens; новые строки случайны, так что заложите дообучение, иначе термины пусты.
- ✗Вызывают add_tokens, не расширив матрицу эмбеддингов модели
- ✗Ждут осмысленности от новых строк без дополнительного обучения
- ✗Винят оптимизатор или learning rate в выходе индекса за диапазон
- →Как инициализировать новые строки лучше, чем случайно?
- →Когда дешевле оставить термины раздробленными?
Расширение словаря токенизатора и матрица эмбеддингов модели — две независимые вещи. add_tokens увеличивает только словарь, поэтому новые id (30522, 30523, …) оказываются за пределами nn.Embedding исходного чекпоинта, и первый же forward падает с IndexError. Лечится одной строкой сразу после добавления токенов:
tok = AutoTokenizer.from_pretrained("bert-base-uncased")
added = tok.add_tokens(DRUG_TERMS)
model = AutoModelForSequenceClassification.from_pretrained(
"bert-base-uncased", num_labels=12
)
model.resize_token_embeddings(len(tok)) # <- пропущенная строка
# Новые строки инициализированы случайно: задайте им среднее
# по субсловным кускам исходного термина, иначе им нужно долгое дообучение.
emb = model.get_input_embeddings().weight.data
for term in DRUG_TERMS:
pieces = tok(term, add_special_tokens=False)["input_ids"]
emb[tok.convert_tokens_to_ids(term)] = emb[pieces].mean(dim=0)
Цена расширения словаря: added новых строк по hidden_size параметров каждая, и все они не обучены. Если доменных терминов немного и корпус для дообучения маленький, дешевле оставить их раздробленными — качество субсловного представления часто выше, чем у случайно инициализированного токена.