В предыдущей статье я рассказал про нашу систему поиска похожих заявок. После ее запуска мы стали получать первые отзывы. Какие-то рекомендации аналитикам нравились и были полезны, какие-то — нет. Для того, чтобы двигаться дальше и находить более качественные модели, необходимо было сначала оценить работу текущей модели. Также необходимо было выбрать критерии, по которым две модели можно было бы сравнить между собой.  Под катом я расскажу про:  Идеально было бы собрать явный отзыв от аналитиков: насколько релевантна рекомендация каждого из предложенных инцидентов. Это позволит понять текущее положение и продолжить улучшение системы, основываясь на количественных показателях. Отзывы было решено собирать в предельно простом формате: "Голосовалку" (маленький проект, который принимал GET запросы с параметрами, и складывал информацию в файл) разместили непосредственно в блоке рекомендаций, чтобы аналитики могли оставить свой отзыв немедленно, просто кликнув на одну из ссылок: "хорошо" или "плохо". Дополнительно, для ретроспективного просмотра рекомендации, было сделано очень простое решение:  Так удалось собрать данные по примерно 4000+ парам инцидент-рекомендация. Начальные метрики были "так себе" — доля "хороших" рекомендаций, по оценке коллег, составляла всего порядка 25%. Основные проблемы первой модели: Возможными путями повышения качества рекомендаций были выбраны: Для поиска улучшенной версии модели необходимо определиться с принципом оценки качества результатов модели. Это позволит количественно сравнивать две модели и выбирать лучшую. У нас есть множество m кортежей вида: "Инцидент", "Рекомендованный инцидент", "Оценка рекомендации".  Имея такие данные, можно посчитать: Эти показатели, посчитанные на основе оценок от пользователей, можно рассматривать как "базовые показатели" исходной модели. С ней мы будем сравнивать аналогичные показатели новых версий модели. Возьмем все уникальные "инциденты" из m, и прогоним их через новую модель. В результате получим множество m* кортежей: "Инцидент", "Рекомендованный инцидент", "Расстояние".
Здесь "расстояние" — метрика, определенная в NearestNeighbour. В нашей модели это косинусное расстояние. Значение "0" соответствует полному совпадению векторов.  Дополнив набор рекомендаций m* информацией об истинной оценке v из исходного набора оценок m, получим соответствие дистанции d и истиной оценки v для данной модели. Имея набор (d, v) можно подобрать оптимальный уровень отсечки t, что для в d <= t рекомендация будет "хорошей", а для d > t — "плохой". Подбор t можно осуществить, проводя оптимизацию простейшего бинарного классификатора v = -1 if d>t else 1 по гиперпараметру t, и используя, например, AUC ROC в качестве метрики. Полученное значение t можно использовать для фильтрации рекомендаций.  Конечно, такой подход может все еще пропускать "плохие" рекомендации и отсекать "хорошие". Поэтому, на данном этапе мы всегда показываем "Top 5" рекомендаций, но специально помечаем те, которые считаются "хорошими", с учетом найденного t.
Альтернативный вариант: если найдена хотя бы одна "хорошая" рекомендация, то показывать только "хорошие". Иначе показывать все имеющиеся (тоже — "Top N"). Для обучения моделей используется один и тот-же корпус инцидентов.
Предположим, что если ранее была найдена "хорошая" рекомендация, то новая модель тоже должна найти "хорошую" рекомендацию для того же инцидента. В частности новая модель может найти те же "хорошие" рекомендации, что и старая. Однако, с новой модель мы рассчитываем, что количество "плохих" рекомендаций станет меньше. Тогда, посчитав те же показатели для рекомендаций m* новой модели, их можно сравнить с соответствующими показателями для m. На основании сравнения можно выбрать лучшую модель. Учет "хороших" рекомендаций для множества m* можно вести одним из двух способов: В первом случае "абсолютные" показатели (n_inc_good, n_rec_good) новой модели должны быть больше, чем для базовой модели. Во втором случае — показатели должны приближаться к показателям базовой модели.
Проблема второго способа: если новая модель лучше исходной, и она находит что-то ранее неизвестное — такая рекомендация не будет учтена в расчете. При выборе новой модели хочется, чтобы по сравнению с существующей моделью улучшились показатели:  Для сравнения с исходной моделью, будем использовать отношения этих параметров новой модели и оригинальной. Таким образом, если отношение параметра новой модели и старой больше 1 — новая модель лучше. Для упрощения выбора лучше использовать единый параметр. Возьмем среднее гармоническое отдельных относительных показателей и будем использовать его как единственный композитный критерий качества новой модели.  Для новой модели в финальный вектор, представляющий инцидент, добавим компоненты отвечающие за "область инцидента" (одна из нескольких систем, обслуживаемых нашей командой).
Информацию о подразделении и расположении сотрудника, создавшего инцидент, тоже вынесена в отдельный векторный компонент. Все компоненты имеют свой вес в финальном векторе.  Ожидается, что гиперпараметры модели влияют на целевые показатели модели. В выбранной архитектуре модели гиперпараметрами будем считать: Начальные значения гиперпараметров текстовой векторизации взяты из предыдущей модели. Начальные веса компонентов выбраны исходя и экспертной оценки. Как сравнивать подбирать уровень осечки и сравнивать модели между собой уже определили. Теперь можно перейти к оптимизации через подбор гиперпараметров.   В таблице приведены результаты экспериментов, в которых были достигнуты интересные результаты — top 5 лучших и худших значений по контролируемым показателям.  Ячейки с показателями в таблице помечены как: Лучший композитный показатель получился у модели с параметрами: Модель с этими параметрами показала улучшение композитного показателя по сравнению с исходной моделью 24% По результатам оптимизации: Использование триграмм ( ngram_range = (1,3) ), похоже, не оправдано. Они раздувают словарь и слабо повышают точность по сравнению с биграммами. Интересное поведение при построение словаря только из биграмм (ngram_range = (2,2)): "точность" рекомендаций возрастает, а количество найденных рекомендаций — падает. Прямо как баланс precision/recall в классификаторах. Аналогичное поведение наблюдается в подборе уровня отсечки t — для биграмм характерен более узкий "конус" отсечки и лучшее разделение "хороших" и "плохих" рекомендаций. Ненулевой параметр min_df, наряду с биграммами, повышает точность рекомендаций. Они начинают быть основанными на терминах, которые встречаются как минимум несколько раз. При росте параметра словарь начинает быстро сокращаться. Для маленьких выборок, как в нашем случае, наверное, понятнее будет оперировать количеством документов (целое значение min_df), чем долей документов (дробное значение min_df), содержащих термин. Хорошие результаты получаются, когда признак инцидента, отвечающий за "область" включен в финальный вектор с весом равным или близким к текстовому компоненту. Низкие значения приводят к возрастанию доли "плохих" рекомендаций за счет нахождения похожих слов в документах из других областей. А вот признаки расположения заказчика так хорошо на результаты рекомендаций в нашем случае не влияют. Появились некоторые новые идеи: Бизнес-анализ, управление проектами, разработка