Предсказание количества лайков у комментария в YouTube

от автора

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

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

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

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

Для извлечения данных воспользуемся YouTube Data API v3. Первым делом надо получить API ключ. Это сделать просто, однако весьма заковыристо и сходу непонятно, поэтому быстро пробегусь по шагам:

1.    Перейти на сайт Console Google Cloud Platform (console.cloud.google.com)

2.    Зайти под аккаунтом Google

Создать проект

 4. Создать новый API

  1. Найти YouTube API v3

  2. И включить его

    Также, чтобы пользоваться этим API, необходимо скачать библиотеку google-api-python-client:

pip install google-api-python-client

После получения ключа API можно начинать парсить комментарии. В качестве цели возьмем ролики популярного в ру сегменте канала “Utopia Show”. Вначале загрузим нужные библиотеки и определим необходимые переменные:

import os import googleapiclient.discovery import csv import tqdm  API_KEY = "your_API_key" VIDEO_IDS = ["Ywpd8M6wfHc", "sskg_JguH28", "JDKqXmOX52Q", "k8FIVugHGSg"] COMMENT_COUNT = 1000 MAX_RESULT = 100

Теперь напишем функцию для парсинга комментариев по id видео, под которыми они размещены:

# Функция для скачивания комментариев по id видео def get_comments(video_id, nextPageToken=None):     # Disable OAuthlib's HTTPS verification when running locally.     # *DO NOT* leave this option enabled in production.     os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"      api_service_name = "youtube"     api_version = "v3"          youtube = googleapiclient.discovery.build(         api_service_name, api_version, developerKey = API_KEY)      request = youtube.commentThreads().list(         part="id,snippet",         maxResults=MAX_RESULT,         pageToken=nextPageToken,         videoId=video_id,         order="relevance"     )     response = request.execute()      return response

Ещё одну функцию для получения даты публикации видео по её id:

# Функция для скачивания даты выхода видео по id def get_video_date_published(video_id):     # Disable OAuthlib's HTTPS verification when running locally.     # *DO NOT* leave this option enabled in production.     os.environ["OAUTHLIB_INSECURE_TRANSPORT"] = "1"      api_service_name = "youtube"     api_version = "v3"          youtube = googleapiclient.discovery.build(         api_service_name, api_version, developerKey = API_KEY)      request = youtube.videos().list(         part="snippet,contentDetails,statistics",         id=video_id     )     response = request.execute()      return response.get("items")[0].get("snippet").get("publishedAt")

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

def youtube_comment_parser():     with open('comments.csv', 'w', encoding="utf-8") as csv_file:         writer = csv.writer(csv_file, quoting=csv.QUOTE_ALL, lineterminator='\r')          # Заголовки столбцов         names = ['textOriginal',                  'authorDisplayName',                  'likeCount',                  'publishedAt',                  'videoPublishedAt']         writer.writerow(names)          iteration_count = int(COMMENT_COUNT/MAX_RESULT)         for video_id in tqdm.tqdm(VIDEO_IDS):             # Скачиваем комментарии             items = []             nextPageToken = None             for _ in range(iteration_count):                 response = get_comments(video_id, nextPageToken)                 nextPageToken = response.get("nextPageToken")                 items = items + response.get("items")              # Дата публикации видео             videoPublishedAt = get_video_date_published(video_id)              # Сохраняем комментарии и дату публикации видео в файл csv             for item in items:                 topLevelComment = item.get("snippet").get("topLevelComment").get('snippet')                  row = [topLevelComment.get('textOriginal'),                        topLevelComment.get('authorDisplayName'),                        topLevelComment.get('likeCount'),                        topLevelComment.get('publishedAt'),                        videoPublishedAt]                  writer.writerow(row)

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

import pandas as pd df = pd.read_csv('comments.csv')

Сначала посмотрим на гистограмму количества лайков:

df['likeCount'].hist(bins=50)

Как видно, число комментариев с нулевым или близким количеством лайков зашкаливает, а это плохо для модели, так как модель будет стараться выдать нулевое значение, угадывая чуть ли не в половине случаев. Поэтому, чтобы исправить это, применим к значениям количества лайков функцию log(x + 1). Прибавление единицы здесь нужно, чтобы не было проблем с нулевыми значениями. Эта функция уже реализована в библиотеке numpy, ею и воспользуемся:

import numpy as np df['logLikeCount'] = np.log1p(df['likeCount'])

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

Далее, обрабатываем даты публикаций комментария и видео и находим разницу между ними в секундах, здесь всё просто:

# Приведение даты к типу datetime df['publishedAt'] = pd.to_datetime(df['publishedAt'], format="%Y-%m-%dT%H:%M:%SZ") df['videoPublishedAt'] = pd.to_datetime(df['videoPublishedAt'], format="%Y-%m-%dT%H:%M:%SZ")  # Разница между датами публикацией комментария и видео  df['publishedDifference'] = (df['publishedAt'] - df['videoPublishedAt']).apply(lambda x: x.total_seconds()).astype(int)

Теперь необходимо обработать текст — в нём нужно убрать знаки препинания, стоп-слова, выделить слова с одной основой и привести всё это в пригодный для машины вид. Чтобы убрать знаки препинания, а заодно и любые другие символы кроме русских букв, можно воспользоваться регулярными выражениями:

import re regex = re.compile('[^а-я А-Я]') text = regex.sub(' ', text)

Для отбора стоп-слов есть специальная библиотека nltk:

import nltk from nltk.corpus import stopwords from nltk.tokenize import word_tokenize  nltk.download('stopwords') nltk.download('punkt')  word_tokens = word_tokenize(text) filtered_sentence = [w for w in word_tokens if not w in stop_words] 

А для приведения слов к своей основной форме можно пропустить их через стемминг:

from nltk.tokenize import word_tokenize from nltk.stem.snowball import SnowballStemmer   word_tokens = word_tokenize(text) filtered_sentence = [stemmer.stem(w) for w in word_tokens]

Объединяя всё вышеуказанное, получаем функцию, обрабатывающая все тексты поступающие ей на вход:

import nltk from nltk.corpus import stopwords from nltk.tokenize import word_tokenize from nltk.stem.snowball import SnowballStemmer   import re import tqdm  nltk.download('stopwords') nltk.download('punkt')  # Функция обрабатывает тексты для дальнейшего использования def process_text(texts):   stemmer = SnowballStemmer(language='russian')   stop_words = set(stopwords.words('russian'))   regex = re.compile('[^а-я А-Я]')   process_texts = []      for text in tqdm.tqdm(texts):     text = text.lower()     # Удаляет любые символы, кроме русских букв     text = regex.sub(' ', text)     # Разбивает текст на отдельные слова     word_tokens = word_tokenize(text)      # Убирает стоп слова и пропускаем через стемминг оставшиеся     filtered_sentence = [stemmer.stem(w) for w in word_tokens if not w in stop_words]      process_texts.append(' '.join(filtered_sentence))        return process_texts   df['textProcessed'] = process_text(df['textOriginal'])

Осталось только привести слова в удобоваримый для компьютера вид. С этим нам поможет векторизация, однако здесь есть один нюанс — векторизатор нужно тренировать только на обучающей выборке, поэтому его применим, когда будем делить данные на обучающие и валидационные. Кстати о них:

from sklearn.feature_extraction.text import CountVectorizer from sklearn.model_selection import train_test_split  X = df[['textProcessed', 'publishedDifference']] y = df['logLikeCount']  X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0)  # Векторизация обработанных слов vectorizer = CountVectorizer() X_train_vec = vectorizer.fit_transform(X_train['textProcessed']) X_test_vec = vectorizer.transform(X_test['textProcessed'])  # Изменяет размерность массива разности дат публикаций, чтобы соединить с векторизированит текстами X_train_pub = np.array(X_train['publishedDifference']).reshape((-1,1)) X_test_pub = np.array(X_test['publishedDifference']).reshape((-1,1))  # Объединяет вектора слов и разности дат публикаций X_train = np.append(X_train_vec.toarray(), X_train_pub, axis=1) X_test = np.append(X_test_vec.toarray(), X_test_pub, axis=1)

Ну и наконец, ради чего всё это затевалось, обучение модели. Обучать будем на модели градиентного бустинга из библиотеки CatBoost. Модель возьмём без дополнительных параметров, укажем лишь случайное зерно и функцию потерь в виде MAE:

from catboost import CatBoostRegressor   catboost = CatBoostRegressor(loss_function='MAE', random_seed=0, silent=True) catboost.fit(X_train, y_train) pred = catboost.predict(X_test)

Для оценки качества модели используем MAE. Как мы помним, ключевой параметр — количество лайков — был преобразован с помощью функции np.log1p, поэтому применение MAE сразу к предсказанным данным нам почти ничего не даст. Для наглядности сначала преобразуем данные, а уже потом применем MAE:

from sklearn.metrics import mean_absolute_error  pred_int = np.expm1(pred).astype(int) y_test_int = np.array(np.expm1(y_test).astype(int))  mae = mean_absolute_error(pred_int, y_test_int) print('MAE =', mae)
output:  MAE = 56.221666666666664

Ещё для полноты картины выведем первые 30 предсказанных значений и реальных:

print("pred - true") for i in range(30):   print(f"{pred_int[i]:4} - {y_test_int[i]}")
output:  pred - true    0 - 5    0 - 0    0 - 0    0 - 0    0 - 0    0 - 0   69 - 10    0 - 0    0 - 1    0 - 20    0 - 1    0 - 0    0 - 0    0 - 1    0 - 0    0 - 0    0 - 0   56 - 104    0 - 0    0 - 23    0 - 0    0 - 0    0 - 0   52 - 5737    0 - 0    0 - 1    0 - 3    0 - 0    0 - 0    0 - 0

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


ссылка на оригинал статьи https://habr.com/ru/post/597331/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *