⼀个⾄简推荐系统的实现(附源代码)
本⽂主要内容对⾮技术背景的⼈来说确实有点⽆趣,如果你不是技术⼈员,可以直接拖到底下看总结,还是有⼀点价值的。
相关旧⽂
其实本⽂核⼼价值没有脱离如上旧⽂,不过重新⽤实战案例说明⼀下,可能更有助于理解,并开放源代码。
在我的知识星球⾥,经常有新⼈问这样的问题,有什么星球是可以推荐的。这似乎是⼀个常见的新⼈话题。
嗯,最近跟知识星球合作,征询吴鲁加⽼板同意,获取了⼀些数据练⼿,简单实现了⼀下相关推荐。
先说推荐系统测试的结果,以 我个⼈的知识星球 为例,在这个推荐系统的测算中,相关度最⾼的星球是哪些呢?以下结果为程序测算,⽆任何⼈⼯⼲预和修正。
相关度第⼀名,是亦仁的 “⽣财有术”。
相关度第⼆名,是冯⼤辉⽼师的“⼩道消息”。
相关度第三名,是冯⼤辉⽼师的”⼩程序淘⾦“。
相关度第四名,抖⾳红利研究⼩组,嗯,貌似圈主是在我的知识星球⾥做过推⼴。
相关度第五名,余弦的慢雾区。
相关度第六名,是鞠海深的"创业直播间",他曾经在我的知识星球⾥⾮常活跃,他的知识星球过半数⽤户与我的知识星球的重合。
相关度第七名,硅⾕⼥⽹红 angela zhu的 ”嘀嗒嘀嗒“。
相关度第⼋名,海外营销交流圈。
相关度第九名,明⽩⽼师的webscraper精进。
相关度第⼗名,池⽼师的 MacTalk的朋友们。
相关度第⼗⼀名,冯⼤辉⽼师的”招聘/求职/跳槽“。
相关度第⼗⼆名,刘⽼师的计算⼴告。
相关度第⼗三名,外贸协会。//讲外贸建站和营销的,星主也是我星球⾥⾮常活跃的。
相关度第⼗四名,投资⼈⼦柳。
相关度第⼗五名,WLJ的创业笔记。外贸建站与推广
后⾯就不罗列了。
这个结果,我觉得是⾮常符合预期的。
所谓符合预期,要特别说明的是,其实前⽂也提到过,在相关推荐的权值因素⾥,有两个最简单的数值代表了两个极端,⼀个是共同的推举数,也就是两个知识星球共同参与的⼈数,这个很容易理解,共同⼈数多是不是相关度就好呢,其实不是,如果按照共同⼈数来计算相关度,那么最⾼的⼀定是那些⼈数众多的热门免费,包括⼏个⽕爆的摄影和美图都会在这个榜单前列,这显然不是相关推荐理想的结果。 第⼆个极端是基于在对⽅星球的⼈数占⽐,如果对⽅星球的⼈数80%都出现在你的星球⾥,是不是相关度就极⾼了呢,但这样会出现⼀个很严重的问题,推荐出来的⼀定是⼀⼤堆⼈数稀少的⼩星球,有些⼏⼗⼈的星球绝⼤部分⽤户与我的星球⽤户重合,但其实价值不⼤,这些星球可能活跃度很差,⽽且星球没有什么号召⼒,所以这也不符合预期。
实际上我做过⼏轮不同权值的测试,最后发现,⼗⼏年前我在百度⽤的权值⼟⽅法,依然是效果最理想的,很好的兼顾了两个因素,既体现了⼆者的相关价值,⼜同时削弱了它们的极端影响。
但这个事情其实没结束,如果我作为⽤户,⽽⾮星主,我已经加⼊了30多个星球,那么系统会如何推荐给我其他未加⼊的呢。
基于我个⼈已加⼊星球的情况,计算出来推荐结果如下
1、池⽼师的 MacTalk的朋友们,不好意思,⼀直没有加⼊。
2、⼆爷书友会
3、KCon⿊客⼤会(嗯,我加⼊了灰袍技能,慢雾区这样的社)
4、抖⾳红利研究⼩组
5、每⽇运营热点案例
6、裂变增长实验室
7、鹅⼚公益笔记
8、我滴妈呀 (⼀个⼩社,完全不知道怎么关联出来的,哈哈)
9、创业直播间
10、新媒体玩法⼤全
当然,前⾯结果中很多星球我都已经加⼊了,这⾥有⼀些和前⾯结果明显不同,是因为这是基于我加⼊的所有星球来判断的,我觉得结果也还是⽐较满意的,出现了⼀些之前想不到的星球,但推荐本⾝不就是为了让你发现⾃⼰不知道⽽⼜可能感兴趣的东西么。
把结果放前⾯,是想证明,这个算法的效果,还是可以的。
那么,下⾯,代码展⽰,代码逻辑参见旧⽂
前置说明,数据准备,吴鲁加⽼师要求信息必须严格脱敏,本⽂章不涉及任何知识星球的数据结构和知识星球的数据获取⽅法。如何通过知识星球获取数据并整理的过程略,整理后的结果是我做相关推荐的中间结果,有这样⼏个数据结构。
gcount,每个星球的⼈数统计
格式为 : 组id - ⽤户⼈数
mcount,每个⽤户加⼊的星球数统计
格式为 :⽤户id - 加⼊星球数
nmlist,每个⽤户加⼊的星球列表
格式为 :⽤户id - 星球1,星球2,星球3,
glist,每个星球的⽤户列表
格式为:星球id - ⽤户1,⽤户2,⽤户3,
再次说明,以上为我整理的中间数据,⾮知识星球原始数据结构。
caoz_data 是我⾃建的数据分析库,⾮知识星球数据库,其中group_relate表⽤于存储相关关系。表结构为
id ⾃加1主键
fromgid 星球id
togid 相关星球id
sums 共同⽤户数
rights 相关权值 //⽣效的是这个,sums和nrights⽤于对⽐不同策略的效果
nrights 对⽐权值
相关推荐的数据⽣成代码,该代码完全基于中间数据,不涉及任何与知识星球系统交互的过程。
1 <?
2    set_time_limit(0);
3    error_reporting(E_ALL || ~E_NOTICE);
4    $now=time();
5    function getarr($fname)
6    {
7        $fd=fopen($fname,"r");
8        $seed=0;
9        while ($str=trim(fgets($fd)))
10        {
11            $arr=explode(" - ",$str);
12            $key=$arr[0];
13            $value=$arr[1];
14            $listarr[$key]=$value;
15            $seed++;
16 //            if ($seed>2000) break;
17        }
18        return ($listarr);
19    }
20    function putskip($now,$key)
21    {
22        $skip=time()-$now;
23        echo "$key  - $skip\n";
24    }
25    $garr=getarr("gcount");
26    $marr=getarr("mcount");
27    $mlarr=getarr("nmlist");
28    $glarr=getarr("glist");
29    putskip($now,"START");
30    require 'conn.php';
31    $seed=0;$seed2=0;
32    mysql_selectdb("caoz_data");
33    foreach ($glarr as $gid=>$value)
34    {
35        $seed++;
36        $seed2++;
37        $v=$garr[$gid];
38        if ($v<5 or $v>1000000) continue;
39        $listarr=explode(",",$value);
40        foreach ($listarr as $key2=>$mid)
41        {
42            if ($marr[$mid]==1 or $marr[$mid]>1000) continue;
43            $gidlist=explode(",",$mlarr[$mid]);
44            foreach ($gidlist as $key3=>$togid)
45            {
46                if (trim($togid)=="") continue;
47                if ($gid==$togid) continue;
48                if ($garr[$togid]>1000000) continue;
49                if ($garr[$togid]<2) continue;
50                $sumlist[$gid][$togid]++;
51                $tmpright=1/(sqrt($marr[$mid]+1)*sqrt($garr[$togid]+1));
52                $tmpright2=1/(50+$garr[$togid]);
53                $rlist[$gid][$togid]+=$tmpright;
54                $nrlist[$gid][$togid]+=$tmpright2;
55            }
56        }
57        $value=$rlist[$gid];
58        $sql="delete from group_relate where fromgid=$gid";
59        mysql_query($sql);
60
61        $tmpseed=0;
62        arsort($value);
63        foreach ($value as $togid=>$rights)
64        {
65            $tmpseed++;
66            $rights=$rlist[$gid][$togid];
67            $rights2=$nrlist[$gid][$togid];
68            if ($tmpseed==1)
69            {
70                $zright=$rights; //归⼀化
71                $zright2=$rights2;
72            }
73            $tmpsum=$sumlist[$gid][$togid];
74            if ($tmpseed>30) break;
75            if ($tmpsum<2) continue;
76            $rights=$rights/$zright*10000;
77            $nrights=$rights2/$zright2*10000;