{"id":3056,"title":"\u0414\u0430 \u2014\u00a0\u0430\u043c\u0431\u0438\u0446\u0438\u044f\u043c, \u043d\u0435\u0442 \u2014\u00a0\u0438\u0435\u0440\u0430\u0440\u0445\u0438\u0438: \u00ab\u0420\u043e\u0441\u0431\u0430\u043d\u043a\u00bb \u0438\u0449\u0435\u0442 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u043e\u0432","url":"\/redirect?component=advertising&id=3056&url=https:\/\/vc.ru\/special\/rosbank&hash=48d484f797e172533ef95dbe2624084158bc983bdeb2b3d0714e66ba982ec284","isPaidAndBannersEnabled":false}
Machine learning
NTA

Как Machine Learning помогает при аудите качества клиентского сервиса

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

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

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

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

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

def cl_text(text): c = text.lower() c = re.sub(r'crm[^\n]+', '', c) c = re.sub(r'документ:\s*\d{2}\s?\d{2}\s?\d{6}\s*', '', c) c = re.sub(r'дул:\s*\d{2}\s?\d{2}\s?\d{6}\s*', '', c) c = re.sub(r'дата рождения( застрахованного лица)?:\s*\d{2}\.?\d{2}\.?\d{4}\s*', '', c) c = re.sub(r'дата начала действия:\s*\d{2}\.?\d{2}\.?\d{4}\s*', '', c) c = re.sub(r'дата окончания действия:\s*\d{2}\.?\d{2}\.?\d{4}\s*', '', c) c = re.sub(r'дата выдачи:\s*\d{2}\.?\d{2}\.?\d{4}\s*', '', c) c = re.sub(r'дата выдачи:[\S\W]\w*', '', c) c = re.sub(r'\n+', ' ', c) c = re.sub(r'\s+', ' ', c) c = re.sub(r"[A-Za-z!#$%&'()*+,./:;<=>?@[\]^_`{|}~—\"\-]+", ' ', c) return c.strip()

Вторым этапом стало приведение слов в обращениях в нормальную форму, попутно удаляя слова, которые ничего не значат в контексте русского языка и нашего домена. Данные стоп-слова частично позаимствованы из библиотеки NLTK и перечислены в массиве stopwords:

import pymorphy2 import nltk morph = pymorphy2.MorphAnalyzer() stopwords = nltk.corpus.stopwords.words('russian') stopwords.extend(['сообщение','документ','номер','запрос','страхование','страховой']) def lemmatize(text): text = re.sub(r"\d+", '', text.lower()) #удаление цифр из текста for token in text.split(): token = token.strip() token = morph.normal_forms(token)[0].replace('ё', 'е') if token and token not in stopwords: tokens.append(token) if len(tokens) > 2: ' '.join(tokens) return None

После этих нехитрых действий обращения приняли следующий вид:

Далее, для того, чтобы объединить похожие жалобы в группы необходимо было перейти от словесного представления жалоб к векторно-числовому. Очень часто для этой цели используют OneHotEncoding или TF-IDF. И хотя эти способы получения эмбеддингов распространены и показывают неплохие результаты в некоторых задачах, все же, у них есть серьезный недостаток – данные подходы основаны на частотных характеристиках корпуса и не учитывают семантику текста. Это означает, что, несмотря на одну и ту же смысловую нагрузку, векторы предложений «сожалеем за доставленные неудобства» и «просим прощение за возникшие трудности» не будут иметь ничего общего друг с другом, т.к. фразы состоят из разных слов.

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

import tensorflow as tf import tensorflow_hub as hub import tensorflow_text model = hub.load(r'/UniverseSentenseEmbeddings/USEv3') embedding = model(‘предложение для перевода в вектор’)

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

input1, input2 = ['большая собака'], ['крупный пёс', 'большая кошка', 'маленькая собака', 'маленькая кошка', 'старая картина'] emb1, emb2 = model(input1), model(input2) results_cosine = pairwise.cosine_similarity(emb1, emb2).tolist()[0] for i, res in enumerate(results_cosine): print('"{}" <> "{}", cos_sim={:.3f}'.format(input1[0],input2[i],results_cosine[i]))

Результат получается достаточно интересным:

"большая собака" <> "крупный пёс", cos_sim = 0.860 "большая собака" <> "большая кошка", cos_sim = 0.769 "большая собака" <> "маленькая собака", cos_sim = 0.748 "большая собака" <> "маленькая кошка", cos_sim = 0.559 "большая собака" <> "старая картина", cos_sim = 0.192

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

В качестве алгоритма кластеризации были использованы 4 метода: dbscan, агломеративная, kMeans и MiniBatchKMeans. В последствии мы остановились на результате работы агломеративной кластеризации, т.к., по нашему мнению, именно этот метод наиболее адекватно разделял наш набор данных на тематические подгруппы:

from sklearn.cluster import AgglomerativeClustering num_clusters = 5 agglo1 = AgglomerativeClustering(n_clusters=num_clusters, affinity='euclidean') #cosine, l1, l2, manhattan get_ipython().magic('time answer = agglo1.fit_predict(sent_embs)')

С помощью вышеописанного подхода были получены 5 кластеров, однако нам предстояло еще выяснить, за какую тему отвечает каждая из групп. Для этого был использован простой подход – для каждого кластера были подсчитаны все входящие слова и ТОП-10 из них были представлены в качестве основной сути:

cl = {} for cluster, data in tqdm(report.groupby('AGGLOM'), desc=method): arr = ' '.join(data['НФ'].values).split() arr_morph = [] for k in arr: arr_morph.append(morph.parse(k)[0].normal_form) cl[method+'_'+str(cluster)] = Counter([x.replace('ё', 'е') for x in arr_morph if x not in stopwords]).most_common(10)

После некоторых дополнений списка стоп-слов получились следующие результаты:

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

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

В результате нам удалось сопоставить информацию из разных БД, выявить отклонения и направить рекомендации по улучшению действующих процессов.

{ "author_name": "NTA", "author_type": "editor", "tags": ["\u0443\u0434\u0430\u043b\u0435\u043d\u0438\u0435","cosine"], "comments": 5, "likes": 6, "favorites": 17, "is_advertisement": false, "subsite_label": "ml", "id": 217126, "is_wide": true, "is_ugc": false, "date": "Fri, 05 Mar 2021 20:48:40 +0300", "is_special": false }
0
5 комментариев
Популярные
По порядку
1

После этих нехитрых действий обращения приняли следующий вид

Вот здесь видно, что так же пропало и имя с отчеством. Это из-за чего произошло? Из-за stopwords NLTK? Вроде в регулярках не вижу ничего подобного

Ответить
1

Регулярное выражение re.sub(r’crm[^\n]+’, ’’, c) в данном случае убрало строчку с именем и отчеством. Кроме того, нужно понимать, что в оригинальном коде функция cl_text имела куда больше строк кода, в статье её усеченная версия, чтобы не перегружать статью.

Ответить
1

спасибо, круто!

Ответить
1

ML значительно упрощает задачи. Главное, правильно составить алгоритмы. 

Ответить
0

Dasha, да, согласны! Очень упрощает!

Ответить

Комментарии

null