Застосовуємо машинне навчання для збору зворотного зв'язку від користувачів
Мене звуть Олександр Бєлобородов, я .NET Developer в Community Management Department у Plarium. Наша команда розробляє інструменти для оптимізації роботи агентів підтримки і ком'юніті-менеджерів, а також інструменти залучення користувачів поза грою. Хочу поділитися нашим досвідом використання машинного навчання для збору зворотного зв'язку від гравців.
Навіщо це потрібно
Plarium Kharkiv — студія повного циклу розробки. Після релізу гри ми випускаємо регулярні оновлення, здійснюємо технічну підтримку проектів і постійно взаємодіємо з гравцями на офіційному форумі і в соцмережах.
У нас 35 груп у соціальних мережах, і в них складається більш 20 млн активних користувачів. Крім публікування і спілкування з гравцями, ком'юніті-менеджери збирають фідбек за новими фічами, приймають раціональні пропозиції щодо покращення гри і передають їх розробникам.
Щодня гравці залишають від 250 до 3 500 коментарів. Проаналізувати їх та скласти об'єктивну картину ставлення користувачів до гри досить затратно по часу, тому ми вирішили автоматизувати цей процес. Ця функціональність стала частиною великого проекту з оптимізації роботи в соціальних мережах.
У підсумку ми розробили інструмент, який виконує наступні функції:
- агрегує всі профілі в один веб-інтерфейс;
- вважає статистику особистих повідомлень;
- вважає статистику публікацій та коментарів;
- підраховує співвідношення позитивних, нейтральних і негативних коментарів;
- рахує кількість лайків і репостов;
- візуалізує статистику переходу за коротким посиланням в гру, а також статистику охоплення.
Щоб навчити нашу систему відрізняти позитивні і негативні коментарі, ми використовували алгоритми машинного навчання, а конкретно — метод опорних векторів. Він досить простий у застосуванні і добре справляється з завданнями бінарної класифікації.
Трохи теорії
Суть методу — знайти гіперплощина, що розділяє дві множини об'єктів. Рішення завдання бінарної класифікації полягає в пошуку якоїсь лінійної функції, яка правильно поділяє набір даних на два класу.
Більше про метод можна прочитати за посиланнями в кінці статті.
Коли потрібно розрізняти більше двох класів, застосовується мультиклассовый метод, суть якого полягає в реалізації однієї з стратегій:
- «Один проти всіх»: навчається N класифікаторів, де N — кількість класів.
Класифікатор з найвищим значенням функції виходу
присвоює новий об'єкт до певного класу. Тут йде зіставлення типу «літак/все, що не літак», «будинок/все, що не дім» і т. д. - «Один проти одного»: навчається N класифікаторів, тільки тепер об'єкт присвоюється до того класу, до якого його віднесло більшість класифікаторів. Стратегія нагадує те, як проводяться змагання у футбольній лізі: команди грають між собою, і та, яка перемагає максимальну кількість разів, стає переможцем.
Переваги використання методу опорних векторів:
- точність класифікації;
- добре справляється з невеликими наборами даних;
- хороша навчальна здатність: метод практично не потребує попередньої настройки, якщо не вважати необхідність підбору функції ядра.
Недоліки методу:
- нестійкий до шуму у вихідних даних;
- не працює з ймовірностями;
- не містить формалізованих алгоритмів вибору ядра;
- при спробі використання в мультиклассовой задачі якість і швидкість роботи падають;
- не підходить для великих обсягів даних. Якщо навчальна вибірка містить шумові викиди, вони будуть істотним чином враховані при побудові розділяє гіперплощини.
Області застосування методу опорних векторів:
- розпізнавання зображень;
- спам-фільтри;
- категоризація тексту;
- розпізнавання рукописного тексту.
Метод опорних векторів у вирішенні наших завдань
Метод опорних векторів відноситься до так званих методів навчання з вчителем. Спершу потрібно подати якусь навчальну вибірку, щоб навчити модель розрізняти класи.
Готову реалізацію методу ми взяли з бібліотеки libsvm.net .
В результаті навчання виходить готова до розпізнавання модель і словник.
Для класифікації даних використовується модель і словник, отримані на етапі навчання.
Адаптуємо бібліотеку libsvm.net під задачу класифікації тексту
Сама бібліотека libsvm.net є лише реалізацією методу опорних векторів. Щоб з її допомогою класифікувати текстові дані, необхідно написати надбудову над цією бібліотекою, яка буде перетворювати текст в вектор ознак.
Перед тим як навчати модель, необхідно очистити вхідні рядок від «гучних» слів. Для цього ми розробили клас StringProcessor. Суть його в тому, що він містить два методу — Normalize і GetWords.
Normalize замінює переноси рядків на прогалини і прибирає фрагменти рядки, які потрапляють під шаблони зі списку ігнорованих регулярних виразів. Це зроблено для того, щоб легко відфільтрувати керуючі конструкції в соцмережах, такі як згадки, що починаються з @ на Facebook. Метод GetWords повертає з вихідної рядки набір слів, одночасно забираючи стоп-слова.
public class StringProcessor : IStringProcessor { private readonly SvmModelSettings _settings; public StringProcessor(SvmModelSettings settings) { if (settings == null) throw new ArgumentNullException("settings"); _settings = settings; } public string Normalize(string text) { var str = text.Replace(' ', ' '); return _settings.IgnoredPatterns.Aggregate(str, (current, pattern) => Regex.Replace(current, pattern, "", RegexOptions.IgnoreCase)); } public IEnumerable<string> GetWords(string text) { return text.Split(_settings.Delimiters, StringSplitOptions.RemoveEmptyEntries) .Select(w => w.ToLower()) .Where(w => !_settings.IgnoredWords.Contains(w)); } }
Основні моделі класифікатора виглядають так:
public enum Emotion { PositiveOrNeutral = 1, Negative = -1 } public class ClassifiedItem //Класифікований зразок { public Emotion Emotion { get; set; } //Тональність зразка public string Text { get; set; } //Текст }
Клас SvmModelBuilder. Вміє тренувати модель, а також отримувати тренированную модель з файлу.
public class SvmModelBuilder //Клас призначений для створення моделі { private readonly IStringProcessor _stringProcessor; public SvmModelBuilder(IStringProcessor stringProcessor) { _stringProcessor = stringProcessor; } public virtual SvmTrainedModel Train(IEnumerable<ClassifiedItem> items) { if (!items.Any()) throw new InvalidOperationException("No data to train the model"); var emotionArr = new List<double>(); var vocabularySet = new HashSet<string>(); var linewords = new List<string[]>(); foreach (var classifiedItem in items) //будуємо словник слів з повного вхідного набору { var words = GetWords(classifiedItem.Text).ToArray(); vocabularySet.UnionWith(words); linewords.Add(words); emotionArr.Add((double)classifiedItem.Emotion); } var vocabulary = new Dictionary<string, int>(vocabularySet.Count); var sorted = vocabularySet.OrderBy(w => w).ToArray(); //сортуємо слова у словнику і проставляємо індекси // щоб потім вихідну рядок можна було перетворити на вектор ознак for (var i = 0; i < sorted.Length; i++) { vocabulary.Add(sorted[i], i); } var problem = CreateProblem(linewords, emotionArr, vocabulary); //отримуємо модель за допомогою класів бібліотеки libsvm.net var model = new C_SVC(problem, KernelHelper.LinearKernel(), 1); //повертаємо модель, готову до класифікації return new SvmTrainedModel(model, vocabulary, _stringProcessor); } private static svm_problem CreateProblem(IReadOnlyCollection<string[]> lines, List<double> emotionArr, IReadOnlyDictionary<string, int> vocabulary) { return new svm_problem() { l = lines.Count, //загальна кількість класифікуються коментарів //перетворює рядок в вектора ознак x = lines.Select(line => NodeUtils.CreateNode(line, vocabulary).ToArray()).ToArray(), y = emotionArr.ToArray() //вектор оцінок коментарів }; } //повертає список слів з рядка, очищені від "шуму" protected virtual IEnumerable<string> GetWords(string text) { var normalized = _stringProcessor.Normalize(text); return _stringProcessor.GetWords(normalized); } //тут виймання моделі з файлу... //... }
Клас SvmTrainedModel, що представляє навчену модель. Основне його призначення в тому, щоб виконувати класифікацію вхідний рядки.
public class SvmTrainedModel { private readonly SVM _model; private readonly IReadOnlyDictionary<string, int> _vocabulary; private readonly IStringProcessor _stringProcessor; public SvmTrainedModel(SVM model, IReadOnlyDictionary<string, int> vocabulary, IStringProcessor stringProcessor) { if (model == null) throw new ArgumentNullException("model"); if (vocabulary == null) throw new ArgumentNullException("vocabulary"); if (stringProcessor == null) throw new ArgumentNullException("stringProcessor"); _model = model; _vocabulary = vocabulary; _stringProcessor = stringProcessor; } public Emotion Classify(string text) //виконує класифікацію рядка { return (Emotion)Model.Predict(NodeUtils.CreateNode(GetWords(text).ToArray(), Vocabulary).ToArray()); } //повертає список слів з рядка, очищені від "шуму" protected virtual IEnumerable<string> GetWords(string text) { var normalized = StringProcessor.Normalize(text); return StringProcessor.GetWords(normalized); } //тут методи для збереження моделі і словника в файл //... }
Клас NodeUtils. Його завдання — перетворювати масив слів у вектор ознак, використовуючи словник.
public static class NodeUtils { public static IEnumerable<svm_node> CreateNode(string[] words, IReadOnlyDictionary<string, int> vocabulary) { var uniqueWords = new HashSet<string>(words); foreach (var uniqueWord in uniqueWords) { int i; //пропускаємо слова, яких немає в словнику //т. до. ми не зможемо проставити для них індекс if (!vocabulary.TryGetValue(uniqueWord, out i)) continue; //вважаємо кількість входжень слова в поточний рядок (коментар) var occuranceCount = words.Count(w => string.Equals(w, uniqueWord, StringComparison.InvariantCultureIgnoreCase)); //зберігаємо індекс слова в словнику, і кількість його примірників // в даному рядку (коментарі) yield return new svm_node() { index = i + 1, value = occuranceCount }; } } }
Ось як все разом виглядає в нашому проекті:
В цьому і є суть класифікатора на основі бібліотеки libsvm.net. Залишається написати обгортки у вигляді сервісів, які вже будуть специфічні для конкретного проекту.
Перевірка класифікатора на реальних даних
Для перевірки роботи методу на реальному прикладі ми відібрали 5 спільнот і витягли 1 200 коментарів з кожного. Після цього ком'юніті менеджери розмітили їх тональність, поставивши «1» позитивним і нейтральним коментарям і «-1» — негативним.
Класифікатор навчали на 800 коментарях кожної спільноти окремо.
Як говорилося вище, метод погано справляється з «шумом». У нашому випадку «шум» — це слова, які не несуть смислового навантаження: артиклі, займенники, прийменники, вигуки і т. д., які часто зустрічаються і в позитивних, і в негативних коментарях. Щоб уникнути їх впливу на результати класифікації, ми склали словник «стоп-слів, які видаляються перед обробкою вхідних рядка на етапі навчання і на етапі класифікації.
Після навчання класифікатора ми провели оцінку якості: звірили залишилися 400 коментарів з кожної спільноти на збіг оцінок ком'юніті-менеджерів з оцінками, які поставив наш класифікатор. В результаті зіставлення ми отримали від 5% до 15% відмінностей. Результат збігів у 85% нас влаштував.
Способи вдосконалення класифікатора
Після впровадження класифікатора у реальний проект стало ясно, що загальні тенденції позитивних/негативних коментарів зберігаються, але чисельні значення ще далекі від тих, які ми очікували. Щоб поліпшити класифікатор, ми плануємо:
- збільшити вихідну вибірку, на якій проводилося навчання;
- проаналізувати помилки класифікації і розширити словник стоп-слів;
- виключати зі статистики коментарі ком'юніті-менеджерів, так як найчастіше вони є відповідями на коментарі гравців;
- розробити можливість виключати коментарі з статистики за деякими правилами, наприклад, коментарі під певними публікаціями, або коментарі з наклейками. У соціальних мережах крім інформації про оновлення, ком'юніті менеджери періодично запускають конкурси, в яких гравці мають можливість взяти участь, залишаючи коментарі. Такі коментарі не показують настрій гравців, тому можуть бути виключені зі статистики;
- провести експерименти з різними ядрами (можливо, після застосування іншого ядра точність класифікації виросте);
- використовувати стемминг слів (приведення їх до одного виду);
- використовувати кластеризацію вхідних даних (заміна схожих за значенням слів на слово зі словника).
Крім того, деякі коментарі важко оцінити без контексту, наприклад, сарказм. Навіть ком'юніті-менеджерам іноді важко визначити, позитивний, нейтральний або негативний коментар без знання контексту.
Висновки
- Метод опорних векторів добре підходить для задач бінарної класифікації:
«спам/не спам», «позитивний/негативний». - SVM не видає імовірнісні показники класифікації, що ускладнює
його використання в алгоритмах прийняття рішень. - SVM погано справляється з «шумом», необхідно складати словник стоп-слів.
- SVM має хорошу навчальну спроможність і не вимагає попередньої установки.
- Підвищення швидкості класифікації можна досягти за рахунок кластеризації, стемминга і використання словника стоп-слів.
Зараз ми спостерігаємо за загальною картиною настрої гравців. Можемо відстежити на графіку різкі зміни тональності в ту або іншу сторону і своєчасно на це відреагувати. Наприклад, різке збільшення негативних коментарів може говорити про якусь проблему, а позитивних — про те, що гравці добре сприйняли останнє оновлення.
На закінчення наведу корисні посилання:
SVM Tutorial: Classify text in C# — стаття-натхненник. Містить покрокову інструкцію, як використовувати бібліотеку libsvm.net у проекті .NET.
Теорія від Інтуїта — методи класифікації та прогнозування. Метод опорних векторів. Метод «найближчого сусіда». Байєсова класифікація.
Класифікація даних методом опорних векторів — описує метод опорних векторів, показує, як працює ядро.
Топ-10 data mining-алгоритмів простою мовою — опис і порівняння різних методів машинного навчання.
К. В. Воронцов. Лекції з SVM — лекції по методу опорних векторів для тих, хто хоче розібратися детальніше.
У чому суть методу опорних векторів простими словами? — принцип роботи класифікатора пояснюється ну дуже простими словами. Рекомендую новачкам.
Класифікація документів методом опорних векторів — приклад розробки класифікатора на основі SVM.
Спасибі за увагу!
Опубліковано: 17/07/18 @ 07:00
Розділ Безпека
Рекомендуємо:
Go дайджест #4: WebAssembly and Go, Go 1.11 Beta 1, GraphQL, Apple Metal API and Go
Що почитати: огляд Telegram-каналів українських IT-фахівців
Поради сеньйорів: як прокачати знання junior C++
В ІТ без диплома: історії Technical Architect, Front-end Dev, Product Manager та інших
Centers of Excellence – майбутнє аутсорсингу?