kwcluster: разметка переменных поискового запроса
LoRA-адаптер поверх Qwen/Qwen2.5-1.5B-Instruct для одной задачи: определить,
чем является слово в поисковом запросе.
BRAND — компания, сайт, оператор. PRODUCT — игра, товар, вид игры.
PAY — способ оплаты. GEO — страна, регион, город. OFFER — вид
предложения или условие сделки. WORD — всё остальное.
Метка отвечает на вопрос, задаёт ли слово отдельную страницу сайта. Модель
работает слоем переменных в кластеризаторе семантики: она заменяет большую
языковую модель там, где раньше уходил час на четыре тысячи слов. На той же
работе — семь секунд.
Студии-разработчики моделью не размечаются намеренно: их семьдесят на всю
отрасль, состав годами не меняется, и точный справочник надёжнее модели.
Это классификатор, а не генеративная модель
Обучалась голова классификации, языковая голова базовой модели не трогалась.
Просить её сгенерировать текст бесполезно. Промпта и системного сообщения нет.
1import torch
2from transformers import AutoModelForSequenceClassification, AutoTokenizer
3from peft import PeftConfig, PeftModel
4
5REPO = "Krasovskiy/kwcluster-variables-1.5b"
6LABELS = ["BRAND", "GEO", "OFFER", "PAY", "PRODUCT", "WORD"] # порядок из labels.json
7
8tok = AutoTokenizer.from_pretrained(REPO)
9tok.pad_token = tok.pad_token or tok.eos_token
10tok.padding_side = "right"
11cfg = PeftConfig.from_pretrained(REPO)
12base = AutoModelForSequenceClassification.from_pretrained(
13 cfg.base_model_name_or_path, num_labels=6, dtype=torch.bfloat16)
14base.config.pad_token_id = tok.pad_token_id
15model = PeftModel.from_pretrained(base, REPO).eval().cuda()
16
17def label(token, queries):
18 text = f"слово: {token}" + chr(10) + f"запрос: {' | '.join(queries[:5]) or token}"
19 enc = tok([text], truncation=True, max_length=64,
20 padding="max_length", return_tensors="pt").to("cuda")
21 with torch.no_grad():
22 return LABELS[model(**enc).logits.float().argmax(-1).item()]
Вход — слово и до пяти запросов, где оно встречается, самые частотные первыми,
разделитель |. Контекст обязателен: «quatro» без него читается как
числительное, «delta» — как буква. Порядок меток брать строго из labels.json.
Что изменилось в этой версии
Добавлена ручная разметка второй сотни верхушки частот: 296 слов, размеченных
по настоящим запросам ядра. Каждое просмотрено вместе с примерами и числом
ключей, которые оно задевает. Вместе с первой сотней это 575 слов.
| было | стало | слова |
|---|
| обычное слово | PAY | boku, btc, litecoin, zimpler, entropay, ideal, deposito |
| обычное слово | GEO | québec, montreal, nunavut, pei, vancouver, labrador |
| обычное слово | OFFER | bono, reward, cashback, tournaments |
| BRAND | PAY | klarna, gigadat, instadebit |
| BRAND | GEO | schweiz |
| BRAND | обычное слово | iphone, igaming, reddit |
Отдельно вычищены обломки чужих имён: hill от William Hill, sultans от
7 Sultans, cooks от Captain Cooks, apple от Apple Pay, brunswick от New
Brunswick. По одному слову бренд не собрать, а метка утащила бы чужие запросы.
Что получилось на настоящем ядре
Мерилось не на отложенной выборке, а прогоном всего инструмента на канадском
ядре из 13 942 запросов — там подглядеть нельзя.
| показатель | прошлая версия | эта |
|---|
| слов ядра без сущности | 58,4% | 52,5% |
| ключей на страницах гео | 942 | 1215 |
| ключей на страницах оплаты | 721 | 1084 |
| ключей на страницах бренда | 2550 | 2685 |
| ключей, сгруппированных по смыслу | 2695 | 2377 |
| контрольные проверки | 9 из 10 | 9 из 10 |
| страниц с двумя брендами | 0 | 0 |
Около 960 запросов перешли из группировки по смысловой близости в группировку
по переменной, видной в самом запросе. Это и есть смысл слоя: меньше догадки,
больше признака.
Заодно ушли три застарелые ошибки: paysafecard переехал с брендовой страницы
на страницу оплаты, microgaming — на страницу студии, baccarat перестал
считаться брендом.
Чего эти цифры не говорят
Появились новые ошибки. blackjack и plinko помечены брендами, хотя это
игры; upaycard — брендом вместо платёжки; argent (по-французски «деньги») и
crypto — географией. Затронуто около 180 запросов против 960 улучшенных.
Одиночных страниц стало больше: 1076 против 898. Каждая новая узнанная
сущность получает свой адрес, даже если запрос к ней один.
Мерилось на одной нише и одном языковом наборе. Канада, английский с
французским вкраплением. На другом рынке цифры будут другими.
На чём обучалась
6839 строк, у каждой — до пяти настоящих поисковых запросов, где слово
встречается.
Обычные слова — половина выборки. Это главное отличие от версий, которые
проигрывали: в настоящем ядре обычных слов четыре пятых, и без них модель
метит сущностью всё подряд.
Бренды — компании, подтверждённые сайтом или лицензией в реестре.
Игры — названия из каталогов, снятых с карт сайтов подтверждённых
операторов.
Оплата и условия — разделы касс и акций плюс корпус на 11 млн наблюдений,
129 стран. Классы многоязычные: einzahlung, freispiele, tiradas,
rodadas живут наравне с английскими.
Пары слов — 35 штук: «no deposit», «free spins», «apple pay», «minimum
deposit». Обломок смысла не несёт, а пара несёт: «no» это отрицание в тысяче
запросов, «no deposit» — конкретное предложение в 785.
LoRA r=16 на проекциях внимания, 4.4 млн обучаемых параметров, шесть эпох,
сохранена лучшая. Веса классов смягчены до корня из обратной частоты.
Как эту модель мерили
Набор для проверки собран из справочников, а не из чьего-то мнения: реестр
операторов регулятора Альберты (31 слово), каталог платёжных систем (24),
страны и регионы (90), виды игр (22) — и, обязательно, 41 отрицательный пример
из списка «не сущности», слов, которым метка не полагается ни от кого.
Отрицательные примеры обязательны. Без них набор меряет полноту и совсем
не меряет точность: модель, щедрая на метки, получает на нём отличную оценку.
Версия, обученная на разметке gpt-5-mini, набрала на наборе без
отрицательных 81% против 42% у этой — и развалила план на 3231 страницу
вместо 2321, с медианой в один ключ вместо двух.
| модель | всего | BRAND | GEO | PAY | PRODUCT | WORD |
|---|
| gpt-5-mini (платная, для сравнения) | 177/208 (85%) | 29/31 | 85/90 | 22/24 | 17/22 | 24/41 |
| лучшая из наших по этому набору | 163/208 (78%) | 24/31 | 83/90 | 17/24 | 11/22 | 28/41 |
| эта версия | 103/208 (50%) | 10/31 | 47/90 | 6/24 | 8/22 | 32/41 |
Цифра выглядит плохо, и это надо объяснить честно. Набор перекошен: в нём 167
сущностей и 41 обычное слово, а в живом ядре наоборот — около 72% обычных
слов. Если взвесить по настоящим долям, счёт становится 67% против 68% у
лучшей, то есть разрыва почти нет. А на прогоне всего инструмента эта версия
выигрывает у обеих «лучших»: 2321 страница против 3165 и 3231, доля страниц
без замечаний 94,6% против 88,8% и 87,5%.
Поэтому опубликована именно она. Правило: версия считается лучше, только
если выиграла дважды — на наборе и на прогоне. Прогон главнее.
Все семнадцать обученных версий, замеренных одной линейкой, лежат в
эксперименты.md.
Где модель слаба
Товары — 8 из 22. bingo, crash, dice, live dealer она зовёт
обычными словами. Ни одна из семнадцати версий не берёт больше 15 из 22: класс
набран из обломков названий слотов, а не из видов игр. В инструменте это
закрыто справочником — множество закрытое, точное совпадение надёжнее.
Касса — 6 из 24. flexepin, gigadat, koho уходят в бренды. Тоже
закрыто справочником.
То есть модель полезна там, где справочник невозможен: незнакомые названия,
регионы, обычные слова. Всё закрытое лучше держать списком.