Возвращение к играм на Reddit

Упражнение в области НЛП и итеративной науки о данных

Один из самых популярных проектов в Data Science Immersive от General Assembly основан на Reddit. Ваша цель: выбрать два субреддита, собрать по тысяче сообщений из каждого и построить модель, чтобы классифицировать сообщения обратно в их правильные субреддиты из заголовка. Легко понять, почему это такой популярный проект, он занимает 3-е место из 5, поэтому мы начинаем привыкать к нашему новому набору навыков и можем выбирать собственные субреддиты, чтобы действительно повеселиться. с ним тоже. Это хорошее представление о мощной технологии, которая всегда доступна специалистам по данным.

Для своей реализации я выбрал два сабреддита по теме, в которой я достаточно разбираюсь в предметной области - играх! Я был геймером по крайней мере со дня своего рождения (по слухам, но не подтвержденным), поэтому я был очень взволнован, когда смог найти собственное понимание игрового мира. Я выбрал два сабреддита с похожей, но различающейся тематикой: r / gaming и r / pcgaming.

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

Недавно я решил потратить некоторое время и снова запустить проект, не только для того, чтобы собрать больше данных, но и для того, чтобы попробовать реализовать некоторые улучшения, до которых я не дошел изначально. Я определенно вырос как программист, поэтому я знал, что хочу внести множество изменений даже с точки зрения эффективности и удобочитаемости. Циклическое переключение проектов - это не просто шанс улучшить обучение машинному обучению, но и отличное время для улучшения вашего кода!

Сбор данных

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

def reddit_puller(url, params, user_agent, pulls, post_list):
    # adapting code from Boom Devahastin Na Ayudhya
    
    for pull_num in range(int(pulls)):
        
        # stating which pull is being attempted
        print("Pulling data attempted", pull_num+1, "times")
        
        # establishing the request code
        res = requests.get(url, headers=user_agent, params=params)
        
        # pull the correct data if the code is good
        if res.status_code == 200:
            json_data = res.json()                      #  Pull JSON
            post_list.extend(json_data['data']['children']) #  Get posts and extend the `posts` list 
            
            # updating url with the id of the last post in the pull
            # next pull will grab the following entries
            after = json_data['data']['after']
            params["after"] = after
        
        else:
            print("There has been an error. The code is: ", res.status_code)
            break
            
        # sleeping the func so we aren't locked out
        time.sleep(2)

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

Очистка и анализ

Шаги по очистке, которые я предпринял изначально, практически не изменились с добавлением новых данных. Было еще несколько перекрестных сообщений (сообщения, появляющиеся в обоих субреддитах) и выбросов (заголовки только с смайликами / смайликами), поэтому там, где я ранее решал эти проблемы вручную, я написал новый масштабируемый код, который не должен быть изменяется каждый раз при изменении данных.

# saving the cross-posts as new dfs
gaming_cross_posts = gaming_df[gaming_df["title"].isin(pcgaming_df["title"]) == True]
pc_cross_posts = pcgaming_df[pcgaming_df["title"].isin(gaming_df["title"]) == True]
# dropping all the cross-posts
gaming_df.drop(gaming_cross_posts.index, inplace=True)
pcgaming_df.drop(pc_cross_posts.index, inplace=True)

Я также создал новый столбец из последней итерации, подсчитав количество слов в каждом заголовке. Я уже сделал характеристику общей длины символа, но я хотел добавить еще одно измерение к информации. Это не только помогает получить представление о данных и самих субреддитах, но и может быть полезно в качестве прогнозирующего фактора на этапе моделирования на более позднем этапе. Визуализируя данные на гистограмме, я мог ясно видеть большое количество совпадений по длине заголовков, а также отчетливое изменение длины заголовков для сообщений в r / pcgaming по сравнению с r / gaming.

Тот факт, что больше постов имеют заголовки в диапазоне 10–15 слов, сигнализирует о том, что может быть более сложным то, что обсуждается в r / pcgaming. С точки зрения моделирования это может означать, что более длинная игра, скорее всего, будет принадлежать r / pcgaming, чем r / gaming, но эффективность еще предстоит увидеть.

Я также более глубоко изучил сами слова, чтобы лучше понять, какие типы языка составляют цели. Я прогнал данные через simpleCountVectorizer, чтобы найти наиболее распространенные слова. Затем, выполнив несколько итераций, я решил использовать PorterStemmer для упрощения слов и определил типичные игнорируемые слова, которые нужно удалить, включая некоторые дополнительные, относящиеся к этим данным.

С моими новыми данными мне пришлось пройти через этот процесс еще несколько раз, определяя еще больше игнорируемых слов, чтобы добавить в свой список. В конце концов, около половины популярных слов из r / gaming на этот раз были другими, с меньшим количеством изменений для r / pcgaming, где верхние слова имели гораздо большее преимущество.

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

Моделирование

В первый раз моделирование было уже довольно сложным процессом. У меня были серьезные проблемы с моделями с высокой степенью переобучения, но в итоге я получил AdaBoostClassifier, который имел низкую дисперсию и точность на уровне 70-х. Я объединил этап векторизации с обучением модели в Pipeline с GridSearchCV, чтобы иметь возможность полностью оптимизировать каждый тип алгоритма классификации.

На этот раз я внес некоторые изменения. Поскольку я хотел добавить свои инженерные функции - количество слов и символов в заголовках - я знал, что не смогу просто сохранить старый конвейер на месте. Я также обнаружил, что использование TfidfVectorizer дает мне лучшие результаты, чем CountVectorizer, поэтому я решил сделать его постоянным изменением и перенести его на этап предварительной обработки, а не на часть поиска по сетке (возможно, это была не лучшая идея , но я вернусь к этому). Итак, с векторизацией моих слов (а теперь и с разреженной матрицей) я преобразовал их и свои дополнительные числовые столбцы в массивы и сложил их вместе, чтобы получить новые данные для обучения.

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

Я не получил ни одного. Я поднял метрики для всех своих моделей - я шел с точностью для этого проекта, потому что классы были хорошо сбалансированы и не было большего ущерба от ложных срабатываний или ложных отрицаний - и сравнил их. Хотя SVC был в верхней части моего списка, на самом деле он имел несколько схожую производительность с моделью логистической регрессии.

Таким образом, при выборе «лучшей» модели из группы все сводилось к этим двум моделям, поскольку они, казалось, давали лучший баланс смещения / дисперсии (в отличие от KNN или Random Forest). В конце концов, я выбрал SVC как «лучший», потому что он минимизировал дисперсию больше, чем логистическая регрессия улучшила производительность. Однако, поскольку SVC - это что-то вроде модели «черного ящика», я хотел по-прежнему использовать логистическую регрессию для ее интерпретируемости. Сопоставив коэффициенты с характеристиками, я смог найти, что было наиболее важным при принятии решения для модели.

Мы видим, что слова, которые мы видели в EDA, в конечном итоге оказались очень важными в процессе моделирования - «Steam», «трейлер» и «релиз» были одними и теми же тремя наиболее часто используемыми словами в r / pcgaming. С другой стороны, наличие таких слов, как «счастливый» и «друг», означало, что сообщение вряд ли было от r / pcgaming. Я не думаю, что хочу вдаваться в подробности того, что это может сказать о компьютерных геймерах, но я знаю, что это было очень полезно для модели. Я также заметил, что две дополнительные функции, которые я сделал, не появлялись где-либо в верхней части списка коэффициентов, что несколько разочаровало.

Заключение

Итак, что на самом деле дала мне эта новая и улучшенная итерация (со свежими данными, функциями и моделями)? Оказывается, немного. Я не оказал большого влияния на производительность (я уже находился в диапазоне точности 78%), но я смог еще больше уменьшить свою дисперсию, что, честно говоря, не было большим для начала.

Что случилось? Я придумал несколько вариантов. Во-первых, я не предоставил достаточно новой информации - может быть, 2400 сообщений недостаточно для эффективного обучения модели решению этой проблемы - но это похоже на отговорку, поскольку вы почти всегда можете сказать что вам «нужно больше данных», и я видел довольно стабильную производительность между обеими итерациями. Другой заключается в том, что я недостаточно разработал функции - возможно, мне нужно копнуть глубже и добавить данные о количестве комментариев или о другом аспекте этих сообщений, скрытых в исходных файлах JSON, которые были извлечены. Возможно, я подхожу к этому слишком упрощенно, и для этого требуется более продвинутое НЛП, как в случае с Word2Vec, и использование вложений вместо «мешка слов».

Другая большая проблема, которую я увидел, была в моем моделировании. В исходной версии я тестировал и оптимизировал различные векторизаторы как часть функции поиска по сетке / конвейера. На этот раз я добавил отдельно разработанные функции и решил перенастроить предварительную обработку, чтобы упростить это. При этом я удалил возможность оптимизации векторизации в сочетании с новыми функциями - возможно, с этой добавленной информацией был другой набор параметров для TF-IDF, который улучшил бы точность определенных моделей, или, может быть, CVEC действительно был путь идти. Недавно я узнал о ScitKit-Learn ColumnTransformer для именно такой комбинации различных преобразований функций в конвейер, поэтому я думаю, что в следующий раз я буду адаптировать свой код для его использования.

К этому абсолютно стоило вернуться после курса GA, и это стало хорошим уроком того, каково это пройти через несколько циклов проекта (и умереть ожидания). Конечно, вы всегда возвращаетесь к чему-то с намерением улучшить это, но иногда вы не оказываете особого влияния. В любом случае, это шанс учиться и расти, так что не упускайте его из виду.

Как всегда, спасибо за поездку на поезде по науке о данных и поделитесь своими мыслями о проекте - действительно ли SVC - лучшая модель? Не следует ли мне разработать больше функций? Комментарий ниже!

Пожалуйста, ознакомьтесь с полным репо проекта здесь.

Вы также можете найти меня в LinkedIn.