readme.md

License Apache 2.0 Python 3.9

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_1goods_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.

Описание

Соревнование по структуризации чеков ОФД

Конвейеры
0 успешных
0 с ошибкой