├── README.md ├── code ├── factor.py ├── latest_new.py ├── read_file.py ├── read_file1.py └── recommend.py └── 个性化新闻推荐解决方案文档.pdf /README.md: -------------------------------------------------------------------------------- 1 | 第二届全国大数据比赛-新闻推荐组 2 | ============== 3 | 比赛背景: 4 | 5 | 随着近年来互联网的飞速发展,个性化推荐已成为各大主流网站的一项必不可少服务。提供各类新闻的门户网站是互联网上的传统服务,但是与当今蓬勃发展的电子商务网站相比,新闻的个性化推荐服务水平仍存在较大差距。一个互联网用户可能不会在线购物,但是绝大部分的互联网用户都会在线阅读新闻。因此资讯类网站的用户覆盖面更广,如果能够更好的挖掘用户的潜在兴趣并进行相应的新闻推荐,就能够产生更大的社会和经济价值。初步研究发现,同一个用户浏览的不同新闻的内容之间会存在一定的相似性和关联,物理世界完全不相关的用户也有可能拥有类似的新闻浏览兴趣。此外,用户浏览新闻的兴趣也会随着时间变化,这给推荐系统带来了新的机会和挑战。因此,希望通过对带有时间标记的用户浏览行为和新闻文本内容进行分析,挖掘用户的新闻浏览模式和变化规律,设计及时准确的推荐系统预测用户未来可能感兴趣的新闻 6 | 7 | 任务: 8 | 9 | 参赛选手需要根据训练集中的浏览记录以及新闻的详细内容,尽可能多的预测出测试集中的数据,即预测每一个用户最后一次浏览的新闻编号。 10 | 11 | 数据集描述: 12 | 13 | 在本次竞赛中,我们从国内某著名财经新闻网站—财新网随机选取了10000名用户,并抽取了这10000名用户在2014年3月的所有新闻浏览记录,每条记录包括用户编号、新闻编号、浏览时间(精确到秒)以及新闻文本内容,其中用户编号已做匿名化处理,防止暴露用户隐私。 14 | 15 | 我的解决方案: 16 | 17 | 首先是根据基于用户的相似度来计算用户u的TopN最近邻的用户,然后基于TopN用户计算总的新闻的并集U,在新闻并集的基础上,采用核密度估计的方法,对每一篇新闻进行兴趣度计算,最后在兴趣度的基础上结合新闻时效性(流行度)的值,综合加权,最终得到对于每一个项目的评分值,根据评分值以及该用户u的新闻阅读能力来推荐TopK用户。 18 | 19 | 评价标准: 20 | 21 | F1值大小 22 | 23 | 程序组成部分: 24 | 25 | factor.py:计算老化系数 26 | 27 | latest_new.py:记录每个用户的最后浏览的时间,以及每个新闻的发布时间 28 | 29 | read_file.py:建立新闻、用户、时间映射矩阵 30 | 31 | read_file1.py:读取用户集合,用户 32 | 33 | recommend.py:程序主函数,根据解决方案,实现功能 34 | 35 | 关于算法的详细公式介绍在pdf文档中 36 | -------------------------------------------------------------------------------- /code/factor.py: -------------------------------------------------------------------------------- 1 | __author__ = 'FLY' 2 | #encoding:utf-8 3 | 4 | from read_file import retrieve_info 5 | import time 6 | import datetime 7 | import math 8 | from numpy import linalg as la 9 | import numpy 10 | import csv 11 | import string 12 | 13 | #转换发布时间为秒数 14 | def cal_time(t1): 15 | #t1='2014年03月08日12:31' 16 | t1=t1.strip() 17 | if t1.find('年')<0: 18 | return t1 19 | t1=t1.replace('年','-') 20 | t1=t1.replace('月','-') 21 | t1=t1.replace('日',' ') 22 | if t1.find(':')<0: 23 | t1=t1+'00:00' 24 | t1=t1+':00' 25 | s = t1 26 | d = datetime.datetime.strptime(s,"%Y-%m-%d %H:%M:%S") 27 | return time.mktime(d.timetuple()) 28 | 29 | 30 | def cal_ave_half_period(news_num_issue_time, news_logic_num_latest_read_time): 31 | """ 32 | 计算每条新闻的半衰期,并求其平均值 33 | """ 34 | ave_half_period = 0.0 35 | for i in (range(news_num_issue_time.__len__())): 36 | ave_half_period += (int(news_logic_num_latest_read_time[i]) - int(cal_time(news_num_issue_time[i]))) 37 | ave_half_period /= 2.0 38 | return ave_half_period 39 | 40 | #计算老化系数 41 | def cal_info_age_factor(ave_half_period): 42 | return -math.log(0.5)/ave_half_period 43 | 44 | 45 | 46 | if __name__ == '__main__': 47 | csvfile = file('csv_result_age.csv', 'wb') 48 | writer=csv.writer(csvfile) 49 | news_num_issue_time, news_logic_num_latest_read_time, user_num_logic_real, news_num_logic_real, \ 50 | user_logic_num_unrated_news_set, Rmn, user_logic_num_latest = retrieve_info() 51 | 52 | factor = cal_info_age_factor(cal_ave_half_period(news_num_issue_time, news_logic_num_latest_read_time))#求解老化系数 53 | print 'factor is: ', factor -------------------------------------------------------------------------------- /code/latest_new.py: -------------------------------------------------------------------------------- 1 | __author__ = 'LiGe' 2 | #encoding:utf-8 3 | #记录每个用户的最后浏览的时间,以及每个新闻的发布时间 4 | 5 | def map_time(): 6 | f=open('train_data.txt','r') 7 | datas=f.readlines() 8 | users=set() 9 | user_late_time=dict() 10 | #news_time=dict() 11 | for line in datas: 12 | line=line.strip() 13 | line=line.split('\t') 14 | if line[0] not in users: 15 | users.add(line[0]) 16 | user_late_time[line[0]]=line[2] 17 | return user_late_time 18 | 19 | def map_item_issue_time(): 20 | f=open('train_data.txt','r') 21 | datas=f.readlines() 22 | item=set() 23 | item_issue_time=dict() 24 | for line in datas: 25 | line=line.strip() 26 | line=line.split('\t') 27 | if line[1] not in item: 28 | item.add(line[1]) 29 | item_issue_time[line[1]]=line[5] 30 | return item_issue_time 31 | 32 | 33 | -------------------------------------------------------------------------------- /code/read_file.py: -------------------------------------------------------------------------------- 1 | __author__ = 'FLY' 2 | #encoding:utf-8 3 | import numpy as np 4 | 5 | def retrieve_info(): 6 | """ 7 | 从文件中提取出: 8 | 词典news_num_issue_time{新闻逻辑编号,发布时间}, 9 | 词典news_logic_num_latest_read_time{新闻逻辑编号,最晚阅读时间}, 10 | 词典news_num_logic_real{新闻逻辑编号,新闻实际编号}, 11 | 词典user_num_logic_real{用户逻辑编号,用户实际编号}, 12 | 词典user_logic_num_unrated_news_set{用户逻辑编号,用户未读新闻编号集合}, 13 | 词典user_logic_num_latest{用户逻辑编号,(最晚的新闻逻辑编号,最晚阅读时间戳)} 14 | 用户-新闻评分矩阵Rmn 15 | """ 16 | f = open('train_data.txt') 17 | news_num_issue_time = dict() 18 | news_logic_num_latest_read_time = dict() 19 | news_num_logic_real = dict() 20 | user_num_logic_real = dict() 21 | user_logic_num_latest = dict() 22 | user_logic_num_unrated_news_set = dict() 23 | users = dict() 24 | #{用户实际编号,用户逻辑编号} 25 | news = dict() 26 | #{新闻实际编号,新闻逻辑编号} 27 | news_data = f.readlines() 28 | i = 0 29 | j = 0 30 | for new in news_data: 31 | new = new.strip() 32 | if len(new) != 0: 33 | new = new.split('\t') 34 | # if users.setdefault(new[0], i) != i: 35 | # i += 1 36 | # if news.setdefault(new[1], j) != j: 37 | # j += 1 38 | if not users.has_key(new[0]): 39 | users[new[0]] = i 40 | i += 1 41 | if not news.has_key(new[1]): 42 | news[new[1]] = j 43 | j += 1 44 | 45 | print len(users) 46 | # print users 47 | print i 48 | print j 49 | user_num_logic_real = dict(map(lambda t: (t[1], t[0]), users.items())) 50 | news_num_logic_real = dict(map(lambda t: (t[1], t[0]), news.items())) 51 | Rmn = np.zeros((users.__len__(), news.__len__())) 52 | for new in news_data: 53 | new = new.split('\t') 54 | i = users[new[0]] 55 | j = news[new[1]] 56 | Rmn[i, j] += 1 57 | if int(user_logic_num_latest.setdefault(users[new[0]], (news[new[1]], new[2]))[1]) < int(new[2]): 58 | user_logic_num_latest[users[new[0]]] = (news[new[1]], new[2]) 59 | if int(news_logic_num_latest_read_time.setdefault(news[new[1]], new[2])) < int(new[2]): 60 | news_logic_num_latest_read_time[news[new[1]]] = new[2] 61 | if new[5] == 'NULL\n': 62 | if int(news_num_issue_time.setdefault(news[new[1]], new[2])) > int(new[2]): 63 | news_num_issue_time[news[new[1]]] = new[2] 64 | else: 65 | news_num_issue_time[news[new[1]]] = new[5] 66 | 67 | for i, j in zip(range(users.__len__()), range(news.__len__())): 68 | if Rmn[i][j] == 0: 69 | user_logic_num_unrated_news_set.setdefault(i, set()).add(j) 70 | 71 | return news_num_issue_time, news_logic_num_latest_read_time, user_num_logic_real, news_num_logic_real, \ 72 | user_logic_num_unrated_news_set, Rmn, user_logic_num_latest 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /code/read_file1.py: -------------------------------------------------------------------------------- 1 | __author__ = 'LiGe' 2 | #encoding:utf-8 3 | import math 4 | 5 | 6 | #读取用户集合,用户-新闻集合 7 | def csv_to_list(): 8 | f=open('train_data.txt') 9 | item=list() 10 | users=dict() 11 | user=set() 12 | ignore_user=set() 13 | temp=list() 14 | user_item_time=dict() 15 | news_data=f.readlines() 16 | for new in news_data: 17 | new=new.strip() 18 | if len(new)!=0: 19 | new=new.split('\t') 20 | if new[0] not in user: 21 | if len(user)!=0: 22 | user_item_time[previous[0]]=temp 23 | temp=list() 24 | users[previous[0]]=item 25 | item=list() 26 | temp.append((new[1],new[2])) 27 | item.append(new[1]) 28 | user.add(new[0]) 29 | else: 30 | user.add(new[0]) 31 | temp=list() 32 | temp.append((new[1],new[2])) 33 | item=list() 34 | item.append(new[1]) 35 | else: 36 | item.append(new[1]) 37 | temp.append((new[1],new[2])) 38 | ignore_time=abs(float(new[2])-float(previous[2])) 39 | if ignore_time>3600 and len(item)==2: 40 | ignore_user.add(previous[0]) 41 | previous=new 42 | users[previous[0]]=item 43 | user_item_time[previous[0]]=temp 44 | return users,user_item_time,ignore_user -------------------------------------------------------------------------------- /code/recommend.py: -------------------------------------------------------------------------------- 1 | __author__ = 'LiGe' 2 | #encoding:utf-8 3 | import csv 4 | from numpy import * 5 | from read_file1 import csv_to_list 6 | from latest_new import map_time 7 | from latest_new import map_item_issue_time 8 | import datetime 9 | import time 10 | 11 | 12 | #把发布时间转换为标准unix时间后,换算成秒数 13 | def cal_time(t1): 14 | #t1='2014年03月08日12:31' 15 | t1=t1.strip() 16 | if t1.find('年')<0: 17 | t1='2014年03月15日12:31' 18 | t1=t1.replace('年','-') 19 | t1=t1.replace('月','-') 20 | t1=t1.replace('日',' ') 21 | if t1.find(':')<0: 22 | t1=t1+'00:00' 23 | t1=t1+':00' 24 | s = t1 25 | d = datetime.datetime.strptime(s,"%Y-%m-%d %H:%M:%S") 26 | return time.mktime(d.timetuple()) 27 | 28 | #计算近邻TopN用户的子函数 29 | def RBF(user_item,user,d,user_item_time,alpha=0.02): 30 | interres=list(set(user_item[user]).intersection(set(user_item[d]))) 31 | sum=0 32 | t1=0 33 | t2=0 34 | simi=0.0 35 | for item in interres: 36 | for line in user_item_time[user]: 37 | if line[0]==item: 38 | t1=line[1] 39 | break 40 | for line in user_item_time[d]: 41 | if line[0]==item: 42 | t2=line[1] 43 | break 44 | sum=sum+1/(1+alpha*abs(float(t1)-float(t2))) 45 | simi=float(sum)/float(math.sqrt(len(user_item[user])*len(user_item[d]))) 46 | return simi 47 | 48 | #计算近邻用户 49 | def pearsSim(user, user_item,user_item_time): 50 | score=list() 51 | for d in user_item: 52 | if d==user:continue 53 | sim=RBF(user_item,user,d,user_item_time,alpha=0.02) 54 | score.append((sim,d)) 55 | return sorted(score, key=lambda jj:jj[0], reverse=True)[:150] 56 | 57 | 58 | def sim(k,item,user_item,user_item_time,fre,alpha=0.02):#求物品与物品的相似度,考虑了时间维度,找出有两种物品的用户,然后计算其距离,采用的是基于项目相似性的计算 59 | count=0 60 | score=0 61 | t1=0.0 62 | t2=0.0 63 | for line in user_item: 64 | flag1=0 65 | flag2=0 66 | score=0 67 | if k in user_item[line]: 68 | if item in user_item[line]: 69 | for item_time in user_item_time[line]: 70 | if item_time[0]==k: 71 | t1=item_time[1] 72 | flag1=1 73 | if item_time[0]==item: 74 | t2=item_time[1] 75 | flag2=1 76 | if flag1 and flag2: 77 | break 78 | score=1/(1+alpha*abs(float(t1)-float(t2))) 79 | count=count+score 80 | count=count/math.sqrt(fre[k]*fre[item]) 81 | return count 82 | 83 | 84 | def interest_distribution(user,user_item,user_item_time,item_fre,user_latest_time,item_issue_time,h=0.2):#对用户未读项的分布的计算 85 | #user_item_interest_socre=dict() 86 | Beta=1.40479988345e-11 87 | item_interest_score=list() 88 | for item in item_fre:#记住,一定是按照顺序放入list中的,是按照同一种顺序放入的 89 | if item not in user_item[user]:#对未知项进行评分估计 90 | count=0.0 91 | for k in user_item[user]: 92 | distance=1-sim(k,item,user_item,user_item_time,item_fre) 93 | #print distance 94 | count=count+math.exp(-float(distance*distance)/float(2*h*h)) 95 | count=0.8*count/(len(user_item[user])*math.sqrt(2*math.pi)*h)+0.2*(math.exp(-Beta*(int(user_latest_time[user])-int(cal_time(item_issue_time[item])))))#时效性与兴趣偏好融合 96 | item_interest_score.append((item,count)) 97 | return item_interest_score 98 | 99 | #给近邻用户进行编号处理,方便后面核密度估计的计算 100 | def create_feature(user,recos,user_item): 101 | union_item=dict() 102 | local_user_item=dict() 103 | for line in recos: 104 | for data in user_item[line[1]]: 105 | if data not in union_item: 106 | union_item[data]=1 107 | else: 108 | union_item[data]=union_item[data]+1 109 | for line in user_item[user]: 110 | if line not in union_item: 111 | union_item[line]=1 112 | else: 113 | union_item[line]=union_item[line]+1 114 | for line in recos: 115 | local_user_item[line[1]]=user_item[line[1]] 116 | local_user_item[user]=user_item[user] 117 | return union_item,local_user_item 118 | 119 | 120 | def recommend1(): 121 | j=0 122 | csvfile = file('csv_result19.csv', 'wb') 123 | writer=csv.writer(csvfile) 124 | user_item,user_item_time,ignore_user=csv_to_list() 125 | user_latest_time=map_time() 126 | item_issue_time=map_item_issue_time() 127 | predict=list() 128 | print ignore_user 129 | print len(ignore_user) 130 | for user in user_item: 131 | if user not in ignore_user: 132 | recos=pearsSim(user,user_item,user_item_time)#求解相似用户 133 | item_fre,local_user_item=create_feature(user,recos,user_item)#编号处理 134 | item_score=interest_distribution(user,local_user_item,user_item_time,item_fre,user_latest_time,item_issue_time,h=0.4)#计算核密度 135 | number=len(user_item[user]) 136 | if number>30: 137 | number=20 138 | final_result=sorted(item_score, key=lambda jj:jj[1], reverse=True)[:(number/10)+1]#结果排序 139 | print final_result 140 | for line in final_result: 141 | if line[1]>0.25: 142 | writer.writerow((user,line[0])) 143 | j=j+1 144 | print j 145 | csvfile.close() 146 | return predict 147 | 148 | 149 | if __name__=='__main__': 150 | predict=recommend1() 151 | 152 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /个性化新闻推荐解决方案文档.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanshengli/second_bigdata/bfe0c576b46149fd2e8d781391fdde664f583892/个性化新闻推荐解决方案文档.pdf --------------------------------------------------------------------------------