Автоматически Назначайте Задачи В Jira С Помощью Ml

Привет, Хабр! Меня зовут Саша и я бэкенд-разработчик.

В свободное время я изучаю ML и развлекаюсь с данными hh.ru. Эта статья о том, как мы использовали машинное обучение для автоматизации рутинного процесса постановки задач тестировщикам.

На hh.ru есть внутренний сервис, для которого в Jira создаются задачи (внутри компании они называются HHS), если у кого-то что-то не работает или работает некорректно.

Эти задачи затем вручную обрабатываются руководителем команды QA Алексеем и распределяются по команде, в зону ответственности которой входит неисправность.

Лёша знает, что скучные задачи должны выполнять роботы.

Поэтому он обратился ко мне за помощью по части ML.

Автоматически назначайте задачи в Jira с помощью ML

На графике ниже показано количество HHS в месяц.

Мы растем и количество задач растёт. Задачи в основном создаются в рабочее время, несколько раз в день, и вам приходится постоянно на это отвлекаться.



Автоматически назначайте задачи в Jira с помощью ML

Итак, необходимо на основе исторических данных определить команду разработчиков, к которой принадлежит HHS. Это проблема классификации нескольких классов.



Данные

В задачах машинного обучения самое главное — качественные данные.

От них зависит исход решения проблемы.

Поэтому любая задача машинного обучения должна начинаться с изучения данных.

С начала 2015 года у нас накопилось около 7000 задач, которые содержат следующую полезную информацию:

  • Резюме – название, краткое описание
  • Описание - полное описание проблемы
  • Ярлыки — список тегов, связанных с проблемой.

  • Репортер — имя создателя HHS. Эта функция полезна, поскольку люди работают с ограниченным набором функций.

  • Создано — дата создания
  • Цессионарий – тот, кому поручена задача.

    На основе этой характеристики будет сгенерирована целевая переменная.

Начнем с целевой переменной.

Во-первых, у каждой команды есть зоны ответственности.

Иногда они пересекаются, иногда одна команда может пересекаться в разработке с другой.

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

Но нам нужно прогнозировать не конкретного человека, а команду.

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

Но есть ряд проблем с определением команды по человеку:

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

    Поэтому надо выкидывать задачи, где исполнитель не из технического отдела

  • иногда команды перестают существовать.

    Они также удаляются из обучающего набора.

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

    К счастью, нам удалось получить историю изменений в составе всех команд. Зная дату создания HHS и правопреемника, можно узнать, какая команда работала над задачей в определенное время.

После фильтрации нерелевантных данных обучающая выборка была сокращена до 4900 задач.

Давайте посмотрим на распределение задач между командами:

Автоматически назначайте задачи в Jira с помощью ML

Задания необходимо распределить между 22 командами.



Знаки:

Сводка и Описание — это текстовые поля.

Для начала следует очистить их от ненужных символов.

Для некоторых задач имеет смысл оставлять в строках символы, несущие информацию, например + и #, чтобы различать C++ и C#, но в данном случае я решил оставить только буквы и цифры, так как не нашел где другие символы могут быть полезны.

Слова необходимо лемматизировать.

Лемматизация – это приведение слова к лемме, его нормальной (словарной) форме.

Например, кошки → кот. Я также пробовал стемминг, но с лемматизацией качество было немного выше.

Стемминг – это процесс нахождения основы слова.

Эта основа находится за счет алгоритма (в разных реализациях они разные), например кошки → кошки.

Смысл первого и второго заключается в сравнении одних и тех же слов друг с другом в разных формах.

Я использовал оболочку Python для Яндекс Мистем .

Далее текст следует очистить от стоп-слов, не несущих полезной нагрузки.

Например, «был», «мне», «еще».

Я обычно беру безопасные слова из НЛТК .

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

Например, есть «поиск».

Если разбить его на составляющие по 3 символа, то получатся слова «пои», «ойс», «иск».

Это помогает получить дополнительные связи.

Допустим, есть еще слово «поиск».

Лемматизация не сводит «поиск» и «поиск» к общей форме, но разбиение на 3 символа выделит общую часть – «поиск».

Я сделал два токенайзера.

Tokenizer — это метод, входные данные которого — текст, а выходные данные — список токенов — компонентов текста.

Первый выделяет лемматизированные слова и числа.

Второй выбирает только лемматизированные слова, которые разбиты на 3 символа, т.е.

на выходе получается список трехсимвольных токенов.

Токенайзеры используются в TfidfVectorizer -e, который служит для преобразования текстовых (и не только) данных в векторное представление на основе tf-idf .

Входные данные представляют собой список строк, а выходные данные — матрицу M на N, где M — количество строк, а N — количество функций.

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

Благодаря параметру ngram_range TfidfVectorizer я добавил больше биграммы и триграммы .

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

Эmbedding — это векторное представление слова.

Для каждого текста я усреднил вложения всех его слов.

Но роста это не дало, поэтому я отказался от этих знаков.

Для этикеток использовалось ГрафВекторизатор .

На вход он получает строки с тегами, а на выходе представляет собой матрицу, где строки соответствуют задачам, а столбцы — тегам.

В каждой ячейке указано количество вхождений тега в задачу.

В моем случае это 1 или 0. Подходит для репортера МеткаБинаризатор .

Он бинаризирует признаки по типу «один против всех».

Для каждой задачи может быть только один создатель.

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

Получается, что в каждой строке в столбце, соответствующем создателю, стоит «1», а в остальных — «0».

Для параметра Создано рассчитывается разница в днях между датой создания задачи и текущей датой.

В результате были получены следующие признаки:

  • tf-idf для сводки по словам и числам (4855, 4593)
  • tf-idf для сводки по трехсимвольному разбиению (4855, 15518)
  • tf-idf для описания в словах и цифрах (4855, 33297)
  • tf-idf для описания трехсимвольного разделения (4855, 75359)
  • количество появлений меток (4855, 505)
  • двоичные функции для Reporter (4855, 205)
  • время жизни задачи (4855, 1)
Все эти особенности объединены в одну большую матрицу (4855, 129478), на которой и будет проводиться обучение.

Отдельно стоит отметить названия знаков.

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

TfidfVectorizer, CountVectorizer, LabelBinarizer имеют методы get_feature_names, которые возвращают список функций, порядок которых соответствует столбцам матриц данных.



Выбор модели для прогнозирования

Очень часто дает хорошие результаты XGBoost .

Я начал с него.

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

В этом случае велика вероятность перетренированности XGBoost. Результат оказался не очень хорошим.

Большие габариты хорошо усваиваются Логистическая регрессия .

Она показала более высокое качество.

Я также попробовал в качестве упражнения построить модель на нейронной сети в Tensorflow, используя Вот этот отличный урок, но он оказался хуже логистической регрессии.



Выбор гиперпараметров

Я также поигрался с гиперпараметрами XGBoost и Tensorflow, но оставляю это за рамками статьи, потому что.

результат логистической регрессии оказался не лучшим.

На последнем я крутил все ручки, какие только мог.

Все параметры в конечном итоге остались по умолчанию, за исключением двух: Solver='liblinear' и C=3.0. Еще одним параметром, который может повлиять на результат, является размер обучающей выборки.

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

.

Для этого я придумал эвристику: чем старше данные, тем меньший вклад они должны вносить в обучение модели.

В зависимости от возраста данные умножаются на определенный коэффициент, который берется из функции.

Я сгенерировал несколько функций для ослабления данных и использовал ту, которая дала наибольший прирост в тестировании.



Автоматически назначайте задачи в Jira с помощью ML

Благодаря этому качество классификации выросло на 3%.



Контроль качества

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

Поэтому решено было брать 100% комплектность.

А для измерения качества была выбрана метрика точности — доля правильных ответов, которая для итоговой модели составила 76%.

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

часть.

Затем результат усредняется.

Но в моем случае такой подход не сработал, потому что.

порядок данных меняется, а как уже стало известно, качество зависит от свежести данных.

Поэтому я все время тренировался на старых, а валидировал на свежих.

Давайте посмотрим, какие команды чаще всего путает алгоритм:

Автоматически назначайте задачи в Jira с помощью ML

Маркетинг и Пандора на первом месте.

Это неудивительно, ведь вторая команда выросла из первой и взяла на себя ответственность за многие функции.

Если мы посмотрим на остальные команды, то также увидим причины, связанные с внутренними процессами компании.

Для сравнения хотелось бы посмотреть на рандомные модели.

Если назначить ответственного случайным образом, то качество будет около 5%, а если для самого обычного класса, то - 29%.



Наиболее значимые признаки

LogisticReгрессия для каждого класса возвращает коэффициенты признаков.

Чем выше значение, тем больший вклад эта функция внесла в этот класс.

Под спойлером краткое изложение основных функций.

Префиксы указывают, откуда были получены характеристики:

  • sum — tf-idf для сводки в словах и цифрах.

  • sum2 - tf-idf для сводки по трехсимвольному разбиению
  • desc — tf-idf для описания в словах и цифрах
  • desc2 — tf-idf для разделения описания на три символа.

  • lab — Поле меток
  • Rep — Поле репортера
Знаки Команда A: sum_site(1.28), lab_responses_and_invitations(1.37), lab_refusal_to_employer(1.07), lab_layout(1.03), sum_work(1.04), sum_work_site(1.59), lab_hhs(1.19), lab_feedback(1.06), Rep_name_0(1.14), lab_brand (1.16), sum_window(1.13), sum_break(1.04), Rep_name_1(1.22), lab_ответы_заявитель(1.0), lab_site(0.92) API: lab_deleting_account(1.12), sum_comment_resume(0.94), Rep_name_2(0.9), Rep_name_3(0.83), Rep_name_4(0.89), Rep_name_5(0.91), lab_vacancy_managers(0.87), lab_comments_on_resume(1.85), lab_api(0.86), sum_deleted _ аккаунт( 0,86), sum_view(0,91), desc_comment(1,02), Rep_name_6(0,85), desc_summary(0,86), sum_api(1,01) Android: sum_android(1.77), lab_ios(1.66), sum_app(2.9), sum_hr_mobile(1.4), lab_android(3.55), sum_hr(1.36), lab_mobile_app(3.33), sum_mobile(1.4), Rep_name_2(1.34), sum2_ril(1.27) ), sum_app_android(1.28), sum2_pri_re_il(1.19), sum2_pri_ril(1.27), sum2_or_false(1.19), sum2_or_false_auger(1.19) Выставление счетов: Rep_name_7(3.88), desc_account(3.23), Rep_name_8(3.15), lab_billing_wtf(2.46), Rep_name_9(4.51), Rep_name_10(2.88), sum_account(3.16), lab_billing(2.41), Rep_name_11(2.27), lab_billing_support(2.36) ) ), sum_service(2.33), lab_pay_for_services(1.92), sum_act(2.26), Rep_name_12(1.92), Rep_name_13(2.4) Brandy: lab_talent_assessment (2.17), rep_name_14 (1,87), Rep_name_15 (3,36), Lab_clickme (1,72), Rep_name_16 (1.44), rep_name_17 (1.63), rep_name_18 (1.29), sum_page (1,24), Sumber_4. (1.39 ), sum_constructor(1.59), lab_brand.pages(1.33), sum_description(1.23), sum_description_company(1.17), lab_article(1.15) Clickme: desc_act(0.73), sum_adv_hh(0.65), sum_adv_hh_ru(0.65), sum_hh(0.77), lab_hhs(1.27), lab_bs(1.91), Rep_name_19(1.17), Rep_name_20(1.29), Rep_name_21(1.9), Rep_name_8(1.16) ) ), sum_ad(0,67), sum_placement(0,65), sum_adv(0,65), sum_hh_ua(0,64), sum_click_31(0,64) Маркетинг: lab_region(0.9), lab_slows_site(1.23), sum_mailing(1.32), lab_vacancy_managers(0.93), sum_calendar(0.93), Rep_name_22(1.33), lab_polls(1.25), Rep_name_6(1.53), lab_production_calendar(1.55), Rep_name_23(0.8) 6 ), sum_yandex(1.26), sum_distribution_vacancy(0.85), sum_distribution(0.85), sum_category(0.85), sum_error_transition(0.83) Меркурий: lab_services(1.76), sum_captcha(2.02), lab_applicant_services(1.89), lab_lawyers(2.1), lab_authorization_employer(1.68), lab_careerguide(2.53), lab_ready_resume(2.21), Rep_name_24(1.77), Rep_name_25(1.59), _ка( 1.56 ), sum_user(1.57), Rep_name_26(1.43), lab_moderation_vacancies(1.58), desc_password(1.39), Rep_name_27(1.36) Mobile_site: sum_mobile_version(1.32), sum_site_version(1.26), lab_application(1.51), lab_statistics(1.32), sum_mobile_version_site(1.25), lab_mobile_version(5.1), sum_version(1.41), Rep_name_28(1.24), sum_page_statistics(1.05), lab_no(1.05) ) ), lab_jtb(1.07), Rep_name_16(1.12), Rep_name_29(1.05), sum_site(0.95), Rep_name_30(0.92) TMS: Rep_name_31(1.39), lab_talantix(4.28), Rep_name_32(1.55), Rep_name_33(2.59), sum_vacancy_talantix(0.74), lab_search(0.57), lab_search(0.63), Rep_name_34(0.64), lab_calendar(0.56), sum_imported(0.66) ), lab_tms(0,74), sum_response_hh(0,57), lab_mailing(0,64), sum_talantix(0,6), sum2_mpo(0,56) Talantix: sum_system(0.86), Rep_name_16(1.37), sum_talantix(1.16), lab_mail(0.94), lab_xor(0.8), lab_talantix(3.19), Rep_name_35(1.07), Rep_name_18(1.33), lab_personal_data(0.79), Rep_name_34(1.08) ) ), sum_talentix(0,89), sum_occur(0,78), lab_mail(0,77), sum_response_become_view(0,73), Rep_name_6(0,72) Веб-службы: sum_vacancy(1.36), desc_template(1.32), sum_archive(1.3), lab_letter_templates(1.39), sum_phone_number(1.44), Rep_name_36(1.28), lab_lawyers(2.1), lab_invitation(1.27), lab_invitation_for_vacancy(1.26), desc_folder(1 .

22), lab_favorite_resume(1.2), lab_key_skills(1.22), sum_find(1.18), sum_phone(1.16), sum_folder(1.17).

iOS: sum_app(1.41), desc_app(1.13), lab_andriod(1.73), Rep_name_37(1.05), sum_ios(1.14), lab_mobile_app(1.88), lab_ios(4.55), Rep_name_6(1.41), Rep_name_38(1.35), sum_mobile_app(1.28) ), sum_mobile(0,98), Rep_name_39(0,74), sum_resume_hide(0,88), Rep_name_40(0,81), lab_duplicate_vacancies(0,76) Архитектура: sum_statistics_response(1.1), Rep_name_41(1.4), lab_graph_views_and_responses_vacancy(1.04), lab_creation_vacancy(1.16), lab_quotas(1.0), sum_special Offer(1.02), Rep_name_42(1.33), Rep_name_24(1.39), lab_resume(1.5 2), lab_backoffice (0,99), Rep_name_43 (1,09), sum_hang (0,83), sum_statistics (0,83), lab_responses_employer (0,76), sum_500ka (0,74) Банк зарплаты: lab_500(1.18), lab_authorization(0.79), sum_500(1.04), Rep_name_44(0.85), sum_500_site(1.03), lab_site(1.08), lab_resume_visibility(1.54), lab_price list(1.26), lab_resume_visibility_settings(0.87), sum_error (0,79), lab_deferred_orders (1,33), Rep_name_43 (0,74), sum_ie_11 (0,69), sum_500_error (0,66), sum2_say_ite (0,65) Мобильные продукты: lab_mobile_application(1.69), lab_responses(1.65), sum_hr_mobile(0.81), lab_applicant(0.88), lab_employer(0.84), sum_mobile(0.81), Rep_name_45(1.2), desc_d0(0.87), Rep_name_46(1.37), sum_hr( 0,79), sum_incorrect_work_search(0,61), desc_application(0,71), Rep_name_47(0,69), Rep_name_28(0,61), sum_work_search(0,59) Pandora: sum_arrive(2.68), desc_arrive(1.72), lab_sms(1.59), sum_letter(2.75), sum_notification_response(1.38), sum_mailing(2.96), lab_password Recovery(1.52), lab_mailings(1.91), lab_email(2.0), sum_check( 1.31) ), lab_mailing(1.72), lab_mailing(3.37), desc_mailing(1.69), desc_mail(1.47), Rep_name_6(1.32) Перцы: lab_saving_resume(1.43), sum_resume(2.02), sum_funnel(1.57), sum_funnel_vacancy(1.66), desc_resume(1.19), lab_resume(1.39), sum_code(1.2), lab_applicant(1.34), sum_index(1.47), sum_index_politeness(1.47) ) ), lab_create_resume(1.28), Rep_name_45(1.82), sum_politeness(1.47), sum_save_resume(1.18), lab_politeness_index(1.13) Поиск-1: sum2_poi_ois_search(1.86), sum_search(3.59), lab_questions_on_search(3.86), sum2_poi(1.86), desc_search(2.49), lab_suitable_resumes(2.2), lab_search(2.32), lab_indexing_problems(4.34), sum2_poi_ois(1.86), ), sum_autosearch (1.62), sum_synonym(1.71), sum_sample(1.62), sum2_search(1.58), sum2_ois_claim(1.57), lab_auto-update_summary(1.57) Поиск-2: Rep_name_48(1.13), desc_d1(1.1), lab_premium_in_search(1.02), lab_views_vacancies(1.4), sum_search(1.4), desc_d0(1.2), lab_show_contacts(1.17), Rep_name_49(1.12), lab_13(1.09), Rep_name_50 (1.05), lab_search_vacancies(1.62), lab_responses_and_invitations(1.61), sum_response(1.09), lab_selected_resumes(1.37), lab_filter_in_responses(1.08) Суперпродукты: lab_contact_information(1.78), desc_address(1.46), Rep_name_46(1.84), sum_address(1.74), lab_vacancy_posting(1.68), lab_selected_resumes(1.45), lab_responses_employer(1.29), sum_right(1.23), sum_vacancy_posting(1.21), de sc_quotas а (1.19), sum_error_placement(1.33), Rep_name_42(1.32), sum_quota(1.14), desc_address_office(1.14), Rep_name_51(1.09) Знаки примерно отражают то, что делают команды.



Использование модели

На этом построение модели завершено и на ее основе можно строить программу.



Автоматически назначайте задачи в Jira с помощью ML

Программа состоит из двух скриптов Python. Первый строит модель, а второй делает прогнозы.

  1. Jira предоставляет API, с помощью которого вы можете загружать уже решенные задачи (HHS).

    Раз в день скрипт запускается и скачивает их.

  2. Загруженные данные преобразуются в функции.

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

    А затем модель второй раз обучается на всех данных.

    Весь процесс занимает около 10 минут.

  3. Обученная модель сохраняется на жесткий диск.

    Я использовал утилиту укроп для сериализации объектов.

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

    Это сделано для того, чтобы знаки нового HHS располагались на одном месте.

  4. С помощью того же укропа модель загружается в скрипт прогнозирования, который запускается раз в 5 минут.
  5. Перейдем к Jira за новым HHS.
  6. Получаем атрибуты и передаем их модели, которая для каждого HHS вернет имя класса — имя команды.

  7. Для команды находим ответственного и ставим ему задачу через Jira API. Это может быть тестер; если в команде нет тестировщика, то это может быть тимлид.
Чтобы программа была удобна в развертывании и имела те же версии библиотек, что и при разработке, скрипты упаковываются в Docker-контейнер.

В результате мы автоматизировали рутинный процесс.

Точность в 76% не слишком высока, но в данном случае промахи не критичны.

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

Все работает автоматически! Ура! Теги: #Машинное обучение #python #программирование #искусственный интеллект #ml #машинное обучение #hh.ru #классификация #Jira #hh #логистическая регрессия

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.