Совместная фильтрация для рекомендации газетных статей
В этой статье показано, как реализовать модель WALS (матричная факторизация) для совместной фильтрации. Для совместной фильтрации нам не нужно ничего знать ни о пользователях, ни о контенте. По сути, все, что нам нужно знать, - это userId, itemId и рейтинг, который конкретный пользователь дал конкретному элементу.
Использование времени, проведенного на странице, в качестве рейтинга
В этом случае мы работаем с газетными статьями. Компания не просит пользователей оценивать статьи. Однако мы можем использовать время, проведенное на странице, как прокси для оценки.
Вот запрос, чтобы получить продолжительность, которую каждый пользователь тратит на каждую газетную статью (обычно мы также добавляем к этому временному фильтру («последние 7 дней»), но сам наш набор данных ограничен несколькими днями):
WITH CTE_visitor_page_content AS ( SELECT # Schema: https://support.google.com/analytics/answer/3437719?hl=en # For a completely unique visit-session ID, we combine combination of fullVisitorId and visitNumber: CONCAT(fullVisitorID,'-',CAST(visitNumber AS STRING)) AS visitorId, (SELECT MAX(IF(index=10, value, NULL)) FROM UNNEST(hits.customDimensions)) AS latestContentId, (LEAD(hits.time, 1) OVER (PARTITION BY fullVisitorId ORDER BY hits.time ASC) - hits.time) AS session_duration FROM `cloud-training-demos.GA360_test.ga_sessions_sample`, UNNEST(hits) AS hits WHERE # only include hits on pages hits.type = "PAGE" GROUP BY fullVisitorId, visitNumber, latestContentId, hits.time ) -- Aggregate web stats SELECT visitorId, latestContentId as contentId, SUM(session_duration) AS session_duration FROM CTE_visitor_page_content WHERE latestContentId IS NOT NULL GROUP BY visitorId, latestContentId HAVING session_duration > 0 LIMIT 10
Результат выглядит так:
Масштабирование поля рейтинга до [0, 1]
Мы ожидаем, что посетителям, которые потратили больше времени на просмотр статьи, она понравится больше. Однако продолжительность сеанса может быть немного смешной. Обратите внимание на 55440 секунд (15 часов), которые показывает строка №3. Мы можем построить гистограмму продолжительности сеанса в наборе данных:
Итак, давайте отмасштабируем и обрежем его значения по средней продолжительности сеанса (на среднюю продолжительность сильно повлияют выбросы):
... normalized_session_duration AS ( SELECT APPROX_QUANTILES(session_duration,100)[OFFSET(50)] AS median_duration FROM aggregate_web_stats ) SELECT * EXCEPT(session_duration, median_duration), CLIP(0.3 * session_duration / median_duration, 0, 1.0) AS normalized_session_duration FROM aggregate_web_stats, normalized_session_duration
где CLIP определяется следующим образом:
CREATE TEMPORARY FUNCTION CLIP_LESS(x FLOAT64, a FLOAT64) AS ( IF (x < a, a, x) ); CREATE TEMPORARY FUNCTION CLIP_GT(x FLOAT64, b FLOAT64) AS ( IF (x > b, b, x) ); CREATE TEMPORARY FUNCTION CLIP(x FLOAT64, a FLOAT64, b FLOAT64) AS ( CLIP_GT(CLIP_LESS(x, a), b) );
Теперь продолжительность сеанса масштабируется и находится в правильном диапазоне:
Собираем все вместе и материализуем в таблицу:
CREATE TEMPORARY FUNCTION CLIP_LESS(x FLOAT64, a FLOAT64) AS ( IF (x < a, a, x) ); CREATE TEMPORARY FUNCTION CLIP_GT(x FLOAT64, b FLOAT64) AS ( IF (x > b, b, x) ); CREATE TEMPORARY FUNCTION CLIP(x FLOAT64, a FLOAT64, b FLOAT64) AS ( CLIP_GT(CLIP_LESS(x, a), b) ); CREATE OR REPLACE TABLE advdata.ga360_recommendations_data AS WITH CTE_visitor_page_content AS ( SELECT # Schema: https://support.google.com/analytics/answer/3437719?hl=en # For a completely unique visit-session ID, we combine combination of fullVisitorId and visitNumber: CONCAT(fullVisitorID,'-',CAST(visitNumber AS STRING)) AS visitorId, (SELECT MAX(IF(index=10, value, NULL)) FROM UNNEST(hits.customDimensions)) AS latestContentId, (LEAD(hits.time, 1) OVER (PARTITION BY fullVisitorId ORDER BY hits.time ASC) - hits.time) AS session_duration FROM `cloud-training-demos.GA360_test.ga_sessions_sample`, UNNEST(hits) AS hits WHERE # only include hits on pages hits.type = "PAGE" GROUP BY fullVisitorId, visitNumber, latestContentId, hits.time ), aggregate_web_stats AS ( -- Aggregate web stats SELECT visitorId, latestContentId as contentId, SUM(session_duration) AS session_duration FROM CTE_visitor_page_content WHERE latestContentId IS NOT NULL GROUP BY visitorId, latestContentId HAVING session_duration > 0 ), normalized_session_duration AS ( SELECT APPROX_QUANTILES(session_duration,100)[OFFSET(50)] AS median_duration FROM aggregate_web_stats ) SELECT * EXCEPT(session_duration, median_duration), CLIP(0.3 * session_duration / median_duration, 0, 1.0) AS normalized_session_duration FROM aggregate_web_stats, normalized_session_duration
Модель рекомендаций по обучению
Данные таблицы теперь выглядят так:
Мы можем обучить модель следующим образом:
CREATE OR REPLACE MODEL advdata.ga360_recommendations_model OPTIONS(model_type='matrix_factorization', user_col='visitorId', item_col='contentId', rating_col='normalized_session_duration', l2_reg=10) AS SELECT * from advdata.ga360_recommendations_data
Примечание. Если вы используете тарифный план по требованию, вы получите сообщение об ошибке Модели факторизации матрицы обучения недоступны для использования по требованию. Totrain, настройте резервирование (гибкое или обычное) в соответствии с инструкциями в общедоступных документах BigQuery . Это связано с тем, что факторизация матрицы имеет тенденцию становиться дорогостоящей, если цена определяется на основе данных. Слоты Flex устанавливаются на основе вычислений и намного дешевле. Итак, настройте слоты flex - вы можете использовать их всего 60 секунд.
Вы будете играть со значением l2_reg, которое дает разумные ошибки для вашего набора данных. Откуда вы знаете, что это разумно? Проверьте вкладку оценки:
Вы хотите, чтобы средняя и медианная абсолютные ошибки были похожи и были намного меньше 0,5 (случайный шанс будет 0,5).
Делать прогнозы
Вот как попасть в топ-3 рекомендаций для каждого пользователя:
SELECT visitorId, ARRAY_AGG(STRUCT(contentId, predicted_normalized_session_duration) ORDER BY predicted_normalized_session_duration DESC LIMIT 3) FROM ML.RECOMMEND(MODEL advdata.ga360_recommendations_model) WHERE predicted_normalized_session_duration < 1 GROUP BY visitorId
Это дает:
Наслаждаться!
Огромное спасибо Луке Семпре и Эвану Джонсу за помощь в получении данных Google Analytics.