阿里云-零基础入门推荐系统 【Baseline】

文章目录

  • 赛题介绍
  • 评价方式理解
  • 赛题理解
  • 代码实战
    • 导包
    • df节省内存函数
    • 读取采样或全量数
    • 获取 用户 - 文章 - 点击时间字典
    • 获取点击最多的topk个文章
    • itemcf的物品相似度计算
    • itemcf 的文章推荐
    • 给每个用户根据物品的协同过滤推荐文章
    • 召回字典转换成df
    • 生成提交文件
    • 获取测试集
    • 从所有的召回数据中将测试集中的用户选出来
    • 生成提交文件
  • 学习过程


赛题介绍

该赛题是以新闻APP中的新闻推荐为背景, 目的是要求我们根据用户历史浏览点击新闻文章的数据信息预测用户未来的点击行为, 即用户的最后一次点击的新闻文章

评价方式理解

最后提交的格式是针对每个用户, 我们都会给出五篇文章的推荐结果,按照点击概率从前往后排序。 而真实的每个用户最后一次点击的文章只会有一篇的真实答案, 所以我们就看我们推荐的这五篇里面是否有命中真实答案的。比如对于user1来说, 我们的提交会是:

user1, article1, article2, article3, article4, article5.

评价指标的公式如下:
在这里插入图片描述

假如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等建模预测。

在这里插入图片描述

代码实战

已修改跑通代码协同过滤+热度召回

导包

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
import collections
warnings.filterwarnings('ignore')# data_path = './data_raw/'
data_path = '/data/temp/用户行为预测数据集/' # '/home/admin/jupyter/data/' # 天池平台路径
save_path = 'results/0213/' # '/home/admin/jupyter/temp_results/'  # 天池平台路径

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='./data_raw/', 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_clickprint("开始全量训练集")# 全量训练集
all_click_df = get_all_click_df(data_path, offline=False)print("结束全量训练集")

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

# 根据点击时间获取用户的点击文章序列   {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_cl

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_print("开始itemcf的物品相似度计算")i2i_sim = itemcf_sim(all_click_df)print("结束itemcf的物品相似度计算")

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]user_hist_items_ = {user_id for user_id, _ in user_hist_items}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

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

# 定义
user_recall_items_dict = collections.defaultdict(dict)print("开始获取 用户 - 文章 - 点击时间的字典")
# 获取 用户 - 文章 - 点击时间的字典
user_item_time_dict = get_user_item_time(all_click_df)
print("结束获取 用户 - 文章 - 点击时间的字典# 去取文章相似度
i2i_sim = pickle.load(open(save_path + 'itemcf_i2i_sim.pkl', 'rb'))# 相似文章的数量
sim_item_topk = 10# 召回文章数量
recall_item_num = 10print("开始用户热度补全")
# 用户热度补全
item_topk_click = get_item_topk_click(all_click_df, k=50)
print("结束用户热度补全")print("开始itemcf 的文章推荐")
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)
print("结束itemcf 的文章推荐")

召回字典转换成df

print("开始召回字典转换成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'])print("结束召回字典转换成df")

生成提交文件

# 生成提交文件
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)

获取测试集

print("开始获取测试集")
# 获取测试集
tst_click = pd.read_csv(data_path + 'testA_click_log.csv')
tst_users = tst_click['user_id'].unique()
print("结束获取测试集")

从所有的召回数据中将测试集中的用户选出来

print("开始从所有的召回数据中将测试集中的用户选出来")
# 从所有的召回数据中将测试集中的用户选出来
tst_recall = recall_df[recall_df['user_id'].isin(tst_users)]
print("结束从所有的召回数据中将测试集中的用户选出来")

生成提交文件

print("开始生成提交文件")
# 生成提交文件
submit(tst_recall, topk=5, model_name='itemcf_baseline')
print("结束生成提交文件")

学习过程

20年当时自身功底是比较零基础(会写些基础的Python[三个科学计算包]数据分析),一开始看这块其实挺懵的,不会就去问百度或其他人,当时遇见困难挺害怕的,但22后面开始力扣题【目前已刷好几轮,博客没写力扣文章之前,力扣排名靠前已刷有5遍左右,排名靠后刷3次左右,代码功底也在一步一步提升】不断地刷、遇见代码不懂的代码,也开始去打印print去理解,到后面问其他人的问题越来越少,个人自主学习、自主解决能力也得到了进一步增强。

比赛源自:阿里云天池大赛 - 零基础入门推荐系统 - 新闻推荐

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/736494.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mysql如何开启手动提交事务

在mysql中&#xff0c;有一个变量autocommit&#xff0c;表示自动提交&#xff0c;默认为1&#xff0c;表示开启自动提交。通过以下命令查询 select autocommit;当autocommit为1时&#xff0c;任何一条sql语句都是一个事务&#xff0c;执行完由mysql自动提交。如果想自己决定什…

python使用DEFLATE 算法decode(‘ascii‘)编码打包的自定义格式的压缩包并未生成

问题描述&#xff1a;打包都为报错且打包的文件大小为0 打包程序&#xff1a; import os import zlibdef compress_folder_to_custom_format(input_folder, output_filename):"""使用 DEFLATE 算法压缩文件夹下的所有文件&#xff0c;并保存为自定义文件包格式…

matlab 基操~

MATLAB基本操作 1. 对象定义 使用sym定义单个对象、使用syms定义多个对象 2. 使用limit求极限 $$ \lim_{v \rightarrow a} f(x) $$ limit(f,v,a) % 使用limit(f,v,a,left)可求左极限 3. 导数 使用diff(f,v,n)对$ f(v)v^{t-1} $求 $ n $ 阶导 $ \frac{d^nf}{d^nv} $&#xf…

智谱清华LongAlign发布:重塑NLP长文本处理

引言 随着大型语言模型&#xff08;LLMs&#xff09;的不断进化&#xff0c;我们现在能够处理的文本长度已经达到了前所未有的规模——从最初的几百个tokens到现在的128k tokens&#xff0c;相当于一本300页的书。这一进步为语义信息的提供、错误率的减少以及用户体验的提升打…

MySQL三种日志

一、undo log&#xff08;回滚日志&#xff09; 1.作用&#xff1a; &#xff08;1&#xff09;保证了事物的原子性 &#xff08;2&#xff09;通过read view和undo log实现mvcc多版本并发控制 2.在事务提交前&#xff0c;记录更新前的数据到undo log里&#xff0c;回滚的时候读…

Clickhouse: 随笔杂记

Clickhouse 文件缓存的使用 元数据缓存 1、Clickhouse启动的时候会加载所有表的元数据信息&#xff0c;这部分会缓存在内存里面。这部分的内存没有办法知道并且也不走MemoryTracker。 Mark数据缓存 2、Clickhouse会缓存表的mark信息在内存里, 使用LRU算法来控制。缓存大小通过…

java学习之路-数据类型与变量

目录 数据类型与变量 1. 字面常量 2. 数据类型 3. 变量 3.1 变量概念 3.2 整型变量 3.2.1 整型变量 3.2.2 长整型变量 3.2.3 短整型变量 3.2.4 字节型变量 3.3 浮点型变量 3.3.1 双精度浮点型 3.3.2 单精度浮点型 3.4 字符型变量 3.5布尔型变量 3.6 类型转换 …

苍穹外卖学习-----2024/03/010---修改套餐,套餐状态修改开发

修改套餐 4.1 需求分析和设计 产品原型&#xff1a; 接口设计&#xff08;共涉及到5个接口&#xff09;&#xff1a; 根据id查询套餐根据类型查询分类&#xff08;已完成&#xff09;根据分类id查询菜品&#xff08;已完成&#xff09;图片上传&#xff08;已完成&#xf…

Linux 地址空间

目录 一、程序地址空间 1、虚拟地址 Makefile新写法 2、进程地址空间分布 3、栈&堆 4、static修饰局部变量 5、字符串常量不可修改 6、虚拟地址与物理地址的联系 二、CPU读取程序全过程 1、形成可执行程序 2、生成虚拟地址 3、程序的启动 4、创建进程 5、地…

Python 学习——Python requests 库文档

目录 快速上手一、 发送请求二、 传递 URL 参数三、 响应内容3.1 文本相应内容3.2 二进制响应内容3.3 JSON 响应内容3.4 原始响应内容 四、 定制请求头五、 更加复杂的 POST 请求5.1 字典方式5.2 元组方式5.3 传递一个string5.4 JSON格式5.5 上传文件5.6 发送字符串为文件 六、…

OrangePiLinux连接小米手机使用adb显示“List of devices attached”的问题解决

参考文章adb连接不上手机&#xff0c;提示“List of devices attached” - 简书 (jianshu.com) adb解决报错error: no devices/emulators found error: cannot connect to daemon_adb.exe: no devices/emulators found-CSDN博客 error: no devices/emulators found解决办法-C…

Java三代日期类

文章目录 日期类第一代日期类第二代日期类第三代日期类LocalDateTime方法LocalDateTime格式化日期与时间戳的转换Date转换为时间戳时间戳转换为Date 日期类 在Java中&#xff0c;有三代日期类&#xff1a;java.util.Date、java.util.Calendar和java.time包下的日期类。这三代日…

【Redis】RedisTemplate序列化传输数据

使用自定义的序列化器 使用RedisTemplate默认的序列化器发送数据&#xff0c;会将key全都当成Object处理&#xff0c;从而按照对象的方式转成json格式发送到服务器&#xff0c;这样会导致两个问题。一是不方便阅读&#xff0c;二是会大大浪费内存。因此&#xff0c;建议自定义…

Linux之线程控制

目录 一、POSIX线程库 二、线程的创建 三、线程等待 四、线程终止 五、分离线程 六、线程ID&#xff1a;pthread_t 1、获取线程ID 2、pthread_t 七、线程局部存储&#xff1a;__thread 一、POSIX线程库 由于Linux下的线程并没有独立特有的结构&#xff0c;所以Linux并…

Qt设置右键菜单无效customContextMenuRequested(const QPoint pos)

问题代码&#xff1a; void MainWindow::onCustomContextMenuRequested(const QPoint &pos) {QTreeWidgetItem *item ui->treeWidget->itemAt(pos);if (item){QMenu menu(ui->treeWidget);TreeNodeType nodeType (TreeNodeType)item->data(0, Qt::UserRole …

LightDB24.1 oracle_fdw支持服务端GBK编码

功能介绍 oracle_fdw是一个PG的插件&#xff0c;用于连接oracle数据库&#xff0c;由于原生PG不支持服务端GBK编码&#xff0c;所以原生的oracle_fdw也不支持服务端GBK编码。在LightDB23.3中支持了服务端GBK编码&#xff0c;导致在GBK编码的数据库中使用oracle_fdw时报错。 Li…

python82-Python的函数高级内容之使用函数变量

Python的函数是“一等公民”&#xff0c;因此函数本身也是一个对象&#xff0c;函数既可用于赋值&#xff0c;也可用作其他函数的参数&#xff0c;还可作为其他函数的返回值。 使用函数变量 Python的函数也是一种值:所有函数都是function对象&#xff0c;这意味着可以把函数本…

es6的新特性

ECMAScript 6&#xff08;也称为 ES6 或 ECMAScript 2015&#xff09;是 JavaScript 的一个重要更新版本&#xff0c;引入了许多新的语法和功能&#xff0c;以提高开发效率和代码可读性。以下是 ES6 中一些比较常见和重要的新特性&#xff1a; let 和 const 声明&#xff1a; l…

一篇搞定mysql数据库基础

目录 一、MySQL具体的特点 1.关系型数据库&#xff08;RDBMS&#xff09;&#xff1a; 2.MySQL是一个“客户端-服务器”结构的程序 Q1:服务器能不能知道客户端什么时候发请求&#xff1f;&#xff1f; Q2:服务器是只给一个客户端提供服务吗&#xff1f;&#xff1f; 二、M…

LeetCode 80.删除有序数组中的重复项 II

目录标题 删除有序数组中的重复项 II题目解题思路实现代码代码讲解总结删除有序数组中的重复项 II 题目 解题思路 慢指针指向满足条件的数字的末尾,快指针遍历原数组。 并且用一个变量记录当前末尾数字出现了几次,防止超过两次。最后返回维护慢指针的结果+1即可。 实现代…