✨写在前面:强烈推荐给大家一个优秀的人工智能学习网站,内容包括人工智能基础、机器学习、深度学习神经网络等,详细介绍各部分概念及实战教程,通俗易懂,非常适合人工智能领域初学者及研究者学习。➡️点击跳转到网站。
➡️前一篇新闻推荐系统系列参考:
https://blog.csdn.net/_/article/details/
?音乐推荐项目描述:
音乐推荐系统demo,针对用户画像、用户行为及物品特征构建推荐系统。使用cb、cf算法做推荐召回,使用redis数据库做缓存处理,结合LR算法推荐排序,达到粗排、精排效果,实现个性化推荐。
?音乐推荐系统项目链接:
https://github.com/GoAlers/music-top-recommend
一、推荐项目流程:

1.预处理阶段:
对三个数据进行预处理,合并用户与物品相关信息,处理后的merge_base.data数据字段包含itemid、userid、用户信息(年龄、性别、收入、地区)、物品信息(名字、描述、时长、标签)、用户行为数据(收听时长)等。
2.粗排召回阶段
使用CB算法,基于内容进行jieba中文分词,计算itemid对应分词的tfidf分数,整理训练数据;使用mr协同过滤进行相关性计算,训练得到物品之间对应分数item-item能得到II矩阵;而CF算法则通过协同过滤将UI矩阵转成II矩阵。最后,将格式化后数据结果按k/v形式批量灌入redis数据库。
3.精排阶段
利用LR算法进行推荐排序,得到权重w、b用于模型构建rank_model。结合用户与物品标签获取用户与物品特征训练数据。
4.推荐流程阶段
加载特征数据及排序模型,检索redis数据库获取候选集,利用逻辑回归sigmoid函数打分并排序,最终利用可视化页面实现itemid->name进行top10评分相关推荐。
二、推荐系统思路框架
流程思路:
数据集: 默认以’\001’分割
userid,性别,年龄,收入,地域 00dee4bd83b6c115b865a64e,男,26-35,10000-20000,台湾 000798c9a4cab6d6b3065e287d,女,36-45,5000-10000,陕西 00a2fe08da2621d2e9dd3ff7f89e7000,女,19-25,20000-,宁夏 009cb9b50e1c38e6e8d70b9accc08000,男,26-35,10000-20000,新疆 0bfb3e321a852abc857e2c9000,男,46-100,10000-20000,山西
itemid,name,内容 ,时长,地域,标签 outta my head 周杰伦MV-范特西 227 周杰伦MV GEE 少女时代 舞蹈,舞蹈教学 197 舞蹈,舞蹈教学,流行 SPICA - You Don't Love Me 敬请期待先上预告 24 他不爱我预告 TAE MIN - Steps 总理和我在OST 218 东方神起TAE,流行
01e3fdfcd6046a07481fbed499 555 16 01e3fdfcd6046a07481fbed499 2024 22 012e5c128fbe16c302c9a12f9238f871 1047 13 012e5c128fbe16c302c9a12f9238f871 1155 10 01e3fdfcd6046a07481fbed499 795 22
三、推荐系统步骤
1、数据预处理:
总体思路:处理原始的数据,将用户画像数据 、物品元数据、用户行为数据,3份融合到一起,得到处理后merge_base.data,用于cb、cf算法进行计算。
python gen_base.py
#coding=utf-8 import sys #找到三类原始数据文件,用户画像数据、物品元数据,用户行为数据 user_action_data = '../data/user_watch_pref.sml' music_meta_data = '../data/music_meta' user_profile_data = '../data/user_profile.data' 将合并后的元数据放到新的文件里 output_file = '../data/merge_base.data' # 将3份数据merge后的结果输出,供下游数据处理 ofile = open(output_file, 'w') # step 1. 处理物品元数据,将处理后的结果放入字典里面,key是itemid,value为物品对应的信息,为最后写入做准备 item_info_dict = {
} with open(music_meta_data, 'r') as fd: for line in fd: ss = line.strip().split('\001') if len(ss) != 6: continue itemid, name, desc, total_timelen, location, tags = ss item_info_dict[itemid] = '\001'.join([name, desc, total_timelen, location, tags]) # step 2. 处理用户画像数据,将处理后的结果放入字典里面,key是用户id,value是用户信息 user_profile_dict = {
} with open(user_profile_data, 'r') as fd: for line in fd: ss = line.strip().split(',') if len(ss) != 5: continue userid, gender, age, salary, location = ss user_profile_dict[userid] = '\001'.join([gender, age, salary, location]) # step 3. 写入最后的信息,将用户行为数据进行处理,把step1和step2得到的数据一并归纳在文件里面 with open(user_action_data, 'r') as fd: for line in fd: ss = line.strip().split('\001') if len(ss) != 4: continue userid, itemid, watch_len, hour = ss if userid not in user_profile_dict: continue if itemid not in item_info_dict: continue ofile.write('\001'.join([userid, itemid, watch_len, hour, \ user_profile_dict[userid], item_info_dict[itemid]])) ofile.write("\n") ofile.close()
2、【召回】CB算法
总体思路:将初始化好的用户,物品,用户行为数据进行处理,对item去重,目的是为了得到token,itemid,score,对于生成的数据里面的name,将itemName进行分词,得到tfidf权重,同时将desc进行分词,处理name和desc。
代码思路:元数据中tags已经分类好无需再次进行切分,只需要用idf词表查处权重即可,针对name、desc、tags三个分词结果,适当调整name权重比例,分别对这三类得出的分数再次进行分数权重划分,最后得到cb的初始数据。
(1)利用jieba分词,对item name进行中文分词,item —> name desc tag ,对每个item的这三个字段进行分词并计算score,将数据用字典token_dict维护执行python gen_cb_train.py
#coding=utf-8 import sys sys.path.append('../') reload(sys) sys.setdefaultencoding('utf-8') import jieba import jieba.posseg import jieba.analyse #读入初始数据 input_file = "../data/merge_base.data" # 输出cb训练数据 output_file = '../data/cb_train.data' outfile = open(output_file, 'w') #定义三类的权重分数(大小可自行设定) RATIO_FOR_NAME = 0.9 RATIO_FOR_DESC = 0.1 RATIO_FOR_TAGS = 0.05 #为tags读入idf权重值 idf_file = '../data/idf.txt' idf_dict = {
} with open(idf_file, 'r') as fd: for line in fd: token, idf_score = line.strip().split(' ') idf_dict[token] = idf_score #开始处理初始数据 itemid_set = set() with open(input_file, 'r') as fd: for line in fd: ss = line.strip().split('\001') # 用户行为 userid = ss[0].strip() itemid = ss[1].strip() watch_len = ss[2].strip() hour = ss[3].strip() # 用户画像 gender = ss[4].strip() age = ss[5].strip() salary = ss[6].strip() user_location = ss[7].strip() # 物品元数据 name = ss[8].strip() desc = ss[9].strip() total_timelen = ss[10].strip() item_location = ss[11].strip() tags = ss[12].strip() # 对item去重,相同的itemid不用再计算,因为都一样,这里用到continue特性,当不同的时候才继续执行下面的代码 if itemid not in itemid_set: itemid_set.add(itemid) else: continue # 去掉重复后的itemid,然后我们进行分词,计算权重,放到字典里面 token_dict = {
} #对name统计 for a in jieba.analyse.extract_tags(name, withWeight=True): token = a[0] score = float(a[1]) token_dict[token] = score * RATIO_FOR_NAME #对desc进行分词,这里需要注意的是描述一般会含有name中的词,这里我们把有的词的分数进行相加,没有的放入 for a in jieba.analyse.extract_tags(desc, withWeight=True): token = a[0] score = float(a[1]) if token in token_dict: token_dict[token] += score * RATIO_FOR_DESC else: token_dict[token] = score * RATIO_FOR_DESC # 对tags 进行分数计算 for tag in tags.strip().split(','): if tag not in idf_dict: continue else: if tag in token_dict: token_dict[tag] += float(idf_dict[tag]) * RATIO_FOR_TAGS else: token_dict[tag] = float(idf_dict[tag]) * RATIO_FOR_TAGS #循环遍历token_dict,输出toke,itemid,score for k, v in token_dict.items(): token = k.strip() score = str(v) ofile.write(','.join([token, itemid, score])) ofile.write("\n") outfile.close()
相似度计算:要用到MapReduce的框架来进行,只要用到shuffle阶段,对map出来的结果排序,reduce进行两两配对,主要是wordcount逻辑,主要说下注意的部分:我们需要把两两分数的过滤掉,或是把itemA和itemB相同的item过滤掉,因为这部分数据没有任何意义。
1.map阶段:
总体思路:这里需要把初始化后的结果进行map排序,为了后续两两取 pair对,所以这里我们需要进行map,其实什么也不用操作输出即可
import sys import re for line in sys.stdin: ss = line.strip().split(',') if len(ss) != 3: continue r1 = u'[a-zA-Z0-9’!"#$%&\'()*+,-./:;<=>?@,。?★、…【】《》?“”‘’![\\]^_`{|}~]+' ss[0] = re.sub(r1,'',ss[0]) if len(ss[0]) == 0: continue print ','.join([ss[0], ss[1], ss[2]])
2.reduce阶段:
在pair reduce之前做过map操作,以token,item,score输出,所以token排序好的, 这里我们相当于求的是II矩阵,所以是相同的token的item进行相似度计算
总体思路: 1、进行user统计,若相同,把相同的user的item和score放入list里面 2、不相同,开始进行两两配对,循环该list,进行两两配对,求出相似度
import sys import math cur_token = None item_score_list = [] for line in sys.stdin: ss = line.strip().split(',') itemid = ss[1] score = float(ss[2]) if len(ss) != 3: continue if cur_token == None: cur_token = ss[0] if cur_token != ss[0]: #这里需要注意的是range的区间前闭后开,同时注意range中即使前闭后开,刚开始是从0即列表里面的第一个,循环到列表最后一个的前一个 for i in range(0,len(item_score_list)-1): for j in range(i+1,len(item_score_list)): item_a,score_a = item_score_list[i] item_b,score_b = item_score_list[j] #score = float(score_a * score_b)/float(math.sqrt(pow(score_a,2))*math.sqrt(pow(score_b,2))) #输出两遍的目的是为了形成II矩阵的对称 score = float(score_a*score_b) if item_a == item_b: continue if score < 0.08: continue print "%s\t%s\t%s" % (item_a, item_b, score) print "%s\t%s\t%s" % (item_b, item_a, score) cur_token = ss[0] item_score_list = [] item_score_list.append((itemid,float(score))) for i in range(0, len(item_score_list) - 1): for j in range(i + 1, len(item_score_list)): item_a, score_a = item_score_list[i] item_b, score_b = item_score_list[j] #score = (score_a * score_b) / (math.sqrt(pow(score_a, 2)) * math.sqrt(pow(score_b, 2)) # 输出两遍的目的是为了形成II矩阵的对称 score = float(score_a * score_b) if item_a == item_b: continue if score < 0.08: continue print "%s\t%s\t%s" % (item_a, item_b, score) print "%s\t%s\t%s" % (item_b, item_a, score)
最后得到基于cb的ii矩阵

#coding=utf-8 import sys infile = '../data/cb.result' outfile = '../data/cb_reclist.redis' ofile = open(outfile, 'w') MAX_RECLIST_SIZE = 100 PREFIX = 'CB_' rec_dict = {
} with open(infile, 'r') as fd: for line in fd: itemid_A, itemid_B, sim_score = line.strip().split('\t') #判断itemA在不在该字典里面,若不在,创建一个key为itemA的列表,把与itemA相关联的itemB和score添加进去 if itemid_A not in rec_dict: rec_dict[itemid_A] = [] rec_dict[itemid_A].append((itemid_B, sim_score)) #循环遍历字典,格式化数据,把itemB和score中间以:分割,不同的itemB以_分割 for k, v in rec_dict.items(): key_item = PREFIX + k #接下来格式化数据,将数据以从大到小排列后再格式化 #排序,由于数据量大,我们只取100个 #排好序后,我们来格式化数据 reclist_result = '_'.join([':'.join([tu[0], str(round(float(tu[1]), 6))]) \ for tu in sorted(v, key=lambda x: x[1], reverse=True)[:MAX_RECLIST_SIZE]]) ofile.write(' '.join(['SET', key_item, reclist_result])) ofile.write("\n") ofile.close()
得到类似如下数据:根据itemid_a,返回相似对应itemid_b的score
3.【召回】CF算法:
下文是利用Mapreduce实现CF算法,如果想利用Spark实现CF算法请参考的这篇文章https://blog.csdn.net/_/article/details/
(1)以userid itemid score形式整理训练数据
python gen_cf_train.py
#coding=utf-8 import sys input_file = "../data/merge_base.data" # 输出cf训练数据 output_file = '../data/cf_train.data' ofile = open(output_file, 'w') key_dict = {
} with open(input_file, 'r') as fd: for line in fd: ss = line.strip().split('\001') # 用户行为 userid = ss[0].strip() itemid = ss[1].strip() watch_len = ss[2].strip() hour = ss[3].strip() # 用户画像 gender = ss[4].strip() age = ss[5].strip() salary = ss[6].strip() user_location = ss[7].strip() # 物品元数据 name = ss[8].strip() desc = ss[9].strip() total_timelen = ss[10].strip() item_location = ss[11].strip() tags = ss[12].strip() #拼接key,为了将同一个用户对相同物品的时长全部得到,需要做个聚合 key = '_'.join([userid, itemid]) if key not in key_dict: key_dict[key] = [] key_dict[key].append((int(watch_len), int(total_timelen))) #循环处理相同用户对相同item的分数 for k, v in key_dict.items(): t_finished = 0 t_all = 0 # 对
为key进行分数聚合
for vv in v: t_finished += vv[0] t_all += vv[1] # 得到userid对item的最终分数 score = float(t_finished) / float(t_all) userid, itemid = k.strip().split('_') ofile.write(','.join([userid, itemid, str(score)])) ofile.write("\n") ofile.close()
得到如下数据:cf_train.data
一、MR实现CF算法
1.归一化, 归一化阶段我们主要是将相同的item进行单位模计算,把数据映射到0~1范围之内处理,更加便捷快速。因为我们要用到cos相似度计算公式,将相同的item的分数进行平方和再开根号,最后进行单位化。
相似度的计算公式:
2.取pair对 相同用户两两取pair,输出两次,形成II矩阵
3.计算总和 将相同pair的分数相加
#!usr/bin/python # -*- coding: UTF-8 -*- ''' 思路:转换成i,u,s的矩阵 ''' import sys for line in sys.stdin: ss = line.strip().split(',') if len(ss) != 3: continue u , i , s = ss print '\t'.join([i,u,s])
2.reduce阶段,我们需要将相同item平方和相加开根号,然后再单位化计算,最后输出。
#!usr/bin/python # -*- coding: UTF-8 -*- ''' 在map的基础上将每个item进行归一化,map已经将相同的item排好序,根据map的结果进行给先平方再开根号: 思路 : 1、截取字符串,取出item,user,socre 2、在for循环中进行判断,当前的item和下一个是否相同,要是相同,将相同的放到列表(user,score)列表里面,否则往下执行 3、若不相同,循环user和score列表,计算模计算,然后再次循环,进行单位化计算 ''' import sys import math cur_item = None user_score_list = [] for line in sys.stdin: ss = line.strip().split('\t') if len(ss) != 3: continue item = ss[0] userid = ss[1] score = ss[2] #wordcount判断,当前和下一个是否相同,相同添加到列表,不相同进行归一化计算 if cur_item == None: cur_item = item if cur_item != item: #定义sum sum = 0.0 #循环列表进行模向量计算 for ss in user_score_list: user,s = ss sum += pow(s,2) sum = math.sqrt(sum) #单位化计算 for touple in user_score_list: u,s = touple # 进行单位化完成后,我们输出重置成原来的user-item-score输出 print "%s\t%s\t%s" % (u, cur_item, float(s / sum)) #初始化这两个变量 cur_item = item user_score_list = [] user_score_list.append((userid,float(score))) #定义sum sum = 0.0 #循环列表进行模向量计算 for ss in user_score_list: user,s = ss sum += pow(s,2) sum = math.sqrt(sum) #单位化计算 for touple in user_score_list: u,s = touple # 进行单位化完成后,我们输出重置成原来的user-item-score输出 print "%s\t%s\t%s" % (u, cur_item, float(s / sum))
(2)两两取pair对
#!usr/bin/python # -*- coding: UTF-8 -*- #在进行pair取对之前,什么都不需要做,输出就行 import sys for line in sys.stdin: u, i, s = line.strip().split('\t') print "%s\t%s\t%s" % (u, i, s)
#!usr/bin/python # -*- coding: UTF-8 -*- ''' 思路:进行map排好序之后,我们的会得到相同user对应的不同item和score,这里我们主要的思路是进行相同用户两两取pair 1、进行判断,当前用户和下一个用户是不是一样,若是不一样,我们进行两两取对,形成ii矩阵 2、若是相同,我们将不同的item和score放入list里面 ''' import sys cur_user = None item_score_list = [] for line in sys.stdin: user,item,score = line.strip().split('\t') if cur_user == None: cur_user= user if cur_user != user: #进行两两pair,利用range函数 for i in range(0,len(item_score_list)-1): for j in range(i+1,len(item_score_list)): item_a, score_a = item_score_list[i] item_b, score_b = item_score_list[j] # 输出两遍的目的是为了形成II矩阵的对称 print "%s\t%s\t%s" % (item_a, item_b, score_a * score_b) print "%s\t%s\t%s" % (item_b, item_a, score_a * score_b) cur_user = user item_score_list = [] item_score_list.append((item,float(score))) #进行两两pair,利用range函数 for i in range(0,len(item_score_list)-1): for j in range(i+1,len(item_score_list)): item_a, score_a = item_score_list[i] item_b, score_b = item_score_list[j] # 输出两遍的目的是为了形成II矩阵的对称 print "%s\t%s\t%s" % (item_a, item_b, score_a * score_b) print "%s\t%s\t%s" % (item_b, item_a, score_a * score_b)
#!usr/bin/python # -*- coding: UTF-8 -*- ''' sum的map中,我们需要把相同的itemA,itemB组成key,为了使相同的key能够在shuffle阶段分配到同一个reduce中, 因为是计算item的相似度,要把相同的相加 ''' import sys for line in sys.stdin: item_a,item_b,score = line.strip().split('\t') key = '#'.join([item_a,item_b]) print '%s\t%s' %(key,score) reduce阶段主要任务就是将相同的item的pair对相加. ''' 思路:将相同的item的分数进行相加,得到最后的相似度 ''' import sys cur_item = None score = 0.0 for line in sys.stdin: item, s = line.strip().split('\t') if not cur_item: cur_item = item if cur_item != item: ss = item.split("#") if len(ss) != 2: continue item_a, item_b = ss print "%s\t%s\t%s" % (item_a, item_b, score) cur_item = item score = 0.0 score += float(s) ss = item.split("#") if len(ss) != 2: sys.exit() item_a, item_b = ss print "%s\t%s\t%s" % (item_a, item_b, score)
执行上述程序运行脚本run.sh
HADOOP_CMD="/usr/local/src/hadoop-2.6.5/bin/hadoop" STREAM_JAR_PATH="/usr/local/src/hadoop-2.6.5/share/hadoop/tools/lib/hadoop-streaming-2.6.5.jar" #要想cf代码直接改成cf_train.data INPUT_FILE_PATH_1="/cf_train.data" OUTPUT_PATH_1="/output1" OUTPUT_PATH_2="/output2" OUTPUT_PATH_3="/output3" $HADOOP_CMD fs -rmr -skipTrash $OUTPUT_PATH_1 $HADOOP_CMD fs -rmr -skipTrash $OUTPUT_PATH_2 $HADOOP_CMD fs -rmr -skipTrash $OUTPUT_PATH_3 Step 1. $HADOOP_CMD jar $STREAM_JAR_PATH \ -input $INPUT_FILE_PATH_1 \ -output $OUTPUT_PATH_1 \ -mapper "python 1_gen_ui_map.py" \ -reducer "python 1_gen_ui_reduce.py" \ -jobconf "mapreduce.map.memory.mb=4096" \ -file ./1_gen_ui_map.py \ -file ./1_gen_ui_reduce.py Step 2. $HADOOP_CMD jar $STREAM_JAR_PATH \ -input $OUTPUT_PATH_1 \ -output $OUTPUT_PATH_2 \ -mapper "python 2_gen_ii_pair_map.py" \ -reducer "python 2_gen_ii_pair_reduce.py" \ -jobconf "mapreduce.map.memory.mb=4096" \ -file ./2_gen_ii_pair_map.py \ -file ./2_gen_ii_pair_reduce.py Step 3. $HADOOP_CMD jar $STREAM_JAR_PATH \ -input $OUTPUT_PATH_2 \ -output $OUTPUT_PATH_3 \ -mapper "python 3_sum_map.py" \ -reducer "python 3_sum_reduce.py" \ -jobconf "mapreduce.map.memory.mb=8000" \ -file ./3_sum_map.py \ -file ./3_sum_reduce.py 最后得到基于cf的ii矩阵 cf_train.data ,执行得到cf.result (最后一列没有超过1的) 000000228 00 0.7 000000228 0. 000000228 0.6 000000228 0. 000000228 0.04
#coding=utf-8 ''' 思路:这个处理的逻辑和CB中完全一样,不一样的是redis的key是CF开头 ''' import sys infile = '../data/cf.result' outfile = '../data/cf_reclist.redis' ofile = open(outfile, 'w') MAX_RECLIST_SIZE = 100 PREFIX = 'CF_' rec_dict = {
} with open(input_file,'r') as fd: for line in fd: itemid_A, itemid_B, score = line.strip().split('\t') #判断itemA在不在该字典里面,若不在,创建一个key为itemA的列表,把与itemA相关联的itemB和score添加进去 if itemid_A not in rec_dict: rec_dict[itemid_A] = [] rec_dict[itemid_A].append((itemid_B, score)) #循环遍历字典,格式化数据,把itemB和score中间以:分割,不同的itemB以_分割 for k,v in rec_dict.items(): key = PREFIX+k #接下来格式化数据,将数据以从大到小排列后再格式化 #排序,由于数据量大,我们只取100个 list = sorted(v,key=lambda x:x[1],reverse=True)[:MAX_RECLIST_SIZE] #拍好序后,我们来格式化数据 result = '_'.join([':'.join([str(val[0]),str(round(float(val[1]),6))]) for val in list]) ofile.write(' '.join(['SET',key,result])) ofile.write("\n") ofile.close()
四、灌库(redis)
3.导入数据
首先连接服务,换一个终端执行:]# ./src/redis-cli,连接redis。
3.2 同上方法可以将cf灌库
unix2dos cf_reclist.redis
cat cf_reclist.redis | /usr/local/src/redis-2.8.3/src/redis-cli –pipe
4、LR训练模型的数据准备
#coding=utf-8 import sys sys.path.append('../') reload(sys) sys.setdefaultencoding('utf-8') import jieba import jieba.analyse import jieba.posseg merge_base_infile = '../data/merge_base.data' output_file = '../data/samples.data' #我们这里需要再生成两个文件,一个是用户样本和item样本,因为要对实时推荐的化,必须使用这两个样本 output_user_feature_file = '../data/user_feature.data' output_item_feature_file = '../data/item_feature.data' #这里生成个类似name和id对应的字典信息 output_itemid_to_name_file = '../data/name_id.dict' #定义函数,来获取各类数据 def get_base_samples(infile): #放待处理样本数据 ret_samples_list = [] #放user用户数据 user_info_set = set() #放物品数据 item_info_set = set() item_name2id = {
} item_id2name = {
} with open(infile, 'r') as fd: for line in fd: ss = line.strip().split('\001') if len(ss) != 13: continue userid = ss[0].strip() itemid = ss[1].strip() #这两个时间为了计算label而使用 watch_time = ss[2].strip() total_time = ss[10].strip() #用户数据 gender = ss[4].strip() age = ss[5].strip() user_feature = '\001'.join([userid, gender, age]) #物品数据 name = ss[8].strip() item_feature = '\001'.join([itemid, name]) #计算标签 label = float(watch_time) / float(total_time) final_label = '0' if label >= 0.82: final_label = '1' elif label <= 0.3: final_label = '0' else: continue #接下来装在数据,并返回结果,首先我们装在itemid2name和itemname2id item_name2id[name] = itemid item_id2name[itemid] = name #装在待处理的标签数据 ret_samples_list.append([final_label, user_feature, item_feature]) user_info_set.add(user_feature) item_info_set.add(name) return ret_samples_list, user_info_set, item_info_set, item_name2id, item_id2name #step 1 程序的入口,开始调用函数,开始处理文件,得到相应的数据 base_sample_list, user_info_set, item_info_set, item_name2id, item_id2name = \ get_base_samples(merge_base_infile) #step 2 抽取用户画像信息,用户标签转换,将年龄和age进行转换,用于样本使用 user_fea_dict = {
} for info in user_info_set: userid, gender, age = info.strip().split('\001') #设置标签idx,将男(1)和女(0)用数剧的形式表示,权重都设置为1 idx = 0 # default 女 if gender == '男': idx = 1 #将标签和权重拼接起来 gender_fea = ':'.join([str(idx), '1']) #性别设置完成,我们接下来设置年龄,将年龄进行划分,0-18,19-25,26-35,36-45 idx = 0 if age == '0-18': idx = 0 elif age == '19-25': idx = 1 elif age == '26-35': idx = 2 elif age == '36-45': idx = 3 else: idx = 4 idx += 2 age_fea = ':'.join([str(idx), '1']) user_fea_dict[userid] = ' '.join([gender_fea, age_fea]) #step 3 抽取物品特征,这里我们要用到分词,将name进行分词,并且把分词后的token转换成id,这里就需要我们来做生成tokenid词表 token_set = set() item_fs_dict = {
} for name in item_info_set: token_score_list = [] for x,w in jieba.analyse.extract_tags(name,withWeight=True): token_score_list.append((x,w)) token_set.add(x) item_fs_dict[name] = token_score_list #进行token2id的转换 token_id_dict = {
} #这里我们要用到刚刚利用set去重过的token列表,生成tokenid的字典表 for s in enumerate(list(token_set)): token_id_dict[s[1]] = s[0] #接下来,我们需要把第三步生成的item_fs_dict中name对应的token全部替换成id,然后当作字典,为下面的全量替换做准备 item_fea_dict = {
} user_feature_offset = 10 for name ,fea in item_fs_dict.items(): token_score_list = [] for (token,score) in fea: if token not in token_id_dict: continue token_id = token_id_dict[token] + user_feature_offset token_score_list.append(':'.join([str(token_id),str(score)])) #接下来输出到字典中 item_fea_dict[name] = ' '.join(token_score_list) #step 4 将第一步输出的样本数据整体替换并且替换user_feature和item_feature,并输出到文件中 ofile = open(output_file,'w') for (label,userfea,itemfea) in base_sample_list: userid = userfea.strip().split('\001')[0] item_name = itemfea.strip().split('\001')[1] if userid not in user_fea_dict: continue if item_name not in item_fea_dict: continue ofile.write(' '.join([label,user_fea_dict[userid],item_fea_dict[item_name]])) ofile.write('\n') ofile.close() #step 5 为了能够实时使用userfeatre,我们需要输出一下 out_put_file = open(output_user_feature_file,'w') for userid,fea in user_fea_dict.items(): out_put_file.write('\t'.join([userid,fea])) out_put_file.write('\n') out_put_file.close() #step 6 输出item_feature out_file = open(output_item_feature_file,'w') for name,fea in item_fea_dict.items(): if name not in item_name2id: continue itemid = item_name2id[name] out_file.write('\t'.join([itemid,fea])) out_file.write('\n') #step 7 输出id2name的对应的字典 o_file = open(output_itemid_to_name_file,'w') for id,name in item_id2name.items(): o_file.write('\t'.join([id,name])) o_file.write('\n') o_file.close()
得到如下数据:
五、模型准备
lr.py
# -*- coding: UTF-8 -*- import sys import numpy as np from scipy.sparse import csr_matrix from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression input_file = sys.argv[1] def load_data(): #由于在计算过程用到矩阵计算,这里我们需要根据我们的数据设置行,列,和训练的数据准备 #标签列表 target_list = [] #行数列表 fea_row_list = [] #特征列表 fea_col_list = [] #分数列表 data_list = [] #设置行号计数器 row_idx = 0 max_col = 0 with open(input_file,'r') as fd: for line in fd: ss = line.strip().split(' ') #标签 label = ss[0] #特征 fea = ss[1:] #将标签放入标签列表中 target_list.append(int(label)) #开始循环处理特征: for fea_score in fea: sss = fea_score.strip().split(':') if len(sss) != 2: continue feature, score = sss #增加行 fea_row_list.append(row_idx) #增加列 fea_col_list.append(int(feature)) #填充分数 data_list.append(float(score)) if int(feature) > max_col: max_col = int(feature) row_idx += 1 row = np.array(fea_row_list) col = np.array(fea_col_list) data = np.array(data_list) fea_datasets = csr_matrix((data, (row, col)), shape=(row_idx, max_col + 1)) x_train, x_test, y_train, y_test = train_test_split(fea_datasets, s, test_size=0.2, random_state=0) return x_train, x_test, y_train, y_test def main(): x_train,x_test,y_train,y_test = load_data() #用L2正则话防止过拟合 model = LogisticRegression(penalty='l2') #模型训练 model.fit(x_train,y_train) ff_w = open('model.w', 'w') ff_b = open('model.b', 'w') #写入训练出来的W for w_list in model.coef_: for w in w_list: print >> ff_w, "w: ", w # 写入训练出来的B for b in model.intercept_: print >> ff_b, "b: ", b print "precision: ", model.score(x_test, y_test) print "MSE: ", np.mean((model.predict(x_test) - y_test) 2) if __name__ == '__main__': main()
六、推荐系统实现

推荐系统demo流程:
初始化工作:加载data,获取user_feature.data,item_feature.data特征 ,用字典维护。
(1)解析请求:userid,itemid
(2)加载模型:加载排序模型(model.w,model.b)
(3)检索候选集合:利用cb,cf去redis里面检索数据库,得到候选集合
(4)获取用户特征user_feature.data :userid
(5)获取物品特征item_feature.data :itemid
(6)打分(逻辑回归函数sigmoid: 1 / (1 + exp(-wx))),排序
(7)top-n过滤(精排)
(8)数据包装(itemid->name),返回
执行主函数 main.py
#coding=utf-8 import web import sys import redis import json import math urls = ( '/', 'index', '/test', 'test', ) app = web.application(urls, globals()) # 加载user特征 user_fea_dict = {
} with open('../data/user_feature.data') as fd: for line in fd: userid, fea_list_str = line.strip().split('\t') user_fea_dict[userid] = fea_list_str # 加载item特征 item_fea_dict = {
} with open('../data/item_feature.data') as fd: for line in fd: ss = line.strip().split('\t') if len(ss) != 2: continue itemid, fea_list_str = ss item_fea_dict[itemid] = fea_list_str class index: def GET(self): r = redis.Redis(host='master', port=6379,db=0) # step 1 : 解析请求,上面我们已经得到userid,itemid params = web.input() userid = params.get('userid', '') req_itemid = params.get('itemid', '') # step 2 : 加载模型 model_w_file_path = '../rankmodel/model.w' model_b_file_path = '../rankmodel/model.b' model_w_list = [] model_b = 0. with open (model_w_file_path, 'r') as fd: for line in fd: ss = line.strip().split(' ') if len(ss) != 3: continue model_w_list.append(float(ss[2].strip())) with open (model_b_file_path, 'r') as fd: for line in fd: ss = line.strip().split(' ') model_b = float(ss[2].strip()) # step 3 : 检索候选(match),这里我们分两次,cb,cf #将检索回来的item全部放到recallitem列表里面 rec_item_mergeall = [] # 3.1 cf cf_recinfo = 'null' key = '_'.join(['CF', req_itemid]) if r.exists(key): cf_recinfo = r.get(key) if len(cf_recinfo) > 6: for cf_iteminfo in cf_recinfo.strip().split('_'): item, score = cf_iteminfo.strip().split(':') rec_item_mergeall.append(item) # 3.2 cb cb_recinfo = 'null' key = '_'.join(['CB', req_itemid]) if r.exists(key): cb_recinfo = r.get(key) if len(cb_recinfo) > 6: for cb_iteminfo in cb_recinfo.strip().split('_'): item, score = cb_iteminfo.strip().split(':') rec_item_mergeall.append(item) # step 4: 获取用户特征,将获取的用户特征处理后放到字典里面,方便后续计算内积 user_fea = '' if userid in user_fea_dict: user_fea = user_fea_dict[userid] u_fea_dict = {
} for fea_idx in user_fea.strip().split(' '): ss = fea_idx.strip().split(':') if len(ss) != 2: continue idx = int(ss[0].strip()) score = float(ss[1].strip()) u_fea_dict[idx] = score # step 5: 获取物品的特征 ,循环遍历刚刚得到itemid,判断item是否在item特征中,若在开始进行处理 rec_list = [] for itemid in rec_item_mergeall: if itemid in item_fea_dict: item_fea = item_fea_dict[itemid] i_fea_dict = dict() for fea_idx in item_fea.strip().split(' '): ss = fea_idx.strip().split(':') if len(ss) != 2: continue idx = int(ss[0].strip()) score = float(ss[1].strip()) i_fea_dict[idx] = score #得到召回item对应的特征和用户的特征,之后根据模型求出来的w,b,进行打分 wx_score = 0. #这里我们求个内积,wx,然后做sigmoid,先将两个字典拼接起来,然后计算分数 for fea, score in dict(u_fea_dict.items() + i_fea_dict.items()).items(): wx_score += (score * model_w_list[fea]) #计算sigmoid: 1 / (1 + exp(-wx)) final_rec_score = 1 / (1 + math.exp(-(wx_score + model_b))) #将itemid和分数放入列表中,方便后续排序 rec_list.append((itemid, final_rec_score)) # step 6 : 精排序(rank) rec_sort_list = sorted(rec_list, key=lambda x:x[1], reverse=True) # step 7 : 过滤(filter)取top10 rec_fitler_list = rec_sort_list[:10] # step 8 : 返回+包装(return),进行将itemid转换成name item_dict = {
} with open('../data/name_id.dict', 'r') as fd: for line in fd: raw_itemid, name = line.strip().split('\t') item_dict[raw_itemid] = name ret_list = [] for tup in rec_fitler_list: req_item_name = item_dict[req_itemid] item_name = item_dict[tup[0]] item_rank_score = str(tup[1]) ret_list.append(' -> '.join([req_item_name, item_name, item_rank_score])) ret = '\n'.join(ret_list) return ret class test: def GET(self): print web.input() return '222' if __name__ == "__main__": app.run()
七.推荐系统测试:
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/199316.html原文链接:https://javaforall.net
