oleg---人工智能第五期
1 数据探索
数据来源于kaggle竞赛:Event Reconmendation EngineChallenge
根据过去用户对他们所看到的活动和他们在app里所点击的活动等,这些用户对活动反应的统计信息,来预测用户对某个活动是否感兴趣。
数据集信息:
train.csv :训练集,共6列,1.5w个样本,没有缺失值
user |
event |
invited |
timestamp |
interested |
not_interested |
用户ID |
活动ID |
是否被邀请 |
时间戳 |
是否感兴趣 |
是否不感兴趣 |
test.csv: 测试集,共4列,1w+个样本,没有缺失值
user |
event |
invited |
timestamp |
用户ID |
活动ID |
是否被邀请 |
时间戳 |
没有interested、not_interested列,其余与训练集相同
users.csv 用户数据集,共7维特征,3.8w条数据,有缺失值
user_id |
locale |
birthyear |
gender |
joinedAt |
location |
timezone |
用户ID |
地区 |
出生年 |
性别 |
加入app时间 |
地点 |
时区 |
events.csv 活动数据集,共110维特征(后101列为词频),300w+条数据,有缺失值
event_id |
user_id |
start_time |
city |
state |
zip |
country |
lat |
lng |
c_i |
活动ID |
用户ID |
开始时间 |
城市 |
州 |
邮编 |
国家 |
纬度 |
经度 |
词频 |
后101列中,前100列为最常用的100个词,最后一列count_other为除了这100个词之外其余词出现的次数
event_attendees.csv ,活动数据集,共5维特征,2.4w条数据,缺失值很多
event |
yes |
maybe |
invited |
no |
活动ID |
参加的用户 |
可能参加的用户 |
被邀请的用户 |
不参加的用户 |
user_friends.csv,用户社交数据集,共2列,3.8w条数据,有缺失值
user |
friends |
用户ID |
好友ID |
2 数据预处理
整个数据集中活动数目太多,我们只找出训练集和测试集中出现的活动和用户数据,得到新的活动和用户数据集
活动数据处理
(1) 从train.csv和test.csv中得到训练集和测试集中出现的活动event_id。
(2) 然后根据event_id,与events.csv数据集融合,得到出现的活动信息,保存为event_cur.csv
用户数据处理
(1) 从train.csv和test.csv中得到训练集和测试集中出现的用户user_id。
(2) 然后根据user_id,与user.csv数据集融合,得到参加了活动的用户信息,保存为user_cur.csv
最后得到训练集和测试集中出现的用户有3391条数据、活动有13418条数据
建立用户和活动的关系矩阵
(1) 通过哈希表,得到用户ID索引和参加活动的活动ID索引
(2) 然后将用户ID添加到被用户参加的活动usersForEvent集合和活动ID添加到用户参加的活动eventsForUser集合中,并保存。
(3) 将用户是否对该活动感兴趣,根据索引值添加到用户活动得分表中,userEventScores中,并保存
建立用户、好友和活动的关系矩阵
我们找出关联用户和关联活动
关联用户:至少在同一个活动event上有行为的用户和好友,组成用户pair,unqiueUserPair
关联活动:至少有同一个用户user有行为的活动,组成活动pair,uniqueEventPair
(1) 对同一个活动,根据活动所对应的索引,找到usersForEvent中的用户users,如果users的长度大于2,则将用户ID添加到用户对中uniqueUserPairs中,并保存
(2) 对同一个用户,根据用户所对应的索引,找到eventsForUser中的活动events,如果events的长度大于2,则将活动ID添加到活动对中uniqueEventPairs中,并保存
3 数据分析处理
活动数据分析
只选择训练集和测试集中出现的活动数据,计算活动之间的
(1) 对于非词频特征,采用person相关系数作为相似度
(2) 对于词频特征,采用余弦相似度,计算出相似度
用户数据处理
只选择训练集和测试集中出现的用户数据,计算用户之间的相似度
采用person相关系数作为相似度
4 基于用户社交数据的分析
只选择训练集和测试集中出现的用户数据,根据朋友对活动的打分,计算该用户和朋友之间的一个分数
(1) 读取用户社交数据,对于用户的每个朋友,查看是否在训练集或者测试集中出现过,如果出现过,则找出该朋友对活动的评分。
(2) 读取用户-活动分数矩阵,根据每个朋友参加的所有活动的评分,计算出平均值,即为该用户和朋友之间的一个分数,也为该朋友和用户之间的一个分数
5 活动热度数据处理
读取活动参加者数据,根据参加和不参加的人数,计算活动的热度,作为后续模型的输入特征
6 推荐系统
将所有特征串联起来,为推荐系统做准备
(1) 读入数据,并初始化
#用户和活动的索引
#用户-活动的关系矩阵R
#每个用户参加的事件,事件参加的用户
#基于模型的协同过滤参数初始化,训练
self.init_SVD()
self.train_SVD(trainfile=’train.csv’)
#根据用户属性计算出的用户之间的相似度
#根据活动属性计算出的活动之间的相似度
#每个用户朋友的数目
#用户的每个朋友参加活动的分数对该用户的影响
#活动本身的热度
(2) 定义相关函数
#初始化模型参数
初始化模型参数K bi bu PQ
训练模型
def train_SVD(self, trainfile=r'C:/Users/oleg/Desktop/机器学习/第四周作业/train.csv', steps=100, gamma=0.04, Lambda=0.15):
# 训练SVD模型(for 基于模型的协同过滤SVD_CF)
# gamma:为学习率
# Lambda:正则参数
# 偷懒了,为了和原来的代码的输入接口一样,直接从训练文件中去读取数据
print( "SVD Train...")
ftrain = open(trainfile, 'r')
ftrain.readline()
self.mu = 0.0
n_records = 0
uids = [] # 每条记录的用户索引
i_ids = [] # 每条记录的item索引
# 用户-Item关系矩阵R(内容同userEventScores相同),临时变量,训练完了R不再需要
R = np.zeros((self.n_users, self.n_items))
for line in ftrain:
cols = line.strip().split(",")
#得到用户的索引
u = self.userIndex[cols[0]] # 用户
#得到活动的索引
i = self.eventIndex[cols[1]] # 活动
uids.append(u)
i_ids.append(i)
#得到用户对活动的评分,建立评分矩阵
R[u, i] = int(cols[4]) # interested
self.mu += R[u, i]
n_records += 1
ftrain.close()
self.mu /= n_records
#优化模型
ftrain = open(trainfile, 'r')
ftrain.readline()
loss=0.0
for step in range(steps):
#梯度下降算法,每次迭代更新四个参数:bi,bu,P,Q
#更新学习率
#if steps>=100
#gamma=gamma*0.95
for line in ftrain:
cols = line.strip().split(",")
#根据用户,得到用户的索引
u = self.userIndex[cols[0]] # 用户
i = self.eventIndex[cols[1]] # 活动
#得到用户对活动的打分
R[u, i] = int(cols[4])
#迭代次数steps=100
#对于每个用户的活动训练对应行列上Q和P的值,对于每一个用户训练一个bu,对于每一个活动训练一个bi
ans = self.mu + self.bi[i] + self.bu[u] + np.dot(self.P[u, :], self.Q[:, i])
self.bi = (1 - gamma * Lambda) * self.bi + gamma * (R[u, i] - ans)
self.bu = (1 - gamma * Lambda) * self.bu + gamma * (R[u, i]- ans)
self.P[u,:] = self.P[u,:] + gamma * (R[u,i] - ans) * self.Q[:,i]
self.Q[:,i] = self.Q[:,i] + gamma * (R[u,i] - ans) * self.P[u,:]
#真实值与预测值差的平方
eui=R[u,i]-self.pred_SVD(u,i)
loss=eui**2
#每10步打印一次loss
if step+1%10==0:
print('step {0}/100 loss:{1:.4f}'.format(step,loss))
#打印出最后一次的loss
print('step {0}/100 loss:{1:.4f}'.format(step,loss))
ftrain.close()
print("SVD trained")
#基于模型的协同预测,预测用户对活动的打分
def pred_SVD(self, uid, i_id):
# 根据当前参数,预测用户uid对Item(i_id)的打分
ans = self.mu + self.bi[i_id] + self.bu[uid] + np.dot(self.P[uid, :], self.Q[:, i_id])
# 将打分范围控制在0-1之间
if ans > 1:
return 1
elif ans < 0:
return 0
return ans
#计算两个用户之间的相似度
def sim_cal_UserCF(self, uid1, uid2):
# 基于用户的协同过滤中的两个用户uid1和uid2之间的相似度(根据两个用户对item打分的相似度)
similarity = 0.0
#统计共同出现的活动
item_count=defaultdict(int)
#计算用户的打分
score_1 = []
score_2 = []
#对于用户1中出现的活动
for item in self.itemsForUser[uid1]:
score_1.append(self.userEventScores[uid1, item])
#统计共同出现的活动
if item in self.itemsForUser[uid2]:
item_count[item]=1#1值没有任何实际意义
for item in self.itemsForUser[uid2]:
score_2.append(self.userEventScores[uid2, item])
#计算用户的平均打分
avg_1=np.sum(score_1)/len(self.itemsForUser[uid1])
avg_2 = np.sum(score_2)/ len(self.itemsForUser[uid2])
#根据公式,计算相似度
#公式中的分母项,初始化
score_item_1=0
score_item_2 = 0
#公式中的分子项,初始化
score_12=0
for item in item_count:
#分子
score_12+=(self.userEventScores[uid1,item]-avg_1)*(self.userEventScores[uid2, item]-avg_2)
#分母各项
score_item_1+=(self.userEventScores[uid1,item]-avg_1)**2
score_item_2 += (self.userEventScores[uid2, item] - avg_2) ** 2
if len(item_count)==0:
return 0
#根据公式计算pearson相关系数
similarity=score_12/((score_item_1*score_item_2)**0.5)
return similarity
#基于用户的协同推荐
def userCFReco(self, userId, eventId):
"""
根据User-based协同过滤,得到event的推荐度
基本的伪代码思路如下:
for item i
for every other user v that has a preference for i
compute similarity s between u and v
incorporate v's preference for i weighted by s into running aversge
return top items ranked by weighted average
"""
ans = 0.0
#公式中的分母项
sim_sum=0.0
#公式中的分子项
sim_weight_sum=0.0
#得到活动的索引
e=self.eventIndex[eventId]
#得到用户的索引
u=self.userIndex[userId]
#根据活动得到所用参加过该活动的用户索引
users=self.usersForItem[e]
for user in users:
#计算参加过该活动用户与user的相似度
sim=self.sim_cal_UserCF(user,u)
score=self.userEventScores[user,e]
sim_weight_sum+=sim*score
sim_sum+=sim
#如果没有相似的用户参加过该活动,则打分为0,表示未知
if sim_sum==0:
return 0
ans=sim_weight_sum/sim_sum
return ans
#计算活动之间的相似度
# 计算Item i_id1和i_id2之间的相似性
similarity = 0.0
user_count = defaultdict(int)
# 根据公式,计算用户的打分
#建立score矩阵,记录用户的平均打分
score=[]
#分子项
score_12=0.0
#分母项
score_item_1 = 0
score_item_2 = 0
avg = defaultdict()
# 统计共同出现的用户
#对于活动i_id1中出现的所有用户进行统计
for user in self.usersForItem[i_id1]:
if user in self.usersForItem[i_id2]:
user_count[user] = 1#1值随便取的,没有任何意义
#对于共同出现过的用户
for user in user_count:
#通过用户索引,找到活动索引
for item in self.itemsForUser[user]:
#计算每个用户的平均打分
score.append(self.userEventScores[user, item])
avg[user]=np.sum(score)/len(self.itemsForUser[user])
#分子
score_12+=(self.userEventScores[user,i_id1]-avg[user])*(self.userEventScores[user,i_id2]-avg[user])
#分母
score_item_1 += (self.userEventScores[user, i_id1] - avg[user]) ** 2
score_item_2 += (self.userEventScores[user, i_id2] - avg[user]) ** 2
#根据公式计算pearson相关系数
#如果没有共同用户,则相似度为0
if len(user_count) == 0:
return 0
similarity = score_12 / ((score_item_1 * score_item_2) ** 0.5)
return similarity
#基于项目的协同推荐
"""
根据基于物品的协同过滤,得到Event的推荐度
基本的伪代码思路如下:
for item i
for every item j tht u has a preference for
compute similarity s between i and j
add u's preference for j weighted by s to a running average
return top items, ranked by weighted average
"""
ans = 0.0
#分母项
sim_sum = 0.0
#分子项
sim_weight_sum = 0.0
#得到活动索引
e = self.eventIndex[eventId]
#得到用户索引
u = self.userIndex[userId]
#得到该用户参加过的所有活动的索引
items = self.itemsForUser[u]
#对于用户参加过的活动
for item in items:
sim = self.sim_cal_ItemCF(item, e)
score = self.userEventScores[u, item]
sim_weight_sum += sim * score
sim_sum += sim
if sim_sum == 0:
return 0
ans = sim_weight_sum / sim_sum
return ans