Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Всем привет! В этом году компания Sibur Digital вновь проводила крупный (по сравнению с другими российскими) чемпионат по анализу данных. Мы с другом в нём участвовали и хотели бы поделиться с читателями Хабра своим решением и опытом, полученным от участия. Конечно вряд ли мы америку откроем этой статьей, но какой-нибудь новичок в соревнованиях по АД точно сможет почерпнуть для себя что-то полезное.
Кто мы такие?
Мы студенты которые очень сильно увлеклись темой DS и ML. Впервые мы узнали об этой сфере на конференции AI Journey, проходившей в нашем вузе. С того момента прошли не один, и не два, и не три курса (от Омского Государственного Технического университета до Andrew NG) и теперь постоянно участвуем в хакатонах и соревнованиях(в некоторых даже заняли призовые места), параллельно ищем стажировку.
О задаче
Мы взялись за вторую задачу соревнования - "сопоставление названий".
Суть следующая : Сибур работает с огромным количеством новых компаний, и для оптимизации рабочего процесса им было бы полезно понимать, что они работают с уже ранее знакомым холдингом. К примеру "Сибур Нефтехим" и "СИБУР ИТ" из одного холдинга, и при работе с одной из этих компаний было бы полезно использовать накопленную ранее информацию о холдинге СИБУР.
Перефразируем задачу на язык DS. Даны два названия, по ним мы должны определить - принадлежат ли компании одному холдингу или нет.
name_1 | name_2 | is_duplicate |
Japan Synthetic Rubber Co | Jsr Bst Elastomer | 1 |
JSR Corporation | BST ELASTOMERS CO. | 0 |
Примерно так выглядел датасет.
Предобработка данных
В первую очередь мы привели данные к латинице с помощью волшебного модуля unidecode. Далее привели к нижнему регистру, убрали всякий мусор в виде ненужных знаков препинания, двойных пробелов и т.д.
from unidecode import unidecode
import re
def preprocess(text: str):
text = unidecode(text)
text = text.lower()
text = re.sub(r'[\.,]+', '', text)
text = re.sub(r"\(.*\)", ' ', text)
text = re.sub(r"[^\w\s]", ' ', text)
text = re.sub(r'\b\w\b', ' ', text)
text = ' '.join(text.split())
return text
После взялись за удаление ненужных слов. Первыми в список мусора полетели названия стран в названиях, для этого взяли модуль pycountry(который любезно подсказали организаторы в бейзлайн решении) и немного дополнили этот список стран их сокращениями, аббревиатурами которые сами нашли в выборке.
Со стоп словами было посложнее. Конечно мы сразу удалили некоторое количество самых частотных слов, но понижая порог количества вхождений для удаления, мы заметили, что большинство названий в выборке просто остается пустым. При этом в "уцелевших названиях" остаются такие слова как "shanghai", и, очевидно они никак не подчеркивают уникальность компании, а лишь увеличивают путаницу среди шанхайских компаний. В результате пришлось из наиболее частотных слов самим выбирать бессмысленные и удалять их.
Поиск фичей
Взявшись за соревнование мы сразу решили, что не будем заострять свое внимание на сложных ансамблях моделей и огромных нейронных сетях, а постараемся подойти креативнее и больше работать с данными - искать закономерности и на их основе придумывать фичи (как минимум, второй подход казался нам поинтереснее).
В целом, можно обобщить : мы пытались использовать придуманные фичи для оценивания "похожести" строк, и уже на них обучать модель.
Первый прорыв нам дал признак "сколько букв сначала совпадает в двух словах подряд". Обучив логистическую регрессию в начале соревнования только на одном этом признаке мы получили результат чуть более 0.3 и перепрыгнули большинство в начале соревнования. В дальнейшем мы использовали не просто число совпадающих букв, а число совпадающих букв делим на суммарное количество букв обоих названий.
Вторым полезным признаком оказался коэффициент Жаккара взятый по словам из двух названий. Т.е мы разделили число пересекающихся слов в названиях на количество уникальных слов в двух названиях.
И в конце мы добавили бинарный признак, который непонятно почему пришел нам в голову последним, хотя на первый взгляд он самый очевидный. Содержит ли одно название все слова другого.
Остальные признаки, которые не оказались столь удачными, приведены в списке ниже:
количество совпадающих первых гласных, согласных
количество совпадающих первых буквы сокращения (аббревиатуры)
стандартные метрики: левенштейн, яро винклер, фузи вузи
tfidf - при подсчёте жаккара (или косинусного расстояния с нграммами)
сортировать уникальные слова в имени и считать наши метрики
процент пересекающихся ngram
количество совпадающих первых букв каждого слова (брать максимум)
количество букв первого, количество букв второго
количество слов первого, количество второго
Модель и блендинг решений
Как упоминалось выше, мы не собирались строить свое решение целиком и полностью на сильной модели, поэтому в качестве классификатора использовали XGBoost, который показал себя получше других простых моделей. И мы получили результат ~ 0.59 на лидерборде.
Далее мы понимали, что было бы неплохо обменяться с кем-то идеями и объединить решения. Мы познакомились с двумя другими участниками(Алехандро, Дмитрий, привет!), и заблендили наши решения, получив скор 0.69 на лидерборде. Так получилось, что все три наших решения имели разные подходы к задаче, поэтому их объединение и улучшило результат.
Выводы
В любой истории главное - выводы, поэтому, уверен, это будет самая интересная часть.
Начать стоит с того, что помогло нам достичь результата. В первую очередь это то, что мы не пошли простым путем fit_predict, а постарались посмотреть на задачу с разных сторон. Перепробовали огромное количество различных методов(начиная от метрики Левенштейна и подсчитывания косинусного расстояния и заканчивая сиамскими нейросетями). Провели анализ ошибок модели, который помог выстроить правильную предобработку и обновить словарь стоп-слов.
Что можно было доработать?
Можно было учитывать семантику слов или выдавать словам веса: если слово в двух названиях совпало и оно полезно (относится к названию компании) - имеет вес, мы автоматически считаем что оно настолько же вредно в "разнице" слов; использование как можно больше внешних данных с названием компаний и т. д. Также не забывать анализировать наблюдения, на которых ошибается модель (False Positive, False Negative), и на основе этого конструировать новые признаки.
P.S.
Весь код лежит здесь
Если хотите связаться с нами : matnik2001@gmail.com , domonion@list.ru