Масштабное использование НЛП, чтобы помочь людям найти подходящую работу. Часть 3: определение модели и обучение

Это последняя из серии статей о том, как мы в Jobtome ежедневно классифицируем миллионы объявлений о вакансиях, чтобы обогатить наш список и помочь людям найти подходящую работу. Вы можете прочитать первую часть здесь и вторую часть здесь.

В предыдущем посте мы описали потребность в Jobtome в создании модели для классификации объявлений о вакансиях по категориям вакансий, т. е. возврата — с учетом названия должности в качестве входных данных — наиболее вероятной категории среди десятков классов. Мы также обсудили шаги по созданию высококачественного обучающего набора данных.

В этой статье подробно описана наша модель классификации — технически это многоклассовая классификация Short Text в среде обработки естественного языка — и некоторые варианты, которые мы сделали на этапе обучения.

Из-за нашего бизнеса некоторые строгие требования сильно повлияли на этап создания модели: необходимость молниеносных прогнозов, возможность обработки многоязычных входных данных и неточное (и, следовательно, расплывчатое) определение категорий должностей. Кроме того, нам также пришлось учитывать некоторые особенности входных обучающих данных, т. е. небольшой и зашумленный набор данных.

Небольшой набор данных для обучения
Наш набор данных для обучения охватывал лишь небольшой процент всех возможных вакансий, доступных на рынке. Следовательно, модель можно попросить предсказать название должности, состоящее из слов, которые никогда не встречались в процессе обучения. Более того, некоторые языки были недостаточно отобраны, а другие вообще отсутствовали. Представьте, что вы обучаете модель только предложениям о работе Разработчик программного обеспечения, прежде чем просить предсказать, кто такой Программист кода, или (что еще хуже) показываете модель только с учетом вакансий на английском языке и притворяетесь сделать хороший прогноз на работу Księgowy / Księgowa (бухгалтер на польском языке).

Зашумленный набор обучающих данных
У нас не было первоклассного помеченного набора данных. Мы настраиваем свою с нуля, используя множество источников данных, в том числе малонадежных. Несмотря на то, что мы применили процесс очистки для улучшения качества, у нас все еще был зашумленный обучающий набор данных, включая подмножество данных с неправильными метками.

Модель

Мы протестировали несколько моделей классификации текста, от классических алгоритмов машинного обучения до более продвинутых архитектур нейронных сетей. Однако все реализованные модели имели общую структуру:

  • предварительная обработка —от ввода исходного текста до очищенного текста.
  • встраивание —от очищенного текста до вектора чисел
  • классификатор —от вектора к классу атрибуции

При создании обучающего набора данных мы разработали простую модель для классификации названий должностей на английском языке, не уделяя слишком много внимания точности и скорости. Следуя приведенной выше схеме, модель состояла из трех модулей. Первым был классический этап предварительной обработки текста с удалением стоп-слов и применением строчных букв, применяемый для ввода названий должностей с помощью стандартных библиотек Python, таких как spaCy и NLTK. Второй шаг преобразовал чистый текст во встраивание предложений, агрегируя (с суммированием) каждое встраивание слова из предварительно обученного Word2Vec, вызванного с помощью библиотеки Gensim. Наконец, центральной частью модуля классификации был классификатор случайного леса, обученный с использованием промежуточного обучающего набора данных и реализованный с помощью библиотеки scikit-learn. Эта простая модель была ориентирована на удаление непригодных данных из обучающего набора данных без особых усилий по настройке и оптимизации модели.

Напротив, окончательная модель должна была соответствовать всем вышеперечисленным требованиям. Мы решили реализовать все три шага с помощью фреймворка Keras из-за большого сообщества и огромного количества предварительно обученных моделей, доступных на TensorFlow Hub.
Вот более подробное объяснение каждого один шаг окончательной модели.

Предварительная обработка
Мы приняли этап предварительной обработки, состоящий из стандартных приемов: применение строчных букв к названиям должностей, пунктуация и удаление отдельных символов, а также удаление определенных шаблонов, таких как HTML-теги. Мы также отказались от некоторых распространенных шаблонов, учитывая их низкую информативность. Например, мы отбросили такие предложения, как Немедленный прием на работу, Требуется опыт работы, Неполный/полный рабочий день, а также такие маркеры, как ожидаемая зарплата. Мы также решили не удалять наиболее распространенные стоп-слова (союзы, предлоги и т. д.), потому что заметили, что этот шаг не улучшил производительность модели.
Мы написали одну пользовательскую функцию в Keras отвечает за применение всех шагов очистки и преобразовал его в лямбда-слой нашей модели.

Встраивание
В большинстве случаев встраивание текста, предварительно обученное на большом корпусе, значительно повышает точность модели в задаче классификации текста:

использование предварительно обученных вложений может помочь нашей модели добиться лучшей производительности даже на невидимых данных, компенсируя уменьшенный размер нашего набора обучающих данных.

В TensorFlow Hub мы выбрали предварительно обученный кодировщик, который мог бы удовлетворить наше многоязычное требование: Многоязычный кодировщик предложений. Кодировщик принимает текст переменной длины (в нашем случае название должности) и выдает 512-мерный вектор, а именно встраивание предложений.
Существенной особенностью кодировщика является многоязычность: кодировщик обучен так, с похожими значениями будут иметь близкие вложения в разных языках. Эта возможность помогла нашей модели обобщаться даже на языки, не включенные в обучающие данные.
В качестве дополнительного преимущества язык входного текста не был обязательным параметром: это позволило нам пропустить трудоемкий этап определения языка.

Встраиваемый кодировщик доступен в двух архитектурах: сверточные нейронные сети и преобразователи. Мы приняли первый из соображений скорости: архитектура CNN намного легче и быстрее, чем архитектура Transformers. Кроме того, названия должностей представляют собой простые предложения, состоящие не более чем из 10–12 слов, часто без глагола (например, Полный рабочий день Ученый по данным). С таким вводом мы решили решить проблему с помощью подхода Bag-of-words, когда последовательность слов не несет дополнительной информации. Возможность обрабатывать последовательности во фразах — например, у Transformers — не была добавленной стоимостью в нашем конкретном случае использования.
Более подробное описание модели кодировщика можно найти в ссылке.

Классификатор
Выходные данные предыдущего шага стали входными данными модели классификатора. Затем мы добавили в архитектуру CNN три скрытых плотных слоя, состоящих из 256, 128 и 64 нейронов, все с функцией активации Relu. Четвертым и последним плотным слоем был Softmax, с одним нейроном на класс и ответственным за вывод прогнозируемой категории работы с соответствующей оценкой. Слой Softmax дал наивысшую оценку наиболее вероятной категории работы и более низкую оценку другим классам.

Собираем все вместе
Три шага, описанные выше, представляют суть нашей модели классификации текста. Основной код модели Keras Sequential показан ниже:

# Input Layer
tf.keras.Input(shape=(), dtype="string")
# Lambda Layer
tf.keras.layers.Lambda(lambda title: preprocess(title))
# Sentence Embedding Layer
hub.KerasLayer(encoder_url, trainable=False)
# Classifier Layers
tf.keras.layers.Dense(256, activation="relu")
tf.keras.layers.Dropout(0.1)
tf.keras.layers.Dense(128, activation="relu")
tf.keras.layers.Dropout(0.1)
tf.keras.layers.Dense(64, activation="relu")
tf.keras.layers.Dropout(0.1)
 
# Softmax Output Layer (n classes = 32)
tf.keras.layers.Dense(32, activation="softmax")

Стоит отметить, что на этапе обучения мы применяли слои предварительной обработки и встраивания только один раз к обучающему набору данных, отбрасывая оба слоя из модели. Таким образом, мы избегали предварительной обработки данных в каждой эпохе, что значительно сократило время обучения.
Наоборот, три слоя отсева были введены только на этапе обучения, чтобы уменьшить переоснащение.

Наконец, мы разделили набор данных на обучающий и проверочный набор для оценки метрики точности и остановили процесс обучения, когда была достигнута сходимость. Сравнение точности, полученной на этапе обучения в наборе данных проверки, 70%, и производительности, полученной при прогнозировании набора данных оценки в нашей предыдущей статье, >80%, подтвердило шум в наших данных, о котором сообщалось в начале. Гиперпараметры, такие как количество плотных слоев и нейронов в каждом плотном слое, процент отсева, оптимизатор градиентного спуска и размер пакета, были оценены путем обучения нескольких моделей и оценки точности на проверочном наборе.

Следует отметить, что мы решили оставить фиксированными веса энкодера на этапе обучения.

Обычно рекомендуется корректировать веса встраивания, чтобы сделать кодировщик более конкретным для предметной области. Мотивация нашего выбора заключалась в уменьшении размера нашего обучающего набора данных и его зашумленных данных. Кроме того, предварительно обученный кодировщик встраивания был критически важен и позволил нашей модели лучше обобщать невидимый текст. Следовательно, мы хотели избежать тонкой настройки наших данных.

Игрушечная модель, показывающая реализацию нашей модели Keras и обучение на подвыборке нашего обучающего набора данных, доступна здесь.

Авторы статьи: Федерико Гульельмин, Сильвио Паванетто, Стефано Рота и Паоло Сантори.