前言

此次比赛是新闻推荐场景下的用户行为预测挑战赛, 是Datawhale与天池联合举办,该赛题是以新闻APP中的新闻推荐为背景, 目的是要求我们根据用户历史浏览点击新闻文章的数据信息预测用户未来的点击行为, 即用户的最后一次点击的新闻文章, 这道赛题的设计初衷是引导大家了解推荐系统中的一些业务背景, 解决实际问题!

赛题链接: 零基础入门推荐系统 - 新闻推荐
论坛:
对于刚接触推荐系统同学推荐先浏览以下基础知识:

  • 推荐系统摘要
  • 推荐系统知识梳理——协同过滤
  • 推荐系统知识梳理——矩阵分解
  • 推荐系统知识梳理——FM
  • 推荐系统知识梳理——Wide&Deep
  • 推荐系统知识梳理——GBDT&LR

数据概况

该数据来自某新闻APP平台的用户交互数据,包括30万用户,近300万次点击,共36万多篇不同的新闻文章,同时每篇新闻文章有对应的embedding向量表示。为了保证比赛的公平性,从中抽取20万用户的点击日志数据作为训练集,5万用户的点击日志数据作为测试集A,5万用户的点击日志数据作为测试集B。具体数据表和参数, 大家可以参考赛题说明。下面说一下拿到这样的数据如何进行理解, 来有效的开展下一步的工作。

评价方式理解

理解评价方式, 我们需要结合着最后的提交文件来看, 根据sample.submit.csv, 我们最后提交的格式是针对每个用户, 我们都会给出五篇文章的推荐结果,按照点击概率从前往后排序。 而真实的每个用户最后一次点击的文章只会有一篇的真实答案, 所以我们就看我们推荐的这五篇里面是否有命中真实答案的。比如对于user1来说, 我们的提交会是:
score(user)=∑k=15s(user⁡,k)k\text {score}(u \text {ser})=\sum_{k=1}^{5} \frac{s(u \operatorname{ser}, k)}{k} score(user)=k=15ks(user,k)

假如article1就是真实的用户点击文章,也就是article1命中, 则s(user1,1)=1, s(user1,2-4)都是0, 如果article2是用户点击的文章, 则s(user,2)=1/2,s(user,1,3,4,5)都是0。也就是score(user)=命中第几条的倒数。如果都没中, 则score(user1)=0。 这个是合理的, 因为我们希望的就是命中的结果尽量靠前, 而此时分数正好比较高。

赛题理解

根据赛题简介,我们首先要明确我们此次比赛的目标: 根据用户历史浏览点击新闻的数据信息预测用户最后一次点击的新闻文章。从这个目标上看, 会发现此次比赛和我们之前遇到的普通的结构化比赛不太一样, 主要有两点:

  • 首先是目标上, 要预测最后一次点击的新闻文章,也就是我们给用户推荐的是新闻文章, 并不是像之前那种预测一个数或者预测数据哪一类那样的问题

  • 数据上, 通过给出的数据我们会发现, 这种数据也不是我们之前遇到的那种特征+标签的数据,而是基于了真实的业务场景, 拿到的用户的点击日志

所以拿到这个题目,我们的思考方向就是结合我们的目标,把该预测问题转成一个监督学习的问题(特征+标签),然后我们才能进行ML,DL等建模预测。那么我们自然而然的就应该在心里会有这么几个问题:如何转成一个监督学习问题呢? 转成一个什么样的监督学习问题呢? 我们能利用的特征又有哪些呢? 又有哪些模型可以尝试呢? 此次面对数万级别的文章推荐,我们又有哪些策略呢?

当然这些问题不会在我们刚看到赛题之后就一下出来答案, 但是只要有了问题之后, 我们就能想办法解决问题了, 比如上面的第二个问题,转成一个什么样的监督学习问题?

由于我们是预测用户最后一次点击的新闻文章,从36万篇文章中预测某一篇的话我们首先可能会想到这可能是一个多分类的问题(36万类里面选1), 但是如此庞大的分类问题, 我们做起来可能比较困难, 那么能不能转化一下? 既然是要预测最后一次点击的文章, 那么如果我们能预测出某个用户最后一次对于某一篇文章会进行点击的概率, 是不是就间接性的解决了这个问题呢?

概率最大的那篇文章不就是用户最后一次可能点击的新闻文章吗? 这样就把原问题变成了一个点击率预测的问题(用户, 文章) --> 点击的概率(软分类), 而这个问题, 就是我们所熟悉的监督学习领域分类问题了, 这样我们后面建模的时候, 对于模型的选择就基本上有大致方向了,比如最简单的逻辑回归模型。

这样, 我们对于该赛题的解决方案应该有了一个大致的解决思路,要先转成一个分类问题来做, 而分类的标签就是用户是否会点击某篇文章,分类问题的特征中会有用户和文章,我们要训练一个分类模型, 对某用户最后一次点击某篇文章的概率进行预测。 那么又会有几个问题:如何转成监督学习问题? 训练集和测试集怎么制作? 我们又能利用哪些特征? 我们又可以尝试哪些模型? 面对36万篇文章, 20多万用户的推荐, 我们又有哪些策略来缩减问题的规模?如何进行最后的预测?

接下来是Baseline

导包

# import packages
import time, math, os
from tqdm import tqdm
import gc
import pickle
import random
from datetime import datetime
from operator import itemgetter
import numpy as np
import pandas as pd
import warnings
from collections import defaultdict
warnings.filterwarnings('ignore')
data_path = './datawhale/lr-news/dataset/'
save_path = './datawhale/lr-news/result/'

df节省内存函数-常用

# 节约内存的一个标配函数
def reduce_mem(df):starttime = time.time()numerics = ['int16', 'int32', 'int64', 'float16', 'float32', 'float64']start_mem = df.memory_usage().sum() / 1024**2for col in df.columns:col_type = df[col].dtypesif col_type in numerics:c_min = df[col].min()c_max = df[col].max()if pd.isnull(c_min) or pd.isnull(c_max):continueif str(col_type)[:3] == 'int':if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max:df[col] = df[col].astype(np.int8)elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max:df[col] = df[col].astype(np.int16)elif c_min > np.iinfo(np.int32).min and c_max < np.iinfo(np.int32).max:df[col] = df[col].astype(np.int32)elif c_min > np.iinfo(np.int64).min and c_max < np.iinfo(np.int64).max:df[col] = df[col].astype(np.int64)else:if c_min > np.finfo(np.float16).min and c_max < np.finfo(np.float16).max:df[col] = df[col].astype(np.float16)elif c_min > np.finfo(np.float32).min and c_max < np.finfo(np.float32).max:df[col] = df[col].astype(np.float32)else:df[col] = df[col].astype(np.float64)end_mem = df.memory_usage().sum() / 1024**2print('-- Mem. usage decreased to {:5.2f} Mb ({:.1f}% reduction),time spend:{:2.2f} min'.format(end_mem,100*(start_mem-end_mem)/start_mem,(time.time()-starttime)/60))return df

读取采样或全量数据

# debug模式:从训练集中划出一部分数据来调试代码
def get_all_click_sample(data_path, sample_nums=10000):"""训练集中采样一部分数据调试data_path: 原数据的存储路径sample_nums: 采样数目(这里由于机器的内存限制,可以采样用户做)"""all_click = pd.read_csv(data_path + 'train_click_log.csv')all_user_ids = all_click.user_id.unique()sample_user_ids = np.random.choice(all_user_ids, size=sample_nums, replace=False) all_click = all_click[all_click['user_id'].isin(sample_user_ids)]all_click = all_click.drop_duplicates((['user_id', 'click_article_id', 'click_timestamp']))return all_click# 读取点击数据,这里分成线上和线下,如果是为了获取线上提交结果应该讲测试集中的点击数据合并到总的数据中
# 如果是为了线下验证模型的有效性或者特征的有效性,可以只使用训练集
def get_all_click_df(data_path='./datawhale/lr-news/dataset/', offline=True):if offline:all_click = pd.read_csv(data_path + 'train_click_log.csv')else:trn_click = pd.read_csv(data_path + 'train_click_log.csv')tst_click = pd.read_csv(data_path + 'testA_click_log.csv')all_click = trn_click.append(tst_click)all_click = all_click.drop_duplicates((['user_id', 'click_article_id', 'click_timestamp']))return all_click
# 全量训练集
all_click_df = get_all_click_df(offline=False)
all_click_df


获取 用户 - 文章 - 点击时间字典

# 根据点击时间获取用户的点击文章序列   {user1: {item1: time1, item2: time2..}...}
def get_user_item_time(click_df):click_df = click_df.sort_values('click_timestamp')def make_item_time_pair(df):return list(zip(df['click_article_id'], df['click_timestamp']))user_item_time_df = click_df.groupby('user_id')['click_article_id', 'click_timestamp'].apply(lambda x: make_item_time_pair(x))\.reset_index().rename(columns={0: 'item_time_list'})user_item_time_dict = dict(zip(user_item_time_df['user_id'], user_item_time_df['item_time_list']))return user_item_time_dict

获取点击最多的topk个文章

# 获取近期点击最多的文章
def get_item_topk_click(click_df, k):topk_click = click_df['click_article_id'].value_counts().index[:k]return topk_click

itemcf的物品相似度计算

def itemcf_sim(df):"""文章与文章之间的相似性矩阵计算:param df: 数据表:item_created_time_dict:  文章创建时间的字典return : 文章与文章的相似性矩阵思路: 基于物品的协同过滤(详细请参考上一期推荐系统基础的组队学习), 在多路召回部分会加上关联规则的召回策略"""user_item_time_dict = get_user_item_time(df)# 计算物品相似度i2i_sim = {}item_cnt = defaultdict(int)for user, item_time_list in tqdm(user_item_time_dict.items()):# 在基于商品的协同过滤优化的时候可以考虑时间因素for i, i_click_time in item_time_list:item_cnt[i] += 1i2i_sim.setdefault(i, {})for j, j_click_time in item_time_list:if(i == j):continuei2i_sim[i].setdefault(j, 0)i2i_sim[i][j] += 1 / math.log(len(item_time_list) + 1)i2i_sim_ = i2i_sim.copy()for i, related_items in i2i_sim.items():for j, wij in related_items.items():i2i_sim_[i][j] = wij / math.sqrt(item_cnt[i] * item_cnt[j])# 将得到的相似性矩阵保存到本地pickle.dump(i2i_sim_, open(save_path + 'itemcf_i2i_sim.pkl', 'wb'))return i2i_sim_
i2i_sim = itemcf_sim(all_click_df)
i2i_sim


itemcf 的文章推荐

# 基于商品的召回i2i
def item_based_recommend(user_id, user_item_time_dict, i2i_sim, sim_item_topk, recall_item_num, item_topk_click):"""基于文章协同过滤的召回:param user_id: 用户id:param user_item_time_dict: 字典, 根据点击时间获取用户的点击文章序列   {user1: {item1: time1, item2: time2..}...}:param i2i_sim: 字典,文章相似性矩阵:param sim_item_topk: 整数, 选择与当前文章最相似的前k篇文章:param recall_item_num: 整数, 最后的召回文章数量:param item_topk_click: 列表,点击次数最多的文章列表,用户召回补全        return: 召回的文章列表 {item1:score1, item2: score2...}注意: 基于物品的协同过滤(详细请参考上一期推荐系统基础的组队学习), 在多路召回部分会加上关联规则的召回策略"""# 获取用户历史交互的文章user_hist_items = user_item_time_dict[user_id]item_rank = {}for loc, (i, click_time) in enumerate(user_hist_items):for j, wij in sorted(i2i_sim[i].items(), key=lambda x: x[1], reverse=True)[:sim_item_topk]:if j in user_hist_items:continueitem_rank.setdefault(j, 0)item_rank[j] +=  wij# 不足10个,用热门商品补全if len(item_rank) < recall_item_num:for i, item in enumerate(item_topk_click):if item in item_rank.items(): # 填充的item应该不在原来的列表中continueitem_rank[item] = - i - 100 # 随便给个负数就行if len(item_rank) == recall_item_num:breakitem_rank = sorted(item_rank.items(), key=lambda x: x[1], reverse=True)[:recall_item_num]return item_rank

给每个用户根据物品的协同过滤推荐文章

# 定义
import collections
user_recall_items_dict = collections.defaultdict(dict)# 获取 用户 - 文章 - 点击时间的字典
user_item_time_dict = get_user_item_time(all_click_df)# 去取文章相似度
i2i_sim = pickle.load(open(save_path + 'itemcf_i2i_sim.pkl', 'rb'))# 相似文章的数量
sim_item_topk = 10# 召回文章数量
recall_item_num = 10# 用户热度补全
item_topk_click = get_item_topk_click(all_click_df, k=50)for user in tqdm(all_click_df['user_id'].unique()):user_recall_items_dict[user] = item_based_recommend(user, user_item_time_dict, i2i_sim, sim_item_topk, recall_item_num, item_topk_click)

召回字典转换成df

# 将字典的形式转换成df
user_item_score_list = []for user, items in tqdm(user_recall_items_dict.items()):for item, score in items:user_item_score_list.append([user, item, score])recall_df = pd.DataFrame(user_item_score_list, columns=['user_id', 'click_article_id', 'pred_score'])

生成提交文件

生成提交文件

def submit(recall_df, topk=5, model_name=None):recall_df = recall_df.sort_values(by=['user_id', 'pred_score'])recall_df['rank'] = recall_df.groupby(['user_id'])['pred_score'].rank(ascending=False, method='first')# 判断是不是每个用户都有5篇文章及以上tmp = recall_df.groupby('user_id').apply(lambda x: x['rank'].max())assert tmp.min() >= topkdel recall_df['pred_score']submit = recall_df[recall_df['rank'] <= topk].set_index(['user_id', 'rank']).unstack(-1).reset_index()submit.columns = [int(col) if isinstance(col, int) else col for col in submit.columns.droplevel(0)]# 按照提交格式定义列名submit = submit.rename(columns={'': 'user_id', 1: 'article_1', 2: 'article_2', 3: 'article_3', 4: 'article_4', 5: 'article_5'})save_name = save_path + model_name + '_' + datetime.today().strftime('%m-%d') + '.csv'submit.to_csv(save_name, index=False, header=True)
# 获取测试集
tst_click = pd.read_csv(data_path + 'testA_click_log.csv')
tst_users = tst_click['user_id'].unique()# 从所有的召回数据中将测试集中的用户选出来
tst_recall = recall_df[recall_df['user_id'].isin(tst_users)]# 生成提交文件
submit(tst_recall, topk=5, model_name='itemcf_baseline')

总结:

理解评估指标
评估指标是检验我们提出的方法,我们给出结果好坏的标准,只有正确的理解了评估指标,我们才能进行更好的训练模型,更好的进行预测。此外,很多情况下,线上验证是有一定的时间和次数限制的,所以在比赛中构建一个合理的本地的验证集和验证的评价指标是很关键的步骤,能有效的节省很多时间。 不同的指标对于同样的预测结果是具有误差敏感的差异性的所以不同的评价指标会影响后续一些预测的侧重点。

有了赛题理解之后,我们该做什么?

在对于赛题有了一定的了解后,分析清楚了问题的类型性质和对于数据理解 的这一基础上,我们可以梳理一个解决赛题的一个大题思路和框架

我们至少要有一些相应的理解分析,比如这题的难点可能在哪里,关键点可能在哪里,哪些地方可以挖掘更好的特征.

用什么样得线下验证方式更为稳定,出现了过拟合或者其他问题,估摸可以用什么方法去解决这些问题

最后推荐大家一直关注论坛,多看其他人的想法:
datawhale: http://www.datawhale.club/
零基础入门推荐系统 - 新闻推荐:https://tianchi.aliyun.com/competition/entrance/531842/forum

天池-新闻推荐-Baseline相关推荐

  1. 天池-新闻推荐-多路召回

    前言读取数据 此次比赛是新闻推荐场景下的用户行为预测挑战赛, 是Datawhale与天池联合举办,该赛题是以新闻APP中的新闻推荐为背景, 目的是要求我们根据用户历史浏览点击新闻文章的数据信息预测用户 ...

  2. 天池-新闻推荐-数据分析

    前言读取数据 此次比赛是新闻推荐场景下的用户行为预测挑战赛, 是Datawhale与天池联合举办,该赛题是以新闻APP中的新闻推荐为背景, 目的是要求我们根据用户历史浏览点击新闻文章的数据信息预测用户 ...

  3. 推荐系统入门(六):新闻推荐实践1(附代码)

    推荐系统入门(六):新闻推荐实践1 目录 推荐系统入门(六):新闻推荐实践1 前言 赛题简介 数据概况 评价方式理解 Baseline 总结 参考资料 前言 相关系列笔记: 推荐系统入门(一):概述 ...

  4. 推荐系统入门(七):新闻推荐实践2(附代码)

    推荐系统入门(七):新闻推荐实践2(附代码) 目录 推荐系统入门(七):新闻推荐实践2(附代码) 引言 数据收集 数据存储 数据分析 实战 总结 参考资料 引言 相关系列笔记: 推荐系统入门(一):概 ...

  5. 【数据挖掘】天池挑战赛 新闻推荐

    比赛网址:https://tianchi.aliyun.com/competition/entrance/531842/introduction 项目源码:Github 一.项目知识点 数据预处理: ...

  6. AI比赛-推荐系统(一)-新闻推荐03:多路召回【用不同策略分别召回部分候选集,然后把候选集混在一起供后续排序模型使用】【①、YoutubeDNN双塔召回;②、基于物品召回;③、基于用户召回】【天池】

    所谓的"多路召回"策略,就是指采用不同的策略.特征或简单模型,分别召回一部分候选集,然后把候选集混合在一起供后续排序模型使用,可以明显的看出,"多路召回策略"是 ...

  7. 新闻推荐笔记(2):Embedding-based News Recommendation for Millions of Users

    面向数百万用户的嵌入式新闻推荐 雅虎 KDD2017 摘要 基于ID的方法,如协同过滤和低阶分解,以推荐著称,但不适合用于新闻推荐,因为候选文章很快过期,并在短时间内被新的文章替换. 信息检索中经常使 ...

  8. 零基础入门推荐系统(新闻推荐)

    零基础入门推荐系统(新闻推荐) 比赛介绍 本次新人赛是Datawhale与天池联合发起的零基础入门系列赛事第五场 -- 零基础入门推荐系统之新闻推荐场景下的用户行为预测挑战赛. 赛题简介 此次比赛是新 ...

  9. 【日本雅虎新闻推荐】:Embedding-based News Recommendation for Millions of Users(附开源代码)

    本篇论文由日本雅虎团队发表于KDD 2017,利用深度学习方法将用户和文章进行embedding化,再进行推荐. 下载地址:http://dacemirror.sci-hub.tw/proceedin ...

最新文章

  1. 谈一谈UNet图像分割
  2. 《机器学习》周志华-CH1 绪论
  3. Python开发工程师必知十大机器学习库
  4. TCP释放连接的四次挥手过程
  5. 东南大学校内智能车竞赛
  6. 4.vuex学习之getters、mapGetters
  7. 软件工程概论作业01
  8. sturct stat 结构体中 st_mode 的含义
  9. python如何设置窗口保持显示_如何保持Python脚本输出窗口打开?
  10. 让我们的linux的shell命令待颜色
  11. 大数据分析过程中包含哪些技术
  12. 几个知识蒸馏相关的BERT变体
  13. 阶段5 3.微服务项目【学成在线】_day02 CMS前端开发_03-vuejs研究-vuejs基础-入门程序...
  14. 【幅频均衡带通滤波器】基于FPGA的幅频均衡带通滤波器的设计
  15. keymaker注册机之内在注册机教程
  16. 仿360加速球。(实现内存释放)
  17. 京东发布《未来科技趋势白皮书》,101页详解5大关键技术(附PDF下载)
  18. 登录时记住密码的实现
  19. STL 堆 鱼塘钓鱼
  20. 华为每天给武汉员工发2千块,任正非:我最擅长的事,就是分钱!

热门文章

  1. 界面发布2019中国最富1000人榜:凛冬望春,马云问鼎中国首富
  2. Android sim卡清空,Android手机卡了?用这几种方法变回来...
  3. mysql左右连接举例_MYSQL 连接举例
  4. 关于EFCore线程内唯一
  5. 【JAVA 第五章 】课后习题 Vector类的 初使用
  6. 【JAVA 第四章 流程控制语句】课后习题 二维坐标距离 三角形判断等
  7. C#LeetCode刷题之#104-二叉树的最大深度​​​​​​​(Maximum Depth of Binary Tree)
  8. golang import中的.和_的使用,import的几种方式点,下划线,别名
  9. mysql语句中变量 c#_C#基础知识-您的第一个C#程序,类型和变量以及流控制语句...
  10. falcon框架_如何使用Falcon构建RESTful API