ODS-NER-2023
Этот проект создан для участия в соревновании по структуризации чеков ОФД, которое проводилось “Альфа-Банком” на платформе ODS (см. первый, второй и финальный этапы).
Установка
Работа всех компонентов проекта проверена в Python 3.9 через Anaconda. Соответственно, для установки всех зависимостей рекомендуется создать новое виртуальное окружение Anaconda, и далее через pip install
поставить туда необходимые зависимости. Это можно сделать, например, вот так:
conda create -n env_for_ods_ner_2023 python=3.9
conda activate env_for_ods_ner_2023
python -m pip install -r requirements.txt
Чтобы проверить корректность установки всех зависимостей, вы можете запустить юнит-тесты:
python -m unittest
Использование
Шаг 1. Необходимо разбить размеченный датасет на подвыборки train, validation и test случайным образом, но так, чтобы бренды в разных подвыборках не пересекались (это позволит более “честно” проверять обобщающую способность будущих моделей):
python -u split_supervised_dataset.py
-i /path/to/trainset/train_supervised_dataset.csv
-o /path/to/trainset/train_supervised_dataset_splitted42.csv
--random 42
Здесь:
-i
определяет путь к CSV-файлу с исходной обучающей выборкой;-o
- шаблон названий файлов с треия подвыборками (так, дляtrain_supervised_dataset_splitted42.csv
из данного примера это будут, соответственноtrain_supervised_dataset_splitted42_train.csv
,train_supervised_dataset_splitted42_validation.csv
иtrain_supervised_dataset_splitted42_test.csv
);--random
- начальное значение генераторов случайных чисел во всех библиотеках (в данном случае - в random и numpy).
Любой CSV-файл должен состоять строго из четырёх столбцов в следующей последовательности:
1) id - уникальный целочисленный неотрицательный идентификатор записи (строки в CSV-файле); 2) name - текст, в котором необходимо распознать именованные сущности классов “товар” и “бренд”, например, Водка “Русская валюта” премиум люкс 38% 0,25л, Россия; 3) good - товар или список товаров через запятую (в нижнем регистре), которые содержатся в исходном тексте из столбца name, например, водка; если товары отсутствуют в тексте, то должна быть пустая строка; 4) brand - бренд или список брендов через запятую (в нижнем регистре), которые содержатся в исходном тексте из столбца name, например, русская валюта; если бренды отсутствуют в тексте, то должна быть пустая строка.
Исходный файл с размеченной обучающей выборкой можно скачать с сайта организаторов (см. train_supervised_dataset на этой веб-странице), но пример такого файла также приведён в данном репозитории в каталоге data_sample
(см. train_supervised_dataset.csv
).
Шаг 2. Необходимо дообучить базовую модель на размеченных вручную данных. В качестве базовой модели можно использовать FRED-T5 (например, FRED-T5-1.7B или FRED-T5-large). Размеченные данные необходимо скачать с сайта соревнования либо же подготовить самому, если проект предполагается использовать для других целей, отличных от участия в соревновании по структуризации чеков ОФД. Пример размеченных данных доступен в самом репозитории (см. data_sample/train_supervised_dataset.csv
). Дообучение FRED-T5 запускается следующей командой:
python -u train_fredt5_ner.py \
-i /path/to/base/FRED-T5-17.B \
-o /path/to/fine-tuned/FRED-T5-ner-1.7B \
-d /path/to/trainset/train_supervised_dataset_splitted42.csv \
-b 16 --lr 3e-4 --random 123 --alg adafactor --without_preprocessing
Здесь:
-i
определяет путь к папке с базовой моделью (она должна быть выкачана с Huggingface и располагаться на вашем локальном диске);-o
- путь к папке, в которую будет записана модель после её дообучения;-d
- шаблон названий csv-файлов с размеченными датасетами для обучения, валидации и внутреннего тестирования (train_supervised_dataset_splitted42 играет здесь роль префикса, а на самом деле программа ожидает, что на диске будут файлы train_supervised_dataset_splitted42_train.csv, train_supervised_dataset_splitted42_validation.csv и train_supervised_dataset_splitted42_test.csv);-b
- размер мини-батча;--lr
- коэффициент скорости обучения;--random
- начальное значение генераторов случайных чисел во всех библиотеках (random
,numpy
иtorch
);--alg
- тип алгоритма обучения (допускается только Adafactor или Adamax, при этом их названия должны быть записаны в нижнем регистре);--without_preprocessing
- отключение препроцессинга исходных текстов из столбца name (препроцессинг включает в себя “умную” токенизацию, исправление некоторых ошибок OCR, связанных с путаницей между кириллицей и латиницей, а также приведение к нижнему регистру, но опытным путём было выяснено, что препроцессинг лучше отключать).
Если вы запускаете несколько процессов дообучения одной и той же модели, но с разными начальными значениями генераторов случайных чисел и с разными вариантами разбиения исходного обучающего датасета на train, validation и test, то вы в итоге получите разные дообученные модели, которые можно ансамблировать для авторазметки неразмеченного датасета.
Шаг 3. Необходимо применить обученную модель для авторазметки неразмеченного датасета (пример такого датасета можно увидеть в репозитории - см., например, data_sample/train_unsupervised_dataset.csv
). Для модели на базе FRED-T5 это можно сделать следующим образом:
python -u submit_with_fredt5_ner.py \
-i /path/to/your/unlabebeled/dataset/train_unsupervised_dataset.csv \
-m /path/to/your/fine-tuned/FRED-T5-ner-1.7B \
-o /path/to/predictions/for/ublabeled/dataset/predictions.csv \
-b 128 --without_preprocessing
Здесь:
-i
определяет путь к csv-файлу с большим неразмеченным датасетом, для которого необходимо вычислить авторазметку;-m
- путь к папке с дообученной моделью NER;-o
- путь к csv-файлу, в который будут записаны результаты распознавания (первый столбец -id
- там такой же, как и в исходном csv-файле, но вместо других столбцов там появляются столбцыgoods
иpreds
с результатами распознавания товаров и брендов соответственно);-b
- размер мини-батча;--without_preprocessing
- отключение препроцессинга исходных текстов из столбца name.
Любой CSV-файл с неразмеченным датасетом должен состоять строго из двух столбцов в следующей последовательности:
1) id - уникальный целочисленный неотрицательный идентификатор записи (строки в CSV-файле); 2) name - текст, в котором необходимо распознать именованные сущности классов “товар” и “бренд”, например, Водка “Русская валюта” премиум люкс 38% 0,25л, Россия.
Исходный файл с большим неразмеченным датасетом можно скачать с сайта организаторов (см. train_unsupervised_dataset на этой веб-странице), но пример такого файла также приведён в данном репозитории в каталоге data_sample
(см. train_unsupervised_dataset.csv
).
В результате генерации авторазметки несколькими моделями вы получаете несколько результирующих CSV-файлов - например, predictions1.csv
, predictions2.csv
, predictions2.csv
и т.п. Их нужно последовательно объединить с исходным неразмеченным датасетом с помощью скрипта join_tables.py
. Например, вот так исходный неразмеченный датасет объединяется с результатами распознавания первой модели:
python -u join_tables.py \
-f /path/to/your/unlabebeled/dataset/train_unsupervised_dataset.csv \
-s /path/to/predictions/for/ublabeled/dataset/predictions1.csv \
-o /path/to/your/joined/dataset/first_dataset_with_autolabeling.csv
Вручную поправьте заголовок в полученном файле first_dataset_with_autolabeling.csv
так, чтобы в нём вместо goods
и brands
были goods_0
и brands_0
. Затем объедините этот файл с результатами распознавания второй модели:
python -u join_tables.py \
-f /path/to/your/joined/dataset/first_dataset_with_autolabeling.csv \
-s /path/to/predictions/for/ublabeled/dataset/predictions2.csv \
-o /path/to/your/joined/dataset/second_dataset_with_autolabeling.csv
Опять-таки, вручную поправьте заголовок в полученном файле first_dataset_with_autolabeling.csv
так, чтобы в нём вместо goods
и brands
были goods_1
и brands_1
(а goods_0
и brands_0
у вас уже есть). Затем объедините этот файл с результатами распознавания третьей модели, и так далее. В итоге вы получите файл с названием в духе dataset_with_united_autolabeling_by_ensemble.csv
, в котором будут содержаться как входные тексты со своими идентификаторами, так и результаты авторазметки ансамблем моделей.
Шаг 4. Необходимо дообучить базовую модель на смеси размеченных вручную и авторазмеченных данных. Дообучение FRED-T5 на смеси данных запускается следующей командой:
python -u train_fredt5_ner.py \
-i /path/to/base/FRED-T5-large \
-o /path/to/fine-tuned/FRED-T5-ner-large-the-best \
-d /path/to/trainset/train_supervised_dataset_splitted42.csv \
-a /path/to/trainset/dataset_with_united_autolabeling_by_ensemble.csv \
-w 0.8 \
-b 64 --lr 3e-4 --random 42 --alg adafactor --wthout_preprocessing
Ключевым отличием от шага 2 здесь является появление аргумента -a
, специфицирующего дополнительный датасет с авторазметкой, полученный в результате выполнения шага 3, а также появление аргумента -w
, специфицирующего “вес” этого датасета относительно исходного размеченного вручную датасета (этот вес должен быть вещественным числом больше нуля, но не больше единицы).
Возможно использование нескольких вспомогательных датасетов, и тогда их нужно перечислить через точку с запятой (и также через точку с запятой нужно перечислить их веса).
Шаг 5. Необходимо подобрать оптимальные гиперпараметры инференса:
python -m find_best_inference.py \
-i /path/to/trainset/train_supervised_dataset_splitted42_validation.csv
-m /path/to/your/fine-tuned/FRED-T5-ner-large-the-best
-o /path/to/your/fine-tuned/FRED-T5-ner-large-the-best/best_inference_config.json
-b 128 --random 24 --without_preprocessing
Здесь:
-i
определяет путь к csv-файлу с размеченным датасетом (здесь правильнее использовать именно валидационную подвыборку или любую другую подвыборку, которую модель не использовала для расчёта градиентов и корректировки весов, а не собственно обучающую);-m
- путь к папке с дообученной моделью NER;-o
- путь к json-файлу в папке модели (это важно, файл должен быть сохранён в папке модели и именно под названиемbest_inference_config.json
), в который будут записаны лучшие гиперпараметры инференса;-b
- размер мини-батча;--without_preprocessing
- отключение препроцессинга исходных текстов из столбца name.
При подборе оптимальных гиперпараметров инференса проверяются четыре алгоритма: greedy search, beam search, diverse beam search и contrastive search (подробнее см. документацию Huggingface).
Небольшой спойлер: как правило, лучшей стратегией инференса оказывается beam search с максимальной шириной луча. Но слишком большая ширина луча приводит к чрезмерным вычислительным затратам, поэтому верхний предел для этого гиперпараметра лучше брать не более семи. Тогда ваш файл best_inference_config.json
будет выглядеть вот так:
{
"algorithm": "beam search",
"num_beams": 7,
"diverse": false,
"penalty_alpha": 0.0,
"top_k": 1,
"diversity_penalty": 0.0
}
Поэтому шаг 5 можно пропустить, а вместо этого создать файл best_inference_config.json
в папке модели вручную :-)
Шаг 6. Наконец, необходимо сгенерировать сабмит в виде CSV-файла. Это можно сделать командой, аналогичной той, что описана на шаге 3. Например, так:
python -u submit_with_fredt5_ner.py \
-i /path/to/trainset/train_supervised_dataset_splitted42_test_without_targets.csv \
-m /path/to/your/fine-tuned/FRED-T5-ner-large-the-best \
-o /path/to/trainset/train_supervised_dataset_splitted42_test_predicted.csv \
-b 128 --without_preprocessing
Ключевым моментом в этом примере является использование CSV-файла с неразмеченным датасетом. Поскольку оригинальный /path/to/trainset/train_supervised_dataset_splitted42_test.csv
, как мы помним по итогам шага 1, имеет разметку, то столбцы goods
и brands
из него нужно удалить, а сам файл сохранить под новым именем, добавив к старому названию что-то вроде _without_targets
, как в данном примере.
Шаг 7. Конец - делу венец! Давайте оценим качество нашего сабмита:
python -u evaluate.py \
-t /path/to/trainset/train_supervised_dataset_splitted42_test.csv \
-p /path/to/trainset/train_supervised_dataset_splitted42_test_predicted.csv
В результате вы увидите в терминале отчёт о тестировании, выглядящий примерно так:
F1 for goods is 0.9449498265028604.
F1 for brands is 0.7604098129523451.
Total F1 is 0.8219231508025168.
Контакты
Иван Бондаренко - @Bond_005 - bond005@yandex.ru
Лицензия
Распространяется по лицензии Apache 2.0. Подробнее см. LICENSE
.