Библиотека Navec — часть проекта Natasha, коллекция предобученных эмбеддингов для русского языка. Качество решения сравнимо или выше, чем у статических моделей от RusVectores, размер в 5–6 раз меньше:
Качество | Размер модели, МБ | Размер словаря, ×103 | |
---|---|---|---|
Navec | 0.719 | 50.6 | 500 |
RusVectores | 0.638–0.726 | 220.6–290.7 | 189–249 |
В этой статье поговорим про старые добрые пословные эмбеддинги, совершившие революцию в NLP в 2013 году.
python − счастье + страдание = perl
Технология актуальна до сих пор. В проекте Natasha модели для разбора морфологии, синтаксиса и извлечения именованных сущностей работают на пословных Navec-эмбеддингах, показывают качество выше других открытых решений.
Для русского языка принято использовать предобученные эмбеддинги от RusVectores, у них есть неприятная особенность: в таблице записаны не слова, а пары «слово_POS-тег». Идея хорошая, для пары «печь_VERB» ожидаем вектор, похожий на «готовить_VERB», «варить_VERB», а для «печь_NOUN» — «изба_NOUN», «топка_NOUN».
На практике использовать такие эмбеддинги неудобно. Недостаточно разделить текст на токены, для каждого нужно как-то определить POS-тег. Таблица эмбеддингов разбухает. Вместо одного слова «стать», мы храним 6: 2 разумных «стать_VERB», «стать_NOUN» и 4 странных «стать_ADV», «стать_PROPN», «стать_NUM», «стать_ADJ». В таблице на 250 000 записей 195 000 уникальных слов.
Оценим качество эмбеддингов на задаче семантической близости. Возьмём пару слов, для каждого найдём вектор-эмбеддинг, посчитаем косинусное сходство. Navec для похожих слов «чашка» и «кувшин» возвращет 0.49, для «фрукт» и «печь» — −0.0047. Соберём много пар с эталонными метками похожести, посчитаем корреляцию Спирмена с нашими ответами.
Кроме семантической близости для оценки качества эмбеддингов используют задачу решения аналогий. RusVectores публикует переведённый датасет Google Analogies. Прогнать на нём Navec пока не дошли руки.
Авторы RusVectores используют небольшой аккуратно проверенный и исправленный тестовый список пар SimLex965. Добавим свежий яндексовый LRWC и датасеты из проекта RUSSE: HJ, RT, AE, AE2:
Таблица с разбивкой по датасетам в репозитории Navec.
Среднее качество на 6 датасетах | Время загрузки, секунды | Размер модели, МБ | Размер словаря, ×103 | ||
---|---|---|---|---|---|
Navec | hudlit_12B_500K_300d_100q |
0.719 | 1.0 | 50.6 | 500 |
news_1B_250K_300d_100q |
0.653 | 0.5 | 25.4 | 250 | |
RusVectores | ruscorpora_upos_cbow_300_20_2019 |
0.692 | 3.3 | 220.6 | 189 |
ruwikiruscorpora_upos_skipgram_300_2_2019 |
0.691 | 5.0 | 290.0 | 248 | |
tayga_upos_skipgram_300_2_2019 |
0.726 | 5.2 | 290.7 | 249 | |
tayga_none_fasttextcbow_300_10_2019 |
0.638 | 8.0 | 2741.9 | 192 | |
araneum_none_fasttextcbow_300_5_2018 |
0.664 | 16.4 | 2752.1 | 195 |
Качество hudlit_12B_500K_300d_100q
сравнимо или лучше, чем у решений RusVectores, словарь больше в 2–3 раза, размер модели меньше в 5–6 раз. Как удалось получить такое качество и размер?
hudlit_12B_500K_300d_100q
— GloVe-эмбеддинги обученные на 145ГБ художественной литературы. Архив с текстами возьмём из проекта RUSSE. Используем оригинальную реализацию GloVe на C, обернём её в удобный Python-интерфейс.
Почему не word2vec? Эксперименты на большом датасете быстрее с GloVe. Один раз считаем матрицу коллокаций, по ней готовим эмбеддинги разных размерностей, выбираем оптимальный вариант.
Оригинальная статья про GloVe: Global Vectors for Word Representation
Почему не fastText? В проекте Natasha мы работаем с текстами новостей. В них мало опечаток, проблему OOV-токенов решает большой словарь. 250 000 строк в таблице news_1B_250K_300d_100q
покрывают 98% слов в новостных статьях.
Статья Давида Дале про компактный предобученный fastText для русского языка: Как сжать модель fastText в 100 раз
Размер словаря hudlit_12B_500K_300d_100q
— 500 000 записей, он покрывает 98% слов в художественных текстах. Оптимальная размерность векторов — 300. Таблица 500 000 × 300 из float-чисел занимает 578МБ, размер архива с весами hudlit_12B_500K_300d_100q
в 12 раз меньше (48МБ). Дело в квантизации.
Заменим 32-битные float-числа на 8-битные коды: [−∞, −0.86) — код 0, [−0.86, -0.79) — код 1, [-0.79, -0.74) — 2, …, [0.86, ∞) — 255. Размер таблицы уменьшится в 4 раз (143МБ):
Было: -0.220 -0.071 0.320 -0.279 0.376 0.409 0.340 -0.329 0.400 0.046 0.870 -0.163 0.075 0.198 -0.357 -0.279 0.267 0.239 0.111 0.057 0.746 -0.240 -0.254 0.504 0.202 0.212 0.570 0.529 0.088 0.444 -0.005 -0.003 -0.350 -0.001 0.472 0.635 -0.170 0.677 0.212 0.202 -0.030 0.279 0.229 -0.475 -0.031 Стало: 63 105 215 49 225 230 219 39 228 143 255 78 152 187 34 49 204 198 163 146 253 58 55 240 188 191 246 243 155 234 127 127 35 128 237 249 76 251 191 188 118 207 195 18 118
Данные огрубляются, разные значения -0.005 и -0.003 заменяет один код 127, -0.030 и -0.031 — 118
Заменим кодом не одно, а 3 числа. Кластеризуем все тройки чисел из таблицы эмбеддингов алгоритмом k-means на 256 кластеров, вместо каждой тройки будем хранить код от 0 до 255. Таблица уменьшится ещё в 3 раза (48МБ). Navec использует библиотеку PQk-means, она разбивает матрицу на 100 колонок, каждую кластеризует отдельно, качество на синтетических тестах падёт на 1 процентный пункт.
Понятно про квантизацию в статье Chris McCormick Product Quantizers for k-NN Tutorial
Квантованные эмбеддинги проигрывают обычным по скорости. Сжатый вектор перед использованием нужно распаковать. Аккуратно реализуем процедуру, применим Numpy-магию, в PyTorch используем torch.gather
. В Slovnet NER доступ к таблице эмбеддингов занимает 0.1% от общего времени вычислений.
Список предобученных моделей, инструкция по установке — в репозитории Navec.
Модуль NavecEmbedding
из библиотеки Slovnet интегрирует Navec в PyTorch-модели:
>>> import torch >>> from navec import Navec >>> from slovnet.model.emb import NavecEmbedding >>> path = 'hudlit_12B_500K_300d_100q.tar' # 51MB >>> navec = Navec.load(path) # ~1 sec, ~100MB RAM >>> words = ['навек', '<unk>', '<pad>'] >>> ids = [navec.vocab[_] for _ in words] >>> emb = NavecEmbedding(navec) >>> input = torch.tensor(ids) >>> emb(input) # 3 x 300 tensor([[ 4.2000e-01, 3.6666e-01, 1.7728e-01, [ 1.6954e-01, -4.6063e-01, 5.4519e-01, [ 0.0000e+00, 0.0000e+00, 0.0000e+00, ...