挑战任务
在代码世界中遨游是件妙不可言的故事,给开发者推荐新项目、新用户、新世界,有助于增强开源精神,提高开发者乐趣。本关源于国内知名开源社区的一个真实的业务需求,需要基于“绿色计算产业联盟”和国内某知名开源技术平台真实开发者及开发者活动数据进行兴趣标签的推荐。
本关任务:
利用已有的同现标签数据以及给出的20
位开发者技能标签数据,推荐兴趣标签给这20
位开发者。
其中,同现标签指的是:“共同出现的标签”。比如,某技术问答帖子的标签为<java>,<spring>,<mybatis>
,这三个标签在该帖子的标签集合中共同出现,则称其为同现标签。
推荐标签指的是:“根据同现标签的数据和开发者自己的技能标签来推荐给该开发者的标签”,比如,某位开发者的技能标签为<java>,<spring>
,根据同现标签数据,可以推荐<mybatis>
给开发者。
数据说明
为了完成本次挑战,你需要处理两种类型的数据,第一:同现标签数据,第二:开发者技能标签数据,根据提供的两种数据生成推荐标签数据,并且存放在文件中,关于这三种数据的说明如下:
我们已经将同现标签存放在tag_cooccurrence.csv
文件中了,文件路径为:src/step1/tag_cooccurrence.csv
,该文件数据结构如下:
id | tags |
---|---|
1 | <objective-c>,<app>,<iphone>,<ios> |
2 | <java>,<jfinal>,<数据库> |
字段id
为一个自增的整数,增量为1
,除了唯一标识这一行的数据外没有其他含义,tags
表示同现标签列表,其中包含多个同现标签。
我们已经将开发者标签数据存放在user_tag.csv
文件中了,文件路径为src/step1/user_tag.csv
,文件数据结构如下:
user_id | origin_tags | tag_value |
---|---|---|
1 | <java>,<spring>,<websocket>,<mybatis>,<redis> | [1.0, 0.851, 0.359, 0.336, 0.85] |
2 | <c#>,<csharp>,<shell>,<html>,<css> | [1.0, 0.929, 0.743, 0.74, 0.722] |
user_id
表示开发者的id
,origin_tags
表示用户的技能标签集合,tag_value
表示用户技能标签对应的权重,权重你可以简单的理解为开发者对于这个技能的熟练度或者关注程度,一个技能标签的权重值越高(最大值为1.0),则表示该开发者对该技能越关注。在推荐时就应该优先推荐权重更高标签的相关标签。
该文件只有20
条数据,即你只需要对20
名开发者做标签推荐。
推荐标签数就是,你要根据同现标签和开发者标签数据生成的数据,也是本次挑战要提交的数据,你必须将计算得出的推荐标签数据存放在文件中,并且命名为:user_recommand.csv
,需要存放在src/step1/
下才可以评测,即完整路径为:src/step1/user_recommand.csv
。
其中存储格式举例如下:
id | recommand_tags |
---|---|
1 | <mvn>,<springmvc>,<numpy>,<pandas>,<Django> |
2 | <monogodb>,<mysql>,<gradle> |
... | ... |
20 | <tensorflow> |
- 字段
id
为一个自增的整形变量,增量为1
; - 字段
recommad_tags
为推荐给用户的标签列表,其中每个标签以英文逗号分隔,注意每个标签用尖括号修饰,即java标签是<java>
,而不是java
; - 每一行数据代表给对应用户推荐的标签,如:上述表格中给用户
id
为1
的用户推荐的标签为:<mvn>,<springmvc>,<numpy>,<pandas>,<Django>
; - 给用户推荐的标签可以是任意数量。
编程要求
请补充完善右侧代码区域中的getRecommand()
函数,构建标签推荐系统,实现根据开发者已有标签,完成对20
位开发者的标签推荐,每个开发者的标准测试推荐标签为10
个,你的推荐标签可以是任意个,参考步骤如下:
- 读取
src/step1/tag_cooccurrence.csv
文件和src/step1/user_tag.csv
文件; - 根据开发者标签和权重值来处理数据;
- 生成
src/step1/user_recommand.csv
推荐标签数据文件; - 提交评测。
提示:
你可以通过如下链接下载本关涉及到的数据文件:
https://www.educoder.net/attachments/download/203519/csvFiles.zip
推荐用记事本或者编辑器打开csv
文件,用excel
打开可能会显示乱码。
你可以持续优化以获得更高的分数。
评测方法
本关得分为:
其中N=20
,表示共有20
个开发者测试记录;Auc_i
表示对每条记录标签推荐的准确率;Recall_i
表示对每条记录标签推荐的召回率。下面为具体的准确率和召回率的描述。
参赛选手根据开发者i
已有的标签列表{t1,t2,..,t5}
,推荐n
个标签形成集合Ri
R_i=\left \{ r_1,r_2,...,r_n \right \}Ri={r1,r2,...,rn}
Ti
为测试集中开发者i
的10
个标签组成的集合。
T_i=\left \{ t_6,t_7,...,t_{15} \right \}Ti={t6,t7,...,t15}
则开发者i
的标签推荐结果准确率Auc_i
为:
Auc_i=\frac{\left | R_i\cap T_i \right |}{\left | R_i \right |}Auci=∣Ri∣∣Ri∩Ti∣
召回率为:
Recall=\frac{\left | R_i\cap T_i \right |}{\left | T_i \right |}Recall=∣Ti∣∣Ri∩Ti∣
下面对某条开发者记录得分规则进行举例说明。
若参赛选手算出的某一开发者的推荐标签列表R
长度为11
,其中有3个标签与开发者对应T
中的标签一致。则该选手针对该开发者的推荐准确率为3/11
,召回率为3/10
,因此针对该条记录选手得分为:
\frac{2*3/10*3/11}{3/11+3/10}3/11+3/102∗3/10∗3/11
本题在于针对每个开发者标签挖掘其相关的标签进行推荐,可以尝试利用关联规则算法挖掘标签之间的关联规则,将其推荐给目标开发者,在此基础上建议采用FPgrowth
挖掘频繁项集加快计算速度,得到关联规则后,针对每个标签关联的其他标签的置信度与该标签对应的权重值计算关联标签的权重值,得到最终的推荐列表。
开始挑战吧,祝你成功!
学了很久的人工智能 非常想建个字典算关联规则 but感觉好麻烦就暴力了一下 居然有84
# coding=utf-8
##################第三方库及数据说明#################
# 本关可使用pandas、numpy
# 同现标签数据:src/step1/tag_cooccurrence.csv
# 开发者标签数据:src/step1/user_tag.csv
# 推荐结果文件:src/step1/user_recommand.csv
#####################################################
import pandas as pd
def create_init_set(data_set):
"""
Function:
计算项集在数据集的出现次数
Parameters:
data_set - 数据集
Returns:
ret_dict - 包含出现次数的项集字典
Modify:
2018-12-27
"""
ret_dict = {}
for trans in data_set:
ret_dict[frozenset(trans)] = 1
return ret_dict
def update_tree(items, in_tree, header_table, count):
"""
Function:
更新树节点,让FP树生长
Parameters:
items - 项集
in_tree - 当前FP树
header_table - 头指针表
count - 次数
Returns:
无
Modify:
2018-12-27
"""
# 判断排序后列表的第一个元素是否已经是根节点的子节点
if items[0] in in_tree.children:
# 添加出现次数
# children = {}
in_tree.children[items[0]].inc(count)
else:
# 创建根节点的子节点
in_tree.children[items[0]] = TreeNode(items[0], count, in_tree)
# 如果该元素的后继节点不存在,则直接加入。如果有后继节点,则遍历链表尾部将其加入
if header_table[items[0]][1] == None:
header_table[items[0]][1] = in_tree.children[items[0]]
else:
update_header(header_table[items[0]][1], in_tree.children[items[0]])
# 列表元素长度大于1,递归调用更新FP树函数
if len(items) > 1:
update_tree(items[1::], in_tree.children[items[0]], header_table, count)
def update_header(node_to_test, target_node):
"""
Function:
更新头指针表的节点链接
Parameters:
node_to_test - 遍历节点
target_node - 目标节点
Returns:
无
Modify:
2018-12-27
"""
# 遍历到链表尾节点
while (node_to_test.node_link != None):
node_to_test = node_to_test.node_link
# 将刚添加的树节点加入链表的尾部
node_to_test.node_link = target_node
def create_tree(data_set, min_sup=1):
"""
Function:
遍历数据集两次构建FP树
Parameters:
data_set - 包含项集出现次数的数据集字典
min_sup - 最小支持度
Returns:
ret_tree - FP树
hearder_table - 头指针表
Modify:
2018-12-27
"""
hearder_table = {}
# 第一次遍历数据集,获取单个元素的次数
for trans in data_set:
for item in trans:
hearder_table[item] = hearder_table.get(item, 0) + data_set[trans]
# 去除不满足最小支持度的单个元素
for k in list(hearder_table.keys()):
if hearder_table[k] < min_sup:
del (hearder_table[k])
freq_item_set = set(hearder_table.keys())
# 无频繁项就直接返回
if len(freq_item_set) == 0:
return None, None
# 扩展头指针表,添加指向每种类型第一个元素的指针(节点链接)
for k in hearder_table:
hearder_table[k] = [hearder_table[k], None]
# 创建根节点
ret_tree = TreeNode('Null Set', 1, None)
# 第二次遍历数据集,构建FP树
for tran_set, count in data_set.items():
# tran_set: frozenset({'h', 'p', 'z', 'j', 'r'})
# count: 1
local_d = {}
# 如果单个元素是频繁项,则加入localD列表
for item in tran_set:
if item in freq_item_set:
# hearder_table:{'b': [3, None]}
local_d[item] = hearder_table[item][0]
# localD: {'r': 3, 'j': 1, 'z': 5, 'h': 1, 'p': 2}
if len(local_d) > 0:
# 元素按出现次数排序
ordered_items = [v[0] for v in sorted(local_d.items(), key=lambda p: p[1], reverse=True)]
# 更新FP树,让FP树生长
update_tree(ordered_items, ret_tree, hearder_table, count)
return ret_tree, hearder_table
class TreeNode:
# name:节点元素名称,在构造时初始化为给定值
# count:出现次数,在构造时初始化为给定值
# node_link:指向下一个相似节点的指针,默认为None
# parent:指向父节点的指针,在构造时初始化为给定值
# children:指向子节点的字典,以子节点的元素名称为键,指向子节点的指针为值,初始化为空字典
def __init__(self, name_value, num_occur, parent_node):
self.name = name_value
self.count = num_occur
self.node_link = None
self.parent = parent_node
self.children = {}
def inc(self, num_occur):
# 增加节点出现次数
self.count += num_occur
def disp(self, ind=1):
# 用于将树以文本形式显示
print(' ' * ind, self.name, ' ', self.count)
for child in self.children.values():
child.disp(ind + 1)
def ascend_tree(leaf_node, prefix_path):
"""
Function:
根据当前节点向前追溯至根节点,记录前缀路径
Parameters:
leaf_node - 给定元素项节点
prefix_path - 前缀路径列表
Returns:
无
Modify:
2019-1-6
"""
# 如果节点有父节点,则将当前节点添加至前缀路径中,之后再递归向上追溯
if leaf_node.parent != None:
prefix_path.append(leaf_node.name)
ascend_tree(leaf_node.parent, prefix_path)
def find_prefix_path(base_pat, tree_node):
"""
Function:
发现以给定元素项结尾的所有前缀路径
Parameters:
base_pat - 元素项
tree_node - 需遍历节点
Returns:
cond_pats - 所有条件模式基字典
Modify:
2019-1-6
"""
# 所有条件模式基字典
cond_pats = {}
# 遍历该节点的整个链表节点,记录每个节点的前缀路径,并将其添加至条件模式基当中
while tree_node != None:
prefix_path = []
ascend_tree(tree_node, prefix_path)
# 因为该节点也被加进了路径当中,所以需要路径的长度大于1
if len(prefix_path) > 1:
# 如果有前缀路径,则将前缀路径加入条件模式基集合中,并且将该元素在该前缀路径中出现的次数也添加进去
cond_pats[frozenset(prefix_path[1:])] = tree_node.count
# 当前节点的条件模式基查找完毕后,继续查找头指针链表中下一个节点的条件模式基
tree_node = tree_node.node_link
return cond_pats
def mine_tree(in_tree, header_table, min_sup, pre_fix, freq_item_list):
"""
Function:
创建条件模式树
Parameters:
in_tree - FP树
header_table - 头指针表
min_sup - 最小支持度
pre_fix - 上一次递归的频繁项集合的前缀
freq_item_list - 频繁项集列表
Returns:
无
Modify:
2019-1-6
"""
# 对头指针表中的元素项按照其出现频率从小到大进行排序
big_l = [v[0] for v in sorted(header_table.items(), key=lambda p:p[1][0])]
# 遍历单元素频繁集
for base_pat in big_l:
new_freq_set = pre_fix.copy()
new_freq_set.add(base_pat)
freq_item_list.append(new_freq_set)
# 获得该元素的所有条件模式基,相当于一个事务集合
cond_patt_bases = find_prefix_path(base_pat, header_table[base_pat][1])
# 根据所有条件模式基集合来构建条件模式树
my_cond_tree, my_head = create_tree(cond_patt_bases, min_sup)
# 如果条件模式树的头指针表不空(每次建树时对元素支持度有要求
# 如果小于支持度则该元素不参与建树过程,所以在建树时,条件模式基中的元素会越来越少,最后会是空树),则递归建树
if my_head != None:
print('conditional tree for:', new_freq_set)
my_cond_tree.disp(1)
mine_tree(my_cond_tree, my_head, min_sup, new_freq_set, freq_item_list)
def func():
df_train=pd.read_csv('./tag_cooccurrence.csv')
df_test=pd.read_csv('./user_tag.csv')
#print(df_train['id'])
#print(df_train['tags'])
train_data=[]
for tags in df_train['tags']:
train_data.append(tags.split(','))
init_data = create_init_set(train_data)
my_fp_tree, my_header_tab = create_tree(init_data, 3)
ans=[]
for tags,tag_value in zip(df_test['origin_tags'],df_test['tag_value']):
tag_value =eval(tag_value)
tag_dict =dict(zip(tags.split(","),tag_value))
tag_pair=sorted(tag_dict.items(),key = lambda x:x[1],reverse = True)
ans_dict={}
ans_list=[]
for pair in tag_pair:
tag,value=pair
try:
cond_pats_3 = find_prefix_path(tag, my_header_tab[tag][1])
except:
continue
for k, v in cond_pats_3.items():
if(v<=3):
continue
for ddict in set(k):
if(ddict in tag_dict.keys()):
continue
if(ddict not in ans_dict):
ans_dict[ddict] = v*value
else:
ans_dict[ddict] += v*value
ans_pair = sorted(ans_dict.items(), key=lambda x: x[1], reverse=True)
for pair in ans_pair:
a,_=pair
ans_list.append(a)
print(",".join(ans_list[:10]))
ans.append(",".join(ans_list[:10]))
id=range(1,21)
data={}
data['id']=id
data['recommand_tags']=ans
data_df = pd.DataFrame(data)
#print(data_df)
data_df.to_csv('./user_recommand.csv')
#func()
# coding=utf-8
##################第三方库及数据说明#################
# 本关可使用pandas、numpy
# 同现标签数据:src/step1/tag_cooccurrence.csv
# 开发者标签数据:src/step1/user_tag.csv
# 推荐结果文件:src/step1/user_recommand.csv
#####################################################
import pandas as pd
def func():
df_train=pd.read_csv('src/step1/tag_cooccurrence.csv')
df_test=pd.read_csv('src/step1/user_tag.csv')
kk={}
#print(df_train['id'])
#print(df_train['tags'])
i=0
for ii in df_train['id']:
kk[i]=df_train['tags'][i].replace("'","")
#print(kk[i])
kk[i]=kk[i].split(',')
i+=1
#print(kk)
i=0
pp=[]
for ii in df_test['user_id']:
a=df_test['origin_tags'][i].replace("'","").split(',')
b=df_test['tag_value'][i].strip('[').strip(']').split(',')
b=[float(x) for x in b]
pro={}
for c,d in zip(a,b):
pro[c]=d
pro=sorted(pro.items(),key = lambda x:x[1],reverse = True)
pp.append(pro)
i+=1
ans=[]
for x in pp:
zz=[]
for xx in x:
tag,pro=xx
#print(tag+' '+str(pro))
for i in range(0,len(kk)):
if(tag in kk[i]):
zz.extend(kk[i])
zz = list(set(zz))
if(len(zz)>10):
break
zz=str(zz[:10]).strip('[').strip(']').replace("'","")
ans.append(zz)
id=range(1,21)
data={}
data['id']=id
data['recommand_tags']=ans
data_df = pd.DataFrame(data)
#print(data_df)
data_df.to_csv('src/step1/user_recommand.csv')
######################## BEGIN ######################
######################## END ########################